defer的陷阱

当使用defer时,请记住如下几点:延迟函数在函数的末尾调用;延迟语句的作用域为函数级别,而不是代码块级别。
换句话说:延迟调用只在函数结束退出时执行,并不在iffor语句创建的代码块中执行。
func main() {
fmt.Println("Before if")
if true {
defer fmt.Println("inside if")
}
fmt.Println("Ater if")
}
// ----Output-----
// Before if
// Ater if
// inside if
您也许以为上述延迟语句会在退出if分支时执行,但事实上它只在函数的最后被执行。

在延迟函数中使用外部变量

func main() {
for i := 0; i < 2; i++ {
defer func() {
fmt.Printfln("%d", i)
}()
}
}
// ----Output----
// 2
// 2
一个常犯的错误是认为上述代码会输出01。当我们看到defer时,它们就是i的值。
但是,defer创建了一个闭包,它只捕获了i的引用,而不是变量的值。
我们看看上述代码实际会怎样执行就清楚了:
var i int
for i = 0; i < 2; i++ {
// 创建闭包,通过i的引用来引用i
}
fmt.Println("%d", i) // 第2次循环迭代中创建的 (记住: 倒序的)
fmt.Println("%d", i) // 在第1次循环迭代中创建的闭包
现在已经清楚了延迟调用fmt.Println时为何两次i的值都是2。
我们可以通过强制捕获变量来修正上述行为:
func main() {
for i := 0; i < 2; i++ {
defer func(i2 int) {
fmt.Println(i2)
}(i)
}
}
//----Output----
// 1
// 0
这样闭包的开销可能会稍微大些,因为它需要分配对象来收集由闭包捕获的所有变量。