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類型的值。

相關文章
相關標籤/搜索