go interface
1 什么是接口
- 接口是一种协议, 一种规则, 规定了一些功能,具体怎么实现不关心
- 比如我们需要将数据进行存储读取,我们规定了读取保存功能,怎么实现不关心.
- 在现实中其实可以说我们很多时候经常说的都是”接口”
- 说打车, 车是一种实现了4个轮子能移动的接口, 不管是比亚迪还是宝马都行
- 说吃饭, 饭是一种实现了可以吃饱功能的接口,具体吃什么,还不清楚
- …
- 所以实际上在开发中我们应当 面向接口开发(实际在设计阶段,沟通中一直说的都是”接口”), 接口是一种抽象, 不关心具体怎么实现,这样以后更换具体实现就会比较方便 ## 基本操作
Tip
当我们定义一个新的类型时, 只要保证其方法集合与某个接口相同即可隐式实现该接口
type Animal interface {
Shout()
}
type Cat struct {
Age int
Name string
}
// 隐式地实现了Animal 接口
// 结构体实现接口
func (c Cat) Shout() {
fmt.Println("miao")
}
type Dog struct {
Age int
}
// 结构体指针实现接口
// 将结构体指针 当成一个整体, 这个东西实现了 方法Shout
func (d *Dog) Shout() {
fmt.Println("wang")
}
func TestSlice(t *testing.T) {
// 实际会做 类型是否实现接口 的检查
// 注意这个实际上就是一种类型转换
var a Animal = Cat{Age: 2, Name: "tom"}
a.Shout()
// 这样也是ok的,前面我们在结构体那里知道,编译器会自动给 Cat的同名方法生成一个*Cat的同名方法
// 这个同名方式 的作用就在这里
// 所以*Cat 实现了 Shout 方法,实现了 Animal接口
var a2 Animal = &Cat{Age: 3, Name: "jjj"}
a2.Shout()
// 结构体指针 *Dog 实现了Shout方法, 所以可以
var d Animal = &Dog{Age: 1}
d.Shout()
// 报错, 因为 Dog{Age: 1} 没有实现Shout() 方法
// var d2 Animal = Dog{Age: 1}
// 类型断言 方式一
cat, ok := a.(Cat)
if ok {
fmt.Println(cat.Age)
}
// 类型断言 方式二
switch a.(type) {
case Cat:
cat := a.(Cat)
fmt.Println(cat.Name)
}
}2 数据结构
2.1 接口与元数据
关于接口的思考
var animal Animal = Cat{Age: 2, Name: "tom"}- animal是一个类型为Animal接口的变量
- 这是一个类型转换的操作, 这句话看起来是个废话
- Cat 能否转换为 接口Animal, 是如何判断的?
- Cat 结构体是否实现了接口的方法
- animal可以转换为Cat 对象, 也就是说 接口变量animal必定存储了Cat对象数据的信息,以及Cat的类型信息
- 接口有方法, ==接口变量animal 必定存储了 接口定义的方法==
关于元数据的思考
- 我们知道 当我们定义 一个 int64 类型的变量时, 编译时编译器最终会根据代码定义的 int64的 “类型信息” 来分配多少内存的
- 这些 “类型信息” 我们能否得到呢?
- 在关于接口的思考中,因为 接口变量和结构体可以互相转换,所以接口变量必定存储了 结构体实际数据以及类型的信息
- 这个不就是我们想要的吗? 那么我们将变量转换成接口类型不就可以了吗!!! 这波推理可以!
- 编译后的可执行文件里会有这些信息吗?
- 没有, 通过二进制 的汇编指令 ,我们可以根据最终的一条条指令,得出指令里是没有所谓这些类型的概念
2.2 空接口 interface
Tip
- 前面我们说道只要实现了接口定义的方法, 就算实现了接口.
- 空接口 可以认为是没有方法的接口, 那么所有类型可以说都实现了该接口
- 那么所有的类型都可以转换为 空接口类型
空接口数据结构
type eface struct {
_type *_type // 类型信息
data unsafe.Pointer // 数据信息
}
type _type struct {
size uintptr // 这个类型需要占用的内存大小
ptrdata uintptr // size of memory prefix holding all pointers
hash uint32 // 类型的hash值, 用来类型比较和查找
tflag tflag // (1)
align uint8 // 类型的对齐边界
fieldAlign uint8 // 类型字段的对齐边界
kind uint8 //类型的分类 (2)
// 比较2个当前类型的变量是否相等
equal func(unsafe.Pointer, unsafe.Pointer) bool
gcdata *byte // gc 相关
str nameOff // 偏移, 可以通过这个得到 类型的名称等信息
ptrToThis typeOff // 同上, 得到对应指针类型的信息
}- tflag
type tflag uint8
const (
tflagUncommon tflag = 1 << 0
tflagExtraStar tflag = 1 << 1
tflagNamed tflag = 1 << 2
tflagRegularMemory tflag = 1 << 3
)- kind
const (
Invalid Kind = iota
Bool
Int
Int8
Int16
Int32
Int64
Uint
Uint8
Uint16
Uint32
Uint64
Uintptr
Float32
Float64
Complex64
Complex128
Array
Chan
Func
Interface
Map
Pointer
Slice
String
Struct
UnsafePointer
)
Caution
更多细节等,后续有时间再弄吧…
代码验证结构
package main
import (
// _ "test/pkg"
"fmt"
"unsafe"
)
type eface struct {
_type *_type // 类型信息
data unsafe.Pointer // 数据信息
}
type _type struct {
size uintptr // 这个类型需要占用的内存大小
ptrdata uintptr // size of memory prefix holding all pointers
hash uint32 // 类型的hash值, 用来类型比较和查找
tflag uint8 //
align uint8 // 类型的对齐边界
fieldAlign uint8 // 类型字段的对齐边界
kind uint8 //类型的分类
// 比较2个当前类型的变量是否相等
equal func(unsafe.Pointer, unsafe.Pointer) bool
gcdata *byte // gc 相关
str nameOff // 偏移, 可以通过这个得到 类型的名称等信息
ptrToThis int32 // 同上, 得到对应指针类型的信息
}
//go:linkname resolveNameOff runtime.resolveNameOff
func resolveNameOff(ptrInModule unsafe.Pointer, off nameOff) name
type Cat struct {
Age int
Name string
}
func (c Cat) Show() {
fmt.Println(11)
}
func (c Cat) pp() {
fmt.Println(11)
}
func main() {
var a int64 = 66
var b interface{} = a
c := *(*eface)(unsafe.Pointer(&b))
fmt.Println(*(*int64)(c.data)) // 66
fmt.Println(*c._type)
d := resolveNameOff(unsafe.Pointer(c._type), c._type.str)
fmt.Println(d.name())
fmt.Println("--自定义类型 (结构体) 的情况 呢?----")
cat := Cat{3, "tom"}
var catAny interface{} = cat
catEface := *(*eface)(unsafe.Pointer(&catAny))
fmt.Println(*catEface._type)
dd := resolveNameOff(unsafe.Pointer(catEface._type), catEface._type.str)
fmt.Println(dd.name())
// 结构体中字段的信息呢? 好像仅从eface 中没有.
// 我们看源码中的 这个方法
// func (t *_type) uncommon() *uncommontype
// 从中知道 struct 类型 有额外的信息存储在 *_type 指针指向的内存
catStructUncommon := *(*structUncommon)(unsafe.Pointer(catEface._type))
// 可自行验证
fmt.Println(catStructUncommon.fields[1].name.name())
fmt.Println(catStructUncommon.u)
}
type structUncommon struct {
structtype
u uncommontype
}
type defaultUncommon struct {
_type
u uncommontype
}
type uncommontype struct {
pkgpath nameOff
mcount uint16 // number of methods
xcount uint16 // number of exported methods
moff uint32 // offset from this uncommontype to [mcount]method
_ uint32 // unused
}
type structtype struct {
typ _type
pkgPath name
fields []structfield
}
type structfield struct {
name name
typ *_type
offset uintptr
}
type name struct {
bytes *byte
}
type nameOff int32
func (n name) name() string {
if n.bytes == nil {
return ""
}
i, l := n.readVarint(1)
return unsafe.String(n.data(1+i, "non-empty string"), l)
}
func (n name) readVarint(off int) (int, int) {
v := 0
for i := 0; ; i++ {
x := *n.data(off+i, "read varint")
v += int(x&0x7f) << (7 * i)
if x&0x80 == 0 {
return i + 1, v
}
}
}
func (n name) data(off int, whySafe string) *byte {
return (*byte)(add(unsafe.Pointer(n.bytes), uintptr(off), whySafe))
}
func add(p unsafe.Pointer, x uintptr, whySafe string) unsafe.Pointer {
return unsafe.Pointer(uintptr(p) + x)
}2.3 非空接口
type iface struct {
tab *itab
data unsafe.Pointer
}
type itab struct {
inter *interfacetype // 接口的元数据
_type *_type // 实际类型的元数据
hash uint32 // _type.hash的复制用来方便做类型断言
_ [4]byte
fun [1]uintptr // variable sized. fun[0]==0 means _type does not implement inter.
}
type interfacetype struct {
typ _type
pkgpath name
mhdr []imethod // 方法列表
}
type _type struct {
size uintptr
ptrdata uintptr // size of memory prefix holding all pointers
hash uint32
tflag tflag
align uint8
fieldAlign uint8
kind uint8
// function for comparing objects of this type
// (ptr to object A, ptr to object B) -> ==?
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
str nameOff
ptrToThis typeOff
}