1. 程式人生 > >【STL】從原始碼看map

【STL】從原始碼看map

map 與set相同,map同樣是以紅黑樹RB_Tree為底層機制的關聯式容器。map的每一個元素都擁有兩個值,一個鍵值(key)和一個實值(value)。它的內部實現是用一個pair來儲存這個兩個值。所以,map的每一個元素又是一個pair。下面是STL原始碼中stl_pair.h對pair的定義。
template <class T1, class T2>
struct pair {
  typedef T1 first_type;
  typedef T2 second_type;
  T1 first;
  T2 second;
  pair() : first(T1()), second(T2()) {}
  pair(const T1& a, const T2& b) : first(a), second(b) {}
#ifdef __STL_MEMBER_TEMPLATES
  template <class U1, class U2>
  pair(const pair<U1, U2>& p) : first(p.first), second(p.second) {}
#endif
};
因為map底層是一棵紅黑樹(RB_Tree),紅黑樹的又是一種平衡二叉搜尋樹,自動排序效果很好。因此map內所有的元素都會根據元素的鍵值(key)自動排序。這一點也限制了我們不能修改map內元素的鍵值(key),鍵值(key)關係到map元素的排列規則,任意改變map的元素鍵值將會嚴重破壞map組織結構。那麼STL原始碼是如何限制我們呢? 開啟stl_map.h,你會看到這麼一句話:
 typedef pair<const Key, T> value_type;
它將pair重新命名成value_type,同時將pair的key宣告成了const型別,限制我們不能對它做出修改。 map的底層是紅黑樹,map的各種操作介面紅黑樹也都提供了,所以幾乎所有的map操作行為,都是呼叫紅黑樹RB_Tree的操作行為。下面我將從原始碼角度講,map是如何呼叫紅黑樹的。 型別:“是你的又怎麼樣,我改個名字就是我的了”
請允許我用“不要臉”和“暴力”這兩個名字來形容map在這裡的做法,儘管它的整個實現都很“不要臉”。它先把紅黑樹重新命名。再用重新命名的紅黑樹把紅黑樹內的引數型別再次重新命名。好像這樣就變成自己的了,但的確是這樣。 這裡,我們可以明顯感覺到STL庫的程式碼複用性是多麼的高,它的編寫者是多麼的厲害。 原始碼:
private:
  //將紅黑樹重新命名
  typedef rb_tree<key_type, value_type,
                  select1st<value_type>, key_compare, Alloc> rep_type;
  rep_type t;  // red-black tree representing map
public:
  //再對型別重新命名
  typedef typename rep_type::pointer pointer;
  typedef typename rep_type::const_pointer const_pointer;
  typedef typename rep_type::reference reference;
  typedef typename rep_type::const_reference const_reference;
  typedef typename rep_type::iterator iterator;
  typedef typename rep_type::const_iterator const_iterator;
  typedef typename rep_type::reverse_iterator reverse_iterator;
  typedef typename rep_type::const_reverse_iterator const_reverse_iterator;
  typedef typename rep_type::size_type size_type;
  typedef typename rep_type::difference_type difference_type;

當然,它也可以有一些自己的東西。 原始碼:
  typedef Key key_type;
  typedef T data_type;
  typedef T mapped_type;
  typedef pair<const Key, T> value_type;
  typedef Compare key_compare;
物件成員 map的原始碼裡面對私有成員(private)的定義有以下四行:
private:
  typedef rb_tree<key_type, value_type,select1st<value_type>, key_compare, Alloc> rep_type;
  rep_type t;  // red-black tree representing map
我們可以看到,它直接將一個紅黑樹型別rb_tree(它的模板引數不再講,想了解的可以去看看rb_tree的原始碼)重新命名成rep_ type,並且利用rep_ type構建了一個紅黑樹物件t。如果你接著往下看,你會發現,map的所有方法中都有t的身影。 方法 基本方法 這裡我將構造,賦值運算子過載定義為基本方法。(純屬本人為了方便) 構造 開啟c++Reference,會看到map的構造有三種實現。前兩種是建構函式,第三種是拷貝建構函式。但是它遠遠沒有表面上這麼簡單。 1.(構造)
explicit map(const Compare& comp = Compare(),const Allocator& = Allocator());
關鍵字explicit的作用是防止隱式轉換或者說防止建構函式被隱式呼叫。引數compare的作用是傳一個比較型別,這麼說可能不嚴謹,但是好理解些。開頭說過,因為map底層機制是紅黑樹,所以它有自動排序的功能。既然要排序,那就肯定有升序降序之分。compare的作用就是決定升序還是降序。它內部有實現了一個operator(),功能是大小比較。在預設情況下,STL原始碼庫內的Compare實現的是升序。如果我們自己寫一個compare給它傳參,它會按照我們自己寫的compare進行排序。 回頭來說,為了顯示它和紅黑樹是一對好基友,它在內部實現的時候直接呼叫了紅黑樹的建構函式。從原始碼我們可以看出,它的預設構造和非預設構造都是呼叫相應的紅黑樹的建構函式。 原始碼:
  map() : t(Compare()) {}
  explicit map(const Compare& comp) : t(comp) {}//
2.(構造)
template <class InputIterator>
map(InputIterator first, InputIterator last,const Compare& comp = Compare(), const Allocator& = Allocator());
 這個建構函式有一個模板引數,這個引數的型別一般是迭代器。通過傳入first,last兩個迭代器。將左閉右開區間[first,last)的內容插入新構造的map中。Allocator是一個空間配置器。 基友情深似海,它同樣以呼叫紅黑樹建構函式的方式來實現它自己建構函式的功能。但,這次沒那麼簡單,使用模板引數必定意味著它有多種的實現。在STL原始碼中,它通過改變引數,實現函式過載,進而來實現多種引數型別的傳參。光說可能有點迷糊,看一下原始碼你就悟到了。 原始碼:
#ifdef __STL_MEMBER_TEMPLATES
  //原版
  template <class InputIterator>
  map(InputIterator first, InputIterator last)
    : t(Compare()) { t.insert_unique(first, last); }
  template <class InputIterator>
  map(InputIterator first, InputIterator last, const Compare& comp)
    : t(comp) { t.insert_unique(first, last); }
#else
  //value_type(也就是pair)的指標型別
  map(const value_type* first, const value_type* last)
    : t(Compare()) { t.insert_unique(first, last); }
  map(const value_type* first, const value_type* last, const Compare& comp)
    : t(comp) { t.insert_unique(first, last); }
  //const型別迭代器
  map(const_iterator first, const_iterator last)
    : t(Compare()) { t.insert_unique(first, last); }
  map(const_iterator first, const_iterator last, const Compare& comp)
    : t(comp) { t.insert_unique(first, last); }
#endif /* __STL_MEMBER_TEMPLATES */


3.(拷貝構造)
map(const map<Key, T, Compare, Allocator>& x);
拷貝構造就不需要多說了,同樣是呼叫紅黑樹的拷貝構造。直接上原始碼。 原始碼:
map(const map<Key, T, Compare, Alloc>& x) : t(x.t) {}//呼叫紅黑樹拷貝構造

賦值運算子過載 同樣的,它直接呼叫的紅黑樹的賦值運算子過載函式,來實現它自己的功能。 原始碼:
map<Key, T, Compare, Alloc>& operator=(const map<Key, T, Compare, Alloc>& x)
{
  t = x.t;//呼叫紅黑樹賦值運算子過載。
  return *this; 
}
功能方法 從上面我們就看到了,map的無賴。所以它準備接著無賴下去。它大部分功能的實現都是呼叫的紅黑樹的方法。但是也有幾個需要注意的我會放在下面特殊的功能方法一欄去講,此欄只貼出原始碼。不分析。(因為沒什麼可分析的) (ps:以下標註C++的是我們可以呼叫的介面,標註STL的是原始碼的實現) 正向迭代器 C++:
  iterator begin();
  const_iterator begin() const;
  iterator end();
  const_iterator end() const;
STL:
  iterator begin() { return t.begin(); }
  const_iterator begin() const { return t.begin(); }
  iterator end() { return t.end(); }
  const_iterator end() const { return t.end(); }
反向迭代器 C++:
  reverse_iterator rbegin();
  const_reverse_iterator rbegin() const;
  reverse_iterator rend();
  const_reverse_iterator rend() const;
STL:
  reverse_iterator rbegin() { return t.rbegin(); }
  const_reverse_iterator rbegin() const { return t.rbegin(); }
  reverse_iterator rend() { return t.rend(); }
  const_reverse_iterator rend() const { return t.rend(); }

判空、求size、求max_size C++:
bool empty() const;
size_type size() const;
size_type max_size() const;

STL:
 bool empty() const { return t.empty(); }
 size_type size() const { return t.size(); }
 size_type max_size() const { return t.max_size(); }

erase、clear、swap C++:
  void erase(iterator position);
  size_type erase(const key_type& x);
  void erase(iterator first, iterator last);
  void clear();
  void swap(map<Key, T, Compare, Allocator>& mp);
STL:
  void erase(iterator position) { t.erase(position); }
  size_type erase(const key_type& x) { return t.erase(x); }
  void erase(iterator first, iterator last) { t.erase(first, last); }
  void clear() { t.clear(); }
  template <class Key, class T, class Compare, class Alloc>
  inline void swap(map<Key, T, Compare, Alloc>& x,
         map<Key, T, Compare, Alloc>& y) {
         x.swap(y);
  }
key_comp、value_comp C++:
key_compare key_comp() const;
value_compare value_comp() const;
STL:
key_compare key_comp() const { return t.key_comp(); }
value_compare value_comp() const { return value_compare(t.key_comp()); }
我在上面說過,compare是一個比較型別。這兩個函式方法就是返回給我們一個這個型別的匿名物件。 注意這裡的value_comp,map的key是關鍵碼,value是實值。進行比較的時候只能比較key。所以我們會看到它將一個key_comp牆專程value_compare型別,本質上還是key_compare,比較的是key。下面是value_compare的原始碼定義: STL:
  class value_compare
    : public binary_function<value_type, value_type, bool> {
  friend class map<Key, T, Compare, Alloc>;
  protected :
    Compare comp;
    value_compare(Compare c) : comp(c) {}
  public:
    bool operator()(const value_type& x, const value_type& y) const {
      return comp(x.first, y.first);// first為key
    }
  };

find、count C++:
  iterator find(const key_type& x);
  const_iterator find(const key_type& x) const;
  size_type count(const key_type& x) const;
STL:
  iterator find(const key_type& x) { return t.find(x); }
  const_iterator find(const key_type& x) const { return t.find(x); }
  size_type count(const key_type& x) const { return t.count(x); }
find的作用是查詢元素是否存在,若存在,返回指向元素位置的迭代器。若不存在,返回end(); count一樣可以查詢元素是否存在,若存在,返回元素的個數。若不存在,返回0;(主要在multimap中使用) lower_bound、upper_bound C++:
  iterator lower_bound(const key_type& x);
  const_iterator lower_bound(const key_type& x) const;

  iterator upper_bound(const key_type& x);
  const_iterator upper_bound(const key_type& x) const;
STL:
  iterator lower_bound(const key_type& x) {return t.lower_bound(x); }
  const_iterator lower_bound(const key_type& x) const {
    return t.lower_bound(x);
  }
  iterator upper_bound(const key_type& x) {return t.upper_bound(x); }
  const_iterator upper_bound(const key_type& x) const {
    return t.upper_bound(x);
  }
我們可以看到,這兩個引數的返回值都是一個key值。 lower_bound的作用是查詢並返回大於等於這個key值的第一個元素的迭代器。 upper_bound的作用是查詢並返回大於這個key值的第一個元素的迭代器。 equal_range C++:
  pair<iterator, iterator> equal_range(const key_type& x);
  pair<const_iterator, const_iterator> equal_range(const key_type& x) const;
STL:
  pair<iterator,iterator> equal_range(const key_type& x) {
    return t.equal_range(x);
  }
  pair<const_iterator,const_iterator> equal_range(const key_type& x) const {
    return t.equal_range(x);
  }
只要你搞明白了lower_bound與upper_bound,搞懂equal_range就很容易了。因為這個就是他們兩個的結合體。 我們可以看到,equal_bound的引數同樣是個key值,返回值是一個pair。pair裡面是兩個迭代器。第一個就是大於等於這個key值的第一個元素的迭代器。第二個迭代器就是大於這個key值的第一個元素的迭代器。 特殊的功能方法 這裡的特殊是指的它與set不同,因為set與map都是以紅黑樹為基礎實現,所以我們經常拿它兩做對比。 insert 我們看到的insert有三種介面,仔細看這個三個insert的實現,你會發現原來他們都一樣,全部呼叫紅黑樹的insert_unique。這也是map與multimap不同的地方。STL的紅黑樹實現了兩種insert,一種是insert_uniq 1. C++:
pair<iterator, bool> insert(const value_type& x);
返回值為一個pair。pair的first是一個迭代器,second是一個bool值。當插入成功時,返回值pair的迭代器指向插入元素的位置,bool值為true。插入失敗時(說明該元素已經存在),迭代器指向已存在元素的位置,bool值為false。 STL:
pair<iterator,bool> insert(c
onst value_type& x) { return t.insert_unique(x); }
2.           C++:
  iterator insert(iterator position, const value_type& x);
返回值為一個迭代器。當插入成功時,迭代器指向新插入元素。當插入失敗時,迭代器指向已存在元素位置。 STL:
  iterator insert(iterator position, const value_type& x) {
    return t.insert_unique(position, x);
  }
3. C++:
 template <class InputIterator>
  void insert(InputIterator first, InputIterator last);
將區間[first,last)的值插入map中。與引數型別相同的建構函式一樣。因為使用了模板,所以它STL原始碼有多種實現。以便支援多種型別的引數。 STL:
#ifdef __STL_MEMBER_TEMPLATES
  template <class InputIterator>
  void insert(InputIterator first, InputIterator last) {
    t.insert_unique(first, last);
  }
#else
  void insert(const value_type* first, const value_type* last) {
    t.insert_unique(first, last);
  }
  void insert(const_iterator first, const_iterator last) {
    t.insert_unique(first, last);
  }
#endif /* __STL_MEMBER_TEMPLATES */

operator[] 它的operator[]是最特殊的一個。因為它有多中功能。 C++:
 T& operator[] (const key_type& x);
STL:
  T& operator[](const key_type& k) {
    return (*((insert(value_type(k, T()))).first)).second;
  }

1.讀功能。它的引數是一個key值,返回值是一個T的引用。T就是實值(value)的型別。我們利用它的返回值可以讀到key值為x的元素的value。 2.寫功能。寫入一個新元素 先搞清楚下面幾點。 ①巧妙的點就在於它的原始碼實現,它在裡面呼叫了一個insert。它將insert的返回值得first解引用,並返回解引用後的到值得second。 ②從上面我們知道insert的返回值是一個pair。pair的first是一個迭代器。當插入成功時,迭代器指向新插入元素。當插入失敗時,迭代器指向已存在元素位置。 ③文章開頭我們就知道,map的元素是pair型別。pair的second就是value。 分析: ①operator[]的引數是一個key值。它將這個key值做為insert的引數,呼叫insert函式。並得到一個返回值。 到這裡,我們就已經完成寫操作了。沒看明白?,如果我傳的引數key不存在於map中。insert就會插入成功,並返回一個pair<Iterator,bool>,這個pair的迭代器就是指向新插入元素的位置。 ②再對這個返回值是一個pair<Iterator,bool>型別的值(insert第一種實現)。這個pair的first是一個迭代器,注意這個迭代器的指向(細看insert第一種實現),對這個迭代器解引用又得到一個pair<key,value>型別的值。並返回它的second(一個value值)。 所以說,當key在map中存在時,operator完成的是讀操作。當key不存在是,完成一個寫操作。寫入一個新的pair<key,T()>。 到此,差不多就分析完了。想深入瞭解的同學可以自己研究一下map的原始碼。你會得到許多意想不到的東西。 參考文獻:《STL原始碼剖析》  侯捷  譯; 我愛學習,學習使我快樂!