defer特性:
- 关键字 defer 用于注册延迟调用。
- 这些调用直到 return 前才被执。因此,可以用来做资源清理。
- 多个defer语句,按先进后出的方式执行。
- defer语句中的变量,在defer声明时就决定了。
defer用途:
- 关闭文件句柄
- 锁资源释放
- 数据库连接释放
根据开发文档描述,下面来做一一验证。
1.defer用于注册延迟调用。
package main
import "fmt"
func main() {
fmt.Println("测试defer start")
defer func() {
fmt.Println("defer func")
}()
fmt.Println("测试defer end")
}
执行结果为:
证实defer函数用来延迟调用,在函数结束的时候被调用。函数结束包括:被调用方法正常return,或者到达方法体结尾,也或者包含defer函数的方法在执行panic的时候都会执行defer函数。
值得注意是,当包含defer函数的方法在执行panic操作的时候,会先执行defer函数,再执行panic方法,panic之后的语句是不可达的。下面我用实例来再次证明一下。
package main
import "fmt"
func main() {
fmt.Println("测试defer start")
defer func() {
fmt.Println("defer func")
}()
fmt.Println("测试defer end")
panic("panic 异常")
}
执行结果为:
由此看出,defer发生在panic执行之前,因此,可以在defer方法里面做资源清理,释放操作。
2.多个defer语句,按先进后出的方式执行。
package main
import "fmt"
func main() {
deferFunc1()
}
func deferFunc1() {
val := "1111"
defer fmt.Println("val1",val)
defer func() {
fmt.Println("val2",val)
}()
defer fmt.Println("val3",val)
defer func(val string) {
fmt.Println("val4",val)
}(val)
defer fmt.Println("val5",val)
fmt.Println("val",val)
}
执行结果为:
简单理解就是:定义defer类似于入栈操作,执行defer类似于出栈操作,先进后出。
3.defer语句中的变量,在defer声明时就决定了。
package main
import "fmt"
func main() {
deferFunc1()
}
func deferFunc1() {
val := "1111"
defer fmt.Println("val1",val)
defer func() {
fmt.Println("val2",val)
}()
defer fmt.Println("val3",val)
val = "2222"
defer func(val string) {
fmt.Println("val4",val)
}(val)
defer fmt.Println("val5",val)
val = "3333"
defer func() {
fmt.Println("val6",val)
}()
fmt.Println("val",val)
}
此时,可以考虑一下上面程序输出结果是啥?为什么?针对上面程序有几个易错点,经常被作为面试的笔试题来考察求职者对defer的理解。
下面看一张很经典的图片,golang程序调用顺序图。
var 这些变量是按照由上到下赋值的,结合上面程序来说,val变量最后被赋值为"3333",而defer又是推迟函数,按理说所有defer包含的延迟函数的val都是"3333"才对,但是执行结果出乎我们的意料。
由上面结果可以看出:
defer直接执行fmt.Println函数,相当于func(val string) val为形参,最后结果由实参决定。
defer执行func(){}()时,val变量的值对应最后赋值的值。这里还有一种特殊情况,defer func(val string) {}(val) 将变量传递到defer函数里面,此时func(val string) val为形参,它的值受传递的实参来决定的。