18、Go 流程控制 - defer 延迟语句
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 机制来执行,因此也就得到了最终的结果。