【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的优点,而完美的避开了其缺陷。


相关文章
|
10月前
|
存储 缓存 C++
C++ 容器全面剖析:掌握 STL 的奥秘,从入门到高效编程
C++ 标准模板库(STL)提供了一组功能强大的容器类,用于存储和操作数据集合。不同的容器具有独特的特性和应用场景,因此选择合适的容器对于程序的性能和代码的可读性至关重要。对于刚接触 C++ 的开发者来说,了解这些容器的基础知识以及它们的特点是迈向高效编程的重要一步。本文将详细介绍 C++ 常用的容器,包括序列容器(`std::vector`、`std::array`、`std::list`、`std::deque`)、关联容器(`std::set`、`std::map`)和无序容器(`std::unordered_set`、`std::unordered_map`),全面解析它们的特点、用法
C++ 容器全面剖析:掌握 STL 的奥秘,从入门到高效编程
|
存储 搜索推荐 C++
【C++篇】深度剖析C++ STL:玩转 list 容器,解锁高效编程的秘密武器2
【C++篇】深度剖析C++ STL:玩转 list 容器,解锁高效编程的秘密武器
246 9
【C++篇】深度剖析C++ STL:玩转 list 容器,解锁高效编程的秘密武器2
|
存储 C++ 容器
【C++篇】深度剖析C++ STL:玩转 list 容器,解锁高效编程的秘密武器1
【C++篇】深度剖析C++ STL:玩转 list 容器,解锁高效编程的秘密武器
253 5
|
存储 编译器 C++
【C++篇】揭开 C++ STL list 容器的神秘面纱:从底层设计到高效应用的全景解析(附源码)
【C++篇】揭开 C++ STL list 容器的神秘面纱:从底层设计到高效应用的全景解析(附源码)
246 2
|
存储 设计模式 C++
【C++】优先级队列(容器适配器)
本文介绍了C++ STL中的线性容器及其适配器,包括栈、队列和优先队列的设计与实现。详细解析了`deque`的特点和存储结构,以及如何利用`deque`实现栈、队列和优先队列。通过自定义命名空间和类模板,展示了如何模拟实现这些容器适配器,重点讲解了优先队列的内部机制,如堆的构建与维护方法。
186 0
|
存储 C++ 容器
C++番外篇——stack、queue的实现及deque的介绍
C++番外篇——stack、queue的实现及deque的介绍
167 0
|
10月前
|
编译器 C++ 开发者
【C++篇】深度解析类与对象(下)
在上一篇博客中,我们学习了C++的基础类与对象概念,包括类的定义、对象的使用和构造函数的作用。在这一篇,我们将深入探讨C++类的一些重要特性,如构造函数的高级用法、类型转换、static成员、友元、内部类、匿名对象,以及对象拷贝优化等。这些内容可以帮助你更好地理解和应用面向对象编程的核心理念,提升代码的健壮性、灵活性和可维护性。
|
6月前
|
人工智能 机器人 编译器
c++模板初阶----函数模板与类模板
class 类模板名private://类内成员声明class Apublic:A(T val):a(val){}private:T a;return 0;运行结果:注意:类模板中的成员函数若是放在类外定义时,需要加模板参数列表。return 0;
191 0
|
6月前
|
存储 编译器 程序员
c++的类(附含explicit关键字,友元,内部类)
本文介绍了C++中类的核心概念与用法,涵盖封装、继承、多态三大特性。重点讲解了类的定义(`class`与`struct`)、访问限定符(`private`、`public`、`protected`)、类的作用域及成员函数的声明与定义分离。同时深入探讨了类的大小计算、`this`指针、默认成员函数(构造函数、析构函数、拷贝构造、赋值重载)以及运算符重载等内容。 文章还详细分析了`explicit`关键字的作用、静态成员(变量与函数)、友元(友元函数与友元类)的概念及其使用场景,并简要介绍了内部类的特性。
290 0
|
8月前
|
编译器 C++ 容器
【c++11】c++11新特性(上)(列表初始化、右值引用和移动语义、类的新默认成员函数、lambda表达式)
C++11为C++带来了革命性变化,引入了列表初始化、右值引用、移动语义、类的新默认成员函数和lambda表达式等特性。列表初始化统一了对象初始化方式,initializer_list简化了容器多元素初始化;右值引用和移动语义优化了资源管理,减少拷贝开销;类新增移动构造和移动赋值函数提升性能;lambda表达式提供匿名函数对象,增强代码简洁性和灵活性。这些特性共同推动了现代C++编程的发展,提升了开发效率与程序性能。
342 12