日本免费高清视频-国产福利视频导航-黄色在线播放国产-天天操天天操天天操天天操|www.shdianci.com

學無先后,達者為師

網站首頁 編程語言 正文

Go語言學習教程之反射的示例詳解_Golang

作者:任沫 ? 更新時間: 2022-11-17 編程語言

介紹

reflect包實現運行時反射,允許一個程序操作任何類型的對象。典型的使用是:取靜態類型interface{}的值,通過調用TypeOf獲取它的動態類型信息,調用ValueOf會返回一個表示運行時數據的一個值。本文通過記錄對reflect包的簡單使用,來對反射有一定的了解。本文使用的Go版本:

$ go version
go version go1.18 darwin/amd64

在了解反射之前,先了解以下幾個概念:

  • 靜態類型:每個變量都有一個靜態類型,這個類型是在編譯時(compile time)就已知且固定的。
  • 動態類型:接口類型的變量還有一個動態類型,是在運行時(run time)分配給變量的值的一個非接口類型。(除非分配給變量的值是nil,因為nil沒有類型)。
  • 空接口類型interface{} (別名any)表示空的方法集,它可以是任何值的類型,因為任何值都滿足有0或多個方法(有0個方法一定是任何值的子集)。
  • 一個接口類型的變量存儲一對內容:分配給變量的具體的值,以及該值的類型描述符??梢允疽庑缘乇硎緸?code>(value, type)對,這里的type是具體的類型,而不是接口類型。

反射的規律

1. 從接口值到反射對象的反射

在基本層面,反射只是檢測存儲在接口變量中的(value, type)對的一種機制。

可以使用reflect包的?reflect.ValueOfreflect.TypeOf方法,獲取接口變量值中的(value, type)對,類型分別為reflect.Valuereflect.Type

(1)TypeOf方法:

func TypeOf(i any) Type

TypeOf返回表示i的動態類型的反射Type。如果inil,那么返回nil。

(2)ValueOf方法:

func ValueOf(i any) Value

ValueOf返回一個新的Value,初始化為存儲在接口i中的具體值。ValueOf(nil) 會返回零Value。這個零Value是反射對象中表示沒有值的Value

var a interface{} = 1
var b interface{} = 1.11
var c string = "aaa"

// 將接口類型的變量運行時存儲的具體的值和類型顯示地獲取到
fmt.Println("type:", reflect.TypeOf(a))   // type: int
fmt.Println("value:", reflect.ValueOf(a)) // value: 1

fmt.Println("type:", reflect.TypeOf(nil))   // type: <nil>
fmt.Println("value:", reflect.ValueOf(nil)) // value: <invalid reflect.Value>

fmt.Println("type:", reflect.TypeOf(b))   // type: float64
fmt.Println("value:", reflect.ValueOf(b)) // value: 1.11

fmt.Println("type:", reflect.TypeOf(c))   // type: string
fmt.Println("value:", reflect.ValueOf(c)) // value: aaa

reflect.Value 的?Type方法,返回一個?reflect.Value的類型。

reflect.ValueString方法,將reflect.Value的底層值作為字符串返回。

fmt.Printf使用了反射:

  • %T使用reflect.TypeOf,拿到變量的動態類型。
  • %v深入到?reflect.Value內部拿到變量具體的值。
var a interface{} = 1

fmt.Println("type:", reflect.ValueOf(a).Type())     // type: int
fmt.Println("string:", reflect.ValueOf(a).String()) // string: <int Value>
fmt.Printf("type: %T \n", a)                        // type: int
fmt.Printf("string: %v \n", a)                      // string: 1

Type?和?Value都有一個?Kind 方法,返回一個表示存儲的項的類型的常量。?比如Uint,?Float64,?Slice等。

Value的類似IntFloat這種名稱的方法能夠獲取存儲在內部的值。

Value 的“getter”(取值) 和 “setter”(設置值)會對能保存該值的最大類型進行操作。比如對于所有有符號整數,都是int64。

var a interface{} = 1
var b interface{} = 1.11

reflectA := reflect.ValueOf(a)
fmt.Println("kind: ", reflectA.Kind()) // kind:  int

reflectIntA := reflectA.Int()               // 返回的是 能存儲有符號整數的最大類型 的值
reflectFloatB := reflect.ValueOf(b).Float() // 返回的是 能存儲浮點數的最大類型 的值

var a1 int64 = reflectIntA
var b1 float64 = reflectFloatB

// var a2 int32 = reflectIntA // 會報錯:cannot use reflectIntA (variable of type int64) as int32 value in variable 
// var b2 float32 = reflectFloatB // 會報錯:cannot use reflectFloatB (variable of type float64) as float32 value in variable 
fmt.Println("a1: ", a1, "b1: ", b1) // a1:  1 b1:  1.11

2. 從反射對象到接口值的反射

像物理反射一樣,Go 中的反射產生了它自己的逆。可以使用reflect.ValueInterface方法還原一個接口值。Interface() 將類型和值信息打包回一個接口表示。

Interface方法:

func (v Value) Interface() (i any)

Interfacev的當前值作為一個interface{}返回。它等同于:

var i interface{} = (v 的底層值)

練習代碼:

var a interface{} = 1

reflectA := reflect.ValueOf(a)

var a3 interface{} = reflectA.Interface()
var a4 int = reflectA.Interface().(int) // 使用接口值的類型斷言

fmt.Println(a3, a4) // 1 1 ,因為fmt.Println接收接口類型interface{}的參數,使用 reflect.Value 拿到具體的值,所以打印出運行時的具體結果

3. 要修改反射對象,該值一定是可設置的

可設置性是reflect.Value的一個屬性,表示一個反射對象可以修改用于創建該反射對象的實際存儲??稍O置性可以通過CanSet方法獲得。如果對不可設置的reflect.Value調用Set方法,就會報錯。

使用reflect.Value 類型的Elem方法能通過指針間接尋址得到一個可設置性為真的reflect.Value

var d float64 = 2.222

fmt.Println(reflect.ValueOf(d).CanSet()) // false

reflectD := reflect.ValueOf(&d).Elem()
fmt.Println(reflectD.CanSet()) // true
reflectD.SetFloat(3.33)

fmt.Println(d, reflectD) // 3.33 3.33

reflect.ValueOf(d) 是通過復制d中的內容得到的reflect.Value類型的值,它復制的內容存放的內存地址 和d的值存放的內存地址是不同的。所以不能通過它來修改d中原本存儲的內容。

上面的代碼中可以看到,調用SetFloat(3.33)之后,反射對象reflectD和創建該反射對象的d都發生了改變。

使用反射修改結構體的字段:

type T struct {
    A int
    B string
}
t := T{111, "xxx"}
s := reflect.ValueOf(&t).Elem()

typeOfT := s.Type()
for i := 0; i < s.NumField(); i++ { // NumField 返回結構體中的字段數量
    f := s.Field(i)
    fmt.Printf("%d: %s %s = %v\n",
        i,
        typeOfT.Field(i).Name, // 獲取第i個字段的名稱
        f.Type(),              // 獲取第i個字段的類型
        f.Interface(),         // 將第i個字段轉換回接口類型的值
    )
    // 0: A int = 111
    // 1: B string = xxx
}

s.Field(0).SetInt(222)      // 設置結構體的第一個字段的值
s.Field(1).SetString("yyy") // 設置結構體的第二個字段的值
fmt.Println(t)              // {222 yyy}

上述練習代碼都在一個reflect.go文件中,練習時在終端執行go run reflect.go運行該文件。

原文鏈接:https://juejin.cn/post/7097534989029343246

欄目分類
最近更新