mirror of
https://github.com/gopl-zh/gopl-zh.github.com.git
synced 2025-12-20 12:44:20 +08:00
rebuild
This commit is contained in:
@@ -8,7 +8,7 @@
|
||||
<title>通過reflect.Value脩改值 | Go语言圣经</title>
|
||||
<meta content="text/html; charset=utf-8" http-equiv="Content-Type">
|
||||
<meta name="description" content="">
|
||||
<meta name="generator" content="GitBook 2.5.2">
|
||||
<meta name="generator" content="GitBook 2.6.6">
|
||||
|
||||
|
||||
<meta name="HandheldFriendly" content="true"/>
|
||||
@@ -48,7 +48,13 @@
|
||||
<body>
|
||||
|
||||
|
||||
<div class="book" data-level="12.5" data-chapter-title="通過reflect.Value脩改值" data-filepath="ch12/ch12-05.md" data-basepath=".." data-revision="Thu Dec 31 2015 16:18:40 GMT+0800 (中国标准时间)">
|
||||
<div class="book"
|
||||
data-level="12.5"
|
||||
data-chapter-title="通過reflect.Value脩改值"
|
||||
data-filepath="ch12/ch12-05.md"
|
||||
data-basepath=".."
|
||||
data-revision="Sat Jan 02 2016 16:00:23 GMT+0800 (中国标准时间)"
|
||||
data-innerlanguage="">
|
||||
|
||||
|
||||
<div class="book-summary">
|
||||
@@ -2024,7 +2030,72 @@
|
||||
<section class="normal" id="section-">
|
||||
|
||||
<h2 id="125-通過reflectvalue脩改值">12.5. 通過reflect.Value脩改值</h2>
|
||||
<p>TODO</p>
|
||||
<p>到目前爲止,反射還隻是程序中變量的另一種訪問方式。然而,在本節中我們將重點討論如果通過反射機製來脩改變量。</p>
|
||||
<p>迴想一下,Go語言中類似x、x.f[1]和*p形式的表達式都可以表示變量,但是其它如x + 1和f(2)則不是變量。一個變量就是一個可尋址的內存空間,里面存儲了一個值,併且存儲的值可以通過內存地址來更新。</p>
|
||||
<p>對於reflect.Values也有類似的區别。有一些reflect.Values是可取地址的;其它一些則不可以。考慮以下的聲明語句:</p>
|
||||
<pre><code class="lang-Go">x := <span class="hljs-number">2</span> <span class="hljs-comment">// value type variable?</span>
|
||||
a := reflect.ValueOf(<span class="hljs-number">2</span>) <span class="hljs-comment">// 2 int no</span>
|
||||
b := reflect.ValueOf(x) <span class="hljs-comment">// 2 int no</span>
|
||||
c := reflect.ValueOf(&x) <span class="hljs-comment">// &x *int no</span>
|
||||
d := c.Elem() <span class="hljs-comment">// 2 int yes (x)</span>
|
||||
</code></pre>
|
||||
<p>其中a對應的變量則不可取地址。因爲a中的值僅僅是整數2的拷貝副本。b中的值也同樣不可取地址。c中的值還是不可取地址,它隻是一個指針<code>&x</code>的拷貝。實際上,所有通過reflect.ValueOf(x)返迴的reflect.Value都是不可取地址的。但是對於d,它是c的解引用方式生成的,指向另一個變量,因此是可取地址的。我們可以通過調用reflect.ValueOf(&x).Elem(),來獲取任意變量x對應的可取地址的Value。</p>
|
||||
<p>我們可以通過調用reflect.Value的CanAddr方法來判斷其是否可以被取地址:</p>
|
||||
<pre><code class="lang-Go">fmt.Println(a.CanAddr()) <span class="hljs-comment">// "false"</span>
|
||||
fmt.Println(b.CanAddr()) <span class="hljs-comment">// "false"</span>
|
||||
fmt.Println(c.CanAddr()) <span class="hljs-comment">// "false"</span>
|
||||
fmt.Println(d.CanAddr()) <span class="hljs-comment">// "true"</span>
|
||||
</code></pre>
|
||||
<p>每當我們通過指針間接地獲取的reflect.Value都是可取地址的,卽使開始的是一個不可取地址的Value。在反射機製中,所有關於是否支持取地址的規則都是類似的。例如,slice的索引表達式e[i]將隱式地包含一個指針,它就是可取地址的,卽使開始的e表達式不支持也沒有關繫。以此類推,reflect.ValueOf(e).Index(i)對於的值也是可取地址的,卽使原始的reflect.ValueOf(e)不支持也沒有關繫。</p>
|
||||
<p>要從變量對應的可取地址的reflect.Value來訪問變量需要三個步驟。第一步是調用Addr()方法,它返迴一個Value,里面保存了指向變量的指針。然後是在Value上調用Interface()方法,也就是返迴一個interface{},里面通用包含指向變量的指針。最後,如果我們知道變量的類型,我們可以使用類型的斷言機製將得到的interface{}類型的接口強製環爲普通的類型指針。這樣我們就可以通過這個普通指針來更新變量了:</p>
|
||||
<pre><code class="lang-Go">x := <span class="hljs-number">2</span>
|
||||
d := reflect.ValueOf(&x).Elem() <span class="hljs-comment">// d refers to the variable x</span>
|
||||
px := d.Addr().Interface().(*<span class="hljs-typename">int</span>) <span class="hljs-comment">// px := &x</span>
|
||||
*px = <span class="hljs-number">3</span> <span class="hljs-comment">// x = 3</span>
|
||||
fmt.Println(x) <span class="hljs-comment">// "3"</span>
|
||||
</code></pre>
|
||||
<p>或者,不使用指針,而是通過調用可取地址的reflect.Value的reflect.Value.Set方法來更新對於的值:</p>
|
||||
<pre><code class="lang-Go">d.Set(reflect.ValueOf(<span class="hljs-number">4</span>))
|
||||
fmt.Println(x) <span class="hljs-comment">// "4"</span>
|
||||
</code></pre>
|
||||
<p>Set方法將在運行時執行和編譯時類似的可賦值性約束的檢査。以上代碼,變量和值都是int類型,但是如果變量是int64類型,那麽程序將拋出一個panic異常,所以關鍵問題是要確保改類型的變量可以接受對應的值:</p>
|
||||
<pre><code class="lang-Go">d.Set(reflect.ValueOf(<span class="hljs-typename">int64</span>(<span class="hljs-number">5</span>))) <span class="hljs-comment">// panic: int64 is not assignable to int</span>
|
||||
</code></pre>
|
||||
<p>通用對一個不可取地址的reflect.Value調用Set方法也會導致panic異常:</p>
|
||||
<pre><code class="lang-Go">x := <span class="hljs-number">2</span>
|
||||
b := reflect.ValueOf(x)
|
||||
b.Set(reflect.ValueOf(<span class="hljs-number">3</span>)) <span class="hljs-comment">// panic: Set using unaddressable value</span>
|
||||
</code></pre>
|
||||
<p>這里有很多用於基本數據類型的Set方法:SetInt、SetUint、SetString和SetFloat等。</p>
|
||||
<pre><code class="lang-Go">d := reflect.ValueOf(&x).Elem()
|
||||
d.SetInt(<span class="hljs-number">3</span>)
|
||||
fmt.Println(x) <span class="hljs-comment">// "3"</span>
|
||||
</code></pre>
|
||||
<p>從某種程度上説,這些Set方法總是盡可能地完成任務。以SetInt爲例,隻要變量是某種類型的有符號整數就可以工作,卽使是一些命名的類型,隻要底層數據類型是有符號整數就可以,而且如果對於變量類型值太大的話會被自動截斷。但需要謹慎的是:對於一個引用interface{}類型的reflect.Value調用SetInt會導致panic異常,卽使那個interface{}變量對於整數類型也不行。</p>
|
||||
<pre><code class="lang-Go">x := <span class="hljs-number">1</span>
|
||||
rx := reflect.ValueOf(&x).Elem()
|
||||
rx.SetInt(<span class="hljs-number">2</span>) <span class="hljs-comment">// OK, x = 2</span>
|
||||
rx.Set(reflect.ValueOf(<span class="hljs-number">3</span>)) <span class="hljs-comment">// OK, x = 3</span>
|
||||
rx.SetString(<span class="hljs-string">"hello"</span>) <span class="hljs-comment">// panic: string is not assignable to int</span>
|
||||
rx.Set(reflect.ValueOf(<span class="hljs-string">"hello"</span>)) <span class="hljs-comment">// panic: string is not assignable to int</span>
|
||||
|
||||
<span class="hljs-keyword">var</span> y <span class="hljs-keyword">interface</span>{}
|
||||
ry := reflect.ValueOf(&y).Elem()
|
||||
ry.SetInt(<span class="hljs-number">2</span>) <span class="hljs-comment">// panic: SetInt called on interface Value</span>
|
||||
ry.Set(reflect.ValueOf(<span class="hljs-number">3</span>)) <span class="hljs-comment">// OK, y = int(3)</span>
|
||||
ry.SetString(<span class="hljs-string">"hello"</span>) <span class="hljs-comment">// panic: SetString called on interface Value</span>
|
||||
ry.Set(reflect.ValueOf(<span class="hljs-string">"hello"</span>)) <span class="hljs-comment">// OK, y = "hello"</span>
|
||||
</code></pre>
|
||||
<p>當我們用Display顯示os.Stdout結構時,我們發現反射可以越過Go語言的導出規則的限製讀取結構體中未導出的成員,比如在類Unix繫統上os.File結構體中的fd int成員。然而,利用反射機製併不能脩改這些未導出的成員:</p>
|
||||
<pre><code class="lang-Go">stdout := reflect.ValueOf(os.Stdout).Elem() <span class="hljs-comment">// *os.Stdout, an os.File var</span>
|
||||
fmt.Println(stdout.Type()) <span class="hljs-comment">// "os.File"</span>
|
||||
fd := stdout.FieldByName(<span class="hljs-string">"fd"</span>)
|
||||
fmt.Println(fd.Int()) <span class="hljs-comment">// "1"</span>
|
||||
fd.SetInt(<span class="hljs-number">2</span>) <span class="hljs-comment">// panic: unexported field</span>
|
||||
</code></pre>
|
||||
<p>一個可取地址的reflect.Value會記録一個結構體成員是否是未導出成員,如果是的話則拒絶脩改操作。因此,CanAddr方法併不能正確反映一個變量是否是可以被脩改的。另一個相關的方法CanSet是用於檢査對應的reflect.Value是否是可取地址併可被脩改的:</p>
|
||||
<pre><code class="lang-Go">fmt.Println(fd.CanAddr(), fd.CanSet()) <span class="hljs-comment">// "true false"</span>
|
||||
</code></pre>
|
||||
|
||||
|
||||
</section>
|
||||
|
||||
Reference in New Issue
Block a user