目录
- 前言
- 容器配接器(适配器)
- stack的使用
- stack的模拟实现
- queue的使用
- queue的模拟实现
- 双端队列(deque)
前言
前面我们已经学习了STL容器中的string、vector还有list。
【C++】string的模拟实现
【C++】STL——vector的模拟实现
【C++】STL——list的模拟实现
下面我们来学习stack和queue。
但是我们由STL这张图可以看出,stcak和queue并不在容器中
,而是在配接器中。什么是配接器,下面来讲解一下。
容器配接器(适配器)
什么是适配器
:适配器是一种设计模式
(设计模式是一套被反复使用的,多人知晓的,经过分类编目的,代码设计经验的总结),该种模式是将一个类的接口转换为客户希望的另外一个接口。
具体了解可看:百度百科——适配器
所以实际上,适配器的本质就是转换
,把原始接口转换为客户想要的就行。
stack和queue中也可以存放元素,但STL中并没有将其放在容器中,而是将其称为容器适配器,是因为stack
和queue
只是对其他容器的接口进行了包装
,而这个其他容器,在STL中默认使用的是deque
(双端队列,后面会稍微讲解一下).
库里面对stack和queue的定义如下:
可以看出,stack和queue都使用了deque,将其进行了包装,就转换成了栈和队列。
stack的使用
stack是一种容器适配器,专门设计用于在 LIFO (后进先出)
上下文中运行,其中元素仅从容器的一端插入和提取。
stack的底层容器可以是任何标准的容器类模板或者其他一些特定的容器类,这些容器应支持以下操作:
empty
:判空操作back
:获取尾部元素操作push_back
:尾部插入元素操作pop_back
:尾部删除元素操作
标准容器vector、deque、list均符合这些要求,默认情况下,如果没有为stack指定特定底层容器,默认使用deque(双端队列)。
stack的使用接口并不多,就如下几个:
函数说明 | 接口说明 |
---|---|
stack() | 构造空的栈 |
empty() | 检测栈是否为空 |
size() | 返回栈中元素个数 |
top() | 返回栈顶元素的引用 |
push() | 将元素val压入栈中 |
pop() | 将stack中尾部元素弹出 |
一个简单的使用样例给到大家参考:
#include<iostream>
#include<list>
#include<deque>
#include<stack>
using namespace std;
int main()
{
stack<int> s;//不写底层容器默认给deque
stack<int, list<int>> sl;
stack<int, deque<int>> sd;
//使用typeid可以查看类型
cout << typeid(s).name() << endl;
cout << typeid(sl).name() << endl;
cout << typeid(sd).name() << endl;
s.push(1);
s.push(2);
s.push(3);
cout << "s.size():" << s.size() << endl;
cout << "s中的元素有:";
while (!s.empty())
{
cout << s.top() << " ";
s.pop();
}
return 0;
}
结果如下:
stack的模拟实现
模拟实现也比较简单:
namespace bit
{
template<class T,class Container = deque<T>>
class stack
{
//默认成员函数都不用自己去写,用底层容器默认生成的即可
public:
void push(const T& x)
{
_con.push_back(x);
}
void pop()
{
_con.pop_back();
}
T& top()
{
return _con.back();
}
const T& top() const
{
return _con.back();
}
size_t size() const
{
return _con.size();
}
bool empty() const
{
return _con.empty();
}
private:
Container _con;
};
我们发现,stack
的私有成员变量
就是一个底层容器
,而stack类的所有默认成员函数都不用自己写,都直接调用底层容器的默认成员函数
即可,包括其他函数的实现,我不需要去管,因为底层容器已经实现好了,我们只需调用
即可,这就是容器适配器的好用之处。
queue的使用
队列
也是一种容器适配器
,专门用于在FIFO(先进先出)
上下文中操作,其中从容器一端插入元素,另一端提取元素。
队列作为容器适配器实现,容器适配器将特定容器类封装作为其底层容器类,底层容器可以是标准容器类模板之一,也可以是其他专门设计的容器类。该底层容器应至少支持以下操作:
empty
:判空操作size
:获取队列中有效元素个数front
:返回队头元素的引用back
:返回队尾元素的引用push_back
:在队列尾部如队列pop_back
:在队列头部出队列
标准容器类deque和list满足了这些需求,默认情况下,如果没有为queue实例化指定容器类,则使用标准容器deque。
list的使用和stack完全一致,接口也就这几个:
函数说明 | 接口说明 |
---|---|
queue() | 构造空的队列 |
empty() | 检测队列是否为空 |
size() | 返回队列中元素个数 |
front() | 返回队头元素的引用 |
back() | 返回队尾元素的引用 |
push() | 将元素val压入栈中 |
pop() | 将队头元素出队列 |
使用样例如下:
#include<iostream>
#include<list>
#include<deque>
#include<queue>
using namespace std;
int main()
{
queue<int> q;//不写底层容器默认给deque
queue<int, list<int>> ql;
queue<int, deque<int>> qd;
//使用typeid可以查看类型
cout << typeid(q).name() << endl;
cout << typeid(ql).name() << endl;
cout << typeid(qd).name() << endl;
q.push(1);
q.push(2);
q.push(3);
cout << "s.size():" << q.size() << endl;
cout << "队尾元素:" << q.back() << endl;
cout << "s中的元素有:";
while (!q.empty())
{
cout << q.front() << " ";
q.pop();
}
return 0;
}
queue的模拟实现
queue的模拟实现和stack类似,同样通过模板和底层容器deque来实现:
namespace bit
{
template<class T, class Container = deque<T>>
class queue
{
public:
bool empty() const
{
return _con.empty();
}
size_t size() const
{
return _con.size();
}
T& front()
{
return _con.front();
}
const T& front() const
{
return _con.front();
}
T& back()
{
return _con.back();
}
const T& back()
{
return _con.back();
}
void pop()
{
return _con.pop_front();
}
void push()
{
return _con.push_back;
}
private:
Container _con;
};
}
要注意函数之间的调用:
- queue中的
pop
函数要调用deque的pop_front删除第一个元素的函数
- queue中的
push
函数要调用deque的push_back尾插函数
双端队列(deque)
通过几张图来简单瞧一瞧双端队列:
对于双端队列中间的各种操作我们不再细说,我们直接来说一说deque的优点和缺陷
:
优点
:
与vector比较
,头插
和头删
时不需要搬移元素
,效率较高
,扩容也不需要搬移大量元素。与List比较
,底层是连续的空间,空间利用率高
。
缺陷:不适合遍历。
所以选择deque作为stack和queue的底层默认容器主要原因为:
stack和queue不需要遍历
stack元素增长时,deque比vector的效率高
(不需要搬移大量数据);queue中的元素增长时,deque不仅效率高,而且内存使用率高。
结合了deque的优点,且完美避开了缺点。
感谢大家观看,如果大家喜欢,希望大家一键三连支持一下,如有表述不正确,也欢迎大家批评指正。