Golang defer关键字
最后修改于 2025 年 5 月 7 日
本教程解释了如何在Go中使用defer关键字。我们将通过实际的资源清理和执行控制示例来涵盖defer基础知识。
defer语句会将函数的执行推迟到周围函数返回为止。它通常用于清理操作,例如关闭文件或解锁互斥锁。
在Go中,defer可以确保即使函数提前退出,重要的清理代码也会运行。当函数完成时,延迟的调用按后进先出(LIFO)的顺序执行。
基本defer示例
defer最简单的用法是将函数调用推迟到周围函数完成之后。此示例演示了基本的defer行为。
package main
import "fmt"
func main() {
defer fmt.Println("This prints last")
fmt.Println("This prints first")
fmt.Println("This prints second")
}
延迟的调用在函数中所有非延迟语句之后执行。延迟的调用被推送到堆栈上,并按相反的顺序执行。
带有文件操作的Defer
defer通常用于确保资源得到妥善关闭。此示例演示了使用defer进行文件清理。
package main
import (
"fmt"
"os"
)
func main() {
file, err := os.Create("test.txt")
if err != nil {
panic(err)
}
defer file.Close()
fmt.Fprintln(file, "Writing to file")
fmt.Println("File operations complete")
}
file.Close()调用被推迟到main()退出为止。这确保了即使在文件操作过程中发生错误,文件也能被正确关闭。
多个defer语句
当存在多个defer语句时,它们会按相反的顺序执行。此示例演示了defer的LIFO(后进先出)行为。
package main
import "fmt"
func main() {
defer fmt.Println("First defer - runs last")
defer fmt.Println("Second defer - runs second")
defer fmt.Println("Third defer - runs first")
fmt.Println("Main function executing")
}
延迟的调用被推送到堆栈上。当函数退出时,最后一个注册的defer是第一个执行的。这种行为对于正确的资源清理至关重要。
带有函数参数的Defer
延迟函数的参数会立即被评估,而不是在函数执行时被评估。此示例显示了参数评估的时间。
package main
import "fmt"
func main() {
i := 0
defer fmt.Println("Deferred print:", i)
i++
fmt.Println("Regular print:", i)
}
延迟的fmt.Println在defer语句执行时捕获i的值(0),而不是在函数退出时。常规打印显示更新后的值(1)。
循环中的Defer
在循环中使用defer需要谨慎,因为延迟的调用直到函数退出才执行。此示例演示了循环defer的行为。
package main
import "fmt"
func main() {
for i := 0; i < 5; i++ {
defer fmt.Println("Deferred in loop:", i)
fmt.Println("In loop:", i)
}
fmt.Println("Loop completed")
}
循环中所有延迟的调用都会累积,并在循环完成后按相反的顺序执行。每次迭代的defer都会捕获i的当前值。
带有命名返回值的Defer
延迟的函数可以修改命名返回值。此示例显示了defer如何影响函数返回值。
package main
import "fmt"
func main() {
fmt.Println("Result:", calculate())
}
func calculate() (result int) {
defer func() {
result *= 2
}()
result = 10
return
}
延迟的函数在return语句之后,但在函数实际返回之前执行。它将命名返回值的执行结果加倍。
实际示例:数据库清理
这个实际示例演示了在真实场景中使用defer进行数据库连接清理。
package main
import (
"database/sql"
"fmt"
"log"
_ "github.com/lib/pq"
)
func main() {
db, err := sql.Open("postgres", "user=test dbname=test sslmode=disable")
if err != nil {
log.Fatal(err)
}
defer db.Close()
rows, err := db.Query("SELECT id, name FROM users")
if err != nil {
log.Fatal(err)
}
defer rows.Close()
for rows.Next() {
var id int
var name string
if err := rows.Scan(&id, &name); err != nil {
log.Fatal(err)
}
fmt.Printf("ID: %d, Name: %s\n", id, name)
}
if err := rows.Err(); err != nil {
log.Fatal(err)
}
}
该示例显示了使用defer对数据库连接和查询结果进行正确的资源清理。这种模式可确保即使在处理过程中发生错误,资源也能被释放。
来源
本教程通过实际的资源管理和执行控制示例,涵盖了Go中的defer关键字。