三十二.golang的函数是头等函数
本教程我们会讨论头等函数的语法和用例。
package main import ( "fmt" ) func main() { a := func() { fmt.Println("hello world first class function") } a() fmt.Printf("%T", a) }
在上面的程序中,我们将一个函数赋值给了变量 a(第 8 行)。这是把函数赋值给变量的语法。你如果观察得仔细的话,会发现赋值给 a 的函数没有名称。由于没有名称,这类函数称为匿名函数(Anonymous Function)。
调用该函数的唯一方法就是使用变量 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") }() }
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) }
Sum 11
高阶函数(Hiher-order Function)定义为:满足下列条件之一的函数:
-
接收一个或多个函数作为参数
-
针对上述两种情况,我们看看一些简单实例。
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) }
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 函数。我们把 simple 的返回值赋值给了 s。现在 s 包含了 simple 函数返回的函数。我们调用了 s
闭包(Closure)是匿名函数的一个特例。当一个匿名函数所访问的变量定义在函数体的外部时,就称这样的匿名函数为闭包。
看看一个示例就明白了。
package main import ( "fmt" ) func main() { a := 5 func() { fmt.Println("a =", a) }() }
在上面的程序中,匿名函数在第 10 行访问了变量 a,而 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。我们来理解这是什么意思。
在第 17 行和第 18 行声明的变量 a 和 b 都是闭包,它们绑定了各自的 t 值。
我们首先用参数 World 调用了 a。现在 a 中 t 值变为了 Hello World。
在第 20 行,我们又用参数 Everyone 调用了 b。由于 b 绑定了自己的变量 t,因此 b 中的 t
该程序会输出:
Hello World
Hello Everyone
Hello World Gopher
Hello Everyone !
迄今为止,我们已经定义了什么是头等函数,也看了一些专门设计的示例,来学习它们如何工作。现在我们来编写一些实际的程序,来展现头等函数的实际用处。
我们会创建一个程序,基于一些条件,来过滤一个 students 切片。现在我们来逐步实现它。
首先定义一个 student 类型。
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 }
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 函数中,我们首先创建了两个学生 s1 和 s2,并将他们添加到了切片 s。现在假设我们想要查询所有成绩为 B 的学生。为了实现这样的功能,我们传递了一个检查学生成绩是否为 B 的函数,如果是,该函数会返回 true。我们把这个函数作为参数传递给了 filter 函数(第 38 行)。上述程序会输出:
[{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
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]