C++标准库提供了多种容器,主要分为序列容器、关联容器和无序关联容器。
序列容器:包括array, vector, deque, list, forward_list等,按线性顺序存储元素。
关联容器:包括set, map, multiset, multimap等,通常使用红黑树实现,元素按键排序。
无序关联容器:包括unordered_set, unordered_map, unordered_multiset, unordered_multimap等,使用哈希表实现,元素无序。
下面我们分别介绍这些容器,并对比它们的使用场景。
一、序列容器
std::array
固定大小数组,支持随机访问。
大小在编译时确定,栈上分配内存。
使用场景:需要固定大小的数组,且大小在编译时已知。
std::vector
动态数组,支持随机访问。
在尾部插入/删除元素效率高(平均O(1)),在中间或头部插入/删除效率低(O(n))。
使用场景:需要动态数组,频繁随机访问,主要在尾部添加/删除元素。
std::deque
双端队列,支持在头尾快速插入/删除(O(1)),支持随机访问(但比vector慢)。
使用场景:需要频繁在头尾插入/删除元素,且需要随机访问。
std::list
双向链表,不支持随机访问,但可以在任意位置快速插入/删除(O(1)),但需要先找到位置(查找O(n))。
使用场景:需要频繁在任意位置插入/删除,且不需要随机访问。
std::forward_list
单向链表,比list更节省空间,但只能单向遍历。
使用场景:只需要单向遍历,且需要频繁插入/删除。
二、关联容器
std::set
存储唯一键的集合,按键排序。
插入、删除、查找的时间复杂度为O(log n)。
使用场景:需要有序集合,且元素唯一。
std::map
存储键值对,按键排序,键唯一。
插入、删除、查找的时间复杂度为O(log n)。
使用场景:需要有序键值对,键唯一。
std::multiset
类似set,但允许重复键。
使用场景:需要有序集合,且允许重复。
std::multimap
类似map,但允许重复键。
使用场景:需要有序键值对,且允许重复键。
三、无序关联容器
std::unordered_set
存储唯一键的集合,使用哈希表实现,无序。
插入、删除、查找的平均时间复杂度为O(1),最坏情况O(n)。
使用场景:需要快速查找,且不关心顺序。
std::unordered_map
存储键值对,使用哈希表实现,无序。
插入、删除、查找的平均时间复杂度为O(1),最坏情况O(n)。
使用场景:需要快速查找键值对,且不关心顺序。
std::unordered_multiset
类似unordered_set,但允许重复键。
使用场景:需要快速查找,允许重复,不关心顺序。
std::unordered_multimap
类似unordered_map,但允许重复键。
使用场景:需要快速查找键值对,允许重复键,不关心顺序。
四、容器选择建议
选择容器时,考虑以下因素:
元素顺序:是否需要有序?如果需要有序,选择关联容器;如果不需要,考虑无序容器或序列容器。
插入/删除操作:在什么位置插入/删除?如果主要在尾部,用vector;在头尾,用deque;在中间,且频繁插入/删除,用list或forward_list。
随机访问:是否需要随机访问?如果需要,选择vector、deque、array;如果不需要,考虑list或forward_list。
查找性能:是否需要频繁查找?如果需要,考虑关联容器(有序则map/set,无序则unordered_map/unordered_set)。
内存开销:考虑容器的内存布局和额外开销。例如,list和forward_list有指针开销,unordered系列有哈希表开销。
五、代码示例
下面是一些容器的基本使用方法:
vector
cpp
#include <vector>
std::vector<int> vec = {1, 2, 3};
vec.push_back(4); // 在尾部插入
int element = vec[0]; // 随机访问
list
cpp
#include <list>
std::list<int> lst = {1, 2, 3};
lst.push_front(0); // 在头部插入
lst.insert(++lst.begin(), 5); // 在第二个位置插入
map
cpp
#include <map>
std::map<std::string, int> m;
m["apple"] = 5;
m["banana"] = 3;
// 遍历时按键排序,输出先apple后banana
unordered_map
cpp
#include <unordered_map>
std::unordered_map<std::string, int> um;
um["apple"] = 5;
um["banana"] = 3;
// 遍历顺序不确定
六、总结
根据具体需求选择合适的容器是关键。例如,在过敏测试的例子中,我们使用unordered_map来存储过敏原和分数的映射,因为我们需要快速查找,且不关心顺序。使用unordered_set来存储当前过敏原,因为我们需要快速查找和去重,且不关心顺序。
C++容器详细指南:使用方法与场景对比
1. 序列容器 (Sequence Containers)
1.1 std::vector - 动态数组
使用方法:
#include <vector>
#include <algorithm>
std::vector<int> vec = {1, 2, 3};
vec.push_back(4); // 尾部添加
vec.pop_back(); // 尾部删除
vec.insert(vec.begin() + 1, 5); // 中间插入
vec.erase(vec.begin()); // 删除第一个元素
// 遍历
for (auto& elem : vec) {
std::cout << elem << " ";
}
// 排序
std::sort(vec.begin(), vec.end());
使用场景:
- ✅ 适合:随机访问、尾部频繁插入删除、需要连续内存
- ❌ 不适合:频繁在头部或中间插入删除
- 性能特点:访问O(1),尾部插入删除平均O(1),中间插入删除O(n)
1.2 std::list - 双向链表
使用方法:
#include <list>
std::list<int> lst = {1, 2, 3};
lst.push_front(0); // 头部添加
lst.push_back(4); // 尾部添加
lst.pop_front(); // 头部删除
lst.insert(++lst.begin(), 5); // 中间插入
// 特殊操作:拼接(O(1)时间复杂度)
std::list<int> lst2 = {6, 7, 8};
lst.splice(lst.end(), lst2);
// 排序(比vector慢,但保持迭代器有效)
lst.sort();
使用场景:
- ✅ 适合:频繁在任意位置插入删除、需要稳定的迭代器
- ❌ 不适合:需要随机访问、内存敏感的场景
- 性能特点:插入删除O(1),访问O(n)
1.3 std::deque - 双端队列
使用方法:
#include <deque>
std::deque<int> dq = {1, 2, 3};
dq.push_front(0); // 头部添加
dq.push_back(4); // 尾部添加
dq.pop_front(); // 头部删除
dq.pop_back(); // 尾部删除
// 支持随机访问
std::cout << dq[1]; // O(1)访问
使用场景:
- ✅ 适合:需要频繁在头尾插入删除、同时需要随机访问
- ❌ 不适合:需要连续内存的场景
- 性能特点:头尾插入删除O(1),中间操作O(n)
2. 关联容器 (Associative Containers)
2.1 std::set / std::multiset - 有序集合
使用方法:
#include <set>
std::set<int> s = {3, 1, 4, 1, 5}; // {1, 3, 4, 5}
s.insert(2); // 自动排序
s.erase(3); // 删除元素
// 查找
auto it = s.find(4); // O(log n)
if (it != s.end()) {
std::cout << "Found: " << *it;
}
// 范围查找
auto lower = s.lower_bound(2); // 第一个>=2的元素
auto upper = s.upper_bound(4); // 第一个>4的元素
// multiset允许重复
std::multiset<int> ms = {1, 1, 2, 2};
使用场景:
- ✅ 适合:需要有序数据、频繁查找、自动去重(set)
- ❌ 不适合:需要随机访问、内存极度敏感
- 性能特点:插入删除查找都是O(log n)
2.2 std::map / std::multimap - 有序映射
使用方法:
#include <map>
std::map<std::string, int> age_map;
age_map["Alice"] = 25;
age_map["Bob"] = 30;
// 插入方式
age_map.insert({"Charlie", 28});
age_map.emplace("David", 32);
// 遍历(按键排序)
for (const auto& [name, age] : age_map) {
std::cout << name << ": " << age << std::endl;
}
// 查找
auto it = age_map.find("Alice");
if (it != age_map.end()) {
std::cout << "Alice's age: " << it->second;
}
使用场景:
- ✅ 适合:键值对映射、需要按键排序、频繁查找
- ❌ 不适合:只需要存储值、对内存要求严格
- 性能特点:操作都是O(log n)
3. 无序关联容器 (Unordered Associative Containers)
3.1 std::unordered_set / std::unordered_multiset
使用方法:
#include <unordered_set>
std::unordered_set<std::string> us = {"apple", "banana"};
us.insert("orange");
us.erase("apple");
// 查找效率高
if (us.contains("banana")) { // C++20
std::cout << "Has banana";
}
使用场景:
- ✅ 适合:需要快速查找、不关心顺序、需要去重
- ❌ 不适合:需要有序遍历、哈希冲突严重的场景
- 性能特点:平均O(1),最坏O(n)
3.2 std::unordered_map / std::unordered_multimap
使用方法:
#include <unordered_map>
std::unordered_map<std::string, int> word_count;
word_count["hello"] = 1;
word_count["world"]++;
// 遍历(无序)
for (const auto& [word, count] : word_count) {
std::cout << word << ": " << count << std::endl;
}
4. 容器适配器 (Container Adapters)
4.1 std::stack - 栈
#include <stack>
std::stack<int> stk;
stk.push(1);
stk.push(2);
int top = stk.top(); // 2
stk.pop(); // 移除2
4.2 std::queue - 队列
#include <queue>
std::queue<int> q;
q.push(1);
q.push(2);
int front = q.front(); // 1
q.pop(); // 移除1
4.3 std::priority_queue - 优先队列
#include <queue>
std::priority_queue<int> pq; // 最大堆
pq.push(3);
pq.push(1);
pq.push(4);
int top = pq.top(); // 4 (最大值)
pq.pop(); // 移除4
5. 详细对比表格
| 容器 | 底层结构 | 插入性能 | 查找性能 | 内存使用 | 迭代器稳定性 | 使用场景 |
|---|---|---|---|---|---|---|
vector | 动态数组 | 尾部O(1) | O(1) | 紧凑 | 插入可能失效 | 通用、随机访问 |
list | 双向链表 | 任意O(1) | O(n) | 每个元素额外开销 | 稳定 | 频繁插入删除 |
deque | 分块数组 | 头尾O(1) | O(1) | 中等 | 插入可能失效 | 双端队列 |
set/map | 红黑树 | O(log n) | O(log n) | 中等 | 稳定 | 有序数据 |
unordered_set/map | 哈希表 | 平均O(1) | 平均O(1) | 较高 | 插入可能失效 | 快速查找 |
6. 实际选择建议
根据操作频率选择:
// 场景1:主要随机访问,偶尔修改
std::vector<int> data; // 最佳选择
// 场景2:频繁在任意位置插入删除
std::list<int> data; // 链表更合适
// 场景3:需要快速查找,不关心顺序
std::unordered_set<std::string> dictionary;
// 场景4:需要有序数据,频繁范围查询
std::set<int> sorted_data;
性能测试示例:
#include <chrono>
#include <vector>
#include <list>
void test_performance() {
const int N = 100000;
// Vector测试
std::vector<int> vec;
auto start = std::chrono::high_resolution_clock::now();
for (int i = 0; i < N; ++i) {
vec.insert(vec.begin(), i); // 头部插入 - 很慢!
}
auto end = std::chrono::high_resolution_clock::now();
// List测试
std::list<int> lst;
start = std::chrono::high_resolution_clock::now();
for (int i = 0; i < N; ++i) {
lst.push_front(i); // 头部插入 - 很快!
}
end = std::chrono::high_resolution_clock::now();
}
7. 最佳实践总结
- 默认选择:
std::vector- 在不确定时总是首选 - 需要快速查找:
std::unordered_map/set - 需要有序数据:
std::map/set - 频繁插入删除:
std::list(如果不需要随机访问) - 内存敏感:避免链表和哈希表,优先考虑
vector - 线程安全:所有STL容器都不是线程安全的,需要外部同步
选择正确的容器可以显著影响程序性能和内存使用,理解每种容器的特性是写出高效C++代码的关键。
6761



被折叠的 条评论
为什么被折叠?



