Go 编程 | 连载 25 - Go 的 defer 语句

简介: Go 编程 | 连载 25 - Go 的 defer 语句

一、defer 语句

Go 中的 defer 语句又叫做延迟执行语句,也就是说 defer 语句会将其后面根素的语句进行延迟处理。

多个 defer 语句的执行顺序

在 defer 关键字所在的函数即将返回时(返回前),将延迟处理的语句按 defer 的逆序进行执行,既先被 defer 的语句最后执行,最后被 defer 的语句最先执行,在被 defer 修饰时遵循 先进后出 原则.

package main
import "fmt"
func main() {
   fmt.Println("defer 修饰语句的执行顺序为 `先进后出`")
   defer fmt.Println("第一个进入 defer 栈,最会出(执行)")
   defer fmt.Println("第二个进入 defer 栈,倒数第二个出(执行)")
   defer fmt.Println("第三个进入 defer 栈,倒数第三个出(执行)")
   fmt.Println("defer 语句会在所属函数返回前执行:")
}
复制代码

执行上述代码,输出结果如下:

defer 修饰语句的执行顺序为 `先进后出`
defer 语句会在所在函数返回前执行:
第三个进入 defer 栈,倒数第三个出(执行)
第二个进入 defer 栈,倒数第二个出(执行)
第一个进入 defer 栈,最会出(执行)
复制代码

二、defer 语句在函数退出时释放资源

在文件处理和连接数据库时都会遇到 打开关闭 这种成对的操作,Go 中没有 try-catch-finally 语句可以在执行 finally 代码块中执行关闭操作。

Go 中可以通过 defer 语句来代替 finally 实现资源关闭的操作,同时也能解决 try-catch-finally 的大量嵌套。

以文件打开和关闭为例,首先创建一个 info.txt 文件,Go 中打开和关闭文件需要使用到 os 包中的 OpenClose 函数。

package main
import (
   "fmt"
   "log"
   "os"
)
func main() {
   fmt.Println(getFileSize("info.txt")) # 90
}
func getFileSize(path string) (size int) {
   f, err := os.Open(path)
   if err != nil {
      log.Fatal(err)
   }
   // 延迟关闭文件
   defer f.Close()
   fileInfo, err := f.Stat()
   if err != nil {
      log.Fatal(err)
   }
   size = int(fileInfo.Size())
   return
}
复制代码

image.png

需要注意的是 defer 后的表达式必须是函数调用。

三、defer 机制的细节

defer 语句执行时的拷贝机制,既会执行目标函数的副本,当原函数改变后,defer 执行不受影响。

func main() {
   zulu := func() {
      fmt.Println("Go")
   }
   defer zulu()
   zulu = func() {
      fmt.Println("Elixir")
   }
}
复制代码

执行上述代码,输出结果如下:

Go
复制代码

defer 后的函数入参为值类型时,会将参数一同拷贝,修改参数不会影响 defer 后函数的执行结果。

func main() {
   x := 6
   zulu := func(x int) {
      fmt.Println(x)
      fmt.Println("Go")
   }
   defer zulu(x)
   x = 16
}
复制代码

执行上述代码,输出结果如下:

6
Go
复制代码

将 defer 后 zulu 变量中函数的参数改为引用传递时,如果原参数改变,对 defer 后函数的执行结果也会有影响。

func main() {
   x := 6
   xPtr := &x
   zulu := func(x *int) {
      fmt.Println(*x)
      fmt.Println("Go")
   }
   defer zulu(xPtr)
   x = 16
}
复制代码

执行上述代码,输出结果如下:

16
Go
复制代码

与 finally 做对比

func main() {
   fmt.Println(f1()) # 10
}
func f1() int {
   x := 10
   defer func(){
      x++
   }()
   return x
}
复制代码

返回的值并未收到影响。

将函数反射值设置为引用类型

func main() {
   fmt.Println(*f1())
}
func f1() *int {
   x := 10
   xPtr := &x
   defer func(){
      *xPtr++
   }()
   tmp := xPtr
   return tmp
}
复制代码

再次执行,输出结果如下:

11
复制代码

输出结果受到影响。

函数 return 前会重新定义一个变量并赋值,返回的是新定义的变量,如果是值类型赋值就重新拷贝一个,对前面定义的变量没有影响,如果使用引用数据类型,这两个变量执行的是同一个内存地址,数据改变了都会跟着改变。


相关文章
|
设计模式 缓存 算法
Go如何进行高质量编程与性能调优实践
本文介绍了Go语言高质量编程与性能调优的实践方法。高质量编程包括良好的编码习惯(如清晰注释、命名规范)、代码风格与设计(如MVC模式)、简洁明了的代码原则,以及单元测试与代码重构的重要性。性能调优方面,涵盖算法优化、数据结构选择、I/O优化、内存管理、并行与并发处理优化及代码层面的改进。通过这些方法,可有效提升代码质量和系统性能。
280 13
|
分布式计算 Go C++
初探Go语言RPC编程手法
总的来说,Go语言的RPC编程是一种强大的工具,让分布式计算变得简单如同本地计算。如果你还没有试过,不妨挑战一下这个新的编程领域,你可能会发现新的世界。
339 10
|
安全 Go
defer关键字:延迟调用机制-《Go语言实战指南》
Go 语言中的 `defer` 是用于延迟执行函数调用的关键字,广泛应用于资源释放、异常捕获和日志记录等场景。它在函数返回前执行,支持栈式后进先出(LIFO)顺序,参数求值时机为声明时而非执行时。常见用法包括文件关闭、锁解锁及结合 `recover` 处理 panic。尽管高效,频繁使用可能带来性能开销,需谨慎处理。总结而言,`defer` 是构建健壮代码的核心工具之一。
|
数据采集 监控 Java
go语言编程学习
【11月更文挑战第3天】
386 7
|
数据库连接 Go 数据库
Go语言中的错误注入与防御编程。错误注入通过模拟网络故障、数据库错误等,测试系统稳定性
本文探讨了Go语言中的错误注入与防御编程。错误注入通过模拟网络故障、数据库错误等,测试系统稳定性;防御编程则强调在编码时考虑各种错误情况,确保程序健壮性。文章详细介绍了这两种技术在Go语言中的实现方法及其重要性,旨在提升软件质量和可靠性。
342 1
|
Java 大数据 Go
Go语言:高效并发的编程新星
【10月更文挑战第21】Go语言:高效并发的编程新星
568 7
|
Unix Linux Go
go进阶编程:Golang中的文件与文件夹操作指南
本文详细介绍了Golang中文件与文件夹的基本操作,包括读取、写入、创建、删除和遍历等。通过示例代码展示了如何使用`os`和`io/ioutil`包进行文件操作,并强调了错误处理、权限控制和路径问题的重要性。适合初学者和有经验的开发者参考。
385 4
|
Go 数据处理 调度
Go语言中的并发模型:解锁高效并行编程的秘诀
本文将探讨Go语言中独特的并发模型及其在现代软件开发中的应用。通过深入分析 Goroutines 和 Channels,我们将揭示这一模型如何简化并行编程,提升应用性能,并改变开发者处理并发任务的方式。不同于传统多线程编程,Go的并发方法以其简洁性和高效性脱颖而出,为开发者提供了一种全新的编程范式。
|
存储 JSON 安全
go语言编程系列(七)
go语言编程系列(七)
|
存储 安全 编译器
go语言编程系列(六)
go语言编程系列(六)

热门文章

最新文章