在Go的類方法中,分為值接收者方法和指針接收者方法,對于剛開始接觸Go的同學來說,有時對Go的方法會感到困惑。下面我們結(jié)合題目來學習Go的方法。
  為了方便敘述,下文描述的值接收者方法簡寫為值方法,指針接收者方法簡寫為指針方法。
  下面代碼中,哪段編號的代碼會報錯?具體報什么錯誤?
```Go
type Animal interface {
? ? Bark()
}
type Dog struct {
}
func (d Dog) Bark() {
? ? fmt.Println("dog")
}
type Cat struct {
}
func (c *Cat) Bark() {
? ? fmt.Println("cat")
}
func Bark(a Animal) {
? ? a.Bark()
}
func getDog() Dog {
? ? return Dog{}
}
func getCat() Cat {
? ? return Cat{}
}
func main() {
? ? dp := &Dog{}
? ? d := Dog{}
? ? dp.Bark() // (1)
? ? d.Bark()? // (2)
? ? Bark(dp)? // (3)
? ? Bark(d)? // (4)
? ? cp := &Cat{}
? ? c := Cat{}
? ? cp.Bark() // (5)
? ? c.Bark()? // (6)
? ? Bark(cp)? // (7)
? ? Bark(c)? // (8)
? ? getDog().Bark() // (9)
? ? getCat().Bark() // (10)
}
```
  拋磚引玉,讓我們學習完再來作答。
### 值方法和指針方法
我們來看看值方法的聲明。
```Go
type Dog struct {
}
func (d Dog) Bark() {
? ? fmt.Println("dog")
}
```
上面代碼中,方法`Bark`的接收者是值類型,那么這就是一個值接收者的方法。
下面再看看指針接收者的方法。
```Go
type Cat struct {
}
func (c *Cat) Bark() {
? ? fmt.Println("cat")
}
```
### 類的方法集合
這個在Go文檔里有定義:
- 對于類型`T`,它的方法集合是所有接收者為`T`的方法。
- 對于類型`*T`,它的方法集合是所有接收者為`*T`和`T`的方法。
Values | Method Sets
----|----
T|(t T)
*T|(t T) and (t *T)
### 方法的調(diào)用者
  **指針`*T`接收者方法**:只有指針類型`*T`才能調(diào)用,但其實值`T`類型也能調(diào)用,為什么呢?因為當使用值調(diào)用`t.Call()`時,Go會轉(zhuǎn)換成`(&t).Call()`,也就是說最后調(diào)用的還是接收者為指針`*T`的方法。
  但要注意t是要能取地址才能這么調(diào)用,比如下面這種情況就不行:
```Go
func getUser() User {
? ? return User{}
}
...
getUser().SayWat()
// 編譯錯誤:
// cannot call pointer method on aUser()
// cannot take the address of aUser()
```
  **值`T`接收者方法:** 指針類型`*T`和值`T`類型都能調(diào)用。
Methods Receivers | Values
----|----
(t T) | T and *T
(t *T) | *T
  使用接收者為`*T`的方法實現(xiàn)一個接口,那么只有那個類型的指針`*T`實現(xiàn)了對應(yīng)的接口。
  如果使用接收者為`T`的方法實現(xiàn)一個接口,那么這個類型的值`T`和指針`*T`都實現(xiàn)了對應(yīng)的接口。
### 聲明建議
  在給類聲明方法時,方法接收者的類型要統(tǒng)一,最好不要同時聲明接收者為值和指針的方法,這樣容易混淆而不清楚到底實現(xiàn)了哪些接口。
  下面我們來看看哪種類型適合聲明接收者為值或指針的方法。
#### 指針接收者方法
下面這2種情況請務(wù)必聲明指針接收者方法:
- 方法中需要對接收者進行修改的。
- 類中包含`sync.Mutex`或類似鎖的變量,因為它們不允許值拷貝。
下面這2種情況也建議聲明指針接收者方法:
- 類成員很多的,或者大數(shù)組,使用指針接收者效率更高。
- 如果拿不準,那也聲明接收者為指針的方法吧。
#### 值接收者方法
下面這些情況建議使用值接收者方法:
- 類型為`map`,`func`,`channel`。
- 一些基本的類型,如`int`,`string`。
- 一些小數(shù)組,或小結(jié)構(gòu)體并且不需要修改接收者的。
### 題目解析
```Go
type Animal interface {
? ? Bark()
}
type Dog struct {
}
func (d Dog) Bark() {
? ? fmt.Println("dog")
}
type Cat struct {
}
func (c *Cat) Bark() {
? ? fmt.Println("cat")
}
func Bark(a Animal) {
? ? a.Bark()
}
func getDog() Dog {
? ? return Dog{}
}
func getCat() Cat {
? ? return Cat{}
}
func main() {
? ? dp := &Dog{}
? ? d := Dog{}
? ? dp.Bark() // (1) 通過
? ? d.Bark()? // (2) 通過
? ? Bark(dp)
? ? // (3) 通過,上面說了類型*Dog的方法集合包含接收者為*Dog和Dog的方法
? ? Bark(d)? // (4) 通過
? ? cp := &Cat{}
? ? c := Cat{}
? ? cp.Bark() // (5) 通過
? ? c.Bark()? // (6) 通過
? ? Bark(cp)? // (7) 通過
? ? Bark(c)
? ? // (8) 編譯錯誤,值類型Cat的方法集合只包含接收者為Cat的方法
? ? // 所以T并沒有實現(xiàn)Animal接口
? ? getDog().Bark() // (9) 通過
? ? getCat().Bark()
? ? // (10) 編譯錯誤,
? ? // 上面說了,getCat()是不可地址的
? ? // 所以不能調(diào)用接收者為*Cat的方法
}
```
### 總結(jié)
- 理清類型的方法集合。
- 理清接收者方法的調(diào)用范圍。
### 參考文獻
- 《Pointer vs Value receiver》
https://yourbasic.org/golang/pointer-vs-value-receiver/
- 《Method sets》
https://golang.org/ref/spec#Method_sets
- https://stackoverflow.com/questions/19433050/go-methods-sets-calling-method-for-pointer-type-t-with-receiver-t?rq=1
#### 感謝閱讀,歡迎大家指正,留言交流~
<img src="https://user-gold-cdn.xitu.io/2019/5/22/16adfd582258b45b?w=1005&h=1164&f=jpeg&s=217262" width=300 height=330 align=center/>