Go语言中的闭包:封装数据与功能的强大工具

打印 上一主题 下一主题

主题 1799|帖子 1799|积分 5397

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

x
闭包是包括 Go 在内的编程语言的一项强大功能。通过闭包,您可以在函数中封装数据,并通过函数的返回值访问这些数据。在本文中,我们将介绍 Go 中闭包的基础知识,包括它们是什么、怎样工作以及怎样有效地使用它们。
什么是闭包?

go官方有一句解释:
   Function literals are closures: they may refer to variables defined in a surrounding function. Those variables are then shared between the surrounding function and the function literal, and they survive as long as they are accessible.
  翻译过来就是:
   函数字面量(匿名函数)是闭包:它们可以引用在周围函数中界说的变量。然后,这些变量在周围的函数和函数字面量之间共享,只要它们还可以访问,它们就会继续存在。
  闭包是一种创建函数的方法,这些函数可以访问在其主体之外界说的变量。闭包是一个可以捕捉其周围情况状态的函数。这意味着函数可以访问不在其参数列表中或在其主体中界说的变量。闭包函数可以在外部函数返回后访问这些变量。
在 Go 中创建闭包

在 Go 中,您可以使用匿名函数创建闭包。创建闭包时,函数会捕获其周围情况的状态,包括外部函数中界说的任何变量。闭包函数可以在外部函数返回后访问这些变量。
下面是一个在 Go 中创建闭包的示例:
  1. func adder() func(int) int { // 外部函数
  2.         sum := 0
  3.         return func(x int) int { // 内部函数
  4.                 fmt.Println("func sum: ", sum)
  5.                 sum += x
  6.                 return sum
  7.         }
  8. }
  9. func main() {
  10.         a := adder()
  11.         fmt.Println(a(1))
  12.         fmt.Println(a(2))
  13.         fmt.Println(a(3))
  14. }
复制代码
在本例中,我们界说了一个返回匿名函数的加法器函数。匿名函数捕捉加法器函数中界说的 sum 变量的状态。每次调用匿名函数时,它都会将参数加到求和变量中,并返回结果。
以是其输出结果为:
  1. func sum:  0
  2. 1
  3. func sum:  1
  4. 3
  5. func sum:  3
  6. 6
复制代码
在 Go 中使用闭包

在 Go 中,闭包可用于多种用途,包括用函数封装数据、创建天生器、迭代器和 memoization 函数。
下面是一个使用闭包将数据与函数封装在一起的示例:
  1. func makeGreeter(greeting string) func(string) string {
  2.         return func(name string) string {
  3.                 fmt.Printf("func greeting: %s, name: %s\n", greeting, name)
  4.                 return greeting + ", " + name
  5.         }
  6. }
  7. func main() {
  8.         englishGreeter := makeGreeter("Hello")
  9.         spanishGreeter := makeGreeter("Hola")
  10.         fmt.Println(englishGreeter("John"))
  11.         fmt.Println(englishGreeter("Tim"))
  12.         fmt.Println(spanishGreeter("Juan"))
  13.         fmt.Println(spanishGreeter("Taylor"))
  14. }
复制代码
在本例中,我们界说了一个名为 makeGreeter 的函数,它返回一个匿名函数。该匿名函数吸收一个字符串参数,并返回一个将问候语和名称连接起来的字符串。我们创建了两个问候语程序,一个用于英语,一个用于西班牙语,然后用不同的名称调用它们。
以是其输出为:
  1. func greeting: Hello, name: John
  2. Hello, John
  3. func greeting: Hello, name: Tim
  4. Hello, Tim
  5. func greeting: Hola, name: Juan
  6. Hola, Juan
  7. func greeting: Hola, name: Taylor
  8. Hola, Taylor
复制代码
更换捕获的变量

Go 闭包的强大功能之一是可以或许更改捕获的变量。这使得代码中的举动更加灵活和动态。下面是一个例子:
  1. func makeCounter() func() int {
  2.         i := 0
  3.         return func() int {
  4.                 fmt.Println("func i: ", i)
  5.                 i++
  6.                 return i
  7.         }
  8. }
  9. func main() {
  10.         counter := makeCounter()
  11.         fmt.Println(counter())
  12.         fmt.Println(counter())
  13.         fmt.Println(counter())
  14. }
复制代码
在本例中,makeCounter 函数返回一个闭包,每次调用都会使计数器递增。i 变量被闭包捕获,并可被修改以更新计数器。
以是其输出为:
  1. func i:  0
  2. 1
  3. func i:  1
  4. 2
  5. func i:  2
  6. 3
复制代码
逃逸变量

Go 闭包的另一个高级概念是变量逃逸分析。在 Go 中,变量通常在堆栈上分配,并在超出作用域时被去分配。然而,当变量被闭包捕获时,它必须在堆上分配,以确保在函数返回后可以访问它。这会导致性能开销,因此了解变量何时以及怎样逃逸非常重要。
我们对比一下两个方法:
  1. func makeAdder1(x1 int) func(int) int {
  2.         return func(y1 int) int {
  3.                 return x1 + y1
  4.         }
  5. }
  6. func makeAdder2(x2 int) func(int) int {
  7.         fmt.Println(x2)
  8.         return func(y2 int) int {
  9.                 return x2 + y2
  10.         }
  11. }
  12. func main() {
  13.         a := makeAdder1(5)
  14.         fmt.Println(a(1))
  15.         b := makeAdder2(6)
  16.         fmt.Println(b(1))
  17. }
复制代码
makeAdder1 和 makeAdder2 的区别在于函数内的 x 是否被使用。
而我们通过逃逸分析:
  1. go build -gcflags "-m" main.go
复制代码
会得到以下输出:
  1. ./main.go:5:6: can inline makeAdder1
  2. ./main.go:6:9: can inline makeAdder1.func1
  3. ./main.go:13:9: can inline makeAdder2.func1
  4. ./main.go:12:13: inlining call to fmt.Println
  5. ./main.go:19:17: inlining call to makeAdder1
  6. ./main.go:6:9: can inline main.makeAdder1.func1
  7. ./main.go:20:15: inlining call to main.makeAdder1.func1
  8. ./main.go:20:13: inlining call to fmt.Println
  9. ./main.go:23:13: inlining call to fmt.Println
  10. ./main.go:6:9: func literal escapes to heap
  11. ./main.go:12:13: ... argument does not escape
  12. ./main.go:12:14: x2 escapes to heap
  13. ./main.go:13:9: func literal escapes to heap
  14. ./main.go:19:17: func literal does not escape
  15. ./main.go:20:13: ... argument does not escape
  16. ./main.go:20:15: ~R0 escapes to heap
  17. ./main.go:23:13: ... argument does not escape
  18. ./main.go:23:15: b(1) escapes to heap
复制代码
从逃逸分析结果来看,x 变量被闭包捕获,必须在堆上分配。不过,如果 x 变量不被闭包之外的任何其他代码使用,编译器可以进行优化,将其分配到栈中。
共享闭包

末了,Go 中的闭包可以在多个函数之间共享,从而实现更高的灵活性和模块化代码。下面是一个例子:
  1. type Calculator struct {
  2.         add func(int, int) int
  3. }
  4. func NewCalculator() *Calculator {
  5.         c := &Calculator{}
  6.         c.add = func(x, y int) int {
  7.                 fmt.Printf("func x: %d, y: %d\n", x, y)
  8.                 return x + y
  9.         }
  10.         return c
  11. }
  12. func (c *Calculator) Add(x, y int) int {
  13.         return c.add(x, y)
  14. }
  15. func main() {
  16.         calc := NewCalculator()
  17.         fmt.Println(calc.Add(1, 2))
  18.         fmt.Println(calc.Add(2, 3))
  19. }
复制代码
在本例中,Calculator 结构具有一个 add 函数,该函数在 NewCalculator 函数中通过闭包进行了初始化。Calculator 结构的 Add 方法只需调用 add 函数,这样就可以在多个上下文中重复使用。
以是其输出为:
  1. func x: 1, y: 2
  2. 3
  3. func x: 2, y: 3
  4. 5
复制代码
结论

在 Go 编程中,闭包是一个强大的工具,可用于用函数封装数据,并创建天生器和迭代器等。它们提供了一种访问函数体外界说的变量的方法,纵然在函数返回后也是如此。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

去皮卡多

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表