一、std::deque的核心性质
deque(Double-Ended Queue)是 STL 中兼顾 “随机访问” 和 “双端高效操作” 的动态容器,核心特性如下:
- 双端操作高效:队首 / 队尾的插入、删除操作均为O(1)时间复杂度(平均);
- 随机访问支持:可以像
vector一样通过下标[]或at()访问元素,时间复杂度O(1); - 分段连续内存:底层不是单一连续内存块,而是 “分段连续 + 中控器管理” 的结构,避免了
vector整体扩容的内存拷贝开销; - 动态扩容:首尾均可扩展,扩容时仅需新增缓冲区(无需移动已有元素),扩容成本远低于
vector; - 迭代器特性:提供随机访问迭代器(和
vector一致),但迭代器内部会记录缓冲区信息,比vector迭代器稍复杂; - 无内存浪费:相比
vector的 “预分配多余空间”,deque的缓冲区大小固定,内存利用率更高(但中控器会占用少量额外内存); - 中间操作低效:中间位置的插入 / 删除仍需移动元素(或跨缓冲区调整),时间复杂度O(n)(和
vector类似,比list差)。
二、deque的底层存储结构(核心难点)
deque的底层结构是vector和list的 “折中设计”,理解它能解释其所有特性,结构分为两部分:
1. 核心结构(可视化)

2. 各部分详解
| 组件 | 本质 | 作用 |
|---|---|---|
| 中控器(Map) | 动态数组(存储T*类型指针) | 管理所有缓冲区的地址,相当于 “索引表”,中控器自身扩容时仅拷贝指针(成本极低); |
| 缓冲区(Buffer) | 固定大小的连续内存块(存储实际元素) | 每个缓冲区存储相同数量的元素(通常默认 512 字节,可通过编译器调整),元素在缓冲区内连续存储; |
3. 关键操作的底层逻辑
-
随机访问(
deque[i]):- 计算
i属于哪个缓冲区(i / 缓冲区大小)→ 找到中控器中对应的指针; - 计算
i在缓冲区中的偏移(i % 缓冲区大小)→ 定位到具体元素; - 全程O(1),仅多两次算术运算,和
vector几乎无性能差异。
- 计算
-
队首插入(
push_front):- 若头部缓冲区有空闲空间,直接在头部插入;
- 若头部无空间,新增一个缓冲区,将其指针加入中控器头部,再插入元素;
- 无需移动已有元素,O(1)复杂度。
-
扩容(对比
vector):vector:扩容时需分配新的更大连续内存,拷贝所有元素,释放旧内存(O(n)成本);deque:扩容时仅需新增缓冲区,中控器若满则扩容(仅拷贝指针),已有元素无需移动(O(1)成本)。
三、std::deque的常用接口(分类详解)
使用deque需包含头文件:#include <deque>,以下是最常用的接口,按功能分类讲解。
1. 构造函数
| 构造方式 | 说明 |
|---|---|
deque<T> d; | 默认构造,空双端队列 |
deque<T> d(n); | 构造包含n个默认构造元素的队列 |
deque<T> d(n, val); | 构造包含n个值为val的元素的队列 |
deque<T> d(d2); | 拷贝构造,复制d2的所有元素 |
deque<T> d(begin, end); | 范围构造,复制[begin, end)区间内的元素(支持数组 / 其他容器迭代器) |
deque<T> d(std::move(d2)); | 移动构造,接管d2的资源(C++11+) |
2. 元素访问(支持随机访问)
| 接口 | 说明 |
|---|---|
d[i] | 访问第i个元素(无越界检查,越界会导致未定义行为) |
d.at(i) | 访问第i个元素(带越界检查,越界抛出out_of_range异常) |
d.front() | 返回队首元素的引用(可修改) |
d.back() | 返回队尾元素的引用(可修改) |
d.data() | 返回指向第一个缓冲区的指针(C++17+,极少用) |
3. 容量相关
| 接口 | 说明 |
|---|---|
d.empty() | 判断队列是否为空,返回bool |
d.size() | 返回元素个数,类型size_t |
d.max_size() | 返回理论最大可存储元素数(受内存限制) |
d.resize(n) | 调整队列大小为n:若n>size(),新增默认构造元素;若n<size(),删除尾部元素 |
d.resize(n, val) | 调整大小为n,新增元素值为val |
d.shrink_to_fit() | 释放未使用的缓冲区内存(C++11+,仅为建议,编译器可忽略) |
4. 修改操作(核心,双端特性体现)
| 接口 | 说明 |
|---|---|
d.push_front(val) | 队首插入元素val(拷贝 / 移动构造) |
d.push_back(val) | 队尾插入元素val(拷贝 / 移动构造) |
d.emplace_front(args...) | 队首直接构造元素(无需先创建对象,更高效,C++11+) |
d.emplace_back(args...) | 队尾直接构造元素(C++11+) |
d.pop_front() | 删除队首元素(无返回值) |
d.pop_back() | 删除队尾元素(无返回值) |
d.insert(pos, val) | 在迭代器pos位置插入val,返回新元素的迭代器 |
d.insert(pos, n, val) | 在pos位置插入n个val |
d.insert(pos, begin, end) | 在pos位置插入[begin, end)区间的元素 |
d.erase(pos) | 删除pos位置的元素,返回下一个元素的迭代器 |
d.erase(begin, end) | 删除[begin, end)区间的元素,返回下一个元素的迭代器 |
d.swap(d2) | 交换两个deque的内容(中控器 + 缓冲区指针,成本极低) |
d.clear() | 清空所有元素(释放缓冲区内存,中控器保留) |
5. 迭代器(支持随机访问)
| 接口 | 说明 |
|---|---|
d.begin() | 返回指向第一个元素的普通迭代器 |
d.end() | 返回指向最后一个元素下一位的普通迭代器 |
d.cbegin() | 返回指向第一个元素的常量迭代器(只读,C++11+) |
d.cend() | 返回指向最后一个元素下一位的常量迭代器(只读) |
d.rbegin() | 返回指向最后一个元素的反向迭代器 |
d.rend() | 返回指向第一个元素前一位的反向迭代器 |
四、std::deque完整使用示例
以下代码覆盖deque的核心接口,可直接编译运行:
#include <iostream>
#include <deque>
#include <string>
using namespace std;
// 自定义结构体,演示emplace系列接口
struct Student {
string name;
int score;
// 构造函数
Student(string n, int s) : name(n), score(s) {}
};
int main() {
// ========== 1. 构造deque ==========
deque<int> d1; // 默认构造
deque<int> d2(3, 10); // 3个10:[10,10,10]
int arr[] = {1,2,3,4};
deque<int> d3(arr, arr+4); // 范围构造:[1,2,3,4]
// ========== 2. 元素访问 ==========
cout << "d3[1] = " << d3[1] << endl; // 2(无越界检查)
cout << "d3.at(2) = " << d3.at(2) << endl; // 3(带越界检查)
cout << "d3队首:" << d3.front() << endl; // 1
cout << "d3队尾:" << d3.back() << endl; // 4
// 修改元素
d3[3] = 40;
cout << "修改后d3队尾:" << d3.back() << endl; // 40
// ========== 3. 双端插入/删除 ==========
d1.push_front(5); // 队首插入5:[5]
d1.push_back(6); // 队尾插入6:[5,6]
d1.emplace_front(4); // 队首直接构造4:[4,5,6]
d1.emplace_back(7); // 队尾直接构造7:[4,5,6,7]
cout << "d1插入后大小:" << d1.size() << endl; // 4
d1.pop_front(); // 删除队首:[5,6,7]
d1.pop_back(); // 删除队尾:[5,6]
cout << "d1删除后:" << d1[0] << "," << d1[1] << endl; // 5,6
// ========== 4. 中间插入/删除 ==========
// 获取指向第二个元素的迭代器
auto it = d3.begin() + 1;
d3.insert(it, 99); // 在位置1插入99:[1,99,2,3,40]
cout << "插入99后d3:";
for (int num : d3) cout << num << " "; // 1 99 2 3 40
cout << endl;
d3.erase(d3.begin() + 1); // 删除位置1的99:[1,2,3,40]
cout << "删除99后d3:";
for (int num : d3) cout << num << " "; // 1 2 3 40
cout << endl;
// ========== 5. 自定义类型示例 ==========
deque<Student> d_stu;
// emplace_back直接传构造参数,无需先创建Student对象
d_stu.emplace_back("小明", 95);
d_stu.emplace_front("小红", 98);
cout << "第一个学生:" << d_stu.front().name << ",分数:" << d_stu.front().score << endl; // 小红 98
return 0;
}
运行结果:
d3[1] = 2
d3.at(2) = 3
d3队首:1
d3队尾:4
修改后d3队尾:40
d1插入后大小:4
d1删除后:5,6
插入99后d3:1 99 2 3 40
删除99后d3:1 2 3 40
第一个学生:小红,分数:98
五、deque vs vector vs list(核心对比)
| 特性 | deque | vector | list |
|---|---|---|---|
| 随机访问 | 支持(O(1)) | 支持(O(1),更高效) | 不支持(O(n)) |
| 队首插入 / 删除 | O(1)(高效) | O(n)(需移动元素) | O(1)(高效) |
| 队尾插入 / 删除 | O(1)(高效) | O(1)(平均,扩容时O(n)) | O(1)(高效) |
| 中间插入 / 删除 | O(n)(需移动元素) | O(n)(需移动元素) | O(1)(仅调整指针) |
| 内存连续性 | 分段连续 | 完全连续 | 不连续(节点 + 指针) |
| 扩容成本 | 极低(仅新增缓冲区) | 高(整体拷贝元素) | 无扩容成本(节点动态分配) |
| 内存利用率 | 较高(固定缓冲区) | 较低(预分配多余空间) | 低(额外指针开销) |
| 迭代器稳定性 | 插入 / 删除首尾时稳定,中间操作可能失效 | 扩容时迭代器全部失效,中间操作局部失效 | 所有操作迭代器均稳定(除被删除节点) |
使用场景建议
- 选
deque:需要随机访问 + 双端高效操作(如实现队列 / 栈、滑动窗口); - 选
vector:需要极致的随机访问性能 + 仅队尾操作(如存储大量数据、数值计算); - 选
list:需要大量中间插入 / 删除 + 迭代器稳定性(如频繁修改的链表结构)。
总结
std::deque是分段连续的双端队列,兼顾vector的随机访问和list的双端高效操作,扩容成本极低;- 核心接口支持双端插入(
push_front/push_back)、双端删除(pop_front/pop_back)和随机访问([]/at()); - 使用场景:优先用于需要 “首尾操作 + 随机访问” 的场景(如队列 / 栈的底层实现、滑动窗口算法),避免用于大量中间插入 / 删除的场景。

951

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



