Go中递归需严防栈溢出与整数溢出:阶乘应以uint64+显式0/1终止条件,斐波那契须避免指数级重复计算。

func 递归调用在 Go 中没有语法门槛,但不加约束的递归极易触发栈溢出或性能崩塌——这不是理论风险,而是运行时真实发生的错误,比如 runtime: goroutine stack exceeds 1000000000-byte limit。
factorial 必须有明确的 base case
阶乘是最直观的递归入口,但新手常犯两个错误:
- 把
0和1的处理混在一起,漏掉边界 - 用
int类型算大数,导致整数溢出(不是栈问题,但结果错得离谱)
正确的写法必须同时守住逻辑和类型:
- 终止条件要覆盖所有入口值(包括负数输入)
- 优先用
uint64避免负数误入,或显式校验
func factorial(n uint64) uint64 {
if n == 0 || n == 1 {
return 1
}
return n * factorial(n-1)
}注意:
立即学习“go语言免费学习笔记(深入)”;
- 不要用
int接收用户输入后直接传入,factorial(-1)会无限递归 -
n-1在uint64下对0取值会绕回极大正数,所以必须先判0
fibonacci 的指数级开销不是 bug,是设计缺陷
原生递归实现 fibonacci 看似优雅,但 fib(40) 就要算上亿次重复子问题。这不是慢,是算法级不可扩展。
func fib(n int) int {
if n < 2 {
return n
}
return fib(n-1) + fib(n-2) // 每次调用产生两个新分支
}关键事实:
- 时间复杂度是
O(2^n),不是线性 - Go 编译器不做尾递归优化(TCO),所以无法靠改写成尾递归来救
- 即使加了
defer或recover,也挡不住栈帧爆炸
如果真要递归解斐波那契,必须加缓存:
- 用
map[int]int记住已算过的n - 或改用迭代(三变量滚动),
O(1)空间、O(n)时间
文件遍历中递归调用 filepath.WalkDir 更安全
实际工程里,手写目录递归最容易翻车:
- 忘记检查
os.IsPermission导致 panic - 对符号链接不做限制,陷入循环引用
- 没控制深度,遇到超深嵌套目录直接栈满
Go 标准库的 filepath.WalkDir 内部就是递归实现,但它:
- 自动跳过权限不足路径(返回
filepath.SkipDir可干预) - 用迭代+栈模拟替代纯函数调用,规避 goroutine 栈限制
- 支持上下文取消(
context.Context),可随时中断
所以别自己写 listFiles(path string),直接用:
err := filepath.WalkDir(root, func(path string, d fs.DirEntry, err error) error {
if err != nil {
if errors.Is(err, fs.ErrPermission) {
return filepath.SkipDir
}
return err
}
if !d.IsDir() {
fmt.Println("file:", path)
}
return nil
})递归最危险的地方,不是它写起来难,而是它“看起来能跑通”——fib(10) 没问题,fib(35) 开始卡顿,fib(50) 直接让 goroutine 崩溃,而你可能还在日志里找逻辑错误。真正要盯住的,永远是调用深度是否可控和子问题是否重复计算。


















