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没有再重载[]运算符了,也就是这个运算符用不了了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值