Pointers 当命名类型T的任何方法具有指针接收器时,复制类型T的实例

Pointers 当命名类型T的任何方法具有指针接收器时,复制类型T的实例,pointers,go,methods,Pointers,Go,Methods,我最近读了《学习golang编程语言的好资源》。 6.2节中有一段关于类型为T的复制实例,当它是指针接收器或不在方法中时,我无法理解它。 有没有人能用一个有意义的例子来解释这一段 6.2带指针接收器的方法 如果命名类型T的所有方法都有一个接收方类型T本身(不是*T),那么复制该类型的实例是安全的;调用它的任何方法都必须创建一个副本。例如,time.Duration值被大量复制,包括作为函数的参数。但是,如果任何方法都有指针接收器,那么应该避免复制T的实例,因为这样做可能会违反内部不变量。例如,复

我最近读了《学习golang编程语言的好资源》。 6.2节中有一段关于类型为
T
的复制实例,当它是指针接收器或不在方法中时,我无法理解它。 有没有人能用一个有意义的例子来解释这一段

6.2带指针接收器的方法

如果命名类型T的所有方法都有一个接收方类型T本身(不是*T),那么复制该类型的实例是安全的;调用它的任何方法都必须创建一个副本。例如,time.Duration值被大量复制,包括作为函数的参数。但是,如果任何方法都有指针接收器,那么应该避免复制T的实例,因为这样做可能会违反内部不变量。例如,复制bytes.Buffer的实例将导致原始和副本别名(§2.3.2)为相同的基本字节数组。随后的方法调用将产生不可预测的影响

(围棋编程语言Alan A.A.Donovan·Brian W.Kernighan)


调用方法时,首先复制调用该方法的值,并将该副本作为接收方传递/使用

如果一个类型只有带值接收者的方法,这意味着无论这些方法在内部执行什么操作,也不管您(或任何其他人)调用什么方法,这些方法都无法更改原始值,因为如上所述,只传递一个副本,而该方法只能修改副本,而不能修改原始副本

因此,这意味着如果复制该值,则无需担心,无论是对原始值还是对副本调用的方法都不能/不会修改该值

如果类型具有带指针接收器的方法,则不会。如果方法具有指针接收器,则该方法可以更改/修改指向的值,该值不是副本,而是原始值(只有指针是副本,但它指向原始值)

让我们看一个例子。我们创建一个
int
包装类型,它有两个字段:一个
int
和一个
*int
。我们打算在两个字段中存储相同的数字,但其中一个是指针(我们将
int
存储在指向的值中):

为了确保两个值(
v
*p
)相同,我们提供了一个
Set()
方法,该方法同时设置:

func (w *Wrapper) Set(v int) {
    w.v = v
    *w.p = v
}
Wrapper.Set()
有一个指针接收器(
*Wrapper
),因为它必须修改值(类型为
Wrapper
)。无论传递给
Set()
的是什么数字,我们都可以确定,一旦
Set()
返回,
v
*p
都将是相同的,并且等于传递给
Set()
的数字

现在,如果我们有一个值
Wrapper

a := Wrapper{v: 0, p: new(int)}
我们可以对其调用
Set()
方法:

a.Set(1)
编译器将自动将
a
的地址用作接收器,因此上述代码表示
(&a)。Set(1)

如果只使用
Set()
方法更改字段的值,则我们希望
Wrapper
类型的任何值都具有存储在
Wrapper.v
*Wrapper.pv
中的相同数字

现在,让我们看看如果复制
a

a := Wrapper{v: 0, p: new(int)}
b := a
fmt.Printf("a.v=%d, a.p=%d;  b.v=%d, b.p=%d\n", a.v, *a.p, b.v, *b.p)

a.Set(1)
fmt.Printf("a.v=%d, a.p=%d;  b.v=%d, b.p=%d\n", a.v, *a.p, b.v, *b.p)
输出(在上尝试):

我们制作了一份
a
(存储在
b
中),并打印了值。到现在为止,一直都还不错。然后我们调用
a.Set(1)
,之后
a
仍然良好,但是
b
的内部状态变得无效:
b.v
不再等于
*b.p
。解释很清楚:当我们复制
a
(这是
struct
类型)时,它复制其字段的值(包括指针
p
),而
b
中的指针将指向与
a
中的指针相同的值。因此,修改指向的值将影响
包装的两个副本,但我们有两个不同的
v
字段(它们是非指针)

如果有带指针接收器的方法,则应使用指针值

请注意,如果您要复制
*Wrapper
的值,那么一切都会很酷:

a := &Wrapper{v: 0, p: new(int)}
b := a
fmt.Printf("a.v=%d, a.p=%d;  b.v=%d, b.p=%d\n", a.v, *a.p, b.v, *b.p)

a.Set(1)
fmt.Printf("a.v=%d, a.p=%d;  b.v=%d, b.p=%d\n", a.v, *a.p, b.v, *b.p)
输出(在上尝试):


在本例中,
a
是一个指针,它的类型是
*Wrapper
。我们制作了一个名为
a.Set()
的副本(存储在
b
中),
a
b
的内部状态仍然有效。这里我们只有一个
包装器
值,
a
只保存指向它的指针(它的地址)。当我们复制
a
时,我们只复制指针值,而不是
struct
值(类型为
Wrapper
)。

基本上就是说,如果您的类型根本没有指针接收器方法(因此没有类似
func(t*t)
)您可以假设它可以被复制,因为任何方法都不会修改类型中的数据。
a.v=0, a.p=0;  b.v=0, b.p=0
a.v=1, a.p=1;  b.v=0, b.p=1
a := &Wrapper{v: 0, p: new(int)}
b := a
fmt.Printf("a.v=%d, a.p=%d;  b.v=%d, b.p=%d\n", a.v, *a.p, b.v, *b.p)

a.Set(1)
fmt.Printf("a.v=%d, a.p=%d;  b.v=%d, b.p=%d\n", a.v, *a.p, b.v, *b.p)
a.v=0, a.p=0;  b.v=0, b.p=0
a.v=1, a.p=1;  b.v=1, b.p=1