CC++ & Algorithm

stack栈容器

困难7
语言版本:C++
概述:stack就像一摞盘子,你只能拿最上面的盘子,后放上去的盘子会先被拿走——这就是“后进先出”。

后进先出,一摞盘子的秘密——C++ stack 栈容器详解

什么是栈?—— 后进先出的数据结构

想象一下,你面前有一摞干净的盘子。你只能从最上面拿盘子,也只能在最上面放新盘子。后放上去的盘子,总是最先被拿走——这个规则就叫 “后进先出”(Last In First Out,简称 LIFO)。

在计算机里,有很多场景都用到这个“后进先出”的规矩:

  • 浏览器里的“后退”按钮:你依次访问页面 A → B → C,它们就像被压进一个栈里。点“后退”时,最先回到的是最近访问的 C,然后是 B,最后是 A。
  • 编辑器的“撤销”操作:你做的每一步操作(打字、删除、改颜色)都依次入栈。撤销时,先撤销最近一步。
  • 还有食堂里堆叠的餐盘、薯片罐里的薯片(最后放进去的薯片在最上面,你先吃到它)……

C++ 标准库中的 stack 容器,就是帮我们实现这个“一摞盘子”的神奇工具。使用它之前,需要加上头文件:

#include <stack>

stack 本质上是一个 容器适配器(container adapter)。什么意思呢?它不自己存储数据,而是套在另一个容器(比如 dequevectorlist)外面,只允许你从“栈顶”进行操作,从而保证 LIFO 顺序。默认情况下,stack 底层用的是 deque(双端队列),但你也可以指定用 vectorlist,比如:

stack<int, vector<int>> my_stack;   // 底层用 vector 实现的栈
stack<int, list<int>> my_stack;     // 底层用 list 实现的栈

不过我们一般直接用默认的 stack<int> 就足够了,不用操心底层细节。


栈的核心操作 —— 就像管理一摞盘子

stack 提供的操作很少,但每个都很关键。我们来逐一看看。

1. push(x) — 放盘子(入栈)

把数据 x 放到栈的最上面。

stack<int> s;          // 创建一个空栈,存放整数
s.push(10);            // 放入第一个盘子:10
s.push(20);            // 再放一个:20
s.push(30);            // 再放一个:30
// 现在栈从底到顶是:10, 20, 30

2. top() — 看一眼最上面的盘子(查看栈顶元素)

返回栈顶元素的值,但 不删除 它。如果栈是空的,调用 top() 会导致程序崩溃(未定义行为),所以使用前最好先检查 empty()

int val = s.top();     // val 等于 30
cout << val << endl;   // 输出 30

3. pop() — 拿走最上面的盘子(移除栈顶元素)

删除栈顶元素,不返回任何值。很多新手会以为 pop() 能返回被删的值,其实不能!需要先用 top() 拿到值,再 pop()

s.pop();               // 移除栈顶的 30,现在栈里剩 10, 20
int next = s.top();    // 拿到现在的栈顶 20
s.pop();               // 再移除 20

4. empty() — 判断栈是不是空的

如果栈中没有元素,返回 true(真);否则返回 false(假)。常用在循环里逐个取出所有元素。

if (s.empty()) {
    cout << "栈是空的" << endl;
}

5. size() — 数一数栈里有多少元素

返回栈中元素个数,类型是 size_t(无符号整数)。

cout << "栈中有 " << s.size() << " 个元素" << endl;

常见错误与注意事项

  1. 在空栈上调用 top()pop()
    ❌ 错误示例:

    stack<int> s;          // s 是空的
    s.pop();               // 崩溃!空栈不能 pop
    s.top();               // 崩溃!空栈没有 top
    

    ✅ 正确做法:每次操作前先检查 if (!s.empty())

  2. 忘记 pop() 不返回被删的值
    ❌ 错误:int x = s.pop();
    ✅ 正确:先 int x = s.top();s.pop();

  3. 循环遍历时不更新栈状态
    ❌ 错误:无限循环

    while (!s.empty()) {
        cout << s.top();   // 只输出,不 pop,栈永远不空
    }
    

    ✅ 正确:每次输出后记得 s.pop();

  4. 混淆顺序:pop() 后原来的 top() 值会失效
    如果你先取 top() 保存到变量里,再 pop(),原来的变量依然有效,因为那是一个副本。但如果你用引用(int &ref = s.top();),pop() 后引用就无效了,小心使用。


完整示例:反转单词(小练习的完整实现)

题目要求:输入一个单词(比如 hello),把每个字母依次入栈,然后全部弹出,看看输出是什么。这正是利用栈“后进先出”的特点来反转字符串。

#include <iostream>
#include <stack>
#include <string>        // 使用 std::string
using namespace std;

int main() {
    string word;                      // 定义字符串,用于存储用户输入的单词
    cout << "请输入一个单词:";
    cin >> word;                      // 读取输入

    stack<char> letters;              // 创建一个字符栈,用来存放每个字母

    // 把单词的每个字母依次入栈
    for (int i = 0; i < word.size(); i++) {
        letters.push(word[i]);        // 将第 i 个字母放到栈顶
    }

    cout << "反转后的结果:";
    // 只要栈不为空,就取出栈顶并输出,然后弹出
    while (!letters.empty()) {
        char ch = letters.top();      // 拿到栈顶字母
        cout << ch;                   // 输出它
        letters.pop();                // 移除栈顶字母
    }
    cout << endl;

    return 0;
}

运行演示:

请输入一个单词:hello
反转后的结果:olleh

你看,输入 hello,输出变成了 olleh。这正是后进先出(LIFO)的奇妙效果:h 第一个进去,最后一个出来;o 最后一个进去,第一个出来。


生活中的更多栈应用

除了之前说的浏览器后退、撤销操作,栈在程序世界里无处不在:

  • 函数调用栈:你写的程序里,一个函数调用另一个函数,再调用第三个函数……每次调用都会把当前函数的返回地址、局部变量等信息“压”进一个栈中。当最里面的函数执行完,就“弹”出,回到上一级函数。这就是为什么函数调用能正确地一层层返回。

  • 括号匹配:写数学表达式 (1 + (2 * 3)) 时,可以用栈检查左右括号是否匹配——遇到左括号入栈,遇到右括号出栈,最后栈为空说明匹配成功。

  • 表达式求值:计算机计算 3 + 4 * 2 时,会先把数字和运算符分别压入两个栈,再按运算优先级依次弹出处理。


相关指引

stack 属于 C++ STL(标准模板库)中的容器适配器之一。如果你已经掌握了栈,可以继续学习:

  • 队列(queue:先进先出(FIFO),就像排队买饭,先来的人先得到服务。
  • 双端队列(deque:两端都可以插入和删除,是 stackqueue 的默认底层容器。
  • 优先队列(priority_queue:每次取出的都是当前最大(或最小)的元素,常用于任务调度、哈夫曼编码等。
  • vector / list:栈的底层容器,了解它们的区别有助于理解为什么栈选择 deque 作为默认底层。

另外,在手写递归算法时,有时会遇到栈溢出(Stack Overflow)——递归层级太深,系统调用栈爆了。这时可以考虑用自己写的 stack 把递归改成迭代,避免溢出。这也是栈在算法竞赛(CSP-J/S 等)中的重要应用之一。

现在,快去试试用栈解决更多有趣的问题吧!比如用栈检查括号是否匹配、模拟火车车厢调度、或者实现一个“最近打开的文件”历史记录……栈虽小,力量大!

例题精讲

1单选题

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

Avector
Bdeque
Clist
Darray
2判断题

调用stack的top()函数时,如果栈为空,则会导致未定义行为。

3填空题
补充代码,使得在栈非空时安全地弹出栈顶元素:\nstack<int> st;\n// 向栈中压入若干元素后\nif( ___ ) {\n    st.pop();\n}
4单选题

stack容器中size()成员函数的返回值类型是?

Aint
Bsize_t
Cunsigned int
Dlong long
5判断题

stack容器允许通过迭代器或下标直接访问栈底元素,例如st[0]或*st.begin()。