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


相关推荐
paopaokaka_luck39 分钟前
基于SpringBoot+Vue的汽车租赁系统(协同过滤算法、腾讯地图API、支付宝沙盒支付、WebsSocket实时聊天、ECharts图形化分析)
vue.js·spring boot·后端·websocket·算法·汽车·echarts
giao源1 小时前
Spring Boot 整合 Shiro 实现单用户与多用户认证授权指南
java·spring boot·后端·安全性测试
【本人】1 小时前
Django基础(四)———模板常用过滤器
后端·python·django
kyle~1 小时前
C++---cout、cerr、clog
开发语言·c++·算法
HHRL-yx2 小时前
C++网络编程 4.UDP套接字(socket)编程示例程序
网络·c++·udp
豌豆花下猫2 小时前
Python 潮流周刊#111:Django迎来 20 周年、OpenAI 前员工分享工作体验(摘要)
后端·python·ai
LaoZhangAI2 小时前
ComfyUI集成GPT-Image-1完全指南:8步实现AI图像创作革命【2025最新】
前端·后端
LaoZhangAI2 小时前
Cline + Gemini API 完整配置与使用指南【2025最新】
前端·后端