当我们定义一个函数时,例如:
1
2
3
|
func f(){
statements
}
|
这种方式只能用在包一级,称为命名函数。这个过程相当于我们定义了一个常量f
,类型为func()
,并且其对应我们刚才定义的函数。
但是有些情况下这种函数定义的方式不够灵活,比如我们希望在调用函数时将某个函数作为参数传入,或者在函数返回时希望返回的是一个函数。
函数字面值
什么是字面值,当我们给一个变量赋值,比如var num int = 5
时,这里的5并非来自程序中的变量或者常量,也并非来自程序外部输入,而是我们编写程序时
指定的,这种情况我们称之为int类型的字面值,也就是说,这里的5,它没有什么别的意义,就是字面意思上的5。
当我们定义一个命名函数时,本质上相当于定义了一个函数类型的常量,这个常量保存的是一个函数的字面值。
对于函数字面值,我们可以直接进行函数调用,或者将它作为参数传入其他函数,或者作为函数的返回值,当我们直接对一个函数字面值进行操作时,由于其没有名字,
所以也称之为匿名函数。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
func main() {
//直接对匿名函数进行调用
func() {
fmt.Println("你好,小明")
}()
//将匿名函数作为参数
greet(func(name string) {
fmt.Printf("你好,%s\n", name)
})
//通过函数获取一个匿名函数并进行调用
greet2("小明")()
}
func greet(greetFunc func(name string)) {
greetFunc("小明")
}
func greet2(name string) func() {
//将函数作为返回值
return func() {
fmt.Printf("你好,%s", name)
}
}
|
闭包
当我们在函数f内定义一个匿名函数nf时(是否将其赋值给其他变量无所谓),在nf内部可以引用f函数中的变量,这种技术称为闭包。
1
2
3
4
5
6
7
8
|
func main() {
num := 0
f := func() {
fmt.Printf("num = %d", num) //num = 100
}
num = 100
f()
}
|
在上面的代码中,虽然对变量num
的修改晚于匿名函数的创建,但是运行结果仍然是修改后的值,这即使因为通过闭包技术,匿名函数内
对函数变量的访问是通过引用而非复制,理解这一点非常重要,有助于避开很多陷阱,比如经典的捕获迭代变量陷阱:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
func main() {
students := []string{"小张", "小王", "小李子"}
greetFuncs := getGreetFuncs(students)
for _, f := range greetFuncs {
f()
}
}
func getGreetFuncs(names []string) []func() {
var ret []func()
for i := 0; i < len(names); i++ {
ret = append(ret, func() {
fmt.Printf("你好,%s!\n", names[i]) //发生panic "runtime error: index out of range [3] with length 3"
})
}
return ret
}
|
上述代码发生panic的原因是我们定义匿名函数的时候引用了外部的变量i,当函数执行时,因为函数对i的访问是引用而非复制,此时的i的值是3。
解决这个问题的办法很简单,只需要在循环内定义一个新的变量保存循环过程中的i,让匿名函数引用这个变量即可:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
func main() {
students := []string{"小张", "小王", "小李子"}
greetFuncs := getGreetFuncs(students)
for _, f := range greetFuncs {
f()
}
}
func getGreetFuncs(names []string) []func() {
var ret []func()
for i := 0; i < len(names); i++ {
index := i //每次循环都定义新变量index,这个index不会改变
ret = append(ret, func() {
fmt.Printf("你好,%s!\n", names[index]) //你好,小张! 你好,小王! 你好,小李子!
})
}
return ret
}
|
BGM