Go基础17-明确哪些函数可以作为deferred函数?

绝大多数Gopher喜欢使用defer,因为它让函数变得简洁且健壮。但“工欲善其事,必先利其器”,要想用defer,就需要提前了解几个关于defer的关键问题,以避免掉进一些不必要的“坑”。

  1. 明确哪些函数可以作为deferred函数
    对于自定义的函数或方法,defer可以给予无条件的支持,但是对于有返回值的自定义
    函数或方法,返回值会在deferred函数被调度执行的时候被自动丢弃。
    Go语言中除了有自定义的函数或方法,还有内置函数。下面是Go语言内置函数的完整
    列表:
    append cap close complex copy delete imag len
    make new panic print println real recover
    内置函数是否都能作为deferred函数呢?我们看看下面的示例:
    // chapter4/sources/deferred_func_6.go
func bar() (int, int) {
    
    
return 1, 2
}
func foo() {
    
    
var c chan int
var sl []int
var m = make(map[string]int, 10)
m["item1"] = 1
m["item2"] = 2
var a = complex(1.0, -1.4)
var sl1 []int
defer bar()
defer append(sl, 11)
defer cap(sl)
defer close(c)
defer complex(2, -2)
defer copy(sl1, sl)
defer delete(m, "item2")
defer imag(a)
defer len(sl)
defer make([]int, 10)
defer new(*int)
defer panic(1)
defer print("hello, defer\n")
defer println("hello, defer")
defer real(a)
defer recover()
}
func main() {
    
    
foo()
}

运行该示例:

./deferred_func_6.go:22:2: defer discards result of append(sl, 11)
./deferred_func_6.go:23:2: defer discards result of cap(sl)
./deferred_func_6.go:25:2: defer discards result of complex(2, -2)
./deferred_func_6.go:28:2: defer discards result of imag(a)
./deferred_func_6.go:29:2: defer discards result of len(sl)
./deferred_func_6.go:30:2: defer discards result of make([]int, 10)
./deferred_func_6.go:31:2: defer discards result of new(*int)
./deferred_func_6.go:35:2: defer discards result of real(a)

Go编译器给出了一组错误提示!从中我们看到,append、cap、len、make、new等内置函数是不可以直接作为deferred函数的,而close、copy、delete、print、recover等可以。

对于那些不能直接作为deferred函数的内置函数,我们可以使用一个包裹它的匿名函数来间接满足要求。以append为例:

defer func() {
    
    
_ = append(sl, 11)
}()

但这么做有什么实际意义需要开发者自己把握。

2. 把握好defer关键字后表达式的求值时机

牢记一点,defer关键字后面的表达式是在将deferred函数注册到deferred函数栈的时候进行求值的。

下面用一个典型的例子来说明defer关键字后表达式的求值时机:

func foo1() {
    
    
for i := 0; i <= 3; i++ {
    
    
	defer fmt.Println(i)
}
}
func foo2() {
    
    
	for i := 0; i <= 3; i++ {
    
    
		defer func(n int) {
    
    
		fmt.Println(n)
	}(i)
}
}

func foo3() {
    
    
for i := 0; i <= 3; i++ {
    
    
defer func() {
    
    
fmt.Println(i)
}()
}
}
func main() {
    
    
	fmt.Println("foo1 result:")
	foo1()
	fmt.Println("\nfoo2 result:")
	foo2()
	fmt.Println("\nfoo3 result:")
	foo3()
}

我们逐一分析foo1、foo2和foo3中defer关键字后的表达式的求值时机:
在foo1中,defer后面直接接的是fmt.Println函数,每当defer将fmt.Println注册到deferred函数栈的时候,都会对Println后面的参数进行求值。根据上述代码逻辑,依次压入deferred函数栈的函数是:

fmt.Println(0)
fmt.Println(1)
fmt.Println(2)
fmt.Println(3)

因此,在foo1返回后,deferred函数被调度执行时,上述压入栈的deferred函数将以
LIFO次序出栈执行,因此输出的结果为:

3
2
1
0

在foo2中,defer后面接的是一个带有一个参数的匿名函数。每当defer将匿名函数注
册到deferred函数栈的时候,都会对该匿名函数的参数进行求值。根据上述代码逻辑,依
次压入deferred函数栈的函数是:

func(0)
func(1)
func(2)
func(3)

因此,在foo2返回后,deferred函数被调度执行时,上述压入栈的deferred函数将以
LIFO次序出栈执行,因此输出的结果为:

3
2
1
0

在foo3中,defer后面接的是一个不带参数的匿名函数。根据上述代码逻辑,依次压入
deferred函数栈的函数是:

func()
func()
func()
func()

因此,在foo3返回后,deferred函数被调度执行时,上述压入栈的deferred函数将以
LIFO次序出栈执行。匿名函数以闭包的方式访问外围函数的变量i,并通过Println输出i的
值,此时i的值为4,因此foo3的输出结果为:

4
4
4
4

鉴于defer表达式求值时机十分重要,我们再来看一个例子:

func foo1() {
    
    
	sl := []int{
    
    1, 2, 3}
	defer func(a []int) {
    
    
	fmt.Println(a)
	}(sl)
	sl = []int{
    
    3, 2, 1}
	_ = sl
}
func foo2() {
    
    
	sl := []int{
    
    1, 2, 3}
	defer func(p *[]int) {
    
    
	fmt.Println(*p)
	}(&sl)
	sl = []int{
    
    3, 2, 1}
	_ = sl
}
func main() {
    
    
	foo1()
	foo2()
}

我们分别分析一下这个示例中的foo1、foo2函数。
在foo1中,defer后面的匿名函数接收一个切片类型参数,当defer将该匿名函数注册到deferred函数栈的时候,会对它的参数进行求值,此时传入的变量sl的值为[]int{1, 2,3},因此压入deferred函数栈的函数是:

func([]int{
    
    1,2,3})

之后虽然sl被重新赋值,但是在foo1返回后,deferred函数被调度执行时,deferred函数的参数值依然为[]int{1, 2, 3},因此foo1输出的结果为[1 2 3]。

在foo2中,defer后面的匿名函数接收一个切片指针类型参数,当defer将该匿名函数注册到deferred函数栈的时候,会对它的参数进行求值,此时传入的参数为变量sl的地址,因此压入deferred函数栈的函数是:
func(&sl)之后虽然sl被重新赋值,但是在foo2返回后,deferred函数被调度执行时,deferred函数的参数值依然为sl的地址,而此时sl的值已经变为[]int{3, 2, 1},因此foo2输出的结果为[3 2 1]。

猜你喜欢

转载自blog.csdn.net/hai411741962/article/details/132808748
今日推荐