Go基礎(chǔ)教程系列之回調(diào)函數(shù)和閉包詳解
Go回調(diào)函數(shù)和閉包
當(dāng)函數(shù)具備以下兩種特性的時(shí)候,就可以稱之為高階函數(shù)(high order functions):
- 函數(shù)可以作為另一個(gè)函數(shù)的參數(shù)(典型用法是回調(diào)函數(shù))
- 函數(shù)可以返回另一個(gè)函數(shù),即讓另一個(gè)函數(shù)作為這個(gè)函數(shù)的返回值(典型用法是閉包)
一般來(lái)說(shuō),附帶的還具備一個(gè)特性:函數(shù)可以作為一個(gè)值賦值給變量。
f := func(){...} f()
由于Go中函數(shù)不能嵌套命名函數(shù),所以函數(shù)返回函數(shù)的時(shí)候,只能返回匿名函數(shù)。
先簡(jiǎn)單介紹下高階函數(shù),然后介紹閉包。
高階函數(shù)示例
例如,將函數(shù)作為另一個(gè)函數(shù)的參數(shù):
package main import "fmt" func added(msg string, a func(a, b int) int) { fmt.Println(msg, ":", a(33, 44)) } func main() { // 函數(shù)內(nèi)部不能嵌套命名函數(shù) // 所以main()中只能定義匿名函數(shù) f := func(a, b int) int { return a + b } added("a+b", f) }
以下示例是函數(shù)返回另一個(gè)函數(shù):
package main import "fmt" func added() func(a, b int) int { f := func(a, b int) int { return a + b } return f } func main() { m := added() fmt.Println(m(33, 44)) }
回調(diào)函數(shù)(sort.SliceStable)
將函數(shù)B作為另一個(gè)函數(shù)A的參數(shù),可以使得函數(shù)A的通用性更強(qiáng),可以隨意定義函數(shù)B,只要滿足規(guī)則,函數(shù)A都可以去處理,這比較適合于回調(diào)函數(shù)。
在Go的sort包中有一個(gè)很強(qiáng)大的Slice排序工具SliceStable(),它就是將排序函數(shù)作為參數(shù)的:
func SliceStable(slice interface{}, less func(i, j int) bool)
這個(gè)函數(shù)是什么意思呢?給定一個(gè)名為slice的Slice結(jié)構(gòu),使用名為less的函數(shù)去對(duì)這個(gè)slice排序。這個(gè)less函數(shù)的結(jié)構(gòu)為less func(i, j int) bool
,其中i和j指定排序依據(jù)。Go中已經(jīng)內(nèi)置好了排序的算法,我們無(wú)需自己去定義排序算法,Go會(huì)自動(dòng)從Slice中每次取兩個(gè)i和j索引對(duì)應(yīng)的元素,然后去回調(diào)排序函數(shù)less。所以我們只需要傳遞升序還是降序、根據(jù)什么排序就可以。
例如:
package main import ( "fmt" "sort" ) func main() { s1 := []int{112, 22, 52, 32, 12} // 定義排序函數(shù) less := func(i, j int) bool { // 降序排序 return s1[i] > s1[j] // 升序排序:s1[i] < s1[j] } // sort.SliceStable(s1, less) fmt.Println(s1) }
這里的排序函數(shù)就是回調(diào)函數(shù)。每取一次i和j對(duì)應(yīng)的元素,就調(diào)用一次less函數(shù)。
可以將排序函數(shù)直接寫(xiě)在SliceStable()的參數(shù)位置:
sort.SliceStable(s1, func(i, j int) bool { return s1[i] > s1[j] })
還可以更強(qiáng)大更靈活。例如,按照字符大小順序來(lái)比較,而不是按照數(shù)值大小比較:
package main import ( "fmt" "sort" "strconv" ) func main() { s1 := []int{112, 220, 52, 32, 42} sort.SliceStable(s1, func(i, j int) bool { // 將i和j對(duì)應(yīng)的元素值轉(zhuǎn)換成字符串 bi := strconv.FormatInt(int64(s1[i]), 10) bj := strconv.FormatInt(int64(s1[j]), 10) // 按字符順序降序排序 return bi > bj }) fmt.Println(s1) }
按照字符串長(zhǎng)度來(lái)比較:
package main import ( "fmt" "sort" ) func main() { s1 := []string{"hello","malong","gaoxiao"} sort.SliceStable(s1, func(i, j int) bool { // 按字節(jié)大小順序降序排序 return len(s1[i]) > len(s1[j]) }) fmt.Println(s1) }
更嚴(yán)格地說(shuō)是按字節(jié)數(shù)比較,因?yàn)閘en()操作字符串時(shí)獲取的是字節(jié)數(shù)而非字符數(shù)。如果要按照字符數(shù)比較,則使用如下代碼:
package main import ( "fmt" "sort" ) func main() { s1 := []string{"hello","世界","gaoxiao"} sort.SliceStable(s1, func(i, j int) bool { // 按字節(jié)大小順序降序排序 return len([]rune(s1[i])) > len([]rune(s1[j])) }) fmt.Println(s1) }
閉包
函數(shù)A返回函數(shù)B,最典型的用法就是閉包(closure)。
簡(jiǎn)單地說(shuō),閉包就是"一個(gè)函數(shù)+一個(gè)作用域環(huán)境"組成的特殊函數(shù)。這個(gè)函數(shù)可以訪問(wèn)不是它自己內(nèi)部的變量,也就是這個(gè)變量在其它作用域內(nèi),且這個(gè)變量是未賦值的,而是等待我們?nèi)ベx值的。
例如:
package main import "fmt" func f(x int) func(int) int{ g := func(y int) int{ return x+y } // 返回閉包 return g } func main() { // 將函數(shù)的返回結(jié)果"閉包"賦值給變量a a := f(3) // 調(diào)用存儲(chǔ)在變量中的閉包函數(shù) res := a(5) fmt.Println(res) // 可以直接調(diào)用閉包 // 因?yàn)殚]包沒(méi)有賦值給變量,所以它稱為匿名閉包 fmt.Println(f(3)(5)) }
上面的f()返回的g之所以稱為閉包函數(shù),是因?yàn)樗且粋€(gè)函數(shù),且引用了不在它自己范圍內(nèi)的變量x,這個(gè)變量x是g所在作用域環(huán)境內(nèi)的變量,因?yàn)閤是未知、未賦值的自由變量。
如果x在傳遞給g之前是已經(jīng)賦值的,那么閉包函數(shù)就不應(yīng)該稱為閉包,因?yàn)檫@樣的閉包已經(jīng)失去意義了。
下面這個(gè)g也是閉包函數(shù),但這個(gè)閉包函數(shù)是自定義的,而不是通過(guò)函數(shù)返回函數(shù)得到的。
package main import "fmt" func main() { // 自由變量x var x int // 閉包函數(shù)g g := func(i int) int { return x + i } x = 5 // 調(diào)用閉包函數(shù) fmt.Println(g(5)) x = 10 // 調(diào)用閉包函數(shù) fmt.Println(g(3)) }
之所以這里的g也是閉包函數(shù),是因?yàn)間中訪問(wèn)了不屬于自己的變量x,而這個(gè)變量在閉包函數(shù)定義時(shí)是未綁定值的,也就是自由變量。
閉包的作用很明顯,在第一個(gè)閉包例子中,f(3)退出后,它返回的閉包函數(shù)g()仍然記住了原本屬于f()中的x=3
。這樣就可以讓很多閉包函數(shù)共享同一個(gè)自由變量x的值。
例如,下面的a(3)
、a(5)
、a(8)
都共享來(lái)自f()的x=3
。
a := f(3) a(3) a(5) a(8)
再往外層函數(shù)看,f(3)可以將自由變量x綁定為x=3
,自然也可以綁定為x=5
、x=8
等等。
所以,什么時(shí)候使用閉包?一般來(lái)說(shuō),可以將過(guò)程分成兩部分或更多部分都進(jìn)行工廠化的時(shí)候,就適合閉包(實(shí)際上,有些地方直接將閉包稱呼為工廠函數(shù))。第一個(gè)部分是可以給自由變量批量綁定不同的值,第二部分是多個(gè)閉包函數(shù)可以共享第一步綁定后的自由變量。
更多關(guān)于Go回調(diào)函數(shù)說(shuō)明,Go閉包說(shuō)明請(qǐng)查看下面的相關(guān)鏈接
相關(guān)文章
Go接口構(gòu)建可擴(kuò)展Go應(yīng)用示例詳解
本文深入探討了Go語(yǔ)言中接口的概念和實(shí)際應(yīng)用場(chǎng)景。從基礎(chǔ)知識(shí)如接口的定義和實(shí)現(xiàn),到更復(fù)雜的實(shí)戰(zhàn)應(yīng)用如解耦與抽象、多態(tài)、錯(cuò)誤處理、插件架構(gòu)以及資源管理,文章通過(guò)豐富的代碼示例和詳細(xì)的解釋,展示了Go接口在軟件開(kāi)發(fā)中的強(qiáng)大功能和靈活性2023-10-10詳解Golang實(shí)現(xiàn)請(qǐng)求限流的幾種辦法
這篇文章主要介紹了詳解Golang實(shí)現(xiàn)請(qǐng)求限流的幾種辦法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-04-04一文搞懂Go語(yǔ)言中defer關(guān)鍵字的使用
defer是golang中用的比較多的一個(gè)關(guān)鍵字,也是go面試題里經(jīng)常出現(xiàn)的問(wèn)題。今天就來(lái)整理一下關(guān)于defer的學(xué)習(xí)使用,希望對(duì)需要的朋友有所幫助2022-09-09Golang 實(shí)現(xiàn)interface類型轉(zhuǎn)string類型
這篇文章主要介紹了Golang 實(shí)現(xiàn)interface類型轉(zhuǎn)string類型的操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2021-04-04深入了解Go語(yǔ)言中database/sql是如何設(shè)計(jì)的
在?Go?語(yǔ)言中內(nèi)置了?database/sql?包,它只對(duì)外暴露了一套統(tǒng)一的編程接口,便可以操作不同數(shù)據(jù)庫(kù),那么database/sql?是如何設(shè)計(jì)的呢,下面就來(lái)和大家簡(jiǎn)單聊聊吧2023-07-07Golang如何快速構(gòu)建一個(gè)CLI小工具詳解
這篇文章主要為大家介紹了Golang如何快速構(gòu)建一個(gè)CLI小工具詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-11-11Golang中context庫(kù)的高級(jí)應(yīng)用
context庫(kù)不僅對(duì)于提升代碼的效率和性能至關(guān)重要,而且還幫助開(kāi)發(fā)者在復(fù)雜的系統(tǒng)中保持代碼的清晰和可維護(hù)性,下面我們就來(lái)看看context庫(kù)的高級(jí)應(yīng)用吧2024-01-01