golang 闭包

说起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(10)
    fmt.Println(f(100))
} 

单纯看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(0)
    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
// 5

函数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 := 0
    innerfunc := func(x int) int {
        sum += x
        return sum
    }
    return innerfunc
}

func main() {
    pos, neg := adder(), adder()
    for i := 0; i < 10; i++ {
        fmt.Println(pos(i), neg(-2*i))
    }

}

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

0 0
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操作(例如从网络获取数据、文件读写)完成的时候,会对获取的数据进行某些操作,这些操作可以交给函数对象处理。

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

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 == 0 {
        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 := 0; i < length-1; i++ {
        isChange := false
        for j := 0; j < length-1-i; j++ {

            if intSlice[j] > intSlice[j+1] {
                isChange = true
                intSlice[j], intSlice[j+1] = intSlice[j+1], intSlice[j]
            }
        }

        if isChange == false {
            return
        }

    }
}
/*
    具体操作:降序排序数组元素
*/
func SortByDescending(ele interface{}) {

    intSlice, ok := ele.([]int)
    if !ok {
        return
    }
    length := len(intSlice)
    for i := 0; i < length-1; i++ {
        isChange := false
        for j := 0; j < length-1-i; j++ {
            if intSlice[j] < intSlice[j+1] {
                isChange = true
                intSlice[j], intSlice[j+1] = intSlice[j+1], intSlice[j]
            }
        }

        if isChange == 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(elem interface{}) {

        if slice, ok := elem.([]string); ok {
            for index, value := range slice {
                fmt.Println("index:", index, "  value:", value)
            }
        }
    })
    floatSlice := make([]float32, 0)
    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 * 2
            }
        }
    })
    fmt.Println(floatSlice) //[2.4 6.8 4.8]
}

输出结果

[4 3 2 1]
[1 2 3 4]
index: 0   value: hello
index: 1   value: world
index: 2   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, 0)
    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%2 == 1 {
            integer = integer + 1
        }
    }
    return integer
}
/*
  具体操作:偶数变奇数(这里可以不使用接口类型,直接使用int类型)
*/
func OddFilter(ele interface{}) interface{} {

    integer, ok := ele.(int)

    if ok {
        if integer%2 != 1 {
            integer = integer + 1
        }
    }

    return integer
}

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]

}

输出结果

[2 2 4 4 6]
[1 3 3 5 5]

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

原文地址:https://www.cnblogs.com/landv/p/11094568.html

时间: 2024-10-25 06:48:24

golang 闭包的相关文章

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

结论: 闭包函数可以直接引用外层代码定义的变量, 但是,注意,闭包函数里面引用的是变量的地址, 当goroutine被调度时,改地址的值才会被传递给goroutine 函数. 介绍 go的闭包是一个很有用的东西.但是如果你不了解闭包是如何工作的,那么他也会给你带来一堆的bug.这里我会拿出Go In Action这本书的一部分代码,来说一说在使用闭包的时候可能遇到的坑.全部的代码在github上. 闭包的坑 首先看一段代码: search/search.go 29 // Launch a gor

golang闭包里的坑

介绍 go的闭包是一个很有用的东西.但是如果你不了解闭包是如何工作的,那么他也会给你带来一堆的bug.这里我会拿出Go In Action这本书的一部分代码,来说一说在使用闭包的时候可能遇到的坑.全部的代码在github上. 闭包的坑 首先看一段代码: search/search.go 29 // Launch a goroutine for each feed to find the results. 30 for _, feed := range feeds { 31 // Retrieve

golang闭包

闭包:一个函数和与其相关的引用环境组合而成的实体 先看下面的这个例子: package main import "fmt" func adder() func(int) int {      sum := 0      return func(x int) int {           sum += x           return sum      } } func main() {      pos, neg := adder(), adder()      for i :=

关于golang闭包

闭包不是必报,睚眦必报,这种事咱不干,咱要干的是程序上所谓的闭包. 在讲闭包之前呢?我们先看一个程序 func add(a, b int) int { return a + b } 乍一看,就感觉想骂人,这是啥?这是在考验我的智商?不,咱是那意思嘛,消消气,先听我说,此功能就是两个数相加得到和,但是我们要用它干一票大的!玄机来了,怎么做,我给他传一个数字,等我想用时,我再传入一个数字,和就出来了! 听起来是不是感觉牛x了 好,开始额的表演..... 我们先这样做 func fadd(a, b,

第一章.java&amp;golang的区别之:闭包

对于golang一直存有觊觎之心,但一直苦于没有下定决心去学习研究,最近开始接触golang.就我个人来说,学习golang的原动力是因为想要站在java语言之外来审视java和其它语言的区别,再就是想瞻仰一下如此NB的语言.年前就想在2019年做一件事情,希望能从各个细节处做一次java和golang的对比分析,不评判语言的优劣,只想用简单的语言和可以随时执行的代码来表达出两者的区别和底层涉及到的原理.今天是情人节,馒头妈妈在加班,送给自己一件贴心的礼物,写下第一篇对比文章:java&gola

Golang泛型函数

目前,golang还不支持模板函数(类型参数化),所以看上去不得不为每一种类型都实现一个函数.但是Golang可以利用空接口interface{}和闭包/高阶函数来实现泛型函数. 1 空接口 空接口interface{}是指方法集为空的接口,任何类型的值都可以赋值给空接口.接口相关内容请参见另一篇博客<Golang中的接口> // interface{} func minimum(first interface{}, rest ...interface{}) interface{} {    

Golang-函数式编程(闭包)

github:https://github.com/ZhangzheBJUT/blog/blob/master/closure.md 一 函数式编程概论 在过去近十年时间里,面向对象编程大行其道,以至于在大学的教育里,老师也只会教给我们两种编程模型,面向过程和面向对象.孰不知,在面向对象思想产生之前,函数式编程已经有了数十年的历史.就让我们回顾这个古老又现代的编程模型,看看究竟是什么魔力将这个概念在21世纪的今天再次拉入我们的视野. 随着硬件性能的提升以及编译技术和虚拟机技术的改进,一些曾被性能

Golang 中关于闭包的坑

所谓闭包是指内层函数引用了外层函数中的变量或称为引用了自由变量的函数,其返回值也是一个函数,了解过的语言中有闭包概念的像 js,python,golang 都类似这样. python 中的闭包可以嵌套函数,像下面这样: def make_adder(addend): def adder(augend): return augend + addend return adder 转化成 golang 代码则像下面这样: func outer(x int) func(int) int{ func in

golang 函数二 (匿名函数和闭包)

匿名函数就是没有定义函数名称的函数.我们可以在函数内部定义匿名函数,也叫函数嵌套. 匿名函数可以直接被调用,也可以赋值给变量.作为参数或返回值.比如: func main(){     func(s string){     //直接被调用         println(s)     }("hello gopher!!!")     /*     func(s string){     //未被调用         println(s)     }     */ } func mai