你如何决定是否应该将指针作为接收器来使用你的函数?

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

How do you decide whether your function should use a pointer as is receiver?

问题

如果你想给每个节点添加一个函数,我会选择选项A:func (n *Node) height() int

原因是,当你使用指针作为接收器时,你可以修改节点的值或者修改节点的左右子节点。这对于计算节点的高度来说是很有用的。另外,使用指针作为接收器可以避免在函数调用时进行值的复制,提高性能。

相反,如果你选择选项B:func (n Node) height() int,则每次调用该函数时,会创建一个节点的副本。这样可能会导致性能下降,并且无法修改节点的值或者子节点。

所以,根据你的需求,使用指针作为接收器是更好的选择。

英文:

Consider a binary tree node:

type Node struct {
    value uint8
    left, right *Node
}

If I want to add a function to each Node, should it be:

A:  func (n *Node) height() int

or

B:  func (n Node) height() int

I want to know which of A or B you would choose and why.

I can model linked lists or recursive structs, but I don't know when the receiver should be a pointer or not-a-pointer.

答案1

得分: 6

主要区别在于按值传递按引用传递的概念。

当你使用示例A时,你是通过引用传递,这意味着你对func (n *Node) height内部的n所做的任何更改都会应用到你用来调用Node.height()的节点上。

相比之下,示例B中所做的是按值传递,你实际上只是传递了一个节点的副本来调用Node.height(),因此对该节点的任何更改都不会应用于函数外部。

这里有一个小的示例来演示这些区别:http://play.golang.org/p/JodPRPBHDg

请注意,在示例中当你调用node.incHeight()时,它会进入函数:

func (n Node) incHeight() uint8 {
  n.value++
  return n.value
}

因为这是按值传递,所以node.value仍然是相同的,除非你将返回的值存储为node.value。然而,如果你调用node.incrementHeight(),它会进入函数:

func (n *Node) incrementHeight() {
  n.value++
  return
}

这将改变node.value的值,而无需返回任何内容,因为它引用的是原始结构体,而不是结构体的副本。

所以对于你要选择哪个以及为什么,这实际上取决于你是否希望能够对用来调用函数的结构体进行持久性更改,或者你是否只想传递一个可以查看和修改值的副本,但只在函数内部有效,并且对原始结构体没有任何持久性影响。

英文:

The main difference is the idea of pass by value vs pass by reference

When you're using example A you're passing be reference, meaning that any changes you apply to n inside of func (n *Node) height will apply to the Node you're using to call Node.height().

By comparison, what you're doing in example B is passing by value where you're really just passing a copy of the Node you're using to call Node.height() so any changes to that Node will not apply outside of the function.

Here is a small playground to demonstrate the differences: http://play.golang.org/p/JodPRPBHDg

Notice in the example that when you call node.incHeight(), it goes to the function:

func (n Node) incHeight() uint8 {
  n.value++
  return n.value
}

Because this is pass by value the node.value is still the same unless you store the returned value as node.value. However, if you call node.incrementHeight() it goes to the function:

func (n *Node) incrementHeight() {
  n.value++
  return
}

This will change the value of node.value without needing to return anything because its referencing the original struct, rather than a copy of the struct.

So in answer to which would you choose and why, it really depends on whether you want to be able to make lasting changes to the struct you're using to call the function or if you'd rather just pass a copy that can see and alter the values but only while within the function and not have any lasting effects on the original struct.

答案2

得分: 5

在https://code.google.com/p/go-wiki/wiki/Style中有一个非常好的解释。

接收器类型

对于新的Go程序员来说,选择在方法中使用值接收器还是指针接收器可能很困难。如果不确定,使用指针即可,但有时候值接收器也是有意义的,通常是出于效率的考虑,比如对于小型不可变的结构体或基本类型的值。以下是一些经验法则:

  • 如果接收器是map、func或chan,则不要使用指针。

  • 如果接收器是slice,并且方法不会重新切片或重新分配slice,则不要使用指针。

  • 如果方法需要修改接收器,则接收器必须是指针。

  • 如果接收器是包含sync.Mutex或类似的同步字段的结构体,则接收器必须是指针,以避免复制。

  • 如果接收器是大型结构体或数组,则指针接收器更高效。多大才算大?假设将其所有元素作为参数传递给方法。如果感觉太大,那么对于接收器来说也太大了。

  • 函数或方法是否可以在并发时或从该方法调用时修改接收器?值类型在调用方法时会创建接收器的副本,因此外部更新不会应用于此接收器。如果必须在原始接收器中看到更改,则接收器必须是指针。

  • 如果接收器是结构体、数组或切片,并且其中任何一个元素是指向可能发生变化的内容的指针,请优先选择指针接收器,因为这将使读者更清楚其意图。

  • 如果接收器是一个自然是值类型的小型数组或结构体(例如,类似time.Time类型的内容),没有可变字段和指针,或者只是一个简单的基本类型(如int或string),则值接收器是有意义的。值接收器可以减少可能产生的垃圾量;如果将值传递给值方法,可以使用栈上的副本而不是在堆上分配。(编译器会尝试智能地避免此分配,但并不总是成功。)在进行性能分析之前,不要仅出于这个原因选择值接收器类型。

  • 最后,如果不确定,使用指针接收器。

英文:

There is a very nice explanation in https://code.google.com/p/go-wiki/wiki/Style

> Receiver Type
>
> Choosing whether to use a value or pointer receiver on
> methods can be difficult, especially to new Go programmers. If in
> doubt, use a pointer, but there are times when a value receiver makes
> sense, usually for reasons of efficiency, such as for small unchanging
> structs or values of basic type. Some rules of thumb:
>
> - If the receiver is a map, func or chan, don't use a pointer to it.
>
> - If the receiver is a slice and the method doesn't reslice or reallocate the slice, don't use a pointer to it.
>
> - If the method needs to mutate the receiver, the receiver must be a pointer.
>
> - If the receiver is a struct that contains a sync.Mutex or similar synchronizing field, the receiver must be a pointer to avoid copying.
>
> - If the receiver is a large struct or array, a pointer receiver is more efficient. How large is large? Assume it's equivalent to passing
> all its elements as arguments to the method. If that feels too large,
> it's also too large for the receiver.
>
> - Can function or methods, either concurrently or when called from this method, be mutating the receiver? A value type creates a copy
> of the receiver when the method is invoked, so outside updates will
> not be applied to this receiver. If changes must be visible in the
> original receiver, the receiver must be a pointer.
>
> - If the receiver is a struct, array or slice and any of its elements is a pointer to something that might be mutating, prefer a pointer
> receiver, as it will make the intention more clear to the reader.
>
> - If the receiver is a small array or struct that is naturally a value type (for instance, something like the time.Time type), with
> no mutable fields and no pointers, or is just a simple basic type
> such as int or string, a value receiver makes sense. A value
> receiver can reduce the amount of garbage that can be generated; if
> a value is passed to a value method, an on-stack copy can be used
> instead of allocating on the heap. (The compiler tries to be smart
> about avoiding this allocation, but it can't always succeed.) Don't
> choose a value receiver type for this reason without profiling
> first.
>
> - Finally, when in doubt, use a pointer receiver.

huangapple
  • 本文由 发表于 2014年2月27日 09:31:47
  • 转载请务必保留本文链接:https://go.coder-hub.com/22057256.html
匿名

发表评论

匿名网友

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

确定