Go语言defer的作用域为当前函数

10 Jan 2023 -

Go官方对defer关键字的行为解释,第一句是:

A “defer” statement invokes a function whose execution is deferred to the moment the surrounding function returns

defer的作用域是一个函数,在函数退出时立即调用。 在任何{ }包围的区间里写defer,效果等同于在该执行点注册一次defer。

1 在循环和if语句中注册defer

考虑下面的代码会打印什么内容?

package main

import "fmt"

func main() {
	i := 0

	for {
		defer func(itr int) {
			fmt.Printf("finish loop %d\n", itr)
		}(i)
		if i > 5 {
			break
		}
		i++
	}

	if i > 5 {
		defer fmt.Printf("finish if\n")
		fmt.Printf("Yes\n")
	}

	fmt.Printf("end proc\n")
}

结果是,先打印”Yes”和”end proc”,之后再按照defer注册的逆序打印相应内容,即defer的调用顺序。如下:

Yes
end proc
finish if
finish loop 6
finish loop 5
finish loop 4
finish loop 3
finish loop 2
finish loop 1
finish loop 0

2 在多次运行行为不同的代码块中注册defer

现在考虑如下代码。和上面的if语句不同的是,由于rand存在,程序每次运行的行为都可能与上一次不同。 那么defer的注册是否也可以是有选择的呢?

package main

import (
	"fmt"
	"math/rand"
	"time"
)

func main() {
	rand.Seed(time.Now().UnixMicro())
	r := rand.Int()
	if r%2 == 1 {
		defer fmt.Printf("random number is odd\n")
	}

	fmt.Printf("end proc, r%%2 = %v\n", r%2)
}

程序运行两次,产生不同的随机数与不同的行为:

end proc, r%2 = 1
random number is odd

end proc, r%2 = 0

所以可以得出结论,defer在代码块中的注册也是有选择的, 没有运行到的代码是不会对defer注册的。