当前位置: 首页 > 文档资料 > Ruby 参考手册 >

类/方法的定义

优质
小牛编辑
134浏览
2023-12-01
  • 类定义
  • 特殊类定义
  • 模块定义
  • 方法定义
    • 方法定义的嵌套
    • 方法的计算
  • 特殊方法定义
  • 类方法的定义
  • 调用限制
  • 与定义有关的操作
    • 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.

调用限制

调用方法时,会受到以下三种限制,即publicprivateprotected.

  • 若方法属于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

使用privateprotected的目的是相同的(将对象隐藏起来,从外部不能调用).但是在下例中,不能使用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"