方法
方法是与对象实例绑定的特殊函数。方法是面向对象。方法和函数区别在于前者有前置实例接收参数,编译器以此确定方法所属类型。
可以为当前包,以及除接口和指针以外的任何类型定义方法。
package main
import(
"fmt"
)
type N int
func (n N)toString()string{ //方法
return fmt.Sprintf("%#x",n)
}
func main(){
var a N = 25
fmt.Println(a.toString())
}
输出:
0x19
方法不支持重载,参数名没有限制。如方法内部并不引用实例,可省略参数名,仅保留类型。
type N int
fun (N)test(){}
方法可以看做特殊的函数,那么前置接受参数的类型自然可以为基础类型或指针类型。可使用实例值或指针调用方法编译器会根据方法receiver类型自动在基础类型和指针类型间转换,但不能使用多级指针的调用方法。
如何选择合适的前置参数类型呢?
- 要修改实例状态,用*T
- 无需修改状态的小对象或固定值建议用T
- 大对象建议用*T,以减少复制成本
- 引用类型、字符串、函数等指针包装对象直接用T
- 若包含Mutex等同步字段,用*T避免因复制造成锁操作无效
- 其他无法确定情况都用*T
匿名字段
可以像访问匿名字段成员那样调用其方法,由编译器负责查找。方法也会有同名覆盖的问题,我们可以利用这种特性实习类似覆盖的操作。
package main
import(
"fmt"
)
type user struct{}
type manager struct{
user
}
func (user)toString()string{
return "user"
}
func (m manager)toString()string{
return m.user.toString() +";manager"
}
func main(){
var m manager
fmt.Println(m.toString())
fmt.Println(m.user.toString())
}
输出:
user;manager
user
方法集
类型有一个与之相关的方法集,这决定了它是否实现某个接口
- 类型T方法集包含所有receiver T方法
- 类型*T方法集包含所有receiver T + *T方法
- 匿名嵌入S,T方法集包含所有receiver S方法
- 匿名嵌入*S,T方法集包含所有receiver S + *S方法
- 匿名嵌入S或*S,*T方法集包含所有receiver S + *S方法
方法集仅影响接口实现和方法表达式转换,与通过实例或实例指针调用方法无关,实例并不使用方法集,而是直接调用。
面向对象的三大特征“封装”、“继承”、“多态”,Go仅实现部分特性。Go语言中实现继承的方式叫做组合,在上例中有所体现。组合没有父子依赖,不会破坏封装。