Go面试必考题目之method篇

https://gocn.vip/article/1767

  在Go的类方法中,分为值接收者方法和指针接收者方法,对于刚开始接触Go的同学来说,有时对Go的方法会感到困惑。下面我们结合题目来学习Go的方法。

  为了方便叙述,下文描述的值接收者方法简写为值方法,指针接收者方法简写为指针方法。

  下面代码中,哪段编号的代码会报错?具体报什么错误?

  1. type Animal interface {
  2. Bark()
  3. }
  4. type Dog struct {
  5. }
  6. func (d Dog) Bark() {
  7. fmt.Println("dog")
  8. }
  9. type Cat struct {
  10. }
  11. func (c *Cat) Bark() {
  12. fmt.Println("cat")
  13. }
  14. func Bark(a Animal) {
  15. a.Bark()
  16. }
  17. func getDog() Dog {
  18. return Dog{}
  19. }
  20. func getCat() Cat {
  21. return Cat{}
  22. }
  23. func main() {
  24. dp := &Dog{}
  25. d := Dog{}
  26. dp.Bark() // (1)
  27. d.Bark() // (2)
  28. Bark(dp) // (3)
  29. Bark(d) // (4)
  30. cp := &Cat{}
  31. c := Cat{}
  32. cp.Bark() // (5)
  33. c.Bark() // (6)
  34. Bark(cp) // (7)
  35. Bark(c) // (8)
  36. getDog().Bark() // (9)
  37. getCat().Bark() // (10)
  38. }

  抛砖引玉,让我们学习完再来作答。

值方法和指针方法

我们来看看值方法的声明。

  1. type Dog struct {
  2. }
  3. func (d Dog) Bark() {
  4. fmt.Println("dog")
  5. }

上面代码中,方法Bark的接收者是值类型,那么这就是一个值接收者的方法。

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

  1. type Cat struct {
  2. }
  3. func (c *Cat) Bark() {
  4. fmt.Println("cat")
  5. }

类的方法集合

这个在Go文档里有定义:

  • 对于类型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是要能取地址才能这么调用,比如下面这种情况就不行:

  1. func getUser() User {
  2. return User{}
  3. }
  4. ...
  5. getUser().SayWat()
  6. // 编译错误:
  7. // cannot call pointer method on aUser()
  8. // 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
  • 一些小数组,或小结构体并且不需要修改接收者的。

题目解析

  1. type Animal interface {
  2. Bark()
  3. }
  4. type Dog struct {
  5. }
  6. func (d Dog) Bark() {
  7. fmt.Println("dog")
  8. }
  9. type Cat struct {
  10. }
  11. func (c *Cat) Bark() {
  12. fmt.Println("cat")
  13. }
  14. func Bark(a Animal) {
  15. a.Bark()
  16. }
  17. func getDog() Dog {
  18. return Dog{}
  19. }
  20. func getCat() Cat {
  21. return Cat{}
  22. }
  23. func main() {
  24. dp := &Dog{}
  25. d := Dog{}
  26. dp.Bark() // (1) 通过
  27. d.Bark() // (2) 通过
  28. Bark(dp)
  29. // (3) 通过,上面说了类型*Dog的方法集合包含接收者为*Dog和Dog的方法
  30. Bark(d) // (4) 通过
  31. cp := &Cat{}
  32. c := Cat{}
  33. cp.Bark() // (5) 通过
  34. c.Bark() // (6) 通过
  35. Bark(cp) // (7) 通过
  36. Bark(c)
  37. // (8) 编译错误,值类型Cat的方法集合只包含接收者为Cat的方法
  38. // 所以T并没有实现Animal接口
  39. getDog().Bark() // (9) 通过
  40. getCat().Bark()
  41. // (10) 编译错误,
  42. // 上面说了,getCat()是不可地址的
  43. // 所以不能调用接收者为*Cat的方法
  44. }

总结

  • 理清类型的方法集合。
  • 理清接收者方法的调用范围。

参考文献

感谢阅读,欢迎大家指正,留言交流~

4 天前

ft_authoradmin  ft_create_time2019-05-27 12:15
 ft_update_time2019-05-27 12:15