Go語言中的閉包詳解
一、函數(shù)的變量作用域和可見性
1.全局變量在main函數(shù)執(zhí)行之前初始化,全局可見
2.局部變量在函數(shù)內(nèi)部或者if、for等語句塊有效,使用之后外部不可見
3.全局變量和局部變量同名的情況下,局部變量生效。
4.可見性:
包內(nèi)任何變量或函數(shù)都是能訪問的。
包外的話,首字母大寫是可以訪問的,首字母小寫的表示私有的不能被外部調(diào)用。
二、匿名函數(shù)
1.Go語言中函數(shù)也是一種類型,所以可以用一個函數(shù)類型的變量進行接收。
func anonyTest1(){
fmt.Println("anonyTest1")
}
//將改函數(shù)賦值給一個變量f,執(zhí)行f
func AnonyTest(){
f:= anonyTest1
f()
}2.匿名函數(shù)就是不指定名稱的函數(shù),如下就是匿名函數(shù)的使用
func AnonyTest2(){
f:= func() {
fmt.Println("AnonyTest2")
}
f()
//或者
func() {
fmt.Println("AnonyTest2...")
}()
}3.下面一個例子結(jié)合defer來看一下,這三個輸出都是什么
func AnonyTest3(){
var i=0
defer func() {
fmt.Printf("defer func i=%v \n",i)
}()
defer fmt.Printf("defer i=%v \n",i)
for;i<10; i++{
}
fmt.Printf("i=%v \n",i)
}從defer那篇文章我們知道 defer fmt.Printf("defer i=%v \n",i) 打印的就是i初始化后的值,最后一個也一定是for循環(huán)之后的值10,
主要就是匿名函數(shù)執(zhí)行之后的值,有意思是10,說明訪問了匿名函數(shù)外部的i,這就涉及到了閉包
運行結(jié)果如下:
i=10
defer i=0
defer func i=10
4.既然函數(shù)也是一種類型,那么就可以把函數(shù)當(dāng)做參數(shù)進行輸入、輸出了。(感覺有點類似C#里面的委托)
func Calc(a,b int, op func(int,int)int) int {
return op(a,b)
}
func add(a,b int) int{
return a+b
}
func sub(a,b int)int{
return a-b
}
func AnonyTest4(){
var a = 2
var b = 1
var x = Calc(a,b,add)
var y = Calc(a,b,sub)
fmt.Printf("x=%v, y=%v \n",x,y)
}結(jié)果:
x=3, y=1
三、閉包
閉包是由函數(shù)和與其相關(guān)的引用環(huán)境組合而成的實體(好抽象,難理解?。?/p>
func Adder() func(int) int{
var x int
return func(d int) int{
x+=d
return x
}
}像上面這段代碼,我們可以看到定義了一個變量x,以及return中的匿名函數(shù)。我們可以看到匿名函數(shù)引用了外部的變量x,我們可以把這個x叫做自由變量。
換句話說,這個匿名函數(shù)和這個自由變量x組成了一個整體,只要是在這個整體的生命周期內(nèi)這個x都是有效的。
下面使用一下這個Adder函數(shù):
func ClosureDemo5(){
var f = Adder()
fmt.Printf("結(jié)果=%d\n",f(1))
fmt.Printf("結(jié)果=%d\n",f(20))
fmt.Printf("結(jié)果=%d\n",f(300))
}執(zhí)行結(jié)果
結(jié)果=1
結(jié)果=21
結(jié)果=321
正如上面所提到的,這個只要Addr() 也就是f這個對象沒有消亡,那么f中的這個x就始終存在,也就是為什么第二次是21,第三次是321的原因了。
其他例子:
例子1:
func Adder2(base int) func(int)int{
return func(i int) int{
base += i
return base
}
}
func main(){
tmp1 := Adder2(10)
fmt.Println(tmp1(1),tmp1(2))
tmp2 := Adder2(100)
fmt.Println(tmp2(10),tmp2(20))
}這里Adder2接收一個int類型參數(shù)base,然后返回一個func,這里這個匿名函數(shù)里面引用了這個參數(shù)base,那么這個參數(shù)base和匿名函數(shù)就形成了一個整體。
后面我們 tmp1被賦值為 Adder2(10) ,那么在tmp1這個對象的生命周期內(nèi),base是被初始化為10且一直存在,所以結(jié)果是 11 和 13,同理后面是 110 和 130
例子2:
func calc(base int) (func(int)int,func(int)int){
add:= func(i int)int{
base +=i
return base
}
sub:= func(i int)int{
base -= i
return base
}
return add,sub
}
func main(){
f1,f2 := calc(10)
fmt.Println(f1(1),f2(2))
fmt.Println(f1(3),f2(4))
fmt.Println(f1(5),f2(6))
fmt.Println(f1(7),f2(8))
}分析一下:
這里base和 add以及sub的匿名函數(shù)也組成了一個實體也就是calc,所以在f1和f2的生命周期內(nèi),base一直存在,并被初始化成了10.
所以結(jié)果就是 f1(1) 就是10+1 =11 而 f2(2)就是 11-2 = 9,其他同理。
所以結(jié)果如下:
11 9
12 8
13 7
14 6
閉包的副作用!
func main(){
for i:=0;i<5;i++{
go func(x int){
fmt.Println(x)
}(i)
}
time.Sleep(time.Second)
}上述代碼應(yīng)該結(jié)果是多少?我的猜想應(yīng)該是0、1、2、3、4
但是實際結(jié)果是:
5
5
5
5
5
為什么會出現(xiàn)這樣的情況?實際上面這里每一個go協(xié)程中的匿名函數(shù)和外部for循環(huán)的i也形成了閉包,因為for循環(huán)執(zhí)行比較快,所以go還沒來得及執(zhí)行就變成5了。
我在每一個go協(xié)程之后加一個延時,結(jié)果就是0,1,2,3,4了。
func main(){
for i:=0;i<5;i++{
go func(){
fmt.Println(i)
}()
time.Sleep(time.Second)
}
time.Sleep(time.Second)
}結(jié)果如下
0
1
2
3
4
問題就在于不可能每次執(zhí)行都進行延遲吧,所以需要做一件事情打破這個閉包。
func main(){
for i:=0;i<5;i++{
go func(x int){
fmt.Println(x)
}(i)
}
time.Sleep(time.Second)
}這里把i當(dāng)做參數(shù)傳入到匿名函數(shù)中,保證了每次循環(huán)傳的值都不一樣。
到此這篇關(guān)于Go語言閉包的文章就介紹到這了。希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Air實現(xiàn)Go程序?qū)崟r熱重載使用過程解析示例
這篇文章主要為大家介紹了Air實現(xiàn)Go程序?qū)崟r熱重載使用過程解析示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步早日升職加薪2022-04-04

