跳到主要内容C++ STL 容器详解与选型指南 | 极客日志C++算法
C++ STL 容器详解与选型指南
C++ STL 容器是标准库核心组件,涵盖序列、关联及无序容器。 vector、list、deque 等特性与性能,对比时间复杂度,提供选型建议与内存优化技巧,帮助开发者根据场景选择合适容器提升效率。
锁机制14 浏览 C++ STL 容器详解:从入门到精通
一、STL 容器概述
STL(Standard Template Library,标准模板库)是 C++ 标准库的核心组件,提供了一套高效、可复用的数据结构和算法。STL 容器作为其重要组成部分,用于存储和管理数据集合,遵循泛型编程思想,通过模板实现类型无关性。
STL 核心组件
- 容器(Containers):存放数据的结构,如 vector、list、map 等
- 算法(Algorithms):如 sort、find、accumulate 等(头文件<algorithm>)
- 迭代器(Iterators):连接算法与容器的'指针风格'对象
- 函数对象/谓词:比较器、定制规则(常用 lambda 表达式)
- 适配器:容器/迭代器/函数的包装器
二、STL 容器分类体系
STL 容器按照数据组织方式分为三大类:
1. 序列容器(Sequence Containers)
元素按插入顺序存储,保持线性顺序:
- vector:动态数组,连续内存存储
- deque:双端队列,支持头尾高效操作
- list:双向链表,任意位置插入删除高效
- forward_list:单向链表(C++11)
- array:固定长度数组(C++11)
2. 关联容器(Associative Containers)
元素按键值自动排序,基于红黑树实现:
- set:唯一元素的有序集合
- multiset:允许重复元素的有序集合
- map:键值对映射,键唯一
- multimap:允许重复键的映射
3. 无序关联容器(Unordered Associative Containers)
基于哈希表实现,元素无序,查找效率高(C++11):
- unordered_set:哈希集合
- unordered_multiset:允许重复的哈希集合
- unordered_map:哈希映射
- unordered_multimap:允许重复键的哈希映射
4. 容器适配器(Container Adapters)
基于其他容器提供特定功能接口:
- stack:栈(后进先出)
- queue:队列(先进先出)
- priority_queue:优先队列
三、序列容器详解
1. vector(动态数组)
核心特性
- 底层实现:连续内存的动态数组
- 随机访问:O(1) 时间复杂度
- 尾部操作:push_back/pop_back均摊 O(1)
- :插入/删除 O(n)
中间操作
内存管理:自动扩容(通常按 1.5 或 2 倍增长)常用操作
#include <vector>
#include <iostream>
int main() {
std::vector<int> v1;
std::vector<int> v2(5);
std::vector<int> v3(5, 10);
std::vector<int> v4 = {1, 2, 3, 4, 5};
v1.reserve(100);
std::cout << "size: " << v4.size() << std::endl;
std::cout << "capacity: " << v4.capacity() << std::endl;
std::cout << "front: " << v4.front() << std::endl;
std::cout << "back: " << v4.back() << std::endl;
std::cout << "at(2): " << v4.at(2) << std::endl;
v4.push_back(6);
v4.insert(v4.begin() + 2, 100);
v4.erase(v4.begin() + 1);
v4.pop_back();
std::sort(v4.begin(), v4.end());
v4.resize(10, 0);
return 0;
}
性能优化技巧
void vector_reserve_demo() {
std::vector<int> bad_vec;
for (int i = 0; i < 10000; ++i) {
bad_vec.push_back(i);
}
std::vector<int> good_vec;
good_vec.reserve(10000);
for (int i = 0; i < 10000; ++i) {
good_vec.push_back(i);
}
}
void vector_erase_demo() {
std::vector<int> vec = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
for (auto it = vec.begin(); it != vec.end();) {
if (*it % 2 == 0) {
it = vec.erase(it);
} else {
++it;
}
}
vec.erase(
std::remove_if(vec.begin(), vec.end(), [](int x) { return x % 2 == 0; }),
vec.end()
);
}
void modern_vector_usage() {
std::vector<std::pair<int, std::string>> vec;
vec.push_back(std::make_pair(1, "hello"));
vec.push_back({2, "world"});
vec.emplace_back(3, "modern");
vec.emplace_back(4, "cpp");
}
2. list(双向链表)
核心特性
- 底层实现:双向链表,元素非连续存储
- 任意位置操作:插入/删除 O(1)
- 随机访问:O(n),需遍历
- 内存开销:每个节点需额外存储前后指针
常用操作
#include <list>
#include <iostream>
int main() {
std::list<int> numbers = {1, 2, 3, 4, 5};
numbers.push_back(10);
numbers.push_front(0);
auto it = numbers.begin();
std::advance(it, 2);
numbers.insert(it, 100);
numbers.erase(numbers.begin());
numbers.pop_back();
numbers.sort();
numbers.reverse();
numbers.unique();
for (auto num : numbers) {
std::cout << num << " ";
}
std::cout << std::endl;
return 0;
}
适用场景
- 需要频繁在中间位置插入删除元素
- 对随机访问要求不高
- 需要高效的链表合并操作(splice)
3. deque(双端队列)
核心特性
- 底层实现:分段连续存储(多段数组)
- 头尾操作:push_front/push_back均为 O(1)
- 随机访问:O(1),但比 vector 略慢
- 中间操作:插入/删除 O(n)
常用操作
#include <deque>
#include <iostream>
int main() {
std::deque<int> dq;
dq.push_front(1);
dq.push_back(2);
dq.push_front(0);
dq.push_back(3);
std::cout << "第一个元素:" << dq[0] << std::endl;
std::cout << "最后一个元素:" << dq[dq.size()-1] << std::endl;
dq.pop_front();
dq.pop_back();
return 0;
}
适用场景
- 需要频繁在两端插入删除
- 偶尔需要随机访问
- 实现滑动窗口算法
- 双向缓冲区
4. array(固定数组)
核心特性
- 底层实现:编译期定长的连续数组
- 随机访问:O(1)
- 大小固定:无法动态扩容
- 安全访问:提供 at() 方法进行边界检查
常用操作
#include <array>
#include <iostream>
int main() {
std::array<int, 5> arr = {1, 2, 3, 4, 5};
std::cout << "第一个元素:" << arr[0] << std::endl;
std::cout << "最后一个元素:" << arr.back() << std::endl;
try {
std::cout << arr.at(10) << std::endl;
} catch (const std::out_of_range& e) {
std::cout << "越界访问:" << e.what() << std::endl;
}
for (auto num : arr) {
std::cout << num << " ";
}
std::cout << std::endl;
return 0;
}
适用场景
- 元素数量固定且已知
- 需要替代原生数组,提供更安全的接口
- 存储配置信息、坐标等固定长度数据
四、关联容器详解
1. set/multiset(集合)
核心特性
- 底层实现:红黑树(平衡二叉搜索树)
- 自动排序:元素按升序排列
- 查找效率:O(log n)
- 唯一性:set 不允许重复,multiset 允许重复
常用操作
#include <set>
#include <iostream>
int main() {
std::set<int> unique_set = {3, 1, 4, 1, 5};
std::multiset<int> multi_set = {3, 1, 4, 1, 5};
unique_set.insert(2);
unique_set.insert(3);
auto it = unique_set.find(3);
if (it != unique_set.end()) {
std::cout << "找到元素:" << *it << std::endl;
}
unique_set.erase(4);
for (auto num : unique_set) {
std::cout << num << " ";
}
std::cout << std::endl;
auto lower = unique_set.lower_bound(2);
auto upper = unique_set.upper_bound(4);
return 0;
}
适用场景
- 需要去重且有序的集合
- 频繁查找、插入、删除操作
- 需要范围查询(lower_bound/upper_bound)
2. map/multimap(映射)
核心特性
- 底层实现:红黑树,存储键值对
- 自动排序:按键值升序排列
- 查找效率:O(log n)
- 唯一性:map 键唯一,multimap 允许重复键
常用操作
#include <map>
#include <iostream>
#include <string>
int main() {
std::map<std::string, int> student_scores;
student_scores["Alice"] = 95;
student_scores["Bob"] = 87;
student_scores["Charlie"] = 92;
auto result = student_scores.insert({"David", 88});
if (result.second) {
std::cout << "David 插入成功" << std::endl;
}
student_scores.emplace("Eve", 91);
auto it = student_scores.find("Alice");
if (it != student_scores.end()) {
std::cout << "Alice 的分数:" << it->second << std::endl;
}
if (student_scores.count("Bob")) {
std::cout << "Bob 的分数:" << student_scores["Bob"] << std::endl;
}
student_scores.erase("Charlie");
for (const auto& pair : student_scores) {
std::cout << pair.first << ": " << pair.second << std::endl;
}
return 0;
}
适用场景
- 键值对存储,需要按键快速查找
- 需要有序遍历键值对
- 实现字典、配置表、计数器等
五、无序关联容器详解
1. unordered_set/unordered_map
核心特性
- 底层实现:哈希表
- 查找效率:平均 O(1),最坏 O(n)
- 元素无序:不保持插入顺序
- 自定义类型:需提供哈希函数和相等比较函数
常用操作
#include <unordered_map>
#include <iostream>
#include <string>
struct Person {
std::string name;
int age;
bool operator==(const Person& other) const {
return name == other.name && age == other.age;
}
};
namespace std {
template<>
struct hash<Person> {
size_t operator()(const Person& p) const {
return hash<string>()(p.name) ^ hash<int>()(p.age);
}
};
}
int main() {
std::unordered_map<std::string, int> word_count;
word_count["hello"] = 1;
word_count["world"] = 2;
word_count["hello"]++;
auto it = word_count.find("hello");
if (it != word_count.end()) {
std::cout << "hello 出现次数:" << it->second << std::endl;
}
for (const auto& pair : word_count) {
std::cout << pair.first << ": " << pair.second << std::endl;
}
std::unordered_map<Person, int> person_map;
person_map[{ "Alice", 25 }] = 100;
return 0;
}
适用场景
- 需要极速查找,不关心元素顺序
- 高频查询的缓存系统
- 计数器、频率统计
- 去重操作
六、容器适配器详解
1. stack(栈)
核心特性
- 底层实现:基于 deque 或 list
- 后进先出:LIFO(Last-In-First-Out)
- 操作限制:只能在栈顶插入删除
常用操作
#include <stack>
#include <iostream>
int main() {
std::stack<int> s;
s.push(1);
s.push(2);
s.push(3);
std::cout << "栈顶元素:" << s.top() << std::endl;
s.pop();
std::cout << "出栈后栈顶:" << s.top() << std::endl;
std::cout << "栈大小:" << s.size() << std::endl;
return 0;
}
适用场景
- 函数调用栈
- 表达式求值
- 括号匹配
- 深度优先搜索(DFS)
2. queue(队列)
核心特性
- 底层实现:基于 deque 或 list
- 先进先出:FIFO(First-In-First-Out)
- 操作限制:队尾插入,队头删除
常用操作
#include <queue>
#include <iostream>
int main() {
std::queue<int> q;
q.push(1);
q.push(2);
q.push(3);
std::cout << "队头元素:" << q.front() << std::endl;
q.pop();
std::cout << "出队后队头:" << q.front() << std::endl;
std::cout << "队尾元素:" << q.back() << std::endl;
return 0;
}
适用场景
- 任务调度
- 消息队列
- 广度优先搜索(BFS)
- 缓存管理
3. priority_queue(优先队列)
核心特性
- 底层实现:基于 vector 的二叉堆(最大堆)
- 优先级排序:默认最大元素在队首
- 操作效率:push/pop为 O(log n),top为 O(1)
常用操作
#include <queue>
#include <iostream>
#include <functional>
int main() {
std::priority_queue<int> max_heap;
max_heap.push(3);
max_heap.push(1);
max_heap.push(4);
max_heap.push(2);
std::cout << "最大堆:";
while (!max_heap.empty()) {
std::cout << max_heap.top() << " ";
max_heap.pop();
}
std::cout << std::endl;
std::priority_queue<int, std::vector<int>, std::greater<int>> min_heap;
min_heap.push(3);
min_heap.push(1);
min_heap.push(4);
min_heap.push(2);
std::cout << "最小堆:";
while (!min_heap.empty()) {
std::cout << min_heap.top() << " ";
min_heap.pop();
}
std::cout << std::endl;
return 0;
}
适用场景
- 任务优先级调度
- 求 Top K 问题
- 堆排序
- Dijkstra 算法等图算法
七、容器性能对比与选型指南
1. 时间复杂度对比
| 操作 | vector | deque | list | map/set | unordered_map/set |
|---|
| 随机访问 | O(1) | O(1) | O(n) | O(log n) | O(1) 平均 |
| 尾部插入 | O(1) 摊销 | O(1) | O(1) | O(log n) | O(1) 平均 |
| 头部插入 | O(n) | O(1) | O(1) | O(log n) | O(1) 平均 |
| 中间插入 | O(n) | O(n) | O(1) | O(log n) | O(1) 平均 |
| 查找 | O(n) | O(n) | O(n) | O(log n) | O(1) 平均 |
| 删除 | O(n) | O(n) | O(1) | O(log n) | O(1) 平均 |
2. 选型原则
选型口诀
- 随机访问选 vector:需要快速随机访问,尾部操作多
- 头尾操作多选 deque:需要频繁在两端插入删除
- 中间操作频繁选 list:需要任意位置高效插入删除
- 快速查找选 unordered:不关心顺序,追求极致查找效率
- 有序需求选 map/set:需要有序遍历、范围查询
常见场景推荐
- 90% 场景:优先选择 vector,通用性强
- 查找场景:unordered_map/unordered_set(O(1) 查找)
- 有序场景:map/set(需要排序或范围查询)
- 频繁插入删除:list 或 deque(根据位置选择)
- 队列/栈:queue/stack(基于 deque)
3. 性能实测数据
| 操作类型 | vector | deque | list | unordered_map |
|---|
| 顺序访问 | ≈1ms | ≈2ms | ≈150ms | - |
| 尾部插入 | ≈2ms | ≈2ms | ≈8ms | - |
| 中间插入 | ≈150ms | ≈50ms | ≈1ms | - |
| 查找操作 | - | - | ≈500ms | ≈1ms |
八、STL 容器内存管理
1. 内存分配机制
STL 容器使用分配器(Allocator)管理内存,默认使用 std::allocator。容器在需要时动态分配内存,自动处理内存的分配和释放。
vector 扩容策略
void vector_memory_analysis() {
std::vector<int> vec;
std::cout << "空 vector 大小:" << sizeof(vec) << "字节" << std::endl;
for (int i = 1; i <= 5; ++i) {
vec.push_back(i);
std::cout << "元素数:" << vec.size() << ", 容量:" << vec.capacity() << std::endl;
}
}
空 vector 大小:24 字节
元素数:1, 容量:1
元素数:2, 容量:2
元素数:3, 容量:4
元素数:4, 容量:4
元素数:5, 容量:8
2. 内存优化技巧
预分配容量
std::vector<int> bad_vec;
for (int i = 0; i < 10000; ++i) {
bad_vec.push_back(i);
}
std::vector<int> good_vec;
good_vec.reserve(10000);
for (int i = 0; i < 10000; ++i) {
good_vec.push_back(i);
}
释放多余内存
std::vector<int> vec;
vec.reserve(1000);
vec.shrink_to_fit();
3. 自定义分配器
#include <vector>
#include <memory>
#include <iostream>
template<typename T>
class MyAllocator : public std::allocator<T> {
public:
T* allocate(std::size_t n) {
std::cout << "Allocating " << n << " elements" << std::endl;
return std::allocator<T>::allocate(n);
}
void deallocate(T* p, std::size_t n) {
std::cout << "Deallocating " << n << " elements" << std::endl;
std::allocator<T>::deallocate(p, n);
}
};
int main() {
std::vector<int, MyAllocator<int>> vec;
vec.push_back(1);
vec.push_back(2);
vec.push_back(3);
vec.pop_back();
return 0;
}
九、常见误区与最佳实践
1. 常见误区
- 事实:list 查找是 O(n),只有插入删除快
- 建议:除非需要频繁中间插入删除,否则优先选择 vector
- 事实:unordered_map 查找是 O(1),比 map 的 O(log n) 更快
- 建议:不关心顺序时优先选择 unordered_map
- 事实:STL 默认不线程安全
- 建议:多线程环境需加锁或使用并发容器
- 事实:vector 扩容是摊销 O(1),不要过度担心
- 建议:合理使用 reserve() 预分配即可
2. 最佳实践
迭代器失效问题
std::vector<int> vec = {1, 2, 3, 4, 5};
auto it = vec.begin();
vec.push_back(6);
it = vec.begin();
std::cout << *it << std::endl;
使用 emplace 避免拷贝
std::vector<std::string> vec;
vec.push_back(std::string("hello"));
vec.emplace_back("hello");
合理选择删除方式
std::vector<int> vec = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
for (auto it = vec.begin(); it != vec.end();) {
if (*it % 2 == 0) {
it = vec.erase(it);
} else {
++it;
}
}
vec.erase(
std::remove_if(vec.begin(), vec.end(), [](int x) { return x % 2 == 0; }),
vec.end()
);
十、总结
STL 容器是 C++ 编程中不可或缺的工具,选择合适的容器能显著提升程序性能。掌握各容器的特性、时间复杂度、适用场景,是成为高效 C++ 程序员的关键。
- vector 是 90% 场景的首选:随机访问快,尾部操作高效
- unordered 系列查找最快:不关心顺序时优先选择
- list 只适合极少数场景:频繁中间插入删除才考虑
- 合理使用 reserve 预分配:避免 vector 频繁扩容
- 理解迭代器失效规则:插入删除操作后需重新获取迭代器
通过本文的系统学习,相信你已经掌握了 STL 容器的核心知识。在实际开发中,根据具体需求选择合适的容器,配合性能分析工具验证效果,才能写出既高效又健壮的 C++ 代码。
相关免费在线工具
- 加密/解密文本
使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
- Gemini 图片去水印
基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online
- Base64 字符串编码/解码
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
- Base64 文件转换器
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
- Markdown转HTML
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
- HTML转Markdown
将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online