Go从入门到放弃13--方法

287 阅读3分钟

Go语言虽然不支持经典的面向对象语法元素,比如类、对象、继承等,但Go语言也有方法。和函数相比,Go语言中的方法在声明形式上仅仅多了一个参数,Go称之为receiver参数 。receiver参数是方法与类型之间的纽带。Go 提供方法这种语法,并非出自对经典面向对象编程范式支持的考虑,而是出自 Go 的组合设计哲学下类型系统实现层面上的需要。

方法定义

func (t *T或T) MethodName(参数列表) (返回值列表) {
    // 方法体
}

无论 receiver 参数的类型为 *T 还是 T,我们都把一般声明形式中的 T 叫做 receiver 参数 t 的基类型。如果 t 的类型为 T,那么说这个方法是类型 T 的一个方法;如果 t 的类型为 *T,那么就说这个方法是类型 *T 的一个方法。而且,要注意的是,每个方法只能有一个 receiver 参数,Go 不支持在方法的 receiver 部分放置包含多个 receiver 参数的参数列表,或者变长 receiver 参数。

receiver 部分的参数名不能与方法参数列表中的形参名,以及具名返回值中的变量名存在冲突,必须在这个方法的作用域中具有唯一性。如果这个不唯一不存在,Go 编译器就会报错

type T struct{}
func (t T) M(t string) { // 编译器报错:duplicate argument t (重复声明参数t)
    ... ...
}

除了 receiver 参数名字要保证唯一外,Go 语言对 receiver 参数的基类型也有约束,那就是 receiver 参数的基类型本身不能为指针类型或接口类型。

type MyInt *int
func (r MyInt) String() string { // r的基类型为MyInt,编译器报错:invalid receiver type MyInt (MyInt is a pointer type)
    return fmt.Sprintf("%d", *(*int)(r))
}

type MyReader io.Reader
func (r MyReader) Read(p []byte) (int, error) { // r的基类型为MyReader,编译器报错:invalid receiver type MyReader (MyReader is an interface type)
    return r.Read(p)
}

Go 对方法声明的位置也是有约束的,Go 要求,方法声明要与 receiver 参数的基类型声明放在同一个包内。

方法的本质

C++的对象在调用方法时,编译器会自动传入指向对象自身的this指针作为方法的第一个参数。而对于Go来说,receiver其实也是同样道理,我们将receiver作为第一个参数传入方法的参数列表。


type T struct { 
    a int
}

func (t T) Get() int {  
    return t.a 
}

func (t *T) Set(a int) int { 
    t.a = a 
    return t.a 
}

// 类型T的方法Get的等价函数
func Get(t T) int {  
    return t.a 
}

// 类型*T的方法Set的等价函数
func Set(t *T, a int) int { 
    t.a = a 
    return t.a 
}

Go 方法本质上其实是一个函数,这个函数以方法的 receiver 参数作为第一个参数,Go 编译器会在我们进行方法调用时协助进行这样的转换。

  • 不管你的method的receiver是指针类型还是非指针类型,都是可以通过指针/非指针类型进行调用的,编译器会帮你做类型转换。
  • 在选择receiver参数类型时要看是否要对类型实例进行修改。如有修改需求,则选择*T;如无修改需求,T类型receiver传值的性能损耗也是考量因素之一。
  • 如果 T 类型需要实现某个接口,那我们就要使用 T 作为 receiver 参数的类型,来满足接口类型方法集合中的所有方法。

参考资料