英文:
Why does a method with a pointer receiver still work when it receives a value?
问题
我刚刚在Go之旅中的第51个练习中玩耍。解释声称当Scale方法接收到一个Vertex而不是一个指向Vertex的指针时,它没有任何效果。
然而,当我在main中将声明v := &Vertex{3, 4}更改为v := Vertex{3, 4}时,输出中唯一的变化是缺少了&来标记指针。
那么为什么Scale会改变它接收到的变量,即使该变量不是一个指针呢?
英文:
I was just playing with Exercise 51 in the Tour of Go. The explanation claims the Scale method has no effect when it receives a Vertex instead of a pointer to a Vertex.
Yet when I change the declaration v := &Vertex{3, 4} to v := Vertex{3, 4} in main the only change in the output is the missing & to mark the pointer.
So why does Scale change the variable it receives even if the variable isn't a pointer?
答案1
得分: 8
它不“接收”一个值。Go是强类型的,所以如果某处规定了一个指向T的指针,那么指向T(*T)是唯一可能作为这种类型位置的值的选项。
“魔法”在于编译器,在某些条件下有效地“重写”你的代码:
如果方法集(x的类型)包含m,并且参数列表可以赋值给m的参数列表,则方法调用
x.m()是有效的。如果x是可寻址的,并且&x的方法集包含m,则x.m()是(&x).m()的简写形式:
相关:方法集
英文:
It does not "receive" a value. Go is strongly typed, so if somewhere a pointer to T is prescribed, a pointer to T (*T) is the only option which can happen as a value for such typed place.
The "magic" is in the compiler which effectively "rewrites" your code under certain conditions:
> A method call x.m() is valid if the method set of (the type of) x contains m and the argument list can be assigned to the parameter list of m. If x is addressable and &x's method set contains m, x.m() is shorthand for (&x).m():
Related: Method sets
答案2
得分: 1
这个差异并不是将v := &Vertex{3, 4}改为v:= Vertex{3, 4},而是改变了两个方法的定义,使其在值上工作而不是指针。所以,例如对于Scale,func (v *Vertex) Scale(f float64) {...变成了func (v Vertex) Scale(f float64) {...(注意(v *Vertex),一个指针值,变成了(v Vertex),一个非指针值)。在这两种情况下,你应该将v的声明保持为v := &Vertex{3, 4}。
你会注意到,在第一种情况下,当方法接受指针时,输出是&{15 20} 25。然而,当方法接受值而不是指针时,输出是&{3 4} 5。
在这两种情况下,v都是一个指向Vertex对象的指针。在第一种情况下,指针被传递给方法,一切都按预期工作 - 对Vertex对象所做的任何修改都是对原始值的修改,因此这些更改在方法返回后仍然存在。然而,在第二种情况下,尽管v仍然是一个指针,但Go编译器足够聪明,将v.Scale(5)转换为(*v).Scale(5),其中v被解引用,并将结果值传递给Scale。
英文:
The difference that the tour suggests isn't actually in changing v := &Vertex{3, 4} to v:= Vertex{3, 4}, but rather in changing the definitions of the two methods so that they work on values instead of pointers. So, for example, for Scale, func (v *Vertex) Scale(f float64) {... becomes func (v Vertex) Scale(f float64) {... (note that (v *Vertex), a pointer value, becomes (v Vertex), a non-pointer value). In both cases, you should leave the declaration of v as v := &Vertex{3, 4}.
You'll notice that in the first case, when the methods take pointers, the output is &{15 20} 25. However, when the methods take values rather than pointers, the output is &{3 4} 5.
In both cases, v is a pointer to a Vertex object. In the first case, the pointer is passed to the methods, and everything works as expected - any modifications made to the Vertex object are made to the original value, so those changes persist after the method returns. In the second case, though v is still a pointer, the Go compiler is smart enough to convert v.Scale(5) to (*v).Scale(5), where v is dereferenced, and the resulting value is passed to Scale.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。


评论