35

golang中接口赋值与方法集

 5 years ago
source link: https://studygolang.com/articles/15278?amp%3Butm_medium=referral
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

接口使用疑问

golang 中的接口可以轻松实现 C++ 中的多态,而且没有 继承自同一父类 的限制,感觉方便很多。但是在使用的时候,如果没有理解,也可能会遇到"坑"。比如 《Go语言实战》 中的一个例子:

package main

import "fmt"

type user struct {
    name  string
    email string
}
type notifier interface {
    notify()
}

func (u *user) notify() {
    fmt.Printf("sending user email to %s<%s>\n",
        u.name,
        u.email)
}
func sendNotification(n notifier) {
    n.notify()
}

func main() {
    u := user{
        name:  "stormzhu",
        email: "[email protected]",
    }
    sendNotification(u) 
}
// compile error
// cannot use u (type user) as type notifier in argument to sendNotification:
//    user does not implement notifier (notify method has pointer receiver)

报的错是 u 没有实现 notifier 这个接口,实现了这个接口的是 *user 类型,而不是 user 类型, uuser 类型,所以不能赋值给 notifier 这个接口。

既然如此,修改为 sendNotification(&u) 就OK了。然而问题是,如何理解到底是 T 类型还是 *T 类型实现了某个接口呢?

接口的定义

参考雨痕的 《Go语言学习笔记》 第七章, go 语言中的接口定义如下:

type iface struct {
    tab  *itab          // 类型信息
    data unsafe.Pointer //实际对象指针
}
type itab struct {
    inter *interfacetype // 接口类型
    _type *_type         // 实际对象类型
    fun   [1]uintptr     // 实际对象方法地址
}

虽然具体的细节操作不太懂,但是可以知道,对一个接口赋值的时候,会拷贝 类型信息 和该类型的 方法集 。这就类似于 C++ 多态中的 虚指针 ( vptr )和 虚函数表 ( vtable )了。我理解的是,只要这个类型的方法集中包括这个接口的所有方法,那么它就是实现了这个接口,才能够赋值给这个接口,那么问题来了,一个类型的方法集是什么呢?

方法集

同样参考雨痕 《Go语言学习笔记》 第6章6.3节,书中总结的很全面:

  • 类型 T 的方法集包含所有 receiver T 方法。
  • 类型 *T 的方法集包含所有 receiver T + *T 方法。
  • 匿名嵌入 S ,类型 T 的方法集包含所有 receiver T + S 方法。
  • 匿名嵌入 *S ,类型 T 的方法集包含所有 receiver T + S + *S 方法。
  • 匿名嵌入 S*S ,类型 *T 的方法集包含所有 receiver T + *T + S + *S 方法。

虽然看起来比较复杂,但总结完就一话, *T 类型就是厉害,方法集包括 T*T 的方法。

所以文章开头的例子中, uuser 类型,方法集是空的,不算是实现了 notifier 接口。

当在纠结应该将 T 类型还是 *T 类型赋值给某个接口的时候,第一步就是看方法集,看一看该类型到底有没有实现这个接口。(所以 T*T 不是一个类型。。。)

一些例子

go 语言的内置库中有定义了很多接口,如 error 接口,

type error interface {
    Error() string
}

内置的 errors 包实现了这个接口:

// Package errors implements functions to manipulate errors.
package errors

// New returns an error that formats as the given text.
func New(text string) error {
    return &errorString{text}
}

// errorString is a trivial implementation of error.
type errorString struct {
    s string
}

func (e *errorString) Error() string {
    return e.s
}

可以看到 New 方法返回值是 error 接口,而只有 *errorString 类型实现了这个接口,所以 New 方法返回的是 &errorString{text} 而不是 errorString{text}

总结

  • T*T 不是一个类型,他们的方法集不同
  • 类型 *T 的方法集包含所有 receiver T + *T 方法,类型 T 的方法集只包含所有 receiver T 方法。

我的简书博客

参考


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK