当我们定义一个函数时,例如:

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