如果您想报告此常见问题解答中的错误或提出改进建议,请访问我们的 GitHub 仓库 并提交问题或拉取请求。
方法
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_reader
、attr_writer
或 attr_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
您还可以定义自己的访问器函数(可能用于执行验证或处理派生属性)。读取访问器只是一个不带参数的方法,而赋值访问器是一个以 =
结尾的方法名称,它接受单个参数。虽然方法名称和 =
之间在方法定义中不能有空格,但您可以在调用方法时在那里插入空格,使其看起来像任何其他赋值。您还可以利用诸如 +=
和 -=
之类的自赋值,只要定义了相应的 +
或 -
方法。
private
和 protected
之间有什么区别?
可见性关键字 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
是受保护的方法,则会编译,但如果它是私有的,则不会编译。
这些功能可以帮助您控制对类内部的访问。
如何更改方法的可见性?
您可以使用 private
、protected
和 public
更改方法的可见性。在类定义期间不带参数使用时,它们会影响后续方法的可见性。当与参数一起使用时,它们会更改命名方法的可见性。
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)
在类中定义的方法的默认可见性是 public。例外是实例初始化方法 initialize
。
在顶层定义的方法默认也是 public 的。
以大写字母开头的标识符可以是方法名称吗?
是的,它可以,但我们不会轻易这样做!如果 Ruby 看到一个大写的名称后跟一个空格,它可能会(取决于上下文)假设它是一个常量,而不是方法名称。因此,如果您使用大写的方法名称,请始终记住将参数列表放在括号中,并且始终将括号放在方法名称旁边,中间没有空格。(无论如何,最后一个建议都是一个好主意!)
调用 super
会给出 ArgumentError
。
在方法中调用不带参数的 super
会将该方法的所有参数传递给超类中同名的方法。如果原始方法的参数数量与更高级别方法的参数数量不一致,则会引发 ArgumentError
。为了解决这个问题,只需调用 super
并传递适当数量的参数即可。
如何调用向上两级的同名方法?
super
调用向上一级名称相同的方法。如果您在更远的祖先中重载方法,请使用 alias
在用您的方法定义屏蔽它之前给它一个新名称。然后,您可以使用该别名来调用它。
如何在重新定义后调用原始的内置方法?
在方法定义中,您可以使用 super
。您还可以使用 alias
来为其提供替代名称。最后,您可以将原始方法作为 Kernel
的单例方法来调用。
什么是破坏性方法?
破坏性方法是改变对象状态的方法。String
、Array
、Hash
和其他类都有这样的方法。通常,一个方法有两种版本,一个版本具有普通的名称,另一个版本具有相同的名称,但后面跟着 !
。普通版本创建接收者的副本,对其进行更改,并返回副本。“bang”版本(带有 !
)会修改接收者本身。
但是请注意,有相当多的破坏性方法没有 !
,包括赋值方法(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