Go基礎系列:defer、panic和recover

來源:互聯網
上載者:User

標籤:rgs   基礎   串連   ret   int   結束   ror   ...   應該   

defer關鍵字

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

例如:

func main() {    a()}func a() {    println("in a")    defer b()    println("leaving a")    //到了這裡才會執行b()}func b() {    println("in b")    println("leaving b")}

上面將輸出:

in aleaving ain bleaving b

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

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

但注意,由於Go的範圍採用的是詞法範圍,defer的定義位置決定了它延遲對象能看見的變數值,而不是延遲對象被調用時所能看見的值。

例如:

package mainvar x = 10func main() {    a()}func a() {    println("start a:",x)   // 輸出10    x = 20    defer b(x)    x = 30    println("leaving a:",x)  // 輸出30    // 調用defer延遲的對象b(),輸出20}func b(x int) {    println("start b:",x)}

比較下面的defer:

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

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

package mainvar x = 10func 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...4321

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 +0x4cexit status 2

注意上面的end aend 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 mainimport "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 mainstart bstart apanic in aend main

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

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

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

Go基礎系列:defer、panic和recover

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.