前言
deque是一种序列式容器,翻译为双端队列,deque实现了常数级别的头尾的插入和删除操作,但代价是deque具有比vector复杂的多的机制。
deque的设计
deque的类图与vector非常相似,数据结构都是存放在_Deque_base中,deque继承_Deque_base,然后定义一些内嵌类型和成员方法。
类图中展示了deque的数据成员主要包括一个指针数组和两个迭代器,这与deque的结构有关,deque维护一个中控器,就是指针数组_M_map,中控器中每个元素指向一个数组,数组中是deque的元素,每个数组大小是固定的,这被称为buffer,而_M_map_size为中控器的长度。中控器是可以扩容的,扩容的过程与vector扩容相同。_M_start和_M_finish则分别指向deque的头元素和尾元素,下图表示了deque的结构。
_M_start和_M_finish迭代器中有四个成员,cur指向当前元素,first指向这条连续空间的第一个元素,last指向这条连续空间的最后一个元素,node则指向这条连续空间在_M_map中对应的节点。
deque要求至少包含8条buffer,并且至少预留一个头尾节点供插入使用,当需要对头节点进行插入时,如果_M_start中的buffer不够使用,那么就重新申请一条buffer放入中控器中,并更新_M_start,删除操作大致是相同的思路,因此,deque通过这种复杂的结构,实现了常数级别的头尾节点删除和插入。
相关代码量较大,可以下载文章末尾的源码自行查看。
deque中间插入元素
尽管deque对头尾的操作是常数级别,但其复杂的结构使得deque在中间插入元素时十分困难,这里省去其他调用过程,直接展示deque中间插入元素时最后调用的函数中的一个重载版本,其他版本大致类似。
template <class _Tp, class _Alloc>
typename deque<_Tp,_Alloc>::iterator
deque<_Tp,_Alloc>::_M_insert_aux(iterator __pos)
{
difference_type __index = __pos - _M_start;
if (__index < size() / 2) {
push_front(front());
iterator __front1 = _M_start;
++__front1;
iterator __front2 = __front1;
++__front2;
__pos = _M_start + __index;
iterator __pos1 = __pos;
++__pos1;
copy(__front2, __pos1, __front1);
}
else {
push_back(back());
iterator __back1 = _M_finish;
--__back1;
iterator __back2 = __back1;
--__back2;
__pos = _M_start + __index;
copy_backward(__pos, __back2, __back1);
}
*__pos = value_type();
return __pos;
}
deque先判断插入的位置,如果在前半部,则将后面的元素全部移动,后半部也是类似的操作,因此尽量不要使用deque的insert或是erase来增删中间元素。
_Deque_iterator
deque的iterator同样较复杂,上图中的_M_start和_M_finish就是_Deque_iterator类型,_Deque_iterator是一种random_access_iterator,包含下面四个元素:
- _M_cur;
- _M_first;
- _M_last;
- _M_node;
其相关含义与_M_start、_M_finish一致。_Deque_iterator由于在地址上是不连续的,为了营造一种连续的假象,迭代器在指向buffer的last元素时调用operator++,必须指向下一个buffer的first元素,因此迭代器重载了operator++,当前类似的operator–,operator+=等等都需要重载。除了加减运算符外,还重载了operator[],这也符合random_access_iterator的语义。
最后来看看deque的iterator失效的情况,当我们在头部或尾部进行插入时,不会影响原本的内存结构,因此不会导致迭代器失效,在头尾删除时也仅导致被删除的迭代器失效,而在中间进行插入和删除时,将使得deque的全部迭代器失效,这是因为可能会触发map的扩容,map扩容将使得所有迭代器的node成员失效。
stack和queue
stack和queue都是deque的适配器,其实非常简单,deque是两端开口的容器,stack和queue就是分别封住了deque的尾部和头部,本质上与deque相同。