ZetCode

Golang defer关键字

最后修改于 2025 年 5 月 7 日

本教程解释了如何在Go中使用defer关键字。我们将通过实际的资源清理和执行控制示例来涵盖defer基础知识。

defer语句会将函数的执行推迟到周围函数返回为止。它通常用于清理操作,例如关闭文件或解锁互斥锁。

在Go中,defer可以确保即使函数提前退出,重要的清理代码也会运行。当函数完成时,延迟的调用按后进先出(LIFO)的顺序执行。

基本defer示例

defer最简单的用法是将函数调用推迟到周围函数完成之后。此示例演示了基本的defer行为。

basic_defer.go
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进行文件清理。

file_defer.go
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(后进先出)行为。

multi_defer.go
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

延迟函数的参数会立即被评估,而不是在函数执行时被评估。此示例显示了参数评估的时间。

defer_args.go
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的行为。

loop_defer.go
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如何影响函数返回值。

defer_return.go
package main

import "fmt"

func main() {
    fmt.Println("Result:", calculate())
}

func calculate() (result int) {
    defer func() {
        result *= 2
    }()
    
    result = 10
    return
}

延迟的函数在return语句之后,但在函数实际返回之前执行。它将命名返回值的执行结果加倍。

实际示例:数据库清理

这个实际示例演示了在真实场景中使用defer进行数据库连接清理。

db_defer.go
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 语言规范

本教程通过实际的资源管理和执行控制示例,涵盖了Go中的defer关键字。

作者

我叫Jan Bodnar,是一名热情的程序员,拥有丰富的编程经验。我从2007年开始撰写编程文章。迄今为止,我已撰写了1400多篇文章和8本电子书。我在教授编程方面拥有十多年的经验。

列出所有 Golang 教程