匿名函数
匿名函数就是没有名称的函数,主要记住它可以用于各种类型(切片、结构体、map等)中就行。
1. 一等公民
golang中的函数是一等公民 —— 它能做类型、变量、参数等传递。
package main
import "fmt"
func main() {
// 切片中充当元素
d := []func(int) int{
func(x int) int { return x + 2 },
func(y int) int { return y + 2 },
}
fmt.Println(d[0](2))
// 结构体中字段
s := struct {
fn func(int) int // 结构体这里没有逗号
}{
fn: func(x int) int { return x + 100 },
}
fmt.Println(s.fn(2))
// map中充当value
m := make(map[string]func(int) int) // map 可以不指定长度
m["first"] = func(x int) int { return x + 200 }
fmt.Println(m["first"](100))
}
// 4
// 102
// 300
2. 闭包
闭包 = 函数 + 环境变量
光看上面的话,我们也不知道具体什么意思;其实记住下面两点就行:
- 匿名函数能获取外部变量的值
package main
import "fmt"
func main() {
x := 2
func() {
fmt.Println(x) // 能获取外部的x
}()
}
- 外部变量的会以地址(引用)方式的方式绑定
例子1:通过地址值验证(打印出来是同一个地址)
package main
import "fmt"
func main() {
x := 1
f := func() {
fmt.Printf("f中x地址: %p\n", &x)
fmt.Println(x) // 能获取外部的x
}
fmt.Printf("x地址: %p\n", &x)
f()
}
// x地址: 0xc00001a198
// f中x地址: 0xc00001a198
// 1
例子2: 在匿名函数中修改值
package main
import "fmt"
func main() {
f := genFuc()
// 同一个匿名函数 修改的是同一个x地址 所以x会变
f() // 打印 1
f() // 打印 2
f() // 打印 3
// 这个时候返回的是 新匿名函数 x地址不同 所以回到初识值了
f1 := genFuc()
f1() // 打印 1
}
// 返回一个匿名函数
func genFuc() func() {
x := 0
return func() {
x++
fmt.Println(x)
}
}
// 1
// 2
// 3
// 1
3. 循环中变量引起的问题
循环指的是for/range,我们先来看产生的问题
package main
import "fmt"
func main() {
ch := make(chan func(), 10) // 这个通道中装的是函数
// 装入通道
for i := 0; i < 10; i++ {
ch <- func() {
fmt.Println(i) // 绑定外部变量i
}
}
close(ch)
// 取出函数后执行
for f := range ch {
f()
}
}
// 10
// 10
// 10
// 10
// 10
// 10
// 10
// 10
// 10
// 10
我们会发现输出的居然全是10;为什么会这样呢?
在循环语句中的变量,它只初始化了一次,它的地址是不变的;每次改动的只是将不同的值分配到这个地址上而已;
而匿名函数绑定的是变量的地址,在绑定变量后,函数没有立即执行,而等到执行时,地址i中的值早已变成10了。
这种情况,发生在延迟执行(绑定变量后,执行前变量值发生了更改)。
如何避免呢?
在每一轮循环下增加一行,初始化一个变量用于存储绑定值即可。
修改后代码如下:
package main
import "fmt"
func main() {
ch := make(chan func(), 10) // 这个通道中装的是函数
// 装入通道
for i := 0; i < 10; i++ {
ni := i
ch <- func() {
fmt.Println(ni) // 绑定外部变量i
}
}
close(ch)
// 取出函数后执行
for f := range ch {
f()
}
}
// 0
// 1
// 2
// 3
// 4
// 5
// 6
// 7
// 8
// 9