方法
方法就是一种特殊的函数
方法总是绑定对象实例,并隐式将实例作为第一实参 (receiver)。(简单说就是能像Java那样:"对象.函数",并且对象会隐式的作为该函数的一个参数)
方法有以下特性:
-
只能为当前包内命名类型定义方法。
-
参数
receiver
可任意命名。如方法中未曾使用,可省略参数名。 -
参数
receiver
类型可以是T
或*T
。基类型T
不能是接口或指针(这将决定作为隐式参数的对象是副本还是本体)。 -
不支持方法重载,
receiver
只是参数签名的组成部分。 -
可用实例
value
或pointer
调用全部方法,编译器自动转换。(无论参数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
方法。
用实例 value
和 pointer
调用方法 (含匿名字段) 不受方法集约束,编译器总是查找全部方法,并自动转换 receiver
实参。
参考
Q.E.D.