標籤:void 自己 刪除 after string 空格 city 自增 seq
順序容器簡介:
順序容器類型 |
描述 |
vector |
可變大小數組,支援快速存取,在尾部之外的地方插入或刪除時可能很慢 |
deque |
雙端隊列。支援快速存取,在頭尾插入刪除會很快。 |
list |
雙向列表。只支援雙向順序訪問。插入刪除很快 |
forward_list |
單向列表。只支援單向順序訪問,在任何位置插入或刪除都很快 |
array |
固定大小數組,支援快速隨機訪問,不能添加刪除元素 |
string |
與vector類似,專門用於儲存字元。隨機訪問快,在尾部添加刪除快 |
其中array和forward_list是新C++標準增加的類型。與內建數組相比,array是一種更安全更容易使用的數群組類型。而 forward_list設計目標是大道與最好的手寫單向鏈表相當的效能,因此它沒有size操作,而對其他容器,size是快速的常量時間操作。
選用容器的基本原則:
- 除非有很好的原因,否則應該使用vector;
- 如果程式有很多很小的元素,且空間的額外開銷很重要,則不要使用list和forward_list;
- 如果要求隨機訪問元素,則使用vector或deque;
- 如果需要在容器中間插入刪除,應該使用list或forward_list;
- 如果需要在容器頭尾插入刪除,不需要在中間插入刪除,則使用deque;
- 如果只有在讀入資料時,需要在容器中間插入元素,隨後要隨機訪問元素,則首先要確定是真的需要在中間插入,能否在尾部插入然後重排來實現,如果還是必須在中間插入,則先用list接受資料在拷貝到vector中;
注意:較舊的編譯器需要在vector<vector<string> >的兩個角括弧之間加上空格。
所有容器的通用操作:
相互關聯類型總結:
size_type 無符號整型,足以儲存此容器類型的最大可能容器長度
iterator 此容器類型的迭代器類型
const_iterator 元素的唯讀迭代器類型
reverse_iterator 按逆序定址元素的迭代器(不支援forward_lsit)
const_reverse_iterator 元素的唯讀(不能寫)逆序迭代器(不支援forward_lsit)
difference_type 足夠儲存兩個迭代器差值的有符號整型,可為負數
value_type 元素類型
reference 元素的左實值型別,是 value_type& 的同義字
const_reference 元素的常量左實值型別,等效於 const value_type&
建構函式總結
C<T> c |
建立一個名為c 的空容器,C 是容器類型名,如vector ,T 是元素類型,如int ,string 。適用於所有容器 |
C c(c2) |
建立容器c2 的副本c ;c2 和c 必須具有相同的容器類型,並存放相同類型的元素。適用於所有容器 |
C c{a,b,c...}
C c={a,b,c...} |
c初始化為初始化列表中元素的拷貝。列表中元素類型必須與c的元素類型相容,對於array類型,列表中元素數目必須等於 或小於array的大小,任何遺漏的元素都進行值初始化。 |
C c(n) |
建立有n 個初始化元素的容器c 。只適用順序容器 |
C c(n, t) |
使用n 個為t 的元素建立容器c ,其中值t 必須是容器類型C 的元素類型的值,或者是可以轉換為該類型的值。只適用順序容器 |
C c(b, e) |
建立容器c ,其中元素是迭代器b 和e 標示的範圍內元素的副本。適用於所有容器 |
將一個容器建立為另一個容器的拷貝的兩種方法:
- 直接拷貝整個容器;兩個容器的類型和元素類型必須匹配;
- 拷貝迭代器指定的元素範圍;不要求容器類型相同,元素類型也可以不同只要能夠相互轉換。
如果元素是內建類型或有預設建構函式的類類型,怎可以只提供容器大小,如果元素沒有預設建構函式就必須顯示指定初始值。
array對類類型初始化時,需要該類類型由預設建構函式,array類型可以拷貝或賦值,但要求初始實值型別和容器類型相同,且元素類型和大小一樣。
賦值操作
c1 = c2; c2賦給c1,如果兩容器大小不同,賦值後,兩者大小與右邊容器的大小相同。array允許賦值,但是左右兩邊的對象必須有相同類型。
c1 = {a,b,c}; c1中的元素替換為列表中的元素,array不支援assign和值列表進行賦值(因為兩邊大小可能不一樣)。
賦值相關運算會導致左邊容器內部迭代器、引用和指標失效,除了array和string外,swap不會導致容器的迭代器、引用和指標失效。
順序容器可以用assign來從一個不同但相容的類型賦值,或者從容器的一個子序列賦值。
seq.assign(b,e);//seq替換為b到e之間的元素
seq.assign(li);//seq替換為初始化列表中的元素
seq.assign(n,t);//seq替換為n個值為t的元素
swap函數:
a.swap(b); a和b交換
swap(a,b); a和b交換
除了array外,swap交換兩個容器內容操作很快,因為他並沒有交換元素本身,而是交換了容器內部的資料結構。所以,可以保證在常數時間內完成。
因此,除了string外指向容器的迭代器、引用和指標在swap後也不會失效,只是它們所屬的容器變化了。
注意。早期版本只有成員函數版本的swap,新版本兩個都有,但是非成員函數的swap對泛型程式設計很重要,統一使用非成員版本的swap是個好習慣。
其他函數
c.size() 返回容器 c 中的元素個數。傳回型別為 c::size_type;(不支援forward_list)
c.max_size() 返回容器 c 可容納的最多元素個數,傳回型別為 c::size_type
c.empty() 返回標記容器大小是否為 0 的布爾值
c.erase(args) 刪除args指定的元素
c.clear() 刪除容器 c 內的所有元素。返回 void
c.insert(args) 將args中的元素拷貝到c中
c.begin() 返回一個迭代器,它指向容器 c 的第一個元素
c.end() 返回一個迭代器,它指向容器 c 的最後一個元素的下一位置
c.rbegin() 返回一個逆序迭代器,它指向容器 c 的最後一個元素
c.rend() 返回一個逆序迭代器,它指向容器 c 的第一個元素前面的位置
c.crbegin() 返回一個const逆序迭代器,它指向容器 c 的最後一個元素
c.crend() 返回一個const逆序迭代器,它指向容器 c 的第一個元素前面的位置
以c開頭的函數是C++新標準引入的用以支援auto與begin和end結合使用。auto與begin和end結合使用時,擷取的迭代器類型依賴於容器類型,而c開頭的獲得的是const_iterator。
每個容器都支援!=和==,除了無序容器外的所有容器都支援關係運算子(>、>=、<、<=)。因此比較容器時,盡量使用!=和==。
添加元素(array不支援這些操作):
push_back(t);//適用於所有順序容器
push_front(t);//僅適用於list和deque
insert(p,t);//在迭代器p前面插入一個元素,返回新添加元素的迭代器
insert(p,n,t)//在迭代器p前面插入n個t元素,返回void
insert(p,b,e)//在迭代器p前面插入迭代器b和e之間的元素
還可以插入初始化列表insert(p,li);在迭代器p前面插入li初始化列表。
新標準增加的方法
emplace();//對應insert()的功能
emplace_back(t);//對應push_back()的功能
emplace_front(t);//對應push_front()的功能
emplace不是拷貝元素,而是調用參數傳遞給元素類型的建構函式直接在容器的記憶體中構造元素。
forward_list有自己專有版本的insert和emplace;forward_list不支援push_back和emplace_back;
容器中訪問元素的函數back()、front()、at()和下標操作返回的都是引用。
如果想下標是合法的可以使用at(),它會檢查界限。越界拋出out_of_range異常。
刪除元素(array不支援這些操作):
C.pop_back();//forward_list不支援該方法,返回void
C.pop_front();//僅適用於vector和deque容器,返回void
C.erase(p);//刪除p所指的元素,返回下一個位置
C.erase(b,e);//刪除迭代器b和e之間的元素,返回下一個位置
C.clear();//刪除所有元素,返回void
刪除前請確保元素存在。
特殊的forward_list操作
forward_list是單向鏈表,所以在它上面添加或刪除元素的操作是通過改變給定元素之後的元素來完成的。
c.push_front() |
在開頭插入元素 |
c.pop_front() |
刪掉開頭元素 |
c.insert_after(pos, elem) |
在pos之後插入元素, 返回新元素的位置 |
c.insert_after(pos, n, elem) |
在pos之後插入n個元素elem, 返回第一個新元素的位置 |
c.insert_after(pos, begin, end) |
在pos之後插入元素[begin, end), 返回第一個新元素的位置 |
c.insert_after(pos, initiallist) |
在pos之後插入initiallist, 返回第一個新元素的位置 |
c.emplace_after(pos, args...) |
在pos之後插入args…元素,返回第一個新元素的位置 |
c.emplace_front(args...) |
在開頭插入元素args…, 無傳回值 |
c.erase_after(pos) |
刪掉pos後面那個元素 |
c.erase_after(begin, end) |
刪掉(begin, end)之間的元素 |
before_begin返回一個首前(off-the-beginning)迭代器。這個迭代器允許我們在鏈表受元素之前不存在的元素之後添加或刪除元素。
大小操作
C.size();//返回容器內元素的個數
C.max_size(0;//返回容器可容納的最大元素個數
C.empty();
C.resize(n);//調整容器的長度,使其能容納n個元素,如果n<C.size(),刪除過多的,else 添加採用值初始化的新元素;類類型需要提供預設建構函式
C.resize(n,t);//調整容器的長度,使其能容納n個元素,新添加的元素初始化為t
總結1:容器操作可能是迭代器失效:
向容器添加元素時,
如果容器是vector和string,且儲存空間被重新分配,則指向容器的迭代器。指標和引用都會失效,如果為重新分配儲存空間,指向插入位置之前的元素的迭代器、指標和引用有效,之後的失效;
如果是deque,插入收尾之外的位置都會使迭代器、指標和引用失效,插入首尾時會使迭代器失效;
如果是list和forward_list,都有效。
向容器刪除元素時,
如果是list和forward_list,指向其他地方的迭代器、指標和引用都有效;
如果是deque,刪除收尾之外的位置都會使迭代器、指標和引用失效;刪除首尾時,其他地方的迭代器、指標和引用都有效,但是刪除為元素時,尾後迭代器會失效;
如果容器是vector和string,指向被刪除元素之前的元素的迭代器、指標和引用仍有效,尾後迭代器會失效;
因此,不要儲存尾後迭代器,添加和刪除元素常常會使尾後迭代器失效;
下面是迭代器失效的例子和解決方案:
1 #include <iostream> 2 #include <map> 3 using namespace std; 4 5 typedef map<int, int> Map; 6 typedef map<int, int>::iterator MapIt; 7 8 void print(Map &m) 9 {10 MapIt it;11 for(it = m.begin(); it != m.end(); it++)12 {13 cout << it->second << " ";14 }15 16 cout << endl;17 }18 19 void deleteValueFromMap(Map &m, int n = 5)20 {21 MapIt it;22 for(it = m.begin(); it != m.end(); it++)23 {24 if(0 == it->second % n)25 {26 m.erase(it);27 }28 }29 }30 31 int main()32 {33 Map m;34 int i = 0;35 for(i = 0; i < 21; i++)36 {37 m[i] = i;38 }39 40 print(m);41 42 deleteValueFromMap(m); // 程式崩潰43 44 return 0;45 }
運行上述程式, 結果程式崩潰,什麼原因呢? 原來, 對於關聯的容器map來說, m.erase(it);後, it就失效了, 而for迴圈中有it++, 自然而然會出問題啊。 那怎麼辦呢? 且看:
1 #include <iostream> 2 #include <map> 3 using namespace std; 4 5 typedef map<int, int> Map; 6 typedef map<int, int>::iterator MapIt; 7 8 void print(Map &m) 9 { 10 MapIt it; 11 for(it = m.begin(); it != m.end(); it++) 12 { 13 cout << it->second << " "; 14 } 15 16 cout << endl; 17 } 18 19 void deleteValueFromMap(Map &m, int n = 5) 20 { 21 MapIt it; 22 MapIt tmp; 23 for(it = m.begin(); it != m.end(); /*不能再自增了*/) 24 { 25 if(0 == it->second % n) 26 { 27 tmp = it++; 28 m.erase(tmp); 29 } 30 else 31 { 32 it++; 33 } 34 } 35 } 36 37 int main() 38 { 39 Map m; 40 int i = 0; 41 for(i = 0; i < 21; i++) 42 { 43 m[i] = i; 44 } 45 46 print(m); 47 48 deleteValueFromMap(m); // 程式ok 49 print(m); 50 51 return 0; 52 } 53 結果為: 54 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 55 1 2 3 4 6 7 8 9 11 12 13 14 16 17 18 19 56 57 58 59 60 61 當然, 上述程式也可以繼續簡化為: 62 63 64 65 #include <iostream> 66 #include <map> 67 using namespace std; 68 69 typedef map<int, int> Map; 70 typedef map<int, int>::iterator MapIt; 71 72 void print(Map &m) 73 { 74 MapIt it; 75 for(it = m.begin(); it != m.end(); it++) 76 { 77 cout << it->second << " "; 78 } 79 80 cout << endl; 81 } 82 83 void deleteValueFromMap(Map &m, int n = 5) 84 { 85 MapIt it; 86 for(it = m.begin(); it != m.end(); /*不能再自增了*/) 87 { 88 if(0 == it->second % n) 89 { 90 m.erase(it++); 91 } 92 else 93 { 94 it++; 95 } 96 } 97 } 98 99 int main()100 {101 Map m;102 int i = 0;103 for(i = 0; i < 21; i++)104 {105 m[i] = i;106 }107 108 print(m);109 110 deleteValueFromMap(m); // 程式ok111 print(m);112 113 return 0;114 }115 結果ok.
注意上面第90行m.erase(it++);這句話就不會出現迭代器失效的情況。因為it++;會返回加一之前的迭代器。
vector的增長
vector和string每次分配空間會分配比需求更大的空間
capacity() 可以告訴我們容器在不擴張記憶體的情況下可以容納多少各元素
reserve() 可以讓我們通知容器它應該準備儲存多少個元素的空間
容器大小管理操作:
shrink_to_fit 只能用於vector,string,deque
capacity,reserve 只能用於vector,string
c.shrink_to_fit() 將capacity()減少為size()相同的大小,實際實現中可以忽略該函數,所以實際上不一定會返回空間
c.capacity() 不重新分配記憶體空間的話,c可以儲存多少元素個數
c.reserve(n) 分配至少能容納n個元素的記憶體空間,n如果<=capacity(),那麼reserve什麼也不做;n大於當前容量時,才會分配空間。
c.size() 容器中元素的個數,與capacity是不一樣的;
大部分vector採用的分配策略:就是在每次需要分配記憶體空間時,將當前的容量capacity翻倍;
這也是不確定的,應該具體問題具體分析。
通過在一個初始為空白的vector上調用push_back來建立一個n個元素的vector,所花費的時間不能超過n的常數倍。
C++基礎之容器