Go-方法集学习笔记

101 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第20天,点击查看活动详情

方法集

Golang中每一个类型所对应的方法,我们把它称为方法集,按方法接收者来划分可以分为:实体类型和指针类型。如下例子:

type Student struct {
        Age int
        Name string
}
// 实体类型方法接收者
func (s Student) showName() {
        fmt.Println("name="+s.Name)
}
​
// 指针类型方法接收者
func (s *Student) setName(name string) {
        s.Name = name
}
  • 实体类型Student接收者方法集包括:showName()
  • 指针类型Student接收者方法集包括:showName(),setName()

Go中规定了如下规则

  • 类型T的方法集包含receiver T的方法
  • 类型*T的方法集包含receiver T和receiver *T的方法

但需要注意的是实体类型也可以调用指针类型接收者的方法的,如下例子:

s := Student{}
s.setName("Tom") // (&s).setName("Tom")

这是因为编译器会进行自动类型转换,所以无论是实体类型还是指针类型都可以随意调用自身的任何方法。因此我们类型调用自身方法时无需在意方法集的概念,方法集需要和接口一起来使用。

方法集和接口

接口代表一个或者多个方法签名的集合,因此只要任何类型具有该接口所有方法签名的话,就可以说它实现了该接口,无需类似Java/C++使用显示implement等关键字去声明。

一个非空方法接口(iface)是由接口表和数据指针所构成的。那么使用实体类型或者指针类型去实现接口有什么区别吗?

  • 如果是实体类型去实现接口所定义的方法集,那么T和*T都可以说实现了该接口
  • 如果是指针类型去实现接口所定义的方法集,那么只有*T才实现了该接口

如下例子:

type IPeople interface {
    setName(name string)
}
​
type People struct {
    Age  int
    Name string
}
​
func (p People) showName() {
    fmt.Println("name=" + p.Name)
}
​
func (p *People) setName(name string) {
    p.Name = name
}
​
func main() {
    people := People{}
    var i IPeople = people // 编译失败:People does not implement IPeople (setName method has pointer receiver)
    
}
​
​

可以看到编译时报People实体类型没有实现IPeople接口,可以修改为var i IPeople = &people或者将setName方法接收者修改为实体类型。

方法集和组合

总所周知,Go中并没有类似Java/C++等传统OOP的extends关键字,这是因为Go提倡使用组合来实现继承的行为,如下面的例子。

type People struct {
    Age  int
    Name string
}
​
type Student struct {
    People
}

那么实体类型组合和指针类型组合又有什么区别呢?

type Student1 struct {
    People
}
​
type Student2 struct {
    *People
}

首先是默认值不同

  • struct是复合类型,因此默认值是所包含类型的零值
  • 指针是引用类型,零值为nil

再其次就是方法集的不同

  • 类型S包含匿名T类型,则S类型和*S类型都包含T的方法集
  • 类型S包含匿名* T类型, 则S类型和*S类型都包含T和 *T的方法集
  • 无论类型S包含匿名*T类型还是T类型, *S都包含T和 *T的方法集