首页 文章

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

提问于
浏览
2

我最近阅读了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 回答

  • 2

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

    如果一个类型只有带有值接收器的方法,那意味着无论方法在内部做什么,无论你(或其他任何人)调用什么方法,这些方法都无法改变原始值,因为 - 注意到上面 - 只传递一个副本,该方法只能修改副本 - 而不是原始副本 .

    所以这意味着如果你复制了这个值,你就不必担心,原始版本和副本上调用的方法都不能/不会修改值 .

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

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

    type Wrapper struct {
        v int
        p *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) .

    我们期望 Wrapper 类型的任何值都具有相同的数字存储在 Wrapper.v*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)
    

    输出(在Go Playground上试试):

    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 的两个副本,但我们有2个不同的 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)
    

    输出(在Go Playground上试试):

    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() ,内部状态 ab 都保持有效 . 这里我们只有一个 Wrapper 值, a 只保存一个指针(它的地址) . 当我们复制 a 时,我们只复制指针值,而不是 struct 值(类型 Wrapper ) .

相关问题