加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 大数据 > 正文

Golang中的defer, panic, recover

发布时间:2020-12-16 18:24:24 所属栏目:大数据 来源:网络整理
导读:golang中没有类似Java/C++等面向对象编程语言中的try…catch…finally…语句结构,对于有些童鞋可能不太习惯。对于从C语言转过来的童鞋,golang提供了一系列相对较好的函数defer,panic,recover。 从英语的语义看,defer表示“延迟”,panic表示“惊恐”,r

golang中没有类似Java/C++等面向对象编程语言中的try…catch…finally…语句结构,对于有些童鞋可能不太习惯。对于从C语言转过来的童鞋,golang提供了一系列相对较好的函数defer,panic,recover。
从英语的语义看,defer表示“延迟”,panic表示“惊恐”,recover表示“恢复”,那在golang中,提供这些函数的意义何在呢?我们先看一个简单的例子:

func CopyFile(dstName,srcName string) (written int64,err error) {
    src,err := os.Open(srcName)
    if err != nil {
        return
    }
    dst,err := os.Create(dstName) //行*, 这一行出错怎么办?
    if err != nil {
        return
    }
    written,err = io.Copy(dst,src) 
    dst.Close() 
    src.Close()
    return
}

显然,对于异常处理比较敏感的童鞋很快能看出问题,行*出错怎么办?会造成后续io.Copy(dst,src)无法正确执行。如果按照C语言中的写法,我们需要针对每个操作都进行判断,最后在判断的基础上依次关闭打开的资源(有点绕,C语言童鞋应该明白我在说什么)。针对这种情况,golang为我们提供了一种比较优雅的方式,上例改写为:

func CopyFile(dstName,err := os.Open(srcName)
    if err != nil {
        return
    }
    defer src.Close()
    dst,err := os.Create(dstName)
    if err != nil {
        return
    }
    defer dst.Close()
    return io.Copy(dst,src)
}

defer表示“延迟”的意思,在golang中表示函数调用返回之前,按照先进后出的顺序(生命defer的顺序)调用defer中的函数,然后再返回函数。个人理解:在进入函数调用时,在函数的调用栈中的栈顶根据defer的顺序依次入栈,当函数执行结束即将返回(包括正常和异常)时,defer函数按照后进先出的顺序依次执行出栈。
defer的使用有三条原则:

  • defer函数中的参数值在定义时即计算,如下例中的Print函数输出0
func a() { i := 0 defer fmt.Println(i) i++ return }
  • 先进后出原则,如下例中的输出为“3210”
func b() {
    for i := 0; i < 4; i++ {
    defer fmt.Print(i)
    }
}
  • defer函数可以读取并设置函数(有名称的)返回值,如下例中,函数f的返回值为2(这一结果说明函数c中的返回值i在拷贝到函数返回区之前,defer中的函数对i执行了i++操作,因此在返回时i值变为2)
func c() (i int) {
    defer func() { i++ }()
    return 1
}

panic函数执行时,意味着函数进入恐慌模式,有点类似于C++或Java中的throw,即抛出异常,告诉函数:“hi,我这里出故障了,你快处理”。如果函数F有panic函数执行,则执行至panic函数处后,依次调用F的defer中函数,如果在defer函数中没有处理,则函数F向上一层调用者返回panic信息,上一层调用者也按此逻辑执行,如果goroute没有处理panic信息,则goroute会异常崩溃,如果是主goroute,则程序崩溃。

recover函数用于处理panic抛出的信息,类似于C++或Java中的catch。但是,golang中规定,recover只能在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中提供的这三个方法够用了,golang函数库的实现源码为我们提供了非常好的学习资源,各位有心的童鞋可以参考源代码。

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读