为什么Go允许将接口变量分配给未实现值接收器的值?

时间:2019-02-22 04:02:37

标签: pointers go interface go-interface

很抱歉给您带来困惑的问题标题,但这是我不明白的地方。在下面的代码中,当我们尝试将x分配给p时遇到错误,因为x需要p来实现M(),而实际上并没有。 t,因为M()有一个指针接收器。

type Person struct {
    Name string
    Age int
}

func (p *Person) M() {
}

type I interface {
    M()
}

func main() {
    var x I
    var p := Person{}
    x = p              // Error, M() has pointer receiver
}

这对我来说相对有意义。我不明白的是,在以下示例中,将x分配给&p是多么的高兴。在此示例中,M()具有 value 接收器,而不是指针接收器,但是它仍然可以正常工作。

type Person struct {
    Name string
    Age int
}

func (p Person) M() {
}

type I interface {
    M()
}

func main() {
    var x I
    var p := Person{}
    x = &p             // No error
}

这对我来说毫无意义。一个方法要么实现一个值接收器,要么实现一个指针接收器,因此,如果它不允许您将接口变量分配给仅实现指针接收器的类型的值,为什么要允许您将其分配给指针,当该方法仅定义为值接收者时?

最后,您可以在Go中执行以下操作而不会感到困惑,这进一步混淆了这一点:

func (p *Person) rename(string newName) {
    p.Name = newName
}

func main() {
    p := Person{Name: "Old name"}
    fmt.Println(p.Name)
    p.rename("New name")
    fmt.Println(p.Name) // Name has changed, despite p not being pointer
}

在Go游览中,它说这样的调用是显式强制转换的(例如p.rename("New name")隐式变为(&p).rename("New name"),并且指针在需要时变为值)。

这似乎确实不一致。我在这里错了吗?

1 个答案:

答案 0 :(得分:-1)

我知道了。简而言之,我搞砸了两个概念。我有很多困惑,所以让我们分解一下,从我关于自动指针取消引用的最后一点开始。

我遇到的主要问题是,似乎编译器很乐意为您应用&运算符。关键是,有时候这有意义,而有时候却没有意义(见下文)。

p := Person{"Old"}
var iface I = p
p.rename("New") // Can safely be rewritten to (&p).rename("New")
iface.rename("New") // Cannot rewrite (&iface != &p)

这就是为什么只有一个指向该值的指针满足要求时才禁止为该接口分配的原因。一旦将原始变量分配给接口,您就不能仅对其进行地址修饰,但是当它是实际类型的变量时,则可以进行操作。现在,这仍然让我感到困惑:

p := Person{"Name"}
var iface I = &p    // No error

我从中得到的主要是... Go就是这样工作的。 This帖子介绍了方法集。我被“可寻址”这个词绊倒了。为了解释我的困惑,我给人的印象是,当存储指针但需要一个值时,编译器正在执行以下插入操作(这没有意义):

p := &Person{"Name"}
var iface I = p
p.rename()  /* becomes */ (*p).rename()
iface.rename() /* becomes */ (*iface).rename()

从编译器的角度来看,执行方法调用是一个多步骤的过程;只需将*放在变量名之前似乎就可以解决问题,但是还有更多变量要从内存中复制并压入堆栈等。实际上,自己取消引用变量并不会“做”适用于编译器的任何内容。在两种情况下,都需要转到该位置并复制那里的内容。从Go的角度来看,物理上写*只是告诉编译器做已经要做的事情,因为值接收器方法需要一个值,所以编译器需要产生一个值。