【问题标题】:Access to another struct by value or by pointer通过值或指针访问另一个结构
【发布时间】:2011-02-14 19:37:05
【问题描述】:

通过值或指针访问另一个结构有什么区别?
什么时候应该使用它们中的每一个?

type foo_ struct {
    st uint8
    nd uint8
}

type bar struct {
    rd  uint8
    foo foo_
}

type barP struct {
    rd  uint8
    foo *foo_
}

【问题讨论】:

    标签: struct pointers go


    【解决方案1】:

    如果您声明或分配type bar 的变量,则为rd uint8foo foo_ 保留并初始化为零内存。在type bar的变量中总是嵌入了一个type foo_的变量。

    var b bar  // declare b
    

    如果您声明或分配type barP 的变量,则为rd uint8foo *foo_ 保留并初始化为零内存。零值指针是nil 指针。没有分配type foo_ 的变量;你必须分开做。 type barP 的变量指向了零个 (foo == nil) 或 type foo_ 的一个变量。 type barP 的变量可能与type barP 的其他变量一样指向type foo_ 的同一个变量,共享type foo_ 变量的同一个副本。指向共享副本的所有变量都可以看到对共享副本的更改。

    var bp barP         // declare bp
    bp.foo = new(foo_)  // allocate bp.foo
    

    使用哪一个取决于type bartype barP 的属性。哪种类型更能反映您要解决的问题?

    例如,考虑这个发票问题。我们总是有一个帐单地址;我们总是要我们的钱。但是,我们经常运送到帐单地址,但并非总是如此。如果送货地址是nil,请使用帐单地址。否则,请使用单独的送货地址。我们有两个仓库,我们总是从一个仓库发货。我们可以共享两个仓库位置。由于我们在订单从仓库发货之前不会发送发票,因此仓库位置永远不会是nil

    type address struct {
        street string
        city   string
    }
    
    type warehouse struct {
        address string
    }
    
    type invoice struct {
        name      string
        billing   address
        shipping  *address
        warehouse *warehouse
    }
    

    【讨论】:

      【解决方案2】:

      答案在很大程度上与语言无关 - C 中的等价物也有同样的问题。

      当你有一个嵌入值时(如bar),那么你的结构就足够大,可以容纳完整的子结构和其他部分。

      当你有一个指向一个值的指针时(如barP),那么许多barP 类型的结构可以共享同一个foo。当任何barP 修改它指向的foo 的一部分时,它会影响指向同一位置的所有其他barP 结构。此外,正如评论所暗示的,您必须管理两个单独的对象 - barPfoo,而不是使用普通的 bar 类型。

      在某些语言中,您将不得不担心悬空指针和未初始化的值等; Go 是垃圾收集的,通常比其他语言更安全。

      因此,当您希望多个barP 对象共享同一个foo 对象时,请使用指针;否则,请使用显式成员对象,而不是指向对象的指针。

      【讨论】:

      • type barP的变量被声明或分配时,foo的初始值为nil指针。
      【解决方案3】:

      Golang FAQ 现在总结了两者之间的区别:

      func (s *MyStruct) pointerMethod() { } // method on pointer
      func (s MyStruct)  valueMethod()   { } // method on value
      

      首先,也是最重要的,方法是否需要修改接收器
      如果是,接收者必须是一个指针。 (切片和映射是引用类型,所以它们的故事有点微妙,但是例如要在方法中更改切片的长度,接收器仍然必须是指针。)
      在上面的例子中,如果pointerMethod 修改了s 的字段,调用者会看到这些变化,但是valueMethod 是用调用者参数的副本调用的(这是传递值的定义),所以改变它make 对调用者是不可见的。
      顺便说一句,指针接收器与 Java 中的情况相同,尽管在 Java 中指针隐藏在幕后。不同寻常的是 Go 的值接收器。

      其次是考虑效率。如果接收器很大,例如一个大结构体,使用指针接收器会便宜很多。

      (这个效率点也在“Memory, variables in memory, and pointers ”中说明)

      接下来是一致性。如果该类型的某些方法必须具有指针接收器,则其余的也应该具有,因此无论该类型如何使用,方法集都是一致的。有关详细信息,请参阅method sets 部分。

      【讨论】:

        猜你喜欢
        • 2021-12-11
        • 1970-01-01
        • 1970-01-01
        • 2011-02-03
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多