C++STL学习第二讲下(从源代码角度讲解STL)

C++STL学习第二讲下(从源代码角度讲解STL)

Associative Containers 讲解:

1. 深度探索 rb_tree

1. rb_tree 介绍
  • rb_tree(红黑树)是一种平衡二叉搜索树;
  • rb_tree 提供遍历操作和 iterator,遍历按照中序遍历,得到排序结果;
  • 【我们不应该使用 rb_tree 的 iterator 修改 key;】
  • rb_tree 提供两种 insert 操作:
    • insert_unique():表示节点的 key 不能重复,因此不能插入重复 key 的节点;
    • insert_equal():表示节点的 key 可以重复,因此可以插入重复 key 的节点;
      在这里插入图片描述
2. rb_tree 源代码
  • 五个模板参数:

    • Key:key 的类型;
    • Value:Value 的类型;
    • KeyOfValue:一个仿函数(即一个 class),提供从 Value 取出 key 的方式;
    • Compare:一个仿函数(即一个 class),提供 key 进行比较的方式;
    • Alloc:内存分配器;
  • 注意点:

    • 明确术语:
    • value == key + data;
      在这里插入图片描述
  • data 部分:

    • size_type node_count:rb_tree 的大小(占4个字节);
    • link_type(一个指针)header:一个空白内容的头节点,如图所示(占4个字节);
    • Compare key_compare:上面的模板参数(占1个字节);
  • sizeof:

    • 仿函数是一个 class,内容只有 operator() 的重载,没有 data 部分,理论 sizeof 应该是0;
    • 但 C++ 编译器对空 class 的大小会设置为 1
    • rb_tree 的 sizeof == 4(unsigned int) + 4(一个指针) + 1(一个仿函数) == 9;
    • 但由于内存对齐,编译器进行了填充,所以最后的 sizeof == 12
      在这里插入图片描述
3. 直接使用 rb_tree(不建议直接使用,使用上层容器)
  • Key 和 Value 模板参数设置相同,说明 value 没有指明 data,即 value 没有 data 部分,只有 key;
  • KeyOfValue 模板参数使用 identity 仿函数,从 Value 取出 Key;
  • Compare 模板参数使用 less 仿函数,Key 比较的方式;
    在这里插入图片描述
4. G2.9版的 rb_tree 用例

在这里插入图片描述

5. 4.9版的 rb_tree 结构
  • C++11 所有的容器修改符合一个设计模式:
    • handle and body:即把接口和实现分开;
  • sizeof:
    • _Rb_tree_node_base 有四个 data:
      • 三个 pointer 和一个枚举 enum;
      • 3*4 + sizeof(enum) == 24;(G2.9版的 sizeof == 12)
        在这里插入图片描述
6. 4.9版的 rb_tree 用例
  • 类型名称、模板参数名称、函数名称改变;
    在这里插入图片描述

2. 深度探索 set / multiset

1. set / multiset 介绍
  • set / multiset 的 key 就是 value,value 就是 key;
  • set / multiset 提供遍历操作和 iterator,因为 rb_tree 有提供;
  • set / multiset 禁止使用 iterator 进行赋值(即禁止修改 key):
    • 实现上怎么做到:
      • 使用 const_iterator,这种迭代器只能用于读取容器内的元素,但不能改变其值;
  • set / multiset 使用的 insert():
    • set 使用的是 rb_tree 的 insert_unique()(key 不能重复);
    • multiset 使用的是 rb_tree 的 insert_equal()(key 可以重复);
      在这里插入图片描述
2. G2.9版的 set(可以类比 multiset)
  • data 部分:
    • 只有一个 t(rb_tree)(12个字节);
  • 模板参数:
    • set 有三个模板参数(两个有默认值),分别对应 rb_tree 的 Key、Compare、Alloc;
  • set → rb_tree 模板参数的对应:
    • Key:Key;
    • Value:Key,rb_tree 的 Key 和 Value 设置相同;
    • KeyOfValue:identity() 仿函数【从 Value 中获取 Key 的方式】;
    • Compare:less() 仿函数【Key 进行比较大小的方式】;
  • iterator:
    • set 的 iterator 使用 const_iterator,这样就能实现 set / multiset 的 iterator 不能修改 key;
    • 但 iterator 的指向可以被改变,所以也可以用来遍历;
  • 理解为 adapter:
    • set 的所有操作就是通过 rb_tree 的操作实现;
    • 所以也可以把 set / multiset 理解为 container adapter;
      在这里插入图片描述

3. 深度探索 map / multimap

1. map / multimap 介绍
  • map / multimap 提供遍历操作和 iterator,因为 rb_tree 有提供;

  • map / multimap 禁止使用 iterator 进行赋值(即禁止修改 key),但可以修改 data:

    • 实现上是怎么做到的,看下面小节
  • map / multimap 使用的 insert():

    • map 使用的是 rb_tree 的 insert_unique()(key 不能重复);
    • multimap 使用的是 rb_tree 的 insert_equal()(key 可以重复);
      在这里插入图片描述
2. G2.9版的 map(可以类比 multimap)
  • data 部分:
    • 只有一个 t(rb_tree);
  • 模板参数:
    • map有四个模板参数(两个有默认值);
    • 重点部分:rb_tree 的 Value == pair<const Key, T>,将 map 传入的两个类型进行了包装,同时把 key 设置成了 const,这是为了实现 map / multimap 通过 iterator 不能修改 key,但能修改 data;
  • set → rb_tree 模板参数的对应
    • Key:Key;
    • Value:pair<const Key, T>:
      • 把 key 设置成了 const,使得 key 不能重新赋值,实现了 map / multimap 通过 iterator 不能修改 key,但能修改 data;
    • KeyOfValue:select1st() 仿函数(从名字可以看出是从 pair 中选择第一个变量)【从 Value 中获取 Key 的方式】;;
    • Compare:less() 仿函数【Key 进行比较大小的方式】;
  • iterator:
    • map 的 iterator 使用一般的 iterator;
  • 理解为 adapter:
    • map 的所有操作就是通过 rb_tree 的操作实现;
    • 所以也可以把 map / multimap 理解为 container adapter;
      在这里插入图片描述
3. select1st() 仿函数实现
  • 传入一个 pair,返回 pair 的 first;
    在这里插入图片描述
4. map 可以使用 operator[] 操作符
  • map 可以使用 operator[]:
    • (multimap 不行,multimap 可能有重复 key,不知道该返回哪个 key 的 data):
    • 使用形式:
      • 返回 data:map[key];
      • 赋值 data:map[key] = data;
    • 如果 key 不存在,会自动创建 key,同时 data 设置默认值;
  • 代码流程:
    • 先用二分查找
      • 如果 key 存在,则返回 key 出现的第一个位置;
      • 如果 key 不存在,则返回 key 应该插入的位置;
        • 使用 insert 插入 key;
    • 返回 pair.second;
  • 使用 insert(pair<…, …>) 和 operator[] 进行插入的效率:
    • 由于 operator[] 需要先使用二分查找,再 insert;
    • 所以对于插入操作,直接 insert 更快;
      在这里插入图片描述

4. 深度探索 hashtable

1. hashtable 存放规则
  • 存放 object,每个 object 都会通过 HashFcn 映射到一个数字 hashcode(这个数字怎么得到的,应该是看编译器);
  • 根据这个数字 hashcode 计算 object 应该在 hashtable 中的位置:
    • hashtable_index = hashcode % hashtable_len;=
2. hashtable 扩容规则
  • 术语:
    • hashtable 的每个位置称为 buckets(篮子)
    • buckets 数组是 vector;
    • 重新扩容称为 rehashing
  • 扩容条件:
    • 当插入节点数量 > buckets(篮子)数量,就需要扩容 hashtable 数组;
  • 扩容大小:
    • **G2.9版:**扩容后的大小,选择当前大小的两倍附近的质数;
    • STL hashtable 有 static 数组,数组内容记录扩容的大小数值,这样不需要每次扩容都计算一次;
  • 扩容后,全部元素要重新计算放置位置
    在这里插入图片描述
3. G2.9版的 hashtable
  • 六个模板参数:
    • Value:Value 的类型,概念同 rb_tree 的 Value;
    • Key:key 的类型;
    • HashFcn:将 object 映射到一个数值(编号)的函数(最重要的部分);
    • ExtractKey:一个仿函数(即一个 class),提供从 Value 取出 key 的方式(和 rb_tree 的 KeyOfValue 含义相同);
    • EqualKey:一个仿函数(即一个 class),提供判断 Key 相同的方式;
    • Alloc:内存分配器;
  • data 部分:
    • hasher hash:模板参数中的 HashFcn(占1个字节);
    • key_equal equals:模板参数中的 EqualKey(占1个字节);
    • ExtractKey get_key:模板参数中 ExtractKey (占1个字节);
    • vector数组 buckets:占12个字节(vector中有三个指针);
    • size_type num_elements:节点的数量(占4个字节);
  • sizeof:
    • sizeof == 1 + 1 + 1 + 12 + 4 = 19(内存对齐为4的倍数,32位系统下)== 20个字节
  • hashtable iterator:
    • data 部分:
      • node* cur:指向当前节点;
      • hashtable* ht:指向 buckets 数组;
  • __hashtable_node:
    • 结构如图右上角;
      在这里插入图片描述
4. 直接使用 hashtable
  • Key 和 Value 模板参数设置相,即 value 没有 data 部分,只有 key;
  • HashFcn 模板参数使用 hash 仿函数;
  • ExtractKey 模板参数使用 identity 仿函数,因为 key 和 value 相同;
  • EqualKey 模板参数使用 eqstr(自定义仿函数,使用 strcmp 函数);
    在这里插入图片描述
5. G2.9版的 hash-function,hash-code
  • STL 的 hash 函数使用**特化(全特化)**实现各种数据类型的 hash-function;

  • 对于数值类型:

    • hash 函数直接返回数值本身,将数值当作 hash-code;
      在这里插入图片描述
  • 对于 C 风格的字符串类型:

    • 调用如图 __stl_hash_string 函数;
    • G2.9 STL 没有提供 hash<std :: string>(C++11 有提供 string 的特化版本)
      在这里插入图片描述
  • 对于自定义数据:

    • 为 hash 函数写出一个自定义的特化版本,如上面的例子所示;
    • 所有的数据类型无外乎就是数值 + 字符串,最简单的自定义 hash 特化:【通过已有的 hash 特化函数,计算出 object 数值、字符串对应的 hashcode,然后全部相加;】
6. modulus(模)运算
  • gnu 编译器中通过 hash-code 查找 buckets 位置,就是通过模运算
  • 图左是 hashtable 中的函数,其中有需要计算 buckets 位置的函数 bkt_num_key;
    在这里插入图片描述
7. hashtable 重要结论
  • 对于 hashtable / hash_set / hash_multiset / hash_map / hash_multimap:
  • buckets 的数量一定 > 容器中元素的数量;

5. 深度探索 hash_set / hash_multiset

  • hash_set 是以 hashtable 为底层机制实现的。故对 hash_set 的各种操作可以转调用 hashtable 来实现。

  • hash_set 与 set 的不同:

  • hash_set 的底层机制是 hashtable,而 set 的底层机制是 rb-tree;

  • set 的元素能够自动排序,而 hash_set 的元素没有排序功能

  • hash_set中 key 就是 value, value 就是 key。

  • hash_set / hash_multiset 使用的 insert():

  • hash_set 中的插入函数使用的是 hashtable 中的 insert_unique() ;

  • 而 hash_multiset 中的插入函数使用的是 hashtable 中的 insert_equal() 。

  • // hash_set类定义, 只做参考,真实性待考证
    template <class Value, class HashFcn = hash<Value>,
              class EqualKey = equal_to<Value>,
              class Alloc = alloc>
    class hash_set
    {
    private:
      typedef hashtable<Value, Value, HashFcn, identity<Value>, 
                        EqualKey, Alloc> ht;
      ht rep;        //底层机制 hashtable
     
    public:
      typedef typename ht::key_type key_type; 
      typedef typename ht::value_type value_type;
      typedef typename ht::hasher hasher;
      typedef typename ht::key_equal key_equal;
     
      typedef typename ht::size_type size_type;
      typedef typename ht::difference_type difference_type;
      ...
      hasher hash_funct() const {return rep.hash_funct();}
      key_equal key_eq() const {return rep.key_eq();}
     
    public:
      hash_set() : rep(100, hasher(), key_equal()) {}    //缺省使用100个篮子,
                                                         //最后将被调整成对应的质数193
     
      hash_set(size_type n, const hasher& hf) : rep(n, hf, key_equal()) {}    //构造函数
      hash_set(size_type n, const hasher& hf, const key_equal& eql)           //构造函数
        : rep(n, hf, eql) {}
     
    //方法,通过hashtable实现
      size_type size() const { return rep.size(); }
      size_type max_size() const { return rep.max_size(); }
      bool empty() const { return rep.empty(); }
      void swap(hash_set& hs) { rep.swap(hs.rep); }
      friend bool operator== __STL_NULL_TMPL_ARGS (const hash_set&,
                                                   const hash_set&);
     
      iterator begin() const { return rep.begin(); }
      iterator end() const { return rep.end(); }
    

6. 深度探索 hash_map / hash_multimap

  • hash_map 和 map 一样可以使用 [] 操作符;

7. 深度探索 unordered 容器

  • C++11 把 hash 容器改名为 unordered 容器
    在这里插入图片描述
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值