您好,登錄后才能下訂單哦!
這篇文章將為大家詳細(xì)講解有關(guān)Go中閉包的底層原理是什么,小編覺得挺實用的,因此分享給大家做個參考,希望大家閱讀完這篇文章后可以有所收獲。
一個函數(shù)內(nèi)引用了外部的局部變量,這種現(xiàn)象,就稱之為閉包。
例如下面的這段代碼中,adder
函數(shù)返回了一個匿名函數(shù),而該匿名函數(shù)中引用了 adder
函數(shù)中的局部變量 sum
,那這個函數(shù)就是一個閉包。
package main import "fmt" func adder() func(int) int { sum := 0 return func(x int) int { sum += x return sum } }
而這個閉包中引用的外部局部變量并不會隨著 adder
函數(shù)的返回而被從棧上銷毀。
我們嘗試著調(diào)用這個函數(shù),發(fā)現(xiàn)每一次調(diào)用,sum
的值都會保留在 閉包函數(shù)中以待使用。
func main() { valueFunc:= adder() fmt.Println(valueFunc(2)) // output: 2 fmt.Println(valueFunc(2)) // output: 4 }
寫一個閉包是比較容易的事,但單單會寫簡單的閉包函數(shù),還遠(yuǎn)遠(yuǎn)不夠,如果不搞清楚閉包真正的原理,那很容易在一些復(fù)雜的閉包場景中對函數(shù)的執(zhí)行邏輯進(jìn)行誤判。
別的不說,就拿下來這個例子來說吧?
你覺得它會打印什么呢?
是 6 還是 11 呢?
import "fmt" func func1() (i int) { i = 10 defer func() { i += 1 }() return 5 } func main() { closure := func1() fmt.Println(closure) }
還是以最上面的例子來分析
package main import "fmt" func adder() func(int) int { sum := 0 return func(x int) int { sum += x return sum } } func main() { valueFunc:= adder() fmt.Println(valueFunc(2)) // output: 2 }
我們先對它進(jìn)行逃逸分析,很容易發(fā)現(xiàn) sum
作為 adder
函數(shù)局部變量,并不是分配在棧上,而是分配在堆上的。
這就解決了第一個疑惑:為什么 adder
函數(shù)返回后, sum
不會隨之銷毀?
$ go build -gcflags="-m -m -l" demo.go # command-line-arguments ./demo.go:8:3: adder.func1 capturing by ref: sum (addr=true assign=true width=8) ./demo.go:7:9: func literal escapes to heap: ./demo.go:7:9: flow: ~r0 = &{storage for func literal}: ./demo.go:7:9: from func literal (spill) at ./demo.go:7:9 ./demo.go:7:9: from return func literal (return) at ./demo.go:7:2 ./demo.go:6:2: sum escapes to heap: ./demo.go:6:2: flow: {storage for func literal} = &sum: ./demo.go:6:2: from func literal (captured by a closure) at ./demo.go:7:9 ./demo.go:6:2: from sum (reference) at ./demo.go:8:3 ./demo.go:6:2: moved to heap: sum ./demo.go:7:9: func literal escapes to heap ./demo.go:15:23: valueFunc(2) escapes to heap: ./demo.go:15:23: flow: {storage for ... argument} = &{storage for valueFunc(2)}: ./demo.go:15:23: from valueFunc(2) (spill) at ./demo.go:15:23 ./demo.go:15:23: flow: {heap} = {storage for ... argument}: ./demo.go:15:23: from ... argument (spill) at ./demo.go:15:13 ./demo.go:15:23: from fmt.Println(valueFunc(2)) (call parameter) at ./demo.go:15:13 ./demo.go:15:13: ... argument does not escape ./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)的匯編代碼
go build -gcflags="-S" demo.go
輸出的內(nèi)容相當(dāng)之多,我提取出下面最關(guān)鍵的一行代碼,它定義了閉包函數(shù)的結(jié)構(gòu)體。
其中 F 是函數(shù)的指針,但這不是重點,重點是 sum 存儲的確實是指針,驗證了我們的猜。
type.noalg.struct { F uintptr; "".sum *int }(SB), CX
有了上面第三節(jié)的背景知識,那對于第二節(jié)給出的這道題,想必你也有答案了。
首先,由于 i 在函數(shù)定義的返回值上聲明,因此根據(jù) go 的 caller-save
模式, i 變量會存儲在 main
函數(shù)的??臻g。
然后,func1
的 return
重新把 5 賦值給了 i ,此時 i = 5
由于閉包函數(shù)存儲了這個變量 i 的指針。
因此最后,在 defer 中對 i 進(jìn)行自增,是直接更新到 i 的指針上,此時 i = 5+1,所以最終打印出來的結(jié)果是 6
import "fmt" func func1() (i int) { i = 10 defer func() { i += 1 }() return 5 } func main() { closure := func1() fmt.Println(closure) }
上面那題聽懂了的話,再來看看下面這道題。
func1
的返回值我們不寫變量名 i 了,然后原先返回具體字面量,現(xiàn)在改成變量 i ,就是這兩小小小的改動,會導(dǎo)致運(yùn)行結(jié)果大大不同,你可以思考一下結(jié)果。
import "fmt" func func1() (int) { i := 10 defer func() { i += 1 }() return i } func main() { closure := func1() fmt.Println(closure) }
如果你在返回值里寫了變量名,那么該變量會存儲 main
的棧空間里,而如果你不寫,那 i 只能存儲在 func1
的??臻g里,與此同時,return
的值,不會作用于原變量 i 上,而是會存儲在該函數(shù)在另一塊棧內(nèi)存里。
因此你在 defer
中對原 i 進(jìn)行自增,并不會作用到 func1
的返回值上。
所以打印的結(jié)果,只能是 10。
你答對了嗎?
關(guān)于“Go中閉包的底層原理是什么”這篇文章就分享到這里了,希望以上內(nèi)容可以對大家有一定的幫助,使各位可以學(xué)到更多知識,如果覺得文章不錯,請把它分享出去讓更多的人看到。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。