文章目录
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)
- _Rb_tree_node_base 有四个 data:
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 数组;
- data 部分:
- __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;
- 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 容器