W3Cschool
恭喜您成為首批注冊(cè)用戶
獲得88經(jīng)驗(yàn)值獎(jiǎng)勵(lì)
原文鏈接:https://gopl-zh.github.io/ch12/ch12-05.html
到目前為止,反射還只是程序中變量的另一種讀取方式。然而,在本節(jié)中我們將重點(diǎn)討論如何通過(guò)反射機(jī)制來(lái)修改變量。
回想一下,Go語(yǔ)言中類似x、x.f[1]和*p形式的表達(dá)式都可以表示變量,但是其它如x + 1和f(2)則不是變量。一個(gè)變量就是一個(gè)可尋址的內(nèi)存空間,里面存儲(chǔ)了一個(gè)值,并且存儲(chǔ)的值可以通過(guò)內(nèi)存地址來(lái)更新。
對(duì)于reflect.Values也有類似的區(qū)別。有一些reflect.Values是可取地址的;其它一些則不可以??紤]以下的聲明語(yǔ)句:
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對(duì)應(yīng)的變量不可取地址。因?yàn)閍中的值僅僅是整數(shù)2的拷貝副本。b中的值也同樣不可取地址。c中的值還是不可取地址,它只是一個(gè)指針&x的拷貝。實(shí)際上,所有通過(guò)reflect.ValueOf(x)返回的reflect.Value都是不可取地址的。但是對(duì)于d,它是c的解引用方式生成的,指向另一個(gè)變量,因此是可取地址的。我們可以通過(guò)調(diào)用reflect.ValueOf(&x).Elem(),來(lái)獲取任意變量x對(duì)應(yīng)的可取地址的Value。
我們可以通過(guò)調(diào)用reflect.Value的CanAddr方法來(lái)判斷其是否可以被取地址:
fmt.Println(a.CanAddr()) // "false"
fmt.Println(b.CanAddr()) // "false"
fmt.Println(c.CanAddr()) // "false"
fmt.Println(d.CanAddr()) // "true"
每當(dāng)我們通過(guò)指針間接地獲取的reflect.Value都是可取地址的,即使開始的是一個(gè)不可取地址的Value。在反射機(jī)制中,所有關(guān)于是否支持取地址的規(guī)則都是類似的。例如,slice的索引表達(dá)式e[i]將隱式地包含一個(gè)指針,它就是可取地址的,即使開始的e表達(dá)式不支持也沒(méi)有關(guān)系。以此類推,reflect.ValueOf(e).Index(i)對(duì)應(yīng)的值也是可取地址的,即使原始的reflect.ValueOf(e)不支持也沒(méi)有關(guān)系。
要從變量對(duì)應(yīng)的可取地址的reflect.Value來(lái)訪問(wèn)變量需要三個(gè)步驟。第一步是調(diào)用Addr()方法,它返回一個(gè)Value,里面保存了指向變量的指針。然后是在Value上調(diào)用Interface()方法,也就是返回一個(gè)interface{},里面包含指向變量的指針。最后,如果我們知道變量的類型,我們可以使用類型的斷言機(jī)制將得到的interface{}類型的接口強(qiáng)制轉(zhuǎn)為普通的類型指針。這樣我們就可以通過(guò)這個(gè)普通指針來(lái)更新變量了:
x := 2
d := reflect.ValueOf(&x).Elem() // d refers to the variable x
px := d.Addr().Interface().(*int) // px := &x
*px = 3 // x = 3
fmt.Println(x) // "3"
或者,不使用指針,而是通過(guò)調(diào)用可取地址的reflect.Value的reflect.Value.Set方法來(lái)更新對(duì)應(yīng)的值:
d.Set(reflect.ValueOf(4))
fmt.Println(x) // "4"
Set方法將在運(yùn)行時(shí)執(zhí)行和編譯時(shí)進(jìn)行類似的可賦值性約束的檢查。以上代碼,變量和值都是int類型,但是如果變量是int64類型,那么程序?qū)伋鲆粋€(gè)panic異常,所以關(guān)鍵問(wèn)題是要確保改類型的變量可以接受對(duì)應(yīng)的值:
d.Set(reflect.ValueOf(int64(5))) // panic: int64 is not assignable to int
同樣,對(duì)一個(gè)不可取地址的reflect.Value調(diào)用Set方法也會(huì)導(dǎo)致panic異常:
x := 2
b := reflect.ValueOf(x)
b.Set(reflect.ValueOf(3)) // panic: Set using unaddressable value
這里有很多用于基本數(shù)據(jù)類型的Set方法:SetInt、SetUint、SetString和SetFloat等。
d := reflect.ValueOf(&x).Elem()
d.SetInt(3)
fmt.Println(x) // "3"
從某種程度上說(shuō),這些Set方法總是盡可能地完成任務(wù)。以SetInt為例,只要變量是某種類型的有符號(hào)整數(shù)就可以工作,即使是一些命名的類型、甚至只要底層數(shù)據(jù)類型是有符號(hào)整數(shù)就可以,而且如果對(duì)于變量類型值太大的話會(huì)被自動(dòng)截?cái)?。但需要?jǐn)慎的是:對(duì)于一個(gè)引用interface{}類型的reflect.Value調(diào)用SetInt會(huì)導(dǎo)致panic異常,即使那個(gè)interface{}變量對(duì)于整數(shù)類型也不行。
x := 1
rx := reflect.ValueOf(&x).Elem()
rx.SetInt(2) // OK, x = 2
rx.Set(reflect.ValueOf(3)) // OK, x = 3
rx.SetString("hello") // panic: string is not assignable to int
rx.Set(reflect.ValueOf("hello")) // panic: string is not assignable to int
var y interface{}
ry := reflect.ValueOf(&y).Elem()
ry.SetInt(2) // panic: SetInt called on interface Value
ry.Set(reflect.ValueOf(3)) // OK, y = int(3)
ry.SetString("hello") // panic: SetString called on interface Value
ry.Set(reflect.ValueOf("hello")) // OK, y = "hello"
當(dāng)我們用Display顯示os.Stdout結(jié)構(gòu)時(shí),我們發(fā)現(xiàn)反射可以越過(guò)Go語(yǔ)言的導(dǎo)出規(guī)則的限制讀取結(jié)構(gòu)體中未導(dǎo)出的成員,比如在類Unix系統(tǒng)上os.File結(jié)構(gòu)體中的fd int成員。然而,利用反射機(jī)制并不能修改這些未導(dǎo)出的成員:
stdout := reflect.ValueOf(os.Stdout).Elem() // *os.Stdout, an os.File var
fmt.Println(stdout.Type()) // "os.File"
fd := stdout.FieldByName("fd")
fmt.Println(fd.Int()) // "1"
fd.SetInt(2) // panic: unexported field
一個(gè)可取地址的reflect.Value會(huì)記錄一個(gè)結(jié)構(gòu)體成員是否是未導(dǎo)出成員,如果是的話則拒絕修改操作。因此,CanAddr方法并不能正確反映一個(gè)變量是否是可以被修改的。另一個(gè)相關(guān)的方法CanSet是用于檢查對(duì)應(yīng)的reflect.Value是否是可取地址并可被修改的:
fmt.Println(fd.CanAddr(), fd.CanSet()) // "true false"
Copyright©2021 w3cschool編程獅|閩ICP備15016281號(hào)-3|閩公網(wǎng)安備35020302033924號(hào)
違法和不良信息舉報(bào)電話:173-0602-2364|舉報(bào)郵箱:jubao@eeedong.com
掃描二維碼
下載編程獅App
編程獅公眾號(hào)
聯(lián)系方式:
更多建議: