分析接口的賦值,反射,斷言的實現原理
版本:golang v1.12
interface底層使用2個struct表示的:eface
和iface
一:接口類型分為2個
1. 空接口
//比如
var i interface{}
2. 帶方法的接口
//比如
type studenter interface {
GetName() string
GetAge() int
}
二:eface 空接口定義
空接口通過eface
結構體定義實現,位於src/runtime/runtime2.go
type eface struct {
_type *_type //類型信息
data unsafe.Pointer //數據信息,指向數據指針
}
可以看到上面eface包含了2個元素,一個是_type,指向對象的類型信息,一個 data,數據指針
三:_type 結構體
_type
位於 src/runtime/type.go
_type
是go里面所有類型的一個抽象,里面包含GC,反射,大小等需要的細節,它也決定了data如何解釋和操作。
里面包含了非常多信息 類型的大小、哈希、對齊以及種類等自動。
所以不論是空eface
和非空iface
都包含 _type
數據類型
type _type struct {
size uintptr //類型大小
ptrdata uintptr //含有所有指針類型前綴大小
hash uint32 //類型hash值;避免在哈希表中計算
tflag tflag //額外類型信息標志
align uint8 //該類型變量對齊方式
fieldalign uint8 //該類型結構字段對齊方式
kind uint8 //類型編號
alg *typeAlg //算法表 存儲hash和equal兩個操作。map key便使用key的_type.alg.hash(k)獲取hash值
// 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 //gc數據
str nameOff // 類型名字的偏移
ptrToThis typeOff
}
_type 中的一些數據類型如下:
// typeAlg is 總是 在 reflect/type.go 中 copy或使用.
// 並保持他們同步.
type typeAlg struct {
// 算出該類型的Hash
// (ptr to object, seed) -> hash
hash func(unsafe.Pointer, uintptr) uintptr
// 比較該類型對象
// (ptr to object A, ptr to object B) -> ==?
equal func(unsafe.Pointer, unsafe.Pointer) bool
}
type nameOff int32
type typeOff int32
但是各個類型需要的類型描敘是不一樣的,比如chan,除了chan本身外,還需要描述其元素類型,而map則需要key類型信息和value類型信息等:
//src/runtime/type.go
type ptrtype struct {
typ _type
elem *_type
}
type chantype struct {
typ _type
elem *_type
dir uintptr
}
type maptype struct {
typ _type
key *_type
elem *_type
bucket *_type // internal type representing a hash bucket
keysize uint8 // size of key slot
valuesize uint8 // size of value slot
bucketsize uint16 // size of bucket
flags uint32
}
看上面的類型信息,第一個自動都是 _type
,接下來也定義了一堆類型所需要的信息(如子類信息),這樣在進行類型相關操作時,可通過一個字(typ *_type)即可表述所有類型,然后再通過_type.kind可解析出其具體類型,最后通過地址轉換即可得到類型完整的”_type樹”,參考reflect.Type.Elem()函數:
// reflect/type.go
// reflect.rtype結構體定義和runtime._type一致 type.kind定義也一致(為了分包而重復定義)
// Elem()獲取rtype中的元素類型,只針對復合類型(Array, Chan, Map, Ptr, Slice)有效
func (t *rtype) Elem() Type {
switch t.Kind() {
case Array:
tt := (*arrayType)(unsafe.Pointer(t))
return toType(tt.elem)
case Chan:
tt := (*chanType)(unsafe.Pointer(t))
return toType(tt.elem)
case Map:
tt := (*mapType)(unsafe.Pointer(t))
return toType(tt.elem)
case Ptr:
tt := (*ptrType)(unsafe.Pointer(t))
return toType(tt.elem)
case Slice:
tt := (*sliceType)(unsafe.Pointer(t))
return toType(tt.elem)
}
panic("reflect: Elem of invalid type")
}
四:沒有方法的interface賦值后內部結構
對於沒有方法的interface賦值后的內部結構是怎樣的呢?
可以先看段代碼:
import (
"fmt"
"strconv"
)
type Binary uint64
func main() {
b := Binary(200)
any := (interface{})(b)
fmt.Println(any)
}
輸出200,賦值后的結構圖是這樣的:
圖片來自:https://blog.csdn.net/i6448038/article/details/82916330
對於將不同類型轉化成type萬能結構的方法,是運行時的convT2E
方法,在runtime包中。
以上,是對於沒有方法的接口說明。
對於包含方法的函數,用到的是另外的一種結構,叫iface
五:iface 非空接口
iface
// runtime/runtime2.go
// 非空接口
type iface struct {
tab *itab
data unsafe.Pointer //指向原始數據指針
}
itab
itab結構體是iface不同於eface,比較關鍵的數據結構
// runtime/runtime2.go
// 非空接口的類型信息
type itab struct {
//inter 和 _type 確定唯一的 _type類型
inter *interfacetype // 接口自身定義的類型信息,用於定位到具體interface類型
_type *_type // 接口實際指向值的類型信息-實際對象類型,用於定義具體interface類型
hash int32 //_type.hash的拷貝,用於快速查詢和判斷目標類型和接口中類型是一致
_ [4]byte
fun [1]uintptr //動態數組,接口方法實現列表(方法集),即函數地址列表,按字典序排序
//如果數組中的內容為空表示 _type 沒有實現 inter 接口
}
屬性interfacetype
類似於_type,其作用就是interface的公共描述,類似的還有maptype
、arraytype
、chantype
…其都是各個結構的公共描述,可以理解為一種外在的表現信息。interfacetype源碼如下:
// runtime/type.go
// 非空接口類型,接口定義,包路徑等。
type interfacetype struct {
typ _type
pkgpath name
mhdr []imethod // 接口方法聲明列表,按字典序排序
}
// 接口的方法聲明,一種函數聲明的抽象
// 比如:func Print() error
type imethod struct {
name nameOff // 方法名
ityp typeOff // 描述方法參數返回值等細節
}
type nameOff int32
type typeOff int32
method 存的是func 的聲明抽象,而 itab 中的 fun 字段才是存儲 func 的真實切片。
非空接口(iface)本身除了可以容納滿足其接口的對象之外,還需要保存其接口的方法,因此除了data字段,iface通過tab
字段描述非空接口的細節,包括接口方法定義,接口方法實現地址,接口所指類型等。iface是非空接口的實現,而不是類型定義,iface的真正類型為interfacetype
,其第一個字段仍然為描述其自身類型的_type
字段。
六:iface整體結構圖
圖片來自:https://blog.csdn.net/i6448038/article/details/82916330
七:含有方法的interface賦值后的內部結構
含有方法的interface賦值后的內部結構是怎樣的呢?
package main
import (
"fmt"
"strconv"
)
type Binary uint64
func (i Binary) String() string {
return strconv.FormatUint(i.Get(), 10)
}
func (i Binary) Get() uint64 {
return uint64(i)
}
func main() {
b := Binary(200)
any := fmt.Stringer(b)
fmt.Println(any)
}
首先,要知道代碼運行結果為:200。
其次,了解到fmt.Stringer是一個包含String方法的接口。
type Stringer interface {
String() string
}
最后,賦值后接口Stringer的內部結構為:
八:參考:
https://wudaijun.com/2018/01/go-interface-implement/
https://blog.csdn.net/i6448038/article/details/82916330#comments