类/方法的定义
- 类定义
- 特殊类定义
- 模块定义
- 方法定义
- 方法定义的嵌套
- 方法的计算
- 特殊方法定义
- 类方法的定义
- 调用限制
- 与定义有关的操作
- alias
- undef
- 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"