云计算百科
云计算领域专业知识百科平台

go defer闭包捕获、返回值机制与panic恢复

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
  • 返回之前确认的返回值
  • 可以看到在defer中修改 t,不会影响最后的返回值

    import (
    "fmt"
    )

    func test() int {
    t := 0

    defer func() {
    t = 1
    }()

    return t
    }

    func main() {
    t := test()
    fmt.Println(t)
    }
    // 输出
    0

    对于命名返回值,它也不是原子的,分为

  • 确定返回值t
  • 执行defer
  • 返回确认的返回值t
  • 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")
    }

    赞(0)
    未经允许不得转载:网硕互联帮助中心 » go defer闭包捕获、返回值机制与panic恢复
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!