C語言中文網 目錄
首頁 > Go語言教程 > Go語言反射 閱讀:1,785

Go語言通過反射修改變量的值

使用 reflect.Value 對包裝的值進行修改時,需要遵循一些規則。如果沒有按照規則進行代碼設計和編寫,輕則無法修改對象值,重則程序在運行時會發生宕機。

判定及獲取元素的相關方法

使用 reflect.Value 取元素、取地址及修改值的屬性方法請參考下表。

反射值對象的判定及獲取元素的方法
方法名 備  注
Elem() Value 取值指向的元素值,類似于語言層*操作。當值類型不是指針或接口時發生宕 機,空指針時返回 nil 的 Value
Addr() Value 對可尋址的值返回其地址,類似于語言層&操作。當值不可尋址時發生宕機
CanAddr() bool 表示值是否可尋址
CanSet() bool 返回值能否被修改。要求值可尋址且是導出的字段

值修改相關方法

使用 reflect.Value 修改值的相關方法如下表所示。

反射值對象修改值的方法
Set(x Value) 將值設置為傳入的反射值對象的值
Setlnt(x int64) 使用 int64 設置值。當值的類型不是 int、int8、int16、 int32、int64 時會發生宕機
SetUint(x uint64) 使用 uint64 設置值。當值的類型不是 uint、uint8、uint16、uint32、uint64 時會發生宕機
SetFloat(x float64) 使用 float64 設置值。當值的類型不是 float32、float64 時會發生宕機
SetBool(x bool) 使用 bool 設置值。當值的類型不是 bod 時會發生宕機
SetBytes(x []byte) 設置字節數組 []bytes值。當值的類型不是 []byte 時會發生宕機
SetString(x string) 設置字符串值。當值的類型不是 string 時會發生宕機

以上方法,在 reflect.Value 的 CanSet 返回 false 仍然修改值時會發生宕機。

在已知值的類型時,應盡量使用值對應類型的反射設置值。

值可修改條件之一:可被尋址

通過反射修改變量值的前提條件之一:這個值必須可以被尋址。簡單地說就是這個變量必須能被修改。示例代碼如下:
package main

import (
    "reflect"
)

func main() {

    // 聲明整型變量a并賦初值
    var a int = 1024

    // 獲取變量a的反射值對象
    valueOfA := reflect.ValueOf(a)

    // 嘗試將a修改為1(此處會發生崩潰)
    valueOfA.SetInt(1)
}
程序運行崩潰,打印錯誤:

panic: reflect: reflect.Value.SetInt using unaddressable value

報錯意思是:SetInt 正在使用一個不能被尋址的值。從 reflect.ValueOf 傳入的是 a 的值,而不是 a 的地址,這個 reflect.Value 當然是不能被尋址的。將代碼修改一下,重新運行:
package main

import (
    "fmt"
    "reflect"
)

func main() {

    // 聲明整型變量a并賦初值
    var a int = 1024

    // 獲取變量a的反射值對象(a的地址)
    valueOfA := reflect.ValueOf(&a)

    // 取出a地址的元素(a的值)
    valueOfA = valueOfA.Elem()

    // 修改a的值為1
    valueOfA.SetInt(1)

    // 打印a的值
    fmt.Println(valueOfA.Int())
}
代碼輸出如下:
1

下面是對代碼的分析:
  • 第 14 行中,將變量 a 取值后傳給 reflect.ValueOf()。此時 reflect.ValueOf() 返回的 valueOfA 持有變量 a 的地址。
  • 第 17 行中,使用 reflect.Value 類型的 Elem() 方法獲取 a 地址的元素,也就是 a 的值。reflect.Value 的 Elem() 方法返回的值類型也是 reflect.Value。
  • 第 20 行,此時 valueOfA 表示的是 a 的值且可以尋址。使用 SetInt() 方法設置值時不再發生崩潰。
  • 第 23 行,正確打印修改的值。

提示

當 reflect.Value 不可尋址時,使用 Addr() 方法也是無法取到值的地址的,同時會發生宕機。雖然說 reflect.Value 的 Addr() 方法類似于語言層的&操作;Elem() 方法類似于語言層的*操作,但并不代表這些方法與語言層操作等效。

值可修改條件之一:被導出

結構體成員中,如果字段沒有被導出,即便不使用反射也可以被訪問,但不能通過反射修改,代碼如下:
package main

import (
    "reflect"
)

func main() {

    type dog struct {
            legCount int
    }
    // 獲取dog實例的反射值對象
    valueOfDog := reflect.ValueOf(dog{})

    // 獲取legCount字段的值
    vLegCount := valueOfDog.FieldByName("legCount")

    // 嘗試設置legCount的值(這里會發生崩潰)
    vLegCount.SetInt(4)
}
程序發生崩潰,報錯:

panic: reflect: reflect.Value.SetInt using value obtained using unexported field

報錯的意思是:SetInt() 使用的值來自于一個未導出的字段。

為了能修改這個值,需要將該字段導出。將 dog 中的 legCount 的成員首字母大寫,導出 LegCount 讓反射可以訪問,修改后的代碼如下:
type dog struct {
    LegCount int
}
然后根據字段名獲取字段的值時,將字符串的字段首字母大寫,修改后的代碼如下:
vLegCount := valueOfDog.FieldByName("LegCount")
再次運行程序,發現仍然報錯:

panic: reflect: reflect.Value.SetInt using unaddressable value

這個錯誤表示第 13 行構造的 valueOfDog 這個結構體實例不能被尋址,因此其字段也不能被修改。修改代碼,取結構體的指針,再通過 reflect.Value 的 Elem() 方法取到值的反射值對象。修改后的完整代碼如下:
package main

import (
    "reflect"
    "fmt"
)

func main() {

    type dog struct {
            LegCount int
    }
    // 獲取dog實例地址的反射值對象
    valueOfDog := reflect.ValueOf(&dog{})

    // 取出dog實例地址的元素
    valueOfDog = valueOfDog.Elem()

    // 獲取legCount字段的值
    vLegCount := valueOfDog.FieldByName("LegCount")

    // 嘗試設置legCount的值(這里會發生崩潰)
    vLegCount.SetInt(4)

    fmt.Println(vLegCount.Int())
}
代碼輸出如下:
4

代碼說明如下:
  • 第 11 行,將 LegCount 首字母大寫導出該字段。
  • 第 14 行,獲取 dog 實例指針的反射值對象。
  • 第 17 行,取 dog 實例的指針元素,也就是 dog 的實例。
  • 第 20 行,取 dog 結構體中 LegCount 字段的成員值。
  • 第 23 行,修改該成員值。
  • 第 25 行,打印該成員值。

值的修改從表面意義上叫可尋址,換一種說法就是值必須“可被設置”。那么,想修改變量值,一般的步驟是:
  1. 取這個變量的地址或者這個變量所在的結構體已經是指針類型。
  2. 使用 reflect.ValueOf 進行值包裝。
  3. 通過 Value.Elem() 獲得指針值指向的元素值對象(Value),因為值對象(Value)內部對象為指針時,使用 set 設置時會報出宕機錯誤。
  4. 使用 Value.Set 設置值。

精美而實用的網站,提供C語言C++STLLinuxShellJavaGo語言等教程,以及socketGCCviSwing設計模式JSP等專題。

Copyright ?2011-2018 biancheng.net, 陜ICP備15000209號

底部Logo