2022-08-04 06:59:33 +00:00
<!DOCTYPE HTML>
< html lang = "zh" class = "sidebar-visible no-js light" >
< head >
2024-08-13 00:15:53 +00:00
<!-- Book generated using https://github.com/wa - lang/wabook -->
2022-08-04 06:59:33 +00:00
< meta charset = "UTF-8" >
< title > Display递归打印 - Go语言圣经< / title >
<!-- Custom HTML head -->
< meta content = "text/html; charset=utf-8" http-equiv = "Content-Type" >
2024-08-08 07:47:03 +00:00
< meta name = "description" content = "" >
2022-08-04 06:59:33 +00:00
< meta name = "viewport" content = "width=device-width, initial-scale=1" >
< meta name = "theme-color" content = "#ffffff" / >
2024-08-08 07:47:03 +00:00
< link rel = "icon" href = "../favicon.svg" >
< link rel = "shortcut icon" href = "../favicon.png" >
2024-08-13 00:15:53 +00:00
< link rel = "stylesheet" href = "../static/wabook/css/variables.css" >
< link rel = "stylesheet" href = "../static/wabook/css/general.css" >
< link rel = "stylesheet" href = "../static/wabook/css/chrome.css" >
< link rel = "stylesheet" href = "../static/wabook/css/print.css" media = "print" >
2022-08-04 06:59:33 +00:00
<!-- Fonts -->
2024-08-13 00:15:53 +00:00
< link rel = "stylesheet" href = "../static/wabook/FontAwesome/css/font-awesome.css" >
< link rel = "stylesheet" href = "../static/wabook/fonts/fonts.css" >
2022-08-04 06:59:33 +00:00
<!-- Highlight.js Stylesheets -->
2024-08-13 00:15:53 +00:00
< link rel = "stylesheet" href = "../static/wabook/highlight.css" >
< link rel = "stylesheet" href = "../static/wabook/tomorrow-night.css" >
< link rel = "stylesheet" href = "../static/wabook/ayu-highlight.css" >
2022-08-04 06:59:33 +00:00
<!-- Custom theme stylesheets -->
< / 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 {
2024-08-13 00:15:53 +00:00
var theme = localStorage.getItem('wabook-theme');
var sidebar = localStorage.getItem('wabook-sidebar');
2022-08-04 06:59:33 +00:00
if (theme.startsWith('"') & & theme.endsWith('"')) {
2024-08-13 00:15:53 +00:00
localStorage.setItem('wabook-theme', theme.slice(1, theme.length - 1));
2022-08-04 06:59:33 +00:00
}
if (sidebar.startsWith('"') & & sidebar.endsWith('"')) {
2024-08-13 00:15:53 +00:00
localStorage.setItem('wabook-sidebar', sidebar.slice(1, sidebar.length - 1));
2022-08-04 06:59:33 +00:00
}
} catch (e) { }
< / script >
<!-- Set the theme before any content is loaded, prevents flash -->
< script type = "text/javascript" >
var theme;
2024-08-13 00:15:53 +00:00
try { theme = localStorage.getItem('wabook-theme'); } catch(e) { }
2022-08-04 06:59:33 +00:00
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) {
2024-08-13 00:15:53 +00:00
try { sidebar = localStorage.getItem('wabook-sidebar'); } catch(e) { }
2022-08-04 06:59:33 +00:00
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" >
2024-08-08 07:47:03 +00:00
< ol class = "chapter" >
< li class = "chapter-item expanded " >
< a href = "../index.html" > Go语言圣经< / a >
< / li >
< li class = "chapter-item expanded " >
< a href = "../preface-zh.html" > 译者序< / a >
< / li >
< li class = "chapter-item expanded " >
< a href = "../preface.html" > 前言< / a >
< / li >
< li class = "chapter-item expanded " >
< a href = "../ch1/ch1.html" > < strong aria-hidden = "true" > 1.< / strong > 入门< / a >
< / 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 class = "chapter-item expanded " >
< a href = "../ch2/ch2.html" > < strong aria-hidden = "true" > 2.< / strong > 程序结构< / a >
< / 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 class = "chapter-item expanded " >
< a href = "../ch3/ch3.html" > < strong aria-hidden = "true" > 3.< / strong > 基础数据类型< / a >
< / 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 class = "chapter-item expanded " >
< a href = "../ch4/ch4.html" > < strong aria-hidden = "true" > 4.< / strong > 复合数据类型< / a >
< / 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< / a >
< / li >
< li class = "chapter-item expanded " >
< a href = "../ch4/ch4-06.html" > < strong aria-hidden = "true" > 4.6.< / strong > 文本和HTML模板< / a >
< / li >
< / ol >
< li class = "chapter-item expanded " >
< a href = "../ch5/ch5.html" > < strong aria-hidden = "true" > 5.< / strong > 函数< / a >
< / li >
< ol class = "section" >
< li class = "chapter-item expanded " >
< a href = "../ch5/ch5-01.html" > < strong aria-hidden = "true" > 5.1.< / strong > 函数声明< / a >
< / li >
< li class = "chapter-item expanded " >
< a href = "../ch5/ch5-02.html" > < strong aria-hidden = "true" > 5.2.< / strong > 递归< / a >
< / li >
< li class = "chapter-item expanded " >
< a href = "../ch5/ch5-03.html" > < strong aria-hidden = "true" > 5.3.< / strong > 多返回值< / a >
< / li >
< li class = "chapter-item expanded " >
< a href = "../ch5/ch5-04.html" > < strong aria-hidden = "true" > 5.4.< / strong > 错误< / a >
< / li >
< li class = "chapter-item expanded " >
< a href = "../ch5/ch5-05.html" > < strong aria-hidden = "true" > 5.5.< / strong > 函数值< / a >
< / li >
< li class = "chapter-item expanded " >
< a href = "../ch5/ch5-06.html" > < strong aria-hidden = "true" > 5.6.< / strong > 匿名函数< / a >
< / li >
< li class = "chapter-item expanded " >
< a href = "../ch5/ch5-07.html" > < strong aria-hidden = "true" > 5.7.< / strong > 可变参数< / a >
< / li >
< li class = "chapter-item expanded " >
< a href = "../ch5/ch5-08.html" > < strong aria-hidden = "true" > 5.8.< / strong > Deferred函数< / a >
< / li >
< li class = "chapter-item expanded " >
< a href = "../ch5/ch5-09.html" > < strong aria-hidden = "true" > 5.9.< / strong > Panic异常< / a >
< / li >
< li class = "chapter-item expanded " >
< a href = "../ch5/ch5-10.html" > < strong aria-hidden = "true" > 5.10.< / strong > Recover捕获异常< / a >
< / li >
< / ol >
< li class = "chapter-item expanded " >
< a href = "../ch6/ch6.html" > < strong aria-hidden = "true" > 6.< / strong > 方法< / a >
< / li >
< ol class = "section" >
< li class = "chapter-item expanded " >
< a href = "../ch6/ch6-01.html" > < strong aria-hidden = "true" > 6.1.< / strong > 方法声明< / a >
< / li >
< li class = "chapter-item expanded " >
< a href = "../ch6/ch6-02.html" > < strong aria-hidden = "true" > 6.2.< / strong > 基于指针对象的方法< / a >
< / li >
< li class = "chapter-item expanded " >
< a href = "../ch6/ch6-03.html" > < strong aria-hidden = "true" > 6.3.< / strong > 通过嵌入结构体来扩展类型< / a >
< / li >
< li class = "chapter-item expanded " >
< a href = "../ch6/ch6-04.html" > < strong aria-hidden = "true" > 6.4.< / strong > 方法值和方法表达式< / a >
< / li >
< li class = "chapter-item expanded " >
< a href = "../ch6/ch6-05.html" > < strong aria-hidden = "true" > 6.5.< / strong > 示例: Bit数组< / a >
< / li >
< li class = "chapter-item expanded " >
< a href = "../ch6/ch6-06.html" > < strong aria-hidden = "true" > 6.6.< / strong > 封装< / a >
< / li >
< / ol >
< li class = "chapter-item expanded " >
< a href = "../ch7/ch7.html" > < strong aria-hidden = "true" > 7.< / strong > 接口< / a >
< / li >
< ol class = "section" >
< li class = "chapter-item expanded " >
< a href = "../ch7/ch7-01.html" > < strong aria-hidden = "true" > 7.1.< / strong > 接口是合约< / a >
< / li >
< li class = "chapter-item expanded " >
< a href = "../ch7/ch7-02.html" > < strong aria-hidden = "true" > 7.2.< / strong > 接口类型< / a >
< / li >
< li class = "chapter-item expanded " >
< a href = "../ch7/ch7-03.html" > < strong aria-hidden = "true" > 7.3.< / strong > 实现接口的条件< / a >
< / li >
< li class = "chapter-item expanded " >
< a href = "../ch7/ch7-04.html" > < strong aria-hidden = "true" > 7.4.< / strong > flag.Value接口< / a >
< / li >
< li class = "chapter-item expanded " >
< a href = "../ch7/ch7-05.html" > < strong aria-hidden = "true" > 7.5.< / strong > 接口值< / a >
< / li >
< li class = "chapter-item expanded " >
< a href = "../ch7/ch7-06.html" > < strong aria-hidden = "true" > 7.6.< / strong > sort.Interface接口< / a >
< / li >
< li class = "chapter-item expanded " >
< a href = "../ch7/ch7-07.html" > < strong aria-hidden = "true" > 7.7.< / strong > http.Handler接口< / a >
< / li >
< li class = "chapter-item expanded " >
< a href = "../ch7/ch7-08.html" > < strong aria-hidden = "true" > 7.8.< / strong > error接口< / a >
< / li >
< li class = "chapter-item expanded " >
< a href = "../ch7/ch7-09.html" > < strong aria-hidden = "true" > 7.9.< / strong > 示例: 表达式求值< / a >
< / li >
< li class = "chapter-item expanded " >
< a href = "../ch7/ch7-10.html" > < strong aria-hidden = "true" > 7.10.< / strong > 类型断言< / a >
< / li >
< li class = "chapter-item expanded " >
< a href = "../ch7/ch7-11.html" > < strong aria-hidden = "true" > 7.11.< / strong > 基于类型断言识别错误类型< / a >
< / li >
< li class = "chapter-item expanded " >
< a href = "../ch7/ch7-12.html" > < strong aria-hidden = "true" > 7.12.< / strong > 通过类型断言查询接口< / a >
< / li >
< li class = "chapter-item expanded " >
< a href = "../ch7/ch7-13.html" > < strong aria-hidden = "true" > 7.13.< / strong > 类型分支< / a >
< / li >
< li class = "chapter-item expanded " >
< a href = "../ch7/ch7-14.html" > < strong aria-hidden = "true" > 7.14.< / strong > 示例: 基于标记的XML解码< / a >
< / li >
< li class = "chapter-item expanded " >
< a href = "../ch7/ch7-15.html" > < strong aria-hidden = "true" > 7.15.< / strong > 补充几点< / a >
< / li >
< / ol >
< li class = "chapter-item expanded " >
< a href = "../ch8/ch8.html" > < strong aria-hidden = "true" > 8.< / strong > Goroutines和Channels< / a >
< / li >
< ol class = "section" >
< li class = "chapter-item expanded " >
< a href = "../ch8/ch8-01.html" > < strong aria-hidden = "true" > 8.1.< / strong > Goroutines< / a >
< / li >
< li class = "chapter-item expanded " >
< a href = "../ch8/ch8-02.html" > < strong aria-hidden = "true" > 8.2.< / strong > 示例: 并发的Clock服务< / a >
< / li >
< li class = "chapter-item expanded " >
< a href = "../ch8/ch8-03.html" > < strong aria-hidden = "true" > 8.3.< / strong > 示例: 并发的Echo服务< / a >
< / li >
< li class = "chapter-item expanded " >
< a href = "../ch8/ch8-04.html" > < strong aria-hidden = "true" > 8.4.< / strong > Channels< / a >
< / li >
< li class = "chapter-item expanded " >
< a href = "../ch8/ch8-05.html" > < strong aria-hidden = "true" > 8.5.< / strong > 并发的循环< / a >
< / li >
< li class = "chapter-item expanded " >
< a href = "../ch8/ch8-06.html" > < strong aria-hidden = "true" > 8.6.< / strong > 示例: 并发的Web爬虫< / a >
< / li >
< li class = "chapter-item expanded " >
< a href = "../ch8/ch8-07.html" > < strong aria-hidden = "true" > 8.7.< / strong > 基于select的多路复用< / a >
< / li >
< li class = "chapter-item expanded " >
< a href = "../ch8/ch8-08.html" > < strong aria-hidden = "true" > 8.8.< / strong > 示例: 并发的目录遍历< / a >
< / li >
< li class = "chapter-item expanded " >
< a href = "../ch8/ch8-09.html" > < strong aria-hidden = "true" > 8.9.< / strong > 并发的退出< / a >
< / li >
< li class = "chapter-item expanded " >
< a href = "../ch8/ch8-10.html" > < strong aria-hidden = "true" > 8.10.< / strong > 示例: 聊天服务< / a >
< / li >
< / ol >
< li class = "chapter-item expanded " >
< a href = "../ch9/ch9.html" > < strong aria-hidden = "true" > 9.< / strong > 基于共享变量的并发< / a >
< / li >
< ol class = "section" >
< li class = "chapter-item expanded " >
< a href = "../ch9/ch9-01.html" > < strong aria-hidden = "true" > 9.1.< / strong > 竞争条件< / a >
< / li >
< li class = "chapter-item expanded " >
< a href = "../ch9/ch9-02.html" > < strong aria-hidden = "true" > 9.2.< / strong > sync.Mutex互斥锁< / a >
< / li >
< li class = "chapter-item expanded " >
< a href = "../ch9/ch9-03.html" > < strong aria-hidden = "true" > 9.3.< / strong > sync.RWMutex读写锁< / a >
< / li >
< li class = "chapter-item expanded " >
< a href = "../ch9/ch9-04.html" > < strong aria-hidden = "true" > 9.4.< / strong > 内存同步< / a >
< / li >
< li class = "chapter-item expanded " >
< a href = "../ch9/ch9-05.html" > < strong aria-hidden = "true" > 9.5.< / strong > sync.Once惰性初始化< / a >
< / li >
< li class = "chapter-item expanded " >
< a href = "../ch9/ch9-06.html" > < strong aria-hidden = "true" > 9.6.< / strong > 竞争条件检测< / a >
< / li >
< li class = "chapter-item expanded " >
< a href = "../ch9/ch9-07.html" > < strong aria-hidden = "true" > 9.7.< / strong > 示例: 并发的非阻塞缓存< / a >
< / li >
< li class = "chapter-item expanded " >
< a href = "../ch9/ch9-08.html" > < strong aria-hidden = "true" > 9.8.< / strong > Goroutines和线程< / a >
< / li >
< / ol >
< li class = "chapter-item expanded " >
< a href = "../ch10/ch10.html" > < strong aria-hidden = "true" > 10.< / strong > 包和工具< / a >
< / li >
< ol class = "section" >
< li class = "chapter-item expanded " >
< a href = "../ch10/ch10-01.html" > < strong aria-hidden = "true" > 10.1.< / strong > 包简介< / a >
< / li >
< li class = "chapter-item expanded " >
< a href = "../ch10/ch10-02.html" > < strong aria-hidden = "true" > 10.2.< / strong > 导入路径< / a >
< / li >
< li class = "chapter-item expanded " >
< a href = "../ch10/ch10-03.html" > < strong aria-hidden = "true" > 10.3.< / strong > 包声明< / a >
< / li >
< li class = "chapter-item expanded " >
< a href = "../ch10/ch10-04.html" > < strong aria-hidden = "true" > 10.4.< / strong > 导入声明< / a >
< / li >
< li class = "chapter-item expanded " >
< a href = "../ch10/ch10-05.html" > < strong aria-hidden = "true" > 10.5.< / strong > 包的匿名导入< / a >
< / li >
< li class = "chapter-item expanded " >
< a href = "../ch10/ch10-06.html" > < strong aria-hidden = "true" > 10.6.< / strong > 包和命名< / a >
< / li >
< li class = "chapter-item expanded " >
< a href = "../ch10/ch10-07.html" > < strong aria-hidden = "true" > 10.7.< / strong > 工具< / a >
< / li >
< / ol >
< li class = "chapter-item expanded " >
< a href = "../ch11/ch11.html" > < strong aria-hidden = "true" > 11.< / strong > 测试< / a >
< / li >
< ol class = "section" >
< li class = "chapter-item expanded " >
< a href = "../ch11/ch11-01.html" > < strong aria-hidden = "true" > 11.1.< / strong > go test< / a >
< / li >
< li class = "chapter-item expanded " >
< a href = "../ch11/ch11-02.html" > < strong aria-hidden = "true" > 11.2.< / strong > 测试函数< / a >
< / li >
< li class = "chapter-item expanded " >
< a href = "../ch11/ch11-03.html" > < strong aria-hidden = "true" > 11.3.< / strong > 测试覆盖率< / a >
< / li >
< li class = "chapter-item expanded " >
< a href = "../ch11/ch11-04.html" > < strong aria-hidden = "true" > 11.4.< / strong > 基准测试< / a >
< / li >
< li class = "chapter-item expanded " >
< a href = "../ch11/ch11-05.html" > < strong aria-hidden = "true" > 11.5.< / strong > 剖析< / a >
< / li >
< li class = "chapter-item expanded " >
< a href = "../ch11/ch11-06.html" > < strong aria-hidden = "true" > 11.6.< / strong > 示例函数< / a >
< / li >
< / ol >
< li class = "chapter-item expanded " >
< a href = "../ch12/ch12.html" > < strong aria-hidden = "true" > 12.< / strong > 反射< / a >
< / li >
< ol class = "section" >
< li class = "chapter-item expanded " >
< a href = "../ch12/ch12-01.html" > < strong aria-hidden = "true" > 12.1.< / strong > 为何需要反射?< / a >
< / li >
< li class = "chapter-item expanded " >
< a href = "../ch12/ch12-02.html" > < strong aria-hidden = "true" > 12.2.< / strong > reflect.Type和reflect.Value< / a >
< / li >
< li class = "chapter-item expanded " >
< a href = "../ch12/ch12-03.html" class = "active" > < strong aria-hidden = "true" > 12.3.< / strong > Display递归打印< / a >
< / li >
< li class = "chapter-item expanded " >
< a href = "../ch12/ch12-04.html" > < strong aria-hidden = "true" > 12.4.< / strong > 示例: 编码S表达式< / a >
< / li >
< li class = "chapter-item expanded " >
< a href = "../ch12/ch12-05.html" > < strong aria-hidden = "true" > 12.5.< / strong > 通过reflect.Value修改值< / a >
< / li >
< li class = "chapter-item expanded " >
< a href = "../ch12/ch12-06.html" > < strong aria-hidden = "true" > 12.6.< / strong > 示例: 解码S表达式< / a >
< / li >
< li class = "chapter-item expanded " >
< a href = "../ch12/ch12-07.html" > < strong aria-hidden = "true" > 12.7.< / strong > 获取结构体字段标签< / a >
< / li >
< li class = "chapter-item expanded " >
< a href = "../ch12/ch12-08.html" > < strong aria-hidden = "true" > 12.8.< / strong > 显示一个类型的方法集< / a >
< / li >
< li class = "chapter-item expanded " >
< a href = "../ch12/ch12-09.html" > < strong aria-hidden = "true" > 12.9.< / strong > 几点忠告< / a >
< / li >
< / ol >
< li class = "chapter-item expanded " >
< a href = "../ch13/ch13.html" > < strong aria-hidden = "true" > 13.< / strong > 底层编程< / a >
< / li >
< ol class = "section" >
< li class = "chapter-item expanded " >
< a href = "../ch13/ch13-01.html" > < strong aria-hidden = "true" > 13.1.< / strong > unsafe.Sizeof, Alignof 和 Offsetof< / a >
< / li >
< li class = "chapter-item expanded " >
< a href = "../ch13/ch13-02.html" > < strong aria-hidden = "true" > 13.2.< / strong > unsafe.Pointer< / a >
< / li >
< li class = "chapter-item expanded " >
< a href = "../ch13/ch13-03.html" > < strong aria-hidden = "true" > 13.3.< / strong > 示例: 深度相等判断< / a >
< / li >
< li class = "chapter-item expanded " >
< a href = "../ch13/ch13-04.html" > < strong aria-hidden = "true" > 13.4.< / strong > 通过cgo调用C代码< / a >
< / li >
< li class = "chapter-item expanded " >
< a href = "../ch13/ch13-05.html" > < strong aria-hidden = "true" > 13.5.< / strong > 几点忠告< / a >
< / li >
< / ol >
< li class = "chapter-item expanded " >
< a href = "../appendix/appendix.html" > < strong aria-hidden = "true" > 14.< / strong > 附录< / a >
< / li >
< ol class = "section" >
< li class = "chapter-item expanded " >
< a href = "../appendix/appendix-a-errata.html" > < strong aria-hidden = "true" > 14.1.< / strong > 附录A: 原文勘误< / a >
< / li >
< li class = "chapter-item expanded " >
< a href = "../appendix/appendix-b-author.html" > < strong aria-hidden = "true" > 14.2.< / strong > 附录B: 作者译者< / a >
< / li >
< li class = "chapter-item expanded " >
< a href = "../appendix/appendix-c-cpoyright.html" > < strong aria-hidden = "true" > 14.3.< / strong > 附录C: 译文授权< / a >
< / li >
< li class = "chapter-item expanded " >
< a href = "../appendix/appendix-d-translations.html" > < strong aria-hidden = "true" > 14.4.< / strong > 附录D: 其它语言< / a >
< / li >
< / ol >
< / ol >
< / div >
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 = "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 >
2024-08-08 07:47:03 +00:00
< / div >
2022-08-04 06:59:33 +00:00
2024-08-08 07:47:03 +00:00
< h1 class = "menu-title" > < a href = "../index.html" > Go语言圣经< / a > < / h1 >
2022-08-04 06:59:33 +00:00
< div class = "right-buttons" >
2024-08-08 07:47:03 +00:00
< a href = "https://github.com/gopl-zh/gopl-zh.github.com" title = "Git repository" aria-label = "Git repository" >
2022-08-04 06:59:33 +00:00
< i id = "git-repository-button" class = "fa fa-github" > < / i >
< / a >
2024-08-08 07:47:03 +00:00
< 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" >
2022-08-04 06:59:33 +00:00
< i id = "git-edit-button" class = "fa fa-edit" > < / i >
< / a >
< / 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 >
2024-08-08 07:47:03 +00:00
2022-08-04 06:59:33 +00:00
< main >
2024-08-13 00:15:53 +00:00
< ul dir = "auto" > < li > < em > 凹语言(Go实现, 面向WASM设计): < a href = "https://github.com/wa-lang/wa" > https://github.com/wa-lang/wa< / a > < / em > < / li > < li > < em > WaBook(Go语言实现的MD电子书构建工具): < a href = "https://github.com/wa-lang/wabook" > https://github.com/wa-lang/wabook< / a > < / em > < / li > < / ul > < hr >
2022-08-04 06:59:33 +00:00
2024-08-08 07:47:03 +00:00
< h2 > 12.3. Display, 一个递归的值打印器< / h2 >
2022-08-04 06:59:33 +00:00
< p > 接下来, 让我们看看如何改善聚合数据类型的显示。我们并不想完全克隆一个fmt.Sprint函数, 我们只是构建一个用于调试用的Display函数: 给定任意一个复杂类型 x, 打印这个值对应的完整结构, 同时标记每个元素的发现路径。让我们从一个例子开始。< / p >
2024-08-08 07:47:03 +00:00
< pre > < code class = "language-Go" > e, _ := eval.Parse(" sqrt(A / pi)" )
Display(" e" , e)
2022-08-04 06:59:33 +00:00
< / code > < / pre >
< p > 在上面的调用中, 传入Display函数的参数是在7.9节一个表达式求值函数返回的语法树。Display函数的输出如下: < / p >
2024-08-08 07:47:03 +00:00
< 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"
2022-08-04 06:59:33 +00:00
< / code > < / pre >
< p > 你应该尽量避免在一个包的API中暴露涉及反射的接口。我们将定义一个未导出的display函数用于递归处理工作, 导出的是Display函数, 它只是display函数简单的包装以接受interface{}类型的参数:< / p >
2024-08-08 07:47:03 +00:00
< p > <!-- raw HTML omitted --> <!-- raw HTML omitted --> gopl.io/ch12/display<!-- raw HTML omitted --> <!-- raw HTML omitted --> < / 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))
}
2022-08-04 06:59:33 +00:00
< / code > < / pre >
< p > 在display函数中, 我们使用了前面定义的打印基础类型——基本类型、函数和chan等——元素值的formatAtom函数, 但是我们会使用reflect.Value的方法来递归显示复杂类型的每一个成员。在递归下降过程中, path字符串, 从最开始传入的起始值( 这里是“e”) , 将逐步增长来表示是如何达到当前值( 例如“e.args[0].value”) 的。< / p >
< p > 因为我们不再模拟fmt.Sprint函数, 我们将直接使用fmt包来简化我们的例子实现。< / p >
2024-08-08 07:47:03 +00:00
< 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))
}
}
2022-08-04 06:59:33 +00:00
< / 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 >
2024-08-08 07:47:03 +00:00
< pre > < code class = "language-Go" > type Movie struct {
Title, Subtitle string
Year int
Color bool
Actor map[string]string
Oscars []string
Sequel *string
}
2022-08-04 06:59:33 +00:00
< / code > < / pre >
< p > 让我们声明一个该类型的变量, 然后看看Display函数如何显示它: < / p >
2024-08-08 07:47:03 +00:00
< 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.)" ,
},
}
2022-08-04 06:59:33 +00:00
< / code > < / pre >
< p > Display(" strangelove" , strangelove)调用将显示( strangelove电影对应的中文名是《奇爱博士》) : < / p >
2024-08-08 07:47:03 +00:00
< 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
2022-08-04 06:59:33 +00:00
< / code > < / pre >
< p > 我们也可以使用Display函数来显示标准库中类型的内部结构, 例如< code > *os.File< / code > 类型:< / p >
2024-08-08 07:47:03 +00:00
< 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
2022-08-04 06:59:33 +00:00
< / code > < / pre >
< p > 可以看出, 反射能够访问到结构体中未导出的成员。需要当心的是这个例子的输出在不同操作系统上可能是不同的, 并且随着标准库的发展也可能导致结果不同。( 这也是将这些成员定义为私有成员的原因之一! ) 我们甚至可以用Display函数来显示reflect.Value 的内部构造(在这里设置为< code > *os.File< / code > 的类型描述体)。< code > Display(" rV" , reflect.ValueOf(os.Stderr))< / code > 调用的输出如下,当然不同环境得到的结果可能有差异:< / p >
2024-08-08 07:47:03 +00:00
< 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"
...
2022-08-04 06:59:33 +00:00
< / code > < / pre >
< p > 观察下面两个例子的区别:< / p >
2024-08-08 07:47:03 +00:00
< 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
2022-08-04 06:59:33 +00:00
< / 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 >
2024-08-08 07:47:03 +00:00
< 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)
2022-08-04 06:59:33 +00:00
< / code > < / pre >
< p > Display会永远不停地进行深度递归打印: < / p >
2024-08-08 07:47:03 +00:00
< 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...
2022-08-04 06:59:33 +00:00
< / 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 >
2024-08-08 07:47:03 +00:00
< 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 >
2022-08-04 06:59:33 +00:00
2024-08-08 07:47:03 +00:00
< footer class = "page-footer" >
< span > © 2015-2016 | < a href = "https://github.com/gopl-zh" > Go语言圣经中文版< / a > , 仅学习交流使用< / span >
< / footer >
2022-08-04 06:59:33 +00:00
< / main >
< nav class = "nav-wrapper" aria-label = "Page navigation" >
<!-- Mobile navigation buttons -->
2024-08-08 07:47:03 +00:00
< a rel = "prev" href = "../ch12/ch12-02.html" class = "mobile-nav-chapters previous" title = "Previous chapter" aria-label = "Previous chapter" aria-keyshortcuts = "Left" >
2022-08-04 06:59:33 +00:00
< i class = "fa fa-angle-left" > < / i >
< / a >
2024-08-08 07:47:03 +00:00
<!-- ../ch12/ch12 - 04.html -->
< a rel = "next" href = "../ch12/ch12-04.html" class = "mobile-nav-chapters next" title = "Next chapter" aria-label = "Next chapter" aria-keyshortcuts = "Right" >
2022-08-04 06:59:33 +00:00
< 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" >
2024-08-08 07:47:03 +00:00
< a rel = "prev" href = "../ch12/ch12-02.html" class = "nav-chapters previous" title = "Previous chapter" aria-label = "Previous chapter" aria-keyshortcuts = "Left" >
2022-08-04 06:59:33 +00:00
< i class = "fa fa-angle-left" > < / i >
< / a >
2024-08-08 07:47:03 +00:00
< a rel = "next" href = "../ch12/ch12-04.html" class = "nav-chapters next" title = "Next chapter" aria-label = "Next chapter" aria-keyshortcuts = "Right" >
2022-08-04 06:59:33 +00:00
< i class = "fa fa-angle-right" > < / i >
< / a >
2024-08-08 07:47:03 +00:00
< / nav >
2022-08-04 06:59:33 +00:00
< / div >
2024-08-08 07:47:03 +00:00
< script type = "text/javascript" >
2022-08-04 06:59:33 +00:00
window.playground_copyable = true;
< / script >
2024-08-13 00:15:53 +00:00
< script src = "../static/wabook/mark.min.js" type = "text/javascript" charset = "utf-8" > < / script >
< script src = "../static/wabook/clipboard.min.js" type = "text/javascript" charset = "utf-8" > < / script >
< script src = "../static/wabook/highlight.js" type = "text/javascript" charset = "utf-8" > < / script >
< script src = "../static/wabook/book.js" type = "text/javascript" charset = "utf-8" > < / script >
2022-08-04 06:59:33 +00:00
< script type = "text/javascript" charset = "utf-8" >
2024-08-08 07:47:03 +00:00
var pagePath = "ch12/ch12-03.md"
2022-08-04 06:59:33 +00:00
< / script >
<!-- Custom JS scripts -->
2024-08-13 00:15:53 +00:00
< script src = "../static/wabook/giscus.js" type = "text/javascript" charset = "utf-8" > < / script >
2022-08-04 06:59:33 +00:00
2024-08-08 07:47:03 +00:00
2022-08-04 06:59:33 +00:00
< / body >
< / html >