脚本之家,脚本语言编程技术及教程分享平台!
分类导航

Python|VBS|Ruby|Lua|perl|VBA|Golang|PowerShell|Erlang|autoit|Dos|bat|

服务器之家 - 脚本之家 - Golang - Go语言中的闭包详解

Go语言中的闭包详解

2022-07-16 19:49奋斗的大橙子 Golang

本文详细讲解了Go语言中的闭包,文中通过示例代码介绍的非常详细。对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

一、函数的变量作用域和可见性

1.全局变量在main函数执行之前初始化,全局可见

2.局部变量在函数内部或者if、for等语句块有效,使用之后外部不可见

3.全局变量和局部变量同名的情况下,局部变量生效。

4.可见性:

包内任何变量或函数都是能访问的。

包外的话,首字母大写是可以访问的,首字母小写的表示私有的不能被外部调用。

二、匿名函数

1.Go语言中函数也是一种类型,所以可以用一个函数类型的变量进行接收。

?
1
2
3
4
5
6
7
8
9
func anonyTest1(){
    fmt.Println("anonyTest1")
}
 
//将改函数赋值给一个变量f,执行f
func AnonyTest(){
    f:= anonyTest1
    f()
}

2.匿名函数就是不指定名称的函数,如下就是匿名函数的使用

?
1
2
3
4
5
6
7
8
9
10
11
func AnonyTest2(){
    f:= func() {
        fmt.Println("AnonyTest2")
    }
    f()
 
    //或者
    func() {
        fmt.Println("AnonyTest2...")
    }()
}

3.下面一个例子结合defer来看一下,这三个输出都是什么

?
1
2
3
4
5
6
7
8
9
10
11
12
13
func AnonyTest3(){
    var i=0
    defer func() {
        fmt.Printf("defer func i=%v \n",i)
    }()
 
     defer fmt.Printf("defer i=%v \n",i)
 
    for;i<10; i++{
    }
 
    fmt.Printf("i=%v \n",i)
}

从defer那篇文章我们知道 defer fmt.Printf("defer i=%v \n",i) 打印的就是i初始化后的值,最后一个也一定是for循环之后的值10,

主要就是匿名函数执行之后的值,有意思是10,说明访问了匿名函数外部的i,这就涉及到了闭包

运行结果如下:

i=10
defer i=0
defer func i=10

4.既然函数也是一种类型,那么就可以把函数当做参数进行输入、输出了。(感觉有点类似C#里面的委托)

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
func Calc(a,b int, op func(int,int)int) int {
    return op(a,b)
}
 
func add(a,b int) int{
    return a+b
}
 
func sub(a,b int)int{
    return  a-b
}
 
func AnonyTest4(){
 
    var a = 2
    var b = 1
 
    var x = Calc(a,b,add)
    var y = Calc(a,b,sub)
 
    fmt.Printf("x=%v, y=%v \n",x,y)
}

结果:

x=3, y=1

三、闭包

闭包是由函数和与其相关的引用环境组合而成的实体(好抽象,难理解啊)

?
1
2
3
4
5
6
7
func Adder() func(int) int{
    var x int
    return func(d int) int{
        x+=d
        return x
    }
}

像上面这段代码,我们可以看到定义了一个变量x,以及return中的匿名函数。我们可以看到匿名函数引用了外部的变量x,我们可以把这个x叫做自由变量。

换句话说,这个匿名函数和这个自由变量x组成了一个整体,只要是在这个整体的生命周期内这个x都是有效的。

下面使用一下这个Adder函数:

?
1
2
3
4
5
6
func ClosureDemo5(){
    var f = Adder()
    fmt.Printf("结果=%d\n",f(1))
    fmt.Printf("结果=%d\n",f(20))
    fmt.Printf("结果=%d\n",f(300))
}

执行结果

结果=1
 
结果=21
 
结果=321

正如上面所提到的,这个只要Addr() 也就是f这个对象没有消亡,那么f中的这个x就始终存在,也就是为什么第二次是21,第三次是321的原因了。

其他例子:

例子1:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
func Adder2(base int) func(int)int{
    return func(i int) int{
        base += i
        return base
    }
}
 
func main(){
    tmp1 := Adder2(10)
    fmt.Println(tmp1(1),tmp1(2))
 
    tmp2 := Adder2(100)
    fmt.Println(tmp2(10),tmp2(20))
}

这里Adder2接收一个int类型参数base,然后返回一个func,这里这个匿名函数里面引用了这个参数base,那么这个参数base和匿名函数就形成了一个整体。

后面我们 tmp1被赋值为 Adder2(10) ,那么在tmp1这个对象的生命周期内,base是被初始化为10且一直存在,所以结果是 11 和 13,同理后面是 110 和 130

例子2:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
func calc(base int) (func(int)int,func(int)int){
    add:= func(i int)int{
        base +=i
        return base
    }
    sub:= func(i int)int{
        base -= i
        return base
    }
    return add,sub
}
 
 
func main(){
    f1,f2 := calc(10)
    fmt.Println(f1(1),f2(2))
    fmt.Println(f1(3),f2(4))
    fmt.Println(f1(5),f2(6))
    fmt.Println(f1(7),f2(8))
}

分析一下:

这里base和 add以及sub的匿名函数也组成了一个实体也就是calc,所以在f1和f2的生命周期内,base一直存在,并被初始化成了10.

所以结果就是 f1(1) 就是10+1 =11 而 f2(2)就是 11-2 = 9,其他同理。

所以结果如下:

11 9 
12 8 
13 7 
14 6 

闭包的副作用!

?
1
2
3
4
5
6
7
8
9
func main(){
    for i:=0;i<5;i++{
        go func(x int){
            fmt.Println(x)
        }(i)
 
    }
    time.Sleep(time.Second)
}

上述代码应该结果是多少?我的猜想应该是0、1、2、3、4

但是实际结果是:

5
5
5
5
5

为什么会出现这样的情况?实际上面这里每一个go协程中的匿名函数和外部for循环的i也形成了闭包,因为for循环执行比较快,所以go还没来得及执行就变成5了。

我在每一个go协程之后加一个延时,结果就是0,1,2,3,4了。

?
1
2
3
4
5
6
7
8
9
func main(){
    for i:=0;i<5;i++{
        go func(){
            fmt.Println(i)
        }()
            time.Sleep(time.Second)
    }
    time.Sleep(time.Second)
}

结果如下

0
1
2
3
4

问题就在于不可能每次执行都进行延迟吧,所以需要做一件事情打破这个闭包。

?
1
2
3
4
5
6
7
8
9
func main(){
    for i:=0;i<5;i++{
        go func(x int){
            fmt.Println(x)
        }(i)
 
    }
    time.Sleep(time.Second)
}

这里把i当做参数传入到匿名函数中,保证了每次循环传的值都不一样。

到此这篇关于Go语言闭包的文章就介绍到这了。希望对大家的学习有所帮助,也希望大家多多支持服务器之家。

原文链接:https://www.cnblogs.com/dcz2015/p/10498343.html

延伸 · 阅读

精彩推荐
  • GolangGo中的条件语句Switch示例详解

    Go中的条件语句Switch示例详解

    Go的switch的基本功能和C、Java类似,switch 语句用于基于不同条件执行不同动作,每一个 case 分支都是唯一的,从上至下逐一测试,直到匹配为止,对Go条件语...

    俊king12442021-09-16
  • GolangGo 语言常见踩坑记

    Go 语言常见踩坑记

    For循环在我们日常编码中可能用的很多。在很多业务场景中我们都需要用for循环处理。但golang中的for循环在使用上需要注意一些问题,大家可否遇到。...

    NoSay7762021-10-28
  • Golanggolang逐行读取文件的操作

    golang逐行读取文件的操作

    这篇文章主要介绍了golang逐行读取文件的操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...

    思维的深度10862021-03-15
  • Golang手把手带你走进Go语言之常量解析

    手把手带你走进Go语言之常量解析

    这篇文章主要介绍了Go语言之常量解析,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下...

    我是小白呀8662021-09-19
  • Golanggolang中值类型/指针类型的变量区别总结

    golang中值类型/指针类型的变量区别总结

    golang的值类型和指针类型receiver一直是大家比较混淆的地方,下面这篇文章主要给大家总结介绍了关于golang中值类型/指针类型的变量区别的相关资料,文中...

    卢春风4392020-05-12
  • GolangGo 编译器已默认启用 -G=3,支持泛型

    Go 编译器已默认启用 -G=3,支持泛型

    Go 项目代码仓库昨日提交和合并的一个 PR 显示,Go 语言已在 cmd/compile 中默认启用 -G=3。...

    开源中国10372021-08-24
  • Golanggolang struct 实现 interface的方法

    golang struct 实现 interface的方法

    这篇文章主要介绍了golang struct 实现 interface的方法,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧 ...

    sanerersan4742020-05-17
  • Golanggo语言在请求http时加入自定义http header的方法

    go语言在请求http时加入自定义http header的方法

    这篇文章主要介绍了go语言在请求http时加入自定义http header的方法,实例分析了Go语言http请求的原理与操作技巧,需要的朋友可以参考下 ...

    令狐不聪8712020-04-22