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