关于Go语言中的闭包


原文链接: 关于Go语言中的闭包

定义

闭包(计算机科学)维基百科

没啥好说的,直接看百科就好。

package main

import (
	"fmt"
)

func main() {
	x := closure(10)
	y := x(1)
	fmt.Println(y)
}

// 闭包里传递的都是变量的引用而非值的拷贝。
// 可以发现输出的x的地址都是一样的。
// x:0xc420014178
// x:0xc420014178
// 11
func closure(x int) func(i int) int {
	fmt.Printf("x:%p\n", &x)
	return func(y int) int {
		fmt.Printf("x:%p\n", &x)
		return x + y
	}
}

闭包里传递的都是变量的引用而非值的拷贝。在上面的一段代码中,如果我们把注视去掉,可以发现输出的x的地址都是一样的。

支持闭包的编程

于是,克服了 C 语言函数指针弱点的功能,才能算是闭包。下面,用 Go 语言做例子:

package main

import "fmt"

func main() {
    f := extent()
    f()
    f()
}

func extent() func() {
    n := 0
    return func() {
        n++
        fmt.Println(n)
    }
}

输出:

1
2

按道理 extent() 函数已经执行完毕,变量 n 的作用域已经结束,但这里可以看到,extent() 结束后 n 依然存在。这就是生存周期,“闭包”封闭了函数的引用环境和函数对象本身,被封闭起来的的变量的生存周期和函数对象本身的相等。所以,也可以说,将“局部变量”这一环境封闭起来的结构称为闭包。Go 语言的函数对象显然就是闭包了。

闭包与面向对象

闭包就是把“数据”绑到“函数”上去,而我们通常还有另一种做法,将“函数”,或者说“方法”绑到“数据”上去,即面向对象的做法。上面用闭包实现的功能,同样可以用面向对象的方法实现:

package main

import "fmt"

func main() {
    f := extent{val:0}
    f.call()
    f.call()
}

type extent struct {
    val int
}

func (e *extent) call() {
    e.val++
    fmt.Println(e.val)
}

输出:

1
2

对象是在数据中以方法的形式内含了过程,而闭包是在过程中以环境的形式内含了数据。“闭包”和面向对象中的“对象”看作同一事物的正反两面,即如果可以用其中一种实现,那也必然可以用另一种实现。

参考

《代码的未来》[日]松本行弘(周自恒 译)
最后两句即摘自该书。

`