這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
Go能實現介面的動態性和反射的基礎是:編譯期為運行時提供了類型資訊。interface底層有兩種結構,上一節講了帶方法的iface,這一節補充不帶方法的eface結構。
interface之eface
Go中的任何對象都可以表示為interface{}。它扮演的角色與C中的void*差不多,區別在於interface{}中包含有類型資訊,可以實現反射。
eface資料結構描述:gcdata
域用於記憶體回收,size
描述類型的大小,hash
表示資料的hash值,align
是對齊,fieldalign
是這個資料嵌入結構體時的對齊,kind
是枚舉值。alg
是函數指標的數組,儲存了hash/equal/print/copy
四個函數操作。uncommentType
指向這個類型的方法集。
type eface struct {_type *_typedata unsafe.Pointer}type _type struct {size uintptrptrdata uintptr // size of memory prefix holding all pointershash uint32_unused uint8align uint8fieldalign uint8kind uint8alg *typeAlggcdata *byte_string *stringx *uncommontypeptrto *_typezero *byte // ptr to the zero value for this type}
運行時convT2E
T2E的轉換 賦值給空interface{},運行時會調用conv類函數convT2E
。與convT2I
一樣,向interface的類型轉換var i interface{} = u
都存在記憶體拷貝。看iface.go源碼時發現convI2I
等介面轉換時沒有分配記憶體和拷貝資料,原因可能是Go介面內部的data域,並不開放途徑讓外部修改,所以介面之間轉換可以用同一塊記憶體。
func convT2E(t *_type, elem unsafe.Pointer, x unsafe.Pointer) (e interface{}) {ep := (*eface)(unsafe.Pointer(&e)) ...if x == nil {x = newobject(t)}typedmemmove(t, x, elem)ep._type = tep.data = x}return}
下面的例子中修改u並不影響i interface的記憶體資料,因為i在賦值時通過convT2E對u進行了拷貝。這也是反射非指標變數時無法直接改變變數資料的原因,因為反射會先把變數轉成interface類型,拿到的是變數的副本。
u := User{1, "Tom"}var i interface{} = uu.id = 2u.name = "Jack"// u {2, "Jack"}// i.(User) {1, "Tom"}
nil的理解
未初始化的interface類型,指標,函數,slice,cannel和map都是nil的。對於interface比較特殊,只有eface的type和data都是nil,或者iface的type和data都是nil時,interface{}才是nil。
type Duck interface{ Walk()}var i interface{} // nilvar d Duck // nilvar v *T // nili = v // (*T)(nil) not nil
type assertion
嚴格來說Go並不支援泛型程式設計,但通過interface可實現泛型程式設計,後面reflect淺析中有個通過reflect實現泛型的例子。interface像其它類型轉換的時候一般需要斷言。下面只給出了eface的例子,當然也可以通過斷言來判斷某個類型是否實現了某個介面。
func do(v interface{}) {n, ok := v.(int)if !ok {...}}func doswitch(i interface{}) {switch v := i.(type) {case int: ...}}
對應的go源碼在iface.go當中。assertE2T
過程判斷了eface的type欄位是否和目標type相等,相等則還需要拷貝資料。assertI2T
也要拷貝資料,不過他比較的是iface.tab._type與目標type是否一致。
func assertE2T(t *_type, e interface{}, r unsafe.Pointer) {ep := (*eface)(unsafe.Pointer(&e))if ep._type == nil {panic(&TypeAssertionError{"", "", *t._string, ""})}if ep._type != t {panic(&TypeAssertionError{"", *ep._type._string, *t._string, ""})}if r != nil {if isDirectIface(t) {writebarrierptr((*uintptr)(r), uintptr(ep.data))} else {typedmemmove(t, r, ep.data)}}}
reflect淺析
反射機制提供了檢查儲存在介面變數中的[類型 值]對的機制。根據Laws Of ReflectionGo的反射可以總結三點,即反射可以從interface中擷取reflect對象;同時可以通過Interface()
方法恢複reflect對象為一個interface;如果要修改反射對象,該對象必須是settable
的。
TypeOf與ValueOf實現
擷取反射對象的實現,是基於對interface底層資料的操作。首先對象經過了convT2E
,然後emptyInterface直接指向了eface的type欄位。typeOf
返回的Type是介面,在類型_type上實現了很多操作。valueOf
返回的就是Value結構體,它包含了資料域和type域資訊。
func TypeOf(i interface{}) Type {eface := *(*emptyInterface)(unsafe.Pointer(&i))return toType(eface.typ)}func ValueOf(i interface{}) Value {if i == nil {return Value{}}escapes(i)return unpackEface(i)}func unpackEface(i interface{}) Value {e := (*emptyInterface)(unsafe.Pointer(&i))t := e.typif t == nil {return Value{}}f := flag(t.Kind())if ifaceIndir(t) {f |= flagIndir}return Value{t, unsafe.Pointer(e.word), f}}
type Type interface{ ...Method(int) MethodMethodByName(string) (Method, bool)NumMethod() intNumField()Field(i) StructFieldFieldByName(name string) (StructField, bool) ...}type Value struct {typ *rtypeptr unsafe.Pointerflag}
通過Type擷取Struct Field資訊
可以通過reflect擷取類型執行個體的結構體資訊,比如每個field的名字類型或標籤。
// A StructField describes a single field in a struct.type StructField struct {Name string // Name is the field name.PkgPath stringType Type // field typeTag StructTag // field tag stringOffset uintptr // offset within struct, in bytesIndex []int // index sequence for Type.FieldByIndexAnonymous bool // is an embedded field}type Person struct {Name string `json:"name"`Age int `json:"age"`}func main() {nino := Person{"nino", 27}t := reflect.TypeOf(nino)n := t.NumField()for i := 0; i < n; i++ {fmt.Println(t.Field(i).Name, t.Field(i).Type, t.Field(i).Tag)}}// Name string json:"name"// Age int json:"age"
通過Value實現泛型
為瞭解決method接受不同類型的slice為入參,可以用反射來完成。對於可記長度和可隨機訪問的類型,可以通過v.Len()
和v.Index(i)
擷取他們的第幾個元素。
v.Index(i).Interface()將reflect.Value反射回了interface類型
func method(in interface{}) (ok bool) {v := reflect.ValueOf(in)if v.Kind() == reflect.Slice {ok = true} else {return false}num := v.Len()for i := 0; i < num; i++ {fmt.Println(v.Index(i).Interface())}return ok}func main() {s := []int{1, 3, 5, 7, 9}b := []float64{1.2, 3.4, 5.6, 7.8}method(s)method(b)}
通過Elem修改reflect對象值
對LawsOfReflect第三點的理解 reflect.ValueOf
如果直接傳入x,則v是x的一個副本的reflect對象。修改v的值並不會作用到x上。p是指向x的指標的reflect對象,修改p的值是在修改指標的指向,同樣不會作用到x上,因此也是CanNotSet的。只有通過p.Elem()
相當於擷取了*p的reflect對象,這時才能使用v.SetFloat(7.2)
來對原始的資料進行修改。
var x float64 = 3.4v := reflect.ValueOf(x) // can not setp := reflect.ValueOf(&x) // can not sete := p.Elem() // can set