【C++】《C++ Primer 》第九章
阿新 • • 發佈:2020-07-29
第九章 順序容器
一、順序容器概述
- 順序容器(sequential container):為程式設計師提供了控制元素儲存和訪問順序的能力。這種順序不依賴於元素的值,而是與元素加入容器時的位置相對應。
- 不同容器在不同的方面都有不同的效能折中:
- 向容器新增或者刪除元素的代價
- 非順序訪問容器中元素的代價
1. 順序容器型別
容器型別 | 解釋 |
---|---|
vector | 可變大小陣列。支援快速隨機訪問。在尾部之外的位置進行插入/刪除元素操作可能很慢。 |
deque | 雙端佇列。支援快速隨機訪問。在頭尾位置進行插入/刪除操作速度很快。 |
list | 雙向連結串列。只支援雙向順序訪問。在任何位置進行插入/刪除操作速度很快。 |
forward_list | 單向連結串列。只支援單向順序訪問。在任何位置進行插入/刪除操作速度很快。 |
array | 固定大小陣列。支援快速隨機訪問,不能進行新增/刪除元素操作。 |
string | 專門用來儲存字元,與vector相似的容器。支援快速隨機訪問。在尾部進行插入/刪除元素操作很塊。 |
- 除了固定大小的array外,其他容器都提供高效、靈活的記憶體管理。
-
- forward_list和array是新C++標準增加的型別。
- 通常使用vector是最好的選擇,除非你有很好的理由選擇其他容器。
- 新標準庫的容器比舊版的快得多
二、容器庫概覽
1. 容器操作
類型別名
操作 | 解釋 |
---|---|
iterator | 此容器的迭代器型別 |
const_iterator | 可以讀取元素,但不能修改元素的迭代器型別 |
size_type | 無符號整數型別,足夠儲存此種容器型別最大可能容器的大小 |
difference_type | 帶符號整數型別,足夠儲存兩個迭代器之間的距離 |
value_type | 元素型別 |
reference | 元素的左值型別;和value_type &含義相同 |
const_reference | 元素的const左值型別,即const value_type & |
建構函式
操作 | 解釋 |
---|---|
C c; | 預設建構函式,構造空容器 |
C c1(c2);或C c1 = c2; | 構造c2的拷貝c1 |
C c(b, e) | 構造c,將迭代器b和e指定範圍內的所有元素拷貝到c(array不支援) |
C c{a, b, c...} | 列表初始化c |
C c(n) | 只支援順序容器,且不包括array,包含n個元素,這些元素進行了值初始化 |
C c(n, t) | 包含n個初始值為t的元素 |
- 只有順序容器的建構函式才接受大小引數,關聯容器並不支援。
- array具有固定大小。
- 和其他容器不同,預設構造的array是非空的。
- 直接複製:將一個容器複製給另一個容器時,型別必須匹配:容器型別和元素型別都必須相同。
- 使用迭代器複製:不要求容器型別相同,容器內的元素型別也可以不同。
賦值和swap
操作 | 解釋 |
---|---|
c1 = c2; | 將c1中的元素替換成c2中的元素 |
c1 = {a, b, c...} | 將c1中的元素替換成列表中的元素(不適用於array) |
c1.swap(c2) | 交換c1和c2的元素 |
swap(c1, c2) | 等價於c1.swap(c2) |
c.assign(b, e) | 將c中的元素替換成迭代器b和e表示範圍中的元素,b和e不能指向c中的元素 |
c.assign(il) | 將c中的元素替換成初始化列表il中的元素 |
c.assign(n, r) | 將c中的元素替換為n個值是t的元素 |
- 使用非成員版本的swap是一個好習慣。
- assign操作不適用於關聯容器和array,僅適用於順序容器。
- 賦值相關運算會導致指向左邊容器內部的迭代器、引用和指標失效。而swap操作將容器內容交換不會導致指向容器的迭代器、引用和指標失效(array和string除外)。
大小
操作 | 解釋 |
---|---|
c.size() | c中元素的數目(不支援forward_list) |
c.max_size() | c中可儲存的最大元素數目 |
c.empty() | 若c中儲存了元素,返回false,否則返回true |
新增元素
操作 | 解釋 |
---|---|
c.push_back(t) | 在c尾部建立一個值為t的元素,返回void |
c.emplace_back(args) | 同上 |
c.push_front(t) | 在c頭部建立一個值為t的元素,返回void |
c.emplace_front(args) | 同上 |
c.insert(p, t) | 在迭代器p指向的元素之前建立一個值是t的元素,返回指向新元素的迭代器 |
c.emplace(p, args) | 同上 |
c.inset(p, n, t) | 在迭代器p指向的元素之前插入n個值為t的元素,返回指向第一個新元素的迭代器;如果n是0,則返回p |
c.insert(p, b, e) | 將迭代器b和e範圍內的元素,插入到p指向的元素之前;如果範圍為空,則返回p |
c.insert(p, il) | il是一個花括號包圍中的元素值列表,將其插入到p指向的元素之前;如果il是空,則返回p |
- 因為這些操作會改變大小,因此不適合於array。
- forward_list有自己專有版本的insert和emplace。
- forward_list不支援push_back和emplace_back。
- 當我們用一個物件去初始化容器或者將物件插入到容器時,實際上放入的是物件的拷貝。
- emplace開頭的函式是新標準引入的,這些操作是構造而不是拷貝元素。
- 傳遞給emplace的引數必須和元素型別的建構函式相匹配。
- 向一個vector、string或deque插入元素會使所有指向內容的迭代器、引用和指標失效。
刪除元素
操作 | 解釋 |
---|---|
c.pop_back() | 刪除c中尾元素,若c為空,則函式行為未定義。函式返回void |
c.pop_front() | 刪除c中首元素,若c為空,則函式行為未定義。函式返回void |
c.erase(p) | 刪除迭代器p指向的元素,返回一個指向被刪除元素之後的元素的迭代器,若p本身是尾後迭代器,則函式行為未定義 |
c.erase(b, e) | 刪除迭代器b和e範圍內的元素,返回指向最後一個被刪元素之後元素的迭代器,若e本身就是尾後迭代器,則返回尾後迭代器 |
c.clear() | 刪除c中所有元素,返回void |
- 會改變容器大小,不適用於array。
- forward_list有特殊版本的erase
- forward_list不支援pop_back
- vector和string不支援pop_front
- 刪除deque中除收尾位置之外的任何元素都會使所有迭代器、引用和指標都會失效。指向vector或string中刪除點之後位置的迭代器、引用和指標都會失效。
訪問元素
操作 | 解釋 |
---|---|
c.back() | 返回c中尾元素的引用。若c為空,函式行為未定義 |
c.front() | 返回c中頭元素的引用。若c為空,函式行為未定義 |
c[n] | 返回c中下標是n的元素的引用,n時候一個無符號整數。若n>=c.size(),則函式行為未定義。 |
c.at(n) | 返回下標為n的元素引用。如果下標越界,則丟擲out_of_range異常 |
- 訪問成員函式返回的是引用。
- at和下標操作只適用於string、vector、deque、array。
- back不適用於forward_list。
- 如果希望下標是合法的,可以使用at函式。
特殊的forward_list操作
操作 | 解釋 |
---|---|
lst.before_begin() | 返回指向連結串列首元素之前不存在的元素的迭代器,此迭代器不能解引用。 |
lst.cbefore_begin() | 同上,但是返回的是常量迭代器。 |
lst.insert_after(p, t) | 在迭代器p之後插入元素。t是一個物件 |
lst.insert_after(p, n, t) | 在迭代器p之後插入元素。t是一個物件,n是數量。若n是0則函式行為未定義 |
lst.insert_after(p, b, e) | 在迭代器p之後插入元素。由迭代器b和e指定範圍。 |
lst.insert_after(p, il) | 在迭代器p之後插入元素。由il指定初始化列表。 |
emplace_after(p, args) | 使用args在p之後的位置,建立一個元素,返回一個指向這個新元素的迭代器。若p為尾後迭代器,則函式行為未定義。 |
lst.erase_after(p) | 刪除p指向位置之後的元素,返回一個指向被刪元素之後的元素的迭代器,若p指向lst的尾元素或者是一個尾後迭代器,則函式行為未定義。 |
lst.erase_after(b, e) | 類似上面,刪除物件換成從b到e指定的範圍。 |
- 連結串列在刪除元素時需要修改前置節點的內容,雙向連結串列會前驅的指標,但是單向連結串列沒有儲存,因此需要增加獲取前置節點的方法。
- forward_list定義了before_begin,即首前(off-the-begining)迭代器,允許我們再在首元素之前新增或刪除元素。
改變容器大小
操作 | 解釋 |
---|---|
c.resize(n) | 調整c的大小為n個元素,若n<c.size(),則多出的元素被丟棄。若必須新增新元素,對新元素進行值初始化。 |
c.resize(n, t) | 調整c的大小為n個元素,任何新新增的元素都初始化為值t。 |
- 如果resize縮小容器,則指向被刪除元素的迭代器、引用和指標都會失效;對vector、string或deque進行resize可能導致迭代器、引用和指標失效。
獲取迭代器
操作 | 解釋 |
---|---|
c.begin(), c.end() | 返回指向c的首元素和尾元素之後位置的迭代器 |
c.cbegin(), c.cend() | 返回const_iterator |
- 以c開頭的版本是C++11新標準引入的
- 當不需要寫訪問時,應該使用cbegin和cend。
反向容器的額外成員
操作 | 解釋 |
---|---|
reverse_iterator | 按逆序定址元素的迭代器 |
const_reverse_iterator | 不能修改元素的逆序迭代器 |
c.rbegin(), c.rend() | 返回指向c的尾元素和首元素之前位置的迭代器 |
c.crbegin(), c.crend() | 返回const_reverse_iterator |
- 不支援forward_list
2. 迭代器
- 迭代器範圍:begin到end,即第一個元素到最後一個元素的後面一個位置。
- 左閉合區間:[begin, end)。
- 使用左閉合蘊含的程式設計設定:
- 如果begin和end相等,則範圍為空。
- 如果二者不等,則範圍至少包含一個元素,且begin指向該範圍中的第一個元素。
- 可以對begin遞增若干次,使得begin == end。
三、順序容器操作
1. 容器操作可能使迭代器失效的情況
- 在向容器新增元素後:
- 對於vector和string,如果儲存空間被重新分配,則指向容器的迭代器、引用和指標都會失效。如果儲存空間未被重新分配,則指向插入位置之前的元素的迭代器、引用和指標仍然有效,但是之後的都會無效,
- 對於deque,插入到除首尾位置之外的任何位置都會導致迭代器、引用和指標失效。如果在首尾位置新增元素,那麼迭代器會失效,但是指向存在的元素的引用和指標不會失效。
- 對於list和forward_list,指向容器的迭代器、引用和指標仍然有效。
- 在從容器刪除元素後:
- 對於vector和string,指向被刪元素之前元素的迭代器、引用和指標仍然有效。
- 對於deque,如果在首尾之外的任何位置刪除元素,那麼指向被刪除元素外其他元素的迭代器、引用和指標都會失效。如果在尾位置刪除元素,則尾後迭代器失效,但是其他迭代器、引用和指標仍然有效。如果在首位置刪除元素,其他迭代器、引用和指標仍然有效。
- 對於list和forward_list,指向容器其他位置的迭代器、引用和指標仍然有效。
- 注意:當我們刪除元素時,尾後迭代器總會失效。使用失效的迭代器、指標、引用是嚴重的執行時錯誤!
- 建議:將要求迭代器必須保持有效的程式片段最小化。並且不要儲存end返回的迭代器。
2. 容器內元素的型別約束
- 元素型別必須支援賦值運算;
- 元素型別的物件必須可以複製。
- 除了輸入輸出標準庫型別外,其他所有標準庫型別都是有效的容器元素型別。
四、vector物件是如何增長的
vector和string在記憶體中是連續儲存的,如果原先分配的記憶體位置已經使用完,則需要重新分配新空間,將已有元素從就位置移動到新空間中,然後新增新元素。但是如果這樣,效能會很差。
為了避免這種代價,標準庫採用了可以減少容器空間重新分配次數的策略。即,當不得不獲取新的記憶體空間時,vector和string的實現通常會分配比新的空間需求更大的記憶體空間。
管理容器的成員函式
操作 | 解釋 |
---|---|
c.shrink_to_fit | 將capacity()減少到和size()相同大小 |
c.capacity() | 不重新分配記憶體空間的話,c可以儲存多少個元素 |
c.reserve(n) | 分配至少能容納n個元素的記憶體空間 |
- 注意:reserve並不改變容器中元素的數量,它僅影響vector預先分配多大的記憶體空間。
五、額外的string操作
1. 構造string的其他方法
操作 | 解釋 |
---|---|
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個字元的拷貝。 |
2. substr操作
操作 | 解釋 |
---|---|
s.substr(pos, n) | 返回一個string,包含s中從pos開始的n個字元的拷貝。pos的預設值為0。n的預設值為s.size()-pos,即拷貝從pos開始的所有字元。 |
3. 改變string其他方法
操作 | 解釋 |
---|---|
s.insert(pos, args) | 在pos之前插入args指定的字元。pos可以使是下標或者迭代器。接受下標的版本返回指向s的引用;接受迭代器的版本返回指向第一個插入字元的迭代器。 |
s.erase(pos, len) | 刪除從pos開始的len個字元,如果len被省略,則刪除後面所有字元,返回指向s的引用。 |
s.assign(args) | 將s中的字元替換成args指定的字元。返回一個指向s的引用。 |
s.append(args) | 將args指定的字元追加到s,返回一個指向s的引用。 |
s.replace(range, args) | 刪除s中範圍range中的字元,替換成args指定的字元。返回一個指向s的引用。 |
4. string搜尋操作
- string類提供了6個不同的搜尋函式,每個函式都有4個過載版本。
- 每個搜尋操作都返回一個string::size_type值,表示匹配發生位置的下標。如果搜尋失敗則返回一個名為string::npos的static成員(型別是string::size_type,初始化值是-1,也就是string最大的可能大小)
操作 | 解釋 |
---|---|
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_first_not_of(args) | 在s中查詢最後一個不在args中的字元 |
args必須是一下的形式之一:
args形式 | 解釋 |
---|---|
c, pos | 從s中位置pos開始查詢字元c。pos預設是0 |
s, pos | 從s中位置pos開始查詢字串s。pos預設是0 |
cp, pos | 從s中位置pos開始查詢指標cp指向的以空字元結尾的C風格字串。pos預設是0 |
cp, pos, n | 從s中位置pos開始查詢指標cp指向的前n個字元。pos和n無預設值。 |
5. compare函式
邏輯類似於C標準庫的strcmp函式,根據s是等於、大於還是小於引數指定的字串,s.compare返回0、正數或負數。
引數形式 | 解釋 |
---|---|
s2 | 比較s和s2 |
pos1, n1, s2 | 比較s從pos1開始的n1個字元和s2 |
pos1, n1, s2, pos2, n2 | 比較s從pos1開始的n1個字元和s2 |
cp | 比較s和cp指向的以空字元結尾的字元陣列 |
pos1, n1, cp | 比較s從pos1開始的n1個字元和cp指向的以空字元結尾的字元陣列 |
pos1, n1, cp, n2 | 比較s從pos1開始的n1個字元和cp指向的地址開始n2個字元 |
6. 數值轉換
操作 | 解釋 |
---|---|
to_string(val) | 一組過載函式,返回數值val的string表示。val可以使任何算術型別。對每個浮點型別和int或更大的整型,都有相應版本的to_string()。和往常一樣,小整型會被提升。 |
stoi(s, p, b) | 返回s起始子串(表示整數內容)的數值,p是s中第一個非數值字元的下標,預設是0,b是轉換所用的基數。返回int |
stol(s, p, b) | 返回long |
stoul(s, p, b) | 返回unsigned long |
stoll(s, p, b) | 返回long long |
stoull(s, p, b) | 返回unsigned long long |
stof(s, p) | 返回s起始子串(表示浮點數內容)的數值,p是s中第一個非數值字元的下標,預設是0。返回float |
stod(s, p) | 返回double |
stold(s, p) | 返回long double |
六、容器介面卡
- 除了順序容器外,標準庫還定義了三個順序容器介面卡:stack、queue、priority_queue。
- 介面卡(adapter)是使一事物的行為類似於另一事物的行為的一種機制,例如 stack 可以使任何一種順序容器(除array或forward_list)以棧的方式工作。
- 預設情況下,stack和queue是基於deque實現的,priority_queue是基於vector實現的。
- 注意:所有介面卡都要求容器具有新增和刪除元素以及訪問尾元素的能力。
// 每個介面卡都定義兩種建構函式:1.預設建構函式建立一個空物件。2. 接受一個容器的建構函式拷貝該容器來初始化介面卡。
// 1.
deque<int> deq;
stack<int> stk(deq);
// 2.
stack<string, vector<string> > str_stk;
1. 介面卡的通用操作和型別
操作 | 解釋 |
---|---|
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必須有相同型別,包括底層容器型別也必須相同 |
a.swap(b) | 同上 |
2. stack
操作 | 解釋 |
---|---|
s.pop() | 刪除棧頂元素,不返回。 |
s.top() | 返回棧頂元素,不刪除。 |
s.push(item) | 建立一個新元素,壓入棧頂,該元素通過拷貝或移動item而來。 |
s.emplace(args) | 同上,但元素由args來構造。 |
- 定義在stack標頭檔案中。
- stack預設基於deque實現,也可以在list或vector之上實現。
3. queue和priority_queue
操作 | 解釋 |
---|---|
q.pop() | 刪除隊首元素,但不返回。 |
q.front() | 返回隊首元素的值,不刪除。 |
q.back() | 返回隊尾元素的值,不刪除。只適用於queue |
q.top() | 返回具有最高優先順序的元素值,不刪除。 |
q.push(item) | 在隊尾壓入一個新元素。 |
q.emplace(args) | 同上,但元素由args來構造。 |
- 定義在queue標頭檔案中。
- queue預設基於deque實現,priority_queue預設基於vector實現。
- queue可以在list或vector之上實現,priority_queue也可以用deque實現。