文章目录
前言
一、核心前提:stack与queue的本质(适配器容器)
二、stack详解:先进后出(LIFO)的栈
三、queue详解:先进先出(FIFO)的队列
四、stack与queue核心差异对比(重点区分)
五、新手高频坑点(避坑必看)
六、总结
前言
在C++ STL(标准模板库)中,stack(栈)和queue(队列)是两个常用的“适配器容器”——它们不直接实现数据存储,而是基于其他基础容器(如vector、deque)进行封装,提供特定的操作接口,满足“先进后出”和“先进先出”的场景需求。
很多新手入门时,容易混淆stack和queue的特性,不清楚二者的适用场景,甚至误用接口导致程序逻辑错误。本文专为新手打造,避开晦涩的底层源码,聚焦stack和queue的核心用法、关键差异,搭配可直接复制运行的代码示例,帮你快速掌握二者的使用技巧,精准匹配实战场景,避免踩坑。
一、核心前提:stack与queue的本质(适配器容器)
stack和queue不同于vector、list这类“基础序列容器”,它们属于容器适配器(container adapter)——本身不管理内存,而是借助一个“底层容器”(默认是deque)来存储数据,仅对外提供有限的操作接口,限制数据的访问方式,从而实现特定的逻辑(栈的先进后出、队列的先进先出)。
核心要点:
stack和queue都不支持随机访问,只能按特定顺序访问(stack只能访问栈顶,queue只能访问队首/队尾);
底层默认使用deque作为存储容器,也可手动指定其他容器(如vector、list),但需满足对应的数据操作需求;
接口简洁,仅提供符合自身逻辑的操作(如stack的push/pop,queue的push/pop),屏蔽了底层容器的其他操作,降低使用复杂度。
二、stack详解:先进后出(LIFO)的栈
stack(栈)的核心特性是先进后出(LIFO,Last In First Out)——最后插入的数据,最先被取出,类似生活中的“叠盘子”:只能从最上面拿盘子,也只能往最上面放盘子,无法直接访问中间的盘子。
1. 基础使用前提
使用stack需包含头文件#include <stack>,建议使用using namespace std;简化代码(新手友好)。
stack的底层默认容器是deque,也可手动指定底层容器,示例:
// 默认底层容器为deque stack<int> s1; // 手动指定底层容器为vector stack<int, vector<int>> s2; // 手动指定底层容器为list stack<int, list<int>> s3;注意:手动指定底层容器时,需确保该容器支持push_back(尾插)、pop_back(尾删)、back(访问尾部元素)操作(vector、list、deque均满足)。
2. stack核心操作(必掌握)
stack的接口简洁,核心操作仅5个,重点掌握“入栈、出栈、访问栈顶”,即可应对日常开发场景。
#include <iostream> #include <stack> // stack头文件 using namespace std; int main() { // 1. 创建stack对象(默认底层deque) stack<int> s; // 2. 入栈(push):将元素插入栈顶(最上方) s.push(10); s.push(20); s.push(30); s.push(40); cout << "栈的大小(元素个数):" << s.size() << endl; // 输出:4 // 3. 访问栈顶元素(top):仅查看,不删除 cout << "当前栈顶元素:" << s.top() << endl; // 输出:40(最后插入的元素) // 4. 出栈(pop):删除栈顶元素,无返回值(注意:出栈前需判空) s.pop(); // 删除40 cout << "出栈后,栈顶元素:" << s.top() << endl; // 输出:30 cout << "出栈后,栈的大小:" << s.size() << endl; // 输出:3 // 5. 判空(empty):判断栈是否为空,为空返回true,否则返回false while (!s.empty()) { // 循环出栈,直到栈为空 cout << "出栈元素:" << s.top() << endl; s.pop(); } cout << "循环出栈后,栈是否为空:" << (s.empty() ? "是" : "否") << endl; // 输出:是 return 0; }注意事项:
stack的
pop()方法仅删除栈顶元素,无返回值,若想获取栈顶元素并删除,需先调用top()获取,再调用pop()删除;访问
top()和调用pop()前,必须用empty()判空,否则栈为空时操作会导致程序崩溃;stack不支持遍历(如范围for、迭代器),因为其设计初衷就是“只能访问栈顶”,无法访问中间元素。
3. stack实战场景
stack的“先进后出”特性,适合解决需要“回溯、逆序处理”的场景,常见实战场景:
括号匹配(如判断表达式“(())()”是否合法);
逆序输出(如将数组逆序打印);
函数调用栈(编译器底层实现,记录函数调用顺序);
进制转换(如十进制转二进制,除2取余法)。
实战案例:括号匹配(简化版)
#include <iostream> #include <stack> #include <string> using namespace std; // 判断括号是否匹配(仅支持()、[]、{}) bool isBracketMatch(const string& str) { stack<char> s; for (char ch : str) { // 左括号入栈 if (ch == '(' || ch == '[' || ch == '{') { s.push(ch); } else { // 右括号:栈为空(无匹配左括号),直接返回false if (s.empty()) return false; // 取出栈顶左括号,判断是否匹配 char topCh = s.top(); s.pop(); if ((ch == ')' && topCh != '(') || (ch == ']' && topCh != '[') || (ch == '}' && topCh != '{')) { return false; } } } // 循环结束后,栈为空则所有括号匹配,否则有未匹配的左括号 return s.empty(); } int main() { string str1 = "(()[]{})"; string str2 = "(()]"; cout << str1 << " 括号匹配:" << (isBracketMatch(str1) ? "是" : "否") << endl; // 是 cout << str2 << " 括号匹配:" << (isBracketMatch(str2) ? "是" : "否") << endl; // 否 return 0; }三、queue详解:先进先出(FIFO)的队列
queue(队列)的核心特性是先进先出(FIFO,First In First Out)——最先插入的数据,最先被取出,类似生活中的“排队买票”:先排队的人先买票,后排队的人后买票,无法插队,也无法直接访问队列中间的人。
1. 基础使用前提
使用queue需包含头文件#include <queue>,建议使用using namespace std;简化代码(新手友好)。
与stack类似,queue的底层默认容器也是deque,也可手动指定底层容器(需支持push_back、pop_front、front、back操作):
// 默认底层容器为deque queue<int> q1; // 手动指定底层容器为deque(与默认一致) queue<int, deque<int>> q2; // 手动指定底层容器为list(vector不支持pop_front,无法作为queue的底层容器) queue<int, list<int>> q3;注意:vector不支持pop_front(头删)操作(头删效率极低,且接口未提供),因此不能作为queue的底层容器;list和deque支持所有所需操作,可作为底层容器。
2. queue核心操作(必掌握)
queue的接口与stack类似,核心操作也是5个,重点掌握“入队、出队、访问队首/队尾”,用法简单易懂。
#include <iostream> #include <queue> // queue头文件 using namespace std; int main() { // 1. 创建queue对象(默认底层deque) queue<int> q; // 2. 入队(push):将元素插入队列尾部(队尾) q.push(10); q.push(20); q.push(30); q.push(40); cout << "队列的大小(元素个数):" << q.size() << endl; // 输出:4 // 3. 访问队首元素(front):查看队列最前面的元素(最先插入的元素) cout << "当前队首元素:" << q.front() << endl; // 输出:10 // 4. 访问队尾元素(back):查看队列最后面的元素(最后插入的元素) cout << "当前队尾元素:" << q.back() << endl; // 输出:40 // 5. 出队(pop):删除队首元素,无返回值(出队前需判空) q.pop(); // 删除10(最先插入的元素) cout << "出队后,队首元素:" << q.front() << endl; // 输出:20 cout << "出队后,队列的大小:" << q.size() << endl; // 输出:3 // 6. 判空(empty):判断队列是否为空 while (!q.empty()) { // 循环出队,直到队列为空 cout << "出队元素:" << q.front() << endl; q.pop(); } cout << "循环出队后,队列是否为空:" << (q.empty() ? "是" : "否") << endl; // 输出:是 return 0; }注意事项:
queue的
pop()方法仅删除队首元素,无返回值,若想获取队首元素并删除,需先调用front()获取,再调用pop()删除;访问
front()、back()和调用pop()前,必须用empty()判空,否则队列空时操作会导致程序崩溃;queue也不支持遍历(范围for、迭代器),只能访问队首和队尾元素,无法访问中间元素。
3. queue实战场景
queue的“先进先出”特性,适合解决需要“顺序处理、排队等待”的场景,常见实战场景:
任务队列(如多线程任务调度,按顺序执行任务);
消息队列(如聊天软件的消息发送/接收,按发送顺序处理);
广度优先搜索(BFS,算法场景,按层级顺序访问节点);
排队模拟(如银行排队、售票排队场景)。
实战案例:简单任务队列模拟
#include <iostream> #include <queue> #include <string> using namespace std; // 模拟任务队列:按顺序执行任务 int main() { queue<string> taskQueue; // 添加任务(入队) taskQueue.push("任务1:加载数据"); taskQueue.push("任务2:处理数据"); taskQueue.push("任务3:保存结果"); taskQueue.push("任务4:输出日志"); cout << "任务队列开始执行:" << endl; // 按顺序执行任务(出队) while (!taskQueue.empty()) { string currentTask = taskQueue.front(); cout << "正在执行:" << currentTask << "(执行完成)" << endl; taskQueue.pop(); // 任务执行完成,出队 } cout << "所有任务执行完毕!" << endl; return 0; }四、stack与queue核心差异对比(重点区分)
stack和queue虽同为适配器容器,但特性、接口、适用场景差异极大,用表格清晰对比,方便新手快速区分和选型:
对比维度 | stack(栈) | queue(队列) |
|---|---|---|
核心特性 | 先进后出(LIFO) | 先进先出(FIFO) |
访问方式 | 仅能访问栈顶元素(top()) | 可访问队首(front())和队尾(back())元素 |
核心操作 | push(栈顶入栈)、pop(栈顶出栈)、top(访问栈顶) | push(队尾入队)、pop(队首出队)、front(访问队首)、back(访问队尾) |
底层默认容器 | deque(可指定vector、list) | deque(可指定list,不能指定vector) |
是否支持遍历 | 不支持 | 不支持 |
适用场景 | 回溯、逆序、括号匹配、进制转换 | 任务调度、消息队列、BFS、排队模拟 |
五、新手高频坑点(避坑必看)
新手使用stack和queue时,容易因忽视细节导致程序错误,以下是4个高频坑点,务必避开:
坑点1:未判空就访问元素或执行出栈/出队操作
stack<int> s; s.pop(); // 错误:栈为空,调用pop()会崩溃 cout << s.top(); // 错误:栈为空,访问top()会崩溃 queue<int> q; q.pop(); // 错误:队列为空,调用pop()会崩溃 cout << q.front(); // 错误:队列为空,访问front()会崩溃解决方案:任何时候,访问top()、front()、back(),或调用pop()前,必须用empty()判空,确保容器非空。
坑点2:混淆stack和queue的操作接口
常见错误:用queue的front()访问stack的元素,用stack的top()访问queue的元素,或误用pop()的作用位置(stack删栈顶,queue删队首)。
stack<int> s; s.push(10); cout << s.front(); // 错误:stack没有front()接口,应使用top() queue<int> q; q.push(10); cout << q.top(); // 错误:queue没有top()接口,应使用front()解决方案:牢记二者的专属接口,stack只有top(),queue只有front()和back(),pop()的作用位置不同。
坑点3:手动指定不支持的底层容器
// 错误:vector不支持pop_front,无法作为queue的底层容器 queue<int, vector<int>> q; // 正确:可使用deque或list作为queue的底层容器 queue<int, deque<int>> q1; queue<int, list<int>> q2;解决方案:stack的底层容器可指定vector、list、deque;queue的底层容器只能指定deque、list,不能指定vector。
坑点4:认为stack/queue支持随机访问或遍历
stack<int> s = {1,2,3}; // 错误:stack不支持范围for遍历 for (int val : s) { cout << val << " "; } queue<int> q = {1,2,3}; // 错误:queue不支持迭代器遍历 for (queue<int>::iterator it = q.begin(); it != q.end(); it++) { cout << *it << " "; }解决方案:stack和queue的设计初衷就是“限制访问方式”,不支持随机访问和遍历,若需要遍历,应使用vector、list等基础容器。
六、总结
stack和queue是C++ STL中常用的适配器容器,二者的核心价值的是“限制数据访问方式”,分别实现“先进后出”和“先进先出”的逻辑,简化特定场景下的代码编写。
新手重点掌握:
核心特性:stack先进后出,queue先进先出;
核心接口:stack的push、pop、top;queue的push、pop、front、back;
底层容器:默认deque,queue不能用vector作为底层容器;
避坑要点:操作前判空、不混淆接口、不指定不支持的底层容器、不尝试遍历;
选型原则:需要回溯/逆序用stack,需要顺序处理/排队用queue。
stack和queue的用法相对简单,只要记住其特性和接口,结合实战场景多练习,就能快速掌握。日常开发中,无需过度关注底层实现,重点是根据业务场景选择合适的容器,让代码更简洁、高效、符合逻辑。