在 go 語言中使用 defer
關(guān)鍵字可以將代碼延遲到函數(shù)結(jié)束之前執(zhí)行。在開發(fā)中,我們經(jīng)常使用defer
關(guān)鍵字完成善后工作,如關(guān)閉打開的文件描述符、關(guān)閉連接以及釋放資源等。
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
關(guān)鍵字一般緊跟在打開資源代碼的后面,防止后續(xù)忘記釋放資源,defer 聲明的代碼實際上要等到函數(shù)結(jié)束之前才會被執(zhí)行。defer 雖然簡單易用,但如果忽略了它的特性,就會在開發(fā)中面臨困惑。于是,我總結(jié)了 defer 的五大特性,通過 8 個demo逐步介紹 defer 的特性。
使用多個 defer 關(guān)鍵字時,先被聲明的 defer 語句后被調(diào)用。類似于“?!?strong>先進后出的特性,defer 的這一特性也很好理解,先被打開的資源,可能會被后續(xù)代碼依賴,所以要后釋放才安全。
func demo1() { for i := 0; i < 5; i++ { defer fmt.Println("defer:", i) }}// defer: 4// defer: 3// defer: 2// defer: 1// defer: 0
運行 demo2 ,從結(jié)果中可以看出,第一個匿名函數(shù)和第二個匿名函數(shù)的 defer 執(zhí)行順序沒有關(guān)系。
defer 作用域僅為當前函數(shù),在當前函數(shù)最后執(zhí)行,所以不同函數(shù)下?lián)碛胁煌?defer 棧。
func demo2() { func() { defer fmt.Println(1) defer fmt.Println(2) }() fmt.Println("=== 新生代農(nóng)民工啊 ===") func() { defer fmt.Println("a") defer fmt.Println("b") }()}// 2// 1// === 新生代農(nóng)民工啊 ===// b// a
運行 demo3_1 ,根據(jù)結(jié)果,我們可以得出:defer 在聲明時,就已經(jīng)確認了形參n的值,而不是在執(zhí)行時確認的;所以,后續(xù)變量 num 無論如何改變都不影響 defer 的輸出結(jié)果。
立即學習“go語言免費學習筆記(深入)”;
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 的最終輸出的結(jié)果會和變量 num 相同?因為這里使用的是指針。
defer 聲明時,已經(jīng)確認了形參p指針的指向地址,指向變量 num;后續(xù)變量 num 發(fā)生改變。所以在 defer 執(zhí)行時,輸出的是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 打印的變量并沒有通過函數(shù)參數(shù)傳入,在defer執(zhí)行時,才獲取的”全局變量”num,所以 defer 輸出結(jié)果與變量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,可以發(fā)現(xiàn) defer、return 都是在函數(shù)最后執(zhí)行,但 return 先于 defer 執(zhí)行;
func demo4_1() (int, error) { defer fmt.Println("defer") return fmt.Println("return")}// return// defer
這一點從輸出結(jié)果上顯而易見,但當 return、defer 的執(zhí)行順序和**函數(shù)返回值**
“相遇”時,又將會產(chǎn)生許多復雜的場景。
在 demo4_2 中,函數(shù)使用命名返回值
,最終輸出結(jié)果為7。其中經(jīng)歷了這幾個過程:
(首先)變量 num 作為返回值,初始值為0;
(其次)隨后變量 num 被賦值為 10;
(然后)return 時,變量 num 作為返回值被重新賦值為 2;
(接著)defer 在 return 后執(zhí)行,拿到變量 num 進行修改,值為7;
(最后)變量 num 作為返回值,最終函數(shù)返回結(jié)果為7;
func demo4_2() (num int) { num = 10 defer func() { num += 5 }() return 2}// 7
再來看一個例子。
在 demo4_3 中,函數(shù)使用匿名返回值
,最終結(jié)果輸出為2。其中經(jīng)歷的過程是這樣的:
進入函數(shù),此時返回值變量并未創(chuàng)建;
創(chuàng)建變量 num,賦值為 10;
return 時創(chuàng)建函數(shù)返回值變量,并賦值為2;這個返回值變量你可以把它看成匿名變量,或者是變量 a、b、c、d……,但它就不是變量 num;
defer 時,無論怎樣修改變量 num,都與函數(shù)返回值無關(guān);
所以,最終的函數(shù)返回結(jié)果為2;
func demo4_3() int { num := 10 defer func() { num += 5 }() return 2}// 2
運行 demo5_1,可以看到當出現(xiàn) panic 時,會觸發(fā)已經(jīng)聲明的 defer 出棧執(zhí)行,隨后在再 panic,而在 panic 之后聲明的 defer 將得不到執(zhí)行。
func demo5_1() { defer fmt.Println(1) defer fmt.Println(2) defer fmt.Println(3) panic("沒點贊異常") // 觸發(fā)defer出棧執(zhí)行 defer fmt.Println(4) // 得不到執(zhí)行}
正是利用這個特性,在 defer 中可以通過 recover 捕獲 panic,防止程序崩潰。
func demo5_2() { defer func() { if err := recover(); err != nil { fmt.Println(err, "問題不大") } }() panic("沒點贊異常") // 觸發(fā)defer出棧執(zhí)行 // ...}
以上就是用八個demo搞懂Go語言defer的五大特性的詳細內(nèi)容,更多請關(guān)注php中文網(wǎng)其它相關(guān)文章!
每個人都需要一臺速度更快、更穩(wěn)定的 PC。隨著時間的推移,垃圾文件、舊注冊表數(shù)據(jù)和不必要的后臺進程會占用資源并降低性能。幸運的是,許多工具可以讓 Windows 保持平穩(wěn)運行。
微信掃碼
關(guān)注PHP中文網(wǎng)服務號
QQ掃碼
加入技術(shù)交流群
Copyright 2014-2025 http://ipnx.cn/ All Rights Reserved | php.cn | 湘ICP備2023035733號