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语言进阶篇的前传,从明天开始我就将开始有关于接口以及面向对象的有关内容了,大家下篇文章见,最后大家如果感到有收获的话,还请三连一下吧!!!!

相关文章
|
5天前
|
存储 JSON 监控
Viper,一个Go语言配置管理神器!
Viper 是一个功能强大的 Go 语言配置管理库,支持从多种来源读取配置,包括文件、环境变量、远程配置中心等。本文详细介绍了 Viper 的核心特性和使用方法,包括从本地 YAML 文件和 Consul 远程配置中心读取配置的示例。Viper 的多来源配置、动态配置和轻松集成特性使其成为管理复杂应用配置的理想选择。
23 2
|
3天前
|
Go 索引
go语言中的循环语句
【11月更文挑战第4天】
11 2
|
3天前
|
Go C++
go语言中的条件语句
【11月更文挑战第4天】
14 2
|
6天前
|
监控 Go API
Go语言在微服务架构中的应用实践
在微服务架构的浪潮中,Go语言以其简洁、高效和并发处理能力脱颖而出,成为构建微服务的理想选择。本文将探讨Go语言在微服务架构中的应用实践,包括Go语言的特性如何适应微服务架构的需求,以及在实际开发中如何利用Go语言的特性来提高服务的性能和可维护性。我们将通过一个具体的案例分析,展示Go语言在微服务开发中的优势,并讨论在实际应用中可能遇到的挑战和解决方案。
|
3天前
|
Go
go语言中的 跳转语句
【11月更文挑战第4天】
10 4
|
3天前
|
JSON 安全 Go
Go语言中使用JWT鉴权、Token刷新完整示例,拿去直接用!
本文介绍了如何在 Go 语言中使用 Gin 框架实现 JWT 用户认证和安全保护。JWT(JSON Web Token)是一种轻量、高效的认证与授权解决方案,特别适合微服务架构。文章详细讲解了 JWT 的基本概念、结构以及如何在 Gin 中生成、解析和刷新 JWT。通过示例代码,展示了如何在实际项目中应用 JWT,确保用户身份验证和数据安全。完整代码可在 GitHub 仓库中查看。
14 1
|
5天前
|
Go 调度 开发者
探索Go语言中的并发模式:goroutine与channel
在本文中,我们将深入探讨Go语言中的核心并发特性——goroutine和channel。不同于传统的并发模型,Go语言的并发机制以其简洁性和高效性著称。本文将通过实际代码示例,展示如何利用goroutine实现轻量级的并发执行,以及如何通过channel安全地在goroutine之间传递数据。摘要部分将概述这些概念,并提示读者本文将提供哪些具体的技术洞见。
|
16天前
|
Go 数据安全/隐私保护 开发者
Go语言开发
【10月更文挑战第26天】Go语言开发
32 3
|
17天前
|
Java 程序员 Go
Go语言的开发
【10月更文挑战第25天】Go语言的开发
26 3
|
3月前
|
JSON 中间件 Go
go语言后端开发学习(四) —— 在go项目中使用Zap日志库
本文详细介绍了如何在Go项目中集成并配置Zap日志库。首先通过`go get -u go.uber.org/zap`命令安装Zap,接着展示了`Logger`与`Sugared Logger`两种日志记录器的基本用法。随后深入探讨了Zap的高级配置,包括如何将日志输出至文件、调整时间格式、记录调用者信息以及日志分割等。最后,文章演示了如何在gin框架中集成Zap,通过自定义中间件实现了日志记录和异常恢复功能。通过这些步骤,读者可以掌握Zap在实际项目中的应用与定制方法
130 1
go语言后端开发学习(四) —— 在go项目中使用Zap日志库