当我们最初写这本书时,我们有一个宏伟的计划(那时我们还年轻)。我们想从上到下记录语言,从类和对象开始,到细节的语法细节结束。这似乎是一个好主意。毕竟,Ruby 中的大多数东西都是对象,所以首先讨论对象是有意义的。

或者我们是这么认为的。

不幸的是,事实证明,以这种方式描述一种语言是很困难的。如果您没有涉及字符串、if 语句、赋值和其他细节,那么很难编写类的示例。在我们自上而下的描述中,我们不断遇到需要覆盖的低级细节,以便示例代码有意义。
所以,我们想出了另一个宏伟的计划(他们不称我们务实)。我们仍然会从顶部开始描述 Ruby。但在我们这样做之前,我们会添加一个简短的章节,描述示例中使用的所有通用语言特性以及 Ruby 中使用的特殊词汇,这是一种引导我们进入本书其余部分的小型教程。

Ruby 是一种面向对象的语言

我们再说一遍。 Ruby 是一种真正的面向对象语言。你操作的一切都是对象,而这些操作的结果本身就是对象。但是,许多语言都提出了相同的主张,并且它们对面向对象的含义通常有不同的解释,并且对它们所使用的概念有不同的术语。

因此,在我们深入细节之前,让我们简要地看一下我们将使用的术语和符号。

当您编写面向对象的代码时,您通常希望在代码中对来自现实世界的概念进行建模。通常在此建模过程中,您会发现需要在代码中表示的事物类别。在自动点唱机中,“歌曲”的概念可能就是这样一个类别。在 Ruby 中,您将定义一个类来表示这些实体中的每一个。类是状态(例如,歌曲的名称)和使用该状态的方法(可能是播放歌曲的方法)的组合。

拥有这些类后,您通常需要为每个类创建多个实例。对于包含一个名为 Song 的类的自动点唱机系统,您将有单独的实例用于流行的热门歌曲,例如“Ruby Tuesday”、“Enveloped in Python”、“String of Pearls”、“Small talk”等等。对象一词可以与类实例互换使用(作为懒惰的打字员,我们可能会更频繁地使用“对象”一词)。

在 Ruby 中,这些对象是通过调用构造函数来创建的,构造函数是与类关联的特殊方法。标准构造函数称为 new

  1. song1 = Song.new("Ruby Tuesday")
  2. song2 = Song.new("Enveloped in Python")
  3. # 等等

这些实例都派生自同一个类,但它们具有独特的特征。首先,每个对象都有一个唯一的对象标识符(缩写为 object id)。其次,您可以定义实例变量,即具有每个实例唯一值的变量。这些实例变量保存对象的状态。例如,我们的每首歌曲都可能有一个保存歌曲标题的实例变量。

在每个类中,您可以定义实例方法。每个方法都是一个功能块,可以从类内部调用,也可以从外部调用(取决于可访问性约束)。这些实例方法反过来可以访问对象的实例变量,从而访问对象的状态。

通过向对象发送消息来调用方法。消息包含方法的名称,以及该方法可能需要的任何参数。[这种以消息的形式表达方法调用的想法来自 Smalltalk。] 当一个对象收到一条消息时,它会在自己的类中查找相应的方法.如果找到,则执行该方法。如果找不到该方法,……好吧,我们稍后再谈。

这种方法和消息的业务可能听起来很复杂,但在实践中它是非常自然的。让我们看一些方法调用。 (请记住,代码示例中的箭头显示了相应表达式返回的值。)

  1. "gin joint".length » 9
  2. "Rick".index("c") » 2
  3. -1942.abs » 1942
  4. sam.play(aSong) » "duh dum, da dum de dum ..."

这里句号前面的东西叫做接收者,句号后面的就是要调用的方法。 第一个例子询问一个字符串的长度,第二个例子询问一个不同的字符串来查找字母“c”的索引。第三行有一个数字,计算它的绝对值。 最后,我们请 Sam 为我们播放一首歌。

这里值得注意的是 Ruby 和大多数其他语言之间的一个主要区别。 在(比如说)Java 中,您可以通过调用一个单独的函数并传入该数字来找到某个数字的绝对值。 你可能会写

  1. number = Math.abs(number) // Java 代码

在 Ruby 中,确定绝对值的能力内置于数字中 —— 它们在内部处理细节。 您只需将消息 abs 发送到数字对象并让它完成工作。

  1. number = number.abs

这同样适用于所有 Ruby 对象:在 C 中你会写 strlen(name),而在 Ruby 中它是 name.length,等等。 当我们说 Ruby 是一种真正的 OO 语言时,这就是我们要表达的意思的一部分。

一些 Ruby 基础

在学习一门新语言时,没有多少人喜欢阅读大量枯燥的语法规则。 所以我们要作弊。 在本节中,我们将介绍一些重点,如果您要编写 Ruby 程序,您只需要知道这些内容。 稍后,在从第 199 页开始的第 18 章中,我们将讨论所有血淋淋的细节。

让我们从一个简单的 Ruby 程序开始。 我们将编写一个返回字符串的方法,在该字符串中添加一个人的姓名。 然后我们将调用该方法几次。

  1. def sayGoodnight(name)
  2. result = "Goodnight, " + name
  3. return result
  4. end
  5. # 该睡了...
  6. puts sayGoodnight("John-Boy")
  7. puts sayGoodnight("Mary-Ellen")

首先,一些一般性观察。 Ruby 语法很干净。只要将每个语句放在单独的行上,语句的末尾就不需要分号。 Ruby 注释以 # 字符开头并一直运行到行尾。代码布局几乎取决于您;缩进不显着。

方法是用关键字 def 定义的,后跟方法名称(在这种情况下,“sayGoodnight”)和括号之间的方法参数。 Ruby 不使用大括号来分隔复合语句和定义的主体。相反,您只需使用关键字 end 完成正文。我们方法的主体非常简单。第一行将文字字符串“Goodnight,[可见空格]”连接到参数 name,并将结果分配给局部变量result。下一行将该结果返回给调用者。请注意,我们不必声明变量结果;当我们分配给它时,它就出现了。

定义方法后,我们调用它两次。在这两种情况下,我们都将结果传递给 puts 方法,该方法简单地输出其参数,后跟一个换行符。

  1. Goodnight, John-Boy
  2. Goodnight, Mary-Ellen

``puts sayGoodnight("John-Boy")‘’ 行包含两个方法调用,一个是 sayGoodnight,另一个是 puts。 为什么一个调用的参数在括号中,而另一个没有? 在这种情况下,这纯粹是一个品味问题。 以下行都是等效的。

  1. puts sayGoodnight "John-Boy"
  2. puts sayGoodnight("John-Boy")
  3. puts(sayGoodnight "John-Boy")
  4. puts(sayGoodnight("John-Boy"))

然而,生活并不总是那么简单,优先规则会让人很难知道哪个参数与哪个方法调用相关,所以我们建议在最简单的情况下使用括号。

此示例还显示了一些 Ruby 字符串对象。创建字符串对象的方法有很多,但最常见的可能是使用字符串文字:单引号或双引号之间的字符序列。这两种形式的区别在于 Ruby 在构造文字时对字符串的处理量。在单引号的情况下,Ruby 做的很少。除了少数例外,您在字符串文字中键入的内容将成为字符串的值。

在双引号的情况下,Ruby 做了更多的工作。首先,它查找替换 —— 以反斜杠字符开头的序列——并用一些二进制值替换它们。其中最常见的是 \n'',它被替换为换行符。当输出包含换行符的字符串时,\n’’ 强制换行。


摘自《Programming Ruby - The Pragmatic Programmer’s Guide》一书
版权所有 © 2001 Addison Wesley Longman, Inc. 本材料只能根据开放出版许可证 v1.0 或更高版本中规定的条款和条件分发(最新版本目前可在 http://www.opencontent .org/openpub/)。
未经版权所有者明确许可,禁止分发本文档的实质性修改版本。
除非事先获得版权所有者的许可,否则禁止以任何标准(纸质)书籍形式分发作品或作品的衍生作品。