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源码剖析>,每一个知识点讨论力求简洁,便于记忆,但讨论深度有限,如要深入研究可点击参考链接,希望对正在找工作的同学有点帮助 ...
随机推荐
- RabbitMQ (八) 队列的参数详解
代码中,我们通常这样声明一个队列: //声明队列 channel.QueueDeclare ( queue: QueueName, //队列名称 durable: false, //队列是否持久化.f ...
- 【Java多线程】线程池学习
Java线程池学习 众所周知,Java不仅提供了线程,也提供了线程池库给我们使用,那么今天来学学线程池的具体使用以及线程池基本实现原理分析. ThreadPoolExecutor ThreadPool ...
- Eclipse line number
- Linux基础系列-Day5
网络管理 ifconfig网络管理工具 ifconfig依赖于命令中使用一些选项属性,不仅可以被用来简单地获取网络接口配置信息,还可以修改这些配置,但是通过ifconfig修改的通常为临时配置,即系统 ...
- 设计模式之State模式
State模式定义: 允许一个对象在状态改变是,改变它的行为.看起来对象似乎修改了它的类. 模式理解(个人): State模式主要解决的事在开发中时常遇到的根据不同状态需要进行不同的处理操作的问题,而 ...
- Flip Game II -- LeetCode
You are playing the following Flip Game with your friend: Given a string that contains only these tw ...
- JDBC 编程
DAO设计 没有使用DAO存在的问题:多个地方都要都同时做CRUD操作时,重复的代码就会很多. DAO:Data Access Object(数据存取对象). 位于业务逻辑和持久化数据之间,实现对持久 ...
- 【Trie模板】HDU1251-统计难题
[题意] n统计出以某个字符串为前缀的单词数量(单词本身也是自己的前缀). [思路] 裸题,不过G++好像会超内存,C++就不会. #include<iostream> #include& ...
- 【贪心】POJ2376-Cleaning Shifts
[题目大意] 给出几个小区间和大区间,求覆盖整个大区间的最少小区间个数,如果不可能则输出-1. [思路] 这道程序写得我很不爽快,迷迷糊糊写完了,提交一遍AC了,可是我自己都没怎么弄懂到底是怎么写出来 ...
- [转]servlet配置中init-param
需要初始化的参数比如你的servlet里面有个属性为int total=0默认是0,你想让他初始时50则用init-param 给他赋值 init-param面对应的参数名和值,是给servlet在初 ...