久久精品人人爽,华人av在线,亚洲性视频网站,欧美专区一二三

defer關鍵字、panic和recover的示例分析

187次閱讀
沒有評論

共計 4198 個字符,預計需要花費 11 分鐘才能閱讀完成。

這篇文章給大家介紹 defer 關鍵字、panic 和 recover 的示例分析,內容非常詳細,感興趣的小伙伴們可以參考借鑒,希望對大家能有所幫助。

defer 關鍵字

defer 關鍵字可以讓函數或語句延遲到函數語句塊的最結尾時,即即將退出函數時執行,即便函數中途報錯結束、即便已經 panic()、即便函數已經 return 了,也都會執行 defer 所推遲的對象。

其實 defer 的本質是,當在某個函數中使用了 defer 關鍵字,則創建一個獨立的 defer 棧幀,并將該 defer 語句壓入棧中,同時將其使用的相關變量也拷貝到該棧幀中(顯然是按值拷貝的)。因為棧是 LIFO 方式,所以先壓棧的后執行。因為是獨立的棧幀,所以即使調用者函數已經返回或報錯,也一樣能在它們之后進入 defer 棧幀去執行。

例如:

func main() {    a()} func a() { println( in a) defer b() // 將 b()壓入 defer 棧中 println(leaving a) // 到了這里才會執行 b()} func b() { println( in b) println(leaving b)}

上面將輸出:

in aleaving a in bleaving b

即便是函數已經報錯,或函數已經 return 返回,defer 的對象也會在函數退出前的最后一刻執行。

func a() TYPE{    …CODE… defer b()        …CODE… // 函數執行出了錯誤 return args // 函數 b()都會在這里執行}

但注意,由于 Go 的作用域采用的是詞法作用域,defer 的定義位置決定了它推遲對象能看見的變量值,而不是推遲對象被調用時所能看見的值。

例如:

package main var x = 10 func main() {    a()} func a() { println( start a: ,x) // 輸出 10 x = 20 defer b(x) // 壓棧,并按值拷貝 20 到棧中 x = 30 println(leaving a: ,x) // 輸出 30 // 調用 defer 延遲的對象 b(),輸出 20} func b(x int) {println( start b: ,x)}

比較下面的 defer:

package main var x = 10 func main() {    a()} func a() int { println( start a: , x) // 輸出 10 x = 20 defer func() { // 壓棧,但并未傳值,所以內部引用 x println( in defer: , x) // 輸出 30 }()    x = 30 println( leaving a: , x) // 輸出 30 return x}

上面 defer 推遲的匿名函數輸出的值是 30,它看見的不應該是 20 嗎?先再改成下面的:

package main var x = 10 func main() {    a()} func a() int { println( start a: , x) // 輸出 10 x = 20 defer func(x int) {println( in defer: , x) // 輸出 20 }(x)    x = 30 println(leaving a: , x) // 輸出 30 return x}

這個 defer 推遲的對象中看見的卻是 20,這和第一種 defer b(x)是相同的。

原因在于 defer 推遲的如果是函數,它直接就在它的定義位置處評估好參數、變量。該拷貝傳值的拷貝傳值,該指針相見的指針相見。所以,對于第 (1) 和第 (3) 種情況,在 defer 的定義位置處,就將 x =20 拷貝給了推遲的函數參數,所以函數內部操作的一直是 x 的副本。而第二種情況則是直接指向它所看見的 x =20 那個變量,則個變量是全局變量,當執行 x =30 的時候會將其值修改,到執行 defer 推遲的對象時,它指向的 x 的值已經是修改過的。

再看下面這個例子,將 defer 放進一個語句塊中,并在這個語句塊中新聲明一個同名變量 x:

func a() int { println( start a: , x) // 輸出 10 x = 20 {       x := 40 defer func() {println( in defer: , x) // 輸出 40 }()    }    x = 30 println(leaving a: , x) // 輸出 30 return x}

上面的 defer 定義在語句塊中,它能看見的 x 是語句塊中 x =40,它的 x 指向的是語句塊中的 x。另一方面,當語句塊結束時,x=40 的 x 會消失,但由于 defer 的函數中仍有 x 指向 40 這個值,所以 40 這個值仍被 defer 的函數引用著,它直到 defer 執行完之后才會被 GC 回收。所以 defer 的函數在執行的時候,仍然會輸出 40。

如果語句塊內有多個 defer,則 defer 的對象以 LIFO(last in first out)的方式執行,也就是說,先定義的 defer 后執行。

func main() { println( start…) defer println(1) defer println(2) defer println(3) defer println(4) println(end…)}

將輸出:

start… end… 4 3 2 1

defer 有什么用呢?一般用來做善后操作,例如清理垃圾、釋放資源,無論是否報錯都執行 defer 對象。另一方面,defer 可以讓這些善后操作的語句和開始語句放在一起,無論在可讀性上還是安全性上都很有改善,畢竟寫完開始語句就可以直接寫 defer 語句,永遠也不會忘記關閉、善后等操作。

例如,打開文件,關閉文件的操作寫在一起:

open()defer file.Close() … 操作文件 …

以下是 defer 的一些常用場景:

打開關閉文件鎖定、釋放鎖建立連接、釋放連接作為結尾輸出結尾信息清理垃圾(如臨時文件)

panic()和 recover()

panic()用于產生錯誤信息并終止當前的 goroutine,一般將其看作是退出 panic()所在函數以及退出調用 panic()所在函數的函數。例如,G()中調用 F(),F()中調用 panic(),則 F()退出,G()也退出。

注意,defer 關鍵字推遲的對象是函數最后調用的,即使出現了 panic 也會調用 defer 推遲的對象。

例如,下面的代碼中,main()中輸出一個 start main 之后調用 a(),它會輸出 start a,然后就 panic 了,panic()會輸出 panic: panic in a,然后報錯,終止程序。

func main() { println( start main)    a() println( end main)} func a() { println( start a) panic(panic in a) println(end a)}

執行結果如下:

start mainstart apanic: panic in agoroutine 1 [running]:main.a()        E:/learning/err.go:14 +0x63main.main()        E:/learning/err.go:8 +0x4c exit status 2

注意上面的 end a 和 end main 都沒有被輸出。

可以使用 recover()去捕獲 panic()并恢復執行。recover()用于捕捉 panic()錯誤,并返回這個錯誤信息。但注意,即使 recover()捕獲到了 panic(),但調用含有 panic()函數的函數 (即上面的 G() 函數)也會退出,所以如果 recover()定義在 G()中,則 G()中調用 F()函數之后的代碼都不會執行(見下面的通用格式)。

以下是比較通用的 panic()和 recover()的格式:

func main() {    G() // 下面的代碼會執行 …CODE IN MAIN…} func G(){ defer func (){if str := recover(); str != nil {           fmt.Println(str)        }    }()    …CODE IN G()… // F()的調用必須在 defer 關鍵字之后 F() // 該函數內下面的代碼不會執行 …CODE IN G()…} func F() {    …CODE1… panic( error found) // 下面的代碼不會執行 …CODE IN F()…}

可以使用 recover()去捕獲 panic()并恢復執行。但以下代碼是錯誤的:

func main() { println( start main)    a() println( end main)} func a() { println( start a) panic(panic in a) // 直接放在 panic 后是錯誤的 panic_str := recover() println(panic_str) println(end a)}

之所以錯誤,是因為 panic()一出現就直接退出函數 a()和 main()了。要想 recover()真正捕獲 panic(),需要將 recover()放在 defer 的推遲對象中,且 defer 的定義必須在 panic()發生之前。

例如,下面是通用格式的示例:

package main import fmt func main() { println( start main)    b() println( end main)} func a() { println( start a) panic(panic in a) println(end a)} func b() { println( start b) defer func() { if str := recover(); str != nil {           fmt.Println(str)        }    }()    a() println(end b)}

以下是輸出結果:

start main start b start apanic in a end main

注意上面的 end b、end a 都沒有被輸出,但是 end main 輸出了。

panic()是內置的函數 (在包 builtin 中),在 log 包中也有一個 Panic() 函數,它調用 Print()輸出信息后,再調用 panic()。go doc log Panic 一看便知:

$ go doc log Panic func Panic(v …interface{}) Panic is equivalent to Print() followed by a call to panic().

關于 defer 關鍵字、panic 和 recover 的示例分析就分享到這里了,希望以上內容可以對大家有一定的幫助,可以學到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到。

正文完
 
丸趣
版權聲明:本站原創文章,由 丸趣 2023-07-17發表,共計4198字。
轉載說明:除特殊說明外本站除技術相關以外文章皆由網絡搜集發布,轉載請注明出處。
評論(沒有評論)
主站蜘蛛池模板: 奉新县| 蕉岭县| 天门市| 河西区| 巴塘县| 黎平县| 乐都县| 山西省| 教育| 芒康县| 新乡市| 中牟县| 曲阳县| 蒙山县| 长寿区| 龙胜| 娄底市| 鄂尔多斯市| 宁城县| 丹巴县| 尚义县| 农安县| 华阴市| 锡林郭勒盟| 宁阳县| 游戏| 高青县| 县级市| 漳州市| 项城市| 茂名市| 丹巴县| 林芝县| 高要市| 天门市| 新河县| 陆川县| 盱眙县| 石嘴山市| 凤冈县| 巩义市|