深入探索C++11标准库STL:新特性和优化技巧
- 一、前言
- 二、容器简介
- 三、迭代器简介
- 四、map与unordered_map(红黑树VS哈希表)
- 4.1、map和unordered_map的差别
- 4.2、优缺点以及适用处
- 4.3、小结
- 五、总结
一、前言
STL定义了强大的、基于模板的、可复用的组件,实现了许多通用的数据结构及处理这些数据结构的算法。其中包含三个关键组件——容器(container,流行的模板数据结构)、迭代器(iterator)和算法(algorithm)。
组件 | 描述 |
---|---|
容器 | 容器是用来管理某一类对象的集合。C++ 提供了各种不同类型的容器,比如 deque、list、vector、map 等。 |
迭代器 | 用于遍历对象集合的元素。这些集合可能是容器,也可能是容器的子集。 |
算法 | 算法作用于容器。它们提供了执行各种操作的方式,包括对容器内容执行初始化、排序、搜索和转换等操作。 |
二、容器简介
STL容器,可将其分为四类:序列容器、有序关联容器、无序关联容器、容器适配器。
序列容器:
标准库容器类 | 描述 |
---|---|
array | 固定大小,直接访问任意元素 |
deque | 从前部或后部进行快速插入和删除操作,直接访问任何元素 |
forward_list | 单链表,在任意位置快速插入和删除 |
list | 双向链表,在任意位置进行快速插入和删除操作 |
vector | 从后部进行快速插入和删除操作,直接访问任意元素 |
有序关联容器(键按顺序保存):
标准库容器类 | 描述 |
---|---|
set | 快速查找,无重复元素 |
multiset | 快速查找,可有重复元素 |
map | 一对一映射,无重复元素,基于键快速查找 |
multimap | 一对一映射,可有重复元素,基于键快速查找 |
无序关联容器:
标准库容器类 | 描述 |
---|---|
unordered_set | 快速查找,无重复元素 |
unordered_multiset | 快速查找,可有重复元素 |
unordered_map | 一对一映射,无重复元素,基于键快速查找 |
unordered_multimap | 一对一映射,可有重复元素,基于键快速查找 |
容器适配器:
标准库容器类 | 描述 |
---|---|
stack | 后进先出(LIFO) |
queue | 先进先出(FIFO) |
priority_queue | 优先级最高的元素先出 |
序列容器描述了线性的数据结构(也就是说,其中的元素在概念上” 排成一行"), 例如数组、向量和 链表。
关联容器描述非线性的容器,它们通常可以快速锁定其中的元素。这种容器可以存储值的集合或 者键-值对。
栈和队列都是在序列容器的基础上加以约束条件得到的,因此STL把stack和queue作为容器适配器来实现,这样就可以使程序以一种约束方式来处理线性容器。类型string支持的功能跟线性容器一样, 但是它只能存储字符数据。
三、迭代器简介
迭代器在很多方面与指针类似,也是用于指向首类容器中的元素(还有一些其他用途,后面将会提
到)。 迭代器存有它们所指的特定容器的状态信息,即迭代器对每种类型的容器都有一个实现。 有些迭代器的操作在不同容器间是统一的。 例如,*
运算符间接引用一个迭代器,这样就可以使用它所指向的元素。++
运算符使得迭代器指向容器中的下一个元素(和数组中指针递增后指向数组的下一个元素类似)。
STL 首类容器提供了成员函数begin
和 end
。函数 begin
返回一个指向容器中第一个元素的迭代器,函数 end
返回一个指向容器中最后一个元素的下一个元素(这个元素并不存在,常用于判断是否到达了容器的结束位位置)的迭代器。 如果迭代器 it
指向一个特定的元素,那么 ++it
指向这个元素的下一个元素。*it
指代的是it
指向的元素。 从函数 end 中返回的迭代器只在相等或不等的比较中使用,来判断这个“移动的迭代器” (在这里指it
)是否到达了容器的末端。
使用一个 iterator 对象来指向一个可以修改的容器元素,使用一个 const_iterator 对象来指向一个不能修改 的容器元素。
类型 | 描述 |
---|---|
随机访问迭代器(random access) | 在双向迭代器基础上增加了直接访问容器中任意元素的功能, 即可以向前或向后跳转任意个元素 |
双向迭代器(bidirectional) | 在前向迭代器基础上增加了向后移动的功能。支持多遍扫描算法 |
前向迭代器(forword) | 综合输入和输出迭代器的功能,并能保持它们在容器中的位置(作为状态信息),可以使用同一个迭代器两次遍历一个容器(称为多遍扫描算法) |
输出迭代器(output) | 用于将元素写入容器。 输出迭代楛每次只能向前移动一个元索。 输出迭代器只支持一遍扫描算法,不能使用相同的输出迭代器两次遍历一个序列容器 |
输入迭代器(input) | 用于从容器读取元素。 输入迭代器每次只能向前移动一个元素。 输入迭代器只支持一遍扫描算法,不能使用相同的输入迭代器两次遍历一个序列容器 |
每种容器所支持的迭代器类型决定了这种容器是否可以在指定的 STL 算 法中使用。 支持随机访问迭代器的容器可用于所有的 STL 算法(除了那些需要改变容器大小的算法,这样的算法不能在数组和 array对象中使用)。 指向 数组的指针可以代替迭代器用于几乎所有的 STL 算法中,包括那些要求随机访问迭代器的算法。
下表显示了每种 STL 容器所支持的迭代器类型。 注意, vector 、 deque 、 list 、 set、 multiset 、 map 、 multimap以及 string 和数组都可以使用迭代器遍历。
容器 | 支持的迭代器类型 | 容器 | 支持的迭代器类型 |
---|---|---|---|
vector | 随机访问迭代器 | set | 双向迭代器 |
array | 随机访问迭代器 | multiset | 双向迭代器 |
deque | 随机访问迭代器 | map | 双向迭代器 |
list | 双向迭代器 | multimap | 双向迭代器 |
forword_list | 前向迭代器 | unordered_set | 双向迭代器 |
stack | 不支持迭代器 | unordered_multiset | 双向迭代器 |
queue | 不支持迭代器 | unordered_map | 双向迭代器 |
priority_queue | 不支持迭代器 | unordered_multimap | 双向迭代器 |
下表显示了在 STL容器的类定义中出现的几种预定义的迭代器 typedef。不是每种 typedef 都出现在每个容器中。 我们使用常量版本的迭代器来访问只读容器或不应该被更改的非只读容器,使用反向迭代器来以相反的方向访问容器。
为迭代器预先定义的typedef | ++的方向 | 读写能力 |
---|---|---|
iterator | 向前 | 读/写 |
const_iterator | 向前 | 读 |
reverse_iterator | 向后 | 读/写 |
const_reverse_iterator | 向后 | 读 |
下表显示了可作用在每种迭代器上的操作。 除了给出的对于所有迭代器都有的运算符,迭代器还必须提供默认构造函数、拷贝构造函数和拷贝赋值操作符。 前向迭代器支持++
和所有的输入和输出迭代器的功能。 双向迭代器支持–操作和前向迭代器的功能。 随机访问迭代器支持所有在表中给出的操作。 另外, 对于输入迭代器和输出迭代器,不能在保存迭代器之后再使用保存的值。
适用所有迭代器的操作 | 描述 |
---|---|
++p | 前置自增迭代器 |
p++ | 后置自增迭代器 |
p=p1 | 将一个迭代器赋值给另一个迭代器 |
输入迭代器 | 描述 |
---|---|
*p | 间接引用一个迭代器 |
p->m | 使用迭代器读取元素m |
p==p1 | 比较两个迭代器是否相等 |
p!=p1 | 比较两个迭代器是否不相等 |
输出迭代器 | 描述 |
---|---|
*p | 间接引用一个迭代器 |
p=p1 | 把一个迭代器赋值给另一个 |
前向迭代器:前向迭代器提供了输入和输出迭代器的所有功能。
双向迭代器 | 描述 |
---|---|
–p | q |
p– | 后置自减迭代器 |
随机访问迭代器 | 描述 |
---|---|
p+=i | 迭代器p前进i个位置 |
p-=i | 迭代器p后退i个位置 |
p+i | 在迭代器p 的位置上前进i个位置 |
p-i | 在迭代器p的位置上后退i个位置 |
p-p1 | 表达式的值是一个整数,它代表同一个容器中两个元素间的距离 |
p[i] | 返回与迭代器p的位置相距i的元素 |
p<p1 | 若迭代器p小于p1(即容器中p在p1前)则返回 true, 否则返回 false |
p<=p1 | 若迭代器p小千或等于p1 (即容器中p 在p1前或位咒相同)则返回 true, 否则返回 false |
p>p1 | 若迭代器p 大于p1(即容器中p在p1后)则返回true, 否则返回false |
p>=p1 | 若迭代器p大于或等于p1(即容楛中p在p1后或位置相同)则返回 true, 否则返回 false |
四、map与unordered_map(红黑树VS哈希表)
C++11 增加了无序容器 unordered_map/unordered_multimap 和unordered_set/unordered_multiset,由于这些容器中的元素是不排序的,因此,比有序容器map/multimap 和 set/multiset 效率更高。 map 和 set 内部是红黑树,在插入元素时会自动排序,而无序容器内部是散列表( Hash Table),通过哈希( Hash),而不是排序来快速操作元素,使得效率更高。由于无序容器内部是散列表,因此无序容器的 key 需要提供 hash_value 函数,其他用法和map/set 的用法是一样的。不过对于自定义的 key,需要提供 Hash 函数和比较函数。
4.1、map和unordered_map的差别
(1)需要引入的头文件不同。
- map:
#include <map>
- unordered_map:
#include <unordered_map>
(2)内部实现机理不同。
- map: map内部实现了一个红黑树(红黑树是非严格平衡二叉搜索树,而AVL是严格平衡二叉搜
索树),红黑树具有自动排序的功能,因此map内部的所有元素都是有序的,红黑树的每一个节点
都代表着map的一个元素。 - unordered_map: unordered_map内部实现了一个哈希表(也叫散列表,通过把关键码值映射到
Hash表中一个位置来访问记录,查找的时间复杂度可达到O(1),其在海量数据处理中有着广泛应
用)。因此,其元素的排列顺序是无序的。
4.2、优缺点以及适用处
map:
- 优点:有序性,这是map结构最大的优点,其元素的有序性在很多应用中都会简化很多的操作
红黑树,内部实现一个红黑书使得map的很多操作在log n 的时间复杂度下就可以实现,因此效率非常的高。 - 缺点:空间占用率高,因为map内部实现了红黑树,虽然提高了运行效率,但是因为每一个节点都需要额外保存父节点、孩子节点和红/黑性质,使得每一个节点都占用大量的空间。
- 适用处:对于那些有顺序要求的问题,用map会更高效一些。
unordered_map:
- 优点: 因为内部实现了哈希表,因此其查找速度非常的快。
- 缺点: 哈希表的建立比较耗费时间。
- 适用处:对于查找问题,unordered_map会更加高效一些,因此遇到查找问题,常会考虑一下用unordered_map。
4.3、小结
- 内存占有率的问题就转化成红黑树 VS hash表 , 还是unorder_map占用的内存要高。
- 但是unordered_map执行效率要比map高很多。
- 对于unordered_map或unordered_set容器,其遍历顺序与创建该容器时输入的顺序不一定相同,因为遍历是按照哈希表从前往后依次遍历的。
五、总结
C++ 参考手册,重点学习C++11起的容器。