Go語言中的閉包詳解
一、函數的變量作用域和可見性
1.全局變量在main函數執行之前初始化,全局可見
2.局部變量在函數內部或者if、for等語句塊有效,使用之後外部不可見
3.全局變量和局部變量同名的情況下,局部變量生效。
4.可見性:
包內任何變量或函數都是能訪問的。
包外的話,首字母大寫是可以訪問的,首字母小寫的表示私有的不能被外部調用。
二、匿名函數
1.Go語言中函數也是一種類型,所以可以用一個函數類型的變量進行接收。
func anonyTest1(){ fmt.Println("anonyTest1") } //將改函數賦值給一個變量f,執行f func AnonyTest(){ f:= anonyTest1 f() }
2.匿名函數就是不指定名稱的函數,如下就是匿名函數的使用
func AnonyTest2(){ f:= func() { fmt.Println("AnonyTest2") } f() //或者 func() { fmt.Println("AnonyTest2...") }() }
3.下面一個例子結合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循環之後的值10,
主要就是匿名函數執行之後的值,有意思是10,說明訪問瞭匿名函數外部的i,這就涉及到瞭閉包
運行結果如下:
i=10
defer i=0
defer func i=10
4.既然函數也是一種類型,那麼就可以把函數當做參數進行輸入、輸出瞭。(感覺有點類似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) }
結果:
x=3, y=1
三、閉包
閉包是由函數和與其相關的引用環境組合而成的實體(好抽象,難理解啊)
func Adder() func(int) int{ var x int return func(d int) int{ x+=d return x } }
像上面這段代碼,我們可以看到定義瞭一個變量x,以及return中的匿名函數。我們可以看到匿名函數引用瞭外部的變量x,我們可以把這個x叫做自由變量。
換句話說,這個匿名函數和這個自由變量x組成瞭一個整體,隻要是在這個整體的生命周期內這個x都是有效的。
下面使用一下這個Adder函數:
func ClosureDemo5(){ var f = Adder() fmt.Printf("結果=%d\n",f(1)) fmt.Printf("結果=%d\n",f(20)) fmt.Printf("結果=%d\n",f(300)) }
執行結果
結果=1
結果=21
結果=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類型參數base,然後返回一個func,這裡這個匿名函數裡面引用瞭這個參數base,那麼這個參數base和匿名函數就形成瞭一個整體。
後面我們 tmp1被賦值為 Adder2(10) ,那麼在tmp1這個對象的生命周期內,base是被初始化為10且一直存在,所以結果是 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的匿名函數也組成瞭一個實體也就是calc,所以在f1和f2的生命周期內,base一直存在,並被初始化成瞭10.
所以結果就是 f1(1) 就是10+1 =11 而 f2(2)就是 11-2 = 9,其他同理。
所以結果如下:
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) }
上述代碼應該結果是多少?我的猜想應該是0、1、2、3、4
但是實際結果是:
5
5
5
5
5
為什麼會出現這樣的情況?實際上面這裡每一個go協程中的匿名函數和外部for循環的i也形成瞭閉包,因為for循環執行比較快,所以go還沒來得及執行就變成5瞭。
我在每一個go協程之後加一個延時,結果就是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) }
結果如下
0
1
2
3
4
問題就在於不可能每次執行都進行延遲吧,所以需要做一件事情打破這個閉包。
func main(){ for i:=0;i<5;i++{ go func(x int){ fmt.Println(x) }(i) } time.Sleep(time.Second) }
這裡把i當做參數傳入到匿名函數中,保證瞭每次循環傳的值都不一樣。
到此這篇關於Go語言閉包的文章就介紹到這瞭。希望對大傢的學習有所幫助,也希望大傢多多支持WalkonNet。
推薦閱讀:
- Golang之defer 延遲調用操作
- Golang 的defer執行規則說明
- 解決go在函數退出後子協程的退出問題
- Go語言函數的延遲調用(Deferred Code)詳解
- Go語言 go程釋放操作(退出/銷毀)