Go语言设计与实现 -- 接口

接口实际上是一个中间层,用于上下游的解耦,在框架和操作系统中,接口都随处可见,而Go语言将接口作为了内置类型,接下来,我们就来重点学习一下,Go语言的接口。

  • 将实现接口的结构体实例赋值给接口
  • 结构便可以调用其中的方法

Java的接口和Go的方式是完全不同的:

  • 在Java中,实现接口需要显式声明接口并实现所有方法
  • 在Go语言中,实现接口的所有方法就隐式实现了接口

Go语言只会在传递参数,返回参数,以及变量赋值的时候检查某个类型是否实现了接口。简而言之,就是在需要它的时候才会去检查

Go接口分为两种:

  • 空接口
  • 带有方法的接口

它与C语言的void *不一样,interface{}类型并不是任意类型。如果我们将类型转换成了interface{},变量在运行期间的类型也会随之发生变化,获取变量时会得到interface{}

接口和指针

来看几组代码:

type Duck interface {
	Walk()
}

type Cat struct{}

// 注意,指针
func (c *Cat) Walk() {
	fmt.Println("lxy")
}

func main() {
    // 接口赋值,指针、
    // 这里的&Cat{}变量能够隐式的指向的结构体
	var d Duck = &Cat{}
	d.Walk()
}

这一组接口赋值没有发生任何错误。

type Duck interface {
   Walk()
}

type Cat struct{}

// 值
func (c Cat) Walk() {
   fmt.Println("lxy")
}

func main() {
    // 指针
   var d Duck = &Cat{}
   d.Walk()
}

代码没有出现任何问题。

type Duck interface {
   Walk()
}

type Cat struct{}

func (c Cat) Walk() {
   fmt.Println("lxy")
}

func main() {
   var d Duck = Cat{}
   d.Walk()
}

这个代码也没有出现任何问题,接下来来看一下会出问题的代码:

在这里插入图片描述

为什么会发生这样的事情呢?

golang-interface-method-receive

  • 对于这张图的左侧而言,这意味着复制一个结构体指针,该指针与原来的指针指向相同且唯一的结构体,所以编译器可以对变量解引用获取指针指向的结构体。
  • 对于右侧的图而言,对于Cat{}来说,这意味着方法会接收一个全新的Cat{},这意味着walk()方法会接收一个全新的Cat{},所以编译器不会无中生有创建一个新指针;即使编译器可以创建新指针,这个指针指向的也不是最初调用改方法的结构体。

总结一下:Go语言中的指针类型在使用的时候会自动进行解引用,如果方法类型和传递的类型是一致的话,那么可以成功是毋庸置疑的,但是指针传给非指针的时候,指针会自动解引用成非指针之后完成复制,如果是非指针传递给指针的时候,那么这个操作就无法完成,Go语言不会自动的加上一个指针。

nil和non-nil

我们要记住一句话Go语言的接口类型不是任意类型。

我们接下来来看组代码:

type TestStruct struct{}

func NilOrNot(v interface{}) bool {
   return v == nil
}

func main() {
   var s *TestStruct
   fmt.Println(s == nil)    // true
   fmt.Println(NilOrNot(s)) // false
}

可以看到这一组的结果,为什么第二个打印是false呢?

调用NilOrNot的时候发生了隐式类型转换。除了向方法传入参数之外,变量的赋值也会触发隐式类型转换。在进行类型转换的时候,*TestStruct类型会转换成interface{}类型。转换后的变量不仅包含转换前的变量,还包含类型信息TestStruct,所以与nil不相等。

数据结构

Go根据结构是否包含一组方法将接口分成了两类:

  • 使用runtime.iface结构体表示包含方法的接口
  • 使用runtime.eface结构体表示不包含任何方法的interface{}

我们来看一下这两个结构体的实现:

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

可以看到第二个字段data都是一样的,它指向接口的数据。

紧接着,我们先来看一下eface_type结构体。

type _type struct {
    // 字段存储了类型占用的内存空间,为内存的空间分配提供信息
   size       uintptr
   ptrdata    uintptr 
    // 快速确定类型是否相等
   hash       uint32
   tflag      tflag
   align      uint8
   fieldAlign uint8
   kind       uint8
    // 用于判断当前类型的多个对象是否相等
   equal func(unsafe.Pointer, unsafe.Pointer) bool
   gcdata    *byte
   str       nameOff
   ptrToThis typeOff
}

这个结构体里面存放的是类型的元信息。

再来看一下itab结构体:

·face除了要存储动态类型但是信息之外,还要存储接口本身的信息,以及动态类型所实现的方法的信息,所以是这样的。

type itab struct {
   inter *interfacetype
   _type *_type
    // 对_type.hash的复制,当我们想将interface类型转换成具体类型的时候,可以使用该字段快速判断目标类型和具体类型的runtime._type是否相同
   hash  uint32
   _     [4]byte
   fun   [1]uintptr // variable sized. fun[0]==0 means _type does not implement inter.
}

可以看到这个结构体里面也有我们的_type结构体,代表它同样可以存储我们的数据类型。

在这里插入图片描述
在这里插入图片描述

我们来逐一分析一下这个结构体,第一个指针interfacetype这个存储着这个结构体类型自身的信息。

type interfacetype struct {
   typ     _type // 类型信息
   pkgpath name // 包路径名
   mhdr    []imethod // 结构方法集合
}

_type字段存储的是这个结构变量的动态类型的信息。

fun是动态类型已实现的接口方法的调用地址数组。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

胡桃姓胡,蝴蝶也姓胡

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值