go?defer避坑指南之拆解延遲語句
對齊基本概念
Go語言的延遲語句defer有哪些特點(diǎn)?通常在什么情況下使用?
Go語言的延遲語句(defer statement)具有以下特點(diǎn):
- 延遲執(zhí)行:延遲語句會在包含它的函數(shù)執(zhí)行結(jié)束前執(zhí)行,無論函數(shù)是正常返回還是發(fā)生異常。
- 后進(jìn)先出:如果有多個(gè)延遲語句,它們會按照后進(jìn)先出(LIFO)的順序執(zhí)行。也就是說,最后一個(gè)延遲語句會最先執(zhí)行,而第一個(gè)延遲語句會最后執(zhí)行。
通常情況下,延遲語句在以下情況下使用:
- 資源釋放:延遲語句可以用于在函數(shù)返回前釋放打開的文件、關(guān)閉數(shù)據(jù)庫連接、釋放鎖等資源,以確保資源的正確釋放,避免資源泄漏。
- 錯(cuò)誤處理:延遲語句可以用于處理函數(shù)執(zhí)行過程中可能發(fā)生的錯(cuò)誤。通過在函數(shù)開始時(shí)設(shè)置延遲語句,在函數(shù)返回前檢查錯(cuò)誤并進(jìn)行相應(yīng)的處理,可以簡化錯(cuò)誤處理的邏輯。
- 日志記錄:延遲語句可以用于在函數(shù)返回前記錄日志或執(zhí)行其他的調(diào)試操作,以便在函數(shù)執(zhí)行過程中收集相關(guān)的信息。
延遲語句的使用可以提高代碼的可讀性和可維護(hù)性,同時(shí)確保資源的釋放和清理操作按照逆序進(jìn)行。它是Go語言中一種常用的編程技巧,用于處理資源管理和錯(cuò)誤處理等場景。
避坑之旅
實(shí)際開發(fā)中defer
的使用并不像前面介紹的這么簡單,defer
用不好,會陷入泥潭。
下面我從兩個(gè)角度帶大家避坑:
- 首先拆解一下延遲語句的執(zhí)行,注意Go語言的return語句不是原子性的;
- 另外重點(diǎn)和大家分享一下defer語句后面接匿名函數(shù)和非匿名函數(shù)的區(qū)別。
拆解延遲語句
避免陷入泥潭的關(guān)鍵是必須深刻理解下面這條語句:
return xxx
上面這條語句經(jīng)過編譯之后,實(shí)際上生成了三條指令:
1)返回值 =xxx。
2)調(diào)用 defer 函數(shù)。
3)空的 return。
第1和第 3 步是return語句生成的指令,也就是說return并不是一條原子指令;
第2步是 defer 定義的語句,這里可能會操作返回值,從而影響最終結(jié)果。
下面來看兩個(gè)例子,試著將return 語句和 defer語句拆解到正確的順序。
第一個(gè)例子
func f()(r int){ t:=5 defer func(){ t=t+5 }() return t }
拆解后:
func f()(r int){ t:=5 //1,賦值指令 r=t // 2.defer 被插入到斌值與返回之間執(zhí)行,這個(gè)例子中返回值r沒被修改過 func(){ t=t+5 }() //3.空的 return 指令 return }
這里第二步實(shí)際上并沒有操作返回值r,因此,main函數(shù)中調(diào)用f()得到5。
第二個(gè)例子
func f()(r int){ defer func(r int){ r=r+5 }(r) return 1 }
拆解后:
func f() (r int) { //1.賦值 r=1 //2.這里改的r是之前傳進(jìn)去的r,不會改變要返回的那個(gè)r值 func(r int) { r=r+5 }(r) // 3. 空的 return return }
第二步,改變的是傳值進(jìn)去的r,是形參的一個(gè)復(fù)制值,不會影響實(shí)參r。因此,main函數(shù)中需要調(diào)用f()得到1。
defer匿名函數(shù)
在Go語言中,使用匿名函數(shù)作為defer的參數(shù)時(shí),可以理解為:defer語句中的匿名函數(shù)在包裹該defer語句的函數(shù)返回后才執(zhí)行。這是因?yàn)閐efer語句的執(zhí)行時(shí)機(jī)是在包裹函數(shù)即將返回之前,但在實(shí)際返回之前。
為什么不是在return語句之前執(zhí)行呢?這是因?yàn)閐efer語句的設(shè)計(jì)初衷是為了在函數(shù)返回之前執(zhí)行一些清理操作,例如關(guān)閉文件、釋放資源等。將defer語句放在return語句之后,可以確保在函數(shù)返回之前執(zhí)行這些清理操作,保證函數(shù)的執(zhí)行完整性和資源的正確釋放。
在使用匿名函數(shù)和非匿名函數(shù)作為defer的參數(shù)時(shí),主要區(qū)別在于對函數(shù)參數(shù)的傳遞和作用域的影響:
- 匿名函數(shù)作為defer的參數(shù):匿名函數(shù)可以直接在defer語句中定義,可以訪問外部函數(shù)的變量,并且在執(zhí)行時(shí)會使用當(dāng)前的變量值。這種方式可以方便地在defer語句中使用外部變量,但需要注意變量的值在執(zhí)行時(shí)可能已經(jīng)發(fā)生了改變。
- 非匿名函數(shù)作為defer的參數(shù):非匿名函數(shù)需要先定義好,然后作為defer的參數(shù)傳遞。在執(zhí)行時(shí),會使用函數(shù)的當(dāng)前參數(shù)值。這種方式可以在defer語句中使用已定義的函數(shù),但需要注意函數(shù)參數(shù)的傳遞和作用域。
產(chǎn)生這種區(qū)別的原因是,匿名函數(shù)和非匿名函數(shù)在定義和作用域上的差異。匿名函數(shù)可以直接在defer語句中定義,可以訪問外部函數(shù)的變量,而非匿名函數(shù)需要先定義好,然后作為參數(shù)傳遞。這種設(shè)計(jì)靈活性使得開發(fā)者可以根據(jù)具體的需求選擇合適的方式來使用defer語句。
舉例來說
當(dāng)使用匿名函數(shù)作為defer的參數(shù)時(shí),可以在defer語句中直接定義匿名函數(shù),并訪問外部變量。
以下是一個(gè)示例代碼:
package main import "fmt" func main() { x := 10 defer func() { fmt.Println("Deferred anonymous function:", x) }() x = 20 fmt.Println("Before return:", x) }
在上述示例中,匿名函數(shù)作為defer的參數(shù),可以訪問外部變量x
。 在函數(shù)返回之前,defer語句中的匿名函數(shù)會執(zhí)行,并打印出x
的值。
輸出結(jié)果如下:
當(dāng)使用非匿名函數(shù)作為defer的參數(shù)時(shí),需要先定義好函數(shù),然后將函數(shù)名作為defer的參數(shù)傳遞。
以下是一個(gè)示例代碼:
package main import "fmt" func main() { x := 10 defer printX(x) x = 20 fmt.Println("Before return:", x) } func printX(x int) { fmt.Println("Deferred function:", x) }
在上述示例中,printX
函數(shù)作為defer的參數(shù)傳遞,函數(shù)定義在main函數(shù)之后。
在函數(shù)返回之前,defer語句中的printX
函數(shù)會執(zhí)行,并打印出傳遞的參數(shù)x
的值。輸出結(jié)果如下:
總結(jié)一下
通過以上示例,我們可以明確體現(xiàn)出使用匿名函數(shù)和非匿名函數(shù)作為defer的參數(shù)的區(qū)別。
匿名函數(shù)可以直接在defer語句中定義,并訪問外部變量,而非匿名函數(shù)需要先定義好函數(shù),然后將函數(shù)名作為參數(shù)傳遞。
通過前面帶著大家拆解了defer的語句的執(zhí)行,相信大家可以更好的理解了。
到此這篇關(guān)于go defer避坑指南之拆解延遲語句的文章就介紹到這了,更多相關(guān)go defer內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
使用Gin框架搭建一個(gè)Go Web應(yīng)用程序的方法詳解
在本文中,我們將要實(shí)現(xiàn)一個(gè)簡單的 Web 應(yīng)用程序,通過 Gin 框架來搭建,主要支持用戶注冊和登錄,用戶可以通過注冊賬戶的方式創(chuàng)建自己的賬號,并通過登錄功能進(jìn)行身份驗(yàn)證,感興趣的同學(xué)跟著小編一起來看看吧2023-08-08Go?Excelize?API源碼閱讀SetSheetViewOptions示例解析
這篇文章主要為大家介紹了Go-Excelize?API源碼閱讀SetSheetViewOptions示例解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-08-08golang跳轉(zhuǎn)語句goto,break,continue的使用及區(qū)別說明
這篇文章主要介紹了golang跳轉(zhuǎn)語句goto,break,continue的使用及區(qū)別說明,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-12-12Go語言遍歷map實(shí)現(xiàn)(訪問map中的每一個(gè)鍵值對)
這篇文章主要介紹了Go語言遍歷map實(shí)現(xiàn)(訪問map中的每一個(gè)鍵值對),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-01-01Go語言使用defer+recover解決panic導(dǎo)致程序崩潰的問題
如果協(xié)程出現(xiàn)了panic,就會造成程序的崩潰,這時(shí)可以在goroutine中使用recover來捕獲panic,進(jìn)行處理,本文就詳細(xì)的介紹一下,感興趣的可以了解一下2021-09-09Go語言中配置實(shí)現(xiàn)Logger日志的功能詳解
當(dāng)我們正式開發(fā)go程序的時(shí)候,就會發(fā)現(xiàn)記錄程序日志已經(jīng)不是fmt.print這么簡單了,所以我們需要專門的去存儲日志文件,這篇文章主要介紹了在Go語言中配置實(shí)現(xiàn)Logger日志的功能,感興趣的同學(xué)可以參考下文2023-05-05go select編譯期的優(yōu)化處理邏輯使用場景分析
select 是 Go 中的一個(gè)控制結(jié)構(gòu),類似于用于通信的 switch 語句。每個(gè) case 必須是一個(gè)通信操作,要么是發(fā)送要么是接收。接下來通過本文給大家介紹go select編譯期的優(yōu)化處理邏輯使用場景分析,感興趣的朋友一起看看吧2021-06-06