Go 中Defer 核心知识点
Posted 半塘少年
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Go 中Defer 核心知识点相关的知识,希望对你有一定的参考价值。
1. defer的执行顺序
多个defer出现的时候,它是一个“栈”的关系,也就是先进后出。一个函数中,写在前面的defer会比写在后面的defer调用的晚。
package main
import "fmt"
func main()
defer func1()
defer func2()
defer func3()
func func1()
fmt.Println("A")
func func2()
fmt.Println("B")
func func3()
fmt.Println("C")
输出结果:
C
B
A
2. defer与return谁先谁后
package main
import "fmt"
func deferFunc() int
fmt.Println("defer func called")
return 0
func returnFunc() int
fmt.Println("return func called")
return 0
func returnAndDefer() int
defer deferFunc()
return returnFunc()
func main()
returnAndDefer()
执行结果为:
return func called
defer func called
结论为:return之后的语句先执行,defer后的语句后执行
3. 函数的返回值初始化
该知识点不属于defer本身,但是调用的场景却与defer有联系,所以也算是defer必备了解的知识点之一。
如 : func DeferFunc1(i int) (t int)
其中返回值t int
,这个t
会在函数起始处被初始化为对应类型的零值并且作用域为整个函数。
package main
import "fmt"
func DeferFunc1(i int) (t int)
fmt.Println("t = ", t)
return 2
func main()
DeferFunc11(10)
结果
t = 0
证明,只要声明函数的返回值变量名称,就会在函数初始化时候为之赋值为0,而且在函数体作用域可见。
4.有名函数返回值遇见defer情况
在没有defer的情况下,其实函数的返回就是与return一致的,但是有了defer就不一样了。
我们通过知识点2得知,先return,再defer,所以在执行完return之后,还要再执行defer里的语句,依然可以修改本应该返回的结果。
package main
import "fmt"
func returnButDefer() (t int) //t初始化0, 并且作用域为该函数全域
defer func()
t = t * 10
()
return 1
func main()
fmt.Println(returnButDefer())
该returnButDefer()
本应的返回值是1
,但是在return之后,又被defer的匿名func函数执行,所以t=t*10
被执行,最后returnButDefer()
返回给上层main()
的结果为10
$ go run test.go
10
5. defer遇见panic
我们知道,能够触发defer的是遇见return(或函数体到末尾)和遇见panic。
根据知识点2,我们知道,defer遇见return情况如下:
那么,遇到panic时,遍历本协程的defer链表,并执行defer。在执行defer过程中:遇到recover则停止panic,返回recover处继续往下执行。如果没有遇到recover,遍历完本协程的defer链表后,向stderr抛出panic信息。
(1)defer遇见panic,但是并不捕获异常的情况
package main
import (
"fmt"
)
func main()
defer_call()
fmt.Println("main 正常结束")
func defer_call()
defer func() fmt.Println("defer: panic 之前1") ()
defer func() fmt.Println("defer: panic 之前2") ()
panic("异常内容") //触发defer出栈
defer func() fmt.Println("defer: panic 之后,永远执行不到") ()
结果
defer: panic 之前2
defer: panic 之前1
panic: 异常内容
//... 异常堆栈信息
(2)defer遇见panic,并捕获异常
package main
import (
"fmt"
)
func main()
defer_call()
fmt.Println("main 正常结束")
func defer_call()
defer func()
fmt.Println("defer: panic 之前1, 捕获异常")
if err := recover(); err != nil
fmt.Println(err)
()
defer func() fmt.Println("defer: panic 之前2, 不捕获") ()
panic("异常内容") //触发defer出栈
defer func() fmt.Println("defer: panic 之后, 永远执行不到") ()
结果
defer: panic 之前2, 不捕获
defer: panic 之前1, 捕获异常
异常内容
main 正常结束
defer 最大的功能是 panic 后依然有效
所以defer可以保证你的一些资源一定会被关闭,从而避免一些异常出现的问题。
6. defer中包含panic
编译执行下面代码会出现什么?
package main
import (
"fmt"
)
func main()
defer func()
if err := recover(); err != nil
fmt.Println(err)
else
fmt.Println("fatal")
()
defer func()
panic("defer panic")
()
panic("panic")
结果
defer panic
分析
panic仅有最后一个可以被revover捕获。
触发panic("panic")
后defer顺序出栈执行,第一个被执行的defer中 会有panic("defer panic")
异常语句,这个异常将会覆盖掉main中的异常panic("panic")
,最后这个异常被第二个执行的defer捕获到。
7. defer下的函数参数包含子函数
package main
import "fmt"
func function(index int, value int) int
fmt.Println(index)
return index
func main()
defer function(1, function(3, 0))
defer function(2, function(4, 0))
这里,有4个函数,他们的index序号分别为1,2,3,4。
那么这4个函数的先后执行顺序是什么呢?这里面有两个defer, 所以defer一共会压栈两次,先进栈1,后进栈2。 那么在压栈function1的时候,需要连同函数地址、函数形参一同进栈,那么为了得到function1的第二个参数的结果,所以就需要先执行function3将第二个参数算出,那么function3就被第一个执行。同理压栈function2,就需要执行function4算出function2第二个参数的值。然后函数结束,先出栈fuction2、再出栈function1.
所以顺序如下:
- defer压栈function1,压栈函数地址、形参1、形参2(调用function3) --> 打印3
- defer压栈function2,压栈函数地址、形参1、形参2(调用function4) --> 打印4
- defer出栈function2, 调用function2 --> 打印2
- defer出栈function1, 调用function1--> 打印1
3
4
2
1
以上是关于Go 中Defer 核心知识点的主要内容,如果未能解决你的问题,请参考以下文章
Go ---- defer 和 return 执行的先后顺序