表达式(Expressions)
;-)
。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 # ... endSidebar:在类中使用访问方法( 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" endRuby也未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 keywordcase
) with each of the comparison expressions after thewhen
keywords. This test is done usingcomparison===
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.
===
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" endRuby 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 fifthThe 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/ endproduces:
first second third eighth ninth
这里有一点需要注意,当while和until用作语句修饰符的时候,如果它们修饰的语句以begin开头,end结尾,这段代码将总会执行,而不管后面的条件。
print "Hello\n" while false begin print "Goodbye\n" end while falseproduces:
Goodbye
迭代Iterators
上面我们知道了,Ruby支持简单的循环,比如,Ruby没有for循环,而c和JAV等都支持for循环的,但是Ruby提供了其他的机制,比如迭代,提供了类似的功能。
让我们看看一个例子:
3.times do print "Ho! " endproduces:
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, " " endproduces:
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 endproduces:
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, " " endproduces:
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, " " endproduces:
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 endproduces:
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]