局部变量由小写字母或下划线(_)开头.局部变量不像全局和实变量一样在初始化前含nil值.ruby$foonilrubyfoonilrubyfooERR:(eval):1:un
局部变量由小写字母或下划线(_)开头.局部变量不像全局和实变量一样在初始化前含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
- 整个程序(除非符合上面某个条件)
异常处理:rescue
在Ruby里,就像其它的现代语言,我们可以通过隔离的办法处理代码域里的异常,因此,这有着惊人的效果却又不会为程序员或以后希望读它的其它人造成过度的负担.代码域由begin开始直到遇到一个异常,这将导致转向一个由rescue标记的错误处理代码域.如果异常没发生,rescue代码就不会使用.下面的代码返回文本文件的第一行,如果有异常则返回 nil.
retry 用于 rescue 代码表示又重新执行 begin 代码.这让我们可以压缩前面的例子:
fname = "some_file" begin file = open(fname) # ... process the input ... rescue fname = "STDIN" retry end |
但这仍有一点瑕疵.一个不存在的文件将导致不停止地 retry.你在使用 retry 做异常处理时应注意到这一点.
如果在我们写文件的时候发生异常,文件会保留打开.我们也不希望这样的冗余出现:
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前面.
什么是一个存取器?
我们在前面已经讨论过实变量了,但却未过多的讨论.一个对象的实变量属于它的属性,也是它与其它来自同一个类的对象的一般区别.读写它的属性是重要的;这样做需要做一个叫着属性存取器(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 # ruby> f1.set_kind("peach") # use the writer "peach" ruby> f1.get_kind # use the reader "peach" ruby> f1 # inspect the object # |
足够简单;我们可以存取关于我们搜索的水果种类的信息.但我们的方法名还有点儿牢骚.下面的这个更简洁,也更方便.
ruby> class Fruit | def kind=(k) | @kind = k | end | def kind | @kind | end | end nil ruby> f2 = Fruit.new # ruby> f2.kind = "banana" "banana" ruby> f2.kind "banana" |
inspect方法
一个小插曲.你已注意到当我们试着直接观察一个对象,就会出现一些像 # 的东西.这只是个缺省的行为,我们可以自由地改变它.我们所要做的只是加一个名为 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? 方法测试实变量并只在它们存在时才报告,但也许那不是很有用;因为每一个水果都有类型和状态.看来我们应该在某种程度上确定其属性.这正是下一节我们要讨论的.
弹性的初始化
上面我们看到一旦一个参数被关联到一个 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却采用了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中可找到更多信息.
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 |