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


相关推荐
东风破_7 小时前
从输入 juejin.cn 到看到页面:DNS、Nginx、服务器集群与 CDN 到底在做什么?
后端·mysql
东风破_7 小时前
从 0 设计一个博客数据库:用户、头像与文章表应该怎么建?
后端·mysql
「QT(C++)开发工程师」8 小时前
C++ 11 常用for循环
开发语言·c++
我还记得那天8 小时前
0 初识C++
开发语言·c++
大鸡腿同学8 小时前
黄仁勋点透的 AI 真相:能实现你知道但做不到的,却实现不了你不知道的
后端
FfHUCisI8 小时前
Go 编译过程全景
开发语言·后端·golang
FfHUCisI8 小时前
Golang 语法分析与 AST:Parser 与 go/ast
开发语言·后端·golang
神奇霸王龙9 小时前
Cursor 3 + Claude Opus 4.8 屠榜:5 编程基座 IDE 卡位
ide·人工智能·ai·aigc·agent·ai编程·ai写作
「QT(C++)开发工程师」10 小时前
C++ std::move 详解
开发语言·c++