• Go匿名函数及闭包


     

    匿名函数

    1. 赋值给函数变量

    package main
    import "fmt"
     func main() {
         sumFun := func(num1, num2 int) int {
             return num1 + num2
         }
         sum := sumFun(10, 20)
         fmt.Println(sum)
         return
     }
     ./hello
    30

    2. 直接执行

    package main
    import "fmt"
     func main() {
         func(name string) {
             fmt.Println("Hello", name)
         }("TOMOCAT")
         return
     }
     ./hello 
    Hello TOMOCAT

    3. 作为函数参数

    可以定义一个接收匿名函数参数的函数,实现回调的效果。

    package main
    import "fmt"
    /*
     求和并调用callback函数对结果进行特殊处理
      */
     func sumWorker(data []int, callback func(int)) {
         sum := 0
         for _, num := range data {
             sum += num
         }
         callback(sum)
     }
     func main() {
         // 打印出求和结果
         sumWorker([]int{1, 2, 3, 4}, func(a int) {
             fmt.Println("sum:", a)
         })
         // 判断求和结果是否大于100
         sumWorker([]int{1, 2, 3, 4}, func(a int) {
             if a > 100 {
                 fmt.Println("sum > 100")
             } else {
                 fmt.Println("sum <= 100")
             }
         })
     }
    sum: 10
    sum <= 100

    闭包

    闭包是由函数及其相关引用环境组成的实体,可以理解为一个函数“捕获”了和它处于同一作用域的其他变量。

    Golang中所有的匿名函数都是闭包。

    1. 理解“捕获”的概念

    “捕获”的本质就是引用传递而非值传递。

    package main
    import "fmt"
    func main() {
         i := 0
         // 闭包: i是引用传递
         defer func() {
             fmt.Println("defer closure i:", i)
         }()
         // 非闭包: i是值传递
         defer fmt.Println("defer i:", i)
         // 修改i的值
         i = 100
         
         return
     }
     ./hello
    defer i: 0
    defer closure i: 100

    2. 匿名函数与自由变量组成闭包

    下面这个例子中实现了Go中常见的闭包场景,我们通过Adder()返回一个匿名函数,这个匿名函数和自由变量x组成闭包,只要匿名函数的实例closure没有消亡,那么x都是引用传递。

    package main
    
    import "fmt"
     /*
     返回匿名函数的函数
     1) x是自由变量
     2) 匿名函数和x自由变量共同组成闭包
      */
     func Adder(x int) func(int) int{
         return func(y int) int{
             x += y
             fmt.Printf("x addr %p, x value %d
    ", &x, x)
             return x
         }
     }
     func main() {
         fmt.Println("----------------Adder()返回的匿名函数实例1----------------")
         closure := Adder(1)
         closure(100)
         closure(1000)
         closure(10000)
         fmt.Println("----------------Adder()返回的匿名函数实例2----------------")
         closure2 := Adder(10)
         closure2(1)
         closure2(1)
         closure2(1)
         closure2(1)
         return
     }
    ----------------Adder()返回的匿名函数实例1----------------
    x addr 0x400019e010, x value 101
    x addr 0x400019e010, x value 1101
    x addr 0x400019e010, x value 11101
    ----------------Adder()返回的匿名函数实例2----------------
    x addr 0x400019e030, x value 11
    x addr 0x400019e030, x value 12
    x addr 0x400019e030, x value 13
    x addr 0x400019e030, x value 14

    x的addr变化了

    3. 闭包中使用值传递

    由于闭包的存在,Golang中使用匿名函数的时候要特别注意区分清楚引用传递和值传递。根据实际需要,我们在不需要引用传递的地方通过匿名函数参数赋值的方式实现值传递。

    package main
    
    import "fmt"
    import "time"
     func main() {
         fmt.Println("----------------引用传递----------------")
         for i := 0; i < 10; i++ {
             go func() {
                 fmt.Println(i)
             }()
         }
         time.Sleep(10 * time.Millisecond)
         fmt.Println("----------------值传递----------------")
         for i := 0; i < 10; i++ {
             go func(x int) {
                 fmt.Println(x)
             }(i)
         }
         time.Sleep(10 * time.Millisecond)
         return
     }
    root@ubuntu:~/go_learn/example.com/hello# ./hello 
    ----------------引用传递----------------
    7
    10
    10
    7
    10
    10
    10
    10
    10
    10
    ----------------值传递----------------
    0
    3
    2
    5
    6
    4
    7
    8
    1
    9
    root@ubuntu:~/go_learn/example.com/hello# ./hello 
    ----------------引用传递----------------
    7
    10
    7
    7
    10
    10
    10
    10
    10
    10
    ----------------值传递----------------
    0
    5
    3
    1
    4
    8
    6
    7
    9
    2
    root@ubuntu:~/go_learn/example.com/hello# ./hello 
    ----------------引用传递----------------
    7
    7
    10
    10
    10
    10
    10
    10
    10
    10
    ----------------值传递----------------
    0
    1
    9
    2
    3
    4
    6
    5
    7
    8
    root@ubuntu:~/go_learn/example.com/hello# ./hello 
    ----------------引用传递----------------
    7
    7
    10
    7
    10
    10
    10
    10
    10
    10
    ----------------值传递----------------
    9
    5
    4
    0
    6
    8
    2
    3
    7
    1
    root@ubuntu:~/go_learn/example.com/hello# ./hello 
    ----------------引用传递----------------
    7
    10
    10
    10
    10
    10
    7
    10
    10
    10
    ----------------值传递----------------
    3
    0
    5
    1
    2
    7
    4
    9
    8
    6
    root@ubuntu:~/go_learn/example.com/hello# 
    package main
    import  "fmt"
    func adder() func(int) int {
       sum := 0
       innerfunc := func(x int) int {
          sum += x
          return sum
       }
       return innerfunc
    }
    
    func main(){
       add := adder()
       for i := 0; i < 5; i++ {
          fmt.Println(add(i))
       }
    ./hello 
    0
    1
    3
    6
    10

    Go可以在函数内部定义匿名函数,adder中定义了一个匿名函数,然后赋值给innerfunc变量,最后将其作为返回值。

    for循环中每次通过不同参数调用adder函数时(不同引用环境),匿名函数引用了外层的局部变量sum,由于外部变量的作用域而没有被释放。

    package main
    import  "fmt"
    func adder() func(int) int {
       sum := 0
       innerfunc := func(x int) int {
          sum += x
          return sum
       }
       return innerfunc
    }
    
    func main(){
       add := adder()
       for i := 0; i < 5; i++ {
          fmt.Println(add(i))
       }
       add2 := adder()
       for i := 0; i < 5; i++ {
          fmt.Println(add2(i))
       }
    }
     
    ./hello 
    0
    1
    3
    6
    10
    0
    1
    3
    6
    10

    4. 总结

    • 匿名函数及其“捕获”的自由变量被称为闭包
    • 被闭包捕获的变量称为“自由变量”,在匿名函数实例未消亡时共享同个内存地址
    • 同一个匿名函数可以构造多个实例,每个实例内的自由变量地址不同
    • 匿名函数内部的局部变量在每次执行匿名函数时地址都是变换的
    • 通过匿名函数参数赋值的方式可以实现值传递
  • 相关阅读:
    2014年第五届蓝桥杯省赛试题(JavaA组)
    2013年第四届蓝桥杯省赛试题(JavaA组)
    2013蓝桥杯JavaA组T10 大臣的旅费(树的直径)
    CodeForces
    天梯赛 L2-006 树的遍历(序列建树)
    PAT甲 1095 解码PAT准考证/1153 Decode Registration Card of PAT(优化技巧)
    2015年第六届蓝桥杯省赛T10 生命之树(树形dp+Java模拟vector)
    ZOJ
    SPOJ
    HDU
  • 原文地址:https://www.cnblogs.com/dream397/p/15035814.html
Copyright © 2020-2023  润新知