欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Go基礎(chǔ)教程系列之defer、panic和recover詳解

 更新時(shí)間:2022年04月16日 16:36:41   作者:駿馬金龍  
這篇文章主要介紹了Go基礎(chǔ)教程系列之defer、panic和recover,需要的朋友可以參考下

defer關(guān)鍵字

defer關(guān)鍵字可以讓函數(shù)或語(yǔ)句延遲到函數(shù)語(yǔ)句塊的最結(jié)尾時(shí),即即將退出函數(shù)時(shí)執(zhí)行,即便函數(shù)中途報(bào)錯(cuò)結(jié)束、即便已經(jīng)panic()、即便函數(shù)已經(jīng)return了,也都會(huì)執(zhí)行defer所推遲的對(duì)象。

其實(shí)defer的本質(zhì)是,當(dāng)在某個(gè)函數(shù)中使用了defer關(guān)鍵字,則創(chuàng)建一個(gè)獨(dú)立的defer棧幀,并將該defer語(yǔ)句壓入棧中,同時(shí)將其使用的相關(guān)變量也拷貝到該棧幀中(顯然是按值拷貝的)。因?yàn)闂J荓IFO方式,所以先壓棧的后執(zhí)行。因?yàn)槭仟?dú)立的棧幀,所以即使調(diào)用者函數(shù)已經(jīng)返回或報(bào)錯(cuò),也一樣能在它們之后進(jìn)入defer棧幀去執(zhí)行。

例如:

func main() {
    a()
}

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

func b() {
    println("in b")
    println("leaving b")
}

上面將輸出:

in a
leaving a
in b
leaving b

即便是函數(shù)已經(jīng)報(bào)錯(cuò),或函數(shù)已經(jīng)return返回,defer的對(duì)象也會(huì)在函數(shù)退出前的最后一刻執(zhí)行。

func a() TYPE{
    ...CODE...
    
    defer b()
    
    ...CODE...
    
    // 函數(shù)執(zhí)行出了錯(cuò)誤
    
    return args
    // 函數(shù)b()都會(huì)在這里執(zhí)行
}

但注意,由于Go的作用域采用的是詞法作用域,defer的定義位置決定了它推遲對(duì)象能看見(jiàn)的變量值,而不是推遲對(duì)象被調(diào)用時(shí)所能看見(jiàn)的值。

例如:

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
    // 調(diào)用defer延遲的對(duì)象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() {      // 壓棧,但并未傳值,所以內(nèi)部引用x
		println("in defer:", x)  // 輸出30
	}()
	x = 30
	println("leaving a:", x) // 輸出30
	return x
}

上面defer推遲的匿名函數(shù)輸出的值是30,它看見(jiàn)的不應(yīng)該是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
}

這個(gè)defer推遲的對(duì)象中看見(jiàn)的卻是20,這和第一種defer b(x)是相同的。

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

再看下面這個(gè)例子,將defer放進(jìn)一個(gè)語(yǔ)句塊中,并在這個(gè)語(yǔ)句塊中新聲明一個(gè)同名變量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定義在語(yǔ)句塊中,它能看見(jiàn)的x是語(yǔ)句塊中x=40,它的x指向的是語(yǔ)句塊中的x。另一方面,當(dāng)語(yǔ)句塊結(jié)束時(shí),x=40的x會(huì)消失,但由于defer的函數(shù)中仍有x指向40這個(gè)值,所以40這個(gè)值仍被defer的函數(shù)引用著,它直到defer執(zhí)行完之后才會(huì)被GC回收。所以defer的函數(shù)在執(zhí)行的時(shí)候,仍然會(huì)輸出40。

如果語(yǔ)句塊內(nèi)有多個(gè)defer,則defer的對(duì)象以LIFO(last in first out)的方式執(zhí)行,也就是說(shuō),先定義的defer后執(zhí)行。

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有什么用呢?一般用來(lái)做善后操作,例如清理垃圾、釋放資源,無(wú)論是否報(bào)錯(cuò)都執(zhí)行defer對(duì)象。另一方面,defer可以讓這些善后操作的語(yǔ)句和開(kāi)始語(yǔ)句放在一起,無(wú)論在可讀性上還是安全性上都很有改善,畢竟寫(xiě)完開(kāi)始語(yǔ)句就可以直接寫(xiě)defer語(yǔ)句,永遠(yuǎn)也不會(huì)忘記關(guān)閉、善后等操作。

例如,打開(kāi)文件,關(guān)閉文件的操作寫(xiě)在一起:

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

以下是defer的一些常用場(chǎng)景:

  • 打開(kāi)關(guān)閉文件
  • 鎖定、釋放鎖
  • 建立連接、釋放連接
  • 作為結(jié)尾輸出結(jié)尾信息
  • 清理垃圾(如臨時(shí)文件)

panic()和recover()

panic()用于產(chǎn)生錯(cuò)誤信息并終止當(dāng)前的goroutine,一般將其看作是退出panic()所在函數(shù)以及退出調(diào)用panic()所在函數(shù)的函數(shù)。例如,G()中調(diào)用F(),F(xiàn)()中調(diào)用panic(),則F()退出,G()也退出。

注意,defer關(guān)鍵字推遲的對(duì)象是函數(shù)最后調(diào)用的,即使出現(xiàn)了panic也會(huì)調(diào)用defer推遲的對(duì)象。

例如,下面的代碼中,main()中輸出一個(gè)start main之后調(diào)用a(),它會(huì)輸出start a,然后就panic了,panic()會(huì)輸出panic: panic in a,然后報(bào)錯(cuò),終止程序。

func main() {
	println("start main")
	a()
	println("end main")
}

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

執(zhí)行結(jié)果如下:

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都沒(méi)有被輸出。

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

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

func main() {
    G()
    // 下面的代碼會(huì)執(zhí)行
    ...CODE IN MAIN...
}
func G(){
    defer func (){
        if str := recover(); str != nil {
            fmt.Println(str)
        }
    }()
    ...CODE IN G()...
    
    // F()的調(diào)用必須在defer關(guān)鍵字之后
    F()
    // 該函數(shù)內(nèi)下面的代碼不會(huì)執(zhí)行
    ...CODE IN G()...
}
func F() {
    ...CODE1...
    panic("error found")
    // 下面的代碼不會(huì)執(zhí)行
    ...CODE IN F()...
}

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

func main() {
	println("start main")
	a()
	println("end main")
}

func a() {
	println("start a")
	panic("panic in a")

    // 直接放在panic后是錯(cuò)誤的
    panic_str := recover()
    println(panic_str)

	println("end a")
}

之所以錯(cuò)誤,是因?yàn)閜anic()一出現(xiàn)就直接退出函數(shù)a()和main()了。要想recover()真正捕獲panic(),需要將recover()放在defer的推遲對(duì)象中,且defer的定義必須在panic()發(fā)生之前。

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

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")
}

以下是輸出結(jié)果:

start main
start b
start a
panic in a
end main

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

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

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

更多關(guān)于 Go基礎(chǔ)教程系列之defer、panic和recover詳解 請(qǐng)查看下面的相關(guān)鏈接

相關(guān)文章

  • golang優(yōu)先級(jí)隊(duì)列的實(shí)現(xiàn)全過(guò)程

    golang優(yōu)先級(jí)隊(duì)列的實(shí)現(xiàn)全過(guò)程

    優(yōu)先級(jí)隊(duì)列是一種特殊隊(duì)列,下面這篇文章主要給大家介紹了關(guān)于golang優(yōu)先級(jí)隊(duì)列的實(shí)現(xiàn)全過(guò)程,文中通過(guò)示例代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2022-12-12
  • 如何在VScode 中編譯多個(gè)Go文件

    如何在VScode 中編譯多個(gè)Go文件

    這篇文章主要介紹了VScode 中編譯多個(gè)Go文件的實(shí)現(xiàn)方法,本文通過(guò)實(shí)例圖文并茂的形式給大家介紹的非常詳細(xì),需要的朋友可以參考下
    2021-08-08
  • golang 中獲取字符串個(gè)數(shù)的方法

    golang 中獲取字符串個(gè)數(shù)的方法

    這篇文章主要介紹了golang 中獲取字符串個(gè)數(shù) ,本文給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2019-08-08
  • golang 切片截取參數(shù)方法詳解

    golang 切片截取參數(shù)方法詳解

    這篇文章主要介紹了golang 切片截取參數(shù)方法詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2021-01-01
  • Golang使用cobra實(shí)現(xiàn)命令行程序的示例代碼

    Golang使用cobra實(shí)現(xiàn)命令行程序的示例代碼

    Cobra 是 Go 語(yǔ)言中一個(gè)強(qiáng)大的命令行應(yīng)用庫(kù),它提供了創(chuàng)建命令行工具所需的基本結(jié)構(gòu)和功能,被許多開(kāi)發(fā)者用于構(gòu)建各種命令行工具和應(yīng)用程序,本文將給大家介紹Golang使用cobra實(shí)現(xiàn)命令行程序,文中通過(guò)代碼示例介紹的非常詳細(xì),需要的朋友可以參考下
    2024-02-02
  • Go語(yǔ)言中調(diào)用外部命令的方法總結(jié)

    Go語(yǔ)言中調(diào)用外部命令的方法總結(jié)

    在工作中,我們時(shí)不時(shí)地會(huì)需要在Go中調(diào)用外部命令。本文為大家總結(jié)了Go語(yǔ)言中調(diào)用外部命令的幾種姿勢(shì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下
    2022-11-11
  • Go?web中cookie值安全securecookie庫(kù)使用原理

    Go?web中cookie值安全securecookie庫(kù)使用原理

    這篇文章主要為大家介紹了Go?web中cookie值安全securecookie庫(kù)使用及實(shí)現(xiàn)原理詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-11-11
  • golang?select?機(jī)制和超時(shí)問(wèn)題

    golang?select?機(jī)制和超時(shí)問(wèn)題

    golang 中的協(xié)程使用非常方便,但是協(xié)程什么時(shí)候結(jié)束是一個(gè)控制問(wèn)題,可以用 select 配合使用,這篇文章主要介紹了golang?select?機(jī)制和超時(shí)問(wèn)題,需要的朋友可以參考下
    2022-06-06
  • 最新評(píng)論