go語(yǔ)言中函數(shù)可以作為返回值刺彩,可以作為參數(shù),可以作為右值綁定到變量嗡害,golan把這些返回值畦攘,參數(shù)知押,或變量稱為function value,函數(shù)指令在編譯期間生成罢绽,而function value本質(zhì)上是一個(gè)指針良价,指向一個(gè)runtime.funcval結(jié)構(gòu)體蒿叠,這個(gè)結(jié)構(gòu)體里面只有一個(gè)地址——函數(shù)指令的入口地址痊银。假設(shè)有如下代碼:
func A(i int){
i++
fmt.Println(i)
}
func B(){
f1:= A
f1(1)
}
func C(){
f2:=A
f2(1)
}
f1和f2都指向同一個(gè)函數(shù)A(int)魂务,其指令入口為addr1,編譯階段編譯器會(huì)在只讀數(shù)據(jù)段為他分配一個(gè)funcval結(jié)構(gòu)體fn指向addr1鬓照,而他本身的地址會(huì)在執(zhí)行階段賦給f1和f2豺裆,既然只要通過(guò)addr1就可以執(zhí)行函數(shù)A()臭猜,為什么還要通過(guò)fn這個(gè)結(jié)構(gòu)體中轉(zhuǎn)一下呢押蚤,這是為閉包準(zhǔn)備的揽碘。
什么是閉包(closure),用一句話來(lái)描述就是劫灶,閉包是一個(gè)函數(shù)和與他綁定的外部環(huán)境的集合本昏。閉包在實(shí)現(xiàn)上是一個(gè)結(jié)構(gòu)體涌穆,他存儲(chǔ)了一個(gè)函數(shù)(通常是其入口地址)和一個(gè)關(guān)聯(lián)的環(huán)境(相當(dāng)于一個(gè)符號(hào)查找表)宿稀。環(huán)境中包含該函數(shù)的內(nèi)部綁定符號(hào),及其在外部定義但在函數(shù)中引用的自由變量赫编。函數(shù)和閉包的不同在于擂送,當(dāng)捕獲閉包的時(shí)候嘹吨,他的自由變量會(huì)在捕捉時(shí)被確定蟀拷,這樣即使脫離了捕捉時(shí)的上下文萍聊,他也能照樣運(yùn)行寿桨。例如下面的例子:
func create() func()int{
c:=2
return func()int{
return c
}
}
func main(){
f1:=create()
f2:=create()
fmt.Println(f1())
fmt.Println(f2())
}
函數(shù)create() func()int
就是一個(gè)閉包亭螟,它有自由變量c,當(dāng)create()函數(shù)執(zhí)行結(jié)束墨微,f1和f2依然能夠正常調(diào)用這個(gè)閉包函數(shù)翘县,獲取捕獲變量c的值炼蹦。因?yàn)殚]包包含捕獲變量掐隐,所以在執(zhí)行階段才創(chuàng)建對(duì)應(yīng)的閉包對(duì)象钞馁,假設(shè)create函數(shù)的指令入口為addr1僧凰,在堆上分配一個(gè)funcval的結(jié)構(gòu)體训措,fn指向閉包函數(shù)入口,同時(shí)其捕獲列表中捕獲一個(gè)變量c怀大,然后這個(gè)結(jié)構(gòu)體的地址add2被賦值給f1化借。再次調(diào)用create函數(shù)蓖康,在分配一個(gè)funcval結(jié)構(gòu)體蒜焊,fn指向閉包函數(shù)入口addr1科贬,在捕獲對(duì)應(yīng)的捕獲變量唆迁,還是只有一個(gè)c唐责,最后將這funcval的起始地址addr2賦值給f2鼠哥。
func create() []func() int {
function := make([](func() int), 2, 2)
for i := 0;i < 2;i++ {
function[i] = func() int {
return i
}
i++
}
return function
}
func main() {
funcs := create()
fmt.Println(funcs[0]()) \\ 2
fmt.Println(funcs[1]()) \\ 2
}
捕獲變量會(huì)發(fā)生改變時(shí),捕獲列表中保存的是捕獲變量的地址允蚣,對(duì)捕獲變量的修改會(huì)保持一致呆贿。因此做入,funcs[0]()
和funcs[1]()
兩個(gè)函數(shù)獲得的i
都是2竟块。