Go 学习笔记(18)— 函数(04)[闭包定义、闭包修改变量、闭包记忆效应、闭包实现生成器、闭包复制原对象指针]
1. 閉包定義
Go 語言中閉包是引用了自由變量的函數,被引用的自由變量和函數一同存在,即使已經離開了自由變量的環境也不會被釋放或者刪除,在閉包中可以繼續使用這個自由變量,因此,簡單的說:
函數 + 引用環境 = 閉包
一個函數類型就像結構體一樣,可以被實例化,函數本身不存儲任何信息,只有與引用環境結合后形成的閉包才具有“記憶性”,函數是編譯期靜態的概念,而閉包是運行期動態的概念。
Go 語言閉包函數的定義:當匿名函數引用了外部作用域中的變量時就成了閉包函數,閉包函數是函數式編程語言的核心。
也就是匿名函數可以會訪問其所在的外層函數內的局部變量。當外層函數運行結束后,匿名函數會與其使用的外部函數的局部變量形成閉包。
2. 閉包示例
2.1 閉包修改變量
閉包對它作用域上部的變量可以進行修改,修改引用的變量會對變量進行實際修改,通過下面的例子來理解:
package mainimport "fmt"func main() {// 準備一個字符串str := "hello world"fmt.Println("str is ", str)// 創建一個匿名函數foo := func() {// 匿名函數中訪問str// 在匿名函數中并沒有定義 str,str 的定義在匿名函數之前,此時,str 就被引用到了匿名函數中形成了閉包。str = "hello dude"}// 調用匿名函數, 執行閉包,此時 str 發生修改,變為 hello dude。foo()fmt.Println("str is ", str)
}
代碼輸出:
str is hello world
str is hello dude
2.2 閉包記憶效應
被捕獲到閉包中的變量讓閉包本身擁有了記憶效應,閉包中的邏輯可以修改閉包捕獲的變量,變量會跟隨閉包生命期一直存在,閉包本身就如同變量一樣擁有了記憶效應。
package mainimport ("fmt"
)/*
累加器生成函數,這個函數輸出一個初始值,
調用時返回一個為初始值創建的閉包函數
*/
func Accumulate(value int) func() int {// 返回一個閉包,每次返回會創建一個新的函數實例return func() int {// 累加,對引用的 Accumulate 參數變量進行累加,注意 value 不是上一行匿名函數定義的,但是被這個匿名函數引用,所以形成閉包。value++// 返回一個累加值return value}
}func main() {// 創建一個累加器,初始值為 1,返回的 accumulator 是類型為 func()int 的函數變量。accumulator := Accumulate(1)// 累加1并打印fmt.Println(accumulator())fmt.Println(accumulator())// 打印累加器的函數地址fmt.Printf("%p\n", &accumulator)// 創建一個累加器, 初始值為10accumulator2 := Accumulate(10)// 累加1并打印fmt.Println(accumulator2())// 打印累加器的函數地址fmt.Printf("%p\n", &accumulator2)
}
輸出結果:
2
3
0xc00000e030
11
0xc00000e040
可以看出 accumulator 與 accumulator2 輸出的函數地址不同,因此它們是兩個不同的閉包實例。
package mainimport "fmt"func add() func(int) int {var x intreturn func(y int) int {x += yreturn x}
}
func main() {var f = add()fmt.Println(f(1)) //1fmt.Println(f(2)) //3fmt.Println(f(3)) //6f1 := add()fmt.Println(f1(4)) //4fmt.Println(f1(5)) //9
}
將 add() 函數賦值給變量 f,那么 f(1) 就相當于執行其內部的匿名函數 func(y int)。所以 f(1) 其實執行的是 x = x+y,此時 x 沒有給值默認為 0,y 為 1,最終返回 x 的值是 1。
f(2) 同樣執行其內部的匿名函數 func(y int),所以 f(2) 其實執行的是 x = x+y。此時 x 已經是 1,y 為 2,最終返回 x 的值是 3,依次類推執行。
2.3 閉包實現生成器
玩家生成器的實現:
package mainimport ("fmt"
)// 創建一個玩家生成器, 輸入名稱, 輸出生成器
func playerGen(name string) func() (string, int) {// 血量一直為150hp := 150// 返回創建的閉包return func() (string, int) {// 將 hp 和 name 變量引用到匿名函數中形成閉包。return name, hp}
}func main() {// 創建一個玩家生成器generator := playerGen("wohu")// 返回玩家的名字和血量name, hp := generator()// 打印值fmt.Println(name, hp) // wohu 150
}
2.4 閉包復制原對象指針
閉包復制的是原對象指針,這就很容易解釋延遲引用現象。
package mainimport "fmt"func test() func() {x := 100fmt.Printf("x (%p) = %d\n", &x, x)return func() {fmt.Printf("x (%p) = %d\n", &x, x)}
}func main() {f := test()f()
}
輸出:
x (0xc000018068) = 100
x (0xc000018068) = 100
3. 對比 Python 閉包
def fun1():sum = 0def fun2(v):nonlocal sumsum += vreturn sumreturn fun2a = fun1()
for i in range(10):print(a(i))0
1
3
6
10
15
21
28
36
45
代碼解析:
fun1 返回的不是一個值,而是一個函數 fun2,a = fun2,所以 a(i) 會打印 sum 的值。
為什么 sum 一直在加呢,函數里的值為什么可以帶到函數體外呢?
其實可以把閉包看做一個類, sum 就是類里的屬性,fun2 就是類的方法,所以 fun2 可以使用 sum(自由變量)。
nonlocal 聲明的變量不是局部變量,也不是全局變量,而是外部嵌套函數內的變量。嵌套函數 fun2 中的 sum 受到了影響,顯示 fun2 中的 sum 是 fun1 函數中的局部變量。
總結
以上是生活随笔為你收集整理的Go 学习笔记(18)— 函数(04)[闭包定义、闭包修改变量、闭包记忆效应、闭包实现生成器、闭包复制原对象指针]的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Go 学习笔记(17)— 函数(03)[
- 下一篇: Go 学习笔记(19)— 函数(05)[