當我們不希望給函數起名字的時候,可以使用匿名函數,例如:func(x, y int) int { return x + y }。
這樣的一個函數不能夠獨立存在(編譯器會傳回錯誤:non-declaration statement outside function body),但可以被指派于某個變量,即儲存函數的位址到變量中:fplus := func(x, y int) int { return x + y },然後通過變量名對函數進行調用:fplus(3,4)。
當然,您也可以直接對匿名函數進行調用:func(x, y int) int { return x + y } (3, 4)。
下面是一個計算從 1 到 1 百萬整數的總和的匿名函數:
func() {
sum := 0
for i := 1; i <= 1e6; i++ {
sum += i
}
}()
表示參數清單的第一對括号必須緊挨着關鍵字 func,因為匿名函數沒有名稱。花括号 {} 涵蓋着函數體,最後的一對括号表示對該匿名函數的調用。
下面的例子展示了如何将匿名函數指派給變量并對其進行調用(function_literal.go):
package main
import “fmt”
func main() {
f()
}
func f() {
for i := 0; i < 4; i++ {
g := func(i int) { fmt.Printf("%d “, i) } //此例子中隻是為了示範匿名函數可配置設定不同的記憶體位址,在現實開發中,不應該把該部分資訊放置到循環中。
g(i)
fmt.Printf(” - g is of type %T and has value %v\n", g, g)
}
}
輸出:
0 - g is of type func(int) and has value 0x681a80
1 - g is of type func(int) and has value 0x681b00
2 - g is of type func(int) and has value 0x681ac0
3 - g is of type func(int) and has value 0x681400
我們可以看到變量 g 代表的是 func(int),變量的值是一個記憶體位址。
是以我們實際上擁有的是一個函數值:匿名函數可以被指派給變量并作為值使用。
練習 6.8 在 main 函數中寫一個用于列印 Hello World 字元串的匿名函數并指派給變量 fv,然後調用該函數并列印變量 fv 的類型。
匿名函數像所有函數一樣可以接受或不接受參數。下面的例子展示了如何傳遞參數到匿名函數中:
func (u string) {
fmt.Println(u)
…
}(v)
請學習以下示例并思考(return_defer.go):函數 f 傳回時,變量 ret 的值是什麼?
package main
import “fmt”
func f() (ret int) {
defer func() {
ret++
}()
return 1
}
func main() {
fmt.Println(f())
}
變量 ret 的值為 2,因為 ret++ 是在執行 return 1 語句後發生的。
這可用于在傳回語句之後修改傳回的 error 時使用。
defer 語句和匿名函數
關鍵字 defer (詳見第 6.4 節)經常配合匿名函數使用,它可以用于改變函數的命名傳回值。
匿名函數還可以配合 go 關鍵字來作為 goroutine 使用(詳見第 14 章和第 16.9 節)。
匿名函數同樣被稱之為閉包(函數式語言的術語):它們被允許調用定義在其它環境下的變量。閉包可使得某個函數捕捉到一些外部狀态,例如:函數被建立時的狀态。另一種表示方式為:一個閉包繼承了函數所聲明時的作用域。這種狀态(作用域内的變量)都被共享到閉包的環境中,是以這些變量可以在閉包中被操作,直到被銷毀,詳見第 6.9 節中的示例。閉包經常被用作包裝函數:它們會預先定義好 1 個或多個參數以用于包裝,詳見下一節中的示例。另一個不錯的應用就是使用閉包來完成更加簡潔的錯誤檢查(詳見第 16.10.2 節)。