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>Display遞歸打印 | 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.3" data-chapter-title="Display遞歸打印" data-filepath="ch12/ch12-03.md" data-basepath=".." data-revision="Thu Dec 31 2015 16:18:40 GMT+0800 (中国标准时间)">
|
||||
<div class="book"
|
||||
data-level="12.3"
|
||||
data-chapter-title="Display遞歸打印"
|
||||
data-filepath="ch12/ch12-03.md"
|
||||
data-basepath=".."
|
||||
data-revision="Sat Jan 02 2016 16:00:23 GMT+0800 (中国标准时间)"
|
||||
data-innerlanguage="">
|
||||
|
||||
|
||||
<div class="book-summary">
|
||||
@@ -2024,7 +2030,181 @@
|
||||
<section class="normal" id="section-">
|
||||
|
||||
<h2 id="123-display遞歸打印">12.3. Display遞歸打印</h2>
|
||||
<p>TODO</p>
|
||||
<p>接下來,讓我們看看如何改善聚合數據類型的顯示。我們併不想完全剋隆一個fmt.Sprint函數,我們隻是像構建一個用於調式用的Display函數,給定一個聚合類型x,打印這個值對應的完整的結構,同時記録每個發現的每個元素的路徑。讓我們從一個例子開始。</p>
|
||||
<pre><code class="lang-Go">e, _ := eval.Parse(<span class="hljs-string">"sqrt(A / pi)"</span>)
|
||||
Display(<span class="hljs-string">"e"</span>, e)
|
||||
</code></pre>
|
||||
<p>在上面的調用中,傳入Display函數的參數是在7.9節一個表達式求值函數返迴的語法樹。Display函數的輸出如下:</p>
|
||||
<pre><code class="lang-Go">Display e (eval.call):
|
||||
e.fn = <span class="hljs-string">"sqrt"</span>
|
||||
e.args[<span class="hljs-number">0</span>].<span class="hljs-keyword">type</span> = eval.binary
|
||||
e.args[<span class="hljs-number">0</span>].value.op = <span class="hljs-number">47</span>
|
||||
e.args[<span class="hljs-number">0</span>].value.x.<span class="hljs-keyword">type</span> = eval.Var
|
||||
e.args[<span class="hljs-number">0</span>].value.x.value = <span class="hljs-string">"A"</span>
|
||||
e.args[<span class="hljs-number">0</span>].value.y.<span class="hljs-keyword">type</span> = eval.Var
|
||||
e.args[<span class="hljs-number">0</span>].value.y.value = <span class="hljs-string">"pi"</span>
|
||||
</code></pre>
|
||||
<p>在可能的情況下,你應該避免在一個包中暴露和反射相關的接口。我們將定義一個未導出的display函數用於遞歸處理工作,導出的是Display函數,它隻是display函數簡單的包裝以接受interface{}類型的參數:</p>
|
||||
<pre><code class="lang-Go">gopl.io/ch12/display
|
||||
|
||||
<span class="hljs-keyword">func</span> Display(name <span class="hljs-typename">string</span>, x <span class="hljs-keyword">interface</span>{}) {
|
||||
fmt.Printf(<span class="hljs-string">"Display %s (%T):\n"</span>, name, x)
|
||||
display(name, reflect.ValueOf(x))
|
||||
}
|
||||
</code></pre>
|
||||
<p>在display函數中,我們使用了前面定義的打印基礎類型——基本類型、函數和chan等——元素值的formatAtom函數,但是我們會使用reflect.Value的方法來遞歸顯示聚合類型的每一個成員或元素。在遞歸下降過程中,path字符串,從最開始傳入的起始值(這里是“e”),將逐步增長以表示如何達到當前值(例如“e.args[0].value”)。</p>
|
||||
<p>因爲我們不再模擬fmt.Sprint函數,我們將直接使用fmt包來簡化我們的例子實現。</p>
|
||||
<pre><code class="lang-Go"><span class="hljs-keyword">func</span> display(path <span class="hljs-typename">string</span>, v reflect.Value) {
|
||||
<span class="hljs-keyword">switch</span> v.Kind() {
|
||||
<span class="hljs-keyword">case</span> reflect.Invalid:
|
||||
fmt.Printf(<span class="hljs-string">"%s = invalid\n"</span>, path)
|
||||
<span class="hljs-keyword">case</span> reflect.Slice, reflect.Array:
|
||||
<span class="hljs-keyword">for</span> i := <span class="hljs-number">0</span>; i < v.Len(); i++ {
|
||||
display(fmt.Sprintf(<span class="hljs-string">"%s[%d]"</span>, path, i), v.Index(i))
|
||||
}
|
||||
<span class="hljs-keyword">case</span> reflect.Struct:
|
||||
<span class="hljs-keyword">for</span> i := <span class="hljs-number">0</span>; i < v.NumField(); i++ {
|
||||
fieldPath := fmt.Sprintf(<span class="hljs-string">"%s.%s"</span>, path, v.Type().Field(i).Name)
|
||||
display(fieldPath, v.Field(i))
|
||||
}
|
||||
<span class="hljs-keyword">case</span> reflect.Map:
|
||||
<span class="hljs-keyword">for</span> _, key := <span class="hljs-keyword">range</span> v.MapKeys() {
|
||||
display(fmt.Sprintf(<span class="hljs-string">"%s[%s]"</span>, path,
|
||||
formatAtom(key)), v.MapIndex(key))
|
||||
}
|
||||
<span class="hljs-keyword">case</span> reflect.Ptr:
|
||||
<span class="hljs-keyword">if</span> v.IsNil() {
|
||||
fmt.Printf(<span class="hljs-string">"%s = nil\n"</span>, path)
|
||||
} <span class="hljs-keyword">else</span> {
|
||||
display(fmt.Sprintf(<span class="hljs-string">"(*%s)"</span>, path), v.Elem())
|
||||
}
|
||||
<span class="hljs-keyword">case</span> reflect.Interface:
|
||||
<span class="hljs-keyword">if</span> v.IsNil() {
|
||||
fmt.Printf(<span class="hljs-string">"%s = nil\n"</span>, path)
|
||||
} <span class="hljs-keyword">else</span> {
|
||||
fmt.Printf(<span class="hljs-string">"%s.type = %s\n"</span>, path, v.Elem().Type())
|
||||
display(path+<span class="hljs-string">".value"</span>, v.Elem())
|
||||
}
|
||||
<span class="hljs-keyword">default</span>: <span class="hljs-comment">// basic types, channels, funcs</span>
|
||||
fmt.Printf(<span class="hljs-string">"%s = %s\n"</span>, path, formatAtom(v))
|
||||
}
|
||||
}
|
||||
</code></pre>
|
||||
<p>讓我們針對不同類型分别討論。</p>
|
||||
<p><strong>Slice和數組:</strong> 兩種的處理邏輯是一樣的。Len方法返迴slice或數組值中的元素個數,Index(i)活動索引i對應的元素,返迴的也是一個reflect.Value類型的值;如果索引i超出范圍的話將導致panic異常,這些行爲和數組或slice類型內建的len(a)和a[i]等操作類似。display針對序列中的每個元素遞歸調用自身處理,我們通過在遞歸處理時向path附加“[i]”來表示訪問路徑。</p>
|
||||
<p>雖然reflect.Value類型帶有很多方法,但是隻有少數的方法對任意值都是可以安全調用的。例如,Index方法隻能對Slice、數組或字符串類型的值調用,其它類型如果調用將導致panic異常。</p>
|
||||
<p><strong>結構體:</strong> NumField方法報告結構體中成員的數量,Field(i)以reflect.Value類型返迴第i個成員的值。成員列表包含了匿名成員在內的全部成員。通過在path添加“.f”來表示成員路徑,我們必鬚獲得結構體對應的reflect.Type類型信息,包含結構體類型和第i個成員的名字。</p>
|
||||
<p><strong>Maps:</strong> MapKeys方法返迴一個reflect.Value類型的slice,每一個都對應map的可以。和往常一樣,遍歷map時順序是隨機的。MapIndex(key)返迴map中key對應的value。我們向path添加“[key]”來表示訪問路徑。(我們這里有一個未完成的工作。其實map的key的類型併不局限於formatAtom能完美處理的類型;數組、結構體和接口都可以作爲map的key。針對這種類型,完善key的顯示信息是練習12.1的任務。)</p>
|
||||
<p><strong>指針:</strong> Elem方法返迴指針指向的變量,還是reflect.Value類型。技術指針是nil,這個操作也是安全的,在這種情況下指針是Invalid無效類型,但是我們可以用IsNil方法來顯式地測試一個空指針,這樣我們可以打印更合適的信息。我們在path前面添加“*”,併用括弧包含以避免歧義。</p>
|
||||
<p><strong>接口:</strong> 再一次,我們使用IsNil方法來測試接口是否是nil,如果不是,我們可以調用v.Elem()來獲取接口對應的動態值,併且打印對應的類型和值。</p>
|
||||
<p>現在我們的Display函數總算完工了,讓我們看看它的表現吧。下面的Movie類型是在4.5節的電影類型上演變來的:</p>
|
||||
<pre><code class="lang-Go"><span class="hljs-keyword">type</span> Movie <span class="hljs-keyword">struct</span> {
|
||||
Title, Subtitle <span class="hljs-typename">string</span>
|
||||
Year <span class="hljs-typename">int</span>
|
||||
Color <span class="hljs-typename">bool</span>
|
||||
Actor <span class="hljs-keyword">map</span>[<span class="hljs-typename">string</span>]<span class="hljs-typename">string</span>
|
||||
Oscars []<span class="hljs-typename">string</span>
|
||||
Sequel *<span class="hljs-typename">string</span>
|
||||
}
|
||||
</code></pre>
|
||||
<p>讓我們聲明一個該類型的變量,然後看看Display函數如何顯示它:</p>
|
||||
<pre><code class="lang-Go">strangelove := Movie{
|
||||
Title: <span class="hljs-string">"Dr. Strangelove"</span>,
|
||||
Subtitle: <span class="hljs-string">"How I Learned to Stop Worrying and Love the Bomb"</span>,
|
||||
Year: <span class="hljs-number">1964</span>,
|
||||
Color: <span class="hljs-constant">false</span>,
|
||||
Actor: <span class="hljs-keyword">map</span>[<span class="hljs-typename">string</span>]<span class="hljs-typename">string</span>{
|
||||
<span class="hljs-string">"Dr. Strangelove"</span>: <span class="hljs-string">"Peter Sellers"</span>,
|
||||
<span class="hljs-string">"Grp. Capt. Lionel Mandrake"</span>: <span class="hljs-string">"Peter Sellers"</span>,
|
||||
<span class="hljs-string">"Pres. Merkin Muffley"</span>: <span class="hljs-string">"Peter Sellers"</span>,
|
||||
<span class="hljs-string">"Gen. Buck Turgidson"</span>: <span class="hljs-string">"George C. Scott"</span>,
|
||||
<span class="hljs-string">"Brig. Gen. Jack D. Ripper"</span>: <span class="hljs-string">"Sterling Hayden"</span>,
|
||||
<span class="hljs-string">`Maj. T.J. "King" Kong`</span>: <span class="hljs-string">"Slim Pickens"</span>,
|
||||
},
|
||||
|
||||
Oscars: []<span class="hljs-typename">string</span>{
|
||||
<span class="hljs-string">"Best Actor (Nomin.)"</span>,
|
||||
<span class="hljs-string">"Best Adapted Screenplay (Nomin.)"</span>,
|
||||
<span class="hljs-string">"Best Director (Nomin.)"</span>,
|
||||
<span class="hljs-string">"Best Picture (Nomin.)"</span>,
|
||||
},
|
||||
}
|
||||
</code></pre>
|
||||
<p>Display("strangelove", strangelove)調用將顯示(strangelove電影對應的中文名是《奇愛博士》):</p>
|
||||
<pre><code class="lang-Go">Display strangelove (display.Movie):
|
||||
strangelove.Title = <span class="hljs-string">"Dr. Strangelove"</span>
|
||||
strangelove.Subtitle = <span class="hljs-string">"How I Learned to Stop Worrying and Love the Bomb"</span>
|
||||
strangelove.Year = <span class="hljs-number">1964</span>
|
||||
strangelove.Color = <span class="hljs-constant">false</span>
|
||||
strangelove.Actor[<span class="hljs-string">"Gen. Buck Turgidson"</span>] = <span class="hljs-string">"George C. Scott"</span>
|
||||
strangelove.Actor[<span class="hljs-string">"Brig. Gen. Jack D. Ripper"</span>] = <span class="hljs-string">"Sterling Hayden"</span>
|
||||
strangelove.Actor[<span class="hljs-string">"Maj. T.J. \"King\" Kong"</span>] = <span class="hljs-string">"Slim Pickens"</span>
|
||||
strangelove.Actor[<span class="hljs-string">"Dr. Strangelove"</span>] = <span class="hljs-string">"Peter Sellers"</span>
|
||||
strangelove.Actor[<span class="hljs-string">"Grp. Capt. Lionel Mandrake"</span>] = <span class="hljs-string">"Peter Sellers"</span>
|
||||
strangelove.Actor[<span class="hljs-string">"Pres. Merkin Muffley"</span>] = <span class="hljs-string">"Peter Sellers"</span>
|
||||
strangelove.Oscars[<span class="hljs-number">0</span>] = <span class="hljs-string">"Best Actor (Nomin.)"</span>
|
||||
strangelove.Oscars[<span class="hljs-number">1</span>] = <span class="hljs-string">"Best Adapted Screenplay (Nomin.)"</span>
|
||||
strangelove.Oscars[<span class="hljs-number">2</span>] = <span class="hljs-string">"Best Director (Nomin.)"</span>
|
||||
strangelove.Oscars[<span class="hljs-number">3</span>] = <span class="hljs-string">"Best Picture (Nomin.)"</span>
|
||||
strangelove.Sequel = <span class="hljs-constant">nil</span>
|
||||
</code></pre>
|
||||
<p>我們也可以使用Display函數來顯示標準庫中類型的內部結構,例如<code>*os.File</code>類型:</p>
|
||||
<pre><code class="lang-Go">Display(<span class="hljs-string">"os.Stderr"</span>, os.Stderr)
|
||||
<span class="hljs-comment">// Output:</span>
|
||||
<span class="hljs-comment">// Display os.Stderr (*os.File):</span>
|
||||
<span class="hljs-comment">// (*(*os.Stderr).file).fd = 2</span>
|
||||
<span class="hljs-comment">// (*(*os.Stderr).file).name = "/dev/stderr"</span>
|
||||
<span class="hljs-comment">// (*(*os.Stderr).file).nepipe = 0</span>
|
||||
</code></pre>
|
||||
<p>要註意的是,結構體中未導出的成員對反射也是可見的。需要當心的是這個例子的輸出在不同操作繫統上可能是不同的,併且隨着標準庫的發展也可能導致結果不同。(這也是將這些成員定義爲私有成員的原因之一!)我們深圳可以用Display函數來顯示reflect.Value,來査看<code>*os.File</code>類型的內部表示方式。<code>Display("rV", reflect.ValueOf(os.Stderr))</code>調用的輸出如下,當然不同環境得到的結果可能有差異:</p>
|
||||
<pre><code class="lang-Go">Display rV (reflect.Value):
|
||||
(*rV.typ).size = <span class="hljs-number">8</span>
|
||||
(*rV.typ).hash = <span class="hljs-number">871609668</span>
|
||||
(*rV.typ).align = <span class="hljs-number">8</span>
|
||||
(*rV.typ).fieldAlign = <span class="hljs-number">8</span>
|
||||
(*rV.typ).kind = <span class="hljs-number">22</span>
|
||||
(*(*rV.typ).<span class="hljs-typename">string</span>) = <span class="hljs-string">"*os.File"</span>
|
||||
|
||||
(*(*(*rV.typ).uncommonType).methods[<span class="hljs-number">0</span>].name) = <span class="hljs-string">"Chdir"</span>
|
||||
(*(*(*(*rV.typ).uncommonType).methods[<span class="hljs-number">0</span>].mtyp).<span class="hljs-typename">string</span>) = <span class="hljs-string">"func() error"</span>
|
||||
(*(*(*(*rV.typ).uncommonType).methods[<span class="hljs-number">0</span>].typ).<span class="hljs-typename">string</span>) = <span class="hljs-string">"func(*os.File) error"</span>
|
||||
...
|
||||
</code></pre>
|
||||
<p>觀察下面兩個例子的區别:</p>
|
||||
<pre><code class="lang-Go"><span class="hljs-keyword">var</span> i <span class="hljs-keyword">interface</span>{} = <span class="hljs-number">3</span>
|
||||
|
||||
Display(<span class="hljs-string">"i"</span>, i)
|
||||
<span class="hljs-comment">// Output:</span>
|
||||
<span class="hljs-comment">// Display i (int):</span>
|
||||
<span class="hljs-comment">// i = 3</span>
|
||||
|
||||
Display(<span class="hljs-string">"&i"</span>, &i)
|
||||
<span class="hljs-comment">// Output:</span>
|
||||
<span class="hljs-comment">// Display &i (*interface {}):</span>
|
||||
<span class="hljs-comment">// (*&i).type = int</span>
|
||||
<span class="hljs-comment">// (*&i).value = 3</span>
|
||||
</code></pre>
|
||||
<p>在第一個例子中,Display函數將調用reflect.ValueOf(i),它返迴一個Int類型的值。正如我們在12.2節中提到的,reflect.ValueOf總是返迴一個值的具體類型,因爲它是從一個接口值提取的內容。</p>
|
||||
<p>在第二個例子中,Display函數調用的是reflect.ValueOf(&i),它返迴一個指向i的指針,對應Ptr類型。在switch的Ptr分支中,通過調用Elem來返迴這個值,返迴一個Value來表示i,對應Interface類型。一個間接獲得的Value,就像這一個,可能代表任意類型的值,包括接口類型。內部的display函數遞歸調用自身,這次它將打印接口的動態類型和值。</p>
|
||||
<p>目前的實現,Display如果顯示一個帶環的數據結構將會陷入死循環,例如首位項鏈的鏈表:</p>
|
||||
<pre><code class="lang-Go"><span class="hljs-comment">// a struct that points to itself</span>
|
||||
<span class="hljs-keyword">type</span> Cycle <span class="hljs-keyword">struct</span>{ Value <span class="hljs-typename">int</span>; Tail *Cycle }
|
||||
<span class="hljs-keyword">var</span> c Cycle
|
||||
c = Cycle{<span class="hljs-number">42</span>, &c}
|
||||
Display(<span class="hljs-string">"c"</span>, c)
|
||||
</code></pre>
|
||||
<p>Display會永遠不停地進行深度遞歸打印:</p>
|
||||
<pre><code class="lang-Go">Display c (display.Cycle):
|
||||
c.Value = <span class="hljs-number">42</span>
|
||||
(*c.Tail).Value = <span class="hljs-number">42</span>
|
||||
(*(*c.Tail).Tail).Value = <span class="hljs-number">42</span>
|
||||
(*(*(*c.Tail).Tail).Tail).Value = <span class="hljs-number">42</span>
|
||||
...ad infinitum...
|
||||
</code></pre>
|
||||
<p>許多Go語言程序都包含了一些循環的數據結果。Display支持這類帶環的數據結構是比較棘手的,需要增加一個額外的記録訪問的路徑;代價是昂貴的。一般的解決方案是采用不安全的語言特性,我們將在13.3節看到具體的解決方案。</p>
|
||||
<p>帶環的數據結構很少會對fmt.Sprint函數造成問題,因爲它很少嚐試打印完整的數據結構。例如,當它遇到一個指針的時候,它隻是簡單第打印指針的數值。雖然,在打印包含自身的slice或map時可能遇到睏難,但是不保證處理這種是罕見情況卻可以避免額外的麻煩。</p>
|
||||
<p><strong>練習 12.1:</strong> 擴展Displayhans,以便它可以顯示包含以結構體或數組作爲map的key類型的值。</p>
|
||||
<p><strong>練習 12.2:</strong> 增強display函數的穩健性,通過記録邊界的步數來確保在超出一定限製前放棄遞歸。(在13.3節,我們會看到另一種探測數據結構是否存在環的技術。)</p>
|
||||
|
||||
|
||||
</section>
|
||||
|
||||
Reference in New Issue
Block a user