内容 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11

官方 Ruby FAQ

如果您希望报告错误或对本 FAQ 提出改进建议,请访问我们的 GitHub 仓库,并提出 issue 或 pull request。

方法

Ruby 如何选择调用哪个方法?

Ruby 在运行时动态地将所有消息绑定到方法。它首先搜索接收者的单例方法,然后搜索接收者自身类中定义的方法,最后搜索接收者超类中定义的方法(包括任何可能被混入的模块)。您可以通过显示 ClassName.ancestors 来查看搜索顺序,它显示了 ClassName 的祖先类和模块。

如果在搜索完所有选项后仍找不到匹配的方法,Ruby 会尝试调用一个名为 method_missing 的方法,并重复相同的搜索过程来查找它。这允许您处理未知方法的调用,并经常用于为类提供动态接口。

module Emphasizable
  def emphasize
    "**#{self}**"
  end
end

class String
  include Emphasizable
end

String.ancestors
  # => [String, Emphasizable, Comparable, Object, Kernel, BasicObject]

"Wow!".emphasize  # => "**Wow!**"

当搜索 emphasize 方法时,如果在 String 类中找不到,Ruby 会接着在 Emphasizable 模块中搜索。

为了覆盖接收者类中已存在的方法,例如 String#capitalize,您需要使用 prepend 将模块插入到该类前面的祖先链中。

module PrettyCapitalize
  def capitalize
    "**#{super}**"
  end
end

class String
  prepend PrettyCapitalize
end

String.ancestors
  # => [PrettyCapitalize, String, Comparable, Object, Kernel, BasicObject]

"hello".capitalize  # => "**Hello**"

+-* 等是运算符吗?

+- 等不是运算符而是方法调用。因此,它们可以通过新的定义来重载。

class MyString < String
  def -(other)
    self[0...other.size]  # self truncated to other's size
  end
end

但是,以下是内置的控制结构,而不是方法,它们不能被重载。

=, .., ..., not, ||, &&, and, or, ::

要重载或定义一元 +- 运算符,您需要使用 +@-@ 作为方法名。

= 用于定义一个方法来设置对象的属性。

class Test
  def attribute=(val)
    @attribute = val
  end
end

t = Test.new
t.attribute = 1

如果定义了 +- 等运算符,Ruby 会自动处理自赋值形式(+=-= 等)。

在哪里可以找到 ++--

Ruby 没有自动增量和自动减量运算符。您可以改用 += 1-= 1

什么是单例方法?

单例方法是与特定对象关联的实例方法。

您可以通过在定义中包含对象来创建单例方法。

class Foo; end

foo = Foo.new
bar = Foo.new

def foo.hello
  puts "Hello"
end

foo.hello
bar.hello

产生

Hello
prog.rb:11:in `<main>': undefined method `hello' for #<Foo:0x000000010f5a40> (NoMethodError)

当您想为对象添加方法而不适合创建新子类时,单例方法非常有用。

所有这些对象都不错,但 Ruby 有简单的函数吗?

有,也有没有。Ruby 有一些看起来像 C 或 Perl 等语言中函数的方法。

def hello(name)
  puts "Hello, #{name}!"
end

hello("World")

产生

Hello, World!

然而,它们实际上是省略了接收者的方法调用。在这种情况下,Ruby 假定接收者是 self

因此,hello 看起来像一个函数,但实际上是 Object 类的一个方法,并作为消息发送给隐藏的接收者 self。Ruby 是一个纯粹的面向对象语言。

当然,您可以像使用函数一样使用这些方法。

那么这些函数式的方法都来自哪里?

Ruby 中的几乎所有类都继承自 Object 类。 Object 类的定义混入了 Kernel 模块中定义的方法。因此,这些方法在系统中的每个对象中都可用。

即使您编写简单的 Ruby 程序而不使用类,您实际上也是在 Object 类中进行操作。

我可以访问对象的实例变量吗?

对象的实例变量(那些以 @ 开头的变量)不能直接在对象外部访问。这促进了良好的封装。但是,Ruby 可以轻松地为您定义这些实例变量的访问器,以便您的类的用户可以像处理属性一样处理实例变量。只需使用 attr_readerattr_writerattr_accessor 中的一个或多个。

class Person
  attr_reader   :name           # read only
  attr_accessor :wearing_a_hat  # read/write

  def initialize(name)
    @name = name
  end
end

p = Person.new("Dave")
p.name           # => "Dave"
p.wearing_a_hat  # => nil
p.wearing_a_hat = true
p.wearing_a_hat  # => true

您还可以定义自己的访问器函数(也许是为了进行验证,或处理派生属性)。读访问器只是一个不带参数的方法,赋值访问器是一个以 = 结尾的方法名,它带有一个参数。虽然在方法定义中方法名和 = 之间不能有空格,但在调用方法时可以在此处插入空格,使其看起来像任何其他赋值。您还可以利用自赋值,如 +=-=,只要相应的 +- 方法已定义。

privateprotected 之间有什么区别?

可见性关键字 private 使方法只能以函数形式调用,没有显式接收者,因此它只能以 self 作为其接收者。私有方法只能在定义该方法的类或其子类中调用。

class Test
  def foo
    99
  end

  def test(other)
    p foo
    p other.foo
  end
end

t1 = Test.new
t2 = Test.new

t1.test(t2)

# Now make `foo' private

class Test
  private :foo
end

t1.test(t2)

产生

99
99
99
prog.rb:8:in `test': private method `foo' called for #<Test:0x00000000b57a48> (NoMethodError)
        from prog.rb:23:in `<main>'

保护方法也只能从其自身的类或其子类内部调用,但它们可以以函数形式和使用接收者来调用。例如:

def <=>(other)
  age <=> other.age
end

如果 age 是一个保护方法,这段代码将可以编译,但如果它是私有的则不行。

这些特性有助于您控制对类内部的访问。

我如何更改方法的可见性?

您可以使用 privateprotectedpublic 来更改方法的可见性。当在类定义期间不带参数使用时,它们会影响后续方法的可见性。当带参数使用时,它们会更改命名方法的可见性。

class Foo
  def test
    puts "hello"
  end
  private :test
end

foo = Foo.new
foo.test

产生

prog.rb:9:in `<main>': private method `test' called for #<Foo:0x0000000284dda0> (NoMethodError)

您可以使用 private_class_method 使类方法成为私有方法。

class Foo
  def self.test
    puts "hello"
  end
  private_class_method :test
end

Foo.test

产生

prog.rb:8:in `<main>': private method `test' called for Foo:Class (NoMethodError)

类中定义的方法的默认可见性是公共的。例外情况是实例初始化方法 initialize

在顶层定义的方法默认也是公共的。

以大写字母开头的标识符可以是方法名吗?

是的,可以,但我们不会轻易这样做!如果 Ruby 看到一个大写名称后面跟着一个空格,它很可能会(取决于上下文)假定它是一个常量,而不是方法名。所以,如果您使用大写方法名,请务必记得将参数列表放在括号中,并将括号始终放在方法名旁边,中间没有空格。(这个最后的建议总是个好主意!)

调用 super 会引发 ArgumentError

在方法中调用不带参数的 super 会将该方法的所有参数传递给超类中同名的方法。如果原始方法的参数数量与更高级别的方法不匹配,将引发 ArgumentError。要解决此问题,只需调用 super 并传递适当数量的参数。

如何调用向上两级的同名方法?

super 调用向上一个级别的同名方法。如果您正在重载一个更远的祖先中的方法,请在用您的方法定义屏蔽它之前,使用 alias 给它起一个新的名字。然后您可以使用该别名来调用它。

重定义后如何调用原始的内置方法?

在方法定义中,您可以使用 super。您还可以使用 alias 给它起一个别名。最后,您可以将原始方法作为 Kernel 的单例方法来调用。

什么是破坏性方法?

破坏性方法是指会改变对象状态的方法。StringArrayHash 等类都有此类方法。通常,一个方法会有两个版本,一个使用普通名称,另一个使用相同的名称,但后面跟着 !。普通版本会创建接收者的副本,对其进行更改,然后返回副本。“感叹号”版本(带有 !)会就地修改接收者。

但是请注意,有很多破坏性方法没有 !,包括赋值方法(name=)、数组赋值([]=)以及 Array.delete 等方法。

为什么破坏性方法可能很危险?

请记住,在大多数情况下,赋值只是复制对象引用,而参数传递等同于赋值。这意味着您可能会最终得到多个引用同一对象的变量。如果其中一个变量被用来调用破坏性方法,所有这些变量引用的对象都将被更改。

def foo(str)
  str.sub!(/foo/, "baz")
end

obj = "foo"
foo(obj)     # => "baz"
obj          # => "baz"

在这种情况下,实际参数会被修改。

我可以从方法中返回多个值吗?

有,也有没有。

def m1
  return 1, 2, 3
end

def m2
  [1, 2, 3]
end

m1  # => [1, 2, 3]
m2  # => [1, 2, 3]

因此,只返回一个东西,但这个东西可以是一个任意复杂的对象。对于数组,您可以使用多重赋值来达到多重返回值效果。例如:

def foo
  [20, 4, 17]
end

a, b, c = foo
a              # => 20
b              # => 4
c              # => 17