Go面試必考題目之method篇

  在Go的類方法中,分爲值接收者方法和指針接收者方法,對於剛開始接觸Go的同窗來講,有時對Go的方法會感到困惑。下面咱們結合題目來學習Go的方法。golang

  爲了方便敘述,下文描述的值接收者方法簡寫爲值方法,指針接收者方法簡寫爲指針方法。c#

  下面代碼中,哪段編號的代碼會報錯?具體報什麼錯誤?數組

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)
}
複製代碼

  拋磚引玉,讓咱們學習完再來做答。學習

值方法和指針方法

咱們來看看值方法的聲明。spa

type Dog struct {
}

func (d Dog) Bark() {
    fmt.Println("dog")
}
複製代碼

上面代碼中,方法Bark的接收者是值類型,那麼這就是一個值接收者的方法。指針

下面再看看指針接收者的方法。code

type Cat struct {
}

func (c *Cat) Bark() {
    fmt.Println("cat")
}
複製代碼

類的方法集合

這個在Go文檔裏有定義:cdn

  • 對於類型T,它的方法集合是全部接收者爲T的方法。
  • 對於類型*T,它的方法集合是全部接收者爲*TT的方法。
Values Method Sets
T (t T)
*T (t T) and (t *T)

方法的調用者

  指針*T接收者方法:只有指針類型*T才能調用,但其實值T類型也能調用,爲何呢?由於當使用值調用t.Call()時,Go會轉換成(&t).Call(),也就是說最後調用的仍是接收者爲指針*T的方法。接口

  但要注意t是要能取地址才能這麼調用,好比下面這種狀況就不行:文檔

func getUser() User {
    return User{}
}

...

getUser().SayWat()
// 編譯錯誤:
// cannot call pointer method on aUser()
// cannot take the address of aUser()
複製代碼

  T接收者方法: 指針類型*T和值T類型都能調用。

Methods Receivers Values
(t T) T and *T
(t *T) *T

  使用接收者爲*T的方法實現一個接口,那麼只有那個類型的指針*T實現了對應的接口。

  若是使用接收者爲T的方法實現一個接口,那麼這個類型的值T和指針*T都實現了對應的接口。

聲明建議

  在給類聲明方法時,方法接收者的類型要統一,最好不要同時聲明接收者爲值和指針的方法,這樣容易混淆而不清楚到底實現了哪些接口。

  下面咱們來看看哪一種類型適合聲明接收者爲值或指針的方法。

指針接收者方法

下面這2種狀況請務必聲明指針接收者方法:

  • 方法中須要對接收者進行修改的。
  • 類中包含sync.Mutex或相似鎖的變量,由於它們不容許值拷貝。

下面這2種狀況也建議聲明指針接收者方法:

  • 類成員不少的,或者大數組,使用指針接收者效率更高。
  • 若是拿不許,那也聲明接收者爲指針的方法吧。

值接收者方法

下面這些狀況建議使用值接收者方法:

  • 類型爲mapfuncchannel
  • 一些基本的類型,如intstring
  • 一些小數組,或小結構體而且不須要修改接收者的。

題目解析

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並無實現Animal接口
    
    getDog().Bark() // (9) 經過
    getCat().Bark()
    // (10) 編譯錯誤,
    // 上面說了,getCat()是不可地址的
    // 因此不能調用接收者爲*Cat的方法
}
複製代碼

總結

  • 理清類型的方法集合。
  • 理清接收者方法的調用範圍。

參考文獻

感謝閱讀,歡迎你們指正,留言交流~

相關文章
相關標籤/搜索