成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

Go 中閉包的底層原理是?

網(wǎng)絡(luò) 通信技術(shù)
寫一個閉包是比較容易的事,但單單會寫簡單的閉包函數(shù),還遠遠不夠,如果不搞清楚閉包真正的原理,那很容易在一些復(fù)雜的閉包場景中對函數(shù)的執(zhí)行邏輯進行誤判。

[[431093]]

1. 什么是閉包?

一個函數(shù)內(nèi)引用了外部的局部變量,這種現(xiàn)象,就稱之為閉包。

例如下面的這段代碼中,adder 函數(shù)返回了一個匿名函數(shù),而該匿名函數(shù)中引用了 adder 函數(shù)中的局部變量 sum ,那這個函數(shù)就是一個閉包。

  1. package main 
  2.  
  3. import "fmt" 
  4.  
  5. func adder() func(intint { 
  6.     sum := 0 
  7.     return func(x intint { 
  8.         sum += x 
  9.         return sum 
  10.     } 

而這個閉包中引用的外部局部變量并不會隨著 adder 函數(shù)的返回而被從棧上銷毀。

我們嘗試著調(diào)用這個函數(shù),發(fā)現(xiàn)每一次調(diào)用,sum 的值都會保留在 閉包函數(shù)中以待使用。

  1. func main() { 
  2.      valueFunc:= adder() 
  3.      fmt.Println(valueFunc(2))     // output: 2 
  4.      fmt.Println(valueFunc(2))   // output: 4 

2. 復(fù)雜的閉包場景

寫一個閉包是比較容易的事,但單單會寫簡單的閉包函數(shù),還遠遠不夠,如果不搞清楚閉包真正的原理,那很容易在一些復(fù)雜的閉包場景中對函數(shù)的執(zhí)行邏輯進行誤判。

別的不說,就拿下來這個例子來說吧?

你覺得它會打印什么呢?

是 6 還是 11 呢?

  1. import "fmt" 
  2.  
  3. func func1() (i int) { 
  4.     i = 10 
  5.     defer func() { 
  6.         i += 1 
  7.     }() 
  8.     return 5 
  9.  
  10. func main() { 
  11.     closure := func1() 
  12.     fmt.Println(closure) 

3. 閉包的底層原理?

還是以最上面的例子來分析

  1. package main 
  2.  
  3. import "fmt" 
  4.  
  5. func adder() func(intint { 
  6.     sum := 0 
  7.     return func(x intint { 
  8.         sum += x 
  9.         return sum 
  10.     } 
  11.  
  12. func main() { 
  13.     valueFunc:= adder() 
  14.     fmt.Println(valueFunc(2))     // output: 2 

我們先對它進行逃逸分析,很容易發(fā)現(xiàn) sum 作為 adder 函數(shù)局部變量,并不是分配在棧上,而是分配在堆上的。

這就解決了第一個疑惑:為什么 adder 函數(shù)返回后, sum 不會隨之銷毀?

  1. $ go build -gcflags="-m -m -l" demo.go 
  2. # command-line-arguments 
  3. ./demo.go:8:3: adder.func1 capturing by ref: sum (addr=true assign=true width=8) 
  4. ./demo.go:7:9: func literal escapes to heap: 
  5. ./demo.go:7:9:   flow: ~r0 = &{storage for func literal}: 
  6. ./demo.go:7:9:     from func literal (spill) at ./demo.go:7:9 
  7. ./demo.go:7:9:     from return func literal (returnat ./demo.go:7:2 
  8. ./demo.go:6:2: sum escapes to heap: 
  9. ./demo.go:6:2:   flow: {storage for func literal} = &sum
  10. ./demo.go:6:2:     from func literal (captured by a closure) at ./demo.go:7:9 
  11. ./demo.go:6:2:     from sum (reference) at ./demo.go:8:3 
  12. ./demo.go:6:2: moved to heap: sum 
  13. ./demo.go:7:9: func literal escapes to heap 
  14. ./demo.go:15:23: valueFunc(2) escapes to heap: 
  15. ./demo.go:15:23:   flow: {storage for ... argument} = &{storage for valueFunc(2)}: 
  16. ./demo.go:15:23:     from valueFunc(2) (spill) at ./demo.go:15:23 
  17. ./demo.go:15:23:   flow: {heap} = {storage for ... argument}: 
  18. ./demo.go:15:23:     from ... argument (spill) at ./demo.go:15:13 
  19. ./demo.go:15:23:     from fmt.Println(valueFunc(2)) (call parameter) at ./demo.go:15:13 
  20. ./demo.go:15:13: ... argument does not escape 
  21. ./demo.go:15:23: valueFunc(2) escapes to heap 

可另一個問題,又浮現(xiàn)出來了,就算它不會銷毀,那閉包函數(shù)若是存儲的若是 sum 拷貝后的值,那每次調(diào)用閉包函數(shù),里面的 sum 應(yīng)該都是一樣的,調(diào)用兩次都應(yīng)該返回 2,而不是可以累加記錄。

因此,可以大膽猜測,閉包函數(shù)的結(jié)構(gòu)體里存儲的是 sum 的指針。

為了驗證這一猜想,只能上匯編了。

通過執(zhí)行下面的命令,可以輸出對應(yīng)的匯編代碼

  1. go build -gcflags="-S" demo.go  

輸出的內(nèi)容相當(dāng)之多,我提取出下面最關(guān)鍵的一行代碼,它定義了閉包函數(shù)的結(jié)構(gòu)體。

其中 F 是函數(shù)的指針,但這不是重點,重點是 sum 存儲的確實是指針,驗證了我們的猜。

  1. type.noalg.struct { F uintptr; "".sum *int }(SB), CX 

4. 迷題揭曉

有了上面第三節(jié)的背景知識,那對于第二節(jié)給出的這道題,想必你也有答案了。

首先,由于 i 在函數(shù)定義的返回值上聲明,因此根據(jù) go 的 caller-save 模式, i 變量會存儲在 main 函數(shù)的棧空間。

然后,func1 的 return 重新把 5 賦值給了 i ,此時 i = 5

由于閉包函數(shù)存儲了這個變量 i 的指針。

因此最后,在 defer 中對 i 進行自增,是直接更新到 i 的指針上,此時 i = 5+1,所以最終打印出來的結(jié)果是 6

  1. import "fmt" 
  2.  
  3. func func1() (i int) { 
  4.     i = 10 
  5.     defer func() { 
  6.         i += 1 
  7.     }() 
  8.     return 5 
  9.  
  10. func main() { 
  11.     closure := func1() 
  12.     fmt.Println(closure) 

5. 再度變題

上面那題聽懂了的話,再來看看下面這道題。

func1 的返回值我們不寫變量名 i 了,然后原先返回具體字面量,現(xiàn)在改成變量 i ,就是這兩小小小的改動,會導(dǎo)致運行結(jié)果大大不同,你可以思考一下結(jié)果。

  1. import "fmt" 
  2.  
  3. func func1() (int) { 
  4.     i := 10 
  5.     defer func() { 
  6.         i += 1 
  7.     }() 
  8.     return i 
  9.  
  10. func main() { 
  11.     closure := func1() 
  12.     fmt.Println(closure) 

如果你在返回值里寫了變量名,那么該變量會存儲 main 的棧空間里,而如果你不寫,那 i 只能存儲在 func1 的棧空間里,與此同時,return 的值,不會作用于原變量 i 上,而是會存儲在該函數(shù)在另一塊棧內(nèi)存里。

因此你在 defer 中對原 i 進行自增,并不會作用到 func1 的返回值上。

所以打印的結(jié)果,只能是 10。

你答對了嗎?

6. 最后一個問題

不知道你有沒有發(fā)現(xiàn),在第一節(jié)示例中的 sum 是存儲在堆內(nèi)存中的,而后面幾個示例都是存儲在棧內(nèi)存里。

這是為什么呢?

仔細對比,不難發(fā)現(xiàn),示例一返回的是閉包函數(shù),閉包函數(shù)在 adder 返回后還要在其他地方繼續(xù)使用,在這種情況下,為了保證閉包函數(shù)的正常運行,無論閉包函數(shù)在哪里,i 都不能回收,所以 Go 編譯器會智能地將其分配在堆上。

而后面的其他示例,都只是涉及了閉包的特性,并不是直接把閉包函數(shù)返回,因此完全可以將其分配在棧上,非常的合理。

是不是很簡單呢?

 

責(zé)任編輯:武曉燕 來源: Go編程時光
相關(guān)推薦

2020-02-12 16:58:15

JavaScript前端技術(shù)

2019-11-07 21:51:18

閉包前端函數(shù)

2023-09-11 08:20:17

對象閉包底層

2011-05-23 13:54:04

閉包

2022-10-24 08:08:27

閉包編譯器

2022-08-08 06:50:06

Go語言閉包

2011-08-05 09:33:30

Func局部變量作用域

2022-08-08 08:31:55

Go 語言閉包匿名函數(shù)

2012-11-29 10:09:23

Javascript閉包

2016-11-01 09:18:33

Python閉包

2023-11-02 08:53:26

閉包Python

2024-01-22 09:51:32

Swift閉包表達式尾隨閉包

2021-01-13 11:25:12

JavaScript閉包函數(shù)

2023-07-11 08:46:38

閉包函數(shù)Rust

2022-06-08 08:01:20

useEffect數(shù)組函數(shù)

2011-05-12 18:26:08

Javascript作用域

2022-12-26 09:27:48

Java底層monitor

2023-01-09 08:00:41

JavaScript閉包

2024-11-26 00:45:29

free區(qū)域字段

2024-01-08 08:35:28

閉包陷阱ReactHooks
點贊
收藏

51CTO技術(shù)棧公眾號

主站蜘蛛池模板: 国产综合精品一区二区三区 | 久久久久久高潮国产精品视 | 欧美日韩视频在线第一区 | 在线午夜 | 蜜桃一区二区三区 | 亚洲精品久久久一区二区三区 | 国产精品资源在线 | 青娱乐一区二区 | 日韩一区二区三区四区五区六区 | 国产精品网页 | 成人免费影院 | 成人夜晚看av | 激情在线视频网站 | 福利社午夜影院 | 超碰在线观看97 | 国产一区二区三区在线 | 毛片免费在线观看 | 9999国产精品欧美久久久久久 | 国产高清美女一级a毛片久久w | 亚洲三级在线观看 | www.天天操 | 欧美456| 最新日韩在线视频 | 婷婷色国产偷v国产偷v小说 | 午夜视频免费在线 | 区一区二在线观看 | 欧美日本一区 | 国产亚洲一区二区三区 | 亚洲国产成人av好男人在线观看 | 精品欧美一区二区三区久久久小说 | 欧美日韩久久 | 国产精品久久 | 国产玖玖 | 久久国产精品视频 | 免费黄色录像视频 | 精品久久久久久国产 | 国产精品成人久久久久 | 国产精品亚洲一区 | 亚洲国产网 | 色综合久 | 久久精品国内 |