1、defer語句
defer?語句的用途是:含有?defer?語句的函數(shù)或類型方法,會在該函數(shù)或方法將要返回之前(return之前),調(diào)用另一個函數(shù)。所以,****我們可以在函數(shù)開始時,將一些容易忘記的結(jié)束操作用defer聲明好,這樣保證這些結(jié)束操作能在函數(shù)結(jié)束后正確執(zhí)行。當(dāng)defer要調(diào)用的函數(shù)有參數(shù)時,執(zhí)行?defer?語句的時候,就會對延遲函數(shù)的實(shí)參進(jìn)行求值。舉例如下
func?printA(a?int)?{??
????fmt.Println("value?of?a?in?deferred?function",?a)
}
func?main()?{??
????a?:=?5
????defer?printA(a)?//此時a=5,所以實(shí)際傳到printA中的參數(shù)值為5,輸出為5
????a?=?10
????fmt.Println("value?of?a?before?deferred?function?call",?a)
}
當(dāng)一個函數(shù)內(nèi)多次調(diào)用?defer?時,Go 會把?defer?調(diào)用放入到一個棧中,隨后按照后進(jìn)先出(Last In First Out, LIFO)的順序執(zhí)行。
2、錯誤處理
2.1、error類型
在 Go 中,錯誤一直是很常見的。錯誤用內(nèi)建的?error?類型來表示。就像其他的內(nèi)建類型(如?int、float64?等),錯誤值可以存儲在變量里、作為函數(shù)的返回值等等。error類型定義如下:
type?error?interface?{?//是一個接口,實(shí)現(xiàn)該接口的類型都可以當(dāng)作錯誤類型
????Error()?string
}
使用示例:
func?main()?{??
????f,?err?:=?os.Open("/test.txt")
????if?err?!=?nil?{//error類型
????????fmt.Println(err)
????????return
????}
????fmt.Println(f.Name(),?"opened?successfully")//println會自動調(diào)用Error()輸出錯誤信息
}
2.2、提取錯誤信息的方法
Error()返回的描述不是很詳細(xì),我們可以用下面的方法獲取更詳細(xì)的錯誤信息:
判斷底層結(jié)構(gòu)體類型(即實(shí)現(xiàn)error接口的結(jié)構(gòu)體),使用結(jié)構(gòu)體字段獲取更多信息
判斷底層結(jié)構(gòu)體類型(即實(shí)現(xiàn)error接口的結(jié)構(gòu)體),調(diào)用類型的方法獲取更多信息
與error類型變量直接比較
func?main()?{??
????files,?err?:=?filepath.Glob("[")
????if?error?!=?nil?&&?err?==?filepath.ErrBadPattern?{//比較,判斷是不是某個指定的錯誤類型
????????fmt.Println(err)
????????return
????}
????fmt.Println("matched?files",?files)
}
2.3、fmt.Errorf
fmt?包中的?Errorf?函數(shù)會根據(jù)格式說明符,規(guī)定錯誤的格式,并返回一個符合該錯誤的字符串。示例:
func?circleArea(radius?float64)?(float64,?error)?{??
????if?radius?0?{
????????//返回錯誤類型
????????return?0,?fmt.Errorf("Area?calculation?failed,?radius?%0.2f?is?less?than?zero",?radius)
????}
????return?math.Pi?*?radius?*?radius,?nil
}
func?main()?{??
????radius?:=?-20.0
????area,?err?:=?circleArea(radius)
????if?err?!=?nil?{
????????fmt.Println(err)
????????return
????}
????fmt.Printf("Area?of?circle?%0.2f",?area)
}
3 panic和recover
有些情況,當(dāng)程序發(fā)生異常時,無法繼續(xù)運(yùn)行。在這種情況下,我們會使用?panic?來終止程序。當(dāng)函數(shù)發(fā)生 panic 時,它會終止運(yùn)行,在執(zhí)行完所有的defer延遲函數(shù)后,程序控制返回到該函數(shù)的調(diào)用方。這樣的過程會一直持續(xù)下去,直到當(dāng)前協(xié)程的所有函數(shù)都返回退出,然后程序會打印出panic信息,接著打印出堆棧跟蹤(Stack Trace),最后程序終止。當(dāng)程序發(fā)生 panic 時,使用?recover?可以重新獲得對該程序的控制。 需要注意:
你應(yīng)該盡可能地使用錯誤,而不是使用 panic 和 recover。只有當(dāng)程序不能繼續(xù)運(yùn)行的時候,才應(yīng)該使用 panic 和 recover 機(jī)制。
**必須在defer函數(shù)中直接調(diào)用recover**。在延遲函數(shù)內(nèi)調(diào)用?recover,可以取到?panic?的錯誤信息,并且停止panic續(xù)發(fā)事件(Panicking Sequence),程序運(yùn)行恢復(fù)正常。如果在延遲函數(shù)的外部調(diào)用?recover,就不能停止panic續(xù)發(fā)事件。
只有在相同的Go協(xié)程中調(diào)用 recover 才管用。recover?不能恢復(fù)一個不同協(xié)程的 panic
使用示例
func?recoverName()?{??
????if?r?:=?recover();?r!=?nil?{//恢復(fù)程序運(yùn)行
????????fmt.Println("recovered?from?",?r)
????????debug.PrintStack()//打印panic異常的堆棧信息
????}
}
func?fullName(firstName?*string,?lastName?*string)?{??
????defer?recoverName()?//延遲函數(shù)
????if?firstName?==?nil?{
????????panic("runtime?error:?first?name?cannot?be?nil")//觸發(fā)程序異常
????}
????if?lastName?==?nil?{
????????panic("runtime?error:?last?name?cannot?be?nil")
????}
????fmt.Printf("%s?%s
",?*firstName,?*lastName)
????fmt.Println("returned?normally?from?fullName")
}
func?main()?{??
????defer?fmt.Println("deferred?call?in?main")
????firstName?:=?"Elon"
????fullName(&firstName,?nil)
????fmt.Println("returned?normally?from?main")
}
一個通用的recover defer使用的模板,可供參考:
func?foo()?(err?error)?{
????defer?func()?{
????????if?r?:=?recover();?r?!=?nil?{
????????????switch?x?:=?r.(type)?{
????????????case?string:
????????????????err?=?errors.New(x)
????????????case?error:
????????????????err?=?x
????????????default:
????????????????err?=?fmt.Errorf("Unknown?panic:?%v",?r)
????????????}
????????}
????}()
????panic("TODO")
}
編輯:黃飛
?
電子發(fā)燒友App






評論