基本概念

Go语言的延迟语句defer有哪些特点?通常在什么情况下使用?

Go语言的延迟语句(defer statement)具有以下特点:

  1. 延迟执行:延迟语句会在包含它的函数执行结束前执行,无论函数是正常返回还是发生异常。

  2. 后进先出:如果有多个延迟语句,它们会按照后进先出(LIFO)的顺序执行。也就是说,最后一个延迟语句会最先执行,而第一个延迟语句会最后执行。

通常情况下,延迟语句在以下情况下使用:

  1. 资源释放:延迟语句可以用于在函数返回前释放打开的文件、关闭数据库连接、释放锁等资源,以确保资源的正确释放,避免资源泄漏。

  2. 错误处理:延迟语句可以用于处理函数执行过程中可能发生的错误。通过在函数开始时设置延迟语句,在函数返回前检查错误并进行相应的处理,可以简化错误处理的逻辑。

  3. 日志记录:延迟语句可以用于在函数返回前记录日志或执行其他的调试操作,以便在函数执行过程中收集相关的信息。

延迟语句的使用可以提高代码的可读性和可维护性,同时确保资源的释放和清理操作按照逆序进行。它是Go语言中一种常用的编程技巧,用于处理资源管理和错误处理等场景。

避坑之旅

实际开发中defer的使用并不像前面介绍的这么简单,defer用不好,会陷入泥潭。

下面我从两个角度带大家避坑:

  1. 首先拆解一下延迟语句的执行,注意Go语言的return语句不是原子性的;

  2. 另外重点和大家分享一下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的参数时,主要区别在于对函数参数的传递和作用域的影响:

  1. 匿名函数作为defer的参数:匿名函数可以直接在defer语句中定义,可以访问外部函数的变量,并且在执行时会使用当前的变量值。这种方式可以方便地在defer语句中使用外部变量,但需要注意变量的值在执行时可能已经发生了改变。

  2. 非匿名函数作为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陷阱:拆解延迟语句,掌握正确使用方法的更多相关文章

  1. oracle 数据库中,应用程序里的连接探測语句的正确使用

    oracle 数据库中,应用程序里的连接探測语句的正确使用 本文为原创文章.转载请注明出处:http://blog.csdn.net/msdnchina/article/details/3851376 ...

  2. 判断下列语句是否正确,如果有错误,请指出错误所在?interface A{

    判断下列语句是否正确,如果有错误,请指出错误所在? interface A{ int add(final A a); } class B implements A{ long add(final A ...

  3. 用XPath定位Web页面元素时,如何快速验证XPath语句是否正确?

    在使用Selenium做Web UI自动化测试的过程中,XPath是一种定位页面元素的常用方式.然而,面对某些元素的XPath路径过于复杂,我们想快速验证拼凑的Xpath语句是否正确时,该怎么办呢?这 ...

  4. jquery源码解析:jQuery延迟对象Deferred(工具方法)详解2

    请接着上一课继续看. $.Deferred()方法中,有两个对象,一个是deferred对象,一个是promise对象. promise对象有以下几个方法:state,always,then,prom ...

  5. CentOS正确关机方法(转)

    CentOS正确关机方法 1关机前准备 1.1观察系统使用状态 ·         谁在线:who ·         联网状态:netstat -a ·         后台执行的程序:ps -au ...

  6. thinkphp3.2 cli模式的正确使用方法

    最近要使用thinkphp3.2版本的cli模式,手动执的话没有问题,比如php /www/index.php home/article/get 这样没有问题,但是一般用cli模式都是定时任务比较多, ...

  7. Linux重启inotify配置max_user_watches无效被恢复默认值8192的正确修改方法

    Linux下Rsync+inotify-tools实现数据实时同步中有一个重要的配置就是设置Inotify的max_user_watches值,如果不设置,当遇到大量文件的时候就会出现出错的情况. 一 ...

  8. MyEclipse10的正确破解方法

    无法转载,故给出原文链接,以供需要者. MyEclipse10的正确破解方法

  9. [转]MySQL忘记密码的正确解决方法

    http://database.51cto.com/art/201005/201986.htm 以下的文章主要介绍的是MySQL忘记密码的正确解决方法,在实际操作中如果你忘记MySQL密码是一件很头痛 ...

  10. Hibernate的hql语句save,update方法不执行

    Hibernate的hql语句save,update方法不执行 可能出现的原因问题: 未进行事务管理 需要进行xml事务配置或者注解方式的事务配置

随机推荐

  1. Git:多人写作时,如何保证代码一致性

    解决方案 git add . git commit -m "message" git pull origin develop # 拉取并合并dev分支上的代码 git push

  2. rpm安装21c单实例数据库

    linux 7.6 使用rpm安装21c单实例数据库 一.基础环境配置 1.1 关闭防火墙 systemctl stop firewalld systemctl disable firewalld s ...

  3. Builder 生成器模式简介与 C# 示例【创建型2】【设计模式来了_2】

    〇.简介 1.什么是生成器模式? 一句话解释:   在构造一个复杂的对象(参数多且有可空类型)时,通过一个统一的构造链路,可选择的配置所需属性值,灵活实现可复用的构造过程. 生成器模式的重心,在于分离 ...

  4. vue结合cesium,配置,插件vue-cli-plugin-cesium

    https://www.npmjs.com/package/vue-cli-plugin-cesium

  5. noip2022保龄记

    第一次参加noip,写第一篇游记纪念一下 天还挺热,今天就穿了个秋衣加卫衣,本来还打算穿袄子来着,但是爸妈没让 到了八中才发现好像没带水,然后我的老父亲跑到不知道哪里去给买了一瓶(八中门口看不到有小卖 ...

  6. json虽然简单,但这些细节你未必知道

    基本介绍 JSON的全称是JavaScript Object Notation,它并不是编程语言,而是一种可以在服务器和客户端之间传输的数据格式,本来是JavaScript的子集,但现在已独立存在于各 ...

  7. 3.0 Python 迭代器与生成器

    当我们需要处理一个大量的数据集合时,一次性将其全部读入内存并处理可能会导致内存溢出.此时,我们可以采用迭代器Iterator和生成器Generator的方法,逐个地处理数据,从而避免内存溢出的问题. ...

  8. fastapi启动后访问docs不显示页面的问题

    笔者之前正常使用fastapi的docs接口进行各种接口调试,使用很正常,之前安装也都是正常安装流程,没有做任何修改,可以突然有一天不知道为啥,docs接口打开是空白的,接口也没有报错,就是空白,摸索 ...

  9. 关于前后端交互,取header的尴尬

    背景: 最近在写一个接口的时候,需求是这样的,上传excel,匹配项目有多少个字段匹配上了,如果匹配上了在单元格上标注绿色背景,然后返回excel文件和匹配的详细. 首先这个excel文件,后端是不会 ...

  10. textarea自动适应高度

    textarea自动适应高度,兼容IE/Firefox.chrome 代码:<textarea name="textarea" id="textarea" ...