Go語言函數的延遲調用(Deferred Code)詳解
先解釋一下這篇Blog延期的原因,本來已經準備好瞭全部內容,但是當我重新回顧實例三的時候,發現自己還是存在認知不足的地方,於是為瞭準確表述,查閱瞭大量的資料,重新編寫瞭第三部分,導致延期。感謝持續關註本筆記更新的朋友,後期我將逐步通過3-5分鐘視頻方式為大傢對筆記內容進行講解,幫助更多的朋友能夠快速掌握Go語言的基礎。
本節將介紹Go語言函數和方法中的延遲調用,正如名稱一樣,這部分定義不會立即執行,一般會在函數返回前再被調用,我們通過下面的幾個示例來瞭解一下延遲調用的使用場景。
基本功能
在以下這段代碼中,我們操作一個文件,無論成功與否都需要關閉文件句柄。這裡在三處不同的位置都調用瞭file.Close()方法,代碼顯得非常冗餘。
func ReadWrite() bool { file.Open("file") // Do your thing if failureX { file.Close() return false } if failureY { file.Close() return false } file.Close() return true }
我們利用延遲調用來優化代碼。定義後的defer代碼,會在return之前返回,讓代碼顯得更加緊湊,且可讀性變強,對上面的代碼改造如下:
func ReadWrite() bool { file.Open("filename") // Define a defer code here defer file.Close() // Do your thing if failureX { return false } if failureY { return false } return true }
示例一:延遲調用執行順序
我們通過這個示例來看一下延遲調用與正常代碼之間的執行順序
package main import "fmt" func TestDefer(x int) { defer fmt.Println("Defer code called") switch x { case 1: fmt.Println("Case 1 triggered!") return case 10: fmt.Println("Case 10 triggered!") return default: fmt.Println("Case default triggered!") return } } func main() { TestDefer(100) TestDefer(1) TestDefer(10) }
先簡單分析一下代碼邏輯:
- 首先定義瞭一個公共的TestDefer函數,這個函數接受一個整型的參數
- 函數體內定義瞭defer部分,會輸出一句Defer code called
- switch case會根據輸入的整型參數,輸出相應的trigger語句
- 按照上面對延遲調用的分析,每次滿足case語句後,才會輸出Defer code called
從輸出中,我們可以觀察到如下現象:
- 首次執行,default條件滿足,Case default triggered先輸出,再輸出defer內容
- 第二次調用,1條件滿足,最後輸出defer內容
- 第三次調用,10條件滿足,最後輸出defer內容
從這個實例中,我們很明顯觀察到,defer語句是在return之前執行
Case default triggered!
Defer code called
Case 1 triggered!
Defer code called
Case 10 triggered!
Defer code called
示例二:多defer使用方法
package main import "fmt" func TestDefer(x int) { defer fmt.Println("1st defined Defer code called") defer fmt.Println("2nd defined Defer code called") defer fmt.Println("3rd defined Defer code called") switch x { case 1: fmt.Println("Case 1 triggered!") return case 10: fmt.Println("Case 10 triggered!") return default: fmt.Println("Case default triggered!") return } } func main() { TestDefer(100) }
仍然是相同的例子,但是在TestDefer中我們定義瞭三個defer輸出,根據LIFO原則,輸出的順序是3rd->2nd->1st,根據最後的結果,也是逆向向上執行defer輸出。
Case default triggered!
3rd defined Defer code called
2nd defined Defer code called
1st defined Defer code called
實例三:defer與局部變量、返回值的關系
就在整理這篇筆記的時候,發現瞭自己的認知誤區,主要是本節實例三中發現的,先來看一下英文的描述:
A defer statement pushes a function call onto a list. The list of saved calls is executed after the surrounding function returns. Defer is commonly used to simplify functions that perform various clean-up actions.
對於上面的這段話的理解:
defer定義的函數會被放入list中
存儲的defer函數會在周邊函數返回後執行
defer一般用於環境清理
原則一:defer函數的參數值,取決於defer函數調用時變量的值
package main import "fmt" func a() int { i := 0 fmt.Printf("func i = %v\n", i) defer fmt.Printf("defer i = %v\n", i) i++ fmt.Printf("func i = %v\n", i) defer fmt.Printf("defer after i++ = %v\n", i) return i } func main() { i := a() fmt.Printf("main i = %v\n", i) }
下面是代碼執行輸出,我們來一起分析一下:
- 在函數a中,定義瞭局部變量i
- 在函數執行過程中進行瞭自增操作i++
- 分別在i++前後,對i值進行瞭輸出,也就是我們下面輸出結果前兩行,與預期一致
- 分別在i++前後,定義兩個defer語句,都是用fmt輸出i的值,輸出的順序與示例二的邏輯一致,先輸出的是defer after,再輸出defer
- 根據原則一,在defer after的輸出中,由於i++完成自增,所以當時i的值已經變為瞭1,所以輸出為1
- 同樣是根據原則一,在defer的輸出中,i並沒有進行自增,所以在當時情況下,i的值仍然為0,所以輸出為0
- 最後返回的i值為1,主函數中輸出i的值為1
func i = 0 func i = 1 defer after i++ = 1 defer i = 0 main i = 1
原則二:defer可以讀取或修改顯示定義的返回值
package main import "fmt" func a() (i int) { fmt.Printf("func initial i = %v\n", i) defer func() { fmt.Printf("defer func initial i++ = %v\n", i) i++ fmt.Printf("defer func after i++ = %v\n", i) }() fmt.Printf("func before return i = %v\n", i) return 10 } func main() { i := a() fmt.Printf("main i = %v\n", i) }
雖然在a()函數內,顯示的返回瞭10,但是main函數中得到的結果是defer函數自增後的結果,我們來分析一下代碼:
在a函數定義時,我們顯示的定義瞭返回變量i和類型int
在剛剛進入函數時,i的初始化值位0,返回前也是0
在最後的return時,直接返回瞭10
接著我們再來看defer函數執行情況,剛剛進入defer函數時,返回值i得到的值正是剛才返回的10
而在自增後,i的值變成瞭11
最後我們在主函數中,獲得的返回值也是11,印證瞭我們原則中的defer函數對於返回值的讀取和修改
func initial i = 0 func before return i = 0 defer func initial i++ = 10 defer func after i++ = 11 main i = 11
到此這篇關於Go語言函數的延遲調用(Deferred Code)詳解的文章就介紹到這瞭,更多相關Go 函數延遲調用內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!