英文:
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
.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论