golang中的defer关键字


defer的应用场景

golang中的defer关键字经常在方法中完成一些收尾工作,常见的有数据库的关闭连接。

defer的执行顺序

package main

import "fmt"

func main() {
	for i := 0; i < 5; i++ {
		fmt.Println(i)
		defer fmt.Println(i)
	}
} 

上面的代码运行的结果为:

0

1

2

3

4

4

3

2

1

0 

之所以会出现上面的运行结果,是因为golang的程序优先执行fmt.Println(i),当执行遇到defer关键字的时候,golang会把关键字后需要执行的操作暂存起来,暂存的结构很像栈。 等整个循环全部运行结束后,暂存起来的defer操作会以出栈的方式运行,也就是倒序运行。 所以运行结果会先有0,1,2,3,4。这个是正常循环出来的结果。然后会有4,3,2,1,0的结果,这是defer暂存的操作以出栈方式执行的结果。

多个defer的执行顺序

上面是一个简单的defer执行顺序实例,咱们再看一个稍微复杂的defer执行顺序实例:

package main

import "fmt"

func main() {
	oneList := []int{}
	for i := 0; i < 5; i++ {
		defer fmt.Println(oneList)
		oneList = append(oneList, i)
		defer fmt.Println(i)
	}
} 

上面代码的运行结果为:

4

[0 1 2 3]

3

[0 1 2]

2

[0 1]

1

[0]

0

[] 

可能有人会疑惑,为什么程序没有打印[0,1,2,3,4]?因为当最后一次运行时,defer关键字捕获的oneList对象还没有添加4这个元素,然后运行就结束了,所以不会运行出[0,1,2,3,4]这个结果。

defer的赋值

我们再来看一下稍微更复杂的代码

package main

import "fmt"

type SortNum struct {
	IntList []int `json:"int_list"`
}

func (sn *SortNum) ReverseNum(oneInt int) {
	sn.IntList = append(sn.IntList, oneInt)
	fmt.Println(*sn)
}

func main() {
	var sn SortNum
	for i := 0; i < 5; i++ {
		defer sn.ReverseNum(i)
	}
	fmt.Println(sn)
} 

上面代码的运行结果是:

{[]}

{[4]}

{[4 3]}

{[4 3 2]}

{[4 3 2 1]}

{[4 3 2 1 0]} 

出现这种运行结果也在情理之中,首先打印的{[]}表示循环体外的fmt.Println(sn),此时的sn对象还没有进行任何操作,打印出来的就是一个空的对象。而第二个输出{[4]}则是执行defer的第一个出栈结果,就是说defer的最后一次循环捕获的参数是4,所以就优先把4加入到sn对象的IntList中,然后依次运行,根据出栈顺序,sn对象的IntList就依次加入3,2,1,0。这也很符合程序的运行结果。

总结:

defer 函数的运行生命周期是在函数return之前,在非defer逻辑运行完之后。以出栈的方式运行。