STL简单引入:把数据结构做成模板

STL:把数据结构做成模板

确定性是唯一的庇护。今天聊 STL。

为什么需要 STL

回顾前面写过的内容:顺序表、单链表、双向链表、栈、队列、二叉树、堆、各种排序。每写一个,都要走同一套流程------定义结构、初始化、扩容、插入、删除、销毁。换成 double,或者换成自定义结构体,整套代码再抄一遍,只改类型名。

C 语言没有模板,只能靠 void* 加强制转换,类型安全全部丢失。C++ 给出了解法:模板(template) 让代码脱离具体类型,类与运算符重载 让自定义类型用起来像内置类型。两者结合,标准库就能提供一套"写一次、任意类型复用"的数据结构与算法。

这就是 STL(Standard Template Library,标准模板库)。

一句话:STL 是数据结构的标准化、泛型化实现。

💡 背景补充:手写过的顺序表对应 vector,链表对应 list,栈对应 stack,队列对应 queue,堆对应 priority_queue。STL 不是新知识,是已学知识的工业版本。

六大组件

STL 不是"一堆现成函数",而是一套分工明确的体系:

组件 职责 代表
容器(Containers) 存储数据 vector、list、deque、map、set
算法(Algorithms) 处理数据 sort、find、reverse、copy
迭代器(Iterators) 连接容器与算法的访问接口 begin()、end()
仿函数(Functors) 让对象像函数一样调用,常作算法的策略参数 less<int>、greater<int>
适配器(Adapters) 改造已有组件的接口 stack、queue、priority_queue、reverse_iterator
空间配置器(Allocators) 为容器管理内存 allocator<T>

关系可以压成一句:容器持有数据,迭代器提供访问方式,算法只通过迭代器工作。

text 复制代码
容器 ──提供──> 迭代器 ──喂给──> 算法
  ↑                                  ↑
配置器                          仿函数(策略)
适配器:包装容器 / 迭代器 / 仿函数

这条设计最关键的地方是:sort 不知道自己在排 vector 还是原生数组,它只认迭代器。所以 sort(v.begin(), v.end()) 和 sort(a, a + n) 是同一个算法。这就是泛型编程(Generic Programming)的核心------算法与数据结构解耦。

试想没有解耦的世界:给数组写一份快排,给链表写一份,给 deque 再写一份。同一套逻辑复制 N 遍,改一处 bug 要改 N 个地方。迭代器把"访问方式"抽出来,算法只写一次。

容器分类

按数据组织方式分:

类别 容器 底层结构 特点
序列式 vector 动态数组 随机访问 O(1),中间插入删除 O(n)
序列式 list 双向链表 已知位置插删 O(1),不支持随机访问
序列式 deque 分段连续空间 头尾插删 O(1),随机访问 O(1)
序列式 string 动态字符数组 专门管理字符序列
关联式 map / set / multimap / multiset 红黑树 有序,查找 O(log n)
无序关联式(C++11) unordered_map / unordered_set 哈希表 平均 O(1),无序
适配器 stack / queue / priority_queue 包装其他容器 只暴露受限接口

📖 参考:《数据结构知识库》第一节、第四节------顺序表与链表的复杂度对比、红黑树与堆的适用场景。

从这里能看出先学数据结构的意义:选容器本质是选复杂度 。知道 vector 中间插入要搬移元素,就不会在频繁中间插入的场景用它;知道 list 无法随机访问,就不会拿它做二分查找。

注意 stack / queue 的位置。它们不是独立的数据结构,而是容器适配器 ------底层可以是 deque 或 list,只是对外只暴露"栈顶进出"或"队头队尾"的接口。这和手写栈时"接口由需求决定"是同一个思路。

迭代器:泛化的指针

迭代器(Iterator)是指针的抽象。它至少支持 *it、++it、it != end 三种操作,用起来和一个指针没有区别。

标准把迭代器按能力分成五类:

类别 能力 典型容器
输入迭代器 只读、单向 istream_iterator
输出迭代器 只写、单向 ostream_iterator
前向迭代器 读写、单向 forward_list
双向迭代器 读写、可后退 list、map、set
随机访问迭代器 读写、可跳跃(it + n、it[n]) vector、deque、string

这个分类不是学术摆设,它直接决定算法能不能用。find 只要求输入迭代器;sort 需要随机访问迭代器,所以 list 用不了 std::sort,只能调用成员函数 list::sort。容器不支持某个算法,通常不是"忘了实现",而是迭代器能力不够。

C++11 之后,遍历通常写成范围 for,但底层仍然是 begin() / end() 这一对迭代器:

cpp 复制代码
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

int main()
{
    vector<int> v = { 5, 2, 8, 1, 9 };

    sort(v.begin(), v.end());          // 算法只依赖迭代器

    for (auto e : v)                   // 范围 for 的底层仍是迭代器
        cout << e << " ";
    cout << endl;

    auto it = find(v.begin(), v.end(), 8);
    if (it != v.end())
        cout << "found: " << *it << endl;

    return 0;
}

sort 和 find 都来自 <algorithm>,与 vector 所在的 <vector> 没有任何包含关系。容器和算法在头文件层面就是分开的,接口靠迭代器对齐。

版本的来龙去脉

STL 并非某一家公司的产品,它有多个实现版本,接口一致、内部不同:

版本 出处 特点
原始版本 Alexander Stepanov、Meng Lee(HP 实验室) STL 的起点,奠定了组件划分
P.J. 版本 P.J. Plauger(微软) 被 Visual C++ 采用,闭源,风格保守
RW 版本 Rouge Wave 被 C++ Builder 采用,闭源
SGI 版本 Silicon Graphics 开源,可读性最好,GCC 采用

读源码首选 SGI 版:结构清晰、注释完整,allocator、traits 这些概念都在这里成型。现代三大标准库实现(GCC 的 libstdc++、Clang 的 libc++、MSVC STL)都受它影响。

📖 参考:《Effective C++》条款 54------标准程序库由 STL、iostreams、国际化、数值处理、异常层次构成,STL 只是其中最核心的一块。

学习路径

三个阶段,顺序不能反:

  1. 会用 :记住常用容器的接口和复杂度,能在 vector / list / map 之间做出选择。
  2. 会读:知道迭代器失效规则、扩容策略、底层数据结构。
  3. 会写 :模拟实现 string、vector、list,把"它会自动管理内存"变成"我知道它怎么管理内存"。

跳过前两步直接啃源码,只会看到一堆模板和 traits。先用起来,再拆开看。工具先替你干活,然后才轮到理解它为什么能干这个活。

常见误区

  • 把 STL 当成函数集合 。它是组件协同的体系,sort 能用是因为有迭代器,不是因为 sort 认识每个容器。
  • 以为所有容器都能 sort 。list 的迭代器是双向的,std::sort 编译不过,必须用成员函数 list::sort。
  • 以为所有容器都能 [] 。只有 vector、deque、string 等提供随机访问。
  • 以为 list 插入一定快 。定位插入位置仍然是 O(n),而且节点分散、缓存不友好,小数据量下往往不如 vector。
  • 忽略迭代器失效 。vector 扩容后旧迭代器全部失效,继续使用就是未定义行为。

本节要点

  • STL 六大组件:容器、算法、迭代器、仿函数、适配器、空间配置器。
  • 算法不依赖容器,只依赖迭代器------这是泛型编程的核心。
  • 迭代器分五类,能力决定算法能否使用。
  • 选容器本质是选复杂度,数据结构知识直接决定选型。
  • 学习顺序:会用 → 懂失效与复杂度 → 模拟实现。

📖 参考:《Effective C++》第 54 条


相关推荐
fanruitian44 分钟前
python property
开发语言·python
HPtiger1 小时前
交换类排序详解:冒泡排序与快速排序
数据结构·算法·排序算法
曹牧1 小时前
C#里引用ActiveX控件
开发语言·c#
kyle~1 小时前
数据结构 --- 无锁队列(Lock-Free Queue)
linux·数据结构·c++·多线程·并发
汤姆宝库1 小时前
推理工程深拆:调度、量化与投机解码
android·开发语言·kotlin
艾莉丝努力练剑1 小时前
【AI大模型接入SDK】ChatServer 路由规则绑定
网络·c++·人工智能·学习·ai·架构
王老师青少年编程1 小时前
2025年6月GESP真题及题解(C++一级): 选择题和判断题(题解)
c++·真题·gesp·答案·csp·一级·信奥赛
Rabitebla1 小时前
【Linux】 权限篇(三):深入理解 umask 位运算与粘滞位的设计思想
java·linux·服务器·c++·笔记·学习
czq_26867194871 小时前
Python打卡第45天
开发语言·python