溫馨提示×

溫馨提示×

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

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

go語言中反射三定律指的是什么

發(fā)布時間:2023-01-28 09:39:35 來源:億速云 閱讀:114 作者:iii 欄目:編程語言

這篇文章主要介紹“go語言中反射三定律指的是什么”,在日常操作中,相信很多人在go語言中反射三定律指的是什么問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”go語言中反射三定律指的是什么”的疑惑有所幫助!接下來,請跟著小編一起來學(xué)習(xí)吧!

反射三定律:1、反射可以將“接口類型變量”轉(zhuǎn)換為“反射類型對象”,這里反射類型指“reflect.Type”和 “reflect.Value”;2、反射可以將“反射類型對象”轉(zhuǎn)換為“接口類型變量”;3、如果要修改“反射類型對象”其值必須是“可寫的”。

在反射的世界里,我們擁有了獲取一個對象的類型,屬性及方法的能力。

go語言中反射三定律指的是什么

兩種類型:Type 和 Value

在 Go 反射的世界里,有兩種類型非常重要,是整個反射的核心,在學(xué)習(xí) reflect 包的使用時,先得學(xué)習(xí)下這兩種類型:

  • reflect.Type

  • reflect.Value

它們分別對應(yīng)著真實(shí)世界里的 type 和 value,只不過在反射對象里,它們擁有更多的內(nèi)容。

從源碼上來看,reflect.Type 是以一個接口的形式存在的

type Type interface {
    Align() int
    FieldAlign() int
    Method(int) Method
    MethodByName(string) (Method, bool)
    NumMethod() int
    Name() string
    PkgPath() string
    Size() uintptr
    String() string
    Kind() Kind
    Implements(u Type) bool
    AssignableTo(u Type) bool
    ConvertibleTo(u Type) bool
    Comparable() bool
    Bits() int
    ChanDir() ChanDir
    IsVariadic() bool
    Elem() Type
    Field(i int) StructField
    FieldByIndex(index []int) StructField
    FieldByName(name string) (StructField, bool)
    FieldByNameFunc(match func(string) bool) (StructField, bool)
    In(i int) Type
    Key() Type
    Len() int
    NumField() int
    NumIn() int
    NumOut() int
    Out(i int) Type
    common() *rtype
    uncommon() *uncommonType
}

而 reflect.Value 是以一個結(jié)構(gòu)體的形式存在,

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

同時它接收了很多的方法(見下表),這里出于篇幅的限制這里也沒辦法一一介紹。

Addr
Bool
Bytes
runes
CanAddr
CanSet
Call
CallSlice
call
Cap
Close
Complex
Elem
Field
FieldByIndex
FieldByName
FieldByNameFunc
Float
Index
Int
CanInterface
Interface
InterfaceData
IsNil
IsValid
IsZero
Kind
Len
MapIndex
MapKeys
MapRange
Method
NumMethod
MethodByName
NumField
OverflowComplex
OverflowFloat
OverflowInt
OverflowUint
Pointer
Recv
recv
Send
send
Set
SetBool
SetBytes
setRunes
SetComplex
SetFloat
SetInt
SetLen
SetCap
SetMapIndex
SetUint
SetPointer
SetString
Slice
Slice3
String
TryRecv
TrySend
Type
Uint
UnsafeAddr
assignTo
Convert

通過上一節(jié)的內(nèi)容(),我們知道了一個接口變量,實(shí)際上都是由一 pair 對(type 和 data)組合而成,pair 對中記錄著實(shí)際變量的值和類型。也就是說在真實(shí)世界里,type 和 value 是合并在一起組成 接口變量的。

而在反射的世界里,type 和 data 卻是分開的,他們分別由 reflect.Type 和 reflect.Value 來表現(xiàn)。

解讀反射的三大定律

Go 語言里有個反射三定律,是你在學(xué)習(xí)反射時,很重要的參考:

  • Reflection goes from interface value to reflection object.

  • Reflection goes from reflection object to interface value.

  • To modify a reflection object, the value must be settable.

翻譯一下,就是:

  • 反射可以將“接口類型變量” 轉(zhuǎn)換為“反射類型對象”;

  • 反射可以將 “反射類型對象”轉(zhuǎn)換為 “接口類型變量”;

  • 如果要修改 “反射類型對象” 其類型必須是 “可寫的”;

第一定律

Reflection goes from interface value to reflection object.

為了實(shí)現(xiàn)從接口變量到反射對象的轉(zhuǎn)換,需要提到 reflect 包里很重要的兩個方法:

  • reflect.TypeOf(i) :獲得接口值的類型

  • reflect.ValueOf(i):獲得接口值的值

這兩個方法返回的對象,我們稱之為反射對象:Type object 和 Value object。

go語言中反射三定律指的是什么

舉個例子,看下這兩個方法是如何使用的?

package main

import (
"fmt"
"reflect"
)

func main() {
    var age interface{} = 25

    fmt.Printf("原始接口變量的類型為 %T,值為 %v \n", age, age)

    t := reflect.TypeOf(age)
    v := reflect.ValueOf(age)

    // 從接口變量到反射對象
    fmt.Printf("從接口變量到反射對象:Type對象的類型為 %T \n", t)
    fmt.Printf("從接口變量到反射對象:Value對象的類型為 %T \n", v)

}

輸出如下

原始接口變量的類型為 int,值為 25 從接口變量到反射對象:Type對象的類型為 *reflect.rtype 
從接口變量到反射對象:Value對象的類型為 reflect.Value 復(fù)制代碼

如此我們完成了從接口類型變量到反射對象的轉(zhuǎn)換。

等等,上面我們定義的 age 不是 int 類型的嗎?第一法則里怎么會說是接口類型的呢?

關(guān)于這點(diǎn),其實(shí)在上一節(jié)(關(guān)于接口的三個 『潛規(guī)則』)已經(jīng)提到過了,由于 TypeOf 和 ValueOf 兩個函數(shù)接收的是 interface{} 空接口類型,而 Go 語言函數(shù)都是值傳遞,因此Go語言會將我們的類型隱式地轉(zhuǎn)換成接口類型。

原始接口變量的類型為 int,值為 25 
從接口變量到反射對象:Type對象的類型為 *reflect.rtype 
從接口變量到反射對象:Value對象的類型為 reflect.Value

第二定律

Reflection goes from reflection object to interface value.

和第一定律剛好相反,第二定律描述的是,從反射對象到接口變量的轉(zhuǎn)換。

go語言中反射三定律指的是什么

通過源碼可知, reflect.Value 的結(jié)構(gòu)體會接收 Interface 方法,返回了一個 interface{}  類型的變量(注意:只有 Value 才能逆向轉(zhuǎn)換,而 Type 則不行,這也很容易理解,如果 Type 能逆向,那么逆向成什么呢?

// Interface returns v's current value as an interface{}.
// It is equivalent to:
//    var i interface{} = (v's underlying value)
// It panics if the Value was obtained by accessing
// unexported struct fields.
func (v Value) Interface() (i interface{}) {
    return valueInterface(v, true)
}

這個函數(shù)就是我們用來實(shí)現(xiàn)將反射對象轉(zhuǎn)換成接口變量的一個橋梁。

例子如下

package main

import (
"fmt"
"reflect"
)

func main() {
    var age interface{} = 25

    fmt.Printf("原始接口變量的類型為 %T,值為 %v \n", age, age)

    t := reflect.TypeOf(age)
    v := reflect.ValueOf(age)

    // 從接口變量到反射對象
    fmt.Printf("從接口變量到反射對象:Type對象的類型為 %T \n", t)
    fmt.Printf("從接口變量到反射對象:Value對象的類型為 %T \n", v)

    // 從反射對象到接口變量
    i := v.Interface()
    fmt.Printf("從反射對象到接口變量:新對象的類型為 %T 值為 %v \n", i, i)

}

輸出如下

原始接口變量的類型為 int,值為 25 
從接口變量到反射對象:Type對象的類型為 *reflect.rtype 
從接口變量到反射對象:Value對象的類型為 reflect.Value 
從反射對象到接口變量:新對象的類型為 int 值為 25

當(dāng)然了,最后轉(zhuǎn)換后的對象,靜態(tài)類型為 interface{} ,如果要轉(zhuǎn)成最初的原始類型,需要再類型斷言轉(zhuǎn)換一下,關(guān)于這點(diǎn),我已經(jīng)在上一節(jié)里講解過了,你可以點(diǎn)此前往復(fù)習(xí):()。

i := v.Interface().(int)

至此,我們已經(jīng)學(xué)習(xí)了反射的兩大定律,對這兩個定律的理解,我畫了一張圖,你可以用下面這張圖來加強(qiáng)理解,方便記憶。

go語言中反射三定律指的是什么

第三定律

To modify a reflection object, the value must be settable.

反射世界是真實(shí)世界的一個『映射』,是我的一個描述,但這并不嚴(yán)格,因?yàn)椴⒉皇悄阍诜瓷涫澜缋锼龅氖虑槎紩€原到真實(shí)世界里。

第三定律引出了一個 settable (可設(shè)置性,或可寫性)的概念。

其實(shí)早在以前的文章中,我們就一直在說,Go 語言里的函數(shù)都是值傳遞,只要你傳遞的不是變量的指針,你在函數(shù)內(nèi)部對變量的修改是不會影響到原始的變量的。

回到反射上來,當(dāng)你使用 reflect.Typeof 和 reflect.Valueof 的時候,如果傳遞的不是接口變量的指針,反射世界里的變量值始終將只是真實(shí)世界里的一個拷貝,你對該反射對象進(jìn)行修改,并不能反映到真實(shí)世界里。

因此在反射的規(guī)則里

  • 不是接收變量指針創(chuàng)建的反射對象,是不具備『可寫性』的

  • 是否具備『可寫性』,可使用 CanSet() 來獲取得知

  • 對不具備『可寫性』的對象進(jìn)行修改,是沒有意義的,也認(rèn)為是不合法的,因此會報錯。

package main

import (
    "fmt"
    "reflect"
)

func main() {
    var name string = "Go編程時光"

    v := reflect.ValueOf(name)
    fmt.Println("可寫性為:", v.CanSet())
}

輸出如下

可寫性為: false

要讓反射對象具備可寫性,需要注意兩點(diǎn)

  • 創(chuàng)建反射對象時傳入變量的指針

  • 使用 Elem()函數(shù)返回指針指向的數(shù)據(jù)

完整代碼如下

package main

import (
    "fmt"
    "reflect"
)

func main() {
    var name string = "Go編程時光"
    v1 := reflect.ValueOf(&name)
    fmt.Println("v1 可寫性為:", v1.CanSet())

    v2 := v1.Elem()
    fmt.Println("v2 可寫性為:", v2.CanSet())
}

輸出如下

v1 可寫性為: false
v2 可寫性為: true

知道了如何使反射的世界里的對象具有可寫性后,接下來是時候了解一下如何對修改更新它。

反射對象,都會有如下幾個以 Set 單詞開頭的方法

go語言中反射三定律指的是什么

這些方法就是我們修改值的入口。

來舉個例子

package main

import (
    "fmt"
    "reflect"
)

func main() {
    var name string = "Go編程時光"
    fmt.Println("真實(shí)世界里 name 的原始值為:", name)

    v1 := reflect.ValueOf(&name)
    v2 := v1.Elem()

    v2.SetString("Python編程時光")
    fmt.Println("通過反射對象進(jìn)行更新后,真實(shí)世界里 name 變?yōu)椋?quot;, name)
}

輸出如下

真實(shí)世界里 name 的原始值為: Go編程時光
通過反射對象進(jìn)行更新后,真實(shí)世界里 name 變?yōu)椋?nbsp;Python編程時光

到此,關(guān)于“go語言中反射三定律指的是什么”的學(xué)習(xí)就結(jié)束了,希望能夠解決大家的疑惑。理論與實(shí)踐的搭配能更好的幫助大家學(xué)習(xí),快去試試吧!若想繼續(xù)學(xué)習(xí)更多相關(guān)知識,請繼續(xù)關(guān)注億速云網(wǎng)站,小編會繼續(xù)努力為大家?guī)砀鄬?shí)用的文章!

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

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

AI