Go語言中沒有try catch語句,而是通過函數返回值逐層往上拋。
錯誤指的是可能出現問題的地方出現了問題。例如:打開一個文件時失敗了,這種情況在人們的意料之中。
異常指的是不應該出現問題的地方出現了問題。例如:引用了空指針,這種情況是在人們的意料之外。
錯誤是業務過程中的一部分,而異常不是。
Go語言引入了內置的error類型表示錯誤,錯誤值可以存儲在變量中,從函數中返回,error也可以看成一種數據類型。
package main
import (
"fmt"
"os"
)
func main(){
/*
如果一個函數或方法返回一個錯誤,那麼按照慣例,它必須是函數返回的最後一個值
*/
f, err := os.Open("test.txt")
if err != nil{
fmt.Println(err)
return
}
// 根據f進行文件的讀或寫
fmt.Println(f.Name(), "opened successfully")
}
error是一種內置數據類型,內置的接口:
type error interface {
Error() string
}
其中定義了一個Error
的方法。使用Go語言提供的包構建一個error:
- errors包下的函數:New(), 創建一個error對象
- fmt包下的Errorf()函數:func Errorf(format string, a …interface{}) error
創建實例如下:
// 1. 創建一個error數據
err1 := errors.New("自己創建的一個錯誤")
fmt.Println(err1)
fmt.Printf("%T\n", err1)
// 2.另一個創建error的方法
err2 := fmt.Errorf("錯誤的信息碼%d", 404)
fmt.Println(err2)
fmt.Printf("%T\n", err2)
例如:
// 設計一個函數:驗證年齡是否合法,如果爲負數,就返回一個error
func checkAge(age int) error{
if age < 0{
// 返回error
// err := errors.New("年齡不合法")
err := fmt.Errorf("給定的年齡是:%d, 是不合法的", age)
return err
}
fmt.Println("年齡是:", age)
return nil
}
根據斷言查看錯誤類型相關數據:
package main
import (
"fmt"
"os"
)
func main(){
/*
如果一個函數或方法返回一個錯誤,那麼按照慣例,它必須是函數返回的最後一個值
*/
f, err := os.Open("test.txt")
if err != nil{
fmt.Println(err)
// 使用斷言 查看錯誤信息
if ins, ok := err.(*os.PathError); ok{
fmt.Println("1.Op:", ins.Op)
fmt.Println("2.Path:", ins.Path)
fmt.Println("3.Err:", ins.Err)
}
return
}
// 根據f進行文件的讀或寫
fmt.Println(f.Name(), "opened successfully")
}
還有可以這個例子:
package main
import (
"fmt"
"net"
)
func main() {
// 正常訪問一個網址
addr, err := net.LookupHost("www.baidu.com")
fmt.Println(err)
fmt.Println(addr)
// 不能訪問一個網址
addr2, err2 := net.LookupHost("www.baidu2.com")
fmt.Println(err2)
fmt.Println(addr2)
// 使用斷言查看斷言相關錯誤信息
if ins, ok := err.(*net.DNSError);ok{
if ins.Timeout(){
fmt.Println("操作超時")
}else if ins.Temporary(){
fmt.Println("臨時性錯誤")
}else{
fmt.Println("通常錯誤")
}
}
}
如果返回的錯誤是我們關心的錯誤,我們可以直接比較,然後具體情況具體分析。例如:
package main
import (
"fmt"
"path/filepath"
)
func main() {
// filepath包的Glob函數用於返回與模式匹配的所有文件的名稱,當模式出現錯誤時,該函數返回一個錯誤ErrBadPattern
files, err := filepath.Glob("[")
if err != nil && err == filepath.ErrBadPattern{
fmt.Println(err)
return
}
fmt.Println("matched files", files)
}
不要忽略一個錯誤,忽視錯誤會招致麻煩。
自定義error。 error是一個接口,實現接口方法即可。例如:
package main
import (
"fmt"
"math"
)
/**
自定義錯誤
*/
func main() {
radius := 4.0
area1, err1 := circleArea(radius)
if err1 != nil{
fmt.Println(err1)
// 斷言判斷錯誤類型爲areaError,獲取錯誤類型中的屬性
if err, ok:=err1.(*areaError);ok{
fmt.Printf("半徑是:%.2f", err.radius)
}
return
}
fmt.Println("圓形的面積:", area1)
radius = - 4.0
area2, err2 := circleArea(radius)
if err2 != nil{
fmt.Println(err2)
// 斷言判斷錯誤類型爲areaError,獲取錯誤類型中的屬性
if err, ok:=err2.(*areaError);ok{
fmt.Printf("半徑是:%.2f", err.radius)
}
return
}
fmt.Println("圓形的面積:", area2)
}
// 定義一個結構體
type areaError struct {
msg string
radius float64
}
// 實現error接口
func (e *areaError) Error() string{
return fmt.Sprintf("error:半徑,%.2f,%s", e.radius, e.msg)
}
func circleArea(radius float64)(float64, error) {
if radius < 0{
return 0, &areaError{msg:"半徑非法", radius:radius}
}
return math.Pi*radius*radius, nil
}
進階:自定義錯誤以及對錯誤的處理方法。
package main
import "fmt"
func main() {
length, width := 5.6, 10.2
area, err := rectArea(length, width)
if err!=nil {
fmt.Println(err)
return
}
fmt.Println("矩形的面積爲:", area)
length, width = -5.6, -10.2 // 這是賦值,不是定義
area, err = rectArea(length, width)
if err!=nil {
fmt.Println(err)
// 使用斷言判斷error類型
if err, ok:= err.(*areaError); ok{
if err.lengthNegative(){
fmt.Printf("error:長度,%.2f小於0", err.length)
}
if err.widthNegative(){
fmt.Printf("error:寬度,%.2f小於0", err.width)
}
}
return
}
fmt.Println("矩形的面積爲:", area)
}
// 結構體
type areaError struct {
msg string // 錯誤的描述
length float64 // 發生錯誤的時候,矩形的長度
width float64 // 發生錯誤的時候,矩形的寬度
}
func (e *areaError)Error() string {
return e.msg
}
func (e *areaError) lengthNegative() bool {
return e.length < 0
}
func (e *areaError) widthNegative() bool {
return e.width < 0
}
// 計算矩形的面積
func rectArea(length, width float64) (float64, error) {
msg := ""
if length < 0{
msg = "矩形長度小於零"
}
if width < 0{
if msg == ""{
msg = "矩形寬度小於零"
}else{
msg += ", 矩形寬度小於零"
}
}
if msg != ""{
// 有錯誤,創建對應的錯誤結構體(對象)
return 0, &areaError{msg:msg, length:length,width:width}
}
return length*width, nil
}
panic()和recover()
golang(go)中引入兩個內置函數panic和recover來觸發和終止異常處理流程,同時引入關鍵字defer來延遲defer後面的內容,一直等到defer語句的函數執行完畢是,延遲函數defer候命的內容纔會被執行,而不管defer語句是通過return正常結束,還是由於panic導致已成結束。
package main
import "fmt"
func main() {
defer func() {
// recover() 返回panic中的參數類型
// 這時說明恐慌了
if msg:= recover() ;msg != nil{
fmt.Println(msg, "程序恢復了") // 執行recover後,程序就可以恢復執行(不報錯),類似於catch
// 但是異常後面的程序是不會再運行了
}
}()
funA()
defer myPrint("defer main 3...")
funB()
defer myPrint("defer main 4...")
fmt.Println("main over")
}
func myPrint(s string) {
fmt.Println(s)
}
func funA() {
fmt.Println("一個很普通的函數A")
}
func funB() { // defer 爲外圍函數
fmt.Println("一個很普通的函數B")
defer myPrint("defer funcB 1...")
for i:=1; i<=10;i++{
fmt.Println("i:", i)
if i == 5{
// 讓程序中斷
panic("funB恐慌了") // 參數爲一個接口,可以接受任意值
// 這時程序後面的內容就不能再執行了
// 注已經被defer的函數還會執行
}
}
defer myPrint("defer funcB 2...")
}
什麼時候使用錯誤,什麼時候使用異常需要根據實際情況而定。例如:
- 空指針引用
- 下標越界
- 除數爲0
- 不應該出現的分支,比如default
- 輸入不應該引起函數錯誤
等,可使用異常處理。