平时保存一组整数或字符串时,我们经常会用到 std::vector。结合上一章刚学的类模板知识来看,同一个 vector 模板,只要在尖括号 <> 里填入不同的数据类型,就能生成存放对应数据的动态数组。
除了 vector 之外,C++ 标准库还提供了自动去重的集合、按键值对查找的映射表,以及排序、查找、反转等一系列实用函数。这些工具背后遵循着同一套设计思路,也就是这一章我们要学习的 STL。先把这张整体地图看明白,后面学习具体的容器和算法时就会顺畅很多。
C++ STL 是什么?
STL 是英文 “Standard Template Library” 的缩写,中文通常翻译为 “标准模板库”。在日常 C++ 开发中,我们常说的 STL,指的就是标准库中基于模板实现的一套通用数据管理与处理工具。
初学的小伙伴需要注意一点,不要把 STL 和 “整个 C++ 标准库” 完全画等号。C++ 标准库的范围更大,除了 STL 之外,还包含输入输出 iostream、智能指针、文件读写、日期时间等功能。简单来说,STL 是 C++ 标准库中专门负责 “装数据” 和 “处理数据” 的那一部分。
此外,STL 本身就是 C++ 标准库自带的组成部分。只要电脑上已经配置好了 C++ 编译环境,我们就不需要像第三方库那样额外下载或安装。平时想使用哪个容器或算法,只需要在代码开头通过 #include 引入对应的头文件就可以了。
C++ STL 的三个组成部分
一整套 STL 主要由 “容器”、“迭代器” 和 “算法” 这 3 个部分组成,它们之间的配合关系如下图所示。

1. 容器(Container)
容器,说白了就是 “用来装数据的工具”。前面学过的 std::vector 是最常用的一种容器,它可以把一组相同类型的数据按顺序存放在一起。
不同容器组织数据的方式并不相同,适合解决的问题也各不一样。比如 vector 适合按先后顺序存放一排数据,后面要学习的 set 适合自动过滤重复元素,而 map 和 unordered_map 则适合按 “键 → 值” 的对应关系快速查找数据。
因此,我们在学习 STL 容器时不需要死记硬背每一个成员函数名,重点是先搞清楚两件事:① 这个容器是怎么组织数据的;② 它适合用在什么场景下。
2. 迭代器(Iterator)
你可以先把迭代器简单理解成 “指向容器中某个元素位置的定位器”,用法跟前面学过的指针很像。
可能有些小伙伴会问:“为什么有了容器,还需要专门设计迭代器呢?” 这是因为不同容器的内部结构差别很大,有的在内存里连续排列,有的像链条一样分散连接。而标准库算法不需要关心容器底层是怎么存数据的,只需要借助迭代器知道 “从哪个位置开始处理、到哪个位置结束” 就够了。
在代码中,我们最常看到的是容器的 begin() 和 end() 这两个成员函数:
- begin():返回指向容器中第一个元素的迭代器,也就是起始位置。
- end():返回指向容器中最后一个元素 “再往后一步” 的空位,也就是结束边界。它本身并不指向有效元素,只是用来提醒算法处理到这里之前就停下。
把 begin() 和 end() 配对传给算法,表示处理从 begin() 开始、直到 end() 之前为止的元素。这种包含起点、不包含终点的范围常被称为 “左闭右开” 区间,刚好覆盖整个容器里的全部数据。关于迭代器的具体操作,下一章还会展开介绍,这里先认识它的定位作用即可。
3. 算法(Algorithm)
STL 在 <algorithm> 等头文件中提供了大量现成的通用算法,其中最常用的就是排序函数 std::sort(),此外还有查找函数 std::find()、反转函数 std::reverse() 等。这些算法大多不是某个容器专属的成员函数,而是独立定义的函数模板,通过迭代器来操作容器里的数据。
这样设计的好处很直观:容器只管 “数据怎么存”,算法只管 “数据怎么处理”,两边通过迭代器连接起来。标准库只需要编写一套通用算法,就能配合多种不同的容器一起使用。
不过我们也要留意一点,并不是所有算法都能随便套在任意容器上。比如 std::sort() 要求容器支持按位置快速跳转,因此可以直接用于 vector 和 array,但不能直接用于双向链表 list。这些具体规则,后面学到对应容器时我们会逐一说明。
提示 我们可以用一句话记住 STL 三个角色的分工:容器负责装数据,迭代器负责指位置,算法负责处理数据。
容器、迭代器与算法如何配合?
光看概念容易觉得抽象,下面我们通过两个简单的例子,直观看看 “容器、迭代器、算法” 这三者在实际代码中是怎么配合工作的。
示例 1:对 vector 元素排序
#include <algorithm>
#include <iostream>
#include <vector>
int main() {
std::vector<int> nums = {5, 2, 4, 1, 3};
std::sort(nums.begin(), nums.end()); // 升序排列
for (int n : nums) {
std::cout << n << ' ';
}
std::cout << '\n';
return 0;
}运行结果如下。
1 2 3 4 5分析:
在这个例子中,我们先创建了一个保存 5 个整数的 vector 容器 nums,随后调用 <algorithm> 头文件中的 std::sort() 函数,并把起始位置 nums.begin() 和结束边界 nums.end() 传进去。
有了这套通用工具,我们就不需要手写双层循环去比较和交换元素,std::sort() 会自动把容器里的数据按从小到大的顺序排好。哪怕容器里装的数据类型变了,或者换用其他通用算法,这套 “容器 + 迭代器 + 算法” 的配合写法也完全一致。下面我们再来看一个例子。
示例 2:字符串排序与首尾反转
#include <algorithm>
#include <iostream>
#include <string>
#include <vector>
int main() {
std::vector<std::string> langs = {"Python", "C++", "Go"};
std::sort(langs.begin(), langs.end()); // 按字母升序
for (const std::string& lang : langs) {
std::cout << lang << ' ';
}
std::cout << '\n';
std::reverse(langs.begin(), langs.end()); // 首尾反转
for (const std::string& lang : langs) {
std::cout << lang << ' ';
}
std::cout << '\n';
return 0;
}运行结果如下。
C++ Go Python
Python Go C++分析:
上面代码中,容器内部的元素换成了 std::string 字符串,调用 std::sort() 后会自动按照首字母从 A 到 Z 的顺序排成 C++ Go Python。紧接着执行的 std::reverse() 则将容器中的元素原地首尾反转,同样只需要传入 begin() 和 end() 起止迭代器即可。
另外在范围 for 循环中,我们将循环变量写成常量引用 const std::string& lang。这样可以直接借用容器中的原字符串,避免每次循环都额外拷贝一份字符串。
C++ STL 常见容器分类
STL 提供的容器种类不少,如果孤立地去记每一个容器,很容易越学越乱。在 C++ 中,我们可以按 “数据在容器里怎么组织” 把常见容器分为 4 大类,如下表所示。
| 容器分类 | 常见容器 | 特点与适用场景 |
|---|---|---|
| 序列容器 | vector、array、deque、list | 元素按先后顺序排列,适合按位置保存、访问或依序遍历数据 |
| 有序关联容器 | set、map | 按键的比较规则自动排序且不重复,适合有序查找与自动去重 |
| 无序关联容器 | unordered_set、unordered_map | 基于哈希表组织数据,不维护固定顺序,适合快速查找与去重 |
| 容器适配器 | stack、queue、priority_queue | 封装底层容器并限制进出位置,表达后进先出、先进先出或优先级规则 |
看到上面这么多容器名字,小伙伴们完全不用有记忆压力。在实际开发中,如果只是 “按顺序动态保存一组数据”,前面学过的 std::vector 依然是使用频率最高的首选工具。
学习其他 STL 容器,并不是为了把代码里的 vector 全部换掉,而是在遇到特定问题时有更合适的工具可用。比如需要自动过滤重复数据时,我们可以选用 std::set。而需要按 “键 → 值” 对应关系快速查数据时,选用 std::map 会比自己手写查找逻辑省事得多。
常见问题
1. STL 里的容器这么多,初学者需要一次性全部背熟吗?
完全不需要。平时写代码时,大多数按顺序存数据的需求直接使用 std::vector 就能解决。对于后面介绍的其他容器,大家只需要先记住每一种容器擅长解决什么问题,比如去重使用 set、按键查值使用 map、后进先出使用 stack。等实际开发遇到对应场景时,再回过头来查阅具体函数的写法就可以了。
2. 所有 STL 容器都支持使用 [] 下标访问元素吗?
并不是。只有 vector、array、deque 这类按位置排列的序列容器支持使用数字下标 [i] 访问元素,另外 map 和 unordered_map 支持使用键名 [key] 访问对应的值。而像链表 list、集合 set 以及 stack、queue 等容器,都不支持按数字下标直接跳到第几个元素。
