【C++】STL详解(八)—stack和queue的模拟实现
✨ 坚持用 清晰易懂的图解 + 代码语言, 让每个知识点都 简单直观 !
🚀 个人主页 :不呆头 · CSDN
🌱 代码仓库 :不呆头 · Gitee
📌 专栏系列 :
- 📖 《C语言》
- 🧩 《数据结构》
- 💡 《C++》
- 🐧 《Linux》
💬 座右铭 : “不患无位,患所以立。”
【C++】STL详解(八)—stack和queue的模拟实现
- 摘要
- 目录
- 一、容器适配器
- 1. 解释
- 2. stack vs queue 对比表
- 二、stack的模拟实现
- 1. 常用函数
- 1.1 类模板定义
- 1.2 push
- 1.3 pop
- 1.4 top
- 1.5 size
- 1.6 empty
- 1.7 swap
- 2. .h(声明定义不分离)
- 3. .c(测试)
- 三、queue的模拟实现
- 1. 常用函数
- 1.1 push
- 1.2 pop
- 1.3 front
- 1.4 back
- 1.5 size
- 1.6 empty
- 1.7 swap
- 2. .h(声明定义不分离)
- 3. .c(测试)
- 四、总结
stack与queue的详细介绍和使用请点击
摘要
不是呆头将在本文详细讲解了C++ STL 中 stack(栈) 和 queue(队列) 的模拟实现:
- 容器适配器概念:stack 与 queue 并非独立容器,而是基于已有顺序容器(
deque
/vector
/list
)的封装。- stack:后进先出(LIFO),只允许在栈顶操作元素;核心接口:
push()
、pop()
、top()
、size()
、empty()
、swap()
。- queue:先进先出(FIFO),入队在队尾,出队在队头;核心接口:
push()
、pop()
、front()
、back()
、size()
、empty()
、swap()
。- 模拟实现:通过底层容器操作完成栈/队列行为,同时支持自定义底层容器,保持高效与灵活性。
📌 编程箴言: “好的C++代码就像好酒,需要时间沉淀。”
(正文开始👇)
目录
一、容器适配器
1. 解释
在 C++ STL 中,stack 和 queue 并不是独立的数据结构容器,而是典型的 容器适配器(Container Adapter)。所谓“容器适配器”,就是它们本身不直接存储数据,而是基于已有的顺序容器(如 deque
、vector
、list
)进行封装,并对外只提供一组受限的接口,从而实现特定的数据操作语义:
- stack(栈) 👉 只允许在一端操作数据,遵循 后进先出(LIFO) 原则;底层默认使用
deque
,也可指定vector
或list
作为适配器容器。 - queue(队列) 👉 只允许一端进、一端出,遵循 先进先出(FIFO) 原则;底层默认同样是
deque
,也可以用list
来替代。
因此,stack 和 queue 可以理解为:
在通用顺序容器的基础上,通过“适配器模式”屏蔽掉不必要的接口,仅保留特定操作(入/出、取顶/取首尾),从而保证语义清晰、逻辑简单、效率稳定。
2. stack vs queue 对比表
特性 | stack(栈) | queue(队列) |
---|---|---|
数据结构类型 | 容器适配器 | 容器适配器 |
底层默认容器 | deque | deque |
可选底层容器 | vector / list | list |
数据操作规则 | 后进先出(LIFO) | 先进先出(FIFO) |
插入位置 | 栈顶(top) | 队尾(back) |
删除位置 | 栈顶(top) | 队首(front) |
主要接口 | push() 、pop() 、top() | push() 、pop() 、front() 、back() |
适用场景 | 函数调用、表达式求值、括号匹配、DFS | 任务调度、消息队列、层序遍历、BFS |
二、stack的模拟实现
1. 常用函数
1.1 类模板定义
template<typename T,typename Container = deque<T>>
class stack
{
public:private:Container _con;
};
template<typename T, typename Container = deque<T>>
类模板定义,T为储存的数据类型,Container为底层容器类型,默认是deque < T >
Container _con;
定义了一个名为stack的类,包装一个底层容器,然后基于这个底层容器来实现功能
1.2 push
对于stack,push是把数据压入,相当于数组的末尾插入,直接执行尾插操作,但是我们不知道函数的参数是内置类型还是自定义类型,所以我们可以使用引用传参来避免是自定义类型的大对象拷贝。
void push(const T& val)
{_con.push_back(val);
}
因为我们插入数据不需要进行修改,加上const修饰。
1.3 pop
对于stack,pop是把顶部的数据进行删除,从数组的角度看是删除数组最后一个数据,直接调用尾删就行。
void pop()
{_con.pop_back();
}
1.4 top
top是获取栈顶元素的引用,也就是获取数组中最后一个数据的引用,我们直接采用back,back的作用是返回数组最后一个数据的引用。
T& top()
{return _con.back();
}
1.5 size
size是获取栈中有效数据的个数,可以直接调用size();而且个数必须是非负数,我们可以直接采用size_t;获取有效个数并不会对容器数据做任何的修改,可以使用const修饰。
size_t size() const
{return _con.size();
}
1.6 empty
empty是通过bool来进行判断容器是否为空,我们可以直接调用empty()函数;同时这个操作不会对容器中的任何数据进行修改,所以可以加const进行修饰。
bool empty() const
{return _con.empty();
}
1.7 swap
swap是用来整体交换两个容器中的数据,为了避免把整个栈拷贝一份(非常耗时),所以我们直接采用引用传参;因为stack是容器适配器,底层容器默认是deque,为了更方便适配各种容器,我们需要一个存储数据类型的类和一个底层容器类;调换是调用底层容器自己的 swap 函数,直接交换两者的存储空间。
这样整个交换操作复杂度 O(1),只是指针/内部资源的交换,不会一一拷贝元素。
void swap(stack<T, Container>& st){_con.swap(st._con);}
2. .h(声明定义不分离)
#pragma once
#include<iostream>
#include<stack>
using namespace std;namespace dh //防止命名冲突
{ template<typename T, typename Container = deque<T>>//类模板定义,T为数据类型,Container为底层容器类型,默认是dequeclass 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();}//交换两个栈中的数据void swap(stack<T, Container>& st){_con.swap(st._con);} private:Container _con;};
}
3. .c(测试)
#include"stack-queue.h"void test01()
{stack<int> st;stack<int> st1;st.push(5);st.push(2);st.push(0);st1.push(6);st1.push(6);st1.push(6);cout << st.size() << endl;cout << st1.size() << endl;while (!st.empty()){cout << st.top() << ' ';st.pop();}cout << endl;st.swap(st1);while (!st.empty()){cout << st.top() << ' ';st.pop();}cout << endl;cout << st.size() << endl;
}int main()
{test01();return 0;
}
三、queue的模拟实现
**注意:**类模板定义同上,T储存数据类型,Container为底层容器类型,默认是deque < T >
1. 常用函数
1.1 push
push是把数据从数据的末尾插入,直接执行尾插操作,但是我们不知道函数的参数是内置类型还是自定义类型,所以我们可以使用引用传参来避免是自定义类型的大对象拷贝。
void push(const T& val)
{_con.push_back(val);
}
因为我们插入数据不需要进行修改,加上const修饰。
1.2 pop
pop是把队头的数据进行删除,直接调用头删就行。
void pop()
{_con.pop_front();
}
1.3 front
front是用来获取队头数据的引用,我们可以直接调用front,它直接返回队头数据的引用
T& front()
{return _con.front();
}
1.4 back
back是用来获取队尾数据的引用,我们可以直接调用back,它直接返回队尾数据的引用
T& back()
{return _con.back();
}
1.5 size
size是获取容器中有效数据的个数,可以直接调用size();而且个数必须是非负数,我们可以直接采用size_t;获取有效个数并不会对容器数据做任何的修改,可以使用const修饰。
size_t size() const
{return _con.size();
}
1.6 empty
empty是通过bool来进行判断容器是否为空,我们可以直接调用empty()函数;同时这个操作不会对容器中的任何数据进行修改,所以可以加const进行修饰。
bool empty() const
{return _con.empty();
}
1.7 swap
swap是用来整体交换两个容器中的数据,为了避免把整个栈拷贝一份(非常耗时),所以我们直接采用引用传参;因为queue是容器适配器,底层容器默认是deque,为了更方便适配各种容器,我们需要一个存储数据类型的类和一个底层容器类;调换是调用底层容器自己的 swap 函数,直接交换两者的存储空间。
这样整个交换操作复杂度 O(1),只是指针/内部资源的交换,不会一一拷贝元素。
void swap(queue<T, Container>& q){_con.swap(q._con);}
2. .h(声明定义不分离)
#pragma once
#include<iostream>
#include<queue>
#include<deque>
using namespace std;namespace dh //防止命名冲突
{template<class T, class Container = std::deque<T>>class queue{public://队尾入队列void push(const T& x){_con.push_back(x);}//队头出队列void pop(){_con.pop_front();}//获取队头元素T& front(){return _con.front();}const T& front() const{return _con.front();}//获取队尾元素T& back(){return _con.back();}const T& back() const{return _con.back();}//获取队列中有效元素个数size_t size() const{return _con.size();}//判断队列是否为空bool empty() const{return _con.empty();}//交换两个队列中的数据void swap(queue<T, Container>& q){_con.swap(q._con);}private:Container _con;};
}
3. .c(测试)
#include"stack-queue.h"void test02()
{queue<int> q;queue<int> q1;q.push(5);q.push(2);q.push(0);q1.push(6);q1.push(7);q1.push(8);cout << q.size() << endl;cout << q1.size() << endl;while (!q.empty()){cout << q.front() << ' ';q.pop();}cout << endl;q.swap(q1);while (!q.empty()){cout << q.front() << ' ';q.pop();}cout << endl;cout << q.size() << endl;
}int main()
{//test01();test02();return 0;
}
四、总结
-
容器适配器的本质:stack 和 queue 是对顺序容器的封装,屏蔽不必要的接口,只保留特定操作。
-
效率与安全:底层容器的
push_back
/pop_back
/push_front
/pop_front
等操作保证了高效操作;swap
操作复杂度 O(1),无需逐元素拷贝。 -
使用场景:
- stack:函数调用管理、括号匹配、DFS 等。
- queue:任务调度、消息队列、层序遍历、BFS 等。
-
实践建议:在自定义栈/队列时,可根据需要添加空栈/空队列保护,避免未定义行为。
🔹 总结一句话:
stack 是“后进先出”的守护者,queue 是“先进先出”的调度员,底层容器让它们高效而灵活。
不是呆头将一直坚持用清晰易懂的图解 + 代码语言,让每个知识点变得简单!
👁️ 【关注】 看一个非典型程序员如何用野路子解决正经问题
👍 【点赞】 给“不写八股文”的技术分享一点鼓励
🔖 【收藏】 把这些“奇怪但有用”的代码技巧打包带走
💬 【评论】 来聊聊——你遇到过最“呆头”的 Bug 是啥?
🗳️ 【投票】 您的投票是支持我前行的动力
技术没有标准答案,让我们一起用最有趣的方式,写出最靠谱的代码! 🎮💻