STL源码剖析(deque)
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)的更多相关文章
- STL"源码"剖析-重点知识总结
STL是C++重要的组件之一,大学时看过<STL源码剖析>这本书,这几天复习了一下,总结出以下LZ认为比较重要的知识点,内容有点略多 :) 1.STL概述 STL提供六大组件,彼此可以组合 ...
- 【转载】STL"源码"剖析-重点知识总结
原文:STL"源码"剖析-重点知识总结 STL是C++重要的组件之一,大学时看过<STL源码剖析>这本书,这几天复习了一下,总结出以下LZ认为比较重要的知识点,内容有点 ...
- STL源码剖析 迭代器(iterator)概念与编程技法(三)
1 STL迭代器原理 1.1 迭代器(iterator)是一中检查容器内元素并遍历元素的数据类型,STL设计的精髓在于,把容器(Containers)和算法(Algorithms)分开,而迭代器(i ...
- STL"源码"剖析
STL"源码"剖析-重点知识总结 STL是C++重要的组件之一,大学时看过<STL源码剖析>这本书,这几天复习了一下,总结出以下LZ认为比较重要的知识点,内容有点略 ...
- 《STL源码剖析》相关面试题总结
原文链接:http://www.cnblogs.com/raichen/p/5817158.html 一.STL简介 STL提供六大组件,彼此可以组合套用: 容器容器就是各种数据结构,我就不多说,看看 ...
- STL源码剖析之序列式容器
最近由于找工作需要,准备深入学习一下STL源码,我看的是侯捷所著的<STL源码剖析>.之所以看这本书主要是由于我过去曾经接触过一些台湾人,我一直觉得台湾人非常不错(这里不涉及任何政治,仅限 ...
- 《STL源码剖析》读书笔记
转载:https://www.cnblogs.com/xiaoyi115/p/3721922.html 直接逼入正题. Standard Template Library简称STL.STL可分为容器( ...
- STL源码剖析之组件
本篇文章开始,进行STL源码剖析的一些知识点,后续系列笔记全是参照<STL源码剖析>进行学习记录的 STL在现在的大部分项目中,实用性已经没有Boost库好了,毕竟STL中仅仅提供了一些容 ...
- 面试题总结(三)、《STL源码剖析》相关面试题总结
声明:本文主要探讨与STL实现相关的面试题,主要参考侯捷的<STL源码剖析>,每一个知识点讨论力求简洁,便于记忆,但讨论深度有限,如要深入研究可点击参考链接,希望对正在找工作的同学有点帮助 ...
随机推荐
- 使用screen后台实时执行命令
在做一个大体积的数据备份时,在远程终端上直接运行备份脚本back.sh,或运行back.sh&放到后台运行,此时关闭远程终端,或网络断开都会让命令停止运行.此时就要使用到screen命令. s ...
- nyoj(表达式求值)
描述 ACM队的mdd想做一个计算器,但是,他要做的不仅仅是一计算一个A+B的计算器,他想实现随便输入一个表达式都能求出它的值的计算器,现在请你帮助他来实现这个计算器吧. 比如输入:"1+2 ...
- 【DFS】【枚举】Gym - 101246G - Revolutionary Roads
给你一张有向图,问你将任意一条边变成双向后,所能得到的最大强连通分量的大小. 缩点之后,预处理can(i,j)表示i能到j. 之后枚举每一条边(u,v),再枚举其他所有点t,如果can(u,t) &a ...
- 【构造】Codeforces Round #423 (Div. 1, rated, based on VK Cup Finals) B. High Load
让你构造一棵树(给定了总结点数和总的叶子数),使得直径最小. 就先弄个菊花图(周围一圈叶子,中间一个点),然后平均地往那一圈放其他的点即可. #include<cstdio> using ...
- 【数论】nefu118 n!后面有多少个0
就是求n!有多少个因子2和因子5,并在这两者中取较小者.因为必须要一个2和一个5才能拼出1个10. 显然2的数量多于5,因此只需要求n!有多少个因子5即可. n!中素因子p的个数= [n/p]+[n/ ...
- 【Kruskal+dfs】BZOJ1016- [JSOI2008]最小生成树计数
[题目大意] 现在给出了一个简单无向加权图.你不满足于求出这个图的最小生成树,而希望知道这个图中有多少个不同的最小生成树. [思路] 拖欠了三个月整(?)的题目,搞出来弄掉了……本年度写的时候姿势最丑 ...
- 【快速幂】POJ3641 - Pseudoprime numbers
输入a和p.如果p不是素数,则若满足ap = a (mod p)输出yes,不满足或者p为素数输出no.最简单的快速幂,啥也不说了. #include<iostream> #include ...
- 课程学习:Linux系统管理
版本 内核版本 发行版本 常见Linux发行版本 ubuntu: 易用,可靠:技术支持付费,生态稍弱 debin: 精简,稳定,可靠; 更新较慢, 无技术支持,软件过时, 企业不太用 opensuse ...
- 【shiro】报错: If the controller requires proxying (e.g. due to @Transactional), please use class-based proxying.
spring整合shiro,项目报如下错误: ==============异常开始============= java.lang.IllegalStateException: The mapped c ...
- Log4net的一点改进
昨天把log4net更新了一下,发现从NUGET上安装log4net后,使用方式简化了许多.以前我在文章<log4net使用简介>中介绍过,使用log4net需要有两步前期准备工作: 在使 ...