Go面試必考題目之method篇

  在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/>

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容