STL set和map

這兩個容器都是以紅黑樹爲底層機制實現的,set中每個節點只有一個值,也就是把鍵值作爲實值來用,而map中每個節點有兩個值,把鍵值和實值分開

一、set特點

  • set底層就是紅黑樹機制,並且鍵值就是實值。所有set元素在插入時,就會按照實值(也是鍵值)自動被排序。,也就是紅黑樹中每個節點中_M_value_field變量存儲的都是單一類型的值。
  • set中不允許兩個相同鍵值存在,因爲set插入時使用的是紅黑樹中的insert_unique類。
  • 插入到set中的元素不能夠修改,因爲一旦修改,就會破壞紅黑樹的組織
  • set的迭代器不會因爲刪除增加等操作而失效

二、set源碼精解

①如何實現鍵值和實值一樣
template <class _Key, class _Compare, class _Alloc>
class set {
...
public:
  // typedefs:
  typedef _Key     key_type;
  typedef _Key     value_type;//實值和鍵值都定義爲_Key
  typedef _Compare key_compare;
  typedef _Compare value_compare;//實值比較函數和鍵值比較函數是同一個
private:
  typedef _Rb_tree<key_type, value_type, 
                  _Identity<value_type>, key_compare, _Alloc> _Rep_type;//注意一
  _Rep_type _M_t;  // 唯一的成員變量,就是一個紅黑樹變量,
...
};

注意:注意一這句程序是讓map和set實現不同的主要地方。尤其是紅黑樹中的第三個模板參數_KeyOfValue,這個模板參數告訴紅黑樹,如果由value_type得到key_type。在set中填入的函數原型如下:

//就是把原值返回,所以value_type就是key_type,也就實現了set的特點,鍵值和實值一樣
template <class _Tp>
struct _Identity : public unary_function<_Tp,_Tp> {
  const _Tp& operator()(const _Tp& __x) const { return __x; }
};
②set的迭代器
typedef typename _Rep_type::const_iterator iterator;

set的迭代器就是紅黑樹的常數迭代器,這也阻止了set中元素的修改

③set插入函數
pair<iterator,bool> insert(const value_type& __x) { 
  pair<typename _Rep_type::iterator, bool> __p = _M_t.insert_unique(__x); //正因爲這裏使用的是insert_unique函數,所以不能有重複鍵值
  return pair<iterator, bool>(__p.first, __p.second);
}

其他:在set中,應該使用紅黑樹的find函數,而不是stl提供的find函數。

三、multiset

multiset和set唯一的差別就是multiset允許鍵值重複,也就是在insert函數上不一樣,multiset的insert函數如下:

iterator insert(iterator __position, const value_type& __x) {
  typedef typename _Rep_type::iterator _Rep_iterator;
  return _M_t.insert_equal((_Rep_iterator&)__position, __x);//使用了insert_equal函數
}

四、map特點

  • map也是以紅黑樹爲底層機制,並且map在紅黑樹中存儲的所有數據都是pair(從程序角度來看就是每個節點中的_M_value_field變量類型都是pair),pair中第一個元素是鍵值,第二個元素是實值。
  • map不允許兩個元素擁有相同的鍵值。
  • 可以修改map中元素的實值,但是不能修改元素的鍵值,原因和set是一樣的
  • map的迭代器不會因爲刪除增加等操作而失效

五、map源碼精解

①如何實現鍵值和實值區分開
class map {
public:
  typedef _Key                  key_type;//鍵值類型
  typedef _Tp                   data_type;//數據值類型
  typedef _Tp                   mapped_type;
  typedef pair<const _Key, _Tp> value_type;//實值爲pair類型
  typedef _Compare              key_compare;
private:
  typedef _Rb_tree<key_type, value_type, 
                   _Select1st<value_type>, key_compare, _Alloc> _Rep_type;//註釋一
  _Rep_type _M_t;

注意:在註釋一里,紅黑樹模板的第三個模板參數_Select1st<value_type>的函數原型如下:

template <class _Pair>
struct _Select1st : public unary_function<_Pair, typename _Pair::first_type> {
  const typename _Pair::first_type& operator()(const _Pair& __x) const {
    return __x.first;
  }
};

這個函數就是把pair中的第一個元素返回出來,所以鍵值就是實值中的第一個元素。

②map迭代器
typedef typename _Rep_type::iterator iterator;

map迭代器就是紅黑樹迭代器,因爲可以改節點中pair的第二個元素。

③map的嵌套類value_compare中重載了括號函數
bool operator()(const value_type& __x, const value_type& __y) const {
  return comp(__x.first, __y.first);
}

第一眼看這種寫法沒有看懂,其實就是在類對象後面使用()函數時,如果裏面是兩個value_type類型的入口採參數,那麼就會執行這個函數。不過這是map中的一個嵌套類。下面就來試驗一下重定義()運算符
測試程序:

class test
{
public:
	bool operator()(int a, int b)
	{
		cout << a << " " << b << endl;
		return false;
	}
};
int main()
{
	test t;//初始化的時候不可以使用這個括號運算符
	//test t(10,20);這樣是錯誤的
	t(30, 40);
	system("pause");
}
④map中重載[]運算符

源碼如下:

//這個運算符的作用是如果有k值,就返回k值在樹中的迭代器,如果沒有,就插入到樹中,然後返回插入位置的迭代器
_Tp& operator[](const key_type& __k) {//注意入口參數是鍵值
  iterator __i = lower_bound(__k);
  // __i->first is greater than or equivalent to __k.
  //__i->first大於等於__k
  if (__i == end() || key_comp()(__k, (*__i).first))//如果__k不在紅黑樹中,就插入
  //其中__i == end()表明樹中沒有大於等於__k的值,所以__k不在樹中
  //key_comp()(__k, (*__i).first)如果爲真,說明__k小於__i->first,說明__k不在樹中
    __i = insert(__i, value_type(__k, _Tp()));//創建一個實值爲0的節點
  return (*__i).second;
}

注意返回值是一個引用返回,並且該變量是存儲在堆上的,所以這個函數返回值既可以當左值,也可以當右值。


(???但是對原理不太明白,函數返回引用也不太明白,有待進一步理解)
可以參考這篇博客:https://blog.csdn.net/guonengneng/article/details/88880956


因此我之前經常喜歡用map來計數,其原理就是這樣,如果有這個鍵值,就使用這個鍵值,如果沒有,就插入。

//比如有如下的情形,有一組int數據,範圍從1到10,我想知道其中1有多少個,2有多少個,以此類推。可以使用map
int a[] = { 1, 2, 3, 1, 2, 3, 4, 5, 3, 4, 6 };
map<int, int> test;
for (int i = 0; i < 11; i++)
{
	test[a[i]]++;
}
for (map<int, int>::iterator i = test.begin(); i != test.end(); i++)
{
	cout << i->first << "個數爲:" << i->second << endl;
}

結果如下:
在這裏插入圖片描述

⑤map中的插入和set差不多,就不詳述了

六、mutlimap

和multiset一樣,就是在插入時,使用了insert_equal。
並且mutlimap沒有再重載[]運算符了,也就是這個運算符用不了了。

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