重点
:deque
: double-end queue,双端队列std::deque
不像序列式容器那样将元素存储在连续的内存中,而是采用多个连续的存储块,在一个映射结构中对这些内存块及顺序进行跟踪。优点
支持[ ]操作符和at()。首尾两端的push
、pop
操作为O(1)
,支持其他位置插入和删除操作O(n)
。对于长序列数据,扩容代价低。缺点
:内存占用较多。
1. 底层原理
1.1 基础结构
std::deque
结构上是一种双向开口的连续线性空间。可以在其头尾两端进行元素的插入和删除操作,当然,vector
也可以在头尾两端插入元素,但是在其头部操作效率非常低。std::deque
与vector
不同点在于:
vector
使用单个数组,需要偶尔重新分配以进行增长。如对于非常长的序列,vector
重新分配内存代价非常高。deque
是动态的以分段的内存块组合而成,随时可以增加一段新的内存空间拼接起来,因此不能保证其中所有元素存储在相邻的存储区域:通过指针偏移操作来访问deque中元素的行为,会导致访问出错。但这也使得deque
可在某些情况下扩容代价更低(如非常长的序列)。
1.2 存储空间
上面提到deque
内部是分段的连续内存空间,为了将内存空间链接在一起、以及支持随机访问,其内部包含一个有 中控器
(map数组)。中控器中是一块小的连续空间,其中每个元素都是一个指针,每一个指针指向一段连续的内存空间(缓冲区
),缓冲区才是deque的储存空间主体,结构如下图。
通过建立 map数组
,deque
申请的这些分段的连续空间就能实现“整体连续”的效果。换句话说,当 deque
需要在头部或尾部增加存储空间时,它会申请一段新的连续空间,同时在 map
数组的开头或结尾添加指向该空间的指针,由此该空间就串接到了 deque 容器的头部或尾部。
此外,如果 map
数组满了,将申请一块更大的连续空间供 map
数组使用,将原有数据(指针)拷贝到新的数组中,然后释放旧的空间。
1.3 内部迭代器实现
由上述可知,deque
底层将序列中的元素分别存储到了不同段的连续空间中,因此要想实现迭代器的功能,必须先解决如下 2 个问题:
-
迭代器在遍历
deque
容器时,必须能够确认各个连续空间在map
数组中的位置; -
迭代器在遍历某个缓冲区(连续的内存空间)时,需判断自己是否已经处于该缓冲区的边缘。如果是,则一旦前进或者后退,就需要跳跃到上一个或者下一个缓冲区中。因此,迭代器内部包含 4 个指针:
cur 指向当前正在遍历的元素 first 指向当前 缓冲区 的首地址 last 指向当前 缓冲区 的末尾地址 node 指向 `map` 数组中存储的指向当前 缓冲区 的指针
如上述三点介绍的那样,这就是
deque
的底层原理。
2. 构造函数
deque
构造函数有6种,其函数原型如下所示:
[1] explicit deque(const allocator_type& alloc = allocator_type());
[2] explicit deque(size_type n);
deque(size_type n, const value_type& val,
const allocator_type& alloc = allocator_type());
[3] template <class InputIterator>
deque(InputIterator first, InputIterator last,
const allocator_type& alloc = allocator_type());
[4] deque(const deque& x);
deque(const deque& x, const allocator_type& alloc);
[5] deque(deque&& x);
deque(deque&& x, const allocator_type& alloc);
[6] deque(initializer_list<value_type> il,
const allocator_type& alloc = allocator_type());
其中:
- [1] 创建一个空的容器,没有元素。
- [2] 创建一个包含
n
个val
的容器。 - [3] 通过两个迭代器对应的半开区间
[first,last)
创建容器,每个元素通过emplace
构建,元素顺序与区间内顺序相同。 - [4] 复制构造函数
- [5] 移动构造函数
- [6] 复制
initializer_list
对象中的元素创建一个容器,顺序与il
相同。
[注释]:initializer_list
是C++11提供的新数据类型,和vector一样,initializer_list也是一种模板类型。可以使用.begin(), .end() 等方法。
例: 代码中构造函数使用顺序与上述相同
int main ()
{
unsigned int i;
std::deque<int> first; // [1]. empty deque of ints
std::deque<int> second(4,100); // [2]. four ints with value 100
std::deque<int> third(second.begin(),second.end()); // [3]. iterating through second
std::deque<int> fourth(third); // [4]. a copy of third
std::deque<int> fifth(std::move(third)); // [5]. a move of third
int myints[] = {16,2,77,29};
std::deque<int> sixth(myints, myints + sizeof(myints) / sizeof(int) ); // [6]. with initializer_list
std::cout << "The contents of sixth are:";
for (std::deque<int>::iterator it = sixth.begin(); it!=sixth.end(); ++it)
std::cout << ' ' << *it;
std::cout << '\n'; // The contents of sixth are: 16 2 77 29
return 0;
}
3. 常用函数
-
std::deque
中成员函数如下图所示。
-
C++ vector总结中存在
std::vector
相同功能成员函数,此处便不在详细介绍,std::deque
详情点击此处 c++plusplus。 -
此处简单叙述下各函数功能。
assign 设置容器中元素 at 通过下标随机访问,无效下标将抛出异常 back 返回最后一个元素引用 begin 返回正序迭代器,指向第一个元素 cbegin c++11新增,返回const的迭代器 cend c++11新增,返回const的迭代器 clear 清空容器,size=0 crbegin c++11新增,返回const的迭代器 crend c++11新增,返回const的迭代器 emplace 移动语义,指定位置插入 emplace_back 移动语义,尾部位置插入 emplace_front 移动语义,头部位置插入 empty 返回容器是否为空 end 返回正序迭代器,指向最后一个元素后+1的位置 erase 删除 front 返回头元素引用 insert 插入 max_size 容器当前最大容量 operator= 重载= operator[] 通过下标随机访问,无效下标将报错 pop_back 尾部删除元素 pop_front 头部删除元素 push_back 尾部插入 push_front 头部插入 rbegin 返回一个逆序迭代器,它指向容器的最后一个元素 rend 返回一个逆序迭代器,它指向容器c的第一个元素前面一个位置 resize 重设容器大小 size 返回容器元素个数 swap 交换当前容器与参数容器中元素 end...
参考链接:
[1] http://www.cplusplus.com/reference/deque/deque/
[2] https://blog.csdn.net/u012940886/article/details/80529721
[3] http://c.biancheng.net/view/6908.html