函数调用
- 传值:函数调用时会对参数进行拷贝,被调用方和调用方两者持有不相关的两份数据;
- 传引用:函数调用时会传递参数的指针,被调用方和调用方两者持有相同的数据,任意一方做出的修改都会影响另一方。
Go 语言选择了传值的方式,无论是传递基本类型、结构体还是指针,都会对传递的参数进行拷贝
- 通过堆栈传递参数,入栈的顺序是从右到左;
- 函数返回值通过堆栈传递并由调用者预先分配内存空间;
- 调用函数时都是传值,接收方会对入参进行复制再计算;
接口
接口也是 Go 语言中的一种类型,它能够出现在变量的定义、函数的入参和返回值中并对它们进行约束,不过 Go 语言中有两种略微不同的接口,一种是带有一组方法的接口,另一种是不带任何方法的 interface{}
Go 语言使用 iface
结构体表示第一种接口,使用 eface
结构体表示第二种空接口,两种接口虽然都使用 interface
声明,但是由于后者在 Go 语言中非常常见,所以在实现时使用了特殊的类型。
type eface struct { // 16 bytes _type *_type data unsafe.Pointer }
由于 interface{}
类型不包含任何方法,所以它的结构也相对来说比较简单,只包含指向底层数据和类型的两个指针。从上述结构我们也能推断出 — Go 语言中的任意类型都可以转换成 interface{}
类型。
另一个用于表示接口的结构体就是 iface
,这个结构体中有指向原始数据的指针 data
,不过更重要的是 itab
类型中的 tab
字段。
type iface struct { // 16 bytes tab *itab data unsafe.Pointer }
_type
是 Go 语言类型的运行时表示。下面是运行时包中的结构体,结构体包含了很多元信息,例如:类型的大小、哈希、对齐以及种类等。
type _type struct { size uintptr ptrdata uintptr hash uint32 tflag tflag align uint8 fieldAlign uint8 kind uint8 equal func(unsafe.Pointer, unsafe.Pointer) bool gcdata *byte str nameOff ptrToThis typeOff }
size
字段存储了类型占用的内存空间,为内存空间的分配提供信息;hash
字段能够帮助我们快速确定类型是否相等;equal
字段用于判断当前类型的多个对象是否相等,该字段是为了减少 Go 语言二进制包大小从typeAlg
结构体中迁移过来
itab
结构体是接口类型的核心组成部分,每一个 itab
都占 32 字节的空间,我们可以将其看成接口类型和具体类型的组合,它们分别用 inter
和 _type
两个字段表示
type itab struct { // 32 bytes inter *interfacetype _type *_type hash uint32 _ [4]byte fun [1]uintptr }
除了 inter
和 _type
两个用于表示类型的字段之外,上述结构体中的另外两个字段也有自己的作用:
hash
是对_type.hash
的拷贝,当我们想将interface
类型转换成具体类型时,可以使用该字段快速判断目标类型和具体类型_type
是否一致;fun
是一个动态大小的数组,它是一个用于动态派发的虚函数表,存储了一组函数指针。虽然该变量被声明成大小固定的数组,但是在使用时会通过原始指针获取其中的数据,所以fun
数组中保存的元素数量是不确定的
反射
三大法则
从 interface{} 变量可以反射出反射对象
从反射对象可以获取 interface{} 变量
要修改反射对象,其值必须可设置
由于 reflect.TypeOf、reflect.ValueOf 两个方法的入参都是 interface{} 类型,所以在方法执行的过程中发生了类型转换
v.Interface().(type)
从接口值到反射对象:
从基本类型到接口类型的类型转换;
从接口类型到反射对象的转换;
从反射对象到接口值:
反射对象转换成接口类型;
通过显式类型转换变成原始类型;
修改变量方式
调用 reflect.ValueOf 函数获取变量指针;
调用 reflect.Value.Elem 方法获取指针指向的变量;
调用 reflect.Value.SetInt 方法更新变量的值
emptyInterface
type
word
reflect.TypeOf 函数的实现原理其实并不复杂,它只是将一个 interface{} 变量转换成了内部的 emptyInterface 表示,然后从中获取相应的类型信息。
reflect.ValueOf 实现也非常简单,在该函数中我们先调用了 reflect.escapes 函数保证当前值逃逸到堆上,然后通过 reflect.unpackEface 方法从接口中获取 Value 结构体
reflect.unpackEface 函数会将传入的接口转换成 emptyInterface 结构体,然后将具体类型和指针包装成 Value 结构体并返回