前言
在前面学习 C++ 模板时,我们知道模板可以帮助我们编写与类型无关的通用代码。
例如,我们可以通过模板实现一个通用的交换函数,让它既能交换 int,也能交换 double、char 等不同类型的数据。
而我们接下来要学习的 STL,就是模板和泛型编程非常经典的一种应用。
在平时编程时,我们经常需要使用数组、栈、队列、链表等数据结构,也经常需要对数据进行排序、查找和统计。
如果这些功能每次都由我们自己实现,不仅代码量比较大,而且还容易出现错误。
为了解决这个问题,C++ 为我们提供了 STL。
STL 就像是 C++ 为程序员准备好的一个大型工具箱,里面包含了许多常见的数据结构和算法。
一、什么是 STL
STL 的全称是:
text
Standard Template Library
中文叫做:
text
标准模板库
STL 是 C++ 标准库中非常重要的一部分,它为我们提供了大量可以直接使用的数据结构和算法。
例如:
cpp
vector<int> nums; // 动态数组
stack<int> st; // 栈
queue<int> que; // 队列
sort(nums.begin(), nums.end()); // 排序
如果不使用 STL,我们可能需要自己实现动态数组、栈、队列、排序算法等功能。
使用 STL 后,就可以直接使用已经实现好的工具,从而减少重复代码,提高开发效率。
简单来说:
STL 帮助我们避免重复"造轮子"。
二、STL 的发展历史
STL 最早由 Alexander Stepanov 等人设计。
它的核心思想是:
编写与具体数据类型无关的通用数据结构和算法。
在 STL 出现之前,数据结构和算法之间通常联系得比较紧密。
例如,如果我们分别使用数组、链表和其他结构保存数据,可能就需要分别编写不同版本的排序和查找算法。
而 STL 希望将数据结构和算法分开,再通过一种统一的方式将它们连接起来。
在 STL 的发展过程中,出现过几个比较有代表性的版本。
1. HP 版本
HP 版本由 Alexander Stepanov、Meng Lee 等人在惠普实验室完成。
它是最早期、最重要的 STL 实现之一,后来许多 STL 版本都是在它的基础上发展而来的,因此通常被看作其他 STL 实现的基础。
2. P. J. 版本
P. J. 版本由 P. J. Plauger 开发,同样继承自 HP 版本。
这个版本曾经被早期的 Visual C++ 使用,不过它的代码命名方式比较特别,可读性相对一般。
3. RW 版本
RW 版本由 Rogue Wave 公司开发,也是在 HP 版本的基础上发展而来的。
这个版本曾经被一些 C++ 编译器使用。
4. SGI 版本
SGI 版本由 Silicon Graphics 公司开发。
这个版本具有较好的可读性和可移植性,对后来的 GCC 标准库实现产生了较大的影响。
很多介绍 STL 底层原理和源码的书籍,也会参考 SGI STL。
后来,STL 被纳入 C++ 标准库,逐渐成为现代 C++ 编程中不可缺少的一部分。
三、STL 的六大组件
STL 主要由六个部分组成:
| 组件 | 简单理解 |
|---|---|
| 容器 | 用来保存和组织数据 |
| 算法 | 用来处理数据 |
| 迭代器 | 用来访问容器中的数据 |
| 函数对象 | 用来规定算法的处理规则 |
| 适配器 | 对已有结构的接口进行转换 |
| 空间配置器 | 负责底层的内存管理 |
初学 STL 时,最重要的是下面三个部分:
text
容器、算法、迭代器
它们之间的关系可以简单理解为:
容器负责保存数据,算法负责处理数据,迭代器负责连接容器和算法。
例如:
cpp
vector<int> nums = {5, 3, 1, 4, 2};
sort(nums.begin(), nums.end());
在这段代码中:
vector是容器,用来保存一组整数;sort是算法,用来完成排序;begin()和end()返回迭代器,用来表示需要排序的数据范围。
四、STL 中常见的容器
容器就是用来保存数据的工具。
STL 中常见的容器包括:
| 容器 | 简单理解 |
|---|---|
vector |
可以自动扩容的动态数组 |
list |
双向链表 |
deque |
双端队列 |
map |
保存键和值的对应关系 |
set |
保存不重复的数据 |
stack |
后进先出的栈 |
queue |
先进先出的队列 |
priority_queue |
每次优先取出最大或最小元素 |
这些容器各有自己的特点和适用场景,后面会分别进行更加系统的学习。
现阶段只需要对它们有一个简单印象即可。
五、STL 中常见的算法
STL 除了提供容器,还提供了许多已经实现好的算法。
常见算法包括:
| 算法 | 作用 |
|---|---|
sort |
排序 |
find |
查找元素 |
reverse |
反转一段数据 |
count |
统计元素出现次数 |
max_element |
查找最大元素 |
min_element |
查找最小元素 |
这些算法大部分定义在头文件:
cpp
#include <algorithm>
使用 STL 算法后,我们就不需要每次都自己编写排序和查找函数。
六、STL 的简单经典应用
下面通过几个简单例子,初步感受一下 STL 的使用方法。
1. 使用 vector 保存数据并排序
vector 可以简单理解为一个能够自动改变长度的数组。
cpp
#include <algorithm>
#include <iostream>
#include <vector>
int main()
{
std::vector<int> nums = {5, 2, 8, 1, 3};
std::sort(nums.begin(), nums.end());
for (int num : nums)
{
std::cout << num << " ";
}
return 0;
}
运行结果:
text
1 2 3 5 8
这段代码使用了三个 STL 中的重要内容:
text
vector:保存数据
sort:完成排序
迭代器:表示排序范围
如果不使用 sort,我们可能需要自己实现冒泡排序、选择排序或者快速排序。
而使用 STL 后,只需要一行代码就能完成排序:
cpp
std::sort(nums.begin(), nums.end());
2. 使用 set 对数据进行去重
set 是一种不允许保存重复元素的容器。
当我们向 set 中插入重复元素时,它只会保留一份。
cpp
#include <iostream>
#include <set>
int main()
{
std::set<int> numbers;
numbers.insert(3);
numbers.insert(1);
numbers.insert(2);
numbers.insert(3);
numbers.insert(2);
for (int num : numbers)
{
std::cout << num << " ";
}
return 0;
}
运行结果:
text
1 2 3
虽然我们插入了两次 2 和两次 3,但是最终每个数字只保存了一份。
因此,set 经常用于:
text
数据去重
判断某个数据是否出现过
保存不重复的数据集合
另外,普通的 set 还会按照一定的顺序保存元素,因此输出结果是有序的。
3. 使用 map 统计数字出现次数
map 用来保存键和值之间的对应关系。
例如:
text
数字 -> 出现次数
姓名 -> 成绩
单词 -> 单词数量
下面使用 map 统计每个数字出现了多少次。
cpp
#include <iostream>
#include <map>
#include <vector>
int main()
{
std::vector<int> nums = {1, 2, 1, 3, 2, 1};
std::map<int, int> countMap;
for (int num : nums)
{
countMap[num]++;
}
for (const auto& item : countMap)
{
std::cout << item.first
<< " 出现了 "
<< item.second
<< " 次"
<< std::endl;
}
return 0;
}
运行结果:
text
1 出现了 3 次
2 出现了 2 次
3 出现了 1 次
其中:
cpp
countMap[num]++;
表示将数字 num 对应的出现次数加一。
map 在统计数据、保存对应关系等问题中非常常见。
4. 使用 stack 和 queue
stack 表示栈,它的特点是:
后进先出。
例如,将数字 1、2、3 依次放入栈中,取出顺序就是:
text
3、2、1
栈经常用于:
text
括号匹配
函数调用
撤销操作
深度优先搜索
queue 表示队列,它的特点是:
先进先出。
例如,将数字 1、2、3 依次放入队列中,取出顺序就是:
text
1、2、3
队列经常用于:
text
排队系统
任务处理
二叉树层序遍历
广度优先搜索
例如,二叉树的层序遍历需要按照从上到下、从左到右的顺序访问节点,这种处理方式就非常适合使用 queue。
七、为什么要学习 STL
STL 在 C++ 的学习和实际开发中都非常重要。
1. 在算法题中
很多算法题都会使用 STL。
例如:
text
使用 vector 保存数据
使用 sort 完成排序
使用 map 统计次数
使用 set 完成去重
使用 stack 判断括号是否匹配
使用 queue 完成层序遍历
使用 priority_queue 解决 Top-K 问题
合理使用 STL,可以明显减少代码量。
2. 在面试中
C++ 面试中经常会考查 STL。
除了基本使用方法,还可能会问:
text
vector 和普通数组有什么区别
map 和 unordered_map 有什么区别
stack 和 queue 分别有什么特点
vector 为什么能够自动扩容
不同容器适合什么场景
这些问题会在后面学习各个容器时逐渐接触。
3. 在实际开发中
在实际开发中,我们通常不会每次都重新实现栈、队列、链表和排序算法。
直接使用 STL 可以:
text
减少重复代码
提高开发效率
降低出错概率
提高代码的可读性
方便后期维护
八、如何学习 STL
STL 的学习可以分为三个阶段:
text
能用、明理、能扩展
1. 能用
第一阶段是学会使用 STL。
例如,能够正确使用:
text
vector
stack
queue
map
set
sort
find
面对一个问题时,能够选择合适的容器和算法。
这是初学 STL 时最重要的目标。
2. 明理
第二阶段是不仅会使用,还需要理解它们的底层原理。
例如:
text
vector 为什么能够扩容
map 的底层是如何查找数据的
不同操作的时间复杂度是多少
什么情况下迭代器会失效
3. 能扩展
第三阶段是能够根据自己的需求扩展 STL。
例如:
text
编写自定义比较规则
编写自定义函数对象
让自定义类型支持排序
阅读部分 STL 源码
编写泛型程序
不过这些都是后续需要学习的内容。
现阶段,我们只需要知道 STL 是什么,并对它提供的常见工具有一个基本认识。
九、总结
STL 是 C++ 标准库中非常重要的一部分,它为我们提供了大量已经实现好的数据结构和算法。
STL 的六大组件分别是:
text
容器
算法
迭代器
函数对象
适配器
空间配置器
其中,初学阶段最重要的是:
text
容器、算法和迭代器
目前可以先记住下面几个常见工具:
text
vector:动态数组
stack:后进先出的栈
queue:先进先出的队列
map:保存键值对应关系
set:保存不重复的数据
sort:完成排序
find:查找元素
STL 中的内容非常多,不可能通过一篇文章全部掌握。
这篇文章主要是对 STL 做一个简单的整体介绍,后面再分别深入学习各个容器、算法以及它们的底层原理。
学习 STL 不需要一开始就研究复杂的源码,可以先学会使用,再逐渐理解原理,最后尝试进行扩展。
完