在 Go 語言中使用defer
關鍵字可以將程式碼延遲到函數結束之前執行。在開發中,我們經常使用defer
關鍵字完成善後工作,例如關閉開啟的檔案描述符、關閉連線以及釋放資源等。
func demo0() { fileName := "./test.txt" f, _ := os.OpenFile(fileName, os.O_RDONLY, 0) defer f.Close() contents, _ := ioutil.ReadAll(f) fmt.Println(string(contents))}
defer
關鍵字一般緊跟在開啟資源程式碼的後面,防止後續忘記釋放資源,defer 宣告的程式碼實際上要等到函數結束之前才會執行。 defer 雖然簡單易用,但如果忽略了它的特性,就會在開發中面臨困惑。於是,我總結了 defer 的五大特性,透過 8 個demo逐步介紹 defer 的特性。
使用多個 defer 關鍵字時,先宣告的 defer 語句後被呼叫。類似「堆疊」先進後出的特性,defer 的這項特性也很好理解,先被開啟的資源,可能會被後續程式碼依賴,所以要後釋放才安全。
func demo1() { for i := 0; i < 5; i++ { defer fmt.Println("defer:", i) }}// defer: 4// defer: 3// defer: 2// defer: 1// defer: 0
運行demo2 ,從結果可以看出,第一個匿名函數和第二個匿名函數的defer 執行順序沒有關係。
defer 作用域僅為目前函數,在目前函數最後執行,所以不同函數下擁有不同的 defer 堆疊。
func demo2() { func() { defer fmt.Println(1) defer fmt.Println(2) }() fmt.Println("=== 新生代农民工啊 ===") func() { defer fmt.Println("a") defer fmt.Println("b") }()}// 2// 1// === 新生代农民工啊 ===// b// a
運行demo3_1 ,根據結果,我們可以得出:defer 在宣告時,就已經確認了形參n的值,而不是在執行時確認的;所以,後續變數num 無論如何改變都不影響defer 的輸出結果。
func demo3_1() { num := 0 defer func(n int) { fmt.Println("defer:", n) }(num) // 等同 defer fmt.Println("defer:", num) for i := 0; i < 10; i++ { num++ } fmt.Println(num)}//10//defer: 0
運行 demo3_2,為什麼這裡 defer 的最終輸出的結果會和變數 num 相同?因為這裡使用的是指標。
defer宣告時,已經確認了形參p指標的指向位址,指向變數 num;後續變數 num 改變。所以在 defer執行時,輸出的是p指標指向的變數num的目前值。
func demo3_2() { num := 0 p := &num defer func(p *int) { fmt.Println("defer:", *p) }(p) for i := 0; i < 10; i++ { num++ } fmt.Println(*p)}//10//defer: 10
再看一下demo3_3,defer 列印的變數並沒有透過函數參數傳入,在defer執行時,才取得的」全域變數」num,所以defer 輸出結果與變數num一致。
func demo3_3() { num := 0 defer func() { fmt.Println("defer:", num) }() for i := 0; i < 10; i++ { num++ } fmt.Println(num)}//10//defer: 10
運行demo4_1,可以發現defer、return 都是在函數最後執行,但return 先於defer 執行;
func demo4_1() (int, error) { defer fmt.Println("defer") return fmt.Println("return")}// return// defer
這一點從輸出結果上顯而易見,但當return、defer 的執行順序和**函數傳回值**
「相遇」時,又將會產生許多複雜的場景。
在 demo4_2 中,函數使用命名傳回值
,最終輸出結果為7。其中經歷了這幾個過程:
(首先)變數num 作為回傳值,初始值為0;
(然後)return 時,變數num 作為回傳值重新賦值為2;
func demo4_2() (num int) { num = 10 defer func() { num += 5 }() return 2}// 7
在 demo4_3 中,函數使用
匿名傳回值,最終結果輸出為2。其中所經歷的過程是這樣的:
func demo4_3() int { num := 10 defer func() { num += 5 }() return 2}// 2
func demo5_1() { defer fmt.Println(1) defer fmt.Println(2) defer fmt.Println(3) panic("没点赞异常") // 触发defer出栈执行 defer fmt.Println(4) // 得不到执行}
func demo5_2() { defer func() { if err := recover(); err != nil { fmt.Println(err, "问题不大") } }() panic("没点赞异常") // 触发defer出栈执行 // ...}
以上是用八個demo來搞懂Go語言defer的五大特性的詳細內容。更多資訊請關注PHP中文網其他相關文章!