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就没什么关系了。