【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 小时前
C++ 标准库排序算法 std::sort 使用详解
开发语言·c++·排序算法
青柠编程9 小时前
基于Spring Boot的选课管理系统架构设计
java·spring boot·后端
湫兮之风9 小时前
C++: Lambda表达式详解(从入门到深入)
开发语言·c++
奔跑吧邓邓子9 小时前
【C++实战(54)】C++11新特性实战:解锁原子操作与异步编程的奥秘
c++·实战·c++11新特性·原子操作·异步编程
Mr_WangAndy9 小时前
C++设计模式_结构型模式_适配器模式Adapter
c++·设计模式·适配器模式·c++设计模式
bkspiderx9 小时前
C++设计模式之结构型模式:代理模式(Proxy)
c++·设计模式·代理模式
RainbowSea10 小时前
9. Spring AI 当中对应 MCP 的操作
java·spring·ai编程
RainbowSea10 小时前
10. Spring AI + RAG
java·spring·ai编程
zzywxc78711 小时前
AI 开发工具全景指南:从编码辅助到模型部署的全流程实践
大数据·人工智能·低代码·机器学习·golang·自动化·ai编程
bkspiderx11 小时前
C++设计模式之行为型模式:解释器模式(Interpreter)
c++·设计模式·解释器模式