Files
the-way-to-go_ZH_CN/eBook/06.8.md
Haigang Zhou c42d8a340a 第六章修改 (#843)
Co-authored-by: Joe Chen <jc@unknwon.io>
2022-05-09 22:19:53 +08:00

101 lines
3.6 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 6.8 闭包
当我们不希望给函数起名字的时候,可以使用匿名函数,例如:`func(x, y int) int { return x + y }`
这样的一个函数不能够独立存在(编译器会返回错误:`non-declaration statement
outside function body`),但可以被赋值于某个变量,即保存函数的地址到变量中:`fplus := func(x, y int) int { return x + y }`,然后通过变量名对函数进行调用:`fplus(3,4)`
当然,您也可以直接对匿名函数进行调用:`func(x, y int) int { return x + y } (3, 4)`
下面是一个计算从 1 到 100 万整数的总和的匿名函数:
```go
func() {
sum := 0
for i := 1; i <= 1e6; i++ {
sum += i
}
}()
```
表示参数列表的第一对括号必须紧挨着关键字 `func`,因为匿名函数没有名称。花括号 `{}` 涵盖着函数体,最后的一对括号表示对该匿名函数的调用。
下面的例子展示了如何将匿名函数赋值给变量并对其进行调用([function_literal.go](examples/chapter_6/function_literal.go)
```go
package main
import "fmt"
func main() {
f()
}
func f() {
for i := 0; i < 4; i++ {
g := func(i int) { fmt.Printf("%d ", i) }
g(i)
fmt.Printf(" - g is of type %T and has value %v\n", g, g)
}
}
```
输出:
```
0 - g is of type func(int) and has value 0x681a80
1 - g is of type func(int) and has value 0x681b00
2 - g is of type func(int) and has value 0x681ac0
3 - g is of type func(int) and has value 0x681400
```
我们可以看到变量 `g` 代表的是 `func(int)`,变量的值是一个内存地址。
所以我们实际上拥有的是一个函数值:匿名函数可以被赋值给变量并作为值使用。
**练习 6.8**`main()` 函数中写一个用于打印 `Hello World` 字符串的匿名函数并赋值给变量 `fv`,然后调用该函数并打印变量 `fv` 的类型。
匿名函数像所有函数一样可以接受或不接受参数。下面的例子展示了如何传递参数到匿名函数中:
```go
func (u string) {
fmt.Println(u)
}(v)
```
请学习以下示例并思考([return_defer.go](examples/chapter_6/return_defer.go)):函数 `f` 返回时,变量 `ret` 的值是什么?
```go
package main
import "fmt"
func f() (ret int) {
defer func() {
ret++
}()
return 1
}
func main() {
fmt.Println(f())
}
```
变量 `ret` 的值为 `2`,因为 `ret++` 是在执行 `return 1` 语句后发生的。
这可用于在返回语句之后修改返回的 `error` 时使用。
**defer 语句和匿名函数**
关键字 `defer` (详见[第 6.4 节](06.4.md))经常配合匿名函数使用,它可以用于改变函数的命名返回值。
匿名函数还可以配合 `go` 关键字来作为 goroutine 使用(详见[第 14 章](14.0.md)和[第 16.9 节](16.9.md))。
匿名函数同样被称之为闭包(函数式语言的术语):它们被允许调用定义在其它环境下的变量。闭包可使得某个函数捕捉到一些外部状态,例如:函数被创建时的状态。另一种表示方式为:一个闭包继承了函数所声明时的作用域。这种状态(作用域内的变量)都被共享到闭包的环境中,因此这些变量可以在闭包中被操作,直到被销毁,详见[第 6.9 节](06.9.md) 中的示例。闭包经常被用作包装函数:它们会预先定义好 1 个或多个参数以用于包装,详见下一节中的示例。另一个不错的应用就是使用闭包来完成更加简洁的错误检查(详见[第 16.10.2 节](16.10.md))。
## 链接
- [目录](directory.md)
- 上一节:[将函数作为参数](06.7.md)
- 下一节:[应用闭包:将函数作为返回值](06.9.md)