- 順序容器型別
| 型別 | 解釋 |
|---|---|
| vector | 可變大小陣列,支持快速隨機訪問,在尾部之外的位置插入或洗掉元素可能很慢 |
| deque | 雙端佇列,支持快速隨機訪問,在頭尾位置插入、洗掉速度很快 |
| list | 雙向鏈表,只支持雙向順序訪問,在list中任何位置進行插入、洗掉操作速度都很快 |
| forward_list | 單向鏈表,只支持單向順序訪問,在鏈表任何位置進行插入、洗掉操作速度都很快 |
| array | 固定大小陣列,支持快速隨機訪問,不能添加或洗掉元素 |
| string | 與vector相似的容器,但專門用于保存字符,隨機訪問快,在尾部插入、洗掉速度快, |
string和vector將元素保存在連續的記憶體空間中,由元素的下標來計算其地址是非常快速的,但在中間位置添加或洗掉元素就會非常耗時,list和forward_list兩個容器的設計目的是令容器任何位置的添加和洗掉操作都很快速,作為代價,這兩個容器不支持元素的隨機訪問,并且相比vector、deque、array,這兩個容器的額外記憶體開銷也很大,deque支持快速的隨機訪問,在deque的兩端添加或洗掉元素都是很快的,與list或forward_list添加洗掉元素的速度相當,array物件的大小是固定的,因此,array不支持添加和洗掉元素以及改變容器大小的操作,forward_list的設計目標是達到與最好的手寫的單向鏈表資料結構相當的性能,因此,forward_list沒有size操作,因為保存或計算其大小就會比手寫鏈表多出額外的開銷,對其他容器而言,size保證是一個快速的常量時間的操作,- 以下是一些選擇容器的基本原則:
- 除非你有很好的理由選擇其他容器,否則應使用vector,
- 如果你的程式有很多小的元素,且空間的額外開銷很重要,則不要使用list或forward_list,
- 如果程式要求隨機訪問元素,應使用vector或deque,
- 如果程式要求在容器的中間插入或洗掉元素,應使用list或forward_list,
- 如果程式需要在頭尾位置插入或洗掉元素,但不會在中間位置進行插入或洗掉操作,則使用deque,
- 如果程式只有在讀取輸入時才需要在容器中間位置插入元素,隨后需要隨機訪問元素,則
- 首先,確定是否真的需要在容器中間位置添加元素,當處理輸入資料時,通常可以很容易地向vector追加資料,然后再呼叫標準庫的sort函式來重排容器中的元素,從而避免在中間位置添加元素,
- 如果必須在中間位置插入元素,考慮在輸入階段使用list,一旦輸入完成,將list中的內容拷貝到一個vector中,
- 如果你不確定應該使用哪種容器,那么可以在程式中只使用vector和list公共的操作:使用迭代器,不使用下標操作,避免隨機訪問,這樣,在必要時選擇使用vector或list都很方便,
- 容器型別上的操作形成了一種層次:
- 某些操作是所有容器型別都提供的,
- 另外一些操作僅針對順序容器、關聯容器或無序容器,
- 還有一些操作只適用于一小部分容器,
- 順序容器建構式的一個版本接受容器大小引數,它使用了元素型別的默認建構式,但某些類沒有默認建構式,我們可以定義一個保存這種型別物件的容器,但我們在構造這種容器時不能只傳遞給它一個元素數目引數:
// 假定noDefault是一個沒有默認建構式的型別 vector<noDefault> v1(10, init); // 正確:提供了元素初始化器 vector<noDefault> v2(10); // 錯誤:必須提供一個元素初始化器 - 容器操作:
| 型別別名 | 解釋 |
|---|---|
| iterator | 此容器型別的迭代器型別 |
| const_iterator | 可以讀取元素,但不能修改元素的迭代器型別 |
| size_type | 無符號整數型別,足夠保存此種容器型別最大可能容器的大小 |
| difference_type | 帶符號整數型別,足夠保存兩個迭代器之間的距離 |
| value_type | 元素型別 |
| reference | 元素的左值型別;與value_type&含義相同 |
| const_reference | 元素的const左值型別(即,const value_type&) |
| 建構式 | 解釋 |
|---|---|
| C c; | 默認建構式,構造空容器 |
| C c1(c2); | 構造c2的拷貝c1 |
| C c(b,e); | 構造c,將迭代器b和e指定的范圍內的元素內的元素拷貝到c |
| C c{a,b,c...}; | 串列初始化c |
| 賦值與swap | 解釋 |
|---|---|
| c1=c2 | 將c1中的元素替換為c2中元素 |
| c1={a,b,c...} | 將c1中的元素替換為串列中元素 |
| a.swap(b) | 交換a和b的元素 |
| swap(a,b) | 與a.swap(b)等價 |
| 大小 | 解釋 |
|---|---|
| c.size() | c中元素的數目 |
| c.max_size() | c可保存的最大元素數目 |
| c.empty() | 若c中儲存了元素,回傳false,否則回傳true |
| 添加、洗掉元素 | 解釋 |
|---|---|
| c.insert(args) | 將args中的元素拷貝進c |
| c.emplace(inits) | 使用inits構造c中的一個元素 |
| c.erase(args) | 洗掉args指定的元素 |
| c.clear() | 洗掉c中的所有元素,回傳void |
| 關系運算子 | 解釋 |
|---|---|
| ==,!= | 所有容器都支持相等(不等)運算子 |
| <,<=,>,>= | 洗掉c中的所有元素,回傳void |
| 獲取迭代器 | 解釋 |
|---|---|
| c.begin(),c.end() | 回傳指向c的首元素和尾元素之后位置的迭代器 |
| c.cbegin(),c.cend() | 回傳const_iterator |
| 反向容器的額外成員(不支持forward_list) | 解釋 |
|---|---|
| reverse_iterator | 按逆序尋址元素的迭代器 |
| const_reverse_iterator | 不能修改元素的逆序迭代器 |
| c.rbegin(),c.rend() | 回傳指向c的尾元素和首元素之前位置的迭代器 |
| c.crbegin(),c.crend() | 回傳const_reverse_iterator |
-
forward_list迭代器不支持遞減運算子(--),
-
一個迭代器范圍由一對迭代器表示,兩個迭代器分別指向同一個容器中的元素或者是尾元素之后的位置,
-
迭代器范圍中的元素包含first所表示的元素以及從first開始直至last(但不包含last)之間的所有元素,左閉合區間:[left, right),
-
如果滿足如下條件,兩個迭代器begin和end構成一個迭代器范圍:
- 它們指向同一個容器中的元素,或者是容器最后一個元素之后的位置,且
- 我們可以通過反復遞增begin來到達end,換句話說,end不在begin之前,
-
標準庫使用左閉合范圍是因為這種范圍有三種方便的性質,假定begin和end構成一個合法的迭代器范圍,則
- 如果begin與end相等,則范圍為空
- 如果begin與end不等,則范圍至少包含一個元素,且begin指向該范圍中的第一個元素
- 我們可以對begin遞增若干次,使得begin==end
-
這些性質意味著我們可以用回圈來處理一個元素范圍,
while (begin != end) { *begin = val; // 正確:范圍非空,因此begin指向一個元素 // 在while回圈中,可以安全地解參考begin,因為begin必然指向一個元素, ++begin; // 移動迭代器,獲取下一個元素 } -
反向迭代器就是一種反向遍歷容器的迭代器,與正向迭代器相比,各種操作的含義也都發生了顛倒,例如,對一個反向迭代器執行++操作,會得到上一個元素,
-
begin和end有多個版本:帶r的版本回傳反向迭代器;以c開頭的版本則回傳const迭代器;不以c開頭的函式都是被多載過的,一個是const成員,回傳容器的const_iterator型別;另一個是非常量成員,回傳容器的iterator型別,當我們對一個非常量物件呼叫這些成員時,得到的是回傳iterator的版本,只有在對一個const物件呼叫這些函式時,才會得到一個const版本,與const指標和參考類似,可以將一個普通的iterator轉換為對應的const_iterator,但反之不行,
-
當auto與begin或end結合使用時,獲得的迭代器型別依賴于容器型別,與我們想要如何使用迭代器毫不相干,但以c開頭的版本還是可以獲得const_iterator的,而不管容器的型別是什么,當不需要寫訪問時,應該使用cbegin和cend,
-
容器定義和初始化
| 方法 | 解釋 |
|---|---|
| C c; | 默認建構式,如果c是一個array,則c中元素按默認方式初始化;否則c為空 |
| C c1(c2) | c1初始化為c2的拷貝,c1和c2必須是相同型別(即,它們必須是相同的容器型別,且保存的是相同的元素型別;對于array型別,兩者還必須具有相同大小) |
| C c{a,b,c...}或C c={a,b,c...} | c初始化為初始化串列中元素的拷貝,串列中元素的型別必須與c的元素型別相容,對于array型別,串列中元素數目必須等于或小于array的大小,任何遺漏的元素都進行值初始化 |
| C c(b,e) | c初始化為迭代器b和e指定范圍中的元素的拷貝,范圍中元素的型別必須與C的元素的型別相容(array不適用) |
| —— | 只有順序容器(不包括array)的建構式才能接受大小引數 |
| C seq(n) | seq包含n個元素,這些元素進行了值初始化;此建構式是explicit的,(string不適用) |
| C seq(n,t) | seq包含n個初始化為值t的元素 |
-
當將一個容器初始化為另一個容器的拷貝時,兩個容器的容器型別和元素型別都必須相同,但當傳遞迭代器引數來拷貝一個范圍時,就不要求容器型別是相同的了,而且,新容器和原容器中的元素型別也可以不同,只要能將要拷貝的元素轉換為要初始化的容器的元素型別即可(建構式只是讀取范圍中的元素并進行拷貝),
// 每個容器有三個元素,用給定的初始化器進行初始化 list<string> authors = {"Milton", "Shakespeare", "Austen"}; vector<const char*> articles = {"a", "an", "the"}; list<string> list2(authors); // 正確:型別匹配 deque<string> authList(authors); // 錯誤:容器型別不匹配 vector<string> words(articles); // 錯誤:容器型別必須匹配 // 正確:可以將const char*元素轉換為string forward_list<string> words(articles.begin(),articles.end()); -
如果元素型別是內置型別或者是具有默認建構式的型別別,可以只為建構式提供一個容器大小引數,如果元素型別沒有默認建構式,除了大小引數外,還必須指定一個顯示的元素初始值,
#include <iostream> #include <vector> using namespace std; class A { private: vector<int> data; public: A(vector<int>::iterator beg, vector<int>::iterator end) { data.assign(beg, end); } void display() { for (auto i : data) cout << i << " "; cout << endl; } }; int main() { vector<int> iv{1, 2, 3}; vector<A> one(5, A(iv.begin(), iv.end())); for (auto i : one) i.display(); cout << endl; return 0; } -
只有順序容器的建構式才接受大小引數,關聯容器并不支持,
-
標準庫array的大小也是型別的一部分,當定義一個array時,除了指定元素型別,我們必須同時指定元素型別和大小,
array<int, 42> // 型別為:保存42個int的陣列 array<string, 10> // 型別為:保存10個string的陣列 array<int, 10>::size_type i; // 陣列型別包括元素型別和大小 array<int>::size_type j; // 錯誤:array<int>不是一個型別 -
一個默認構造的array是非空的:它包含了于其大小一樣多的元素,這些元素都被默認初始化,就像一個內置陣列中的元素那樣,如果我們對array進行串列初始化,初始值的數目必須等于或小于array的大小,如果初始值數目小于array的大小,則它們被用來初始化array中靠前的元素,所有剩余元素都會進行值初始化,在這兩種情況下,如果元素型別是一個型別別,那么該類必須有一個默認建構式,以使值初始化能夠進行,
array<int, 10> ia1; // 10個默認初始化的int array<int, 10> ia2 = {0,1,2,3,4,5,6,7,8,9}; // 串列初始化 array<int, 10> ia3 = {42}; // ia3[0]為42,剩余元素為0 int digs[10] = {0,1,2,3,4,5,6,7,8,9}; int cpy[10] = digs; // 錯誤:內置陣列不支持拷貝或賦值 array<int, 10> digits = {0,1,2,3,4,5,6,7,8,9}; array<int, 10> copy = digits; // 正確:只要陣列型別匹配即合法 -
與其他容器一樣,array也要求初始值的型別必須與要創建的容器型別相同,此外,array還要求元素型別和大小也都一樣,因為大小是array型別的一部分,
-
與賦值相關的運算子可用于所有容器,賦值運算子將其左邊容器中的全部元素替換為右邊容器中元素的拷貝,如果兩個容器原來大小不同,賦值運算后兩者的大小都與右邊容器的原大小相同,
c1 = c2; // 將c1的內容替換為c2中元素的拷貝 c1 = {a,b,c}; // 賦值后,c1大小為3 -
與內置陣列不同,標準庫array型別允許賦值,賦值號左右兩邊的運算物件必須具有相同的型別,由于右邊運算物件的大小可能與左邊運算物件的大小不同,因此array型別不支持assign,也不允許用花括號包圍的值串列進行賦值,
array<int, 10> a1 = {0,1,2,3,4,5,6,7,8,9}; array<int, 10> a2 = {0}; // 所有元素值均為0 a1 = a2; // 替換a1中的元素 a2 = {0}; // 錯誤:不能將一個花括號串列賦予陣列
| 容器賦值運算 | 解釋 |
|---|---|
| c1=c2 | 將c1中的元素替換為c2中元素的拷貝,c1和c2必須具有相同的型別 |
| c={a,b,c...} | 將c1中元素替換為初始化串列中元素的拷貝(array不適用) |
| swap(c1,c2) | 交換c1和c2中的元素,c1和c2必須具有相同的型別,swap通常比從c2向c1拷貝元素快得多 |
| —— | assign操作不適用于關聯容器和array |
| seq.assign(b,e) | 將seq中的元素替換為迭代器b和e所表示的范圍中的元素,迭代器b和e不能指向seq中的元素 |
| seq.assign(il) | 將seq中的元素替換為初始化串列il(initializer_list)中的元素 |
| seq.assign(n,t) | 將seq中的元素替換為n個值為t的元素 |
-
賦值相關運算會導致指向左邊容器內部的迭代器、參考和指標失效,而swap操作將容器內容交換不會導致指向容器的迭代器、參考和指標失效(容器型別為array和string的情況除外),
-
順序容器的assign成員允許我們從一個不同但相容的型別賦值,或者從容器的一個子序列賦值,assign操作用引數所指定的元素(的拷貝)替換左邊容器中的所有元素,
list<string> names; vector<const char*> oldstyle; names = oldstyle; // 錯誤:容器型別不匹配 // 正確:可以將const char*轉換為string names.assign(oldstyle.cbegin(), oldstyle.cend()); -
由于其舊元素被替換,因此傳遞給assign的迭代器不能指向呼叫assign的容器,
-
除array外,swap不對任何元素進行拷貝、洗掉或插入操作,因此可以保證在常數時間內完成,
-
除string外,指向容器的迭代器、參考和指標在swap操作之后都不會失效,它們仍指向swap操作之前所指向的那些元素,但是,在swap之后,這些元素已經屬于不同的容器了,對一個string呼叫swap會導致迭代器、參考和指標失效,
-
swap兩個array會真正交換它們的元素,因此,交換兩個array所需的時間與array中元素的數目成正比,
-
對于array,在swap操作之后,指標、參考和迭代器所系結的元素保持不變,但元素值已經與另一個array中對應元素進行了交換,
-
在標準庫中,容器既提供成員函式版本的swap,也提供非成員版本的swap,非成員版本的swap在泛型編程中是非常重要的,統一使用非成員版本的swap是一個好習慣,
-
成員函式size回傳容器中元素的數目;empty當size為0時回傳布林值true,否則回傳false;max_size回傳一個大于或等于該型別容器所能容納的最大元素數的值,forward_list支持max_size和empty,但不支持size,
-
每個容器型別都支持相等運算子(==和!=);除了無序關聯容器都支持關系運算子(>、>=、<、<=),關聯運算子左右兩邊的運算物件必須是相同型別的容器,且必須保存相同型別的元素,
-
比較兩個容器實際上是進行元素的逐對比較,這些運算子的作業方式與string的關系運算類似,
- 如果兩個容器具有相同大小且所有元素都兩兩對應相等,則這兩個容器相等;否則兩個容器不等,
- 如果兩個容器大小不同,但較小容器中每個元素都等于較大容器中的對應元素,則較小容器小于較大容器,
- 如果兩個容器都不是另一個容器的前綴子序列,則它們的比較結果取決于第一個不相等的元素的比較結果,
-
只有當其元素型別也定義了相應的比較運算子時,我們才可以使用關系運算子來比較兩個容器,
-
容器的相等運算子實際上是使用元素的==運算子實作比較的,而其他關系運算子是使用元素的<運算子,如果元素型別不支持所需運算子,那么保存這種元素的容器就不能使用相應的關系運算,
| 向順序容器添加元素的操作 | 解釋 |
|---|---|
| —— | 這些操作會改變容器的大小;array不支持這些操作 |
| —— | forward_list有自己專有版本的insert和emplace |
| —— | forward_list不支持push_back和emplace_back |
| —— | vector和string不支持push_front和emplace_front |
| c.push_back(t)或c.emplace_back(args) | 在c的尾部創建一個值為t或由args創建的元素,回傳void |
| c.push_front(t)或c.emplace_front(args) | 在c的頭部創建一個值為t或由args創建的元素,回傳void |
| c.insert(p,t)或c.emplace(p,args) | 在迭代器p指向的元素之前創建一個值為t或由args創建的元素,回傳指向新添加的元素的迭代器 |
| c.insert(p,n,t) | 在迭代器p指向的元素之前插入n個值為t的元素,回傳指向新添加的第一個元素的迭代器;若n為0,則回傳p |
| c.insert(p,b,e) | 將迭代器b和e指定的范圍內的元素插入到迭代器p指向的元素之前,b和e不能指向c中的元素(insert會破壞迭代器),回傳指向新添加的第一個元素的迭代器;若范圍為空,則回傳p |
| c.insert(p,il) | il是一個花括號包圍的元素值串列(initializer_list),將這些給定值插入到迭代器p指向的元素之前,回傳指向新添加的第一個元素的迭代器;若串列為空,則回傳p |
-
向一個vector、string或deque插入元素會使所有指向容器的迭代器、參考和指標失效,
-
不同容器使用不同的策略來分配元素空間,而這些策略直接影響性能,
-
當我們用一個物件來初始化容器時,或將一個物件插入到容器中時,實際上放入到容器中的是物件的一個拷貝,而不是物件本身,就像我們將一個物件傳遞給非參考引數一樣,容器中的元素與提供值的物件之間沒有任何關聯,隨后對容器中元素的任何改變都不會影響到原始物件,反之亦然,
-
通過使用insert的回傳值,可以在容器中一個特定位置反復插入元素:
list<string> lst; auto iter = lst.begin(); while (cin >> word) iter = lst.insert(iter, word); // 等價于呼叫push_front -
emplace_front、emplace和emplace_back這些操作構造而不是拷貝元素,
-
emplace函式在容器中直接構造元素,傳遞給emplace函式的引數必須與元素型別的建構式相匹配,
#include <iostream> #include <vector> using namespace std; class A { private: vector<int> data; public: A(vector<int>::iterator beg, vector<int>::iterator end) { data.assign(beg, end); } void display() { for (auto i : data) cout << i << " "; cout << endl; } }; int main() { vector<int> iv{1, 2, 3}; vector<A> one(5, A(iv.begin(), iv.end())); for (auto i : one) i.display(); cout << endl; vector<int> ivv{2, 3, 6}; one.emplace_back(ivv.begin(), ivv.end()); one.back().display(); return 0; } -
包括array在內的每個順序容器都有一個front成員函式,而除forward_list之外的所有順序容器都有一個back成員函式,這兩個操作分別回傳首元素和尾元素的參考:
// 在解參考一個迭代器或呼叫front或back之前檢查是否有元素 if (!c.empty()) { // val和val2是c中第一個元素值的拷貝 auto val = *c.begin(), val2 = c.front(); // val3和val4是c中最后一個元素值的拷貝 auto last = c.end(); auto val3 = *(--last); // forward_list迭代器不能執行--操作 auto val4 = c.back(); // forward_list不支持 } -
在呼叫front和back(或解參考begin和end回傳的迭代器)之前,要確保c非空,如果容器為空,if中操作的行為將是未定義的,
| 在順序容器中訪問元素的操作 | 解釋 |
|---|---|
| —— | at和下標操作只適用于string、vector、deque和array |
| —— | back不適用于forward_list |
| c.back() | 回傳c中尾元素的參考,若c為空,函式行為未定義, |
| c.front() | 回傳c中首元素的參考,若c為空,函式行為未定義, |
| c[n] | 回傳c中下標為n的元素的參考,n是一個無符號整數,若n>=c.size(),則函式行為未定義 |
| c.at(n) | 回傳下標為n的元素的參考,類似下標運算子,但如果下標越界,則拋出一out_of_range例外 |
-
對一個空容器呼叫front和back,就像使用一個越界的下標一樣,是一種嚴重的程式設計錯誤,
-
在容器中訪問元素的成員函式(即,front、back、下標和at)回傳的都是參考,如果容器是一個const物件,則回傳值是const的參考,如果容器不是const的,則回傳值是普通參考,我們可以用來改變元素的值:
if (!c.empty()) { c.front() = 42; // 將42賦予c中的第一個元素 auto &v = c.back(); // 獲得指向最后一個元素的參考 v = 1024; // 改變c中的元素 auto v2 = c.back(); // v2不是一個參考,它是c.back()的一個拷貝 v2 = 0; // 未改變c中的元素 // 如果我們使用auto變數來保存這些函式的回傳值,并且希望使用此變數來改變元素的值,必須記得將變數定義為參考型別 }
| 順序容器的洗掉操作 | 解釋 |
|---|---|
| —— | 這些操作會改變容器的大小,所以不適用于array |
| —— | forward_list有特殊版本的erase |
| —— | forward_list不支持pop_back;vector和string不支持pop_front |
| c.pop_back() | 洗掉c中尾元素,若c為空,則函式行為未定義,函式回傳void |
| c.pop_front() | 洗掉c中首元素,若c為空,則函式行為未定義,函式回傳void |
| c.erase(p) | 洗掉迭代器p所指定的元素,回傳一個指向被刪元素之后元素的迭代器,若p指向尾元素,則回傳尾后迭代器,若p是尾后迭代器,則函式行為未定義 |
| c.erase(b,e) | 洗掉迭代器b和e所指定范圍內的元素,回傳一個指向最后一個被刪元素之后元素的迭代器,若e本身就是尾后迭代器,則函式也回傳尾后迭代器(總之回傳e) |
| c.clear() | 洗掉c中的所有元素,回傳void |
- 洗掉deque中除首尾位置之外的任何元素都會使所有迭代器、參考和指標失效,指向vector或string中洗掉點之后位置的迭代器、參考和指標都會失效,
- 洗掉元素的成員函式并不檢查其引數,在洗掉元素之前,程式員必須確保它(們)是存在的,
- 在一個單向鏈表中,沒有簡單的辦法來獲取一個元素的前驅,因此,在一個forward_list中添加或洗掉元素的操作是通過改變給定元素之后的元素來完成的,為了支持這些操作,forward_list也定義了before_begin,它回傳一個首前迭代器(允許我們添加洗掉鏈表首元素)
| 在forward_list中插入或洗掉元素的操作 | 解釋 |
|---|---|
| lst.before_begin()或lst.cbefore_begin() | 回傳指向鏈表首元素之前不存在的元素的迭代器,此迭代器不能解參考,cbefore_begin()回傳一個const_iterator |
| lst.insert_after(p,t)或lst.insert_after(p,n,t)或lst.insert_after(p,b,e)或lst.insert_after(p,il) | 在迭代器p之后的位置插入元素,t是一個物件,n是數量,b和e是表示范圍的一對迭代器(b和e不能指向lst內),il是一個花括號串列(initializer_list),回傳一個指向最后一個插入元素的迭代器,如果范圍為空,則回傳p,若p為尾后迭代器,則函式行為未定義 |
| emplace_after(p,args) | 使用args在p指定的位置之后創建一個元素,回傳一個指向這個新元素的迭代器,若p為尾后迭代器,則函式行為未定義 |
| lst.erase_after(p)或lst.erase_after(b,e) | 洗掉p指向的位置之后的元素,或洗掉從b之后直到(但不包含)e之間的元素,回傳一個指向被刪元素之后元素的迭代器,若不存在這樣的元素,則回傳尾后迭代器,如果p指向lst的尾元素或者是一個尾后迭代器,則函式行為未定義 |
// 示例代碼:
forward_list<int> flst = {0,1,2,3,4,5,6,7,8,9};
auto prev = flst.before_begin(); // 表示flst的“首前元素”
auto curr = flst.begin(); // 表示flst中的第一個元素
while (curr != flst.end()) // 仍有元素要處理
{
if (*curr % 2) // 若元素為奇數,也可以用*curr & 1
curr = flst.erase_after(prev); // 洗掉它并移動curr
else
{
prev = curr; // 移動迭代器curr,指向下一個元素,prev指向
++curr; // curr之前的元素
}
}
| 順序容器大小操作 | 解釋 |
|---|---|
| —— | resize不適用于array |
| c.resize(n) | 調整c的大小為n個元素,若n<c.size(),則多出的元素被丟棄,若必須添加新元素(n>c.size()),對新元素進行值初始化, |
| c.resize(n,t) | 調整c的大小為n個元素,任何新添加的元素都初始化為值t |
-
如果resize縮小容器,則指向被洗掉元素的迭代器、參考和指標都會失效;對vector、string或deque進行resize可能導致迭代器、指標和參考失效,
-
容器操作可能使迭代器失效,使用失效的迭代器、指標或參考是嚴重的運行時錯誤,
- 在向容器添加元素后:
- 如果容器是vector或string,且存盤空間被重新分配,則指向容器的迭代器、指標和參考都會失效,如果存盤空間未重新分配,指向插入位置之前的元素的迭代器、指標和參考仍有效,但指向插入位置之后元素的迭代器、指標和參考將會失效,
- 對于deque,插入到除首尾位置之外的任何位置都會導致迭代器、指標和參考失效,如果在首尾位置添加元素,迭代器會失效,但指向存在的元素的參考和指標不會失效,
- 對于list和forward_list,指向容器的迭代器(包括尾后迭代器和首前迭代器)、指標和參考仍有效,
- 當我們從一個容器中洗掉元素后,指向被洗掉元素的迭代器、指標和參考一定會失效,畢竟,這些元素都已經被銷毀了,當我們洗掉一個元素后:
- 對于list和forward_list,指向容器其他位置的迭代器(包括尾后迭代器和首前迭代器)、參考和指標仍有效,
- 對于deque,如果在首尾之外的任何位置洗掉元素,那么指向被洗掉元素外其他元素的迭代器、參考和指標也會失效,如果是洗掉deque的尾元素,則尾后迭代器也會失效,但其他迭代器、參考和指標不受影響;如果是洗掉首元素,這些也不會受影響,
- 對于vector和string,指向被刪元素之前元素的迭代器、參考和指標仍有效,注意:當我們洗掉元素時,尾后迭代器總是會失效,
- 在向容器添加元素后:
-
當你使用迭代器(或指向容器元素的參考或指標)時,最小化要求迭代器必須保持有效的程式片段是一個好的辦法,由于向迭代器添加元素和從迭代器洗掉元素的代碼可能會使迭代器失效,因此必須保證每次改變容器的操作之后都正確地重新定位迭代器,這個建議對vector、string和deque尤為重要,
-
程式必須保證每個回圈布中都更新迭代器、參考和指標:
// 傻瓜回圈,洗掉偶數元素,復制每個奇數元素 vector<int> vi = {0,1,2,3,4,5,6,7,8,9}; auto iter = vi.begin(); // 呼叫begin而不是cbegin,因為我們要改變vi while (iter != vi.end()) { if (*iter % 2) { iter = vi.insert(iter, *iter); // 復制當前元素 iter += 2; // 向前移動迭代器,跳過插入到它之前的元素以及當前元素 } else { iter = vi.erase(iter); // 洗掉偶數元素 // 不應向前移動迭代器,iter指向我們洗掉的元素之后的元素 } } -
不要保存end回傳的迭代器,當我們添加、洗掉vector或string的元素后,或在deque中首元素之外任何位置添加、洗掉元素后,原來end回傳的迭代器總是會失效,因此,添加或洗掉元素的回圈程式必須反復呼叫end,而不能在回圈之前保存end回傳的迭代器,一直當作容器末尾使用,
| 容器大小管理操作 | 解釋 |
|---|---|
| —— | shrink_to_fit只適用于vector、string和deque |
| —— | capacity和reserve只適用于vector和string |
| c.shrink_to_fit() | 請將capacity()減少為與size()相同大小(具體的實作可以選擇忽略此請求,即,呼叫shrink_to_fit并不保證一定退回記憶體空間) |
| c.capacity() | 不重新分配記憶體空間的話,c可以保存多少元素 |
| c.reserve(n) | 分配至少能容納n個元素的記憶體空間 |
-
reserve并不改變容器中元素的數量,它僅影響vector預先分配多大的記憶體空間,只有當需要的記憶體空間超過當前容量時,reserve呼叫才會改變vector的容量,如果需求大小大于當前容量,reserve至少分配與需求一樣大的記憶體空間(可能更大),如果需求大小小于或等于當前容量,reserve什么也不做,特別是,當需求大小小于當前容量時,容器不會退回記憶體空間,因此,在呼叫reserve之后,capacity將會大于或等于傳遞給reserve的引數,這樣,呼叫reserve永遠也不會減少容器占用的記憶體空間,類似的,resize成員函式只能改變容器中元素的數目,而不是容器的容量,我們同樣不能使用resize來減少容器預留的記憶體空間,
-
每個vector實作都可以選擇自己的記憶體分配策略,但是必須遵守的一條原則是:只有當迫不得已(在執行insert操作時size與capacity相等,或者呼叫resize或reserve時給定的大小超過當前capacity)時才可以分配新的記憶體空間,
| 構造string的其他方法 | 解釋 |
|---|---|
| —— | n、len2和pos2都是無符號值 |
| string s(cp,n) | s是cp指向的陣列中前n個字符的拷貝,此陣列至少應該包含n個字符 |
| string s(s2,pos2) | s是string s2從下標pos2開始的字符的拷貝,若pos2>s2.size(),建構式的行為未定義 |
| string s(s2,pos2,len2) | s是string s2從下標pos2開始len2個字符的拷貝,若pos2>s2.size(),建構式的行為未定義,不管len2的值是多少,建構式至多拷貝s2.size()-pos2個字符, |
| 子字串操作 | 解釋 |
|---|---|
| s.substr(pos,n) | 回傳一個string,包含s中從pos開始的n個字符的拷貝,pos的默認值為0.n的默認值為s.size()-pos,即拷貝從pos開始的所有字符 |
- 在c++11中,vector 增加了data()的用法,它回傳內置vecotr所指的陣列記憶體的第一個元素的指標,
| 修改string的操作 | 解釋 |
|---|---|
| s.insert(pos,args) | 在pos之前插入args指定的字符,pos可以是一個下標或一個迭代器,接受下標的版本回傳一個指向s的參考;接受迭代器的版本回傳指向第一個插入字符的迭代器 |
| s.erase(pos,len) | 洗掉從位置pos開始的len個字符,如果len被省略,則洗掉從pos開始直至s末尾的所有字符,回傳一個指向s的參考 |
| s.assign(args) | 將s中的字符替換為args指定的字符,回傳一個指向s的參考 |
| s.append(args) | 將args追加到s,回傳一個指向s的參考 |
| s.replace(range,args) | 洗掉s中范圍range內的字符,替換為args指定的字符,range或者是一個下標和一個長度,或者是一對指向s的迭代器,回傳一個指向s的參考 |
| —— | args可以是下列形式之一;append和assign可以使用所有形式 |
| —— | str不能與s相同,迭代器b和e不能指向s |
| str | 字串str |
| str,pos,len | str中從pos開始最多len個字 |
| cp,len | 從cp(char pointer)指向的字符陣列的前(最多)len個字符 |
| n,c | n個字符c |
| b,e | 迭代器b和e指定的范圍內的字符 |
| 初始化串列 | 花括號包圍的,以逗號分隔的字串列 |
| —— | replace和insert所允許的args形式依賴于range和pos是如何指定的 |
| replace(pos,len,args) | replace(b,e,args) | insert(pos,args) | insert(iter,args) | args可以是 |
|---|---|---|---|---|
| 是 | 是 | 是 | 否 | str |
| 是 | 否 | 是 | 否 | str,pos,len |
| 是 | 是 | 是 | 否 | cp,len |
| 是 | 是 | 否 | 否 | cp |
| 是 | 是 | 是 | 是 | n,c |
| 否 | 是 | 否 | 是 | b2,e2 |
| 否 | 是 | 否 | 是 | 初始化串列 |
- string搜索函式回傳string::size_type值,該型別是一個unsigned型別,因此,用一個int或其他帶符號型別來保存這些函式的回傳值不是一個好主意,
| string搜索操作 | 解釋 |
|---|---|
| —— | 搜索操作回傳指定字符出現的下標,如果未找到則回傳npos |
| s.find(args) | 查找s中args第一次出現的位置 |
| s.rfind(args) | 查找s中args最后一次出現的位置 |
| s.find_first_of(args) | 在s中查找args中任何一個字符第一次出現的位置 |
| s.find_last_of(args) | 在s中查找args中任何一個字符最后一次出現的位置 |
| s.find_first_not_of(args) | 在s中查找第一個不在args中的字符 |
| s.find_last_not_of(args) | 在s中查找最后一個不在args中的字符 |
| —— | args必須是以下形式之一 |
| c,pos | 從s中位置pos開始查找字符c,pos默認為0 |
| s2,pos | 從s中位置pos開始查找字串s2,pos默認為0 |
| cp,pos | 從s中位置pos開始查找指標cp指向的以空字符結尾的C風格字串 |
| cp,pos,n | 從s中位置pos開始查找指標cp指向的陣列的前n個字符,pos和n無默認值 |
-
指定在哪里開始搜索:
string numbers("0123456789"), name("r2d2"); string::size_type pos = 0; // 每步回圈查找name中下一個數 while ((pos = name.find_first_of(numbers, pos)) != string::npos) { cout << "found number at index: " << pos << " element is " << name[pos] << endl; ++pos; // 移動到下一個字符 }
| s.compare的幾種引數形式 | 解釋 |
|---|---|
| s2 | 比較s和s2 |
| pos1,n1,s2 | 將s中從pos1開始的n1個字符與s2進行比較 |
| pos1,n1,s2,pos2,n2 | 將s中從pos1開始的n1個字符與s2中從pos2開始的n2個字符進行比較 |
| cp | 比較s與cp指向的以空字符結尾的字符陣列 |
| pos1,n1,cp | 將s中從pos1開始的n1個字符與cp指向的以空字符結尾的字符陣列進行比較 |
| pos1,n1,cp,n2 | 將s中從pos1開始的n1個字符與指標cp指向的地址開始的n2個字符進行比較 |
| string和數值之間的轉換 | 解釋 |
|---|---|
| to_string(val) | 一組多載函式,回傳數值val的string表示,val可以是任何算術型別,對每個浮點型別和int或更大的整型,都有相應版本的to_string,與往常一樣,小整型會被提升 |
| stoi(s,p,b)或stol(s,p,b)或stoul(s,p,b)或stoll(s,p,b)或stoull(s,p,b) | 回傳s的起始子串(表示整數內容)的數值,回傳值型別分別是int、long、unsigned long、long long、unsigned long long,b表示轉換所用的基數,默認值為10,p是size_t指標,用來保存s中第一個非數值字符的下標,p默認為0,即,函式不保存下標 |
| stof(s,p)或stod(s,p)或stold(s,p) | 回傳s的起始子串(表示浮點數內容)的數值,回傳值型別分別是float、double、或long double,引數p的作用與整數轉換函式中一樣 |
| —— | string引數中第一個非空白符必須是符號(+或-)或數字,它可以以0x或0X開頭來表示十六進制數,對那些將字串轉換為浮點值的函式,string引數也可以以小數點(.)開頭,并可以包含e或E來表示指數部分,對于那些將字串轉換為整型值的函式,根據基數不同,string引數可以包含字母字符,對應大于數字9的數, |
| —— | 如果string不能轉換為一個數值,這些函式拋出一個invalid_argument例外,如果轉換得到的數值無法用任何型別來表示,則拋出一個out_of_range例外, |
- 配接器是標準庫中的一個通用概念,本質上,一個配接器是一種機制,能使某種事物的行為看起來像另外一種事物一樣,一個容器配接器接受一種已有的容器型別,是其行為看起來像一種不同的型別,
| 所有容器配接器都支持的操作和型別 | 解釋 |
|---|---|
| size_type | 一種型別,足以保存當前型別的最大物件的大小 |
| value_type | 元素型別 |
| container_type | 實作配接器的底層容器型別 |
| A a; | 創建一個名為a的空配接器 |
| A a(c); | 創建一個名為a的配接器,帶有容器c的一個拷貝 |
| 關系運算子 | 每個配接器都支持所有關系運算子:==、!=、<、<=、>、>=,這些運算子回傳呼層容器的比較結果 |
| a.empty() | 若a包含任何元素,回傳false,否則回傳true |
| a.size() | 回傳a中的元素數目 |
| swap(a,b) | 交換a和b的內容,a和b必須有相同型別,包括底層容器型別也必須相同 |
-
默認情況下,stack和queue是基于deque實作的,priority_queue是在vector之上實作的,我們可以在創建一個配接器時將一個命名的順序容器作為第二個型別引數,來多載默認容器型別,
// 從deq拷貝元素到stk stack<int> stk(deq); // 在vector上實作的空堆疊 stack<string, vector<string>> str_stk; // str_stk2在vector上實作,初始化時保存svec的拷貝 stack<string, vector<string>> str_stk2(svec); // svec是vector<string>型別
| 額外的堆疊操作 | 解釋 |
|---|---|
| —— | 堆疊默認基于deque實作,也可以在list或vector之上實作, |
| s.pop() | 洗掉堆疊頂元素,但不回傳該元素值 |
| s.push(item) | 創建一個新元素壓入堆疊頂,該元素通過拷貝或移動item而來,或者由args構造 |
| s.top() | 回傳堆疊頂元素,但不將元素彈出堆疊 |
| 額外的queue和priority_queue操作 | 解釋 |
|---|---|
| —— | queue默認基于deque實作,priority_queue默認基于vector實作 |
| —— | queue也可以用list或vector實作,priority_queue也可以用deque實作 |
| q.pop() | 彈出queue的首元素或priority_queue的最高優先級的元素,但不回傳此元素 |
| q.front() | 回傳首元素或尾元素,但不洗掉此元素 |
| q.back() | 只適用于queue |
| q.top() | 回傳最高優先級元素,但不洗掉該元素 |
| q.push(item)或q.emplace(args) | 在queue末尾或priority_queue中恰當的位置創建一個元素,其值為item,或者由args構造 |
- deque支持在容器頭尾位置的快速插入和洗掉,而且在兩端插入或洗掉元素都不會導致重新分配空間,
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/254339.html
標籤:其他
上一篇:HashSet詳解
下一篇:1.Yolov5學習率調整策略
