
本文深入剖析go中递归函数的执行顺序、调用栈行为及返回值传递逻辑,通过经典rec(i)示例揭示“先触底、后回溯”的本质,帮助开发者真正掌握递归的运行时模型。
本文深入剖析go中递归函数的执行顺序、调用栈行为及返回值传递逻辑,通过经典rec(i)示例揭示“先触底、后回溯”的本质,帮助开发者真正掌握递归的运行时模型。
递归不是简单的“函数自己调自己”,而是一套严格依赖调用栈(Call Stack) 的执行模型。理解它,关键在于把握两个核心阶段:递推(Forward Phase) 与 回溯(Backward Phase)。
以你提供的代码为例:
func rec(i int) int {
if i == 5 {
fmt.Println("Break", i) // ← 第1次打印(最深层调用)
return i // ← 返回5给上一层rec(4),但该返回值未被接收!
}
rec(i + 1) // ← 递推:rec(1)→rec(2)→rec(3)→rec(4)→rec(5)
fmt.Println("i = ", i) // ← 回溯:仅当rec(i+1)返回后才执行!
return i // ← 每层最终返回当前i值(1~4),而非递归路径中的5
}
? 执行过程逐层拆解(栈帧视角)
| 栈帧深度 | 调用 | i值 | 执行动作 | 是否打印 | 返回值(实际被谁接收?) |
|---|---|---|---|---|---|
| 1️⃣ 最深 | rec(5) | 5 | 触发 if → 打印 "Break 5" → return 5 | ✅ "Break 5" | 返回给 rec(4) 的 rec(i+1) 调用处 |
| 2️⃣ ↑ | rec(4) | 4 | rec(5) 返回后 → 执行 fmt.Println("i = ", 4) → return 4 | ✅ "i = 4" | 返回给 rec(3) 的 rec(i+1) 调用处 |
| 3️⃣ ↑ | rec(3) | 3 | rec(4) 返回后 → 打印 "i = 3" → return 3 | ✅ "i = 3" | 返回给 rec(2) |
| 4️⃣ ↑ | rec(2) | 2 | rec(3) 返回后 → 打印 "i = 2" → return 2 | ✅ "i = 2" | 返回给 rec(1) |
| 5️⃣ 顶层 | rec(1) | 1 | rec(2) 返回后 → 打印 "i = 1" → return 1 | ✅ "i = 1" | 被 j = rec(1) 接收 → j 值为 1 |
⚠️ 关键注意:
- rec(5) 确实返回了 5,但它只被 rec(4) 的 rec(i+1) 表达式接收——而 rec(4) 并未使用这个返回值(无赋值、无参与运算),它执行完 fmt.Println 后,直接返回自己的 i(即 4)。
- 同理,每一层都忽略下一层的返回值,仅在自身逻辑结束后返回当前 i。因此,main 中接收到的 j 是最外层 rec(1) 的返回值 1,而非最内层的 5。
✅ 正确设计:让返回值穿透回溯链
若你希望 rec(1) 最终返回 5(即基准条件的值),需显式传递返回值:
func rec(i int) int {
if i == 5 {
fmt.Println("Break", i)
return i // ✅ 基准值
}
result := rec(i + 1) // ✅ 接收并传递下一层结果
fmt.Println("i = ", i)
return result // ✅ 不再返回当前i,而是透传基准值
}
此时输出变为:
Break 5 i = 4 i = 3 i = 2 i = 1 Value j = 5
? 总结:写好递归的三大铁律
- 必设基准条件(Base Case):防止栈溢出,如 if i == 5 { return i };
- 每次递归必须逼近基准:参数须变化(如 i+1),确保终将触发基准;
- 明确返回值流向:若需结果穿透多层,必须用变量接收并返回(result := rec(...); return result),切忌“调了就丢”。
递归的本质是问题分解 + 结果组装。栈是它的舞台,而返回值是它传递答案的信使——读懂栈,就读懂了递归。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











