gopl-zh.github.com/ch10/ch10-07.html
github-actions[bot] 15f2a4c650 deploy: d14d1d0a9c
2024-06-12 07:20:07 +00:00

532 lines
51 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE HTML>
<html lang="zh" class="sidebar-visible no-js light">
<head>
<!-- Book generated using mdBook -->
<meta charset="UTF-8">
<title>工具 - Go语言圣经</title>
<!-- Custom HTML head -->
<meta content="text/html; charset=utf-8" http-equiv="Content-Type">
<meta name="description" content="&lt;The Go Programming Language&gt;中文版">
<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">
<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</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><li class="chapter-item expanded "><a href="../ch5/ch5.html"><strong aria-hidden="true">5.</strong> 函数</a></li><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><li class="chapter-item expanded "><a href="../ch6/ch6.html"><strong aria-hidden="true">6.</strong> 方法</a></li><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><li class="chapter-item expanded "><a href="../ch7/ch7.html"><strong aria-hidden="true">7.</strong> 接口</a></li><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><li class="chapter-item expanded "><a href="../ch8/ch8.html"><strong aria-hidden="true">8.</strong> Goroutines和Channels</a></li><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><li class="chapter-item expanded "><a href="../ch9/ch9.html"><strong aria-hidden="true">9.</strong> 基于共享变量的并发</a></li><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><li class="chapter-item expanded "><a href="../ch10/ch10.html"><strong aria-hidden="true">10.</strong> 包和工具</a></li><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" class="active"><strong aria-hidden="true">10.7.</strong> 工具</a></li></ol></li><li class="chapter-item expanded "><a href="../ch11/ch11.html"><strong aria-hidden="true">11.</strong> 测试</a></li><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><li class="chapter-item expanded "><a href="../ch12/ch12.html"><strong aria-hidden="true">12.</strong> 反射</a></li><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"><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><li class="chapter-item expanded "><a href="../ch13/ch13.html"><strong aria-hidden="true">13.</strong> 底层编程</a></li><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><li class="chapter-item expanded "><a href="../appendix/appendix.html"><strong aria-hidden="true">14.</strong> 附录</a></li><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></li></ol> </div>
<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/./ch10/ch10-07.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">
<li><em>Go语言圣经读书笔记(挖坑中...): <a href="https://github.com/chai2010/gopl-notes-zh">https://github.com/chai2010/gopl-notes-zh</a></em></li>
<li><em>凹语言(Go实现, 面向WASM设计): <a href="https://github.com/wa-lang/wa">https://github.com/wa-lang/wa</a></em></li>
</ul>
<hr>
<h2 id="107-工具"><a class="header" href="#107-工具">10.7. 工具</a></h2>
<p>本章剩下的部分将讨论Go语言工具箱的具体功能包括如何下载、格式化、构建、测试和安装Go语言编写的程序。</p>
<p>Go语言的工具箱集合了一系列功能的命令集。它可以看作是一个包管理器类似于Linux中的apt和rpm工具用于包的查询、计算包的依赖关系、从远程版本控制系统下载它们等任务。它也是一个构建系统计算文件的依赖关系然后调用编译器、汇编器和链接器构建程序虽然它故意被设计成没有标准的make命令那么复杂。它也是一个单元测试和基准测试的驱动程序我们将在第11章讨论测试话题。</p>
<p>Go语言工具箱的命令有着类似“瑞士军刀”的风格带着一打的子命令有一些我们经常用到例如get、run、build和fmt等。你可以运行go或go help命令查看内置的帮助文档为了查询方便我们列出了最常用的命令</p>
<pre><code>$ go
...
build compile packages and dependencies
clean remove object files
doc show documentation for package or symbol
env print Go environment information
fmt run gofmt on package sources
get download and install packages and dependencies
install compile and install packages and dependencies
list list packages
run compile and run Go program
test test packages
version print Go version
vet run go tool vet on packages
Use &quot;go help [command]&quot; for more information about a command.
...
</code></pre>
<p>为了达到零配置的设计目标Go语言的工具箱很多地方都依赖各种约定。例如根据给定的源文件的名称Go语言的工具可以找到源文件对应的包因为每个目录只包含了单一的包并且包的导入路径和工作区的目录结构是对应的。给定一个包的导入路径Go语言的工具可以找到与之对应的存储着实体文件的目录。它还可以根据导入路径找到存储代码的仓库的远程服务器URL。</p>
<h3 id="1071-工作区结构"><a class="header" href="#1071-工作区结构">10.7.1. 工作区结构</a></h3>
<p>对于大多数的Go语言用户只需要配置一个名叫GOPATH的环境变量用来指定当前工作目录即可。当需要切换到不同工作区的时候只要更新GOPATH就可以了。例如我们在编写本书时将GOPATH设置为<code>$HOME/gobook</code></p>
<pre><code>$ export GOPATH=$HOME/gobook
$ go get gopl.io/...
</code></pre>
<p>当你用前面介绍的命令下载本书全部的例子源码之后,你的当前工作区的目录结构应该是这样的:</p>
<pre><code>GOPATH/
src/
gopl.io/
.git/
ch1/
helloworld/
main.go
dup/
main.go
...
golang.org/x/net/
.git/
html/
parse.go
node.go
...
bin/
helloworld
dup
pkg/
darwin_amd64/
...
</code></pre>
<p>GOPATH对应的工作区目录有三个子目录。其中src子目录用于存储源代码。每个包被保存在与$GOPATH/src的相对路径为包导入路径的子目录中例如gopl.io/ch1/helloworld相对应的路径目录。我们看到一个GOPATH工作区的src目录中可能有多个独立的版本控制系统例如gopl.io和golang.org分别对应不同的Git仓库。其中pkg子目录用于保存编译后的包的目标文件bin子目录用于保存编译后的可执行程序例如helloworld可执行程序。</p>
<p>第二个环境变量GOROOT用来指定Go的安装目录还有它自带的标准库包的位置。GOROOT的目录结构和GOPATH类似因此存放fmt包的源代码对应目录应该为$GOROOT/src/fmt。用户一般不需要设置GOROOT默认情况下Go语言安装工具会将其设置为安装的目录路径。</p>
<p>其中<code>go env</code>命令用于查看Go语言工具涉及的所有环境变量的值包括未设置环境变量的默认值。GOOS环境变量用于指定目标操作系统例如android、linux、darwin或windowsGOARCH环境变量用于指定处理器的类型例如amd64、386或arm等。虽然GOPATH环境变量是唯一必须要设置的但是其它环境变量也会偶尔用到。</p>
<pre><code>$ go env
GOPATH=&quot;/home/gopher/gobook&quot;
GOROOT=&quot;/usr/local/go&quot;
GOARCH=&quot;amd64&quot;
GOOS=&quot;darwin&quot;
...
</code></pre>
<h3 id="1072-下载包"><a class="header" href="#1072-下载包">10.7.2. 下载包</a></h3>
<p>使用Go语言工具箱的go命令不仅可以根据包导入路径找到本地工作区的包甚至可以从互联网上找到和更新包。</p>
<p>使用命令<code>go get</code>可以下载一个单一的包或者用<code>...</code>下载整个子目录里面的每个包。Go语言工具箱的go命令同时计算并下载所依赖的每个包这也是前一个例子中golang.org/x/net/html自动出现在本地工作区目录的原因。</p>
<p>一旦<code>go get</code>命令下载了包然后就是安装包或包对应的可执行的程序。我们将在下一节再关注它的细节现在只是展示整个下载过程是如何的简单。第一个命令是获取golint工具它用于检测Go源代码的编程风格是否有问题。第二个命令是用golint命令对2.6.2节的gopl.io/ch2/popcount包代码进行编码风格检查。它友好地报告了忘记了包的文档</p>
<pre><code>$ go get github.com/golang/lint/golint
$ $GOPATH/bin/golint gopl.io/ch2/popcount
src/gopl.io/ch2/popcount/main.go:1:1:
package comment should be of the form &quot;Package popcount ...&quot;
</code></pre>
<p><code>go get</code>命令支持当前流行的托管网站GitHub、Bitbucket和Launchpad可以直接向它们的版本控制系统请求代码。对于其它的网站你可能需要指定版本控制系统的具体路径和协议例如 Git或Mercurial。运行<code>go help importpath</code>获取相关的信息。</p>
<p><code>go get</code>命令获取的代码是真实的本地存储仓库而不仅仅只是复制源文件因此你依然可以使用版本管理工具比较本地代码的变更或者切换到其它的版本。例如golang.org/x/net包目录对应一个Git仓库</p>
<pre><code>$ cd $GOPATH/src/golang.org/x/net
$ git remote -v
origin https://go.googlesource.com/net (fetch)
origin https://go.googlesource.com/net (push)
</code></pre>
<p>需要注意的是导入路径含有的网站域名和本地Git仓库对应远程服务地址并不相同真实的Git地址是go.googlesource.com。这其实是Go语言工具的一个特性可以让包用一个自定义的导入路径但是真实的代码却是由更通用的服务提供例如googlesource.com或github.com。因为页面 https://golang.org/x/net/html 包含了如下的元数据它告诉Go语言的工具当前包真实的Git仓库托管地址</p>
<pre><code>$ go build gopl.io/ch1/fetch
$ ./fetch https://golang.org/x/net/html | grep go-import
&lt;meta name=&quot;go-import&quot;
content=&quot;golang.org/x/net git https://go.googlesource.com/net&quot;&gt;
</code></pre>
<p>如果指定<code>-u</code>命令行标志参数,<code>go get</code>命令将确保所有的包和依赖的包的版本都是最新的,然后重新编译和安装它们。如果不包含该标志参数的话,而且如果包已经在本地存在,那么代码将不会被自动更新。</p>
<p><code>go get -u</code>命令只是简单地保证每个包是最新版本如果是第一次下载包则是比较方便的但是对于发布程序则可能是不合适的因为本地程序可能需要对依赖的包做精确的版本依赖管理。通常的解决方案是使用vendor的目录用于存储依赖包的固定版本的源代码对本地依赖的包的版本更新也是谨慎和持续可控的。在Go1.5之前一般需要修改包的导入路径所以复制后golang.org/x/net/html导入路径可能会变为gopl.io/vendor/golang.org/x/net/html。最新的Go语言命令已经支持vendor特性但限于篇幅这里并不讨论vendor的具体细节。不过可以通过<code>go help gopath</code>命令查看Vendor的帮助文档。</p>
<p>(译注墙内用户在上面这些命令的基础上还需要学习用翻墙来go get。)</p>
<p><strong>练习 10.3:</strong> 从 http://gopl.io/ch1/helloworld?go-get=1 获取内容,查看本书的代码的真实托管的网址(<code>go get</code>请求HTML页面时包含了<code>go-get</code>参数,以区别普通的浏览器请求)。</p>
<h3 id="1073-构建包"><a class="header" href="#1073-构建包">10.7.3. 构建包</a></h3>
<p><code>go build</code>命令编译命令行参数指定的每个包。如果包是一个库则忽略输出结果这可以用于检测包是可以正确编译的。如果包的名字是main<code>go build</code>将调用链接器在当前目录创建一个可执行程序;以导入路径的最后一段作为可执行程序的名字。</p>
<p>由于每个目录只包含一个包因此每个对应可执行程序或者叫Unix术语中的命令的包会要求放到一个独立的目录中。这些目录有时候会放在名叫cmd目录的子目录下面例如用于提供Go文档服务的golang.org/x/tools/cmd/godoc命令就是放在cmd子目录§10.7.4)。</p>
<p>每个包可以由它们的导入路径指定,就像前面看到的那样,或者用一个相对目录的路径名指定,相对路径必须以<code>.</code><code>..</code>开头。如果没有指定参数,那么默认指定为当前目录对应的包。下面的命令用于构建同一个包,虽然它们的写法各不相同:</p>
<pre><code>$ cd $GOPATH/src/gopl.io/ch1/helloworld
$ go build
</code></pre>
<p>或者:</p>
<pre><code>$ cd anywhere
$ go build gopl.io/ch1/helloworld
</code></pre>
<p>或者:</p>
<pre><code>$ cd $GOPATH
$ go build ./src/gopl.io/ch1/helloworld
</code></pre>
<p>但不能这样:</p>
<pre><code>$ cd $GOPATH
$ go build src/gopl.io/ch1/helloworld
Error: cannot find package &quot;src/gopl.io/ch1/helloworld&quot;.
</code></pre>
<p>也可以指定包的源文件列表这一般只用于构建一些小程序或做一些临时性的实验。如果是main包将会以第一个Go源文件的基础文件名作为最终的可执行程序的名字。</p>
<pre><code>$ cat quoteargs.go
package main
import (
&quot;fmt&quot;
&quot;os&quot;
)
func main() {
fmt.Printf(&quot;%q\n&quot;, os.Args[1:])
}
$ go build quoteargs.go
$ ./quoteargs one &quot;two three&quot; four\ five
[&quot;one&quot; &quot;two three&quot; &quot;four five&quot;]
</code></pre>
<p>特别是对于这类一次性运行的程序,我们希望尽快的构建并运行它。<code>go run</code>命令实际上是结合了构建和运行的两个步骤:</p>
<pre><code>$ go run quoteargs.go one &quot;two three&quot; four\ five
[&quot;one&quot; &quot;two three&quot; &quot;four five&quot;]
</code></pre>
<p>(译注其实也可以偷懒直接go run <code>*.go</code>)</p>
<p>第一行的参数列表中,第一个不是以<code>.go</code>结尾的将作为可执行程序的参数运行。</p>
<p>默认情况下,<code>go build</code>命令构建指定的包和它依赖的包,然后丢弃除了最后的可执行文件之外所有的中间编译结果。依赖分析和编译过程虽然都是很快的,但是随着项目增加到几十个包和成千上万行代码,依赖关系分析和编译时间的消耗将变的可观,有时候可能需要几秒种,即使这些依赖项没有改变。</p>
<p><code>go install</code>命令和<code>go build</code>命令很相似,但是它会保存每个包的编译成果,而不是将它们都丢弃。被编译的包会被保存到$GOPATH/pkg目录下目录路径和 src目录路径对应可执行程序被保存到$GOPATH/bin目录。很多用户会将$GOPATH/bin添加到可执行程序的搜索列表中。还有<code>go install</code>命令和<code>go build</code>命令都不会重新编译没有发生变化的包,这可以使后续构建更快捷。为了方便编译依赖的包,<code>go build -i</code>命令将安装每个目标所依赖的包。</p>
<p>因为编译对应不同的操作系统平台和CPU架构<code>go install</code>命令会将编译结果安装到GOOS和GOARCH对应的目录。例如在Mac系统golang.org/x/net/html包将被安装到$GOPATH/pkg/darwin_amd64目录下的golang.org/x/net/html.a文件。</p>
<p>针对不同操作系统或CPU的交叉构建也是很简单的。只需要设置好目标对应的GOOS和GOARCH然后运行构建命令即可。下面交叉编译的程序将输出它在编译时的操作系统和CPU类型</p>
<p><u><i>gopl.io/ch10/cross</i></u></p>
<pre><code class="language-Go">func main() {
fmt.Println(runtime.GOOS, runtime.GOARCH)
}
</code></pre>
<p>下面以64位和32位环境分别编译和执行</p>
<pre><code>$ go build gopl.io/ch10/cross
$ ./cross
darwin amd64
$ GOARCH=386 go build gopl.io/ch10/cross
$ ./cross
darwin 386
</code></pre>
<p>有些包可能需要针对不同平台和处理器类型使用不同版本的代码文件以便于处理底层的可移植性问题或为一些特定代码提供优化。如果一个文件名包含了一个操作系统或处理器类型名字例如net_linux.go或asm_amd64.sGo语言的构建工具将只在对应的平台编译这些文件。还有一个特别的构建注释参数可以提供更多的构建过程控制。例如文件中可能包含下面的注释</p>
<pre><code class="language-Go">// +build linux darwin
</code></pre>
<p>在包声明和包注释的前面,该构建注释参数告诉<code>go build</code>只在编译程序对应的目标操作系统是Linux或Mac OS X时才编译这个文件。下面的构建注释则表示不编译这个文件</p>
<pre><code class="language-Go">// +build ignore
</code></pre>
<p>更多细节可以参考go/build包的构建约束部分的文档。</p>
<pre><code>$ go doc go/build
</code></pre>
<h3 id="1074-包文档"><a class="header" href="#1074-包文档">10.7.4. 包文档</a></h3>
<p>Go语言的编码风格鼓励为每个包提供良好的文档。包中每个导出的成员和包声明前都应该包含目的和用法说明的注释。</p>
<p>Go语言中的文档注释一般是完整的句子第一行通常是摘要说明以被注释者的名字开头。注释中函数的参数或其它的标识符并不需要额外的引号或其它标记注明。例如下面是fmt.Fprintf的文档注释。</p>
<pre><code class="language-Go">// Fprintf formats according to a format specifier and writes to w.
// It returns the number of bytes written and any write error encountered.
func Fprintf(w io.Writer, format string, a ...interface{}) (int, error)
</code></pre>
<p>Fprintf函数格式化的细节在fmt包文档中描述。如果注释后紧跟着包声明语句那注释对应整个包的文档。包文档对应的注释只能有一个译注其实可以有多个它们会组合成一个包文档注释包注释可以出现在任何一个源文件中。如果包的注释内容比较长一般会放到一个独立的源文件中fmt包注释就有300行之多。这个专门用于保存包文档的源文件通常叫doc.go。</p>
<p>好的文档并不需要面面俱到文档本身应该是简洁但不可忽略的。事实上Go语言的风格更喜欢简洁的文档并且文档也是需要像代码一样维护的。对于一组声明语句可以用一个精炼的句子描述如果是显而易见的功能则并不需要注释。</p>
<p>在本书中,只要空间允许,我们之前很多包声明都包含了注释文档,但你可以从标准库中发现很多更好的例子。有两个工具可以帮到你。</p>
<p>首先是<code>go doc</code>命令,该命令打印其后所指定的实体的声明与文档注释,该实体可能是一个包:</p>
<pre><code>$ go doc time
package time // import &quot;time&quot;
Package time provides functionality for measuring and displaying time.
const Nanosecond Duration = 1 ...
func After(d Duration) &lt;-chan Time
func Sleep(d Duration)
func Since(t Time) Duration
func Now() Time
type Duration int64
type Time struct { ... }
...many more...
</code></pre>
<p>或者是某个具体的包成员:</p>
<pre><code>$ go doc time.Since
func Since(t Time) Duration
Since returns the time elapsed since t.
It is shorthand for time.Now().Sub(t).
</code></pre>
<p>或者是一个方法:</p>
<pre><code>$ go doc time.Duration.Seconds
func (d Duration) Seconds() float64
Seconds returns the duration as a floating-point number of seconds.
</code></pre>
<p>该命令并不需要输入完整的包导入路径或正确的大小写。下面的命令将打印encoding/json包的<code>(*json.Decoder).Decode</code>方法的文档:</p>
<pre><code>$ go doc json.decode
func (dec *Decoder) Decode(v interface{}) error
Decode reads the next JSON-encoded value from its input and stores
it in the value pointed to by v.
</code></pre>
<p>第二个工具名字也叫godoc它提供可以相互交叉引用的HTML页面但是包含和<code>go doc</code>命令相同以及更多的信息。图10.1演示了time包的文档11.6节将看到godoc演示可以交互的示例程序。godoc的在线服务 https://godoc.org ,包含了成千上万的开源包的检索工具。</p>
<p><img src="../images/ch10-01.png" alt="" /></p>
<p>你也可以在自己的工作区目录运行godoc服务。运行下面的命令然后在浏览器查看 http://localhost:8000/pkg 页面:</p>
<pre><code>$ godoc -http :8000
</code></pre>
<p>其中<code>-analysis=type</code><code>-analysis=pointer</code>命令行标志参数用于打开文档和代码中关于静态分析的结果。</p>
<h3 id="1075-内部包"><a class="header" href="#1075-内部包">10.7.5. 内部包</a></h3>
<p>在Go语言程序中包是最重要的封装机制。没有导出的标识符只在同一个包内部可以访问而导出的标识符则是面向全宇宙都是可见的。</p>
<p>有时候,一个中间的状态可能也是有用的,标识符对于一小部分信任的包是可见的,但并不是对所有调用者都可见。例如,当我们计划将一个大的包拆分为很多小的更容易维护的子包,但是我们并不想将内部的子包结构也完全暴露出去。同时,我们可能还希望在内部子包之间共享一些通用的处理包,或者我们只是想实验一个新包的还并不稳定的接口,暂时只暴露给一些受限制的用户使用。</p>
<p>为了满足这些需求Go语言的构建工具对包含internal名字的路径段的包导入路径做了特殊处理。这种包叫internal包一个internal包只能被和internal目录有同一个父目录的包所导入。例如net/http/internal/chunked内部包只能被net/http/httputil或net/http包导入但是不能被net/url包导入。不过net/url包却可以导入net/http/httputil包。</p>
<pre><code>net/http
net/http/internal/chunked
net/http/httputil
net/url
</code></pre>
<h3 id="1076-查询包"><a class="header" href="#1076-查询包">10.7.6. 查询包</a></h3>
<p><code>go list</code>命令可以查询可用包的信息。其最简单的形式,可以测试包是否在工作区并打印它的导入路径:</p>
<pre><code>$ go list github.com/go-sql-driver/mysql
github.com/go-sql-driver/mysql
</code></pre>
<p><code>go list</code>命令的参数还可以用<code>&quot;...&quot;</code>表示匹配任意的包的导入路径。我们可以用它来列出工作区中的所有包:</p>
<pre><code>$ go list ...
archive/tar
archive/zip
bufio
bytes
cmd/addr2line
cmd/api
...many more...
</code></pre>
<p>或者是特定子目录下的所有包:</p>
<pre><code>$ go list gopl.io/ch3/...
gopl.io/ch3/basename1
gopl.io/ch3/basename2
gopl.io/ch3/comma
gopl.io/ch3/mandelbrot
gopl.io/ch3/netflag
gopl.io/ch3/printints
gopl.io/ch3/surface
</code></pre>
<p>或者是和某个主题相关的所有包:</p>
<pre><code>$ go list ...xml...
encoding/xml
gopl.io/ch7/xmlselect
</code></pre>
<p><code>go list</code>命令还可以获取每个包完整的元信息,而不仅仅只是导入路径,这些元信息可以以不同格式提供给用户。其中<code>-json</code>命令行参数表示用JSON格式打印每个包的元信息。</p>
<pre><code>$ go list -json hash
{
&quot;Dir&quot;: &quot;/home/gopher/go/src/hash&quot;,
&quot;ImportPath&quot;: &quot;hash&quot;,
&quot;Name&quot;: &quot;hash&quot;,
&quot;Doc&quot;: &quot;Package hash provides interfaces for hash functions.&quot;,
&quot;Target&quot;: &quot;/home/gopher/go/pkg/darwin_amd64/hash.a&quot;,
&quot;Goroot&quot;: true,
&quot;Standard&quot;: true,
&quot;Root&quot;: &quot;/home/gopher/go&quot;,
&quot;GoFiles&quot;: [
&quot;hash.go&quot;
],
&quot;Imports&quot;: [
&quot;io&quot;
],
&quot;Deps&quot;: [
&quot;errors&quot;,
&quot;io&quot;,
&quot;runtime&quot;,
&quot;sync&quot;,
&quot;sync/atomic&quot;,
&quot;unsafe&quot;
]
}
</code></pre>
<p>命令行参数<code>-f</code>则允许用户使用text/template包§4.6的模板语言定义输出文本的格式。下面的命令将打印strconv包的依赖的包然后用join模板函数将结果链接为一行连接时每个结果之间用一个空格分隔</p>
<pre><code>$ go list -f '{{join .Deps &quot; &quot;}}' strconv
errors math runtime unicode/utf8 unsafe
</code></pre>
<p>译注上面的命令在Windows的命令行运行会遇到<code>template: main:1: unclosed action</code>的错误。产生这个错误的原因是因为命令行对命令中的<code>&quot; &quot;</code>参数进行了转义处理。可以按照下面的方法解决转义字符串的问题:</p>
<pre><code>$ go list -f &quot;{{join .Deps \&quot; \&quot;}}&quot; strconv
</code></pre>
<p>下面的命令打印compress子目录下所有包的导入包列表</p>
<pre><code>$ go list -f '{{.ImportPath}} -&gt; {{join .Imports &quot; &quot;}}' compress/...
compress/bzip2 -&gt; bufio io sort
compress/flate -&gt; bufio fmt io math sort strconv
compress/gzip -&gt; bufio compress/flate errors fmt hash hash/crc32 io time
compress/lzw -&gt; bufio errors fmt io
compress/zlib -&gt; bufio compress/flate errors fmt hash hash/adler32 io
</code></pre>
<p>译注Windows下有同样有问题要避免转义字符串的干扰</p>
<pre><code>$ go list -f &quot;{{.ImportPath}} -&gt; {{join .Imports \&quot; \&quot;}}&quot; compress/...
</code></pre>
<p><code>go list</code>命令对于一次性的交互式查询或自动化构建或测试脚本都很有帮助。我们将在11.2.4节中再次使用它。每个子命令的更多信息,包括可设置的字段和意义,可以用<code>go help list</code>命令查看。</p>
<p>在本章我们解释了Go语言工具中除了测试命令之外的所有重要的子命令。在下一章我们将看到如何用<code>go test</code>命令去运行Go语言程序中的测试代码。</p>
<p><strong>练习 10.4</strong> 创建一个工具,根据命令行指定的参数,报告工作区所有依赖包指定的其它包集合。提示:你需要运行<code>go list</code>命令两次一次用于初始化包一次用于所有包。你可能需要用encoding/json§4.5包来分析输出的JSON格式的信息。</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="../ch10/ch10-06.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="../ch11/ch11.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="../ch10/ch10-06.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="../ch11/ch11.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 = "ch10/ch10-07.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>