STL容器(libstdc++11.4版本) --- deque

149 阅读6分钟

介绍

deque是一个双端队列, 是一个顺序容器,允许在开头和结尾快速插入和删除.此外,在deque的两端插入和删除永远不会使指向其余元素的指针或引用失效.
deque的存储会根据需要自动扩展和收缩.扩展deque要比std::vector扩容快速,因为它不涉及将现有元素复制到新的内存位置.
deque常见操作的时间复杂度如下:

  • 随机存储 - 常数O(1)
  • 在首尾插入或删除元素 - 常数O(1)
  • 在其他位置插入或删除元素 - 线性O(n)

这次使用一行一行代码讲解,会比较啰嗦.但是对不熟悉的人来说比较友好一点.也是为了给未来自己复习一下

1. deque的核心原理图

deque的底层要比vector复杂那么一丢丢 image.png

文字描述

  • 创建队列的时候预先创建一个主控map.它是一个二级指针,每个元素是一个指针类型.在源码中被称为node(也叫缓冲区).在创建主控map的时候,也会创建node(缓冲区),队列实际操作就是这个node(缓冲区)
  • 底层几个核心成员变量分别是:
  1. _M_map: 二级指针,指向主控map
  2. _M_map_size: 主控map的大小
  3. _M_start: 队头
  4. _M_finish: 队尾

iterator的核心成员变量为:

  1. _M_cur: 指向缓冲区位置,也是当前队头或队尾在缓冲区的位置
  2. _M_first: 队头、队尾所在缓冲区的起始地址
  3. _M_last: 队头、队尾所在缓冲区的终止地址
  4. _M_node: 缓冲区所在主控map的地址

2. deque类图

image.png

文字说明

  • _Deque_base提供了几个成员函数,用来操作底层的主控map和node.
  • _Deque_base内部还有个枚举: S_initial_map_size.定义默认的主控map的大小
  • 设置node指向主控map的存储地址由_Deque_iterator提供

3. deque原理剖析

3.1 构造、析构函数

deque提供了如下几个构造、析构函数 image.png 因为deque继承自_Deque_base,在deque构造的时候也会调用基类_Deque_base的构造、析构函数.下面是_Deque_base定义的几种构造、析构函数
image.png

下面逐个分析每个函数

3.1.1 deque() = default

当我们如下定义deque的时候会调用该构造函数

deque<int> d;

由此调用基类_Deque_base的_Deque_base()版本构造函数,该版本细节如下

_Deque_base()
    : _M_impl()
{ _M_initialize_map(0); }

底层调用_M_initialize_map(0) 来初始化主控map和分配缓冲区(node)._M_initialize_map(0)是_Deque_base的成员函数,下面来分析这个函数做了什么. 记住,调用这个函数传入的参数是0

_M_initialize_map(size_t num_elemtns)

这是加上了注释的代码细节 image.png 下面一行一行并画个图解释一下这个函数在做什么

第一行调用了__deque_buf_size()函数,通过函数名得知,这个函数用来获取缓冲区的大小
image.png 这个函数的参数都是传入deque元素大小.如果元素类型小于 512 的话,那么返回 512 / sizeof(元素类型) ;如果元素类型比512还要大,那么则直接返回 1.

回到_M_initialize_map的第一行来.获取了缓冲区的大小后,根据传入的参数(元素的个数) 除以缓冲区大小,表示需要几个这样的缓冲区,然后再加1,一次性多分配一个缓冲区.毕竟重复分配内存也是挺耗时间的.

第二行则计算主控map的大小.这里用到了一个枚举_S_initial_map_size,这个值为 8. image.png 主控map默认大小为 8.如果你要分配的元素个数比较大,至少需要 7个缓冲区才能装下.那么主控map的大小为缓冲区个数 + 2(多分配两个结点)

第三行分配一个主控map,实际就是一个数组

第四、五行设置两个临时变量,用于指向主控map哪个位置存储的缓冲区是队头和队尾.

第六行在这个范围内创建缓冲区,代码细节如下: image.png image.png

程序执行到这里,就变为了如下图所示的情况
image.png

函数的最后几行就是设置队头队尾的指针了,指向合适的位置.这里调用了 _Deque_iterator_M_set_node函数来设置指向的位置
image.png image.png 设置完相应的指针后,如下所示这样:
image.png 现在队头和队尾都指向同一个位置


3.1.2 deque(size_type n, const allocator_type& a = allocator_type())

我们定义如下deque代码

deque<int> d(10);

deque构造函数代码细节如下:
image.png 调用了基类 _Deque_base_Deque_base(size_t num_elements) 构造函数,代码细节如下:
image.png 和上面一样,只不过这次指定了容器的初始元素个数,细节就不再重新赘述一遍了.

把焦点重新回到deque构造函数的函数体里,这里调用了成员函数 _M_default_initialize().代码细节如下:
image.png 这个函数就是将缓冲区初始化为 元素类型 的默认值.


3.1.3 deque(size_type n, const value_type& value, const allocator_type& a = allocator_type())

我们定义如下代码:

deque<int> d(10, 3);

定义一个deque容器,初始元素个数为10,值为3

deque构造函数代码细节如下:
image.png 还是调用了基类的构造,和 3.1.2一样,也都调用了基类的 _M_default_initialize(). 先把主控map和缓冲区初始化出来.随后deque构造函数体里调用了 _M_fill_initialize执行初始化.代码细节如下:
image.png


3.1.4 deque(initializer_list<value_type> __l, const allocator_type& __a = allocator_type())

定义如下代码:

deque<int> d({1, 2, 3}); 

初始化列表方式来定义一个deque容器

构造函数代码细节如下:
image.png 这个版本的构造函数调用了基类的allocator_type&版本构造函数
image.png 基类只是初始化了成员而已,没有创建主控map和缓冲区,那么这个工作就应该是由deque构造函数体的 _M_range_initialize 来做
注意,调用 _M_range_initialize的第三个参数传入的是 random_access_iterator_tag,这个是迭代器类别,表示可以随机访问.

random_access_iterator_tag细节定义在 bits/stl_iterator_base_types.h 头文件中 image.png

_M_range_initialize一共有两个版本:

  1. image.png

  2. image.png

所以两个版本,第二个版本更加匹配.
image.png

3.1.5 deque(_InputIterator first, InputIterator last, const allocator_type& a = allocator_type())

image.png image.png

3.2 修饰符

3.2.1 clear

image.png image.png

3.2.2 insert

版本1iterator insert(const_iterator __position, const value_type& __x) image.png

版本2 iterator insert(const_iterator __position, value_type&& __x) image.png

版本3 iterator insert(const_iterator __p, initializer_list<value_type> __l) image.png image.png

版本4 iterator insert(const_iterator __position, size_type __n, const value_type& __x) image.png image.png

3.2.3 emplace

image.png

3.2.4 erase

版本1 iterator erase(const_iterator __position) image.png image.png

版本2 iterator erase(const_iterator __first, const_iterator __last) image.png image.png

3.2.5 push_back

版本1 void push_back(const value_type& __x) image.png image.png

版本2 void push_back(value_type&& __x) image.png

3.2.6 emplace_back

image.png

3.2.7 pop_back

image.png image.png

3.2.8 push_front

版本1 void push_front(const value_type& __x) image.png image.png

版本2 void push_front(value_type&& __x) image.png

3.2.9 emplace_front

image.png

3.2.10 pop_front

image.png image.png

3.2.11 resize

版本1 void resize(size_type __new_size) image.png

版本2 void resize(size_type __new_size, const value_type& __x) image.png