匿名函數是一種沒有函數名的函數,即定義即使用;閉包作為一種攜帶狀態的函數,我們可以簡單地將它理解為“對象”,因為它同時具備狀態和行為。
匿名函數
匿名函數沒有函數名,只有函數體,它只有在被調用的時候才會初始化。匿名函數一般被當作一種類型賦值給函數類型的變量,經常被用作回調函數。
Go語言的匿名函數的聲明樣式如下所示:
func(params)(return params) {
function body
}
匿名函數的聲明與普通函數的定義基本一致,只是沒有名字。我們可以在匿名函數聲明之后直接調用它,如下所示:
func (name string) {
fmt.Println("My name is ", name)
}('小明')
在聲明匿名函數之后,在其后加上調用的參數列表,即可對匿名函數進行調用。除此之外,我們還可以將匿名函數賦值給函數的變量,用於多次調用或者求值,如下所示:
currentTime := func() {
fmt.Println(time.Now())
}
// 調用匿名函數
currentTime()
上述例子中,通過匿名函數實現了一個簡單的報時器,並賦值給 currentTime
,我們每次調用 currentTime
都能知道當前系統的最新時間。
匿名函數一個比較常用的場景是用作回調函數。
實例:使用回調函數處理字符串
它接收 string
和匿名函數的參數輸入,然后使用匿名函數對 string
進行處理。
package main
import "fmt"
func proc(input string, proessor func(str string)) {
// 調用匿名函數
processor(input)
}
func main() {
proc("小明", func(str string) {
for _, v := range str {
fmt.Printf("%c\n", v)
}
})
閉包
所謂閉包指的是引用了自由變量(未綁定到特定對象的變量,通常在函數外定義)的函數,被引用的自由變量將和這個函數一同存在,即使已經離開了創造它的上下文環境也不會被釋放(比如傳遞到其他函數或對象中)。簡單來說,「閉」的意思是「封閉外部狀態」,即使外部狀態已經失效,閉包內部依然保留了一份從外部引用的變量。
顯然,閉包只能通過匿名函數實現,我們可以把閉包看作是有狀態的匿名函數,反過來,如果匿名函數引用了外部變量,就形成了一個閉包(Closure)。
閉包的價值在於可以作為持有外部變量的函數對象或者匿名函數,對於類型系統而言,這意味着不僅要表示數據還要表示代碼。支持閉包的語言都將函數作為第一類對象(firt-class object,有的地方也譯作第一級對象、一等公民等,都是一個意思),Go 語言也不例外,這意味 Go 函數和普通 Go 數據類型(整型、字符串、數組、切片、字典、結構體等)具有同等的地位,可以賦值給變量,也可以作為參數傳遞給其他函數,還能夠被函數動態創建和返回。
閉包是攜帶狀態的函數,它是將函數內部和函數外部連接起來的橋梁。通過閉包,我們可以讀取函數內部的變量。我們也可以使用閉包封裝私有狀態,讓它們常駐於內存當中。
閉包能夠引用其作用域上部的變量並進行修改,被捕獲到閉包中的變量將隨着閉包的生命周期一直存在,函數本身是不存在信息的,但是閉包中的變量使閉包本身具備了存儲信息的能力。
注:所謂第一類對象指的是運行期可以被創建並作為參數傳遞給其他函數或賦值給變量的實體,在絕大多數語言中,數值和基本類型都是第一類對象,在支持閉包的編程語言中(比如 Go、PHP、JavaScript、Python 等),函數也是第一類對象,而像 C、C++ 等不支持匿名函數的語言中,函數不能在運行期創建,所以在這些語言中,函數不是不是第一類對象。
實例:利用閉包的特效實現一個簡單的計數器
package main
import "fmt"
func createCounter(initial int) func() int {
if initial < 0 {
initial = 0
}
// 引用 initial,創建一個閉包
return func() int {
initial++
// 返回當前計數
return initial;
}
}
func main() {
// 計數器 1
c1 := createCounter(1)
fmt.Println(c1()) // 2
fmt.Println(c2()) // 3
// 計數器 2
c2 := createCounter(10)
fmt.Println(c2()) // 11
fmt.Println(c1()) // 4
}
不同的閉包之間變量不會互相干擾,c1和c2兩個計數器都是獨立進行計數。