编程后端开发工程师

解释 Go 中 deferred function parameters 的工作特点:何时以及如何计算 defer 的参数,与在 defer 触发时调用函数有何不同?

用 Hintsage AI 助手通过面试

答案。

Defer 是 Go 的一个独特机制,它允许在当前函数结束后执行一个函数,即使发生了 panic。历史上它类似于 on-exit 结构,但在 Go 中实现为一个 deferred 调用栈。重要的细节是,deferred 函数的参数在声明 defer 时立即计算,而不是在它实际被调用时!

问题 — 不明显的行为:可能会期望参数在 defer 触发时传递,但事实并非如此。这常常导致在处理可变或外部变量时出现 bug。

解决方案 — 始终要记住,参数是立即计算的,而 deferred 调用的效果随后才生效。

代码示例:

func f() { x := 5 defer fmt.Println(x) x = 10 } // 输出:5,而不是 10

关键特点:

  • deferred 函数的参数值在声明 defer 时计算。
  • 即使发生 panic(如果 panic 未被提前捕获),deferred 函数总是会执行。
  • 如果在 deferred 中声明匿名函数,则可以访问变量的当前值。

隐藏的问题。

以下代码将输出什么?为什么?

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 的当前值

常见错误和反模式

  • 期待参数在 deferred 调用时是 актуальные。
  • 与可变变量一起使用的 defer 具有参数。
  • 没有明确依赖关系的混乱 defer 栈。

生活中的例子

负面案例

代码这样调用:

var f *os.File // ... defer f.Close()

但 f 是稍后赋值的,因此会因调用 nil 指针而 panic!

优点:

  • 如果变量已经初始化,则记录简洁。

缺点:

  • 如果 f == nil — panic。

正面案例

通过带有检查的匿名 deferred 函数包装清理操作:

defer func() { if f != nil { f.Close() } }()

优点:

  • 安全性和无 panic。

缺点:

  • 记录更长且"吵闹"。