C++ STL 标准库详解
什么是 STL?
STL(Standard Template Library,标准模板库)是 C++ 标准库的重要组成部分,它提供了一组通用的模板类和函数,用于实现常见的数据结构和算法。STL 的设计理念是泛型编程,通过模板技术实现了数据结构和算法的高度复用。
STL 的组成部分
STL 主要由以下六个部分组成:
- 容器(Containers):存储数据的对象
- 迭代器(Iterators):用于遍历容器中的元素
- 算法(Algorithms):操作容器中元素的函数
- 函数对象(Functors):具有函数行为的对象
- 适配器(Adapters):修改其他组件接口的组件
- 分配器(Allocators):负责内存分配和释放
常用容器
1. 序列容器
序列容器按照线性顺序存储元素,支持随机访问或顺序访问。
1.1 vector
vector 是最常用的序列容器,它是一个动态数组,支持随机访问。
常用函数:
| 函数 |
功能 |
时间复杂度 |
push_back() |
在末尾添加元素 |
均摊 O(1) |
pop_back() |
删除末尾元素 |
O(1) |
size() |
返回元素个数 |
O(1) |
empty() |
检查是否为空 |
O(1) |
clear() |
清空容器 |
O(n) |
resize() |
调整容器大小 |
O(n) |
reserve() |
预留空间 |
O(n) |
operator[] |
随机访问元素 |
O(1) |
at() |
带边界检查的随机访问 |
O(1) |
begin() |
返回起始迭代器 |
O(1) |
end() |
返回结束迭代器 |
O(1) |
使用示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| #include <iostream> #include <vector>
int main() { std::vector<int> v; v.push_back(10); v.push_back(20); v.push_back(30); for (int i = 0; i < v.size(); ++i) { std::cout << v[i] << " "; } std::cout << std::endl; for (std::vector<int>::iterator it = v.begin(); it != v.end(); ++it) { std::cout << *it << " "; } std::cout << std::endl; for (int x : v) { std::cout << x << " "; } std::cout << std::endl; std::cout << "第一个元素: " << v.front() << std::endl; std::cout << "最后一个元素: " << v.back() << std::endl; v.pop_back(); std::cout << "大小: " << v.size() << std::endl; v.clear(); std::cout << "是否为空: " << (v.empty() ? "是" : "否") << std::endl; return 0; }
|
1.2 list
list 是双向链表,支持快速的插入和删除操作,但不支持随机访问。
常用函数:
| 函数 |
功能 |
时间复杂度 |
push_back() |
在末尾添加元素 |
O(1) |
push_front() |
在开头添加元素 |
O(1) |
pop_back() |
删除末尾元素 |
O(1) |
pop_front() |
删除开头元素 |
O(1) |
insert() |
在指定位置插入元素 |
O(1) |
erase() |
删除指定位置的元素 |
O(1) |
size() |
返回元素个数 |
O(1) |
empty() |
检查是否为空 |
O(1) |
clear() |
清空容器 |
O(n) |
begin() |
返回起始迭代器 |
O(1) |
end() |
返回结束迭代器 |
O(1) |
使用示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| #include <iostream> #include <list>
int main() { std::list<int> lst; lst.push_back(10); lst.push_back(20); lst.push_front(5); for (int x : lst) { std::cout << x << " "; } std::cout << std::endl; auto it = lst.begin(); ++it; lst.insert(it, 7); for (int x : lst) { std::cout << x << " "; } std::cout << std::endl; it = lst.begin(); ++it; lst.erase(it); for (int x : lst) { std::cout << x << " "; } std::cout << std::endl; std::cout << "大小: " << lst.size() << std::endl; lst.clear(); std::cout << "是否为空: " << (lst.empty() ? "是" : "否") << std::endl; return 0; }
|
1.3 deque
deque 是双端队列,支持在两端快速插入和删除,也支持随机访问。
常用函数:
| 函数 |
功能 |
时间复杂度 |
push_back() |
在末尾添加元素 |
O(1) |
push_front() |
在开头添加元素 |
O(1) |
pop_back() |
删除末尾元素 |
O(1) |
pop_front() |
删除开头元素 |
O(1) |
size() |
返回元素个数 |
O(1) |
empty() |
检查是否为空 |
O(1) |
clear() |
清空容器 |
O(n) |
operator[] |
随机访问元素 |
O(1) |
at() |
带边界检查的随机访问 |
O(1) |
begin() |
返回起始迭代器 |
O(1) |
end() |
返回结束迭代器 |
O(1) |
使用示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| #include <iostream> #include <deque>
int main() { std::deque<int> dq; dq.push_back(10); dq.push_back(20); dq.push_front(5); for (int x : dq) { std::cout << x << " "; } std::cout << std::endl; std::cout << "第一个元素: " << dq[0] << std::endl; std::cout << "第二个元素: " << dq[1] << std::endl; dq.pop_front(); dq.pop_back(); for (int x : dq) { std::cout << x << " "; } std::cout << std::endl; return 0; }
|
2. 关联容器
关联容器按照键值对存储元素,支持快速查找。
2.1 map
map 是有序关联容器,存储键值对,按键自动排序,键唯一。
常用函数:
| 函数 |
功能 |
时间复杂度 |
insert() |
插入键值对 |
O(log n) |
erase() |
删除元素 |
O(log n) |
find() |
查找键 |
O(log n) |
count() |
统计键的个数 |
O(log n) |
size() |
返回元素个数 |
O(1) |
empty() |
检查是否为空 |
O(1) |
clear() |
清空容器 |
O(n) |
begin() |
返回起始迭代器 |
O(1) |
end() |
返回结束迭代器 |
O(1) |
operator[] |
访问或插入元素 |
O(log n) |
at() |
访问元素(带检查) |
O(log n) |
使用示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| #include <iostream> #include <map> #include <string>
int main() { std::map<std::string, int> scores; scores["Alice"] = 95; scores["Bob"] = 88; scores.insert({"Charlie", 92}); for (const auto& pair : scores) { std::cout << pair.first << ": " << pair.second << std::endl; } auto it = scores.find("Bob"); if (it != scores.end()) { std::cout << "Bob's score: " << it->second << std::endl; } scores.erase("Charlie"); std::cout << "Size: " << scores.size() << std::endl; return 0; }
|
2.2 set
set 是有序关联容器,存储唯一元素,自动排序。
常用函数:
| 函数 |
功能 |
时间复杂度 |
insert() |
插入元素 |
O(log n) |
erase() |
删除元素 |
O(log n) |
find() |
查找元素 |
O(log n) |
count() |
统计元素个数 |
O(log n) |
size() |
返回元素个数 |
O(1) |
empty() |
检查是否为空 |
O(1) |
clear() |
清空容器 |
O(n) |
begin() |
返回起始迭代器 |
O(1) |
end() |
返回结束迭代器 |
O(1) |
使用示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| #include <iostream> #include <set>
int main() { std::set<int> s; s.insert(10); s.insert(20); s.insert(10); for (int x : s) { std::cout << x << " "; } std::cout << std::endl; auto it = s.find(15); if (it == s.end()) { std::cout << "15 not found" << std::endl; } s.erase(10); for (int x : s) { std::cout << x << " "; } std::cout << std::endl; return 0; }
|
2.3 unordered_map
unordered_map 是无序关联容器,使用哈希表实现,查找速度更快。
常用函数:与 map 类似,但时间复杂度为平均 O(1)。
使用示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| #include <iostream> #include <unordered_map> #include <string>
int main() { std::unordered_map<std::string, int> scores; scores["Alice"] = 95; scores["Bob"] = 88; scores["Charlie"] = 92; for (const auto& pair : scores) { std::cout << pair.first << ": " << pair.second << std::endl; } auto it = scores.find("Bob"); if (it != scores.end()) { std::cout << "Bob's score: " << it->second << std::endl; } return 0; }
|
2.4 unordered_set
unordered_set 是无序关联容器,使用哈希表实现,存储唯一元素。
常用函数:与 set 类似,但时间复杂度为平均 O(1)。
使用示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| #include <iostream> #include <unordered_set>
int main() { std::unordered_set<int> s; s.insert(10); s.insert(20); s.insert(10); for (int x : s) { std::cout << x << " "; } std::cout << std::endl; return 0; }
|
3. 容器适配器
容器适配器是对现有容器的封装,提供特定的接口。
3.1 stack
stack 是栈适配器,提供后进先出(LIFO)的操作。
常用函数:
| 函数 |
功能 |
时间复杂度 |
push() |
入栈 |
O(1) |
pop() |
出栈 |
O(1) |
top() |
获取栈顶元素 |
O(1) |
size() |
返回元素个数 |
O(1) |
empty() |
检查是否为空 |
O(1) |
使用示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| #include <iostream> #include <stack>
int main() { std::stack<int> st; st.push(10); st.push(20); st.push(30); std::cout << "Top: " << st.top() << std::endl; st.pop(); std::cout << "Top after pop: " << st.top() << std::endl; std::cout << "Size: " << st.size() << std::endl; return 0; }
|
3.2 queue
queue 是队列适配器,提供先进先出(FIFO)的操作。
常用函数:
| 函数 |
功能 |
时间复杂度 |
push() |
入队 |
O(1) |
pop() |
出队 |
O(1) |
front() |
获取队首元素 |
O(1) |
back() |
获取队尾元素 |
O(1) |
size() |
返回元素个数 |
O(1) |
empty() |
检查是否为空 |
O(1) |
使用示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| #include <iostream> #include <queue>
int main() { std::queue<int> q; q.push(10); q.push(20); q.push(30); std::cout << "Front: " << q.front() << std::endl; std::cout << "Back: " << q.back() << std::endl; q.pop(); std::cout << "Front after pop: " << q.front() << std::endl; return 0; }
|
3.3 priority_queue
priority_queue 是优先队列适配器,自动保持最大元素在队首。
常用函数:
| 函数 |
功能 |
时间复杂度 |
push() |
入队 |
O(log n) |
pop() |
出队 |
O(log n) |
top() |
获取队首元素 |
O(1) |
size() |
返回元素个数 |
O(1) |
empty() |
检查是否为空 |
O(1) |
使用示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| #include <iostream> #include <queue>
int main() { std::priority_queue<int> pq; pq.push(30); pq.push(10); pq.push(20); std::cout << "Top: " << pq.top() << std::endl; pq.pop(); std::cout << "Top after pop: " << pq.top() << std::endl; return 0; }
|
迭代器
迭代器是连接容器和算法的桥梁,提供了遍历容器元素的统一接口。
迭代器类型
| 迭代器类型 |
功能 |
支持的操作 |
| 输入迭代器 |
只读,单向移动 |
++, * |
| 输出迭代器 |
只写,单向移动 |
++, * |
| 前向迭代器 |
读写,单向移动 |
++, *, ==, != |
| 双向迭代器 |
读写,双向移动 |
++, --, *, ==, != |
| 随机访问迭代器 |
读写,随机访问 |
++, --, +, -, [], *, ==, !=, <, >, etc. |
常用迭代器操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| #include <iostream> #include <vector>
int main() { std::vector<int> v = {10, 20, 30, 40, 50}; std::cout << "Forward: "; for (auto it = v.begin(); it != v.end(); ++it) { std::cout << *it << " "; } std::cout << std::endl; std::cout << "Reverse: "; for (auto it = v.rbegin(); it != v.rend(); ++it) { std::cout << *it << " "; } std::cout << std::endl; auto it = v.begin(); std::cout << "First element: " << *it << std::endl; std::cout << "Third element: " << *(it + 2) << std::endl; std::cout << "Last element: " << *(v.end() - 1) << std::endl; return 0; }
|
常用算法
STL 提供了大量的算法,定义在 <algorithm> 头文件中。
1. 遍历算法
for_each
对容器中的每个元素执行操作。
使用示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| #include <iostream> #include <vector> #include <algorithm>
void print(int x) { std::cout << x << " "; }
int main() { std::vector<int> v = {1, 2, 3, 4, 5}; std::for_each(v.begin(), v.end(), print); std::cout << std::endl; std::for_each(v.begin(), v.end(), [](int x) { std::cout << x * 2 << " "; }); std::cout << std::endl; return 0; }
|
2. 查找算法
find
查找元素,返回第一个匹配的迭代器。
使用示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| #include <iostream> #include <vector> #include <algorithm>
int main() { std::vector<int> v = {10, 20, 30, 40, 50}; auto it = std::find(v.begin(), v.end(), 30); if (it != v.end()) { std::cout << "Found: " << *it << " at position: " << it - v.begin() << std::endl; } else { std::cout << "Not found" << std::endl; } return 0; }
|
find_if
根据条件查找元素。
使用示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| #include <iostream> #include <vector> #include <algorithm>
int main() { std::vector<int> v = {10, 25, 30, 35, 40}; auto it = std::find_if(v.begin(), v.end(), [](int x) { return x > 30; }); if (it != v.end()) { std::cout << "Found: " << *it << std::endl; } return 0; }
|
3. 排序算法
sort
对元素进行排序。
使用示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| #include <iostream> #include <vector> #include <algorithm>
int main() { std::vector<int> v = {3, 1, 4, 1, 5, 9, 2, 6}; std::sort(v.begin(), v.end()); std::cout << "Ascending: "; for (int x : v) { std::cout << x << " "; } std::cout << std::endl; std::sort(v.begin(), v.end(), std::greater<int>()); std::cout << "Descending: "; for (int x : v) { std::cout << x << " "; } std::cout << std::endl; std::sort(v.begin(), v.end(), [](int a, int b) { return a % 2 < b % 2; }); std::cout << "Custom: "; for (int x : v) { std::cout << x << " "; } std::cout << std::endl; return 0; }
|
4. 修改算法
对元素进行转换。
使用示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| #include <iostream> #include <vector> #include <algorithm>
int main() { std::vector<int> v = {1, 2, 3, 4, 5}; std::vector<int> result(v.size()); std::transform(v.begin(), v.end(), result.begin(), [](int x) { return x * 2; }); std::cout << "Original: "; for (int x : v) std::cout << x << " "; std::cout << std::endl; std::cout << "Transformed: "; for (int x : result) std::cout << x << " "; std::cout << std::endl; return 0; }
|
replace
替换元素。
使用示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| #include <iostream> #include <vector> #include <algorithm>
int main() { std::vector<int> v = {1, 2, 3, 2, 5}; std::replace(v.begin(), v.end(), 2, 99); std::cout << "After replace: "; for (int x : v) { std::cout << x << " "; } std::cout << std::endl; return 0; }
|
5. 数值算法
需要包含 <numeric> 头文件。
accumulate
计算元素的累加和。
使用示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| #include <iostream> #include <vector> #include <numeric>
int main() { std::vector<int> v = {1, 2, 3, 4, 5}; int sum = std::accumulate(v.begin(), v.end(), 0); std::cout << "Sum: " << sum << std::endl; int product = std::accumulate(v.begin(), v.end(), 1, [](int a, int b) { return a * b; }); std::cout << "Product: " << product << std::endl; return 0; }
|
函数对象
函数对象(Functor)是重载了 operator() 的类,具有函数行为。
使用示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| #include <iostream> #include <vector> #include <algorithm>
class GreaterThan { private: int value; public: GreaterThan(int v) : value(v) {} bool operator()(int x) const { return x > value; } };
int main() { std::vector<int> v = {10, 20, 30, 40, 50}; GreaterThan gt(25); auto it = std::find_if(v.begin(), v.end(), gt); if (it != v.end()) { std::cout << "First element > 25: " << *it << std::endl; } return 0; }
|
适配器
适配器用于修改其他组件的接口。
1. 容器适配器
如前所述的 stack、queue 和 priority_queue。
2. 迭代器适配器
reverse_iterator
反向迭代器,用于反向遍历容器。
使用示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| #include <iostream> #include <vector>
int main() { std::vector<int> v = {10, 20, 30, 40, 50}; std::cout << "Reverse: "; for (auto it = v.rbegin(); it != v.rend(); ++it) { std::cout << *it << " "; } std::cout << std::endl; return 0; }
|
3. 函数适配器
C++11 后推荐使用 lambda 表达式,函数适配器使用较少。
分配器
分配器负责容器的内存管理,通常使用默认分配器即可。
自定义分配器示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| #include <iostream> #include <vector>
template <typename T> class MyAllocator { public: typedef T value_type; MyAllocator() noexcept {} template <typename U> MyAllocator(const MyAllocator<U>&) noexcept {} T* allocate(std::size_t n) { std::cout << "Allocating " << n << " elements" << std::endl; return static_cast<T*>(std::malloc(n * sizeof(T))); } void deallocate(T* p, std::size_t n) { std::cout << "Deallocating " << n << " elements" << std::endl; std::free(p); } };
template <typename T, typename U> bool operator==(const MyAllocator<T>&, const MyAllocator<U>&) noexcept { return true; }
template <typename T, typename U> bool operator!=(const MyAllocator<T>&, const MyAllocator<U>&) noexcept { return false; }
int main() { std::vector<int, MyAllocator<int>> v; v.push_back(10); v.push_back(20); std::cout << "Size: " << v.size() << std::endl; return 0; }
|
STL 的最佳实践
1. 容器选择
| 场景 |
推荐容器 |
理由 |
| 随机访问,频繁在末尾添加/删除 |
vector |
随机访问 O(1),末尾操作高效 |
| 频繁在中间插入/删除 |
list |
插入/删除 O(1) |
| 频繁在两端插入/删除 |
deque |
两端操作 O(1) |
| 键值对,需要排序 |
map |
自动排序,查找 O(log n) |
| 键值对,不需要排序 |
unordered_map |
查找平均 O(1) |
| 唯一元素,需要排序 |
set |
自动排序,查找 O(log n) |
| 唯一元素,不需要排序 |
unordered_set |
查找平均 O(1) |
| 后进先出操作 |
stack |
符合栈的特性 |
| 先进先出操作 |
queue |
符合队列的特性 |
| 优先级操作 |
priority_queue |
自动保持优先级 |
2. 性能优化
- 使用
reserve():对于 vector,预先分配足够的空间
- 避免不必要的复制:使用移动语义和
emplace_back()
- 选择合适的容器:根据操作类型选择最优容器
- 使用
unordered_* 容器:对于频繁查找的场景
- 避免在循环中修改容器:可能导致迭代器失效
3. 常见陷阱
- 迭代器失效:修改容器后,迭代器可能失效
- 内存泄漏:容器中的指针需要手动管理
- 性能退化:不正确的容器选择会导致性能问题
- 异常安全:需要考虑异常情况下的资源管理
总结
C++ STL 是一个功能强大、设计优雅的标准库,它提供了丰富的容器、算法和工具,极大地提高了开发效率和代码质量。
STL 的核心优势在于:
- 代码复用:通过模板技术实现泛型编程
- 性能优化:底层实现经过精心优化
- 接口统一:提供了一致的编程接口
- 可扩展性:支持自定义类型和算法
掌握 STL 的使用,对于编写高效、简洁、可维护的 C++ 代码至关重要。通过合理选择容器、灵活使用算法,可以大大简化开发工作,提高代码质量。
参考资料
- C++ 标准库文档
- 《C++ Primer》
- 《Effective STL》
- 《STL 源码剖析》