函数
在Go语言中,函数是一等(first-class)公民,函数类型也是一等的数据类型。
函数不但可以用于封装代码、分割功能、解耦逻辑,还可以化身为普通的值,在其他函数间传递、赋予变量、做类型判断和转换等等。函数值可以由此成为能够被随意传播的独立逻辑组件(或者说功能模块)。
一等公民
开头说的,函数是一等公民,函数类型是一等数据类型:
package main
import "fmt"
type calcFunc func(int, int) int
func add(x, y int) int {
return x + y
}
func sub(x, y int) int {
return x - y
}
func main() {
var f1, f2 calcFunc
f1, f2 = add, sub
fmt.Println(f1(1, 2))
fmt.Println(f2(100, 50))
}
这里,先声明了一个函数类型。在下面声明的两个函数的前面与caleFunc是一致的。因此都是caleFunc的一个实现。在main函数中,分别把两个函数赋值给caleFunc类型的变量f1和f2,然后可以调用它们。
这里书写函数签名的方式与函数声明的是一致的。只是紧挨在参数列表左边的不是函数名称,而是关键字func。这里函数名称和func互换了一下位置而已。
函数签名,就是函数的参数列表和结果列表的统称,它定义了可用来鉴别不同函数的那些特征,同时也定义了我们与函数交互的方式。
“函数是一等的公民”是函数式编程(functional programming)的重要特征。Go语言在语言层面支持了函数式编程。
高阶函数
高阶函数可能具有如下两个特点:
- 接收其他的函数作为参数传入
- 把其他的函数作为结果返回
函数只要满足了上面的任意一个特点,就可以说这个函数是一个高阶函数。高阶函数也是函数式编程中的重要概念和特征。
在上面的例子的基础上,写一个高阶函数,然后再main主函数里调用执行:
package main
import (
"errors"
"fmt"
)
type calcFunc func(int, int) int
func add(x, y int) int {
return x + y
}
func sub(x, y int) int {
return x - y
}
func calculate(x, y int, f calcFunc) (int, error) {
if f == nil {
return 0, errors.New("invalid calcFunc")
}
return f(x, y), nil
}
func main() {
res, err := calculate(1, 2, add)
if err != nil {
fmt.Println("ERROR:", err)
}
fmt.Println("Result:", res)
}
上面的calculate就是一个高阶函数,“接收其他的函数作为参数传入”的这种高阶函数。下面的示例是另外一种高阶函数“把其他的函数作为结果返回”:
package main
import (
"errors"
"fmt"
)
type calcFunc func(int, int) int
func add(x, y int) int {
return x + y
}
func sub(x, y int) int {
return x - y
}
type resFunc func(int, int) (int, error)
func genCalculator(f calcFunc) resFunc {
return func(x, y int) (int, error) {
if f == nil {
return 0, errors.New("invalid calcFunc")
}
return f(x, y), nil
}
}
func main() {
f := genCalculator(sub)
res, _ := f(10, 6)
fmt.Println("Result:", res)
}
闭包
自由变量,在一个函数中存在对外来标识符的引用。所谓的外来标识符,是既不代表当前函数的任何参数或结果,也不是函数内部声明的,它是直接从外边拿过来的。
上面的例子中的genCalculator函数内部,实际上就实现了一个闭包。而genCalculator函数也是一个高阶函数:
func genCalculator(f calcFunc) resFunc {
return func(x, y int) (int, error) {
if f == nil {
return 0, errors.New("invalid calcFunc")
}
return f(x, y), nil
}
}
genCalculator函数只做了一件事,那就是定义一个匿名的函数并把它作为结果值返回。而这个匿名的函数就是一个闭包函数。它里面使用的变量f既不代表它的任何参数或结果也不是它自己声明的,而是定义它的genCalculator函数的参数,所以是一个自由变量。而自由变量具体是什么,并不是在定义闭包的时候确定的,而是在genCalculator函数被调用的时候确定的。
函数是Go语言支持函数式编程的主要体现。我们可以通过“把函数传给函数”以及“让函数返回函数”来编写高阶函数,也可以用高阶函数来实现闭包,并以此做到部分程序逻辑的动态生成。
最后,还有一个闭包的典型例子:
package main
import "fmt"
func increment () func() {
var x int
return func () {
x ++
fmt.Println(x)
}
}
func main() {
f := increment()
f() // 打印1
f() // 打印2
f() // 打印3
}
主函数里,每次调用执行的结果都会变化。变量x属于闭包的一部分,但是是在闭包的函数外的,每次调用闭包后的x的状态都会保留下来。
有疑问加站长微信联系(非本文作者)