新聞中心
Go語言中類似 x、x.f[1] 和 *p 形式的表達式都可以表示變量,但是其它如 x + 1 和 f(2) 則不是變量。一個變量就是一個可尋址的內存空間,里面存儲了一個值,并且存儲的值可以通過內存地址來更新。

創(chuàng)新互聯服務項目包括從江網站建設、從江網站制作、從江網頁制作以及從江網絡營銷策劃等。多年來,我們專注于互聯網行業(yè),利用自身積累的技術優(yōu)勢、行業(yè)經驗、深度合作伙伴關系等,向廣大中小型企業(yè)、政府機構等提供互聯網行業(yè)的解決方案,從江網站推廣取得了明顯的社會效益與經濟效益。目前,我們服務的客戶以成都為中心已經輻射到從江省份的部分城市,未來相信會繼續(xù)擴大服務區(qū)域并繼續(xù)獲得客戶的支持與信任!
對于 reflect.Values 也有類似的區(qū)別。有一些 reflect.Values 是可取地址的;其它一些則不可以??紤]以下的聲明語句:
x := 2 // value type variable?
a := reflect.ValueOf(2) // 2 int no
b := reflect.ValueOf(x) // 2 int no
c := reflect.ValueOf(&x) // &x *int no
d := c.Elem() // 2 int yes (x)
其中 a 對應的變量則不可取地址。因為 a 中的值僅僅是整數 2 的拷貝副本。b 中的值也同樣不可取地址。c 中的值還是不可取地址,它只是一個指針 &x 的拷貝。實際上,所有通過 reflect.ValueOf(x) 返回的 reflect.Value 都是不可取地址的。但是對于 d,它是 c 的解引用方式生成的,指向另一個變量,因此是可取地址的。我們可以通過調用 reflect.ValueOf(&x).Elem(),來獲取任意變量x對應的可取地址的 Value。
我們可以通過調用 reflect.Value 的 CanAddr 方法來判斷其是否可以被取地址:
fmt.Println(a.CanAddr()) // "false"
fmt.Println(b.CanAddr()) // "false"
fmt.Println(c.CanAddr()) // "false"
fmt.Println(d.CanAddr()) // "true"
每當我們通過指針間接地獲取的 reflect.Value 都是可取地址的,即使開始的是一個不可取地址的 Value。在反射機制中,所有關于是否支持取地址的規(guī)則都是類似的。例如,slice 的索引表達式 e[i]將隱式地包含一個指針,它就是可取地址的,即使開始的e表達式不支持也沒有關系。
以此類推,reflect.ValueOf(e).Index(i) 對于的值也是可取地址的,即使原始的 reflect.ValueOf(e) 不支持也沒有關系。
使用 reflect.Value 對包裝的值進行修改時,需要遵循一些規(guī)則。如果沒有按照規(guī)則進行代碼設計和編寫,輕則無法修改對象值,重則程序在運行時會發(fā)生宕機。
判定及獲取元素的相關方法
使用 reflect.Value 取元素、取地址及修改值的屬性方法請參考下表。
| 方法名 | 備 注 |
|---|---|
| Elem() Value | 取值指向的元素值,類似于語言層*操作。當值類型不是指針或接口時發(fā)生宕 機,空指針時返回 nil 的 Value |
| Addr() Value | 對可尋址的值返回其地址,類似于語言層&操作。當值不可尋址時發(fā)生宕機 |
| CanAddr() bool | 表示值是否可尋址 |
| CanSet() bool | 返回值能否被修改。要求值可尋址且是導出的字段 |
值修改相關方法
使用 reflect.Value 修改值的相關方法如下表所示。
| Set(x Value) | 將值設置為傳入的反射值對象的值 |
|---|---|
| Setlnt(x int64) | 使用 int64 設置值。當值的類型不是 int、int8、int16、 int32、int64 時會發(fā)生宕機 |
| SetUint(x uint64) | 使用 uint64 設置值。當值的類型不是 uint、uint8、uint16、uint32、uint64 時會發(fā)生宕機 |
| SetFloat(x float64) | 使用 float64 設置值。當值的類型不是 float32、float64 時會發(fā)生宕機 |
| SetBool(x bool) | 使用 bool 設置值。當值的類型不是 bod 時會發(fā)生宕機 |
| SetBytes(x []byte) | 設置字節(jié)數組 []bytes值。當值的類型不是 []byte 時會發(fā)生宕機 |
| SetString(x string) | 設置字符串值。當值的類型不是 string 時會發(fā)生宕機 |
以上方法,在 reflect.Value 的 CanSet 返回 false 仍然修改值時會發(fā)生宕機。
在已知值的類型時,應盡量使用值對應類型的反射設置值。
值可修改條件之一:可被尋址
通過反射修改變量值的前提條件之一:這個值必須可以被尋址。簡單地說就是這個變量必須能被修改。示例代碼如下:
package main
import (
"reflect"
)
func main() {
// 聲明整型變量a并賦初值
var a int = 1024
// 獲取變量a的反射值對象
valueOfA := reflect.ValueOf(a)
// 嘗試將a修改為1(此處會發(fā)生崩潰)
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() 方法設置值時不再發(fā)生崩潰。
- 第 23 行,正確打印修改的值。
提示
當 reflect.Value 不可尋址時,使用 Addr() 方法也是無法取到值的地址的,同時會發(fā)生宕機。雖然說 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的值(這里會發(fā)生崩潰)
vLegCount.SetInt(4)
}程序發(fā)生崩潰,報錯:
panic: reflect: reflect.Value.SetInt using value obtained using unexported field
報錯的意思是:SetInt() 使用的值來自于一個未導出的字段。
為了能修改這個值,需要將該字段導出。將 dog 中的 legCount 的成員首字母大寫,導出 LegCount 讓反射可以訪問,修改后的代碼如下:
type dog struct {
LegCount int
}然后根據字段名獲取字段的值時,將字符串的字段首字母大寫,修改后的代碼如下:
vLegCount := valueOfDog.FieldByName("LegCount")再次運行程序,發(fā)現仍然報錯:
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的值(這里會發(fā)生崩潰)
vLegCount.SetInt(4)
fmt.Println(vLegCount.Int())
}代碼輸出如下:
4
代碼說明如下:
- 第 11 行,將 LegCount 首字母大寫導出該字段。
- 第 14 行,獲取 dog 實例指針的反射值對象。
- 第 17 行,取 dog 實例的指針元素,也就是 dog 的實例。
- 第 20 行,取 dog 結構體中 LegCount 字段的成員值。
- 第 23 行,修改該成員值。
- 第 25 行,打印該成員值。
值的修改從表面意義上叫可尋址,換一種說法就是值必須“可被設置”。那么,想修改變量值,一般的步驟是:
- 取這個變量的地址或者這個變量所在的結構體已經是指針類型。
- 使用 reflect.ValueOf 進行值包裝。
- 通過 Value.Elem() 獲得指針值指向的元素值對象(Value),因為值對象(Value)內部對象為指針時,使用 set 設置時會報出宕機錯誤。
- 使用 Value.Set 設置值。
網站名稱:創(chuàng)新互聯GO教程:Go語言通過反射修改變量的值
分享地址:http://fisionsoft.com.cn/article/dpiiogi.html


咨詢
建站咨詢
