golang中反射與接口的關(guān)系

golang中interface底層分析文中分析了接口的底層原理。其中接口的內(nèi)部結(jié)構(gòu)分兩種一種是iface接口,就是有方法的接口,另一種是eface是空接口。不管是哪種都有兩個(gè)字段:data、_type 代表接口變量的數(shù)據(jù)和變量類型信息。那它和反射類型有什么關(guān)系嗎?今天的文章就是分析接口變量和反射變量的關(guān)系。

環(huán)境:go version go1.12.5 linux/amd64

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

示例1代碼如下圖:


圖片.png

輸出I

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

看一下TypeOf(interface)函數(shù)的實(shí)現(xiàn):

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

我們發(fā)現(xiàn)TypeOf的參數(shù)是接口類型,就是說變量x的副本被包裝成了runtime/runtime2.go中定義的eface(空接口)。然后將eface強(qiáng)制轉(zhuǎn)換成了emptyInterface,如下是reflect和runtime包下定義兩個(gè)空接口:

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

//runtime/runtime2.go
type eface struct {
    _type *_type
    data  unsafe.Pointer
}

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

//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
}

完全一樣所以就可以毫無顧慮轉(zhuǎn)換了。
也就是說emptyInterface.rtype結(jié)構(gòu)體里已經(jīng)有x的類型信息了。接下來繼續(xù)看Name()函數(shù)是如何獲取到類型的字符串信息的:
Type(interface{})函數(shù)里有個(gè)toType()函數(shù),去看一下:

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

上面代碼是將*rtype直接轉(zhuǎn)換成了Type類型了,那Type類型是啥?

//reflect/type.go
type Type interface {
......
    Name() string
......
}

其實(shí)Type是個(gè)接口類型。

那*rtype肯定實(shí)現(xiàn)了此接口中的方法,其中就包括Name()方法。找到了Name()的實(shí)現(xiàn)函數(shù)如下。如果不先看Name()的實(shí)現(xiàn),其實(shí)也能猜到:就是從*rtype類型中定位數(shù)據(jù)獲取數(shù)據(jù)并返回給調(diào)用者的過程,因?yàn)?code>*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:]
}

重點(diǎn)看一下t.String()

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

再重點(diǎn)看一下nameOff():

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

從名字可以猜測(cè)出Off是Offset的縮寫(這個(gè)函數(shù)里面的具體邏輯就探究了)進(jìn)行偏移從而得到對(duì)應(yīng)內(nèi)存地址的值。
String()函數(shù)中的name()函數(shù)如下:


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()函數(shù)的邏輯是根據(jù)nameOff()返回的*byte(就是類型信息的首地址)計(jì)算出字符串的Data和Len位置,然后通過返回值&s包裝出stringHeader(字符串原型)并將Data,Len賦值給字符串原型,從而將返回值s賦值。

總結(jié) :
普通的變量 => 反射中Type類型 => 獲取變量類型信息 。
1,變量副本包裝成空接口runtime.eface。
2,將runtime.eface轉(zhuǎn)換成reflat.emptyInterface(結(jié)構(gòu)都一樣)。
3,將*emptyInterface.rtype 轉(zhuǎn)換成 reflect.Type接口類型(包裝成runtime.iface結(jié)構(gòu)體類型)。
4,接口類型變量根據(jù)runtime.iface.tab.fun找到reflat.Name()函數(shù)。
5,reflect.Name()根據(jù)*rtype結(jié)構(gòu)體str(nameoff類型)找到偏移量。
6,根據(jù)偏移量和基地址(基地址沒有在*rtype中,這塊先略過)。找到類型內(nèi)存塊。
7,包裝成stringHeader類型返回給調(diào)用者。

其實(shí)核心就是將runtime包中的eface結(jié)構(gòu)體數(shù)據(jù)復(fù)制到reflect包中的emptyInterface中然后在從里面獲取相應(yīng)的值類型信息。

refact.Type接口里面的其他方法就不在在這里說了,核心思想就是圍繞reflat.emptyInterface中的數(shù)據(jù)進(jìn)行查找等操作。

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()實(shí)現(xiàn):

func ValueOf(i interface{}) Value {
    ....
    return unpackEface(i)
}

返回值是Value類型:

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

Value是個(gè)結(jié)構(gòu)體類型,包含著值變量的類型和數(shù)據(jù)指針。


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}
}

具體實(shí)現(xiàn)是在unpackEface(interface{})中:

    e := (*emptyInterface)(unsafe.Pointer(&i))

和上面一樣從*runtime.eface轉(zhuǎn)換成*reflect.emptyInterface了。
最后包裝成Value:

    return Value{t, e.word, f}

繼續(xù)看一下示例代碼:

    i := v.Interface()

的實(shí)現(xiàn):

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")
        }
               //將值的數(shù)據(jù)信息指針賦值給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
}

最終調(diào)用了packEface()函數(shù),從函數(shù)名字面意思理解是打包成空接口。
邏輯是:從value.typ信息包裝出reflect.emptyInterface結(jié)構(gòu)體信息,然后將reflect.eface寫入i變量中,又因?yàn)閕是interface{}類型,編譯器又會(huì)將i轉(zhuǎn)換成runtime.eface類型。

z := i.(int)

根據(jù)字面量int編譯器會(huì)從runtime.eface._type中查找int的值是否匹配,如果不匹配panic,匹配i的值賦值給z。

總結(jié):從值變量 => value反射變量 => 接口變量:
1,包裝成value類型。
2,從value類型中獲取rtype包裝成reflect.emptyInterface類型。
3,reflect.eface編譯器轉(zhuǎn)換成runtime.eface類型。
4,根據(jù)程序z :=i(int) 從runtime.eface._type中查找是否匹配。
5,匹配將值賦值給變量z。

總結(jié):Value反射類型轉(zhuǎn)interface{}類型核心還是reflet.emptyInterface與runtime.eface的相互轉(zhuǎn)換。

參考:
Golang反射包的實(shí)現(xiàn)原理(The Laws of Reflection)
譯|interface 和反射的關(guān)系

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容