【问题标题】:Reassigning in pointer method receiver在指针方法接收器中重新分配
【发布时间】:2016-06-29 16:42:28
【问题描述】:

我对指针方法接收器和非指针方法接收器的理解是第一个可以在方法中修改,下一个不能。

所以,下面的工作完全符合我的预期。

type student struct {
    name string
    age  int
}

func (s *student) update() {
    s.name = "unknown"
    s.age = 0
}

func main() {
    s := student{"hongseok", 13}
    fmt.Println(s)

    s.update()
    fmt.Println(s)
}

它打印 hongseok/13unknown/0

但是,我想用重新分配一次替换整个 s 在更新方法中。所以,我刚刚改变了更新方法如下。

func (s *student) update() {
    s = &student{"unknown", 0}
}

并且它不会改变 main 方法中的 s 并打印双 hongseok/13

func (s *student) update() {
    *s = student{"unknown", 0}
}

以上更改解决了问题。

我认为没有语义差异。我错过了什么?

【问题讨论】:

    标签: go


    【解决方案1】:

    在第一个例子中:

    func (s *student) update() {
        s = &student{"unknown", 0}
    }
    

    您正在为s 分配一个全新的“指针值”,新的*s 指向一个新的student 值。变量s 只作用于方法体,所以返回后没有副作用。

    在第二个例子中

    func (s *student) update() {
        *s = student{"unknown", 0}
    }
    

    您正在取消引用s,并将*s 的值更改为指向新的student 值,或者换句话说,您将新的student 值放在s 所在的地址点。

    【讨论】:

      【解决方案2】:

      在本例中,您将存储在s 中的地址更改为不同的值;

      func (s *student) update() {
          s = &student{"unknown", 0}
      }
      

      虽然使用指针被视为“通过引用传递”,但引用本身是一个与任何其他被压入调用堆栈的值一样的值。当您返回 main 时,s 的值就是它在该范围内的任何值。因此,为了提供更具体的内容,您使用 s = 1 调用 main(为简单起见调用地址 1 和 2),在该方法中,您分配位于地址 2 的新 student 并设置 s = 2,当您返回时s 的版本从堆栈中弹出,s in main 指向 1,它保持不变。

      在后一个例子中;

      func (s *student) update() {
          *s = student{"unknown", 0}
      }
      

      您正在取消引用 s 并将新对象分配给该位置,从而覆盖现有内存。当您返回 main 中的指针仍指向同一位置时,但您在内存中的该位置有不同的数据。因此,在本例中,您正在编写一个新的 student 实例来寻址 1,因此当您返回时,您会在调用范围内看到新值。

      【讨论】:

        【解决方案3】:

        我认为,您的主要问题是您对问题中出现的两个概念都不太了解。

        让我们从指针开始。当您不使用指针时,赋值意味着创建先前值的简单副本。新值与前一个值没有任何约束。这意味着如果您更改旧值或新值,它不会影响第二个值。这对于原始类型(如 ints、bool、string)和结构是正常的。

        a := 1
        b := a
        a++   // a was changed, but value of b does not change at all
        

        现在是指针——它指向内存中的一些空间。为简单起见,我们创建了两个指针,它们都指向同一个地方。

        package main
        
        import (
            "fmt"
        )
        
        func main() {
            type student struct {
                name string
                age  int
            }
            p1 := &student{"hongseok", 13} // this mean create a new student, but we want only address of student, not value
            p2 := p1
            (*p1).age = 15      // because p1 and p2 point to same memory (*p2).age also changed
            fmt.Println("1.", p2.age) // 15
            p2.age = 32         // in golang I could write (*p2).ago or p2.ago this is the same
            fmt.Println("2.", p1.age) // 32
            fmt.Println("3.",p1, "==", p2)
            fmt.Printf("4. %p == %p\n", p1, p2)
            // But now I force point p2 to new place
            p2 = &student{"another student", 12}
            fmt.Println("5.", p1, "!=", p2)
            fmt.Printf("6. %p == %p\n", p1, p2)
            p1.age = 14    // does it influce p2.age? no! Why? Because it is in different address
            fmt.Println("7.", p1, "!=", p2)
                // but could is somehow force that point to same address ? Of course
            p2 = p1 // p2 will point to same place as p1 and old value of p2 will be freed by garbage collector 
        }
        

        不要混淆——指针也可以指向命名值。

        a := 42  // means allocate memory for integer, and we give that memory name "a" 
        p := &a
        *p++     // it change value of a
        a = 5    // and of course this change value of *p
        

        现在回到方法,接收器是/不是指针。如果方法的接收者是指针,这意味着您可以更改它的值 - 就像我在几行前所做的那样。

        如果方法接收器不是指针,这意味着 - 在调用方法之前,将创建一个结构副本,并在该副本上调用方法。你当然可以更改副本的价值,但它不会影响原始价值。

        【讨论】:

        • 如果没有第一句话,你的答案会好很多。
        • 同意,没有理由说第一句话,我们都在学习。
        猜你喜欢
        • 2017-07-30
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-07-30
        • 2016-02-29
        • 1970-01-01
        • 1970-01-01
        • 2018-02-28
        相关资源
        最近更新 更多