golang中的defer

2025-07-09

基本概念

​defer​​是Go语言中的一个关键字,作用在函数作用域中,用于在​​函数结束之前执行特定代码逻辑​​。


核心特性

1. 多个defer的执行顺序

多个defer之间采用​​栈结构(FILO)​​:先进后出

func main() {
    defer fmt.Println("First")
    defer fmt.Println("Second")
    defer fmt.Println("Third")
}
// 输出: Third -> Second -> First

2. defer与return的执行顺序

  • return是函数的最后一条执行语句(但函数作用域尚未结束)

  • defer在​​函数作用域结束时​​触发

  • 执行顺序:先执行return → 再执行defer

func test() int {
    defer fmt.Println("defer executed")
    return 1  // 先执行return
}
// 函数实际结束前执行defer

3. 函数返回值初始化

​命名返回值​​在函数开始时初始化(零值)

func DeferFunc1(i int) (t int) {
    fmt.Println("t = ", t)  // 输出: t = 0 (初始值)
    return 2
}
// 实际返回值 = 2(覆盖初始值)

4. 命名返回值与defer

defer可以修改命名返回值

func DeferFunc2() (t int) {
    defer func() {
        t = t * 10  // 修改返回值
    }()
    return 1  // 相当于: t = 1 → 执行defer → 返回t
}
// 输出: 10

5. defer与panic

规则:

  1. panic后强制执行已注册的defer

  2. panic之后的defer不再执行

  3. 即使触发panic,panic之前的defer仍会执行

func main() {
    defer fmt.Println("This will print")  // 会执行
    panic("something wrong")
    defer fmt.Println("This won't print") // 不会执行
}

6. defer中嵌套panic

func main() {
    defer func() {
        if err := recover(); err != nil {
            fmt.Println(err)  // 捕获最近的panic
        } else {
            fmt.Println("fatal")
        }
    }()

    defer func() {
        panic("defer panic")  // 覆盖原始panic
    }()

    panic("main panic")  
}
// 输出: defer panic

​执行流程​​:

  1. panic("main panic") 触发

  2. 执行已注册的defer(按FILO顺序)

  3. 第二个defer执行panic("defer panic") 覆盖前一个

  4. 第一个defer中recover捕获到最新panic


7. defer的函数参数包含子函数

​参数在defer语句注册时立即求值​

func main() {
    defer DeferFunc(1, DeferFunc(3, 0))  // 先计算参数
    defer DeferFunc(2, DeferFunc(4, 0))  // 先计算参数
}

func DeferFunc(index int, value int) int {
    fmt.Print(index)
    return index
}
// 输出顺序: 3 → 4 → 2 → 1

​原因​​:

  1. 注册第一个defer时先计算参数 DeferFunc(3,0) → 打印3

  2. 注册第二个defer时先计算参数 DeferFunc(4,0) → 打印4

  3. 函数结束时执行defer:先进后出 → 先执行第二个defer(打印2)→ 再执行第一个defer(打印1


8. 经典案例分析

func DeferFunc() (t int) {
    defer func(i int) {
        fmt.Print(i)  // i=0(注册时传参值)
        fmt.Print(t)  // t=2(函数结束时的值)
    }(t)  // t=0在此刻传入

    t = 1
    return 2  // t被赋值为2 → 然后执行defer
}

// 输出: 0 2

​关键点​​:

  • 参数t在defer注册时​​值拷贝​​(此时t=0)

  • 内部函数访问的是​​当前作用域的t变量​​(执行时t=2)


defer的执行机制

三个关键时机:

  1. ​注册时机​​:函数执行到defer语句时

    • 参数值被确定(值拷贝)

    • 函数被放入调用栈

  2. ​执行时机​​:函数return之后,返回之前

    • 可以访问/修改命名返回值

    • 按照后进先出顺序执行

  3. ​作用域​​:在定义defer的函数作用域内

    • 能访问外层函数的变量

    • 值可能被后续代码修改


实际应用建议

  1. ​资源清理​​:文件关闭、锁释放等

    file, _ := os.Open("file.txt")
    defer file.Close()  // 确保文件关闭
  2. ​避免在循环中使用​​:

    // 错误:所有defer共享相同的变量值
    for i := 0; i < 5; i++ {
        defer fmt.Println(i)  // 全部输出4
    }
    
    // 正确:使用函数参数拷贝值
    for i := 0; i < 5; i++ {
        defer func(i int) { fmt.Println(i) }(i)
    }
  3. ​避免长耗时操作​​:

    // 不宜在defer中执行耗时操作
    defer func() {
        time.Sleep(2 * time.Second)  // 延迟函数退出
    }()

PREV
golang如何实现“面向对象”
NEXT
go语言的逃逸分析