defer 基础知识回顾

  • defer 作用defer是一种延迟执行机制,它确保在函数即将退出前执行特定的操作,如释放资源(如关闭数据库连接或文件流)。

  • 执行顺序defer遵循栈的后进先出(LIFO)原则,即先定义的defer语句会在函数退出时最后执行,后定义的defer语句则最先执行。

  • 与 return 的关系:尽管defer在函数返回之前执行,但return本身分为两步:首先计算返回值,然后执行defer语句,最后完成函数的返回。

defer 相关知识点总结

1. defer 执行顺序

defer的执行顺序严格按照它们在代码中定义的逆序进行。

2. defer 与 return 的先后顺序

通过实例代码演示,我们发现尽管defer总是在return之前执行,但在return语句开始执行前,会先完成返回值的赋值操作。

3. 函数返回值初始化与 defer 的间接影响

在函数返回前,defer可以修改返回值,最终函数实际返回的是经过defer修改后的值。

4. defer 遇见 panic

当函数中发生panic时,先前定义的所有defer仍按顺序执行。

5. defer 中包含 panic

即使defer内的匿名函数触发了panicdefer依然会按顺序执行,并且defer能够捕获到panic的信息。

6. defer 下的函数参数包含子函数

defer表达式中调用带有子函数的参数时,会先执行子函数以获取其结果值,然后按照defer的栈顺序依次执行。

示例解析

示例一:defer 和 return 结合使用
package main

import "fmt"

func main() {
    function1()
}

func function1() {
    fmt.Printf("1")
    defer function2()
    fmt.Printf("2")
}

func function2() {
    fmt.Printf("3")
}

// 输出结果:
// 1 2 3
示例二:defer 修改返回值
func demo3() (a int) {
    defer func() {
        a = 3
    }()
    return 1
}

// 输出结果:
// 3
示例三:局部变量值拷贝对defer的影响
func demo7() int {
    var a int
    defer func(a int) {
        a = 10
    }(a)
    return 2
}

// 输出结果:
// 10 2

这里解释了为何demo7函数返回10而非2:由于在执行defer时传入的是a的副本,而不是引用,所以修改副本并不会影响原始变量a的值。如果传递的是指向a的指针,那么结果将会不同。

示例四:defer 遇到 panic
func demo4() {
    defer func() {
        fmt.Println("1")
    }()
    defer func() {
        fmt.Println("2")
    }()
    panic("panic")
    defer func() {
        fmt.Println("3")
    }()
    defer func() {
        fmt.Println("4")
    }()
}

// 输出结果:
// 2 1 panic: panic
// goroutine 1 [running]:
// main.demo4()

// 分析:
// 即使发生了panic,仍然执行了部分defer语句。由于panic导致程序中断,后续的defer未被执行。
示例五:defer 中使用 recover 处理 panic
func demo5() {
    defer func() {
        fmt.Println("1")
        if err := recover(); err != nil {
            fmt.Println(err)
        }
    }()
    defer func() { fmt.Println("2") }()
    panic("panic")

    // 不会被执行的defer语句...
}

// 输出结果:
// 2 1 panic

总结来说,defer的使用虽简单,但仍需注意以下几点:

  1. defer在函数返回前执行,其执行顺序优于return,且return包含两步操作:先确定返回值,然后执行defer,最后执行return返回调用者。

  2. 即使函数内部出现panic异常,panic发生前已定义的defer仍然会被执行。

  3. defer内包含子函数调用,子函数会按defer定义的顺序先行执行,而整个defer逻辑则是按照栈的顺序执行。