【STL】deque(七)

vector是单向开口的连续线性空间,deque则是一种双向开口的连续线性空间。如图1
在这里插入图片描述

图1

deque与vector的差异:

  1. deque允许于参数时间内对起头端进行元素的插入或移除操作。
  2. deque没有所谓容量(capacity)概念,因为deque是动态地以分段连续空间组合而成。
  3. deque也提供了Ramdon Access Iterator,但它的迭代器并不是普通的指针,比vector复杂多些。因此除非必要,我们尽可能使用vector而非deque。
【deque中控器】

对线性连续空间array或vector来说,array无法成长,vector虽可成长,却只能向尾端成长,而且代价很大。
deque在逻辑上也是连续空间,由一段一段的定量连续空间构成,根据需要可配置一段定量的连续空间串接在deque的头端或尾端。deque的最大任务便是在这些分段的定量连续空间上,维护其整体连续的假象,并提供随机存取的接口,避开了“重新配置、复制、释放”的轮回,但迭代器架构更复杂了。
为了管理这些连续分段的空间,deque通过中央控制器来进行管理。deque的结构设计如图2:
在这里插入图片描述

图2

deque采用一块所谓的map(注意,不是STL的map容器)作为主控。这里的map是一小块连续的空间,其中每个元素(此处称为一个节点,node)都是指针,指向另一段(较大的)连续空间,称为缓冲区。缓冲区才是deque的存储主体。
SGI STL允许我们指定缓冲区大小,默认值0表示将使用512 bytes缓冲区。

template <class T, class Alloc = alloc, size_t BufSiz = 0> 
class deque {
public:                         // Basic types
  typedef T value_type;
  typedef value_type* pointer;
  ...
  protected:                      // Internal typedefs
  //元素的指针的指针
  typedef pointer* map_pointer;
  
protected:                      // Data members
  //指向map,map是块连续空间,其内的每个元素
  //都是一个指针(称为节点),指向一块缓冲区
  map_pointer map;
  //map内可容纳多少指针
  size_type map_size;
  ...
}
【deque的迭代器】

deque是分段连续空间。维持其“整体连续”假象的任务,落在了迭代器的operator++operator--两个运算子身上。
deque迭代器具备的结构:

  1. 必须能够指出分段连续空间(亦即缓冲区)在哪里。
  2. 必须能够判断自己是否已经处于其所在缓冲区的边缘,如果是,一旦前进或后退时就必须跳跃至下一个缓冲区。

为了能够正确跳跃,deque必须随时掌握管控中心(map),实现代码如下:

template <class T, class Ref, class Ptr, size_t BufSiz>
struct __deque_iterator { //未继承std::iterator
  typedef __deque_iterator<T, T&, T*, BufSiz>             iterator;
  typedef __deque_iterator<T, const T&, const T*, BufSiz> const_iterator;
  static size_t buffer_size() {return __deque_buf_size(BufSiz, sizeof(T)); }
  
  //未继承std::iterator,所以必须自行撰写五个必要的迭代器相应型别
  typedef random_access_iterator_tag iterator_category; //(1)
  typedef T value_type;									//(2)
  typedef Ptr pointer;									//(3)
  typedef Ref reference;								//(4)
  typedef size_t size_type;
  typedef ptrdiff_t difference_type;					//(5)
  typedef T** map_pointer;

  typedef __deque_iterator self;
  //保持与容器的联结
  T* cur;   //此迭代器所指之缓冲区中的现行(current)元素
  T* first; //此迭代器所指之缓冲区的头
  T* last;  //此迭代器所指之缓冲区的尾(含备用空间)
  map_pointer node;
...
}

决定缓冲区大小的函数buffer_size(),调用__deque_buf_size()(全局函数)

<! 如果n不为0,传回n,表示buffer size由用户自定义
   如果n为0,表示buffer size使用默认值,那么
      如果sz(元素大小,sizeof(value_type))小于512,传回512/sz,
      如果sz不小于512,传回1.
!>
/*
*
*/
inline size_t __deque_buf_size(size_t n, size_t sz)
{
	return n != 0 ? n : (sz < 512 ? size_t(512 / sz) : size_t(1));
}

deque的中控器、缓冲区、迭代器的相互关系入图3:
在这里插入图片描述

图3

操作举例:

  1. 元素形态为int,缓存区大小为8(个元素)的deque。
  2. deque拥有20个元素。

通过以上两个条件,20个元素需要20/8 = 3 个缓冲区,所以map之内运用了三个节点。
迭代器start内的cur指针指向缓存区第一个元素,迭代器finish内的cur指针指向缓冲区的最后一个元素(的下一个位置),deque的结构布局如图4:
在这里插入图片描述

图4

deque::begin()传回迭代器start,deque::end()传回迭代器finish。图中每个缓冲区可储存8个int元素。
map大小为8(起始值)。

由于迭代器内对各种指针运算都进行了重载操作,所以各种指针运算如加、减、前进、后退都不能直观视之。其中最关键的就是一旦行进时遇到缓冲区边缘,视前进或后退而定,可能需要调用set_node()跳一个缓冲区:

  void set_node(map_pointer new_node) {
    node = new_node;
    first = *new_node;
    last = first + difference_type(buffer_size()); //内部用到了上文提到了__deque_buf_size()
  }

重载运算符如下:

//计算两个迭代器之间相差的元素个数即距离。
//这里是用后一个迭代器减去前一个迭代器。
difference_type operator-(const self& x) const {
    return difference_type(buffer_size()) * (node - x.node - 1) +
      (cur - first) + (x.last - x.cur);
  }

  self& operator++() {
    ++cur; 					//切换至下一个元素
    if (cur == last) {      //如果已达到所在缓冲区的尾端 
      set_node(node + 1);   //就切换至下一个(亦即缓冲区)
      cur = first;          //的第一个元素。
    }
    return *this; 
  }
  self operator++(int)  {
    self tmp = *this;
    ++*this;
    return tmp;
  }

  self& operator--() {
    if (cur == first) {      //如果已达所在缓冲区的头端,
      set_node(node - 1);    //就切换至前一个结点(亦即缓冲区)
      cur = last;            //的最后一个元素(的下一位置)
    }
    --cur;					//切换至前一个元素
    return *this;
  }
  self operator--(int) {
    self tmp = *this;
    --*this;
    return tmp;
  }

以下重载实现随机存取。迭代器可直接跳跃n个距离。

self& operator+=(difference_type n) {
    difference_type offset = n + (cur - first);
    if (offset >= 0 && offset < difference_type(buffer_size()))
      cur += n; //1、目标位置和当前迭代器在同一个缓冲区
    else {
       //2、目标位置和当前迭代器不在同一个缓冲区
       //此处计算出便宜几个缓冲区。
       //a)offset > 0 表示目标向后定位,直接offset / difference_type(buffer_size())求整。
       //b)offset < 0 表示目标先前定位:
       //  b-1) 既然offset < 0 说明无论怎样,node_offset都要前进一个,
       //       所以-difference_type((-offset - 1) / buffer_size())整体 -1.
       //  b-2) (-offset - 1)是为了进位而存在。因为是对buffer_size()求整,
       //        -offset 在 (0, buffer_size()]范围之内是不需要偏移node的,
       //        这里-1就是为了解决-offset == buffer_size()时不偏移。                           
      difference_type node_offset =
        offset > 0 ? offset / difference_type(buffer_size())
                   : -difference_type((-offset - 1) / buffer_size()) - 1;
      //切换至正确结点(亦即缓冲区),
      //内部已重新指定first和last
      set_node(node + node_offset);
      //切换至正确的元素,这里画一下也好理解
      cur = first + (offset - node_offset * difference_type(buffer_size()));
    }
    return *this;
  }

  self operator+(difference_type n) const {
    self tmp = *this;
    return tmp += n; //调用operator+=
  }

  //利用operator += 完成operator -=
  self& operator-=(difference_type n) { return *this += -n; }
 
  self operator-(difference_type n) const {
    self tmp = *this;
    return tmp -= n;
  }

  reference operator[](difference_type n) const { return *(*this + n); }

  bool operator==(const self& x) const { return cur == x.cur; }
  bool operator!=(const self& x) const { return !(*this == x); }
  bool operator<(const self& x) const {
    return (node == x.node) ? (cur < x.cur) : (node < x.node);
  }
【deque数据结构】

deque除了维护一个先前说过的指向map的指针外,还要做到:

  1. 维护start,finish两个迭代器。start指向第一个缓冲区的第一个元素,finish指向最后一个缓冲区的最后一个元素(的下一个位置)。
  2. 必须记住目前map大小。因为一旦map所提供的结点不足,就必须重新配置更大的一块map。

相关代码如下:

template <class T, class Alloc = alloc, size_t BufSiz = 0> 
class deque {
public:   
  typedef T value_type;
  typedef value_type* pointer;
  typedef size_t size_type;
public:                         // Iterators
  typedef __deque_iterator<T, T&, T*, BufSiz>              iterator;
  
protected:                      // Internal typedefs
  //元素指针的指针
  typedef pointer* map_pointer;
protected:                      // Data members
  iterator start; //表现第一个节点
  iterator finish; //表现最后一个节点
 
  //指向map,map是块连续空间,
  //其每个元素都是指针,指向一个节点(缓冲区)
  map_pointer map;
  //map内有多少个指针
  size_type map_size;
 ... 
}
【deque的构造与内存管理】
1、构造

举例如下代码:

deque<int, alloc, 8> ideq(20, 9);

这个deque是缓冲区大小为8(个元素),并令其保留20个元素空间,每个元素初值为9。deque的结构图如上图4。
deque自行定义了两个专属的空间配置器:

protected:
	//专属之空间配置器,每次配置一个元素大小
	typedef simple_alloc<value_type, Alloc> data_allocator;
	//专属之空间配置器,每次配置一个指针大小
	typedef simple_alloc<pointer, Alloc> map_allocator;

提供有一个constructor如下:

deque(int n, const value_type& value)
:start(), finish(), map(0), map_size(0)
{
	fill_initialize(n, value);
}

fill_initialize()负责产生并安排好deque的结构,并将元素的初值设定妥当:

template <class T, class Alloc, size_t BufSize>
void deque<T, Alloc, BufSize>::fill_initialize(size_type n,
                                               const value_type& value) {
  create_map_and_nodes(n); //把deque的结构都产生并安排好
  map_pointer cur;
  __STL_TRY {
    //为每个结点的缓冲区设定初值
    for (cur = start.node; cur < finish.node; ++cur)
      uninitialized_fill(*cur, *cur + buffer_size(), value);
	
	//最后一个节点的设置稍有不同(因为尾端可能有备用空间,不必设初值)
    uninitialized_fill(finish.first, finish.cur, value);
  }
  //commit or rollback
#ifdef __STL_USE_EXCEPTIONS
  catch(...) {
    for (map_pointer n = start.node; n < cur; ++n)
      destroy(*n, *n + buffer_size());
    destroy_map_and_nodes();
    throw;
  }
#endif /* __STL_USE_EXCEPTIONS */
}

其中create_map_and_nodes()负责产生并安排好deque的结构:

template <class T, class Alloc, size_t BufSize>
void deque<T, Alloc, BufSize>::create_map_and_nodes(size_type num_elements) {
  //需要结点数 = (元素个数 / 每个缓冲区可容纳的元素个数) + 1
  //如果刚好整除,会多配一个节点。
  size_type num_nodes = num_elements / buffer_size() + 1;
 
  //一个map要管理几个节点。最少8个,最多是“所需节点数加2”
  //(前后各预留一个,扩充时可用)
  map_size = max(initial_map_size(), num_nodes + 2);
  map = map_allocator::allocate(map_size);
  //以上配置出一个“具有map_size个节点”的map
  
  //这里令nstart和nfinish指向map被分配节点的前后两个点
  //保持在此位置,可使头尾两端的扩充能量一样大。每个结点对应一个缓冲区。
  map_pointer nstart = map + (map_size - num_nodes) / 2;
  map_pointer nfinish = nstart + num_nodes - 1;
    
  map_pointer cur;
  __STL_TRY {
    //为map内的每个现用结点配置缓冲区。所有缓冲区加起来就是deque的
    //可用空间(最后一个缓冲区可能留有余裕)
    for (cur = nstart; cur <= nfinish; ++cur)
      *cur = allocate_node();
  }
  //commit or rollback
#ifdef  __STL_USE_EXCEPTIONS 
  catch(...) {
    for (map_pointer n = nstart; n < cur; ++n)
      deallocate_node(*n);
    map_allocator::deallocate(map, map_size);
    throw;
  }
#endif /* __STL_USE_EXCEPTIONS */
  
  //为deque内的两个迭代器start和end设定正确的内容
  start.set_node(nstart);
  finish.set_node(nfinish);
  start.cur = start.first;
  finish.cur = finish.first + num_elements % buffer_size();
}

2、内存管理

举例如下操作:

deque<int, alloc, 8> ideq(20, 9);

for(int i=0; i<ideq.size(); ++i)
	ideq[i] = i;

for(int i = 0; i < 3; i++)
	ideq.push_back(i);

由于此时最后一个缓冲区仍有4个备用元素空间,所以不会引起缓冲区的再配置。此时的deque状态如图5:
在这里插入图片描述

图5

push_back()实现如下:

  void push_back(const value_type& t) {
    if (finish.cur != finish.last - 1) {
     //最后缓冲区尚有两个(含)以上的元素备用空间
      construct(finish.cur, t); //直接在备用空间上构造元素
      ++finish.cur;//调整最后缓冲取得使用状态
    }
    else
      push_back_aux(t); //最后缓冲区只剩一个元素备用空间
  }

当最后一个缓冲区只剩一个备用元素空间时,如果调用push_back(),则会引发调用push_back_aux():

  1. 先配置一整块的缓存区。
  2. 再设妥新元素内容。
  3. 然后更改迭代器finish的状态。
template <class T, class Alloc, size_t BufSize>
void deque<T, Alloc, BufSize>::push_back_aux(const value_type& t) {
  value_type t_copy = t;
  reserve_map_at_back(); //若符合某种条件则必须重换一个map(其实就是map空间不够,要重新分配)
  *(finish.node + 1) = allocate_node(); //配置一个新节点(缓冲区)
  __STL_TRY {
    construct(finish.cur, t_copy);      //针对标的元素设值
    finish.set_node(finish.node + 1);   //改变finish,令其指向新节点。
    finish.cur = finish.first;          //设定finish的状态
  }
  __STL_UNWIND(deallocate_node(*(finish.node + 1)));
}

操作之后的deque的状态如图6:
在这里插入图片描述

图6

上文描述的是在deque的后端插入元素,接下来说下在deque的前端插入一个新元素。
举例:

ideq.push_front(99);

push_front()函数操作如下:

  void push_front(const value_type& t) {
    if (start.cur != start.first) { //第一缓冲区尚有备用空间
      construct(start.cur - 1, t);  //直接在备用空间上构造元素
      --start.cur;                  //调整第一缓冲区的使用状态
    }
    else
      push_front_aux(t); //第一缓冲区已无备用空间
  }

如果第一缓冲区无备用空间,就好调用push_front_aux()函数:

// Called only if start.cur == start.first.
template <class T, class Alloc, size_t BufSize>
void deque<T, Alloc, BufSize>::push_front_aux(const value_type& t) {
  value_type t_copy = t;
  reserve_map_at_front(); //若map空间不够,需要重新申请空间
  *(start.node - 1) = allocate_node(); //配置一个新节点(缓冲区)
  __STL_TRY {
    start.set_node(start.node - 1);  //改变start,令其指向新节点。
    start.cur = start.last - 1;      //设定start的当前cur指向。
    construct(start.cur, t_copy);   //针对标的元素设值
  }
  //commit or rollback
#ifdef __STL_USE_EXCEPTIONS
  catch(...) {
    start.set_node(start.node + 1);
    start.cur = start.first;
    deallocate_node(*(start.node - 1));
    throw;
  }
#endif /* __STL_USE_EXCEPTIONS */
} 

操作之后的deque状态如图7:
在这里插入图片描述

图7

通过上诉的push_back()和push_front()操作,基本上已充分展示了deque容器的空间运用策略。那什么时候会重新整治map呢?
在进行后插和前插的时候,降到了reserve_map_at_back()reserve_map_at_front(),这两个函数就是负责整治map空间的,操作源码如下:

  void reserve_map_at_back (size_type nodes_to_add = 1) {
    //因为下标是0~7,当是最后一个节点时finish.node - map = 7。所以+1是为了解决因下标
    //问题导致的数量少1问题。
    if (nodes_to_add + 1 > map_size - (finish.node - map))
     //如果map尾端的节点空间不足
     //符合以上条件则必须重换一个map(配置更大的,拷贝原来的,释放原来的)
      reallocate_map(nodes_to_add, false);
  }

  void reserve_map_at_front (size_type nodes_to_add = 1) {
    //当前半部分节点一个都不剩时
    if (nodes_to_add > start.node - map)
      reallocate_map(nodes_to_add, true);
  }

前端插入和后端插入重新申请节点是有点区别的,后端插入时如果检测到只剩一个节点了就要重新申请,而前端插入则是一个节点都不剩时才重新申请。
map的重新申请最终调用的是reallocate_map()函数:

template <class T, class Alloc, size_t BufSize>
void deque<T, Alloc, BufSize>::reallocate_map(size_type nodes_to_add,
                                              bool add_at_front) {
  //旧节点的个数,因为下标是0~7,所以要+1才是真实的数量                                           
  size_type old_num_nodes = finish.node - start.node + 1;
  //新节点的个数
  size_type new_num_nodes = old_num_nodes + nodes_to_add;
  //二级指针指向
  map_pointer new_nstart;
  if (map_size > 2 * new_num_nodes)
  {
    //此条件是为了防止出现一端已经用完,另一端却还有很多剩余节点的情况。
    //比如一端pop_front(),另一端push_back(),此时没必要重新申请更大的map空间。
    new_nstart = map + (map_size - new_num_nodes) / 2 
                     + (add_at_front ? nodes_to_add : 0);
    if (new_nstart < start.node)
      //前端剩余比较多,则前移
      copy(start.node, finish.node + 1, new_nstart);
    else
      //后端剩余比较多则后移
      copy_backward(start.node, finish.node + 1, new_nstart + old_num_nodes);
  }
  else 
  {
    //新map大小,至少是2倍 + 2
    size_type new_map_size = map_size + max(map_size, nodes_to_add) + 2;
    //配置一块空间,准备给新map使用
    map_pointer new_map = map_allocator::allocate(new_map_size);
    //这里还是为了保证start在map的最中央区,保证首尾两端扩充一样。
    //是前置的扩充就多加下nodes_to_add。
    new_nstart = new_map + (new_map_size - new_num_nodes) / 2
                         + (add_at_front ? nodes_to_add : 0);
    //将原map内容拷贝过来
    copy(start.node, finish.node + 1, new_nstart);
    //释放原map
    map_allocator::deallocate(map, map_size);
    //设定新map的起始地址和大小
    map = new_map;
    map_size = new_map_size;
  }
  //重新设定迭代器start和finish的指向
  start.set_node(new_nstart);
  finish.set_node(new_nstart + old_num_nodes - 1);
}
【deque的元素操作】

接下来讲解几个deque的操作函数,重点观察下deque对应的内存变化。
pop_front()和pop_back()是将元素拿掉,无论是前端拿还是后端拿,都需要考虑在某种条件下,将缓冲区释放掉:

pop_back()
void pop_back() {
    if (finish.cur != finish.first) {
      //最后缓冲区有一个(或更多)元素
      --finish.cur; //调整指针
      destroy(finish.cur); //将最后元素析构
    }
    else
      //最后缓冲区没有元素
      pop_back_aux();
  }

// Called only if finish.cur == finish.first.
template <class T, class Alloc, size_t BufSize>
void deque<T, Alloc, BufSize>:: pop_back_aux() {
  deallocate_node(finish.first);     //释放最后一个缓冲区
  finish.set_node(finish.node - 1);  //调整finish的状态,使指向
  finish.cur = finish.last - 1;      //上一个缓冲区的最后一个元素
  destroy(finish.cur);               //将该元素析构。     
}
pop_front()
  void pop_front() {
    if (start.cur != start.last - 1) {
      //第一个缓冲区有两个(或更多)元素
      destroy(start.cur);    //将第一元素析构
      ++start.cur;           //调整指针位置   
    }
    else 
      //第一缓冲区仅有一个元素
      pop_front_aux();   //内部会进行缓冲区的释放工作
  }
  
template <class T, class Alloc, size_t BufSize>
void deque<T, Alloc, BufSize>::pop_front_aux() {
  destroy(start.cur);             //将第一缓冲区的第一个(也是最好一个,唯一一个)元素析构
  deallocate_node(start.first);   //释放第一缓冲区
  start.set_node(start.node + 1); //调整start的状态,使指向 
  start.cur = start.first;        //下一个缓冲区的第一个元素
}    
clear()

clear() 用来清除整个deque,由于deque的最初状态(无任何元素时)保有一个缓冲区,因此,clear()完成之后恢复初始状态,也一样保留一个缓冲区。

template <class T, class Alloc, size_t BufSize>
void deque<T, Alloc, BufSize>::clear() {
  //针对头尾以外的缓冲区进行释放
  for (map_pointer node = start.node + 1; node < finish.node; ++node) {
    //将缓冲区内的元素析构
    destroy(*node, *node + buffer_size());
    //释放缓冲区内存
    data_allocator::deallocate(*node, buffer_size());
  }

  if (start.node != finish.node) {
    //当有头尾两个缓冲区时
    //将头缓冲区的目前所有元素析构
    destroy(start.cur, start.last);
    //将尾缓冲区的目前所有元素析构
    destroy(finish.first, finish.cur);
    //释放尾缓冲区内存,保留头缓冲区
    data_allocator::deallocate(finish.first, buffer_size());
  }
  else
    //只有一个缓冲区
    //释放所有元素,但不释放内存。
    destroy(start.cur, finish.cur);
  
  //调整位置
  finish = start;
}
erase()

清除某个点的元素:

 //清除pos所指的元素。pos为清除点
  iterator erase(iterator pos) {
    iterator next = pos;
    ++next;
    difference_type index = pos - start; //清除点之前的元素个数
    if (index < (size() >> 1)) {         //如果清除点之前的元素比较少, 
      copy_backward(start, pos, next);   //就移动清除点之前的元素
      pop_front();                    //移动完毕,最前一个元素冗余,去除之。 
    }
    else {                           //清除点之后的元素比较少
      copy(next, finish, pos);       //就移动清除点之后的元素
      pop_back();                    //移动完毕,最后一个元素冗余,去除之。
    }
    return start + index;
  }

清除某个区间所有元素:

//清除[first, last)区间内的所有元素
template <class T, class Alloc, size_t BufSize>
deque<T, Alloc, BufSize>::iterator 
deque<T, Alloc, BufSize>::erase(iterator first, iterator last) {
  if (first == start && last == finish) {  //如果清除区间就是整个deque,
    clear();                               //直接调用clear()即可。
    return finish;
  }
  else {
    difference_type n = last - first;              //清除区间的长度
    difference_type elems_before = first - start;  //清除区间前方的元素个数 
    if (elems_before < (size() - n) / 2) {         //如果前方的元素比较少
      copy_backward(start, first, last);           //向后移动前方元素(覆盖清除区间)
      iterator new_start = start + n;              //标记deque的新起点   
      destroy(start, new_start);                   //移动完毕,将冗余的元素析构
      //以下将冗余的缓冲去释放
      for (map_pointer cur = start.node; cur < new_start.node; ++cur)
        data_allocator::deallocate(*cur, buffer_size());
      //设定新标志
      start = new_start;
    }
    else {  //如果清除区间后方的元素比较少
      copy(last, finish, first);            //向前移动后方元素(覆盖清除区间)
      iterator new_finish = finish - n;     //标记deque的新尾点
      destroy(new_finish, finish);          //移动完毕,将冗余的元素析构
      //以下将冗余的缓冲区释放掉
      //因为新的结尾已经是new_finish,如果要释放也是释放new_finish后面的一个node,
      //所以此处是new_finish.node+1
      for (map_pointer cur = new_finish.node + 1; cur <= finish.node; ++cur)
        data_allocator::deallocate(*cur, buffer_size());
      finish = new_finish; //设定deque的新尾点。
    }
    return start + elems_before;
  }
}
insert()

deque提供了许多版本的的insert,这里讲解下允许在某个点(之前)插入一个元素,并设定其值。

  //在position处插入一个元素,其值为x
  iterator insert(iterator position, const value_type& x) {
    if (position.cur == start.cur) {   //如果插入点是deque最前端
      push_front(x);                   //交给push_front去做。
      return start;
    }
    else if (position.cur == finish.cur) {      //如果插入点是deque最尾端
      push_back(x);                             //交给push_back去做。     
      iterator tmp = finish;
      --tmp;
      return tmp;
    }
    else {
      //其它交给insert_aux去做。
      return insert_aux(position, x);
    }
  }

insert_aux()

template <class T, class Alloc, size_t BufSize>
typename deque<T, Alloc, BufSize>::iterator
deque<T, Alloc, BufSize>::insert_aux(iterator pos, const value_type& x) {
  difference_type index = pos - start;   //插入点之前的元素个数
  value_type x_copy = x;
  if (index < size() / 2) {     //如果插入点之前的元素个数比较少
    push_front(front());        //在最前端加入与第一个元素同值的元素
    iterator front1 = start;    //以下标示记号,然后进行元素移动(元素向前移动) 
    ++front1;
    iterator front2 = front1;
    ++front2;
    pos = start + index;
    iterator pos1 = pos;
    ++pos1;
    copy(front2, pos1, front1);  //元素移动
  }
  else {                        //插入点之后的元素个数比较少
    push_back(back());          //在最尾端加入与最后元素相同值的元素
    iterator back1 = finish;    //以下标示记号,然后进行元素移动(元素向后移动) 
    --back1;
    iterator back2 = back1;
    --back2;
    pos = start + index;
    copy_backward(pos, back2, back1);    //元素移动
  }
  *pos = x_copy;
  return pos;
}

插入元素的移动操作有点复杂。

【总结】
  1. 如开头所说,deque的复杂度和vector不可以道里计,deque的各个运算层面都是比较复杂的,因此除非必要,应尽可能的使用vector。
  2. 对deque的排序,为了最高效率,可将deque先完整复制到一个vector,将vector排序后(STL的sort算法),再复制会deque。
  3. deque的内部涉及到清除、移动时,都分操作点之前和之后来操作,目的是为了提升效率。
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值