1. 简介
在面向对象编程中,一个对象其实就是一个简单的值或者一个变量,在这个对象中会包含一些函数,这种带有接收者的函数,称为方法。本质上,方法是和一种类型关联的函数。(类似于C++的成员函数,只有这种类型的对象才可以调用)
2. 定义方式
在Go语言中,方法总是绑定对象实例,并隐式地将实例作为第一实参,方法的定义语法如下:
func (v Type) MethodName() {
}
3. 实际例子
结构体作为接收者
首先,声明一个新的类型:
type Band struct {
member int
name string
vocal string
guitar string
bass string
drummer string
}
然后,定义方法:
func (b Band) Name() string {
return b.name
}
最后,调用方法:只需要声明一个Band类型的对象,就可以直接调用这个方法:
band := &Band{
member: 5,
name: "SoBand",
vocal: "AShin",
guitar: "Monster;Stone",
bass: "Matthew",
drummer: "Ming",
}
fmt.Println(band.Name()) // 输出结果为:SoBand
基础类型作为接收者
type LiarInt int
func (i LiarInt) Get() int {
res := int(i) + 5
return res
}
func main() {
var a LiarInt = 5
fmt.Printf("[a = %d]\n", a.Get()) // 输出结果为:10
}
这里为什么要新声明一种类型,而不直接用int呢?
因为Go语言中,定义某种类型的方法,接收者类型的定义与其方法的定义必须在同一个包中,否则会报错。
4. 指针接收者&值接收者
在上面的例子中,都是用的值作为接收者,还可以用指针作为接收者。其区别与函数传参时使用值传递或指针传递一样。
值接收者,在方法内部对其进行修改,方法调用完成后,不会对调用者产生影响。
指针接收者,在方法内部对其进行修改,方法调用完成后,调用者也会随之改变。
请看下面的例子:
// 值接收者,这里是值传递,实际上并没有对它进行修改
func (b Band) ChangeName_Err(name string) {
b.name = name
}
// 指针接收者
func (b *Band) ChangeName(name string) {
b.name = name
}
func main() {
band := &Band{
member: 5,
name: "SoBand",
vocal: "AShin",
guitar: "Monster;Stone",
bass: "Matthew",
drummer: "Ming",
}
fmt.Println(band.Name()) // 输出结果为:SoBand
// 调用值传递的方法,调用完成后,band对象实际上并没有改变
band.ChangeName_Err("MAYDAY")
fmt.Println(band.Name()) // 输出结果为:SoBand
// 调用指针传递的方法,调用完成后,band对象发生了改变
band.ChangeName("MAYDAY")
fmt.Println(band.Name()) // 输出结果为:MAYDAY
}
但它与函数传参不同的是,在函数传参时,它的类型必须一致,比如函数的参数定义的是指针,那么在传参就必须是指针。
而在方法的调用中,则没有这种限制。不管我们调用者是值类型还是指针类型的对象,它都可以随意去调用接收者为值类型或指针类型的方法。
如上的例子中,ChangeName_Err 方法的接收者为值类型,但我们的调用者实际上是指针类型的对象,依然可以调用。同理,如果我们创建一个band对象是值类型的,去调用接收者为指针类型的 ChangeName 方法,也能成功,且效果一样。
对上面的例子稍作更改:
func main() {
p_band := &Band{
member: 5,
name: "SoBand",
vocal: "AShin",
guitar: "Monster;Stone",
bass: "Matthew",
drummer: "Ming",
}
var band Band = *p_band
fmt.Println(band.Name()) // 输出结果为:SoBand
// 调用值传递的方法,调用完成后,band对象实际上并没有改变
band.ChangeName_Err("MAYDAY")
fmt.Println(band.Name()) // 输出结果为:SoBand
// 调用指针传递的方法,调用完成后,band对象发生了改变
band.ChangeName("MAYDAY")
fmt.Println(band.Name()) // 输出结果为:MAYDAY
}
总结:接收者为值类型或者指针类型,不会影响我们的调用方式,不管我们调用者的对象是值类型,还是指针类型,都可以随意的调用它们。
所以,当我们希望方法内的修改对调用者同样产生影响,或者避免值拷贝的开销时,应当考虑使用指针类型的接收者。
5. 匿名字段
当一个结构体作为匿名字段被嵌套到另外一个结构体中,跟字阶提升一样,我们同样可以直接调用被嵌套的结构体中的方法。
// Band作为匿名字段被嵌套在Show中
type Show struct {
Time string
Arena string
Song string
Band
}
func main() {
show := &Show{"18:00", "Bird's Nest", "Embrace", band}
bandName := show.Name() // Show的对象直接调用其匿名字段Band的方法
fmt.Printf("[Shower: %s]\n", bandName) // 输出结果为: MAYDAY
}