从其他语言学习 Ruby

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

本文档包含两个主要部分。第一部分试图快速总结从语言X到 Ruby 的预期内容。第二部分讨论主要语言特性以及它们与你已经熟悉的语言的比较。

预期:从语言 X到 Ruby

重要的语言特性和一些注意事项

以下是一些关于学习 Ruby 时会遇到的主要 Ruby 特性的提示和建议。

迭代

Ruby 的两个特性与你之前可能见过的有些不同,需要一些时间来适应,它们是“块”和迭代器。与遍历索引(如 C、C++ 或 1.5 之前的 Java)或遍历列表(如 Perl 的 for (@a) {...} 或 Python 的 for i in aList: ...)不同,在 Ruby 中,你经常会看到

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

符号不是轻量级的字符串

许多 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 相同,则它们是相同的(指向内存中的同一个对象)。

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

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

如果您不确定是使用 Symbol 还是字符串,请考虑哪一个更重要:对象的标识(例如 Hash 键)还是内容(在上面的例子中是 “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 实际上是方法,因此它们可以接受参数。如果您向其中一个方法传递一个 Symbol,则该方法的可见性将被更改。

方法访问

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

Ruby 有些不同。 public 自然是公开的。 private 表示方法仅在没有显式接收者的情况下才能调用时才可访问。 只有 self 才能作为私有方法调用的接收者。

protected 是需要注意的。 受保护的方法可以从类或子类实例调用,也可以使用另一个实例作为其接收者。 以下是一个示例(改编自 Ruby 语言常见问题解答

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 中,方法允许以问号或感叹号结尾。 按照惯例,回答问题的 method 以问号结尾(例如 Array#empty?,如果接收者为空,则返回 true)。 按照惯例,潜在的“危险”方法以感叹号结尾(例如修改 self 或参数的方法,exit! 等)。 不过,并非所有更改其参数的方法都以感叹号结尾。 Array#replace 用另一个数组的内容替换数组的内容。 这样一种修改 self 的方法没有多大意义。

单例方法

单例方法是每个对象的 method。 它们仅在您定义它的对象上可用。

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

块是对象,只是它们还没有意识到这一点

块(实际上是闭包)被标准库广泛使用。要调用块,您可以使用 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 知识时,请查看我们的 文档 部分。