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关闭,确保资源一定会被释放:gof, 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:
gofor _, 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"之后的值,因为闭包持有变量引用 -
如果想在闭包中捕获特定时刻的值,可以用参数传递:
godefer 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 与闭包 | 闭包捕获变量引用,执行时读取最终值;与参数捕获(值)行为不同 |