defer不仅可以用于资源释放,也可以用于流程控制和异常处理,但defer关键字只能用于函数或函数调用。
defer关键字后接一个匿名函数
defer func(){
fmt.Println("hello world!")
}
defer关键字后接一个函数调用:
file,err :=os.open(name)
if err != nil {
return nil,err
}
defer file.Close()
1、使用场景
1)释放资源
m.mutex.Lock()
defer m.mutex.Unlock()
defer 常用于文件句柄、数据库连接,停止定时器 Ticker 以及关闭管道等资源清理场景 。
2)流程控制
var wg wait.group
defer wg.Wait()
defer 也用于控制函数的执行顺序,比如配合wait.Group实现等待携程的退出。
3)异常处理
defer 常用于处理异常,与recover配合可以消除panic,另外recover只能用于defer函数。
2、行为规则
1)规则一:延迟函数的参数在defer语句出现的时候就已经确定了
官方给出的例子如下图所示:
func f(){
i :=0
defer fmt.Println(i)
i++
return
}
defer语句中的fmt.Println()参数i值在defer出现时就已经确定了,实际上是复制了一份。后面对变量i的修改
不会影响fmt.Println()函数的执行,仍然打印0。
2) 规则二:延迟函数按照先进后出的顺序执行,也就是说在函数中,最先出现的defer最后执行。
3)规则三:延迟函数可能影响主函数的返回值
定义defer的函数可能有名字的返回值,也可能是没名字的返回值!延迟函数可能会影响到返回值。
那如果想要理解延迟函数是如何影响主函数返回值的,只要明白函数是如何返回就够了。
(1)有个事实必须要清楚,关键字return不是一个原子操作,实际上return只代表汇编指令ret,即跳转程序的执行。
比如return i,实际上是分两步执行,即先将i值存入栈中作为返回值,然后执行跳转,而defer的执行时机介于返回值
入栈与跳转之间。这意味着defer是有机会操作返回值的。
那举个例子:
func deferResult() (res int){
i :=1
defer func(){
res++
}
return i
}
该函数的return语句可以拆分成下面两行:
result = i
return
前面说过延迟函数在return之前执行
result = i //1
result++ //2
return
所以上面的返回值是 result++ 的值
(2)主函数有匿名函数返回值,则返回字面值。
一个主函数拥有一个匿名返回值,比如说1,2,3,"hello"等,那么defer是无法操作返回值的
比如下面一个返回字面值的函数:
func f() int{
var i int
defer func(){
i++
}
return 1
}
上面的return语句直接把1写入栈中作为返回值,延迟函数无法操作该返回值,索引就无法影响到该返回值。
(3)主函数拥有匿名返回值,返回变量。
一个主函数拥有一个匿名返回值,返回本地变量或者局部变量,这种情况下defer可以引用返回值,但不会改变返回值。
一个返回本地变量的函数如下:
func f() int{
var i int
defer func (){
i++
}
return i
}
上面的函数返回一个局部变量,同时defer函数也会操作这个局部变量。对于一个匿名返回值来说,可以假定一个匿名变量
来存储该返回值,那么上面的返回值可以拆分以下步骤:
anony = i
i++
return
由于i是整数,会将值复制给anony,所以defer语句中的修改不会函数返回值造成影响