我最近阅读了The Go Programming Language Book,这是学习golang编程语言的好资源 . 6.2节中有一段关于 T
类型的复制实例,当它是方法的指针接收器时,我无法理解它 . 有没有用一个有意义的例子来解释这一段?
6.2带指针接收器的方法如果命名类型T的所有方法都有一个T本身的接收器类型(不是* T),则复制该类型的实例是安全的 . 调用它的任何方法都必须复制 . 例如,time.Duration值被大量复制,包括作为函数的参数 . 但是如果任何方法都有一个指针接收器,你应该避免复制T的实例,因为这样做可能会违反内部不变量 . 例如,复制bytes.Buffer的实例会导致原始和副本别名(§2.3.2)为相同的底层字节数组 . 后续方法调用会产生不可预测的影响 . (Go编程语言Alan A. A. Donovan·Brian W. Kernighan)
1 回答
调用方法时,首先复制调用方法的值,并将该副本作为接收方传递/使用 .
如果一个类型只有带有值接收器的方法,那意味着无论方法在内部做什么,无论你(或其他任何人)调用什么方法,这些方法都无法改变原始值,因为 - 注意到上面 - 只传递一个副本,该方法只能修改副本 - 而不是原始副本 .
所以这意味着如果你复制了这个值,你就不必担心,原始版本和副本上调用的方法都不能/不会修改值 .
当类型具有指针接收器的方法时,不是 . 如果一个方法有一个指针接收器,该方法可以改变/修改指向的值,这不是一个副本,它是原始值(只有指针是一个副本,但它指向原始值) .
我们来看一个例子吧 . 我们创建了一个
int
包装器类型,它有2个字段:int
和*int
. 我们打算在两个字段中存储相同的数字,但一个是指针(我们将int
存储在指向的值中):为了确保两个值(
v
和*p
)相同,我们提供了一个Set()
方法,它设置两个:Wrapper.Set()
有一个指针接收器(*Wrapper
),因为它必须修改该值(类型为Wrapper
) . 无论我们传递给Set()
的是什么号码,我们都可以确定一旦Set()
返回,v
和*p
将是相同的,并且等于传递给Set()
的数字 .现在,如果我们的值为
Wrapper
:我们可以在上面调用
Set()
方法:编译器将自动将
a
的地址用作接收器,因此上面的代码表示(&a).Set(1)
.我们期望
Wrapper
类型的任何值都具有相同的数字存储在Wrapper.v
和*Wrapper.pv
中,如果仅使用Set()
方法来更改字段的值 .现在让我们看看问题,如果我们复制
a
:输出(在Go Playground上试试):
我们制作了
a
的副本(将其存储在b
中)并打印了值 . 到现在为止还挺好 . 然后我们调用a.Set(1)
,之后a
仍然很好,但b
的内部状态变为无效:b.v
不再等于*b.p
. 解释非常明确:当我们复制a
(这是一个struct
类型)时,复制其字段的值(包括指针p
),b
中的指针将指向与指针相同的值 .a
. 因此,修改指向的值将影响Wrapper
的两个副本,但我们有2个不同的v
字段(它们是非指针) .如果您有带指针接收器的方法,则应使用指针值 .
请注意,如果您要复制
*Wrapper
的值,一切都会很酷:输出(在Go Playground上试试):
在这种情况下
a
是一个指针,它的类型为*Wrapper
. 我们制作了一份副本(存储在b
中),名为a.Set()
,内部状态a
和b
都保持有效 . 这里我们只有一个Wrapper
值,a
只保存一个指针(它的地址) . 当我们复制a
时,我们只复制指针值,而不是struct
值(类型Wrapper
) .