1
Daizong 2014-10-29 16:43:09 +08:00
package main
import "fmt" func main() { var fs []func() int for i := 0; i < 3; i++ { fn := func(j int) func() int { return func() int { return j } }(i) fs = append(fs, fn) } for _, f := range fs { fmt.Printf("%p = %v\n", f, f()) } } |
2
defia 2014-10-29 16:48:47 +08:00 1
后面range得到的f,是一个值,其类型为函数,同一个函数所以为同一个指针
f()即函数返回值 至于为什么3个返回值都是3,我们来分析一下函数本身: func() int { return i } 这个函数为一个返回int的函数 返回的数值为i 在这里,i是闭包,记住一点,通过闭包获取的函数作用域之外(这里为函数内部引用外部)的外部变量,类似于指针,即在闭包内部作用域引用外部变量时,并非像传参一样复制外部的变量数值,而是保留了外部变量的引用,所以在内部使用外部变量时,该数值取决于执行时该外部变量的值 在执行f()的时候,上面的for循环已经结束,i的数值为3,所以最终3个结果都是3 同样的再来个例子: func main() { values := []string{"a", "b", "c"} for _, v := range values { go func() { fmt.Println(v) //通过闭包传递了v的引用 }() } var input string fmt.Scanln(&input) fmt.Println("done") } 该例子中,最终结果输出是3个"c",而非abc 因为在runtime.GOMAXPROCS()==1的情况下,3个goroutine的执行发生于阻塞时,即fmt.Scanln时,在这个时候循环已经结束,v=="c" 这点类似javascript for(var i=0;i<5;i++){ var j=1; setTimeout(function(){ console.log(i+' '+j) },200); j=2; } |
3
defia 2014-10-29 16:59:11 +08:00
1楼的代码在每次循环体时,执行了一个函数,其把循环变量i作为参数传递了进去并返回一个跟原文一样功能的函数,这样一来j就是复制传参了,最终就会返回123
|
4
snachx OP @defia
其他的我都能理解也是这么想的,就是第一句不明白,为什么是同一个函数呢? 如果生成fs的那段我写成这样: for i := 0; i < 3; i++ { if i == 0 { a := func() int { return i } fs = append(fs, a) } if i == 1 { a := func() int { return i } fs = append(fs, a) } if i == 2 { a := func() int { return i } fs = append(fs, a) } } 结果就会是: 0x401410 3 0x401420 3 0x401430 3 这样写为什么就不是同一个函数了呢? 我认为func() int { return i}就是函数定义了啊,循环三次就相当于三次定义了... |
5
oaix 2014-10-29 17:28:34 +08:00
我觉得是被编译器优化掉了。
|
7
snachx OP |
9
gihnius 2014-10-29 18:39:08 +08:00
@snachx 你可以这样试试:
for i := 0; i < 3; i++ { func(n int){ fs = append(fs, func() int { return n }) }(i) } |
10
chemzqm 2014-10-29 19:25:52 +08:00
楼上说法是有问题的,遍历的时候肯定是了3个函数,虽然它们代码相同,但是range的时候go并没有重新声明变量,所以f使用的地址不会变的,但它指向的函数变了,3个函数返回的是同一个变量i。你试试append 3个不同的函数就知道了。
|
11
snachx OP |
12
snachx OP |
13
defia 2014-10-29 22:17:08 +08:00
@snachx 编译器优化啦
package main import "fmt" func main() { for i := 0; i < 3; i++ { a := []string{"str"} fmt.Printf("%p\n", a) } } 这样就是3个不同值 而 package main import "fmt" func main() { for i := 0; i < 3; i++ { a := func() {} fmt.Printf("%p\n", a) } } 这样就是三个相同值.. 可见编译器会对内联函数有优化.当然也很好理解,不然每个函数一套还调试个屁啊 |
14
defia 2014-10-29 22:18:29 +08:00
所以其实内联函数/匿名函数,本身跟普通的命名函数是完全一回事的
|