【C++】-- STL容器适配器之底层deque浅析

简介: 【C++】-- STL容器适配器之底层deque浅析

一、deque的使用

【C++】-- STL容器适配器之stack一文中介绍了容器适配器的概念,容器适配器是一个封装了序列容器的类模板,对容器进行了转换,转换成栈的后进先出和队列的先进先出的等模板。

虽然stack和queue中也可以存放元素,但在STL中并没有将其划分在容器的行列,而是将其称为容器适配器,这是因为stack和队列只是对其他容器的接口进行了包装,STL中stack和queue默认使用deque作为容器:

1. class template
2. std::stack
3. template <class T, class Container = deque<T> > class stack;
1. class template
2. std::queue
3. template <class T, class Container = deque<T> > class queue;

那么deque到底是什么呢?有什么优势使得stack和queue都以它作为底层默认容器,对它进行包装,让stack和queue变成容器适配器的呢?

二、deque的原理

1.deque的结构

deque作为双端队列,是一种双开口的连续空间的数据结构 ,双开口即可以在头尾两端进行插入和删除,并且时间复杂度为O(1),有没有发现结合了vector和list的优点:

(1)与vector相比,头插O(1),不需要挪动元素

(2)与list相比,连续存储,空间利用率高

2.deque的底层结构

(1) deque底层空间

deque并不是真正连续的空间,而是由一段段连续的小空间拼接而成,实际deque类似于一个动态的二维数组,其底层结构如下:

中控指针数组中存放的是指向缓冲区buffer的指针,是动态开辟的二维数组,先malloc一个指针数组,指针数组的每个位置存放一维数组buffer的地址。当deque不断开buffer时,map中控指针数组满了,那么会增容,不过map增容的代价非常低,因为只需要拷贝存储数据的buffer数组的指针,不需要拷贝buffer中的内容。

如果要计算要访问的元素在第几个buffer里面,每个buffer固定大小,i/buffer.size()+1算出在第几个buffer中,i%buffer.size()算出是buffer中的第几个元素。

(2)deque如何支持随机访问

双端队列底层是一段假象的连续空间,实际是分段连续的,为了维护其“整体连续”以及随机访问的假象,落在了deque的迭代器身上,因此deque的迭代器设计就比较复杂:

(3)deque迭代器

那deque是如何借助其迭代器维护其假想连续的结构呢? 如果一个buffer走完了,如何走到下一个buffer的位置呢?用node++来控制,node反向指向map当中当前buffer的位置,那么node++就指向下一个node的位置,解引用就是下一个buffer的位置。

node并不是从第一个位置就开始存放的,从中间开始存放,那么头插就还有空余位置。

3.deque的缺点

deque适合头尾插入删除,不适合随机访问,不适合大量中间插入删除,由于它结合了vector和list的特性

所以其优缺点也跟vector和list相关。

(1)deque的优势

① 与vector比较:头部插入和删除时,不需要搬移元素,效率特别高,而且在扩容时,也不需要搬移大量的元素,因此其效率是必vector高的。

② 与list比较:其底层是连续空间,空间利用率比较高,不需要存储额外字段。

(2)deque致命缺陷

不适合遍历,因为在遍历时,deque的迭代器要频繁的去检测其是否移动到某段小空间的边界,导致效率低下,而序列式场景中,可能需要经常遍历,因此在实际中,需要线性结构时,大多数情况下优先考虑vector和list,deque的应用并不多,不适合大量的头部和中间插入删除,也不适合大量的随机访问。而目前能看到的一个应用就是,STL用其作为stack和queue的底层数据结构。

4.deque作为stack和queue的底层默认容器的原因

stack是一种后进先出的特殊线性数据结构,因此只要具有push_back()和pop_back()操作的线性结构,都可 以作为stack的底层容器,比如vector和list都可以;queue是先进先出的特殊线性数据结构,只要具有 push_back和pop_front操作的线性结构,都可以作为queue的底层容器,比如list。但是STL中对stack和 queue默认选择deque作为其底层容器,主要是因为:

(1)stack和queue不需要遍历(因此stack和queue没有迭代器),只需要在固定的一端或者两端进行操作。

(2)在stack中元素增长时,deque比vector的效率高(扩容时不需要搬移大量数据);queue中的元素增长时,使用deque作为底层默认容器,不仅效率高,而且内存使用率高。

stack和queue结合了deque的优点,而完美的避开了其缺陷。


相关文章
|
6月前
|
缓存 算法 程序员
C++STL底层原理:探秘标准模板库的内部机制
🌟蒋星熠Jaxonic带你深入STL底层:从容器内存管理到红黑树、哈希表,剖析迭代器、算法与分配器核心机制,揭秘C++标准库的高效设计哲学与性能优化实践。
C++STL底层原理:探秘标准模板库的内部机制
|
编译器 C++ 容器
【c++丨STL】基于红黑树模拟实现set和map(附源码)
本文基于红黑树的实现,模拟了STL中的`set`和`map`容器。通过封装同一棵红黑树并进行适配修改,实现了两种容器的功能。主要步骤包括:1) 修改红黑树节点结构以支持不同数据类型;2) 使用仿函数适配键值比较逻辑;3) 实现双向迭代器支持遍历操作;4) 封装`insert`、`find`等接口,并为`map`实现`operator[]`。最终,通过测试代码验证了功能的正确性。此实现减少了代码冗余,展示了模板与仿函数的强大灵活性。
342 2
|
存储 算法 C++
【c++丨STL】map/multimap的使用
本文详细介绍了STL关联式容器中的`map`和`multimap`的使用方法。`map`基于红黑树实现,内部元素按键自动升序排列,存储键值对,支持通过键访问或修改值;而`multimap`允许存在重复键。文章从构造函数、迭代器、容量接口、元素访问接口、增删操作到其他操作接口全面解析了`map`的功能,并通过实例演示了如何用`map`统计字符串数组中各元素的出现次数。最后对比了`map`与`set`的区别,强调了`map`在处理键值关系时的优势。
692 73
|
存储 算法 C++
【c++丨STL】set/multiset的使用
本文深入解析了STL中的`set`和`multiset`容器,二者均为关联式容器,底层基于红黑树实现。`set`支持唯一性元素存储并自动排序,适用于高效查找场景;`multiset`允许重复元素。两者均具备O(logN)的插入、删除与查找复杂度。文章详细介绍了构造函数、迭代器、容量接口、增删操作(如`insert`、`erase`)、查找统计(如`find`、`count`)及`multiset`特有的区间操作(如`lower_bound`、`upper_bound`、`equal_range`)。最后预告了`map`容器的学习,其作为键值对存储的关联式容器,同样基于红黑树,具有高效操作特性。
555 3
|
8月前
|
Kubernetes Docker Python
Docker 与 Kubernetes 容器化部署核心技术及企业级应用实践全方案解析
本文详解Docker与Kubernetes容器化技术,涵盖概念原理、环境搭建、镜像构建、应用部署及监控扩展,助你掌握企业级容器化方案,提升应用开发与运维效率。
1140 108
|
9月前
|
存储 监控 测试技术
如何将现有的应用程序迁移到Docker容器中?
如何将现有的应用程序迁移到Docker容器中?
671 57
|
6月前
|
监控 Kubernetes 安全
还没搞懂Docker? Docker容器技术实战指南 ! 从入门到企业级应用 !
蒋星熠Jaxonic,技术探索者,以代码为笔,在二进制星河中书写极客诗篇。专注Docker与容器化实践,分享从入门到企业级应用的深度经验,助力开发者乘风破浪,驶向云原生新世界。
706 51
还没搞懂Docker? Docker容器技术实战指南 ! 从入门到企业级应用 !