一、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語句時,會按出現順序的倒序執行。例:
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語句被執行的時候該表達式就會被求值了。這與被攜帶的表達式語句的執行時機是不同的。
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攜帶的表達式代表的是對匿名函數的調用,那麼我們就一定要非常警惕:
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)
}()
}
}
二、Go語言錯誤處理 error
Go語言的函數可以一次返回多個結果。上一節中例子:
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語言內置的一個接口類型。它的聲明如下:
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函數的開始處加入這段代碼可以在參數無效的時候告知調用方:
if path == "" {
return nil, errors.New("The parameter is invalid!")
}
Go語言標準庫的代碼包中有很多由errors.New函數創建出來的錯誤值。例:os.ErrPermission、io.EOF。我們可以方便的用操作符==來判斷一個error類型的值與這些變量的值是否相等,從而來確定錯誤的具體類型。比如io.EOF,它代表讀取方已無更多數據可讀,我們在得到這個錯誤的時候不該把它看成一個錯誤而應該只去結束相應的讀取操作。
if err == io.EOF {
...
}
三、Go語言異常處理 panic
可以把panic理解爲異常。如果不顯式的處理panic程序會崩潰。內建函數panic可以人爲地產生一個異常。不過,這種致命錯誤可以被恢復。在Go中,內建函數recover可以做到這一點。
recocer函數必須要在defer語句中調用纔有效。因爲一旦有異常產生,當前函數以及在調用棧上的所有代碼都會失去對流程的控制權。只有defer語句攜帶的函數中的代碼纔可以在異常時攔截到。例:
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類型的值。