1. 程式人生 > 實用技巧 >【C++】《C++ Primer 》第九章

【C++】《C++ Primer 》第九章

第九章 順序容器

一、順序容器概述

  • 順序容器(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,插入到除首尾位置之外的任何位置都會導致迭代器、引用和指標失效。如果在首尾位置新增元素,那麼迭代器會失效,但是指向存在的元素的引用和指標不會失效
    • 對於listforward_list,指向容器的迭代器、引用和指標仍然有效。
  • 在從容器刪除元素後
    • 對於vector和string,指向被刪元素之前元素的迭代器、引用和指標仍然有效。
    • 對於deque,如果在首尾之外的任何位置刪除元素,那麼指向被刪除元素外其他元素的迭代器、引用和指標都會失效。如果在尾位置刪除元素,則尾後迭代器失效,但是其他迭代器、引用和指標仍然有效。如果在首位置刪除元素,其他迭代器、引用和指標仍然有效。
    • 對於listforward_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

六、容器介面卡

  • 除了順序容器外,標準庫還定義了三個順序容器介面卡:stackqueuepriority_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實現