defer是Go语言中用于延迟执行函数的关键字,确保函数在返回前执行,常用于资源释放。它遵循后进先出(LIFO)顺序执行多个defer函数。参数在defer语句执行时立即求值,可能导致循环中闭包捕获变量的陷阱,需通过局部变量避免。

Golang的
defer
panic
defer
defer
在我看来,
defer
它的工作原理其实不难理解。当你写下
defer someFunction()
someFunction
someFunction()
defer
然后,当包含这个
defer
return
panic
defer
立即学习“go语言免费学习笔记(深入)”;
举个最简单的例子:
package main
import "fmt"
func exampleDefer() {
fmt.Println("函数开始执行")
defer fmt.Println("这是第一个 defer")
defer fmt.Println("这是第二个 defer") // 这个会先执行
fmt.Println("函数主体逻辑")
}
func main() {
exampleDefer()
}运行这段代码,你会看到输出是:
函数开始执行 函数主体逻辑 这是第二个 defer 这是第一个 defer
这很直观地展示了LIFO的执行顺序。在我看来,这种机制让代码变得更加整洁,也减少了忘记清理资源的风险。
defer
这是
defer
defer
file.Close()
有了
defer
defer
defer
panic
比如,处理文件:
package main
import (
"fmt"
"os"
)
func readFile(filename string) error {
f, err := os.Open(filename)
if err != nil {
return fmt.Errorf("无法打开文件: %w", err)
}
// 在函数返回前关闭文件,无论发生什么
defer func() {
if closeErr := f.Close(); closeErr != nil {
fmt.Printf("关闭文件时发生错误: %v\n", closeErr)
} else {
fmt.Println("文件已成功关闭。")
}
}() // 注意这里是匿名函数,可以处理关闭时的错误
// 模拟读取文件内容
// 如果这里发生 panic,defer 依然会执行
// if filename == "panic.txt" {
// panic("模拟一个读取错误")
// }
buffer := make([]byte, 1024)
n, err := f.Read(buffer)
if err != nil {
return fmt.Errorf("读取文件失败: %w", err)
}
fmt.Printf("读取了 %d 字节: %s\n", n, string(buffer[:n]))
return nil
}
func main() {
// 创建一个测试文件
os.WriteFile("test.txt", []byte("Hello, Go defer!"), 0644)
defer os.Remove("test.txt") // 确保测试文件最后被清理
fmt.Println("--- 正常情况 ---")
err := readFile("test.txt")
if err != nil {
fmt.Println("错误:", err)
}
fmt.Println("\n--- 文件不存在情况 ---")
err = readFile("nonexistent.txt")
if err != nil {
fmt.Println("错误:", err)
}
// 假设我们想模拟一个panic,看看defer是否依然有效
// fmt.Println("\n--- 模拟 panic 情况 ---")
// os.WriteFile("panic.txt", []byte("Will panic"), 0644)
// defer os.Remove("panic.txt")
// func() {
// defer func() {
// if r := recover(); r != nil {
// fmt.Println("Recovered from panic:", r)
// }
// }()
// readFile("panic.txt")
// }()
}在这个
readFile
os.Open
f.Read
panic
defer f.Close()
defer
前面已经提到了,多个
defer
defer
defer
我们可以用一个更复杂的例子来验证:
package main
import "fmt"
func demonstrateLIFO() {
fmt.Println("进入 demonstrateLIFO 函数")
for i := 0; i < 3; i++ {
defer fmt.Printf("defer %d 执行\n", i)
}
fmt.Println("离开 demonstrateLIFO 函数主体")
}
func main() {
demonstrateLIFO()
}输出会是:
进入 demonstrateLIFO 函数 离开 demonstrateLIFO 函数主体 defer 2 执行 defer 1 执行 defer 0 执行
这完美展示了LIFO的特性。
至于为什么这样设计,我个人认为这是非常符合直觉和实际需求的。在很多场景下,资源的获取和释放是嵌套的。比如:
LIFO的
defer
defer closeB()
defer closeA()
closeB()
closeA()
defer
defer
defer
这是
defer
defer
defer
defer
这在我看来,是一个典型的“双刃剑”特性。它在某些情况下非常方便,比如你希望在函数返回时打印一个变量的“旧值”。但在另一些情况下,它可能导致一些难以察觉的bug。
考虑下面这个例子:
package main
import "fmt"
import "time"
func showParamEvaluation() {
i := 0
defer fmt.Println("defer 1: i =", i) // i 在这里被求值为 0
i++
defer fmt.Println("defer 2: i =", i) // i 在这里被求值为 1
i++
fmt.Println("函数内 i =", i) // i 在这里是 2
}
func main() {
showParamEvaluation()
fmt.Println("\n--- 循环中的陷阱 ---")
trapInLoop()
}
func trapInLoop() {
for i := 0; i < 3; i++ {
// 陷阱:这里 defer 捕获的是 i 的值,而不是 i 的引用。
// 但因为 fmt.Println 是一个函数调用,它的参数在 defer 时就被求值了。
// 所以这里会打印 0, 1, 2
defer fmt.Printf("外部循环变量 i (错误理解): %d\n", i)
}
for i := 0; i < 3; i++ {
// 正确的做法:引入一个局部变量来捕获当前 i 的值
j := i
defer fmt.Printf("局部变量 j (正确捕获): %d\n", j)
}
fmt.Println("循环结束后")
}运行
showParamEvaluation()
函数内 i = 2 defer 2: i = 1 defer 1: i = 0
可以看到,
defer 1
i
defer
0
defer 2
i
defer
1
i
2
更常见的陷阱出现在循环中,尤其是当
defer
trapInLoop
defer fmt.Printf("外部循环变量 i (错误理解): %d\n", i)fmt.Printf
i
defer
然而,如果
defer
package main
import "fmt"
import "time"
func trapInLoopWithClosure() {
fmt.Println("--- 循环中的闭包陷阱 ---")
for i := 0; i < 3; i++ {
// 陷阱:匿名函数捕获的是 i 的引用,而不是 i 的值。
// 当 defer 真正执行时,i 已经变成了最终值 3。
defer func() {
fmt.Printf("闭包捕获的 i (错误理解): %d\n", i)
}()
}
for i := 0; i < 3; i++ {
// 正确的做法:引入一个局部变量来捕获当前 i 的值
j := i // 每次循环都会创建一个新的 j
defer func() {
fmt.Printf("闭包捕获的 j (正确捕获): %d\n", j)
}()
}
fmt.Println("循环结束后")
}
func main() {
trapInLoopWithClosure()
}运行
trapInLoopWithClosure()
--- 循环中的闭包陷阱 --- 循环结束后 闭包捕获的 j (正确捕获): 2 闭包捕获的 j (正确捕获): 1 闭包捕获的 j (正确捕获): 0 闭包捕获的 i (错误理解): 3 闭包捕获的 i (错误理解): 3 闭包捕获的 i (错误理解): 3
这下就清楚了!第一个循环中,匿名函数捕获的是变量
i
i
defer
i
3
defer
3
而第二个循环中,通过引入局部变量
j := i
j
i
defer
j
j
defer
2, 1, 0
在我日常开发中,这个“参数立即求值”和“闭包捕获引用”的差异,是导致
defer
以上就是Golangdefer关键字 延迟执行与顺序的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号