"STL 是 C++ 标准模板库,包含容器、算法、迭代器、函数对象、适配器、分配器六大组件。它采用泛型编程思想,通过迭代器将算法与容器解耦,提供高效、通用的数据结构和算法实现,是 C++ 编程的基础工具集。"
1. STL 的六大组件
STL 包含六大核心组件,用这个记忆法:AC2FIM
// 六大组件关系
STL = Algorithms (算法) + Containers (容器) + Iterators (迭代器)
+ Function objects (函数对象) + Adapters (适配器) + Allocators (分配器)
这个缩写对应 STL 最核心的六个部分:
| 字母 | 对应组件 (英文) | 中文含义 | 核心作用 |
|---|---|---|---|
| A | Algorithms | 算法 | 提供 sort、find等通用算法 |
| C | Containers | 容器 | 提供 vector、map等数据结构 |
| 2 | Iterators | 迭代器 | 充当容器与算法之间的"胶水" |
| F | Functors | 函数对象/仿函数 | 让对象像函数一样被调用 |
| I | Adapters | 适配器 | 接口转换(如 stack由 deque适配而来) |
| M | Allocators | 分配器 | 管理内存的分配与释放 |
注 :其中的 2 是取 It erators 的谐音("I" 和 "t" 连读类似 "two"),I 是取 Adapters 中的 'd' 音变而来。这是一种常见的记忆技巧。
2. 容器(Containers)
容器是存储数据的模板类,分为三类:
① 序列容器(Sequence Containers)
cpp
#include <vector> // 动态数组
#include <deque> // 双端队列
#include <list> // 双向链表
#include <forward_list> // 单向链表(C++11)
#include <array> // 固定大小数组(C++11)
// 示例
std::vector<int> vec = {1, 2, 3}; // 随机访问,尾部插入高效
std::list<int> lst = {1, 2, 3}; // 任意位置插入高效
std::array<int, 3> arr = {1, 2, 3}; // 编译时固定大小
"插入高效" 指在特定位置插入元素的时间复杂度低。
② 关联容器(Associative Containers)
#include <set> // 集合(值唯一,自动排序)
#include <map> // 映射(键值对,键唯一,自动排序)
#include <multiset> // 多重集合(值可重复)
#include <multimap> // 多重映射(键可重复)
// 示例
std::set<int> s = {3, 1, 4, 1, 5}; // {1, 3, 4, 5}
std::map<std::string, int> m = {{"Alice", 25}, {"Bob", 30}};
"关联" 指的是元素之间通过键(key)建立逻辑关系
③ 无序容器(Unordered Containers,C++11)
#include <unordered_set> // 哈希集合
#include <unordered_map> // 哈希映射
#include <unordered_multiset> // 哈希多重集合
#include <unordered_multimap> // 哈希多重映射
// 示例
std::unordered_map<std::string, int> um = {{"Alice", 25}, {"Bob", 30}};
// 基于哈希,访问O(1),但无序
哈希是一种将任意长度数据映射为固定长度值的计算过程 ,核心思想是通过哈希函数 建立键 → 桶索引的快速映射,实现近似O(1)的查找速度。
桶是哈希表中的基本存储单元 ,可以看作数组的一个槽位 ,每个桶存放哈希值相同的元素(解决冲突时可能存放多个)。哈希值不直接匹配,而是通过取模运算映射到桶索引 。具体过程是:哈希值 → 桶索引 → 桶内线性比较。
// 把哈希表想象成一个有很多格子的储物柜
哈希表 = 储物柜
├── 桶0 = 1号柜子 [ ]
├── 桶1 = 2号柜子 [("Alice", 25), ("Bob", 30)] ← 这个柜子有2个物品
├── 桶2 = 3号柜子 [ ]
├── 桶3 = 4号柜子 [("Charlie", 35)]
└── ...
// 哈希函数决定物品放哪个柜子
// 冲突:不同物品哈希到同一柜子 → 都放进去
cpp
// 查找键 "Alice" 的过程
std::unordered_map<std::string, int> um = {{"Alice", 25}, {"Bob", 30}};
// 1. 计算哈希值
size_t hash_value = std::hash<std::string>{}("Alice");
// 假设得到: 0x5A3F8C1B (十进制: 1513473051)
// 2. 映射到桶索引
size_t bucket_index = hash_value % um.bucket_count();
// 假设有8个桶: 1513473051 % 8 = 3
// 所以应该去桶3找
// 3. 在桶3内线性比较
// 遍历桶3的链表,用 operator== 逐个比较键
// 找到 "Alice" == "Alice",返回对应的值25
3. 算法(Algorithms)
<algorithm>头文件包含 100+ 个通用算法,不依赖于具体容器:
#include <algorithm> // 最重要的头文件
std::vector<int> v = {3, 1, 4, 1, 5, 9, 2, 6};
// 常用算法
std::sort(v.begin(), v.end()); // 排序
auto it = std::find(v.begin(), v.end(), 5); // 查找
int count = std::count(v.begin(), v.end(), 1); // 计数
std::reverse(v.begin(), v.end()); // 反转
std::rotate(v.begin(), v.begin() + 3, v.end()); // 旋转
bool sorted = std::is_sorted(v.begin(), v.end()); // 检查是否有序
// 数值算法
#include <numeric>
int sum = std::accumulate(v.begin(), v.end(), 0); // 求和
算法分类:
-
不修改序列:
find,count,search,equal -
修改序列:
copy,transform,replace,fill -
排序相关:
sort,stable_sort,nth_element,partition -
数值运算:
accumulate,inner_product,partial_sum
4. 迭代器(Iterators)
迭代器是连接容器和算法的桥梁,提供统一的访问接口,用于遍历和访问容器中的元素
!算法通过迭代器操作容器
迭代器是:智能下标
从用法上看:迭代器完全可以看作访问容器元素的下标,作用一模一样;
从本质上看:它不是整数下标,而是指向元素的指针对象;
从能力上看:它是所有容器通用的下标,数组下标做不到这一点
// 五种迭代器类别
1. 输入迭代器 (Input Iterator) : 只读,单次遍历
2. 输出迭代器 (Output Iterator) : 只写,单次遍历
3. 前向迭代器 (Forward Iterator) : 可读写,可重复遍历
4. 双向迭代器 (Bidirectional Iterator): 可向前向后移动
5. 随机访问迭代器 (Random Access Iterator): 支持随机访问
// 示例
std::vector<int> vec = {1, 2, 3, 4, 5};
for (auto it = vec.begin(); it != vec.end(); ++it) {
std::cout << *it << " "; // 通过迭代器访问
}
// 等价于范围for
for (int val : vec) {
std::cout << val << " ";
}
5. 函数对象(Function Objects)和 Lambda
任何重载了 operator()的 struct/class 对象,都能像函数一样被调用,因此称为函数对象(仿函数)
operator()是函数调用运算符,是 C++ 允许类/结构体重载的特殊运算符,让对象能像函数一样被调用。
定义
cpp
返回类型 operator()(参数列表) [const] [异常说明] [-> 尾置返回类型] {
函数体
}
和成员函数的使用相对比,成员函数要加个点,调用
cpp
class Calculator {
public:
// 普通成员函数
int add(int a, int b) { return a + b; }
// 函数调用运算符
int operator()(int a, int b) { return a + b; }
};
Calculator calc;
// 使用普通成员函数
int r1 = calc.add(3, 4); // calc.add(3, 4)
// 使用 operator()
int r2 = calc(3, 4); // calc.operator()(3, 4)
// 语法更简洁,像调用函数
cpp
#include <functional>
// 1. 函数对象(仿函数)
struct Add {
int operator()(int a, int b) const { return a + b; }
};
Add add;
int result = add(3, 4); // 7
---------------------------------------------------------------------------
cpp
#include <functional> // 需要这个头文件
// STL 预定义的函数对象模板
std::plus<int> p; // 加法:p(a, b) 返回 a + b
std::less<int> l; // 小于比较:l(a, b) 返回 a < b
std::logical_and<bool> la; // 逻辑与:la(a, b) 返回 a && b
"预定义" 指的是 C++ 标准库已经提前定义好的模板类,我们可以直接使用,不需要自己实现。
函数适配器已弃用。!=适配器
6. 适配器(Adapters)
适配器修改现有组件的接口:
-
容器适配器:适配底层容器的接口
-
迭代器适配器:适配迭代器的行为
-
函数适配器:适配函数的调用方式
接口
(Interface)是类/对象对外提供的操作方法的集合 ,定义了如何使用 这个对象,但不暴露如何实现。
生活中的接口:
// 比如电视机遥控器:
遥控器接口 = {开关(), 调音量(), 换台(), 静音()}
// 你只需要知道按哪个按钮,不需要知道电视内部电路
编程中的接口
// 比如 vector 的接口:
vector接口 = {
push_back(), // 添加元素
pop_back(), // 移除元素
at(), // 访问元素
size(), // 获取大小
empty(), // 是否为空
// ... 其他操作
}
// 你只需要调用这些方法,不需要知道 vector 内部如何存储
7. 分配器(Allocators)
管理内存分配,通常使用默认分配器:
template<class T>
class MyAllocator {
// 自定义内存分配策略
};
std::vector<int, MyAllocator<int>> v; // 使用自定义分配器
8. STL 的设计哲学
泛型编程是一种编程范式
STL 是 C++ 中对泛型编程思想的具体实现
Template 是泛型编程的实现工具
泛型编程是一种编程思想
思想:编写不依赖于具体类型的代码
目标:提高代码复用性、类型安全性
原则:算法与数据结构解耦,"解耦" 指算法不直接依赖具体数据结构,而是通过统一接口(迭代器)间接访问数据。
cpp
// 定义泛型查找算法
template<typename InputIt, typename T> // 模板:InputIt=迭代器类型,T=值类型
InputIt my_find(InputIt first, InputIt last, const T& value) {
// 遍历范围 [first, last)
for (; first != last; ++first) { // 迭代器前进:++first
if (*first == value) // 解引用迭代器:*first
return first; // 找到,返回迭代器
}
return last; // 没找到,返回 last
}
// 同一算法适用于不同容器
template<typename Container>
void print_container(const Container& c) {
for (const auto& item : c) {
std::cout << item << " ";
}
}
std::vector<int> v = {1, 2, 3};
std::list<int> l = {4, 5, 6};
print_container(v); // 同一函数处理不同容器
print_container(l);
迭代器解耦
// 算法不依赖具体容器
template<typename InputIt, typename T>
InputIt my_find(InputIt first, InputIt last, const T& value) {
for (; first != last; ++first) {
if (*first == value) return first;
}
return last;
}
// 可用于vector、list、array等
std::vector<int> vec = {1, 2, 3};
std::list<int> lst = {1, 2, 3};
auto it1 = my_find(vec.begin(), vec.end(), 2);
auto it2 = my_find(lst.begin(), lst.end(), 2);
9. 现代 C++ 中的 STL 发展
C++11 增强
cpp
// 1. 移动语义
std::vector<std::string> v;
v.push_back(std::string("Hello")); // 移动而非拷贝
// 2. 初始化列表
std::vector<int> v = {1, 2, 3, 4, 5}; // 列表初始化
// 3. auto 类型推导
for (auto it = v.begin(); it != v.end(); ++it) {}
// 4. 基于范围的for循环
for (int x : v) { std::cout << x; }
-
push_back通过移动而非拷贝将其放入 vector,避免不必要的深拷贝
-
栈上创建
temp对象 -
push_back在堆上分配新内存 -
复制
"Hello"的内容 -
复制后,有两个
"Hello"字符串 -
函数结束,
temp被销毁
10. 常用 STL 组件总结
| 类别 | 常用组件 | 描述 |
|---|---|---|
| 容器 | vector, list, deque, array |
序列容器 |
set, map, multiset, multimap |
关联容器 | |
unordered_set, unordered_map |
无序容器 | |
| 适配器 | stack, queue, priority_queue |
容器适配器 |
| 算法 | sort, find, transform, copy |
通用算法 |
| 迭代器 | begin(), end(), rbegin(), rend() |
访问元素 |
| 函数对象 | plus, less, equal_to |
预定义仿函数 |
| 智能指针 | unique_ptr, shared_ptr, weak_ptr |
内存管理 |
11. STL常见问答
Q1:vector 和 list 的区别?
A:
vector: 连续内存,随机访问O(1),中间插入O(n)
list: 不连续内存,随机访问O(n),中间插入O(1)
选择:需要快速访问用vector,需要频繁插入用list
Q2:map 和 unordered_map 的区别?
A:
map: 红黑树实现,有序,O(log n)访问
unordered_map: 哈希表实现,无序,平均O(1)访问
选择:需要有序遍历用map,需要快速查找用unordered_map
Q3:迭代器失效问题?
A:
vector: 插入/删除可能使所有迭代器失效
list: 插入不会失效,删除只使被删元素迭代器失效
map: 插入不会失效,删除只使被删元素迭代器失效
Q4:STL 算法的时间复杂度?
A:
sort: O(n log n)
find: O(n) // 线性查找
binary_search: O(log n) // 有序序列
count: O(n)