什么是 first class 函数?
支持 fist class 函数的语言允许将函数分配给变量,作为参数传递给其他函数并从其他函数返回。 Go 支持 first class 函数。
在本教程中,我们将讨论 first class 函数的语法和各种用例。
匿名函数
让我们从一个简单的例子开始,它将一个函数赋给变量。
package mainimport ("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 functionfunc()
也可以调用匿名函数而不将其赋值给变量。让我们看看如何在下面示例中完成此操作。
package mainimport ("fmt")func main() {func() {fmt.Println("hello world first class function")}()}
在上面的程序中,第 8 行定义了一个匿名函数,在函数定义之后,我们使用 () 调用函数。 该程序将输出,
hello world first class function
也可以像任何其他函数一样将参数传递给匿名函数。
package mainimport ("fmt")func main() {func(n string) {fmt.Println("Welcome", n)}("Gophers")}
在上面的程序中,第 10 行中字符串参数被传递给匿名函数。运行此程序将打印,
Welcome Gophers
用户定义的函数类型
就像我们定义自己的结构类型一样,可以定义我们自己的函数类型。
type add func(a int, b int) int
上面的代码片段创建了一个新的函数类型 add ,它接受两个整数参数并返回一个整数。现在我们可以定义 add 类型的变量。
package 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)}
在上面的程序中,第 10 行我们定义了一个类型为 add 的变量 a,并为其赋予一个签名与 add 类型匹配的函数。我们将该函数在第 13 行调用并将结果分配给 s。该程序将输出,
Sum 11
高阶函数
wiki 中的高阶函数的定义是至少具有下列功能之一的函数
- 将一个或多个函数作为参数
 
**
- 返回函数的结果
 
让我们看一下上面两个场景的一些简单示例。
将函数作为参数传递给其他函数
package 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)}
在上面的例子中,第 7 行我们定义了一个 simple 函数,它接受一个接受两个 int 参数并返回一个 int 作为参数的函数。 在 main 函数内部,第 12 行我们创建一个匿名函数f,其签名与函数 simple 的参数匹配。 我们调用 simple,并在下一行中将 f 作为参数传递给它。 该程序打印 67 作为输出。
从其他函数返回函数
现在让我们重写上面的程序,并从 simple 函数返回一个函数。
package 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))}
在上面的程序中,第 7 行中的 simple 函数返回一个函数,该函数接受两个 int  参数并返回一个 int 值。
这个 simple 函数是第 15 行调用的。 simple 的返回值被赋给 s,现在 s 包含了simple 函数返回的函数。我们调用 s 并在第 16 行传递两个 int 参数。这个程序输出 67。
闭包
闭包是匿名函数的一种特殊情况。闭包是匿名函数,它访问函数体外部定义的变量。
来一起看个例子就明白了。
package mainimport ("fmt")func main() {a := 5func() {fmt.Println("a =", a)}()}
在上面的程序中,第 10 行匿名函数访问存在于其体外的变量 a 。 因此,这个匿名函数是一个闭包。
每个闭包都绑定到它自己的周围变量。 让我们通过一个简单的例子来理解这是什么意思。
package mainimport ("fmt")func appendStr() func(string) string {t := "Hello"c := func(b string) string {t = t + " " + breturn 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。 现在,一个版本的 t 的值变成了 Hello World。
我们在 20 行传进参数 Everyone 调用 b 函数。 由于 b 绑定到自己的变量 t,因此 b 的 t 版本的初始值为 Hello 。 因此,在此函数调用之后,b 的 t 版本的值变为 Hello Everyone。 剩下的程序就一目了然了。
该程序将输出,
Hello WorldHello EveryoneHello World GopherHello Everyone !
first class函数的实际用途
到目前为止,我们已经明白 first class 函数的定义,并且我们已经通过一些例子来了解它们的工作原理。 现在让我们编写一个具体的程序,它显示了 first class 函数的实际用法。
我们将创建一个程序,根据某些标准过滤一部分学生。 让我们一步一步地解决这个问题。
首先让我们定义 student 类型。
type student struct {firstName stringlastName stringgrade stringcountry string}
下一步是编写 filter 函数。这个函数取一部分学生和一个函数作为参数,该函数确定学生是否匹配过滤标准。一旦我们写出这个函数,我们就能更好地理解。让我们开始吧。
func filter(s []student, f func(student) bool) []student {var r []studentfor _, v := range s {if f(v) == true {r = append(r, v)}}return r}
在上面的函数中,要过滤的第 2 个参数是一个以 student 为参数并返回 bool 的函数。 此函数确定特定学生是否符合标准。 我们在第 3 行遍历 student 切片,并将每个学生作为参数传递给函数 f。 如果返回 true,则表示学生已通过过滤条件,并将其添加到结果切片 r 中。 你可能对此函数的实际使用感到有些困惑,但是一旦我们完成该程序就会很清楚。 我添加了 main 函数,并在下面提供了完整的程序。
package mainimport ("fmt")type student struct {firstName stringlastName stringgrade stringcountry string}func filter(s []student, f func(student) bool) []student {var r []studentfor _, 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,然后在第 38 行作为参数传递给 filter 函数。 以上程序将输出,
[{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 并返回输出,可以使用 first class 函数轻松地完成。这种作用于集合中每个元素的函数称为 map 函数。我提供下面的程序,非常容易看懂。
package mainimport ("fmt")func iMap(s []int, f func(int) int) []int {var r []intfor _, 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]
下面简要回顾一下我们在本教程中学习的内容
- 什么是 first class 函数
 
- 用户定义函数类型
 
- 高阶函数
 
- 将函数作为参数传递给其他函数
 
- 从其他函数返回函数
 
- 闭包
 
- first class 函数的实际应用
 
