一、STL 总体架构
STL是 C++ 标准库的核心组成部分。它不是单一的概念,而是由五个相互协作的组件组成的完整体系。这五个组件就像一个精密的钟表,每个部件都有自己的职责,协同工作。
想象一下这五个组件的关系:
容器是各种盒子(书架、衣柜、抽屉)
算法是操作方法(怎么放、怎么取、怎么整理)
迭代器是手或工具(连接你和盒子的桥梁)
仿函数是智能工具(带特殊功能的夹子、剪刀)
适配器是转换接口(把方孔变成圆孔的转接头)
二、五大组件详解
1. 容器(Containers)—— 数据的“盒子”
容器就是装数据的各种“容器”,就像现实中的数组、链表、树、哈希表等数据结构的模板实现。
1.1 容器分类(三层理解)
第一层:按存储方式分
序列容器:元素按线性顺序排列
vector:动态数组(像可以自动扩容的货架)deque:双端队列(像两头的传送带)list:双向链表(像一串珍珠项链)forward_list:单向链表(只能向前走的项链)array:固定数组(固定大小的储物格)
关联容器:元素按键值排序存储(像字典)
set:唯一键集合(不允许重复的集合)multiset:可重复键集合(允许重复的集合)map:键值对映射(像电话簿:名字→号码)multimap:可重复键的映射(同名可以有多个号码)
无序关联容器:哈希表实现(像乱放但能快速找到的抽屉)
unordered_set:无序唯一集合unordered_map:无序键值映射等等...
容器适配器:特殊用途的包装
stack:栈(后进先出,像叠盘子)queue:队列(先进先出,像排队)priority_queue:优先队列(按优先级出队,像医院急诊)
1.2 容器的核心特性
关键点1:模板
所有容器都是类模板,可以存储任何类型:
cpp
vector<int> // 存int vector<string> // 存string vector<vector<int>> // 存vector(二维数组)
关键点2:内存管理
值语义:容器存储的是对象的副本
RAII原则:自动管理内存,离开作用域自动释放
异常安全:保证基本异常安全
关键点3:接口统一
所有容器都提供类似的接口:
size():元素个数empty():是否为空begin()/end():迭代器clear():清空
2. 算法(Algorithms)—— 操作的“说明书”
算法是操作数据的通用方法,就像一本说明书,告诉你如何对数据进行排序、查找、复制、删除等操作。
2.1 算法特点
关键特点1:通用性
算法不关心具体容器类型,只通过迭代器操作数据:
// 同样的sort算法,可以对vector、deque、array等排序 sort(vector.begin(), vector.end()); sort(deque.begin(), deque.end());
关键特点2:函数模板
算法都是函数模板,可以处理各种数据类型:
sort(int_vector.begin(), int_vector.end()); // 排序int sort(string_vector.begin(), string_vector.end()); // 排序string
关键特点3:迭代器依赖
算法通过迭代器访问容器元素,不直接操作容器本身。
2.2 算法分类
常用算法类别:
排序算法
sort:快速排序stable_sort:稳定排序(相等元素保持原顺序)partial_sort:部分排序
查找算法
find:线性查找binary_search:二分查找(要求已排序)lower_bound/upper_bound:边界查找
修改算法
copy:复制fill:填充replace:替换remove:删除(实际是移动,需要配合erase)
数值算法
accumulate:累加inner_product:内积adjacent_difference:相邻差
集合算法
set_union:并集set_intersection:交集set_difference:差集
重要概念:算法复杂度
cpp
sort() // O(n log n) - 快排 find() // O(n) - 线性查找 binary_search() // O(log n) - 二分查找
3. 迭代器(Iterators)—— 访问的“指针”
迭代器是连接容器和算法的桥梁,它像是一个智能指针,知道如何在容器中移动并访问元素。
3.1 迭代器的作用
桥梁作用:
text
容器(数据存储) ←→ 迭代器(访问接口) ←→ 算法(操作逻辑)
关键能力:
遍历:逐个访问容器元素
访问:读写容器元素
连接:让算法可以操作不同容器
3.2 迭代器分类(五种)
理解层次:从简单到复杂
输入迭代器(Input Iterator)
只能读,不能写
只能向前移动(单次)
像一次性读取器
例子:从键盘读取数据
输出迭代器(Output Iterator)
只能写,不能读
只能向前移动
像打印机
例子:向屏幕输出
前向迭代器(Forward Iterator)
可以读写
只能向前移动
可以多次遍历
例子:单向链表
双向迭代器(Bidirectional Iterator)
可以向前向后移动
可以读写
例子:双向链表、红黑树
随机访问迭代器(Random Access Iterator)
可以任意跳跃访问
支持所有指针运算
例子:数组、vector、deque
3.3 迭代器失效问题
重要警告:某些操作会使迭代器失效!
vector的典型问题:
cpp
vector<int> v = {1, 2, 3}; auto it = v.begin() + 1; // 指向2 v.push_back(4); // 可能导致重新分配内存 // 此时it失效!不能再使用不同容器的失效规则:
vector:插入/删除可能导致所有迭代器失效
deque:中间插入/删除会使所有迭代器失效
list/set/map:只影响被删除元素的迭代器
4. 仿函数(Functors)—— 行为的“函数对象”
仿函数是行为像函数的对象,它实际上是一个重载了()运算符的类对象。
4.1 为什么需要仿函数?
传统函数指针的局限性:
无法携带状态
无法内联优化
类型不安全
仿函数的优势:
可以保存状态
编译器可以内联优化
是对象,有类型信息
4.2 仿函数的使用
简单示例:
cpp
// 定义一个仿函数类 struct AddValue { int value; AddValue(int v) : value(v) {} // 重载()运算符 int operator()(int x) const { return x + value; } }; // 使用 AddValue add5(5); int result = add5(10); // 调用add5.operator()(10),返回154.3 STL中的预定义仿函数
算术运算:
plus<T>:加法minus<T>:减法multiplies<T>:乘法divides<T>:除法modulus<T>:取模negate<T>:取负
比较运算:
equal_to<T>:等于not_equal_to<T>:不等于greater<T>:大于less<T>:小于greater_equal<T>:大于等于less_equal<T>:小于等于
逻辑运算:
logical_and<T>:与logical_or<T>:或logical_not<T>:非
使用示例:
cpp
// 使用greater进行降序排序 sort(v.begin(), v.end(), greater<int>());
4.4 现代替代品:Lambda表达式
C++11引入lambda表达式,很多情况下替代仿函数:
// 传统仿函数 struct Compare { bool operator()(int a, int b) { return a > b; } }; sort(v.begin(), v.end(), Compare()); // Lambda表达式(更简洁) sort(v.begin(), v.end(), [](int a, int b) { return a > b; });5. 适配器(Adapters)—— 接口的“转换器”
适配器是接口转换器,它修改现有组件的接口,使其适应新的需求。
5.1 三种适配器
1. 容器适配器
基于现有容器提供新接口
三种主要类型:
stack:基于deque/vector/list,提供栈接口queue:基于deque/list,提供队列接口priority_queue:基于vector,提供优先队列接口
示例:stack的实现原理
cpp
// stack内部包含一个deque template<typename T, typename Container = deque<T>> class stack { private: Container c; // 底层容器 public: void push(const T& value) { c.push_back(value); } void pop() { c.pop_back(); } T& top() { return c.back(); } // ... 其他操作 };2. 迭代器适配器
修改迭代器的行为
主要类型:
reverse_iterator:反向迭代器insert_iterator:插入迭代器stream_iterator:流迭代器
示例:反向迭代器
cpp
vector<int> v = {1, 2, 3}; // 反向遍历 for (auto rit = v.rbegin(); rit != v.rend(); ++rit) { cout << *rit << " "; // 输出:3 2 1 }3. 函数适配器
修改函数对象的行为
在C++11后,很多被bind/lambda替代
主要类型:
binder1st/binder2nd:绑定参数(C++11前)not1/not2:逻辑取反
示例:bind适配器(C++11)
cpp
// 绑定函数的第二个参数 auto add = [](int a, int b) { return a + b; }; auto add5 = std::bind(add, std::placeholders::_1, 5); cout << add5(10); // 输出15三、五大组件如何协同工作
工作流程示例:排序一个vector
cpp
// 1. 容器:创建数据存储 vector<int> numbers = {5, 2, 8, 1, 9}; // 2. 迭代器:提供访问接口 auto begin = numbers.begin(); // 获取起始位置 auto end = numbers.end(); // 获取结束位置 // 3. 算法:执行排序操作 // 内部使用迭代器访问元素 sort(begin, end); // 可选:使用仿函数改变排序行为 sort(begin, end, greater<int>()); // 降序排序 // 4. 适配器:改变访问方式 // 反向输出 copy(numbers.rbegin(), numbers.rend(), ostream_iterator<int>(cout, " "));整个过程的数据流:
text
数据 → 存入容器 → 迭代器访问 → 算法处理 → 结果输出
组件间的松耦合设计
关键设计思想:
容器和算法分离:算法通过迭代器操作容器,不依赖具体容器类型
迭代器作为粘合剂:统一了不同容器的访问方式
仿函数提供策略:允许自定义操作行为
适配器扩展功能:不改动原有代码,增加新功能
五、总结:五大组件的关系比喻
完整比喻:图书馆系统
容器= 书架、储物柜(各种存储设施)
vector:一排可以扩展的书架
list:用链条连接的书格
map:按书名排序的目录柜
set:不允许重复的珍本书架
算法= 图书管理方法
sort:按某种规则整理书籍
find:查找特定书籍
copy:复印书籍
remove:下架旧书
迭代器= 图书管理员的手/扫码枪
可以指向特定位置
可以移动到相邻位置
可以读取书籍信息
连接书架(容器)和管理方法(算法)
仿函数= 智能工具/规则
按作者排序的规则
按出版日期筛选的条件
自动贴标签的机器
适配器= 转换接口
把普通书架变成只能从顶部取书的"栈式书架"
把书架变成只能从一端取书的"队列式书架"
反向阅读器(从后向前读)
整个系统工作流程:
管理员(算法)使用扫码枪(迭代器)按照特定规则(仿函数)在书架(容器)上操作,通过特殊设备(适配器)满足特殊需求。