类型系统
类型系统
如果我们自定义一个结构体类型T,并给它关联一个方法F1。我们知道变量t的内存布局只包含一个字符串变量,那它和F1之间怎样建立关联?
type T struct {
name string
}
func (t T) F1() {
fmt.Println(t.name)
}
func main() {
t := T{name: "eggo"}
t.F1()
}
由于编译阶段,编译器知道每种类型定义了哪些方法,之前我们也介绍过,方法本质上就是函数,只不过在调用时,接收者会作为第一个参数传入。所以通过变量t来调用方法F1时,编译器自然知道要调用哪个函数。
但是到了执行阶段,为了支持反射、接口动态派发、类型断言这些语言特性或机制,编译器会给每种类型生成对应的类型描述信息写入可执行文件,这些类型描述信息就是“类型元数据”。
在Go语言里,int、float、string、slice、map、interface等属于内置类型(built-in),而我们自己通过下面这三种方式定义的类型,都属于自定义类型。
type T1 int
type T2 struct {
name string
}
type T3 interface {
F1()
}
给内置类型定义方法是不被允许的,而接口类型是无效的方法接收者,所以我们只能给上面第一、第二种形式的自定义类型定义方法。
数据类型虽然很多,但是不管是内置类型还是自定义类型,它的“类型元数据”都是全局唯一的。这些类型元数据共同构成了Go语言的类型系统。
下面就把类型元数据展开,看看里面究竟是什么结构,有什么内容。
内置类型元数据
像类型名称,大小,对齐边界,是否为自定义类型等信息,是每个类型元数据都要记录的。所以被放到了runtime._type结构体中,作为每个类型元数据的Header。
type _type struct {
size uintptr
ptrdata uintptr
hash uint32
tflag tflag
align uint8
fieldalign uint8
kind uint8
alg *typeAlg
gcdata *byte
str nameOff
ptrToThis typeOff
}
在_type之后存储的是各类型额外需要描述的信息,例如slice的类型元数据在_type结构体后面,记录着一个*_type,指向其存储的元素的类型元数据。
type slicetype struct {
typ _type
elem *_type
}
如果是存储string的slice类型,这个指针就指向string类型的元数据。
再看看指针类型的元数据,它在_type后面也额外存储了一个*_type,指向指针类型指向的那个类型的元数据。
type ptrtype struct {
typ _type
elem *_type
}
内置类型的元数据都是这样的结构,可以到runtime包下的type.go查看struct、map等类型的元数据信息。
自定义类型元数据
然而如果是自定义类型,这后面还会有一个uncommontype结构体。
type uncommontype struct {
pkgpath nameOff
mcount uint16
_ uint16 // unused
moff uint32
}
- pkgpath 记录类型所在的包路径;
- mcount 记录了该类型关联到多少个方法;
- moff 记录的是这些方法的元数据组成的数组,相对于这个uncommontype结构体偏移了多少字节。方法元数据结构如下:
type method struct {
name nameOff
mtyp typeOff
ifn textOff
tfn textOff
}
例如,我们基于[]string定义一个新类型myslice,它就是一个自定义类型,可以给它定义两个方法Len和Cap。
myslice的类型元数据中,首先是slicetype类型描述信息,然后在后面加上uncommontype结构体。注意通过uncommontype这里记录的信息,我们就可以找到myslice的方法元数据列表了。
alias
现在我们可以利用类型元数据来描述下面这两种写法的不同了:
type MyType1 = int32
type MyType2 int32
MyType1这种写法,叫做给类型int32取别名,实际上MyType1和int32会关联到同一个类型元数据,属于同一种类型。rune和int32就是这样的关系。
而MyType2这种写法,属于基于已有类型创建新类型,MyType2会自立门户,拥有自己的类型元数据,即使MyType2相对于int32来说没有做任何改变,它们两个对应的类型元数据也已经不同了。
既然每种类型都有唯一对应的类型元数据,而类型定义的方法能通过类型元数据找到,那么,很多问题就变得好解释了,例如接下来的“接口”。
文章转载于: