deque

本文详细探讨了C++ STL中的deque容器,解释了deque与vector的区别,其作为双端队列的特性使得它可以在两端进行O(1)的插入删除操作。deque的内部实现类似于列表,但提供O(1)的随机访问,这使得deque在某些场景下效率可能低于vector。文章介绍了deque的内部结构,包括指针数组和缓冲区,以及如何通过迭代器实现随机访问。此外,还讨论了deque在头部插入时的特殊处理和map的扩容机制。
摘要由CSDN通过智能技术生成

deque

此章节会详细解析deque这一容器,使用VS STL的deque为例子。VS STL在核心部分和SGI STL是一致的,但是在内存分配和一些地方有着很大的不同,我会着重把他讲的清楚明白。

首先我们还是要讲讲deque与vector的不同,我们称deque为双端队列,理由是他可以同时在容器的结尾和开头进行O(1)的插入删除操作。

正是这一差别导致deque在内部于vector有着很大的不同。vector在扩增的时候时常不停的申请内存复制移动,但是deque却不存在这样的操作。这一行为让deque在某方面又和list有着惊人的相似,所以deque没有扩容的概念,因为他们内部实现也是向list一样,各个元素是连接起来的。

但是,deque却又允许O(1)的访问这又deque和list之间有了差别,也就是说deque内部的迭代器是随机访问迭代器。

为了让deque又如上的特性,STL内部不得不让deque的实现变得很复杂而且运行起来又相当耗费时间,所以deque的效率有时候会不如vector。什么时候deque是程序员们值得考虑的一件事情。


内部结构

依旧,我们打开deque的容器看看他包含着什么,于是我们再次发现了我们的老朋友

 _Compressed_pair<_Alty, _Scary_val> _Mypair;

这东西我们见过好多次了,都知道他里面存着对控制容器至关重要的数据。而这些数据,存储在_Deque_val中,也是_Scary_val别名过去的东西。

_Deque_val内部:

private:
    static constexpr size_t _Bytes = sizeof(value_type);
public:
    // elements per block (a power of 2)
    static constexpr int _Block_size = _Bytes <= 1 ? 16
                                     : _Bytes <= 2 ? 8
                                     : _Bytes <= 4 ? 4
                                     : _Bytes <= 8 ? 2
                                                   : 1; 
    _Mapptr _Map; // pointer to array of pointers to blocks
    size_type _Mapsize; // size of map array, zero or 2^N
    size_type _Myoff; // offset of initial element
    size_type _Mysize; // current length of sequence

deque使用一个指针数组用来掌控他的所有元素,这个数组,就是_Map。注意,这个_Map是一个二级指针,因为它是指向指针数组的指针,在_Deque_simple_types中,我们能之间看到它的定义:

template <class _Ty>
struct _Deque_simple_types : _Simple_types<_Ty> {
   
    using _Mapptr = _Ty**;
};

这个数组的用途是以下图示所指出的。

在这里插入图片描述

map中的每个指针指向一个缓冲区,那里存着deque中真正的数据。想必你已经明白为什么deque的可以如此方便在开头和结尾的插入,同时他又像list一样不需要容量的概念。

下面是_Deque_val中各元素的解释:

  • _Map:指向map指针数组。
  • _Mapsize:指出了_Map数组的大小,它固定是2^n的数量。
  • _Myoff:指出了deque中第一个元素的偏移量。
  • _Mysize:deque容器目前的大小,即元素的数量。

之后我会详细解释map数组的扩容。

有了大致的实现我们就可以想想怎么去访问了。deque要求随机访问,就像数组的行为一样。这个责任就落在deque的iterator上。

iterator重载了一系列运算符,我挑一些重点的来说。

_NODISCARD reference operator*() const noexcept {
   
    _Size_type _Block = _Mycont->_Getblock(_Myoff);
    _Size_type _Off   = _Myoff % _Block_size;
    return _Mycont->_Map[_Block][_Off];
}

这个是iterator重载的*运算符,可以看到它最终是通过_Map[_Block][_Off]来获取我们需要的元素的,这个_Block通过_Myoff算出,_Off通过求余得到。

注意:每一个iterator里面都有一个_Myoff,指出了该迭代器的偏移量。

下面是_Getblock的内部实现:

return (_Off / _Block_size) & (_Mapsize - 1);

是的,它只有一行代码,但是却有着相当大的作用,之后在deque扩增的时候我们会再次提起它。

重载了operator*()之后,只需要一个_Myoff就能够返回我们需要的元素,这样,其他迭代器增加减少的操作只需要控制迭代器内部的_Myoff即可。比如operator++(int)

_Deque_unchecked_const_iterator operator++(int) noexcept {
   
    _Deque_unchecked_const_iterator _Tmp = *
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值