说起golang闭包,在官方手册里面看过一次,没怎么用过,还是因为6哥经常用,阅读他的代码好多闭包,emmm,今天就学习一下。

在过去近十年时间里,面向对象编程大行其道,以至于在大学的教育里,老师也只会教给我们两种编程模型,面向过程和面向对象。孰不知,在面向对象思想产生之前,函数式编程已经有了数十年的历史。就让我们回顾这个古老又现代的编程模型,看看究竟是什么魔力将这个概念在21世纪的今天再次拉入我们的视野

闭包是函数式编程语言中的概念,没有研究过函数式语言的人可能很难理解闭包的强大(我就是其中一个,看见的第一眼就是一脸懵逼)

闭包=函数+引用环境

所谓闭包是指内层函数引用了外层函数中的变量或称为引用了自由变量的函数,其返回值也是一个函数,了解过的语言中有闭包概念的像 js,python,golang 都类似这样。

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

函数是一等公民(First-class value),即函数可以作为另一个函数的返回值或参数,还可以作为一个变量的值。
函数可以嵌套定义,即在一个函数内部可以定义另一个函数。

在面向对象编程中,我们把对象传来传去,那在函数式编程中,要做的是把函数传来传去,说成术语,把他叫做高阶函数。在数学和计算机科学中,高阶函数是至少满足下列一个条件的函数:

接受一个或多个函数作为输入
输出一个函数

上代码,最基础的闭包方式

package main

import (
"fmt"
) func outer(x int) func(int) int {
return func(y int) int {
return x + y
}
} func main() {
f := outer()
fmt.Println(f())
}

单纯看return x+y

就知道返回结果是什么。也就是110.

上面的例子还不够简单,再来一个

func f(i int) func() int {
return func() int {
i++
return i
}
}

函数f返回了一个函数,返回的这个函数就是一个闭包。这个函数本身中没有定义变量I的,而是引用了它所在的环境(函数f)中的变量i。

package main

import "fmt"

func f(i int) func() int {
return func() int {
i++
return i
}
} func main() {
a := f()
fmt.Println(a())
fmt.Println(a())
fmt.Println(a())
fmt.Println(a())
fmt.Println(a()) } // PS C:\Users\13584\go\src\awesomeProject> go run .\main.go
// 1
// 2
// 3
// 4
//

函数f每进入一次,就形成了一个新的环境,对应的闭包中,函数都是同一个函数,环境却是引用不同的环境。

变量i是函数f中的局部变量,假设这个变量是在函数f的栈中分配的,是不可以的。因为函数f返回以后,对应的栈就失效了,f返回的那个函数中变量i就引用一个失效的位置了。所以闭包的环境中引用的变量不能够在栈上分配。

闭包结构体

回到闭包的实现来,前面说过,闭包是函数和它所引用的环境。那么是不是可以表示为一个结构体呢:

type Closure struct {
F func()()
i *int
}

事实上,Go在底层确实就是这样表示一个闭包的。让我们看一下汇编代码:

func f(i int) func() int {
return func() int {
i++
return i
}
} MOVQ $type.int+0(SB),(SP)
PCDATA $0,$16
PCDATA $1,$0
CALL ,runtime.new(SB) // 是不是很熟悉,这一段就是i = new(int)
...
MOVQ $type.struct { F uintptr; A0 *int }+0(SB),(SP) // 这个结构体就是闭包的类型
...
CALL ,runtime.new(SB) // 接下来相当于 new(Closure)
PCDATA $0,$-1
MOVQ 8(SP),AX
NOP ,
MOVQ $"".func·001+0(SB),BP
MOVQ BP,(AX) // 函数地址赋值给Closure的F部分
NOP ,
MOVQ "".&i+16(SP),BP // 将堆中new的变量i的地址赋值给Closure的值部分
MOVQ BP,8(AX)
MOVQ AX,"".~r1+40(FP)
ADDQ $24,SP
RET ,

其中func·001是另一个函数的函数地址,也就是f返回的那个函数。

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

package main

import (
"fmt"
) func adder() func(int) int {
sum :=
innerfunc := func(x int) int {
sum += x
return sum
}
return innerfunc
} func main() {
pos, neg := adder(), adder()
for i := ; i < ; i++ {
fmt.Println(pos(i), neg(-*i))
} }

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

 -
-
-
-
-
-
-
-
-

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

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

三 闭包使用

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

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

package main

import (
"errors"
"fmt"
) type Traveser func(ele interface{})
/*
Process:封装公共切片数组操作
*/
func Process(array interface{}, traveser Traveser) error { if array == nil {
return errors.New("nil pointer")
}
var length int //数组的长度
switch array.(type) {
case []int:
length = len(array.([]int))
case []string:
length = len(array.([]string))
case []float32:
length = len(array.([]float32))
default:
return errors.New("error type")
}
if length == {
return errors.New("len is zero.")
}
traveser(array)
return nil
}
/*
具体操作:升序排序数组元素
*/
func SortByAscending(ele interface{}) {
intSlice, ok := ele.([]int)
if !ok {
return
}
length := len(intSlice) for i := ; i < length-; i++ {
isChange := false
for j := ; j < length--i; j++ { if intSlice[j] > intSlice[j+] {
isChange = true
intSlice[j], intSlice[j+] = intSlice[j+], intSlice[j]
}
} if isChange == false {
return
} }
}
/*
具体操作:降序排序数组元素
*/
func SortByDescending(ele interface{}) { intSlice, ok := ele.([]int)
if !ok {
return
}
length := len(intSlice)
for i := ; i < length-; i++ {
isChange := false
for j := ; j < length--i; j++ {
if intSlice[j] < intSlice[j+] {
isChange = true
intSlice[j], intSlice[j+] = intSlice[j+], intSlice[j]
}
} if isChange == false {
return
} }
} func main() { intSlice := make([]int, )
intSlice = append(intSlice, , , , ) Process(intSlice, SortByDescending)
fmt.Println(intSlice) //[4 3 2 1]
Process(intSlice, SortByAscending)
fmt.Println(intSlice) //[1 2 3 4] stringSlice := make([]string, )
stringSlice = append(stringSlice, "hello", "world", "china") /*
具体操作:使用匿名函数封装输出操作
*/
Process(stringSlice, func(elem interface{}) { if slice, ok := elem.([]string); ok {
for index, value := range slice {
fmt.Println("index:", index, " value:", value)
}
}
})
floatSlice := make([]float32, )
floatSlice = append(floatSlice, 1.2, 3.4, 2.4) /*
具体操作:使用匿名函数封装自定义操作
*/
Process(floatSlice, func(elem interface{}) { if slice, ok := elem.([]float32); ok {
for index, value := range slice {
slice[index] = value *
}
}
})
fmt.Println(floatSlice) //[2.4 6.8 4.8]
}

输出结果

[   ]
[ ]
index: value: hello
index: value: world
index: value: china
[2.4 6.8 4.8]

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

package main

import (
"fmt"
) type FilterFunc func(ele interface{}) interface{} /*
公共操作:对数据进行特殊操作
*/
func Data(arr interface{}, filterFunc FilterFunc) interface{} { slice := make([]int, )
array, _ := arr.([]int) for _, value := range array { integer, ok := filterFunc(value).(int)
if ok {
slice = append(slice, integer)
} }
return slice
}
/*
具体操作:奇数变偶数(这里可以不使用接口类型,直接使用int类型)
*/
func EvenFilter(ele interface{}) interface{} { integer, ok := ele.(int)
if ok {
if integer% == {
integer = integer +
}
}
return integer
}
/*
具体操作:偶数变奇数(这里可以不使用接口类型,直接使用int类型)
*/
func OddFilter(ele interface{}) interface{} { integer, ok := ele.(int) if ok {
if integer% != {
integer = integer +
}
} return integer
} func main() {
sliceEven := make([]int, )
sliceEven = append(sliceEven, , , , , )
sliceEven = Data(sliceEven, EvenFilter).([]int)
fmt.Println(sliceEven) //[2 2 4 4 6] sliceOdd := make([]int, )
sliceOdd = append(sliceOdd, , , , , )
sliceOdd = Data(sliceOdd, OddFilter).([]int)
fmt.Println(sliceOdd) //[1 3 3 5 5] }

输出结果

[    ]
[ ]

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

golang 闭包的更多相关文章

  1. Golang闭包的坑

    team leader 发现一个Golang程序的bug,是由不正确使用闭包引起.记载一下,以作备忘. 猜猜一下程序的结果: import ( "fmt" "time&q ...

  2. 【GoLang】golang 闭包 closure 参数传递的蹊跷!

    结论: 闭包函数可以直接引用外层代码定义的变量, 但是,注意,闭包函数里面引用的是变量的地址, 当goroutine被调度时,改地址的值才会被传递给goroutine 函数. 介绍 go的闭包是一个很 ...

  3. Golang闭包案例分析与普通函数对比

    闭包案例 package main import ( "fmt" "strings" //记住一定引入strings包 ) //①编写一个函数makeSuffi ...

  4. Golang闭包入门了解

    概念闭包就是一个函数与其相关的引用环境组成的一个整体.闭包本质其实是一个函数,但是这个函数会用到函数外的变量,它们共同组成的整体我们叫做闭包. 简单举例说明 package main import ( ...

  5. golang 闭包求斐波那契数列

    题目是Go指南中的闭包求斐波那契数列 package main import "fmt" // 返回一个"返回int的函数" func fibonacci() ...

  6. golang闭包里的坑

    介绍 go的闭包是一个很有用的东西.但是如果你不了解闭包是如何工作的,那么他也会给你带来一堆的bug.这里我会拿出Go In Action这本书的一部分代码,来说一说在使用闭包的时候可能遇到的坑.全部 ...

  7. golang闭包

    http://blog.51cto.com/speakingbaicai/1703229 https://www.jianshu.com/p/fa21e6fada70 所谓闭包就是一个函数" ...

  8. Golang闭包和匿名函数

    1. 匿名函数 匿名函数就是没有函数名的函数,如下所示. func test() int { max := func(a, b int) int { if a > b { return a } ...

  9. GOLANG 闭包和普通函数的区别

    闭包和匿名函数是一回事 闭包使用完毕之后不会自动释放,值依然存在 普通函数调用完毕后,值会自动释放

随机推荐

  1. java之servlet之文件下载

    1.在页面中,可以直接通过超链接来下载: a) 如果浏览器能够打开该文件,那么直接在浏览器中显示---不是想要的效果 b) 任何人都能下载,不能进行权限控制 2.通过servlet来进行下载,在ser ...

  2. C# 数组排序带索引

    想到了两种方法来实现,分别利用了List.Sort()和Dictionary.OrderBy()方法,代码如下: , , , , , , , , , }; //List.Sort() List< ...

  3. 前端开发 Vue Vue.js和Nodejs的关系

    首先vue.js 是库,不是框架,不是框架,不是框架. Vue.js 使用了基于 HTML 的模版语法,允许开发者声明式地将 DOM 绑定至底层 Vue 实例的数据. Vue.js 的核心是一个允许你 ...

  4. 关闭 OSX 10.11 SIP (System Integrity Protection) 功能

    关闭 OSX 10.11 SIP (System Integrity Protection) 功能 来源 https://cms.35g.tw/coding/%E9%97%9C%E9%96%89-os ...

  5. php后台实现页面跳转的方法-转载

    地址:http://blog.csdn.net/abandonship/article/details/6459104 其中方法三的js代码在tp框架使用存在故障,一个是需要把代码写在一起(可能也不需 ...

  6. javascript创建一个基于对象的栈结构

    上篇博客介绍了基于数组创建一个栈,这是用对象创建一个栈 s1.声明一个Stack类 class Stack { constructor() { this.count = 0; this.items = ...

  7. vue初级使用

    一.Vue是什么? Vue(读音 /vjuː/, 类似于 view)是一个构建数据驱动的 web 界面的渐进式框架.采用自底向上增量开发的设计.Vue.js 的目标是通过尽可能简单的 API 实现响应 ...

  8. Java面向对象程序设计----接口

    接口:接口是一套规范.一个比抽象类更抽象的类. 接口中只能写抽象方法.接口中没有构造函数接口中的变量:public Stratic final接口怎么来使用(implements)实现接口 接口语法: ...

  9. c# Queue 类

  10. idea中添加web.xml配置文件与tomcat启动中遇到的web.xml文件找不到的问题

    1,如何在idea中向war项目中添加web.xml的配置文件 idea通过maven创建war项目时没有指定是webapp导致创建出来的项目没有webapp的文件夹.其实war项目中都是在" ...