Go中的defer看似很简单,实则一点都不难

Golang 中的 Defer

在Go语言中,defer语句用于将一个函数调用推迟到外围函数返回之后执行。它常用于确保某些操作在函数结束时一定会执行,例如资源释放、文件关闭等。

基本语法

defer语句的基本使用方法如下:

func main() {defer fmt.Println("World")fmt.Println("Hello")
}

输出:

Hello
World

在上面的例子中,fmt.Println("World")defer语句中,所以它会在main函数结束时执行,而不是在定义它的地方立即执行。

多个defer

如果在一个函数中有多个defer语句,它们的执行顺序是后进先出(LIFO)的。也就是说,最后一个defer语句会最先执行。

func main() {defer fmt.Println("First")defer fmt.Println("Second")defer fmt.Println("Third")fmt.Println("Hello")
}

输出:

Hello
Third
Second
First

典型用例

1. 文件操作

在处理文件操作时,可以使用defer确保文件关闭:

package mainimport ("fmt""os"
)func main() {file, err := os.Open("example.txt")if err != nil {fmt.Println(err)return}defer file.Close()// 读取文件内容
}

2. 资源释放

defer还可以用于释放其它类型的资源,例如网络连接、数据库连接等:

package mainimport ("database/sql"_ "github.com/go-sql-driver/mysql""log"
)func main() {db, err := sql.Open("mysql", "user:password@/dbname")if err != nil {log.Fatal(err)}defer db.Close()// 数据库操作
}

3. 锁的解锁

在并发编程中,可以使用defer确保锁在临界区操作完成后被释放:

package mainimport ("fmt""sync"
)var mu sync.Mutexfunc main() {mu.Lock()defer mu.Unlock()// 临界区操作fmt.Println("Critical section")
}

defer与匿名函数

有时候,我们需要在defer中执行更复杂的操作,此时可以使用匿名函数:

package mainimport "fmt"func main() {defer func() {fmt.Println("Deferred call")}()fmt.Println("Main function")
}

输出:

Main function
Deferred call

defer与返回值

defer还可以用来修改返回值:

package mainimport "fmt"func test() (result int) {defer func() {result++}()return 0
}func main() {fmt.Println(test())  // 输出1
}

在上面的例子中,defer中的匿名函数会在test函数返回之前执行,并修改result的值。

defer 误区

以下,我将以对比的形式展开对 defer 误区的展示。(你可以先自己猜一下每段代码的执行结果)

误区一

func Defer() {i := 0defer func() {println(i)}()i = 1
}

在这个函数中,defer 语句延迟执行匿名函数,直到 Defer 函数即将返回。

这个匿名函数是一个闭包,它捕获并引用了外部变量 i

因此,当 defer 延迟的匿名函数最终执行时,它打印的是闭包捕获的变量 i 的当前值。

  1. 定义变量 i 并赋值为 0
  2. 定义 defer 延迟执行的匿名函数,它捕获变量 i
  3. 将变量 i 修改为 1
  4. Defer 函数即将返回时,执行 defer 延迟的匿名函数,打印捕获的变量 i 的当前值 1
    因此,Defer 函数的输出是 1
func DeferV1() {i := 0// 立即调用 defer 延迟的匿名函数,并将当前变量 i 的值传递给它的参数 i。defer func(i int) {println(i)}(i)i = 1
}

在这个函数中,defer 语句延迟执行的匿名函数有一个参数 i,并且在调用时将当前的 i 值作为参数传递给匿名函数。

这意味着在 defer 声明时,匿名函数参数 i 的值已经确定,并且与外部变量 i 无关。

  1. 定义变量 i 并赋值为 0
  2. 定义 defer 延迟执行的匿名函数,并立即将当前变量 i(值为 0)传递给它的参数 i
  3. 将外部变量 i 修改为 1
  4. DeferV1 函数即将返回时,执行 defer 延迟的匿名函数,打印参数 i 的值 0

因此,DeferV1 函数的输出是 0

误区二

func DeferReturn() int {a := 0defer func() {a = 1}()return a
}

在这个函数中,变量 a 是一个局部变量。

return a 语句执行时,defer 语句的执行会在 return 语句之后立即发生,但在实际返回值被传递给调用者之前。

  1. 定义变量 a 并赋值为 0
  2. 设置 defer 延迟执行的匿名函数,将变量 a 修改为 1
  3. 执行 return a,此时返回值为 0
  4. defer 延迟的匿名函数执行,将变量 a 修改为 1,但此时返回值已经确定为 0

因此,DeferReturn 函数的返回值是 0

func DeferReturnV1() (a int) {// 全局可见 命名返回值 aa = 0defer func() {a = 1}()return
}

在这个函数中,变量 a 是一个命名返回值。

在 Go 语言中,当一个函数声明了命名返回值时,该返回值变量会在函数开始时被隐式声明,并且在整个函数体中都是可见的。

因此,当 return 语句执行时,返回的值是命名返回值变量 a 的当前值。

  1. 定义命名返回值变量 a 并隐式初始化为 0
  2. 设置 defer 延迟执行的匿名函数,将变量 a 修改为 1
  3. 执行 return 语句,返回命名返回值变量 a
  4. 在实际返回值被传递给调用者之前,执行 defer 延迟的匿名函数,将变量 a 修改为 1

因此,DeferReturnV1 函数的返回值是 1

误区三

type MyStruct struct {name string
}func DeferReturnV2() *MyStruct {a := &MyStruct{name: "ypb",}defer func() {a.name = "zmz"}()return a
}

关键点

  1. 指针修改

a 是一个指向 MyStruct 实例的指针。

defer 延迟的匿名函数中,修改的是 a 指向的对象的 name 字段,而不是指针本身。

这意味着,即使 return a 语句执行时,defer 语句会在实际返回之前执行,修改指针所指向的对象的内容。

  1. defer 的执行顺序

defer 语句会在包含它的函数返回之前执行。

具体来说,defer 延迟的匿名函数在 return 语句设置返回值之后,实际返回之前执行。

因此,匿名函数在返回之前对指针所指向的对象的修改是有效的。

本例子执行顺序:

  1. 创建 MyStruct 实例并赋值给 a,此时 a.name = "ypb"
  2. 设置 defer 延迟执行的匿名函数,准备在函数返回之前执行。
  3. 执行 return a,此时准备返回 a 指向的对象。
  4. 在返回之前,执行 defer 延迟的匿名函数,将 a.name 修改为 "zmz"
  5. 返回 a,此时 a 指向的 MyStruct 实例的 name 字段已经被修改为 "zmz"

defer 自测

只需要自己猜测一下代码的输出结果即可。

很多人误以为在循环中使用 defer 会在每次迭代时执行推迟的操作。实际上,defer 是在函数返回时才执行的,因此在循环中多次使用 defer 会在函数结束时按照后进先出顺序依次执行所有的 defer 语句。

测试一:

// DeferClosureLoop1 函数的输出结果为 十个 10
func DeferClosureLoop1() {for i := 0; i < 10; i++ {i := idefer func() {println(i)}()}
}

测试二:

// DeferClosureLoop2 函数的输出结果为 9 ~ 0
func DeferClosureLoop2() {for i := 0; i < 10; i++ {defer func(val int) {println(val)}(i)}
}

测试三:

// DeferClosureLoop3 与 DeferClosureLoop2 函数的输出结果相同
func DeferClosureLoop3() {for i := 0; i < 10; i++ {j := idefer func() {println(j)}()}
}

总结

defer在 Go 语言中用于推迟函数调用,直到外围函数返回。

常见用法包括确保资源释放(如文件关闭、解锁)、在多层嵌套函数中统一处理异常等。

defer语句按后进先出顺序执行,支持匿名函数并可修改命名返回值。

需注意变量捕获、循环中使用defer导致堆积等误区。正确使用defer有助于代码清晰与资源管理。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/45531.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

距离变换 Distance Transformation

以下为该学习地址的学习笔记&#xff1a;Distance transformation in image - Python OpenCV - GeeksforGeeks 其他学习资料&#xff1a;Morphology - Distance Transform 简介 距离变换是一种用于计算图像中每个像素与最近的非零像素之间距离的技术。它通常用于图像分割和物体…

51单片机5(GPIO简介)

一、序言&#xff1a;不论学习什么单片机&#xff0c;最简单的外设莫过于I口的高低电平的操作&#xff0c;接下来&#xff0c;我们将给大家介绍一下如何在创建好的工程模板上面&#xff0c;通过控制51单片机的GPIO来使我们的开发板上的LED来点亮。 二、51单片机GPIO介绍&#…

第三节SHELL脚本中的变量与运算(1.1-1.5)

一,脚本中的变量 1,1什么是变量 在编写程序是,通常会遇到被操作对象不固定的情况我们需要用一串固定的字符来的表示不固定的值,这就是变量存在的根本意义变量的实现原理就是内存存储单元的一个符合名称 1,2 变量的命名规则 变量的名称中只能包含数字,大小写字母以及下划线 …

PySide在Qt Designer中使用QTableView 显示表格数据

在 PySide6 中&#xff0c;可以使用 Qt Model View 架构中的 QTableView 部件来显示和编辑表格数据。 1、创建ui文件 在Qt Designer中新建QMainWindow&#xff0c;命名为csvShow.ui。QMainWindow上有两个部件&#xff1a;tableview和btn_exit。 2、使用pyuic工具将ui文件转换为…

Kafka(四) Consumer消费者

一&#xff0c;基础知识 1&#xff0c;消费者与消费组 每个消费者都有对应的消费组&#xff0c;不同消费组之间互不影响。 Partition的消息只能被一个消费组中的一个消费者所消费&#xff0c; 但Partition也可能被再平衡分配给新的消费者。 一个Topic的不同Partition会根据分配…

MySQL集群、Redis集群、RabbitMQ集群

一、MySQL集群 1、集群原理 MySQL-MMM 是 Master-Master Replication Manager for MySQL&#xff08;mysql 主主复制管理器&#xff09;的简称。脚本&#xff09;。MMM 基于 MySQL Replication 做的扩展架构&#xff0c;主要用来监控 mysql 主主复制并做失败转移。其原理是将真…

环境变量在Gradle中的妙用:构建自动化的秘诀

环境变量在Gradle中的妙用&#xff1a;构建自动化的秘诀 在构建自动化的过程中&#xff0c;环境变量扮演着至关重要的角色。它们允许开发者根据不同的运行环境&#xff08;如开发、测试和生产环境&#xff09;来调整配置&#xff0c;而无需修改代码。Gradle&#xff0c;作为一…

基于Faster R-CNN的安全帽目标检测

基于Faster R-CNN的安全帽目标检测项目通常旨在解决工作场所&#xff0c;特别是建筑工地的安全监管问题。这类项目使用计算机视觉技术&#xff0c;特别是深度学习中的Faster R-CNN算法&#xff0c;来自动检测工人是否正确佩戴了安全帽&#xff0c;从而确保遵守安全规定并减少事…

实验一:图像信号的数字化

目录 一、实验目的 二、实验原理 三、实验内容 四、源程序及结果 源程序&#xff08;python&#xff09;&#xff1a; 结果&#xff1a; 五、结果分析 一、实验目的 通过本实验了解图像的数字化过程&#xff0c;了解数字图像的数据矩阵表示法。掌握取样&#xff08;象素个…

用Python爬虫能实现什么?得到什么?

Python爬虫是一种强大的工具&#xff0c;可以用来自动化地从互联网上抓取数据和信息。使用Python实现爬虫可以达成多种目的&#xff0c;包括但不限于以下几个方面&#xff1a; 数据收集&#xff1a; 网页内容抓取&#xff1a;可以抓取网页上的文本、图片、视频等内容。搜索引擎…

Linux 网络配置与连接

一、网络配置 1.1 ifconfig 网卡配置查询 ifconfig #查看所有启动的网络接口信息 ifconfig 指定的网卡 #查看指定网络接口信息 1.2 修改网络配置文件 vim /etc/sysconfig/network-scripts/ifcfg-ens33 #ens33网络配置文…

【电源拓扑】反激拓扑

目录 工作模式 固定频率 CCM连续电流模式 DCM不连续电流模式 可变频率 CRM电流临界模式 反激电源CRM工作模式为什么要跳频 反激电源应用场景 为什么反激电源功率做不大 电感电流爬升 反激变压器的限制条件 精通反激电源设计的关键-反激电源变压器设计 反激电源变压…

MySQL 事务与锁

事务ACID特性 原子性&#xff1a;事务要么同时成功&#xff0c;要么同时失败&#xff0c;事务的原子性通过undo log日志保证 一致性&#xff1a;业务代码要抛出报错&#xff0c;让数据库回滚 隔离性&#xff1a;事务并发执行时&#xff0c;他们内部操作不能互相干扰 持久性&…

Python 读取esxi上所有主机的设备信息

&#xff08;主要是为了统计所有虚拟机的设备名称和所属主机&#xff09; 代码&#xff1a; from pyVim import connect from pyVmomi import vim import ssldef get_vm_devices(vm):devices []try:if vm.config is not None and hasattr(vm.config, hardware) and hasattr(v…

SpringBoot解决Apache Tomcat输入验证错误漏洞

Apache Tomcat是美国阿帕奇&#xff08;Apache&#xff09;基金会的一款轻量级Web应用服务器。该程序实现了对Servlet和JavaServer Page&#xff08;JSP&#xff09;的支持。 Apache Tomcat存在输入验证错误漏洞&#xff0c;该漏洞源于HTTP/2请求的输入验证不正确&#xff0c;会…

postgresql简单导出数据与手动本地恢复(小型数据库)

问题 需要每天手动备份postgresql。 步骤 导出数据 /opt/homebrew/opt/postgresql16/bin/pg_dump --file/Users/zhangyalin/backup_sql/<IP地址>_pg-2024_07_15_17_30_15-dump.sql --dbname<数据库名> --username<用户名> --host<IP地址> --port54…

Day53:图论 岛屿数量 岛屿的最大面积

99. 岛屿数量 时间限制&#xff1a;1.000S 空间限制&#xff1a;256MB 题目描述 给定一个由 1&#xff08;陆地&#xff09;和 0&#xff08;水&#xff09;组成的矩阵&#xff0c;你需要计算岛屿的数量。岛屿由水平方向或垂直方向上相邻的陆地连接而成&#xff0c;并且四周…

低空经济持续发热,无人机培训考证就业市场及前景剖析

随着科技的不断进步和社会需求的日益增长&#xff0c;低空经济已成为全球及我国经济增长的新引擎。作为低空经济的重要组成部分&#xff0c;无人机技术因其广泛的应用领域和显著的经济效益&#xff0c;受到了社会各界的广泛关注。为满足市场对无人机人才的需求&#xff0c;无人…

深入剖析 Android 开源库 EventBus 的源码详解

文章目录 前言一、EventBus 简介EventBus 三要素EventBus 线程模型 二、EventBus 使用1.添加依赖2.EventBus 基本使用2.1 定义事件类2.2 注册 EventBus2.3 EventBus 发起通知 三、EventBus 源码详解1.Subscribe 注解2.注册事件订阅方法2.1 EventBus 实例2.2 EventBus 注册2.2.1…

梦想CAD在线预览编辑功能

1.最近有个需求&#xff0c;在web系统里进行在线进行CAD预览和编辑&#xff0c;这里用的是梦想CAD实现此功能&#xff0c;梦想CAD官网文档 2.CAD预览&#xff0c;需要需要对CAD文件格式进行转化&#xff0c;将dwg文件格式转化为mxweb格式&#xff0c;再进行调用梦想CAD里的打开…