为什么在Go语言中接收器是按值传递的?

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

Why are receivers pass by value in Go?

问题

似乎你总是希望这样写:

func (self *Widget) Do() {
}

而不是这样写:

func (self Widget) Do() {
}

如果是这样的话,那么以前的语义应该通过使用后一种语法来实现。也就是说,接收者应该是按引用传递的。

英文:

Seems like you'd ALWAYS want this:

func (self *Widget) Do() {
}

instead of this

func (self Widget) Do() {
}

If so, then the way to get the former semantics OUGHT to be by using the latter syntax. i.e. receivers ought to be pass by reference.

答案1

得分: 30

这是因为Go语言中的一切都是按值传递。这使得它与其他C系列语言保持一致,并且意味着你永远不需要记住你所看到的情况是按值传递还是不按值传递。

从链接中可以看到:

> 与C系列语言中的所有语言一样,Go语言中的一切都是按值传递的。也就是说,函数总是得到被传递的东西的副本,就好像有一个赋值语句将值赋给参数一样。例如,将int值传递给函数会复制int,将指针值传递给函数会复制指针,但不会复制指针所指向的数据。(有关这如何影响方法接收器的讨论,请参见下一节。)

然后稍后:

> func (s *MyStruct) pointerMethod() { } // 指针方法
func (s MyStruct) valueMethod() { } // 值方法
> 对于不习惯使用指针的程序员来说,这两个示例之间的区别可能会令人困惑,但实际情况非常简单。在为类型定义方法时,接收器(上述示例中的s)的行为就像它是方法的参数一样。因此,将接收器定义为值还是指针是同一个问题,就像函数参数应该是值还是指针一样。有几个考虑因素。

> 首先,也是最重要的,方法是否需要修改接收器?如果需要修改,接收器必须是指针。(切片和映射作为引用,所以它们的情况要稍微复杂一些,但是例如要在方法中更改切片的长度,接收器仍然必须是指针。)在上面的示例中,如果pointerMethod修改了s的字段,调用者将看到这些更改,但是valueMethod是使用调用者的参数的副本调用的(这就是传递值的定义),所以它所做的更改对调用者是不可见的。

> 顺便说一下,指针接收器与Java中的情况完全相同,尽管在Java中指针是隐藏在底层的;而Go语言中的值接收器才是不寻常的。

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

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

> 对于基本类型、切片和小结构体等类型,值接收器非常便宜,因此除非方法的语义要求使用指针,否则值接收器是高效且清晰的。

英文:

It is because everything in Go is pass by value. This makes it consistent with other C family languages, and means that you never need to remember whether the situation you're looking at is pass by value or not.

From that link:

> As in all languages in the C family, everything in Go is passed by value. That is, a function always gets a copy of the thing being passed, as if there were an assignment statement assigning the value to the parameter. For instance, passing an int value to a function makes a copy of the int, and passing a pointer value makes a copy of the pointer, but not the data it points to. (See the next section for a discussion of how this affects method receivers.)

Then later:

> func (s *MyStruct) pointerMethod() { } // method on pointer
func (s MyStruct) valueMethod() { } // method on value
> For programmers unaccustomed to pointers, the distinction between these two examples can be confusing, but the situation is actually very simple. When defining a method on a type, the receiver (s in the above examples) behaves exactly as if it were an argument to the method. Whether to define the receiver as a value or as a pointer is the same question, then, as whether a function argument should be a value or a pointer. There are several considerations.

> First, and most important, does the method need to modify the receiver? If it does, the receiver must be a pointer. (Slices and maps act as references, 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.

> 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.

> For types such as basic types, slices, and small structs, a value receiver is very cheap so unless the semantics of the method requires a pointer, a value receiver is efficient and clear.

答案2

得分: 6

有时候你并不想通过引用传递。以下是一个例子:

func (self Widget) Get() Value {
}

如果你有一个小的不可变对象,这种语义可能很有用。调用者可以确定这个方法不会修改它的接收者。如果接收者是一个指针,调用者在不阅读代码的情况下无法确定这一点。

再举一个例子:

// 获取配置的访问器
func (self Thing) GetConfig() *Config {
}

通过查看这个方法,我可以知道GetConfig总是返回相同的Config。我可以修改这个配置,但是我不能修改Thing内部的指向Config的指针。这与Thing内部的一个常量指针非常接近。

英文:

Sometimes you don't want to pass by reference though. The semantics of

func (self Widget) Get() Value {
}

Can be useful if for instance you have a small immutable object. The caller can know for certain that this method doesn't modify it's reciever. They can't know this if the reciever is a pointer without reading the code first.

To expand on that for instance

// accessor for things Config
func (self Thing) GetConfig() *Config {
}

Just by looking at this method I can know GetConfig is always going to return the same Config. I can modify that config but I can't modify the pointer to Config inside Thing. It's pretty close to a const pointer inside of Thing.

答案3

得分: 2

似乎你总是想要这样:

不,值接收器更通用。它可以在所有可以使用指针接收器的地方使用;但是指针接收器不能在所有可以使用值接收器的地方使用——例如,如果你有一个类型为Widget的右值表达式,你可以在其上调用值接收器方法,但不能调用指针接收器方法。

英文:

> Seems like you'd ALWAYS want this:

No. The value receiver is more general. It can be used in all the places that a pointer receiver can; but a pointer receiver cannot be used in all the places that a value receiver can -- for example, if you have an rvalue expression of the type Widget; you can call value-receiver methods on it, but not pointer-receiver methods.

huangapple
  • 本文由 发表于 2013年8月26日 09:37:14
  • 转载请务必保留本文链接:https://go.coder-hub.com/18435498.html
匿名

发表评论

匿名网友

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

确定