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

这样闭包的开销可能会稍微大些,因为它需要分配对象来收集由闭包捕获的所有变量。

最后更新于