21

我目前正在学习使用 Go 语言进行编程。我在理解 Go 指针时遇到了一些困难(而且我的 C/C++ 现在还很远......)。例如,在 Tour of 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}

反之亦然:

func (v *Vertex) Abs() float64 {
[...]
v := Vertex{3, 4}

我得到了完全相同的结果。有区别(内存方面等)吗?

4

5 回答 5

33

您的示例使用的 Go 语言有两种不同的规则:

  1. 可以从具有值接收器的方法派生具有指针接收器的方法。因此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) }  // GENERATED METHOD
    

    编译器会自动找到生成的方法:

    v := &Vertex{3, 4}
    v.Abs()  // calls the generated method
    
  2. 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()
    
于 2013-02-26T20:44:17.507 回答
13

有区别。例如,非指针接收器形式强制该方法在副本上工作。这样,该方法就不能改变它被调用的实例——它只能访问副本。这在时间/内存性能/消耗等方面可能无效。

OTOH,指向实例的指针和带有指针接收器的方法允许在需要时轻松共享(和变异)实例。

更多细节在这里

于 2013-02-26T18:27:46.537 回答
2

区别在于传递引用与传递值。

func f(v Vertex)参数中被复制到参数v中。在func f(v *Vertex)一个指向现有Vertex实例的指针中传递。

使用方法时,可以为您完成一些取消引用,因此您可以拥有一个方法func (v *Vertex) f()并调用它而无需先获取指针:v := Vertex{...}; v.f(). 这只是语法糖的一小部分,AFAIK。

于 2013-02-26T18:27:35.547 回答
0

这些示例有两个主要区别:

func (v *Vertex) Abs()....

接收器将通过引用传递v 并且您只能在指针上调用此方法:

v := Vertex{1,3}
v.Abs() // This will result in compile time error
&v.Abs() // But this will work

另一方面

func (v Vertex) Abs() ....

您可以在指针和结构上调用此方法。即使您在指针上调用此方法,接收器也将按值传递。

v := Vertex{1,3}
v.Abs() // This will work, v will be copied.
&v.Abs() // This will also work, v will also be copied.

您可以同时声明func (v *Vertex)func (v Vertex)

于 2014-11-05T15:06:19.953 回答
0

正如规范所说

如果 x 的(类型)的方法集包含 m 并且参数列表可以分配给 m 的参数列表,则方法调用 xm() 是有效的。如果 x 是可寻址的并且 &x 的方法集包含 m,则 xm() 是 (&x).m() 的简写:

在您的情况下,如果方法是可寻址的,则 v.Abs() 是 &v.Abs() 的简写。

于 2014-12-27T19:10:32.933 回答