Golang interface详解

interface

在go语言中,interface有两种用法。

第一种是空接口,代表任意类型,空接口也可以理解为没有任何要求的接口类型,也可以说所有的类型都实现了空接口。

另一种是有方法的接口,在接口中定义一系列方法,一个类型如果实现了这些方法,那么我们就说这个类型实现了这个接口。

接口声明

空接口:

var any interface{}
any = 1 
any = true
any = "hello"

带方法的接口:

类型不需要显式声明它实现了某个接口:接口被隐式地实现。多个类型可以实现同一个接口

type Bird interface {
	Fly()
}

type Sparrow struct{} 

func (Sparrow) Fly() {}

var _ Bird = (*Sparrow)(nil)

interface的底层实现

所有interface,包括有方法和空接口,在内存中都是占据两个字长,在32位机器上就是8个字节,在64位机器上就是16个字节。

runtime.eface:

type eface struct{
    _type *_type	// 数据的真实类型
    data unsafe.Pointer
}

空接口由两个指针组成,_type表示接口的类型相关的信息,data表示数据的指针。

type _type struct {
    size       uintptr	// 内存大小
    ptrdata    uintptr 	// 内存前缀大小
    hash       uint32	// 类型的hash值
    tflag      tflag	// 类型信息标志,和反射相关
    align      uint8	// 内存对齐大小
    fieldalign uint8	// 结构体字段内存对齐大小
    kind       uint8	// 类别
    equal func(unsafe.Pointer, unsafe.Pointer) bool
    // gcdata stores the GC type data for the garbage collector.
    // If the KindGCProg bit is set in kind, gcdata is a GC program.
    // Otherwise it is a ptrmask bitmap. See mbitmap.go for details.
    gcdata    *byte		// gc相关
    str       nameOff
    ptrToThis typeOff
}

runtime.iface

type iface struct {
    tab  *itab
    data unsafe.Pointer
}

有方法的接口也由两个指针组成,tab存放接口的类型以及方法表,data表示数据的指针

type itab struct {
    inter  *interfacetype	// 接口类型(比如Bird)
    _type  *_type			// 数据真实类型,动态类型(比如Sparrow)
    hash   uint32 // copy of _type.hash. Used for type switches.
	_      [4]byte
	fun    [1]uintptr // variable sized. fun[0]==0 means _type does not implement inter.
}

type nameOff int32
type typeOff int32

type imethod struct {
	name nameOff
	ityp typeOff
}

type interfacetype struct {
    typ     _type	// 具体的类型
    pkgpath name	// 接口的包名
    mhdr    []imethod  //接口定义的函数列表
}
  • itab中 inter 表示接口类型,包含类型、包名、方法等信息,_type 表示数据的真实类型。
  • fun字段为与接口方法对应的具体数据类型的方法地址,fun的数组大小为1,存放的是第一个方法的地址,如果有多个方法,因为内存是连续的,可以通过增加指针获取后面的方法地址。
  • reflect.Typeof() 返回的是数据的真实类型。

interface的类型转换

func main() {
	var x int64 = 1
	var a interface{} = x
	fmt.Println(a)
}

这段代码将int64的x赋值给a,将发生类型转换。打印其汇编代码:

go tool compile -S mian.go > mian.s

观察汇编可以发现,在执行 var a interface{} = x的时候,发现其调用了runtime.convT64(SB)方法。

实际上根据所赋与值的类型的不同,会调用不同的转换方法,类似的还有 convTstring,convT32等。

类型断言

var any interface{} = 1
i,ok := any.(int)
var any interface{}
switch any.(type){
case int:
	fmt.Println("type int")
case string:
	fmt.Println("type string)
default:
	fmt.Println("type unknow)
}

类型断言interface的类型就是它的数据真实类型。例如:

type a int64

func main(){
    var i interface{} = a(1) // i的真实数据类型为 main.a而不是int64
    fmt.Println(i.(a))
}

类型断言参考:runtime.assertxx 方法的实现。

指针接收者还是值接收者?

先看指针接收者和值接收者的区别:

  1. 当Fly的接收者为值类型时:
type Bird interface {
	Fly()
}

type Sparrow struct {
}

func (Sparrow) Fly() {
	fmt.Println("sparrow fly")
}

func main() {
	var s1 Bird = Sparrow{}
	s1.Fly()
	var s2 Bird = &Sparrow{}
	s2.Fly()
}

打印结果为:

sparrow fly
sparrow fly
  1. 当Fly的接收者为指针类型时:
func (*Sparrow) Fly() {
	fmt.Println("sparrow fly")
}

func main() {
	var s1 Bird = Sparrow{}
	s1.Fly()
	var s2 Bird = &Sparrow{}
	s2.Fly()
}

打印结果为:

cannot use Sparrow literal (type Sparrow) as type Bird in assignment:
	Sparrow does not implement Bird (Fly method has pointer receiver)

可以看出,当方法的接收者为值类型时,不管是指针类型的值还是值类型的值都可以调用该方法,因为go会把指针进行隐式的转换得到指针的值,而当方法的接收者为指针类型时,值类型的值调用该方法会报错。

*类型 T 变量只有接受者是 T 的方法;而类型 *T变量拥有接受者是 T 和 T 的方法

选择指针接收者主要考虑以下因素:

  1. 期望修改结构体的值
  2. 当结构体比较大的时候,避免每次都进行值拷贝
var _ intertype= (*mytype)(nil)