《Go 语音圣经(中文版)》
匿名函数
拥有函数名的函数只能在包级语法块中被声明,通过函数字面量
(function literal),我们可绕过这一限制,在任何表达式中表示一个函数值。
函数值字面量是一种表达式,它的值被称为匿名函数
(anonymous function)。
函数字面量允许我们在使用函数时,再定义它。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 package mainimport "fmt" func squares () func () int { var x int return func () int { x += 2 return x * x } } func main () { f := squares() fmt.Println(f()) fmt.Println(f()) fmt.Println(f()) fmt.Println(f()) }
1 2 3 4 5 [13:36:09] onns@Onns ~/Onns/code/go/go-bible/ch5 $ go run 5.2.go 4 16 36 64
squares
的例子证明,函数值不仅仅是一串代码,还记录了状态。在squares
中定义的匿名内部函数可以访问和更新squares
中的局部变量,这意味着匿名函数和squares
中,存在变量引用。这就是函数值属于引用类型和函数值不可比较的原因。Go
使用闭包(closures
)技术实现函数值,Go
程序员也把函数值叫做闭包。
当匿名函数需要被递归调用时,必须首先声明一个变量,再将匿名函数赋值给这个变量。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 package mainimport ( "fmt" "sort" ) var prereqs = map [string ][]string { "algorithms" : {"data structures" }, "calculus" : {"linear algebra" }, "compilers" : {"data structures" , "formal languages" , "computer organization" }, "data structures" : {"discrete math" }, "databases" : {"data structures" }, "discrete math" : {"intro to programming" }, "formal languages" : {"discrete math" }, "networks" : {"operating systems" }, "operating systems" : {"data structures" , "computer organization" }, "programming languages" : {"data structures" , "computer organization" }, } func topoSort (m map [string ][]string ) []string { var order []string seen := make (map [string ]bool ) var learn func (items []string ) learn = func (items []string ) { for _, item := range items { if !seen[item] { seen[item] = true learn(m[item]) order = append (order, item) } } } var keys []string for key := range m { keys = append (keys, key) } sort.Strings(keys) learn(keys) return order } func main () { for i, course := range topoSort(prereqs) { fmt.Printf("%d:\t%s\n" , i+1 , course) } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 [14:45:42] onns@Onns ~/Onns/code/go/go-bible/ch5 $ go run 5.3.go 1: intro to programming 2: discrete math 3: data structures 4: algorithms 5: linear algebra 6: calculus 7: formal languages 8: computer organization 9: compilers 10: databases 11: operating systems 12: networks 13: programming languages
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 package mainimport ( "fmt" "strconv" ) func createdir (s int , _ int ) { fmt.Println("Create " + strconv.Itoa((s))) } func removedir (s int ) { fmt.Println("Remove " + strconv.Itoa((s))) } func main () { tempList := []int {1 , 2 , 3 , 4 , 5 , 6 , 7 } var rmdirs []func () for _, d := range tempList { dir := d createdir(dir, 0755 ) rmdirs = append (rmdirs, func () { removedir(dir) }) } for _, rmdir := range rmdirs { rmdir() } for _, dir := range tempList { createdir(dir, 0755 ) rmdirs = append (rmdirs, func () { removedir(dir) }) } for _, rmdir := range rmdirs { rmdir() } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 [16:27:50] onns@Onns ~/Onns/code/go/go-bible/ch5/itertest $ ./itertest Create 1 Create 2 Create 3 Create 4 Create 5 Create 6 Create 7 Remove 1 Remove 2 Remove 3 Remove 4 Remove 5 Remove 6 Remove 7 Create 1 Create 2 Create 3 Create 4 Create 5 Create 6 Create 7 Remove 1 Remove 2 Remove 3 Remove 4 Remove 5 Remove 6 Remove 7 Remove 7 Remove 7 Remove 7 Remove 7 Remove 7 Remove 7 Remove 7
问题的原因在于循环变量的作用域。在上面的程序中,for循环语句引入了新的词法块,循环变量dir在这个词法块中被声明。在该循环中生成的所有函数值都共享相同的循环变量。需要注意,**函数值中记录的是循环变量的内存地址,而不是循环变量某一时刻的值。**以dir为例,后续的迭代会不断更新dir的值,当删除操作执行时,for循环已完成,dir中存储的值等于最后一次迭代的值。这意味着,每次对os.RemoveAll的调用删除的都是相同的目录。
通常,为了解决这个问题,我们会引入一个与循环变量同名的局部变量,作为循环变量的副本。比如下面的变量dir,虽然这看起来很奇怪,但却很有用。
1 2 3 4 for _, dir := range tempDirs() { dir := dir }