deque是一个双向开口的容器,在头尾两端进行元素的插入跟删除操作都有理想的时间复杂度。

deque使用的是分段连续线性空间,它维护一个指针数组(T** map),其中每个指针指向一块连续线性空间。

(map左右两边一般留有剩余空间,用于前后插入元素,具体下面可以看到其实现)

根据上图,可以了解到deque的迭代器的基本定义。

 template <class T, class Ref, class Ptr, size_t BufSiz>
struct __deque_iterator {
// 基本型别的定义
typedef __deque_iterator<T, T&, T*, BufSiz> iterator;
typedef random_access_iterator_tag iterator_category;
typedef T value_type;
typedef Ptr pointer;
typedef Ref reference;
typedef size_t size_type;
typedef ptrdiff_t difference_type;
typedef T** map_pointer;
typedef __deque_iterator self; // 缓冲区的大小
tatic size_t buffer_size() { ... } // 主要维护的三个指针
T* cur; // 指向当前元素
T* first; // 指向当前缓冲区的头
T* last; // 指向当前缓冲区的尾 map_pointer node; // 指向当前缓冲区在map中的位置
// ...
};

deque的实现基本都是依赖于其迭代器的实现(主要是各种操作符的重载)

 // 用于跳到下一个缓冲区
void set_node(map_pointer new_node) {
node = new_node;
first = *new_node;
last = first + difference_type(buffer_size());
} reference operator*() const { return *cur; }
pointer operator->() const { return &(operator*()); } self& operator++() {
++cur;
if (cur == last) { // 到达缓冲区尾端
set_node(node + );
cur = first;
}
return *this;
} self& operator--() {
if (cur == first) { // 已到达缓冲区头端
set_node(node - );
cur = last;
}
--cur;
return *this;
} // 迭代器之间的距离(相隔多少个元素)
difference_type operator-(const self& x) const {
return difference_type(buffer_size()) * (node - x.node - ) +
(cur - first) + (x.last - x.cur);
}

该迭代器还重载了operator+=、operator+、operator-=、operator-(difference_type)等,

都是通过set_node()跟调整cur、first、last、node成员来实现。同时重载的operator[]使用operator+来进行随机存取。

 self& operator+=(difference_type n) {
difference_type offset = n + (cur - first);
if (offset >= && offset < difference_type(buffer_size()))
cur += n;
else {
// 目标在不同的缓冲区
difference_type node_offset =
offset > ? offset / difference_type(buffer_size())
: -difference_type((-offset - ) / buffer_size()) - ;
// 跳到相应的缓冲区
set_node(node + node_offset);
// 调整cur指针
cur = first + (offset - node_offset * difference_type(buffer_size()));
}
return *this;
} // 下面的都直接或间接的调用operator+=
self operator+(difference_type n) const {
self tmp = *this;
return tmp += n;
} self& operator-=(difference_type n) { return *this += -n; } self operator-(difference_type n) const {
self tmp = *this;
return tmp -= n;
} reference operator[](difference_type n) const { return *(*this + n); }

有了__deque_iterator,deque的基本实现就比较简单了(主要维护start、finish这两个迭代器)

下面是deque的基本定义

 template <class T, class Alloc = alloc, size_t BufSiz = >
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[] // ...
}

deque的constructor会调用create_map_and_nodes()来初始化map

 // 每次配置一个元素大小的配置器
typedef simple_alloc<value_type, Alloc> data_allocator;
// 每次配置一个指针大小的配置器
typedef simple_alloc<pointer, Alloc> map_allocator; template <class T, class Alloc, size_t BufSize>
void deque<T, Alloc, BufSize>::create_map_and_nodes(size_type num_elements) {
// 需要分配的结点数 如果为能整除 则多分配多一个结点
size_type num_nodes = num_elements / buffer_size() + ; // 分配结点内存 (前后预留一个 用于扩充)
map_size = max(initial_map_size(), num_nodes + );
map = map_allocator::allocate(map_size); // 将需要分配缓冲区的结点放在map的中间
map_pointer nstart = map + (map_size - num_nodes) / ;
map_pointer nfinish = nstart + num_nodes - ; map_pointer cur;
// 为了简化 去掉了异常处理的代码
for (cur = nstart; cur <= nfinish; ++cur)
*cur = allocate_node(); // 为每个结点分配缓冲区
} // 设置start、finish指针
start.set_node(nstart);
finish.set_node(nfinish);
start.cur = start.first;
finish.cur = finish.first + num_elements % buffer_size();
}

下面就剩下插入跟删除元素的实现了,首先看看关于push_front()的操作的实现。

 void push_front(const value_type& t) {
if (start.cur != start.first) { // 第一缓冲区还有容量
construct(start.cur - , t);
--start.cur;
}
else
push_front_aux(t);
} // 如果第一缓冲区容量不足会调用这个函数来配置新的缓冲区
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的重新整治
*(start.node - ) = allocate_node();
start.set_node(start.node - );
start.cur = start.last - ;
construct(start.cur, t_copy);
} // 根据map前面为分配的结点数量来判断是否需要重新整治
void reserve_map_at_front (size_type nodes_to_add = ) {
if (nodes_to_add > start.node - map)
reallocate_map(nodes_to_add, true);
}

上面留下的reallocate_map函数执行如下功能:

1.如果map中空闲指针足够多,则将已分配的结点移到map的中间。

2.否则重新分配一个map,将旧的map释放,把已分配的结点移到new_map的中间。

然后调整start跟finish迭代器。

然后是pop_front()的实现

 void pop_front() {
if (start.cur != start.last - ) {
destroy(start.cur);
++start.cur;
}
else
pop_front_aux();
} // 当前缓冲区只剩一个元素
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 + );
start.cur = start.first;
}

而push_back()跟pop_back()的实现跟上面的大同小异。

最后看看erase()跟insert()的实现

 iterator erase(iterator pos) {
iterator next = pos;
++next;
difference_type index = pos - start; // 迭代器的operator-
if (index < (size() >> )) { // 如果清除点之前的元素比较少
// 将清除点之前的所有元素后移一位 然后删除第一个元素
copy_backward(start, pos, next); // 利用了迭代器的operator--
pop_front();
}
else { // 如果清除点之后的元素比较少
// 将清除点之后的所有元素前移一位 然后删除最后一个元素
copy(next, finish, pos); // 利用了迭代器的operator++
pop_back();
}
return start + index;
}
 iterator insert(iterator position, const value_type& x) {
if (position.cur == start.cur) {
// 插入位置为begin()
push_front(x);
return start;
}
else if (position.cur == finish.cur) {
// 插入位置为end()
push_back(x);
iterator tmp = finish;
--tmp;
return tmp;
}
else {
// 如果插入位置是在(begin(), end())
return insert_aux(position, x);
}
} // insert_aux()跟erase()实现类似
// 调用copy()或者copy_backward()将元素前移或者后移
// 然后修改原来位置的值

STL源码剖析(deque)的更多相关文章

  1. STL"源码"剖析-重点知识总结

    STL是C++重要的组件之一,大学时看过<STL源码剖析>这本书,这几天复习了一下,总结出以下LZ认为比较重要的知识点,内容有点略多 :) 1.STL概述 STL提供六大组件,彼此可以组合 ...

  2. 【转载】STL"源码"剖析-重点知识总结

    原文:STL"源码"剖析-重点知识总结 STL是C++重要的组件之一,大学时看过<STL源码剖析>这本书,这几天复习了一下,总结出以下LZ认为比较重要的知识点,内容有点 ...

  3. STL源码剖析 迭代器(iterator)概念与编程技法(三)

    1 STL迭代器原理 1.1  迭代器(iterator)是一中检查容器内元素并遍历元素的数据类型,STL设计的精髓在于,把容器(Containers)和算法(Algorithms)分开,而迭代器(i ...

  4. STL"源码"剖析

    STL"源码"剖析-重点知识总结   STL是C++重要的组件之一,大学时看过<STL源码剖析>这本书,这几天复习了一下,总结出以下LZ认为比较重要的知识点,内容有点略 ...

  5. 《STL源码剖析》相关面试题总结

    原文链接:http://www.cnblogs.com/raichen/p/5817158.html 一.STL简介 STL提供六大组件,彼此可以组合套用: 容器容器就是各种数据结构,我就不多说,看看 ...

  6. STL源码剖析之序列式容器

    最近由于找工作需要,准备深入学习一下STL源码,我看的是侯捷所著的<STL源码剖析>.之所以看这本书主要是由于我过去曾经接触过一些台湾人,我一直觉得台湾人非常不错(这里不涉及任何政治,仅限 ...

  7. 《STL源码剖析》读书笔记

    转载:https://www.cnblogs.com/xiaoyi115/p/3721922.html 直接逼入正题. Standard Template Library简称STL.STL可分为容器( ...

  8. STL源码剖析之组件

    本篇文章开始,进行STL源码剖析的一些知识点,后续系列笔记全是参照<STL源码剖析>进行学习记录的 STL在现在的大部分项目中,实用性已经没有Boost库好了,毕竟STL中仅仅提供了一些容 ...

  9. 面试题总结(三)、《STL源码剖析》相关面试题总结

    声明:本文主要探讨与STL实现相关的面试题,主要参考侯捷的<STL源码剖析>,每一个知识点讨论力求简洁,便于记忆,但讨论深度有限,如要深入研究可点击参考链接,希望对正在找工作的同学有点帮助 ...

随机推荐

  1. leetcode104 Maximum Depth

    题意:二叉树最大深度 思路:递归,但是不知道怎么回事直接在return里面计算总是报超时,用俩变量就可以A···奇怪,没想通 代码: int maxDepth(TreeNode* root) { if ...

  2. AtCoder - 3939 Strange Nim

    Problem Statement Takahashi and Aoki are playing a stone-taking game. Initially, there are N piles o ...

  3. 【线段树】洛谷 P3372 【模板】线段树 1

    动态开结点线段树板子. #include<cstdio> using namespace std; typedef long long ll; ll sumv[400005],delta[ ...

  4. 【搜索】【约数个数定理】[HAOI2007]反素数ant

    对于任何正整数x,其约数的个数记作g(x).例如g(1)=1.g(6)=4.如果某个正整数x满足:g(x)>g(i) 0<i<x,则称x为反质数. 所以,n以内的反质数即为不超过n的 ...

  5. 十. 图形界面(GUI)设计14.键盘事件

    键盘事件的事件源一般丐组件相关,当一个组件处于激活状态时,按下.释放或敲击键盘上的某个键时就会发生键盘事件.键盘事件的接口是KeyListener,注册键盘事件监视器的方法是addKeyListene ...

  6. ConCurrent并发包 - Lock详解(转)

    synchronized的缺陷   我们知道,可以利用synchronized关键字来实现共享资源的互斥访问.Java 5在java.util.concurrent.locks包下提供了另一种来实现线 ...

  7. oracle如何查询哪个表数据量大

  8. iOS文件和文件夹的创建,删除,移动, 拷贝,是否存在及简单数据类型的读写

    - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launc ...

  9. Object.keys()的简单理解

    1.对象的话返回属性名 var obj = {'a':'123','b':'345'}; console.log(Object.keys(obj)); //['a','b'] var obj1 = { ...

  10. 18.并发类容器MQ

    package demo7.MQ; public class QueueData { private int id; private String name; private String taskC ...