這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。![custom errors](https://raw.githubusercontent.com/studygolang/gctt-images/master/golang-series/first-class-functions-golang.png)歡迎來到 [Golang 系列教程](https://studygolang.com/subject/2)的第 33 篇。## 什麼是頭等函數?**支援頭等函數(First Class Function)的程式設計語言,可以把函數賦值給變數,也可以把函數作為其它函數的參數或者傳回值。Go 語言支援頭等函數的機制**。本教程我們會討論頭等函數的文法和用例。## 匿名函數我們來編寫一個簡單的樣本,把[函數](https://studygolang.com/articles/11892)賦值給一個[變數](https://studygolang.com/articles/11756)。```gopackage mainimport ( "fmt")func main() { a := func() { fmt.Println("hello world first class function") } a() fmt.Printf("%T", a)}```[在 playground 上運行](https://play.golang.org/p/Xm_ihamhlEv)在上面的程式中,我們將一個函數賦值給了變數 `a`(第 8 行)。這是把函數賦值給變數的文法。你如果觀察得仔細的話,會發現賦值給 `a` 的函數沒有名稱。**由於沒有名稱,這類函數稱為匿名函數(Anonymous Function)**。調用該函數的唯一方法就是使用變數 `a`。我們在下一行調用了它。`a()` 調用了這個函數,列印出 `hello world first class function`。在第 12 行,我們列印出 `a` 的類型。這會輸出 `func()`。運行該程式,會輸出:```hello world first class functionfunc()```要調用一個匿名函數,可以不用賦值給變數。通過下面的例子,我們看看這是怎麼做到的。```gopackage mainimport ( "fmt")func main() { func() { fmt.Println("hello world first class function") }()}```[在 playground 上運行](https://play.golang.org/p/c0AjB3g8UEn)在上面的程式中,第 8 行定義了一個匿名函數,並在定義之後,我們使用 `()` 立即調用了該函數(第 10 行)。該程式會輸出:```hello world first class function```就像其它函數一樣,還可以向匿名函數傳遞參數。```gopackage mainimport ( "fmt")func main() { func(n string) { fmt.Println("Welcome", n) }("Gophers")}```[在 playground 上運行](https://play.golang.org/p/9ttJ5Wi4fj4)在上面的程式中,我們向匿名函數傳遞了一個字串參數(第 10 行)。運行該程式後會輸出:```Welcome Gophers```## 使用者自訂的函數類型正如我們定義自己的[結構體](https://studygolang.com/articles/12263)類型一樣,我們可以定義自己的函數類型。```gotype add func(a int, b int) int```以上程式碼片段建立了一個新的函數類型 `add`,它接收兩個整型參數,並返回一個整型。現在我們來定義 `add` 類型的變數。我們來編寫一個程式,定義一個 `add` 類型的變數。```gopackage mainimport ( "fmt")type add func(a int, b int) intfunc main() { var a add = func(a int, b int) int { return a + b } s := a(5, 6) fmt.Println("Sum", s)}```[在 playground 上運行](https://play.golang.org/p/n3yPQ7hG7ip)在上面程式的第 10 行,我們定義了一個 `add` 類型的變數 `a`,並向它賦值了一個符合 `add` 類型簽名的函數。我們在第 13 行調用了該函數,並將結果賦值給 `s`。該程式會輸出:```Sum 11```## 高階函數[wiki](https://en.wikipedia.org/wiki/Higher-order_function) 把高階函數(Hiher-order Function)定義為:**滿足下列條件之一的函數**:- **接收一個或多個函數作為參數**- **傳回值是一個函數**針對上述兩種情況,我們看看一些簡單一實例。### 把函數作為參數,傳遞給其它函數```gopackage mainimport ( "fmt")func simple(a func(a, b int) int) { fmt.Println(a(60, 7))}func main() { f := func(a, b int) int { return a + b } simple(f)}```[在 playground 上運行](https://play.golang.org/p/C0MNwz2TSGU)在上面的執行個體中,第 7 行我們定義了一個函數 `simple`,`simple` 接收一個函數參數(該函數接收兩個 `int` 參數,返回一個 `a` 整型)。在 `main` 函數的第 12 行,我們建立了一個匿名函數 `f`,其簽名符合 `simple` 函數的參數。我們在下一行調用了 `simple`,並傳遞了參數 `f`。該程式列印輸出 67。### 在其它函數中返回函數現在我們重寫上面的代碼,在 `simple` 函數中返回一個函數。```gopackage mainimport ( "fmt")func simple() func(a, b int) int { f := func(a, b int) int { return a + b } return f}func main() { s := simple() fmt.Println(s(60, 7))}```[在 playground 上運行](https://play.golang.org/p/82y2caejUy8)在上面程式中,第 7 行的 `simple` 函數返回了一個函數,並接受兩個 `int` 參數,返回一個 `int`。在第 15 行,我們調用了 `simple` 函數。我們把 `simple` 的傳回值賦值給了 `s`。現在 `s` 包含了 `simple` 函數返回的函數。我們調用了 `s`,並向它傳遞了兩個 int 參數(第 16 行)。該程式輸出 67。## 閉包閉包(Closure)是匿名函數的一個特例。當一個匿名函數所訪問的變數定義在函數體的外部時,就稱這樣的匿名函數為閉包。看看一個樣本就明白了。```gopackage mainimport ( "fmt")func main() { a := 5 func() { fmt.Println("a =", a) }()}```[在 playground 上運行](https://play.golang.org/p/6QriMs-zbnf)在上面的程式中,匿名函數在第 10 行訪問了變數 `a`,而 `a` 存在於函數體的外部。因此這個匿名函數就是閉包。每一個閉包都會綁定一個它自己的外圍變數(Surrounding Variable)。我們通過一個簡單樣本來體會這句話的含義。```gopackage mainimport ( "fmt")func appendStr() func(string) string { t := "Hello" c := func(b string) string { t = t + " " + b return t } return c}func main() { a := appendStr() b := appendStr() fmt.Println(a("World")) fmt.Println(b("Everyone")) fmt.Println(a("Gopher")) fmt.Println(b("!"))}```[在 playground 上運行](https://play.golang.org/p/134NiQGPOcS)在上面程式中,函數 `appendStr` 返回了一個閉包。這個閉包綁定了變數 `t`。我們來理解這是什麼意思。在第 17 行和第 18 行聲明的變數 `a` 和 `b` 都是閉包,它們綁定了各自的 `t` 值。我們首先用參數 `World` 調用了 `a`。現在 `a` 中 `t` 值變為了 `Hello World`。在第 20 行,我們又用參數 `Everyone` 調用了 `b`。由於 `b` 綁定了自己的變數 `t`,因此 `b` 中的 `t` 還是等於初始值 `Hello`。於是該函數調用之後,`b` 中的 `t` 變為了 `Hello Everyone`。程式的其他部分很簡單,不再解釋。該程式會輸出:```Hello World Hello Everyone Hello World Gopher Hello Everyone ! ```## 頭等函數的實際用途迄今為止,我們已經定義了什麼是頭等函數,也看了一些專門設計的樣本,來學習它們如何工作。現在我們來編寫一些實際的程式,來展現頭等函數的實際用處。我們會建立一個程式,基於一些條件,來過濾一個 `students` 切片。現在我們來逐步實現它。首先定義一個 `student` 類型。```gotype student struct { firstName string lastName string grade string country string}```下一步是編寫一個 `filter` 函數。該函數接收一個 `students` 切片和一個函數作為參數,這個函數會計算一個學生是否滿足篩選條件。寫出這個函數後,你很快就會明白,我們繼續吧。```gofunc filter(s []student, f func(student) bool) []student { var r []student for _, v := range s { if f(v) == true { r = append(r, v) } } return r}```在上面的函數中,`filter` 的第二個參數是一個函數。這個函數接收 `student` 參數,返回一個 `bool` 值。這個Function Compute了某一學生是否滿足篩選條件。我們在第 3 行遍曆了 `student` 切片,將每個學生作為參數傳遞給了函數 `f`。如果該函數返回 `true`,就表示該學生通過了篩選條件,接著將該學生添加到了結果切片 `r` 中。你可能會很困惑這個函數的實際用途,等我們完成程式你就知道了。我添加了 `main` 函數,整個程式如下所示:```gopackage mainimport ( "fmt")type student struct { firstName string lastName string grade string country string}func filter(s []student, f func(student) bool) []student { var r []student for _, v := range s { if f(v) == true { r = append(r, v) } } return r}func main() { s1 := student{ firstName: "Naveen", lastName: "Ramanathan", grade: "A", country: "India", } s2 := student{ firstName: "Samuel", lastName: "Johnson", grade: "B", country: "USA", } s := []student{s1, s2} f := filter(s, func(s student) bool { if s.grade == "B" { return true } return false }) fmt.Println(f)}```[在 playground 上運行](https://play.golang.org/p/YUL1CqSrvfc)在 `main` 函數中,我們首先建立了兩個學生 `s1` 和 `s2`,並將他們添加到了切片 `s`。現在假設我們想要查詢所有成績為 `B` 的學生。為了實現這樣的功能,我們傳遞了一個檢查學產生績是否為 `B` 的函數,如果是,該函數會返回 `true`。我們把這個函數作為參數傳遞給了 `filter` 函數(第 38 行)。上述程式會輸出:```[{Samuel Johnson B USA}]```假設我們想要尋找所有來自印度的學生。通過修改傳遞給 `filter` 的函數參數,就很容易地實現了。實現它的代碼如下所示:```goc := filter(s, func(s student) bool { if s.country == "India" { return true } return false})fmt.Println(c) ```請將該函數添加到 `main` 函數,並檢查它的輸出。我們最後再編寫一個程式,來結束這一節的討論。這個程式會對切片的每個元素執行相同的操作,並返回結果。例如,如果我們希望將切片中的所有整數乘以 5,並返回出結果,那麼通過頭等函數可以很輕鬆地實現。我們把這種對集合中的每個元素進行操作的函數稱為 `map` 函數。相關代碼如下所示,它們很容易看懂。```gopackage mainimport ( "fmt")func iMap(s []int, f func(int) int) []int { var r []int for _, v := range s { r = append(r, f(v)) } return r}func main() { a := []int{5, 6, 7, 8, 9} r := iMap(a, func(n int) int { return n * 5 }) fmt.Println(r)}```[在 playground 上運行](https://play.golang.org/p/cs37QwCQ_0H)該程式會輸出:```[25 30 35 40 45]```現在簡單概括一下本教程討論的內容:- 什麼是頭等函數?- 匿名函數- 使用者自訂的函數類型- 高階函數 - 把函數作為參數,傳遞給其它函數 - 在其它函數中返回函數- 閉包- 頭等函數的實際用途本教程到此結束。祝你愉快。**上一教程 - [panic 和 recover](https://studygolang.com/articles/12785)**
via: https://golangbot.com/first-class-functions/
作者:Nick Coghlan 譯者:Noluye 校對:polaris1119
本文由 GCTT 原創編譯,Go語言中文網 榮譽推出
本文由 GCTT 原創翻譯,Go語言中文網 首發。也想加入譯者行列,為開源做一些自己的貢獻嗎?歡迎加入 GCTT!
翻譯工作和譯文發表僅用於學習和交流目的,翻譯工作遵照 CC-BY-NC-SA 協議規定,如果我們的工作有侵犯到您的權益,請及時聯絡我們。
歡迎遵照 CC-BY-NC-SA 協議規定 轉載,敬請在本文中標註並保留原文/譯文連結和作者/譯者等資訊。
文章僅代表作者的知識和看法,如有不同觀點,請樓下排隊吐槽
2202 次點擊