Go的defer语句
什么是 defer 语句
编程的时候,经常需要申请一些资源,比如数据库连接、文件、锁等,这些资源需要在用完之后释放掉,否则会造成内存泄漏。但开发人员经常容易忘记释放这些资源,从而造成一些事故。Go 语言直接在语言曾谜案提供 defer 关键字,在申请资源语句的下一行,可以直接用 defer 语句来注册函数结束后执行释放资源的动作。
defer 语句采用后进先出的设计,类似于栈的方式,函数执行时,每遇到一个 defer 都会把函数压入栈中,函数返回前再将函数从栈中取出来执行,最早被压入栈中的函数最晚执行那个。
注意,不仅函数正常金额数会执行 defer 延迟函数,函数中任意一个 return 语句、panic 语句都会触发延迟函数。
defer 语句如何使用
defer 补给你可以用于资源释放,也可以用于流程控制和异常处理,但 defer 关键字只能作用于函数或者函数调用。
defer 关键字后接一个匿名函数:
1 | |
defer 关键字后接一个函数调用
1 | |
defer 的行为规则
在 Go 官方博客中总结了 defer 的行为准则,规则只有短短的 3 条。
1)规则一:延迟函数的参数在 defer 语句出现时就已经确定了
1 | |
defer 语句中的 fmt.Println(i)参数 i 值在 defer 出现时就已经雀帝那个了,实际上是复制了一份。后面对变量 i 的修改不会影响 fmt.Println(i) 函数的执行,所以会打印 0。
2)规则二:延迟函数按后进先出(LIFO)的顺序执行,即先出现的 defer 最后执行
这个规则很好理解,定义 defer 类似于入栈操作,执行 defer 类似于出栈操作。
设计 defer 的初衷是简化函数返回时资源清理的动作,而资源往往有依赖顺序,比如先申请的 A 资源,再根据 A 资源申请 B 资源,根据 B 资源申请 C 资源,即申请顺序是 A-B-C,释放时往往要相反顺序。
3)规则三:延迟函数可能操作主函数的具名返回值
定义 defer 的函数(下面称主函数)可能有返回值,返回值可能有名子(具名返回值),也有可能没有返回值(匿名返回值),延迟函数可能会影响返回值。
若要理解延迟函数是如何影响主函数返回值的,只要明白函数是如何返回的就足够了。
函数中的return 关键字不是一个原子操作,比如 return i 在执行时,它会被为两步执行:
- 先将 i 值存入栈中作为返回值
- 然后执行 return
而 defer 语句的执行时机就是在这两步的中间,所以说 defer 语句也是有可能修改返回值的值的。
举个例子:
1 | |
以上函数的 return 语句可以拆分为下面两行:
1 | |
而延迟函数的执行正在 return 之前,所以加入 defer 之后的执行过程如下:
1 | |
所以上面的函数实际返回 i++。
4)根据返回方式的不同,defer 函数的所造成对返回值的修改也会不同
主函数有不同的返回方式,包括匿名返回值和具名返回值,先看看下面的举例:
(1)主函数拥有匿名返回值,返回字面值
一个主函数拥有匿名返回值,返回时使用字面值,这种情况下 defer 是无法操作返回值的。
1 | |
上面的 return 语句直接把 1 写入栈中作为返回值,延迟函数无法操作该返回值,所以也就无法影响返回值了。
(2)主函数拥有匿名返回值,返回变量
一个函数拥有一个匿名返回值,返回本地或全局变量,这种情况下 defer 语句可以引用返回值,但不会改变返回值。
1 | |
上面的函数返回一个局部变量,同时 defer 函数也会操作这个局部变量。对于匿名返回值来说,可以假设仍然有一个变量存储返回值,假设返回变量名为 anony,上面的返回语句可以拆分为以下过程:
1 | |
由于 i 是整型属于基本类型,所以会将值复制个 anony,所以在 defer 语句中修改 i 值,不会对函数返回值造成影响。
(3)主函数拥有具名返回值
主函数声明语句中大有名字的返回值会被初始化称一个局部变量,函数内部可以像使用局部变量一样使用该返回值。如果 defer 语句操作该返回值,则可能修改返回值。
1 | |
上面的函数拆解出来如下所示:
1 | |
函数在真正返回请安,在 defer 中对返回值做了+1 操作,所以函数最终返回 1。
总结
什么是 defer 语句
defer 语句用于延迟函数的迪奥用,常用于关闭文件描述符、释放锁等资源释放场景。
defer 如何使用
defer 关键字后接一个匿名函数:
1 | |
defer 关键字后接一个函数调用
1 | |
defer 的行为规则
- 规则一:延迟函数的参数在 defer 语句出现时就已经确定了
- 规则二:延迟函数按后进先出(LIFO)的顺序执行,即先出现的 defer 最后执行
- 规则三:延迟函数可能操作主函数的具名返回值
defer 可能会改变函数的返回值
- 主函数拥有匿名返回值,返回字面值
一个主函数拥有匿名返回值,返回时使用字面值,这种情况下 defer 是无法操作返回值的。
- 主函数拥有匿名返回值,返回变量
一个函数拥有一个匿名返回值,返回本地或全局变量,这种情况下 defer 语句可以引用返回值,但不会改变返回值。
- 主函数拥有具名返回值
主函数声明语句中大有名字的返回值会被初始化称一个局部变量,函数内部可以像使用局部变量一样使用该返回值。如果 defer 语句操作该返回值,则可能修改返回值。