Go 支持使用关键字 defer 创建函数内延迟语句(进栈),当函数在 return 之前,这些 defer 语句会按照先进后出执行(出栈)。
如下所示,在 test 函数 return 之前所有进入 defer 栈的语句都会先执行。
1 | package main |
需要注意的是,编译器在到达 defer 语句的时候要进行确认参数值以及类型,分配堆栈等。下面这段代码输出的是 2 而不是 4,这是因为 i 已经进行了一次计算。REF
1 | // https://play.golang.org/p/dOUFNj96EIQ |
不止如此,就算使用 go 关键字也是这样。
1 | package main |
如果要改变这里的方式就要把这里的函数进行改成闭包即可。
1 | package main |
另外这些 defer 语句不受错误的影响,之前入栈的 defer 会照样执行。
如下所示,在发生了除零错误后,之前调用的 fmt.Println()
函数依旧会执行,而后续的没有入栈的就不会调用了。
1 | func test2(x int) { |
所以我们可以用来做资源释放和错误处理。
1 | func test3() error { |
Go 没有 C 系语言的 try...throw
的形式,而是使用 panic 和 recover 的形式,而这两个都是内建函数。
panic 用于发出错误(恐慌),而 recover 用于接收 panic 的信息。捕获函数 recover 只有在延迟调⽤内直接调⽤才会终⽌错误,否则总是返回 nil。任何未捕获的错误都会沿调⽤堆栈向外传递。
1 | func throwsPanic() { |
以上调用就会输出 panic func
。如果是延迟调⽤中引发的错误,可被后续延迟调⽤捕获,但仅最后⼀个错误可被捕获。
1 | func test() { |
补充错误处理
Panic 是一个内建函数,可以中断原有的控制流程,进入一个令人恐慌的流程中。当函数F调用panic,函数F的执行被中断,但是F中的延迟函数会正常执行,然后F返回到调用它的地方。在调用的地方,F的行为就像调用了panic。这一过程继续向上,直到发生panic的goroutine中所有调用的函数返回,此时程序退出。恐慌可以直接调用panic产生。也可以由运行时错误产生,例如访问越界的数组。
Recover 是一个内建的函数,可以让进入令人恐慌的流程中的goroutine恢复过来。recover仅在延迟函数中有效。在正常的执行过程中,调用recover会返回nil,并且没有其它任何效果。如果当前的goroutine陷入恐慌,调用recover可以捕获到panic的输入值,并且恢复正常的执行。
一定要记住,你应当把它作为最后的手段来使用,也就是说,你的代码中应当没有,或者很少有panic的东西。这是个强大的工具,请明智地使用它。