编程基础篇 · 线性结构:你天天在用
栈:Cmd+Z 和「爆栈」的秘密
第二种收纳方式长得像一摞盘子:只能从最上面放、也只能从最上面拿,这叫栈。听着限制很大?可你每天按的撤销键、程序里每一次函数调用,靠的都是它。这一课先亲手玩坏一个文档再一键撤回来,最后围观一场「爆栈」事故——全球最大的程序员问答网站 Stack Overflow,名字就是从这场事故来的。
先玩 · 撤销键的真身
左边是个迷你文档,用四个按钮随便折腾它。留意右边:你的每一步操作都被压成一张牌,叠在「操作栈」上——后做的压在上面。折腾够了,连按几下「Cmd+Z 撤销」,看看它撤的顺序。
📄 迷你文档
随便操作,别心疼
🥞 操作栈
每步操作压一张牌,最上面 = 最近一步
(还没有操作)
发现了吗?撤销永远先撤最近那步。这不是产品经理的选择,是栈的天性:后进先出(LIFO,Last In First Out)。只从顶端拿,最先拿到的自然是最后放的。撤销要是不按这个顺序来——先撤掉十步前的操作——文档立刻乱成一锅粥。凡是「按原路退回去」的事,天生就该用栈收纳。
程序怎么记得「回到哪」?
你让 AI「做晚饭」,它做到一半要切菜,切菜切到一半刀钝了要磨刀……每次「暂停手头的事去干别的」,程序都会把当前进度压进一个栈里。看下面的动画(滚到这里会自动播一遍),留意每个任务完成后,程序是怎么自动找回上一个任务的。
准备就绪点「播放」或滚动到这里,看一次完整的调用与返回
这个栈叫函数调用栈,每张牌叫一个「栈帧」,里面记着「这个任务干到哪了、干完回到哪」。程序不需要什么全局调度员——弹出栈顶,自然就回到了上一层暂停的位置。你学过的 Agent 也一样:主任务干到一半发现要先查资料,就把主任务压栈、先干子任务,子任务完成弹出,接着干主任务。
爆栈现场 · Stack Overflow 是怎么发生的
栈的容量是有限的。下面这个函数自己调用自己(这叫递归),左边是代码,右边是调用栈。先不勾终止条件跑一次,看看会发生什么;再勾上跑一次对比。
function 倒数(n) {
if (n === 0) return; // 终止条件(保险丝)
打印(n);
倒数(n - 1); // 自己调用自己
}
倒数(5);
💥 Stack Overflow栈溢出,程序崩溃
这和 AI 有什么关系?关系大了。Agent 干活也是「主任务压栈、先干子任务」,那万一子任务又派生子任务、一层层没完没了呢?和没写终止条件的递归一模一样——所以所有正经的 Agent 框架都要设最大步数 / 最大深度,超过就强制停下来。那根保险丝,就是给「无限递归」上的保险。下次你看到 AI 写的递归代码,验收清单第一条:终止条件在哪?
✅ 这一课想和你分享的
- 栈 = 一摞盘子:只从顶端放和拿,后进先出(LIFO)
- 撤销键的真身:每步操作压一张牌,Cmd+Z 永远先弹最近那张
- 函数调用靠栈记路:弹出栈顶,自动回到上一层暂停的地方
- 爆栈 = 只压不弹:没有终止条件的递归把栈堆满,程序当场崩溃
- Agent 的保险丝:最大步数限制,防的就是任务栈的「无限递归」