C++ 裏的容器很多,但可以按照不同的標準進行分類,常見的一種分類是依據元素的訪問方式,分成順序容器、有序容器和無序容器三大類別。
1. 容器的基本特徵
容器就是對數據結構的抽象和封裝,即能夠“容納”“存放”元素的一些數據結構。
C++ 標準庫裏的容器幫助我們實現了最基本和最經典的數據結構,且容器的性能和優化水平已經非常完善,這一塊不需要自己造輪子。
容器的基本特性:保存元素採用的是“值”(value)語義,容器裏存儲的是元素的拷貝、副本,而不是引用。
關於拷貝操作
容器操作元素的很大一塊成本就是值的拷貝,如果元素比較大,或者非常多,那麼操作時的拷貝開銷就會很高,性能也就不會太好。常用的解決方法如下:
- 方法1:爲元素實現轉移構造和轉移賦值函數,在加入容器的時候使用 std::move() 來“轉移”,減少元素複製的成本:
Point p; // 一個拷貝成本很高的對象
v.push_back(p); // 存儲對象,拷貝構造,成本很高
v.push_back(std::move(p)); // 定義轉移構造後就可以轉移存儲,降低成本
- 方法2:使用 C++11 爲容器新增加的 emplace 操作函數,它可以“就地”構造元素,免去了構造後再拷貝、轉移的成本,不但高效,而且用起來也很方便:
v.emplace_back(...); // 直接在容器裏構造元素,不需要拷貝或者轉移
- 方法3: 使用指針
可以使用智能指針 unique_ptr/shared_ptr,來自動管理元素。
一般情況下,shared_ptr 是一個更好的選擇,它的共享語義與容器的值語義基本一致。
使用 unique_ptr 就要當心,它不能被拷貝,只能被轉移。
注意: 不建議在容器裏存放元素的指針,來間接保存元素。
原因: 指針的開銷很低,但因爲它是“間接”持有,導致不能利用容器自動銷燬元素的特性,而且必須要自己手動管理元素的生命週期,非常容易出錯,且有內存泄漏的隱患。
2. 容器的具體分類
C++中標準容器分3類:順序容器、有序容器和無序容器,如下圖,圖片來自[羅劍鋒c++實戰筆記]:
優先選擇的應該是 array 和 vector,因爲速度最快,開銷最低;
- list 是鏈表結構,插入刪除的效率高,但查找效率低;
- 有序容器是紅黑樹結構,對 key 自動排序,查找效率高,但有插入成本;
- 無序容器是散列表結構,由 hash 值計算存儲位置,查找和插入的成本都很低;
- 有序容器和無序容器都屬於關聯容器,元素有 key 的概念,操作元素實際上是在操作 key,所以要定義對 key 的比較函數或者散列函數。
Trick:多利用類型別名,而不要“寫死”容器定義。因爲容器的大部分接口是相同的,所以只要變動別名定義,就能夠隨意改換不同的容器,對於開發、測試都非常方便。