defer
defer 延迟语句不会马上执行,而是会进入一个栈,函数return 前,会按先进后出的顺序执行。
在defer 函数定义时,对外部变量的引用有三种方式:值传参、指针传参、闭包引用。
值传递:defer声明时立即计算参数值(即函数定义时),后续修改不影响defer执行
指针传递:指针地址在声明时确定,但可以访问到指针指向的最新值
闭包传递:可以捕获外部变量的引用,访问到变量的最新状态
golang在1.21版本之前,for-2 的输出应该是 for-1 的输出结果,即变量i在整个for循环中之后定义一次,每次循环的 i 的地址是不变的。但是在 1.21 版本后for循环中的i每次循环都会重新定义,我这里版本是1.24,可以观察到 for-2 输出的 i 的地址是不同的
package main
import (
"fmt"
)
func main() {
// for-1
var i int
for i = 0; i < 3; i++ {
fmt.Println(&i)
}
fmt.Println("——————")
// for-2
for i := 0; i < 3; i++ {
fmt.Println(&i)
}
}
// 输出
0xc00000a0d8
0xc00000a0d8
0xc00000a0d8
—————————
0xc00000a110
0xc00000a118
0xc00000a120
闭包示例
示例1:
闭包传递:defer 延迟函数通过闭包获取i的引用,最后 test 返回时执行,1.21之前输出的是3个3。
package main
import (
"fmt"
)
func test() {
for i := 0; i < 3; i++ {
defer func() {
fmt.Println(i)
}()
}
}
func main() {
test()
}
// 输出
3
3
3
示例2:需要每次创建一个局部的i,defer通过闭包获取到的是局部的i,最后输出的不再是3个3,而是期望的。(1.21之前需要这么做)
package main
import (
"fmt"
)
func test() {
for i := 0; i < 3; i++ {
i := i
defer func() {
fmt.Println(i)
}()
}
}
func main() {
test()
}
// 输出
2
1
0
示例3:1.21之后的版本已解决这个问题,因为每次循环都是一个新的i,不需要手动写 i := i
import (
"fmt"
)
func test() {
for i := 0; i < 3; i++ {
defer func() {
fmt.Println(i)
}()
}
}
func main() {
test()
}
// 输出
2
1
0
defer和返回值
go 的return有两种情况:
正常情况下return不是原子操作,它分为
可以看到在defer中修改 t,不会影响最后的返回值
import (
"fmt"
)
func test() int {
t := 0
defer func() {
t = 1
}()
return t
}
func main() {
t := test()
fmt.Println(t)
}
// 输出
0
对于命名返回值,它也不是原子的,分为
import (
"fmt"
)
func test() (t int) {
defer func() {
t = 1
}()
return t
}
func main() {
t := test()
fmt.Println(t)
}
// 输出
1
defer 和 recover
注意事项
示例1:recover 必须要在defer的函数中调用才可以捕获panic
// 可以
func main() {
defer func() {
recover()
}()
panic("test")
}
// 不可以
func main() {
defer recover()
panic("test")
}
示例2:不能在多重defer 嵌套里调用recover
// 不可以
func main() {
defer func() {
defer func() {
recover()
}()
}()
panic("test")
}
评论前必须登录!
注册