go之defer,panic,recover用法

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
}

顯然,對於異常處理比較敏感的童鞋很快能看出問題,行6出錯怎麼辦?會造成後續io.Copy(dst, src)無法正確執行。如果按照C語言中的寫法,我們需要針對每個操作都進行判斷,最後在判斷的基礎上依次關閉打開的資源(有點繞,C語言童鞋應該明白我在說什麼)。針對這種情況,golang爲我們提供了一種比較優雅的方式,上例改寫爲:

func CopyFile(dstName, srcName string) (written int64, err error) {
    src, 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)
}

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函數庫的實現源碼爲我們提供了非常好的學習資源,各位有心的童鞋可以參考源代碼。
原文鏈接:http://www.jianshu.com/p/f76b9ce083c4

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章