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. python3中使用xpath无法定位,为什么一直返回空列表?

    tbody问题: 在爬去某些网站一些信息的时候,xpath工具上显示类容是正确的,但是在scrapy代码中一直返回空列表 Scrapy的部分代码: class LotteryspiderSpider( ...

  2. 【LA 3641】 Leonardo's Notebook (置换群)

    [题意] 给出26个大写字母组成 字符串B问是否存在一个置换A使得A^2 = B [分析] 置换前面已经说了,做了这题之后有了更深的了解. 再说说置换群.   首先是群. 置换群的元素是置换,运算时是 ...

  3. DHCP获取IP地址过程中捕获的报文—三级网络总结(二)

    上一篇文章主要说了一下知识点中的IP地址的考点,这一篇我打算说说DHCP获取IP地址过程中捕获的报文的这个考点,都是自己的理解,有错误欢迎指正. DHCP是应用层协议,UDP是传输层协议,IP是网络层 ...

  4. 数据库系统入门 | Oracle Linux上部署Oracle 11g服务,并实现SSH远程登录管理

    文章目录 写在前面 一.实验内容 二.实验前期准备 1.软件目录 2.准备一些配置文件.脚本文件 三.实验方案(具体步骤) (一)在虚拟机上安装Oracle Linux (二)在Linux上安装Ora ...

  5. [转]软件开发规范—模块开发卷宗(GB8567——88)

    做软件开发是有那么一套国准可参照的,当然就是那些文档了,这里列出一下所有软件开发的规范文档: 操作手册 用户手册 软件质量保证计划 软件需求说明书 概要设计说明书 开发进度月报 测试计划文档 测试分析 ...

  6. 源码安装python及paramikon的初步试用

    Auth: jin Date: 20140314 OS: CentOS release 5.5 (Final) 默认2.4版本 莫 1.download wget http://www.python. ...

  7. nodejs第一个练习:用Node.js建HTTP服务器

    这是官方的一个例子, 在F盘建立一个tinyphp.js文件,内容: var http = require('http'); http.createServer(function (req, res) ...

  8. palm os

    在3G展会上,Palm将告别Palm OS操作系统 2009年2月12日消息,据国外媒体报道,周三,Palm CEO埃德•科林根(Ed Colligan)在旧金山的一个投资者会议上表示,Palm将告别 ...

  9. Android进阶笔记:Messenger源码详解

    Messenger可以理解为一个是用于发送消息的一个类用法也很多,这里主要分析一下再跨进程的情况下Messenger的实现流程与源码分析.相信结合前面两篇关于aidl解析文章能够更好的对aidl有一个 ...

  10. img转base64的两种方式的比较

    关于图片转base64然后提交后台,项目中一直用的是canvas的toDataUrl方法,但是之前看HTML5 API文档的时候,一直记得好像有个叫fileReader的东西也可以做到.于是过年无事的 ...