* 打印three, three, three
```
package main
import (
"fmt"
"time"
)
type field struct {
name string
}
func (p *field) print() {
fmt.Println(p.name)
}
func main() {
data := []field{{"one"}, {"two"}, {"three"}}
for _, v := range data {
go v.print()
}
time.Sleep(3 * time.Second)
//goroutines print: three, three, three
}
```
* 打印one, two, three
```
package main
import (
"fmt"
"time"
)
type field struct {
name string
}
func (p *field) print() {
fmt.Println(p.name)
}
func main() {
data := []*field{{"one"}, {"two"}, {"three"}}
for _, v := range data {
go v.print()
}
time.Sleep(3 * time.Second)
//goroutines print: one, two, three
}
```
第一个打印三个three,表示理解,因为for迭代的时候变量会被重新使用,最终打印的是引用最后一个变量的值,但是为什么第二部分的代码是打印one, two, three,而不是打印three,three,three?
首次循环时候v的内容应该还是one吧
为啥go 调用会打不出来
这个地方我感觉跟c里面的未定义行为类似吧
为啥能断定一定是'three'
#6
更多评论
虽然楼主已经知道原因了,但是原因最好还是注明下,方便解惑他人。
先声明为了方便起见,我把指针称为地址,这样会更好理解一些。
先说下案例一
问题的关键在于print方法的声明,现在是func (p *field) print()。
1、首先明确一点,在for循环结束之后,复用变量v的值其实已经变成了three。
2、其次注意print方法的声明,func (p *field) print(),细心的同学发现了,print方法的接收者其实是一个地址。
3、for循环中,我们传递给print的方法接收者是对象v,但是方法接收者却要求是一个地址,这时,golang自动做了转换,把对象的地址传递过去了,注意是对象的地址传递过去了,而不是对象的值,参考第一条,因为v是复用变量,v的地址是不会变,但v的值会变,由one变成two,由two变成three,既然我们3次传递的都是v的地址,所以打印出来的自然是同一个值
再说下案例二
案例二中,print方法的声明依然是func (p *field) print(),但是打印出来的却是one,two,three,这是为何呢?
原因就是在于这次我们传递不再是一个对象,而是一个对象地址,这个时候,我们传递的数据类型和方法声明的类型是同一个类型,这个时候,可以理解为golang不会对参数进行转换,而是直接拷贝了传递过来的地址,既然每次传递过来的地址都是不一样的,那么打印出来的值自然不同
如何让案例一也输出one,two,three呢?
修改print方法的声明,func (p *field) print()改成func (p field) print(),我们把方法接收者改成对象,而不是对象的地址,,因为for中,每次传递过去的值都是不一样的,这个时候,打印出来的自然就是one,two,three。不建议这么做,原因在下面会说明。
说了那么多,总结一句话,注意传递给方法的接收者类型是不是该方法声明的类型,不一样的话,自然会出现一些意想不到的效果,最好在编码的时候,注意这点。
还有一点说明下上面为什么说不建议修改print方法的声明,案例一中,for循环其实是在拷贝数组中的每一个元素给v,这样内存是默默的一次次的无情消耗,所以,最好for循环遍历是指针数组,而不是对象数组。最好的方式是data就是一个指针数组,这样和方法声明一致。而且for循环中也不会产生大量的隐形拷贝行为。
#2