1. 说明
C 语言本身不提供标准通用容器模板,常见数据结构通常需要:
- 手动实现;
- 使用第三方库;
- 使用平台提供的链表、哈希表等接口。
C++ 标准模板库 STL 提供了丰富的容器、迭代器和算法。现代 C++ 开发应优先使用标准容器。
2. 容器分类
2.1 序列容器
按元素排列顺序组织数据:
std::arraystd::vectorstd::dequestd::liststd::forward_list
2.2 有序关联容器
通常基于平衡搜索树,元素保持有序:
std::setstd::multisetstd::mapstd::multimap
2.3 无序关联容器
通常基于哈希表,元素不保证顺序:
std::unordered_setstd::unordered_multisetstd::unordered_mapstd::unordered_multimap
2.4 容器适配器
基于其他容器封装特定访问方式:
std::stackstd::queuestd::priority_queue
3. 容器总览
| 容器 | 主要特点 | 随机访问 | 头部插入 | 尾部插入 | 中间插入 | 查找 |
|---|---|---|---|---|---|---|
array |
固定长度连续数组 | O(1) | 不支持 | 不支持 | 不支持 | O(n) |
vector |
动态连续数组 | O(1) | O(n) | 均摊 O(1) | O(n) | O(n) |
deque |
双端动态序列 | O(1) | O(1) | O(1) | O(n) | O(n) |
list |
双向链表 | 不支持 | O(1) | O(1) | O(1) | O(n) |
forward_list |
单向链表 | 不支持 | O(1) | 不直接支持 | O(1) | O(n) |
set |
有序唯一键 | 不支持 | --- | --- | O(log n) | O(log n) |
map |
有序键值对 | 不支持 | --- | --- | O(log n) | O(log n) |
unordered_set |
哈希唯一键 | 不支持 | --- | --- | 平均 O(1) | 平均 O(1) |
unordered_map |
哈希键值对 | 不支持 | --- | --- | 平均 O(1) | 平均 O(1) |
复杂度为典型情况。无序容器在严重哈希冲突时,最坏可能退化为 O(n)。
4. 通用成员函数
大多数 STL 容器支持以下接口。
cpp
container.empty(); // 是否为空
container.size(); // 元素个数
container.clear(); // 清空
container.begin(); // 首元素迭代器
container.end(); // 尾后迭代器
container.cbegin(); // const 迭代器
container.cend();
遍历:
cpp
for (const auto& item : container) {
// 只读访问
}
修改元素:
cpp
for (auto& item : container) {
// 可修改访问
}
5. std::array
固定长度、连续存储、长度在编译期确定。
cpp
#include <array>
std::array<int, 4> values{1, 2, 3, 4};
values[0] = 10;
int x = values.at(1);
特点:
- 无动态内存分配;
- 可使用 STL 算法;
- 支持
size()、begin()、end(); at()越界时抛出异常;- 适合固定维度数据、查找表、小型缓冲区。
6. std::vector
最常用的动态数组。
cpp
#include <vector>
std::vector<int> values;
values.reserve(100);
values.push_back(10);
values.emplace_back(20);
values.pop_back();
int first = values.front();
int last = values.back();
常用接口:
cpp
values.size();
values.capacity();
values.reserve(1000);
values.resize(100);
values.shrink_to_fit();
values.insert(values.begin(), 5);
values.erase(values.begin());
特点:
- 内存连续,缓存友好;
- 支持随机访问;
- 尾部插入高效;
- 扩容可能导致指针、引用和迭代器失效;
- 中间插入删除需要移动后续元素。
通常没有特殊理由时,序列数据优先考虑 vector。
7. std::deque
双端队列,支持头尾高效插入删除。
cpp
#include <deque>
std::deque<int> data;
data.push_back(10);
data.push_front(5);
data.pop_back();
data.pop_front();
特点:
- 头尾操作通常为 O(1);
- 支持随机访问;
- 元素一般不是一整块连续内存;
- 不适合把
data()当作连续缓冲区; - 常作为
queue和stack的底层容器。
8. std::list
双向链表。
cpp
#include <list>
std::list<int> values{1, 2, 3};
auto it = values.begin();
++it;
values.insert(it, 10);
values.erase(it);
特点:
- 已知位置时插入、删除为 O(1);
- 不支持随机访问;
- 每个节点有额外指针开销;
- 缓存局部性通常较差;
- 迭代器通常较稳定;
- 支持
splice()在链表间移动节点。
不要因为"中间插入快"就默认选择 list。实际工程中,vector 常因缓存优势表现更好。
9. std::forward_list
单向链表,比 list 更节省节点开销。
cpp
#include <forward_list>
std::forward_list<int> values{1, 2, 3};
values.push_front(0);
values.insert_after(values.before_begin(), -1);
values.erase_after(values.before_begin());
特点:
- 仅支持单向遍历;
- 不提供常数时间
size(); - 使用
insert_after、erase_after; - 适合极低开销单向链式结构。
10. std::set 与 std::multiset
cpp
#include <set>
std::set<int> unique_values;
unique_values.insert(3);
unique_values.insert(1);
unique_values.insert(3); // 不会重复插入
std::multiset<int> repeated_values;
repeated_values.insert(3);
repeated_values.insert(3);
特点:
- 自动排序;
set键唯一;multiset允许重复;- 查找、插入、删除通常为 O(log n);
- 适合范围查询和有序遍历。
11. std::map 与 std::multimap
cpp
#include <map>
#include <string>
std::map<std::string, int> scores;
scores["Alice"] = 90;
scores.insert({"Bob", 85});
scores.emplace("Carol", 95);
安全查找:
cpp
auto it = scores.find("Alice");
if (it != scores.end()) {
int score = it->second;
}
注意:
cpp
int value = scores["Unknown"];
operator[] 在键不存在时会创建一个默认值元素。
只查询不插入时,可使用:
cpp
int value = scores.at("Alice");
12. std::unordered_set 和 std::unordered_map
cpp
#include <unordered_map>
#include <unordered_set>
std::unordered_set<int> ids{1, 2, 3};
std::unordered_map<int, std::string> names;
names.emplace(1, "camera");
names.emplace(2, "lidar");
特点:
- 平均 O(1) 查找;
- 不保证元素顺序;
- 需要哈希函数和相等比较;
- 扩容重新哈希会使迭代器失效;
- 可通过
reserve()降低重复扩容成本。
cpp
names.reserve(1000);
names.max_load_factor(0.75f);
13. std::stack
后进先出 LIFO。
cpp
#include <stack>
std::stack<int> st;
st.push(1);
st.push(2);
int top = st.top();
st.pop();
常用接口:
pushemplacetoppopemptysize
注意:pop() 不返回被删除的元素。
14. std::queue
先进先出 FIFO。
cpp
#include <queue>
std::queue<int> q;
q.push(1);
q.push(2);
int first = q.front();
int last = q.back();
q.pop();
常用接口:
pushemplacefrontbackpopemptysize
15. std::priority_queue
优先级队列,默认最大元素优先。
cpp
#include <queue>
#include <vector>
#include <functional>
std::priority_queue<int> max_heap;
max_heap.push(3);
max_heap.push(10);
max_heap.push(5);
int max_value = max_heap.top();
最小堆:
cpp
std::priority_queue<
int,
std::vector<int>,
std::greater<int>
> min_heap;
典型用途:
- Top-K;
- Dijkstra;
- 任务调度;
- 实时选择最高优先级元素。
16. 常用 STL 算法
cpp
#include <algorithm>
#include <numeric>
#include <vector>
std::vector<int> values{4, 1, 3, 2};
std::sort(values.begin(), values.end());
auto it = std::find(values.begin(), values.end(), 3);
int count = std::count(values.begin(), values.end(), 2);
int sum = std::accumulate(values.begin(), values.end(), 0);
常见算法:
sortstable_sortfindfind_ifcountcount_iflower_boundupper_boundbinary_searchremove_iftransformaccumulate
删除满足条件的元素:
cpp
values.erase(
std::remove_if(
values.begin(),
values.end(),
[](int x) { return x < 0; }
),
values.end()
);
C++20 可使用:
cpp
std::erase_if(values, [](int x) { return x < 0; });
17. 容器选择建议
| 需求 | 推荐容器 |
|---|---|
| 默认动态序列 | vector |
| 固定长度数组 | array |
| 频繁头尾插入删除 | deque |
| 已知节点位置频繁链式操作 | list |
| 需要自动排序且键唯一 | set |
| 需要有序键值映射 | map |
| 需要快速查找,不关心顺序 | unordered_map / unordered_set |
| 后进先出 | stack |
| 先进先出 | queue |
| 始终获取最高或最低优先级 | priority_queue |
18. 工程注意事项
- 优先使用值语义和 RAII。
- 避免在容器中存储具有裸指针所有权的对象。
- 提前知道数据规模时,给
vector、unordered_map调用reserve()。 - 只读遍历使用
const auto&,避免不必要拷贝。 - 插入复杂对象时优先考虑
emplace,但不要机械替换所有push。 - 修改容器结构后,注意迭代器、指针、引用是否失效。
- 对外暴露容器时,避免让调用方依赖具体底层类型。
- 根据访问模式、内存局部性和数据规模选择容器,而不只看理论复杂度。