Go語言函數(shù)的延遲調(diào)用(Deferred Code)詳解
先解釋一下這篇Blog延期的原因,本來已經(jīng)準(zhǔn)備好了全部內(nèi)容,但是當(dāng)我重新回顧實(shí)例三的時(shí)候,發(fā)現(xiàn)自己還是存在認(rèn)知不足的地方,于是為了準(zhǔn)確表述,查閱了大量的資料,重新編寫了第三部分,導(dǎo)致延期。感謝持續(xù)關(guān)注本筆記更新的朋友,后期我將逐步通過3-5分鐘視頻方式為大家對(duì)筆記內(nèi)容進(jìn)行講解,幫助更多的朋友能夠快速掌握Go語言的基礎(chǔ)。
本節(jié)將介紹Go語言函數(shù)和方法中的延遲調(diào)用,正如名稱一樣,這部分定義不會(huì)立即執(zhí)行,一般會(huì)在函數(shù)返回前再被調(diào)用,我們通過下面的幾個(gè)示例來了解一下延遲調(diào)用的使用場(chǎng)景。
基本功能
在以下這段代碼中,我們操作一個(gè)文件,無論成功與否都需要關(guān)閉文件句柄。這里在三處不同的位置都調(diào)用了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 }
我們利用延遲調(diào)用來優(yōu)化代碼。定義后的defer代碼,會(huì)在return之前返回,讓代碼顯得更加緊湊,且可讀性變強(qiáng),對(duì)上面的代碼改造如下:
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 }
示例一:延遲調(diào)用執(zhí)行順序
我們通過這個(gè)示例來看一下延遲調(diào)用與正常代碼之間的執(zhí)行順序
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) }
先簡單分析一下代碼邏輯:
- 首先定義了一個(gè)公共的TestDefer函數(shù),這個(gè)函數(shù)接受一個(gè)整型的參數(shù)
- 函數(shù)體內(nèi)定義了defer部分,會(huì)輸出一句Defer code called
- switch case會(huì)根據(jù)輸入的整型參數(shù),輸出相應(yīng)的trigger語句
- 按照上面對(duì)延遲調(diào)用的分析,每次滿足case語句后,才會(huì)輸出Defer code called
從輸出中,我們可以觀察到如下現(xiàn)象:
- 首次執(zhí)行,default條件滿足,Case default triggered先輸出,再輸出defer內(nèi)容
- 第二次調(diào)用,1條件滿足,最后輸出defer內(nèi)容
- 第三次調(diào)用,10條件滿足,最后輸出defer內(nèi)容
從這個(gè)實(shí)例中,我們很明顯觀察到,defer語句是在return之前執(zhí)行
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中我們定義了三個(gè)defer輸出,根據(jù)LIFO原則,輸出的順序是3rd->2nd->1st,根據(jù)最后的結(jié)果,也是逆向向上執(zhí)行defer輸出。
Case default triggered!
3rd defined Defer code called
2nd defined Defer code called
1st defined Defer code called
實(shí)例三:defer與局部變量、返回值的關(guān)系
就在整理這篇筆記的時(shí)候,發(fā)現(xiàn)了自己的認(rèn)知誤區(qū),主要是本節(jié)實(shí)例三中發(fā)現(xiàn)的,先來看一下英文的描述:
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.
對(duì)于上面的這段話的理解:
defer定義的函數(shù)會(huì)被放入list中
存儲(chǔ)的defer函數(shù)會(huì)在周邊函數(shù)返回后執(zhí)行
defer一般用于環(huán)境清理
原則一:defer函數(shù)的參數(shù)值,取決于defer函數(shù)調(diào)用時(shí)變量的值
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) }
下面是代碼執(zhí)行輸出,我們來一起分析一下:
- 在函數(shù)a中,定義了局部變量i
- 在函數(shù)執(zhí)行過程中進(jìn)行了自增操作i++
- 分別在i++前后,對(duì)i值進(jìn)行了輸出,也就是我們下面輸出結(jié)果前兩行,與預(yù)期一致
- 分別在i++前后,定義兩個(gè)defer語句,都是用fmt輸出i的值,輸出的順序與示例二的邏輯一致,先輸出的是defer after,再輸出defer
- 根據(jù)原則一,在defer after的輸出中,由于i++完成自增,所以當(dāng)時(shí)i的值已經(jīng)變?yōu)榱?,所以輸出為1
- 同樣是根據(jù)原則一,在defer的輸出中,i并沒有進(jìn)行自增,所以在當(dāng)時(shí)情況下,i的值仍然為0,所以輸出為0
- 最后返回的i值為1,主函數(shù)中輸出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()函數(shù)內(nèi),顯示的返回了10,但是main函數(shù)中得到的結(jié)果是defer函數(shù)自增后的結(jié)果,我們來分析一下代碼:
在a函數(shù)定義時(shí),我們顯示的定義了返回變量i和類型int
在剛剛進(jìn)入函數(shù)時(shí),i的初始化值位0,返回前也是0
在最后的return時(shí),直接返回了10
接著我們?cè)賮砜磀efer函數(shù)執(zhí)行情況,剛剛進(jìn)入defer函數(shù)時(shí),返回值i得到的值正是剛才返回的10
而在自增后,i的值變成了11
最后我們?cè)谥骱瘮?shù)中,獲得的返回值也是11,印證了我們?cè)瓌t中的defer函數(shù)對(duì)于返回值的讀取和修改
func initial i = 0 func before return i = 0 defer func initial i++ = 10 defer func after i++ = 11 main i = 11
到此這篇關(guān)于Go語言函數(shù)的延遲調(diào)用(Deferred Code)詳解的文章就介紹到這了,更多相關(guān)Go 函數(shù)延遲調(diào)用內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
golang爬蟲colly?發(fā)送post請(qǐng)求
本文主要介紹了golang爬蟲colly?發(fā)送post請(qǐng)求實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-07-07Golang?Fasthttp選擇使用slice而非map?存儲(chǔ)請(qǐng)求數(shù)據(jù)原理探索
本文將從簡單到復(fù)雜,逐步剖析為什么?Fasthttp?選擇使用?slice?而非?map,并通過代碼示例解釋這一選擇背后高性能的原因,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2024-02-02詳解Go如何基于現(xiàn)有的context創(chuàng)建新的context
在?Golang?中,context?包提供了創(chuàng)建和管理上下文的功能,那么在GO語言中如何基于現(xiàn)有的context創(chuàng)建新的context,下面小編就來和大家詳細(xì)聊聊2024-01-01go?singleflight緩存雪崩源碼分析與應(yīng)用
這篇文章主要為大家介紹了go?singleflight緩存雪崩源碼分析與應(yīng)用示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-09-09golang實(shí)現(xiàn)整型和字節(jié)數(shù)組之間的轉(zhuǎn)換操作
這篇文章主要介紹了golang實(shí)現(xiàn)整型和字節(jié)數(shù)組之間的轉(zhuǎn)換操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2020-12-12go語言區(qū)塊鏈學(xué)習(xí)調(diào)用以太坊
這篇文章主要為大家介紹了go語言區(qū)塊鏈學(xué)習(xí)如何調(diào)用以太坊的示例實(shí)現(xiàn)過程,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步2021-10-10golang 內(nèi)存對(duì)齊的實(shí)現(xiàn)
在代碼編譯階段,編譯器會(huì)對(duì)數(shù)據(jù)的存儲(chǔ)布局進(jìn)行對(duì)齊優(yōu)化,本文主要介紹了golang 內(nèi)存對(duì)齊的實(shí)現(xiàn),具有一定的參考價(jià)值,感興趣的可以了解一下2024-08-08