zhcn 编程语言 Golang Golang 容器 非公開: Go 中的闭包 – 引用外部变量的匿名函数

Go 中的闭包 – 引用外部变量的匿名函数

Go中的闭包是一个引用自由变量的函数,当自由变量的环境离开时,引用的自由变量不会被释放或删除,因此,很简单。下列的:

函数类型就像结构体,可以被实例化函数本身不存储任何信息只有与引用环境结合后形成的闭包才具有“内存”函数在编译时存储闭包是一个静态的概念,闭包是运行时动态的。

其他编程语言中的闭包

在某些编程语言中,闭包也称为 lambda 表达式。

引用环境中变量的闭包的过程有时称为“捕获”。 C++11 标准中有两种类型的捕获:引用和副本。可以修改引用的原始值称为“引用捕获”。使用称为“复制捕获”的方法将过程的值复制到闭包中。

在Lua语言中,捕获的变量被命名为Upvalue。这是因为捕获过程总是引用闭包上面定义的自由变量。

闭包的实现也因语言的不同而不同,但在Lua语言中,闭包和函数都属于原型的概念,捕获的变量以Upvalue的形式引用到闭包。

在 C++ 和 C# 中,为闭包创建类,并在编译时将捕获的变量放入类的成员中。当闭包访问捕获的变量时,它实际上访问闭包隐藏类的成员。

修改闭包内的引用变量

闭包可以修改其作用域顶部的变量,并且修改引用的变量实际上修改了该变量。这可以通过下面的例子来理解。

 // 文字列を準備する
str := "hello world"

// 無名関数を作成する
foo := func() {

    // 無名関数内でstrにアクセスする
    str = "hello dude"
}

// 無名関数を呼び出す
foo() 

代码说明如下:

  • 第二行准备要修改的字符串。
  • 第 5 行创建一个匿名函数。
  • 第8行,str没有在匿名函数内部定义,而是在匿名函数之前定义了str,此时str被匿名函数引用,形成闭包。
  • 第 12 行执行闭包,此时 str 更改为 hello dude。

代码输出:

hello dude

示例:由于关闭而产生的记忆效应

闭包中捕获的变量对闭包本身有记忆效应。闭包内的逻辑可以修改闭包捕获的变量。变量在闭包的生命周期内始终存在。闭包本身有内存,就像变量一样。影响。

累加器实现:

 import main

import (
    "fmt"
)

//値を提供して、関数を呼び出すたびに値を累算するようにしましょう
func Accumulate(value int) func() int {

    //クロージャーを返す
    return func() int {

        //累算する
        value++

        //累算された値を返す
        return value
    }
}

func main() {

    //1が初期値の累算器を作成する
    accumulator := Accumulate(1)

    //1を累算して印刷
    fmt.Println(accumulator())

    fmt.Println(accumulator())

    //累算器の関数のアドレスを印刷する
    fmt.Printf("%p\n", &accumulator)

    //10が初期値の累算器を作成する
    accumulator2 := Accumulate(10)

    //1を累算して印刷
    fmt.Println(accumulator2())

    //累算器の関数のアドレスを印刷する
    fmt.Printf("%p\n", &accumulator2)
} 

代码说明如下:

  • 第8行,累加器生成函数。该函数打印初始值并返回调用时为初始值创建的闭包函数。
  • 第 11 行返回闭包函数。每个返回值都会创建一个新的函数实例。
  • 第 14 行累积引用的 Accumulate 参数变量。请注意,虽然第 11 行中该值不是由匿名函数定义的,但它被匿名函数引用,从而形成了一个闭包。
  • 第 17 行通过闭包的返回值返回修改后的值。
  • 第 24 行创建一个初始值为 1 的累加器。返回的累加器是 func()int 类型的函数变量。
  • 当在第 27 行调用 Accumulator() 时,代码会执行从第 11 行一直到第 17 行的匿名函数逻辑。
  • 第 32 行打印累加器的函数地址。

比较输出日志可以看出,accumulator和accumulator2是两个不同的闭包实例,因为它们打印的函数地址不同。

每次调用累加器时,引用的变量都会自动累加。

示例:实现生成器的闭包

闭包记忆效应用于实现类似于设计模式中工厂模式的生成器。以下示例显示了创建玩家生成器的过程。

实现玩家生成器:

 package main

import (
    "fmt"
)

//プレイヤーのジェネレーターを作成し、名前を入力してジェネレータを出力
func playerGen(name string) func() (string, int) {

    //体力は常に150
    hp := 150

    //クロージャを作成して返す
    return func() (string, int) {

        //変数をクロージャに参照する
        return name, hp
    }
}

func main() {

    //プレイヤージェネレータを作成
    generator := playerGen("high noon")

    //プレイヤーの名前と体力を返す
    name、 hp := generator()

    //値を出力する
    fmt.Println(name、 hp)
} 

这是代码输出:

high noon 150

代码说明如下:

  • 在第 8 行,您需要指定名称以使用playerGen() 创建玩家生成器函数。
  • 在第 11 行,声明 hp 变量并将其设置为 150。
  • 第 14 到 18 行引用匿名函数引用 hp 和 name 变量,形成闭包。
  • 在第 24 行,在调用并传递参数playerGen 后检索播放器生成器。
  • 在第 27 行,我们调用玩家生成器函数来获取玩家的姓名和 HP。

闭包也有一定的封装性,虽然第11行的变量是playerGen本地的,不能在playerGen之外直接访问和修改,但这个特性也和我的面向对象封装类似。

《Go语言中的闭包——引用外部变量的匿名函数》浅显易懂的讲解!您必须观看的 2 个最佳视频

Golang チュートリアル #17 – 高度な関数の概念と関数クロージャ
https://www.youtube.com/watch?v=vdm04bVzkLg&pp=ugMICgJqYRABGAHKBVYgR28g6KiA6Kqe44Gu44Kv44Ot44 O844K444OjIChDbG9zdXJlKSAtIOWklumDqOWkieaVsOOCkuWPgueFp-OBmeOCi-WMv-WQjemWouaVsCZobD1KQQ%3D%3D
Golang の関数リテラルとクロージャ – パート 1
https://www.youtube.com/watch?v=EQAM0aWX2ck&pp=ugMICgJqYRABGAHKBVYgR28g6KiA6Kqe44Gu44Kv44Ot44 O844K444OjIChDbG9zdXJlKSAtIOWklumDqOWkieaVsOOCkuWPgueFp-OBmeOCi-WMv-WQjemWouaVsCZobD1KQQ%3D%3D
Go中的闭包是一个引用自由变量的函数,当自由变量的环境离开时,引用的自由变量不会被释放或删除,因此,很简单。下列的:

函数类型就像结构体,可以被实例化函数本身不存储任何信息只有与引用环境结合后形成的闭包才具有“内存”函数在编译时存储闭包是一个静态的概念,闭包是运行时动态的。

其他编程语言中的闭包

在某些编程语言中,闭包也称为 lambda 表达式。

引用环境中变量的闭包的过程有时称为“捕获”。 C++11 标准中有两种类型的捕获:引用和副本。可以修改引用的原始值称为“引用捕获”。使用称为“复制捕获”的方法将过程的值复制到闭包中。

在Lua语言中,捕获的变量被命名为Upvalue。这是因为捕获过程总是引用闭包上面定义的自由变量。

闭包的实现也因语言的不同而不同,但在Lua语言中,闭包和函数都属于原型的概念,捕获的变量以Upvalue的形式引用到闭包。

在 C++ 和 C# 中,为闭包创建类,并在编译时将捕获的变量放入类的成员中。当闭包访问捕获的变量时,它实际上访问闭包隐藏类的成员。

修改闭包内的引用变量

闭包可以修改其作用域顶部的变量,并且修改引用的变量实际上修改了该变量。这可以通过下面的例子来理解。

 // 文字列を準備する
str := "hello world"

// 無名関数を作成する
foo := func() {

    // 無名関数内でstrにアクセスする
    str = "hello dude"
}

// 無名関数を呼び出す
foo() 

代码说明如下:

  • 第二行准备要修改的字符串。
  • 第 5 行创建一个匿名函数。
  • 第8行,str没有在匿名函数内部定义,而是在匿名函数之前定义了str,此时str被匿名函数引用,形成闭包。
  • 第 12 行执行闭包,此时 str 更改为 hello dude。

代码输出:

hello dude

示例:由于关闭而产生的记忆效应

闭包中捕获的变量对闭包本身有记忆效应。闭包内的逻辑可以修改闭包捕获的变量。变量在闭包的生命周期内始终存在。闭包本身有内存,就像变量一样。影响。

累加器实现:

 import main

import (
    "fmt"
)

//値を提供して、関数を呼び出すたびに値を累算するようにしましょう
func Accumulate(value int) func() int {

    //クロージャーを返す
    return func() int {

        //累算する
        value++

        //累算された値を返す
        return value
    }
}

func main() {

    //1が初期値の累算器を作成する
    accumulator := Accumulate(1)

    //1を累算して印刷
    fmt.Println(accumulator())

    fmt.Println(accumulator())

    //累算器の関数のアドレスを印刷する
    fmt.Printf("%p\n", &accumulator)

    //10が初期値の累算器を作成する
    accumulator2 := Accumulate(10)

    //1を累算して印刷
    fmt.Println(accumulator2())

    //累算器の関数のアドレスを印刷する
    fmt.Printf("%p\n", &accumulator2)
} 

代码说明如下:

  • 第8行,累加器生成函数。该函数打印初始值并返回调用时为初始值创建的闭包函数。
  • 第 11 行返回闭包函数。每个返回值都会创建一个新的函数实例。
  • 第 14 行累积引用的 Accumulate 参数变量。请注意,虽然第 11 行中该值不是由匿名函数定义的,但它被匿名函数引用,从而形成了一个闭包。
  • 第 17 行通过闭包的返回值返回修改后的值。
  • 第 24 行创建一个初始值为 1 的累加器。返回的累加器是 func()int 类型的函数变量。
  • 当在第 27 行调用 Accumulator() 时,代码会执行从第 11 行一直到第 17 行的匿名函数逻辑。
  • 第 32 行打印累加器的函数地址。

比较输出日志可以看出,accumulator和accumulator2是两个不同的闭包实例,因为它们打印的函数地址不同。

每次调用累加器时,引用的变量都会自动累加。

示例:实现生成器的闭包

闭包记忆效应用于实现类似于设计模式中工厂模式的生成器。以下示例显示了创建玩家生成器的过程。

实现玩家生成器:

 package main

import (
    "fmt"
)

//プレイヤーのジェネレーターを作成し、名前を入力してジェネレータを出力
func playerGen(name string) func() (string, int) {

    //体力は常に150
    hp := 150

    //クロージャを作成して返す
    return func() (string, int) {

        //変数をクロージャに参照する
        return name, hp
    }
}

func main() {

    //プレイヤージェネレータを作成
    generator := playerGen("high noon")

    //プレイヤーの名前と体力を返す
    name、 hp := generator()

    //値を出力する
    fmt.Println(name、 hp)
} 

这是代码输出:

high noon 150

代码说明如下:

  • 在第 8 行,您需要指定名称以使用playerGen() 创建玩家生成器函数。
  • 在第 11 行,声明 hp 变量并将其设置为 150。
  • 第 14 到 18 行引用匿名函数引用 hp 和 name 变量,形成闭包。
  • 在第 24 行,在调用并传递参数playerGen 后检索播放器生成器。
  • 在第 27 行,我们调用玩家生成器函数来获取玩家的姓名和 HP。

闭包也有一定的封装性,虽然第11行的变量是playerGen本地的,不能在playerGen之外直接访问和修改,但这个特性也和我的面向对象封装类似。

《Go语言中的闭包——引用外部变量的匿名函数》浅显易懂的讲解!您必须观看的 2 个最佳视频

Golang チュートリアル #17 – 高度な関数の概念と関数クロージャ
https://www.youtube.com/watch?v=vdm04bVzkLg&pp=ugMICgJqYRABGAHKBVYgR28g6KiA6Kqe44Gu44Kv44Ot44 O844K444OjIChDbG9zdXJlKSAtIOWklumDqOWkieaVsOOCkuWPgueFp-OBmeOCi-WMv-WQjemWouaVsCZobD1KQQ%3D%3D
Golang の関数リテラルとクロージャ – パート 1
https://www.youtube.com/watch?v=EQAM0aWX2ck&pp=ugMICgJqYRABGAHKBVYgR28g6KiA6Kqe44Gu44Kv44Ot44 O844K444OjIChDbG9zdXJlKSAtIOWklumDqOWkieaVsOOCkuWPgueFp-OBmeOCi-WMv-WQjemWouaVsCZobD1KQQ%3D%3D