和其他纯粹的 OOP 的编程语言不同,Go 对面向对象的特性支持不多,Method(方法)是其中一种,我们可以显式的对一个类型 T 声明方法,只要这个类型符合以下条件
- 必须是一个已经定义的类型
- 必须和方法在同一个包中
- 不能是指针类型
- 不能是接口类型
对于每个声明的方法,编译器都会隐式声明对应的函数,这两个函数其实都是 TypeDenotation.MethodName 的形式,但是我们自己却不能在代码里主动声明,它只能由编译器声明,但是我们自己却能在代码里主动使用这种形式进行调用
type Book struct {
pages int
}
func (b Book) Pages() int {
return b.pages
}
func (b *Book) SetPages(pages int) {
b.pages = pages
}
func Book.Pages(b Book) int {
// Pages
return b.pages
}
func (*Book).SetPages(b *Book, pages int) {
// SetPages
b.pages = pages
}
对于一个方法来说,*T 被称为指针接受者,T 被称为值接受者,虽然有的时候,我们只是声明了值类型接受者的方法,但是使用指针类型依然可以调用,这是为什么呢?这是因为对于每一个接受者是 T 的方法,编译器都会隐式声明一个接受者是*T 的方法
和通用的函数一样,接受者参数都是通过复制来进行传递的,所以在有些情况下,内部的修改不会影响到外部,所以如果需要修改外部的内容,那么就应该使用指针接受者
type Book struct {
pages int
}
type Books []Book
func (b Books) Modify() {
b[0].pages = 500
b = append(b, Book{789})
}
func main() {
var books = Books{{123}, {456}}
books.Modify()
fmt.Println(books) // [{500} {456}]
}
所以什么时候该使用值接受者,什么时候又该使用指针接受者呢?其实任何时候都使用指针接受者在逻辑上是没有问题,但是有的时候考虑其他问题的时候,我们需要使用值接受者
- 值接受者相比指针接受者大部分的时候都会占用更多的内存
- 大量的指针接受者会对垃圾回收造成负担
- sync 包中的内容是不能被复制的,所以涉及到的只能使用指针接受者