通过值或指针访问另一个结构体

huangapple go评论101阅读模式
英文:

Access to another struct by value or by pointer

问题

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

type foo_ struct {
	st uint8
	nd uint8
}

type bar struct {
	rd  uint8
	foo foo_
}

type barP struct {
	rd  uint8
	foo *foo_
}
英文:

What difference there is when you access to another struct by value or by a pointer?
When should be used each one of them?

type foo_ struct {
	st uint8
	nd uint8
}

type bar struct {
	rd  uint8
	foo foo_
}

type barP struct {
	rd  uint8
	foo *foo_
}

答案1

得分: 5

如果你声明或分配一个bar类型的变量,你会为rd uint8foo foo_两个变量预留并初始化为零的内存。在一个bar类型的变量中,始终嵌入一个foo_类型的变量。

var b bar  // 声明 b

如果你声明或分配一个barP类型的变量,你会为rd uint8foo *foo_两个变量预留并初始化为零的内存。零值指针是一个nil指针。不会分配foo_类型的变量;你必须单独完成这个操作。一个barP类型的变量要么指向零个(foo == nil),要么指向一个foo_类型的变量。一个barP类型的变量可能指向与其他barP类型的变量相同的foo_类型的变量,共享同一个foo_类型的变量的副本。对共享副本的更改会被所有指向它的变量看到。

var bp barP         // 声明 bp
bp.foo = new(foo_)  // 分配 bp.foo

使用哪种类型取决于bar类型与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
}
英文:

If you declare or allocate a variable of type bar, you reserve and initialize to zero memory for both rd uint8 and foo foo_. There is always one variable of type foo_ embedded in a variable of type bar.

var b bar  // declare b

If you declare or allocate a variable of type barP, you reserve and initialize to zero memory for both rd uint8 and foo *foo_. A zero value pointer is a nil pointer. No variable of type foo_ is allocated; you must do that separately. There is either zero (foo == nil) or one variable of type foo_ pointed to by a variable of type barP. A variable of type barP may point to the same variable of type foo_ as other variables of type barP, sharing the same copy of the variable of type foo_. A change to a shared copy is seen by all variables that point to it.

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

Which one to use depends on the properties of type bar versus type barP. Which type more closely reflects the problem that you are trying to solve?

For example, consider this invoice problem. We always have a billing address; we are always going to ask for our money. However, we often ship to the billing address, but not always. If the shipping address is nil, use the billing address. Otherwise, use a separate shipping address. We have two warehouses, and we always ship from one or the other. We can share the two warehouse locations. Since we don't send an invoice until the order is shipped from the warehouse, the warehouse location will never be 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

得分: 2

答案在很大程度上与语言无关 - 在C中的等效部分存在相同的问题。

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

当你有一个指向值的指针(如barP)时,一些类型为barP的结构可能共享相同的foo。当任何一个barP修改它指向的foo的一部分时,它会影响到所有指向相同位置的其他barP结构。此外,正如注释所建议的那样,你必须管理两个单独的对象 - barPfoo,而不是只有一个普通的bar类型。

在某些语言中,你需要担心悬空指针和未初始化的值等问题;Go语言具有垃圾回收功能,并且通常比其他语言更具类型安全性。

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

英文:

The answer is largely independent of language - the equivalent in C has the same issues.

When you have an embedded value (as in bar), then your structure is big enough to hold the complete sub-structure and the other part.

When you have a pointer to a value (as in barP), then a number of structures of type barP may share the same foo. When any of the barP modifies a part of the foo it points to, it affects all the other barP structures that point to the same place. Also, as the commentary suggests, you have to manage two separate objects - the barP and the foo as against one with the plain bar type.

In some languages, you would have to worry about dangling pointers and uninitialized values etc; Go is garbage collected and generally more type-safe than other languages.

So, use a pointer when you want multiple barP objects to share the same foo object; otherwise, use an explicit member object, rather than a pointer to an object.

答案3

得分: 2

Golang FAQ现在总结了以下两种方法之间的区别:

func (s *MyStruct) pointerMethod() { } // 指针方法
func (s MyStruct)  valueMethod()   { } // 值方法

> 首先,最重要的是,方法是否需要修改接收者
如果需要修改,接收者必须是指针。(切片和映射是引用类型,所以它们的情况稍微复杂一些,但是例如在方法中改变切片的长度,接收者仍然必须是指针。)
在上面的例子中,如果pointerMethod修改了s的字段,调用者将看到这些更改,但是valueMethod是使用调用者参数的副本调用的(这就是传值的定义),所以它所做的更改对调用者是不可见的。
顺便说一下,在Java中,指针接收者与此情况完全相同,尽管在Java中指针是隐藏在底层的;而Go的值接收者则是不寻常的。

> 其次是效率的考虑。如果接收者很大,比如一个大的结构体,使用指针接收者会更加高效。

(这个效率点也在“内存、内存中的变量和指针”中有所说明)

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

英文:

The Golang FAQ now summarizes the difference between:

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

> First, and most important, does the method need to modify the receiver?
If it does, the receiver must be a pointer. (Slices and maps are reference types, so their story is a little more subtle, but for instance to change the length of a slice in a method the receiver must still be a pointer.)
In the examples above, if pointerMethod modifies the fields of s, the caller will see those changes, but valueMethod is called with a copy of the caller's argument (that's the definition of passing a value), so changes it makes will be invisible to the caller.
By the way, pointer receivers are identical to the situation in Java, although in Java the pointers are hidden under the covers; it's Go's value receivers that are unusual.

> Second is the consideration of efficiency. If the receiver is large, a big struct for instance, it will be much cheaper to use a pointer receiver.

(This efficiency point is also illustrated in "Memory, variables in memory, and pointers ")

> Next is consistency. If some of the methods of the type must have pointer receivers, the rest should too, so the method set is consistent regardless of how the type is used. See the section on method sets for details.

huangapple
  • 本文由 发表于 2010年5月3日 02:26:02
  • 转载请务必保留本文链接:https://go.coder-hub.com/2754474.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定