golang interface 与 反射

本文详细探讨了Go语言接口(interface)的使用场景、数据结构及其原理,包括带方法和空接口的区别、函数参数成本、类型转换和断言。同时,文章分析了反射的用途、实现原理以及与interface的关系,解释了动态类型、动态分发和类型断言的性能损耗,并提供了反射性能评估的测试结果。
摘要由CSDN通过智能技术生成


base go 1.13.5

golang interface 使用场景

这里先简单描述一下 interface 的使用场景。 我们通常有两种方式使用interface,一种是带方法的interface,一种是空interface。

我们一般用带方法的interface作为一个通用的抽象。用空的interface{} 来作为一种泛型使用。

具体的使用姿势的形式上,一般也就是作为函数入参,返回值,属性域等等。

除了要会用、用对以外,我觉得有必要搞清楚内部原理。比如作为函数入参,返回值,值和指针接受者的函数调用等的性能损耗。

golang interface 数据结构

interface变量前面说了有两种,一种是带方法的,一种是不带方法的。编译器会自动映射成底层的两种结构:iface 和 eface。区别在于 iface 描述的接口包含方法,而 eface 则是不包含任何方法的空接口:interface{}。

下面看一下源码的定义: runtime/runtime2.go

type iface struct {
   
	tab  *itab
	data unsafe.Pointer
}

type eface struct {
   
	_type *_type
	data  unsafe.Pointer
}

// 描述带方法的interface的类型信息以及接口信息
type itab struct {
   
	inter *interfacetype
	_type *_type
	hash  uint32 // copy of _type.hash. Used for type switches.
	_     [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
}

// 描述interface存储的实际对象的类型信息
type _type struct {
   
	size       uintptr
	ptrdata    uintptr // size of memory prefix holding all pointers
	hash       uint32
	tflag      tflag
	align      uint8
	fieldalign uint8
	kind       uint8
	alg        *typeAlg
	// 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
}

从eface和iface的定义可知道,interface的portal层的定义实际上是2个指针,一个类型相关的信息,一个是指向实际存储对象的数据指针。也就是16个字节。itab的fun 字段放置和接口方法对应的具体数据类型的方法地址,实现接口调用方法的动态分派,一般在每次给接口赋值发生转换时会更新此表,或者直接拿缓存的 itab。

另外,你可能会觉得奇怪,为什么 fun 数组的大小为 1,要是接口定义了多个方法可怎么办?实际上,这里存储的是第一个方法的函数指针,如果有更多的方法,在它之后的内存空间里继续存储。从汇编角度来看,通过增加地址就能获取到这些函数指针,没什么影响。顺便提一句,这些方法是按照函数名称的字典序进行排列的。

再看一下 interfacetype 类型,它描述的是接口的类型:

type interfacetype struct {
   
	typ     _type
	pkgpath name
	mhdr    []imethod
}

可以看到,它包装了 _type 类型,_type 实际上是描述 Go 语言中各种数据类型的结构体。我们注意到,这里还包含一个 mhdr 字段,表示接口所定义的函数列表, pkgpath 记录定义了接口的包名。

下面用一张图描述 iface 的全貌:
在这里插入图片描述
下面可以看一个实例:

package main

import "fmt"

func main() {
   
	x := 100
	var inter interface{
   } = x
	fmt.Println(inter)

	g := Gopher{
   "Go"}
	var c coder = g
	fmt.Println(c)
}

type coder interface {
   
	code()
	debug()
}

type Gopher struct {
   
	language string
}

func (p Gopher) code() {
   
	fmt.Printf("I am coding %s language\n", p.language)
}

func (p Gopher) debug() {
   
	fmt.Printf("I am debuging %s language\n", p.language)
}

通过 go tool compile -S 输出汇编代码,可以看到,main 函数里调用了两个函数:

func convT64(val uint64) (x unsafe.Pointer)
func convTstring(val string) (x unsafe.Pointer) 

这里编译器可以自动识别数据的类型,并转换成对应的值。

上面的convTXXX函数定义在 runtime/iface.go 里面,这个文件里面有一段注释:

// The conv and assert functions below do very similar things.
// The convXXX functions are guaranteed by the compiler to succeed.
// The assertXXX functions may fail (either panicking or returning false,
// depending on whether they are 1-result or 2-result).
// The convXXX functions succeed on a nil input, whereas the assertXXX
// functions fail on a nil input.

下面的conv和assert函数做的事情非常类似。
编译器保证了convXXX函数的成功。
assertXXX函数可能失败(panic或返回false,这取决于它们是1-结果,还是2-结果)。
convXXX函数在nil输入时成功,而assertXXX则失败

这里列出所有的函数:

//下面的这些方法是将指定的类型转换成interface类型,但是下面的这些方法返回的仅仅是返回data指针

// 转换对象成一个 interface{}
func convT2E(t *_type, elem unsafe.Pointer) (e eface)
// 转换uint16成一个interface的data指针
func convT16(val uint16) (x unsafe.Pointer)
// 转换uint32成一个interface的data指针
func convT32(val uint32) (x unsafe.Pointer)
// 转换uint64成一个interface的data指针
func convT64(val uint64) (x unsafe.Pointer)
// 转换string成一个interface的data指针
func convTstring(val string) (x unsafe.Pointer)
// 转换slice成一个interface的data指针
func convTslice(val []byte) (x unsafe.Pointer)
// 转换t类型的元素到interface{}, 这里的t不是指针类型
func convT2Enoptr(t *_type, elem unsafe.Pointer) (e eface) 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值