第一章:概論:
換句話說,STL所實現的,是依據泛型思維架設起來的一個概念結構。這個以抽象概念(abstract concepts)為主體而非以實際類(classes)為主體的結構,形成了一個嚴謹的接口標准。在此接口之下,任何組件都有最大的獨立性,並以所謂迭代器(iterator)膠合起來,或以所謂配接器(adapter)互相配接,或以所謂仿函數(functor)動態選擇某種策略(policy或strategy)。
STL提供六大組件
1.容器(containers):各種數據結構,如 vector,list,deque,set,map,用來存放數據,詳見本書4,5兩章。從實現的角度來看,STL容器是一種class template。就體積而言,這一部分很像冰山在海面下的比率。
2.算法(algorithms):各種常用算法如sort,search,copy,erase…詳見第6章。從實現的角度來看,STL算法是一種function template。
3.迭代器(iterators):扮演容器與算法之間的膠合劑,是所謂的“泛型指針”,詳見第3章。共有五種類型,以及其它衍生變化。從實現的角度來看,迭代器是一種將 operator*,operator->,operatort+,operator--等指針相關操作予以重載的class template。所有STL容器都附帶有自己專屬的迭代器——是的,只有容器設計者才知道如何遍歷自己的元素。原生指針(native pointer)也是一種迭代器。
4.仿函數(functors):行為類似函數,可作為算法的某種策略(policy),詳見第7章。從實現的角度來看,仿函數是一種重載了operator()的class或class template.一般函數指針可視為狹義的仿函數。
5.配接器(adapters):一種用來修飾容器(containers)或仿函數(functors)
或迭代器(iterators)接口的東西,詳見第8章。例如,STL提供的queue和stack,雖然看似容器,其實只能算是一種容器配接器,因為它們的底部完全借助 deque,所有操作都由底層的deque供應。改變functor接口者,稱為function adapter;改變container 接口者,稱為container adapter;改變iterator接口者,稱為iterator adapter。配接器的實現技術很難一言以蔽之,必須逐一分析,詳見第8章。
6.配置器(allocators):負責空間配置與管理,詳見第2章。從實現的角度來看,配置器是一個實現了動態空間配置、空間管理、空間釋放的class template。
class alloc{
};
C++11 STL中的容器
==================================================
一、順序容器:
vector:可變大小數組;
deque:雙端隊列;
list:雙向鏈表;
forward_list:單向鏈表;
array:固定大小數組;
string:與vector相似的容器,但專門用於保存字符。
==================================================
二、關聯容器:
按關鍵字有序保存元素:(底層實現為紅黑樹)
map:關聯數組;保存關鍵字-值對;
set:關鍵字即值,即只保存關鍵字的容器;
multimap:關鍵字可重復的map;
multiset:關鍵字可重復的set;
--------------------------------------------------------------------------------
無序集合:
【不會按字典規則進行排序】
unordered_map:用哈希函數組織的map;
unordered_set:用哈希函數組織的set;
unordered_multimap:哈希組織的map;關鍵字可以重復出現;
unordered_multiset:哈希組織的set;關鍵字可以重復出現。
==================================================
三、其他項:
1、stack、queue、valarray、bitset
隨機訪問中,[ ]與 .at( )功能相同,但是[ ]越界了會直接導致程序崩潰,而 .at( )會拋出異常,故其更安全!
2、size、capacity與shrink_to_fit
size表示目前容器的實際大小、
capacity表示容器的空間,一般 >=size,因為容器初始化或者賦值時,系統會根據情況給予容器一個適當的空間,避免每次增加數據時又得重新新分配空間,索性一次給多點,但也不會很大,
為了節省空間,你可以使用shrink_to_fit將容器空間capacity縮小為size
四、迭代器刪除失效:
1. vector,erase(pos),直接把pos+1到finish的數據拷貝到以pos為起點的區間上,也就是vector的長度會逐漸變短,同時iter會逐漸往后移動,直到iter == cont.end(),由於容器中end()返回的迭代器是最后一個元素的下一個(這個地方沒有任何值),現在考慮這個狀態前一個狀態,此時要刪除的點是iter, tempIt = iter, ++iter會指向此時的end(),但是執行erase(tempIt)之后,end()向前移動了!!!問題來了,此時iter空了!!!不崩潰才怪。
2. list,erase(pos),干的事情很簡單,刪除自己,前后的節點連接起來就完了,所以iter自增的過程不會指空,不會崩潰嘍。
3. map,erase(pos),干的事情太復雜,但是我們需要知道的信息其實很少。該容器底層實現是RBTree,刪除操作分了很多種情形來討論的,目的是為了維持紅黑樹性質。但是我們需要知道的就是每個節點類似於list節點,都是單獨分配的空間,所以刪除一個節點並不會對其他迭代器產生影響,對應到題目中,不會崩潰嘍。
4. deque,erase(pos),與vector的erase(pos)有些類似,基於結構的不同導致中間有些步驟不太一致。先說說deque的結構(這個結構本身比較復雜,揀重要說吧,具體看STL源碼),它是一個雙向開口的連續線性空間,實質是分段連續的,由中控器map維持其整體連續的假象。其實題中只要知道它是雙向開口的就夠了(可以在頭部或尾部增加、刪除)。在題中有erase(pos),deque是這樣處理的:如果pos之前的元素個數比較少,那么把start到pos-1的數據移到起始地址為start+1的區間內;否則把pos后面的數據移到起始地址為pos的區間內。在題中iter一直往后移動,總會出現后面數據比前面少的時候,這時候問題就和1一樣了,必須崩潰!
關聯容器(如map, set, multimap,multiset),【不會失效】刪除當前的iterator,只會使當前的iterator失效,只要在erase時,遞增當前iterator即可。
對於序列式容器(如vector,deque),【會失效】刪除當前的iterator會使后面所有元素的iterator都失效。這是因為vetor,deque使用了連續分配的內存,刪除一個元素導致后面所有的元素會向前移動一個位置。不過erase方法可以返回下一個有效的iterator,cont.erase(iter++)可以修改為cont.erase(iter)
list使用了不連續分配的內存,並且它的erase方法也會返回下一個有效的iterator。
五、為不同的容器選擇不同刪除方式
刪除連續容器(vector,deque,string)的元素
1 // 當c是vector、string,刪除value 2 c.erase(remove(c.begin(), c.end(), value), c.end()); 3 4 // 判斷value是否滿足某個條件,刪除 5 bool assertFun(valuetype); 6 c.erase(remove_if(c.begin(), c.end(), assertFun), c.end()); 7 8 // 有時候我們不得不遍歷去完成,並刪除 9 for(auto it = c.begin(); it != c.end(); ){ 10 if(assertFun(*it)){ 11 ··· 12 it = c.erase(it); 13 } 14 else 15 ++it; 16 } 17 18 刪除list中某個元素 19 20 c.remove(value); 21 22 // 判斷value是否滿足某個條件,刪除 23 c.remove(assertFun); 24 25 刪除關聯容器(set,map)中某個元素 26 27 c.erase(value) 28 29 for(auto it = c.begin(); it != c.end(); ){ 30 if(assertFun(*it)){ 31 ··· 32 c.erase(it++); 33 } 34 else 35 ++it; 36 } 37
各大容器性能對比:
1、vector
變長一維數組,連續存放的內存塊,有保留內存,堆中分配內存;
支持[]操作,高效率的隨機訪問;
在最后增加元素時,一般不需要分配內存空間,速度快;在中間或開始操作元素時要進行內存拷貝效率低;
vector高效的原因在於配置了比其所容納的元素更多的內存,內存重新配置會花很多時間;
vector的內存分配:
一般是按你當時存儲數據的兩倍開辟空間,當插入數據空間不夠時,又會重新增加空間至當時數據空間的2倍,此動作降低了vector的工作效率!!!
注:需要高效的隨即存取,而不在乎插入和刪除使用vector。
2、list
雙向鏈表,內存空間上可能是不連續的,無保留內存,堆中分配內存;
不支持隨機存取,開始和結尾元素的訪問時間快,其它元素都O(n);
在任何位置安插和刪除元素速度都比較快,安插和刪除操作不會使其他元素的各個pointer,reference,iterator失效;
注:大量的插入和刪除,而不關系隨即存取使用list。
3、deque
雙端隊列,在堆上分配內存,一個堆保存幾個元素,而堆之間使用指針連接;
支持[]操作,在首端和末端插入和刪除元素比較快,在中部插入和刪除則比較慢,像是list和vector的結合;
注:關心插入和刪除並關心隨即存取折中使用deque。
4、set&multiset
有序集合,使用平衡二叉樹存儲,按照給定的排序規則(默認按less排序)對set中的數據進行排序;
set中不允許有重復元素,multiset中運行有重復元素;
兩者不支持直接存取元素的操作;
因為是自動排序,查找元素速度比較快;
不能直接改變元素值,否則會打亂原本正確的順序,必須先下刪除舊元素,再插入新的元素。
5、map&multimap
字典庫,一個值映射成另一個值,使用平衡二叉樹存儲,按照給定的排序規則對map中的key值進行排序;
map中的key值不允許重復,multimap中的key允許重復;
根據已知的key值查找元素比較快;
插入和刪除操作比較慢。
6、hash_map
hash_map使用hash表來排列配對,hash表是使用關鍵字來計算表位置。當這個表的大小合適,並且計算算法合適的情況下,hash表的算法復雜度為O(1)的,但是這是理想的情況下的,如果hash表的關鍵字計算與表位置存在沖突,那么最壞的復雜度為O(n)。
選用map還是hash_map,關鍵是看關鍵字查詢操作次數,以及你所需要保證的是查詢總體時間還是單個查詢的時間。如果是要很多次操作,要求其整體效率,那么使用hash_map,平均處理時間短。如果是少數次的操作,使用 hash_map可能造成不確定的O(N),那么使用平均處理時間相對較慢、單次處理時間恆定的map,便更好些。
STL容器對比:
|
vector |
deque |
list |
set |
multiset |
map |
multimap |
名稱 |
向量容器 |
雙向隊列容器 |
列表容器 |
集合 |
多重集合 |
映射 |
多重映射 |
內部數 據結構 |
連續存儲的數組形式(一端開口的組) |
連續或分段連續存儲數組(兩端 開口的數組) |
雙向環狀鏈表 |
紅黑樹(平衡檢索二叉樹) |
紅黑樹 |
紅黑樹 |
紅黑樹 |
特 點 |
獲取元素效率很高,插入和刪除的 效率很低 |
獲取元素效率較高,插入和刪除的效率較高 |
獲取元素效率很低,插入和刪除的效率很高 |
1.鍵(關鍵字)和值(數據)相等(就是模版只有一個參數,鍵和值合起來) 2.鍵唯一 3.元素默認按升序排列 |
1.鍵和值相等 2.鍵可以不唯一 3.元素默認按升序排列 |
1.鍵和值分開(模版有兩個參數,前面是鍵后面是值) 2.鍵唯一 3.元素默認按鍵的升序排列 |
1.鍵和值分開 2.鍵可以不唯一 3.元素默認按鍵的升序排列 |
頭文件 |
#include <vector> |
#include <deque> |
#include <list> |
#include <set> |
#include <set> |
#include <map> |
#include <map> |
操作元素的方式 |
下標運算符:[0](可以用迭代器,但插入刪除操作時會失效) |
下標運算符或迭代器 |
只能用迭代器(不斷用變量值來遞推新值,相當於指針),不支持使用下標運算符 |
迭代器 |
迭代器 |
迭代器 |
迭代器 |
插入刪除操作迭代器是否失效 |
插入和刪除元素都會使迭代器失效 |
插入任何元素都會使迭代器失效。刪除頭和尾元素,指向被刪除節點迭代器失效,而刪除中間元素會使所有迭代器失效 |
插入,迭代器不會失效。刪除,指向被刪除節點迭代器失效 |
插入,迭代器不會失效。刪除,指向被刪除節點迭代器失效 |
插入,迭代器不會失效。刪除,指向被刪除節點迭代器失效 |
插入,迭代器不會失效。刪除,指向被刪除節點迭代器失效 |
插入,迭代器不會失效。刪除,指向被刪除節點迭代器失效 |
總結:
|
vector |
deque |
list |
set |
multiset |
map |
multimap |
典型內存結構 |
單端數組 |
雙端數組 |
雙向鏈表 |
二叉樹 |
二叉樹 |
二叉樹 |
二叉樹 |
可隨機存取 |
是 |
是 |
否 |
否 |
否 |
對 key 而言:不是 |
否
|
|
vector |
deque |
list |
set |
multiset |
map |
multimap |
元素搜尋速度 |
慢 |
慢 |
非常慢 |
快 |
快 |
對 key 而言:快 |
對 key 而言:快 |
元素安插移除 |
尾端 |
頭尾兩端 |
任何位置 |
- |
- |
- |
- |
容器自定義比較方式:
1 struct fruit 2 { 3 string name; 4 int price; 5 friend bool operator <(fruit fl, fruit f2) 6 { 7 return fl. price>f2. price; 8 } 9 };
STL容器新穎使用
將數組中的值直接付給容器
int arry[size] = {0,1,2,3,4,5,6};
xxx<int>contain(arry, arry+size);//即將數組中的值直接初始化賦予了容器中
簡介SGI:
vector:
1 class alloc{ 2 3 }; 4 5 template <class T,class Alloc=alloc> 6 7 class vector{ 8 9 public: 10 11 void swap(vector<T,Alloc>&){ 12 13 cout<<"swap()"<< endl; 14 15 } 16 17 }; 18 19 vector<int>x,y; 20 21 template <class T,class Alloc=alloc> 22 class vector{ 23 public: 24 typedef T value_type; 25 typedef value_type* iterator; 26 template <class I> 27 void insert(iterator position,I first,I last){ 28 cout <<"insert()"<< endl; 29 } 30 }; 31 vector<int>x; 32 vector<int>::iterator ite;
stack:
1 template <class T,clase A1loc=alloc,size_t BufSiz=0> 2 3 class deque{ 4 5 public: 6 7 deque(){ 8 9 cout <<"deque"<< endl; 10 11 } 12 13 }; 14 15 //根據前一個參數值T,設定下一個參數Sequence的默認值為deque<T> 16 17 template <class T,claas Sequence= deque<T>> 18 19 class stack{ 20 21 public: 22 23 stack(){ 24 25 cout <<"stack"<< end1; 26 27 } 28 29 30 31 private: 32 33 Sequence c; 34 35 }; 36 37 stack<int>x; 38 39 40 template <class T,class Alloc=alloc,size_t BufSiz=0> 41 class deque{ 42 public: 43 deque(){ 44 cout <<"deque"<<''; 45 } 46 }; 47 48 template <class T,class Sequence> 49 class stack; 50 51 template <class T,class Sequence> 52 bool operator==(const stack<T,Sequence>&x,const stack<T,Sequence>&y); 53 54 template <class T,class Sequence> 55 bool operator<(const stack<T,Sequence>&x,const stack<T,Sequence>&y); 56 57 58 template <class T,class Sequence=deque<T>> 59 class stack{ 60 //寫成這樣是可以的 61 friend bool operator==<T>(const stack<T>&,const stack<T>&); 62 friend bool operator< <T>(const stack<T>&,const stack<T>&); 63 //寫成這樣也是可以的 64 friend bool operator==<T> (const stack&,const stack&); 65 friend bool operator< <T> (const stack&,const stack&); 66 //寫成這樣也是可以的 67 friend bool operator==<> (const stack&,const stack&); 68 friend bool operator<<>(const stack&,const stack&); 69 //寫成這樣就不可以 70 //friend bool operator==(const stack&,const stack&); 71 //friend bool operator<(const stack&,const stack&); 72 public: 73 stack(){cout <<"stack"<<endl;} 74 75 private: 76 Sequence c; 77 }; 78 79 template <class T, class Sequence> 80 bool operator==(const stack<T, Sequence>& x, const stack<T, Sequence>&y){ 81 return cout <<"operator=="<<'\t'; 82 } 83 template <class T, class Sequence> 84 bool operator<(const stack<T, Sequence>& x, const stack<T, Sequence>&y){ 85 return cout <<"operator<"<<'\t'; 86 } 87 int main(){ 88 stack<int>x;//deque stack 89 stack<int>y;//deque stack 90 cout <<(x==y)<<endl;//operator==1 91 cout <<(x<y)<<end1;//operator<1 92 93 stack<char>y1;//deque stack 94 //cout c<(x== y1)<< endl; //error: no match for... 95 //cout <<(x< y1)<< end1;//error: no match for... 96 }
deque:
1 inline sizet _deque_buf_size(size_t n,size_t sz){ 2 3 return n!=0?n:(sz<512?size_t(512/sz):size_t(1)); 4 5 } 6 7 8 9 template <class T,class Ref,class Ptr,size_t BufSiz> 10 11 struct _deque_iterator{ 12 13 typedef _deque_iterator<T,T&,T*,BufSiz> iterator; 14 15 typedef __deque_iterator<T,const T&,const T*,BufSiz>const_iterator; 16 17 18 19 static size_t buffer_size(){ 20 21 return deque_ buf_size(Bufsiz,sizeof(T)); 22 23 } 24 25 }; 26 27 28 29 template <class T,class Alloc=alloc,size_t Buf8iz=0> 30 31 class deque{ 32 33 typedef deque_iterator<T,T&,T*,BufSiz> iterator; 34 35 }; 36 37 38 39 cout << deque<int>::iterator::buffer_size()<<end1;//128 40 41 cout << deque<int,alloc,64>::iterator::buffer_size()<<endl;//64
第二章、空間配置:
(1)考慮到小型區塊所可能造成的內存破碎問題,SGI設計了雙層配置器,第一級配置器直接使用malloc和free,第二級配置器則視情況采用不用的策略(需求區塊是否大於128bytes)。
第一級適配器以malloc(),free(),reaclloc()等C函數執行實際的內存配置、釋放、重配置操作。第二級配置器多了一些機制,避免太多小額區塊造成內存的碎片。
SGI第二級配置器的做法是,如果區塊夠大,超過128字節時,就移交第一級配置器處理。當區塊小於128字節時,則以內存池(memory pool)管理,此法又稱為次層配置(sub-allocation):每次配置一大塊內存,並維護對應的自由鏈表(free-list)。下次若再有相同大小的內存需求,就直接從free-lists中拔出。如果沒有,則向系統要一大塊內存,然后做切割,此時切割出來的小內存塊,不帶cookie。如果客戶端釋放小額區塊,就由配置器回收。為方便管理,任何小額區塊的內存需求量上調至8的倍數,並維護16個free-lists,各自管理大小分別為8,16,24...128字節。
在G4.9中編譯器使用的是不作任何優化的空間配置器,如果需要制定,則需要指明第二參數:
vector<string, __gnu_cxx::__pool_alloc<string>> vec;
如果free-list中沒有可用的區塊,將區塊大小上調至8的倍數邊界,然后調用refill(),准備為free-list重新填充空間。refill()之后介紹。
空間的釋放,大於128字節就調用第一級配置器,小於128字節就找出對應的free list,將區塊回收。
refill()重新填充空間,新的空間將取自內存池(經由chunk_alloc()完成)。內存池實際操練結果如下圖:
(2)第一級配置器直接調用C函數執行實際的內存操作,並實現類似C++ new handler的機制。
(3)當區塊超過128bytes時,視之為足夠大,便調用第一級配置器。當配置器小於128bytes時,視之為過小,便采用內存池的方式。每次配置一大塊內存,並維護對應之自由鏈表,下次若再有相同大小的內存需求,就直接從free-list中拔出。如果客端釋還小額區塊,就由配置器回收到free-list中。為了方便管理,SGI第二季配置器會主動將任何小額區塊的內存需求量上調至8的倍數(例如客端要求30bytes,就自動調整為32bytes),並維護16個free-list,大小分別是8,16,24,32,40,48,56,64,72,80,88,96,104,112,120,128bytes。
(4)內存池
如果水量充足,就直接調出20個區塊返回給free-list,如果不夠20則返回不足實際個數。如果一個都拿不出,則調用malloc配置40個區塊的空間,其中20個給free-list,剩下的20個留在內存池。如果malloc分配失敗,則調用第一級配置器。因為第一級配置器有new-handler機制,獲取能夠整理出多余的空間。、
(5)內存基本處理工具
uninitialized_copy,uninitialized_fill,uninitialized_fill_n,能夠將內存配置與對象構造行為分離開來。並且會對POD(即標量型別或傳統C struct)對象采用最有效率的初值填寫手法,而對non-POD型別采取最保險安全的做法。
為了細化分工,STL allocator將兩階段操作區分開來。內存配置操作由alloc::allocate()負責,內存釋放操作由alloc::deallocate()負責;對象構造操作由::construct負責,對象析構操作由::destroy()負責。
- 內存池:
當申請空間不充足時,系統首先將剩余的空間盡量為你申請出來
一般新申請的空間大小為你所需求空間大小的2倍加上一個隨機配置次數增加愈加增大的附加量
第三章、迭代器概念
STL的中心思想在於:將數據容器(containers)和算法(algorithms)分開,彼此獨立設計,最后再以一帖膠着劑將它們撮合在一起。
迭代器相應型別:
迭代器相應型別之一:value type:
所謂 value type,是指迭代器所指對象的型別。任何一個打算與STL算法有完美搭配的class,都應該定義自己的vlue type內嵌型別,做法就像上節所述。
迭代器相應型別之二:difference type:
difference type用來表示兩個迭代器之間的距離,因此它也可以用來表示一個容器的最大容量,因為對於連續空間的容器而言,頭尾之間的距離就是其最大容量。
如果一個泛型算法提供計數功能,例如STL的count(),其傳回值就必須使用迭代器的diference type:
迭代器相應型別之三:reference type
從“迭代器所指之物的內容是否允許改變”的角度觀之,迭代器分為兩種:
不允許改變“所指對象之內容”者,稱為constant iterators,例如 const int *pic;【視為不可改的右值】
允許改變“所指對象之內容”者,稱為mutable iterators,例如int *pi。當我們對一個mutable iterators進行提領操作時,獲得的不應該是一個右值(rvalue),應該是一個左值(lvalue),因為右值不允許賦值操作(assignment),左值才允許:
int *pi=new int(5);
const int *pci=new int(9);
*pi=7;//對mutable iterator進行提領操作時,獲得的應該是個左值,允許賦值
*pci=1;//這個操作不允許,因為pci是個constant iterator,
//提領pci所得結果,是個右值,不允許被賦值
迭代器相應型別之四:pointer type
pointers和references在C++中有非常密切的關聯。如果“傳回一個左值,令它代表p所指之物”是可能的,那么“傳回一個左值,令它代表p所指之物的地址”也一定可以。也就是說,我們能夠傳回一個pointer,指向迭代器所指之物。
迭代器相應型別之五:iterator_category
最后一個(第五個)迭代器的相應型別會引發較大規模的寫代碼工程。在那之前,我必須先討論迭代器的分類。
根據移動特性與施行操作,迭代器被分為五類:
·Input lterator:這種迭代器所指的對象,不允許外界改變。只讀(read only)。
·Output terator:唯寫(write only)。
·Forward lterator:允許“寫入型”算法(例如replace())在此種迭代器所形成的區間上進行讀寫操作。
·Bidirectiona lterator:可雙向移動。某些算法需要逆向走訪某個迭代器區間(例如逆向拷貝某范圍內的元素),可以使用Biairectional lterators。
·Random Access lterator:前四種迭代器都只供應一部分指針算術能力(前三種支持 operator++,第四種再加上operator--),第五種則涵蓋所有指針算術能力,包括p+n,p-n,p[n],pl-p2,p1<p2。