|
|
<!DOCTYPE HTML>
|
|
|
<html lang="en">
|
|
|
<head>
|
|
|
<meta charset="UTF-8">
|
|
|
<title>迭代器 - Rust 程序设计语言 简体中文版</title>
|
|
|
<meta content="text/html; charset=utf-8" http-equiv="Content-Type">
|
|
|
<meta name="description" content="Rust 程序设计语言 简体中文版">
|
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
|
|
|
|
<base href="">
|
|
|
|
|
|
<link rel="stylesheet" href="book.css">
|
|
|
<link href='https://fonts.googleapis.com/css?family=Open+Sans:300italic,400italic,600italic,700italic,800italic,400,300,600,700,800' rel='stylesheet' type='text/css'>
|
|
|
|
|
|
<link rel="shortcut icon" href="favicon.png">
|
|
|
|
|
|
<!-- Font Awesome -->
|
|
|
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.3.0/css/font-awesome.min.css">
|
|
|
|
|
|
<link rel="stylesheet" href="highlight.css">
|
|
|
<link rel="stylesheet" href="tomorrow-night.css">
|
|
|
|
|
|
<!-- MathJax -->
|
|
|
<script type="text/javascript" src="https://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML"></script>
|
|
|
|
|
|
<!-- Fetch JQuery from CDN but have a local fallback -->
|
|
|
<script src="https://code.jquery.com/jquery-2.1.4.min.js"></script>
|
|
|
<script>
|
|
|
if (typeof jQuery == 'undefined') {
|
|
|
document.write(unescape("%3Cscript src='jquery.js'%3E%3C/script%3E"));
|
|
|
}
|
|
|
</script>
|
|
|
</head>
|
|
|
<body class="light">
|
|
|
<!-- Set the theme before any content is loaded, prevents flash -->
|
|
|
<script type="text/javascript">
|
|
|
var theme = localStorage.getItem('theme');
|
|
|
if (theme == null) { theme = 'light'; }
|
|
|
$('body').removeClass().addClass(theme);
|
|
|
</script>
|
|
|
|
|
|
<!-- Hide / unhide sidebar before it is displayed -->
|
|
|
<script type="text/javascript">
|
|
|
var sidebar = localStorage.getItem('sidebar');
|
|
|
if (sidebar === "hidden") { $("html").addClass("sidebar-hidden") }
|
|
|
else if (sidebar === "visible") { $("html").addClass("sidebar-visible") }
|
|
|
</script>
|
|
|
|
|
|
<div id="sidebar" class="sidebar">
|
|
|
<ul class="chapter"><li><a href="ch01-00-introduction.html"><strong>1.</strong> 介绍</a></li><li><ul class="section"><li><a href="ch01-01-installation.html"><strong>1.1.</strong> 安装</a></li><li><a href="ch01-02-hello-world.html"><strong>1.2.</strong> Hello, World!</a></li></ul></li><li><a href="ch02-00-guessing-game-tutorial.html"><strong>2.</strong> 猜猜看教程</a></li><li><a href="ch03-00-common-programming-concepts.html"><strong>3.</strong> 通用编程概念</a></li><li><ul class="section"><li><a href="ch03-01-variables-and-mutability.html"><strong>3.1.</strong> 变量和可变性</a></li><li><a href="ch03-02-data-types.html"><strong>3.2.</strong> 数据类型</a></li><li><a href="ch03-03-how-functions-work.html"><strong>3.3.</strong> 函数如何工作</a></li><li><a href="ch03-04-comments.html"><strong>3.4.</strong> 注释</a></li><li><a href="ch03-05-control-flow.html"><strong>3.5.</strong> 控制流</a></li></ul></li><li><a href="ch04-00-understanding-ownership.html"><strong>4.</strong> 认识所有权</a></li><li><ul class="section"><li><a href="ch04-01-what-is-ownership.html"><strong>4.1.</strong> 什么是所有权</a></li><li><a href="ch04-02-references-and-borrowing.html"><strong>4.2.</strong> 引用 & 借用</a></li><li><a href="ch04-03-slices.html"><strong>4.3.</strong> Slices</a></li></ul></li><li><a href="ch05-00-structs.html"><strong>5.</strong> 结构体</a></li><li><ul class="section"><li><a href="ch05-01-method-syntax.html"><strong>5.1.</strong> 方法语法</a></li></ul></li><li><a href="ch06-00-enums.html"><strong>6.</strong> 枚举和模式匹配</a></li><li><ul class="section"><li><a href="ch06-01-defining-an-enum.html"><strong>6.1.</strong> 定义枚举</a></li><li><a href="ch06-02-match.html"><strong>6.2.</strong> <code>match</code>控制流运算符</a></li><li><a href="ch06-03-if-let.html"><strong>6.3.</strong> <code>if let</code>简单控制流</a></li></ul></li><li><a href="ch07-00-modules.html"><strong>7.</strong> 模块</a></li><li><ul class="section"><li><a href="ch07-01-mod-and-the-filesystem.html"><strong>7.1.</strong> <code>mod</code>和文件系统</a></li><li><a href="ch07-02-controlling-visibility-with-pub.html"><strong>7.2.</strong> 使用<code>pub</code>控制可见性</a></li><li><a href="ch07-03-importing-names-with-use.html"><strong>7.3.</strong> 使用<code>use</code>导入命名</a></li></ul></li><li><a href="ch08-00-common-collections.html"><strong>8.</strong> 通用集合类型</a></li><li><ul class="section"><li><a href="ch08-01-vectors.html"><strong>8.1.</strong> vector</a></li><li><a href="ch08-02-strings.html"><strong>8.2.</strong> 字符串</a></li><li><a href="ch08-03-hash-maps.html"><strong>8.3.</strong> 哈希 map</a></li></ul></li><li><a href="ch09-00-error-handling.html"><strong>9.</strong> 错误处理</a></li><li><ul class="section"><li><a href="ch09-01-unrecoverable-errors-with-panic.html"><strong>9.1.</strong> <code>panic!</code>与不可恢复的错误</a></li><li><a href="ch09-02-recoverable-errors-with-result.html"><strong>9.2.</strong> <code>Result</code>与可恢复的错误</a></li><li><a href="ch09-03-to-panic-or-not-to-panic.html"><strong>9.3.</strong> <code>panic!</code>还是不<code>panic!</code></a></li></ul></li><li><a href="ch10-00-generics.html"><strong>10.</strong> 泛型、trait 和生命周期</a></li><li><ul class="section"><li><a href="ch10-01-syntax.html"><strong>10.1.</strong> 泛型数据类型</a></li><li><a href="ch10-02-traits.html"><strong>10.2.</strong> trait:定义共享的行为</a></li><li><a href="ch10-03-lifetime-syntax.html"><strong>10.3.</strong> 生命周期与引用有效性</a></li></ul></li><li><a href="ch11-00-testing.html"><strong>11.</strong> 测试</a></li><li><ul class="section"><li><a href="ch11-01-writing-tests.html"><strong>11.1.</strong> 编写测试</a></li><li><a href="ch11-02-running-tests.html"><strong>11.2.</strong> 运行测试</a></li><li><a href="ch11-03-test-organization.html"><strong>11.3.</strong> 测试的组织结构</a></li></ul></li><li><a href="ch12-00-an-io-project.html"><strong>12.</strong> 一个 I/O 项目</a></li><li><ul class="section"><li><a href="ch12-01-accepting-command-line-arguments.html"><strong>12.1.</strong> 接受命令行参数</a></li><li><a href="ch12-02-reading-a-file.html"><strong>12.2.</strong> 读取文件</a></li><li><a href="ch12-03-improving-error-handling-and-modularity.html"><strong>12.3.</strong> 增强错误处理和模块化</a></li><li><a href="ch12-04-testing-the-librarys-functionality.html"><strong>12.4.</strong> 测试库的功能</a></li><li><a href="ch12-05-working-with-environment-variables.html"><strong>12.5.</strong> 处理环境变量</a></li><li><a href="ch12-06-writing-to-stderr-instead-of-stdout.html"><strong>12.6.</strong> 输出到<code>stderr</code>而不是<code>stdout</code></a></li></ul></li><li><a href="ch13-00-functional-features.html"><strong>13.</strong> Rust 中的函数式语言功能</a></li><li><ul class="section"><li><a href="ch13-01-closures.html"><strong>13.1.</strong> 闭包</a></li><li><a href="ch13-02-iterators.html" class="active"><strong>13.2.</strong> 迭代器</a></li><li><a href="ch13-03-improving-our-io-project.html"><strong>13.3.</strong> 改进 I/O 项目</a></li><li><a href="ch13-04-performance.html"><strong>13.4.</strong> 性能</a></li></ul></li><li><a href="ch14-00-more-about-cargo.html"><strong>14.</strong> 更多关于 Cargo 和 Crates.io</a></li><li><ul class="section"><li><a href="ch14-01-release-profiles.html"><strong>14.1.</strong> 发布配置</a></li><li><a href="ch14-02-publishing-to-crates-io.html"><strong>14.2.</strong> 将 crate 发布到 Crates.io</a></li><li><a href="ch14-03-cargo-workspaces.html"><strong>14.3.</strong> Cargo 工作空间</a></li><li><a href="ch14-04-installing-binaries.html"><strong>14.4.</strong> 使用<code>cargo install</code>从 Crates.io 安装文件</a></li><li><a href="ch14-05-extending-cargo.html"><strong>14.5.</strong> Cargo 自定义扩展命令</a></li></ul></li><li><a href="ch15-00-smart-pointers.html"><strong>15.</strong> 智能指针</a></li><li><ul class="section"><li><a href="ch15-01-box.html"><strong>15.1.</strong> <code>Box<T></code>用于已知大小的堆上数据</a></li><li><a href="ch15-02-deref.html"><strong>15.2.</strong> <code>Deref</code> Trait 允许通过引用访问数据</a></li><li><a href="ch15-03-drop.html"><strong>15.3.</strong> <code>Drop</code> Trait 运行清理代码</a></li><li><a href="ch15-04-rc.html"><strong>15.4.</strong> <code>Rc<T></code> 引用计数智能指针</a></li><li><a href="ch15-05-interior-mutability.html"><strong>15.5.</strong> <code>RefCell<T></code>和内部可变性模式</a></li><li><a href="ch15-06-reference-cycles.html"><strong>15.6.</strong> 引用循环和内存泄漏是安全的</a></li></ul></li></ul>
|
|
|
</div>
|
|
|
|
|
|
<div id="page-wrapper" class="page-wrapper">
|
|
|
|
|
|
<div class="page">
|
|
|
<div id="menu-bar" class="menu-bar">
|
|
|
<div class="left-buttons">
|
|
|
<i id="sidebar-toggle" class="fa fa-bars"></i>
|
|
|
<i id="theme-toggle" class="fa fa-paint-brush"></i>
|
|
|
</div>
|
|
|
|
|
|
<h1 class="menu-title">Rust 程序设计语言 简体中文版</h1>
|
|
|
|
|
|
<div class="right-buttons">
|
|
|
<i id="print-button" class="fa fa-print" title="Print this book"></i>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<div id="content" class="content">
|
|
|
<a class="header" href="#迭代器" name="迭代器"><h2>迭代器</h2></a>
|
|
|
<blockquote>
|
|
|
<p><a href="https://github.com/rust-lang/book/blob/master/second-edition/src/ch13-02-iterators.md">ch13-02-iterators.md</a>
|
|
|
<br>
|
|
|
commit 3f2a1bd8dbb19cc48b210fc4fb35c305c8d81b56</p>
|
|
|
</blockquote>
|
|
|
<p>迭代器是 Rust 中的一个模式,它允许你对一个项的序列进行某些处理。例如。列表 13-5 中对 vecctor 中的每一个数加一:</p>
|
|
|
<pre><code class="language-rust">let v1 = vec![1, 2, 3];
|
|
|
|
|
|
let v2: Vec<i32> = v1.iter().map(|x| x + 1).collect();
|
|
|
|
|
|
assert_eq!(v2, [2, 3, 4]);
|
|
|
</code></pre>
|
|
|
<p><span class="caption">Listing 13-5: Using an iterator, <code>map</code>, and <code>collect</code> to
|
|
|
add one to each number in a vector</span></p>
|
|
|
<!-- Will add wingdings in libreoffice /Carol -->
|
|
|
<p>vector 的<code>iter</code>方法允许从 vector 创建一个<strong>迭代器</strong>(<em>iterator</em>)。接着迭代器上的<code>map</code>方法调用允许我们处理每一个元素:在这里,我们向<code>map</code>传递了一个对每一个元素<code>x</code>加一的闭包。<code>map</code>是最基本的与比较交互的方法之一,因为依次处理每一个元素是非常有用的!最后<code>collect</code>方法消费了迭代器并将其元素存放到一个新的数据结构中。在这个例子中,因为我们指定<code>v2</code>的类型是<code>Vec<i32></code>,<code>collect</code>将会创建一个<code>i32</code>的 vector。</p>
|
|
|
<p>像<code>map</code>这样的迭代器方法有时被称为<strong>迭代器适配器</strong>(<em>iterator adaptors</em>),因为他们获取一个迭代器并产生一个新的迭代器。也就是说,<code>map</code>在之前迭代器的基础上通过调用传递给它的闭包来创建了一个新的值序列的迭代器。</p>
|
|
|
<p>概括一下,这行代码进行了如下工作:</p>
|
|
|
<ol>
|
|
|
<li>从 vector 中创建了一个迭代器。</li>
|
|
|
<li>使用<code>map</code>适配器和一个闭包参数对每一个元素加一。</li>
|
|
|
<li>使用<code>collect</code>适配器来消费迭代去并生成了一个新的 vector。</li>
|
|
|
</ol>
|
|
|
<p>这就是如何产生结果<code>[2, 3, 4]</code>的。如你所见,闭包是使用迭代器的很重要的一部分:他们提供了一个自定义类似<code>map</code>这样的迭代器适配器的行为的方法。</p>
|
|
|
<a class="header" href="#迭代器是惰性的" name="迭代器是惰性的"><h3>迭代器是惰性的</h3></a>
|
|
|
<p>在上一部分,你可能已经注意到了一个微妙的用词区别:我们说<code>map</code><strong>适配</strong>(<em>adapts</em>)了一个迭代器,而<code>collect</code><strong>消费</strong>(<em>consumes</em>)了一个迭代器。这是有意为之的。单独的迭代器并不会做任何工作;他们是惰性的。也就是说,像列表 13-5 的代码但是不调用<code>collect</code>的话:</p>
|
|
|
<pre><code class="language-rust">let v1: Vec<i32> = vec![1, 2, 3];
|
|
|
|
|
|
v1.iter().map(|x| x + 1); // without collect
|
|
|
</code></pre>
|
|
|
<p>这可以编译,不过会给出一个警告:</p>
|
|
|
<pre><code>warning: unused result which must be used: iterator adaptors are lazy and do
|
|
|
nothing unless consumed, #[warn(unused_must_use)] on by default
|
|
|
--> src/main.rs:4:1
|
|
|
|
|
|
|
4 | v1.iter().map(|x| x + 1); // without collect
|
|
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
|
</code></pre>
|
|
|
<p>这个警告是因为迭代器适配器实际上并不自己进行处理。他们需要一些其他方法来触发迭代器链的计算。我们称之为<strong>消费迭代器</strong>(<em>consuming adaptors</em>),而<code>collect</code>就是其中之一。</p>
|
|
|
<p>那么如何知道迭代器方法是否消费了迭代器呢?还有哪些适配器是可用的呢?为此,让我们看看<code>Iterator</code> trait。</p>
|
|
|
<a class="header" href="#iterator-trait" name="iterator-trait"><h3><code>Iterator</code> trait</h3></a>
|
|
|
<p>迭代器都实现了一个标准库中叫做<code>Iterator</code>的 trait。其定义看起来像这样:</p>
|
|
|
<pre><code class="language-rust">trait Iterator {
|
|
|
type Item;
|
|
|
|
|
|
fn next(&mut self) -> Option<Self::Item>;
|
|
|
}
|
|
|
</code></pre>
|
|
|
<p>这里有一些还未讲到的新语法:<code>type Item</code>和<code>Self::Item</code>定义了这个 trait 的<strong>关联类型</strong>(<em>associated type</em>),第XX章会讲到关联类型。现在所有你需要知道就是这些代码表示<code>Iterator</code> trait 要求你也定义一个<code>Item</code>类型,而这个<code>Item</code>类型用作<code>next</code>方法的返回值。换句话说,<code>Item</code>类型将是迭代器返回的元素的类型。</p>
|
|
|
<p>让我们使用<code>Iterator</code> trait 来创建一个从一数到五的迭代器<code>Counter</code>。首先,需要创建一个结构体来存放迭代器的当前状态,它有一个<code>u32</code>的字段<code>count</code>。我们也定义了一个<code>new</code>方法,当然这并不是必须的。因为我们希望<code>Counter</code>能从一数到五,所以它总是从零开始:</p>
|
|
|
<pre><code class="language-rust">struct Counter {
|
|
|
count: u32,
|
|
|
}
|
|
|
|
|
|
impl Counter {
|
|
|
fn new() -> Counter {
|
|
|
Counter { count: 0 }
|
|
|
}
|
|
|
}
|
|
|
</code></pre>
|
|
|
<p>接下来,我们将通过定义<code>next</code>方法来为<code>Counter</code>类型实现<code>Iterator</code> trait。我们希望迭代器的工作方式是对当前状态加一(这就是为什么将<code>count</code>初始化为零,这样迭代器首先就会返回一)。如果<code>count</code>仍然小于六,将返回当前状态,不过如果<code>count</code>大于等于六,迭代器将返回<code>None</code>,如列表 13-6 所示:</p>
|
|
|
<pre><code class="language-rust"># struct Counter {
|
|
|
# count: u32,
|
|
|
# }
|
|
|
#
|
|
|
impl Iterator for Counter {
|
|
|
// Our iterator will produce u32s
|
|
|
type Item = u32;
|
|
|
|
|
|
fn next(&mut self) -> Option<Self::Item> {
|
|
|
// increment our count. This is why we started at zero.
|
|
|
self.count += 1;
|
|
|
|
|
|
// check to see if we've finished counting or not.
|
|
|
if self.count < 6 {
|
|
|
Some(self.count)
|
|
|
} else {
|
|
|
None
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
</code></pre>
|
|
|
<p><span class="caption">Listing 13-6: Implementing the <code>Iterator</code> trait on our
|
|
|
<code>Counter</code> struct</span></p>
|
|
|
<!-- I will add wingdings in libreoffice /Carol -->
|
|
|
<p><code>type Item = u32</code>这一行表明迭代器中<code>Item</code>的关联类型将是<code>u32</code>。同样无需担心关联类型,因为第XX章会涉及他们。</p>
|
|
|
<p><code>next</code>方法是迭代器的主要接口,它返回一个<code>Option</code>。如果它是<code>Some(value)</code>,相当于可以迭代器中获取另一个值。如果它是<code>None</code>,迭代器就结束了。在<code>next</code>方法中可以进行任何迭代器需要的计算。在这个例子中,我们对当前状态加一,接着检查其是否仍然小于六。如果是,返回<code>Some(self.count)</code>来产生下一个值。如果大于等于六,迭代结束并返回<code>None</code>。</p>
|
|
|
<p>迭代器 trait 指定当其返回<code>None</code>,就代表迭代结束。该 trait 并不强制任何在<code>next</code>方法返回<code>None</code>后再次调用时必须有的行为。在这个情况下,在第一次返回<code>None</code>后每一次调用<code>next</code>仍然返回<code>None</code>,不过其内部<code>count</code>字段会依次增长到<code>u32</code>的最大值,接着<code>count</code>会溢出(在调试模式会<code>panic!</code>而在发布模式则会折叠从最小值开始)。有些其他的迭代器则选择再次从头开始迭代。如果需要确保迭代器在返回第一个<code>None</code>之后所有的<code>next</code>方法调用都返回<code>None</code>,可以使用<code>fuse</code>方法来创建不同于任何其他的迭代器。</p>
|
|
|
<p>一旦实现了<code>Iterator</code> trait,我们就有了一个迭代器!可以通过不停的调用<code>Counter</code>结构体的<code>next</code>方法来使用迭代器的功能:</p>
|
|
|
<pre><code class="language-rust,ignore">let mut counter = Counter::new();
|
|
|
|
|
|
let x = counter.next();
|
|
|
println!("{:?}", x);
|
|
|
|
|
|
let x = counter.next();
|
|
|
println!("{:?}", x);
|
|
|
|
|
|
let x = counter.next();
|
|
|
println!("{:?}", x);
|
|
|
|
|
|
let x = counter.next();
|
|
|
println!("{:?}", x);
|
|
|
|
|
|
let x = counter.next();
|
|
|
println!("{:?}", x);
|
|
|
|
|
|
let x = counter.next();
|
|
|
println!("{:?}", x);
|
|
|
</code></pre>
|
|
|
<p>这会一次一行的打印出从<code>Some(1)</code>到<code>Some(5)</code>,之后就全是<code>None</code>。</p>
|
|
|
<a class="header" href="#各种iterator适配器" name="各种iterator适配器"><h3>各种<code>Iterator</code>适配器</h3></a>
|
|
|
<p>在列表 13-5 中有一个迭代器并调用了其像<code>map</code>和<code>collect</code>这样的方法。然而在列表 13-6 中,只实现了<code>Counter</code>的<code>next</code>方法。<code>Counter</code>如何才能得到像<code>map</code>和<code>collect</code>这样的方法呢?</p>
|
|
|
<p>好吧,当讲到<code>Iterator</code>的定义时,我们故意省略一个小的细节。<code>Iterator</code>定义了一系列默认实现,他们会调用<code>next</code>方法。因为<code>next</code>是唯一一个<code>Iterator</code> trait 没有默认实现的方法,一旦实现之后,<code>Iterator</code>的所有其他的适配器就都可用了。这些适配器可不少!</p>
|
|
|
<p>例如,处于某种原因我们希望获取一个<code>Counter</code>实例产生的头五个值,与另一个<code>Counter</code>实例第一个之后的值相组合,将每组数相乘,并只保留能被三整除的相乘结果,最后将保留结果相加,我们可以这么做:</p>
|
|
|
<pre><code class="language-rust"># struct Counter {
|
|
|
# count: u32,
|
|
|
# }
|
|
|
#
|
|
|
# impl Counter {
|
|
|
# fn new() -> Counter {
|
|
|
# Counter { count: 0 }
|
|
|
# }
|
|
|
# }
|
|
|
#
|
|
|
# impl Iterator for Counter {
|
|
|
# // Our iterator will produce u32s
|
|
|
# type Item = u32;
|
|
|
#
|
|
|
# fn next(&mut self) -> Option<Self::Item> {
|
|
|
# // increment our count. This is why we started at zero.
|
|
|
# self.count += 1;
|
|
|
#
|
|
|
# // check to see if we've finished counting or not.
|
|
|
# if self.count < 6 {
|
|
|
# Some(self.count)
|
|
|
# } else {
|
|
|
# None
|
|
|
# }
|
|
|
# }
|
|
|
# }
|
|
|
let sum: u32 = Counter::new().take(5)
|
|
|
.zip(Counter::new().skip(1))
|
|
|
.map(|(a, b)| a * b)
|
|
|
.filter(|x| x % 3 == 0)
|
|
|
.sum();
|
|
|
assert_eq!(18, sum);
|
|
|
</code></pre>
|
|
|
<p>注意<code>zip</code>只生成四对值;理论上的第五对值并不会产生,因为<code>zip</code>在任一输入返回<code>None</code>时也会返回<code>None</code>(这个迭代器最多就生成 5)。</p>
|
|
|
<p>因为实现了<code>Iterator</code>的<code>next</code>方法,所有这些方法调用都是可能的。请查看标准库文档来寻找迭代器可能会用得上的方法。</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<!-- Mobile navigation buttons -->
|
|
|
|
|
|
<a href="ch13-01-closures.html" class="mobile-nav-chapters previous">
|
|
|
<i class="fa fa-angle-left"></i>
|
|
|
</a>
|
|
|
|
|
|
|
|
|
|
|
|
<a href="ch13-03-improving-our-io-project.html" class="mobile-nav-chapters next">
|
|
|
<i class="fa fa-angle-right"></i>
|
|
|
</a>
|
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
<a href="ch13-01-closures.html" class="nav-chapters previous" title="You can navigate through the chapters using the arrow keys">
|
|
|
<i class="fa fa-angle-left"></i>
|
|
|
</a>
|
|
|
|
|
|
|
|
|
|
|
|
<a href="ch13-03-improving-our-io-project.html" class="nav-chapters next" title="You can navigate through the chapters using the arrow keys">
|
|
|
<i class="fa fa-angle-right"></i>
|
|
|
</a>
|
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
<!-- Local fallback for Font Awesome -->
|
|
|
<script>
|
|
|
if ($(".fa").css("font-family") !== "FontAwesome") {
|
|
|
$('<link rel="stylesheet" type="text/css" href="_FontAwesome/css/font-awesome.css">').prependTo('head');
|
|
|
}
|
|
|
</script>
|
|
|
|
|
|
<!-- Livereload script (if served using the cli tool) -->
|
|
|
|
|
|
|
|
|
<script src="highlight.js"></script>
|
|
|
<script src="book.js"></script>
|
|
|
</body>
|
|
|
</html>
|