目录
[C++ STL deque 深度剖析](#C++ STL deque 深度剖析)
[一、deque 是什么](#一、deque 是什么)[二、deque 核心特性总览](#二、deque 核心特性总览)
三、底层底层原理(博客重点)[四、deque 常用接口大全](#四、deque 常用接口大全)
五、三大容器深度对比[六、迭代器失效规则(面试 + 博客重点)](#六、迭代器失效规则(面试 + 博客重点))
C++ STL deque 深度剖析
一、deque 是什么
**
std::deque**全称 double-ended queue,译作双端队列,是 C++ STL 中序列式容器之一。一句话定义:
deque 是一种支持头尾常数时间插入删除、支持随机访问、底层分段连续的序列容器,兼具
vector和list的优势。
二、deque 核心特性总览
- 头尾插入 / 删除 O (1),效率极高;
- 支持随机访问,可用
[]、at()访问元素,像数组一样;- 底层分段连续内存,不是整块连续,也不是双向链表;
- 中间位置插入 / 删除为 O (n),效率低;
- 迭代器失效规则比
vector更温和;- 自动扩容,无需手动管理内存。
三、底层底层原理
**存储模型:**中控数组 + 数据缓冲区
deque 底层采用 map 中控表 + 多个固定大小缓冲区 的结构:
- 缓冲区(buffer):每块是固定大小的连续数组,真正存数据;
- 中控数组(map):是一个指针数组,每个元素存放一块缓冲区的起始地址。
四、deque 常用接口大全
deque<int> d1; // 空双端队列 deque<int> d2(5, 10); // 5个元素,初始值10 deque<int> d3 = {1,2,3,4}; // 初始化列表构造 deque<int> d4(d3.begin(), d3.end()); // 迭代器区间构造 dq.push_back(x); // 尾部插入 dq.push_front(x); // 头部插入(vector没有) dq.pop_back(); // 尾部删除 dq.pop_front(); // 头部删除 dq[0]; // 下标随机访问,不越界检查 dq.at(1); // 带越界检查,越界抛异常 dq.front(); // 获取队首元素 dq.back(); // 获取队尾元素 dq.size(); // 有效元素个数 dq.empty(); // 判断是否为空 dq.clear(); // 清空所有元素 // 普通遍历 for(auto it = dq.begin(); it != dq.end(); ++it) cout << *it << " "; // 范围for遍历 for(auto x : dq) cout << x << " "; dq.insert(dq.begin()+2, 99); // 指定位置插入 dq.erase(dq.begin()+1); // 指定位置删除
五、三大容器深度对比
特性 vector deque list 底层结构 整块连续数组 中控表 + 分段连续缓冲区 双向循环链表 尾部增删 O(1) O(1) O(1) 头部增删 O (n) 极慢 O (1) 极快 O (1) 极快 随机访问 []支持 O (1) 支持 O (1) 不支持 内存连续性 完全连续 分段连续 完全不连续 中间插入删除 O(n) O(n) O(1) 遍历效率 最高 中等 较低 迭代器失效 极易失效 仅当前块迭代器失效 仅被删除节点迭代器失效
对比维度 vector deque list 随机访问效率 **最高,**CPU 缓存命中率拉满 较高,跨块有开销 无法随机访问,只能遍历 顺序遍历速度 最快 次之 很慢,缓存失效严重 可用 std::sort可以 可以 不可以 排序方式 std::sort / stable_sort std::sort / stable_sort 只能用成员函数 sort () 排序算法 Introsort 内省排序(快) Introsort 内省排序 归并排序(慢、稳定) 排序稳定性 sort 不稳定 /stable_sort 稳定 sort 不稳定 /stable_sort 稳定 自带 sort 默认稳定
对比维度 vector deque list 内存要求 需要整块连续大内存 只需零散小块内存 仅需单个节点内存 超大数据容量 易分配失败(内存碎片) 适合海量大数据 适合海量大数据 内存占用开销 最小 中等 最大(每个节点 2 个指针) 内存释放 缩容需手动 shrink_to_fit 自动回收空闲内存块 销毁即释放节点
容器 最佳使用场景 不推荐场景 vector 1. 大量数据随机访问 / 遍历 2. 需要频繁排序 3. 只在尾部增删 4. 算法、普通业务存储 1. 频繁头插头删 2. 超大内存无连续空间 3. 频繁中间插入删除 deque 1. 头尾都要频繁增删 2. 需要随机访问 + 排序 3. 数据量大怕连续内存不足 1. 极致追求遍历访问速度 2. 频繁中间插入删除 list 1. 频繁任意位置增删 2. 要求迭代器永不失效 1. 大量遍历、随机访问 2. 需要高性能排序 3. 对内存开销敏感 与vector比较,deque的优势是:头部插入和删除时,不需要搬移元素,效率特别高,而且在扩 容时,也不需要搬移大量的元素,因此其 效率是比vector高 的。
与list比较,其底层是连续空间, 空间利用率比较高 ,不需要存储额外字段。
但是,deque有一个致命缺陷: 不适合遍历 ,因为在遍历时,deque的迭代器要频繁的去检测其 是否移动到某段小空间的边界,导致效率低下,而序列式场景中,可能需要经常遍历,因此在实 际中,需要线性结构时,大多数情况下优先考虑vector和list,deque的应用并不多,而目前能看 到的一个应用就是,STL用其作为stack和queue的底层数据结构。
六、迭代器失效规则
deque 插入
- 头尾插入:迭代器不会失效;
- 中间 insert:所有迭代器失效。
deque 删除
- 头尾 pop:其他迭代器不失效;
- 中间 erase:当前位置及后面迭代器失效。
对比 vector:一旦扩容,全部迭代器直接失效,deque 友好很多
模拟实现
queue
cppnamespace wxx1 { template<class T,class Container=deque<T>> class queue { public: void push(const T& x) { _con.push_back(x); } void pop() { _con.pop_front(); } size_t size()const { return _con.size; } bool empty() const { return _con.empty(); } const T& front()const { return _con.front(); } T& front() { return _con.front(); } const T& back()const { return _con.back(); } T& back() { return _con.back(); } private: Container _con; }; }
stack
cppnamespace wxx2 { // 适配器/配接器 // 容器适配器 template<class T, class Container = deque<T>> class stack { public: void push(const T& x) { _con.push_back(x); } void pop() { _con.pop_back(); } size_t size() const { return _con.size(); } bool empty() const { return _con.empty(); } const T& top() const { return _con.back(); } T& top() { return _con.back(); } private: Container _con; }; }
deque
cpp// 每块缓冲区大小 #define BLOCK_SIZE 8 template<typename T> class MyDeque { private: // 中控:保存每个数据块的起始地址 vector<T*> _map; // 当前首尾所在块、块内偏移 int _startBlock; int _startPos; int _endBlock; int _endPos; // 元素总数 size_t _size; // 申请一个数据块 T* allocBlock() { return new T[BLOCK_SIZE]; } // 扩容中控map void expandMap(bool front) { if (front) _map.insert(_map.begin(), allocBlock()); else _map.push_back(allocBlock()); } public: MyDeque() : _startBlock(0), _startPos(0), _endBlock(0), _endPos(0), _size(0) { _map.push_back(allocBlock()); } // 尾插 void push_back(const T& val) { // 当前块满了,新开块 if (_endPos == BLOCK_SIZE) { expandMap(false); _endBlock++; _endPos = 0; } _map[_endBlock][_endPos++] = val; _size++; } // 头插 void push_front(const T& val) { // 当前头块到头了 if (_startPos == 0) { expandMap(true); _startBlock++; // 中控前面加一块,起始块后移 _startPos = BLOCK_SIZE; } _map[_startBlock][--_startPos] = val; _size++; } // 尾删 void pop_back() { if (empty()) return; _endPos--; _size--; // 块删空,跳到前一块 if (_endPos == 0 && _endBlock > _startBlock) { _endBlock--; _endPos = BLOCK_SIZE; } } // 头删 void pop_front() { if (empty()) return; _startPos++; _size--; // 当前块走完,跳到下一块 if (_startPos == BLOCK_SIZE && _startBlock < _endBlock) { _startBlock++; _startPos = 0; } } // 随机访问 [] T& operator[](size_t idx) { // 计算相对于起始的总偏移 int total = _startPos + idx; int blockIdx = _startBlock + total / BLOCK_SIZE; int pos = total % BLOCK_SIZE; return _map[blockIdx][pos]; } size_t size() const { return _size; } bool empty() const { return _size == 0; } // 清空 void clear() { for (auto p : _map) delete[] p; _map.clear(); _map.push_back(allocBlock()); _startBlock = _endBlock = 0; _startPos = _endPos = 0; _size = 0; } // 析构 ~MyDeque() { for (auto p : _map) delete[] p; } };



