# 标准模板库(Standard Template Library)
STL的代码从广义上讲分为三类:
- container(容器): 包含、放置数据的地方。
- iterator(迭代器): 在容器中指出一个位置、或成对使用以划定一个区域,用来限定操作所涉及到的数据范围。
- algorithm(算法): 要执行的操作
几乎所有的代码都采用了模板类和模板函数的方式,这相比于传统的由函数和类组成的库来说提供了更好的代码重用机会。
在C++标准中,STL被组织为下面的13个头文件:\<algorithm>、\<deque>、\<functional>、\<iterator>、\<vector>、\<list>、\<map>、\<memory>、\<numeric>、\<queue>、\<set>、\<stack>和\<utility>
## 容器
STL提供了专家级的各种容器,功能更好,复用性更强,在应用程序开发过程中尽可能应用STL。容器可以分为以下三类:
- 序列容器
按线性序列来存储某类型值的集合,每个元素都有自己特定的位置。例如vector, list, forward_list, deque和arrays
- 关系容器
更注重快速高效的检索数据的能力,根据键值key来检索数据。容器中成员在初始化后都是按一定顺序排好的。例如set, multiset, map, multimap, unordered_set, bitset和valarray等
- 容器适配器
在已有容器的基础上进行再封装,使其实现更多的特性,而不必重新定义一个新容器。例如stack, queue, priority_queue
顺序容器和关联容器都共有下列函数:
![](http://image.135editor.com/files/users/195/1958867/201802/4urKrGUn_jLK2.png)
![](https://img-blog.csdn.net/20170718154433858?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvd29uaXUyMTExMTE=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)
遵循规则:
1.所有容器中存放的都是值而非引用。因此容器中的每个元素必须能够被拷贝,如想存放的不是副本,容器元素只能是指针。
2.容器中所有元素都是有次序(order)的,可以进行一次或多次遍历每个元素。
常用容器:
### vector
是一种序列式容器,事实上和数组差不多,但它比数组更优越。一般来说数组不能动态拓展,因此在程序运行的时候不是浪费内存,就是造成越界。而vector正好弥补了这个缺陷,它的特征是相当于可分配拓展的数组(动态数组),它的随机访问快,在中间插入和删除慢,但在末端插入和删除快。
- 拥有一段连续的内存空间,并且起始地址不变,因此它能非常好的支持随机存取,即[]操作符,但由于它的内存空间是连续的,所以在中间进行插入和删除会造成内存块的拷贝,另外,-当该数组后的内存空间不够时,需要重新申请一块足够大的内存并进行内存的拷贝。这些都大大影响了vector的效率。
- 对头部和中间进行插入删除元素操作需要移动内存,如果你的元素是结构或类,那么移动的同时还会进行构造和析构操作,所以性能不高。
- 对最后元素操作最快(在后面插入删除元素最快),此时一般不需要移动内存,只有保留内存不够时才需要。
- 优点:支持随机访问,即使用[下标]的方式来访问元素,所以查询效率高。
缺点:当向其头部或中部插入或删除元素时,为了保持原本的相对次序,插入或删除点之后的所有元素都必须移动,所以插入的效率比较低。
适用场景:适用于对象简单,变化较小,并且频繁随机访问的场景。
### deque
所谓 deque (发音为['dek]),是"double-ended queue” 的缩写。它是一个动态数组(dynamic array),可以向两端发展,因此不论在尾部或头部安插元素都十分迅速。 在中间部分安插元素则比较费时,因为必须移动其它元素。
- 按页或块来分配存储器的,每页包含固定数目的元素。
- deque是list和vector的折中方案。兼有list的优点,也有vector随机线性访问效率高的优点。
优点:支持随机访问,即[]操作和deque.at(),所以查询效率高;可在双端进行pop,push。
缺点:不适合中间插入删除操作;占用内存多。
适用场景:适用于既要频繁随机存取,又要关心两端数据的插入与删除的场景。
### list
List由双向链表(doubly linked list)实现而成,元素也存放在堆中,每个元素都是放在一块内存中,他的内存空间可以是不连续的,通过指针来进行数据的访问,这个特点使得它的随机存取变得非常没有效率,因此它没有提供[]操作符的重载。但是由于链表的特点,它可以很有效率的支持任意地方的插入和删除操作。
- 没有空间预留习惯,所以每分配一个元素都会从内存中分配,每删除一个元素都会释放它占用的内存。
- 在哪里添加删除元素性能都很高,不需要移动内存,当然也不需要对每个元素都进行构造与析构了,所以常用来做随机插入和删除操作容器。
- 访问开始和最后两个元素最快,其他元素的访问时间一样。
优点:内存不连续,动态操作,可在任意位置插入或删除且效率高。
缺点:不能随机访问,相对于vector占用内存多。
适用场景:适用于经常进行插入和删除操作并且不经常随机访问的场景。
### set
set(集合)由红黑树实现,其内部元素依据其值自动排序,每个元素值只能出现一次,不允许重复。(红黑树是平衡二叉树的一种)
- set中的元素都是排好序的。
- set集合中没有重复的元素。
- map和set的插入删除效率比用其他序列容器高,因为对于关联容器来说,不需要做内存拷贝和内存移动。
优点:使用平衡二叉树实现,便于元素查找,且保持了元素的唯一性,以及能自动排序。
缺点:每次插入值的时候,都需要调整红黑树,效率有一定影响。
适用场景:适用于经常查找一个元素是否在某群集中且需要排序的场景。
### map
Map由红黑树实现,其元素都是“键值/实值”所形成的一个对组(key/value pairs)。每个元素有一个键,是排序准则的基础。每一个键只能出现一次,不允许重复。
Map主要用于资料一对一映射(one-to-one)的情况,map内部自建一颗红黑树(平衡二叉树中的一种),这颗树具有对数据自动排序的功能,所以在map内部所有的数据都是有序的。比如一个班级中,每个学生的学号跟他的姓名就存在着一对一映射的关系。
- 自动建立Key - value的对应。key 和 value可以是任意你需要的类型。
- 根据key值快速查找记录,查找的复杂度基本是O(logN),如果有1000个记录,二分查找最多查找10次(1024),1,000,000个记录,二分查找最多查找20次。
- 增加和删除节点对迭代器的影响很小,除了那个操作节点,对其他的节点都没有什么影响。
- 对于迭代器来说,可以修改实值,而不能修改key。
优点:使用平衡二叉树实现,便于元素查找,且能把一个值映射成另一个值,可以创建字典。
缺点:每次插入值的时候,都需要调整红黑树,效率有一定影响。
适用场景:适用于需要存储一个数据字典,并要求方便地根据key找value的场景。
### stack, queue, priority_queue
stack 容器对元素采取LIFO(后进先出)的管理策略。
queue 容器对元素采取FIFO(先进先出)的管理策略。
priority_queue 容器中的元素可以拥有不同的优先权。所谓优先权,乃是基于程序员提供的排序准则(缺省使用operators)而定义。Priority queue的效果相当于这样一个buffer: “下一元素永远是queue中优先级最高的元素”。如果同时有多个元素具备最髙优先权,则其次序无明确定义。
### 小结
| 容器 | 实现原理 |
| -------------- | ------------------------------------------------------------ |
| vector | 底层数据结构为数组 ,支持快速随机访问 |
| list | 底层数据结构为双向链表,支持快速增删 |
| deque | 底层数据结构为一个中央控制器和多个缓冲区,详细见STL源码剖析P146,支持首尾(中间不能)快速增删,也支持随机访问。deque是一个双端队列(double-ended queue),也是在堆中保存内容的.它的保存形式如下: [堆1] --> [堆2] -->[堆3] --> ...每个堆保存好几个元素,然后堆和堆之间有指针指向,看起来像是list和vector的结合品. |
| stack | 底层一般用list或deque实现,封闭头部即可,不用vector的原因应该是容量大小有限制,扩容耗时 |
| queue | 底层一般用list或deque实现,封闭头部即可,不用vector的原因应该是容量大小有限制,扩容耗时 |
| priority_queue | 底层数据结构一般为vector为底层容器,堆heap为处理规则来管理底层容器实现 |
| set | 底层数据结构为红黑树,有序,不重复 |
| multiset | 底层数据结构为红黑树,有序,可重复 |
| map | 底层数据结构为红黑树,有序,不重复 |
| multimap | 底层数据结构为红黑树,有序,可重复 |
| hash_set | 底层数据结构为hash表,无序,不重复 |
| hash_multiset | 底层数据结构为hash表,无序,可重复 |
| hash_map | 底层数据结构为hash表,无序,不重复 |
| hash_multimap | 底层数据结构为hash表,无序,可重复 |
## 迭代器
一种对象,本质是一个指向容器中数据的**指针**,通过改变这个指针遍历容器中的所有元素。它是最大好处是使容器与算法分离。因为不同容器中完成相同功能代码的**思路**大体相同,将其**抽象**出来就产生了迭代器。
STL迭代器共分五大类型:
- 输入迭代器,按顺序只能读取一次,支持++、==、!=
- 输出迭代器,按顺序只写一次,支持++
- 前向迭代器,包含了输入输出迭代器两者所有可能,可以对一个值多次读写,只能向前移动,支持++、==、!=
- 双向迭代器,具有前向迭代器的全部功能,也可以向后移动操作,支持++、--。**C++的全部标准库容器都至少在双向迭代器的层次上**
- 随机访问迭代器,具有双向迭代器的全部功能,再加上一个指针的所有功能,支持++、--、[n]、-n、<、<=、>、>=
从以上可以看出,从前到后迭代器是一步步细化的,编程时根据实际情况选择适宜的迭代器,从而达到最高的效率。
所有容器都提供获得迭代器的函数。
```
begin() 返回一个迭代器,指向第一个元素
end() 返回一个迭代器,指向最后一个元素
```
![](https://img-blog.csdn.net/20170718164229180?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvd29uaXUyMTExMTE=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)
容器的迭代器都是定身制作的,什么意思呢?每个容器都内置一个迭代器。该内置迭代器由容器的设计者实现。
每一个容器相应的迭代器都是依据容器的特点来实现的,以求达到最高效率。
### 迭代器失效
迭代器失效指的是迭代器原来所指向的元素不存在了或者发生了移动,此时假设不更新迭代器,将无法使用该过时的迭代器。
迭代器失效的根本原因是对容器的某些操作改动了容器的内存状态(如容器又一次载入到内存)或移动了容器内的某些元素。
举个例子,使vector迭代器失效的操作有:
1.向vector容器内加入元素(push_back,insert)
```
向vector容器加入元素分下面两种情况:
1)若向vector加入元素后,整个vector又一次载入。即前后两次vector的capacity()的返回值不同一时候,此时该容器 内的全部元素相应的迭代器都将失效。
2)若该加入操作不会导致整个vector容器载入,则指向新插入元素后面的那些元素的迭代器都将失效。
```
2.删除操作(erase,pop_back,clear)
```
vector运行删除操作后,被删除元素相应的迭代器以及其后面元素相应的迭代器都将失效。
```
3.resize操作:调整当前容器的size
```
调整容器大小对迭代器的影响分例如以下情况讨论:
A.若调整后size>capacity,则会引起整个容器又一次载入,整个容器的迭代器都将失效。
B.若调整后size<capacity,则不会又一次载入,详细情况例如以下:
B1.若调整后容器的size>调整前容器的size,则原来vector的全部迭代器都不会失效。
B2.若调整后容器的size<调整前容器的size,则容器中那些别切掉的元素相应的迭代器都将失效。
```
4.赋值操作(v1=v2 v1.assign(v2))
```
会导致左操作数v1的全部迭代器都失效,显然右操作数v2的迭代器都不会失效。
```
5.交换操作(v1.swap(v2))
```
因为在做交换操作时。v1,v2均不会删除或插入元素,所以容器内不会移动不论什么元素。故v1,v2的全部迭代器都不会失效。
```
## 算法
STL中算法大致可以分为四类:
- 非可变序列算法:指不直接修改其所操作的容器内容的算法
- 可变序列算法:指可以修改它们所操作的容器内容的算法
- 排序算法:包括对序列进行排序和合并的算法、搜索算法以及有序序列上的集合操作
- 数值算法: 对容器内容进行数值计算
### 查找算法(13个):判断容器中是否包含某个值
| 函数名 | 函数说明 |
| ------------- | ------------------------------------------------------------ |
| binary_search | 使用二分法在有序序列中查找value,找到返回true。重载的版本实用指定的比较函数对象或函数指针来判断相等。 |
| adjacent_find | 在iterator对标识元素范围内,查找一对相邻重复元素,找到则返回指向这对元素的第一个元素的ForwardIterator。否则返回last。重载版本使用输入的二元操作符代替相等的判断。 |
| count | 利用等于操作符,把标志范围内的元素与输入值比较,返回相等元素个数。 |
| count_if | 利用输入的操作符,对标志范围内的元素进行操作,返回结果为true的个数。 |
| equal_range | 功能类似equal,返回一对iterator,第一个表示lower_bound,第二个表示upper_bound。 |
| find | 利用底层元素的等于操作符,对指定范围内的元素与输入值进行比较。当匹配时,结束搜索,返回该元素的一个InputIterator。 |
| find_end | 在指定范围内查找"由输入的另外一对iterator标志的第二个序列"的最后一次出现。找到则返回最后一对的第一个ForwardIterator,否则返回输入的"另外一对"的第一个ForwardIterator。重载版本使用用户输入的操作符代替等于操作。 |
| find_first_of | 在指定范围内查找"由输入的另外一对iterator标志的第二个序列"中任意一个元素的第一次出现。重载版本中使用了用户自定义操作符。 |
| find_if | 使用输入的函数代替等于操作符执行find。 |
| lower_bound | 返回一个ForwardIterator,指向在有序序列范围内的可以插入指定值而不破坏容器顺序的第一个位置。重载函数使用自定义比较操作。 |
| upper_bound | 返回一个ForwardIterator,指向在有序序列范围内插入value而不破坏容器顺序的最后一个位置,该位置标志一个大于value的值。重载函数使用自定义比较操作。 |
| search | 给出两个范围,返回一个ForwardIterator,查找成功指向第一个范围内第一次出现子序列(第二个范围)的位置,查找失败指向last1。重载版本使用自定义的比较操作。 |
| search_n | 在指定范围内查找val出现n次的子序列。重载版本使用自定义的比较操作。 |
### 排序和通用算法(14个):提供元素排序策略
| 函数名 | 函数说明 |
| ----------------- | ------------------------------------------------------------ |
| inplace_merge | 合并两个有序序列,结果序列覆盖两端范围。重载版本使用输入的操作进行排序。 |
| merge | 合并两个有序序列,存放到另一个序列。重载版本使用自定义的比较。 |
| nth_element | 将范围内的序列重新排序,使所有小于第n个元素的元素都出现在它前面,而大于它的都出现在后面。重载版本使用自定义的比较操作。 |
| partial_sort | 对序列做部分排序,被排序元素个数正好可以被放到范围内。重载版本使用自定义的比较操作。 |
| partial_sort_copy | 与partial_sort类似,不过将经过排序的序列复制到另一个容器。 |
| partition | 对指定范围内元素重新排序,使用输入的函数,把结果为true的元素放在结果为false的元素之前。 |
| random_shuffle | 对指定范围内的元素随机调整次序。重载版本输入一个随机数产生操作。 |
| reverse | 将指定范围内元素重新反序排序。 |
| reverse_copy | 与reverse类似,不过将结果写入另一个容器。 |
| rotate | 将指定范围内元素移到容器末尾,由middle指向的元素成为容器第一个元素。 |
| rotate_copy | 与rotate类似,不过将结果写入另一个容器。 |
| sort | 以升序重新排列指定范围内的元素。重载版本使用自定义的比较操作。 |
| stable_sort | 与sort类似,不过保留相等元素之间的顺序关系。 |
| stable_partition | 与partition类似,不过不保证保留容器中的相对顺序。 |
### 删除和替换算法(15个)
| 函数名 | 函数说明 |
| --------------- | ------------------------------------------------------------ |
| copy | 复制序列 |
| copy_backward | 与copy相同,不过元素是以相反顺序被拷贝。 |
| iter_swap | 交换两个ForwardIterator的值。 |
| remove | 删除指定范围内所有等于指定元素的元素。注意,该函数不是真正删除函数。内置函数不适合使用remove和remove_if函数。 |
| remove_copy | 将所有不匹配元素复制到一个制定容器,返回OutputIterator指向被拷贝的末元素的下一个位置。 |
| remove_if | 删除指定范围内输入操作结果为true的所有元素。 |
| remove_copy_if | 将所有不匹配元素拷贝到一个指定容器。 |
| replace | 将指定范围内所有等于vold的元素都用vnew代替。 |
| replace_copy | 与replace类似,不过将结果写入另一个容器。 |
| replace_if | 将指定范围内所有操作结果为true的元素用新值代替。 |
| replace_copy_if | 与replace_if,不过将结果写入另一个容器。 |
| swap | 交换存储在两个对象中的值。 |
| swap_range | 将指定范围内的元素与另一个序列元素值进行交换。 |
| unique | 清除序列中重复元素,和remove类似,它也不能真正删除元素。重载版本使用自定义比较操作。 |
| unique_copy | 与unique类似,不过把结果输出到另一个容器。 |
### 排列组合算法(2个):提供计算给定集合按一定顺序的所有可能排列组合
| 函数名 | 函数说明 |
| ---------------- | ------------------------------------------------------------ |
| next_permutation | 取出当前范围内的排列,并重新排序为下一个排列。重载版本使用自定义的比较操作。 |
| prev_permutation | 取出指定范围内的序列并将它重新排序为上一个序列。如果不存在上一个序列则返回false。重载版本使用自定义的比较操作。 |
### 算术算法(4个)
| 函数名 | 函数说明 |
| ------------------- | ------------------------------------------------------------ |
| accumulate | iterator对标识的序列段元素之和,加到一个由val指定的初始值上。重载版本不再做加法,而是传进来的二元操作符被应用到元素上。 |
| partial_sum | 创建一个新序列,其中每个元素值代表指定范围内该位置前所有元素之和。重载版本使用自定义操作代替加法。 |
| inner_product | 对两个序列做内积(对应元素相乘,再求和)并将内积加到一个输入的初始值上。重载版本使用用户定义的操作。 |
| adjacent_difference | 创建一个新序列,新序列中每个新值代表当前元素与上一个元素的差。重载版本用指定二元操作计算相邻元素的差。 |
### 生成和异变算法(6个)
| 函数名 | 函数说明 |
| ---------- | ------------------------------------------------------------ |
| fill | 将输入值赋给标志范围内的所有元素。 |
| fill_n | 将输入值赋给first到first+n范围内的所有元素。 |
| for_each | 用指定函数依次对指定范围内所有元素进行迭代访问,返回所指定的函数类型。该函数不得修改序列中的元素。 |
| generate | 连续调用输入的函数来填充指定的范围。 |
| generate_n | 与generate函数类似,填充从指定iterator开始的n个元素。 |
| transform | 将输入的操作作用与指定范围内的每个元素,并产生一个新的序列。重载版本将操作作用在一对元素上,另外一个元素来自输入的另外一个序列。结果输出到指定容器。 |
### 关系算法(8个)
| 函数名 | 函数说明 |
| ----------------------- | ------------------------------------------------------------ |
| equal | 如果两个序列在标志范围内元素都相等,返回true。重载版本使用输入的操作符代替默认的等于操作符。 |
| includes | 判断第一个指定范围内的所有元素是否都被第二个范围包含,使用底层元素的<操作符,成功返回true。重载版本使用用户输入的函数。 |
| lexicographical_compare | 比较两个序列。重载版本使用用户自定义比较操作。 |
| max | 返回两个元素中较大一个。重载版本使用自定义比较操作。 |
| max_element | 返回一个ForwardIterator,指出序列中最大的元素。重载版本使用自定义比较操作。 |
| min | 返回两个元素中较小一个。重载版本使用自定义比较操作。 |
| min_element | 返回一个ForwardIterator,指出序列中最小的元素。重载版本使用自定义比较操作。 |
| mismatch | 并行比较两个序列,指出第一个不匹配的位置,返回一对iterator,标志第一个不匹配元素位置。如果都匹配,返回每个容器的last。重载版本使用自定义的比较操作。 |
### 集合算法(4个)
| 函数名 | 函数说明 |
| ------------------------ | ------------------------------------------------------------ |
| set_union | 构造一个有序序列,包含两个序列中所有的不重复元素。重载版本使用自定义的比较操作。 |
| set_intersection | 构造一个有序序列,其中元素在两个序列中都存在。重载版本使用自定义的比较操作。 |
| set_difference | 构造一个有序序列,该序列仅保留第一个序列中存在的而第二个中不存在的元素。重载版本使用自定义的比较操作。 |
| set_symmetric_difference | 构造一个有序序列,该序列取两个序列的对称差集(并集-交集)。 |
### 堆算法(4个)
| 函数名 | 函数说明 |
| --------- | ------------------------------------------------------------ |
| make_heap | 把指定范围内的元素生成一个堆。重载版本使用自定义比较操作。 |
| pop_heap | 并不真正把最大元素从堆中弹出,而是重新排序堆。它把first和last-1交换,然后重新生成一个堆。可使用容器的back来访问被"弹出"的元素或者使用pop_back进行真正的删除。重载版本使用自定义的比较操作。 |
| push_heap | 假设first到last-1是一个有效堆,要被加入到堆的元素存放在位置last-1,重新生成堆。在指向该函数前,必须先把元素插入容器后。重载版本使用指定的比较操作。 |
| sort_heap | 对指定范围内的序列重新排序,它假设该序列是个有序堆。重载版本使用自定义比较操作。 |