直接逼入正題。 Standard Template Library簡稱STL。STL可分爲容器(containers)、迭代器(iterators)、空間配置器(allocator)、配接器(adaptors)、算法(algorithms)、仿函數(functors)六個部分。html
迭代器和泛型編程的思想在這裏幾乎用到了極致。模板或者泛型編程其實就是算法實現時不指定具體類型,而由調用的時候指定類型,進行特化。在STL中,迭代器保證了STL中算法的連續性和緊密型,使得各算法不須要考慮具體的數據結構。node
仿函數的實質就是在結構體中重載operator ()、<、>等操做符。仿函數實際上更多的是一種概念和思想性的東西,而非一種物質上的實際提高。算法
容器和算法是STL中最爲核心的部件,一切工做的鋪墊(例如迭代器、空間配置器等)都是根據他們而展開的。下面概要的講一下容器和算法的主要內容。編程
容器:數組
一、vector數據結構
vector實際上是一種順序性的容器,固然這裏的順序不是說排好序,而是說操做的時候按照必定的次序關係。另外,vector和數組array是很是類似的,最大的不一樣是array是靜態的,而vector是動態,這就是爲何大部分時候使用vector的緣由了,由於咱們每每不知道數組開多大比較合適,開大了浪費,開小了保存不下來,選擇vector是一個比較優的選擇。其實vector本質上就是一個array,只不過它是一個變化的array,那麼一個固定大小的array怎麼變成vector的呢?咱們看一下它的數據結構或許會更加明白一些。架構
1 template<class T,class Alloc=alloc>
2 class vector{ 3 public: 4 typedef T value_type; 5 typedef value_type* point; 6 typedef value_type* iterator; 7 typedef value_type& reference; 8 typedef size_t size_type; 9 ... 10 protected: 11 iterator start;//目前使用空間的頭部
12 iterator finish;//目前使用空間的尾
13 iterator end_of_storage;//可以使用空間的尾
14 ... 15 };
咱們注意到vector中的start,finish,end_of_storage。函數
好了,有了這三個變量,一切就有了答案。好比咱們初始化vector的大小爲10,那麼end_of_storage=start+10了。咱們能夠經過維護finish這個變量來使得它看起來是動態的,好比咱們插入5個值,那麼經過計算finish與start的差值就能獲得size()爲5了。咱們還能夠繼續插入,只不過此時須要finish向後移動一位就OK。可是,隨之而來的問題又來了,當finish等於end_of_storage以後怎麼辦呢?顯然,若是要繼續插入的話,不能進行操做啊。spa
莫慌,請別懷疑程序猿是智商最高的那一類,咱們新建一個array,此次申請的空間比上一個大那不就OK了嗎?同時把舊的array複製到newarray中去,同時delete掉old array。這樣就保證了還能夠繼續添加。可是有時候申請資源會不成功哦,因此插入操做並不老是OK的啦。程序猿的世界,bug老是無處不在。這個時候須要重回old array,同時delete 掉新申請的array.翻譯
咱們已經解決掉了怎麼使一個固定大小的array變成一個動態vector的問題,就是建一個更大的array。那麼更大究竟是多大呢?STL中把這個更大定義爲兩倍的當前SIZE.這個多是根據經驗獲得的數據,兩倍會是一個比較優的解。
vector中提供了insert,erase,pushback還有重載操做符等方法。其實insert就是在後面添加一個值,vector容量不夠了先擴容再insert.
erase也就是擦掉某個值的操做其實就是刪除某個值,而後後面的值前移,vector中erase效率極其低下,若是須要頻繁的erase操做,建議仍是換一個數據結構會比較好。
還有例如begin(),end(),size().這些其實都是經過維護start,finish,end_of_storage獲得的。
二、list
在STL中list表示的是一種環狀的雙向鏈表(帶頭),相比vector須要維護start finish end_of_storage,list只須要維護一個node指針結點(頭結點)就能夠了。
雙向環狀鏈表的初始過程以下:
1 node=getNode();//新建一個結點
2 node->next=node;//後一結點是node
3 node->pre=node;//前一結點是node
其餘插入、刪除、取值等操做只須要了解鏈表的操做都是很是easy了。
三、deque(雙端隊列,兩邊均可進可出)
deque在這裏其實也是一個array,只不過是分段而已,所謂分段在這裏的意思就是。deque可能連續第一組有連續十個空間存放值,第二組也有十個空間存放值,可是這兩組空間不是連續的,也就是說第一組空間的尾+1,不等於第二組空間的頭。這兩組空間的組織和管理是經過鏈表(在STL中稱爲map,(這個map翻譯成地圖更合適,而非數據結構中的map))的方式造成的。
四、stack(先進後出的數據結構),queue(先進先出的數據結構)
他們的底層實現都是list,只是根據List作一些量身定製而已。就好比都是一件衣服,不一樣人穿的時候,稍微在外表上作點修飾,本質不改變。
上面說的都是順序性的容器,固然順序性容器中海油優先隊列,slist等,它們其實都是大同小異,不具體闡述了。
五、set、map、multiset、multimap
set和map的最大不一樣就是set是單值,而map是鍵值對,固然set也能夠理解成鍵值對相等的特殊鍵值對。這樣講的話,咱們大概就知道了,它們的底層實現就是一個東西。那麼multiset和set有什麼區別呢?主要值能不能重複的問題,若是能夠保存多個相同值的話就使用multiset不然就使用set。由於插入的時候,它們分別調用的是insert和unique_insert;同理,map和multimap也是這個意思。
map有排序的功能,而且可以快速的插入和查找,甚至刪除。它是怎麼作到的呢?
這就要看的底層實現了。它們的底層都是基於紅黑樹,請看博主這篇文章:map,set的底層實現:紅黑樹[多圖,手機慎入]
博文已經講得很是清楚了,這裏再也不累述。
六、hashtable、hashset、hashmap、hash_multiset,hash_multimap
其實這裏面主要是講了一個東西,就是hashtable,一樣hashset和hashmap的問題是是不是鍵值對的問題。而mullti..是是不是多值的問題。
hash的最重要的功能就是插入和查找至關迅速,咱們使用hash更多的是看中他查找的時間複雜度幾乎是O(1).那麼hash是怎麼實現如此高效的查找呢?
咱們須要看他的數據結構,其實hash是一個array,經過某個合理的方式把值映射到array某個array[i]中。這就是hash的最核心思想了。說的簡單,咱們這裏有幾個問題須要解決:
a、如何纔是高效的映射?
b、若是兩個不一樣的元素映射到同一個array[i]中又怎麼辦?
c、數據量過小,array太大,會形成浪費;若是數據量太大,array過小,查找效率會很是低下,怎麼解決?
咱們一個一個來看。首先會到a.這裏映射關係須要用到某個合適哈希函數,實驗說明質數是一個很是好的選擇,好比(value)mod(某個質數)。
b,提到的其實就是衝突處理的問題,衝突通常有如下方式,線性探測:衝突以後把元素放到下一個位置,下一個也有元素繼續後面。二次探測:即一個哈希函數衝突了以後,用下一個哈希函數;開鏈法:就是在該array[i]下用一個鏈表表示。衝突了就放到鏈表中。STL中選擇的是開鏈法。
對於問題c,STL中的解決方法是,選擇某個質數K做爲N,當K小於hash中的元素個數以後,用更大的質數代替N,同時對以前的每個元素重構hash.
其餘算法:
其餘例如find,accumulate...等算法,其實就是維護迭代器,通常都是fistIterator,lastIterator,operatorValue.這種架構。
從始至終,迭代器和泛型在幾乎每個STL容器或算法中都有出現、這纔是精髓!
空間配置器
這裏在STL中說的空間配置器更多的是指內存的申請和釋放。在STL中,通常擁有兩級空間配置器,其中第一級配置器更像咱們傳統意義上的申請;而第二級配置器是一個池,通常稱爲內存池。
這裏對空間配置器進行一個簡短的綜述,空間配置器管理存儲資源的申請和釋放的策略是當申請的資源較大時,這裏說的較大時大於126BYTES時,調用第一級配置器,採用malloc和free的方式申請和釋放;當小於126BYTES時,調用第二級配置器,第一級配置器其實是由一個鏈表進行維護,但freeList中找到了合適大小的內存,直接給申請者使用,若是實在不夠的話,就只能用部分,甚至再用堆申請一部分,即用malloc申請。釋放的時候加入到freeList中。
採用二級配置器的一個利好就是,可以較大程度上減小碎片化,提升內存內部的使用率,減小資源浪費。
參考文獻:STL源碼剖析
版權全部,歡迎轉載,可是轉載請註明出處:瀟一