STL 容器 deque(双端队列)完全解析
你现在要了解的 deque(double-ended queue)是 C++ STL 中双端高效操作 的动态序列容器,它弥补了 vector 头部 / 中间操作低效的缺点,同时保留了随机访问的特性,是介于 vector 和 list 之间的灵活选择。下面从「核心特性→基础用法→与 vector 对比→适用场景」全面讲解,新手也能快速理解。
一、核心特性(先搞懂本质)
- 双端高效操作:头部 / 尾部插入 / 删除元素的时间复杂度都是 O(1)(核心优势);
- 随机访问 :通过下标
[]访问元素的时间复杂度 O(1),和vector一样; - 非连续内存 :底层是「分段连续内存」+ 中控数组(存储各段内存的指针),避免了
vector扩容时的大规模拷贝; - 中间操作低效:插入 / 删除中间元素仍需移动后续元素,时间复杂度 O(n);
- 无容量概念 :
deque没有capacity()方法,扩容时只需新增分段内存,无需拷贝原有元素,因此也没有reserve()方法。
二、基础用法(必掌握)
1. 头文件与命名空间
使用 deque 必须包含专属头文件,指定 std 命名空间更便捷:
cpp
运行
#include <deque> // 核心头文件
using namespace std; // 可选,否则需写 std::deque
2. 初始化(常见方式)
cpp
运行
// 1. 空deque
deque<int> d1;
// 2. 初始化指定大小+默认值(8个元素,每个值为0)
deque<int> d2(8);
// 3. 初始化指定大小+自定义值(5个元素,每个值为9)
deque<int> d3(5, 9);
// 4. 用其他deque初始化(拷贝构造)
deque<int> d4(d3);
// 5. C++11 列表初始化(最简洁)
deque<int> d5 = {1,2,3,4,5};
// 6. 用迭代器范围初始化(适配其他容器)
vector<int> v = {6,7,8};
deque<int> d6(v.begin(), v.end()); // d6: [6,7,8]
3. 核心操作(增删改查)
(1)添加元素(双端 + 指定位置)
cpp
运行
deque<int> d;
// 尾部添加(和vector一致)
d.push_back(10); // d: [10]
// 头部添加(deque核心优势,vector无此高效方法)
d.push_front(5); // d: [5,10]
// 指定位置插入(效率比vector略优,但仍为O(n))
d.insert(d.begin()+1, 7); // 在第2个位置插入7 → d: [5,7,10]
d.insert(d.end(), 2, 15); // 尾部插入2个15 → d: [5,7,10,15,15]
(2)删除元素(双端 + 指定位置)
cpp
运行
// 尾部删除(和vector一致)
d.pop_back(); // 删除最后一个元素 → d: [5,7,10,15]
// 头部删除(deque核心优势)
d.pop_front(); // 删除第一个元素 → d: [7,10,15]
// 指定位置删除
d.erase(d.begin()+1); // 删除第2个元素 → d: [7,15]
// 清空所有元素(仅清空内容,释放分段内存)
d.clear();
(3)访问元素(和 vector 几乎一致)
cpp
运行
deque<int> d = {1,2,3,4};
// 方式1:下标访问(无越界检查,速度快)
int a = d[2]; // a=3
// 方式2:at()访问(有越界检查,抛出out_of_range异常)
int b = d.at(2); // b=3
// 方式3:迭代器访问
for (deque<int>::iterator it = d.begin(); it != d.end(); ++it) {
cout << *it << " "; // 输出:1 2 3 4
}
// 方式4:C++11 范围for
for (int num : d) {
cout << num << " ";
}
// 方式5:访问首尾元素(快捷方式)
int first = d.front(); // first=1
int last = d.back(); // last=4
(4)修改元素
cpp
运行
d[1] = 99; // 下标修改 → d: [1,99,3,4]
d.at(2) = 88; // at()修改 → d: [1,99,88,4]
d.front() = 66; // 修改第一个元素 → d: [66,99,88,4]
d.back() = 77; // 修改最后一个元素 → d: [66,99,88,77]
4. 常用属性 / 方法
cpp
运行
deque<int> d = {1,2,3,4};
d.size(); // 元素个数 → 4
d.empty(); // 是否为空 → false
d.resize(6); // 调整大小为6,新增元素默认值0 → d: [1,2,3,4,0,0]
// 注意:deque 无 capacity()/reserve() 方法(无统一容量概念)
三、deque vs vector(核心区别,避坑关键)
| 特性 | deque | vector |
|---|---|---|
| 内存布局 | 分段连续,中控数组管理 | 单一连续内存 |
| 首尾操作效率 | 头部 / 尾部均为 O (1)(高效) | 尾部 O (1),头部 O (n)(低效) |
| 随机访问效率 | O (1)(略低于 vector,需计算分段) | O (1)(最优) |
| 扩容机制 | 新增分段,无大规模拷贝 | 重新分配整块内存 + 拷贝所有元素 |
| 容量相关方法 | 无 capacity ()/reserve () | 有 capacity ()/reserve () |
| 迭代器稳定性 | 插入元素时,仅当前分段迭代器失效 | 扩容后所有迭代器 / 指针失效 |
| 内存利用率 | 分段可能有少量内存浪费 | 连续内存,利用率高 |
四、进阶技巧(避坑 + 优化)
1. 性能优化点
- ✅ 优先用
push_front()/pop_front()处理头部操作(这是 deque 的核心价值); - ✅ 避免频繁在中间插入删除(改用
list); - ✅ 若需大量随机访问 + 偶尔头部操作,选 deque;若仅尾部操作 + 大量随机访问,选 vector。
2. 常见坑点
- ❌ 误以为 deque 是完全连续内存:分段连续导致「取地址后算术运算」可能出错(如
&d[0]+5可能跨分段,结果错误); - ❌ 试图调用
capacity()/reserve():deque 无这两个方法,编译报错; - ❌ 追求极致随机访问效率选 deque:vector 的随机访问略快,仅当需要头部高效操作时才选 deque。
3. 典型使用场景
STL 中 queue(队列)和 stack(栈)的底层默认容器是 deque,而非 vector------ 因为 queue 需要头部出队、尾部入队,stack 需要尾部操作,deque 能更好适配这些场景的效率需求。
五、适用场景
✅ 推荐用 deque:
- 需要头部 + 尾部频繁增删元素(如实现队列、双端队列逻辑);
- 数据量不确定,且不想承受 vector 扩容的拷贝开销;
- 需随机访问,同时偶尔有头部操作需求。
❌ 不推荐用 deque:
- 追求极致的随机访问效率(选 vector);
- 频繁在中间插入删除元素(选 list);
- 需要将容器数据直接转换为原生连续数组(选 vector)。
总结
deque核心优势是双端高效操作(头部 / 尾部增删均为 O (1)),弥补了 vector 头部操作低效的缺点;- 基础用法和 vector 高度相似,重点记住
push_front()/pop_front()这两个独有的高效方法; - 与 vector 的核心区别在内存布局:deque 分段连续,无容量概念,迭代器稳定性略优;
- 选型原则:头部操作多选 deque,仅尾部操作 + 极致随机访问选 vector。
简易实现deque容器
#include<iostream>
#include<string.h>
#include<memory>
using namespace std;
template<class T>
class My_deque {
private:
using value_type = T;
using pointer = T*;
using reference = T&;
using iterator = T*;
using const_iterator = const T*;
pointer _start;
pointer _finish;
size_t _size;
size_t _capacity;
public:
//===================构造与析构==================
My_deque() {
_start = nullptr;
_finish = nullptr;
_size = 0;
_capacity = 0;
}
My_deque(size_t n) {
_capacity = n;
_size = n;
_start = (pointer)operator new(sizeof(value_type) * _capacity);//new运算符
_finish = _start + _size;
for (size_t i = 0; i < n; ++i) {
new (_start + i) T();//定位 new
}
}
My_deque(size_t n, const T& value) {
_capacity = n;
_size = n;
_start = (pointer)operator new(sizeof(value_type) * _capacity);//new运算符
_finish = _start + _size;
for (size_t i = 0; i < n; ++i) {
new (_start + i) T(value);//定位 new
}
}
My_deque(const My_deque& dq) {
_capacity = dq._capacity;
_size = dq._size;
_start = (pointer)operator new(sizeof(value_type) * _capacity);
_finish = _start + _size;
for (size_t i = 0; i < _size; ++i) {
new (_start + i) T(dq._start[i]);
}
}
My_deque(initializer_list<T> ilist) {
_capacity = ilist.size();
_size = ilist.size();
_start = (pointer)operator new(sizeof(value_type) * _capacity);
_finish = _start + _size;
size_t i = 0;
for (const auto& elem : ilist) {
new (_start + i) T(elem);
++i;
}
}
~My_deque() {
for (size_t i = 0; i < _size; ++i) {
_start[i].~T(); // 显式调用析构函数
}
operator delete(_start); // 释放内存
}
// Additional member functions to be implemented
//===================迭代器接口==================
iterator begin() noexcept {
return _start;
}
iterator end() noexcept {
return _finish;
}
iterator cbegin() noexcept {
return _start;
}
iterator cend() noexcept {
return _finish;
}
//===================元素访问==================
reference operator[](size_t i) {
if (i < 0 || i >= _size) {
exit(EXIT_FAILURE);
}
return _start[i];
}
reference at(size_t i) {
if (i < 0 || i >= _size) {
exit(EXIT_FAILURE);
}
return _start[i];
}
reference front() {
return _start[0];
}
reference back() {
return _finish[-1];
}
pointer data() noexcept {
return _start;
}
//===================容量==================
size_t size() noexcept {
return _size;
}
size_t capacity() noexcept {
return _capacity;
}
bool empty() noexcept {
return _size == 0;
}
//===================操作==================
void push_back(const T& value) {
if (_size >= _capacity) {
size_t new_capacity = (_capacity == 0) ? 1 : _capacity * 2;
pointer new_start = (pointer)operator new(sizeof(value_type) * new_capacity);
for (size_t i = 0; i < _size; ++i) {
new (new_start + i) T(std::move(_start[i]));
_start[i].~T();
}
operator delete(_start);
_start = new_start;
_capacity = new_capacity;
}
new (_start + _size) T(value);
++_size;
_finish = _start + _size;
}
void pop_back() {
if (_size == 0) {
exit(EXIT_FAILURE);
}
--_size;
_finish = _start + _size;
_start[_size].~T();
}
void push_front(const T& value) {
if (_size >= _capacity) {
size_t new_capacity = (_capacity == 0) ? 1 : _capacity * 2;
pointer new_start = (pointer)operator new(sizeof(value_type) * new_capacity);
for (size_t i = 0; i < _size; ++i) {
new (new_start + i + 1) T(std::move(_start[i]));
_start[i].~T();
}
operator delete(_start);
_start = new_start;
_capacity = new_capacity;
} else {
for (size_t i = _size; i > 0; --i) {
new (_start + i) T(std::move(_start[i - 1]));
_start[i - 1].~T();
}
}
new (_start) T(value);
++_size;
_finish = _start + _size;
}
void pop_front() {
if (_size == 0) {
exit(EXIT_FAILURE);
}
_start[0].~T();
for (size_t i = 1; i < _size; ++i) {
new (_start + i - 1) T(std::move(_start[i]));
_start[i].~T();
}
--_size;
_finish = _start + _size;
}
void insert(size_t index, const T& value) {
if (index < 0 || index > _size) {
exit(EXIT_FAILURE);
}
if (_size >= _capacity) {
size_t new_capacity = (_capacity == 0) ? 1 : _capacity * 2;
pointer new_start = (pointer)operator new(sizeof(value_type) * new_capacity);
for (size_t i = 0; i < index; ++i) {
new (new_start + i) T(std::move(_start[i]));
_start[i].~T();
}
new (new_start + index) T(value);
for (size_t i = index; i < _size; ++i) {
new (new_start + i + 1) T(std::move(_start[i]));
_start[i].~T();
}
operator delete(_start);
_start = new_start;
_capacity = new_capacity;
} else {
for (size_t i = _size; i > index; --i) {
new (_start + i) T(std::move(_start[i - 1]));
_start[i - 1].~T();
}
new (_start + index) T(value);
}
++_size;
_finish = _start + _size;
}
void erase(size_t index) {
if (index < 0 || index >= _size) {
exit(EXIT_FAILURE);
}
_start[index].~T();
for (size_t i = index + 1; i < _size; ++i) {
new (_start + i - 1) T(std::move(_start[i]));
_start[i].~T();
}
--_size;
_finish = _start + _size;
}
void clear() noexcept {
for (size_t i = 0; i < _size; ++i) {
_start[i].~T();
}
_size = 0;
_finish = _start;
}
void swap(My_deque& other) noexcept {
std::swap(_start, other._start);
std::swap(_finish, other._finish);
std::swap(_size, other._size);
std::swap(_capacity, other._capacity);
}
void resize(size_t new_size, const T& value = T()) {
if (new_size < _size) {
for (size_t i = new_size; i < _size; ++i) {
_start[i].~T();
}
} else if (new_size > _size) {
if (new_size > _capacity) {
size_t new_capacity = new_size;
pointer new_start = (pointer)operator new(sizeof(value_type) * new_capacity);
for (size_t i = 0; i < _size; ++i) {
new (new_start + i) T(std::move(_start[i]));
_start[i].~T();
}
operator delete(_start);
_start = new_start;
_capacity = new_capacity;
}
for (size_t i = _size; i < new_size; ++i) {
new (_start + i) T(value);
}
}
_size = new_size;
_finish = _start + _size;
}
};
int main(){
//初始化测试
My_deque<int> dq1;
My_deque<int> dq2(5);
My_deque<int> dq3(5, 10);
My_deque<int> dq4 = {1, 2, 3, 4, 5};
//元素访问测试
dq3[2] = 20;
cout << "dq3[2]: " << dq3.at(2) << endl;
cout << "dq4 front: " << dq4.front() << ", back: " << dq4.back() << endl;
//容量测试
cout << "dq4 size: " << dq4.size() << ", empty: " << dq4.empty() << endl;
//操作测试
dq4.push_back(6);
dq4.push_front(0);
cout << "After push_back and push_front, dq4: ";
for (auto it = dq4.begin(); it != dq4.end(); ++it) {
cout << *it << " ";
}
cout << endl;
dq4.pop_back();
dq4.pop_front();
cout << "After pop_back and pop_front, dq4: ";
for (auto it = dq4.begin(); it != dq4.end(); ++it) {
cout << *it << " ";
}
cout << endl;
dq4.insert(2, 15);
cout << "After insert 15 at index 2, dq4: ";
for (auto it = dq4.begin(); it != dq4.end(); ++it) {
cout << *it << " ";
}
cout << endl;
dq4.erase(2);
cout << "After erase at index 2, dq4: ";
for (auto it = dq4.begin(); it != dq4.end(); ++it)
{
cout << *it << " ";
}
cout << endl;
dq4.resize(10, 100);
cout << "After resize to 10 with fill value 100, dq4: ";
for (auto it = dq4.begin(); it != dq4.end(); ++it)
{
cout << *it << " ";
}
cout << endl;
dq4.clear();
cout << "After clear, dq4 size: " << dq4.size() << " empty: " << dq4.empty() << endl;
return 0;
}