C++中map和set的使用与区别——2019-11-14

set

set是一种关联式容器,其特性如下:

set以RBTree作为底层容器

所得元素的只有key没有value,value就是key

不允许出现键值重复

所有的元素都会被自动排序

不能通过迭代器来改变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的值不作为键,键和值是分开的。它的特性如下:

map以RBTree作为底层容器

所有元素都是键+值存在

不允许键重复

所有元素是通过键进行自动排序的

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); }

````

<div class="se-preview-section-delimiter"></div>

### 重载运算符

上面介绍到map重载了[],==和<运算符,[]的实现已经介绍过,下面是==和<的实现

<div class="se-preview-section-delimiter"></div>

```cpp

// 比较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的基础上,使他们的键可以重复,除此之外基本等同。

————————————————

版权声明:本文为CSDN博主「zy20150613」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。

原文链接:https://blog.csdn.net/zy20150613/article/details/78693579

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,108评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,699评论 1 296
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,812评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,236评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,583评论 3 288
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,739评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,957评论 2 315
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,704评论 0 204
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,447评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,643评论 2 249
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,133评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,486评论 3 256
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,151评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,108评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,889评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,782评论 2 277
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,681评论 2 272

推荐阅读更多精彩内容