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 代码的重要一步。

相关推荐
不做无法实现的梦~5 分钟前
Linux 上使用 CLion 开发嵌入式,并用 Codex CLI
linux·运维·服务器
ILYT NCTR16 分钟前
搭建Golang gRPC环境:protoc、protoc-gen-go 和 protoc-gen-go-grpc 工具安装教程
开发语言·后端·golang
小雅痞23 分钟前
[Java][Leetcode simple] 28. 找出字符串中第一个匹配项的下标
java·开发语言·leetcode
likerhood28 分钟前
java中的不可变类(Immutable)
java·开发语言
被摘下的星星1 小时前
以太网技术
服务器·网络
yaoxin5211231 小时前
389. Java IO API - 获取文件名
java·开发语言·python
Wang15302 小时前
Java排序
java
逸风尊者2 小时前
XGBoost模型工程使用
java·后端·算法
苦逼大学生被编程薄纱2 小时前
Ext 文件系统基础:Linux 存储基石入门(下)
linux·运维·服务器
一嘴一个橘子2 小时前
MP 自定义业务方法 (二)
java