避免defer陷阱:拆解延迟语句,掌握正确使用方法
基本概念
Go语言的延迟语句defer有哪些特点?通常在什么情况下使用?
Go语言的延迟语句(defer statement)具有以下特点:
延迟执行:延迟语句会在包含它的函数执行结束前执行,无论函数是正常返回还是发生异常。
后进先出:如果有多个延迟语句,它们会按照后进先出(LIFO)的顺序执行。也就是说,最后一个延迟语句会最先执行,而第一个延迟语句会最后执行。
通常情况下,延迟语句在以下情况下使用:
资源释放:延迟语句可以用于在函数返回前释放打开的文件、关闭数据库连接、释放锁等资源,以确保资源的正确释放,避免资源泄漏。
错误处理:延迟语句可以用于处理函数执行过程中可能发生的错误。通过在函数开始时设置延迟语句,在函数返回前检查错误并进行相应的处理,可以简化错误处理的逻辑。
日志记录:延迟语句可以用于在函数返回前记录日志或执行其他的调试操作,以便在函数执行过程中收集相关的信息。
延迟语句的使用可以提高代码的可读性和可维护性,同时确保资源的释放和清理操作按照逆序进行。它是Go语言中一种常用的编程技巧,用于处理资源管理和错误处理等场景。
避坑之旅
实际开发中defer的使用并不像前面介绍的这么简单,defer用不好,会陷入泥潭。
下面我从两个角度带大家避坑:
首先拆解一下延迟语句的执行,注意Go语言的return语句不是原子性的;
另外重点和大家分享一下defer语句后面使用匿名函数和非匿名函数的区别。
拆解延迟语句
避免陷入泥潭的关键是必须深刻理解下面这条语句:
return xxx
上面这条语句经过编译之后,实际上生成了三条指令:
1)返回值 =xxx。
2)调用 defer 函数。
3)空的 return。
第1和第 3 步是return语句生成的指令,也就是说return并不是一条原子指令;
第2步是 defer 定义的语句,这里可能会操作返回值,从而影响最终结果。
下面来看两个例子,试着将return 语句和 defer语句拆解到正确的顺序。
第一个例子:
func f()(r int){
t:=5
defer func(){
t=t+5
}()
return t
}
拆解后:
func f()(r int){
t:=5
//1,赋值指令
r=t
// 2.defer 被插入到赋值与返回之间执行,这个例子中返回值r没被修改过
func(){
t=t+5
}()
//3.空的 return 指令
return
}
这里第二步实际上并没有操作返回值r,因此,main函数中调用f()得到5。

第二个例子:
func f()(r int){
defer func(r int){
r=r+5
}(r)
return 1
}
拆解后:
func f() (r int) {
//1.赋值
r=1
//2.这里改的r是之前传进去的r,不会改变要返回的那个r值
func(r int) {
r=r+5
}(r)
// 3. 空的 return
return
}
第二步,改变的是传值进去的r,是形参的一个复制值,不会影响实参r。因此,main函数中需要调用f()得到1。

defer匿名函数
在Go语言中,使用匿名函数作为defer的参数时,可以理解为:defer语句中的匿名函数在包裹该defer语句的函数返回后才执行。这是因为defer语句的执行时机是在包裹函数即将返回之前,但在实际返回之前。
为什么不是在return语句之前执行呢?这是因为defer语句的设计初衷是为了在函数返回之前执行一些清理操作,例如关闭文件、释放资源等。将defer语句放在return语句之后,可以确保在函数返回之前执行这些清理操作,保证函数的执行完整性和资源的正确释放。
在使用匿名函数和非匿名函数作为defer的参数时,主要区别在于对函数参数的传递和作用域的影响:
匿名函数作为defer的参数:匿名函数可以直接在defer语句中定义,可以访问外部函数的变量,并且在执行时会使用当前的变量值。这种方式可以方便地在defer语句中使用外部变量,但需要注意变量的值在执行时可能已经发生了改变。
非匿名函数作为defer的参数:非匿名函数需要先定义好,然后作为defer的参数传递。在执行时,会使用函数的当前参数值。这种方式可以在defer语句中使用已定义的函数,但需要注意函数参数的传递和作用域。
产生这种区别的原因是,匿名函数和非匿名函数在定义和作用域上的差异。匿名函数可以直接在defer语句中定义,可以访问外部函数的变量,而非匿名函数需要先定义好,然后作为参数传递。这种设计灵活性使得开发者可以根据具体的需求选择合适的方式来使用defer语句。
举例来说
当使用匿名函数作为defer的参数时,可以在defer语句中直接定义匿名函数,并访问外部变量。
以下是一个示例代码:
package main
import "fmt"
func main() {
x := 10
defer func() {
fmt.Println("Deferred anonymous function:", x)
}()
x = 20
fmt.Println("Before return:", x)
}
在上述示例中,匿名函数作为defer的参数,可以访问外部变量x。
在函数返回之前,defer语句中的匿名函数会执行,并打印出x的值。
输出结果如下:

当使用非匿名函数作为defer的参数时,需要先定义好函数,然后将函数名作为defer的参数传递。
以下是一个示例代码:
package main
import "fmt"
func main() {
x := 10
defer printX(x)
x = 20
fmt.Println("Before return:", x)
}
func printX(x int) {
fmt.Println("Deferred function:", x)
}
在上述示例中,printX函数作为defer的参数传递,函数定义在main函数之后。
在函数返回之前,defer语句中的printX函数会执行,并打印出传递的参数x的值。输出结果如下:

总结一下
通过以上示例,我们可以明确体现出使用匿名函数和非匿名函数作为defer的参数的区别。
匿名函数可以直接在defer语句中定义,并访问外部变量,而非匿名函数需要先定义好函数,然后将函数名作为参数传递。
通过前面带着大家拆解了defer的语句的执行,相信大家可以更好的理解了。
更多defer使用的技巧和踩坑经验,欢迎在评论区交流讨论。
欢迎加我微信:wangzhongyang1993
避免defer陷阱:拆解延迟语句,掌握正确使用方法的更多相关文章
- oracle 数据库中,应用程序里的连接探測语句的正确使用
oracle 数据库中,应用程序里的连接探測语句的正确使用 本文为原创文章.转载请注明出处:http://blog.csdn.net/msdnchina/article/details/3851376 ...
- 判断下列语句是否正确,如果有错误,请指出错误所在?interface A{
判断下列语句是否正确,如果有错误,请指出错误所在? interface A{ int add(final A a); } class B implements A{ long add(final A ...
- 用XPath定位Web页面元素时,如何快速验证XPath语句是否正确?
在使用Selenium做Web UI自动化测试的过程中,XPath是一种定位页面元素的常用方式.然而,面对某些元素的XPath路径过于复杂,我们想快速验证拼凑的Xpath语句是否正确时,该怎么办呢?这 ...
- jquery源码解析:jQuery延迟对象Deferred(工具方法)详解2
请接着上一课继续看. $.Deferred()方法中,有两个对象,一个是deferred对象,一个是promise对象. promise对象有以下几个方法:state,always,then,prom ...
- CentOS正确关机方法(转)
CentOS正确关机方法 1关机前准备 1.1观察系统使用状态 · 谁在线:who · 联网状态:netstat -a · 后台执行的程序:ps -au ...
- thinkphp3.2 cli模式的正确使用方法
最近要使用thinkphp3.2版本的cli模式,手动执的话没有问题,比如php /www/index.php home/article/get 这样没有问题,但是一般用cli模式都是定时任务比较多, ...
- Linux重启inotify配置max_user_watches无效被恢复默认值8192的正确修改方法
Linux下Rsync+inotify-tools实现数据实时同步中有一个重要的配置就是设置Inotify的max_user_watches值,如果不设置,当遇到大量文件的时候就会出现出错的情况. 一 ...
- MyEclipse10的正确破解方法
无法转载,故给出原文链接,以供需要者. MyEclipse10的正确破解方法
- [转]MySQL忘记密码的正确解决方法
http://database.51cto.com/art/201005/201986.htm 以下的文章主要介绍的是MySQL忘记密码的正确解决方法,在实际操作中如果你忘记MySQL密码是一件很头痛 ...
- Hibernate的hql语句save,update方法不执行
Hibernate的hql语句save,update方法不执行 可能出现的原因问题: 未进行事务管理 需要进行xml事务配置或者注解方式的事务配置
随机推荐
- 一键配置 Linux 环境:zsh + tmux + vim
默认使用root用户进行安装,整个流程优化过之后,如下 curl -sSL http://119.3.1.43/pub/sh/init-terminal.sh | bash -x # 安装完成之后,重 ...
- centos7.9 时间相关整理
1.date / timedatectl 显示当前时间(秒): date / date +"%Y-%m-%d %H:%M:%S" (%Y等含义通过data --h查看) 显示当前时 ...
- ABC274 题解
A 题目:给定 \(A,B\) 输出 \({B}\over{A}\) 保留 \(3\) 位小数. 简答题,和A+B problem 一样,除一除,保留一下小数. B 题目:给定一个 \(n\) 行 \ ...
- openlayers动态添加自定义div图层 具有筛选功能 和浮窗
https://blog.csdn.net/weixin_43863505/article/details/119493664
- Unity的UnityStats: 属性详解与实用案例
UnityStats 属性详解 UnityStats 是 Unity 引擎提供的一个用于监测游戏性能的工具,它提供了一系列的属性值,可以帮助开发者解游戏的运行情况,从而进行优化.本文将详细介绍 Uni ...
- Python | 函数、数据容器
1.函数 函数:是组织好的,可重复使用的,用来实现特定功能的代码段. 1.1 简单案例 重复使用计算字符串的长度 str1 = "heystar" str2 = "pyt ...
- Hugging Face 的文本生成和大语言模型的开源生态
[更新于 2023 年 7 月 23 日: 添加 Llama 2.] 文本生成和对话技术已经出现多年了.早期的挑战在于通过设置参数和分辨偏差,同时控制好文本忠实性和多样性.更忠实的输出一般更缺少创造性 ...
- 【后端面经-数据库】Redis详解——Redis基本概念和特点
目录 1. Redis基本概念 2. Redis特点 2.1 优点 2.2 缺点 3. Redis的应用场景 面试模拟 参考资料 声明:Redis的相关知识是面试的一大热门知识点,同时也是一个庞大的体 ...
- Dokcer学习之旅(1)——运行一个简单的容器
基本概念 镜像 我们都知道,操作系统分为 内核 和 用户空间.对于 Linux 而言,内核启动后,会挂载 root 文件系统为其提供用户空间支持.而 Docker 镜像(Image),就相当于是一个 ...
- Andrew Ng 机器学习&深度学习课程 代码作业解答 集合
写在最前 2018年是对自己来说是崭新的一年,在过去的3个多月里,从最基础的lr, 学到现在的LSTM, GAN..感觉第一次追上了计算机科学飞速发展的浪潮.虽然很多地方都仍是一知半解,但时间还长 ...