2022-08-04 06:59:33 +00:00
<!DOCTYPE HTML>
< html lang = "zh" class = "sidebar-visible no-js light" >
< head >
<!-- Book generated using mdBook -->
< meta charset = "UTF-8" >
< title > Display递归打印 - Go语言圣经< / title >
<!-- Custom HTML head -->
< meta content = "text/html; charset=utf-8" http-equiv = "Content-Type" >
< meta name = "description" content = "<The Go Programming Language>中文版" >
< meta name = "viewport" content = "width=device-width, initial-scale=1" >
< meta name = "theme-color" content = "#ffffff" / >
< link rel = "icon" href = "../favicon.svg" >
< link rel = "shortcut icon" href = "../favicon.png" >
< link rel = "stylesheet" href = "../css/variables.css" >
< link rel = "stylesheet" href = "../css/general.css" >
< link rel = "stylesheet" href = "../css/chrome.css" >
< link rel = "stylesheet" href = "../css/print.css" media = "print" >
<!-- Fonts -->
< link rel = "stylesheet" href = "../FontAwesome/css/font-awesome.css" >
< link rel = "stylesheet" href = "../fonts/fonts.css" >
<!-- Highlight.js Stylesheets -->
< link rel = "stylesheet" href = "../highlight.css" >
< link rel = "stylesheet" href = "../tomorrow-night.css" >
< link rel = "stylesheet" href = "../ayu-highlight.css" >
<!-- Custom theme stylesheets -->
< link rel = "stylesheet" href = "../style.css" >
< / head >
< body >
<!-- Provide site root to javascript -->
< script type = "text/javascript" >
var path_to_root = "../";
var default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "navy" : "light";
< / script >
<!-- Work around some values being stored in localStorage wrapped in quotes -->
< script type = "text/javascript" >
try {
var theme = localStorage.getItem('mdbook-theme');
var sidebar = localStorage.getItem('mdbook-sidebar');
if (theme.startsWith('"') & & theme.endsWith('"')) {
localStorage.setItem('mdbook-theme', theme.slice(1, theme.length - 1));
}
if (sidebar.startsWith('"') & & sidebar.endsWith('"')) {
localStorage.setItem('mdbook-sidebar', sidebar.slice(1, sidebar.length - 1));
}
} catch (e) { }
< / script >
<!-- Set the theme before any content is loaded, prevents flash -->
< script type = "text/javascript" >
var theme;
try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
if (theme === null || theme === undefined) { theme = default_theme; }
var html = document.querySelector('html');
html.classList.remove('no-js')
html.classList.remove('light')
html.classList.add(theme);
html.classList.add('js');
< / script >
<!-- Hide / unhide sidebar before it is displayed -->
< script type = "text/javascript" >
var html = document.querySelector('html');
var sidebar = 'hidden';
if (document.body.clientWidth >= 1080) {
try { sidebar = localStorage.getItem('mdbook-sidebar'); } catch(e) { }
sidebar = sidebar || 'visible';
}
html.classList.remove('sidebar-visible');
html.classList.add("sidebar-" + sidebar);
< / script >
< nav id = "sidebar" class = "sidebar" aria-label = "Table of contents" >
< div class = "sidebar-scrollbox" >
2023-06-26 17:30:52 +00:00
< ol class = "chapter" > < li class = "chapter-item expanded affix " > < a href = "../index.html" > Go语言圣经< / a > < / li > < li class = "chapter-item expanded affix " > < a href = "../preface-zh.html" > 译者序< / a > < / li > < li class = "chapter-item expanded affix " > < a href = "../preface.html" > 前言< / a > < / li > < li class = "chapter-item expanded " > < a href = "../ch1/ch1.html" > < strong aria-hidden = "true" > 1.< / strong > 入门< / a > < / li > < li > < ol class = "section" > < li class = "chapter-item expanded " > < a href = "../ch1/ch1-01.html" > < strong aria-hidden = "true" > 1.1.< / strong > Hello, World< / a > < / li > < li class = "chapter-item expanded " > < a href = "../ch1/ch1-02.html" > < strong aria-hidden = "true" > 1.2.< / strong > 命令行参数< / a > < / li > < li class = "chapter-item expanded " > < a href = "../ch1/ch1-03.html" > < strong aria-hidden = "true" > 1.3.< / strong > 查找重复的行< / a > < / li > < li class = "chapter-item expanded " > < a href = "../ch1/ch1-04.html" > < strong aria-hidden = "true" > 1.4.< / strong > GIF动画< / a > < / li > < li class = "chapter-item expanded " > < a href = "../ch1/ch1-05.html" > < strong aria-hidden = "true" > 1.5.< / strong > 获取URL< / a > < / li > < li class = "chapter-item expanded " > < a href = "../ch1/ch1-06.html" > < strong aria-hidden = "true" > 1.6.< / strong > 并发获取多个URL< / a > < / li > < li class = "chapter-item expanded " > < a href = "../ch1/ch1-07.html" > < strong aria-hidden = "true" > 1.7.< / strong > Web服务< / a > < / li > < li class = "chapter-item expanded " > < a href = "../ch1/ch1-08.html" > < strong aria-hidden = "true" > 1.8.< / strong > 本章要点< / a > < / li > < / ol > < / li > < li class = "chapter-item expanded " > < a href = "../ch2/ch2.html" > < strong aria-hidden = "true" > 2.< / strong > 程序结构< / a > < / li > < li > < ol class = "section" > < li class = "chapter-item expanded " > < a href = "../ch2/ch2-01.html" > < strong aria-hidden = "true" > 2.1.< / strong > 命名< / a > < / li > < li class = "chapter-item expanded " > < a href = "../ch2/ch2-02.html" > < strong aria-hidden = "true" > 2.2.< / strong > 声明< / a > < / li > < li class = "chapter-item expanded " > < a href = "../ch2/ch2-03.html" > < strong aria-hidden = "true" > 2.3.< / strong > 变量< / a > < / li > < li class = "chapter-item expanded " > < a href = "../ch2/ch2-04.html" > < strong aria-hidden = "true" > 2.4.< / strong > 赋值< / a > < / li > < li class = "chapter-item expanded " > < a href = "../ch2/ch2-05.html" > < strong aria-hidden = "true" > 2.5.< / strong > 类型< / a > < / li > < li class = "chapter-item expanded " > < a href = "../ch2/ch2-06.html" > < strong aria-hidden = "true" > 2.6.< / strong > 包和文件< / a > < / li > < li class = "chapter-item expanded " > < a href = "../ch2/ch2-07.html" > < strong aria-hidden = "true" > 2.7.< / strong > 作用域< / a > < / li > < / ol > < / li > < li class = "chapter-item expanded " > < a href = "../ch3/ch3.html" > < strong aria-hidden = "true" > 3.< / strong > 基础数据类型< / a > < / li > < li > < ol class = "section" > < li class = "chapter-item expanded " > < a href = "../ch3/ch3-01.html" > < strong aria-hidden = "true" > 3.1.< / strong > 整型< / a > < / li > < li class = "chapter-item expanded " > < a href = "../ch3/ch3-02.html" > < strong aria-hidden = "true" > 3.2.< / strong > 浮点数< / a > < / li > < li class = "chapter-item expanded " > < a href = "../ch3/ch3-03.html" > < strong aria-hidden = "true" > 3.3.< / strong > 复数< / a > < / li > < li class = "chapter-item expanded " > < a href = "../ch3/ch3-04.html" > < strong aria-hidden = "true" > 3.4.< / strong > 布尔型< / a > < / li > < li class = "chapter-item expanded " > < a href = "../ch3/ch3-05.html" > < strong aria-hidden = "true" > 3.5.< / strong > 字符串< / a > < / li > < li class = "chapter-item expanded " > < a href = "../ch3/ch3-06.html" > < strong aria-hidden = "true" > 3.6.< / strong > 常量< / a > < / li > < / ol > < / li > < li class = "chapter-item expanded " > < a href = "../ch4/ch4.html" > < strong aria-hidden = "true" > 4.< / strong > 复合数据类型< / a > < / li > < li > < ol class = "section" > < li class = "chapter-item expanded " > < a href = "../ch4/ch4-01.html" > < strong aria-hidden = "true" > 4.1.< / strong > 数组< / a > < / li > < li class = "chapter-item expanded " > < a href = "../ch4/ch4-02.html" > < strong aria-hidden = "true" > 4.2.< / strong > Slice< / a > < / li > < li class = "chapter-item expanded " > < a href = "../ch4/ch4-03.html" > < strong aria-hidden = "true" > 4.3.< / strong > Map< / a > < / li > < li class = "chapter-item expanded " > < a href = "../ch4/ch4-04.html" > < strong aria-hidden = "true" > 4.4.< / strong > 结构体< / a > < / li > < li class = "chapter-item expanded " > < a href = "../ch4/ch4-05.html" > < strong aria-hidden = "true" > 4.5.< / strong > JSON< /
2022-08-04 06:59:33 +00:00
< div id = "sidebar-resize-handle" class = "sidebar-resize-handle" > < / div >
< / nav >
< div id = "page-wrapper" class = "page-wrapper" >
< div class = "page" >
< div id = "menu-bar-hover-placeholder" > < / div >
< div id = "menu-bar" class = "menu-bar sticky bordered" >
< div class = "left-buttons" >
< button id = "sidebar-toggle" class = "icon-button" type = "button" title = "Toggle Table of Contents" aria-label = "Toggle Table of Contents" aria-controls = "sidebar" >
< i class = "fa fa-bars" > < / i >
< / button >
< button id = "theme-toggle" class = "icon-button" type = "button" title = "Change theme" aria-label = "Change theme" aria-haspopup = "true" aria-expanded = "false" aria-controls = "theme-list" >
< i class = "fa fa-paint-brush" > < / i >
< / button >
< ul id = "theme-list" class = "theme-popup" aria-label = "Themes" role = "menu" >
< li role = "none" > < button role = "menuitem" class = "theme" id = "light" > Light (default)< / button > < / li >
< li role = "none" > < button role = "menuitem" class = "theme" id = "rust" > Rust< / button > < / li >
< li role = "none" > < button role = "menuitem" class = "theme" id = "coal" > Coal< / button > < / li >
< li role = "none" > < button role = "menuitem" class = "theme" id = "navy" > Navy< / button > < / li >
< li role = "none" > < button role = "menuitem" class = "theme" id = "ayu" > Ayu< / button > < / li >
< / ul >
< button id = "search-toggle" class = "icon-button" type = "button" title = "Search. (Shortkey: s)" aria-label = "Toggle Searchbar" aria-expanded = "false" aria-keyshortcuts = "S" aria-controls = "searchbar" >
< i class = "fa fa-search" > < / i >
< / button >
< / div >
< h1 class = "menu-title" > Go语言圣经< / h1 >
< div class = "right-buttons" >
< a href = "../print.html" title = "Print this book" aria-label = "Print this book" >
< i id = "print-button" class = "fa fa-print" > < / i >
< / a >
< a href = "https://github.com/gopl-zh/gopl-zh.github.com" title = "Git repository" aria-label = "Git repository" >
< i id = "git-repository-button" class = "fa fa-github" > < / i >
< / a >
< a href = "https://github.com/gopl-zh/gopl-zh.github.com/edit/master/./ch12/ch12-03.md" title = "Suggest an edit" aria-label = "Suggest an edit" >
< i id = "git-edit-button" class = "fa fa-edit" > < / i >
< / a >
< / div >
< / div >
< div id = "search-wrapper" class = "hidden" >
< form id = "searchbar-outer" class = "searchbar-outer" >
< input type = "search" id = "searchbar" name = "searchbar" placeholder = "Search this book ..." aria-controls = "searchresults-outer" aria-describedby = "searchresults-header" >
< / form >
< div id = "searchresults-outer" class = "searchresults-outer hidden" >
< div id = "searchresults-header" class = "searchresults-header" > < / div >
< ul id = "searchresults" >
< / ul >
< / div >
< / div >
<!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM -->
< script type = "text/javascript" >
document.getElementById('sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');
document.getElementById('sidebar').setAttribute('aria-hidden', sidebar !== 'visible');
Array.from(document.querySelectorAll('#sidebar a')).forEach(function(link) {
link.setAttribute('tabIndex', sidebar === 'visible' ? 0 : -1);
});
< / script >
< div id = "content" class = "content" >
<!-- Page table of contents -->
< div class = "sidetoc" > < nav class = "pagetoc" > < / nav > < / div >
< main >
<!-- 头部 -->
< ul dir = "auto" >
2022-08-24 10:48:15 +00:00
< li > < em > KusonStack一站式可编程配置技术栈(Go): < a href = "https://github.com/KusionStack/kusion" > https://github.com/KusionStack/kusion< / a > < / em > < / li >
< li > < em > KCL 配置编程语言(Rust): < a href = "https://github.com/KusionStack/KCLVM" > https://github.com/KusionStack/KCLVM< / a > < / em > < / li >
< li > < em > 凹语言™: < a href = "https://github.com/wa-lang/wa" > https://github.com/wa-lang/wa< / a > < / em > < / li >
2022-08-04 06:59:33 +00:00
< / ul >
< hr >
< h2 id = "123-display一个递归的值打印器" > < a class = "header" href = "#123-display一个递归的值打印器" > 12.3. Display, 一个递归的值打印器< / a > < / h2 >
< p > 接下来, 让我们看看如何改善聚合数据类型的显示。我们并不想完全克隆一个fmt.Sprint函数, 我们只是构建一个用于调试用的Display函数: 给定任意一个复杂类型 x, 打印这个值对应的完整结构, 同时标记每个元素的发现路径。让我们从一个例子开始。< / p >
< pre > < code class = "language-Go" > e, _ := eval.Parse(" sqrt(A / pi)" )
Display(" e" , e)
< / code > < / pre >
< p > 在上面的调用中, 传入Display函数的参数是在7.9节一个表达式求值函数返回的语法树。Display函数的输出如下: < / p >
< pre > < code class = "language-Go" > Display e (eval.call):
e.fn = " sqrt"
e.args[0].type = eval.binary
e.args[0].value.op = 47
e.args[0].value.x.type = eval.Var
e.args[0].value.x.value = " A"
e.args[0].value.y.type = eval.Var
e.args[0].value.y.value = " pi"
< / code > < / pre >
< p > 你应该尽量避免在一个包的API中暴露涉及反射的接口。我们将定义一个未导出的display函数用于递归处理工作, 导出的是Display函数, 它只是display函数简单的包装以接受interface{}类型的参数:< / p >
< p > < u > < i > gopl.io/ch12/display< / i > < / u > < / p >
< pre > < code class = "language-Go" > func Display(name string, x interface{}) {
fmt.Printf(" Display %s (%T):\n" , 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 = "language-Go" > func display(path string, v reflect.Value) {
switch v.Kind() {
case reflect.Invalid:
fmt.Printf(" %s = invalid\n" , path)
case reflect.Slice, reflect.Array:
for i := 0; i < v.Len(); i++ {
display(fmt.Sprintf(" %s[%d]" , path, i), v.Index(i))
}
case reflect.Struct:
for i := 0; i < v.NumField(); i++ {
fieldPath := fmt.Sprintf(" %s.%s" , path, v.Type().Field(i).Name)
display(fieldPath, v.Field(i))
}
case reflect.Map:
for _, key := range v.MapKeys() {
display(fmt.Sprintf(" %s[%s]" , path,
formatAtom(key)), v.MapIndex(key))
}
case reflect.Ptr:
if v.IsNil() {
fmt.Printf(" %s = nil\n" , path)
} else {
display(fmt.Sprintf(" (*%s)" , path), v.Elem())
}
case reflect.Interface:
if v.IsNil() {
fmt.Printf(" %s = nil\n" , path)
} else {
fmt.Printf(" %s.type = %s\n" , path, v.Elem().Type())
display(path+" .value" , v.Elem())
}
default: // basic types, channels, funcs
fmt.Printf(" %s = %s\n" , 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的一个key。和往常一样, 遍历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 = "language-Go" > type Movie struct {
Title, Subtitle string
Year int
Color bool
Actor map[string]string
Oscars []string
Sequel *string
}
< / code > < / pre >
< p > 让我们声明一个该类型的变量, 然后看看Display函数如何显示它: < / p >
< pre > < code class = "language-Go" > strangelove := Movie{
Title: " Dr. Strangelove" ,
Subtitle: " How I Learned to Stop Worrying and Love the Bomb" ,
Year: 1964,
Color: false,
Actor: map[string]string{
" Dr. Strangelove" : " Peter Sellers" ,
" Grp. Capt. Lionel Mandrake" : " Peter Sellers" ,
" Pres. Merkin Muffley" : " Peter Sellers" ,
" Gen. Buck Turgidson" : " George C. Scott" ,
" Brig. Gen. Jack D. Ripper" : " Sterling Hayden" ,
`Maj. T.J. " King" Kong`: " Slim Pickens" ,
},
Oscars: []string{
" Best Actor (Nomin.)" ,
" Best Adapted Screenplay (Nomin.)" ,
" Best Director (Nomin.)" ,
" Best Picture (Nomin.)" ,
},
}
< / code > < / pre >
< p > Display(" strangelove" , strangelove)调用将显示( strangelove电影对应的中文名是《奇爱博士》) : < / p >
< pre > < code class = "language-Go" > Display strangelove (display.Movie):
strangelove.Title = " Dr. Strangelove"
strangelove.Subtitle = " How I Learned to Stop Worrying and Love the Bomb"
strangelove.Year = 1964
strangelove.Color = false
strangelove.Actor[" Gen. Buck Turgidson" ] = " George C. Scott"
strangelove.Actor[" Brig. Gen. Jack D. Ripper" ] = " Sterling Hayden"
strangelove.Actor[" Maj. T.J. \" King\" Kong" ] = " Slim Pickens"
strangelove.Actor[" Dr. Strangelove" ] = " Peter Sellers"
strangelove.Actor[" Grp. Capt. Lionel Mandrake" ] = " Peter Sellers"
strangelove.Actor[" Pres. Merkin Muffley" ] = " Peter Sellers"
strangelove.Oscars[0] = " Best Actor (Nomin.)"
strangelove.Oscars[1] = " Best Adapted Screenplay (Nomin.)"
strangelove.Oscars[2] = " Best Director (Nomin.)"
strangelove.Oscars[3] = " Best Picture (Nomin.)"
strangelove.Sequel = nil
< / code > < / pre >
< p > 我们也可以使用Display函数来显示标准库中类型的内部结构, 例如< code > *os.File< / code > 类型:< / p >
< pre > < code class = "language-Go" > Display(" os.Stderr" , os.Stderr)
// Output:
// Display os.Stderr (*os.File):
// (*(*os.Stderr).file).fd = 2
// (*(*os.Stderr).file).name = " /dev/stderr"
// (*(*os.Stderr).file).nepipe = 0
< / code > < / pre >
< p > 可以看出, 反射能够访问到结构体中未导出的成员。需要当心的是这个例子的输出在不同操作系统上可能是不同的, 并且随着标准库的发展也可能导致结果不同。( 这也是将这些成员定义为私有成员的原因之一! ) 我们甚至可以用Display函数来显示reflect.Value 的内部构造(在这里设置为< code > *os.File< / code > 的类型描述体)。< code > Display(" rV" , reflect.ValueOf(os.Stderr))< / code > 调用的输出如下,当然不同环境得到的结果可能有差异:< / p >
< pre > < code class = "language-Go" > Display rV (reflect.Value):
(*rV.typ).size = 8
(*rV.typ).hash = 871609668
(*rV.typ).align = 8
(*rV.typ).fieldAlign = 8
(*rV.typ).kind = 22
(*(*rV.typ).string) = " *os.File"
(*(*(*rV.typ).uncommonType).methods[0].name) = " Chdir"
(*(*(*(*rV.typ).uncommonType).methods[0].mtyp).string) = " func() error"
(*(*(*(*rV.typ).uncommonType).methods[0].typ).string) = " func(*os.File) error"
...
< / code > < / pre >
< p > 观察下面两个例子的区别:< / p >
< pre > < code class = "language-Go" > var i interface{} = 3
Display(" i" , i)
// Output:
// Display i (int):
// i = 3
Display(" & i" , & i)
// Output:
// Display & i (*interface {}):
// (*& i).type = int
// (*& i).value = 3
< / code > < / pre >
< p > 在第一个例子中, Display函数调用reflect.ValueOf(i), 它返回一个Int类型的值。正如我们在12.2节中提到的, reflect.ValueOf总是返回一个具体类型的 Value, 因为它是从一个接口值提取的内容。< / p >
< p > 在第二个例子中, Display函数调用的是reflect.ValueOf(& i), 它返回一个指向i的指针, 对应Ptr类型。在switch的Ptr分支中, 对这个值调用 Elem 方法, 返回一个Value来表示变量 i 本身, 对应Interface类型。像这样一个间接获得的Value, 可能代表任意类型的值, 包括接口类型。display函数递归调用自身, 这次它分别打印了这个接口的动态类型和值。< / p >
< p > 对于目前的实现, 如果遇到对象图中含有回环, Display将会陷入死循环, 例如下面这个首尾相连的链表: < / p >
< pre > < code class = "language-Go" > // a struct that points to itself
type Cycle struct{ Value int; Tail *Cycle }
var c Cycle
c = Cycle{42, & c}
Display(" c" , c)
< / code > < / pre >
< p > Display会永远不停地进行深度递归打印: < / p >
< pre > < code class = "language-Go" > Display c (display.Cycle):
c.Value = 42
(*c.Tail).Value = 42
(*(*c.Tail).Tail).Value = 42
(*(*(*c.Tail).Tail).Tail).Value = 42
...ad infinitum...
< / code > < / pre >
< p > 许多Go语言程序都包含了一些循环的数据。让Display支持这类带环的数据结构需要些技巧, 需要额外记录迄今访问的路径; 相应会带来成本。通用的解决方案是采用 unsafe 的语言特性, 我们将在13.3节看到具体的解决方案。< / p >
< p > 带环的数据结构很少会对fmt.Sprint函数造成问题, 因为它很少尝试打印完整的数据结构。例如, 当它遇到一个指针的时候, 它只是简单地打印指针的数字值。在打印包含自身的slice或map时可能卡住, 但是这种情况很罕见, 不值得付出为了处理回环所需的开销。< / p >
< p > < strong > 练习 12.1: < / strong > 扩展Display函数, 使它可以显示包含以结构体或数组作为map的key类型的值。< / p >
< p > < strong > 练习 12.2: < / strong > 增强display函数的稳健性, 通过记录边界的步数来确保在超出一定限制后放弃递归。( 在13.3节,我们会看到另一种探测数据结构是否存在环的技术。)< / p >
<!-- 公众号 -->
< hr >
< table >
< tr >
< td >
< img width = "222px" src = "https://chai2010.cn/advanced-go-programming-book/css.png" >
< / td >
< td >
< img width = "222px" src = "https://chai2010.cn/advanced-go-programming-book/cch.png" >
< / td >
< / tr >
< / table >
< div id = "giscus-container" > < / div >
< footer class = "page-footer" >
< span > © 2015-2016 | < a href = "https://github.com/gopl-zh" > Go语言圣经中文版< / a > , 仅学习交流使用< / span >
< / footer >
< / main >
< nav class = "nav-wrapper" aria-label = "Page navigation" >
<!-- Mobile navigation buttons -->
< a rel = "prev" href = "../ch12/ch12-02.html" class = "mobile-nav-chapters previous" title = "Previous chapter" aria-label = "Previous chapter" aria-keyshortcuts = "Left" >
< i class = "fa fa-angle-left" > < / i >
< / a >
< a rel = "next" href = "../ch12/ch12-04.html" class = "mobile-nav-chapters next" title = "Next chapter" aria-label = "Next chapter" aria-keyshortcuts = "Right" >
< i class = "fa fa-angle-right" > < / i >
< / a >
< div style = "clear: both" > < / div >
< / nav >
< / div >
< / div >
< nav class = "nav-wide-wrapper" aria-label = "Page navigation" >
< a rel = "prev" href = "../ch12/ch12-02.html" class = "nav-chapters previous" title = "Previous chapter" aria-label = "Previous chapter" aria-keyshortcuts = "Left" >
< i class = "fa fa-angle-left" > < / i >
< / a >
< a rel = "next" href = "../ch12/ch12-04.html" class = "nav-chapters next" title = "Next chapter" aria-label = "Next chapter" aria-keyshortcuts = "Right" >
< i class = "fa fa-angle-right" > < / i >
< / a >
< / nav >
< / div >
< script type = "text/javascript" >
window.playground_copyable = true;
< / script >
< script src = "../elasticlunr.min.js" type = "text/javascript" charset = "utf-8" > < / script >
< script src = "../mark.min.js" type = "text/javascript" charset = "utf-8" > < / script >
< script src = "../searcher.js" type = "text/javascript" charset = "utf-8" > < / script >
< script src = "../clipboard.min.js" type = "text/javascript" charset = "utf-8" > < / script >
< script src = "../highlight.js" type = "text/javascript" charset = "utf-8" > < / script >
< script src = "../book.js" type = "text/javascript" charset = "utf-8" > < / script >
< script type = "text/javascript" charset = "utf-8" >
var pagePath = "ch12/ch12-03.md"
< / script >
<!-- Custom JS scripts -->
< script type = "text/javascript" src = "../js/custom.js" > < / script >
< script type = "text/javascript" src = "../js/bigPicture.js" > < / script >
< / body >
< / html >