18、Go 流程控制 - defer 延迟语句

作者: 温新

分类: 【Go基础】

阅读: 673

时间: 2023-08-29 15:42:35

hi,我是温新

延迟语句,简单理解就是跳出当前执行顺序从而延后执行的语句。先来看一个简单的案例:

package main

import "fmt"

func main() {
    defer fmt.Println("1")
    fmt.Println("2")
}

结果是:先输出 2,再输出 1。

在这个案例中,程序从上到下执行,遇到了defer 先不执行,而是压入到栈中,先执行后面的语句,执行完了,再回来执行 defer

defer 的作用

defer 用于延迟调用指定的函数,它只在出现在函数内部。

由于 defer 具有延迟的特点,可以把 defer 用于资源回收、清理收尾等工作。

defer 的特点

1、只有当 defer 语句全部执行,defer 所在函数在算真正执行结束;

2、当函数中有 defer 语句时,需要等到所有的 defer 语句执行完毕,才会执行 return 语句。

defer 案例

多个 defer 的执行顺序

package main

import "fmt"

func main() {
    defer fmt.Println(1)
    defer fmt.Println(2)
    defer fmt.Println(3)
    fmt.Println("hello")
}

defer 的执行顺序遵循后进先出的原则,最后的 defer 优先执行,输出结果为:

hello
3
2
1

defer 和 return 的顺序

package main

import "fmt"

func main() {
    fmt.Println(deferReturn())
}

func deferReturn() (i int) {
    defer func() {
        i += 5
    }()

    i = 1

    return
}

输出结果为 6。defer 是在函数 return 之前执行。具名返回参数 i 的作用域是整个函数,因此,defer 可以访问 i 并对其修改。

defer 和 panic

package main

import "fmt"

func main() {
	deferPanic()
}

func deferPanic() {
    defer fmt.Println(1)
    defer fmt.Println(2)
    defer fmt.Println(3)

    panic("哎呀,页面飞走啦~~~")
}

当函数遇到panic,defer 仍然会被执行。Go会先执行所有的defer链表(该函数的所有defer),当所有defer被执行完毕且没有recover时,才会进行panic。输出结果:

3
2
1
panic: 哎呀,页面飞走啦~~~

defer & recover & panic

panic 中断程序执行;recover 恢复 panic。先观察案例和输出结果,再进行解释。

package main

import "fmt"

func main() {
	deferRevover()
}

func deferRevover() {
    defer fmt.Println("1、最后执行的函数")

    defer func() {
        if err := recover(); err != nil {
            fmt.Println(err)
            fmt.Println("2、哎呀,这里发生错误了~~")
        }
    }()

    fmt.Println("3、发生错误之前")
    panic("4、出了一点小问题")

    fmt.Println("5、错误后")
}

其输出结果如下:

3、发生错误之前
4、出了一点小问题
2、哎呀,这里发生错误了~~
1、最后执行的函数

代码解释:

1、代码执行,遇到 defer 先不执行,因此 1 现在没有被输出;

2、又遇到一个 defer,因此仍旧不输出结果;

3、遇到正常程序,因此首先输出了 3

4、遇到 panic,程序中断,返回执行 defer,在函数中,捕获了 panic 的错误,因此输出 4,紧接着输出 2,最后输出 1

注意:5 没有被执行。

看到这里,是不是有点恍然大悟的感觉,这不是 try...catch 吗?Go 中没有直接的try...catch异常捕获功能,异常处理都是返回 error,从而被处理。

捕获 err

package main

import "fmt"

func main() {
    defer func() {
        if err := recover(); err != nil {
            fmt.Println(err) // 输出 我飞走了
        }
    }()

    panicFunc()
}

func panicFunc() {
	panic("我飞走了")
}

recover 顺序

package main

import "fmt"

func main() {

    defer func() {
        if err := recover(); err != nil {
            fmt.Println("1、main 修复错误!")
        }
    }()

    panicFunction()
}

func panicFunction() {

    defer func() {
        if err := recover(); err != nil {
            fmt.Println("2、panicFunction 修复错误!")
        }
    }()
    panic("我是错误")
}

这个案例中输出的结果是 2、panicFunction 修复错误!

根据 defer 执行的机制可以分析出来,当程序执行到 panic("我是错误") 时,就要去执行 defer,而 defer 是后进先出,因此也就先执行了 panicFunction 函数中的 defer,而错误就在这里被修复了,修完完成后,main 中就没有去修复了。

defer 参数陷阱

package main

import "fmt"

func main() {
    defer printResult(printResult(1, 1), 2)
    defer printResult(printResult(2, 3), 3)
}

func printResult(a, b int) int {
    sum := a + b
    fmt.Printf("%d + %d = %d\n", a, b, sum)

    return sum
}

根据 defer 机制,预计中输出的结果应该是这样的:

2 + 5 = 5
5 + 5 = 8
1 + 1 = 2
2 + 2 = 4

然后正确的输出结果是这样的:

1 + 1 = 2
2 + 3 = 5
5 + 3 = 8
2 + 2 = 4

这所以产生与预期不符合的结果,原因在于定义 defer 时,Go 就需要确定 defer 语句的函数的参数。因此,Go 顺序执行到 defer 时,会先把 defer 函数的参数计算出来。

顺序执行到第一个 defer 时,先把 printResult(1, 1) 的结果计算出来,当执行到第二个 defer 时,把 printResult(2, 3) 计算出来,因此,此时的输出结果是:

1 + 1 = 2
2 + 3 = 5

参数结果计算完成后,再按照 defer 机制来执行,因此也就得到了最终的结果。

请登录后再评论