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寫釋放資源的語句)
- 開啟檔案,關閉檔案
- 加鎖,解鎖
- 建立連線,釋放連線