操作符表达式
* 赋值
o 计算赋值
o 多重赋值
* 范围表达式
* 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
完全相同。
控制结构
* 条件分支
o if
o if 修饰句
o unless
o unless 修饰句
o case
* 循环
o while
o while 修饰句
o until
o until修饰句
o for
o break
o next
o redo
o retry
* 异常处理
o raise
o begin
o rescue修饰句
* 其他
o return
o BEGIN
o 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"
方法调用
* 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
这样的多重赋值还不会有警告,但最好不要使用。
类/方法的定义
* 类定义
* 特殊类定义
* 模块定义
* 方法定义
o 方法定义的嵌套
o 方法的计算
* 特殊方法定义
* 类方法的定义
* 调用限制
* 与定义有关的操作
o alias
o undef
o 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"
* 赋值
o 计算赋值
o 多重赋值
* 范围表达式
* 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
完全相同。
控制结构
* 条件分支
o if
o if 修饰句
o unless
o unless 修饰句
o case
* 循环
o while
o while 修饰句
o until
o until修饰句
o for
o break
o next
o redo
o retry
* 异常处理
o raise
o begin
o rescue修饰句
* 其他
o return
o BEGIN
o 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"
方法调用
* super
* 带块的方法调用
* yield
例:
foo.bar()
foo.bar
bar()
print "hello world\n"
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
这样的多重赋值还不会有警告,但最好不要使用。
类/方法的定义
* 类定义
* 特殊类定义
* 模块定义
* 方法定义
o 方法定义的嵌套
o 方法的计算
* 特殊方法定义
* 类方法的定义
* 调用限制
* 与定义有关的操作
o alias
o undef
o 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"