【问题标题】:Types of Go struct methods that satisfy an interface满足接口的 Go 结构方法的类型
【发布时间】:2016-12-07 10:33:09
【问题描述】:

给出以下 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 的指针来调用结构 tourGuide 的两个方法。换句话说,我可以使用T*T 类型的变量调用带有T 接收器的方法。同样,我可以使用T 类型的变量(如果Taddressable)或*T 调用带有*T 接收器的方法。我了解编译器会处理此处的差异(请参阅代码中的我的 cmets)。

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

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

【问题讨论】:

  • 谢谢。但我的问题是,为什么编译器允许使用值类型和指针调用带有值和指针接收器的结构方法。但是不提供类似的接口支持吗?可能更多的是设计问题,而不是语言的工作原理?
  • @danze 如果这是一个设计问题,那么它不适合 SO。
  • @Volker 我的问题源于不完全理解 Go 接口,而接受的答案为我澄清了这一点。这不是设计问题。

标签: pointers go methods struct interface


【解决方案1】:

如果一个方法有一个指针接收器,那么只有一个指针值可以作为接收器值。所以要在某个值上调用这个方法,这个值本身必须是一个指针,或者必须可以获取它的地址(用作接收者)。

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

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

查看此答案,详细信息/证明在创建接口值时复制了值:How can a slice contain itself?

【讨论】:

  • 这就是我所追求的。现在我明白了为什么编译器会这样。谢谢!
【解决方案2】:

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

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

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

    【讨论】:

    • 赞成投票,因为您链接到的答案(也由您编写)非常好。不过,我认为这应该改写为“我的答案”,以明确其他答案也已由您编写。
    猜你喜欢
    • 1970-01-01
    • 2022-07-03
    • 1970-01-01
    • 2018-01-13
    • 1970-01-01
    • 1970-01-01
    • 2016-12-26
    • 1970-01-01
    • 2019-09-12
    相关资源
    最近更新 更多