- 原文地址:Part 33: First Class Functions
- 原文作者:Naveen R
- 譯者:咔嘰咔嘰 轉載請註明出處。
什麼是頭等函式
支援頭等函式的語言允許將函式賦值給變數,作為引數傳遞給其他函式並從其他函式返回。 Go 支援頭等函式功能。
在本教程中,我們將討論頭等函式的語法和各種用例。
匿名函式
讓我們從一個簡單的例子開始,它將一個函式賦給變數。
package main
import (
"fmt"
)
func main() {
a := func() {
fmt.Println("hello world first class function")
}
a()
fmt.Printf("%T", a)
}
複製程式碼
在上面的程式中的第 8 行,我們為變數a
分配了一個函式。 這是將函式賦值給變數的語法。如果你仔細觀察的話會發現,分配給a
的函式沒有名稱。這些沒有名稱的函式稱為匿名函式。
呼叫此函式的唯一方法是使用變數a
。我們使用 a()
呼叫該函式,將列印hello world first class function
。在第 12 行,我們列印變數a
的型別,將列印func()
。
執行該程式,將輸出,
hello world first class function
func()
複製程式碼
也可以不賦值給變數來呼叫匿名函式,讓我們看看以下示例中怎麼完成的此操作。
package main
import (
"fmt"
)
func main() {
func() {
fmt.Println("hello world first class function")
}()
}
複製程式碼
在上面的程式中,我們在第 8 行定義了一個匿名函式。 在函式定義之後,我們在行號中使用()
呼叫函式。 該程式將輸出,
hello world first class function
複製程式碼
也可以像任何其他函式一樣將引數傳遞給匿名函式。
package main
import (
"fmt"
)
func main() {
func(n string) {
fmt.Println("Welcome", n)
}("Gophers")
}
複製程式碼
在上面的程式中的第 10 行,將字串引數傳遞給匿名函式。執行此程式將列印,
Welcome Gophers
複製程式碼
使用者定義的函式型別
就像定義自己的結構型別一樣,我們也可以定義自己的函式型別。
type add func(a int, b int) int
複製程式碼
上面的程式碼片段建立了一個新的函式型別add
,它接受兩個整數引數並返回一個整數。現在我們可以定義add
型別的變數。
讓我們編寫一個定義add
型別變數的程式。
package main
import (
"fmt"
)
type add func(a int, b int) int
func main() {
var a add = func(a int, b int) int {
return a + b
}
s := a(5, 6)
fmt.Println("Sum", s)
}
複製程式碼
在上面的程式的第 10 行,我們定義了一個型別為add
的變數a
,併為其賦予與add
型別匹配的函式。在第 13 行,我們呼叫該函式,並將結果分配給s
。該程式將列印,
Sum 11
複製程式碼
高階函式
wiki上高階函式的定義是滿足以下至少一個條件
- 將一個或多個函式作為引數
- 返回一個函式作為結果
讓我們看一下上述兩種情況的一些簡單示例。
將函式作為引數傳遞給其他函式
package main
import (
"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)
}
複製程式碼
在上面的例子中的第 7 行,我們定義一個函式simple
,它接受一個函式,該函式接受兩個int
引數並返回一個int
作為引數。在 main 函式裡面,我們建立了一個匿名函式f
,其與函式simple
的引數匹配。我們呼叫simple
,並在下一行中將f
作為引數傳遞給它。該程式列印67
作為輸出。
從其他函式返回函式
現在讓我們重寫上面的程式並從simple
函式返回一個函式。
package main
import (
"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))
}
複製程式碼
在上面程式中的第 7 行,simple
函式返回一個函式,該函式接受兩個int
引數並返回一個int
引數。
在第 15 行呼叫了simple
函式,然後將返回值分配給s
。現在s
包含simple
函式返回的函式。我們呼叫s
並傳遞兩個int
引數。 該程式將輸出67
。
閉包
閉包是匿名函式的特例。閉包是匿名函式,用於訪問該匿名函式體外定義的變數。
用個例子瞭解一下它,
package main
import (
"fmt"
)
func main() {
a := 5
func() {
fmt.Println("a =", a)
}()
}
複製程式碼
在上面程式中的第 10 行,匿名函式訪問函式體外定義的變數a
。因此,這個匿名函式是一個閉包。
每個閉包都把變數繫結到它周圍的變數。讓我們通過一個簡單的例子來理解這是什麼意思。
package main
import (
"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("!"))
}
複製程式碼
在上面的程式中,函式appendStr
返回一個閉包。該閉包繫結到變數t
。
變數a
和b
分別在第 17 和 18 行中宣告為閉包,它們與自己的t
值繫結。
我們先用引數World
呼叫a
。現在,a
閉包t
的值變成了Hello World
。
在第 20 行,我們用引數Everyone
呼叫b
。由於b
繫結到自己的變數t
,因此b
閉包的初始值為Hello
。在此函式呼叫之後,b
閉包變數t
的值將變為Hello Everyone
。
程式將輸出,
Hello World
Hello Everyone
Hello World Gopher
Hello Everyone !
複製程式碼
頭等函式的實際用法
到目前為止,我們已經定義了頭等函式,並且我們已經編寫了一些例子來了解它們的工作原理。現在讓我們編寫一個具體的程式,它揭示了頭等函式的實際用法。
我們將建立一個程式,根據某些標準過濾一部分學生。讓我們一步一步地解決這個問題。
首先定義學生型別,
type student struct {
firstName string
lastName string
grade string
country string
}
複製程式碼
下一步是編寫filter
函式。該函式的入參為student
結構的切片和一個確認student
是否符合條件的函式。編寫這個函式,能讓我們更好地理解。讓我們繼續吧。
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
}
複製程式碼
在上面的函式中,第二個引數是一個以student
為引數並返回bool
的函式。此函式是為了確認該student
是否符合條件。我們在第 3 行中迭代student
切片,然後將每個student
作為引數傳遞給函式f
。如果返回true
,則表示student
符合過濾條件,並將其新增到結果切片r
中。你可能對此功能的實際使用感到有些困惑,繼續完成該程式就會更清楚。我新增了 main 函式,並在下面提供了完整的程式。
package main
import (
"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)
}
複製程式碼
在 main 函式中,我們首先建立兩個student
型別的變數s1
和s2
,並將它們新增到切片s
中作為引數傳遞給filter
函式。現在我想找出所有B
級的學生。我們在上面的程式中通過一個函式來檢查學生是否有B
級,如果是,那麼返回true
。上述程式將列印,
[{Samuel Johnson B USA}]
複製程式碼
假設我們想找到所有來自印度的學生。將filter
函式引數改變一下,可以輕鬆完成此操作。
我提供了以下程式碼,
c := filter(s, func(s student) bool {
if s.country == "India" {
return true
}
return false
})
fmt.Println(c)
複製程式碼
請將其新增到 main 函式並檢查輸出。
讓我們再寫一個程式來結束本節。該程式將對切片的每個元素執行相同的操作並返回結果。例如,如果我們想要將切片中的所有整數乘以 5 並返回輸出,則可以使用頭等函式輕鬆完成。這些對集合的每個元素進行操作的函式稱為map
函式(譯者注:map/reduce 函式)。我提供了以下程式。
package main
import (
"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)
}
複製程式碼
上述程式將輸出,
[25 30 35 40 45]
複製程式碼
快速回顧一下本教程的內容,
- 什麼是頭等函式
- 匿名函式
- 使用者定義的函式型別
- 高階函式
- 將函式作為引數傳遞給其他函式
- 從其他函式返回函式
- 閉包
- 頭等函式的實際用法