CC++ & Algorithm

C++栈(stack)——像叠盘子一样存放数据

中等14
语言版本:C++Python
概述:栈是一种后进先出的数据结构,就像一叠盘子,最后放上去的盘子最先被拿走。

C++栈(stack)——后进先出,就像叠盘子

栈是什么?为什么需要它?

栈是一种“后进先出(LIFO)”的数据结构,就像你家里叠盘子:最后放上去的盘子在最上面,最先被拿走。在编程中,当我们需要“记住操作顺序,然后倒着处理”时,栈就派上了大用场。比如:

  • 你在手机上打字时按“撤销”,就会撤销最近一次操作——这正是栈的后进先出。
  • 浏览器点击“后退”,会回到上一个浏览的页面——也是用栈来记录访问历史。
  • 检查算式里的括号是不是成对出现——把左括号压栈,遇到右括号就弹出一个左括号,如果最后栈为空说明括号匹配。

生活中的栈,多举几个例子

除了叠盘子,还有很多地方用到“后进先出”:

  • 叠衣服:你把一件件叠好的T恤放进衣柜,最先放的在最底下,最后放的在最上面。你要拿衣服时,总是先拿最上面的(最后放的那件)。
  • 一摞书:把书一本本叠起来,要取书的时候只能从最上面取。
  • 地铁排队上车:想象一个很窄的通道,只能一个人通过,进去的人排在通道里,最后进去的人得先出来才能让前面的人出来?不,地铁排队是先进先出(队列)。但是如果你把一群人挤在一个电梯里,最后上电梯的人站在门口,下电梯时他先出来——这就是后进先出。

注意:栈和队列的区别就像叠盘子(后进先出)和排队买东西(先进先出)。后面我们会学到队列。

C++ 中的栈:怎么用?

C++ 标准库已经帮我们写好了栈,你只需要包含头文件 <stack>,然后像这样定义一个栈:

#include <stack>   // 用栈必须包含它
using namespace std;

stack<int> s;       // 定义一个存储整数的栈,变量名叫 s
stack<char> c;      // 定义一个存储字符的栈
stack<string> words; // 定义一个存储字符串的栈

常用操作有 5 个,记起来很简单:

操作代码例子意思
压入s.push(100);把 100 放到栈顶(叠上去)
弹出s.pop();把栈顶元素拿走(但不能拿到值)
查看栈顶int x = s.top();看一眼栈顶的值,但不拿走
判空if (s.empty())栈为空返回 true,否则 false
大小int n = s.size();返回栈里有多少个元素

特别提醒

  • pop() 只是弹出元素,不会返回它;如果你既要看又要拿走,需要先 top()pop()
  • 在调用 top()pop() 之前,一定要先检查栈是否为空,否则程序会崩溃(就像你从空盘架上拿盘子,手会伸空)。

新手容易犯的 3 个错误

1. 忘记检查空栈就 top()pop()

stack<int> s;
s.pop();          // ❌ 错误!栈是空的,程序会崩溃

正确做法:先 if (!s.empty()) 再操作。

2. 忘记 pop(),导致死循环

while (!s.empty()) {
    cout << s.top();  // 每次都只打印栈顶,不弹出
    // 忘记 s.pop(),循环永远停不下来!
}

要记得在打印后调用 s.pop()

3. 混淆 pushpop 的方向

栈只有一端操作(栈顶),不能像数组那样随意插入中间。如果你想实现“撤销”,只能撤销最近一次操作,不能撤销中间的。

完整示例:用栈模拟“撤销操作”

小明在写作业,他依次做了三件事:

  1. 写了“A”
  2. 写了“B”
  3. 写了“C”

现在他想撤销两次(也就是删除最后写的两个字母),最后剩下什么?

我们用栈来模拟:每做一件事,就把这件事压入栈;撤销一次,就弹出一个栈顶。最后栈里剩下的就是没被撤销的操作。

#include <iostream>
#include <stack>   // 使用栈
#include <string>  // 使用字符串
using namespace std;

int main() {
    // 定义一个栈,用来存储每一步操作的名字
    stack<string> actions;

    // 依次做三件事,每做一件事就压入栈
    actions.push("写A");   // 第一步:写A
    actions.push("写B");   // 第二步:写B
    actions.push("写C");   // 第三步:写C

    cout << "当前栈里有 " << actions.size() << " 个操作" << endl;

    // 撤销两次:每次弹出栈顶
    if (!actions.empty()) {
        cout << "撤销:" << actions.top() << endl;
        actions.pop();   // 弹出“写C”
    }
    if (!actions.empty()) {
        cout << "撤销:" << actions.top() << endl;
        actions.pop();   // 弹出“写B”
    }

    // 看看现在栈里还剩什么
    cout << "撤销两次后,栈里还剩 " << actions.size() << " 个操作" << endl;
    if (!actions.empty()) {
        cout << "剩下的是:" << actions.top() << endl;  // 应该是“写A”
    }

    return 0;
}

运行结果:

当前栈里有 3 个操作
撤销:写C
撤销:写B
撤销两次后,栈里还剩 1 个操作
剩下的是:写A

你看,最后剩下的就是最开始的“写A”,因为它是最早压入的,最后才被弹出。

小练习:自己试试看

  1. 把上面的代码改成用栈模拟“浏览器后退”:依次访问“首页”、“新闻页”、“图片页”,后退两次,最后当前页面是什么?
  2. 用栈实现一个简单的括号匹配程序:输入一串只包含 () 的字符串,判断括号是否匹配(比如 () 匹配,) ( 不匹配)。提示:遇到左括号压栈,遇到右括号就弹出一个左括号,如果最后栈为空则匹配。

相关指引

学完栈,你可能会对以下内容感兴趣:

  • 队列(queue):先进先出,就像排队买饭。栈和队列经常一起出现。
  • 函数调用栈:C++ 程序运行时的函数调用就是靠栈来实现的,调用一个函数就压入栈,返回时弹出。
  • 深度优先搜索(DFS):在图或迷宫中搜索路径时,经常用栈来记录走过的位置。
  • 表达式求值:计算器里的中缀表达式转后缀、求值,都用到了栈。

栈虽然简单,但它就像一个万能工具,很多看起来复杂的问题,用栈就能轻松解决。试试用栈解决你身边的问题吧!

例题精讲

1单选题

C++ STL中stack默认使用的底层容器是?

Avector
Blist
Cdeque
Darray
2判断题

栈是一种线性结构,只允许在一端(栈顶)进行插入和删除操作。

3填空题
以下代码利用栈将字符串反转,请填空:
string reverseString(string s) {
    stack<char> st;
    for (char c : s) st.push(c);
    string reversed = "";
    while (!st.empty()) {
        reversed += ___;
        st.pop();
    }
    return reversed;
}
4单选题

给定字符串 "({[]})",使用栈判断括号是否匹配。以下说法正确的是?

A括号序列合法
B括号序列不合法
C无法确定,取决于栈的实现
D仅当栈容量足够时合法
5判断题

在利用栈进行表达式求值时,操作数栈中存放的是运算符,运算符栈中存放的是操作数。