Golang 入門系列(十四)defer, panic和recover用法
以前講過golang 的基本語法。但是,只是講了一些基礎的語法,感興趣的可以看看以前的文章,https://www.cnblogs.com/zhangweizhong/category/1275863.html,前段時間有人問我defer,recover的用法。所以,還是統一的總結一下相關的關鍵字吧。
其實,Go語言是不支援 try…catch…finally 這種異常處理的,因為Go語言的設計者們認為,將異常與控制結構混在一起會很容易使得程式碼變得混亂。因為開發者很容易濫用異常,甚至一個小小的錯誤都丟擲一個異常。 在Go語言中,使用多值返回來返回錯誤。不要用異常代替錯誤,更不要用來控制流程。在極個別的情況下,才使用Go中引入的Exception處理:defer, panic, recover。
一. defer 用法
defer的特性: 在函式返回之前, 呼叫defer函式的操作, 簡化函式的清理工作.
在初接觸到go時, 就被defer吸引住了,但是在使用defer關鍵字的時候,還是得注意這些:
1. 在defer表示式確定的時候,defer修飾的函式(後面統稱為defered函式)的引數也就確定了
package main
import (
"fmt"
)
func main() {
g()
}
func g() {
i := 0
defer fmt.Println(i)
i++
return
}
-------output-------
0
2. 函式內可以有多個defered函式,但是這些defered函式在函式返回時遵守後進先出的原則
package main
import "fmt"
func main() {
g()
}
func g() {
for i := 0; i<4; i++ {
defer fmt.Println(i)
}
}
-------output-------
3
2
1
0
3. 函式命名的返回值跟defered函式一起使用
函式的返回值有可能被defer更改,本質原因是return xxx語句並不是一條原子指令,執行過程是: 儲存返回值(若有)-->執行defer(若有)-->執行return跳轉。
func f() (result int) { defer func() { result++ }() return 0 } func g() (r int) { t := 5 defer func() { t = t + 5 }() return t } func h() (r int) { defer func(r int) { r = r + 5 }(r) return 1 }
-------output-------
0
對於defered函式跟函式命名返回值一塊使用的情況, 當無法判斷返回值的時候, 需要對函式進行變形.
func f(result int) { result = 0 func () { result++ }() return }
-------output-------
1
func g() (r int) { t := 5 r = t func () { t = t + 5 } return }
-------output-------
5
func h() (r int) { r = 1 func (r int) { r = r + 5 }(r) return }
-------output-------
1
在func(r int) {...}中,由於r是以值傳遞的方式進行的, 所以r的值不會改變。
注意:
1. 申請資源後最好立即使用defer關閉資源。
二. panic用法
panic用法挺簡單的, 其實就是throw exception。
panic是golang的內建函式,panic會中斷函式F的正常執行流程, 從F函式中跳出來, 跳回到F函式的呼叫者. 對於呼叫者來說, F看起來就是一個panic, 所以呼叫者會繼續向上跳出, 直到當前goroutine返回. 在跳出的過程中, 程序會保持這個函式棧. 當goroutine退出時, 程式會crash。
要注意的是, F函式中的defered函式會正常執行, 按照上面defer的規則。
同時引起panic除了我們主動呼叫panic之外, 其他的任何執行時錯誤, 例如陣列越界都會造成panic
看下面一個例子
package main import ( "fmt" ) func main() { test() } func test() { defer func() { fmt.Println("列印前") }() defer func() { fmt.Println("列印中") }() defer func() { fmt.Println("列印後") }() panic("觸發異常") fmt.Println("test") }
-------output-------
列印後
列印中
列印前
panic: 觸發異常 goroutine 1 [running]:
main.test()
D:/Go_Path/go/src/logDemo/main.go:15 +0x98
main.main() D:/Go_Path/go/src/logDemo/main.go:8 +0x27
exit status 2
三. recover 用法
recover也是golang的一個內建函式, 其實就是try catch。
不過需要注意的是:
1. recover如果想起作用的話, 必須在defered函式中使用。
2. 在正常函式執行過程中,呼叫recover沒有任何作用, 他會返回nil。如這樣:fmt.Println(recover()) 。
3. 如果當前的goroutine panic了,那麼recover將會捕獲這個panic的值,並且讓程式正常執行下去。不會讓程式crash。
func main() { fmt.Println("c") defer func() { // 必須要先宣告defer,否則不能捕獲到panic異常 fmt.Println("d") if err := recover(); err != nil { fmt.Println(err) // 這裡的err其實就是panic傳入的內容 } fmt.Println("e") }() f() //開始呼叫f fmt.Println("f") //這裡開始下面程式碼不會再執行 } func f() { fmt.Println("a") panic("異常資訊") fmt.Println("b") //這裡開始下面程式碼不會再執行 }
-------output------- c a d 異常資訊 e
參考連結
1. defer關鍵字
2. Golang中defer、return、返回值之間執行順序的坑