文章目录
- [C++ stack和queue](#C++ stack和queue)
C++ stack和queue
stack的介绍和使用
stack的介绍
- 本质 :
stack是一种容器适配器 ,专门用于后进先出(LIFO) 的上下文环境。 - 操作限制:只能从容器的一端(栈顶)进行插入和删除。
- 底层结构 :作为容器适配器实现,是对特定容器类的封装,提供一组特定成员函数访问元素。元素从底层容器的尾部(栈顶) 压入和弹出。
- 底层容器要求 ,必须支持以下操作:
empty():判空back():获取尾部元素push_back():尾部插入pop_back():尾部删除
- 可用底层容器 :
vector、deque、list均满足要求。 - 默认底层容器 :
deque。

stack的使用
| 函数说明 | 接口说明 |
|---|---|
| stack() | 构造空的栈 |
| empty() | 检测stack是否为空 |
| size() | 返回stack中元素的个数 |
| top() | 返回栈顶元素的引用 |
| push() | 将元素val压入stack中 |
| pop() | 将stack中尾部的元素弹出 |
cpp
void test1()
{
stack<int> stk;
vector<int> vec = {4, 5, 6};
stack<int,vector<int>> stv(vec);
list<int> lt = {1,2,3};
stack<int,list<int>> stl(lt);
stk.push(1);
stk.push(2);
cout << stk.top() << endl;
stk.pop();
cout << stk.size() << endl;
if(stk.empty())
{
cout << "栈为空" << endl;
}
else {
cout << "栈不为空" << endl;
}
}
stack初始化不写第二参数默认就是deque,这里我们可以去官方文档上确认,stack不能像数组那样初始化因为stack不是序列容器,但可以通过底层容器间接实现。
stack的模拟实现
stack操作仅限于 push、pop、top、empty、size 等。所以我们只需要手动去实现这些函数接口即可。
那么为什么stack不需要去实现迭代器呢?
- 因为stack是容器适配器而非容器并且stack和其他的容器不同他并不需要++、--、随机访问和删除等操作,如果stack有迭代器那么我们就可以通过迭代器去遍历stack,这就破坏了stack本来的意思。
我们在使用stack的初始化时说到过不指定vector和list容器时默认使用deque,那么这是怎么做到的呢?我们去看一下官方文档吧。

这里第二个参数是什么意思?
class Container:表示指定用哪个容器来存放这些 T 类型的元素deque<T>:默认值。如果不写第二个参数,编译器就会自动使用 std::deque 作为底层容器。
经过上述对stack的了解,我们现在开始模拟实现一个属于我们自己的栈吧,这里和官方一致都使用deque。
cpp
namespace xhs{
template<class T,class Con = deque<T> >
class stack{
public:
stack()
{}
void push(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:
Con _con;
};
}
queue的介绍和使用
queue的介绍
- 本质 :queue 是一种容器适配器 ,专门用于 FIFO(先进先出) 上下文。
- 操作限制 :从容器一端插入元素,另一端提取元素。元素从队尾 入队列,从队头出队列。
- 底层容器要求 ,必须支持以下操作:
empty():检测队列是否为空size():返回有效元素个数front():返回队头元素的引用back():返回队尾元素的引用push_back():在队列尾部入队列
-pop_front():在队列头部出队列
- 可用底层容器:
deque、list满足要求。 - 默认底层容器:
deque。

queue的使用
| 函数声明 | 接口说明 |
|---|---|
| queue() | 构造空的队列 |
| empty() | 检测队列是否为空,是返回true,否则返回false |
| size() | 返回队列中有效元素的个数 |
| front() | 返回队头元素的引用 |
| back() | 返回队尾元素的引用 |
| push() | 在队尾将元素val入队列 |
| pop() | 将队头元素出队列 |
cpp
void test2()
{
queue<int> q;
q.push(1);
q.push(2);
cout << q.front() << endl;
cout << q.back() << endl;
q.pop();
cout << q.size() << endl;
if(q.empty())
{
cout << "队列为空" << endl;
}
else {
cout << "队列不为空" << endl;
}
}
和stack一样,不写第二参数默认为deque,因为vector来封装效率太低,所以一般都为deque、list。
queue的模拟实现

cpp
namespace xhs
{
template <class T, class Con = deque<T>>
class queue
{
public:
queue() {}
void push(const T &x) { _con.push_back(x); }
void pop() { _con.pop_front(); }
T &back() { return _con.back(); }
const T &back() const { return _con.back(); }
T &front() { return _con.front(); }
const T &front() const { return _con.front(); }
size_t size() const { return _con.size(); }
bool empty() const { return _con.empty(); }
private:
Con _con;
};
}
容器适配器
什么是适配器
通过对栈和队列的了解,我们知道栈和队列是容器适配器,我们最开始了解STL时就说到过6大组件,其中的配接器也叫适配器,通常叫适配器。
那么什么是适配器呢?
- 适配器是一种设计模式(设计模式是一套被反复使用的、多数人知晓的、经过分类编目的、代码设计经验的总结),该种模式是将一个类的接口转换成客户希望的另外一个接口。

为什么stack和queue明明可以存放元素但是stack和queue不是容器而是容器适配器呢?
- 虽然stack和queue中也可以存放元素,但在STL中并没有将其划分在容器的行列,而是将其称为容器适配器,这是因为stack和队列只是对其他容器的接口进行了包装,STL中stack和queue默认使用deque。
deque的简单介绍
deque的原理介绍
- deque(双端队列) :是一种双开口的"连续"空间的数据结构。双开口的含义是:可以在头尾两端进行插入和删除操作,且时间复杂度为 O(1)。
- 与 vector 比较:头插效率高,不需要搬移元素
- 与 list 比较:空间利用率比较高

deque并不是真正连续的空间,而是由一段段连续的小空间拼接而成的,实际deque类似于一个动态的二维数组,其底层结构如下图所示:

实际实现起来就十分复杂,因为双端队列底层是一段假象的连续空间,实际是分段连续的,为了维护其"整体连续"以及随机访问的假象,落在了deque的迭代器身上,所以实际学习C++中很少出现模拟实现deque,因为stack和queue本身可以封装vector和list来达成同样的效果。
deque的缺陷
- 优点 :
- 与 vector 比较:头部插入和删除时不需要搬移元素,效率特别高;扩容时也不需要搬移大量元素,效率比 vector 高
- 与 list 比较:底层是连续空间,空间利用率比较高,不需要存储额外字段
- 致命缺陷 :
- 不适合遍历 。遍历时,deque 的迭代器要频繁地去检测是否移动到某段小空间的边界,导致效率低下
- 序列式场景中,可能需要经常遍历,因此在实际中,需要线性结构时,大多数情况下优先考虑 vector 和 list
- deque 的应用并不多,目前能看到的一个应用就是 STL 用其作为 stack 和 queue 的底层数据结构
为什么选择deque作为stack和queue的底层默认容器
- stack 是一种后进先出 的特殊线性数据结构,因此只要具有
push_back()和pop_back()操作的线性结构,都可以作为 stack 的底层容器,比如vector和list。 - queue 是先进先出的特殊线性数据结构,只要具有
push_back和pop_front操作的线性结构,都可以作为 queue 的底层容器,比如list。
主要原因:
- stack和queue不需要遍历(因此stack和queue没有迭代器),只需要在固定的一端或者两端进行操作。
- 在stack中元素增长时,deque比vector的效率高(扩容时不需要搬移大量数据);queue中的元素增长时,deque不仅效率高,而且内存使用率高。
完