乐途乐途
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
  • C++ 学习路径
  • 第1章 C++概述与开发环境

    • C++ 语言概述与编译模型
    • 第一个 C++ 程序与规范
    • 开发环境配置
    • 预处理指令详解
  • 第2章 基本语法与类型系统

    • 变量与基本类型
    • 类型转换
    • 枚举类型
    • 引用与指针
    • 数组与 stdarray
    • 字符串与原始字符串字面量
    • const 与 constexpr
    • nullptr 与空指针
    • 类型推导 auto 与 decltype
    • 基于范围的 for 循环
    • 值类别全面解析
    • char16_t 与 char32_t
    • static_assert 编译期断言
  • 第3章 函数与重载

    • 函数声明与定义
    • 函数重载
    • 默认参数与内联函数
    • Lambda 表达式
    • 函数对象与 stdfunction
    • 后置返回类型与 noexcept
  • 第4章 类与对象

    • 类的基本定义
    • 构造函数与析构函数
    • 拷贝控制
    • 移动构造函数与移动赋值
    • 列表初始化与类内初始化器
    • 静态成员与嵌套类
    • 友元
    • =default 与 =delete
  • 第5章 继承与多态

    • 继承基础
    • 虚函数与多态
    • 虚函数表与动态绑定原理
    • 虚析构函数
    • 抽象类与纯虚函数
    • 多重继承与虚继承
    • 继承构造函数
  • 第6章 运算符重载

    • 运算符重载基础
    • 算术与关系运算符重载
    • 赋值与移动运算符重载
    • 特殊运算符重载
  • 第7章 模板与泛型编程

    • 函数模板
    • 类模板
    • 模板特化与偏特化
    • 可变参数模板
    • 别名模板与模板模板参数
    • SFINAE 与类型萃取
    • 依赖名与 typename/template 关键字
  • 第8章 异常处理

    • 异常处理机制
    • noexcept 与异常安全
    • RAII 与异常安全实践
  • 第9章 内存管理与智能指针

    • 动态内存与内存分区
    • RAII 惯用法
    • unique_ptr
    • shared_ptr 与 weak_ptr
    • 内存管理最佳实践
  • 第10章 右值引用与移动语义

    • 右值引用与值类别深度解析
    • std::move 原理与使用
    • 完美转发与 std::forward
    • 移动语义性能对比与最佳实践
  • 第11章 STL容器

    • vector 深度剖析
    • deque 内部机制
    • list 与 forward_list
    • map 与 set 深度解析
    • unordered 容器与哈希原理
    • array 与 tuple
    • 容器适配器
    • 容器选择全景指南
  • 第12章 STL算法与迭代器

    • 迭代器体系全解
    • 非变异算法
    • 变异算法
    • 排序与二分算法
    • Lambda 与算法组合
    • std::random 随机数库
    • 自定义迭代器开发
  • 第13章 IO流与文件

    • 标准 IO 流
    • 格式化输出控制
    • 文件流操作
    • 字符串流
    • std::regex 正则表达式
  • 第14章 并发与多线程

    • thread 基础与线程管理
    • mutex 与 lock_guard
    • unique_lock 与 condition_variable
    • thread_local 线程局部存储
    • atomic 与内存序
    • future 与 async 异步编程
    • std::chrono 时间库
  • 第15章 现代C++新特性

    • 从 C++11 到 C++20 演进路线
    • C++14 关键新特性
    • C++17 关键新特性
    • C++20 核心特性速览
  • 第16章 面试考点与最佳实践

    • C++ 综合最佳实践清单
    • 高频面试题精讲
    • 多线程面试题与实战
    • 内存管理常见陷阱与排查
  • 附录

    • C++ 核心知识点
    • C++ 专业术语

deque 内部机制

定义与作用

std::deque(double-ended queue,双端队列)是一种分段数组——元素并非连续存储在一块内存中,而是分布在多个固定大小的块中,通过中控器(map of pointers)管理。支持 O(1) 两端增删和 O(1) 随机访问。

#include <deque>
std::deque<int> dq{1, 2, 3};
dq.push_front(0);   // O(1) 头部插入
dq.push_back(4);    // O(1) 尾部插入
int x = dq[2];      // O(1) 随机访问(两次间接跳转)
对比vectordeque
内存布局连续单块分段多块
头部增删O(n)O(1)
尾部增删摊销 O(1)O(1)
随机访问O(1) 直接O(1) 两次跳转
扩容行为整块重分配+移动分配新块,中控器扩容
迭代器失效扩容时全失效仅被操作块附近失效

核心原理

分段数组结构

典型块大小:GCC 512 字节(64 个 int),MSVC 16 字节或元素大小。

push_front 与 push_back 过程

完整示例

示例一:飞翔科技任务调度队列

场景说明:白歌用 deque 实现飞翔科技的动态任务调度器,新紧急任务插入前端,普通任务追加尾端。

#include <iostream>
#include <deque>
#include <string>

struct Task {
    int id;
    std::string name;
    int priority;  // 值越大越紧急

    Task(int i, std::string n, int p)
        : id(i), name(std::move(n)), priority(p) {}
};

class TaskScheduler {
public:
    // 紧急任务:插入前端
    void addUrgent(Task t) {
        tasks_.push_front(std::move(t));
        std::cout << "[紧急] 任务#" << tasks_.front().id
                  << " 插入队首\n";
    }

    // 普通任务:追加末端
    void addNormal(Task t) {
        tasks_.push_back(std::move(t));
        std::cout << "[普通] 任务#" << tasks_.back().id
                  << " 追加队尾\n";
    }

    // 取最高优先级任务(简单遍历)
    Task popHighest() {
        auto best = tasks_.begin();
        for (auto it = tasks_.begin(); it != tasks_.end(); ++it) {
            if (it->priority > best->priority) best = it;
        }
        Task result = std::move(*best);
        tasks_.erase(best);
        return result;
    }

    void print() const {
        std::cout << "队列 [";
        for (size_t i = 0; i < tasks_.size(); ++i)
            std::cout << tasks_[i].name << "|";
        std::cout << "]\n";
    }

    size_t size() const { return tasks_.size(); }

private:
    std::deque<Task> tasks_;
};

int main() {
    TaskScheduler scheduler;

    scheduler.addNormal({1, "飞翔科技-日报统计", 2});
    scheduler.addNormal({2, "飞翔科技-日志归档", 1});
    scheduler.addUrgent({3, "飞翔科技-支付异常-紧急", 9});
    scheduler.addNormal({4, "飞翔科技-缓存预热", 3});
    scheduler.addUrgent({5, "飞翔科技-数据库连接池耗尽", 10});

    scheduler.print();
    std::cout << "队列大小: " << scheduler.size() << "\n";

    // 按优先级弹出
    std::cout << "\n--- 按优先级执行 ---\n";
    while (scheduler.size() > 0) {
        Task next = scheduler.popHighest();
        std::cout << "执行: " << next.name
                  << " (优先级: " << next.priority << ")\n";
    }
}

预期输出:

[普通] 任务#1 追加队尾
[普通] 任务#2 追加队尾
[紧急] 任务#3 插入队首
[普通] 任务#4 追加队尾
[紧急] 任务#5 插入队首
队列 [飞翔科技-数据库连接池耗尽|飞翔科技-支付异常-紧急|飞翔科技-日报统计|飞翔科技-日志归档|飞翔科技-缓存预热|]
队列大小: 5

--- 按优先级执行 ---
执行: 飞翔科技-数据库连接池耗尽 (优先级: 10)
执行: 飞翔科技-支付异常-紧急 (优先级: 9)
执行: 飞翔科技-缓存预热 (优先级: 3)
执行: 飞翔科技-日报统计 (优先级: 2)
执行: 飞翔科技-日志归档 (优先级: 1)

逐段分析:

  • push_front / push_back 都 O(1),deque 天生适合双端操作
  • 随机访问 tasks_[i] 支持 O(1) 遍历
  • 与 vector 不同:deque 在两端增删不导致已有元素移动

示例二:deque vs vector 前端插入性能对比

场景说明:小崔在代码评审中因使用 vector 做前端插入被白歌指出性能问题。

#include <iostream>
#include <chrono>
#include <deque>
#include <vector>

int main() {
    const int N = 100000;

    // vector 前端插入:O(n) 每次
    {
        auto start = std::chrono::high_resolution_clock::now();
        std::vector<int> v;
        for (int i = 0; i < N; ++i)
            v.insert(v.begin(), i);  // 每次都移动所有元素
        auto elapsed = std::chrono::duration<double, std::milli>(
            std::chrono::high_resolution_clock::now() - start).count();
        std::cout << "vector push_front x " << N << ": "
                  << elapsed << " ms\n";
    }

    // deque 前端插入:O(1) 每次
    {
        auto start = std::chrono::high_resolution_clock::now();
        std::deque<int> dq;
        for (int i = 0; i < N; ++i)
            dq.push_front(i);  // O(1)
        auto elapsed = std::chrono::duration<double, std::milli>(
            std::chrono::high_resolution_clock::now() - start).count();
        std::cout << "deque  push_front x " << N << ": "
                  << elapsed << " ms\n";
    }
}

预期输出(典型):

vector push_front x 100000: 452.3 ms
deque  push_front x 100000: 1.2 ms

逐段分析:

  • vector 前端插入:每次 O(n) 移动 → 总 O(n²) ≈ 50 亿次元素移动
  • deque 前端插入:每次 O(1) 分配或定位 → 总 O(n)
  • 性能差距约 400 倍

易错场景与面试考点

易错场景

1. deque 随机访问比 vector 慢

// deque 的 operator[] 需要两次间接跳转(中控器 → 块)
// 遍历大量数据时 vector 的缓存局部性更好

2. 插入/删除中间元素

// deque 在中间插入元素时,会选择移动较少元素的一侧
// 但依然 O(n),不如 list 的 O(1)

3. 迭代器失效

std::deque<int> dq{1, 2, 3, 4, 5};
auto it = dq.begin() + 2;
dq.push_front(0);  // 可能导致所有迭代器失效(取决于实现)
dq.pop_back();     // 可能导致 end() 附近失效

面试考点

考点要点
内存结构分段数组 + 中控器,非连续存储
vs vector前端 O(1) vs O(n),随机访问更慢
块大小GCC 512B,MSVC 16B 或元素大小
push_frontdeque 独有,vector 无
迭代器类型随机访问迭代器(但比 vector 复杂)
适用场景双端增删为主、不宜大量随机遍历
上一页
vector 深度剖析
下一页
list 与 forward_list