深入理解Go語言(01): interface源碼分析


分析接口的賦值,反射,斷言的實現原理

版本:golang v1.12


interface底層使用2個struct表示的:efaceiface

一:接口類型分為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結構體表示非空接口:

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的公共描述,類似的還有maptypearraytypechantype…其都是各個結構的公共描述,可以理解為一種外在的表現信息。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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM