调用方法时,首先复制调用该方法的值,然后将该副本传递/用作接收者。
如果一个类型只有带有值接收器的方法,这意味着无论这些方法在内部做什么,也无论您(或任何其他人)调用什么方法,这些方法都无法更改original值,因为如上所述,仅传递副本,并且该方法只能修改副本,而不能修改原始副本。
因此,这意味着如果您复制该值,则不必担心,在原始值或副本上调用的方法都不能/不会修改该值。
当类型具有带有指针接收器的方法时则不然。如果一个方法有一个指针接收者,该方法可以改变/修改pointed值,它不是副本,它是原始值(仅pointer是一个副本,但它指向原始值)。
让我们看一个例子。我们创建一个int
包装类型,有 2 个字段:int
and an *int
。我们打算在两个字段中存储相同的数字,但其中一个是指针(并且我们存储int
in the pointed value):
type Wrapper struct {
v int
p *int
}
为了确保两个值(v
and *p
)是一样的,我们提供一个Set()
方法,同时设置:
func (w *Wrapper) Set(v int) {
w.v = v
*w.p = v
}
Wrapper.Set()
有一个指针接收器(*Wrapper
)因为它必须修改值(其类型为Wrapper
)。无论我们转给什么号码Set()
,我们可以确定一旦Set()
返回,两者v
and *p
将是相同的,并且等于传递给的数字Set()
.
现在如果我们的值为Wrapper
:
a := Wrapper{v: 0, p: new(int)}
我们可以调用Set()
其方法:
a.Set(1)
编译器会自动获取地址a
用作接收器,所以上面的代码意味着(&a).Set(1)
.
我们期望任何类型的值Wrapper
具有相同的号码存储在Wrapper.v
and *Wrapper.pv
,如果只有Set()
方法用于更改字段的值。
现在让我们看看如果我们复制以下问题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.v=0, a.p=0; b.v=0, b.p=0
a.v=1, a.p=1; b.v=0, b.p=1
我们复制了一份a
(将其存储在b
),并打印这些值。到目前为止,一切都很好。然后我们打电话a.Set(1)
, 之后a
还是不错的,但是内部状态b
变得无效:b.v
不等于*b.p
不再了。解释很清楚:当我们复制a
(这是一个struct
类型),复制其字段的值(包括指针p
),以及指针b
将指向与指针相同的值a
。因此修改指向的值将影响两个副本Wrapper
,但我们有两个不同的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.v=0, a.p=0; b.v=0, b.p=0
a.v=1, a.p=1; b.v=1, b.p=1
在这种情况下a
是一个指针,它的类型*Wrapper
。我们制作了它的副本(将其存储在b
),称为a.Set()
,以及内部状态a
and b
仍然有效。这里我们只有一个Wrapper
value, a
只保存一个指向它的指针(它的地址)。当我们复制时a
,我们只复制指针值,而不复制struct
值(类型Wrapper
).