Go part 7 反射

反射

反射是指在程序運行期間對程序自己進行訪問和修改的能力,(程序在編譯時,變量被轉換爲內存地址,變量名不會被編譯器寫入到可執行部分,在運行程序時,程序沒法獲取自身的信息)編程

支持反射的語言能夠在程序編譯期間將變量的反射信息,如字段名稱、類型等信息整合到可執行文件中,並給程序提供接口訪問反射信息,這樣就能夠在程序運行期間獲取類型的反射信息,而且有能力修改它們json

Go 程序在運行期間使用 reflect 包訪問程序的反射信息數組

像 Python,JavaScript 類動態語言,因爲自己的語法特性就可讓代碼運行期間訪問程序自身的值和類型信息,所以不須要反射系統函數

 

反射類型對象(reflect.Type)

使用 reflect.TypeOf() 函數能夠獲取變量的反射類型對象(reflect.Type)ui

typeOfTest := reflect.TypeOf(test)

 

經過反射獲取類型信息spa

經過反射類型對象能夠獲取自身的類型信息,使用 Name() 方法獲取類型名稱,使用 Kind() 方法獲取類型歸屬的種類 指針

package main
import (
	"fmt"
	"reflect"
)

func GetReflectInfo(a interface{}) {
	// 獲取變量 a 的類型對象,類型對象的類型是 reflect.Type
	var typeOfA reflect.Type = reflect.TypeOf(a)
	fmt.Printf("%T\n", typeOfA)
        // 打印類型名 和 種類
	fmt.Println(typeOfA.Name(), typeOfA.Kind())
}

func main() {
	GetReflectInfo("666")
}

運行結果:
*reflect.rtype
string string

發現類型和種類都是 string,很奇怪是否是,接着看下面的例子 ...對象

 

理解反射的類型(Type)和種類(Kind)blog

編程中,使用最多的是類型,但在反射中,當須要區分一個大品種的類型時,就會用到種類(Kind)索引

1)類型(Type)

類型指的是系統原生數據類型,如 int、string、bool、float32 等類型,以及使用 type 關鍵字自定義的類型,自定義類型須要指定類型名稱,例如,type A struct {},類型名就是 A

 

2)種類(Kind)

種類指的是對象歸屬的大品種,在 reflect 包中有以下定義:

type Kind uint

const (
    Invalid Kind = iota  // 非法類型
    Bool                 // 布爾型
    Int                  // 有符號整型
    Int8                 // 有符號8位整型
    Int16                // 有符號16位整型
    Int32                // 有符號32位整型
    Int64                // 有符號64位整型
    Uint                 // 無符號整型
    Uint8                // 無符號8位整型
    Uint16               // 無符號16位整型
    Uint32               // 無符號32位整型
    Uint64               // 無符號64位整型
    Uintptr              // 指針
    Float32              // 單精度浮點數
    Float64              // 雙精度浮點數
    Complex64            // 64位複數類型
    Complex128           // 128位複數類型
    Array                // 數組
    Chan                 // 通道
    Func                 // 函數
    Interface            // 接口
    Map                  // 映射
    Ptr                  // 指針
    Slice                // 切片
    String               // 字符串
    Struct               // 結構體
    UnsafePointer        // 底層指針
)

Map、Slice、Chan 屬於引用類型,使用起來相似於指針,可是在種類常量定義中仍然屬於獨立的種類,不屬於 Ptr

type A struct{} 結構體屬於 Struct 種類,*A 屬於 Ptr 種類

 

3)從自定義的類型對象中獲取類型名稱和類型種類(增強理解)

package main

import (
	"fmt"
	"reflect"
)

type Cat struct{}
type Enum int

func main(){
	var cat Cat = Cat{}
	var num Enum = 1

	//對 cat 變量使用反射
	var typeOfCat reflect.Type= reflect.TypeOf(cat)
	fmt.Println(typeOfCat.Name(), typeOfCat.Kind())

	//對 num 變量使用反射
	typeOfNum := reflect.TypeOf(num)
	fmt.Println(typeOfNum.Name(), typeOfNum.Kind())
}

運行結果:
Cat struct
Enum int

  

經過反射獲取指針指向的元素類型

對指針類型的變量獲取反射類型對象後,能夠經過 reflect.Elem() 方法獲取這個指針指向的元素類型,這個過程被稱之爲取元素,等效於對指針類型變量作了一個 * 操做

dmeo:cat 指針變量的反射類型對象的類型名稱是 空字符串(爲何不是 *cat),種類是 ptr,獲取值以後的類型名是 cat,種類是 struct

package main
import (
	"fmt"
	"reflect"
)

type Cat struct {}

func GetPointerReflectInfo(a interface{}) {
	//獲取指針類型的反射類型對象
	typeOfA := reflect.TypeOf(a)
	fmt.Printf("NameType:%T Name:%v Kind:%v\n", typeOfA.Name(), typeOfA.Name(), typeOfA.Kind())

	//取指針類型的元素
	typeOfA = typeOfA.Elem()
	fmt.Printf("Name:%v Kind:%v\n", typeOfA.Name(), typeOfA.Kind())
}

func main(){
	//建立 cat 類型的指針實例
	var cat *Cat = new(Cat)
	GetPointerReflectInfo(cat)
}

運行結果:
NameType:string Name: Kind:ptr
Name:Cat Kind:struct

  

經過反射獲取結構體的字段信息

變量經過 reflect.TypeOf() 函數獲取反射類型對象後,若是反射類型對象的種類是 struct,那麼能夠經過反射類型對象(reflect.Type)的 NumField() 和 Field() 等方法得到結構體字段的詳細信息,以結構體字段類型(StructField)返回

種類是 struct 的反射類型對象(reflect.Type)能夠經過調用下面的方法來獲取:

結構體成員(字段)訪問的方法列表
方法 說明
Field(i int) StructField 根據索引,返回索引對應的StructField。當值不是結構體或索引超界時發生宕機
NumField() int 返回結構體成員字段數量。當類型不是結構體或索引超界時發生宕機
FieldByName(name string) (StructField, bool) 根據給定字符串返回字符串對應的StructField。沒有找到時 bool 返回 false,當類型不是結構體或索引超界時發生宕機
FieldByIndex(index []int) StructField 多層成員訪問時,根據 []int 提供的每一個結構體的字段索引,返回StructField。沒有找到時返回零值。當類型不是結構體或索引超界時 發生宕機
FieldByNameFunc( match func(string) bool) (StructField,bool) 根據匹配函數匹配須要的字段。當值不是結構體或索引超界時發生宕機

  

結構體字段類型(StructField)也是一個結構體種類,裏面包含的字段有 Name、PkgPath、Type 等,StructField 的結構以下:

type StructField struct {
    Name string          // 字段名
    PkgPath string       // 字段路徑
    Type      Type       // 字段反射類型對象
    Tag       StructTag  // 字段的結構體標籤
    Offset    uintptr    // 字段在結構體中的相對偏移
    Index     []int      // Type.FieldByIndex中的返回的索引值
    Anonymous bool       // 是否爲匿名字段
}

 

demo:實例化一個結構體,並給字段賦值,而後獲取該結構體變量的反射類型對象(reflect.Type),而後調用 FieldByName() 方法查找結構體中指定 Name 的字段,最後直接獲取到了字段的結構體數據

package main
import (
	"fmt"
	"reflect"
)

type Cat struct {
	Name string
    Type int `json:"type" id:"100"`
}

func main(){
	//建立貓的結構體實例
	var cat Cat = Cat{"tom", 66}
	//獲取反射類型對象
	typeOfCat := reflect.TypeOf(cat)

	//經過索引遍歷結構體字段
	for i:=0; i<typeOfCat.NumField(); i++ {
		var catField reflect.StructField = typeOfCat.Field(i)
		fmt.Printf("%v, %v\n", catField.Name, catField.Tag)
	}

	//經過 Type 字段查找字段信息
	catTagField, ok := typeOfCat.FieldByName("Type")
	if ok {
		//從 Tag 字段中經過 Get() 方法獲取到指定的 tag,沒有取到默認爲 空 string
		fmt.Printf("'%v', '%v', '%v'\n", catTagField.Tag.Get("json"), catTagField.Tag.Get("id"), catTagField.Tag.Get("nil"))
	}
}

運行結果:
Name, 
Type, json:"type" id:"100"
'type', '100', ''

  

反射值對象(reflect.Value)

反射不只能夠獲取變量的類型信息,還能夠動態的獲取 和 修改變量的值

使用 reflect.ValueOf() 函數獲得變量的反射值對象(reflect.Value),進而獲取 和 修改變量的值

valueOfTest := reflect.ValueOf(test)

  

從反射值對象獲取值

相關文章
相關標籤/搜索