①error接口數組
Go語言中的error類型其實是抽象了Error()方法的error接口ide
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接口的用法:this
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(正確的狀況):spa
5 2 結果是: 2
若輸入5 0(產生錯誤的狀況):設計
5 0 自定義的error,error名稱爲算數不合法
經過上面的例子能夠看出error類型相似於Java中的Exception類型,不一樣的是Exception必須搭配throw和catch使用。code
②defer--延遲語句blog
在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語句。