【C++】deque的设计思想

我们来深入解析 C++ STL 中 deque 的底层结构实现原理 ,它其实比 vectorlist 都更复杂一些,是一种 分段连续内存结构 + 中央控制器 的设计。

本文主要针对deque的设计思想进行讨论。


一、整体设计思想:分段数组 + 中控指针数组

deque ≠ 一块连续内存(像 vector),也 ≠ 单个链表(像 list),而是:

一段段固定大小的小数组 + 一个中央控制结构(map),指向这些数组块

组成:

  1. Map(中控器)
    • 是一个指针数组:T** map(其中每个 T* 指向一块缓冲区)
    • 记录了所有内存块的指针,每个块可以容纳固定个数的元素
    • map 本身也可能会动态扩容(不过不频繁)
  2. 缓冲区(Buffer)
    • 每个是固定大小(通常 512 字节)的数组,比如 T[64](元素个数视元素大小而定)
  3. 迭代器实现
    • 是四个指针:cur(当前元素)、first(当前缓冲区起点)、last(缓冲区终点)、node(当前块的指针位置)

二、可视化结构示意

css 复制代码
      +--------+--------+--------+--------+--------+
map → |  p0    |  p1    |  p2    |  p3    |  p4    |
      +--------+--------+--------+--------+--------+
          ↓        ↓        ↓        ↓        ↓
        [64]     [64]     [64]     [64]     [64]   ← 每块内存可容纳 64 个元素
  • 中控器 map 本身是一个指针数组,指向若干 固定大小的缓冲区
  • 所有元素分布在这些小数组里,整体上形成逻辑上的连续空间
  • deque 就是用这些拼起来模拟可双端扩展、快速随机访问的序列容器

⚙ 三、双端插入删除是如何实现的?

1. 插入尾部 push_back()

  • 如果尾部缓冲区还有空间:直接插入,O(1)
  • 如果缓冲区满了:
    • 分配一个新缓冲区
    • map 中尾部添加指向新块的指针
    • 插入新块的开头位置,O(1)

2. 插入头部 push_front()

  • 同理,如果头部缓冲区有空间:直接插入
  • 没有的话,分配新块,map 前部添加指针

所以双端插入效率为 O(1),除非 map 扩容


四、为什么 deque 可以随机访问?

虽然底层是分段数组,但 deque 重载了 operator[]

  • 它通过:

    cpp 复制代码
    index / block_size → 找到 map 中的块位置
    index % block_size → 找到块内偏移
  • 从而可以实现逻辑上的连续下标访问,时间复杂度为 O(1)

不像 list,每次都要顺着链表遍历


五、扩容机制与复杂度

map 扩容:

  • 如果 map 空间满了,需要:
    • 分配更大的 map
    • 将原来的指针复制过去(新 map 的中心区域)
  • 这个过程代价较大,但很少发生

与 vector 的区别:

扩容代价 vector deque
扩容是否搬移数据? 是(所有元素都要搬) 否(只扩容 map 或添加块)
是否复制原数据? 全部复制 指针级别复制即可
扩容频率 较高(尾插增长时) 较低(头尾满时 + map 满时)

六、迭代器实现细节

deque 的迭代器并不是简单的指针,而是一个复杂对象,内部结构大致如下(源码实现略简化):

cpp 复制代码
template<typename T>
struct deque_iterator {
    T* cur;       // 当前元素位置
    T* first;     // 当前块起始地址
    T* last;      // 当前块结束地址
    T** node;     // 指向当前 map 中的块指针
};

++it 时:

  • 如果 cur + 1 < last:直接移动到下一个元素
  • 否则跨块:node++,进入下一块,更新 first/last/cur

所以 deque 的迭代器不是普通指针,移动时要判断是否跨块


七、为什么 deque 不能保留迭代器稳定性?

因为:

  • 插入新块或 map 扩容后,原来 node 指针位置可能失效
  • 所以不如 list 稳定

总结

deque 是用 多个定长内存块 + 中控器 map 组成的逻辑连续结构,既支持随机访问,也支持头尾快速插入/删除,是一种折中的设计,牺牲部分性能换取了更强的通用性。


相关推荐
Re2751 小时前
揭秘索引的 “快”:从翻书到 B+ 树的效率革命
后端
界面开发小八哥2 小时前
MFC扩展库BCGControlBar Pro v36.2:MSAA和CodedUI测试升级
c++·mfc·bcg·界面控件
David爱编程2 小时前
Java 三目运算符完全指南:写法、坑点与最佳实践
java·后端
学习编程的小羊3 小时前
Spring Boot 全局异常处理与日志监控实战
java·spring boot·后端
Sword993 小时前
🎮 AI编程新时代:Trae×Three.js打造沉浸式3D魔方游戏
前端·ai编程·trae
hongweihao4 小时前
Cursor 不讲武德,我反手开通了Claude max试试能不能用 Claude code 代替它
ai编程·claude
LEAFF4 小时前
手把手教你使用Coze开发一个AI翻译应用
agent·ai编程·coze
Moonbit4 小时前
MoonBit 作者寄语 2025 级清华深圳新生
前端·后端·程序员
前端的阶梯4 小时前
开发一个支持支付功能的微信小程序的注意事项,含泪送上
前端·后端·全栈
CoderLiu4 小时前
AI提示词工程优化指南:8个技巧,释放大语言模型的全部潜力
前端·人工智能·ai编程