Go语言进阶篇——浅谈函数中的闭包

简介: Go语言进阶篇——浅谈函数中的闭包

什么是闭包

前言

在进入今天的课程前,我想和大家一起复习一下之前的一些概念,首先我们在有关函数的文章中介绍了函数是Go语言的一等公民,不同于其他语言,函数在Go语言里面扮演的角色很多,下面我们来看一下:

  1. 作为函数的参数来传递(也叫回调函数)
func printOneParam(p int) {
    fmt.Println(p)
}
func printOnePlus(p int) {
    fmt.Println(p + 1)
}
func funcParamOne(f func(a int)) {
    num := 100
    f(num)
}
func TestFuncParamOne(t *testing.T) {
    funcParamOne(printOneParam)
    funcParamOne(printOnePlus)
}
  1. 作为变量
package main
import "fmt"
func Print() func(){
    return func(){
    fmt.Println("Hello World")
  }
}
func main(){
  my_func:=Print()
  my_func()
}
  1. 单纯的返回值
func sum(a,b int){
    return a+b
}

像以上无论是func(a int)my_func还是a+b这样参数在Go语言中被称为function value,我们知道函数指令的生成在编译时期生成,我们需要找到函数入口的地址来执行它,但是其实function value并不直接指向函数指令入口,它作为指针指向的是一个在runtime库中的结构体,

runtime.funcval结构体

type funcval struct{
    fn uniptr  //指向函数入口的指针
    extra *uint8  //指向存储数据的指针
}

开始有一个小问题:在这里我们可以看到,在调用函数过程中,其实本质上是使用了有关二级指针来访问函数,这样看起来十分的繁琐,那这样设计的目的是什么呢?这就要提到我们今天的主角——闭包了

什么是闭包

关于什么是闭包,博主也查阅了很多资料于文章,首先我们来看一下百度百科给出的答案:

闭包包含自由(未绑定到特定对象)变量,这些变量不是在这个代码块内或者任何全局上下文中定义的,而是在定义代码块的环境中定义(局部变量)。“闭包” 一词来源于以下两者的结合:要执行的代码块(由于自由变量被包含在代码块中,这些自由变量以及它们引用的对象没有被释放)和为自由变量提供绑定的计算环境(作用域)
• 1

看这个大家有可能会感到很懵,我们来截取一下关键词,首先闭包里面要有在函数外部定义但是在函数内部被引用的自由变量,还有就是即使脱离了捕捉自由变量的上下文,闭包也可以正常使用这些自由变量

上面的乍一看可能还是会让人感觉非常晕,那就让我们来看下面这个例子:

package main
import "fmt"
func create_func () func() int{
  c:=2
  return func() int{
    return c
  } 
}
func main(){
  f1:=create_func()
  f2:=create_func()
  fmt.Println(f1())
  fmt.Println(f2())
}

这个函数的输出结果为:

2
2

这里的c也就是我们所说的自由变量了。

或许我说的大家还不是很明白,我来给大家分析一下这个代码里面的堆栈调用大家就明白了:

首先是函数栈,示例代码里面一共有两个函数栈,一个是main函数栈,一个是create函数栈

首先我们来看main函数栈一共两个局部变量分别是f1与f2,除此之外就是main函数的返回值

但是create函数里面存在闭包,所以除了局部变量和返回值以外,它会在堆中去创建一个funcval结构体,而这里面的funcval的值有两个:捕获的自由变量以及匿名函数的入口。每一次创建的结构体也会被赋值给f1与f2

扩展:当我们利用funcval去调用一个函数的时候其实是通过寄存器来读取结构体的地址继而调用函数,这里不做深入,大家有兴趣的话可以自行做了解。

示例分析

最后我们来看下面的例子:

func main() {
  sum := Sum(1, 2)
  fmt.Println(sum(3, 4))
  fmt.Println(sum(5, 6))
}
func Sum(a, b int) func(int, int) int {
  return func(int, int) int {
    return a + b
  }
}

我在没有学习今天讲的这些东西之前我以为它的输出应该是这样的:

3
7
11

但是实际上它的输出结果是

3
3
3

这是为什么呢,主要是因为这里的a,b都是自由变量,它们在Sum函数中被捕获的时候就被抓到堆里面去了,我们如果学过内存分配的话应该知道,堆里面的变量不会像栈变量随函数结束上面周期而被销毁,所以这里的a=1 b=2除非我们手动重新赋值,比如:

a=2
b=3

否则不会因为传入匿名函数的参数不同而改变,故输出值始终是3.

总结

闭包函数的处理方式十分多样,这里我也只是揭露了它的冰山一角,大家有兴趣的话可以继续做深入学习,但其实无论处理方式如何变化,其本质也只不过是为了保证捕获变量在外层函数与闭包函数的一致性,这是它的核心存在

尾语

本来有关闭包的部分我是想放在上一篇文章中的,但是有关捕获变量等问题一时间不是很明白,就决定将它单独的拿出来作为Go语言进阶篇的前传,从明天开始我就将开始有关于接口以及面向对象的有关内容了,大家下篇文章见,最后大家如果感到有收获的话,还请三连一下吧!!!!

相关文章
|
22天前
|
存储 Go 索引
go语言中数组和切片
go语言中数组和切片
35 7
|
21天前
|
Go 开发工具
百炼-千问模型通过openai接口构建assistant 等 go语言
由于阿里百炼平台通义千问大模型没有完善的go语言兼容openapi示例,并且官方答复assistant是不兼容openapi sdk的。 实际使用中发现是能够支持的,所以自己写了一个demo test示例,给大家做一个参考。
|
22天前
|
程序员 Go
go语言中结构体(Struct)
go语言中结构体(Struct)
94 71
|
21天前
|
存储 Go 索引
go语言中的数组(Array)
go语言中的数组(Array)
101 67
|
2天前
|
存储 监控 算法
员工上网行为监控中的Go语言算法:布隆过滤器的应用
在信息化高速发展的时代,企业上网行为监管至关重要。布隆过滤器作为一种高效、节省空间的概率性数据结构,适用于大规模URL查询与匹配,是实现精准上网行为管理的理想选择。本文探讨了布隆过滤器的原理及其优缺点,并展示了如何使用Go语言实现该算法,以提升企业网络管理效率和安全性。尽管存在误报等局限性,但合理配置下,布隆过滤器为企业提供了经济有效的解决方案。
27 8
员工上网行为监控中的Go语言算法:布隆过滤器的应用
|
22天前
|
存储 Go
go语言中映射
go语言中映射
33 11
|
13天前
|
Go 数据安全/隐私保护 UED
优化Go语言中的网络连接:设置代理超时参数
优化Go语言中的网络连接:设置代理超时参数
|
24天前
|
开发框架 Go 计算机视觉
纯Go语言开发人脸检测、瞳孔/眼睛定位与面部特征检测插件-助力GoFly快速开发框架
开发纯go插件的原因是因为目前 Go 生态系统中几乎所有现有的人脸检测解决方案都是纯粹绑定到一些 C/C++ 库,如 OpenCV 或 dlib,但通过 cgo 调用 C 程序会引入巨大的延迟,并在性能方面产生显著的权衡。此外,在许多情况下,在各种平台上安装 OpenCV 是很麻烦的。使用纯Go开发的插件不仅在开发时方便,在项目部署和项目维护也能省很多时间精力。
|
1月前
|
Go 数据安全/隐私保护 开发者
Go语言开发
【10月更文挑战第26天】Go语言开发
42 3
|
1月前
|
Java 程序员 Go
Go语言的开发
【10月更文挑战第25天】Go语言的开发
34 3