1 简介
Go 语言提供了一个称为匿名函数的特殊功能。匿名函数可以形成一个闭包。闭包是一种特殊类型的匿名函数,它引用在函数本身之外声明的变量。它类似于访问在函数声明之前可用的全局变量。这意味着这些函数几乎可以在任何地方使用。

这将为函数创建一个新属性,以了解其周围环境。此属性称为 闭包closure 属性。
- 什么是 Golang 中的闭包?
当 Go 中的匿名函数可以访问其周围环境时,就会发生闭包。然后它可以保持自己的独特状态。 然后,当我们创建函数的新实例时,之前实例的状态将是独立的。
2 变量的范围
匿名函数,嵌套函数 和 返回函数都可以访问局部块变量,如果不使用局部变量隔离数据,那么就使用全局变量(package var)。
csharp
var x = 0
func increment() int {
x++
return x
}
func main() {
fmt.Println(increment())
fmt.Println(increment())
}
全局变量 x 未作为参数传递给匿名函数,但该函数可以访问它。
在这个例子中,有一个小问题,因为将在package中定义的任何其他函数都可以访问全局变量 x,并且它可以在不调用 increment 函数的情况下更改它。
而闭包还提供了另一个方面,即数据隔离,我们来了解它。
css
func main() {
x := 42
fmt.Println(x)
{
fmt.Println(x)
y := "功劳属于在擂台上的人。"
fmt.Println(y)
}
// fmt.Println(y) // outside scope of y
}
可以看到,我们也可以直接在main函数使用匿名函数,如果要在外部函数使用 闭包内部的变量就会报错。
3 匿名函数和嵌套函数
- 嵌套函数
当我们在函数中创建另一个函数时,这称之为嵌套函数,以下main函数中通过创建匿名函数访问main函数变量x。
可以直接在函数中创建内部函数并且立即调用它。如此在另一个函数中创建一个函数。这称为嵌套函数。例如
go
package main
import "fmt"
// outer function
func Hello(name string) {
// inner function
var displayName = func() {
fmt.Println("Hi", name)
}
// call inner function
displayName()
}
func main() {
// call outer function
Hello("Frank") // Hi Frank
}
在该示例中,我们在函数Hello内部创建了一个匿名函数displayName = func(){...}, 在这里displayName它是一个嵌套函数。嵌套函数displayName的工作方式与普通函数类似。它在函数Hello内部。
这里 是一个嵌套函数。嵌套函数的工作方式与普通函数类似。它在函数 var displayName = func() {...}displayName()
4 返回匿名函数
同样的,我们可以设置匿名函数为外部函数的返回值,也就是可以创建一个返回匿名函数的函数
go
package main
import "fmt"
func Hello() func() {
return func() {
fmt.Println("Hi Frank")
}
}
func main() {
h1 := Hello()
h1()
}
输出
markdown
Hi Frank
在上面的示例中,我们创建了函数 Hello()。
go
func Hello() func() {...}
此处,大括号之前表示该函数返回另一个函数。func()
此外,如果你查看这个函数的 return 语句,我们可以看到该函数正在返回一个函数。
从 main() 中,我们调用函数Hello()。
css
h1 := Hello()
在这里,返回的函数现在被分配给变量。因此,执行嵌套的匿名函数。
5 闭包实现自然数包装器
Go 闭包是一个嵌套函数,它允许我们在外部函数关闭后访问外部函数的变量。 其中的嵌套函数返回一个函数,这里的一个嵌套函数示例。嵌套函数的工作方式与普通函数类似
go
func wrapper() func() int {
x := 0
return func() int {
x++
return x
}
}
func main() {
increment := wrapper()
fmt.Println(increment())
fmt.Println(increment())
}
在main函数调用完成运行之后,就创建了一个名为increment的函数,
css
increment := wrapper()
wrapper返回的函数现在已分配给变量 increment,它返回的函数也会引用变量 x 从0开始打印自然数,
但 increment() 函数之外的其他代码都无法访问此变量。 这就是在函数调用之间保持数据持久性的方式,同时将数据与其他代码隔离开来。
此时wrapper()这个外部函数的执行已完成,因此应销毁该变量。 但是,当我们使用increment时仍然可以使x递增并输出。 我们能够访问外部wrapper函数的变量x。
因为嵌套函数现在充当一个闭包,即使在执行外部函数之后,也会在其范围内关闭外部作用域变量。
因此闭包帮助我们限制多个函数使用的变量范围如果没有闭包,则两个或多个 func 可以访问同一个变量,该变量需要为包变量。
6 闭包实现斐波那契数列输出
那么如何使用闭包打印斐波那契数列,同时隔离数据呢?
go
package main
import "fmt"
func fib() func() int {
a := 1
f := 0
// returns inner function
return func() int {
f, a = a, f+a
return f
}
}
func main() {
// call the outer function
fdd := fib()
// call the inner function
for i := 0; i < 10; i++ {
fmt.Println("f :", fdd())
}
// call the outer function again
fdd2 := fib()
fmt.Println(fdd2())
}
此代码执行外部函数并返回 斐波那契数的闭包。 这就是为什么我们可以在完成外部函数fib后, 可以连续通过fdd 访问 f 的变量。 而再次调用外部fib函数后,有fdd2 := fib(),重新得到1
在上面的示例中,该fib()函数返回一个匿名函数,
go
return func() int {
f, a = a, f+a
return f
}
该函数将计算原有的变量 a和f,每次调用该匿名函数都将a的值赋予f,并修改其函数内部的变量a,然后并返回f。 这表明闭包数据是彼此隔离的。
7 小结
由此可以看到, 每次我们调用闭包外部函数时都会返回一个新的闭包。 在这里,每个返回的闭包都是彼此独立的,并且一个闭包的更改不会影响另一个闭包。
这有助于我们在彼此隔离的情况下处理多个数据。比如让我们看前面这个斐波那契数列的例子。
在需要数据隔离的地方,该闭包属性被广泛使用。它提供的数据状态使它们在这方面提供了极大的帮助。 当我们想创建一个状态封装的函数时,我们使用闭包来实现。