SGI STL双端队列deque
deque vs vector
vector是单向开口的连续线性空间,适合在尾端插入、删除元素,O(1);deque是双向开口的连续线性空间,适合在头尾两端分别进行元素的插入和删除操作,O(1)。
vector也可以在头尾插入、删除元素,不过在头部操作的效率非常低,O(n)。
可以指定,deque和vector区别在于:
1)deque允许常数时间在头端插入、删除元素,vector需要O(n)时间;
2)deque没有容量(capacity)概念,因为是动态地以分段连续空间组合而成,随时可以增加一段新空间并链接起来。而vector容量不足时,会重新配置一块更大的连续空间,然后将元素复制到新空间并释放旧空间。
3)deque提供Random Access Iterator,但其迭代器并不是普通指针,访问元素效率比vector低。因此,应尽可能选择用vector。对deque的排序,可以先将deque完整复制到一个vector,排序后再复制回deque。
deque中控器
deque是由一段一段的定量连续空间构成。一旦有必要在deque的首端或尾端增加新空间,就会配置一段连续空间,然后串接在deque的首端或尾端。而deque的最大任务就是,在这些分段的连续空间上,维护其整体连续的假象,并提供随机存取的接口。避免vector那种“重新配置、复制、释放”的扩容步骤,代价是复杂的迭代器结构。
为了维护分段空间整体连续的假象,数据结构和迭代器前进、后退等操作繁琐,必须有中央控制。
deque采用一块所谓map作为主控,不是STL的map容器,而是一小块连续空间,可以看作是一个数组,map中每个元素称为节点(即node),指向一个缓冲区,用于存放用户数据。SGI STL允许用户指定缓冲区大小,默认值0表示使用512byte缓冲区。
旧版的SGI STL deque包含对底层空间的维护;而SGI STL v3.3中,deque已经将对底层空间的维护,包括map,封装到了其基类_Deque_base中。
这里为了方便,采用SGI STL v2.03 进行分析。
// deque.h from SGI STL v2.03
template <class T, class Alloc = alloc, size_t BufSiz = 0>
class deque {
public: // Basic types
// 定义内嵌类型
typedef T value_type;
typedef value_type* pointer;
typedef value_type& reference;
typedef const value_type& const_reference;
typedef size_t size_type;
typedef ptrdiff_t difference_type;
...
protected: // Internal typedefs
typedef pointer* map_pointer;
...
protected: // Data members
...
map_pointer map;
size_type map_size;
...
};
deque 迭代器
deque是分段连续空间。如何维护“整体连续”的假象?
这个重任落在operator++和operator--身上,而其实现离不开deque迭代器。deque迭代器必须能1)指出分段连续空间(实际存储数据的缓冲区)的地址;2)判断自己是否已经处于其所在缓冲区的边缘,如果是一旦前进或后退,就要跳跃至下一个或上一个缓冲区。而为了能正确跳跃,必须通过中控器(map)。
deque迭代器(__deque_iterator)的数据结构:
// deque.h from SGI STL v2.03
template <class T, class Ref, class Ptr, size_t BufSiz>
struct __deque_iterator { // 没有继承自 std::iterator
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)); }
// 未继承自 std::iterator, 须自定义标准迭代器要求的5个迭代器关联类型
typedef random_access_iterator_tag iterator_category; // 1
typedef T value_type; // 2
typedef Ptr pointer; // 3
typedef Ref reference; // 4
typedef size_t size_type;
typedef ptrdiff_t difference_type; // 5
typedef T** map_pointer;
typedef __deque_iterator self;
// 保持与容器的联结
T* cur; // 迭代器所指缓冲区的当前(current)元素
T* first; // 迭代器所指缓冲区的头
T* last; // 迭代器所指缓冲区的尾(含备用空间)
map_pointer node; // 指向中控器
...
};
buffer_size()决定缓冲区大小,其内部调用了全局函数__deque_buf_size():
// deque.h from SGI STL v2.03
// Note: this function is simply a kludge to work around several compilers'
// bugs in handling constant expressions.
// 如果n不为0, 传回n, 表示buffer size 由用户定义
// 如果n为0, 表示buffer size使用默认值, 那么
// 如果sz(元素大小, sizeof(value_type))小于512, 传回512/sz,
// 如果sz不小于512, 传回1
inline size_t __deque_buf_size(size_t n, size_t sz)
{
return n != 0 ? n : (sz < 512 ? size_t(512 / sz) : size_t(1));
}
deque的中控器,缓冲区,迭代器的相互关系:
假设我们现在有一个deque
20个元素,共需要20/3=8个缓冲区(向上取整),所以map内需要3个节点,分别指向这3个缓冲区:迭代器start内的cur指针指向第一个缓冲区的第一个元素,迭代器finish内的cur指针指向最后一个缓冲区的最后一个元素。
每个缓冲区只能提供32byte空间,3个缓冲区就是96byte,20个元素(每个占4byte),共计80byte,也就是说最后会剩16byte,也就是4个元素大小。
deque迭代器内对各种指针运算都做了重载,如各种指针运算(加、减、前进、后退等),特别需要注意的是:一旦移动指针遇到缓冲区边界时,就要特别小心,视前进或后退具体情况而定,可能需要调用set_node()跳跃到下一个缓冲区。
// __deque_iterator<> member function
void set_node(map_pointer new_node) {
node = new_node;
first = *new_node;
last = first + buffer_size();
}
// 下面各个重载函数是__deque_iterator<>成功运作的关键
reference operator*() const { return *cur; }
pointer operator->() const { return &(operator*()); }
difference_type operator-(const self& x) const {
return buffer_size() * (node - x.node - 1) +
(cur - first) + (x.last - x.cur);
}
// prefix increment operator
self& operator++() { // 前置式
++cur; // 切换至下一个元素
if (cur == last) { // 如果已经到达缓冲区末尾
set_node(node + 1); // 切换至下一个node, 即下一个缓冲区
cur = first;
}
return *this;
}
// post increment operator
self operator++(int) { // 后置式
self tmp = *this;
++*this; // 注意这里调用前置式递增
return tmp;
}
// prefix decrement operator
self& operator--() {
if (cur == first) { // 如果已经达到所在缓冲区头端
set_node(node - 1); // 就切换至前一个node, 即前一个缓冲区
cur = last; // 切换至缓冲区最后一个元素(前一个缓冲区的最后一个元素)
}
--cur; // 切换至前一个元素
return *this;
}
// post decrement operator
self operator--(int) {
self tmp = *this;
--*this; // 注意这里调用前置式递减
return tmp;
}
// 以下实现随机存取. 迭代器可以直接跳跃n个距离
// 前进n个距离
self& operator+=(difference_type n) { // 形如 x += n
difference_type offset = n + (cur - first);
if (offset >= 0 && offset < buffer_size())
cur += n;
else {
difference_type node_offset =
offset > 0 ? offset / buffer_size()
: -difference_type((-offset - 1) / buffer_size()) - 1;
set_node(node + node_offset);
cur = first + (offset - node_offset * buffer_size());
}
return *this;
}
self operator+(difference_type n) const { // 形如 x = x + n
self tmp = *this;
return tmp += n;
}
// 后退n个距离
self& operator-=(difference_type n) { return *this += -n; } // 形如 x -= n
self operator-(difference_type n) const { // 形如 x = x - n
self tmp = *this;
return tmp -= n;
}
// 随机存取, 迭代器直接跳跃n个距离, 调用了operator+
reference operator[](difference_type n) const { return *(*this + n); }
// 以下实现大小判断
bool operator==(const self& x) const { return cur == x.cur; }
bool operator!=(const self& x) const { return !(*this == x); }
bool operator<(const self& x) const {
return (node == x.node) ? (cur < x.cur) : (node < x.node);
}
deque 数据结构
deque维护一个指向map的指针(map),start、finish 两个迭代器:指向第一个缓冲区、最后一个缓冲区。维护当前map大小(map_size)。当map所能容纳的节点数不足时,就需要扩容,为其配置一块更大map,相当于vector扩容。
// See __deque_buf_size(). The only reason that the default value is 0
// is as a workaround for bugs in the way that some compilers handle
// constant expressions.
template <class T, class Alloc = alloc, size_t BufSiz = 0>
class deque {
public: // Basic types
// 定义内嵌类型
typedef T value_type;
typedef value_type* pointer;
typedef value_type& reference;
typedef const value_type& const_reference;
typedef size_t size_type;
typedef ptrdiff_t difference_type;
public: // Iterators
typedef __deque_iterator<T, T&, T*, BufSiz> iterator;
typedef __deque_iterator<T, const T&, const T&, BufSiz> const_iterator;
...
protected: // Internal typedefs
typedef pointer* map_pointer;
protected: // Data members
iterator start; // map中第一个node
iterator finish; // map中最后一个node
map_pointer map; // 指向map, map可以看做是一个数组, 是一块连续空间, 每个元素都是个指针, 指向一个node
size_type map_size; // map内有多少个指针, 即对应有多少个缓冲区
...
};
有了上面的结构,再实现下面获取头尾迭代器,元素个数等信息,就很容易了
public: // Basic accessors
iterator begin() { return start; }
iterator end() { return finish; }
const_iterator begin() const { return start; }
const_iterator end() const { return finish; }
// 反向迭代器
reverse_iterator rbegin() { return reverse_iterator(finish); }
reverse_iterator rend() { return reverse_iterator(start); }
const_reverse_iterator rbegin() const {
return const_reverse_iterator(finish);
}
const_reverse_iterator rend() const {
return const_reverse_iterator(start);
}
reference operator[](size_type n) { return start[n]; } // 调用__deque_iterator<>::operator[]
const_reference operator[](size_type n) const { return start[n]; }
reference front() { return *start; } // 调用__deque_iterator<>::operator*
reference back() {
iterator tmp = finish;
--tmp; // 调用__deque_iterator<>::operator--
return *tmp; // 调用__deque_iterator<>::operator*
}
const_reference front() const { return *start; }
const_reference back() const {
const_iterator tmp = finish;
--tmp;
return *tmp;
}
// 下面最后有2个';', 奇观但合语法
// 求deque的元素个数
size_type size() const { return finish - start;; } // 调用__deque_iterator<>::operator-
// 求deque理论上最大元素个数0xFFFFFFFF (UINT32_MAX)
size_type max_size() const { return size_type(-1); }
// 判断deque是否为空, 即元素个数是否为0
bool empty() const { return finish == start; }
deque的构造与内存管理
构造deque及插入元素示例
站在客户角度使用deque,观察deque构造方式、大小变化(size()),插入数据方式(push_front, push_back)。
// 客户角度测试deque
int main()
{
deque<int, alloc, 32> ideq(20, 9); // 构造deque, 20个值为9的元素, 缓冲区大小32byte
cout << "size = " << ideq.size() << endl; // 20
// 每个元素设新值
for (int i = 0; i < ideq.size(); ++i) {
ideq[i] = i;
}
for (int i = 0; i < ideq.size(); ++i) {
cout << ideq[i] << ' '; // 值分别为0 1 2 3 ... 19
}
cout << endl;
// 在尾端增加3个元素, 值0,1,2
for (int i = 0; i < 3; ++i) {
ideq.push_back(i);
}
for (int i = 0; i < ideq.size(); ++i) {
cout << ideq[i] << ' '; // 值分别为0 1 2 3 ... 19 0 1 2
}
cout << endl;
cout << "size=" << ideq.size() << endl; // size=23
// 尾端添加1个元素, 值3
ideq.push_back(3);
for (int i = 0; i < ideq.size(); ++i) {
cout << ideq[i] << ' '; // 值分别为0 1 2 3 ... 19 0 1 2 3
}
cout << endl;
cout << "size=" << ideq.size() << endl; // size=24
// 在最前端增加1个元素, 值99
ideq.push_front(99);
for (int i = 0; i < ideq.size(); ++i) {
cout << ideq[i] << ' '; // 值分别为99 0 1 2 3 ... 19 0 1 2 3
}
cout << endl;
cout << "size=" << ideq.size() << endl; // size=25
// 在最前端增加2个元素, 值分别为98 97
ideq.push_front(98);
ideq.push_front(97);
for (int i = 0; i < ideq.size(); ++i) {
cout << ideq[i] << ' '; // 值分别为97 98 99 0 1 2 3 ... 19 0 1 2 3
}
cout << endl;
cout << "size=" << ideq.size() << endl; // size=27
// 查找值为99的元素并打印
deque<int, alloc, 32>::iterator it;
it = std::find(ideq.begin(), ideq.end(), 99);
cout << *it << endl; // 99
cout << *(it._M_cur) << endl; // 99
return 0;
}
deque缓冲区是如何扩充的?
分步讲解。
1)用户程序声明一个deque。
缓冲区大小32byte(第三个模板参数),保留20个元素空间,每个元素初值9。
deque<int, alloc, 32> ideq(20, 9);
2)deque自定义空间配置器。
protected: // Internal typedefs
// deque内嵌的专属空间配置器, 每次配置一个元素大小
typedef simple_alloc<value_type, Alloc> data_allocator;
// deque内嵌的专属空间配置器, 每次配置一个指针大小
typedef simple_alloc<pointer, Alloc> map_allocator;
3)调用构造函数constructor,构造dque
4)调用push_back、push_front,插入数据
下面就构造函数、push_back、push_front等方面进行分析。
constructor
并提供一个constructor:
// 构造函数
deque(int n, const value_type& value)
: start(), finish(), map(0), map_size(0) {
fill_initialize(n, value);
}
其内调用的fill_initialize(),负责产生并安排好deque的结构,并设置元素初值
// 负责产生并安排好deque的结构, 并设置元素初值
// @tparam T 元素类型
// @tparam Alloc 内存分配子
// @tparam BufSiz 缓冲区大小
// @param n 元素个数
// @param value 元素初值
template <class T, class Alloc, size_t BufSize>
void deque<T, Alloc, BufSize>::fill_initialize(size_type n,
const value_type& value) {
create_map_and_nodes(n); // 把deque的结构都产生并安排好
map_pointer cur;
try {
// 为每个节点的缓冲区设置初值
for (cur = start.node; cur < finish.node; ++cur)
uninitialized_fill(*cur, *cur + buffer_size(), value);
// 最后一个节点的设定稍有不同, 因为尾端可能有备用空间不需要设初值
uninitialized_fill(finish.first, finish.cur, value);
}
catch(...) {
// 发生异常时rollback
for (map_pointer n = start.node; n < cur; ++n)
destroy(*n, *n + buffer_size());
destroy_map_and_nodes();
throw;
}
}
内部调用的create_map_and_nodes(),负责产生并安排好deque的结构。注意和fill_initialize区别,前者并不负责初值填充。
// 负责产生并安排好deque的结构
// @param num_elements 元素个数
template <class T, class Alloc, size_t BufSize>
void deque<T, Alloc, BufSize>::create_map_and_nodes(size_type num_elements) {
// 需要的节点数 = 元素个数 / 每个缓冲区可容纳元素个数 + 1
size_type num_nodes = num_elements / buffer_size() + 1;
// map要管理的节点数量. 最少是8个, 最多是 所需节点数+2
// 前后各预留一个, 扩充时可用
map_size = max(initial_map_size(), num_nodes + 2);
// 配置一个 具有map_size个节点的map
map = map_allocator::allocate(map_size);
// 让nstart指向map管理node的第一个(如果开头有额外分配的+1节点, 就忽略)
// 让nfinish指向map管理node的最后一个(如果末尾有额外分配的+1节点, 就忽略)
// 总之, [nstart, nfinish] 节点数为num_nodes (而非num_nodes+2)
map_pointer nstart = map + (map_size - num_nodes) / 2;
map_pointer nfinish = nstart + num_nodes - 1;
map_pointer cur;
try {
// 为map内每个节点配置缓冲区. 所有缓冲区加起来就是deque的可用空间,
// 最后一个缓冲区可能有剩余空间
for (cur = nstart; cur <= nfinish; ++cur)
*cur = allocate_node();
}
catch(...) {
// commit or rollback
for (map_pointer n = nstart; n < cur; ++n)
deallocate_node(*n);
map_allocator::deallocate(map, map_size);
throw;
}
// 为deque内两个迭代器start和end设置正确内容
// 注意这里的finish跟普通迭代器有区别, finish指向的node是有缓冲区, 是有意义的;
// 普通迭代器的最后一个(end), 通常是没有对应数据的, 只是一个标记
start.set_node(nstart);
finish.set_node(nfinish);
start.cur = start.first;
finish.cur = finish.first + num_elements % buffer_size();
}
// map初始大小, 管理的节点数量
static size_type initial_map_size() { return 8; }
// 每个缓冲区可容纳元素个数
static size_t buffer_size() {return __deque_buf_size(BufSiz, sizeof(T)); }
以示例中一段为每个元素重新设置,并在末尾插入3个新元素为例:
// 摘自前面示例程序
for (int i = 0; i < ideq.size(); ++i)
ideq[i] = i;
for (int i = 0; i < 3; ++i
ideq.push_back(i);
运行完这个代码片段,此时,最后一个缓冲区仍有4个备用元素空间,所以不会引起缓冲区的再配置。此时,deque状态如下图:
push_back
push_back在deque的尾端插入一个新元素。
public: // push_* and pop_*
// 向末尾插入一个元素
void push_back(const value_type& t) {
if (finish.cur != finish.last - 1) {
// 最后缓冲区尚有一个以上备用空间
construct(finish.cur, t); // 直接在备用空间上构造元素
++finish.cur; // 调整最后缓冲区的使用状态
}
else // 最后缓冲区已无(或只剩1个)元素备用空间
push_back_aux(t);
}
// Called only if finish.cur == finish.last - 1.
// 最后一个缓冲区备用空间不足(为1)时, 才会被调用
// 会配置一整块新的缓冲区, 再设置新元素, 更新相应迭代器状态
template <class T, class Alloc, size_t BufSize>
void deque<T, Alloc, BufSize>::push_back_aux(const value_type& t) {
value_type t_copy = t;
reserve_map_at_back(); // 若符合某种条件则必须重新换一个map, 为map扩容
*(finish.node + 1) = allocate_node(); // 配置一个新节点(缓冲区)
try {
construct(finish.cur, t_copy); // 调用构造函数, 针对插入元素设值
finish.set_node(finish.node + 1); // 改变finish, 令其指向新节点
finish.cur = finish.first; // 设定finish的状态
}
catch(...) {
// commit or rollback
deallocate_node(*(finish.node + 1));
throw;
}
}
其中,push_back_aux只有在最后一个缓冲区(迭代器finish所指buffer)的备用空间只剩一个元素剩余时,才会被调用。思路是先配置一整块新的缓冲区,然后再设置新元素的内容,再更新迭代器finish。
也就是说,在上面例子基础上,当备用空间只剩1个元素大小空间时,继续调用push_back()插入新元素3,会导致配置信缓冲区,更新相应的数据结构,备用空间也由原来的1变成新缓冲区的8。deque结构状态变化,如下图所示:
push_front
push_front在deque的头端插入一个新元素。
// 向头端插入一个元素
void push_front(const value_type& t) {
if (start.cur != start.first) {
// 第一个缓冲区尚有一个以上备用空间
construct(start.cur - 1, t); // 直接在备用空间上构造元素
--start.cur; // 调整第一个缓冲区的使用状态
}
else // 第一个缓冲区无备用空间
push_front_aux(t);
}
// Called only if start.cur == start.first.
// 只有当第一个缓冲区没有备用空间时, 才会被调用
template <class T, class Alloc, size_t BufSize>
void deque<T, Alloc, BufSize>::push_front_aux(const value_type& t) {
value_type t_copy = t;
reserve_map_at_front(); // 如果符合某种条件则必须换一个map, 为map扩容
*(start.node - 1) = allocate_node(); // 配置一个新节点(即缓冲区)
try {
start.set_node(start.node - 1); // 跳转到第一个缓冲区的前一个缓冲区(新配置的缓冲区), 新缓冲区成为新的第一个缓冲区
start.cur = start.last - 1; // 更新第一个缓冲区的待插入数据位置(start.cur)为第一个缓冲区末尾
construct(start.cur, t_copy); // 在第一个缓冲区末尾构造元素
}
catch(...) {
// commit or rollback
start.set_node(start.node + 1);
start.cur = start.first;
deallocate_node(*(start.node - 1));
throw;
}
}
在push_back向尾端插入3的例子基础上,接着利用push_front向头端插入99,deque的结构状态变成如下图所示。由于第一个缓冲区前面已无备用空间,故需要调用push_front_aux()申请新的空间,第一个缓冲区备用空间也由原来的0增加为新值7。同时,也需要更新相应的迭代器状态。
如果借着调用push_front,向deque头部插入98,97,那么就可以直接在备用空间上构造新元素,而无需申请新的缓冲区,因为第一个缓冲区备用空间足够(并非为空)。deque的结构状态会变成:
push_back 和push_front中,分别调用了一个很重要的函数reserve_map_at_back(),reserve_map_at_front()。该函数用来在必要的时候,重新配置map,为其扩容。
// Makes sure the map has space for new nodes. Does not actually
// add the nodes. Can invalidate map pointers. (And consequently,
// deque iterators.)
// 如果map尾端的节点备用空间不足, 就换一个更大的map
void reserve_map_at_back (size_type nodes_to_add = 1) {
// 判断map尾端节点备用空间是否足够容纳nodes_to_add个node
if (nodes_to_add + 1 > map_size - (finish.node - map))
reallocate_map(nodes_to_add, false); // 重新配置一个map(配置更大的, 拷贝原来的到新的, 释放原来的)
}
// 如果map前端的节点备用空间不足, 就换一个更大的map
void reserve_map_at_front (size_type nodes_to_add = 1) {
// 判断map头端节点备用空间是否足够容纳nodes_to_add个node
if (nodes_to_add > start.node - map)
reallocate_map(nodes_to_add, true); // 重新配置一个map(配置更大的, 拷贝原来的到新的, 释放原来的)
}
// 满足一定条件时, 为map重新配置新空间, 相当于vector扩容
// 主要工作: 配置更大空间, 拷贝原来元素到新空间, 释放原来空间
template <class T, class Alloc, size_t BufSize>
void deque<T, Alloc, BufSize>::reallocate_map(size_type nodes_to_add,
bool add_at_front) {
size_type old_num_nodes = finish.node - start.node + 1; // 已经在使用的node数
size_type new_num_nodes = old_num_nodes + nodes_to_add; // 旧的node数 + 待添加的node数
map_pointer new_nstart;
if (map_size > 2 * new_num_nodes) {
// map空间足够: map现有大小超过2倍的新node数
new_nstart = map + (map_size - new_num_nodes) / 2
+ (add_at_front ? nodes_to_add : 0); // 计算新的第一个缓冲区对应node在map中位置
// 如果原来node起点更早, 就从左到右将内容拷贝到新区间
if (new_nstart < start.node)
copy(start.node, finish.node + 1, new_nstart);
// 如果原来的node起点更晚, 就从右到左将内容拷贝到新区间
else
copy_backward(start.node, finish.node + 1, new_nstart + old_num_nodes);
}
else {
size_type new_map_size = map_size + max(map_size, nodes_to_add) + 2;
// 配置一块空间, 准备给新map使用
map_pointer new_map = map_allocator::allocate(new_map_size);
new_nstart = new_map + (new_map_size - new_num_nodes) / 2
+ (add_at_front ? nodes_to_add : 0);
// 把原map内容拷贝过来
copy(start.node, finish.node + 1, new_nstart);
// 释放原map
map_allocator::deallocate(map, map_size);
// 设置新map的起始地址, 大小
map = new_map;
map_size = new_map_size;
}
// 更新迭代器start和finish
start.set_node(new_nstart);
finish.set_node(new_nstart + old_num_nodes - 1);
}
deque的元素操作
关于元素操作,只讲几个典型的member function。
pop_back
pop_back 从末尾弹出一个元素。可能会引起最后一个缓冲区释放。
// 从末尾弹出一个元素, 缓冲区为空时, 将缓冲区释放掉
void pop_back() {
if (finish.cur != finish.first) {
// 最后一个缓冲区有一个(或更多)元素
--finish.cur; // 调整指针, 相当于排除了最后元素
destroy(finish.cur); // 析构最后元素
}
else
// 最后一个缓冲区没有任何元素, 将缓冲区释放掉
pop_back_aux();
}
// Called only if finish.cur == finish.first.
// 只有当finish.cur == finish.first 时才会被调用, 用于释放最后一个缓冲区
template <class T, class Alloc, size_t BufSize>
void deque<T, Alloc, BufSize>:: pop_back_aux() {
deallocate_node(finish.first); // 释放最后一个缓冲区
finish.set_node(finish.node - 1); // 调整finish状态, 使其指向上一个缓冲区的最后一个元素
finish.cur = finish.last - 1;
destroy(finish.cur); // 析构该元素
}
pop_front
pop_front 从头端弹出一个元素。可能会引起第一个缓冲区释放。
// 从头端弹出一个元素, 缓冲区为空时, 将缓冲区释放掉
void pop_front() {
if (start.cur != start.last - 1) {
// 第一个缓冲区有1个以上元素
destroy(start.cur); // 将第一个元素析构
++start.cur; // 调整指针, 相当于排除了第一元素
}
else // 第一个缓冲区只有1个元素, 释放缓冲区
pop_front_aux();
}
// Called only if start.cur == start.last - 1. Note that if the deque
// has at least one element (a necessary precondition for this member
// function), and if start.cur == start.last, then the deque must have
// at least two nodes.
// 只有当start.cur == start.last - 1时才会被调用
template <class T, class Alloc, size_t BufSize>
void deque<T, Alloc, BufSize>::pop_front_aux() {
destroy(start.cur); // 将第一缓冲区的第一个元素析构
deallocate_node(start.first); // 释放第一个缓冲区
start.set_node(start.node + 1); // 调整start的状态, 使指向下一个缓冲区的第一个元素
start.cur = start.first;
}
erase
清除指定位置元素。清除元素后,会导致其他元素的移动,具体是前移,还是后移,取决于前后哪个部分的元素较少。
public: // Erase
// 清除pos所指元素
void erase(iterator pos) {
iterator next = pos;
++next;
// 如果清除点之前元素比较少( < 现有元素数量一半), 就移动清除点以前的元素
if (pos - start < size() / 2) {
copy_backward(start, pos, next); // 从后往前移动元素
pop_front(); // 移动完毕, 最前一个元素冗余, 去除之
}
else { // 清除点之后的元素比较少
copy(next, finish, pos); // 移动清除点之后的元素
pop_back(); // 移动完毕, 最后一个元素冗余, 去除之
}
}
清除指定区间[first, last)内所有元素。
// 清除[first, last)范围内所有元素
template <class T, class Alloc, size_t BufSize>
void deque<T, Alloc, BufSize>::erase(iterator first, iterator last) {
if (first == start && last == finish) // 如果清除区间是整个deque, 直接调用clear()
clear();
else {
difference_type n = last - first; // 清除区间长度
difference_type elems_before = first - start; // 清除区间前方元素个数
if (elems_before < (size() - n) / 2) { // 如果前方的元素较少, 向后移动前方元素(覆盖清除区间)
copy_backward(start, first, last); // 向后移动元素, [start, first) => [xxx, last)
iterator new_start = start + n; // 标记deque的新起点, new_start = last-(first-start) = start + n
destroy(start, new_start); // 移动完毕, 将冗余的元素析构
// 以下将冗余的缓冲区[start, new_start)释放
for (map_pointer cur = start.node; cur < new_start.node; ++cur)
data_allocator::deallocate(*cur, buffer_size());
start = new_start; // 设定deque新起点
}
else {
copy(last, finish, first); // 向前移动元素, [last, finish) => [first, xxx)
iterator new_finish = finish - n; // 标记deque的新尾点
destroy(new_finish, finish); // 移动完毕, 将冗余的元素析构, 要销毁的元素个数n = finish - new_finish
// 以下将冗余的缓冲区[new_finish+1, finish]释放
for (map_pointer cur = new_finish.node + 1; cur <= finish.node; ++cur)
data_allocator::deallocate(*cur, buffer_size());
finish = new_finish; // 设定deque新起点
}
}
}
insert
insert也有很多个版本,不过最基础的是下面这个版本,允许在某个点之前插入一个元素, 并设定其值。
public: // Insert
// 在position出插入一个元素x, 值为x
iterator insert(iterator position, const value_type& x) {
if (position.cur == start.cur) { // 如果插入点是deque最前端, 就交给push_front
push_front(x);
return start;
}
else if (position.cur == finish.cur) { // 如果插入点是deque最尾端, 就交给push_back
push_back(x);
iterator tmp = finish;
--tmp;
return tmp;
}
else {
return insert_aux(position, x); // 如果插入点是中间位置, 就交给insert_aux
}
}
// 在position处插入一个元素, 其值为x
template <class T, class Alloc, size_t BufSize>
deque<T, Alloc, BufSize>::iterator
deque<T, Alloc, BufSize>::insert_aux(iterator pos, const value_type& x) {
difference_type index = pos - start; // 插入点之前元素个数
value_type x_copy = x;
if (index < size() / 2) { // 如果插入点之前的元素个数比较少, 不到总元素个数1/2
push_front(front()); // 在最前端加入与第一个元素同值的元素
iterator front1 = start; // 下面标示记号, 然后进行元素移动
++front1;
iterator front2 = front1;
++front2;
pos = start + index;
iterator pos1 = pos;
++pos1;
copy(front2, pos1, front1); // 往前移动一个单位, [front2, pos1) => [front1, xxx)
}
else { // 如果插入点之前的元素个数比较多, 那就把元素整体往后移动
push_back(back()); // 在尾端插入一个与最后一个元素同值的元素
iterator back1 = finish;
--back1;
iterator back2 = back1;
--back2;
pos = start + index;
copy_backward(pos, back2, back1); // 往后移动一个单位, [pos, back2) => [xxx, back1)
}
*pos = x_copy; // 将x赋值给待插入点pos
return pos;
}
小结
1)deque是双端队列,依赖于中控器+缓冲区来存取数据,中控器管理缓冲区,缓冲区用于存储数据。从而实现在头尾两端O(1)时间复杂度内插入数据,有效避免了vector在头部插入数据时需要整体移动数据的问题。
2)deque的iterator并非继承自标准的迭代器(iterator<>),内含自定义的cur, fisrt, last, node等域,使用时需要特别小心。新版STL可能已经修改,如C++11 MSVC STL中deque迭代器是继承自标准的迭代器。
3)从deque擦除数据(erase)效率并不高,擦除节点在中间位置时,时间复杂度O(n)。
4)如果可以,优先使用vector。因为vector支持O(1)时间随机访问内部元素,内部数据结构维护简单。