stl源码剖析_STL之set源码剖析

源码面前,了无秘密。

8907835e130ba704b3411ff0e391100d.png

之前在 小bug蕴含大能量 中讲过一个和set相关的bug,说过要从红黑树到STL 红黑树,再到STL set的源码逐步掌握整个知识架构。

最近已经把这块的东西都学习完了,总结的时候发现并不好写。要以什么样的顺序分享出来,也很让人头疼。

因为set的源码本身看起来非常简单,最后还是觉得应该先学会set的使用再去呈现红黑树的知识点,这样大家再学习红黑树的时候直观感觉也会好一点,这也是我自己学习过程中的一点体会。

因此,这次我们通过剖析set的源码,总结set的常用方法,学会怎么使用set容器。

01

set源码剖析

set的源码是非常简单的,包含在“stl_set.h”文件中,我们把set的源码部分分解来看,并总结出它的特性。

第一部分:set的定义

// 模板包含三个参数/** * @tparam _Key  Type of key objects. * @tparam _Compare  Comparison function object type, defaults to less<_key> * @tparam _Alloc  type, defaults to allocator<_key>*/template<typename _Key, typename _Compare = std::less<_key>,  typename _Alloc = std::allocator<_key> >class set{  typedef typename _Alloc::value_type    _Alloc_value_type;  public:  // key和value是同一个  typedef _Key     key_type;  typedef _Key     value_type;  typedef _Compare key_compare;  typedef _Compare value_compare;  typedef _Alloc   allocator_type;private:  // 底层的结构是红黑树    typedef _Rb_tree,           key_compare, _Key_alloc_type> _Rep_type;  _Rep_type _M_t;  // Red-black tree representing set.}

从上述代码中我们可以看出

  • set的底层结构是红黑树_Rep_type _M_t;

  • 因为是红黑树所以元素自动排序,排序函数为_Compare,默认按照标准函数std::less<_key>进行排序的,std::less<_key>的源码如下

template<typename _Tp>struct less : public binary_function<_tp _tp>bool>{  bool  operator()(const _Tp& __x, const _Tp& __y) const{ return __x < __y; }}
当然也可以传入自定义的排序函数。但是,如果要使用默认的函数,对于       自定义类型就必须重载“
  • set中的元素实际上也是一个key-value对,只是key和value是一样的

第二部分:set的迭代器

// set的迭代器都是const“型”的// _GLIBCXX_RESOLVE_LIB_DEFECTS// DR 103. set::iterator is required to be modifiable,// but this allows modification of keys.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

从上述代码可以看出,set的迭代器都是const的,因此无法通过迭代器修改set元素。

第三部分:set的构造函数

set() : _M_t() { }set(const _Compare& __comp,const allocator_type& __a = allocator_type()): _M_t(__comp, _Key_alloc_type(__a)) { }templateset(_InputIterator __first, _InputIterator __last): _M_t(){ _M_t._M_insert_unique(__first, __last); }set(const set& __x): _M_t(__x._M_t) { }set(initializer_list __l,const _Compare& __comp = _Compare(),const allocator_type& __a = allocator_type()): _M_t(__comp, _Key_alloc_type(__a)){ _M_t._M_insert_unique(__l.begin(), __l.end()); }

可以使用一个[__first,__last)区间来构造,也可以利用列表初始化的形式来构造initializer_list。

另外,我们可以注意到底层都调用了红黑树的_M_insert_unique函数,源码如下,可以发现底层又调用了_M_get_insert_unique_pos函数,该函数返回的是一个pair类型,如果pair的second为0则插入失败。

template<typename _Key, typename _Val, typename _KeyOfValue,   typename _Compare, typename _Alloc>#if __cplusplus >= 201103Ltemplate<typename _Arg>#endifpair<typename _Rb_tree<_key _val _keyofvalue>           _Compare, _Alloc>::iterator, bool>_Rb_tree<_key _val _keyofvalue _compare _alloc>::_M_insert_unique( _Arg && __v ){  typedef pairbool> _Res;  pair<_base_ptr _base_ptr> __res    = _M_get_insert_unique_pos( _KeyOfValue() ( __v ) );  // 如果pair的second不为0,则执行插入  if ( __res.second )    return(_Res( _M_insert_( __res.first, __res.second,           _GLIBCXX_FORWARD( _Arg, __v ) ),           true ) );  // 否则返回失败  return(_Res( iterator( static_cast<_link_type>(__res.first) ), false ) );}

而_M_get_insert_unique_pos函数的源码如下,我们看看在什么时候second为0

template<typename _Key, typename _Val, typename _KeyOfValue,typename _Compare, typename _Alloc>pair<typename _Rb_tree<_key _val _keyofvalue>_Compare, _Alloc>::_Base_ptr,typename _Rb_tree<_key _val _keyofvalue _alloc>::_Base_ptr>_Rb_tree<_key _val _keyofvalue _compare _alloc>::_M_get_insert_unique_pos(const key_type& __k){    // typedef pair    typedef pair<_base_ptr _base_ptr> _Res;    // _x表示当前节点,_y表示_x的父节点    _Link_type __x = _M_begin();    _Link_type __y = _M_end();    bool __comp = true;        // 寻找插入点    while (__x != 0)    {        __y = __x;        // __k<__x>        __comp = _M_impl._M_key_compare(__k, _S_key(__x));        // __k<__x>        __x = __comp ? _S_left(__x) : _S_right(__x);    }    iterator __j = iterator(__y);    // __k<__y>    if (__comp)    {        // 特殊位置        if (__j == begin())        return _Res(__x, __y);        else        --__j;  // 左孩子 这里调用了--操作符    }    // 否则直接比较__j和__k的大小,此时的__j就是y    // __j<__k>    if (_M_impl._M_key_compare(_S_key(__j._M_node), __k))        return _Res(__x, __y);    // _j>=__k,插入失败    return _Res(__j._M_node, 0);}

通过分析上述源代码,可以发现满足当k

所以,经过这么长的分析,我们可以得出这样的结论:set中的元素都是唯一的。

02

set常用方法总结

如果读懂了上面的源码分析,学习起set的方法来接简单多了。

begin()方法返回set的首元素,调用的是红黑树的begin()方法,实际上这个红黑树的begin()方法返回的并不是整棵树的根节点,而是整个树的最左节点。因为set的元素是按顺序排列的,最左节点也是最小节点。

iteratorbegin() const _GLIBCXX_NOEXCEPT{ return _M_t.begin(); }

同理end()方法则返回的是最右节点,最小的值。

iteratorend() const _GLIBCXX_NOEXCEPT{ return _M_t.end(); }

empty()方法判断是否为空

boolempty() const _GLIBCXX_NOEXCEPT{ return _M_t.empty(); }

insert方法有很多种,但是底层都调用的是红黑树的_M_insert_unique方法

std::pairbool>insert(const value_type& __x){  std::pair<typename _Rep_type::iterator, bool> __p =  _M_t._M_insert_unique(__x);  return std::pairbool>(__p.first, __p.second);}

erase方法是删除元素

size_typeerase(const key_type& __x){ return _M_t.erase(__x); }

clear()方法用于清空所有元素

voidclear() _GLIBCXX_NOEXCEPT{ _M_t.clear(); }

count方法和find方法都用于查找元素

size_typecount(const key_type& __x) const{ return _M_t.find(__x) == _M_t.end() ? 0 : 1; }
iteratorfind(const key_type& __x){ return _M_t.find(__x); }

5c6b80833ac99c683ef87f4b9d6e6a7f.png

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值