1、版本声明 - RUBY文档中心

存疑，知行 RUBY文档中心
提供RUBY中文教程、文章、翻译作品

发表文章 发表文章　　 浏览文章 浏览文章　　 全文检索 全文检索　　 用户登录 用户登录
1、版本声明

原创：夏克　　发表时间：2005-6-15 3:34:20　　点击次数：345

日文原版由Ruby作者matz提供；最初的英文版由GOTO Kentaro和Julian Fondren翻译；Mark Slagell进行了深入的英文翻译和整理排版；中文版由oasis_me(cai@pardan.org)翻译，由huzheng(huzheng_001@163.com)整理，校对，并于2003年1月22日发布。
版权声明：RUBY文档中心的所有文章标明[原创]的均为本站作品，版权属RUBY中文化计划，若转载请注明；标明[翻译]的其外文版权归原作者，译文版权属RUBY中文化计划；标明[转贴]的，若原作者感到侵犯了他的著作权，那么请及时跟主持人联系，我们会尽快更正。

版权所有(C) RUBY中文化计划


2、Ruby是什么? - RUBY文档中心

存疑，知行 RUBY文档中心
提供RUBY中文教程、文章、翻译作品

发表文章 发表文章　　 浏览文章 浏览文章　　 全文检索 全文检索　　 用户登录 用户登录
2、Ruby是什么?

转帖：oasis_me　　发表时间：2005-6-15 3:37:44　　点击次数：478

Ruby是"一种用于迅速和简便的面向对象编程的解释性脚本语言";这意味着什么?

解释性脚本语言:
  • 有直接呼叫系统调用的能力
  • 强大的字符串操作和正则表达式
  • 开发中快速回馈

迅速和简便:
  • 无需变量声明
  • 变量无类型
  • 语法简单而坚实
  • 自动内存管理

面向对象编程
  • 任何事物都是一个对象
  • 类,继承,方法,等等
  • 单态方法
  • 模块糅合
  • 迭代器和闭包(closures)

以及:
  • 多精度整数
  • 异常处理模式
  • 动态装载
  • 线程

如果你对上面的那些概念还不熟悉,继续读,别担心.Ruby的箴言是"迅速和简便".
版权声明：RUBY文档中心的所有文章标明[原创]的均为本站作品，版权属RUBY中文化计划，若转载请注明；标明[翻译]的其外文版权归原作者，译文版权属RUBY中文化计划；标明[转贴]的，若原作者感到侵犯了他的著作权，那么请及时跟主持人联系，我们会尽快更正。

版权所有(C) RUBY中文化计划


3、开始 - RUBY文档中心

存疑，知行 RUBY文档中心
提供RUBY中文教程、文章、翻译作品

发表文章 发表文章　　 浏览文章 浏览文章　　 全文检索 全文检索　　 用户登录 用户登录
3、开始

转帖：oasis_me　　发表时间：2005-6-15 3:46:01　　点击次数：481

首先,你得检测Ruby是否已经安装了.在shell的提示符处(这里表示为"%",所以不要敲入%),敲入

% ruby -v

(-v 告诉解释器打印出Ruby的版本),然后敲回车.如果安装了Ruby,你会看见类似如下的信息:

% ruby -v
ruby 1.6.6 (2001-12-26) [i586-linux]

如果Ruby未安装,你可以让管理员安装它,或者可以自己做,Ruby是一个没有安装或使用限制的自由软件.

现在,让我们和Ruby玩玩.你可以通过一个-e参数直接在命令行上放置一个Ruby程序:

% ruby -e 'print "hello world\n"'
hello world

一般情况下,Ruby程序都会保存在一个文件里.

% cat > test.rb
print "hello world\n"
^D
% cat test.rb
print "hello world\n"
% ruby test.rb
hello world

^D指control-D.上面是在UNIX下的情况.如果你用DOS,那就这样:

C:\ruby> copy con: test.rb
print "hello world\n"
^Z
C:\ruby> type test.rb
print "hello world\n"
C:\ruby> ruby test.rb
hello world

当写比这个更具实际意义的代码时,你会希望使用一个真正的文本编辑器!

一些惊人复杂和有用的东西可以用一行命令的迷你程序做出来.举个例子,这个玩意儿将当前目录下所有C源程序和头文件中的foo替换为bar,并将原来的文件追加个".bak"备份:

% ruby -i.bak -pe 'sub "foo", "bar"' *.[ch]

这个程序和UNIX下的cat命令差不多(但比cat要慢):

% ruby -pe 0 file
版权声明：RUBY文档中心的所有文章标明[原创]的均为本站作品，版权属RUBY中文化计划，若转载请注明；标明[翻译]的其外文版权归原作者，译文版权属RUBY中文化计划；标明[转贴]的，若原作者感到侵犯了他的著作权，那么请及时跟主持人联系，我们会尽快更正。

版权所有(C) RUBY中文化计划


4、简单的例子 - RUBY文档中心

存疑，知行 RUBY文档中心
提供RUBY中文教程、文章、翻译作品

发表文章 发表文章　　 浏览文章 浏览文章　　 全文检索 全文检索　　 用户登录 用户登录
4、简单的例子

转帖：oasis_me　　发表时间：2005-6-15 3:57:52　　点击次数：546

让我们写一个计算阶乘的函数.对于阶乘的数学定义如下:

n! = 1 (当 n==0 时)
= n * (n-1)! (其它情况)

在Ruby里,可以这样来写:

def fact(n)
if n == 0
1
else
n * fact(n-1)
end
end

你可能会发现 end 的反复出现,正因为如此,Ruby被叫做"类Algol"语言.(实际上,Ruby的语法更像Eiffel).同时,你也可能会发现这段函数缺少return语句.在这里是可以用return的,但却不是必须的,因为一个Ruby的函数会自动返回它所最后赋值的元素.

让我们来试试我们的阶乘函数.加入一行代码会使它成为一个可工作的程序:

# Program to find the factorial of a number
# Save this as fact.rb
def fact(n)
if n == 0
1
else
n * fact(n-1)
end
end

print fact(ARGV[0].to_i), "\n"

这里,ARGV是一个包含命令行参数的数组,to_i将字符串转化为整数.

% ruby fact.rb 1
1
% ruby fact.rb 5
120

当参数为40时它可以工作吗?它将使你的计算器产生溢出(overflow)...

% ruby fact.rb 40
815915283247897734345611269596115894272000000000

它确实算出来了.实际上,Ruby能处理任何你机器的内存所允许的整数.实际上400!也可以:

% ruby fact.rb 400
64034522846623895262347970319503005850702583026002959458684
44594280239716918683143627847864746326467629435057503585681
08482981628835174352289619886468029979373416541508381624264
61942352307046244325015114448670890662773914918117331955996
44070954967134529047702032243491121079759328079510154537266
72516278778900093497637657103263503315339653498683868313393
52024373788157786791506311858702618270169819740062983025308
59129834616227230455833952075961150530223608681043329725519
48526744322324386699484224042325998055516106359423769613992
31917134063858996537970147827206606320217379472010321356624
61380907794230459736069956759583609615871512991382228657857
95493616176544804532220078258184008484364155912294542753848
03558374518022675900061399560145595206127211192918105032491
00800000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000

我们不能一眼看出是否算对了,但我想应该是对的吧. :-)

输入/求值循环

当你不带参数启动Ruby的时候,Ruby会从标准输入读入命令并在输入结束后再执行:

% ruby
print "hello world\n"
print "good-bye world\n"
^D
hello world
good-bye world

Ruby还包含了一个名为eval.rb的程序,它可以令到你在一个交互式的循环中输入Ruby代码并随之显示结果.在后面的教程里将广泛使用这个程序.

如果你有一个ANSI兼容(ANSI-compliant)的终端(如果你正运行着一个UNIX变种,一般都是的);在DOS下,你需要安装ANSI.SYS和ANSI.COM;然后就可以使用这个支持可视化缩排,警告提示,彩色高亮度显示的增强eval.rb了.不行的话,试试Ruby发行版里sample目录下的那个非ANSI版本,它可以运行于所有的控制台.这是一个简短的eval.rb对话:

% ruby eval.rb
ruby> print "Hello, world.\n"
Hello, world.
nil
ruby> exit

hello world 由 print 打印出来.下一行 nil 报告最后计算出来的值;Ruby并不区分语法和表达式,因此计算一段代码从根本上说和执行它是一回事.在这里,nil的意思是print并未返回一个有意义的值.注意:可以用exit或^D离开这个循环的解释器.

在整个教程里,'ruby>'表示我们这个非常有用却很小的eval.rb程序的命令提示符.
版权声明：RUBY文档中心的所有文章标明[原创]的均为本站作品，版权属RUBY中文化计划，若转载请注明；标明[翻译]的其外文版权归原作者，译文版权属RUBY中文化计划；标明[转贴]的，若原作者感到侵犯了他的著作权，那么请及时跟主持人联系，我们会尽快更正。

版权所有(C) RUBY中文化计划


5、字符串 - RUBY文档中心

存疑，知行 RUBY文档中心
提供RUBY中文教程、文章、翻译作品

发表文章 发表文章　　 浏览文章 浏览文章　　 全文检索 全文检索　　 用户登录 用户登录
5、字符串

转帖：oasis_me　　发表时间：2005-6-15 4:03:57　　点击次数：530

Ruby将字符串像数字一样处理.我们用单引号('...')或双引号("...")将它们括起来.

ruby> "abc"
"abc"
ruby> 'abc'
"abc"

单引号和双引号在某些情况下有不同的作用.一个由双引号括起来的字符串允许字符由一个前置的斜杠引出,而且可以用#{}内嵌表达式.而

单引号括起来的字符串并不会对字符串作任何解释;你看到的是什么便是什么.几个例子:

ruby> print "a\nb\nc","\n"
a
b
c
nil
ruby> print 'a\nb\n',"\n"
a\nb\nc
nil
ruby> "\n"
"\n"
ruby> '\n'
"\\n"
ruby> "\001"
"\001"
ruby> '\001'
"\\001"
ruby> "abcd #{5*3} efg"
"abcd 15 efg"
ruby> var = " abc "
" abc "
ruby> "1234#{var}5678"
"1234 abc 5678"

Ruby的字符串操作比C更灵巧,更直观.比如说,你可以用+把几个串连起来,用*把一个串重复好几遍:

ruby> "foo" + "bar"
"foobar"
ruby> "foo" * 2
"foofoo"

相比之下,在C里,因为需要精确的内存管理,串联字符串要笨拙的多:

char *s = malloc(strlen(s1)+strlen(s2)+1);
strcpy(s, s1);
strcat(s, s2);
/* ... */
free(s);

但对于Ruby,我们不需要考虑字符串的空间占用问题,这令到我们可以从烦琐的内存管理中解脱出来.

下面是一些字符串的处理,

串联:

ruby> word = "fo" + "o"
"foo"

重复:

ruby> word = word * 2
"foofoo"

抽取字符(注意:在Ruby里,字符被视为整数):

ruby> word[0]
102 # 102 is ASCII code of `f'
ruby> word[-1]
111 # 111 is ASCII code of `o'

(负的索引指从字符串尾算起的偏移量,而不是从串头.)

提取子串:

ruby> herb = "parsley"
"parsley"
ruby> herb[0,1]
"p"
ruby> herb[-2,2]
"ey"
ruby> herb[0..3]
"pars"
ruby> herb[-5..-2]
"rsle"

检查相等:

ruby> "foo" == "foo"
true
ruby> "foo" == "bar"
false

注意:在Ruby 1.0里,以上结果以大写字母出现.

好,让我们来试试这些特性.下面是一个猜词的谜题,可能"谜题"这个词用在下面的东西上太酷了一点;-)

# save this as guess.rb
words = ['foobar', 'baz', 'quux']
secret = words[rand(3)]
print "guess? "
while guess = STDIN.gets
guess.chop!
if guess == secret
print "You win!\n"
break
else
print "Sorry, you lose.\n"
end
print "guess? "
end
print "The word was ", secret, ".\n"

现在,别太担心代码细节了.下面是谜题程序运行的一个对话.

% ruby guess.rb
guess? foobar
Sorry, you lose.
guess? quux
Sorry, you lose.
guess? ^D
The word was baz.

(考虑到1/3的成功率,也许我本该做得好一点.)
版权声明：RUBY文档中心的所有文章标明[原创]的均为本站作品，版权属RUBY中文化计划，若转载请注明；标明[翻译]的其外文版权归原作者，译文版权属RUBY中文化计划；标明[转贴]的，若原作者感到侵犯了他的著作权，那么请及时跟主持人联系，我们会尽快更正。

版权所有(C) RUBY中文化计划


6、正则表达式 - RUBY文档中心

存疑，知行 RUBY文档中心
提供RUBY中文教程、文章、翻译作品

发表文章 发表文章　　 浏览文章 浏览文章　　 全文检索 全文检索　　 用户登录 用户登录
6、正则表达式

转帖：oasis_me　　发表时间：2005-6-15 4:10:37　　点击次数：392

我们再看一个更有趣的程序.这次我们来测试一个字符串是否和一个由简明模式(concise pattern)编码产生的描述相匹配.

在这些模式(pattern)里,一些字符或字符组合都有独特的意义,包括:

[] 范围描述符 (比如,[a - z] 表示在a 到 z 范围内的一个字母)

\w 字母或数字;相当于 [0-9A-Za-z]

\W 非字母,数字

\s [ \t\n\r\f]空字符;相当于 [ \t\n\r\f]

\S 非空字符

\d [0-9]数字;相当于 [0-9]

\D 非数字字符

\b 退格符 (0x08) (仅在范围描述符内部时)

\b 字边界(word boundary) (在范围描述符外部时)

\B 非字边界

* 前面元素出现0或多次

+ 前面元素出现1或多次

{m,n} 前面元素最少出现m次,最多出现n次

? 前面元素最多出现1次;相当于 {0,1}

| 与前面或后面的表达式匹配

() 群( grouping)

那些模式中共同使用的古怪词汇叫做正则表达式.就象Perl一样,Ruby也用前斜杠(而不是双引号)将它们括起来.如果你以前从未使用过正则表达式,也许它们看起来除了规则(regular)什么都不是,但花上一点儿时间了解它们是明智的.当你需要对字符串进行模式匹配,查找或其它操作时,它的高效的表达能力能治好你的头痛(并节约很多行代码).

举个例子,设想我们想要测试一个字符串是否符合这样的描述信息"由小写f开头,跟一个大写字母,并可能跟许多非小写字母在后面."如果你是一个老练的C程序员,大概你的头脑里已经装满几十行程序了,对不对?承认吧,你难以控制住自己.在Ruby里,你只需要将你的字符串用正则表达式/^f[A-Z](^[a-z])*$/检验一下就可以了.

那"一个由<>括起来的16位数呢"?没问题.

ruby> def chab(s) # "contains hex in angle brackets"
| (s =~ /<0(x|X)(\d|[a-f]|[A-F])+>/) != nil
| end
nil
ruby> chab "Not this one."
false
ruby> chab "Maybe this? {0x35}" # wrong kind of brackets
false
ruby> chab "Or this? <0x38z7e>" # bogus hex digit
false
ruby> chab "Okay, this: <0xfc0004>."
true

虽然,初看起来正则表达式挺让人头痛的,但你很快会因能够如此高效地表达出你心中的意思而感到满足.

下面是一个可以帮助你实验正则表达式的小程序,把它存为regx.rb,然后在命令行里输入'ruby regx.rb'运行.

# Requires an ANSI terminal!
st = "\033[7m"
en = "\033[m"
while TRUE
print "str> "
STDOUT.flush
str = gets
break if not str
str.chop!
print "pat> "
STDOUT.flush
re = gets
break if not re
re.chop!
str.gsub! re, "#{st}\\&#{en}"
print str, "\n"
end
print "\n"

这个小程序要求输入两次,一次字符串,一次正则表达式.输入的字符串由正则表达式检验,然后用反视高亮度显示所有匹配部分.先别管细节,等会儿就有代码分析.

str> foobar
pat> ^fo+
foobar
~~~

上面红色部分将在程序输入中以反视表示出.下面的"~~~"行是为了方便那些使用基于字符浏览器的人.

我们再试几个输入:

str> abc012dbcd555
pat> \d
abc012dbcd555

如果让你感到惊讶,看看本页开头部分的那个表格: \d与字母d无关,而是对应于单个数字.

如果有不止一种方法能匹配模式会怎样呢?

str> foozboozer
pat> f.*z
foozboozer
~~~~~~~~

之所以foozbooz被匹配而不只是fooz,是因为一个正则表达符尽可能匹配最长的子串.

下面是一个将冒号分隔的数字时间段从字符串中隔离出来的模式匹配.

str> Wed Feb 7 08:58:04 JST 1996
pat> [0-9]+:[0-9]+(:[0-9]+)?
Wed Feb 7 08:58:04 JST 1996

"=~"是一个用于匹配正则表达式的匹配(matching)运算符;它会返回在字符串里找到的匹配的位置,或者返回 nil 表示模式无法匹配.

ruby> "abcdef" =~ /d/
3
ruby> "aaaaaa" =~ /d/
nil
版权声明：RUBY文档中心的所有文章标明[原创]的均为本站作品，版权属RUBY中文化计划，若转载请注明；标明[翻译]的其外文版权归原作者，译文版权属RUBY中文化计划；标明[转贴]的，若原作者感到侵犯了他的著作权，那么请及时跟主持人联系，我们会尽快更正。

版权所有(C) RUBY中文化计划


7、数组 - RUBY文档中心

存疑，知行 RUBY文档中心
提供RUBY中文教程、文章、翻译作品

发表文章 发表文章　　 浏览文章 浏览文章　　 全文检索 全文检索　　 用户登录 用户登录
7、数组

转帖：oasis_me　　发表时间：2005-6-15 4:12:58　　点击次数：350

你通过在方括号里列出元素并用逗号将它们相互隔开来创建一个数组. Ruby的数组可以适应不同的对象类型

ruby> ary = [1, 2, "3"]
[1, 2, "3"]

就像前面提到的字符串一样.数组也可以相乘或相加

ruby> ary + ["foo", "bar"]
[1, 2, "3", "foo", "bar"]
ruby> ary * 2
[1, 2, "3", 1, 2, "3"]

我们可用索引来访问数组的任意一部分.

ruby> ary[0]
1
ruby> ary[0,2]
[1, 2]
ruby> ary[0..1]
[1, 2]
ruby> ary[-2]
2
ruby> ary[-2,2]
[2, "3"]
ruby> ary[-2..-1]
[2, "3"]

(负数索引表示到数组末尾的偏移,而不是从开头算起.)

数组可以和字符串互相转化,分别使用join和split:

ruby> str = ary.join(":")
"1:2:3"
ruby> str.split(":")
["1", "2", "3"]

哈希表

一个关联数组不通过连续的数字索引来访问,而是通过任何类型的主键(key)访问.这样的数组有时被叫作哈希(hash)或者字典(dictionary).在Ruby里,我们趋向于用哈希这个术语.将一对对的元素用逗号分隔开,并用大括号({})括起来,这样就组成了一个哈希表.你用一个关键字在哈希表里进行搜索,就像你在数组里用索引来提取数据一样.

ruby> h = {1 => 2, "2" => "4"}
{1=>2, "2"=>"4"}
ruby> h[1]
2
ruby> h["2"]
"4"
ruby> h[5]
nil
ruby> h[5] = 10 # appending value
10
ruby> h
{5=>10, 1=>2, "2"=>"4"}
ruby> h.delete 1 # deleting value
2
ruby> h[1]
nil
ruby> h
{5=>10, "2"=>"4"}
版权声明：RUBY文档中心的所有文章标明[原创]的均为本站作品，版权属RUBY中文化计划，若转载请注明；标明[翻译]的其外文版权归原作者，译文版权属RUBY中文化计划；标明[转贴]的，若原作者感到侵犯了他的著作权，那么请及时跟主持人联系，我们会尽快更正。

版权所有(C) RUBY中文化计划


8、回到那些简单的例子 - RUBY文档中心

存疑，知行 RUBY文档中心
提供RUBY中文教程、文章、翻译作品

发表文章 发表文章　　 浏览文章 浏览文章　　 全文检索 全文检索　　 用户登录 用户登录
8、回到那些简单的例子

转帖：oasis_me　　发表时间：2005-6-15 4:44:30　　点击次数：330

现在我们将前面的一些示例程序的代码坼开来分析一下.

下面的例子出现在简单的例子一节.

def fact(n)
if n == 0
1
else
n * fact(n-1)
end
end
print fact(ARGV[0].to_i), "\n"

因为是第一次解释,我们将逐行分析.

def fact(n)

第一行,def 用于定义一个函数(或者,更准确地说,一个方法(method);我们会在稍后的一节中详细讨论什么是一个方法).这里,它指明 fact 函数带一个参数,也就是 n.

if n == 0

if 用来检查一个条件.当条件吻合时,执行下面的代码;否则执行跟在else后的代码.

1

当条件成立时if 的值为 1.

else

如果条件不成立,执行从这里到end的代码.

n * fact(n-1)

如果条件不满足, if的值会是n乘fact(n-1)的结果.

end

第一个 end 与 if 语句对应.

end

第二个 end 与 def 语句对应.

print fact(ARGV[0].to_i), "\n"

这句用由命令行指定的值来调用fact()函数并打印结果.

ARGV是一个包含命令行参数的数组.ARGV的成员是字符串,所以我们必须通过to_i转化其为整数. Ruby并不像Perl那样自动将字符串转化为整数.

Hmmm...如果向程序赋一个负值作为参数会怎样?你看到这个问题了吗?你可以修复它吗?

Strings

下面我们来检查在字符串这章中出现的猜谜程序.由于这个要长一点,我们为每一行打上行数.

01 words = ['foobar', 'baz', 'quux']
02 secret = words[rand(3)]
03
04 print "guess? "
05 while guess = STDIN.gets
06 guess.chop!
07 if guess == secret
08 print "you win\n"
09 break
10 else
11 print "you lose.\n"
12 end
13 print "guess? "
14 end
15 print "the word is ", secret, ".\n"

这个程序里,我们使用了一个新的控制结构 while.只要某个指定的条件保持为真,while和它对应的end之间的代码会重复执行.

行2的rand(3)返回一个介于0-2之间的随机数.这个随机数用于提取数组 words 中的一个成员.

在行5我们通过STDIN.gets方法从标准输入读取一行.如果读行遇到时 EOF (文件结束), gets会返回nil.因此,与while相连的代码会一直执行直到它遇到^D(或DOS下的^Z),表示输入的结束.

行6的guess.chop!去掉 guess 的最后一个字符;那一定是个换行符.

行15,我们打印出要猜的词.我们写的代码是上向 print 语句传递三个参数(这三个参数一个接一个地打印),但也可以用一个参数等效地打印: 将secret写为 #{secret}以表明将它是一个要取值的变量,而非一个要打印的一般文字:

print "the word is #{secret}.\n"

正则表达式

最后我们来看看正则表达式一节的那个程序.

01 st = "\033[7m"
02 en = "\033[m"
03
04 while TRUE
05 print "str> "
06 STDOUT.flush
07 str = gets
08 break if not str
09 str.chop!
10 print "pat> "
11 STDOUT.flush
12 re = gets
13 break if not re
14 re.chop!
15 str.gsub! re, "#{st}\\&#{en}"
16 print str, "\n"
17 end
18 print "\n"

在行4,while的条件被硬设为 true,因此这好像构成了一个无限循环.但我们在行8和行13放置了break语句以跳出循环.这两个break语句也是 if 修饰辞(if modifier)的一个例子.一个"if修饰辞"当且仅当指明条件满足时执行它左边的语句.

再说说 chop! (出现在行9和行14).在Ruby里,我们亦可将"!"和"?"附于某些方法名字后面.惊叹号(!,有时大声地念作"bang!")暗示某些东西可能具破坏性(destructive),也就是指,某些东西可以改变它所触及的东西. chop!直接作用于一个字符串,但不带!的chop只会产生一个拷贝.下面有这一区别的演示.

ruby> s1 = "forth"
"forth"
ruby> s1.chop! # This changes s1.
"fort"
ruby> s2 = s1.chop # This puts a changed copy in s2,
"for"
ruby> s1 # ... without disturbing s1.
"fort"

以后你还会遇见以问号(?,有时大声地念做 "huh?")结束的方法名;这指"断言"(prediacte)方法,只会返回true或false.

行15应给予注意.首先,注意gsub!也是一个破坏函数.它通过替换所有符合 re 模式字符来修改 str(sub指替换,首字母 g 指全局, 比如,替换所有的匹配而不只是第一个匹配).到此为止,还好;但我们用什么来替代文本中的匹配部分呢? 在行1和行2里的 st 和 en 被分别定义为表示反转文本颜色(color-inverted)和恢复正常文本颜色的ANSI码. 在行15,它们被#{}括起以确保他们被前面定义的那样解释(这样我们才没看见变量名被打印出来).在这中间是 "\\&".这是个小把戏.因为替换字符串是由双引号引起的,一对反斜杠会被解释为一个单一的反斜杠;所以 gsub!实际上得到的是"\&",就一段特殊代码正好就是表示"任何与模式于第一处匹配的字符".因此新的字符串在被打印出来的时候,很像原来的那个,只不过匹配的部分以反视(inverse video)的方式高亮度显示出来.
版权声明：RUBY文档中心的所有文章标明[原创]的均为本站作品，版权属RUBY中文化计划，若转载请注明；标明[翻译]的其外文版权归原作者，译文版权属RUBY中文化计划；标明[转贴]的，若原作者感到侵犯了他的著作权，那么请及时跟主持人联系，我们会尽快更正。

版权所有(C) RUBY中文化计划


9、流程控制 - RUBY文档中心

存疑，知行 RUBY文档中心
提供RUBY中文教程、文章、翻译作品

发表文章 发表文章　　 浏览文章 浏览文章　　 全文检索 全文检索　　 用户登录 用户登录
9、流程控制

转帖：oasis_me　　发表时间：2005-6-15 4:49:54　　点击次数：344

这章我们将讨论更多的Ruby流程控制.

case

我们用case语句测试有次序的条件.正如我们所见的,这和C,Java的switch相当接近,但更强大.

ruby> i=8
ruby> case i
| when 1, 2..5
| print "1..5\n"
| when 6..10
| print "6..10\n"
| end
6..10
nil

2..5表示2到5之间的一个范围.下面的表达式测试 i 是否在范围内:

(2..5) === i

case 内部也是用关系运算符 === 来同时测试几个条件.为了保持ruby的面对对象性质, === 可以合适地理解为出现在 when 条件里的对

象.比如,下面的代码现在第一个 when 里测试字符串是否相等,并在第二个 when 里进行正则表达式匹配.

ruby> case 'abcdef'
| when 'aaa', 'bbb'
| print "aaa or bbb\n"
| when /def/
| print "includes /def/\n"
| end
includes /def/
nil

while

虽然你将会在下一章发现并不需要经常将循环体写得很清楚,但 Ruby 还是提供了一套构建循环的好用的方法.

while 是重复的 if.我们在猜词游戏和正则表达式中使用过它(见前面的章节);这里,当条件(condition)为真的时候,它围绕一个代码域以

while condition...end的形式循环.但 while 和 if 可以很容易就运用于单独语句:

ruby> i = 0
0
ruby> print "It's zero.\n" if i==0
It's zero.
nil
ruby> print "It's negative.\n" if i<0
nil
ruby> print "#{i+=1}\n" while i<3
1
2
3
nil

有时候你想要否定一个测试条件. unless 是 if 的否定, until 是一个否定的 while.在这里我把它们留给你实验.

There are four ways to interrupt the progress of a loop from inside. First, break means, as in C, to escape from the

loop entirely. Second, next skips to the beginning of the next iteration of the loop (corresponding to C's continue).

Third, ruby has redo, which restarts the current iteration. The following is C code illustrating the meanings of break,

next, and redo:

有四种从内部中断循环的方法.第一,和C一样break从循环中完全退出.第二, next 跳到下一次循环迭代的开始(对应于C的 continue ).第

三,Ruby有redo,它可以重新开始现在的迭代.下面是用 C 代码对break,next,redo的意义做了演示:

while (condition) {
label_redo:
goto label_next; /* ruby's "next" */
goto label_break; /* ruby's "break" */
goto label_redo; /* ruby's "redo" */
...
...
label_next:
}
label_break:
...

第四种方法是由循环内跳出的方法是 returen. return的结果是不仅从循环中跳出,而且会从含循环的方法中跳出.如果有参数,它会返回给方法调用,不然就返回nil.

for

C程序员现在会想知道怎样做一个"for"循环.Ruby的for比你想象的要有趣一点.下面的loop由集合中的元素控制运行:

for elt in collection
...
end

集合可以是一个数集(也是传统意义上的for循环):

ruby> for num in (4..6)
| print num,"\n"
| end
4
5
6
4..6

也可以是其它的什么类型的集合,比如一个数组:

ruby> for elt in [100,-9.6,"pickle"]
| print "#{elt}\t(#{elt.type})\n"
| end
100 (Fixnum)
-9.6 (Float)
pickle (String)
[100, -9.6, "pickle"]

但我们说过头了.for其实是 each 的另一写法,正巧,这是我们关于迭代器的第一个例子.下面的两种形式是等价的:

# If you're used to C or Java, you might prefer this.
for i in collection
...
end
# A Smalltalk programmer might prefer this.
collection.each {|i|
...
}

一旦你熟悉了迭代器,它便会常常代替传统的循环.它们一般更容易处理.因此,让我们接着学习更多关于迭代器的知识.
版权声明：RUBY文档中心的所有文章标明[原创]的均为本站作品，版权属RUBY中文化计划，若转载请注明；标明[翻译]的其外文版权归原作者，译文版权属RUBY中文化计划；标明[转贴]的，若原作者感到侵犯了他的著作权，那么请及时跟主持人联系，我们会尽快更正。

版权所有(C) RUBY中文化计划


10、迭代器 - RUBY文档中心

存疑，知行 RUBY文档中心
提供RUBY中文教程、文章、翻译作品

发表文章 发表文章　　 浏览文章 浏览文章　　 全文检索 全文检索　　 用户登录 用户登录
10、迭代器

转帖：oasis_me　　发表时间：2005-6-15 4:56:57　　点击次数：335

迭代器并不是Ruby发明的.它广泛地运用于各种面向对象语言.在Lisp中也有,只是不这么叫罢了.尽管如此,迭代器的概念并不为许多人熟悉,因此我们将在此做较为详细的介绍.

你知道,动词 iterate 的意思是做同一件事许多遍,因此,iterator就是用来将同一件事做许多次的东西.

当我们写代码时,我们需要各种环境下的循环.在C里,我们用for或者while.比如,

char *str;
for (str = "abcdefg"; *str != '\0'; str++) {
/* process a character here */
}

C的for(...)语法提供了一种写循环的抽象方法,但测试 *str 是否为空(null)字符需要程序员了解字符串内部结构的细节.这让C看起来像低级(low-level)语言.更高级的语言是通过它们更具弹性的迭代器支持来实现的.考虑下面的 sh 命令行脚本:

#!/bin/sh
for i in *.[ch]; do
# ... here would be something to do for each file
done

当前目录下所有的C源文件和头文件都将被处理,由命令行shell来一个个地捡取文件名并处理其中的细节.我想这是在比 C 要高的级别上工作,你觉得呢?

但有更多值得我们考虑的:在一种语言能够很好的给内建的数据类型的提供迭代器的同时,我们却仍需要回去用低级别的循环语言来实现对自己定义的数据类型的迭代,这真是让人失望.在面对对象编程时,用户经常一个接一个地定义数据类型,因此这是一个很严重的问题.

因此,所有的OOP语言都包含了一定的迭代器机制.某些语言为此提供一种特殊的类;Ruby则允许我们直接定义迭代器.

Ruby的String类型有很多有用的迭代器:

ruby> "abc".each_byte{|c| printf "<%c>", c}; print "\n"
<a><b><c>
nil

each_byte 是个用于字符串中每个字符的迭代器.每个字符串由局部变量c代替.这可以翻译为类似C的代码...

ruby> s="abc";i=0
0
ruby> while i<s.length
| printf "<%c>", s[i]; i+=1
| end; print "\n"
<a><b><c>
nil

...然而, each_byte 迭代器在概念上要简单些,而且即使以后 String 类突然有所改变也应该可以照样工作.使用迭代器的一个好处便是在此类改变中仍然可以保持健壮;一般的,它的确是好代码的一个特点.(好,请有点儿耐心,我们将会马上谈到什么是类)

String的另一个迭代器是 each_line.

ruby> "a\nb\nc\n".each_line{|l| print l}
a
b
c
nil

采用迭代器,这将很轻松的取代C的大多数编程效果(找换行符,生成子串等等)

前面出现的for语句通过each迭代器实现迭代功能. String的each和each_line的工作原理差不多,让我们用for重写上面的例子:

ruby> for l in "a\nb\nc\n"
| print l
| end
a
b
c
nil

我们可以用retry流程控制语句连接迭代循环,它会从头执行当前循环的迭代.

ruby> c=0
0
ruby> for i in 0..4
| print i
| if i == 2 and c == 0
| c = 1
| print "\n"
| retry
| end
| end; print "\n"
012
01234
nil

yield有时会在一个迭代器的定义中出现. yield将流程控制移至传递给迭代器的代码域(这将会在过程对象那一节介绍更多的细节).下面的例子定义了一个repeat迭代器,会依参数的设置执行多次代码域.

ruby> def repeat(num)
| while num > 0
| yield
| num -= 1
| end
| end
nil
ruby> repeat(3) { print "foo\n" }
foo
foo
foo
nil

利用retry,我们可以定义一个有while相同作用的迭代器,虽然在实际应用中它太慢了.

ruby> def WHILE(cond)
| return if not cond
| yield
| retry
| end
nil
ruby> i=0; WHILE(i<3) { print i; i+=1 }
012 nil

搞懂什么是迭代器了吗?有一些限制,但你可以写自己的迭代器;实际上,当你定义一个新的数据类型时,为它定义一个合适的迭代器经常也很方便.这样看来,上面的例子并不是很好用.在我们理解了类以后,我们可以讨论讨论更具实际意义的迭代器.
版权声明：RUBY文档中心的所有文章标明[原创]的均为本站作品，版权属RUBY中文化计划，若转载请注明；标明[翻译]的其外文版权归原作者，译文版权属RUBY中文化计划；标明[转贴]的，若原作者感到侵犯了他的著作权，那么请及时跟主持人联系，我们会尽快更正。

版权所有(C) RUBY中文化计划


11、面向对象思维 - RUBY文档中心

存疑，知行 RUBY文档中心
提供RUBY中文教程、文章、翻译作品

发表文章 发表文章　　 浏览文章 浏览文章　　 全文检索 全文检索　　 用户登录 用户登录
11、面向对象思维

转帖：oasis_me　　发表时间：2005-6-15 5:00:37　　点击次数：296

面向对象是一个挺让人迷惑的措辞.叫一切东西都是面向对象会让别人觉得你很时髦. Ruby 声称自己是面向对象的脚本语言;但究竟什么才是"面向对象"?

我们已经有了各种各样的答案,但所有这些恐怕都归结于同一件事.与其快速地概括它,不如让我们先花点儿时间考虑一下传统的编程模式.

传统意义上,一个编程问题从出现的各种数据,以及处理数据的过程(procedures)着手.在这一模式下,数据是呆板,被动和无用的;它完全的求助于那个体积庞大的,主动的,逻辑性的,全能的过程体.

这一做法的问题在于程序是由程序员写的,而他们仅仅能在工作中记下为数不多的细节.而随着工程的加大,它的核心也增长到难以记住整个工程是如何工作.细微的失误和敲打错误变得越来越容易造成难以发现的臭虫(bugs).繁杂和意外的干扰开始在核心内出现,维护也变得像企图抓住一只愤怒的乌贼还要不让它的触须碰到你脸.当然,对于传统方法,我们也有很多指导你去缩小和检测这些臭虫的方法,但更好的解决方法是彻底的改变我们的工作方式.

而面向对象所做的就是让我们将现实的,重复性的逻辑工作交给数据本身;它将我们对数据的概念从被动变为主动.换种说法:

  • 我们不再让数据像一个开口的盒子一样让我们随意地伸手进去并取出东西.
  • 我们将其看作一个封口的并带有开关和刻度盘的机器.

上面所说的"机器"的内部可能相当简单或者复杂;我们不能从外面窥视,我们也不能允许自己打开机器外壳(除非我们确实发现其中有误),因此我们只需要通过拨动开关,读取刻度来操作数据.机器一旦建好,我们便不再必须考虑它是如何运转.

也许,你会觉得这是在无端的增加自己的工作量,但这一方法可以让我们有效避免事情向坏的方向发展.

让我们从一个简单的毫无实际价值却至少能说明部分概念的例子开始.你的汽车有个计程器.它的工作就是记录从上次复位以来的行车路
程.我们将如何用编程语言建模?在C里面,这个计程器也许就是个数值变量,大概是float类型的.这个程序会通过每隔一定的行程增加它的值,在合适的时候也会复位为零.哪里会出问题呢?程序里的一个臭虫会无端的向这个变量赋一个伪值,这可以由某些未预料到的原因而发生.任何有过C编程经验的人都会知道那会花掉数小时或数天来除掉这个当找出时简单得荒唐无比的臭虫.(找出它的那一刻往往会使劲地拍拍前额)

同样的问题在使用面向对象时会从一个截然不同的角度着手.当设计它时,程序员会问的头一件事不会是"什么是最类似它的数据类型?"而是"究竟这玩意儿是干什么的?"这不同之处引出了一个稍带难度的东西.我们需要花点儿时间确定到底计程器是用来干什么的,外部世界希望怎样操纵它.然后我们决定构造一个允许我们增加,复位,读值的小机器,再就没别的了.

我们并未提供一种用于向计程器赋任意值的方法:为什么?因为我们都知道计程器不是那样工作的.你只能向计程器做那定义好的几件事,而且是那些允许我们去做的事.因此,如果程序里的别的什么东西错误地向其赋值(比如,车的温度控制器),这立刻意味着错误的发生.当运行它的时候(或者当编译时,视语言的性质而定)我们便会被告之禁止向计程器这个对象赋任意值.给出的消息也许并非如此清楚,但它应该接近其真实原因.这并不防止错误的发生,是吧?但它很快给我们直接指出了问题所在.这只是OO编程会为我们节约很多时间的许多方法中的一个.

一般的,我们会考虑将上面的那玩意儿抽象化,因为建个工厂来造机器比一个个单独地造要简单的多.我们不希望直接的造单独的计程器;
换句话,我们希望所有的计程器都能由一个模型造出.这个模式(如果你喜欢,叫它计程器工厂)对应于我们所称的一个类,每一个由它生成的单独的计程器(或由工厂造出)对应于一个对象.许多面向对象的语言要求我们在拥有一个新的对象之前定义它的类,但Ruby不是如此.

当然运用面对对象语言并不意味着就会有好的面对对象设计.实际上,用任何语言都有写出模糊,粗心,多虫,低造诣及不稳定的代码.Ruby能为你做的(作为反例,特别是C++)是让OO编程的实践足够自然,即使你仅仅在小范围内使用也不会感到必须凭借丑陋的代码去提高效率.我们将会随着这本手册深入地讨论Ruby实现这些预定目标的机制;下一章将是"开关和刻度表"(对象方法),然后我们将讨论到"工厂"(类).你还会跟我们来吗?
版权声明：RUBY文档中心的所有文章标明[原创]的均为本站作品，版权属RUBY中文化计划，若转载请注明；标明[翻译]的其外文版权归原作者，译文版权属RUBY中文化计划；标明[转贴]的，若原作者感到侵犯了他的著作权，那么请及时跟主持人联系，我们会尽快更正。

版权所有(C) RUBY中文化计划


12、方法 - RUBY文档中心

存疑，知行 RUBY文档中心
提供RUBY中文教程、文章、翻译作品

发表文章 发表文章　　 浏览文章 浏览文章　　 全文检索 全文检索　　 用户登录 用户登录
12、方法

转帖：oasis_me　　发表时间：2005-6-15 21:13:25　　点击次数：310

什么叫做一个方法呢?在OO编程里,我们不希望直接从外部操纵一个对象的数据;相反的,对象自己了解如何操纵自身(当被恰当地要求时).
你也许会说我们传递一条消息给一个对象,那些消息就会调用某类行为或者做有意义的回复.这些都会在我们无需了解或关心这个对象内部
工作机制的情况下发生.为我们所允许的可让其执行的作业(或等价的,它能听懂的消息)就是对象的方法.

在ruby里,我们通过点(dot notation,正如C++或Java)来调用一个对象的方法.被调用的对象在点的左方给出.

ruby> "abcdef".length
6

表面上看,这一字符串被问及其长度.技术上看,我们正调用对象"abcdef"的length方法.

其它的对象也许拥有一个稍有不同的length接口,或者根本没有.消息的如何应答在程序运行期间做决定,行为也取决于其所指变量.

ruby> foo = "abc"
"abc"
ruby> foo.length
3
ruby> foo = ["abcde", "fghij"]
["abcde", "fghij"]
ruby> foo.length
2

length的意义也会随对象的变化而改变.在上例中,第一次我们叫foo返回其长度,它对应于一个简单字符串,这里也就只有一种合理的答案.第二次,foo代表一个数组,我们也许会考虑其长度为2,5或10;但一般情况下最合适的答案当然是2(其它类型长度也应被很容易的猜出).

ruby> foo[0].length
5
ruby> foo[0].length + foo[1].length
10

这里需注意的是一个数组了解其自身作为一个数组的意义.Ruby中的一部分代码赋予了他们这一性质,因此对他们的需求可以自动的通过各种
适当的方法实现.由于相当少量的对应于我们在自然语言中所表达概念的方法名能被不同种类的数据如我们所愿地采用,这便将程序员从大量特殊函数名中解脱出来.OO编程语言的这一特点(在我看来,Java的这一点做的并不是很好)叫做多态(polymorphism).

当一个对象碰到一个不能理解的信息,一个错误便会"发生":

ruby> foo = 5
5
ruby> foo.length
ERR: (eval):1: undefined method `length' for 5(Fixnum)

所以我们就需要了解什么方法是被这一对象所接受的,虽然我们并不需要了解这个方法是如何工作的.

如果要给方法赋参数,参数应在一对括号内,

object.method(arg1, arg2)

如果不会引起歧义,括号可以去掉.

object.method arg1, arg2

Ruby里有个特殊变量self;它指向任意调用方法的对象.由于"self"经常用到,为了方便它可被省掉:

self.method_name(args...)

和这一样

method_name(args...)

我们传统意义上的函数调用只不过是self方式调用的一种简写罢了.这也使Ruby可以叫作一种纯正的面向对象语言.当然,为了那些搞不清在Ruby里函数调用其实就是对象方法的人,函数化的方法看起来很像在别的语言中的函数.如果我们喜欢,我们也可以叫它们函数,就仿佛他们不是真的对象方法一样.
版权声明：RUBY文档中心的所有文章标明[原创]的均为本站作品，版权属RUBY中文化计划，若转载请注明；标明[翻译]的其外文版权归原作者，译文版权属RUBY中文化计划；标明[转贴]的，若原作者感到侵犯了他的著作权，那么请及时跟主持人联系，我们会尽快更正。

版权所有(C) RUBY中文化计划


13、类 - RUBY文档中心

存疑，知行 RUBY文档中心
提供RUBY中文教程、文章、翻译作品

发表文章 发表文章　　 浏览文章 浏览文章　　 全文检索 全文检索　　 用户登录 用户登录
13、类

转帖：oasis_me　　发表时间：2005-6-15 21:16:08　　点击次数：323

现实世界满是对象,我们可以为他们分类.举个例子,非常小的孩子看见狗的时候,不管那是什么种类的,都会叫它们"bow-wow";我们天生有通过种类来了解世界的能力.

在OO编程的术语里,像"狗"这样的一类对象称之为一个类,一些特殊的属于这个类的对象叫作类的实体(instance of that class).

一般的,在Ruby或其它OO语言里创造一个对象首先得定义类的属性,接着再创造一个实体.为了说明这一点,让我们先来定义一个简单的Dog类.

ruby> class Dog
| def speak
| print "Bow Wow\n"
| end
| end
nil

在Ruby里,一个类的定义是在关键字class和end之间的一段代码.在域中的def开始定义类的一个方法(我们已在上一节讨论过了),它对应于此类中的某些特定的对象方法.

既然我们已有了Dog类,我们就可以用它来创造一只狗:

ruby> pochi = Dog.new
#<Dog:0xbcb90>

我们已完成了Dog类的一个全新的实体并为它取名pochi.任何类的new方法都会创生一个新的实体.因为pochi是一只对应于我们已定义类中的狗,它就有我们定义的Dog的一切属性.因为我们对Dog的想法实在简单,我们只能让pochi玩一种把戏.

ruby> pochi.speak
Bow Wow
nil

创造一个新的类实体有时也被称作类的实体化(instantiating).我们需要逗一只实实在在的狗;而不是希望让Dog类向我们汪汪叫.

ruby> Dog.speak
ERR: (eval):1: undefined method `speak' for Dog:class

吃"三明治的概念"显然也是毫无意义的.

同时,如果我们只是想听听狗叫声而不带任何情感,我们可以创造(实体化)一只短命的,临时存在的狗,并在消失前小吠几声.

ruby> (Dog.new).speak # or more commonly, Dog.new.speak
Bow Wow
nil

"等等,"你说,"这只可怜的家伙消失到什么地方去了?"是这样的:如果我们不介意给它个名字(就像早先给pochi一样),Ruby的自动垃圾回收器会认为那是只流浪犬,然后不留情的处理掉它.这的确可行,你知道的,因为我们可以随心所欲地创造任何的狗.
版权声明：RUBY文档中心的所有文章标明[原创]的均为本站作品，版权属RUBY中文化计划，若转载请注明；标明[翻译]的其外文版权归原作者，译文版权属RUBY中文化计划；标明[转贴]的，若原作者感到侵犯了他的著作权，那么请及时跟主持人联系，我们会尽快更正。

版权所有(C) RUBY中文化计划


14、继承 - RUBY文档中心

存疑，知行 RUBY文档中心
提供RUBY中文教程、文章、翻译作品

发表文章 发表文章　　 浏览文章 浏览文章　　 全文检索 全文检索　　 用户登录 用户登录
14、继承

转帖：oasis_me　　发表时间：2005-6-15 21:17:20　　点击次数：306

在日常生活中,我们对一切对象的分类都是有一定层次的.我们知道所有的猫都是哺乳动物,而所有的哺乳动物又都是动物.小型的类从更大的
类中继承它们的属性.如果一切的哺乳动物要呼吸,那么猫也要呼吸.

在Ruby里,我们可以这样表述这一概念:

ruby> class Mammal
| def breathe
| print "inhale and exhale\n"
| end
| end
nil
ruby> class Cat<Mammal
| def speak
| print "Meow\n"
| end
| end
nil

虽然我们并未指明一只猫要怎样呼吸,但因为Cat是定义为Mammal的子类的(在OO术语里,较小的类叫子类,相比较大的类称父类),每一只猫都将继承来自于Mammal类的行为.因此从程序员的角度出发,猫天生就拥有呼吸这一能力;当我们加上speak方法后,我们的猫就能呼吸,也可以发声了.

ruby> tama = Cat.new
#<Cat:0xbd80e8>
ruby> tama.breathe
inhale and exhale
nil
ruby> tama.speak
Meow
nil

也会遇到这样的情况:父类的某些属性不可以被某一特定的子类继承.虽然一般鸟类都会飞,但企鹅是鸟类中不会飞的一个子类.

ruby> class Bird
| def preen
| print "I am cleaning my feathers."
| end
| def fly
| print "I am flying."
| end
| end
nil
ruby> class Penguin<Bird
| def fly
| fail "Sorry. I'd rather swim."
| end
| end
nil

抛开脑尽力疲地为每一个新类定义属性,我们只需要新增或重定义子类和父类之间的区别.继承的这一用法有时也叫做特点编程(differential programming).这是面向对象编程的又一好处.
版权声明：RUBY文档中心的所有文章标明[原创]的均为本站作品，版权属RUBY中文化计划，若转载请注明；标明[翻译]的其外文版权归原作者，译文版权属RUBY中文化计划；标明[转贴]的，若原作者感到侵犯了他的著作权，那么请及时跟主持人联系，我们会尽快更正。

版权所有(C) RUBY中文化计划


15、重载方法 - RUBY文档中心

存疑，知行 RUBY文档中心
提供RUBY中文教程、文章、翻译作品

发表文章 发表文章　　 浏览文章 浏览文章　　 全文检索 全文检索　　 用户登录 用户登录
15、重载方法

转帖：oasis_me　　发表时间：2005-6-15 21:18:57　　点击次数：291

在子类里,我们可以通过重载父类方法来改变实体的行为.

ruby> class Human
| def identify
| print "I'm a person.\n"
| end
| def train_toll(age)
| if age < 12
| print "Reduced fare.\n";
| else
| print "Normal fare.\n";
| end
| end
| end
nil
ruby> Human.new.identify
I'm a person.
nil
ruby> class Student1<Human
| def identify
| print "I'm a student.\n"
| end
| end
nil
ruby> Student1.new.identify
I'm a student.
nil

如果我们只是想增强父类的 identify 方法而不是完全地替代它,就可以用 super.

ruby> class Student2<Human
| def identify
| super
| print "I'm a student too.\n"
| end
| end
nil
ruby> Student2.new.identify
I'm a human.
I'm a student too.
nil

super 也可以让我们向原有的方法传递参数.这里有时会有两种类型的人...

ruby> class Dishonest<Human
| def train_toll(age)
| super(11) # we want a cheap fare.
| end
| end
nil
ruby> Dishonest.new.train_toll(25)
Reduced fare.
nil

ruby> class Honest<Human
| def train_toll(age)
| super(age) # pass the argument we were given
| end
| end
nil
ruby> Honest.new.train_toll(25)
Normal fare.
nil
版权声明：RUBY文档中心的所有文章标明[原创]的均为本站作品，版权属RUBY中文化计划，若转载请注明；标明[翻译]的其外文版权归原作者，译文版权属RUBY中文化计划；标明[转贴]的，若原作者感到侵犯了他的著作权，那么请及时跟主持人联系，我们会尽快更正。

版权所有(C) RUBY中文化计划


16、访问控制 - RUBY文档中心

存疑，知行 RUBY文档中心
提供RUBY中文教程、文章、翻译作品

发表文章 发表文章　　 浏览文章 浏览文章　　 全文检索 全文检索　　 用户登录 用户登录
16、访问控制

转帖：oasis_me　　发表时间：2005-6-15 21:20:22　　点击次数：300

前面,我们说Ruby没有函数,只有方法.而且实际上有不止一种方法.这一节我们介绍访问控制(access controls).

想想当我们在"最高层"而不是在一个类的定义里定义一个方法时会发生什么.我们可以把这样的方法设想为在像C那样的传统语言中的函数的类似物.

ruby> def square(n)
| n * n
| end
nil
ruby> square(5)
25

我们的新方法看起来不属于任何类,但实际上Ruby将其分给Object类,也就是所有其它类的父类.因此,所有对象现在都可以使用这一方法.这本应是正确的,但有个小陷阱:它是所有类的私有(private)方法.我们将在下面讨论这是什么意思,但一个结果是它只能以函数的风格调用,像这样:

ruby> class Foo
| def fourth_power_of(x)
| square(x) * square(x)
| end
| end
nil
ruby> Foo.new.fourth_power_of 10
10000

我们不允许向一个对象明确地运用这一方法:

ruby> "fish".square(5)
ERR: (eval):1: private method `square' called for "fish":String

这一聪明的做法在 ruby 使得提供可以像在传统语言中那样运用函数的同时保持了 ruby 的纯OO性质(函数仍是对象方法,但接受者隐式的为self).

在OO编程里,有一个不成文的习惯,我们在前面的章节里有所暗示的,即有关规格(specification)和实现(implementation)的区别,或者说对象被要求完成什么任务和实际上它是怎样完成的.对象的内部工作应该对用户保持隐蔽;他们应该只关心输入什么和输出什么,并相信对象知道它在内部是在做什么.如此,某些外面世界看不见但却在内部使用(并可以由程序员在任何需要的时候改进,而不用改变用户看到的类的对象)的方法将很有用.在下面这个普通的例子里,你可以把engine看作类的内部隐式方法.

ruby> class Test
| def times_two(a)
| print a," times two is ",engine(a),"\n"
| end
| def engine(b)
| b*2
| end
| private:engine # this hides engine from users
| end
Test
ruby> test = Test.new
#<Test:0x4017181c>
ruby> test.engine(6)
ERR: (eval):1: private method `engine' called for #<Test:0x4017181c>
ruby> test.times_two(6)
6 times two is 12.
nil

开始时,我们本希望test.engine(6)返回12,但随后当我们扮演一个Test对象用户时我们了解到engine是不可访问的(inaccessible).只有Test的其它方法,像 times_two可以使用 engine.我们被要求保持程序的外部接口,也就是 times_two这些方法.管理此类的程序员可以自由的更改engine(这里,可能把b*2改为b+b,假设这样可以提高性能)而不影响用户与 Test 对象打交道.这个例子当然过于简单;访问控制的优点只有当我们开始写更复杂和有趣的类时方能显现出来.
版权声明：RUBY文档中心的所有文章标明[原创]的均为本站作品，版权属RUBY中文化计划，若转载请注明；标明[翻译]的其外文版权归原作者，译文版权属RUBY中文化计划；标明[转贴]的，若原作者感到侵犯了他的著作权，那么请及时跟主持人联系，我们会尽快更正。

版权所有(C) RUBY中文化计划


17、单态方法 - RUBY文档中心

存疑，知行 RUBY文档中心
提供RUBY中文教程、文章、翻译作品

发表文章 发表文章　　 浏览文章 浏览文章　　 全文检索 全文检索　　 用户登录 用户登录
17、单态方法

转帖：oasis_me　　发表时间：2005-6-15 21:21:40　　点击次数：288

实体的行为取决于其类,但很多时候我们知道一个特定的实体需要特定的行为.在很多语言里,我们必须陷入另外再定义一个类的麻烦里,即使它只是用来接着实体化一次.在Ruby里,我们可以赋予任何对象属于其自身的方法.

ruby> class SingletonTest
| def size
| print "25\n"
| end
| end
nil
ruby> test1 = SingletonTest.new
#<SingletonTest:0xbc468>
ruby> test2 = SingletonTest.new
#<SingletonTest:0xbae20>
ruby> def test2.size
| print "10\n"
| end
nil
ruby> test1.size
25
nil
ruby> test2.size
10
nil

在这个例子里,test1和test2属于相同的类,但test2已被赋给一个重载的size方法,因而他们有不同的行为.一个仅属于某个对象的方法叫做单态方法.

单态方法常常用于图形用户界面(GUI)的元素的设计,在那里当不同的按钮被压下时将会激发不同的事件.

单态方法并非Ruby的专利,它也出现在CLOS,Dylan等语言中.同时,有些语言,比如,Self和NewtonScript仅有单态方法.他们有时被称作基于范例(prototype-based)语言.
版权声明：RUBY文档中心的所有文章标明[原创]的均为本站作品，版权属RUBY中文化计划，若转载请注明；标明[翻译]的其外文版权归原作者，译文版权属RUBY中文化计划；标明[转贴]的，若原作者感到侵犯了他的著作权，那么请及时跟主持人联系，我们会尽快更正。

版权所有(C) RUBY中文化计划


18、模块 - RUBY文档中心

存疑，知行 RUBY文档中心
提供RUBY中文教程、文章、翻译作品

发表文章 发表文章　　 浏览文章 浏览文章　　 全文检索 全文检索　　 用户登录 用户登录
18、模块

转帖：oasis_me　　发表时间：2005-6-15 21:23:27　　点击次数：299

Ruby的模块非常类似类,除了:

  • 模块不可以有实体
  • 模块不可以有子类
  • 模块由module...end定义.

实际上...模块的'模块类'是'类的类'这个类的父类.搞懂了吗?不懂?让我们继续看下去吧.

模块有两种用法.其一是将相近的方法和实体放在一个相对集中的域里.Ruby标准包里的Math模块就扮演着这一角色:

ruby> Math.sqrt(2)
1.41421
ruby> Math::PI
3.14159

::操作符告诉 Ruby 解释器在哪里找常数的值(可以想像,Math外的其它模块用PI表示其它的一些东西).如果我们想省掉 :: 直接调用一个模块的方法和常数,我们可以用 include:

ruby> include Math
Object
ruby> sqrt(2)
1.41421
ruby> PI
3.14159

模块的另一用法是糅和(mixin).某些OO语言,包括C++,允许多重继承(multiple inheritance),即从多个的父类里继承. 现实世界里一个多重继承的例子是闹钟:你可以想像闹钟属于钟类同是属于带蜂音器的事物类.

Ruby并未特意实现真正的多重继承,但糅和技术是一很好的替代.记得模块无法实体化或子类化;但如果我们在类定义里include一个方法,
它的方法便实际上已加入,或"糅合"进这个类.

糅合可以视为一种寻求一切我们希望得到的特定属性的方法.举个例子,如果一个类有个 each 方法,把标准库里的Enumerable模块糅合进来就自然地赋予我们 sort 和 find 两个方法.

模块的使用令到我们获得多重继承的基本功能却可通过简单的树结构表述类关系,同时也可观地简化了语言的实现(Java的设计者也做了一个类似的选择).
版权声明：RUBY文档中心的所有文章标明[原创]的均为本站作品，版权属RUBY中文化计划，若转载请注明；标明[翻译]的其外文版权归原作者，译文版权属RUBY中文化计划；标明[转贴]的，若原作者感到侵犯了他的著作权，那么请及时跟主持人联系，我们会尽快更正。

版权所有(C) RUBY中文化计划


19、过程对象 - RUBY文档中心

存疑，知行 RUBY文档中心
提供RUBY中文教程、文章、翻译作品

发表文章 发表文章　　 浏览文章 浏览文章　　 全文检索 全文检索　　 用户登录 用户登录
19、过程对象

转帖：oasis_me　　发表时间：2005-6-15 21:24:58　　点击次数：274

我们总是希望对未知事件分类.当它发生时,向其它方法传递一块作为参数的代码是最容易地解决方法,也就是说我们希望像处理数据一样处理代码.

一个新的过程对象可以通过proc创建:

ruby> quux = proc {
| print "QUUXQUUXQUUX!!!\n"
| }
#<Proc:0x4017357c>

现在quux指向一个对象,像其它对象一样,它也有可以调用的行为.特别的,我们可以用call方法执行它:

ruby> quux.call
QUUXQUUXQUUX!!!
nil

那么quux可以用做一个方法的参数吗?当然.

ruby> def run( p )
| print "About to call a procedure...\n"
| p.call
| print "There: finished.\n"
| end
nil
ruby> run quux
About to call a procedure...
QUUXQUUXQUUX!!!
There: finished.
nil

trap方法令到我们可以对任何系统信号做出我们自己的选择.

ruby> inthandler = proc{ print "^C was pressed.\n" }
#<Proc:0x401730a4>
ruby> trap "SIGINT", inthandler
#<Proc:0x401735e0>

一般的,敲入^C将导致解释器退出.但现在一个信息被打印出来,解释器继续执行着,所以你不会丢失掉正在作业的工作.(你不会永远留在解释器里,你仍可以用 exit 或者按 ^D退出)

最后在我们开始下一节之前还应注意的一点是:在将一个过程对象捆绑到一个信号之前,不是必须给这个过程对象命名的.一个等效的匿名(anonymous)过程对象像这样

ruby> trap "SIGINT", proc{ print "^C was pressed.\n" }
nil

或着更简略,

ruby> trap "SIGINT", 'print "^C was pressed.\n"'
nil

这种简写为你提供了一种方便和可读性更强的写小匿名过程的路子.
版权声明：RUBY文档中心的所有文章标明[原创]的均为本站作品，版权属RUBY中文化计划，若转载请注明；标明[翻译]的其外文版权归原作者，译文版权属RUBY中文化计划；标明[转贴]的，若原作者感到侵犯了他的著作权，那么请及时跟主持人联系，我们会尽快更正。

版权所有(C) RUBY中文化计划


20、变量 - RUBY文档中心

存疑，知行 RUBY文档中心
提供RUBY中文教程、文章、翻译作品

发表文章 发表文章　　 浏览文章 浏览文章　　 全文检索 全文检索　　 用户登录 用户登录
20、变量

转帖：oasis_me　　发表时间：2005-6-15 21:26:46　　点击次数：286

Ruby有三类变量,一种常量和两种严格意义上的伪变量(pseudo-variables).变量和常量都没有类型.虽然无类型变量存在一定的缺点,但却有更多的优点并很好的符合Ruby快速简便(quick and easy)的哲学精神.

在大多数语言里,变量都必须指定其类型,可更改性(是不是个常数)和范围;由于类型的不存在,剩下的东西也可由变量名字很快确定(你马上会看见),在Ruby里我们不需要变量声明.

由首字母标识符将其分类:

$ 全局变量
@ 实变量
[a-z] 局部变量
[A-Z] 常量

唯一的例外是Ruby的伪变量:self,它永远指向当前正执行着的对象或未初始化变量的空值(meaningless value)nil.虽然这两者的命名都像是局部变量,但 self 却是个由解释器把持的全局变量,而 nil 实际上是个常量.既然只有这两种意外,他们并不会过多的干扰我们.

你并能向 self 或 nil 赋值.下面的例子中, main作为 self 的值,指向最高层的对象:

ruby> self
main
ruby> nil
nil
版权声明：RUBY文档中心的所有文章标明[原创]的均为本站作品，版权属RUBY中文化计划，若转载请注明；标明[翻译]的其外文版权归原作者，译文版权属RUBY中文化计划；标明[转贴]的，若原作者感到侵犯了他的著作权，那么请及时跟主持人联系，我们会尽快更正。

版权所有(C) RUBY中文化计划


21、全局变量 - RUBY文档中心

存疑，知行 RUBY文档中心
提供RUBY中文教程、文章、翻译作品

发表文章 发表文章　　 浏览文章 浏览文章　　 全文检索 全文检索　　 用户登录 用户登录
21、全局变量

转帖：oasis_me　　发表时间：2005-6-15 21:27:59　　点击次数：290

全局变量由$开头.它们可以在程序的任何位置访问到.在初始化前,全局变量有一个特殊的值 nil.

ruby> $foo
nil
ruby> $foo = 5
5
ruby> $foo
5

应谨慎使用全局变量.由于在任何地方都可以被写因此他们相当危险.滥用全局变量会导致很难隔离臭虫;同时也视为程序的设计未经严格考虑.当你发现必须要使用全局变量时,记得给它一个不会在其它地方一不小心就用到的描述性名字(像上面那样叫$foo可能不是一个好想法).

全局变量的好处是其可以被跟踪;你可以做一个当变量值改变时被调用的过程.

ruby> trace_var :$x, proc{print "$x is now ", $x, "\n"}
nil
ruby> $x = 5
$x is now 5
5

当一个全局变量(改变时)作为一个过程的激发器,我们也管它叫活动变量(active variable).比如说,它可用于保持GUI显示的更新.

这里列出了一些以$打头并跟单个字符的特殊变量.比如,$$包含了Ruby解释器的进程id,它是只读的.这里是主要的系统变量以及它们的含义(细节可在Ruby的参考手册中查到):

$! 最近一次的错误信息
$@ 错误产生的位置
$_ gets最近读的字符串
$. 解释器最近读的行数(line number)
$& 最近一次与正则表达式匹配的字符串
$~ 作为子表达式组的最近一次匹配
$n 最近匹配的第n个子表达式(和$~[n]一样)
$= 是否区别大小写的标志
$/ 输入记录分隔符
$\ 输出记录分隔符
$0 Ruby脚本的文件名
$* 命令行参数
$$ 解释器进程ID
$? 最近一次执行的子进程退出状态

上面的 $_ 和 $~ 都有作用范围.它们的名字暗示其为全局的,但它们一般都是这样用的,关于它们的命名有历史上的原因.
版权声明：RUBY文档中心的所有文章标明[原创]的均为本站作品，版权属RUBY中文化计划，若转载请注明；标明[翻译]的其外文版权归原作者，译文版权属RUBY中文化计划；标明[转贴]的，若原作者感到侵犯了他的著作权，那么请及时跟主持人联系，我们会尽快更正。

版权所有(C) RUBY中文化计划


22、实变量 - RUBY文档中心

存疑，知行 RUBY文档中心
提供RUBY中文教程、文章、翻译作品

发表文章 发表文章　　 浏览文章 浏览文章　　 全文检索 全文检索　　 用户登录 用户登录
22、实变量

转帖：oasis_me　　发表时间：2005-6-15 21:29:11　　点击次数：272

一个实变量由@开头,它的范围限制在 self 对象内.两个不同的对象,即使属于同一个类,也可以拥有不同值的实变量.从对象外部来看,实变量不能改变甚至观察(比如, Ruby的实变量从来不是公用的),除非方法由程序员明确声明.像全局变量一样,实变量在初始前的值是nil.

Ruby的实变量用不着声明.这暗含着对象的弹性结构.实际上,每个实变量都是在第一次出现时动态加入对象的.

ruby> class InstTest
| def set_foo(n)
| @foo = n
| end
| def set_bar(n)
| @bar = n
| end
| end
nil
ruby> i = InstTest.new
#<InstTest:0x83678>
ruby> i.set_foo(2)
2
ruby> i
#<InstTest:0x83678 @foo=2>
ruby> i.set_bar(4)
4
ruby> i
#<InstTest:0x83678 @foo=2, @bar=4>

注意上例中直到调用了 set_bar方法 i 才报告 @bar 的值.
版权声明：RUBY文档中心的所有文章标明[原创]的均为本站作品，版权属RUBY中文化计划，若转载请注明；标明[翻译]的其外文版权归原作者，译文版权属RUBY中文化计划；标明[转贴]的，若原作者感到侵犯了他的著作权，那么请及时跟主持人联系，我们会尽快更正。

版权所有(C) RUBY中文化计划


23、局部变量 - RUBY文档中心

存疑，知行 RUBY文档中心
提供RUBY中文教程、文章、翻译作品

发表文章 发表文章　　 浏览文章 浏览文章　　 全文检索 全文检索　　 用户登录 用户登录
23、局部变量

转帖：oasis_me　　发表时间：2005-6-15 21:31:33　　点击次数：254

局部变量由小写字母或下划线(_)开头.局部变量不像全局和实变量一样在初始化前含nil值.

ruby> $foo
nil
ruby> @foo
nil
ruby> foo
ERR: (eval):1: undefined local variable or method `foo' for main(Object)

对局部变量的第一次赋值做的很像一次声明.如果你指向一个未初始化的局部变量,Ruby解释器会认为那是一个方法的名字;正如上面所见错误

信息的.

一般的,局部变量的范围会是

  • proc{...}
  • loop{...}
  • def...end
  • class...end
  • module...end
  • 整个程序(除非符合上面某个条件)

下面的例子,define?是一个检查标识符是否已定义的操作符.如果已定义它将返回标识符的描述,否则返回nil.正如你所见的,bar的范围是

loop的局部变量;当loop退出时,bar无定义.

ruby> foo = 44; print foo, "\n"; defined? foo
44
"local-variable"
ruby> loop{bar=45; print bar, "\n"; break}; defined? bar
45
nil

一个范围内的过程对象共享这个范围内的局部变量.这里,局部变量 bar 由 main 和过程对象 p1, p2共享:

ruby> bar=0
0
ruby> p1 = proc{|n| bar=n}
#<Proc:0x8deb0>
ruby> p2 = proc{bar}
#<Proc:0x8dce8>
ruby> p1.call(5)
5
ruby> bar
5
ruby> p2.call
5

注意开始的"bar=0"不能省略;此赋值允许bar的范围被 p1和 p2共享.不然 p1, p2 将会分别生成并处理它们自己的局部变量 bar, 调用 p2

也将导致"未定义局部变量或方法"错误.

过程对象的强大在于它们能被作为参数传递:共享的局部变量即使传递出原范围也仍然有效.

ruby> def box
| contents = 15
| get = proc{contents}
| set = proc{|n| contents = n}
| return get, set
| end
nil
ruby> reader, writer = box
[#<Proc:0x40170fc0>, #<Proc:0x40170fac>]
ruby> reader.call
15
ruby> writer.call(2)
2
ruby> reader.call
2

Ruby对待范围的办法相当聪明.显然,上面例子里 contents 变量是由 reader 和 writer 共享的.我们也可以像上面那样创造多对使用box的

reader-writer;每一对共享一个 contents 变量,对之间不相干扰.

ruby> reader_1, writer_1 = box
[#<Proc:0x40172820>, #<Proc:0x4017280c>]
ruby> reader_2, writer_2 = box
[#<Proc:0x40172668>, #<Proc:0x40172654>]
ruby> writer_1.call(99)
99
ruby> reader_1.call
99
ruby> reader_2.call
15
版权声明：RUBY文档中心的所有文章标明[原创]的均为本站作品，版权属RUBY中文化计划，若转载请注明；标明[翻译]的其外文版权归原作者，译文版权属RUBY中文化计划；标明[转贴]的，若原作者感到侵犯了他的著作权，那么请及时跟主持人联系，我们会尽快更正。

版权所有(C) RUBY中文化计划


24、类常量 - RUBY文档中心

存疑，知行 RUBY文档中心
提供RUBY中文教程、文章、翻译作品

发表文章 发表文章　　 浏览文章 浏览文章　　 全文检索 全文检索　　 用户登录 用户登录
24、类常量

转帖：oasis_me　　发表时间：2005-6-15 21:32:41　　点击次数：269

一个常量由大写字母开头.它应最多被赋值一次.在Ruby的当前版本中,常量的再赋值只会产生警告而不是错误(non-ANSI版的eval.rb不会报告这一警告)

ruby>fluid=30
30
ruby>fluid=31
31
ruby>Solid=32
32
ruby>Solid=33
(eval):1: warning: already initialized constant Solid
33

常量可以定义在类里,但不像实变量,它们可以在类的外部访问.

ruby> class ConstClass
| C1=101
| C2=102
| C3=103
| def show
| print C1," ",C2," ",C3,"\n"
| end
| end
nil
ruby> C1
ERR: (eval):1: uninitialized constant C1
ruby> ConstClass::C1
101
ruby> ConstClass.new.show
101 102 103
nil

常量也可以定义在模块里.

ruby> module ConstModule
| C1=101
| C2=102
| C3=103
| def showConstants
| print C1," ",C2," ",C3,"\n"
| end
| end
nil
ruby> C1
ERR: (eval):1: uninitialized constant C1
ruby> include ConstModule
Object
ruby> C1
101
ruby> showConstants
101 102 103
nil
ruby> C1=99 # not really a good idea
99
ruby> C1
99
ruby> ConstModule::C1 # the module's constant is undisturbed ...
101
ruby> ConstModule::C1=99
ERR: (eval):1: compile error
(eval):1: parse error
ConstModule::C1=99
^
ruby> ConstModule::C1 # .. regardless of how we tamper with it.
101
版权声明：RUBY文档中心的所有文章标明[原创]的均为本站作品，版权属RUBY中文化计划，若转载请注明；标明[翻译]的其外文版权归原作者，译文版权属RUBY中文化计划；标明[转贴]的，若原作者感到侵犯了他的著作权，那么请及时跟主持人联系，我们会尽快更正。

版权所有(C) RUBY中文化计划


25、异常处理:rescue - RUBY文档中心

存疑，知行 RUBY文档中心
提供RUBY中文教程、文章、翻译作品

发表文章 发表文章　　 浏览文章 浏览文章　　 全文检索 全文检索　　 用户登录 用户登录
25、异常处理:rescue

转帖：oasis_me　　发表时间：2005-6-15 21:36:10　　点击次数：294

一个运行着的程序常会遇到意外的问题.一个要读取的文件不存在;当希望存入一些数据时磁盘满了;用户可能输入不恰当的数据.

ruby> file = open("some_file")
ERR: (eval):1:in `open': No such file or directory - some_file

一个健壮的程序会合理并漂亮的处理这些问题.面对那些异常是一件讨人厌的工作.C程序员被要求做到检查每一个可能导致错误发生的系统调用的返回值并立刻做出决定.

FILE *file = fopen("some_file", "r");
if (file == NULL) {
fprintf( stderr, "File doesn't exist.\n" );
exit(1);
}
bytes_read = fread( buf, 1, bytes_desired, file );
if (bytes_read != bytes_desired ) {
/* do more error handling here ... */
}
...

这项无聊的工作会使程序员最终变得马虎并忽略掉它,结果是程序无法应对异常.令一方面,这样也会降低程序的可读性.因为过多的错误处理使有意义的代码也变得杂乱了.

在Ruby里,就像其它的现代语言,我们可以通过隔离的办法处理代码域里的异常,因此,这有着惊人的效果却又不会为程序员或以后希望读它的其它人造成过度的负担.代码域由begin开始直到遇到一个异常,这将导致转向一个由rescue标记的错误处理代码域.如果异常没发生,rescue代码就不会使用.下面的代码返回文本文件的第一行,如果有异常则返回 nil.

def first_line( filename )
begin
file = open("some_file")
info = file.gets
file.close
info # Last thing evaluated is the return value
rescue
nil # Can't read the file? then don't return a string
end
end

有时我们会希望围绕问题展开创造性工作.这里,如果文件不存在,我们用标准输入代替:

begin
file = open("some_file")
rescue
file = STDIN
end
begin
# ... process the input ...
rescue
# ... and deal with any other exceptions here.
end

retry 用于 rescue 代码表示又重新执行 begin 代码.这让我们可以压缩前面的例子:

fname = "some_file"
begin
file = open(fname)
# ... process the input ...
rescue
fname = "STDIN"
retry
end

但这仍有一点瑕疵.一个不存在的文件将导致不停止地 retry.你在使用 retry 做异常处理时应注意到这一点.

每个Ruby库在遇到错误时都会提交一个异常,你可以在自己的代码里明确地提交异常.用 raise 来提交异常.它带一个参数,也就是描述异常的一个字符串.参数是可选的但不应被省略.之后它可以通过一个特殊的全局变量 $! 访问.

ruby> raise "test error"
test error
ruby> begin
| raise "test2"
| rescue
| print "An error occurred: ",$!, "\n"
| end
An error occurred: test2
nil
版权声明：RUBY文档中心的所有文章标明[原创]的均为本站作品，版权属RUBY中文化计划，若转载请注明；标明[翻译]的其外文版权归原作者，译文版权属RUBY中文化计划；标明[转贴]的，若原作者感到侵犯了他的著作权，那么请及时跟主持人联系，我们会尽快更正。

版权所有(C) RUBY中文化计划


26、异常处理:ensure - RUBY文档中心

存疑，知行 RUBY文档中心
提供RUBY中文教程、文章、翻译作品

发表文章 发表文章　　 浏览文章 浏览文章　　 全文检索 全文检索　　 用户登录 用户登录
26、异常处理:ensure

转帖：oasis_me　　发表时间：2005-6-15 21:38:27　　点击次数：252

当一个方法结束工作时我们也许需要进行清理工作.也许一个打开的文件需要关闭,缓冲区的数据应清空等等.如果对于每一个方法这里永远只有一个退出点,我们可以心安理得地将我们的清理代码放在一个地方并知道它会被执行;但一个方法可能从多个地方返回,或者因为异常我们的清理代码被意外跳过.

begin
file = open("/tmp/some_file", "w")
# ... write to the file ...
file.close
end

上面,如果在我们写文件的时候发生异常,文件会保留打开.我们也不希望这样的冗余出现:

begin
file = open("/tmp/some_file", "w")
# ... write to the file ...
file.close
rescue
file.close
fail # raise an exception
end

这是个笨办法,当程序增大时,代码将失去控制,因为我们必须处理每一个 return 和 break,.

为此,我们向"begin...rescue...end"体系中加入了一个关键字 ensure. 无论begin块是否成功,ensure代码域都将执行.

begin
file = open("/tmp/some_file", "w")
# ... write to the file ...
rescue
# ... handle the exceptions ...
ensure
file.close # ... and this always happens.
end

可以只用ensure或只用rescue,但当它们在同一begin...end域中时, rescue 必须放在 ensure前面.
版权声明：RUBY文档中心的所有文章标明[原创]的均为本站作品，版权属RUBY中文化计划，若转载请注明；标明[翻译]的其外文版权归原作者，译文版权属RUBY中文化计划；标明[转贴]的，若原作者感到侵犯了他的著作权，那么请及时跟主持人联系，我们会尽快更正。

版权所有(C) RUBY中文化计划


27、存取器 - RUBY文档中心

存疑，知行 RUBY文档中心
提供RUBY中文教程、文章、翻译作品

发表文章 发表文章　　 浏览文章 浏览文章　　 全文检索 全文检索　　 用户登录 用户登录
27、存取器

转帖：oasis_me　　发表时间：2005-6-15 21:41:52　　点击次数：276

什么是一个存取器?

我们在前面已经讨论过实变量了,但却未过多的讨论.一个对象的实变量属于它的属性,也是它与其它来自同一个类的对象的一般区别.读写它的属性是重要的;这样做需要做一个叫着属性存取器(attribute accessors)的方法.我们将很快看到我们并不是总要明确地写出存取器方法,但现在先让我们了解所有的细节.存取器的两种类型是写(writer)和读(reader).

ruby> class Fruit
| def set_kind(k) # a writer
| @kind = k
| end
| def get_kind # a reader
| @kind
| end
| end
nil
ruby> f1 = Fruit.new
#<Fruit:0xfd7e7c8c>
ruby> f1.set_kind("peach") # use the writer
"peach"
ruby> f1.get_kind # use the reader
"peach"
ruby> f1 # inspect the object
#<Fruit:0xfd7e7c8c @kind="peach">

足够简单;我们可以存取关于我们搜索的水果种类的信息.但我们的方法名还有点儿牢骚.下面的这个更简洁,也更方便.

ruby> class Fruit
| def kind=(k)
| @kind = k
| end
| def kind
| @kind
| end
| end
nil
ruby> f2 = Fruit.new
#<Fruit:0xfd7e7c8c>
ruby> f2.kind = "banana"
"banana"
ruby> f2.kind
"banana"

inspect方法

一个小插曲.你已注意到当我们试着直接观察一个对象,就会出现一些像 #<anObject: 0x83678> 的东西.这只是个缺省的行为,我们可以自由地改变它.我们所要做的只是加一个名为 inspect 的方法.它会换一个更明了的描述对象的字符串,包括部分或全部的实变量.

ruby> class Fruit
| def inspect
| "a fruit of the " + @kind + " variety"
| end
| end
nil
ruby> f2
"a fruit of the banana variety"

一个相关的方法是to_s(转化为字符串),用在打印对象的时候.一般的,你可以认为 inspect 是一个编写或调试程序时用的工具,而 to_s 是一个美化程序输出的方法.eval.rb显示结果时总采用 inspect. 你可以用 p 方法简单的从程序里取得调试信息.

# These two lines are equivalent:
p anObject
print anObject.inspect, "\n"

生成存取器的简单方法

因为许多实变量需要存取方法, Ruby提供了对应于标准方法的缩写.

Shortcut缩写 Effect等同于
attr_reader :v def v; @v; end
attr_writer :v def v=(value); @v=value; end
attr_accessor :v attr_reader :v; attr_writer :v
attr_accessor :v, :w attr_accessor :v; attr_accessor :w

让我们利用它加上"新鲜"信息.首先,我们自动生成了读和写方法,然后我们合并这一新信息到 inspect 中去:

ruby> class Fruit
| attr_accessor :condition
| def inspect
| "a " + @condition + @kind"
| end
| end
nil
ruby> f2.condition = "ripe"
"ripe"
ruby> f2
"a ripe banana"

更有趣的水果

如果没人吃我们成熟的水果,也许我们该让它们烂掉.

ruby> class Fruit
| def time_passes
| @condition = "rotting"
| end
| end
nil
ruby> f2
"a ripe banana"
ruby> f2.time_passes
"rotting"
ruby> f2
"a rotting banana"

但当我们这样做时,却引入了一个小问题.现在,如果我们再创造第三个水果会发生什么?记住:实变量不会在赋值前存在.

ruby> f3 = Fruit.new
ERR: failed to convert nil into String

是 inspect 方法在这里挺有理由地抱怨.我们已让它报告水果的品种和状态,但 f3 还未赋过任何值.如果我们愿意,我们可以重写inspect方法使之用 define? 方法测试实变量并只在它们存在时才报告,但也许那不是很有用;因为每一个水果都有类型和状态.看来我们应该在某种程度上确定其属性.这正是下一节我们要讨论的.
版权声明：RUBY文档中心的所有文章标明[原创]的均为本站作品，版权属RUBY中文化计划，若转载请注明；标明[翻译]的其外文版权归原作者，译文版权属RUBY中文化计划；标明[转贴]的，若原作者感到侵犯了他的著作权，那么请及时跟主持人联系，我们会尽快更正。

版权所有(C) RUBY中文化计划


28、对象的初始化 - RUBY文档中心

存疑，知行 RUBY文档中心
提供RUBY中文教程、文章、翻译作品

发表文章 发表文章　　 浏览文章 浏览文章　　 全文检索 全文检索　　 用户登录 用户登录
28、对象的初始化

转帖：oasis_me　　发表时间：2005-6-15 21:43:47　　点击次数：268

前面一节的 Fruit 类有两个实变量,分别表述水果的类型和状态.直到为这个类写了一个定制的inspect方法,我们方才了解它不会对一个缺乏属性的水果做出合理的解释.幸运的是,Ruby提供了一种允许实变量总是被初始化的方法.

initalize方法

当Ruby创建一个新对象时,它总是会寻找一个名为 initialize 的方法并执行它.因此,我们可以简单通过一个initialize方法向实变量中加入缺省值,这样一来, inspect方法就有东西可说了.

ruby> class Fruit
| def initialize
| @kind = "apple"
| @condition = "ripe"
| end
| end
nil
ruby> f4 = Fruit.new
"a ripe apple"

将缺省的变为需要的

很多时候,缺省值不会有太多的意思.真的是缺省那样的水果吗?也许在创造水果的时候对其类型作要求是个更好的办法.要这样做,我们就必须给initialize 方法加入一个正式的参数.因为一些不会在此提及的原因,你向 new 传递的参数实际上是递给了initialize.

ruby> class Fruit
| def initialize( k )
| @kind = k
| @condition = "ripe"
| end
| end
nil
ruby> f5 = Fruit.new "mango"
"a ripe mango"
ruby> f6 = Fruit.new
ERR: (eval):1:in `initialize': wrong # of arguments(0 for 1)

弹性的初始化

上面我们看到一旦一个参数被关联到一个 initialize 方法上,就无法在避免错误产生的情况下将其省掉.如果希望考虑周全,我们可以在给了参数的情况下使用它,否则使用缺省值.

ruby> class Fruit
| def initialize( k="apple" )
| @kind = k
| @condition = "ripe"
| end
| end
nil
ruby> f5 = Fruit.new "mango"
"a ripe mango"
ruby> f6 = Fruit.new
"a ripe apple"

可以在任何方法内使用缺省参数,而不仅仅是initialize.参数表(argument list)必须以有缺省值的参数结尾.

有时,提供多种初始化对象方法是有益的.虽然已超出本教程的范围,但Ruby提供了对象映象(object reflection)和可变长度的参数表(variable-length argument lists),这些都有效地促进了方法重载.
版权声明：RUBY文档中心的所有文章标明[原创]的均为本站作品，版权属RUBY中文化计划，若转载请注明；标明[翻译]的其外文版权归原作者，译文版权属RUBY中文化计划；标明[转贴]的，若原作者感到侵犯了他的著作权，那么请及时跟主持人联系，我们会尽快更正。

版权所有(C) RUBY中文化计划


29、杂项 - RUBY文档中心

存疑，知行 RUBY文档中心
提供RUBY中文教程、文章、翻译作品

发表文章 发表文章　　 浏览文章 浏览文章　　 全文检索 全文检索　　 用户登录 用户登录
29、杂项

转帖：oasis_me　　发表时间：2005-6-15 21:47:42　　点击次数：314

本章处理一些实际问题.

语句定界符

有些语言需要一定类型的标点,一般会是分号(;)来结束程序的每一语句.Ruby却采用了shell里的sh和csh的方便做法.一行中的多个语句由分号分开,但在行尾分号却并不需要;一个换行被看作一个分号.如果行以反斜杠(\)结束,随后的换行将忽略;这就允许你的单个逻辑行可以跨越数行.

注释

为什么写注释?虽然良好的代码可自成文档,但那种自以为别人能看懂并按你的方式很快去理解的想法是错误的.除此之外,你自己在离开数天后也会是另一个人;一段时间后我们忘了我们还未修补或增强程序中的哪些部分,你会说,我知道我写了这个的,但我究竟写的是些什么?

一些有经验的程序员会相当正确地指出,矛盾的和过期的注释比没有强.当然,有了注释并不意味着代码的可读性;如果你的代码不清晰,它也许是多虫的.当你学习Ruby的时候,你会发现自己需要更多的注释;然后当你可以通过更简单,优雅,可读的代码来表达思想时,它们就会减少.

Ruby遵从一些普遍的书写习惯,用井号(#)表示注释的开始.跟在#号后面直到#号这行结束为止的代码都将被解释器忽略.

同时,为了方便写大块的注释, Ruby解释器省略以"=begin"和"=end"开始的行中间的一切.

#!/usr/bin/env ruby
=begin
**********************************************************************
This is a comment block, something you write for the benefit of
human readers (including yourself). The interpreter ignores it.
There is no need for a '#' at the start of every line.
**********************************************************************
=end

组织你的代码

Ruby读到什么就处理什么.没有编译处理;如果有什么还没读到,就被简单地认为未定义.

# this results in an "undefined method" error:
print successor(3),"\n"
def successor(x)
x + 1
end

这并不是像一开始认为的那样,强迫你以从上至下的方式组织你的代码.只要你确保其在调用前将被定义,当解释器遇到一个方法定义时,它能安全地接受暂未定义的引用.

# Conversion of fahrenheit to celsius, broken
# down into two steps.
def f_to_c(f)
scale(f - 32.0) # This is a forward reference, but it's okay.
end
def scale(x)
x * 5.0 / 9.0
end
printf "%.1f is a comfortable temperature.\n", f_to_c(72.3)

所以,一方面看起来比使用Perl或Java要稍稍不方便一些,但却没有写C那么严格(要求你永远维持所指的部分排序).将最高层的代码放在源文件的最后总是可行的.即使这样也比看见时要好的多.一个明智而无痛苦的好办法是将main定义在文件顶端,再在底端调用它.

#!/usr/bin/env ruby
def main
# Express the top level logic here...
end
# ... put support code here, organized as you see fit ...
main # ... and start execution here.

Ruby也提供了将复杂程序分割为可读,可重用,逻辑相关的大块的工具.我们已看到用 include 来访问模块.你将发现 load 和 require 也很有用.load的作用类似于文件的复制加粘贴(和C的#include处理器指令相似).require更复杂,仅在需要时才加载,而且最多加载一次.load和require还有其它一些区别;在语言手册,FAQ中可找到更多信息.

就是这些了...

这个教程应该足够帮助你开始编写Ruby程序了.随着问题的深入,你可以深入参考手册.FAQ和库参考也是很重要的资源.

祝好运,编程快乐!
版权声明：RUBY文档中心的所有文章标明[原创]的均为本站作品，版权属RUBY中文化计划，若转载请注明；标明[翻译]的其外文版权归原作者，译文版权属RUBY中文化计划；标明[转贴]的，若原作者感到侵犯了他的著作权，那么请及时跟主持人联系，我们会尽快更正。

版权所有(C) RUBY中文化计划


1、什么是RGSS - RUBY文档中心

存疑，知行 RUBY文档中心
提供RUBY中文教程、文章、翻译作品

发表文章 发表文章　　 浏览文章 浏览文章　　 全文检索 全文检索　　 用户登录 用户登录
1、什么是RGSS

原创：夏克　　发表时间：2005-6-2 23:52:29　　点击次数：509

RGSS：

RGSS (Ruby Game Scripting System),中文意思就是：Ruby游戏脚本系统，是一个脚本性质的游戏编程系统，并不是整个游戏都用它来生成，RGSS所能做的只是一些有限的功能，而这些功能，是由厂家也就是EnterBrain所规定的，我们只能在自己力所能及的范围内来挖掘RGSS的潜力。

脚本（Script）:

脚本这个概念应用的范围很广，Windows系统里就有VBScript,JavaScript,WScript等脚本程序，Unix系统也有很多Perl,C等脚本程序，脚本可以解释为是一种系统内用来实现一些特定功能的有着局限性的编程环境。有的脚本可以触及到系统底层，有的却只是完成一些运算或者控制流程的功能，这取决于脚本系统的权限，也可以说取决于脚本系统所提供的库函数功能。

RGSS中的脚本：

在RMXP中，按F11就可以打开[脚本编辑器]，左边的窗口是脚本列表，右边的窗口是脚本内容，左下角可以修改脚本名称。

在RMXP中，游戏开始运行后，总是从最上边的脚本开始运行，依次往下，所以，经常把变量声明、类声明、函数声明、函数定义的脚本放在上面，而MAIN脚本总是放在最后。
版权声明：RUBY文档中心的所有文章标明[原创]的均为本站作品，版权属RUBY中文化计划，若转载请注明；标明[翻译]的其外文版权归原作者，译文版权属RUBY中文化计划；标明[转贴]的，若原作者感到侵犯了他的著作权，那么请及时跟主持人联系，我们会尽快更正。

版权所有(C) RUBY中文化计划


2、开始：最简单的脚本 - RUBY文档中心

存疑，知行 RUBY文档中心
提供RUBY中文教程、文章、翻译作品

发表文章 发表文章　　 浏览文章 浏览文章　　 全文检索 全文检索　　 用户登录 用户登录
2、开始：最简单的脚本

原创：夏克　　发表时间：2005-6-2 23:56:01　　点击次数：360

一个最简单的脚本：

我们建立一个新的游戏工程来从头学习RGSS，按[CTRL+N]新建工程，在[文件夹名]中键入TEST，按确定，建立新游戏TEST。
按F11调出[脚本编辑器]，用鼠标把第一个脚本Game_Temp选中，按DEL键删除它，一直按DEL键删除所有的脚本，只剩下Main脚本，然后右键点Main脚本，选择[插入]来插入一个新的脚本，给新脚本命名为Test，删除Main脚本，点[确定]。这样我们就得到了一个空的脚本系统，试着运行一下游戏，可以看到游戏在短暂的启动后就自动终止了，这表明没有任何脚本可以执行，游戏只好退出。

下面我们在Test脚本中加入一些内容。

按F11打开[脚本编辑器]，因为我们只有Test一个空脚本，所以在Test的内容中输入下面的代码：

p "欢迎使用RGSS来进行编程.."

把这行代码复制并粘贴在Test脚本中，按[确定]，按F12，保存后游戏开始运行，短暂停顿后就会弹出一个对话框，内容是不是："欢迎使用RGSS来进行编程.."！这样我们就做出了第一个最简单的脚本。

需要注意的是：p要小写，这是因为RGSS是区分大小写的，如果你用P，你可以试试看能不能得到正确的结果。

脚本的注释：

用#号来表示注释，#号后面的代码不会被执行。

p "欢迎使用RGSS来进行编程.." #这里是注释，不会被显示

把这行代码复制到Test脚本中，运行脚本，观察结果看到注释是不会被执行的。
版权声明：RUBY文档中心的所有文章标明[原创]的均为本站作品，版权属RUBY中文化计划，若转载请注明；标明[翻译]的其外文版权归原作者，译文版权属RUBY中文化计划；标明[转贴]的，若原作者感到侵犯了他的著作权，那么请及时跟主持人联系，我们会尽快更正。

版权所有(C) RUBY中文化计划


3、数据类型：数字 - RUBY文档中心

存疑，知行 RUBY文档中心
提供RUBY中文教程、文章、翻译作品

发表文章 发表文章　　 浏览文章 浏览文章　　 全文检索 全文检索　　 用户登录 用户登录
3、数据类型：数字

原创：夏克　　发表时间：2005-6-3 0:22:07　　点击次数：250

数字：

数字包括整数和小数，小数在计算机术语中被称为浮点数，相信大家都明白什么是整数和小数了。整数包括正整数和负整数，0可以被视为整数。常见的数字有：
1 整数
100 整数
-10 整数
0 整数
0.11 浮点数
-12.3 浮点数

数字属于常量的一部分。

我们写一个脚本来学习如何显示一个常量的值：

在Test脚本中，清除脚本内容，输入下面的代码：

p 100

运行脚本，看到弹出的对话框显示：100

若输入：

p 100.100

会弹出什么呢，你想想，实际上弹出的是100.1，而不是100.100。

数字的计算：

输入：

p 1+1

运行脚本，看到弹出的对话框显示：2。这就是常量的加法。你可以很聪明地想象出乘法、除法、减法：

输入：

p 3+5
p 3*5
p 3/5
p 3-5

运行脚本后看到，显示的是运算后的结果。注意到p 3/5一句显示的是0，这是因为：

整数和整数的运算，其结果仍旧表现为整数，如果你希望得到浮点数，那么就应该使被除数或者除数至少有一个是小数形式的表示。请看下面的代码：

p 3.0/5
p 3/5.0

显示的结果就都是小数了。

值得一提的是还有两个很有用的算符，求余数和乘方：

求余数%：

输入：

p 14%4

结果显示2。

乘方**：

输入：

p 2**3

结果显示8。

括号的用处：

各种运算符之间总会存在优先顺序，加减乘除的顺序是不变的，对于其它你所不熟悉的或者弄不清除的，只要记住一点就可以了，那就是括号的优先权是最高的，善用括号能够至少确保程序的准确性而先不管是不是很难看。
版权声明：RUBY文档中心的所有文章标明[原创]的均为本站作品，版权属RUBY中文化计划，若转载请注明；标明[翻译]的其外文版权归原作者，译文版权属RUBY中文化计划；标明[转贴]的，若原作者感到侵犯了他的著作权，那么请及时跟主持人联系，我们会尽快更正。

版权所有(C) RUBY中文化计划


4、数据类型：常量与变量 - RUBY文档中心

存疑，知行 RUBY文档中心
提供RUBY中文教程、文章、翻译作品

发表文章 发表文章　　 浏览文章 浏览文章　　 全文检索 全文检索　　 用户登录 用户登录
4、数据类型：常量与变量

原创：夏克　　发表时间：2005-6-3 0:28:51　　点击次数：215

常量：

常量就是我们经常会用的数字、字符串了，比如我们问一张光盘多少钱，回答说5元，那么5就是常量，如果说一张光盘的价格不定，在3-5元的范围内浮动，那么这就是我们学过的未知数，对于这样的未知数我们经常会用一个变量来表示。

变量：

变量就是我们用一个符号来表示一个我们想要表示的概念，比如我们可以用price这个变量来表示一张光盘的价格。变量与常量的区别就在变量是可以变动的，也就是说我们用变量来定义一个概念后，接下来就会来操作这个变量使它变化。而我们不能让一个常量变化，比如我们不能让5=4，但我们可以让price=4。

变量的命名：

变量的取名有以下的限制：

1、必须以英文字母（大小写均可）、汉字、下划线开头。

2、第二个字符开始可以使用数字、英文字母、汉字、下划线。

3、不能使用保留字作变量的名字。

保留字是保留给系统用的，也就是说系统已经占用了，以下是系统的保留字：

alias def false nil return unless

and do for not self until

begin else if or super when

break elsif in redo then while

case end module rescue true yield

class ensure next retry undef

不要使用上面列出的单词做你的变量的名字。

变量的赋值：

变量无需事先声明，可以拿来就用，例如

a=100
p a

运行脚本，看到弹出的对话框显示：100

但必须先赋值才可以使用变量，否则，试试下面的代码：

p b

会弹出什么呢，如果不赋值给变量，就相当于这个变量不存在，而一旦赋值给它，就表明这个变量存在了。

变量的运算：

下面的代码你应该可以猜出是什么结果的：

战斗前的体力=234

战斗后的体力=200

p 战斗前的体力

p 战斗后的体力

继续：

p 战斗前的体力-战斗后的体力

看到了吗，这里我进行了运算，变量的运算。

就像对于常量一样，加减乘除和括号同样适用于变量的运算：

x=1

y=2

z=3

p x+(y*z)/(y+z)-y+z

但下面的运算是常量所没有的：

自运算：

x+=5

y*=2

z/=3

p x

p y

p z

+=,-=,*=,/=这四个运算符是自运算符，x+=1相当于x=x+1,其它同理。

全局变量：

局部变量和全局变量的区别在于局部变量只能被所在的脚本访问，而全局变量能被所有的脚本访问。

在变量名前加$符号就可以声明全局变量了。
版权声明：RUBY文档中心的所有文章标明[原创]的均为本站作品，版权属RUBY中文化计划，若转载请注明；标明[翻译]的其外文版权归原作者，译文版权属RUBY中文化计划；标明[转贴]的，若原作者感到侵犯了他的著作权，那么请及时跟主持人联系，我们会尽快更正。

版权所有(C) RUBY中文化计划


5、数据类型：字符串 - RUBY文档中心

存疑，知行 RUBY文档中心
提供RUBY中文教程、文章、翻译作品

发表文章 发表文章　　 浏览文章 浏览文章　　 全文检索 全文检索　　 用户登录 用户登录
5、数据类型：字符串

原创：夏克　　发表时间：2005-6-3 0:31:54　　点击次数：215

显示字符串：
先看看下面这几行语句在RGSS中的效果：

p "这将会显示双引号"

p '这也会显示双引号'

print "这不会显示双引号"

print '这也不会显示双引号'

把上面的四行语句复制下来，然后在我们刚刚建立好的Test脚本中粘贴，粘贴前最好把Test脚本的内容清除掉，我们只需要测试我们现在的代码。好，运行游戏，看看效果吧。

首先，看得出来，用来输出显示的方法又多了一种：print，不同的是，p可以显示很多种数据类型，对不同的数据类型，它会按人们容易理解的格式来显示，比如说这里的字符串，它都会加上双引号来告诉人们：这次显示的是字符串，而print直接显示字符串本身。

在上面的代码中，分别输出下面的四行字符串：

"这将会显示双引号"

"这也会显示双引号"

这不会显示双引号

这也不会显示双引号

字符串常量：

字符串常量的表示有两种方法。

1、双引号表示的字符串：

这种表示方法使得字符串可以支持一些特殊格式，这将是我们用的最多的表示方法，下面会有更详细的介绍。

2、单引号表示的字符串：

直白的说，单引号所包括的字符串会被原样显示出来，也就是说，即使单引号中包含特殊格式，也不会显示这种特殊格式。

字符串变量：

和数字变量一样，看看例子吧：

1、赋值：

a="欢迎使用RGSS来编程"

print a

结果输出：欢迎使用RGSS来编程

2、连接：

a="中华人民"

b="共和国"

print a+b

结果输出：中华人民共和国

3、乘法：

a="连续两遍"

print a*2

结果输出：连续两遍连续两遍

4、换行符：

\n表示换行，但是只能用在双引号字符串内，若是在单引号字符串内便不起作用了，看看下面两个例子：

a='中华人民\n共和国'

b="中华人民\n共和国"

print a

print b

结果输出：

中华人民\n共和国

中华人民
共和国

5、常量中包含变量：

a="人民"

print "中华#{a}共和国"

结果输出：中华人民共和国

记住：这个特殊格式和\n一样只能在双引号形式的字符串中使用。

再看一个：

a="C:\\Program Files\\RPG Maker XP\\System\\Data\\Skills.rxdata"

print "系统安装后的初始脚本文件是：#{a}"

你可以试试看下面的例子：

a="人民"

print '中华#{a}共和国'

结果#输出：中华#{a}共和国
版权声明：RUBY文档中心的所有文章标明[原创]的均为本站作品，版权属RUBY中文化计划，若转载请注明；标明[翻译]的其外文版权归原作者，译文版权属RUBY中文化计划；标明[转贴]的，若原作者感到侵犯了他的著作权，那么请及时跟主持人联系，我们会尽快更正。

版权所有(C) RUBY中文化计划


6、控制语句：条件分歧语句 - RUBY文档中心

存疑，知行 RUBY文档中心
提供RUBY中文教程、文章、翻译作品

发表文章 发表文章　　 浏览文章 浏览文章　　 全文检索 全文检索　　 用户登录 用户登录
6、控制语句：条件分歧语句

原创：夏克　　发表时间：2005-6-3 1:33:03　　点击次数：218

比较运算符：
有6个比较运算符,分别是

== 相等

!= 不相等

< 小

> 大

<= 小或相等

>= 大或相等

比较运算符，顾名思义，就是用来比较的，比较的对象可以是任意的，比较的结果是True或者False。

举例：

p(" 早安 "==" 早安 ") # => true
p(" 早安 "==" 晚安 ") # => false

p (3 + 1 == 3 + 5) # => false

p (3 + 1 == 2 + 2) # => true

观察一下结果就会明白。

逻辑运算符：

逻辑运算符也有6个，分别是：

与：and , &&

或：or , ||

非：not , !

举例：

p (100 > 77 and 1 + 1 == 2) # => true
p (100 > 77 && 1 + 1 == 2) # => true

if..elseif..else..end 语句：

结构：

if 条件1
语句1
elseif 条件2
语句2
.
.
else
语句
end

举例：

x=123

y=23

z=67

a=(x*y*z+x/y+z/y)*(y-z)+x*z

if a>0

print "大于0"

elseif a=0

print "等于0"

else

print "小于0"

end

最常用的还是if...end语句：

金钱数=10

if 金钱数<100

print "对不起，你的钱不够了.."

end

unless..end 语句：

这是if..end语句的变种，正好跟if..end相反,就是除非的意思：

unless 条件

语句

end

举例：

金钱数=10

unless 金钱数>=100

print "对不起，你的钱不够了.."

end

除非你的金钱数大于等于100，否则：“对不起，你的钱不够了..”

case..when..end 语句：

如果对于把条件限制在某个范围或者某些特定的值的情况，使用case..end语句更方便：

case 变量

when 特定的值或者范围

when 特定的值或者范围

.

.

end

举例：

主角状态="昏睡"

case 主角状态

when "昏迷"

print "你昏迷了.."

when "中毒"

print "你中毒了.."

when "昏睡"

print "你昏睡了.."

end

条件赋值语句：

条件赋值语句给我们提供了一个非常方便的if..else..end的简化版。

(条件1 ? 语句1 : 语句2)

相当于：

if 条件1

语句1

else

语句2

end

举例：

战斗状态=1

print (战斗状态>0 ? "胜利" : "失败")

版权声明：RUBY文档中心的所有文章标明[原创]的均为本站作品，版权属RUBY中文化计划，若转载请注明；标明[翻译]的其外文版权归原作者，译文版权属RUBY中文化计划；标明[转贴]的，若原作者感到侵犯了他的著作权，那么请及时跟主持人联系，我们会尽快更正。

版权所有(C) RUBY中文化计划


7、控制语句：循环 - RUBY文档中心

存疑，知行 RUBY文档中心
提供RUBY中文教程、文章、翻译作品

发表文章 发表文章　　 浏览文章 浏览文章　　 全文检索 全文检索　　 用户登录 用户登录
7、控制语句：循环

原创：夏克　　发表时间：2005-6-3 1:33:55　　点击次数：244

while..end 循环：
举例：

a = 0

i = 1

while i <= 5

a += i

i += 1

end

p a

这很简单，很容易明白的。

for..in..end 循环：
类似于c语言中的for,但不同，in后面给出变量的变化范围。

变化范围：

类似于1..5表示一个变化范围，其所含的值为大于等于1小于等于5。

举例：

a = 0

for i in 1..5

a += i

end

p a

这也很简单，很容易明白的。

loop do..end 循环：

举例：

i = 0

loop do

i += 1

p i

end

上面的代码会一直循环下去，也就是说是个死循环。只有使用break才可以从中跳出。

break 语句：
上面的例子如果改成下面的样子，就不再是死循环了：

i = 0

loop do

i += 1

if i == 5

break

end

p i

end

break也可以从while、for循环中跳出。

next 语句：

跳过本次循环，进入下次循环。

举例：

for i in 1..5

if i == 3

next

end

p i

end

结果显示四次，就只有1 ， 2 ， 4 ， 5被显示出来了。
版权声明：RUBY文档中心的所有文章标明[原创]的均为本站作品，版权属RUBY中文化计划，若转载请注明；标明[翻译]的其外文版权归原作者，译文版权属RUBY中文化计划；标明[转贴]的，若原作者感到侵犯了他的著作权，那么请及时跟主持人联系，我们会尽快更正。

版权所有(C) RUBY中文化计划


8、函数 - RUBY文档中心

存疑，知行 RUBY文档中心
提供RUBY中文教程、文章、翻译作品

发表文章 发表文章　　 浏览文章 浏览文章　　 全文检索 全文检索　　 用户登录 用户登录
8、函数

原创：夏克　　发表时间：2005-6-3 1:55:05　　点击次数：213

函数的概念：

我们把事先编好的，能够解决或者说处理某种情况的功能的集合叫做函数。不必在意概念，用得多了自然就明白。其实我们一直在使用的p,print就是函数的一种，下面介绍一个很有用的函数，随机函数rand()：

rand(x)返回0-(xx-1)范围内的随机数，例如：

p rand(100)

返回的数字在0-99范围内。

我们也可以设计自己的函数以便增加我们需要的功能，更多的时候，我们大多数时间是在跟函数打交道。

函数的声明：

函数的名字基本上和变量的名字有着相同的限制，例外的情况是，函数可以在名字的最后添加?或!符号，这种符号有着特殊的用处，以后会讲到。

函数的声明要用def..end语句，形如：
def 函数名字
语句
end

我们用rand函数来设计一个自己的函数bet()：

def bet
if rand(6)>3
return "大"
else
return "小"
end
end
print bet

这里的return表示函数返回的值，如果省略return也可以，但最好带上，能够使程序可读性更好。

我们给bet函数增加参数：

def bet(x)
if rand(x)>3
return "大"
else
return "小"
end
end
print bet(7)

还可以为参数设置默认值:

def bet(x=7)
if rand(x)>3
return "大"
else
return "小"
end
end
print bet #这和print bet(7)一样

函数可以有很多参数：

def bet(x,y,z)
if rand(x)>3 and rand(y)>3 and rand(z)>3
return "大"
else
return "小"
end
end
print bet(7,6,10)

重定义函数：

如果定义了两次相同的函数，则只有后面定义的函数有效，而先前的定义就无效了。

def hello
return" 您好 "
end

def hello
return" 晚安 "
end

p hello #=>" 晚安 "

版权声明：RUBY文档中心的所有文章标明[原创]的均为本站作品，版权属RUBY中文化计划，若转载请注明；标明[翻译]的其外文版权归原作者，译文版权属RUBY中文化计划；标明[转贴]的，若原作者感到侵犯了他的著作权，那么请及时跟主持人联系，我们会尽快更正。

版权所有(C) RUBY中文化计划


9、对象与类 - RUBY文档中心

存疑，知行 RUBY文档中心
提供RUBY中文教程、文章、翻译作品

发表文章 发表文章　　 浏览文章 浏览文章　　 全文检索 全文检索　　 用户登录 用户登录
9、对象与类

原创：夏克　　发表时间：2005-6-3 2:04:24　　点击次数：215

对象：

ruby是面向对象的语言，至于对象，可以用来表示任何东西，对于我们的脚本系统，任何东西指的是系统提供的任何可以称之为对象的东西，这样的概念很混乱，但总之，记得对象可以表示任何东西。

像整数是Integer对象，浮点数是Float对象，文字是String对象，1..10是Range对象，所有的对象都是属于类的，或者说所有的对象都是由类所产生的。

类：

类和对象的关系，就像是哲学上著名的意识与物质的关系一样。类不是真正存在的，而对象是。类只不过是对某种东西的描述或者规划，而实现了这种描述的就是对象，也叫做类的实例。所以，有时也把声明对象叫做类的实例化。

RGSS参考中列出了所有的类的详细文档。

RGSS参考的翻译将放在《RGSS中级教程》。（夏克注）

类的属性和方法：

属性其实就是类中的一些公共变量，可以被其它类访问到。

方法其实就是类中的一些公共函数，可以被其它类访问到。

例如：

a = "Ruby"
p a.size # => 4

这返回a的长度，也是字符数。

再一個试看看!把文字都转换为大写。

a = "Ruby"
p a.upcase # => "RUBY"

如果是下面的代码：

a = 3
p a.upcase # ERROR!!

发生了错误，因为a是integer整数对象，或者说是integer类的，这个类没有upcase方法可以执行。

类的继承：

在RGSS参考中看一下Fixnum类，它处在Integer类的子类中，而Integer处在Numeric子类中，而Numeric处在Object类中。我们把上一级类叫做父类，父类的下一级叫做子类。子类是由父类衍生出来的，所以子类继承了父类的某些属性和方法。

例如，Fixnum就继承了Integer的chr方法。我们观察RGSS参考中的Fixnum一档，它的属性和方法少得可怜，但Integer所有的属性和方法，Fixnum都继承了过来。
版权声明：RUBY文档中心的所有文章标明[原创]的均为本站作品，版权属RUBY中文化计划，若转载请注明；标明[翻译]的其外文版权归原作者，译文版权属RUBY中文化计划；标明[转贴]的，若原作者感到侵犯了他的著作权，那么请及时跟主持人联系，我们会尽快更正。

版权所有(C) RUBY中文化计划


10、显示图片 - RUBY文档中心

存疑，知行 RUBY文档中心
提供RUBY中文教程、文章、翻译作品

发表文章 发表文章　　 浏览文章 浏览文章　　 全文检索 全文检索　　 用户登录 用户登录
10、显示图片

原创：夏克　　发表时间：2005-6-3 2:05:14　　点击次数：256

Graphics：

Graphics类是RGSS中最重要的类了，游戏中所有涉及到图片的处理都使用这个类的功能。

在前面的脚本中，我们运行完脚本游戏就自动结束了，这是因为我们没有启动更新游戏画面的原因，下面我们就来启动：

loop do
Graphics.update
end

这个死循环不停地更新游戏画面，update是Graphics的一个方法，这种对象和方法的呼叫形式你应该可以很熟的，如果不熟的话，多看看《Ruby初级教程》吧。

这个循环就是RGSS的最基本的样子，而大规模的RPG游戏的复杂的脚本也不过是在此基础上添加各种各样的处理而已。

但我们只看到乌黑一片，什么都没有。下面来加入图片：

devil = Sprite.new
devil.bitmap = Bitmap.new("Graphics/Battlers/075-Devil01")

loop do
Graphics.update
end

如果你看到小恶魔的画像就表示成功了。

Sprite表示人物的图像，devil=Sprite.new就是说生成一个人物的实例给变量devil,然后生成一个Bitmap也就是图像的实例给devil的图像属性，这样devil就被显示出来了。

下面给小恶魔换一下位置：

devil = Sprite.new
devil.bitmap = Bitmap.new("Graphics/Battlers/075-Devil01")
devil.x = 320
devil.y = 240

loop do
Graphics.update
end

然后我们给小恶魔放到正好正中央：

devil = Sprite.new
devil.bitmap = Bitmap.new("Graphics/Battlers/075-Devil01")
devil.ox = devil.bitmap.width / 2
devil.oy = devil.bitmap.height / 2
devil.x = 320
devil.y = 240

loop do
Graphics.update
end

ox,oy表示原点坐标，我们把devil的原点坐标放在devil的中心，那么devil就正好在正中央了。
版权声明：RUBY文档中心的所有文章标明[原创]的均为本站作品，版权属RUBY中文化计划，若转载请注明；标明[翻译]的其外文版权归原作者，译文版权属RUBY中文化计划；标明[转贴]的，若原作者感到侵犯了他的著作权，那么请及时跟主持人联系，我们会尽快更正。

版权所有(C) RUBY中文化计划


11、数组 - RUBY文档中心

存疑，知行 RUBY文档中心
提供RUBY中文教程、文章、翻译作品

发表文章 发表文章　　 浏览文章 浏览文章　　 全文检索 全文检索　　 用户登录 用户登录
11、数组

原创：夏克　　发表时间：2005-6-3 2:06:57　　点击次数：216

数组的生成：

举例：

a=[" Allex"," 国家 "," 颂歌 "]

上面的代码生成一个字符串数组，有3个单元。

通过下标来访问数组单元

举例：

a=[" Allex"," 国家 "," 颂歌 "]
p a[0] #=>" Allex"
p a[1] #=>" 国家 "
p a[2] #=>" 颂歌 "

当给数组变量赋值时下标按从做到右，从0开始的次序来分配。

数组可以容纳任意类型的对象，即使是同一个数组也可以容纳不同种类的对象。

举例：

a=[2000," Allex", true,1..50]
p a #=>[2000," Allex", true,1..50]

数组的单元就像变量一样，可以像操作变量一样操作数组单元。

举例：

a = [10, 20, 30]
a[0] = 100
p a # => [100, 20, 30]

若是下标超出了原来的边界，则自动添加新的数组单元。

举例：

a = [10, 20, 30]
a[3] = 40
p a # => [10, 20, 30, 40]

Ruby语言的灵活性由此可见一斑。

如果越过了原来的边界，会怎么样：

举例：

a = [10, 20, 30]
a[5] = 60
p a # => [10, 20, 30, nil, nil, 60]

nil是与True/False一类的常量，表示空。

数组的操作：

数组是 Array 类的实例，所以查看这个类的定义，就可以看出数组如何操作了。

例如想知道数组的元素数量的情况就使用size属性。String 级的 size 属性返回字符串长度，然而 Array 类的 size 属性返回数组的元素数量。数组为空时size=0。

举例：

a=[" Allex"," 国家 "," 颂歌 "]
p a.size # => 3

如果想知道是否包含就是用include?方法：

举例：

a=[" Allex"," 飞行学院 "," 颂歌 "]
p a.include?(" 飞行学院 ") # => true
p a.include?(" 人间四月天") # => false

也可以使用for来循环访问数组单元：

举例：

a=[" Allex"," 中华人民共和国 "," 颂歌 "]
for s in a
p s
end

这里用变量 s 来循环访问数组成员。
版权声明：RUBY文档中心的所有文章标明[原创]的均为本站作品，版权属RUBY中文化计划，若转载请注明；标明[翻译]的其外文版权归原作者，译文版权属RUBY中文化计划；标明[转贴]的，若原作者感到侵犯了他的著作权，那么请及时跟主持人联系，我们会尽快更正。

版权所有(C) RUBY中文化计划


12、哈希表（关联数组） - RUBY文档中心

存疑，知行 RUBY文档中心
提供RUBY中文教程、文章、翻译作品

发表文章 发表文章　　 浏览文章 浏览文章　　 全文检索 全文检索　　 用户登录 用户登录
12、哈希表（关联数组）

原创：夏克　　发表时间：2005-6-3 2:09:04　　点击次数：218

哈希表的生成：

一个关联数组不通过下标来访问,而是通过主键(key)访问.这样的数组有时被叫作哈希(hash).将一对对的元素用逗号分隔开,并用大括号({})括起来,这样就组成了一个哈希表.你用一个关键字在哈希表里进行搜索,就像你在数组里用索引来提取数据一样.

例如：

a={" Allex"=>2000," 帆布背包 "=>2003}

在这个例子中，" Allex" 和 " 帆布背包 " 是主键(key)，2000 和 2003 是实际的数值。这样，主键和数值之间用“ =>” 号连接，形成关系。

如果要取出实际的数值，如下所示：

a={" Allex"=>2000," 帆布背包 "=>2003}
p a[" Allex"] # => 2000
p a[" 帆布背包 "] # => 2003
p a[" 中国"] # => nil

这里以字符串为key ,但对其他的数据类型也一样。

对哈希表元素的追加，如下示例：

a = {} #建一个空表
a = {} #建一个空表
a[" Allex"]=2000 #赋值
p a #=>{" Allex"=>2000}

哈希表属于Hash类的实例，可以调用这个类的相关方法。

例如删除一个元素：

a={" Allex"=>2000," 帆布背包 "=>2003}
a.delete(" 帆布背包 ")
p a #=>{" Allex"=>2000}

查看是否包含某键值：

a={" Allex"=>2000," 帆布背包 "=>2003}
p a.include?(" Allex") # => true
p a.include?(" 中中中 ") # => false

取得 keys 的方法：

a = {3=>6, 4=>10, 5=>15}
p a.keys # => [5, 3, 4]

把keys放在for循环中：

a = {3=>6, 4=>10, 5=>15}
for k in a.keys
p k
end

如果使用 values，可以取得数值方面的一览表。

a = {3=>6, 4=>10, 5=>15}

for v in a.values
p v
end
版权声明：RUBY文档中心的所有文章标明[原创]的均为本站作品，版权属RUBY中文化计划，若转载请注明；标明[翻译]的其外文版权归原作者，译文版权属RUBY中文化计划；标明[转贴]的，若原作者感到侵犯了他的著作权，那么请及时跟主持人联系，我们会尽快更正。

版权所有(C) RUBY中文化计划


13、类 - RUBY文档中心

存疑，知行 RUBY文档中心
提供RUBY中文教程、文章、翻译作品

发表文章 发表文章　　 浏览文章 浏览文章　　 全文检索 全文检索　　 用户登录 用户登录
13、类

原创：夏克　　发表时间：2005-6-3 2:11:01　　点击次数：207

类的定义：

前面所用到的String、Array、Sprite等都是类，我们已经使用过系统为它们编好的一些方法了。在 puriset 的脚本中除去最后的 Main脚本全部是类定义。

在例如定义 Person 的类时是这样的：

class Person
end

上面定义了一个什么都没有的类，但这个类却可以使用，如：

alex = Person.new

也就是说，我们可以实例化这个类。

想要继承也很简单，例如：

class Person < Monkey
end

方法的定义：

和普通函数的情况相同，如果在类的定义当中上写 def..end 的内容，那么这个函数就自动成为那个类的方法了：

class Person
def hello
print" 您好 "
end
end
alex = Person.new
alex.hello #=> 您好

属性：

以@为开头的变量名，这个变量自动地作为属性而使用。局部变量只有在 def..end 等结构内有效，然而如果能使用属性的话，只要对象的实例存在，则属性的值总是被保存。

class Person
def name
return @name
end
def name=(name)
@name = name
end
def hello
print" 我是 "+@ name+""
end
end

alex = Person.new
alex.name=" Allex"
alex.hello #=> 我是 Allex

在这个例子中， @ 准备 name 属性，而 name() ， name=()方法分别定义了如何对@name取值和如何设置@name的值。

为属性赋初值：

class Person
def initialize
@ name=" 无名先生 "
end
end

这样在实例化对象的时候，会默认先调用initialize方法来为@name赋初值

如果在 initialize 方法中加入参数， new 会取那个参数。

class Person
def initialize(name)
@name = name
end
end

alex= Person.new(" Allex")

在这个例子中， initialize 方法把参数 name 的取值赋给属性 @name作为初值。

方法的重载：

在子类里,我们可以通过重载父类方法来改变实体的行为，如果我们只是想增强父类的 identify 方法而不是完全地替代它,就可以用 super。

class Hahaha
def laugh(x)
return" 是 "* x
end
end

class Wahaha < Hahaha
def laugh(x)
return" 哇 "+ super
end
end

man = Wahaha.new
p man.laugh(4) #=>" 哇是是是是 "

super总是返回父类返回的值。
版权声明：RUBY文档中心的所有文章标明[原创]的均为本站作品，版权属RUBY中文化计划，若转载请注明；标明[翻译]的其外文版权归原作者，译文版权属RUBY中文化计划；标明[转贴]的，若原作者感到侵犯了他的著作权，那么请及时跟主持人联系，我们会尽快更正。

版权所有(C) RUBY中文化计划


14、数据库 - RUBY文档中心

存疑，知行 RUBY文档中心
提供RUBY中文教程、文章、翻译作品

发表文章 发表文章　　 浏览文章 浏览文章　　 全文检索 全文检索　　 用户登录 用户登录
14、数据库

原创：夏克　　发表时间：2005-6-3 2:19:10　　点击次数：254

数据库的内容和使用：

本篇讲述如何利用脚本来使用RPGXP的数据库。

新建一个游戏工程，按F11打开[脚本编辑器]，在左边的目录，寻找 Scene_Title 名字的脚本，它的内容有如下的部分：

# 载入数据库
$data_actors = load_data("Data/Actors.rxdata")
$data_classes = load_data("Data/Classes.rxdata")
$data_skills = load_data("Data/Skills.rxdata")
$data_items = load_data("Data/Items.rxdata")
$data_weapons = load_data("Data/Weapons.rxdata")
$data_armors = load_data("Data/Armors.rxdata")
$data_enemies = load_data("Data/Enemies.rxdata")
$data_troops = load_data("Data/Troops.rxdata")
$data_states = load_data("Data/States.rxdata")
$data_animations = load_data("Data/Animations.rxdata")
$data_tilesets = load_data("Data/Tilesets.rxdata")
$data_common_events = load_data("Data/CommonEvents.rxdata")
$data_system = load_data("Data/System.rxdata")

这里的信息表明系统是通过读取哪些文件来设置数据库的内容的。$开头的变量是全局变量，这些变量为其它脚本提供了数据库方面可以参照的信息。

所谓 load_data 就是 RGSS 的系统函数之一，是从被指定的文件中读取数据的函数。例如第一行，从 Data 文件夹内的 Actors.rxdata 文件中读取数据赋值给全局变量 $data_actors。

数据库的内容：各对象如下所列

变量 名称 所属类名

$ data_actors 　　角色 RPG:: Actor 的数组
$ data_classes 　　类 RPG:: Class 的数组
$ data_skills 　　技能 RPG:: Skill 的数组
$ data_items 　　 物品 RPG:: Item 的数组
$ data_weapons 　　 武器 RPG:: Weapon 的数组
$ data_armors 　　 护具 RPG:: Armor 的数组
$ data_enemies 　　 敌人 RPG:: Enemy 的数组
$ data_troops 　　军队 RPG:: Troop 的数组
$ data_states 　　状态 RPG:: State 的数组
$ data_animations 　　动画 RPG:: Animation 的数组
$ data_tilesets 　　单元 RPG:: Tileset 的数组
$ data_common_events　　公共事件 RPG:: CommonEvent 的数组
$ data_system 　　系统 RPG:: System

从角色到公共事件的12种数据，是与数据库号码相对应的数组，但是在RPGXP因为数组下标是从1开始，所以为了使数组下标与数据库ID一致，数组的第一个单元(第0号)的值为 nil 。

对数据库的使用：

例如：下面的代码表示角色1号的名字。

$data_actors[1].name

下面的代码表示敌人2号的 MaxHP 。

$data_enemies[2].maxhp

下面的代码表示向物品3号指定的图标文件名。

$data_items[3].icon_name

下面的代码表示开关4号的名字。

$data_system.switches[4]
版权声明：RUBY文档中心的所有文章标明[原创]的均为本站作品，版权属RUBY中文化计划，若转载请注明；标明[翻译]的其外文版权归原作者，译文版权属RUBY中文化计划；标明[转贴]的，若原作者感到侵犯了他的著作权，那么请及时跟主持人联系，我们会尽快更正。

版权所有(C) RUBY中文化计划


15、游戏对象 - RUBY文档中心

存疑，知行 RUBY文档中心
提供RUBY中文教程、文章、翻译作品

发表文章 发表文章　　 浏览文章 浏览文章　　 全文检索 全文检索　　 用户登录 用户登录
15、游戏对象

原创：夏克　　发表时间：2005-6-3 2:20:13　　点击次数：255

游戏对象的内容：

数据库原则上在游戏过程中是不会被写入的，而针对地图上人物的走动等等情况，发生变化的是游戏对象。

在Scene_Title脚本中，会生成各种游戏对象，比如command_new_game的方法就是其中的一个，这个方法是在选择了标题画面中的[新的游戏]的时候被运行的。

# 生成各种游戏对象
$game_temp = Game_Temp.new
$game_system = Game_System.new
$game_switches = Game_Switches.new
$game_variables = Game_Variables.new
$game_self_switches = Game_SelfSwitches.new
$game_screen = Game_Screen.new
$game_actors = Game_Actors.new
$game_party = Game_Party.new
$game_troop = Game_Troop.new
$game_map = Game_Map.new
$game_player = Game_Player.new

和数据库中的$data_xxxx变量相同，这里的$game_xxxx也是全局变量。像Game_xxxx这样的名字应该认识吧，如果不认识，把脚本目录拉到最上面，看到了吗，这里的游戏对象正是上面的那些游戏脚本中定义的类的实例。

游戏对象的内容如下，和数据库的情况不同，这些对象所属的类不是RGSS系统预设，而是在脚本中定义的：

变量 名称 类
$ game_temp 临时数据 Game_Temp
$ game_system 系统数据 Game_System
$ game_switches 开关 Game_Switches
$ game_variables 变量 Game_Variables
$ game_self_switches 自我开关 Game_SelfSwitches
$ game_screen 画面效果 Game_Screen
$ game_actors 角色 Game_Actors
$ game_party 派对 Game_Party
$ game_troop 军队 Game_Troop
$ game_map 地图 Game_Map
$ game_player 玩家 Game_Player

这些类原则上只提供数据构造，而不具备处理画面或者接受键盘输入的功能，不过Game_Player类可以处理方向键和回车键。

其实上面的这些对象内部进一步包含着另外的类的对象：

Game_Picture 在 Game_Screen 内部使用
Game_Actor 在 Game_Actors 内部使用
Game_Enemy 在 Game_Troop 内部使用
Game_CommonEvent 在 Game_Map 内部使用
Game_Event 在 Game_Map 内部使用

还有两个重要的类要介绍，它们的分量很多，多数脚本都继承它们

Game_Battler 作为 Game_Actor类和Game_Enemy类的超级类
Game_Character作为Game_Player类和Game_Event类的超级类

所谓超级类就是父类，因为在Actor和Enemy,Player和Event当中都有各自相同的性质，所以要给它们一个共同的父类。

Game_BattleAction类在Game_Battler内部使用

这个类担当战斗中的行动(动作)的功能处理，被Battler的内部调用。
版权声明：RUBY文档中心的所有文章标明[原创]的均为本站作品，版权属RUBY中文化计划，若转载请注明；标明[翻译]的其外文版权归原作者，译文版权属RUBY中文化计划；标明[转贴]的，若原作者感到侵犯了他的著作权，那么请及时跟主持人联系，我们会尽快更正。

版权所有(C) RUBY中文化计划


16、精灵的管理 - RUBY文档中心

存疑，知行 RUBY文档中心
提供RUBY中文教程、文章、翻译作品

发表文章 发表文章　　 浏览文章 浏览文章　　 全文检索 全文检索　　 用户登录 用户登录
16、精灵的管理

原创：夏克　　发表时间：2005-6-3 2:26:07　　点击次数：226

在脚本目录里，看到的几个Sprite_Xxxx名字的脚本也是类。

类 内容 父类
Sprite_Character 人物 RPG:: Sprite
Sprite_Battler 战斗 RPG:: Sprite
Sprite_Picture 图片 Sprite
Sprite_Timer 秒表 Sprite

各类的实例监视与游戏对象相对应的数据变化，一旦那个情况变化了，就自动向系统反映。例如 Sprite_Character监视着Game_Character被继承了的类的实例。人物在地图上稍微譬如说如果走动一下的话，立即就会被它所侦查到。

还有两个类：
类 内容
Spriteset_Map 地图
Spriteset_Battle 战斗

这些类，正在独立地管理着其它画面的大部分或全部的画像 ( 窗口和光标除外 ) 。以前说的地图本身和天气效果等，会向游戏的情况同时被更新。

在这些类，景观入口和Z座标有了重要的作用。这些牵涉到各画像的优先顺序。对Viewport类的解说也同时参照。

Spriteset_Map 地图画面的 成为如其次那样的构成。 ( 数字 Z 座标 )

@viewport3 (5000) - 画面的闪光
@timer_sprite (500) - 秒表
@viewport2 (200) @picture_sprites (1..50) piccha
@ viewport1(0)@ fog(3000) fog
@ weather(1000) 天气
@character_sprites (0..800) 人物
@tilemap (0..800) 地图
@ panorama(-1000) 全景
Spriteset_Battle Battle 画面的 spu 右方组合成为如其次那样的构成。 ( 数字 Z 座标 )

@viewport4 (5000) - 画面的闪光
@timer_sprite (500) - 秒表
@viewport3 (200) @picture_sprites (51..100) piccha
@viewport2 (101) @actor_sprites (1..4) batrai( acter)
@ viewport1(0)@ weather(1000) 天气
@enemy_sprites (1..320) batrai( enemii)
@ battleback_sprite(0) Battle 背景

版权声明：RUBY文档中心的所有文章标明[原创]的均为本站作品，版权属RUBY中文化计划，若转载请注明；标明[翻译]的其外文版权归原作者，译文版权属RUBY中文化计划；标明[转贴]的，若原作者感到侵犯了他的著作权，那么请及时跟主持人联系，我们会尽快更正。

版权所有(C) RUBY中文化计划


17、窗口的管理 - RUBY文档中心

存疑，知行 RUBY文档中心
提供RUBY中文教程、文章、翻译作品

发表文章 发表文章　　 浏览文章 浏览文章　　 全文检索 全文检索　　 用户登录 用户登录
17、窗口的管理

原创：夏克　　发表时间：2005-6-3 2:28:20　　点击次数：258

窗口的内容：
在 RPG 中必须要使用很多的窗口。这些窗口如何管理呢，下面我们就来讲讲这个问题。

形如Window_Xxxx 的名字的脚本很多吧，但有两个尤其重要，那就是最上面的两个。

Window_Base，基本窗口，其父类是Window

Window_Selectable，项目选择窗口，其父类是Window_Base

在 RGSS 中，Window类是预先编好的，它负责确定窗口边界和背景的绘画，是一个非常底层的类。

Window_Base 类继承这个 Window 类，在 RPG 的窗口上追加了必要的基本功能。这个类的重要的功能就是自动读取图像文件来为现在的窗口作皮肤，而且，各种文字颜色也在这个类中定义。

Window_Selectable类继承了Window_Base 类，对探测键盘按键或者使光标移动追加了处理，例如物品的选择等。

一口气将剩余窗口类的介绍写出来，这些类全部是从 Window_Base 或 Window_Selectable 类继承来的。

类 内容 父类
Window_Command 指令窗口 ( 泛用 ) Window_Selectable
Window_Help 帮忙窗口 Window_Base
Window_Gold 所带物品窗口 Window_Base
Window_PlayTime 菜单画面用途PLAY 时间窗口 Window_Base
Window_Steps 菜单画面用途数量窗口 Window_Base
Window_MenuStatus 菜单画面用途状态窗口 Window_Selectable
Window_Item 物品选择窗口 Window_Selectable
Window_Skill 技能选择窗口 Window_Selectable
Window_SkillStatus 技能状态窗口 Window_Base
Window_Target 对象选择窗口 Window_Selectable
Window_EquipLeft 装备画面用途左的窗口 Window_Base
Window_EquipRight
装备画面用途右边的窗口 Window_Selectable
Window_EquipItem
装备画面用途条款窗口 Window_Selectable
Window_Status
状态窗口 Window_Base
Window_SaveFile
存盘窗口 Window_Base
Window_ShopCommand
店面窗口 Window_Selectable
Window_ShopBuy
店面用途买进窗口 Window_Selectable
Window_ShopSell
店面用途卖出窗口 Window_Selectable
Window_ShopNumber 店面用途件数输入窗口
Window_Base
Window_ShopStatus
店面用途状态窗口 Window_Base
Window_NameEdit 名字修改窗口
Window_Base
Window_NameInput
名字输入窗口 Window_Base
Window_InputNumber
数值输入窗口 Window_Base
Window_Message 消息窗口
Window_Selectable
Window_PartyCommand Battle
画面用途派对指令窗口 Window_Selectable
Window_BattleStatus Battle
战斗状态窗口 Window_Base
Window_BattleResult Battle
战斗结果表示窗口 Window_Base
Window_DebugLeft
排除故障左的窗口 Window_Selectable
Window_DebugRight
排除故障右边的窗口 Window_Selectable

光标处理：

类　　　　　　　内容　　　　　　　父类
Arrow_Base 　　aroi 　光标 　　　Sprite
Arrow_Enemy 　enemii 　选择用途 　aroi 光标 Arrow_Base
Arrow_Actor 　　acter 　选择用途 　aroi 光标 Arrow_Base

在 Arrow_Base 类中包含对光标图标的忽明忽暗等的基本处理，Arrow_Enemy ， Arrow_Actor 类根据方向键的左右输入选择 batrai 的处理。窗口和相同，忽明忽暗和光标移动的处理 在update 方法中。

版权声明：RUBY文档中心的所有文章标明[原创]的均为本站作品，版权属RUBY中文化计划，若转载请注明；标明[翻译]的其外文版权归原作者，译文版权属RUBY中文化计划；标明[转贴]的，若原作者感到侵犯了他的著作权，那么请及时跟主持人联系，我们会尽快更正。

版权所有(C) RUBY中文化计划


18、活动指令 - RUBY文档中心

存疑，知行 RUBY文档中心
提供RUBY中文教程、文章、翻译作品

发表文章 发表文章　　 浏览文章 浏览文章　　 全文检索 全文检索　　 用户登录 用户登录
18、活动指令

原创：夏克　　发表时间：2005-6-3 2:28:56　　点击次数：222

RPGXP 的活动指令在脚本内如何使用：
对于[ 文章的表示 ] 和 [ 地点移动 ]等命令进行丰富多彩的组合就会产生丰富多彩的游戏。

在脚本编辑器左边的目录中，Interpreter1..7等的名字的脚本定义了尤其是大规模的类。

这个类的实例被下面的3个类拥有。

Game_System
Game_Event
Game_CommonEvent

101: 文章的表示
102: 可选择的方法的表示
103: 数值输入的处理
104: 文章选择变更
105: 按钮输入的处理
106: 等待
108: 注释
111: 条件分歧
112: 环
113: 环的中断
115: 活动处理的中断
116: 活动的一时消去
117: 顾问活动
118: 标签
119: 标签跳跃
121: 开关的操作
122: 变数的操作
123: 自我开关的操作
124: 秒表的操作
125: 金的增减
126: 条款的增减
127: 武器的增减
128: 护具的增减
129: acter 的换上新品
131: 窗口皮肤的变更
132: Battle BGM 的变更
133: Battle 结束 ME 的变更
134: 救球禁止的变更
135: 菜单禁止的变更
136: 冲突禁止的变更
201: 地点移动
202: 活动的位置设定
203: 地图的纸卷
204: 地图的设定变更
205: fog 的色调变更
206: fog 的不透明度变更
207: 动画片的表示
208: 透明情况的变更
209: 移动路线的设定
210: 到移动完成等待
221: Tran zition 准备
222: Tran zition 实行
223: 画面的色调变更
224: 画面的闪光
225: 画面的 sche 畏惧
231: piccha 的表示
232: piccha 的移动
233: piccha 的旋转
234: piccha 的色调变更
235: piccha 的消去
236: 天气的设定
241: BGM 的演奏
242: BGM 的褪色外面
245: BGS 的演奏
246: BGS 的褪色外面
247: BGM/ BGS 的记忆
248: BGM/ BGS 的恢复
249: ME 的演奏
250: SE 的演奏
251: SE 的停止
301: Battle 的处理
302: 店的处理
303: 名字输入的处理
311: HP 的增减
312: SP 的增减
313: 国家的变更
314: 全体恢复
315: EXP 的增减
316: 水平的增减
317: 参数的增减
318: 技能的增减
319: 装备的变更
320: acter 的名字变更
321: acter 的班上变更
322: acter 的画刊变更
331: enemii 的 HP 增减
332: enemii 的 SP 增减
333: enemii 的国家变更
334: enemii 的出现
335: enemii 的变身
336: enemii 的全体恢复
337: 动画片的表示
338: 损害的处理
339: 动作的强制
340: Battle 的中断
351: 菜单画面的传唤
352: 救球画面的传唤
353: 游戏超过
354: 向标题画面回复
355: 手稿
版权声明：RUBY文档中心的所有文章标明[原创]的均为本站作品，版权属RUBY中文化计划，若转载请注明；标明[翻译]的其外文版权归原作者，译文版权属RUBY中文化计划；标明[转贴]的，若原作者感到侵犯了他的著作权，那么请及时跟主持人联系，我们会尽快更正。

版权所有(C) RUBY中文化计划


19、场景类 - RUBY文档中心

存疑，知行 RUBY文档中心
提供RUBY中文教程、文章、翻译作品

发表文章 发表文章　　 浏览文章 浏览文章　　 全文检索 全文检索　　 用户登录 用户登录
19、场景类

原创：夏克　　发表时间：2005-6-3 2:52:20　　点击次数：247

无论地图画面还是菜单画面等等，担任各画面的处理的是场面类。
把所有的场面类列个表，类名和脚本名一一对应。还有，Scene_File 类作为 Scene_Save 和 Scene_Load 的超级类而定义的这个列表没包含。

类 内容
Scene_Title 标题画面
Scene_Map 地图画面
Scene_Menu 菜单画面
Scene_Item 条款画面
Scene_Skill 技能画面
Scene_Equip 装备画面
Scene_Status 状态画面
Scene_Save 储存画面
Scene_Load 载入画面
Scene_End 游戏结束画面
Scene_Battle Battle 战斗画面
Scene_Shop 商店画面
Scene_Name 名字输入画面
Scene_Gameover 游戏结束画面
Scene_Debug 排除故障程序画面

场面类的实例由全局变量$scene保存。这个变量能调出main方法，是继续游戏的构造所在。典型的场面类的骨架如下：

    class Scene_Xxxx
      def main
        Graphics.transition
        loop do
          Graphics.update
          Input.update
          update
          if $scene != self
            break
          end
        end
        Graphics.freeze
      end
      def update
      end
    end

Graphics.freeze 和 Graphics.transition参看Graphics类的详细资料。

if $scene != self 就是如果全局变量 $scene 指向的对象不是自己的话，Input.update 是更新游戏 pad和键盘的输入信息的方法。参照 Input 类的详细资料。

我们曾经在前面提到过，RGSS 的游戏的最基本的事情就是下面的行为：

loop do
Graphics.update
end

场面类的 main 方法是从这里变复杂来的,那么看看是怎样变复杂的吧：

Main 脚本是最后的脚本，以上的脚本基本都是全部类的定义，所以实际上，真正脚本的开始实行是从这里，Main 脚本除去注释 , 仅有成为 11 行。

begin
Graphics.freeze
$scene = Scene_Title.new
while $scene != nil
$scene.main
end
Graphics.transition(20)
rescue Errno::ENOENT
filename = $!.message.sub("No such file or directory - ", "")
print(" 文件夹 #{ filename} 没被发现。")
end

所谓 begin .. rescue ..end 是异常处理的语句。

Graphics.freeze 和 Graphics.transition 刚才也已提出了。

剩下的 4 行，担当标题画面的处理的 Scene_Title 类的实例，生成全局变量 $scene 。
版权声明：RUBY文档中心的所有文章标明[原创]的均为本站作品，版权属RUBY中文化计划，若转载请注明；标明[翻译]的其外文版权归原作者，译文版权属RUBY中文化计划；标明[转贴]的，若原作者感到侵犯了他的著作权，那么请及时跟主持人联系，我们会尽快更正。

版权所有(C) RUBY中文化计划


Programming Ruby: The Pragmatic Programmer's Guide

Programming Ruby

实用程序员指南

目 录

前言（完成，by halu，liubin）
序（未完成）
Roadmap（未动工）
Ruby.new（完成，by liubin）
类，对象和变量（完成，by liubin）
容器Containers，块Blocks和迭代Iterators（多半完成，by liubin）
标准类型（完成，by jellen，liubin）
深入方法（完成，by liubin）
表达式Expressions（完成，by liubin）
异常，捕捉和抛出（已经开始，by jellen）
模块（完成，by liubin）
基本输入输出（未完成，by liubin）
线程和进程（完成，by liubin）
当遭遇挫折
Ruby和它的世界
Ruby和Web开发（完成 by liubin）
Ruby Tk（完成 by liubin）
Ruby 和微软的 Windows （完成 by liubin）
扩展Ruby（进行中，by liubin）
Ruby语言 （by jellen）
类和对象 （by jellen）
Ruby安全
反射Reflection, ObjectSpace,和分布式Distributed Ruby
内建类和方法
标准库
OO设计
网络和Web库
Windows支持
内嵌文档
交互式Ruby Shell
支持

翻译：http://www.ruby-cn.org/

翻译说明：

Extracted from the book "Programming Ruby - The Pragmatic Programmer's Guide"
Copyright © 2001 by Addison Wesley Longman, Inc. This material may be distributed only subject to the terms and conditions set forth in the Open Publication License, v1.0 or later (the latest version is presently available at http://www.opencontent.org/openpub/)).

Distribution of substantively modified versions of this document is prohibited without the explicit permission of the copyright holder.

Distribution of the work or derivative of the work in any standard (paper) book form is prohibited unless prior permission is obtained from the copyright holder.


Programming Ruby: The Pragmatic Programmer's Guide

Programming Ruby

实用程序员指南

目录 ^
下一章 >

前言

人类是富于创造性的，我也喜欢创造新事物。我虽然不善于美术，音乐，但是我能创造软件。

接触到计算机之后不就，我就喜欢上了编程。而且我相信，理想的编程语言是能实现的，我也希望自己就是那个创造者。随后，当我有了更多经验之后，意识到这种全能的理想语言比我想像中的难多了。但我仍然想设计出一种能完成我现在每天工作的语言。这是我学生时代的梦想。

几年后，我和同事们谈论到脚本语言，关于脚本语言的能力和可能性，作为一个15年的面向对象的爱好者，我觉得面向对象的特性也适合脚本语言。我在网上找了一段时间，发现perl和python并不是我想要找的，我想要一种比perl更强大，比python更面向对象的语言。

这时，我想起来了当时的梦想，并且决定设计出自己的语言。开始时我只是把它当作玩具来工作，但渐渐地，它成为一种工具，并且非常的方便可以取代perl。我给它取了ruby（贵重的红宝石）这个名字。并在1995年发布。

自此，越来越多的人开始对ruby感兴趣。无论您是否喜欢这个工具，在日本ruby都已经比python盛行。我希望它也能最终被整个世界承认。

我认为生命的目的总有一部分是为了快乐。据此，ruby用于编程不仅仅是因为它简单，还因为它能带给人们欢乐。它允许你完全把精力集中于创造，几乎不会产生压力。如果你不相信我，那么请你读一下本书。那么你将会自己发现这一切。

我非常感谢那些已经加入了ruby团队的人；他们给了我很多帮助。我甚至觉得ruby象我自己的孩子，但事实上它是很多人共同努力的结果。没有他们的帮助，Ruby不会如此地成长。

我尤其感谢本书的作者Dave Thomas 和Andy Hunt。Ruby语言从来没有被形成过如此完美的文档。因为和写文档相比，我一直都是更爱写程序。Ruby本来不应该只有这么少，以致于人们要了解语言的实现内容不得不去读源代码。但现在Dave和Andy为我们写好了本书。

他们开始对这门从远东来的，鲜为人知的语言感兴趣了.他们开始研究，阅读成千上万行的源代码，撰写无数测试脚本和电子邮件，明晰语言中模糊不清的表述,发现bug甚至修补其中的一部分，最后终于编写了此书。现在Ruby有一本非常优秀的文档了！

他们做的这些工作曾经是微不足道的。他们写书的时候，我在修改语言本身。但最终更新的时候我们是同步的，这本书已经足够准确！

我希望这些本和Ruby语言本身都能让你的编程工作更简单更富于乐趣！

Yukihiro Matsumoto, a.k.a. ``Matz''

Japan, October 2000

Extracted from the book "Programming Ruby - The Pragmatic Programmer's Guide"
Copyright © 2001 by Addison Wesley Longman, Inc. This material may be distributed only subject to the terms and conditions set forth in the Open Publication License, v1.0 or later (the latest version is presently available at http://www.opencontent.org/openpub/)).

Distribution of substantively modified versions of this document is prohibited without the explicit permission of the copyright holder.

Distribution of the work or derivative of the work in any standard (paper) book form is prohibited unless prior permission is obtained from the copyright holder.


Programming Ruby: The Pragmatic Programmer's Guide

Programming Ruby

实用程序员指南

前一章< 目录 ^
下一章>

序

本书是Ruby程序设计语言的参考手册。用Ruby，在写好程序的基础上，你还能提高生产效率，并享受编程过程。

这虽然是一种大胆的观点，但是我相信您会在读完本书之后会同意他们的观点。这是我们的亲身经历。

作为注重实效的程序员，我们尝试了很多种语言，我们用这些工具简化我们的工作，工具确实也能让我们更好的工作。可是此前我们用过的语言一直让我们失望。

我们的工作目的就是解决问题，而不是做编译器，所以我们喜欢动态语言，它适应我们，没有独断性（arbitrary），有严格的规则。我们能用代码顺畅的沟通，能用代码正确有效的表达需求。而且，我们写的代码越少，出错的机会也越少（这样也会减少我们的手腕和手指的工作量）。

我们要求尽可能的提高生产效率，所以我们希望写完代码之后马上能运行起来；我们必须从开发时间中留出时间用于调试。他也能帮助我们在编写时提炼代码；如果你必须花费两个小时写一个循环，那你可能也要用punch cards和成批编辑提交你的工作。（We want to be as productive as possible, so we want our code to run the first time; time spent in the debugger is time stolen from the development clock. It also helps if we can try out code as we edit it; if you have to wait for a 2-hour make cycle, you may as well be using punch cards and submitting your work for batch compilation. ）

我们需要一个具有很高抽象层的语言，语言的抽象层越高，我们把需求转化为代码所需的时间越少。

当我们发现了Ruby，我们认识到我们找到了我们在一直寻找的东西。和我们用过的其他语言相比，ruby显得【stays out of your way】，你可以集中精力处理手边的事情，而不必去过多的陷在编译器和语言问题中。ruby正是如此让你能成为一个更好的程序员：你只需专心地为用户解决问题，而不是和编译器纠缠。

Ruby的闪光点

采用从真正的面向对象语言，比如Smalltalk，摒弃除去那些未成熟新奇的语法，创造更多的有用的，忠实于文档的代码。这样Ruby也加入到这些适应性强且又实用的工具（比如：Python and Perl）之中。你或许要终身使用Ruby作为你的工具了。（Take a true object-oriented language, such as Smalltalk. Drop the unfamiliar syntax and move to more conventional, file-based source code. Now add in a good measure of the flexibility and convenience of languages such asPython and Perl . You end up with Ruby. ）

OO爱好者有很多原因喜欢Ruby：纯面向对象（任何东西都是对象），元类，闭包，迭代，无处不在的集合。Smalltalk用户会感到很熟悉（c++和java用户可能会嫉妒了）。
同时，perl和python的拥护者也有他们很多喜欢perl或者python的特点：完全的正则表达式的支持，与底层操作系统的紧密结合，便利的捷径，以及动态调用。

Ruby很容易去学习，每天的任务是简单的编写代码，完成之后，这些代码很容易维护和完善。不过毕竟，很多困难的事情最初或许并不被认为很困难。但是Ruby遵循的是Principle of Least Surprise原理，即：事情会按你的预想发展，并且几乎没有特例或者例外。当然，就是这点让你在编程时感觉到不同。（ is easy to learn. Everyday tasks are simple to code, and once you've done them, they are easy to maintain and grow. Apparently difficult things often turn out not to have been difficult after all. Ruby follows the Principle of Least Surprise---things work the way you would expect them to, with very few special cases or exceptions. And that really does make a difference when you're programming. ）

我们称 Ruby为透明语言。这就是说， Ruby不会让你写下的解决方案因众多的语法而模糊不清，

We call Ruby a transparent language. By that we mean that Ruby doesn't obscure the solutions you write behind lots of syntax and the need to churn out reams of support code just to get simple things done. With Ruby you write programs close to the problem domain. Rather than constantly mapping your ideas and designs down to the pedestrian level of most languages, with Ruby you'll find you can express them directly and express them elegantly. This means you code faster. It also means your programs stay readable and maintainable.

使用Ruby，我们可能会经常为它的特点感到惊奇，没有那么多得语法错误，没有类型错误，更少的bug。这很有用，它会让我们更少出错，没有在每行结尾机械式的加入分号，省得类型定义的麻烦（特别是分散在多个文件中的时候）。没有不需要的语句会让编译器报错。

那么，为什么要学习ruby呢？因为我们认为它能帮助我们更好的编程，它将帮助你把主要注意力集中在实质的东西上，而不必过分分心。总之，它将使你生活变得容易。

Ruby是一种什么样的语言?

In the old days, the distinction between languages was simple: they were either compiled, like C or Fortran, or interpreted, like BASIC. Compiled languages gave you speed and low-level access; interpreted languages were higher-level but slower.

Times change, and things aren't that simple anymore. Some language designers have taken to calling their creations ``scripting languages.'' By this, we guess they mean that their languages are interpreted and can be used to replace batch files and shell scripts, orchestrating the behavior of other programs and the underlying operating system. Perl, TCL, and Python have all been called scripting languages.

What exactly is a scripting language? Frankly we don't know if it's a distinction worth making. In Ruby, you can access all the underlying operating system features. You can do the same stuff in Ruby that you can in Perl or Python, and you can do it more cleanly. But Ruby is fundamentally different. It is a true programming language, too, with strong theoretical roots and an elegant, lightweight syntax. You could hack together a mess of ``scripts'' with Ruby, but you probably won't. Instead, you'll be more inclined to engineer a solution, to produce a program that is easy to understand, simple to maintain, and a piece of cake to extend and reuse in the future.

Although we have used Ruby for scripting jobs, most of the time we use it as a general-purpose programming language. We've used it to write GUI applications and middle-tier server processes, and we're using it to format large parts of this book. Others have used it for managing server machines and databases. Ruby is serving Web pages, interfacing to databases and generating dynamic content. People are writing artificial intelligence and machine learning programs in Ruby, and at least one person is using it to investigate natural evolution. Ruby's finding a home as a vehicle for exploratory mathematics. And people all over the world are using it as a way of gluing together all their different applications. It truly is a great language for producing solutions in a wide variety of problem domains.

Ruby适合我吗?

Ruby is not the universal panacea for programmers' problems. There will always be times when you'll need a particular language: the environment may dictate it, you may have special libraries you need, performance concerns, or simply an issue with training. We haven't given up languages such as Java and C++ entirely (although there are times when we wish we could).

However, Ruby is probably more applicable than you might think. It is easy to extend, both from within the language and by linking in third-party libraries. It is portable across a number of platforms. It's relatively lightweight and consumes only modest system resources. And it's easy to learn; we've known people who've put Ruby code into production systems within a day of picking up drafts of this book. We've used Ruby to implement parts of an X11 window manager, a task that's normally considered severe C coding. Ruby excelled, and helped us write code in hours that would otherwise have taken days.

Once you get comfortable with Ruby, we think you'll keep coming back to it as your language of choice.

为什么我们要写这本书?

So we'd just finished writing The Pragmatic Programmer, our families had just started talking to us again, and suddenly we felt the need to write another book. Why? We guess it comes down to a kind of missionary zeal.

Ruby was created by Yukihiro Matsumoto (Matz) in Japan. Since 1995, its popularity in Japan has grown at an astounding rate; there are rumors that Ruby is more popular than Python in Japan. But to date, much of the detailed Ruby documentation is in Japanese. It probably isn't a programming language you'd just stumble across.

We wanted to spread the word, to have more people outside Japan using Ruby and enjoying the benefits, so we decided to document Ruby in English. And what started out as a small project just sort of grew....

Ruby 版本

This book documents Version 1.6 of Ruby, which was released in September 2000.

Ruby version numbering follows the same scheme used for many other open source projects. Releases with even subversion numbers (1.0, 1.2, 1.4, and so on) are stable, public releases. These are the releases that are prepackaged and made available on the various Ruby Web sites.

Development versions of the software have odd subversion numbers, such as 1.1 and 1.3. These you'll have to download and build for yourself, as described in the box on page xxvii.

安装 Ruby

You can get Ruby from ftp://ftp.netlab.co.jp/pub/lang/ruby, or from the mirror sites listed on page 526 in Appendix C. There you will find the latest stable release, as well as various development releases.

You'll always find source code releases of Ruby; you may also find prebuilt binaries for Windows or other operating systems (like the binary distribution of Ruby for Windows at http://www.pragmaticprogrammer.com/ruby/downloads/ruby-install.html).

Sidebar: 最新版的 Ruby

For those who just have to be on the very latest, hot-off-the-press and untested cutting edge (as we were while writing this book), you can get development versions straight from the developers' working repository.

The Ruby developers use CVS (Concurrent Version System, freely available from http://www.cvshome.com) as their revision control system. You can check files out as an anonymous user from their archive by executing the following CVS commands:

% cvs -d :pserver:anonymous@cvs.netlab.co.jp:/home/cvs
   login
(Logging in to anonymous@cvs.netlab.co.jp)
CVS password: guest
% cvs -d :pserver:anonymous@cvs.netlab.co.jp:/home/cvs
   checkout ruby

The complete source code tree, just as the developers last left it, will now be copied to a ``ruby'' subdirectory on your machine, updating your local source tree from a repository on the other side of the world. Isn't it a great time to be alive?

Building Ruby

In the Ruby distribution you'll find a file named README, which explains the installation procedure in detail. To summarize, you build Ruby on POSIX-based systems using the same four commands you use for most other open source applications: ./configure, make, make test, and make install. You can build Ruby under other environments (including Windows) by using a POSIX emulation environment such as cygwin [See http://sourceware.cygnus.com/cygwin for details.] or by using native compilers---see ``ntsetup.bat'' in the distribution's win32 subdirectory as a starting point.

运行 Ruby

Now that Ruby is installed, you'd probably like to run some programs. Unlike compiled environments, there are two ways to run Ruby---interactively and as a program.

交互式 Ruby

The easiest way to run Ruby interactively is simply to type ``ruby'' at the shell prompt.

% ruby
puts "Hello, world!"
^D
Hello, world!

Here we typed in the single puts expression and an end of file character (which is control-D on our system). This process works, but it's sort of painful if you make a typo, and you can't really see what's going on as you type.

In the sample directory in the Ruby distribution you'll find a script named ``eval.rb''. It goes one step better by showing us the value of each expression as it is entered:

% cd sample
% ruby eval.rb
ruby> a = "Hello, world!"
"Hello, world!"
ruby> puts a
Hello, world!
nil
ruby> ^D
%

Here we can see the output from puts, and then the return value from puts (which is nil).

That's all fine and well, except that multiline expressions do not work, and you can't edit the line you're on, or go back and use previous lines (as you might with command history in a shell).

For the next step up from eval.rb, we have irb---Interactive Ruby. irb is a Ruby Shell, complete with command-line history, line editing capabilities, and job control. It is quite configurable and has many options, so much so that it has its own appendix beginning on page 517. We recommend that you get familiar with irb so you can try some of our examples interactively.

Ruby 程序

Finally, you can run a Ruby program from a file as you would any other shell script, Perl program, or Python program. You can simply run Ruby giving the script name as an argument:

% ruby myprog.rb

Or you can use the Unix ``shebang'' notation as the first line of the program file.[If your system supports it, you can avoid hard-coding the path to Ruby in the shebang line by using #!/usr/bin/env ruby, which will search your path for ruby and then execute it.]

#!/usr/local/bin/ruby -w

puts "Hello, World!"

If you make this source file executable (using, for instance, chmod +x myprog.rb), Unix lets you run the file as a program:

% ./myprog.rb
Hello, World!

You can do something similar under Microsoft Windows using file associations.

资源

Visit the Ruby Web sites, http://www.rubycentral.com and http://www.ruby-lang.org, to see what's new, and chat with other Ruby users on the newsgroup or mailing lists (see Appendix C).

And we'd certainly appreciate hearing from you. Comments, suggestions, errors in the text, and problems in the examples are all welcome. E-mail us at:

mailto:rubybook@pragmaticprogrammer.com

If you tell us about errors in the book, we'll add them to the errata list at:

http://www.pragmaticprogrammer.com/ruby/errata/errata.html

Finally, http://www.pragmaticprogrammer.com/ruby also contains the source code for almost all the book's examples, organized by page.

致谢

A book is a massive undertaking, one that we would never be able to complete without help from our all our friends, old and new. We're proud to count among our old friends the team at Addison-Wesley: Mike Hendrickson, John Fuller, the ever-helpful Julie Steele, and the wonderful Julie DiNicola. Thank you all.

Our reviewers were fantastic. We put them up against some incredibly tight deadlines and they came through for us. Reviewing a book full of technical detail isn't easy, so we're especially grateful to George Coe, Bob Davison, Jeff Deifik, Hal Fulton, Tadayoshi Funaba, Clemens Hintze, Kazuhiro Hiwada, Kikutani Makoto, Mike Linksvayer, Aleksi Niemel? Lew Perin, Jared Richardson, Armin Roehrl, Conrad Schneiker, Patrick Schoenbach, and Eric Vought. Thanks also go to the two Julies at Addison-Wesley for coordinating this truly international effort.

Several people helped us with specific areas of this book. Tadayoshi Funaba exchanged countless e-mails with us until we finally understood the Date module. Guy Decoux and Clemens Hintze patiently answered our questions about writing Ruby extensions, and Masaki Suketa helped us understand the WinOLE module.

Although much of the original Ruby documentation is in Japanese, there is a growing body of English translations, mostly undertaken by Japanese developers whose skills with English never cease to amaze us. Although there are too many individual contributions to this effort to name each author, we would like to single out Goto Kentaro, who has produced a large volume of high-quality documentation and placed it online.

Finally, we have to thank Yukihiro ``Matz'' Matsumoto, the creator of Ruby. We've lost count of the number of questions we've asked of him, and the number of patient and detailed answers he's sent back. As well as creating a truly wonderful language, Matz has fostered a wonderfully supportive and open culture in which that language can prosper.

Thank you all. Domo arigato gozaimasu.

Dave Thomas and Andy Hunt
THE PRAGMATIC PROGRAMMERS
http://www.pragmaticprogrammer.com

符号约定

Throughout this book, we use the following typographic notations.

Literal code examples are shown using a typewriter-like font:

class SampleCode
  def run
    #...
  end
end

Within the text, Fred#doIt is a reference to an instance method (doIt) of class Fred, while Fred.new [In some other Ruby documentation, you may see class methods written as Fred::new. This is perfectly valid Ruby syntax; we just happen to feel that Fred.new is less distracting to read.] is a class method, and Fred::EOF is a class constant.

The book contains many snippets of Ruby code. Where possible, we've tried to show what happens when they run. In simple cases, we show the value of expressions on the same line as the expression. For example:

a = 1
b = 2
a + b ?/td> 3

At times, we're also interested in the values of assignment statements, in which case we'll show them.

a = 1 | ?/td> | 1
b = 2 | ?/td> | 2
a + b | ?/td> | 3

If the program produces more complex output, we show it below the program code:

3.times { puts "Hello!" }
produces:
Hello!
Hello!
Hello!

In some of the library documentation, we wanted to show where spaces appear in the output. You'll see these spaces as `` [visible space] '' characters.

Command-line invocations are shown with literal text in a Roman font, and parameters you supply in an italic font. Optional elements are shown in large square brackets.

ruby [
            flags
            ]*
             [
            progname
            ] [
            arguments
            ]+

Extracted from the book "Programming Ruby - The Pragmatic Programmer's Guide"
Copyright © 2001 by Addison Wesley Longman, Inc. This material may be distributed only subject to the terms and conditions set forth in the Open Publication License, v1.0 or later (the latest version is presently available at http://www.opencontent.org/openpub/)).

Distribution of substantively modified versions of this document is prohibited without the explicit permission of the copyright holder.

Distribution of the work or derivative of the work in any standard (paper) book form is prohibited unless prior permission is obtained from the copyright holder.


Programming Ruby: The Pragmatic Programmer's Guide

Programming Ruby

The Pragmatic Programmer's Guide

Previous < Contents ^
Next >

Roadmap

The main text of this book has four separate parts, each with its own personality, and each addressing different aspects of the Ruby language.

In Part I, Facets of Ruby, you'll find a Ruby tutorial. It starts off with a short chapter on some of the terminology and concepts that are unique to Ruby. This chapter also includes enough basic syntax so that the other chapters will make sense. The rest of the tutorial is a top-down look at the language. There we talk about classes and objects, types, expressions, and all the other things that make up the language. We even end with a short chapter on digging yourself out when trouble strikes.

One of the great things about Ruby is how well it integrates with its environment. Part II, Ruby in Its Setting, investigates this. Here you'll find practical information on running Ruby, and using Ruby with the Web. You'll learn how to create GUI applications using Tk, and how to use Ruby in a Microsoft Windows environment, including wonderful things such as making native API calls, COM integration, and Windows Automation. And you'll discover just how easy it is to extend Ruby and to embed Ruby within your own code.

Part III, Ruby Crystallized, contains more advanced material. Here you'll find all the gory details about the language, the metaclass model, tainting, reflection, and marshaling. You could probably speed-read this the first time through, but we found ourselves using the tables in this section even as we were writing the rest of the book.

The Ruby Library Reference is Part IV. It's big. We document over 800 methods in more than 40 built-in classes and modules. On top of that, we have another 70 pages describing some of the more useful library modules that come with Ruby.

So, how should you read this book? Well, it depends on you.

Depending on your level of expertise with programming in general, and OO in particular, you may want to read just a few portions of the book to start with. Here are our recommendations.

If you're a beginner, you may want to start with the tutorial material in Part I. Keep the library reference close at hand as you start to write programs. Get familiar with the basic classes such as Array, Hash, and String. As you become more comfortable in the environment, you may want to investigate some of the more advanced topics in Part III.

If you're already comfortable with Perl, Python, Java, or Smalltalk, then we'd suggest reading the introduction in Chapter 2 first. From there, you may want to take the slower approach and keep going with the tutorial that follows, or skip ahead to the gritty details starting in Part III, followed by the library reference in Part IV.

Experts, gurus, and ``I-don't-need-no-stinking-tutorial'' types can dive straight into the language reference in Chapter 18, which begins on page 199, skim the library reference, then use the book as a (rather attractive) coffee coaster.

Of course, there's nothing wrong with just starting at the beginning and working your way through.

And don't forget, if you run into a problem that you can't figure out, help is available. See Appendix C beginning on page 525 for more information.

Previous < Contents ^
Next >

Extracted from the book "Programming Ruby - The Pragmatic Programmer's Guide"
Copyright © 2001 by Addison Wesley Longman, Inc. This material may be distributed only subject to the terms and conditions set forth in the Open Publication License, v1.0 or later (the latest version is presently available at http://www.opencontent.org/openpub/)).

Distribution of substantively modified versions of this document is prohibited without the explicit permission of the copyright holder.

Distribution of the work or derivative of the work in any standard (paper) book form is prohibited unless prior permission is obtained from the copyright holder.


Programming Ruby: The Pragmatic Programmer's Guide

Programming Ruby

The Pragmatic Programmer's Guide

前一章< 目录 ^
下一章>

Ruby.new

当我们开始要写这本书的时候，有一个伟大重要的计划，因为我们那时候还很年轻。我们决定从最高到低层次来记录ruby语言，从类和对象开始，然后以详细的核心语法结束。那时候，这看来是个不错的选择，几乎Ruby中很多东西都是对象，感觉也也应该先说说对象。

我们是这么想的。

但是，那样来描述一门语言显得比较困难：如果我们不曾涉及到string，if语句，赋值语句，或其他详细的东西，很难写一个类的例子。贯穿于我们从高到低的计划，我们不得不穿插一些低层次的细节来使得例子能被读者看懂。

所以，我们开始了另一个计划，虽然我们仍然从对象开始，但是在这之前，先用一章来描述ruby比较普通的特点，这都会在将来的例子里出现。就让我们从一小段指南开始，然后再进入剩余的部分。

Ruby 是面向对象的语言

重申一遍的是，ruby是纯的面向对象的语言，你操作的一切都是对象，并且他们产生的结果也是对象。尽管很多其他语言也说自己是面向对象的，但是他们总是用不同的解释来阐述什么是面向对象，用不同的术语描述他们的使用的观念。

所以，在进一步深入到详细内容之前，先来简要看看我们要用到的术语和符号。

当你编写面向对象的代码时，总会将现实世界中的事物模型化到你的代码之中，在建模的过程中，你会发现事物能分成不同的对象（类）在代码中实现，比如在自动点唱机（jukebox）中，歌曲就是这样的例子。在ruby里，我们用class来表示一个对象类。一个类是状态（比如歌曲名称等）和方法（比如播放，停止）等地组合。

当你写完了类，就可以为每个类建立若干个实例，比如对于jukebox来说，它包含一个类song，我们可以建立诸如``Ruby Tuesday,'' ``Enveloped in Python,'' ``String of Pearls,'' ``Small talk,''等实例。对象这个词经常和类的实例交换使用，一般的时候我们还是乐意叫它对象。

在 Ruby中，要建立这些对象，可以调用类的构造函数。这个函数是类中的特殊的函数标准名称是new.

song1 = Song.new("Ruby Tuesday")
song2 = Song.new("Enveloped in Python")
# and so on

这两个实例都来源于一个类，但他们有不同的特征。首先，每个对象都有一个唯一的对象标识符（object identifier）也就是对象id。第二，你可以定义实例变量，每个实例的实例变量都是不同的。实例变量保存着实例的不同状态。上面的例子中，两个实例是不同的因为他们有着不同的歌曲名称。

在每一个类里面，还可以定义实例方法（instance methods），每个方法都是都是一块代码的集合，根据访问限制的不同，可以在内部或者外部访问这些方法。这些方法可以访问对象的实例变量，也就是对象的状态。

要想调用一个方法，可以向这个对象发送一个消息。这个消息包括方法名和一些参数。[这种用发消息的方式调用方法来自smalltalk]。当一个对象收到消息，它会检查自己所属的类是否有对应的方法，如果有，那么就执行这个方法，如果没有，下面会讲到该会如何。

方法和消息的关系听起来可能有些复杂，但是实际上这样很自然。我们先来看看一些方法调用(记住}}在这里表示的是对应的表达式返回的结果)：

"gin joint".length | }} | 9
"Rick".index("c") | }} | 2
-1942.abs | }} | 1942
sam.play(aSong) | }} | "duh dum, da dum de dum ..."

上面的例子中，点（.）前面的是消息的接收者，点后面的是要调用的方法名。第一个例子中，调用一个字符串的方法，得到它的长度，第二个得到一个字符在字符串中的位置，第三个取得了一个数的绝对值，第四个调用sam对象的play方法。

It's worth noting here a major difference between Ruby and most other languages. 在java里，要想得到一个数的绝对值，要调用额外的方法，并且把那个数字传过去，比如这样：

number = Math.abs(number)     // Java code

在Ruby中, 得到一个数的绝对值的功能在数字对象里是内建的，所有细节都在内部，我们只需要调用就行了。

number = number.abs

同样，对其他ruby中的对象来说也是这样的。比如在c语言中要写 strlen(name)，在ruby中，只要name.length 就行了。这也是为什么我们说ruby是一个纯的oo语言。

Ruby基础

一个人在学习一门新的语言的时候，都不大愿意去看长篇大论的繁琐的语法，所以，这一节我们打算涉及到一些显著的特点，如果你打算写ruby程序的话，这些东西你都应该知道。在第18章，我们将会更深入的解释类和对象。

让我们从一个简单的程序开始。这里有一个方法，它返回一个string类型的值，简单的把一个字符串加到用户名后面。然后调用这个函数2次。

def sayGoodnight(name)
  result = "Goodnight, " + name
  return result
end

# Time for bed...
puts sayGoodnight("John-Boy")
puts sayGoodnight("Mary-Ellen")

首先，从大体上我们看到，ruby的语法还是比较简单的，首先，你不必每行都写个分号，Ruby注释以＃开头，直到行尾。 Code layout is pretty much up to you; indentation is not significant.

Methods定义以关键字def开始，接着是方法名(这里是sayGoodnight)和用两个小括号括起来的方法参数，ruby不需要用{ }来界定程序主体，只需要关键字end就行了。这个程序也相当简单，第一行把``Goodnight, ''加上参数name，并把它赋给了局部变量result，第二行把结果返回给调用者。注意我们不需要定义变量result，当给它赋值的时候，系统会自动创建的。

最后我们调用了2次这个方法，并把结果传给puts函数，这个函数简单的再新行上打印传给它的参数而已，最后结果像这样：

Goodnight, John-Boy
Goodnight, Mary-Ellen

其实 ``puts sayGoodnight("John-Boy")'' 包括了2个函数调用，一个调用sayGoodnight，另一个调用puts，但是为什么puts调用没有用括号呢？实际上，下面的调用都是等价的：

puts sayGoodnight "John-Boy"
puts sayGoodnight("John-Boy")
puts(sayGoodnight "John-Boy")
puts(sayGoodnight("John-Boy"))

但是事实上，这样很难看清楚哪个参数是给哪个方法的，所以还是建议在方法后面加上括号，除非是非常简单的场合。

这个方法还展示了string对象，有很多种办法可以创建string对象，但最普通的要算用string literals了：单引号或双引号包起来的一组字符。它们的区别是ruby构建这两种字符串时要做的操作。对单引号引起来的字符串来说，ruby做的工作会很少，单引号引起来得部分就是它的值。如果是双引号引起来得，则要做多一些工作了。首先，它检查是否包含反斜线，也就是转义符，然后用适当的二进制值代替，最常见的就是"\n"了，它将会被换行替换。

比如：

puts "And Goodnight,\nGrandma"

产生结果如下:

And Goodnight,
Grandma

处理双引号括起来的字符串的时候如果做的第二件事情就是表达式处理（expression interpolation）。#{ expression }将被expression的值代替，例如，下面的方法和刚才的例子是一样的结果：

def sayGoodnight(name)
  result = "Goodnight, #{name}"
  return result
end

当ruby构造这个字符串时，先检查name的值，然后用这个值替换到字符串里面去。表达式要放到 #{...}结构里面去。As a shortcut, you don't need to supply the braces when the expression is simply a global, instance, or class variable. For more information on strings, as well as on the other Ruby standard types, see Chapter 5, which begins on page 47.

最后，我们还可以把刚才的函数简化成这样的。一个ruby函数返回的结果就是最后一行的值，所以这个函数也可以写成如下：

def sayGoodnight(name)
    "Goodnight, #{name}"
end

We promised that this section would be brief. We've got just one more topic to cover: Ruby names. For brevity, we'll be using some terms (such as class variable) that we aren't going to define here. However, by talking about the rules now, you'll be ahead of the game when we actually come to discuss instance variables and the like later.

Ruby uses a convention to help it distinguish the usage of a name: the first characters of a name indicate how the name is used. Local variables, method parameters, and method names should all start with a lowercase letter or with an underscore. Global variables are prefixed with a dollar sign ($), while instance variables begin with an ``at'' sign (@). Class variables start with two ``at'' signs (@@). Finally, class names, module names, and constants should start with an uppercase letter. Samples of different names are given in Table 2.1 on page 10.

Following this initial character, a name can be any combination of letters, digits, and underscores (with the proviso that the character following an @ sign may not be a digit).

Example variable and class names
Variables |  |  |  | Constants and
Local | Global | Instance | Class | Class Names
name | $debug | @name | @@total | PI
fishAndChips | $CUSTOMER | @point_1 | @@symtab | FeetPerMile
x_axis | $_ | @X | @@N | String
thx1138 | $plan9 | @_ | @@x_pos | MyClass
_26 | $Global | @plan9 | @@SINGLE | Jazz_Song

数组和哈希表

ruby的数组和哈希是有索引的集合，它们存放着各种对象，并且用一个key来访问。对数组来说这个key是一个整数，而哈希则支持任何对象作为key。两者都可以动态增长，如果他们需要的话。访问数组是一件很方便的事情，但是访问哈希就要复杂一些了。
最简单的创建和初始化一个数组的方法是用一对中括号包含一组值，这组值用逗号分割。然后用一个索引可以访问单独的任何一个元素。如下：

a = [ 1, 'cat', 3.14 ] # array with three elements
# access the first element
a[0] | }} | 1
# set the third element
a[2] = nil
# dump out the array
a | }} | [1, "cat", nil]

也可以这样创建新的空的数组：用空元素构造或者调用Array的构造函数 Array.new .

empty1 = []
empty2 = Array.new

当然，像第一种方法那样写很多引号和逗号可能很麻烦，但是ruby提供了一个好东西，如下面那样%w可真管用：

a = %w{ ant bee cat dog elk }
a[0] | }} | "ant"
a[3] | }} |  | "dog"

哈希和数组类似，不过它不用[]来包括，而是用{}，哈希里的每条记录要两个要素来表示，一个是关键字，另一个是对应这个key的值。如：

instSection = {
  'cello'     => 'string',
  'clarinet'  => 'woodwind',
  'drum'      => 'percussion',
  'oboe'      => 'woodwind',
  'trumpet'   => 'brass',
  'violin'    => 'string'
}

哈希的引用也是用[]，像数组一样，但是它的[]里面是key，可以是字符串不像数组，只能是整数比如：

instSection['oboe'] | }} | "woodwind"
instSection['cello'] | }} | "string"
instSection['bassoon'] | }} | nil

从上面最后一个例子看出，如果哈希里不包含一个key值，则默认会返回nil，这也是一个关键字（保留字），在条件运算中，nil跟false是一样的。如果你想改变这个默认值，则可以在创建一个哈希的时候提供这个默认值就行了，如下面的  Hash.new(0)中，0就是默认值：

histogram = Hash.new(0)
histogram['key1'] | }} | 0
histogram['key1'] = histogram['key1'] + 1
histogram['key1'] | }} | 1

数组和哈希又很多其他有用的用法，可以参看书末附录中的参考手册。

条件控制

Ruby包括通常的一些条件控制语句，比如if，then，while等。 Java, C, 和 Perl 程序员可能会被打括号{}弄得很烦乱，与此不同Ruby用关键字end来结束一块代码 ，比如：

if count > 10
  puts "Try again"
elsif tries == 3
  puts "You lose"
else
  puts "Enter a number"
end

同样，while语句看起来都是这样的。

while weight < 100 and numPallets <= 30
  pallet = nextPallet()
  weight += pallet.weight
  numPallets += 1
end

statement modifiers 是ruby语法中的一点特别之处，如果你的if或while条件下的语句特别简单，可以把他们写在一行上：先写表达式，然后跟着写if或者while语句，比如下面的例子：

if radiation > 3000
  puts "Danger, Will Robinson"
end

可以改写成如下：

puts "Danger, Will Robinson" if radiation > 3000

同样，while循环如下：

while square < 1000
   square = square*square
end

更简洁的形式：

square = square*square  while square < 1000

statement modifiers对perl程序员来说应该比较熟悉。

正则表达式Regular Expressions

大多数语言都有字符串，整型，浮点型，数组等数据类型，所以ruby中的大多数内建类型对大家来说一定不算陌生。所谓的脚本语言比如perl，python，awk中都支持正则表达式，
However, until Ruby came along, regular expression support was generally built into only the so-called scripting languages, such as Perl, Python, and awk. This is a shame: regular expressions, although cryptic, are a powerful tool for working with text.

本书整个都包括正则表达式的内容，所以这里没必要用一节涵盖所有内容。所以，我们只会涉及到几个正则表达式的例子。

正则表达式就是在一个字符串中查找一个匹配的模式，在Ruby中，要建立一个正在表达式，只要把要匹配的模式放到两个斜线中就行了(/pattern/)，而且，在Ruby中，正则表达式也是对象，可以像对象一样被操作。

例如，你写了一个模式，匹配一个字符串中的Perl或者Python，模式如下：

/Perl|Python/

The forward slashes delimit the pattern, which consists of the two things we're matching, separated by a pipe character (``|''). You can use parentheses within patterns, just as you can in arithmetic expressions, so you could also have written this pattern as

/P(erl|ython)/

在模式里，还可以指定重复的字符，比如 /ab+c/匹配一个字符串包括一个a，一个或多个b，然后跟一个c。把加号改成星号，即 /ab*c/将匹配一个a，0个或多个b，然后是一个c。

在一个模式中，也可以匹配一类字符，比如"\s"匹配一个空格，"\d"匹配数字，"\w"匹配在典型单词出现的字符，"."匹配任何字符。

我们可以把这些规则写道一块，生成一些比较有用的表达式。

/\d\d:\d\d:\d\d/     # a time such as 12:34:56
/Perl.*Python/       # Perl, zero or more other chars, then Python
/Perl\s+Python/      # Perl, one or more spaces, then Python
/Ruby (Perl|Python)/ # Ruby, a space, and either Perl or Python

创建了一个模式，当然要使用它。匹配操作符"=~"可以用来在一个字符串中匹配一个模式，如果在字符串中找到匹配的模式，=~ 返回它的开始位置，否则返回nil。所以，你也可以将正则表达式放到if或者while语句中作为判断条件。 比如，下面代码判断到如果一个字符串包括指定的模式（包含Perl或者Python），则输出一条消息。

if line =~ /Perl|Python/
  puts "Scripting language mentioned: #{line}"
end

一个字符串中被匹配到的部分也可以用ruby自带的替换函数替换成不同的值。

line.sub(/Perl/, 'Ruby')    # replace first 'Perl' with 'Ruby'
line.gsub(/Python/, 'Ruby') # replace every 'Python' with 'Ruby'

在本书以后 章节中，还有很多关于正则表达式的东西要讲述。

块和迭代

这节简要介绍ruby特别的一点长处。 一大块代码，可以像方法一样被调用，好像是方法的参数一样。你可以用块来实现方法回调（比java的内类要简单多了），给一段代码传递另一段代码（比c的方法指针要灵活），并且可以实现迭代。块用{}或者do…end包含起来：

{ puts "Hello" }       # this is a block

do                     #
  club.enroll(person)  # and so is this
  person.socialize     #
end                    #

当你完成了一个块，下一步可以用一个方法来调用这个块，这个方法可以用yield关键字来调用一次或者多次块。如下面这个例子，我们定义了一个方法，调用了两次yield，然后调用这个方法，并且在同一行放了一个块：

def callBlock
  yield
  yield
end

callBlock { puts "In the block" }
结果如下:
In the block
In the block

可以看到代买(puts "In the block")执行了两次，每次yield调用都会执行一次。

而且，我们还可以给yield传参数，这个参数也会被传到块中。在块中，列出参数的名字，用竖线 "|" 格开

  def callBlock
    yield ,
  end

  callBlock { |, | ... }

块代码贯穿于Ruby库中迭代的实现之中：某种集合返回下一个值得方法，比如数组

a = %w( ant bee cat dog elk )    # create an array
a.each { |animal| puts animal }  # iterate over the contents
产生:
ant
bee
cat
dog
elk

让我们看看上面的例子是如何来实现数组的each迭代的，each迭代循环整个数组，从第一个到最后一个元素，对每一个元素，调用yield。伪代码表示如下：

# within class Array...
def each
  for each element
    yield(element)
  end
end

你可以循环迭代数组中的每个元素，并且提供一个块来处理。

[ 'cat', 'dog', 'horse' ].each do |animal|
  print animal, " -- "
end
结果:
cat -- dog -- horse --

ruby也提供了类似c和java的其他一些循环结构，而且非常容易使用，每个方法可以运行0次和多次提供的块代码。

5.times {  print "*" }
3.upto(6) {|i|  print i }
('a'..'e').each {|char| print char }
产生:
*****3456abcde

这里，我们让数字5调用块5次，然后从3到6调用另一个块，每次给块传递下一个循环值，最后，在从a到e的范围里用each方法循环。

读写操作

Ruby自带了丰富的I/O操作库，在本书的例子中，我们只会用到很少的一些方法。目前我们已经接触到了2个输出方法，puts和print。puts依次输出所有的参数，每个参数之后都会加一个换行；print也类似， 不过不会在每个参数后面加入新行，比如（译者测试的例子，原书无）：

irb(main):011:0> puts "puts 1","puts 2"
puts 1
puts 2
=> nil
irb(main):012:0> printf " puts %s puts %s",1,2
puts 1 puts 2=> nil #nil出现在此处
irb(main):013:0>

这两个方法都可以输出到I/O对象，默认都输出到控制台。另一个经常用到的是printf，类似c里的，是格式化输出语句：

printf "Number: %5.2f, String: %s", 1.23, "hello"
produces:
Number:  1.23, String: hello

printf的格式如下：pringf（格式控制，输出列表）这个例子里，格式化字符串"Number: %5.2f, String: %s"中有两个参数，第一个是一个浮点型（%5.2f），5表示一共有5个数字，2表示小数点后边有两位；第二个表示是一个字符串。

读入输入字符有几种方法，按传统惯例来说最简单的是用gets。它返回你的程序标准输入的下一行。（下例为译者修改过的）

irb(main):016:0> line=gets
ruby
=> "ruby\n"
irb(main):017:0> puts line
ruby
=> nil
irb(main):018:0>

不过gets也有一个副作用：用它得到一行的时候，它还会将这个值存到一个全局变量$_里，这个变量很特殊，在很多场合，他都会作为一些方法的默认值。比如你光运行print而不带任何参数，就会输出$_的值，如果你的if或者while没有条件语句，而只有表达式，它们也会判断当前$_值的真假来做判断。这些缩写可能会使你能够写出很简练的程序。比如下面的程序从输入流接收数据，打印出所有包含"Ruby"的行。

while gets           # assigns line to $_
  if /Ruby/          # matches against $_
    print            # prints $_
  end
end

#以下代码为译者添加

irb(main):004:0> line=gets
hehe # 此时$_=line＝"hehe"
=> "hehe\n"
irb(main):006:0> print # print $_
hehe
=> nil
irb(main):007:0> puts # 无效

=> nil
irb(main):008:0> gets # 存到$_
xixi
=> "xixi\n"
irb(main):009:0> print # print $_
xixi
=> nil
irb(main):010:0>

Ruby特色的写法应该用一个迭代如下：

ARGF.each { |line|  print line  if line =~ /Ruby/ }

这里用到了一个预先定义好的对象ARGF，它代表了一个可以被程序使用的输入流。

Onward and Upward

到这里，我们快速的浏览了ruby的一些基本特点，我们知道了对象，方法字符串，容器，正则表达式，一些简单的条件控制，迭代等。希望这一章能帮助你很好的掌握本书的其他章节。

Time to move on, and up---up to a higher level. Next, we'll be looking at classes and objects, things that are at the same time both the highest-level constructs in Ruby and the essential underpinnings of the entire language.

前一章< 目录 ^
下一章>

Extracted from the book "Programming Ruby - The Pragmatic Programmer's Guide"
Copyright © 2001 by Addison Wesley Longman, Inc. This material may be distributed only subject to the terms and conditions set forth in the Open Publication License, v1.0 or later (the latest version is presently available at http://www.opencontent.org/openpub/)).

Distribution of substantively modified versions of this document is prohibited without the explicit permission of the copyright holder.

Distribution of the work or derivative of the work in any standard (paper) book form is prohibited unless prior permission is obtained from the copyright holder.


Programming Ruby: The Pragmatic Programmer's Guide

Programming Ruby

实用程序员指南

前一章< 目录 ^
下一章>

类，对象和变量

看了前面我们谈论到的一些例子，你也许会怀疑ruby的面向对象特性是否属实，这章我们将会详细讲述这方面的内容。我们将会探讨在ruby中如何创建类和对象，并且讨论ruby比其他面向对象语言的一些更强之处。同时，我们也会部分实现我们数亿美元的产品：基于因特网的爵士和布鲁斯自动点唱机。

经过几个月的工作，我们负责的研究人员决定我们的点唱机学要歌曲（songs），所以我们要在ruby中建立一个song类来表示现实中的歌曲。我们知道歌曲都有一个名字，演唱者，时长等，所以，我们的song对象也应如此。

我们开始创建了一个类：Song,[前面我们已经知道类名以大写字母开头，而方法以小写字母开头] 它只含有一个方法：initialize.

class Song
  def initialize(name, artist, duration)
    @name     = name
    @artist   = artist
    @duration = duration
  end
end

initialize 在Ruby中是一个特殊方当法，当你调用Song.new 来创建一个Song 对象的时候, Ruby先创建一个没有初始化的对象，然后调用它的initialize 方法,把传给new的参数再传给 initialize 这个方法。这样，我们就可以编写代码来设置对象的状态了。

对于类Song来说，initialize 方法接收3个参数，这三个参数作用域跟方法里的局部变量一样，所以他们的命名方式更具备变量一样，以小写字母开头。

每个对象代表自己的歌曲，他们有不同的名字，演唱者和时长等，也就是我们要把这些东西当成对象里面的实例变量。在ruby中实例变量用@开头，比如我们上面的例子，@name，@artist，@duration都是实例变量。

让我们看看我们的成果如何：

aSong = Song.new("Bicylops", "Fleck", 260)
aSong.inspect }} "#<Song:0x401b4924 @duration=260, @artist=\"Fleck\", @name=\"Bicylops\">"

它已经可以工作了。默认的inspect方法可以发送给任何对象，并且能得到这个对象的id和它的实例变量。从上面的结果看到我们正确的设置了对象的各个状态。

我们的经验告诉我们，在开发过程中，我们要多次打印Song中的内容，等inspect的默认格式不能完全满足我们的要求，幸运的是，Ruby有一个标准的消息to_s，当向一个对象发送这个消息时，将会返回一个字符串，比如对于song来说：

aSong = Song.new("Bicylops", "Fleck", 260)
aSong.to_s }} "#<Song:0x401b499c>"

这样没多少用处，甚至还不如inspect，只有对象id。但是我们可以重载这个to_s方法。同时，我们也会用一点时间来说说Ruby中如何定义一个类。

在Ruby中，类永远不会关闭，你可以一直往里面加入方法，不光是你自己写的类，系统内建的类也可以加入的。你需要做的是打开一个类的定义，然后就可以加入自己的方法了。

这对我们来说非常好。在本章以后的例子里，我们只需要添加新的方法，老的方法还继续存在，这样省得我们花费多余的时间去在每个例子里都重写一遍。尽管我们现在写的代码比较分散，但最好还是把它们都写到一个文件中去比较好。

我想已足够详细了，还是回到我们要添加的to_s方法吧。

class Song
  def to_s
    "Song: #{@name}--#{@artist} (#{@duration})"
  end
end
aSong = Song.new("Bicylops", "Fleck", 260)
aSong.to_s }} "Song: Bicylops--Fleck (260)"

非常好，我们进步了不少。但是你也许觉得被骗了，我们曾经说过Ruby中所有对象都支持to_s方法，但没有说怎么支持，答案是继承。ruby如何决定当一个对象接受一个消息后执行哪个方法，在下一节我们将会看到。

继承和消息

继承使你能够创建一个基于一个类的特殊化的类，比如，我们的自动点唱机里有song这个类，但是随着市场的需求，我们需要增加对卡拉ok的支持。卡拉ok也是歌曲的一种，只不过光有伴奏，没有演唱音，但是他们需要歌词这个属性，当我们播放卡拉ok时，歌词还要显示出来。

比较好的办法是定义一个类KaraokeSong，它就是一个song，但是有一个歌词的属性。

class KaraokeSong < Song
  def initialize(name, artist, duration, lyrics)
    super(name, artist, duration)
    @lyrics = lyrics
  end
end

" < Song" 告诉 ruby karaokeSong 这个类是Song的一个子类，Song是karaokeSong的父类。先不用管initialize这个方法，以后我们会谈到。

我们可以创建KaraokeSong 对象看看它是否能工作(在最后的系统中，lyrics存在另外的对象里，这个对象有文本和时间信息。为了测试方便，我们这里只用了字符串。这也是无类型语言的优点：我们在执行代码之前不需要对所有对象进行定义。

aSong = KaraokeSong.new("My Way", "Sinatra", 225, "And now, the...")
aSong.to_s }} "Song: My Way--Sinatra (225)"

这个类已经可以工作了，但是to_s没有显示歌词信息。

这和ruby如何决定调用哪个方法的机制有关。当ruby看到这个 aSong.to_s方法调用，它并不需要知道去哪里找to_s这个方法，而是要在程序运行到此的时候再去调用这个函数。开始在aSong里面找，如果这个类里面定义了一个和发送给这个对象的消息一样名称的方法的话，就运行这个方法。否则，就会到这个类的父类去找，如果还没找到，再到父类的父类去找。这样一直找到祖先Object。如果找到最高层还没有找到这个方法，一般会返回一个错误。[实际上，你可以拦截这个错误，你可以在运行时弥补这个错误，见 Object#method_missing ]

现在再回到我们的例子，我们向aSong发送了一个消息to_s，在karaokeSong这个类里，ruby找不到to_s这个方法，所以，再去karaokeSong的父类Song去找。在父类里发现了这个方法，所以就执行这个方法，所以，它只打印了除了歌词的信息。类Song一点都不知道lyrics的存在。

我们可以在这里实现这个方法来弥补这个不足，又很多方法可以实现这个方法，我们先来看一下一个不是很好的例子，从Song的to_s拷贝出来一些代码，然后加上lyric信息。

class KaraokeSong
  # ...
  def to_s
    "KS: #{@name}--#{@artist} (#{@duration}) [#{@lyrics}]"
  end
end
aSong = KaraokeSong.new("My Way", "Sinatra", 225, "And now, the...")
aSong.to_s }} "KS: My Way--Sinatra (225) [And now, the...]"

我们正确地显示了@lyrics 这个实例变量，但是这样做直接在子类里访问了父类的实例变量，为什么这样实现to_s方法不好呢？

这和好的编程风格有关（可以称作decoupling）
The answer has to do with good programming style (and something called ). By poking around in our parent's internal state, we're tying ourselves tightly to its implementation. Say we decided to change Song to store the duration in milliseconds. Suddenly, KaraokeSong would start reporting ridiculous values. The idea of a karaoke version of ``My Way'' that lasts for 3750 minutes is just too frightening to consider.

我们需要每个类只操作自己内部的状态，当KaraokeSong#to_s 被调用的时候，先在KaraokeSong#to_s调用父类的to_s方法，然后在加上lyric信息返回给调用者。这里需要ruby的关键字"super"。当你不带参数调用super时，ruby向父类发送消息，调用父类的同名函数（即和子类同名的函数），传递给当前类方法的参数会默认的传给父类。比如改写后如下：

class KaraokeSong < Song
  # Format ourselves as a string by appending
  # our lyrics to our parent's #to_s value.
  def to_s
    super + " [#{@lyrics}]"
  end
end
aSong = KaraokeSong.new("My Way", "Sinatra", 225, "And now, the...")
aSong.to_s }} "Song: My Way--Sinatra (225) [And now, the...]"

我们显示的声明了KaraokeSong是Song的一个子类，但是并没有说明Song的父类。如果定义一个类时没有指定父类，默认为Object为它的父类。也就是说，所有的类的祖先都是Object类，而且Object的实例方法在子类中也是可以访问的。比如to_s是ruby中大概35个实例方法之一，这些方法列表后面可以看到。

继承和 Mixins

像c++这样的面向对象语言都支持多重继承，也就是说一个类可以有多个父类，从每个类继承特性。尽管很有效，但它有时候很危险，有可能产生混乱。

其他一些语言，比如java，支持单继承，一个类只能有一个父类，尽管清洗明了，容易实现，但是也有一些缺点，因为事实上一个事务同时具备很多种事务的特征。比如一个球，既是球形的东西，也是能弹跳的东西。

ruby采取了有趣而强大的折中办法，你能轻松的实现单继承和多继承。一个ruby只能有一个直接父类，是单继承语言，但是ruby类可以包含其他的mixin(mixin可以看作是一个部分类定义a partial class definition)中的一些功能，从而引入附加的功能，以这种方式实现了多重继承，并且不会出现多继承语言中的问题。

上面我们已经看到了类和方法，下面来看看对象，也就是类的实例。

对象和属性

Song对象有一些内部属性，比如名称和演唱者，这些属性都是私有的，其他对象都不能直接访问。一般来说，这样是不错的设计，每个对象只负责自己的完整性，一致性。

但是，如果把对象装饰的这么秘密将会使这些对象变得毫无作用，我们能创建它，但是我们不能修改它的属性。所以，我们可以定义一些方法，通过这些方法，外部对象可以访问，修改对象的属性。这些从外面看起来表现叫做属性（attributes）。

对于我们Song对象，我们可能需要访问它的名字和演唱者，以便在播放的时候打印出来，还有它的时长（可以用类似进度条来显示）。

class Song
def name
@name
end
def artist
@artist
end
def duration
@duration
end
end
aSong = Song.new("Bicylops", "Fleck", 260)
aSong.artist | }} | "Fleck"
aSong.name | }} | "Bicylops"
aSong.duration | }} | 260

这里，我们定义了三个访问方法，每个方法返回一个实例属性。在实际中，这些操作很普遍，所以ruby提供了一个方便的方法：用attr_reader，它将为我们自动创建访问方法。

class Song
attr_reader :name, :artist, :duration
end
aSong = Song.new("Bicylops", "Fleck", 260)
aSong.artist | }} | "Fleck"
aSong.name | }} | "Bicylops"
aSong.duration | }} | 260

这个例子引入了一些新东西，比如 ":artist"可以当作一个表达式，返回一个指向artist的符号链接。也可以把":artist"当作是artist的名字。这个例子里，我们定义了三个访问方法： name, artist, duration。而实例变量@name, @artist, @duration会自动创建。这样定义访问方法和我们上面写的一样。

可写属性

有时候需要在外部对对象的属性进行修改。比如，一首歌的时长这个属性可能开始的时候只是一个估算的值，当第一次播放的时候，我们知道了它的真正时长，并且要把它写回到Song这个对象。

在c++或者java中，我们可以用setter方法。

class JavaSong {                     // Java code
  private Duration myDuration;
  public void setDuration(Duration newDuration) {
    myDuration = newDuration;
  }
}
s = new Song(....)
s.setDuration(length)

在Ruby中，可以象其他变量一样访问属性,比如上面我们调用了aSong.name ，所以我们也应该像变量一样给属性赋值。在ruby中，这样做就行：

class Song
def duration=(newDuration)
@duration = newDuration
end
end
aSong = Song.new("Bicylops", "Fleck", 260)
aSong.duration | }} | 260
aSong.duration = 257 # set attribute with updated value
aSong.duration | }} | 257

赋值语句"aSong.duration = 257"调用了aSong中的方法duration= 参数为257 。实际上，一个方法名以=结尾，就像这个属性出现左边的赋值语句一样。同样，ruby也为创建可写属性提供了一个快捷方式

class Song
  attr_writer :duration
end
aSong = Song.new("Bicylops", "Fleck", 260)
aSong.duration = 257

虚拟属性

这些属性访问方法不是对一个对象的实例变量的包装，比如，你需要得到以分钟为单位的时长，而不是以秒为单位：

class Song
def durationInMinutes
@duration/60.0 # force floating point
end
def durationInMinutes=(value)
@duration = (value*60).to_i
end
end
aSong = Song.new("Bicylops", "Fleck", 260)
aSong.durationInMinutes | }} | 4.333333333
aSong.durationInMinutes = 4.2
aSong.duration | }} | 252

这里我们用属性方法建立了一个虚拟的实例变量，对于外面来说durationInMinutes可以看作和其他一样的属性，但实际上，并没有与之对应的实例变量。

这并不止是有趣而已，在Bertrand Meyer 的杰作《Object-Oriented Software Construction》 中，作者称这叫做统一访问原则（Uniform Access Principle）。通过把这些实例变量和他们计算之后的值隐藏起来，你就可以不用在自己的实现里来处理这些问题，而且，当需要改动的时候，你只需要改动一个文件，而不是很多文件。

类变量和类方法

到目前为止我们讨论的都是实例变量和实例方法，这些变量术语每个不同的对象，用方法来操作，有时候，类也可能需要自己的状态，所以引入了类变量。

Class Variables

一个类变量被所有它的对象实例共享，也可以被下面要提到的类方法修改。在系统中，类变量只有一个拷贝。类变量名以两个at 即"@@"开头，比如"@@count"。不想全局变量和实例变量，类变量在使用之前必须被初始化。通常初始化只是类定一中的一条赋值语句。

比如，在我们的自动点唱机中，我们想记录一个指定的歌曲播放过多少次，这个次数应该是一个song实例变量，每当这个Song被播放，这个变量都要加1。如果，我们还想计算所有歌曲总共播放了多少次，我们可以找到所有Song对象，然后累加他们的播放次数，或者用全局变量，相反，这里我们用了类变量。

class Song
  @@plays = 0
  def initialize(name, artist, duration)
    @name     = name
    @artist   = artist
    @duration = duration
    @plays    = 0
  end
  def play
    @plays += 1
    @@plays += 1
    "This  song: #@plays plays. Total #@@plays plays."
  end
end

为了调试方便， Song#play方法返回了一个字符串，显示了这首歌播放过多少次，和所有歌曲的总的播放次数。

s1 = Song.new("Song1", "Artist1", 234) # test songs..
s2 = Song.new("Song2", "Artist2", 345)
s1.play | }} | "This song: 1 plays. Total 1 plays."
s2.play | }} | "This song: 1 plays. Total 2 plays."
s1.play | }} | "This song: 2 plays. Total 3 plays."
s1.play | }} | "This song: 3 plays. Total 4 plays."

类变量属于类和它的实例私有，如果你想在外面访问它，需要编写访问方法，既可以是实例的访问方法，也可以是下面我们要说到的类的访问方法。

Class Methods

有时候，一个类需要提供一个不需要任何类实例就能使用的方法。我们已经见过一个这样的方法了，new方法创建了一个Song对象，但是它不属于Song类。

aSong = Song.new(....)

你将会发现类方法贯穿于ruby的库文件之中。比如，File类的对象表示一个打开的文件，但是File也提供了几个类方法，比如删除文件，我们不需要打开文件，直接调用 File.delete ，提供要删除的文件名就行了。

File.delete("doomedFile")

类方法和实例方法定义时候是不一样的，类方法定义的时候要加上类名：

class Example

  def instMeth              # instance method
  end

  def Example.classMeth     # class method
  end

end

我们的自动点唱机是要收钱的，按歌曲数量而不是时间来收，所以提供长的歌曲不如提供短的歌曲效益高。我们不希望在SongList中出现太长的歌曲，所以我们在SongList里面定一个类方法，判断一首歌是否超过了规定的长度。这个长度存在一个常量里面（常量以大写字母开头），并且在类体里面初始化这个常量。

class SongList
MaxTime = 5*60 # 5 minutes
def SongList.isTooLong(aSong)
return aSong.duration > MaxTime
end
end
song1 = Song.new("Bicylops", "Fleck", 260)
SongList.isTooLong(song1) | }} | false
song2 = Song.new("The Calling", "Santana", 468)
SongList.isTooLong(song2) | }} | true

单例（Singletons）和其他构造函数

有时候，你想改变缺省的对象的创建方式，比如，对于我们的点唱机系统，我们又很多点唱机，遍布全国，我们想尽可能的使他容易维护，所以我们需要记录点唱机发生的所有事情，比如一首歌被播放了，收钱了等，所以我们需要一个日志类。因为我们想把带宽留给音乐数据，所以日志记录在本机。我们想一个点唱机系统只有一个log类，并被系统中的所有类共有使用。

通过使用单例模式，要想使用这个log类，只有一种创建方法：Logger.create,并且确保系统中只有一个log的实例存在。

class Logger
  private_class_method :new
  @@logger = nil
  def Logger.create
    @@logger = new unless @@logger
    @@logger
  end
end

我们把logger类的new方法设成了私有的，这样就不能用Looger.new来创建logger对象了。我们提供了一个类方法 Logger.create ，用到了类变量 @@logger ，这是一个指向logger类实例的引用。可以看到，如果实例已经创建了，这个方法直接返回已经创建的实例，不会再创建第二个。[这里的实现是非线程安全的，如果有多个线程来访问这个函数，可能会出产生多个logger对象。我们可以用ruby提供的Singleton mixin来解决，而不必自己处理线程安全问题。]我们可以检查一下这两个方法的返回情况。

Logger.create.id | }} | 537766930
Logger.create.id | }} | 537766930

用类方法来包装构造函数，也可以让使用你的类的人感到轻松。比如我们的类Shape代表一个多边形，构造函数接收边数和周长：

class Shape
  def initialize(numSides, perimeter)
    # ...
  end
end

但是，多年以后，使用方法变了，现在需要提供shape的名称，边数，和边长而不是周长。而我们只需要加几个类方法就行了：

class Shape
  def Shape.triangle(sideLength)
    Shape.new(3, sideLength*3)
  end
  def Shape.square(sideLength)
    Shape.new(4, sideLength*4)
  end
end

类方法还有很多强大有趣的特性，但是目前我们还是要继续我们现在的内容。

Access Control

我们设计一个类的接口的时候，一个重要的问题是，我们应该向外界暴露多少内部实现，外部能访问我们的类有多少限制。如果过多的让外部访问内部的东西，可能增加了耦合，用户越来越依赖我们的类的内部实现，而不是逻辑接口。因为我们要改变一个实例的状态需要调用这个实例的相关方法，控制对实例的方法的访问，就能避免对对象实例的状态的直接修改。Ruby提供了三种保护层次：
  • Public methods 任何人都可以访问，没有访问控制。方法默认都是public（initialize除外）。
  • Protected methods 可以在本类或者子类中调用。访问控制在家族内。
  • Private methods 不能用显示的接收者来调用。cannot be called with an explicit receiver. Because you cannot specify an object when using them, private methods can be called only in the defining class and by direct descendents within that same object.

"protected"和"private"两者的区别非常微妙，在ruby中，两者的关系和在其他语言中是不一样的。如果一个方法是protected的，它可以在定义它的实例或者子类的实例来调用。如果一个方法是"private"的，只可以在这个方法所处的对象中被使用，不能直接调用另一个对象的private方法。甚至这个对象就是调用者本身。
Ruby和其他oo语言另一个重要的不同点在于，ruby动态确定访问控制，在程序运行而不是静止时，只有你运行到那一行，才会去判断是否出错。

指定访问控制

在一个类或者模块定义中设定方法的访问控制层次： public, protected,private。有两种定义方法。

如果不带参数使用 public/protected/private，那么这后面的方法默认都是指定的值，比如一行写了private，那么后面的方法默认都是private，除非指定了另外的访问控制符。

class MyClass

      def method1    # default is 'public'
        #...
      end

  protected          # 后面方法将是 'protected'

      def method2    # will be 'protected'
        #...
      end

  private            # 后面方法将是 'private'

      def method3    # will be 'private'
        #...
      end

  public             # subsequent methods will be 'public'

      def method4    # and this will be 'public'
        #...
      end
end

另一种方法，定义方法的时候不指定访问控制符，而是将相关的方法列在对应的访问控制后面，比如：

class MyClass

  def method1
  end

  # ... and so on

  public    :method1, :method4
  protected :method2
  private   :method3
end

initialize方法自动声明为private型。

现在来看看一个例子。假如我们有一个记帐系统，每一个借方（debit）对应一个贷方（credit），我们要求都必须遵守这个规则，所以我们把debit和credit的方法设成private，提供了一个外部接口来处理：

class Accounts

  private

    def debit(account, amount)
      account.balance -= amount
    end
    def credit(account, amount)
      account.balance += amount
    end

  public

    #...
    def transferToSavings(amount)
      debit(@checking, amount)
      credit(@savings, amount)
    end
    #...
end

Protected access is used when objects need to access the internal state of other objects of the same class. For example, we may want to allow the individual Account objects to compare their raw balances, but may want to hide those balances from the rest of the world (perhaps because we present them in a different form).

class Account
  attr_reader :balance       # accessor method 'balance'

  protected :balance         # and make it protected

  def greaterBalanceThan(other)
    return @balance > other.balance
  end
end

Because the attribute balance is protected, it's available only within Account objects.

变量Variables

变量用来跟踪一个对象的状态，是指向其他对象的一个引用 。

person = "Tim"
person.id | }} | 537771100
person.type | }} | String
person | }} | "Tim"

第一行，我们创建了一个新字符串对象"Tim",person是指向这个字符串对象的一个引用，下两行的测试语句显示了这个对象的类型和id，最后显示了它的值。

但变量是对象吗？

在ruby中，答案是否定的。一个变量只是指向一个对象的引用。这些对象可能正在某地，比如堆栈中，变量只是指向了这些对象。
我们再看看稍微负责的例子

person1 = "Tim"
person2 = person1
person1[0] = 'J'
person1 | }} | "Jim"
person2 | }} | "Jim"

我们改变了person1的第一个字符，但是第二个也跟着改了。这是因为变量只是指向一个对象的引用，而不是对象本身。赋值语句person2 = person1 没有创建新的对象，只是person2拷贝了person1的引用而已。所以person1和person2都指向了同一个对象。

有时候赋值语句只是为对象建立了别名，而潜在的创建了新的变量指向同一个对象。这在我们的代码中可能会引起问题，但并不像你想的那么容易出错。我们可以用String的dup方法，这个方法会创建一个新的字符串对象，并且内容和消息接受者（方法执行者）一样。

person1 = "Tim"
person2 = person1.dup
person1[0] = "J"
person1 | }} | "Jim"
person2 | }} | "Tim"

如果你不想别人修改一个对象，也可以冻结（freezing）这个对象。如果你修改一个被冻结的对象，ruby会抛出一个TypeError异常。

person1 = "Tim"
person2 = person1
person1.freeze       # prevent modifications to the object
person2[0] = "J"
produces:
prog.rb:4:in `=': can't modify frozen string (TypeError)
    from prog.rb:4

Extracted from the book "Programming Ruby - The Pragmatic Programmer's Guide"
Copyright © 2001 by Addison Wesley Longman, Inc. This material may be distributed only subject to the terms and conditions set forth in the Open Publication License, v1.0 or later (the latest version is presently available at http://www.opencontent.org/openpub/)).

Distribution of substantively modified versions of this document is prohibited without the explicit permission of the copyright holder.

Distribution of the work or derivative of the work in any standard (paper) book form is prohibited unless prior permission is obtained from the copyright holder.


Programming Ruby: The Pragmatic Programmer's Guide

Programming Ruby

The Pragmatic Programmer's Guide

上一章< 目录^
下一章 >

容器，块（ Blocks）和迭代器（ Iterator）

一个自动点唱机只有一首歌恐怕不会太流行，所以我们需要建立一个歌曲目录和一个等待播放的列表。这都是容器的例子，一个包含若干个对其它对象引用的对象。

目录和播放列表都有类似的操作，增加歌曲，删除歌曲，返回歌曲列表等等。播放列表可能还需要别的方法，比如插入广告，记录累计播放时间等，我们将在后面考率这些问题。现在，我们需要建立一个SongList类，以便在目录和播放列表中使用。

容器

在开始实现之前，我们要知道怎么在SongList中存储歌曲列表。我们有三个明显得选择：Ruby的提供的Array，Ruby提供的Hash，或者我们自己创建一个列表结构。下面我们来看看数组合和哈希，然后选择一个来在我们的类中使用。

数组Arrays

数组类包含了若干个对其它对象的引用，每个引用在数组的一个位置上，用一个正整数来索引。

你可以直接指定一组值来创建一个数组，或者用数组类的new方法。用第一种方法创建数组要用中括号把各个元素括起来，每个元素之间用逗号隔开。

a = [ 3.14159, "pie", 99 ]
a.type | ? | Array
a.length | ? | 3
a[0] | ? | 3.14159
a[1] | ? | "pie"
a[2] | ? | 99
a[3] | ? | nil
b = Array.new
b.type | ? | Array
b.length | ? | 0
b[0] = "second"
b[1] = "array"
b | ? | ["second", "array"]

数组指定索引的下标用[ ]操作符，这实际上也是一个方法，可以在子类中被重载。数组索引从0开始，从左向右技术，如果指定的索引为负数，则表示从右边开始计数（这时候最右边的索引下标为-1，不是0）。

a = [ 1, 3, 5, 7, 9 ]
a[-1] | ? | 9
a[-2] | ? | 7
a[-99] | ? | nil

你也可以一对数字[start, count]作为下标，这将返回一个数组，包含原数组中从start开始的count个元素。

a = [ 1, 3, 5, 7, 9 ]
a[1, 3] | ? | [3, 5, 7]
a[3, 1] | ? | [7]
a[-3, 2] | ? | [5, 7]

最后，你也可以在数组下标中使用Range作为索引。Range可参看下一章。Range的起始值之间如果是2个点号，则包括最后的结束值，如果是3个点号，则Range的值不包括最后的那个边界值。

a = [ 1, 3, 5, 7, 9 ]
a[1..3] | ? | [3, 5, 7]
a[1...3] | ? | [3, 5]
a[3..3] | ? | [7]
a[-3..-1] | ? | [5, 7, 9]

[ ]方法有一个对应的 []= 方法，这个方法是用来给指定的位置设置新的值，如果数组的下标位置超过了数组的大小，那么指定的下标将被设为指定的值，而中间没有涉及到的位置为设为nil。如下面例子数组最大下标为4，而对a[6]设置新值，a[5]则被设为nil。

a = [ 1, 3, 5, 7, 9 ] | ? | [1, 3, 5, 7, 9]
a[1] = 'bat' | ? | [1, "bat", 5, 7, 9]
a[-3] = 'cat' | ? | [1, "bat", "cat", 7, 9]
a[3] = [ 9, 8 ] | ? | [1, "bat", "cat", [9, 8], 9]
a[6] = 99 | ? | [1, "bat", "cat", [9, 8], 9, nil, 99]

如果在[]= 方法中下标索引为两个数字（一个开始位置，一个长度）或者一个Range，则对应的一系列值将被重新设置。如果第二个参数即长度为0，则右边的值将被插入当前位置，没有值会被删除。如果[ ]=右边的长度和左边下标中指定的长度不一样，则原数组自动进行调整。

a = [ 1, 3, 5, 7, 9 ] | ? | [1, 3, 5, 7, 9]
a[2, 2] = 'cat' | ? | [1, 3, "cat", 9]
a[2, 0] = 'dog' | ? | [1, 3, "dog", "cat", 9]
a[1, 1] = [ 9, 8, 7 ] | ? | [1, 9, 8, 7, "dog", "cat", 9]
a[0..3] = [] | ? | ["dog", "cat", 9]
a[5] = 99 | ? | ["dog", "cat", 9, nil, nil, 99]

数组有很多有用的方法，使用这些方法你能用数组实现队列，堆栈，列表等各种数据结构。

哈希Hashes

Hashes (sometimes known as associative arrays or dictionaries) are similar to arrays, in that they are indexed collectives of object references.

However, while you index arrays with integers, you can index a hash with objects of any type: strings, regular expressions, and so on. When you store a value in a hash, you actually supply two objects---the key and the value. You can subsequently retrieve the value by indexing the hash with the same key. The values in a hash can be any objects of any type. The example that follows uses hash literals: a list of key => value pairs between braces.

h = { 'dog' => 'canine', 'cat' => 'feline', 'donkey' => 'asinine' }
h.length | ? | 3
h['dog'] | ? | "canine"
h['cow'] = 'bovine'
h[12] = 'dodecine'
h['cat'] = 99
h | ? | {"cow"=>"bovine", "cat"=>99, 12=>"dodecine", "donkey"=>"asinine", "dog"=>"canine"}

Compared with arrays, hashes have one significant advantage: they can use any object as an index. However, they also have a significant disadvantage: their elements are not ordered, so you cannot easily use a hash as a stack or a queue.

You'll find that hashes are one of the most commonly used data structures in Ruby. A full list of the methods implemented by class Hash starts on page 317.

Implementing a SongList Container

After that little diversion into arrays and hashes, we're now ready to implement the jukebox's SongList. Let's invent a basic list of methods we need in our SongList. We'll want to add to it as we go along, but it will do for now.

append( aSong ) ? list
Append the given song to the list.
deleteFirst() ? aSong
Remove the first song from the list, returning that song.
deleteLast() ? aSong
Remove the last song from the list, returning that song.
[ anIndex } ? aSong
Return the song identified by anIndex, which may be an integer index or a song title.

This list gives us a clue to the implementation. The ability to append songs at the end, and remove them from both the front and end, suggests a dequeue---a double-ended queue---which we know we can implement using an Array. Similarly, the ability to return a song at an integer position in the list is supported by arrays.

However, there's also the need to be able to retrieve songs by title, which might suggest using a hash, with the title as a key and the song as a value. Could we use a hash? Well, possibly, but there are problems. First a hash is unordered, so we'd probably need to use an ancillary array to keep track of the list. A bigger problem is that a hash does not support multiple keys with the same value. That would be a problem for our playlist, where the same song might be queued up for playing multiple times. So, for now we'll stick with an array of songs, searching it for titles when needed. If this becomes a performance bottleneck, we can always add some kind of hash-based lookup later.

We'll start our class with a basic initialize method, which creates the Array we'll use to hold the songs and stores a reference to it in the instance variable @songs.

class SongList
  def initialize
    @songs = Array.new
  end
end

The SongList#append method adds the given song to the end of the @songs array. It also returns self, a reference to the current SongList object. This is a useful convention, as it lets us chain together multiple calls to append. We'll see an example of this later.

class SongList
  def append(aSong)
    @songs.push(aSong)
    self
  end
end

Then we'll add the deleteFirst and deleteLast methods, trivially implemented using Array#shift and Array#pop , respectively.

class SongList
  def deleteFirst
    @songs.shift
  end
  def deleteLast
    @songs.pop
  end
end

At this point, a quick test might be in order. First, we'll append four songs to the list. Just to show off, we'll use the fact that append returns the SongList object to chain together these method calls.

list = SongList.new
list.
  append(Song.new('title1', 'artist1', 1)).
  append(Song.new('title2', 'artist2', 2)).
  append(Song.new('title3', 'artist3', 3)).
  append(Song.new('title4', 'artist4', 4))

Then we'll check that songs are taken from the start and end of the list correctly, and that nil is returned when the list becomes empty.

list.deleteFirst | ? | Song: title1--artist1 (1)
list.deleteFirst | ? | Song: title2--artist2 (2)
list.deleteLast | ? | Song: title4--artist4 (4)
list.deleteLast | ? | Song: title3--artist3 (3)
list.deleteLast | ? | nil

So far so good. Our next method is [], which accesses elements by index. If the index is a number (which we check using Object#kind_of? ), we just return the element at that position.

class SongList
  def [](key)
    if key.kind_of?(Integer)
      @songs[key]
    else
      # ...
    end
  end
end

Again, testing this is pretty trivial.

list[0] | ? | Song: title1--artist1 (1)
list[2] | ? | Song: title3--artist3 (3)
list[9] | ? | nil

Now we need to add the facility that lets us look up a song by title. This is going to involve scanning through the songs in the list, checking the title of each. To do this, we first need to spend a couple of pages looking at one of Ruby's neatest features: iterators.

块和迭代器Blocks and Iterators

下面，我们要在SongList中修改[ ] 方法，使它能接受一个字符串参数，返回以此为标题的歌曲的。看起来我们很容易可以实现：我们有一个包含了很多Song对象的对象的数组，我们只需循环遍历整个数组，找到匹配的那个就可以了。

class SongList
  def [](key)
    if key.kind_of?(Integer)
      return @songs[key]
    else
      for i in 0...@songs.length
        return @songs[i] if key == @songs[i].name
      end
    end
    return nil
  end
end

这样已经可以工作了，而且看上去很符合常规：用一个for循环来遍历数组。

有没有更自然的方法呢？

当然有，我们可以用Array的find方法。

class SongList
  def [](key)
    if key.kind_of?(Integer)
      result = @songs[key]
    else
      result = @songs.find { |aSong| key == aSong.name }
    end
    return result
  end
end

我们可以用if修饰符来使代码更简短一些。

class SongList
  def [](key)
    return @songs[key] if key.kind_of?(Integer)
    return @songs.find { |aSong| aSong.name == key }
  end
end

方法find就是一个迭代器，这个方法重复不断地执行一个给定的block。块和迭代器都是Ruby中比较有趣的特点。我们后面会进一步来讨论这些特点。

实现迭代器

一个Ruby迭代器就是一个简单的能接收代码块的方法。第一眼看上去，Ruby中的block像C，Java，Perl中的一样，但是实际上是有不同的。

首先，块在源代码中紧挨着方法调用，并且和这个方法的最后一个参数写在同一行上。其次，这个块不会立即被执行，Ruby首先会记住这个块出现的上下文（局部变量，当前对象等），然后进入方法，这里也是魔术开始的地方。

在方法里面，这个块才会用yield来调用执行，就像这个块是方法本身一样，每当yield在方法中被执行，这个块就会被调用。当这个块执行完退出后，控制将交给yield后面的语句（yield来自一个有20多年历史的语言：CLU）。我们来看一个小例子。

def threeTimes
  yield
  yield
  yield
end
threeTimes { puts "Hello" }
produces:
Hello
Hello
Hello

这个块（用两个大括号定义）赋给了一个方法threeTimes，在这个方法里面，yield执行了3次，每次执行它都会调用给定的block，即打印一个欢迎语句。使块变得有趣的是你可以给块传递参数，并且从块中得到结果。下面例子，我们将会得到小于一个指定值得Fibonacci 数列。

def fibUpTo(max)
  i1, i2 = 1, 1        # parallel assignment
  while i1 <= max
    yield i1
    i1, i2 = i2, i1+i2
  end
end
fibUpTo(1000) { |f| print f, " " }
produces:
1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987

在这个例子中，yield接收一个参数，这个参数将会在执行的时候传递给指定的块。在块的定义中，参数用两个竖线括起来，放在最前面。在这个例子中f用来接收yield传递的参数，所以，这个块才能打印这个序列。一个块可以接受任意个参数。如果一个块的参数和yield中传递的参数个数不一样，将会怎样呢？很巧合，这和我们在并行赋值（parallel assignment）中谈到的原则一样（如果一个block只接收一个参数，而yield提供的参数多于1个，那么这些参数将被转化为一个数组。）

传递给一个块的参数可以是存在地局部变量，如果是这样的话，那么这个局部变量的新值（如果在块中被修改了）在块退出后将会保留，这可能会有一定的副作用，但是这样做有一个性能方面的考率。

一个块也可以返回一个结果给调用它的方法。这个块中的最后一个表达式的值将会返回给方法，Array中的find方法就是这样工作的。（find在Enumerable中定义，被插入到了类Array）

class Array
  def find
    for i in 0...size
      value = self[i]
      return value if yield(value)
    end
    return nil
  end
end
[1, 3, 5, 7, 9].find {|v| v*v > 30 } ? 7

这个用法中数组将连续的元素传递给指定的块，如果这个块返回true，则这个方法返回当前对应的元素值，如果没有符合的值，则返回nil。这个方法显示了迭代器的好处，Array类只作自己应该做的，访问数组元素，而应用代码只关注于特殊的需求。

Ruby中的集合对象中也包含其它一些常用迭代器，其中之二是each和collect。each可以认为是最简单的迭代器，它们都会对集合的每个元素来调用块。

[ 1, 3, 5 ].each { |i| puts i }
produces:
1
3
5

另一个是collect，它跟each类似，它将集合中的元素传递给一个块，在块中处理后返回一个包含处理结果的新数组。

["H", "A", "L"].collect { |x| x.succ } ? ["I", "B", "M"]

Ruby 和 C++ 、 Java的比较

这值得我们再花一些时间来比较一下Ruby，C＋＋,JAVA中的迭代器。在Ruby中，迭代器是一个简单的方法，每当它产生一个新值，都会调用yield方法。使用迭代器只需要给这个迭代器传递一个块，不需要像C＋＋，Java中那样创建辅助类来处理迭代器的状态。从这一点和其它一些特点来说，Ruby是一种透明语言，当你写程序的时候，你只需关注于让功能能够实现，而不必编写脚手架来支持语言的一些功能。

迭代器不仅仅用在数组和哈希等集合结构上，它也能返回上面Fibonacci 例子中那样的序列值，Ruby中的输入输出类也用到了迭代器，这些类实现了迭代器接口，每次返回一个I/O流的下一行。

f = File.open("testfile")
f.each do |line|
  print line
end
f.close
produces:
This is line one
This is line two
This is line three
And so on...

让我们再看看另外一个迭代器实现。Smalltalk也支持迭代器，如果你用smalltalk语言来计算一个数组中元素的和，可以这样：

sumOfValues              "Smalltalk method"
    ^self values
          inject: 0
          into: [ :sum :element | sum + element value]

inject 这样工作：第一次指定的block被执行的时候，sum被设成inject的参数（本例为0），element被设为数组的第一个元素。以后block被执行的时候，sum的值设为上次block执行后返回的值。这样，sum就可以记录总数了最后的inject的值是block最后一次执行后返回的值。

Ruby 没有inject 方法，但我们可以很容易的写一个，在这个例子中我们把它加入Array类。

class Array
def inject(n)
each { |value| n = yield(n, value) }
n
end
def sum
inject(0) { |n, value| n + value }
end
def product
inject(1) { |n, value| n * value }
end
end
[ 1, 2, 3, 4, 5 ].sum | ? | 15
[ 1, 2, 3, 4, 5 ].product | ? | 120

尽管block经常是用在迭代器中，但是，块也有其它一些有用的用处。

在事务处理中使用块

block也可以用作定义一块代码，这些代码必须在一定的事务控制下运行。比如，你经常会打开一个文件，对内容作一些处理，然后需要确保文件在最后会被关闭。尽管我们可以用常规的方法实现，但是，我们可以让文件对象自己负责关闭它。一个简单例子如下（忽略了错误处理等）：

class File
  def File.openAndProcess(*args)
    f = File.open(*args)
    yield f
    f.close()
  end
end

File.openAndProcess("testfile", "r") do |aFile|
  print while aFile.gets
end
produces:
This is line one
This is line two
This is line three
And so on...

这个小例子阐述了几个技术点。方法openAndProcess 是一个类方法，这个方法可以独立于任何File对象来单独调用，即不需要生成类的实例。我们在这个方法的参数列表中使用了 *args，这表示所有调用时候的参数将作为数组传递到这个方法。而这个参数是传递给File.open方法的。

一旦这个文件被打开，openAndProcess 将调用yield，然后将打开的文件对象传递给这个block。当block返回后，这个文件将被关闭。这种情况下，关闭文件的任务就从使用文件对象的用户转变为文件本身了。

最后，这个例子用do..end来定义一个块，这和用两个打括号定义一个块只是有优先级别的区别，将在后面讨论。

让文件自己管理自己的生命周期十分有用，Ruby自带的File类就提供了这样的支持。如果File.open调用时指定了一个block，那么这个块就会用传递过来文件对象作为参数执行，然后，当块结束后，这个文件会被文件对象关闭。这很有趣，也就是说File.open方法有两个版本，一个接受block，一个不接受block。当没有指定block调用这个方法的时候，这个方法只会返回一个打开的文件对象。方法Kernel::block_given?提供了实现这种功能的可能，如果调用的时候给了一个block，这个方法将返回true，这样，你也可以实现自己open方法：

class File
  def File.myOpen(*args)
    aFile = File.new(*args)
    # If there's a block, pass in the file and close
    # the file when it returns
    if block_given?
      yield aFile
      aFile.close
      aFile = nil
    end
    return aFile
  end
end

块和闭包

让我们再回来看看自动点唱机，某些时候，我们需要处理点唱机和用户的界面：很多按钮，供用户选择歌曲和控制播放，我们需要给这些按钮指定相应的时间处理代码，而Ruby中的block就很适合干这样的事情。假设点唱机的设计者通过Ruby扩展为我们提供了一个基本的按钮类。

bStart = Button.new("Start")
bPause = Button.new("Pause")
# ...

当用户按下按钮之后如何处理呢？Button类提供了一个buttonPressed方法，在按钮按下时能被回调。所以最简单的方法创建Button的子类，然后在每个子类中实现buttonPressed 方法。

class StartButton < Button
  def initialize
    super("Start")       # invoke Button's initialize
  end
  def buttonPressed
    # do start actions...
  end
end

bStart = StartButton.new

这样做有两个问题，首先这样将产生大量子类，如果Button变化了，所有子类都需要维护；第二，按钮按下之后需要执行的行为不应该在按钮上表示，这是点唱机的责任。我们可以用块来消除这些问题

class JukeboxButton < Button
  def initialize(label, &action)
    super(label)
    @action = action
  end
  def buttonPressed
    @action.call(self)
  end
end

bStart = JukeboxButton.new("Start") { songList.start }
bPause = JukeboxButton.new("Pause") { songList.pause }

上面代码的关键点在JukeboxButton#initialize的第二个参数，这个参数前面带有一个&符号，在Ruby中代表这个参数是一个块，这个块将会被转化为一个Proc对象，并关联道相应变量。在本例中我们把它赋给实例变量@action 。当回调方法buttonPressed 执行时，我们用方法Proc#call来调用相应的block。

当创建一个Proc对象之后我们得到的是什么呢？很有趣，我们得到的不仅是一串代码。和这个block定义相关联的上下文环境（范围内的）：self值，定义它的方法，变量，常量等。Ruby中的一个有趣的地方就是即使block定义时候的变量已经不在它的使用范围内，这个变量还保留着，仍可以使用

我们来看一个例子，这个例子用了方法proc，这个方法把一个block变为了一个Proc对象。

def nTimes(aThing)
return proc { |n| aThing * n }
end
p1 = nTimes(23)
p1.call(3) | ? | 69
p1.call(4) | ? | 92
p2 = nTimes("Hello ")
p2.call(3) | ? | "Hello Hello Hello "

方法nTimes 返回一个Proc对象，这个对象引用了这个函数的参数：aThing 。即使这个参数在块被调用时已经不在自己的作用域里了，这个块还是可以访问这个参数。（比如p1.call(4)，这个时候，虽然对p1的赋值语句中的参数23已经超出了作用域范围，但是它仍然保存着23×4＝92。）

Extracted from the book "Programming Ruby - The Pragmatic Programmer's Guide"
Copyright © 2001 by Addison Wesley Longman, Inc. This material may be distributed only subject to the terms and conditions set forth in the Open Publication License, v1.0 or later (the latest version is presently available at http://www.opencontent.org/openpub/)).

Distribution of substantively modified versions of this document is prohibited without the explicit permission of the copyright holder.

Distribution of the work or derivative of the work in any standard (paper) book form is prohibited unless prior permission is obtained from the copyright holder.


Programming Ruby: The Pragmatic Programmer's Guide

Programming Ruby

实用程序员指南

上一章 < 目录 ^
下一章 >

标准类型

到现在我们已经实现了我们那点唱机的一部分代码，我们看到了数组，哈希，方法，但我们还没有涉及到Ruby中其他的数据类型：数字，字符串，范围（ranges），正则表达式。下面我们就要花些时间来看看这些类型。

数字型

Ruby 支持整型和浮点型两种数字类型。整型可以是任意长度（最大值由你机器的内存大小决定）。在一定范围内（通常是-230 to 230-1 or -262 to 262-1）在内部由二进制方式表示，内部类为Fixnum。大小超过这个范围的整数由Bignum表示，如果Fixnum计算之后结果超出范围，自动转换为Bignum。Ruby在两者之间自动转换，对用户来说是透明的。

num = 8
7.times do
  print num.type, " ", num, "\n"
  num *= num
end
produces:
Fixnum 8
Fixnum 64
Fixnum 4096
Fixnum 16777216
Bignum 281474976710656
Bignum 79228162514264337593543950336
Bignum 6277101735386680763835789423207666416102355444464034512896

你也可以在使用整型的时候在前面使用进制标示符，比如0表示八进制，0x表示十六进制，0b表示二进制等。而且，如果一个整型数字中有一个下划线，这个下划线将被忽略。

123456                    # Fixnum
123_456                   # Fixnum (underscore ignored)
-543                      # Negative Fixnum
123_456_789_123_345_789   # Bignum
0xaabb                    # Hexadecimal
0377                      # Octal
-0b101_010                # Binary (negated)

你也可以得到一个ASCII字符或者一个转意字符的数字值通过在它前面加一个问号。Control和Meta键的组合也可以用?\C-x, ?\M-x 和 ?\M-\C-x表达。字符value的加Control键的版本和"value & 0x9f"的值是一样的；字符value的加Meta键的版本和"value & 0x80"的值是一样的。最后，序列 ?\C-? 产生一个ASCII码的删除，0177。

?a                        # 字符的数字值
?\n                       # 换行符的值 (0x0a)
?\C-a                     # control a = ?A & 0x9f = 0x01
?\M-a                     # meta sets bit 7
?\M-\C-a                  # meta 和 control a
?\C-?                     # 删除字符

一个带小数点或者带指数的数字字符串会转换成一个Float对象，对应于本机操作系统构架的double数据类型。你必须在小数点后面加一个数字，因为像 1.e3会认为是调用了Fixnum类的e3这个方法。

所有数字都是对象，会响应一些消息（在290, 313, 315, 323和349页会完整的描述）。所以不像（比如说） C++，你会发现求一个数字的绝对值是这样写的 aNumber.abs，而不是abs(aNumber)。

整数也支持一些有用的迭代器（iterators）。我们已经看到过一个了--- 7.times在47页的代码例子中。还有其他的比如 upto 和 downto，用来在两个整数之间向上和向下迭代，还有 step，用于传统的 for 循环语句。

3.times        { print "X " }
1.upto(5)      { |i| print i, " " }
99.downto(95)  { |i| print i, " " }
50.step(80, 5) { |i| print i, " " }
会产生:
X X X 1 2 3 4 5 99 98 97 96 95 50 55 60 65 70 75 80

最后，给Perl使用者提供一个警告。含有数字字符的字符串在表达式中使用时不会自动转换成数字。这最有可能在从文件里读取数字时引发错误。下面的代码没有做我们 想要的。

DATA.each do |line|
  vals = line.split    #分割一行，把值存在vals里面
  print vals[0] + vals[1], " "
end

给它一个文件，内容是：

3 4
5 6
7 8

你会得到结果： ``34 56 78.'' 到底发生了什么呢？

问题就出在程序把输入当成字符串，而不是数字的。加号运算符把两个字符串连接，这就是我们看到这个结果的原因。要解决这个问题，我们可以使用String#to_i这个方法来把字符串转换成整数。

DATA.each do |line|
  vals = line.split
  print vals[0].to_i + vals[1].to_i, " "
end
得到结果:
7 11 15

字符串 Strings

Ruby的字符串是简单的8位字节（8-bit bytes）序列。它们通常保存可打印字符序列，但是这不是必须的；一个字符串也可以保存二进制数据。字符串是String类的对象。

字符串通常用字符常量建立---包括在分隔符里面的字符序列。因为二进制数据很难在程序代码里面表达，你可以在一个字符串常量里面使用各种转义字符。每个转义字符都会在程序编译的时候转换成相应的二进制数值。分隔符的种类表明了取代作用的程度。用单引号刮住的字符串里，两个连续的反斜杠会被一个反斜杠取代，一个反斜杠后面跟一个单引号变成一个单引号。

'escape using "\\"' | }} | escape using "\"
'That\'s right' | }} | That's right

用双引号刮住的字符串支持更多的转义字符。最常见的转义字符可能是"\n"了，代表一个换行符。第203页的表18.2列出了完整的转义字符。另外，你可以用 #{ expr } 来把任何的Ruby表达式的值插入到字符串中。如果那个表达式是全局变量，类变量或者实例变量，你可以省略大括号。

"Seconds/day: #{24*60*60}" | }} | Seconds/day: 86400
"#{'Ho! '*3}Merry Christmas" | }} | Ho! Ho! Ho! Merry Christmas
"This is line #$." | }} | This is line 3

还有三种方法来构建字符串常量：%q, %Q和“here documents.”

%q 和 %Q用来界定单引号和双引号的范围。

%q/general single-quoted string/ | }} | general single-quoted string
%Q!general double-quoted string! | }} | general double-quoted string
%Q{Seconds/day: #{24*60*60}} | }} | Seconds/day: 86400

跟在'q'或者'Q'后面的字符是分隔符，如果那个字符是括号，大括号，圆括号或者小于等于符号，那么程序会一直向下读直到遇见最近的停止符号，或者到匹配到相应的符号才停止，然后把读入的字符作为一个字符串整体。

最后，你可以用"here document"构建字符串。

aString = <<END_OF_STRING
    The body of the string
    is the input lines up to
    one ending with the same
    text that followed the '<<'
END_OF_STRING

一个 here document 由包含在开始到一个由你在'<<'后面指定的结束符之间的（但是不包括结束符）多行字符串组成。一般的，这个结束符必须在第一列开始，但是如果你在'<<'后面加一个减号，你就可以缩进结束符了。

print <<-STRING1, <<-STRING2
   Concat
   STRING1
      enate
      STRING2
产生结果:
     Concat
        enate

运用字符串

String可能是Ruby最大的内建类了，它有超过75个标准方法。我们不会在这里挨个介绍它们；函数库参考有它们完整的介绍。让我们先来看看几个常用的字符串用法---在日常编程中我们经常会用到的。

让我们回头看看我们的点唱机。尽管它是被设计成联接到互联网上的，但是也保存了一些很流行的歌曲在本地硬盘上。这样，即使我们的网络连接出问题了，我们仍然能够服务我们的顾客。

由于历史的原因（还有其他原因吗？），歌曲列表是成行的保存在一个文件里面的。文件中的每一行内容包含了歌曲，歌曲长度，演唱者和曲名。所有的字段都是用小竖线分隔开的。一个典型的文件如下：

/jazz/j00132.mp3  | 3:45 | Fats     Waller     | Ain't Misbehavin'
/jazz/j00319.mp3  | 2:58 | Louis    Armstrong  | Wonderful World
/bgrass/bg0732.mp3| 4:09 | Strength in Numbers | Texas Red
         :                  :           :                   :

观察一下数据，很明显在我们建立一些基于这个文件的Song对象之前，我们需要用String类的一些方法来展开和清理这些字段。至少，我们需要：

  • 把一行分成一个字段,
  • 把MM:SS格式的时间转换成秒, 然后
  • 把演唱者名字中额外的空格去掉.

我们的第一个任务是要把每行分成各个字段， String#split 方法最适合最这个。在这里，我们给 split 传递一个正则表达式，/\s*|\s*/，它会用小竖线和空格来分隔一行文本，把文本分成各个字段。还有，因为因为用文件里读取的这行文本后面跟了一个换行符，在我们用split函数之前，我们可以使用String#chomp 来把换行符去掉。

songs = SongList.new

songFile.each do |line|
  file, length, name, title = line.chomp.split(/\s*\|\s*/)
  songs.append Song.new(title, name, length)
end
puts songs[1]
产生结果:
Song: Wonderful World--Louis    Armstrong (2:58)

不幸的是，那个创建原始文件的人在敲演唱者名字的时候是按列敲的，有些名字里面可能包含额外的空格。这样那些名字在我们极好的，高科技的，24小时运行的显示板上显示会很难看。我们必须去掉那些额外的空格才能继续我们的工作。有很多方法来做这个工作，但是最简单的可能是用String#squeeze ,它会修饰重复的字符。我们在这里使用这个方法的 squeeze! 格式，它会改变适当位置的字符串。

songs = SongList.new

songFile.each do |line|
  file, length, name, title = line.chomp.split(/\s*\|\s*/)
  name.squeeze!(" ")
  songs.append Song.new(title, name, length)
end
puts songs[1]
产生结果:
Song: Wonderful World--Louis Armstrong (2:58)

最后，还有一个小问题---时间的格式问题：文件说 2:58，我们想要用秒数来表示，178秒。我们可以再次使用 split 函数，把用冒号分隔的两个字段分开。

mins, secs = length.split(/:/)

不过，我们这里使用另一个相关的函数。String#scan 和 split有点相象，都可以通过一个模式匹配把一个字符串变成几部分。 但是和split不同的是，scan允许你指定用来匹配字段的模式串。在咱们这个例子中，我们想为分字段和秒字段匹配一个或多个数字。一个或多个数字的正则式是 /\d+/。

songs = SongList.new
songFile.each do |line|
  file, length, name, title = line.chomp.split(/\s*\|\s*/)
  name.squeeze!(" ")
  mins, secs = length.scan(/\d+/)
  songs.append Song.new(title, name, mins.to_i*60+secs.to_i)
end
puts songs[1]
产生结果:
Song: Wonderful World--Louis Armstrong (178)

我们的点唱机有关键字搜索的能力。给一个歌曲名或者演唱者姓名中的单词，它能够列出所有匹配的歌曲。比如，敲进"fats"，它可能列出 Fats Domino, Fats Navarro, 和Fats Waller的歌曲。我们通过建立一个索引类来实现这个功能。给它一个对象和一些字符串，它会索引出那个对象里所有包含在字符串里的单词（有两个或者多个字符的）。这会用到String类里面另一些其他方法。

class WordIndex
  def initialize
    @index = Hash.new(nil)
  end
  def index(anObject, *phrases)
    phrases.each do |aPhrase|
      aPhrase.scan /\w[-\w']+/ do |aWord|   # extract each word
        aWord.downcase!
        @index[aWord] = [] if @index[aWord].nil?
        @index[aWord].push(anObject)
      end
    end
  end
  def lookup(aWord)
    @index[aWord.downcase]
  end
end

String#scan 方法能从一个字符串里面提取出符合一个正则表达式 的元素。在这个例子里面模式串“\w[-\w']+”匹配任何能出现在单词里的字符，后面跟一个或多个在方括号里指定的东东（一个小横线，另一个单词符号或者一个单引号）。我们会在56页开始部分更详细地介绍正则表达式。要让我们的搜索大小写不敏感，我们把刚才我们提取出来的字段和传进来的关键字转化成小写。注意在第一个 downcase! 方法后面的感叹号。就像以前我们使用的 squeeze! 方法一样，"!"是一个标识来表明方法会在某个地方改变接受者，在这里表示把字符串变成小写。[这段代码里面有一个Bug, 歌曲"Gone, Gone, Gone"会被索引三次。你能想一个方法修正它吗？]

我们来扩展一下我们的 SongList 类，让它能够在歌曲加进来的时候索引它们，然后加一个用一个字符串查找歌曲的方法。

class SongList
  def initialize
    @songs = Array.new
    @index = WordIndex.new
  end
  def append(aSong)
    @songs.push(aSong)
    @index.index(aSong, aSong.name, aSong.artist)
    self
  end
  def lookup(aWord)
    @index.lookup(aWord)
  end
end

最后，我们来测试一下：

songs = SongList.new
songFile.each do |line|
  file, length, name, title = line.chomp.split(/\s*\|\s*/)
  name.squeeze!(" ")
  mins, secs = length.scan(/\d+/)
  songs.append Song.new(title, name, mins.to_i*60+secs.to_i)
end
puts songs.lookup("Fats")
puts songs.lookup("ain't")
puts songs.lookup("RED")
puts songs.lookup("WoRlD")
产生结果:
Song: Ain't Misbehavin'--Fats Waller (225)
Song: Ain't Misbehavin'--Fats Waller (225)
Song: Texas Red--Strength in Numbers (249)
Song: Wonderful World--Louis Armstrong (178)

我们可以再花50页的篇幅来介绍 String 类里面的所有方法。但是，现在还是让我们继续来学习一个简单的数据类型：范围（ranges）。

（范围）Ranges

范围无处不在：从一月到十二月，0到9，半熟到完全煮熟，从第50行到第67行等等。如果ruby想要帮助我们很好的根据现实世界来建模，那么它也应该支持这些范围。实际上正是如此，ruby支持的ranges有三种用途：序列，条件，和间隔（sequences, conditions, and intervals）。

作为序列

ruby的range最常用的用处是表示一个顺序的序列，序列有一个开始点，和一个结束点，和产生序列中下一个值的方法。ruby中，定义学列使用".."和"..."操作符。".."创建的序列包括两边的边界值，而"..."创建的序列将不包括最大的那个边界值。

1..10
'a'..'z'
0...anArray.length

ruby不像其他一些地早期perl那样，把序列保存在一个内部列表中，比如，1..100000在ruby中只是一个Range对象，包括两个指向Fixnum对象的引用。如果需要，你可以把一个Range用to_a转换成一个数组。

(1..10).to_a | }} | [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
('bar'..'bat').to_a | }} | ["bar", "bas", "bat"]

Ranges实现了一些可以让你对其进行迭代，测试是否包含某个值的方法。

digits = 0..9
digits.include?(5) | }} | true
digits.min | }} | 0
digits.max | }} | 9
digits.reject {|i| i < 5 } | }} | [5, 6, 7, 8, 9]
digits.each do |digit|
dial(digit)
end

到现在我们的ranges表示的都是数字和字符串型，作为一个面向对象的语言，ranges也可以用于我们自己创建的对象，但是这个对象必须实现一个succ方法，以返回下一个值，而且这个对象也必须支持<=>来对其进行比较。调用<=>时，它们比较前后两个对象的大小关系，根据两个对象是小于，等于，还是大于而返回-1, 0, 或者 +1

下面看一个简单的例子，这个类表示由若干个"#"符号组成的一行，我们可以用它来对我们点唱机音量做基于文本的测试。

class VU

  include Comparable

  attr :volume

  def initialize(volume)  # 0..9
    @volume = volume
  end

  def inspect
    '#' * @volume
  end

  # Support for ranges

  def <=>(other)
    self.volume <=> other.volume
  end

  def succ
    raise(IndexError, "Volume too big") if @volume >= 9
    VU.new(@volume.succ)
  end
end

我们可以创建一个VU的range来测试一下：

medium = VU.new(4)..VU.new(7)
medium.to_a | }} | [####, #####, ######, #######]
medium.include?(VU.new(3)) | }} | false

条件范围（ Ranges as Conditions ）

除了表示一系列连续值之外，range还能作为条件表达式。比如，下面代码将接收标准输入，打印出来那些以start开头和以end结尾的代码行。

while gets
  print if /start/../end/
end

Ranges表示间隔

rang最后一个用处是测试一些值是否在这个间隔之内，这要用到操作符===

(1..10) === 5 | }} | true
(1..10) === 15 | }} | false
(1..10) === 3.14159 | }} | true
('a'..'j') === 'c' | }} | true
('a'..'j') === 'z' | }} | false

正则表达式

回到第50页当我们从一个文件里创建歌曲列表的时候，我们用了一个正则表达式去匹配文件里的字段。我们声明了正则表达式 line.split(/\s*|\s*)来匹配一个小竖线被（不是必须的）空格环绕的情况。让我们来仔细研究一下正则表达式来证明为什么我们的声明是正确的。

正则表达式用来匹配字符串的模式。Ruby提供了对模式匹配和替换内建的支持，使我们使用时很方便简练。在这部分，我们会介绍大部分主要的正则表达式特征。有些细节我们不会涉及：你可以参考第205页取得更多信息。

正则表达式是类型Regexp的对象。它们可以用显式的构造函数建立或者直接用 /pattern/ 和 %r/pattern/这种格式的字符常量构造。

a = Regexp.new('^\s*[a-z]') | }} | /^\s*[a-z]/
b = /^\s*[a-z]/ | }} | /^\s*[a-z]/
c = %r{^\s*[a-z]} | }} | /^\s*[a-z]/

一当你有了一个正则表达式对象，你可以用它和一个字符串比较，通过使用 Regexp#match(aString) 或者用匹配操作符 =~（确定匹配）和 !~（否定匹配）。字符串和Regexp对象都可以使用匹配操作符。如果匹配操作符的两个操作数都是字符串的话，右边那个会转化成正则表达式。

a = "Fats Waller"
a =~ /a/ | }} | 1
a =~ /z/ | }} | nil
a =~ "ll" | }} | 7

匹配操作符返回模式匹配成功的字符位置。 它们还有一个设置所有Ruby变量的额外作用。$&接受模式匹配成功的那部分字符，$`(键盘1左边那个键）接受模式匹配成功前面那一部分字符，$'接受模式匹配成功后面那部分字符。我们可以用这个来编写一个函数，showRE，它阐明了一个特殊的模式匹配例子：
def showRE(a,re)
if a =~ re
"#{$`}<<#{$&}>>#{$'}"
else
"no match"
end
end
showRE('very interesting', /t/) | }} | very in<<t>>eresting
showRE('Fats Waller', /ll/) | }} | Fats Wa<<ll>>er

这个匹配也设置了Ruby的全局线程(thread-global)变量 $~ 和 $1 到 $9。变量 $~ 是一个MatchData对象（在336页开始部分有描述），它保存了所有关于这个匹配的信息。$1和其他$*保存了这个匹配的部分，我们呆会儿还会讨论它们。如果有人看见这些像Perl语言的变量名感到害怕，不要着急，这章后面还有好消息。

模式（Patterns）

每个正则表达式都有一个模式，用来和字符串做匹配。

在一个模式中，除了., |, (, ), [, {, +, \, ^, $, *和 ? 之外的字符都是和它本身匹配。

showRE('kangaroo', /angar/) | }} | k<<angar>>oo
showRE('!@%&-_=+', /%&/) | }} | !@<<%&>>-_=+

如果你想在字面上匹配一个上面的特殊字符，在它前面加一个'\'。这解释了我们用在分离那个歌曲列表文件时用的一个正则表达式的一部分，/\s*|\s*/。'\|'表示匹配一个'|'，没有那个反斜杠，'|'代表交换（我们会在后面描述）。

showRE('yes | no', /\|/) | }} | yes <<|>> no
showRE('yes (no)', /\(no\)/) | }} | yes <<(no)>>
showRE('are you sure?', /e\?/) | }} | are you sur<<e?>>

反斜杠后跟一个数字字符用来引进一个特殊的匹配构造，我们会在后面介绍它。另外，一个正则表达式可以包含#{...}表达式取代。

Anchors

一个正则表达式默认会找到字符串中第一个匹配的情况。要在字符串"Mississippi"中匹配 /iss/ ，它会找到那个靠近开始位置的哪个子串"iss"。但是当你想自己指定从头部或者末尾开始匹配时要怎么设置呢？

模式 ^ 和 $ 分别匹配一行字符的开始和结束。它们经常用来指定一个模式匹配的方向：比如，/^option/和在一行文本开始处出现的字符串'option'匹配。字符序列 \A 和一个字符串的开始匹配，\z 和 \Z 和一个字符串的结束匹配。（事实上，\Z 和一个以"\n"结尾的字符串的结束匹配，这种情况下，它从'\n'前面开始匹配）。

showRE("this is\nthe time", /^the/) | }} | this is\n<<the>> time
showRE("this is\nthe time", /is$/) | }} | this <<is>>\nthe time
showRE("this is\nthe time", /\Athis/) | }} | <<this>> is\nthe time
showRE("this is\nthe time", /\Athe/) | }} | no match

相似的，模式 \b 和 \B 分别和单词界限(word boundaries)和非单词界限(nonword boundaries)匹配。构成单词的字符有字母，数字和下划线。

showRE("this is\nthe time", /\bis/) | }} | this <<is>>\nthe time
showRE("this is\nthe time", /\Bis/) | }} | th<<is>> is\nthe time

字符类（character class）

一个字符类是一系列在方括号("[...]")之间的字符，用来匹配方括号里面的单个字符。比如，[aeiou]会和元音字符匹配，[,.:;!?]和标点符号匹配，等等。那些重要的特殊字符（.|()[{+^$*?）在方括号里面会失去匹配作用。但是普通的转义字符仍然起作用，所以，\b代表退格键，\n是换行符（见203页表18.2）。另外，你可以用59页的表5.1的缩写，所以 \s 表示空白符，不仅仅是一个字面上的空格。

showRE('It costs $12.', /[aeiou]/) | }} | It c<<o>>sts $12.
showRE('It costs $12.', /[\s]/) | }} | It<< >>costs $12.

在方括号里面，序列 c1-c2 表示包括在c1到c2之间的字符。

如果你想在字符类（方括号）里面包含 ] 和 - 的话，它们必须出现在开始。

a = 'Gamma [Design Patterns-page 123]'
showRE(a, /[]]/) | }} | Gamma [Design Patterns-page 123<<]>>
showRE(a, /[B-F]/) | }} | Gamma [<<D>>esign Patterns-page 123]
showRE(a, /[-]/) | }} | Gamma [Design Patterns<<->>page 123]
showRE(a, /[0-9]/) | }} | Gamma [Design Patterns-page <<1>>23]

在'['后面紧跟一个 ^ 代表字符类相反的含义: [^a-z]和不是小写字母的字符匹配。

一些字符类特别常用，所以Ruby提供了它们的缩写形式。这些缩写列在59页的表5.1上，它们可以用在方括号和模式串里面。

showRE('It costs $12.', /\s/) | }} | It<< >>costs $12.
showRE('It costs $12.', /\d/) | }} | It costs $<<1>>2.

字符类缩写
字符序列 | [ ... ] | 意思
\d | [0-9] | 数字字符
\D | [^0-9] | 非数字
\s | [\s\t\r\n\f] | 空格字符
\S | [^\s\t\r\n\f] | 非空格字符
\w | [A-Za-z0-9_] | 单词符号
\W | [^A-Za-z0-9_] | 非单词符号

最后，一个在放括号外面出现的句点"."表示除了换行符以外的任何字符（在多行模式下它也表示一个换行符）。

a = 'It costs $12.'
showRE(a, /c.s/) | }} | It <<cos>>ts $12.
showRE(a, /./) | }} | <<I>>t costs $12.
showRE(a, /\./) | }} | It costs $12<<.>>

重复（Repetition）

在我们讲述那个分隔歌曲文件的正则模式（/\s*\|\s*/）的时候, 我们说想匹配在一个'|'两边环绕任意的空格的情况。现在我们知道了 \s 匹配一个空白符，所以看来星号'*'代表任意数的大小。事实上，星号是允许你匹配模式多次出现的修饰符中的一个。

如果 r 代表一个模式里面的前置字符，那么：
r * | 匹配0个或多个 r.
r + | 匹配1个或多个 r.
r ? | 匹配0个或1个 r.
r {m,n} | 匹配最少m个，最多n个 r.
r {m,} | 匹配最少m个 r.

这些重复修饰符有很高的优先权---在正则模式串里它们仅仅和它们的紧密前缀绑定。/ab+/匹配一个"a"后面跟一个或多个"b"而不是一个"ab"组成的序列。你也必须小心使用'*'修饰符---正则模式串/a*/会匹配任何字符串；任何有0个或者多个"a"的字符串。

这些模式串被称为“贪婪地”，因为它们默认会匹配尽量多的字符。你可以改变这种行为，让它们匹配最少的，只要加一个问号后缀就可以了。

a = "The moon is made of cheese"
showRE(a, /\w+/) | }} | <<The>> moon is made of cheese
showRE(a, /\s.*\s/) | }} | The<< moon is made of >>cheese
showRE(a, /\s.*?\s/) | }} | The<< moon >>is made of cheese
showRE(a, /[aeiou]{2,99}/) | }} | The m<<oo>>n is made of cheese
showRE(a, /mo?o/) | }} | The <<moo>>n is made of cheese

间隔（Alternation）

我们知道‘|’是特殊的，因为我们的在行分隔模式中必须用一个反斜杠使之转义。因为一个没有反斜杠的‘|’匹配正则表达式中它左右两边模式中的一个。

a = "red ball blue sky"
showRE(a, /d|e/) | }} | r<<e>>d ball blue sky
showRE(a, /al|lu/) | }} | red b<<al>>l blue sky
showRE(a, /red ball|angry sky/) | }} | <<red ball>> blue sky

要是我们一粗心，这里会有一个陷阱，因为‘|’的优先级很低。在上面最后一个例子中，我们的正则式匹配“red ball”或者“angry sky”，而不是“red ball sky”或“red angry sky”。为了匹配“red ball sky”或“red angry sky”，我们用grouping重载默认的优先级。

成组技术（Grouping)

你可以在正则式中用圆括号来成组字符集。在这个组里面的所有字符会被认为是一个正则表达式。

showRE('banana', /an*/) | }} | b<<an>>ana
showRE('banana', /(an)*/) | }} | <<>>banana
showRE('banana', /(an)+/) | }} | b<<anan>>a

a = 'red ball blue sky'
showRE(a, /blue|red/) | }} | <<red>> ball blue sky
showRE(a, /(blue|red) \w+/) | }} | <<red ball>> blue sky
showRE(a, /(red|blue) \w+/) | }} | <<red ball>> blue sky
showRE(a, /red|blue \w+/) | }} | <<red>> ball blue sky

showRE(a, /red (ball|angry) sky/) | }} | no match
a = 'the red angry sky'
showRE(a, /red (ball|angry) sky/) | }} | the <<red angry sky>>

圆括号也用来收集模式匹配的结果。Ruby对左括号记数，对每个左括号，它保存了已经匹配的部分结果和相应的右括号。你可以在剩下的模式串中或者你的Ruby程序里使用这个匹配。在模式匹配中，\1代表第1个组，\2代表第2个组，其他依次类推。在模式串外面，特殊变量 $1, $2和其他$*和这个作用一样。

"12:50am" =~ /(\d\d):(\d\d)(..)/ | }} | 0
"Hour is #$1, minute #$2" | }} | "Hour is 12, minute 50"
"12:50am" =~ /((\d\d):(\d\d))(..)/ | }} | 0
"Time is #$1" | }} | "Time is 12:50"
"Hour is #$2, minute #$3" | }} | "Hour is 12, minute 50"
"AM/PM is #$4" | }} | "AM/PM is am"

能够利用目前的部分匹配允许你寻找各种形式的循环。

# 匹配重复的字母
showRE('He said "Hello"', /(\w)\1/) | }} | He said "He<<ll>>o"
# 匹配重复的子串
showRE('Mississippi', /(\w+)\1/) | }} | M<<ississ>>ippi

你也可以使用后置引用来匹配分隔符。

showRE('He said "Hello"', /(["']).*?\1/) | }} | He said <<"Hello">>
showRE("He said 'Hello'", /(["']).*?\1/) | }} | He said <<'Hello'>>

基于模式的子串技术

有时候在一个字符串里面寻找一个模式已经满足要求了。如果一个朋友刁难你，要你找出一个顺序包含a, b, c, d 和 e 的单词，你可以用模式串 /a.*b.*c.*d.*e/来寻找然后可以找到"absconded"和"ambuscade" 。这毫无疑问是挺有用的。

然后，有时候我们需要改变一个模式匹配的内容。让我们回到我们的歌曲列表文件。创建文件的人用小写字母敲进了演唱者的名字。当我们把名字显示在点唱机上时，我们想让它大小写混写。那么我们怎么样才能把每个单词的首字母变成大写呢？

String#sub 和 String#gsub 方法 寻找字符串中匹配它们第一个参数的那部分，然后把那部分用它们的第二个参数代替。String#sub 只替换一次，String#gsub 则替换所有在字符串里出现的匹配。两个方法都返回一个已经替换过的新字符串的拷贝。另外一个版本的方法String#sub! 和 String#gsub! 会修改原始字符串。

a = "the quick brown fox"
a.sub(/[aeiou]/, '*') | }} | "th* quick brown fox"
a.gsub(/[aeiou]/, '*') | }} | "th* q**ck br*wn f*x"
a.sub(/\s\S+/, '') | }} | "the brown fox"
a.gsub(/\s\S+/, '') | }} | "the"

第二个参数可以是一个字符串或者一个程序块（block）。如果用了程序块，那个程序块的值被替换进了字符串。

a = "the quick brown fox"
a.sub(/^./) { $&.upcase } | }} | "The quick brown fox"
a.gsub(/[aeiou]/) { $&.upcase } | }} | "thE qUIck brOwn fOx"

所以，这看起来是我们转变演唱者名字的正确方法。匹配一个单词的首字母的模式串是 \b\w---寻找一个单词边界然后跟一个字母。结合 gsub 使用，我们可以来修改演唱者的名字了。

def mixedCase(aName)
aName.gsub(/\b\w/) { $&.upcase }
end
mixedCase("fats waller") | }} | "Fats Waller"
mixedCase("louis armstrong") | }} | "Louis Armstrong"
mixedCase("strength in numbers") | }} | "Strength In Numbers"

子串函数中的转义字符

前面我们讲过\1, \2和类似的字符序列可以在模式串中使用，代表到现在为止已经匹配的第n组数据。相同的字符序列也可以在 sub 和 gsub函数的第二个参数中使用。

"fred:smith".sub(/(\w+):(\w+)/, '\2, \1') | }} | "smith, fred"
"nercpyitno".gsub(/(.)(.)/, '\2\1') | }} | "encryption"

还有一些外加的转义字符用在字符串替换中：\&（最后那个匹配），\+（最后匹配的组），\`（匹配串前面的字符串），\'（匹配后面的字符串），\\（反斜杠）。如果你在替换中使用反斜杠那么会引起混乱。最明显的例子是：

str.gsub(/\\/, '\\\\')

很清楚，这句代码想把 str 里面的一个反斜杠变成两个。程序员用了两个反斜杠在替换文本里面，希望它们在语法分析时变成两个反斜杠。但是当替换发生时，正则表达式引擎又读了一遍字符串，把"\\"变成了"\"，所以上面代码的作用是用一个反斜杠替换另外一个反斜杠。你需要这样写 gsub(/\\/, '\\\\\\\\')!

str = 'a\b\c' | }} | "a\b\c"
str.gsub(/\\/, '\\\\\\\\') | }} | "a\\b\\c"

因为 \& 会被匹配的字符串替换，所以你也可以这样写：

str = 'a\b\c' | }} | "a\b\c"
str.gsub(/\\/, '\&\&') | }} | "a\\b\\c"

如果你使用 gsub 的程序块形式，用来替换的字符串仅仅被分析一次（在语法分析阶段），所以结果是你想要的：

str = 'a\b\c' | }} | "a\b\c"
str.gsub(/\\/) { '\\\\' } | }} | "a\\b\\c"

最后，作为正则表达式和程序块结合起来的强大表现力的例子，我们来看看这段CGI函数库模块里的代码，是Wakou Aoyama编写的。代码接受一段包含HTML文本的字符串然后把它转化成普通的ASCII文本。因为这是为日本的用户编写的，它在正则式用了"n"修饰符来使宽字符失效。代码也演示了Ruby的 case 语句，我们在81页讨论它。

def unescapeHTML(string)
  str = string.dup
  str.gsub!(/&(.*?);/n) {
    match = $1.dup
    case match
    when /\Aamp\z/ni           then '&'
    when /\Aquot\z/ni          then '"'
    when /\Agt\z/ni            then '>'
    when /\Alt\z/ni            then '<'
    when /\A#(\d+)\z/n         then Integer($1).chr
    when /\A#x([0-9a-f]+)\z/ni then $1.hex.chr
    end
  }
  str
end

puts unescapeHTML("1&lt;2 &amp;&amp; 4&gt;3")
puts unescapeHTML("&quot;A&quot; = &#65; = &#x41;")
产生结果:
1<2 && 4>3
"A" = A = A

面向对象的正则表达式

我们必须承认虽然这些关怪的表达式很好用，但是它们不是面向对象的，而且相当晦涩难懂。你们不是说过Ruby里面任何东西都是对象吗？为什么这里是这样的呢？

这无关紧要，真的。因为Matz在设计Ruby的时候，他构建了一个完全面向对的正则表达式处理系统。但是为了让Perl程序员感到熟悉一些，他把这些 $* 包装在这系统之上。这些对象和类还在那里，在外观里面，现在让我们花点时间把它们挖出来。

我们已经遇到过这样一个类了：字面正则表达式的产生类 Regexp（在361页有具体描述）。

re = /cat/
re.type }} Regexp

方法 Regexp#match 把一个正则表达式和一个字符串进行匹配。如果不成功，方法返回 nil。在成功的情况下，它返回类 Matchdata 的一个实例，在336页有详细描述。然后那个 MatchData 对象给你访问这个匹配的各种信息的方法。所有能从 $-*变量里得到的好东东都可以在咱们手边这个小对象里得到。

re = /(\d+):(\d+)/ # 匹配一个时间 hh:mm
md = re.match("Time: 12:34am")
md.type | }} | MatchData
md[0] # == $& | }} | "12:34"
md[1] # == $1 | }} | "12"
md[2] # == $2 | }} | "34"
md.pre_match # == $` | }} | "Time: "
md.post_match # == $' | }} | "am"

因为匹配信息是保存在它自己对象里的，你可以在同一时间保存两个或者多个匹配的结果，这用$-*你可能不能实现。在下面一个例子里，我们用同一个 Regexp 对象去匹配两个不同的字符串。每个匹配都返回一个唯一的 MatchData 对象，我们通过它们的两个子模式字段来区别它们。

re = /(\d+):(\d+)/ # 匹配一个时间 hh:mm
md1 = re.match("Time: 12:34am")
md2 = re.match("Time: 10:30pm")
md1[1, 2] | }} | ["12", "34"]
md2[1, 2] | }} | ["10", "30"]

但是那些 $-*是怎么包装起来的呢？每个模式匹配结束以后，Ruby在一个局部线程（thread-local）变量中保存了一个指向结果的引用（nil 或者 是一个 MatchData 对象）。所有其他的正则表达式变量都是从这个对象中继承而来的。虽然我们不能真正这样使用以下代码，但是它证明了所有其他的和MatchData有关的 $-*变量都是 $~里面的值。

re = /(\d+):(\d+)/
md1 = re.match("Time: 12:34am")
md2 = re.match("Time: 10:30pm")
[ $1, $2 ] # 最后匹配成功的情况 | }} | ["10", "30"]
$~ = md1
[ $1, $2 ] # 上一个匹配成功情况 | }} | ["12", "34"]

说完了所有这些，我们必须说点实话：） Andy和Dave平常都只是使用$-*变量而不去担心 MatchData对象。在日常应用中，我们只要用起来舒服就行。有时候我们情不自禁地使自己变得更务实。

Extracted from the book "Programming Ruby - The Pragmatic Programmer's Guide"
Copyright © 2001 by Addison Wesley Longman, Inc. This material may be distributed only subject to the terms and conditions set forth in the Open Publication License, v1.0 or later (the latest version is presently available at http://www.opencontent.org/openpub/)).

Distribution of substantively modified versions of this document is prohibited without the explicit permission of the copyright holder.

Distribution of the work or derivative of the work in any standard (paper) book form is prohibited unless prior permission is obtained from the copyright holder.


Programming Ruby: The Pragmatic Programmer's Guide

Programming Ruby

实用程序员指南

上一章 < 目录 ^
下一章>

深入方法

一些其它语言有函数，过程，方法等，而Ruby中只有方法：一段表达式代码，返回一个值。

到目前为止，我们在这本书中只是基本的介绍了如何定义，使用方法，现在，我们会继续深入的探讨一些关于方法更深层的东西。

方法定义

如同在前面看到的一样，定义一个方法用关键字def开头，方法名应该以小写字母开头[如果你用大写字母开头定义一个方法，你不会立即得到一个错误，但是当你调用这个方法时，Ruby首先认为你访问的是一个常量，所以可能会解析错误]，如果一个方法主要用来完成一些查询操作（不专指数据库查询），通常以一个问号"?"结束，作为函数名的最后一个字母，比如instance_of?等。如果一个方法有一定危险，或者可能修改方法的接受者，通常以"!"结尾，比如String类提供了chop和chop!两个方法，第一个方法返回一个修改过的字符串，而第二个方法直接就修改了接收者本身。"?"和"!"是唯一两个能作为方法名后缀的特殊字符。

我们已经指定了方法名，如果需要，我们可以定义一些参数，这些参数用双括号括起来，作用域范围都是局部变量，一些例子如下：

def myNewMethod(arg1, arg2, arg3)     # 3 arguments
  # Code for the method would go here
end

def myOtherNewMethod                  # No arguments
  # Code for the method would go here
end

Ruby允许为方法的参数设置默认值：如果调用者没有显示的为这些参数提供值，将使用这些默认值。通过"="，就可以为这些参数设定默认值。

def coolDude(arg1="Miles", arg2="Coltrane", arg3="Roach")
"#{arg1}, #{arg2}, #{arg3}."
end
coolDude | ? | "Miles, Coltrane, Roach."
coolDude("Bart") | ? | "Bart, Coltrane, Roach."
coolDude("Bart", "Elwood") | ? | "Bart, Elwood, Roach."
coolDude("Bart", "Elwood", "Linus") | ? | "Bart, Elwood, Linus."

方法体中包含了一般的Ruby表达式，但是你不能在方法里面定义实例方法，类或者模块。方法的返回值是方法体最后一行执行后的结果，或者你显示的用一个return语句。

可变长度的参数列表

如果我们想给方法传入一个数目不定的参数，或者把所有参数放到一个参数中进行传递的话，该怎么办呢？我们可以在普通的参数后面加入一个特殊的参数，这个参数以"*"开头，就可以达到这个目的了。

def varargs(arg1, *rest)
"Got #{arg1} and #{rest.join(', ')}"
end
varargs("one") | ? | "Got one and "
varargs("one", "two") | ? | "Got one and two"
varargs "one", "two", "three" | ? | "Got one and two, three"

在这个例子中，第一个参数很普通，直接作为第一个参数变量，而后面以"*"开头的参数，将会包括调用时候后面的所有参数，是一个Array的结构，包括了从第二个开始的所有参数。

方法和块

在讨论块和迭代的那章时，我们知道，当一个方法被调用时候，可以接收一个block，而我们在方法中可以用yield来执行这个block。

def takeBlock(p1)
  if block_given?
    yield(p1)
  else
    p1
  end
end

takeBlock("no block") | ? | "no block"
takeBlock("no block") { |s| s.sub(/no /, '') } | ? | "block"

但是，当方法接受参数中最后一个参数以"&"开始的时候，任何给定的block都会转换为Proc对象，并且这个Proc对象将会赋值给这个参数（下例中block指向一个Proc对象）。

class TaxCalculator
def initialize(name, &block)
@name, @block = name, block
end
def getTax(amount)
"#@name on #{amount} = #{ @block.call(amount) }"
end
end
tc = TaxCalculator.new("Sales tax") { |amt| amt * 0.075 }
tc.getTax(100) | ? | "Sales tax on 100 = 7.5"
tc.getTax(250) | ? | "Sales tax on 250 = 18.75"

调用方法

通常，调用一个方法需要指定一个接收者，方法名，还有一些参数或者block。

connection.downloadMP3("jitterbug") { |p| showProgress(p) }

在这个例子里，connection是接收者，downloadMP3是方法名，"jitterbug"是一个参数，{ |p| showProgress(p) }是传递给这个方法的块。

对于类或者模块方法来说，接收者是类或模块名：

File.size("testfile")
Math.sin(Math::PI/4)

如果你省略了接收者，那么默认为self是接收者，即当前对象：

self.id | ? | 537794160
id | ? | 537794160
self.type | ? | Object
type | ? | Object

这种机制也是Ruby实现private方法的体现，private方法不能用一个接收者来直接调用，只能在当前对象中使用。

方法名后面是可选的参数，如果不会出现歧义的话，调用方法时参数可以不加括号括起来[Ruby文档有时候也叫做这样的方法是命令（commands）]，然而，除非特别简单的方法，否则还是加上括号的好，要不可能容易出错，比如，你的方法嵌套在另一个方法调用之中。

a = obj.hash    # Same as
a = obj.hash()  # this.

obj.someMethod "Arg1", arg2, arg3   # Same thing as
obj.someMethod("Arg1", arg2, arg3)  # with parentheses.

在方法调用时使用数组

前面我们已经说过了，在一个方法的参数前面可以加一个星号，这样所有后面的参数都被放到了一个数组中，反过来，Ruby也支持调用的时候指定一个数组代替若干个参数。

在调用方法的时候，你可以使用一个数组作为一个参数，它的每个元素都将作为一个单独的参数使用。使用的时候，需要在这个作为参数的数组前面加一个星号。

def five(a, b, c, d, e)
"I was passed #{a} #{b} #{c} #{d} #{e}"
end
five(1, 2, 3, 4, 5 ) | ? | "I was passed 1 2 3 4 5"
five(1, 2, 3, *['a', 'b']) | ? | "I was passed 1 2 3 a b"
five(*(10..14).to_a) | ? | "I was passed 10 11 12 13 14"

更加动态的block

我们已经看过了如何把一个方法和一个块联系起来。

listBones("aardvark") do |aBone|
  # ...
end

通常，这已经足够好了，我们可以给一个方法提供一个机构良好的块，而不必再方法中使用很多的if或者while等语句。

但是有些时候，你需要更灵活一些，比如，下面的例子，如果选择times，即输入t，将会打印2，4，6，8等等：

print "(t)imes or (p)lus: "
times = gets
print "number: "
number = gets.to_i

if times =~ /^t/
  puts((1..10).collect { |n| n*number }.join(", "))
else
  puts((1..10).collect { |n| n+number }.join(", "))
end
结果:
(t)imes or (p)lus: t
number: 2
2, 4, 6, 8, 10, 12, 14, 16, 18, 20

虽然这样可以工作，但是不是很完美，我们可以把负责计算的部分抽出来组成一个block。

print "(t)imes or (p)lus: "
times = gets
print "number: "
number = gets.to_i

if times =~ /^t/
  calc = proc { |n| n*number }
else
  calc = proc { |n| n+number }
end
puts((1..10).collect(&calc).join(", "))
produces:
(t)imes or (p)lus: t
number: 2
2, 4, 6, 8, 10, 12, 14, 16, 18, 20

如果最后一个方法的最后一个参数以"&"开头，Ruby把它最为一个Proc来处理，传到相应的block。

这种技术也有另外的用处，比如我们使用迭代器处理一些数据，把每个步骤地结果存储到一个数组中，我们下面将用到前面的Fibonacci 例子来产生一组数据：

a = []
fibUpTo(20) { |val| a << val } | ? | nil
a.inspect | ? | "[1, 1, 2, 3, 5, 8, 13]"

尽管这样已经可以工作了，但是这显示出来的意图不像我们想象的那么明晰，所以，我们取而代之的是另外定义了一个方法into，它将返回一个完成填充array功能的block。（注意返回的block是一个闭包closure ，即使into返回了，它还指向参数anArray）

def into(anArray)
  return proc { |val| anArray << val }
end
fibUpTo 20, &into(a = [])
a.inspect ? "[1, 1, 2, 3, 5, 8, 13]"

哈希结构作为参数

一些语言支持基于键的参数，即hash结构的参数。不按照参数的个数和位置来调用一个方法，而是用一个hash结构的键－值结构来设定参数，而不是按位置。Ruby1。6不支持这种特性，1。8支持。[本书写的是基于1.6，而目前最新的Ruby是1.8]

同时，人们可以用hash结构来实现这一功能，比如，我们要为我们的SongList实现一个按名字查找的功能。

class SongList
  def createSearch(name, params)
    # ...
  end
end
aList.createSearch("short jazz songs", {
                   'genre'            => "jazz",
                   'durationLessThan' => 270
                   } )

第一个参数是查找的名称，第二个参数是一个hash结构，包含了各种查找的参数。使用hash结构，我们可以是有一些键－值特性：音乐流派是jazz，时长小于4.5分钟。但是这段代码不是太好，而且大括号中的内容很容易被误认为块。所以，Ruby提供了一个快捷方式，你可以在方法的参数中指定键=>值的结构，像普通的参数那样。这样的结构都将作为一个hash结构传给方法，而不需要大扩号了。

aList.createSearch("short jazz songs",
                   'genre'            => "jazz",
                   'durationLessThan' => 270
                   )

Extracted from the book "Programming Ruby - The Pragmatic Programmer's Guide"
Copyright © 2001 by Addison Wesley Longman, Inc. This material may be distributed only subject to the terms and conditions set forth in the Open Publication License, v1.0 or later (the latest version is presently available at http://www.opencontent.org/openpub/)).

Distribution of substantively modified versions of this document is prohibited without the explicit permission of the copyright holder.

Distribution of the work or derivative of the work in any standard (paper) book form is prohibited unless prior permission is obtained from the copyright holder.


Programming Ruby: The Pragmatic Programmer's Guide

Programming Ruby

实用程序员指南

上一章< 目录^
下一章>

表达式（Expressions）

到目前为止我们已经用了一些基本的表达式，毕竟，a＝b＋c是最基本的了，你即使不看本章，也能写出一大堆的Ruby代码来。但是那样做不是什么有趣的事情;-)。

Ruby和其它语言的一个不同之处就是任何东西都能返回一个值，几乎所有的东西都是表达式，在实际中，这有什么意义呢？

一些明显得作用是可以实现链式语句：

a = b = c = 0 | ? | 0
[ 3, 1, 7, 0 ].sort.reverse | ? | [7, 3, 1, 0]

一些不太起眼的东西，比如C和JAVA中的语句，在Ruby中都是表达式，例如，if和case都返回一个值，这个值就是这些语句中最后执行的那行。

songType = if song.mp3Type == MP3::Jazz
             if song.written < Date.new(1935, 1, 1)
               Song::TradJazz
             else
               Song::Jazz
             end
           else
             Song::Other
           end

 rating = case votesCast
          when 0...10    then Rating::SkipThisOne
          when 10...50   then Rating::CouldDoBetter
          else                Rating::Rave
          end

操作表达式（Operator Expressions）

Ruby提供了诸如加减乘除等一些操作符，完整的操作符列表和优先级在第18章有列表。

在Ruby中，很多操作符就是对一些方法的调用。比如你执行a*b+c，实际上就是调用对象a的乘方法，把b作为一个参数传递过去，然后在调用这个结果对象的加方法，把c作为参数传递，实际上等于：

(a.*(b)).+(c)

因为你可以重新定义实例方法，所以你可以修改一些不能满足你的需求的方法，让它达到你需要的作用

class Fixnum
alias oldPlus +
def +(other)
oldPlus(other).succ
end
end
1 + 2 | ? | 4
a = 3
a += 4 | ? | 8

很有用的一个技巧是你自己写的的类可以像内建对象一样参与操作符的操作，比如，我们想从一首歌中间某处开始提取一部分音乐，我们可以用操作符"[ ]"来完成：

class Song
  def [](fromTime, toTime)
    result = Song.new(self.title + " [extract]",
                      self.artist,
                      toTime - fromTime)
    result.setStartTime(fromTime)
    result
  end
end

这段代码扩展了类Song，增加了[ ]方法，这个方法接收两个参数，一个开始时间，一个结束时间。这个方法返回一个新的Song对象，这个对象是歌曲的一部分。然后，我们就可以这样播放这段音乐：

aSong[0, 0.15].play

混合表达式（Miscellaneous Expressions）

除了上面最普通的操作符表达式，或者不是很显眼的语句表达式（比如if或case），Ruby还支持在表达式中使用更多的东西。

命令展开 （Command Expansion）

如果你用反引号（`）来括起来一个字符串，或者用%x{ 和 }括起来，那么这个表达式中的字符串默认得会作为底层的操组系统命令来执行，并返回结果，这个结果就是这个命令在操作系统中执行之后的结果。换行符将不会从结果中去掉，所以返回结果一般都会包含一个回车符。

`date` | ? | "Sun Jun 9 00:08:26 CDT 2002\n"
`dir`.split[34] | ? | "lib_singleton.tip"
%x{echo "Hello there"} | ? | "Hello there\n"

你也可以在命令中使用表达式展开和所有通常的转义序列。

for i in 0..3
  status = `dbmanager status id=#{i}`
  # ...
end

执行的命令的返回状态存放在全局变量$?中。

重载反引号方法

上面我们说道，反引号之中的命令"默认"会作为操作系统命令来执行，实际上，这个字符串是传递给了Kernel::` 这个方法（一个反引号）来执行。如果你愿意，可以重写这个方法，比如如下：

alias oldBackquote `
def `(cmd)
  result = oldBackquote(cmd)
  if $? != 0
    raise "Command #{cmd} failed"
  end
  result
end
print `date`
print `data`
产生:
Sun Jun  9 00:08:26 CDT 2002
prog.rb:3: command not found: data
prog.rb:5:in ``': Command data failed (RuntimeError)
    from prog.rb:10

赋值

我们前面的例子中都涉及到了赋值这一基本表达式，下面，我们来讨论一些关于赋值语句的东西。

一个赋值语句给一个变量或者属性设定一个指定的值，变量或属性在左边，值在右边。然后这个值作为表达式的返回值返回。也就是说，我们可以用链式赋值来给一些变量赋值：

a = b = 1 + 2 + 3
a | ? | 6
b | ? | 6
a = (b = 1 + 2) + 3
a | ? | 6
b | ? | 3
File.open(name = gets.chomp)

在Ruby中有两种基本的赋值格式，第一种是指给一个引用某一参数或者常量的对象赋值，这种形式是紧密连接到语言中的。

instrument = "piano"
MIDDLE_A   = 440

另一种是在赋值语句左边使用对象的属性或者元素的引用。

aSong.duration    = 234
instrument["ano"] = "ccolo"

这种方法比较特殊，通过调用左值的方法来赋值，也就是说我们可以重写这些方法。

我们已经看过如何定义一个可以修改的属性了，只需要简单的在方法后面以等号结尾即可。这个方法把接收的参数作为赋值语句的右值。

class Song
  def duration=(newDuration)
    @duration = newDuration
  end
end

没有理由要求这些给参数设置值得方法与内部的实例变量一致，或者每个可以修改的属性都要提供一个读方法，反过来也是一样。

class Amplifier
  def volume=(newVolume)
    self.leftChannel = self.rightChannel = newVolume
  end
  # ...
end

Sidebar:在类中使用访问方法（ Accessors）
上面的例子中为什么我们必须要写 self.leftChannel 而不能省掉self呢？一般的，一个类中的方法可以直接调用同类或者父类中的其他方法（默认得接收者是self），但是，对于attribute writers来说这就不管用了，Ruby将把左面的名字作为一个本地变量，而不是一个对写属性方法的调用。

class BrokenAmplifier
attr_accessor :leftChannel, :rightChannel
def volume=(vol)
leftChannel = self.rightChannel = vol
end
end
ba = BrokenAmplifier.new
ba.leftChannel = ba.rightChannel = 99
ba.volume = 5
ba.leftChannel | ? | 99
ba.rightChannel | ? | 5

我们在leftChannel前面忘了写self.了，所以ruby把这个新值赋给了一个方法volume＝的一个局部变量，而这个对象的属性没有任何变化。这可能会经常产生问题。

并行赋值

在学习了一段时间程序设计之后，我们可能会遇到要求将两个变量的值互换：

int a = 1;
int b = 2;
int temp;
temp = a; a = b; b = temp;

在Ruby中很简单，只需要：

a, b = b, a

Ruby可以有效的实现并行赋值，在右边的值在被赋给左面的变量或属性之前按照它们的顺讯进行求值，然后对应的赋给左面的属性或变量。一个例子如下，第二行给a，b，c的值分别是x，x＋＝1，x＋＝1计算之后的值。

x = 0 | ? | 0
a, b, c = x, (x += 1), (x += 1) | ? | [0, 1, 2]

当一个赋值语句左值多余一时，这个表达式的返回值是一个由右面的值组成的数组，如果一个赋值语句的左值多余右值，多余的左值被设为nil，反过来如果右值多余左值，那么多余的右值将被忽略。在Ruby1.6.2中，如果左值只有一个，右值有多个，那么这些右值将作为一个数组赋给左值。

你也可以在并行赋值语句中分解和扩展数组。如果最后的左值以星号作为前缀，那么所有对应这个得值和以后的值将会组成一个数组，赋给这个左值（如下面第三行的c）；类似的，如果最后一个右值是一个数组，你可以加一个星号作为前缀，Ruby将会把这个数组拆开按相应的位置赋给左值（如下面第六行的c，而且，如果这个数组是唯一的右值，这个星号是可以省略的，作为右值得数组自动拆开，如第二行所示）。

a = [1, 2, 3, 4]
b, c = a | ? | b == 1, | c == 2
b, *c = a | ? | b == 1, | c == [2, 3, 4]
b, c = 99, a | ? | b == 99, | c == [1, 2, 3, 4]
b, *c = 99, a | ? | b == 99, | c == [[1, 2, 3, 4]]
b, c = 99, *a | ? | b == 99, | c == 1
b, *c = 99, *a | ? | b == 99, | c == [1, 2, 3, 4]

嵌套赋值

并行赋值还有一个值得一提的特性，赋值语句左边还可以包括用括号括起来的变量列表，Ruby中叫做嵌套赋值语句。Ruby首先摘出右值中相应的项进行赋值，然后在进行高层的赋值操作。

b, (c, d), e = 1,2,3,4 | ? | b == 1, | c == 2, | d == nil, | e == 3
b, (c, d), e = [1,2,3,4] | ? | b == 1, | c == 2, | d == nil, | e == 3
b, (c, d), e = 1,[2,3],4 | ? | b == 1, | c == 2, | d == 3, | e == 4
b, (c, d), e = 1,[2,3,4],5 | ? | b == 1, | c == 2, | d == 3, | e == 5
b, (c,*d), e = 1,[2,3,4],5 | ? | b == 1, | c == 2, | d == [3, 4], | e == 5

其它赋值形式

像其它语言一样，ruby也为a=a+2提供了类似a+=2的快捷方式。

第二种方式是第一种的深入，可以让操作符当成方法来工作。

class Bowdlerize
def initialize(aString)
@value = aString.gsub(/[aeiou]/, '*')
end
def +(other)
Bowdlerize.new(self.to_s + other.to_s)
end
def to_s
@value
end
end
a = Bowdlerize.new("damn ") | ? | d*mn
a += "shame" | ? | d*mn sh*m*

条件执行（Conditional Execution）

Ruby有几种不同的机制来实现条件执行，大多数都感觉很类似，也有一些很灵巧，在深入讨论之前，我们先来花点时间看看布尔表达式。

Boolean 表达式

Ruby中的true定义很简单，任何不是nil和false常量的东西都是true，你会发现系统的实现库中很多这种用法。比如，IO#gets ，用来返回一个文件的下一行，如果到了文件末尾，返回nil，所以，我们才可以这样通过while来循环读取数据：

while line = gets
  # process line
end

但是，这里对于c和perl程序员来说有一个误区，数字0和长度为0的字符串都不会被解释成false值，需要注意。

Defined?, And, Or, 和 Not

Ruby支持所有标准的布尔操作，另外，还引入了新的操作符defined？

操作符``and'' 和``&&'' 只有当两面的值都为真才会返回真，第一个值为真，才会判断第二个值，否则直接返回假。这两个操作符的区别是优先级不同（and低于 &&）

类似的 ``or'' 和``||''有一方为真就会返回真，如果第一个为真，则不会判断后面的值，类似and，这两个操作符只有优先级的不同。

and和or有相同的优先级，而&&的优先级高于||。

``not'' and ``!'' 返回操作数的相反的值，如果操作数为true，则这个操作符返回false。并且and和！也只是优先级不同。

所有的这些操作符和优先级都在18章有详细讲述。

操作符defined?将返回nil，如果操作数没有定义的话。否则，将返回后面参数的描述信息。

defined? 1 | ? | "expression"
defined? dummy | ? | nil
defined? printf | ? | "method"
defined? String | ? | "constant"
defined? $& | ? | nil
defined? $_ | ? | "global-variable"
defined? Math::PI | ? | "constant"
defined? ( c,d = 1,2 ) | ? | "assignment"
defined? 42.abs | ? | "method"

除了这些布尔表达式，Ruby对象还支持使用 ==, ===, <=>, =~, eql?, 和equal?进行对象之间的比较。除了<=>之外这些操作符都在Object类中定义，但是经常被子类重载。比如，类Array重定义了==方法，判断两个数组相同的条件事它们的个数相同，同一位置的元素也相同。

通用比较操作符
操作符 | 意义
== | 测试是否相同
=== | 在case中的when语句判断是否相等
<=> | 通用比较操作符，根据前面的对象小于，等于还是大于后面的对象，返回 -1, 0, 或者 +1。
<, <=, >=, > | 小于，小于等于，大于等于，大于
=~ | 正则表达式匹配
eql? | 如果前后两个对象都是同一类型，则返回true： 1 == 1.0返回true但是 1.eql?(1.0) 结果为false。
equal? | 只有两个对象有相同的object id 才返回true。

==和=~都有相反的操作符!=和!~，但是Ruby会将程序中的a!=b转换为!(a==b)，a!~b转换成!(a=~b)，如果你自己的类中重新写了==和=~方法，那么你同时的到了!=和!~两个方法；同时，你也不能离开了==和=~而孤立的定义!=和!~两个方法。

你可以使用Ruby range 作为一个布尔表达式，一个类似 exp1..exp2 的range只有在遇到exp1为true，然后exp2又为true之后，才会返回true。下面循环部分有例子。

最后，你可以用正则表达式来当作一个布尔表达式。Ruby expands it to $_=~/re/.

If 和 Unless表达式

Ruby中的if语句跟其他语言类似。

if aSong.artist == "Gillespie" then
  handle = "Dizzy"
elsif aSong.artist == "Parker" then
  handle = "Bird"
else
  handle = "unknown"
end

如果你的if语句写在多行上，可以省略then关键字。

if aSong.artist == "Gillespie"
  handle = "Dizzy"
elsif aSong.artist == "Parker"
  handle = "Bird"
else
  handle = "unknown"
end

但是，如果你的语句都写在一行上，then应该写上来分开布尔表达式和后面的语句。

if aSong.artist == "Gillespie" then  handle = "Dizzy"
elsif aSong.artist == "Parker" then  handle = "Bird"
else  handle = "unknown"
end

你可以使用0个或多个elsif语句，和一个可选的else语句。

就像我们前面说道的，if是一个表达式，不是一个statement，它可以返回一个值，你不必使用if表达式的返回值，但是它可能有些用处。

handle = if aSong.artist == "Gillespie" then
           "Dizzy"
         elsif aSong.artist == "Parker" then
           "Bird"
         else
           "unknown"
         end

Ruby也未if提供了一个否定的形式，unless：

unless aSong.duration > 180 then
  cost = .25
else
  cost = .35
end

最后，也为使用C语言的程序员准备了条件表达式：

cost = aSong.duration > 180 ? .35 : .25

这个条件表达式在根据?前面的布尔值为true或false返回冒号前面或后面的值。在这个例子中，如果歌曲的时长大于3分钟，将返回.35，否则返回.25，然后，将这个值赋给cost。

If 和 Unless 修饰符（Modifiers）

Ruby也借鉴了Perl的一些特点，语句修饰符（Statement modifiers）使我们可以在语句末尾加上条件语句。

mon, day, year = $1, $2, $3 if /(\d\d)-(\d\d)-(\d\d)/
puts "a = #{a}" if fDebug
print total unless total == 0

对于if修饰符来说，只有当if后面的条件为true，前面的语句才会执行，unless正好和if相反。

while gets
  next if /^#/            # Skip comments
  parseLine unless /^$/   # Don't parse empty lines
end

因为if本身也是表达式，所以下面的写法将会使代码变得难懂。

if artist == "John Coltrane"
  artist = "'Trane"
end unless nicknames == "no"

Case 表达式

Ruby的 case 表达式非常强大，就像多个if的固化物一样。

case inputLine

  when "debug"
    dumpDebugInfo
    dumpSymbols

  when /p\s+(\w+)/
    dumpVariable($1)

  when "quit", "exit"
    exit

  else
    print "Illegal command: #{inputLine}"
end

像if一样，case返回最后执行的语句的结果，如果你的when和后面的语句都在一行，你也需要加一个then关键字。

kind = case year
         when 1850..1889 then "Blues"
         when 1890..1909 then "Ragtime"
         when 1910..1929 then "New Orleans Jazz"
         when 1930..1939 then "Swing"
         when 1940..1950 then "Bebop"
         else                 "Jazz"
       end

case 操作符根据case后面目标的值，跟每个when后面的值用===进行判断，

operates by comparing the target (the expression after the keyword case) with each of the comparison expressions after the when keywords. This test is done using comparison === target. As long as a class defines meaningful semantics for === (and all the built-in classes do), objects of that class can be used in case expressions.

For example, regular expressions define === as a simple pattern match.

case line
  when /title=(.*)/
    puts "Title is #$1"
  when /track=(.*)/
    puts "Track is #$1"
  when /artist=(.*)/
    puts "Artist is #$1"
end

Ruby classes are instances of class Class, which defines === as a test to see if the argument is an instance of the class or one of its superclasses. So (abandoning the benefits of polymorphism and bringing the gods of refactoring down around your ears), you can test the class of objects:

case shape
  when Square, Rectangle
    # ...
  when Circle
    # ...
  when Triangle
    # ...
  else
    # ...
end

循环

不要告诉他人，Ruby支持原始的灵巧的内建循环结构。

while循环根据它的条件的真假来执行0次或者多次语句，比如，下面程序将一直运行，直到输入被打断。

while gets
  # ...
end

util也可以用来循环，知道条件为真，才停止操作。

until playList.duration > 60
  playList.add(songList.pop)
end

像if和unless一样，while和until也可以用作语句操作符。

a *= 2 while a < 100
a -= 10 until a < 100

在前面的布尔表达式中，我们说过range也可以作为布尔表达式，这个机制多用于循环中，在下面的例子中，我们从一个包含从first到tenth的数字的文本文件中读取数据，但是只打印从以third开头的行，直到遇到fifth开头的行为止。

file = File.open("ordinal")
while file.gets
  print  if /third/ .. /fifth/
end
打印结果:
third
fourth
fifth

The elements of a range used in a boolean expression can themselves be expressions. These are evaluated each time the overall boolean expression is evaluated. For example, the following code uses the fact that the variable $. contains the current input line number to display line numbers one through three and those between a match of /eig/ and /nin/.

file = File.open("ordinal")
while file.gets
  print if ($. == 1) || /eig/ .. ($. == 3) || /nin/
end
produces:
first
second
third
eighth
ninth

这里有一点需要注意，当while和until用作语句修饰符的时候，如果它们修饰的语句以begin开头，end结尾，这段代码将总会执行，而不管后面的条件。

print "Hello\n" while false
begin
  print "Goodbye\n"
end while false
produces:
Goodbye

迭代Iterators

上面我们知道了，Ruby支持简单的循环，比如，Ruby没有for循环，而c和JAV等都支持for循环的，但是Ruby提供了其他的机制，比如迭代，提供了类似的功能。

让我们看看一个例子：

3.times do
  print "Ho! "
end
produces:
Ho! Ho! Ho!

这可以避免off-by-1 错误，这个循环将执行3次。除了times，整数还可以接收一些方法来执行循环，比如downto，upto，和step等。比如，传统的从0到9的循环(类似for( i=0; i < 10; i++)) 类似下面的样子：

0.upto(9) do |x|
  print x, " "
end
produces:
0 1 2 3 4 5 6 7 8 9

一个从0到12，步长为3的循环如下：

0.step(12, 3) {|x| print x, " " }
produces:
0 3 6 9 12

用于数组和其它容器的迭代的each方法也可以用来循环。

[ 1, 1, 2, 3, 5 ].each {|val| print val, " " }
produces:
1 1 2 3 5

如果一个类支持了each方法，那么在模块Enumerable 中的方法也可以直接使用。比如，File类提供了each方法，依次返回一个文件的每一行。使用Enumerable中的grep方法，我们可以只迭代符合条件的行。

File.open("ordinal").grep /d$/ do |line|
  print line
end
produces:
second
third

最后也是最简单的，Ruby提供了一个内建的最基本的迭代器loop。

loop {
  # block ...
}

loop迭代器一直调用给定的block（或者你调用了break跳出循环，后面会讲到）。

For ... In

前面我们说道Ruby支持的最基本循环视while和until，而for指的什么呢，可以看如下代码：

for aSong in songList
  aSong.play
end

Ruby将会把它翻译为如下：

songList.each do |aSong|
  aSong.play
end

for和each的唯一区别是局部变量的作用域。

你可以在支持each的类上使用for方法，比如Array或者Range。

for i in ['fee', 'fi', 'fo', 'fum']
  print i, " "
end
for i in 1..3
  print i, " "
end
for i in File.open("ordinal").find_all { |l| l =~ /d$/}
  print i.chomp, " "
end
produces:
fee fi fo fum 1 2 3 second third

一旦你的类支持了each方法，你就可以使用for来进行遍历。

class Periods
  def each
    yield "Classical"
    yield "Jazz"
    yield "Rock"
  end
end

periods = Periods.new
for genre in periods
  print genre, " "
end
produces:
Classical Jazz Rock

Break, Redo, 和 Next

循环控制结构 break, redo, 和 next 让你可以控制循环或者迭代器的流程。

break 立即结束当前循环，然后跳出去执行循环后面的语句。redo从这次循环体的头开始重新执行，但是不会在对条件进行运算或者从迭代中取下一个值。next跳到本次循环末尾，开始执行下一次循环。

while gets
  next if /^\s*#/   # skip comments
  break if /^END/   # stop at end
                    # substitute stuff in backticks and try again
  redo if gsub!(/`(.*?)`/) { eval($1) }
  # process line ...
end

这些关键字也可以用在基于迭代器的循环机制中。

i=0
loop do
  i += 1
  next if i < 3
  print i
  break if i > 4
end
produces:
345

Retry

redo使一个循环从当前迭代中重新执行。有时候，你需要从新开始一个循环，retry从新开始任何地迭代循环。

for i in 1..100
  print "Now at #{i}. Restart? "
  retry if gets =~ /^y/i
end

运行上面的程序，结果如下：

Now at 1. Restart? n
Now at 2. Restart? y
Now at 1. Restart? n
 . . .

retry 将重新计算条件值，然后再开始循环。Ruby文档有如下例子：

def doUntil(cond)
  yield
  retry unless cond
end

i = 0
doUntil(i > 3) {
  print i, " "
  i += 1
}
produces:
0 1 2 3 4

变量作用域和循环

while，until和for循环内建于Ruby语言之中，没有引入新的作用域，前面定义的局部变量可以在循环中使用，在循环中创建的变量在后面的代码也可以使用。

而对loop或each使用block来说则不一样了。在这个block中创建的变量在外面是不能访问的。

[ 1, 2, 3 ].each do |x|
  y = x + 1
end
[ x, y ]
produces:
prog.rb:4: undefined local variable or method `x'
for #<Object:0x401c2ce0> (NameError)

然而，如果block中的变量和前面已经定义的变量重名的话，已经存在的变量将会在块中使用，而在块执行完成后，这个变量的值也会改变。下面的例子，我们看到block执行之后，两个变量都改变了。

x = nil
y = nil
[ 1, 2, 3 ].each do |x|
  y = x + 1
end
[ x, y ] ? [3, 4]

Extracted from the book "Programming Ruby - The Pragmatic Programmer's Guide"
Copyright © 2001 by Addison Wesley Longman, Inc. This material may be distributed only subject to the terms and conditions set forth in the Open Publication License, v1.0 or later (the latest version is presently available at http://www.opencontent.org/openpub/)).

Distribution of substantively modified versions of this document is prohibited without the explicit permission of the copyright holder.

Distribution of the work or derivative of the work in any standard (paper) book form is prohibited unless prior permission is obtained from the copyright holder.


Programming Ruby: The Pragmatic Programmer's Guide

Programming Ruby

The Pragmatic Programmer's Guide

Previous < Contents ^
Next >

Exceptions, Catch, and Throw

So far we're been developing code in Pleasantville, a wonderful place where nothing ever, ever goes wrong. Every library call succeeds, users never enter incorrect data, and resources are plentiful and cheap. Well, that's about to change. Welcome to the real world!

In the real world, errors happen. Good programs (and programmers) anticipate them and arrange to handle them gracefully. This isn't always as easy as it might be. Often the code that detects an error does not have the context to know what to do about it. For example, attempting to open a file that doesn't exist is acceptable in some circumstances and is a fatal error at other times. What's your file-handling module to do?

The traditional approach is to use return codes. The open method returns some specific value to say it failed. This value is then propagated back through the layers of calling routines until someone wants to take responsibility for it.

The problem with this approach is that managing all these error codes can be a pain. If a function calls open, then read, and finally close, and each can return an error indication, how can the function distinguish these error codes in the value it returns to its caller?

To a large extent, exceptions solve this problem. Exceptions let you package up information about an error into an object. That exception object is then propagated back up the calling stack automatically until the runtime system finds code that explicitly declares that it knows how to handle that type of exception.

The Exception Class

The package that contains the information about an exception is an object of class Exception, or one of class Exception's children. Ruby predefines a tidy hierarchy of exceptions, shown in Figure 8.1 on page 91. As we'll see later, this hierarchy makes handling exceptions considerably easier.

Figure not available...

When you need to raise an exception, you can use one of the built-in Exception classes, or you can create one of your own. If you create your own, you might want to make it a subclass of StandardError or one of its children. If you don't, your exception won't be caught by default.

Every Exception has associated with it a message string and a stack backtrace. If you define your own exceptions, you can add additional information.

Handling Exceptions

Our jukebox downloads songs from the Internet using a TCP socket. The basic code is simple:

opFile = File.open(opName, "w")
while data = socket.read(512)
  opFile.write(data)
end

What happens if we get a fatal error halfway through the download? We certainly don't want to store an incomplete song in the song list. ``I Did It My *click*''.

Let's add some exception handling code and see how it helps. We enclose the code that could raise an exception in a begin/end block and use rescue clauses to tell Ruby the types of exceptions we want to handle. In this case we're interested in trapping SystemCallError exceptions (and, by implication, any exceptions that are subclasses of SystemCallError), so that's what appears on the rescue line. In the error handling block, we report the error, close and delete the output file, and then reraise the exception.

opFile = File.open(opName, "w")
begin
  # Exceptions raised by this code will
  # be caught by the following rescue clause
  while data = socket.read(512)
    opFile.write(data)
  end

rescue SystemCallError
  $stderr.print "IO failed: " + $!
  opFile.close
  File.delete(opName)
  raise
end

When an exception is raised, and independent of any subsequent exception handling, Ruby places a reference to the Exception object associated with the exception in the global variable $! (the exclamation point presumably mirroring our surprise that any of our code could cause errors). In the previous example, we used this variable to format our error message.

After closing and deleting the file, we call raise with no parameters, which reraises the exception in $!. This is a useful technique, as it allows you to write code that filters exceptions, passing on those you can't handle to higher levels. It's almost like implementing an inheritance hierarchy for error processing.

You can have multiple rescue clauses in a begin block, and each rescue clause can specify multiple exceptions to catch. At the end of each rescue clause you can give Ruby the name of a local variable to receive the matched exception. Many people find this more readable than using $! all over the place.

begin
  eval string
rescue SyntaxError, NameError => boom
  print "String doesn't compile: " + boom
rescue StandardError => bang
  print "Error running script: " + bang
end

How does Ruby decide which rescue clause to execute? It turns out that the processing is pretty similar to that used by the case statement. For each rescue clause in the begin block, Ruby compares the raised exception against each of the parameters in turn. If the raised exception matches a parameter, Ruby executes the body of the rescue and stops looking. The match is made using $!.kind_of?(parameter), and so will succeed if the parameter has the same class as the exception or is an ancestor of the exception. If you write a rescue clause with no parameter list, the parameter defaults to StandardError.

If no rescue clause matches, or if an exception is raised outside a begin/end block, Ruby moves up the stack and looks for an exception handler in the caller, then in the caller's caller, and so on.

Although the parameters to the rescue clause are typically the names of Exception classes, they can actually be arbitrary expressions (including method calls) that return an Exception class.

Tidying Up

Sometimes you need to guarantee that some processing is done at the end of a block of code, regardless of whether an exception was raised. For example, you may have a file open on entry to the block, and you need to make sure it gets closed as the block exits.

The ensure clause does just this. ensure goes after the last rescue clause and contains a chunk of code that will always be executed as the block terminates. It doesn't matter if the block exits normally, if it raises and rescues an exception, or if it is terminated by an uncaught exception---the ensure block will get run.

f = File.open("testfile")
begin
  # .. process
rescue
  # .. handle error
ensure
  f.close unless f.nil?
end

The else clause is a similar, although less useful, construct. If present, it goes after the rescue clauses and before any ensure. The body of an else clause is executed only if no exceptions are raised by the main body of code.

f = File.open("testfile")
begin
  # .. process
rescue
  # .. handle error
else
  puts "Congratulations-- no errors!"
ensure
  f.close unless f.nil?
end

Play It Again

Sometimes you may be able to correct the cause of an exception. In those cases, you can use the retry statement within a rescue clause to repeat the entire begin/end block. Clearly there is tremendous scope for infinite loops here, so this is a feature to use with caution (and with a finger resting lightly on the interrupt key).

As an example of code that retries on exceptions, have a look at the following, adapted from Minero Aoki's net/smtp.rb library.

@esmtp = true

begin
  # First try an extended login. If it fails because the
  # server doesn't support it, fall back to a normal login

  if @esmtp then
    @command.ehlo(helodom)
  else
    @command.helo(helodom)
  end

rescue ProtocolError
  if @esmtp then
    @esmtp = false
    retry
  else
    raise
  end
end

This code tries first to connect to an SMTP server using the EHLO command, which is not universally supported. If the connection attempt fails, the code sets the @esmtp variable to false and retries the connection. If this fails again, the exception is reraised up to the caller.

Raising Exceptions

So far we've been on the defensive, handling exceptions raised by others. It's time to turn the tables and go on the offensive. (There are those that say your gentle authors are always offensive, but that's a different book.)

You can raise exceptions in your code with the Kernel::raise method.

raise
raise "bad mp3 encoding"
raise InterfaceException, "Keyboard failure", caller

The first form simply reraises the current exception (or a RuntimeError if there is no current exception). This is used in exception handlers that need to intercept an exception before passing it on.

The second form creates a new RuntimeError exception, setting its message to the given string. This exception is then raised up the call stack.

The third form uses the first argument to create an exception and then sets the associated message to the second argument and the stack trace to the third argument. Typically the first argument will be either the name of a class in the Exception hierarchy or a reference to an object instance of one of these classes.[Technically, this argument can be any object that responds to the message exception by returning an object such that object.kind_of?(Exception) is true.] The stack trace is normally produced using the Kernel::caller method.

Here are some typical examples of raise in action.

raise

raise "Missing name" if name.nil?

if i >= myNames.size
  raise IndexError, "#{i} >= size (#{myNames.size})"
end

raise ArgumentError, "Name too big", caller

In the last example, we remove the current routine from the stack backtrace, which is often useful in library modules. We can take this further: the following code removes two routines from the backtrace.

raise ArgumentError, "Name too big", caller[1..-1]

Adding Information to Exceptions

You can define your own exceptions to hold any information that you need to pass out from the site of an error. For example, certain types of network errors might be transient depending on the circumstances. If such an error occurs, and the circumstances are right, you could set a flag in the exception to tell the handler that it might be worth retrying the operation.

class RetryException < RuntimeError
  attr :okToRetry
  def initialize(okToRetry)
    @okToRetry = okToRetry
  end
end

Somewhere down in the depths of the code, a transient error occurs.

def readData(socket)
  data = socket.read(512)
  if data.nil?
    raise RetryException.new(true), "transient read error"
  end
  # .. normal processing
end

Higher up the call stack, we handle the exception.

begin
  stuff = readData(socket)
  # .. process stuff
rescue RetryException => detail
  retry if detail.okToRetry
  raise
end

Catch and Throw

While the exception mechanism of raise and rescue is great for abandoning execution when things go wrong, it's sometimes nice to be able to jump out of some deeply nested construct during normal processing. This is where catch and throw come in handy.

catch (:done)  do
  while gets
    throw :done unless fields = split(/\t/)
    songList.add(Song.new(*fields))
  end
  songList.play
end

catch defines a block that is labeled with the given name (which may be a Symbol or a String). The block is executed normally until a throw is encountered.

When Ruby encounters a throw, it zips back up the call stack looking for a catch block with a matching symbol. When it finds it, Ruby unwinds the stack to that point and terminates the block. If the throw is called with the optional second parameter, that value is returned as the value of the catch. So, in the previous example, if the input does not contain correctly formatted lines, the throw will skip to the end of the corresponding catch, not only terminating the while loop but also skipping the playing of the song list.

The following example uses a throw to terminate interaction with the user if ``!'' is typed in response to any prompt.

def promptAndGet(prompt)
  print prompt
  res = readline.chomp
  throw :quitRequested if res == "!"
  return res
end

catch :quitRequested do
  name = promptAndGet("Name: ")
  age  = promptAndGet("Age:  ")
  sex  = promptAndGet("Sex:  ")
  # ..
  # process information
end

As this example illustrates, the throw does not have to appear within the static scope of the catch.

Extracted from the book "Programming Ruby - The Pragmatic Programmer's Guide"
Copyright © 2001 by Addison Wesley Longman, Inc. This material may be distributed only subject to the terms and conditions set forth in the Open Publication License, v1.0 or later (the latest version is presently available at http://www.opencontent.org/openpub/)).

Distribution of substantively modified versions of this document is prohibited without the explicit permission of the copyright holder.

Distribution of the work or derivative of the work in any standard (paper) book form is prohibited unless prior permission is obtained from the copyright holder.


Programming Ruby: The Pragmatic Programmer's Guide

Programming Ruby

The Pragmatic Programmer's Guide

上一章< 目录 ^
下一章>

模块 Modules

模块是一种集中方法，类和常量的方法，主要带来两个好处：
  1. 模块提供了一个命名空间（namespace ）防止命名冲突。
  2. 通过模块能实现混合插入（mixin）功能。

命名空间

当你写的Ruby程序越来越大，越来越多之后，都会发现有很多代码都可以重用，通常可以将相关例程组成一个库，分布到不同的文件以便被其它Ruby程序共享。

通常，这些代码都以类的形式组织在一起，所以你可能将一个类或者和其相关的几个类放到一个文件中。

但是，有时候你也许需要把一些不能在常理上认为属于一个类的东西组合到一起。

最初的办法可能是将所有这些东西都放到一个文件中，然后在需要使用它的程序中引入这个文件，就像C语言那样。但是，这样做有一个问题，比如你编写了一系列的三角函数sin，cos等，你将它们放到一个文件中，比如trig.rb，同时，Sally也做着类似的工作，创建了自己的库文件action.rb，包括自己的一些例程，其中有beGood和sin方法。Joe想编写一个程序，需要同时用到trig.rb和action.rb两个文件，但是这两个文件都定义了一个方法sin，这可不是什么好消息。

答案是模块机制。模块定义了一个命名空间，在这个空间里，你的方法和常量可以不必担心和别人的重名，比如三角函数（trig）就可以放到一个模块中：

module Trig
  PI = 3.141592654
  def Trig.sin(x)
   # ..
  end
  def Trig.cos(x)
   # ..
  end
end

然后Sally的方法可以放到另一个模块：

module Action
  VERY_BAD = 0
  BAD      = 1
  def Action.sin(badness)
    # ...
  end
end

模块中常量的命名和类中的一样，以大写字母开头。方法定义也类似，模块的方法定义和类方法定义类似，格式为mod_name.method_name。

如果第三个程序需要使用这些模块，只需要简单的把这些模块载入（使用Ruby的require语句，将在103页讨论），然后引用 限定的名字（ qualified names）。

require "trig"
require "action"

y = Trig.sin(Trig::PI/4)
wrongdoing = Action.sin(Action::VERY_BAD)

和类方法一样，调用一个模块方法也以这个模块名字为前缀然后一个点再加上方法名；引用一个模块的则是在模块后面加两个冒号再加常量。

混合插入（Mixins）

模块还有一个非常有用的作用，即通过模块使用叫做混合插入（mixin）的机制实现了多重继承。

在上一节的例子里，我们定义了模块方法，方法名前面加上了模块名作为前缀。如果这样让你想到这是类方法，那么你可能会进一步想“如果我在模块里面定义实例变量会怎样呢？”这个问题非常好，一个模块不能产生实例，因为它不是类。但是，你可以在一个类的定义里包含（include ）一个模块，这时候，这个模块中所有的实例方法都变成了在这个类所拥有（能使用）的方法了（all the module's instance methods are suddenly available as methods in the class as well）。这就叫做mixin，实际上，mix－in很像超类（superclasses）。

module Debug
def whoAmI?
"#{self.type.name} (\##{self.id}): #{self.to_s}"
end
end
class Phonograph
include Debug
# ...
end
class EightTrack
include Debug
# ...
end
ph = Phonograph.new("West End Blues")
et = EightTrack.new("Surrealistic Pillow")
ph.whoAmI? | ? | "Phonograph (#537766170): West End Blues"
et.whoAmI? | ? | "EightTrack (#537765860): Surrealistic Pillow"

通过包含Debug模块，Phonograph 和EightTrack 都能访问whoAmI?实例方法。

关于include语句需要注意几点。首先，这个语句和文件无关，C程序员也使用预处理指令#include来将一个文件的内容在编译的时候加入到另一个文件。而Ruby的include语句只是创建了一个指向一个有名字的模块的引用，如果这个模块在一个独立的文件中，那么你必须先用require将这个文件引入，然后才能使用include。第二，Ruby的include不是简单的将模块的实例方法拷贝到类里面，而是建立一个从类到模块的引用。如果很多个类都包含了同一个模块，它们都指向同一样东西。如果你修改了模块中一个方法的定义，即使你的程序还在运行之中，你的类也能使用新的方法的行为[ 注意，我们这里说的是实例方法，实例变量永远都是每个对象都有一份拷贝]

Mixin使得你能非常方便的给类增加方法，它的真正的强大之处在于能使模块中的代码和引入它的类中的代码能相互作用。我们以标准的一个Ruby模块Comparable 为例来说明，这个模块可以用来给类增加比较操作符（<,<= ,==,>=,>等）和between?方法。为了使得它能工作，Comparable 假设使用它的类都定义了 <=>操作符，所以作为类的创建者，你定一个<=>方法,，引入Comparable模块，然后，你就免费得到了6个其它的方法。在我们的Song类里，我们比较的基准是歌曲的时长。我们需要做的是增加<=>方法，和引入Comparable 模块。

class Song
  include Comparable
  def <=>(other)
    self.duration <=> other.duration
  end
end

然后，我们就可以检查一下结果了，看看它的比较功能。

song1 = Song.new("My Way", "Sinatra", 225)
song2 = Song.new("Bicylops", "Fleck", 260)
song1 <=> song2 | ? | -1
song1 < song2 | ? | true
song1 == song1 | ? | true
song1 > song2 | ? | false

最后，再看一下我们第43页实现的Smalltalk的inject方法，我们可以把它改得更通用一些，通过使用一个能mixin的模块。

module Inject
  def inject(n)
     each do |value|
       n = yield(n, value)
     end
     n
  end
  def sum(initial = 0)
    inject(initial) { |n, value| n + value }
  end
  def product(initial = 1)
    inject(initial) { |n, value| n * value }
  end
end

然后，我们就可以用一些内建类来测试一下：

class Array
include Inject
end
[ 1, 2, 3, 4, 5 ].sum | ? | 15
[ 1, 2, 3, 4, 5 ].product | ? | 120

class Range
include Inject
end
(1..5).sum | ? | 15
(1..5).product | ? | 120
('a'..'m').sum("Letters: ") | ? | "Letters: abcdefghijklm"

更多的关于mixin的例子，可以参看403页开始的关于Enumerable 模块的文档。

Mixins中的实例变量（Instance Variables）

从C++转到Ruby来的人经常问我们“在C++中，我们必须绕一些弯才能控制如何在多重继承中共享实例变量，mixin中的实例变量会怎么样呢？Ruby怎么处理这种情况呢？”

这对于初学者来说不是什么大问题，记住Ruby中的实例变量是如何工作的：以"@"作为前缀的变量作为当前对象self的实例变量。

对于混合插入来说，你要引入的模块可以在你的类里创建实例变量，并且可以用attr和friends来定义这些变量的访问器（accessor ）。比如：

（For a mixin, this means that the module that you mix into your client class (the mixee?) may create instance variables in the client object and may use attr and friends to define accessors for these instance variables. ）

module Notes
  attr  :concertA
  def tuning(amt)
    @concertA = 440.0 + amt
  end
end

class Trumpet
  include Notes
  def initialize(tune)
    tuning(tune)
    puts "Instance method returns #{concertA}"
    puts "Instance variable is #{@concertA}"
  end
end

# The piano is a little flat, so we'll match it
Trumpet.new(-5.3)
结果:
Instance method returns 434.7
Instance variable is 434.7

我们不仅访问了模块中的方法，我们也可以访问它的实例变量。但是，这样也会有一定的风险，比如不同的模块可能定义了一个同名的实例变量，从而产生了冲突。

module MajorScales
  def majorNum
    @numNotes = 7 if @numNotes.nil?
    @numNotes # Return 7
  end
end

module PentatonicScales
  def pentaNum
    @numNotes = 5 if @numNotes.nil?
    @numNotes # Return 5?
  end
end

class ScaleDemo
  include MajorScales
  include PentatonicScales
  def initialize
    puts majorNum # Should be 7
    puts pentaNum # Should be 5
  end
end

ScaleDemo.new
结果:
7
7

上面的两个模块中都定义了实例变量@numNotes，当然程序最后的结果也与作者的期望的结果不同。

一般来说，mixin的模块本身并不怎么携带实例数据，它们使用访问器（accessors ）来从对象取得数据。但是如果你想要创建一个必须要有自己状态的模块，请确定这个模块的实例变量的名字要唯一，不要和其它模块的重名（比如用模块名作为变量名的一部分）

迭代器和模块 Enumerable

你应该早就注意到了Ruby的集合类（collection classes）支持很多对它的处理：遍历，排序等等，没准你也会想要是自己的类支持这么多优秀的特点就更好了。

当然，答案是肯定的，通过使用混合插入这个有用的机制和模块Enumerable，你只需要再写一个名为each的迭代方法就可以了，在这个方法里，依次返回你自己的集合类的元素。混合插入Enumerable模块，然后你的类就支持了比如map，include?，find_all?等方法了。如果你的集合里的元素对象支持<=>方法，那么你的这个集合也可以得到min，max，sort方法。

包含（Including）其它文件

因为使用Ruby轻松的能编写优良的模块化的代码，你会经常发现自己会写一些包含自包含的代码，比如面向x的接口，用于y的算法等。一般的时候，我们会把这些文件作为类或者模块的库。

有了这些文件，如果你想把它们整合到你的新程序中，Ruby提供了两种方法：

load "filename.rb"

require "filename"

load方法每次执行都会包含一个Ruby源文件，而require只会包含一个文件一次，而且，require还有别的功能，它还能装载共享二进制的库文件（ load shared binary libraries）。这两个方法都接收相对和绝对的文件路径作为参数，如果给的是相对路径（或者只是一个文件名），系统将会在当前的装载路径（load path）中的每个文件夹下寻找这个文件（装载路径保存在全局变量$:中，见140页的讨论）

使用load和require包含的文件也可以包含其它文件。其中需要注意的是require是一个可执行的语句，它可以在一个if语句里使用，or it may include a string that was just built。包含时候的查找路径也可以在运行时候更改，你可以将需要的目录加到$:，这是一个数组。

因为load将无条件的装载应该源文件，你可以用它在运行时候重新装载一个可能在程序运行之后更改过的文件。

5.times do |i|
   File.open("temp.rb","w") { |f|
     f.puts "module Temp\ndef Temp.var() #{i}; end\nend"
   }
   load "temp.rb"
   puts Temp.var
 end
结果:
0
1
2
3
4

Extracted from the book "Programming Ruby - The Pragmatic Programmer's Guide"
Copyright © 2001 by Addison Wesley Longman, Inc. This material may be distributed only subject to the terms and conditions set forth in the Open Publication License, v1.0 or later (the latest version is presently available at http://www.opencontent.org/openpub/)).

Distribution of substantively modified versions of this document is prohibited without the explicit permission of the copyright holder.

Distribution of the work or derivative of the work in any standard (paper) book form is prohibited unless prior permission is obtained from the copyright holder.


Programming Ruby: The Pragmatic Programmer's Guide

Programming Ruby

实用程序员指南

上一章 < 目录 ^
下一章 >

基本输入输出

Ruby提供了两种I/O例程，第一种是简单的接口，我们已经用了很多了。

print "Enter your name: "
name = gets

Kernel模块提供了一整套和I/O相关的方法：gets, open, print, printf, putc, puts, readline, readlines, 和test等，这些方法能使你简单方便的进行Ruby编程。这些方法典型的对标准输入输出进行操作，很适合编写过滤器等。

另一方面，ruby提供了IO对象，让我们可以对io进行更多的控制。

什么是IO对象?

Ruby defines a single base class, IO, to handle input and output. This base class is subclassed by classes File and BasicSocket to provide more specialized behavior, but the principles are the same throughout. An IO object is a bidirectional channel between a Ruby program and some external resource.[For those who just have to know the implementation details, this means that a single IO object can sometimes be managing more than one operating system file descriptor. For example, if you open a pair of pipes, a single IO object contains both a read pipe and a write pipe.] There may be more to an IO object than meets the eye, but in the end you still simply write to it and read from it.

In this chapter, we'll be concentrating on class IO and its most commonly used subclass, class File. For more details on using the socket classes for networking, see the section beginning on page 469.

Opening and Closing Files

As you might expect, you can create a new file object using File.new .

aFile = File.new("testfile", "r")

# ... process the file

aFile.close

You can create a File object that is open for reading, writing, or both, according to the mode string (here we opened ``testfile'' for reading with an ``r''). The full list of allowed modes appears on page 326. You can also optionally specify file permissions when creating a file; see the description of File.new on page 303 for details. After opening the file, we can work with it, writing and/or reading data as needed. Finally, as responsible software citizens, we close the file, ensuring that all buffered data is written and that all related resources are freed.

But here Ruby can make life a little bit easier for you. The method File.open also opens a file. In regular use, it behaves just like File.new . However, if there's a block associated with the call, open behaves differently. Instead of returning a new File object, it invokes the block, passing the newly opened File as a parameter. When the block exits, the file is automatically closed.

File.open("testfile", "r") do |aFile|

# ... process the file

end

Reading and Writing Files

The same methods that we've been using for ``simple'' I/O are available for all file objects. So, gets reads a line from standard input, and aFile.gets reads a line from the file object aFile.

However, I/O objects enjoy an additional set of access methods, all intended to make our lives easier.

Iterators for Reading

As well as using the usual loops to read data from an IO stream, you can also use various Ruby iterators. IO#each_byte invokes a block with the next 8-bit byte from the IO object (in this case, an object of type File).

aFile = File.new("testfile")
aFile.each_byte {|ch| putc ch; putc ?. }
produces:
T.h.i.s. .i.s. .l.i.n.e. .o.n.e.
.T.h.i.s. .i.s. .l.i.n.e. .t.w.o.
.T.h.i.s. .i.s. .l.i.n.e. .t.h.r.e.e.
.A.n.d. .s.o. .o.n.......
.

IO#each_line calls the block with the next line from the file. In the next example, we'll make the original newlines visible using String#dump , so you can see that we're not cheating.

aFile.each_line {|line| puts "Got #{line.dump}" }
produces:
Got "This is line one\n"
Got "This is line two\n"
Got "This is line three\n"
Got "And so on...\n"

You can pass each_line any sequence of characters as a line separator, and it will break up the input accordingly, returning the line ending at the end of each line of data. That's why you see the ``\n'' characters in the output of the previous example. In the next example, we'll use ``e'' as the line separator.

aFile.each_line("e") do |line|
  puts "Got #{ line.dump }"
end
produces:
Got "This is line"
Got " one"
Got "\nThis is line"
Got " two\nThis is line"
Got " thre"
Got "e"
Got "\nAnd so on...\n"

If you combine the idea of an iterator with the auto-closing block feature, you get IO.foreach . This method takes the name of an I/O source, opens it for reading, calls the iterator once for every line in the file, and then closes the file automatically.

IO.foreach("testfile") { |line| puts line }
produces:
This is line one
This is line two
This is line three
And so on...

Or, if you prefer, you can retrieve an entire file into an array of lines:

arr = IO.readlines("testfile")
arr.length | ?/td> | 4
arr[0] | ?/td> | "This is line one\n"

Don't forget that I/O is never certain in an uncertain world---exceptions will be raised on most errors, and you should be ready to catch them and take appropriate action.

Writing to Files

So far, we've been merrily calling puts and print, passing in any old object and trusting that Ruby will do the right thing (which, of course, it does). But what exactly is it doing?

The answer is pretty simple. With a couple of exceptions, every object you pass to puts and print is converted to a string by calling that object's to_s method. If for some reason the to_s method doesn't return a valid string, a string is created containing the object's class name and id, something like <ClassName:0x123456>.

The exceptions are simple, too. The nil object will print as the string ``nil,'' and an array passed to puts will be written as if each of its elements in turn were passed separately to puts.

What if you want to write binary data and don't want Ruby messing with it? Well, normally you can simply use IO#print and pass in a string containing the bytes to be written. However, you can get at the low-level input and output routines if you really want---have a look at the documentation for IO#sysread and IO#syswrite on page 335.

And how do you get the binary data into a string in the first place? The two common ways are to poke it in byte by byte or to use Array#pack .

str = "" | ?/td> | ""
str << 1 << 2 << 3 | ?/td> | "\001\002\003"
[ 4, 5, 6 ].pack("c*") | ?/td> | "\004\005\006"

But I Miss My C++ Iostream

Sometimes there's just no accounting for taste...However, just as you can append an object to an Array using the << operator, you can also append an object to an output IO stream:

endl = "\n"
$stdout << 99 << " red balloons" << endl
produces:
99 red balloons

Again, the << method uses to_s to convert its arguments to strings before sending them on their merry way.

与网络交互

Ruby支持很多网络协议，不管是高层的还是底层的。

ruby提供了一些基本类，让你可以使用TCP,UDP,SOCKS等很多协议交互，而不必拘泥在网络层。这些类也提供了辅助类，让你可以轻松的对服务器进行读写。这个例子利用finger协议查询用户oracle的信息：

require 'socket'
client = TCPSocket.open('localhost', 'finger')
client.send("oracle\n", 0)    # 0 means standard packet
puts client.readlines
client.close
结果:
Login: oracle         			Name: Oracle installation
Directory: /home/oracle             	Shell: /bin/bash
Never logged in.
No Mail.
No Plan.

对于高层，lib/net里面提供了一些与应用层协议（FTP,HTTP,POP,SMTP,TELNET）等交互的库模块。比如，下面的例子列出了Pragmatic Programmer主页里的图像。

require 'net/http'

h = Net::HTTP.new('www.pragmaticprogrammer.com', 80)
resp, data = h.get('/index.html', nil)
if resp.message == "OK"
  data.scan(/<img src="(.*?)"/) { |x| puts x }
end
produces:
images/title_main.gif
images/dot.gif
images/dot.gif
images/dot.gif
images/aafounders_70.jpg
images/pp_cover_thumb.png
images/ruby_cover_thumb.png
images/dot.gif
images/dot.gif

Extracted from the book "Programming Ruby - The Pragmatic Programmer's Guide"
Copyright © 2001 by Addison Wesley Longman, Inc. This material may be distributed only subject to the terms and conditions set forth in the Open Publication License, v1.0 or later (the latest version is presently available at http://www.opencontent.org/openpub/)).

Distribution of substantively modified versions of this document is prohibited without the explicit permission of the copyright holder.

Distribution of the work or derivative of the work in any standard (paper) book form is prohibited unless prior permission is obtained from the copyright holder.


Programming Ruby: The Pragmatic Programmer's Guide

Programming Ruby

实用程序员指南

上一章 < 目录 ^
下一章 >

线程和进程

Ruby给了你两个基本的方法来组织你的程序，使它同时能运行自己的不同部分。你可以使用线程在程序内部将任务分割，或者将任务分解为不同的程序，使用多进程来运行。下面我们轮流看一下这两种方法。

多线程

一般来说在Ruby中同时做两件事情最简单的是使用Ruby线程。线程在进程中，由Ruby解释器实现。这使得Ruby线程也能完全的可移至，因为它不需要依赖特定的操作系统，但是这样你也不能利用本地线程（native threads）的优点了。你也许有过线程饥饿得经验（优先级低的线程没有机会运行）。也许你会遇到线程死锁，整个进程都被挂起。或者一些线程的某些操作占用了CPU的太多时间，以至于其它线程不得不等待。但是，不要被这些潜在的问题吓倒，Ruby线程是使你程序并行运行的轻量而有效的方法。

创建 Ruby 线程

创建一个新的线程十分简单，下面的部分代码并行的下载一些网页，每次有请求调用，这段代码都将产生一个独立的线程处理HTTP传输。

require 'net/http'

pages = %w( www.rubycentral.com
            www.awl.com
            www.pragmaticprogrammer.com
           )

threads = []

for page in pages
  threads << Thread.new(page) { |myPage|

    h = Net::HTTP.new(myPage, 80)
    puts "Fetching: #{myPage}"
    resp, data = h.get('/', nil )
    puts "Got #{myPage}:  #{resp.message}"
  }
end

threads.each { |aThread|  aThread.join }

produces:

Fetching: www.rubycentral.com
Fetching: www.awl.com
Fetching: www.pragmaticprogrammer.com
Got www.rubycentral.com:  OK
Got www.pragmaticprogrammer.com:  OK
Got www.awl.com:  OK

让我们更详细的看看这段代码，这里有一些新技巧在里面。

新线程用 Thread.new 创建，这个方法接收一个block作为线程中要运行的代码，在我们的例子里面，这个block使用net/http库从每个指定的站点抓取首页，从我们打印出来的信息来看，这些抓取活动是同时进行的。

当我们创建线程的时候，我们将这个HTML页面作为参数传入，这个参数然后作为myPage参数传给了block。为什么我们这么做而不是直接在block里面用page这个变量那？

一个线程共享了所有在它启动之前已经存在的所有全局变量，实例变量和局部变量。善意的人有时候会告诉你，共享有时候不一定是好事。在这个例子里面，3个线程将共享page变量，第一个线程启动之后，page被设为http://www.rubycentral.com，在这个时候，创建线程的循环还没有结束，第二次，page被设为http://www.awl.com，如果第一个线程还没有使用page变量运行完毕，那么可能这个线程会使用page的新值。这个bug将很难被跟踪发现。

但是，在线程块中创建的局部变量的作用域只在创建它的线程里，而不能被其它线程共享。在我们的例子里面，变量myPage将在线程被创建时赋值，每个线程都有自己的myPage变量的拷贝。

多线程

另一个很微妙的地方是程序的最后一行，为什么我们调用所创每个建线程的join方法？

当一个Ruby程序结束退出的时候，它会杀死所有的线程，而不管它们的状态。但是，你可以通过线程的 Thread#join 方法，使得主程序在等待这个线程结束后再退出。调用它的线程将会被阻塞，直到给定的线程结束。通过调用3个线程的join方法，你可以确定这三个请求将会在主程序退出之前完成。

 除了join，还有其它几个用于控制线程的方便的方法。首先，你可以用 Thread.current 来访问当前线程，你可以用 Thread.list 来取得所有线程列表，这个列表包括所有可运行或者已停止的线程。为了检测一个线程的状态，可以用方法 Thread#status 和 Thread#alive? 。

另外，你可以使用 Thread#priority= 来设置线程的不同的优先级别。高优先级的将会先于低优先级的线程执行。

线程变量

在上面我们已经说过，一个线程可以访问在它定义之前已经存在的，在作用域范围内的变量，但是，在线程内部定义的变量的作用域只在这个线程内部有效。

但是，如果你想一个线程的变量能被其它线程访问，包括主线程，该怎么办呢？Ruby中的线程提供了一个功能，就是能够按名字创建和访问线程内的局部变量。你可以简单的把这个线程对象作为一个哈希，使用[ ]=方法写这个对象的变量值，用 [ ]来读它的值。在这个例子里， 每个线程记录当前变量count的值，把它存到线程的局部变量，名字（key）为mycount。（这里有竞争条件的出现，但是我们还没有谈到同步问题，这里我们只是忽略它们。）

count = 0
arr = []
10.times do |i|
  arr[i] = Thread.new {
    sleep(rand(0)/10.0)
    Thread.current["mycount"] = count
    count += 1
  }
end
arr.each {|t| t.join; print t["mycount"], ", " }
puts "count = #{count}"

产生结果:

8, 0, 3, 7, 2, 1, 6, 5, 4, 9, count = 10

主线程等待每个子线程结束之后，打印出来每个线程得到的count的值。我们人为的让每个线程在取得count值之前休眠随机的时间，这只是为了增加点趣味而已。

线程和异常

如果一个线程抛出一个没有被处理的异常，将会怎样呢？这依赖于系统设置Thread.abort_on_exception ，这个设置在第384页和387页。

如果abort_on_exception 被设置为false，这也是默认的缺省值，那么如果一个线程出现错误而没有处理，则这个线程将会被杀死，其它没有遇到异常的线程将继续运行。在下面的例子里，编号为3的线程将产生一个异常，而不会输出任何东西，但是，你仍然可以看到其它线程的输出。

threads = []
6.times { |i|
  threads << Thread.new(i) {
    raise "Boom!" if i == 3
    puts i
  }
}
threads.each {|t| t.join }

产生:

01
2

45prog.rb:4: Boom! (RuntimeError)
    from prog.rb:8:in `join'
    from prog.rb:8
    from prog.rb:8:in `each'
    from prog.rb:8

但是，如果将abort_on_exception 设为true，一个线程出现没有捕获（处理）的异常，则所有的线程将都被杀死，上面的例子，如果编号为3的线程出错，所有后面的线程都被杀死，不会产生任何输出。

Thread.abort_on_exception = true
threads = []
6.times { |i|
  threads << Thread.new(i) {
    raise "Boom!" if i == 3
    puts i
  }
}
threads.each {|t| t.join }

produces:

01
2
prog.rb:5: Boom! (RuntimeError)
    from prog.rb:7:in `initialize'
    from prog.rb:7:in `new'
    from prog.rb:7
    from prog.rb:3:in `times'
    from prog.rb:3

控制线程调度器

在一个设计良好的应用程序中，你应该让线程只做自己改作的事情；在一个多线程环境中创建一个基于时间的系统一般来说不是一个好主意。

但是，有时候我们需要控制线程的运行。也许我们的自动点唱机有一个线程用来控制指示灯，我们希望在音乐停止播放的时候也停止指示灯。你也许在一个经典的生产者－消费者关系中有两个线程，一个消费者在生产者挂起的时候也必须挂起。

类Thread 提供了很多方法用来控制线程调度，调用 Thread.stop 能停止当前线程，而Thread#run 将使某个线程启动运行，调用Thread.pass 将告诉线程调度器去执行另外一个线程。  Thread#join 和 Thread#value 将使调用者挂起，直到这个线程结束。

我们可以用下面代码来示范一下上面的特点。

t = Thread.new { sleep .1; Thread.pass; Thread.stop; }
t.status | ? | "sleep"
t.run
t.status | ? | "run"
t.run
t.status | ? | false

但是，使用这些原始的方法来控制线程调度实现同步，不管怎么说，都可能会遇到竞争条件。如果你需要在线程中共享数据，竞争条件将会一直存在并且给调试带来麻烦。幸运的是，线程还有另一个工具：临界区（critical section），使用它，我们能编写一个安全的同步方案。

互斥（Mutual Exclusion）

用来阻塞一个线程运行的低层的方法是使用全局的"线程临界"（thread critical）条件。当这个条件被设为true（用 Thread.critical= 方法）时，调度器将不会让任何已经存在地线程执行，但是，它不会阻止新线程的建立和运行；一些特定的线程操作（比如停止或者杀死一个线程，在当前线程中休眠，或者抛出一个异常）都会引起一个线程被调度，即使在临界区之内。

直接使用 Thread.critical= 虽然可行，但是它并不是很方便。幸运的是，Ruby自带了很多其它选项，当然，最好的两个是thread库模块中的类Mutex和类ConditionVariable。关于它们的文档从第457页开始。

类 Mutex

Mutex 是一个为对互斥地访问某一共享对象而设计的一个简单的信号量锁。也就是说，在一个时候，只有一个线程能持有这个锁。其它线程可以继续等待直到这个锁可用，或者立即返回一个错误不再继续等待。

一个mutex经常用于原子性的对一个共享对象进行修改更新。假设我们需要更新一个事务中的两个变量，比如下面的程序模拟增加两个数的计数。这个更新假定是原子性的，外面的世界不可能看到这两个数有不同的值。如果不使用互斥，则不能达到该目的。

count1 = count2 = 0
difference = 0
counter = Thread.new do
loop do
count1 += 1
count2 += 1
end
end
spy = Thread.new do
loop do
difference += (count1 - count2).abs
end
end
sleep 1
Thread.critical = 1
count1 | ? | 184846
count2 | ? | 184846
difference | ? | 58126

这个例子显示了在执行的过程中count1和count2的值曾经出现过不同，尽管最后还是一样的。
幸运的是，我们可以用互斥来改善这个例子。

require 'thread'
mutex = Mutex.new

count1 = count2 = 0
difference = 0
counter = Thread.new do
  loop do
    mutex.synchronize do
      count1 += 1
      count2 += 1
    end
  end
end
spy = Thread.new do
  loop do
    mutex.synchronize do
      difference += (count1 - count2).abs
    end
  end
end

sleep 1
mutex.lock
count1 | ? | 21192
count2 | ? | 21192
difference | ? | 0

通过把需要访问共享数据的代码放到muxtex的控制下，我们确保了数据的一致性。但不幸的是，你也从这些数字看到了，我们在经受着性能上的损失。

条件变量（Condition Variables）

有时候使用互斥（mutex ）来保护对临界数据的访问并不能满足要求，比如假设我们在一个临界区内，但是你还需要等待一个特殊的资源，如果你的线程这时候为了等待这个资源而休眠，可能会导致其它线程不能释放这个资源，因为它们都无法进入这个临界区，原来的线程一直在锁定着这个临界区。你也许需要暂时的放弃对临界区的控制，同时告诉其它线程你在等待某一资源。当这个资源可用之后，你的线程同时需要重新得到对临界区的控制权。

条件变量正是用在此处。一个条件变量是一个简单的信号量，它关联一个特定的资源，在临界区的保护范围内使用。当你需要一个资源而这个资源暂时不可用的时候，你等待一个条件变量，这个操作将放弃对这个条件变量所在互斥（临界区？）的锁定。当其它线程发送信号告诉这个变量可用之后，原来的线程停止等待立即取得对临界区的锁定。

require 'thread'
mutex = Mutex.new
cv = ConditionVariable.new

a = Thread.new {
  mutex.synchronize {
    puts "A: I have critical section, but will wait for cv"
    cv.wait(mutex)
    puts "A: I have critical section again! I rule!"
  }
}

puts "(Later, back at the ranch...)"

b = Thread.new {
  mutex.synchronize {
    puts "B: Now I am critical, but am done with cv"
    cv.signal
    puts "B: I am still critical, finishing up"
  }
}
a.join
b.join

结果：

A: I have critical section, but will wait for cv(Later, back at the ranch...)

B: Now I am critical, but am done with cv
B: I am still critical, finishing up
A: I have critical section again! I rule!

另一个同步机制的实现，可以参考Ruby发布程序中lib文件夹下的文件 monitor.rb 和sync.rb。

运行多个进程（Multiple Processes）

有时候你可能需要把一个任务分成几个进程级别的子任务，或者你需要运行一个另外的不使用Ruby写的程序，没关系，Ruby有好几种方法能使你创建和管理其它独立的进程。

产生一个新的进程

在Ruby中产生一个新的进程有好几种方法，最简单的方法是运行一个命令并且等到它结束。你也许运行一些其它的独立的命令，并且从主机得到返回的结果，Ruby提供了system方法和反引号方法。（反引号即"`"）

system("tar xzf test.tgz") | ? | tar: test.tgz: Cannot open: No such file or directory\ntar: Error is not recoverable: exiting now\ntar: Child returned status 2\ntar: Error exit delayed from previous errors\nfalse
result = `date` |  |
result | ? | "Sun Jun 9 00:08:50 CDT 2002\n"

方法 Kernel::system 运行一个指定的命令，如果这个命令存在且正确的运行结束，这个方法返回true，否则返回false。如果这个命令运行失败，你可以从全局变量$?得到这个命令的返回代码。

但system也有一个问题，就是它所运行程序的输出简单的被指定到了你的程序的输出，这可能不是你想要的。为了取得子进程的标准输出，你可以用反引号，比如上面例子的 `date` 。注意，你需要用 String#chomp 来去除返回结果最后的换行符。

这中方法对简单的场合比较合适，我们只需要运行一个命令，然后取得它的返回结果。但是，很多时候我们都需要对进程有更多的控制，比如我们需要和子进程进行会话，向它输入数据，并且从它取回数据。方法 IO.popen 正是具有这样的作用。popen方法以一个子进程来运行一个命令，并且把这个进程的标准输入和标准输出绑定到Ruby的IO对象。向IO对象写数据，子进程就可以从它的标准输入读取数据，而子进程输出的数据，也可以通过Ruby的IO对象读出来。
比如，我们的操作系统中有一个有用的程序叫做pig，这个程序从标准输入读入数据，然后以pig Latin方式打印这些数据。

pig = IO.popen("pig", "w+")
pig.puts "ice cream after they go to bed"
pig.close_write
puts pig.gets

produces:

iceway eamcray afterway eythay ogay otay edbay

这个例子看起来很简单，打开这个管道（pipe），写入一个短语，然后读取返回结果。但是pig程序并不会立即将它写的东西flush。假如上面例子中，如果pig.puts 后面紧跟pig.gets的话，程序将被挂起，pig程序处理了我们的输入，但是返回结果却一直不会被写到管道。我们必须在这两行之间插入 pig.close_write ，这将给pig的标准输入发送一个文件结束标志（end-of-file），然后我们需要的结果就会返回。

popen方法还有另外一些注意事项。如果指定的命令是一个减号（"-"），Ruby将强迫产生一个新的Ruby解释器，它将和原来的解释器一起运行。原来的解释器进程将得到一个IO对象作为返回结果，而子解释器将得到nil。

pipe = IO.popen("-","w+")
if pipe
  pipe.puts "Get a job!"
  $stderr.puts "Child says '#{pipe.gets.chomp}'"
else
  $stderr.puts "Dad says '#{gets.chomp}'"
  puts "OK"
end

produces:

Dad says 'Get a job!'
Child says 'OK'

除了popen方法，传统的Unix调用 Kernel::fork ， IO.pipe 和 Kernel::exec 也可以在支持它们的系统上使用。许多IO方法和 Kernel::open 也能产生新的子进程，使用方法是将文件名前面加上一个竖线``|'' 。注意你不能用 File.new 来产生一个子进程，这个方法只是用于文件。

Independent Children

有时候我们并不需要这样处理：我们只想把产生的子进程赋给一个变量，然后继续处理自己的事务。一段时间以后，我们也许还需要一下这个进程是否结束。比如，我们需要从主程序分离一个需要很长运行时间的外部排序：

exec("sort testfile > output.txt") if fork == nil
# The sort is now running in a child process
# carry on processing in the main program

# then wait for the sort to finish
Process.wait

系统调用 Kernel::fork 在父进程中返回fork产生的子进程id，在子进程中返回nil，所以，上面例子中子进程将调用 Kernel::exec 来运行一个外部的排序。一段时间以后，我们使用 Process::wait ，这将等待排序完成，然后返回这个进程的id。（pid）

如果你需要在子进程退出后通知父进程（而不是等待子进程结束），可以用 Kernel::trap 来对返回的信号进行处理。比如这里我们建立了一个用于捕获SIGCLD信号的trap，这个信号的意思是“子进程结束（死亡）”

trap("CLD") {
  pid = Process.wait
  puts "Child pid #{pid}: terminated"
  exit
}

exec("sort testfile > output.txt") if fork == nil

# do other stuff...

produces:

Child pid 31842: terminated

块（Block）和子进程

IO.popen 也能像 File.open 那样接受一个block。通过传递一个参数给 popen 作为一个命令，比如 date，然后，这个block将得到一个IO对象作为参数。

IO.popen ("date") { |f| puts "Date is #{f.gets}" }

produces:

Date is Sun Jun  9 00:08:50 CDT 2002

这个IO对象将会在BLOCK结束之后自动关闭，就如同 File.open 一样。

如果你给 Kernel::fork 提供一个block，那么这些block中的代码将在Ruby的子进程中运行，而父进程在block结束后继续运行。

fork do
  puts "In child, pid = #$$"
  exit 99
end
pid = Process.wait
puts "Child terminated, pid = #{pid}, exit code = #{$? >> 8}"

produces:

In child, pid = 31849
Child terminated, pid = 31849, exit code = 99

最后一个问题，为什么我们子进程的返回代码 $? 要右移8位？这是Posix系统的特点，退出代码（exit code）的低8位是程序结束的原因，高8位才是真正的退出代码。

Extracted from the book "Programming Ruby - The Pragmatic Programmer's Guide"
Copyright © 2001 by Addison Wesley Longman, Inc. This material may be distributed only subject to the terms and conditions set forth in the Open Publication License, v1.0 or later (the latest version is presently available at http://www.opencontent.org/openpub/)).

Distribution of substantively modified versions of this document is prohibited without the explicit permission of the copyright holder.

Distribution of the work or derivative of the work in any standard (paper) book form is prohibited unless prior permission is obtained from the copyright holder.


Programming Ruby: The Pragmatic Programmer's Guide

Programming Ruby

The Pragmatic Programmer's Guide

Previous < Contents ^
Next >

When Trouble Strikes

Sad to say, it is possible to write buggy programs using Ruby. Sorry about that.

But not to worry! Ruby has several features that will help debug your programs. We'll look at these features, and then we'll show some common mistakes you can make in Ruby and how to fix them.

Ruby Debugger

Ruby comes with a debugger, which is conveniently built into the base system. You can run the debugger by invoking the interpreter with the -r debug option, along with any other Ruby options and the name of your script:

ruby -r debug [
            options
            ] [
            programfile
            ] [
            arguments
            ]

The debugger supports the usual range of features you'd expect, including the ability to set breakpoints, to step into and step over method calls, and to display stack frames and variables.

It can also list the instance methods defined for a particular object or class, and allows you to list and control separate threads within Ruby. Table 12.1 on page 131 lists all of the commands that are available under the debugger.

If your Ruby has readline support enabled, you can use cursor keys to move back and forth in command history and use line editing commands to amend previous input.

To give you an idea of what the Ruby debugger is like, here is a sample session.

%
              ruby -rdebug t.rb

Debug.rb
Emacs support available.
t.rb:1:def fact(n)
(rdb:1)
              list 1-9

[1, 10] in t.rb
=> 1  def fact(n)
   2    if n <= 0
   3      1
   4    else
   5      n * fact(n-1)
   6    end
   7  end
   8
   9  p fact(5)
(rdb:1)
              b 2

Set breakpoint 1 at t.rb:2
(rdb:1)
              c

breakpoint 1, fact at t.rb:2
t.rb:2:  if n <= 0
(rdb:1)
              disp n

  1: n = 5
(rdb:1)
              del 1

(rdb:1)
              watch n==1

Set watchpoint 2
(rdb:1)
              c

watchpoint 2, fact at t.rb:fact
t.rb:1:def fact(n)
1: n = 1
(rdb:1)
              where

--> #1  t.rb:1:in `fact'
    #2  t.rb:5:in `fact'
    #3  t.rb:5:in `fact'
    #4  t.rb:5:in `fact'
    #5  t.rb:5:in `fact'
    #6  t.rb:9
(rdb:1)
              del 2

(rdb:1)
              c

120

Interactive Ruby

If you want to play with Ruby, there is a facility called Interactive Ruby---irb, for short. irb is essentially a Ruby ``shell'' similar in concept to an operating system shell (complete with job control). It provides an environment where you can ``play around'' with the language in real time. You launch irb at the command prompt:

irb [
            irb-options
            ] [
            ruby_script
            ] [
            options
            ]

irb will display the value of each expression as you complete it. For instance:

% irb
irb(main):001:0>
              a = 1 +

irb(main):002:0*
              2 * 3 /

irb(main):003:0*
              4 % 5

2
irb(main):004:0>
              2+2

4
irb(main):005:0>
              def test

irb(main):006:1>
              puts "Hello, world!"

irb(main):007:1>
              end

nil
irb(main):008:0>
              test

Hello, world!
nil
irb(main):009:0>

irb also allows you to create subsessions, each one of which may have its own context. For example, you can create a subsession with the same (top-level) context as the original session, or create a subsession in the context of a particular class or instance. The sample session shown in Figure 12.1 on page 126 is a bit longer, but shows how you can create subsessions and switch between them.

Figure not available...

For a full description of all the commands that irb supports, see the reference beginning on page 517.

As with the debugger, if your version of Ruby was built with GNU Readline support, you can use arrow keys (as with Emacs) or vi-style key bindings to edit individual lines or to go back and reexecute or edit a previous line---just like a command shell.

irb is a great learning tool: it's very handy if you want to try out an idea quickly and see if it works.

Editor Support

Ruby is designed to read a program in one pass; this means you can pipe an entire program to Ruby's standard input and it will work just fine.

We can take advantage of this feature to run Ruby code from inside an editor. In Emacs, for instance, you can select a region of Ruby text and use the command Meta-| to execute Ruby. The Ruby interpreter will use the selected region as standard input and output will go to a buffer named ``*Shell Command Output*.'' This feature has come in quite handy for us while writing this book---just select a few lines of Ruby in the middle of a paragraph and try it out!

You can do something similar in the vi editor using ``:!ruby'' which replaces the program text with its output, or ``:w [visible space]!ruby'', which displays the output without affecting the buffer. Other editors have similar features.

While we are on the subject, this would probably be a good place to mention that there is a Ruby mode for Emacs included in the distribution as misc/ruby-mode.el. There are also several syntax-highlighting modules for vim (an enhanced version of the vi editor), jed, and other editors available on the net as well. Check the Ruby FAQ for current locations and availability.

But It Doesn't Work!

So you've read through enough of the book, you start to write your very own Ruby program, and it doesn't work. Here's a list of common gotchas and other tips.

  • Attribute setter not being called. Within an object, Ruby will parse setter= as an assignment to a local variable, not as a method call. Use self.setter= to indicate the method call.

  • A parse error at the last line of the source often indicates a missing end keyword.

  • Make sure that the type of the object you are using is what you think it is. If in doubt, use Object#type to check the type of an object.

  • Make sure that your methods start with a lowercase letter and that classes and constants start with an uppercase letter.

  • If you happen to forget a ``,'' in an argument list---especially to print---you can produce some very odd error messages.

  • Block parameters are actually local variables. If an existing local of the same name exists when the block executes, that variable will be modified by the call to the block. This may or may not be a good thing.

  • Watch out for precedence, especially when using {} instead of do/end.

  • Make sure that the open parenthesis of a method's parameter list butts up against the end of the method name with no intervening spaces.

  • Output written to a terminal may be buffered. This means that you may not see a message you write immediately. In addition, if you write messages to both $stdout and $stderr, the output may not appear in the order you were expecting. Always use nonbuffered I/O (set sync=true) for debug messages.

  • If numbers don't come out right, perhaps they're strings. Text read from a file will be a String, and will not be automatically converted to a number by Ruby. A call to to_i will work wonders. A common mistake Perl programmers make is:

    while gets
  num1, num2 = split /,/
  # ...
end

  • Unintended aliasing---if you are using an object as the key of a hash, make sure it doesn't change its hash value (or arrange to call Hash#rehash if it does).

  • Use trace_var to watch when a variable changes value.

  • Use the debugger.

  • Use Object#freeze . If you suspect that some unknown portion of code is setting a variable to a bogus value, try freezing the variable. The culprit will then be caught during the attempt to modify the variable.

There's one major technique that makes writing Ruby code both easier and more fun. Develop your applications incrementally. Write a few lines of code, then run them. Write a few more, then run those. One of the major benefits of an untyped language is that things don't have to be complete before you use them.

But It's Too Slow!

Ruby is an interpreted, high-level language, and as such it may not perform as fast as a lower-level language such as C. In this section, we'll list some basic things you can do to improve performance; also have a look in the index under Performance for other pointers.

Create Locals Outside Blocks

Try defining the variables used in a block before the block executes. When iterating over a very large set of elements, you can improve execution speed somewhat by predeclaring any iterator variables. In the first example below, Ruby has to create new x and y variables on each iteration, but in the second version it doesn't. We'll use the benchmark package from the Ruby Application Archive to compare the loops:

require "benchmark"
include Benchmark
n = 1000000
bm(12) do |test|
  test.report("normal:")    do
    n.times do |x|
      y = x + 1
    end
  end
  test.report("predefine:") do
    x = y = 0
    n.times do |x|
      y = x + 1
    end
  end
end
produces:
                  user     system      total        real
normal:       2.450000   0.020000   2.470000 (  2.468109)
predefine:    2.140000   0.020000   2.160000 (  2.155307)

Use the Profiler

Ruby comes with a code profiler (documentation begins on on page 454). In and of itself, that isn't too surprising, but when you realize that the profiler is written in just about 50 lines of Ruby, that makes for a pretty impressive language.

You can add profiling to your code using the command-line option -r  profile, or from within the code using require "profile". For example:

require "profile"
class Peter
  def initialize(amt)
    @value = amt
  end

  def rob(amt)
    @value -= amt
    amt
  end
end

class Paul
  def initialize
    @value = 0
  end

  def pay(amt)
    @value += amt
    amt
  end
end

peter = Peter.new(1000)
paul = Paul.new
1000.times do
  paul.pay(peter.rob(10))
end

Run this, and you'll get something like the following.

 time   seconds   seconds    calls  ms/call  ms/call  name
 32.14     0.27      0.27        1   270.00   840.00  Fixnum#times
 30.95     0.53      0.26     1000     0.26     0.27  Paul#pay
 29.76     0.78      0.25     1000     0.25     0.30  Peter#rob
  5.95     0.83      0.05     1000     0.05     0.05  Fixnum#-
  1.19     0.84      0.01     1000     0.01     0.01  Fixnum#+
  0.00     0.84      0.00        1     0.00     0.00  Paul#initialize
  0.00     0.84      0.00        2     0.00     0.00  Class#inherited
  0.00     0.84      0.00        4     0.00     0.00  Module#method_added
  0.00     0.84      0.00        1     0.00     0.00  Peter#initialize
  0.00     0.84      0.00        1     0.00   840.00  #toplevel
  0.00     0.84      0.00        2     0.00     0.00  Class#new
With the profiler, you can quickly identify and fix bottlenecks. Remember to check the code without the profiler afterward, though---sometimes the slowdown the profiler introduces can mask other problems.

Ruby is a wonderfully transparent and expressive language, but it does not relieve the programmer of the need to apply common sense: creating unnecessary objects, performing unneeded work, and creating generally bloated code are wasteful in any language.
Debugger commands
b[reak] [file:]line | Set breakpoint at given line in file (default current file).
b[reak] [file:]name | Set breakpoint at method in file.
b[reak] | Display breakpoints and watchpoints.
wat[ch] expr | Break when expression becomes true.
del[ete] [nnn] | Delete breakpoint nnn (default all).
disp[lay] expr | Display value of nnn every time debugger gets control.
disp[lay] | Show current displays.
undisp[lay] [nnn] | Remove display (default all).
c[ont] | Continue execution.
s[tep] nnn=1 | Execute next nnn lines, stepping into methods.
n[ext] nnn=1 | Execute next nnn lines, stepping over methods.
fi[nish] | Finish execution of the current function.
q[uit] | Exit the debugger.
w[here] | Display current stack frame.
f[rame] | Synonym for where.
l[ist] [start--end] | List source lines from start to end.
up nnn=1 | Move up nnn levels in the stack frame.
down nnn=1 | Move down nnn levels in the stack frame.
v[ar] g[lobal] | Display global variables.
v[ar] l[ocal] | Display local variables.
v[ar] i[stance] obj | Display instance variables of obj.
v[ar] c[onst] Name | Display constants in class or module name.
m[ethod] i[nstance] obj | Display instance methods of obj.
m[ethod] Name | Display instance methods of the class or module name.
th[read] l[ist] | List all threads.
th[read] [c[ur[rent]]] | Display status of current thread.
th[read] [c[ur[rent]]] nnn | Make thread nnn current and stop it.
th[read] stop nnn | Make thread nnn current and stop it.
th[read] resume nnn | Resume thread nnn.
[p] expr | Evaluate expr in the current context. expr may include assignment to variables and method invocations.
empty | A null command repeats the last command.

Extracted from the book "Programming Ruby - The Pragmatic Programmer's Guide"
Copyright © 2001 by Addison Wesley Longman, Inc. This material may be distributed only subject to the terms and conditions set forth in the Open Publication License, v1.0 or later (the latest version is presently available at http://www.opencontent.org/openpub/)).

Distribution of substantively modified versions of this document is prohibited without the explicit permission of the copyright holder.

Distribution of the work or derivative of the work in any standard (paper) book form is prohibited unless prior permission is obtained from the copyright holder.


Programming Ruby: The Pragmatic Programmer's Guide

Programming Ruby

The Pragmatic Programmer's Guide

Previous < Contents ^
Next >

Ruby and Its World

It's an unfortunate fact of life that our applications have to deal with the big, bad world. In this chapter, we'll look at how Ruby interacts with its environment. Microsoft Windows users will probably also want to look at platform-specific information beginning on page 163.

Command-Line Arguments

``In the Beginning was the Command Line.''[Title of a marvelous essay by Neal Stephenson (available online at http://www.spack.org/essays/commandline.html).] Regardless of the system in which Ruby is deployed, whether it be a super high-end scientific graphics workstation or an embedded PDA device, you've got to start the Ruby interpreter somehow, and that gives us the opportunity to pass in command-line arguments.

A Ruby command line consists of three parts: options to the Ruby interpreter, optionally the name of a program to run, and optionally a set of arguments for that program.

ruby [
            options
            ] [--] [
            programfile
            ] [
            arguments
            ]

The Ruby options are terminated by the first word on the command line that doesn't start with a hyphen, or by the special flag ``--'' (two hyphens).

If no filename is present on the command line, or if the filename is a single hyphen (-), Ruby reads the program source from standard input.

Arguments for the program itself follow the program name. For example:

% ruby -w - "Hello World"

will enable warnings, read a program from standard input, and pass it the quoted string "Hello World" as an argument.

Command-Line Options

-0[octal}
' The number ``0'' flag specifies the record separator character (\0, if no digit follows). -00 indicates paragraph mode: records are separated by two successive default record separator characters. -0777 reads the entire file at once (as it is an illegal character). Sets $/.

-a
' Auto split mode when used with -n or -p; equivalent to executing {$F at the top of each loop iteration.

-C directory
' Changes working directory to directory before executing.

-c
' Checks syntax only; does not execute the program.

--copyright
Prints the copyright notice and exits.

-d, --debug
' Sets $DEBUG to true. This can be used by your programs to enable additional tracing.

-e 'command'
' Executes command as one line of Ruby source. Several -e's are allowed, and the commands are treated as multiple lines in the same program. If programfile is omitted when -e is present, execution stops after the -e commands have been run.

-F pattern
' Specifies the input field separator ($;) used as the default for split() (affects -a).

-h, --help
' Displays a short help screen.

-I directories
' Specifies directories to be prepended to $LOAD_PATH ($:). Multiple -I options may be present, and multiple directories may appear following each -I. Directories are separated by a ``:'' on Unix-like systems and by a ``;'' on DOS/Windows systems.

-i [extension}
' Edits ARGV files in place. For each file named in ARGV, anything you write to standard output will be saved back as the contents of that file. A backup copy of the file will be made if extension is supplied.
% ruby -pi.bak -e "gsub(/Perl/, 'Ruby')" *.txt

-K kcode
' Specifies the code set to be used. This option is useful mainly when Ruby is used for Japanese-language processing. kcode may be one of: e, E for EUC; s, S for SJIS; u, U for UTF-8; or a, A, n, N for ASCII.

-l
' Enables automatic line-ending processing; sets $\ to the value of $/ and chops every input line automatically.

-n
' Assumes ``while gets; ...; end'' loop around your program. For example, a simple grep command might be implemented as:
% ruby -n -e "print if /wombat/"  *.txt

-p
' Places your program code within the loop ``while gets; ...; print; end.''
% ruby -p -e "$_.downcase!" *.txt
-r library
' requires the named library before executing.

-S
' Looks for the program file using RUBYPATH or PATH environment variable.

-s
' Any command line switches found after the program filename, but before any filename arguments or before a --, are removed from ARGV and set to a global variable named for the switch. In the following example, the effect of this would be to set the variable $opt to ``electric''.

% ruby -s prog -opt=electric ./mydata

-T[level}
' Sets the safe level, which among other things enables tainting checks (see page 253). Sets $SAFE.

-v, --verbose
' Enables verbose mode and print the version number. In verbose mode, compilation warnings are printed. If no program filename appears on the command line, Ruby exits.

--version
Displays the Ruby version number and exits.

-w
' Enables verbose mode. Unlike -v, reads program from standard input if no program files are present on the command line. We recommend running your Ruby programs with -w.

-X directory
' Changes working directory to directory before executing. Same as -C directory.

-x [directory}
' Strips off text before #!ruby line and changes working directory to directory if given.

-y, --yydebug
' Enables yacc debugging in the parser (waaay too much information).

ARGV

Any command-line arguments after the program filename are available to your Ruby program in the global array ARGV. For instance, invoking Ruby as

% ruby -w ptest "Hello World" a1 1.6180

yields an ARGV array containing ["Hello World", a1, 1.6180]. There's a gotcha here for all you C programmers---ARGV[0] is the first argument to the program, not the program name. The name of the current program is available in the global variable $0.

Program Termination

The method Kernel#exit terminates your program, returning a status value to the operating system. However, unlike some languages, exit doesn't just terminate the program immediately. Kernel#exit first raises a SystemExit exception, which you may catch, and then performs a number of cleanup actions, including running any registered at_exit methods and object finalizers. See the reference for Kernel#exit beginning on page 415 for details.

Environment Variables

You can access operating system environment variables using the predefined variable ENV. It responds to the same methods as Hash.[ENV is not actually a hash, but if you need to, you can convert it into a Hash using ENV#to_hash.]

The values of some environment variables are read by Ruby when it first starts. These variables modify the behavior of the interpreter, as shown in Table 13.1 on page 139.
Environment variables used by Ruby
Variable Name | Description
RUBYOPT | Additional command-line options to Ruby; examined after real command-line options are parsed ($SAFE must be 0).
RUBYLIB | Additional search path for Ruby programs ($SAFE must be 0).
RUBYPATH | With -S option, search path for Ruby programs (defaults to PATH).
RUBYSHELL | Shell to use when spawning a process; if not set, will also check SHELL or COMSPEC.
DLN_LIBRARY_PATH | Search path for dynamically loaded modules.
RUBYLIB_PREFIX | (Windows only) Mangle the RUBYLIB search path by adding this prefix to each component.

Writing to Environment Variables

A Ruby program may write to the ENV object, which on most systems changes the values of the corresponding environment variables. However, this change is local to the process that makes it and to any subsequently spawned child processes. This inheritance of environment variables is illustrated in the code that follows. A subprocess changes an environment variable and this change is seen in a process that it then starts. However, the change is not visible to the original parent. (This just goes to prove that parents never really know what their children are doing.)

puts "In parent, term = #{ENV['TERM']}"
fork do
  puts "Start of child 1, term = #{ENV['TERM']}"
  ENV['TERM'] = "ansi"
  fork do
    puts "Start of child 2, term = #{ENV['TERM']}"
  end
  Process.wait
  puts "End of child 1, term = #{ENV['TERM']}"
end
Process.wait
puts "Back in parent, term = #{ENV['TERM']}"
produces:
In parent, term = xterm
Start of child 1, term = xterm
Start of child 2, term = ansi
End of child 1, term = ansi
Back in parent, term = xterm

Where Ruby Finds Its Modules

You use require or load to bring a library module into your Ruby program. Some of these modules are supplied with Ruby, some you installed off the Ruby Application Archive, and some you wrote yourself. How does Ruby find them?

When Ruby is built for your particular machine, it predefines a set of standard directories to hold library stuff. Where these are depends on the machine in question. You can determine this from the command line with something like:

% ruby -e 'puts $:'

On a typical Linux box, you'll probably find something such as:

/usr/local/lib/ruby/site_ruby/1.6/i686-linux
/usr/local/lib/ruby/site_ruby/1.6
/usr/local/lib/ruby/site_ruby
/usr/local/lib/ruby/1.6/i686-linux
/usr/local/lib/ruby/1.6
.

The site_ruby directories are intended to hold modules and extensions that you've added. The architecture-dependent directories (i686-linux in this case) hold executables and other things specific to this particular machine. All these directories are automatically included in Ruby's search for modules.

Sometimes this isn't enough. Perhaps you're working on a large project written in Ruby, and you and your colleagues have built a substantial library of Ruby code. You want everyone on the team to have access to all of this code. You have a couple of options to accomplish this. If your program runs at a safe level of zero (see Chapter 20 beginning on page 253), you can set the environment variable RUBYLIB to a list of one or more directories to be searched.[The separator between entries depends on your platform. For Windows, it's a semicolon; for Unix, a colon.] If your program is not setuid, you can use the command-line parameter -I to do the same thing.

Finally, the Ruby variable $: is an array of places to search for loaded files. This variable is initialized to the list of standard directories, plus any additional ones you specified using RUBYLIB and -I. You can always add additional directories to this array from within your running program.

Build Environment

When Ruby is compiled for a particular architecture, all of the relevant settings used to build it (including the architecture of the machine on which it was compiled, compiler options, source code directory, and so on) are written to the module Config within the library file ``rbconfig.rb''. After installation, any Ruby program can use this module to get details on how Ruby was compiled.

require "rbconfig.rb"
include Config
CONFIG["host"] | » | "i686-pc-linux"
CONFIG["LDFLAGS"] | » | "-rdynamic"

Extension libraries use this configuration file in order to compile and link properly on any given architecture. See Chapter 17 beginning on page 169, and the reference for mkmf beginning on page 451 for details.

Extracted from the book "Programming Ruby - The Pragmatic Programmer's Guide"
Copyright © 2001 by Addison Wesley Longman, Inc. This material may be distributed only subject to the terms and conditions set forth in the Open Publication License, v1.0 or later (the latest version is presently available at http://www.opencontent.org/openpub/)).

Distribution of substantively modified versions of this document is prohibited without the explicit permission of the copyright holder.

Distribution of the work or derivative of the work in any standard (paper) book form is prohibited unless prior permission is obtained from the copyright holder.


Programming Ruby: The Pragmatic Programmer's Guide

Programming Ruby

The Pragmatic Programmer's Guide

上一章 < 目录 ^
下一章 >

Ruby a和 Web

Ruby对互联网来说并不陌生，不只是因为你可以用它来写SMTP服务器，FTP服务器，或者web服务器，你还可以用它来完成通常的任务，比如CGI编程或者作为PHP的一个替代品。

编写 CGI 脚本

你可以用Ruby轻松的编写CGI脚本，为了让Ruby脚本产生HTML输出，你只需要这样做：

#!/usr/bin/env ruby
print "HTTP/1.0 200 OK\r\n"
print "Content-type: text/html\r\n\r\n"
print "<html><body>Hello World!</body></html>\r\n"

你可以用Ruby的正则表达式来解析输入字符串，查询环境变量，检查标记，填充模板，转义（escape ）特殊字符，格式化HTML，然后输出。

或者，你可以用类CGI。

使用cgi.rb

类CGI主要来帮助你更方便的编写CGI脚本，使用它，你可以操纵表单（form），cookies和环境变量，维护有状态的session等等。关于它的文档在第497页，但是我们在这里要先看一下它大体的功能。

引用（Quoting）

当处理URL和HTML的时候，我们必须注意对一些特殊字符的引用。比如，一个斜线 (``/'') 在URL里有特殊意义，如果它不是URL的路径名称的一部分，那么它必须被转义（escaped）。也就是说，它在URL中将被转换为 ``%2F''，而且反过来你要想使用它，必须把它再转换为斜线。空格和and符号（"&"）也是特殊字符。为了处理这样的情况，CGI提供了方法 CGI.escape和CGI.unescape。

require 'cgi'
puts CGI.escape( "Nicholas Payton/Trumpet & Flugel Horn" )
结果:
Nicholas+Payton%2FTrumpet+%26+Flugel+Horn

类似的，你也许需要对HTML中的特殊字符进行转义（escape ）：

require 'cgi'
puts CGI.escapeHTML( '<a href="/mp3">Click Here</a>' )
结果:
&lt;a href=&quot;/mp3&quot;&gt;Click Here&lt;/a&gt;

而且，你也可以只对HTML的一部分标记进行转义，而不是全部：

require 'cgi'
puts CGI.escapeElement('<hr><a href="/mp3">Click Here</a><br>','A')
produces:
<hr>&lt;a href=&quot;/mp3&quot;&gt;Click Here&lt;/a&gt;<br>

这里又有A标记被转义了，其它标记都没有变化。

每个这样的转义方法都有一个un-开头的版本，来恢复这个转义之前的字符串。

表单（Forms）

使用CGI类你有两种方法访问HTML传过来的参数。比如我们有一个URL/cgi-bin/lookup?player=Miles%20Davis&year=1958，你可以用CGI#[ ]方法直接访问player和year变量：

require 'cgi'
cgi = CGI.new
cgi['player'] | ? | ["Miles Davis"]
cgi['year'] | ? | ["1958"]

或者，你可以把所有参数放入一个Hash中，再从这个哈希查询参数。

require 'cgi'
cgi = CGI.new
h = cgi.params
h['player'] ? ["Miles Davis"]

创建窗体（Forms）和 HTML

CGI类包含了很多产生HTML的方法，每个HTML标记都有一个方法。为了使用这些方法，你首先要创建一个CGI对象，这可以通过CGI.new方法。这个方法可以接收一个参数表示HTML的级别。比如，我们这里用了html3。

为了使得这些标记嵌套方便，这些方法都接受一个block作为来处理内容，这个block返回一个字符串，这也是这个tag的内容。比如下面的例子，我们加入了一些换行是为了能适应屏幕，能使得它被很好的显示而已。

require "cgi"
cgi = CGI.new("html3")  # add HTML generation methods
cgi.out{
  cgi.html{
    cgi.head{ "\n"+cgi.title{"This Is a Test"} } +
    cgi.body{ "\n"+
      cgi.form{"\n"+
        cgi.hr +
        cgi.h1 { "A Form: " } + "\n"+
        cgi.textarea("get_text") +"\n"+
        cgi.br +
        cgi.submit
      }
    }
  }
}
produces:
Content-Type: text/html
Content-Length: 302

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN"><HTML><HEAD>
<TITLE>This Is a Test</TITLE></HEAD><BODY>
<FORM METHOD="post" ENCTYPE="application/x-www-form-urlencoded">
<HR><H1>A Form: </H1>
<TEXTAREA NAME="get_text" ROWS="10" COLS="70"></TEXTAREA>
<BR><INPUT TYPE="submit"></FORM></BODY></HTML>

这段代码将产生一个HTML文档，主题（title）为“This Is a Test”，然后一个水平线，然后是一个header，一个测试的输入框，一个提交按钮。当接收到这个提交的页面，你会得到一个变量名为get_text，其值为你输入的文本。

Cookies

你可以通过使用cookies在客户机上存放各种有用的信息，你可以创建一个有名字的cookie，并且设定一个指定的值。为了将这个cookie发送到浏览器，你需要在CGI#out中设置cookie 头（header）。

require "cgi"
cookie = CGI::Cookie.new("rubyweb", "CustID=123", "Part=ABC");
cgi = CGI.new("html3")
cgi.out( "cookie" => [cookie] ){
  cgi.html{
    "\nHTML content here"
  }
}
produces:
Content-Type: text/html
Content-Length: 86
Set-Cookie: rubyweb=CustID%3D123&Part%3DABC; path=

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN"><HTML>
HTML content here</HTML>

当用户下次访问这个页面时，你可以取得cookie CustID 和Part，然后将它们显示在HTML输出中。

require "cgi"
cgi = CGI.new("html3")
cgi.out{
  cgi.html{
    cgi.pre{
      cookie = cgi.cookies["rubyweb"]
        "\nCookies are\n" + cookie.value.join("\n")
    }
  }
}
produces:
Content-Type: text/html
Content-Length: 111

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN"><HTML><PRE>
Cookies are
CustID=123
Part=ABC</PRE></HTML>

Sessions

cookies需要我们进行一些人为处理后才变得有用，我们真正需要的是一个session：web客户端的持久性的状态。session在Ruby中用 CGI::Session处理，它也使用了cookie，但是提供了一个高层次的接口。

require "cgi"
require "cgi/session"

cgi = CGI.new("html3")
sess = CGI::Session.new( cgi, "session_key" => "rubyweb",
                          "session_id"  => "9650",
                          "new_session" => true,
                          "prefix" => "web-session.")
sess["CustID"] = 123
sess["Part"] = "ABC"

cgi.out{
  cgi.html{
    "\nHTML content here"
  }
}

这将给用户rubyweb发送一个cookie，它的值为9650。它也会在服务器的上建立一个文件$TMP/web-session.9650，以“键－值”（key－value）的方式保存着CustID和Part的值。

当这个用户又访问这个网站时，你需要一个参数指明session id。在这个例子里应该是rubyweb=9650，然后，你就可以取得所有和这个sesion相关的数据了。

require "cgi"
require "cgi/session"

cgi = CGI.new("html3")
sess = CGI::Session.new( cgi, "session_key" => "rubyweb",
                               "prefix" => "web-session.")
cgi.out{
  cgi.html{
    "\nCustomer #{sess['CustID']} orders an #{sess['Part']}"
  }
}

将Ruby嵌入HTML

到目前为止我们已经看过了用Ruby创建HTML文件和显示它，下面我们来看看如何将Ruby嵌入HTML文档中。

有好几个包支持我们将Ruby嵌入其它文档中，特别是HTML页面，一般来说，这叫做eRuby，它的实现也有好几种，比如eruby和erb。下面的部分将介绍eruby。

将Ruby脚本嵌入HTML非常有用，我们不仅能像ASP,JSP,PHP一样使用它，而且还能发挥Ruby的力量。

使用eruby

eruby 简单来说就像一个过滤器，对于输入的文件中的普通HTML内容，它将不会做什么处理，而对下面的格式的代码进行特殊处理：

表达式 | 意义
<% ruby code %> | 将定界符内的Ruby代码转换为它的结果输出
<%= ruby expression %> | 输出这个表达式的值到HTML中
<%# ruby code %> | 代码注释，这些内容将被忽略。对测试来说可能有用。

调用eruby的方法如下：

eruby [
            options
            ] [
            document
            ]

如果document忽略，则eruby将从标准输入读取。eruby的命令行参数如下：

Option | Description
-d, --debug | 设置$DEBUG 为 true.
-K kcode | 指定不同的编码系统（coding system 参见137页)
-M mode 指定运行模式，为下面之一：
f | 过滤模式
c | CGI 模式(将错误打印为HTML, 设置$SAFE=1)
n | NPH-CGI 模式(打印额外的头部为HTTP，设置$SAFE=1)
-n, --noheader | 禁止CGI头部（ header）输出
-v, --verbose | 允许详细（verbose ）模式
--version | 打印出版本信息并退出程序

让我们来看一些简单的例子，假如我们给eruby运行时候的输入为下面的内容：

This text is <% a = 100; puts "#{a}% Live!" %>

eruby 将把<%和%>之间的Ruby代码换成它执行后的值然后显示出来。
This text is 100% Live!

使用 <%= 的形式，直接将后面的表达式的值输出，比如，我们的输入如下：

<%a = 100%>This text is almost <%=a%> degrees! Cool!

将=a替换为a的实际值，如下：
This text is almost 100 degrees! Cool!

当然，你也可以将Ruby嵌入更复杂的文档，比如HTML。

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html>
<head>
<title>eruby example</title>
</head>
<body>
<h1>Enumeration</h1>
<ul>
<%(1..10).each do|i|%>
  <li>number <%=i%></li>
<%end%>
</ul>
<h1>Environment variables</h1>
<table>
<%ENV.keys.sort.each do |key|%>
  <tr>
    <th><%=key%></th><td><%=ENV[key]%></td>
  </tr>
<%end%>
</table>
</body>
</html>

在 Apache中安装eruby

你可以安装一个使用eRuby的Apache服务器来自动解析嵌入Ruby的文档，就像PHP那样。你可以将嵌入Ruby的文档命名为以`.rhtml'' 为后缀，然后设置web服务器来调用eruby来解析这些文件输出想要的HTML。

为了在Apache中使用eruby，需要以下几步。

  • 将可执行的 eruby 程序拷贝到 cgi-bin 目录

  • 在 httpd.conf 文件中增加如下两行：

    AddType application/x-httpd-eruby .rhtml
Action application/x-httpd-eruby /cgi-bin/eruby

  • 如果需要，你可以在你的 DirectoryIndex 指令中加入 index.rhtml，这样，如果你的目录下面如果没有index.html文件，则就会使用index.rhtml文件作为index页。比如下面的例子

    DirectoryIndex index.html index.shtml index.rhtml

    当然，你也可以用一个作用于一个全站点范围的Ruby脚本

    Of course, you could also simply use a site-wide Ruby script as well.

    DirectoryIndex index.html index.shtml /cgi-bin/index.rb

就是如此，你可以在HTML文档中加入嵌入的Ruby脚本来动态地创建表单和内容。但也请你阅读一下从497页开始的CGI库。

提高性能

你可以使用Ruby为web编写CGI程序，但是，如同其它CGI程序一样，默认得设置下，对每个cgi页面的请求都将产生一个新的Ruby进程，这将占用服务器很多的资源，而且容易引起性能的下降。Apache web服务器通过使用可装载的模块来解决这个问题。

一般来说，这些模块都会动态的被载入，而且会成为Web服务器进程的一部分，也就是说不需要每次有CGI请求进来都产生一个Ruby解释器程序，Web服务器就充当了解释器的角色。

所以我们会用到mod_ruby，它会将Ruby解释器连接（links）到Web服务器，关于它的具体信息可以看它的发布程序附带的README文件。

一旦你安装配置好之后，你就可以像原来一样运行Ruby脚本，不同的是现在地速度应该是快很多了。

Extracted from the book "Programming Ruby - The Pragmatic Programmer's Guide"
Copyright © 2001 by Addison Wesley Longman, Inc. This material may be distributed only subject to the terms and conditions set forth in the Open Publication License, v1.0 or later (the latest version is presently available at http://www.opencontent.org/openpub/)).

Distribution of substantively modified versions of this document is prohibited without the explicit permission of the copyright holder.

Distribution of the work or derivative of the work in any standard (paper) book form is prohibited unless prior permission is obtained from the copyright holder.


Programming Ruby: The Pragmatic Programmer's Guide

Programming Ruby

实用程序员指南

上一章< 目录 ^
Next >

Ruby和 Tk

译者注：因原来的发布不带图片，所以本章图片选自
http://www.ruby.no/prosjekt/ProgrammeringIRuby/ext_tk.html，不是英语，可能看不清，不过界面都显示出来了。

Ruby Application Archive （简称RAA）包括几个支持你使用Ruby来创建GUI（Graphical User Interface）程序的扩展（extensions ），包括Tcl/Tk扩展，GTK扩展，OpenGL扩展和其它的一些。

TK扩展是随着Ruby软件一起发布的，可以在Unix和windows上使用。为了使用它，你需要在你的系统上安装Tk。Tk是一个庞大的系统，关于它的书已经有很多了，所以我们不会在这里浪费太多的时间和资源去深入的研究Tk本身，而是着重于如何在Ruby中使用Tk。要想很好的在Ruby中使用Tk，你需要下面的一些参考书中的一本。因为Tk和Ruby的绑定和Tk到Perl的绑定类似，所以你可以用Learning Perl/Tk 或者 Perl/Tk Pocket Reference

Tk以一种组合的方式工作，就是说你首先创建一个容器（container ）部件（widget ），比如TkFrame 或者TkRoot，然后在它上面继续创建其它部件，比如按钮或者标签（labels）。当你打算开始启动GUI的时候，调用Tk.mainloop，然后Tk引擎将接替对程序的控制，显示各个窗口部件，响应各种GUI事件。

简单的Tk应用程序

Ruby中一个简单的Tk应用程序像下面一样：

require 'tk'
root = TkRoot.new { title "Ex1" }
TkLabel.new(root) {
  text  'Hello, World!'
  pack  { padx 15 ; pady 15; side 'left' }
}
Tk.mainloop

界面如图：

让我们更近距离的看看这些代码。在装载完tk扩展模块之后，我们用TkRoot.new建立了一个最顶级（root-level ）的框架（frame）。然后，我们建立了一个标签部件作为这个父级窗口的子部件，并设置了这个标签的一些属性，最后，最后，我们让父窗口显示（pack？）并进入GUI主事件循环。

显示的建立一个顶级窗口是一个很好的习惯，尽管你不建立这个窗口程序也能工作。例如上面的代码即使变为以下三行也能工作：

require 'tk'
TkLabel.new { text 'Hello, World!' }
Tk.mainloop

这样就可以工作了，再参考一下上面我们提到的Perl/Tk的书，我们就能写出非常好的GUI程序了。但是，下面我们还是会更详细的讲述一下Tk在Ruby中的使用。

窗口部件（Widgets）

创建一个窗口部件很容易，在Ruby中这些部件名字都在原来的基础上加上了Tk前缀，比如，Label，Button和Entry变成了TkLabel，TkButton和TkEntry。你可以通过使用new方法创建一个部件，就像创建其它对象一样。如果你创建组件的时候没有指定父部件的名字，那么将把顶级窗口作为默认父部件。我们有时候也会在创建部件的时候设置一些部件属性（选项），比如颜色，大小等等；有时候，我们也希望从这些部件得到程序返回信息，我们可以设置回调（callbacks ）方法和共享数据（sharing data）。

设置部件（Widget）选项

如果你看一下Tk的参考手册（比如关于Perl/Tk），你会发现这些窗口部件选项可以用一个连字符列出（listed with a hyphen ），就像命令行参数那样。在Perl/Tk中，你可以将这些参数放入一个Hash传给这个widget，在Ruby中你也可以这么做，但是，你也可以通过一个代码block来设置这些参数：选项的名称作为方法名，选项的值作为这个方法的参数。一个widget创建时候的第一个参数为父部件，然后后面跟一个哈希结构的选项列表，或者以代码块格式来设置这些选项。所以，下面这两项实际上是相等地。

TkLabel.new(parent_widget) {
  text    'Hello, World!'
  pack('padx'  => 5,
       'pady'  => 5,
       'side'  => 'left')
}
# or
TkLabel.new(parent_widget, text => 'Hello, World!').pack(...)

不过有一个需要注意的小地方：有时候变量的作用域可能出乎你的想象。block是在widget的环境上下文中执行（evaluated ），而不是调用者的（caller's）。这就意味着调用者的实例变量在这个block中不可用，但是在包含它的范围内的局部变量和全局变量（不是你曾经用过的）。我们将在后面的例子中使用这两种方法设置widget的选项。

不确定的英文原文：The block is actually in the context of the widget's object, not the . This means that the caller's instance variables will not be available in the block, but local variables from the enclosing scope and globals (not that you ever use those) will be.

距离（比如上面例子中的padx和pady）默认单位为象素，但是你也可以指定为其它单位，比如以c为后缀表示单位为厘米，i表示英寸，m表示毫米，p表示点（point）等。

取得Widget的数据

我们可以通过方法回调和绑定变量（binding variables）从窗口部件得到信息。

回调（Callbacks ）很容易使用。command选项（比如下面例子中的TkButton）接收一个Proc对象，这个Proc对象会在这个回调触发的时候被调用。这里，我们可以用 Kernel::proc 将block {exit}转换为一个Proc对象。

TkButton.new(bottom) {
  text "Ok"
  command proc { p mycheck.value; exit }
  pack('side'=>'left', 'padx'=>10, 'pady'=>10)
}

我们也可以使用TkVariable 代理将Ruby的变量绑定到 Tk widget的值。注意下面的TkCheckButton 是如何创建的，variable选项接受一个TkVariable类型的参数，这个例子里我们用 TkVariable.new首先创建了这个变量。访问变量mycheck.value将根据这个选择框是否被选中而返回0或1。你可以在各种支持variable选项的部件中使用这种方法，比如radio，button和文本框。

mycheck = TkVariable.new

TkCheckButton.new(top) {
  variable mycheck
  pack('padx'=>5, 'pady'=>5, 'side' => 'left')
}

动态设置和获取选项

除了在创建一个部件的时候设定选项，我们也可以在这个部件运行的时候重新配置它。每个widget都支持configure方法，这个方法接收一个Hash或者代码块作为参数，和new方法类似，比如我们可以修改一下第一个例子，使它的按钮在按下的时候能给修改标签的文本。

lbl = TkLabel.new(top) { justify 'center'
  text    'Hello, World!';
  pack('padx'=>5, 'pady'=>5, 'side' => 'top') }
TkButton.new(top) {
  text "Cancel"
  command proc { lbl.configure('text'=>"Goodbye, Cruel World!") }
  pack('side'=>'right', 'padx'=>10, 'pady'=>10)
}

所以，当你按Cancel按钮的时候，标签的文本将由``Hello, World!'' 变为``Goodbye, Cruel World!''。

你也可以用cget方法取得某个widget的某个选项的值。

require 'tk'
b = TkButton.new {
text "OK"
justify 'left'
border 5
}
b.cget('text') | ? | "OK"
b.cget('justify') | ? | "left"
b.cget('border') | ? | 5

程序示例

下面来看一个稍微长点例子，一个真正的应用程序－``pig Latin''生成器。输入一个短语，比如 ``Ruby rules'' ，然后按下按钮``Pig It''，程序将立即把这个短语翻译为

require 'tk'

class PigBox
  def pig(word)
    leadingCap = word =~ /^A-Z/
    word.downcase!
    res = case word
      when /^aeiouy/
        word+"way"
      when /^([^aeiouy]+)(.*)/
        $2+$1+"ay"
      else
        word
    end
    leadingCap ? res.capitalize : res
  end

  def showPig
    @text.value = @text.value.split.collect{|w| pig(w)}.join(" ")
  end

  def initialize
    ph = { 'padx' => 10, 'pady' => 10 }     # common options
    p = proc {showPig}

    @text = TkVariable.new
    root = TkRoot.new { title "Pig" }
    top = TkFrame.new(root)
    TkLabel.new(top) {text    'Enter Text:' ; pack(ph) }
    @entry = TkEntry.new(top, 'textvariable' => @text)
    @entry.pack(ph)
    TkButton.new(top) {text 'Pig It'; command p; pack ph}
    TkButton.new(top) {text 'Exit'; command {proc exit}; pack ph}
    top.pack('fill'=>'both', 'side' =>'top')
  end
end

PigBox.new
Tk.mainloop

界面如图：

Sidebar: 布局管理器？（Geometry Management）

在本章的例子中，我们已经看到了pack方法，这是一个非常重要的方法，如果你不调用这个方法，那么你的widget将永远不会出现，pack命令将告诉geometry manager将这个部件按照设定的参数放到合适的位置上。支持三种命令：

命令 | 定位标准
pack | 灵活的，基于约束（constraint）的定位
place | Absolute position
grid | Tabular (row/column) position

因为pack可能是最常用的方法了，所以我们的例子里都使用pack方法。

译者注：如果一个方法（method）没有参数，有时候也叫做命令（command）

绑定事件（Binding Events）

我们的窗口部件是暴露给现实世界的，人们可以在它上面点击，移动鼠标，进入这个组件等。所有的这些事情，都会产生一个事件，我们也可以捕获这些事件。你可以通过窗口部件的bind方法，给它的某一事件绑定一个事件处理，通常，这个时间处理是一个代码块（block of code）。

比如，我们创建了一个用来显示一个图像的按钮，我们希望鼠标在这个按钮上移动的时候图像会变化，则可以用以下代码：

image1 = TkPhotoImage.new { file "img1.gif" }
image2 = TkPhotoImage.new { file "img2.gif" }

b = TkButton.new(@root) {
  image    image1
  command  proc { doit }
}

b.bind("Enter") { b.configure('image'=>image2) }
b.bind("Leave") { b.configure('image'=>image1) }

首先，我们用TkPhotoImage创建了2个GIF图像，然后，创建了一个按钮，名字为b，它显示的图像为image1，然后我们将它的Enter方法绑定一个将它的图像变为image2的代码块，Leave事件帮定到恢复image1图像的代码块。

这个例子显示了简单的Enter和Leave方法，而且，bind方法的参数可以有几个子字符串构成，每个串之间用减号分割，顺序为modifier-modifier-type-detail。 修饰符（Modifiers ）可以在Tk的参考手册中找到，包括Button1, Control, Alt, Shift等等，type是事件的名字（遵循X11的命名习惯），包括ButtonPress，KeyPress和Expose等。Detail或者是一个用于表示button的从1到5的数字，或者用来表示键盘输入的keysym。比如，如果我们想处理事件：在control键被按下的时候释放鼠标的button1，可以如下表示：

Control-Button1-ButtonRelease
或者
Control-ButtonRelease-1

事件本身也会包含一些特定的字段（fields ），比如事件的发成的时间，坐标x，y的值等。bind可以将这些属性传给一个回调方法，这叫做event field codes 。这种方法使用起来类似printf的规则，比如，为了得到一个鼠标移动时候的xy坐标值，我们可以给bind方法传3个参数，第二个参数是一个用于回调的Proc对象，第三个参数是包括事件属性的字符串（event field string）

canvas.bind("Motion", proc{|x, y| do_motion (x, y)}, "%x %y")

画布Canvas

Tk提供了一个画布（canvas）部件，你可以在上面进行绘画，然后输出为PostScript 格式。这里是一个简单的例子，来自Ruby发布。按下鼠标按键1，然后拖动，当松开按键1的时候，将会在两个点之间划一条直线。按下按键2将会把这个画布转出为适合打印的PostScript 格式。

require 'tk'
class Draw
  def do_press(x, y)
    @start_x = x
    @start_y = y
    @current_line = TkcLine.new(@canvas, x, y, x, y)
  end
  def do_motion(x, y)
    if @current_line
      @current_line.coords @start_x, @start_y, x, y
    end
  end
  def do_release(x, y)
    if @current_line
      @current_line.coords @start_x, @start_y, x, y
      @current_line.fill 'black'
      @current_line = nil
    end
  end
  def initialize(parent)
    @canvas = TkCanvas.new(parent)
    @canvas.pack
    @start_x = @start_y = 0
    @canvas.bind("1", proc{|e| do_press(e.x, e.y)})
    @canvas.bind("2", proc{ puts @canvas.postscript({}) })
    @canvas.bind("B1-Motion", proc{|x, y| do_motion(x, y)}, "%x %y")
    @canvas.bind("ButtonRelease-1",
                 proc{|x, y| do_release (x, y)}, "%x %y")
  end
end

root = TkRoot.new{ title 'Canvas' }
Draw.new(root)
Tk.mainloop

只需要用鼠标点几下，就能创作出你的杰作了。

界面如下：

滚动Scrolling

除非你只想画一个非常小的图画，否则上面的程序肯定不会很适合的。TkCanvas, TkListbox和TkText 都可以支持滚动条，所以你能处理一个“大图象”的一部分

滚动条和widget之间的通信是双向的，移动滚动条的话widget的视图也会移动，而widget的试图被其他方法移动之后，滚动条也必须相应的变化。

我们以前还没有怎么介绍列表框（listbox），我们下面的滚动条的例子将使用一个列表框。下面的例子中，我们先创建一个普通的TkListbox，然后，又创建一个TkScrollbar，通过command选项为这个滚动条增加了一个回调，这个回调将调用列表的yview方法，这个方法用来在纵坐标方向改变list的可见部分。

在回调建立之后，我们反过来也需要在list在移动之后，滚动条的设置也要改变，可以用TkScrollbar#set方法。下面的例子只是下一节例子中的一部分代码。

list_w = TkListbox.new(frame, 'selectmode' => 'single')

scroll_bar = TkScrollbar.new(frame,
                  'command' => proc { |*args| list_w.yview *args })

scroll_bar.pack('side' => 'left', 'fill' => 'y')

list_w.yscrollcommand(proc { |first,last|
                             scroll_bar.set(first,last) })

Just One More Thing

我们可以用几百页来继续讨论Tk，但是那是其它的书本的内容了。下面的程序是我们最后的Tk例子，一个简单的GIF图像查看器。你可以从一个列表框选择一个GIF文件名，然后显示一个适应窗口大小的图像。这里有一些内容需要指出。

你是否看见过一个应用程序将光标设置为忙状态（沙漏）而忘了改回来吗？在Ruby中则不必担心这种情况的发生。还记得 File.new 方法能接收一个block而确保文件最后被关闭吗，我们可以同样的在busy方法中使用这种机制，来确保最后光标被恢复，如下面的例子。

这个例子也显示了TkListbox 的一些其它常用方法，向它增加元素，建立鼠标键松开时候的事件回调[你也许需要鼠标松开事件，而不是按下事件，鼠标按下的时候表示选择了一个widget]，并且得到选择的那一个元素等。

到目前位置我们用TkPhotoImage 直接来显示图片，我们也可以对它进行缩放，抽样（subsample），或者只显示它的一部分。这里，我们用到了subsample 来使图片适合预览。

require 'tk'

def busy
  begin
    $root.cursor "watch" # Set a watch cursor
    $root.update # Make sure it updates  the screen
    yield # Call the associated block
  ensure
    $root.cursor "" # Back to original
    $root.update
  end
end

$root = TkRoot.new {title 'Scroll List'}
frame = TkFrame.new($root)

list_w = TkListbox.new(frame, 'selectmode' => 'single')

scroll_bar = TkScrollbar.new(frame,
                  'command' => proc { |*args| list_w.yview *args })

scroll_bar.pack('side' => 'left', 'fill' => 'y')

list_w.yscrollcommand(proc { |first,last|
                             scroll_bar.set(first,last) })
list_w.pack('side'=>'left')

image_w = TkPhotoImage.new
TkLabel.new(frame, 'image' => image_w).pack('side'=>'left')
frame.pack

list_contents = Dir["screenshots/gifs/*.gif"]
list_contents.each {|x|
  list_w.insert('end',x) # Insert each file name into the list
}
list_w.bind("ButtonRelease-1") {
  index = list_w.curselection[0]
  busy {
    tmp_img = TkPhotoImage.new('file'=> list_contents[index])
    scale   = tmp_img.height / 100
    scale   = 1 if scale < 1
    image_w.copy(tmp_img, 'subsample' => [scale,scale])
    tmp_img = nil # Be sure to remove it, the
    GC.start      # image may have been large
  }
}

Tk.mainloop

译者注：上面代码倒数第七行，即copy图像的那一行，在我的系统（2000＋ruby 1.8.2）上运行出错，改为如下即可通过：image_w.copy(tmp_img, '-subsample 2 2')

界面如下：

最后再来看看垃圾收集，当我们有一些很大的图片时，我们不想让这些图片在不需要的时候占用内存，所以我们把对这个对象的引用设为nil，这就告诉了垃圾收集器立即把这些变量清理。

借鉴Perl/Tk文档

就说道这里了，你已经初步了解了在Ruby中使用Tk。在很大程度上，你可以轻松的在Ruby中借鉴适用于Perl/Tk的文档，但是这里有一些要注意，不是每个在Perl中实现的方法在Ruby中也被实现，而且也可能存在没有被文档化的功能。在关于Ruby/Tk的书出来之前，你最好还是有什么问题到新闻组上去讨论，或者阅读更多的源代码。

但是通常来说，你会直到如何去做。记住部件的选项可以通过一个哈希或者代码block传给widget，而且TkWidget 中代码块方法的变量作用域在这个widget内，而不是在类的实例中。

Object 的创建

Perl/Tk:  $widget = $parent->Widget( [ option => value ] )
Ruby:     widget = TkWidget.new(parent, option-hash)
          widget = TkWidget.new(parent) { code block }

你也许不需要保存新创建widget的返回值，尽管这个值可以得到。不要忘了调用一个widget 的pack方法，否则你将不会看到这个部件。

选项（Options）

Perl/Tk:  -background => color
Ruby:     'background' => color
          { background color }

记住代码块的作用域是不同的。

变量引用（Variable References）

Perl/Tk:  -textvariable => \$variable
          -textvariable => varRef
Ruby:     ref = TkVariable.new
          'textvariable' => ref
          { textvariable ref }

使用将一个Ruby变量绑定到widget的值，然后，你就可以用TkVariable 里的value的访问方法（TkVariable#value和TkVariable#value=）来直接处理widget的内容了。

Extracted from the book "Programming Ruby - The Pragmatic Programmer's Guide"
Copyright © 2001 by Addison Wesley Longman, Inc. This material may be distributed only subject to the terms and conditions set forth in the Open Publication License, v1.0 or later (the latest version is presently available at http://www.opencontent.org/openpub/)).

Distribution of substantively modified versions of this document is prohibited without the explicit permission of the copyright holder.

Distribution of the work or derivative of the work in any standard (paper) book form is prohibited unless prior permission is obtained from the copyright holder.


Programming Ruby: The Pragmatic Programmer's Guide

Programming Ruby

实用程序员指南

前一章< 目录 ^
后一章>

Ruby和微软的Windows

Ruby 的实现是基于 POSIX环境的，也就是说它可以利用unix程序员熟悉的所有的系统调用和系统库。

同时，系统设计的时候扩展了对windows的支持，这章，我们将看看这方面的特性，并展示一些在windows下高效使用ruby的秘诀。

Ruby Ports

Windows does not provide a POSIX environment by itself, so some sort of emulation library is required in order to provide the necessary functions. There are several ports of Ruby for Windows: the most commonly used one relies on the GNU Win32 environment, and is called the ``cygwin32'' port. The cygwin32 port works well with extension libraries, and is available on the Web as a precompiled binary. Another port, ``mswin32,'' does not rely on cygwin. It is currently available as source code only. The remainder of this chapter will refer to the cygwin32 port.

在 Windows下运行Ruby

cygwin32版本的ruby发布包括两个可执行文件：ruby.exe ，rubyw.exe

ruby.exe 用在命令提示符下（dos shell），就像unix版本中的一样。对于需要从标准输入输出进行读写操作的应用来说比较适合，但是这样的话我们运行什么程序都需要额外多开了一个窗口，而不管我们需不需要这个窗口，有时候这样就显得不合适了，比如我们双击一个ruby脚本运行一个图形接口（比如Tk），或者作为后台程序运行，或者在别的程序中被调用。
在这种情况下，我们可以用rubyw.exe ，他和 ruby.exe 一样，但是不提供标准输入和标准输出，或者标准错误，并且不会打开一个dos shell的窗口。

可以对ruby脚本（.rb结尾的文件）设定文件关联[在用菜单 查看/选项/文件类型]，然后双击这个文件就可以调用rubyw.exe来执行这个程序了。

Win32API

如果你的ruby程序打算直接访问win32api，或者用一些DLL的入口点，你将会用到Win32API扩展。

你可以用要访问的DLL名字创建一个Win32API对象，表示对一个指定的DLL的入口点的调用，DLL的名字包括函数，函数签名（参数和返回值），建立的win32api对象就可以用来对指定的dll进行调用。

DLL的很多参数都是一定形式的二进制结构，Win32API用ruby的字符串对象处理这些参数，进行来回的传递。你需要对这些字符串进行打包和解包（pack and unpack）

Windows Automation

如果你对使用低层次的windows API调用不感兴趣，你可以使用windows utomation，ruby可以当成Windows Automation 的客户。感谢 Masaki Suketa写的 win32OLE扩展。下面例子是WIN32OLE发布包中提供的。

Windows 自动化允许自动化控制器（客户端）向自动化服务器（Excel, Word, PowerPoint等）发送一个命令或者进行查询。

你可以向WIN32OLE发送一个命令，从而可以执行自动化服务器上具有相同名字的方法。例如，你可以创建一个WIN32OLE客户，创建几个全新的IE浏览器，并且显示ie设置的主页：

ie = WIN32OLE.new('InternetExplorer.Application')
ie.visible = true
ie.gohome

WIN32OLE并不知道这些方法（比如visible和gohome），它会把这些向它调用的方法传到WIN32OLE#invoke，在这个方法里向自动化服务器发送正确的命令。

读、写自动化服务器的属性

你可以用通常的哈希表示方法从自动化服务器读写服务器的属性。比如，可以如下面一样设定一个Excel chart的Rotation的值：

excel = WIN32OLE.new("excel.application")
excelchart = excel.Charts.Add()
...
excelchart['Rotation'] = 45
puts excelchart['Rotation']

OLE对象的参数自动都会被设成属性，也就是说你可以你可以通过给一个属性赋值而设定一个参数。

excelchart.rotation = 45
r = excelchart.rotation

因为这些属性是常规的ruby访问方法，而属性名不能以大写字母开头，所以你要用rotation而不是Rotation。

命名参数（Named Arguments）

其他的能实现自动化客户端的语言，比如VB，有一种叫做命名参数（named arguments）的概念，假设我们有这样的一个VB方法：

Song(artist, title, length):    rem Visual Basic

要想调用这个方法，除了按顺序写全这三个参数来调用之外，我们可以用命名参数的方法：

Song title := 'Get It On':      rem Visual Basic

这等同于调用 Song(nil, 'Get It On', nil)。
在Ruby中，可以在调用的时候用哈希结构来指定参数名和它的值，比如：

Song.new( 'title' => 'Get It On' )

for each方法

VB有一个方法 ``for each''语句，可以在服务端对一个集合进行迭代， WIN32OLE对象也有一个方法完成相同的作用。

一个例子

下面这个例子，用到了微软的Excel，覆盖了上面说的很多概念。首先，我们创建了一个新的基于Excel的WIN32OLE对象，并增加了一些单元格，并给这些单元格赋值。然后，我们选中了一个区域，并创建了图表（chart），我们通过给这个图表的type属性赋值，使它成为一个3D的图表。然后，

The following example, using Microsoft Excel, illustrates most of these concepts. First, we create a new object attached to Excel and set some cell values. Next we select a range of cells and create a . We set the Type property in the excelchart object to make it a 3D chart. Next we'll loop through and change the chart rotation, 10?at a time. We'll add a few charts, and we'll use each to step through and print them out. Finally, we'll close down the Excel application and exit.

require 'win32ole'

# -4100 is the value for the Excel constant xl3DColumn.
ChartTypeVal = -4100;

# Creates OLE object to Excel
excel = WIN32OLE.new("excel.application")

# Create and rotate the chart

excel['Visible'] = TRUE;
workbook = excel.Workbooks.Add();
excel.Range("a1")['Value'] = 3;
excel.Range("a2")['Value'] = 2;
excel.Range("a3")['Value'] = 1;
excel.Range("a1:a3").Select();
excelchart = workbook.Charts.Add();
excelchart['Type'] = ChartTypeVal;

30.step(180, 10) do |rot|
    excelchart['Rotation'] = rot
end

excelchart2 = workbook.Charts.Add();
excelchart3 = workbook.Charts.Add();

charts = workbook.Charts
charts.each { |i| puts i }

excel.ActiveWorkbook.Close(0);
excel.Quit();

优化

As with most (if not all) high-level languages, it can be all too easy to churn out code that is unbearably slow, but that can be easily fixed with a little thought.

对WIN32OLE来说， 我们要避免不必要的动态查找（dynamic lookups）。如果可能，尽量把WIN32OLE对象 赋给一个变量，然后引用这个变量的元素，而不要使用一大串的"." 构成的表达式。

比如，我们不应该写出这样的代码：

workbook.Worksheets(1).Range("A1").value = 1
workbook.Worksheets(1).Range("A2").value = 2
workbook.Worksheets(1).Range("A3").value = 4
workbook.Worksheets(1).Range("A4").value = 8

我们可以通过把这些语句的前面部分放到一个临时变量里，而使用临时变量来执行方法。

worksheet = workbook.Worksheets(1)

worksheet.Range("A1").value = 1
worksheet.Range("A2").value = 2
worksheet.Range("A3").value = 4
worksheet.Range("A4").value = 8

Extracted from the book "Programming Ruby - The Pragmatic Programmer's Guide"
Copyright © 2001 by Addison Wesley Longman, Inc. This material may be distributed only subject to the terms and conditions set forth in the Open Publication License, v1.0 or later (the latest version is presently available at http://www.opencontent.org/openpub/)).

Distribution of substantively modified versions of this document is prohibited without the explicit permission of the copyright holder.

Distribution of the work or derivative of the work in any standard (paper) book form is prohibited unless prior permission is obtained from the copyright holder.


Programming Ruby: The Pragmatic Programmer's Guide

Programming Ruby

实用程序员指南

上一章< 目录 ^
下一章>

扩展Ruby

在Ruby中扩展Ruby的新功能是很容易的，如果你用c来写底层的代码，那么我们就能更好的扩展Ruby的功能。

用c来扩展ruby是非常简单的事情。比如，我们我们在为Sunset Diner and Grill建造一个基于internet的自动点唱机，它将从硬盘播放mp3文件或者从cd唱机播放cd音频。我们想从ruby程序中控制硬件系统，硬件提供商为我们提供了一个C语言的头文件，和一个二进制的实现库文件，我们所需要做的是创建一个Ruby对象，映射到相应的C函数调用。

在进一步使Ruby和C语言一起工作之前，先让我们从c语言角度看看ruby对象是什么样的。[本章又很多信息都是从随同ruby发布的 README.EXT摘录的，如果你想扩展ruby的话，可以参考最新版本的README.EXT]

C语言中的Ruby对象

我们要看的第一件事情是在C语言中如何表示和访问Ruby的数据类型。Ruby中所有东西都是对象，所有的变量都指向一个对象。在C语言中，所有Ruby变量的类型都是VALUE，它要们是一个指向Ruby对象的指针，要们指向一个立即值（immediate value），比如Fixnum。

这也是Ruby如何在C中实现面向对象的代码的：一个Ruby对象是在内存中分配的一个结构，这个结构包括一个包括实例变量的表，和关于关于类的信息。这个类本身是另一个对象（分配了内存结构），有一个包括类中方法定义的表。

VALUE 是一个指针

VALUE 是一个指针指向一个定义好的Ruby对象结构，而不能指向其他类型的结构。每个Ruby内建的类的结构定义在"ruby.h"中，命名都以R开头加类名，比如RString和RArray。

有几种办法来判断一个VALUE所对应的结构的类型。宏TYPE( obj )可以返回代表指定对象的C类型的常量的值：T_OBJECT, T_STRING或其它。代表内建类的常量在ruby.h中定义。注意这里我们说得类型是实现上的细节，而不是向某一个对象的类。(Note that the type we are referring to here is an implementation detail---it is not the same as the class of an object.)

如果你想确认一个vaulue指针指向一个特定的结构，你可以用宏Check_Type。如果给定的value不是type型的，将会产生一个TypeError 的异常。（type是T_STRING, T_FLOAT或其它）。

Check_Type(VALUE value, int type)

如果你很关心速度，这里有一些很快的宏来判断立即值Fixnum 和nil：
FIXNUM_P(value) -> non-zero if value is a Fixnum
NIL_P(value) -> non-zero if value is nil
RTEST(value) -> non-zero if value is neither nil nor false

Again, note that we are talking about ``type'' as the C structure that represents a particular built-in type. The class of an object is a different beast entirely. The class objects for the built-in classes are stored in C global variables named rb_c Classname (for instance, rb_cObject); modules are named rb_m Modulename.

It wouldn't be advisable to mess with the data in these structures directly, however---you may look, but don't touch unless you are fond of debuggers. You should normally use only the supplied C functions to manipulate Ruby data (we'll talk more about this in just a moment).

However, in the interests of efficiency you may need to dig into these structures to obtain data. In order to dereference members of these C structures, you have to cast the generic VALUE to the proper structure type. ruby.h contains a number of macros that perform the proper casting for you, allowing you to dereference structure members easily. These macros are named RCLASSNAME , as in RSTRING or RARRAY. For example:

VALUE str, arr;
RSTRING(str)->len -> length of the Ruby string
RSTRING(str)->ptr -> pointer to string storage
RARRAY(arr)->len  -> length of the Ruby array
RARRAY(arr)->capa -> capacity of the Ruby array
RARRAY(arr)->ptr  -> pointer to array storage

VALUE 作为立即对象（Immediate Object）

我们上面说过，立即值不是指针：: Fixnum, Symbol, true, false, 和 nil 直接存储在 VALUE中。

Fixnum 值在存储器中占用31个bit(其他的cpu结构可能占用63个bit)，然后这个值左移一位，最后一位设置为 "1"，当VAULE指向其他类型的数据时，这个最低位（LSB）总是"0"，其他的立即值得LSB也是0，这样，可以通过这个位的值是0还是1来判断这个值是不是Fixnum。

其它的立即值true, false, nil在C语言中用常量Qtrue, Qfalse, Qnil来表示，你可以用这些常量来测试一个值是不是true，false，nil等，或者用转换宏来测试。

Writing Ruby in C

用Ruby编程的乐趣之一是你可以在C语言里像Ruby中一样实现，你可以用同样的方法名，同样的逻辑，除了一些语法的区别，比如，这里有一个用Ruby写的简单的test类：

class Test
  def initialize
    @arr = Array.new
  end
  def add(anObject)
    @arr.push(anObject)
  end
end

完成上面同样功能的C代码如下：

#include "ruby.h"

static VALUE t_init(VALUE self)
{
  VALUE arr;

  arr = rb_ary_new();
  rb_iv_set(self, "@arr", arr);
  return self;
}

static VALUE t_add(VALUE self, VALUE anObject)
{
  VALUE arr;

  arr = rb_iv_get(self, "@arr");
  rb_ary_push(arr, anObject);
  return arr;
}

VALUE cTest;

void Init_Test() {
  cTest = rb_define_class("Test", rb_cObject);
  rb_define_method(cTest, "initialize", t_init, 0);
  rb_define_method(cTest, "add", t_add, 1);
}

让我们详细的看看这里面的细节，这段程序包括了本章的很多重要的内容。首先，我们需要把ruby.h文件引入，才能使用其中的一些预定义的东西。

然后来看看最后一个函数：Init_Test。每个类或模块都要定义一个C的全局函数 Init_name局函，这个函数将会在ruby解释器第一次载入扩展name的时候执行，它用来初始化这个扩展（extension ），使它融入Ruby的环境中。这个例子里，我们定义了一个类Test，它是Object的子类。（Object在ruby.h中用rb_cObject表示）

然后我们建立了两个函数add和initialize，这两个都是类Test的实例方法。函数rb_define_method 绑定了Ruby中的方法和对应的C语言的实现。所以，在Ruby中调用这个类的add方法，将会调用C函数t_add。

类似的，当在Ruby中调用Test的new方法时，将会调用initialize方法，也就是C程序里面的t_init方法（没有Ruby参数）。

现在回来看看方法initialize的定义，我们说过它不需要参数，但是那里却真的有一个参数。除了ruby方法中的参数，每个方法都会被传递一个最初的VALUE参数，这个VALUE指的是方法的接收者（receiver ），类似Ruby中的self。

在initialize 方法中我们做的是创建一个Ruby中的数组，并用实例变量@arr指向这个数组。 Just as you would expect if you were writing Ruby source, referencing an instance variable that doesn't exist creates it.

最后，t_add方法从当前对象取得实例变量@arr，并用 Array#push 把传递过来的参数放到里面。当用这种方法访问实例变量的时候，前缀@是必不可少的，否则在Ruby中将不能访问这个变量。

Despite the extra, clunky syntax that C imposes, you're still writing in Ruby---you can manipulate objects using all of the method calls you've come to know and love, with the added advantage of being able to craft tight, fast code when needed.

警告: 从Ruby中能访问的C语言中的所有方法都必须返回一个VALUE值，即使Qnil也行。否则会出现core dump。

要想在Ruby中使用上面的C代码，只需要动态的把它require进来就行。

require "code/ext/Test"
t = Test.new
t.add("Bill Chase")
C/Ruby 类型转换函数和宏
从C 类型转换到 Ruby：
 | INT2NUM(int) | -> Fixnum or Bignum
 | INT2FIX(int) | -> Fixnum (faster)
 | INT2NUM(long or int) | -> Fixnum or Bignum
 | INT2FIX(long or int) | -> Fixnum (faster)
 | CHR2FIX(char) | -> Fixnum
 | rb_str_new2(char *) | -> String
 | rb_float_new(double) | -> Float
从Ruby对象转换到C的数据类型：
int | NUM2INT(Numeric) | (Includes type check)
int | FIX2INT(Fixnum) | (Faster)
unsigned int | NUM2UINT(Numeric) | (Includes type check)
unsigned int | FIX2UINT(Fixnum) | (Includes type check)
long | NUM2LONG(Numeric) | (Includes type check)
long | FIX2LONG(Fixnum) | (Faster)
unsigned long | NUM2ULONG(Numeric) | (Includes type check)
char | NUM2CHR(Numeric or String) | (Includes type check)
char * | STR2CSTR(String) |
char * | rb_str2cstr(String, int *length) | Returns length as well
double | NUM2DBL(Numeric) |

在C语言中运行Ruby表达式

如果你在C语言中编程，而且需要用到一些Ruby的表达式，但是不想写一段代码来实现，你可以用C版本的eval。假如你有一个数组，需要把里面的flag标志都清除：

rb_eval_string("anObject.each{|x| x.clearFlag }");

如果你只是想调用一个特别的方法，可以这样：

If you just want to call a particular method (which is cheaper than eval-ing an entire string) you can use

rb_funcall(receiver, method_id, argc, ...)

在Ruby 和 C之间共享数据

我们已经涉及到了很多基础的东西，现在来看看我们自动点唱机的例子，我们要用Ruby包装C代码，还要在两种语言之间共享数据。

直接共享变量

尽管你可以在C中和Ruby中各维护一个变量，并保持它们之间的同步，但是这样做是不可取得，违反了DRY（Don't Repeat Yourself）原则。更好的方法是直接在Ruby和C之间共享一个变量，你可以通过在C语言里面创建一个Ruby对象，然后把它绑定到一个Ruby中地全局变量来共享一个全局变量，在这种情况下，"$"是可选的，但是为了阅读方便最好还是在前面加上"$"。

VALUE hardware_list;
hardware_list = rb_ary_new();
rb_define_variable("$hardware", &hardware_list);
...
rb_ary_push(hardware_list, rb_str_new2("DVD"));
rb_ary_push(hardware_list, rb_str_new2("CDPlayer1"));
rb_ary_push(hardware_list, rb_str_new2("CDPlayer2"));

然后，在Ruby中就可以$hardware来访问C语言中的变量hardware_list。

$hardware ? ["DVD", "CDPlayer1", "CDPlayer2"]

You can also create hooked variables that will call a specified function when the variable is accessed, and virtual variables that only call the hooks---no actual variable is involved. See the API section that begins on page 189 for details.

If you create a Ruby object from C and store it in a C global variable without exporting it to Ruby, you must at least tell the garbage collector about it, lest ye be reaped inadvertently:

VALUE obj;
obj = rb_ary_new();
rb_global_variable(obj);

包装C结构（Wrapping C Structures）

Now on to the really fun stuff. We've got the vendor's library that controls the audio CD jukebox units, and we're ready to wire it into Ruby. The vendor's header file looks like this:

typedef struct _cdjb {
  int statusf;
  int request;
  void *data;
  char pending;
  int unit_id;
  void *stats;
} CDJukebox;

// Allocate a new CDPlayer structure and bring it online
CDJukebox *CDPlayerNew(int unit_id);

// Deallocate when done (and take offline)
void CDPlayerDispose(CDJukebox *rec);

// Seek to a disc, track and notify progress
void CDPlayerSeek(CDJukebox *rec,
                  int disc,
                  int track,
                  void (*done)(CDJukebox *rec, int percent));
// ... others...
// Report a statistic
double CDPlayerAvgSeekTime(CDJukebox *rec);

This vendor has its act together; while the vendor might not admit it, the code is written with an object-oriented flavor. We don't know what all those fields mean within the CDJukeBox structure, but that's okay---we can treat it as an opaque pile of bits. The vendor's code knows what to do with it, we just have to carry it around.

Anytime you have a C-only structure that you would like to handle as a Ruby object, you should wrap it in a special, internal Ruby class called DATA (type T_DATA). There are two macros to do this wrapping, and one to retrieve your structure back out again.

C Datatype Wrapping
VALUE | Data_Wrap_Struct(VALUE class, void (*mark)(), void (*free)(), void *ptr")
 | Wraps the given C datatype ptr, registers the two garbage collection routines (see below), and returns a VALUE pointer to a genuine Ruby object. The C type of the resulting object is T_DATA and its Ruby class is class.
VALUE | Data_Make_Struct(VALUE class, c-type, void (*mark)(), void (*free)(), c-type *")
 | Allocates a structure of the indicated type first, then proceeds as Data_Wrap_Struct. c-type is the name of the C datatype that you're wrapping, not a variable of that type.
 | Data_Get_Struct(VALUE obj,c-type,c-type *")
 | Returns the original pointer. This macro is a type-safe wrapper around the macro DATA_PTR(obj), which evaluates the pointer.

The object created by Data_Wrap_Struct is a normal Ruby object, except that it has an additional C datatype that can't be accessed from Ruby. As you can see in Figure 17.1 on page 177, this C datatype is separate from any instance variables that the object contains. But since it's a separate thing, how do you get rid of it when the garbage collector claims this object? What if you have to release some resource (close some file, clean up some lock or IPC mechanism, and so on)?
Figure not available...

In order to participate in Ruby's mark-and-sweep garbage collection process, you need to define a routine to free your structure, and possibly a routine to mark any references from your structure to other structures. Both routines take a void pointer, a reference to your structure. The mark routine will be called by the garbage collector during its ``mark'' phase. If your structure references other Ruby objects, then your mark function needs to identify these objects using rb_gc_mark(value). If the structure doesn't reference other Ruby objects, you can simply pass 0 as a function pointer.

When the object needs to be disposed of, the garbage collector will call the free routine to free it. If you have allocated any memory yourself (for instance, by using Data_Make_Struct), you'll need to pass a free function---even if it's just the standard C library's free routine. For complex structures that you have allocated, your free function may need to traverse the structure to free all the allocated memory.

First a simple example, without any special handling. Given the structure definition

typedef struct mp3info {
  char *title;
  char *artist;
  int  genre;
} MP3Info;

we can create a structure, populate it, and wrap it as an object.[We cheat a bit in this example. Our MP3Info structure has a couple of char pointers in it. In our code we initialize them from two static strings. This means that we don't have to free these strings when the MP3Info structure is freed. If we'd allocated these strings dynamically, we'd have to write a free method to dispose of them.]

MP3Info *p;
VALUE info;

p = ALLOC(MP3Info);
p->artist = "Maynard Ferguson";
p->title = "Chameleon";
...
info = Data_Wrap_Struct(cTest, 0, free, p);

info is a VALUE type, a genuine Ruby object of class Test (represented in C by the built-in type T_DATA). You can push it onto an array, hold a reference to it in an object, and so on. At some later point in the code, we may want to access this structure again, given the VALUE:

VALUE doit(VALUE info) {
  MP3Info *p;
  Data_Get_Struct(info, MP3Info, p);
  ...
  p->artist    -> "Maynard Ferguson"
  p->title     -> "Chameleon"
  ...
}

In order to follow convention, however, you may need a few more things: support for an initialize method, and a ``C-constructor.'' If you were writing Ruby source, you'd allocate and initialize an object by calling new. In C extensions, the corresponding call is Data_Make_Struct. However, although this allocates memory for the object, it does not automatically call an initialize method; you need to do that yourself:

info = Data_Make_Struct(cTest, MP3Info, 0, free, one);
rb_obj_call_init(info, argc, argv);

This has the benefit of allowing subclasses in Ruby to override or augment the basic initialize in your class. Within initialize, it is allowable (but not necessarily advisable) to alter the existing data pointer, which may be accessed directly with DATA_PTR(obj).

And finally, you may want to define a ``C-constructor''---that is, a globally available C function that will create the object in one convenient call. You can use this function within your own code or allow other extension libraries to use it. All of the built-in classes support this idea with functions such as rb_str_new, rb_ary_new, and so on. We can make our own:

VALUE mp3_info_new() {
  VALUE info;
  MP3Info *one;
  info = Data_Make_Struct(cTest, MP3Info, 0, free, one);
  ...
  rb_obj_call_init(info, 0, 0);
  return info;
}

An Example

Okay, now we're ready for a full-size example. Given our vendor's header file above, we write the following code.

#include "ruby.h"
#include "cdjukebox.h"
VALUE cCDPlayer;
static void cd_free(void *p) {
  CDPlayerDispose(p);
}
static void progress(CDJukebox *rec, int percent)
{
  if (rb_block_given_p()) {
    if (percent > 100) percent = 100;
    if (percent < 0) percent = 0;
    rb_yield(INT2FIX(percent));
  }
}
static VALUE
cd_seek(VALUE self, VALUE disc, VALUE track)
{
  CDJukebox *ptr;
  Data_Get_Struct(self, CDJukebox, ptr);
  CDPlayerSeek(ptr,
               NUM2INT(disc),
               NUM2INT(track),
               progress);
  return Qnil;
}
static VALUE
cd_seekTime(VALUE self)
{
  double tm;
  CDJukebox *ptr;
  Data_Get_Struct(self, CDJukebox, ptr);
  tm = CDPlayerAvgSeekTime(ptr);
  return rb_float_new(tm);
}
static VALUE
cd_unit(VALUE self)
{
  return rb_iv_get(self, "@unit");
}

static VALUE
cd_init(VALUE self, VALUE unit)
{
  rb_iv_set(self, "@unit", unit);
  return self;
}
VALUE cd_new(VALUE class, VALUE unit)
{
  VALUE argv[1];
  CDJukebox *ptr = CDPlayerNew(NUM2INT(unit));
  VALUE tdata = Data_Wrap_Struct(class, 0, cd_free, ptr);
  argv[0] = unit;
  rb_obj_call_init(tdata, 1, argv);
  return tdata;
}
void Init_CDJukebox() {
  cCDPlayer = rb_define_class("CDPlayer", rb_cObject);
  rb_define_singleton_method(cCDPlayer, "new", cd_new, 1);
  rb_define_method(cCDPlayer, "initialize", cd_init, 1);
  rb_define_method(cCDPlayer, "seek", cd_seek, 2);
  rb_define_method(cCDPlayer, "seekTime", cd_seekTime, 0);
  rb_define_method(cCDPlayer, "unit", cd_unit, 0);
}

Now we have the ability to control our jukebox from Ruby in a nice, object-oriented manner:

require "code/ext/CDJukebox"
p = CDPlayer.new(1)
puts "Unit is #{p.unit}"
p.seek(3, 16) {|x| puts "#{x}% done" }
puts "Avg. time was #{p.seekTime} seconds"
produces:
Unit is 1
26% done
79% done
100% done
Avg. time was 1.2 seconds

This example demonstrates most of what we've talked about so far, with one additional neat feature. The vendor's library provided a callback routine---a function pointer that is called every so often while the hardware is grinding its way to the next disc. We've set that up here to run a code block passed as an argument to seek. In the progress function, we check to see if there is an iterator in the current context and, if there is, run it with the current percent done as an argument.

Memory Allocation

You may sometimes need to allocate memory in an extension that won't be used for object storage---perhaps you've got a giant bitmap for a Bloom filter, or an image, or a whole bunch of little structures that Ruby doesn't use directly.

In order to work correctly with the garbage collector, you should use the following memory allocation routines. These routines do a little bit more work than the standard malloc. For instance, if ALLOC_N determines that it cannot allocate the desired amount of memory, it will invoke the garbage collector to try to reclaim some space. It will raise a NoMemError if it can't or if the requested amount of memory is invalid.

Memory Allocation
type * | ALLOC_N(c-type, n")
 | Allocates n c-type objects, where c-type is the literal name of the C type, not a variable of that type.
type * | ALLOC(c-type")
 | Allocates a c-type and casts the result to a pointer of that type.
 | REALLOC_N(var, c-type, n")
 | Reallocates n c-types and assigns the result to var, a pointer to a c-type.
type * | ALLOCA_N(c-type, n")
 | Allocates memory for n objects of c-type on the stack---this memory will be automatically freed when the function that invokes ALLOCA_N returns.

创建扩展程序

写完了需要的源代码，我们需要编译它，以使Ruby程序可以访问它。我们可以把它编译成共享的对象，在运行时候动态装载；或者把它静态的连接到Ruby解释器本身。基本的过程都已眼：

  • 在给定的目录写好源代码。
  • 创建 extconf.rb文件。
  • 运行extconf.rb创建 Makefile，用来编译C文件。
  • 运行make。
  • 运行 make install。

用 extconf.rb创建Makefile

上面看到的创建一个Ruby扩展程序的过程中，主要的步骤是作为程序员写的extconf.rb。在这个程序里，需要判断当前系统需要支持哪些特性，以及这些特性的位置。运行extconf.rb程序将产生一个Makefile文件，这是一个根据用户的需求和系统属性定制文件。当你运行make程序时，我们创建的扩展程序将会被编译（也可能被安装到某个地方）。

最简单的extconf.rb有可能只有两行长，并且，对很多Ruby扩展程序来说，这两行足够了：

require 'mkmf'
create_makefile("Test")

第一行引入了模块"mkmf"，这个模块有我们要用到的各种命令。第二行为扩展程序"Test"创建了一个Makefile文件。（注意"Test"是扩展程序的名字，而Makefile一直都是这个名字）。Test将会在这个目录被编译。

Let's say that we run this extconf.rb program in a directory containing a single source file, main.c. The result is a Makefile that will build our extension. On our system, this contains the following commands.

gcc -fPIC -I/usr/local/lib/ruby/1.6/i686-linux -g -O2  \
  -c main.c -o main.o
gcc -shared -o Test.so main.o -lc

The result of this compilation is Test.so, which may be dynamically linked into Ruby at runtime with ``require''. See how the mkmf commands have located platform-specific libraries and used compiler-specific options automatically. Pretty neat, eh?

Although this basic program works for many simple extensions, you may have to do some more work if your extension needs header files or libraries that aren't included in the default compilation environment, or if you conditionally compile code based on the presence of libraries or functions.

A common requirement is to specify nonstandard directories where include files and libraries may be found. This is a two-step process. First, your extconf.rb should contain one or more dir_config commands. This specifies a tag for a set of directories. Then, when you run the extconf.rb program, you tell mkmf where the corresponding physical directories are on the current system.

If extconf.rb contains the line dir_config( name ), then you give the location of the corresponding directories with the command-line options:

--with-name-include=directory

* Add directory/include to the compile command.
--with-name-lib=directory

* Add directory/lib to the link command.

If (as is common) your include and library directories are both subdirectories of some other directory, and (as is also common) they're called include and lib, you can take a shortcut:

--with-name-dir=directory

* Add directory/lib and directory/include to the link command and compile command, respectively.

There's a twist here. As well as specifying all these --with options when you run extconf.rb, you can also use the --with options that were specified when Ruby was built for your machine. This means you can find out the locations of libraries that are used by Ruby itself.

To make all this concrete, lets say you need to use libraries and include files for the CD jukebox we're developing. Your extconf.rb program might contain

require 'mkmf'
dir_config('cdjukebox')
# .. more stuff
create_makefile("CDJukeBox")

You'd then run extconf.rb with something like:

% ruby extconf.rb --with-cdjukebox-dir=/usr/local/cdjb

The generated Makefile would assume that the libraries were in /usr/local/cdjb/lib and the include files were in /usr/local/cdjb/include.

The dir_config command adds to the list of places to search for libraries and include files. It does not, however, link the libraries into your application. To do that, you'll need to use one or more have_library or find_library commands.

have_library looks for a given entry point in a named library. If it finds the entry point, it adds the library to the list of libraries to be used when linking your extension. find_library is similar, but allows you to specify a list of directories to search for the library.

require 'mkmf'
dir_config('cdjukebox')
have_library('cdjb', 'CDPlayerNew')
create_makefile("CDJukeBox")

On some platforms, a popular library may be in one of several places. The X Window system, for example, is notorious for living in different directories on different systems. The find_library command will search a list of supplied directories to find the right one (this is different from have_library, which uses only configuration information for the search). For example, to create a Makefile that uses X Windows and a jpeg library, extconf.rb might contain

require 'mkmf'

if have_library("jpeg","jpeg_mem_init") and
   find_library("X11", "XOpenDisplay", "/usr/X11/lib",
                "/usr/X11R6/lib", "/usr/openwin/lib")
then
    create_makefile("XThing")
else
    puts "No X/JPEG support available"
end

We've added some additional functionality to this program. All of the mkmf commands return false if they fail. This means that we can write an extconf.rb that generates a Makefile only if everything it needs is present. The Ruby distribution does this so that it will try to compile only those extensions that are supported on your system.

You also may want your extension code to be able to configure the features it uses depending on the target environment. For example, our CD jukebox may be able to use a high-performance MP3 decoder if the end user has one installed. We can check by looking for its header file.

require 'mkmf'
dir_config('cdjukebox')
have_library('cdjb', 'CDPlayerNew')
have_header('hp_mp3.h')
create_makefile("CDJukeBox")

We can also check to see if the target environment has a particular function in any of the libraries we'll be using. For example, the setpriority call would be useful but isn't always available. We can check for it with:

require 'mkmf'
dir_config('cdjukebox')
have_func('setpriority')
create_makefile("CDJukeBox")

Both have_header and have_func define preprocessor constants if they find their targets. The names are formed by converting the target name to uppercase and prepending ``HAVE_''. Your C code can take advantage of this using constructs such as:

#if defined(HAVE_HP_MP3_H)
#  include <hp_mp3.h>
#endif

#if defined(HAVE_SETPRIORITY)
  err = setpriority(PRIOR_PROCESS, 0, -10)
#endif

If you have special requirements that can't be met with all these mkmf commands, your program can directly add to the global variables $CFLAGS and $LFLAGS, which are passed to the compiler and linker, respectively.

静态连接 Static Linking

最后，如果你的系统不支持动态连接，或者你想你的扩展程序静态的连接到Ruby本身，编辑Ruby发行版本中的ext目录下的Setup文件，把你的扩展程序的目录加进去，然后重新编译Ruby。在Setup文件中列出来的扩展程序都会被静态的连接到Ruby可执行程序。如果你不想支持任何动态连接，你可以编辑Setup文件让它只包含一行：

option nodynamic

Embedding a Ruby Interpreter

In addition to extending Ruby by adding C code, you can also turn the problem around and embed Ruby itself within your application. Here's an example.

#include "ruby.h"

main() {
  /* ... our own application stuff ... */
  ruby_init();
  ruby_script("embedded");
  rb_load_file("start.rb");
  while (1) {
    if (need_to_do_ruby) {
      ruby_run();
    }
    /* ... run our app stuff */
  }
}

To initialize the Ruby interpreter, you need to call ruby_init(). But on some platforms, you may need to take special steps before that:

#if defined(NT)
  NtInitialize(&argc, &argv);
#endif
#if defined(__MACOS__) && defined(__MWERKS__)
  argc = ccommand(&argv);
#endif

See main.c in the Ruby distribution for any other special defines or setup needed for your platform.

Embedded Ruby API
void | ruby_init(")
 | Sets up and initializes the interpreter. This function should be called before any other Ruby-related functions.
void | ruby_options(int argc, char **argv")
 | Gives the Ruby interpreter the command-line options.
void | ruby_script(char *name")
 | Sets the name of the Ruby script (and $0) to name.
void | rb_load_file(char *file")
 | Loads the given file into the interpreter.
void | ruby_run(")
 | Runs the interpreter.

You need to take some special care with exception handling; any Ruby calls you make at this top level should be protected to catch exceptions and handle them cleanly. rb_protect, rb_rescue, and related functions are documented on page 192.

For an example of embedding a Ruby interpreter within another program, see also eruby, which is described beginning on page 147.

Bridging Ruby to Other Languages

So far, we've discussed extending Ruby by adding routines written in C. However, you can write extensions in just about any language, as long as you can bridge the two languages with C. Almost anything is possible, including awkward marriages of Ruby and C++, Ruby and Java, and so on.

But you may be able to accomplish the same thing without resorting to C code. For example, you could bridge to other languages using middleware such as CORBA or COM. See the section on Windows automation beginning on page 164 for more details.

Ruby C Language API

Last, but by no means least, here are several C-level functions that you may find useful when writing an extension.

Some functions require an ID: you can obtain an ID for a string by using rb_intern and reconstruct the name from an ID by using rb_id2name.

As most of these C functions have Ruby equivalents that are already described in detail elsewhere in this book, the descriptions here will be brief.

Also note that the following listing is not complete. There are many more functions available---too many to document them all, as it turns out. If you need a method that you can't find here, check ``ruby.h'' or ``intern.h'' for likely candidates. Also, at or near the bottom of each source file is a set of method definitions that describe the binding from Ruby methods to C functions. You may be able to call the C function directly, or search for a wrapper function that calls the function you are looking for. The following list, based on the list in README.EXT, shows the main source files in the interpreter.

Ruby Language Core

class.c error.c eval.c gc.c object.c parse.y variable.c
Utility Functions

dln.c regex.c st.c util.c
Ruby Interpreter

dmyext.c inits.c keywords main.c ruby.c version.c
Base Library

array.c bignum.c compar.c dir.c enum.c file.c hash.c io.c marshal.c math.c numeric.c pack.c prec.c process.c random.c range.c re.c signal.c sprintf.c string.c struct.c time.c

Defining Objects
VALUE | rb_define_class(char *name, VALUE superclass")
 | Defines a new class at the top level with the given name and superclass (for class Object, use rb_cObject).
VALUE | rb_define_module(char *name")
 | Defines a new module at the top level with the given name.
VALUE | rb_define_class_under(VALUE under, char *name, VALUE superclass")
 | Defines a nested class under the class or module under.
VALUE | rb_define_module_under(VALUE under, char *name")
 | Defines a nested module under the class or module under.
void | rb_include_module(VALUE parent, VALUE module")
 | Includes the given module into the class or module parent.
void | rb_extend_object(VALUE obj, VALUE module")
 | Extends obj with module.
VALUE | rb_require(const char *name")
 | Equivalent to ``require name.'' Returns Qtrue or Qfalse.

In some of the function definitions that follow, the parameter argc specifies how many arguments a Ruby method takes. It may have the following values.

argc | Function prototype
0..17 | VALUE func(VALUE self, VALUE arg...)
 | The C function will be called with this many actual arguments.
-1 | VALUE func(int argc, VALUE *argv, VALUE self)
 | The C function will be given a variable number of arguments passed as a C array.
-2 | VALUE func(VALUE self, VALUE args)
 | The C function will be given a variable number of arguments passed as a Ruby array.

In a function that has been given a variable number of arguments, you can use the C function rb_scan_args to sort things out (see below).

Defining Methods
void  | rb_define_method(VALUE classmod, char *name, VALUE(*func)(), int argc")
Defines an instance method in the class or module classmod with the given name, implemented by the C function func and taking argc arguments.
void  | rb_define_module_function(VALUE classmod, char *name, VALUE(*func)(), int argc)")
Defines a method in class classmod with the given name, implemented by the C function func and taking argc arguments.
void  | rb_define_global_function(char *name, VALUE(*func)(), int argc")
Defines a global function (a private method of Kernel) with the given name, implemented by the C function func and taking argc arguments.
void  | rb_define_singleton_method(VALUE classmod, char *name, VALUE(*func)(), int argc")
Defines a singleton method in class classmod with the given name, implemented by the C function func and taking argc arguments.
int  | rb_scan_args(int argcount, VALUE *argv, char *fmt, ...")
Scans the argument list and assigns to variables similar to scanf: fmt is a string containing zero, one, or two digits followed by some flag characters. The first digit indicates the count of mandatory arguments; the second is the count of optional arguments. A ``*'' means to pack the rest of the arguments into a Ruby array. A ``&'' means that an attached code block will be taken and assigned to the given variable (if no code block was given, Qnil will be assigned). After the fmt string, pointers to VALUE are given (as with scanf) to which the arguments are assigned.

VALUE name, one, two, rest;
rb_scan_args(argc, argv, "12", &name, &one, &two);
rb_scan_args(argc, argv, "1*", &name, &rest);
void  | rb_undef_method(VALUE classmod, const char *name")
Undefines the given method name in the given classmod class or module.
void  | rb_define_alias(VALUE classmod, const char *newname, const char *oldname")
Defines an alias for oldname in class or module classmod.

Defining Variables and Constants
void  | rb_define_const(VALUE classmod, char *name, VALUE value")
Defines a constant in the class or module classmod, with the given name and value.
void  | rb_define_global_const(char *name, VALUE value")
Defines a global constant with the given name and value.
void  | rb_define_variable(const char *name, VALUE *object")
Exports the address of the given object that was created in C to the Ruby namespace as name. From Ruby, this will be a global variable, so name should start with a leading dollar sign. Be sure to honor Ruby's rules for allowed variable names; illegally named variables will not be accessible from Ruby.
void  | rb_define_class_variable(VALUE class, const char *name, VALUE val")
Defines a class variable name (which must be specified with a ``@@'' prefix) in the given class, initialized to value.
void  | rb_define_virtual_variable(const char *name, VALUE(*getter)(), void(*setter)()")
Exports a virtual variable to Ruby namespace as the global $name. No actual storage exists for the variable; attempts to get and set the value will call the given functions with the prototypes:

VALUE getter(ID id, VALUE *data,
             struct global_entry *entry);
void setter(VALUE value, ID id, VALUE *data,
            struct global_entry *entry);

You will likely not need to use the entry parameter and can safely omit it from your function declarations.
void  | rb_define_hooked_variable(const char *name, VALUE *variable, VALUE(*getter)(), void(*setter)()")
Defines functions to be called when reading or writing to variable. See also rb_define_virtual_variable.
void  | rb_define_readonly_variable(const char *name, VALUE *value")
Same as rb_define_variable, but read-only from Ruby.
void  | rb_define_attr(VALUE variable, const char *name, int read, int write")
Creates accessor methods for the given variable, with the given name. If read is nonzero, create a read method; if write is nonzero, create a write method.
void  | rb_global_variable(VALUE *obj")
Registers the given address with the garbage collector.

Calling Methods
VALUE | rb_funcall(VALUE recv, ID id, int argc, ...")
 | Invokes the method given by id in the object recv with the given number of arguments argc and the arguments themselves (possibly none).
VALUE | rb_funcall2(VALUE recv, ID id, int argc, VALUE *args")
 | Invokes the method given by id in the object recv with the given number of arguments argc and the arguments themselves given in the C array args.
VALUE | rb_funcall3(VALUE recv, ID id, int argc, VALUE *args")
 | Same as rb_funcall2, but will not call private methods.
VALUE | rb_apply(VALUE recv, ID name, int argc, VALUE args")
 | Invokes the method given by id in the object recv with the given number of arguments argc and the arguments themselves given in the Ruby Array args.
ID | rb_intern(char *name")
 | Returns an ID for a given name. If the name does not exist, a symbol table entry will be created for it.
char * | rb_id2name(ID id")
 | Returns a name for the given id.
VALUE | rb_call_super(int argc, VALUE *args")
 | Calls the current method in the superclass of the current object.

Exceptions
void | rb_raise(VALUE exception, const char *fmt, ...")
 | Raises an exception. The given string fmt and remaining arguments are interpreted as with printf.
void | rb_fatal(const char *fmt, ...")
 | Raises a Fatal exception, terminating the process. No rescue blocks are called, but ensure blocks will be called. The given string fmt and remaining arguments are interpreted as with printf.
void | rb_bug(const char *fmt, ...")
 | Terminates the process immediately---no handlers of any sort will be called. The given string fmt and remaining arguments are interpreted as with printf. You should call this function only if a fatal bug has been exposed. You don't write fatal bugs, do you?
void | rb_sys_fail(const char *msg")
 | Raises a platform-specific exception corresponding to the last known system error, with the given msg.
VALUE | rb_rescue(VALUE (*body)(), VALUE args, VALUE(*rescue)(), VALUE rargs")
 | Executes body with the given args. If a StandardError exception is raised, then execute rescue with the given rargs.
VALUE | rb_ensure(VALUE(*body)(), VALUE args, VALUE(*ensure)(), VALUE eargs")
 | Executes body with the given args. Whether or not an exception is raised, execute ensure with the given rargs after body has completed.
VALUE | rb_protect(VALUE (*body)(), VALUE args, int *result")
 | Executes body with the given args and returns nonzero in result if any exception was raised.
void | rb_notimplement(")
 | Raises a NotImpError exception to indicate that the enclosed function is not implemented yet, or not available on this platform.
void | rb_exit(int status")
 | Exits Ruby with the given status. Raises a SystemExit exception and calls registered exit functions and finalizers.
void | rb_warn(const char *fmt, ...")
 | Unconditionally issues a warning message to standard error. The given string fmt and remaining arguments are interpreted as with printf.
void | rb_warning(const char *fmt, ...")
 | Conditionally issues a warning message to standard error if Ruby was invoked with the -w flag. The given string fmt and remaining arguments are interpreted as with printf.

Iterators
void | rb_iter_break(")
 | Breaks out of the enclosing iterator block.
VALUE | rb_each(VALUE obj")
 | Invokes the each method of the given obj.
VALUE | rb_yield(VALUE arg")
 | Transfers execution to the iterator block in the current context, passing arg as an argument. Multiple values may be passed in an array.
int | rb_block_given_p(")
 | Returns true if yield would execute a block in the current context---that is, if a code block was passed to the current method and is available to be called.
VALUE | rb_iterate(VALUE (*method)(), VALUE args, VALUE (*block)(), VALUE arg2")
 | Invokes method with argument args and block block. A yield from that method will invoke block with the argument given to yield, and a second argument arg2.
VALUE | rb_catch(const char *tag, VALUE (*proc)(), VALUE value")
 | Equivalent to Ruby catch.
void | rb_throw(const char *tag , VALUE value")
 | Equivalent to Ruby throw.

Accessing Variables
VALUE | rb_iv_get(VALUE obj, char *name")
 | Returns the instance variable name (which must be specified with a ``@'' prefix) from the given obj.
VALUE | rb_ivar_get(VALUE obj, ID name")
 | Returns the instance variable name from the given obj.
VALUE | rb_iv_set(VALUE obj, char *name, VALUE value")
 | Sets the value of the instance variable name (which must be specified with a ``@'' prefix) in the given obj to value. Returns value.
VALUE | rb_ivar_set(VALUE obj, ID name, VALUE value")
 | Sets the value of the instance variable name in the given obj to value. Returns value.
VALUE | rb_gv_set(const char *name, VALUE value")
 | Sets the global variable name (the ``$'' prefix is optional) to value. Returns value.
VALUE | rb_gv_get(const char *name")
 | Returns the global variable name (the ``$'' prefix is optional).
void | rb_cvar_set(VALUE class, ID name, VALUE val")
 | Sets the class variable name in the given class to value.
VALUE | rb_cvar_get(VALUE class, ID name")
 | Returns the class variable name from the given class.
int | rb_cvar_defined(VALUE class, ID name")
 | Returns Qtrue if the given class variable name has been defined for class; otherwise, returns Qfalse.
void | rb_cv_set(VALUE class, const char *name, VALUE val")
 | Sets the class variable name (which must be specified with a ``@@'' prefix) in the given class to value.
VALUE | rb_cv_get(VALUE class, const char *name")
 | Returns the class variable name (which must be specified with a ``@@'' prefix) from the given class.

Object Status
 | OBJ_TAINT(VALUE obj")
 | Marks the given obj as tainted.
int | OBJ_TAINTED(VALUE obj")
 | Returns nonzero if the given obj is tainted.
 | OBJ_FREEZE(VALUE obj")
 | Marks the given obj as frozen.
int | OBJ_FROZEN(VALUE obj")
 | Returns nonzero if the given obj is frozen.
 | Check_SafeStr(VALUE str")
 | Raises SecurityError if current safe level > 0 and str is tainted, or a TypeError if str is not a T_STRING.
int | rb_safe_level(")
 | Returns the current safe level.
void | rb_secure(int level")
 | Raises SecurityError if level <= current safe level.
void | rb_set_safe_level(int newlevel")
 | Sets the current safe level to newlevel.

Commonly Used Methods
VALUE | rb_ary_new(")
 | Returns a new Array with default size.
VALUE | rb_ary_new2(long length")
 | Returns a new Array of the given length.
VALUE | rb_ary_new3(long length, ...")
 | Returns a new Array of the given length and populated with the remaining arguments.
VALUE | rb_ary_new4(long length, VALUE *values")
 | Returns a new Array of the given length and populated with the C array values.
void | rb_ary_store(VALUE self, long index, VALUE value")
 | Stores value at index in array self.
VALUE | rb_ary_push(VALUE self, VALUE value")
 | Pushes value onto the end of array self. Returns value.
VALUE | rb_ary_pop(VALUE self")
 | Removes and returns the last element from the array self.
VALUE | rb_ary_shift(VALUE self")
 | Removes and returns the first element from the array self.
VALUE | rb_ary_unshift(VALUE self, VALUE value")
 | Pushes value onto the front of array self. Returns value.
VALUE | rb_ary_entry(VALUE self, long index")
 | Returns array self's element at index.
int | rb_respond_to(VALUE self, ID method")
 | Returns nonzero if self responds to method.
VALUE | rb_thread_create(VALUE (*func)(), void *data")
 | Runs func in a new thread, passing data as an argument.
VALUE | rb_hash_new(")
 | Returns a new, empty Hash.
VALUE | rb_hash_aref(VALUE self, VALUE key")
 | Returns the element corresponding to key in self.
VALUE | rb_hash_aset(VALUE self, VALUE key, VALUE value")
 | Sets the value for key to value in self. Returns value.
VALUE | rb_obj_is_instance_of(VALUE obj, VALUE klass")
 | Returns Qtrue if obj is an instance of klass.
VALUE | rb_obj_is_kind_of(VALUE obj, VALUE klass")
 | Returns Qtrue if klass is the class of obj or class is one of the superclasses of the class of obj.
VALUE | rb_str_new(const char *src, long length")
 | Returns a new String initialized with length characters from src.
VALUE | rb_str_new2(const char *src")
 | Returns a new String initialized with the null-terminated C string src.
VALUE | rb_str_dup(VALUE str")
 | Returns a new String object duplicated from str.
VALUE | rb_str_cat(VALUE self, const char *src, long length")
 | Concatenates length characters from src onto the String self. Returns self.
VALUE | rb_str_concat(VALUE self, VALUE other")
 | Concatenates other onto the String self. Returns self.
VALUE | rb_str_split(VALUE self, const char *delim")
 | Returns an array of String objects created by splitting self on delim.

Extracted from the book "Programming Ruby - The Pragmatic Programmer's Guide"
Copyright © 2001 by Addison Wesley Longman, Inc. This material may be distributed only subject to the terms and conditions set forth in the Open Publication License, v1.0 or later (the latest version is presently available at http://www.opencontent.org/openpub/)).

Distribution of substantively modified versions of this document is prohibited without the explicit permission of the copyright holder.

Distribution of the work or derivative of the work in any standard (paper) book form is prohibited unless prior permission is obtained from the copyright holder.


Programming Ruby: The Pragmatic Programmer's Guide

Programming Ruby

实用程序员指南

上一章 < 目录 ^
下一章 >

Ruby语言

这一章让我们来对Ruby语言做一个从头到脚地审视。和前面的章节不同，在这里我们集中精力阐述事实，而不是揭示语言设计的细节问题。然后我们也尽量避免对内建的类和模块的介绍，这些内容在第275页开始有详细地介绍。

如果这章的内容看上去很眼熟，那是因为它们本来就应该是我们熟悉的；我们在前面介绍性的章节中对它们都有描述。应该把这一章看成自包含的对核心Ruby语法描述的语言参考。

代码编排（Source Layout）

Ruby程序是用7字节ACSII码（7-bit ASCII）来表示的。[Ruby也有完整的语言扩展来支持Kanji编码，就是使用EUC, SJIS或UTF-8编码系统。如果一段代码使用了7字节ACSII码以外的编码格式，那么KCODE选项就应该被适当地设置，如137页所示。]

Ruby是一种面向行的（line-oriented）程序设计语言。Ruby的表达式和声明语句都是在一行的最后结束，除非一个语句很明显是没有结束的---比如一行的最后一个字符是一个操作符或者逗号。一个分号可以用来分割一行中的多个语句。你也可以在一行最后加一个反斜杠来使这一行延续到下一行。注释以'#'开始，到物理行结束为止。在编译（执行）的时候，注释将被忽略。

a = 1

b = 2; c = 3

d = 4 + 5 + #不需要'\' 6 + 7

e = 8 + 9 \ + 10 #需要'\'

在以=begin开始的一行和以=end结束的一行之间的语句会被编译器忽略，不过它们可以作为内嵌文档来使用（请参见第511页开始的附录A）。

Ruby只读一遍源程序，所以你可以向Ruby编译器的stdin发送程序代码。

echo 'print "Hello\n"' | ruby

在任何地方，只要编译器遇到一行仅包含“__END__”的代码，没有前行和后随的空格，它就把这一行当成程序的结束---后面的所有代码都不会被编译了。然而，后面的代码可以用一个全局IO对象DATA读入正在执行的当前程序，这在217页有描述。

BEGIN和END模块

每个Ruby源文件都可以声明一些代码模块用来在文件被载入前（BEGIN模块）或者在程序执行结束后（END模块）运行。

BEGIN {
  begin code
}

END { end code }

一个程序可以有多个BEGIN和END模块。BEGIN模块以它们出现的顺序执行，END模块以相反的顺序执行。

常规定界输入（General Delimited Input）

还有另外一种用来表示字符串常量，数组，正则表达式和shell命令的格式，就是用常规定界语法。所有这些常量都是以一个百分号开始，后面跟一个用来指定常量类型的字符。对这些字符的描述请参见200页的表18.1；常用的几个字符在这章的后面会有描述。

常规定界输入
类型 | 涵义 | 参见
%q | 单引号字符串 | 202
%Q, % | 双引号字符串 | 202
%w | 常量数组 | 204
%r | 正则表达式 | 205
%x | Shell命令 | 218

在类型字符后面是一个分隔符，它可以是任何字符。如果分隔符是“(”，“[”，“{”，“<”中的一个，那么常量由分隔符到相应的后随结束符（译者注：比如左括号'('对右括号')'）之间的字符组成，注意嵌套的分隔符。对于其他的分隔符，常量由两个分隔符之间的字符组成。

%q/this is a string/
%q-string-
%q(a (nested) string)

定界字符串可以跨越多行文本。

%q{def fred(a)
     a.each { |i| puts i }
   end}

基本数据类型

Ruby的基本数据类型有数字，字符串，数组，哈希表（hashes），范围（ranges），符号（symbols）和正则表达式。

整数和浮点数类型

Ruby的整数是类Fixnum或者Bignum的对象。Fixnum可以容纳物理机器一个字长少一位大小的整数。当一个Fixnum超过它的范围时，它会自动转换成Bignum类型，而Bignum类型的范围只能被物理内存所限制。如果一个Bignum上的操作使得它的最终值适合一个Fixnum保存，那么结果就会以一个Fixnum对象返回。

整数前面可以带一个可选的符号标记，一个可选的进制指示符（0代表八进制，0x代表十六进制，0b代表二进制），然后跟一串相应进制的数字串。在数字串中的下划线将被忽略。

你可以在一个ASCII字符前面加一个问号来取得它相应的整数值。Ctrl和Meta（译者注：Meta就是Alt键）的键组合可以由?\C-x, ?\M-x, 和?\M-\C-x来产生。一个字符ch的Ctrl版本是（就是Ctrl + ch组合）ch&0x9f，然后它的Meta版本是ch | 0x80。你可以用字符序列?\\来取得反斜杠的整数值。

123456                    # Fixnum
123_456                   # Fixnum (下划线被忽略)
-543                      # Negative Fixnum
123_456_789_123_345_789   # Bignum
0xaabb                    # 十六进制
0377                      # 八进制
-0b1010                   # 二进制 (负的)
0b001_001                 # 二进制
?a                        # character code
?A                        # upper case
?\C-a                     # control a = A - 0x40
?\C-A                     # case ignored for control chars
?\M-a                     # meta sets bit 7
?\M-\C-a                  # meta and control a

一个带有小数点的数字串（也可以带指数）被认为是Float对象，大小和物理机器上double类型一样。你必须在小数点后面跟一个数字，因为像1.e3意味着去调用类Fixnum的方法e3。

12.34 | 12.34
-.1234e2 | -12.34
1234e-2 | 12.34

字符串

Ruby提供了好几种方法来产生字符串常量。每中方法都产生String对象。每种方法在字符串是如何定界和要产生多少替换上是不一样的。

单引号字符串（'stuff'和%q/stuff/）进行最少限度的替换。两种格式的表示方法都把“\\”转换成一个反斜杠，然后把“\'”转换成一个单引号。

'hello' | hello
'a backslash \'\\\'' | a backslash '\'
%q/simple string/ | simple string
%q(nesting (really) works) | nesting (really) works
%q no_blanks_here ; | no_blanks_here

双引号字符串（"stuff"，%Q/stuff/，和%/stuff/）还会进行额外的替换，详见203页表18.2。

双引号字符串中的替换

\a | Bell/alert (0x07) | \nnn | Octal nnn
\b | Backspace (0x08) | \xnn | Hex nn
\e | Escape (0x1b) | \cx | Control-x
\f | Formfeed (0x0c) | \C-x | Control-x
\n | Newline (0x0a) | \M-x | Meta-x
\r | Return (0x0d) | \M-\C-x | Meta-control-x
\s | Space (0x20) | \x | x
\t | Tab (0x09) | #{expr} | Value of expr
\v | Vertical tab (0x0b)

a = 123
"\123mile" | Smile
"Say \"Hello\"" | Say "Hello"
%Q!"I said 'nuts'," I said! | "I said 'nuts'," I said
%Q{Try #{a + 1}, not #{a - 1}} | Try 124, not 122
%<Try #{a + 1}, not #{a - 1}> | Try 124, not 122
"Try #{a + 1}, not #{a - 1}" | Try 124, not 122

字符串可以跨越多个文本行，在这种情况下它们可以包含换行符。我们也可以使用here documents来表达很长的字符串常量。每当Ruby遇到字符序列<<标识符 或者 <<括号字符串的时候，它就把后随的所有输入行变成一个字符串常量。它一直接收字符到字符串中，直到它遇到一行以前面的标识符或者括号字符串开始的程序行。你可以在<< 后面跟一个负号，使得结束符可以向右边缩进。如果一个括号字符串被用来作为结束符，那么引号规则就会应用到here document中；另外，双引号规则也起作用。

a = 123
print <<HERE
Double quoted \
here document.
Sum = #{a + 1}
HERE

print <<-'THERE' This is single quoted. The above used #{a + 1} THERE
产生结果:
Double quoted here document.
Sum = 124
    This is single quoted.
    The above used #{a + 1}

相邻的单引号和双引号字符串会连接在一起成为一个 String 对象。

'Con' "cat" 'en' "ate" "Concatenate"

字符串保存在8比特字节长的序列中[为了在日本使用，jcode库支持一系列对EUC，SJIS，或者UTF-8编码字符串的操作。不过底层的字符串还是以字节形式被访问。]，然后每个字节都保存一个256以内的8比特值，包括空值null和换行。203页表18.2所示的替换规则允许不可打印字符可以便利并且可移植地嵌入到普通字符串中。

每一次一个字符串被用在一个赋值或者用做一个参数的时候，一个新的String对象会被创建。

for i in 1..3
  print 'hello'.id, " "
end
产生结果:
537767360 537767070 537767040

String类的详细资料请参见第363页开始的文档。

范围（Ranges）

在一个条件表达式外面，表达式expr..expr和expr...expr会创建范围对象（Range）。中间两点的格式不包括两边的边界值；中间有三点的格式还包括最后那个边界值。要看Range类的详细信息，请参见第359页。第222页有在条件语句中使用范围的其他方法。

数组

类Array的对象由一系列在方括号中由逗号分隔的对象引用组成。最后一个逗号会被忽略。

arr = [ fred, 10, 3.14, "This is a string", barney("pebbles"), ]

字符串数组可以由一个简捷符号组成，%w，它可以把由逗号分隔的一个连续字符串中的子字符提取出来组成一个字符串数组。空格可以由反斜杠转义。这是一种定界输入的格式，请参见200--201页。

arr = %w( fred wilma barney betty great\ gazoo )
arr ["fred", "wilma", "barney", "betty", "great gazoo"]

哈希值（Hashes）

一个Ruby的Hash对象是由一系列在花括号里面的键/值匹配对组成的。由逗号分隔键/值对，键和值之间由=>连接。最后一个逗号会被忽略。

colors = { "red"   => 0xf00,
           "green" => 0x0f0,
           "blue"  => 0x00f
         }

在一个特定的哈希表中的键和值的类型不一定要一致。

哈希键值的要求

作为一个哈希键值的唯一要求是它必须响应消息hash，然后返回一个哈希值，这个哈希值不能改变。这意味着一些类（比如Array 和 Hash，像这种类型的）不能很好的适合哈希键值的要求，因为它们的值会随着它们的内容而变化。

如果你保持了一个对象的外部引用，然后这个对象又被用来做为一个哈希键。那么你通过这个引用来改变这个对象然后改变哈希值，这个哈希键对应的哈希值将不会起作用。

因为字符串经常被用来当作键值，然后字符串值又会经常改变，Ruby对字符串键值做了特殊处理。当你把一个String对象当做哈希键时，哈希表会复制一个字符串，然后把这个字符串当作键值。后随的所有对原来字符串的改变不会影响哈希表。

如果你写了自己的类然后把它们的实例当作哈希键值，你必须确保哈希表的键值在创建以后不再改变或者在键值改变的时候记得调用Hash#rehash方法来重新索引哈希表。

符号对象（Symbols）

一个Ruby的符号对象是一个对象名的内部表示。你可以在对象名前面加一个冒号来产生相应的符号对象。一个特定的对象名总是产生相同的符号对象，不论对象名在程序中是如何被使用的。

:Object
:myVariable

其他编程语言称这个替换过程为“interning”，然后把符号对象叫做“atoms”。

正则表达式

正则表达式是类型Regexp的对象。它们可以通过显式地调用Regexp.new来创建，或者使用文字常量方式，/pattern/和%r{ pattern}。%r创建方法是一种普通定界输入（详见200--201页）。

/pattern/
/pattern/选项
%r{pattern}
%r{pattern}选项
Regexp.new( 'pattern' [, 选项
            ] )

正则式的选项

一个正则表达式可以包含一个或多个选项来修改默认的匹配规则。如果你使用文字常量来创建正则式，那么一个或多哥选项就紧跟在结束符后面。如果你使用Regexp.new方法，那么选项就是构造函数的第二个参数。

i | 忽略大小写. 这个匹配将会忽略模式和字符串中的字符的大小写。如果全局变量$=被设置，那么匹配也会大小写不敏感。
o | 替换一次。任何在正则式中的#{...}替换都仅进行一次，就是第一次遇到它的时候。其他情况中，替换会在每次一个Regexp对象产生的时候进行。
m | 多行模式。通常的，'.'匹配除了换行以外的任何字符。有了/m 选项以后，'.'匹配任何字符了。
x | 扩展模式。复杂的正则表达式会很难阅读。'x'选项允许我们可以在模式中加入空格，换行和注释来使它更容易被阅读。

正则式的模式

规则字符
除了.，|， (， )， [， \， ^， {， +， $， *，和?，任何字符都匹配它自己。如果要匹配上面的字符，那么应该在它们前面加一个反斜杠。

^
匹配一行的开始

$
匹配一行的末尾

\A
匹配字符串的开始

\z
匹配字符串的末尾

\Z
匹配字符串的末尾，如果字符串以"\n"结束，那么它从"\n"前面开始匹配。

\b, \B
相应匹配单词边界和非单词的边界

[ 字符 ]
一个字符串类型匹配任何出现在方括号里面的单个字符。 字符|, (, ), [, ^, $, *,和?，在其他地方有特殊含义，但是在方括号里面就失去了它们的特殊含义。序列 \ nnn, \x nn, \c x, \C- x, \M- x, and \M-\C- x有如203页表18.2所示的特殊含义。序列\d, \D, \s, \S, \w，和\W是一组字符串的缩写，如59页表5.1所示。序列c1-c2表示在c1和c2之间的所有字符（包括c1，c2）。字符]或-必须仅跟在左括号后面。一个（^）紧跟在左括号后使匹配反义---模式匹配任何不在方括号中出现的字符。

\d, \s, \w
分别匹配数字，空格和单词字符（word character）。\D, \S, 和\W匹配不是数字，空格和单词字符的字符。这些缩写在59页表5.1有详细描述。

. (句点)
出现在方括号外面，它代表除了换行以外的任何字符（如果/m选项被设置了，那么它也匹配换行符）。

re *

匹配零次或者多次re的出现。

re +
匹配一次或者多次re的出现。

re {m,n}
匹配最少"m"次然后最多"n"次re的出现。

re ?
匹配零次或一次re的出现。 *, +, 和{m,n}都是最大匹配的，加上一个问号使得它们进行最小匹配。

re1 | re2
匹配re1或者re2，|的优先级很低。

(...)
圆括号用来集合正则表达式。比如，模式 /abc+/匹配一个含有一个'a', 一个'b'然后一个或多个'c'的字符串。/(abc)+/就匹配一次或多次"abc"的出现。圆括号还用来收集模式匹配的结果。Ruby为每一个左圆括号保存括号中的部分匹配结果，直到相应的右括号为止。在同一个匹配中，\1代表第一组匹配的结果，\2代表第二组结果，依次类推。在匹配外面，特殊变量$1, $2等起到相同的作用。

替换作用

#{...}
表示一个字符串中的表达式的替换。默认情况，替换会在每次匹配成功时发生。当/o选项被设置时，它仅仅匹配一次。

\0, \1, \2, ... \9, \&, \`, \', \+
替换第n组匹配，或者整个，前面或后面，或者最高匹配的那一组。

语法扩展

和Perl和Python类试，Ruby的正则表达式提供了一些对传统Unix正则表达式的扩展。这些扩展都写在(?/code> and )里面。这些圆括号括着的扩展是组，但是它们不会产生后引用（backreferences）：它们不会设置\1和$1等变量的值。

(?# 注释)
在模式串中插入注释。注释在模式匹配的时候被忽略。

(?:re)
把re加入组，然而不产生后引用。这在当你希望创建一个组但是又不想让它设置$1等值的时候特别有用。在下面的例子中，两个模式都保存了一个包括月份，日子和年份的数据，用冒号或空格分隔。第一种格式的在$2和$4中保存了数据，但是第二种格式没有保存内部数据。

date = "12/25/01"
date =~ %r{(\d+)(/|:)(\d+)(/|:)(\d+)}
[$1,$2,$3,$4,$5] | ["12", "/", "25", "/", "01"]
date =~ %r{(\d+)(?:/|:)(\d+)(?:/|:)(\d+)}
[$1,$2,$3] | ["12", "25", "01"]

(?=re)
在此处匹配re，但是不要销毁它（也可以惬意地叫做零距离回顾（``zero-width positive lookahead''））。它可以让我们方便地引用前面的内容而不会影响$&。在这个例子中，scan方法匹配后面跟一个句点的单词，但是句点不包括在匹配结果里面。

str = "red, white, and blue"
str.scan(/[a-z]+(?=,)/) ?/td> ["red", "white"]

(?!re)
看看在此处是否不匹配re。不要销毁这个匹配（否定的零距离回顾）。比如，/hot(?!dog)(\w+)/匹配任何包含“hot”然后后面不跟“dog”的单词，返回$1中的最后一个单词。

(?>re)
把一个独立的正则式嵌入到第一个正则式中。这个表达式在当前匹配点被确定。如果它销毁了一些字符，这些字符就不会被上级的正则式访问到。因此这个结构是有后回溯机制的（backtracking），这可以是一个执行效果的增强。比如，模式/a.*b.*a/的基本意思是匹配一个含有一个'a'，后面跟数个'b'，然后没有后随'a'的字符串。然而，这可以用嵌套的正则式/a(?>.*b).*a/来避免。在这种格式中，内嵌的正则式消耗掉了所有字符串，直到有字符'b'的出现。当一个对后随'a'的查找失败以后，就没有回溯的必要了，这个模式匹配迅速结束。

require "benchmark"
include Benchmark
str = "a" + ("b" * 5000)
bm(8) do |test|
  test.report("Normal:") { str =~ /a.*b.*a/ }
  test.report("Nested:") { str =~ /a(?>.*b).*a/ }
end
产生结果:
              user     system      total        real
Normal:   0.420000   0.000000   0.420000 (  0.414843)
Nested:   0.000000   0.000000   0.000000 (  0.001205)

(?imx)
打开"i"，"m"或"x"选项。如果用在一个组里面，那么效果就限制在组里面。

(?-imx)
关闭"i"，"m"或"x"选项。

(?imx:re)
打开re的"i"，"m"或"x"选项。

(?-imx:re)
关闭re的"i"，"m"或"x"选项。

标识名

Ruby的标识名用来指向常量，变量，方法，类和模块。标识名的首字符用来帮助我们确定标识所指向内容的类型。一些标识名，就是210页表18.3所示的都是保留字，不能用来当作变量，方法，类或模块的名字。

保留字

__FILE__ | and | def | end | in | or | self | unless
__LINE__ | begin | defined? | ensure | module | redo | super | until
BEGIN | break | do | false | next | rescue | then | when
END | case | else | for | nil | retry | true | while
alias | class | elsif | if | not | return | undef | yield

在下面的描述中，小写字母表示'a'到'z'的字母和'_'，下划线。大写字母表示'A'到'Z'的字母，还有数字表示'0'到'9'的数字字符。标识符字符表示大小写字母和数字的组合。

一个局部变量由一个小写字母后面跟标识符组成。

fred  anObject  _x  three_two_one

类的实例变量由一个‘@’开始，后面跟一个大写或小写字母，然后后面还可以有一些标识符字符。

@name  @_  @Size

一个类变量由两个‘@@’开始，后面跟一个大写或小写字母，然后后面还可以有一些标识符字符。

@@name  @@_  @@Size

一个常量名由一个大写字母开始，后面跟标识符字符。类名和模块名是常量，所以也遵循常量的命名规则。常量名一般是由大写字母组成，然后可以带下划线。

module Math
  PI = 3.1415926
end
class BigBlob

全局变量，以及一些特殊的系统变量，由一个美圆符号'$'开始，然后后面跟标识符字符。另外，还有一些有两个字符组成的变量名，它们第二个字符是标点符号。这些预设的变量名在213页有描述。最后，全局变量名也可以用"$-"开头，后面跟任意一个字符。

$params  $PROGRAM  $!  $_  $-a  $-.

关于方法的命名规则，在从第225页的开始的章节有描述。

变量/方法的疑惑

每当Ruby在一个表达式中遇到一个比如像'a'这样的字符，他必须确定这个'a'到底是一个局部变量的引用还是一个无参数的函数方法调用。为了确定这个，ruby用了一种启发式（heuristic）的方法。当Ruby在读取源代码的时候，它记下了那些被赋值的标识符。它假定这些标识符是变量名。然后如果它在随后遇到一个标识符可能是变量，也可能是方法名的时候，它就查看这个标识服是否在以前被赋过值。如果被赋过值，它就把标识符当成变量名；否则它就把标识符当成一个方法调用。作为一个比较诡异的例子，请参见以下的代码片段，由Clemens Hintze递交。

def a
  print "Function 'a' called\n"
  99
end

for i in 1..2   if i == 2     print "a=", a, "\n"   else     a = 1     print "a=", a, "\n"   end end
produces:
a=1
Function 'a' called
a=99

在语法解析的时候，Ruby遇到第一个'a'，因为此时'a'没有被赋值，就认为它是一个方法调用。然后到第二个打印语句的时候，Ruby已经遇到了一个赋值，所以此时它把'a'当成一个变量。

注意赋值语句不一定要被执行---Ruby只要见到它就可以了。这个程序不会出现错误。

a = 1 if false; a

变量和常量

Ruby变量和常量都是指向对象的引用。变量自身并没有内在的类型。一个变量的类型仅仅由它指向的对象的信息决定。 [我们说一个变量没有类型，我们的意思就是任何给定的变量可以在不同时间指向许多不同的类型。]

一个Ruby的常量也是对一个对象的引用。常量在Ruby首次对它赋值时被创建（一般在一个类或模块的定义中）。和一些通融性差的语言不同，允许我们修改变量的值，尽管这样会得到一个警告信息。

MY_CONST = 1
MY_CONST = 2   # generates a warning
产生结果:
prog.rb:2: warning: already initialized constant MY_CONST

注意虽然常量不应该被修改，但是我们可以修改它指向的对象的内部状态。

MY_CONST = "Tim"
MY_CONST[0] = "J"   #修改常量的内部状态
MY_CONST "Jim"

赋值潜在地给一个对象别名，给同一个对象不同的名字。

常量和变量的作用域

在一个类或模块中定义的常量可以方便地在类或模块任何地方访问。在模块或类外面，它们可以使用域作用符“::”来访问，在一个表达式前加一个“::”可以返回类或模块相应的对象。在任何类外面定义的常量，可以通过在它前面加"::"访问到。我们不能在方法中定义常量。

OUTER_CONST = 99
class Const
def getConst
CONST
end
CONST = OUTER_CONST + 1
end
Const.new.getConst | 100
Const::CONST | 100
::OUTER_CONST | 99

全局变量在整个程序中都可以使用。每个指向特定全局对象名的引用都返回相同的对象。引用一个未初始化的全局变量返回一个

类变量在整个类或模块体中都可以被使用。类变量在使用之前必须被初始化。一个类变量被这个类的所有对象所共有，然后只在类内部使用。

class Song
  @@count = 0
  def initialize
    @@count += 1
  end
  def Song.getCount
    @@count
  end
end

类变量属于最内层嵌套类或模块。最顶层的类变量是定义在Object中的，然后就像全局变量一样。如果接收者是一个类或者模块，那么定义在单例类里面的类变量属于接收者；否则，它们属于接收者的类。

class Holder
  @@var = 99
  def Holder.var=(val)
    @@var = val
  end
end

a = Holder.new def a.var   @@var end

Extracted from the book "Programming Ruby - The Pragmatic Programmer's Guide"
Copyright © 2001 by Addison Wesley Longman, Inc. This material may be distributed only subject to the terms and conditions set forth in the Open Publication License, v1.0 or later (the latest version is presently available at http://www.opencontent.org/openpub/)).

Distribution of substantively modified versions of this document is prohibited without the explicit permission of the copyright holder.

Distribution of the work or derivative of the work in any standard (paper) book form is prohibited unless prior permission is obtained from the copyright holder.


Programming Ruby: The Pragmatic Programmer's Guide

Programming Ruby

The Pragmatic Programmer's Guide

Previous < Contents ^
Next >

Classes and Objects

Classes and objects are obviously central to Ruby, but at first sight they can seem a little confusing. There seem to be a lot of concepts: classes, objects, class objects, instance methods, class methods, and singleton classes. In reality, however, Ruby has just a single underlying class and object structure, which we'll discuss in this chapter. In fact, the basic model is so simple, we can describe it in a single paragraph.

A Ruby object has three components: a set of flags, some instance variables, and an associated class. A Ruby class is an object of class Class, which contains all the object things plus a list of methods and a reference to a superclass (which is itself another class). All method calls in Ruby nominate a receiver (which is by default self, the current object). Ruby finds the method to invoke by looking at the list of methods in the receiver's class. If it doesn't find the method there, it looks in the superclass, and then in the superclass's superclass, and so on. If the method cannot be found in the receiver's class or any of its ancestors, Ruby invokes the method method_missing on the original receiver.

And that's it---the entire explanation. On to the next chapter.

``But wait,'' you cry, ``I spent good money on this chapter. What about all this other stuff---singleton classes, class methods, and so on. How do they work?''

Good question.

How Classes and Objects Interact

All class/object interactions are explained using the simple model given above: objects reference classes, and classes reference zero or more superclasses. However, the implementation details can get a tad tricky.

We've found that the simplest way of visualizing all this is to draw the actual objects that Ruby implements. So, in the following pages we'll look at all the possible combinations of classes and objects. Note that these are not class diagrams in the UML sense; we're showing structures in memory and pointers between them.

Your Basic, Everyday Object

Let's start by looking at an object created from a simple class. Figure 19.1 on page 239 shows an object referenced by a variable, lucille, the object's class, Guitar, and that class's superclass, Object. Notice how the object's class reference (called klass for historical reasons that really bug Andy) points to the class object, and how the super pointer from that class references the parent class.

Figure not available...

When Ruby executes Guitar.strings(), it follows the same process as before: it goes to the receiver, class Guitar, follows the klass reference to class Guitar$'$, and finds the method.

Finally, note that an ``S'' has crept into the flags in class Guitar$'$. The classes that Ruby creates automatically are marked internally as singleton classes. Singleton classes are treated slightly differently within Ruby. The most obvious difference from the outside is that they are effectively invisible: they will never appear in a list of objects returned from methods such as Module#ancestors or ObjectSpace::each_object .

Object-Specific Classes

Ruby allows you to create a class tied to a particular object. In the following example, we create two String objects. We then associate an anonymous class with one of them, overriding one of the methods in the object's base class and adding a new method.

a = "hello"
b = a.dup
class <<a
def to_s
"The value is '#{self}'"
end
def twoTimes
self + self
end
end
a.to_s | » | "The value is 'hello'"
a.twoTimes | » | "hellohello"
b.to_s | » | "hello"

This example uses the ``class << obj'' notation, which basically says ``build me a new class just for object obj.'' We could also have written it as:

a = "hello"
b = a.dup
def a.to_s
"The value is '#{self}'"
end
def a.twoTimes
self + self
end
a.to_s | » | "The value is 'hello'"
a.twoTimes | » | "hellohello"
b.to_s | » | "hello"

The effect is the same in both cases: a class is added to the object ``a''. This gives us a strong hint about the Ruby implementation: a singleton class is created and inserted as a's direct class. a's original class, String, is made this singleton's superclass. The before and after pictures are shown in Figure 19.3 on page 242.

Ruby performs a slight optimization with these singleton classes. If an object's klass reference already points to a singleton class, a new one will not be created. This means that the first of the two method definitions in the previous example will create a singleton class, but the second will simply add a method to it.

Figure not available...

Mixin Modules

When a class includes a module, that module's instance methods become available as instance methods of the class. It's almost as if the module becomes a superclass of the class that uses it. Not surprisingly, that's about how it works. When you include a module, Ruby creates an anonymous proxy class that references that module, and inserts that proxy as the direct superclass of the class that did the including. The proxy class contains references to the instance variables and methods of the module. This is important: the same module may be included in many different classes, and will appear in many different inheritance chains. However, thanks to the proxy class, there is still only one underlying module: change a method definition in that module, and it will change in all classes that include that module, both past and future.

module SillyModule
  def hello
    "Hello."
  end
end
class SillyClass
  include SillyModule
end
s = SillyClass.new
s.hello » "Hello."

module SillyModule
  def hello
    "Hi, there!"
  end
end
s.hello » "Hi, there!"

The relationship between classes and the modules they include is shown in Figure 19.4 on page 243. If multiple modules are included, they are added to the chain in order.

Figure not available...

If a module itself includes other modules, a chain of proxy classes will be added to any class that includes that module, one proxy for each module that is directly or indirectly included.

Extending Objects

Just as you can define an anonymous class for an object using ``class <<obj '', you can mix a module into an object using Object#extend . For example:

module Humor
  def tickle
    "hee, hee!"
  end
end
a = "Grouchy"
a.extend Humor
a.tickle » "hee, hee!"

There is an interesting trick with extend. If you use it within a class definition, the module's methods become class methods.

module Humor
def tickle
"hee, hee!"
end
end
class Grouchy
include Humor
extend Humor
end
Grouchy.tickle | » | "hee, hee!"
a = Grouchy.new
a.tickle | » | "hee, hee!"

This is because calling extend is equivalent to self.extend, so the methods are added to self, which in a class definition is the class itself.

Class and Module Definitions

Having exhausted the combinations of classes and objects, we can (thankfully) get back to programming by looking at the nuts and bolts of class and module definitions.

In languages such as C++ and Java, class definitions are processed at compile time: the compiler loads up symbol tables, works out how much storage to allocate, constructs dispatch tables, and does all those other obscure things we'd rather not think too hard about.

Ruby is different. In Ruby, class and module definitions are executable code. Although parsed at compile time, the classes and modules are created at runtime, when the definition is encountered. (The same is also true of method definitions.) This allows you to structure your programs far more dynamically than in most conventional languages. You can make decisions once, when the class is being defined, rather than each time that objects of the class are used. The class in the following example decides as it is being defined what version of a decryption routine to create.

class MediaPlayer
  include Tracing if $DEBUGGING

  if ::EXPORT_VERSION
    def decrypt(stream)
      raise "Decryption not available"
    end
  else
    def decrypt(stream)
      # ...
    end
  end

end

If class definitions are executable code, this implies that they execute in the context of some object: self must reference something. Let's find out what it is.

class Test
  puts "Type of self = #{self.type}"
  puts "Name of self = #{self.name}"
end
produces:
Type of self = Class
Name of self = Test

This means that a class definition is executed with that class as the current object. Referring back to the section about metaclasses on page 238, we can see that this means that methods in the metaclass and its superclasses will be available during the execution of the method definition. We can check this out.

class Test
  def Test.sayHello
    puts "Hello from #{name}"
  end

  sayHello
end
produces:
Hello from Test

In this example we define a class method, Test.sayHello, and then call it in the body of the class definition. Within sayHello, we call name, an instance method of class Module. Because Module is an ancestor of Class, its instance methods can be called without an explicit receiver within a class definition.

In fact, many of the directives that you use when defining a class or module, things such as alias_method, attr, and public, are simply methods in class Module. This opens up some interesting possibilities---you can extend the functionality of class and module definitions by writing Ruby code. Let's look at a couple of examples.

As a first example, let's look at adding a basic documentation facility to modules and classes. This would allow us to associate a string with modules and classes that we write, a string that is accessible as the program is running. We'll choose a simple syntax.

class Example
  doc "This is a sample documentation string"
  # .. rest of class
end

We need to make doc available to any module or class, so we need to make it an instance method of class Module.

class Module
  @@docs = Hash.new(nil)
  def doc(str)
    @@docs[self.name] = str
  end

  def Module::doc(aClass)
    # If we're passed a class or module, convert to string
    # ('<=' for classes checks for same class or subtype)
    aClass = aClass.name if aClass.type <= Module
    @@docs[aClass] || "No documentation for #{aClass}"
  end
end

class Example
  doc "This is a sample documentation string"
  # .. rest of class
end

module Another
  doc <<-edoc
    And this is a documentation string
    in a module
  edoc
  # rest of module
end

puts Module::doc(Example)
puts Module::doc("Another")
produces:
This is a sample documentation string
      And this is a documentation string
      in a module

The second example is a performance enhancement based on Tadayoshi Funaba's date module (described beginning on page 439). Say we have a class that represents some underlying quantity (in this case, a date). The class may have many attributes that present the same underlying date in different ways: as a Julian day number, as a string, as a [year, month, day] triple, and so on. Each value represents the same date and may involve a fairly complex calculation to derive. We therefore would like to calculate each attribute only once, when it is first accessed.

The manual way would be to add a test to each accessor:

class ExampleDate
  def initialize(dayNumber)
    @dayNumber = dayNumber
  end

  def asDayNumber
    @dayNumber
  end

  def asString
    unless @string
      # complex calculation
      @string = result
    end
    @string
  end

  def asYMD
    unless @ymd
      # another calculation
      @ymd = [ y, m, d ]
    end
    @ymd
  end
  # ...
end

This is a clunky technique---let's see if we can come up with something sexier.

What we're aiming for is a directive that indicates that the body of a particular method should be invoked only once. The value returned by that first call should be cached. Thereafter, calling that same method should return the cached value without reevaluating the method body again. This is similar to Eiffel's once modifier for routines. We'd like to be able to write something like:

class ExampleDate
  def asDayNumber
    @dayNumber
  end

  def asString
    # complex calculation
  end

  def asYMD
    # another calculation
    [ y, m, d ]
  end

  once :asString, :asYMD
end

We can use once as a directive by writing it as a class method of ExampleDate, but what should it look like internally? The trick is to have it rewrite the methods whose names it is passed. For each method, it creates an alias for the original code, then creates a new method with the same name. This new method does two things. First, it invokes the original method (using the alias) and stores the resulting value in an instance variable. Second, it redefines itself, so that on subsequent calls it simply returns the value of the instance variable directly. Here's Tadayoshi Funaba's code, slightly reformatted.

def ExampleDate.once(*ids)
  for id in ids
    module_eval <<-"end_eval"
      alias_method :__#{id.to_i}__, #{id.inspect}
      def #{id.id2name}(*args, &block)
        def self.#{id.id2name}(*args, &block)
          @__#{id.to_i}__
        end
        @__#{id.to_i}__ = __#{id.to_i}__(*args, &block)
      end
    end_eval
  end
end

This code uses module_eval to execute a block of code in the context of the calling module (or, in this case, the calling class). The original method is renamed __nnn__, where the nnn part is the integer representation of the method name's symbol id. The code uses the same name for the caching instance variable. The bulk of the code is a method that dynamically redefines itself. Note that this redefinition uses the fact that methods may contain nested singleton method definitions, a clever trick.

Understand this code, and you'll be well on the way to true Ruby mastery.

However, we can take it further. Look in the date module, and you'll see method once written slightly differently.

class Date
  class << self
    def once(*ids)
      # ...
    end
  end
  # ...
end

The interesting thing here is the inner class definition, ``class << self''. This defines a class based on the object self, and self happens to be the class object for Date. The result? Every method within the inner class definition is automatically a class method of Date.

The once feature is generally applicable---it should work for any class. If you took once and made it a private instance method of class Module, it would be available for use in any Ruby class.

Class Names Are Constants

We've said that when you invoke a class method, all you're doing is sending a message to the Class object itself. When you say something such as String.new("gumby"), you're sending the message new to the object that is class String. But how does Ruby know to do this? After all, the receiver of a message should be an object reference, which implies that there must be a constant called ``String'' somewhere containing a reference to the String object.[It will be a constant, not a variable, because ``String'' starts with an uppercase letter.] And in fact, that's exactly what happens. All the built-in classes, along with the classes you define, have a corresponding global constant with the same name as the class. This is both straightforward and subtle. The subtlety comes from the fact that there are actually two things named (for example) String in the system. There's a constant that references an object of class String, and there's the object itself.

The fact that class names are just constants means that you can treat classes just like any other Ruby object: you can copy them, pass them to methods, and use them in expressions.

def factory(klass, *args)
klass.new(*args)
end
factory(String, "Hello") | » | "Hello"
factory(Dir, ".") | » | #<Dir:0x401b51bc>
flag = true
(flag ? Array : Hash)[1, 2, 3, 4] | » | [1, 2, 3, 4]
flag = false
(flag ? Array : Hash)[1, 2, 3, 4] | » | {1=>2, 3=>4}

Top-Level Execution Environment

Many times in this book we've claimed that everything in Ruby is an object. However, there's one thing that we've used time and time again that appears to contradict this---the top-level Ruby execution environment.

puts "Hello, World"

Not an object in sight. We may as well be writing some variant of Fortran or QW-Basic. But dig deeper, and you'll come across objects and classes lurking in even the simplest code.

We know that the literal "Hello, World" generates a Ruby String, so there's one object. We also know that the bare method call to puts is effectively the same as self.puts. But what is ``self''?

self.type » Object

At the top level, we're executing code in the context of some predefined object. When we define methods, we're actually creating (private) singleton methods for this object. Instance variables belong to this object. And because we're in the context of Object, we can use all of Object's methods (including those mixed-in from Kernel) in function form. This explains why we can call Kernel methods such as puts at the top level (and indeed throughout Ruby): these methods are part of every object.

Inheritance and Visibility

There's one last wrinkle to class inheritance, and it's fairly obscure.

Within a class definition, you can change the visibility of a method in an ancestor class. For example, you can do something like:

class Base
  def aMethod
    puts "Got here"
  end
  private :aMethod
end

class Derived1 < Base
  public :aMethod
end

class Derived2 < Base
end

In this example, you would be able to invoke aMethod in instances of class Derived1, but not via instances of Base or Derived2.

So how does Ruby pull off this feat of having one method with two different visibilities? Simply put, it cheats.

If a subclass changes the visibility of a method in a parent, Ruby effectively inserts a hidden proxy method in the subclass that invokes the original method using super. It then sets the visibility of that proxy to whatever you requested. This means that the code:

class Derived1 < Base
  public :aMethod
end

is effectively the same as:

class Derived1 < Base
  def aMethod(*args)
    super
  end
  public :aMethod
end

The call to super can access the parent's method regardless of its visibility, so the rewrite allows the subclass to override its parent's visibility rules. Pretty scary, eh?

Freezing Objects

There are times when you've worked hard to make your object exactly right, and you'll be damned if you'll let anyone just change it. Perhaps you need to pass some kind of opaque object between two of your classes via some third-party object, and you want to make sure it arrives unmodified. Perhaps you want to use an object as a hash key, and need to make sure that no one modifies it while it's being used. Perhaps something is corrupting one of your objects, and you'd like Ruby to raise an exception as soon as the change occurs.

Ruby provides a very simple mechanism to help with this. Any object can be frozen by invoking Object#freeze . A frozen object may not be modified: you can't change its instance variables (directly or indirectly), you can't associate singleton methods with it, and, if it is a class or module, you can't add, delete, or modify its methods. Once frozen, an object stays frozen: there is no Object#thaw . You can test to see if an object is frozen using Object#frozen? .

What happens when you copy a frozen object? That depends on the method you use. If you call an object's clone method, the entire object state (including whether it is frozen) is copied to the new object. On the other hand, dup typically copies only the object's contents---the new copy will not inherit the frozen status.

str1 = "hello"
str1.freeze | » | "hello"
str1.frozen? | » | true
str2 = str1.clone
str2.frozen? | » | true
str3 = str1.dup
str3.frozen? | » | false

Although freezing objects may initially seem like a good idea, you might want to hold off doing it until you come across a real need. Freezing is one of those ideas that looks essential on paper but isn't used much in practice.

Extracted from the book "Programming Ruby - The Pragmatic Programmer's Guide"
Copyright © 2001 by Addison Wesley Longman, Inc. This material may be distributed only subject to the terms and conditions set forth in the Open Publication License, v1.0 or later (the latest version is presently available at http://www.opencontent.org/openpub/)).

Distribution of substantively modified versions of this document is prohibited without the explicit permission of the copyright holder.

Distribution of the work or derivative of the work in any standard (paper) book form is prohibited unless prior permission is obtained from the copyright holder.


Programming Ruby: The Pragmatic Programmer's Guide

Programming Ruby

The Pragmatic Programmer's Guide

Previous < Contents ^
Next >

Locking Ruby in the Safe

Walter Webcoder has a great idea for a portal site: The Web Arithmetic Page. Surrounded by all sorts of cool mathematical links and banner ads that will make him rich is a simple central frame, containing a text field and a button. Users type an arithmetic expression into the field, press the button, and the answer is displayed. All the world's calculators become obsolete overnight, and Walter cashes in and retires to devote his life to his collection of car license plate numbers.

Implementing the calculator is easy, thinks Walter. He accesses the contents of the form field using Ruby's CGI library, and uses the eval method to evaluate the string as an expression.

require 'cgi'

cgi = CGI::new("html4")

# Fetch the value of the form field "expression"
expr = cgi["expression"].to_s

begin
  result = eval(expr)
rescue Exception => detail
  # handle bad expressions
end

# display result back to user...

Roughly seven seconds after Walter puts the application online, a twelve-year-old from Waxahachie with glandular problems and no real life types ``system("rm *")'' into the form and, like his application, Walter's dreams come tumbling down.

Walter learned an important lesson: All external data is dangerous. Don't let it close to interfaces that can modify your system. In this case, the content of the form field was the external data, and the call to eval was the security breach.

Fortunately, Ruby provides support for reducing this risk. All information from the outside world can be marked as tainted. When running in a safe mode, potentially dangerous methods will raise a SecurityError if passed a tainted object.

Safe Levels

The variable $SAFE determines Ruby's level of paranoia. Table 20.1 on page 257 gives details of the checks performed at each safe level.

$SAFE | Constraints
0 | No checking of the use of externally supplied (tainted) data is performed. This is Ruby's default mode.
>= 1 | Ruby disallows the use of tainted data by potentially dangerous operations.
>= 2 | Ruby prohibits the loading of program files from globally writable locations.
>= 3 | All newly created objects are considered tainted.
>= 4 | Ruby effectively partitions the running program in two. Nontainted objects may not be modified. Typically, this will be used to create a sandbox: the program sets up an environment using a lower $SAFE level, then resets $SAFE to 4 to prevent subsequent changes to that environment.

The default value of $SAFE is zero under most circumstances. However, if a Ruby script is run setuid or setgid,[A Unix script may be flagged to be run under a different user or group id than the person running it. This allows the script to have privileges that the user does not have; the script can access resources that the user would otherwise be prohibited from using. These scripts are called setuid or setgid.] its safe level is automatically set to 1. The safe level may also be set using the -T command-line option, and by assigning to $SAFE within the program. It is not possible to lower the value of $SAFE by assignment.

The current value of $SAFE is inherited when new threads are created. However, within each thread, the value of $SAFE may be changed without affecting the value in other threads. This facility may be used to implement secure ``sandboxes,'' areas where external code may run safely without risking the rest of your application or system. Do this by wrapping code that you load from a file in its own, anonymous module. This will protect your program's namespace from any unintended alteration.

f=open(fileName,"w")
f.print ...   # write untrusted program into file.
f.close
Thread.start {
  $SAFE = 4
  load(fileName, true)
}

With a $SAFE level of 4, you can load only wrapped files. See Kernel::load on page 418 for details.

Tainted Objects

Any Ruby object derived from some external source (for example, a string read from a file, or an environment variable) is automatically marked as being tainted. If your program uses a tainted object to derive a new object, then that new object will also be tainted, as shown in the code below. Any object with external data somewhere in its past will be tainted. This tainting process is performed regardless of the current safe level. You can inspect the tainted status of an object using Object#tainted? .

# internal data
# =============
x1 = "a string"
x1.tainted? | » | false
x2 = x1[2, 4]
x2.tainted? | » | false
x1 =~ /([a-z])/ | » | 0
$1.tainted? | » | false

# external data
# =============
y1 = ENV["HOME"]
y1.tainted? | » | true
y2 = y1[2, 4]
y2.tainted? | » | true
y1 =~ /([a-z])/ | » | 1
$1.tainted? | » | true

You can force any object to become tainted by invoking its taint method. If the safe level is less than 3, you can remove the taint from an object by invoking untaint.[There are also some devious ways of doing this without using untaint. We'll leave it up to your darker side to find them.] This is clearly not something to do lightly.

Clearly, Walter should have run his CGI script at a safe level of 1. This would have raised an exception when the program tried to pass form data to eval. Once this had happened, Walter would have had a number of choices. He could have chosen to implement a proper expression parser, bypassing the risks inherent in using eval. However, being lazy, it's more likely he'd have performed some simple sanity check on the form data, and untaint it if it looked innocuous.

require 'cgi';

$SAFE = 1

cgi = CGI::new("html4")

expr = cgi["field"].to_s

if expr =~ %r{^-+*/\d\seE.()*$}
  expr.untaint
  result = eval(expr)
  # display result back to user...
else
  # display error message...
end

Personally, we think Walter's still taking undue risks. We'd probably prefer to see a real parser here, but implementing one here has nothing to teach us about tainting, so we'll move on.

And remember---it's a dangerous world out there. Be careful.

Definition of the safe levels
$SAFE >= 1
  • The environment variables RUBYLIB and RUBYOPT are not processed, and the current directory is not added to the path.
  • The command-line options -e, -i, -I, -r, -s, -S, and -x are not allowed.
  • Can't start processes from $PATH if any directory in it is world-writable.
  • Can't manipulate or chroot to a directory whose name is a tainted string.
  • Can't glob tainted strings.
  • Can't eval tainted strings.
  • Can't load or require a file whose name is a tainted string.
  • Can't manipulate or query the status of a file or pipe whose name is a tainted string.
  • Can't execute a system command or exec a program from a tainted string.
  • Can't pass trap a tainted string.

$SAFE >= 2
  • Can't change, make, or remove directories, or use chroot.
  • Can't load a file from a world-writable directory.
  • Can't load a file from a tainted filename starting with ~.
  • Can't use File#chmod , File#chown , File#lstat , File.stat , File#truncate , File.umask , File#flock , IO#ioctl , IO#stat , Kernel#fork , Kernel#syscall , Kernel#trap . Process::setpgid , Process::setsid , Process::setpriority , or Process::egid= .
  • Can't handle signals using trap.

$SAFE >= 3
  • All objects are created tainted.
  • Can't untaint objects.

$SAFE >= 4
  • Can't modify a nontainted array, hash, or string.
  • Can't modify a global variable.
  • Can't access instance variables of nontainted objects.
  • Can't change an environment variable.
  • Can't close or reopen nontainted files.
  • Can't freeze nontainted objects.
  • Can't change visibility of methods (private/public/protected).
  • Can't make an alias in a nontainted class or module.
  • Can't get meta information (such as method or variable lists).
  • Can't define, redefine, remove, or undef a method in a nontainted class or module.
  • Can't modify Object.
  • Can't remove instance variables or constants from nontainted objects.
  • Can't manipulate threads, terminate a thread other than the current, or set abort_on_exception.
  • Can't have thread local variables.
  • Can't raise an exception in a thread with a lower $SAFE value.
  • Can't move threads between ThreadGroups.
  • Can't invoke exit, exit!, or abort.
  • Can load only wrapped files, and can't include modules in nontainted classes and modules.
  • Can't convert symbol identifiers to object references.
  • Can't write to files or pipes.
  • Can't use autoload.
  • Can't taint objects.

Extracted from the book "Programming Ruby - The Pragmatic Programmer's Guide"
Copyright © 2001 by Addison Wesley Longman, Inc. This material may be distributed only subject to the terms and conditions set forth in the Open Publication License, v1.0 or later (the latest version is presently available at http://www.opencontent.org/openpub/)).

Distribution of substantively modified versions of this document is prohibited without the explicit permission of the copyright holder.

Distribution of the work or derivative of the work in any standard (paper) book form is prohibited unless prior permission is obtained from the copyright holder.


Programming Ruby: The Pragmatic Programmer's Guide

Programming Ruby

The Pragmatic Programmer's Guide

上一章< 目录 ^
下一章 >

反射（Reflection）, 对象空间（ObjectSpace）,和分布式Ruby（Distributed Ruby）

One of the many advantages of dynamic languages such as Ruby is the ability to introspect---to examine aspects of the program from within the program itself. Java, for one, calls this feature reflection.

The word ``reflection'' conjures up an image of looking at oneself in the mirror---perhaps investigating the relentless spread of that bald spot on the top of one's head. That's a pretty apt analogy: we use reflection to examine parts of our programs that aren't normally visible from where we stand.

In this deeply introspective mood, while we are contemplating our navels and burning incense (being careful not to swap the two tasks), what can we learn about our program? We might discover:

  • what objects it contains,
  • the current class hierarchy,
  • the contents and behaviors of objects, and
  • information on methods.

Armed with this information, we can look at particular objects and decide which of their methods to call at runtime---even if the class of the object didn't exist when we first wrote the code. We can also start doing clever things, perhaps modifying the program as it's running.

Sound scary? It needn't be. In fact, these reflection capabilities let us do some very useful things. Later in this chapter we'll look at distributed Ruby and marshaling, two reflection-based technologies that let us send objects around the world and through time.

Looking at Objects

Have you ever craved the ability to traverse all the living objects in your program? We have! Ruby lets you perform this trick with ObjectSpace::each_object . We can use it to do all sorts of neat tricks.

For example, to iterate over all objects of type Numeric, you'd write the following.

a = 102.7
b = 95.1
ObjectSpace.each_object(Numeric) {|x| p x }
produces:
95.1
102.7
2.718281828
3.141592654

Hey, where did those last two numbers come from? We didn't define them in our program. If you look on page 429, you'll see that the Math module defines constants for e and PI; since we are examining all living objects in the system, these turn up as well.

However, there is a catch. Let's try the same example with different numbers.

a = 102
b = 95
ObjectSpace.each_object(Numeric) {|x| p x }
produces:
2.718281828
3.141592654

Neither of the Fixnum objects we created showed up. That's because ObjectSpace doesn't know about objects with immediate values: Fixnum, true, false, and nil.

Looking Inside Objects

Once you've found an interesting object, you may be tempted to find out just what it can do. Unlike static languages, where a variable's type determines its class, and hence the methods it supports, Ruby supports liberated objects. You really cannot tell exactly what an object can do until you look under its hood.[Or under its bonnet, for objects created to the east of the Atlantic.]

For instance, we can get a list of all the methods to which an object will respond.

r = 1..10 # Create a Range object
list = r.methods
list.length | ? | 60
list[0..3] | ? | ["size", "end", "length", "exclude_end?"]

Or, we can check to see if an object supports a particular method.

r.respond_to?("frozen?") | ? | true
r.respond_to?("hasKey") | ? | false
"me".respond_to?("==") | ? | true

We can determine our object's class and its unique object id, and test its relationship to other classes.

num = 1
num.id | ? | 3
num.class | ? | Fixnum
num.kind_of? Fixnum | ? | true
num.kind_of? Numeric | ? | true
num.instance_of? Fixnum | ? | true
num.instance_of? Numeric | ? | false

Looking at Classes

Knowing about objects is one part of reflection, but to get the whole picture, you also need to be able to look at classes---the methods and constants that they contain.

Looking at the class hierarchy is easy. You can get the parent of any particular class using Class#superclass . For classes and modules, Module#ancestors lists both superclasses and mixed-in modules.

klass = Fixnum
begin
  print klass
  klass = klass.superclass
  print " < " if klass
end while klass
puts
p Fixnum.ancestors
produces:
Fixnum < Integer < Numeric < Object
[Fixnum, Integer, Precision, Numeric, Comparable, Object, Kernel]

If you want to build a complete class hierarchy, just run that code for every class in the system. We can use ObjectSpace to iterate over all Class objects:

ObjectSpace.each_object(Class) do |aClass|
   # ...
end

Looking Inside Classes

We can find out a bit more about the methods and constants in a particular object. Instead of just checking to see whether the object responds to a given message, we can ask for methods by access level, we can ask for just singleton methods, and we can have a look at the object's constants.

class Demo
private
def privMethod
end
protected
def protMethod
end
public
def pubMethod
end
def Demo.classMethod
end
CONST = 1.23
end
Demo.private_instance_methods | ? | ["privMethod"]
Demo.protected_instance_methods | ? | ["protMethod"]
Demo.public_instance_methods | ? | ["pubMethod"]
Demo.singleton_methods | ? | ["classMethod"]
Demo.constants - Demo.superclass.constants | ? | ["CONST"]

Module.constants returns all the constants available via a module, including constants from the module's superclasses. We're not interested in those just at the moment, so we'll subtract them from our list.

Given a list of method names, we might now be tempted to try calling them. Fortunately, that's easy with Ruby.

Calling Methods Dynamically

C and Java programmers often find themselves writing some kind of dispatch table: functions which are invoked based on a command. Think of a typical C idiom where you have to translate a string to a function pointer:

typedef struct {
  char *name;
  void (*fptr)();
} Tuple;

Tuple list[]= {
  { "play",   fptr_play },
  { "stop",   fptr_stop },
  { "record", fptr_record },
  { 0, 0 },
};

...

void dispatch(char *cmd) {
  int i = 0;
  for (; list[i].name; i++) {
    if (strncmp(list[i].name,cmd,strlen(cmd)) == 0) {
      list[i].fptr();
      return;
    }
  }
  /* not found */
}

In Ruby, you can do all this in one line. Stick all your command functions into a class, create an instance of that class (we called it commands), and ask that object to execute a method called the same name as the command string.

commands.send(commandString)

Oh, and by the way, it does much more than the C version---it's dynamic. The Ruby version will find new methods added at runtime just as easily.

You don't have to write special command classes for send: it works on any object.

"John Coltrane".send(:length) | ? | 13
"Miles Davis".send("sub", /iles/, '.') | ? | "M. Davis"

Another way of invoking methods dynamically uses Method objects. A Method object is like a Proc object: it represents a chunk of code and a context in which it executes. In this case, the code is the body of the method, and the context is the object that created the method. Once we have our Method object, we can execute it sometime later by sending it the message call.

trane = "John Coltrane".method(:length)
miles = "Miles Davis".method("sub")
trane.call | ? | 13
miles.call(/iles/, '.') | ? | "M. Davis"

You can pass the Method object around as you would any other object, and when you invoke Method#call , the method is run just as if you had invoked it on the original object. It's like having a C-style function pointer but in a fully object-oriented style.

You can also use Method objects with iterators.

def double(a)
  2*a
end
mObj = method(:double)
[ 1, 3, 5, 7 ].collect(&mObj) ? [2, 6, 10, 14]

As good things come in threes, here's yet another way to invoke methods dynamically. The eval method (and its variations such as class_eval, module_eval, and instance_eval) will parse and execute an arbitrary string of legal Ruby source code.

trane = %q{"John Coltrane".length}
miles = %q{"Miles Davis".sub(/iles/, '.')}
eval trane | ? | 13
eval miles | ? | "M. Davis"

When using eval, it can be helpful to state explicitly the context in which the expression should be evaluated, rather than using the current context. You can obtain a context by calling Kernel#binding at the desired point.

class CoinSlot
  def initialize(amt=Cents.new(25))
    @amt = amt
    $here = binding
  end
end

a = CoinSlot.new eval "puts @amt", $here eval "puts @amt"
produces:
$0.25USD
nil

The first eval evaluates @amt in the context of the instance of class CoinSlot. The second eval evaluates @amt in the context of Object, where the instance variable @amt is not defined.

Performance Considerations

As we've seen in this section, there are several ways to invoke an arbitrary method of some object: Object#send , Method#call , and the various flavors of eval.

You may prefer to use any one of these techniques depending on your needs, but be aware that eval is significantly slower than the others (or, for optimistic readers, send and call are significantly faster than eval).

require "benchmark"   # from the Ruby Application Archive
include Benchmark

test = "Stormy Weather" m = test.method(:length) n = 100000

bm(12) {|x| x.report("call") { n.times { m.call } } x.report("send") { n.times { test.send(:length) } } x.report("eval") { n.times { eval "test.length" } } }
produces:
                  user     system      total        real
call          0.220000   0.000000   0.220000 (  0.214065)
send          0.210000   0.000000   0.210000 (  0.217070)
eval          2.540000   0.000000   2.540000 (  2.518311)

System Hooks

A hook is a technique that lets you trap some Ruby event, such as object creation.

The simplest hook technique in Ruby is to intercept calls to methods in system classes. Perhaps you want to log all the operating system commands your program executes. Simply rename the method Kernel::system [This Eiffel-inspired idiom of renaming a feature and redefining a new one is very useful, but be aware that it can cause problems. If a subclass does the same thing, and renames the methods using the same names, you'll end up with an infinite loop. You can avoid this by aliasing your methods to a unique symbol name or by using a consistent naming convention.] and substitute it with one of your own that both logs the command and calls the original Kernel method.

module Kernel
  alias_method :old_system, :system
  def system(*args)
    result = old_system(*args)
    puts "system(#{args.join(', ')}) returned #{result}"
    result
  end
end

system("date") system("kangaroo", "-hop 10", "skippy")
produces:
Sun Jun  9 00:09:44 CDT 2002
system(date) returned true
system(kangaroo, -hop 10, skippy) returned false

A more powerful hook is catching objects as they are created. If you can be present when every object is born, you can do all sorts of interesting things: you can wrap them, add methods to them, remove methods from them, add them to containers to implement persistence, you name it. We'll show a simple example here: we'll add a timestamp to every object as it's created.

One way to hook object creation is to do our method renaming trick on Class#new , the method that's called to allocate space for a new object. The technique isn't perfect---some built-in objects, such as literal strings, are constructed without calling new---but it'll work just fine for objects we write.

class Class
  alias_method :old_new,  :new
  def new(*args)
    result = old_new(*args)
    result.timestamp = Time.now
    return result
  end
end

We'll also need to add a timestamp attribute to every object in the system. We can do this by hacking class Object itself.

class Object
  def timestamp
    return @timestamp
  end
  def timestamp=(aTime)
    @timestamp = aTime
  end
end

Finally, we can run a test. We'll create a couple of objects a few seconds apart and check their timestamps.

class Test
end
obj1 = Test.new
sleep 2
obj2 = Test.new
obj1.timestamp | ? | Sun Jun 09 00:09:45 CDT 2002
obj2.timestamp | ? | Sun Jun 09 00:09:47 CDT 2002

All this method renaming is fine, and it really does work. However, there are other, more refined ways to get inside a running program. Ruby provides several callback methods that let you trap certain events in a controlled way.

Runtime Callbacks

You can be notified whenever one of the following events occurs:

Event | Callback Method
Adding an instance method | Module#method_added
Adding a singleton method | Kernel::singleton_method_added
Subclassing a class | Class#inherited
Mixing in a module | Module#extend_object

These techniques are all illustrated in the library descriptions for each callback method. At runtime, these methods will be called by the system when the specified event occurs. By default, these methods do nothing. If you want to be notified when one of these events happens, just define the callback method, and you're in.

Keeping track of method creation and class and module usage lets you build an accurate picture of the dynamic state of your program. This can be important. For example, you may have written code that wraps all the methods in a class, perhaps to add transactional support or to implement some form of delegation. This is only half the job: the dynamic nature of Ruby means that users of this class could add new methods to it at any time. Using these callbacks, you can write code that wraps these new methods as they are created.

Tracing Your Program's Execution

While we're having fun reflecting on all the objects and classes in our programs, let's not forget about the humble statements that make our code actually do things. It turns out that Ruby lets us look at these statements, too.

First, you can watch the interpreter as it executes code. set_trace_func executes a Proc with all sorts of juicy debugging information whenever a new source line is executed, methods are called, objects are created, and so on. There's a full description on page 422, but here's a taste.

class Test
  def test
    a = 1
    b = 2
  end
end

set_trace_func proc { |event, file, line, id, binding, classname| printf "%8s %s:%-2d %10s %8s\n", event, file, line, id, classname } t = Test.new t.test
produces:
    line prog.rb:11               false
  c-call prog.rb:11        new    Class
  c-call prog.rb:11 initialize   Object
c-return prog.rb:11 initialize   Object
c-return prog.rb:11        new    Class
    line prog.rb:12               false
    call prog.rb:2        test     Test
    line prog.rb:3        test     Test
    line prog.rb:4        test     Test
  return prog.rb:4        test     Test

There's also a method trace_var (described on page 427) that lets you add a hook to a global variable; whenever an assignment is made to the global, your Proc object is invoked.

How Did We Get Here?

A fair question, and one we ask ourselves regularly. Mental lapses aside, in Ruby at least you can find out exactly ``how you got there'' by using the method caller, which returns an Array of String objects representing the current call stack.

def catA
  puts caller.join("\n")
end
def catB
  catA
end
def catC
  catB
end
catC
produces:
prog.rb:5:in `catB'
prog.rb:8:in `catC'
prog.rb:10

Once you've figured out how you got there, where you go next is up to you.

Marshaling and Distributed Ruby

Java features the ability to serialize objects, letting you store them somewhere and reconstitute them when needed. You might use this facility, for instance, to save a tree of objects that represent some portion of application state---a document, a CAD drawing, a piece of music, and so on.

Ruby calls this kind of serialization marshaling.[Think of railroad marshaling yards where individual cars are assembled in sequence into a complete train, which is then dispatched somewhere.] Saving an object and some or all of its components is done using the method Marshal::dump . Typically, you will dump an entire object tree starting with some given object. Later on, you can reconstitute the object using Marshal::load .

Here's a short example. We have a class Chord that holds a collection of musical notes. We'd like to save away a particularly wonderful chord so our grandchildren can load it into Ruby Version 23.5 and savor it, too. Let's start off with the classes for Note and Chord.

class Note
  attr :value
  def initialize(val)
    @value = val
  end
  def to_s
    @value.to_s
  end
end

class Chord def initialize(arr) @arr = arr end def play @arr.join('-') end end

Now we'll create our masterpiece, and use Marshal::dump to save a serialized version of it to disk.

c = Chord.new( [ Note.new("G"),  Note.new("Bb"),
                 Note.new("Db"), Note.new("E") ] )

File.open("posterity", "w+") do |f| Marshal.dump(c, f) end

Finally, our grandchildren read it in, and are transported by our creation's beauty.

File.open("posterity") do |f|
  chord = Marshal.load(f)
end
chord.play ? "G-Bb-Db-E"

Custom Serialization Strategy

Not all objects can be dumped: bindings, procedure objects, instances of class IO, and singleton objects cannot be saved outside of the running Ruby environment (a TypeError will be raised if you try). Even if your object doesn't contain one of these problematic objects, you may want to take control of object serialization yourself.

Marshal provides the hooks you need. In the objects that require custom serialization, simply implement two methods: an instance method called _dump, which writes the object out to a string, and a class method called _load, which reads a string that you'd previously created and converts it into a new object.

For instance, here is a sample class that defines its own serialization. For whatever reasons, Special doesn't want to save one of its internal data members, ``@volatile''.

class Special
  def initialize(valuable)
    @valuable = valuable
    @volatile = "Goodbye"
  end

def _dump(depth) @valuable.to_str end

def Special._load(str) result = Special.new(str); end

def to_s "#{@valuable} and #{@volatile}" end end

a = Special.new("Hello, World") data = Marshal.dump(a) obj = Marshal.load(data) puts obj
produces:
Hello, World and Goodbye

For more details, see the reference section on Marshal beginning on page 428.

Distributed Ruby

Since we can serialize an object or a set of objects into a form suitable for out-of-process storage, we can use this capability for the transmission of objects from one process to another. Couple this capability with the power of networking, and voilà: you have a distributed object system. To save you the trouble of having to write the code, we suggest downloading Masatoshi Seki's Distributed Ruby library (drb) from the RAA.

Using drb, a Ruby process may act as a server, as a client, or as both. A drb server acts as a source of objects, while a client is a user of those objects. To the client, it appears that the objects are local, but in reality the code is still being executed remotely.

A server starts a service by associating an object with a given port. Threads are created internally to handle incoming requests on that port, so remember to join the drb thread before exiting your program.

require 'drb'

class TestServer def doit "Hello, Distributed World" end end

aServerObject = TestServer.new DRb.start_service('druby://localhost:9000', aServerObject) DRb.thread.join # Don't exit just yet!

A simple drb client simply creates a local drb object and associates it with the object on the remote server; the local object is a proxy.

require 'drb'
DRb.start_service()
obj = DRbObject.new(nil, 'druby://localhost:9000')
# Now use obj
p obj.doit

The client connects to the server and calls the method doit, which returns a string that the client prints out:

"Hello, Distributed World"

The initial nil argument to DRbObject indicates that we want to attach to a new distributed object. We could also use an existing object.

Ho hum, you say. This sounds like Java's RMI, or CORBA, or whatever. Yes, it is a functional distributed object mechanism---but it is written in just 200 lines of Ruby code. No C, nothing fancy, just plain old Ruby code. Of course, there's no naming service or trader service, or anything like you'd see in CORBA, but it is simple and reasonably fast. On the 233MHz test system, this sample code runs at about 50 remote message calls per second.

And, if you like the look of Sun's JavaSpaces, the basis of their JINI architecture, you'll be interested to know that drb is distributed with a short module that does the same kind of thing. JavaSpaces is based on a technology called Linda. To prove that its Japanese author has a sense of humor, Ruby's version of Linda is known as ``rinda.''

Compile Time? Runtime? Anytime!

The important thing to remember about Ruby is that there isn't a big difference between ``compile time'' and ``runtime.'' It's all the same. You can add code to a running process. You can redefine methods on the fly, change their scope from public to private, and so on. You can even alter basic types, such as Class and Object.

Once you get used to this flexibility, it is hard to go back to a static language such as C++, or even to a half-static language such as Java.

But then, why would you want to?

Previous < Contents ^
Next >

Extracted from the book "Programming Ruby - The Pragmatic Programmer's Guide"
Copyright © 2001 by Addison Wesley Longman, Inc. This material may be distributed only subject to the terms and conditions set forth in the Open Publication License, v1.0 or later (the latest version is presently available at http://www.opencontent.org/openpub/)).

Distribution of substantively modified versions of this document is prohibited without the explicit permission of the copyright holder.

Distribution of the work or derivative of the work in any standard (paper) book form is prohibited unless prior permission is obtained from the copyright holder.


找不到服务器

无法显示网页

您正在查找的页当前不可用。 网站可能遇到支持问题，或者您需要 调整您的浏览器设置。

请尝试以下操作:

  • 单击 refresh.gif (82 字节) 刷新按钮，或稍后重试。
  • 如果您已经在地址栏中输入该网页的地址， 请确认其拼写正确。
  • 要检查您的网络连接，请单击工具菜单，然后单击 Internet 选项。在连接选项卡上，单击设置。 设置必须与您的局域网 (LAN) 管理员或 Internet 服务供应商 (ISP) 提供的一致。
  • 查看您的 Internet 连接设置是否正确被检测。您可能设置让 Microsoft Windows 检查您的网站并自动发现网络连接设置(如果您的网络管理员已经启用此设置)。
    1. 单击工具菜单，然后单击Internet 选项。
    2. 在连接选项卡上，单击LAN 设置。
    3. 选择自动检测设置，然后单击确定。
  • 某些站点要求 128-位的连接安全性。单击帮助菜单，然后单击关于 Internet Explorer 可以查看您所安装的安全强度。
  • 如果您要访问某安全站点，请确保您的安全设置能够支持。请单击工具菜单，然后单击 Internet 选项。在“高级”选项卡上，滚动到“安全”部分，复选 SSL 2.0、SSL 3.0、TLS 1.0、PCT 1.0 设置。
  • 单击 [image] 上一步按钮，尝试其他链接。

找不到服务器或 DNS 错误
Internet Explorer


Programming Ruby: The Pragmatic Programmer's Guide

Programming Ruby

The Pragmatic Programmer's Guide

Previous < Contents ^
Next >

Standard Library

Ruby comes ``out of the box'' with a large and useful library of modules and classes. This chapter contains a sampling of the more useful of these.

Interestingly, and unlike some of the code in later chapters, all of these libraries are written in Ruby. You'll find the source in the lib subdirectory of the standard Ruby distribution.

class Complex
Parent: | Numeric
Version: | 1.6

Index:

new Arithmetic operations <=> == abs abs2 arg conjugate image polar real to_f to_i to_r to_s

require "complex"
v1 = Complex(2,3) | » | Complex(2, 3)
v2 = 2.im | » | Complex(0, 2)
v1 + v2 | » | Complex(2, 5)
v1 * v2 | » | Complex(-6, 4)
v2**2 | » | Complex(-4, 0)
Math.sin(v1) | » | Complex(9.154499147, -4.16890696)
v1 < v2 | » | false
v2**2 == -4 | » | true

constants
Complex::I 0 +1i

class methods
new | Complex.new( a, b ) -> aComplex
 | Returns a + bi.

In addition to the Complex.new constructor, the Complex library defines the method Numeric.im , such that aNumeric .im returns 0 + aNumeric i. Complex numbers are also constructed using the global method Complex, which takes one or two arguments. The value it returns depends on the type of its arguments:

a | b | Result
Number | Number | a + bi
Complex | 0 | a
Complex | Complex | Complex( a.real - b.image, a.image + b.real )
Number | Complex | Complex( a - b.image, b.real )

instance methods
Arithmetic operations

Performs various arithmetic operations on ref.

ref | + | aNumeric -> aComplex | Addition
ref | - | aNumeric -> aComplex | Subtraction
ref | * | aNumeric -> aComplex | Multiplication
ref | / | aNumeric -> aComplex | Division
ref | % | aNumeric -> aComplex | Remainder
ref | ** | aNumeric -> aComplex | Exponentiation (real and complex power)
<=> ref <=> other -> -1, 0, +1

Returns ref.abs <=> other.abs.

== ref == anObject -> true or false

If anObject is a complex number, returns true if its real and imaginary parts match ref. If anObject is a simple number, returns true if ref.real equals anObject and ref.image is zero. Otherwise, attempts to coerce anObject to a complex number and compares the result.

abs | ref.abs -> aFloat

Absolute value.

abs2 | ref.abs2 -> aFloat

Square of absolute value.

arg | ref.arg -> aFloat

Argument (angle from (1,0)).

conjugate | ref.conjugate -> aComplex

Complex conjugate.

image | ref.image -> aNumeric

The imaginary part of ref.

polar | ref.polar -> anArray

Returns the two-element array: [c.abs, c.arg].

real | ref.real -> aNumeric

The real part of ref.

to_f | ref.to_f -> aComplex

Returns Complex(real.to_f, image.to_f).

to_i | ref.to_i -> aComplex

Returns Complex(real.to_i, image.to_i).

to_r | ref.to_r -> aComplex

Returns Complex(real.to_r, image.to_r), converting both parts of the complex to a rational number.

to_s | ref.to_s -> aString

String representation of ref.

In addition, the Math functions sqrt, exp, cos, sin, tan, log, log10, and atan2 are extended to support a Complex argument.

class Date
Parent: | Object
Version: | 1.6

Index:

exist2? exist? existw? gregorian_leap? julian_leap? leap? new new1 new2 new3 neww today Accessors + -- << <=> === >> downto england gregorian italy jd julian leap? mjd newsg next ns? os? sg step succ to_s upto

require 'date'
d = Date.new(2000, 3, 31) | » | #<Date: 2451635,2299161>
[d.year, d.yday, d.wday] | » | [2000, 91, 5]
[d.month, d.mday] | » | [3, 31]
[d.cwyear, d.cweek, d.cwday] | » | [2000, 13, 5]
[d.jd, d.mjd] | » | [2451635, 51634.5]
(d << 1).to_s | » | "2000-02-29"
d.succ.to_s | » | "2000-04-01"
(d + 100).to_s | » | "2000-07-09"
d.leap? | » | true
Date.new(2000, 3, -10).to_s | » | "2000-03-22"
d1 = Date.neww(2000, 13, 7) | » | #<Date: 2451637,2299161>
d1.to_s | » | "2000-04-02"
[d1.cwday, d1.wday] | » | [7, 0]

The date library implements class Date, which provides a comprehensive set of facilities for storing, manipulating, and converting dates. To document its options, we need to take a brief historical detour to establish some vocabulary.

Internally a date is stored as a Julian day number, the number of days since midday, January 1st, 4713 BCE.[In the code, you may find references to the year -4712. As astronomical dates include a year zero, 4713 BCE is the same year as -4712.] The rules for converting a Julian day number to a calendar date are complicated because the Romans estimated the length of a year incorrectly. In the Julian calendar (often called Old Style, or O.S.), every year divisible by 4 is a leap year. The Date class has options to convert dates using this as an assumption.

By the sixteenth century, the inaccuracies in this measurement had become apparent. An edict from Pope Gregory XIII in 1582 created the New Style (N.S.) or Gregorian calendar, where years divisible by 100 were no longer leap years unless they were also divisible by 400. This system was adopted by most Catholic countries immediately, but religious differences held up a wider adoption. England (and several other countries) switched in 1752, with some countries following later. The Date class allows you to determine whether to implement the cutover in 1582 (the Date::ITALY option), 1752 (Date::ENGLAND), or another date of your choosing.

The Date class also provides conversions to Modified Julian Day (MJD) numbers. MJD values count from midnight, November 17, 1858. Because these values count from midnight, not midday, there is a half-day added to the conversion factor.

The descriptions that follow use the abbreviations listed in Table 24.1 on page 441.

Abbreviations used describing dates

Field | Meaning
cwday | An ISO 8601 calendar weekday. 1 is Monday, 7 is Sunday.
cweek | An ISO 8601 calendar week. Week 1 is the week containing the first Thursday (or equivalently the week that contains January 4th).
cwyear | An ISO 8601 calendar-week-based year. May be different from year, as it rolls forward only on a Monday.
jd | The Julian day number---the number of days since January 1st, 4713 BCE.
mday | The day of the month (1..31).
mjd | A modified Julian day number.
mon | The month of the year (1..12).
sg | The start of the Gregorian correction: Date::ITALY (the default) for 1582, Date::ENGLAND for 1752, or JULIAN, meaning no correction. You may also provide an arbitrary Julian day number for this parameter, in which case the correction will start from this date.
wday | The day of the week (0 is Sunday).
week | The week number into a year (1..53).
yday | The day into the year (1..366).
year | A year (1966, 2001, and the like).

Class Date exports the constant arrays Date::MONTHNAMES and Date::DAYNAMES, which can be indexed by mon and wday values to return the corresponding English names.

The Date class also provides low-level date-conversion methods:

* civil_to_jd | * jd_to_civil
* commercial_to_jd | * jd_to_commercial
* ordinal_to_jd | * jd_to_ordinal
* jd_to_mjd | * mjd_to_jd

These methods perform limited error checking of their parameters, and are not documented here. The somewhat confusingly named exist..? routines perform conversions from different formats into a Julian day number with error checking. These routines also automatically normalize their parameters.

mixins
Comparable: <, <=, ==, >=, >, between?

class methods
exist2? | Date.exist2?( year, yday, sg=Date::ITALY) -> jd
 | Converts a year and yday into a Julian day number, returning nil on error.
exist? | Date.exist?( year, mon, mday, sg=Date::ITALY) -> jd
 | Converts a year, mon, and mday into a Julian day number, or nil if the parameters are invalid.
existw? | Date.existw?( cyear, cweek, cwday, sg=Date::ITALY) -> jd
 | Converts a cyear, cweek, and cwday into a Julian day number.
gregorian_leap? | Date.gregorian_leap?( year ) -> true or false
 | If year does not end with ``00'', returns true if year is divisible by 4, otherwise returns true if year is divisible by 400.
julian_leap? | Date.julian_leap?( year ) -> true or false
 | Returns true if year is divisible by 4.
leap? | Date.leap?( year ) -> true or false
 | Synonym for Date.gregorian_leap?.
new | Date.new( year=-4712, mon=1, mday=1, sg=Date::ITALY) -> aNewDate
 | Returns a Date for the given year, mon, and mday. If mon is negative, it counts back from the end of the year. If mday is negative, it counts back from the end of the month.
new1 | Date.new1( jd, sg=Date::ITALY) -> aNewDate
 | Creates a Date corresponding to the given Julian day number.
new2 | Date.new2( year=-4712, yday=1, sg=Date::ITALY) -> aNewDate
 | Returns a Date for the given year and yday. If yday is negative, it counts back from the end of the year.
new3 | Date.new3( year=-4712, mon=1, mday=1, sg=Date::ITALY) -> aNewDate
 | Synonym for Date.new.
neww | Date.neww( cyear=1582, cweek=41, cwday=5, sg=Date::ITALY) -> aNewDate
 | Returns a Date for the given cyear, cweek, and cwday. If cweek is negative, it counts back from the end of the year. If cwday is negative, it counts back from the end of the week.
today | Date.today( sg=Date::ITALY) -> aNewDate
 | Returns a Date for today.

instance methods
Accessors | ref.year -> year ref.yday -> yday ref.mjd -> mjd ref.mon -> mon ref.month -> mon ref.mday -> mday ref.day -> mday ref.cwyear -> cwyear ref.cweek -> cweek ref.cwday -> cwday ref.wday -> wday
 | Returns the given component of ref as a number.
+ | ref + anInteger -> aNewDate
 | Returns a new Date anInteger days from ref.
-- | ref - anInteger -> aNewDate ref - anOtherDate -> anInteger
 | The first form returns a new Date anInteger days before ref. The second form returns the number of days between ref and anOtherDate.
<< | ref << anInteger -> aNewDate
 | Returns a new Date formed by subtracting anInteger months to ref, adjusting the mday value back to the last day of the month if it otherwise exceeds it.
<=> | ref <=> anOther -> -1, 0, +1
 | anOther must be a Numeric, in which case it is treated as a Julian day number, or a Date. Returns -1, 0, +1 if ref is less than, equal to, or greater than anOther. See module Comparable on page 402.
=== | ref === anOther -> true or false
 | anOther must be a Numeric, in which case it is treated as a Julian day number, or a Date. Returns true if the Julian day number of anOther is the same as ref.
>> | ref >> anInteger -> aNewDate
 | Returns a new Date formed by adding anInteger months to ref, adjusting the mday value back to the last day of the month if it otherwise exceeds it.
downto | ref.downto( aDateMin ) {| date | block } -> ref
 | Invokes block with dates from ref down to aDateMin.
england | ref.england -> aDate
 | Equivalent to ref .newsg(Date::ENGLAND).
gregorian | ref.gregorian -> aDate
 | Equivalent to ref .newsg(Date::GREGORIAN).
italy | ref.italy -> aDate
 | Equivalent to ref .newsg(Date::ITALY).
jd | ref.jd -> jd
 | Returns the Julian day number for ref.
julian | ref.julian -> aDate
 | Equivalent to ref .newsg(Date::JULIAN).
leap? | ref.leap? -> true or false
 | Returns true if ref falls within a leap year.
mjd | ref.mjd -> mjd
 | Returns the Julian day number of ref converted to a modified Julian day number.
newsg | ref.newsg( sg=Date::ITALY ) -> aNewDate
 | Returns a new Date.
next | ref.next -> aNewDate
 | Synonym for ref.succ.
ns? | ref.ns? -> true or false
 | Returns true if ref falls in the period of New Style dates.
os? | ref.os? -> true or false
 | Returns true if ref falls in the period of Old Style dates.
sg | ref.sg -> anInteger
 | Returns the Julian day number of the start of New Style dates for ref.
step | ref.step( aDateLimit, step ) {| date | block } -> ref
 | Invokes block with dates starting at ref, incrementing by step days, ending at the first date greater than aDateLimit (less than for a negative step).
succ | ref.succ -> aNewDate
 | Returns the date of ref plus one day.
to_s | ref.to_s -> aString
 | Returns self as ``year-mon-mday.''
upto | ref.upto( aDateMax ) {| date | block } -> ref
 | Invokes block with dates from ref to aDateMax.

Library: English

require "English"

$OUTPUT_FIELD_SEPARATOR = ' -- '
"waterbuffalo" =~ /buff/
print $LOADED_FEATURES, $POSTMATCH, $PID, "\n"
print $", $', $$, "\n"
produces:
English.rb -- alo -- 32130 --
English.rb -- alo -- 32130 --

Include the English library file in a Ruby script, and you can reference the global variables such as $_ using less cryptic names, listed in the following table.

$* | $ARGV | $" | $LOADED_FEATURES
$? | $CHILD_STATUS | $& | $MATCH
$< | $DEFAULT_INPUT | $. | $NR
$> | $DEFAULT_OUTPUT | $, | $OFS
$! | $ERROR_INFO | $\ | $ORS
$@ | $ERROR_POSITION | $\ | $OUTPUT_RECORD_SEPARATOR
$; | $FIELD_SEPARATOR | $, | $OUTPUT_FIELD_SEPARATOR
$; | $FS | $$ | $PID
$= | $IGNORECASE | $' | $POSTMATCH
$. | $INPUT_LINE_NUMBER | $` | $PREMATCH
$/ | $INPUT_RECORD_SEPARATOR | $$ | $PROCESS_ID
$~ | $LAST_MATCH_INFO | $0 | $PROGRAM_NAME
$+ | $LAST_PAREN_MATCH | $/ | $RS
$_ | $LAST_READ_LINE

module Find

Index:

find prune

require "find"
Find.find("/etc/passwd", "/var/spool/lp1", ".") do |f|
  Find.prune if f == "."
  puts f
end
produces:
/etc/passwd
/var/spool/lp1
/var/spool/lp1/status
/var/spool/lp1/lock
/var/spool/lp1/.seq

The Find module supports the top-down traversal of a set of file paths.

class methods
find | ref.find( [ aName ]* ) {| aFileName | block }
 | Calls the associated block with the name of every file and directory listed as arguments, then recursively on their subdirectories, and so on.
prune | ref.prune
 | Skips the current file or directory, restarting the loop with the next entry. If the current file is a directory, that directory will not be recursively entered. Meaningful only within the block associated with Find::find.

class File
Parent: | IO
Version: | 1.6

Index:

cmp compare copy cp install makedirs mkpath move mv rm_f safe_unlink syscopy

require 'ftools'
File.copy 'testfile', 'testfile1' | » | true
File.compare 'testfile', 'testfile1' | » | true

The FTools library adds several methods to the built-in File class. These methods are particularly useful to programs that move and copy files, such as installers.

class methods
cmp | ref.cmp( name1, name2, verbose=false ) -> true or false
 | Synonym for File.compare .
compare | ref.compare( name1, name2, verbose=false ) -> true or false
 | Returns true only if the contents of files name1 and name2 are identical.
copy | ref.copy( fromName, toName, verbose=false ) -> true or false
 | Equivalent to calling File.syscopy , but logs the attempt to $stderr if verbose is not false.
cp | ref.cp( fromName, toName, verbose=false ) -> true or false
 | Synonym for File.copy .
install | ref.install( fromName, toName, aMode=nil, verbose=false )
 | Copies file fromName to file toName using File.syscopy , unless toName already exists and has the same content as fromName. Sets the mode of the resulting file to aMode unless aMode is nil.
makedirs | ref.makedirs( [ dirName ]* [, aBoolean ] )
 | Creates the given directories, logging each attempt to $stderr if the last parameter is true. Creates any missing parent directories as required.
mkpath | ref.mkpath( [ dirName ]* [, aBoolean ] )
 | Synonym for File.makedirs .
move | ref.move( fromName, toName, verbose=false ) -> true or false
 | Effectively renames fromName to toName, logging to $stderr if verbose is not false.
mv | ref.mv( fromName, toName, verbose=false ) -> true or false
 | Synonym for File.move .
rm_f | ref.rm_f( [ fileName ]* [, aBoolean ] ) -> anInteger
 | Synonym for File.safe_unlink (the name refers to the Unix rm -f command).
safe_unlink | ref.safe_unlink( [ fileName ]* [, aBoolean ] ) -> anInteger or nil
 | Unlinks (deletes) the given files, logging to $stderr if the last parameter is true. The method attempts to make all files writable before unlinking them, so no errors will occur deleting read-only files. Returns the number of files deleted, or nil on error.
syscopy | ref.syscopy( fromName, toName ) -> true or false
 | Efficiently copies the file named fromName to toName. If toName names a directory, the destination will be a file in that directory with the same basename as fromName. After the copy, the file mode of toName will be the same as that of fromName. Returns true on success.

class GetoptLong
Parent: | Object
Version: | 1.6

Index:

new each error? error_message get get_option ordering ordering= quiet quiet= quiet? set_options terminate terminated?

# Call using "ruby example.rb --size 10k -v -q a.txt b.doc"

require 'getoptlong'

# specify the options we accept and initialize
# the option parser

opts = GetoptLong.new(
  [ "--size",    "-s",            GetoptLong::REQUIRED_ARGUMENT ],
  [ "--verbose", "-v",            GetoptLong::NO_ARGUMENT ],
  [ "--query",   "-q",            GetoptLong::NO_ARGUMENT ],
  [ "--check",   "--valid", "-c", GetoptLong::NO_ARGUMENT ]
)

# process the parsed options

opts.each do |opt, arg|
  puts "Option: #{opt}, arg #{arg.inspect}"
end

puts "Remaining args: #{ARGV.join(', ')}"
produces:
Option: --size, arg "10k"
Option: --verbose, arg ""
Option: --query, arg ""
Remaining args: a.txt, b.doc

Class GetoptLong supports GNU-style command-line option parsing. Options may be a minus sign (`-') followed by a single character, or two minus signs (`--') followed by a name (a long option). Long options may be abbreviated to their shortest unambiguous lengths.

A single internal option may have multiple external representations. For example, the option to control verbose output could be any of -v, --verbose, or --details. Some options may also take an associated value.

Each internal option is passed to GetoptLong as an array, containing strings representing the option's external forms and a flag. The flag (NO_ARGUMENT, REQUIRED_ARGUMENT, or OPTIONAL_ARGUMENT) specifies how GetoptLong is to associate an argument with the option.

If the environment variable POSIXLY_CORRECT is set, all options must precede nonoptions on the command line. Otherwise, the default behavior of GetoptLong is to reorganize the command line to put the options at the front. This behavior may be changed by setting GetoptLong#ordering= to one of the constants PERMUTE, REQUIRE_ORDER, or RETURN_IN_ORDER. POSIXLY_CORRECT may not be overridden.

constants

Per-option constants
NO_ARGUMENT | Flags an option that takes no argument.
OPTIONAL_ARGUMENT | A nonoption following this option will be used as this option's argument.
REQUIRED_ARGUMENT | This option must be followed by an argument.
Overall constants
PERMUTE | Options and their arguments will be shuffled to the front of the command line.
REQUIRE_ORDER | Options and their arguments must appear at the start of the command line. The first nonoption terminates option processing.
RETURN_IN_ORDER | Return options in the order in which they occur on the command line.

class methods
new | GetoptLong.new( [ options ]* ) -> ref
 | Returns a new option parser. Any options are passed to ref.set_options.

instance methods
each ref.each {| anOption, anArgument | block }

Loops calling GetoptLong#get, passing the returned option and argument to the associated block. The loop ends when get returns nil for anOption.

error? | ref.error? -> anException

Returns an Exception object documenting any error that has occurred, or nil if there has not been an error.

error_message | ref.error_message -> aString

Returns the text of the last error message.
get | ref.get -> [ anOption, anArgument ]

Returns the next option, along with any associated argument. If there is no argument, nil is returned for anArgument. If there are no remaining unprocessed options, or if there is an error in option processing and quiet has been set, nil is returned for anOption. Otherwise, if there is an error, a message is written to $stderr and an exception (a subclass of StandardError) is raised.

The option string returned is the first option that was given in the corresponding array passed to set_options.

get_option | ref.get_option -> [ anOption, anArgument ]

Synonym for GetoptLong#get.

ordering | ref.ordering -> aFixnum

Returns the current ordering.

ordering= | ref.ordering = aFixnum

Sets the ordering to one of PERMUTE, REQUIRE_ORDER, or RETURN_IN_ORDER. Quietly ignored if the environment variable POSIXLY_CORRECT is set. Ordering may not be changed once option processing has been started.

quiet | ref.quiet -> true or false

Returns the current value of the quiet attribute.

quiet= | ref.quiet = true or false

Sets the current value of the quiet attribute. If false, any errors encountered are reported to $stderr.

quiet? | ref.quiet? -> true or false

Synonym for GetoptLong#quiet.

set_options | ref.set_options( [ anOptArray ]* ) -> ref

Each parameter is an array specifying a single internal option. The array contains one or more strings specifying the external form(s) of the option, and one of the flags NO_ARGUMENT, OPTIONAL_ARGUMENT, or REQUIRED_ARGUMENT. See the sample code on page 448 for examples of use.

terminate | ref.terminate -> ref

Terminates option processing. Any remaining arguments are written back to ARGV. This may be called from within a GetoptLong#each or on its own. For example, calling the following program using ``ruby example.rb --size 10k -v -term -q a.txt b.doc'' will leave the -q and filenames in ARGV.

require 'getoptlong'

opts = GetoptLong.new(
  [ "--size",    "-s",            GetoptLong::REQUIRED_ARGUMENT ],
  [ "--verbose", "-v",            GetoptLong::NO_ARGUMENT ],
  [ "--term",    "-t",            GetoptLong::NO_ARGUMENT ],
  [ "--query",   "-q",            GetoptLong::NO_ARGUMENT ],
  [ "--check",   "--valid", "-c", GetoptLong::NO_ARGUMENT ]
  )

opts.each do |opt, arg|
  puts "Option: #{opt}, arg #{arg.inspect}"
  opts.terminate if (opt == '--term')
end

puts "Remaining args: #{ARGV.join(', ')}"
produces:
Option: --size, arg "10k"
Option: --verbose, arg ""
Option: --term, arg ""
Remaining args: -q, a.txt, b.doc

terminated? | ref.terminated? -> true or false

Returns true if option processing has been terminated.

module mkmf

Index:

create_makefile dir_config find_library have_func have_header have_library

The mkmf library is used by Ruby extension modules to help create Makefiles. When writing an extension, you create a program named ``extconf.rb'', which may be as simple as:

require 'mkmf'
create_makefile("Test")

When run, this script will produce a Makefile suited to the target platform. mkmf contains several methods you can use to find libraries and include files and to set compiler flags.

For more information on creating extension modules, see Chapter 17, which begins on page 169.

constants

PLATFORM | varies | A constant string that describes the platform on which Ruby is running, such as ``mswin32'' or ``i686-linux.''
$CFLAGS |  | Global variable for compiler flags.
$LDFLAGS |  | Global variable for linker flags.

instance methods
create_makefile | create_makefile( target )

Creates a Makefile for an extension named target. If this method is not called, no Makefile is created.

dir_config | dir_config( name )

Looks for directory configuration options for name given as arguments to this program or to the original build of Ruby. These arguments may be one of:

--with- name -dir=directory
--with- name -include=directory
--with- name -lib=directory

The given directories will be added to the appropriate search paths (include or link) in the Makefile.

find_library | find_library( name, function, [ path ]+ ) -> true or false

Same as have_library, but will also search in the given directory paths.

have_func | have_func( function ) -> true or false

If the named function exists in the standard compile environment, adds the directive -DHAVE_FUNCTION to the compile command in the Makefile and returns true.

have_header | have_header( header ) -> true or false

If the given header file can be found in the standard search path, adds the directive -DHAVE_HEADER to the compile command in the Makefile and returns true.

have_library | have_library( library, function ) -> true or false

If the given function exists in the named library, which must exist in the standard search path or in a directory added with dir_config, adds the library to the link command in the Makefile and returns true.

module ParseDate

Index:

parsedate

The ParseDate module defines a single method, ParseDate::parsedate, which converts a date and/or time string into its constituents. It uses heuristics that handle a wide variety of date and time formats, including a subset of ISO 8601, Unix ctime, and most common written variants. The following table shows some examples.

StringGuess? |  | yy | mm | dd | hh | min | sec | zone | wd
1999-09-05 23:55:21+0900 | F | 1999 | 9 | 5 | 23 | 55 | 21 | +0900 | --
1983-12-25 | F | 1983 | 12 | 25 | -- | -- | -- | -- | --
1965-11-10 T13:45 | F | 1965 | 11 | 10 | 13 | 45 | -- | -- | --
10/9/75 1:30pm | F | 75 | 10 | 9 | 13 | 30 | -- | -- | --
10/9/75 1:30pm | T | 1975 | 10 | 9 | 13 | 30 | -- | -- | --
Mon Feb 28 17:15:49 CST 2000 | F | 2000 | 2 | 28 | 17 | 15 | 49 | CST | 1
Tue, 02-Mar-99 11:20:32 GMT | F | 99 | 3 | 2 | 11 | 20 | 32 | GMT | 2
Tue, 02-Mar-99 11:20:32 GMT | T | 1999 | 3 | 2 | 11 | 20 | 32 | GMT | 2
12-January-1990, 04:00 WET | F | 1990 | 1 | 12 | 4 | 0 | -- | WET | --
4/3/99 | F | 99 | 4 | 3 | -- | -- | -- | -- | --
4/3/99 | T | 1999 | 4 | 3 | -- | -- | -- | -- | --
10th February, 1976 | F | 1976 | 2 | 10 | -- | -- | -- | -- | --
March 1st, 84 | T | 1984 | 3 | 1 | -- | -- | -- | -- | --
Friday | F | -- | -- | -- | -- | -- | -- | -- | 5

class methods
parsedate | ref.parsedate( aString, guessYear=false ) -> [ year, mon, mday, hour, min, sec, zone, wday ]
 | Parses a string containing a date and/or a time, returning an array of Fixnum objects containing the various components. nil is returned for fields that cannot be parsed from aString. If the result contains a year that is less than 100 and guessYear is true, parsedate will return a year value equal to year plus 2000 if year is less than 69, year plus 1900 otherwise.

Library: profile

The profile library prints to $stderr a summary of the number of calls to, and the time spent in, each method in a Ruby program. The output is sorted by the total time spent in each method. Profiling can be enabled from the command line using the -r profile option, or from within a source program by requiring the profile module.

require 'profile'
def ackerman(m, n)
  if m == 0 then  n+1
  elsif n == 0 and m > 0 then ackerman(m-1, 1)
  else ackerman(m-1, ackerman(m, n-1))
  end
end
ackerman(3,3)

produces:
 time   seconds   seconds    calls  ms/call  ms/call  name
 74.17     2.47      2.47     2432     1.02    41.95  Object#ackerman
 17.42     3.05      0.58     3676     0.16     0.16  Fixnum#==
  5.71     3.24      0.19     2431     0.08     0.08  Fixnum#-
  2.70     3.33      0.09     1188     0.08     0.08  Fixnum#+
  0.00     3.33      0.00        1     0.00     0.00  Module#method_added
  0.00     3.33      0.00       57     0.00     0.00  Fixnum#>
  0.00     3.33      0.00        1     0.00  3330.00  #toplevel

class PStore
Parent: | Object
Version: | 1.6

Index:

new [ ] [ ]= abort commit path root? roots transaction

The PStore class provides transactional, file-based persistent storage of Ruby objects. The following example stores two hierarchies in a PStore. The first, identified by the key ``names'', is an array of Strings. The second, identified by ``tree'', is a simple binary tree.

require "pstore"

class T
  def initialize(val, left=nil, right=nil)
    @val, @left, @right = val, left, right
  end
  def to_a
    [ @val, @left.to_a, @right.to_a ]
  end
end

store = PStore.new("/tmp/store")
store.transaction do
   store['names'] = [ 'Douglas', 'Barenberg', 'Meyer' ]
   store['tree']  = T.new('top',
                      T.new('A', T.new('B')),
                      T.new('C', T.new('D', nil, T.new('E'))))
end

# now read it back in

store.transaction do
   puts "Roots: #{store.roots.join(', ')}"
   puts store['names'].join(', ')
   puts store['tree'].to_a.inspect
end
produces:
Roots: names, tree
Douglas, Barenberg, Meyer
["top", ["A", ["B", [], []], []], ["C", ["D", [], ["E", [], []]], []]]

Each PStore can store several object hierarchies. Each hierarchy has a root, identified by a key (often a string). At the start of a PStore transaction, these hierarchies are read from a disk file and made available to the Ruby program. At the end of the transaction, the hierarchies are written back to the file. Any changes made to objects in these hierarchies are therefore saved on disk, to be read at the start of the next transaction that uses that file.

In normal use, a PStore object is created and then is used one or more times to control a transaction. Within the body of the transaction, any object hierarchies that had previously been saved are made available, and any changes to object hierarchies, and any new hierarchies, are written back to the file at the end.

class methods
new | PStore.new( aFilename ) -> aPStore
 | Returns a new PStore object associated with the given file. If the file exists, its contents must have been previously written by PStore.

instance methods
[ ] | ref[ anObject ] -> anOtherObject
 | Root Access---Returns the root of an object hierarchy identified by anObject. An exception is raised if anObject does not identify a root.
[ ]= | ref[ anObject ] = anOtherObject -> anOtherObject
 | Root Creation---Sets anOtherObject as the base of the object hierarchy to be identified using anObject.
abort | ref.abort
 | Terminates this transaction, losing any changes made to the object hierarchies.
commit | ref.commit
 | Terminates the current transaction, saving the object hierarchies into the store's file.
path | ref.path -> aString
 | Returns the name of the file associated with this store.
root? | ref.root?( anObject ) -> true or false
 | Returns true if anObject is the key of a root in this store.
roots | ref.roots -> anArray
 | Returns an array containing the keys of the root objects available in this store.
transaction | ref.transaction {| ref | block } -> anObject
 | If the file associated with ref exists, reads in the object hierarchies from it. It then executes the associated block, passing in ref. The block may use this parameter to access the roots of the hierarchies and hence access the persistent objects. If the block calls PStore#abort, or if it raises an exception, no data is saved back to the associated file. Otherwise, if it invokes PStore#commit, or if it terminates normally, the object hierarchies are written back to the file. The value returned is the value returned by the block.

class Tempfile
Parent: | [IO]
Version: | 1.6

Index:

new close open path

require "tempfile"
tf = Tempfile.new("afile")
tf.path | » | "/tmp/afile32146.0"
tf.puts("Cosi Fan Tutte") | » | nil
tf.close | » | nil
tf.open | » | #<File:0x40196fc8>
tf.gets | » | "Cosi Fan Tutte\n"
tf.close(true) | » | #<File:0x40196fc8>

Class Tempfile creates managed temporary files. Although they behave the same as any other IO objects, temporary files are automatically deleted when the Ruby program terminates. Once a Tempfile object has been created, the underlying file may be opened and closed a number of times in succession.

Tempfile does not directly inherit from IO. Instead, it delegates calls to a File object. From the programmer's perspective, apart from the unusual new, open, and close semantics, a Tempfile object behaves as if it were an IO object.

class methods
new | Tempfile.new( basename, tmpdir=<see below> ) -> ref
 | Constructs a temporary file in the given directory. The file name is built by concatenating basename, the current process id and (as an extension) a unique sequence number. If the tmpdir parameter is not supplied, it defaults to the value of one of the environment variables TMPDIR, TMP, or TEMP, or to the directory /tmp. The file is then opened using mode ``w+'', which allows reading and writing and deletes any existing content (see Table 22.5 on page 326).

open Tempfile.open( basename, tmpdir ) -> ref

Synonym for Tempfile.new.

instance methods
close | ref.close( final=false )
 | Closes ref. If final is true, deletes the underlying real file. If final is false, ref may be subsequently reopened. In all cases, the underlying file is deleted when the program terminates.
open | ref.open
 | Reopens ref using mode ``r+'', which allows reading and writing but does not delete existing content.
path | ref.path -> aString
 | Returns the full path of the underlying file.

class Mutex
Parent: | Object
Version: | 1.6

Index:

lock locked? synchronize try_lock unlock

require 'thread'
sema4 = Mutex.new

a = Thread.new {
  sema4.synchronize {
    # access shared resource
  }
}

b = Thread.new {
  sema4.synchronize {
    # access shared resource
  }
}

Mutex implements a simple semaphore that can be used to coordinate access to shared data from multiple concurrent threads.

instance methods
lock | ref.lock -> ref
 | Attempts to grab the lock and waits if it isn't available.
locked? | ref.locked? -> true or false
 | Returns true if this lock is currently held by some thread.
synchronize | ref.synchronize { block } -> ref
 | Obtains a lock (using Mutex#lock), runs the block, and releases the lock when the block completes.
try_lock | ref.try_lock -> true or false
 | Attempts to obtain the lock and returns immediately. Returns true if the lock was granted.
unlock | ref.unlock -> ref or nil
 | Releases the lock. Returns nil if ref wasn't locked.

class ConditionVariable
Parent: | Object
Version: | 1.6

Index:

broadcast signal wait

require 'thread'
mutex = Mutex.new
resource = ConditionVariable.new

a = Thread.new {
  mutex.synchronize {
    # Thread 'a' now needs the resource
    resource.wait(mutex)
    # 'a' can now have the resource
  }
}

b = Thread.new {
  mutex.synchronize {
    # Thread 'b' has finished using the resource
    resource.signal
  }
}

ConditionVariable objects augment class Mutex. Using condition variables, it is possible to suspend while in the middle of a critical section until a resource becomes available (see the discussion on page 117).

instance methods
broadcast | ref.broadcast
 | Wakes up all threads waiting for this lock.
signal | ref.signal
 | Wakes up the first thread in line waiting for this lock.
wait | ref.wait( aMutex ) -> aMutex
 | Releases the lock held in aMutex and waits; reacquires the lock on wakeup.

Library: timeout

require "timeout"

for snooze in 1..2
  puts "About to sleep for #{snooze}"
  timeout(1.5) do
    sleep(snooze)
  end
  puts "That was refreshing"
end
produces:
About to sleep for 1
That was refreshing
About to sleep for 2
/tc/usr/lib/ruby/1.6/timeout.rb:37: execution expired (TimeoutError)
    from prog.rb:5:in `timeout'
    from prog.rb:5
    from prog.rb:3:in `each'
    from prog.rb:3

The timeout method takes a single parameter, representing a timeout period in seconds, and a block. The block is executed, and a timer is run concurrently. If the block terminates before the timeout, timeout returns true. Otherwise, a TimeoutError exception is raised.

class WeakRef
Parent: | Delegator
Version: | 1.6

Index:

new weakref_alive?

require "weakref"

ref = "fol de rol"
puts "Initial object is #{ref}"
ref = WeakRef.new(ref)
puts "Weak reference is #{ref}"
ObjectSpace.garbage_collect
puts "But then it is #{ref}"
produces:
Initial object is fol de rol
Weak reference is fol de rol
prog.rb:8: Illegal Reference - probably recycled (WeakRef::RefError)

In Ruby, objects are not eligible for garbage collection if there are still references to them. Normally, this is a Good Thing---it would be disconcerting to have an object simply evaporate while you were using it. However, sometimes you may need more flexibility. For example, you might want to implement an in-memory cache of commonly used file contents. As you read more files, the cache grows. At some point, you may run low on memory. The garbage collector will be invoked, but the objects in the cache are all referenced by the cache data structures, and so will not be deleted.

A weak reference behaves exactly as any normal object reference with one important exception---the referenced object may be garbage collected, even while references to it exist. In the cache example, if the cached files were accessed using weak references, once memory runs low they will be garbage collected, freeing memory for the rest of the application.

Weak references introduce a slight complexity. As the object referenced can be deleted by garbage collection at any time, code that accesses these objects must take care to ensure that the references are valid. Two techniques can be used. First, the code can reference the objects normally. Any attempt to reference an object that has been garbage collected will raise a WeakRef::RefError exception.

An alternative approach is to use the WeakRef#weakref_alive? method to check that a reference is valid before using it. Garbage collection must be disabled during the test and subsequent reference to the object. In a single-threaded program, you could use something like:

ref = WeakRef.new(someObject)
#
# .. some time later
#

gcWasDisabled = GC.disable
if ref.weakref_alive?
  # do stuff with 'ref'
end
GC.enable unless gcWasDisabled

class methods
new | WeakRef.new( anObject ) -> ref
 | Creates and returns a weak reference to anObject. All future references to anObject should be made using ref.

instance methods
weakref_alive? | ref.weakref_alive? -> true or false
 | Returns false if the object referenced by ref has been garbage collected.

Extracted from the book "Programming Ruby - The Pragmatic Programmer's Guide"
Copyright © 2001 by Addison Wesley Longman, Inc. This material may be distributed only subject to the terms and conditions set forth in the Open Publication License, v1.0 or later (the latest version is presently available at http://www.opencontent.org/openpub/)).

Distribution of substantively modified versions of this document is prohibited without the explicit permission of the copyright holder.

Distribution of the work or derivative of the work in any standard (paper) book form is prohibited unless prior permission is obtained from the copyright holder.


Programming Ruby: The Pragmatic Programmer's Guide

Programming Ruby

The Pragmatic Programmer's Guide

Previous < Contents ^
Next >

Object-Oriented Design Libraries

One of the interesting things about Ruby is the way it blurs the distinction between design and implementation. Ideas that have to be expressed at the design level in other languages can be implemented directly in Ruby.

To help in this process, Ruby has support for some design-level strategies.

  • The Visitor pattern (Design Patterns, ) is a way of traversing a collection without having to know the internal organization of that collection.
  • Delegation is a way of composing classes more flexibly and dynamically than can be done using standard inheritance.
  • The Singleton pattern is a way of ensuring that only one instantiation of a particular class exists at a time.
  • The Observer pattern implements a protocol allowing one object to notify a set of interested objects when certain changes have occurred.

Normally, all four of these strategies require explicit code each time they're implemented. With Ruby, they can be abstracted into a library and reused freely and transparently.

Before we get into the proper library descriptions, let's get the simplest strategy out of the way.

The Visitor Pattern

It's the method each.

Library: delegate

Object delegation is a way of composing objects---extending an object with the capabilities of another---at runtime. This promotes writing flexible, decoupled code, as there are no compile-time dependencies between the users of the overall class and the delegates.

The Ruby Delegator class implements a simple but powerful delegation scheme, where requests are automatically forwarded from a master class to delegates or their ancestors, and where the delegate can be changed at runtime with a single method call.

The delegate.rb library provides two mechanisms for allowing an object to forward messages to a delegate.

  1. For simple cases where the class of the delegate is fixed, arrange for the master class to be a subclass of DelegateClass, passing the name of the class to be delegated as a parameter (Example 1). Then, in your class's initialize method, you'd call the superclass, passing in the object to be delegated. For example, to declare a class Fred that also supports all the methods in Flintstone, you'd write

    class Fred < DelegateClass(Flintstone)
  def initialize
    # ...
    super(Flintstone.new(...))
  end
  # ...
 end
    This is subtly different from using subclassing. With subclassing, there is only one object, which has the methods and the defined class, its parent, and their ancestors. With delegation there are two objects, linked so that calls to one may be delegated to the other.

  2. For cases where the delegate needs to be dynamic, make the master class a subclass of SimpleDelegator (Example 2). You can also add delegation capabilities to an existing object using SimpleDelegator (Example 3). In these cases, you can call the __setobj__ method in SimpleDelegator to change the object being delegated at runtime.

Example 1. Use the DelegateClass method and subclass the result when you need a class with its own behavior that also delegates to an object of another class. In this example, we assume that the @sizeInInches array is large, so we want only one copy of it. We then define a class that accesses it, converting the values to feet.

require 'delegate'

sizeInInches = [ 10, 15, 22, 120 ]

class Feet < DelegateClass(Array)
  def initialize(arr)
    super(arr)
  end
  def [](*n)
    val = super(*n)
    case val.type
    when Numeric; val/12.0
    else;         val.collect {|i| i/12.0}
    end
  end
end

sizeInFeet = Feet.new(sizeInInches)
sizeInInches[0..3] | » | [10, 15, 22, 120]
sizeInFeet[0..3] | » | [0.8333333333, 1.25, 1.833333333, 10.0]

Example 2. Use subclass SimpleDelegator when you want an object that both has its own behavior and delegates to different objects during its lifetime. This is an example of the State pattern. Objects of class TicketOffice sell tickets if a seller is available, or tell you to come back tomorrow if there is no seller.

require 'delegate'

class TicketSeller
  def sellTicket()
    return 'Here is a ticket'
  end
end

class NoTicketSeller
  def sellTicket()
    "Sorry-come back tomorrow"
   end
end

class TicketOffice < SimpleDelegator
  def initialize
    @seller = TicketSeller.new
    @noseller = NoTicketSeller.new
    super(@seller)
  end
  def allowSales(allow = true)
    __setobj__(allow ? @seller : @noseller)
    allow
  end
end

to = TicketOffice.new
to.sellTicket | » | "Here is a ticket"
to.allowSales(false) | » | false
to.sellTicket | » | "Sorry-come back tomorrow"
to.allowSales(true) | » | true
to.sellTicket | » | "Here is a ticket"

Example 3. Create SimpleDelegator objects when you want a single object to delegate all its methods to two or more other objects.

# Example 3 - delegate from existing object
seller = TicketSeller.new
noseller = NoTicketSeller.new
to = SimpleDelegator.new(seller)
to.sellTicket | » | "Here's a ticket"
to.sellTicket | » | "Here's a ticket"
to.__setobj__(noseller)
to.sellTicket | » | "Sorry-come back tomorrow"
to.__setobj__(seller)
to.sellTicket | » | "Here's a ticket"

Library: observer

The Observer pattern, also known as Publish/Subscribe, provides a simple mechanism for one object to inform a set of interested third-party objects when its state changes.

In the Ruby implementation, the notifying class mixes in the Observable module, which provides the methods for managing the associated observer objects.

add_observer(obj) | Add obj as an observer on this object. obj will now receive notifications.
delete_observer(obj) | Delete obj as an observer on this object. It will no longer receive notifications.
delete_observers | Delete all observers associated with this object.
count_observers | Return the count of observers associated with this object.
changed(newState=true) | Set the changed state of this object. Notifications will be sent only if the changed state is true.
changed? | Query the changed state of this object.
notify_observers(*args) | If this object's changed state is true, invoke the update method in each currently associated observer in turn, passing it the given arguments. The changed state is then set to false.

The observers must implement the update method to receive notifications.

require "observer"

  class Ticker # Periodically fetch a stock price
    include Observable

    def initialize(symbol)
      @symbol = symbol
    end

    def run
      lastPrice = nil
      loop do
        price = Price.fetch(@symbol)
        print "Current price: #{price}\n"
        if price != lastPrice
          changed                 # notify observers
          lastPrice = price
          notify_observers(Time.now, price)
        end
      end
    end
  end

  class Warner
    def initialize(ticker, limit)
      @limit = limit
      ticker.add_observer(self)   # all warners are observers
    end
  end

  class WarnLow < Warner
    def update(time, price)       # callback for observer
      if price < @limit
        print "--- #{time.to_s}: Price below #@limit: #{price}\n"
      end
    end
  end

  class WarnHigh < Warner
    def update(time, price)       # callback for observer
      if price > @limit
        print "+++ #{time.to_s}: Price above #@limit: #{price}\n"
      end
    end
  end

ticker = Ticker.new("MSFT")
WarnLow.new(ticker, 80)
WarnHigh.new(ticker, 120)
ticker.run
produces:
Current price: 83
Current price: 75
--- Sun Jun 09 00:10:25 CDT 2002: Price below 80: 75
Current price: 90
Current price: 134
+++ Sun Jun 09 00:10:25 CDT 2002: Price above 120: 134
Current price: 134
Current price: 112
Current price: 79
--- Sun Jun 09 00:10:25 CDT 2002: Price below 80: 79

Library: singleton

The Singleton design pattern ensures that only one instance of a particular class may be created.

The singleton library makes this simple to implement. Mix the Singleton module into each class that is to be a singleton, and that class's new method will be made private. In its place, users of the class call the method instance, which returns a singleton instance of that class.

In this example, the two instances of MyClass are the same object.

require 'singleton'
class MyClass
include Singleton
end
a = MyClass.instance | » | #<MyClass:0x401b4ca8>
b = MyClass.instance | » | #<MyClass:0x401b4ca8>

Extracted from the book "Programming Ruby - The Pragmatic Programmer's Guide"
Copyright © 2001 by Addison Wesley Longman, Inc. This material may be distributed only subject to the terms and conditions set forth in the Open Publication License, v1.0 or later (the latest version is presently available at http://www.opencontent.org/openpub/)).

Distribution of substantively modified versions of this document is prohibited without the explicit permission of the copyright holder.

Distribution of the work or derivative of the work in any standard (paper) book form is prohibited unless prior permission is obtained from the copyright holder.


Programming Ruby: The Pragmatic Programmer's Guide

Programming Ruby

The Pragmatic Programmer's Guide

Previous < Contents ^
Next >

Network and Web Libraries

Ruby provides two levels of access to network services. At a low level, you can access the basic socket support in the underlying operating system, which allows you to implement clients and servers for both connection-oriented and connectionless protocols. These are documented in the next section.

Ruby also has libraries that provide higher-level access to specific application-level network protocols, such as FTP, HTTP, and so on. These are documented starting on page 482.

Finally, the CGI libraries, documented beginning on page 497, provide server-side developers with a convenient interface for developing Web applications.

Socket-Level Access

Sockets are the endpoints of a bidirectional communications channel. Sockets may communicate within a process, between processes on the same machine, or between processes on different continents. Sockets may be implemented over a number of different channel types: Unix domain sockets, TCP, UDP, and so on. The socket library provides specific classes for handling the common transports as well as a generic interface for handling the rest. All functionality in the socket library is accessible through a single extension library. Access it using

require 'socket'

Sockets have their own vocabulary:

domain
The family of protocols that will be used as the transport mechanism. These values are constants such as PF_INET, PF_UNIX, PF_X25, and so on.
type
The type of communications between the two endpoints, typically SOCK_STREAM for connection-oriented protocols and SOCK_DGRAM for connectionless protocols.
protocol
Typically zero, this may be used to identify a variant of a protocol within a domain and type.
hostName
The identifier of a network interface:
  • a string, which can be a host name, a dotted-quad address, or an IPV6 address in colon (and possibly dot) notation,
  • the string ``<broadcast>'', which specifies an INADDR_BROADCAST address,
  • a zero-length string, which specifies INADDR_ANY, or
  • an Integer, interpreted as a binary address in host byte order.
port
(sometimes called service) Each server listens for clients calling on one or more ports. A port may be a Fixnum port number, a string containing a port number, or the name of a service.

Sockets are children of class IO. Once a socket has been successfully opened, the conventional I/O methods may be used. However, greater efficiency is sometimes obtained by using socket-specific methods. As with other I/O classes, socket I/O blocks by default. The hierarchy of the socket classes is shown in Figure 26.1 on page 471.

For more information on the use of sockets, see your operating system documentation. You'll also find a comprehensive treatment in W. Richard Stevens, Unix Network Programming, Volumes 1 and 2 .

class BasicSocket
Parent: | IO
Version: | 1.6

Index:

do_not_reverse_lookup do_not_reverse_lookup= lookup_order lookup_order= close_read close_write getpeername getsockname getsockopt recv send setsockopt shutdown

BasicSocket is an abstract base class for all other socket classes.

This class and its subclasses often manipulate addresses using something called a struct sockaddr, which is effectively an opaque binary string.[In reality, it maps onto the underlying C-language struct sockaddr set of structures, documented in the man pages and in the books by Stevens.]

class methods
do_not_reverse_lookup | BasicSocket.do_not_reverse_lookup -> true or false

Returns the value of the global reverse lookup flag. If set to true, queries on remote addresses will return the numeric address but not the host name.

do_not_reverse_lookup= | BasicSocket.do_not_reverse_lookup = true or false

Sets the global reverse lookup flag.

lookup_order | BasicSocket.lookup_order -> aFixnum

Returns the global address lookup order, one of:

Order | Families Searched
LOOKUP_UNSP | AF_UNSPEC
LOOKUP_INET | AF_INET, AF_INET6, AF_UNSPEC
LOOKUP_INET6 | AF_INET6, AF_INET, AF_UNSPEC
lookup_order= | BasicSocket.lookup_order = aFixnum

Sets the global address lookup order.

instance methods
close_read | aSession.close_read -> nil
 | Closes the readable connection on this socket.
close_write | aSession.close_write -> nil
 | Closes the writable connection on this socket.
getpeername | aSession.getpeername -> aString
 | Returns the struct sockaddr structure associated with the other end of this socket connection.
getsockname | aSession.getsockname -> aString
 | Returns the struct sockaddr structure associated with aSession.
getsockopt | aSession.getsockopt( level, optname ) -> aString
 | Returns the value of the specified option.
recv | aSession.recv( len, [, flags ] ) -> aString
 | Receives up to len bytes from aSession.
send | aSession.send( aString, flags, [, to ] ) -> aFixnum
 | Sends aString over aSession. If specified, to is a struct sockaddr specifying the recipient address. flags are the sum or one or more of the MSG_ options (listed on page 478). Returns the number of characters sent.
setsockopt | aSession.setsockopt( level, optname, optval ) -> 0
 | Sets a socket option. level is one of the socket-level options (listed on page 478). optname and optval are protocol specific---see your system documentation for details.
shutdown | aSession.shutdown( how=2 ) -> 0
 | Shuts down the receive (how == 0), or send (how == 1), or both (how == 2), parts of this socket.

class IPSocket
Parent: | BasicSocket
Version: | 1.6

Index:

getaddress addr peeraddr

Class IPSocket is a base class for sockets using IP as their transport. TCPSocket and UDPSocket are based on this class.

class methods
getaddress IPSocket.getaddress( hostName ) -> aString

Returns the dotted-quad IP address of hostName.

a = IPSocket.getaddress('www.ruby-lang.org')
a » "210.251.121.214"

instance methods
addr | aSession.addr -> anArray

Returns the domain, port, name, and IP address of aSession as a four-element array. The name will be returned as an address if the do_not_reverse_lookup flag is true.

u = UDPSocket.new
u.bind('localhost', 8765)
u.addr | » | ["AF_INET", 8765, "localhost", "127.0.0.1"]
BasicSocket.do_not_reverse_lookup = true
u.addr | » | ["AF_INET", 8765, "127.0.0.1", "127.0.0.1"]
peeraddr | aSession.peeraddr -> anArray

Returns the domain, port, name, and IP address of the peer.

class TCPSocket
Parent: | IPSocket
Version: | 1.6

Index:

gethostbyname new open recvfrom

t = TCPSocket.new('localhost', 'ftp')
t.gets | » | "220 zip.local.thomases.com FTP server (Version 6.5/OpenBSD, linux port 0.3.2) ready.\r\n"
t.close | » | nil

class methods
gethostbyname | TCPSocket.gethostbyname( hostName ) -> anArray

Looks up hostName and returns its canonical name, an array containing any aliases, the address type (AF_INET), and the dotted-quad IP address.

a = TCPSocket.gethostbyname('ns.pragprog.com')
a » ["pragprog.com", [], 2, "216.87.136.211"]

new | TCPSocket.new( hostName, port ) -> aSession

Opens a TCP connection to hostName on the port.

open | TCPSocket.open( hostName, port ) -> aSession

Synonym for TCPSocket.new.

instance methods
recvfrom aSession.recvfrom( len [, flags ] ) -> anArray

Receives up to len bytes on the connection. flags is zero or more of the MSG_ options (listed on page 478). Returns a two-element array. The first element is the received data, the second is an array containing information about the peer.

t = TCPSocket.new('localhost', 'ftp')
data = t.recvfrom(30)
data

class SOCKSSocket
Parent: | TCPSocket
Version: | 1.6

Index:

new open close

Class SOCKSSocket supports connections based on the SOCKS protocol.
class methods
new | SOCKSSocket.new( hostName, port ) -> aSession
 | Opens a SOCKS connection to port on hostName.
open | SOCKSSocket.open( hostName, port ) -> aSession
 | Synonym for SOCKSSocket.new.

instance methods
close | aSession.close -> nil
 | Closes this SOCKS connection.

class TCPServer
Parent: | TCPSocket
Version: | 1.6

Index:

new open accept

A TCPServer accepts incoming TCP connections. Here is a Web server that listens on a given port and returns the time.

require 'socket'
port = (ARGV[0] || 80).to_i
server = TCPServer.new('localhost', port)
while (session = server.accept)
  puts "Request: #{session.gets}"
  session.print "HTTP/1.1 200/OK\r\nContent-type: text/html\r\n\r\n"
  session.print "<html><body><h1>#{Time.now}</h1></body></html>\r\n"
  session.close
end

class methods
new | TCPServer.new( [ hostName,] port ) -> aSession
 | Creates a new socket on the given interface (identified by hostName and port). If hostName is omitted, the server will listen on all interfaces on the current host (equivalent to an address of 0.0.0.0).
open | TCPServer.open( [ hostName,] port ) -> aSession
 | Synonym for TCPServer.new.

instance methods
accept | aSession.accept -> aTCPSocket
 | Waits for a connection on aSession, and returns a new TCPSocket connected to the caller. See the example on page 474.

class UDPSocket
Parent: | IPSocket
Version: | 1.6

Index:

new open bind connect recvfrom send

UDP sockets send and receive datagrams. In order to receive data, a socket must be bound to a particular port. You have two choices when sending data: you can connect to a remote UDP socket and thereafter send datagrams to that port, or you can specify a host and port for use with every packet you send. This example is a UDP server that prints the message it receives. It is called by both connectionless and connection-based clients.

require 'socket'

$port = 4321

sThread = Thread.start do     # run server in a thread
  server = UDPSocket.open
  server.bind(nil, $port)
  2.times { p server.recvfrom(64) }
end

# Ad-hoc client
UDPSocket.open.send("ad hoc", 0, 'localhost', $port)

# Connection based client
sock = UDPSocket.open
sock.connect('localhost', $port)
sock.send("connection-based", 0)
sThread.join
produces:
["ad hoc", ["AF_INET", 33224, "localhost", "127.0.0.1"]]
["connection-based", ["AF_INET", 33225, "localhost", "127.0.0.1"]]

class methods
new | UDPSocket.new( family = AF_INET ) -> aSession
 | Creates an endpoint for UDP communications, optionally specifying the address family.
open | UDPSocket.open( family = AF_INET ) -> aSession
 | Synonym for UDPSocket.new.

instance methods
bind | aSession.bind( hostName, port ) -> 0

Associates the local end of the UDP connection with a given hostName and port. Must be used by servers to establish an accessible endpoint.

connect | aSession.connect( hostName, port ) -> 0

Creates a connection to the given hostName and port. Subsequent UDPSocket#send requests that don't override the recipient will use this connection. Multiple connect requests may be issued on aSession: the most recent will be used by send.

recvfrom | aSession.recvfrom( len [, flags ] ) -> anArray

Receives up to len bytes from aSession. flags is zero or more of the MSG_ options (listed on page 478). The result is a two-element array containing the received data and information on the sender. See the example on page 475.

send aSession.send( aString, flags ) -> aFixnum
aSession.send( aString, flags, hostName, port ) -> aFixnum

The two-parameter form sends aString on an existing connection. The four-parameter form sends aString to port on hostName.

class UNIXSocket
Parent: | BasicSocket
Version: | 1.6

Index:

new open addr path peeraddr recvfrom

Class UNIXSocket supports interprocess communications using the Unix domain protocol. Although the underlying protocol supports both datagram and stream connections, the Ruby library provides only a stream-based connection.

require 'socket'

$path = "/tmp/sample"

sThread = Thread.start do        # run server in a thread
  sock = UNIXServer.open($path)
  s1 = sock.accept
  p s1.recvfrom(124)
end

client = UNIXSocket.open($path)
client.send("hello", 0)
client.close

sThread.join
produces:
["hello", ["AF_UNIX", ""]]

class methods
new | UNIXSocket.new( path ) -> aSession
 | Opens a new domain socket on path, which must be a pathname.
open | UNIXSocket.open( path ) -> aSession
 | Synonym for UNIXSocket.new.

instance methods
addr | aSession.addr -> anArray
 | Returns the address family and path of this socket.
path | aSession.path -> aString
 | Returns the path of this domain socket.
peeraddr | aSession.peeraddr -> anArray
 | Returns the address family and path of the server end of the connection.
recvfrom | aSession.recvfrom( len [, flags ] ) -> anArray
 | Receives up to len bytes from aSession. flags is zero or more of the MSG_ options (listed on page 478). The first element of the returned array is the received data, and the second contains (minimal) information on the sender.

class UNIXServer
Parent: | UNIXSocket
Version: | 1.6

Index:

new open accept

Class UNIXServer provides a simple Unix domain socket server. See UNIXSocket for example code.

class methods
new | UNIXServer.new( path ) -> aSession
 | Creates a server on the given path. The corresponding file must not exist at the time of the call.
open | UNIXServer.open( path ) -> aSession
 | Synonym for UNIXServer.new.

instance methods
accept | aSession.accept -> aUnixSocket
 | Waits for a connection on the server socket and returns a new socket object for that connection. See the example for UNIXSocket on page 476.

class Socket
Parent: | BasicSocket
Version: | 1.6

Index:

for_fd getaddrinfo gethostbyaddr gethostbyname gethostname getnameinfo getservbyname new open pair socketpair accept bind connect listen recvfrom

Class Socket provides access to the underlying operating system socket implementation. It can be used to provide more operating system-specific functionality than the protocol-specific socket classes, but at the expense of greater complexity. In particular, the class handles addresses using struct sockaddr structures packed into Ruby strings, which can be a joy to manipulate.

constants

Class Socket defines constants for use throughout the socket library. Individual constants are available only on architectures that support the related facility.

Types:

SOCK_DGRAM, SOCK_PACKET, SOCK_RAW, SOCK_RDM, SOCK_SEQPACKET, SOCK_STREAM.

Protocol families:

PF_APPLETALK, PF_AX25, PF_INET6, PF_INET, PF_IPX, PF_UNIX, PF_UNSPEC.

Address families:

AF_APPLETALK, AF_AX25, AF_INET6, AF_INET, AF_IPX, AF_UNIX, AF_UNSPEC.

Lookup-order options:

LOOKUP_INET6, LOOKUP_INET, LOOKUP_UNSPEC.

Send/receive options:

MSG_DONTROUTE, MSG_OOB, MSG_PEEK.

Socket-level options:

SOL_ATALK, SOL_AX25, SOL_IPX, SOL_IP, SOL_SOCKET, SOL_TCP, SOL_UDP.

Socket options:

SO_BROADCAST, SO_DEBUG, SO_DONTROUTE, SO_ERROR, SO_KEEPALIVE, SO_LINGER, SO_NO_CHECK, SO_OOBINLINE, SO_PRIORITY, SO_RCVBUF, SO_REUSEADDR, SO_SNDBUF, SO_TYPE.

QOS options:

SOPRI_BACKGROUND, SOPRI_INTERACTIVE, SOPRI_NORMAL.

Multicast options:

IP_ADD_MEMBERSHIP, IP_DEFAULT_MULTICAST_LOOP, IP_DEFAULT_MULTICAST_TTL, IP_MAX_MEMBERSHIPS, IP_MULTICAST_IF, IP_MULTICAST_LOOP, IP_MULTICAST_TTL.

TCP options:

TCP_MAXSEG, TCP_NODELAY.

getaddrinfo error codes:

EAI_ADDRFAMILY, EAI_AGAIN, EAI_BADFLAGS, EAI_BADHINTS, EAI_FAIL, EAI_FAMILY, EAI_MAX, EAI_MEMORY, EAI_NODATA, EAI_NONAME, EAI_PROTOCOL, EAI_SERVICE, EAI_SOCKTYPE, EAI_SYSTEM.

ai_flags values:

AI_ALL, AI_CANONNAME, AI_MASK, AI_NUMERICHOST, AI_PASSIVE, AI_V4MAPPED_CFG.

class methods
for_fd | Socket.for_fd( anFD ) -> aSession

Wraps an already open file descriptor into a socket object.

getaddrinfo Socket.getaddrinfo( hostName, port,
[ family [ socktype [ protocol [ flags ] ] ] ] ) -> anArray

Returns an array of arrays describing the given host and port (optionally qualified as shown). Each subarray contains the address family, port number, host name, host IP address, protocol family, socket type, and protocol.

for line in Socket.getaddrinfo('www.microsoft.com', 'http')
  puts line.join(", ")
end
produces:
AF_INET, 80, microsoft.net, 207.46.130.149, 2, 1, 6
AF_INET, 80, microsoft.net, 207.46.131.137, 2, 1, 6
AF_INET, 80, microsoft.com, 207.46.230.218, 2, 1, 6
AF_INET, 80, microsoft.com, 207.46.230.219, 2, 1, 6
AF_INET, 80, microsoft.net, 207.46.130.14, 2, 1, 6

gethostbyaddr | Socket.gethostbyaddr( addr, type=AF_INET ) -> anArray
Returns the host name, address family, and sockaddr component for the given address.

a = Socket.gethostbyname("216.87.136.211")
res = Socket.gethostbyaddr(a[3], a[2])
res.join(', ') » "pragprog.com, , 2, \330W\210\323"

gethostbyname | Socket.gethostbyname( hostName ) -> anArray

Returns a four-element array containing the canonical host name, a subarray of host aliases, the address family, and the address portion of the sockaddr structure.

a = Socket.gethostbyname("216.87.136.211")
a.join(', ') » "pragprog.com, , 2, \330W\210\323"

gethostname | aSession.gethostname -> aString

Returns the name of the current host.

getnameinfo | Socket.getnameinfo( addr [, flags ] ) -> anArray

Looks up the given address, which may be either a string containing a sockaddr or a three- or four-element array. If sockaddr is an array, it should contain the string address family, the port (or nil), and the host name or IP address. If a fourth element is present and not nil, it will be used as the host name. Returns a canonical hostname (or address) and port number as an array.

a = Socket.getnameinfo(["AF_INET", '23', 'www.ruby-lang.org'])
a » ["helium.ruby-lang.org", "telnet"]

getservbyname | Socket.getservbyname( service, proto='tcp' ) -> aFixnum

Returns the port corresponding to the given service and protocol.

Socket.getservbyname("telnet") » 23

new | Socket.new( domain, type, protocol ) -> aSession

Creates a socket using the given parameters.

open | Socket.open( domain, type, protocol ) -> aSession

Synonym for Socket.new.

pair | Socket.pair( domain, type, protocol ) -> anArray

Returns a pair of connected, anonymous sockets of the given domain, type, and protocol.

socketpair | Socket.socketpair( domain, type, protocol ) -> anArray

Synonym for Socket.pair.

instance methods
accept | aSession.accept -> anArray
 | Accepts an incoming connection returning an array containing a new Socket object and a string holding the struct sockaddr information about the caller.
bind | aSession.bind( sockaddr ) -> 0
 | Binds to the given struct sockaddr, contained in a string.
connect | aSession.connect( sockaddr ) -> 0
 | Connects to the given struct sockaddr, contained in a string.
listen | aSession.listen( aFixnum ) -> 0
 | Listens for connections, using the specified aFixnum as the backlog.
recvfrom | aSession.recvfrom( len [, flags ] ) -> anArray
 | Receives up to len bytes from aSession. flags is zero or more of the MSG_ options. The first element of the result is the data received. The second element contains protocol-specific information on the sender.

Higher-Level Access

Ruby provides a set of classes to facilitate writing clients for:
  • File Transfer Protocol (FTP)
  • HyperText Transfer Protocol (HTTP)
  • Post Office Protocol (POP)
  • Simple Mail Transfer Protocol (SMTP)
  • Telnet

HTTP, POP, and SMTP are layered on top of a helper class, lib/net/protocol. Although we don't document the Protocol class here, you should probably study it if you are considering writing your own network client.

class Net::FTP
Parent: | Object
Version: | 1.6

Index:

new open Server commands close closed? connect debug_mode debug_mode= dir getbinaryfile gettextfile lastresp list login ls mtime passive passive= putbinaryfile puttextfile resume resume= retrbinary retrlines return_code storbinary storlines welcome

require 'net/ftp'

ftp = Net::FTP.new('ftp.netlab.co.jp')
ftp.login
files = ftp.chdir('pub/lang/ruby/contrib')
files = ftp.list('n*')
ftp.getbinaryfile('nif.rb-0.91.gz', 'nif.gz', 1024)
ftp.close

The net/ftp library implements a File Transfer Protocol (FTP) client.

constants
FTP_PORT Default port for FTP connections (21).

class methods
new | FTP.new( host=nil, user=nil, passwd=nil, acct=nil) -> aSession
 | Creates and returns a new FTP object. If the host parameter is not nil, a connection is made to that host. Additionally, if the user parameter is not nil, the given user name, password, and (optionally) account are used to log in. See the description of FTP#login on page 484.
open | FTP.open( host, user=nil, passwd=nil, acct=nil) -> aSession
 | A synonym for FTP.new, but with a mandatory host parameter.

instance methods
Server commands aSession.acct( account )
aSession.chdir( dir )
aSession.delete( remoteFile )
aSession.mdtm( remoteFile ) -> aString
aSession.mkdir( dir )
aSession.nlst( dir=nil ) -> anArray
aSession.rename( fromname, toname )
aSession.rmdir( dir )
aSession.pwd -> aString
aSession.size( remoteFile ) -> anInteger
aSession.status -> aString
aSession.system -> aString

Issues the corresponding server command and returns the result.

close | aSession.close

Closes the current connection.

closed? | aSession.closed? -> true or false

Returns true if the current connection is closed.

connect | aSession.connect( host, port=FTP_PORT )

Establishes an FTP connection to host, optionally overriding the default port. If the environment variable SOCKS_SERVER is set, sets up the connection through a SOCKS proxy. Raises an exception (typically Errno::ECONNREFUSED) if the connection cannot be established.

debug_mode | aSession.debug_mode -> true or false

Returns the current debug mode.

debug_mode= | aSession.debug_mode = true or false

If the debug mode is true, all traffic to and from the server is written to $stdout.

dir aSession.dir( [pattern]* ) -> anArray
aSession.dir( [pattern]* ) {| line | block }

Synonym for FTP#list.

getbinaryfile aSession.getbinaryfile( remotefile, localfile, blocksize, callback=nil)
aSession.getbinaryfile( remotefile, localfile, blocksize ) {| data | block }

Retrieves remotefile in binary mode, storing the result in localfile. If callback or an associated block is supplied, calls it, passing in the retrieved data in blocksize chunks.

gettextfile aSession.gettextfile( remotefile, localfile, callback=nil)
aSession.gettextfile( remotefile, localfile ) {| data | block }

Retrieves remotefile in ASCII (text) mode, storing the result in localfile. If callback or an associated block is supplied, calls it, passing in the retrieved data one line at a time.

lastresp | aSession.lastresp -> aString

Returns the host's last response.

list aSession.list( [pattern]* ) -> anArray
aSession.list( [pattern]* ) {| line | block }

Fetches a directory listing of files matching the given pattern(s). If a block is associated with the call, invokes it with each line of the result. Otherwise, returns the result as an array of strings.

login | aSession.login( user="anonymous", passwd=nil, acct=nil ) -> aString

Logs into the remote host. aSession must have been previously connected. If user is the string ``anonymous'' and the password is nil, a password of user@host is synthesized. If the acct parameter is not nil, an FTP ACCT command is sent following the successful login. Raises an exception on error (typically Net::FTPPermError).

ls aSession.ls( [pattern]* ) -> anArray
aSession.ls( [pattern]* ) {| line | block }

Synonym for FTP#list.

mtime | aSession.mtime( remoteFile, local=false ) -> aTime

Returns the last-modified time of remoteFile, interpreting the server's response as a GMT time if local is false, or as a local time otherwise.

passive | aSession.passive -> true or false

Returns the state of the passive flag.

passive= | aSession.passive = true or false

Puts the connection into passive mode if true.

putbinaryfile aSession.putbinaryfile( localfile, remotefile, blocksize, callback=nil)
aSession.putbinaryfile( localfile, remotefile, blocksize ) {| data | block }

Transfers localfile to the server in binary mode, storing the result in remotefile. If callback or an associated block is supplied, calls it, passing in the transmitted data in blocksize chunks.

puttextfile aSession.puttextfile( localfile, remotefile, callback=nil)
aSession.puttextfile( localfile, remotefile, blocksize ) {| data | block }

Transfers localfile to the server in ASCII (text) mode, storing the result in remotefile. If callback or an associated block is supplied, calls it, passing in the transmitted data one line at a time.

resume | aSession.resume -> true or false

Returns the status of the resume flag (see FTP#resume=). Default is false.

resume= | aSession.resume=aBoolean

Sets the status of the resume flag. When resume is true, partially received files will resume where they left off, instead of starting from the beginning again. This is done by sending a REST command (RESTart incomplete transfer) to the server.

retrbinary aSession.retrbinary( cmd, blocksize ) {| data | block }

Puts the connection into binary (image) mode, issues the given command, and fetches the data returned, passing it to the associated block in chunks of blocksize characters. Note that cmd is a server command (such as ``RETR myfile'').

retrlines aSession.retrlines(cmd) {| line | block }

Puts the connection into ASCII (text) mode, issues the given command, and passes the resulting data, one line at a time, to the associated block. If no block is given, prints the lines. Note that cmd is a server command (such as ``RETR myfile'').

return_code | aSession.return_code -> aFixnum

Returns the return code from the last operation.

storbinary aSession.storbinary( cmd, fileName, blocksize, callback=nil)
aSession.storbinary( cmd, fileName, blocksize ) {| data | block }

Puts the connection into binary (image) mode, issues the given server-side command (such as ``STOR myfile''), and sends the contents of the file named fileName to the server. If the optional block is given, or if the callBack parameter is a Proc, also passes it the data, in chunks of blocksize characters.

storlines aSession.storlines( cmd, fileName, callback=nil)
aSession.storlines( cmd, fileName ) {| data | block }

Puts the connection into ASCII (text) mode, issues the given server-side command (such as ``STOR myfile''), and sends the contents of the file named fileName to the server, one line at a time. If the optional block is given, or if the callBack parameter is a Proc, also passes it the lines.

welcome | aSession.welcome -> aString

Returns the host's welcome message.

class Net::HTTP
Parent: | Net::Protocol
Version: | 1.6

Index:

new port start get head post start

require 'net/http'

h = Net::HTTP.new('www.pragmaticprogrammer.com', 80)
resp, data = h.get('/index.html', nil )
puts "Code = #{resp.code}"
puts "Message = #{resp.message}"
resp.each {|key, val| printf "%-14s = %-40.40s\n", key, val }
p data[0..55]
produces:
Code = 200
Message = OK
last-modified  = Wed, 29 May 2002 11:08:01 GMT
connection     = close
content-type   = text/html
etag           = "804d98-255c-3cf4b691"
date           = Sun, 09 Jun 2002 05:15:10 GMT
server         = Rapidsite/Apa/1.3.20 (Unix) FrontPage/4.
content-length = 9564
accept-ranges  = bytes
"<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0 Transitional"

The net/http library provides a simple client to fetch headers and Web page contents using the HTTP protocol.

The get, post, and head requests raise exceptions on any error, including some HTTP status responses that would normally be considered recoverable. There are two ways of handling these.

  1. Each method has a corresponding version get2, post2, or head2 that does not raise an exception. These versions are documented in the source.
  2. Recoverable errors raise a Net::ProtoRetriableError exception. This exception contains a data attribute containing the response returned by the server.

The code below illustrates the handling of an HTTP status 301, a redirect. It uses Tomoyuki Kosimizu's URI package, available in the RAA.

h = Net::HTTP.new(ARGV[0] || 'www.ruby-lang.org', 80)
url = ARGV[1] || '/'

begin
  resp, data = h.get(url, nil) { |a| }
rescue Net::ProtoRetriableError => detail
  head = detail.data

  if head.code == "301"
    uri = URI.create(head['location'])

    host = uri['host']
    url  = uri['path']
    port = uri['port']

    h.finish
    h = Net::HTTP.new(host, port)

    retry
  end
end

class methods
new | Net::HTTP.new( host='localhost', port=80, proxy=nil, proxy_port=nil ) -> aSession

Creates and returns a new HTTP object. No connection is made until HTTP#start is called.

port | Net::HTTP.port -> aFixnum

Returns the default HTTP port (80).

start Net::HTTP.start( host=nil, port=80 )
Net::HTTP.start( host=nil, port=80 ) {| aSession | block }

Equivalent to Net::HTTP.new(host, port).start.

instance methods
get aSession.get( path, headers=nil, dest="" ) -> anArray
aSession.get( path, headers=nil) {| result | block }

-> anArray

Retrieves headers and content from the specified path on the host specified when aSession was created. If specified, the headers parameter is a Hash containing additional header names and values to be sent with the request. The method returns a two-element array. The first element is an HTTPResponse object (documented in the next section). The second element is the page's content. The page's content is also passed to the << method of the dest parameter, or to the block if specified. This result is built network block by network block, not line by line. An exception is raised if an error is encountered. Multiple get calls may be made on aSession. Unless Protocol#finish is explicitly called, the connection will use the HTTP/1.1 keep-alive protocol, and will not close between requests.

head aSession.head( path, headers=nil ) -> aHash

Retrieves headers from the specified path on the host specified when aSession was created. If specified, the headers parameter is a hash containing additional header names and values to be sent with the request. The method returns a hash of received headers. An exception is raised if an error is encountered. Multiple head calls may be made on aSession.

post aSession.post( path, data, headers=nil, dest="" ) -> anArray
aSession.post( path, data, headers=nil ) {| result | block } -> anArray

Sends data to path using an HTTP POST request. headers is a hash containing additional headers. Assigns the result to data or to the block, as for Net_HTTP#get. Returns a two-element array containing an HTTPResponse object and the reply body.

start aSession.start
aSession.start {| aSession | block }

Establishes a connection to the host associated with aSession. (start is actually a method in Net::Protocol, but its use is required in HTTP objects.) In the block form, closes the session at the end of the block.

class Net::HTTPResponse
Parent: |
Version: | 1.6

Index:

[ ] [ ]= code each key? message

Represents an HTTP response to a GET or POST request.

instance methods
[ ] | aSession[ aKey ] -> aString
 | Returns the header corresponding to the case-insensitive key. For example, a key of ``Content-type'' might return ``text/html''.
[ ]= | aSession[ aKey ] = aString
 | Sets the header corresponding to the case-insensitive key.
code | aSession.code -> aString
 | Returns the result code from the request (for example, ``404'').
each | aSession.each {| key, val | block }
 | Iterates over all the header key-value pairs.
key? | aSession.key?( aKey ) -> true or false
 | Returns true only if a header with the given key exists.
message | aSession.message -> aString
 | Returns the result message from the request (for example, ``Not found'').

class Net::POP
Parent: | Net::Protocol
Version: | 1.6

Index:

new each finish mails start

require 'net/pop'
pop = Net::POP3.new('server.ruby-stuff.com')
pop.start('user', 'secret') do |pop|
  msg = pop.mails[0]

  # Print the 'From:' header line
  puts msg.header.split("\r\n").grep(/^From: /)

  # Put message to $stdout (by calling <<)
  puts "\nFull message:\n"
  msg.all($stdout)
end
produces:
From: dummy msg for Andy

Full message:
From: dummy msg for Andy
looks-better: on dave's box

That's all folks!

The net/pop library provides a simple client to fetch and delete mail on a Post Office Protocol (POP) server.

The class Net::POP3 is used to access a POP server, returning a list of Net::POPMail objects, one per message stored on the server. These POPMail objects are then used to fetch and/or delete individual messages. The library also provides an alternative to the POP3 class that performs APOP authentication.

class methods
new | HTTP.new( host='localhost', port=110 ) -> aSession
 | Creates and returns a new POP3 object. No connection is made until POP3#start is called.

instance methods
each aSession.each {| popmail | block }

Calls the associated block once for each e-mail stored on the server, passing in the corresponding POPMail object.

finish | aSession.finish -> true or false

Closes the pop connection. Some servers require that a connection is closed before they honor actions such as deleting mail. Returns false if the connection was never used.

mails | aSession.mails -> anArray

Returns an array of POPMail objects, where each object corresponds to an e-mail message stored on the server.

start aSession.start( user, password )
aSession.start( user, password ) {| pop | block }

Establishes a connection to the pop server, using the supplied username and password. Fetches a list of mail held on the server, which may be accessed using the POP3#mails and POP3#each methods. In block form, passes aSession to the block, and closes the connection using finish when the block terminates.

class Net::APOP
Parent: | Net::POP3
Version: | 1.6

Index:

start

instance methods
start | aSession.start( user, password )
 | Establishes a connection to the APOP server.

class Net::POPMail
Parent: | Object
Version: | 1.6

Index:

all delete delete! header size top uidl

instance methods
all aSession.all -> aString
aSession.all( dest )
aSession.all {| aString | block }

Fetches the corresponding e-mail from the server. With no argument or associated block, returns the e-mail as a string. With an argument but no block, appends the e-mail to dest by invoking dest << for each line in the e-mail. With an associated block, invokes the block once for each line in the e-mail.

delete | aSession.delete

Deletes the e-mail from the server.

delete! | aSession.delete!

Synonym for POPMail#delete.

header | aSession.header -> aString

Returns the header lines for the corresponding e-mail message.

size | aSession.size -> aFixnum

Returns the size in bytes of the corresponding e-mail.

top | aSession.top( lines ) -> aString

Returns the header lines, plus lines message lines for the corresponding e-mail message.

uidl | aSession.uidl -> aString

Returns the server-specific unique identifier for the corresponding e-mail.

class Net::SMTP
Parent: | Net::Protocol
Version: | 1.6

Index:

new start ready sendmail start

require 'net/smtp'

# --- Send using class methods
msg = [ "Subject: Test\n", "\n", "Now is the time\n" ]
Net::SMTP.start do |smtp|
  smtp.sendmail( msg,  'dave@localhost', ['dave'] )
end

# --- Send using SMTP object and an adaptor
smtp = Net::SMTP.new
smtp.start('pragprog.com')
smtp.ready('dave@localhost', 'dave') do |a|
  a.write "Subject: Test1\r\n"
  a.write "\r\n"
  a.write "And so is this"
end

The net/smtp library provides a simple client to send electronic mail using the Simple Mail Transfer Protocol (SMTP).
class methods
new Net::SMTP.new( server='localhost', port=25 ) -> aSession

Returns a new SMTP object connected to the given server and port.

start Net::SMTP.start( server='localhost', port=25, domain=ENV['HOSTNAME'], acct=nil, passwd=nil, authtype=:cram_md5 ) -> aSession
Net::SMTP.start( server='localhost', port=25, domain=ENV['HOSTNAME'], acct=nil, passwd=nil, authtype=:cram_md5 ) {| smtp | block }

Equivalent to Net::SMTP.new(server, port).start(...). For an explanation of the remainder of the parameters, see the instance method Net_SMTP#start. Creates a new SMTP object. The domain parameter will be used in the initial HELO or EHLO transaction with the SMTP server. In the block form, the smtp object is passed into the block. When the block terminates, the session is closed.

instance methods
ready aSession.ready( from, to ) {| anAdaptor | block }

Equivalent to sendmail(from, to) { ...}. Sends header and body lines to the sendmail server. The from parameter is used as the sender's name in the MAIL FROM: command, and the to is either a string or an array of strings containing the recipients for the RCPT TO: command. The block is passed an adaptor object. Lines are sent to the server by calling the adaptor's write method. The terminating '.' and QUIT are sent automatically.

sendmail aSession.sendmail( src, from, to )

Sends header and body lines to the sendmail server. The from parameter is used as the sender's name in the MAIL FROM: command, and to is either a string or an array of strings containing the recipients for the RCPT TO: command. Lines to be sent are fetched by invoking src .each. The terminating '.' and QUIT are sent automatically.

start aSession.start( domain=ENV['HOSTNAME'], acct=nil, passwd=nil, authtype=:cram_md5 ) -> true or false
aSession.start( domain=ENV['HOSTNAME'], acct=nil, passwd=nil, authtype=:cram_md5 ) {| smtp | block } -> true or false

Starts an SMTP session by connecting to the given domain (host). If acct and passwd are given, authentication will be attempted using the given authentication type (:plain or :cram_md5). If a block is supplied, it will be invoked with aSession as a parameter. The connection will be closed when the block terminates.

class Net::Telnet
Parent: | [Socket]
Version: | 1.6

Index:

new binmode binmode= cmd login print telnetmode telnetmode= waitfor write

Connect to a localhost, run the ``date'' command, and disconnect.

require 'net/telnet'
tn = Net::Telnet.new({})
tn.login "guest", "secret"
tn.cmd "date" » "date\r\nSun Jun  9 00:15:20 CDT 2002\n\r> "

Monitor output as it occurs. We associate a block with each of the library calls; this block is called whenever data becomes available from the host.

require 'net/telnet'

tn = Net::Telnet.new({})     { |str| print str }
tn.login("guest", "secret")  { |str| print str }
tn.cmd("date")  { |str| print str }
produces:
Trying localhost...
Connected to localhost.
Welcome to SuSE Linux 7.1 (i386) - Kernel 2.4.0-64GB-SMP (8).

zip login: guest
Password:
Last login: Sun Jun  9 00:15:19 from localhost
/etc/zshrc: setopt: no such option: histexpiredupsfirst [31]

> date
Sun Jun  9 00:15:20 CDT 2002

>

Get the time from an NTP server.

require 'net/telnet'
tn = Net::Telnet.new('Host'       => 'time.nonexistent.org',
                     'Port'       => 'time',
                     'Timeout'    => 60,
                     'Telnetmode' => false)
atomicTime = tn.recv(4).unpack('N')[0]
puts "Atomic time: " + Time.at(atomicTime - 2208988800).to_s
puts "Local time:  " + Time.now.to_s
produces:
Atomic time: Sun Jun 09 00:15:12 CDT 2002
Local time:  Sun Jun 09 00:15:20 CDT 2002

The net/telnet library provides a complete implementation of a telnet client and includes features that make it a convenient mechanism for interacting with non-telnet services.

Although the class description that follows indicates that Net::Telnet is a subclass of class Socket, this is a lie. In reality, the class delegates to Socket. The net effect is the same: the methods of Socket and its parent, class IO, are available through Net::Telnet objects.

The methods new, cmd, login, and waitfor take an optional block. If present, the block is passed output from the server as it is received by the routine. This can be used to provide realtime output, rather than waiting for (for example) a login to complete before displaying the server's response.

class methods
new Net::Telnet.new( options ) -> aSession
Net::Telnet.new( options ) {| str | block } -> aSession

Connects to a server. options is a Hash with zero or more of the following:

Option | Default | Meaning
Binmode | false | If true, no end-of-line processing will be performed.
Host | localhost | Name or address of server's host.
Port | 23 | Name or number of service to call.
Prompt | /[$%#>]/ | Pattern that matches the host's prompt.
Telnetmode | true | If false, ignore the majority of telnet embedded escape sequences. Used when talking with a non-telnet server.
Timeout | 10 | Time in seconds to wait for a server response (both during connection and during regular data transmission).
Waittime | 0 | Time to wait for prompt to appear in received data stream.

instance methods
binmode | aSession.binmode -> true or false

Returns the current value of the Binmode flag.

binmode= | aSession.binmode = true or false

Sets the Binmode flag, returning the new value.

cmd aSession.cmd( options ) -> aString
aSession.cmd( options ) {| str | block } -> aString

Sends a string to the server and waits (using a timeout) for a string that matches a pattern to be returned by the server. If the parameter is not a Hash, it is sent as a string to the server, and the pattern to match and the timeout are the Prompt and Timeout options given when aSession was created. If options is a Hash, then options['String'] is sent to the server. options['Match'] may be used to override the class Prompt parameter, and options['Timeout'] the timeout. The method returns the complete server response.

login aSession.login( options, password=nil ) -> aString
aSession.login( options, password=nil ) {| str | block } -> aString

If options is a Hash, a username is taken from options['Name'] and a password from options['Password']; otherwise, options is assumed to be the username, and password the password. The method waits for the server to send the string matching the pattern /login[: [visible space]]*\z/ and sends the username. If a password is given, it then waits for the server to send /Password[: [visible space]]*\z/ and sends the password. The method returns the full server response.

print | aSession.print( aString )

Sends aString to the server, honoring Telnetmode, Binarymode, and any additional modes negotiated with the server.

telnetmode | aSession.telnetmode -> true or false

Returns the current value of the Telnetmode flag.

telnetmode= | aSession.telnetmode= true or false

Sets the Telnetmode flag, returning the new value.

waitfor aSession.waitfor( options ) -> aString
aSession.waitfor( options ) {| str | block } -> aString

Waits for the server to respond with a string that matches a string or pattern. If options is not a Hash, it is compared against the cumulative server output as that output is received using options. ===. It is likely that you will want to use a regular expression in this case.

If options is a Hash, then options['Match'], options['Prompt'], or options['String'] provides the match. In the latter case, the string will be converted to a regular expression before being used. options may also include the keys ``Timeout'' and ``Waittime'' to override the class options of the same names.

write | aSession.write( aString )

Writes aString to the server with no translation.

CGI Development

class CGI
Parent: | Object
Version: | 1.6

Index:

escape escapeElement escapeHTML new parse pretty rfc1123_date unescape unescapeElement unescapeHTML [ ] cookies has_key? header keys out params

require "cgi"
cgi = CGI.new("html3")  # add HTML generation methods
cgi.out {
  CGI.pretty (
    cgi.html {
      cgi.head { cgi.title{"TITLE"} } +
      cgi.body {
        cgi.form {
          cgi.textarea("get_text") +
          cgi.br +
          cgi.submit
        } +
        cgi.h1 { "This is big!" } +
        cgi.center { "Jazz Greats of the 20" +
          cgi.small {"th"} + " century" + cgi.hr
        } + cgi.p + cgi.table ('BORDER' => '5') {
          cgi.tr { cgi.td {"Artist"} + cgi.td {"Album"} } +
          cgi.tr { cgi.td {"Davis, Miles"} +
          cgi.td {"Kind of Blue"} }
        }
      }
    }
  ) # CGI.pretty is a method call, not a block
}

(The output of this script is shown in Figure 26.2 on page 499.)

The CGI class provides support for programs used as a Web server CGI (Common Gateway Interface) script. It contains several methods for accessing fields in a CGI form, manipulating ``cookies'' and the environment, and outputting formatted HTML.

Since environment variables contain a lot of useful information for a CGI script, CGI makes accessing them very easy---environment variables are accessible as attributes of CGI objects. For instance, cgi.auth_type returns the value of ENV["AUTH_TYPE"]. To create the method name, the environment variable name is translated to all lowercase, and the ``HTTP_'' prefix is stripped off. Thus, HTTP_USER_AGENT would be available as the method user_agent.

Cookies are represented using a separate object of class CGI::Cookie, containing the following accessors:
Accessor | Description
name | Name of this cookie
value | Array of values
path | Path (optional)
domain | Domain (optional)
expires | Time of expiry, defaults to Time.now (optional)
secure | true for a secure cookie

Figure not available...

You create a cookie object using CGI_Cookie.new, which takes as arguments the accessors listed above, or CGI_Cookie.parse, which takes an encoded string and returns a cookie object.

class methods
escape | CGI.escape( aString ) -> aNewString

Returns a URL-encoded string made from the given argument, where unsafe characters (not alphanumeric, ``_'', ``-'', or ``.'') are encoded using ``%xx'' escapes.

escapeElement | CGI.escapeElement( aString [, elements ]* ) -> aNewString

Returns a string made from the given argument with certain HTML-special characters escaped. The HTML elements given in elements will be escaped; other HTML elements will not be affected.

print CGI::escapeElement('<BR><A HREF="url"></A><P>', "A", "IMG")
produces:
<BR>&lt;A HREF=&quot;url&quot;&gt;&lt;/A&gt;<P>

escapeHTML | CGI.escapeHTML( aString ) -> aNewString

Returns a string made from the given argument with HTML-special characters (such as ``&'',``"'',``<'',``>'') quoted using ``&amp;'', ``&quot;'', ``&lt;'', ``&gt;'', and so on.

new | CGI.new( [ aString ]* ) -> aSession

Returns a new CGI object. If HTML output is required, the desired standards level must be given in aString (otherwise, no output routines will be created). The level may be one of:

String | Standards Level | String | Standards Level
``html3'' | HTML 3.2 | ``html4'' | HTML 4.0 Strict
``html4Tr'' | HTML 4.0 Transitional | ``html4Fr'' | HTML 4.0 Frameset
parse | CGI.parse( aString ) -> aHash

Parses a query string and returns a hash of its key-value pairs.

pretty | CGI.pretty( anHTMLString, aLeaderString=" " ) -> aSession

Formats the given anHTMLString in a nice, readable format, optionally prefixing each line with aLeaderString.

rfc1123_date | CGI.rfc1123_date( aTime ) -> aString

Returns a string representing the given time according to RFC 1123 (for instance, Mon, 1 Jan 2001 00:00:00 GMT).

unescape | CGI.unescape( aString ) -> aNewString

Returns a string containing ``unsafe'' characters made from the given URL-encoded argument, where unsafe characters were encoded using ``%'' escapes.

unescapeElement | CGI.unescapeElement( aString [, elements ]* ) -> aNewString

Returns a string with the selected escaped HTML elements expanded to the actual characters.

unescapeHTML | CGI.unescapeHTML( aString ) -> aNewString

Returns a string made from the given argument with HTML-special quoted characters expanded to the actual characters.

instance methods
[ ] aSession[ [ aString ]+ ] -> anArray

Returns the values of the given field names from the CGI form in an Array. See the note on multipart forms on page 503.

cookies | aSession.cookies -> aHash

Returns a new Hash object containing key-value pairs of cookie keys and values.

has_key? | aSession.has_key( aString ) -> true or false

Returns true if the form contains a field named aString.

header aSession.header( aContentType="text/html" ) -> aString
aSession.header( aHash ) -> aString

Returns a string containing the given headers (in the MOD_RUBY environment, the resulting header is sent immediately instead). If a hash is given as an argument, then the key-value pairs will be used to generate headers.

keys | aSession.keys -> anArray

Returns an array of all existing field names for the form.

out aSession.out( aContentType="text/html" ) { block } -> nil
aSession.out( aHash ) { block } -> nil

Generates HTML output using the results of the block as the content. Headers are generated as with CGI#header. See the example at the start of this section.

params | aSession.params -> aHash

Returns a new Hash object containing key-value pairs of field names and values from the form.

HTML Output Methods

In addition, CGI supports the following HTML output methods. Each of these methods is named after the corresponding HTML feature (or close to it). Those tags that require content (such as blockquote) take an optional block; the block should return a String that will be used as the content for the feature. These methods may take arguments as indicated, or as a hash with the given names as keys.

\

a( url )
a( HREF => )

base( url )
base( HREF => )

blockquote( cite="" ) { aString }
blockquote( CITE => ) { aString }

caption( align=nil ) { aString }
caption( ALIGN => ) { aString }

checkbox( name=nil, value=nil, checked=nil )
checkbox( NAME, VALUE, CHECKED => )

checkbox_group( name=nil, [ items ]+ )
checkbox_group( NAME, VALUES => )

Items may be individual String names, or any of: an array of [ name, checked ], an array of [ value, name ], or an array of [ value, name, checked ]. The value for the hash key VALUES should be an array of these items.

file_field( name="", size=20, maxlength=nil )
file_field( NAME, SIZE, MAXLENGTH => )

form( method="post", action=nil, enctype="application/x-www-form-urlencoded" ) { aStr }
form( METHOD, ACTION, ENCTYPE => ) { aStr }

hidden( name="", value=nil )
hidden( NAME, VALUE => )

html( ) { aString }
html( PRETTY, DOCTYPE => ) { aString }

img_button( src="", name=nil, alt=nil )
img_button( SRC, NAME, ALT => )

img( src="", alt="", width=nil, height=nil )
img( SRC, ALT, WIDTH, HEIGHT => )

multipart_form( action=nil, enctype="multipart/form-data" ) { aString }
multipart_form( METHOD, ACTION, ENCTYPE => ) { aString }

password_field( name="", value=nil, size=40, maxlength=nil )
password_field( NAME, VALUE, SIZE, MAXLENGTH => )

popup_menu( name="", items )
popup_menu( NAME, SIZE, MULTIPLE, VALUES (array of items) => )

Items may be individual String names, or any of: an array of [ name, selected ], an array of [ value, name ], or an array of [ value, name, selected ]. The value for the hash key VALUES should be an array of these items.

radio_button( name="", value=nil, checked=nil )
radio_button( NAME, VALUE, CHECKED => )

radio_group( name="", items )
radio_group( NAME, VALUES (array of items) => )

Items may be individual String names, or any of: an array of [ name, selected ], an array of [ value, name ], or an array of [ value, name, selected ]. The value for the hash key VALUES should be an array of these items.

reset( value=nil, name=nil )
reset( VALUE, NAME => )

scrolling_list( alias for popup_menu )
scrolling_list( => )

submit( value=nil, name=nil )
submit( VALUE, NAME => )

text_field( name="", value=nil, size=40, maxlength=nil )
text_field( NAME, VALUE, SIZE, MAXLENGTH => )

textarea( name="", cols=70, rows=10 )
textarea( NAME, COLS, ROWS => )

\

In addition, all HTML tags are supported as methods, including title, head, body, br, pre, and so on. The block given to the method must return a String, which will be used as the content for that tag type. Not all tags require content: <P>, for example, does not. The available tags vary according to the supported HTML level---Table 26.1 on page 503 lists the complete set. For these methods, you can pass in a hash with attributes for the given tag. For instance, you might pass in 'BORDER'=>'5' to the table method to set the border width of the table.
HTML tags available as methods
{HTML 3}
a address applet area b base basefont big blockquote body br caption center cite code dd dfn dir div dl dt em font form h1 h2 h3 h4 h5 h6 head hr html i img input isindex kbd li link listing map menu meta ol option p param plaintext pre samp script select small strike strong style sub sup table td textarea th title tr tt u ul var xmp
{HTML 4}
a abbr acronym address area b base bdo big blockquote body br button caption cite code col colgroup dd del dfn div dl dt em fieldset form h1 h2 h3 h4 h5 h6 head hr html i img input ins kbd label legend li link map meta noscript object ol optgroup option p param pre q samp script select small span strong style sub sup table tbody td textarea tfoot th thead title tr tt ul var
{HTML 4 Transitional}
a abbr acronym address applet area b base basefont bdo big blockquote body br button caption center cite code col colgroup dd del dfn dir div dl dt em fieldset font form h1 h2 h3 h4 h5 h6 head hr html i iframe img input ins isindex kbd label legend li link map menu meta noframes noscript object ol optgroup option p param pre q s samp script select small span strike strong style sub sup table tbody td textarea tfoot th thead title tr tt u ul var
frame frameset

Multipart Form Values

When dealing with a multipart form, the array returned by CGI#[] is composed of objects of class Tempfile, with the following dynamically added methods:
Method | Description
read | Body
local_path | Path to local file containing the content
original_filename | Original filename of the content
content_type | Content type

class CGI::Session
Parent: | Object
Version: | 1.6

Index:

new [ ] [ ]= delete update

A CGI::Session maintains a persistent state for web users in a CGI environment. Sessions may be memory-resident or may be stored on disk. See the discussion on page 146 for details.

class methods
new CGI::Session.new( aCgi, [ aHash ]* ) -> aSession

Returns a new session object for the CGI query. Options that may be given in aHash include:

Option | Description
session_key | Name of CGI key for session identification.
session_id | Value of session id.
new_session | If true, create a new session id for this session. If false, use an existing session identified by session_id. If omitted, use an existing session if available, otherwise create a new one.
database_manager | Class to use to save sessions; may be CGI::Session::FileStore or CGI::Session::MemoryStore (or user defined if you're brave). Default is FileStore.
tmpdir | For FileStore, directory for session files.
prefix | For FileStore, prefix of session filenames.

instance methods
[ ] | aSession[ aKey ] -> aValue
 | Returns the value for the given key.
[ ]= | aSession[ aKey ] = aValue -> aValue
 | Sets the value for the given key.
delete | aSession.delete
 | Calls the delete method of the underlying database manager. For FileStore, deletes the physical file containing the session. For MemoryStore, removes the session from memory.
update | aSession.update
 | Calls the update method of the underlying database manager. For FileStore, writes the session data out to disk. Has no effect with MemoryStore.

Extracted from the book "Programming Ruby - The Pragmatic Programmer's Guide"
Copyright © 2001 by Addison Wesley Longman, Inc. This material may be distributed only subject to the terms and conditions set forth in the Open Publication License, v1.0 or later (the latest version is presently available at http://www.opencontent.org/openpub/)).

Distribution of substantively modified versions of this document is prohibited without the explicit permission of the copyright holder.

Distribution of the work or derivative of the work in any standard (paper) book form is prohibited unless prior permission is obtained from the copyright holder.


Programming Ruby: The Pragmatic Programmer's Guide

Programming Ruby

The Pragmatic Programmer's Guide

Previous < Contents ^
Next >

Microsoft Windows Support

The three libraries documented in this chapter turn Ruby into a powerful and convenient Windows scripting language. Now you have the power to control your applications, but in a controlled, object-oriented environment.

class WIN32OLE
Parent: | Object
Version: | 1.6

Index:

connect const_load new [ ] [ ]= each invoke

require 'win32ole'
ie = WIN32OLE.new('InternetExplorer.Application')
ie.visible = true
ie.gohome

WIN32OLE provides a client interface to Windows 32 OLE Automation servers. See the tutorial description on page 164 for more information.

constants
WIN32OLE::VERSION Current version number

class methods
connect | WIN32OLE.connect( aString ) -> wapi
 | Returns a new OLE automation client connected to an existing instance of the named automation server.
const_load | WIN32OLE.const_load( wapi, [ aClass=WIN32OLE ] ) -> nil
 | Defines the constants from the specified automation server as class constants in aClass.
new | WIN32OLE.new( aString ) -> wapi
 | Returns a new OLE automation client connected to a new instance of the automation server named by aString.

instance methods
[ ] | wapi[ aString ] -> anObject
 | Returns the named property from the OLE automation object.
[ ]= | wapi[ aString ] = aValue -> nil
 | Sets the named property in the OLE automation object.
each | wapi.each {| anObj | block } -> nil
 | Iterates over each item of this OLE server that supports the IEnumVARIANT interface.
invoke | wapi.invoke ( aCmdString, [ args ]* ) -> anObject
 | Invokes the command given in aCmdString with the given args. args may be a Hash of named parameters and values. You don't need to call invoke explicitly; this class uses method_missing to forward calls through invoke, so you can simply use the OLE methods as methods of this class.

class WIN32OLE_EVENT
Parent: | Object
Version: | 1.6

Index:

message_loop new on_event

This (slightly modified) example from the Win32OLE 0.1.1 distribution shows the use of an event sink.

require 'win32ole'

$urls = []

def navigate(url)
  $urls << url
end

def stop_msg_loop
  puts "IE has exited..."
  throw :done
end

def default_handler(event, *args)
  case event
  when "BeforeNavigate"
    puts "Now Navigating to #{args[0]}..."
  end
end

ie = WIN32OLE.new('InternetExplorer.Application')
ie.visible = TRUE
ie.gohome
ev = WIN32OLE_EVENT.new(ie, 'DWebBrowserEvents')

ev.on_event {|*args| default_handler(*args)}
ev.on_event("NavigateComplete") {|url| navigate(url)}
ev.on_event("Quit") {|*args| stop_msg_loop}

catch(:done) {
  loop {
    WIN32OLE_EVENT.message_loop
  }
}

puts "You Navigated to the following URLs: "
$urls.each_with_index do |url, i|
  puts "(#{i+1}) #{url}"
end

WIN32OLE_EVENT is used in conjunction with the WIN32OLE class to add callbacks for Windows 32 events.
class methods
message_loop | WIN32OLE_EVENT.message_loop -> nil
 | Executes the Windows event loop, translating and dispatching events.
new | WIN32OLE_EVENT.new ( anOle, aName ) -> wapi
 | Returns a new WIN32OLE_EVENT (an event sink) for the given WIN32OLE object and named event source. If aName is nil, it will attempt to use the default source and will raise a RuntimeError if it cannot find one.

instance methods
on_event | wapi.on_event ( [ anEvent ] ) {| args | block } -> nil
 | Defines a callback for the named anEvent. If anEvent is nil, then this callback is associated with all events. The block will be given any arguments appropriate for this event.

class Win32API
Parent: | Object
Version: | 1.6

Index:

new call Call

This example is from the Ruby distribution, in ext/Win32API:

require 'Win32API'

getCursorPos = Win32API.new("user32", "GetCursorPos", ['P'], 'V')

lpPoint = " " * 8 # store two LONGs
getCursorPos.Call(lpPoint)
x, y = lpPoint.unpack("LL") # get the actual values

print "x: ", x, "\n"
print "y: ", y, "\n"

ods = Win32API.new("kernel32", "OutputDebugString", ['P'], 'V')
ods.Call("Hello, World\n")

GetDesktopWindow = Win32API.new("user32", "GetDesktopWindow", [], 'L')
GetActiveWindow = Win32API.new("user32", "GetActiveWindow", [], 'L')
SendMessage = Win32API.new("user32", "SendMessage", ['L'] * 4, 'L')
SendMessage.Call(GetDesktopWindow.Call, 274, 0xf140, 0)

The Win32API module allows access to any arbitrary Windows 32 function. Many of these functions take or return a Pointer datatype---a region of memory corresponding to a C string or structure type.

In Ruby, these pointers are represented using class String, which contains a sequence of 8-bit bytes. It is up to you to pack and unpack the bits in the String. See the reference section for unpack on page 378 and pack on page 286 for details.
class methods
new | Win32API.new( dllname, procname, importArray, export ) -> wapi
 | Returns a new object representing a Windows 32 API function. dllname is the name of the DLL containing the function, such as ``user32'' or ``kernel32.'' procname is the name of the desired function. importArray is an array of strings representing the types of arguments to the function. export is a string representing the return type of the function. Strings ``n'' and ``l'' represent numbers, ``i'' represent integers, ``p'' represents pointers to data stored in a string, and ``v'' represents a void type (used for export parameters only). These strings are case-insensitive.

instance methods
call | wapi.call( [ args ]* ) -> anObject
 | Calls this API function with the given arguments, which must match the signature specified to new.
Call | wapi.Call( [ args ]* ) -> anObject
 | Synonym for Win32API#call.

Extracted from the book "Programming Ruby - The Pragmatic Programmer's Guide"
Copyright © 2001 by Addison Wesley Longman, Inc. This material may be distributed only subject to the terms and conditions set forth in the Open Publication License, v1.0 or later (the latest version is presently available at http://www.opencontent.org/openpub/)).

Distribution of substantively modified versions of this document is prohibited without the explicit permission of the copyright holder.

Distribution of the work or derivative of the work in any standard (paper) book form is prohibited unless prior permission is obtained from the copyright holder.


Programming Ruby: The Pragmatic Programmer's Guide

Programming Ruby

The Pragmatic Programmer's Guide

Previous < Contents ^
Next >

Embedded Documentation

Figure not available...

Figure not available...

So you've written a masterpiece, a class in a class of its own, and you'd like to share it with the world. But, being a responsible developer, you feel the need to document your creation. What do you do? The simplest solution is to use Ruby's built-in documentation format, RD, and rdtool, a Ruby utility suite that converts this documentation into a variety of output formats.

rdtool scans a file for =begin and =end{=begin...=end@{=begin pairs, and extracts the text between them all. This text is assumed to be documentation in RD format. The text is then processed according to a simple set of rules:

  • Lines of text flush to the left margin are converted to paragraphs.
  • Lines starting with one to four equals signs are headings. ``='' is a first-level heading, ``=='' a second-level heading, and so on. ``+'' and ``++'' can be used to signal fifth- and sixth-level headings if you really want to go that deep.

    = Top Level Heading
== Second Level Heading
...
  • Lines in which the first nonspace is an asterisk indicate the beginnings of bullet lists. Continuation lines for each bullet item should line up with the text on the first line. Lists may be nested.

    This is normal text
* start of a
  multiline bullet item
* and another
  * nested item
  * second nested
* third item at top level
  • Lines where the first nonspace characters are digits between parentheses indicate numbered lists. The actual digits used are ignored. Again, lists may be nested.

    (1) A numbered item
    * subitem in a bulleted list
    * subitem
(2) Second numbered item
(9) This will actually be labeled '3.'
  • Lines starting with a colon indicate labeled lists. The text on the colon line is the label. The immediately following text (which may not be indented less than the label) is the descriptive text. Again, each type of list may be nested.

    : red
  when the light is red, you
  must stop
: amber
  the amber light means that things are about to change. Either:
  * step on the gas, or
  * slam on the brakes
: green
  green means GO
  • Lines starting with three minus signs are a special kind of labeled list, when the labels are method names and signatures. The source in Figure A.1 on page 512 shows a handful of these in action.

Indented text that isn't part of a list is set verbatim (such as the stuff under ``Synopsis'' in Figures A.1 and A.2).

Inline Formatting

Within blocks of text and headings, you can use special inline sequences to control text formatting. All sequences are nested within a set of double parentheses.

Sequence | Example | Intended Use
((*emphasis*)) | emphasis | Emphasis (normally italic)
(({code stuff})) | code stuff | Code
((|variable|)) | variable | Variable name
((%type me%)) | type me | Keyboard input
((:index term:)) | index term | Something to be indexed
((<reference>)) | reference | Hyperlink reference
((-footnote-)) | text.4 | Footnote text. A reference is placed inline, and the text of the footnote appears at the bottom of the page.
(('verb')) | verb | Verbatim text

Cross References

The content of headings, the labels of labeled lists, and the names of methods are automatically made into potential cross reference targets. You make links to these targets from elsewhere in the document by citing their contents in the ((<...>)) construct.

= Synopsis
...
See ((<Return Codes>)) for details.
..
== Instance Methods

--- Tempfile.open( filename )
    Opens the file...

== Return Codes
..
The method ((<Tempfile.open>)) raises an (({IOException}))...

If a reference starts with ``URL:'', rdtool attempts to format it as an external hyperlink.

The reference ((<display part|label>)) generates a link to label but places the text ``display part'' in the output document. This is used in the description section of the example in Figure A.1 on page 512 to generate references to the method names:

perspective, apart from the unusual ((<(({new}))|Tempfile.new>)),
...

This construct displays the word ``new'' in code font but uses it as a hyperlink to the method Tempfile.new.

Method Names

rdtool makes certain assumptions about the format of method names. Class or module methods should appear as Class.method, instance methods as Class#method, and class or module constants as Class::Const.

--- Tempfile::IOWRITE
    Open the file write-only.
    ...
--- Tempfile.new( filename )
    Constructs a temporary file in the given directory. The file
    ...
--- Tempfile#open
    Reopens ((|aTempfile|)) using mode ``r+'', which allows reading
    ..

Including Other Files

The contents of filename will be inserted wherever the document contains

<<< filename

If the file is specified with an .rd or .rb extension, it will be interpreted as RD documentation.

If the filename has no extension, rdtool will look for a file with an extension that matches the type of output being produced (.html for HTML files, .man for man files, and so on) and interpolate that file's contents in the output stream. Thus, a line such as:

<<< header

could be used to add an output-dependent header to a document.

Using rdtool

RD documentation can be included directly in a Ruby source program or written into a separate file (which by convention will have the extension .rd). These files are processed using the rd2 command to produce appropriately formatted output.

rd2  [
            options
            ]  inputfile   [ >outputfile ]

Some common options include:

-r format | Select an output format. -rrd/rd2html-lib.rb produces HTML output (the default). -rrd/rd2man-lib.rb produces Unix man page output.
-o name | Set the base part of the output filename.
--help | List the full set of options.

Mandatory Disclaimer

As we are writing this, RD and rdtool are undergoing continuous development. It is likely that some of the details we give here will be out of date (or just plain wrong) by the time you read this.

Included with the rdtool distribution is the file README.rd. We suggest you do so, as it will give you the current scoop on producing Ruby documentation.

Previous < Contents ^
Next >

Extracted from the book "Programming Ruby - The Pragmatic Programmer's Guide"
Copyright © 2001 by Addison Wesley Longman, Inc. This material may be distributed only subject to the terms and conditions set forth in the Open Publication License, v1.0 or later (the latest version is presently available at http://www.opencontent.org/openpub/)).

Distribution of substantively modified versions of this document is prohibited without the explicit permission of the copyright holder.

Distribution of the work or derivative of the work in any standard (paper) book form is prohibited unless prior permission is obtained from the copyright holder.


Programming Ruby: The Pragmatic Programmer's Guide

Programming Ruby

The Pragmatic Programmer's Guide

Previous < Contents ^
Next >

Interactive Ruby Shell

Back on page 124 we introduced irb, a Ruby module that lets you enter Ruby programs interactively and see the results immediately. This appendix goes into more detail on using and customizing irb.

Command Line

irb is run from the command line.

irb [
            irb-options
            ] [
            ruby_script
            ] [
            options
            ]

The command-line options for irb are listed in Table B.1 on page 518. Typically, you'll run irb with no options, but if you want to run a script and watch the blow-by-blow description as it runs, you can provide the name of the Ruby script and any options for that script.
irb command-line options
Option | Description
-f | Suppress reading ~/.irbrc.
-m | Math mode (fraction and matrix support is available).
-d | Set $DEBUG to true (same as ``ruby -d'').
-r load-module | Same as ``ruby -r''.
--inspect | Use ``inspect'' for output (the default, unless in math mode).
--noinspect | Do not use inspect for output.
--readline | Use Readline extension module.
--noreadline | Do not use Readline extension module.
--prompt prompt-mode | Switch prompt mode. Predefined prompt modes are ``default'', ``simple'', ``xmp'', and ``inf-ruby''.
--prompt-mode prompt-mode | Same as --prompt.
--inf-ruby-mode | Sets up irb to run in inf-ruby-mode under Emacs. Changes the prompt and suppresses --readline.
--simple-prompt | Simple prompt mode.
--noprompt | Do not display a prompt.
--tracer | Display trace for execution of commands.
--back-trace-limit n | Display backtrace information using the top n and last n entries. The default value is 16.
--irb_debug n | Set internal debug level to n (only for irb development).
-v, --version | Print the version of irb.

Initialization File

irb uses an initialization file in which you can set commonly used options or execute any required Ruby statements. When irb is run, it will try to load an initialization file from one of the following sources in order: ~/.irbrc, .irbrc, irb.rc, _irbrc, and $irbrc.

Within the initialization file you may run any arbitrary Ruby code. You can also set any of the configuration values that correspond to command-line arguments as shown in Table B.2 on page 518.
irb configuration values

IRB.conf[:IRB_NAME] = "irb" | IRB.conf[:MATH_MODE] = false
IRB.conf[:USE_TRACER] = false | IRB.conf[:USE_LOADER] = false
IRB.conf[:IGNORE_SIGINT] = true | IRB.conf[:IGNORE_EOF] = false
IRB.conf[:INSPECT_MODE] = nil | IRB.conf[:IRB_RC] = nil
IRB.conf[:BACK_TRACE_LIMIT] = 16 | IRB.conf[:USE_LOADER] = false
IRB.conf[:USE_READLINE] = nil | IRB.conf[:USE_TRACER] = false
IRB.conf[:IGNORE_SIGINT] = true | IRB.conf[:IGNORE_EOF] = false
IRB.conf[:PROMPT_MODE] = :DEFAULT | IRB.conf[:PROMPT] = { ... }
IRB.conf[:DEBUG_LEVEL] = 0 | IRB.conf[:VERBOSE] = true

As an interesting twist on configuring irb, you can set IRB.conf[:IRB_RC] to a Proc object. This proc will be invoked whenever the irb context is changed, and will receive that new context as a parameter. You can use this facility to change the configuration dynamically based on the context.

Commands

At the irb prompt, you can enter any valid Ruby expression and see the results. You can also use any of the following commands to control the irb session.

exit, quit, irb_exit
Quits this irb session or subsession. If you've used cb to change bindings (see below), exits from this binding mode.

conf, irb_context
Displays current configuration. Modifying the configuration is achieved by invoking methods of conf.

conf.back_trace_limit n
Sets display lines of backtrace as top n and tail n. The default value is 16.

conf.debug_level = N
Sets debug level of irb.

conf.ignore_eof = true/false
Specifies the behavior of an end of file received on input. If true, it will be ignored; otherwise, it will quit irb.

conf.ignore_sigint= true/false
Specifies the behavior of ^C (control-c). If false, ^C will quit irb. If true, ^C during input will cancel input and return to the top level; during execution, ^C will abort the current operation.

conf.inf_ruby_mode = true/false
If true, changes the prompt and disables readline support, allowing irb to work with inf-ruby-mode. [inf-ruby-mode allows Emacs users to interact with Ruby while editing programs. See the file inf_ruby.el in the misc directory of the distribution for more details.] The default value is false.

conf.inspect_mode = true/false/nil
Specifies inspect mode according to the following values:

true | Display inspect (default).
false | Display to_s.
nil | Inspect mode in non-math mode, non-inspect mode in math mode.

conf.irb_level
Displays the current binding level (see cb).

conf.math_mode
Displays whether or not Ruby is in math mode.

conf.use_loader = true/false
Specifies whether or not irb's own file reader method is used with load/require.

conf.prompt_c
The prompt for a continuing statement (for example, immediately after an ``if'').

conf.prompt_i
The standard, top-level prompt.

conf.prompt_s
The prompt for a continuing string.

conf.rc = true/false
Specifies whether or not to use the initialization file ~/.irbrc.

conf.use_prompt = true/false
Specifies whether or not to display prompts.

conf.use_readline = true/false/nil
Specifies whether or not to use Readline according to the following values:

true | Use Readline.
false | Do not use Readline.
nil | Use Readline except for inf-ruby-mode (default).

conf.verbose=true/false
Specifies whether or not verbose messages are displayed.

cb, irb_change_binding [ obj ]
Creates and enters a new binding that has its own scope for local variables. If obj is given, it will be used as self in the new binding.

irb [obj]
Starts an irb subsession. If obj is given, it will be used as self.

jobs, irb_jobs
Lists irb subsessions.

fg n, irb_fg n
Switches into the specified irb subsession. n may be any of the following values:

irb subsession number
thread id
irb object
self (the obj that launched a particular subsession)

kill n, irb_kill n
Kills an irb subsession. n may be any of the values as described for irb_fg.

Configuring the Prompt

You have a lot of flexibility in configuring the prompts that irb uses. Sets of prompts are stored in the prompt hash:

IRB.conf[:PROMPT]

For example, to establish a new prompt mode called ``MY_PROMPT'', you might enter the following (either directly at an irb prompt or in the .irbrc file):

IRB.conf[:PROMPT][:MY_PROMPT] = { # name of prompt mode
  :PROMPT_I => "...",             # normal prompt
  :PROMPT_S => "...",             # prompt for continuing strings
  :PROMPT_C => "...",             # prompt for continuing statement
  :RETURN => "    ==>%s\n"        # format to return value
}

Then, invoke irb with the prompt mode above by

% irb --prompt my-prompt

Or set the following configuration value:

IRB.conf[:PROMPT_MODE] = :MY_PROMPT

The constants PROMPT_I, PROMPT_S, and PROMPT_C specify the format for each of the prompt strings. Within the prompt format, the following flags are available and will expand to the given text:

Flag | Description
%N | Current command.
%m | to_s of the main object (self).
%M | inspect of the main object (self).
%l | Delimiter type. In strings that are continued across a line break, %l will display the type of delimiter used to begin the string, so you'll know how to end it. The delimiter will be one of ", ', /, ], or `.
%ni | Indent level. The optional number n is used as a width specification to printf, as printf("%nd").
%nn | Current line number (n used as with the indent level).
%% | A literal percent sign.

For instance, the default prompt mode is defined as follows:

IRB.conf[:PROMPT_MODE][:DEFAULT] = {
      :PROMPT_I => "%N(%m):%03n:%i> ",
      :PROMPT_S => "%N(%m):%03n:%i%l ",
      :PROMPT_C => "%N(%m):%03n:%i* ",
      :RETURN => "%s\n"
}

Restrictions

Because of the way irb works, there is a minor incompatibility between it and the standard Ruby interpreter. The problem lies in the determination of local variables.

Normally, Ruby looks for an assignment statement to determine if something is a variable---if a name hasn't been assigned to, then Ruby assumes that name is a method call.

eval "a = 0"
a
produces:
prog.rb:2: undefined local variable or method `a'
for #<Object:0x401c2ce0> (NameError)

In this case, the assignment is there, but it's within a string, so Ruby doesn't take it into account.

irb, on the other hand, executes statements as they are entered.

  irb(main):001:0> eval "a = 0"
  0
  irb(main):002:0> a
  0

In irb, the assignment was executed before the second line was encountered, so ``a'' is correctly identified as a local variable.

If you need to match the Ruby behavior more closely, you can place these statements within a begin/end pair.

  irb(main):001:0> begin
  irb(main):002:1*   eval "a = 0"
  irb(main):003:1>   a
  irb(main):004:1> end
  NameError: undefined local variable or method `a'
  (irb):3:in `irb_binding'

rtags, xmp, and the Frame Class

The base version of irb is installed along with Ruby itself. But there is an extended version of irb in the archives containing a few extra goodies that need mentioning.

rtags

rtags is a command used to create a TAGS file for use with either the emacs or vi editor.

rtags [
            -vi
            ] [
            files
            ]...

By default, rtags makes a TAGS file suitable for emacs (see etags.el). The -vi option makes a TAGS file for use with vi.

rtags needs to be installed in the same manner as irb (that is, you need to install irb in the library path and make a link from irb/rtags.rb to bin/rtags).

xmp

irb's xmp is an ``example printer''---that is, a pretty-printer that shows the value of each expression as it is run (much like the script we wrote to format the examples in this book). There is also another stand-alone xmp in the archives.

xmp can be used as follows:

require "irb/xmp"

xmp <<END
artist = "Doc Severinsen"
artist
END
produces:
[pwd:/tc/work/ruby/ProgrammingRuby/latex]
artist = "Doc Severinsen"
    ==>"Doc Severinsen"
artist
    ==>"Doc Severinsen"

Or, it can be used as an object instance. Used in this fashion, the object maintains context between invocations:

require "irb/xmp"

x = XMP.new
x.puts <<END
artist = "Louis Prima"
END

x.puts <<END
artist
END
produces:
[pwd:/tc/work/ruby/ProgrammingRuby/latex]
artist = "Louis Prima"
    ==>"Louis Prima"
artist
    ==>"Louis Prima"

You can explicitly provide a binding with either form; otherwise, xmp uses the caller's environment.

xmp code_string, abinding
XMP.new(abinding)

Note that xmp does not work with multithreading.

The Frame Class

The IRB::Frame class represents the interpreter's stack and allows easy access to the Binding environment in effect at different stack levels.

IRB::Frame.top(n = 0) | Returns a Binding for the nth context from the top. The 0th context is topmost, most recent frame.
IRB::Frame.bottom(n = 0) | Returns a Binding for the nth context from the bottom. The 0th context is the bottommost, initial frame.
IRB::Frame.sender | Returns the object (the sender) that invoked the current method.

You can use this facility, for instance, to examine local variables from the method that called the current method:

require 'irb/frame'

def outie
  b = IRB::Frame.top(1)
  eval "p my_local", b
end

def innie
  my_local = 102.7
  outie
end

innie
produces:
102.7

Note that this doesn't work with multithreaded programs.

Extracted from the book "Programming Ruby - The Pragmatic Programmer's Guide"
Copyright © 2001 by Addison Wesley Longman, Inc. This material may be distributed only subject to the terms and conditions set forth in the Open Publication License, v1.0 or later (the latest version is presently available at http://www.opencontent.org/openpub/)).

Distribution of substantively modified versions of this document is prohibited without the explicit permission of the copyright holder.

Distribution of the work or derivative of the work in any standard (paper) book form is prohibited unless prior permission is obtained from the copyright holder.


Programming Ruby: The Pragmatic Programmer's Guide

Programming Ruby

The Pragmatic Programmer's Guide

Previous < Contents ^

Support

One of the major features of Open Source projects is the technical support. Articles in the mass media often criticize open source efforts for not having the same tech support that a commercial product has. And boy is that a good thing! Instead of dialing up some overworked and understaffed help desk and being treated to Music On Hold for an hour or so without ever getting the answer you need, we have a better solution: the Ruby community. The author of Ruby, the authors of this book, and many other Ruby users are willing and able to lend you a hand, should you need it.

The syntax of Ruby remains fairly stable, but as with all evolving software, new features are added every now and again. As a result, both printed books and the online documentation can fall behind. All software has bugs, and Ruby is no exception. There aren't many, but they do crop up. See the bug reporting section on page 527 for details.

If you experience a problem with Ruby, feel free to ask in the mailing lists or on the newsgroup (more on those in just a minute). Generally you'll get timely answers from Matz himself, the author of the language, from other gurus, and from those who've solved problems similar to your own.

There might be similar questions in the mailing lists or on the newsgroup, and it is good ``netiquette'' to read through recent postings before asking. If you can't find the answer you need, ask, and a correct answer will usually show up with remarkable speed and precision.

Web Sites

The official Ruby Home Page is http://www.ruby-lang.org.

You can also find Ruby information at http://www.rubycentral.com. In particular, you'll find complete online references to Ruby's built-in classes and modules at http://www.rubycentral.com/ref/, and to the Ruby FAQ at http://www.rubycentral.com/faq/.

While you're surfing, drop in on http://www.pragmaticprogrammer.com and see what we're up to.

Download Sites

The latest version of Ruby can be downloaded from: http://www.ruby-lang.org/en/download.html.

Mirror sites are:

  • ftp://ftp.TokyoNet.AD.JP/pub/misc/ruby
  • ftp://ftp.iij.ad.jp/pub/lang/ruby
  • ftp://blade.nagaokaut.ac.jp/pub/lang/ruby
  • ftp://ftp.krnet.ne.jp/pub/ruby
  • ftp://mirror.nucba.ac.jp/mirror/ruby
  • http://mirror.nucba.ac.jp/mirror/ruby

Precompiled Windows binaries (using cygwin) are in the pc/ subdirectory.

Usenet Newsgroup

Ruby has its own newsgroup, comp.lang.ruby. Traffic on this group is archived and mirrored to the ruby-talk mailing list.

Mailing Lists

There are five mailing lists now talking about Ruby. The first is in English, the last four in Japanese:

ruby-talk@netlab.co.jp | English language discussion of Ruby (mirrored to comp.lang.ruby).
ruby-list@netlab.co.jp | Japanese language discussion of Ruby.
ruby-dev@netlab.co.jp | List for Ruby developers.
ruby-ext@netlab.co.jp | List for people writing extensions for or with Ruby.
ruby-math@netlab.co.jp | Ruby in mathematics.

See http://www.ruby-lang.org/en/ml.html for details on joining a mailing list.

The mailing lists are archived, and can be searched using http://blade.nagaokaut.ac.jp/ruby/ruby-talk/index.shtml.

Bug Reporting

If you think you've spotted a bug in Ruby, you may want to browse the bug database at http://www.ruby-lang.org/cgi-bin/ruby-bugs. You may also want to check to see if a new version of Ruby is available---perhaps the bug you've found has already been fixed.

You can submit a bug report either by using the Web page mentioned above or by sending an e-mail to ruby-bugs@ruby-lang.org.

When reporting a suspected bug, it would be a good idea to include the output of ``ruby -v'' along with any problematic source code. People will also need to know the operating system you're running. If you compiled your own version of Ruby, it might be a good idea to attach your rbconfig.rb file as well.

If you have a problem using irb, be aware of its limitations (see the reference section beginning on page 517). See what happens using just Ruby itself.

Previous < Contents ^

Extracted from the book "Programming Ruby - The Pragmatic Programmer's Guide"
Copyright © 2001 by Addison Wesley Longman, Inc. This material may be distributed only subject to the terms and conditions set forth in the Open Publication License, v1.0 or later (the latest version is presently available at http://www.opencontent.org/openpub/)).

Distribution of substantively modified versions of this document is prohibited without the explicit permission of the copyright holder.

Distribution of the work or derivative of the work in any standard (paper) book form is prohibited unless prior permission is obtained from the copyright holder.


欢迎光临Ruby中文网!

面向对象的脚本语言Ruby

Ruby是何方神圣?

Ruby是一种功能强大的面向对象的脚本语言,她可以使您方便快捷地进行面向对象编程.Ruby使文本处理和系统管理变得简单，与Perl非常相似.一句话：简单明了，扩展性强，移植性好.

Ruby的几大亮点:

  • 语法简单
  • 垃圾回收
  • 可移植性
  • 动态载入
  • 完全免费
  • 正则表达式

走向世界的Ruby

松本行弘"Matz"(Matsumoto Yukihiro)是Ruby语言的发明人，他从1993年起便开始着手Ruby的研发工作。他一直想发明一种语言，使你既能进行高效开发又能享受编程的快乐。1993年2月24日Ruby诞生了，1995年12月Matz推出了Ruby的第一个版本Ruby 0.95。不久Ruby便凭借其独特的魅力横扫日本，相信在不久的将来，Ruby将走向世界。

Top
Ruby概述
  • What's Ruby?
  • 许可证
  • Ruby in China Manifesto
下载
  • Ruby
  • 文档
文档
  • 安装指南
  • 教程
  • 参考手册
  • FAQ
社区
  • 邮件列表
  • Who's Who
开发
  • CVS指南
相关项目
  • RAA: Ruby应用档案
  • YARAA: RAA镜像
  • RDP: 库文档
链接
  • 链接
  • 相关书籍
原版网站
  • Ruby(日语)
  • Ruby(英文)

Powered by
Originally Written by webmaster@ruby-lang.org
Translated by Community Engine China (Mail)
Last update on 2005-01-08


Ruby参考手册 - 卷首语

卷首语

Ruby是一种简便快捷的面向对象编程的脚本语言.Ruby可以(像Perl一样)进行文本处理及系统管理,其功能十分丰富.同时,Ruby简单易懂,便于扩充.

若您正在寻找一种简单的面向对象语言,或者觉得Perl不够简便,又或者虽然喜欢Lisp的思想却讨厌到处都是括号的话,Ruby正是您不二的选择.

Ruby有下列优点.

解释器

Ruby是解释型语言,因此执行程序时无需编译.

变量无类型(动态地确定类型)

Ruby的变量可以接收各种类型的数据,因此没有必要担心变量类型的问题.另一方面,这弱化了编译检查的功能.

无需声明变量

Ruby中无需声明变量即可使用.可以根据变量名分辨变量的种类(局部变量,全局变量,实例变量等).

语法简单

受Eiffel影响,Ruby的语法十分简单.

内存管理无需用户干预

Ruby自动进行内存管理.解释器内置的垃圾回收器会自动回收不再使用的对象.

一切都是对象

从一开始Ruby就被设计成为纯粹的面向对象语言.包括整数这种基本的数据类型在内所有数据都被看是对象,进而得到统一的处理.

类，继承，方法

Ruby当然具有面向对象语言的基本功能,包括类，继承和方法等.

特殊方法

可向某对象添加方法.例如，可以把GUI按钮被按下时的动作作为方法记述下来，还可以用它来进行原型库（prototypebase）的面向对象编程（只要您想这样的话）.

使用模块进行糅合（Mix-in）

Ruby认为多重继承会导致问题复杂化,因此故意舍弃了多重继承,但可以使用模块超越类的界限来共享数据和方法等.这就是"Mix-in"糅合功能

迭代器

迭代器功能可以将流程控制结构抽象化.

闭包

可以将某过程片段对象化,对象化后的该过程片段就称作闭包.

功能强大的字符串操作／正则表达式

以Perl为样板创造出了功能强大的字符串操作和正则表达式检索功能.

超长整数

内置了处理超长整数的功能,所以只要内存允许就可以计算非常大的整数.例如计算400的阶乘等也轻而易举.

异常处理功能

异常处理功能可以使您编写代码处理异常情况.

可以直接访问OS

Ruby可以使用（UNIX的）绝大部分的系统调用.即使单独使用Ruby也可以进行系统编程.

动态加载

若OS支持的话,可以在运行时读入对象文件.

Originally Written by webmaster@ruby-lang.org
Translated by Community Engine China (Mail)
Last update on 2004-12-22


Ruby参考手册 - Ruby的启动

Ruby的启动

  • 命令行选项
  • 关于解释器行

请使用如下命令行启动Ruby解释器.

ruby [ option ...] [ -- ] [ programfile ] [ argument ...]

这里的"option"指下文将要提到的命令行选项中的一个。"--"则显式地表明选项字符串到此结束。"programfile"是装载Ruby脚本的文件。若省略不写或者写成"-"时,Ruby会把标准输入当做Ruby脚本进行处理。

programfile若以“#!”开始，则进行特殊的处理。详细情况请参考下文的关于解释器行。

argument中的字符串将变成内部常数ARGV的初始值。在有的环境中(Win32)，标准shell不会展开通配符，这时Ruby解释器将自行展开通配符然后赋值给ARGV。此时，可使用的通配符有“*”、“?”、“[]”和“**/”(与Dir.glob不同，这里不能使用“{..}”)。在Win32环境中，若不想展开通配符的话，请使用单引号(')将参数括起来。

命令行选项

Ruby解释器可接受下列命令行选项。基本上与Perl的类似。

-0数字

以8进制数指定输入记录分隔符('$/')。

若不指定数字的话，分隔符是空字符(等同于$/="\0")。数字后面可以有其他的开关(switch)。

-00代表段落模式(等同于$/="")，-0777(因为这个代码不代表任何文字)表示将文件的全部内容一次性读入(相当于$/=nil)。

-a

与'-n'或'-p'一起使用时，可以打开自动拆分模式(auto split mode)。自动拆分模式将在各个循环前执行以下动作。

$F = $_.split

若没有同时指定'-n'或'-p'选项的话将不起作用。

-C directory

执行脚本之前，先移动到指定目录。

-c

只对脚本进行编译，而并不执行。编译后若没发现语法错误，则显示“Syntax OK”。

--copyright

显示版权信息。

-Kc

指定Ruby要处理的汉字编码。若是'E'或'e'，则Ruby认定字符串或访问文件中的汉字编码为EUC。同样，若是'S'或's'的话则认定为SJIS。若是'U'或'u'则当作UTF-8处理。'N'表示不对汉字进行处理。该选项的默认值就是N(NONE)。

将来有可能会改变文字编码处理方式，届时该选项的内容也会有所变化。

-d
--debug

以调试模式执行脚本。将$DEBUG设置成true。

-e script

在命令行中指定脚本。添加-e选项后，就不会从参数中抽取脚本文件名了。

若多次使用-e选项时，系统会按照以下方式处理。

下列各表达式的意义相同。
ruby -e "5.times do |i|" -e "puts i" -e "end"

ruby -e "5.times do |i|
  puts i
end"

ruby -e "5.times do |i|; puts i; end"
-Fregexp

将regexp指定给输入域分隔符(field separator)。

-h
--help

显示命令行选项的简介。

-i[extension]

对参数中指定的文件内容进行替换(in-place edit)。原始文件将被加上扩展名并保存下来。若没有扩展名的话，将不会进行备份，而且只有替换后的文件会被保留下来。

例:

% echo matz > /tmp/junk
% cat /tmp/junk
matz
% ruby -p -i.bak -e '$_.upcase!' /tmp/junk
% cat /tmp/junk
MATZ
% cat /tmp/junk.bak
matz
-I directory

指定(追加)加载文件的路径。指定的目录将被追加到Ruby的数组变量($:)中。

-l

进行行尾自动处理。首先，将$\改为$/的值，在print输出时添加换行。若使用了-n标志或-p标志的话，将对gets读入的各行末尾进行String#chop!处理。

-n

若使用了该标志，则整个程序会像sed -n或awk一样，被

while gets
 ...
end

括起来运行。

-p

与-n标志相仿，在各循环后输出变量$_的值。

例:

% echo matz | ruby -p -e '$_.tr! "a-z", "A-Z"'
MATZ
-r feature

执行脚本前，先对feature指定的库执行require操作。与'-n'选项、'-p'选项一起使用时特别奏效。

-s

对跟在脚本名后并且以'-'开头的参数进行解释，并将其值赋值给同名的全局变量。遇到以'--'开头的参数就停止解释，并将该参数从ARGV中删除。

例:

#! /usr/local/bin/ruby -s
# prints "true" if invoked with `-xyz' switch.
print "true\n" if $xyz
-S

该选项表明，当脚本名不是以'/'开头的时候，要使用环境变量PATH的值搜索脚本。若您的机器不支持#!的话，可以使用下列方法模拟#!的运行：

#!/bin/sh
exec ruby -S -x $0 "$@"
#! ruby

因为第1行的关系，系统把脚本交给/bin/sh。/bin/sh执行第2行后启动Ruby解释器。在-x选项的作用下，Ruby解释器把从'#!'到包含'ruby'的行的内容全部读入。

根据系统的不同，$0未必包含完整路径，因此有必要使用'-S'选项来告诉Ruby在必要时搜索脚本。

-T[level]

执行不纯度测试。若给level指定了一个值之后，安全等级也会使用这个值。省略level时，其值为1。对于CGI程序来说，将其指定为-T1比较合适。$SAFE的等级也将被设定。

-v
--verbose

冗长模式。启动时显示版本信息，然后将内部变量$VERBOSE设为true。当此变量为true时，众多的方法在运行时会显示冗长的信息。若只设定'-v'选项，而没有其他参数时，启动后会先显示版本信息，然后就结束运行(不会等待来自标准输入的脚本)。

--version

显示Ruby的版本信息。

-w

不显示版本信息的冗长模式。

-W[level]

ruby 1.8 特性

可以指定3种级别的冗长模式，如下所示。

  • -W0: 不显示警告
  • -W1: 只显示重要警告(默认)
  • -W2 or -W: 显示所有警告

内部变量$VERBOSE被分别设置为nil,false,true。

-x[directory]

从message中取出脚本并执行。读入脚本的范围是从'#!'开始，直到包含'ruby'的行为止。用EOF(文件结束)，^D(controlD)，^Z(controlZ)或保留字_END_来指定脚本结束。

若指定了目录名的话，则在执行脚本前移动到该指定目录。

-y
--yydebug

编译器调试模式。编译脚本时显示语法分析的过程。该显示过程会很漫长，可能只对那些想调试编译器的人有用。

关于解释器行

命令行指定的脚本是以'#!'开头的文件，当该行中不包含'ruby'时，将替代OS把'#!'后面的字符串看成命令行，然后启动解释器。

例如，用Ruby运行下面的shell脚本时将启动sh。

#!/bin/sh -vx
echo "$@"

若此行中包含'ruby'的话，则'ruby'左侧的部分将被忽略，右侧以'-'开头的部分被视为选项。

这里指定的选项将被追加到以命令行方式指定的选项之中。这主要是为了嵌入那些本该在脚本中指定的选项。例如，下面脚本的作用等价于使用命令行方式指定-Ke选项。

#! ruby -Ke
Originally Written by webmaster@ruby-lang.org
Translated by Community Engine China (Mail)
Last update on 2004-12-22


Ruby参考手册 - 环境变量

环境变量

Ruby解释器使用下列环境变量。

RUBYOPT

指定默认情况下传给Ruby解释器的选项。

  • sh系

    RUBYOPT='-Ke -rkconv'
export RUBYOPT

  • csh系

    setenv RUBYOPT '-Ke -rkconv'

  • MS-DOS系

    set RUBYOPT=-Ke -rkconv
RUBYPATH

指定-S选项后，Ruby会搜索环境变量PATH指定的Ruby脚本。此时，该环境变量指定的目录也被纳入搜索范围(其优先程度高于PATH)。

  • sh系

    RUBYPATH=$HOME/ruby:/opt/ruby
export RUBYPATH

  • csh系

    setenv RUBYPATH $HOME/ruby:/opt/ruby

  • MS-DOS系

    set RUBYPATH=%HOME%\ruby:\opt\ruby
RUBYLIB

该环境变量把自己的值添加在Ruby库的搜索路径$:的默认值之前。

  • sh系

    RUBYLIB=$HOME/ruby/lib:/opt/ruby/lib
export RUBYLIB

  • csh系

    setenv RUBYLIB $HOME/ruby/lib:/opt/ruby/lib

  • MS-DOS系

    set RUBYLIB=%HOME%\ruby\lib:\opt\ruby\lib
RUBYLIB_PREFIX

该环境变量只在DJGPP版、Gygwin版、mswin32版和mingw32版的Ruby中有效。

该环境变量的值是path1;path2或path1 path2。若Ruby库的搜索路径$:的前面部分与path1一致时，则将其替换为path2。

  • MS-DOS系

    set RUBYLIB_PREFIX=/usr/local/lib/ruby;d:/ruby
RUBYSHELL

该环境变量只在OS2版、mswin32版和mingw32版的Ruby中有效。

指定用system执行命令时所使用的shell。若省略此环境变量，则使用COMSPEC的值。

PATH

用system等执行命令时的搜索路径。若没有设定时(其值为nil)，将按照“/usr/local/bin:/usr/ucb:/usr/bin:/bin:.”进行搜索。

Originally Written by webmaster@ruby-lang.org
Translated by Community Engine China (Mail)
Last update on 2004-12-22


Ruby参考手册 - 对象

对象

对象概述

Ruby所能处理的值都是对象。Ruby操作对象时的办法只有一种，那就是方法调用。对象所属的类决定了对象所包含的方法。生成对象时，对象的所属类也就确定了，除非导入特殊类以外，对象的所属类不会改变。可以说对象是某个特定类的“实例”。

生成对象一般是通过调用别的对象的方法来实现的。

类

类决定了归属其下的对象的方法。若能调用归属对象的某方法时，我们就说该方法是“由类定义的”。方法由类和方法名决定。

每个类都必然拥有一个“超类”。若在超类中定义了一个方法，则在当前类中必然也会出现一个相同的方法，这叫做“继承”。继承是发展的，所以超类的超类中的方法也会出现在当前类中。

模块

include

这个有点特殊，它并不是类，但功能相同。

方法

方法是可以执行的。通常我们把执行方法的动作叫做“调用”。调用方法时可以传递对象，这个对象就是“参数”。可接受的参数数量是在定义方法时确定的，不可改变。

Originally Written by webmaster@ruby-lang.org
Translated by Community Engine China (Mail)
Last update on 2004-12-22


Ruby参考手册 - 类

类

类和对象

在Ruby程序中，类也可以作为对象来处理。您可以进行下列基本操作。

  • 查询某方法是否存在
  • 定义方法
  • 取消方法

模块

特殊类

Originally Written by webmaster@ruby-lang.org
Translated by Community Engine China (Mail)
Last update on 2004-12-22


Ruby参考手册 - 执行

执行

Ruby 程序

Ruby程序的执行就是对代码进行计算的过程。先编译程序文本，遇到BEGIN就对其作出计算；然后计算顶层的一系列的表达式；若遇到END的话，将在最后对其进行处理然后结束程序(关于结束程序时处理步骤的详细过程请参考结束程序时的相关处理)。

语句

if

if句的计算过程如下：先对条件表达式进行计算，若为真则执行相应代码段，若为假则依次计算elseif部分的条件表达式，若遇到值为真的表达式则执行相应的代码段。若所有的条件表达式的值都为假的话，就执行else部分的代码段。

语句的值取决于最后执行的代码块的值。若最后的代码块中没有表达式，或者所有条件表达式的值都是假而且没有else部分的话，则语句的值为nil。

until

if 修饰部分

unless 修饰部分

while

until

while 修饰部分

until 修饰部分

for

begin ～ end

类定义句

定义类的内容。在执行时(而并非编译时)进行计算。

书写样式

class ClassName [< 超类表达式]
  表达式
end

在对类定义句进行计算时，将先试图生成类。若有超类表达式就加以计算,其值作为ClassName类的父类,然后生成ClassName类的实例.若没有超类表达式,就把Object作为其父类.

另一方面，若有同名类的话，就先使用那个同名类。然后处理超类表达式，若新生成的超类(在equal?)有所不同的话，就再生成一个新的类。

得到类之后就将其代入常数“ClassName”中，由此决定类名。此时，若同名的常数中被代入一个非Class的实例的话，就会引发异常TypeError。

最后生成新的框架(frame)，向顶层块的self以及class设定要进行定义的类，然后在框架的基础上对定义句中的表达式进行计算。我们无法得到类定义句的值。

也就是说，在Ruby中我们可以多次“追加类定义”。

模块定义句

定义模块的内容。在执行时(而并非编译时)进行计算。

书写样式

module ModuleName
  模块内容
end

对模块定义句进行计算时，首先会生成新的无名模块。但是，若已经有了一个名为ModuleName的模块的话，就使用该模块。此时就变成“追加模块的定义”了。

得到模块后就将其代入常数ModuleName中。这个常数就成为模块的名称。此时，若向同名常数代入非模块的话就会引发异常TypeError。

最后生成新的框架(frame)，向顶层块的self以及class中设定模块ModuleName，然后在框架的基础上对定义句中的表达式进行计算。模块定义句的值就是模块内容的最后一个表达式的值。若模块内容中没有可计算的表达式时，其值为nil。

特殊类定义句

定义对象的特殊类。在执行时(而并非编译时)进行计算。

书写样式

class << EXPR
  类的内容
end

先计算想定义特殊类的对象的表达式EXPR。然后生成该对象的特殊类(若尚未生成的话)。最后生成新框架，向顶层块的self和class中设定新生成的特殊类。在新框架的基础上对定义句中的表达式进行计算。

特殊类定义句的值取决于类的内容中的最后一个表达式的值。若没有可计算的表达式时，其值为nil。

请注意，Fixnum Symbol的实例以及 true false nil 不能定义特殊类

方法定义句

定义方法的内容。在执行时(而并非编译时)进行计算。

书写样式

def method_name(arg, argwithdefault=expr, *restarg, &block)
  方法内容
end

对其进行计算时，将向运行块的class中定义该方法。若class中已经存在同名的方法的话，则抛弃旧方法，添加新方法。

方法定义句的值为nil。

特殊方法定义句

向对象的特殊类中定义方法。在执行时(而并非编译时)进行计算。

书写样式

def expr.method_name(arg, argwithdefault=expr, *restarg, &block)
  方法内容
end

首先计算表达式expr。然后生成对象的特殊类(若尚未生成的话)。最后向特殊类中定义方法method_name。

特殊方法定义句的值为nil。

请注意，Fixnum Symbol的实例以及true false nil不能定义特殊方法。

BEGIN

编译时会用到(执行时首先计算)

END

编译时会用到(执行时最后计算)

方法

方法的调用

首先计算被调(receiver)表达式，得到被调用对象。省略被调表达式时，调用块的self将成为被调。

接下来从左到右地对参数表达式进行计算，检索被调里面的方法。若检索失败则引发异常NameError，成功的话就执行方法。

另外，执行方法的时候还可以添加块(block)。若向方法添加块时，只有当运行中的方法执行yield时才会对块进行计算。若没有执行yield的话，块将被忽视，不会执行。

将块传给方法时，该块将会继承调用方的块的self和class。只有Module#module_eval/class_eval和Object#instance_eval这三个例外，如下所示。

Module#module_eval, class_eval

self和class都是被调(receiver)

Object.instance_eval

self是被调，class是被调的特殊类

eval

把Proc对象和Binding对象传给eval的第二参数时，将在生成时的块的基础上对字符串进行计算。

方法的执行

当框架上只有一个块的情况下，才开始执行方法。下面我们暂时把这个块称作顶层块(top level block)。顶层块的self是被调，class尚未被定义。

首先，若有必选参数的话，就把得到值代入顶层块的局部变量。

若存在可选参数，且已被省略的话，则在顶层块上对默认值表达式进行计算，然后将得到的默认值代入顶层块的局部变量。若可选参数没被省略的话，就把得到的值代入顶层块的局部变量。

若存在*args这样的参数的话，则将剩下的所有参数以数组的形式代入局部变量。

另外，若存在块参数blockvar的话，则将传给方法的块进行Proc对象化，然后代入顶层块的局部变量blockvar中。若没向方法传递块的话，就代入nil。

接下来对方法内容进行计算，先计算方法层(method level)的rescue以及else部分，最后计算ensure部分。

整个方法的值取决于传递给return的值。若没有调用return的话，则取决于 方法内容／rescue／else 中最后被计算的表达式的值。若三个都为空的话，值为nil。

带块的方法调用

若向方法传递一个块的话，这个方法就叫做带块的方法。带块方法遇到yield时会转向块。

可以使用块参数。

break...若块位于堆栈框架(stack frame)上的话，就跳到框架的块的后面。break并结束带块方法，其值为nil。若块不在堆栈框架上，则引发异常LocalJumpError。

next 跳到块的终点

retry 这个就复杂了...

eval, instance_eval, module_eval

略

赋值

赋值是指让变量或常数记住某个对象。从语法的角度来看，虽然[]=和属性赋值的方法调用看起来很像是赋值，但却并非这里谈到的赋值。

我们可以反复地将各种对象赋值给变量。也可以将各种对象赋值给常数，但却只能赋值一次。也就是说，一旦将对象赋值给常数，就不能再更改。但这并不意味着赋值给常数的对象本身不允许更改，请您注意这点。

多重赋值

暂无

变量和常数

我们可以让变量或常数记住一个对象。这叫做“变量(常数)的赋值”。

当对变量或常数进行计算时，它就会返回记住的对象。这叫做“变量(常数)的调用”。

下面我们就分别来看一看变量和常数的赋值与调用过程。

局部变量

局部变量只属于一个块。块是指与代码的某个范围相对应的运行时的结构，可以嵌套。具体说来，它伴随带块的方法调用以及eval系方法的执行而出现。我们只能在局部变量的所属块以及该块的嵌套块中对局部变量进行赋值和引用。

同时，块被放在特定的“框架”上，并归属于该框架。因此，不能调用其他框架上的局部变量。所谓框架是指开始执行下列语句时生成的运行时的结构。

  • 程序文本的顶层(传递给ruby的文件名、-e、load)
  • 执行方法
  • 类／模块的定义句
  • BEGIN和END句

生成框架时自动搭载一个块，因此可以在这些语句中使用局部变量。

编译时，写入程序代码的局部变量将赋值给框架中的尚未定义的局部变量。局部变量被赋值时所在的块就是它的归属块。由此可知，编译时局部变量的定义过程已经完成(请注意，eval系的方法在执行过程中进行编译)。定义的变量的初始值为nil。

局部变量在定义和调用时，先是在块中从外到内地进行搜索。其结果就是，局部变量不能进行嵌套和屏蔽(shadowing)。但是，当若干的块处于非主从关系时，其内部可以包含不同的局部变量。

调用未定义(即没有在代码中标出)的局部变量时，Ruby会试图把它当作对self的(无参数的)方法调用来处理。若搜索方法失败则引发异常NameError。

再来看一下调用块的执行情况,块也可以带参数,但常被看做是在将要执行的块上进行的多重赋值.例如,下面代码的块在开始执行时

some_iterator do |a,b|
  ....
end

首先会进行下列操作。

a, b = <some_iterator 被yield的值 >

实例变量

实例变量属于一个对象，在self代表的块的范围内可以进行赋值和调用。实例变量的赋值过程同时也就是该变量的定义过程，若调用未定义的实例变量则返回nil。

remove_instance_variable

类变量

类变量为一个特定的类、该类的子类以及该类的实例所拥有。在以这些对象为self的块的范围内，可对其进行赋值和调用。最初的赋值过程也兼做定义。若调用一个未经定义的类变量的话就会引发异常NameError。

类变量的继承和“继承中止”

全局变量

在任何地方都可以对全局变量进行赋值和调用。最初的赋值过程兼做变量的定义，若调用一个未经定义的全局变量的话，就会返回nil。

可跟踪(？)

常数

常数属于类/模块。我们可以使用除method以外的方式对其进行赋值。最初的赋值兼做定义。对常数赋值时所在的块的class就是常数的归属类。有个非常特殊的例外，我们可以使用Module#const_set方法来定义常数，同时，还可以使用Module#remove_const来取消常数。

无法对已定义的常数进行再定义或赋值。实际上，只使用警告还可以进行赋值，但这只是一时的应急措施，并不符合规范。所以要少写这样的程序。

可调用范围因写法不同而有所差异。

只写出常数名时 (例： Const)

可调用范围有：常数所属的类、子类、嵌套类的框架内的代码

写出完整路径时 (例： Mod::Cls::Const)

可在任何地方调用

另外，像"::Const"这种前置"::"的写法，只有写成"Object::Const"时才能进行调用。

伪变量

下面这些变量看起来好像是局部变量，但实际上是保留字，返回确定的值。不可赋值。

self

返回该块的self。

nil

返回NilClass的唯一的实例--nil。

true

返回TrueClass的唯一的实例--true。

false

返回FalseClass的唯一的实例--false。

Originally Written by webmaster@ruby-lang.org
Translated by Community Engine China (Mail)
Last update on 2004-12-22


Ruby参考手册 - 结束时的相关处理

Ruby结束时的相关处理

当到达脚本尾部或出现了未能捕捉到的异常时，Ruby会结束程序(当调用函数exit、abort或对主线程发出Thread.kill命令时，会引发SystemExit异常)。结束程序时会依次进行下列处理。

  1. 对所有线程执行Thread.kill。
  2. 若注册了Ruby的伪信号SIGEXIT的处理程序的话，就执行它(请参考trap)。
  3. 若注册了END块(END {...}或at_end函数指定的块)的话,就以与记录相反的顺序执行它.若在该块的执行过程中发生大规模退出(non-local exit)的话,就中断该块的运行.但脚本仍将继续运行.

  4. 在ObjectSpace.define_finalizer中,若注册了finalizer的话就执行它(执行顺序不定).若在执行finalizer的过程中,发生了大规模退出的话,就中断finalizer的运行,但脚本仍将继续运行.

  5. 使用exit(3)结束程序.

    此时传来的状态值取决于程序的结束方法.例如,

    • 到达脚本尾部时 值为0
    • 传递给exit的参数
    • 若非由SystemExit引起的异常的话,其值为1
    • 若明显是SystemExit的话,其值为0

使用exit!函数时,将只执行exit(2),而不会执行上述任何步骤.

Originally Written by webmaster@ruby-lang.org
Translated by Community Engine China (Mail)
Last update on 2004-12-22


Ruby参考手册 - Ruby 线程

Ruby 线程

线程是指共享内存空间并可同时运行的控制流。使用线程便可进行并行编程。

现在Ruby的线程是用户级的线程，可在所有平台上实现相同的运作。下面就来看看线程的运作情况。

Ruby线程的运作方式

程序开始时同步生成的线程称作main thread。当main thread结束时，其他所有线程以及整个程序也会结束。用户的介入所引起的异常也会被送给main thread。

线程启动时会指定一个块。当这个块结束运行时，该线程也会终结。块的结束既包括正常结束又包括因错误等引起的异常结束。

Ruby的线程调度采用的是一种带优先顺序的循环调度法。每隔一段时间或者当运行中的线程放弃权利的时候，就对进行调度。从可运行的线程中选出优先级别最高的加以运行。

线程和异常

当某线程发生异常，且没有被rescue捕捉到时，该线程通常会被无警告地终止。但是，若有其它线程因为Thread#join的关系一直等待该线程的话，则等待的线程同样会被引发相同的异常。

begin
  t = Thread.new do
    Thread.pass    # 主线程确实在等join
    raise "unhandled exception"
  end
  t.join
rescue
  p $!  # => "unhandled exception"
end

使用下列3个方法，就可以让解释器在某个线程因异常而终止时中断运行。

  • 启动脚本时指定-d选项，并以调试模时运行。
  • 用Thread.abort_on_exception设置标志。
  • 使用Thread#abort_on_exception对指定的线程设定标志。

当使用上述3种方法之一后，整个解释器就会被中断。

Originally Written by webmaster@ruby-lang.org
Translated by Community Engine China (Mail)
Last update on 2004-12-22


Ruby参考手册 - 安全模型

安全模型

为了安全地运行CGI等程序，Ruby设置了安全结构。

Ruby的安全模型由“对象的污染”和“安全级别”构成。

对象的污染

Ruby有时会认为对象“遭到了污染”，这主要有两种用途。

第一，以不安全的输入为基础制成的对象就是“受污染”的对象，不能用作“危险操作”的参数。这主要是为了防止恶意数据导致程序作出一些意外的危险动作。

第二，可以使安全对象(未遭污染的对象)得到保护，免遭不安全对象的威胁。若安全级别为4，则对未受污染的对象进行操作时就会受到很多限制，这正体现了对于安全方面的考虑。

与对象的污染有关的方法

Object#taint

污染对象

Object#tainted?

若对象受到了污染就返回真

Object#untaint

消除对象受到的污染

安全级别

每个线程都有特有的“安全级别”。安全级别越高，操作受到的限制也就越多。线程局部变量$SAFE标明了安全级别。

[ruby-list:37415]

$SAFE的相关规则

  • 程序开始时$SAFE的值为0
  • 各线程在生成时继承父线程的$SAFE值
  • 不能降低现有的$SAFE值

从原则上讲，低安全等级时的限制也适用于高安全等级。例如，若某操作在1级就被禁止的话，在2级就更不可能通过了。

0级

默认的安全级别。

被污染对象

  • 可从IO、环境变量或命令行参数(ARGV)中获得的字符串

    (只有环境变量PATH例外)

环境变量PATH比较特殊，只有当其值中含有危险路径时才会受到污染。

这时所说的危险路径是指，谁都可以变更或写入的路径。从根目录起层层检查，若包含谁都可以更改的地方的话，该路径就是危险的。

禁止的操作

  • 没有

1级

特指以安全程序处理不安全数据的情况。适合于用CGI等处理用户的输入。

被污染对象

  • 与0级相同

禁止的操作

  • 下列以受污染字符串为参数的操作
    • Dir, IO, File、FileTest的类方法、方法
    • 使用FileTest操作符、比较文件的更新时间
    • 执行外部命令(system, exec, ``)
    • eval (参考4级的说明)
    • 加载顶层(若使用第二参数进行wrap则可以执行)
    • require
    • trap
  • 执行外部命令(只有当环境变量PATH中包含危险路径时)

2级

被污染对象

  • 与1级相同

禁止的操作

在1级限制的基础上，以下操作也被禁止。

  • Dir.chdir Dir.chroot Dir.mkdir Dir.rmdir
  • File.chown File.chmod File.umask File.truncate File#lstat File#chmod File#chown File#delete File#unlink File#truncate File#flock 以及FileTest模块的方法
  • IO#ioctl, IO#fcntl
  • Process.fork Process.setpgid Process.setsid Process.setpriority Process.egid= Process.kill
  • 使用危险路径load
  • 以被污染字符串为参数的load(即使被wrap也不行)
  • syscall
  • exit!
  • trap

3级

所有生成的对象都被污染。适于为在4级状态下运行程序提供环境。

被污染对象

  • 所有生成的对象

禁止的操作

在2级限制的基础上，以下操作也被禁止。

  • Object#untaint

4级

执行不安全程序时等级。

此时，3级时禁止的“受污染字符串的eval”却被解禁。(这是因为用eval时，所有的危险操作都已经被禁止了。)

被污染对象

  • 与3级相同。

禁止的操作

在3级限制(如上所述，不包括eval)的基础上，以下操作也被禁止。

  • Object#taint
  • 改变顶层的定义(autoload, load, include)
  • 对既存方法的再定义
  • 改变Object类的定义
  • 改变未被污染的类和模块的定义或改变类变量
  • 改变未被污染的对象的状态
  • 改变未被污染的全局变量
  • 使用未被污染的IO及File的处理
  • 输出到IO
  • 程序的终结(exit, abort)(且out of memory也不fatal)
  • 对其他线程造成影响的Thread类的操作以及其他线程的Thread#[]
  • ObjectSpace._id2ref
  • ObjectSpace.each_object ruby 1.7 feature
  • 改变环境变量
  • srand

其他的安全级别相关信息

  • 当$SAFE = 0时才执行require
  • 若超过Level1的话，启动时会有下列不同
    • 不把环境变量RUBYLIB添加到$:之中
    • 不把当前目录添加到$:之中
    • 不处理环境变量RUBYOPT
    • 不能使用下列开关 -s -S -e -r -i -I -x (就算脚本被setgid, setuid也是如此)
    • 不会从标准输入读入程序 (就算脚本被setgid, setuid也一样)
  • 被setuid, setgid的脚本将在超过$SAFE = 1的状态下运行。
  • 在3级以上的环境中生成的Proc将会记下该时刻的安全级别。若受污染的Proc对象被call的话，它将以记忆的安全级别来运行。
  • 若受污染的Method对象被call的话，将以4级状态运行。
  • 若将受污染的字符串指定为trap/trace_var的第二参数时，将以4级状态运行ruby 1.7 feature：在 version 1.7中，若将受污染的字符串指定为第二参数而运行trap/trace_var的话，马上就会引发异常SecurityError。
  • 超过4级的话，即使out of memory也不会fatal。
  • 根据您安装情况的不同，Fixnum Symbol true false nil可能不会被污染。但请注意Bignum Float可能会受到污染。

实例

$SAFE级别一旦升高就不能调低了。如下所示，可以使用线程将程序的一部分置入高安全级别状态下运行。

例:

def safe(level)
  result = nil
  Thread.start {
    $SAFE = level
    result = yield
  }.join
  result
end

safe(4) { puts "hello" }    # 因为是$SAFE所以例外
puts "world"                # 外部不受影响

扩展库中的应对

  • 在扩展库中，有必要对对象的污染状态进行适当的传播。
  • 改变全局状态或与外部联系之前，有必要检查安全级别。

[ruby-list:37407]

Originally Written by webmaster@ruby-lang.org
Translated by Community Engine China (Mail)
Last update on 2004-12-22


Ruby参考手册 - 正则表达式

正则表达式

  • 后方参考
  • 字符范围
  • 回缩(backtrack)
  • 范例

下面就来讲讲ruby支持的正则表达式符号(元字符)。

有这么个规则：

  • 不带\的数字和字母不是元字符
  • 带\的符号也不是元字符

下文中出现的“匹配多字节字符的正则表达式”是指，通过使用$KCODE进行设定，或显式地使用汉字选项(请参考正则表达式字面值)等方式进行的匹配多字节字符的正则表达式。

  • ^

    行首。与字符串的头部或换行符之后的位置相匹配。

  • $

    行尾。与字符串的尾部或换行符之前的位置相匹配。不包括换行符本身。

    ruby 1.8 特性：以前，只匹配字符串尾部换行符前的位置，现在则扩大到字符串的尾部。trap::Regexp

    p "\n".gsub(/$/, "o")
=> "o\n"  (1.6)
=> "o\no" (1.8)

  • .

    匹配除换行符以外的任意一个字符。使用正则表达式选项 m(多行模式。请参考正则表达式字面值) 时，则匹配包括换行符在内的任意一个字符。在匹配多字节字符的正则表达式中，则匹配一个字(非单字节)。

    当遇到不完整的多字节字符的一部分(无法判断该字符是多字节字符？二进制？还是ASCII)时，也不会匹配。

    p /./e =~ "あ"[0,1]     # => nil

  • \w

    字母和数字。等同于[0-9A-Za-z]。

    若为匹配多字节字符的正则表达式时，则也会匹配日语的全角字符。

  • \W

    非字母和数字。\w以外的单个字符。

  • \s

    空字符。相当于[ \t\n\r\f]

  • \S

    非空字符。[ \t\n\r\f] 以外的单个字符。

  • \d

    数字。即[0-9]

  • \D

    非数字

  • \A

    字符串头部。与^不同的是，它不受有无换行符的影响 。

  • \Z

    字符串尾部。若字符串以换行符结尾，则匹配换行符前的位置。

    ruby 1.8 特性：以前，只匹配字符串尾部换行符前的位置，现在则扩大到字符串的尾部。trap::Regexp

    p "\n".gsub(/\Z/, "o")
=> "o\n"   (1.6)
=> "o\no"  (1.8)

  • \z

    字符串结尾。与$以及\Z不同的是，它不受有无换行符的影响。

  • \b

    在字符范围描述符之外时表示词边界(匹配从\w到\W)。在字符范围描述符之内时表示退格符(0x08)。

  • \B

    非词边界

  • \G

    在上次成功匹配的地方(之后)进行匹配(不留余地)。只有在首次使用时才会匹配到头部(与\A相同)。

    可以用在scan和gsub中。当您想在上次匹配的地方之后再进行匹配的话，可以使用。

    举个简单(没什么用)的例子。

    # 从头取出3位数字(数字必须相连)。
str = "123456 789"
str.scan(/\G\d\d\d/) {|m| p m }

  • [ ]

    指定字符范围。请参考字符范围

    。

  • *

    前面元素至少出现0次。尽可能匹配较长的部分。

  • *?

    负责指定数量(quantifiers)。表示前面元素至少出现0次(尽量匹配短的部分)

  • +

    负责指定数量(quantifiers)。表示前面元素至少出现1次

  • +?

    负责指定数量(quantifiers)。表示前面元素至少出现1次(尽量匹配短的部分)

  • {m}
  • {m,}

  • {m,n}

    指定元素重复出现的次数(interval quantifier)。分别表示前面元素重复出现

    • m 次
    • 至少 m 次
    • 至少 m 次，至多 n 次

    {,n} 或 {,} 将导致匹配失败。

    str = "foofoofoo"
p str[/(foo){1}/]   # => "foo"
p str[/(foo){2,}/]  # => "foofoofoo"
p str[/(foo){1,2}/] # => "foofoo"

    正则表达式 ?, *, + 分别等同于 {0,1}, {0,} {1,} 。

  • {m}?
  • {m,}?

  • {m,n}?

    指定元素重复出现的次数(interval quantifier)。分别表示前面元素重复出现

    • m 次
    • 至少 m 次
    • 至少 m 次，至多 n 次

    (尽量匹配短的部分)。

  • ?

    负责指定数量(quantifiers)。表示前面元素至多出现1次。

  • ??

    负责指定数量(quantifiers)。表示前面元素至多出现1次(尽量匹配短的部分)

  • |

    选择(alternative)。

  • ( )

    正则表达式的群组化。与括号中的正则表达式相匹配的字符串将被保存下来，供后方参考使用。

  • \1, \2 ... \n

    后方参考(back reference)。请参考后方参考。

  • (?# )

    注释。括号中的任意字符串将被忽视。

  • (?: )

    不具备后方参考功能的群组化。它不为\1,\2(或$1,$2)提供服务，是一种单纯的群组功能。

    /(abc)/ =~ "abc"
p $1
=> "abc"

/(?:abc)/ =~ "abc"
p $1
=> nil

  • (?= )

    先行(lookahead)。使用模式(pattern)指定位置(不留间隔)

    (?=re1)re2

    表示将匹配同时符合re1和re2的要求的字符串。

    re1(?=re2)

    という山附は、稿に re2 とマッチする矢机误が鲁く、正则表达式 re1 です。

    p /foo(?=bar)/ =~ "foobar"      # => 0
p $&    # => "foo"   (bar の婶尸の攫鼠はない)

  • (?! )

    否定先行(negative lookahead)。使用否定的模式(pattern)来指定位置(不留间隔)

    (?!re1)re2

    该正则表达式表示，匹配re1但不匹配re2。

    # 除000以外的3位数字
re = /(?!000)\d\d\d/
p re =~ "000"   # => nil
p re =~ "012"   # => 0
p re =~ "123"   # => 0

# C语言标识符 (首位是[A-Za-z_]然后是[0-9A-Za-z_]的字符串)
/\b(?![0-9])\w+\b/

  • (?> )

    禁用回缩功能。

    该功能尚处于试验阶段。将来有可能被停用，请您注意使用。特别是不要在广义库中使用。

  • (?ixm-ixm)

    正则表达式中的i选项、x选项、m选项的开关。请您参考正则表达式字面值来了解选项的详细内容。

    re = /A(?i)a(?-i)A/
p re =~ "AaA"         # => 0
p re =~ "AAA"         # => 0
p re =~ "AAa"         # => nil

  • (?ixm-ixm: )

    括号中的i选项、x选项、m选项的开关。在括号范围内有效。

    re = /A(?i:a)A/
p re =~ "AaA"         # => 0
p re =~ "AAA"         # => 0
p re =~ "AAa"         # => nil

后方参考

正则表达式 \1 \2 ... \n 表示后方参考。\n表示将匹配第n个括号(正则表达式的()表示群)的内容保存起来，供后面使用。

/((foo)bar)\1\2/

和

/((foo)bar)foobarfoo/

是一样的。

例:

re = /(foo|bar|baz)\1/
p re =~ 'foofoo'   # => 0
p re =~ 'barbar'   # => 0
p re =~ 'bazbaz'   # => 0
p re =~ 'foobar'   # => nil

对应的括号必须位于后方参考表达式的左侧。

若后方参考表达式位于对应的括号中时，匹配常常会失败。当后方参考表达式中的数字是1位，且没有对应的括号时，匹配也将失败。

p /(\1)/ =~ "foofoofoo" # => nil
p /(foo)\2/ =~ "foo\2"  # => nil

虽然可以指定2位以上的后方参考表达式，但是不要把它同反斜线表示法的\nnn(对应于8进制数nnn的字符)混为一谈。当数字只有1位时，通常是后方参考表达式。当指定了一个超过2位的数字时，若没有对应括号的话，则被看作是8进制代码。

相反地，若在正则表达式中使用1位的8进制代码时，必须以0打头，例如\01等(不可能存在形如\0这样的后方参考表达式，因此不会混淆)。

p   /\1/ =~ "\1"   # => nil     # 无对应括号的后方参考
p  /\01/ =~ "\1"   # => 0       8 进制代码
p  /\11/ =~ "\11"  # => 0       8 进制代码

# 8 进制代码 (因为没有对应括号)
p /(.)\10/ =~ "1\10" # => 0

# 后方参考 (因为有对应的括号)
p /((((((((((.))))))))))\10/ =~ "aa"  # => 0

# 8 进制代码 (因为没有像"\0" + "8" -> \08 这样的8进制代码)
p /(.)\08/ =~ "1\0008" # => 0

# 如果想在后方参考表达式之后插入数字的话，就必须使用括号加以分隔。
p /(.)(\1)1/ =~ "111"   # => 0

字符范围

正则表达式 [] 负责指定字符范围。这将匹配 [] 内列出的任何一个字符。

例如/[abc]/表示只要匹配"a", "b", "c"中任何一个即可。也可以按照ASCII代码顺序，在连续的字符串之间插入“-”后写成/[a-c]/也是一样的效果。另外，若头上是“^”的话，表示要匹配指定字符之外的一个字符。

若“^”不在头上的话，表示匹配该字符本身。同时，当“-”出现在头或尾上时，表示匹配该字符本身。

p /[a^]/ =~ "^"   # => 0
p /[-a]/ =~ "-"   # => 0
p /[a-]/ =~ "-"   # => 0
p /[-]/ =~ "-"   # => 0

空的字符范围将引发错误。

p /[]/ =~ ""
p /[^]/ =~ "^"
# => invalid regular expression; empty character class: /[^]/

当“]”出现在头上(或否定的“^”之后)时，表示“]”本身，而并非字符范围的结尾。

p /[]]/ =~ "]"       # => 0
p /[^]]/ =~ "]"      # => nil

可以使用反斜线对"^", "-", "]" 以及 "\\"(反斜线)进行转义，使其匹配该字符本身。

p /[\^]/ =~ "^"   # => 0
p /[\-]/ =~ "-"   # => 0
p /[\]]/ =~ "]"   # => 0
p /[\\]/ =~ "\\"  # => 0

在[]中可以使用反斜线表示法以及正则表达式\w, \W, \s, \S, \d, \D (这些都是表示字符范围的简写法)。

请注意，下列包含否定意味的字符范围也将匹配换行符(正则表达式 \W,\D 也是如此)。

p /[^a-z]/ =~ "\n"    # => 0

字符范围中也可以使用下列特殊的表达法，但是，将来这些表达法是否会继续得到支持还未可知(所以此处从略，欲知详情请参考grep(1)的手册)。

[:alnum:]  数字和字母 0-9a-zA-Z
[:alpha:]  字母 a-zA-Z
[:blank:]  空白类
[:cntrl:]  控制字符
[:digit:]  数字
[:graph:]  除空白以外的可打印可视字符
[:lower:]  小写字母
[:print:]  可视字符
[:punct:]  符号
[:space:]  空白字符
[:upper:]  大写字母
[:xdigit:] 16进制字符

例: (包括"[]"在内，"[:...:]"表示1个字符。并非文字类的"[]")

p /[[:alnum:]][[:cntrl:]]/ =~ "a\x01"  # => 0

注: 全角字符不在考虑范围之内。即使指定让正则表达式对汉字进行匹配时，[:alpha:]等也不会匹配全角的字母。

p /[[:alpha:]]/e =~ "Ａ"        # => nil

回缩(backtrack)

用特殊括号(?> )将正则表达式括起来后，与该正则表达式相匹配的字符串中的回缩功能就将失效。举例如下。

例如在通常的正则表达式中

p /(a*)ab/ === 'aaab'

是匹配的。该匹配过程如下所示。

  1. 正则表达式 a* 从索引0开始匹配3个a
  2. 正则表达式 a 匹配失败
  3. 正则表达式 a* 将匹配部分稍稍“缩小”一下，匹配2个a(使用了回缩功能)
  4. 正则表达式 a 与字符a匹配
  5. 正则表达式 b 与字符b匹配
但是，如果用括号(?> )把正则表达式括起来的话，就不再匹配了。详细过程如下。

  1. 正则表达式 a* 从索引0开始匹配3个a
  2. 正则表达式 a 匹配失败
  3. a* 想把匹配部分回缩一下，但由于特殊括号的作用，回缩功能失效。
  4. 正则表达式 a* 从索引1开始匹配2个a

接下来的匹配都不成功，最终导致整体匹配失败。

简单说来，通常的正则表达式是“贪婪型的匹配”，而(?> )则是“超贪婪型的匹配”，因为它一旦匹配成功就决不放手。

范例

为了便于您拷贝使用，我们将其代入到以$re_开头的全局变量中。

数值

  • 浮点数(包括整数)

    $re_float = /[-+]?(?:[0-9]+(\.[0-9]*)?|(\.[0-9]+))([eE][-+]?[0-9]+)?/

p $re_float =~ "1.23"    # => 0
p $&.to_f                # => 1.23
p $re_float =~ ".23"     # => 0
p $&.to_f                # => 0.23
p $re_float =~ "1.23e1"  # => 0
p $&.to_f                # => 12.3
p $re_float =~ "1.23e-1" # => 0
p $&.to_f                # => 0.123

用逗号将数字划分成3位一组的形式

  • 方法1：使用回行和先行的方法(回行(lookbehind)需要Oniguruma库的支持)

    p "tone of 12345Hz".gsub(/(?<=\d)(?=(?:\d\d\d)+(?!\d))/, ',')
=> ruby 1.8.0 (2003-08-07) [i586-linux]
   "tone of 12,345Hz"

  • 方法2：只使用先行的方法

    p "tone of 12345Hz".gsub(/(\d)(?=(?:\d\d\d)+(?!\d))/, '\1,')
=> ruby 1.8.0 (2003-08-07) [i586-linux]
   "tone of 12,345Hz"

  • 方法3：不使用先行的方法

    s = "tone of 12345Hz"
nil while s.gsub!(/(.*\d)(\d\d\d)/, '\1,\2')
p s
=> ruby 1.8.0 (2003-08-07) [i586-linux]
   "tone of 12,345 Hz"
Originally Written by webmaster@ruby-lang.org
Translated by Community Engine China (Mail)
Last update on 2004-12-22


Ruby参考手册 - 字句构造

字句构造

  • 标识符
  • 注释
  • 内嵌文档
  • 保留字

现在Ruby使用的是ASCII字符集。对英文字母的大小区别是敏感的。您可以在任何地方插入空字符或注释，除了标识符和部分字面值的中部。空字符包括space,tab,垂直tab,backspace,回车,换页。换行比较特殊，若换行后内容是继续上一行内容的话，该换行符就是空字符，除此以外的换行符就被解释为语句的切分。

标识符

例:

foobar
ruby_is_simple

Ruby的标识符的首位由字母或下划线('_')构成，后面部分可以是字母、下划线('_')或数字。对标识符的长度没有限制。

注释

例:

# this is a comment line

遵从脚本语言的习惯，Ruby把 除了字符串内部和数值字面值'?#'之外的 以#开始的行当作注释行来处理。

内嵌文档

例:

=begin
the everything between a line beginning with `=begin' and
that with `=end' will be skipped by the interpreter.
=end

可以在Ruby代码中嵌入文档。从行首为=begin的行开始到行首为=end的行为止的部分就是内嵌文档。虽然Ruby解释器对内嵌文档的内容没有什么要求，但是内嵌文档的格式最好是RD。

保留字

下列词语就是Ruby的保留字。

BEGIN    class    ensure   nil      self     when
END      def      false    not      super    while
alias    defined? for      or       then     yield
and      do       if       redo     true
begin    else     in       rescue   undef
break    elsif    module   retry    unless
case     end      next     return   until

保留字不可用作类名和变量名。但若这些词语前面出现$,@,@@等前缀的话，就不再是保留字了。当在def定义中或方法调用操作符'.'后面出现这些词语时，则可确定它们是方法名，这时就可以使用这些词语。

Originally Written by webmaster@ruby-lang.org
Translated by Community Engine China (Mail)
Last update on 2004-12-22


Ruby参考手册 - 程序

程序

  • 表达式
  • 程序的结束

将表达式连接起来就构成程序。使用分号(;)或换行将表达式分隔开来。但反斜线后出现的换行并非分隔符，它表示该行将继续下去。

例:

print "hello world!\n"

表达式

例:

true
(1+2)*3
foo()
if test then ok else ng end

Ruby的表达式包括，变量和常数、各种字面值、相关计算和赋值、if或while等控制结构、方法调用、类/方法的定义。

还可以使用括号将表达式括起来进行群组化。

空表达式 () 返回nil。

Ruby的表达式包括返回值的表达式和不返回值的表达式。

不返回值的表达式的例子
  • while, until, while 修饰表达式, until 修饰表达式

另外，有的表达式不能用作某方法的参数，而有的则可以(有时将这种表达式称作“句”以示区别)。

不能用作参数的表达式的例子
  • and, or, not
  • if/unless/rescue 修饰表达式, ...

若使用括号将那些不能用作参数的表达式括起来进行群组化之后，就可以像使用普通表达式一样使用它们了。

程序的结束

Ruby解释器在读取程序时，若遇到下列标识就会停止读取动作。

  • 文件的末尾(eval中字符串的末尾)
  • ^D(control D)、^Z(control Z)
  • 只包含__END__的行(若前后出现空白字符将无法辨认)
Originally Written by webmaster@ruby-lang.org
Translated by Community Engine China (Mail)
Last update on 2004-12-22


Ruby参考手册 - 变量和常数

变量和常数

  • 局部变量
  • 实例变量
  • 类变量
  • 全局变量
  • 伪变量
  • 常数
    • 引用常数的优先顺序

您可以通过区分Ruby变量名的首位字符来确定它是局部变量、实例变量、类变量、全局变量还是常数。通常情况下，变量名的第二位字符以后是数字、字母或下划线，但有的内部变量名比较特殊，如“'$'+1个符号”(请参考内部变量)。变量名长度只受内存大小的限制。

局部变量

例:

foobar

若标识符首位是小写字母或“_”，则该标识符就是局部变量或方法调用。在局部变量的作用域(类、模块、方法的定义部分)内，若对一个首位是小写字母的标识符进行首次赋值的话，也就意味着声明了一个属于该作用域的局部变量。若引用尚未被声明的标识符的话，就会被解释成一个无参数的方法调用。

局部变量的作用域起始于声明处，结束于该声明所在的块、方法定义、类／模块定义的结尾。随着块的消亡，局部变量也将寿终正寝(顶层局部变量则一直持续到程序终结)，但也有例外。若块已经变成过程对象的话，则局部变量将一直持续到该过程对象终结为止。若多个过程对象引用同一个作用域的话，局部变量将被这些对象所共享。

# (A)的部分位于作用域之外
2.times {
  p defined?(v)    # (A)
  v = 1            # 从(开始声明)起
  p v              # 到(块的终结)是 v 的作用域
}

# => nil
     1
     nil           <- 请注意这里是 nil
     1

即使声明部分未被执行仍将有效。

v = 1 if false # 虽未赋值，但声明有效
p defined?(v)  # => "local-variable"
p v            # => nil

若使用了 -K 选项的话就可以使用日语标识符，日语标识符被当作局部变量处理。实际上，我们并不推荐您这样做。

实例变量

例：

@foobar

以@开始的变量是实例变量，它属于特定的对象。可以在类或子类的方法中引用实例变量。若引用尚未被初始化的实例变量的话，其值为nil。

类变量

例：

class Foo
  @@foo = 1
  def bar
    puts @@foo
  end
end

以@@开始的变量是类变量。在类的定义中定义类变量，可以在类的特殊方法、实例方法等处对类变量进行引用／赋值。

类变量与常数的区别如下。

  • 可以重复赋值(常数则会发出警告)
  • 不能在类的外部直接引用(在继承类中则可以引用／赋值)

类变量与类的实例变量的区别如下。

  • 可在子类中引用／赋值
  • 可在实例方法中引用／赋值

可以把类变量看作一种被类、子类以及它们的实例所共享的全局变量。

class Foo
  @@foo = 1
end
class Bar < Foo
  p @@foo += 1          # => 2
end
class Baz < Bar
  p @@foo += 1          # => 3
end

模块中定义的类变量(模块变量)被所有包含该模块的类所共享。

module Foo
  @@foo = 1
end
class Bar
  include Foo
  p @@foo += 1          # => 2
end
class Baz
  include Foo
  p @@foo += 1          # => 3
end

全局变量

例：

$foobar
$/

以$开始的变量是全局变量，可以在程序的任何地方加以引用(因此需要特别留意)。全局变量无需变量声明。引用尚未初始化的全局变量时，其值为 nil。

伪变量

除普通的变量之外，还有一种叫做伪变量的特殊变量。

self

当前方法的执行主体

nil

NilClass类的唯一实例

true

TrueClass类的唯一实例

false

FalseClass类的唯一实例。nil 和 false 表示“伪”。

__FILE__

当前源文件名

__LINE__

当前源文件中的行号

伪变量的值不可改变，若对伪变量赋值将引发语法错误。

常数

例：

FOOBAR

以大写字母([A-Z])开始的标识符是常数.常数的定义(和初始化)由赋值过程完成.不能在方法中对常数进行定义.若对已定义的常数进行赋值的话,会出现警告信息.若引用未定义的常数的话,则会引发NameError异常.

可以在下列地方引用常数,如,定义常数的类/模块的定义句(也包括方法正文以及嵌套类/模块的定义句)中,继承该类的子类中,以及包含模块的类/模块中等等.在类定义之外(顶层)定义的常数属于Object.

例：

class Foo
  FOO = 'FOO'       # 定义Foo类的常数FOO(Foo::FOO)
end

class Bar < Foo
  BAR = 'BAR'       # 定义Bar类的常数BAR(Bar::BAR)

  # 可直接引用父类的常数
  p FOO             # => "FOO"
  class Baz

    # 虽然嵌套类与该类间不存在继承关系
    # 但还是可以直接引用嵌套外部的常数
    p BAR           # => "BAR"
  end
end

另外,在类定义表达式生成类对象的同时,还会将类对象赋值给一个与该类同名的常数.从语法上讲,引用类名也就是引用该常数.

class C
end
p C    # => C

若想在外部访问类或模块中的常数时,要使用"::"操作符.若想准确地访问Object类中的常数(顶层的常数)时,也需要也使用"::"操作符,但操作符左边为空.另外,不能使用该操作符对常数进行赋值.

ruby 1.8 特性: 可以使用"::"对常数进行赋值.

例：

module M
  I = 35
  class C
  end
end
p M::I   #=> 35
p M::C   #=> M::C
p ::M    #=> M

M::NewConst = 777   # error--> parse error

引用常数的优先顺序

若在父类和嵌套外侧存在同名常数时,会先引用嵌套外侧的常数.也就是说,引用常数时会先搜索嵌套关系的外侧,然后才会按照继承关系向上搜索.

例：

class Foo
  CONST = 'Foo'
end

class Bar
  CONST = 'Bar'
  class Baz < Foo
    p CONST             # => "Bar"      外侧的常数
    # 此时,若不显式地指定父类中的常数的话,则无法找到该常数
    p Foo::CONST        # => "Foo"
  end
end

一般认为顶层常数定义并不是位于嵌套外侧,所以在搜索了继承关系之后才能找到它.可见顶层常数的优先度很低.

例：

class Foo
  CONST = 'Foo'
end

CONST = 'Object'

class Bar < Foo
  p CONST               # => "Foo"
end

# 若能明显看出是嵌套关系的话,按规则来说
# 首先搜索到的是Object的常数(位于嵌套外侧)
class Object
  class Bar < Foo
    p CONST             # => "Object"
  end
end

若对与父类常数同名的常数进行赋值的话,则意味着在该类中定义一个新常数,而并不会对父类常数进行赋值.

例：

class Foo
  CONST = 'Foo'
end
class Bar < Foo
  p CONST               # => "Foo"
  CONST = 'Bar'         # *定义*Bar的常数CONST
  p CONST               # => "Bar"  (Foo::CONST 被屏蔽了)
  p Foo::CONST          # => "Foo"  (若使用:: 的话就可以了)
end
Originally Written by webmaster@ruby-lang.org
Translated by Community Engine China (Mail)
Last update on 2004-12-22


Ruby参考手册 - 字面值

字面值

  • 数值字面值
  • 字符串字面值
    • 反斜线表示法
    • 展开式
  • 命令输出
  • 集成字符串(here document)
  • 正则表达式字面值
  • 数组表达式
  • 哈希表表达式
  • 范围对象
  • 符号
  • %表示法

我们把形如数字1或字符串“hello world”这样可以直接写入Ruby程序中的值叫做字面值。

数值字面值

123
0d123 ruby 1.7 特性

整数

-123

有符号整数 trap::Numeric

123.45

浮点数 ruby 1.8 特性: 不能把0.1写成".1"，而只能完整地写为0.1。

1.2e-3

浮点数

0xffff

16进制整数

0b1011

2进制整数

0377
0o377 ruby 1.7 特性

8进制整数

?a

字符a的代码(97)

ruby 1.7 特性: 用在空白类字符上时，必须写成这样，?\s、?\t等。

?\C-a

Control a 的代码(1)

?\M-a

Meta a 的代码(225)

?\M-\C-a

Meta-Control a 的代码(129)

在?句中，所有的反斜线表示法都是有效的。

您可以在除了字符代码以外的数值字面值中插入“_”。Ruby解释器会忽视“_”而不作处理。当数值位数非常多时，使用“_”会让您快速看清其位数。但是不能在字面值的前面或后面插入“_”(若在字面值前面(包括(+,-)号与字面值间的位置)插入_时，它会被误认成局部变量或方法调用)。

1_000_000_000  => 1000000000
0xffff_ffff  => 0xffffffff

字符串字面值

例:

"this is a string expression\n"
'this is a string expression\n'
%q!I said, "You said, 'She said it.'"!
%!I said, "You said, 'She said it.'"!
%Q('This is it.'\n)

字符串被双引号或单引号包围着。在被双引号中括起来的字符串中，反斜线表示法和展开式(后面详述)都是有效的。在被单引号括起来的字符串中，除了\\(反斜线本身)、\'(单引号)和行尾的\(忽略换行)以外，不会对字符串内容作任何处理。

中间留有空白的字符串在编译时会被当作1个字符串字面值来处理。

p "foo" "bar"
=> "foobar"

还有其他一些使用%表示法的字符串项目。

每次对字符串表达式进行计算时都会生成新的字符串对象。

反斜线表示法

\t

Tab(0x09)

\n

换行(0x0a)

\r

回车(0x0d)

\f

换页(0x0c)

\b

BackSpace(0x08)

\a

Bell (0x07)

\e

Escape(0x1b)

\s

空白(0x20)

\nnn

8 进制数表示 (n 为 0-7)

\xnn

16 进制数表示 (n 为 0-9,a-f)

\cx
\C-x

Control字符 (x 为 ASCII 字符)

\M-x

Meta x (c | 0x80)

\M-\C-x

Meta Control x

\x

字符 x 本身

展开式

例:

(假设$ruby = "RUBY")

   "my name is #{$ruby}" #=> "my name is RUBY"
   'my name is #{$ruby}' #=> "my name is #{$ruby}"

在以双引号(")括起来的字符串表达式、命令字符串以及正则表达式中，可以使用“#{表达式}”把表达式的内容(以字符串的形式)嵌入其中。若表达式是以($,@)开始的变量的话，则可以省略{}部分，只使用“#变量名”的形式即可。如果在字符#后出现的是“{,$,@”以外的其他字符的话，字符#仍代表"#"本身。若想显式地阻止展开式的话，在#前添加反斜线即可。

展开式中可以包括双引号和其他的Ruby表达式。注释也可以纳入其中。

p "#{ "string" # comment }"   # => "string"

ruby 1.7 特性: 在version 1.7中，展开式中的注释内容开始于“#”终止在行尾，而非“}”。因此，上例应该写成

p "#{ "string" # comment
  }"                          # => "string"

这个样子。

命令输出

例:

`date`
%x{ date }

被后引号(`)括起来的字符串与被双引号括起来的字符串是一样的。首先会处理其中的反斜线表示法和展开式，然后才会执行该命令。命令的标准输出是字符串。每次计算命令时都会把它执行一次。若想得到命令的结束status，请参考$?。

还有其他一些使用%表示法的命令输出。

here document - 集成字符串 (多行字符串的字面值)

语法:

<<[-]["'`]标识符["'`]
   ...
标识符

普通的字符串字面值是指使用分隔符(", ', ` 等)括起来的字符串部分。集成字符串是指，把从包含“<<标识符”的行的下一行开始，到只包含“标识符”的行的行首之间的内容封装起来之后得到的多行字符串(的字面值)。例如

print <<EOS      # (从这里开始)直到标识符 EOS 之前的部分就是字面值
  the string
  next line
EOS

等同于下例

print "  the string\n  next line\n"

集成字符串中的开始标签“<<标识符”具有非常重要的语法意义。您可以使用开始标签，将集成字符串的内容传递给某参数，或者把集成字符串本身当作被调(receiver)使用。

# 在表达式中加入开始标签
# 把集成字符串的内容传递给method的第2个参数
method(arg1, <<LABEL, arg2)
    集成字符串的具体内容
LABEL

# 将集成字符串用作被调
p  <<LABEL.upcase
the lower case string
LABEL

# => "THE LOWER CASE STRING"

开始标签的下一行通常是集成字符串的具体内容。例如，下面的写法是错误的。

printf('%s%d',
       <<EOS,
       3055 * 2 / 5)   # <- 该行已经混入集成字符串的内部
This line is a here document.
EOS

若把开始标签写成“<<-标识符”的话，表示集成字符串的最后一行将使用缩进格式。除此以外，您不能在最后一行中加入空白或是注释。

if need_define_foo
  eval <<-EOS   # 使用'<<-'后
    def foo
      print "foo\n"
    end
  EOS
  #↑最后一行使用了缩进格式。
end

您还可以在同一行中使用多个集成字符串。

print <<FIRST, <<SECOND
   这里是第一个集成字符串的内容。
   这里也是。
FIRST
   从该行开始第二个集成字符串的内容。
   第二个集成字符串将在本行后面结束。
SECOND

您可以使用引号(""、''或``)将开始标签“<<标识符”中的标识符括起来。这时，集成字符串的性质将取决于您所选择的引号的种类。请参考下列，当您选择双引号时，其效果与不使用任何引号时的效果相同。

# 反斜线表示法和展开式都是有效的
print <<"EOS"
The price is #{$price}.
EOS

# 效果同上
print <<EOS
The price is #{$price}.
EOS

# 展开式无法工作
print <<'EOS'
The price is #{$price}.
EOS

# 执行命令
print <<`EOC`
date
diff test.c.org test.c
EOC

请参考 字符串字面值、 展开式、 反斜线表示法、 命令输出 来了解字符串字面值的性质

正则表达式字面值

例:

/^Ruby the OOPL/
/Ruby/i
/my name is #{myname}/o
%r|Ruby|

被/括起来的部分就是正则表达式。关于正则表达式中元字符的用法问题，请参考正则表达式。

尾部/后面的字符是正则表达式中使用的选项。具体内容如下。

i

匹配时不区分字母大小写

o

开始计算正则表达式时只处理一次展开式

x

正则表达式中的空白(包括换行)将被忽视。同时，从"#"到行尾的部分将被当作注释处理(请注意，若注释中包含／则会引起语法解析错误)。

/foo        # 注释
 bar/x

等同于 /foobar/。

若想包含空白，可以像\一样使用转义。

m

多行模式。正则表达式 "."将会匹配换行。

因为Ruby的日语化做得比较好，您可以设定$KCODE使其能够正确处理日语字符。若$KCODE =“n”时，Ruby将不会识别任何日语字符，而把它们当作字节串处理，这是默认状态。

您还可以摆脱$KCODE的束缚，分别为每个正则表达式指定n, e, s, u中的任意一个选项来确定该正则表达式的字符代码。

您还可以使用%表示法来指定其他形式的正则表达式。

在正则表达式中，反斜线表示法和 展开式也是有效的。

若正则表达式字面值中不含展开式的话，则对其进行计算时始终返回同一个正则表达式对象。若含有展开式的话，则每次(以展开式的结果为基础)进行计算时都会编译正则表达式并生成正则表达式对象(但是，若指定了上述的o选项的话，则返回同一个正则表达式对象)。

数组表达式

例:

[1, 2, 3]
%w(a b c)
%W(a b c)             ((<ruby 1.7 特性>))

语法:

`[' 表达式`,' ... `]'

分别计算每个表达式的值，然后返回由这些值构成的数组。数组是Array类的实例。

当数组元素都是字符串字面值时，可以使用%表示法将数组写成其他形式。

每次对数组表达式进行计算时都会返回一个新的数组对象。

哈希表表达式

例:

{1=>2, 2=>4, 3=>6}

语法:

`{' 表达式 `=>' 表达式 `,' ... `}'
`{' 表达式 `,' 表达式 `,' ... `}'

分别计算每个表达式的值，然后返回由索引和值构成的哈希表对象。所谓哈希表是指将任意对象当作索引而构成的数组，它是Hash类的实例。

当向某方法的参数尾部传递哈希表，且该哈希表中的元素超过1个时，可以省略{,}。但是，在形如obj[...]的方法调用以及数组表达式中，当所有元素都是哈希表时，才可以省略{,}。

例:

method(1,2,3=>4)      # method(1,2,{3=>4})
obj[1=>2,3=>4]        # obj[{1=>2,3=>4}]
[1=>2,3=>4]           # [{1=>2, 3=>4}]

每次对哈希表表达式进行计算时都会生成一个新的哈希表对象。

范围对象

请参考范围表达式

若范围表达式两端都是数值字面值时，每次计算都将返回同一个对象。除此以外，每次计算时都会返回新的范围对象。

符号

例:

(符号的例子)

      :class
      :lvar
      :method!
      :andthisis?
      :$gvar
      :@ivar
      :@@cvar
      :+

语法:

`:' 标识符
`:' 变量名
`:' 操作符

Symbol类的实例。字符串与Symbol对象是一一对应的。

指定给Symbol字面值的操作符，必须是那些可以作为方法进行再定义的操作符。请参考操作符表达式。

ruby 1.7 特性: 在1.7中，也可以这样

p :'foo-bar' #=> :"foo-bar"
p :"foo-bar" #=> :"foo-bar"
p %s{foo-bar} #=> :"foo-bar"

在这种表示法中，可以定义任意的符号(但不能包含“\0”)。

在:"..." 中，反斜线表示法和 展开式都是有效的。

通常符号是一种很专一的对象，计算时(就算包含展开式也好，只要其结果是同一字符串的话)总是返回同一对象。

%表示法

在字符串字面值、 命令输出、 正则表达式字面值、 数组表达式、 符号 中，可以使用这种以％开头的表达法。特别是当字符串或正则表达式中包含双引号(")、斜线(/)等(切分字面值时使用的字符)要素时，它可以减少反斜线(\)在代码中出现的次数。另外，它还可以方便地表示字符串数组。详细内容如下。

  • %!STRING! : 双引号中的字符串
  • %Q!STRING! : 同上
  • %q!STRING! : 单引号中的字符串
  • %x!STRING! : 命令输出
  • %r!STRING! : 正则表达式
  • %w!STRING! : 字符串数组(以空白字符切分各元素)
  • %W!STRING! : 字符串数组(以空白字符切分各元素)。展开式、反斜线表示法都有效(ruby 1.7 特性)
  • %s!STRING! : 符号。展开式、反斜线表示法都无效(ruby 1.7 特性)

!部分可以使用任何非字母非数字的字符，包括换行。若起始切分字符是括号("(","[","{","<")的话，则终止切分字符就是与其对应的括号。若以括号作为切分字符的话，只要对应部分使用了对应的括号，就可以在其中的元素中使用与切分字符一样的括号。

%(()) => "()"

若在数组表达式中使用%表示法的话，相当于使用空白字符将数组中以单引号括起来的字符串元素分隔开来。例如，

%w(foo bar baz)

与['foo', 'bar', 'baz']是一样。

还可以使用反斜线将空白字符纳入数组元素。

%w(foo\ bar baz)

=> ["foo bar", "baz"]

ruby 1.7 特性:%W与%w相同，就如同双引号中的字符串一样，可以使用展开式和反斜线表示法。但使用空白字符进行切分的过程要早于展开式的计算过程。

v = "c d"
%W(a\ b #{v}e\sf #{})

=> ["a b", "c de f", ""]
Originally Written by webmaster@ruby-lang.org
Translated by Community Engine China (Mail)
Last update on 2004-12-22


Ruby参考手册 - 操作符表达式

操作符表达式

  • 赋值
    • 计算赋值
    • 多重赋值
  • 范围表达式
  • and
  • or
  • not
  • 条件操作符

例:

1+2*3/4

为了便于编程，有些方法调用和控制结构是以操作符的形式出现的。Ruby语言中有下列操作符。

高     ::
       []
       +(单项)  !  ~
       **
       -(单项)
       *  /  %
       +  -
       << >>
       &
       |  ^
       > >=  < <=
       <=> ==  === !=  =~  !~
       &&
       ||
       ..  ...
       ?:(条件操作符)
       =(+=, -= ... )
       not
低     and or

左侧的“高”和“低”表示操作符的优先度。例如“&&”的优先度要高于“||”，所以

a && b || c   #=> (a && b) || c
a || b && c   #=>  a || (b && c)

大部分操作符都是些特殊形式的方法调用，但有的操作符被嵌入语言之中，无法进行再定义。

  • 可再定义的操作符(方法):

    +@, -@表示单项操作符+, -，在方法定义等场合中可以使用这种表示法。

    |  ^  &  <=>  ==  ===  =~  >   >=  <   <=   <<  >>
+  -  *  /    %   **   ~   +@  -@  []  []=  `

  • 不可再定义的操作符(控制结构):

    由多个操作符组合而成的计算赋值操作符、“!=”以及“!~”是不能再定义的。

    =  ?:  ..  ...  !  not  &&  and  ||  or  ::

赋值

例:

foo = bar
foo[0] = bar
foo.bar = baz

语法:

变量 '=' 表达式
常数 '=' 表达式
表达式`['expr..`]' '=' 表达式
表达式`.'标识符 '=' 表达式

我们使用赋值表达式向变量等对象进行赋值。赋值也可以用作局部变量和常数的声明。赋值表达式的左边必须是下列之一。

  • 变量

    变量 `=' 表达式

    若左边是变量的话，就将表达式的计算值代入其中。

  • 数组调用

    表达式1`[' 表达式2 ... `]' `=' 表达式n

    先计算表达式1得到一个对象，再把从表达式2到表达式n之间的这些表达式作为参数，来调用该对象的"[]="方法。

    例:

    class C
  def initialize
    @ary = [0,1,2,3,4,5,6,7]
  end
  def [](i)
    @ary[i * 2]
  end
  def []=( i, v )
    @ary[i * 2] = v
  end
end
c = C.new
p c[3]      # 变成 c.[]( 3 )， 结果为6
p c[3] = 1  # 变成 c.[]=(3,1)，结果为1

  • 属性调用

    表达式1 `.' 标识符 `=' 表达式2

    计算表达式1得到一个对象，再以表达式2为参数来调用该对象的"标识符="方法。

    例:

    class C
  def foo
    @foo
  end
  def foo=( v )
    @foo = v
  end
end
c = C.new
c.foo = 5   # 变成 c.foo=( 5 )
p c.foo     # => 5

    还可以使用 attr 来定义属性。

    例:

    class C
  attr :foo, true
end
c = C.new
c.foo = 5   # 变成 c.foo=( 5 )
p c.foo     # => 5

计算赋值

例:

foo += 12       # foo = foo + 12
a ||= 1         # 若a为伪或者a尚未定义，就把1赋值给a。初始化时的一种习惯写法。

语法:

表达式1 op= 表达式2     # 表达式1等同于普通赋值表达式左边的部分

op就是下列中的某一个。操作符与=之间不留间隔。

+, -, *, /, %, **, &, |, ^, <<, >>, &&, ||

这种赋值形式等同于

表达式1 = 表达式1 op 表达式2

但是，当op是“&&”或“||”时就会变成

表达式1 op (表达式1 = 表达式2)

这个样子。例如在属性调用时

obj.foo ||= true

就不能解释成

obj.foo = obj.foo || true

而应该是

obj.foo || (obj.foo = true)

这样才对。(根据obj.foo结果的不同，有时就不能调用obj.foo=)

多重赋值

例:

foo, bar, baz = 1, 2, 3
foo, = list()
foo, *rest = list2()

语法:

表达式 [`,' [表达式 `,' ... ] [`*' [表达式]]] = 表达式 [, 表达式 ... ][`*' 表达式]
`*' [表达式] = 表达式 [, 表达式 ... ][`*' 表达式]

多重赋值是指，在多个表达式以及数组中同时进行的赋值。左边的各个表达式必须是可以被赋值的。若右边只有一个表达式时，则将该表达式的计算值转为数组后，再把数组中的各个元素依次赋值给左边。若右边数组元素的数量超过左边的话，将忽略多余的数组元素。若右边数组元素个数不足的话，将向左边多余的元素中代入nil。

若左边最后一个表达式前带*的话，则将右边多余的元素以数组的形式代入这个带*的表达式中。若右边没有多余元素的话，就把空数组代入其中。

例:

foo, bar = [1, 2]       # foo = 1; bar = 2
foo, bar = 1, 2         # foo = 1; bar = 2
foo, bar = 1            # foo = 1; bar = nil

foo, bar, baz = 1, 2    # foo = 1; bar = 2; baz = nil
foo, bar = 1, 2, 3      # foo = 1; bar = 2
foo      = 1, 2, 3      # foo = [1, 2, 3]
*foo     = 1, 2, 3      # foo = [1, 2, 3]
foo,*bar = 1, 2, 3      # foo = 1; bar = [2, 3]

在多重赋值过程中，若使用括号的话，就可以把数组内含的元素取出来进行赋值。

(foo, bar), baz = [1, 2], 3       # foo = 1; bar = 2; baz = 3

还可以把特殊形式的赋值表达式转化为多重代入。

class C
  def foo=( v )
    @foo = v
  end
  def []=(i,v)
    @bar = ["a", "b", "c"]
    @bar[i] = v
  end
end

obj = C.new
obj.foo, obj[2] = 1, 2     # @foo = 1; @bar = ["a", "b", 2]

若左边以","结尾或"*"后面没有表达式时，将忽略右边多余的元素。

例:

foo,*    = 1, 2, 3      # foo = 1
foo,     = 1, 2, 3      # foo = 1
*        = 1, 2, 3

当您想忽略方法调用中的所有参数时，就可以使用这个"*"(方法调用中的参数在进行交接时，大体遵循多重代入的规则)。

例:

def foo(*)
end
foo(1,2,3)

多重赋值的值变换为右边的数组。

范围表达式

例:

1 .. 20
/first/  ...  /second/

语法:

表达式1 `..' 表达式2
表达式1 ` ... ' 表达式2

如果不是出现在条件表达式中的话，它将返回从表达式1到表达式2的范围对象。范围对象是 Range 类的实例。使用"..."生成的范围对象将不包括终点。

若它出现在条件表达式中时，在表达式1变成真之前，它将一直返回伪。接下来，在表达式2返回真之前，它将一直返回真。一旦表达式2变为真，它将重新返回伪。使用".."时，一旦表达式1返回真，马上就会对表达式2进行计算(就象awk)。而使用"..."时，在进行下个计算之前，一直都不会对表达式2进行计算(就象sed)。

and

例:

test && set
test and set

语法:

表达式 `&&' 表达式
表达式 `and' 表达式

首先计算左边，若结果为真就接着计算右边。"and"操作符的作用相同但优先度更低。

将包含and的表达式传递给某方法的参数时，必须使用双层括号。

p(true && false)    #=> false
p((true and false)) #=> false

or

例:

demo || die
demo or die

语法:

表达式 `||' 表达式
表达式 or 表达式

首先计算左边，若结果为伪就接着计算右边。"or"操作符的作用相同但优先度更低。

将包含or的表达式传递给某方法的参数时，必须使用双层括号。

p(false || true)    #=> true
p((false or true)) #=> true

not

例:

! me
not me
i != you

语法:

`!' 表达式
not 表达式

若表达式值为真就返回伪，若表达式值为伪则返回真。

表达式 `!=' 表达式

等同于!(表达式 == 表达式)。

表达式 `!~' 表达式

等同于!(表达式 =~ 表达式)。

将包含not的表达式传递给某方法的参数时，必须使用双层括号。

p(! false)      #=> true
p((not false))  #=> true

条件操作符

例:

obj == 1 ? foo : bar

语法:

表达式1 ? 表达式2 : 表达式3

根据表达式1的结果，选择返回表达式2或表达式3。它与

if 表达式1 then 表达式2 else 表达式3 end

完全相同。

Originally Written by webmaster@ruby-lang.org
Translated by Community Engine China (Mail)
Last update on 2004-12-22


Ruby参考手册 - 控制结构

控制结构

  • 条件分支
    • if
    • if 修饰句
    • unless
    • unless 修饰句
    • case
  • 循环
    • while
    • while 修饰句
    • until
    • until修饰句
    • for
    • break
    • next
    • redo
    • retry
  • 异常处理
    • raise
    • begin
    • rescue修饰句
  • 其他
    • return
    • BEGIN
    • END

(与C等语言不同的是)Ruby的控制结构是表达式，其中的一部分还会返回值(也有不返回值的，若把这些不返回值的表达式放在赋值表达式右边的话，就会引发 parse error)。

Ruby中包括从C和Perl那里继承来的控制结构，还包括一种可以将控制结构抽象化的功能，即 带块的方法调用。带块的方法调用使类的设计者可以自己定义一些包括循环在内的控制结构。

条件分支

if

例:

if age >= 12 then
  print "adult fee\n"
else
  print "child fee\n"
end
gender = if foo.gender == "male" then "male" else "female" end

语法:

if 表达式 [then]
  表达式 ...
[elsif 表达式 [then]
  表达式 ... ]
...
[else
  表达式 ... ]
end

若条件表达式的计算结果为真时，将计算then以下的表达式。若if的条件表达式为伪时，将计算elsif的条件部分。可以存在若干个elsif部分，若所有的if以及elsif的条件表达式都为伪的话，如果有else部分，就计算它的表达式。

if 表达式的结果取决于条件成立部分(或else部分)中最后被计算的表达式的结果。若没有else部分，且所有条件均不成立的话，就返回nil。

Ruby中只有false和nil代表伪，其他都是真，甚至0或空字符串也是如此。

请注意，在Ruby中，和if对应的是elsif，而并非else if(C的语句)或者elif(sh的语句)。

另外，当if 条件表达式中出现正则表达式字面值时，将作如下处理

$_ =~ 字面值

if 修饰句

例:

print "debug\n" if $DEBUG

语法:

表达式 if 表达式

当右边的条件成立时，计算左边的表达式，并返回其结果。若条件不成立则返回nil。

unless

例:

unless baby?
  feed_meat
else
  feed_milk
end

语法:

unless 表达式 [then]
  表达式 ...
[else
  表达式 ... ]
end

unless与if相反，当条件表达式结果为伪时，才计算then后面的表达式。unless表达式中不能安插elsif语句。

unless 修饰句

例:

print "stop\n" unless valid(passwd)

语法:

表达式 unless 表达式

当右边的条件不成立时，计算左边的表达式，并返回其结果。若条件不成立时返回nil。

case

例:

case $age
when 0 .. 2
  "baby"
when 3 .. 6
  "little child"
when 7 .. 12
  "child"
when 13 .. 18
  "youth"
else
  "adult"
end

语法:

case [表达式]
[when 表达式 [, 表达式] ...[, `*' 表达式] [then]
  表达式..]..
[when `*' 表达式 [then]
  表达式..]..
[else
  表达式..]
end

case先对一个表达式进行匹配判断，然后根据匹配结果进行分支选择。它使用"==="操作符比较when的指定值和最初那个表达式的计算值，若一致的话就计算when部分的内容。

也就是说

case 表达式0
when 表达式1, 表达式2
  stmt1
when 表达式3, 表达式4
  stmt2
else
  stmt3
end

基本上等同于下面的if表达式。

_tmp = 表达式0
if 表达式1 === _tmp or 表达式2 === _tmp
  stmt1
elsif 表达式3 === _tmp or 表达式4 === _tmp
  stmt2
else
  stmt3
end

when 部分的计算顺序同上面这个if句的计算顺序是相同的。即从上到下(从左到右)地计算"==="。另外，“表达式0”只计算一次。

若when 部分中的最后一个表达式前带"*"的话，该表达式将被当作数组展开。

ary = [1,2,3]

case v
when *ary
 ..
end

等同于

case v
when 1, 2, 3
 ..
end

请参考描述各个类中"==="方法技术细节的文档，来了解"==="在何种条件下为真。

当case的“表达式”部分被省略时，将计算第一个when条件部分为真的表达式。

foo = false
bar = true
quu = false

case
when foo then puts 'foo is true'
when bar then puts 'bar is true'
when quu then puts 'quu is true'
end
# 显示 "bar is true"

case将返回条件成立的when部分(或else部分)中最后被计算的表达式的结果。若所有条件都不成立的话，则返回nil。

循环

while

例:

ary = [0,2,4,8,16,32,64,128,256,512,1024]
i = 0
while i < ary.length
  print ary[i]
  i += 1
end

语法:

while 表达式 [do]
   ...
end

只要表达式的计算值为真，就循环执行while语句中的内容。while不返回值。

ruby 1.7 特性: while返回nil。另外，可以使用带参数的break，将while表达式的返回值设为那个参数的值。

while 修饰句

例:

sleep(60) while io_not_ready?

语法:

表达式 while 表达式

只要右边表达式的计算值为真，就循环执行左边部分。

若左边表达式是begin，且即不含rescue，又不含ensure的话，则只在开始时计算一次然后就执行循环。

ruby 1.7 特性: 在version 1.7中，即使出现rescue/ensure部分也会作相同处理。

例:

send_request(data)
begin
  res = get_response()
end while res == 'Continue'

while 修饰的表达式没有返回值。

ruby 1.7 特性: while修饰的表达式返回nil。另外，可以使用带参数的break，将while修饰的表达式的返回值设为那个参数的值。

until

例:

until f.eof?
  print f.gets
end

语法:

until 表达式 [do]
   ...
end

在表达式的计算值变为真之前，一直循环执行until中的内容。until不返回值。

ruby 1.7 特性: until 返回 nil。另外，可以使用带参数的break，将until表达式的返回值设定为那个参数的值。

until修饰句

例:

print(f.gets) until f.eof?

语法:

表达式 until 表达式

在右边表达式的计算值变为真之前，一直循环执行左边部分。

若左边表达式是begin，且即不含rescue，又不含ensure的话，则只在开始时计算一次然后就执行循环。

ruby 1.7 特性: 在version 1.7中，即使出现rescue/ensure部分也会作相同处理

例:

send_request(data)
begin
  res = get_response()
end until res == 'OK'

until修饰的表达式没有返回值。

ruby 1.7 特性: until修饰的表达式返回nil。另外，可以使用带参数的break，将until修饰的表达式的返回值设为那个参数的值。

for

例:

for i in [1, 2, 3]
  print i*2, "\n"
end

语法:

for lhs ...  in 表达式 [do]
  表达式..
end

先计算表达式得到一个对象，然后分别针对该对象中的每个要素，循环执行for的内容。这基本等价于

(表达式).each `{' `|' lhs..`|' 表达式.. `}'

之所以说“基本”是因为，do...end以及由{}构成的块中导入了新的局部变量的有效范围，而for语句对于局部变量的有效范围没有任何影响。

for将返回in所指对象的each方法的返回值。

若想使用多个循环变量的话，可以这样

for i,j in [[1,2], [3,4], [5,6]]
  p [i,j]
end
=> [1, 2]
   [3, 4]
   [5, 6]

使用for或each时，每次只能取一个数组元素进行循环，而不能一次取多个。

for i,j in [1, 2, 3]
  p [i,j]
end

=> [1, nil]
   [2, nil]
   [3, nil]

# 可能您希望这样[1,2] [3,nil]，但实际上这是行不通的

您必须自己定义这样的方法(迭代器)。请参考 each。

class Array
  def each2
    i = 0
    while i < self.size
      yield self[i], self[i+1]
      i += 2
    end
  end
end

break

例:

i = 0
while i < 3
  print i, "\n"
  break
end

语法:

break

break val             ruby 1.7 特性

break将退出最内层的循环。所谓循环是指，下列之一

  • while
  • until
  • for
  • 迭代

与C语言不同，break只能从循环中退出，而不能从case中退出。

若使用break退出for或迭代循环后，该循环将返回nil。ruby 1.7 特性:但如果使用了参数的话，循环将返回那个参数的值。

next

例:

# 忽略空行的cat
ARGF.each_line do |line|
  next if line.strip.empty?
  print line
end

语法:

next

next val              ruby 1.7 特性

next将跳转到最内侧循环的头部。在迭代器中，它将跳离yield调用。

使用next跳离yield后，yield表达式将返回nil。ruby 1.7 特性:但如果使用了参数的话，yield表达式的返回值就是该参数的值。

redo

例:

redo

语法:

redo

不检查循环条件，重新开始当前循环。

retry

例:

retry

语法:

retry

在迭代、块或for语句中使用retry，意味着重启迭代器。同时迭代器的参数也将被重新计算。

for i in 1..5
  retry if some_condition # 从 i == 1 开始重新执行
end

# 用户定义的 "until循环"
def UNTIL(cond)
  return if cond
  yield
  retry
end

除了循环以外，还可以在rescue部分(后述)中使用retry。这时将从begin表达式开始重新执行。使用retry可以在某处理过程成功之前，一直循环该处理过程。

begin
  do_something # exception raised
rescue
  # handles error
  retry  # restart from beginning
end

若在rescue部分、迭代器块或for语句之外使用retry的话会引发LocalJumpError异常。

归纳一下，在迭代调用中使用break, next, redo, retry的作用如下。

def iter
 (a)
  :
 (b)
 yield
 (c)
  :
 (d)
end
iter { retry }   -> 跳到 (a)
iter { redo  }   -> 跳到 (b)
iter { next  }   -> 跳到 (c)
iter { break }   -> 跳到 (d)

严格地讲(a)是从计算参数开始的。(b)指的是即将开始执行块的时候(yield的参数不会被再次计算)。(d)指的是方法的终结。

def iter(var = p("(a)"))
  p " : "
  yield
  p "(c)"
  p " : "
ensure
  p "(d)"
end

iter { p "(b)"; retry }     # => (a) .. (b)(d)(a) .. (b)(d)(a) ...
iter { p "(b)"; redo  }     # => (a) .. (b)(b)(b)(b) ...
iter { p "(b)"; next  }     # => (a) .. (b)(c) .. (d)
iter { p "(b)"; break }     # => (a)..(b)(d)

异常处理

raise

例:

raise "you lose"  # 引发RuntimeError异常
# 下面两个将引发SyntaxError异常
raise SyntaxError, "invalid syntax"
raise SyntaxError.new("invalid syntax")
raise             # 再次引发上一个异常

语法:

raise
raise message或exception
raise error_type, message
raise error_type, message, traceback

引发异常。第一句将再次引发上一个异常。第二句中，若参数是字符串的话，就把它当作错误信息(message)再引发RuntimeError异常。若参数为异常对象则引发该异常。第三句中，将引发第一个参数所指的异常，并以第二个参数的内容作为错误信息。第四句中，第三参数装载的是源自于$@或caller的堆栈信息，它指明发生异常的地点。

可以使用begin表达式的rescue部分来捕捉异常。这时使用rescue error_type => var就可以得到异常对象。您还可以从内部变量$!中获得这个对象。另外，变量$@中装载的是发生异常的源代码位置。

raise并不是Ruby的保留字，它是Kernel模块中定义的函数式的方法。

begin

例:

begin
  do_something
rescue
  recover
ensure
  must_to_do
end

语法:

begin
  表达式..
[rescue [error_type,..] [=> evar] [then]
  表达式..]..
[else
  表达式..]
[ensure
  表达式..]
end

若给出了rescue部分(可以有若干个)的话，就可以在发生异常时捕捉到它。若存在与异常类型一致的rescue部分的话，就执行rescue的内容。可以使用$!来查看异常的情况。另外，若事先设定了变量evar的话，它也可以像$!一样存储那些异常的信息。

begin
  raise "error message"
rescue => evar
  p $!
  p evar
end
# => #<RuntimeError: error message>
     #<RuntimeError: error message>

rescue部分使用Object#kind of?来判断刚才的异常的类是否就是自己期待的异常类，或者这二者是否处于父类／子类的关系。

若error_type被省略，则将捕捉StandardError的子类中的所有异常。Ruby的内部异常(除了SystemExit和Interrupt这些退出命令以外)是StandardError的子类。

请参考异常类来了解异常类的层次关系。

在rescue部分中，error_type与普通的参数一样接受计算，若符合的话就执行相应部分的内容。若error_type的计算值既非类又非模块的话，则引发TypeError异常。

若运行过程中没发生异常，则开始计算可选的else部分。

若存在ensure部分的话，则在begin表达式结束之前一定会计算它。

begin表达式整体的计算值取决于，begin的内容部分/rescue部分/else部分中最后被计算的句子的值。若各部分中均无语句时，其值为nil。不管怎样，ensure部分的值始终会被忽略。

rescue修饰句

例:

open("nonexistent file") rescue STDERR.puts "Warning: #$!"

语法:

表达式1 rescue 表达式2

若表达式1中发生异常时就计算表达式2。这等同于下例。不能指定想捕捉的异常类。(也就是说，只能捕捉StandardError异常类的子类了)

begin
  表达式1
rescue
  表达式2
end

在包括rescue修饰句的表达式中，若没发生异常则返回表达式1的值，若发生异常则返回表达式2的值。但在大多数场合中，因为考虑到优先度的问题，所以需要使用括号将整个表达式括起来。

var = open("nonexistent file") rescue false
p var
=> nil      # 因为只定义了一个空变量var

var = (open("nonexistent file") rescue false)
p var
=> false

特别是传递给某方法的参数时，有必要使用双重括号。

p(open("nonexistent file") rescue false)
=> parse error

p((open("nonexistent file") rescue false))
=> false

ruby 1.7 特性: 在1.7中，rescue的优先度发生了变化，因此免去了这些烦恼。

var = open("nonexistent file") rescue false
p var
=> false

p(open("nonexistent file") rescue false)
=> false

其他

return

例:

return
return 12
return 1,2,3

语法:

return [表达式[`,' 表达式 ... ]]

结束方法的运行，且把表达式的值设定为方法的返回值。若给出了2个以上的表达式，则将把这些表达式化为一个数组，然后把该数组设定为方法的返回值。若省略表达式，将返回值设为nil。

BEGIN

例:

BEGIN {
   ...
}

语法:

BEGIN '{' 语句.. '}'

注册初始化例程(routine)。BEGIN块所指定的语句的执行顺序将先于该文件中任何语句。若有多个BEGIN块的话，将按照出现顺序依次执行。

BEGIN块在编译时被注册。也就是说，同一条语句只会被注册一次。

if false
  BEGIN { p "begin" }
end

# => "begin"

BEGIN块引入了独立的局部变量作用域，因此不能和外部共享局部变量。为了与块外交换信息，必须借助于常数或全局变量。

BEGIN { $foo, foo = true, true }
p $foo  # => true
p foo   # undefined local variable or method `foo' for main:Object (NameError)

BEGIN不能出现在方法定义表达式中，否则会引发 parse error。

def foo
  BEGIN { p "begin" }
end
# => -:2: BEGIN in method

END

例:

END {
   ...
}

语法:

END '{' 语句.. '}'

注册“善后”例程。END块中指定的语句会在解释器结束前得到执行。关于Ruby退出程序时的相关处理问题，请参考结束时的相关处理。

若注册了若干END块的话，则以与注册时相反的顺序依次执行这些块。

END { p 1 }
END { p 2 }
END { p 3 }

# => 3
     2
     1

END块中同一条语句只会执行一次。如下例，即使把END块置入循环中，也只会注册一次。若想实现复用，请使用 at_exit。

5.times do |i|
  END { p i }
end
# => 0

若把END块置入方法定义表达式中会引起警告。若有意如此，请使用at_exit。

def foo
  END { p "end" }
end
p foo

# => -:2: warning: END in method; use at_exit
     nil
     "end"

END与BEGIN不同的是，它在运行时进行注册。因此，下例中的END块将不会运行。

if false
  END { p "end" }
end

END和at_exit中注册的善后处理无法取消。

END块与BEGIN块不同的是，它同周围部分共享作用域。也就是说，它的作用域同迭代器一样。

若END块中发生了异常，将中断该块。但解释器并不结束，只是发出信息，并且试图处理完所有的善后例程。

例:

END { p "FOO" }
END { raise "bar"; p "BAR" }
END { raise "baz"; p "BAZ" }

=> baz (RuntimeError)
   bar (RuntimeError)
   "FOO"
Originally Written by webmaster@ruby-lang.org
Translated by Community Engine China (Mail)
Last update on 2004-12-22


Ruby参考手册 - 方法调用

方法调用

  • super
  • 带块的方法调用
  • yield

例:

foo.bar()
foo.bar
bar()
print "hello world\n"
print
Class::new

语法:

[表达式  `.'] 标识符 [`(' 表达式 ... [`*' [表达式]],[`&' 表达式] `)']
[表达式 `::'] 标识符 [`(' 表达式 ... [`*' [表达式]],[`&' 表达式] `)']

方法调用表达式表示调用被调(receiver,"."左侧表达式的值)的方法。若未指定被调，则调用self的方法。

"."与"::"的意义大体相同。但表示常数时，必须使用"::"(例：Math::PI，Errno::ERANGE)。若写成

Klass::Foo

的话，通常会被认为是常数。请注意，通常是将"::"用作类方法的调用。若方法名是以大写字母开始的时候，要写成

Klass.Foo

或者写成

Klass::Foo()

但要用括号显式地表明这是方法调用。

方法名中除了通常的标识符以外，还可以添加"?"或"!"等后缀。通常在布尔型(返回真或伪的方法)方法名后添加"?"，在比同名(无"!")方法更具破坏性的方法名(例：tr和tr!)后添加"!"。

若最后一个参数带"*"的话，将会先展开该参数的值，然后才传递。也就是：

foo(1,*[2,3,4])
foo(1,*[])

和下例

foo(1,2,3,4)
foo(1)

等效。

若最后一个参数带"&"，则该参数指定的过程对象(Proc)以及方法对象(Method)等会被当作一个块传递给方法。详情请参考迭代器。

方法调用时，private方法只能用函数形式(省略被调的形式)来调用。而protected方法只能在拥有该方法的对象的方法定义表达式内进行调用。(请参考方法调用的限制)

super

例:

super
super(1,2,3)

语法:

super
super(表达式, ... )

super将调用被当前方法覆盖的父类中的同名方法。若省略括号和参数时，将会把当前方法的参数原封不动地传递给父类中的同名方法。若调用时不想使用参数的话，请使用括号显式地标出，像super()这样。

例:

class Foo
  def foo(arg=nil)
    p arg
  end
end

class Bar < Foo
  def foo(arg)
    super(5)       # 以5作为参数进行调用
    super(arg)     # 以5作为参数进行调用
    super          # 以5作为参数进行调用，super(arg)的简写
    arg = 1
    super          # 以1作为参数进行调用，super(arg)的简写
    super()        # 无参数的调用
  end
end
Bar.new.foo 5

带块的方法调用

例:

[1,2,3].each do |i| print i*2, "\n" end
[1,2,3].each {|i| print i*2, "\n" }

语法:

method(arg1, arg2, ...)  do [`|' 表达式 ... `|'] 表达式 ... end
method(arg1, arg2, ...) `{' [`|' 表达式 ... `|'] 表达式 ... `}'
method(arg1, arg2, ..., `&' proc_object)

所谓带块的方法是指，为了对控制结构进行抽象化而设计的方法。最初常用于对循环进行抽象化，所以有时也叫迭代器。将do...end或{...}中的代码片段(也就是块)添加在方法后面，然后再调用该方法时，就能从该方法内部对快进行计算。在带块的方法内进行块调用时使用 yield 表达式。传给yield的值会被赋值给夹在"｜｜"中的变量。

{...}比do...end块的结合能力强。例如：

foobar a, b do .. end   # foobar 是带块的方法
foobar a, b { .. }      # b    成了带块的方法

块中首次被赋值(声明)的局部变量的作用域仅限于该块。例如：

foobar {
  i = 20                # 声明了局部变量i
   ...
}
print defined? i        # 此处的i尚未定义，false
foobar a, b do
  i = 11                # 声明了一个新变量i
   ...
end

如下所示，在块外仍然有效。

i = 10
[1,2,3].each do |m|
  p i * m               # 马上就能使用i
end

还可以把过程对象( Proc )当作块传递给带块的方法。这时要在过程对象名前面添加"&"，并把该过程对象传递给带块的方法的最后一个参数。除了过程对象以外，还可以传递方法对象( Method )。这时将生成一个调用该方法的过程对象，然后把这个过程对象传给带块的方法。

pobj = proc {|v|
  p v
}

[1,2,3].each(&pobj)
=> 1
   2
   3

ruby 1.7 特性: 在version 1.7中，若该对象自带to_proc方法的话，就可以把它当作带"&"的参数传给带块方法(默认状态下，Proc、Method对象都有to_proc方法)。方法调用时会执行to_proc，它将返回Proc对象。

class Foo
  def to_proc
    Proc.new {|v| p v}
  end
end

[1,2,3].each(&Foo.new)

=> 1
   2
   3

带块方法的返回值与通常的方法是一样的。若块中的 break 引起中断时，将返回nil。 ruby 1.7 特性 ：若break带参数的话，该参数的值就是带块方法的返回值。

yield

例:

yield data

语法:

yield `(' [表达式 [`,' 表达式 ... ]] `)'
yield [表达式 [`,' 表达式 ... ]]

把参数传给块之后，对块进行计算。因为yield定义迭代器，所以是在方法定义内使用。

def foo
  yield(1,2)
end

foo {|a,b| p [a,b]}

对块参数进行赋值时遵从多重赋值规律。若执行yield时，方法并没有带块(不是迭代器)的话，就会引发 LocalJumpError 异常。

yield将会返回块内最后被计算的表达式的值。若因 next 引起块的运行中断的话，返回nil。

ruby 1.7 特性 :若next带参数的话，该参数的值就是yield的返回值。

ruby 1.8 特性 ：关于块参数的交付问题，以后可能会对下述内容进行变更(会发出警告)。

def foo
  yield 1,2,3
end

foo {|v| p v}

# => -:5: warning: multiple values for a block parameter (3 for 1)
     [1, 2, 3]

应该写成

yield [1,2,3]

或者

foo {|*v| p v}

这样才对。虽然现在使用

v = 1,2,3

这样的多重赋值还不会有警告，但最好不要使用。

Originally Written by webmaster@ruby-lang.org
Translated by Community Engine China (Mail)
Last update on 2004-12-22


Ruby参考手册 - 类／方法的定义

类／方法的定义

  • 类定义
  • 特殊类定义
  • 模块定义
  • 方法定义
    • 方法定义的嵌套
    • 方法的计算
  • 特殊方法定义
  • 类方法的定义
  • 调用限制
  • 与定义有关的操作
    • alias
    • undef
    • defined?

类定义

例:

class Foo < Super
  def test
     :
  end
     :
end

语法:

class 标识符 [`<' superclass ]
  表达式..
end

语法:ruby 1.7 特性

class 标识符 [`<' superclass ]
  表达式..
[rescue [error_type,..] [=> evar] [then]
  表达式..]..
[else
  表达式..]
[ensure
  表达式..]
end

用来定义类的内容。类名(标识符)由大写字母开头。ruby 1.7 特性：在version 1.7中，可以添加rescue/ensure部分。

类定义实际上就是把类赋值给由类名指定的常数(在Ruby中，类也是一个对象，它是Class类的实例)。

若某个类已经被定义过，此时又用相同的类名进行类定义的话，就意味着对原有的类的定义进行追加。但是若显式地标出新类的超类与原有类的超类不同时，就表示将使用原有的类名定义一个新的类(这将覆盖与类同名的常数，因此会出现警告)。

class Foo < Array
  def foo
  end
end

# 追加定义(即使显式地标明超类是Array，其结果也是一样)
class Foo
  def bar
  end
end

# 定义新的类(因为超类不同)
class Foo < String
end
# => warning: already initialized constant Foo

在类定义表达式中，self指的是该类本身，这与顶层没有什么不同，只是默认的调用限制有些许差异。可以在类定义表达式中写入任何表达式，在定义类时这些表达式将被执行。

类定义中可以出现嵌套。下例中，嵌套外侧的Foo类和内侧的Bar类之间根本没有什么继承关系之类的功能上的联系(除了常数Bar是Foo中的常数Foo:Bar之外)。

class Foo
  class Bar
  end
end

ruby 1.8 特性:如果Foo类已经定义过了的话，还可以这么写。

class Foo
end

class Foo::Bar
end

类的嵌套就是指，把与类有关的类和模块放在该类的外侧，使它们构成一个整体，借以表达某种包含关系。

# 把与NET有关的类置入NET内部
# 常使用模块来作为嵌套的外侧部分
# (Net没有实例。这主要是为了能够包含(include)Net)
module Net
  class HTTP
  end
  class FTP
  end
end

obj = Net::HTTP.new

# 或者

include Net
obj = HTTP.new

# 下列用法在内部类中也可使用
# 使用者只要包含(include)了File::Constants
# 就可以直接使用RDONLY，而不必写File::RDONLY等。
class File
  module Constants
     RDONLY = 0
     WRONLY = 1
  end
  include Constants
end

File.open("foo", File::RDONLY)

# 或者

include File::Constants
File.open("foo", RDONLY)

# 上面的只是例子。实际上，使用File.open时可以写得更简单
# 可以这么写，File.open("foo", "r")

类定义表达式没有返回值。ruby 1.7 特性:类定义表达式将返回最后被计算的式子的值。若最后的式子不返回值，就返回nil。

特殊类定义

例:

class << obj
  def test
     :
  end
     :
end

语法:

class `<<' expr
  表达式..
end

语法:ruby 1.7 特性

class `<<' expr
  表达式..
[rescue [error_type,..] [=> evar] [then]
  表达式..]..
[else
  表达式..]
[ensure
  表达式..]
end

与类定义的语法结构相同，它定义特定对象的功能。在其内部定义的方法和常数只对该特定对象有效。ruby 1.7 特性：在version 1.7中，还可以使用rescue/ensure部分。

特殊类定义表达式将返回最后被计算的式子的值。若最后的式子不返回值，就返回nil。

模块定义

例:

module Foo
  def test
     :
  end
     :
end

语法:

module 标识符
  表达式..
end

语法:ruby 1.7 特性

module 标识符
  表达式..
[rescue [error_type,..] [=> evar] [then]
  表达式..]..
[else
  表达式..]
[ensure
  表达式..]
end

用来定义模块的内容。模块名(标识符)由大写字母开头。ruby 1.7 特性：在version 1.7中，还可以使用rescue/ensure。

模块定义实际上就是把模块赋值给由模块名指定的常数(在Ruby中，模块也是一个对象，它是Module类的实例)。

若某个模块已经被定义过，此时又用相同的模块名来定义模块的话，就意味着对原有的模块定义进行追加。

模块定义表达式没有返回值.ruby 1.7 特性：模块定义表达式将返回最后被计算的式子的值.若该式子不返回值,则返回nil.

方法定义

例:

def fact(n)
  if n == 1 then
     1
  else
    n * fact(n-1)
  end
end

语法:

def 方法名 [`(' [arg ['=' default]] ... [`,' `*' arg] [',' '&' arg]`)']
  表达式..
[rescue [error_type,..] [=> evar] [then]
  表达式..]..
[else
  表达式..]
[ensure
  表达式..]
end

在定义语句所在的区域内定义一个方法.也就是说,若在类/模块的定义部分内定义一个方法的话,该方法就属于这个类/模块.若在顶层定义了一个方法的话,您就可以在任何地方调用它.这种方法其实就是其他语言中所说的"函数".

方法名中,除了可以使用通常的标识符以外,还可以使用可重载的操作符(例:==,+,-等等.请参考操作符表达式).

若给形参指定了默认表达式的话,在方法调用过程中如果实参被省略时,该默认表达式的值就会变成默认值(方法调用时,在方法定义内计算默认表达式的值).

若最后一个形参的前面带"*"的话,所有剩下的实参将被转为数组后传递给该参数.

例:

# 没有参数的方法。以下省略 end
def foo
end

# 有参数的方法
def foo(arg, arg2)

# 有默认参数的方法
def foo(arg = nil)

# 带块
def foo(arg, &block)

# 参数一应俱全
def foo(arg, arg2, arg3 = nil, *rest, &block)

# 操作符表达式
def ==(other)
def +(other)
def *(other)

若最后一个形参前面带"&"的话,表示传递给该参数的块是一个过程对象(Proc).这是定义迭代器的一种方法.(定义迭代器的典型方法是调用yield.还可以使用Proc.new/proc等方法.)当没有给出块时,块参数的值为nil.

在方法定义中,只能以下列顺序指定形参.其中任何一项都是可选的.

  • 没有默认表达式的参数(可多选)
  • 有默认表达式的参数(可多选)
  • 带*的参数(只能有一个)
  • 带&的参数(只能有一个)

例: 定义迭代器

# 使用 yield
def foo
  # block_given? 是内部函数
  # 用来判断方法有没有块
  if block_given?
    yield(1,2)
  end
end

# 使用 Proc.new
def bar
  if block_given?
    Proc.new.call(1,2)    # proc.call(1,2)也是一样(proc是内部函数)
  end
end

    # 应用:定义一个既能接受Proc对象
    # 又能接受块的迭代器
    def foo(block = Proc.new)
      block.call(1,2)
    end
    foo(proc {|a,b| p [a,b]})
    foo {|a,b| p [a,b]}

# 使用块参数
def baz(&block)
  if block
    block.call(1,2)
  end
end

我们再举几个特殊的例子.

# 单相+/-
def +@
def -@

# 给要素赋值
def foo=(value)             # obj.foo = value

# [] と []=
def [](key)                 # obj[key]
def []=(key, value)         # obj[key] = value
def []=(key, key2, value)   # obj[key, key2] = value

# 后引号表示法
def `(arg)                  # `arg` 或  %x(arg)

因为后引号表示法与方法密切相关,所以可以进行再定义.通常情况下,不应该对该方法进行再定义.偶尔OS(SHELL)命令的运作不太正常时,可以使用这种方法.

为了捕捉在方法运行时发生的异常,可以使用同begin一样的rescue,else或ensure语句.

方法定义表达式不会返回值.ruby 1.7 特性:方法定义表达式返回nil.

方法定义的嵌套

除了特殊方法定义以外,方法定义表达式不能进行嵌套.

ruby 1.7 特性: 在1.7 以后的版本中,就可以进行嵌套了.只有嵌套外侧的方法被执行时,嵌套方法才会被定义.除此以外,它和普通的方法定义表达式没有区别.请参考下例.

class Foo
  def foo
    def bar
      p :bar
    end
  end

  def Foo.method_added(name)
    puts "method \"#{name}\" was added"
  end
end
obj = Foo.new
obj.bar rescue nil # => undefined method `bar' for #<Foo:0x4019eda4>
obj.foo            # => method "foo" was added
obj.foo            # => warning: method redefined; discarding old bar
Foo.new.bar        # => :bar  (在其他实例中,嵌套方法也已完成定义)

在version 1.6之前的版本中,若想达到相同的目的就必需使用instance_eval(此时特殊方法已被定义,因此稍有不同).

class Foo
  def foo
    instance_eval <<-END
      def bar
        p :bar
      end
    END
  end
end

obj = Foo.new
def obj.singleton_method_added(name)
    puts "singleton method \"#{name}\" was added"
end                # => singleton method "singleton_method_added" was added

obj.bar rescue nil # => undefined method `bar' for #<Foo:0x4019eda4>
obj.foo            # => singleton method "bar" was added

obj.foo            # => warning: method redefined; discarding old bar
                   # => singleton method "bar" was added
Foo.new.bar        # => undefined method `bar' for #<Foo:0x4019eda4>

还可以这么写.

class Foo
   def foo
     instance_eval {
       def bar
         p :bar
       end
     }
   end
 end

方法的计算

调用方法时,将按照下列顺序依此计算各个表达式.

  • 参数的默认表达式(若有的话)
  • 方法的内容
  • 根据发生异常的实际状况,处理方法定义表达式的rescue部分或else部分(若有的话)
  • ensure部分(若有的话)

在方法内,根据实际情况来计算这些部分,包括参数的默认表达式在内.

方法的返回值就是传给return的值.若没有调用return时,将返回在ensure部分之前最后计算的式子的值.

若最后的式子(例如while等)没有返回值,则返回nil.

在定义某方法之前,是不能使用该方法的.例如

foo
def foo
  print "foo\n"
end

调用未定义的方法会引发NameError异常.

特殊方法定义

例:

def foo.test
  print "this is foo\n"
end

语法:

def 表达式 `.' 标识符 [`(' [参数 [`=' default]] ... [`,' `*' 参数 ]`)']
  表达式..
[rescue [error_type,..] [=> evar] [then]
  表达式..]..
[else
  表达式..]
[ensure
  表达式..]
end

特殊方法就是专属于某个对象的方法.特殊方法的定义可以嵌套.

类的特殊方法将被该类的子类所继承.换言之,类的特殊方法所起到的作用,与其他面向对象系统中的类方法的作用是相同的.

特殊方法定义表达式不会返回值.ruby 1.7 特性:特殊方法定义表达式返回nil.

类方法的定义

Ruby中的类方法是指类的特殊方法.在Ruby中,类也是对象.因此它就可以像普通对象一样来定义特殊方法.

因此,若能在类对象中定义方法的话,该方法就会成为类方法.具体的定义方法如下(模块也一样).

# 特殊方法方式.
class Hoge
  def Hoge.foo
  end
end

# 在类定义的外侧也行
def Hoge.bar
end

# 若使用下面的方法的话,即使类名改变了,也不必更改方法定义
class Hoge
  def self.baz
    'To infinity and beyond!'
  end
end

# 特殊类方式.适合于大批量地定义方法
class << Hoge
  def bar
    'bar'
  end
end

# 若把模块extend到类的话,模块的实例方法
# 就会变成类方法
module Foo
  def foo
  end
end
class Hoge
  extend Foo
end

请参考Object#extend来了解extend.

调用限制

调用方法时,会受到以下三种限制,即public、private、protected.

  • 若方法属于public类型,则没有任何限制.
  • 若方法属于private类型,则只能在函数中调用.
  • 若方法属于protected类型,则只能在该方法所属对象的方法定义表达式内使用.

例: protected的可用性

class Foo
  def foo
   p caller.last
  end
  protected :foo
end

obj = Foo.new

# 不可直接调用
obj.foo rescue nil    # => -:11 - private method `foo' called for #<Foo:0x401a1860> (NameError)

# 也不能在类定义中调用
class Foo
  Foo.new.foo rescue nil # => -:15 - protected method `foo' called for #<Foo:0x4019eea8>
  # 可以在方法定义表达式中调用
  def bar
    self.foo
  end
end
Foo.new.bar             # => ["-:21"]

# 还可以在特殊方法定义表达式中调用
def obj.bar
  self.foo rescue nil
end
obj.bar                 # => ["-:27"]

默认情况下,若def表达式位于类定义以外(顶层),则该方法属于private类型.若在类定义之中,则该方法属于public类型.可以使用Module#public,Module#private或Module#protected来改变它们的类型.但是,initialize方法和initialize_copy(ruby 1.8 特性)方法总是private类型,这与它们的位置无关.

例:

def foo           # 默认为 private
end

class C
  def bar         # 默认为 public
  end

  def ok          # 默认为 public
  end
  private :ok     # 变为 privat

  def initialize  # initialize 是 private
  end
end

使用private 和 protected的目的是相同的(将对象隐藏起来,从外部不能调用).但是在下例中,不能使用private,而必须使用protected.

class Foo
  def _val
    @val
  end
  protected :_val

  def op(other)

    # other 也假定 Foo 的实例
    # 如果_val 是 private的话,就只能以函数的形式来调用
    # 所以不能这么用

    self._val + other._val
  end
end

与定义有关的操作

alias

例:

alias foo bar
alias :foo :bar
alias $MATCH $&

语法:

alias 新方法名 旧方法名
alias 新全局变量名 旧全局变量名

给方法或全局变量添加别名.可以给方法名指定一个标识符或Symbol(不能写obj.method这样的表达式).alias的参数不会被计算.

若想在方法定义内部添加别名时,请使用Module类的Module#alias_method方法.

给方法添加别名时,别名方法将继承此刻的原始方法.此后,即使原始方法被重新定义,别名方法仍然保持着重定义前的老方法的特性.若您改变了某方法的内容后,又想使用修改前的方法时,别名会非常有用.

# 定义 foo 方法
def foo
  "foo"
end

# 设定别名(避开方法定义)
alias :_orig_foo :foo

# 再定义 foo (利用以前的定义)
def foo
  _orig_foo * 2
end

p foo  # => "foofoo"

给全局变量设定alias就意味着定义一个完全相同的变量.当你向一个赋值时,另一个也会有所反映.附加库的importenv.rb正是利用了这个特性,给内部变量添加了英文名.ruby 1.7 特性:在1.6版本中,只能给特定的内部全局变量添加别名.到了1.7版本时,这项限制被取消了.

# 在给特殊变量添加别名之后,当其中一个发生变化时,另一个也会有所反应
$_ = 1
alias $foo $_
$_ = 2
p [$foo, $_]   # => [2, 2]

# 这是通常的变量的别名,它并非真正意义上的别名.
# 这是1.6版本以前
# 的限制
$bar = 3
alias $foo $bar
$bar = 4
p [$foo, $bar] # => [3, 4]

但是,您不能给正则表达式中的变量$1,$2,...等添加别名.另外,有些全局变量(请参考内部变量)对于解释器来说是举足轻重的,若重新定义它们的话,有时会影响解释器的工作.

alias 表达式返回 nil.

undef

例:

undef bar

语法:

undef 方法名[, 方法名[, ...]]

取消方法定义.可以向方法名指定一个标识符或Symbol(不能写obj.method这样的表达式).undef的参数不会被计算.

若想在方法定义的内部取消定义时,请使用Module类的Module#undef_method方法.

undef会取消方法名和方法定义之间的关系,然后把该方法名关联到一个特殊的定义上.若在此时进行方法调用的话,即使超类中有同名方法,也会引发NameError异常.(另外,Module#remove_method方法只负责取消关系,这点差别非常重要.)

用alias添加别名或用undef取消定义时,会修改类的接口,而不受超类的限制.但有时方法会向self发出消息,若不小心处理的话可能会导致原有方法失效.

undef 表达式返回 nil.

defined?

例:

defined? print
defined? File.print
defined?(foobar)
defined?($foobar)
defined?(@foobar)
defined?(Foobar)

语法:

defined? 表达式

若表达式尚未定义,则返回伪.若已经定义,则返回一个字符串,字符串的内容是该表达式的种类.

不论是未定义的方法,被undef的方法,还是被Module#remove_method删除的方法,defined?都将返回伪.

还可以使用下列特殊用法.

defined? yield

若yield调用可用,则返回真(字符串"yield").它的作用同block_given?一样,可以判断能否以带块方式来调用某方法.

defined? super

若super可行,则返回真(字符串"super").

defined? a = 1
p a # => nil

返回"assignment".虽然没有赋值,但已经定义了局部变量.

/(.)/ =~ "foo"
p defined? $&  # => "$&"
p defined? $1  # => "$1"
p defined? $2  # => nil

只有设定了前面的匹配值以后,测试$&, $1, $2才会返回真.

def Foo(a,b)
end
p defined? Foo       # => nil
p defined? Foo()     # => "method"
Foo = 1
p defined? Foo       # => "constant"

若没在以大写字母开头的方法名后添加"()"时,该方法名会被当做常数处理.

下列就是defined?的所有的返回值.

  • "super"
  • "method"
  • "yield"
  • "self"
  • "nil"
  • "true"
  • "false"
  • "assignment"
  • "local-variable"
  • "local-variable(in-block)"
  • "global-variable"
  • "instance-variable"
  • "constant"
  • "class variable"
  • "$&", "$`", "$1", "$2", ...
  • "expression"
Originally Written by webmaster@ruby-lang.org
Translated by Community Engine China (Mail)
Last update on 2004-12-22


Ruby参考手册 - 内部函数

内部函数

严格说来,Ruby中没有函数.但Kernel模块中定义的方法(可以在任何地方用做函数,因此)可被其他语言当做函数来调用.若您对这些方法进行再定义时,就必需考虑到对其他方面的影响.

` str

把字符串str当做外部命令来运行,并以字符串方式返回命令的输出.使用`str`形式来调用该方法(还可以使用%x(...)表示法进行调用.请参考命令输出).

可以使用$?来了解命令的结束状态.

若您只想执行命令,而不需要得到命令输出时,可以使用system.如果是那些终端控制命令的话,`command`可能会运行失败.

Array(arg)

调用 arg.to_ary 或 arg.to_a 将参数转为数组并返回结果.若变换后没有得到数组就会引发TypeError异常.

若arg中没有to_ary或to_a方法的话,就返回只包含一个元素的数组[arg].

Float(arg)

将参数变为浮点数(Float)并返回结果.

若将不能转为整数或浮点数的字符串传递给参数的话,会引发ArgumentError异常.

请参考String#to_f.

p Float("10")    # => 10.0
p Float("10e2")  # => 1000.0
p Float("1e-2")  # => 0.01
p Float(".1")    # => 0.1

p Float("nan")   # => NaN         (ruby 1.7 特性: => invalid value (ArgumentError))
p Float("INF")   # => Infinity    (ruby 1.7 特性: => invalid value (ArgumentError))
p Float("-Inf")  # => -Infinity   (ruby 1.7 特性: => invalid value (ArgumentError))
p Float(("10" * 1000).to_f) # => Infinity

p Float("0xa.a") # => 10.625    # (ruby 1.7 特性: => invalid value (ArgumentError))
p Float(" \n10") # => 10.0      # 前面的空白被忽略
p Float("1_0_0") # => 100.0     # `_' 也被看做是数值要素
p Float("")      # => invalid value (ArgumentError)
p Float(nil)     # => 0.0       # ruby1.7特性: cannot convert nil into Float (TypeError)

p Float(Object.new) # => cannot convert Object into Float (TypeError)
Integer(arg)

将参数变为整数(Fixnum,Bignum)并返回结果.对数值,字符串以外的对象使用to_i方法(Ruby 1.7 特性:在1.7中是to_int).若变换结果不是整数(Integer的子类)就会引发TypeError异常.

若参数是字符串的话,将按其前缀,如0x,0b,0等分别决定要按照16进制,2进制或是8进制的标准来处理该字符串.

若把不能变为整数的字符串传递给参数时,将引发ArgumentError异常.

p Integer(10.1)       # => 10
p Integer(10.8)       # => 10
p Integer("10")       # => 10
p Integer("10_0_0")   # => 1000
p Integer("10__0")    # => 100
                      # => invalid value for Integer: "10__0" (ArgumentError) (ruby 1.7 特性)
p Integer("_10")      # => invalid value for Integer: "_10" (ArgumentError)
p Integer("10_")      # => invalid value for Integer: "10_" (ArgumentError)
p Integer("0d10")     # => invalid value for Integer: "0d10" (ArgumentError)
                      # => 10 (ruby 1.7 特性)
p Integer("010")      # => 8
p Integer("0o10")     # => invalid value for Integer: "0o10" (ArgumentError) (ruby 1.7 特性)
                      # => 8
p Integer("0x10")     # => 16
p Integer("0b10")     # => 2
p Integer(" \n10\t ") # => 10
p Integer("")         # => `Integer': invalid value for Integer: "" (ArgumentError)
p Integer(nil)        # => 0

p Integer(Object.new) # => cannot convert Object into Integer (TypeError)

请参考String#hex,String#oct,String#to_i.

String(arg)

调用arg.to_s将参数变为字符串并返回结果。若变换后的结果并非字符串的话，会引发TypeError异常。若arg已经是字符串的话，则不作任何处理直接返回arg。

abort
abort(message) ((<ruby 1.7 特性>))

以非正常方式结束Ruby程序的运行。它与Exit的区别在于，调用时若$!不为nil的话，就将异常消息输出到标准错误输出当中；另外，程序的结束status始终都是EXIT_FAILURE(在绝大多数环境中都是1)。

ruby 1.7 特性:若指定了message参数的话，就将message赋值给SystemExit#message后输出到标准错误输出中。

at_exit { .... }

在解释器结束工作之前执行指定的块。除去at_exit是个方法这点差异之外，它与END块所进行工作基本相同。一旦注册了要进行的处理内容之后就不能取消。请参考结束时的相关处理。

Proc对象返回注册的处理内容。

autoload(const_name, feature)
autoload?(const_name) ((<ruby 1.8 feature>))

在首次调用常数const_name时会require feature。const_name可以是字符串或Symbol。而且const_name中不能包含"::"操作符(即只能指定顶层的常数)。

返回nil。

ruby 1.8 特性：可以autoload任何类／模块的常数。const_name中还是不能有"::"操作符，可以像下面这样定义。(实际上是调用了Module#autoload)

------- /tmp/foo.rb ---------
class Foo
  class Bar
  end
end
----- end of /tmp/foo.rb ----

class Foo
  autoload :Bar, '/tmp/foo.rb'
end
p Foo::Bar

另外，还可以下面这样定义。

class Foo
end
Foo.autoload :Bar, '/tmp/foo.rb'
p Foo::Bar

autoload?的功能是，若autoload常数尚未定义(没被load)时，返回其路径名。另外，若已经load的话就返回nil。

------- /tmp/foo.rb ---------
class Foo
  class Bar
  end
end
----- end of /tmp/foo.rb ----

class Foo
end
Foo.autoload :Bar, '/tmp/foo.rb'
p Foo.autoload?(:Bar)         # => "/tmp/foo.rb"
p Foo::Bar                    # => Foo::Bar
p Foo.autoload?(:Bar)         # => nil

请注意下例。在autoload的库中，嵌套内的常数还没有被定义。乍看起来好像会正常运行，实则不然(出现了警告消息)。

------- /tmp/bar.rb ---------
class Bar
end
----- end of /tmp/bar.rb ----

class Foo
  autoload :Bar, '/tmp/bar.rb'
end
p Foo::Bar
p Foo.autoload?(:Bar)

# => -:4: warning: toplevel constant Bar referenced by Foo::Bar
     Bar
     nil

若不使用嵌套的话，可以把上例简写为

class Foo
end
class Bar
end
p Foo::Bar

# => -:5: warning: toplevel constant Bar referenced by Foo::Bar
     Bar
binding

生成并返回Binding对象。该对象包含变量、方法等的环境信息，它通常用作Eval的第二参数。

caller([level])

以$@的back trace(字符串数组)形式返回level层上(省略参数时为1)的调用者的信息。若是顶层的话就返回空数组。将caller的返回值代入$@就可以设定异常的发生位置。

def foo
  p caller(0)
  p caller(1)
  p caller(2)
  p caller(3)
end

def bar
  foo
end

bar

=> ["-:2:in `foo'", "-:9:in `bar'", "-:12"]
   ["-:9:in `bar'", "-:12"]
   ["-:12"]
   []

下列函数可以从caller的要素中抽取并返回[文件名、行号、方法名]。

def parse_caller(at)
  if /^(.+?):(\d+)(?::in `(.*)')?/ =~ at
    file = $1
    line = $2.to_i
    method = $3
    [file, line, method]
  end
end

def foo
  p parse_caller(caller.first)
end

def bar
  foo
  p parse_caller(caller.first)
end

bar
p parse_caller(caller.first)

=> ["-", 15, "bar"]
   ["-", 19, nil]
   nil

当$DEBUG为真时，debug函数非常有用。以下是该函数例程。

def debug(*args)
  p [caller.first, *args] if $DEBUG
end

debug "debug information"

=> ["-:5", "debug information"]
callcc {|cont| .... }

请参考Continuation。

catch(tag) {|tag| .... }

运行块并返回它的值。若在块的运行过程中，遇到与tag同名的throw的话，将把throw的第二参数的值作为返回值。

例如下例代码在运行过程中不会调用some_process，同时catch的值是25，而并非10。

ret = catch(:exit) {
   throw :exit, 25
   some_process()
   10
}
p ret   #=> 25

嵌套的循环不会因为遇到break就一下子跳出。这时可使用catch或异常。

catch(:loop1) {
  for i in 1..2
    for j in 1..2
      throw :loop1, j
    end
  end
}
chop
chop!

将内部变量$_末尾的字符去掉(若末尾是"\r\n"则去掉2个字符)。chop!将改变字符串本身并返回结果。若没有可以去掉的字符则返回nil。

请参考String#chop了解详情。chop函数与$_.chop之间存在以下区别。

  • chop先拷贝$_的值，然后对拷贝进行修改，最后把这个拷贝的值重新赋值给$_。
chomp([rs])
chomp!([rs])

去掉位于内部变量$_末尾且由rs指定的字符。chomp!将改变字符串本身并返回结果。若没有可被去掉的字符串则返回nil。rs的默认值为$/

请参考String#chomp了解详情。chomp函数与$_.chomp之间存在以下区别。

  • chomp先拷贝$_的值，然后对拷贝进行修改，最后把这个拷贝的值重新赋值给$_。
eval(expr[, binding[, fname[, lineno=1]]])

把字符串expr当作Ruby程序来运行并返回其结果。若给第二参数传递Proc对象或Binding对象的话，将在生成该对象的环境中对字符串进行计算。请参考binding。

def foo
  a = 1
  binding
end

eval("p a", foo)  # => 1

若指定了fname 和 lineno的话，将假定字符串位于fname文件lineno行，并且进行编译。这时可以显示栈跟踪(stack trace)等信息。

exec(command)
exec(program[, arg1[, arg2[, ...]]])

执行由command指定的命令.该命令(或shell,详见下文)将变成进程执行的代码,所以一旦启动成功,就不会再从该函数中返回.若启动失败,控制权重新回归ruby解释器,然后会引发Errno::EXXX异常.

在第一种语句中,若command中包含shell的元字符(* ? {} [] <> () ~ & | \ $ ; ' ` " \n)的话,则经由shell执行命令.除此之外,将由解释器直接执行.

使用第二种语句时,通常不经过shell就执行命令.此时,将把空字符或shell的元字符等原般不动地传递给program的参数.若首个参数是包含两个元素的数组时,第一个元素的字符串将成为启动程序时的路径,而第二个元素将成为程序名的"别名".

ruby 1.8 特性:即使第一个元素不是完整路径,也会自动从环境变量PATH中开始搜索.

exit([status])

终止Ruby程序的运行.若向status传递了一个整数的话,该整数就将成为Ruby命令的结束状态值.默认的结束状态值是0.

exit通过引发SystemExit异常来结束程序的运行.若有必要的话,可以使用rescue部分来捕捉到它.

ruby 1.8 特性:若status为true的话,就以EXIT_SUCCESS作为结束状态值.若status为false的话,就以EXIT_FAILURE作为结束状态值.默认的结束状态值已被改为EXIT_SUCCESS.

exit!([status])

以整数status为结束状态值来终止Ruby程序的运行.默认的结束状态值为-1.

exit!与exit不同,它不会进行异常处理.在fork之后,若想终止子进程时可以使用该语句.

ruby 1.8 特性:若status为true的话,就以EXIT_SUCCESS作为结束状态值.若status为false的话,就以EXIT_FAILURE作为结束状态值.默认的结束状态值已被改为EXIT_FAILURE.

fork
fork { ... }

使用fork(2)系统调用来制作进程的拷贝.若在父进程中将返回子进程的进程ID,若在子进程中则返回nil.若指定一个块之后再进行调用,则会在生成的子进程中对该块进行计算.

gets([rs])
readline([rs])

Ruby解释器在运行时,会把从参数那里得到的文件(若没有的话,就利用标准输入)假设成一个虚拟文件(可以使用内部变量$<或ARGF访问该虚拟文件),然后从该虚拟文件中读入一行,并返回字符串.若到达文件尾部时返回nil.

使用rs指定的字符串作为行的切分符号.rs的默认值取自内部变量$/.读入的字符串也被存入内部变量$_中.

若将rs设为nil的话,则意味着读入文件的全部内容而忽略行的切分.若设为空字符串""的话,会把连续的换行当做行的切分符(段落模式).

readline的功能与gets相同,但是当它遇到文件结尾时会引发EOFError异常.

global_variables

返回程序中已定义的全局变量(以'$'开头的变量)名的数组.

另外请参考local_variables, Object#instance_variables, Module.constants, Module#constants, Module#class_variables.

gsub(pattern[, replace])
gsub!(pattern[, replace])
gsub(pattern) {|matched| ... }
gsub!(pattern) {|matched| ... }

在内部变量$_所包含的字符串中,将符合pattern的部分替换为replace,然后返回结果.若省略参数replace时,该方法将演变为迭代器,以块的计算值进行替换操作.将匹配的字符串以参数的形式传递给块.

$_ = "foobar"
p gsub(/o+/) {|m|
  m.upcase
}
# => "fOObar"

gsub!将改写$_所包含的字符串本身.请参考String#gsub来获得更多资讯.String#gsub和gsub之间存在以下差异.

  • gsub方法会拷贝$_的值,然后对拷贝进行更新,最后再赋值给$_.
iterator? ((<obsolete>))
block_given?

若某方法带块则返回真,否则返回伪.

load(file[, priv])

加载Ruby程序file并予以执行.若file是绝对路径,则从file开始加载.若file是相对路径,则依次搜索内部变量$:所指定的路径,并加载第一个搜索到的文件.此时,若$:中的元素是以"~"(tilde)开头的话,就把它扩展为环境变量HOME的值.

若可选参数priv的值为真,则在内部生成一个无名模块作为顶层来进行加载和运行,这便不会污染全局名字空间了.

若加载成功则返回true,若失败则引发LoadError异常.

[require与load之间的区别]

require对于相同的文件只会加载一次,而load则是无条件的加载.另外,require会自动补全扩展名,如.rb或.so等.而load则没有该功能.一般的典型用法是,使用require加载库,而使用load加载配置文件等.

# 用例
load "#{ENV['HOME']}/.myapprc"
load "/etc/myapprc"

另外,如果想在特定目录中加载文件的话,写成 load 'filename' 就不恰当了.这时请务必使用绝对路径.

local_variables

返回当前作用域中已定义的局部变量名的数组.

另外,请参考global_variables, Object#instance_variables, Module.constants, Module#constants, Module#class_variables.

loop { ... }

(只要不被中断就)反复对块进行计算.

open(file[, mode[, perm]])
open(file[, mode[, perm]]) {|io| ... }

打开file并返回File对象.mode可以是下列字符串或者整数(File::Constants模块的常数的逻辑和).省略时默认为"r".

  • "r", RDONLY: 以读取模式打开文件.
  • "w", WRONLY|CREAT|TRUNC: 以写入模式打开文件.打开文件时,若文件已存在就清空文件内容.
  • "a", WRONLY|CREAT|APPEND: 以写入模式打开文件.通常把写入内容添加到文件结尾.

若有"+"的话,就以读写两用模式(RDWR)打开文件.

  • "r+": 将文件的读写位置设在开头.
  • "w+": 与"r+"一样.打开文件时,若文件已存在就清空其内容.
  • "a+": 与"r+"一样.打开文件时,若文件已存在,就把读写位置设在文件结尾.

可在上述任意一项后面添加"b"(如"r+b")标志(如果是整数就是File::BINARY).此时,将以二进制方式打开文件(只限于以文本/二进制方式来区分文件的系统).

第三参数perm与open(2)的第三参数是一样的，它以整数的形式说明了文件在CREAT时的访问权限。若第二参数不是数值形式的话，该参数将被忽略。它的默认值是0666。

若文件名以`|'开头时，其后的字符串会被当作命令来执行。另外，将生成管线(pipeline)来应对命令的标准输入输出。

若文件名为"|-"的话，open将生成Ruby的子进程，并返回与子进程之间的管道(pipe)(IO对象)。(此时的运作与IO.popen相同。File.open没有生成管线的功能)。

注意: 与Perl不同的是，命令通常是以`|'开头的。

当open与块同时被调用时，open将打开文件并执行块，当块的运行终止后就关闭文件。此时返回块的计算结果。请看下例。

open(path, mode) do |f|
   ...
end

# 等效代码
f = open(path, mode)
begin
   ...
ensure
  f.close
end
p(obj, [obj2, ...])

以通俗易懂的方式输出obj。等同于以下代码。(请参考Object#inspect)

print obj.inspect, "\n", obj2.inspect, "\n", ...

返回nil。

print([arg1[, arg2, ...]])

依次输出参数。若没有得到参数则输出变量$_的值。若把非字符串对象传给参数的话，将对该对象使用to_s方法将其变为字符串后输出。若是nil的话则输出字符串"nil"。

若给变量$;(输出字段分隔符)指定了一个非nil的值的话，将在各个参数之间输出该分隔符。若给变量$\(输出记录分隔符)指定了一个非nil的值的话，将在最后输出它。

返回nil。

printf([port, ]format[, arg[, ...]])

类似于C语言的printf，它将按照format将参数格式化为字符串，然后输出到$>。当第一参数是IO的子类的实例时，将对该对象进行输出。若没有任何参数时，将不作反应。

请参考sprintf格式来了解Ruby中format字符串的扩展问题。

返回nil。

proc { ... }
lambda { ... }
proc
lambda

在给出的块中生成过程对象(Proc的实例)并返回它(等同于Proc.new)。

若没有给出块的话，将返回主调(caller)方法所指的块。若主调方法没有块时，将引发ArgumentError异常。

putc(ch)

将字符ch输出到$>。若ch是数字的话，将输出对应于0～255的字符。若ch是字符串的话，将输出字符串中的第一个字符。

返回ch。

putc("ch")
putc(?c)
putc(99)
# => ccc
puts([obj[, obj2[, ....]]] )

依次将obj和换行符输出到$>。若没有参数的话则只会输出换行符。

若参数是数组，则依次输出数组元素和换行符。若将既非数组又非字符串的对象传递给参数时，将尝试使用to_ary方法将其化为数组，再使用to_s方法将其化为字符串。若是nil则输出字符串"nil"。

若参数是以换行符结尾时，puts将不再输出换行符。

puts "foo", "bar\n", "baz"
puts ""    # 只输出换行
puts       # 只输出换行
puts "foo"
=> foo
   bar
   baz

   foo

返回nil。

另外，请参考print, warn。

raise
raise(exception)
raise(message)
raise(error_type, message [, backtrace])
fail(error_type, message [, backtrace])

引发异常。

若没有参数时，将再次引发本线程本块内最后被rescue的异常对象($!)。若没有这样的异常时，将引发RuntimeError异常。

begin
  open("nonexist")
rescue
  raise   # => `open': No such file or directory - "nonexist" (Errno::ENOENT)
end

若只有一个参数，且该参数为字符串时，将以该字符串为message来引发RuntimeError异常。若该参数为异常类或异常对象时，将引发该异常。

raise "error message"    # => -:1: error message (RuntimeError)

raise ArgumentError      # => -:1: ArgumentError (ArgumentError)

raise ArgumentError.new  # => -:1: ArgumentError (ArgumentError)

若有2或3个参数时，将以第二参数为message来引发第一参数所指的异常。此时是以异常类或异常对象来指定异常的。而第三参数则是发生异常时的栈跟踪(track trace)，它的形式必须与caller的返回值一致。

raise ArgumentError, "error message"
# => -:1: error message (ArgumentError)

raise ArgumentError, "error message", ["file1:99",
                                       "file2:999:in `method'"]

# => file1:99: error message (ArgumentError)
              from file2:999:in `method'

若将非异常的类或对象赋予第一参数时，实际上发生的异常正是该对象的exception方法的返回值。

class MyException
  def exception
    ArgumentError.new
  end
end

raise MyException.new

# => -:7: ArgumentError (ArgumentError)

若采用第二种形式来指定参数时，该参数将被赋予exception方法。

class MyException
  def exception(mesg)
    ArgumentError.new(mesg)
  end
end

raise MyException.new, "error message"

# => -:7: error message (ArgumentError)

exception方法必定返回异常对象。否则将引发TypeError。

发生的异常会被存入变量$!中。而发生异常时的栈跟踪信息将被存入变量$@中。

rand([max=0])

产生一个0至max(不含)之间的随机数。若未调用srand的话，就自动调用它。

若将max设为nil或0的话，将返回一个0至1(不含)的实数型Float随机数。

readlines([rs])

把从命令行参数那里得到的文件(若没有的话,就利用标准输入)假设成一个虚拟文件(ARGF)，再读入该文件的所有内容，然后以行为单位将这些内容转换为数组，最后返回该数组。

以rs所指的字符串为行的切分符。rs的默认值取自内部变量$/。

若将rs设为nil则意味着没有行切分符。若设定为空字符串""，则把连续换行当作行切分符(段落模式)。

require(feature)

从加载路径$:开始搜索Ruby库feature，若该库尚未被加载就立即加载。

Ruby库就是指Ruby脚本(*.rb)或扩展库(*.so)，若feature的扩展名被省略时，则会同时搜索这两种文件(欲知具体的搜索顺序，请参考$:)。若给出扩展名时，则只会搜索这种文件。另外，扩展库的扩展名不仅限于常用的那些，还常常使用.so。

加载库后返回true。若该库已被加载则不会重复加载，此时返回false。若加载失败则引发LoadError异常。feature被加载后，它的名字(包括扩展名)会被追加到变量$"中。

scan(re)
scan(re) {|matched| ... }

与$_.scan一样。

select(reads[, writes[, excepts[, timeout]]])

与IO.select一样。

set_trace_func(trace_proc)

在Ruby解释器执行程序的过程中，每当发生方法调用或对表达式进行计算等事件时都将执行trace_proc过程对象。、以及等标准附件就是使用该内部函数来实现的。

例:

set_trace_func lambda {|event, file, line, id, binding, klass|
    # ....
}

块参数的意义如下。

event

表示发生事件的类别。详细内容如下。

  • "line" ... 表达式的计算。
  • "call" ... 方法调用。
  • "return" ... 从方法调用中返回。
  • "c-call" ... C(语言)方法的调用。
  • "c-return" ... 从C(语言)方法调用中返回。
  • "class" ... 进入类定义、特殊类定义或模块定义中。
  • "end" ... 类定义、特殊类定义或模块定义的结束。
  • "raise" ... 发生异常。
file

运行中的程序的源文件名(字符串)。

line

运行中的程序的源文件行号(整数)。

id

根据event的不同，将使用下列之一。与第六个块参数klass相对应。

line

Symbol对象，它表示最后被调用的方法。在顶层中则为nil。

call/return/c-call/c-return

Symbol对象，它表示被调用／return的方法。

class/end

nil。

raise

Symbol对象，它表示最后被调用的方法。在顶层中则为nil。

binding

Binding对象，它表示运行中的程序的状况(context)。

klass

根据event的不同，将使用下列之一。与第四个块参数id相对应。

line

Class对象，它表示最后被调用的方法的所属类。在顶层中则为false。

call/return/c-call/c-return

Class对象，它表示被调用／return的方法的所属类。

class/end

false。

raise

Class对象，它表示最后被调用的方法的所属类。在顶层中则为false。

sleep([sec])

使程序暂停sec秒。也可以将sec设为浮点数。省略sec时，若没有显式地(SIGALRM或其他线程发出的Thread#run)终止睡眠的话，将一直sleep下去。返回值是实际睡眠的秒数(整数)。

spawn(cmd, [arg, ...]) ((<ruby 1.9 特性>))

基本上等同于system，但它不会等待子进程的结束。返回生成的子进程的进程ID。

split([sep[, limit]])

使用sep模型将$_中的字符串分割成limit个部分后转化为数组，然后返回该数组。请参考String#split以获得更多的资讯。

sprintf(format ... )
format(format ... )

采用同C语言sprintf一样的方法对format字符串进行解释，格式化参数后返回结果。使用format时，就好像使用C语言sprintf一样。

Ruby对整数的大小没有上限，因此把负数指定给%b, %o, %x时(可看作左边有无数个1)，就会显示成..f。若想以“符号加绝对值”的形式进行输出的话，就得写成%+x、% x这样。

请参考sprintf格式化来获取更多资讯。

srand([seed])

设定rand的随机数的种子，返回旧的初始值(第一次返回0)。若省略初始值时，则以当前时刻、进程ID或srand的调用次数为基础生成一个种子。

sub(pattern[, replace])
sub!(pattern[, replace])
sub(pattern) {|matched| ... }
sub!(pattern) {|matched| ... }

在内部变量$_所指的字符串中,将首个匹配pattern的部分替换为replace,然后返回结果.若省略参数replace时,该方法将变为迭代器,以块的计算值进行替换操作.将匹配的字符串以参数的形式传递给块.

sub!将改写$_所包含的字符串本身.请参考String#sub来获得更多资讯.String#sub和sub之间存在以下差异.

  • sub方法会拷贝$_的值,然后对拷贝进行更新,最后再赋值给$_.
syscall(num, arg ... )

执行num号码所对应的系统调用。将第二参数以后的部分传给系统调用作为其参数。参数必须是字符串或整数。

请参考syscall(2)或 /usr/include/sys/syscall.h 以了解数值与系统调用的对应关系。

Ruby遵从系统调用的习惯，若syscall(2)返回-1时，引发Errno::EXXX异常。若返回其他值则不作变动。

system(command)
system(program[, arg1[, arg2[, ...]]])

执行command，成功(子进程以 status 0 的状态结束时)则返回真，失败(包括不能执行命令)则返回伪。可参照变量$?来获取结束状态值(status)。

当不能执行命令时，大多数的shell会返回状态值127。(所以$?的值为0x7f00)若没有使用shell时，Ruby子进程以状态值127结束。通常，查看$?的值即可区分是不能执行命令还是命令失败。

ruby 1.9 特性:不能执行命令时会引发Errno::EXXX异常。若使用了shell，则同上所述。

请参考exec以获取更多资讯。

另外，请参考`command`,open。

test(cmd, file1 [, file2])

进行文件测试。cmd可以是下列数值字面值或字符串(只把字符串的首字符当成命令)。

  • 带一个参数时

    • ?r

      可以使用有效 uid 读取文件

    • ?w

      可以使用有效 uid 写入文件

    • ?x

      可以使用有效 uid 执行文件

    • ?o

      文件的所有者是有效 uid

    • ?G

      文件的所有者group是有效 gid

    • ?R

      可以使用实 uid 读取文件

    • ?W

      可以使用实 uid 写入文件

    • ?X

      可以使用实 uid 执行文件

    • ?O

      文件的所有者是实 uid

    • ?e

      文件存在

    • ?z

      文件大小为 0

    • ?s

      文件大小非 0 (返回文件大小)

    • ?f

      文件是无格式(plain)文件

    • ?d

      文件是目录

    • ?l

      文件是符号连接

    • ?p

      文件是带名的管道(FIFO)

    • ?S

      文件是socket

    • ?b

      文件是特殊块文件

    • ?c

      文件是特殊字符文件

    • ?u

      文件中有 setuid bit

    • ?g

      文件中有 setgid bit

    • ?k

      文件中有 sticky bit

    • ?M

      返回最近更新文件时刻

    • ?A

      返回最近访问文件时刻

    • ?C

      返回文件的 inode 变更时刻

  • 带两个参数时

    • ?=

      文件1和文件2的最近更新时刻相同

    • ?>

      文件1的最近更新时刻要比文件2的新

    • ?<

      文件1的最近更新时刻要比文件2的旧

    • ?-

      文件2对文件1发出了硬连接指令

throw(tag[, value=nil])

跳到使用了相同的tag的catch块的终点(跳过方法)。若没有使用相同tag的catch的话，就以NameError结束线程。tag可以是字符串或符号。value将变为catch的返回值。

例:

ret = catch(:exit) {
   throw :exit, 25
   some_process()    # 决不会被执行
   10
}
p ret   #=> 25
trace_var(varname, hook)
trace_var(varname) {|newval| .... }

注册钩子以监视对全局变量varname所进行的赋值。varname可以是字符串或Symbol。

这里所说的“全局变量”是指以“$”开头的变量，包括特殊变量。

调用之后，只要varname所指的全局变量被赋值，就会计算字符串或Proc对象hook。若钩子是Proc对象的话，将把块参数中的值向外传递。另外，可注册多个钩子。

若想解除跟踪的话，可以把hook设为nil，还可以使用untrace_var。

例:

trace_var(:$v) {|val| puts "$v=#{val.inspect}" }
$v = "foo"   #=> $v="foo"
$v = 1       #=> $v=1

若hook为nil，将返回hook的数组(若是以块注册的话，将以Proc对象返回)。除此之外，将返回nil。

trap(signal, command)
trap(signal) { ... }

※ 在Ruby 1.8 以后的版本中，推荐使用Signal.trap。

将command注册为signal所指的中断的处理程序。signal可以是信号名字符串、Symbol或信号编号。

command可以是字符串或块。若将其设为nil、空字符串""、"SIG_IGN"或者"IGNORE"时，将忽略该信号(若可能的话)。若将其设为"SIG_DFL"或"DEFAULT"时，将进行默认的动作。若设为"EXIT"时，在接到信号之后将进行结束时的相关处理，然后以status 0 的状态结束。

还可以将signal设为0或"EXIT"等特殊值。这表示“程序结束时”。

Ruby解释器会为一些特定的信号引发异常Interrupt或SignalException。还可以使用异常处理来捕捉这些信号。

begin
  Process.kill :QUIT, $$   # 对自己发出SIGQUIT
rescue SignalException
  puts "rescue #$!"
end
# => rescue SIGQUIT

用trap()捕捉到的信号不会引发异常。

若注册了与信号相对应的command时，trap将返回它(若是块的话，将被当作Proc对象返回。若是"IGNORE"或"DEFAULT"则返回nil)。若没有注册的话则返回nil。

ruby 1.8 特性：在执行trap之前，若注册了"IGNORE"或"DEFAULT"的话，则返回"IGNORE"、"DEFAULT"。若注册了ruby解释器无法识别的信号处理程序时将返回nil。

p trap(:INT, "p true")     # => nil
p trap(:INT) { }           # => "p true"
p trap(:INT, "SIG_IGN")    # => #<Proc:0x401b1328>
p trap(:INT, "DEFAULT")    # => nil
p trap(:INT, "EXIT")       # => nil
p trap(:INT, nil)          # => "EXIT"

若指定了并不存在的信号时，将引发ArgumentError异常。

untrace_var(varname[, hook])

取消与全局变量varname有关的钩子。若指定了hook的话，将只会取消该钩子。若省略hook或将其设为nil时，将取消varname所有的钩子。返回取消钩子的数组。

例:

$v = nil
trace_var(:$v) {|val| puts "$v=#{val.inspect}" }
$v = 'str'        #=> $v="str"
untrace_var :$v
$v = 'str'        # 不输出任何信息
warn(mesg)

ruby 1.8 特性

依次输出mesg和换行符到$stderr之中。若内部变量$VERBOSE为nil时，则不会输出。这等同于

$stderr.print mesg, "\n" unless $VERBOSE.nil?

返回nil。

Originally Written by webmaster@ruby-lang.org
Translated by Community Engine China (Mail)
Last update on 2004-12-22


Ruby参考手册 - 内部变量

内部变量

本参考手册使用下列术语.

全局变量

所有以`$'开头的变量

内部变量

全局变量中的内部变量(本网页介绍的变量)

特殊变量

内部变量中,形如"`$' + 1位数字或符号"的变量

选项变量

内部变量中,由命令行选项设定的变量,形如"`$-' +1个选项字符"

有时,内部变量(有特殊的功能和用途)的有效作用域不只限于全局,尽管如此,上述定义还是把它们划入到全局变量的范畴中(可以在任何地方使用内部变量,从这种意义上说它们是全局的,但它们的值并不只限于全局).

根据变量值的作用域的不同,大致将内部变量划分如下.

局部域

下列也可看做是线程内的局部变量.

$_

gets或readline最后读入的字符串.若遇到EOF则为nil.该变量的作用域是局部域.(记忆方法:与 Perl相同)

$&

在当前作用域中,正则表达式最后一次匹配成功的字符串.若最后一次匹配失败,则为nil.(记忆方法: 它和某些编辑器中的&是相同的)

等同于Regexp.last_match[0].

$~

在当前作用域中,最后一次匹配成功的相关信息(MatchData对象).若对其进行设定的话, 则$&以及$1 ... $9等的值也会发生变化.

可以使用$~[n]的形式从该数据中抽取第n个匹配结果($n).(记忆方法: ~ 是用来进行匹配的)

等同于Regexp.last_match.

$`

在当前作用域中,正则表达式最后一次匹配成功的字符串前面的字符串.若最后的匹配失败则为nil.(记忆方法: `被放在字符串前面)

等同于Regexp.last_match.pre_match.

$'

在当前作用域中,正则表达式最后一次匹配成功的字符串后面的字符串.若最后的匹配失败则为nil.(记忆方法: '被放在字符串后面)

等同于Regexp.last_match.post_match.

$+

在当前作用域中,正则表达式最后一次匹配成功的字符串部分中,与最后一个括号相对应的那部分字符串.若最后的匹配失败则为nil.在多项选择型匹配模型中,若您无法断定是哪个部分匹配成功时,该变量将会非常有用.(记忆方法: be positive and forward looking.)

$1
$2
$3 ...

分别存储着最后一次模型匹配成功时与第n个括号相匹配的值.若没有相应的括号时,其值为nil.(记忆方法: 类似于 \数字)

等同于Regexp.last_match[1], Regexp.last_match[2],...

线程局部域

下列变量在一个线程内部时是全局域变量,但在不同的线程之间是彼此独立的.

$?

本线程中最后结束的子进程的状态值. 1.6版本以前是整数,从1.7版本开始变为Process::Status对象.另外,请参考Process#wait等.

整数值就是使用wait()系统调用所获得的值,要想得到子进程的exit status的话,还得除以256($?/256). 1.7版本以后还可以使用Process::Status#exitstatus.

$!

最近发生的异常的信息.由raise设定.

$@

以数组形式保存着发生异常时的back trace信息. 数组元素是字符串,它显示了方法调用的位置,其形式为

"filename:line"

或

"filename:line:in `methodname'"

这和caller的返回值形式一致。

在向$@赋值时，$!不能为nil。调用$@的方法与$!.backtrace相同。而赋值方法与$!.set_backtrace相同。

(记忆方法：where exception occurred at.)

$SAFE

当前线程的安全等级。关于安全等级，请参考安全模型。

与Thread.current.safe_level相同。

全局域

$=

obsolete: 该变量将被废止。

说明在模式匹配或字符串比较中是否要区分大小写字母的标识。默认值为nil。

$/

输入记录分隔符。默认值为"\n"。其运作类似于awk的RS变量。

若将该变量设为 nil 时，将一次读入整个文件。若设为空字符串 "" 则将是段落模式，此时会把2个以上的连续的换行符当作记录切分符。

不能在$/中使用正则表达式。

(记忆方法: 在诗歌中使用 / 作为行的切分)

$\

输出记录分隔符。print会在最后输出该字符串。

默认值为nil，此时不会输出任何字符。

$,

默认的切分字符。若Array#join中省略了参数时或在print的各个参数间将会输出它。

默认值为 nil ，等同于空字符串。

$;

当String#split中省略参数时的切分字符。默认值为nil，此时将进行特殊的分割。详情请参考String#split。

ruby 1.8 feature：在1.6版本中只能把字符串赋值给$;。在1.8版本中则可以将任何对象代入其中，但考虑到String#split的变更问题，还是应该使用正则表达式。

同时，为了提供兼容性，最好不要依赖于 $; 。

$.

最后读入的输入文件的行号。

与ARGF.lineno相同。若需要取得每个参数文件的行号时，需要使用ARGF.file.lineno。

$<

由参数(若没的话就使用标准输入)构成的虚拟文件。也就是常数ARGF的别名。(记忆方法: <指定了shell的输入源)

$deferr ((<ruby 1.8 特性>)) ((<obsolete>))

Ruby解释器输出错误信息、警告信息和warn时的输出对象。

只能将内部带有write方法的对象赋值给该变量。(warn 等内部方法最终将调用$deferr.write方法)。

$deferr 是 $stderr 的别名。$deferr (尽管它刚出现不久) 将被废止。

$>
$defout ((<obsolete>))

内部函数print、puts或p等的默认输出对象。初始值为STDOUT。若指定了-i[extension]选项的话，则将使用与读取源同名的文件。(记忆方法: >指定了shell的输出对象)

只能将内部带有write方法的对象赋值给该变量(print等内部方法最终将调用write方法)。

若想改变print等Ruby内部函数的输出对象时，可以将该变量的值设定为别的IO即可。若想要改变子进程或C语言扩展库的标准输出时，则必须使用IO#reopen将标准输出重定向(redirect)到别的IO。请参考$stdout。

ruby 1.8 特性:

$defout 是 $stdout 的别名。$defout 是obsolete。

$0
$PROGRAM_NAME ((<ruby 1.8 特性>))

当前运行中的Ruby脚本名.根据OS的不同,有时向该变量赋值后,ps(1)的输出会发生变化.该功能适合于用来表示当前程序的状态.(记忆方法: 与sh 或 ksh 相同)

$*

传递给Ruby脚本的参数.内部常数ARGV的别名.Ruby自身用的参数已经被摘除.(记忆方法: 与sh 或 ksh 相同)

$$

当前运行中的Ruby进程的pid。(记忆方法: 与shell相同)

与Process.pid相同.

$:
$LOAD_PATH

包含一个数组,其内容是load或require加载文件时用的搜索目录列表.(记忆方法: 冒号是环境变量PATH的切分符)

包含下列内容:启动时-I directory 选项所指定的目录,环境变量RUBYLIB的值,编译时指定的默认值还有"."(当前目录).下列就是典型的UNIX系统上的加载路径.

-I 指定的路径
环境变量 RUBYLIB 的值
/usr/local/lib/ruby/site_ruby/VERSION        site固有的,取决于VERSION的库
/usr/local/lib/ruby/site_ruby/VERSION/ARCH   site固有的,取决于系统的扩展库
/usr/local/lib/ruby/site_ruby                site固有的库
/usr/local/lib/ruby/VERSION                  标准库
/usr/local/lib/ruby/VERSION/ARCH             标准的,取决于系统的扩展库
.                                            当前目录

上表中的VERSION是表示Ruby版本的字符串,如"1.6"或"1.8"等.ARCH是表示硬件和OS的字符串,如"i686-linux"或"alpha-osf5.1"等.可以从Config::CONFIG['arch']中得到这些信息.

在多数UNIX系统中,编译时的默认路径是"/usr/local/lib/ruby".在mswin32,mingw32,cygwin,bccwin32,mswince这些环境中,是以ruby.dll所在位置为起点的相对路径.而在djgpp,emx(OS/2)中,则是以ruby.exe所在位置为起点的相对路径.

在使用-T 选项启动时,若将$SAFE设为1以上的值的话,则"." (当前目录)不会被纳入加载路径.

如require 'foo'时,将交互搜索.rb和.so.

/usr/local/lib/ruby/site_ruby/VERSION/foo.rb
/usr/local/lib/ruby/site_ruby/VERSION/foo.so
/usr/local/lib/ruby/site_ruby/VERSION/ARCH/foo.rb
/usr/local/lib/ruby/site_ruby/VERSION/ARCH/foo.so
  :
  :

有的系统的共享库扩展名并非.so,此时将自动使用新的扩展名.例如在HP-UX上require 'foo.so'时将搜索foo.sl.因此在Ruby内部,可以一直使用.so.

若想用命令行查看加载路径的话,可以这样

$ ruby -e 'puts $:'

即可.

$"
$LOADED_FEATURES ((<ruby 1.8 特性>))

包含以require加载的文件名的数组.这可以防止require多次加载同一个文件.

$DEBUG

若此值为真则变成调试模式。它由-d选项进行设定。

调试模式与普通的运行有以下不同。

  • 若某线程因发生异常而结束时，整个解释器也将中止工作。这等同于将Thread.abort_on_exception设置为true的效果，但是在调试模式中，即使在脚本中使用 abort_on_exception= 类方法来重置标识也无法取消该效果。

    在通常的运行中，若某线程发生了异常却并没有被Thread#join等检测到的话，该线程将被无警告地终止。

  • 不管有没有捕捉到异常，只要它一旦发生就会被报告到 $stderr 。处理会继续进行。
$FILENAME

虚拟文件ARGF中，当前正在读入的(gets方法正在读的)文件名。与ARGF.filename相同。

$LOAD_PATH

$:的别名。

$stdin
$stdout
$stderr

标准输入，标准输出，标准错误输出。

ruby 1.8 特性

$stdout, $stderr 是 $defout,$deferr 的别名。($defout, $deferr 已经废止)

$stdout, $stderr的对应对象中必须要有名为write的方法。详细情况请参考defout,deferr。

$stdin同$stdout、$stderr一样，即使没有特定的方法也可以对其赋值。若执行gets等方法时，该方法将被投射到$stdin对应的对象中。(将执行$stdin.gets)

$stdin所对应的对象中应该定义下列方法。(请根据需要取舍)

gets, readline, readlines, getc, readchar, tell, seek,
pos=, rewind, fileno, to_io, eof, each_line, each_byte,
binmode, closed?

例:

$stdin = Object.new
def $stdin.gets
  "foo"
end
p gets

# => "foo"

若想对标准输入、输出、错误输出等进行重定向(redirect)时，可以使用IO#reopen(1.6版也是如此)。例如

$stdout = File.open("/tmp/foo", "w")

写成这样

STDOUT.reopen("/tmp/foo", "w")

就可以了。若想取消重定向时

stdout_sv = STDOUT.dup          # 保存 STDOUT
STDOUT.reopen("/tmp/foo")       # 将 STDOUT 重定向到 /tmp/foo

puts "foo"                      # 输出到 /tmp/foo

STDOUT.flush                    # 必须(?)
STDOUT.reopen(stdout_sv)        # 恢复原状

就可以了。若您不想让重定向影响到子进程的话，只要向$stdout等赋值就足够了。

# 改变输出方法的默认输出对象
$stdout = File.open("/tmp/foo", "w")

puts "foo"

# 取回输出方法的默认输出对象。
$stdout = STDOUT

ruby 1.6 特性: 向$stdin、$stdout、$stderr赋值时，会进行重定向。

ruby 1.7 特性: 暂时修改了重定向的运作方式[ruby-dev:14601]。

$VERBOSE

冗长消息标识。由面向Ruby解释器的-v选项进行设定。

ruby 1.8 特性

警告等级分为三级，分别如下。

  • nil: 不输出警告
  • false: 只输出重要警告(默认)
  • true: 输出所有警告

可以使用命令行选项-W[level]来指定警告等级，分别为-W0、-W1、-W2 (or -W)。指定-v或-w时，等同于-W2。

若设定为nil、false之外的值时，其值为变为true。

$KCODE

Ruby可识别的多字节字符串的编码。变量值为"EUC" "SJIS" "UTF8" "NONE"之一。

当$KCODE的值为"EUC"时，将假定字符串或正则表达式的编码为EUC-JP。同样地，若为"SJIS"时则认定为Shift JIS。若为"UTF8"时则认定为UTF-8。若为"NONE"时，将不会识别多字节字符串。

在向该变量赋值时，只有第1个字节起作用，且不区分大小写字母。"e" "E" 代表 "EUC"，"s" "S" 代表 "SJIS"，"u" "U" 代表 "UTF8"，而"n" "N" 则代表 "NONE"。

默认值为"NONE"。

[参考]

目前$KCODE将对Ruby的下列动作产生影响。

  • 解释器的字句解析器
  • Regexp的编码标识的默认值
  • (正则表达式字面值
  • Regexp.new)
  • upcase
  • downcase
  • swapcase
  • capitalize
  • inspect
  • split
  • gsub
  • scan

选项变量

用来显示Ruby解释器命令行信息的变量。其形式为$-?，?的部分是1位选项字符。

$-0

$/ 的别名。

$-a

若指定了-a时，其值为真。只读变量。

$-d

$DEBUG 的别名。

$-F

$; 的别名。

$-i

若指定了-i[extension]时，它将保存扩展名字符串。否则其值为nil。也可以在脚本中对其进行赋值，此时将在开始读入ARGV中的下一个文件之前进行in-place替换。

$-I

$LOAD_PATH 的别名。

$-K

$KCODE 的别名。

$-l

若指定了-l时，其值为真。只读变量。

$-p

若指定了-p时，其值为真。只读变量。

$-v
$-w

$VERBOSE 的别名。

$-W ((<ruby 1.9 特性>))

返回由-W[level]指定的值。

也就是说，根据$VERBOSE的取值不同

  • nil: 不输出警告 -> 0
  • false: 只输出重要警告(默认) -> 1
  • true: 输出所有警告 -> 2

而返回上述诸值之一。只读变量。

Originally Written by webmaster@ruby-lang.org
Translated by Community Engine China (Mail)
Last update on 2004-12-22


Ruby参考手册 - 内部常数

内部常数

TRUE ((<obsolete>))

真值的代表。与true相同。Ruby中把所有非伪值(即非false又非nil的值)都看作真。(请参考obsolete。)

FALSE ((<obsolete>))

伪值。与false相同。(请参考obsolete。)

NIL ((<obsolete>))

与nil相同。(请参考obsolete。)

STDIN

标准输入。$stdin的默认值。

STDOUT

标准输出。$stdout的默认值。

STDERR

标准错误输出。$stderr的默认值。

ENV

表示环境变量的(伪)哈希表(详情请参考ENV)。若改变该哈希表的值时，将作为子进程环境而被继承下来。

ARGF

由参数(若没的话就使用标准输入)构成的虚拟文件(详情请参考ARGF)。也就是说，gets与ARGF.gets的意义相同。可以使用ARGF.file得到当前读入的文件对象，使用ARGF.filename得到当前读入的文件名。

ARGV

传给Ruby脚本的参数。内部变量$*的别名。而面向Ruby自身的参数已经被摘除。

DATA

访问脚本中__END__(脚本终点)以后部分的File对象。

注意:

  • 使用DATA.rewind后的读取位置不再是__END__后面，而是脚本文件的开头。
  • 若读入脚本时使用的是标准输入的话，则为标准输入。
  • 如果读入脚本时并没有使用文件或标准输入，或者脚本并非以__END__结束时，则该定义将不再适用。
  • 即使用require或load加载文件时，也并不是指该文件(_FILE_)，而是指运行文件($0)。
TOPLEVEL_BINDING

顶层中的Binding对象。

RUBY_VERSION

显示Ruby版本的字符串。Ruby版本信息的形式为major.minor.teeny。(当前)各个号码的位数不会超过2位，因此

if RUBY_VERSION >= '1.6.7'
  # 在1.6.7以后的版本中有效的操作
else
  # 在此之前的版本中有效地操作
end

按照版本信息的不同来进行分支处理。

在1.8以前的版本中，minor是奇数时则为开发版，minor是偶数时则为稳定版。在1.9.0以后的版本中，teeny为0的版本是开发版。

RUBY_RELEASE_DATE

显示Ruby发布日期的字符串。

RUBY_PLATFORM

显示平台信息的字符串。

VERSION ((<obsolete>))
RELEASE_DATE ((<obsolete>))
PLATFORM ((<obsolete>))

这些都是前面那些常数RUBY_XXX的旧名称。请参考obsolete。

ruby 1.9 特性: 这些常数已经停用。

SCRIPT_LINES__

默认情况下，该常数不会被定义。

若将该常数定义为哈希表后，在编译源代码时，源文件名将成为哈希表元素的索引，而源文件的内容会被按行切分后转化为数组，该数组将成为哈希表元素的内容。在调试(debug.rb)等场合会用到该常数。

SCRIPT_LINES__ = {}
require 'English'
p SCRIPT_LINES__

# 注: 输出内容被处理过。
# => {"/usr/local/lib/ruby/1.6/English.rb"=>
      ["alias $ERROR_INFO              $!\n",
       "alias $ERROR_POSITION          $@\n",
       "alias $LOADED_FEATURES         $\"\n",
                :
                :
       "alias $POSTMATCH               $'\n",
       "alias $LAST_PAREN_MATCH        $+\n"]}

另外,只有在$SAFE等级为0时才有效。同时，也不能由eval进行编译。

Originally Written by webmaster@ruby-lang.org
Translated by Community Engine China (Mail)
Last update on 2004-12-22


Ruby参考手册 - 内部类／模块／异常类

内部类／模块／异常类

内部类

  • Object
    • Array
      • Values (ruby 1.9 特性)
    • Binding
    • Continuation
    • Data
    • Exception (参考异常类)
    • Dir
    • FalseClass
    • File::Stat
    • Hash
    • IO
      • File
    • MatchData
    • Method
      • (UnboundMethod) (ruby 1.7 特性 version 1.6以前的位置)
    • Module
      • Class
    • Numeric
      • Integer
        • Bignum
        • Fixnum
      • Float
    • Proc
    • Process::Status (ruby 1.7 特性)
    • Range
    • Regexp
    • String
    • Struct
    • Symbol
    • Thread
    • ThreadGroup
    • Time
    • TrueClass
    • UnboundMethod (ruby 1.7 特性)
    • NilClass

内部模块

  • Comparable
  • Enumerable
  • Errno
  • File::Constants
  • FileTest
  • GC
  • Kernel
  • Marshal
  • Math
  • ObjectSpace
  • Precision
  • Process
  • Process::GID (ruby 1.8 特性)
  • Process::Sys (ruby 1.8 特性)
  • Process::UID (ruby 1.8 特性)
  • Signal (ruby 1.7 特性)

异常类

  • Object
    • Exception
      • (Interrupt) (ruby 1.7 特性 version 1.6以前的位置)
      • NoMemoryError
      • ScriptError
        • LoadError
        • (NameError) (ruby 1.7 特性 version 1.6以前的位置)
        • NotImplementedError
        • SyntaxError
      • SignalException
        • Interrupt (ruby 1.7 特性)
      • StandardError
        • ArgumentError
        • IndexError
          • KeyError (ruby 1.9 特性)
        • IOError
          • EOFError
        • LocalJumpError
        • NameError (ruby 1.7 特性)
          • NoMethodError (ruby 1.7 特性)
        • RangeError
          • FloatDomainError
        • RegexpError
        • RuntimeError
        • SecurityError
        • SystemCallError
          • Errno::EXXX
        • SystemStackError
        • ThreadError
        • TypeError
        • ZeroDivisionError
      • SystemExit
      • fatal
Originally Written by webmaster@ruby-lang.org
Translated by Community Engine China (Mail)
Last update on 2004-12-22


Ruby参考手册 - 附加库

附加库

在Ruby中, 可以使用库来添加新的类,模块或方法. 下列就是标准发布包中附加库的一览表. 读取库时需要使用require或load.

附加库一览表

文本 /文件 /网络 /输入输出 /日语 /数学 /数据库 /画面控制/CUI /GUI /日期·时间 /多线程·同步 /Unix /MS Windows /正则表达式 /GC /Design Pattern /开发工具 /命令行 /其他

  • English.rb 给特殊变量 $! 等添加英文别名($ERROR_INFO 等)
  • Env.rb 该库已经停用(obsolete)
  • base64.rb 处理Base64编码的模块
  • benchmark.rb ruby 1.7 特性
  • bigdecimal.so 可变长度浮点数运算 ruby 1.8 特性
  • bigdecimal/jacobian.rb
  • bigdecimal/ludcmp.rb
  • bigdecimal/newton.rb
  • bigdecimal/nlsolve.rb
  • bigdecimal/utils.rb
  • cgi/session.rb CGI会话管理
    • CGI::Session
  • cgi/session/pstore.rb
    • CGI::Session::PStore
  • cgi-lib.rb 该库已经停用(obsolete)(请使用cgi.rb)
  • cgi.rb 辅助生成CGI
    • CGI
  • complex.rb 复数类
    • Complex
  • csv.rb CSV(Comma Separated Values)库 ruby 1.8 特性
  • curses.so 终端操作库 curses 的接口
    • Curses 模块
    • Curses::Window 类
  • date.rb 日期类
    • Date
    • DateTime
  • date2.rb 该库已经停用(obsolete)(请使用date.rb)
  • dbm.so 可将ndbm用作哈希表的库
    • DBM
  • debug.rb Ruby 调试器
  • delegate.rb 支持委托的类
    • Delegator
      • SimpleDelegator
  • digest.so 消息摘要库 请参考[ruby-src:ext/digest/digest.txt.ja]
  • digest/md5.so
  • digest/rmd160.so
  • digest/sha1.so
  • digest/sha2.so
    • Digest::Base
      • Digest::MD5
      • Digest::RMD160
      • Digest::SHA1
      • Digest::SHA256
      • Digest::SHA384
      • Digest::SHA512
  • dl.so [ruby-src:ext/dl/doc/dl.txt] ruby 1.7 特性
  • dl/import.rb 请参考dl.so 。ruby 1.7 特性
  • dl/struct.rb 请参考dl.so 。ruby 1.7 特性
  • dl/types.rb 请参考dl.so 。ruby 1.7 特性
  • dl/win32.rb 用来代替Win32API。ruby 1.7 特性
  • drb.rb 分布式 Ruby (dRuby) ruby 1.8 特性
  • e2mmap.rb 异常类和消息的映象
    • Exception2MessageMapper
  • enumerator.so
    • Enumerable::Enumerator
  • erb.rb 嵌入 Ruby (ERB) ruby 1.8 特性
    • ERB
  • eregex.rb 可使用`|', `&'操作符将2个正则表达式连接起来
  • etc.so 操作/etc/passwd等的库
    • Etc
  • expect.rb 在脚本中控制交互程序
  • fcntl.so 该模块中囊括了fcntl(2)中用到的常数
    • Fcntl
  • fileutils.rb 文件操作utility (ftools.rb 的升级版) ruby 1.7 特性
    • module FileUtils
    • module FileUtils::Verbose
    • module FileUtils::NoWrite
  • final.rb 该库已经停用(obsolete)(已被纳入系统. 在1.8中,该库已被取消)
  • finalize.rb
    • Finalizer
  • find.rb 文件搜索模块
    • Find
  • forwardable.rb 向类中定义方法委托的功能(请参考[ruby-src:doc/forwardable.rd.ja])
    • Forwardable
    • SingleForwardable
  • ftools.rb 文件操作utility(旧)
  • ftplib.rb 该库已经停用(obsolete)(请使用net/ftp.rb. 在1.8中,该库已被取消)
  • gdbm.so 将gdbm (GNU dbm)用作哈希表的库
    • GDBM
  • getoptlong.rb 命令行选项的解析 请参考<URL:http://www.sra.co.jp/people/m-kasahr/ruby/getoptlong/>
  • getopts.rb 命令行选项的解析
  • gserver.rb Ruby Generic Server ruby 1.8 特性
  • iconv.so 字符串编码转换库 [ruby-src:ext/iconv/iconv.c] ruby 1.7 特性
    • Iconv
  • importenv.rb 以操作全局变量的方式来处理环境变量
  • io/nonblock.rb IO 类的扩展(添加与nonblock模式相关的方法)
  • io/wait.so IO 类的扩展(添加负责等待IO输入的方法)
  • ipaddr.rb IP地址类(IPAddr) ruby 1.8 特性
  • irb.rb Interactive Ruby (Ruby 的交互界面) 请参考[ruby-src:doc/irb/irb.rd.ja], [ruby-src:doc/irb/irb-tools.rd.ja]
  • jcode.rb 使String类支持日语
  • kconv.rb 汉字编码转换
    • Kconv
  • mailread.rb 从mail中获取信息
    • Mail
  • mathn.rb
  • matrix.rb 矩阵类
    • Matrix
    • Vector
  • md5.rb 该库已经停用(obsolete)(请使用digest/md5.so)
  • mkmf.rb 制作扩展库的工具
  • monitor.rb
  • multi-tk.rb 支持多个Tcl解释器的Ruby/Tk ruby 1.8 特性
  • mutex_m.rb
  • net/ftp.rb
    • Net::FTP
  • net/ftptls.rb
  • net/http.rb
    • class Net::HTTP
    • class Net::HTTPRequest
    • class Net::HTTPResponse
  • net/https.rb
  • net/imap.rb
  • net/pop.rb
    • Net::POP3 class
    • Net::APOP
    • Net::POPMail
  • net/smtp.rb
    • class Net::SMTP
  • net/telnet.rb
    • Net::Telnet
  • net/telnets.rb
  • nkf.so 日语字符代码编码转换
    • NKF
  • observer.rb Ruby中的Observer Pattern
    • Observable
  • open-uri.rb open() 的 URI支持扩展 ruby 1.8 特性
  • open3.rb
    • Open3
  • openssl.rb Ruby/OpenSSL ruby 1.8 特性
  • optparse.rb 命令行选项的解析 ruby 1.7 特性
  • ostruct.rb Python 式的“attr on write”Struct
    • OpenStruct
  • parsearg.rb 命令行选项的解析(请参考getopts.rb)
  • parsedate.rb 日期格式的解析
    • ParseDate
  • pathname.rb 路径名类 ruby 1.8 特性
  • ping.rb 验证数据包是否到达主机
    • Ping
  • pp.rb Pretty-printer ruby 1.7 特性
    • PP
  • prettyprint.rb PrettyPrint ruby 1.7 特性
    • PrettyPrint
  • profile.rb PROFILER
  • profiler.rb ruby 1.7 特性
  • pstore.rb 对象永久化
    • PStore
  • pty.so 处理伪终端(Pseudo tTY)的模块
    • PTY
  • racc/parser.rb Racc 运行时库
  • racc/cparse.so
  • rational.rb
    • Rational
  • rbconfig.rb Ruby 解释器的设置信息
    • Config
  • readbytes.rb
  • readline.so GNU Readline 接口
    • Readline
  • resolv-replace.rb 在处理Socket相关类名时使用resolv.rb
  • resolv.rb Ruby版Resolver
    • Resolv
  • rexml/rexml.rb XML parser ruby 1.8 特性
    • REXML
  • rubyunit.rb [RAA:RubyUnit]兼容库 ruby 1.8 特性
    • RUNIT::Assert
  • scanf.rb ruby 1.8 特性
  • sdbm.so
    • SDBM
  • set.rb 有限集 ruby 1.7 特性
    • Set ruby 1.7 特性
  • sha1.rb 该库已经停用(obsolete)(请使用digest/sha1.so)
  • shell.rb 请参考[ruby-src:doc/shell.rd.ja]
  • shellwords.rb
    • Shellwords
  • singleton.rb Ruby中的Singleton Pattern
    • Singleton
  • socket.so socket扩展库
    • BasicSocket
      • IPSocket
        • TCPSocket
          • TCPServer
          • SOCKSSocket
        • UDPSocket
      • UNIXSocket
        • UNIXServer
      • Socket
    • Socket::Constants
  • stringio.so 将字符串伪装成IO ruby 1.7 特性
    • StringIO
  • strscan.so 高速Scanner ruby 1.7 特性
    • class StringScanner
  • sync.rb
    • Sync_m
    • Synchronizer_m
    • Sync
    • Synchronizer
  • syslog.so
    • Syslog
    • Syslog::Constants
  • tcltk.rb Tcl/Tk 库 (与tk.rb不同,它直接调用Tcl/Tk)
  • telnet.rb 该库已经停用(obsolete)(请使用net/telnet.rb。在1.8中，该库已被删除)
  • tempfile.rb 生成临时文件
    • Tempfile
  • test/unit.rb unit测试 ruby 1.8 特性
    • Test::Unit
  • thread.rb 与线程相关的utility
    • ConditionVariable
    • Mutex
    • Queue
      • SizedQueue
  • thwait.rb
    • ThreadsWait
    • ThWait
  • time.rb 字符串和Time对象的变换
  • timeout.rb 处理超时的方法 timeout
    • TimeoutError
  • tk.rb Ruby/Tk 请参考<URL:http://ns103.net/~arai/ruby/rubytk.html.gz>
  • tmpdir.rb 返回临时目录 ruby 1.8 特性
  • tracer.rb Ruby 的tracer
    • Tracer
  • tsort.rb 拓扑排序和强连接成分 ruby 1.7 特性
    • TSort
  • un.rb 类似Unix命令的文件操作utility ruby 1.8 特性
  • uri.rb URI 库
  • uri/common.rb
  • uri/ftp.rb
  • uri/generic.rb
  • uri/http.rb
  • uri/https.rb
  • uri/ldap.rb
  • uri/mailto.rb
  • weakref.rb 生成可被GC回收的"弱"reference
    • class WeakRef
    • class WeakRef::RefError
  • webrick.rb WEB server toolkit
  • Win32API.so 调用Win32 API的类 (win32 专用) 以后将用 dl/win32.rb 来取代它
    • Win32API
  • win32ole.so [ruby-src:ext/win32ole/doc/win32ole.rd] ruby 1.7 特性
    • WIN32OLE
  • win32/registry.rb ruby 1.7 特性
    • Win32::Registry
  • xmlrpc/base64.rb ruby 1.8 特性
  • xmlrpc/client.rb ruby 1.8 特性
  • xmlrpc/config.rb ruby 1.8 特性
  • xmlrpc/create.rb ruby 1.8 特性
  • xmlrpc/datetime.rb ruby 1.8 特性
  • xmlrpc/httpserver.rb ruby 1.8 特性
  • xmlrpc/marshal.rb ruby 1.8 特性
  • xmlrpc/parser.rb ruby 1.8 特性
  • xmlrpc/server.rb ruby 1.8 特性
  • xmlrpc/utils.rb ruby 1.8 特性
    • XMLRPC
  • yaml.rb YAML ruby 1.8 特性
  • zlib.so 字符串压缩·扩展 ruby 1.8 特性

范畴分类

文本

  • base64.rb 处理Base64编码的模块
  • csv.rb CSV(Comma Separated Values)库 ruby 1.8 特性
  • digest.so 消息摘要库 请参考[ruby-src:ext/digest/digest.txt.ja]
  • digest/md5.so
  • digest/rmd160.so
  • digest/sha1.so
  • digest/sha2.so
  • erb.rb 嵌入Ruby(ERB) ruby 1.8 特性
  • iconv.so 字符串编码转换库 [ruby-src:ext/iconv/iconv.c] ruby 1.7 特性
  • mailread.rb 从mail中获取信息
  • shellwords.rb
  • stringio.so 将字符串伪装成IO ruby 1.7 特性
  • strscan.so 高速Scanner ruby 1.7 特性
  • yaml.rb YAML ruby 1.8 特性
  • zlib.so 字符串压缩·扩展 ruby 1.8 特性

文件

  • ftools.rb 文件操作utility(旧)
  • fileutils.rb 文件操作utility (ftools.rb 的升级版) ruby 1.7 特性
  • find.rb 文件搜索模块
  • io/nonblock.rb IO 类的扩展(添加与nonblock模式相关的方法)
  • io/wait.so IO 类的扩展(添加负责等待IO输入的方法)
  • pathname.rb 路径名类 ruby 1.8 特性
  • tempfile.rb 生成临时文件
  • tmpdir.rb 返回临时目录 ruby 1.8 特性
  • un.rb 类似Unix命令的文件操作utility ruby 1.8 特性

网络

  • cgi/session.rb CGI会话管理
  • cgi/session/pstore.rb
  • cgi.rb 辅助生成CGI
  • drb.rb 分布式 Ruby (dRuby) ruby 1.8 特性
  • gserver.rb Ruby Generic Server ruby 1.8 特性
  • ipaddr.rb IP地址类(IPAddr) ruby 1.8 特性
  • net/ftp.rb
  • net/ftptls.rb
  • net/http.rb
  • net/https.rb
  • net/imap.rb
  • net/pop.rb
  • net/smtp.rb
  • net/telnet.rb
  • net/telnets.rb
  • openssl.rb Ruby/OpenSSL ruby 1.8 特性
  • open-uri.rb open() 的 URI支持扩展 ruby 1.8 特性
  • ping.rb 验证数据包是否到达主机
  • resolv-replace.rb 在处理Socket相关类名时使用resolv.rb
  • resolv.rb Ruby版Resolver
  • socket.so socket扩展库
  • webrick.rb WEB server toolkit
  • xmlrpc/base64.rb XML-RPC (remote procedure calls over HTTP using XML) for Ruby ruby 1.8 特性
  • xmlrpc/client.rb ruby 1.8 特性
  • xmlrpc/config.rb ruby 1.8 特性
  • xmlrpc/create.rb ruby 1.8 特性
  • xmlrpc/datetime.rb ruby 1.8 特性
  • xmlrpc/httpserver.rb ruby 1.8 特性
  • xmlrpc/marshal.rb ruby 1.8 特性
  • xmlrpc/parser.rb ruby 1.8 特性
  • xmlrpc/server.rb ruby 1.8 特性
  • xmlrpc/utils.rb ruby 1.8 特性

输入输出

  • open3.rb
  • readbytes.rb
  • scanf.rb ruby 1.8 特性

日语

  • jcode.rb 使String类支持日语
  • kconv.rb 汉字编码转换
  • nkf.so 日语字符代码编码转换

数学

  • bigdecimal.so 可变长度浮点数运算 ruby 1.8 特性
  • bigdecimal/jacobian.rb
  • bigdecimal/ludcmp.rb
  • bigdecimal/newton.rb
  • bigdecimal/nlsolve.rb
  • bigdecimal/utils.rb
  • complex.rb 复数类
  • mathn.rb
  • matrix.rb 矩阵类
  • rational.rb
  • set.rb 有限集 ruby 1.7 特性

数据库

  • dbm.so 可将ndbm用作哈希表的库
  • gdbm.so 将gdbm (GNU dbm)用作哈希表的库
  • pstore.rb 对象永久化
  • sdbm.so

画面控制/CUI

  • curses.so 终端操作库 curses 的接口
  • expect.rb 在脚本中控制交互程序
  • fcntl.so 该模块中囊括了fcntl(2)中用到的常数
  • irb.rb Interactive Ruby (Ruby 的交互界面)
  • pty.so 处理伪终端(Pseudo tTY)的模块
  • readline.so GNU Readline 接口
  • shell.rb 请参考[ruby-src:doc/shell.rd.ja]

GUI

  • multi-tk.rb 支持多个Tcl解释器的Ruby/Tk ruby 1.8 特性
  • tk.rb Ruby/Tk <URL:http://ns103.net/~arai/ruby/rubytk.html.gz>

日期·时间

  • date.rb 日期类
  • parsedate.rb 日期格式的解析
  • time.rb 字符串和Time对象的变换

多线程·同步

  • monitor.rb
  • mutex_m.rb
  • sync.rb
  • thread.rb 与线程相关的utility
  • thwait.rb

Unix

  • etc.so 操作/etc/passwd等的库
  • syslog.so UNIX syslog 接口

MS Windows

  • Win32API.so 调用Win32 API的类 (win32 专用) 以后将用 dl/win32.rb 来取代它
  • win32ole.so [ruby-src:ext/win32ole/doc/win32ole.rd] ruby 1.7 特性
  • win32/registry.rb ruby 1.7 特性

正则表达式

  • eregex.rb 可使用`|', `&'操作符将2个正则表达式连接起来

GC

  • finalize.rb
  • weakref.rb 生成可被GC回收的"弱"reference

Design Pattern

  • delegate.rb 支持委托的类
  • forwardable.rb 向类中定义方法委托的功能(请参考[ruby-src:doc/forwardable.rd.ja])
  • observer.rb Ruby中的Observer Pattern
  • singleton.rb Ruby中的Singleton Pattern

开发工具

  • benchmark.rb ruby 1.7 特性
  • debug.rb Ruby 调试器
  • mkmf.rb 制作扩展库的工具
  • profile.rb PROFILER
  • profiler.rb ruby 1.7 特性
  • rbconfig.rb Ruby 解释器的设置信息
  • rubyunit.rb [RAA:RubyUnit]兼容库 ruby 1.8 特性
  • test/unit.rb unit测试 ruby 1.8 特性
  • tracer.rb Ruby 的tracer

命令行

  • getoptlong.rb 命令行选项的解析
  • getopts.rb 命令行选项的解析
  • parsearg.rb 命令行选项的解析(getopts.rb)
  • optparse.rb 命令行选项的解析 ruby 1.7 特性

其他

  • English.rb 给特殊变量 $! 等添加英文别名($ERROR_INFO 等)
  • dl.so [ruby-src:ext/dl/doc/dl.txt] ruby 1.7 特性
  • dl/import.rb 请参考dl.so 。ruby 1.7 特性
  • dl/struct.rb 请参考dl.so 。ruby 1.7 特性
  • dl/types.rb 请参考dl.so 。ruby 1.7 特性
  • dl/win32.rb 用来代替Win32API。ruby 1.7 特性
  • e2mmap.rb 异常类和消息的映象
  • enumerator.so
  • importenv.rb 以操作全局变量的方式来处理环境变量
  • ostruct.rb Python 式的“attr on write”Struct
  • pp.rb Pretty-printer ruby 1.7 特性
  • prettyprint.rb PrettyPrint ruby 1.7 特性
  • racc/parser.rb Racc 运行时库
  • racc/cparse.so
  • rexml/rexml.rb XML parser ruby 1.8 特性
  • timeout.rb 处理超时的方法 timeout
  • tsort.rb 拓扑排序和强连接成分 ruby 1.7 特性
  • uri.rb URI 库
  • uri/common.rb
  • uri/ftp.rb
  • uri/generic.rb
  • uri/http.rb
  • uri/https.rb
  • uri/ldap.rb
  • uri/mailto.rb
Originally Written by webmaster@ruby-lang.org
Translated by Community Engine China (Mail)
Last update on 2004-12-22


Ruby参考手册 - Ruby变更记录

Ruby变更记录

  • ruby 1.6 特性
  • ruby 1.7 特性
  • ruby 1.8 特性
  • obsolete
  • 顺应DOSISH
Originally Written by webmaster@ruby-lang.org
Translated by Community Engine China (Mail)
Last update on 2005-01-24


Ruby参考手册 - ruby 1.6 特性

ruby 1.6 特性

ruby version 1.6是稳定版。在该版本中，主要修改了一些bug。

stable-snapshot是稳定版的源代码，且每日更新。

1.6.8 (2002-12-24) -> stable-snapshot

2003-01-22: errno

在EAGAIN与EWOULDBLOCK同值的系统中，EWOULDBLOCK消失不见了。现在，这种系统中的EWOULDBLOCK被定义为EAGAIN。(这点与1.6.7不同)

p Errno::EAGAIN
p Errno::EWOULDBLOCK

=> ruby 1.6.7 (2002-03-01) [i586-linux]
   Errno::EAGAIN
   Errno::EWOULDBLOCK

=> ruby 1.6.8 (2002-12-24) [i586-linux]
   Errno::EAGAIN
   -:2: uninitialized constant EWOULDBLOCK at Errno (NameError)

=> ruby 1.6.8 (2003-02-13) [i586-linux]
   Errno::EAGAIN
   Errno::EAGAIN

1.6.7 (2002-03-01) -> 1.6.8 (2002-12-24)

2002-10-02: Thread (cygwin)

在Cygwin中，有时无法切换Thread。 [ruby-list:36058], [ruby-list:24637]

2002-10-01: Socket (win)

好像解决了一个Windows中的socket问题。(从原来的邮件中无法得到具体的问题描述，但好像是：虽然可以用select来进行读入，但返回的是空数组) [ruby-talk:40015], [ruby-win32:366]

2002-09-12: Thread.status (?)

有时用trap捕获信号时却并没有保存线程的状态，导致被信号中断的线程状态混乱[ruby-talk:40337], [ruby-core:00019]

2002-09-11: Queue#pop

Queue#pop中存在竞争问题[ruby-dev:17223]

2002-09-11: SizedQueue.new

修改了可接受0以下的参数的bug。

2002-09-05: 展开式

在stable snapshot中，曾经有一段时间，需要对展开式中引号使用反斜线转义。该变更解决了这个问题。

p "#{ "" }"

=> ruby 1.6.7 (2002-03-01) [i586-linux]
   ""

=> -:1: warning: bad substitution in string
   ruby 1.6.7 (2002-09-12) [i586-linux]
   "#{  }"

=> ruby 1.6.7 (2002-09-25) [i586-linux]
   ""

这并不是1.7之后的backport。在处理注释等对象时，与1.7有所不同。(请参考ruby 1.7 特性的2002-06-24)

p "#{ "" # comment }"
=> ruby 1.6.8 (2002-10-04) [i586-linux]
   ""
=> -:1: parse error
   ruby 1.7.3 (2002-10-04) [i586-linux]
SizedQueue#deq, #shift
SizedQueue#enq

添加(push, pop的别名)。以前尚未定义它们时，调用enq等就会执行超类Queue的enq。

2002-09-11: Tempfile#size

添加[ruby-dev:17221]

2002-09-09

在mswin32版和mingw32版的ruby中，从1.6.6版起就有一个bug：ruby的子进程无法接收环境变量。[ruby-dev:18236]

2002-09-03

在使用Bison编译的Ruby中，多次加载库的速度有所提高。(若不使用Bison的话，每次加载时，好像都会显式地执行GC，所以运行速度会下降) [ruby-dev:18145]

2002-08-20 File.expand_path

Cygwin 1.3.x [ruby-bugs-ja:PR#299]

p File.expand_path('file', 'c:/')

=> ruby 1.6.7 (2002-03-01) [i586-linux]
   /tmp/c:/file
=> ruby 1.6.7 (2002-08-21) [i586-linux]
   c:/file
2002-08-19 Thread (win)

以前，同时使用Ruby线程和Win32的结构化异常（包括来自Win32 API的callback）的话就会引起故障。现在该bug已被修复。[ruby-win32:273]

2002-08-12 Hash#==

以前，只要两个Hash对象的默认值(default)==的话，它们也就被看作相等。

p Hash.new("foo") == Hash.new("bar")

=> ruby 1.6.7 (2002-03-01) [i586-linux]
   true
=> ruby 1.6.7 (2002-08-21) [i586-linux]
   false
2002-07-11 String#slice!

以前，若指定了范围之外的字符串的话，有时会返回异常。现在统一返回nil。(也就是返回与String#[]或String#slice一样的结果)

p "foo".slice!("bar")   # <- 以前此处返回 nil
p "foo".slice!(5,10)

=> ruby 1.6.7 (2002-03-01) [i586-linux]
   nil
   -:2:in `slice!': index 5 out of string (IndexError)
        from -:2
=> ruby 1.6.7 (2002-08-01) [i586-linux]
   nil
   nil
2002-07-05 String#split

现在可以给第一个参数指定nil了。[ruby-talk:43513] 此时把$;当作分割字符串。以前，只有省略参数时才能使用$;。

$; = ":"
p "a:b:c".split(nil)
=> -:2:in `split': bad separator (ArgumentError)
        from -:2
   ruby 1.6.7 (2002-03-01) [i586-linux]

=> ruby 1.6.7 (2002-07-30) [i586-linux]
   ["a", "b", "c"]
2002-06-15 Dir.glob

以前，Dir.glob不会匹配已断链的符号连接。

File.symlink("foo", "bar")
p Dir.glob("bar")
=> ruby 1.6.7 (2002-03-01) [i586-linux]
   []
=> ruby 1.6.7 (2002-08-01) [i586-linux]
   ["bar"]
2002-06-13 Hash[]

以前，无法用Hash[]来dup & freeze键字符串。

a = "key"
h = Hash[a,"val"]
h.keys[0].upcase!
p a
=> ruby 1.6.7 (2002-03-01) [i586-linux]
   "KEY"
=> -:3:in `upcase!': can't modify frozen string (TypeError)
        from -:3
   ruby 1.6.7 (2002-08-01) [i586-linux]
2002-06-10 Fixnum#>>, <<

有时对负数进行右移位时，就会变成0。 [ruby-bugs-ja:PR#247]

以负数为参数的左移位(即右移位)中也会出现这种问题。[ruby-bugs-ja:PR#248]

p(-1 >> 31)
=> ruby 1.6.7 (2002-03-01) [i586-linux]
   0
=> ruby 1.6.7 (2002-08-01) [i586-linux]
   -1

p(-1 << -1)
=> ruby 1.6.7 (2002-03-01) [i586-linux]
   -2147483649
=> ruby 1.6.7 (2002-08-01) [i586-linux]
   -1
2002-06-05
Math.acosh
Math.asinh
Math.atanh

添加。

2002-06-03
String#[]=

若receiver中不包含指定的索引字符串时，直接返回右边部分。

foo = "foo"
p foo["bar"] = "baz"
p foo

=> ruby 1.6.7 (2002-03-01) [i586-linux]
   "baz"
   "foo"
=> -:2:in `[]=': string not matched (IndexError)
        from -:2
   ruby 1.6.7 (2002-07-30) [i586-linux]
2002-06-03 sprintf()

用"%d"把参数变为整数时，使用与Integer相同的规则。

p sprintf("%d", nil)

=> -:1:in `sprintf': no implicit conversion from nil (TypeError)
        from -:1
   ruby 1.6.7 (2002-03-01) [i586-linux]

=> ruby 1.6.7 (2002-07-30) [i586-linux]
   "0"
2002-05-23 -* 选项(?)

以前，为了兼顾那些使用

#! ruby -*- mode: ruby -*-

这种Emacs'-*-'的脚本，而特别规定：忽略-*以后的部分(将其看作是不进行任何操作的选项)，现在已经取消了这种特别设定。若想设定Emacs的'-*-'时，应该写在第2行。[ruby-dev:17193]

ruby '-*' -v
=> ruby 1.6.7 (2002-03-01) [i586-linux]

=> ruby: invalid option -*  (-h will show valid options)
2002-05-22 parsedate

版本升级[ruby-dev:17171]

2002-05-22 -T 选项

以前，若在ruby的命令行选项-T后不留空格地添加其他选项时，-T后面的选项将被忽略。现在，-T后面的非数字部分都被看作是选项(与-0选项相同) [ruby-dev:17179]

ruby -Tv  # -v が無効 (ruby 1.6.7 (2002-03-01) [i586-linux])

=> ruby: No program input from stdin allowed in tainted mode (SecurityError)

=> ruby 1.6.7 (2002-07-30) [i586-linux]
2002-05-20 IO#close

若对双向管道dup进行close_write时，则会引发错误。 [ruby-dev:17155]

open("|-","r+") {|f|
  if f
    f.dup.close_write
  else
     sleep 1
  end
}

=> ruby 1.6.7 (2002-03-01) [i586-linux]
   -:3:in `close_write': closing non-duplex IO for writing (IOError)
        from -:3
        from -:1:in `open'
        from -:1

=> ruby 1.6.7 (2002-07-30) [i586-linux]
2002-05-02 Regexp.quote

# 已经变为back slash quote。主要是为了能将quote中的正则表达式正确嵌入//x。[ruby-bugs-ja:PR#231]

p Regexp.quote("#")

p /a#{Regexp.quote("#")}b/x =~ "ab"

=> -:3: warning: ambiguous first argument; make sure
   ruby 1.6.7 (2002-03-01) [i586-linux]
   "#"
   0

=> -:3: warning: ambiguous first argument; make sure
   ruby 1.6.7 (2002-07-30) [i586-linux]
   "\\#"
   nil
2002-04-29: rb_find_file()

在$SAFE >= 4的情况下，若没有指定绝对路径的话，则会引发SecurityError异常。

2002-04-26: Regexp.quote

[ruby-bugs-ja:PR#231]

p Regexp.quote("\t")

p /a#{Regexp.quote("\t")}b/x =~ "ab"

=> -:3: warning: ambiguous first argument; make sure
   ruby 1.6.7 (2002-03-01) [i586-linux]
   "\t"
   0

=> -:3: warning: ambiguous first argument; make sure
   ruby 1.6.7 (2002-05-04) [i586-linux]
   "\\t"
   nil
2002-04-20: Regexp#inspect

带/x标识的正则表达式对象的inspect把换行变成了\n。[ruby-bugs-ja:PR#225]

p /a
        b/x

=> -:1: warning: ambiguous first argument; make sure
   ruby 1.6.7 (2002-03-01) [i586-linux]
   /a\n                b/x

=> -:1: warning: ambiguous first argument; make sure
   ruby 1.7.2 (2002-04-24) [i586-linux]
   /a
                   b/x
2002-04-19: 结束时的相关处理

以前在下列脚本中，需要发送2次信号才能结束。现在该问题已解决。[ruby-bugs-ja:PR#223]

trap(:TERM, "EXIT")

END{
  puts "exit"
}

Thread::start{ Thread::stop }
sleep
2002-04-17: Regexp#inspect

[ruby-bugs-ja:PR#222]

p %r{\/}

=> ruby 1.6.7 (2002-03-01) [i586-linux]
   /\\//

=> ruby 1.6.7 (2002-05-04) [i586-linux]
   /\//
2002-04-15: pack('U')

已经修复了无法使用unpack('U')来还原pack('U')的bug。(unpack按照字符单位进行处理，而并非以前的字节单位) [ruby-bugs-ja:PR#220]

p [128].pack("U")
p [128].pack("U").unpack("U")

=> ruby 1.6.7 (2002-03-01) [i586-linux]
   "\302\200"
   [0]

=> ruby 1.6.7 (2002-05-04) [i586-linux]
   "\302\200"
   [128]
2002-04-11: IO#write

检测socket或管道中的EPIPE时，有时会出现失败的情况。[ruby-dev:16849]

2002-04-11: cgi/session.rb (*文档中未出现*)

support for multipart form.

2002-04-10: Object#remove_instance_variable

若指定的实例变量尚未定义时，则引发NameError异常。[ruby-bugs-ja:PR#216]

Object.new.instance_eval {
  p remove_instance_variable :@foo
}
=> ruby 1.6.7 (2002-03-01) [i586-linux]
   nil

=> -:2:in `remove_instance_variable': instance variable @foo not defined (NameError)
   ruby 1.6.7 (2002-04-10) [i586-linux]
2002-04-04: Integer#step

以前，若将第二参数设为小于1的数时，它会被看作为0，进而引发错误。

1.step(2, 0.1) {|f| p f }

=> -:1:in `step': step cannot be 0 (ArgumentError)
        from -:1
   ruby 1.6.7 (2002-03-01) [i586-linux]

=> ruby 1.6.7 (2002-04-10) [i586-linux]
   1
   1.1
    :
   1.9
2002-04-01: $~

修复了无法将nil赋值给$~的bug。[ruby-dev:16697]

/foo/ =~ "foo"
p $~
$~ = nil
p $~
=> ruby 1.6.7 (2002-03-01) [i586-linux]
   #<MatchData:0x401b1be4>
   -:3: wrong argument type nil (expected Match) (TypeError)
                                          ^^^^^ MatchData的错误
=> ruby 1.6.7 (2002-04-04) [i586-linux]
   #<MatchData:0x401b1c98>
   nil
2002-03-25 BasicSocket.do_not_reverse_lookup

在$SAFE > 3的情况下，无法设定值。[ruby-dev:16554]

2002-03-23 IO#read

修复了File#read等无法读取大小为0的有内容文件(在Linux的/proc文件系统中会发生这种情况)的bug。

p File.open("/proc/#$$/cmdline").read

=> ruby 1.6.7 (2002-03-01) [i586-linux]
   ""

=> ruby 1.6.7 (2002-03-29) [i586-linux]
   "ruby-1.6\000-v\000-"
2002-03-22 module_eval

在module_eval的块中，常数和类变量的作用域不再变化。[ruby-dev:17876]

class Foo
  FOO = 1
  @@foo = 1
end

FOO = 2
@@foo = 2

Foo.module_eval { p FOO, @@foo }

=> ruby 1.6.7 (2002-03-01) [i586-linux]
   1
   1

=> ruby 1.6.7 (2002-03-29) [i586-linux]
   2
   2
2002-03-22 net/http.rb

以前，Net::HTTP.new在不带块的时候返回nil。 [ruby-bugs-ja:PR#214]

一般认为会逐渐取消net/protocol.rb，该bug好像是在调试时不小心种下的。

2002-03-20 File.expand_path

有时会出现内存释放不彻底的情况。[ruby-bugs:PR#276]

2002-03-18 字符串字面值

在#{..}等结构中，有时会出现汉字代码处理不全的现象。[ruby-list:34478]

#! ruby -Ks
p a = "#{"表"}"
=> -:1: compile error in string expansion (SyntaxError)
   -:1: unterminated string meets end of file
   ruby 1.6.7 (2002-03-15) [i586-linux]
=> ruby 1.6.7 (2002-03-19) [i586-linux]
   "表"

#! ruby -Ks
p %[评价]
=> -:2: parse error
           p %[评价]
                   ^
   ruby 1.6.7 (2002-03-15) [i586-linux]

=> ruby 1.6.7 (2002-03-19) [i586-linux]
   "评价"
2002-03-16 $~

以前，若正则表达式方法没有在内部进行匹配操作时，将不会清除$~ 的状态。[ruby-bugs-ja:PR#208]

/foo/ =~ "foo"
/foo/ =~ nil
p $~

/foo/ =~ "foo"
$_ = nil; ~"foo"
p $~

/foo/ =~ "foo"
"foo".index(/bar/, 4)
p $~

/foo/ =~ "foo"
"foo".rindex(/bar/, -4)
p $~

=> ruby 1.6.7 (2002-03-06) [i586-linux]
   #<MatchData:0x401b1be4>
   #<MatchData:0x401b198c>
   #<MatchData:0x401b1644>
   #<MatchData:0x401b1414>
=> ruby 1.6.7 (2002-03-19) [i586-linux]
   nil
   nil
   nil
   nil
2002-03-14 扩展库的autoload

无法对扩展库进行autoload。[ruby-dev:16379]

autoload :Fcntl, "fcntl"
require "fcntl"

=> -:2:in `require': uninitialized constant Fcntl (NameError)
        from -:2
   ruby 1.6.7 (2002-03-01) [i586-linux]

=> ruby 1.6.7 (2002-03-15) [i586-linux]
2002-03-13 getopts.rb

refine. [ruby-dev:16193], [ruby-dev:16213]

2002-03-11 正则表达式中的8进制代码

以前，在正则表达式中的\nnn形式的8进制表示法中，只有开头是0时，才允许4位数。[ruby-bugs-ja:PR#207]

p /\0001/ =~ "\0001"   # equivalent to "\0" + "1"
=> -:1: warning: ambiguous first argument; make sure
   ruby 1.6.7 (2002-03-01) [i586-linux]
   nil
=> -:1: warning: ambiguous first argument; make sure
   ruby 1.6.7 (2002-03-15) [i586-linux]
   0
2002-03-11 trap

[ruby-bugs-ja:PR#206]

trap('EXIT','Foo')
=> -:1: [BUG] Segmentation fault
   ruby 1.6.7 (2002-03-01) [i586-linux]
=> ruby 1.6.7 (2002-03-15) [i586-linux]
2002-03-10 方法的返回值

下列方法的返回值已被修正。[ruby-bugs-ja:PR#205]

  • each_with_index返回self(以前是nil)
  • Process.setpgrp返回不定值。
  • ljust, rjust, center返回已dup的字符串
2002-03-08 class variable

[ruby-talk:35122]

class C
  class << self
    def test
      @@cv = 5
      p @@cv
    end
  end

  test
end
=> -:5:in `test': uninitialized class variable @@cv in C (NameError)
        from -:9
   ruby 1.6.7 (2002-03-01) [i586-linux]

=> ruby 1.6.6 (2001-12-26) [i586-linux]
   5
2002-03-03 Marshal.load

以前，Marshal.load一直调用1.7的Proc#yield方法。[ruby-dev:16178]

Marshal.load(Marshal.dump('foo'), proc {|o| p o})
=> -:1:in `load': undefined method `yield' for #<Proc:0x401b1b30> (NameError)
        from -:1
   ruby 1.6.7 (2002-03-01) [i586-linux]

=> ruby 1.6.6 (2001-12-26) [i586-linux]
   "foo"

1.6.6 (2001-12-26) -> 1.6.7 (2002-03-01)

2002-02-20 true/false/nil的特殊方法定义

可以通过定义特殊类的方法来为这些伪变量定义特殊方法。

class <<true
  def foo
   "foo"
  end
end
p true.foo
=> -:1: no virtual class for true (TypeError)
   ruby 1.6.6 (2001-12-26) [i586-linux]

=> ruby 1.6.7 (2002-03-01) [i586-linux]
   "foo"
time.rb, URI

已添加。

Ruby/Tk

修复bug，增加功能[ruby-dev:16139],[ruby-dev:16153]。

数值字面值的`_'

重新制定`_'的使用规则，统一了String#hex等数值变换方法的行为规则。[rubyist:1018], [ruby-dev:15684], [ruby-dev:15757]

include

不会递归地对模块进行include操作。

module Foo; end
module Bar; include Foo; end
module Foo; include Bar; end

p Foo.ancestors

=> ruby 1.6.6 (2001-12-26) [i586-linux]
   [Foo, Bar, Foo]

=> -:3:in `append_features': cyclic include detected (ArgumentError)
        from -:3:in `include'
        from -:3
   ruby 1.6.6 (2002-01-28) [i586-linux]
方法的返回值

修正了下列方法的返回值。 [ruby-bugs-ja:PR#182], [rubyist:1016]

  • Hash#default= 返回右边部分(以前返回self)。
  • Dir#pos= 返回右边部分(以前返回self)。 (Dir#seek仍然返回self)
  • Dir.glob 带块时返回nil(以前返回false)
  • IO#close 遇到已关闭的IO则引发IOError。
  • IO#each_byte 返回self(以前返回nil)
rb_define_module_under()

以前，若C函数 rb_define_module_under()定义模块时遇到了已定义的同名常数的话就会失败。[ruby-talk:30203]

Constants = 1
require 'syslog'
p Syslog::Constants

=> -:2:in `require': Syslog::Fixnum is not a module (TypeError)
        from -:2
   ruby 1.6.6 (2001-12-26) [i586-linux]

=> ruby 1.6.6 (2002-01-07) [i586-linux]
   Syslog::Constants

因为这个bug的问题，可能最近就会放出1.6.7[ruby-talk:30387](好像不是这样。因此使用1.6.6的stable-snapshot的用户需要特别注意。因为2002/1/30的下列变更(ChangeLog)

  • re.c (rb_reg_search): should set regs.allocated.

的原因，将导致内存不足。2002/2/13以后的修正版解决了这个问题)。

1.6.5 (2001-09-19) -> 1.6.6 (2001-09-19)

Syslog

已添加。

CGI

针对Netscape(版本是？)的bug进行了处理 [ruby-list:32089]

Time#localtime
Time#gmtime

可以调用一次已冻结的Time 对象。

t = Time.new.freeze
p t.gmtime
p t.localtime

=> -:2:in `gmtime': can't modify frozen Time (TypeError)
        from -:2
   ruby 1.6.5 (2001-09-19) [i586-linux]

=> ruby 1.6.5 (2001-11-01) [i586-linux]
   Mon Nov 05 18:08:34 UTC 2001
   -:3:in `localtime': can't modify frozen Time (TypeError)
        from -:3
File::SEPARATOR
File::ALT_SEPARATOR
File::PATH_SEPARATOR
RUBY_PLATFORM
RUBY_RELEASE_DATE
RUBY_VERSION

上述各项已变为被freeze的字符串。

p File::SEPARATOR.frozen?
p File::ALT_SEPARATOR.frozen?
p File::PATH_SEPARATOR.frozen?

=> ruby 1.6.5 (2001-09-19) [i586-linux]
   false
   false
   false

=> ruby 1.6.5 (2001-11-01) [i586-linux]
   true
   false  # 因为运行环境是Linux，所以ALT_SEPARATOR是nil
   true
Integer[nth]

以前，若遇到大索引值就会引发异常。 [ruby-bugs-ja:PR#114]

p(-1[10000000000])

=> -:1:in `[]': bignum too big to convert into `int' (RangeError)
        from -:1
   ruby 1.6.5 (2001-09-19) [i586-linux]

=> ruby 1.6.5 (2001-11-01) [i586-linux]
   1

不能对整数的负索引返回0。？[ruby-bugs-ja:PR#122]

p(-1[-1])

=> ruby 1.6.5 (2001-09-19) [i586-linux]
   1
=> ruby 1.6.5 (2001-11-01) [i586-linux]
   1
Numeric#remainder

[ruby-bugs-ja:PR#110]

p( 3.remainder(-3))
p(-3.remainder(3))

=> ruby 1.6.5 (2001-09-19) [i586-linux]
   3
   -3
=> ruby 1.6.5 (2001-11-01) [i586-linux]
   0
   0
END { ... }

以前，不能运行END块中的END块。 [ruby-bugs-ja:PR#107]

END {
  p 1
  END { p 2 }
}

=> ruby 1.6.5 (2001-09-19) [i586-linux]
   1

=> ruby 1.6.5 (2001-11-01) [i586-linux]
   1
   2
String#succ

[ruby-talk:22557]

p "***".succ
p "*".succ
p sprintf("%c", 255).succ
p sprintf("*%c", 255).succ
p sprintf("**%c", 255).succ

=> ruby 1.6.5 (2001-09-19) [i586-linux]
   "**+"
   "\001+"
   "\001\000"
   "\001+\000"
   "*+\000"

=> ruby 1.6.5 (2001-11-01) [i586-linux]
   "**+"
   "+"
   "\001\000"
   "+\000"
   "*+\000"
method_missing

以前，下列内容被Segmentation Fault。[ruby-dev:14942]

Module::constants.each {|c|
  c = eval c
  if c.instance_of?(Class)
    p c
    c.instance_methods.each {|m|
      c.module_eval "undef #{m};"
    }
    c.module_eval {undef initialize}
  end
}

=> ruby 1.6.5 (2001-09-19) [i586-linux]
   NotImplementedError
   MatchData
   Exception
   Numeric
   MatchData
   Segmentation fault

=> ruby 1.6.5 (2001-10-15) [i586-linux]
   MatchData
   NotImplementedError
   FloatDomainError
   LoadError
   Float
   Binding
   SignalException
   Module
   -:6:in `method_missing': stack level too deep (SystemStackError)
%q(...)

在使用%表示法的字面值中，不能将英文字母和数字用作切分字符。

p %q1..1

=> ruby 1.6.5 (2001-10-10) [i586-linux]
   ".."
=> -:1: unknown type of %string
        p %q1..1
             ^
   ruby 1.6.5 (2001-10-15) [i586-linux]
String#=~

调用String#=~时，若两边都是字面值的话，为了提升速度而禁止进行方法调用。(实际上，此前就曾考虑过这么做，但因为出现了bug而未能禁止方法调用(不是String#=~，而是Regexp#=~))

class String
  def =~(arg)
    ["String#=~", self, arg]
  end
end

class Regexp
  def =~(arg)
    ["Regexp#=~", self, arg]
  end
end

p "foo" =~ /foo/
p "foo" =~ Regexp.new("foo")

=> -:2: warning: discarding old =~
   -:8: warning: discarding old =~
   ruby 1.6.5 (2001-09-19) [i586-linux]
   ["Regexp#=~", /foo/, "foo"]
   ["String#=~", "foo", /foo/]

=> -:2: warning: discarding old =~
   -:8: warning: discarding old =~
   ruby 1.6.5 (2001-10-10) [i586-linux]
   0
   ["String#=~", "foo", /foo/]

(有时会对内部方法进行这种最优化设置，因此可能导致重定义方法无效的情况，而确切地说，您应该先判断某方法是否已被重定义，然后再选择是否进行最优化)

class 定义

以前，若显式地使用一个不同的超类来重定义某个已定义的类时，则会忽略指定的超类。[ruby-bugs-ja:PR#87]

class A
  p self.id
end
class A < String
  p self.id
  p self.superclass
end

=> ruby 1.6.5 (2001-09-19) [i586-linux]
   537760880
   -:4: warning: already initialized constant A
   537757180
   Object
=> ruby 1.6.5 (2001-10-10) [i586-linux]
   537760960
   -:4: warning: already initialized constant A
   537757200
   String
%w(...)

以前，语法解释器会将数组字面值 %w(...)误判为字符串字面值，从而导致下列异常情况。[ruby-bugs-ja:PR#91]

%w!a! "b"
=> -:1: tried to allocate too big memory (NoMemoryError)
   ruby 1.6.5 (2001-09-19) [i586-linux]

=> -:1: parse error
       %w!a! "b"
                ^
   ruby 1.6.5 (2001-10-10) [i586-linux]
Thread

以前存在下列bug：Thread#status对aborting状态返回"run"，而Thread#priority = val会返回self而不是val。现已解决这些问题。[rubyist:0820], [ruby-dev:14903]

Marshal

不能dump无名的类／模块。

p Marshal.dump(Class.new)

=> ruby 1.6.4 (2001-06-04) [i586-linux]
   "\004\005c\031#<Class 0lx401a6b44>"

=> -:1:in `dump': can't dump anonymous class #<Class 0lx401ab980> (ArgumentError)
        from -:1
   ruby 1.6.5 (2001-10-05) [i586-linux]
UNIXSocket#addr

以前，UNIXSocket#addr会返回一些垃圾信息(在BSD中如何呢？)。 [ruby-bugs-ja:PR#85]

# server
require 'socket'
File.unlink("/tmp/sss")
sock = UNIXServer.new("/tmp/sss").accept

# client
require 'socket'
sock = UNIXSocket.new("/tmp/sss").addr

=> ["AF_UNIX", "\031((\306\031(\010"]

=> ["AF_UNIX", ""]
???

[ruby-talk:21722]

class Ptr

def initialize(obj) @obj = obj end
def []=() @obj = obj end
def []() @obj end

end module Kernel

def _ptr() Ptr.new(self) end

end

def foo(int)

int[] += 1

end x = 1._ptr foo(x) puts x[]

=> -:11: [BUG] Segmentation fault

ruby 1.6.5 (2001-09-19) [i586-linux]

=> -:11:in `[]=': wrong # of arguments(1 for 0) (ArgumentError)

from -:11:in `foo'
from -:14

ruby 1.6.5 (2001-10-05) [i586-linux]
Subclass of String and Array

以前，在String, Array的子类中调用某些特定方法时，就会变成String, Array。

class Foo < String
end
p Foo.new("").type
p Foo.new("foo")[0,0].type              # String ???
p Foo.new("foo")[1,1].type
p Foo.new("foo").succ.type
p Foo.new("foo").reverse.type
p((Foo.new("foo") * 5).type)
p Foo.new("foo").gsub(/foo/, "bar").type
p Foo.new("foo").sub(/foo/, "bar").type
p Foo.new("foo").ljust(10).type
p Foo.new("foo").rjust(10).type
p Foo.new("foo").center(10).type

=> ruby 1.6.5 (2001-09-19) [i586-linux]
   Foo
   String
   String
   String
   String
   String
   String
   Foo
   String
   String
   String

=> ruby 1.6.5 (2001-10-05) [i586-linux]
   Foo
   String
   Foo
   Foo
   Foo
   Foo
   Foo
   Foo
   Foo
   Foo
   Foo

class Bar < Array
end
bar = Bar.new
p bar.type
p bar.push(1,2,3)
p bar.type
p bar[0,0].type            # => Array ???
p bar[0,1].type
p ((bar * 5).type)

=> -:9: warning: p (...) interpreted as method call
   ruby 1.6.5 (2001-09-19) [i586-linux]
   Bar
   [1, 2, 3]
   Bar
   Array
   Array
   Array
=> -:9: warning: p (...) interpreted as method call
   ruby 1.6.5 (2001-10-05) [i586-linux]
   Bar
   [1, 2, 3]
   Bar
   Array
   Bar
   Bar

1.6.4 (2001-06-04) -> 1.6.5 (2001-09-19)

$_, $~, if a..b

以前，在函数中使用Thread#run时，与该线程共享scope的父线程中的$_, $~会被子线程所覆盖。[ruby-dev:14743]

def foo(t)
  t.run
end

t = Thread.start do
  t = $_= "sub"
  loop{Thread.stop;puts "sub:#$_"}
end

$_ = "main"
t.run                   # => sub:sub
puts "main:#$_"         # => main:main
foo(t)                  # => sub:sub
puts "main:#$_"         # => main:sub
=> ruby 1.6.4 (2001-06-04) [i586-linux]
   sub:sub
   main:main
   sub:sub
   main:sub
=> ruby 1.6.5 (2001-09-19) [i586-linux]
   sub:sub
   main:main
   sub:sub
   main:main
net/telnet.rb

有时，当Net::Telnet连接到特定的端口之后，就失去反应。 [ruby-list:31303]

CGI#header

以前，在下述脚本中，TEXT_PLAIN会被改写成"text/plain; charset=iso-8859-1"。 [ruby-dev:14716]

require 'cgi'

TEXT_PLAIN = "text/plain"

cgi = CGI.new
print cgi.header("type" => TEXT_PLAIN,
                 "charset" => "iso-8859-1")
printf("TEXT_PLAIN: %s\n", TEXT_PLAIN)

=> ruby 1.6.4 (2001-06-04) [i586-linux]
   Content-Type: text/plain; charset=iso-8859-1
   ^M
   TEXT_PLAIN: text/plain; charset=iso-8859-1
   TEXT_PLAIN: text/plain

=> ruby 1.6.5 (2001-09-19) [i586-linux]
   Content-Type: text/plain; charset=iso-8859-1
   ^M
   TEXT_PLAIN: text/plain
Dir.chdir

在尚未定义HOME或LOGDIR时，若不带参数地调用Dir.chdir的话，就会引发ArgumentError异常

ENV['HOME'] = nil ENV['LOGDIR'] = nil Dir.chdir => -:3:in `chdir': Bad address (Errno::EFAULT)

from -:3

ruby 1.6.4 (2001-08-26) [i586-linux]

=> -:3:in `chdir': HOME/LOGDIR not set (ArgumentError)

from -:3

ruby 1.6.5 (2001-09-19) [i586-linux]
Dir.glob

以前，下列代码会引起死循环。

Dir.mkdir("test?") rescue nil
p Dir.glob("test?/*")
=> ruby 1.6.5 (2001-09-19) [i586-linux]
   []
jcode.rb

修改了若干bug。[ruby-list:31238]

Dir.glob

Dir.glob("*/**/*")会重复返回子目录中的文件。 [ruby-dev:14576]

Dir.mkdir('foo') rescue nil
Dir.mkdir('foo/bar') rescue nil
p Dir.glob('*/**/*')

=> ruby 1.6.4 (2001-06-04) [i586-linux]
   ["foo/bar", "foo/bar"]

=> ruby 1.6.4 (2001-08-26) [i586-linux]
   ["foo/bar"]
UnboundMethod#bind

无法bind模块的UnboundMethod对象。 [rubyist:0728]

module Foo
  def foo
    :foo
  end
end

class Bar
  include Foo
end

m = Foo.instance_method :foo
p m.bind(Bar.new).call

=> ruby 1.6.4 (2001-06-04) [i586-linux]
   -:12:in `bind': first argument must be an instance of Foo (TypeError)
        from -:12

=> ruby 1.6.4 (2001-08-23) [i586-linux]
   :foo
内部类的替换

对内部类／模块进行赋值时，会出现警告。

Array = nil
p Array
=> ruby 1.6.4 (2001-06-04) [i586-linux]
   nil

=> -:1: warning: already initialized constant Array
   ruby 1.6.4 (2001-08-23) [i586-linux]
   nil
Regexp

以前，若反向参考中的数字大于括号数量时，则会匹配任何内容。[ruby-list:30975]

p /(foo)\2/ =~ "foobar"
=> ruby 1.6.4 (2001-06-04) [i586-linux]
   0
=> ruby 1.6.4 (2001-08-23) [i586-linux]
   nil
TCPSocket.open

修复了Cygwin中TCPSocket.open 时常出错(Errno::EINVAL, EALREADY)的问题。(1.6.4 20010712以后) [ruby-talk:9939], [ruby-talk:16632], [ruby-list:24702], [ruby-list:27805], [ruby-list:30512] 等等

lib/resolv.rb, lib/resolv-replace.rb

添加。主要是为了在ruby的resolver(DNS解析)和Socket相关的类中使用该库而设立。

在ruby的resovler中，timeout.rb的控制是有效的(即，在域名解析过程中可以进行Thread的切换)

require 'resolv'
p Resolv.new.getaddress("www.ruby-lang.org").to_s

=> /usr/local/lib/ruby/1.6/resolv.rb:160: warning: timeout (...) interpreted as method call
   /usr/local/lib/ruby/1.6/resolv.rb:55: warning: instance variable @initialized not initialized
   /usr/local/lib/ruby/1.6/resolv.rb:113: warning: instance variable @initialized not initialized
   /usr/local/lib/ruby/1.6/resolv.rb:392: warning: instance variable @initialized not initialized
   ruby 1.6.4 (2001-08-23) [i586-linux]
   "210.251.121.214"
Digest 模块

SHA1, MD5被替换成Digest::SHA1, Digest::MD5。 另外，还新增了Digest::SHA256, Digest::SHA384, Digest::SHA512, Digest::RMD160。

require 'digest/md5'
include Digest

md = MD5.new
md << "abc"
puts md

puts MD5.hexdigest("123")
Struct

以前，可以修改已被冻结的结构体对象。另外，当$SAFE = 4时，则禁止进行修改操作。[ruby-talk:19167]

cat = Struct.new("Cat", :name, :age, :life)
a = cat.new("cat", 12, 7).freeze
a.name = "dog"
p a
=> ruby 1.6.4 (2001-06-04) [i586-linux]
   #<Struct::Cat name="dog", age=12, life=7>
=> ruby 1.6.4 (2001-08-06) [i586-linux]
   -:4:in `name=': can't modify frozen Struct (TypeError)
        from -:4

cat = Struct.new("Cat", :name, :age, :life)
a = cat.new("cat", 12, 7)
Thread.new do
   abort_on_exception = true
   $SAFE = 4
   a.life -= 1
end.join
p a.life
=> ruby 1.6.4 (2001-06-04) [i586-linux]
   6
=> ruby 1.6.4 (2001-08-06) [i586-linux]
   -:6:in `life=': Insecure: can't modify Struct (SecurityError)
        from -:3:in `join'
        from -:3
String#rindex

将正则表达式传给rindex时，则会出现bug。[ruby-dev:13843] (该bug出现在1.6.4 版本之后)

p "foobar".rindex(/b/)
=> ruby 1.6.4 (2001-06-04) [i586-linux]
   3

=> ruby 1.6.4 (2001-06-19) [i386-freebsd5.0]
   nil

=> ruby 1.6.4 (2001-08-06) [i586-linux]
   3
require

以前，若把以~开头的文件名传给require时必须带上扩展名，否则无法进行加载。[ruby-dev:13756]

$ echo p __FILE__ > ~/a.rb
$ ruby17 -v -r~/a -e0
ruby 1.7.1 (2001-07-03) [i686-linux]
0: No such file to load -- ~/a (LoadError)
$ ruby16 -v -r~/a -e0
ruby 1.6.4 (2001-07-02) [i686-linux]
0: No such file to load -- ~/a (LoadError)
$ ruby14 -v -r~/a -e0
ruby 1.4.6 (2000-08-16) [i686-linux]
"/home/nobu/a.rb"
String#each_line

以前，无法正确传递污染。[ruby-dev:13755]

"foo\nbar\n".taint.each_line {|v| p v.tainted?}
=> ruby 1.6.4 (2001-06-04) [i586-linux]
   false
   true
=> ruby 1.6.4 (2001-08-06) [i586-linux]
   true
   true
NKF::nkf

以前，无法正确传递污染。[ruby-dev:13754]

require 'nkf'
p NKF.nkf("-j", "a".taint).tainted?

=> ruby 1.6.4 (2001-06-04) [i586-linux]
   false
=> ruby 1.6.4 (2001-08-06) [i586-linux]
   true
ruby -x

指定了-x[directory]选项的话，有时会出现尚未执行脚本就已经结束的情况。[ruby-dev:13752]

attr_*

向accessor传递多余的参数也不会引起错误。 [ruby-dev:13748]

class C
  def initialize
    @message = 'ok'
  end
  attr_reader :message
end
puts C.new.message(1,2,3)

=> ruby 1.6.4 (2001-06-04) [i586-linux]
   ok
=> ruby 1.6.4 (2001-08-06) [i586-linux]
   -:7:in `message': wrong # of arguments(3 for 0) (ArgumentError)
        from -:7
Readline::completion_append_character
Readline::completion_append_character=

添加。GNU Readline库中的rl_completion_append_character变量的accessor。(在GNU readline 2.1之后的版本中才能使用该变量) [ruby-ext:01760]

Socket::Constants

添加了下列socket相关的常数。

SO_PASSCRED
SO_PEERCRED
SO_RCVLOWAT
SO_SNDLOWAT
SO_RCVTIMEO
SO_SNDTIMEO
SO_SECURITY_AUTHENTICATION
SO_SECURITY_ENCRYPTION_TRANSPORT
SO_SECURITY_ENCRYPTION_NETWORK
SO_BINDTODEVICE
SO_ATTACH_FILTER
SO_DETACH_FILTER
SO_PEERNAME
SO_TIMESTAMP
require / $LOAD_PATH

Changed to use a new algorithm to locate a library.

Now when requiring "foo", the following directories are searched for the library in the order listed.

$prefix/lib/ruby/site_ruby/$ver/foo.rb
$prefix/lib/ruby/site_ruby/$ver/foo.so
$prefix/lib/ruby/site_ruby/$ver/$arch/foo.rb
$prefix/lib/ruby/site_ruby/$ver/$arch/foo.so
$prefix/lib/ruby/site_ruby/foo.rb
$prefix/lib/ruby/site_ruby/foo.so
$prefix/lib/ruby/$ver/foo.rb
$prefix/lib/ruby/$ver/foo.so
$prefix/lib/ruby/$ver/$arch/foo.rb
$prefix/lib/ruby/$ver/$arch/foo.so
./foo.rb
./foo.so

The previous behavior had a potential security risk because a foo.rb (if exists) in the current directory is located prior to a foo.so in $prefix/lib/ruby/site_ruby/$ver/$arch.

[ruby-bugs:PR#140], [ruby-ext:01778], [ruby-dev:13659]

lib/sync.rb
lib/mutex_m.rb

Fixed for obj.extend(Sync_m) and obj.extend(Mutex_m).[ruby-dev:13463]

$ ruby -v -rsocket -rmutex_m -e 's=TCPSocket.new("localhost",25); s.extend(Mutex_m)'
ruby 1.6.4 (2001-06-04) [i386-linux]
/usr/lib/ruby/1.6/mutex_m.rb:104:in `initialize': wrong # of arguments (0 for 1) (ArgumentError)
        from /usr/lib/ruby/1.6/mutex_m.rb:104:in `initialize'
        from /usr/lib/ruby/1.6/mutex_m.rb:50:in `mu_extended'
        from /usr/lib/ruby/1.6/mutex_m.rb:34:in `extend_object'
        from -e:1:in `extend'
        from -e:1
$SAFE / load

以前存在以下bug：在1 <= $SAFE <= 3的情况下，当第二参数为true时，即使指定了被污染的对象名，也能进行load()。现在已经修复该bug。[ruby-dev:13481]

$SAFE = 1
filename = "foo"
filename.taint
p load(filename, true)

=> ruby 1.6.4 (2001-06-04) [i586-linux]
   true

=> ruby 1.6.4 (2001-08-06) [i586-linux]
   -:4:in `load': Insecure operation - load (SecurityError)
        from -:4
Regexp

以前，只有后者能匹配成功。[ruby-talk:16233]

puts "OK 1" if /(.|a)bd/ =~ "cxbd"
puts "OK 2" if /(a|.)bd/ =~ "cxbd"

=> ruby 1.6.4 (2001-06-04) [i586-linux]
   OK 2
=> ruby 1.6.4 (2001-08-06) [i586-linux]
   OK 1
   OK 2
Marshal

以前，模块加载时的类型检测中存在错误。修复该错误后，dump format的minor版本号升高1级

p Marshal.dump(Object.new).unpack("CC").join(".")
    => ruby 1.6.4 (2001-06-04) [i586-linux]
       "4.5"
p Marshal.dump(Object.new).unpack("CC").join(".")
    => ruby 1.6.4 (2001-06-11) [i586-linux]
       "4.6"
$SAFE / def

虽然doc/NEWS中有下列内容

Fixed so defining a new method is allowed under $SAFE == 4, which
previously wasn't.

但实际上是行不通的。

$SAFE = 4; def a; end

=> -:1: Insecure operation `(null)' at level 4 (SecurityError)
   ruby 1.6.4 (2001-06-04) [i586-linux]

=> -:1: Insecure: can't define method (SecurityError)
   ruby 1.6.4 (2001-08-06) [i586-linux]

对应的ChangeLog内容如下。

Tue Jun  5 15:16:06 2001  Yukihiro Matsumoto  <matz@ruby-lang.org>

        * eval.c (rb_add_method): should not call rb_secure(), for
          last_func may not be set.

差别如下所示。

@@ -227,10 +227,7 @@ rb_add_method(klass, mid, node, noex)
     NODE *body;

     if (NIL_P(klass)) klass = rb_cObject;
-    if (klass == rb_cObject) {
-       rb_secure(4);
-    }
-    if (rb_safe_level() >= 4 && !OBJ_TAINTED(klass)) {
+    if (rb_safe_level() >= 4 && (klass == rb_cObject || !OBJ_TAINTED(klass))) {
        rb_raise(rb_eSecurityError, "Insecure: can't define method");
     }
     if (OBJ_FROZEN(klass)) rb_error_frozen("class/module");

留作日后调查。

IO#ioctl

第2参数也可以接受Bignum了(为了覆盖long int的范围)

1.6.3 (2001-03-19) -> 1.6.4 (2001-06-04)

Hash#replace

以前，在哈希表迭代过程中，若删除某元素后，向其他哈希表中进行replace时，就会Abort。[ruby-dev:13432]

h  = { 10 => 100, 20 => 200 }
h2 = { }

h.each { |k, v|
  if (k == 10)
    h.delete(10)
    h2.replace(h)  # => Abort core dumped
  end
}
$SAFE / File::unlink

在$SAFE >= 2的环境中，即使参数未被污染也不能使用File.unlink。[ruby-dev:13426]

touch foo
ruby -v -e '$SAFE=2;File.unlink("foo")'

=> ruby 1.6.3 (2001-03-19) [i586-linux]
=> ruby 1.6.4 (2001-06-04) [i586-linux]
   -e:1:in `unlink': Insecure operation `unlink' at level 2 (SecurityError)
           from -e:1
Object#untaint

现在不能对已冻结的对象使用untaint方法。[ruby-dev:13409]

a = Object.new
a.taint
a.freeze
a.untaint

=> ruby 1.6.3 (2001-03-19) [i586-linux]
=> ruby 1.6.4 (2001-06-04) [i586-linux]
   -:4:in `untaint': can't modify frozen object (TypeError)
           from -:4
ruby -T4

指定-T4选项时，因为无法修改ARGV导致程序无法运行。[ruby-dev:13401]

touch foo
ruby-1.6.3 -v -T4 foo
=> ruby 1.6.3 (2001-03-19) [i586-linux]
   foo: Insecure: can't modify array (SecurityError)
Regexp

现在，正则表达式中的 \1 .. \9 总是会被解释为反向参考(以前则不同，若有对应的括号则看作反向参考，若没有的话则看作8进制字符代码)。

在正则表达式中指定8进制字符代码时，请使用形如\001这种3位表示法。

另外，若反向参考中没有对应的括号，或者对应的括号中包含反向参考本身的话，则会引起匹配失败。

p /(foo)\2/ =~ "foo\002"
=> ruby 1.6.3 (2001-03-19) [i586-linux]
   0
=> ruby 1.6.4 (2001-06-04) [i586-linux]
   0
=> ruby 1.6.4 (2001-08-23) [i586-linux]
   nil

(如上所示，1.6.4中仍然有bug。大概是在2001-08-23的时候得到了修正[ruby-list:30975])

p /(foo\1)/ =~ "foo"
=> ruby 1.6.3 (2001-03-19) [i586-linux]
   0
=> ruby 1.6.4 (2001-06-04) [i586-linux]
   nil
污染字符串的传播

下列各项都会返回true。[ruby-dev:13340]

# []=
s1 = "abc"
s2 = "cde".taint
s1[0]= s2
p s1.tainted?             # => false

# crypt
s = "abc".taint
p s.crypt("cd").tainted?  # => false

# ljust
s = "abc".taint
p s.ljust(10).tainted?    # => false

# rjust
s = "abc".taint
p s.rjust(10).tainted?    # => false

# center
s = "abc".taint
p s.center(10).tainted?   # => false
rb_yield_0()

以前在C API中使用yield时，1个参数会被当作只包含1个元素的数组来处理。[ruby-dev:13299]

class X
  include Enumerable

  def each(&block)
    block.call(1,2)
    block.call(2,3)
    block.call(3,4)
  end
end

x = X.new
p x.to_a #=> [[1], [2], [3]]

# => ruby 1.6.3 (2001-03-19) [i586-linux]
     [[1], [2], [3]]

# => ruby 1.6.4 (2001-06-04) [i586-linux]
     [1, 2, 3]
$SAFE / alias

当$SAFE = 4时，不允许全局变量使用别名。 [ruby-dev:13287]

Open3::popen3

结束的进程不会调用at_exit。 (将exit修正为exit!) [ruby-dev:13170]

SizedQueue#pop

下列代码不会再导致死锁了。[ruby-dev:13169]

ruby -r thread -e 'q = SizedQueue.new(1); q.push(1);'\
               -e 'Thread.new{sleep 1; q.pop}; q.push(1);'
SizedQueue#max=

以前当max大于当前值时，则会唤起相应的等待线程。但在该处理过程中存在错误。[ruby-dev:13170]

Queue
SizedQueue

在调用Thread#run时，若线程忽然死亡则会引发ThreadError。现在已经针对这个问题进行了相应的处理。[ruby-dev:13194]

Ctrl-C (Interrupt)失效

[ruby-dev:13195]

th1 = Thread.start {
  begin
    Thread.stop
  ensure
    Thread.pass
    Thread.stop
  end
}
sleep 1

(据我所知，在ruby-1.7.0 (2001-05-17) 以后的版本中解决了这个问题，但1.6好像还不行)

Array#&
Array#|
Array#uniq

以前，结果数组中的元素均被冻结，不能修改。[ruby-list:29665]

(%w(foo bar) & %w(foo baz))[0].upcase!
=> -:1:in `upcase!': can't modify frozen string (TypeError)

%w(foo bar bar baz).uniq[0].upcase!
=> -:1:in `upcase!': can't modify frozen string (TypeError)
shell.rb

shell.rb 0.6被添加到标准库中。(文档在doc目录中)

forwardable.rb

forwardable.rb 1.1被添加到标准库中。(文档在doc目录中)

irb.rb & irb-tools

irb和irb-tools分别升级到0.7.4和0.7.1。

夏令时

对夏令时的考虑不周(？) [ruby-bugs-ja:PR#46]

env TZ=America/Managua ruby -e 'p Time.local(1998,12,1,0,59,59)'
=> Mon Nov 30 01:59:59 EST 1998
env TZ=America/Managua ruby -e 'p Time.local(1998,12,1,0,59,59).tv_sec'
=> 912409199
SIGINFO

对4.4BSD的SIGINFO信号采取了相应的处理措施。[ruby-bugs-ja:PR#45]

Thread.stop中的SEGV

有时会在Thread.stop中发生SEGV。[ruby-dev:13189]

rescue 修饰部分

下列代码在1.6.3中会引起parse error，现在已经修复了该bug。[ruby-dev:13073], [ruby-dev:13292]

raise "" rescue []
raise "" rescue (p "foo"; true)
raise "" rescue -1
raise "" rescue (-1)
Thread

下列代码不会再引起dead lock了。

Thread.start { Thread.stop }
sleep

=> deadlock 0x40199b58: 2:0  - -:1
   deadlock 0x401a2528: 2:4 (main) - -:2
   -:2:in `sleep': Thread: deadlock (fatal)
           from -:2
   ruby 1.6.3 (2001-03-19) [i586-linux]
Module#const_defined?
Module#const_get
Module#const_set

以前，这些方法甚至可以访问常数以外的对象。现在已经修正了该bug [ruby-dev:13019]

Marshal.dump

dump Float时精度由"%.12g"提升到了"%.16g"。 [ruby-list:29349]

Fixnum#[]

好像是修正了在sizeof(long) > sizeof(int)这种系统上的bug。

正则表达式

修正了2个较少见的bug [ruby-talk:13658], [ruby-talk:13744]

retry

在1.6.3中，下列代码不能正常运行[ruby-talk:13957]

def WHILE(cond)
  return if not cond
  yield
  retry
end

i=0
WHILE(i<3) {
  print i
  i+=1
}

ruby 1.6.2 (2000-12-25) [i586-linux]
=> 012

ruby 1.6.3 (2001-03-19) [i586-linux]
=> 0

ruby 1.6.4 (2001-05-02) [i586-linux]
=> 012
File::Stat#size

以前无法正确返回超过1G byte的文件的大小。

File.open("/tmp/1GB", "w") {|f|
  f.seek(2**30-1, 0)
  f.puts
  f.flush
  p f.stat.size
}

# => ruby 1.6.3 (2001-04-03) [i586-linux]
     -1073741824
# => ruby 1.6.4 (2001-04-19) [i586-linux]
     1073741824
Float#modulo, Float#divmod

好像修正了一些问题 [ruby-dev:12718]

ObjectSpace#_id2ref

以前会莫名其妙地返回异常。

malloc的递归调用问题

当stdio在内部调用malloc()时，会破坏其与Thread的兼容性，因此采取了一些补救措施。(通过使用setvbuf()，来避免调用malloc()) [ruby-dev:12795]

File#flock

在File#flock锁定之后，有时会抛出Errno::EACCES异常，而并不会返回false(这种情况出现在没有flock()的系统中)

File::Stat.new(filename)

添加 [ruby-dev:12803]

Bignum#%的计算错误

(再次)修正了导致% 计算错误的bug

a = 677330545177305025495135714080
b = 14269972710765292560
p a % b  #=> 0
p -a % b #=>

=> ruby 1.6.3 (2001-04-02) [i386-cygwin]
   0
   14269972710765292560

=> ruby 1.6.4 (2001-04-19) [i586-linux]
   0
   0
Marshal

将Bignum进行dump -> load操作之后得到的结果往往与原值不同。

在1.6.3版本之后，共进行了3次相关部分的修改。 请使用stable-snapshot的

ruby 1.6.3 (2001-03-22)

之后的版本。

Universal Naming Convention(UNC) 的支持(win32)

UNC 形式的路径名 (//host/share)得到了支持。使用斜线(`/')，而并非反斜线(`\')。 (其实以前就支持的，难道是修正bug了？？)

Dir.glob (win32)

以前，无法对当前目录(./)进行glob。

p Dir["./*.c"]
=> []

1.6.2 -> 1.6.3 (2001-03-19)

do .. end 和 { .. }

以前的版本认定二者的结合度相同，现在修正了这个bug。

在1.6.0到1.6.2之间的版本中，

method v { .. }
method v do .. end

两者之间没有区别。正确的描述详见迭代器部分。

Bignum#% 的计算错误

修正了导致 % 计算错误的bug

ruby-1.6.2 -ve 'p 6800000000%4000000000'
=> ruby 1.6.2 (2000-12-25) [i586-linux]
   -1494967296

ruby-1.6.3 -ve 'p 6800000000%4000000000'
=> ruby 1.6.3 (2001-03-10) [i586-linux]
   2800000000
特殊方法定义

与通常的方法定义一样，也可以指定rescue, ensure部分

obj = Object.new
def obj.foo
rescue
ensure
end
String#count
String#delete
String#squeeze
String#tr_s

可以用'\-'来表示'-'(tr! 等、bang method也是一样的)。以前，只有字符串开头或末尾的'-'才会被看作是'-'。

p "-".tr("a-z",  "+")  # => "-"
p "-".tr("-az",  "+")  # => "+"
p "-".tr("az-",  "+")  # => "+"
p "-".tr('a\-z', "+")  # => "+" # 请注意单引号字符串
p "-".tr("a\\-z", "+") # => "+" # 在""中需要使用二个\
Regexp#==

所有选项都相同的话，则被判定为相同。以前，只要汉字代码选项 和 /i (case-insensitive)相同，就被看作是相同。

%q(), %w()

可以使用反斜线对字面值的末尾字符(`)'等)进行转义。

Dir.glob

"**/"无法找到符号连接了。

String#[]

现在"a"[1,2]会返回""。

p "a"[1,2]
=> ""

这是正确的结果。以前的老版本(1.4.6等)也都是对的。但1.6.0到1.6.2之间的版本则返回nil。

p "a"[2,1]返回nil。

Object#taint

无法对freeze的对象进行taint操作

obj = Object.new.freeze
obj.taint
=> -:2:in `taint': can't modify frozen object (TypeError)
           from -:2
Originally Written by webmaster@ruby-lang.org
Translated by Community Engine China (Mail)
Last update on 2005-01-24


Ruby参考手册 - ruby 1.7 特性

ruby 1.7 特性

ruby version 1.7是开发版。将来可能会删除下列中的部分内容，也可能因为兼容性问题而对其进行修改。

1.7.3 -> 1.8.0 preview1 (2002-12-24)

2002-12-20

profiler.rb [lib] [new]

新增。作为profile.rb的实体将其分离出来。

rb_define_alloc_func() [api] [new]
rb_undef_alloc_func() [api] [new]

新增。用在allocate方法的定义中。 [ruby-dev:19116]

2002-12-18

Regexp#=== [compat]

返回布尔值。

p(/foo/ === "foo")

=> ruby 1.6.8 (2002-12-24) [i586-linux]
   0
=> ruby 1.8.0 (2003-03-12) [i586-linux]
   true

2002-12-17

defined? [compat]

遇到对属性赋值或对数组元素进行赋值的情况时，返回"assignment"而非"method"。

class Foo
  attr_accessor :foo
end
p defined? Foo.new.foo = 1

ary = []
p defined? ary[2] = 1

=> ruby 1.6.8 (2002-12-24) [i586-linux]
   "method"
   "method"
=> ruby 1.8.0 (2003-03-12) [i586-linux]
   "assignment"
   "assignment"

2002-12-17

open-uri.rb [lib] [new]

新增

2002-12-14

WindowsCE [platform]

加入了WindowsCE的支持补丁。

2002-12-11

IO#read [compat]
IO#sysread [compat]

向IO#read, IO#sysread新增了第二参数(指定了预先分配好的读入缓冲)

2002-12-10

Thread#terminate [new]

新增。与Thread#kill 相同。

2002-12-07

Process.abort [new]
Process.exit [new]

新增。与abort, exit函数相同。

2002-12-06

Process::Status#pid [new]

新增

2002-12-04

Object#copy_object

改名了，原名为become。(此后，在1.8中又改名为initialize_copy)

2002-11-27

SystemExit#initialize [compat]

增加了参数。

ruby -e 'raise SystemExit.new(2)'
echo $?
# => 2

2002-11-19

Array#transpose [new]

新增

p [[1,2,3],
   [4,5,6],
   [7,8,9]].transpose
=> ruby 1.7.3 (2002-12-11) [i586-linux]
   [[1, 4, 7], [2, 5, 8], [3, 6, 9]]

2002-11-08

[parser] [experimental]

试验性的修改。

a = 1
p a / 5
  => ruby 1.6.8 (2002-12-24) [i586-linux]
     0
  => ruby 1.8.0 (2003-03-12) [i586-linux]
     0

a = 1
p a /5
  => -:2: warning: ambiguous first argument; make sure
     -:2: unterminated regexp meets end of file
     ruby 1.6.8 (2002-12-24) [i586-linux]
  => ruby 1.8.0 (2003-03-12) [i586-linux]
     0

2002-11-02

Object#object_id [new]

新增 (Object#id是obsolete)

p Object.new.id

=> ruby 1.6.7 (2002-03-01) [i586-linux]
   537730140
=> -:1: warning: Object#id will be deprecated; use Object#object_id
   ruby 1.7.3 (2002-12-04) [i586-linux]
   537723790

2002-11-02

Fixnum#to_sym [new]
String#to_sym [new]

新增(取消了Symbol#intern)

2002-11-01

Array#zip [new]
Enumerable#zip [new]

新增

p [1,2,3].zip([4,5,6], [7,8,9])

=> ruby 1.7.3 (2002-12-11) [i586-linux]
   [[1, 4, 7], [2, 5, 8], [3, 6, 9]]

p [1,2,3].zip([4,5,6], [7,8,9]) {|v| p v}

=> ruby 1.7.3 (2002-12-11) [i586-linux]
   [1, 4, 7]
   [2, 5, 8]
   [3, 6, 9]
   nil

2002-10-30

Module#private_method_defined? [new]
Module#protected_method_defined? [new]
Module#public_method_defined? [new]
Object#methods [change]
Module#instance_methods [change]

[ruby-dev:18606]

增加了Module#private_method_defined?，Module#protected_method_defined?，Module#public_method_defined?

修改了Object#methods, Module#instance_methods(为了与 Module#method_defined?和Module#instance_methods的关系 取得一致)

class Foo
  def public_m; end
private
  def private_m; end
protected
  def protected_m; end
end

foo = Foo.new

m = %w(public_m private_m protected_m)

p m.collect {|_| Foo.method_defined?(_)}
if Foo.respond_to? :public_method_defined?
  p m.collect {|_| Foo.public_method_defined?(_)}
  p m.collect {|_| Foo.private_method_defined?(_)}
  p m.collect {|_| Foo.protected_method_defined?(_)}
end
puts '---'
p m.collect {|_| Foo.instance_methods.member?(_)}
p m.collect {|_| Foo.public_instance_methods.member?(_)}
p m.collect {|_| Foo.private_instance_methods.member?(_)}
p m.collect {|_| Foo.protected_instance_methods.member?(_)}
puts '---'
p m.collect {|_| foo.methods.member?(_)}
p m.collect {|_| foo.public_methods.member?(_)}
p m.collect {|_| foo.private_methods.member?(_)}
p m.collect {|_| foo.protected_methods.member?(_)}

=> ruby 1.6.8 (2002-12-24) [i586-linux]
   [true, false, true]
   ---
   [true, false, false]
   [true, false, false]
   [false, true, false]
   [false, false, true]
   ---
   [true, false, false]
   [true, false, false]
   [false, true, false]
   [false, false, true]
=> ruby 1.8.0 (2003-03-09) [i586-linux]
   [true, false, true]
   [true, false, false]
   [false, true, false]
   [false, false, true]
   ---
   [true, false, true]
   [true, false, false]
   [false, true, false]
   [false, false, true]
   ---
   [true, false, true]
   [true, false, false]
   [false, true, false]
   [false, false, true]

2002-10-23

[parser] [new]

采用了符号的扩展表示法。[ruby-dev:18537]

p :"foo#{"bar"}"
p :'foo#{"bar"}'
p %s{foo#{"bar"}}

=> ruby 1.7.3 (2002-11-14) [i586-linux]
   :foobar
   :"foo\#{\"bar\"}"
   :"foo\#{\"bar\"}"

2002-10-11

rescue修饰部分 [parser] [change]

修改了rescue修饰部分的优先级。好像是试验性的修改。 (在1.8版本中正式采用了这个修改)。因此

a = b rescue c

不会被解释成

(a = b) rescue c

而是被解释为

a = (b rescue c)

虽然与if修饰部分的优先级有所不同，但它有个好处：如果b发生异常时可以使用c的值。

# 若在以前的版本(1.6)中执行下列代码时，则不会进行赋值
# 只是对变量进行了定义，结果是v等于nil。

v = raise rescue true
p v

=> ruby 1.6.7 (2002-03-01) [i586-linux]
   nil
=> ruby 1.7.3 (2002-10-18) [i586-linux]
   true

2002-10-02

Object#type [obsolete]

使用它就会出现警告。请您使用Object#class来代替它。

p Object.new.type
=> -:1: warning: Object#type is deprecated; use Object#class
   ruby 1.7.3 (2002-10-08) [i586-linux]
   Object

2002-09-27

Class#inherited [change]

在类定义表达式的末尾才会调用inherited方法。 [ruby-bugs-ja:PR#342]

def Object.inherited(c)
  p "inherited!"
end
class Foo
  p "defining Foo"
end

=> ruby 1.6.7 (2002-03-01) [i586-linux]
   "inherited!"
   "defining Foo"
=> ruby 1.7.3 (2002-10-04) [i586-linux]
   "defining Foo"
   "inherited!"

2002-09-26

[parser] [compat]

若在方法定义的外侧调用return的话，则会在运行时而非编译时引发错误。

p :doing
return
=> -:2: return appeared outside of method
   ruby 1.6.7 (2002-03-01) [i586-linux]
=> ruby 1.7.3 (2002-10-04) [i586-linux]
   :doing
   -:2: unexpected return

2002-09-13

||= [bug]

以前，使用||=对未定义的变量进行赋值时，会在全局变量中出现警告。另外，在类变量中会引发错误。 [ruby-dev:18278]

local ||= 1
@instance ||= 1
$global ||= 1
@@class ||= 1

=> -:3: warning: global variable `$global' not initialized
   -:4: uninitialized class variable @@class in Object (NameError)
   ruby 1.6.7 (2002-03-01) [i586-linux]
=> ruby 1.7.3 (2002-09-13) [i586-linux]

2002-09-11

Process.pid (win) [change]

在mswin32版和mingw32版中，ruby会在内部将进程ID变为正数。虽然在NT系列的OS中没有什么变化，但在Win9x系列的OS中，由OS控制的进程ID是负数，所以才将其变为正数。[ruby-dev:18263]

2002-09-11

IO#read, gets ..., etc. [bug]

在对File::NONBLOCK模式的IO进行读入操作时，如果发生EWOULDBLOCK的话，可能会导致读入数据丢失。 [ruby-dev:17855]

在使用Thread的程序中，如果从文件中读出数据并写入socket时，可能会在Socket#write中引发Errno::EINTR，但这种情况极少出现。[ruby-dev:17878], [ruby-core:00444]

2002-09-05

Marshal.dump [marshal] [change]

无法对包含(include)了无名模块的对象进行dump。 [ruby-dev:18186]

class << obj = Object.new
  include Module.new
end
Marshal.dump(obj)

=> ruby 1.6.7 (2002-03-01) [i586-linux]
=> -:4:in `dump': can't dump anonymous class #<Module:0x401a871c> (ArgumentError)
        from -:4
   ruby 1.7.3 (2002-09-06) [i586-linux]

可以dump包含(include)了有名模块的对象，此时模块的信息被保存到dump format之中。

module M
  def foo
    p :foo
  end
end
class << obj = Object.new
  include M
end
p dump = Marshal.dump(obj)
p obj2 = Marshal.load(dump)
class << obj2
   p included_modules
end
obj2.foo

=> ruby 1.6.7 (2002-03-01) [i586-linux]
   "\004\006o:\vObject\000"
   #<Object:0x401a9630>
   [Kernel]
   -:14: undefined method `foo' for #<Object:0x401a9630> (NameError)
=> ruby 1.7.3 (2002-09-06) [i586-linux]
   "\004\ae:\006Mo:\vObject\000"
   #<Object:0x401a821c>
   [M, Kernel]
   :foo

因此将format version由4.7提升到4.8。 (2002-09-17)

2002-09-03

mkmf.rb, extmk.rb [lib] [compat]

开始着手合并extmk.rb和mkmf.rb。extmk.rb将会用到mkmf.rb。相应地对mkmf.rb也作出调整。[ruby-dev:18109]

2002-08-31

ruby interpreter [ruby] [change]

定义规定：类的特殊类的特殊类，就是特殊类本身[ruby-bugs-ja:PR#313]。不太明白(^^;

class << Object
  p [self.id, self]
  class << self
    p [self.id, self]
  end
end
=> ruby 1.6.7 (2002-03-01) [i586-linux]
   [537771634, Class]
   [537742484, Class]
=> ruby 1.7.3 (2002-09-05) [i586-linux]
   [537771634, #<Class:Object>]
   [537771634, #<Class:Object>]

另外好像说，对象的特殊类的超类的特殊类 和 对象的特殊类的特殊类的超类是一回事儿[ruby-bugs-ja:PR#324]。更不明白了(^^;;

class << Object.new
  class << self.superclass
    p [self.id, self]
  end
  class << self
    p [self.superclass.id, self.superclass]
  end
end
=> ruby 1.6.7 (2002-03-01) [i586-linux]
   [537771634, Class]
   [537771644, Class]
=> ruby 1.7.3 (2002-09-05) [i586-linux]
   [537771634, #<Class:Object>]
   [537771634, #<Class:Object>]

[ruby-bugs-ja:PR#336]中好像还有些变化 (请参考2002-09-21的ChangeLog。)

2002-08-30

set.rb [lib] [new]

新增

2002-08-27

Object#become

新增(后来改名为copy_object。再后来又改名为initialize_copy)

ary = [1,2,3]
p ary, ary.id
ary.become [3,2,1]
p ary, ary.id

=> ruby 1.7.3 (2002-08-30) [i586-linux]
   [1, 2, 3]
   537743354
   [3, 2, 1]
   537743354

ary = [1,2,3]
p ary, ary.id
ary.replace [3,2,1]
p ary, ary.id
=> ruby 1.7.3 (2002-08-30) [i586-linux]
   [1, 2, 3]
   537743354
   [3, 2, 1]
   537743354

obj = Object.new
p obj, obj.id
obj.become Object.new
p obj, obj.id
=> ruby 1.7.3 (2002-08-30) [i586-linux]
   #<Object:0x401a9ff4>
   537743354
   #<Object:0x401a9ff4>
   537743354

2002-08-23

ruby interpreter (win32, MinGW) [ruby] [change]

保证了mswin32版ruby 和 MinGW版ruby中的扩展库的兼容性。分别把Config::CONFIG['RUBY_SO_NAME']变更为msvcrt-rubyXX(成为DLL名)，把Config::CONFIG['sitearch'](扩展库所在地的路径元素)变更为"i386-msvcrt"。 [ruby-dev:17144], [ruby-dev:18047]

在这次修改中，新增了sitearch(在其他环境中，则与CONFIG['arch']相同)

另外请参考Win32 native版的脚注

2002-08-20

IO#putc [compat]

在各输出方法中，只有putc不使用write方法。 [ruby-dev:18038]

class << foo = STDOUT.dup
  def write(s)
    p "foo"
  end
end

foo.putc("bar")
puts
=> ruby 1.6.7 (2002-03-01) [i586-linux]
   b
=> ruby 1.7.3 (2002-09-05) [i586-linux]
   "foo"

2002-08-13

Hash#default_proc [new]

新增 [ruby-dev:17966]

2002-08-11

Proc#to_s [compat]

在Proc#to_s 的结果中新增了脚本的源文件名和行号。[ruby-dev:17968]

p Proc.new {
   2
   3
}.to_s
=> -:2: warning: useless use of a literal in void context
   ruby 1.6.7 (2002-03-01) [i586-linux]
   "#<Proc:0x401ab8b8>"
=> -:2: warning: useless use of a literal in void context
   ruby 1.7.3 (2002-09-05) [i586-linux]
   "#<Proc:0x0x401a87d0@-:2>"

2002-08-01

Enumerable#find [change]

不能将字符串指定给参数了。

[1,2,3].find("p :nothing") {|v| v > 5}

=> ruby 1.6.7 (2002-03-01) [i586-linux]
   :nothing

=> -:1:in `find': undefined method `call' for "p :nothing":String (NoMethodError)
        from -:1
   ruby 1.7.2 (2002-08-01) [i586-linux]

另外，若没有找到元素的话，就返回ifnone 的结果。

p [1,2,3].find(proc {:nothing}) {|v| v > 5}
=> ruby 1.6.7 (2002-03-01) [i586-linux]
   nil
=> ruby 1.7.2 (2002-08-01) [i586-linux]
   :nothing

2002-07-27

Numeric#to_int [new]
Float#to_int [new]

新增。

2002-07-26

rand [compat]

在生成随机数的算法中使用了Mersenne Twister。

2002-07-24

[parser] [compat]

允许对方法定义进行嵌套。

def func1
  def func2
    p :func2
  end
end
func1
func2

=> -:2: nested method definition
   ruby 1.6.7 (2002-03-01) [i586-linux]
=> ruby 1.7.3 (2002-09-05) [i586-linux]
   :func2

允许在方法定义中出现alias, undef。

def bar
end
def foo
  p :foo
  undef bar
end

foo

def bar
  p :bar
  alias foo bar
end

bar
foo
=> -:5: undef within method
   -:12: alias within method
   ruby 1.6.7 (2002-03-01) [i586-linux]
=> ruby 1.7.3 (2002-09-05) [i586-linux]
   :foo
   -:10: warning: method redefined; discarding old bar
   -:10: warning: overriding global function `bar'
   :bar
   :bar

在方法定义外侧调用super时，将会在运行时而不是编译时引发错误。

p 1
super
=> -:2: super called outside of method
   ruby 1.6.7 (2002-03-01) [i586-linux]
=> ruby 1.7.3 (2002-09-05) [i586-linux]
   1
   -:2: super called outside of method (NoMethodError)

也许[ruby-dev:16969]中给出了变更的理由。[ruby-dev:17882]

2002-07-19

数值字面值 [compat]

新增了10进制整数字面值的0d前缀。

p 0d10
=> ruby 1.7.3 (2002-09-04) [i586-linux]
   10

p 0d10.1
=> -:1: parse error
   ruby 1.7.3 (2002-09-04) [i586-linux]

允许使用大写字母。

p 0D10
=> ruby 1.7.3 (2002-09-04) [i586-linux]
   10

但不能像下面这样。

p(/\d10/)
p "\d10"
=> ruby 1.7.3 (2002-09-04) [i586-linux]
   /\d10/
   "d10"

只要字面值允许，Integer()也就允许。

p Integer("0d010")
=> ruby 1.7.3 (2002-09-04) [i586-linux]
   10

p Integer("0d010.1")
=> -:1:in `Integer': invalid value for Integer: "0d010.1" (ArgumentError)
        from -:1
   ruby 1.7.3 (2002-09-04) [i586-linux]

String#to_i、String#oct也是如此

p "0d010".to_i
p "0d010".oct
=> ruby 1.6.7 (2002-03-01) [i586-linux]
   0
   0
=> ruby 1.7.3 (2002-09-04) [i586-linux]
   10
   10

2002-07-18

net/ftp.rb [new]

新增set_socket方法

2002-07-17

数值字面值 [compat]

新增了8进制字面值的0和0o前缀。

p 0o377
=> ruby 1.7.3 (2002-09-04) [i586-linux]
   255

可以使用大写字母。

p 0O377
=> ruby 1.7.3 (2002-09-04) [i586-linux]
   255

不能像下面这样。

p(/\o377/)
p "\o377"
=> ruby 1.7.3 (2002-09-04) [i586-linux]
   /\o377/
   "o377"

只要字面值允许，Integer()也没问题。

p Integer("0o377")
=> -:1:in `Integer': invalid value for Integer: "0o377" (ArgumentError)
        from -:1
   ruby 1.6.7 (2002-03-01) [i586-linux]
=> ruby 1.7.3 (2002-09-04) [i586-linux]
   255

String#to_i、String#oct也是如此

p "0o377".oct
p "0o377".to_i(8)
=> ruby 1.6.7 (2002-03-01) [i586-linux]
   0
   -:2:in `to_i': wrong # of arguments(1 for 0) (ArgumentError)
        from -:2
=> ruby 1.7.3 (2002-09-04) [i586-linux]
   255
   255

2002-07-11

String#scan [change]
String#split [change]
String#sub, String#sub! [change]
String#gsub, String#gsub! [change]
String#~ [change]
String#=~ [change]

若将字符串而非正则表达式传给pattern的话，就直接把它用作匹配模型，而不会将其编译成正则表达式。(准确地讲，不会Regexp.compile(arg)这样处理，而是Regexp.compile(Regexp.quote(arg))这样)

只有str =~ arg中的arg是字符串时，才会执行str.index(arg)，它等价于Regexp.compile(Regexp.quote(arg)) =~ str(所以没有设定$~)。

p "aaaa*".scan("a*")
=> ruby 1.6.7 (2002-03-01) [i586-linux]
   ["aaaa", "", ""]
=> -:1: warning: string pattern instead of regexp; metacharacters no longer effective
   ruby 1.7.3 (2002-09-04) [i586-linux]
   ["a*"]

p "aa*aa*aa*".split("a*")
=> ruby 1.6.7 (2002-03-01) [i586-linux]
   ["", "*", "*", "*"]
=> -:1: warning: string pattern instead of regexp; metacharacters no longer effective
   ruby 1.7.3 (2002-09-04) [i586-linux]
   ["a", "a", "a"]

p "aa*".sub('a*', '')
=> ruby 1.6.7 (2002-03-01) [i586-linux]
   "*"
=> -:1: warning: string pattern instead of regexp; metacharacters no longer effective
   ruby 1.7.3 (2002-09-04) [i586-linux]
   "a"

p "aa*aa*aa*aa*".gsub('a*', '')
=> ruby 1.6.7 (2002-03-01) [i586-linux]
   "****"
=> -:1: warning: string pattern instead of regexp; metacharacters no longer effective
   ruby 1.7.3 (2002-09-04) [i586-linux]
   "aaaa"

$_ = "aa*"
p ~"a*"
=> ruby 1.6.7 (2002-03-01) [i586-linux]
   0
=> ruby 1.7.3 (2002-09-04) [i586-linux]
   1

2002-07-03

net/ftp.rb [compat]

getbinaryfile() 的第二参数(本地文件名)变为可选参数。新增get(), put(), binary(),binary = 方法

2002-06-29

双向管道 (win) [compat]

听说加入了支持Win32用的双向管道的补丁 [ruby-win32:185]

2002-06-26

Object#to_a [obsolete]

使用它的话会出现警告消息。(听说已经变成obsolete)

p Object.new.to_a

=> ruby 1.6.7 (2002-03-01) [i586-linux]
   [#<Object:0x401ab8b8>]
=> -:1: warning: default `to_a' will be obsolete
   ruby 1.7.3 (2002-09-02) [i586-linux]
   [#<Object:0x401a88ac>]

2002-06-26

Array() [change]

Array()的参数不再接受nil。 (在ruby 1.8.0 (2003-05-29)中，又开始接受nil了)

p Array(nil)

=> ruby 1.6.7 (2002-03-01) [i586-linux]
   []
=> -:1:in `Array': cannot convert nil into Array (TypeError)
        from -:1
   ruby 1.7.3 (2002-09-02) [i586-linux]

=> ruby 1.8.0 (2003-05-29) [i586-linux]
   []

2002-06-26

%W() [parser]

新增了%W(...) 数组字面值。与%w()不同的是，它可以使用反斜线表示法和展开式。[ruby-dev:15988]

v = "b c"
p %W(a #{v}d\se)

=> ruby 1.7.3 (2002-09-04) [i586-linux]
   ["a", "b cd e"]

2002-06-25

Integer() [change]

在把数值或字符串以外的对象变换为整数时，不再使用to_i，而是使用to_int进行变换。

class << obj = Object.new
  def to_i()   0 end
  def to_int() 1 end
end

p Integer(obj)

=> ruby 1.6.7 (2002-03-01) [i586-linux]
   0
=> ruby 1.7.3 (2002-09-02) [i586-linux]
   1

2002-06-25

NilClass#to_f [new]

新增

p nil.to_f

=> -:1: undefined method `to_f' for nil (NameError)
   ruby 1.6.7 (2002-03-01) [i586-linux]
=> ruby 1.7.3 (2002-09-02) [i586-linux]
   0.0

2002-06-25

Float() [change]

Float()的参数不再接受nil。

p Float(nil)

=> ruby 1.6.7 (2002-03-01) [i586-linux]
   0.0
=> -:1:in `Float': cannot convert nil into Float (TypeError)
        from -:1
   ruby 1.7.3 (2002-09-02) [i586-linux]

2002-06-24

展开式 [parser]

在#{ ... }展开式中，可以书写任何ruby程序，包括字符串分隔符在内。虽然以前也是如此，但此次则明确了规则。也就是说，展开式中的语法规则与外面相同。ruby程序会被正确解析。[ruby-dev:17422]

(1.6 版本中，曾出现过异常的举动)

p "#{ "foo" }"
=> ruby 1.6.7 (2002-03-01) [i586-linux]
   "foo"
=> -:1: warning: bad substitution in string
   -:1: parse error
           p "#{ "foo" }"
                     ^
   ruby 1.6.7 (2002-08-21) [i586-linux]
=> ruby 1.6.8 (2002-12-24) [i586-linux]
   "foo"
=> ruby 1.7.3 (2002-09-02) [i586-linux]
   "foo"

不应该对下列分隔符进行转义。

p "#{ \"foo\" }"
=> ruby 1.6.7 (2002-03-01) [i586-linux]
   "foo"
=> ruby 1.6.7 (2002-08-21) [i586-linux]
   "foo"
=> -:1: warning: escaped terminator '"' inside string interpolation
   ruby 1.7.3 (2002-09-02) [i586-linux]
   "foo"

请注意：展开式中注释并不是从 # 到 } ，而是从 # 到换行。

p "#{ "foo" # comment }"

=> ruby 1.6.7 (2002-03-01) [i586-linux]
   "foo"
=> -:1: parse error
   ruby 1.7.3 (2002-09-02) [i586-linux]

p "#{ "foo" # comment
   }"

=> ruby 1.6.7 (2002-03-01) [i586-linux]
   "foo"
=> ruby 1.7.3 (2002-09-02) [i586-linux]
   "foo"

2002-06-21

[parser] [compat]

字符串字面值中的行首的 __END__ 不再被当作脚本的结束标志了。[ruby-dev:17513]

# p "
#__END__
#"
p eval(%Q(p "\n__END__\n"))
=> -:1: compile error (SyntaxError)
   (eval):1: unterminated string meets end of file
   ruby 1.6.7 (2002-03-01) [i586-linux]
=> ruby 1.7.3 (2002-09-02) [i586-linux]
   "\n__END__\n"
   nil

2002-06-15

?<whitespace> [parser] [change]

?空格、?换行、?TAB 等不再是字面值。若必须使用的话，可以写成 ?\s, ?\n, ?\t 。(请注意，下例中的前半部分使用了双引号) [ruby-bugs-ja:PR#261], [ruby-dev:17446]

p eval("?\t")
p eval("?\n")
p eval("?\v")
p eval("?\f")
p eval("?\r")
p eval("? ")

=> ruby 1.6.7 (2002-03-01) [i586-linux]
   9
   10
   11
   12
   13
   32

=> -:1: compile error (SyntaxError)
   (eval):1: parse error
   ruby 1.7.3 (2002-09-02) [i586-linux]

p eval('?\t')
p eval('?\n')
p eval('?\v')
p eval('?\f')
p eval('?\r')
p eval('?\s')

=> ruby 1.6.7 (2002-03-01) [i586-linux]
   9
   10
   11
   12
   13
   32
=> ruby 1.7.3 (2002-09-02) [i586-linux]
   9
   10
   11
   12
   13
   32

2002-06-11

Borland C++ 支持 [platform]

合并了支持在bcc中编译ruby解释器的补丁。

2002-06-04

Range#max [change]
Range#min [change]
Range#include? [change]
Range#member? [change]

Range#max, Range#min, Range#include? 使用 <=> 方法进行范围计算。[ruby-list:35253], [ruby-dev:17228] (2003-03-18: min, max 变回原样。[ruby-dev:19837])

Range#member? 使用 each 来遍历所有元素并确认是否有member。(与Enumerable#member?相同)

截止1.6，max, min, member? include? 是 Enumerable 的方法，=== 是 Range的方法。在1.7中，max, min, member?, include?, === 都是 Range 的方法，include? 成了 === 的别名。(在1.8中，max, min重新成为 Enumerable 的方法)

因为这些变动导致下列不同。

p((0.1 .. 2.0).include?(1.1))
=> ruby 1.6.7 (2002-03-01) [i586-linux]
   false
=> ruby 1.7.3 (2002-09-02) [i586-linux]
   true

p((0.1 .. 2.0).member?(1.0))
=> ruby 1.6.7 (2002-03-01) [i586-linux]
   true
=> -:1:in `member?': cannot iterate from Float (TypeError)
        from -:1
   ruby 1.7.3 (2002-09-02) [i586-linux]

p "b" < "ba"
p(("a"..."bc").max)
=> ruby 1.6.7 (2002-03-01) [i586-linux]
   true
   "b"
=> ruby 1.7.3 (2002-09-05) [i586-linux]
   true
   "bc"

=> ruby 1.8.0 (2003-03-20) [i586-linux]
   true
   "b"

2002-06-01

win32ole.so [lib] [new]

新增

2002-05-30

Range#each [change]

Range#each 使用各元素的 succ 方法进行迭代操作。

(1.0 .. 2.0).each {|v| p v}
=> ruby 1.6.7 (2002-03-01) [i586-linux]
   1
   2
=> -:1:in `each': cannot iterate from Float (TypeError)
        from -:1
   ruby 1.7.3 (2002-09-02) [i586-linux]

class Float
  def succ
   self + 1.0
  end
end
(1.0 .. 2.0).each {|v| p v}

=> ruby 1.7.3 (2002-09-02) [i586-linux]
   1.0
   2.0

2002-05-30

Range#size [obsolete]
Range#length [obsolete]

该方法也被删除。 [ruby-talk:64479], [ruby-talk:72133]

p(("a".."z").size)
=> ruby 1.6.7 (2002-03-01) [i586-linux]
   26
=> -:1: undefined method `size' for #<Range:0x401aa780> (NoMethodError)
   ruby 1.7.2 (2002-08-01) [i586-linux]

若想得到Range的元素数量，必须这样

p(("a".."z").to_a.size)

=> ruby 1.7.2 (2002-08-01) [i586-linux]
   26

才行。

2002-05-29

Proc#binding [new]

新增

2002-05-28

负的数值字面值

2003-01-21: 该修改好像是变回原来的样子

对 -数值 的字面值的解释发生了变化，-数值 总是被当作一个字面值来处理。

例如，下面的表达式的结果就各不相同。

p -2**2
=> -:1: warning: ambiguous first argument; make sure
   ruby 1.6.7 (2002-03-01) [i586-linux]
   -4
=> -:1: warning: ambiguous first argument; make sure
   ruby 1.7.2 (2002-08-01) [i586-linux]
   4

=> -:1: warning: ambiguous first argument; make sure
   ruby 1.8.0 (2003-03-12) [i586-linux]
   -4

以前-2**2 被解释成-(2**2)。这是由于操作符的优先级不同所致 (真的如此吗？)。在1.7中则被解释成(-2)**2。另外，若在 - 和数值之间插入空格的话， - 会被当作单项操作符(方法)。(这与以前相同)

p(- 2**2)

=> ruby 1.6.7 (2002-03-01) [i586-linux]
   -4
=> ruby 1.7.2 (2002-08-01) [i586-linux]
   -4

=> ruby 1.8.0 (2003-03-12) [i586-linux]
   -4

class Fixnum
  def -@
      1
  end
end

p(- 2**2)
=> -:2: warning: discarding old -@
   ruby 1.6.7 (2002-03-01) [i586-linux]
   1
=> -:2: warning: method redefined; discarding old -@
   ruby 1.7.2 (2002-08-01) [i586-linux]
   1

=> -:2: warning: method redefined; discarding old -@
   ruby 1.8.0 (2003-03-12) [i586-linux]
   1

2002-05-23

IO.sysopen [new]
Socket#sysaccept [new]
TCPServer#sysaccept [new]
UNIXServer#sysaccept [new]

新增

2002-05-13

[parser] [change]
String#to_f [change]
Float() [change]

在将字符串变为浮点数时，不再依赖库函数 strtod(3)了。这样，即使修改了库的内容，也不会影响到它的运作。

p "0xa.a".to_f

=> ruby 1.6.7 (2002-03-01) [i586-linux]
   10.625
=> ruby 1.7.2 (2002-08-01) [i586-linux]
   0.0

2002-05-13

Float#to_s [compat]

表示最大精度的格式由"%.10g"变为"%.16g"。(2003-03-20: 其后又变为"%.15g" [ruby-bugs-ja:PR#406])

p 1.0/3
p 99.6
=> ruby 1.6.7 (2002-03-01) [i586-linux]
   0.3333333333
   99.6

=> ruby 1.7.2 (2002-08-01) [i586-linux]
   0.3333333333333333
   99.59999999999999

=> ruby 1.8.0 (2003-03-20) [i586-linux]
   0.333333333333333
   99.6

2002-05-10

Thread#join [compat]

可以使用limit来指定等待线程的时间。

2002-04-26

Enumerable#partition [new]

新增

2002-04-22

方法的参数中的 & [compat]
Proc#to_proc [new]

在使用 & 来修饰方法的参数时，若传给参数的对象中包含to_proc 方法就调用它，并把结果当作块来传给方法。以前，& 就只能修饰Proc, Method对象。另外，还新增了Proc#to_proc。

class Foo
  def to_proc
    p "should generate Proc object"
  end
end

def foo
end

foo(&Foo.new)

=> ruby 1.7.2 (2002-04-24) [i586-linux]
   "should generate Proc object"
   -:10: wrong argument type Foo (expected Proc) (TypeError)

2002-04-19

Numeric#step [compat]

从Fixnum, Integer移动到这里。

2002-04-18

Regexp#to_s [new]

新增。[ruby-dev:16909]

p /foo(bar)*/.to_s
=> "(?-mix:foo(bar)*)"

2002-04-09

File.extname [new]

新增。返回文件名中的扩展名。[ruby-talk:37617]

2002-04-08

each_pair [new]

新增。

2002-04-06

Bignum [bug]

以前，若遇到比-2147483648还小的数值时，其2进制、8进制、16进制的表示形式就会出问题 [ruby-list:34828]

p "%b" % -2147483648
p "%b" % -2147483649
p "%b" % -2147483650

=> ruby 1.6.7 (2002-03-01) [i586-linux]
   "..10000000000000000000000000000000"
   "..1"
   "..10"

=> ruby 1.7.2 (2002-04-11) [i586-linux]
   "..10000000000000000000000000000000"
   "..101111111111111111111111111111111"
   "..101111111111111111111111111111110"

2002-04-08

结束状态值 [compat]

raise SystemExit时，会使用结束状态值 1 。 [ruby-dev:16776]

2002-04-02

dl.so [lib] [new]

新增

2002-03-27

IO#sysseek(offset, whence) [new]

新增 [ruby-talk:21612], [ruby-talk:36703]

2002-03-26

net/http.rb [compat]

在Net::HTTP 的类方法中，可以使用URI对象了。

Net::HTTP.get_print(URI.parse('http://www.ruby-lang.org/ja/'))

请注意，在实例方法中则无法使用。

2002-03-26

rescue/ensure on begin .. end while [compat]

在包含rescue/ensure的begin语句中，也可以使用while/until来进行修饰了。

以前在包含rescue/ensure的while/until修饰表达式中，并没有最先执行主体部分(与C语言中的do ... while语法相同)。 [ruby-list:34618]

i = 0
begin
  p i
  i += 1
rescue
end while i < 0

=> ruby 1.6.7 (2002-03-01) [i586-linux]

=> ruby 1.7.2 (2002-03-29) [i586-linux]
   0

2002-03-26

rescue/ensure on class/module [compat]

不仅可以在方法定义中使用rescue/ensure，在类定义或模块定义中也可以如此。

class Foo
  hogehoge
rescue
  p $!
end

=> -:3: parse error
   ruby 1.6.7 (2002-03-01) [i586-linux]
=> ruby 1.7.2 (2002-03-29) [i586-linux]
   #<NameError: undefined local variable or method `hogehoge' for Foo:Class>

2002-03-25

Thread.list [compat]
ThreadGroup#list [compat]

已结束(aborting)的线程也被包含到列表中。 [rubyist:1282]

th = Thread.new {sleep}
Thread.critical = true
th.kill

p Thread.list

=> ruby 1.6.7 (2002-03-01) [i586-linux]
   [#<Thread:0x401ba5c8 run>]

=> ruby 1.7.2 (2002-03-29) [i586-linux]
   [#<Thread:0x401b0618 aborting>, #<Thread:0x401ba0b4 run>]

2002-03-25

Thread#wakeup [bug]
Thread#run [bug]

若对已结束(aborting)的线程使用上述方法时，该线程就会起死回生。现在已经修复了这个bug。 [rubyist:1282]

2002-03-22

sprintf('%u') [compat]

在sprintf 的 '%u' 中，不再新增".."了。[ruby-dev:16522]

p sprintf("%u", -1)

=> ruby 1.6.7 (2002-03-01) [i586-linux]
   "..4294967295"

=> -:1: warning: negative number for %u specifier
   ruby 1.7.2 (2002-03-29) [i586-linux]
   "4294967295"

2002-03-22

VMS support [platform]

打上了支持VMS的补丁。

附加库 [lib] [new]

新增了下列各库。 iconv.so, tsort.rb, stringio.so, strscan.so, fileutils.rb, racc/*

Dir.glob [compat]

可以指定Dir.glob的第2参数(决定匹配方式的标识)了。在Dir[] 中则无法使用该标识。

新增了相关的常数 File::FNM_DOTMATCH (表示 与FNM_PERIOD相反)。

p Dir.glob("/*")
=> ruby 1.7.2 (2002-03-15) [i586-linux]
   ["/lost+found", "/root", ...]

p Dir.glob("/*", File::FNM_DOTMATCH)

=> ruby 1.7.2 (2002-03-15) [i586-linux]
   ["/.", "/..", "/lost+found", "/root", "/boot", ...]
large file [bug]

可以正确处理large file(大小超过4G bytes的文件)了(真的？) [ruby-talk:35316], [ruby-talk:35470]

Process.kill [compat]

在mswin32, mingw32中，也可以使用Process.kill(9, pid)来强制结束进程(TerminateProcess)。(好像Process.kill("KILL", pid)就不行???2002-08-28 以后好像可以使用 "KILL" 了)

benchmark.rb [new]

新增

abort [compat]

可以指定结束消息了。

abort("abort!")
=> abort!
   ruby 1.7.2 (2002-03-15) [i586-linux]

您所指定的消息会被设置给异常SystemExit对象的message 属性。

begin
  abort("abort!")
rescue SystemExit
  p $!.message
end

=> abort!
   ruby 1.7.2 (2002-03-29) [i586-linux]
   "abort!"
GDBM [lib] [change]
DBM [lib] [change]
SDBM [lib] [change]

文档中没有提到 [ruby-dev:16126]

Module#include [change]
Object#extend [change]

传递若干个模块时，include的顺序有所改变。好像[ruby-dev:16035] extend 也是如此。[ruby-dev:16183]

module Foo; end
module Bar; end
module Baz; end

include Foo, Bar, Baz
p Object.ancestors

=> ruby 1.6.7 (2002-03-01) [i586-linux]
   [Object, Baz, Bar, Foo, Kernel]

=> ruby 1.7.2 (2002-03-01) [i586-linux]
   [Object, Foo, Bar, Baz, Kernel]

obj = Object.new
module Foo; end
module Bar; end
module Baz; end

obj.extend Foo, Bar, Baz
class << obj
  p ancestors
end

=> ruby 1.6.7 (2002-03-01) [i586-linux]
   [Baz, Bar, Foo, Object, Kernel]

=> ruby 1.7.2 (2002-03-08) [i586-linux]
   [Foo, Bar, Baz, Object, Kernel]

这与一个一个地include时的顺序相反。

module Foo; end
module Bar; end
module Baz; end

include Foo
include Bar
include Baz
p Object.ancestors

=> ruby 1.7.2 (2002-03-01) [i586-linux]
   [Object, Baz, Bar, Foo, Kernel]
UNIXSocket.pair [new]
UNIXSocket.socketpair [new]
UNIXSocket#recv_io [new]
UNIXSocket#send_io [new]

新增

Proc#yield

[ruby-bugs-ja:PR#98] (2003-03-11: 该修改被取消 [ruby-dev:19799]) (其后Proc#yield也被取消)

Proc.new { break }.call
Proc.new { break }.yield

=> -:2:in `yield': break from proc-closure (LocalJumpError)
        from -:2
   ruby 1.7.3 (2002-09-05) [i586-linux]

=> ruby 1.8.0 (2003-03-12) [i586-linux]
Array#pack [compat]
String#unpack [compat]

可以在pack/unpack 的模板中写入注释了。

p [1,2,3,4].pack("s  # short (fixed 2 bytes)
                  i  # int (machine dependent)
                  l  # long (fixed 4 bytes)
                  q  # quad (fixed 8 bytes)")
=> ruby 1.7.2 (2002-02-21) [i586-linux]
   "\001\000\002\000\000\000\003\000\000\000\004\000\000\000\000\000\000\000"
LocalJumpError#exitstatus [new]

新增(其后变为exit_value)

def foo
  proc { return 10 }
end

begin
  foo.call
rescue LocalJumpError
  p $!.exitstatus
end

=> ruby 1.7.2 (2002-02-14) [i586-linux]
   10
UNIXServer#listen [new]
TCPServer#listen [new]

新增。与Socket#listen相同。

Time#getgm [new]
Time#getlocal [new]
Time#getutc [new]
Time#gmt_offset [new]
Time#gmtoff [new]
Time#utc_offset [new]

新增

2002-05-21

Module#<=> [change]

比较两个没有继承关系的类/模块时，将返回nil。

p Array <=> String

=> ruby 1.6.7 (2002-03-01) [i586-linux]
   1
=> ruby 1.7.3 (2002-09-13) [i586-linux]
   nil
IO#fsync [new]

新增

Array#pack [compat]
String#unpack [compat]

新征了64 bit 整数的模板字符 Q/q (表示Quad之意)。 Q表示unsigned，而q表示signed。 与perl不同的是，即使在不支持64 bit 整数的平台上，仍然可以使用。

p [ 1].pack("Q")
p [-1].pack("Q")
p [ 1].pack("q")
p [-1].pack("q")

p [ 1].pack("Q").unpack("Q")
p [-1].pack("Q").unpack("Q")
p [ 1].pack("q").unpack("q")
p [-1].pack("q").unpack("q")

=> ruby 1.7.2 (2002-02-13) [i586-linux]
   "\001\000\000\000\000\000\000\000"
   "\377\377\377\377\377\377\377\377"
   "\001\000\000\000\000\000\000\000"
   "\377\377\377\377\377\377\377\377"
   [1]
   [18446744073709551615]
   [1]
   [-1]
Method#inspect [compat]

特殊方法的输出形式更具实际意义了。 [ruby-bugs-ja:PR#193]

obj = []
def obj.foo
end
p obj.method(:foo)

=> ruby 1.6.6 (2001-12-26) [i586-linux]
   #<Method: Array(Array)#foo>

=> ruby 1.7.2 (2002-02-05) [i586-linux]
   #<Method: [].foo>

2002-01-28

Array.new [compat]
Array#fill [compat]

可以将块的计算结果指定为fill值。依次为各个元素计算块的内容，所以下列中每次都会生成"val"

ary = Array.new(3, "val")
p ary.collect {|v| v.id }       # => [537774036, 537774036, 537774036]
ary = Array.new(3) { "val" }
p ary.collect {|v| v.id }       # => [537770040, 537770028, 537770016]

2002-01-28

File::Stat#rdev_major [new]
File::Stat#rdev_minor [new]

新增

s = File.stat("/dev/null")
p s.rdev_major
p s.rdev_minor

=> ruby 1.7.2 (2002-01-28) [i686-linux]
   1
   3

2002-01-24

Hash#update [compat]

可以指定块了。还可以控制重复键的处理方式。

2002-01-18

IO.open [new]

新增

2002-01-14

Proc [bug]

当$SAFE为1或2时，被污染的Proc将无法变成块 [ruby-dev:15682]

$SAFE = 1
proc = proc {}
proc.taint
p proc.tainted?
def foo(&b)
  p b.tainted?
end
foo(&proc)

=> ruby 1.6.8 (2003-08-03) [i586-linux]
   true
   true
=> ruby 1.7.2 (2002-01-23) [i586-linux]
   true
   true

2002-01-08

String#to_i [compat]

可以将基数(2,8,10,16)指定给参数。(2002-01-26: 当参数为0时，用prefix来判定基数)

p "010".to_i(16)
=> ruby 1.7.2 (2002-01-11) [i586-linux]
   16

2001-12-08

Hash.new [compat]

可以把块当作哈希的默认值了。指定块之后，每次使用空的哈希元素时都会执行块的内容，并返回其结果。此时会把 哈希本身 和 使用哈希时的键 传给块。

h = Hash.new("foo")
p h.default.id
p h.default(0).id    # Hash#default 可以指定传给块的键
p h[0].id
p h[0].id
p h[1].id

=> ruby 1.7.2 (2001-12-10) [i586-linux]
   537774276
   537774276
   537774276
   537774276

h = Hash.new { "foo" }
p h.default.id
p h.default(0).id
p h[0].id
p h[0].id
p h[1].id

=> ruby 1.7.2 (2001-12-10) [i586-linux]
   537770616
   537770352
   537770316
   537770280

h = Hash.new { raise IndexError, "undefined!!" }
p h[0]

=> -:1: undefined!! (IndexError)
        from -:1:in `yield'
        from -:2:in `default'
        from -:2:in `[]'
        from -:2
   ruby 1.7.2 (2001-12-10) [i586-linux]

2001-12-11

Array#select
Hash#select
ENV.select
MatchData#select
Struct::XXX#select

新增(此后被values_at方法所取代)

# 没有给出块的话，则与indexes/indicies 相同。
# (注: indexes/indicies已变为obsolete)

p [1,2,3].select(0,1,2,3)
p [1,2,3].select(-4,-3,-2,-1)

p( {1=>"a", 2=>"b", 3=>"c"}.select(3,2,1) )

=> ruby 1.7.2 (2001-12-10) [i586-linux]
   [1, 2, 3, nil]
   [nil, 1, 2, 3]
   ["c", "b", "a"]

# 若给出了块的话，则与Enumerable#select 相同。

p [1,2,3,4,5].select {|v| v % 2 == 1}
p( {1=>"a", 2=>"b", 3=>"c"}.select {|k,v| k % 2 == 1} )

=> ruby 1.6.6 (2001-12-04) [i586-linux]
   [1, 3, 5]
   [[1, "a"], [3, "c"]]

=> ruby 1.7.2 (2001-12-10) [i586-linux]
   [1, 3, 5]
   [[1, "a"], [3, "c"]]

m = /(foo)(bar)(baz)/.match("foobarbaz")
p m.select(0,1,2,3,4)   # same as m.to_a.indexes(...)
p m.select(-1,-2,-3)

=> ruby 1.7.2 (2001-12-10) [i586-linux]
   ["foobarbaz", "foo", "bar", "baz", nil]
   ["baz", "bar", "foo"]

2001-12-11

String#match(re) [new]

新增。与 re.match(str) 相同。

2001-11-17

Marshal [marshal] [change]

对Float进行dump时，不再依赖sprintf(3)了。format version由4.6升到4.7。 (此后，由于把strtod(3)收入了语言本身，即使进行读入时也不再依赖strtod(3)了)

2001-11-12

trap [bug]
trace_var [bug]

如将受污染的字符串传给第二参数的话，就会引发SecurityError异常。在1.6中，若安全级别为4则会计算受污染的字符串。 [ruby-list:32215]

2001-10-16

Module.new [compat]
Class.new [compat]

在Module.new, Class.new中，若给出了块的话，则在生成的模块/类的上下文中计算块的内容。

Module.new {|m| p m}

=> ruby 1.7.1 (2001-10-15) [i586-linux]
   #<Module:0x401afd5c>

2001-10-10

Object#clone [change]

不能对Numeric这种immutable对象进行clone。 [ruby-bugs-ja:PR#94], [rubyist:0831]

$DEBUG=true
true.clone     rescue nil
false.clone    rescue nil
nil.clone      rescue nil
:sym.clone     rescue nil
(10**10).clone rescue nil
0.clone        rescue nil

=> Exception `TypeError' at -:2 - can't clone true
   Exception `TypeError' at -:3 - can't clone false
   Exception `TypeError' at -:4 - can't clone nil
   Exception `TypeError' at -:5 - can't clone Symbol
   ruby 1.6.6 (2001-12-26) [i586-linux]

=> Exception `TypeError' at -:2 - can't clone TrueClass
   Exception `TypeError' at -:3 - can't clone FalseClass
   Exception `TypeError' at -:4 - can't clone NilClass
   Exception `TypeError' at -:5 - can't clone Symbol
   Exception `TypeError' at -:6 - can't clone Bignum
   Exception `TypeError' at -:7 - can't clone Fixnum
   ruby 1.7.1 (2001-10-10) [i586-linux]
puts
Array#to_s

puts不再对数组进行特殊处理了，而是输出Array#to_s。Array#to_s在默认情况下会输出带换行的字符串，所以从其输出形式看来是没有什么变化的(但要受到$,的值的影响)。[ruby-dev:15043]

该修改尚处于试验阶段，又可能会改回原样。。。[ruby-dev:15313]

$, = ","
puts %w(foo bar baz)
=> ruby 1.6.5 (2001-11-01) [i586-linux]
   foo
   bar
   baz
=> ruby 1.7.2 (2001-11-25) [i586-linux]
   foo,bar,baz

???好像是改回原样了。

=> ruby 1.7.2 (2001-12-10) [i586-linux]
   foo
   bar
   baz
Integer#to_s [compat]

可以将基数指定给参数了。

p 10.to_s(16)
=> ruby 1.7.2 (2001-11-25) [i586-linux]
   "a"
String#chomp [change]
String#chomp! [change]
chomp [change]
chomp! [change]

只要$/ 的值为"\n" (默认)，不管是哪种行尾("\r\n", "\r"或"\n")都可以清除干净。

p "aaa\r\n".chomp
=> ruby 1.6.5 (2001-11-01) [i586-linux]
   "aaa\r"
=> ruby 1.7.2 (2001-11-25) [i586-linux]
   "aaa"
Complex#to_i [lib] [obsolete]
Complex#to_f [lib] [obsolete]
Complex#to_r [lib] [obsolete]

Complex#to_i, #to_f, #to_r已被取消。 [ruby-bugs-ja:PR#102], [rubyist:0879]

方法调用 [parser] [change]

若方法名和括弧之间有空格，则该括号不会为当作是包含参数的括号，而会被当作表达式中的括号。

p (1+2)*3

=> -:1: warning: p (...) interpreted as method call
   -:1: warning: useless use of * in void context
   ruby 1.6.5 (2001-09-19) [i586-linux]
   3
   -:1: undefined method `*' for nil (NameError)

=> -:1: warning: p (...) interpreted as command call
   ruby 1.7.1 (2001-06-05) [i586-linux]
   9
Marshal [marshal] [bug]

若对结构体类的子类进行dump的话，则无法读出。[ruby-bugs-ja:PR#104]

S = Struct.new("S", :a)
class C < S
end
p Marshal.load(Marshal.dump(C.new))

=> -:4: warning: instance variable __member__ not initialized
   -:4:in `dump': uninitialized struct (TypeError)
        from -:4
   ruby 1.6.5 (2001-09-19) [i586-linux]

=> ruby 1.7.1 (2001-10-19) [i586-linux]
   #<C a=nil>
alias [bug]

全局变量的别名无效。 [ruby-dev:14922]

$g2 = 1
alias $g1 $g2
p [$g1, $g2]
$g2 = 2
p [$g1, $g2]
=> ruby 1.6.5 (2001-09-19) [i586-linux]
   [1, 1]
   [1, 2]

=> ruby 1.7.1 (2001-10-19) [i586-linux]
   [1, 1]
   [2, 2]

2001-10-03

String#[] [new]
String#[]= [new]

新增

String#[re, idx]
String#[re, idx] = val

新增第二个可选参数idx。

p "foobarbaz"[/(foo)(bar)(baz)/, 1]
p /(foo)(bar)(baz)/.match("foobarbaz").to_a[1]
=> -:2: warning: ambiguous first argument; make sure
   ruby 1.7.1 (2001-10-05) [i586-linux]
   "foo"
   "foo"

str = "foobarbaz"
p str[/(foo)(bar)(baz)/, 2] = "BAR"  # => "BAR"
p str                                # => "fooBARbaz"

str[/re/, 0] 与 str[/re/] 相同。

2001-10-02

Class#allocate [new]

由allocate 和 initialize 这两个方法来生成对象。[ruby-dev:14847] 请参考 rb_define_alloc_func() 。

2001-09-24

Array.new [compat]

若将数组传给Array.new 的参数时，将会生成该数组的拷贝。

ary = [1,2,3]
ary2 = Array.new ary
p ary, ary2
p ary.id, ary2.id

=> ruby 1.7.1 (2001-10-05) [i586-linux]
   [1, 2, 3]
   [1, 2, 3]
   537758120
   537755360

2001-09-18

String.new [compat]

可以省略String.new 的参数了。

p String.new
=> -:1:in `initialize': wrong # of arguments(0 for 1) (ArgumentError)
        from -:1:in `new'
        from -:1
   ruby 1.7.1 (2001-08-29) [i586-linux]

=> ruby 1.7.1 (2001-10-05) [i586-linux]
   ""

2001-09-11

Dir#path [new]

新增

p Dir.open(".").path
=> ruby 1.7.1 (2001-10-05) [i586-linux]
   "."

2001-08-26

Readline [change]

在Readline.readline的执行过程中使用Ctrl-C进行中断之后，还可以恢复终端状态。[ruby-dev:14574]

2001-08-29

Precision.included [new]

新增(Module#included的重定义)

2001-08-23

Signal 模块 [new]

新增。

while, until, class, def 的值 [ruby] [change]

while, until, class, def能返回值了。

class/module 返回最后计算的表达式的值。def返回nil。while/until通常返回 nil，若使用带参数的break的话，则可以返回任何值。

p(while false; p nil end)
p(while true; break "bar" end)
p(class Foo; true end)
p(module Bar; true end)
p(def foo; true end)
=> -:1: void value expression
   -:2: void value expression
   -:3: void value expression
   -:4: void value expression
   -:5: void value expression
   ruby 1.7.1 (2001-08-20) [i586-linux]
=> -:1: warning: void value expression
   -:2: warning: void value expression
   -:3: warning: void value expression
   -:4: warning: void value expression
   -:5: warning: void value expression
   ruby 1.7.1 (2001-08-23) [i586-linux]
   false
   "bar"
   true
   true
   nil

修正后，while/until会在途中返回nil。 [ruby-dev:15909]

=> -:1: warning: void value expression
   -:2: warning: void value expression
   -:3: warning: void value expression
   -:4: warning: void value expression
   -:5: warning: void value expression
   ruby 1.7.2 (2002-02-20) [i586-linux]
   nil
   "bar"
   true
   true
   nil
Range#===

以前在对 字符串的范围对象 和 字符串 进行比较时，只会与范围的两段作比较。而现在则使用String#upto来和每个元素进行比较。

(2002-06-04: 之后作的修改)

p(("a" .. "ab") === "aa")
=> ruby 1.7.1 (2001-08-20) [i586-linux]
   true
=> ruby 1.7.1 (2001-08-23) [i586-linux]
   false
Enumerable#sort_by [new]

新增。这是为处理[ruby-dev:8986]之后提到的Schwartzian transform而准备的sort。

Curses [lib] [compat]

Updated. New methods and constants for using the mouse, character attributes, colors and key codes have been added.

Range#step([step=1]) [new]

新增。每隔step就使用对应元素进行迭代。

条件式中的正则表达式字面值 [parser] [change]

条件式中的正则表达式字面值 会给出警告。

在于$_ 进行正则表达式匹配时，推荐您这样显式地处理 ~/re/ (单项的 ~方法)。

$_ = "foo"
p $_ if /foo/
p $_ if /bar/

=> -:2: warning: regex literal in condition
   -:3: warning: regex literal in condition
   ruby 1.7.1 (2001-08-14) [i586-linux]
   "foo"
String#lstrip [new]
String#rstrip [new]
String#lstrip! [new]
String#rstrip! [new]

新增。去除左端或右端的空格。

Socket.pack_sockaddr_in [new]
Socket.unpack_sockaddr_in [new]

新增。套接字地址结构体(INET domain)的pack/unpack。

Socket.pack_sockaddr_un [new]
Socket.unpack_sockaddr_un [new]

新增。套接字地址结构体(UNIX domain)的pack/unpack。

String#casecmp [new]

新增。忽略字母的大小写区别来比较字符串。

String#eql? [change]

不管$=的值如何，总是区分字母的大小写。

Module#include? [new]

新增 [ruby-dev:13941]

Dir.chdir [bug]

Changed to warn only when invoked from multiple threads or no block is given. [ruby-dev:13823]

Dir.chdir("/tmp")

pwd = Dir.pwd       #=> "/tmp"
puts pwd

Dir.chdir("foo") {
  pwd = Dir.pwd     #=> "/tmp/foo"
  puts pwd

  Dir.chdir("bar") {        # <-- previously warned
    pwd = Dir.pwd   #=> "/tmp/foo/bar"
    puts pwd
  }

  pwd = Dir.pwd     #=> "/tmp/foo"
  puts pwd
}

pwd = Dir.pwd       #=> "/tmp"
puts pwd
Proc#yield

新增 [ruby-dev:13597] (之后又被删除了)

除去它不检查参数的个数之外，其他则与Proc#call 相同。

File.fnmatch [new]
File.fnmatch? [new]

新增

在File::Constants模块中定义了该方法所用的FNM_NOESCAPE, FNM_PATHNAME, FNM_PERIOD, FNM_CASEFOLD标识。

p %w(foo bar bar.bak).reject! { |fn| File::fnmatch?("*.bak", fn) }
=> ruby 1.7.1 (2001-06-12) [i586-linux]
   ["foo", "bar"]

2001-06-12

Method#== [new]

新增

2001-06-12(?)

多重赋值 [change]

修改了多重赋值的规则。变更如下。

#
*a = nil
p a
=> ruby 1.7.1 (2001-06-05) [i586-linux]
   [nil]
=> ruby 1.7.1 (2001-06-12) [i586-linux]
   []
=> ruby 1.8.0 (2003-01-18) [i586-linux]
   [nil]

但好像是又改回去了。与2003-01-07 的 eval.c的块参数比较起来，它应该是正确的。

def foo
  yield nil
  yield
end
foo {|*a| p a}

=> ruby 1.6.8 (2002-12-24) [i586-linux]
   [nil]
   [nil]
=> ruby 1.8.0 (2003-08-04) [i586-linux]
   [nil]
   []

2001-06-05(?)

展开数组 [change]

修正了下列问题。现在，只包含1元素的数组也能被正常展开。

a = *[1]
p a #=> [1]

=> ruby 1.6.8 (2002-12-24) [i586-linux]
   [1]
=> ruby 1.7.1 (2001-06-05) [i586-linux]
   1
NameError & NoMethodError [change]

重新回到NameError を StandardError 的子类中去。类的层次关系如下。

NoMethodError < NameError < StandardError.
File.open [bug]

以前，只有将第2参数指定为数值(File::RDONLY|File::CREAT等)时，才会使用第3参数。现在只要给出了第3参数，就总是有效的。[ruby-bugs-ja:PR#54]

[ruby] [compat]

由于使用了内部的哈希表，所以使用常数的速度有所提升。(对应于ChangeLog的

Tue Jun  5 16:15:58 2001  Yukihiro Matsumoto  <matz@ruby-lang.org>

部分)

语法 [parser] [change]

下列代码(请注意p后面的空格)

p ("xx"*2).to_i

不会被解生成

(p("xx"*2)).to_i

而是

p (("xx"*2).to_i)

这样(这只是试验性的修改)。

Range#to_ary

新增。现在(因为会自动进行数组变换，所以)可以写成这样。(2003-03-29: 此后Range#to_ary就被删除了)

a, b, c = 1..3
p [a, b, c]

=> ruby 1.6.8 (2002-12-24) [i586-linux]
   [1..3, nil, nil]

=> ruby 1.7.3 (2002-12-11) [i586-linux]
   [1, 2, 3]

=> ruby 1.8.0 (2003-03-29) [i586-linux]
   [1..3, nil, nil]
break and next [compat]

使用参数之后，break, next就可以返回 迭代器 或 yield 的值了。(该功能尚处于试验阶段)

break [n]会终止迭代器，n就是迭代器的返回值。 而next [n]会跳到块的外面，n就是yield的返回值。

def foo
  p yield
end

foo { next 1 }

def bar
  yield
end

p bar { break "foo" }

=> ruby 1.7.1 (2001-08-20) [i586-linux]
   1
   "foo"
to_str [compat]

定义了to_str的对象将在更大的空间内扮演String的角色。

大部分将字符串作为参数的内部方法都在尝试调用to_str进行隐式的类型变换。

foo = Object.new
class <<foo
  def to_str
    "foo"
  end
end
p File.open(foo)
=> -:7:in `open': wrong argument type Object (expected String) (TypeError)
   ruby 1.6.4 (2001-04-19) [i586-linux]
=> -:7:in `open': No such file or directory - "foo" (Errno::ENOENT)
   ruby 1.7.0 (2001-05-02) [i586-linux]
扩展库API(STR2CSTR()) [api] [new]

如果把一个包含to_str方法的非字符串对象传给扩展库的API--STR2CSTR()的话，它就会在内部调用to_str，进行隐式的类型转换。此时虽然能返回变换结果所保持的字符串指针，但在该API中，这个隐式变换的结果不会被任何对象所使用，因此可能会被GC回收掉。 [ruby-dev:12731]

version 1.7以后，使用StringValuePtr()来替代它。此时，参数所用的对象会被隐式转换结果所替代，因此变换结果不会被GC所回收。(在version 1.7中，STR2CSTR()变成obsolete)

另外还准备了一个新的API--StringValue()。在需要对参数进行to_str的隐式类型变换时使用它。若参数是字符串的话，则不进行任何操作。把它用在处理字符串的方法的前面则会相当方便。

目前还没有开发出替代str2cstr() (返回C指针和字符串长度)的安全的API。(在[ruby-dev:15644]中有一些建议)

范围操作符表达式中的字面值 [ruby] [change]

只有在使用-e选项的单行脚本中，才能对 范围操作符表达式中的单个数值字面值 和 $. 进行比较。

rescue中的异常类 和 发生的异常对象 之间的比较 [ruby] [change]

使用Module#===来比较 发生的异常$! 和 rescue中的异常类。

以前使用kind_of?来进行比较时，基本上也没有什么问题。这里主要是对SystemCallError.===进行了重定义，只要两个异常的errno相同就把它们看作是同一个异常。因此，若Errno::EWOULDBLOCK 和 Errno::EAGAIN的意义相同(相同的errno)时，不管指定哪个，都可以进行rescue。

后来，只要两个Errno::XXX对象的errno相同，就被看作是相同的异常，因此这个修改也就失效了，但却一直保留下来。 (或许在用户定义异常类时还能用到) [ruby-dev:19589]

Array#collect [bug]
Array#map [bug]

在不带块的时候，Array#collect会返回self.dup。因此可能会返回Array以外的对象[ruby-list:30480]。

Foo = Class.new Array

a = Foo.new
p a.map.class
p a.collect.class

=> ruby 1.7.1 (2001-06-12) [i586-linux]
   Array
   Foo

=> ruby 1.7.1 (2001-07-31) [i586-linux]
   Array
   Array
Array#dup

修正了dup中的bug [ruby-list:30481] (1.6 中也进行了修正)

class Foo < Array
  attr_accessor :foo
end

a = Foo.new
a.foo = 1
b = a.dup
b.foo
b.foo = 99
p b.foo

# => ruby 1.6.4 (2001-06-04) [i586-linux]
     nil

# => ruby 1.6.4 (2001-07-31) [i586-linux]
     99
Array#fetch [new]

新增

Array#insert [new]

新增 [ruby-talk:14289]

与ary[n,0] = [other,...] 相同(但却返回self)

ary = [0,1,2,3]
ary[2, 0] = [4, 5, 6]
p ary

ary = [0,1,2,3]
ary.insert(2, 4, 5, 6)
p ary
Array#pack [change]
String#unpack [change]

Array#pack, String#unpack 的模板字符"p", "P"可以对nil和NULL指针进行互换了。[ruby-dev:13017]。

p [nil].pack("p")
p "\0\0\0\0".unpack("p")

=> ruby 1.7.0 (2001-05-17) [i586-linux]
   "\000\000\000\000"
   [nil]
Array#sort! [change]

总是返回self。

不能保证将来一直如此 [ruby-dev:12506]。

Class.inherited [compat]

(注: 并非Class#inherited)

以前为了禁止类定义子类而设立该方法，(其作用是引发TypeError异常)现在由Class.new来完成这个任务，所以Class.inherited方法就被删除了。

class SubClass < Class
end

#=> -:1:in `inherited': can't make subclass of Class (TypeError)
            from -:1
    ruby 1.7.1 (2001-06-12) [i586-linux]

#=> -:1: can't make subclass of Class (TypeError)
    ruby 1.7.1 (2001-07-31) [i586-linux]
Dir.open [change]

若带块的话，则与File.open相同，块的结果成为方法的返回值。(在1.6以前，其返回值恒为nil)

Dir.chdir [compat]

可以指定块了。

Dir.glob [change]

可以使用前面的反斜线来对通配符进行转义处理。另外，空格也不再具有特殊意义('\0'依然有效)。

Enumerable#all? [new]
Enumerable#any? [new]
Enumerable#inject [new]

新增

File.lchmod [new]
File.lchown [new]

新增

IO.for_fd [new]

新增

IO.read [new]

新增。主要是实现了[ruby-talk:9460]中提到的功能

Interrupt [change]

Interrupt 成为SignalException的子类。(在1.6以前，它是Exception的子类)

Marshal::MAJOR_VERSION [new]
Marshal::MINOR_VERSION [new]

新增。Marshal输出的dump format的版本号。 [ruby-dev:14172]

MatchData#to_ary

新增 [ruby-dev:12766] (2003-03-28: 后来又消失了)

为了方便Regexp#match而设。以前必须得

foo, bar, baz = /(\w+?)\s+(\w+?)\s+(\w+)/.match("foo bar baz").to_a[1..-1]
p [foo, bar, baz]

这样才行。现在可以

_, foo, bar, baz = /(\w+?)\s+(\w+?)\s+(\w+)/.match("foo bar baz")
p [foo, bar, baz]

这样。

Math.acos [new]
Math.asin [new]
Math.atan [new]
Math.cosh [new]
Math.sinh [new]
Math.tanh [new]
Math.hypot [new]

新增

Module#included [new]

新增。在Module#append_feature后调用的hook

Module#method_removed [new]
Module#method_undefined [new]

新增

Numeric#/

新增。返回商。

(之后变成div(?))

NoMethodError [new]

新增 [ruby-dev:12763]

NotImplementError [obsolete]

旧称被删除。请使用NotImplementedError

Object#singleton_method_removed [new]
Object#singleton_method_undefined [new]

新增

Object#singleton_methods [compat]

新增了可选参数all。

class Foo
  def foo
  end
end
obj = Foo.new

module Bar
  def bar
  end
end

class <<obj
  include Bar
  def baz
  end
end
p obj.singleton_methods      #=> ["baz"]
p obj.singleton_methods true #=> ["baz", "bar"]
Process.times [change]

从Time.times移到这里。(Time.times还在，但会出现warning)

Process::Status [new]

新增。$? 的值从整数中分离出来，变成该类的实例。

Process.waitall [new]

新增

Range#include? [new]
Range#member? [new]

新增

Regexp.last_match [compat]

新增了可选参数。

Regexp#options [new]

新增

String#casecmp [new]

新增。比较字符串而不区分大小写。

String#insert [new]

新增

与str[n, 0] = other 相同(但会返回 self)

Symbol.all_symbols [new]

新增 [ruby-dev:12921]

Symbol#intern

新增(???之后又被删除)

SystemCallError.=== [new]

新增 (请参考上面的「rescue 节的...」内容) [ruby-dev:12670]

SystemExit#status [new]

新增

TCPSocket.new [compat]
TCPSocket.open [compat]

可使用第3,4参数来进行指定。

Thread#keys [new]

新增。返回Thread固有数据的键的数组。

Time [compat]

可以处理负的 time_t (仅限于OS支持的场合)

p Time.at(-1)
=> Thu Jan 01 08:59:59 JST 1970
Time#to_a [change]
Time#zone [change]

对gmtime 时区返回UTC" (以前取决于系统环境，大部分场合返回"GMT")

Originally Written by webmaster@ruby-lang.org
Translated by Community Engine China (Mail)
Last update on 2005-01-24


Ruby参考手册 - Ruby术语集

Ruby术语集

--按英文排序

A

AWK

由Alfred Aho(A)、Peter Weinberger(W)和Brian Kernighan(K)共通创建的一种小型脚本语言。

B

blade

ml archive (blade/ruby)

C

Coerce

进行数据类型变换的方法。如果某数值计算方法从参数那里获得了一个类型不明的实例时, 它会调用coerce方法来进行数据类型变换。coerce方法会返回参数中的数值和本身。

Ruby库的数值类型变换顺序为

Fixnum -> Bignum -> Rational -> Float -> Complex

D

Data

该类可将C中的指针处理成Ruby对象。由C指针、mark函数和free函数构成。如果您想使用C语言来扩充Ruby的功能的话, 就必须掌握该类的使用方法。相反地, 如果您没有这个打算的话, 它也就没什么用了。

defined?

该操作符可以检查某对象(表达式)是否已经被定义。若未被定义就返回nil,若已被定义,就以字符串形式返回它的种类。虽然defined?看似一个方法,实际上它是Ruby语法中的操作符, 它不会对参数进行计算。因此下面的表达式

defined? print("abc\n")

不会输出任何内容。

E

Eiffel

面向对象的编程语言。据说,早先matz在读了该语言作者所撰写的《Object-oriented Software Construction》之后顿觉恍然大悟。但奇怪的是Ruby与Eiffel却并不相似。如果硬要找出雷同之处的话, 可能只有两点: 1.Ruby的块以end结尾;2.Ruby也有rescue这个保留字。

end

该保留字表明了块的结束。据统计,大约有33%的人在看到这个end时会联想起Pascal(纯属杜撰)。但它并不和begin连用,因此它可能更接近于Ada或Eiffel。

Ruby之所以不使用C和Perl中的{},主要是因为以下原因

  • 避开单句·复句的问题

    例如在C语言中, 若想向下例中添加语句时

    if (a==b)
   c();

    如果写成这样的话

    if (a==b)
   c();
   d();

    就会造成难以发现的bug。即使是Pascal也存在这个问题。

  • 回避else的问题

    与上例类似, 如果写出下面这样的语句的话

    if (a==b)
   if (c==d) foo();
else bar();

    就会引起混乱。其实它的本意应该是

    if (a==b) {
   if (c==d) foo();
   else bar();
}

    这个样子。

  • 提高可读性

    可能您对此持有异议, 但有的人认为:用end来结束块的做法可以提高程序的可读性。

  • begin和case语法表达上的问题

    说句实话，matz曾多次想把end用作变量。甚至探讨过在ruby语法中添加{ }的问题，但因为无法完美地解决begin和case的语法表达的问题，最终不得不放弃这个念头。恐怕这才是最大的理由吧。

ENV

该对象的运作方式与访问环境变量的Hash相同。实际上，它就是添加了特殊方法的Object类的实例。使用该对象来修改环境变量后，其变化将会影响到Ruby的子进程。

F

FAQ
Frequently Asked Questions

常见的问题和解答。Ruby FAQ尚处于不断完善的阶段，问题和解答是随时更新的。

G

goto

Ruby中没有该语句。这并不是因为“我们觉得不应该使用”goto，而是“实现其功能实在是太麻烦了”。实际上，您可以使用catch/throw或异常来实现goto的功能。

H

I

J

JARH
Just another Ruby hacker,

K

L

M

main

顶层中的self。因为self是必不可少的，所以它只是表明在顶层中有个Object类的实例--self而已。另外为了操作Object类，还特别定义了若干特殊方法。

已定义的特殊方法

  • private
  • public
  • include
matz

Ruby的作者，也叫松本 行弘。请参考<URL:http://www.st.rim.or.jp/~fuku/cmail/>，另外，他还是3个孩子的父亲。

Mix-in
混合插入、糅合

就像在冰淇淋中混合多种配料可以做成美味的混合冰淇淋一样，在类中混合插入各种模块就可以添加相应的功能。请参考继承。

matz坚信滥用多重继承会导致继承关系的混乱，因此Ruby中不允许使用多重继承。同时为充分发挥继承功能的优势，Ruby支持两种继承关系：1.使用is-a语句的继承；2.使用Mix-in来共享并继承模块中的功能。

N

O

P

Perl

不必多说了罢

POLS

Principle of least surprise

Python

Ruby的劲敌。其功力深厚，可谓“千年蛇妖”。但matz认为Python的功能仍不完美，不然就不会创造Ruby了。最要命的是Python限定了名称长度(6个字符)。

Q

R

RAA

Ruby Application Archive(RAA)

RCR

Ruby Change Request

RD

Ruby Document

Ruby

面向对象的脚本语言。Ruby的意思是“紧跟在Perl(pearl是6月的诞生石，Ruby则是7月的诞生石)之后”。Ruby并不是其他单词的缩写。

S

Sather

面向对象的编程语言。其实matz钟爱Sather胜过Eiffel。但Ruby与Sather一点都不像。

self

表示被调的表达式。那为什么把它叫做self呢？因为如果把方法看作动词的话，那么被调就是该动作的主语，从方法的角度来看，被调当然就是自己了。一般人认为，Ruby对此并未深究，只不过是模仿Smalltalk的做法罢了。

Smalltalk

面向对象的编程语言。它奠定了现代面向对象 理论体系的基础。

super

在重定义的方法中调用上级方法。省略参数时，将使用主调方方法的参数来进行调用。

  • 问题:

    修改参数中给出的数值后再调用super的话，将会使用原来的值还是修改后的值呢？

    def foo(a)
   print a
end
def self.foo(a)
  a=25
  super
end
foo(5)    # 5 or 25??

  • 答案:

    使用修改后的值(25)

T

Thread

原为Thread of control的缩略语，意指一系列的控制流。在Ruby中，一个程序内可以同时存在若干线程。

U

undef

将方法设置为未定义状态。继承 和Mix-in的功能都是在类中添加方法，而undef则可以取消方法。但是，如果取消了类所必需的方法(被其他方法所调用的方法)的话，其后果不堪设想。

V

W

X

Y

Z

--按拼音排序

A

B

被调
Receiver

方法的执行主体。也就是方法调用表达式的`.'中的左边部分。在方法内，可以使用self来表示它。另外，您可以使用@变量名的形式来访问被调的实例变量。

变量
Variable

贴在对象上的标签。Ruby的变量包括全局变量、局部变量和实例变量。因为常数的值不能改变，所以不是变量。但它也是一种标签，因此在这一点上它与变量是相同的。

C

常数
Constant

一旦定义了就不能再改变的变量。这个定义本身似乎就有点矛盾。

重定义
Override

即指再定义。重新定义超类或include模块中已经定义的方法。使用super即可调用原来的方法。

抽象数据类型
Abstract Data Type

将数据构造和对其进行操作的过程封装在一起，就形成了抽象数据类型。对抽象数据进行操作时，必须使用封装内的操作才行。其结果是不能从外部直接使用数据构造，同时一旦内部构造发生变化也不会对外界造成不良影响。我们把这个过程叫做封装。

初始化
Initialize

使对象(或“某事物”)变为“就绪”状态。对实例进行初始化操作时，需要重定义Object#initialize方法。类方法Class#new的默认定义就是对新生成的实例执行initialize方法。传给new的参数会被原封不动地传给initialize。另外，若带块调用时，该块会被传给initialize。

因此，不必对Class#new进行重定义。

辞典
Dictionary

根据给出的条目即可查出对应的定义。它是哈希表的别名。面向对象的始作俑者Smalltalk把类似哈希表的数据构造称作“辞典”，所以时至今日仍然有一些人把哈希表称作辞典。

D

大Endian
Big Endian

美洲大陆的原住民是Indian而并非Endian，那么这个Endian的词源是什么呢？其实它出自Jonathan Swift写的《格列佛游记》。这本书中的人们因为吃鸡蛋的方法不同而分成两类，从圆头开始吃的叫大Endian，从尖头开始吃的叫小Endian。在计算机业界，该词表示CPU等排列数据的一种方式，据说网络一族的人们喜欢大Endian。请参考字节顺序。

大规模退出
Non-Local Exit

它并不是那种发生在break, next, redo, retry, return等方法的范围内的普通退出，而是一种跨度极大的退出。只要没被捕捉到，它甚至会跳出方法调用的牢笼来引发中断。Ruby的大规模退出包括由异常引起的退出和catch/throw。

大多数的异常(包括由exit所引发的SystemExit在内)都会被rescue 捕捉到。但是若该异常不值得捕捉(例：内存分配失败／解释器的bug)的话，就会放他一马。

在catch/throw中，通常会从被throw的地方起一直跳转到与throw部分具有相同标签的catch部分为止。

迭代器
Iterator

即指调用带块方法。当初为了进行迭代操作而设置了带块方法，现在它仍然常被称作迭带器。虽然可以将那些进行迭代操作的方法叫做迭代器，但如果将所有带块方法的调用过程都看作迭带器的话，势必会引起混乱。

调用带块方法

我们把那些可接受代码段(块)的方法叫做带块方法。调用带块方法就是指调用这些带块方法的过程。

在带块方法中使用yield就可以执行块的内容。

当然了，如何处理给出的块，这完全取决于方法。所以，如果硬是把块传给一个不能带块的方法的话，也不会有什么结果，而且也不会发生错误。

动态绑定
Dynamic Binding

指在运行时根据操作对象的数据类型的不同来选择合适的过程(方法)。它可以提高程序的灵活性。它是面向对象的必要条件之一。在Ruby中变量是没有类型的，因此必然可以进行动态绑定。

动态局部变量
Dynamic Local Variable

它是一种特殊的局部变量。Ruby的局部变量的作用域是固定的，因此在编译时就会生成局部变量。动态局部变量则有所不同，每次执行时才会生成变量。在块中首次被赋值的局部变量就是动态局部变量，其作用域仅限于块的内部。这主要是为了让各个Thread都能拥有自己独立的变量而设的。

对象
Object

即指物体。举个例子，“爱”可能不是对象，但“情书”却是对象。甄别某事物是否属于对象，这可能是个哲学问题。或许正因为如此，面向对象也变得扑朔迷离起来。在计算机业界有人认为对象就是指内存中的特定空间。到底何谓对象，还真是个难题。另外，请参考封装和抽象数据类型。

多态
多态, Polymorphism

根据对象的不同选择合适的操作。在Ruby中的实现方法是，根据被调的对象的不同来选择不同的方法。

  • 例

    obj = "abc"
print obj.length, "\n"          # => 3
obj = [1,2,3,4]
print obj.length, "\n"          # => 4

F

方法
Method

对对象进行的操作。操作对象(被调)以self来表示。在Ruby中，除去内部类的对象以外，通常对象的构造都是动态确定的。某对象的性质由其内部定义的方法所决定。

封装
Encapsulation

将内部结构和算法隐藏起来，以确保只有特定的过程(也叫方法)才能直接操作数据，这种隔离方法就叫做封装。请参考抽象数据类型。

在Ruby中，只有方法可以操作实例变量，因此可以说Ruby中的封装是强制性的。

G

关联数组
Associative Array

哈希表的别名。因为哈希表可以使用任意的键来引出值，这就是“关联”特性。另外，可以将哈希表看作是使用非数字形式索引的数组，这是它的“数组”特性，因此它也叫做“关联数组”。以前是使用硬件来实现关联数组(也叫关联记忆)的功能的，但是随着计算速度的攀升以及关键算法(叫做“哈希表”，它是现在的哈希表的词源)的成功研发，现在只用软件就可以实现其功能了。

H

哈希表
Hash

Ruby中的一种从键到值的映像(mapping)。也叫做关联数组或辞典。哈希表之所以得此名，是因为在实现其功能时使用了一种叫做“哈希表”的算法。哈希的意思是“切碎”，是“hashed beef”中的“hash”。

函数
Function

严格地讲，Ruby中没有函数。但那些省略被调的方法调用看来确实很像函数，而且有的方法根本不需要self或实例变量等被调信息，事实上后者已成为函数了。所以有时也就网开一面地把这样的方法叫成函数了。

通常将这种函数(式的方法)的方法可视性设成了private，这样就只能以省略被调的形式来调用它们了。这类方法中比较有代表性的是 模块函数。

环境变量
Environment Variable

父进程传给子进程的值。使用ENV就可以访问环境变量。传给子进程只是环境变量的副本，因此子进程无法通过环境变量来向父进程传递信息。这就好比老子不会听小孩的话一样。

J

继承
Inheritance

主要依赖于从祖先或亲戚那里继承的功能，而自己只做一些补充性的工作。在现实世界中，这种行为是要遭到唾弃的，但在计算机世界里这却是个很经济的做法。继承也可以指在某类中添加新功能后生成一个新的类。继承可以用is-a的关系来加以诠释。例如，如果您要生成一个“理科学生”类的话，需要首先继承描述一般学生特征的“学生”类，然后再添加“整天忙于应付实验”等特征后即可生成该类。若不存在is-a关系，而只想共享某些特性或功能时，我们推荐您使用Mix-in。

脚本
Script

脚本，特指由解释器进行处理的较短的程序。当然了，其中也不乏大作。

脚本语言
Script Language

脚本语言。

局部变量
Local Variable

只能在特定范围内使用的变量。该范围就是作用域。Ruby的作用域包括

  • 程序整体
  • 类·模块定义
  • 方法定义
  • 块

几种。只有块能访问外侧作用域的局部变量。局部变量的作用域 从该变量首次被赋值的位置起 直到该赋值位置所在的作用域结束为止。这个优先范围是静态决定的，与具体的运行情况无关。

K

块
Block

可用来构成循环或打家劫舍。

L

类方法
Class Method

就是类的方法。可分为两种：第一种是在所有的类的超类Class中定义的，且被所有的类所共享的方法；第二种是各个类所特有的特殊方法。这倒没什么问题。重要的是类方法中的self指的是类本身，这点需要牢记。

立即值
Immediate Value

实际的数值就保存在变量之中，这和引用是不同的。目前，Ruby中只有Fixnum、Symbol和nil/true/false是立即值。然而Ruby的某些版本并未将Fixnum算做立即值，这也无关紧要。在理论模型层面上，可以将所有的值都看作是对某对象的引用。

理论体系
Paradigm

类似于“思维方式”，这个词很难说得清楚。

M

面向对象

以对象为中心的理论体系。英语中的"Object-Oriented"是个形容词，可是到了日语中就变成名词了。似乎只要将对象置于思考的中心点就万事大吉了，但也要兼顾一下几点

  • 继承
  • 封装

  • 多态

    (或者动态绑定)

有人甚至把它看作是包治百病的“魔法”，但事实上世界并非如此简单。面对对象概念诞生至今已逾20个年头，它已经磨砺成为一把实用的利剑。

面向对象设计
Object-Oriented Design

以对象作为出发点的系统设计

面向对象编程
Object-Oriented Programming

以对象作为编程的中心。

面向对象分析
Object-Oriented Analysis

以对象为根本的系统分析。

模块函数
Module Function

在那些函数式的方法中，模块函数既可用作模块的方法，又可用作特殊方法。例如Math模块中的大部分方法都是模块函数。您既可以这样

Math.sqrt(2)

又可以这样

include Math
sqrt(2)

来使用它们。

N

内部类
Built-In Class

这些内部类被嵌入Ruby解释器内部，其实例的结构与普通对象有所不同。我们不建议您继承内部类。Ruby的内部类如下所示(实际上远不止这些，更多详情请参考内部类/模块/异常类)

  • Array
  • Bignum
  • Class
  • Data
  • FalseClass
  • File
  • Fixnum
  • Float
  • Hash
  • IO
  • MatchData
  • Module
  • NilClass
  • Proc
  • Regexp
  • String
  • Struct
  • Thread
  • TrueClass

P

排序
Sort

将对象依次排列。只要元素是可数的(include了Enumerable)、且已定义了顺序(定义了<=>)的话，Ruby就可以对这些元素的集合进行排序。这并不仅限于数组，也适用于其他复杂对象的集合。

破坏性的
Destructive

因为String#chop!, Array#concat这种方法会直接改变被调的状态，因而会产生“破环性的作用”。不过您不必担心，因为它们不会损坏您的计算机。

Q

全局变量
Global Variable

在程序的各个角落中都可以使用的变量。比较危险，少用为佳。

R

S

实例
Instance

即指对象。在强调对象归属于某类时，常使用实例这个词。据说有好多人因为不了解对象和实例的关系，因而搞不懂面对对象到底是怎么一回事儿。

实例变量
Instance Variable

对象所特有的变量。Ruby实例变量名前都有一个@符号，您只能在方法内部使用它。

T

特殊方法
Singleton Method

专属于某特定对象的方法。请参考方法。在下列情况下，其他对象也可以继承该特殊方法。

  • Kernel#clone时
  • 定义了子类时

若在特殊方法中重定义了原先类中的方法时，可以使用super来调用原来的方法。

特殊类
Singleton Class

只对应于某特定对象的假想类。

W

文档
Document

matz最头疼的就是写文档了。他平时总是说“源代码就是文档。连bug也写得清清楚楚”，当然了谁都不以为然。

X

小Endian
Little Endian

开始有10个人，后来越来越少。在计算机业界中，它是表示一种排列数据的形式。据说有一家大的CPU制造商很喜欢小Endian。请参考字节顺序。

Y

异常
Exception

遇到非正常情况就会引发异常。发生异常时，只要没使用begin中的rescue进行捕捉的话，它将跨越方法调用的阻拦，进而中断程序(thread)的运行。有了异常处理功能之后，我们就不必再逐一检查Ruby程序中的每个异常情况了。发生异常的地点信息被保存在$@中，而异常本身的信息被保存在$!中。

Z

再定义
Redefinition

即指重定义。

字节顺序
Byte Order

将0x01020304这4个字节数据按照1,2,3,4或是4,3,2,1的顺序排列。前者叫做大Endian，而后者叫做小Endian。人们一直在争论哪种方法更好，但至今尚无定论。

Originally Written by webmaster@ruby-lang.org
Translated by Community Engine China (Mail)
Last update on 2004-12-22


Ruby参考手册 - Ruby的运行平台

Ruby的运行平台

在各方有志之士的努力下，Ruby已经被移植到多种平台。下面，就从OS或开发环境等方面对Ruby的运行环境做一个简要介绍。

关于安装和编译问题，请参考Ruby 安装指南。

  • Unix
  • Windows(Win32)
    • Win32 native版
      • mswin32
      • MinGW (mingw, mingw32)
      • bccwin32
    • Cygwin (cygwin)
  • Mac
    • Mac OS X
  • BeOS
  • MS-DOS
    • DJGPP (djgpp)
  • OS2 (os2_emx), (emx)
  • VMS
  • human68k
  • GNU (GNU Hurd)
  • WindowsCE
    • mswince
Originally Written by webmaster@ruby-lang.org
Translated by Community Engine China (Mail)
Last update on 2004-12-29


Ruby参考手册 - pack模板字符串

pack模板字符串

下面就是Array#pack、String#unpack中所用到的模板字符的一览表。模板字符后面可以跟上表示"长度"的数字。若使用'*'来取代"长度"的话, 则表示"剩下的所有字符"之意。

长度的定义因模板字符的不同而有所差异, 大体上像

"iiii"

这样的连续字符可以写成

"i4"

这个样子。

在下面的说明中, short和long分别表示长度为2和4字节的数值(也就是通常32位机器所指的short和long的大小), 这与具体的系统无关。 若`s', `S', `l', `L'后面出现`_'或`!'(如"s!")的话, 则表示这个short或long的大小取决于具体的系统。

请注意: `i', `I' (int)的大小总是取决于系统的, 而`n', `N', `v', `V'的大小则是系统无关的(不能添加`!')。

模板字符串中的空格会被忽略。 ruby 1.7 特性: 另外,从`#'开始到换行处或者到模板字符串结尾之间的部分会被看做是注释。

在下面的说明中, 若针对某问题Array#pack和String#unpack有不同的解释时, 就使用/将两者分开, 即采用 "Array#pack的说明部分/String#unpack的说明部分" 的形式加以说明.

  • a

    ASCII字符串(塞入null字符/保留后续的null字符或空格)

    ["abc"].pack("a") => "a"
["abc"].pack("a*") => "abc"
["abc"].pack("a4") => "abc\0"

"abc\0".unpack("a4") => ["abc\0"]
"abc ".unpack("a4") => ["abc "]

  • A

    ASCII字符串(塞入空格/删除后续的null字符和空格)

    ["abc"].pack("A") => "a"
["abc"].pack("A*") => "abc"
["abc"].pack("A4") => "abc "

"abc ".unpack("A4") => ["abc"]
"abc\0".unpack("A4") => ["abc"]

  • Z

    null终点字符串(与a相同 / 删除后续的null字符)

    ["abc"].pack("Z") => "a"
["abc"].pack("Z*") => "abc"
["abc"].pack("Z4") => "abc\0"

"abc\0".unpack("Z4") => ["abc"]
"abc ".unpack("Z4") => ["abc "]

  • b

    位串(从下级位到上级位)

    "\001\002".unpack("b*") => ["1000000001000000"]
"\001\002".unpack("b3") => ["100"]

["1000000001000000"].pack("b*") => "\001\002"

  • B

    位串(从上级位到下级位)

    "\001\002".unpack("B*") => ["0000000100000010"]
"\001\002".unpack("B9") => ["000000010"]

["0000000100000010"].pack("B*") => "\001\002"

  • h

    16进制字符串(下级半字节(nibble)在先)

    "\x01\xfe".unpack("h*") => ["10ef"]
"\x01\xfe".unpack("h3") => ["10e"]

["10ef"].pack("h*") => "\001\376"

  • H

    16进制字符串(上级半字节在先)

    "\x01\xfe".unpack("H*") => ["01fe"]
"\x01\xfe".unpack("H3") => ["01f"]

["01fe"].pack("H*") => "\001\376"

  • c

    char (8bit 有符号整数)

    "\001\376".unpack("c*") => [1, -2]

[1, -2].pack("c*") => "\001\376"
[1, 254].pack("c*") => "\001\376"

  • C

    unsigned char (8bit 无符号整数)

    "\001\376".unpack("C*") => [1, 254]

[1, -2].pack("C*") => "\001\376"
[1, 254].pack("C*") => "\001\376"

  • s

    short (16bit 有符号整数, 取决于Endian) (s! 并非16bit, 它取决于short的大小)

    小Endian:

    "\001\002\376\375".unpack("s*") => [513, -514]

[513, 65022].pack("s*") => "\001\002\376\375"
[513, -514].pack("s*") => "\001\002\376\375"

    大Endian:

    "\001\002\376\375".unpack("s*") => [258, -259]

[258, 65277].pack("s*") => "\001\002\376\375"
[258, -259].pack("s*") => "\001\002\376\375"

  • S

    unsigned short (16bit 无符号整数, 取决于Endian) (S!并非16bit,它取决于short 的大小)

    小Endian:

    "\001\002\376\375".unpack("S*") => [513, 65022]

[513, 65022].pack("s*") => "\001\002\376\375"
[513, -514].pack("s*") => "\001\002\376\375"

    大Endian:

    "\001\002\376\375".unpack("S*") => [258, 65277]

[258, 65277].pack("S*") => "\001\002\376\375"
[258, -259].pack("S*") => "\001\002\376\375"

  • i

    int (有符号整数, 取决于Endian和int的大小)

    小Endian, 32bit int:

    "\001\002\003\004\377\376\375\374".unpack("i*") => [67305985, -50462977]

[67305985, 4244504319].pack("i*") => RangeError
[67305985, -50462977].pack("i*") => "\001\002\003\004\377\376\375\374"

    大Endian, 32bit int:

    "\001\002\003\004\377\376\375\374".unpack("i*") => [16909060, -66052]

[16909060, 4294901244].pack("i*") => RangeError
[16909060, -66052].pack("i*") => "\001\002\003\004\377\376\375\374"

  • I

    unsigned int (无符号整数, 取决于Endian和int的大小)

    小Endian, 32bit int:

    "\001\002\003\004\377\376\375\374".unpack("I*") => [67305985, 4244504319]

[67305985, 4244504319].pack("I*") => "\001\002\003\004\377\376\375\374"
[67305985, -50462977].pack("I*") => "\001\002\003\004\377\376\375\374"

    大Endian, 32bit int:

    "\001\002\003\004\377\376\375\374".unpack("I*") => [16909060, 4294901244]

[16909060, 4294901244].pack("I*") => "\001\002\003\004\377\376\375\374"
[16909060, -66052].pack("I*") => "\001\002\003\004\377\376\375\374"

  • l

    long (32bit 有符号整数, 取决于Endian) (l! 并非32bit, 它取决于long的大小)

    小Endian, 32bit long:

    "\001\002\003\004\377\376\375\374".unpack("l*") => [67305985, -50462977]

[67305985, 4244504319].pack("l*") => RangeError
[67305985, -50462977].pack("l*") => "\001\002\003\004\377\376\375\374"

  • L

    unsigned long (32bit 无符号整数, 取决于Endian) (L! 并非32bit, 它取决于long的大小)

    小Endian, 32bit long:

    "\001\002\003\004\377\376\375\374".unpack("L*") => [67305985, 4244504319]

[67305985, 4244504319].pack("L*") => "\001\002\003\004\377\376\375\374"
[67305985, -50462977].pack("L*") => "\001\002\003\004\377\376\375\374"

  • q

    ruby 1.7 特性: long long (有符号整数, 取决于Endian和long long 的大小) (在C中无法处理long long时, 就是64bit)

    小Endian, 64bit long long:

    "\001\002\003\004\005\006\007\010\377\376\375\374\373\372\371\370".unpack("q*")
=> [578437695752307201, -506097522914230529]

[578437695752307201, -506097522914230529].pack("q*")
=> "\001\002\003\004\005\006\a\010\377\376\375\374\373\372\371\370"
[578437695752307201, 17940646550795321087].pack("q*")
=> "\001\002\003\004\005\006\a\010\377\376\375\374\373\372\371\370"

  • Q

    ruby 1.7 特性: unsigned long long (无符号整数, 取决于Endian和 long long 的大小) (在C中无法处理long long时, 就是64bit)

    小Endian, 64bit long long:

    "\001\002\003\004\005\006\007\010\377\376\375\374\373\372\371\370".unpack("Q*")
=> [578437695752307201, 17940646550795321087]

[578437695752307201, 17940646550795321087].pack("Q*")
=> "\001\002\003\004\005\006\a\010\377\376\375\374\373\372\371\370"
[578437695752307201, -506097522914230529].pack("Q*")
=> "\001\002\003\004\005\006\a\010\377\376\375\374\373\372\371\370"

  • m

    被base64编码过的字符串。每隔60个八位组(或在结尾)添加一个换行代码。

    Base64是一种编码方法, 它只使用ASCII码中的65个字符(包括[A-Za-z0-9+/]这64字符和用来padding的'='),将3个八位组(8bits * 3 = 24bits)中的二进制代码转为4个(6bits * 4 = 24bits)可印刷的字符。具体细节请参考RFC2045。

    [""].pack("m") => ""
["\0"].pack("m") => "AA==\n"
["\0\0"].pack("m") => "AAA=\n"
["\0\0\0"].pack("m") => "AAAA\n"
["\377"].pack("m") => "/w==\n"
["\377\377"].pack("m") => "//8=\n"
["\377\377\377"].pack("m") => "////\n"

["abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"].pack("m")
=> "YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXpBQkNERUZHSElKS0xNTk9QUVJT\nVFVWV1hZWg==\n"
["abcdefghijklmnopqrstuvwxyz"].pack("m3")
=> "YWJj\nZGVm\nZ2hp\namts\nbW5v\ncHFy\nc3R1\ndnd4\neXo=\n"

"".unpack("m") => [""]
"AA==\n".unpack("m") => ["\000"]
"AA==".unpack("m") => ["\000"]

"YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXpBQkNERUZHSElKS0xNTk9QUVJT\nVFVWV1hZWg==\n".unpack("m")
=> ["abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"]
"YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXpBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWg==\n".unpack("m")
=> ["abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"]

  • M

    经过quoted-printable encoding编码的字符串

    ["a b c\td \ne"].pack("M") => "a b c\td =\n\ne=\n"

"a b c\td =\n\ne=\n".unpack("M") => ["a b c\td \ne"]

  • n

    网络字节顺序(大Endian)的unsigned short (16bit 无符号整数)

    [0,1,-1,32767,-32768,65535].pack("n*")
=> "\000\000\000\001\377\377\177\377\200\000\377\377"

"\000\000\000\001\377\377\177\377\200\000\377\377".unpack("n*")
=> [0, 1, 65535, 32767, 32768, 65535]

  • N

    网络字节顺序(大Endian)的unsigned long (32bit 无符号整数)

    [0,1,-1].pack("N*") => "\000\000\000\000\000\000\000\001\377\377\377\377"

"\000\000\000\000\000\000\000\001\377\377\377\377".unpack("N*") => [0, 1, 4294967295]

  • v

    "VAX"字节顺序(小Endian)的unsigned short (16bit 无符号整数)

    [0,1,-1,32767,-32768,65535].pack("v*")
=> "\000\000\001\000\377\377\377\177\000\200\377\377"

"\000\000\001\000\377\377\377\177\000\200\377\377".unpack("v*")
=> [0, 1, 65535, 32767, 32768, 65535]

  • V

    "VAX"字节顺序(小Endian)的unsigned long (32bit 无符号整数)

    [0,1,-1].pack("V*") => "\000\000\000\000\001\000\000\000\377\377\377\377"

"\000\000\000\000\001\000\000\000\377\377\377\377".unpack("V*") => [0, 1, 4294967295]

  • f

    单精度浮点数(取决于系统)

    IA-32 (x86) (IEEE754 单精度 小Endian):

    [1.0].pack("f") => "\000\000\200?"

    sparc (IEEE754 单精度 大Endian):

    [1.0].pack("f") => "?\200\000\000"

  • d

    双精度浮点数(取决于系统)

    IA-32 (IEEE754 双精度 小Endian):

    [1.0].pack("d") => "\000\000\000\000\000\000\360?"

    sparc (IEEE754 双精度 大Endian):

    [1.0].pack("d") => "?\360\000\000\000\000\000\000"

  • e

    小Endian的单精度浮点数(取决于系统)

    IA-32:

    [1.0].pack("e") => "\000\000\200?"

    sparc:

    [1.0].pack("e") => "\000\000\200?"

  • E

    小Endian的双精度浮点数(取决于系统)

    IA-32:

    [1.0].pack("E") => "\000\000\000\000\000\000\360?"

    sparc:

    [1.0].pack("E") => "\000\000\000\000\000\000\360?"

  • g

    大Endian的单精度浮点数(取决于系统)

    IA-32:

    [1.0].pack("g") => "?\200\000\000"

    sparc:

    [1.0].pack("g") => "?\200\000\000"

  • G

    大Endian的双精度浮点数(取决于系统)

    IA-32:

    [1.0].pack("G") => "?\360\000\000\000\000\000\000"

    sparc:

    [1.0].pack("G") => "?\360\000\000\000\000\000\000"

  • p

    指向null终点字符串的指针

    [""].pack("p") => "\310\037\034\010"
["a", "b", "c"].pack("p3") => " =\030\010\340^\030\010\360^\030\010"
[nil].pack("p") => "\000\000\000\000"

  • P

    指向结构体(定长字符串)的指针

    [nil].pack("P") => "\000\000\000\000"
["abc"].pack("P3") => "x*\024\010"

["abc"].pack("P4") => ArgumentError: too short buffer for P(3 for 4)
[""].pack("P") => ArgumentError: too short buffer for P(0 for 1)

  • u

    被uuencode编码的字符串

    [""].pack("u") => ""
["a"].pack("u") => "!80``\n"
["abc"].pack("u") => "#86)C\n"
["abcd"].pack("u") => "$86)C9```\n"
["a"*45].pack("u") => "M86%A86%A86%A86%A86%A86%A86%A86%A86%A86%A86%A86%A86%A86%A86%A\n"
["a"*46].pack("u") => "M86%A86%A86%A86%A86%A86%A86%A86%A86%A86%A86%A86%A86%A86%A86%A\n!80``\n"
["abcdefghi"].pack("u6") => "&86)C9&5F\n#9VAI\n"

  • U

    utf-8

    [0].pack("U") => "\000"
[1].pack("U") => "\001"
[0x7f].pack("U") => "\177"
[0x80].pack("U") => "\302\200"
[0x7fffffff].pack("U") => "\375\277\277\277\277\277"
[0x80000000].pack("U") => ArgumentError
[0,256,65536].pack("U3") => "\000\304\200\360\220\200\200"

"\000\304\200\360\220\200\200".unpack("U3") => [0, 256, 65536]
"\000\304\200\360\220\200\200".unpack("U") => [0]
"\000\304\200\360\220\200\200".unpack("U*") => [0, 256, 65536]

  • w

    BER压缩整数

    用7位来表现1字节, 这样就能以最少的字节数来表现任意大小的0以上的整数。各字节的最高位中除了数据的末尾以外,肯定还有个1(也就是说, 最高位可以表示数据伸展到的位置)。

    BER是Basic Encoding Rules的缩略语(BER并非只能处理整数。ASN.1的编码中也用到了它)

  • x

    读入null字节/1字节

  • X

    后退1字节

  • @

    向绝对位置移动

用例

下面是一些pack/unpack的用例。

其实有的问题并不需要使用pack, 但我们还是给了出它的例子。主要是因为pack很容易进行加密, 我们想向不愿使用pack的人提供一点新思路。

  • 将数值(字符代码)的数组变为字符串的例子

    p [82, 117, 98, 121].pack("cccc")
=> "Ruby"

p [82, 117, 98, 121].pack("c4")
=> "Ruby"

p [82, 117, 98, 121].pack("c*")
=> "Ruby"

s = ""
[82, 117, 98, 121].each {|c| s << c}
p s
=> "Ruby"

p [82, 117, 98, 121].collect {|c| sprintf "%c", c}.join
=> "Ruby"

p [82, 117, 98, 121].inject("") {|s, c| s << c}
=> "Ruby"

  • 将字符串变为数值(字符代码)的数组的例子

    p "Ruby".unpack('C*')
=> [82, 117, 98, 121]

a = []
"Ruby".each_byte {|c| a << c}
p a
=> [82, 117, 98, 121]

  • 可以用"x"来处理null字节

    p [82, 117, 98, 121].pack("ccxxcc")
=> "Ru\000\000by"

  • 可以用"x"来读取字符

    p "Ru\0\0by".unpack('ccxxcc')
=> [82, 117, 98, 121]

  • 将Hex dump变为数值数组的例子

    p "61 62 63 64 65 66".delete(' ').to_a.pack('H*').unpack('C*')
=> [97, 98, 99, 100, 101, 102]

p "61 62 63 64 65 66".split.collect {|c| c.hex}
=> [97, 98, 99, 100, 101, 102]

  • 在二进制和16进制数的pack中, 指定的长度并不是指生成的字节数, 而是指位或半字节的个数

    p [0b01010010, 0b01110101, 0b01100010, 0b01111001].pack("C4")
=> "Ruby"
p ["01010010011101010110001001111001"].pack("B32") # 8 bits * 4
=> "Ruby"

p [0x52, 0x75, 0x62, 0x79].pack("C4")
=> "Ruby"
p ["52756279"].pack("H8")  # 2 nybbles * 4
=> "Ruby"

  • 模板字符'a'的长度指定 只适用于一个字符串

    p  ["RUBY", "u", "b", "y"].pack("a4")
=> "RUBY"

p ["RUBY", "u", "b", "y"].pack("aaaa")
=> "Ruby"

p ["RUBY", "u", "b", "y"].pack("a*aaa")
=> "RUBYuby"

  • 在模板字符"a"中, 若长度不够时, 就用null字符进行填充

    p ["Ruby"].pack("a8")
=> "Ruby\000\000\000\000"

  • 小Endian和大Endian

    p [1,2].pack("s2")
=> "\000\001\000\002" # 在大Endian的系统中的输出
=> "\001\000\002\000" # 在小Endian的系统中的输出

p [1,2].pack("n2")
=> "\000\001\000\002" # 系统无关的大Endian

p [1,2].pack("v2")
=> "\001\000\002\000" # 系统无关的小Endian

  • 网络字节顺序的 signed long

    s = "\xff\xff\xff\xfe"
n = s.unpack("N")[0]
if n[31] == 1
  n = -((n ^ 0xffff_ffff) + 1)
end
p n
=> -2

  • 网络字节顺序的 signed long(第2个)

    s = "\xff\xff\xff\xfe"
p n = s.unpack("N").pack("l").unpack("l")[0]
=> -2

  • IP地址

    require 'socket'
p Socket.gethostbyname("localhost")[3].unpack("C4").join(".")
=> "127.0.0.1"

p "127.0.0.1".split(".").collect {|c| c.to_i}.pack("C4")
=> "\177\000\000\001"

  • sockaddr_in 结构体

    require 'socket'
p [Socket::AF_INET,
   Socket.getservbyname('echo'),
   127, 0, 0, 1].pack("s n C4 x8")
=> "\002\000\000\a\177\000\000\001\000\000\000\000\000\000\000\000"

    ruby 1.7 特性: 除了pack/unpack以外, 您还可以使用Socket.pack_sockaddr_in 和 Socket.unpack_sockaddr_in方法。

  • '\0'终点字符串的地址

    模板字符 "p" 和 "P"是为了处理C语言层的接口而存在的(例如ioctl)。

    p ["foo"].pack("p")
=> "8\266\021\010"

    结果字符串看起来乱七八糟, 实际上它表示的是字符串"foo\0"的地址(二进制形式)。您可以像下面这样,把它变成您熟悉的形式

    printf "%#010x\n", "8\266\021\010".unpack("L")[0]
=> 0x0811b638

    在pack的结果被GC回收之前, 地址所指的对象(在本例中是"foo\0")保证不会被GC所回收.

    您只能使用pack的结果来unpack("p")和unpack("P")。

    p ["foo"].pack("p").unpack("p")
=> ["foo"]
p "8\266\021\010".unpack("p")
=> -:1:in `unpack': no associated pointer (ArgumentError)
        from -:1

    ruby 1.7 特性: "p"和"P"被解释为NULL指针, 它负责对nil进行特殊的处理。(下面是在普通的32bit机器上的结果)

    p [nil].pack("p")        #=> "\000\000\000\000"
p "\0\0\0\0".unpack("p") #=> [nil]

  • 结构体的地址

    例如, 表示

    struct {
  int   a;
  short b;
  long  c;
} v = {1,2,3};

    的字符串是

    v = [1,2,3].pack("i!s!l!")

    (考虑到byte alignment的问题, 可能需要进行适当的padding才行)

    您可以使用

    p [v].pack("P")
=> "\300\265\021\010"

    来获得指向该结构体的地址。

Originally Written by webmaster@ruby-lang.org
Translated by Community Engine China (Mail)
Last update on 2004-12-22


Ruby参考手册 - sprintf格式

sprintf格式

Ruby的sprintf格式与C语言的sprintf(3)基本相同。但还是有些差别: 它没有针对C特有类型的修饰符,如short或long等; 它包含2进制数的指示符(%b); 它不支持sprintf的方言式的语法。

下面就对ruby的sprintf格式进行详细的说明。

sprintf格式的规格如下所示。[]中的部分是可选的。

%[指定参数$][标识符][宽度][.精度]指示符

若想输出`%'本身时, 请这样`%%'处理。

下面就分别介绍一下各元素的用法。

标识符

标识符包括`#', `+', ` '(空格), `-'和`0'这5个。

#

使用2进制、8进制、16进制的指示符(`b', `o', `x', `X')时, 会分别添加"0b", "0", "0x", "0X"前缀。

p sprintf("%#b", 10) # => "0b1010"
p sprintf("%#o", 10) # => "012"
p sprintf("%#x", 10) # => "0xa"
p sprintf("%#X", 10) # => "0XA"

对于浮点数 (`f', `e', `E', `g', `G'), 则必定在输出中添加"."。

p sprintf("%.0f", 10) # => "10"
p sprintf("%#.0f", 10) # => "10."
p sprintf("%.0e", 10) # => "1e+01"
p sprintf("%#.0e", 10) # => "1.e+01"

`g', `G'除了具有上述特性外, 还会在末尾添加多余的0。

p sprintf("%.05g", 10) # => "10"
p sprintf("%#.05g", 10) # => "10.000"
+

使输出字符串带上符号。如果是正数的话, 就会添加`+'。它只对数值指示符(`d', `i', `b', `o', `x', `X', `u', `f', `e', `E', `g', `G')起作用。另外, 如果是`b', `o', `x', `X', `u'的话, 则会为负数添加`-'。

p sprintf("%d", 1)   # => "1"
p sprintf("%+d", 1)  # => "+1"

p sprintf("%x", -1)  # => "..f"  # ".." 表示f无限延续
p sprintf("%+x", -1) # => "-1"
' '(空格)

与`+'相同, 用空格来代替正号`+'。它只对数值指示符(`d', `i', `b', `o', `x', `X', `u', `f', `e', `E', `g', `G')起作用。

p sprintf("%d", 1)   # => "1"
p sprintf("%+d", 1)  # => "+1"
p sprintf("% d", 1)  # => " 1"

p sprintf("%x", -1)  # => "..f"
p sprintf("% x", 1)  # => " 1"
p sprintf("% x", -1) # => "-1"
-

使输出内容靠左. 若尚未指定宽度的话,则不起作用。

0

当输出内容靠右时, 使用`0'而并非空格来填充多余部分。

它只对数值指示符(`d', `i', `b', `o', `x', `X', `u', `f', `g', `G')起作用(对`e', `E'无效)

p sprintf("%010d", 10)
# => "0000000010"

与`#'一起使用时, 输出情况如下。

p sprintf("%#010x", 10)  # => "0x0000000a"
p sprintf("%#010o", 10)  # => "0000000012"
p sprintf("%#010b", 10)  # => "0b00001010"

它等同于下例。

p sprintf("%#10.8x", 10) # => "0x0000000a"
p sprintf("%#10.9o", 10) # => "0000000012"
p sprintf("%#10.8b", 10) # => "0b00001010"

通常情况下, 会输出如下内容。

p sprintf("%#10x", 10)   # => "       0xa"
p sprintf("%#10o", 10)   # => "       012"
p sprintf("%#10b", 10)   # => "    0b1010"

宽度

以非0数字开头的数串负责指定宽度。宽度是指生成字符串的宽度, 它不受后文中的精度的限制。

确定宽度时, 也会考虑标识符中附加的" ", "+","-", "0b", "0", "0x", "0X"的长度。

p sprintf("%#05x", 10) # => "0x00a"

宽度是指"必要的最小宽度". 若结果字符串的宽度超过指定宽度时, 指定宽度就会失效。

若将宽度指定为`*'时, 将从参数中取得宽度值。

p sprintf("%10s", "foo")    # => "       foo"
p sprintf("%*s", 10, "foo") # => "       foo"

精度

紧跟在"."后面的数串表示精度(若只有"."的话，则为".0")。若遇到整数的指示符(`d', `i', `b', `o', `x', `X', `u')的话，精度表示数值部分的长度。

p sprintf("%10.5d", 1)  # => "     00001"
p sprintf("%#10.5x", 1) # => "   0x00001"
p sprintf("%+10.5x", 1) # => "    +00001"

若遇到浮点数的指示符(`f')的话，它表示小数部分的位数。

p sprintf("%10.5f", 1)   # => "   1.00000"
p sprintf("%10.5f", 10)  # => "  10.00000"

若遇到浮点数的指示符(`e', `E', `g', `G')的话，它表示有效位数。

p sprintf("%10.5e", 1)   # => "1.00000e+00"
p sprintf("%10.5e", 10)  # => "1.00000e+01"
p sprintf("%10.5g",  10)  # => "        10"
p sprintf("%#10.5G", 10)  # => "    10.000"

如果是字符串指示符(`s', `p')的话，将会按照精度的规定来检查参数中的字符串长度，并切除多余部分。若将宽度和精度设为同值的话，则只输出参数字符串中的符合精度规定的部分。

p sprintf("%10.2s", "foo")  # => "        fo"

p sprintf("%5.5s", "foo")     # => # => "  foo"
p sprintf("%5.5s", "foobar")  # => # => "fooba"

若将精度设为`*'的话，将从参数中提取精度的值。

p sprintf("%.5s", "foobar")    # => "fooba"
p sprintf("%.*s", 5, "foobar") # => "fooba"

指示符

指示符指出参数的类型，且是必选的。大体说来它包括：

  • 表示字符串的指示符: `c', `s', `p'
  • 表示整数的指示符: `d', `i', `u', `b', `o', `x', `X',
  • 表示浮点数的指示符: `f', `g', `e', `E', `G'

这几类。

c

将参数的数值(0×255)看作是字符代码，并输出对应的字符。若参数并非数值、String、 nil, true或false的话，将尝试用to_int方法进行变换。

此时，只有标识符`-'和"宽度"的设定是有效的。

s

输出字符串。

若参数并非String对象的话，将使用to_s方法对其进行变换。

p

ruby 1.8 特性: 输出Object#inspect的结果。

p sprintf("%s", [1, 2, 3])      # => "123"
p sprintf("%p", [1, 2, 3])      # => "[1, 2, 3]"
d
i

以10进制整数的形式输出参数中的数值。

若参数并非整数，则使用与Integer函数相同的规则将其变为整数。

u

将参数的数值看作是无符号整数，并以10进制整数的形式输出它。

p sprintf("%u", -1) # => "..4294967295"

上面的代码会输出 p ".." + 0xffff_ffff.to_s。

ruby 1.7 特性: 在version 1.7中，不会附加".."。若是'%u'的话，则将参数看作是定长整数。此时，对于负整数n来说

printf("%u", n)

与

printf("%d", n & ~(-1 << n.size*8))

是一个意思。

b
o
x
X

分别以2进制、8进制、16进制、16进制(大写字母)字符串的形式输出整数。

若使用了`#' 标识符的话，则分别在前面添加"0b", "0", "0x", "0X"。

若没有使用`+', ` ' 标识符时，将在负数的前面(若有`#' 标识符，则在"0x"等的后面)添加".."。这表示最高位字符无限延伸，它采用了2的补数形式来表现负数。

p sprintf("%#b", 10)    # => "0b1010"
p sprintf("%#o", 10)    # => "012"
p sprintf("%#x", 10)    # => "0xa"

# 对负数添加".."
p sprintf("%#b", -1)    # => "0b..1"
p sprintf("%#o", -1)    # => "0..7"
p sprintf("%#x", -1)    # => "0x..f"

p sprintf("%10x", -1)   # => "       ..f"
p sprintf("%-10x", -1)  # => "..f       "

# 若指定了"精度"的话，则不会添加".."
p sprintf("%.10x", -1)  # => "ffffffffff"
f
e
E
g
G

`f' 以小数点形式(xxx.xxx)输出数值。

`e' 以指数形式(x.xxxe+xx)输出数值。

`g' 的情况比较特殊。当指数小于-4或者超出精度范围时，它采用`e'方式进行输出。除此之外，它采用`f'方式进行输出。另外，它会删除小数部分尾部的0。

大写字母指示符(`E', `G')会将输出中的字母变为大写形式。

p sprintf("%f", 1.0) # => "1.000000"
p sprintf("%e", 1.0) # => "1.000000e+00"
p sprintf("%g", 1.0) # => "1"

p sprintf("%f", 10.1) # => "10.100000"
p sprintf("%e", 10.1) # => "1.010000e+01"
p sprintf("%g", 10.1) # => "10.1"

p sprintf("%g", 10 ** 6)  # => "1e+06"
p sprintf("%g", 10 ** -5) # => "1e-05"

精度的缺省值为6。

若遇到无限大值或NaN(Not a Number)时，输出情况如下。

p sprintf("%f",  1.0/0)  # => "inf"
p sprintf("%f", -1.0/0)  # => "-inf"
p sprintf("%f",  0.0/0)  # => "nan"

p sprintf("%E",  1.0/0)  # => "INF"
p sprintf("%E", -1.0/0)  # => "-INF"
p sprintf("%E",  0.0/0)  # => "NAN"

指定参数

这部分的利用频率最低，所以放在最后。

nth$

表示将使用第nth个参数进行格式化操作。

p sprintf("%1$d, %1$x, %1$o", 10)
=> "10, a, 12"

p sprintf("%3$d, %2$x, %1$o", 1, 2, 3)
=> "3, 2, 1"

若您不想改变参数的顺序而只想改变格式的话，也可以使用它。

case ENV['LC_TIME']
when /^ja_JP/
   fmt = "%1$d年%2$d月%3$d日"
else
   fmt = "%2$02d/%03$2d/%1$02d"
end

p sprintf(fmt, 1, 4, 22)
=> "04/22/01"

您也可以先插入"*"，然后借用参数来设定"宽度"和"精度"的值。

p sprintf("%5.2f", 1);              # => " 1.00"
p sprintf("%*.*f", 5, 2, 1);        # => " 1.00"
p sprintf("%1$*2$.*3$f", 1, 5, 2);  # => " 1.00"
Originally Written by webmaster@ruby-lang.org
Translated by Community Engine China (Mail)
Last update on 2004-12-22


Ruby参考手册 - Marshal格式

Marshal格式

2002-04-04 草稿....

  • 以4.8(对应于1.8)版的格式为蓝本

    # 截至2003-05-02为止的格式版本如下所示
p Marshal.Dump(Object.new).unpack("cc").join(".")
    => ruby 1.6.0 (2000-09-19) [i586-linux]
       "4.4"
    => ruby 1.6.1 (2000-09-27) [i586-linux]
       "4.4"
    => ruby 1.6.2 (2000-12-25) [i586-linux]
       "4.5"
    => ruby 1.6.3 (2001-03-19) [i586-linux]
       "4.5"
    => ruby 1.6.4 (2001-06-04) [i586-linux]
       "4.5"
    => ruby 1.6.5 (2001-09-19) [i586-linux]
       "4.6"
    => ruby 1.6.6 (2001-12-26) [i586-linux]
       "4.6"
    => ruby 1.6.7 (2002-03-01) [i586-linux]
       "4.6"
    => ruby 1.6.7 (2002-09-06) [i586-linux]
       "4.6"
    => ruby 1.7.3 (2002-09-06) [i586-linux]
       "4.7"
    => ruby 1.7.3 (2002-09-20) [i586-linux]
       "4.8"
    => ruby 1.8.0 (2003-08-03) [i586-linux]
       "4.8"
  • 本文兼顾了以前的版本，同时也指出了兼容性问题
  • 还提到了Ruby的Marshal中的BUG(?)
nil
true
false

分别是'0', 'T', 'F'

p Marshal.Dump(nil).unpack("x2 a*")
# => ["0"]

此时，即使设置了实例变量也无法Dump。

class NilClass
  attr_accessor :foo
end
nil.foo = 1
p nil.foo                           # => 1
p Marshal.Dump(nil).unpack("x2 a*") # => ["0"]
Fixnum

在'i'之后是表示Fixnum的数据结构。

以数值n为例，在表示数值部分的形式中(不仅限于Fixnum，在其它地方也是如此)，保存着

形式 1:

n == 0:       0
0 < n < 123:  n + 5
-124 < n < 0: n - 5

这样的数值(1 byte)。之所以加减5，是为了有别于下面的形式。

例:

p Marshal.Dump(-1).unpack("x2 a*") # => "i\372"
p Marshal.Dump(0).unpack("x2 a*")  # => "i\000"
p Marshal.Dump(1).unpack("x2 a*")  # => "i\006"
p Marshal.Dump(2).unpack("x2 a*")  # => "i\a"   ("i\007")

若数值N超出形式1的范围时，则有下面的形式。

形式 2:

| len | n1 | n2 | n3 | n4 |
 <-1-> <-     len       ->
 byte        bytes

len的值是-4 ～ -1, 1 ～ 4。这表示符号和后续的数据存在于n1 ～ n|len|。

# 举个更好的例子...
def foo(len, n1, n2 = 0, n3 = 0, n4 = 0)

    case len
    when -3;           n4 = 255
    when -2;      n3 = n4 = 255
    when -1; n2 = n3 = n4 = 255
    end

    n = (0xffffff00 | n1) &
        (0xffff00ff | n2 * 0x100) &
        (0xff00ffff | n3 * 0x10000) &
        (0x00ffffff | n4 * 0x1000000)
    # p "%x" % n
    n = -((n ^ 0xffff_ffff) + 1) if len < 0
    n
end

p Marshal.Dump(-125).unpack("x2 acC*") # => ["i", -1, 131]
p foo(-1, 131)
p Marshal.Dump(-255).unpack("x2 acC*") # => ["i", -1, 1]
p foo(-1, 1)
p Marshal.Dump(-256).unpack("x2 acC*") # => ["i", -1, 0]
p foo(-1, 0)
p Marshal.Dump(-257).unpack("x2 acC*") # => ["i", -2, 255, 254]
p foo(-2, 255, 254)
p Marshal.Dump(124).unpack("x2 acC*") # => ["i", 1, 124]
p foo(1, 124)
p Marshal.Dump(256).unpack("x2 acC*") # => ["i", 2, 0, 1]
p foo(2, 0, 1)

即使设定了实例变量，也无法Dump。

class Fixnum
  attr_accessor :foo
end
99.foo = 1
p 99.foo                           # => 1
p 999.foo                          # => nil
p Marshal.Dump(99).unpack("x2 ac") # => ["i", 104]
instance of the user class

'C': String, Regexp, Array, Hash 的子类的实例变量

| 'C' | 类名(Symbol)的 Dump | 父类的实例的 Dump |

例 1:

class Foo < String # (or Regexp, Array, Hash)
end
p Marshal.Dump(Foo.new("foo")).unpack("x2 a a c a3 aca*")
# => ["C", ":", 8, "Foo", "\"", 8, "foo"]
                          ^^^ (or '/', '[', '{')

例 2: 有实例变量(请参考instance variable)

class Foo < String # (or Regexp, Array, Hash)
  def initialize(obj)
    @foo = obj
    super(obj)
  end
end
p Marshal.Dump(Foo.new("foo")).unpack("x2 a a a c a3 aca3 caca4 aca*")
# => ["I", "C", ":", 8, "Foo", "\"", 8, "foo", 6, ":", 9, "@foo", "\"", 8, "foo"]

除此以外，将变为'o'。这是因为内部结构有所差异所致(请参考Object)

例:

class Foo
end
p Marshal.Dump(Foo.new).unpack("x2 a a c a*")
# => ["o", ":", 8, "Foo\000"]

'u'

若定义了_Dump、_load的话，就是'u'。因为无法Dump实例变量，所以必须使用_Dump/_load进行处理。

| 'u' | 类名(Symbol)的 Dump | _Dump 的结果的长度(Fixnum形式) |
| _Dump 的返回值 |

例:

class Foo
  def self._load
  end
  def _Dump(obj)
    "hogehoge"
  end
end
p Marshal.Dump(Foo.new).unpack("x2 a aca3 c a*")
# => ["u", ":", 8, "Foo", 13, "hogehoge"]

'U' ruby 1.8 特性

若定义了marshal_Dump、marshal_load的话，就是'U'。因为无法Dump实例变量，所以必须使用marshal_Dump/marshal_load来处理。

| 'U' | 类名(Symbol)的 Dump | marshal_Dump 方法的返回值的 Dump |

例:

class Foo
  def marshal_Dump
    "hogehoge"
  end
  def marshal_load(obj)
  end
end
p Marshal.Dump(Foo.new).unpack("x2 a aca3 a c a*")

# => ["U", ":", 8, "Foo", "\"", 13, "hogehoge"]
Object

'o'

| 'o' | 类名(Symbol)的 Dump | 实例变量的数量(Fixnum形式) |
| 实例变量名(Symbol) 的Dump(1) | 值(1) |
          :
          :
| 实例变量名(Symbol) 的Dump(n) | 值(n) |

例 1:

p Marshal.Dump(Object.new).unpack("x2 a a c a*")
# => ["o", ":", 11, "Object\000"]

例 2: 有实例变量

class Foo
  def initialize
    @foo = "foo"
    @bar = "bar"
  end
end
p Marshal.Dump(Foo.new).unpack("x2 a a c a3 c aca4 aca3 aca4 aca3")
# => ["o", ":", 8, "Foo", 7,
      ":", 9, "@bar", "\"", 8, "bar",
      ":", 9, "@foo", "\"", 8, "foo"]
Float

'f'

| 'f' | 数串的长度(Fixnum形式) | "%.16g" 的字符串 |

例:

p Marshal.Dump(Math::PI).unpack("x2 a c a*")
# => ["f", 22, "3.141592653589793"]

p Marshal.Dump(0.0/0).unpack("x2 a c a*")  # => ["f", 8, "nan"]
p Marshal.Dump(1.0/0).unpack("x2 a c a*")  # => ["f", 8, "inf"]
p Marshal.Dump(-1.0/0).unpack("x2 a c a*") # => ["f", 9, "-inf"]
p Marshal.Dump(-0.0).unpack("x2 a c a*")   # => ["f", 9, "-0"]
Bignum

'l'

| 'l' | '+'/'-' | short的个数(Fixnum形式) | ... |

例:

p Marshal.Dump(2**32).unpack("x2 a a c a*")
# => ["l", "+", 8, "\000\000\000\000\001\000"]

# => ["l", "+", 8, "\000\000\001\000"]  <- BUG: ruby version 1.6.3
String

'"'

| '"' | 长度(Fixnum形式) | 字符串 |

例:

p Marshal.Dump("hogehoge").unpack("x2 a c a*")
# => ["\"", 13, "hogehoge"]
Regexp

'/'

| '/' | 长度(Fixnum形式) | source字符串 | 选项 |

选项是 options的结果+汉字代码的flag值。

例:

p Marshal.Dump(/(hoge)*/).unpack("x2 a c a7 c")
# => ["/", 12, "(hoge)*", 0]

p Marshal.Dump(/hogehoge/m).unpack("x2 a c a8 c")
# => ["/", 13, "hogehoge", 4]

p Marshal.Dump(/hogehoge/e).unpack("x2 a c a8 c")

# => ["/", 13, "hogehoge", 32]
Array

'['

| '[' | 元素数(Fixnum形式) | 元素的 Dump | ... |

例:

p Marshal.Dump(["hogehoge", /hogehoge/]).unpack("x2 a c aca8 aca*")
# => ["[", 7, "\"", 13, "hogehoge", "/", 13, "hogehoge\000"]
Hash

'{'

| '{' | 元素数(Fixnum形式) | 键的 Dump | 值的 Dump | ... |

例:

p Marshal.Dump({"hogehoge", /hogehoge/}).unpack("x2 a c aca8 aca*")
# => ["{", 6, "\"", 13, "hogehoge", "/", 13, "hogehoge\000"]
Hash with default value ( not Proc )

'}'

| '}' | 元素数(Fixnum形式) | 键的 Dump | 值的 Dump | ... | 默认值 |

例:

h = Hash.new(true)
h["foo"] = "bar"
p Marshal.Dump(h).unpack("x2 a c aca3 aca*")
# => ["}", 6, "\"", 8, "foo", "\"", 8, "barT"]

若某Hash的默认对象是Proc的话，则无法Dump该Hash

h = Hash.new { }
Marshal.Dump(h)
=> -:2:in `Dump': cannot Dump hash with default proc (TypeError)
Struct

'S': 结构体类的实例的Dump

| 'S' | 类名(Symbol) 的 Dump | 成员数量(Fixnum形式) |
| 成员名(Symbol) 的 Dump | 值 | ... |

例:

Struct.new("XXX", :foo, :bar)
p Marshal.Dump(Struct::XXX.new).unpack("x2 a ac a11 c aca3a aca3a")
# => ["S", ":", 16, "Struct::XXX", 7,
      ":", 8, "foo", "0",
      ":", 8, "bar", "0"]
Class/Module (old format)

'M'

| 'M' | 长度(Fixnum形式) | 模块/类名 |

例: 因为已经无法dump这种形式，所以使用load进行说明。

class Mod
end
p Marshal.load([4,7, 'M', 3+5, 'Mod'].pack("ccaca*"))
# => Mod
Class/Module

'c', 'm'

| 'c'/'m' | 类名的长度(Fixnum 形式) | 类名 |

例:

class Foo
end
p Marshal.Dump(Foo).unpack("x2 a c a*") # => ["c", 8, "Foo"]

例 2: 无法dump类/模块的实例变量

module Bar
  @bar = 1
end
p Bar.instance_eval { @bar }
Marshal.Dump(Bar, open("/tmp/foo", "w"))
# => 1

module Bar
end
p bar = Marshal.load(open("/tmp/foo"))
p bar.instance_eval { @bar }
# => nil

例 3: 无法dump类变量

module Baz
  @@baz = 1
  def self.baz
    @@baz
  end
end
p Baz.baz
Marshal.Dump(Baz, open("/tmp/foo", "w"))
# => 1

module Baz
  def self.baz
    @@baz
  end
end
p baz = Marshal.load(open("/tmp/foo"))
baz.baz
# => Baz
     -:3:in `baz': uninitialized class variable @@baz in Baz (NameError)
             from -:7
Symbol

':'

| ':' | 符号名的长度(Fixnum形式) | 符号名 |

例:

p Marshal.Dump(:foo).unpack("x2 a c a*")
# => [":", 8, "foo"]
Symbol (link)

';'

| ';' | 表明Symbol实际状态的号码(Fixnum形式) |

在相应符号名已被dump/load时使用。该号码是内部管理的号码。(在dump/load时，会生成哈希表以便对Symbol进行管理。它表示记录位置)

例:

p Marshal.Dump([:foo, :foo]).unpack("x2 ac aca3 aC*")
# => ["[", 7, ":", 8, "foo", ";", 0]

p Marshal.Dump([:foo, :foo, :bar, :bar]).
    unpack("x2 ac aca3 aC aca3 aC*")
# => ["[", 9, ":", 8, "foo", ";", 0, ":", 8, "bar", ";", 6]
instance variable

'I': Object, Class, Module 的实例以外的对象

| 'I' | 对象的 Dump | 实例变量的数量(Fixnum形式) |
| 实例变量名(Symbol) 的Dump(1) | 值(1) |
          :
          :
| 实例变量名(Symbol) 的Dump(n) | 值(n) |

因为Object的实例中包含实例变量，所以会采用其他的形式进行Dump(请参考Object)。该形式只针对Array 或 String 的实例。

例:

obj = String.new
obj.instance_eval { @foo = "bar" }
p Marshal.Dump(obj).unpack("x2 a ac c a c a4 aca*")
# => ["I", "\"", 0, 6, ":", 9, "@foo", "\"", 8, "bar"]

类或模块(Class/Module的实例)不会dump实例变量的信息。(请参考Class/Module)

link

'@'

| '@' | 表明对象实际状态的号码(Fixnum形式 |

在相应对象已被dump/load时使用。该号码是内部管理的号码。(在dump/load时，会生成哈希表以便对对象进行管理。它表示记录位置)

例:

obj = Object.new
p Marshal.Dump([obj, obj]).unpack("x2 ac aaca6c aca*")
# => ["[", 7, "o", ":", 11, "Object", 0, "@", 6, ""]

ary = []
ary.push ary
p Marshal.Dump(ary).unpack("x2 acac")

# => ["[", 6, "@", 0]

Marshal 的BUG

在ruby version 1.6中发现了下列BUG。括号()中列出的是正确的运作方式(1.7的运作方式)。

<= 1.6.7
  • 类的clone中的实例是可以Dump的，但却不能加载(因为是无名类的对象，所以无法Dump)
  • 当某对象通过include/extend无名Module而定义了特殊方法后，仍可以Dump/加载该对象(若某对象include了无名模块的话，则不能Dump该对象)
1.6.6, 1.6.7
  • 拥有实例变量的Array和String是可以Dump的，但却不能加载(既能Dump，又能加载)
<= 1.6.5
  • 类的clone中的实例是可以Dump的，但却不能正常加载。否则就会生成奇怪的对象(?)
  • 特殊类被dump成为普通类了(特殊类是无法Dump的)
  • 无名类是可以Dump的，但却不能加载(无名类是无法Dump的)
<= 1.6.4
  • 模块可以Dump却不能加载(可以加载)
  • 无名模块可以Dump却不能加载(无名模块是不能Dump的)
<= 1.6.3
  • dump Float时，其保存精度偏低
<= 1.6.2
  • dump时，无法保存正则表达式中/m, /x 选项的状态
1.6.2, 1.6.3
  • 在1.6.2, 1.6.3中，Bignum可以Dump却不能加载。按理说其他版本也会有这个BUG，但因为没有测试脚本，所以无法证实。
<= 1.6.1
  • dump时无法保存Range中的特定标识，该标识表明该范围中是否包含终点

下面就是测试脚本(请参考[RAA:RubyUnit])

# test for Marshal for ruby version 1.6
require 'rubyunit'

$version_dependent_behavior = true
# for test_userClass, test_userModule
module UserModule
  def foo
  end
end
class UserClass
  def foo
  end
end

class TestMarshal < RUNIT::TestCase

  def assert_no_Dumpable(obj)
    ex = assert_exception(TypeError) {
      begin
        # Marshal.Dump will cause TypeError or ArgumentError
        Marshal.Dump obj
      rescue ArgumentError
        case $!.message
        when /can't Dump anonymous/,
             /cannot Dump hash with default proc/
          raise TypeError
        else
          raise "unknown error"
        end
      end
    }
  end
  def assert_Dumpable_but_not_equal(obj)
    obj2 = Marshal.load(Marshal.Dump(obj))
    assert(obj != obj2)
    assert_equals(obj.type, obj2.type)
  end
  def assert_Dumpable_and_equal(obj)
    obj2 = Marshal.load(Marshal.Dump(obj))
    assert_equals(obj, obj2)
    assert_equals(obj.type, obj2.type)

    # check values of instance variable
    ivars = obj.instance_variables
    ivars2 = obj2.instance_variables
    assert_equals(ivars, ivars2)
    while ivars.size != 0
      assert_equals(obj.instance_eval(ivars.shift),
                    obj2.instance_eval(ivars2.shift))
    end
  end

  def test_Object
    assert_Dumpable_but_not_equal Object.new
  end

  # object with singleton method
  def test_Object_with_singleton_method
    obj = Object.new
    # On ruby version 1.6.0 - 1.6.2, cause parse error (nested method)
    class <<obj
      def foo
      end
    end

    # object has singleton method can't be Dumped
    assert_no_Dumpable obj
  end

  # object with singleton method (with named module)
  def test_Object_with_singleton_method2
    obj = Object.new
    # On ruby version 1.6.0 - 1.6.2, cause parse error (nested method)
    class <<obj
      include UserModule
    end

    # On ruby version 1.6.0 - 1.6.7, no consider the singleton
    # method with Mix-in.
    # On ruby version 1.7, Dumpable object which is extended by
    # named module.
    assert_Dumpable_but_not_equal obj
  end

  # object with singleton method (with anonymous module)
  def test_Object_with_singleton_method3
    obj = Object.new
    # On ruby version 1.6.0 - 1.6.2, cause parse error (nested method)
    class <<obj
      include Module.new
    end

    if $version_dependent_behavior and RUBY_VERSION <= "1.6.7"
      # On ruby version 1.6.0 - 1.6.7, no consider the singleton method with Mix-in.
      assert_Dumpable_but_not_equal obj
    else
      # object has singleton method (with anonymous module) can't be Dumped
      assert_no_Dumpable obj
    end
  end

  # singleton class
  def test_singletonClass
    obj = Object.new
    # On ruby version 1.6.0 - 1.6.2, cause parse error (nested method)
    singleton_class = class <<obj
                        def foo
                        end
                        self
                      end

    # singleton class can't be Dumped
    # On ruby version 1.6.0 - 1.6.5, singleton class be able to Dumped
    # as normal class.
    if $version_dependent_behavior and RUBY_VERSION <= "1.6.5"
      assert_equals(Object, Marshal.load(Marshal.Dump(singleton_class)))
    else
      assert_no_Dumpable singleton_class
    end
  end

  def test_Array
    assert_Dumpable_and_equal [1,"foo", :foo]
  end

  def test_Array_with_instance_variable
    ary = [1,"foo", :foo]
    ary.instance_eval{ @var = 1 }

    if $version_dependent_behavior and %w(1.6.6 1.6.7).member?(RUBY_VERSION)
      # On ruby version 1.6.6 - 1.6.7, Array(or String ...) has instance
      # variable is able to be Dumped, but can't load it.
      Dump = Marshal.Dump(ary)
      ex = assert_exception(ArgumentError) {
        Marshal.load(Dump)
      }
    else
      assert_Dumpable_and_equal ary
    end
  end

  def test_Binding
    assert_no_Dumpable binding
  end

  def test_Continuation
    assert_no_Dumpable callcc {|c| c}
  end

  def test_Data
    # assert_fail("")
  end

  def test_Exception
    assert_Dumpable_but_not_equal Exception.new("hoge")
  end

  def test_Dir
    assert_no_Dumpable Dir.open("/")
  end

  def test_FalseClass
    assert_Dumpable_and_equal false
  end

  def test_File__Stat
    assert_no_Dumpable File.stat("/")
  end

  def test_Hash
    assert_Dumpable_and_equal(1=>"1",2=>"2")

    # 1.7 feature.
    if $version_dependent_behavior and RUBY_VERSION >= '1.7.0'
      # On ruby version 1.7, hash with default Proc cannot be Dumped.
      # see [ruby-dev:15417]
      assert_no_Dumpable(Hash.new { })
    end
  end

  def test_IO
    assert_no_Dumpable IO.new(0)
  end

  def test_File
    assert_no_Dumpable File.open("/")
  end

  def test_MatchData
    assert_no_Dumpable(/foo/ =~ "foo" && $~)
  end

  def test_Method
    assert_no_Dumpable Object.method(:method)
  end

  def test_UnboundMethod
    assert_no_Dumpable Object.instance_method(:id)
  end

  def test_Module
    # On ruby version 1.6.0 - 1.6.4, loaded module is not a module.
    if $version_dependent_behavior and RUBY_VERSION <= '1.6.4'
      Dump = Marshal.Dump Enumerable
      ex = assert_exception(TypeError) {
        Marshal.load Dump
      }
      assert_matches(ex.message, /is not a module/)
    else
      assert_Dumpable_and_equal Enumerable
    end
  end

  def test_userModule
    # On ruby version 1.6.0 - 1.6.4, loaded module is not a module.
    if $version_dependent_behavior and RUBY_VERSION <= '1.6.4'
      # same as test_Module
    else
      # Note: this module must be defineed for Marshal.load.
      assert_Dumpable_and_equal(UserModule)
    end
  end

  def test_anonymousModule
    # On ruby version 1.6.0 - 1.6.4, anonymous class is able to be Dumped,
    # but loaded object is not identical.
    if $version_dependent_behavior and RUBY_VERSION <= '1.6.4'
      Dump = Marshal.Dump(Module.new)
      ex = assert_exception(ArgumentError) {
        Marshal.load Dump
      }
      assert_matches(ex.message, /can\'t retrieve anonymous class/)
    else
      assert_no_Dumpable Module.new
    end
  end

  def test_Class
    assert_Dumpable_and_equal Class
  end

  def test_userClass
    # Note: this class must be defineed for Marshal.load.
    assert_Dumpable_and_equal(UserClass)
  end
  def test_anonymousClass
    # On ruby version 1.6.0 - 1.6.5, anonymous class able to be Dumped,
    # but can't load it.
    if $version_dependent_behavior and RUBY_VERSION <= '1.6.5'
      Dump = Marshal.Dump(Class.new)
      ex = assert_exception(ArgumentError) {
        Marshal.load(Dump)
      }
      assert_matches(ex.message, /can\'t retrieve anonymous class/)
    else
      assert_no_Dumpable Class.new
    end
  end

  def test_clonedClass
    # On ruby version 1.6.0 - 1.6.7, instance of cloned class is able to
    # Dumped, but loaded object is not identical.
    # see [ruby-dev:14961]
    if $version_dependent_behavior
      if RUBY_VERSION <= '1.6.5'
        obj = String.clone.new("foo")
        Dump = Marshal.Dump(obj)
        obj2 = Marshal.load Dump
        assert(obj == obj2)
        assert(obj.type != obj2.type)
        assert(obj.type.inspect == obj2.type.inspect)
      elsif RUBY_VERSION <= '1.6.7'
        Dump = Marshal.Dump(String.clone.new("foo"))
        assert_exception(ArgumentError) {
          Marshal.load Dump
        }
      else
        assert_no_Dumpable String.clone.new("foo")
      end
    else
      # anonymous class can't be Dumped
      assert_no_Dumpable String.clone.new("foo")
    end
  end

  def test_Numeric
    # assert_fail("")
  end

  def test_Integer
    # assert_fail("")
  end

  def test_Fixnum
    assert_Dumpable_and_equal 100
  end

  def test_Bignum
    # derived from Rubicon
    assert_Dumpable_and_equal 123456789012345678901234567890
    assert_Dumpable_and_equal -123**99
    if $version_dependent_behavior and %w(1.6.2 1.6.3).member?(RUBY_VERSION)
      Dump = Marshal.Dump 2**32
      ex = assert_exception(ArgumentError) {
        Marshal.load(Dump)
      }
      assert_matches(ex.message, /marshal data too short/)
    else
      assert_Dumpable_and_equal 2**32
    end
  end

  def test_Float
    assert_Dumpable_and_equal 1.41421356

    # On ruby version 1.6.4, Dumped format changed from "%.12g" to "%.16g"
    if $version_dependent_behavior and RUBY_VERSION <= '1.6.3'
      assert_Dumpable_but_not_equal Math::PI
    else
      assert_Dumpable_and_equal Math::PI
    end
  end

  def test_Proc
    assert_no_Dumpable proc { }
  end

  def test_Process__Status
    assert_Dumpable_and_equal system("true") && $?
  end

  def test_Range
    # Range#== is changed from 1.6.2
    # On ruby version 1.6.0 - 1.6.1, Range.new(1,2) != Range.new(1,2)
    # assert_Dumpable_and_equal 1..2
    # assert_Dumpable_and_equal 1...2

    obj = Marshal.load(Marshal.Dump 1..2)
    assert_equals(1, obj.begin)
    assert_equals(2, obj.end)
    assert_equals(false, obj.exclude_end?)

    obj = Marshal.load(Marshal.Dump 1...2)
    assert_equals(1, obj.begin)
    assert_equals(2, obj.end)

    # On ruby version 1.6.0 - 1.6.1, the attribute exclude_end? is not saved.
    if $version_dependent_behavior and RUBY_VERSION <= '1.6.1'
      assert_equals(false, obj.exclude_end?)
    else
      assert_equals(true, obj.exclude_end?)
    end
  end

  def test_Regexp
    # this test is no consider the /foo/p
    assert_Dumpable_and_equal /foo/
    assert_Dumpable_and_equal /foo/i
    assert_Dumpable_and_equal /foo/m
    assert_Dumpable_and_equal /foo/x
    assert_Dumpable_and_equal /foo/e
    assert_Dumpable_and_equal /foo/s
    assert_Dumpable_and_equal /foo/u

    # On ruby version 1.6.0 - 1.6.2, Regexp#== is ignore the option.
    for obj in [/foo/, /foo/i, /foo/m, /foo/x, /foo/e, /foo/s, /foo/u]
      obj2 = Marshal.load(Marshal.Dump obj)

      if $version_dependent_behavior and RUBY_VERSION <= '1.6.2' and
          %w(/foo/m /foo/x).member?(obj.inspect)
        # On ruby version 1.6.0 - 1.6.2,
        # //m options is not saved.
        assert_equals('/foo/', obj2.inspect)
      else
        assert_equals(obj.inspect, obj2.inspect)
      end
    end
  end

  def test_String
    assert_Dumpable_and_equal "foo"
  end

  def test_Struct
    assert_Dumpable_and_equal Struct.new("Foo", :foo, :bar)

    Object.const_set('Foo', Struct.new(:foo, :bar))
    assert_Dumpable_and_equal Foo
  end

  def test_aStruct
    assert_Dumpable_and_equal Struct.new("Bar", :foo, :bar).new("foo", "bar")

    # see [ruby-dev:14961]
  end

  def test_Symbol
    assert_Dumpable_and_equal :foo
  end

  def test_Thread
    assert_no_Dumpable Thread.new { sleep }
  end

  def test_ThreadGroup
    assert_no_Dumpable ThreadGroup::Default
  end

  def test_Time
    assert_Dumpable_and_equal Time.now
    assert_Dumpable_and_equal Time.now.gmtime

    # time zone is not saved.
    assert_equals(false, Marshal.load(Marshal.Dump(Time.now)).utc?)
    assert_equals(false, Marshal.load(Marshal.Dump(Time.now.gmtime)).utc?)
  end

  def test_TrueClass
    assert_Dumpable_and_equal true
  end

  def test_NilClass
    assert_Dumpable_and_equal nil
  end
end


Ruby参考手册 - Ruby FAQ

Ruby FAQ

  1. 一般的问题
  2. 变量、常数、参数
  3. 调用带块方法（迭代器）
  4. 句法
  5. 方法
  6. 类、模块
  7. 内部库
  8. 扩展库
  9. 尚未列出的功能
  10. 日语字符的处理
  11. Ruby的处理系统

一般的问题

  • 1.1 Ruby是什么？
  • 1.2 为什么取名叫Ruby呢？
  • 1.3 请介绍一下Ruby的诞生过程
  • 1.4 哪里有Ruby的安装文件？
  • 1.5 请问Ruby的主页在哪里？
  • 1.6 请问有Ruby邮件列表吗？
  • 1.7 怎么才能看到邮件列表中的老邮件？
  • 1.8 rubyist和ruby hacker的区别是什么？
  • 1.9 它的正确写法是"Ruby"还是"ruby"？
  • 1.10 请介绍一些Ruby的参考书
  • 1.11 我看了手册可还是不明白，该怎么办？
  • 1.12 ruby的性格比较像羊？
  • 1.13 遇到bug时怎么上报？

变量、常数、参数

  • 2.1 将对象赋值给变量或常数时,会先拷贝该对象吗?
  • 2.2 局部变量的作用域是如何划定的?
  • 2.3 何时才能使用局部变量?
  • 2.4 常数的作用域是如何划定的?
  • 2.5 实参是怎么传递给形参的呢?
  • 2.6 将实参赋值给形参之后,对实参本身有什么影响吗?
  • 2.7 若向形参所指对象发送消息的话,可能出现什么结果?
  • 2.8 参数前面的*是什么意思?
  • 2.9 参数前面的&代表什么?
  • 2.10 可以给形参指定默认值吗?
  • 2.11 如何向块传递参数呢?
  • 2.12 为什么变量和常数的值会自己发生变化?
  • 2.13 常数不能被修改吗?

调用带块方法

  • 3.1 什么是"带块的方法调用"?
  • 3.2 怎么将块传递给带块方法?
  • 3.3 如何在主调方法中使用块?
  • 3.4 为什么Proc.new没有生成过程对象呢?

句法

  • 4.1 像:exit这种带:的标识符表示什么？
  • 4.2 如何取得与符号同名的变量的值？
  • 4.3 loop是控制结构吗？
  • 4.4 a +b报错，这是怎么回事儿？
  • 4.5 s = "x"; puts s *10 报错，这是怎么回事儿？
  • 4.6 为什么p {}没有任何显示呢？
  • 4.7 明明有pos=()这样的setter方法，可为什么pos=1时却没有任何反应呢？
  • 4.8 '\1'和'\\1'有什么不同？
  • 4.9 在p true or true and false中会显示true，但在a=true if true or true and false中却不会把true赋值给a。
  • 4.10 为什么p(nil || "")什么事儿都没有，可p(nil or "")却会报错呢？

方法

  • 5.1 向对象发出消息之后，将按照什么顺序来搜索要执行的方法？
  • 5.2 +和-是操作符吗？
  • 5.3 Ruby中有函数吗？
  • 5.4可以在外部使用对象的实例变量吗？
  • 5.5 private和protected有什么不同？
  • 5.6 能不能将实例变量变成public类型的变量？
  • 5.7 怎样指定方法的可见性？
  • 5.8 方法名可以用大写字母开头吗？
  • 5.9 为什么使用super时会出现ArgumentError?
  • 5.10 如何调用上2层的同名方法？
  • 5.11 重定义内部函数时，如何调用原来的函数？
  • 5.12 何谓破环性的方法？
  • 5.13 那些情况下会产生副作用？
  • 5.14 能让方法返回多个值吗？

类、模块

  • 6.1 重定义类时，是否会覆盖原来的定义？
  • 6.2 有类变量吗？
  • 6.3 什么是类的实例变量？
  • 6.4 什么是特殊方法？
  • 6.5 什么是类方法？
  • 6.6 什么是特殊类？
  • 6.7 什么是模块函数？
  • 6.8 类和模块有什么区别？
  • 6.9 模块可以生成子类吗？
  • 6.10 在类定义中定义类方法 和 在顶层中定义类方法 之间有什么不同？
  • 6.11 load和require有什么不同？
  • 6.12 include和extend有什么不同？
  • 6.13 self是什么？
  • 6.14 MatchData中的begin、end分别返回什么？
  • 6.15 如何使用类名来获得类？

内部库

  • 7.1 instance_methods(true)返回什么?
  • 7.2 为什么rand总是生成相同的随机数?
  • 7.3 怎样从0到51中选出5个不重复的随机数呢?
  • 7.4 Fixnum、Symbol、true、nil和false这些立即值与引用有什么不同?
  • 7.5 nil和false有什么不同?
  • 7.6 为什么读入文件并修改之后, 原文件依然没有变化?
  • 7.7 怎样覆盖同名文件?
  • 7.8 写文件后拷贝该文件,但所得副本并不完整,请问原因何在?
  • 7.9 在管道中将字符串传给less后, 为什么看不到结果?
  • 7.10 无法引用的File对象将会何去何从?
  • 7.11 怎样手动关闭文件?
  • 7.12 如何按照更新时间的新旧顺序来排列文件?
  • 7.13 如何获取文件中单词的出现频度?
  • 7.14 为什么条件表达式中的空字符串表示true呢?
  • 7.15 如何按照字典顺序来排列英文字符串数组?
  • 7.16 "abcd"[0]会返回什么?
  • 7.17 怎么把tab变成space?
  • 7.18 如何对反斜线进行转义操作?
  • 7.19 sub和sub!的区别在哪里?
  • 7.20 \Z匹配什么？
  • 7.21 范围对象中的..和...有什么不同?
  • 7.22 有函数指针吗?
  • 7.23 线程和进程fork有何异同？
  • 7.24 如何使用Marshal？
  • 7.25 Ruby有异常处理语句吗?
  • 7.26 如何使用trap？
  • 7.27 如何统计文件的行数？
  • 7.28 怎样把数组转化为哈希表？
  • 7.29 将字符串变为Array时可以使用%w(...)，那么将字符串变为Hash时能不能如法炮制呢？
  • 7.30 为何无法捕捉NameError异常呢？
  • 7.31 为什么有succ却没有prev呢

扩展库

  • 8.1 如何使用交互式Ruby？
  • 8.2 有调试器吗？
  • 8.3 怎样在Ruby中使用以C写成的库？
  • 8.4 有Tcl/Tk的接口吗？
  • 8.5 为什么我的Tk不管用？
  • 8.6 有gtk+、xforms的接口吗？
  • 8.7 进行日期计算时需要注意哪些问题？

尚未列出的功能

日语字符的处理

  • 10.1 若包含汉字的脚本输出乱码或无法正常运行时，该如何处理？
  • 10.2 选项-K和$KCODE有什么不同？
  • 10.3 可以使用日语标识符吗？
  • 10.4 如何从包含日语字符的字符串中依次抽出1个字符？
  • 10.5 tr("あ","a")运作不正常，应如何处置？
  • 10.6 如何对平假名进行排序？
  • 10.7 如何用空白来替代SJIS中从84BF到889F之间的系统相关代码？
  • 10.8 如何进行全角－半角字符的变换？
  • 10.9 关于半角假名的问题
  • 10.10 怎样从包含日语字符的字符串中抽出n字节的内容？
  • 10.11 怎么让日语文本在第n个字处换行？

Ruby的处理系统

  • 11.1 能不能编译Ruby脚本呢？
  • 11.2 有没有Java VM版的Ruby？
  • 11.3 除了original Ruby之外，就没有其他版本吗？
  • 11.4 有没有Ruby用的indent？
  • 11.5 有没有使用本地线程的Ruby？
  • 11.6 GC实在是太慢了，怎么办才好？
  • 11.7 有没有Mac版的Ruby？
Originally Written by webmaster@ruby-lang.org
Translated by Community Engine China (Mail)
Last update on 2004-12-08


Ruby参考手册 - Ruby的陷阱

Ruby的陷阱

语法

  • trap::()的功能
  • trap::有无空格的差异
  • trap::\的影响
  • trap::作用域、控制结构
  • trap::类型变换
  • trap::破坏性的方法
  • trap::带块的方法调用
  • trap::参数
  • trap::布尔值
  • trap::隐含的$_

内部类

  • trap::Array
  • trap::Dir
  • trap::File
  • trap::Hash
  • trap::IO
  • trap::Numeric
  • trap::Process
  • trap::Regexp
  • trap::String
  • trap::Thread
  • trap::Time
  • trap::NilClass

库

  • trap::timeout
  • trap::Socket
  • trap::matrix
Originally Written by webmaster@ruby-lang.org
Translated by Community Engine China (Mail)
Last update on 2004-12-22