Golang 笔记 4 defer、error、panic

1、defer语句

  defer语句仅能被放置在函数或方法中。它由关键字defer和一个调用表达式组成。这里的表达式所表示的既不能是对Go语言内建函数的调用也不能是对Go语言标准库代码包unsafe中的那些函数的调用。实际上,知足上述条件的表达式被称为表达式语句。例:函数

func readFile(path string) ([]byte, error) { file,err != os.Open(path) if err != nil { return nil,err } defer file.Close() return ioutil.ReadAll(file) }

  函数readFile的功能是读取指定文件或目录自己的内容并将其返回,同时当有错误发生时当即向调用方报告。其中os和ioutil表明的都是Go语言标准库中的代码包。在打开文件且未发现有错误发生以后,紧跟了一条defer语句。其中携带的表达式语句表示的是对被打开文件的关闭操做。当这条defer语句被执行的时候,其中的这条表达式语句并不会被执行。它的确切的执行时机是在其所属的函数(这里是readFile)的执行即将结束的那个时刻。也就是说,在readFile函数真正结束执行的前一刻,file.Close()才会被执行。该语句可保证在readFile函数将结果返回给调用方以前,那个文件或目录必定会被关闭。
  不管readFile函数正常返回仍是发生了异常其中的file.Close()都会在该函数即将退出那一刻被执行。
  当一个函数中存在多个defer语句时,会按出现顺序的倒序执行。例:ui

func deferIt() { defer func(){ fmt.Print(1) }() defer func() { fmt.Print(2) }() defer func() { fmt.Print(3) }() fmt.Print(4) }

  deferIt()的输出结果是4321。
  defer携带的表达式语句表明的是对某个函数或方法的调用。这个调用可能会有参数传入,好比:fmt.Print(i+1)。若是表明传入参数是一个表达式,那么在defer语句被执行的时候该表达式就会被求值了。这与被携带的表达式语句的执行时机是不一样的。spa

func deferIt3() { f := func(i int) int { fmt.Printf("%d", i) return i * 10 } for i := 1; i < 5; i++ { defer fmt.Printf("%d", f(i)) } }

  输出结果为1 2 3 4 40 30 20 10
  若是defer携带的表达式表明的是对匿名函数的调用,那么咱们就必定要很是警戒:code

funct deferIt4() {
    for i := 1; i < 5; i++ { defer func() { fmt.Print(i) }() } }

  此函数执行后会输出5555,而不是4321。缘由是defer语句携带的表达式语句中的那个匿名函数包含了对外部的变量的使用。等待这个匿名函数要被执行的时候,包含该defer语句的那条for语句已经执行完毕了。此时的变量i的值已经变为5,所以该匿名函数只会打印5。正确的用法是:把要使用的外部变量做为参数传入到匿名函数中接口

func deferIt4() { for i := 1; i < 5; i++ { defer func(n int) { fmt.Print(n) }() } }

2、Go语言错误处理 error

  Go语言的函数能够一次返回多个结果。上一节中例子:string

func readFile(path string) ([]byte, errro) {
    file, err := os.Open(path) if err != nil { return nil, err } defer file.Close() return ioutil.ReadAll(file) }

  函数readFile有两个结果声明。第二个结果声明的类型是error。error是Go语言内置的一个接口类型。它的声明以下:it

type error interface { Error() string }

  显然只要一个类型的方法集合包含了名为Error、无参数声明且仅声明了一个string类型的结果的方法,就至关实现了error接口。os.Open函数的第二个结果值的类型就是这样的。咱们把它赋给了变量err。
  在调用了os.Open函数并取得其结果以后,咱们判断err是否为nil。若是答案不是则直接返回该错误。
&nnbsp; readFile函数的最后一条语句是return,它把ioutil.ReadAll函数的调用结果直接做为readFile函数的结果返回。实际上,ioutil.ReadAll函数的结果声明列表与readFile的结果声明列表是一致的。
  接下来讲明一下如何建立错误:只需调用标准库代码包errors的New函数便可。例,能够在readFile函数的开始处加入这段代码能够在参数无效的时候告知调用方:io

if path == "" { return nil, errors.New("The parameter is invalid!") }

  Go语言标准库的代码包中有不少由errors.New函数建立出来的错误值。例:os.ErrPermission、io.EOF。咱们能够方便的用操做符==来判断一个error类型的值与这些变量的值是否相等,从而来肯定错误的具体类型。好比io.EOF,它表明读取方已无更多数据可读,咱们在获得这个错误的时候不应把它当作一个错误而应该只去结束相应的读取操做。function

if err == io.EOF { ... }

3、Go语言异常处理 panic

  能够把panic理解为异常。若是不显式的处理panic程序会崩溃。内建函数panic能够人为地产生一个异常。不过,这种致命错误能够被恢复。在Go中,内建函数recover能够作到这一点。
  recocer函数必需要在defer语句中调用才有效。由于一旦有异常产生,当前函数以及在调用栈上的全部代码都会失去对流程的控制权。只有defer语句携带的函数中的代码才能够在异常时拦截到。例:class

defer func() { if p := recover(); p != nil { fmt.Printf("Fatal error: %s\n", p) } }

  recover函数会返回一个interface{}类型的值,interface{}表明空接口。Go中的任何类型都是它的实现类型。若是p不为nil那么就说明当前确有异常发生。这时咱们要根据状况作相应处理。一旦defer语句中的recover函数调用被执行了,异常就会被恢复,不论咱们是否进行了后续处理。咱们必定不要只拦截不处理。  panic函数可接受一个interface{}类型的值做为其参数,即咱们能够传任何类型的参数给panic。这里最好只传error类型的值。

相关文章
相关标签/搜索