基本概念
defer
介绍:defer主要用来注册多个延迟调用,这些调用以先进后出的顺序在函数返回前被执行。有点类似于java中的finaly语句。defer常用于保证一些资源最终能够得到回收和释放。
使用:
- defer函数的实参在注册的时候通过值拷贝传递进去。例如下面的代码,实参a的值在defer注册的时候通过值拷贝传递进去,后续语句a++并不会影响defer语句最后的输出结果。
func f() int{
a := 0
defer func(i int){
println("defer i=",i)
}(a)//这个地方是把参数传进去的功能
a++
return a
}
//打印结果
defer i=0
- defer语句必须先注册再执行,如果defer位于return之后,则defer因为没有注册,不会执行。
- 主动调用os.Exit(int)退出进程时,defer将不再被执行(即使已经提前注册了defer)
- 匿名返回值函数
// 匿名函数
func Anonymous() int {
var i int
defer func() {
i++
fmt.Println("defer2 value is ", i)
}()
defer func() {
i++
fmt.Println("defer1 in value is ", i)
}()
return i
}
命名返回值的函数
func HasName() (j int) {
defer func() {
j++
fmt.Println("defer2 in value", j)
}()
defer func() {
j++
fmt.Println("defer1 in value", j)
}()
return j
}
先来公布一下答案吧:
1.Anonymous()的返回值为0
2.HasName()的返回值为2
从这我们可以看出命名返回值的函数的返回值被 defer 修改了。这里想必大家跟我一样,都很疑惑,带着疑惑我查阅了一下go官方文档,文档指出,defer的执行顺序有以下三个规则:
1.A deferred function’s arguments are evaluated when the defer statement is evaluated.
2.Deferred function calls are executed in Last In First Out order after the surrounding function returns.
3.Deferred functions may read and assign to the returning function’s named return values.
规则3就可以印证为什么命名返回值的函数的返回值被更改了,其实在函数最终返回前,defer 函数就已经执行了,在命名返回值的函数 中,由于返回值已经被提前声明,所以 defer 函数能够在 return 语句对返回值赋值之后,继续对返回值进行操作,操作的是同一个变量,而匿名返回值函数中return先返回,已经进行了一次值拷贝r=i,defer函数中再次对变量i的操作并不会影响返回值。
这里可能有些小伙伴还不是很懂,我在讲一下return返回步骤,相信你们会豁然开朗。
- 函数在返回时,首先函数返回时会自动创建一个返回变量假设为ret(如果是命名返回值的函数则不会创建),函数返回时要将变量i赋值给ret,即有ret = i。
- 然后检查函数中是否有defer存在,若有则执行defer中部分。
- 最后返回ret
缺点:
- defer语句的位置不当,可能导致panic,一般defer语句放在错误检查语句之后;
- defer会推迟资源的释放,造成内存的浪费。
panic
- 引发panic的情况有两种,一种是程序主动调用panic函数,另一种是程序产生运行时错误,由运行时检测并抛出。
- 发生panic之后,程序会从调用panic的函数位置或者发生panic的地方立即返回,逐层向上执行函数的defer语句,然后逐层打印函数调用堆栈,直到被recover捕获或运行到最外层函数而退出。
- panic的参数是一个空接口类型interface{},所以任意类型的变量都可以传递给panic。
- panic不但可以在函数正常的流程中抛出,在defer逻辑里也可以再次调用panic或抛出panic。defer里面的panic能够被后续执行的defer捕获。
- 可以有连续多个panic被抛出,这种场景只能出现在延迟调用里面,否则不会出现多个panic被抛出的情况。但是只有最后一个panic会被捕获。
recover
- recover()用来捕获panic,阻止panic继续向上传递。recover()和defer一起使用,但是recover()只有在defer后面的函数体内被直接调用才能捕获panic终止异常,否则返回nil,异常继续向外传递。
使用场景
一般有两种情况:
- 程序遇到了无法正常执行下去的错误,主动调用panic函数结束程序运行
- 在调试程序的时候,通过主动调用panic实现快速退出,panic打印出的堆栈能够更快地定位错误。