Go Defer语句详解

在实际编程中,我们经常需要清理一些资源,比如打开的文件数据库连接 等。当程序不再使用这些资源时,及时关闭它们非常重要,否则可能会造成:

  • 内存泄漏

  • 文件或连接被长期占用

  • 其他程序无法访问这些资源

在本节中,我们将学习 Go 语言中的一个特殊语句,它可以帮助我们在程序执行过程中自动清理资源 ,让代码更加简洁、安全、不易出错 ,并且让"关闭资源"的代码紧挨着"打开资源"的代码,提高可读性。


defer 语句

在 Go 中,我们使用 defer 语句延迟执行一个函数 ,直到包含它的函数即将返回时才执行。


defer 的基本用法

下面是一个最基础的例子:

go 复制代码
package main

import "fmt"

func main() {
    defer fmt.Println("Printed second! 2")
    fmt.Println("Printed first! 1")
}

输出结果:

go 复制代码
Printed first! 1
Printed second! 2

代码解释

虽然 "Printed second! 2" 在代码中先出现,但它并没有先输出。

这是因为:

任何使用 defer 修饰的语句,都会等到当前函数执行结束时才被调用。


多个 defer 语句

在 Go 程序中,可以有多个 defer 语句。

当存在多个 defer 时,它们会被当作一个栈来管理

来看下面的例子:

go 复制代码
package main

import "fmt"

func main() {
    defer fmt.Println("🐥") // 第 1 个 defer
    defer fmt.Println("🐣") // 第 2 个 defer
    defer fmt.Println("🥚") // 第 3 个 defer
}

输出结果:

go 复制代码
🥚
🐣
🐥

代码解释

  • 🐥最先被 defer 的 ,但最后执行

  • 🥚最后被 defer 的 ,却最先执行

这说明:

defer 的执行顺序是 后进先出(LIFO,Last In First Out)

可以把它理解为一个栈结构

go 复制代码
defer 🐥
defer 🐣
defer 🥚  ← 先执行

多个函数中的 defer

当多个函数中都包含 defer 时,需要注意:

defer 只在它所在的函数结束时才会执行

来看下面的例子:

go 复制代码
package main

import "fmt"

func greeting() {
    defer fmt.Println("Printed after Hello, JB Academy!") // 2
    fmt.Println("Hello, JB Academy!") // 1
}

func main() {
    defer fmt.Println("Printed after the main() function is completed.") // 4

    greeting()

    fmt.Println("Printed after calling the greeting() function.") // 3
}

输出结果:

go 复制代码
Hello, JB Academy!
Printed after Hello, JB Academy!
Printed after calling the greeting() function.
Printed after the main() function is completed.

代码解释

执行顺序如下:

  1. greeting() 内部先打印

    Hello, JB Academy!

  2. greeting() 结束

    → 执行其 defer

  3. 回到 main()

    → 打印普通语句

  4. main() 结束

    → 执行 main() 中的 defer

结论:

每个函数中的 defer,都会在该函数结束时立即执行,与其他函数互不影响。


作用域中的 defer

再看一个关于作用域的例子:

go 复制代码
func scopedDefer() {
    n := 0
    defer func() { fmt.Println("n =", n, "- first deferred print") }()
    {
        defer func() { fmt.Println("n =", n, "- second deferred print") }()
        n++ // n = 1
    }
    n++ // n = 2
}

输出结果:

go 复制代码
n = 2 - second deferred print
n = 2 - first deferred print

代码解释

  • 两个 defer 都在 scopedDefer() 函数中

  • 即使其中一个写在代码块 {} 内,它们也不会提前执行

  • 都要等到 scopedDefer() 函数结束

  • 执行时,变量 n 的值已经变成 2

说明:

defer 的执行时间与 函数结束有关,而不是代码块结束。


使用 defer 关闭文件(最常见用法)

defer 最常见、最重要的用途之一,就是释放资源,例如关闭文件。

go 复制代码
package main

import (
    "fmt"
    "log"
    "os"
)

func main() {
    file, err := os.Create("test.txt")
    if err != nil {
        log.Fatal(err)
    }
    defer file.Close() // 程序结束前自动关闭文件

    if _, err := fmt.Fprintln(file, "Hello World!"); err != nil {
        log.Fatal(err)
    }
}

代码解释

  • os.Create() 创建并打开文件

  • defer file.Close() 保证函数结束前文件一定会被关闭

  • 即使中途 return 或发生错误,也不会忘记关闭文件


为什么要用 defer 关闭资源?

使用 defer 有两个明显优点:

1. 防止忘记关闭资源

如果以后给函数增加新的 return 路径,也不用担心遗漏 Close()

2. 代码更清晰

"打开资源"和"关闭资源"写在一起,可读性更好,而不是把 Close() 放在函数结尾。


总结

在本节中,主要内容包括:

  • defer 会在当前函数返回前执行

  • 多个 defer后进先出(LIFO) 顺序执行

  • defer 的作用域是 函数级别

  • defer 最常见的用途是 关闭文件、释放资源

掌握 defer 是写出安全、优雅 Go 代码的重要一步。

相关推荐
Mr -老鬼9 小时前
Rust与Go:从学习到实战的全方位对比
学习·golang·rust
8***f3959 小时前
Spring容器初始化扩展点:ApplicationContextInitializer
java·后端·spring
r_oo_ki_e_10 小时前
java22--常用类
java·开发语言
linweidong10 小时前
C++ 中避免悬挂引用的企业策略有哪些?
java·jvm·c++
用户937611475816110 小时前
并发编程三大特性
java·后端
阿在在10 小时前
Spring 系列(二):加载 BeanDefinition 的几种方式
java·后端·spring
ling-4510 小时前
Linux-day09 11
linux·运维·服务器
zbguolei10 小时前
Debian提示:“用户名” 不是 sudoers 文件
linux·服务器·debian
小当家.10510 小时前
Maven与Gradle完整对比指南:构建工具深度解析
java·gradle·maven
neoooo10 小时前
🍃Spring Boot 多模块项目中 Parent / BOM / Starter 的正确分工
java·后端·架构