## 12.5. 通過reflect.Value脩改值

到目前爲止，反射還隻是程序中變量的另一種訪問方式。然而，在本節中我們將重點討論如果通過反射機製來脩改變量。

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

對於reflect.Values也有類似的區别。有一些reflect.Values是可取地址的；其它一些則不可以。考慮以下的聲明語句：

```Go
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方法來判斷其是否可以被取地址：

```Go
fmt.Println(a.CanAddr()) // "false"
fmt.Println(b.CanAddr()) // "false"
fmt.Println(c.CanAddr()) // "false"
fmt.Println(d.CanAddr()) // "true"
```

每當我們通過指針間接地獲取的reflect.Value都是可取地址的，卽使開始的是一個不可取地址的Value。在反射機製中，所有關於是否支持取地址的規則都是類似的。例如，slice的索引表達式e[i]將隱式地包含一個指針，它就是可取地址的，卽使開始的e表達式不支持也沒有關繫。以此類推，reflect.ValueOf(e).Index(i)對於的值也是可取地址的，卽使原始的reflect.ValueOf(e)不支持也沒有關繫。

要從變量對應的可取地址的reflect.Value來訪問變量需要三個步驟。第一步是調用Addr()方法，它返迴一個Value，里面保存了指向變量的指針。然後是在Value上調用Interface()方法，也就是返迴一個interface{}，里面通用包含指向變量的指針。最後，如果我們知道變量的類型，我們可以使用類型的斷言機製將得到的interface{}類型的接口強製環爲普通的類型指針。這樣我們就可以通過這個普通指針來更新變量了：

```Go
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"
```

或者，不使用指針，而是通過調用可取地址的reflect.Value的reflect.Value.Set方法來更新對於的值：

```Go
d.Set(reflect.ValueOf(4))
fmt.Println(x) // "4"
```

Set方法將在運行時執行和編譯時類似的可賦值性約束的檢査。以上代碼，變量和值都是int類型，但是如果變量是int64類型，那麽程序將拋出一個panic異常，所以關鍵問題是要確保改類型的變量可以接受對應的值：

```Go
d.Set(reflect.ValueOf(int64(5))) // panic: int64 is not assignable to int
```

通用對一個不可取地址的reflect.Value調用Set方法也會導致panic異常：

```Go
x := 2
b := reflect.ValueOf(x)
b.Set(reflect.ValueOf(3)) // panic: Set using unaddressable value
```

這里有很多用於基本數據類型的Set方法：SetInt、SetUint、SetString和SetFloat等。

```Go
d := reflect.ValueOf(&x).Elem()
d.SetInt(3)
fmt.Println(x) // "3"
```

從某種程度上説，這些Set方法總是盡可能地完成任務。以SetInt爲例，隻要變量是某種類型的有符號整數就可以工作，卽使是一些命名的類型，隻要底層數據類型是有符號整數就可以，而且如果對於變量類型值太大的話會被自動截斷。但需要謹慎的是：對於一個引用interface{}類型的reflect.Value調用SetInt會導致panic異常，卽使那個interface{}變量對於整數類型也不行。

```Go
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"
```

當我們用Display顯示os.Stdout結構時，我們發現反射可以越過Go語言的導出規則的限製讀取結構體中未導出的成員，比如在類Unix繫統上os.File結構體中的fd int成員。然而，利用反射機製併不能脩改這些未導出的成員：

```Go
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
```

一個可取地址的reflect.Value會記録一個結構體成員是否是未導出成員，如果是的話則拒絶脩改操作。因此，CanAddr方法併不能正確反映一個變量是否是可以被脩改的。另一個相關的方法CanSet是用於檢査對應的reflect.Value是否是可取地址併可被脩改的：

```Go
fmt.Println(fd.CanAddr(), fd.CanSet()) // "true false"
```


