其他语言的 Ruby

当你第一次看到一些 Ruby 代码时,它可能会让你想起你用过的其他编程语言。这是故意的。许多语法对于 Perl、Python 和 Java(以及其他语言)的用户来说都很熟悉,所以如果你用过这些语言,学习 Ruby 将会是小菜一碟。

本文档包含两个主要部分。第一部分试图快速总结一下从语言 X 到 Ruby 会看到什么。第二部分将介绍主要的语言特性以及它们与你熟悉的事物相比如何。

预期:语言 X 到 Ruby

重要的语言特性和一些陷阱

以下是你在学习 Ruby 时会看到的一些主要 Ruby 特性的要点和提示。

迭代

Ruby 中有两个特性可能与你之前见过的有所不同,并且需要一些时间来适应,那就是“块”(blocks)和迭代器(iterators)。Ruby 中你不会像 C、C++ 或 1.5 版本之前的 Java 那样通过索引来循环,也不会像 Perl 的 for (@a) {...} 或 Python 的 for i in aList: ... 那样通过列表来循环,而你将非常频繁地看到:

some_list.each do |this_item|
  # We're inside the block.
  # deal with this_item.
end

有关 each(及其朋友 collectfindinjectsort 等)的更多信息,请参阅 ri Enumerable(然后是 ri Enumerable#some_method)。

一切都有值

表达式和语句之间没有区别。一切都有值,即使那个值是 nil。这是可能的。

x = 10
y = 11
z = if x < y
      true
    else
      false
    end
z # => true

符号(Symbol)不是轻量级的字符串

许多 Ruby 新手在理解符号是什么以及它们可以用来做什么方面会遇到困难。

符号最好被描述为身份。符号的重点在于它是谁,而不是它是什么。启动 irb 并查看区别。

irb(main):001:0> :george.object_id == :george.object_id
=> true
irb(main):002:0> "george".object_id == "george".object_id
=> false
irb(main):003:0>

方法 object_id 返回对象的身份。如果两个对象具有相同的 object_id,则它们是相同的(指向内存中的同一个对象)。

如你所见,一旦你使用过一个符号,任何具有相同字符的符号都会引用内存中相同的对象。对于表示相同字符的任意两个符号,它们的 object_id 都会匹配。

现在看看字符串(“george”)。object_ids 不匹配。这意味着它们指向内存中的两个不同对象。每当你使用一个新的字符串时,Ruby 都会为其分配内存。

如果你不确定是使用符号还是字符串,请考虑什么更重要:对象的身份(例如,哈希的键)还是内容(在上面的例子中是“george”)。

一切皆对象

“一切皆对象”不仅仅是夸张的说法。即使是类和整数也是对象,你可以用它们做任何其他对象能做的事情。

# This is the same as
# class MyClass
#   attr_accessor :instance_var
# end
MyClass = Class.new do
  attr_accessor :instance_var
end

变量常量

常量并非真正意义上的常量。如果你修改了一个已经初始化的常量,它会触发一个警告,但不会中断你的程序。但这并不意味着你应该重新定义常量。

命名约定

Ruby 强制执行一些命名约定。如果一个标识符以大写字母开头,它就是一个常量。如果它以美元符号($)开头,它就是一个全局变量。如果它以 @ 开头,它就是一个实例变量。如果它以 @@ 开头,它就是一个类变量。

然而,方法名可以以大写字母开头。这可能会导致混淆,如下面的示例所示。

Constant = 10
def Constant
  11
end

现在 Constant 是 10,而 Constant() 是 11。

关键字参数

与 Python 类似,从 Ruby 2.0 开始,方法可以定义为使用关键字参数。

def deliver(from: "A", to: nil, via: "mail")
  "Sending from #{from} to #{to} via #{via}."
end

deliver(to: "B")
# => "Sending from A to B via mail."
deliver(via: "Pony Express", from: "B", to: "A")
# => "Sending from B to A via Pony Express."

普适真理

在 Ruby 中,除了 nilfalse 之外,所有其他值都被认为是真的。在 C、Python 和许多其他语言中,0 以及可能的其他值,如空列表,被认为是假的。看看下面的 Python 代码(该示例也适用于其他语言)。

# in Python
if 0:
  print("0 is true")
else:
  print("0 is false")

这将打印“0 is false”。等效的 Ruby 代码:

# in Ruby
if 0
  puts "0 is true"
else
  puts "0 is false"
end

打印“0 is true”。

访问修饰符应用到作用域结束

在下面的 Ruby 代码中:

class MyClass
  private
  def a_method; true; end
  def another_method; false; end
end

你可能会期望 another_method 是公共的。并非如此。private 访问修饰符会一直持续到作用域结束,或者直到出现另一个访问修饰符为止,以先到者为准。默认情况下,方法是公共的。

class MyClass
  # Now a_method is public
  def a_method; true; end

  private

  # another_method is private
  def another_method; false; end
end

publicprivateprotected 实际上是方法,所以它们可以接受参数。如果你向其中一个传递一个符号,该方法的可见性将被更改。

方法访问

在 Java 中,public 意味着任何人都可以在任何地方访问该方法。protected 意味着该类的实例、子类的实例以及同一包中的类的实例可以访问它,但其他人不能,而 private 意味着除了该类的实例之外,没有人可以访问该方法。

Ruby 略有不同。public 自然是公共的。private 意味着方法只能在不使用显式接收者的情况下调用。只有 self 才允许成为私有方法调用的接收者。

要特别留意 protected。受保护的方法可以从类或子类的实例中调用,也可以使用另一个实例作为其接收者。这是一个例子(改编自The Ruby Language FAQ)。

class Test
  # public by default
  def identifier
    99
  end

  def ==(other)
    identifier == other.identifier
  end
end

t1 = Test.new  # => #<Test:0x34ab50>
t2 = Test.new  # => #<Test:0x342784>
t1 == t2       # => true

# now make `identifier' protected; it still works
# because protected allows `other' as receiver

class Test
  protected :identifier
end

t1 == t2  # => true

# now make `identifier' private

class Test
  private :identifier
end

t1 == t2
# NoMethodError: private method `identifier' called for #<Test:0x342784>

类是开放的

Ruby 类是开放的。你可以随时打开它们、向它们添加内容、更改它们。即使是核心类,如 Integer 甚至是所有对象的父类 Object。Ruby on Rails 为 Integer 定义了许多处理时间的方法。请看。

class Integer
  def hours
    self * 3600 # number of seconds in an hour
  end
  alias hour hours
end

# 14 hours from 00:00 January 1st
# (aka when you finally wake up ;)
Time.mktime(2006, 01, 01) + 14.hours # => Sun Jan 01 14:00:00

有趣的函数名

在 Ruby 中,方法名允许以问号或感叹号结尾。按照惯例,回答问题的函数名以问号结尾(例如 Array#empty?,如果接收者为空则返回 true)。潜在的“危险”方法名按惯例以感叹号结尾(例如,修改 self 或参数的方法,exit! 等)。并非所有改变参数的方法都以感叹号结尾。Array#replace 用另一个数组的内容替换一个数组的内容。让一个方法不修改自身而有这样的行为不太有意义。

单例方法

单例方法是针对特定对象的。它们只在定义它们的对象上可用。

class Car
  def inspect
    "Cheap car"
  end
end

porsche = Car.new
porsche.inspect # => Cheap car
def porsche.inspect
  "Expensive car"
end

porsche.inspect # => Expensive car

# Other objects are not affected
other_car = Car.new
other_car.inspect # => Cheap car

丢失的方法

如果 Ruby 找不到响应特定消息的方法,它不会放弃。它会调用 method_missing 方法,并将找不到的方法名和参数传递给它。默认情况下,method_missing 会引发一个 NameError 异常,但你可以重新定义它以更好地适应你的应用程序,许多库都这样做。这是一个例子。

# id is the name of the method called, the * syntax collects
# all the arguments in an array named 'arguments'
def method_missing(id, *arguments)
  puts "Method #{id} was called, but not found. It has " +
       "these arguments: #{arguments.join(", ")}"
end

__ :a, :b, 10
# => Method __ was called, but not found. It has these
# arguments: a, b, 10

上面的代码只是打印了调用的详细信息,但你可以自由地以任何合适的方式处理消息。

消息传递,而非函数调用

方法调用实际上是向另一个对象发送一条消息

# This
1 + 2
# Is the same as this ...
1.+(2)
# Which is the same as this:
1.send "+", 2

块(Blocks)是对象,只是它们还不知道

块(实际上是闭包)被标准库大量使用。要调用一个块,你可以使用 yield,或者通过在参数列表末尾附加一个特殊参数将其变成一个 Proc,如下所示。

def block(&the_block)
  # Inside here, the_block is the block passed to the method
  the_block # return the block
end
adder = block { |a, b| a + b }
# adder is now a Proc object
adder.class # => Proc

你也可以在方法调用之外创建块,通过调用带有块的 Proc.new 或调用 lambda 方法。

同样,方法也是潜在的对象。

method(:puts).call "puts is an object!"
# => puts is an object!

运算符是语法糖

Ruby 中的大多数运算符只是方法调用的语法糖(带有一些优先级规则)。例如,你可以覆盖 Integer 的 + 方法。

class Integer
  # You can, but please don't do this
  def +(other)
    self - other
  end
end

你不需要 C++ 的 operator+ 等。

你甚至可以通过定义 [][]= 方法来实现类数组访问。要定义一元加号和减号(例如 +1 和 -2),你必须分别定义 +@-@ 方法。然而,下面的运算符不是语法糖。它们不是方法,不能被重新定义。

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

此外,+=*= 等只是 var = var + other_varvar = var * other_var 等的缩写,因此不能被重新定义。

了解更多

当你准备好学习更多 Ruby 知识时,请参阅我们的文档部分。