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

ara*_*stu 4 methods pointers go

最近读了《Go编程语言书》,这是学习golang编程语言的好资源。\n6.2节中有一段关于类型的复制实例T在方法中是否是指针接收者时类型的复制实例,我无法理解。 \n有没有用一个有意义的例子来解释这一段?

\n\n
\n

6.2 使用指针接收器的方法

\n\n

如果命名类型 T 的所有方法都有 T 本身的接收者类型(不是 *T ),则复制该类型的实例是安全的;调用它的任何方法都必然会生成一个副本。例如,time.Duration 值可以自由复制,包括作为函数的参数。但是,如果任何方法具有指针接收器,则应避免复制 T 的实例,因为这样做可能会违反内部不变量。例如,复制 bytes.Buffer 的实例将导致原始和副本为相同的底层字节数组别名( \xc2\xa72.3.2 )。后续的方法调用将产生不可预测的效果。

\n\n

(Go 编程语言 Alan AA Donovan \xc2\xb7 Brian W. Kernighan)

\n
\n

icz*_*cza 5

调用方法时,首先复制调用该方法的值,然后将该副本传递/用作接收者。

\n\n

如果一个类型只有带有值接收器的方法,这意味着无论这些方法在内部做什么,也无论你(或其他任何人)调用什么方法,这些方法都无法更改原始值因为 \xe2 \x80\x93 如上所述,\xe2\x80\x93 只传递了一个副本,并且该方法只能修改副本 \xe2\x80\x93 而不是原始的。

\n\n

因此,这意味着如果您复制该值,则不必担心,在原始值或副本上调用的方法都不能/不会修改该值。

\n\n

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

\n\n

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

\n\n
type Wrapper struct {\n    v int\n    p *int\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

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

\n\n
func (w *Wrapper) Set(v int) {\n    w.v = v\n    *w.p = v\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

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

\n\n

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

\n\n
a := Wrapper{v: 0, p: new(int)}\n
Run Code Online (Sandbox Code Playgroud)\n\n

我们可以调用Set()它的方法:

\n\n
a.Set(1)\n
Run Code Online (Sandbox Code Playgroud)\n\n

编译器会自动将 的地址用作a接收者,所以上面的代码意味着(&a).Set(1)

\n\n

我们期望类型的任何值都具有存储在和Wrapper中的相同数字,只要Wrapper.v*Wrapper.pvSet()使用该方法来更改字段的值

\n\n

现在让我们看看如果我们复制以下问题a

\n\n
a := Wrapper{v: 0, p: new(int)}\nb := a\nfmt.Printf("a.v=%d, a.p=%d;  b.v=%d, b.p=%d\\n", a.v, *a.p, b.v, *b.p)\n\na.Set(1)\nfmt.Printf("a.v=%d, a.p=%d;  b.v=%d, b.p=%d\\n", a.v, *a.p, b.v, *b.p)\n
Run Code Online (Sandbox Code Playgroud)\n\n

输出(在Go Playground上尝试一下):

\n\n
a.v=0, a.p=0;  b.v=0, b.p=0\na.v=1, a.p=1;  b.v=0, b.p=1\n
Run Code Online (Sandbox Code Playgroud)\n\n

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

\n\n

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

\n\n

请注意,如果您要复制值*Wrapper,一切仍然很酷:

\n\n
a := &Wrapper{v: 0, p: new(int)}\nb := a\nfmt.Printf("a.v=%d, a.p=%d;  b.v=%d, b.p=%d\\n", a.v, *a.p, b.v, *b.p)\n\na.Set(1)\nfmt.Printf("a.v=%d, a.p=%d;  b.v=%d, b.p=%d\\n", a.v, *a.p, b.v, *b.p)\n
Run Code Online (Sandbox Code Playgroud)\n\n

输出(在Go Playground上尝试一下):

\n\n
a.v=0, a.p=0;  b.v=0, b.p=0\na.v=1, a.p=1;  b.v=1, b.p=1\n
Run Code Online (Sandbox Code Playgroud)\n\n

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

\n