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详解