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\n", &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. 总结

  • 匿名函数及其“捕获”的自由变量被称为闭包
  • 被闭包捕获的变量称为“自由变量”,在匿名函数实例未消亡时共享同个内存地址
  • 同一个匿名函数可以构造多个实例,每个实例内的自由变量地址不同
  • 匿名函数内部的局部变量在每次执行匿名函数时地址都是变换的
  • 通过匿名函数参数赋值的方式可以实现值传递

posted on 2021-07-20 16:56  tycoon3  阅读(245)  评论(0编辑  收藏  举报

导航