045-函数值(Function Value)及匿名函数(Anonymous Function)

在 Go 里,你每声明定义一个函数,就意味着产生了一个函数值。函数值就像其它类型的值一样,可以进行赋值,也可以作为函数的返回结果返回。

函数值在 Go 语言里被称为 first-class value (第一等值)。相比那些什么 int 类型的值,float 类型的值来说,函数值的地位就显的要高的多,而且也重要的多。

在讲解函数值前,需要普及一些基本的概念。

1. 基本概念

1.1 函数值与函数签名

函数值,既然是值,那么它就一定有类型。函数值的类型,也称为函数签名(signature). 举个例子:

func add(x int, y int) int {
    return x + y
}

上面的函数 add 的函数值可以通过 add 取到,比如你可以:

val := add

这样就可以拿到 add 函数的值了。那 val 变量的类型(签名)是什么呢?这非常好区分,有一个简单的办法,把函数声明里所有的名字去掉(包括函数名,参数名,返回值名),就是函数类型了。比如上面 val 的类型就是:

func(int, int) int

当然了 add 也是函数值,它的类型也是上面那样。既然这样,那你就可以通过func(int, int) int声明一个这样类型的变量:

var f func(int, int) int
f = add

如果有另一个函数:

func sub(x int, y int) int {
    return x - y
}

sub 函数值的签名也是 func(int, int) int,也就是说它和 add 函数拥有相同的函数签名,但是 sub 函数和 add 函数的函数值不同。

聪明的同学一定能联想到 C/C++ 中的函数类型了,比如在 C 里,你可以定义一个函数类型,再使用该函数类型去声明和定义新的函数指针变量了:

typedef int (*MyFunc)(int, int);
int add(int x, int y) {
    return x + y;
}
MyFunc f = add;

1.2 函数值的特性

  • 正如 int 类型的零值是 0,map 类型的零值是 nil 一样,函数类型的零值是 nil.

  • 函数值是可以被调用的,就像调用普通函数一样。如果调用一个 nil 值的函数值,会引起 panic 错误。举几个调用函数值的例子:

package main
import "fmt"

func add(x int, y int) int {
    return x + y
}
func sub(x int, y int) int {
    return x - y
}

func main() {
    var f func(int, int) int
    f = add
    fmt.Println(f(5, 4)) // Output: 9
    f = sub
    fmt.Println(f(5, 4)) // Output: 1
}
  • 函数值是不能比较大小的,也不能互相比较相等。但是函数值可以和 nil 进行比较(函数值是一种引用类型)。例如上面的例子中的函数 add 和 sub:
fmt.Println(add == sub) // not ok! 函数值之间不能比较

// 下面的使用方法都是 ok 的
if add != nil { // ok
    z := add(1, 2)
}

fmt.Println(f != nil) // ok

2. 匿名函数(Anonymous Function)

前面我们遇到的所有函数都是有名函数。有名函数,只能在包级作用域声明。例如下面的声明是错误的:

func main() {
    // not ok! Go 里会报语法错误
    func add(x, y int) int {
        return x + y
    }
}

但是如果函数没有名字,则可以在函数体内声明:

// 下面是合法的
func main() {
    add := func(x, y int) int {
        return x + y
    }
}

有人会问,上面的函数不是有名字吗,叫 add。不,这不一样,我说的是函数名字,是指 func 关键字后面的名字。如果 func 关键字后面不写函数名,那就是一个匿名函数。在上面的例子里,我们只是将这个匿名函数的函数值赋值给了一个局部变量 add.

这样一来,我们又可以愉快的调用 add 函数了。

3. 闭包(closure)

闭包,我个人理解如下:

  • 它是一个函数
  • 这个函数有状态

通常我们使用的函数都是无状态的,也就是说,只要你给相同的输入,那这个函数的输出结果每次一定都是一样的。

而有状态的函数却不一样,你每次调用,返回的结果可能都不一样。举个例子:

package main
import "fmt"

func step(x int) func() int{
    return func() int {
        x++
        return x
    }
}

func main() {
    f := step(10)
    fmt.Println(f()) // Output: 11
    fmt.Println(f()) // Output: 12
    fmt.Println(f()) // Output: 13
    fmt.Println(f()) // Output: 14
    fmt.Println(f()) // Output: 15
}

在上面的例子中,函数值 f 就是一个闭包,因为每次调用它,返回的结果都不一样。它就是一个有状态的函数。它的原理很简单,匿名函数可以访问它所在的词法块内的变量。比如 step 函数内的匿名函数,就可以访问到 step 函数词法块内的局部变量 x.

无论是哪种语言,闭包都是一种非常强大的技术。Go 天然支持了它,如虎添翼。

上面的例子从侧面说明了函数值不同于其它普通 int 类型值,函数值不仅仅是可以被调用,还存在状态!

4. 总结

  • 这一篇非常重要
  • 掌握函数值和函数类型的概念
  • 掌握匿名函数
  • 掌握闭包,理解什么是闭包

猜你喜欢

转载自blog.csdn.net/q1007729991/article/details/80377962