在写 Go 的时候,我们常会发现以下情况:
type Z struct {
}
func (zv Z) Hello() {
log.Println("hello")
}
func (zp *Z) World() {
log.Println("world")
}
func main() {
{
zv := Z{}
zv.Hello()
zv.World()
} // 正常执行
{
zp := &Z{}
zp.Hello()
zp.World()
} // 正常执行
}
你会发现 zv 是值类型,zp 是指针类型,它们都能调用 Hello,World 方法。这是因为 Go 会帮我们做类型的智能转换。当执行语句为 zv.World()
,Go 发现 zv 是值类型,先对 zv 做取地址(&zv
),再调用 World ((&zv).World()
)。
同理,当执行 zp.Hello()
时,Go 发现 Hello 的接收器是值类型,于是寻址(*zp
),再调用 Hello((*zp).Hello()
)。
到底是不是上面说的那样,我们可以举几个例子测试一下。
在 Go 语言中,类似 zMap[1]
这样的语句是不允许取地址的。
zMap := map[int]{1: Z{}}
_ := &zMap[1] // 编译阶段出错:cannot take the address of zMap[1]
既然不允许取地址,那么调用接收器为指针的方法应该也是不允许的。
zMap := map[int]{1: Z{}}
zMap[1].World() // 编译阶段出错:
// cannot call pointer method on zMap[1]
// cannot take the address of zMap[1]
zMap[1].Hello() // 正常执行
什么时候不允许对指针寻址呢?答案很简单:空指针。
zNilPtr := (*Z)(nil)
zNilPtr.World() // 正常执行
// panic: runtime error: invalid memory address or nil pointer dereference
zNilPtr.Hello() // 运行时恐慌
注意二者的区别,对于值类型来说,是编译时失败;对指针类型,是运行时恐慌。这就可以反向推测,Go 编译器认为值类型只拥有值方法;指针类型拥有值方法和指针方法。我们可以用接口进行佐证。
type Z struct {
}
func (zv Z) Hello() {
log.Println("hello")
}
func (zp *Z) World() {
log.Println("world")
}
type Coding interface {
Hello()
World()
}
func main() {
var c Coding
// 编译时错误
// c = Z{}
// log.Println(c)
// 正常执行
c = &Z{}
log.Println(c)
}
从编译到运行结果看,符合前面猜想。
还不快抢沙发