C++ STL标准模板库详解
简介
STL(Standard Template Library,标准模板库)是C++标准库的重要组成部分,它提供了一套通用的、可复用的组件,包括容器、迭代器、算法、函数对象、适配器和空间配置器。本文重点讲解最常用的三大组件------容器、迭代器和算法,并通过丰富的代码示例帮助开发者掌握STL的核心用法。
一、STL 总览
1.1 六大组件
| 组件 | 说明 | 头文件示例 |
|---|---|---|
| 容器(Container) | 存储和组织数据的数据结构 | <vector>, <list>, <map> |
| 迭代器(Iterator) | 连接容器和算法的桥梁 | 各容器头文件 |
| 算法(Algorithm) | 操作容器内容的模板函数 | <algorithm> |
| 仿函数(Functor) | 重载了 operator() 的类 | <functional> |
| 适配器(Adapter) | 修改容器或函数接口的包装器 | <stack>, <queue> |
| 空间配置器(Allocator) | 负责内存分配与释放 | <memory> |
1.2 容器分类
STL 容器
├── 序列容器(Sequential)
│ ├── vector 向量(动态数组)
│ ├── deque 双端队列
│ └── list 双向链表
├── 关联容器(Associative)
│ ├── set 集合(唯一键)
│ ├── multiset 多重集合
│ ├── map 映射(键值对)
│ └── multimap 多重映射
└── 容器适配器(Adapter)
├── stack 栈(LIFO)
├── queue 队列(FIFO)
└── priority_queue 优先队列
图片占位符:STL容器分类结构图
二、vector(向量)
2.1 简介
vector 是一种动态数组,能自动管理内存,支持随机访问。在尾部插入和删除元素的时间复杂度为 O(1),在中间或头部操作为 O(n)。
cpp
#include <vector>
using namespace std;
2.2 构造方式
cpp
vector<int> vec1; // 默认构造,空向量
vector<int> vec2(10); // 10 个元素,值为 0
vector<int> vec3(10, 5); // 10 个元素,值为 5
vector<int> vec4(vec3); // 拷贝构造
vector<int> vec5(vec3.begin(), vec3.end()); // 区间构造
int arr[] = {1, 2, 3, 4, 5};
vector<int> vec6(arr, arr + 5); // 从数组构造
2.3 元素访问
cpp
vector<int> vec = {10, 20, 30, 40, 50};
vec[0]; // 下标访问(不检查越界)
vec.at(2); // at 访问(越界抛出异常)
vec.front(); // 第一个元素
vec.back(); // 最后一个元素
vec.data(); // 返回底层数组指针
2.4 添加与删除
cpp
vector<int> vec;
// 添加元素
vec.push_back(10); // 尾部添加
vec.emplace_back(20); // 尾部原地构造(C++11,更高效)
vec.insert(vec.begin(), 5); // 在指定位置插入
vec.insert(vec.begin() + 2, 3, 100); // 插入 3 个 100
// 删除元素
vec.pop_back(); // 删除末尾元素
vec.erase(vec.begin()); // 删除指定位置
vec.erase(vec.begin(), vec.begin()+2);// 删除区间
vec.clear(); // 清空
// 重新赋值
vec.assign(7, 100); // 7 个 100
vec.assign(vec2.begin(), vec2.end()); // 用另一个容器赋值
2.5 容量与大小
cpp
vector<int> vec = {1, 2, 3};
vec.size(); // 元素个数:3
vec.empty(); // 是否为空:false
vec.capacity(); // 当前容量(>= size)
vec.reserve(100); // 预分配容量(避免多次重新分配)
vec.resize(10); // 改变大小(多出的元素默认初始化)
vec.shrink_to_fit(); // 释放多余容量(C++11)
2.6 完整使用示例
cpp
#include <iostream>
#include <vector>
#include <cstring>
using namespace std;
int main() {
int arr[] = {1, 2, 3, 4, 5};
const char *str = "Hello STL";
vector<int> vec_i(arr, arr + 5);
vector<char> vec_c(str, str + strlen(str));
// 使用迭代器遍历
for (vector<int>::iterator it = vec_i.begin(); it != vec_i.end(); ++it) {
cout << *it << " ";
}
cout << endl;
// 输出:1 2 3 4 5
// 使用下标遍历
for (size_t i = 0; i < vec_c.size(); i++) {
cout << vec_c[i];
}
cout << endl;
// 输出:Hello STL
return 0;
}
2.7 vector 的注意事项
- 不支持前插 :
vector没有提供push_front,因为在头部插入需要移动所有元素 - 迭代器失效:插入或删除元素可能导致迭代器失效,需要重新获取
- reserve 优化 :如果知道大致元素数量,提前
reserve可以避免多次内存重分配
三、deque(双端队列)
3.1 简介
deque(Double-Ended Queue)与 vector 类似,支持随机访问,但额外支持在头部高效插入和删除。
cpp
#include <deque>
using namespace std;
3.2 与 vector 的区别
| 特性 | vector | deque |
|---|---|---|
| 头部插入 | 不支持 | 支持 push_front |
| 内存分布 | 连续内存 | 分段连续 |
| capacity/reserve | 支持 | 不支持 |
| 随机访问 | O(1) | O(1) |
3.3 使用示例
cpp
#include <iostream>
#include <deque>
using namespace std;
void print_deque(deque<int> que, const char *name) {
cout << "========= " << name << endl;
for (auto it = que.begin(); it != que.end(); ++it) {
cout << *it << " ";
}
cout << endl;
}
int main() {
deque<int> que(5, 0); // 5 个 0
print_deque(que, "initQue");
// 输出:0 0 0 0 0
que.push_back(12);
que.push_back(13);
print_deque(que, "push_back");
// 输出:0 0 0 0 0 12 13
que.push_front(1);
que.push_front(2);
print_deque(que, "push_front");
// 输出:2 1 0 0 0 0 0 12 13
que.pop_back();
que.pop_back();
print_deque(que, "pop_back");
// 输出:2 1 0 0 0 0 0
que.pop_front();
que.pop_front();
print_deque(que, "pop_front");
// 输出:0 0 0 0 0
return 0;
}
四、list(双向链表)
4.1 简介
list 是由节点组成的双向链表,不支持随机访问(没有 operator[]),但在任意位置插入和删除元素的时间复杂度都是 O(1)。
cpp
#include <list>
using namespace std;
4.2 与 vector/deque 的区别
| 特性 | vector | deque | list |
|---|---|---|---|
| 随机访问 | O(1) | O(1) | 不支持 |
| 头部插入 | 不支持 | O(1) | O(1) |
| 中间插入/删除 | O(n) | O(n) | O(1) |
| 内存开销 | 低 | 中 | 高(每个节点额外指针) |
4.3 list 特有操作
cpp
list<int> lst1 = {123, 0, 34, 1123};
list<int> lst2 = {12, 100};
lst1.sort(); // 排序
lst2.sort(); // 排序
// lst1: 0, 34, 123, 1123
// lst2: 12, 100
lst1.merge(lst2); // 合并两个有序链表
// lst1: 0, 12, 34, 100, 123, 1123
// lst2: 空(元素被转移到 lst1)
lst1.reverse(); // 反转
lst1.unique(); // 去除连续重复元素
lst1.splice(it, lst2); // 将 lst2 的元素拼接到 lst1 的指定位置
4.4 使用示例
cpp
#include <iostream>
#include <list>
using namespace std;
void PrintIt(list<int> n) {
for (list<int>::iterator iter = n.begin(); iter != n.end(); ++iter)
cout << *iter << " ";
}
int main() {
list<int> listn1, listn2;
listn1.push_back(123);
listn1.push_back(0);
listn1.push_back(34);
listn1.push_back(1123);
listn2.push_back(100);
listn2.push_back(12);
listn1.sort();
listn2.sort();
PrintIt(listn1); // 0 34 123 1123
cout << endl;
PrintIt(listn2); // 12 100
listn1.merge(listn2);
cout << endl;
PrintIt(listn1); // 0 12 34 100 123 1123
return 0;
}
五、set / multiset(集合)
5.1 set 简介
set 是有序的唯一元素集合,底层实现为红黑树。元素既充当数据又充当关键字,最大的特点是元素唯一 且自动排序。
cpp
#include <set>
using namespace std;
5.2 基本用法
cpp
set<int, less<int>> myset;
myset.insert(30);
myset.insert(10);
myset.insert(20);
myset.insert(10); // 插入失败,元素已存在
// 遍历(自动排序)
for (auto it = myset.begin(); it != myset.end(); ++it) {
cout << *it << " "; // 输出:10 20 30
}
// 查找
auto it = myset.find(20);
if (it != myset.end()) {
cout << "找到:" << *it << endl;
}
// 删除
myset.erase(10);
myset.erase(myset.begin());
// 大小
myset.size();
myset.empty();
// count:存在返回 1,不存在返回 0
myset.count(20);
5.3 multiset
multiset 与 set 的区别在于允许重复元素:
cpp
multiset<int, less<int>> myset;
myset.insert(10);
myset.insert(10); // 成功,允许重复
myset.count(10); // 返回 2
六、map / multimap(映射)
6.1 map 简介
map 存储键值对(key-value),通过唯一的 key 快速查找 value。底层实现为红黑树,key 自动排序。与 set 不同,map 的 key 和 value 是分开的。
cpp
#include <map>
using namespace std;
6.2 基本用法
cpp
map<char, int, less<char>> mymap;
// 插入
mymap.insert(pair<char, int>('a', 100));
mymap.insert(map<char, int>::value_type('b', 200));
mymap['c'] = 300; // 使用下标操作符
// 遍历
for (auto it = mymap.begin(); it != mymap.end(); ++it) {
cout << it->first << " => " << it->second << endl;
}
// a => 100
// b => 200
// c => 300
// 查找
auto it = mymap.find('b');
if (it != mymap.end()) {
cout << "找到:" << it->second << endl;
}
// 下标访问
mymap['a']; // 返回 100
mymap['d']; // 如果不存在,创建并初始化为 0
6.3 map 与 set 的关键区别
| 特性 | set | map |
|---|---|---|
| 元素 | 数据即关键字 | key-value 分离 |
| 下标运算 | 不支持 | 支持 [] |
| 访问方式 | *it |
it->first, it->second |
6.4 multimap
multimap 允许 key 重复,不支持 [] 运算符:
cpp
multimap<int, string> mmap;
mmap.insert(pair<int, string>(1, "hello"));
mmap.insert(pair<int, string>(1, "world")); // 允许重复 key
mmap.insert(pair<int, string>(2, "foo"));
// 查找某个 key 的所有值
auto range = mmap.equal_range(1);
for (auto it = range.first; it != range.second; ++it) {
cout << it->second << endl;
}
6.5 注意事项
set和map的 key 只能存在一个,insert重复值会失败multiset和multimap允许重复 key- 关联容器的
find()成员函数比泛型find()算法效率更高(O(log n) vs O(n))
七、容器适配器
7.1 stack(栈)
后进先出(LIFO):
cpp
#include <stack>
stack<int> stk;
stk.push(10);
stk.push(20);
stk.push(30);
stk.top(); // 30
stk.pop(); // 移除 30
stk.size(); // 2
stk.empty(); // false
7.2 queue(队列)
先进先出(FIFO):
cpp
#include <queue>
queue<int> q;
q.push(10);
q.push(20);
q.push(30);
q.front(); // 10
q.back(); // 30
q.pop(); // 移除 10
q.size(); // 2
7.3 priority_queue(优先队列)
元素按优先级自动排序(默认大顶堆):
cpp
#include <queue>
priority_queue<int> pq;
pq.push(30);
pq.push(10);
pq.push(20);
pq.top(); // 30(最大值)
pq.pop(); // 移除 30
// 自定义小顶堆
priority_queue<int, vector<int>, greater<int>> min_pq;
八、迭代器
8.1 迭代器的作用
迭代器是连接容器和算法的桥梁,类似于指向容器元素的指针。可以递增迭代器使其依次指向容器中的每个元素。
8.2 迭代器类型
| 类型 | 能力 | 支持的容器 |
|---|---|---|
| 输入迭代器 | 只读,单向 | istream |
| 输出迭代器 | 只写,单向 | ostream |
| 前向迭代器 | 读写,单向 | forward_list |
| 双向迭代器 | 读写,双向 | list, set, map |
| 随机访问迭代器 | 读写,随机 | vector, deque |
8.3 迭代器基本操作
cpp
vector<int> vec = {1, 2, 3, 4, 5};
// 获取迭代器
vector<int>::iterator it = vec.begin(); // 首元素
vector<int>::iterator end = vec.end(); // 尾后元素(不包含)
// C++11 自动类型推导
auto it2 = vec.begin();
// const 迭代器(只读)
vector<int>::const_iterator cit = vec.cbegin();
// 遍历
for (auto it = vec.begin(); it != vec.end(); ++it) {
cout << *it << " ";
}
// 范围 for 循环(C++11)
for (auto& elem : vec) {
cout << elem << " ";
}
8.4 begin/end 与 cbegin/cend
cpp
// begin() 和 end() 返回的类型根据容器是否为 const 而定
// cbegin() 和 cend()(C++11)始终返回 const_iterator
auto it = vec.cbegin(); // 不论 vec 是否为 const,返回只读迭代器
8.5 迭代器失效问题
以下操作可能导致迭代器失效:
cpp
vector<int> vec = {1, 2, 3, 4, 5};
auto it = vec.begin();
vec.push_back(6); // 可能导致 it 失效(重新分配内存)
vec.insert(vec.begin() + 1, 10); // it 之后的位置可能失效
vec.erase(vec.begin()); // it 及之后的迭代器失效
// 安全做法:操作后重新获取迭代器
it = vec.begin();
九、常用算法
9.1 非修改序列算法
cpp
#include <algorithm>
vector<int> vec = {1, 2, 3, 4, 5};
// 查找
auto it = find(vec.begin(), vec.end(), 3);
// 计数
int n = count(vec.begin(), vec.end(), 3);
// 遍历
for_each(vec.begin(), vec.end(), [](int x) {
cout << x << " ";
});
9.2 修改序列算法
cpp
vector<int> vec1 = {1, 2, 3};
vector<int> vec2(3);
// 复制
copy(vec1.begin(), vec1.end(), vec2.begin());
// 填充
fill(vec2.begin(), vec2.end(), 0);
// 变换
transform(vec1.begin(), vec1.end(), vec2.begin(),
[](int x) { return x * 2; });
// 替换
replace(vec1.begin(), vec1.end(), 2, 20);
// 移除(逻辑删除,不改变大小)
auto new_end = remove(vec1.begin(), vec1.end(), 20);
vec1.erase(new_end, vec1.end()); // 物理删除
// 反转
reverse(vec1.begin(), vec1.end());
9.3 排序算法
cpp
vector<int> vec = {5, 2, 8, 1, 9};
// 排序(默认升序)
sort(vec.begin(), vec.end());
// 自定义排序
sort(vec.begin(), vec.end(), greater<int>()); // 降序
sort(vec.begin(), vec.end(), [](int a, int b) {
return a > b;
});
// 稳定排序
stable_sort(vec.begin(), vec.end());
// 部分排序
partial_sort(vec.begin(), vec.begin() + 3, vec.end());
// 检查是否有序
is_sorted(vec.begin(), vec.end());
9.4 查找算法(有序区间)
cpp
vector<int> vec = {1, 2, 3, 4, 5, 6, 7, 8, 9};
// 二分查找
bool found = binary_search(vec.begin(), vec.end(), 5);
// 下界/上界
auto lower = lower_bound(vec.begin(), vec.end(), 5); // 第一个 >= 5
auto upper = upper_bound(vec.begin(), vec.end(), 5); // 第一个 > 5
9.5 数值算法
cpp
#include <numeric>
vector<int> vec = {1, 2, 3, 4, 5};
// 累加
int sum = accumulate(vec.begin(), vec.end(), 0);
// 内积
int product = inner_product(vec.begin(), vec.end(), vec.begin(), 0);
// 部分和
vector<int> result(5);
partial_sum(vec.begin(), vec.end(), result.begin());
// result: 1, 3, 6, 10, 15
十、forward_list(前向链表,C++11)
cpp
#include <forward_list>
forward_list<int> fl = {1, 2, 3, 4};
// 单向链表,迭代器只能 ++
// 不支持 size() 操作
// 提供 before_begin() 获取头节点前位置
fl.push_front(0);
fl.insert_after(fl.before_begin(), -1);
for (auto it = fl.begin(); it != fl.end(); ++it) {
cout << *it << " ";
}
十一、容器选择指南
| 需求场景 | 推荐容器 | 原因 |
|---|---|---|
| 随机访问,尾部增删 | vector | 连续内存,缓存友好 |
| 头尾增删 | deque | 支持两端高效操作 |
| 频繁中间插入/删除 | list | 链表结构,O(1) 操作 |
| 查找唯一元素 | set / unordered_set | 红黑树/哈希表 |
| 键值对查找 | map / unordered_map | 红黑树/哈希表 |
| LIFO 操作 | stack | 适配器封装 |
| FIFO 操作 | queue | 适配器封装 |
| 带优先级的队列 | priority_queue | 堆结构 |
十二、string 容器
严格来说,string 不属于STL容器,但它支持与容器相似的操作:
cpp
#include <string>
using namespace std;
string s1 = "Hello";
string s2 = "World";
s1 + s2; // 拼接
s1.size(); // 长度
s1.substr(0, 3); // 子串
s1.find("ell"); // 查找
s1[0]; // 下标访问
s1.empty(); // 是否为空
// C++11 数字与字符串转换
int n = stoi("123");
string str = to_string(456);
十三、C++/C 混合调用注意事项
在 C++ 中调用 C 编写的库时,必须使用 extern "C" 包裹头文件,否则由于 C++ 的名称修饰机制,链接器无法找到正确的符号。
cpp
extern "C" {
#include "c_library.h"
}
这个规则适用于所有 C 编写的静态库和动态库。
总结
STL 是 C++ 开发者必须掌握的核心工具库。合理使用 STL 容器和算法,不仅能提高开发效率,还能保证代码的质量和性能。
关键原则:
- 根据使用场景选择合适的容器(随机访问选 vector,频繁插入选 list,查找选 map/set)
- 理解迭代器的种类和失效机制,避免使用失效的迭代器
- 善用泛型算法,减少手写循环
- 优先使用
emplace系列函数减少不必要的对象拷贝 - 注意 STL 的线程安全性:读操作是线程安全的,写操作需要加锁
原始笔记来源: frasight/C++笔记.cpp(STL 部分)