6.1 定义
方法是与对象实例绑定的特殊函数。
方法是面对对象编程的基本概念,用于维护和展示对象的自身状态。对象是内敛的,每个实例都有各自不同的独立特征,以属性和方法来暴露对外通信接口。普通函数则专注于算法流程,通过接收参数来完成特定逻辑运算,并返回最终结果。换句话说,方法是有关联状态的,而函数通常没有。
方法和函数定义语法区别的在于前者有前置实例接收参数(receiver),编译器以此确定方法所属类型。在某些语言里,尽管没有显示定义,但会在调用时隐式传递this实例参数。
可以为当前包,以及接口和指针以外的任何类型定义方法。
type N int
func (n N) toString() string {
return fmt.Sprintf("%#x", n)
}
func main() {
var a N = 25
println(a.toString()) // 0x19
}
方法同样不支持重载(overload)receiver参数名没有限制,按惯例会选用简短有意义的名称(不推荐使用this、self)。如方法内部并不引用实例,可省略参数名,仅保留类型。
type N int
func (n N) test() {
println("hi")
}
func main() {
var a N = 25
a.test()
}
方法可看作特殊的函数,那么receiver的类型自然可以是基础类型或指针类型。这会关系到调用时对象实例是否被复制。
type N int
func (n N) value() {
n++
fmt.Printf("v:%p,%v\n", &n, n)
}
func (n *N) pointer() {
(*n)++
fmt.Printf("p:%p,%v\n", n, *n)
}
func main() {
var a N = 25
a.value()
a.pointer()
fmt.Printf("a:%p,%v\n", &a, a)
}
结果
v:0xc00004c070,26
p:0xc00004c058,26
a:0xc00004c058,26
可使用实例值或指针调用方法,编译器会根据方法receiver类型自动在基础类型和指针类型间转换。
type N int
func (n N) value() {
n++
fmt.Printf("v:%p,%v\n", &n, n)
}
func (n *N) pointer() {
(*n)++
fmt.Printf("p:%p,%v\n", n, *n)
}
func main() {
var a N = 25
p := &a
p.value()
p.pointer()
}
结果
v:0xc00004c070,26
p:0xc00004c058,26
不能使用多级指针调用方法
func main() {
var a N = 25
p := &a
q := &p
q.value() //calling method value with receiver q (type **N) requires explicit dereference
q.pointer() // calling method pointer with receiver q (type **N) requires explicit dereferenc
}
指针类型的ereceiver必须是合法指针(包括nil),或能获取实例地址
type X struct{}
func (x *X) test() {
println("test")
}
func main() {
var a *X
a.test()
X{}.test() //cannot take the address of X literal
}
如何选择方法的receiver类型
- 无需修改状态的小对象或固定值,建议用T
- 引用类型、字符串、函数等指针包装类型,直接用T
- 其他情况用*T
6.2 匿名字段
可以像访问匿名字段成员那样,直接调用其方法,由编译器负责查找。
type data struct {
sync.Mutex
buf [1024]byte
}
func main() {
d := data{}
d.Lock()
defer d.Unlock()
}
方法也会有同名遮蔽问题。但利用这种特性,可实现类似覆盖操作。
type user struct{}
type manage struct {
user
}
func (u *user) test() string {
return "user test"
}
func (m *manage) test() string {
return m.user.test() + " then manage test"
}
func main() {
var m manage
println(m.test())
println(m.user.test())
}
结果
user test then manage test
user test
尽管能直接访问匿名字段的成员和方法,但他们依然不属于继承关系。
6.3 方法集
类型有一个与之相关的方法集(method set),这决定了它是否实现了某个接口。
- 类型T方法集包含所有receiverT方法。
- 类型*T方法集包含了所有receiverT + *T方法。
- 匿名嵌入类型S,T方法集包含所有receiverS方法。
- 匿名嵌入*S,T方法集包含所有receiverS+*S方法。
- 匿名嵌入S或*S,*T方法集包含所有receiverS + *S方法。
可利用反射(reflect)测试这些规则。
6.4 表达式
方法和函数一样,除直接调用外,还可复制给变量,或作为参数传递。依照具体引用方式的不同,可分为expression和value两种状态
Method Expression
通过类型引用的method expression会被还原成普通函数样式,receiver是第一参数,调用时须显示传参。至于类型,可以是T或*T,只要目标方法存在于该类型方法集中即可。
type N int
func (n N) test() {
fmt.Printf("test.n:%p,%d\n", &n, n)
}
func main() {
var n N = 25
fmt.Printf("main.n:%p,%d\n", &n, n)
f1 := N.test
f1(n)
f2 := (*N).test
f2(&n)
}
结果
main.n:0xc00004c058,25
test.n:0xc00004c090,25
test.n:0xc00004c0a0,25
尽管*N方法集包装的test方法receiver类型不同,但编译器会保证按原定义类型拷贝传值。
当然,也可直接以表达方式调用。
func (n N) test() {
fmt.Printf("test.n:%p,%d\n", &n, n)
}
func main() {
var n N = 25
N.test(n)
(*N).test(&n)
}
结果
test.n:0xc00004c070,25
test.n:0xc00004c088,25
Method Value
基于实例或指针引用的method value,参数签名不变,依旧按正常方式调用。
但当method value 被赋值给变量或作为参数传递时,会立即计算并复制该方法执行所需要的receiver对象,与其绑定,以便在稍后执行时,能隐式传入receiver参数
type N int
func (n N) test() {
fmt.Printf("test.n:%p,%d\n", &n, n)
}
func main() {
var n N = 25
p := &n
n++
f1 := n.test // 因为test方法的receiver类型是N类型,复制n,等于26
n++
f2 := p.test // 复制*p 等于27
n++
fmt.Printf("main.n:%p,%d\n", &n, n)
f1()
f2()
}
结果
main.n:0xc00004c058,28
test.n:0xc00004c090,26
test.n:0xc00004c0a0,27
编译器会为method value生成一个包装函数,实现间接调用。至于receiver复制,和闭包的实现方法基本相同,打包成funcval,经由Dx寄存器传递
当method value作为参数时,会复制含receiver在内的整个method value。
type N int
func (n N) test() {
fmt.Printf("test.n:%p,%d\n", &n, n)
}
func call(m func()) {
m()
}
func main() {
var n N = 25
p := &n
n++
call(n.test)
n++
call(p.test)
n++
fmt.Printf("main.n:%p,%d\n", &n, n)
}
结果
test.n:0xc00004c070,26
test.n:0xc00004c088,27
main.n:0xc00004c058,28
当然,如果目标方法的receiver类型是指针类型,那么复制的仅是指针。
func (n *N) test() {
fmt.Printf("test.n:%p,%d\n", n, *n)
}
func main() {
var n N = 25
p := &n
n++
f1 := n.test // 由于复制的是指针,所以调用时的值相同
n++
f2 := p.test
n++
fmt.Printf("main.n:%p,%d\n", &n, n)
f1()
f2()
}
结果
main.n:0xc00004c058,28
test.n:0xc00004c058,28
test.n:0xc00004c058,28
只要receiver参数类型正确,使用nil同样可以执行
type N int
func (N) value() {}
func (*N) pointer() {}
func main() {
var p *N
fmt.Println(p == nil) //true
p.pointer()
//p.value() //invalid memory address or nil pointer dereference(无法获取地址的数据,因为是nil)
(*N).pointer(nil)
(*N)(nil).pointer()
}