【问题标题】:Why implicit non-pointer methods not satisfy interface?为什么隐式非指针方法不满足接口?
【发布时间】:2017-06-14 19:12:11
【问题描述】:

假设我们了解,

对于X 类型的显式方法定义,GO 编译器为*X 类型隐式定义相同的方法,反之亦然,如果我声明,

func (c Cat) foo(){
  //do stuff_
} 

并声明,

func (c *Cat) foo(){
  // do stuff_
}

然后GO编译器报错,

Compile error: method re-declared

表示指针方法是隐式定义的,反之亦然


在下面的代码中,

package main

type X interface{
  foo();
  bar();
}

type Cat struct{

}

func (c Cat) foo(){
  // do stuff_
}

func (c *Cat) bar(){
  // do stuff_
}

func main() {
  var c Cat
  var p *Cat
  var x X

  x = p // OK; *Cat has explicit method bar() and implicit method foo()
  x = c //compile error: Cat has explicit method foo() and implicit method bar()

}

GO 编译器报错,

cannot use c (type Cat) as type X in assignment:
    Cat does not implement X (bar method has pointer receiver)

x = c,因为隐式指针方法满足接口,但隐式非指针方法不满足。

问题:

为什么隐式非指针方法不满足接口?

【问题讨论】:

标签: go methods interface structural-typing


【解决方案1】:

让我们看看specification的语言:

一个类型可能有一个与之关联的方法集。方法集 接口类型是它的接口。任何其他的方法集 类型 T 由所有声明为接收者类型 T 的方法组成。 对应指针类型的方法集合 *T 是所有的集合 用接收者 *T 或 T 声明的方法(也就是说,它还包含 T)的方法集

在你的例子中,接口类型x的方法集是[foo(), bar()]Cat 类型的方法集是[foo()]*Cat 类型的方法集是[foo()] + [bar()] = [foo(), bar()]

这就解释了为什么变量p满足接口x,而变量c不满足。

【讨论】:

  • c.bar() 的工作方式如 code 所示。那么,如何证明类型(Cat)的方法集是[foo()]
  • c.bar() 有效,因为编译器将此语句替换为 (&c).bar()。但是c不能赋值给接口x,因为Cat是自己的类型,所以没有bar()这样的方法。
  • 对于非指针var c Cat,你会如何调用(*c).bar()
  • 这是一个错字,已修复。
  • 根据规范,code 中的 type(*Cat) 方法集是什么?为什么p.bar() 失败了?根据您的观点,为什么不调用 (*p).bar() 代替 p.bar()
【解决方案2】:

方法集

关注spec

任何其他命名类型 T 的方法集由所有接收者类型为 T 的方法组成。对应指针类型 *T 的方法集是所有接收者为 *T 或 T 的方法集(即它还包含T)的方法集。

在您遵循可寻址和不可寻址类型概念之前,方法集定义听起来很奇怪。

可寻址和不可寻址类型

如果值是可寻址类型,则可以调用指针接收器方法。

与选择器一样,使用指针对具有值接收器的非接口方法的引用将自动取消对该指针的引用:pt.Mv 等价于 (*pt).Mv。

与方法调用一样,使用可寻址值的指针接收器对非接口方法的引用将自动获取该值的地址:t.Mp 等价于 (&t).Mp。

在处理可寻址类型(结构是可寻址的)之前,可以对值调用指针接收器方法:

type Cat struct {}

func (c *Cat) bar() string { return "Mew" }

func main() {
    var c Cat
    c.bar()
}

接口类型的变量不可寻址

但并非所有 Go 类型都是可寻址的。 通过接口引用的变量也是不可寻址的

不可能在不可寻址类型的值上调用指针接收器:

type X interface {
    bar() string
}

type Cat struct{}

func (c *Cat) bar() string { return "Mew" }

/* Note `cat` variable is not a `struct` type value but
   it is type of `X` interface therefor it is not addressable. */
func CatBar(cat X) { 
    fmt.Print(cat.bar())
}

func main() {
    var c Cat
    CatBar(c)
}

因此,Go 运行时会出现以下错误,从而防止段错误:

不能在赋值中使用 c(类型 Cat)作为类型 X: Cat 没有实现 X(bar 方法有指针接收器)

【讨论】:

  • @overexchange, t 不是隐式定义,它只是指针可以访问(实际上是访问)值但值无法访问指针的事实。受此限制的值接收器。
  • 1) 还包含T的方法集,意思是,如果接收者类型(*Cat)已经定义了方法(bar())然后编译器为接收器类型(Cat)创建方法 bar() 的隐式定义,我在查询的第一部分以及代码中的问题行(使用 cmets)中说。 2) 与你行x = &c,你是说&c*Cat 类型,*Cat 类型有满意的接口,我也在查询中说,所以它工作
  • @overexchange, "t" 是 "it" 丢失了 "i" =)
  • 请通过 code 并在第 24 行检查 (c.bar()) 并解释我,谁为 type(Cat) 定义了方法 bar()
  • @overexchange 没有人。 Cat 类型没有方法 bar()。但是因为c 是可寻址的,所以编译器将调用c.bar() 转换为(&c).bar() 以方便您使用。同样,如果p*Cat,编译器会为您将p.foo() 转换为(*p).foo()。没有隐式方法,只有隐式重写调用
【解决方案3】:

在 dev.bmax 的回答中添加一点。

type Cat struct{
}

func (c Cat) foo(){
  // do stuff_
}

func (c *Cat) bar(){
  // do stuff_
}

你可以的

var c cat
c.bar() // ok to call bar(), since c is a variable.

但不是

cat{}.bar() // not ok to call bar(), c is not a variable.

只要参数是一个变量,对T类型的参数调用*T方法是合法的;编译器隐式地获取它的地址。但这只是语法糖:类型 T 的值并不拥有 *T 指针所拥有的所有方法,因此它可能满足更少的接口。

另一方面,您总是可以使用 Cat 或 *Cat 调用 foo()。

【讨论】:

    【解决方案4】:

    这个怎么样?

    package main
    
    import (
        "fmt"
    )
    
    type Growler interface{
        Growl() bool
    }
    
    type Cat struct{
        Name string
        Age int
    } 
    
    // *Cat is good for both objects and "references" (pointers to objects)
    func (c *Cat) Speak() bool{
        fmt.Println("Meow!")
            return true
    }
    
    func (c *Cat) Growl() bool{
        fmt.Println("Grrr!")
        return true
    }
    
    func main() {
        var felix Cat // is not a pointer
        felix.Speak() // works :-)
        felix.Growl() // works :-)
    
        var ginger *Cat = new(Cat) 
        ginger.Speak() // works :-)
        ginger.Growl() // works :-)
    }
    

    【讨论】:

    • felix.Speak()felix.Growl() 有效,因为正如我在 Query 中所说,对于类型(*Cat)的显式方法定义(Speak() & Browl()),GO 编译器隐式定义了这两个类型(Cat)的方法(Speak() & Browl())。你同意吗?
    • 据我所知,所有规则都在Go语言规范中的“方法表达式”和“方法值”下进行了简洁的解释。我只是想提供一个简单的例子来说明这对猫科动物是如何起作用的! ;-)
    猜你喜欢
    • 2020-08-11
    • 2019-09-12
    • 1970-01-01
    • 1970-01-01
    • 2021-03-16
    • 2011-04-07
    • 2021-10-06
    • 1970-01-01
    • 2012-06-05
    相关资源
    最近更新 更多