C++ STL标准模板库详解
简介
STL(Standard Template Library,标准模板库)是C++标准库的重要组成部分,它提供了一套通用的、可复用的组件,包括容器、迭代器、算法、函数对象、适配器和空间配置器。本文重点讲解最常用的三大组件——容器、迭代器和算法,并通过丰富的代码示例帮助开发者掌握STL的核心用法。
一、STL 总览
1.1 六大组件
| 组件 | 说明 | 头文件示例 |
|---|
| 容器(Container) | 存储和组织数据的数据结构 | , , | | 迭代器(Iterator) | 连接容器和算法的桥梁 | 各容器头文件 | | 算法(Algorithm) | 操作容器内容的模板函数 | | | 仿函数(Functor) | 重载了 operator() 的类 | | | 适配器(Adapter) | 修改容器或函数接口的包装器 | , | | 空间配置器(Allocator) | 负责内存分配与释放 | |
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)。 - #include <vector>
- using namespace std;
复制代码2.2 构造方式- 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 元素访问- vector<int> vec = {10, 20, 30, 40, 50};
- vec[0]; // 下标访问(不检查越界)
- vec.at(2); // at 访问(越界抛出异常)
- vec.front(); // 第一个元素
- vec.back(); // 最后一个元素
- vec.data(); // 返回底层数组指针
复制代码2.4 添加与删除- 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 容量与大小- 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 完整使用示例- #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 类似,支持随机访问,但额外支持在头部高效插入和删除。 - #include <deque>
- using namespace std;
复制代码3.2 与 vector 的区别
| 特性 | vector | deque |
|---|
| 头部插入 | 不支持 | 支持 push_front | | 内存分布 | 连续内存 | 分段连续 | | capacity/reserve | 支持 | 不支持 | | 随机访问 | O(1) | O(1) |
3.3 使用示例- #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)。 - #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 特有操作- 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 使用示例- #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 是有序的唯一元素集合,底层实现为红黑树。元素既充当数据又充当关键字,最大的特点是元素唯一且自动排序。 - #include <set>
- using namespace std;
复制代码5.2 基本用法- 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 的区别在于允许重复元素: - 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 是分开的。 - #include <map>
- using namespace std;
复制代码6.2 基本用法- 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 重复,不支持 [] 运算符: - 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): - #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): - #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(优先队列)
元素按优先级自动排序(默认大顶堆): - #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 迭代器基本操作- 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- // begin() 和 end() 返回的类型根据容器是否为 const 而定
- // cbegin() 和 cend()(C++11)始终返回 const_iterator
- auto it = vec.cbegin(); // 不论 vec 是否为 const,返回只读迭代器
复制代码8.5 迭代器失效问题
以下操作可能导致迭代器失效: - 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 非修改序列算法- #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 修改序列算法- 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 排序算法- 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 查找算法(有序区间)- 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 数值算法- #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)- #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容器,但它支持与容器相似的操作: - #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++ 的名称修饰机制,链接器无法找到正确的符号。 - extern "C" {
- #include "c_library.h"
- }
复制代码这个规则适用于所有 C 编写的静态库和动态库。
总结
STL 是 C++ 开发者必须掌握的核心工具库。合理使用 STL 容器和算法,不仅能提高开发效率,还能保证代码的质量和性能。
关键原则:
- 根据使用场景选择合适的容器(随机访问选 vector,频繁插入选 list,查找选 map/set)
- 理解迭代器的种类和失效机制,避免使用失效的迭代器
- 善用泛型算法,减少手写循环
- 优先使用 emplace 系列函数减少不必要的对象拷贝
- 注意 STL 的线程安全性:读操作是线程安全的,写操作需要加锁
原始笔记来源: frasight/C++笔记.cpp(STL 部分) |