golang中反射與接口的關係

golang中interface底層分析文中分析了接口的底層原理。其中接口的內部結構分兩種一種是iface接口,就是有方法的接口,另外一種是eface是空接口。無論是哪一種都有兩個字段:data、_type 表明接口變量的數據和變量類型信息。那它和反射類型有什麼關係嗎?今天的文章就是分析接口變量和反射變量的關係。linux

環境:go version go1.12.5 linux/amd64git

1 類型方法 reflect.TypeOf(interface{})

示例1代碼以下圖: github

圖片.png

輸出Igolang

變量x的類型是I,那將x傳入TypeOf()函數以後 Name()函數是如何獲取到變量x的類型信息的呢? 接下來咱們一步一步分析,第12行代碼的Name()函數是如何獲取到類型I的。bash

看一下TypeOf(interface)函數的實現:函數

func TypeOf(i interface{}) Type {
	eface := *(*emptyInterface)(unsafe.Pointer(&i))
	return toType(eface.typ)
}
複製代碼

咱們發現TypeOf的參數是接口類型,就是說變量x的副本被包裝成了runtime/runtime2.go中定義的eface(空接口)。而後將eface強制轉換成了emptyInterface,以下是reflect和runtime包下定義兩個空接口:post

//reflect/type.go
type emptyInterface struct {
	typ  *rtype
	word unsafe.Pointer
}

//runtime/runtime2.go
type eface struct {
	_type *_type
	data  unsafe.Pointer
}
複製代碼

發現和runtime包中的空接口很像,emptyInterface.word,runtime.eface字段類型是相同的。那就看看rtype和_type是否相同呢?ui

//reflect/type.go
type rtype struct {
	size       uintptr
	ptrdata    uintptr  // number of bytes in the type that can contain pointers
	hash       uint32   // hash of type; avoids computation in hash tables
	tflag      tflag    // extra type information flags
	align      uint8    // alignment of variable with this type
	fieldAlign uint8    // alignment of struct field with this type
	kind       uint8    // enumeration for C
	alg        *typeAlg // algorithm table
	gcdata     *byte    // garbage collection data
	str        nameOff  // string form
	ptrToThis  typeOff  // type for pointer to this type, may be zero
}

//runtime/type.go
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
}
複製代碼

徹底同樣因此就能夠毫無顧慮轉換了。 也就是說emptyInterface.rtype結構體裏已經有x的類型信息了。接下來繼續看Name()函數是如何獲取到類型的字符串信息的: Type(interface{})函數裏有個toType()函數,去看一下:this

//reflect/type.go
func toType(t *rtype) Type {
	if t == nil {
		return nil
	}
	return t
}
複製代碼

上面代碼是將*rtype直接轉換成了Type類型了,那Type類型是啥?spa

//reflect/type.go
type Type interface {
......
    Name() string
......
}
複製代碼

其實Type是個接口類型。

那*rtype確定實現了此接口中的方法,其中就包括Name()方法。找到了Name()的實現函數以下。若是不先看Name()的實現,其實也能猜到:就是從*rtype類型中定位數據獲取數據並返回給調用者的過程,由於*rtype裏面有包含值變量類型等信息。

func (t *rtype) Name() string {
	if t.tflag&tflagNamed == 0 {
		return ""
	}
	s := t.String()
	i := len(s) - 1
	for i >= 0 {
		if s[i] == '.' {
			break
		}
		i--
	}
	return s[i+1:]
}
複製代碼

重點看一下t.String()

func (t *rtype) String() string {
	s := t.nameOff(t.str).name()
	if t.tflag&tflagExtraStar != 0 {
		return s[1:]
	}
	return s
}
複製代碼

再重點看一下nameOff():

func (t *rtype) nameOff(off nameOff) name {
	return name{(*byte)(resolveNameOff(unsafe.Pointer(t), int32(off)))}
}
複製代碼

從名字能夠猜想出Off是Offset的縮寫(這個函數裏面的具體邏輯就探究了)進行偏移從而獲得對應內存地址的值。 String()函數中的name()函數以下:

func (n name) name() (s string) {
	if n.bytes == nil {
		return
	}
	b := (*[4]byte)(unsafe.Pointer(n.bytes))

	hdr := (*stringHeader)(unsafe.Pointer(&s))
	hdr.Data = unsafe.Pointer(&b[3])
	hdr.Len = int(b[1])<<8 | int(b[2])
	return s
}
複製代碼

name()函數的邏輯是根據nameOff()返回的*byte(就是類型信息的首地址)計算出字符串的Data和Len位置,而後經過返回值&s包裝出stringHeader(字符串原型)並將Data,Len賦值給字符串原型,從而將返回值s賦值。

總結 : 普通的變量 => 反射中Type類型 => 獲取變量類型信息 。

1,變量副本包裝成空接口runtime.eface

2,將runtime.eface轉換成reflat.emptyInterface(結構都同樣)。

3,將*emptyInterface.rtype 轉換成 reflect.Type接口類型(包裝成runtime.iface結構體類型)。

4,接口類型變量根據runtime.iface.tab.fun找到reflat.Name()函數。

5,reflect.Name()根據*rtype結構體str(nameoff類型)找到偏移量。

6,根據偏移量和基地址(基地址沒有在*rtype中,這塊先略過)。找到類型內存塊。

7,包裝成stringHeader類型返回給調用者。

其實核心就是將runtime包中的eface結構體數據複製到reflect包中的emptyInterface中而後在從裏面獲取相應的值類型信息。

refact.Type接口裏面的其餘方法就不在在這裏說了,核心思想就是圍繞reflat.emptyInterface中的數據進行查找等操做。

2 值方法 reflect.ValueOf(interface{})

package main
import (
	"reflect"
	"fmt"
)
func main() {
	var a = 3
	v := reflect.ValueOf(a)
	i := v.Interface()
	z := i.(int)
	fmt.Println(z)
}
複製代碼

看一下reflect.ValueOf()實現:

func ValueOf(i interface{}) Value {
	....
	return unpackEface(i)
}
複製代碼

返回值是Value類型:

type Value struct {
	typ *rtype
	ptr unsafe.Pointer
	flag //先忽略
}
複製代碼

Value是個結構體類型,包含着值變量的類型和數據指針。

func unpackEface(i interface{}) Value {
	e := (*emptyInterface)(unsafe.Pointer(&i))

	t := e.typ
	if t == nil {
		return Value{}
	}
	f := flag(t.Kind())
	if ifaceIndir(t) {
		f |= flagIndir
	}
	return Value{t, e.word, f}
}
複製代碼

具體實現是在unpackEface(interface{})中:

e := (*emptyInterface)(unsafe.Pointer(&i))
複製代碼

和上面同樣從*runtime.eface轉換成*reflect.emptyInterface了。 最後包裝成Value:

return Value{t, e.word, f}
複製代碼

繼續看一下示例代碼:

i := v.Interface()
複製代碼

的實現:

func (v Value) Interface() (i interface{}) {
	return valueInterface(v, true)
}

func valueInterface(v Value, safe bool) interface{} {
	......
	return packEface(v)
}

func packEface(v Value) interface{} {
	t := v.typ
	var i interface{}
	e := (*emptyInterface)(unsafe.Pointer(&i))
	switch {
	case ifaceIndir(t):
		if v.flag&flagIndir == 0 {
			panic("bad indir")
		}
               //將值的數據信息指針賦值給ptr
		ptr := v.ptr
		if v.flag&flagAddr != 0 {
			c := unsafe_New(t)
			typedmemmove(t, c, ptr)
			ptr = c
		}
                //爲空接口賦值
		e.word = ptr 
	case v.flag&flagIndir != 0:
		e.word = *(*unsafe.Pointer)(v.ptr)
	default:
		e.word = v.ptr
	}
        //爲空接口賦值
	e.typ = t
	return i
}
複製代碼

最終調用了packEface()函數,從函數名字面意思理解是打包成空接口。 邏輯是:從value.typ信息包裝出reflect.emptyInterface結構體信息,而後將reflect.eface寫入i變量中,又由於i是interface{}類型,編譯器又會將i轉換成runtime.eface類型。

z := i.(int)
複製代碼

根據字面量int編譯器會從runtime.eface._type中查找int的值是否匹配,若是不匹配panic,匹配i的值賦值給z。

總結:從值變量 => value反射變量 => 接口變量:

1,包裝成value類型。

2,從value類型中獲取rtype包裝成reflect.emptyInterface類型。

3,reflect.eface編譯器轉換成runtime.eface類型。

4,根據程序z :=i(int) 從runtime.eface._type中查找是否匹配。

5,匹配將值賦值給變量z。

總結:Value反射類型轉interface{}類型核心仍是reflet.emptyInterface與runtime.eface的相互轉換。

參考:

Golang反射包的實現原理(The Laws of Reflection)

譯|interface 和反射的關係

相關文章
相關標籤/搜索