方法调用
type T struct {
name string
}
func (t T) F1() {
fmt.Println(t.name)
}
func main() {
t := T{name: "eggo"}
t.F1()
}
方法调用本质上是函数调用。
自定义一个结构体类型T,并给它关联一个方法F1。当调用方法F1时,实际上就是T.F1(t)
。方法的接受者作为函数的第一个入参传入。
如何动态获取数据类型信息
Go内置类型
给内置类型定义方法是不允许的
- bool
- int、int8、int16、int32、int64、uint、uint8(byte)、uint16、uint32、uint64
- float32、float64
- complex64、complex128
- string
- rune
- error
- slice、map、func
自定义类型
接口类型是无效的方法接受者,因此不能给接口类型关联方法。(例如下面的Test方法)
type MyInt int
type T struct {
name string
}
type I interface {
Name() string
}
func (i I) Test() {
}
但是不管内置类型还是自定义类型,都有对应的类型描述信息。而且每种类型元数据都是全局唯一的,这些类型元数据共同构成了Go语言的类型系统。
类型元数据信息
runtime._type保存了类型的元数据信息,并且作为每个类型元数据的Header。
type _type struct {
size uintptr // 大小
ptrdata uintptr // 含有所有指针类型前缀大小
hash uint32 // 哈希值,用于快速比较两个类型是否相同
tflag tflag // 类型的特征标记
align uint8 // 作为整体变量存放时的对齐字节数
fieldalign uint8 // 当前结构字段的对齐字节数
kind uint8 // 基础类型枚举值和反射中的 Kind 一致,kind 决定了如何解析该类型
alg *typeAlg // 指向一个函数指针表,该表有两个函数,一个是计算类型 Hash 函数。另一个是比较两个类型是否相同的 equal 函数
gcdata *byte // GC 相关
str nameOff // 类型名称字符串在二进制文件段中的偏移量
ptrToThis typeOff // 类型元信息指针在二进制文件段中的偏移量
}
在_type之后保存的是各种类型额外需要的描述信息。
例如slice的类型元数据,在_type后有一个_type,指向其存储的元素的类型元数据。[]slice的类型元数据中的_type就指向string类型的元数据(stringtype)
type slicetype struct {
typ _type
elem *_type
}
自定义类型
自定义类型除了有_type和*_type之外,还有一个uncommontype结构体。
type uncommontype struct {
pkgpath nameOff // 包路径
mcount uint16 // 方法数量,记录了该类型关联了多少个方法
xcount uint16 // 可导出的方法数量
moff uint32 // 方法元数据组成的数组相对于uncommontype结构体偏移了多少字节
_ uint32 // unused
}
举例
自定义myslice类型,并为它关联两个方法。
type myslice []string
func (ms myslice) Len() {
fmt.Println(len(ms))
}
func (ms myslice) Cap() {
fmt.Println(cap(ms))
}
myslice的类型元数据如下:
首先是slice的类型元数据,后面是uncommontype记录的信息。假设uncommontype的地址为addrA,加上moff字节的偏移量,就是myslice关联的方法元数据数组。
类型定义和类型别名
类型定义
类型定义会基于已有类型创建新类型,MyType会自立门户拥有自己的元数据,即使MyType的类型元数据和int32的类型元数据没有任何改变。
类型别名
MyType2和int32会关联到同一个类型元数据。rune和int32就是这样的关系。