方法

方法就是一种特殊的函数

方法总是绑定对象实例,并隐式将实例作为第一实参 (receiver)。(简单说就是能像Java那样:"对象.函数",并且对象会隐式的作为该函数的一个参数)

方法有以下特性:

  • 只能为当前包内命名类型定义方法。

  • 参数 receiver 可任意命名。如方法中未曾使用,可省略参数名。

  • 参数 receiver 类型可以是 T*T。基类型 T 不能是接口或指针(这将决定作为隐式参数的对象是副本还是本体)。

  • 不支持方法重载,receiver 只是参数签名的组成部分。

  • 可用实例 valuepointer 调用全部方法,编译器自动转换。(无论参数 receiver 类型是 T*T,实例均可调用)

由于没有构造和析构方法,通常用简单工厂模式返回对象实例
举个例子

type Cat struct {}

//创建实例
func NewCat() *Cat{
    return &Cat{}
}

//隐式参数为副本
func (this Cat) CopyMeow(){// receiver 参数名可以是 self、this 或其他。
    fmt.Println(this)
}

//隐式参数为本体
func (self *Cat) RealMeow(){// receiver 参数名可以是 self、this 或其他。
    fmt.Println(self)
}

//方法/函数名为唯一标识符,不能重名(重载)
//func (self *Cat) RealMeow(){
//    fmt.Println(c)
//}

从 1.4 开始,不再支持多级指针查找方法成员

package main

type X struct{}

func (*X) test() {
	println("X.test")
}

func main() {
	p := &X{}
	p.test()
    (*p).test()

	// Error: calling method with receiver &p (type **X) requires explicit dereference
    //(&p).test() //此处为二级指针(**main.X)
}

匿名字段

可以像字段成员那样访问匿名字段的方法,由编译器负责查找

package main

import "fmt"

type User struct {
	id   int
	name string
}

type Manager struct {
    User // 此处的User必须匿名才能通过Manager调用User的ToString()
    //有其他字段(与匿名字段不同类型)不会有影响查找
}

func (self *User) ToString() string { // receiver = &(Manager.User)
	return fmt.Sprintf("User: %p, %v, %T", self, self,self)
}

func main() {
	m := Manager{User{1, "Tom"}} //这里的m是Manager

	fmt.Printf("Manager: %p\n", &m)
	fmt.Println(m.ToString())//但是此处的m却可以调用User的方法,因为Manager里有一个User类型的匿名变量
}

通过匿名字段,可获得和继承类似的复用能力。依据编译器查找次序,只需在外层定义同名方法,就可以实现 "override"。 顺带一提:重载与重写

package main

import "fmt"

type User struct {
	id   int
	name string
}

type Manager struct {
	User
	title string
}

func (self *User) ToString() string {
	return fmt.Sprintf("User: %p, %v", self, self)
}

func (self *Manager) ToString() string {
	return fmt.Sprintf("Manager: %p, %v", self, self)
}

func main() {
	m := Manager{User{1, "Tom"}, "Administrator"}

	fmt.Println(m.ToString())//调用Manager的ToString
	fmt.Println(m.User.ToString())//调用User的ToString
}

方法集

每个类型都有与之关联的方法集,这会影响到接口实现规则。

  • 类型 T 方法集包含全部 receiver T 方法。
  • 类型 *T 方法集包含全部 receiver T + *T 方法。
  • 如类型 S 包含匿名字段 T,则 S 方法集包含 T 方法。
  • 如类型 S 包含匿名字段 *T,则 S 方法集包含 T + *T 方法。
  • 不管嵌入 T*T*S 方法集总是包含 T + *T 方法。

用实例 valuepointer 调用方法 (含匿名字段) 不受方法集约束,编译器总是查找全部方法,并自动转换 receiver 实参。

参考

go 边看边练

Q.E.D.


此 生 无 悔 恋 真 白 ,来 世 愿 入 樱 花 庄 。