分享
  1. 首页
  2. 文章

Go 的闭包操作

谢权 · · 1924 次点击 · · 开始浏览
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

闭包是什么,闭包是由函数及其相关的引用环境组合而成的实体(即:闭包=函数+引用环境)。

闭包包含着外部的环境变量值,但这个环境变量值并不像匿名函数那样作为参数副本,而是实实在在在的引用,当外部变量变化时,闭包能使用的值自然也就变化了。

闭包只是在形式和表现上像函数,但实际上不是函数。函数是一些可执行的代码,这些代码在函数被定义后就确定了,不会在执行时发生变化,所以一个函数只有一个实例。闭包在运行时可以有多个实例,不同的引用环境和相同的函数组合可以产生不同的实例。所谓引用环境是指在程序执行中的某个点所有处于活跃状态的约束所组成的集合。其中的约束是指一个变量的名字和其所代表的对象之间的联系。那么为什么要把引用环境与函数组合起来呢?这主要是因为在支持嵌套作用域的语言中,有时不能简单直接地确定函数的引用环境。

闭包小结:
函数只是一段可执行代码,编译后就"固化"了,每个函数在内存中只有一份实例,得到函数的入口点便可以执行函数了。在函数式编程语言中,函数是一等公民(First class value):第一类对象,我们不需要像命令式语言中那样借助函数指针,委托操作函数,函数可以作为另一个函数的参数或返回值,可以赋给一个变量。函数可以嵌套定义,即在一个函数内部可以定义另一个函数,有了嵌套函数这种结构,便会产生闭包问题。如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
packagemain
import(
"fmt"
)
func adder()func(int)int{
sum:=0
innerfunc:=func(xint)int{
sum+=x
returnsum
}
returninnerfunc
}
func main(){
pos,neg:=adder(),adder()
fori:=0;i<10;i++{
fmt.Println(pos(i),neg(-2*i))
}
}

在这段程序中,函数innerfunc是函数adder的内嵌函数,并且是adder函数的返回值。我们注意到一个问题:内嵌函数innerfunc中引用到外层函数中的局部变量sum,Go会这么处理这个问题呢?先让我们来看看这段代码的运行结果:

1
2
3
4
5
6
7
8
9
10
00
1-2
3-6
6-12
10-20
15-30
21-42
28-56
36-72
45-90

注意:Go不能在函数内部显式嵌套定义函数,但是可以定义一个匿名函数。如上面所示,我们定义了一个匿名函数对象,然后将其赋值给innerfunc,最后将其作为返回值返回。

当用不同的参数调用adder函数得到(pos(i),neg(i))函数时,得到的结果是隔离的,也就是说每次调用adder返回的函数都将生成并保存一个新的局部变量sum。其实这里adder函数返回的就是闭包。
这个就是Go中的闭包,一个函数和与其相关的引用环境组合而成的实体。一句关于闭包的名言: 对象是附有行为的数据,而闭包是附有数据的行为。

闭包经常用于回调函数,当IO操作(例如从网络获取数据、文件读写)完成的时候,会对获取的数据进行某些操作,这些操作可以交给函数对象处理。

除此之外,在一些公共的操作中经常会包含一些差异性的特殊操作,而这些差异性的操作可以用函数来进行封装。看下面的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
packagemain
import(
"errors"
"fmt"
)
typeTraveserfunc(eleinterface{})
/*
Process:封装公共切片数组操作
*/
funcProcess(arrayinterface{},traveser Traveser)error{
ifarray==nil{
returnerrors.New("nil pointer")
}
varlength int//数组的长度
switcharray.(type){
case[]int:
length=len(array.([]int))
case[]string:
length=len(array.([]string))
case[]float32:
length=len(array.([]float32))
default:
returnerrors.New("error type")
}
iflength==0{
returnerrors.New("len is zero.")
}
traveser(array)
returnnil
}
/*
具体操作:升序排序数组元素
*/
funcSortByAscending(eleinterface{}){
intSlice,ok:=ele.([]int)
if!ok{
return
}
length:=len(intSlice)
fori:=0;i<length-1;i++{
isChange:=false
forj:=0;j<length-1-i;j++{
ifintSlice[j]>intSlice[j+1]{
isChange=true
intSlice[j],intSlice[j+1]=intSlice[j+1],intSlice[j]
}
}
ifisChange==false{
return
}
}
}
/*
具体操作:降序排序数组元素
*/
funcSortByDescending(eleinterface{}){
intSlice,ok:=ele.([]int)
if!ok{
return
}
length:=len(intSlice)
fori:=0;i<length-1;i++{
isChange:=false
forj:=0;j<length-1-i;j++{
ifintSlice[j]<intSlice[j+1]{
isChange=true
intSlice[j],intSlice[j+1]=intSlice[j+1],intSlice[j]
}
}
ifisChange==false{
return
}
}
}
func main(){
intSlice:=make([]int,0)
intSlice=append(intSlice,3,1,4,2)
Process(intSlice,SortByDescending)
fmt.Println(intSlice)//[4 3 2 1]
Process(intSlice,SortByAscending)
fmt.Println(intSlice)//[1 2 3 4]
stringSlice:=make([]string,0)
stringSlice=append(stringSlice,"hello","world","china")
/*
具体操作:使用匿名函数封装输出操作
*/
Process(stringSlice,func(eleminterface{}){
ifslice,ok:=elem.([]string);ok{
forindex,value:=rangeslice{
fmt.Println("index:",index," value:",value)
}
}
})
floatSlice:=make([]float32,0)
floatSlice=append(floatSlice,1.2,3.4,2.4)
/*
具体操作:使用匿名函数封装自定义操作
*/
Process(floatSlice,func(eleminterface{}){
ifslice,ok:=elem.([]float32);ok{
forindex,value:=rangeslice{
slice[index]=value *2
}
}
})
fmt.Println(floatSlice)//[2.4 6.8 4.8]
}

输出结果:

1
2
3
4
5
6
[4321]
[1234]
index:0value:hello
index:1value:world
index:2value:china
[2.46.84.8]

在上面的例子中,Process函数负责对切片(数组)数据进行操作,在操作切片(数组)时候,首先要做一些参数检测,例如指针是否为空、数组长度是否大于0等。这些是操作数据的公共操作。具体针对数据可以有自己特殊的操作,包括排序(升序、降序)、输出等。针对这些特殊的操作可以使用函数对象来进行封装。
再看下面的例子,这个例子没什么实际意义,只是为了说明闭包的使用方式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
packagemain
import(
"fmt"
)
typeFilterFuncfunc(eleinterface{})interface{}
/*
公共操作:对数据进行特殊操作
*/
funcData(arrinterface{},filterFunc FilterFunc)interface{}{
slice:=make([]int,0)
array,_:=arr.([]int)
for_,value:=rangearray{
integer,ok:=filterFunc(value).(int)
ifok{
slice=append(slice,integer)
}
}
returnslice
}
/*
具体操作:奇数变偶数(这里可以不使用接口类型,直接使用int类型)
*/
funcEvenFilter(eleinterface{})interface{}{
integer,ok:=ele.(int)
ifok{
ifinteger%2==1{
integer=integer+1
}
}
returninteger
}
/*
具体操作:偶数变奇数(这里可以不使用接口类型,直接使用int类型)
*/
funcOddFilter(eleinterface{})interface{}{
integer,ok:=ele.(int)
ifok{
ifinteger%2!=1{
integer=integer+1
}
}
returninteger
}
func main(){
sliceEven:=make([]int,0)
sliceEven=append(sliceEven,1,2,3,4,5)
sliceEven=Data(sliceEven,EvenFilter).([]int)
fmt.Println(sliceEven)//[2 2 4 4 6]
sliceOdd:=make([]int,0)
sliceOdd=append(sliceOdd,1,2,3,4,5)
sliceOdd=Data(sliceOdd,OddFilter).([]int)
fmt.Println(sliceOdd)//[1 3 3 5 5]
}

输出结果:

1
2
[22446]
[13355]

上面例子中闭包的使用有点类似于面向对象设计模式中的模版模式,在模版模式中是在父类中定义公共的行为执行序列,然后子类通过重载父类的方法来实现特定的操作,而在Go语言中我们使用闭包实现了同样的效果。
其实理解闭包最方便的方法就是将闭包函数看成一个类,一个闭包函数调用就是实例化一个类(在Objective-c中闭包就是用类来实现的),然后就可以从类的角度看出哪些是"全局变量",哪些是"局部变量"。例如在第一个例子中,pos和neg分别实例化了两个"闭包类",在这个"闭包类"中有个"闭包全局变量"sum。所以这样就很好理解返回的结果了。

参考:
http://www.ibm.com/developerworks/cn/linux/l-cn-closure/index.html
http://www.cnblogs.com/kym/archive/2011/03/07/1976519.html

Go 的闭包操作

有疑问加站长微信联系(非本文作者)

本文来自:谢权SELF

感谢作者:谢权

查看原文:Go 的闭包操作

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

关注微信
1924 次点击
上一篇:(7)安装Go
下一篇:gdb 调试go
2 回复 | 直到 2019年06月15日 22:08:38
暂无回复
添加一条新回复 (您需要 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传

用户登录

没有账号?注册
(追記) (追記ここまで)

今日阅读排行

    加载中
(追記) (追記ここまで)

一周阅读排行

    加载中

关注我

  • 扫码关注领全套学习资料 关注微信公众号
  • 加入 QQ 群:
    • 192706294(已满)
    • 731990104(已满)
    • 798786647(已满)
    • 729884609(已满)
    • 977810755(已满)
    • 815126783(已满)
    • 812540095(已满)
    • 1006366459(已满)
    • 692541889

  • 关注微信公众号
  • 加入微信群:liuxiaoyan-s,备注入群
  • 也欢迎加入知识星球 Go粉丝们(免费)

给该专栏投稿 写篇新文章

每篇文章有总共有 5 次投稿机会

收入到我管理的专栏 新建专栏