3.6 Go defer 语句学习笔记

3.6 Go defer 语句学习笔记

1. defer 基本用法 --- 延迟执行到函数返回前

defer 将函数调用推迟到当前函数返回之前执行,常用于资源清理:

go 复制代码
package main

import "fmt"

// 清理函数
func cleanup() {
    fmt.Println("Cleaning up resources")
}

// 打开文件:先 defer 清理,再执行正常逻辑
func openFile(filename string) {
    fmt.Println("Opening file:", filename)
    defer cleanup()
    fmt.Println("File operations complete")
}

func main() {
    fmt.Println("=== openFile example ===")
    openFile("data.txt")
}

执行结果:

复制代码
=== openFile example ===
Opening file: data.txt
File operations complete
Cleaning up resources

要点

  • defer cleanup() 延迟调用 cleanup(),在 openFile 函数返回前才执行

  • 执行顺序:先 Opening → 再 File operations complete → 最后 Cleaning up

  • 常见用法:打开资源后立即 defer 关闭,确保资源一定会被释放:

    go 复制代码
    f, err := os.Open("file.txt")
    defer f.Close()  // 不管后续代码是否出错,文件都会被关闭
  • 好处:资源的获取和释放在代码上紧挨着,逻辑清晰,不会遗漏清理


2. defer 的执行顺序 --- LIFO(后进先出)

多个 defer 按 LIFO(后进先出/栈)顺序执行,最后 defer 的最先执行:

go 复制代码
package main

import "fmt"

func deferOrder() {
    fmt.Println("Start")
    defer fmt.Println("First defer")
    defer fmt.Println("Second defer")
    defer fmt.Println("Third defer")
    fmt.Println("End")
}

func main() {
    fmt.Println("=== deferOrder example ===")
    deferOrder()
}

执行结果:

复制代码
=== deferOrder example ===
Start
End
Third defer
Second defer
First defer

要点

  • 正常代码先执行:Start → End
  • defer 按 LIFO 顺序执行:Third → Second → First(最后 defer 的最先执行)
  • 可以理解为 defer 把调用压入栈,函数返回时从栈顶依次弹出执行
  • 实际场景:如果有多个资源需要清理,最先获取的资源最后释放(符合栈的打开-关闭顺序)

3. defer 的参数捕获 --- 在 defer 时求值,不是执行时

defer 的参数在 defer 语句执行时 就确定,不是在 defer 函数实际调用时确定:

go 复制代码
package main

import "fmt"

func deferWithArgs() {
    x := 10
    defer fmt.Println("Deferred x:", x) // x 在此时被捕获为 10
    x = 20
    fmt.Println("Current x:", x)
    // defer 实际执行时会打印 10,不是 20
}

func main() {
    fmt.Println("=== deferWithArgs example ===")
    deferWithArgs()
}

执行结果:

复制代码
=== deferWithArgs example ===
Current x: 20
Deferred x: 10

要点

  • defer fmt.Println("Deferred x:", x) 执行时,x=10,参数值 10 被立即捕获
  • 后续 x = 20 修改了 x,但 defer 的参数已经锁定为 10
  • 所以 deferred 输出是 10,不是 20
  • 这与闭包不同:闭包捕获的是变量的引用,defer 的函数参数捕获的是值

4. defer 在循环中 --- 所有 defer 在函数返回时集中执行

循环中的 defer 不会在每次迭代结束时执行,而是在函数返回时统一按 LIFO 顺序执行:

go 复制代码
package main

import "fmt"

func deferInLoop() {
    for i := 1; i <= 3; i++ {
        defer fmt.Println("Loop defer:", i)
        fmt.Println("Loop iteration:", i)
    }
    // 函数返回时,defer 按 LIFO 执行:3, 2, 1
}

func main() {
    fmt.Println("=== deferInLoop example ===")
    deferInLoop()
}

执行结果:

复制代码
=== deferInLoop example ===
Loop iteration: 1
Loop iteration: 2
Loop iteration: 3
Loop defer: 3
Loop defer: 2
Loop defer: 1

要点

  • 循环体内 fmt.Println("Loop iteration:", i) 正常执行:1 → 2 → 3

  • 循环体内 defer 只注册,不执行,等到 deferInLoop 函数返回时才集中执行

  • 集中执行按 LIFO:i=3 → i=2 → i=1

  • 注意:循环中使用 defer 可能导致资源延迟释放。例如循环中打开 100 个文件并 defer close,所有文件直到函数结束才关闭。这种场景应在循环体内直接调用 close,不要 defer:

    go 复制代码
    for _, filename := range files {
        f, _ := os.Open(filename)
        process(f)
        f.Close()  // 每次迭代立即关闭,不要 defer f.Close()
    }

5. defer 与闭包 --- 捕获变量引用而非值

defer 闭包(匿名函数)捕获的是变量的引用,defer 执行时读取变量的最终值:

go 复制代码
package main

import "fmt"

func main() {
    fmt.Println("=== defer with anonymous function ===")
    message := "Hello"
    defer func() {
        fmt.Println("Deferred message:", message) // 闭包捕获引用,读到 "Goodbye"
    }()
    message = "Goodbye"
    fmt.Println("Current message:", message)
}

执行结果:

复制代码
=== defer with anonymous function ===
Current message: Goodbye
Deferred message: Goodbye

要点

  • 这是与知识点 3 最关键的对比:

    • defer 函数参数:在 defer 语句执行时求值,捕获值 → 输出 10(知识点 3)
    • defer 闭包变量:捕获变量的引用,执行时读取最终值 → 输出 "Goodbye"(知识点 5)
  • defer func() { ... }() --- 注意最后的 (),匿名函数必须立即调用(defer 注册的是函数调用,不是函数本身)

  • 闭包中的 message 读取的是 message = "Goodbye" 之后的值,因为闭包持有变量引用

  • 如果想在闭包中捕获特定时刻的值,可以用参数传递:

    go 复制代码
    defer func(msg string) {
        fmt.Println("Deferred message:", msg) // msg 是 "Hello"
    }(message)  // 参数在 defer 时求值,传入 "Hello"

知识点总结

知识点 关键概念
defer 基本用法 延迟执行到函数返回前,常用于资源清理(defer Close())
defer 执行顺序 多个 defer 按 LIFO(后进先出)执行,最后 defer 的最先执行
defer 参数捕获 参数在 defer 语句执行时 求值,锁定当时的值
defer 在循环中 所有 defer 在函数返回时集中执行,循环中慎用 defer 释放资源
defer 与闭包 闭包捕获变量引用,执行时读取最终值;与参数捕获(值)行为不同
相关推荐
存在morning2 小时前
【Python 开发实践 一】Python vs Go vs Java 三门语言对比
java·python·golang
圣殿骑士-Khtangc14 小时前
Go字符串高效拼接性能对比与底层原理分析
服务器·前端·golang
xzlAwin19 小时前
Win10安装Go语言多版本管理器g工具
开发语言·golang
FfHUCisI1 天前
Golang RESTful API 设计原则
开发语言·golang·restful
大可-1 天前
Go Air 热重载安装配置指南
开发语言·后端·golang
Zenova EdgeOS1 天前
工业边缘 SDK 设计实战:从 API 到 Python/Go 多语言工程落地
python·golang·php
名字还没想好☜2 天前
Go 的 unsafe.Pointer 实战:零拷贝 []byte↔string 转换与三条铁律
开发语言·后端·golang·go·unsafe
xzlAwin2 天前
Go语言多版本管理器g工具命令
开发语言·golang
songtaiwu2 天前
Go泛型的应用
开发语言·后端·golang