参考手册:Ruby的语法——类/方法的定义

类定义

例:

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部分。

类定义实际上就是把类赋值给由类名指定的常数(在php?name=Ruby" onclick="tagshow(event)" class="t_tag">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 k  # 变为 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"