go语言中defer的执行顺序是什么?

在Go语言中,defer语句用于延迟函数的执行。具体来说,它会在包含defer的函数即将返回前,按照其在代码中出现的顺序逆序执行。

package main

import (
    "fmt"
)

/* 
执行流程分析:
    1. i := 1,此时 i 的值为 1。
    2. 注册第一个 defer(打印 "defer1")。
    3. 注册第二个 defer(会打印 i 的值并自增)。
    4. i += 4,即 i = i + 4,i 变为 5。
    5. 执行 return i,此时 return 表达式会先将 i 的值(5)计算出来,准备返回。
    6. 在 return 语句真正返回前,依次执行 defer 语句(后注册的先执行):
    - 先执行第二个 defer:
        - fmt.Println("defer update before i:", i),此时 i 是 5,输出:defer update before i: 5
        - i += 1,i 变为 6
        - fmt.Println("defer update before i:", i),此时 i 是 6,输出:defer update before i: 6
    - 再执行第一个 defer:
        - 输出:defer1
    7. 最终返回值是多少?是 return 表达式计算时的值,也就是 return 时 i 的值(5),而不是 defer 修改后的值。

output:
    defer update before i: 5
    defer update before i: 6
    defer1

返回值
    返回值为 5

总结
    - return i 时,i 的值为 5,defer 虽然修改了 i,但不会影响已经确定的返回值。
    - defer 里的打印和自增操作会在 return 之后、函数真正返回前执行。
*/
func DeferDemo1() int {
    i := 1
    defer func() {
        fmt.Println("defer1")
    }()
    defer func() {
        fmt.Println("defer update before i:", i)
        i += 1
        fmt.Println("defer update before i:", i)
    }()
    i += 4
    return i
}

/*
执行流程:
    1. i = 0,此时 i 的值为 0。
    2. 注册 defer,defer 里的闭包会在 return 之前执行。
    3. i = 2,此时 i 的值变为 2。
    4. 执行 return i,此时 return 语句会把 i 的值(2)准备好赋值给返回值,但因为这里是具名返回值 i,实际上就是准备返回 i 这个变量。
    5. 在 return 语句真正返回前,执行 defer 里的函数:
        defer update before i:2
        defer 里 i = 1,把 i 的值改成了 1,并打印 "defer2"。
        defer update before i:1
    6. 函数返回,返回值就是 i 的当前值,也就是 1。

output:
    defer2
    return2 1
*/

func DeferDemo2() (i int) {
    i = 0
    defer func() {
        fmt.Println("defer update before i:", i)
        i = 1
        fmt.Println("defer update before i:", i)

    }()
    i = 2
    return i
}

/*
执行流程:
    1. 进入函数,声明具名返回值 i,初始值为 0。
    2. i = 0,此时 i 还是 0。
    3. 注册 defer,defer 里的闭包会在 return 语句执行后、函数返回前执行。
    4. 执行 return i,此时 return 语句会把 i 的当前值(0)准备好赋值给返回值变量 i。
    5. 但因为这里是具名返回值,实际上 return 只是“准备返回”,还没真正返回。
    6. 在 return 语句后,真正返回前,先执行 defer 里的函数:
        defer 里 i += 1,即 i = i + 1,此时 i 变为 1。
        打印 "defer2"。
    7. defer 执行完毕,函数返回,返回值就是 i 的当前值,也就是 1。

output:
    defer2
    return2 1
*/

func DeferDemo3() (i int) {
    i = 0
    defer func() {
        i += 1
        fmt.Println("defer2")
    }()
    return i
}

/*
return函数返回return操作不是原子操作,过程具体可以分为以下几步:
最后返回返回值 70
*/
func DeferDemo4() (t int) { //1. 首先t为默认值0
    defer func() {
        t = t * 10 //2. 执行defer操作,t=0*10=0  4. 最后检查defer操作,有则调用,所以defer里面的函数是可以更改返回值的 7*10=70
    }()
    return 7 //3. 返回值为7
}

/*
执行流程说明:
    1.进入函数,result 默认值为 0。
    2.注册 defer,defer 里有 recover 逻辑。
    3.result = 1,此时 result 变为 1。
    4.执行 panic("发生了panic"),程序发生 panic,后续代码不会执行。
    5.由于 panic,defer 被触发,进入 defer 的匿名函数。
    6.recover() 捕获到 panic,打印“捕获到异常: 发生了panic”,并将 result 修改为 100。
    7.defer 结束,函数返回,返回值为 100。

output:
    捕获到异常: 发生了panic
    defer 执行
    return5 100
*/
func DeferPanicRecoverDemo() (result int) {
    defer func() {
        if r := recover(); r != nil {
            fmt.Println("捕获到异常:", r)
            result = 100 // 修改返回值
        }
        fmt.Println("defer 执行")
    }()
    result = 1
    panic("发生了panic")
    return 2 // 这行不会被执行
}

/*
关键点分析:
    1. 返回值不是具名返回值
    这个函数的返回值类型是 int,但没有具名返回值(不像 func xxx() (result int) 这样)。
    这里的 result 只是函数体内的一个局部变量。
    2. defer 里修改的是局部变量
    在 defer 里 result = 100,只是修改了局部变量 result,并不会影响函数的实际返回值。
    3. panic 触发,defer 执行
    panic 发生后,defer 会被执行,recover 捕获了 panic,defer 里 result = 100,但这只是局部变量的值。
    4. 返回值的确定
    对于没有具名返回值的函数,Go 会在函数返回时,把 return 表达式的值作为返回值。
    但这里 panic 发生后,return 语句不会被执行,Go 会返回该类型的零值,即 int 的零值 0。

总结:
    - 没有具名返回值,defer 里对局部变量的修改不会影响返回值。
    - panic 被 recover 后,函数返回类型的零值(int 的零值是 0)。

output:
    defer 执行
    return6 0
*/
func DeferPanicRecoverDemo2() int {
    result := 0
    defer func() {
        if r := recover(); r != nil {
            fmt.Println("捕获到异常:", r)
            result = 100 
        }
        fmt.Println("defer 执行")
    }()
    result = 1
    panic("发生了panic")
    return result  // 这行不会被执行
}

func main() {
    fmt.Println("return1", DeferDemo1()) //return1 4
    // fmt.Println("return2", DeferDemo2()) //return2 1
    // fmt.Println("return3", DeferDemo3()) //return3 1
    // fmt.Println("return4", DeferDemo4()) //return4 70
    // fmt.Println("return5", DeferPanicRecoverDemo()) //return5 100
    // fmt.Println("return6", DeferPanicRecoverDemo2()) //return6 0
}

更新多参考文档:
https://www.mianshiya.com/bank/1810641215871569922/question/1810649486855266305#heading-0