源码面前,了无秘密。

之前在 小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); }

stl源码剖析_STL之set源码剖析相关推荐

  1. Spring Boot 2.x 启动全过程源码分析(上)入口类剖析

    转载自   Spring Boot 2.x 启动全过程源码分析(上)入口类剖析 Spring Boot 的应用教程我们已经分享过很多了,今天来通过源码来分析下它的启动过程,探究下 Spring Boo ...

  2. 知识图谱实战案例完全剖析(附完整源码和数据集)-张子良-专题视频课程

    知识图谱实战案例完全剖析(附完整源码和数据集)-2070人已学习 课程介绍         课程定位:系统学习知识图谱的佳实践: 系统学习:完全覆盖知识建模.图数据库.知识应用和知识获取: 实战指引: ...

  3. C++ STL 体系结构与内核分析 P8-P15(list源码,迭代器设计原则)

    C++ STL 体系结构与内核分析 P8-P15 OOP(面向对象编程)和GP(泛型编程) 操作符重载 分配器allocators 容器之间的实现关系与分类 深度探索list(双向链表) 迭代器设计原 ...

  4. 深入java并发包源码(三)AQS独占方法源码分析

    深入java并发包源码(一)简介 深入java并发包源码(二)AQS的介绍与使用 深入java并发包源码(三)AQS独占方法源码分析 AQS 的实现原理 学完用 AQS 自定义一个锁以后,我们可以来看 ...

  5. .net webim 源码_Netty服务器启动过程源码带你分析「你能坚持看完吗?」

    基本说明 1.只有看过Netty源码,才能说是真正的掌握了Netty框架: 2.在io.netty.example包下,有很多netty源码案例,可以用来分析: 3.源码分析,是针对有Java项目经验 ...

  6. java经典源码 阅读_公开!阿里甩出“源码阅读指南”,原来源码才是最经典的学习范例...

    我们为啥要阅读源码? 为什么面试要问源码?为什么我们Java程序员要去看源码?相信大多数程序员看到源码第一感觉都是:枯燥无味,费力不讨好!要不是为了"涨薪"我才不去看这个鬼东西!但 ...

  7. Nacos源码阅读开篇之下载源码

    文章目录 Nacos源码阅读开篇 看源码的方法 nacos服务注册与发现源码剖析 nacos核心功能点 nacos服务端原理 nacos 客户端原理 下载Nacos源码 配置单机启动 Nacos源码阅 ...

  8. crm开源系统 tp框架_thinkphp6学习教程与源码 tp6开源CMS系统源码研究

    thinkphp6最新正式版框架上市已经有一段时间了,从官方的介绍来看,tp6的框架和tp5有很大的区别,完全重新改写了底层架构代码和逻辑,所以不支持thinkphp5的无缝升级,也就是说如果你之前的 ...

  9. Android 源码分析之 EventBus 的源码解析

    1.EventBus 的使用 1.1 EventBus 简介 EventBus 是一款用于 Android 的事件发布-订阅总线,由 GreenRobot 开发,Gihub 地址是:EventBus. ...

最新文章

  1. 学习Guava Cache知识汇总
  2. 后端常用开源组件合集(持续更新中)
  3. hibernate动态表名映射
  4. hdu 1495 非常可乐 (bfs)
  5. puppet安装与配置
  6. Python 库安装方法:pip安装tar.gz压缩包,pip安装whl文件
  7. 算法题目——第K大的数
  8. 滴滴公司多次被下架的原因是什么,深挖测试员究竟还该不该去滴滴?
  9. 关键字nullable,nonnull,null_resettable,_Null_unspecified详解
  10. IronPython系列:利用.NET SoapFormatter学习SOAP序列化
  11. 阿里巴巴Java编程规范试题答案
  12. php 表格自动适应页面,h5纯css实现表格的自适应布局
  13. 三菱PLC排故障的方法
  14. 家用无线路由器的设置
  15. HTML5制作坦克大战游戏+Canvas绘制基础图形——学习笔记一
  16. Mac OS关机/睡眠快捷键
  17. java网络编程 TCP程序
  18. FFMPEG 播放 RTSP视频流
  19. 心怀远方,顶峰相见!!!
  20. 搭建商城系统怎么选择合适的运营模式?

热门文章

  1. java 重用性_Java开发重用性必备的三大核心知识点
  2. java操作mongodb_Java操作MongoDB
  3. vue搜索好友_Vue实现类似通讯录功能(中)
  4. python正则表达式提取电话号码_Python学习笔模式匹配与正则表达式之电话号码和Email地址提取程序...
  5. docker ip地址_理解 Docker 网络(番外) -- 《Docker 源码分析》勘误
  6. python restful 框架_restful-dj
  7. unity 敌人自动攻击和寻路_【A*Pathfinding】超级简单的Unity2D寻路
  8. c语言判断一个分数是不是最简分数_青岛版六年级数学上册7.2小数、分数和百分数的互化微课视频 | 练习...
  9. python爬取b站用户_用Python爬取bilibili全站用户信息
  10. sessionlistener方法中获取session中存储的值报空指针异常_从Golang实践中得到的教训...