双端队列deque,与vector的最大差异在于:
一、deque运行常数时间对头端或尾端进行元素的插入和删除操作。
二、deque没有所谓的容器概念,因为它是动态地以分段连续空间组合而成随时可以增加一块新的内存空间并拼接起来。
虽然deque也提供随机访问的迭代器,但它的迭代器与list和vector的不一样,其设计相当复杂而精妙。因此,会对各种运算产生一定影响,除非必要,尽可能的选择使用vetor而非deque。
deque内部是一段一段的定量连续空间构成。
一旦有必要在deque的前端或尾端增加新空间,deque会配置一段定量的连续空间,串联在整个deque的头部或尾部。
deque的设计最大的调整应该就是如何在这段分段的定量连续空间上还能维护其整体连续的假象,并提供其随机存取的接口,从而避开了像vector那样的“重新配置-复制-释放”开销三部曲。这样一来,虽然开销降低,却提高了复杂的迭代器架构。
因此,deque数据结构的设计和迭代前进或后退等操作都非常复杂。
deque采用一块所谓的map(注意,不是stl里面的map容器)作为中控器,其实就是一小块连续空间,其中的每一个元素都是指针,指向另外一段较大的连续线性空间,称之为缓冲区。在后面我们将看到,缓冲区才是deque的存储空间主体。
当一个连续的内存块存储满之后first迭代器会重新指向_M_map的下一个内存位置。当前连续块已经存储满,first迭代器的_M_node指针会重新指向 _M_map的下一个连续内存位置
deque的构造和析构函数
#include<deque> //包含头文件
deque<int> deqT;
deque<int>dequeT2(dequeT.begin(), dequeT.end());//将dequeT中从begin到end区间的元素复制到本容器中
deque<int>dequeT3(3,5);//向容器中插入3个5
deque<int>dequeT4(dequeT3);//将dequeT3容器中的元素拷贝到dequeT4中
deque& operator=(deque& deqT);//重载operator=运算符
deque<int>deqT1;
T1.push_back(1);
deque<int>deqT2;
deqT2 = deqT1;
assign(begin,end)
deque<int>deqT3;
deqT3.assign(deqT1.begin(), deqT1.end());//将deqT2容器begin到end区间的元素拷贝到deqT3容器中
assign(int n, elem);
deque<int>deqT4;
deqT4.assign(3,6);//deqT4容器中的元素为6,6,6
五. deque的插入和删除元素函数
- 因为deque是能够双向操作,所以其push和pop操作都类似于list,都可以直接有对应的操作,需要注意的是list是链表,并不会涉及到界线的判断,而deque是由数组来存储的,所以需要随时对界限进行判断。
void push_front(const value_type& _Val)
{ // insert element at beginning
this->_Orphan_all();
_PUSH_FRONT_BEGIN;
this->_Getal().construct(
this->_Map()[_Block] + _Newoff % _DEQUESIZ, _Val);
_PUSH_FRONT_END;
}
void pop_front()
{ // erase element at beginning
#if _ITERATOR_DEBUG_LEVEL == 2
if (empty())
_DEBUG_ERROR("deque empty before pop");
else
{ // something to erase, do it
_Orphan_off(this->_Myoff());
size_type _Block = this->_Getblock(this->_Myoff());
this->_Getal().destroy(
this->_Map()[_Block] + this->_Myoff() % _DEQUESIZ);
if (--this->_Mysize() == 0)
this->_Myoff() = 0;
else
++this->_Myoff();
}
void push_back(const value_type& _Val)
{ // insert element at end
this->_Orphan_all();
_PUSH_BACK_BEGIN;
this->_Getal().construct(
this->_Map()[_Block] + _Newoff % _DEQUESIZ, _Val);
_PUSH_BACK_END;
}
void pop_back()
{ // erase element at end
#if _ITERATOR_DEBUG_LEVEL == 2
if (empty())
_DEBUG_ERROR("deque empty before pop");
else
{ // something to erase, do it
size_type _Newoff = this->_Myoff() + this->_Mysize() - 1;
_Orphan_off(_Newoff);
size_type _Block = this->_Getblock(_Newoff);
this->_Getal().destroy(
this->_Map()[_Block] + _Newoff % _DEQUESIZ);
if (--this->_Mysize() == 0)
this->_Myoff() = 0;
}
deque<int> deq;
deq.push_front(1);//deq容器中元素为:1 头插
deq.push_back(2);//deq容器中元素为:1 2 尾插
deq.insert(deq.begin(), 5);//deq容器中元素为:5 1 2 向pos位置插入elem元素
deq.insert(deq.end(), 4, 5);//deq容器中元素为: 5 1 2 5 5 5 5 向pos位置插入n个elem元素
deque<int>deq1;
deq1.push_back(1);
deq1.push_back(2);
deq1.push_back(3);
deq.insert(deq.begin(), deq1.begin(), deq1.end());//deq容器中元素为: 5 1 2 5 5 5 5 1 2 3
向pos位置插入(begin,end)区间元素
删除操作
deque容器两端删除数据
deq.pop_front();//deq容器中元素为:1 2 5 5 5 5 1 2 3 删除首部数据
deq.pop_back();//deq容器中元素为:1 2 5 5 5 5 1 2 删除尾部数据
erase(pos);//删除pos位置的元素,返回下个数据的位置,pos用迭代器表示位置
deque<int>::iterator a = deq.erase(deq.begin());//deq容器中元素为2 5 5 5 5 1 2
cout << "a为:" << *a << endl;//a为2
注意:
push_back则先执行构造再移动node,而push_front是先移动node再进行构造,实现的差异主要是finish是指向最后一个元素的后一个地址,而first指向的是第一个元素的地址,下面pop也是一样的。
deque源码里还有一些其它的成员函数:
clear:删除所有的元素,分两步执行:
首先,从第二个数组开始到倒数第二个数组一次性全部删除,这样做是考虑到中间的数组肯定都是满的,前后两个数组则不一定是满的,最后删除前后两个数组元素。
deque的swap操作:只是交换了start,finish,map,并没有交换所有的元素。
resize:重新将deque进行调整,实现方式与list一样。
析构函数:分步释放内存。
deque的迭代器
deque是分段连续空间。维护其整体连续假象的任务,这就落在迭代器的operator++和operator--两个运算子上。
deque迭代器应该具备什么结构。首先,他必须能够指出分段连续空间(亦即缓冲区)在哪里,其次他必须能够判断自己是否已经处于其所在缓冲区的边缘,如果是,一旦前进或后退时必须跳跃至下一个或上一个缓存区。为了能够正确跳跃,deque必须随时掌握管控中心(map)。
所以迭代器在前进和后退时就需要判断是否为当前缓冲区的最后一个元素或者是第一个元素。
六. 总结
deque实际上是在功能上合并了vector和list。
优点:
随机访问方便,即支持[]操作和vector.at();
在内部方便的进行插入和删除操作;
可在两端进行push、pop。
缺点:
因为涉及数据结构的维护比较复杂,采用分段连续空间,所以占有内存相对多。
使用区别:
如果需要高效的随机存储,而不在乎插入和删除的效率,则使用vector。
如果需要大量的插入和删除,而不关心随机存取,则应使用list。
和list。
优点:
随机访问方便,即支持[]操作和vector.at();
在内部方便的进行插入和删除操作;
可在两端进行push、pop。
缺点:
因为涉及数据结构的维护比较复杂,采用分段连续空间,所以占有内存相对多。
使用区别:
如果需要高效的随机存储,而不在乎插入和删除的效率,则使用vector。
如果需要大量的插入和删除,而不关心随机存取,则应使用list。
如果需要随机存取,且关心亮度数据的插入和删除,则应使用deque。