满足接口

时间:2016-12-07 10:33:09

标签: pointers go methods struct interface

给出以下Go代码示例:

package main

import "fmt"

type greeter interface {
    hello()
    goodbye()
}

type tourGuide struct {
    name string
}

func (t tourGuide) hello() {
    fmt.Println("Hello", t.name)
}

func (t *tourGuide) goodbye() {
    fmt.Println("Goodbye", t.name)
}

func main() {
    var t1 tourGuide = tourGuide{"James"}
    t1.hello()   // Hello James
    t1.goodbye() // Goodbye James (same as (&t1).goodbye())

    var t2 *tourGuide = &tourGuide{"Smith"}
    t2.hello()   // Hello Smith
    t2.goodbye() // Goodbye Smith (same as (*t2).hello())

    // illegal: t1 is not assignable to g1 (why?)
    // var g1 greeter = t1

    var g2 greeter = t2
    g2.hello()   // Hello Smith
    g2.goodbye() // Goodbye Smith
}

我可以使用tourGuide tourGuide类型的变量或指向tourGuide t1的指针调用struct t2的两个方法。换句话说,我可以使用TT类型的变量调用*T接收方的方法。同样,我可以使用*T类型的变量(如果Taddressable)或T使用*T接收方调用方法。我知道编译器在这里处理差异(参见我在代码中的注释)。

然而,当我们实现接口时,事情会发生变化。在上面的代码中,类型greeter接口的变量可以从指向tourGuide的指针分配,但不能从tourGuide指定。

谁能告诉我为什么会这样呢?为什么我能够拨打t1.hello()t1.goodbye(),但某种方式t1对于greeter接口来说还不够?

3 个答案:

答案 0 :(得分:7)

如果方法具有指针接收器,则只能将指针值用作接收器值。因此,要在某个值上调用此方法,值本身必须是指针,或者必须可以获取其地址(用作接收器)。

如果您有一个变量,例如,它是addressable,因此可以获取其地址并将其用作接收器。规范允许你这样做,这会自动发生。

接口中包含的值不可寻址。创建接口值时,将复制包含在接口中的值。因此无法取其地址。理论上你可以允许获取副本的地址,但这将是(甚至)更混乱的来源,而不是它提供的好处,因为地址指向副本,而带有指针接收器的方法只能修改副本而不是原来的。

请参阅此答案,详细说明/证明在创建界面值时复制值:How can a slice contain itself?

答案 1 :(得分:3)

如果你有一个指向结构的指针,那么go将允许你访问结构及其函数的属性,这些属性具有值类型接收器(与指针接收器相对应),而不必取消引用指针,但这仅适用于一级指针,请参阅下面的代码,我将t2转换为指向tourguide指针的指针,此时我需要显式取消引用它,使其返回指向tourguide的指针。想想结构指针的第一级作为一个特殊情况,允许你使用合成糖来访问值类型属性和函数,以节省你不得不经常手动取消引用变量。

package main

import "fmt"

type greeter interface {
    hello()
    goodbye()
}

type tourGuide struct {
    name string
}

func (t tourGuide) hello() {
    fmt.Println("Hello", t.name)
}

func (t *tourGuide) goodbye() {
    fmt.Println("Goodbye", t.name)
}

func main() {
    var t1 tourGuide = tourGuide{"James"}
    t1.hello()   // Hello James
    t1.goodbye() // Goodbye James (same as (&t1).goodbye())

    var tmpT2 *tourGuide = &tourGuide{"Smith"}
    var t2 **tourGuide = &tmpT2
    (*t2).hello()   // Hello Smith
    (*t2).goodbye() // Goodbye Smith (same as (*t2).hello())

    //illegal: t1 is not assignable to g1 (why?)
    //var g1 greeter = t1

    //now this is illegal too
    //var g2 greeter = t2

    var g3 greeter = (*t2)
    g3.hello()   // Hello Smith
    g3.goodbye() // Goodbye Smith
}

答案 2 :(得分:0)

The answer here解释了为什么Go阻止您获取存储在接口中的值的地址。

tl; dr ,因为指向 A 的指针指向类型 A B 的值时,界面中的将失效。