C++標準模板庫(STL):vector、deque和list
之所以把這幾個容器寫在一起,是因為他們都是序列式容器。
序列式容器以線性序列的方式儲存元素(線性結構)。它沒有對元素進行排序,元素的順序和儲存它們的順序相同。以下有幾種標準的序列容器,每種容器都具有不同的特性:
- vector<T>(向量容器)是一個長度可變的序列,用來存放 T 型別的物件。必要時,可以自動增加容量,但只能在序列的末尾高效地增加或刪除元素。
- deque<T>(雙向佇列容器)是一個長度可變的、可以自動增長的序列,在序列的兩端都不能高效地增加或刪除元素。
- list<T>(連結串列容器)是一個長度可變的、由 T 型別物件組成的序列,它以雙向連結串列的形式組織元素,在這個序列的任何地方都可以高效地增加或刪除元素。訪問容器中任意元素的速度要比前三種容器慢,這是因為 list<T> 必須從第一個元素或最後一個元素開始訪問,需要沿著連結串列移動,直到到達想要的元素。
vector
vector<T> 容器可以方便、靈活地代替陣列。在大多數時候,都可以用 vector<T> 代替陣列存放元素。vector<T> 在擴充套件容量,以及在序列內部刪除或新增元素時會產生一些開銷,效率低下;但大多數情況下,不會明顯變慢。 為了使用 vector<T> 容器模板,需要在程式碼中包含標頭檔案 vector。 vector<T> 容器的大小可以自動增長,從而可以包含任意數量的元素。只要元素個數超出 vector 當前容量,就會自動分配更多的空間。只能在容器尾部高效地刪除或新增元素(在中間新增和刪除並不高效)。
- 建立vector物件
vector<元素型別>物件名([容器大小],[元素初始值】);
vector<int> v1; //建立一個空的vector容器 ,物件名後面不能有括號
vector<string> v2(10, "abc"); //建立一個大小為10的容器物件,並把這十個元素全部賦值abc
vector<string> v3(v2); //用v2來初始化v3
vector<int> v4{ 1,2,3,6 };
- 獲取容器容量和大小
分配大小(如果通過呼叫 reserve() 來增加記憶體,任何現有的迭代器,例如開始迭代器和結束迭代器,都會失效,所以需要重新生成它們。這是因為,為了增加容器的容量,vector<T> 容器的元素可能已經被複制或移到了新的記憶體地址。一般不用自己擴容,因為這是自動的。)
v1.reserve(20);
獲取容器容量和大小(一般容量比大小大)
容器容量:int capacity=v1.capacity();
容器大小:int size=v.size();
- 賦值函式
可以用成員函式assign()來給容器賦值。
//將5個3賦值給v1
v1.assign(5, 3);
//將[begin,end)區間中的元素賦值給容器,即[a,a+5)不包括a+5
int a[5] = { 1,2,3,5,6 };
v4.assign(a, a + 5);
- 訪問元素
//兩種訪問方式
cout<<v1[1];
cout << v1.at(1);
- 對首元素和末元素的操作
v4.push_back(2); //在容器末尾新增元素2
v4.pop_back(); //刪除容器最後一個元素,但並不會返回這個元素值
v4.front()=2; //獲取容器第一個元素的引用,可對第一個元素進行操作
v4.back()=2; //獲取容器最後一個元素的引用,可對最後一個元素進行操作
v4.begin(); //獲得指向首元素的迭代器(可理解為指標)
v4.end(); //獲得指向末元素後一個元素的迭代器(其實這個元素並不存在)
- 插入和刪除(中間元素)
insert()和erase()函式的pos位置只能用迭代器begin()和end()來指示,不能用數字作為引數。
v4.insert(pos, elem); //在pos位置上插入元素elem,返回該資料的位置(指向該位置的迭代器)
v4.insert(pos, n, elem);//在pos位置上插入n個elem元素,無返回值
v4.insert(pos, begin, end);//在pos位置上插入[begin,end)區間的資料,無返回值
v4.erase(pos); //移除pos位置上的元素,返回下一個元素的迭代器
v4.erase(begin,end) //移除[begin,end)區間的資料,返回下一個元素的迭代器
deque(雙端佇列)
deque<T>,一個定義在 deque 標頭檔案中的容器模板,可以生成包含 T 型別元素的容器,它以雙端佇列的形式組織元素。可以在容器的頭部和尾部高效地新增或刪除物件,這是它相對於 vector 容器的優勢。當需要這種功能時,可以選擇這種型別的容器。無論何時,當應用包含先入先出的事務處理時,都應該使用 deque 容器。處理資料庫事務或模擬一家超市的結賬佇列,像這兩種應用都可以充分利用 deque 容器。
deque的建立物件、賦值、訪問和對首末元素的操作和vector一模一樣(front()函式依舊是返回引用)。
對於插入刪除也和vector一樣,但是deque增加了以下函式。
v1.push_front(1); //在雙向佇列最前面新增元素
v1.pop_back(); //刪除最後一個元素
v1.pop_front(); //刪除第一個元素
list(雙向連結串列)
list<T> 容器模板定義在 list 標頭檔案中,是 T 型別物件的雙向連結串列。 list 容器具有一些 vector 和 deque 容器所不具備的優勢,它可以在常規時間內,在序列已知的任何位置插入或刪除元素。這是我們使用 list,而不使用 vector 或 deque 容器的主要原因。 list 的缺點是無法通過位置來直接訪問序列中的元素,也就是說,不能索引元素。為了訪問 list 內部的一個元素,必須一個一個地遍歷元素,通常從第一個元素或最後一個元素開始遍歷。
list<T> 容器的每個 T 物件通常都被包裝在一個內部節點物件中,節點物件維護了兩個指標,一個指向前一個節點,另一個指向下一個節點。這些指標將節點連線成一個連結串列。通過指標可以從任何位置的任一方向來遍歷連結串列中的元素。第一個元素的前向指標總是為 null,因為它前面沒有元素,尾部元素的後向指標也總為 null。這使我們可以檢測到連結串列的尾部。list<T> 例項儲存了頭部和尾部的指標。這允許我們從兩端訪問連結串列,也允許從任一端開始按順序檢索列表中的元素。 可以用和其他序列容器相同的方式,來獲取 list 容器的迭代器。因為不能隨機訪問 list 中的元素,獲取到的迭代器都是雙向迭代器。以 list 為引數,呼叫 begin() 可以得到指向 list 中第一個元素的迭代器。通過呼叫 end(),可以得到一個指向最後一個元素下一個位置的迭代器,因此像其他序列容器一樣,可以用它們來指定整個範圍的元素。也可以像其他容器一樣,使用 rbegin()、rend()、crbegin()、crend()、cbegin()、cend() 來獲取反向迭代器和 const 迭代器。
- 建立物件、賦值
和vector、deque一樣。
- 元素訪問和首尾元素的操作
一樣和deque有front()、back()、push_back()、push_front()、pop_back()、pop_front()。
但是不能像前面兩個容器那樣直接用[]和at()訪問其中的元素。
- 插入和刪除
insert()和erase()和前面一樣,只不過多了一個remove()函式。
list.remove(elem); //從容器中刪除所有與elem匹配的元素
list一些函式的應用(成員函式,不需要包含在algorithm標頭檔案中)
- merge()合併函式
list 的成員函式 merge() 以另一個具有相同型別元素的 list 容器作為引數。兩個容器中的元素都必須是升序。引數 list 容器中的元素會被合併到當前的 list 容器中。合併後另一個容器就為空了。
list<int> lt1{1,2,3 };//必須為升序
list<int> lt2{ 8,9,10};//必須為升序
lt1.merge(lt2);
for (auto lt = lt1.begin(); lt != lt1.end(); lt++)
{
cout << *lt<<" ";
}//最後輸出的也是升序的
- sort()排序函式
sort() 函式模板定義在標頭檔案 algorithm 中,要求使用隨機訪問迭代器。但 list 容器並不提供隨機訪問迭代器(沒有lt.begin()+2這樣的操作)只提供雙向迭代器,因此不能對 list 中的元素使用 sort() 演算法。但是,還是可以進行元素排序,因為 list 模板定義了自己的 sort() 函式。
lt.sort(); //從小到大排序
- splice()合併函式
上面提到的merge()函式雖可以合併,但是合併後進行了排序。有的時候不希望改變原有元素的位置,只想首尾相接。
list<int> lt1{1,5,6 }; //不一定要是升序
list<int> lt2{ 8,3,10};
lt1.splice(lt1.end(), lt2); //將lt2插入到lt1後
lt1.splice(lt1.begin(), lt2); //將lt2插入到lt1前