Effective Go: 方法Method

225 阅读2分钟

这是我参与11月更文挑战的第23天,活动详情查看:2021最后一次更文挑战

指针vs值

正如我们在ByteSize中看到的,可以为任何类型定义方法(指针或接口除外); 接收者不必是一个结构体。

在上面对切片的讨论中,我们编写了一个 Append 函数。 我们可以将其定义为切片上的方法。 为此,我们首先声明一个可以绑定方法的命名类型,然后使方法的接收器成为该类型的值。

type ByteSlice []bytefunc (slice ByteSlice) Append(data []byte) []byte {
    // Body exactly the same as the Append function defined above.
}

这仍然需要返回更新切片的方法。 我们可以通过重新定义方法以将指向 ByteSlice 的指针作为其接收者来消除这种笨拙的情况,因此该方法可以覆盖调用者的切片。

func (p *ByteSlice) Append(data []byte) {
    slice := *p
    // Body as above, without the return.
    *p = slice
}

事实上,我们还可以做得更好。 如果我们修改我们的函数,让它看起来像一个标准的 Write 方法,像这样,

func (p *ByteSlice) Write(data []byte) (n int, err error) {
    slice := *p
    // Again as above.
    *p = slice
    return len(data), nil
}

这样的话 *ByteSlice 类型就实现了标准接口 io.Writer,这将会很方便。 例如,我们可以打印成下面这样。

var b ByteSlice
fmt.Fprintf(&b, "This hour has %d days\n", 7)

我们传递一个 ByteSlice 的地址,因为只有 *ByteSlice 满足 io.Writer。 关于接收者是指针还是值的区别或者选择,有一点区别是值方法可以在指针和值上调用,但指针方法只能在指针上调用。

出现这个规则是因为指针方法可以修改接收者;而在一个值上调用它们将导致该方法接收该值的副本,因此任何修改其实修改的都是副本,并没有修改到原来的值。 因此,Go语言不允许这种错误。 但是,有一个例外。 当值可寻址时,该语言会通过自动插入地址运算符来处理对值调用指针方法。(ps:其实就是可以通过值来调用接受者为指针的方法) 在我们的例子中,变量 b 是可寻址的,所以我们可以只用 b.Write 调用它的 Write 方法。 编译器会将其重写为 (&b).Write 。

顺便说一下,在字节切片上实现 Write方法 是实现 bytes.Buffer 的核心。