满足接口的Go结构方法的类型

给出以下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 t1 类型的变量或者指向tourGuide t2 的变量来调用struct tourGuide 的两个方法 . 换句话说,我可以使用 T*T 类型的变量调用 T 接收器的方法 . 类似地,我可以使用 T 类型的变量(如果 Taddressable)或 *T 使用 *T 接收器调用方法 . 我知道编译器在这里处理差异(参见我在代码中的注释) .

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

谁能告诉我为什么会这样呢?为什么我能够调用 t1.hello()t1.goodbye() 但不知何故 t1 对于接口 greeter 来说还不够?

回答(3)

3 years ago

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

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

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

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

3 years ago

如果你有一个指向结构的指针,那么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
}

3 years ago

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

tl;dr 因为当接口中随后存储了不同类型的值 B 时,指向接口中 A 类型值的 A 指针将无效 .