【问题标题】:Golang pointersGolang 指针
【发布时间】:2013-02-26 14:31:54
【问题描述】:

我目前正在学习使用 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}

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

【问题讨论】:

  • 您好,欢迎来到程序员。诸如此类的直接实施问题在这里是题外话,但在 Stack Overflow 上是主题。我将启动迁移。度过愉快的一天。
  • 尝试在所有方法中更改v,然后在调用后更改原始fmt.Println(),您会看到不同。使用(v Vertex) 版本,您将获得原始版本的副本。如果它是在指针上调用的,它会自动为您取消引用。

标签: pointers go


【解决方案1】:

您的示例使用了两种不同的 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()
    

【讨论】:

  • 执行 *v 是否会在内存(顶点结构)中生成 v 的副本?如果否,那么在执行 func (v Vertex) Abs() 期间 v 本身的变化是否反映在外部?
  • 在输入func (v Vertex) Abs()之前会创建一个struct Vertex的新副本。
  • @Atom 在第一种情况下写是不是更正确,在输入func (v Vertex) Abs()之前复制了struct Vertex:func (v *Vertex) Abs() float64 { return (*v).Abs() } // GENERATED METHOD?我无法理解 Vertex.Abs(*v) ...
  • (*v).Abs() 也是正确的,它等同于Vertex.Abs(*v)。这两个表达式是等价的,只是语法不同。
  • 如何获得指向结构成员的指针?你已经明白了 v。你如何获得指向 v 成员的指针?
【解决方案2】:

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

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

更多详情here.

【讨论】:

    【解决方案3】:

    区别在于 pass-by-referenve 与 pass-by-value。

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

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

    【讨论】:

      【解决方案4】:

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

      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)

      【讨论】:

        【解决方案5】:

        正如规范所说

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

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

        【讨论】:

          猜你喜欢
          • 2021-12-16
          • 2015-01-10
          • 1970-01-01
          • 1970-01-01
          • 2017-06-21
          • 2019-05-09
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多