溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

Golang中的反射規(guī)則是什么

發(fā)布時間:2022-12-15 09:44:09 來源:億速云 閱讀:115 作者:iii 欄目:編程語言

這篇文章主要介紹“Golang中的反射規(guī)則是什么”的相關(guān)知識,小編通過實際案例向大家展示操作過程,操作方法簡單快捷,實用性強,希望這篇“Golang中的反射規(guī)則是什么”文章能幫助大家解決問題。

簡單來看反射是什么

簡單來看,反射就是在程序運行時期對程序本身進(jìn)行訪問和修改的能力,例如在程序運行時,可以修改程序的字段名稱,字段值,還可以給程序提供接口訪問的信息等等

這是 Go 語言中提供的一種機制,我們可以在 Go 語言公共庫中可以看到很多關(guān)于 reflect 的使用位置

例如常用的 fmt 包,常用的 json 序列化和反序列化,自然前面我們說到的 gorm 庫自然也是使用了反射的

Golang中的反射規(guī)則是什么

Golang中的反射規(guī)則是什么

可是我們一般為什么要使用反射呢?

根據(jù)反射的能力,自然是因為我們提供的接口并不知道傳入的數(shù)據(jù)類型會是什么樣的, 只有當(dāng)程序運行的時候才知道具體的數(shù)據(jù)類型

但是我們編碼的時候又期望去校驗程序運行時傳入的類型會是什么樣的(例如 json 的序列化)并對其這種具體的數(shù)據(jù)進(jìn)行操作,這個時候,咱們就需要用到反射的能力了

所以對于使用到反射的地方,你都能看到 interface{} 是不是就不奇怪了呢?

正是因為不確定傳入的數(shù)據(jù)類型會是什么樣的,所以才設(shè)計成 interface{} 

先關(guān)注反射的規(guī)則

首先關(guān)注反射的三個重要的定律,知道規(guī)則之后,我們按照規(guī)則玩就不會有什么問題,只有當(dāng)我們不清楚規(guī)則,總是觸發(fā)條款的時候,才會出現(xiàn)奇奇怪怪的問題

  • 反射是可以將 接口類型的變量 轉(zhuǎn)換成 反射類型的對象

  • 反射可以將 反射類型的對象 轉(zhuǎn)換成 接口類型的變量

  • 我們在運行時要去修改的 反射類型的對象 ,那么要求這個對象對應(yīng)的值是要可寫的

對于上述 3 個規(guī)則也是比較好理解,還記的之前我們說過的 unsafe 包里面的指針嗎?

都是將我們常用的數(shù)據(jù)類型,轉(zhuǎn)換成包(例如 unsafe包,或者 reflect 包)里面的指定數(shù)據(jù)類型,然后再按照包里面的規(guī)則進(jìn)行修改數(shù)據(jù)

相當(dāng)于,換個馬甲,就可以進(jìn)行不同的操作了

關(guān)注使用案例并靈活運用

一般咱們先會基本的應(yīng)用,再去研究他的原理,研究他為什么可以這樣用,慢慢的才能理解的更加深刻

對于定律一,將 接口類型的變量 轉(zhuǎn)換成 反射類型的對象

實際上此處說的 接口類型的變量 我們可以傳入任意數(shù)據(jù)類型的變量,例如 int, float, string ,map, slice, struct 等等

反射類型的對象 這里就可以理解成 reflect 反射包中的 reflect.Type reflect.Value 對象,可以通過 reflect 包中提供的 TypeOfValueOf 函數(shù)得到

其中 reflect.Type 實際上是一個 interface ,他里面包含了各種接口需要進(jìn)行實現(xiàn),它里面提供了關(guān)于類型相關(guān)的信息

其中如下圖可以查看到 reflect.Type 的所有方法,其中

  • 綠色的 是所有數(shù)據(jù)類型都是可以調(diào)用的

  • 紅色的是 函數(shù)類型數(shù)據(jù)可以調(diào)用的

  • 黑色的是 Map,數(shù)組 Array,通道 Chan,指針 Ptr 或者 切片Slice 可以調(diào)用的

  • 藍(lán)色的是結(jié)構(gòu)體調(diào)用的

  • 黃色的是通道 channel 類型調(diào)用的

Golang中的反射規(guī)則是什么

reflect.Value 實際上是一個 struct,根據(jù)這個 struct 還關(guān)聯(lián)了一組方法,這里面存放了數(shù)據(jù)類型和具體的數(shù)據(jù),通過查看其數(shù)據(jù)結(jié)構(gòu)就可以看出

type Value struct {
   typ *rtype
   ptr unsafe.Pointer
   flag
}

看到此處的 unsafe.Pointer 是不是很熟悉,底層自然就可以將 unsafe.Pointer 轉(zhuǎn)換成 uintptr,然后再修改其數(shù)據(jù)后,再轉(zhuǎn)換回來,對于 Go 指針不太熟悉的可以查看這篇文章:

  • GO 中的指針?

寫一個簡單的 demo 就可以簡單的獲取到變量的數(shù)據(jù)類型和值

func main() {   var demoStr string = "now reflect"
   fmt.Println("type:", reflect.TypeOf(demoStr))
   fmt.Println("value:", reflect.ValueOf(demoStr))
}

Golang中的反射規(guī)則是什么

對于定律二,將 反射類型的對象 轉(zhuǎn)換成 接口類型的變量

我們可以通過將 reflect.Value 類型轉(zhuǎn)換成我們具體的數(shù)據(jù)類型,因為 reflect.Value 中有對應(yīng)的 typ *rtype 以及 ptr unsafe.Pointer

例如我們可以 通過 reflect.Value 對象的 interface() 方法來處理

Golang中的反射規(guī)則是什么

func main() {   var demoStr string = "now reflect"
   fmt.Println("type:", reflect.TypeOf(demoStr))
   fmt.Println("value:", reflect.ValueOf(demoStr))   var res string
   res = reflect.ValueOf(demoStr).Interface().(string)
   fmt.Println("res == ",res)
}

Golang中的反射規(guī)則是什么

對于定律三,修改反射類型的對象

首先我們看上書的 demo 代碼,傳入 TypeOfValueOf 的變量實際上也是一個拷貝,那么如果期望在反射類型的對象中修改其值,那么就需要拿到具體變量的地址然后再進(jìn)行修改,前提是這個變量是可寫的

舉個例子你就能明白

func main() {
   var demoStr string = "now reflect"
   v := reflect.ValueOf(demoStr)
   fmt.Println("is canset ", v.CanSet())
   //v.SetString("hello world")   // 會panic
   }

Golang中的反射規(guī)則是什么

可以先調(diào)用 reflect.Value 對象的 CanSet 查看是否可寫,如果是可寫的,我們再寫,如果不可寫就不要寫了,否則會 panic

Golang中的反射規(guī)則是什么

那么傳入變量的地址就可以修改了??

Golang中的反射規(guī)則是什么

傳入地址的思路沒有毛病,但是我們?nèi)ピO(shè)置值的方式有問題,因此也會出現(xiàn)上述的 panic 情況

此處仔細(xì)看能夠明白,反射的對象 v 自然是不可修改的,我們應(yīng)該找到 reflect.Value 里面具體具體的數(shù)據(jù)指針,那么才是可以修改的,可以使用 reflect.Value Elem 方法

Golang中的反射規(guī)則是什么

稍微復(fù)雜一點的

看上了上述案例可能會覺得那么簡單的案例,一演示就 ok,但是工作中一用就崩潰,那自然還是沒有融會貫通,說明還沒有消化好,再來一個工作中的例子

  • 一個結(jié)構(gòu)體里面有 map,map 中的 key 是 string,value 是 []string

  • 需求是訪問 結(jié)構(gòu)體中 hobby 字段對應(yīng)的 map key 為 sport 的切片的第1 個元素,并將其修改為 hellolworld

type RDemo struct {
   Name  string
   Age   int
   Money float32
   Hobby map[string][]string
}

func main() {
   tmp := &RDemo{
      Name:  "xiaomiong",
      Age:   18,
      Money: 25.6,
      Hobby: map[string][]string{
         "sport": {"basketball", "football"},
         "food":  {"beef"},
      },
   }

   v := reflect.ValueOf(tmp).Elem()  // 拿到結(jié)構(gòu)體對象
   h := v.FieldByName("Hobby")    // 拿到 Hobby 對象
   h2 := h.MapKeys()[0]    // 拿到 Hobby 的第 0 個key
   fmt.Println("key1 name == ",h2.Interface().(string))

   sli := h.MapIndex(h2)    // 拿到 Hobby 的第 0 個key對應(yīng)的對象
   str := sli.Index(1)      // 拿到切片的第 1 個對象
   fmt.Println(str.CanSet())

   str.SetString("helloworld")
   fmt.Println("tmp == ",tmp)
}

Golang中的反射規(guī)則是什么

可以看到上述案例運行之后有時可以運行成功,有時會出現(xiàn) panic 的情況,相信細(xì)心的 xdm 就可以看出來,是因為 map 中的 key 是 無序的導(dǎo)致的,此處也提醒一波,使用 map 的時候要注意這一點

看上述代碼,是不是就能夠明白咱們使用反射去找到對應(yīng)的數(shù)據(jù)類型,然后按照數(shù)據(jù)類型進(jìn)行處理數(shù)據(jù)的過程了呢

有需要的話,可以慢慢的去熟練反射包中涉及的函數(shù),重點是要了解其三個規(guī)則,對象轉(zhuǎn)換方式,訪問方式,以及數(shù)據(jù)修改方式

反射原理

那么通過上述案例,可以知道關(guān)于反射中數(shù)據(jù)類型和數(shù)據(jù)指針對應(yīng)的值是相當(dāng)重要的,不同的數(shù)據(jù)類型能夠用哪些函數(shù)這個需要注意,否則用錯直接就會 panic

TypeOf

來看 TypeOf 的接口中涉及的數(shù)據(jù)結(jié)構(gòu)

Golang中的反射規(guī)則是什么

Golang中的反射規(guī)則是什么

在 reflect 包中 rtype 是非常重要的,Go 中所有的類型都會包含這個結(jié)構(gòu),所以咱們反射可以應(yīng)用起來,結(jié)構(gòu)如下

// rtype must be kept in sync with ../runtime/type.go:/^type._type.
type rtype struct {
   size       uintptr
   ptrdata    uintptr
   hash       uint32 
   tflag      tflag
   align      uint8
   fieldAlign uint8
   kind       uint8
   equal     func(unsafe.Pointer, unsafe.Pointer) bool
   gcdata    *byte 
   str       nameOff
   ptrToThis typeOff
}

其中可以看到此處的 rtype 的結(jié)構(gòu)保持和 runtime/type.go 一致 ,都是關(guān)于數(shù)據(jù)類型的表示,以及對應(yīng)的指針,關(guān)于這一塊的說明和演示可以查看文末的 interface{} 處的內(nèi)容

ValueOf

ValueOf 的源碼中,我們可以看到,重要的是 emptyInterface 結(jié)構(gòu)

Golang中的反射規(guī)則是什么

Golang中的反射規(guī)則是什么

Golang中的反射規(guī)則是什么

// emptyInterface is the header for an interface{} value.type emptyInterface struct {
   typ  *rtype
   word unsafe.Pointer
}復(fù)制代碼

emptyInterface 結(jié)構(gòu)中有 rtype 類型的指針, word 自然是對應(yīng)的數(shù)據(jù)的地址了

reflect.Value 對象中的方法也是非常的多,用起來和上述說到的 reflect.Type 接口中的功能類似

關(guān)于源碼中涉及到的方法,就不再過多的贅述了,更多的還是需要自己多多實踐才能體會的更好

殊不知,此處的 reflect.Value 也是可以轉(zhuǎn)換成 reflect.Type ,可以查看源碼中 reflect\value.gofunc (v Value) Type() Type {

其中   reflect.Value  ,reflect.Type ,和任意數(shù)據(jù)類型 可以相互這樣來轉(zhuǎn)換

如下圖:

Golang中的反射規(guī)則是什么

關(guān)于“Golang中的反射規(guī)則是什么”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識,可以關(guān)注億速云行業(yè)資訊頻道,小編每天都會為大家更新不同的知識點。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI