1. 程式人生 > >C++ vector的用法及algorithm.h中常用函式

C++ vector的用法及algorithm.h中常用函式

c++ vector用法

C++內建的陣列支援容器的機制,但是它不支援容器抽象的語義。要解決此問題我們自己實現這樣的類。在標準C++中,用容器向量(vector)實現。容器向量也是一個類模板。

標準庫vector型別使用需要的標頭檔案:#include <vector>。vector 是一個類模板。不是一種資料型別,vector<int>是一種資料型別。Vector的儲存空間是連續的,list不是連續儲存的。

一、 定義和初始化

vector< typeName > v1;       //預設v1為空,故下面的賦值是錯誤的v1[0]=5;
vector<typeName>v2(v1); 或v2=v1;或vector<typeName> v2(v1.begin(), v1.end());//v2是v1的一個副本,若v1.size()>v2.size()則賦值後v2.size()被擴充為v1.size()。
vector< typeName > v3(n,i);//v3包含n個值為i的typeName型別元素
vector< typeName > v4(n); //v4含有n個值為0的元素
int a[4]={0,1,2,3,3}; vector<int> v5(a,a+5);//v5的size為5,v5被初始化為a的5個值。後一個指標要指向將被拷貝的末元素的下一位置。
vector<int> v6(v5);//v6是v5的拷貝
vector< 型別 > 識別符號(最大容量,初始所有值);

二、 值初始化

1>     如果沒有指定元素初始化式,標準庫自行提供一個初始化值進行值初始化。
2>     如果儲存的式含有建構函式的類型別的元素,標準庫使用該型別的建構函式初始化。
3>     如果儲存的式沒有建構函式的類型別的元素,標準庫產生一個帶初始值的物件,使用這個物件進行值初始化。

三、vector物件最重要的幾種操作

1. v.push_back(t)    在容器的最後新增一個值為t的資料,容器的size變大。
                     另外list有push_front()函式,在前端插入,後面的元素下標依次增大。
2. v.size()        返回容器中資料的個數,size返回相應vector類定義的size_type的值。v.resize(2*v.size)或                   
v.resize(2*v.size, 99) 將v的容量翻倍(並把新元素的值初始化為99)
3. v.empty()     判斷vector是否為空
4. v[n]           返回v中位置為n的元素
5. v.insert(pointer,number, content)    向v中pointer指向的位置插入number個content的內容。
                  還有v. insert(pointer, content),v.insert(pointer,a[2],a[4])將a[2]到a[4]三個元素插入。
6. v.pop_back()    刪除容器的末元素,並不返回該元素。
7.v.erase(pointer1,pointer2) 刪除pointer1到pointer2中間(包括pointer1所指)的元素。
                   vector中刪除一個元素後,此位置以後的元素都需要往前移動一個位置,雖然當前迭代器位置沒有自動加1,
                   但是由於後續元素的順次前移,也就相當於迭代器的自動指向下一個位置一樣。
8. v1==v2          判斷v1與v2是否相等。
9. !=、<、<=、>、>=      保持這些操作符慣有含義。
10. vector<typeName>::iterator p=v1.begin( ); p初始值指向v1的第一個元素。*p取所指向元素的值。
                    對於const vector<typeName>只能用vector<typeName>::const_iterator型別的指標訪問。
11.   p=v1.end( ); p指向v1的最後一個元素的下一位置。
12.v.clear()      刪除容器中的所有元素。12.v.clear()      刪除容器中的所有元素。
#include<algorithm>中的泛函演算法
搜尋演算法:find() 、search() 、count() 、find_if() 、search_if() 、count_if() 
分類排序:sort() 、merge() 
刪除演算法:unique() 、remove() 
生成和變異:generate() 、fill() 、transformation() 、copy() 
關係演算法:equal() 、min() 、max() 
sort(v1.begin(),vi.begin()+v1.size/2); 對v1的前半段元素排序
list<char>::iterator pMiddle =find(cList.begin(),cList.end(),'A');找到則返回被查內容第一次出現處指標,否則返回end()。
vector< typeName >::size_type x ; vector< typeName >型別的計數,可用於迴圈如同for(int i)
初學C++的程式設計師可能會認為vector的下標操作可以新增元素,其實不然:
vector<int> ivec;   // empty vector
for (vector<int>::size_type ix = 0; ix != 10; ++ix)
     ivec[ix] = ix; // disaster: ivec has no elements
上述程式試圖在ivec中插入10個新元素,元素值依次為0到9的整數。但是,這裡ivec是空的vector物件,而且下標只能用於獲取已存在的元素。
這個迴圈的正確寫法應該是:
for (vector<int>::size_type ix = 0; ix != 10; ++ix)
     ivec.push_back(ix); // ok: adds new element with value ix
警告:必須是已存在的元素才能用下標操作符進行索引。通過下標操作進行賦值時,不會新增任何元素。僅能對確知已存在的元素進行下標操作  
   
四、記憶體管理與效率

      1.使用reserve()函式提前設定容量大小,避免多次容量擴充操作導致效率低下。
        關於STL容器,最令人稱讚的特性之一就是是隻要不超過它們的最大大小,它們就可以自動增長到足以容納你放進去的資料。(要知道這個最大值,只要呼叫名叫max_size的成員函式。)對於vector和string,如果需要更多空間,就以類似realloc的思想來增長大小。vector容器支援隨機訪問,因此為了提高效率,它內部使用動態陣列的方式實現的。在通過 reserve() 來申請特定大小的時候總是按指數邊界來增大其內部緩衝區。當進行insert或push_back等增加元素的操作時,如果此時動態陣列的記憶體不夠用,就要動態的重新分配當前大小的1.5~2倍的新記憶體區,再把原陣列的內容複製過去。所以,在一般情況下,其訪問速度同一般陣列,只有在重新分配發生時,其效能才會下降。正如上面的程式碼告訴你的那樣。而進行pop_back操作時,capacity並不會因為vector容器裡的元素減少而有所下降,還會維持操作之前的大小。對於vector容器來說,如果有大量的資料需要進行push_back,應當使用reserve()函式提前設定其容量大小,否則會出現許多次容量擴充操作,導致效率低下。
      reserve成員函式允許你最小化必須進行的重新分配的次數,因而可以避免真分配的開銷和迭代器/指標/引用失效。但在我解釋reserve為什麼可以那麼做之前,讓我簡要介紹有時候令人困惑的四個相關成員函式。在標準容器中,只有vector和string提供了所有這些函式。
(1) size()告訴你容器中有多少元素。它沒有告訴你容器為它容納的元素分配了多少記憶體。 
(2) capacity()告訴你容器在它已經分配的記憶體中可以容納多少元素。那是容器在那塊記憶體中總共可以容納多少元素,而不是還可以容納多少元素。如果你想知道一個vector或string中有多少沒有被佔用的記憶體,你必須從capacity()中減去size()。如果size和capacity返回同樣的值,容器中就沒有剩餘空間了,而下一次插入(通過insert或push_back等)會引發上面的重新分配步驟。 
(3) resize(Container::size_type n)強制把容器改為容納n個元素。呼叫resize之後,size將會返回n。如果n小於當前大小,容器尾部的元素會被銷燬。如果n大於當前大小,新預設構造的元素會新增到容器尾部。如果n大於當前容量,在元素加入之前會發生重新分配。 
(4) reserve(Container::size_type n)強制容器把它的容量改為至少n,提供的n不小於當前大小。這一般強迫進行一次重新分配,因為容量需要增加。(如果n小於當前容量,vector忽略它,這個呼叫什麼都不做,string可能把它的容量減少為size()和n中大的數,但string的大小沒有改變。在我的經驗中,使用reserve來從一個string中修整多餘容量一般不如使用“交換技巧”,那是條款17的主題。)
     這個簡介表示了只要有元素需要插入而且容器的容量不足時就會發生重新分配(包括它們維護的原始記憶體分配和回收,物件的拷貝和析構和迭代器、指標和引用的失效)。所以,避免重新分配的關鍵是使用reserve儘快把容器的容量設定為足夠大,最好在容器被構造之後立刻進行。
例如,假定你想建立一個容納1-1000值的vector<int>。沒有使用reserve,你可以像這樣來做:

vector<int> v;
for (int i = 1; i <= 1000; ++i) v.push_back(i);

在大多數STL實現中,這段程式碼在迴圈過程中將會導致2到10次重新分配。(10這個數沒什麼奇怪的。記住vector在重新分配發生時一般把容量翻倍,而1000約等於210。)
把程式碼改為使用reserve,我們得到這個:

vector<int> v;
v.reserve(1000);
for (int i = 1; i <= 1000; ++i) v.push_back(i);

這在迴圈中不會發生重新分配。
在大小和容量之間的關係讓我們可以預言什麼時候插入將引起vector或string執行重新分配,而且,可以預言什麼時候插入會使指向容器中的迭代器、指標和引用失效。例如,給出這段程式碼,

string s;
...
if (s.size() < s.capacity()) {
s.push_back('x');
}

push_back的呼叫不會使指向這個string中的迭代器、指標或引用失效,因為string的容量保證大於它的大小。如果不是執行push_back,程式碼在string的任意位置進行一個insert,我們仍然可以保證在插入期間沒有發生重新分配,但是,與伴隨string插入時迭代器失效的一般規則一致,所有從插入位置到string結尾的迭代器/指標/引用將失效。
回到本條款的主旨,通常有兩情況使用reserve來避免不必要的重新分配。第一個可用的情況是當你確切或者大約知道有多少元素將最後出現在容器中。那樣的話,就像上面的vector程式碼,你只是提前reserve適當數量的空間。第二種情況是保留你可能需要的最大的空間,然後,一旦你新增完全部資料,修整掉任何多餘的容量。
       2.使用“交換技巧”來修整vector過剩空間/記憶體
      有一種方法來把它從曾經最大的容量減少到它現在需要的容量。這樣減少容量的方法常常被稱為“收縮到合適(shrink to fit)”。該方法只需一條語句:vector<int>(ivec).swap(ivec);
表示式vector<int>(ivec)建立一個臨時vector,它是ivec的一份拷貝:vector的拷貝建構函式做了這個工作。但是,vector的拷貝建構函式只分配拷貝的元素需要的記憶體,所以這個臨時vector沒有多餘的容量。然後我們讓臨時vector和ivec交換資料,這時我們完成了,ivec只有臨時變數的修整過的容量,而這個臨時變數則持有了曾經在ivec中的沒用到的過剩容量。在這裡(這個語句結尾),臨時vector被銷燬,因此釋放了以前ivec使用的記憶體,收縮到合適。
     3.用swap方法強行釋放STL Vector所佔記憶體

template < class T> void ClearVector( vector<T>& v )
{ 
    vector<T>vtTemp;
    vtTemp.swap( v );
}

    vector<int> v ;
    nums.push_back(1);
    nums.push_back(3);
    nums.push_back(2);
    nums.push_back(4);
    vector<int>().swap(v); 
/* 或者v.swap(vector<int>()); */
/*或者{ std::vector<int> tmp = v;   v.swap(tmp);   }; //加大括號{ }是讓tmp退出{ }時自動析構*/


五、Vector 記憶體管理成員函式的行為測試

C++ STL的vector使用非常廣泛,但是對其記憶體的管理模型一直有多種猜測,下面用例項程式碼測試來了解其記憶體管理方式,測試程式碼如下:

#include <iostream>
#include <vector>
using namespace std;
int main()
{
vector<int> iVec;
cout << "容器 大小為: " << iVec.size() << endl;
cout << "容器 容量為: " << iVec.capacity() << endl; //1個元素, 容器容量為1
iVec.push_back(1);
cout << "容器 大小為: " << iVec.size() << endl;
cout << "容器 容量為: " << iVec.capacity() << endl; //2個元素, 容器容量為2
iVec.push_back(2);
cout << "容器 大小為: " << iVec.size() << endl;
cout << "容器 容量為: " << iVec.capacity() << endl; //3個元素, 容器容量為4
iVec.push_back(3);
cout << "容器 大小為: " << iVec.size() << endl;
cout << "容器 容量為: " << iVec.capacity() << endl; //4個元素, 容器容量為4
iVec.push_back(4);
iVec.push_back(5);
cout << "容器 大小為: " << iVec.size() << endl;
cout << "容器 容量為: " << iVec.capacity() << endl; //5個元素, 容器容量為8
iVec.push_back(6);
cout << "容器 大小為: " << iVec.size() << endl;
cout << "容器 容量為: " << iVec.capacity() << endl; //6個元素, 容器容量為8
iVec.push_back(7);
cout << "容器 大小為: " << iVec.size() << endl;
cout << "容器 容量為: " << iVec.capacity() << endl; //7個元素, 容器容量為8
iVec.push_back(8);
cout << "容器 大小為: " << iVec.size() << endl;
cout << "容器 容量為: " << iVec.capacity() << endl; //8個元素, 容器容量為8
iVec.push_back(9);
cout << "容器 大小為: " << iVec.size() << endl;
cout << "容器 容量為: " << iVec.capacity() << endl; //9個元素, 容器容量為16
/* vs2005/8 容量增長不是翻倍的,如 
  9個元素  容量9 
  10個元素 容量13 */
/* 測試effective stl中的特殊的交換 swap() */
cout << "當前vector 的大小為: " << iVec.size() << endl;
cout << "當前vector 的容量為: " << iVec.capacity() << endl;
vector<int>(iVec).swap(iVec);
cout << "臨時的vector<int>物件 的大小為: " << (vector<int>(iVec)).size() << endl;
cout << "臨時的vector<int>物件 的容量為: " << (vector<int>(iVec)).capacity() << endl;
cout << "交換後,當前vector 的大小為: " << iVec.size() << endl;
cout << "交換後,當前vector 的容量為: " << iVec.capacity() << endl;
return 0;
}


六、vector的其他成員函式

c.assign(beg,end)
將[beg; end)區間中的資料賦值給c。
c.assign(n,elem)
將n個elem的拷貝賦值給c。 
c.at(idx)
傳回索引idx所指的資料,如果idx越界,丟擲out_of_range。 
c.back()
傳回最後一個數據,不檢查這個資料是否存在。
c.front()
傳回地一個數據。 
get_allocator
使用建構函式返回一個拷貝。 
c.rbegin()
傳回一個逆向佇列的第一個資料。 
c.rend()
傳回一個逆向佇列的最後一個數據的下一個位置。 
c.~ vector <Elem>()
銷燬所有資料,釋放記憶體。  

以下是其它網友的補充:

1 、基本操作

(1)標頭檔案#include<vector>.

(2)建立vector物件,vector<int> vec;

(3)尾部插入數字:vec.push_back(a);

(4)使用下標訪問元素,cout<<vec[0]<<endl;記住下標是從0開始的。

(5)使用迭代器訪問元素.

vector<int>::iterator it;
for(it=vec.begin();it!=vec.end();it++)
  cout<<*it<<endl;


(6)插入元素:    vec.insert(vec.begin()+i,a);在第i+1個元素前面插入a;

(7)刪除元素:    vec.erase(vec.begin()+2);刪除第3個元素

vec.erase(vec.begin()+i,vec.end()+j);刪除區間[i,j-1];區間從0開始

(8)向量大小:vec.size();

(9)清空:vec.clear();

2、vector的元素不僅僅可以使int,double,string,還可以是結構體,但是要注意:結構體要定義為全域性的,否則會出錯。下面是一段簡短的程式程式碼:

#include<stdio.h>
#include<algorithm>
#include<vector>
#include<iostream>
using namespace std;

typedef struct rect
{
 int id;
 int length;
 int width;

  //對於向量元素是結構體的,可在結構體內部定義比較函式,下面按照id,length,width升序排序。
  bool operator< (const rect &a) const
 {
  if(id!=a.id)
   return id<a.id;
  else
  {
   if(length!=a.length)
    return length<a.length;
   else
    return width<a.width;
  }
 }
}Rect;

int main()
{
 vector<Rect> vec;
 Rect rect;
 rect.id=1;
 rect.length=2;
 rect.width=3;
 vec.push_back(rect);
 vector<Rect>::iterator it=vec.begin();
 cout<<(*it).id<<' '<<(*it).length<<' '<<(*it).width<<endl; 

return 0;

}


3  演算法

(1) 使用reverse將元素翻轉:需要標頭檔案#include<algorithm>

reverse(vec.begin(),vec.end());將元素翻轉(在vector中,如果一個函式中需要兩個迭代器,

一般後一個都不包含.)

(2)使用sort排序:需要標頭檔案#include<algorithm>,

sort(vec.begin(),vec.end());(預設是按升序排列,即從小到大).

可以通過重寫排序比較函式按照降序比較,如下:

定義排序比較函式:

bool Comp(const int &a,const int &b)
{
    return a>b;
}

呼叫時:sort(vec.begin(),vec.end(),Comp),這樣就降序排序。

轉載自:http://www.jb51.net/article/44234.htm