英文:
Golang pointers
问题
我目前正在学习使用Go语言编程。
我在理解Go指针方面遇到了一些困难(而且我的C/C++知识已经很久没有用了)。
例如,在Go之旅#52(http://tour.golang.org/#52)中,我读到:
type Vertex struct {
X, Y float64
}
func (v *Vertex) Abs() float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
func main() {
v := &Vertex{3, 4}
fmt.Println(v.Abs())
}
但是,如果我将代码改为:
func (v Vertex) Abs() float64 {
[...]
v := Vertex{3, 4}
或者甚至:
func (v Vertex) Abs() float64 {
[...]
v := &Vertex{3, 4}
反之亦然:
func (v *Vertex) Abs() float64 {
[...]
v := Vertex{3, 4}
我得到了完全相同的结果。是否有区别(在内存方面等)?
英文:
I am currently learning to program with Go language.
I am having some difficulties understanding Go pointers (and my C/C++ is far away now...).
In the Tour of Go #52 (http://tour.golang.org/#52) for example, I read:
type Vertex struct {
X, Y float64
}
func (v *Vertex) Abs() float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
func main() {
v := &Vertex{3, 4}
fmt.Println(v.Abs())
}
But if instead of
func (v *Vertex) Abs() float64 {
[...]
v := &Vertex{3, 4}
I wrote:
func (v Vertex) Abs() float64 {
[...]
v := Vertex{3, 4}
Or even:
func (v Vertex) Abs() float64 {
[...]
v := &Vertex{3, 4}
and vice-versa:
func (v *Vertex) Abs() float64 {
[...]
v := Vertex{3, 4}
I got the exact same result. Is there a difference (memory-wise, etc)?
答案1
得分: 33
Go语言中有两种不同的规则用于你的示例:
-
可以从具有值接收器的方法派生具有指针接收器的方法。因此,
func (v Vertex) Abs() float64
将自动生成一个额外的方法实现:func (v Vertex) Abs() float64 { return math.Sqrt(v.X*v.X+v.Y*v.Y) } func (v *Vertex) Abs() float64 { return Vertex.Abs(*v) } // 生成的方法
编译器将自动找到生成的方法:
v := &Vertex{3, 4} v.Abs() // 调用生成的方法
-
Go语言可以自动获取变量的地址。在以下示例中:
func (v *Vertex) Abs() float64 { return math.Sqrt(v.X*v.X+v.Y*v.Y) } func main() { v := Vertex{3, 4} v.Abs() }
表达式
v.Abs()
等同于以下代码:vp := &v vp.Abs()
英文:
There are two different rules of the Go language used by your examples:
-
It is possible to derive a method with a pointer receiver from a method with a value receiver. Thus
func (v Vertex) Abs() float64
will automatically generate an additional method implementation:func (v Vertex) Abs() float64 { return math.Sqrt(v.X*v.X+v.Y*v.Y) } func (v *Vertex) Abs() float64 { return Vertex.Abs(*v) } // GENERATED METHOD
The compiler will automatically find the generated method:
v := &Vertex{3, 4} v.Abs() // calls the generated method
-
Go can automatically take the address of a variable. In the following example:
func (v *Vertex) Abs() float64 { return math.Sqrt(v.X*v.X+v.Y*v.Y) } func main() { v := Vertex{3, 4} v.Abs() }
the expression
v.Abs()
is equivalent to the following code:vp := &v vp.Abs()
答案2
得分: 13
有一些区别。例如,非指针接收器形式会强制方法在副本上工作。这样,方法就无法改变它被调用的实例 - 它只能访问副本。这可能在时间/内存性能/消耗等方面是低效的。
另一方面,指向实例的指针和具有指针接收器的方法允许在需要时轻松共享实例(和改变实例)。
更多详细信息在这里。
英文:
There are differences. For example, the non-pointer receiver form forces the method to work on a copy. This way the method is not able to mutate the instance it was invoked on - it can access only the copy. Which might be ineffective in terms of e.g. time/memory performance/consumption etc.
OTOH, pointer to instances and methods with pointer receivers allow for easy instance sharing (and mutating) where desirable.
More details here.
答案3
得分: 2
差异在于按引用传递和按值传递。
在 func f(v Vertex)
中,参数被 复制 到参数 v
中。在 func f(v *Vertex)
中,传递了一个指向现有 Vertex
实例的指针。
在使用方法时,一些解引用操作可以为您完成,因此您可以拥有一个方法 func (v *Vertex) f()
并在不先取指针的情况下调用它:v := Vertex{...}; v.f()
。据我所知,这只是一点语法糖。
英文:
The difference is pass-by-referenve vs pass-by-value.
In func f(v Vertex)
the argument is copied into parameter v
. In func f(v *Vertex)
a pointer to an existing Vertex
instance is passed.
When using methods, some of the dereferencing can be done for you, so you can have a method func (v *Vertex) f()
and call it without taking a pointer first: v := Vertex{...}; v.f()
. This is just a grain of syntax sugar, AFAIK.
答案4
得分: 0
在这些示例中有两个主要的区别:
func (v *Vertex) Abs()....
对于v,接收器将是按引用传递的,您只能在指针上调用此方法:
v := Vertex{1,3}
v.Abs() // 这将导致编译时错误
&v.Abs() // 但这将起作用
另一方面
func (v Vertex) Abs() ....
您可以在指针和结构体上都调用此方法。即使在指针上调用此方法时,接收器也将是按值传递的。
v := Vertex{1,3}
v.Abs() // 这将起作用,v将被复制。
&v.Abs() // 这也将起作用,v也将被复制。
您可以声明func (v *Vertex)
和func (v Vertex)
。
英文:
There are two main differences in those examples:
func (v *Vertex) Abs()....
The receiver will be passed-by-reference for v and you would be able to call this method only on pointers:
v := Vertex{1,3}
v.Abs() // This will result in compile time error
&v.Abs() // But this will work
On the other hand
func (v Vertex) Abs() ....
You can call this method on both pointers and structs. The receiver will be passed-by-value even when you call this method on pointers.
v := Vertex{1,3}
v.Abs() // This will work, v will be copied.
&v.Abs() // This will also work, v will also be copied.
You can declare both func (v *Vertex)
and func (v Vertex)
.
答案5
得分: 0
根据规范所说
> 如果方法集(类型的)x 包含 m 并且参数列表可以分配给 m 的参数列表,则方法调用 x.m() 是有效的。如果 x 是可寻址的并且 &x 的方法集包含 m,则 x.m() 是 (&x).m() 的简写:
在你的情况下,如果方法是可寻址的,v.Abs() 是 &v.Abs() 的简写。
英文:
As the specification says
> 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():
In your case v.Abs() is shorthand for &v.Abs() if method is addressable.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论