来自其他语言的 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,则它们是相同的(指向内存中的同一个对象)。

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

现在看一下字符串 (“george”)。object_id 不匹配。这意味着它们引用内存中的两个不同对象。每当您使用新字符串时,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 之外的所有内容都被视为 true。在 C、Python 和许多其他语言中,0 和其他可能的值(例如空列表)被视为 false。请查看以下 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 是需要注意的一个。可以从类或后代类实例调用受保护的方法,但也可以使用另一个实例作为其接收器。这是一个示例(改编自 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 中,允许方法以问号或感叹号结尾。按照惯例,回答问题的方法以问号结尾(例如,Array#empty?,如果接收器为空,则返回 true)。按照惯例,潜在的“危险”方法以感叹号结尾(例如,修改 self 或参数的方法、exit! 等)。但是,并非所有更改其参数的方法都以感叹号结尾。Array#replace 将数组的内容替换为另一个数组的内容。有一个这样的方法而修改 self 并没有多大意义。

单例方法

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

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 知识时,请参阅我们的文档部分。