1. 程式人生 > >C++中map和set的使用與區別

C++中map和set的使用與區別

set

set是一種關聯式容器,其特性如下:

  1. set以RBTree作為底層容器
  2. 所得元素的只有key沒有value,value就是key
  3. 不允許出現鍵值重複
  4. 所有的元素都會被自動排序
  5. 不能通過迭代器來改變set的值,因為set的值就是鍵

針對這五點來說,前四點都不用再多作說明,第五點需要做一下說明。如果set中允許修改鍵值的話,那麼首先需要刪除該鍵,然後調節平衡,在插入修改後的鍵值,再調節平衡,如此一來,嚴重破壞了set的結構,導致iterator失效,不知道應該指向之前的位置,還是指向改變後的位置。所以STL中將set的迭代器設定成const,不允許修改迭代器的值。

set的資料結構

// 比較器預設採用less,內部按照升序排列,配置器預設採用alloc
template <class Key, class Compare = less<Key>, class Alloc = alloc>
class set
{
public:
  //  在set中key就是value, value同時也是key
  typedef Key key_type;
  typedef Key value_type;
 
  // 用於比較的函式
  typedef Compare key_compare;
  typedef Compare value_compare;
 
private:
  // 內部採用RBTree作為底層容器
  typedef rb_tree<key_type, value_type,
                  identity<value_type>, key_compare, Alloc> rep_type;
  rep_type t;   // t為內部RBTree容器
 
public:
  // 用於提供iterator_traits<I>支援
  typedef typename rep_type::const_pointer pointer;            
  typedef typename rep_type::const_pointer const_pointer;
  typedef typename rep_type::const_reference reference;        
  typedef typename rep_type::const_reference const_reference;
  typedef typename rep_type::difference_type difference_type; 
  // 設定成const迭代器,set的鍵值不允許修改
  typedef typename rep_type::const_iterator iterator;          
  typedef typename rep_type::const_iterator const_iterator;
  // 反向迭代器
  typedef typename rep_type::const_reverse_iterator reverse_iterator;
  typedef typename rep_type::const_reverse_iterator const_reverse_iterator;
  typedef typename rep_type::size_type size_type;
 
  iterator begin() const { return t.begin(); }
  iterator end() const { return t.end(); }
  reverse_iterator rbegin() const { return t.rbegin(); }
  reverse_iterator rend() const { return t.rend(); }
  bool empty() const { return t.empty(); }
  size_type size() const { return t.size(); }
  size_type max_size() const { return t.max_size(); }
 
  // 返回用於key比較的函式
  key_compare key_comp() const { return t.key_comp(); }
  // 由於set的性質, value比較和key使用同一個比較函式
  value_compare value_comp() const { return t.key_comp(); }
 
  // 聲明瞭兩個友元函式,過載了==和<操作符
  friend bool operator== __STL_NULL_TMPL_ARGS (const set&, const set&);
  friend bool operator< __STL_NULL_TMPL_ARGS (const set&, const set&);
  // ...
}


set的建構函式
set提供瞭如下幾個建構函式用於初始化一個set

// 注:下面相關函式都在set類中定義,為了介紹方便才抽出來單獨講解
// 空建構函式,初始化一個空的set
set() : t(Compare()) {}
// 支援自定義比較器,如set<int,greater<int> > myset的初始化
explicit set(const Compare& comp) : t(comp) {}
// 實現諸如set<int> myset(anotherset.begin(),anotherset.end())這樣的初始化
template <class InputIterator>
set(InputIterator first, InputIterator last)
: t(Compare()) { t.insert_unique(first, last); }
// 支援自定義比較器的初始化操作
template <class InputIterator>
set(InputIterator first, InputIterator last, const Compare& comp)
: t(comp) { t.insert_unique(first, last); }
// 以另一個set來初始化
set(const set<Key, Compare, Alloc>& x) : t(x.t) {}
// 賦值運算子函式
set<Key, Compare, Alloc>& operator=(const set<Key, Compare, Alloc>& x)
{
    t = x.t;
    return *this;
}


set的操作函式
insert

插入函式,呼叫RBTree的插入函式即可typedef  pair<iterator, bool> pair_iterator_bool;
// 由於set不允許鍵值重複,所以必須呼叫RBTree的insert_unique函式
// second表示插入操作是否成功
pair<iterator,bool> insert(const value_type& x)
{
  pair<typename rep_type::iterator, bool> p = t.insert_unique(x);
  return pair<iterator, bool>(p.first, p.second);
}
 
// 在position處插入元素, 但是position僅僅是個提示, 如果給出的位置不能進行插入,
// STL會進行查詢, 這會導致很差的效率
iterator insert(iterator position, const value_type& x)
{
  typedef typename rep_type::iterator rep_iterator;
  return t.insert_unique((rep_iterator&)position, x);
}
// 將[first,last)區間內的元素插入到set中
template <class InputIterator>
void insert(InputIterator first, InputIterator last)
{
  t.insert_unique(first, last);
}


erase

擦除函式,用於擦除單個元素或者區間內的元素,直接呼叫RBTree的函式即可

// 擦除指定位置的元素, 會導致內部的紅黑樹重新排列
void erase(iterator position)
{
  typedef typename rep_type::iterator rep_iterator;
  t.erase((rep_iterator&)position);
}
 
// 會返回擦除元素的個數, 其實就是標識set內原來是否有指定的元素
size_type erase(const key_type& x)
{
  return t.erase(x);
}
 
// 擦除指定區間的元素, 會導致紅黑樹有較大變化
void erase(iterator first, iterator last)
{
  typedef typename rep_type::iterator rep_iterator;
  t.erase((rep_iterator&)first, (rep_iterator&)last);
}


clean

清除整個set容器,直接呼叫RBTree的clean函式即可void clear() { t.clear(); }


find

查詢函式,RBTree也提供了,直接呼叫即可

// 查詢指定的元素
  iterator find(const key_type& x) const { return t.find(x); }


count

查詢制定元素的個數// 返回指定元素的個數, set不允許鍵值重複,其實就是測試元素是否在set中
  size_type count(const key_type& x) const { return t.count(x); }


過載操作符

set過載了==和<操作符,基本上都是呼叫RBTree的介面函式即可,如下所示:

template <class Key, class Compare, class Alloc>
inline bool operator==(const set<Key, Compare, Alloc>& x,
                       const set<Key, Compare, Alloc>& y) {
  return x.t == y.t;
}
 
template <class Key, class Compare, class Alloc>
inline bool operator<(const set<Key, Compare, Alloc>& x,
                      const set<Key, Compare, Alloc>& y) {
  return x.t < y.t;
}
其他操作函式
// 返回小於當前元素的第一個可插入的位置
iterator lower_bound(const key_type& x) const
{
  return t.lower_bound(x);
}
 
// 返回大於當前元素的第一個可插入的位置
iterator upper_bound(const key_type& x) const
{
  return t.upper_bound(x);
}
// 返回與指定鍵值相等的元素區間
pair<iterator,iterator> equal_range(const key_type& x) const
{
  return t.equal_range(x);
}


multiset
multiset相對於set來說,區別就是multiset允許鍵值重複,在multiset中呼叫的是RBTree的insert_equal函式,其他的基本與set相同。

其他的就不贅述了,下面列舉一下跟set不同的地方:

// 初始化函式,
// 注意!!!!插入操作採用的是RBTree的insert_equal,而不是insert_unique
template <class InputIterator>
multiset(InputIterator first, InputIterator last)
  : t(Compare()) { t.insert_equal(first, last); }
template <class InputIterator>
multiset(InputIterator first, InputIterator last, const Compare& comp)
  : t(comp) { t.insert_equal(first, last); }
 
// 插入元素, 注意, 插入的元素key允許重複
iterator insert(const value_type& x)
{
  return t.insert_equal(x);
}
 
// 在position處插入元素, 但是position僅僅是個提示, 如果給出的位置不能進行插入,
// STL會進行查詢, 這會導致很差的效率
iterator insert(iterator position, const value_type& x)
{
  typedef typename rep_type::iterator rep_iterator;
  return t.insert_equal((rep_iterator&)position, x);
}


map
map和set一樣是關聯式容器,它們的底層容器都是紅黑樹,區別就在於map的值不作為鍵,鍵和值是分開的。它的特性如下:

  1. map以RBTree作為底層容器
  2. 所有元素都是鍵+值存在
  3. 不允許鍵重複
  4. 所有元素是通過鍵進行自動排序的
  5. map的鍵是不能修改的,但是其鍵對應的值是可以修改的

在map中,一個鍵對應一個值,其中鍵不允許重複,不允許修改,但是鍵對應的值是可以修改的,原因可以看上面set中的解釋。下面就一起來看看STL中的map的原始碼。

map的資料結構

// 預設比較器為less<key>,元素按照鍵的大小升序排列
template <class Key, class T, class Compare = less<Key>, class Alloc = alloc>
class map {
public:
  typedef Key key_type;                         // key型別
  typedef T data_type;                          // value型別
  typedef T mapped_type;
  typedef pair<const Key, T> value_type;        // 元素型別, 要保證key不被修改
  typedef Compare key_compare;                  // 用於key比較的函式
private:
  // 內部採用RBTree作為底層容器
  typedef rb_tree<key_type, value_type,
                  identity<value_type>, key_compare, Alloc> rep_type;
  rep_type t; // t為內部RBTree容器
public:
  // 用於提供iterator_traits<I>支援
  typedef typename rep_type::const_pointer pointer;            
  typedef typename rep_type::const_pointer const_pointer;
  typedef typename rep_type::const_reference reference;        
  typedef typename rep_type::const_reference const_reference;
  typedef typename rep_type::difference_type difference_type; 
  // 注意:這裡與set不一樣,map的迭代器是可以修改的
  typedef typename rep_type::iterator iterator;          
  typedef typename rep_type::const_iterator const_iterator;
  // 反向迭代器
  typedef typename rep_type::const_reverse_iterator reverse_iterator;
  typedef typename rep_type::const_reverse_iterator const_reverse_iterator;
  typedef typename rep_type::size_type size_type;
 
  // 常規的返回迭代器函式
  iterator begin() { return t.begin(); }
  const_iterator begin() const { return t.begin(); }
  iterator end() { return t.end(); }
  const_iterator end() const { return t.end(); }
  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(); }
  bool empty() const { return t.empty(); }
  size_type size() const { return t.size(); }
  size_type max_size() const { return t.max_size(); }
 
  // 返回用於key比較的函式
  key_compare key_comp() const { return t.key_comp(); }
 
  // 由於map的性質, value和key使用同一個比較函式, 實際上我們並不使用value比較函式
  value_compare value_comp() const { return value_compare(t.key_comp()); }
 
  // 注意: 這裡有一個常見的陷阱, 如果訪問的key不存在, 會新建立一個
  T& operator[](const key_type& k)
  {
    return (*((insert(value_type(k, T()))).first)).second;
  }
  // 過載了==和<操作符,後面會有實現
  friend bool operator== __STL_NULL_TMPL_ARGS (const map&, const map&);
  friend bool operator< __STL_NULL_TMPL_ARGS (const map&, const map&);
}


map的建構函式
map提供了一下的建構函式來初始化一個map

// 空建構函式,直接呼叫RBTree的空建構函式
map() : t(Compare()) {}
explicit map(const Compare& comp) : t(comp) {}
// 提供類似map<int,int> myMap(anotherMap.begin(),anotherMap.end())的初始化
template <class InputIterator>
map(InputIterator first, InputIterator last)
  : t(Compare()) { t.insert_unique(first, last); }
// 提供類似map<int,int> myMap(anotherMap.begin(),anotherMap.end(),less<int>)初始化
template <class InputIterator>
map(InputIterator first, InputIterator last, const Compare& comp)
  : t(comp) { t.insert_unique(first, last); }
// 提供類似map<int> maMap(anotherMap)的初始化
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的操作函式
insert
同set一樣,直接呼叫RBTree的插入函式即可,注意map不允許鍵值重複,所以呼叫的是insert_unique

// 對於相同的key, 只允許出現一次, bool標識
pair<iterator,bool> insert(const value_type& x) { return t.insert_unique(x); }
 
// 在position處
插入元素, 但是position僅僅是個提示, 如果給出的位置不能進行插入,
// STL會進行查詢, 這會導致很差的效率
iterator insert(iterator position, const value_type& x)
{
  return t.insert_unique(position, x);
}
// 將[first,last)區間內的元素插入到map中
template <class InputIterator>
void insert(InputIterator first, InputIterator last) {
  t.insert_unique(first, last);
}


erase

同set,直接呼叫即可// 擦除指定位置的元素, 會導致內部的紅黑樹重新排列
void erase(iterator position) { t.erase(position); }
 
// 會返回擦除元素的個數, 其實就是標識map內原來是否有指定的元素
size_type erase(const key_type& x) { return t.erase(x); }
void erase(iterator first, iterator last) { t.erase(first, last); }


clean

同set,直接呼叫即可

void clear() { t.clear(); }


find

// 查詢指定key的元素
iterator find(const key_type& x) { return t.find(x); }
const_iterator find(const key_type& x) const { return t.find(x); }
````


 
 
 
 
 過載運算子
上面介紹到map過載了[],==和<運算子,[]的實現已經介紹過,下面是==和<的實現

// 比較map直接是對其底層容器t的比較,直接呼叫RBTree的比較函式即可

template <class Key, class T, class Compare, class Alloc>
inline bool operator==(const map<Key, T, Compare, Alloc>& x,
                       const map<Key, T, Compare, Alloc>& y)
{
  return x.t == y.t;
}
template <class Key, class T, class Compare, class Alloc>
inline bool operator<(const map<Key, T, Compare, Alloc>& x,
                      const map<Key, T, Compare, Alloc>& y)
{
  return x.t < y.t;
}
其他操作函式
// 返回小於當前元素的第一個可插入的位置
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);
}
// 返回與指定鍵值相等的元素區間
pair<iterator,iterator> equal_range(const key_type& x)
{
  return t.equal_range(x);
}


multimap
multimap和map的關係就跟multiset和set的關係一樣,multimap允許鍵的值相同,因此在插入操作的時候用到insert_equal(),除此之外,基本上與map相同。

下面就僅僅列出不同的地方

template <class Key, class T, class Compare = less<Key>, class Alloc = alloc>
class multimap
{
  // ... 其他地方與map相同
  // 注意下面這些函式都呼叫的是insert_equal,而不是insert_unique
  template <class InputIterator>
  multimap(InputIterator first, InputIterator last)
    : t(Compare()) { t.insert_equal(first, last); }
 
  template <class InputIterator>
  multimap(InputIterator first, InputIterator last, const Compare& comp)
    : t(comp) { t.insert_equal(first, last); }
 
  // 插入元素, 注意, 插入的元素key允許重複
  iterator insert(const value_type& x) { return t.insert_equal(x); }
 
  // 在position處插入元素, 但是position僅僅是個提示, 如果給出的位置不能進行插入,
  // STL會進行查詢, 這會導致很差的效率
  iterator insert(iterator position, const value_type& x)
  {
    return t.insert_equal(position, x);
  }
  // 插入一個區間內的元素
  template <class InputIterator>
  void insert(InputIterator first, InputIterator last)
  {
    t.insert_equal(first, last);
  }
  // ...其餘地方和map相同
}


總結
總的來說,這四類容器僅僅只是在RBTree上進行了一層封裝,首先,set和map的區別就在於鍵和值是否相同,set中將值作為鍵,支援STL的提供的一些交集、並集和差集等運算;map的鍵和值不同,每個鍵都有自己的值,鍵不能重複,但是值可以重複。

multimap和multiset就在map和set的基礎上,使他們的鍵可以重複,除此之外基本等同。
--------------------- 
作者:zy20150613 
來源:CSDN 
原文:https://blog.csdn.net/zy20150613/article/details/78693579 
版權宣告:本文為博主原創文章,轉載請附上博文連結!