C++ 知識總結 P03:容器
容器
容器可以分為三類:序列式容器、關聯容器、無序容器,此外還有一些容器介面卡。
序列容器
array
與 vector
:array
與C中的陣列類似,是一種大小固定的儲存連續的容器;vector
也是儲存連續的,但它的長度可以動態調整。相對於陣列型別,這兩種容器更為安全。由於 array
與 vector
都是連續儲存的容器,能夠高效地利用索引訪問元素,但在中間部分插入/刪除元素比較低效。
deque
:一種雙端佇列,也是一種連續儲存的容器,能夠高效地在首尾部新增/刪除元素,但在中間部分插入/刪除元素比較低效。
list
與 forward_list
:list
是由雙向連結串列實現的,forward_list
關聯容器
map
:map
型別儲存鍵值對,通常是以二叉樹實現的,預設以 key 作為比較的依據。map
中的 key 不允許重複,而 multimap
與 map
的唯一的區別是元素可以重複。
set
:set
型別只儲存鍵值,其它特性與 map
相同,multiset
與 set
的唯一區別是元素可以重複。
由於關聯容器的底層實現是二叉樹(紅黑樹),因此預設情況下關聯容器中的 keys 是有序的。如果使用關聯容器時對排序沒有需求,可以選擇下面的無序容器。
無序容器
關聯容器中的四種類型都有對應的無序版本:unordered_map
unordered_multimap
, unordered_set
, unordered_multiset
;無序容器通常是以雜湊表實現的。
容器介面卡
stack
預設的容器是 deque
,queue
預設的容器是 deque
,priority_queue
與 queue
功能類似,但是會按照壓入元素的值排序,彈出值最大的元素。預設使用的容器是 vector
。
如何選擇容器
根據《C++標準庫》中的描述,預設情況下應該選擇 vector
,如果需要經常在首尾部插入或移除元素,應該選擇 deque
,如果需要經常在中間部分插入或移除元素,應該選擇 list
。
如果需要經常查詢元素,應該選擇 unordered_set
unordered_map
,如果在上述兩種場景下,需要滿足有序,應該選擇 set
/ map
。
容器的操作
容器實現了大量的功能,以成員函式的形式提供這些功能,這裡不在贅述,可以檢視 C++ Reference / Container。但是僅僅閱讀文件可能無法弄清楚這些成員函式的具體表現,需要在實踐中多累積經驗。
下面是一些我認為靈活使用的方法。
pair 與 tuple
儘管 pair
提供了 first
, second
來訪問元素,但是還可以使用 get<idx>()
函式來訪問 pair
, tuple
, 甚至 array
的值,並且該函式還可以作為左值使用 get<1>(p) = 10
。
pair
, tuple
都提供來比較的能力,按照先比較第一個元素,相等時再比較第二個元素,……,以這樣的順序進行比較。
array
array.data()
會返回指向首元素的指標,這樣就可以像使用陣列一樣處理 array 的資料。如:
array<int, 10> a {1, 2, 3, 4, 5};
int* p = a.data();
for (int i = 0; i < 10; ++i) {
cout << *(p+i) << endl;
}
vector
也提供了這個功能。
vector 與 deque
vector
與 deque
非常相似,只有在雙端插入移除元素的需求時才會使用 deque
。儘管 vector
也提供來訪問首個元素的成員函式 front
,但是沒有像 deque
一樣提供插入移除首個元素的成員函式。
藉助 initlist 來實現初始化一個值的容器。
// 如果想初始化一個 大小為 n 的 vector / deque
vector<type> vec(n);
// 如果想初始化一個 第一個元素為 n 的 vector / deque
vector<type> vec({n});
list 與 forward_list
相比與前面的 vector
與 deque
,list
與 forward_list
增加來很多不同的能力:排序 sort,逆序 reverse,去重 unique,合併 merge,移動元素 splice,去除元素 remove 等。
forward_list
沒有實現 size()
,原因可能是由於這種容器的設計目的是快速的插入,每次插入操作都去維護一個儲存 size 的變數顯然會降低效率;而每次獲得 size 都遍歷一遍連結串列的實現也是低效的,因為人們容易濫用 size()
成員函式。索性就沒有實現,而且對於使用者而言也不難自己實現。
map 與 multimap
map
中的元素是有序的,在 map
模板中,除前兩個引數用來指定 key 與 value 的型別外,還可以提供第三個引數,用於指定排序時使用的比較方法。因此可以有兩種方式來改變預設的排序規則:1) 指定 map
模板中的排序引數;2) 修改所使用型別的比較函式。
預設的排序準則是 std::less<>
,如果像降序排列,可以使用 std::greater<>
,尖括號內為 key 的型別。或者想使用自定義的排序準則,可以自定義一個比較的函式:
struct StrLenCmp {
bool operator() (const string& lhs, const string& rhs) const {
return lhs.size() < rhs.size();
}
};
map<string, int, StrLenCmp> m;
儘管使用這樣自定義的函式甚至可以實現以 value 值進行排序,但是 map
中只能保證 key 值唯一,不能保證 value 值唯一,因此使用 value 作為排序依據得到的排序結果無法保證。如果真的需要使用 value 排序,可以將 pair 放入一個 vector 中進行排序。
set 與 multiset
set
與 map
同理,也可以設定排序準則。
unordered 容器
unordered 容器可以在模板引數中指定一個 hash 函式,也可以在模板引數中指定一個用於比較相等的函式,對於小型程式用的的概率不是很大,使用預設的引數就好。
補充:bitset
bitset
用來儲存一串布林值,使用 vector<bool>
也可以。具體的用法:
bitset<4> flag("1100");
bitset<16> flags(0xffff);
這種工具提供了下標訪問,翻轉 flip,以及用於檢查全部位的 any all。
其它細節
- 容器無法以 引用 作為元素,但是可以以 指標 作為元素。
- 一般情況下,容器在不指定其它構造方法的情況下,都可以推斷容器會進行預設構造。但是對於不是通過物件實現的型別,如基本型別,是沒有預設構造的,都需要進行手動地初始化。
clear()
成員函式會將容器置為空容器,不是把所有元素設為預設值。
列表初始化
列表初始化為 C++ 提供來統一的初始化方式。不管是基本型別,還是容器,以及自定義的型別,都可以使用列表初始化。在自定義型別中的建構函式中使用初始化列表,那麼就可以使用列表初始化來進行,這兩個是對應的。