新聞中心
Go語言中閉包是引用了自由變量的函數(shù),被引用的自由變量和函數(shù)一同存在,即使已經(jīng)離開了自由變量的環(huán)境也不會(huì)被釋放或者刪除,在閉包中可以繼續(xù)使用這個(gè)自由變量,因此,簡(jiǎn)單的說:

公司主營業(yè)務(wù):網(wǎng)站建設(shè)、成都網(wǎng)站設(shè)計(jì)、移動(dòng)網(wǎng)站開發(fā)等業(yè)務(wù)。幫助企業(yè)客戶真正實(shí)現(xiàn)互聯(lián)網(wǎng)宣傳,提高企業(yè)的競(jìng)爭(zhēng)能力。創(chuàng)新互聯(lián)是一支青春激揚(yáng)、勤奮敬業(yè)、活力青春激揚(yáng)、勤奮敬業(yè)、活力澎湃、和諧高效的團(tuán)隊(duì)。公司秉承以“開放、自由、嚴(yán)謹(jǐn)、自律”為核心的企業(yè)文化,感謝他們對(duì)我們的高要求,感謝他們從不同領(lǐng)域給我們帶來的挑戰(zhàn),讓我們激情的團(tuán)隊(duì)有機(jī)會(huì)用頭腦與智慧不斷的給客戶帶來驚喜。創(chuàng)新互聯(lián)推出望都免費(fèi)做網(wǎng)站回饋大家。
函數(shù) + 引用環(huán)境 = 閉包
同一個(gè)函數(shù)與不同引用環(huán)境組合,可以形成不同的實(shí)例,如下圖所示。
圖:閉包與函數(shù)引用
一個(gè)函數(shù)類型就像結(jié)構(gòu)體一樣,可以被實(shí)例化,函數(shù)本身不存儲(chǔ)任何信息,只有與引用環(huán)境結(jié)合后形成的閉包才具有“記憶性”,函數(shù)是編譯期靜態(tài)的概念,而閉包是運(yùn)行期動(dòng)態(tài)的概念。
其它編程語言中的閉包
閉包(Closure)在某些編程語言中也被稱為 Lambda 表達(dá)式。
閉包對(duì)環(huán)境中變量的引用過程也可以被稱為“捕獲”,在 C++11 標(biāo)準(zhǔn)中,捕獲有兩種類型,分別是引用和復(fù)制,可以改變引用的原值叫做“引用捕獲”,捕獲的過程值被復(fù)制到閉包中使用叫做“復(fù)制捕獲”。
在 Lua 語言中,將被捕獲的變量起了一個(gè)名字叫做 Upvalue,因?yàn)椴东@過程總是對(duì)閉包上方定義過的自由變量進(jìn)行引用。
閉包在各種語言中的實(shí)現(xiàn)也是不盡相同的,在 Lua 語言中,無論閉包還是函數(shù)都屬于 Prototype 概念,被捕獲的變量以 Upvalue 的形式引用到閉包中。
C++ 與 C# 中為閉包創(chuàng)建了一個(gè)類,而被捕獲的變量在編譯時(shí)放到類中的成員中,閉包在訪問被捕獲的變量時(shí),實(shí)際上訪問的是閉包隱藏類的成員。
在閉包內(nèi)部修改引用的變量
閉包對(duì)它作用域上部的變量可以進(jìn)行修改,修改引用的變量會(huì)對(duì)變量進(jìn)行實(shí)際修改,通過下面的例子來理解:
// 準(zhǔn)備一個(gè)字符串
str := "hello world"
// 創(chuàng)建一個(gè)匿名函數(shù)
foo := func() {
// 匿名函數(shù)中訪問str
str = "hello dude"
}
// 調(diào)用匿名函數(shù)
foo()代碼說明如下:
- 第 2 行,準(zhǔn)備一個(gè)字符串用于修改。
- 第 5 行,創(chuàng)建一個(gè)匿名函數(shù)。
- 第 8 行,在匿名函數(shù)中并沒有定義 str,str 的定義在匿名函數(shù)之前,此時(shí),str 就被引用到了匿名函數(shù)中形成了閉包。
- 第 12 行,執(zhí)行閉包,此時(shí) str 發(fā)生修改,變?yōu)?hello dude。
代碼輸出:
hello dude
示例:閉包的記憶效應(yīng)
被捕獲到閉包中的變量讓閉包本身擁有了記憶效應(yīng),閉包中的邏輯可以修改閉包捕獲的變量,變量會(huì)跟隨閉包生命期一直存在,閉包本身就如同變量一樣擁有了記憶效應(yīng)。
累加器的實(shí)現(xiàn):
package main
import (
"fmt"
)
// 提供一個(gè)值, 每次調(diào)用函數(shù)會(huì)指定對(duì)值進(jìn)行累加
func Accumulate(value int) func() int {
// 返回一個(gè)閉包
return func() int {
// 累加
value++
// 返回一個(gè)累加值
return value
}
}
func main() {
// 創(chuàng)建一個(gè)累加器, 初始值為1
accumulator := Accumulate(1)
// 累加1并打印
fmt.Println(accumulator())
fmt.Println(accumulator())
// 打印累加器的函數(shù)地址
fmt.Printf("%p\n", &accumulator)
// 創(chuàng)建一個(gè)累加器, 初始值為1
accumulator2 := Accumulate(10)
// 累加1并打印
fmt.Println(accumulator2())
// 打印累加器的函數(shù)地址
fmt.Printf("%p\n", &accumulator2)
}代碼說明如下:
- 第 8 行,累加器生成函數(shù),這個(gè)函數(shù)輸出一個(gè)初始值,調(diào)用時(shí)返回一個(gè)為初始值創(chuàng)建的閉包函數(shù)。
- 第 11 行,返回一個(gè)閉包函數(shù),每次返回會(huì)創(chuàng)建一個(gè)新的函數(shù)實(shí)例。
- 第 14 行,對(duì)引用的 Accumulate 參數(shù)變量進(jìn)行累加,注意 value 不是第 11 行匿名函數(shù)定義的,但是被這個(gè)匿名函數(shù)引用,所以形成閉包。
- 第 17 行,將修改后的值通過閉包的返回值返回。
- 第 24 行,創(chuàng)建一個(gè)累加器,初始值為 1,返回的 accumulator 是類型為 func()int 的函數(shù)變量。
- 第 27 行,調(diào)用 accumulator() 時(shí),代碼從 11 行開始執(zhí)行匿名函數(shù)邏輯,直到第 17 行返回。
- 第 32 行,打印累加器的函數(shù)地址。
對(duì)比輸出的日志發(fā)現(xiàn) accumulator 與 accumulator2 輸出的函數(shù)地址不同,因此它們是兩個(gè)不同的閉包實(shí)例。
每調(diào)用一次 accumulator 都會(huì)自動(dòng)對(duì)引用的變量進(jìn)行累加。
示例:閉包實(shí)現(xiàn)生成器
閉包的記憶效應(yīng)被用于實(shí)現(xiàn)類似于 設(shè)計(jì)模式中工廠模式的生成器,下面的例子展示了創(chuàng)建一個(gè)玩家生成器的過程。
玩家生成器的實(shí)現(xiàn):
package main
import (
"fmt"
)
// 創(chuàng)建一個(gè)玩家生成器, 輸入名稱, 輸出生成器
func playerGen(name string) func() (string, int) {
// 血量一直為150
hp := 150
// 返回創(chuàng)建的閉包
return func() (string, int) {
// 將變量引用到閉包中
return name, hp
}
}
func main() {
// 創(chuàng)建一個(gè)玩家生成器
generator := playerGen("high noon")
// 返回玩家的名字和血量
name, hp := generator()
// 打印值
fmt.Println(name, hp)
}代碼輸出如下:
high noon 150
代碼說明如下:
- 第 8 行,playerGen() 需要提供一個(gè)名字來創(chuàng)建一個(gè)玩家的生成函數(shù)。
- 第 11 行,聲明并設(shè)定 hp 變量為 150。
- 第 14~18 行,將 hp 和 name 變量引用到匿名函數(shù)中形成閉包。
- 第 24 行中,通過 playerGen 傳入?yún)?shù)調(diào)用后獲得玩家生成器。
- 第 27 行,調(diào)用這個(gè)玩家生成器函數(shù),可以獲得玩家的名稱和血量。
閉包還具有一定的封裝性,第 11 行的變量是 playerGen 的局部變量,playerGen 的外部無法直接訪問及修改這個(gè)變量,這種特性也與面向?qū)ο笾袕?qiáng)調(diào)的封裝性類似。
本文標(biāo)題:創(chuàng)新互聯(lián)GO教程:Go語言閉包(Closure)——引用了外部變量的匿名函數(shù)
URL網(wǎng)址:http://www.dlmjj.cn/article/djdjosh.html


咨詢
建站咨詢
