網站首頁 編程語言 正文
測試題
defer有一些規則,如果不了解,代碼實現的最終結果會與預期不一致。對于這些規則,你了解嗎?
這是關于defer使用的代碼,可以先考慮一下返回值。
package main import ( "fmt" ) /** * @Author: Jason Pang * @Description: 快照 */ func deferFuncParameter1() { var aInt = 1 defer fmt.Println(aInt) aInt = 2 return } /** * @Author: Jason Pang * @Description: 快照 */ func deferFuncParameter2() { var aInt = 1 defer func(t int) { fmt.Println(t) }(aInt) aInt = 2 return } /** * @Author: Jason Pang * @Description: 動態 */ func deferFuncParameter3() { var aInt = 1 defer func() { fmt.Println(aInt) }() aInt = 2 return } /** * @Author: Jason Pang * @Description: 影響返回值 * @return ret */ func deferFuncReturn1() (ret int) { ret = 10 defer func() { ret++ fmt.Println("-----", ret) }() return 2 } /** * @Author: Jason Pang * @Description: 不影響返回值 * @return ret */ func deferFuncReturn2() (ret int) { ret = 10 defer func(ret int) { ret++ fmt.Println("-----", ret) }(ret) return 2 } /** * @Author: Jason Pang * @Description: defer順序 */ func deferFuncSeq1() { var aInt = 1 defer fmt.Println(aInt) aInt = 2 defer fmt.Println(aInt) return } func main() { fmt.Println("快照") deferFuncParameter1() deferFuncParameter2() deferFuncParameter3() fmt.Println("返回值") fmt.Println(deferFuncReturn1()) fmt.Println(deferFuncReturn2()) fmt.Println("執行順序") deferFuncSeq1() }
正確輸出為:
? myproject go run main.go
快照
1
1
2
返回值
----- 3
3
----- 11
2
執行順序
2
1
分析
defer有幾條重要規則,上面的結果都能從這些規則中找到答案。
規則一當defer被聲明時,其參數就會被實時解析
當defer被聲明的時候,如果直接使用了參數,此時的參數就會使用快照值,在整個生命周期內不會變化。如deferFuncParameter1、deferFuncParameter2,雖然aInt在defer聲明后被變更,但defer里的值不會再變了。
func deferFuncParameter1() { var aInt = 1 defer fmt.Println(aInt) aInt = 2 return } func deferFuncParameter2() { var aInt = 1 defer func(t int) { fmt.Println(t) }(aInt) aInt = 2 return }
與之相反的是deferFuncParameter3,隨aInt的變化而變化。
func deferFuncParameter3() { var aInt = 1 defer func() { fmt.Println(aInt) }() aInt = 2 return }
規則二 defer可能操作主函數的具名返回值
defer有可能更改函數的返回值,這是最容易導致錯誤的地方。
關鍵字_return_不是一個原子操作,實際上_return_只代理匯編指令_ret_,即將跳轉程序執行。比如語句 return i ,實際上分兩步進行,即將i值存入棧中作為返回值,然后執行跳轉,而defer的執行時機正是跳轉前,所以說defer執行時還是有機會操作返回值的。return i的執行過程如下所示:
result = i?
執行defer
return
所以基于這個規則,對于deferFuncReturn1,
func deferFuncReturn1() (ret int) { ret = 10 defer func() { ret++ fmt.Println("-----", ret) }() return 2 }
執行過程為:
ret = 2
ret++
fmt.Println("-----", ret)
return
所以最終ret的值為3。
對于deferFuncReturn2,因為defer聲明的時候直接使用了參數,所以使用的是快照,不會影響ret的返回值。
規則三 延遲函數執行按后進先出順序執行
即先出現的 defer最后執行
這個規則大家都很熟悉,defer按照棧的順序執行。
坑實例
舉一個錯誤使用defer的實例。在go中使用事務時,有一種推薦寫法:將Rollback放到defer中,通過判斷函數是否有報錯或者panic,來判斷是否要回滾。
func Update() (resp *baseinfo.Resp, err error) { //開啟事務 panicked := true tx, err := db.TXBegin() if err != nil { return resp, nil } defer func() { if panicked || err != nil { tx.Rollback() } }() //更新 err = h.update(shopId, tx) if err != nil {//失敗返回 return resp, nil } panicked = false err = tx.Commit().Error if err != nil { //失敗返回 return resp, nil } return }
判斷回滾的err正是函數的具名返回值,在有報錯的情況下,返回值被賦值為nil,這意味如果有失敗,Rollback也不會被執行。
之所以不將err直接返回,而是使用nil,是因為框架設計的問題,業務錯誤通過resp返回,如果直接返回err,框架會認為是RPC錯誤。
原文鏈接:https://juejin.cn/post/7068854815589138439
相關推薦
- 2022-12-10 Input系統之InputReader處理合成事件詳解_Android
- 2022-06-06 typescript中的泛型(genericParadigm)、interface、extends、
- 2022-09-24 opencv實現圖像傾斜校正_C 語言
- 2022-04-01 k8s no matches for kind “Ingress“ in version “exte
- 2022-04-27 Oracle?觸發器trigger使用案例_oracle
- 2022-05-20 C#隊列的簡單使用_C#教程
- 2022-04-04 前端小技巧:關閉瀏覽器時觸發事件
- 2023-04-26 C語言函數調用基礎應用詳解_C 語言
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支