一、STL 到底是个啥?

说实话,我刚开始学 C++ 那会儿,对 STL 是又爱又恨。爱的是它写起来真快,恨的是出了错根本不知道咋调。后来做项目久了,才慢慢摸清它的脾气。

STL,全称 Standard Template Library,标准模板库。它不是 C++ 语法的一部分,而是 C++ 标准委员会提供的一套通用库。说白了,就是一群大佬把最常用的数据结构和算法给你封装好了,你直接拿来用就行。

我个人习惯把 STL 比作一个「乐高工具箱」。里面有很多现成的积木块(容器)、拼装工具(算法)、连接件(迭代器),还有各种改装件(适配器)。你不需要自己造轮子,只需要选对积木,搭出你要的结构。

核心要点: STL 是 C++ 标准库的核心,它让程序员从重复造轮子中解放出来,专注于业务逻辑。

二、六大组件,一个都不能少

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:哈希表实现的键值对。我项目中缓存数据首选它。
我的建议: 如果你需要排序,用 map/set;如果你只关心查找速度,用 unordered_map/unordered_set。别纠结,实测一下就知道差距了。

四、迭代器:容器和算法的桥梁

迭代器,说白了就是容器的「通用指针」。它让算法不关心底层容器是啥,只要给我迭代器,我就能遍历。

我记得有一次,我需要把 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
避坑指南: 我曾经在 unordered_map 上用了 std::sort,结果编译报错。因为 unordered_map 的迭代器是双向迭代器,不支持随机访问。记住:算法对迭代器类型有要求,用之前先查文档。

五、算法:别自己造轮子

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:成员函数转函数对象。
小技巧: 我个人现在很少用函数适配器了,因为 lambda 更直观。但 bind 在某些泛型编程场景下还是很有用的。

七、知识体系总览

下面这张图是我自己总结的 STL 核心结构,你看一眼就能明白各个组件之间的关系:

STL 六大组件关系图 容器 迭代器 算法 适配器 仿函数 空间配置器 提供遍历接口 算法操作迭代器 包装容器接口 算法可接受仿函数 容器使用配置器分配内存 六大组件协同工作:容器存储数据,迭代器遍历,算法处理,适配器转换接口, 仿函数提供策略,空间配置器管理内存。

八、总结

STL 是 C++ 的精髓,也是很多面试官喜欢问的东西。我个人觉得,学 STL 最好的方式就是多用。你写代码时,先想想 STL 有没有现成的,别急着造轮子。

最后送大家一句话:STL 不是银弹,但用好了,能让你的代码质量提升一个档次。

避坑总结:
  • 迭代器失效问题:插入/删除元素后,迭代器可能失效,尤其是 vector 和 deque。
  • 算法复杂度:别以为 STL 算法都是 O(1),sort 是 O(n log n),find 是 O(n)。
  • 线程安全:STL 容器默认不是线程安全的,多线程访问需要加锁。
公众号:蓝海资料掘金营,微信deep3321