教程集 www.jiaochengji.com
教程集 >  Golang编程  >  golang教程  >  正文 golang之interface底层原理

golang之interface底层原理

发布时间:2021-12-30   编辑:jiaochengji.com
教程集为您提供golang之interface底层原理等资源,欢迎您收藏本站,我们将为您提供最新的golang之interface底层原理资源

golang的interface类型可以接受任何实现了这个interface规定的方法的类型的变量。比如有如下interface类型定义:

type Notifier interface {

     notify()
change(str1, str2  string)

}

这里定义了一个interface类型Notifier,它要求有两个方法。如果其他的类型实现了这两个方法,那Notifier的一个对象就可以接受那个类型的对象。

下面的 * user  类型实现了这两种方法:

type user struct {
name string
email string
}
func (u *user)  notify() {
fmt.Printf("Sending user email %s<%s>\n", u.name, u.email)
}
func (u *user) change(str1, str2 string) {
u.name = str1
u.email = str2

}

于是, *user 类型的对象就可以赋值给Notifier类型的对象:

u1 := user{name:"agan", email:"aaa@163"}
var f_u1 Notifier = &u1 

//var f_u2 Notifier = u1

<<注意这里是 *user 类型实现了那两个方法,而不是user类型实现了,所以上面的f_u2赋值失败。但是因为 interface {} 类型没有要求实现什么方法,所以任何类型都可以给之赋值,所以下面的赋值是可以的:

var f_u2 interface {} = u1

interface对象有两个指针,这两个指针有如下定义:

runtime.h

struct Iface

{

    Itab* tab;<<对象接口表指针,指向接口类型、动态类型、以及实现接口的方法表;

    void* data; <<数据指针,是目标对象的只读复制品,要么是完整对象的复制品,要么是一个指针的复制品

};

struct Itab

{

    InterfaceType* inter;

    Type*     type;

    void (*func[]) (void);

};

也就是说,语句

u1 := user{name:"agan", email:"aaa@163"}
var f_u1 Notifier = &u1 

中,f_u1有两个指针,第一个f_u1.tab指针指向 描述 *user 的表,第二个f_u1.data指向 &u1。这样在我们写出f_u1.notify()的语句时,编译器才知道我们是给那个对象(&u1,f_u1.data指定),调用u1.notify定义的notify函数,而不是其他类型定义的notify函数(f_u1.tabl指定)。

而语句

u1 := user{name:"agan", email:"aaa@163"}

var f_u2 interface {} = u1

中,f_u2的第一个指针f_u2.tab指向 user类型的描述表,第二个指针f_u2.data指向一个新分配的地址,里面有u1的只读复制品。

为了验证上面的结论,有如下代码:

package main
import (
"fmt"
"unsafe"

)

//定义一个interface,要求有两个方法

type Notifier interface {
notify()
change(str1, str2  string) 

}

//定义一个类型,它的指针类型实现了Notifier interface要求的两个方法

type user struct {
name string
email string
}
func (u *user)  notify() {
fmt.Printf("Sending user email %s<%s>\n", u.name, u.email)
}
func (u *user) change(str1, str2 string) {
u.name = str1
u.email = str2
}

//这个类型和interface变量的一样,有两个指针。等下用这类型来打印一些interface的信息
type iface struct {
itab, data uintptr
}

func main() {
u1 := user{name:"agan", email:"aaa@163"}
var f_u1 Notifier = &u1 <<<因为user的指针类型才实现了Notifier interface,所以这里是&u1
var f_u2 interface {} = u1 <<interface{} 不要实现什么方法,可以接受任何类型

 

        //打印一些各自的初始值

fmt.Println(u1)
fmt.Println(f_u1)
fmt.Println(f_u2)

      //<<打印出&u1, &u1.name, &u1.email

fmt.Printf("u1:%p name addr:%p, email addr:%p \n",&u1, &u1.name, &u1.email)

        //将&f_u1强制转换成 *iface,这样我们就可以看那两个指针了

ia := *(*iface)(unsafe.Pointer(&f_u1))

fmt.Printf("ia data:%x \n", ia.data)

       //将 &f_u2强制转换成 *iface,因为给f_u2赋值的是 user的变量,而不是user的指针,看看和f_u1的区别

ib := *(*iface)(unsafe.Pointer(&f_u2))
fmt.Printf("ib data:%x \n", ib.data)

        //这里通过 f_u1调用了 *user的notify函数

f_u1.notify()

         //因为f_u2是 user类型赋值给interface {}的,它没有实现notify方法,所以下面编译报错

//f_u2.notify()

 

        //我们通过u1的change函数改变u1的内容。

u1.change("agan1",  "bbbb@163")

//这里调用f_u1.change去改变f_u1的内容,看看u1的内容会不会被改变。

f_u1.change("agan22222222222222", "cccc@163")
fmt.Println(u1)
fmt.Println(f_u1)
fmt.Println(f_u2)


f_u1.notify()
//f_u2.notify()
}

下面是运行结果:

 

root@ubuntu:my_go# go run interface.go 

//初始时各自的值:

{agan aaa@163}

&{agan aaa@163}

//这里f_u2的data指向 u1的一个只读拷贝

{agan aaa@163}

//看看地址

u1:0xc42000a180 name addr:0xc42000a180, email addr:0xc42000a190 
ia data:c42000a180 <<<f_u1的data指针和 &u1一样,说明f_u1.data是 &u1的一个拷贝。

ib data:c42000a1a0 <<f_u2的data指针和 &u1的不一样,但是打印的f_u2和 u1值一样,说明f_u2的data指向u1值的一个拷贝

//我们通过f_u1.notify()正确的调用了 *u1.notify()函数。并且值是f_u1.data指向的地址,即u1。

Sending user email agan<aaa@163>

{agan22222222222222 cccc@163} <<<u1的值被改变了
&{agan22222222222222 cccc@163}
{agan aaa@163}<<<<f_u2还是原来的,没有被改变

这里原来的代码是:

u1.change("agan1",  "bbbb@163")

 

f_u1.change("agan22222222222222", "cccc@163")

fmt.Println(u1)<<我们可以看到u1的值被f_ui.change 改变了。
fmt.Println(f_u1)

fmt.Println(f_u2) <<但是在打印f_u2时,它的值还是原来的,没有跟着u1和f_u2一起改变。所以f_u2.data指向的是另一个地址

 

//这里再次调用f_u1.notify() 它打印改变后的值

Sending user email agan22222222222222<cccc@163>

在f_u1改变u1值的方法中,我们其实可以不用f_u1.change()方法,而是使用如下代码:

  //f_u1.change("agan22222222222222", "cccc@163")
  f_u1.(*user).name = "agan44444" <<<<这里我们将f_u1通过f_u1.(*user)断言为*user的对象,f_u1.(*user)这个返回一个 *user临时对象,也就是将f_u1.data这个指针当作 *user类型来访问,然后.name就会指向u1的name 域,从而修改了u1.name。注意这里我们不是修改临时对象,而是临时对象指针指向的name。

  //f_u2.(user).name = "agan55555 <<这里因为f_u2.(user)返回的是一个临时的user对象,f_u2.(user).name 访问的是一个临时对象的name,这里是想去修改一个临时对象,不能给临时对象赋值,所以这里编译会出错。

看看我们的运行结果:

root@ubuntu:my_go# go run interface.go 
{agan aaa@163}
&{agan aaa@163}
{agan aaa@163}
u1:0xc42000a180 name addr:0xc42000a180, email addr:0xc42000a190 
ia data:c42000a180 
ib data:c42000a1a0 
Sending user email agan<aaa@163>
{agan44444 bbbb@163} <<< f_u1.(*user).name = "agan44444" 方法照样可以修改u1的东西
&{agan44444 bbbb@163}
{agan aaa@163}
Sending user email agan44444<bbbb@163><<这里调用f_u1.notify()方法,使用的是新的name。

f_u1.(*user)这是对interface的一个类型推断,它返回的不是一个类型,而是一个对象。*user类型的对象。如果开始给f_u1赋值的时候不是 *user的类型,这里会发生panic错误。

即如果f_u1.(fmt.Stringer),这里就会发生panic了。

我们可以用interface的类型推断,结合switch语句来执行一些东西。因为Notifier interface,这类型的一个变量可以接受任何实现了那两个方法的类型的 变量。但是我们可能后边需要根据不同的类型来做一些不同的动作,所以这里需要类型推断。

一般代码写法是:

switch v := f_u1.(type) {

case nil:

case fmt.Stringer:

case func() string:

case *user: <<这里就是本例的类型,我们可以在这个case下面进行一些特别的操作。

default:

}

到此这篇关于“golang之interface底层原理”的文章就介绍到这了,更多文章或继续浏览下面的相关文章,希望大家以后多多支持JQ教程网!

您可能感兴趣的文章:
golang 初始化并赋值_Golang | 既是接口又是类型,interface是什么神仙用法?
Golang interface 接口要点梳理
【GoLang】golang底层数据类型实现原理
golang之interface底层原理
真的理解go interface了吗?
golang 反射_Go进阶:反射3定律
golang类型转换与类型断言
golang 面试题(十三)interface内部结构和nil详解
图解 Go 反射实现原理
Go语言interface详解

[关闭]
~ ~