# **第 6 章 循环**
与条件判断一样,循环也遍布于程序的各个角落,是程序中不可缺少的重要组成部分。本章我们将会探讨:
-
**程序中的循环是什么**
-
**写循环时需要注意的事项**
-
**循环的种类及其写法**
### **6.1 循环的基础**
我们在编写程序时,常常遇到“希望这个处理重复执行多次”的情况。例如:
- **希望同样的处理执行 X 次**
更复杂点的例子有:
-
**用其他对象置换数组里的所有元素;**
-
**在达成某条件之前,一直重复执行处理。**
这时,我们都需要用到循环。
接下来,我们将会介绍 Ruby 中基本的循环结构。其中比较特别的是,除了用传统的循环语句实现循环外,我们还能用方法来实现循环,也就是说我们可以根据自己的需要定制循环方法。关于如何定制循环 , 我们会在第 11 章再详细说明,这里我们先介绍一些预定义的循环语法结构。
### **6.2 循环时的注意事项**
下面两点是循环时必须注意的。
-
**循环的主体是什么**
-
**停止循环的条件是什么**
大家也许会认为,我们自己写的循环处理,“循环的主体是什么”我们自己总会知道吧。但是,实际编写程序时,稍不注意就会发生把不应该循环的处理加入到循环中这样的错误。而且,如果是循环里再嵌套循环的结构,在哪里做怎么样的循环、循环的结果怎么处理等都可能会使程序变得难以读懂。
另外,如果把“停止循环的条件”弄错了,有可能会发生处理无法终止,或者处理还没完成但已经跳出循环等这样的情况。大家写循环结构时务必注意上述两点,避免发生错误。
### **6.3 实现循环的方法**
Ruby 中有两种实现循环的方法。
-
**使用循环语句**
利用 Ruby 提供现有的循环语句,可以满足大部分循环处理的需求。
-
**使用方法实现循环**
将块传给方法,然后在块里面写上需要循环的处理。一般我们在为了某种特定目的而需要定制循环结构时,才使用方法来实现循环。
下面是我们接下来要介绍的六种循环语句或方法。
-
**`times` 方法**
-
**`while` 语句**
-
**`each` 方法**
-
**`for` 语句**
-
**`until` 语句**
-
**`loop` 方法**
Ruby 的常用循环结构就介绍到这里,接下来就让我们来具体看看如何使用这些语句或方法实现循环。
### **6.4 times 方法**
如果只是单纯执行一定次数的处理,用 `times` 方法可以很轻松实现。
假设我们希望把“满地油菜花”这个字符串连续输出 7 次。
**代码清单 6.1 times.rb**
~~~
7.times do
puts "满地油菜花"
end
~~~
> **执行示例**
~~~
> ruby times.rb
满地油菜花
满地油菜花
满地油菜花
满地油菜花
满地油菜花
满地油菜花
满地油菜花
~~~
使用 times 方法实现循环时,需要用到块 `do ~ end`。
**循环次数`.times do`
希望循环的处理
`end`**
块的 `do ~ end` 部分可以用`{~}`代替,像下面这样:
**循环次数`.times {`
希望循环的处理
`}`**
在 `times` 方法的块里,也是可以获知当前的循环次数的。
~~~
10.times do |i|
┊
end
~~~
这样,就可以把当前的循环次数赋值给变量 `i`。我们来看看实际的例子(代码清单 6.2)。
**代码清单 6.2 times2.rb**
~~~
5.times do |i|
puts "第#{i} 次的循环。"
end
~~~
> **执行示例**
~~~
> ruby times2.rb
第 0 次的循环。
第 1 次的循环。
第 2 次的循环。
第 3 次的循环。
第 4 次的循环。
~~~
请注意循环的次数是从 0 开始计算的。把循环次数的初始值设为 `1` 不失为一个好方法,但可惜我们不能这么做,因此我们只能在块里面对循环次数做调整(代码清单 6.3)。
**代码清单 6.3 times3.rb**
~~~
5.times do |i|
puts "第#{i+1} 次的循环。"
end
~~~
> **执行示例**
~~~
> ruby times3.rb
第 0 次的循环。
第 1 次的循环。
第 2 次的循环。
第 3 次的循环。
第 4 次的循环。
~~~
但是,这样的写法会使变量 `i` 的值与实际输出的值产生差异。从降低程序复杂度来看,这并不是一个好的的编程习惯。若是对循环次数比较在意时,我们不必勉强使用 `times` 方法,可使用下面即将介绍的 `for` 语句和 `while` 语句。
### **6.5 for 语句**
`for` 语句同样是用于实现循环的。需要注意的是,与刚才介绍的 `times` 方法不同,`for` 并不是方法,而是 Ruby 提供的循环控制语句。
以下是使用 `for` 语句的典型示例(代码清单 6.4)。
**代码清单 6.4 for.rb**
~~~
1: sum = 0
2: for i in 1..5
3: sum = sum + i
4: end
5: puts sum
~~~
> **执行示例**
~~~
> ruby for.rb
15
~~~
这是一个求从 1 到 5 累加的程序。`for` 语句的结构如下所示:
**`for` 变量 `in` 开始时的数值`..`结束时的数值 `do`
希望循环的处理
`end`**
※ 可以省略 `do`
我们回顾一下程序代码清单 6.4。程序第 1 行将 0 赋值给变量 `sum`,程序第 5 行输出变量 `sum` 的值并换行。
第 2 行到第 4 行的 `for` 语句指定变量 `i` 的范围是从 1 到 5。也就是说,程序一边从 1 到 5 改变变量 `i` 的值,一边执行 `sum = sum + i`。如果不使用循环语句,这个程序可以改写为:
~~~
sum = 0
sum = sum + 1
sum = sum + 2
sum = sum + 3
sum = sum + 4
sum = sum + 5
puts sum
~~~
`for` 语句与 `times` 方法不一样,循环的开始值和结束值可以任意指定。例如,我们想计算从变量 `from` 到变量 `to` 累加的总数,使用 `times` 方法的程序为:
~~~
from = 10
to = 20
sum = 0
(to - from + 1).times do |i|
sum = sum + (i + from)
end
puts sum
~~~
使用 for 语句的程序为:
~~~
from = 10
to = 20
sum = 0
for i in from..to
sum = sum + i
end
puts sum
~~~
使用 `for` 语句后的程序变得更加简单了。
另外,`sum = sum + i` 这个式子也有更简单的写法:
~~~
sum += i
~~~
本例是加法的简写,做减法、乘法时也同样可以做这样的省略。
~~~
a -= b
a *= b
~~~
第 9 章我们会再详细讨论这方面的内容,现在暂时先记着有这么一个省略写法就可以了。
### **6.6 普通的 for 语句**
其实上一节介绍的是 `for` 语句的特殊用法,普通的 `for` 语句如下所示:
**`for` 变量 `in` 对象 `do`
希望循环的处理
`end`**
※ 可以省略 `do`
可以看出,`in` 后面的部分和之前介绍的有点不同。
但和之前的 `for` 语句相比也并非完全不一样。实际上,.. 或者 ... 都是创建范围对象时所需的符号。
当然,并非任何对象都可以指定给 `for` 语句使用。下面是使用数组对象的例子。
**代码清单 6.5 for_names.rb**
~~~
names = ["awk", "Perl", "Python", "Ruby"]
for name in names
puts name
end
~~~
> **执行示例**
~~~
> ruby for_names.rb
awk
Perl
Python
Ruby
~~~
本例中,循环遍历各数组的元素,并各自将其输出。
### **6.7 while 语句**
不管哪种类型的循环,`while` 语句都可以胜任,`while` 语句的结构如下:
**`while` 条件 `do`
希望循环的处理
`end`**
※ 可以省略 `do`
![{%}](https://box.kancloud.cn/2015-10-26_562e01ddd862d.png)
这几行程序的意思就是,只要条件成立,就会不断地重复循环处理。我们来看看下面的示例(代码清单 6.6)。
**代码清单 6.6 while.rb**
~~~
i = 1
while i < 3
puts i
i += 1
end
~~~
> **执行示例**
~~~
> ruby while.rb
1
2
~~~
本例为什么会得出这样的结果呢。首先,程序将 1 赋值给变量 `i`,这时 `i` 的值为 1。接下来 `while` 语句循环处理以下内容:
1. **执行 `i < 3` 的比较。**
2. **比较结果为真(也就是 `i` 比 3 小)时,程序执行 `puts i` 和 `i += 1`。比较结果为假(也就是 `i` 大于等于 3)时,程序跳出 `while` 循环,不执行任何内容。**
3. **返回 1 处理。**
首次循环,由于 `i` 的初始值为 1,因此程序执行 `puts 1`。第 2 次循环,`i` 的值为 2,比 3 小,因此程序执行 `puts 2`。当程序执行到第 3 次循环,i 的值为 3,比 3 小的条件不成立,也就是说比较结果为假,因此,程序跳出 `while` 循环,并终止所有处理。
我们再来写一个使用 `while` 语句的程序。
把之前使用 `for` 语句写的程序(代码清单 6.4),改写为使用 `while` 语句程序(代码清单 6.7)。
**代码清单 6.7 while2.rb**
~~~
sum = 0
i = 1
while i <= 5
sum += i
i += 1
end
puts sum
~~~
这时与使用 `for` 语句的程序有细微的区别。首先,变量 `i` 的条件指定方式不一样。`for` 语句的例子通过 `1..5` 指定条件的范围。`while` 语句使用比较运算符 <=,指定“`i` 小于等于 5 时(循环)”的条件。
另外,`i` 的累加方式也不一样。`while` 语句的例子在程序里直接写出了 `i` 是如何进行加 1 处理的——`i += 1`。而 `for` 语句的例子并不需要在程序里直接写如何对 `i` 进行操作,自动在每次循环后对 i 进行加 1 处理。
就像这个例子一样,只要 `for` 语句能实现的循环,我们没必要特意将它改写为 `while` 语句。程序代码清单 6.8 是使用 `while` 语句反而会更便于理解的一个例子。
**代码清单 6.8 while3.rb**
~~~
sum = 0
i = 1
while sum < 50
sum += i
i += 1
end
puts sum
~~~
在这个例子里,作为循环条件的不是变量 `i`,而是变量 `sum`。循环条件现在变为“`sum` 小于 50 时执行循环处理”。一般来说,不通过计算,我们并不知道 `i` 的值为多少时 `sum` 的值才会超过 50,因此这种情况下使用 `for` 语句的循环反而会让程序变得难懂。
有时 `for` 语句写的程序易懂,有时候 `while` 语句写的程序易懂。我们会在本章的最后说明,如何区别使用 `for` 语句和 `while` 语句。
### **6.8 until 语句**
与 `if` 语句相对的有 `unless` 语句,同样地,与 `while` 语句相对的有 `until` 语句。`until` 语句的结构与 `while` 语句完全一样,只是条件判断刚好相反,不满足条件时才执行循环处理。换句话说,`while` 语句是一直执行循环处理,直到条件不成立为止;`until` 语句是一直执行循环处理,直到条件成立为止。
**`until` 条件 `do`
希望循环的处理
`end`**
※ 可以省略 `do`
![{%}](https://box.kancloud.cn/2015-10-26_562e01dde6237.png)
代码清单 6.9 是使用了 `until` 语句的程序。
**代码清单 6.9 until.rb**
~~~
sum = 0
i = 1
until sum >= 50
sum += i
i+= 1
end
puts sum
~~~
本例是将使用 `while` 语句的程序(代码清单 6.8)用 `until` 语句改写了,与 `while` 语句所使用的条件刚好相反。
其实,在 `while` 语句的条件上使用表示否定的运算符 !,也能达到和 `until` 语句相同的效果。
**代码清单 6.10 while_not.rb**
~~~
sum = 0
i = 1
while !(sum >= 50)
sum += i
i += 1
end
puts sum
~~~
虽然可以使用 `while` 语句的否定形式代替 `until` 语句。但是,有时对一些比较复杂的条件表达式使用否定,反而会不直观,影响程序理解,在这种情况下,我们应该考虑使用 `until` 语句。
### **6.9 each 方法**
`each` 方法将对象集合里的对象逐个取出,这与 `for` 语句循环取出数组元素非常相似。实际上,我们可以非常简单地将使用 `for` 语句的程序(代码清单 6.5)改写为使用 `each` 方法的程序(代码清单 6.11)。
**代码清单 6.11 each_names.rb**
~~~
names = ["awk","Perl","Python","Ruby"]
names.each do |name|
puts name
end
~~~
`each` 方法的结构如下:
**对象`.each do` | 变量 |
希望循环的处理
`end`**
在说明 `times` 方法我们曾提到过,块的 `do` ~ `end` 部分可换成 { ~ }。
**对象.`each {`| 变量 |
希望循环的处理
`}`**
这与下面的程序的效果是几乎一样。
**`for` 变量 `in` 对象
希望循环的处理
`end`**
在 Ruby 内部,`for` 语句是用 `each` 方法来实现的。因此,可以使用 `each` 方法的对象,同样也可以指定为 `for` 语句的循环对象。
在介绍 `for` 语句时我们举过使用范围对象的例子(代码清单 6.4),我们试着用 `each` 方法改写一下。
**代码清单 6.12 each.rb**
~~~
sum = 0
(1..5).each do |i|
sum= sum + i
end
puts sum
~~~
像本例这样,我们可以轻松互相改写两种用法。那到底什么时候该用 `for` 语句,什么时候该用 `each` 方法呢,我们将在本章的最后讨论这个问题。
### **6.10 loop 方法**
还有一种循环的方法,没有终止循环条件,只是不断执行循环处理。Ruby 中的 `loop` 就是这样的循环方法。
~~~
loop do
print "Ruby"
end
~~~
执行上面的程序后,整个屏幕会不停的输出文字 `Ruby`。为了避免这样的情况发生,在实际使用 `loop` 方法时,我们需要用到接下来将要介绍的 `break`,使程序可以中途跳出循环。
> **备注** 程序不小心执行了死循环时,我们可以使用 CTRL + c 来强行终止程序。
### **6.11 循环控制**
在进行循环处理的途中,我们可以控制程序马上终止循环,或者跳到下一个循环等。Ruby 提供了如下表(表 6.1)所示的三种控制循环的命令。
**表 6.1 循环控制命令**
<table border="1" data-line-num="407 408 409 410 411 412" width="90%"><thead><tr><th> <p class="表头单元格">命令</p> </th> <th> <p class="表头单元格">用途</p> </th> </tr></thead><tbody><tr><td> <p class="表格单元格">break</p> </td> <td> <p class="表格单元格">终止程序,跳出循环</p> </td> </tr><tr><td> <p class="表格单元格">next</p> </td> <td> <p class="表格单元格">跳到下一次循环</p> </td> </tr><tr><td> <p class="表格单元格">redo</p> </td> <td> <p class="表格单元格">在相同的条件下重复刚才的处理</p> </td> </tr></tbody></table>
在控制循环的命令中,可能不太容易区分使用 `next` 和 `redo`。借着下面的例子(代码清单 6.13),我们来介绍一下如何区分这三种循环命令。
**代码清单 6.13 break_next_redo.rb**
~~~
1: puts "break 的例子:"
2: i = 0
3: ["Perl", "Python", "Ruby", "Scheme"].each do |lang|
4: i += 1
5: if i == 3
6: break
7: end
8: p [i,lang]
9: end
10:
11: puts "next 的例子:"
12: i = 0
13: ["Perl", "Python", "Ruby", "Scheme"].each do |lang|
14: i += 1
15: if i == 3
16: next
17: end
18: p [i,lang]
19: end
20:
21: puts "redo 的例子:"
22: i = 0
23: ["Perl", "Python", "Ruby", "Scheme"].each do |lang|
24: i += 1
25: if i == 3
26: redo
27: end
28: p [i,lang]
29: end
~~~
我们来看看本例中的 `break`、`next`、`redo` 有什么不同。程序由三部分组成,除了 `break`、`next`、`redo` 这三部分的代码外,其他地方都是相同的。下面是执行后的结果。
> **执行示例**
~~~
> ruby break_next_redo.rb
break 的例子:
[1, "Perl"]
[2, "Python"]
next 的例子:
[1, "Perl"]
[2, "Python"]
[4, "Scheme"]
redo 的例子:
[1, "Perl"]
[2, "Python"]
[4, "Ruby"]
[5, "Scheme"]
~~~
### **6.11.1 break**
`break` 会终止全体程序。在代码清单 6.13 中,`i` 为 3 时,程序会执行第 6 行的 `break`(图 6.1)。执行 `break` 后,程序跳出 `each` 方法循环,前进至程序的第 10 行。因此,程序没有输出 `Ruby` 和 `Scheme`。
![{%}](https://box.kancloud.cn/2015-10-26_562e01ddf342f.png)
**图 6.1 break**
我们再来介绍一个关于 `break` 的例子。程序代码清单 6.14 对第 3 章的 simple_grep.rb 做了点小修改,使程序最多只能输出 10 行匹配到的内容。匹配的时候,累加变量 `matches`,当达到 `max_matches` 时,程序就会终止 `each_line` 方法的循环。
**代码清单 6.14 ten_lines_grep.rb**
~~~
pattern = Regexp.new(ARGV[0])
filename = ARGV[1]
max_matches = 10 # 输出的最大行数
matches = 0 # 已匹配的行数
file = File.open(filename)
file.each_line do |line|
if matches >= max_matches
break
end
if pattern =~ line
matches += 1
puts line
end
end
file.close
~~~
### **6.11.2 next**
使用 `next` 后,程序会忽略 `next` 后面的部分,跳到下一个循环开始的部分。在代码清单 6.13 中,`i` 为 3 时在执行第 16 行的 `next` 后,程序前进到 `each` 方法的下个循环(图 6.2)。也就是说,将 `Scheme` 赋值给 `lang`,并执行 `i += 1`。因此,程序并没有输出 `Ruby`,而是输出了 `Scheme`。
![{%}](https://box.kancloud.cn/2015-10-26_562e01de22018.png)
**图 6.2 next**
我们再来看看另外一个 `next` 的例子(代码清单 6.15)。程序逐行读取输入的内容,忽略空行或者以 # 开头的行,原封不动地输出除此以外所有行的内容。
执行以下命令后,我们会得到去掉 fact.rb(代码清单 6.16)的注释和空行后的 stripped_fact.rb(代码清单 6.17)。
~~~
> ruby strip.rb fact.rb > stripped_fact.rb
~~~
**代码清单 6.15 strip.rb**
~~~
file = File.open(ARGV[0])
file.each_line do |line|
next if /^\s*$/ =~ line # 空行
next if /^#/ =~ line # 以“#”开头的行
puts line
end
file.close
~~~
**代码清单 6.16 fact.rb**
~~~
# 求10 的阶乘
ans = 1
for i in 1..10
ans *= i
end
# 输出
puts "10! = #{ans}"
~~~
**代码清单 6.17 stripped_fact.rb**
~~~
ans = 1
for i in 1..10
ans *= i
end
puts "10! = #{ans}"
~~~
### **6.11.3 redo**
`redo` 与 `next` 非常像,与 `next` 的不同之处是,`redo` 会再执行一次相同的循环。
在代码清单 6.13 中,与 `next` 时的情况不同,`redo` 会输出 `Ruby`。这是由于,`i` 为 3 时就执行了第 26 行的 `redo`,程序只是返回循环的开头,也就是从程序的第 24 行的 `i += 1` 部分开始重新再执行处理,所以 `lang` 的值并没有从 `Ruby` 变为 `Scheme`。由于重复执行了 `i += 1`,`i` 的值变为 4,这样 `if` 语句的条件 `i == 3` 就不成立了,`redo` 也不会再执行了,程序顺理成章地输出了 `[4, "Ruby"]` 以及 `[5, "Scheme"]`(图 6.3)。
![{%}](https://box.kancloud.cn/2015-10-26_562e01de3f45e.png)
**图 6.3 redo**
另外,大家要注意 `redo` 的使用方法,稍不留神就会在同样的条件下,不断地重复处理,陷入死循环中。
`break`、`next` 和 `redo` 中,一般比较常用是 `break` 和 `next`。大家应该熟练掌握这两个命令的用法。即使是 Ruby 默认提供的库里面,实际上也很难找到 `redo` 的踪影,所以当我们在希望使用 `redo` 时,应该好好考虑是否真的有必要使用 `redo`。
### **6.12 总结**
本章我们主要介绍了循环时所用的语句以及方法。
如果纯粹只考虑实现循环功能,任何种类的循环都可以用 `while` 语句实现。讲得极端一点,其他循环语句和方法根本也没存在的必要。即便如此,那为什么 Ruby 还提供了那么丰富的循环语句和方法呢。这是因为,程序并不只是单纯为了实现功能,同时还应该使代码更便于读写,使人更容易理解。
为了使大家更快地熟悉循环的用法,我们在最初介绍循环语句和方法的一览表中加上“主要用途”一栏(表 6.2),以供大家参考。
**表 6.2 循环语句、方法及其主要用途**
<table border="1" data-line-num="566 567 568 569 570 571 572 573 574" width="90%"><thead><tr><th> <p class="表头单元格"> </p> </th> <th> <p class="表头单元格">主要用途</p> </th> </tr></thead><tbody><tr><td> <p class="表格单元格"><code>times</code>方法</p> </td> <td> <p class="表格单元格">确定循环次数时使用</p> </td> </tr><tr><td> <p class="表格单元格"><code>for</code>语句</p> </td> <td> <p class="表格单元格">从对象取出元素时使用(each 的语法糖)</p> </td> </tr><tr><td> <p class="表格单元格"><code>while</code>语句</p> </td> <td> <p class="表格单元格">希望自由指定循环条件时使用</p> </td> </tr><tr><td> <p class="表格单元格"><code>until</code>语句</p> </td> <td> <p class="表格单元格">使用 while 语句使循环条件变得难懂时使用</p> </td> </tr><tr><td> <p class="表格单元格"><code>each</code>方法</p> </td> <td> <p class="表格单元格">从对象取出元素时使用</p> </td> </tr><tr><td> <p class="表格单元格"><code>loop</code>方法</p> </td> <td> <p class="表格单元格">不限制循环次数时使用</p> </td> </tr></tbody></table>
> **备注**
> 语法糖(syntax sugar),是指一种为了照顾一般人习惯而产生的特殊语法 1。例如,使用常规的语法调 用方法,那么加法运算应该写成 `3.add(2)`。但是对于一般人来说,写成 `3 + 2` 会更直观,更简明易懂。
> 语法糖并不会加强编程语言的任何功能,但是对于提高程序的易读性来讲是不可或缺的。
1即一种符合人的思维模式、工作习惯等,便于理解的“甜蜜”的语法。——译者注
以上都是笔者个人意见,仅供大家参考。
确定循环次数时循环处理使用 `times` 方法,除此以外的大部分循环处理,根据情况选择 `while` 语句或者 `each` 方法,一般也能写出直观易懂的程序。请大家先熟练掌握这三种循环方法。
> **专栏**
> **do~end 与 {~}**
> 在 `times` 方法的示例中,我们介绍了块的两种写法,`do ~ end` 与 `{ ~ }`。从执行效果来看,两种方法虽然没有太大区别,但一般我们会遵守以下这个约定俗成的编码规则:
> - > 程序是跨行写的时候使用 **`do ~ end`**
> - > 程序写在 1 行的时候用 **`{ ~ }`**
> 以 `times` 方法来举例,会有以下两种写法。
~~~
10.times do |i|
puts i
end
~~~
> 或者,
~~~
10.times{|i| puts i}
~~~
> 刚开始大家可能会有点不习惯。我们可以这样理解,`do ~ end` 表示程序要执行内容是多个处理的集合,而 `{ ~ }` 则表示程序需要执行的处理只有一个,即把整个带块的方法看作一个值。
> 如果用把 `do ~ end` 代码合并在一起,程序会变成下面这样:
~~~
10.times do |i| puts i end
~~~
> 以上写法,怎么看都给人一种很难断句的感觉。虽然实际上使用哪种写法都不会影响程序的运行,但在刚开始编写程序时,还是建议大家先遵守这个编码规则。
- 推荐序
- 译者序
- 前言
- 本书的读者对象
- 第 1 部分 Ruby 初体验
- 第 1 章 Ruby 初探
- 第 2 章 便利的对象
- 第 3 章 创建命令
- 第 2 部分 Ruby 的基础
- 第 4 章 对象、变量和常量
- 第 5 章 条件判断
- 第 6 章 循环
- 第 7 章 方法
- 第 8 章 类和模块
- 第 9 章 运算符
- 第 10 章 错误处理与异常
- 第 11 章 块
- 第 3 部分 Ruby 的类
- 第 12 章 数值类
- 第 13 章 数组类
- 第 14 章 字符串类
- 第 15 章 散列类
- 第 16 章 正则表达式类
- 第 17 章 IO 类
- 第 18 章 File 类与 Dir 类
- 第 19 章 Encoding 类
- 第 20 章 Time 类与 Date 类
- 第 21 章 Proc 类
- 第 4 部分 动手制作工具
- 第 22 章 文本处理
- 第 23 章 检索邮政编码
- 附录
- 附录 A Ruby 运行环境的构建
- 附录 B Ruby 参考集
- 后记
- 谢辞