您的位置:首页 > Web前端

golang之defer、panic、recover

2018-02-27 22:10 881 查看
go中可以抛出一个panic的异常,然后在defer中通过recover捕获异常,然后生成终止函数(程序)

defer介绍

defer语法介绍

1.defer函数的参数被计算在defer被声明的的时候。

func a() {
i := 0
defer fmt.Println(i)
i++
return
}
//结果: 打印的i值为0,因为i值为0的时候,使用了defer关键字


2.defer函数的执行按照后进先出 的顺序。

func b() {
for i := 0; i < 4; i++ {
defer fmt.Print(i)
}
}
//结果: “3210”,(后进先出的顺序,无需解释)


3.defer函数可以读取和分配返回函数的返回值(Deferred functions may read and assign to the returning function’s named return values)。

func c() (i int) {
defer func() { i++ }()
return 1
}
//结果: 函数返回值为2
//原因:return 1会将函数返回值i赋值为1, 然后执行i++,所以返回值为2


//这里出一道结合了1,3两个用法的题。
func c() (i int) {
defer func(i int) {
i++
fmt.Println("i's value ", i)
}(i)
return 1
}
//结果:函数的返回值为1,打印内容为“i's value 1"
//原因:defer函数在定义的时候就确定参数的值


defer的执行次序

defer、return、返回值三者的执行逻辑应该是:return最先执行,return负责将结果写入返回值;接着defer开始执行一些收尾工作;最后函数携带当前返回值退出。

1.无名返回值的情况

package main

import (
"fmt"
)

func main() {
fmt.Println("return:", a()) // 打印结果为 return: 0
}

func a() int {
var i int
defer func() {
i++
fmt.Println("defer2:", i) // 打印结果为 defer: 2
}()
defer func() {
i++
fmt.Println("defer1:", i) // 打印结果为 defer: 1
}()
return i
}
//原因:函数的返回值没有被提前声名,其值来自于其他变量的赋值,而defer中修改的也是其他变量,而非返回值本身,因此函数退出时返回值并没有被改变。


2.有名返回值的情况

package main

import (
"fmt"
)

func main() {
fmt.Println("return:", b()) // 打印结果为 return: 2
}

func b() (i int) {
defer func() {
i++
fmt.Println("defer2:", i) // 打印结果为 defer: 2
}()
defer func() {
i++
fmt.Println("defer1:", i) // 打印结果为 defer: 1
}()
return i // 或者直接 return 效果相同
}
//原因:函数的返回值被提前声名,也就意味着defer中是可以调用到真实返回值的,因此defer在return赋值返回值 i 之后,再一次地修改了 i 的值,最终函数退出后的返回值才会是defer修改过的值。


3.返回指针的情况

package main

import (
"fmt"
)

func main() {
fmt.Println("c return:", *(c())) // 打印结果为 c return: 2
}

func c() *int {
var i int
defer func() {
i++
fmt.Println("c defer2:", i) // 打印结果为 c defer: 2
}()
defer func() {
i++
fmt.Println("c defer1:", i) // 打印结果为 c defer: 1
}()
return &i
}
//原因:c()*int 的返回值是指针变量,那么在return将变量 i 的地址赋给返回值后,defer再次修改了 i 在内存中的实际值,因此函数退出时返回值虽然依旧是原来的指针地址,但是其指向的内存实际值已经被成功修改了。


panic

panic是用来表示非常严重的不可恢复的错误的。在Go语言中这是一个内置函数,接收一个interface{}类型的值作为参数。如果不使用recover,panic会导致程序直接挂掉。关键的一点是,在函数执行panic的时候,函数会首先执行defer定义的内容,在defer内容执行完之后,panic再向上传递。

recover

recover也是一个Go语言的内置函数,用于再次恢复panic的goroutine。recover必须在defer函数中使用 。当正常执行的是时候,调用recover将会返回nil并且不会有任何其他影响;如果当前的goroutine是panic,调用recover将捕捉到panic传入的值并且再次正常执行。

注意: recover之后,逻辑并不会恢复到panic那个点去,函数还是会在defer之后返回。

综合例子展示

package main

import "fmt"

func main() {
f()
fmt.Println("Returned normally from f.")
}

func f() {
defer func() {
if r := recover(); r != nil {
fmt.Println("Recovered in f", r)
}
}()
fmt.Println("Calling g.")
g(0)
fmt.Println("Returned normally from g.")
}

func g(i int) {
if i > 3 {
fmt.Println("Panicking!")
panic(fmt.Sprintf("%v", i))
}
defer fmt.Println("Defer in g", i)
fmt.Println("Printing in g", i)
g(i + 1)
}


结果

Calling g.
Printing in g 0
Printing in g 1
Printing in g 2
Printing in g 3
Panicking!
Defer in g 3
Defer in g 2
Defer in g 1
Defer in g 0
Recovered in f 4
Returned normally from f.


此处不做解释,如果不懂,返回查看前文描述的内容即可。

参考资料

Golang中defer、return、返回值之间执行顺序的坑

defer,panic and recover
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: