STL 模板库 第六日 : deque
一:什么是deque
所谓的deque是”double ended queue”的缩写,双端队列不论在尾部或头部插入元素,都十分迅速。而在中间插入元素则会比较费时,因为必须移动中间其他的元素。双端队列是一种随机访问的数据类型,提供了在序列两端快速插入和删除操作的功能,它可以在需要的时候改变自身大小,完成了标准的C++数据结构中队列的所有功能。
Vector是单向开口的连续线性空间,deque则是一种双向开口的连续线性空间。deque对象在队列的两端放置元素和删除元素是高效的,而向量vector只是在插入序列的末尾时操作才是高效的。deque和vector的最大差异,一在于deque允许于常数时间内对头端进行元素的插入或移除操作,二在于deque没有所谓的capacity观念,因为它是动态地以分段连续空间组合而成,随时可以增加一段新的空间并链接起来。换句话说,像vector那样“因旧空间不足而重新配置一块更大空间,然后复制元素,再释放旧空间”这样的事情在deque中是不会发生的。也因此,deque没有必要提供所谓的空间预留(reserved)功能。
虽然deque也提供Random Access Iterator,但它的迭代器并不是普通指针,其复杂度和vector不可同日而语,这当然涉及到各个运算层面。因此,除非必要,我们应尽可能选择使用vector而非deque。对deque进行的排序操作,为了最高效率,可将deque先完整复制到一个vector身上,将vector排序后(利用STL的sort算法),再复制回deque。
deque是一种优化了的对序列两端元素进行添加和删除操作的基本序列容器。通常由一些独立的区块组成,第一区块朝某方向扩展,最后一个区块朝另一方向扩展。它允许较为快速地随机访问但它不像vector一样把所有对象保存在一个连续的内存块,而是多个连续的内存块。并且在一个映射结构中保存对这些块以及顺序的跟踪。
二:deque的常用方法
(1)申明一个deque
#include<deque> // 头文件
deque<type> deq; // 声明一个元素类型为type的双端队列que
deque<type> deq(size); // 声明一个类型为type、含有size个默认值初始化元素的的双端队列que
deque<type> deq(size, value); // 声明一个元素类型为type、含有size个value元素的双端队列que
deque<type> deq(mydeque); // deq是mydeque的一个副本
deque<type> deq(first, last); // 使用迭代器first、last范围内的元素初始化deq
(2)deque的常用成员函数
deque<int> deq;
deq[ ]:用来访问双向队列中单个的元素。
deq.front():返回第一个元素的引用。
deq.back():返回最后一个元素的引用。
deq.push_front(x):把元素x插入到双向队列的头部。
deq.pop_front():弹出双向队列的第一个元素。
deq.push_back(x):把元素x插入到双向队列的尾部。
deq.pop_back():弹出双向队列的最后一个元素。
三:deque的注意点
支持随机访问,即支持[ ]以及at(),但是性能没有vector好。
可以在内部进行插入和删除操作,但性能不及list。
deque两端都能够快速插入和删除元素,而vector只能在尾端进行。
deque的元素存取和迭代器操作会稍微慢一些,因为deque的内部结构会多一个间接过程。
deque迭代器是特殊的智能指针,而不是一般指针,它需要在不同的区块之间跳转。
deque可以包含更多的元素,其max_size可能更大,因为不止使用一块内存。
deque不支持对容量和内存分配时机的控制。
在除了首尾两端的其他地方插入和删除元素,都将会导致指向deque元素的任何pointers、references、iterators失效。不过,deque的内存重分配优于vector,因为其内部结构显示不需要复制所有元素。
deque的内存区块不再被使用时,会被释放,deque的内存大小是可缩减的。不过,是不是这么做以及怎么做由实际操作版本定义。
deque不提供容量操作:capacity()和reverse(),但是vector可以。
四:源码解析
(转 自:http://blog.****.net/chengonghao/article/details/51464598)
Deque采用一块map作为主控,这里所谓map是一小块连续空间,其中每个元素(此处称为节点,node)都是指针,指向另一段连续线性空间,称之为缓冲区。
deque的实现需要以下几个文件:
1. globalConstruct.h,构造和析构函数文件,位于cghSTL/allocator/cghAllocator/
2. cghAlloc.h,空间配置器文件,位于cghSTL/allocator/cghAllocator/
3. cghDequeIterator.h,迭代器的实现,位于cghSTL/sequence containers/cghDeque/
4. cghDeque.h,cghDeque的实现,位于cghSTL/sequence containers/cghDeque/
5. test_cghDeque.cpp,测试代码,位于cghSTL/test/
1.构造函数
先看第一个,globalConstruct.h构造函数文件
- /*******************************************************************
- * Copyright(c) 2016 Chen Gonghao
- * All rights reserved.
- *
- * [email protected]
- *
- * 功能:全局构造和析构的实现代码
- ******************************************************************/
- #include "stdafx.h"
- #include <new.h>
- #include <type_traits>
- #ifndef _CGH_GLOBAL_CONSTRUCT_
- #define _CGH_GLOBAL_CONSTRUCT_
- namespace CGH
- {
- #pragma region 统一的构造析构函数
- template<class T1, class T2>
- inline void construct(T1* p, const T2& value)
- {
- new (p)T1(value);
- }
- template<class T>
- inline void destroy(T* pointer)
- {
- pointer->~T();
- }
- template<class ForwardIterator>
- inline void destroy(ForwardIterator first, ForwardIterator last)
- {
- // 本来在这里要使用特性萃取机(traits编程技巧)判断元素是否为non-trivial
- // non-trivial的元素可以直接释放内存
- // trivial的元素要做调用析构函数,然后释放内存
- for (; first < last; ++first)
- destroy(&*first);
- }
- #pragma endregion
- }
- #endif
按照STL的接口规范,正确的顺序是先分配内存然后构造元素。构造函数的实现采用placement new的方式;为了简化起见,我直接调用析构函数来销毁元素,而在考虑效率的情况下一般会先判断元素是否为non-trivial类型。
关于 trivial 和 non-trivial 的含义,参见:stack overflow
2.空间配置器
cghAlloc.h是空间配置器文件,空间配置器负责内存的申请和回收。
- /*******************************************************************
- * Copyright(c) 2016 Chen Gonghao
- * All rights reserved.
- *
- * [email protected]
- *
- * 功能:cghAllocator空间配置器的实现代码
- ******************************************************************/
- #ifndef _CGH_ALLOC_
- #define _CGH_ALLOC_
- #include <new>
- #include <cstddef>
- #include <cstdlib>
- #include <climits>
- #include <iostream>
- namespace CGH
- {
- #pragma region 内存分配和释放函数、元素的构造和析构函数
- // 内存分配
- template<class T>
- inline T* _allocate(ptrdiff_t size, T*)
- {
- set_new_handler(0);
- T* tmp = (T*)(::operator new((size_t)(size * sizeof(T))));
- if (tmp == 0)
- {
- std::cerr << "out of memory" << std::endl;
- exit(1);
- }
- return tmp;
- }
- // 内存释放
- template<class T>
- inline void _deallocate(T* buffer)
- {
- ::operator delete(buffer);
- }
- // 元素构造
- template<class T1, class T2>
- inline void _construct(T1* p, const T2& value)
- {
- new(p)T1(value);
- }
- // 元素析构
- template<class T>
- inline void _destroy(T* ptr)
- {
- ptr->~T();
- }
- #pragma endregion
- #pragma region cghAllocator空间配置器的实现
- template<class T>
- class cghAllocator
- {
- public:
- typedef T value_type;
- typedef T* pointer;
- typedef const T* const_pointer;
- typedef T& reference;
- typedef const T& const_reference;
- typedef size_t size_type;
- typedef ptrdiff_t difference_type;
- template<class U>
- struct rebind
- {
- typedef cghAllocator<U> other;
- };
- static pointer allocate(size_type n, const void* hint = 0)
- {
- return _allocate((difference_type)n, (pointer)0);
- }
- static void deallocate(pointer p, size_type n)
- {
- _deallocate(p);
- }
- static void deallocate(void* p)
- {
- _deallocate(p);
- }
- void construct(pointer p, const T& value)
- {
- _construct(p, value);
- }
- void destroy(pointer p)
- {
- _destroy(p);
- }
- pointer address(reference x)
- {
- return (pointer)&x;
- }
- const_pointer const_address(const_reference x)
- {
- return (const_pointer)&x;
- }
- size_type max_size() const
- {
- return size_type(UINT_MAX / sizeof(T));
- }
- };
- #pragma endregion
- #pragma region 封装STL标准的空间配置器接口
- template<class T, class Alloc = cghAllocator<T>>
- class simple_alloc
- {
- public:
- static T* allocate(size_t n)
- {
- return 0 == n ? 0 : (T*)Alloc::allocate(n*sizeof(T));
- }
- static T* allocate(void)
- {
- return (T*)Alloc::allocate(sizeof(T));
- }
- static void deallocate(T* p, size_t n)
- {
- if (0 != n)Alloc::deallocate(p, n*sizeof(T));
- }
- static void deallocate(void* p)
- {
- Alloc::deallocate(p);
- }
- };
- #pragma endregion
- }
- #endif
classcghAllocator是空间配置器类的定义,主要的四个函数的意义如下:allocate函数分配内存,deallocate函数释放内存,construct构造元素,destroy析构元素。这四个函数最终都是通过调用_allocate、_deallocate、_construct、_destroy这四个内联函数实现功能。
我们自己写的空间配置器必须封装一层STL的标准接口,
- template<classT, class Alloc = cghAllocator<T>>
- class simple_alloc
构造与析构函数、空间配置器是最最基本,最最底层的部件,把底层搭建好之后我们就可以着手设计deque了。
3.deque的迭代器
我们首先设计deque的迭代器,我把迭代器的内部结构分解为一下几个部分
1. 一堆typedef、成员变量的声明。最重要的成员变量是node,它是衔接管控中心和迭代器的枢纽;
2. 迭代器管控中心和cghDeque管控中心的衔接,只有一个函数:set_node,初始化迭代器的成员变量,更重要的是与deque的管控中心连接;
3. 确定每个缓冲区的大小:只有一个函数buffer_size();
4. 迭代器基本操作:deque的迭代器要保证random access;
迭代器代码的注释已经写得十分详细了,有疑问的地方我都给出了说明,童鞋们可以参考迭代器的内部结构来总体把握迭代器的框架,通过注释来理解迭代器的工作原理。
cghDequeIterator.h(迭代器)代码如下:
- /*******************************************************************
- * Copyright(c) 2016 Chen Gonghao
- * All rights reserved.
- *
- * [email protected]
- *
- * 功能:cghDeque的迭代器的实现代码
- ******************************************************************/
- #ifndef _CGH_DEQUE_ITERATOR_
- #define _CGH_DEQUE_ITERATOR_
- #include <memory>
- namespace CGH{
- template<class T, class Ref, class Ptr, size_t BufSiz>
- struct __deque_iterator{
- #pragma region typedef和成员变量的定义
- typedef T value_type;
- typedef Ptr pointer;
- typedef Ref reference;
- typedef size_t size_type;
- typedef ptrdiff_t difference_type; // ptrdiff_t的使用要#include <memory>
- typedef T** map_pointer; // 迭代器所属缓冲区,该缓冲区由cghDeque的管控中心管理
- typedef __deque_iterator self;
- typedef __deque_iterator<T, T&, T*, BufSiz> iterator;
- T* cur; // 当前位置
- T* first; // 缓冲区头部
- T* last; // 缓冲区尾部
- map_pointer node; // 迭代器所属缓冲区,该缓冲区由cghDeque的管控中心管理
- #pragma endregion
- #pragma region 迭代器管控中心和cghDeque管控中心的衔接
- /*
- 输入参数:new_node,cghDeque传过来的缓冲区位置(管控中心的某个节点)
- */
- void set_node(map_pointer new_node)
- {
- node = new_node; // 连接cghDeque管控中心的某个节点和迭代器缓冲区
- first = *new_node; // 缓冲区头部
- last = first + difference_type(buffer_size()); // 缓冲区尾部
- }
- #pragma endregion
- #pragma region 确定每个缓冲区的大小
- /*
- 缓冲区默认大小为512字节
- 1.如果BufSiz不为0,传回BufSiz,表示buffer_size由用户自定义
- 2.如果BufSiz,表示buffer_size使用默认值,那么如果元素大小(sizeof(T))小于512字节,传回size_t(512 / sizeof(T)
- 如果元素大小(sizeof(T))大于512字节,传回1
- */
- size_t buffer_size()
- {
- return BufSiz != 0 ? BufSiz : (sizeof(T) < 512 ? size_t(512 / sizeof(T)) : size_t(1));
- }
- #pragma endregion
- #pragma region 迭代器基本操作
- #pragma region 解除引用
- reference operator*() const{ return *cur; }
- pointer operator->()const{ return &(operator*()); }
- #pragma endregion
- #pragma region 迭代器的单步移动
- self& operator++()
- {
- ++cur; // 切换至下一个元素
- // 如果到所在缓冲区的尾端,注意缓冲区是前闭后开的空间,last是缓冲区结束的哨兵,到达last就该切换缓冲区了
- if (cur == last)
- {
- set_node(node + 1); // 就切换至管控中心的下一个节点(也即缓冲区)
- cur = first; // 下一个缓冲区的第一个元素
- }
- return *this;
- }
- self& operator++(int)
- {
- self tmp = *this;
- ++*this;
- return tmp;
- }
- self& operator--()
- {
- if (cur == first) // 如果到达所在缓冲区的头部
- {
- set_node(node - 1); // 就切换至管控中心的前一个节点(也即缓冲区)
- cur = last; // 下一个缓冲区的最后一个元素
- }
- // 注意缓冲区是前闭后开的空间,last是缓冲区结束的哨兵,本身没有意义,last的前一个元素才有正确的值域
- --cur;
- return *this;
- }
- self& operator--(int)
- {
- self tmp = *this;
- --*this;
- return tmp;
- }
- #pragma endregion
- #pragma region 迭代器的随机移动
- /*
- 实现随机存取(random access)
- */
- self& operator+=(difference_type n)
- {
- difference_type offset = n + (cur - first); // 偏移
- // 1.offset >= 0:向后偏移
- // 2.offset < difference_type(buffer_size()):偏移小于缓冲区长度
- if (offset >= 0 && offset < difference_type(buffer_size()))
- {
- cur += n;
- }
- else
- {
- difference_type node_offset = offset > 0
- ? offset / difference_type(buffer_size()) // 向后偏移:确定管控中心的偏移的节点(偏移多少个缓冲区)
- : -difference_type((-offset - 1) / buffer_size()) - 1; // 向前偏移:确定管控中心的偏移的节点(偏移多少个缓冲区)
- set_node(node + node_offset); // 从管控中心中选择新的节点,切换缓冲区
- cur = first + (offset - node_offset*difference_type(buffer_size()));
- }
- return *this;
- }
- /*
- 实现随机存取(random access)
- */
- self operator+(difference_type n) const
- {
- self tmp = *this;
- return tmp += n;
- }
- self& operator-=(difference_type n)const{ return *this += -n; }
- self operator-(difference_type n)const
- {
- self tmp = *this;
- return tnp -= n;
- }
- difference_type operator-(const self& x)
- {
- return difference_type(buffer_size())*(node - x.node - 1) + (cur - first) + (x.last - x.cur);
- }
- reference operator[](difference_type n)const{ return *(*this + n); }
- #pragma endregion
- #pragma region 迭代器的相互比较
- bool operator==(const self& x)const{ return cur == x.cur; }
- bool operator!=(const self& x)const{ return cur != x.cur; }
- bool operator<(const self& x)const
- {
- return (node == x.node) ? (cur < x.cur) : (node < x.node);
- }
- #pragma endregion
- #pragma endregion
- };
- }
- #endif
4.deque
重头戏来啦:deque的实现。
Deque的内部结构我用region分为了以下几类:
1. 一堆typedef、成员变量的定义,最重要的成员变量是map(管控中心),是deque实现头尾插删,动态扩容的灵魂;
2. 辅助函数:buffer_size(),确定每个缓冲区的大小;
3. 初始化:deque的构造函数,以及构造函数引出的一大堆东西~~
4. 查询操作:返回deque的大小、头部、尾部等等;
5. 添加和删除:前插、后插、清空
deque代码的注释已经写得十分详细了,有疑问的地方我都给出了说明,童鞋们可以参考deque的内部结构来总体把握deque的框架,通过注释来理解deque的工作原理。
cghDeque.h:
- /*******************************************************************
- * Copyright(c) 2016 Chen Gonghao
- * All rights reserved.
- *
- * [email protected]
- *
- * 功能:cghDeque的实现代码
- ******************************************************************/
- #ifndef _CGH_DEQUE_
- #define _CGH_DEQUE_
- #include "cghAlloc.h"
- #include "globalConstruct.h"
- #include "cghDequeIterator.h"
- #include <algorithm>
- namespace CGH{
- template<class T, class Alloc = cghAllocator<T>, size_t BufSiz = 0>
- class cghDeque{
- #pragma region typedef和成员变量的定义
- public:
- typedef T value_type;
- typedef value_type* pointer;
- typedef value_type& reference;
- typedef size_t size_type;
- typedef ptrdiff_t difference_type;
- typedef typename __deque_iterator<T, T&, T*, BufSiz>::iterator iterator; // 迭代器,#include "cghDequeIterator.h"
- protected:
- typedef pointer* map_pointer;
- protected:
- iterator start;
- iterator finish;
- map_pointer map; // 管控中心
- size_type map_size;
- #pragma endregion
- #pragma region 辅助函数
- protected:
- /*
- 管控中心(map)每个节点指向的缓冲区大小
- 缓冲区默认大小为512字节
- 1.如果BufSiz不为0,传回BufSiz,表示buffer_size由用户自定义
- 2.如果BufSiz,表示buffer_size使用默认值,那么如果元素大小(sizeof(T))小于512字节,传回size_t(512 / sizeof(T)
- 如果元素大小(sizeof(T))大于512字节,传回1
- */
- size_t buffer_size()
- {
- return BufSiz != 0 ? BufSiz : (sizeof(T) < 512 ? size_t(512 / sizeof(T)) : size_t(1));
- }
- #pragma endregion
- #pragma region cghDeque的初始化
- protected:
- typedef simple_alloc<value_type, Alloc> data_allocator; // cghDeque节点的空间配置器
- typedef simple_alloc<pointer, Alloc> map_allocator; // cghDeque管控中心(map)的空间配置器
- public:
- /*
- 构造函数
- */
- cghDeque(int n, const value_type& value) :start(), finish(), map(0), map_size(0)
- {
- fill_initialize(n, value);
- }
- /*
- 默认构造函数
- */
- cghDeque() :start(), finish(), map(0), map_size(0) { fill_initialize(0, 0); }
- protected:
- /*
- 各种初始化
- */
- void fill_initialize(size_type n, const value_type& value)
- {
- create_map_and_nodes(n);
- map_pointer cur;
- for (cur = start.node; cur < finish.node; ++cur)
- {
- std::uninitialized_fill(*cur, *cur + buffer_size(), value);
- }
- std::uninitialized_fill(finish.first, finish.cur, value);
- }
- /*
- 初始化管控中心(map)、管控中心每个节点管理的缓冲区大小
- */
- void create_map_and_nodes(size_type num_elements)
- {
- // 需要初始化的节点数 = (元素个数 / 每个缓冲区可容纳的元素个数) + 1
- size_type num_nodes = num_elements / buffer_size() + 1;
- // 一个管控中心(map)要管理几个节点,最少8个,最多是“所需节点数 + 2”
- map_size = std::max(initial_map_size(), num_nodes + 2); // std::max 需要 #include <algorithm>
- // 配置出一个具有“map_size个节点”的管控中心(map)
- map = map_allocator::allocate(map_size);
- // 令nstart和nfinish指向管控中心(map)所有节点的中间
- // 保持在中间,可使头尾两端扩充能量一样大
- map_pointer nstart = map + (map_size - num_nodes) / 2;
- map_pointer nfinish = nstart + num_nodes - 1;
- // 为管控中心(map)的每个现用节点配置缓冲区,所有缓冲区加起来就是cghDeque的可用空间
- map_pointer cur;
- for (cur = nstart; cur <= nfinish; ++cur)
- {
- *cur = allocate_node(); // 配置缓冲区
- }
- start.set_node(nstart); // 衔接start迭代器
- finish.set_node(nfinish); // 衔接finish迭代器
- start.cur = start.first; // 确定start迭代器的游标
- finish.cur = finish.first + num_elements % buffer_size(); // 确定finish迭代器的游标
- }
- /*
- 管控中心(map)最小节点数
- */
- size_type initial_map_size(){ return (size_type)8; }
- /*
- 配置管控中心(map)每个节点的缓冲区大小
- */
- pointer allocate_node()
- {
- return data_allocator::allocate(buffer_size() / sizeof(T));
- }
- /*
- 释放管控中心(map)节点的缓冲区
- */
- void deallocate_node(void* node)
- {
- data_allocator::deallocate(node);
- }
- #pragma endregion
- #pragma region cghDeque的查询操作
- public:
- iterator begin(){ return start; } // 获得cghDeque的头部
- iterator end(){ return finish; } // 获得cghDeque的尾部
- reference operator[](size_type n){ return start[difference_type(n)]; } // 获得cghDeque第n个元素
- reference front(){ return *start; } // 获得cghDeque的头部的值
- /*
- 因为缓冲区是前闭后开的区间,获得cghDeque的尾部时需要finish回退一个步长
- */
- reference back()
- {
- iterator tmp = finish;
- --tmp;
- return *tmp;
- }
- /*
- 获得cghDeque的长度
- */
- size_type size()
- {
- return finish - start;
- }
- size_type max_size() const{ return finish - start; } // 获得cghDeque的最大程长度
- bool empty()const{ return finish == start; } // cghDeque是否为空
- #pragma endregion
- #pragma region cghDeque元素的添加和删除操作
- public:
- /*
- 在cghDeque尾部插入元素
- */
- void push_back(const value_type& t)
- {
- // 缓冲区是前闭后开的区间,finish迭代器的last元素做哨兵
- // 如果到达finish.last,说明缓冲区尾部已满,调用push_back_aux,来到管控中心(map)的下一个节点,也就是下一个缓冲区
- if (finish.cur != finish.last - 1)
- {
- construct(finish.cur, t);
- ++finish.cur;
- }
- else
- {
- push_back_aux(t);
- }
- }
- /*
- 在cghDeque头部插入元素
- */
- void push_front(const value_type& t)
- {
- // 如果没有到达缓冲区头部,说明缓冲区前半部分有剩余,直接插入
- // 如果到达start.first,说明缓冲区头部已满,调用push_front_aux,来到管控中心(map)的上一个节点,也就是上一个缓冲区
- if (start.cur != start.first)
- {
- construct(start.cur - 1, t);
- --start.cur;
- }
- else
- {
- push_front_aux(t);
- }
- }
- /*
- 从cghDeque尾部弹出元素
- */
- void pop_back()
- {
- // 如果没有到达finish迭代器的头部,直接destroy
- if (finish.cur != finish.first)
- {
- --finish.cur;
- destroy(finish.cur);
- }
- else
- {
- pop_back_aux(); // 如果到达finish的头部,说明我们要销毁的元素跨了缓冲区,我们要到上一个缓冲区去删除元素
- }
- }
- /*
- 从cghDeque头部弹出元素
- */
- void pop_front()
- {
- // 如果没有到达start迭代器的尾,直接destroy
- if (start.cur != start.last - 1)
- {
- destroy(start.cur);
- ++start.cur;
- }
- else
- {
- pop_front_aux(); // 如果到达start的尾,说明我们要销毁的元素跨了缓冲区,我们要到下一个缓冲区去删除元素
- }
- }
- /*
- 清除cghDeque的所有元素
- */
- void clear()
- {
- // [start.node + 1, finish.node)是满员的,所以先清除[start.node + 1, finish.node)这段缓冲区的元素
- for (map_pointer node = start.node + 1; node < finish.node; ++node)
- {
- destroy(*node, *node + buffer_size());
- data_allocator::deallocate(*node, buffer_size());
- }
- // 如果start.node != finish.node,说明start迭代器和finish迭代器跨了管控中心的节点,要分别清除
- if (start.node != finish.node)
- {
- destroy(start.cur, start.last);
- destroy(finish.first, finish.last);
- data_allocator::deallocate(finish.first, buffer_size());
- }
- else // 如果start.node == finish.node,说明start迭代器和finish迭代器在管控中心同一个节点中
- {
- destroy(start.cur, finish.cur);
- }
- finish = start; // 不要忘了!
- }
- protected:
- /*
- 缓冲区溢出时的后插
- */
- void push_back_aux(const value_type& t)
- {
- value_type t_copy = t;
- *(finish.node + 1) = allocate_node(); // 给管控中心(map)的下一个节点(也就是下一个缓冲区)分配内存
- construct(finish.cur, t_copy); // 构造元素
- finish.set_node(finish.node + 1); // 重置finish迭代器,指向下一个缓冲区
- finish.cur = finish.first; // 重置finish迭代器的游标
- }
- /*
- 缓冲区溢出时的前插
- */
- void push_front_aux(const value_type& t)
- {
- value_type t_copy = t;
- *(start.node - 1) = allocate_node(); // 给管控中心(map)的上一个节点(也就是上一个缓冲区)分配内存
- start.set_node(start.node - 1); // 重置start迭代器,指向上一个缓冲区
- start.cur = start.last - 1; // 重置start迭代器的游标
- construct(start.cur, t_copy); // 构造元素
- }
- /*
- 缓冲区溢出时的后删
- */
- void pop_back_aux()
- {
- deallocate_node(finish.first); // 释放内存
- finish.set_node(finish.node - 1); // 上一个缓冲区
- finish.cur = finish.last - 1; // 重置元素,注意缓冲区是前闭后开
- destroy(finish.cur); // 析构上一个缓冲区的最后一个元素
- }
- /*
- 缓冲区溢出时的前删
- */
- void pop_front_aux()
- {
- destroy(start.cur); // 析构元素
- deallocate_node(start.first); // 释放内存
- start.set_node(start.node + 1); // 下一个缓冲区
- start.cur = start.first; // 重置游标
- }
- #pragma endregion
- };
- }
- #endif
5.测试
最后是测试环节,测试的主要内容已在注释中说明
test_cghDeque.cpp:
- /*******************************************************************
- * Copyright(c) 2016 Chen Gonghao
- * All rights reserved.
- *
- * [email protected]
- *
- * 文件名称:cghDeque容器的测试代码
- ******************************************************************/
- #include "stdafx.h"
- #include "cghVector.h"
- #include "cghDeque.h"
- using namespace::std;
- int _tmain(int argc, _TCHAR* argv[])
- {
- using namespace::CGH;
- /***************************************************************************************/
- /***************************************************************************************/
- std::cout << "************************初始化、前插、后插测试************************" << endl;
- std::cout << endl;
- cghDeque<int> test(1, 1); // 初始化
- test.push_back(2); // 后插
- test.push_back(3); // 后插
- test.push_back(4); // 后插
- test.push_front(0); // 前插
- test.push_front(-1); // 前插
- std::cout << "当前元素:";
- for (cghDeque<int>::iterator iter = test.begin(); iter != test.end(); ++iter){
- std::cout << *iter << ",";
- }
- std::cout << endl;
- std::cout << endl;
- std::cout << "长度:" << test.size() << endl;
- std::cout << endl;
- /***************************************************************************************/
- /***************************************************************************************/
- std::cout << "************************前删、后删测试************************" << endl;
- std::cout << endl;
- test.pop_front(); // 前删
- test.pop_back(); // 后删
- std::cout << "当前元素:";
- for (cghDeque<int>::iterator iter = test.begin(); iter != test.end(); ++iter){
- std::cout << *iter << ",";
- }
- std::cout << endl;
- std::cout << endl;
- std::cout << "长度:" << test.size() << endl;
- std::cout << endl;
- /***************************************************************************************/
- /***************************************************************************************/
- std::cout << "************************清空测试************************" << endl;
- std::cout << endl;
- test.clear(); // 前删
- std::cout << "当前元素:";
- for (cghDeque<int>::iterator iter = test.begin(); iter != test.end(); ++iter){
- std::cout << *iter << ",";
- }
- std::cout << endl;
- std::cout << endl;
- std::cout << "长度:" << test.size() << endl;
- std::cout << endl;
- /***************************************************************************************/
- /***************************************************************************************/
- std::cout << "************************跨主控节点后插测试************************" << endl;
- std::cout << endl;
- std::cout << "缓冲区默认大小为512字节,一个int占4字节,512 / 4 = 128" << endl << endl;
- std::cout << "当插入的元素量 > 128 时就会跨主控节点" << endl;
- std::cout << endl;
- test.clear(); // 前删
- for (int i = 0; i < 150; i++){
- test.push_back(i);
- }
- std::cout << "当前元素:";
- for (cghDeque<int>::iterator iter = test.begin(); iter != test.end(); ++iter){
- std::cout << *iter << ",";
- }
- std::cout << endl;
- std::cout << endl;
- std::cout << "长度:" << test.size() << endl;
- std::cout << endl;
- /***************************************************************************************/
- /***************************************************************************************/
- std::cout << "************************跨主控节点前插测试************************" << endl;
- std::cout << endl;
- std::cout << "缓冲区默认大小为512字节,一个int占4字节,512 / 4 = 128" << endl << endl;
- std::cout << "当插入的元素量 > 128 时就会跨主控节点" << endl;
- std::cout << endl;
- test.clear(); // 前删
- for (int i = 0; i < 150; i++){
- test.push_front(i);
- }
- std::cout << "当前元素:";
- for (cghDeque<int>::iterator iter = test.begin(); iter != test.end(); ++iter){
- std::cout << *iter << ",";
- }
- std::cout << endl;
- std::cout << endl;
- std::cout << "长度:" << test.size() << endl;
- std::cout << endl;
- std::cout << "************************测试结束************************" << endl;
- std::cout << endl;
- system("pause");
- return 0;
- }
测试结果图:
6.扩展讨论
在STL简单stack的实现(传送门:点击打开链接)中,我们为什么选择deque作为stack的底层结构,而不是vector?
我们从以下三个方面来分析:
1. vector和deque的空间使用效率;
2. vector和deque的迭代器访问元素效率;
3. stack的应用场景;
通过上表对比,我们可以得出如下结论:
1. vector是真正意义上的连续空间,而deque底层不连续,只是用户看来连续罢了,vector容量溢出时要做三步:1.分配新空间;2.数据移动;释还旧空间。Vector在空间使用上的效率远低于deque;
2. vector底层使用连续空间,故可以采用普通指针作为迭代器,访问元素的效率高;deque在底层不连续,迭代器设计较复杂,在随机访问元素时效率低。
Stack应用场景包括:函数调用时传递参数、保存现场,局部变量分配和使用。
注意,我们只能访问栈顶元素,迭代器一次只移动一个步长,没有随机访问元素的情况,这意味着采用普通指针和deque专属迭代器来获得栈顶元素的效率相差不大。
得出结论一:vector和deque的迭代器在访问stack元素时打个平手。
但是,程序运行过程中stack会不断的变大缩小,对空间的伸缩性要求很高。deque可以自由组合分段空间,而vector显得有些死板,空间不够就搬家。
得出结论二:deque在空间上使用更灵活,更符合stack的应用场景。
综合结论一、二,我们可以得出最终结论:deque更适合作为stack的底层结构。