Defer 是 Go 的一个独特机制,它允许在当前函数结束后执行一个函数,即使发生了 panic。历史上它类似于 on-exit 结构,但在 Go 中实现为一个 deferred 调用栈。重要的细节是,deferred 函数的参数在声明 defer 时立即计算,而不是在它实际被调用时!
问题 — 不明显的行为:可能会期望参数在 defer 触发时传递,但事实并非如此。这常常导致在处理可变或外部变量时出现 bug。
解决方案 — 始终要记住,参数是立即计算的,而 deferred 调用的效果随后才生效。
代码示例:
func f() { x := 5 defer fmt.Println(x) x = 10 } // 输出:5,而不是 10
关键特点:
以下代码将输出什么?为什么?
func main() { i := 0 defer fmt.Println(i) i = 1 }
答案:输出 0。函数 fmt.Println 的参数在声明 defer 时立即保存。
在声明 defer 后更改变量,会影响其值传递给函数吗?
不会,不影响 — 计算在声明 defer 时发生:
defer fmt.Println(x) // 当前保存 x 的值,而不是之后
可以做 defer 以最终输出变量的最后状态吗?
可以,通过匿名函数(闭包):
defer func() { fmt.Println(x) }() // 将在 deferred 调用时捕获 x 的当前值
代码这样调用:
var f *os.File // ... defer f.Close()
但 f 是稍后赋值的,因此会因调用 nil 指针而 panic!
优点:
缺点:
通过带有检查的匿名 deferred 函数包装清理操作:
defer func() { if f != nil { f.Close() } }()
优点:
缺点: