1. 程式人生 > >GO學習筆記——defer呼叫(21)

GO學習筆記——defer呼叫(21)

defer呼叫也是一種流程控制語句,經常用來呼叫一些資源處理函式。

defer語句確保被執行的語句具有下面的呼叫時機

defer呼叫必須出現在函式內,並且在該函式返回之前才回去執行defer呼叫的函式

 給一個示例來看一下

func testdefer(){
	defer fmt.Println("calling last")
	fmt.Println("calling first")
}

func main() {
	testdefer()
}

執行結果

calling first
calling last

可見,defer後面的函式呼叫在程式最後才去執行。

那如果有多個defer語句怎麼辦呢?defer執行順序其實相當於一個defer棧,採用的是先進後出的原則

func testdefer(){
	defer fmt.Println("calling last")
	defer fmt.Println("calling next")
	fmt.Println("calling first")
}

func main() {
	testdefer()
}

執行結果

calling first
calling next
calling last

延遲方法

不僅是函式,方法也可以作為defer的呼叫

type student struct {
	name string
	age int
}

func (s student) print() {
	fmt.Println(s.name)
}

func testdefer(){
	s := student{"pigff",21}
	defer s.print()
	fmt.Println(s.age)	//會先執行這句,列印21
}

func main() {
	testdefer()
}

執行結果

21
pigff

defer的實參取值

defer呼叫的函式內的引數值,並不是在真正呼叫defer時確定的,而是在執行到defer時就確定了。

還是上面的例子

type student struct {
	name string
	age int
}

func (s student) print() {
	fmt.Println(s.name)
}

func testdefer(){
	s := student{"pigff",21}
	defer s.print()		//執行到defer語句時,name還是pigff,所以在最好defer呼叫時,也是這個
	s.name = "Kobe"	//這裡改變名字,下一句列印的是Kobe
	s.print()
}

func main() {
	testdefer()
}

執行結果

Kobe
pigff

這裡在執行到defer語句的時候,s.name還是pigff,所以在最後defer呼叫時s.name依舊還是pigff,並不是在程式後面改變的Kobe。

再換一個簡單的例子

func  print(a int) {
	fmt.Println(a)
}

func testdefer(){
	a := 5
	defer print(a)    //在執行到這一句的時候a還是5
	a = 10
	print(10)
}

func main() {
	testdefer()
}

執行結果

10
5

因此,需要注意defer的實參取值,是在執行時確定的,而不是在呼叫時。

關於defer的實際使用場景

其實在學C++的智慧指標的時候,學到過類似的概念。

當我們申請了資源的時候,比如鎖,資料庫連線,加了鎖我們得解鎖,建立了連線我們得關閉。假設我們確實寫了釋放資源的語句,但是如果程式突然在執行釋放語句之前return了,比如說報出panic了導致程式中斷等,這個時候釋放資源的語句就沒有被呼叫了,那麼我們申請的資源就會沒有釋放,長此以往就會導致資源洩漏等很多問題。

雖說在程式執行結束時資源都會全部釋放,但是一般伺服器程式是不會經常關閉的。

所以defer呼叫保證呼叫的函式肯定會在函式結束之前被執行,即使程式報了panic中斷,defer呼叫依舊會被執行。

這就是defer呼叫的好處,它經常與釋放資源等操作配套執行,確保資源被釋放。

常用的場景如下(都是配套的,我們在申請資源時就應該使用defer寫釋放資源的語句)

  • 開啟檔案,關閉檔案
  • 加鎖,解鎖
  • 建立連線,釋放連線