Go语言中的匿名函数和闭包,简单理解

匿名函数是一种没有函数名的函数,即定义即使用;闭包作为一种携带状态的函数,我们可以简单地将它理解为“对象”,因为它同时具备状态和行为。

匿名函数没有函数名,只有函数体,它只有在被调用的时候才会初始化。匿名函数一般被当作一种类型赋值给函数类型的变量,经常被用作回调函数。

Go语言的匿名函数的声明样式如下所示:

func(params)(return params) {
      function body
}

匿名函数的声明与普通函数的定义基本一致,只是没有名字。我们可以在匿名函数声明之后直接调用它,如下所示:

func (name string) {
      fmt.Println("My name is ", name)
}('小明')

在声明匿名函数之后,在其后加上调用的参数列表,即可对匿名函数进行调用。除此之外,我们还可以将匿名函数赋值给函数的变量,用于多次调用或者求值,如下所示:

currentTime := func() {
      fmt.Println(time.Now())
}
// 调用匿名函数
currentTime()

上述例子中,通过匿名函数实现了一个简单的报时器,并赋值给 currentTime,我们每次调用 currentTime 都能知道当前系统的最新时间。

匿名函数一个比较常用的场景是用作回调函数。

实例:使用回调函数处理字符串

它接收 string 和匿名函数的参数输入,然后使用匿名函数对 string 进行处理。

package main

import "fmt"

func proc(input string, proessor func(str string)) {
      // 调用匿名函数
      processor(input)
}

func main() {
      proc("小明", func(str string) {
            for _, v := range str {
                  fmt.Printf("%c\n", v)
            }
      })

闭包

所谓闭包指的是引用了自由变量(未绑定到特定对象的变量,通常在函数外定义)的函数,被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的上下文环境也不会被释放(比如传递到其他函数或对象中)。简单来说,「闭」的意思是「封闭外部状态」,即使外部状态已经失效,闭包内部依然保留了一份从外部引用的变量。

显然,闭包只能通过匿名函数实现,我们可以把闭包看作是有状态的匿名函数,反过来,如果匿名函数引用了外部变量,就形成了一个闭包(Closure)。

闭包的价值在于可以作为持有外部变量的函数对象或者匿名函数,对于类型系统而言,这意味着不仅要表示数据还要表示代码。支持闭包的语言都将函数作为第一类对象(firt-class object,有的地方也译作第一级对象、一等公民等,都是一个意思),Go 语言也不例外,这意味 Go 函数和普通 Go 数据类型(整型、字符串、数组、切片、字典、结构体等)具有同等的地位,可以赋值给变量,也可以作为参数传递给其他函数,还能够被函数动态创建和返回。

闭包是携带状态的函数,它是将函数内部和函数外部连接起来的桥梁。通过闭包,我们可以读取函数内部的变量。我们也可以使用闭包封装私有状态,让它们常驻于内存当中。

闭包能够引用其作用域上部的变量并进行修改,被捕获到闭包中的变量将随着闭包的生命周期一直存在,函数本身是不存在信息的,但是闭包中的变量使闭包本身具备了存储信息的能力。

注:所谓第一类对象指的是运行期可以被创建并作为参数传递给其他函数或赋值给变量的实体,在绝大多数语言中,数值和基本类型都是第一类对象,在支持闭包的编程语言中(比如 Go、PHP、JavaScript、Python 等),函数也是第一类对象,而像 C、C++ 等不支持匿名函数的语言中,函数不能在运行期创建,所以在这些语言中,函数不是不是第一类对象。

实例:利用闭包的特效实现一个简单的计数器

package main

import "fmt"

func createCounter(initial int) func() int {
      if initial < 0 {
            initial = 0
      }

      // 引用 initial,创建一个闭包
      return func() int {
            initial++
            // 返回当前计数
            return initial;
      }
}

func main() {
      // 计数器 1
      c1 := createCounter(1)

      fmt.Println(c1())  // 2
      fmt.Println(c2()) // 3

      // 计数器 2
      c2 := createCounter(10)

      fmt.Println(c2()) // 11
      fmt.Println(c1()) // 4
}

不同的闭包之间变量不会互相干扰,c1和c2两个计数器都是独立进行计数。