目录
前言
一、STL简介
二、STL的组件
三、STL头文件与命名空间
四、STL三大组件之 —— 容器
4.1 容器概述
4.2 序列式容器
4.3 排序式容器
4.4 哈希容器
五、STL三大组件之 —— 迭代器
5.1 迭代器概述
5.2 五种迭代器
5.3 迭代器的定义
5.4 迭代器的使用方法
六、STL三大组件之 —— 算法
6.1 查找算法
6.2 排序算法
6.3 删除/替换算法
6.4 累加 / 内积算法
6.5 赋值算法
6.6 关系运算算法
6.7 交 / 并 / 补 算法
致谢
本文近万字,不过每一句话都经过了我仔细斟酌,同时参阅了大量资料及底层代码。读完本文,你可以全面的掌握STL的设计思想、使用方法以及一些关键的底层实现。另外,本文可能并不适合 C++ 初学者,你可以当做字典使用。
STL(Standard Template Library)标准模板库,最早由惠普实验室开发,是一个基于泛型编程思想(模板)的程序库,内嵌于C++编译器中。它提供了几乎所有常见的数据结构类型及其算法,是对基础数据结构的补充与强化。它有两个特征:
特性1:数据结构与算法的分离。基于泛型编程思想,STL中算法并不依赖于数据结构进行实现。换句话说,STL中的一个算法可以操作几种不同的容器数据。例如,sort()可作用于动态数组、栈和队列,甚至是链表。实际上,这主要是通过迭代器实现的。
特性2:STL并非面向对象(OOP)。STL中的数据结构与算法皆不具有明显的继承关系,各部分之间相互独立。“采用非面向对象的方法在逻辑上降低了各部分之间的耦合关系,以提升各自的独立性、弹性、交互操作性”。这符合泛型编程设计原理。不过,STL也采纳了一些封装的思想,令使用者不必知晓其底层实现也能应用自如。
常用的STL手册:Containers - C++ Reference、STL教程:C++ STL快速入门
要设计一个复杂的系统,当然不是用一个 .cpp 就能完成的。工程师的方法当然是将其拆分成组件,从而分别进行设计,再将这些组件按照一定规则组合起来。STL在整体上就遵循这样的设计思路。STL由6个组件组成:容器、算法、迭代器、仿函数、容器适配器、空间配置器;
* 实际使用时,我们只要掌握前三个即可!
STL组件的交互关系:容器通过空间配置器取得数据存储空间,算法通过迭代器操作容器中的内容,仿函数可以协助算法,适配器可以修饰仿函数。如图: (了解即可)
STL被定义在命名空间 std 中,头文件如下:
头文件 | 作用 | 头文件 | 作用 |
---|---|---|---|
提供 vector向量 容器 | 为 迭代器 提供支持 | ||
提供 deque双端队列 容器 | 为 仿函数 提供支持 | ||
提供 list列表 容器 | 为大量 算法 提供支持 | ||
提供 queue队列 适配器 | 为部分 算法 提供支持 | ||
提供 stack栈 适配器 | 为 配置器 提供支持 | ||
提供 set集合 容器 | 重载关系运算符 定义pair类型 |
||
提供 map 映射容器 |
详细文档链接:STL 头文件一览表_ba_jie的博客-CSDN博客_stl头文件
要操作任何数据,首先要解决数据的存储问题。简单地说,容器就是一个能够容纳各种数据的“桶”。不过,容器中不仅储存数据,还用各种组织方式将数据组织起来,就形成了数据结构。容器就是一个高级的桶,它里面还存放了成员函数。
根据组织数据的方式不同,STL 提供的标准容器可分为 3 类:序列容器、排序容器、哈希容器。其中后两类容器也统称为关联容器。
哈希容器与排序式容器类似,但其底层依靠哈希表实现;它不会对键值对进行排序,元素的位置由哈希函数决定;
我们前面说到 “...分别进行设计,再将这些组件按照一定规则组合起来” ,那么,怎么将容器与算法组合起来?算法应该如何去操作容器这种复杂的组件?需要为每个容器都定制算法吗?
首先,算法要操作容器,需要有一个类似中介的装置。这个中介需要能阅读 (遍历) 容器内的数据,然后提供给算法使用。另外,它还要能对外隐藏容器的内部差异,从而以统一的界面向算法传送数据 (泛型)。在这样的思想下,迭代器应运而生。
其次,我们不需要为每个容器都定制算法。尽管不同容器的内部结构各异,但它们本质上都是用来存储大量数据的,换句话说,都是一串能存储多个数据的存储单元。因此,诸如数据的排序、查找、求和等操作方法应该在逻辑上是类似的。既然类似,完全可以利用泛型技术,将它们设计成适用所有容器的通用算法,而算法的具体化交给迭代器完成,这将大大降低我们的使用难度。
总的来说,你可以将迭代器看做适用于所有容器的强大指针,它给算法提供支持。实际上,指针就是一种迭代器。
* 以下内容不需要记忆,但要理解
STL标准库为每一种标准容器定义了一种迭代器类型,常见的有:前向迭代器、双向迭代器、随机访问迭代器、输入迭代器、输出迭代器。要注意的是,这五种迭代器都用同一语法定义:
容器类型::iterator it
而 it 具体是哪种迭代器,是由容器类型决定的;不同容器的对应的迭代器类型如下:
容器 | 对应的迭代器类型 |
---|---|
array | 随机访问迭代器 |
vector | 随机访问迭代器 |
deque | 随机访问迭代器 |
list | 双向迭代器 |
set / multiset | 双向迭代器 |
map / multimap | 双向迭代器 |
forward_list | 前向迭代器 |
unordered_map / unordered_multimap | 前向迭代器 |
unordered_set / unordered_multiset | 前向迭代器 |
stack | 不支持迭代器 |
queue | 不支持迭代器 |
* 详细文档:迭代器是什么,C++ STL迭代器(iterator)用法详解
ForwardIterator 支持 ++、* 操作,还可以被复制或赋值,可以用 == 和 != 运算符进行比较。此外,两个正向迭代器可以互相赋值;
BidirectionalIterator 在正向迭代器的基础上,添加了 -- 操作;
RandomAccessIterator 具有双向迭代器的全部功能,与指针极其相似。它支持以下操作:( i 为整数)
RandomAccessIterator += i |
迭代器往后移动 i 个元素 |
RandomAccessIterator -= i |
迭代器往前移动 i 个元素 |
RandomAccessIterator + i |
返回后面第 i 个元素的迭代器 |
RandomAccessIterator - i |
返回前面第 i 个元素的迭代器 |
RandomAccessIterator[i] |
返回后面第 i 个元素的引用 |
另外,RandomAccessIterator 还支持 <、>、<=、>= 比较运算符。表达式 p2-p1 也是有定义的,其返回值表示 p2 所指向元素和 p1 所指向元素的序号之差。
将输入流作为操作对象,了解即可
将输出流作为操作对象,了解即可
前面说到,五种迭代器都是用同一语法定义的,迭代器 it 的类型由容器类型决定。不过,定义迭代器的语法不止 iterator 这一个,还有:
定义 | 迭代器 |
---|---|
容器类型::iterator it | 正向迭代器 |
容器类型::const_iterator it | 常量正向迭代器 |
容器类型::reverse_iterator it | 反向迭代器 |
容器类型::const_reverse_iterator it | 常量反向迭代器 |
常量迭代器和非常量迭代器的分别在于:
反向迭代器和正向迭代器的区别在于:
另外,以上 4 种定义迭代器的方式,并不是每种容器都适用,我们最常用的还是正向迭代器。
一般来说,算法常用 first、last 迭代器作为参数,其作用请顾名思义。另外,你常见到的 a.begin()、a.end()、a.begin() + 1 甚至是 a + 1,它们通通都是迭代器。
如果你不幸需要用迭代器遍历容器,你完全可以将迭代器看做指针, *it 就表示迭代器所指向的元素,使用方法如下:
// 用 iterator 遍历数组
vector v = {1,2,3,4,5,6,7,8,9,10};
vector::iterator it;
for (it = v.begin(); it != v.end(); ++it)
cout << *i << " ";
// 用 reverse_iterator 反向遍历数组
std::reverse_iterator::iterator> rbegin = values.rbegin();
std::reverse_iterator::iterator> rend = values.rend();
while (rbegin != rend) {
cout << *rbegin << " ";
++rbegin; // 反向迭代器 ++ 向前移动
}
* 这里的 v.begin() 以及 v.end() 是 vector
* 请不要用 it < v.end() 来代替 it != v.end() ,虽然这是正确的;
* 反向迭代器的语法特殊,详见:STL 反向迭代器适配器(reverse_iterator)详解
STL中的算法提供了一些对容器的常用的操作方法,这些算法被内嵌到C++编译器中,1其体积小、速度快,且空间和时间开销都基本优化到了极限。若你熟悉STL算法,你会发现LeetCode上乱七糟八的题,只需要几行代码就可以搞定!
下面给出一些常见的算法的功能及其用法,都是我精选出来的,你可以通读一遍并留有大概的印象,需要用时再来查阅说明文档。另外,上文已经说过,STL算法对容器的操作多借助于迭代器进行,若你还不了解迭代器,请先阅读 五、STL三大组件之 —— 迭代器 。
算法 | 功能 / 用法 | 说明文档 |
---|---|---|
find() | 在指定区域内查找目标元素 | C++ find() |
it find (first, last, const T& val); | ||
find_if() | 与 find() 类似,但它允许自定义查找规则 | C++ find_if() |
it find_if (first, last, UnaryPredicate pred); | ||
search() | 查找序列 B 在序列 A 中第一次出现的位置 | C++ search() |
it search (first1, last1, first2, last2); | ||
adjacent_find() | 在指定范围内查找 2 个连续相等的元素 | C++ adjacent_find() |
it adjacent_find (first, last); | ||
lower_bound() | (二分查找) 在指定区域内查找不小于目标值的第一个元素 | C++ lower_bound() *二分查找仅适用于有序序列! |
it lower_bound (first, last, const T& val); | ||
upper_bound() | (二分查找) 在指定区域内查找大于目标值的第一个元素 | C++ upper_bound() *二分查找仅适用于有序序列 |
it upper_bound (first, last, const T& val); | ||
binary_search() |
(二分查找) 查找指定区域内是否包含某个目标元素 | C++ binary_search() *二分查找仅适用于有序序列! |
bool binary_search (first, last, const T& val); | ||
equel_range() | (二分查找) 查找指定区域内所有目标元素 | C++ equel_range() *二分查找仅适用于有序序列! |
pair |
算法 | 功能 / 用法 | 说明 |
---|---|---|
sort() | 将区域内的元素升序排序(一种改进过后的快排方法) | C++ sort() |
void sort (first, last); | ||
partial_sort() | 将 [first, last) 范围内最小的 middle-first 个元素移动到 [first, middle) 区域中,并对这部分元素做升序排序 | C++ partial_sort() * 类似的还有: partial_sort_copy() |
void partial_sort (first, middle, last); | ||
reverse() | 将指定范围内的元素反序 | C++ reverse_copy() |
void reverse(first, last); | ||
partition() | 将序列按自定义规则分为两组,返回其分界位置 | C++ partition() * 类似的还有: stable_partition() |
it partition (first, last, UnaryPredicate pred); | ||
merge() | 将 2 个有序序列合并为 1 个有序序列,返回的是新序列的末尾 | merge()、inplace_merge() * 类似的还有: inplace_merge() |
it merge (first1, last1, first2, last2, it result); | ||
next_permutation() * 排列组合 |
判断下一字典序是否“存在” | next_permutation算法详解* * 你还可以用它来输出字典序! |
bool next_permutation(first, last); | ||
prev_permutation() * 排列组合 |
判断上一字典序是否“存在” | prev_permutation算法详解* 你还可以用它来反序输出字典序!prev_permutation算法详解 |
bool prev_permutation(first, last); |
算法 | 功能 / 用法 | 说明 |
---|---|---|
copy() | 复制元素到新容器中 | c++ copy函数 |
copy(start, end, container_start); | ||
copy_if() | 将满足条件的元素复制到新容器中 (谓词函数) | copy_if算法详解 * 这是copy的升级版 |
copy(start, end, container_start, UnaryPredicate pred); | ||
swap() | 交换两个同种容器内的数据 | swap函数 |
swap(container1, container2); | ||
swap_ranges() | 交换两个等长的序列 | swap_ranges函数详解 |
it swap_ranges(first, last, first1); | ||
iter_swap() | 交换两个同种类的迭代器 | iter_swap函数详解 |
void iter_swap(first, second); | ||
remove() | "移除"与迭代器flag值相同的元素 | remoe函数详解 * 注意,事实上只是将其后的元素前移,并未真正移除元素!! |
it remove(first, last, flag); | ||
remove_if() | "移除"满足条件的元素 | remove_if函数详解 * 注意,事实上只是将其后的元素前移,并未真正移除元素!! |
it remove(first, last, UnaryPredicate pred); | ||
replace() | 将与目标值T1相等的元素替换为T2 | replace函数详解 |
void replace(first, last, T1, T2); | ||
replace_if() | 将与满足条件的元素替换为T | replace_if函数详解 |
void replace_if(first, last, UnaryPredicate pred, T); | ||
unique() | “去除”相邻重复元素 | unique函数 * 注意,事实上只是将其后的元素前移,并未真正移除元素!! |
it unique(first, second); |
算法 | 功能 / 用法 | 说明 |
---|---|---|
accumulate() | 累加区间内的元素值 | accumulate |
T accumulate(first, last, 0); | ||
inner_product() | 将两个等长区间内的元素值做内积并累加 | inner_product |
T inner_product(first1, last1, first2, 0); |
算法 | 功能 / 用法 | 说明 |
---|---|---|
for_each() | 对每一个元素都执行操作 fuction 并返回 | for_each() * 此功能异常强大! |
T for_each(first, last, T fuction); | ||
fill() | 将输入值赋给标志范围内的所有元素 | fill |
void fill(first,last,T val); | ||
generate() | 通过函数生成一个值并赋给所有元素 | generate * 实质上是fill的升级版 |
void generate(first,last,T val); |
算法 | 功能 / 用法 | 说明 |
---|---|---|
equal() | 比较两个等长区间内的元素是否相等 | equal |
bool equal(first1, last, first2); | ||
includes() | 判断一个有序区间的元素是否被另一个有序区间包含 | includes |
bool includes(first1, last1, first2, last2); | ||
max() | 返回两个值中最大的一个 | max * min用法相同 |
T max(T val1, T val2); | ||
max_element() | 返回区间内最大的元素 | max_element * min_element用法相同 |
T max_element(first, last); |
算法 | 功能 / 用法 | 说明 |
---|---|---|
set_union | 对两个序列取“并集”,并存入新序列 | set_union * 必须是有序序列 |
set_union(first1, last1, first2, last2, new_begin); | ||
set_intersection | 对两个序列取“交集”,并存入新序列 | set_intersection * 必须是有序序列 |
set_intersection(first1, last1, first2, last2, new_begin); | ||
set_difference | 取只在第一个序列中存在的元素,并存入新序列 | set_difference * 必须是有序序列 |
set_difference(first1, last1, first2, last2, new_difference); |
在本文创作的过程中,征引了许多博主、站长的文章,他们为我提供了宝贵的帮助:@站长长严生、@HUST_Miao
同时,感谢 @Dr.Luosifen 对我的精神支持。
最后,感谢您的阅读!