英文:
"cannot take the address of" and "cannot call pointer method on"
问题
这是编译并运行成功的代码:
diff := projected.Minus(c.Origin)
dir := diff.Normalize()
这段代码无法编译通过(会产生标题中的错误):
dir := projected.Minus(c.Origin).Normalize()
有人可以帮我理解为什么吗?(学习Go)
以下是那些方法的实现:
// Minus 从该向量中减去另一个向量
func (a *Vector3) Minus(b Vector3) Vector3 {
return Vector3{a.X - b.X, a.Y - b.Y, a.Z - b.Z}
}
// Normalize 将向量归一化为长度为1
func (a *Vector3) Normalize() Vector3 {
d := a.Length()
return Vector3{a.X / d, a.Y / d, a.Z / d}
}
英文:
This compiles and works:
diff := projected.Minus(c.Origin)
dir := diff.Normalize()
This does not (yields the errors in the title):
dir := projected.Minus(c.Origin).Normalize()
Can someone help me understand why? (learning Go)
Here are those methods:
// Minus subtracts another vector from this one
func (a *Vector3) Minus(b Vector3) Vector3 {
return Vector3{a.X - b.X, a.Y - b.Y, a.Z - b.Z}
}
// Normalize makes the vector of length 1
func (a *Vector3) Normalize() Vector3 {
d := a.Length()
return Vector3{a.X / d, a.Y / d, a.Z / d}
}
答案1
得分: 50
Vector3.Normalize()
方法具有指针接收器,因此为了调用该方法,需要一个指向 Vector3
值的指针 (*Vector3
)。在你的第一个示例中,你将 Vector3.Minus()
的返回值存储在一个变量中,该变量的类型将为 Vector3
。
在 Go 中,变量是可寻址的,当你写 diff.Normalize()
时,这是一种简写方式,编译器会自动取 diff
变量的地址,以获得调用 Normalize()
所需的类型为 *Vector3
的接收器值。因此,编译器会将其转换为
(&diff).Normalize()
这在规范: 调用中有详细说明:
> 如果 x
的方法集合(的类型)包含 m
,并且参数列表可以分配给 m
的参数列表,则方法调用 x.m()
是有效的。如果 x
是可寻址的,并且 &x
的方法集合包含 m
,则 x.m()
是 (&x).m()
的简写。
你的第二个示例无法工作的原因是函数和方法调用的返回值不可寻址,因此编译器无法在这里执行相同的操作,编译器无法获取 Vector3.Minus()
调用的返回值的地址。
可寻址的内容在规范: 地址运算符中有明确列出:
> 操作数必须是_可寻址的_,即变量、指针间接引用或切片索引操作;或者是可寻址结构操作数的字段选择器;或者是可寻址数组的数组索引操作。作为对可寻址要求的例外,x
(在 &x
的表达式中)也可以是(可能带括号的)复合字面量。
相关问题请参考:
可能的“解决方法”
最简单的方法(需要最少的更改)是将其赋值给一个变量,然后在调用方法之后调用。这是你的第一个可行解决方案。
另一种方法是修改方法,使其具有值接收器(而不是指针接收器),这样就不需要获取方法的返回值的地址,因此可以进行“链式”调用。请注意,如果方法需要修改接收器,这种方法可能不可行,因为只有指针才能修改接收器(因为接收器像任何其他参数一样传递 - 通过复制 -,如果它不是指针,你只能修改副本)。
另一种方法是修改返回值,将其返回指针(*Vector3
)而不是 Vector3
。如果返回值已经是指针,则无需获取其地址,因为对于需要指针接收器的方法来说,它已经足够好了。
你还可以创建一个简单的辅助函数,该函数返回其地址。它可能如下所示:
func pv(v Vector3) *Vector3 {
return &v
}
使用它:
dir := pv(projected.Minus(c.Origin)).Normalize()
这也可以是 Vector3
的一个方法,例如:
func (v Vector3) pv() *Vector3 {
return &v
}
然后使用它:
dir := projected.Minus(c.Origin).pv().Normalize()
一些注意事项:
如果你的类型仅由 3 个 float64
值组成,你不应该看到显著的性能差异。但是你应该在接收器和结果类型上保持一致。如果大多数方法具有指针接收器,那么所有方法都应该具有指针接收器。如果大多数方法返回指针,那么所有方法都应该返回指针。
英文:
The Vector3.Normalize()
method has a pointer receiver, so in order to call this method, a pointer to Vector3
value is required (*Vector3
). In your first example you store the return value of Vector3.Minus()
in a variable, which will be of type Vector3
.
Variables in Go are addressable, and when you write diff.Normalize()
, this is a shortcut, and the compiler will automatically take the address of the diff
variable to have the required receiver value of type *Vector3
in order to call Normalize()
. So the compiler will "transform" it to
(&diff).Normalize()
This is detailed in Spec: Calls:
> 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()
.
The reason why your second example doesn't work is because return values of function and method calls are not addressable, so the compiler is not able to do the same here, the compiler is not able to take the address of the return value of the Vector3.Minus()
call.
What is addressable is exactly listed in the Spec: Address operators:
> The operand must be addressable, that is, either a variable, pointer indirection, or slice indexing operation; or a field selector of an addressable struct operand; or an array indexing operation of an addressable array. As an exception to the addressability requirement, x
[in the expression of &x
] may also be a (possibly parenthesized) composite literal.
See related questions:
Possible "workarounds"
"Easiest" (requiring the least change) is simply to assign to a variable, and call the method after that. This is your first working solution.
Another way is to modify the methods to have a value receiver (instead of pointer receiver), so that there is no need to take the address of the return values of the methods, so calls can be "chained". Note that this might not be viable if a method needs to modify the receiver, as that is only possible if it is a pointer (as the receiver is passed just like any other parameters – by making a copy –, and if it's not a pointer, you could only modify the copy).
Another way is to modify the return values to return pointers (*Vector3
) instead of Vector3
. If the return value is already a pointer, no need to take its address as it's good as-is for the receiver to a method that requires a pointer receiver.
You may also create a simple helper function which returns its address. It could look something like this:
func pv(v Vector3) *Vector3 {
return &v
}
Using it:
dir := pv(projected.Minus(c.Origin)).Normalize()
This could also be a method of Vector3
, e.g.:
func (v Vector3) pv() *Vector3 {
return &v
}
And then using it:
dir := projected.Minus(c.Origin).pv().Normalize()
Some notes:
If your type consists of 3 float64
values only, you should not see significant performance differences. But you should be consistent about your receiver and result types. If most of your methods have pointer receivers, so should all of them. If most of your methods return pointers, so should all of them.
答案2
得分: 5
接受的答案太长了,所以我只会发布对我有帮助的部分:
我遇到了这个错误,与这行代码有关:
services.HashingServices{}.Hash("blabla")
所以我只是将其更改为:
(&services.HashingServices{}).Hash("blabla")
英文:
The accepted answer is really long so I'm just going to post what helped me:
I got this error regarding this line:
services.HashingServices{}.Hash("blabla")
so I just changed it to:
(&services.HashingServices{}).Hash("blabla")
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论