【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 组成的逻辑连续结构,既支持随机访问,也支持头尾快速插入/删除,是一种折中的设计,牺牲部分性能换取了更强的通用性。


相关推荐
bkspiderx2 小时前
C++经典的数据结构与算法之经典算法思想:贪心算法(Greedy)
数据结构·c++·算法·贪心算法
thinktik2 小时前
还在手把手教AI写代码么? 让你的AWS Kiro AI IDE直接读飞书需求文档给你打工吧!
后端·serverless·aws
郝学胜-神的一滴3 小时前
避免使用非const全局变量:C++中的最佳实践 (C++ Core Guidelines)
开发语言·c++·程序人生
老青蛙5 小时前
权限系统设计-用户设计
后端
晚云与城5 小时前
今日分享:C++ Stack和queue(栈与队列)
开发语言·c++
echoyu.5 小时前
消息队列-初识kafka
java·分布式·后端·spring cloud·中间件·架构·kafka
yuluo_YX5 小时前
Go Style 代码风格规范
开发语言·后端·golang
量子位5 小时前
Hinton万万没想到,前女友用ChatGPT跟他闹分手
chatgpt·ai编程
David爱编程5 小时前
从 JVM 到内核:synchronized 与操作系统互斥量的深度联系
java·后端
bikong75 小时前
一种高效绘制余晖波形的方法Qt/C++
数据库·c++·qt