【数据结构手册008】STL容器完全参考指南
0 容器概览:统一接口,各异特性
C++ STL容器虽然功能各异,但遵循统一的设计哲学。理解这种"家族相似性"能让我们更快掌握新容器。
cpp
// 所有容器的共同基础
template<typename T>
class ContainerConcept {
public:
// 类型定义
using value_type = T;
using size_type = size_t;
using iterator = ...;
using const_iterator = ...;
// 基础操作
bool empty() const;
size_type size() const;
void clear();
// 迭代器
iterator begin();
iterator end();
const_iterator cbegin() const;
const_iterator cend() const;
};
1 序列容器:线性存储
std::vector - 动态数组
本质:连续内存的动态数组
cpp
#include <vector>
std::vector<int> vec = {1, 2, 3};
// === 核心操作 ===
vec.push_back(4); // 尾部添加: {1,2,3,4}
vec.pop_back(); // 尾部删除: {1,2,3}
vec.insert(vec.begin() + 1, 9); // 指定位置插入: {1,9,2,3}
vec.erase(vec.begin() + 1); // 指定位置删除: {1,2,3}
// === 元素访问 ===
int a = vec[1]; // 无边界检查: 2
int b = vec.at(1); // 有边界检查: 2
int c = vec.front(); // 首元素: 1
int d = vec.back(); // 末元素: 3
int* data = vec.data(); // 原始指针
// === 容量管理 ===
vec.reserve(100); // 预分配容量
vec.shrink_to_fit(); // 释放多余容量
size_t cap = vec.capacity(); // 当前容量
size_t sz = vec.size(); // 当前大小
// === 现代C++ ===
vec.emplace_back(5); // 原地构造,避免拷贝
std::deque - 双端队列
本质:分段连续数组
cpp
#include <deque>
std::deque<int> dq = {2, 3, 4};
// === 双端操作 ===
dq.push_front(1); // 头部添加: {1,2,3,4}
dq.push_back(5); // 尾部添加: {1,2,3,4,5}
dq.pop_front(); // 头部删除: {2,3,4,5}
dq.pop_back(); // 尾部删除: {2,3,4}
// === 元素访问 ===
int a = dq[1]; // 随机访问: 3
int b = dq.at(1); // 安全访问: 3
int c = dq.front(); // 头部: 2
int d = dq.back(); // 尾部: 4
// === 修改操作 ===
dq.insert(dq.begin() + 1, 9); // 中间插入: {2,9,3,4}
dq.erase(dq.begin() + 2); // 中间删除: {2,9,4}
std::list - 双向链表
本质:双向链表
cpp
#include <list>
std::list<int> lst = {1, 3, 4};
// === 双端操作 ===
lst.push_front(0); // 头部添加: {0,1,3,4}
lst.push_back(5); // 尾部添加: {0,1,3,4,5}
lst.pop_front(); // 头部删除: {1,3,4,5}
lst.pop_back(); // 尾部删除: {1,3,4}
// === 高效插入删除 ===
auto it = lst.begin();
++it; // 指向1
lst.insert(it, 2); // {1,2,3,4} - O(1)
it = lst.begin();
++++it; // 指向3
lst.erase(it); // {1,2,4} - O(1)
// === 特殊操作 ===
lst.sort(); // 排序: {1,2,4}
lst.unique(); // 去重
lst.reverse(); // 反转: {4,2,1}
// === 链表拼接 ===
std::list<int> other = {5, 6};
lst.splice(lst.end(), other); // {4,2,1,5,6}
std::forward_list - 单向链表
本质:单向链表(更省内存)
cpp
#include <forward_list>
std::forward_list<int> flst = {2, 3, 4};
// === 单端操作 ===
flst.push_front(1); // 头部添加: {1,2,3,4}
flst.pop_front(); // 头部删除: {2,3,4}
// === 特殊插入 ===
auto it = flst.begin();
++it; // 指向3
flst.insert_after(it, 9); // {2,3,9,4} - 在指定位置后插入
// === 特殊删除 ===
it = flst.begin();
flst.erase_after(it); // {2,9,4} - 删除指定位置后的元素
// 注意:没有size()、push_back()、pop_back()!
std::array - 固定大小数组
本质:编译期固定大小的数组
cpp
#include <array>
std::array<int, 5> arr = {1, 2, 3, 4, 5};
// === 元素访问 ===
int a = arr[1]; // 2
int b = arr.at(1); // 2
int c = arr.front(); // 1
int d = arr.back(); // 5
// === 容量信息 ===
bool empty = arr.empty(); // false
size_t size = arr.size(); // 5
size_t max_size = arr.max_size(); // 5
// === 填充操作 ===
arr.fill(0); // {0,0,0,0,0}
// 注意:大小在编译期确定,无法改变!
2 容器适配器
std::stack - 后进先出
本质:LIFO栈
cpp
#include <stack>
std::stack<int> stk;
// === 栈操作 ===
stk.push(1); // 压栈: [1]
stk.push(2); // [1,2]
stk.push(3); // [1,2,3]
int top = stk.top(); // 查看栈顶: 3
stk.pop(); // 出栈: [1,2]
bool empty = stk.empty(); // false
size_t size = stk.size(); // 2
// === 底层容器 ===
std::stack<int, std::vector<int>> stk_vec; // 使用vector
std::stack<int, std::list<int>> stk_list; // 使用list
std::queue - 先进先出
本质:FIFO队列
cpp
#include <queue>
std::queue<int> q;
// === 队列操作 ===
q.push(1); // 入队: [1]
q.push(2); // [1,2]
q.push(3); // [1,2,3]
int front = q.front(); // 队首: 1
int back = q.back(); // 队尾: 3
q.pop(); // 出队: [2,3]
bool empty = q.empty(); // false
size_t size = q.size(); // 2
std::priority_queue - 优先队列
本质:堆实现的优先级队列
cpp
#include <queue>
// 默认大顶堆
std::priority_queue<int> pq;
// === 优先队列操作 ===
pq.push(3); // 添加: [3]
pq.push(1); // [3,1]
pq.push(4); // [4,3,1]
pq.push(2); // [4,3,2,1]
int top = pq.top(); // 最大元素: 4
pq.pop(); // 删除最大: [3,2,1]
bool empty = pq.empty(); // false
size_t size = pq.size(); // 3
// === 小顶堆 ===
std::priority_queue<int, std::vector<int>, std::greater<int>> min_pq;
min_pq.push(3); // [3]
min_pq.push(1); // [1,3]
min_pq.push(4); // [1,3,4]
int min_top = min_pq.top(); // 最小元素: 1
3 关联容器
std::set - 唯一键有序集合
本质:红黑树实现的有序集合
cpp
#include <set>
std::set<int> s = {3, 1, 4, 1, 5}; // {1,3,4,5}
// === 插入操作 ===
auto [it1, success1] = s.insert(2); // {1,2,3,4,5}, success1=true
auto [it2, success2] = s.insert(3); // 不变, success2=false
// === 查找操作 ===
auto it = s.find(3); // 返回迭代器
if (it != s.end()) {
int value = *it; // 3
}
bool exists = s.contains(4); // C++20: true
int count = s.count(1); // 1 (存在)
// === 范围操作 ===
auto lower = s.lower_bound(2); // 第一个>=2的元素
auto upper = s.upper_bound(4); // 第一个>4的元素
// === 删除操作 ===
s.erase(3); // 删除3: {1,2,4,5}
s.erase(it); // 通过迭代器删除
std::multiset - 可重复键有序集合
本质:允许重复的有序集合
cpp
#include <set>
std::multiset<int> ms = {1, 3, 3, 4};
// === 重复插入 ===
ms.insert(3); // {1,3,3,3,4}
// === 计数操作 ===
int count = ms.count(3); // 3
// === 范围查找 ===
auto range = ms.equal_range(3);
for (auto it = range.first; it != range.second; ++it) {
std::cout << *it << " "; // 3 3 3
}
std::unordered_set - 唯一键无序集合
本质:哈希表实现的无序集合
cpp
#include <unordered_set>
std::unordered_set<int> us = {3, 1, 4, 1, 5}; // {1,3,4,5} 顺序不定
// === 基本操作 ===
us.insert(2); // 插入2
us.erase(3); // 删除3
bool found = us.contains(4); // 检查存在
// === 哈希表管理 ===
us.reserve(100); // 预分配桶
float load_factor = us.load_factor(); // 当前负载因子
size_t bucket_count = us.bucket_count(); // 桶数量
// === 桶迭代 ===
for (size_t i = 0; i < us.bucket_count(); ++i) {
size_t bucket_size = us.bucket_size(i);
}
std::unordered_multiset - 可重复键无序集合
本质:允许重复的无序集合
cpp
#include <unordered_set>
std::unordered_multiset<int> ums = {1, 3, 3, 4};
// === 重复操作 ===
ums.insert(3); // 再添加一个3
int count = ums.count(3); // 3
auto range = ums.equal_range(3); // 所有等于3的元素
4 关联数组
std::map - 唯一键有序映射
本质:红黑树实现的有序键值对
cpp
#include <map>
std::map<std::string, int> scores = {
{"Alice", 95},
{"Bob", 87}
};
// === 插入操作 ===
scores["Charlie"] = 92; // 插入或更新
auto [it, success] = scores.insert({"David", 78});
// === 访问操作 ===
int alice_score = scores["Alice"]; // 95
int bob_score = scores.at("Bob"); // 87
// === 查找操作 ===
auto find_it = scores.find("Alice");
if (find_it != scores.end()) {
auto& [key, value] = *find_it; // 结构化绑定
}
bool has_eve = scores.contains("Eve"); // false
// === 范围操作 ===
auto lower = scores.lower_bound("B"); // 第一个键>=B
auto upper = scores.upper_bound("C"); // 第一个键>C
// === 现代插入 ===
scores.emplace("Eve", 88); // 原地构造
scores.try_emplace("Frank", 91); // 键不存在时插入
scores.insert_or_assign("Alice", 96); // 插入或更新
std::multimap - 可重复键有序映射
本质:允许重复键的有序映射
cpp
#include <map>
std::multimap<std::string, int> mmap = {
{"Alice", 95},
{"Alice", 88},
{"Bob", 87}
};
// === 重复插入 ===
mmap.insert({"Alice", 92}); // 允许重复键
// === 范围查找 ===
auto range = mmap.equal_range("Alice");
for (auto it = range.first; it != range.second; ++it) {
std::cout << it->second << " "; // 95 88 92
}
std::unordered_map - 唯一键无序映射
本质:哈希表实现的无序键值对
cpp
#include <unordered_map>
std::unordered_map<std::string, int> word_count;
// === 基本操作 ===
word_count["hello"] = 3; // 插入或更新
word_count["world"]++; // 更新
word_count.erase("hello"); // 删除
// === 安全访问 ===
if (word_count.find("test") != word_count.end()) {
int count = word_count["test"]; // 安全访问
}
// === 哈希表管理 ===
word_count.reserve(1000); // 预分配
word_count.rehash(500); // 重新哈希
// === 现代操作 ===
word_count.emplace("new", 1); // 原地构造
word_count.try_emplace("key", 42); // 安全插入
std::unordered_multimap - 可重复键无序映射
本质:允许重复键的无序映射
cpp
#include <unordered_map>
std::unordered_multimap<std::string, int> ummap;
// === 重复插入 ===
ummap.insert({"key", 1});
ummap.insert({"key", 2}); // 允许重复
// === 范围操作 ===
auto range = ummap.equal_range("key");
for (auto it = range.first; it != range.second; ++it) {
std::cout << it->second << " "; // 1 2
}
5 字符串
std::string - 字符序列
本质:动态字符数组
cpp
#include <string>
std::string str = "Hello";
// === 修改操作 ===
str += " World"; // 拼接: "Hello World"
str.append("!"); // 追加: "Hello World!"
str.insert(5, ","); // 插入: "Hello, World!"
str.erase(5, 1); // 删除: "Hello World!"
// === 查找操作 ===
size_t pos = str.find("World"); // 6
size_t rpos = str.rfind("l"); // 9 (最后一个l)
// === 子串操作 ===
std::string sub = str.substr(6, 5); // "World"
str.replace(6, 5, "C++"); // "Hello C++!"
// === 数值转换 ===
std::string num_str = std::to_string(42); // "42"
int num = std::stoi("123"); // 123
// === C字符串交互 ===
const char* cstr = str.c_str(); // C风格字符串
size_t len = str.length(); // 字符串长度
位集合:紧凑的布尔数组
std::bitset - 固定大小位集合
本质:编译期固定大小的位数组
cpp
#include <bitset>
std::bitset<8> bs(0b10101010); // 170
// === 位操作 ===
bs.set(0); // 设置第0位: 0b10101011
bs.reset(1); // 清除第1位: 0b10101001
bs.flip(2); // 翻转第2位: 0b10101101
// === 访问操作 ===
bool bit0 = bs[0]; // 访问第0位
bool bit1 = bs.test(1); // 安全访问第1位
// === 统计操作 ===
size_t count = bs.count(); // 设置位数量: 5
bool any = bs.any(); // 是否有设置位: true
bool all = bs.all(); // 是否所有位都设置: false
bool none = bs.none(); // 是否没有设置位: false
// === 转换操作 ===
std::string str = bs.to_string(); // "10101101"
unsigned long num = bs.to_ulong(); // 173
性能特征快速参考
时间复杂度对比表
| 操作 | vector | deque | list | set | unordered_set |
|---|---|---|---|---|---|
| 随机访问 | O(1) | O(1) | O(n) | O(log n) | O(1) avg |
| 头部插入 | O(n) | O(1) | O(1) | - | - |
| 尾部插入 | O(1) | O(1) | O(1) | - | - |
| 中间插入 | O(n) | O(n) | O(1) | O(log n) | O(1) avg |
| 查找 | O(n) | O(n) | O(n) | O(log n) | O(1) avg |
迭代器失效规则
| 容器 | 插入操作 | 删除操作 |
|---|---|---|
| vector | 所有迭代器可能失效 | 被删元素之后的迭代器失效 |
| deque | 除首尾插入外可能失效 | 除首尾删除外可能失效 |
| list | 不会失效 | 只有被删元素迭代器失效 |
| 关联容器 | 不会失效 | 只有被删元素迭代器失效 |
选择决策树
基础选择流程
需要键值对?
├─ 是 → 需要有序?
│ ├─ 是 → 键是否唯一?
│ │ ├─ 是 → std::map
│ │ └─ 否 → std::multimap
│ └─ 否 → 键是否唯一?
│ ├─ 是 → std::unordered_map
│ └─ 否 → std::unordered_multimap
│
└─ 否 → 需要唯一元素?
├─ 是 → 需要有序?
│ ├─ 是 → std::set
│ └─ 否 → std::unordered_set
│
└─ 否 → 主要操作在首尾?
├─ 是 → std::deque
├─ 否 → 需要随机访问?
│ ├─ 是 → std::vector
│ └─ 否 → std::list
│
└─ 特定访问模式?
├─ LIFO → std::stack
├─ FIFO → std::queue
└─ 优先级 → std::priority_queue
性能优化指南
cpp
// 1. 预分配空间
std::vector<int> vec;
vec.reserve(1000); // 避免重复分配
std::unordered_map<int, int> umap;
umap.reserve(1000); // 避免重新哈希
// 2. 使用emplace避免拷贝
std::vector<std::string> vec;
vec.emplace_back("hello"); // 原地构造
std::map<int, std::string> map;
map.emplace(1, "world"); // 避免临时对象
// 3. 利用移动语义
std::vector<std::string> getData() {
std::vector<std::string> data;
// ... 填充数据
return data; // 移动而非拷贝
}
// 4. 选择合适的算法
std::vector<int> data = {5, 3, 1, 4, 2};
std::sort(data.begin(), data.end()); // 快速排序
std::stable_sort(data.begin(), data.end()); // 稳定排序
// 5. 利用视图避免拷贝 (C++20)
#if __has_include(<ranges>)
#include <ranges>
std::vector<int> numbers = {1, 2, 3, 4, 5};
auto even_numbers = numbers | std::views::filter([](int n) {
return n % 2 == 0;
}); // 不拷贝数据
#endif
现代C++最佳实践
1. 结构化绑定 (C++17)
cpp
std::map<std::string, int> scores = {{"Alice", 95}, {"Bob", 87}};
// 传统方式
for (const auto& pair : scores) {
std::cout << pair.first << ": " << pair.second << std::endl;
}
// 现代方式
for (const auto& [name, score] : scores) {
std::cout << name << ": " << score << std::endl;
}
2. 透明比较器 (C++14)
cpp
std::set<std::string, std::less<>> transparent_set;
transparent_set.insert("hello");
bool found = transparent_set.contains("hello"); // 不需要构造std::string
3. 安全访问模式
cpp
std::optional<int> safeGet(const std::map<std::string, int>& map,
const std::string& key) {
auto it = map.find(key);
if (it != map.end()) {
return it->second;
}
return std::nullopt;
}
// 使用
if (auto value = safeGet(scores, "Alice")) {
std::cout << "Score: " << *value << std::endl;
}
总结:STL容器的设计哲学
- RAII原则:自动管理资源生命周期
- 泛型编程:模板实现类型无关算法
- 迭代器抽象:统一访问接口
- 算法与容器分离:通过迭代器连接
- 异常安全:提供基本异常安全保证
掌握这些容器不仅意味着知道它们的API,更重要的是理解它们的设计思想和适用场景。在实际开发中,正确的容器选择往往比算法优化带来更大的性能提升。