以前的11個容器分別是
deque,list,queue,priority_queue,stack,vector,map,multimap,set,multiset,bitset
C++11新增:
array,forward_list,unordered_map,unordered_multimap,unordered_set,unordered_multiset
這裏只挑常用的講解。部分容器只作列表,沒作講述,持續更新。
以下內容只做大概的講述,詳情用法參考英文文檔 http://www.cplusplus.com/reference/s 或者中文文檔 http://classfoo.com/ccby/article/oC7Qu#sec_HL85sR
1.deque
deque雙向隊列是一種雙向開口的連續線性空間(雖說是連續性存儲空間,但這種連續性只是表面上的,實際上它的內存是動態分配的,它在堆上分配了一塊一塊的動態儲存區,每一塊動態存儲去本身是連續的,deque自身的機制把這一塊一塊的存儲區虛擬地連在一起。),可以高效的在頭尾兩端插入和刪除元素,deque在接口上和vector非常相似。
deque的實現比較複雜,內部會維護一個map(注意!不是STL中的map容器)即一小塊連續的空間,該空間中每個元素都是指針,指向另一段(較大的)區域,這個區域稱爲緩衝區,緩衝區用來保存deque中的數據。因此deque在隨機訪問和遍歷數據會比vector慢。
如果只是平時使用deque的話,可能就想不到其內部實現細節了。下面的圖片展示deque的內部結構設計:
可以看到,deque擁有一個bitmap結構(稱之爲map),map中每一個元素指向一塊連續的內存塊,後者纔是真正存儲deque元素的地方,因爲每個塊都是固定大小的,但是每個塊之間不要求是連續的,所以擴充空間的時候,就沒有vector那樣的副作用了。
鑑於deque的特殊設計,其迭代器也需要特殊設計,deque的iterator的設計結構如圖:
迭代器內部維護了四個指針,分別爲cur, first, last, node,具體在後面進行介紹。
deque的迭代器的設計,主要是讓其看起來像一個random access iterator,因此源碼主要各種operator的重載。內部結構分爲四個指針,分別爲cur, first, last, node。
在某一個時刻,迭代器肯定指向某個具體元素,而這個元素位於N段連續內存塊中的某一塊,其中node指向map結構中的一個節點(這個節點指向當前的內存塊),first指向當前內存塊的起始位置,cur指向當前內存塊中的特定元素節點,last指向當前內存塊的末尾位置,一圖抵千言,還是看圖來的明白:
[堆1]
...
[堆2]
...
[堆3]
每個堆保存好幾個元素,然後堆和堆之間有指針指向,看起來像是list和vector的結合品,不過確實也是如此deque可以讓你在前面快速地添加刪除元素,或是在後面快速地添加刪除元素,然後還可以有比較高的隨機訪問速度.vector是可以快速地在最後添加刪除元素,並可以快速地訪問任意元素list是可以快速地在所有地方添加刪除元素,但是隻能快速地訪問最開始與最後的元素
另外要注意一點。對於deque和vector來說,儘量少用erase(pos)和erase(beg,end)。因爲這在中間刪除數據後會導致後面的數據向前移動,從而使效率低下。
它首次插入一個元素,默認會動態分配512字節空間,當這512字節空間用完後,它會再動態分配自己另外的512字節空間,然後虛擬地連在一起。deque的這種設計使得它具有比vector複雜得多的架構、算法和迭代器設計。它的性能損失比之vector,是幾個數量級的差別。所以說,deque要慎用.
deque在開始和最後添加元素都一樣快,並提供了隨機訪問方法,像vector一樣使用[]訪問任意元素,但是隨機訪問速度比不上vector快,因爲它要內部處理堆跳轉。deque也有保留空間.另外,由於deque不要求連續空間,所以可以保存的元素比vector更大,這點也要注意一下.還有就是在前面和後面添加元素時都
不需要移動其它塊的元素,所以性能也很高。
以下情形,最好採用deque:
1)需要在兩端插入和刪除元素。
2)無需引用容器內的元素。
3)要求容器釋放不再使用的元素。
2.list
list是雙向循環鏈表,,每一個元素都知道前面一個元素和後面一個元素。在STL中,list和vector一樣,是兩個常被使用的容器。和vector不一樣的是,list不支持對元素的任意存取。list中提供的成員函數與vector類似,不過list提供對錶首元素的操作push_front、pop_front,這是vector不具備的。和vector另一點不同的是,list的迭代器不會存在失效的情況,他不像vector會保留備份空間,在超過容量額度時重新全部分配內存,導致迭代器失效;list沒有備份空間的概念,出入一個元素就申請一個元素的空間,所以它的迭代器不會失效。
3.queue
隊列是一種特殊的線性表,它只允許在表的前端(front)進行刪除操作,而在表的後端(rear)進行插入操作。進行插入操作的端稱爲隊尾,進行刪除操作的端稱爲隊頭。隊列中沒有元素時,稱爲空隊列。在隊列這種數據結構中,最先插入在元素將是最先被刪除;反之最後插入的元素將最後被刪除,因此隊列又稱爲“先進先出”(FIFO—first in first out)的線性表。
4.priority_queue
5.stack
c++stack(堆棧)是一個容器的改編,它實現了一個先進後出的數據結構(FILO)
6.vector
vector是STL中最常見的容器,它是一種順序容器,支持隨機訪問。vector是一塊連續分配的內存,從數據安排的角度來講,和數組極其相似,不同的地方就是:數組是靜態分配空間,一旦分配了空間的大小,就不可再改變了;而vector是動態分配空間,隨着元素的不斷插入,它會按照自身的一套機制不斷擴充自身的容量。
vector的擴充機制:按照容器現在容量的一倍進行增長。vector容器分配的是一塊連續的內存空間,每次容器的增長,並不是在原有連續的內存空間後再進行簡單的疊加,而是重新申請一塊更大的新內存,並把現有容器中的元素逐個複製過去,然後銷燬舊的內存。這時原有指向舊內存空間的迭代器已經失效,所以當操作容器時,迭代器要及時更新。
詳情見 http://blog.csdn.net/saya_/article/details/49178021
7.map
C++中map容器提供一個鍵值對容器,map與multimap差別僅僅在於multiple允許一個鍵對應多個值。map是一類關聯式容器,它是模板類。關聯的本質在於元素的值與某個特定的鍵相關聯,而並非通過元素在數組中的位置類獲取。它的特點是增加和刪除節點對迭代器的影響很小,除了操作節點,對其他的節點都沒有什麼影響。對於迭代器來說,不可以修改鍵值,只能修改其對應的實值。
map的功能:
1.自動建立Key - value的對應。key 和 value可以是任意你需要的類型,但是需要注意的是對於key的類型,唯一的約束就是必須支持<操作符。
2.根據key值快速查找記錄,查找的複雜度基本是Log(N),如果有1000個記錄,最多查找10次,1,000,000個記錄,最多查找20次。
3.快速插入Key - Value 記錄。
4.快速刪除記錄
5.根據Key 修改value記錄。
6.遍歷所有記錄。
8.multimap
9.set
關於set,必須說明的是set關聯式容器。set作爲一個容器也是用來存儲同一數據類型的數據類型,並且能從一個數據集合中取出數據,在set中每個元素的值都唯一,而且系統能根據元素的值自動進行排序。應該注意的是set中數元素的值不能直接被改變。C++ STL中標準關聯容器set, multiset, map, multimap內部採用的就是一種非常高效的平衡檢索二叉樹:紅黑樹,也成爲RB樹(Red-Black Tree)。RB樹的統計性能要好於一般平衡二叉樹,所以被STL選擇作爲了關聯容器的內部結構。
看下面的代碼
#include<iostream>
#include<set>
using namespace std;
int main()
{
set<int> s;
s.insert(2);
s.insert(1);
auto it=s.begin();
*it = 2;//error,不能修改值
for (auto x : s)
cout << x << endl;
return 0;
}
在set中查找是使用二分查找,也就是說,如果有16個元素,最多需要比較4次就能找到結果,有32個元素,最多比較5次。那麼有10000個呢?最多比較的次數爲log10000,最多爲14次,如果是20000個元素呢?最多不過15次。看見了吧,當數據量增大一倍的時候,搜索次數只不過多了1次,多了1/14的搜索時間而已。你明白這個道理後,就可以安心往裏面放入元素了。
10.multiset
11.bitset
有些程序要處理二進制位的有序集,每個位可能包含的是0(關)或1(開)的值。位是用來保存一組項或條件的yes/no信息(有時也稱標誌)的簡潔方法。標準庫提供了bitset類使得處理位集合更容易一些。注意下面的看不懂可以去我開頭推薦的網址,查看使用例子。
- (constructor)
- Construct bitset (public member function) //構造bitset.. 格式 bitset<長度> 名字
- applicable operators
- Bitset operators (functions) //可以直接對bitset容器進行二進制操作,如 ^,|,~,<<,>>等等
- operator[]
- Access bit (public member function) //可以用如數組形式的賦值。bitset<4> b; b[0]=1;
- set
- Set bits (public member function)//默認將容器中所有值賦爲1,也可以將特定的位置賦給特定的值
- 如 bitset<4> b; b.set(); //1111. b.set(2,0) // 1011.
- reset
- Reset bits (public member function) //默認將容器中所有值賦值爲0,也可以將特定位置賦特定的值
- flip
- Flip bits (public member function)//默認將容器中的數取反,1變0,0變1,也可以將特定位置取反bitset<4> b(string ("0001")); b.file(2); // 0101; b.file(); //1010
- to_ulong
- Convert to unsigned long integer (public member function) //將容器的值轉化成10進制的數
- to_string
- Convert to string (public member function) //將容器累的值轉爲字符串
- count
- Count bits set (public member function) //統計容器中1的個數
- size
- Return size (public member function) //容器的大小
- test
- Return bit value (public member function) //返回每個位置上的數
- any
- Test if any bit is set (public member function) //容器的值>0返回真,反之。
- none
- Test if no bit is set (public member function) //和any取反。容器的值==0返回真。反之
下面轉載一篇博客,原址是 http://blog.chinaunix.net/uid-24790746-id-252685.html
1.定義一個unsigned long型的副本
例:
unsigned long tmp = 1111;
bitset<32> bitvec(tmp);
那麼把bitvec對象給cout出來是:00000000000000000000000000001111嗎?
答案顯示是不對的,前幾天我馬虎了,原來bitset類的構造函數把tmp的十進制值給轉換到了二進制(10001010111),所以
bitvec輸出是:00000000000000000000010001010111。
如果想讓tmp直接輸出00000000000000000000000000001111的話,那麼只需將tmp更改爲十六進制0xf即可(unsgined long tmp = 0xf)。
2.定義一個string型的副本
例:
string str("1111");
bitset<32> bitvec(str);
那麼bitvec對象cout出來就是:00000000000000000000000000001111。
好了,我前幾天遇到的問題出來了,現在拿string型的副本來討論,現在bitset裏面到底是怎麼存儲str對象的字符串字面值呢??
bitvec對象裏面存儲的是00000000000000000000000000001111,
還是11110000000000000000000000000000呢?
還沒遇到這個模糊問題的時候,我用cout輸出bitvec對象的值,跟我用for循環從bitvec對象的第0位輸出到第31位的結果竟然是不一樣的。。
當執行:cout << bitvec; //result = 00000000000000000000000000001111;
當執行:
for (unsigned int ix = 0; ix != 32; ++ix)
{
cout << bitvec[ix];
}
//result = 11110000000000000000000000000000
現在已經很清楚的知道,bitvec對象是把string對象裏面的值先轉換成二進制值,然後再倒過來存儲。
用1111可能看得不是很清楚,
那麼現在:
string str("1010");
bitset<32> bitvec(str);
通過上面那個for循環輸出的結果是:01010000000000000000000000000000,
這樣看起來就比較直觀了。一般平時要輸出bitset對象時,都是用cout輸出,因爲cout已經重載了各種各樣的運算符,用for循環輸出只是爲了更加深入的去理解bitset罷了。
#include <iostream>
#include <bitset>
/*順便提一下,這裏爲什麼不直接使用"using namespace std;"比較簡便了,因爲這樣對於比較大的程序來
說不是很好的習慣,因爲使用這句話雖然簡便了,但是這樣導致把std命名空間的所有標準庫函數都引進到程序文件裏,這樣如
果以後程序涉及到同名函數名或變量名,將會出現難以調試的錯誤*/
using std::cout;
using std::endl;
using std::bitset;
int main()
{
bitset<32> bitvec("1010");
//1.
cout << bitvec;
cout << endl;
//2.
for (int ix = 0; ix != bitvec.size(); ++ix)
{
cout << bitvec[ix];
}
cout << endl;
//3.
for (int ix = bitvec.size() - 1; ix != -1; --ix)
{
cout << bitvec[ix];
}
cout << endl;
/*
00000000000000000000000000001010
01010000000000000000000000000000
00000000000000000000000000001010
*/
return 0;
}
12.forward_list
13.unordered_map
14.unordered_multimap
15.unordered_set
16.unordered_multiset