Go 方法接收器与接口

Go 小记 2020-02-08 1688 字 957 浏览 点赞

在写 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)
}

从编译到运行结果看,符合前面猜想。



本文由 Guan 创作,采用 知识共享署名 3.0,可自由转载、引用,但需署名作者且注明文章出处。

还不快抢沙发

添加新评论