Friday, October 31, 2008

一个异常的解决办法

如果你和我一样也装了Aptana的话,可能你曾经遇到过这样的异常:
---------------------------
Aptana
---------------------------
JVM terminated. Exit code=-1073741819
C:\WINDOWS\system32\javaw.exe
-Xmx384M
-Xms128M
-Djava.awt.headless=true
-jar C:\Programmi\Aptana Studio _noInstall\startup.jar
-os win32
-ws win32
-arch x86
-launcher C:\Programmi\Aptana Studio _noInstall\AptanaStudio.exe
-name Aptana
-showsplash 600
-exitdata e34_54 C:\Programmi\Aptana Studio _noInstall\AptanaStudio.exe Studio
-vm C:\WINDOWS\system32\javaw.exe
-vmargs
-Xmx384M
-Xms128M
-Djava.awt.headless=true
-jar C:\Programmi\Aptana Studio _noInstall\startup.jar
---------------------------
OK
---------------------------

开始我百思不得其解,刚才还好好的,怎么升级就出问题了呢 。最后才发现原来线程里有一个AptanaStudio.exe一直存在着,所以你再打开一个IDE就会出现上面的异常,解决办法?kill it.

Ruby on Rails 安装

Instant Rails 是一个包含Ruby,Rails和MySQL的安装包,准确的说你不用安装,只有在Instant Rails下载最近一个版本的压缩文件,然后把它解压缩到一个目录下就可以了,但是记住这个目录不能存在空格,比如不要把它放在C:\Program Files 下。
解压缩完毕之后双击那个InstantRails.exe 文件,第一次运行它会提示你是否修改配置文件到你当前的目录下,记住点“OK” 否则Apahce和Mysql的好多东西都要自己配置。配置完之后可能会出现端口被占用的异常,这可能是因为你的80端口被其他程序占用了,Apache的默认端口设置了80,你只要打开新X:\InstantRails-2.0-win\conf_files\httpd.conf 文件,然后更改port 的值为其他的端口号就可以了,比如:8080
正常启动后你应该可以看到一个控制台程序,在上边你可以配置Apahce和Mysql,并且这个打包程序还提供了PhPAdmin模块,这样你可以通过它来创建Mysql数据库、表并维护数据库。你还可以在这个控制台上打开Ruby的交互式命令行;运行它本身自带的两个应用程序。下面是我的程序运行的样子。
如果你可以运行所有这些,OK,你开发RoR应用的所有东西都准备好了。

Wednesday, October 29, 2008

(7)导入模块

我们说类是模块的子集,那么不管你定义一个模块还是定义一个类,在你需要他的时候都必须要导入它。
一个Ruby文件中可以写多个类和方法已经模块,Ruby提供了两个导入模块的方法:require和load。
  • require可以导入源文件和二进制文件,而load不能。
  • require只会导入一次需要的模块,而load在每次调用的时候都会重新导入一次。
  • require是可执行的,这意味着你可以把它放在代码中,比如按条件导入需要的模块
  • require和load关键字都支持相对路径和觉得路径的模块导入。
  • require可以支持导入文件包含扩展名或者不包含扩展名。
  • 如果你要导入多个文件,那么需要每行导入一个文件或者用分号隔开。

(6)模块与名称空间

Ruby中提供了模块的概念,你可以使用module关键字来定义一个模块。模块类似于类,但是模块不能像类那样可以继承,你也不可能想类那样去实例化一个模块。类是模块的子类,这意味着所有的类都是模块,但是模块不是类。
模块可以被用于把一系列相关的方法放到一起,这样你就可以避免重载了Ruby核心库中的方法或者类,就是说你有了自己的名称空间,下面是一个例子,比如我们现在有两个方法用于编码和解码64位字节,我们觉得没必要专门定义两个类来维护着两个方法,那么我们可以把它们放在一个名称空间里:
module Base64
def self.encode
end

def self.decode
end
end

除了用self关键字外,我们还可以像定义类方法那样直接用模块名作为前缀:
module Base64
def Base64.encode
end

def Base64.decode
end
end




# 下面是调用这个模块内方法的示例
text = Base64.encode(data)
data = Base64.decode(text)

当然我们还可以把一组相关的类、方法、常量等都放在一个模块里,这样更加清晰:
module Base64
DIGITS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'

class Encoder
def encode
DIGITS
end
end

class Decoder
def decode
Base64.helper
end
end

# 为每个类提供一个助手类
def Base64.helper
end
end
对于上面的模块,DIGITS可以在两个类中直接方法,无需加模块名前缀,但是如果方法helper方法就必须要加上模块名前缀了。
模块的另一个重要的作用是“插入”(mixin)功能。你可以在模块中定义一些列的方法,然后在其它模块或类中用到这个模块中方法的时候,通过include 方法来将模块包含进当前的模块或者方法。需要注意的是你不能把一个类插入到另一个类中。比如我们上面定义的模块,你可以在一个类中包含它,实际上include是一个Ruby的核心方法而是不是一个关键字,所以你可以向调用方法那样使用它,如果你有多个模块需要导入,不同,模块之间可以用逗号分开,也可以用括号包含所有要导入的模块,下面的两种方式是一样的:
class Point
include Comparable,OtherModule
end

class Point
include(Comparable,OtherModule)
end

说到这里我想再谈谈继承,前面我们说过Ruby中不允许多重已成,也就是说你的子类只能有一个父类。如果你是一个C++程序员你可能要问为什么Ruby不提供像C++那样的多重继承。实际上你可以通过include多个模块的方式模拟多重继承,因为如果你在类中实现了一个和模块中同名的方法,那么你最终调用的是你自己定义的类方法,而不是模块中的同名方法,这在某种意义上模拟了多重继承。
另一个要注意的地方是,假设我们现在包含了两个模块A和B,这两个模块中存在重名的方法printa,那么如果我们在类C中调用这个方法printa的时候,会调用哪个模块中的方法?答案是取决于你include模块的顺序,如果A在B的前面,那么就调用A中的printa方法而不是b中的。

(5)Ruby中的工厂方法实现

了解设计模式的人一定知道工厂方法,有时候我们希望通过其他的方式创建一个类对象,而不是通过new关键字。不过new是所有类多有的public方法,我们要防止使用这个类的人直接通过new来创建类。Ruby提供了一个对方法访问权限控制的方法:private_class_method :new,这样我们就可以防止别人直接调用new来创建对象,下面是一个例子:
class Point
# 定义初始化方法...
def initialize(x,y) #传入参数
@x,@y = x,y
end

# 我们让new变成私有方法,这样别人就不能从外部调用了
private_class_method :new

def Point.cartesian(x,y) # 定义工厂方法,注意它是一个类方法
new(x,y) # 在方法内部调用new方法
end

def Point.polar(r, theta) # 创建另一种类型的Point对象
new(r*Math.cos(theta), r*Math.sin(theta))
end
end

Tuesday, October 28, 2008

(4)类继承

Ruby和大多数面向对象语言一样,类可以继承。如果你是Java或者C++程序员你一定知道我在说什么。Ruby和Java一样,一个类可以有多个子类,但是一个类只能有一个父类。Ruby中所有的类多是Object类的子类,Ruby1.9中又增加了一个BasicObject,它是Object类的父类。
class Base
attr_accessor :x,:y
def initialize(x,y)
@x,@y=x,y
end

end

class Child <Base
attr_accessor :z
def initialize x,y,z
super(x,y)
@z=z
end

end

c=Child.new(1, 2, 3)


上面的例子中可以看出Ruby使用符号< 来表示类的继承,Ruby中子类会继承父类的所有方法,包括initialize方法也会继承。就像self关键字表示对象本身一样,其中super 关键字表示父类对象。子类会继承父类的所有方法、实例变量、常量和类变量。但是这里有几点区别:
对于类变量来说,子类中如果命名了与父类同名的变量名,那么在调用子类的时候,子类中变量值会覆盖父类中变量值,而对于常量来说,父类中常量不会被子类中的同名常量覆盖,因为他们所在的“域”不同:
class A

@@value=1
def initialize
@x=1
end
def A.vale
@@value
end
def A.x
@x
end
A::LET=1

end
puts A.vale #打印1
class B < A ; @@value=2; B::LET=2; end
puts A.vale #打印2,说明父类的变量值别子类覆盖了。
class C < B;@@value=3;end
puts A.vale #打印3,说明父类的变量值别子类覆盖了。

puts A::LET # 打印1
puts B::LET # 打印2,说明常量没有被覆盖,因为常量之前有类的前缀

(3)类方法的可见性,public,protected,private

如果你使用过其它语言,你一定知道public,protected,private是什么,在Ruby中所有的方法,如果没有特殊的限制都是public的,类之外的方法Ruby解析为Object对象的private方法,在Ruby中定义方法的可见范围可以通过下面的方式,即区块方式,凡是protect关键字后下面的都是protect方法,private关键字下面的是private方法,public方法放在最上面没有任何关键字修饰。
class Med
  def pb
    print "Public"
  end
  protected
  def pt1
      print "pt1"
  end
  def pt2
    print "pt2"
  end
  private
  def pv1
    print "pv1"
  end
  def pv2
    print "pv2"
  end
end

m=Med.new
m.pb  #public方法可以执行
m.pt1 #这里你会得到NameError的异常 
m.pt2 #这里你会得到NameError的异常 
m.pv1 #这里你会得到NameError的异常 
m.pv2 #这里你会得到NameError的异常
你还可以把方法名的Symbold当作protected或者private的参数的方式来定义方法的可见性:
class Test
  def pb
    print "Public"
  end
  def pt
    print "Protected"
  end
  def pv
   print "Private"    
  end
  def pv2
    print "Private"    
  end
  protected :pt #通过Symbol的方式定义protected方法
  private :pv,:pv2
end

t=Test.new
t.pb #这里你会得到NameError的异常 
t.pt #这里你会得到NameError的异常 
t.pv #这里你会得到NameError的异常 
t.pv2 #这里你会得到NameError的异常
有时候我们可能希望让一些public的类方法变成private的那么可以通过private_class_method关键字实现,举个例子,有时候我们希望我们的类实现“单态模式”:
class Test
  @@test=nil  #声明一个类变量
  def pb
    print "Public"
  end
  def pt
    print "Protected"
  end
  def pv
   print "Private"    
  end
  def pv2
    print "Private"    
  end
  def Test.create #自定义一个“工厂方法” 实现“单态模式”
     @@test=new unless @@test
     @@test
  end
  protected :pt #通过Symbol的方式定义protected方法
  private :pv,:pv2
  private_class_method :new #将默认的new方法变成private
  
end

t=Test.create #通过自定义方法创建Test对象
t.pb

(2)重载操作符

我们知道Ruby中所有的操作符都定义为方法,所以你可以重载这些操作符以便定义你自己的操作行为:比如+,-,*/,[]。Ruby 把一元减定义为@-,所以我们前面讲的Point类可以定义如下:
class Point
  attr_reader :x, :y   # 定义我们的两个实例变量都有setter和getter方法

  def initialize(x,y)
    @x,@y = x, y
  end

  def +(other)         # 重载加法操作
    Point.new(@x + other.x, @y + other.y)
  end

  def -@               # 重载一元减号操作,返回负数值得对象
    Point.new(-@x, -@y)
  end

  def *(scalar)        # 重载乘号,这样Point对象的x,y可以乘以一个数值
    Point.new(@x*scalar, @y*scalar)
  end
end
注意我们上面定义的+操作,它没有对参数类型进行检查,也就是说如果我们传入的参数对象是一个有.x和y方法的对象,并且这个两个方法都返回数字型,那么就可以正常执行,否则会抛出异常,通常这没有问题,但是如果这个对象没有这个方法或者方法的返回值不是数字,那么就会抛出异常。一个解决的办法是像下面这样进行对象类型检查:
def +(other)
  raise TypeError, "Point argument expected" unless other.is_a? Point
  Point.new(@x + other.x, @y + other.y)
end
上面的方法在传入的对象不符号要求的时候会抛出一个指定的异常,我们用is_a?方法来进行判断,但是这种坚持还不是很严格,比如,如果我传入的是一个Point的子类也是可以正常执行的,但有的时候我们希望进行更严格的检查,即,只允许两个Point类进行相加,那么就要用到方法instance_of?
当然我们有时候要求很宽松,只要参数对象有方法x,y就可以对两个对象进行相加;
def +(other)
  raise TypeError, "Point-like argument expected" unless
    other.respond_to? :x and other.respond_to? :y
  Point.new(@x + other.x, @y + other.y)
end
不过这还是没有解决对参数方法类型判断的问题,如果我们既希望只要有x,y方法就可以相加,但是又不希望这两个方法返回字符串或者其他非数字的情况下出现难于理解的堆栈异常,我们可以使用下面这种通用的做法,通过rescue捕获所有异常:
def +(other)         #传入一个对象
  Point.new(@x + other.x, @y + other.y)
rescue               # 如果出现任何异常
  raise TypeError,   # 抛出我们自定义的异常消息
    "Point addition with an argument that does not quack like a Point!"
end

既然我们可以继承+-*/这样的操作符我们当然也可以继承其他的操作符,比如我们希望给Point对象加上类似数组或者字典对象的功能。可以重载[]操作符:
def [](index)
  case index
  when 0, -2: @x         # 如果索引是0或者-2返回变量x
  when 1, -1: @y         # 如果索引是1或者-1返回变量x
  when :x, "x": @x       # 如果键值是Symbol对象x或者字符串“x”,返回变量x
  when :y, "y": @y       # 如果键值是Symbol对象y或者字符串“y”,返回变量x
  else nil               # 如果索引或者键值不存在就返回nil
  end
end

如果对象可以加上类似数组的功能,我们理所当然的认为他可以实现迭代方法,方法很简单只要用yield就可以了:
def each
  yield @x
  yield @y
end
然后我们就可以这样调用了,是不是很酷。
p = Point.new(1,2)
p.each {|x| print x }   # 打印"12"
下面谈谈对象的比较,如果你曾经是一个Java程序员你一定重载过对象的equals方法,以便实现对象比较的功能,Ruby中定义比较的操作有多种,而且很简单:
def ==(o)               # 判断对象的值是否相同
  if o.is_a? Point      # 如果o是一个Point对象
    @x==o.x && @y==o.y  # 那么比较这两个对象的值.
  elsif                 # I如果不是Point对象
    false               # 返回false.
  end
end

如果现在有另一个需求,我们希望比的o对象可以不是一个Point,只要他们的x,y值相等即可,这种情况下,我们可以不进行类型校验,而是通过异常捕获来实现这个功能:
def ==(o)                  # 传入对象参数
  @x == o.x && @y == o.y   # 比较对象的参数值
rescue                     # 比较失败
  false                    # 返回false
end
记住我们这里比较的是对象的值,而不是对象本身,通常情况下这可以满足我们的需求,你甚至可以把eql?方法也定义成比较值得操作,方法是给它取别名方法为我们已经定义的==操作:
class Point
  alias eql? ==
end
不过有的时候如果你的对象是作为字典对象的键值,那么这样定义是不够的,因为字典对象内部的比较操作是通过eql?方法,而且同时你还需要重载hash方法,下面是一个例子:
def eql?(o)             
  if o.instance_of? Point      
    @x.eql?(o.x) && @y.eql?(o.y)
  elsif
    false
  end
end
def hash
  @x.hash + @y.hash
end
上面的重装hash的方式不是很好,比如Point(1,2)和Point(2,1)就无法区分,这样会带来性能问题,一个推荐的方法是如下所示这样,几乎所有的Ruby类对象比较都可以使用这种方法:
def hash
  code = 17
  code = 37*code + @x.hash
  code = 37*code + @y.hash  
  code  # 返回最终的哈希码
end
你是否想过如果一个数组中有多个相同的Point对象,那么如何利用sort方法进行排序呢?方法是重载<=>
include Comparable   # 必须导入这个包.

def <=>(other)
  return nil unless other.instance_of? Point
  @x**2 + @y**2 <=> other.x**2 + other.y**2
end

我们上面的Point在开始定义的时候是一个不可变的对象,后来我们定义了x=,y=方法来修改对象的属性,Ruby也提供了简单的方法通过attr_accessor :x,:y 来方便的定义可访问方法。除此之外我们还可以定义自己的方法来修改对象属性,还记得以前我们说到的Ruby中以!结尾的方法会修改对象的内部属性,没有叹号的不会,现在我们来给Point也加上这样的方法,不然我们定义一个add放用于将两个Point的值相加:
def add!(p)          # 这个方法将对象p与当前对象相加
  @x += p.x          # 并且更改了当前对象的值
  @y += p.y
  self
end



#下面这个方法也完成了类似的操作,但是返回的是一个对象的拷贝,而不�是修改对象本身

def add(p)           
  q = self.dup       # 通过dup创建一个拷贝对象
  q.add!(p)          # 调用已经定义的add!方法
end
说实话,Ruby语言为我们做了很多工作,它甚至提供了一个简便的方法让我们快速的创建一个可变对象,你可以使用Strut.new的方式来完成:
Struct.new("Point", :x, :y)  # 创建一个新类 Struct::Point
Point = Struct.new(:x, :y)   # 把这个新的类赋给常量Point,现在Point就代表一个新类
通过Strut创建的类和你自己写代码创建的类是一样的:
p = Point.new(1,2)   # => #<struct Point x=1, y=2>
p.x                  # => 1 
p.y                  # => 2
p.x = 3              # => 3
p.x                  # => 3
通过Strut.new的方式定义了Point类后,你可以通过下面的方式给这个类增加新的方法:
Point = Struct.new(:x, :y)   # 创建一个新的类,赋给 Point
class Point                  # “打开”这个类,给他增加新的方法 
def add!(other)            # 定义add!方法
    self.x += other.x
    self.y += other.y
    self
  end

  include Comparable         # 导入必要的包
  def <=>(other)             # 重装 <=> 操作
    return nil unless other.instance_of? Point
    self.x**2 + self.y**2 <=> other.x**2 + other.y**2
  end
end
再说一个有用的方法定义方式,如果你是一个Java程序员你一定使用过类的静态方法,你可以不必创建对象就直接引用这个方法,Ruby也提供了类似的功能,不过这里叫做类方法,定义类方法有3种方式:
你可以在方法前加上类名的前缀和点号:
class Point
  attr_reader :x, :y     

  def Point.sum(*points) # 定义一个类方法,返回多个Point对象的“合计”Point对象
    x = y = 0
    points.each {|p| x += p.x; y += p.y }
    Point.new(x,y)
  end
 
end
或者使用self关键字:
def self.sum(*points)  # 定义类方法的另一种方式
  x = y = 0
  points.each {|p| x += p.x; y += p.y }
  Point.new(x,y)
end
你还可以使用<<定义一个新的类来想Point类中增加方法:
# 向Point类中增加类方法
class << Point      # 使用<<符号来给指定的类增加类方法
  def sum(*points)  # 等同于Point.sum
    x = y = 0
    points.each {|p| x += p.x; y += p.y }
    Point.new(x,y)
  end

  # Other class methods can be defined here
end

再来看另一个问题,如何在类中使用常量,常量是非常有用的,在Ruby中你可以通过以下方式定义和使用常量,使用大写字母来定义常量,使用::来引用类的常量:
class Point
  def initialize(x,y)  # Initialize method
    @x,@y = x, y 
  end
#定义的常量
  ORIGIN = Point.new(0,0) 
  UNIT_X = Point.new(1,0)
  UNIT_Y = Point.new(0,1)
 
end
#引用常量
Point::NEGATIVE_UNIT_X = Point.new(-1,0)
说完了常量我们来看看类变量,类变量一@@开头,它可以在所有的方法之间共享,对所有方法都是可见得,比如下面的例子中,我们给Point类增加了一些类变量;
class Point
  # 初始化类变量
  @@n = 0              # Point个数的计数器
  @@totalX = 0         # x值的合计值
  @@totalY = 0         # y值的合计值

  def initialize(x,y)  # 类初始化方法
    @x,@y = x, y       # 初始化实例变量

    # 使用定义的类变量
    @@n += 1           # 记录创建的Point对象个数
    @@totalX += x      # 计算合计值
    @@totalY += y
  end

  # 打印所有合计值的方法
  def self.report
    # 下面演示了如何使用类变量
    puts "Number of points created: #@@n"
    puts "Average X coordinate: #{@@totalX.to_f/@@n}"
    puts "Average Y coordinate: #{@@totalY.to_f/@@n}"
  end
end
从某些角度来看,类变量和常量有些相似,但还是存在不少差别。类变量与常量的区别如下:
  • 类变量可以重复赋值(但对常量赋值会发出警告)。
  • 类变量默认是protected的,不能在类外部直接引用(在继承类中则可以引用或赋值)。
从某些角度来看,类变量和实例变量也有些相似。类变量与实例变量的区别如下:
  • 在类范围内定义的类变量,可以在该类的方法中访问,但实例变量则不行。
  • 类变量可在子类中引用或者赋值,但实例变量则只可在类范围内直接引用或赋值。

Monday, October 27, 2008

(1)类的定义

Ruby中定义类非常简单,只要在名称前加class关键字并以end结尾就可以了,一个惯例是类名被Ruby视为常量,所以按照命名常量的规则,类名应该以大写字母开头。下面是一个例子:
class Point
  @z=10  #你不应该在方法外部直接定义实例变量
  def initialize(x,y) #初始化方法用initialize关键字
    @x,@y=x,y
    @z=10 
  end
  def pp    
    print @z #通过@z来访问类内部的实例变量
  end
  def to_s #to_s 方法用于定义类对象转换为字符串的信息   
    "#{@x},#{@y}" 
  end
  def x  #定义getter 方法
    @x
  end
  def y #定义getter 方法
    @y
  end
  def x=(x_value) #定义setter 方法
    @x=x_value
  end
  def y=(y_value) #定义setter 方法
    @y=y_value
  end
  def setall #自定义定义setter 方法
    self.x=11
    self.y=22
  end
end
p=Point.new(1,2) #创建对象,这会调用initialize 方法对类进行初始化
p.pp  #调用方法

puts "1:#{p.x},#{p.y};"

p.x=3  # 修改x的值,实际上是调用方法p.x()=3
p.y=4

puts "2:#{p.x},#{p.y};"

p.setall

puts "3:#{p.x},#{p.y};"
就像我在上面代码中加的注释,一样你不能在Ruby的类中直接方法他的内部实例变量,必须通过定义方法来访问这些变量,而且如果你是一个Java程序员,记住类似类中第一行那样的定义是错误的,你不能在方法外直接声明一个实例变量然后给他赋值,这在Ruby中是不允许的。如果你要在类内容调用一个已经存在的实例变量,你可以通过self关键字,但是前提是你已经定义了setter和getter方法。
除此之外,Ruby还提供了一个方便的方法来定义setter和getter 方法,你可以通过attr_reader和attr_accssor来定义,比如:
class Point
  attr_accessor :x, :y # 通过Symbol方式定义实例变量的setter和getter方法
end


class Point
  attr_reader :x, :y  # 通过Symbol方式定义实例变量的getter方法
end


attr_reader "x", "y" #你可以通过给出实例变量的字符串名来定义getter或setter方法
还有一个关键字attr,他用于定义一个单一的实例变量的setter和getter方法:
attr :x        # 为 @x定义一个setter方法
attr :y, true  # 定义getter 和 setter 方法
记住,以上方式只能按照当前变量名的规则来给出setter和getter方法,如果你需要自定义的setter和getter方法,你必须自己手工完成,就像我们开始代码中那样。

(8)lambda 和Proc

前面我们一再提到块的概念,你知道块只是一段代码,而不是一个对象。在Ruby中你可以通过Proc或者lambda来描述一个块,这时候这个块就是一个对象了,那么它也就有了方法call.
区分Proc和lambda可以在一个对象上调用lambda?方法,如果返回true就是lambda否则就是Proc
你可以通过a=Proc.new{|x| puts x}的方式定义一个Proc,你可以可以通过y=lambda{|x| x+1}的方式定义一个lambda。这两个的对象的却别在于Proc更像是一个块的定义,而lambda更像是一个方法的定义。比如下面的代码中,同样是return两种对象的行为是不一样的:
def test
  puts "entering method"
  p = Proc.new { puts "entering proc"; return } 
  p.call                 # 这里调用proc会使得方法return
  puts "exiting method"  # 这行永远不会执行
end
test

def test
  puts "entering method"
  p = lambda { puts "entering lambda"; return } 
  p.call                 # 调用lambda不会导致方法return
  puts "exiting method"  #现在这行会执行
end
test
另外这两种方式对参数的要求也是不一样的,比如:
p = Proc.new {|x,y| print x,y }
p.call(1)       # x,y=1:     如果缺少参数就用nil代替,打印 1 和 nil
p.call(1,2)     # x,y=1,2:   打印 12
p.call(1,2,3)   # x,y=1,2,3: 多余的参数被忽略了: 打印 12
p.call([1,2])   # x,y=[1,2]: 数组元素自动分配个每个参数: 打印 12



l = lambda {|x,y| print x,y }
l.call(1,2)     # 这样没有问题
l.call(1)       # 参数个数错误
l.call(1,2,3)   # 参数个数错误
l.call([1,2])   # 参数个数错误
l.call(*[1,2])  # 明确的告诉lambda这是一个数组参数,这可以正常运行。

Sunday, October 26, 2008

(7)方法的块参数

前面我们提到了有一种参数以&开头,这就是块参数,比如下面的代码中doubleplus有一个参数&b,它是一个块参数,块参数通过call方法调用执行:
def doubleplus(x,y,&b)
  c=x+y  
  while (c>1)
    b.call(2*c) #块参数通过call执行块中的内容
    c=c-1
  end
end
doubleplus(4,2){|c|
  puts "c:is #{c}" #我们在这里调用这个方法,并给他传递一个块参数
}
在随后的章节中我们会降到Proc对象,如果是Proc对象的话参数的&符号就可以省略了,因为这是时他是一个对象:
def doubleplus(x,y,b)
  c=x+y  
  while (c>1)
    b.call(2*c)#Proc对象的call方法
    c=c-1
  end
end
b=Proc.new{|x| puts x} #声明一个Proc对象变量
doubleplus(4,2,b)

(6)利用字典对象实现命名参数功能

在其他一些语言中(比如Python)支持方法的命名参数调用,比如你有一个方法有两个参数name和age,并且他们都有缺省值,那么你可以通过指定参数名而只给这个方法传递某一个参数,不幸的是,Ruby不提供这样的语法支持。一个变通的方法(或者说我们玩些小花招),你可以用一个字典对象作为方法的参数,那么你可以通过把字典对象总的元素键当作参数名,把字典对象中的元素值当作参数值的方式来实现这一功能,比如下面的例子:
def sequence(args)
 
  n = args[:n] || 0
  m = args[:m] || 1
  c = args[:c] || 0

  a = []                      
  n.times {|i| a << m*i+c }   
  a                           
end
然后你可以给这个方法传递一个字典对象参数:
sequence({:n=>3, :m=>5})      # => [0, 5, 10]
作为一种特殊的方式Ruby允许你把参数的花括号{}去掉,所以你可以写成这样:
sequence(:m=>3, :n=>5)        # => [0, 3, 6, 9, 12]

# Ruby 1.9 的写法
sequence c:1, m:3, n:5        # => [1, 4, 7, 10, 13]
不过,说实话比起Python的语法格式,这只能算是一种变通的用法,因为,你不能直观的看出这个方法到底有多少个参数,都是什么。

(5)任意参数的方法

有么有想过你的方法可以接受任意参数,如果你是Java程序员你一定记得那个main方法,它的参数是一个字符串数组,所以它的参数是可以有多个的。在Ruby中也是这样的,不过你要在参数前加一个星号*,这样就告诉Ruby你的方法有任意个数的参数,其实这个参数还是一个数组:
def max(first, *rest)  
  max = first 
  rest.each {|x| max = x if x > max } 
  max
end
上面这个方法中*rest是一个数组参数,你可以传递任意数量的参数给他。在Ruby1.8中要求这个带星号的参数必须位于所有参数(缺省参数和带&的参数)之后,也就是说它是最后一个参数。Ruby1.9中没有这样的要求,它不必是方法的最后一个参数,但是它必须位于缺省参数和&前缀的参数之后。
调用这个方法:
data = [3, 2, 1]
m = max(*data)   # first = 3, rest=[2,1] => 3
如果没有这个*那么方法的计算结果可就不同了:
m = max(data)   # first = [3,2,1], rest=[] => [3,2,1]

(4)方法的缺省参数

在Ruby中方法的参数是可以有缺省值的,比如下面的方法中len的值是缺省值,也就是说如果你调用这个方法时,没有给他传值那么就用缺省值:
def prefix(s, len=1)
  s[0,len]
end


prefix("Ruby", 3)    # => "Rub"
prefix("Ruby")       # => "R"
而且,方法的缺省参数可以是任意的表达式,甚至可以是对前一个参数的计算值,比如:
# 第二个参数的缺省值是通过第一个参数的长度值计算得到的。
def suffix(s, index=s.size-1)
  s[index, s.size-index]
end
需要记住的是,方法的缺省值是在方法被调用的时候创建的,而不是给他传值的时候,比如下面的方法中,缺省参数a是在调用方法append的时候就创建了:
def append(x, a=[])
  a << x
end
在Ruby1.8中方法的缺省参数只能出现在其它非缺省参数的后面,在Ruby1.9中没有这个限制,不过,你不能在两个缺省参数中放一个非缺省参数,而且在调用方法时,参数的传递必须是按顺序的。比如你有一个方法有2个参数,他们都存在缺省值,你可以不给它传递参数从而全部使用缺省值,你也可以只传递一个参数,那么方法会对第二个参数使用缺省值,但是,遗憾的是我们没办法只给第二个参数传值而让第一个参数使用缺省值。因为方法的参数传递是按顺序的。

Friday, October 24, 2008

(3)方法的括号

在Ruby中方法的括号是可选的,比如下面的两行代码是一样的效果:
puts "Hello World"
puts("Hello World")
Ruby中没有属性的概念,你要访问一个对象必须通过方法,比如下面的代码中length实际上是一个方法而不是属性:
greeting = "Hello"
size = greeting.length
不仅是方法调用,方法声明是也可以不加括号,比如:
def hello lname,fname
  puts lname
  puts fname
end
有时候加上括号可以使得代码更清楚,比如:
puts(sum 2,2)   # 这段代码的含义到底是 puts(sum(2,2)) 还是 puts(sum(2), 2)?
这种情况下,没有括号容易让代码引起混淆。需要注意的一点是,方法后的括号应该紧跟方法名,否则他们的含义是不一样的,比如:
square(2+2)*2    # square(4)*2 = 16*2 = 32
square (2+2)*2   # square(4*2) = square(8) = 64

(2)方法的命名

Ruby中的方法命名没有强制的要求,但是按照惯例,方法以小写字母开头,如果以大写字母开头通常会被任务是一个常量;如果方法中包含多个单词,那么每个单词之间用下划线分隔,而不是传统的驼峰式写法,比如:按惯例方法名写成like_this而不是likeThis。但是记住这只是一种约定,Ruby语法解析器没有强制要求你这样做。
另外要说到的是Ruby中方法可以以等号=,问号?和叹号结尾。以等号结尾的方法它的参数可以通过赋值的方式传递,这个我们之前就提到过。按照约定,以问号结尾的方法一般是告诉调用者,这个方法返回的是一个布尔值,比如Array的empty?用于判断数组是否为空,以!结尾的方法提醒程序员这个方法会更高对象内部的状态,比如Array的sort方法用于排序数组内的元素,返回的是一个新的数组拷贝,但是sort!方法是将当前数组重新排序并返回数组本身,一般Ruby的核心类库中都会提供两同名方法,一个有!结尾一个没有。但是记住这只是一种约定,Ruby语法解析器没有强制要求你这样做。
符号方法
Ruby中所有的操作符号都定义成了方法,比如+,-,*,/ []等等,所以你可以通过重载自定义符号方法。比如:
def +(other)               # 定义二进制的加法操作: x+y is x.+(y)
  self.concatenate(other)
end
但是值得注意的是,你只能使用我们以前讲到的Ruby中操作符中定义的操作符,也就是说你不能发明一个这些操作符以为的操作符。
方法别名
有时候我们用的方法有一个很长的名字,如果我们多次调用它时不得不一次次重复的写这个冗长的名字,幸运的是Ruby对方法提供了一个别名操作符alias,你可以通过这种方式给一个方法取另外一个别名:
alias aka also_known_as   # 给方法起个别名
 
看到这个你想到了什么?我想到了Python中的import some.module.clzz as clz,是不是很像?
执行了这个方法后aka就相当于also_known_as方法。这种语法结构让Ruby程序代码看起来更“清晰”,比如Range对象有一个方法用于判断某个元素是否位于其中,include?还记得吧,如果你的Range是某州类型的集合,那么另一个别名方法member?看起来让代码更容易阅读。
另外一个有用的功能是,你可以通过这种方式给一个已经存在的方法动态的增加新的功能,比如下面这样:
def hello                       # 定义一个简单的方法
  puts "Hello World"            # 假设现在我们想扩展它的功能...
end

alias original_hello hello      # 给已经存在的方法取一个别名

def hello                       # 现在我们用之前的那个方法名定义一个新的方法
  puts "Your attention please"  # 加入新的打印功能
  original_hello                # 调用之前的方法
  puts "This has been a test"   # 加入另一个功能
end
现在你在执行hello方法得到的结果将是:
Your attention please
Hello World
This has been a test
Ruby的方法名是不能重复的,在一些静态语言中,比如Java,你可以拥有相同方法名的方法,他们的参数个数和类型不同,这称之为方法重载,这在Ruby中是不允许的。
不过Ruby中不要求定义参数的类型,所有者意味着你可以传递任意的参数给一个方法,而且Ruby的方法参数可以有缺省值,你甚至可以不给已经有缺省值的参数传值。我们后面的章节中将详细讲解这部分。

(1)Ruby中的方法

前面我们就已经接触到了Ruby中的方法,这一节我们将详细讲解方法的定义、调用等细节。
首先说,从语法上来讲,方法以def 关键字开头后面是方法名、0到多个参数,方法以end关键字结尾,方法的参数可以当作变量在方法体内使用。下面是一个例子:
# 定义一个名为 'factorial' 的方法,它只有一个参数 'n'
def factorial(n)
  if n < 1                # 校验参数的合法性
    raise "argument must be > 0"
  elsif n == 1            # 如果参数为 1
    1                     # 那么参数的返回值就是 1
  else                    # 否则计算阶乘
    n * factorial(n-1)    # 计算n-1的阶乘
  end
end
上面的方法只有一个参数,用于计算整数的阶乘,你可以看到这是一个递归调用的方法。
方法都有返回值,返回值就是方法的最后一个表达式的值,如果一个方法中没有表达式那么返回值就是nil。像上面的factorial方法,如果正常执行,返回值就是1或者n*factorial(n-1)。
当然,你还可以用return 关键字来强制返回一个值,这种方式一般出现在两种情况:

  1. 你需要在方法的最后一行之前返回一个值。
  2. 你的方法有多个返回值,如果有多个返回值,那么最终的返回值是一个数组。
下面是一些例子:
def factorial(n)
  raise "bad argument" if n < 1
  return 1 if n == 1
  n * factorial(n-1)
end

#返回多个值时要使用关键字return
def polar(x,y)
  return Math.hypot(y,x), Math.atan2(y,x)
end

# 将最后一行的多个表达式值,转换成一个数组就可以省略return
def cartesian(magnitude, angle)
  [magnitude*Math.cos(angle), magnitude*Math.sin(angle)]
end
这种返回多个值得方法,通常被用于平行赋值:
distance, theta = polar(x,y)
x,y = cartesian(distance,theta)

Ruby是一个完全面向对象的语言,那么所有的方法都是通过对象调用的,即使是我们在一个脚本中写的一个单一的方法,其实底层实现中它实际上是Object类的私有方法。如果你定义了一个类并在类中定义了方法,那么和其他语言类似,Ruby中对象的方法通过点号访问(.),self关键字表示调用方法那个对象本身的引用。如果你使用过Java,那么这个self相当于Java中的this。对了,还记得我们以前讲过的异常处理吗,那么说rescue要存在于begin和end块之间,如果你是在一个方法中使用rescue,可以直接放在方法体中。
前面我们给出的方法都是“全局”方法,后面我们介绍类的时候还会介绍类的方法,然而,我们还可以定义另一种方法,假如现在你有一个对象,你只想在当前这个对象上定义一个方法,那么你需要使用def关键字并且方法名是一个对象名加点号,后面再跟方法名的方式,我们称这种方法为单一方法:
o = "message"    # 一个字符串对象
def o.printme    # 为这个对象定义一个单一方法
  puts self
end
o.printme        # 调用这个方法
我们除了可以定义一个方法还可以“解除定义”一个已经存在的方法,方法是使用关键字undef:
def sum(x,y); x+y; end      # 定义一个方法
puts sum(1,2)               # 使用它
undef sum                   # 解除定义

undef并不常用,不过有一种情况可能有用,比如对于子类方法访问的限制,举个例子:类A定义了方法m,B是A的子类,那么按照继承的原则,B当然拥有方法m,但是可能出于某种原因你不希望使用B的人调用方法m,那么你可以在类B中undef m。不过通常的方式我们是通过在B中重载m的方式来完成的。对于类和模块,还有一个私有方法undef_method,你可以把你不需要的一系列方法通过它来“解除定义”。

Thursday, October 23, 2008

(9)BEGIN和END

BEGIN和END是Ruby中的关键字,用于定义一段代码。这两个关键字后需要跟一对{}代码就放在其中。记住这里的BEGIN和END 与小写的begin和end是不同的。BEGIN中的代码总是会被执行一次,不管这段代码在什么位置,即使想下面的例子中这段代码在一个不可能执行的条件分支中,它还是会被执行一次。而END中的代码是在所有的代码都执行之后才会被执行。而且,如果这两个关键字中的代码存在于循环或者迭代器中时,他们只会被执行一次。
if (false) 
  BEGIN {
    puts "if";                   # 这里会被打印
    a = 4;                       # 这里的变量只存在于BEGIN块中
  }
else
  BEGIN { puts "else" }          # 这里也会被打印
end

10.times {BEGIN { puts "loop" }} # BEGIN位于迭代器中时,只被打印一次

如你所见,在BEGIN中的变量只在它所在的BEGIN块中又要,而END不同,由于它在所有代码执行之后执行,所以,外部变量也可以在END中访问。
a = 4;
if (true) 
  END {                        # 这里会执行
    puts "if";                 # 打印
    puts a                     # 这里的a是一个外部变量,打印 "4"
  }
else
  END { puts "else" }          # 这里不会执行,因为前面的条件判断不成立
end
10.times {END { puts "loop" }} # 在迭代器中只执行一次

(8)异常与异常的处理

程序中会出现异常,或者说错误,比如除法的除数为0,访问一个不存在的文件或者调用一个不存在的方法,又或者访问的网络突然中断,这些都会引起一场,一般情况下异常会造成程序的中断,但是我们也可以通过处理这些异常让程序按照我们的想法继续执行。Ruby中通过raise和rescue还处理异常,raise用于抛出异常,resuce用于捕获异常。所有的异常都继承自Exception类,下列给出了Ruby 中异常类的继承结构,你不必全部记住他们,事实上这样类中没有定义很多方法,你唯一要知道的事下列的一场都属于Ruby中的标准异常,你可以继承或者扩展他们:
Object
 +--Exception
     +--NoMemoryError
     +--ScriptError
     |   +--LoadError
     |   +--NotImplementedError
     |   +--SyntaxError
     +--SecurityError         # Ruby1.8中的标准异常
     +--SignalException
     |   +--Interrupt
     +--SystemExit
     +--SystemStackError      # Ruby1.8中的标准异常
     +--StandardError
         +--ArgumentError
         +--FiberError        # Ruby 1.9中的新增异常
         +--IOError
         |   +--EOFError
         +--IndexError
         |   +--KeyError      # New in 1.9
         |   +--StopIteration # New in 1.9
         +--LocalJumpError
         +--NameError
         |   +--NoMethodError
         +--RangeError
         |   +--FloatDomainError
         +--RegexpError
         +--RuntimeError
         +--SystemCallError
         +--ThreadError
         +--TypeError
         +--ZeroDivisionError
Exception类定义了两个方法用于返回异常信息,message方法用于返回“可读”的异常信息字符串,如果Ruby程序出现了一个未捕获的一场,这个消息会显示给最终用户,通常用于程序员调试代码。另一个方法是backtrace,这个方法返回一个数组,数组中包含了调用的堆栈信息:
filename : linenumber in methodname
以上的代码中filename说明出错的文件位置,linenumer说明那一行出现了错误,methodname说明由那个方法引起的异常。如果你要定义自己的异常类,可以通过set_backtrace来自定义这个信息。
你可以通过扩展当前异常类来定义自己的异常类:
class MyError < StandardError; end

你可以通过raise或者fail来抛出一个异常,有以下几种方式:
  • 如果raise没有参数,那么Ruby会创建一个RuntimeError(不带任何消息)的异常类,并抛出它。
  • 如果raise有一个参数,并且这个参数是一个Exception对象,那么Ruby会直接抛出这个异常对象,通常我们不这样作 。
  • 如果raise有一个参数,并且这个参数是一个字符串,那么Ruby会创建一个RuntimeError对象并将字符串的内容作为异常的消息,我们经常这样使用。
  •  如果raise的第一个参数是一个对象,且这个对象有一个exception方法,那么Ruby会调用这个方法并抛出这个方法返回的异常对象。第二个参数可以是一个字符串,这个字符串会传递给第一个对象的exception方法的第一个参数,作为异常消息。
def factorial(n)                 # 定义一个函数
  raise "bad argument" if n < 1  # 如果条件不满足就抛出异常
  return 1 if n == 1             # factorial(1) 为 1
  n * factorial(n-1)             # 继续计算
end
上面的例子中raise只有一个字符串参数,下面是用其它方式抛出的异常:
raise RuntimeError, "bad argument" if n < 1
raise RuntimeError.new("bad argument") if n < 1
raise RuntimeError.exception("bad argument") if n < 1
就像上面的代码那样,你可以通过new或者exception关键字来创建你自己的异常,这两个方法的字符串参数是异常显示的消息。
Ruby中捕获异常通过rescue关键字,通常一个rescue位于begin和end之间,如下例:
begin
  # 这里放执行代码.
  # 通常他们可以正常的执行 
rescue
  # 这里是rescue子句,如果上面的代码出现异常,那么就跳转到这里,
  # 可以在这里放置其他代码来处理异常 .
end

当然我们可以命名异常对象,如果定义全局变量$!并且require 'English',那么你可以使用$ERROR_INFO 来命名异常,当是一个更好的方法是使用变量:
begin                                # 在这个块中捕获异常
x = factorial(-1)                  # 这里我们故意加入了非法的参数
rescue => ex                         # 将异常对象存储在一个变量ex中
  puts "#{ex.class}: #{ex.message}"  # 捕获这个异常并打印异常消息
end                                  # 结束块
注意这里声明的异常变量不仅仅在异常处理范围,一旦定义了这个变量,你可以在块的外部使用它。
还要注意的是上面这种方式只能捕获StandardError。如果你要捕获StandardError以外的异常,或者你要捕获一个你自定义的异常类型,你必须声明这个异常,或者使用rescue Exception的方式捕获任何类型的异常:


rescue Exception #捕获任何类型的异常
rescue ArgumentError => e #捕获ArgumentError异常
rescue ArgumentError, TypeError => error #将多个异常赋值到一个异常变量中

我们同样可以以一种分支的方式分别处理不同的异常:

rescue Exception #捕获任何类型的异常
rescue ArgumentError => e #捕获ArgumentError异常
rescue ArgumentError, TypeError => error #将多个异常赋值到一个异常变量中
我们前面讲过的retry关键字可以在rescue中使用,这样使用的目的是,有的时候引发的异常只是短暂异常,可能过一会儿就可以正常运行了,比如,当前服务器可能无法访问但是过一会儿就正常了,下面是一个访问网络的例子,它遇到网络异常后尝试再次访问:
require 'open-uri'

tries = 0       # 访问一个URL等待的时间
begin           # 块开始部分
   tries += 1    # 尝试连接 URL并打印连接信息
  open('http://www.example.com/') {|f| puts f.readlines }
rescue OpenURI::HTTPError => e  # 如果得到一个HTTP error
  puts e.message                # 打印异常信息
  if (tries < 4)                # 判断尝试的次数...
    sleep(2**tries)             # 等待 2, 4, 或者 8 秒钟
    retry                       # 然后再试一次!
  end
end
这里还有一个else子句,你可能认为else是在rescue执行失败后执行的语句,其实不然,实际上else子句部分的代码是在当begin部分的代码可以没有异常的正常执行后执行的代码。老实说else在rescue中并不经常使用,如果begin中没有rescue的话使用else是没有意义的。
另外一个重要的关键字是ensure,在这个子句中的代码无论如何都会被执行,比如你要关闭一个文件、一个数据库连接或者提交数据库执行语句。如果ensure在方法中,那么它的值不会作为方法的返回值,也就是说,在你调用一个方法的时候实际上会先执行ensure中的语句然后再执行begin中的语句。不过,如果你在ensure中使用了return关键字那么方法的返回值会被替换为ensure的返回值。
begin
  return 1     # 方法的返回值
ensure
  return 2     # 最终会返回这个值
end
事实上rescue,ensure这些关键字不仅可以用于begin/end之间,还可以用于def定义的模块、类、方法。
下面是resuce子句的一个变体,如果resuce前面的代码出现异常或者raise一个异常,那么resuce后面的代码就会执行:
y = factorial(x) rescue 0
上面的代码等同于:
y = begin
      factorial(x)
    rescue
      0
    end
这种写法的好处是可以省略begin/end,但必须在一行里完成,而且这种变态中只能处理StandardError异常,而不能出来其它类型的异常。