思考一個(gè)問題:
如何能夠在運(yùn)行的時(shí)候生成或者改變一個(gè)函數(shù)?
比如在一個(gè)函數(shù)外部修改一個(gè)變量就能改變這個(gè)函數(shù)的行為?
于是一個(gè)稱之為閉包的東西出現(xiàn)了。
閉包:函數(shù)和引用的外部變量一起構(gòu)成一個(gè)閉包。
?注意:如果沒有上述目的,不要引用函數(shù)外部的變量。把它作為參數(shù)傳入你的函數(shù)。否則你很有可能因此引入意想不到的bug。
如何實(shí)現(xiàn)閉包?
下面是golang的實(shí)現(xiàn)
- 假如閉包定義后立即被調(diào)用 因?yàn)橹粫皇褂靡淮?,所以?yīng)該力圖避免閉包對象的內(nèi)存分配操作,那怎么優(yōu)化一下呢,以下面的示例代碼為例。
func(a int) {
println(byval)
byref++
}(42)
上面的閉包將被轉(zhuǎn)換為簡單函數(shù)調(diào)用的形式:
func(byval int, &byref *int, a int) {
println(byval)
(*&byref)++
}(byval, &byref, 42)
注意看函數(shù)原型的變化,原來閉包里面捕獲的變量都被轉(zhuǎn)換成了通過函數(shù)參數(shù)來供值:
因?yàn)閜rintln操作不涉及對byval變量的修改操作,所以是按值捕獲;
而byref++涉及到對捕獲變量的修改,所以是按引用捕獲,對于按引用捕獲的變量會進(jìn)行特殊處理,golang編譯器會在編譯時(shí)將按引用捕獲的變量名byref轉(zhuǎn)換成“&byref”,同時(shí)將其類型轉(zhuǎn)換成pointer類型,捕獲變量對應(yīng)的寫操作也會轉(zhuǎn)換為通過pointer來操作。
2) 假如閉包定以后并不是立即調(diào)用 閉包定義后不是立即使用,而是后續(xù)調(diào)用,這種情況下同一個(gè)閉包可能調(diào)用多次,這種情況下就需要?jiǎng)?chuàng)建閉包對象,如何實(shí)現(xiàn)呢?
如果變量是按值捕獲,并且該變量占用存儲空間小于2*sizeof(int),那么就通過在函數(shù)體內(nèi)創(chuàng)建局部變量的形式來shadow捕獲的變量,相比于通過引用捕獲,這么做的好處應(yīng)該是考慮到減少引用數(shù)量、減少逃逸分析相關(guān)的計(jì)算。
如果變量是按引用捕獲,或者按值捕獲但是捕獲的變量占用存儲空間較大(拷貝到本地做局部變量代價(jià)太大),這種情況下就將捕獲的變量var轉(zhuǎn)換成pointer類型的“&var”,并在函數(shù)prologue階段將其初始化為捕獲變量的值。