EffectiveGoMethod

引用

Effective Go - The Go Programming Language (google.cn)

方法 |《高效的 Go 编程 Effective Go 2020》| Go 技术论坛 (learnku.com)

Pointers vs. Values

正如 ByteSize 那样,我们可以为任何已命名的类型(除了指针或接口)定义方法; 接收者可不必为结构体。

在之前讨论切片时,我们编写了一个 Append 函数。 我们也可将其定义为切片的方法。为此,我们首先要声明一个已命名的类型来绑定该方法, 然后使该方法的接收者成为该类型的值

type ByteSlice []byte

func (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
}
复制代码

示例

type ByteSlice []byte

func (slice ByteSlice) Append(data []byte) []byte {
   // Body exactly the same as the Append function defined above.
   byteSlice := append(slice, data...)
   return byteSlice
}

func (p *ByteSlice) Append2(data []byte) {
   byteSlice := append(*p, data...)
   *p = byteSlice

}

func main() {
   slice := ByteSlice{}
   byteSlice := append(slice, byte(1))
   bytes := byteSlice.Append([]byte{ byte(2)})
   fmt.Println(bytes) // [1 2]
   byteSlice.Append2([]byte{byte(3)})
   fmt.Println(byteSlice) //[ 31]
}
复制代码

事实上我们可以做到更好。如我们修改方法跟write接口一样。

func (p *ByteSlice) Write(data []byte) (n int, err error) {
    slice := *p
    // Again as above.
    *p = slice
    return len(data), nil
}
复制代码
// Writer is the interface that wraps the basic Write method.
//
// Write writes len(p) bytes from p to the underlying data stream.
// It returns the number of bytes written from p (0 <= n <= len(p))
// and any error encountered that caused the write to stop early.
// Write must return a non-nil error if it returns n < len(p).
// Write must not modify the slice data, even temporarily.
//
// Implementations must not retain p.
type Writer interface {
   Write(p []byte) (n int, err error)
}
复制代码

那么类型 *ByteSlice 就满足了标准的 io.Writer 接口,这将非常实用。 例如,我们可以通过打印将内容写入。

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

我们将 ByteSlice 的地址传入,因为只有 *ByteSlice 才满足 io.Writer。以指针或值为接收者的区别在于:值方法可通过指针和值调用, 而指针方法只能通过指针来调用。

之所以会有这条规则是因为指针方法可以修改接收者;通过值调用它们会导致方法接收到该值的副本, 因此任何修改都将被丢弃,因此该语言不允许这种错误。

不过有个方便的例外:若该值是可寻址的, 那么该语言就会自动插入取址操作符来对付一般的通过值调用的指针方法。在我们的例子中,变量 b 是可寻址的,因此我们只需通过 b.Write 来调用它的 Write 方法,编译器会将它重写为 (&b).Write。

顺便一提,在字节切片上使用 Write 的想法已被 bytes.Buffer 所实现。

// Write appends the contents of p to the buffer, growing the buffer as
// needed. The return value n is the length of p; err is always nil. If the
// buffer becomes too large, Write will panic with ErrTooLarge.
func (b *Buffer) Write(p []byte) (n int, err error) {
   b.lastRead = opInvalid
   m, ok := b.tryGrowByReslice(len(p))
   if !ok {
      m = b.grow(len(p))
   }
   return copy(b.buf[m:], p), nil
}
复制代码