deque可以理解为一个双端队列,它是一种双向开口的连续性空间(可在头尾两端分别做元素的插入和删除操作)。
来看下它的空间结构组织:
从图中我们可以看到deque的内存不是整体连续的,而是由一段一段的定量连续空间构成。它给我们的假象是整体连续,并提供了随机存取的接口。所以为了维持这种整体连续的假象,它的数据结构的设计以及迭代器的前进后退等操作都颇为繁琐。(实现代码可想而知比其他两个顺序性容器多得多)
来看看它的数据结构设计:
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;
punlic:
typedef _deque_iterator<T, T&, T*, BufSiz> iterator;
...
protected:
//二级指针
typedef pointer* map_pointer;
protected:
map_pointer map; //指向上图中的map,相当于指针数组
size_type map_size; //记录map中可以容纳多少指针
iterator start; //指向第一个节点
iterator finish; //指向最后一个节点
}
分析deque的迭代器应该具备的结构
- 能够指出分段连续空间在哪里
能够判断自己是否已经处于其所在缓冲区的边缘,如果是,一旦前进或者后退时就必须跳跃到下一个或上一个缓冲区。
开看看deque的迭代器数据结构:
template<class T,class Ref,class Ptr,size_t BufSiz>
struct _deque_iterator
{
...
typedef _deque_iterator self;
T* cur; //此迭代器所指之缓冲区的现行元素
T* first; //此迭代器所指之缓冲区的头
T* last; //此迭代器所指之缓冲区的尾(含备用空间)
map_pointer node; //指向管控中心(中控器)
...
}
来看看deque 的中控器、缓冲区、迭代器的相互关系。
从上面deque的数据结构分析,除了上面说的map二级指针外,它也维护start,finish两个迭代器。这两个迭代器分别指向第一缓冲区的第一个元素和最后缓冲区的最后元素(的下一位置)。当然也得记录中控器的大小(map_size),因为一旦map所提供的节点不足,就必须重新配置更大的一块map。
在迭代器中有个重要的函数是set_node(),是跳到下一个缓冲区
void set_node(map_pointer new_node)
{
node = new_node;
first = *new_node;
last = first + difference_type(buffer_size());
}
来看下加入first、finish迭代器的后deque的内存管理方式图
deque的基本函数使用如下:
#include<iostream>
using namespace std;
#include<vector>
#include<deque>
#include<algorithm>
void main()
{
deque<int> deq; //定义一个deque,默认使用512bytes缓冲区
//deque<int, alloc, 8> ideq(20, 9);
//(源码中有空间配置器alloc)定义一个大小为20,并且初始化为9的deque,缓冲区指定为8(个元素)
deque<int> ideq(20, 9);
cout << "ideq.size()= " << ideq.size() << endl;
for (int i = 0; i < 10; ++i)
{
deq.push_back(i); //尾后加入10个元素
}
cout << "deq.size()= " << deq.size() << endl; //打印其大小
deq.push_front(100); //从deque最前面加入一个元素
//访问deque中的元素
cout << "deq.at(0) = " << deq.at(0) << endl;
cout << "deq[3] = " << deq[3] << endl;
deque<int>::iterator it = deq.begin();
for (; it != deq.end(); it++)
{
cout << *it << " ";
}
cout << endl;
//deque的元素操作
deq.insert(deq.begin() + 4, 900); //在位置i前面插入某一元素
cout << deq.at(4) << endl;
deq.insert(deq.begin(),ideq.begin(), ideq.end()); //插入一整段元素
cout << deq.size() << endl;
deq.pop_back(); //删除尾元素
deq.pop_front(); //删除首元素
deq.erase(deq.begin()); //清除首元素
deq.erase(deq.begin(), deq.begin() + 2); //清除[first,last)区间内的所有元素
cout << deq.size() << endl;
deq.clear(); //清除所有元素
cout << deq.size() << endl;
}
来分析下细节问题:
插入元素:
如果在头部插入一个元素,则会先比较start迭代器的内容,即start.cur 和start.first是否相等,相等说明第一缓冲区没有备用空间,所以会调用push_front_axu()函数申请一块缓冲区,在中控器中对应的指针会指向它,当然也会改变start迭代器node项的指向。假设插入的是99,即ideq.push_front(99);
在尾部插入和首部插入类似,如果最后一块缓冲区有备用空间则,只改变finish迭代器的cur指针,否则重新申请一块缓冲区作为最后一块缓冲区。
在中间的插入需要注意的是在插入一个元素(或一区间元素)后,insert()函数内部会先检查插入的元素位置之前的元素多还是插入元素位置之后的元素多,取元素少的方向进行元素的移动。
删除元素:
删除元素和插入元素类似,在次不再赘述。值得注意的是清除中间位置的元素时,erase()函数内部也会先判断清除位置之前的元素和清除位置之后的元素的大小。会取元素少的方向进行元素的移动。
deque迭代器失效的问题
#include<iostream>
using namespace std;
#include<deque>
#include<algorithm>
void main()
{
deque<int> deq;
deq.push_back(10);
deq.push_back(20);
deq.push_back(30);
deq.push_back(40);
deq.push_back(50);
cout << "deq.size() = " << deq.size() << endl;
deque<int>::iterator it = deq.begin();
deque<int>::iterator it2 = deq.end();
deque<int>::iterator it3 = deq.begin()+2;
//插入元素测试
//cout << "*it3 = " << *it3 << endl;
//deq.insert(it3, 100);
//cout << "*it = " << *it << endl; //失效
//cout << "*(it2-1) = " << *(it2-1) << endl; //失效
//cout << "*it3 = " << *it3 << endl; //失效
//deq.push_back(100);
//cout << "*it = " << *it << endl; //失效
//cout << "*(it2-1) = " << *(it2 - 1) << endl; //失效
//cout << "*it3 = " << *it3 << endl; //失效
//deq.push_front(200);//同push_bask()函数
//删除元素测试
//deq.erase(deq.begin() + 1);
//cout << "*it = " << *it << endl; //失效
//cout << "*(it2-1) = " << *(it2 - 1) << endl; //失效
//cout << "*it3 = " << *it3 << endl; //失效
//deq.pop_back();
//cout << "*it = " << *it << endl; //正常
//cout << "*(it2-1) = " << *(it2 - 1) << endl; //失效
//cout << "*it3 = " << *it3 << endl; //正常
deq.pop_front();
//cout << "*it = " << *it << endl; //失效
//cout << "*(it2-1) = " << *(it2 - 1) << endl; //正常
cout << "*it3 = " << *it3 << endl; //正常
}
测试内容与C++primer(第五版上的描述相吻合),即
- 在向容器添加元素后:插入到除首尾位置之外的任何位置都会导致迭代器、指针和引用失效。如果在首尾位置添加元素,迭代器也会失效,但指向存在的元素的引用和指针不会失效。
- 当我们删除一个元素后:如果在首尾位置之外的任何位置删除元素,那么指向被删除元素外其他元素的迭代器、引用或指针也会失效。如果删除的是尾元素,则尾后迭代器也会失效,但其他迭代器、引用或指针不受影响;删除首元素和尾元素类似。
解释如下~~~~~~~~
插入操作:
1、在队头或队尾插入元素时(push_back(),push_front()),可能缓冲区的空间不够,需要增加map中控器,而中控器的个数也不够,所以新开辟更大的空间来容纳中控器,所以可能会使迭代器失效;但指针、引用仍有效,因为缓冲区已有的元素没有重新分配内存。
2、在队列其他位置插入元素时,由于会造成缓冲区的一些元素的移动(源码中执行copy()来移动数据),所以肯定会造成迭代器的失效;并且指针、引用都会失效。
删除操作:
1、删除队头或队尾的元素时,由于只是对当前的元素进行操作,所以其他元素的迭代器不会受到影响,所以一定不会失效,而且指针和引用也都不会失效;
2、删除其他位置的元素时,也会造成元素的移动,可能会涉及到中控器的改变,所以其他元素的迭代器、指针和引用都会失效。