一、STL(Standard Template Library),即標準模板庫,是一個高效的C++程序庫,包含了諸多經常使用的基本數據結構和基本算法。爲廣大C++程序員們提供了一個可擴展的應用框架,高度體現了軟件的可複用性。前端
二、從邏輯層次來看,在STL中體現了泛型化程序設計的思想(generic programming)。在這種思想裏,大部分基本算法被抽象,被泛化,獨立於與之對應的數據結構,用於以相同或相近的方式處理各類不一樣情形。ios
三、從實現層次看,整個STL是以一種類型參數化(type parameterized)的方式實現的,基於模板(template)。程序員
STL有六大組件,但主要包含容器、迭代器和算法三個部分。算法
STL 的基本觀念就是將數據和操做分離。數據由容器進行管理,操做則由算法進行,而迭代器在二者之間充當粘合劑,使任何算法均可以和任何容器交互運做。這一篇博客暫時只介紹容器,下一篇介紹迭代器。數組
容器用來管理某類對象。爲了應付程序中的不一樣需求,STL 準備了兩類共七種基本容器類型:數據結構
示意圖以下圖所示:框架
vector(向量): 是一種序列式容器,事實上和數組差很少,但它比數組更優越。通常來講數組不能動態拓展,所以在程序運行的時候不是浪費內存,就是形成越界。而 vector 正好彌補了這個缺陷,它的特徵是至關於可拓展的數組(動態數組),它的隨機訪問快,在中間插入和刪除慢,但在末端插入和刪除快。函數
特色性能
優缺點和適用場景spa
優勢:支持隨機訪問,即 [] 操做和 .at(),因此查詢效率高。 缺點:當向其頭部或中部插入或刪除元素時,爲了保持本來的相對次序,插入或刪除點以後的全部元素都必須移動,因此插入的效率比較低。 適用場景:適用於對象簡單,變化較小,而且頻繁隨機訪問的場景。 |
例子
如下例子針對整型定義了一個 vector,插入 6 個元素,而後打印全部元素:
#include <iostream> #include <vector> using namespace std; int main(int argc, char* argv[]) { vector<int> vecTemp; for (int i = 0; i<6; i++) vecTemp.push_back(i); for (int i = 0; i<vecTemp.size(); i++) cout << vecTemp[i] <<" "; // 輸出:0 1 2 3 4 5 return 0; }
deque(double-ended queue)是由一段一段的定量連續空間構成。一旦要在 deque 的前端和尾端增長新空間,便配置一段定量連續空間,串在整個 deque 的頭端或尾端。所以不論在尾部或頭部安插元素都十分迅速。 在中間部分安插元素則比較費時,由於必須移動其它元素。deque 的最大任務就是在這些分段的連續空間上,維護其總體連續的假象,並提供隨機存取的接口。
特色
優缺點和適用場景
優勢:支持隨機訪問,即 [] 操做和 .at(),因此查詢效率高;可在雙端進行 pop,push。 缺點:不適合中間插入刪除操做;佔用內存多。 適用場景:適用於既要頻繁隨機存取,又要關心兩端數據的插入與刪除的場景。 |
例子
如下例子聲明瞭一個浮點類型的 deque,並在容器尾部插入 6 個元素,最後打印出全部元素。
#include <iostream> #include <deque> using namespace std; int main(int argc, char* argv[]) { deque<float> dequeTemp; for (int i = 0; i<6; i++) dequeTemp.push_back(i); for (int i = 0; i<dequeTemp.size(); i++) cout << dequeTemp[i] << " "; // 輸出:0 1 2 3 4 5 return 0; }
List 由雙向鏈表(doubly linked list)實現而成,元素也存放在堆中,每一個元素都是放在一塊內存中,他的內存空間能夠是不連續的,經過指針來進行數據的訪問,這個特色使得它的隨機存取變得很是沒有效率,所以它沒有提供 [] 操做符的重載。可是因爲鏈表的特色,它能夠頗有效率的支持任意地方的插入和刪除操做。
特色
優缺點和適用場景
優勢:內存不連續,動態操做,可在任意位置插入或刪除且效率高。 缺點:不支持隨機訪問。 適用場景:適用於常常進行插入和刪除操做而且不常常隨機訪問的場景。 |
例子
如下例子產生一個空 list,準備放置字符,而後將 'a' 至 'z' 的全部字符插入其中,利用循環每次打印並移除集合的第一個元素,從而打印出全部元素:
#include <iostream> #include <list> using namespace std; int main(int argc, char* argv[]) { list<char> listTemp; for (char c = 'a'; c <= 'z'; ++c) listTemp.push_back(c); while (!listTemp.empty()) { cout <<listTemp.front() << " "; listTemp.pop_front(); } return 0; }
成員函數empty()
的返回值告訴咱們容器中是否還有元素,只要這個函數返回 false,循環就繼續進行。循環以內,成員函數front()
會返回第一個元素,pop_front()
函數會刪除第一個元素。
注意:list <指針> 徹底是性能最低的作法,還不如直接使用 list <對象> 或使用 vector <指針> 好,由於指針沒有構造與析構,也不佔用很大內存。
set(集合)由紅黑樹實現,其內部元素依據其值自動排序,每一個元素值只能出現一次,不容許重複。
特色
優缺點和適用場景
優勢:使用平衡二叉樹實現,便於元素查找,且保持了元素的惟一性,以及能自動排序。 缺點:每次插入值的時候,都須要調整紅黑樹,效率有必定影響。 適用場景:適用於常常查找一個元素是否在某羣集中且須要排序的場景。 |
例子
下面的例子演示 set(集合)的兩個特色:
#include <iostream> #include <set> using namespace std; int main(int argc, char* argv[]) { set<int> setTemp; setTemp.insert(3); setTemp.insert(1); setTemp.insert(2); setTemp.insert(1); set<int>::iterator it; for (it = setTemp.begin(); it != setTemp.end(); it++) { cout << *it << " "; } return 0; }
輸出結果:1 2 3。一共插入了 4 個數,可是集合中只有 3 個數而且是有序的,可見以前說過的 set 集合的兩個特色,有序和不重複。
當 set 集合中的元素爲結構體時,該結構體必須實現運算符 ‘<’ 的重載:
#include <iostream> #include <set> #include <string> using namespace std; struct People { string name; int age; bool operator <(const People p) const { return age < p.age; } }; int main(int argc, char* argv[]) { set<People> setTemp; setTemp.insert({"張三",14}); setTemp.insert({ "李四", 16 }); setTemp.insert({ "隔壁老王", 10 }); set<People>::iterator it; for (it = setTemp.begin(); it != setTemp.end(); it++) { printf("姓名:%s 年齡:%d\n", (*it).name.c_str(), (*it).age); } return 0; } /* 輸出結果 姓名:王二麻子 年齡:10 姓名:張三 年齡:14 姓名:李四 年齡:16 */
能夠看到結果是按照年齡由小到大的順序排列。另外 string 要使用c_str()
轉換一下,不然打印出的是亂碼。
另外 Multiset 和 set 相同,只不過它容許重複元素,也就是說 multiset 可包括多個數值相同的元素。這裏再也不作過多介紹。
map 由紅黑樹實現,其元素都是 「鍵值/實值」 所造成的一個對組(key/value pairs)。每一個元素有一個鍵,是排序準則的基礎。每個鍵只能出現一次,不容許重複。
map 主要用於資料一對一映射的狀況,map 內部自建一顆紅黑樹,這顆樹具備對數據自動排序的功能,因此在 map 內部全部的數據都是有序的。好比一個班級中,每一個學生的學號跟他的姓名就存在着一對一映射的關係。
特色
優缺點和適用場景
優勢:使用平衡二叉樹實現,便於元素查找,且能把一個值映射成另外一個值,能夠建立字典。 缺點:每次插入值的時候,都須要調整紅黑樹,效率有必定影響。 適用場景:適用於須要存儲一個數據字典,並要求方便地根據key找value的場景。 |
例子
#include "stdafx.h" #include <iostream> #include <map> #include <string> using namespace std; int main(int argc, char* argv[]) { map<int, string> mapTemp; mapTemp.insert({ 5,"張三" }); mapTemp.insert({ 3, "李四"}); mapTemp.insert({ 4, "隔壁老王" }); map<int, string>::iterator it; for (it = mapTemp.begin(); it != mapTemp.end(); it++) { printf("學號:%d 姓名:%s\n", (*it).first, (*it).second.c_str()); } return 0; } /* 輸出結果: 學號:3 姓名:李四 學號:4 姓名:隔壁老王 學號:5 姓名:張三 */
multimap 和 map 相同,但容許重複元素,也就是說 multimap 可包含多個鍵值(key)相同的元素。這裏再也不作過多介紹。
除了以上七個基本容器類別,爲知足特殊需求,STL還提供了一些特別的(而且預先定義好的)容器配接器,根據基本容器類別實現而成。包括:
一、stack
名字說明了一切,stack 容器對元素採起 LIFO(後進先出)的管理策略。
二、queue
queue 容器對元素採起 FIFO(先進先出)的管理策略。也就是說,它是個普通的緩衝區(buffer)。
三、priority_queue
priority_queue 容器中的元素能夠擁有不一樣的優先權。所謂優先權,乃是基於程序員提供的排序準則(缺省使用 operators)而定義。Priority queue 的效果至關於這樣一個 buffer:「下一元素永遠是queue中優先級最高的元素」。若是同時有多個元素具有最髙優先權,則其次序無明肯定義。
各容器的特色總結
在實際使用過程當中,到底選擇這幾種容器中的哪個,應該根據遵循如下原則:
一、若是須要高效的隨機存取,不在意插入和刪除的效率,使用 vector。 二、若是須要大量的插入和刪除元素,不關心隨機存取的效率,使用 list。 三、若是須要隨機存取,而且關心兩端數據的插入和刪除效率,使用 deque。 四、若是打算存儲數據字典,而且要求方便地根據 key 找到 value,一對一的狀況使用 map,一對多的狀況使用 multimap。 五、若是打算查找一個元素是否存在於某集合中,惟一存在的狀況使用 set,不惟一存在的狀況使用 multiset。 |
各容器的時間複雜度分析
各容器的共性
各容器通常來講都有下列函數:默認構造函數、複製構造函數、析構函數、empty()、max_size()、size()、operator=、operator<、operator<=、operator>、operator>=、operator==、operator!=、swap()。
順序容器和關聯容器都共有下列函數:
begin()
:返回容器第一個元素的迭代器指針;end()
:返回容器最後一個元素後面一位的迭代器指針;rbegin()
:返回一個逆向迭代器指針,指向容器最後一個元素;rend()
:返回一個逆向迭代器指針,指向容器首個元素前面一位;clear()
:刪除容器中的全部的元素;erase(it)
:刪除迭代器指針it處元素。參考:
《C++標準庫 - 侯捷》中的 5.2 節-容器