Ruby 简介

作者:Reuven M. Lerner

我们程序员很幸运能生活在今天。我之所以这么说,是因为现在有如此多优秀的编程语言可供选择,尤其是在开源世界中。

Ruby 是最受关注的语言之一。Ruby 实际上并不是什么新事物。Yukihiro “Matz” Matsumoto 在 1995 年发布了第一个公开版本,此后它的受欢迎程度一直在增长。随着 Web 开发的 Ruby on Rails 框架变得越来越流行,人们对 Ruby 的兴趣也随之飙升。

Ruby 经常被描述为 Perl 和 Smalltalk 的混合体,我认为这不失为一个看待它的好方法。当然,如果您有 Perl 和面向对象编程的经验,您可能会觉得使用 Ruby 非常得心应手。

在本文中,我将介绍 Ruby 的基础知识,展示它与其他高级语言的相似之处以及它添加的独特之处。在本文结束时,我希望您对 Ruby 有足够的了解,可以尝试将其用于一些小型应用程序。如果您像我一样,您会很快发现 Ruby 非常简洁优雅,可以快速轻松地编写可维护的代码。

基础知识

下载和安装 Ruby 非常容易,尤其因为许多 Linux 发行版都包含最新版本 (1.8.2)。您可以直接使用该版本,也可以从 Ruby 主站点安装最新版本 (1.8.4)。作为一款开源产品,您应该不会惊讶地发现 Ruby 主站点 (www.ruby-lang.org) 以 .tar.gz 格式提供源代码。其他格式,如 RPM 和 Debs,可从您喜欢的发行版的官方存储库获得。

如果您想从源代码安装最新版本的 Ruby,请下载并解压缩 .tar.gz 文件

$ cd Downloads
$ tar -zxvf ruby-1.8.4.tar.gz

现在使用标准的 configure 程序自动查找系统配置,使用 make 进行编译,然后使用 make test 确保编译后的 Ruby 版本工作正常

$ ./configure && make && make test

如果一切顺利,上述命令的最后一行输出将显示测试成功。现在您可以成为 root 用户并将 Ruby 安装到您的系统中

$ su
# make install

这会将各种 Ruby 程序和库安装到您的计算机上。

交互式 Ruby:Irb

Ruby 语言本身以可执行文件 ruby 的形式存在,您可以通过在命令行中键入它来手动运行

$ ruby

但是,此版本的 Ruby 专为非交互式使用而设计。要测试代码或试验 Ruby 语言,可以使用 irb,即交互式 Ruby shell。Irb 有点像调试器,因为它接受用户的输入(以按 Enter 键结束)并执行它。例如,输入

$ irb

然后,irb 会以其提示符响应

irb(main):001:0>

现在我们可以输入一些 Ruby 代码

irb(main):001:0> print "Hello, world"

然后,irb 会以以下内容响应

Hello, world=> nil

上面的输出表明 print 在屏幕上显示 Hello, world 并返回 nil 值;nil 是 Ruby 表示空值的方式,很像 Perl 中的 undef、Python 中的 None 和 SQL 中的 NULL。

与许多其他高级语言一样,Ruby 允许我们将值分配给变量,而无需预先声明它们。因此,我们可以写

greeting = "Hello, world"
print greeting

Ruby 也可以进行数学运算,使用熟悉的运算符 +、-、* 和 /

5 + 3
60 - 23
60 * 23
10 / 2

我在上面的行中省略了对 print 的调用,因为它在 irb 中是不必要的。但是,在独立的 Ruby 程序中,如果不使用 print,则不会将任何输出发送到屏幕(或其他地方)。

如果您是一位经验丰富的 Perl 程序员,您可能会对以下结果感到有些惊讶

5 / 2

上面返回 2,因为 5 和 2 都是整数,并且 Ruby 假定您要执行整数算术。要获得浮点结果,您必须确保至少有一个数字是浮点数

5 / 2.0

果然,它返回 2.5。与许多其他语言不同,Ruby 要求小数具有前导 0;您必须说 0.5,而不是 .5。

您可以使用 to_i 和 to_s 方法将字符串转换为整数或浮点数

"5".to_i
"5".to_f

Ruby 中的所有对象都有类似的 to_s 方法,该方法将对象转换为字符串。

Ruby 中令一些新手感到惊讶的一种数据类型是符号。您可以将符号视为特殊的字符串,它占用内存空间要小得多,尤其是在多个位置使用时。符号以冒号开头(例如,:reader),并非总是可以代替字符串使用,但它们允许程序员使程序更具可读性。它们有时也用于引用对象和方法,我将在本文后面解释。

插值和方法

与许多其他高级语言一样,Ruby 允许我们在双引号字符串内插值。(单引号字符串按字面意思理解,这在许多其他语言中都是惯例。)例如

name = "Reuven"
"Hello, #{name}"

上面的表达式等效于

Hello, Reuven

在 #{ } 中,我们可以放置任何 Ruby 表达式,而不仅仅是变量名

name = "Reuven"
print "Hello, #{name}. Your name is #{name.length} letters long."
print "Backwards, your name is '#{name.reverse}'."
print "Capitalized, your backwards name is '#{name.reverse.capitalize}'."

如您所见,插值允许我们将任意复杂的表达式放在双引号字符串中。但是请稍等一下——我们对表达式 name.length、name.reverse 和 name.reverse.capitalize 做了什么?

答案是字符串,就像 Ruby 中的所有内容一样,都是对象。我们对字符串执行的几乎任何操作都表示为方法,而不是独立的函数。如果您想反转字符串、获取其长度、将其大写或将其分解,您将使用 Ruby 的 object.message 语法在对象上调用方法。例如

name.reverse

上面的代码返回一个新的字符串对象,其值是 name 的反转。name 本身在此过程中不会改变。因为这个新的返回对象也是一个字符串,所以我们可以在其上调用任何字符串方法,包括 capitalize,正如我们之前看到的那样。Ruby 程序员经常将方法链接在一起以完成任务。

在对象实例上调用的方法通常在 Ruby 文档中称为 Object#method。因此,上面的方法将被称为 String#reverse。

我们如何知道特定对象将响应哪些方法?一种方法是询问对象它是什么类

name.class

我们还可以询问对象是否是特定类的成员

name.is_a?(String)

这可能看起来有点奇怪,既是因为方法名称中的问号,也因为跟在其后的参数。但它的工作方式与我们迄今为止调用的其他方法完全相同。我们向 name 发送 is_a? 消息,该消息返回布尔值(真或假)响应。is_a? 的参数是一个类名,即 String。

如果我们不想查找 Ruby 字符串的 API 文档,我们可以直接询问对象本身它将响应哪些方法

name.methods

这将返回 name 响应的方法数组(即列表)。我们稍后会介绍数组,但重要的是要认识到 name.methods 不是字符串;相反,它是一个数组,其内容恰好是字符串。但是,数组会响应内置的 sort 方法,该方法返回一个新数组,其内容已排序

name.methods.sort

我可能每天至少调用一次 OBJECT.methods.sort,而不是翻阅书籍或在线 API 来查找 Ruby。

数组和哈希

如果您过去使用过 Perl 或 Python,您不会惊讶地发现 Ruby 具有内置的数组(如上所述)和哈希。我们使用方括号创建数组

an_array = [1, "two", true]

一个数组可以包含任意数量的对象,并且每个对象可以是任何类型,包括另一个数组。上面的数组包含三个对象(类型分别为 Fixnum、String 和 Boolean)。数组中的每个项目都有一个唯一的索引;第一个元素的索引为 0。我们可以按如下方式检索项目

an_array[1]

上面的表达式返回"TWO",即 an_array 中索引为 1 的项目。数组是可变的,这意味着我们可以通过分配给该索引来替换任何项目

an_array[1] = "TWO"

我们可以使用负索引从数组末尾开始计数;因此 an_array[-1] 返回布尔值 true。我们还可以通过传递用逗号分隔的两个索引来查看原始数组的子集,指示我们想要的第一个和最后一个索引

an_array[0,1]

要将数组的所有元素组合成一个字符串,我们可以使用 join 方法,例如

an_array.join(", ")

上面的代码创建一个字符串,其内容来自 an_array 的值,每对元素之间用“,”分隔。

哈希类似于数组,不同之处在于它们不是使用有序的数字索引存储值,而是使用键存储值,例如

my_hash = {'a' => 1, 'b' => 2}

我们现在可以通过使用其键来检索两个值中的任何一个

my_hash['a']
my_hash['b']

上面的代码行分别返回数字 1 和 2。与数组一样,我们可以将任何对象存储为哈希中的值;它不必是整数。

我们可以分别使用 Hash#keys 和 Hash#values 方法检索哈希的键和值。(稍后,我将解释如何迭代键和值以从哈希中检索内容。)但是,有时我们只想知道哈希中是否存在特定键。这很容易通过 Hash#has_key? 来完成,它接受一个字符串作为参数并返回一个布尔值。因此,以下代码将返回 true

my_hash.has_key?("a")
条件语句

每种语言都允许我们有条件地执行代码。在 Ruby 中,这通常使用 if 语句完成。考虑以下(有些牵强的)示例

if server_status == 0
print "Server is in single-user mode"
elsif server_status == 1
print "Server is being fixed "
elsif network_response == 3
print "Server is available"
else
print "Network response was unexpected value '#{network_response}'"
end

请注意,Ruby 不需要条件周围的括号。虽然条件不必返回布尔值,但如果您尝试在条件中使用 = (即赋值)而不是 == (即比较),Ruby 会产生警告。== 比较运算符适用于所有对象;与 Perl 中一样,没有单独的文本比较和数字比较运算符。< 和 > 也是如此,它们可用于比较字符串和数字。最后,Ruby 不使用开括号或闭括号;相反,它使用 end 关闭有条件执行的代码块。

与 Perl 一样,您可以使用 if 和 unless 作为后缀来使语句有条件

print "We won!" if our_score > their_score
print "Here is your change of #{amount_paid - price}!"
    unless amount_paid <= price

您也可以执行以下操作

if inputs.length < 4
    print "Not enough inputs!\n"
end

以及

if not my_hash.has_key?("debug")
    print "Debugging is inactive.\n"
end
循环

Ruby 确实有一些循环运算符,例如 for 和 while。但真正的乐趣和刺激在于执行如下操作

5.times {print "hello\n"}

考虑一下——我们正在使用标准的 Ruby 方法调用语法在数字上调用方法。整数的 times 方法执行特定次数的代码块。因此,上面的代码行执行五次,每次打印单词 hello(后跟换行符)。

块也可以接受参数,参数位于管道 (|) 字符之间

5.times {|iteration| print "Hello, iteration number #{iteration}.\n"}

我们也可以使用 each 方法迭代数组的元素

an_array = ['Reuven', 'Shira', 'Atara', 'Shikma', 'Amotz']
an_array.each {|name| print "#{name}\n"}

each 方法的一个变体称为 each_with_index,它需要一个接受两个参数的块。第一个参数是项目,第二个参数是索引

an_array = ['Reuven', 'Shira', 'Atara', 'Shikma', 'Amotz']
an_array.each_with_index {|name, index| print "#{index}: #{name}\n"}

在某种程度上,块在这种语法中变得难以阅读。Ruby 提供了一种替代语法,将花括号替换为 do 和 end

an_array = ['Reuven', 'Shira', 'Atara', 'Shikma', 'Amotz']
an_array.each_with_index do |name, index|
    print "#{index}: #{name}\n"
end

我们可以通过多种方式迭代哈希。一种方法是使用 Perl 和 Python 程序员多年来使用过的迭代类型,获取哈希的键(通过 Hash#keys,它返回一个数组),然后获取与键关联的值

state_codes = {'Illinois' => 'IL', 'New York' => 'NY',
               'New Jersey' => 'NJ', 'Massachusetts' => 'MA',
               'California' => 'CA'}

state_codes.keys.each do |state|
    print "State code for #{state} is #{state_codes[state]}.\n"
end

当然,我们可能希望在迭代键之前对其进行排序

state_codes.keys.sort.each do |state|
    print "State code for #{state} is #{state_codes[state]}.\n"
end

Ruby 提供了一种更简单的方法来执行此任务,即 each_pair 方法

state_codes.each_pair do |state, code|
    print "State code for #{state} is #{code}.\n"
end
类和方法

最后,我们可以将所有这些放在一起,定义一个类和一些方法。我们可以在 irb 中或 Ruby 中的任何其他位置创建一个类,只需说

class Simple
end

果然,我们设法仅用两行代码创建了一个类。这足以创建 Simple 类型的对象吗?让我们看看

foo = Simple.new
foo.class

似乎是这样;我们的变量 foo 声称它是 Simple 类的。我们没有指定 Simple 对象从哪个对象继承,因此它自动从 Object(最终的 Ruby 超类)继承。Ruby 仅支持单继承,这在类定义中声明为

class SimpleArray < Array
end

我们已经定义了两个类,这很好,但我们尚未定义任何特定于这些类的方法。Ruby 允许我们随时打开一个类,在类中添加或替换方法。我们使用 def 语句定义方法,指示该方法是否接受任何参数,例如

class Simple
    def id_squared
        return self.object_id * self.object_id
    end
end

我们定义的方法非常简单,它做了一些我认为我们永远都不想做的事情——即,它获取对象的唯一 ID(通过继承的方法 object_id 可用)并返回其加倍的值(这很可能是一个 Bignum 实例)。

如果我们在 irb 中键入上面的定义,就会发生一些令人惊奇的事情:我们 Simple 类的 foo 变量现在响应 Simple#id_squared 方法!是的,Ruby 允许我们动态修改方法并打开现有类。例如,我们可以修改内置的 Array 或 String 类,用我们自己的方法替换内置方法。

最后,我们可能希望在对象中存储一些状态。这是通过实例变量完成的。在 Ruby 中,实例变量以 @ 字符开头,如果您来自 Perl 世界,这可能会有点令人困惑

class Simple
    def initialize
        @simple_data = [ ]
    end
end

每当我们创建 Simple 的新实例时,都会调用特殊的 initialize 方法。因此,如果我们再次将 foo 定义为 Simple 的实例

foo = Simple.new

我们可以看到 foo 现在定义了一个实例变量,方法是调用

foo.instance_variables

上面返回一个数组

["@simple_data"]

我们如何分配给 @simple_data?我们又如何检索其值?一种方法是定义许多方法:一种用于写入此实例变量,另一种用于检索其值。但一种简写方法是使用 attr_reader 和 attr_writer 方法

class Simple
    attr_reader :simple_data
    attr_writer :simple_data
end

上面的代码告诉 Ruby 我们有一个名为 @simple_data 的实例变量,并且我们希望创建一些方法,这些方法将允许我们读取和设置其值。您可以在此处看到符号如何允许我们通过不是字符串但也不是字面变量的东西来引用实例变量。有了这个,我们可以做这样的事情

foo = Simple.new
foo.simple_data = 'abc'
foo.simple_data = [1, 2, 3]
print foo.simple_data.join(', ')
结论

Ruby 在过去一两年中变得非常流行,这在很大程度上是因为 Ruby on Rails 在 Web 开发人员中的发展。即使没有 Rails,Ruby 也值得它所受到的很多关注。所有数据都存储在对象中、方法和块结构的简洁性和优雅性,以及标准库中包含的大量对象,所有这些都构成了一种令人印象深刻的语言。

本文没有空间介绍一些 Ruby 程序员会感兴趣的其他功能,例如模块、类变量、与文件的输入/输出、网络、XML 解析、Internet 上提供的 RubyGems 库以及对正则表达式的内置支持。Ruby 是一种丰富的语言,但它相当一致且易于学习——假设您已经具备一些面向对象编程的背景知识,我认为这是理解 Ruby 的最大障碍。

Ruby 仍然有许多问题需要解决,包括其相对较慢的速度和缺乏 Unicode 支持,但这些问题正在为未来的版本解决,并且社区是我见过的最强大的社区之一。

在过去的一年中,我越来越多地使用 Ruby,并且对这门语言印象深刻。我建议您也尝试一下 Ruby。即使您不将其作为您的主要编程语言,它也会让您以新的方式思考,并且它也可能使其他语言的编程更加愉快。

本文的资源: /article/9017

Reuven M. Lerner,一位资深的 Web/数据库顾问,目前是伊利诺伊州埃文斯顿西北大学学习科学专业的博士生。他和他的妻子最近庆祝了他们的儿子 Amotz David 的诞生。

加载 Disqus 评论