defer概览
- defer是go语言里的一个关键字,在 函数内部使用;
- defer关键字后面跟一个 函数或匿名函数;
defer用法
- 执行一些资源的收尾工作,如 关闭数据库连接,关闭文件描述符,释放资源等等;
- 结合recover()函数使用,防止函数内部的异常导致整个程序停止;
- defer在遇到panic后,仍然会执行(defer语句要在panic之前编译),从而保证即使出错也能进行对应的错误处理;
函数中多个defer间存储方式及运行顺序
defer数据结构
type _defer struct {
siz int32
started bool
openDefer bool
sp uintptr
pc uintptr
fn *funcval
_panic *_panic
link *_defer
}
- _defer结构体中的link字段 指向下一个defer结构体地址的指针;
- _defer结构体是 延迟调用链表中的一个元素,所有的结构体都是通过 link字段串联成链表;
- 使用链表方式存储,
- 代码从上到下编译,遇到的 defer都会放到链表头部,后面执行的执行按照链表顺序从头到尾执行
- defer编译及执行顺序 就是 栈的入栈出栈的顺序
defer必须要了解的特性
多defer执行顺序
结论
多defer执行顺序是 最后编译的先执行;
defer后面的函数的参数值的预计算
结论
defer在编译时,会对后面跟着的函数的参数值进行预计算;
也就是 编译器编译到此行时,会立刻确定 defer后面跟着函数的参数值,并且是 值传递方式,不是引用传递方式; 这样后续无论 这个参数对应的变量 值怎么改变,都和defer后面的函数无关;
所以 即使 参数 传了一个 函数,也会先计算函数的值 作为参数 进行值引用;
示例代码
defer后面跟的是系统自带的 fmt.Println函数
package main
import "fmt"
func A(v int) int {
fmt.Println("A函数,入参为", v)
v += 1
return v
}
func UseA(v int) int {
defer fmt.Println("defer执行结果", A(v)) // A(v)的值:2 对应解析里的 "即使 参数 传了一个 函数,也会先计算函数的值 作为参数 进行值引用; "
defer fmt.Println("defer执行结果", v) //v的值:1 对应解析里的 "后续无论 这个参数对应的变量 值怎么改变,都和defer后面的函数无关;"
fmt.Println("UseA执行")
v += 5
fmt.Println("UseA执行return代码前的最后一行")
return v
}
func main() {
fmt.Println("UseA执行结果", UseA(1)) //6
return
}
代码执行结果
defer与return关键字执行顺序
结论
- defer会在return关键字之后, 在返回给调用方前执行;
示例代码
package main
import "fmt"
func A(v int) int {
fmt.Println("A函数,入参为", v)
v += 1
return v
}
func UseOtherA(v int) int {
defer fmt.Println("defer执行")
return A(v)
}
func main() {
fmt.Println("UseOtherA执行结果",UseOtherA(1))
return
}
代码执行结果
defer对 函数的返回值 是否定义变量名的影响
示例代码
package main
import "fmt"
func A(v int) int {
fmt.Println("A函数,入参为", v)
v += 1
return v
}
func B(v int) (result int) {
result = v
defer func() {
result = A(v)
}()
return
}
func C(v int) int {
defer func() {
v = A(v)
}()
return v
}
func main() {
fmt.Println("B执行结果", B(1))
fmt.Println("C执行结果", C(1))
return
}
代码执行结果
结论
- 定义返回值变量名 的函数,在返回给函数调用方前,这个变量的值都是可以修改的;
- 未定义返回值变量名 的函数, 如上示例的C函数在 return语句执行时,其实是 将v变量 赋值给一个未定义名字的隐藏变量,来完成值传递, 所以后续对v变量的操作对 返回给函数调用结果无任何影响; 示意如下
defer遇到panic执行情况
结论
- panic触发后,函数内的后续代码不再执行,在panic之前编译的defer仍然会执行;
示例代码
package main
import "fmt"
func PanicT() {
defer func() {
if err := recover(); err != nil {
fmt.Println("执行recover()方法,尝试恢复程序,错误内容为:", err)
}
}()
defer fmt.Println("defer1")
defer fmt.Println("defer2")
panic("手动触发panic")
defer fmt.Println("defer3,执行不到")
}
func main() {
PanicT()
return
}
运行结果
defer中包含panic
结论
- panic会被覆盖,只保留最新的panic;
- 如: 函数中的panic会被defer的panic覆盖;
- 如: 多个defer都有panic,只有最后执行的defer的panic会保留;
- 其实 defer 后面也是普通函数,那么普通函数遇到panic就会停止运行,执行后续defer的函数;
代码示例
package main
import "fmt"
func PanicMany() {
defer func() {
if err := recover(); err != nil {
fmt.Println("执行recover()方法,尝试恢复程序,错误内容为:", err)
}
}()
defer func() {
fmt.Println("defer1执行")
panic("defer1手动触发panic")
fmt.Println("defer1执行不到此处")
}()
defer func() {
fmt.Println("defer2执行")
panic("defer2手动触发panic")
fmt.Println("defer2执行不到此处")
}()
panic("手动触发panic")
defer fmt.Println("defer3,执行不到")
}
func main() {
PanicMany()
return
}
代码执行结果
知识点训练
func main() {
fmt.Println(test1())
fmt.Println(test2())
fmt.Println(test3())
fmt.Println(test4())
}
func test1() (v int) {
defer fmt.Println(v) //0
return v //0
}
func test2() (v int) {
defer func() {
fmt.Println(v) //3
}()
return 3 //3
}
func test3() (v int) {
defer func() {fmt.Println(v)}() //4
defer fmt.Println(v) //0
defer func(v int) { fmt.Println(v) }(v) //0
v = 3
return 4 //4
}
func test4() (v int) {
defer func(n int) {
fmt.Println(n) //0
}(v)
return 5 //5
}
总结
- 对于defer执行结果的准确预测, 要了解函数的参数传递方式,函数的返回值是否定义变量名时 编译器的执行过程 等额外的知识点;
- defer代码编写时经常遇到,常用于 异常捕捉,资源释放等收尾工作;