網站首頁 編程語言 正文
閉包與defer
1.閉包
閉包 : 一個函數與其相關的引用環境組合的一個實體,其實可以理解為面向對象中類中的屬性與方法。
如代碼塊中,函數function的返回值(匿名函數)與變量n就是1個閉包。
該匿名函數就相當于類中的方法 變量n相當于類中的屬性
// 無形參 返回值是該匿名函數 func function() func(int) int { var n int = 10 // 相當于類屬性 return func(x int) int { //匿名函數 x = x + n return x } } var f func(int) int = function() fmt.Println(f(1)) // 11 fmt.Println(f(2)) // 13
再舉幾個例子:
//示例1 func adder2(x int) func(int) int { return func(y int) int { x += y return x } } func main() { var f = adder2(10) fmt.Println(f(10)) //20 fmt.Println(f(20)) //40 fmt.Println(f(30)) //70 f1 := adder2(20) fmt.Println(f1(40)) //60 fmt.Println(f1(50)) //110 } //示例2 func makeSuffixFunc(suffix string) func(string) string { return func(name string) string { if !strings.HasSuffix(name, suffix) { return name + suffix } return name } } func main() { jpgFunc := makeSuffixFunc(".jpg") txtFunc := makeSuffixFunc(".txt") fmt.Println(jpgFunc("test")) //test.jpg fmt.Println(txtFunc("test")) //test.txt }
2.defer
1.defer 是 Go 語言提供的一種用于注冊延遲調用的機制,每一次 defer 都會把函數壓入棧中,當前函數返回前再把延遲函數取出并執行。
defer 定義的函數會先進入一個棧,函數 return 前,會按先進后出(FILO)的順序執行。也就是說最先被定義的 defer 語句最后執行。
2.defer 語句定義時,對 外部變量的引用 是有兩種方式的,分別是作為 函數參數 和作為 閉包引用。
- 作為 函數參數,則在 defer 定義時 就把值傳遞給 defer,并被 緩存 起來;
- 作為 閉包引用 的話,則會在 defer 函數真正調用時根據整個上下文確定當前的值。
下面就分別對這兩種情況舉例子。
情況一:
func trace(str string) string { fmt.Println("entering " + str) return str } func leave(str string) { fmt.Println("leaving " + str) } func point() { defer leave(trace("point")) fmt.Println("in point") } func main() { point() } //輸出結果: //entering point //in point //leaving point
這是第一種情況,defer的函數接受的參數在它入棧的時候就被緩存下來了。
再舉個例子:
func main() { a := 1 b := 2 defer calc("1", a, calc("10", a, b)) a = 0 defer calc("2", a, calc("20", a, b)) b = 1 } func calc(index string, a, b int) int { ret := a + b fmt.Println(index, a, b, ret) return ret } //10 1 2 3 //20 0 2 2 //2 0 2 2 //1 1 3 4
情況二:
要完全理解第二條規則,需要了解 return
與 defer
是怎么運行的。
函數內的 return xxx
并不是一個原子執行的返回:即不是先執行 return xxx
再執行 defer
,也不是先執行 defer
再執行 return xxx
。而是將 return xxx
拆分開來,經過編譯后執行過程如下:
1. 返回變量 = xxx 2. 調用 defer 函數(有可能更新返回變量的值) 3. return 返回變量。
1. func f1() (r int) { defer func() { r++ }() return 0 } 2. func f2() (r int) { t := 5 defer func() { t = t + 5 }() return t } 3. func f3() (r int) { defer func(r int) { // 作為函數參數傳入 defer 函數 r = r + 5 }(r) return 1 } 拆解: 1.r = 0 // 1. 賦值 func() { // 2. 運行 defer 函數 r++,r = 1 r++ }() return r // 3. return,即返回結果為 1 2.r = t (= 5) // 1. 賦值,r 取值 5 func() { // 2. 執行 defer 函數,執行后 t = 10,但 r = 5 t = t + 5 }() return r // 3. return r,即返回 5 3.r = 1 // 1. 賦值, r 取值 1 func(r int) { // 2. 執行 defer 函數,但作為函數參數傳入(緩存值為0) r = r + 5 // 執行后 r = 0 + 5 = 5,但這是局部變量,函數外仍是 1 }(r) return r // 3. return r, 即返回 1
踩坑點:
func increaseA() int { var i int defer func() { i++ }() return i }
注意,上面這段代碼的返回值是匿名的,所以結果返回0。
現在我們再以2個例子來做總結和鞏固:
type Person struct { age int } func main() { person := &Person{28} // 1. defer fmt.Println(person.age) // 2. defer func(p *Person) { fmt.Println(p.age) }(person) // 3. defer func() { fmt.Println(person.age) }() person.age = 29 }
參考答案及解析:29 29 28。變量 person 是一個指針變量 。
1.person.age 此時是將 28 當做 defer 函數的參數,會把 28 緩存在棧中,等到最后執行該 defer 語句的時候取出,即輸出 28;
2.defer 緩存的是結構體 Person{28} 的地址,最終 Person{28} 的 age 被重新賦值為 29,所以 defer 語句最后執行的時候,依靠緩存的地址取出的 age 便是 29,即輸出 29;
3.閉包引用,輸出 29;
又由于 defer 的執行順序為先進后出,即 3 2 1,所以輸出 29 29 28。
type Person struct { age int } func main() { person := &Person{28} // 1. defer fmt.Println(person.age) // 2. defer func(p *Person) { fmt.Println(p.age) }(person) // 3. defer func() { fmt.Println(person.age) }() person = &Person{29} }
參考答案及解析:29 28 28。這道題在第 19 天題目的基礎上做了一點點小改動,前一題最后一行代碼
person.age = 29 是修改引用對象的成員 age,這題最后一行代碼 person = &Person{29} 是修改引用對象本身,來看看有什么區別。
1.person.age 這一行代碼跟之前含義是一樣的,此時是將 28 當做 defer 函數的參數,會把 28 緩存在棧中,等到最后執行該 defer 語句的時候取出,即輸出 28;
2.defer 緩存的是結構體 Person{28} 的地址,這個地址指向的結構體沒有被改變,最后 defer 語句后面的函數執行的時候取出仍是 28;
3.閉包引用,person 的值已經被改變,指向結構體 Person{29},所以輸出 29.
由于 defer 的執行順序為先進后出,即 3 2 1,所以輸出 29 28 28。
最后打個小廣告:最近朋友建立了一個倉庫,記錄golang開發中踩過的坑和遇到的問題,歡迎大家把自己遇到的問題記錄下來,共同進步!
倉庫地址:https://github.com/remake100/go-study
原文鏈接:https://blog.csdn.net/m0_52138519/article/details/126728792
相關推薦
- 2022-08-10 Github簡單易用的?Android?ViewModel?Retrofit框架_Android
- 2023-11-14 Kubernetes常用命令(持續更新)
- 2022-11-23 Shell執行腳本并輸出日志文件的方法_linux shell
- 2023-04-06 ndarray的轉置(numpy.transpose()與A.T命令對比分析)_python
- 2022-10-05 Numpy中Meshgrid函數基本用法及2種應用場景_python
- 2023-07-13 替換字符串中的任意字符及正則隱藏手機號中間四位
- 2022-02-05 lxml提取html標簽內容, tostring()不能顯示中文 解決方案
- 2022-03-26 Linux快速部署Redis_Redis
- 最近更新
-
- 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同步修改后的遠程分支