Go语言支持函数作为一等公民,这意味着函数可以像普通变量一样被赋值、作为参数传递,也可以作为返回值返回,这种特性为高阶函数的实现提供了基础。高阶函数指的是至少满足以下一个条件的函数:接受函数作为参数,或者返回函数作为结果。

函数类型的定义
在Go语言中,要把函数作为参数传递,首先需要明确函数的类型。函数的类型由它的参数列表和返回值列表共同决定,只有类型匹配的函数才能互相赋值或者作为参数传递。函数类型的定义格式如下:
// 定义一个函数类型,接受两个int参数,返回一个int结果 type MathFunc func(int, int) int
上面的代码定义了一个名为MathFunc的函数类型,所有接受两个int参数、返回一个int结果的函数,都属于这个类型,可以作为MathFunc类型的变量使用。
将函数作为参数传递的实现
定义好函数类型之后,就可以在函数的参数列表中声明该类型的参数,从而实现函数作为参数传递。下面是一个简单的示例,实现一个通用的计算函数,接受不同的运算函数作为参数:
package main
import "fmt"
// 定义函数类型
type MathFunc func(int, int) int
// 加法函数,符合MathFunc类型
func add(a, b int) int {
return a + b
}
// 减法函数,符合MathFunc类型
func sub(a, b int) int {
return a - b
}
// 高阶函数,接受MathFunc类型的参数
func calculate(a, b int, op MathFunc) int {
// 调用传入的函数参数
return op(a, b)
}
func main() {
// 把add函数作为参数传递给calculate
result1 := calculate(10, 5, add)
fmt.Println("10 + 5 =", result1)
// 把sub函数作为参数传递给calculate
result2 := calculate(10, 5, sub)
fmt.Println("10 - 5 =", result2)
}
运行上面的代码,会输出以下结果:
10 + 5 = 15 10 - 5 = 5
匿名函数作为参数传递
除了已经定义好的具名函数,匿名函数也可以作为参数传递给高阶函数,这在只需要临时使用一次函数的场景下非常方便。下面的示例展示了匿名函数作为参数传递的用法:
package main
import "fmt"
type MathFunc func(int, int) int
func calculate(a, b int, op MathFunc) int {
return op(a, b)
}
func main() {
// 传递匿名函数作为参数,实现乘法运算
result := calculate(10, 5, func(a, b int) int {
return a * b
})
fmt.Println("10 * 5 =", result)
}
传递函数时的类型匹配要求
把函数作为参数传递时,必须保证传入的函数和参数声明的函数类型完全匹配,包括参数的类型、个数、顺序,以及返回值的类型、个数。如果有不匹配的情况,编译时会直接报错。比如下面的代码就会编译失败:
package main
type MathFunc func(int, int) int
func add(a, b int) int {
return a + b
}
func calculate(a, b int, op MathFunc) int {
return op(a, b)
}
func main() {
// 错误:传入的函数返回两个值,和MathFunc类型不匹配
wrongFunc := func(a, b int) (int, error) {
return a + b, nil
}
calculate(10, 5, wrongFunc) // 编译报错
}
常见使用场景
高阶函数把函数作为参数传递的特性,在实际开发中有很多应用场景:
- 实现通用的数据处理逻辑,比如遍历切片时传入不同的处理逻辑,避免重复编写遍历代码。
- 实现中间件模式,比如在Web框架中,把请求处理函数作为参数传递给中间件函数,实现请求的预处理和后处理。
- 实现回调机制,比如异步操作完成后,调用传入的回调函数处理操作结果。
注意事项
在使用函数作为参数传递时,需要注意几个问题:
- 如果传入的函数是
nil,调用该函数参数时会导致程序 panic,所以在高阶函数内部调用函数参数前,最好先判断参数是否为nil。 - 函数作为参数传递时,传递的是函数的引用,而不是函数的拷贝,所以在函数内部修改函数外部的变量时,需要注意变量的作用域和生命周期。
- 如果高阶函数需要返回传入的函数参数,或者把函数参数存储到全局变量中,需要注意闭包带来的变量捕获问题,避免出现预期之外的结果。