Deque是一种双向开口的连续线性空间。能在头尾两端分别做元素的插入和删除,而且是在常数的时间内完成。虽然Vector也可以在首端进行元素的插入和删除(利用insert和erase),但效率差(涉及到整个数组的移动),无法被接受。
deque的定义
#include <deque> // deque属于std命名域的,因此需要通过命名限定
deque<int> a; // 定义一个int类型的双端队列a
deque<int> a(10); // 定义一个int类型的双端队列a,并设置初始大小为10
deque<int> a(10, 1); // 定义一个int类型的双端队列a,并设置初始大小为10且初始值都为1
deque<int> b(a); // 定义并用双端队列a初始化双端队列b
deque<int> b(a.begin(), a.begin()+3); // 将双端队列a中从第0个到第2个(共3个)作为双端队列b的初始值
int n[] = { 1, 2, 3, 4, 5 };
deque<int> a(n, n + 5);
deque<int> a(&n[1], &n[4]); // 将n[1]、n[2]、n[3]作为双端队列a的初值
deque的使用
操作 | 描述 |
deq.size() | 返回容器中的元素数量。 |
deq.max_size() | 返回容器可能存储的最大元素数量。 |
deq.resize(n) | 改变容器的大小为 n 个元素。如果 n 大于当前大小,新位置会被默认插入元素进行填充。 |
deq.empty() | 检查容器是否为空。 |
deq.push_front(const T& x) | 在容器的头部插入一个元素 x。 |
deq.push_back(const T& x) | 在容器的末尾添加一个元素 x。 |
deq.insert(iterator it, const T& x) | 在迭代器 it 指定的位置插入一个元素 x。 |
deq.insert(iterator it, int n, const T& x) | 在迭代器 it 指定的位置插入 n 个相同的元素 x。 |
deq.insert(iterator it, iterator first, iterator last) | 在迭代器 it 指定的位置插入另一个容器的 [first, last) 区间的数据。 |
deq.pop_front() | 删除容器头部的一个元素。 |
deq.pop_back() | 删除容器末尾的一个元素。 |
deq.erase(iterator it) | 删除迭代器 it 指定的元素。 |
deq.erase(iterator first, iterator last) | 删除迭代器 [first, last) 区间的元素。 |
deq.clear() | 删除容器中的所有元素。 |
deq[1] | 通过下标访问元素,不检查越界。 |
deq.at(1) | 通过 at 方法访问元素,如果越界会抛出异常。 |
deq.front() | 访问第一个元素。 |
deq.back() | 访问最后一个元素。 |
deq.assign(int nSize, const T& x) | 用 nSize 个 x 值的副本替换容器中的内容。 |
swap(deque&) | 交换两个同类型容器的元素。 |
deq.begin() | 返回指向容器第一个元素的迭代器。 |
deq.end() | 返回指向容器最后一个元素之后的迭代器。 |
deq.cbegin() | 返回指向容器第一个元素的常量迭代器。 |
deq.cend() | 返回指向容器最后一个元素之后的常量迭代器。 |
deq.rbegin() | 返回指向容器最后一个元素的反向迭代器。 |
deq.rend() | 返回指向容器第一个元素前面位置的反向迭代器。 |
#include <iostream>
#include <deque>int main() {std::deque<int> deq;// 头部添加元素deq.push_front(1);// 末尾添加元素deq.push_back(2);// 在任意位置插入一个元素auto it = deq.begin();deq.insert(it + 1, 3); // 在第一个元素之后插入3// 在任意位置插入 n 个相同的元素deq.insert(it, 2, 4); // 在头部插入两个4// 插入另一个deque的[first, last)间的数据std::deque<int> another_deq{5, 6, 7};deq.insert(deq.end(), another_deq.begin(), another_deq.end());// 头部删除元素deq.pop_front();// 末尾删除元素deq.pop_back();// 任意位置删除一个元素deq.erase(deq.begin() + 1); // 删除第二个元素// 删除[first, last)之间的元素deq.erase(deq.begin(), deq.begin() + 2); // 删除前两个元素// 清空所有元素deq.clear();// 多个元素赋值deq.assign(3, 8); // deq现在有三个元素,每个都是8// 交换两个同类型容器的元素std::deque<int> deq2;deq2.push_back(9);deq.swap(deq2);// 访问第一个元素和最后一个元素std::cout << "Front: " << deq.front() << ", Back: " << deq.back() << std::endl;// 使用迭代器遍历dequestd::cout << "Contents of deq:";for(auto iter = deq.begin(); iter != deq.end(); ++iter) {std::cout << ' ' << *iter;}std::cout << std::endl;// 使用下标访问if (!deq.empty()) {std::cout << "Element at index 0: " << deq[0] << std::endl;std::cout << "Element at index 0 (using at): " << deq.at(0) << std::endl;}// 使用反向迭代器遍历dequestd::cout << "Contents of deq in reverse:";for(auto riter = deq.rbegin(); riter != deq.rend(); ++riter) {std::cout << ' ' << *riter;}std::cout << std::endl;return 0;
}
deque数据结构
deque维护一个指向map的指针外和 start和finish两个迭代器,分别指向第一缓冲区的第一个元素和最后缓冲区的最后一个元素的下一位置。
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;typedef pointer* map_pointer;
public:typedef __deque_iterator<T, T&, T*, BufSiz> iterator;
protected:iterator start; // 第一个节点iterator finish; // 最后一个结点map_pointer map;size_type map_size;
public:iterator begin() { return start; }iterator end() { return finish; }reference operator[](size_type n) { return start[difference_type(n)]; } // 调用迭代器重载的operator[]// ...
}
- 指针数组 (map):是一个指针数组,维护指向缓冲区的指针,这些缓冲区构成了 deque 的内存空间。map 左右两边预留有空间,允许前后扩展。
- 迭代器 (start, finish):分别指向 deque 中第一个元素和最后一个元素之后的位置,通过迭代器可以快速访问或修改 deque 的元素。
- map_size 表示 map 中指针的数量,即数据块的总数。
- map 的左右两边留有剩余空间是 deque 设计的一个关键特点。这些剩余空间允许快速地在 deque 的前端或后端添加新的分段,而无需重新分配整个 map。这样,即使是在 deque 的两端插入或删除元素,操作的时间复杂度也能保持在常数时间内。
- erase 和 insert 函数实现了元素的删除和插入操作,它们采用了不同的策略来最小化元素移动的开销。具体策略取决于操作位置相对于 deque 中间位置的不同,以减少需要移动的元素数量。
当 deque 需要扩展其存储空间时,它会分配一个新的 map,这个新 map 有更多的指针,可以指向更多的缓冲区。然后,deque 会将现有的缓冲区指针从旧 map 复制到新 map 中,并适当地调整 start 和 finish 迭代器,以反映新的布局。这个过程使得 deque 能够在维持高效插入和删除操作的同时,提供对元素的快速随机访问。
当 deque 的一个分段被填满时,它会动态地添加一个新的分段来存储更多的元素。同样地,如果 deque 的大小减少,一些分段可能会被释放以节约空间。deque 动态调整其分段的能力,使得它在存储大量数据时非常灵活和高效。
deque迭代器
deque 使用特化的迭代器(__deque_iterator),以适应其复杂的内部结构。这个迭代器能够跨越不同的数据块,为外部提供连续访问的接口。
template <class T, class Ref, class Ptr, size_t BufSiz>
struct __deque_iterator { //并未继承std::iterator,为了符合STL规范,要对五个相应类型都做定义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)); }typedef random_access_iterator_tag iterator_category; //1typedef T value_type; //2typedef Ptr pointer; //3typedef Ref reference; //4typedef size_t size_type;typedef ptrdiff_t difference_type; //5typedef T** map_pointer;typedef __deque_iterator self;//保持与空间的联结T* cur; //所指是当前缓冲区里的当前元素T* first; //所指是当前缓冲区里的头元素T* last; //所指是当前缓冲区里的尾元素的下一位(含备用空间)map_pointer node; //指向map的头...
}
__deque_iterator 结构体
类型定义
- 迭代器对外提供了与 STL 兼容的五种类型定义,包括迭代器类别(iterator_category),值类型(value_type),指针类型(pointer),引用类型(reference),以及差异类型(difference_type)。
- 静态成员函数 buffer_size计算并返回每个缓冲区(即段)可以容纳的元素数量。
- cur:指向当前缓冲区中当前元素的指针。
- first:指向当前缓冲区第一个元素的指针。
- last:指向当前缓冲区最后一个元素之后位置的指针,即标记缓冲区结束的边界。
- node:指向当前缓冲区所在 map 中的指针,即指向管理当前缓冲区指针的 map 元素。
当迭代器前进(++)或后退(--)时,它首先检查是否即将超出当前缓冲区的边界(通过比较 cur 与 first 或 last)。如果是这种情况,迭代器会跳转到相邻的缓冲区(通过修改 node 指向的指针),并相应地更新 cur、first 和 last,以指向新缓冲区的正确位置。
这种设计使得迭代器能够平滑地跨越 deque 的分段存储,对使用者隐藏了复杂的内部结构。用户可以像操作一个连续存储的数组那样,使用 deque 迭代器进行随机访问和遍历,而不需要关心背后的分段逻辑。迭代器抽象了 deque 的分段存储细节,使得从用户的角度看,deque 像是一个完全连续的数据结构。
参考
《STL源码剖析》
https://www.cnblogs.com/linuxAndMcu/p/10260124.html
https://www.cnblogs.com/MisakiJH/p/11765567.html