①error接口
Go語言中的error類型實際上是抽象了Error()方法的error接口
type error interface { Error() string }
Go語言使用該接口進行標准的錯誤處理。
對於大多數函數,如果要返回錯誤,大致上都可以定義為如下模式,將error作為多種返回
值中的最后一個,但這並非是強制要求:
func Foo(param int)(n int, err error) { // ... }
調用時的代碼建議按如下方式處理錯誤情況:
n, err := Foo(0) if err != nil { // 錯誤處理 } else { // 使用返回值n }
看下面的例子綜合了一下error接口的用法:
package main import ( "fmt" ) //自定義錯誤類型 type ArithmeticError struct { error //實現error接口 } //重寫Error()方法 func (this *ArithmeticError) Error() string { return "自定義的error,error名稱為算數不合法" } //定義除法運算函數 func Devide(num1, num2 int) (rs int, err error) { if num2 == 0 { return 0, &ArithmeticError{} } else { return num1 / num2, nil } } func main() { var a, b int fmt.Scanf("%d %d", &a, &b) rs, err := Devide(a, b) if err != nil { fmt.Println(err) } else { fmt.Println("結果是:", rs) } }
運行,輸入參數5 2(正確的情況):
5 2 結果是: 2
若輸入5 0(產生錯誤的情況):
5 0 自定義的error,error名稱為算數不合法
通過上面的例子可以看出error類型類似於Java中的Exception類型,不同的是Exception必須搭配throw和catch使用。
②defer--延遲語句
在Go語言中,可以使用關鍵字defer向函數注冊退出調用,即主調函數退出時,defer后的函數才會被調用。
defer語句的作用是不管程序是否出現異常,均在函數退出時自動執行相關代碼。(相當於Java中的finally )
當函數執行到最后時,這些defer語句會按照逆序執行,最后該函數返回。
例如:
package main import ( "fmt" ) func main() { for i := 0; i < 5; i++ { defer fmt.Println(i) } }
其執行結果為:
4 3 2 1 0
defer語句在聲明時被加載到內存(多個defer語句按照FIFO原則) ,加載時記錄變量的值,而在函數返回之后執行,看下面的例子:
例子1:defer語句加載時記錄值
func f1() { i := 0 defer fmt.Println(i) //實際上是將fmt.Println(0)加載到內存 i++ return } func main() { f1() }
其結果顯然是0
例子2:在函數返回后執行
func f2() (i int) { var a int = 1 defer func() { a++ fmt.Println("defer內部", a) }() return a } func main() { fmt.Println("main中", f2()) }
其結果是
defer內部 2 main中 1
例子3:defer語句會讀取主調函數的返回值,並對返回值賦值.(注意和例子2的區別)
func f3() (i int) { defer func() { i++ }() return 1 } func main() { fmt.Println(f3()) }
其結果竟然是2.
通過上面的幾個例子,自然而然會想到用defer語句做清理工作,釋放內存資源(這樣你再也不會為Java中的try-catch-finally層層嵌套而苦惱了)
例如關閉文件句柄:
srcFile,err := os.Open("myFile") defer srcFile.Close()
關閉互斥鎖:
mutex.Lock()
defer mutex.Unlock()
上面例子中defer語句的用法有兩個優點:
1.讓設計者永遠也不會忘記關閉文件,有時當函數返回時常常忘記釋放打開的資源變量。
2.將關閉和打開靠在一起,程序的意圖變得清晰很多。
下面看一個文件復制的例子:
package main import ( "fmt" "io" "os" ) func main() { copylen, err := copyFile("dst.txt", "src.txt") if err != nil { return } else { fmt.Println(copylen) } } //函數copyFile的功能是將源文件sec的數據復制給dst func copyFile(dstName, srcName string) (copylen int64, err error) { src, err := os.Open(srcName) if err != nil { return } //當return時就會調用src.Close()把源文件關閉 defer src.Close() dst, err := os.Create(dstName) if err != nil { return } //當return是就會調用src.Close()把目標文件關閉 defer dst.Close() return io.Copy(dst, src) }
可以看到確實比Java簡潔許多。
③panic-recover運行時異常處理機制
Go語言中沒有Java中那種try-catch-finally結構化異常處理機制,而使用panic()函數答題throw/raise引發錯誤,
然后在defer語句中調用recover()函數捕獲錯誤,這就是Go語言的異常恢復機制——panic-recover機制
兩個函數的原型為:
func panic(interface{})//接受任意類型參數 無返回值 func recover() interface{}//可以返回任意類型 無參數
一定要記住,你應當把它作為最后的手段來使用,也就是說,你的代碼中應當沒有,或者很少有panic的東西。這是個強大的工具,請明智地使用
它。那么,我們應該如何使用它呢?
panic()
是一個內建函數,可以中斷原有的控制流程,進入一個令人panic(恐慌即Java中的異常)的流程中。當函數F調用panic,函數F的執行被中
斷,但是F中的延遲函數(必須是在panic之前的已加載的defer)會正常執行,然后F返回到調用它的地方。在調用的地方,F的行為就像調用了panic。這一
過程繼續向上,直到發生panic的goroutine中所有調用的函數返回,此時程序退出。異常可以直接調用panic產
生。也可以由運行時錯誤產生,例如訪問越界的數組。
recover()
是一個內建的函數,可以讓進入令人恐慌的流程中的goroutine恢復過來。recover僅在延遲函數中有效。在正常
的執行過程中,調用recover會返回nil,並且沒有其它任何效果。如果當前的goroutine陷入panic,調用
recover可以捕獲到panic的輸入值,並且恢復正常的執行。
一般情況下,recover()應該在一個使用defer關鍵字的函數中執行以有效截取錯誤處理流程。如果沒有在發生異常的goroutine中明確調用恢復
過程(使用recover關鍵字),會導致該goroutine所屬的進程打印異常信息后直接退出。
這里結合自定義的error類型給出一個使用panic和recover的完整例子:
package main import ( "fmt" ) //自定義錯誤類型 type ArithmeticError struct { error } //重寫Error()方法 func (this *ArithmeticError) Error() string { return "自定義的error,error名稱為算數不合法" } //定義除法運算函數***這里與本文中第一幕①error接口的例子不同 func Devide(num1, num2 int) int { if num2 == 0 { panic(&ArithmeticError{}) //當然也可以使用ArithmeticError{}同時recover等到ArithmeticError類型 } else { return num1 / num2 } } func main() { var a, b int fmt.Scanf("%d %d", &a, &b) defer func() { if r := recover(); r != nil { fmt.Printf("panic的內容%v\n", r) if _, ok := r.(error); ok { fmt.Println("panic--recover()得到的是error類型") } if _, ok := r.(*ArithmeticError); ok { fmt.Println("panic--recover()得到的是ArithmeticError類型") } if _, ok := r.(string); ok { fmt.Println("panic--recover()得到的是string類型") } } }() rs := Devide(a, b) fmt.Println("結果是:", rs) }
其執行的結果為:
使用與上面相同的測試數據,輸入5 2得:
5 2 結果是: 2
輸入5 0得:
5 0 panic的內容自定義的error,error名稱為算數不合法 panic--recover()得到的是error類型 panic--recover()得到的是ArithmeticError類型
可見已將error示例程序轉換為了Java中的用法,但是在大多數程序中使用error處理的方法較多。
需要注意的是:defer語句定義的位置 如果defer放在了
rs := Devide(a, b)語句之后,defer將沒有機會執行即下面的程序失效:
rs := Devide(a, b) defer func() { if r := recover(); r != nil { fmt.Printf("panic的內容%v\n", r) if _, ok := r.(error); ok { fmt.Println("panic--recover()得到的是error類型") } if _, ok := r.(*ArithmeticError); ok { fmt.Println("panic--recover()得到的是ArithmeticError類型") } if _, ok := r.(string); ok { fmt.Println("panic--recover()得到的是string類型") } } }()
因為在在陷入panic之前defer語句沒有被加載到內存,而在執行panic時程序被中斷,因而無法執行defer語句。