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寫釋放資源的語句)

  • 打開文件,關閉文件
  • 加鎖,解鎖
  • 建立連接,釋放連接
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章