递归函数必须显式定义终止条件,否则必然栈溢出;Go无尾递归优化,错误条件会导致崩溃或静默错误;计算阶乘应依数值大小选用uint64或big.Int。

递归函数必须显式定义终止条件,否则必然栈溢出
Go 没有尾递归优化,runtime.GOMAXPROCS 也救不了无限递归。一旦忘记写 return 或条件判断错位,程序会在几毫秒内因 runtime: goroutine stack exceeds 1000000000-byte limit 崩溃。
常见错误是把终止条件写成 if n 却在递归调用时传入负数,或用 <code>== 判断浮点数/指针导致永远不触发退出。
- 终止条件必须覆盖所有可能输入路径(包括非法输入)
- 优先用
或 <code>>=而非==,防越界 - 对用户输入做预校验,别全靠递归层内判断
计算阶乘:注意 int 类型溢出比栈溢出更早发生
用 int 写 factorial(n) 在 n > 20 时就返回错误结果,但不会 panic —— 这比崩溃更危险。实际应根据场景选类型:uint64 支持到 20!,big.Int 才能安全算 100!。
func factorial(n uint64) *big.Int {
if n <= 1 {
return big.NewInt(1)
}
return new(big.Int).Mul(big.NewInt(int64(n)), factorial(n-1))
}
- 不要在递归体里反复创建
big.Int零值,用new(big.Int)复用对象 - Go 的函数不能重载,若需支持
int/int64输入,得另起函数名如FactorialInt64 - 递归深度 ≈ n,10000! 会直接爆栈,此时该换迭代或分治
遍历嵌套结构体或 map:避免 interface{} 类型断言失败
用递归打印任意 interface{} 时,reflect.ValueOf(x).Kind() 是唯一可靠入口。直接对 map 或 struct 做类型断言(如 x.(map[string]interface{}))会在遇到 map[int]string 时 panic。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
立即学习“go语言免费学习笔记(深入)”;
func walk(v reflect.Value, indent string) {
switch v.Kind() {
case reflect.Map:
for _, key := range v.MapKeys() {
val := v.MapIndex(key)
fmt.Printf("%s%s: ", indent, formatKey(key))
walk(val, indent+" ")
}
case reflect.Struct:
for i := 0; i < v.NumField(); i++ {
field := v.Field(i)
if field.CanInterface() {
fmt.Printf("%s%s: ", indent, v.Type().Field(i).Name)
walk(field, indent+" ")
}
}
}
}
- 必须检查
v.IsValid()和v.CanInterface(),空指针或未导出字段会 panic - 递归前加深度限制参数(如
depth int),防止循环引用导致无限深入 - 对
chan、func、unsafe.Pointer等类型,直接跳过或打标记,别硬递归
文件系统遍历:filepath.Walk 本质是递归,但你不必自己实现
手动用 os.ReadDir + 递归遍历目录,容易漏掉权限错误、符号链接循环、并发读取冲突。标准库 filepath.Walk 已处理这些,且支持提前返回 filepath.SkipDir。
真要手写递归时,关键点是:
- 用
os.Lstat而非os.Stat,避免符号链接无限跳转 - 维护已访问路径的
map[string]bool检测循环,注意不同路径字符串可能指向同一 inode - 错误处理不能只打日志,要决定继续还是中断(
return err会终止整个递归链)
递归不是银弹。当数据结构深度不可控、或性能敏感时,显式用栈模拟递归(stack []Node)反而更稳,也更容易加限流和超时。

















