“ 源码面前,了无秘密。”
之前在 小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); }