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