STL源碼剖析——容器

一.模板特化

針對任何模板參數更進一步的條件限制所設計出來的一個特化版本,如:

template<typename T>
class C{...};//泛化版本,可以接受T爲任何型別
template<typename T>
class C<T*>{...};//特化版本,僅適合於T爲原生指針時的情況

二.設計容器必須定義的型別

  • value_type
  • difference_type
  • reference_type
  • pointer_type
  • iterator_category:
    其中設計實現了只讀/只寫迭代器,允許寫入型,可雙向移動,隨機訪問迭代器這5類
    總結
    traits編程技法(特性萃取)大量的實現大大提高了STL設計的便利性。需要注意設計正確的型別是迭代器的職責,而設計正確的迭代器則屬於容器的職責。

三.deque

deque與vector的差別

  • deque除了可以像vector那樣在尾部以O(1)的時間複雜度完成插入和刪除之外,還可以實現在頭部以O(1)的代價插入元素
  • deque沒有所謂的容量的概念,這個和它底層的空間組織形式有關,所以不提供reserve成員函數來限定容量
  • deque的底層實現原理限制了對他的使用範圍,尤其使得迭代器設計極其複雜,所以在選擇使用對應的容器時要綜合考慮,尤其是設計到排序等使用場景是儘量使用vector容器

deque的底層構造圖示
deque構造
deque的迭代器設計
對於每一個緩衝區擁有對應的迭代器指向緩衝區的開始和結束位置,對於每一個緩衝區,其在對應的中控器map中也有對應的指向指針。。。,同時還維護start與finish兩個迭代器分別指向第一個緩衝區的第一個元素和最後一個緩衝區的最後一個元素(尾後)
deque作爲stack與queue的隊列
stack與queue的使用特性決定了他們更適合於使用雙端開口的容器作爲底層實現,所以我們可以使用deque於list作爲他們的底層實現,默認是deque,又由於queue與stack的使用屬性,所以設計的時候並未爲他們設計私人的迭代器。

四.心心念唸的優先隊列

原來所謂的優先隊列確實是我們都知道的堆,但其默認使用vector作爲底層容器,若未指定具體的權值規則,會按照大根堆的方式來設計優先隊列。因此他本質上也和queue一樣,也是容器配接器,沒有自己的迭代器,因爲用不着呀!原本還以爲是自己維護空間來着,最後要了解幾個建堆的函數:

  • push_heap

default (1)

template <class RandomAccessIterator>
  void push_heap (RandomAccessIterator first, RandomAccessIterator last);

custom (2)

template <class RandomAccessIterator, class Compare>
  void push_heap (RandomAccessIterator first, RandomAccessIterator last,
                   Compare comp);

注意在使用push_heap之前應該保證新元素已經位於底層容器的尾部,否則函數的行爲將是未定義的,對於第一個版本,默認是構造大根堆的形式,其實就是缺省使用了less<T>,若要使用小根堆可以調用第二個函數,使用STL中的greater<T>

  • pop_heap
    具體構造就不寫了,需要注意的是它僅僅是將pop出去的頭部元素放在底層容器的尾後而已,如果想要真正的移除它最好調用底層容器的pop_back方法。
  • sort_heap
    本質就是多次調用pop_heap操作,最終實現堆排序的過程,這和我們使用數組時的堆排序過程一毛一樣,下面是僞代碼:
while(last-first>1){
    pop_heap(first,last--);
}
  • make_heap
    就是將一個序列構造成爲一個堆,一般是針對vector進行類似的操作
    需要注意的是上面的四個函數都有最上面push_heap的版本2類型模板函數,支持自定義建堆

五.hashtable的構造

常用的hash函數:直接映射法,平方取中法,除餘法
解決hash衝突的方法:開放尋址法(線性、二次探測),拉鍊法,新建緩衝區法
線性探測法存在主集團的文件,尤其是大量hash的時候;二次探測法則解決了主集團衝突的問題,但是仍舊存在次集團的問題(畢竟仍然有可能兩個元素有一樣的hash值,之後平方相加減的問題依舊存在)
hashtable的桶子
常常只有在討論拉鍊法時我們纔會將其叫做桶子,而且桶子的個數要設置爲質數個,這樣可以減小hash衝突的概率。並且桶子維護的鏈表不是STL中的list而是自己設計的hashtable_node,至於桶子的聚合體則仍然是vector,以便hash具有動態增長的能力,這也就是爲什麼我們說hash的闊容方式和vector一樣的根本原因之所在。
各種set與map
神奇的C++ STL,設計的也太巧妙了,瞧一瞧源碼就會發現hashset與hashmap的相關操作都會調用hashtable的基礎接口,而hashtable就是我們前面說的以vector爲底層容器的桶式拉鍊法hashtable,至於在C++11 中要特別注意學習使用以下四個hash容器:
unordered_map與unordered_map
unordered_multimap與unordered_multiset

//unordered_multiset平常使用只要指定型別即可
template < class Key,                         // unordered_multiset::key_type/value_type
           class Hash = hash<Key>,            // unordered_multiset::hasher
           class Pred = equal_to<Key>,        // unordered_multiset::key_equal
           class Alloc = allocator<Key>       // unordered_multiset::allocator_type
           > class unordered_multiset;
//unordered_multimap平常只要指定鍵值與實值的類型即可
template < class Key,                                    // unordered_multimap::key_type
           class T,                                      // unordered_multimap::mapped_type
           class Hash = hash<Key>,                       // unordered_multimap::hasher
           class Pred = equal_to<Key>,                   // unordered_multimap::key_equal
           class Alloc = allocator< pair<const Key,T> >  // unordered_multimap::allocator_type
           > class unordered_multimap;

此處省去unordered_set與unordered_map的定義式,來源於cplusplus.com,若深入下去就會發現對於這些容器的實現底層都是基於hashtable的,只不過unordered類型的和unordered_multi類型的兩類容器底層採用的插入方式不同罷了,unordered_set不支持重複元素,而unordered_multiset則支持。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章