91超碰碰碰碰久久久久久综合_超碰av人澡人澡人澡人澡人掠_国产黄大片在线观看画质优化_txt小说免费全本

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Go的defer、panic和recover怎么用

發布時間:2022-04-18 10:56:14 來源:億速云 閱讀:173 作者:iii 欄目:開發技術

這篇文章主要介紹“Go的defer、panic和recover怎么用”的相關知識,小編通過實際案例向大家展示操作過程,操作方法簡單快捷,實用性強,希望這篇“Go的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 a
leaving a
in b
leaving 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 main
start a
panic: panic in a

goroutine 1 [running]:
main.a()
        E:/learning/err.go:14 +0x63
main.main()
        E:/learning/err.go:8 +0x4c
exit 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 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 a
panic in a
end main

注意上面的end bend 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().

關于“Go的defer、panic和recover怎么用”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業相關的知識,可以關注億速云行業資訊頻道,小編每天都會為大家更新不同的知識點。

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

彰武县| 长武县| 方城县| 克东县| 张家港市| 三门县| 通河县| 东兴市| 清远市| 萍乡市| 封开县| 辽阳县| 泉州市| 南开区| 许昌市| 贵德县| 满城县| 吉安市| 孟连| 仪陇县| 扶绥县| 北川| 巫山县| 内江市| 边坝县| 阜康市| 定结县| 雷州市| 三门峡市| 江永县| 滨海县| 台东市| 九寨沟县| 无棣县| 宁晋县| 崇左市| 江陵县| 喀什市| 明光市| 德钦县| 辽宁省|