STL之deque

Posted randyniu

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了STL之deque相关的知识,希望对你有一定的参考价值。

deque为了维持在头和尾进行插入的假象,这些操作全部都交给了迭代器去做了。

deque由一段一段定量连续空间构成,一旦有必要在deque的前端或者是尾端增加新的空间,便配置一段定量的连续空间,串在头部或者是尾部。

deque采用的是一块所谓的map作为主控,这里的map是一小段连续的空间,其中每个元素都是一个指针,指向另一端较大的连续线性空间,称为缓冲区。缓冲区才是真正的存储主体。允许指定大小的缓冲区,默认是512字节的缓冲区。

//deque迭代器的设计
//*deque是分段连续的线性空间,迭代器设计时必须能够进行operator++或operator--操作
//*    迭代器的功能:
//*    1、必须知道缓冲区的位置
//*    2、能够判断是否处于其所在缓冲区的边界
//*    3、能够知道其所在缓冲区当前所指位置的元素
//********************************************
template <class _Tp, class _Ref, class _Ptr>
struct _Deque_iterator {
  typedef _Deque_iterator<_Tp, _Tp&, _Tp*>             iterator;
  typedef _Deque_iterator<_Tp, const _Tp&, const _Tp*> const_iterator;
  static size_t _S_buffer_size() { return __deque_buf_size(sizeof(_Tp)); }

  typedef random_access_iterator_tag iterator_category;
  typedef _Tp value_type;
  typedef _Ptr pointer;
  typedef _Ref reference;
  typedef size_t size_type;
  typedef ptrdiff_t difference_type;
  typedef _Tp** _Map_pointer;

  typedef _Deque_iterator _Self;

  //以下是迭代器设计的关键,访问容器的节点
  _Tp* _M_cur;//指向缓冲区当前的元素
  _Tp* _M_first;//指向缓冲区的头(起始地址)
  _Tp* _M_last;//指向缓冲区的尾(结束地址)
  _Map_pointer _M_node;//指向中控器的相应节点

  _Deque_iterator(_Tp* __x, _Map_pointer __y) 
    : _M_cur(__x), _M_first(*__y),
      _M_last(*__y + _S_buffer_size()), _M_node(__y) {}
  _Deque_iterator() : _M_cur(0), _M_first(0), _M_last(0), _M_node(0) {}
  _Deque_iterator(const iterator& __x)
    : _M_cur(__x._M_cur), _M_first(__x._M_first), 
      _M_last(__x._M_last), _M_node(__x._M_node) {}

  //****************************************************************************
  //*********************以下是迭代器_Deque_iterator的操作**********************
  //*    这些操作符的重载方便我们访问容器的内容
  //*    也是让deque从接口看来是维护连续线性空间的关键
  //****************************************************************************
  reference operator*() const { return *_M_cur; }//解除引用,返回当前元素
#ifndef __SGI_STL_NO_ARROW_OPERATOR
  pointer operator->() const { return _M_cur; }
#endif /* __SGI_STL_NO_ARROW_OPERATOR */

  //返回两个迭代器之间的距离
  difference_type operator-(const _Self& __x) const {
    return difference_type(_S_buffer_size()) * (_M_node - __x._M_node - 1) +
      (_M_cur - _M_first) + (__x._M_last - __x._M_cur);
  }

  //前缀自增++操作符重载 
  _Self& operator++() {
    ++_M_cur;                    //普通自增操作,移至下一个元素
    if (_M_cur == _M_last) {   //若已达到缓冲区的尾部
      _M_set_node(_M_node + 1);//切换至下一缓冲区(节点)
      _M_cur = _M_first;      //的第一个元素
    }
    return *this;
  }
  //后缀自增++操作符重载
  //返回当前迭代器的一个副本
  _Self operator++(int)  {
    _Self __tmp = *this;//定义当前迭代器的一个副本
    ++*this;//这里前缀++不是普通的++操作,是上一步骤已经重载过的前缀++
    return __tmp;
  }

  //前缀自减--操作符重载
  //基本思想类似于前缀自增操作 
  _Self& operator--() {
    if (_M_cur == _M_first) {  //若是当前缓冲区的第一个元素
      _M_set_node(_M_node - 1);//切换到上一个缓冲区
      _M_cur = _M_last;          //的尾部(即最后一个元素的下一个位置)
    }
    --_M_cur;//普通的自减操作,移至前一个元素
    return *this;
  }
  //后缀自减--操作符重载
  //返回当前迭代器的副本
  _Self operator--(int) {
    _Self __tmp = *this;//定义一个副本
    --*this;            //迭代器自减操作
    return __tmp;
  }

  //以下实现随机存取,迭代器可以直接跳跃n个距离
  //将迭代器前移n个距离,当n负值时就为下面的operator-=操作
  _Self& operator+=(difference_type __n)
  {
    difference_type __offset = __n + (_M_cur - _M_first);//定义一个中间变量
    if (__offset >= 0 && __offset < difference_type(_S_buffer_size()))
        //若前移n个距离后,目标依然在同一个缓冲区
        //则直接前移n个距离
      _M_cur += __n;
    else {
        //若前移n个距离后,目标超出该缓冲区范围
        //__offset / difference_type(_S_buffer_size())计算向后移动多少个缓冲区
        //-difference_type((-__offset - 1) / _S_buffer_size()) - 1计算向前移动多少个缓冲区
      difference_type __node_offset =
        __offset > 0 ? __offset / difference_type(_S_buffer_size())
                   : -difference_type((-__offset - 1) / _S_buffer_size()) - 1;
      //调整到正确的缓冲区
      _M_set_node(_M_node + __node_offset);
      //切换至正确的元素
      _M_cur = _M_first + 
        (__offset - __node_offset * difference_type(_S_buffer_size()));
    }
    return *this;
  }

  //操作符+重载
  //返回操作之后的副本
  _Self operator+(difference_type __n) const
  {
    _Self __tmp = *this;
    //调用operator+=操作
    return __tmp += __n;
  }

  //利用operator+=操作实现
  _Self& operator-=(difference_type __n) { return *this += -__n; }
 
  _Self operator-(difference_type __n) const {
    _Self __tmp = *this;
    return __tmp -= __n;
  }

  //返回指定位置的元素,即实现随机存取
  //该函数调用operator+,operator*
  reference operator[](difference_type __n) const { return *(*this + __n); }

  bool operator==(const _Self& __x) const { return _M_cur == __x._M_cur; }
  bool operator!=(const _Self& __x) const { return !(*this == __x); }
  bool operator<(const _Self& __x) const {
    return (_M_node == __x._M_node) ? 
      (_M_cur < __x._M_cur) : (_M_node < __x._M_node);
  }
  bool operator>(const _Self& __x) const  { return __x < *this; }
  bool operator<=(const _Self& __x) const { return !(__x < *this); }
  bool operator>=(const _Self& __x) const { return !(*this < __x); }

  //调整到正确的缓冲区
  //切换到正确的元素位置
  void _M_set_node(_Map_pointer __new_node) {
    _M_node = __new_node;//指向新的节点
    _M_first = *__new_node;//指向新节点的头部
    _M_last = _M_first + difference_type(_S_buffer_size());//指向新节点的尾部
  }
};

//operator+迭代器向前移动n个位置
template <class _Tp, class _Ref, class _Ptr>
inline _Deque_iterator<_Tp, _Ref, _Ptr>
operator+(ptrdiff_t __n, const _Deque_iterator<_Tp, _Ref, _Ptr>& __x)
{
  return __x + __n;
}

#ifndef __STL_CLASS_PARTIAL_SPECIALIZATION

template <class _Tp, class _Ref, class _Ptr>
inline random_access_iterator_tag
iterator_category(const _Deque_iterator<_Tp,_Ref,_Ptr>&)
{
  return random_access_iterator_tag();
}

template <class _Tp, class _Ref, class _Ptr>
inline _Tp* value_type(const _Deque_iterator<_Tp,_Ref,_Ptr>&) { return 0; }

template <class _Tp, class _Ref, class _Ptr>
inline ptrdiff_t* distance_type(const _Deque_iterator<_Tp,_Ref,_Ptr>&) {
  return 0;
}

#endif /* __STL_CLASS_PARTIAL_SPECIALIZATION */

#ifdef __STL_USE_STD_ALLOCATORS

// Base class for ordinary allocators.
//这里只负责中控器节点内存管理
template <class _Tp, class _Alloc, bool __is_static>
class _Deque_alloc_base {
public:
  typedef typename _Alloc_traits<_Tp,_Alloc>::allocator_type allocator_type;
  allocator_type get_allocator() const { return _M_node_allocator; }

  _Deque_alloc_base(const allocator_type& __a)
    : _M_node_allocator(__a), _M_map_allocator(__a),
      _M_map(0), _M_map_size(0)
  {}
  
protected:
  typedef typename _Alloc_traits<_Tp*, _Alloc>::allocator_type
          _Map_allocator_type;

  allocator_type      _M_node_allocator;
  _Map_allocator_type _M_map_allocator;

  _Tp* _M_allocate_node() {
    return _M_node_allocator.allocate(__deque_buf_size(sizeof(_Tp)));
  }
  void _M_deallocate_node(_Tp* __p) {
    _M_node_allocator.deallocate(__p, __deque_buf_size(sizeof(_Tp)));
  }
  _Tp** _M_allocate_map(size_t __n) 
    { return _M_map_allocator.allocate(__n); }
  void _M_deallocate_map(_Tp** __p, size_t __n) 
    { _M_map_allocator.deallocate(__p, __n); }

  _Tp** _M_map; //指向中控器map
  size_t _M_map_size;//中控器map可容纳指针的个数
};

deque 迭代器中有的数据也就4个指针,因此如果sizeof的话应该大小是16。

迭代器中联系了缓存区中的位置,以及在map控制中心的位置。

deque的数据结构

deque中维护了指向map的指针,还维护了start和finish两个迭代器,分别指向第一个缓冲区的第一个元素,和最后一个缓冲区的最后一个元素。同是记住map大小,因为当map节点不足的时候,需要更新配置一个更大的一块map。

template <class _Tp, class _Alloc>
class _Deque_base
  : public _Deque_alloc_base<_Tp,_Alloc,
                              _Alloc_traits<_Tp, _Alloc>::_S_instanceless>
{
public:
  typedef _Deque_alloc_base<_Tp,_Alloc,
                             _Alloc_traits<_Tp, _Alloc>::_S_instanceless>
          _Base;
  typedef typename _Base::allocator_type allocator_type;
  typedef _Deque_iterator<_Tp,_Tp&,_Tp*>             iterator;
  typedef _Deque_iterator<_Tp,const _Tp&,const _Tp*> const_iterator;

  //参数__num_elements表示缓冲区(节点)存储元素的个数
  _Deque_base(const allocator_type& __a, size_t __num_elements)
    : _Base(__a), _M_start(), _M_finish()
    { _M_initialize_map(__num_elements); }
  _Deque_base(const allocator_type& __a) 
    : _Base(__a), _M_start(), _M_finish() {}
  ~_Deque_base();    

protected:
  void _M_initialize_map(size_t);
  void _M_create_nodes(_Tp** __nstart, _Tp** __nfinish);
  void _M_destroy_nodes(_Tp** __nstart, _Tp** __nfinish);
  enum { _S_initial_map_size = 8 };//中控器map默认大小

protected:
  iterator _M_start;//指向第一个缓冲区的第一个元素
  iterator _M_finish;//指向最后一个缓冲区的最后一个元素的下一个位置
};

deque中有两个迭代器,还有一个map指针,还有一个size_type ,因此大小应该是40个字节。

template <class _Tp, class _Alloc>
void deque<_Tp,_Alloc>::_M_reallocate_map(size_type __nodes_to_add,
                                          bool __add_at_front)
{
  //map原始节点数
  size_type __old_num_nodes = _M_finish._M_node - _M_start._M_node + 1;
  //map新的节点数
  size_type __new_num_nodes = __old_num_nodes + __nodes_to_add;

  _Map_pointer __new_nstart;
  if (_M_map_size > 2 * __new_num_nodes) {//若map的大小比新节点数的两倍还大,则只需调整迭代器start和finish
    __new_nstart = _M_map + (_M_map_size - __new_num_nodes) / 2 
                     + (__add_at_front ? __nodes_to_add : 0);
    if (__new_nstart < _M_start._M_node)
      copy(_M_start._M_node, _M_finish._M_node + 1, __new_nstart);
    else
      copy_backward(_M_start._M_node, _M_finish._M_node + 1, 
                    __new_nstart + __old_num_nodes);
  }
  else {//否则需重新分配一个新的map空间
    size_type __new_map_size = 
      _M_map_size + max(_M_map_size, __nodes_to_add) + 2;

    _Map_pointer __new_map = _M_allocate_map(__new_map_size);
    __new_nstart = __new_map + (__new_map_size - __new_num_nodes) / 2
                         + (__add_at_front ? __nodes_to_add : 0);
    //拷贝原始map的内容
    copy(_M_start._M_node, _M_finish._M_node + 1, __new_nstart);
    //释放原始map空间
    _M_deallocate_map(_M_map, _M_map_size);
    //更新map的起始地址和大小
    _M_map = __new_map;
    _M_map_size = __new_map_size;
  }

  //更新迭代器start和finish
  _M_start._M_set_node(__new_nstart);
  _M_finish._M_set_node(__new_nstart + __old_num_nodes - 1);
}

这个函数时用来设置map的相关内容。最少是管理8个节点,最多是所需节点的2倍。

当finish.cur == finish.last-1的时候,是需要一个新的缓冲空间,如果缓存空间还有的话就直接使用,如果没有了就需要换一块新的缓存空间。

由于会不停的增删元素,因此会导致deque的map会向一边偏狭,因此有时候就需要重新调整map。在不够用的时候,在进行重新申请空间之前进行判断是否是真的需要换空间,如果当前map空间 是大于 2 倍的被占用的实际空间,就进行调整,否则就进行新空间的分配。不过deque的重新配置要比vector简单很多,不用涉及到大量的元素移动,要拷贝的也仅仅是map中的指针而已,因此deque的生长要比vector简单一点 。

 void pop_back() {
    if (_M_finish._M_cur != _M_finish._M_first) {
      --_M_finish._M_cur;
      destroy(_M_finish._M_cur);
    }
    else
      _M_pop_back_aux();
  }

  void pop_front() {
    if (_M_start._M_cur != _M_start._M_last - 1) {
      destroy(_M_start._M_cur);
      ++_M_start._M_cur;
    }
    else 
      _M_pop_front_aux();
  }

添加元素的时候要考虑空间扩展的问题,那么当进行弹出元素的时候就要考虑空间收缩的问题。

当最后缓冲区只剩下一个元素了,那么弹出的时候要调整当前的指针,同时析构当前元素,然后进行缓冲区的释放工作。

当前面的缓冲区只有最后一个元素存在的时候,如果这个时候要将前面的元素给弹出,这个时候要进行前面缓冲区的释放工作。同时设置好当前迭代的指针,每次进行增加或者是删除的时候,都需要进行迭代器指向的重置工作。

// Called only if _M_finish._M_cur == _M_finish._M_first.
template <class _Tp, class _Alloc>
void deque<_Tp,_Alloc>::_M_pop_back_aux()
{
  _M_deallocate_node(_M_finish._M_first);
  _M_finish._M_set_node(_M_finish._M_node - 1);
  _M_finish._M_cur = _M_finish._M_last - 1;
  destroy(_M_finish._M_cur);
}
template <class _Tp, class _Alloc>
void deque<_Tp,_Alloc>::_M_pop_front_aux()
{
  destroy(_M_start._M_cur);
  _M_deallocate_node(_M_start._M_first);
  _M_start._M_set_node(_M_start._M_node + 1);
  _M_start._M_cur = _M_start._M_first;
} 

下面的clear函数是用来清除整个deque,deque的最初状态是只有一个缓冲区,因此clear之后的deque是没有任何元素,同时仅仅保留了一个缓冲区这样的状态。

首先释放头和尾意外的缓冲区,

如果还剩下两个缓冲区,就将头缓冲区保存下来,当然析构对象是在所难免的了。

如果仅剩下一个缓冲区,就仅仅进行对象的析构工作。

template <class _Tp, class _Alloc> 
void deque<_Tp,_Alloc>::clear()
{
  //首先清空缓冲区是满数据的节点
    for (_Map_pointer __node = _M_start._M_node + 1;
       __node < _M_finish._M_node;
       ++__node) {
    destroy(*__node, *__node + _S_buffer_size());
    _M_deallocate_node(*__node);
  }

 //清除缓冲区还存在可用空间的节点,即只存储部分数据
    if (_M_start._M_node != _M_finish._M_node) {
    destroy(_M_start._M_cur, _M_start._M_last);
    destroy(_M_finish._M_first, _M_finish._M_cur);
    _M_deallocate_node(_M_finish._M_first);
  }
  else
    destroy(_M_start._M_cur, _M_finish._M_cur);

  _M_finish = _M_start;//表示容器为空

接下来的例子是erase函数。

首先计算那个前面和后面部分那个元素少,那个元素少就移动那个方向的元素。这么考虑也是很自然的事情。

template <class _Tp, class _Alloc>
typename deque<_Tp,_Alloc>::iterator 
deque<_Tp,_Alloc>::erase(iterator __first, iterator __last)
{
  if (__first == _M_start && __last == _M_finish) {//若擦除整个容器内容
    clear();
    return _M_finish;
  }
  else {
    difference_type __n = __last - __first;
    difference_type __elems_before = __first - _M_start;
    if (__elems_before < difference_type((this->size() - __n) / 2)) {
      copy_backward(_M_start, __first, __last);
      iterator __new_start = _M_start + __n;
      destroy(_M_start, __new_start);
      _M_destroy_nodes(__new_start._M_node, _M_start._M_node);
      _M_start = __new_start;
    }
    else {
      copy(__last, _M_finish, __first);
      iterator __new_finish = _M_finish - __n;
      destroy(__new_finish, _M_finish);
      _M_destroy_nodes(__new_finish._M_node + 1, _M_finish._M_node + 1);
      _M_finish = __new_finish;
    }
    return _M_start + __elems_before;
  }
}

最后说的是插入工作,删除需要移动元素,同样,插入也需要移动元素,所以用的方法和erase是一样的,都需要判断前面还有后面那边的元素少就移动那边的元素,

当然有两种特殊情况,如果是在最前面,就直接调用push_front函数去处理,如果是在后面插入就直接调用push_back来进行,否则就进行判断。

记得要更新迭代器哦。

大概deque就这么多了,其实deque的抽象确实是很棒的,这些都是迭代器的功劳。

 

以上是关于STL之deque的主要内容,如果未能解决你的问题,请参考以下文章

stl之deque双端队列容器

C++ STL 之 deque

STL之Deque

STL之deque使用详解

STL之deque用法

STL之deque