defer在Go中用于延迟执行函数,遵循后进先出原则,参数在defer语句执行时即求值,常用于资源释放;常见陷阱包括参数求值时机、循环中资源未及时释放及与命名返回值交互问题。

defer
defer
defer
defer
defer
defer
想象一下,你打开了一个文件,或者获取了一个锁,又或者建立了一个数据库连接。这些资源用完后都需要被关闭或释放。如果手动在代码的各个出口处写关闭逻辑,很容易遗漏,尤其是在有多个
return
panic
defer
一个经典的例子是文件操作:
立即学习“go语言免费学习笔记(深入)”;
func readFile(filename string) ([]byte, error) {
f, err := os.Open(filename)
if err != nil {
return nil, err
}
// 关键在这里:无论函数如何退出(正常返回或发生panic),f.Close()都会被调用
defer f.Close()
data, err := io.ReadAll(f)
if err != nil {
return nil, err
}
return data, nil
}这里,
defer f.Close()
f
readFile
defer
defer
defer
defer
defer
更重要的是,
defer
defer
func deferOrderExample() {
i := 0
defer fmt.Println("First defer:", i) // i在这里被求值为0
i++
defer fmt.Println("Second defer:", i) // i在这里被求值为1
fmt.Println("Inside function:", i) // i此时为1
}
// 预期输出:
// Inside function: 1
// Second defer: 1
// First defer: 0在这个例子里,当
defer fmt.Println("First defer:", i)i
0
fmt.Println
"First defer:", 0
i
1
defer fmt.Println("Second defer:", i)i
1
"Second defer:", 1
defer
defer
Second defer: 1
defer
First defer: 0
理解参数的立即求值对于避免一些微妙的bug至关重要。
尽管
defer
参数立即求值导致的混淆: 这是最常见的陷阱,就像上面例子展示的。如果你期望
defer
defer
defer
func deferLoopTrap() {
for i := 0; i < 3; i++ {
// 陷阱:这里的i在defer时就被求值了,每次都是当前循环的i
// 结果会是 0, 1, 2
defer fmt.Println("Bad defer (value at defer time):", i)
}
for i := 0; i < 3; i++ {
// 正确做法:使用闭包,让defer函数在执行时才访问i的最终值
// 或者更常见的,将i作为参数传递给闭包
j := i // 每次循环创建一个新的j
defer func() {
fmt.Println("Good defer (value captured by closure):", j)
}()
}
}
// 预期输出(顺序可能不同,因为defer是LIFO,但值是关键):
// Bad defer (value at defer time): 2
// Bad defer (value at defer time): 1
// Bad defer (value at defer time): 0
// Good defer (value captured by closure): 2
// Good defer (value captured by closure): 1
// Good defer (value captured by closure): 0在循环中滥用defer
defer
defer Close()
func processFiles(filenames []string) error {
for _, filename := range filenames {
f, err := os.Open(filename)
if err != nil {
// 如果这里返回,之前打开的文件都不会被defer关闭
return err
}
// 陷阱:文件直到processFiles函数返回才关闭
// 如果filenames很多,可能导致文件句柄耗尽
defer f.Close()
// 处理文件内容...
fmt.Println("Processing:", filename)
// 实际项目中,这里如果文件很多,f.Close()应该在每次循环结束时调用
// 或者将循环体封装成一个函数
}
return nil
}
// 更好的处理方式
func processSingleFile(filename string) error {
f, err := os.Open(filename)
if err != nil {
return err
}
defer f.Close() // defer在这里是安全的,因为它只作用于单个文件
// 处理文件内容...
fmt.Println("Processing (single):", filename)
return nil
}
func processFilesBetter(filenames []string) {
for _, filename := range filenames {
if err := processSingleFile(filename); err != nil {
fmt.Printf("Error processing %s: %v\n", filename, err)
}
}
}与命名返回值结合使用:
defer
panic
recover
func calculateSomething() (result int, err error) {
defer func() {
// 这个匿名函数会在calculateSomething返回前执行
// 它可以访问并修改result和err
if r := recover(); r != nil {
err = fmt.Errorf("recovered from panic: %v", r)
result = 0 // 清空结果,因为发生了panic
}
fmt.Println("Defer executed. Final result:", result, "Error:", err)
}()
// 假设这里可能发生panic
// panic("something went wrong!")
result = 100
return result, nil
}理解这些陷阱并学会规避,能让你更自信、更有效地使用
defer
以上就是Golang中的defer关键字怎么用 剖析延迟调用的执行顺序与陷阱的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号