Skip to content

1.14 流程控制:defer 延迟语句

defer 关键字用于延迟一个函数的执行,直到包含该 defer 语句的函数执行完毕(返回)时才执行。它主要用于确保资源在任何情况下都能被正确释放和清理。

一、defer 的基础概念

1. 语法与执行时机

语法defer functionCall()
执行时机在外层函数执行结束(return 之前,或发生 panic)时执行。

示例:

go
func main() {
    fmt.Println("开始执行 main 函数")

    defer fmt.Println("这是 defer 语句 1") // 延迟执行
    defer fmt.Println("这是 defer 语句 2") // 延迟执行

    fmt.Println("main 函数即将结束")
    // defer 语句在 main 函数 return 之前执行
}

// 输出顺序:
// 1. 开始执行 main 函数
// 2. main 函数即将结束
// 3. 这是 defer 语句 2  <-- 注意顺序是 LIFO
// 4. 这是 defer 语句 1

2. defer 的核心用途

defer 的主要目的是简化清理操作,确保无论函数是正常返回还是发生 panic,清理工作都会被执行。

场景示例操作
文件操作确保文件句柄被关闭:defer file.Close()
数据库连接确保数据库连接被关闭:defer db.Close()
锁操作确保互斥锁被释放:defer mu.Unlock()

二、defer 的执行机制

Go 语言将 defer 语句放入一个特殊的栈 栈(Stack) (LIFO)中。当外层函数返回时,这些延迟函数会按照后进先出 (LIFO) 的顺序依次执行。

规则: 声明的 defer 语句会执行。

go
func printNumbers() {
    for i := 0; i < 3; i++ {
        defer fmt.Println(i)
    }
}

func main() {
    printNumbers()
    // 输出顺序:2, 1, 0 (LIFO)
}

三、defer 语句的参数求值时机

这是 defer 最容易让人困惑的地方:defer 语句的参数是在 defer 语句被定义时立即求值的,而不是在它被执行时。

go
func main() {
    i := 1
    
    // defer 语句被定义时,i 的值是 1,所以参数 i 立即被求值并固定为 1。
    defer fmt.Println("defer 结果:", i) 

    i++ // i 的值现在是 2,但这不影响 defer 语句的参数 i。
    
    fmt.Println("main 结束前 i:", i) 
    
    // 输出顺序:
    // 1. main 结束前 i: 2
    // 2. defer 结果: 1  <-- 打印的是 i 声明时的值 1
}

💡 如果您想让 defer 使用变量的最终值:

传递一个指针或将 defer 语句放在一个闭包中。

go
i := 1
// 使用闭包,闭包捕获的是变量本身,而不是值
defer func() {
    fmt.Println("闭包结果:", i) // 此时 i 的值是 2
}()
i++
// 输出: 闭包结果: 2

四、deferpanic/recover

panic 时,已注册的 defer 仍会执行,故清理逻辑照常放在 defer 即可;要用 recover() 接住 panic,也必须在 defer 里调用。

规则、使用场景与 Goroutine 边界等见 《1.16 异常机制:panic 和 recover》

五、总结与建议

特性描述最佳实践
LIFO 顺序延迟调用是后进先出栈式执行。确保 defer 语句成对出现(如 Lock 对应 Unlock)。
参数即时求值defer 语句的参数在定义时立即求值并固定。如果需要变量的最终值,请传递指针或使用闭包
资源清理保证资源(文件、锁、连接)在任何退出路径上都能被关闭。只要获取了资源,就立即定义 defer 来释放它。
panic/recoverdefer 的配合方式及使用场景。《1.16 异常机制:panic 和 recover》