一、C++ STL 总览:六大组件如何协同工作
说实话,STL 这玩意儿我刚接触时也觉得头大。六个组件,名字还都挺唬人——容器、算法、迭代器、仿函数、适配器、空间配置器。但用久了你会发现,它们就像一套精密的乐高积木,每个零件都有自己该待的位置。
我个人习惯把 STL 比作一个「数据加工厂」:容器是仓库,算法是流水线,迭代器是传送带,仿函数是加工指令,适配器是转换接头,空间配置器则是后勤部门。嗯,这么一想是不是清晰多了?
1.1 容器:数据住哪儿?
容器就是存数据的地方。STL 提供了两大类:序列式容器和关联式容器。
- 序列式容器:vector、deque、list、forward_list、array。数据按插入顺序排列。
- 关联式容器:set、multiset、map、multimap。数据按关键字自动排序。
- 无序容器(C++11):unordered_set、unordered_map 等。基于哈希表,查找更快。
1.2 算法:数据怎么处理?
算法是 STL 的灵魂。排序、查找、复制、替换、删除……大概有 100 多个。它们不直接操作容器,而是通过迭代器来工作。
#include <algorithm>
#include <vector>
std::vector<int> v = {4, 2, 5, 1, 3};
std::sort(v.begin(), v.end()); // 排序
auto it = std::find(v.begin(), v.end(), 3); // 查找
你看,sort 和 find 根本不关心你传的是 vector 还是 deque,只要给迭代器就行。这就是「泛型」的力量。
1.3 迭代器:算法和容器的桥梁
迭代器就是指针的泛化版本。它让算法可以统一地访问不同容器。
STL 定义了五种迭代器类别:
| 类别 | 能力 | 示例容器 |
|---|---|---|
| 输入迭代器 | 只读,单向 | istream_iterator |
| 输出迭代器 | 只写,单向 | ostream_iterator |
| 前向迭代器 | 读写,单向 | forward_list |
| 双向迭代器 | 读写,双向 | list, set, map |
| 随机访问迭代器 | 读写,任意跳转 | vector, deque, array |
1.4 仿函数:让算法更灵活
仿函数就是重载了 operator() 的类对象。它可以像函数一样调用,但又能携带状态。
struct Greater {
bool operator()(int a, int b) const {
return a > b;
}
};
std::vector<int> v = {1, 5, 3, 2, 4};
std::sort(v.begin(), v.end(), Greater()); // 降序排序
说白了,仿函数就是给算法传递「自定义规则」的工具。STL 也预置了一些,比如 std::greater<T>、std::less<T>。
1.5 适配器:接口转换器
适配器的作用是「包装」一个组件,让它表现出另一种接口。常见的有:
- 容器适配器:stack、queue、priority_queue。底层默认用 deque 实现。
- 迭代器适配器:reverse_iterator、back_insert_iterator 等。
- 函数适配器:std::bind、std::not_fn(C++17 起)。
std::stack<int> s; // 底层默认是 deque
s.push(10);
s.push(20);
s.pop(); // 只能从栈顶操作
你想想看,stack 其实就是把 deque 的接口「阉割」了——只保留 push_back、pop_back、back。这就是适配器的精髓。
1.6 空间配置器:幕后英雄
空间配置器负责内存的分配和释放。STL 默认的 std::allocator 做了两件事:
- 内存分配:调用 ::operator new
- 对象构造:调用 placement new
为什么要分开?因为 C++ 允许你只分配内存而不构造对象,或者只析构对象而不释放内存。这在容器扩容时特别有用。
1.7 它们如何协同工作?
来看一个完整的例子:
#include <iostream>
#include <vector>
#include <algorithm>
#include <functional>
int main() {
std::vector<int> data = {3, 1, 4, 1, 5, 9, 2, 6};
// 仿函数 + 算法 + 迭代器
std::sort(data.begin(), data.end(), std::greater<int>());
// 适配器:反向迭代器
for (auto it = data.rbegin(); it != data.rend(); ++it) {
std::cout << *it << " ";
}
// 输出:1 1 2 3 4 5 6 9
}
你看,这一小段代码就用了容器(vector)、算法(sort)、迭代器(begin/end、rbegin/rend)、仿函数(greater)。空间配置器在背后默默分配内存,适配器让反向遍历变得简单。
为什么会这样设计?因为 STL 追求的是「分离」与「组合」。容器只管存,算法只管算,迭代器负责沟通。你不需要为每种容器都写一套排序函数——写一次 sort,所有容器都能用。
1.8 知识体系总览
下面这张图帮你理清六大组件的关系:
这张图我画了好几次才满意。你看,容器在最左边,算法在中间,迭代器在最右边——但箭头是双向的。实际上迭代器既服务于算法,也依赖于容器。仿函数和适配器像是「插件」,随时可以接入。空间配置器则默默支撑着整个体系。
好了,六大组件的基本概念就聊到这儿。记住一句话:STL 不是六个独立的东西,而是一套设计哲学——泛型编程、分离关注点、组合优于继承。理解了这一点,后面学具体组件时会轻松很多。
公众号:蓝海资料掘金营,微信 deep3321