浅析Golang中闭包的创建与使用

 更新时间:2023年11月01日 15:52:22   作者:洛天枫  
闭包是包括 Go 在内的编程语言的一项强大功能,通过闭包,您可以在函数中封装数据,并通过函数的返回值访问这些数据,本文将介绍Go 中闭包的基础知识,希望对大家有所帮助

闭包是包括 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 中创建闭包的示例:

func adder() func(int) int { // 外部函数
	sum := 0
	return func(x int) int { // 内部函数
		fmt.Println("func sum: ", sum)
		sum += x
		return sum
	}
}

func main() {
	a := adder()
	fmt.Println(a(1))
	fmt.Println(a(2))
	fmt.Println(a(3))
}

在本例中,我们定义了一个返回匿名函数的加法器函数。匿名函数捕捉加法器函数中定义的 sum 变量的状态。每次调用匿名函数时,它都会将参数加到求和变量中,并返回结果。

所以其输出结果为:

func sum:  0
1
func sum:  1
3
func sum:  3
6

在 Go 中使用闭包

在 Go 中,闭包可用于多种用途,包括用函数封装数据、创建生成器、迭代器和 memoization 函数。

下面是一个使用闭包将数据与函数封装在一起的示例:

func makeGreeter(greeting string) func(string) string {
	return func(name string) string {
		fmt.Printf("func greeting: %s, name: %s\n", greeting, name)
		return greeting + ", " + name
	}
}

func main() {
	englishGreeter := makeGreeter("Hello")
	spanishGreeter := makeGreeter("Hola")

	fmt.Println(englishGreeter("John"))
	fmt.Println(englishGreeter("Tim"))
	fmt.Println(spanishGreeter("Juan"))
	fmt.Println(spanishGreeter("Taylor"))
}

在本例中,我们定义了一个名为 makeGreeter 的函数,它返回一个匿名函数。该匿名函数接收一个字符串参数,并返回一个将问候语和名称连接起来的字符串。我们创建了两个问候语程序,一个用于英语,一个用于西班牙语,然后用不同的名称调用它们。

所以其输出为:

func greeting: Hello, name: John
Hello, John
func greeting: Hello, name: Tim
Hello, Tim
func greeting: Hola, name: Juan
Hola, Juan
func greeting: Hola, name: Taylor
Hola, Taylor

替换捕获的变量

Go 闭包的强大功能之一是能够更改捕获的变量。这使得代码中的行为更加灵活和动态。下面是一个例子:

func makeCounter() func() int {
	i := 0
	return func() int {
		fmt.Println("func i: ", i)
		i++
		return i
	}
}

func main() {
	counter := makeCounter()
	fmt.Println(counter())
	fmt.Println(counter())
	fmt.Println(counter())
}

在本例中,makeCounter 函数返回一个闭包,每次调用都会使计数器递增。i 变量被闭包捕获,并可被修改以更新计数器。

所以其输出为:

func i:  0
1
func i:  1
2
func i:  2
3

逃逸变量

Go 闭包的另一个高级概念是变量逃逸分析。在 Go 中,变量通常在堆栈上分配,并在超出作用域时被去分配。然而,当变量被闭包捕获时,它必须在堆上分配,以确保在函数返回后可以访问它。这会导致性能开销,因此了解变量何时以及如何逃逸非常重要。

我们对比一下两个方法:

func makeAdder1(x1 int) func(int) int {
	return func(y1 int) int {
		return x1 + y1
	}
}

func makeAdder2(x2 int) func(int) int {
	fmt.Println(x2)
	return func(y2 int) int {
		return x2 + y2
	}
}

func main() {
	a := makeAdder1(5)
	fmt.Println(a(1))

	b := makeAdder2(6)
	fmt.Println(b(1))
}

makeAdder1 和 makeAdder2 的区别在于函数内的 x 是否被使用。

而我们通过逃逸分析:

go build -gcflags "-m" main.go

会得到以下输出:

./main.go:5:6: can inline makeAdder1
./main.go:6:9: can inline makeAdder1.func1
./main.go:13:9: can inline makeAdder2.func1
./main.go:12:13: inlining call to fmt.Println
./main.go:19:17: inlining call to makeAdder1
./main.go:6:9: can inline main.makeAdder1.func1
./main.go:20:15: inlining call to main.makeAdder1.func1
./main.go:20:13: inlining call to fmt.Println
./main.go:23:13: inlining call to fmt.Println
./main.go:6:9: func literal escapes to heap
./main.go:12:13: ... argument does not escape
./main.go:12:14: x2 escapes to heap
./main.go:13:9: func literal escapes to heap
./main.go:19:17: func literal does not escape
./main.go:20:13: ... argument does not escape
./main.go:20:15: ~R0 escapes to heap
./main.go:23:13: ... argument does not escape
./main.go:23:15: b(1) escapes to heap

从逃逸分析结果来看,x 变量被闭包捕获,必须在堆上分配。不过,如果 x 变量不被闭包之外的任何其他代码使用,编译器可以进行优化,将其分配到栈中。

共享闭包

最后,Go 中的闭包可以在多个函数之间共享,从而实现更高的灵活性和模块化代码。下面是一个例子:

type Calculator struct {
	add func(int, int) int
}

func NewCalculator() *Calculator {
	c := &Calculator{}
	c.add = func(x, y int) int {
		fmt.Printf("func x: %d, y: %d\n", x, y)
		return x + y
	}
	return c
}

func (c *Calculator) Add(x, y int) int {
	return c.add(x, y)
}

func main() {
	calc := NewCalculator()
	fmt.Println(calc.Add(1, 2))
	fmt.Println(calc.Add(2, 3))
}

在本例中,Calculator 结构具有一个 add 函数,该函数在 NewCalculator 函数中通过闭包进行了初始化。Calculator 结构的 Add 方法只需调用 add 函数,这样就可以在多个上下文中重复使用。

所以其输出为:

func x: 1, y: 2
3
func x: 2, y: 3
5

结论

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

到此这篇关于浅析Golang中闭包的创建与使用的文章就介绍到这了,更多相关go闭包内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Go语言中的值类型与引用类型的使用

    Go语言中的值类型与引用类型的使用

    Go语言中的数据类型可以分为值类型和引用类型,值类型包括基本和复合数据类型,特点是在传递时进行复制,保证数据的安全性,引用类型如slice、map等,传递的是引用副本,便于大型数据操作且提高性能,理解这些概念有助于编写更高效、安全的Go代码
    2024-09-09
  • GoLand编写 TCP 端口扫描器的详细过程

    GoLand编写 TCP 端口扫描器的详细过程

    TCP,也就是传输控制协议(Transmission Control Protocol),这篇文章主要介绍了Go语言(Golang)编写 TCP 端口扫描器,需要的朋友可以参考下
    2023-05-05
  • go语言VScode see 'go help modules' (exit status 1)问题的解决过程

    go语言VScode see 'go help modules' (exit statu

    最近上手学习go语言,准备在VSCode上写程序的时候却发现出了一点问题,下面这篇文章主要给大家介绍了关于go语言VScode see 'go help modules'(exit status 1)问题的解决过程,需要的朋友可以参考下
    2022-07-07
  • Go语言学习技巧之命名规范

    Go语言学习技巧之命名规范

    最近在学习go语言,发现了不少需要整理的知识点,所以整理下分享出来,下面这篇文章主要给大家介绍了关于Go语言学习技巧之命名规范的相关资料,文中通过示例代码介绍的非常详细,需要的朋友可以参考借鉴,下面来一起看看吧。
    2017-12-12
  • golang实现整型和字节数组之间的转换操作

    golang实现整型和字节数组之间的转换操作

    这篇文章主要介绍了golang实现整型和字节数组之间的转换操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-12-12
  • Golang中int类型和字符串类型相互转换的实现方法

    Golang中int类型和字符串类型相互转换的实现方法

    在日常开发中,经常需要将数字转换为字符串或者将字符串转换为数字,在 Golang 中,有一些很简便的方法可以实现这个功能,接下来就详细讲解一下如何实现 int 类型和字符串类型之间的互相转换,需要的朋友可以参考下
    2023-09-09
  • CGO编程基础快速入门

    CGO编程基础快速入门

    这篇文章主要为大家介绍了CGO编程基础快速入门示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-12-12
  • go语言使用range来接收通道里面的数据

    go语言使用range来接收通道里面的数据

    本文主要介绍了go语言使用range来接收通道里面的数据,for ... range 循环会一直从通道中接收值,直到通道关闭并且所有值都被接收完毕,下面就来介绍一下,感兴趣的可以了解一下
    2025-04-04
  • Go语言题解LeetCode599两个列表的最小索引总和

    Go语言题解LeetCode599两个列表的最小索引总和

    这篇文章主要为大家介绍了Go语言题解LeetCode599两个列表的最小索引总和示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-12-12
  • go开发alertmanger实现钉钉报警

    go开发alertmanger实现钉钉报警

    本文主要介绍了go开发alertmanger实现钉钉报警,通过自己的url实现alertmanager的钉钉报警,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-07-07

最新评论