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寫釋放資源的語句)
- 打開文件,關閉文件
- 加鎖,解鎖
- 建立連接,釋放連接