go 语言之defer

defer

对于go语言里面的defer关键字来说,是表示延迟调用,通常用于关闭一些资源,比如打开的文件资源,socket连接,同时也配合recover函数来处理panic的异常。
1、单个defer例子:

func test1(){
	fmt.Println("start")
	defer fmt.Println("defer")
	fmt.Println("end")
}

func main() {
	test1()
}

上述输出结果是:

start
end
defer

2、多个defer例子

func test1(){
	fmt.Println("start")
	defer fmt.Println("defer1")
	defer fmt.Println("defer2")
	fmt.Println("end")
}

func main() {
	test1()
}

输出结果

start
end
defer2
defer1

说明:对于多个defer来说,执行的顺序与定义的顺序是相反的。
比如:对于上述的代码,在执行到第一个defer的时候,go编译器会把当前的derfer放到一个栈里面,然后执行下一行代码,发现下一行代码也是defer,那么也会放到同一个栈(因为属于同一个函数作用域),当函数执行完成以后,准备返回时,发现还有defer语句没有执行,那么就会从栈里面把defer语句拿出来执行。由于栈是先进后出,也就说明了defer是先定义后执行了。

3、defer执行的时机在说明
一般来说,对于函数没有返回值的情况下,defer并不影响最终的结果,当函数有返回值时,来看看下面的一个例子:

func test2() int{
	x := 1
	defer func ()  {
		x++
	}()

	return x
}

func main() {
	fmt.Println(test2())
}

执行结果:

1

可以发现,函数最终返回的结果是1,而不是2.
说明:
go语言当中 return并不是原子性的,对于 return x来说,首先执行的是对x赋值,然后在执行return。二defer关键字的执行时机就是在x赋值之后,return之前。
那么为什么最终返回的是1而不是2呢?
个人理解如下:
由于函数的返回值是没有命名的返回值类型,所以当执行到return x以后,保存的是x这个变量的副本,但是由于x是值类型,所以这个副本里面存储的是真真实实的int类型,也就是1,所以后续在defer里面对x++,改变的是x的值,对于return后面的副本来说是不会修改的。其实这有点类似函数的形参与实参的区别。

那么再来看一个下面的例子:

func test2() []int{
	x := []int{1,2,3}
	defer func ()  {
		x[0] = 10
	}()

	return x
}

func main() {
	fmt.Println(test2())
}

返回结果:

[10 2 3]

其实这就跟上述说的一致,return x语句的x保存的是一个副本,但是由于x是一个引用类型,所以在defer里面进行对x修改以后,最终的x值也就发生了变化。

在来看一个命名返回值参数类型的函数

func test3() (x int){
	defer func ()  {
		x = 20
	}()

	x = 10
	return
}

func main() {
	fmt.Println(test3())
}

函数返回值:

20

为什么是20呢?
这是因为这是一个命名返回值的函数,在go语言当中,命名返回值当中的x就已经是一个实实在在的变量了,所以当在函数当中进行修改的时候,最终都是修改的这个x值。
同理,看一下下面这个函数的返回值是什么:

func test3() (x int){
	defer func ()  {
		x = 20
	}()

	x = 10
	return 5
}

func main() {
	fmt.Println(test3())
}

这里依然返回20,因为命名返回值来说,返回的就是x的值。跟5就没什么关系了。

posted @ 2021-04-03 13:23  北漂-boy  阅读(343)  评论(0编辑  收藏  举报