stack,queue以及deque的介绍
1.stack与queue的实现
- 栈和队列作为容器适配器实现,容器适配器即将特定容器类封装作为其底层容器类
- 底层容器可以是标准容器类模板之一,也可以是其他专门设计的容器类。该底层容器应至少
支持以下操作:
empty:检测队列是否为空
size:返回队列中有效元素的个数
front:返回队头元素的引用
back:返回队尾元素的引用
push_back:在队列尾部入队列
pop_front:在队列头部出队列 - 标准容器类deque,list和vector满足了这些要求。默认情况下,如果没有为queue实例化指定容器
类,则使用标准容器deque。
#include<iostream>
#include<vector>
#include<list>
#include<deque>
using namespace std;
namespace LL
{ //适配器template<class T, class Container = deque<T>>//默认为dequeclass stack{public:void push(const T& x){_con.push_back(x);}void pop(){_con.pop_back();}bool empty(){return _con.empty();}size_t size(){return _con.size();}T& top(){return _con.back();}const T& top()const{return _con.back();}private:Container _con;};
}
queue的底层同理
2.那为什么要用deque为默认呢
我们先来看一下vector与list的优缺点
而deque就像是他们的结合,既可以满足头插头删的效率也可以利用下表随机访问
我们来通过他的底层来分析他是如何实现的:
这就是他的底层结构,利用中控数组来控制其中的小数组,将小数组的地址存放在中控数组中。一个小数组满了之后就去下一个小数组中。
下面我们来分析一下他的源码进行理解:
这是他的迭代器结构cur是小数组的遍历指针,first与last是小数组的头尾位置,node是小数组在中控数组中的位置。
有start和finish分别指向中控数组的头尾
尾插很简单先判断小数组是否还有空位,如果有就直接尾插但如果小数组已满,先判断中控数组是否需要扩容(即便扩容效率也很高因为中控数组中存储的是小数组的地址不需要像vector那样将数据一个一个拷贝)
而头插还需要注意一点头插需要将数据从小数组的尾部开始从前插入,这样才能正常遍历,如果从头部开始那么迭代器++后数据是空的。
接下来我们来看下标访问是怎么实现的
已知下标n,因为每个小数组大小相同,所以用简单的/与%就可以算出在那个小数组的第几个。但真的是这样实现的吗
来看源码
在operator+=中的n+(cur - first)是因为头插时数据是从后开始插入的所以不能直接用n来计算,用cur与first来补上头插的空数据。
补充一点:一开始的小数组并不是从中控数组开头插入而是在中控数组的中间这样不会一开始头插和尾插就要扩容