首页 文章

接口和指针接收器

提问于
浏览
5

我是新手gopher,并试图让我的头围绕指针接收器和接口 .

type Foo interface {
    foo()
}
type Bar struct {}
func (b *Bar) foo() {}

基于以上定义..

--- Allowed ---------
b := Bar{}
b.foo()

--- Not allowed ----- 

var foo Foo = Bar{}

获取编译器错误:不能在赋值时使用Bar literal(类型Bar)作为类型Foo:Bar不实现Foo(foo方法有指针接收器)

我理解编译器在第一个场景中代表我们做了一些指针转换和解引用 . 为什么不在第二种情况下做同样的事情?

3 回答

  • 6

    简短回答 var foo Foo = Bar{} 无效,因为存储在接口中的具体值无法寻址 .

    Longer Version

    请阅读https://github.com/golang/go/wiki/MethodSets

    对任何已经是指针或可以采用其地址的东西调用指针值方法是合法的 . 在任何值上调用值方法或者可以取消引用其值是合法的 .

    关于上面的解释,你的代码

    b := Bar{}
    b.foo()
    

    有效,因为 b 是可寻址的 .

    存储在接口中的具体值不可寻址 . 因此,当您在接口上调用方法时,它必须具有相同的接收器类型,或者必须可以直接从具体类型中识别:指针和值接收器方法可以分别使用指针和值调用,如您所料 . 可以使用指针值调用值接收器方法,因为它们可以首先解除引用 . 但是,不能使用值调用指针接收器方法,因为存储在接口内的值没有地址 . 在为接口赋值时,编译器会确保实际上可以在该值上调用所有可能的接口方法,因此尝试进行不正确的赋值将在编译时失败 .

    根据上面的解释,存储在接口中的具体值是不可寻址的,因此代码,

    var foo Foo = Bar{}
    

    因为存储在接口中的具体值(在本例中为 Bar{} )不可寻址,所以不起作用 .

  • 3

    解释在于,当处理具体结构本身时,它具有自动处理它的适当信息 . 你可以阅读in the tour here

    Go自动处理方法调用的值和指针之间的转换 .

    但是当您处理 interface{} 类型时,它对变量中实际包含的内容的信息较少 . 它只知道有一个 foo() 方法 . 但这里有一个微妙的需要额外的解释所以这里是一个例子 .

    https://play.golang.org/p/Y0fJcAISw1

    type Foo interface {
        foo()
    }
    type Bar struct {}
    func (b *Bar) foo() {}
    
    type Baz struct {}
    func (b Baz) foo() {}
    
    func main() {
        b := Bar{}
        b.foo()
    
        var v Foo = &Bar{}
        // v = Bar{} // fails
        v.foo()
    
        v = Baz{}
        v.foo()
        v = &Baz{} // works too
        v.foo()
    }
    

    请注意, &Baz{} 即使具有值接收器也能正常工作,但不是相反 . 原因是 *Baz 完全指向 one Baz ,两者都存在(指针和值),因此很容易获得该值 . 当您尝试执行 v = Bar{} 时,该值存在,但指针不存在,并且Go不会自动为 interface{} 值创建一个值 .

    这一切都在 Pointers and interfaces Headers in this blog post下详细解释

  • 2

    您的问题的一半取决于您的 Value 是否可寻址:

    对于类型为T的操作数x,地址操作&x生成类型为* T到x的指针 . 操作数必须是可寻址的,即:变量,指针间接或切片索引操作;或可寻址结构操作数的字段选择器;或者可寻址数组的数组索引操作 . 作为可寻址性要求的例外,x也可以是(可能带括号的)复合文字 . - 地址运营商

    Bar{} 是复合文字,因此无法寻址 . 您可以键入 &Bar{} 来创建 *Bar 类型的对象,但它被列为"an exception to the addressability requirement",这强化了 Bar{} 本身不可寻址的想法 .

    Bar 类型的变量 b 可以调用 b.foo() 尽管 Bar.foo() 需要指针接收器,原因很简单:

    如果方法集(类型)x包含m并且参数列表可以分配给m的参数列表,则方法调用x.m()有效 . 如果x是可寻址的并且&x的方法集包含m,则x.m()是(&x).m()的简写 - 调用

    但是,这并不意味着 Bar.foo() 位于 b 的方法集中 . 这是因为 b 的类型为 Bar ,而 Bar.foo() 的类型为 *Bar

    类型可能具有与之关联的方法集 . 接口类型的方法集是其接口 . 任何其他类型T的方法集由用接收器类型T声明的所有方法组成 . 相应指针类型* T的方法集是用receiver * T或T声明的所有方法的集合(也就是说,它还包含方法一套T) . - 来自方法集

    因为 bFoo 接口的方法集不同,所以尽管编译器将 b.foo() 转换为 (&b).foo() ,但不能使用 var foo Foo = b . 否则, var foo Foo = Bar{} 会起作用 . 但是,您可以使用以下任一项,因为 Bar.foo() 收到 *Bar

    var foo Foo = &b
    var foo Foo = &Bar{}
    

相关问题