C++ STL:deque使用及源码剖析

  Deque是一种双向开口的连续线性空间。能在头尾两端分别做元素的插入和删除,而且是在常数的时间内完成。虽然Vector也可以在首端进行元素的插入和删除(利用insert和erase),但效率差(涉及到整个数组的移动),无法被接受。 

 

deque的定义

 #include <deque> // deque属于std命名域的,因此需要通过命名限定

deque<int> a; // 定义一个int类型的双端队列a

deque<int> a(10); // 定义一个int类型的双端队列a,并设置初始大小为10

deque<int> a(10, 1); // 定义一个int类型的双端队列a,并设置初始大小为10且初始值都为1

deque<int> b(a); // 定义并用双端队列a初始化双端队列b

deque<int> b(a.begin(), a.begin()+3); // 将双端队列a中从第0个到第2个(共3个)作为双端队列b的初始值

int n[] = { 1, 2, 3, 4, 5 };

deque<int> a(n, n + 5);

deque<int> a(&n[1], &n[4]); // 将n[1]、n[2]、n[3]作为双端队列a的初值

deque的使用 

操作

描述

deq.size()

返回容器中的元素数量。

deq.max_size()

返回容器可能存储的最大元素数量。

deq.resize(n)

改变容器的大小为 n 个元素。如果 n 大于当前大小,新位置会被默认插入元素进行填充。

deq.empty()

检查容器是否为空。

deq.push_front(const T& x)

在容器的头部插入一个元素 x

deq.push_back(const T& x)

在容器的末尾添加一个元素 x

deq.insert(iterator it, const T& x)

在迭代器 it 指定的位置插入一个元素 x

deq.insert(iterator it, int n, const T& x)

在迭代器 it 指定的位置插入 n 个相同的元素 x

deq.insert(iterator it, iterator first, iterator last)

在迭代器 it 指定的位置插入另一个容器的 [first, last) 区间的数据。

deq.pop_front()

删除容器头部的一个元素。

deq.pop_back()

删除容器末尾的一个元素。

deq.erase(iterator it)

删除迭代器 it 指定的元素。

deq.erase(iterator first, iterator last)

删除迭代器 [first, last) 区间的元素。

deq.clear()

删除容器中的所有元素。

deq[1]

通过下标访问元素,不检查越界。

deq.at(1)

通过 at 方法访问元素,如果越界会抛出异常。

deq.front()

访问第一个元素。

deq.back()

访问最后一个元素。

deq.assign(int nSize, const T& x)

nSize 个 x 值的副本替换容器中的内容。

swap(deque&)

交换两个同类型容器的元素。

deq.begin()

返回指向容器第一个元素的迭代器。

deq.end()

返回指向容器最后一个元素之后的迭代器。

deq.cbegin()

返回指向容器第一个元素的常量迭代器。

deq.cend()

返回指向容器最后一个元素之后的常量迭代器。

deq.rbegin()

返回指向容器最后一个元素的反向迭代器。

deq.rend()

返回指向容器第一个元素前面位置的反向迭代器。

#include <iostream>
#include <deque>int main() {std::deque<int> deq;// 头部添加元素deq.push_front(1);// 末尾添加元素deq.push_back(2);// 在任意位置插入一个元素auto it = deq.begin();deq.insert(it + 1, 3); // 在第一个元素之后插入3// 在任意位置插入 n 个相同的元素deq.insert(it, 2, 4); // 在头部插入两个4// 插入另一个deque的[first, last)间的数据std::deque<int> another_deq{5, 6, 7};deq.insert(deq.end(), another_deq.begin(), another_deq.end());// 头部删除元素deq.pop_front();// 末尾删除元素deq.pop_back();// 任意位置删除一个元素deq.erase(deq.begin() + 1); // 删除第二个元素// 删除[first, last)之间的元素deq.erase(deq.begin(), deq.begin() + 2); // 删除前两个元素// 清空所有元素deq.clear();// 多个元素赋值deq.assign(3, 8); // deq现在有三个元素,每个都是8// 交换两个同类型容器的元素std::deque<int> deq2;deq2.push_back(9);deq.swap(deq2);// 访问第一个元素和最后一个元素std::cout << "Front: " << deq.front() << ", Back: " << deq.back() << std::endl;// 使用迭代器遍历dequestd::cout << "Contents of deq:";for(auto iter = deq.begin(); iter != deq.end(); ++iter) {std::cout << ' ' << *iter;}std::cout << std::endl;// 使用下标访问if (!deq.empty()) {std::cout << "Element at index 0: " << deq[0] << std::endl;std::cout << "Element at index 0 (using at): " << deq.at(0) << std::endl;}// 使用反向迭代器遍历dequestd::cout << "Contents of deq in reverse:";for(auto riter = deq.rbegin(); riter != deq.rend(); ++riter) {std::cout << ' ' << *riter;}std::cout << std::endl;return 0;
}

 deque数据结构

deque维护一个指向map的指针外和 start和finish两个迭代器,分别指向第一缓冲区的第一个元素和最后缓冲区的最后一个元素的下一位置。

template <class T, class Alloc = alloc, size_t BufSiz = 0>
class deque {
public:typedef T value_type;typedef value_type* pointer;typedef size_t size_type;typedef pointer* map_pointer;
public:typedef __deque_iterator<T, T&, T*, BufSiz>  iterator;
protected:iterator start;    // 第一个节点iterator finish;   // 最后一个结点map_pointer map;size_type map_size;
public:iterator begin() { return start; }iterator end() { return finish; }reference operator[](size_type n) { return start[difference_type(n)]; } // 调用迭代器重载的operator[]// ...
}
  • 指针数组 (map):是一个指针数组,维护指向缓冲区的指针,这些缓冲区构成了 deque 的内存空间。map 左右两边预留有空间,允许前后扩展。
  • 迭代器 (start, finish):分别指向 deque 中第一个元素和最后一个元素之后的位置,通过迭代器可以快速访问或修改 deque 的元素。
  • map_size 表示 map 中指针的数量,即数据块的总数。
  • map 的左右两边留有剩余空间是 deque 设计的一个关键特点。这些剩余空间允许快速地在 deque 的前端或后端添加新的分段,而无需重新分配整个 map。这样,即使是在 deque 的两端插入或删除元素,操作的时间复杂度也能保持在常数时间内。
  • erase 和 insert 函数实现了元素的删除和插入操作,它们采用了不同的策略来最小化元素移动的开销。具体策略取决于操作位置相对于 deque 中间位置的不同,以减少需要移动的元素数量。

 当 deque 需要扩展其存储空间时,它会分配一个新的 map,这个新 map 有更多的指针,可以指向更多的缓冲区。然后,deque 会将现有的缓冲区指针从旧 map 复制到新 map 中,并适当地调整 start 和 finish 迭代器,以反映新的布局。这个过程使得 deque 能够在维持高效插入和删除操作的同时,提供对元素的快速随机访问。

当 deque 的一个分段被填满时,它会动态地添加一个新的分段来存储更多的元素。同样地,如果 deque 的大小减少,一些分段可能会被释放以节约空间。deque 动态调整其分段的能力,使得它在存储大量数据时非常灵活和高效。

 deque迭代器

    deque 使用特化的迭代器(__deque_iterator),以适应其复杂的内部结构。这个迭代器能够跨越不同的数据块,为外部提供连续访问的接口。

template <class T, class Ref, class Ptr, size_t BufSiz>
struct __deque_iterator {    //并未继承std::iterator,为了符合STL规范,要对五个相应类型都做定义typedef __deque_iterator<T, T&, T*, BufSiz>             iterator;typedef __deque_iterator<T, const T&, const T*, BufSiz> const_iterator;static size_t buffer_size() { return __deque_buf_size(BufSiz, sizeof(T)); }typedef random_access_iterator_tag iterator_category;    //1typedef T value_type;    //2typedef Ptr pointer;    //3typedef Ref reference;    //4typedef size_t size_type;typedef ptrdiff_t difference_type;    //5typedef T** map_pointer;typedef __deque_iterator self;//保持与空间的联结T* cur;        //所指是当前缓冲区里的当前元素T* first;    //所指是当前缓冲区里的头元素T* last;    //所指是当前缓冲区里的尾元素的下一位(含备用空间)map_pointer node;    //指向map的头...
}

__deque_iterator 结构体

类型定义

  • 迭代器对外提供了与 STL 兼容的五种类型定义,包括迭代器类别(iterator_category),值类型(value_type),指针类型(pointer),引用类型(reference),以及差异类型(difference_type)。
  • 静态成员函数 buffer_size计算并返回每个缓冲区(即段)可以容纳的元素数量。
  • cur:指向当前缓冲区中当前元素的指针。
  • first:指向当前缓冲区第一个元素的指针。
  • last:指向当前缓冲区最后一个元素之后位置的指针,即标记缓冲区结束的边界。
  • node:指向当前缓冲区所在 map 中的指针,即指向管理当前缓冲区指针的 map 元素。

   当迭代器前进(++)或后退(--)时,它首先检查是否即将超出当前缓冲区的边界(通过比较 cur 与 first 或 last)。如果是这种情况,迭代器会跳转到相邻的缓冲区(通过修改 node 指向的指针),并相应地更新 cur、first 和 last,以指向新缓冲区的正确位置。

   这种设计使得迭代器能够平滑地跨越 deque 的分段存储,对使用者隐藏了复杂的内部结构。用户可以像操作一个连续存储的数组那样,使用 deque 迭代器进行随机访问和遍历,而不需要关心背后的分段逻辑。迭代器抽象了 deque 的分段存储细节,使得从用户的角度看,deque 像是一个完全连续的数据结构。

参考

《STL源码剖析》

https://www.cnblogs.com/linuxAndMcu/p/10260124.html

https://www.cnblogs.com/MisakiJH/p/11765567.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/687689.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

JS进阶——JS闭包

JavaScript 闭包 (w3school.com.cn) JavaScript中的闭包&#xff08;Closure&#xff09;是一个非常重要的概念&#xff0c;它涉及到函数作用域和变量引用的深入理解。 闭包的形成主要依赖于两个特性&#xff1a;函数嵌套和函数内部的变量引用。当一个内部函数引用了其外部函…

【Day44】代码随想录之动态规划完全背包_518. 零钱兑换 II_377. 组合总和 Ⅳ

文章目录 动态规划理论基础动规五部曲&#xff1a;出现结果不正确&#xff1a; 518. 零钱兑换 II377. 组合总和 Ⅳ 动态规划理论基础 动规五部曲&#xff1a; 确定dp数组 下标及dp[i] 的含义。递推公式&#xff1a;比如斐波那契数列 dp[i] dp[i-1] dp[i-2]。初始化dp数组。…

代码随想录 Leetcode56. 合并区间

题目&#xff1a; 代码(首刷自解 2024年2月18日&#xff09;&#xff1a; 这题与气球扎针&#xff0c;删除重复的大体逻辑相似。需要额外定义些变量来存储头尾 class Solution { private:const static bool cmp(vector<int>& a, vector<int>& b) {return …

001 QGIS介绍

Quantum GIS&#xff08;QGIS&#xff09;是开源地理信息系统桌面软件&#xff0c;使用GNU&#xff08;General Public License&#xff09;授权&#xff0c; 属于 Open Source eospatial Foundation&#xff08;OSGeo&#xff09;的官方计划。在 GNU 授权下&#xff0c;开发者…

Postman路径修改

默认安装好Postman之后&#xff0c;默认路径在&#xff1a;C:\Users\用户名\AppData\Local\Postman。 修改路径只需要将整个文件夹拷贝到需要移动的位置即可&#xff0c;然后重新创建一个快捷方式。再删除原来路径的文件夹。

使用消息中间件实现系统间的异步通信和解耦

​​​​​​​目录 引言 一. 选择合适的消息中间件 二. 定义消息格式和通信协议 1. 定义消息格式 消息头 消息体 2. 定义通信协议 发送消息 接收消息 消息处理 3. 示例代码 定义消息格式 发送消息 接收消息 三、发布-订阅模式 1. 定义发布-订阅模式 2. 示例代…

C++ //练习 7.29 修改你的Screen类,令move、set和display函数返回Screen并检查程序的运行结果,在上一个练习中你的推测正确吗?

C Primer&#xff08;第5版&#xff09; 练习 7.29 练习 7.29 修改你的Screen类&#xff0c;令move、set和display函数返回Screen并检查程序的运行结果&#xff0c;在上一个练习中你的推测正确吗&#xff1f; 环境&#xff1a;Linux Ubuntu&#xff08;云服务器&#xff09; …

RIP协议详解

​RIP是最早的动态路由协议&#xff0c;虽然已经过时并且很少使用&#xff0c;但是可以通过学习RIP并且和ospf等现在正在使用的路由协议对比&#xff0c;了解其工作原理和过时原因&#xff0c;具有很强的学习性。 一、RIP协议简介 RIP&#xff08;Routing Information Protoc…

(OpenCV)图片拼接

前言 图片拼接在许多领域都有广泛的应用&#xff0c;包括但不限于以下几个方面&#xff1a; 全景摄影&#xff1a;在摄影中&#xff0c;通过将多张照片拼接在一起可以实现全景照片的效果。这在旅游景点、房地产展示等领域有着广泛的应用&#xff0c;能够提供更加生动、真实的视…

Bpmn-js 属性控制

我们可以通过bpmn-js来访问对应的BPMN图例的属性信息。对应的流程图中的每个图例元素&#xff08;如开始、结束、中间/边界事件等都通过businessObject属性存储对基础BPMN元素的引用。业务对象是从BPMN 2.0 XML导入并在导出过程中序列化的实际元素。使用业务对象来读取和写入BP…

如何减少HTTP请求次数

资料来源 : 小林coding 小林官方网站 : 小林coding (xiaolincoding.com) 如何减少HTTP请求次数? 减少 HTTP 请求次数自然也就提升了 HTTP 性能&#xff0c;可以从这 3 个方面入手: 减少重定向请求次数合并请求延迟发送请求 减少重定向请求次数 我们先来看看什么是重定向请…

美相关 APT 组织分析报告

获取方式&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/1AsysdggUIbvB3PZ41MaJaQ?pwd8euh 提取码&#xff1a;8euh

Debug Monitor中断详细解析

文章目录 0 基本术语1 相关寄存器和指令1.1 Debug Halting Control and Status Register (DHCSR), 0xE000EDF01.2 Debug Exception and Monitor Control Register (DEMCR), 0xE000EDFC1.3 Debug Fault Status Register, DFSR, 0xE000ED301.4 BKPT指令 2 Debug Monitor中断示例2…

DNS域名解析过程、工具、文件配置

目录 DNS介绍 DNS域名层次结构 DNS域名解析过程 递归查询和迭代查询 DNS 查询的命令行工具&#xff1a;host、dig、nslookup host 语法 参数和选项 示例用法 dig 语法 参数和选项 示例用法 nslookup 语法 参数和选项 交互式命令 示例用法 配置 DNS 客户端 DNS介…

解读OpenAI视频生成模型Sora背后的原理:Diffusion Transformer

Diffusion Models视频生成-博客汇总 前言&#xff1a;OpenAI最近推出的视频生成模型Sora在效果上实现了真正的遥遥领先&#xff0c;很多博主都介绍过Sora&#xff0c;但是深入解读背后原理的博客却非常少。Sora的原理最主要的是核心模型主干《Scalable Diffusion Models with T…

Code Composer Studio (CCS) - Breakpoint (断点)

Code Composer Studio [CCS] - Breakpoint [断点] 1. BreakpointReferences 1. Breakpoint 选中断点右键 -> Breakpoint Properties… Skip Count&#xff1a;跳过断点总数&#xff0c;在断点执行之前设置总数 Current Count&#xff1a;当前跳过断电累计值 References […

xtu oj 1215 A+B V

题目描述 小明很喜欢做ab&#xff0c;他但经常忘记进位&#xff0c;所以他算881290,而不是100。 现在你给了小明一些ab的算式&#xff0c;请问他算出来会是什么&#xff1f; 输入 第一行是一个整数K&#xff0c;表示样例的个数。 每个样例占一行&#xff0c;为两个整数a,b&a…

CCF编程能力等级认证GESP—C++7级—20231209

CCF编程能力等级认证GESP—C7级—20231209 单选题&#xff08;每题 2 分&#xff0c;共 30 分&#xff09;判断题&#xff08;每题 2 分&#xff0c;共 20 分&#xff09;编程题 (每题 25 分&#xff0c;共 50 分)商品交易纸牌游戏 答案及解析单选题判断题编程题1编程题2 单选题…

深度探索Python集合:从基本操作到高级用法

在Python编程中&#xff0c;集合(Set)作为一种高效且功能强大的内置数据结构&#xff0c;常用于处理不包含重复元素的无序数据集合。本文将详述Python集合的基本操作、进阶技巧以及在实际场景中的应用。 一、集合基础 Python集合初始化可通过大括号 {} 或者 set() 函数实现&a…

Vue实现多个input输入,光标自动聚焦到下一个input

遇到一个需求&#xff0c;需要实现和移动端短信输入一样&#xff0c;输入内容后&#xff0c;光标会进入下一个输入框 需要用到2个事件 keydown事件发生在键盘的键被按下的时候 keyup 事件在按键被释放的时候触发 <template><div class"box"><el-fo…