一、STL 到底是个啥?
说实话,我刚开始学 C++ 那会儿,对 STL 是又爱又恨。爱的是它写起来真快,恨的是出了错根本不知道咋调。后来做项目久了,才慢慢摸清它的脾气。
STL,全称 Standard Template Library,标准模板库。它不是 C++ 语法的一部分,而是 C++ 标准委员会提供的一套通用库。说白了,就是一群大佬把最常用的数据结构和算法给你封装好了,你直接拿来用就行。
我个人习惯把 STL 比作一个「乐高工具箱」。里面有很多现成的积木块(容器)、拼装工具(算法)、连接件(迭代器),还有各种改装件(适配器)。你不需要自己造轮子,只需要选对积木,搭出你要的结构。
二、六大组件,一个都不能少
STL 有六大组件,我当年背了好久才记住。其实不用死记,你只要理解它们各自干啥的,自然就记住了。
| 组件 | 作用 | 我的一句话理解 |
|---|---|---|
| 容器(Container) | 管理数据集合 | 装东西的盒子 |
| 迭代器(Iterator) | 遍历容器中的元素 | 盒子的指针 |
| 算法(Algorithm) | 对数据进行操作 | 怎么处理盒子里的东西 |
| 适配器(Adapter) | 为容器/迭代器/函数提供新接口 | 给盒子加个转换头 |
| 仿函数(Functor) | 行为像函数的对象 | 可以当函数用的对象 |
| 空间配置器(Allocator) | 管理内存分配 | 管盒子怎么造出来的 |
嗯,这里要注意:仿函数和空间配置器平时用得少,但它们是 STL 能高效运行的基石。尤其是空间配置器,我曾在一次内存泄漏排查中,发现是自定义分配器没写好导致的。从那以后,我再也不敢小看它了。
三、容器分类:选对容器,事半功倍
容器是 STL 里最常用的组件。我见过不少新手,不管啥场景都用 vector,结果性能惨不忍睹。你想想看,如果你要频繁在头部插入数据,用 vector 就是灾难,deque 或 list 才是正解。
3.1 序列式容器
- vector:动态数组,随机访问快,尾部插入快,头部/中间插入慢。我项目中 80% 的场景都用它。
- deque:双端队列,头尾插入都快,随机访问也还行。适合做任务队列。
- list:双向链表,任意位置插入/删除都快,但随机访问慢。适合频繁增删的场景。
- forward_list:单向链表,比 list 更省内存,但只能单向遍历。
- array:固定大小数组,比原生数组安全,但大小编译期确定。
3.2 关联式容器
- set / multiset:自动排序,元素唯一(set)或可重复(multiset)。底层红黑树。
- map / multimap:键值对,按键自动排序。我经常用它做字典或配置表。
3.3 无序关联式容器(C++11 引入)
- unordered_set / unordered_multiset:哈希表实现,查找 O(1),但不排序。
- unordered_map / unordered_multimap:哈希表实现的键值对。我项目中缓存数据首选它。
四、迭代器:容器和算法的桥梁
迭代器,说白了就是容器的「通用指针」。它让算法不关心底层容器是啥,只要给我迭代器,我就能遍历。
我记得有一次,我需要把 vector 里的数据拷贝到 list 里。如果用传统方式,得写循环。但用 STL 算法 + 迭代器,一行搞定:
std::vector<int> vec = {1, 2, 3, 4, 5};
std::list<int> lst;
std::copy(vec.begin(), vec.end(), std::back_inserter(lst));
你看,back_inserter 就是一个适配器迭代器,它自动调用 push_back。这就是迭代器的威力——它让算法和容器解耦。
迭代器分类(按能力)
| 类型 | 能力 | 示例容器 |
|---|---|---|
| 输入迭代器 | 只读,单向移动 | istream_iterator |
| 输出迭代器 | 只写,单向移动 | ostream_iterator |
| 前向迭代器 | 读写,单向移动 | forward_list |
| 双向迭代器 | 读写,双向移动 | list, set, map |
| 随机访问迭代器 | 读写,任意跳转 | vector, deque, array |
五、算法:别自己造轮子
STL 提供了上百种算法,覆盖了排序、查找、修改、数值计算等。我见过很多同事,明明有现成的 std::find 不用,非要自己写 for 循环。何必呢?
常用的算法分类:
- 查找:find, find_if, binary_search, lower_bound
- 排序:sort, stable_sort, partial_sort
- 修改:copy, replace, transform, fill
- 数值:accumulate, inner_product, partial_sum
- 集合:set_union, set_intersection, set_difference
举个例子,你想把一个 vector 里所有偶数乘以 2:
std::vector<int> v = {1, 2, 3, 4, 5, 6};
std::transform(v.begin(), v.end(), v.begin(),
[](int x) { return x % 2 == 0 ? x * 2 : x; });
一行 lambda + transform,搞定。比手写循环清晰多了。
六、适配器:给现有组件换个马甲
适配器,说白了就是「包装」。它不改变底层实现,只改变接口。STL 有三种适配器:
6.1 容器适配器
- stack:后进先出,底层默认用 deque。
- queue:先进先出,底层默认用 deque。
- priority_queue:优先级队列,底层默认用 vector + heap 算法。
我项目中用 priority_queue 做任务调度,非常方便。只需要自定义比较函数,就能控制任务优先级。
6.2 迭代器适配器
- reverse_iterator:反向遍历。
- back_insert_iterator / front_insert_iterator / insert_iterator:自动调用插入操作。
- istream_iterator / ostream_iterator:把流当容器用。
6.3 函数适配器(C++11 后多用 lambda 替代)
- bind:绑定参数。
- not1 / not2:取反。
- mem_fn:成员函数转函数对象。
七、知识体系总览
下面这张图是我自己总结的 STL 核心结构,你看一眼就能明白各个组件之间的关系:
八、总结
STL 是 C++ 的精髓,也是很多面试官喜欢问的东西。我个人觉得,学 STL 最好的方式就是多用。你写代码时,先想想 STL 有没有现成的,别急着造轮子。
最后送大家一句话:STL 不是银弹,但用好了,能让你的代码质量提升一个档次。
- 迭代器失效问题:插入/删除元素后,迭代器可能失效,尤其是 vector 和 deque。
- 算法复杂度:别以为 STL 算法都是 O(1),sort 是 O(n log n),find 是 O(n)。
- 线程安全:STL 容器默认不是线程安全的,多线程访问需要加锁。