C++ STL(Standard Template Library)中主要提供了三大类通用容器,用于存储和管理不同类型和结构的数据。它们分别是:
STL 容器总览
容器类别 | 容器 | 说明 |
---|---|---|
序列式容器 (Sequence Containers) | vector , deque , list , forward_list , array |
元素按插入顺序排列,类似数组 |
关联式容器 (Associative Containers) | set , map , multiset , multimap |
自动排序,基于平衡树(红黑树) |
无序关联容器 (Unordered Associative Containers) | unordered_set , unordered_map , unordered_multiset , unordered_multimap |
无序哈希表,查找插入效率高 |
容器适配器 (Container Adapters) | stack , queue , priority_queue |
封装其他容器,提供特定行为 |
序列式容器(有顺序,按位置存储)
容器名 | 特点 | 常见用途 |
---|---|---|
vector |
动态数组,支持随机访问,尾部插入快 | 默认首选容器,替代 C 数组 |
deque |
双端队列,支持首尾插入删除 | 双端栈、滚动窗口 |
list |
双向链表,插入删除快,不能随机访问 | 频繁插入/删除位置稳定 |
forward_list |
单向链表,内存更小(C++11) | 内嵌链表、只需前向遍历 |
array |
定长数组,封装 C 风格数组(C++11) | 栈上存储,性能好,长度固定 |
一、序列式容器特性对比速查表
特性维度 | vector |
array |
deque |
list |
forward_list |
---|---|---|---|---|---|
底层结构 | 动态数组(连续内存) | 静态数组(连续内存) | 分段数组 | 双向链表 | 单向链表 |
大小可变 | ✅ 是 | ❌ 否 | ✅ 是 | ✅ 是 | ✅ 是(无 size() ) |
下标访问 ([] , at() ) |
✅ 支持 | ✅ 支持 | ✅ 支持 | ❌ 不支持 | ❌ 不支持 |
头部操作 (push_front ) |
❌ 慢(需移动) | ❌ | ✅ 高效 | ✅ 高效 | ✅ 高效 |
尾部操作 (push_back ) |
✅ 高效 | ❌ | ✅ 高效 | ✅ 高效 | ❌ 无此接口 |
中间插入/删除 | ❌ 慢(移动元素) | ❌ | ⚠️ 中等偏慢 | ✅ 高效 | ✅ 但不方便 |
空间管理函数 | ✅ reserve() 、shrink_to_fit() |
❌ | ❌ | ❌ | ❌ |
支持 data() |
✅ | ✅ | ✅(C++17 起) | ❌ | ❌ |
支持 size() |
✅ | ✅ | ✅ | ✅ | ❌(需手动计数) |
迭代器类型 | 随机访问 | 随机访问 | 随机访问 | 双向 | 单向(forward only) |
STL算法兼容性 | ✅ 全兼容 | ✅ 全兼容 | ✅ 全兼容 | ⚠️ 不能用 std::sort() |
同左 |
稳定性(指针/引用稳定) | ❌ 增删会失效 | ✅ | ❌ | ✅ 稳定 | ✅ 稳定 |
排序支持 | std::sort() |
std::sort() |
std::sort() |
.sort() (成员函数) |
.sort() |
内存分配位置 | 堆 | 栈 | 堆(分段) | 堆(节点) | 堆(节点) |
适用场景 | 随机访问、连续优化、频繁尾部插入 | 定长数组替代品、轻量 | 双端队列、滑动窗口 | 频繁中间修改 | 单向处理流、大量前插、轻量链表 |
deque
内存分块不支持空间管理函数,vector
是一片连续的内存,支持 data()
、reserve()
、shrink_to_fit()
等内存操作;
list
和forward_list
内部结构是链表,没有下标操作,链表自带排序api;
array
在编译时就确定了大小,不能动态改变元素的数量,内存分配在栈上;
vector
:适合连续内存优化,支持data()
、reserve()
、shrink_to_fit()
等内存操作,尾部插入效率高,但头部和中间插入较慢。array
:固定大小的轻量容器,栈上分配,支持下标访问和data()
,不支持任何动态操作。deque
:支持头部和尾部插入操作,适合双端队列场景,但不支持空间管理函数,底层为分段内存结构。list
:双向链表容器,适合频繁的中间插入和删除,元素地址稳定,但不支持下标访问,也不能用std::sort()
(需要用成员sort()
)。forward_list
:单向链表容器,接口更轻量,不支持尾部插入和size()
,只能使用insert_after()
/erase_after()
等后置操作,适合嵌入式或轻量场景。
二、共性 API 总结
几乎所有序列式容器都支持如下操作(但有些容器支持受限):
类别 | API | 说明 |
---|---|---|
容器大小 | size() |
返回当前元素个数 |
empty() |
是否为空 | |
max_size() |
可容纳最大元素数 | |
元素访问 | front() / back() |
返回头/尾元素(不能用于 forward_list 的 back() ) |
迭代器 | begin() / end() |
常规迭代器 |
cbegin() / cend() |
const 版本 | |
rbegin() / rend() |
反向迭代器 | |
修改操作 | insert() |
插入元素(forward_list 仅支持 after) |
erase() |
删除元素(forward_list 仅支持 after) |
|
push_back() / pop_back() |
尾部插入/删除(forward_list 无) |
|
push_front() / pop_front() |
头部插入/删除(vector 无) |
|
clear() |
清空容器 | |
resize() |
改变大小 | |
其他 | swap() |
交换两个容器 |
assign() |
批量赋值(如指定区间或重复值) |
二、每个容器详细 API + 特性对比
1. vector<T>
cpp
#include <vector>
std::vector
是一个动态数组容器- 元素连续存储,支持随机访问
- 尾部插入/删除效率高,插入中间位置效率较低
std::vector
的常用构造函数构造函数
构造函数 | 说明 |
---|---|
vector() |
默认构造,空容器 |
vector(size_t n) |
创建含 n 个默认值元素的容器 |
vector(size_t n, const T& val) |
创建含 n 个值为 val 的元素 |
vector(initializer_list<T>) |
使用花括号初始化列表构造(C++11) |
vector(iter1, iter2) |
通过迭代器区间构造 |
vector(const vector& other) |
拷贝构造 |
vector(vector&& other) |
移动构造(C++11) |
具体例子:
构造方式 | 示例 | 说明 |
---|---|---|
默认构造 | vector<int> v; |
创建空容器 |
指定大小 | vector<int> v(5); |
创建 5 个默认元素(0) |
指定大小和值 | vector<int> v(3, 9); |
创建 3 个值为 9 的元素 |
初始化列表 | vector<int> v = {1, 2}; |
推荐用法,清晰直观 |
区间构造 | vector<int> v(v2.begin(), v2.end()); |
从其他容器复制子区间 |
拷贝构造 | vector<int> v(v2); |
拷贝另一个 vector |
移动构造 | vector<int> v(std::move(v2)); |
高效转移所有权(C++11) |
示例代码:
cpp
#include <iostream>
#include <vector>
int main() {
// 1. 默认构造
std::vector<int> v1;
std::cout << "v1.size = " << v1.size() << "\n";
// 2. 构造 n 个默认值元素
std::vector<int> v2(5);
std::cout << "v2: ";
for (int x : v2) std::cout << x << " "; // 输出 0 0 0 0 0
std::cout << "\n";
// 3. 构造 n 个指定值
std::vector<int> v3(4, 7); // 4 个 7
std::cout << "v3: ";
for (int x : v3) std::cout << x << " "; // 输出 7 7 7 7
std::cout << "\n";
// 4. 使用初始化列表
std::vector<int> v4 = {1, 2, 3, 4};
std::cout << "v4: ";
for (int x : v4) std::cout << x << " "; // 输出 1 2 3 4
std::cout << "\n";
// 5. 通过迭代器范围构造(从 v4 拷贝前两个元素)
std::vector<int> v5(v4.begin(), v4.begin() + 2);
std::cout << "v5: ";
for (int x : v5) std::cout << x << " "; // 输出 1 2
std::cout << "\n";
// 6. 拷贝构造
std::vector<int> v6 = v3;
std::cout << "v6: ";
for (int x : v6) std::cout << x << " "; // 输出 7 7 7 7
std::cout << "\n";
// 7. 移动构造(C++11)
std::vector<int> v7 = std::move(v6);
std::cout << "v7: ";
for (int x : v7) std::cout << x << " "; // 输出 7 7 7 7
std::cout << "\n";
}
增(插入)操作
push_back(val)
--- 在尾部插入一个元素
cpp
std::vector<int> v;
v.push_back(10);
v.push_back(20); // v = {10, 20}
insert(pos, val)
--- 在指定位置插入一个元素
cpp
auto it = v.begin(); // 指向第一个元素
v.insert(it + 1, 15); // 在索引1处插入 15:v = {10, 15, 20}
insert(pos, count, val)
--- 插入多个相同元素
cpp
v.insert(v.begin(), 3, 7); // v = {7, 7, 7, 10, 15, 20}
insert(pos, range_begin, range_end)
--- 插入迭代器范围
cpp
std::vector<int> other = {1, 2};
v.insert(v.end(), other.begin(), other.end()); // 尾部插入 1, 2
删(删除)操作
pop_back()
--- 删除最后一个元素
cpp
v.pop_back(); // 删除末尾元素 2
erase(pos)
--- 删除指定位置的元素
cpp
v.erase(v.begin() + 1); // 删除索引1处元素
erase(begin, end)
--- 删除范围内元素
cpp
v.erase(v.begin(), v.begin() + 2); // 删除前两个元素
clear()
--- 清空所有元素
cpp
v.clear(); // v.size() == 0
改(修改)操作
- 使用
operator[]
修改元素
cpp
std::vector<int> v = {1, 2, 3};
v[1] = 10; // v = {1, 10, 3}
- 使用
at(index)
(带越界检查)
cpp
v.at(2) = 99; // v = {1, 10, 99}
⚠️ 如果越界,at()
会抛出 std::out_of_range
异常。
查(访问)操作
- 随机访问
cpp
int x = v[0];
int y = v.at(1); // 推荐安全版本
- 访问首尾元素
cpp
int first = v.front();
int last = v.back();
- 遍历(推荐 range-for)
cpp
for (int val : v) {
std::cout << val << " ";
}
也可以用传统迭代器:
cpp
for (auto it = v.begin(); it != v.end(); ++it) {
std::cout << *it << " ";
}
其他操作
resize(n)
--- 修改容器大小
cpp
v.resize(5); // 增大到5个元素,自动补 0
v.resize(2); // 缩小为2个元素
reserve(capacity)
--- 预分配内存(避免多次扩容)
cpp
v.reserve(100); // 分配100个元素空间,提高效率
shrink_to_fit()
--- 回收冗余空间
vector::shrink_to_fit()
是 C++11 引入的一个标准库成员函数,作用是:请求 释放多余的内存容量,使 vector
的容量(capacity)收缩到与当前元素个数(size)相同,减少内存占用。
cpp
v.shrink_to_fit();
data() 返回指向 vector
内部元素数组的指针
std::vector::data()
是一个成员函数,其作用是:返回指向 vector
内部元素数组的指针(也就是首元素的指针),你可以通过它访问底层的原始连续内存。
data()
给你一个T\*
指针 ,指向vector
内部的连续数组。
cpp
T* data(); // 返回可写指针(C++11 起)
const T* data() const; // 返回只读指针
示例
cpp
#include <iostream>
#include <vector>
int main() {
std::vector<int> v = {1, 2, 3};
int* ptr = v.data();
ptr[1] = 42; // 直接通过指针修改 vector 中的元素
std::cout << v[1] << "\n"; // 输出 42
}
注意事项
vector
的元素是 连续的内存块 ,所以data()
指针指向的是合法数组首地址。- 如果
vector
是空的(size() == 0
),返回的指针 不是nullptr
,但不能解引用。 - 若后续对
vector
执行了插入/删除/扩容操作,原data()
返回的指针可能会失效。
表达式 | 含义 | 备注 |
---|---|---|
v.data() |
返回首元素地址指针 | 推荐方式,语义清晰 |
&v[0] |
同样返回首地址 | C++03 常用,不安全(空 vector 会崩溃) |
示例完整程序
cpp
#include <iostream>
#include <vector>
int main() {
std::vector<int> v;
// 增
v.push_back(1);
v.push_back(2);
v.insert(v.begin(), 0); // 插入到开头
v.insert(v.end(), 2, 99); // 插入两个 99
// 改
v[1] = 100; // 修改第 1 个元素
v.at(2) = 200;
// 查
std::cout << "Front: " << v.front() << ", Back: " << v.back() << "\n";
std::cout << "遍历:";
for (int x : v) std::cout << x << " ";
std::cout << "\n";
// 删
v.pop_back(); // 删除最后一个 99
v.erase(v.begin()); // 删除第一个元素
v.clear(); // 清空
std::cout << "Size after clear: " << v.size() << "\n";
}
总结表格:vector 增删改查 API
操作 | 方法 | 说明 |
---|---|---|
增 | push_back(val) |
尾部插入 |
insert(pos, val) |
指定位置插入 | |
删 | pop_back() |
删除末尾元素 |
erase(pos) |
删除指定元素 | |
clear() |
清空 | |
改 | v[i] = x , v.at(i) |
修改第 i 个元素 |
查 | v[i] , v.at(i) |
访问第 i 个元素 |
front() , back() |
首尾元素 | |
begin() , end() |
迭代器遍历 |
2. deque<T>
std::deque
(double-ended queue )是由多个连续的小块内存(称为块、block、buffer)*构成的*分段动态数组 ,这些小块由一个中央控制结构(map,通常是指针数组)*统一管理,因此整体上*不连续 。deque
支持头部操作但不支持空间管理函数,vector
适合连续内存优化,支持 data()
、reserve()
、shrink_to_fit()
等内存操作。 vector
和 deque
在 API 层面上虽然大多数接口相同,但也存在以下几点显著差异**:
构造函数
与vector的构造函数很类似
构造函数形式 | 功能描述 | 示例 |
---|---|---|
deque() |
默认构造,创建空容器 | std::deque<int> dq; |
deque(size_type n) |
创建包含 n 个默认值元素的容器 |
std::deque<int> dq(5); |
deque(size_type n, const T& val) |
创建包含 n 个值为 val 的元素 |
std::deque<int> dq(3, 42); |
deque(InputIt first, InputIt last) |
用迭代器区间 [first, last) 构造容器 |
std::deque<int> dq(vec.begin(), vec.end()); |
deque(const deque& other) |
拷贝构造函数,复制另一个容器 | std::deque<int> dq2(dq1); |
deque(deque&& other) (C++11) |
移动构造函数,接管另一个容器资源 | std::deque<int> dq2(std::move(dq1)); |
deque(std::initializer_list<T> ilist) (C++11) |
使用初始化列表构造容器 | std::deque<int> dq = {1, 2, 3}; |
与 vector API 上的区别
功能类别 | vector |
deque |
说明 |
---|---|---|---|
头部插入/删除 | ❌ 无 push_front / pop_front |
✅ 有 push_front / pop_front |
这是最明显的区别之一 |
内存管理 | ✅ reserve() / capacity() / shrink_to_fit() |
❌ 不支持这些函数 | deque 不暴露连续空间概念 |
随机访问 | ✅ operator[] , at() |
✅ 同样支持 | 接口一样,性能略有差别 |
构造函数与赋值 | ✅ 基本一致 | ✅ 基本一致 | 如构造、assign() 等均支持 |
数据指针获取 | ✅ data() |
⚠️ 无 data() |
deque 元素非连续,无法获取底层指针 |
空间大小控制 | ✅ capacity() 表示分配空间大小 |
❌ 无此 API | deque 内部是block链表,不存在 capacity |
与 vector 内存结构 上的区别
特性 | vector |
deque |
---|---|---|
内存结构 | 一整块连续内存 | 多块分段内存(非连续) |
指针访问 | data() 可用 |
无 data() (不能整体访问) |
局部性(cache 命中) | 优秀(线性) | 较差(跨 block) |
增长方式 | 一次性重新分配更大空间 | 增加新的 block |
deque内部结构图(简化示意)
std::deque 内存布局示意:
map(指针数组)
↓
+-----+-----+-----+-----+
| → | → | → | → | ← 控制中心
+-----+-----+-----+-----+
↓ ↓ ↓ ↓
block block block block
[x x] [x x] [x x] [x x] ← 实际元素在这些 block 中存储(固定大小)
- 每个 block 通常存储 512~4096 字节左右(依元素类型大小决定)
- 当 deque 两端插入导致空间不足时,它会:
- 分配新 block(前/后扩展)
- 可能重新分配指针数组(map)
实验验证deque
内存不连续
你可以验证 deque
是否连续:
cpp
#include <iostream>
#include <deque>
int main() {
std::deque<int> dq;
// 插入较多元素,促使 deque 使用多个 block
for (int i = 0; i < 1000; ++i) {
dq.push_back(i);
}
// 打印地址,并检测地址跳变
const void* last_addr = nullptr;
for (size_t i = 0; i < dq.size(); ++i) {
const void* addr = static_cast<const void*>(&dq[i]);
if (last_addr != nullptr) {
// 判断是否连续(以 sizeof(int) 为步长)
std::ptrdiff_t diff = static_cast<const char*>(addr) - static_cast<const char*>(last_addr);
if (diff != sizeof(int)) {
std::cout << "⚠️ Address jump detected at i = " << i
<< " (diff = " << diff << " bytes)\n";
}
}
// 输出地址
std::cout << "dq[" << i << "] = " << dq[i]
<< " | address: " << addr << "\n";
last_addr = addr;
}
return 0;
}
常用函数一览表
类别 | 函数 | 说明 |
---|---|---|
增 | push_back(val) |
尾部插入 |
push_front(val) |
头部插入 | |
insert(pos, val) |
插入元素 | |
删 | pop_back() / pop_front() |
删除尾/头元素 |
erase(pos) / clear() |
删除指定位置 / 全部 | |
改 | dq[i] = x / dq.at(i) |
修改第 i 个元素 |
查 | dq[i] / at(i) / front() / back() |
访问元素 |
其他 | size() , empty() , begin() , end() |
常规容器操作 |
3. list<T>
虽然 std::list
和 std::deque
都支持双端插入和删除 ,但它们在底层结构、功能特性和 API 上存在一些明显区别。
deque
是 双端数组容器 ,支持随机访问;
list
是 双向链表容器,支持常量时间的中间插入删除。
构造函数
构造函数形式 | 功能描述 | 示例 |
---|---|---|
list() |
默认构造,创建空链表容器 | std::list<int> lst; |
list(size_type n) |
创建包含 n 个默认值元素的链表 |
std::list<int> lst(5); |
list(size_type n, const T& val) |
创建包含 n 个值为 val 的元素 |
std::list<int> lst(3, 42); |
list(InputIt first, InputIt last) |
用迭代器区间 [first, last) 构造链表 |
std::list<int> lst(vec.begin(), vec.end()); |
list(const list& other) |
拷贝构造,复制另一个链表 | std::list<int> lst2(lst1); |
list(list&& other) (C++11) |
移动构造,接管另一个链表资源 | std::list<int> lst2(std::move(lst1)); |
list(std::initializer_list<T> ilist) (C++11) |
初始化列表构造 | std::list<int> lst = {1, 2, 3}; |
API 区别总览(重点)
功能类别 | std::deque |
std::list |
区别说明 |
---|---|---|---|
随机访问 | ✅ 支持 operator[] , at() |
❌ 不支持 | list 无法直接访问第 i 个元素 |
访问接口 | .at(i) , [i] , .front() , .back() |
.front() , .back() |
list 没有下标访问 |
迭代器 | 双向迭代器 | 双向迭代器 | 都是 bidirectional(list 不能做随机跳跃) |
插入位置 | .insert(pos, val) |
✅ 同上 | 接口类似,list 插入效率更高 |
删除元素 | .erase(pos) |
.erase(pos) , .remove(val) |
list 多了 remove() 和 unique() 等 |
移动元素 | ❌ 不支持 | ✅ splice() |
list 可直接移动其他 list 的节点(不拷贝) |
排序 | ❌ 需 std::sort (必须 random access) |
✅ 内建 .sort() |
list 内建排序,不用 std::sort |
内存结构 | 分段数组 | 双向链表 | list 节点分散,不连续 |
内存局部性 | 好 | 差 | deque 在性能上通常比 list 更好 |
list
独有 API
cpp
std::list<int> lst = {1, 2, 2, 3, 3, 4};
lst.remove(2); // 删除所有值为2的元素
lst.unique(); // 去重:相邻重复元素只保留一个
lst.sort(); // 内建排序(非随机访问)
4. forward_list<T>
(C++11)
std::forward_list
是单向链表 ,轻量、高效但功能少;
std::list
是双向链表,功能强大但相对更重。
forward_list
vs list
API 对比总览
功能类别 | std::forward_list |
std::list |
区别说明 |
---|---|---|---|
链表类型 | 单向链表 | 双向链表 | 方向不同,结构完全不同 |
迭代器类型 | 单向(forward_iterator ) |
双向(bidirectional_iterator ) |
forward_list 只能从前往后走 |
遍历方向 | 只能正向遍历 | 支持正向和反向遍历 | list 支持 reverse_iterator |
头部插入 | .push_front() / .emplace_front() |
✅ 支持 | |
尾部插入 | ❌ 无 .push_back() |
✅ .push_back() / .emplace_back() |
|
中间插入 | .insert_after(pos, val) |
.insert(pos, val) |
插入方式不同 |
中间删除 | .erase_after(pos) |
.erase(pos) |
删除方式也不同 |
删除所有指定值 | .remove(val) |
.remove(val) |
✅ 两者都支持 |
排序 | .sort() |
.sort() |
✅ 都内建排序(非 std::sort ) |
合并 | .merge() |
.merge() |
✅ 都支持(要求排序) |
去重 | .unique() |
.unique() |
✅ 都支持 |
大小查询 | ❌ 无 .size() |
✅ .size() |
forward_list 需手动统计 |
反转 | .reverse() |
.reverse() |
✅ 都支持 |
splice(转移节点) | .splice_after() |
.splice() |
名字和语义不同 |
示例对比
cpp
std::list<int> lst = {1, 2, 3, 4};
auto it = lst.begin();
++it;
lst.insert(it, 99); // 在第二个位置插入
lst.erase(it); // 删除第二个元素
std::forward_list<int> fl = {1, 2, 3, 4};
auto it = fl.before_begin(); // 注意:必须使用 before_begin()
fl.insert_after(it, 99); // 在开头插入
fl.erase_after(it); // 删除开头后一个元素
本质结构差异图示:
std::forward_list (单向):
[1] → [2] → [3] → nullptr
std::list (双向):
nullptr ← [1] ⇄ [2] ⇄ [3] → nullptr
为什么没有push_back api
性能问题 :push_back
操作意味着在链表的末尾添加一个新元素。然而,在 std::forward_list
中,要找到链表的末尾需要从头开始遍历整个链表,因为每个节点只知道它的下一个节点是谁,而不知道它在整个链表中的位置或者如何快速访问最后一个节点。这意味着 push_back
的时间复杂度是 O(n),其中 n 是链表的长度。对于一个以高效插入和删除为卖点的数据结构来说,这不是理想的设计选择。
总结重点区别表
特性 | forward_list |
list |
---|---|---|
链表方向 | 单向 | 双向 |
迭代器 | 单向迭代器 | 双向迭代器 |
尾部插入 | ❌ 无 | ✅ 有 |
中间插入 | .insert_after() |
.insert() |
删除方式 | .erase_after() |
.erase() |
是否支持 .size() |
❌ 无 | ✅ 有 |
是否轻量 | ✅ | ❌ 略重 |
5. array<T, N>
(C++11)
std::array
是 C++11 引入的 定长数组容器 ,在头文件 <array>
中定义,提供了类似 STL 容器的接口但底层是栈上固定大小数组。
std::array<T, N>
是 C++ 中封装了原生数组的 STL 容器,大小固定、性能接近 C 数组,但支持标准容器接口。
构造函数
构造/初始化形式 | 说明 | 示例 |
---|---|---|
默认构造 | 元素未初始化(内置类型无默认值,需注意) | std::array<int, 3> a; |
列表初始化(推荐) | 直接初始化所有元素,大小固定必须匹配 | std::array<int, 3> a = {1,2,3}; |
拷贝构造/赋值 | 支持,拷贝元素 | std::array<int, 3> b(a); |
常用 API 总览
类别 | 成员函数 | 说明 |
---|---|---|
元素访问 | at(i) |
安全访问(越界抛异常) |
operator[i] |
非安全访问(无越界检查) | |
front() |
返回首元素 | |
back() |
返回尾元素 | |
data() |
返回底层原始数组指针(T*) | |
容量 | size() |
返回元素数量(固定) |
empty() |
是否为空(即 size == 0 ) |
|
迭代器 | begin(), end() |
正向迭代器 |
rbegin(), rend() |
反向迭代器 | |
cbegin(), cend() |
const 版本 | |
修改操作 | fill(val) |
全部元素填充为指定值 |
swap(other) |
与另一个 std::array 交换内容 |
示例代码:快速上手
cpp
#include <iostream>
#include <array>
int main() {
std::array<int, 5> arr = {1, 2, 3, 4, 5};
// 元素访问
std::cout << arr[0] << "\n"; // 1
std::cout << arr.at(1) << "\n"; // 2(越界抛异常)
std::cout << arr.front() << "\n"; // 1
std::cout << arr.back() << "\n"; // 5
// 容量
std::cout << "size: " << arr.size() << "\n"; // 5
std::cout << "empty: " << arr.empty() << "\n"; // false
// 迭代器
for (int x : arr) {
std::cout << x << " "; // 1 2 3 4 5
}
std::cout << "\n";
// 修改操作
arr.fill(9);
for (int x : arr) std::cout << x << " "; // 9 9 9 9 9
}
std::array
vs std::vector
类别 | std::vector |
std::array |
差异说明 |
---|---|---|---|
大小可变性 | ✅ 动态变化 | ❌ 固定大小 | vector 支持增删元素,array 不支持 |
元素访问 | operator[] , at() , front() , back() |
同上 | 元素访问接口一致 |
获取裸指针 | .data() |
.data() |
相同 |
容量相关 | .size() , .capacity() , .empty() , .resize() , .reserve() |
.size() , .empty() |
array 无 .capacity() 和 .resize() |
修改操作 | .push_back() , .pop_back() , .insert() , .erase() |
❌ 无这些操作 | array 不支持动态修改 |
元素填充 | ❌ 无 .fill() |
✅ .fill(val) |
array 独有 |
交换内容 | .swap() |
.swap() |
相同 |
迭代器 | .begin() , .end() , rbegin() , rend() 等 |
同上 | 支持一致的 STL 迭代器接口 |
内存位置 | 堆上分配(new) | 栈上分配 | array 更轻量,vector 更灵活 |
关联式容器(自动排序,基于红黑树)
容器名 | 特点 | 键是否唯一 | 复杂度 |
---|---|---|---|
set |
自动排序的集合 | ✅ 是 | O(log n) |
multiset |
可重复元素集合 | ❌ 否 | O(log n) |
map |
键值对(key-value) | ✅ 是 | O(log n) |
multimap |
可重复键的 map | ❌ 否 | O(log n) |
功能 / 容器 | std::map | std::multimap | std::set | std::multiset |
---|---|---|---|---|
键唯一性 | 是 | 否,允许重复键 | 是 | 否,允许重复元素 |
元素类型 | pair<const Key, Value> |
pair<const Key, Value> |
Key |
Key |
插入方式 | insert , emplace , operator[] |
insert , emplace |
insert , emplace |
insert , emplace |
访问元素 | 支持 operator[] , at() |
不支持 | 只支持查找 | 只支持查找 |
查找元素 | find(key) , count(key) (0/1) |
find(key) , count(key) (≥0) |
find(val) , count(val) (0/1) |
find(val) , count(val) (≥0) |
范围查询 | lower_bound , upper_bound , equal_range |
同 map |
同 map |
同 map |
删除元素 | erase(key) , erase(iterator) |
erase(key) 删除所有匹配元素 |
erase(value) , erase(iterator) |
erase(value) , erase(iterator) |
元素排序 | 默认按键升序 | 默认按键升序 | 默认按元素升序 | 默认按元素升序 |
是否允许重复 | 否 | 是 | 否 | 是 |
是否支持下标 | 是 | 否 | 否 | 否 |
默认底层容器 | 红黑树 | 红黑树 | 红黑树 | 红黑树 |
典型用途 | 唯一键值映射 | 一键多值映射 | 唯一有序集合 | 允许重复元素的有序集合 |
std::pair
std::pair
是 C++ 标准库中一个非常基础的模板类,定义在 <utility>
头文件里。它的作用是 将两个类型可以不同的值"成对"存储起来,是构建简单复合数据结构的便捷工具。
主要作用
- 成对存储两个数据 ,可以是不同类型,比如
int
和std::string
。 - 常用来表示键值对(比如
std::map
的元素类型就是std::pair<const Key, Value>
)。 - 用于函数返回多个值。
- 用作容器元素,方便存储关联的两个数据。
定义示例
cpp
template <class T1, class T2>
struct pair {
T1 first;
T2 second;
};
典型用法示例
cpp
#include <iostream>
#include <utility> // std::pair
#include <string>
int main() {
std::pair<int, std::string> p(1, "apple");
std::cout << "first: " << p.first << "\n"; // 输出 1
std::cout << "second: " << p.second << "\n"; // 输出 apple
// 使用 std::make_pair 自动推导类型
auto p2 = std::make_pair(2, 3.14);
std::cout << p2.first << ", " << p2.second << "\n"; // 2, 3.14
return 0;
}
常见操作
p.first
:访问第一个元素p.second
:访问第二个元素- 比较运算符(==, < 等)基于成员逐个比较
在 STL 中的应用
-
std::map
和std::multimap
存储元素的类型是std::pair<const Key, Value>
-
std::unordered_map
也是以std::pair
表示元素 -
迭代器解包常用结构绑定:
cppfor (const auto& [key, val] : myMap) { // 直接访问 key 和 val }
map
关联容器 API 对比:map
vs multimap
vs unordered_map
特性/容器 | std::map |
std::multimap |
std::unordered_map |
---|---|---|---|
是否允许重复 key | ❌ 否 | ✅ 是 | ❌ 否 |
key 排序方式 | ✅ 按 key 升序(默认用 < ) |
✅ 按 key 升序 | ❌ 无序(基于哈希) |
底层结构 | 平衡二叉搜索树(通常是红黑树) | 平衡二叉搜索树 | 哈希表 |
平均时间复杂度 | O(log n) | O(log n) | O(1)(最坏 O(n)) |
元素类型 | pair<const Key, T> |
pair<const Key, T> |
pair<const Key, T> |
访问元素方式 | operator[] , at() |
❌ 不支持 [] ,只能用 insert /find |
operator[] , at() |
插入方式 | insert , emplace , [] |
insert , emplace |
insert , emplace , [] |
删除方式 | erase(key) / erase(iterator) |
同上 | 同上 |
遍历顺序是否稳定/有序 | ✅ 有序 | ✅ 有序 | ❌ 无序 |
边界操作支持 | ✅ lower_bound , upper_bound |
✅ equal_range , lower_bound , 等 |
❌ 不支持边界操作 |
遍历所有重复 key 的值 | 不适用(key 唯一) | ✅ equal_range(key) |
不适用(key 唯一) |
典型使用场景 | 一一映射、排序要求的数据结构 | 一对多映射(如成绩→学生) | 快速哈希查找映射 |
示例代码对比
std::map
和std::unordered_map
std::map
和 std::multimap
和std::unordered_map
插入遍历是一致的,不同的是 std::multimap
有时候可能遍历某个键的所有元素,使用 equal_range。
equal_range也可以用在 std::map
和std::unordered_map
,但是因为键是唯一的,没有必要。
cpp
#include <iostream>
#include <map>
int main() {
std::map<std::string, int> mp;
mp["apple"] = 3;
mp["banana"] = 5;
mp.insert({"orange", 7}); // 插入
mp.emplace("pear", 2); // 原地构造
std::cout << mp["apple"] << "\n"; // 3
if (mp.find("banana") != mp.end())
std::cout << "found\n";
mp.erase("orange"); // 删除元素
std::cout << "C++17 及以上遍历:" << "\n";
//遍历元素 范围基 for(结构化绑定,C++17 及以上)
for (const auto& [key, value] : mp) {
std::cout << key << ": " << value << "\n";
}
std::cout << "传统迭代器遍历:" << "\n";
// 传统迭代器遍历写法
for (auto it = mp.begin(); it != mp.end(); ++it) {
std::cout << it->first << ": " << it->second << "\n";
}
return 0;
}
std::multimap
cpp
#include <iostream>
#include <map>
int main() {
std::multimap<std::string, int> mmap;
mmap.insert({"apple", 3});
mmap.insert({"apple", 4}); // 允许重复 key
std::cout << "C++17 及以上遍历:" << "\n";
// 遍历所有元素(包含重复键)
for (const auto& [key, value] : mmap) {
std::cout << key << ": " << value << "\n";
}
std::cout << "遍历某个键的所有元素:" << "\n";
// 如果想遍历某个键的所有元素,使用 equal_range
auto range = mmap.equal_range("apple");
std::cout << "All values for key 'apple': ";
for (auto it = range.first; it != range.second; ++it) {
std::cout << it->second << " ";
}
std::cout << "\n";
}
set
三种set对比
特性/容器 | std::set |
std::multiset |
std::unordered_set |
---|---|---|---|
元素是否唯一 | ✅ 是(自动去重) | ❌ 否(允许重复) | ✅ 是(自动去重) |
元素是否有序 | ✅ 是(默认升序) | ✅ 是(默认升序) | ❌ 否(无序) |
底层结构 | 红黑树 | 红黑树 | 哈希表 |
时间复杂度(插/删/查) | O(log n) | O(log n) | 平均 O(1),最坏 O(n) |
插入方式 | insert , emplace |
同左 | 同左 |
删除方式 | erase(val) , erase(it) |
同左 | 同左 |
查找方式 | find(val) , count(val) |
find(val) , count(val) |
find(val) , count(val) |
范围操作(lower_bound 等) |
✅ 支持 | ✅ 支持 | ❌ 不支持(哈希无顺序) |
遍历顺序是否固定 | ✅ 升序 | ✅ 升序 | ❌ 无序 |
示例代码
1. std::set
示例:唯一且自动升序
cpp
#include <iostream>
#include <set>
int main() {
std::set<int> s;
s.insert(3);
s.insert(1);
s.insert(2);
s.insert(2); // 重复元素不会插入
for (int val : s) {
std::cout << val << " "; // 输出:1 2 3
}
if (s.count(2)) std::cout << "\nFound 2\n";
s.erase(2); // 删除元素
}
2. std::multiset
示例:允许重复元素
cpp
#include <iostream>
#include <set>
int main() {
std::multiset<int> ms;
ms.insert(2);
ms.insert(1);
ms.insert(2); // 允许插入重复元素
for (int val : ms) {
std::cout << val << " "; // 输出:1 2 2
}
std::cout << "\nCount of 2: " << ms.count(2) << "\n"; // 输出 2
ms.erase(ms.find(2)); // 只删一个 2
}
3. std::unordered_set
示例:唯一但无序
cpp
#include <iostream>
#include <unordered_set>
int main() {
std::unordered_set<int> us;
us.insert(2);
us.insert(3);
us.insert(1);
us.insert(2); // 重复插入无效
for (int val : us) {
std::cout << val << " "; // 输出顺序不确定
}
if (us.count(3)) std::cout << "\nFound 3\n";
us.erase(1); // 删除元素
}
容器适配器(不是独立容器,是"封装器")
容器名 | 底层实现 | 特点 |
---|---|---|
stack |
默认用 deque |
后进先出(LIFO) |
queue |
默认用 deque |
先进先出(FIFO) |
priority_queue |
默认用 vector + make_heap |
优先队列(最大堆/最小堆) |
三种容器适配器对比
适配器 | 行为模型 | 默认底层容器 | 支持的接口 |
---|---|---|---|
stack |
后进先出(LIFO) | deque |
push() , pop() , top() |
queue |
先进先出(FIFO) | deque |
push() , pop() , front() , back() |
priority_queue |
最大堆(默认) | vector |
push() , pop() , top() ,可自定义比较器 |
std::stack
stack常用 API 一览表
函数名 | 说明 | 时间复杂度 |
---|---|---|
push(const T& val) |
将元素压入栈顶 | O(1) |
pop() |
移除栈顶元素(不返回) | O(1) |
top() |
返回栈顶元素(不移除) | O(1) |
empty() |
判断栈是否为空 | O(1) |
size() |
返回元素数量 | O(1) |
emplace(args...) |
原地构造元素(C++11) | O(1) |
swap(other) |
与另一个 stack 交换 |
O(1) |
示例代码
cpp
#include <iostream>
#include <stack>
int main() {
std::stack<int> st;
st.push(10);
st.push(20);
st.push(30);
std::cout << "Top: " << st.top() << "\n"; // 30
st.pop();
std::cout << "Top after pop: " << st.top() << "\n"; // 20
std::cout << "Size: " << st.size() << "\n"; // 2
std::cout << "Is empty? " << std::boolalpha << st.empty() << "\n"; // false
// 原地构造(适合自定义类型)
std::stack<std::pair<int, std::string>> pst;
pst.emplace(1, "Hello");
std::cout << pst.top().second << "\n"; // Hello
}
注意事项
-
pop()
不会返回值 ,只能先用top()
查看再pop()
。cppint x = st.pop(); // ❌ 编译错误
-
默认底层容器是
std::deque
,也可以使用std::vector
、std::list
(但不推荐list
)。cppstd::stack<int, std::vector<int>> vstack;
std::queue
std::queue
是 C++ STL 中的容器适配器 ,实现的是**先进先出(FIFO)**的队列行为。
- 默认基于
std::deque
实现。 - 只能从队尾添加元素 ,从队头移除元素。
- 不支持迭代器、下标访问等功能。
常用 API 总览
函数名 | 说明 | 时间复杂度 |
---|---|---|
push(const T& val) |
将元素加入队尾 | O(1) |
emplace(args...) |
在队尾原地构造元素 | O(1) |
pop() |
移除队头元素(不返回) | O(1) |
front() |
返回队头元素(不移除) | O(1) |
back() |
返回队尾元素 | O(1) |
empty() |
判断是否为空 | O(1) |
size() |
返回元素个数 | O(1) |
swap(other) |
与另一个 queue 交换内容 | O(1) |
示例代码
cpp
#include <iostream>
#include <queue>
int main() {
std::queue<int> q;
q.push(10);
q.push(20);
q.push(30);
std::cout << "Front: " << q.front() << "\n"; // 10
std::cout << "Back: " << q.back() << "\n"; // 30
q.pop(); // 移除10
std::cout << "New Front: " << q.front() << "\n"; // 20
std::cout << "Size: " << q.size() << "\n"; // 2
std::cout << "Empty? " << std::boolalpha << q.empty() << "\n"; // false
}
注意事项
-
pop()
不返回值:cppint x = q.pop(); // ❌ 编译错误!
需要先
front()
再pop()
:cppint x = q.front(); q.pop();
-
不能访问中间元素或用下标:
cppq[0]; // ❌ 错误 for (auto it = q.begin(); ...) // ❌ 错误
-
可以使用其他底层容器(如
list
),但必须支持front()
/back()
/push_back()
/pop_front()
:cppstd::queue<int, std::list<int>> q2;
std::priority_queue
std::priority_queue
是 C++ STL 中的容器适配器 ,实现的是一个堆(heap)结构的优先队列。
- 默认行为:大顶堆(最大元素优先出队)
- 底层容器 :默认使用
std::vector
- 排序工具 :基于
std::make_heap
/push_heap
/pop_heap
实现
常用 API 总览
函数名 | 功能 | 时间复杂度 |
---|---|---|
push(const T& val) |
插入元素 | O(log n) |
emplace(args...) |
原地构造元素(C++11) | O(log n) |
pop() |
移除优先级最高的元素 | O(log n) |
top() |
查看优先级最高的元素 | O(1) |
empty() |
判断是否为空 | O(1) |
size() |
返回元素数量 | O(1) |
swap(other) |
与另一个 priority_queue 交换内容 |
O(1) |
示例代码(默认大顶堆)
cpp
#include <iostream>
#include <queue>
int main() {
std::priority_queue<int> pq;
pq.push(10);
pq.push(30);
pq.push(20);
std::cout << pq.top() << "\n"; // 30
pq.pop(); // 删除 30
std::cout << pq.top() << "\n"; // 20
}
小顶堆写法(自定义比较器)
cpp
#include <queue>
#include <vector>
#include <functional>
std::priority_queue<int, std::vector<int>, std::greater<>> minHeap;
minHeap.push(5);
minHeap.push(2);
minHeap.push(8);
// 最小值优先
std::cout << minHeap.top() << "\n"; // 2
存储结构说明
cpp
template<
class T,
class Container = std::vector<T>,
class Compare = std::less<typename Container::value_type>
> class priority_queue;
T
: 元素类型Container
: 底层容器(默认std::vector
)Compare
: 比较器(默认std::less<T>
,即大顶堆)
自定义类型示例
cpp
struct Task {
int priority;
std::string name;
bool operator<(const Task& other) const {
return priority < other.priority; // 大顶堆:priority 高的优先
}
};
std::priority_queue<Task> taskQ;
taskQ.push({5, "compile"});
taskQ.push({10, "build"});
std::cout << taskQ.top().name; // build
不支持的操作(适配器特性)
操作 | 是否支持 | 原因说明 |
---|---|---|
下标访问 | ❌ | 仅支持访问 top() |
迭代器遍历 | ❌ | 不支持遍历 |
排序顺序访问 | ❌ | 每次只能访问"堆顶最大"元素 |
总结对比表
名称 | 行为模型 | 默认底层容器 | 主要接口 | 是否可自定义比较器 |
---|---|---|---|---|
stack |
LIFO | deque |
push , pop , top |
❌ |
queue |
FIFO | deque |
push , pop , front , back |
❌ |
priority_queue |
堆 | vector |
push , pop , top |
✅ 支持 |