Ruby 快速入门

503 阅读7分钟

1、Ruby 简介

Ruby 发明者的理想

Ruby 是一个注重均衡的语言,它的发明者松本行弘(Yukihiro “Matz” Matsumoto),混合了他喜欢的多门语言(Perl、Smalltalk、Eiffel、Ada 和 Lisp),创造出了一种兼具函数式编程和命令式编程特色的新语言。 他常说,他是“试着让 Ruby 更自然,而不是更简单”,让一切符合生活常规。 除此之外,他还提到: Ruby 就像人的身体一样,表面上看来简单,但是内部却相当复杂。1

Ruby 的成长

自从 1995 年公开发布以来,Ruby 在全球吸引了许多忠实的程序员。2006 年,Ruby 得到广泛接受,各大城市都有活跃的用户组,以及 Ruby 相关的开发者大会。

把一切视为对象

最初,Matz 从其它语言中找寻理想的语法。回想他的研究,他说,“我想要一种比 Perl 更强大、比 Python 更面向对象的脚本语言”。2

在 Ruby 中,一切皆对象。所有的信息和代码都拥有属性和行为。面向对象编程称属性为实例变量(instance variables),称行为为方法(methods)。 从下列代码可以看出,Ruby 能给数字赋于行为,从这一点可以证明,Ruby 是纯面向对象的语言。

5.times { print "We *love* Ruby -- it's outrageous!" }

Ruby 的灵活性

Ruby 是一门相当灵活的语言,允许用户改变自身。 Ruby 的核心部分可以更改,也可以重新定义。此外,还可以在现有功能的基础上增加新功能。Ruby 不想阻碍程序员的创造力。

代码块:表现力强大的特性

Ruby 的代码块非常灵活。程序员可以给任何方法添加闭包,指明方法该如何工作。闭包也叫代码块,是从其他命令式语言(比如 PHP、Visual Basic 等)转到 Ruby 的初学者最喜欢使用的特性。

代码块取自函数式语言。Matz 说:“我希望在 Ruby 的闭包中融入 Lisp 的文化。”3

search_engines =
  %w[Google Yahoo MSN].map do |engine|
    "http://www." + engine.downcase + ".com"
  end

在上述代码中,代码块使用 do ... end 结构表述。map 方法把代码块应用到单词列表上。Ruby 中有很多方法都留出了切入点,让程序员编写代码块,控制方法具体的操作细节。

Ruby 中的混入

与其他面向对象语言不同,Ruby“有意”只提供单继承。不过 Ruby 有模块(在 Objective-C 中叫做类别)。模块是一系列方法。

Ruby 的视觉呈现

尽管 Ruby 只用很少的符号,而且偏向使用英文单词做关键字,但是也用一些符号装饰 Ruby。在 Ruby 中,不需要提前声明变量。Ruby 使用简单的命名约定指明变量的作用域。

  • var 可能是局部变量
  • @var 是实例变量
  • $var 是全局变量

这些符号能让程序员轻易识别变量的作用。此外,实例成员前面无需加上烦人的 self.

Ruby 的其他实现

作为一门语言,Ruby 有不同的实现。本页讨论的是推荐的实现,社区通常称之为 MRI(“Matz’s Ruby Interpreter”)或 CRuby(因为是用 C 语言写的)。不过,还有一些别的实现。其他实现通常在特定的场合中有用,集成了其他语言或环境,或者有 MRI 不具有的特性。

下面列出一些其他实现:

  • JRuby 是基于 JVM(Java Virtual Machine)的 Ruby 实现,利用了 JVM 中优秀的 JIT 编译器、垃圾回收程序、并发线程、工具生态系统和大量的库。
  • Rubinius 是用“Ruby 编写的 Ruby”。构建于 LLVM 之上,Rubinius 跑在一个很灵活的虚拟机上,别的语言也可以构建于这个虚拟机上。
  • mruby 是 Ruby 语言的轻量级实现,可以链接或嵌入到程序之中。mruby 由 Ruby 的创建者松本行弘(Matz)领导开发。
  • IronRuby 是一个“与 .NET 框架紧密集成”的实现。
  • MagLev 是“一个快速、稳定的 Ruby 实现,支持集成对象持久化和分布式共享缓存”。
  • Cardinal 是一个“为 Parrot 虚拟机 (Perl 6)编写的 Ruby 编译器”。

2、Ruby 下载安装

下载地址

安装 Ruby 的方法

每个流行的平台都有多种工具可用于安装 Ruby:

  • Linux/UNIX 平台,可以使用第三方工具(如 rbenv 或 RVM)或使用系统中的包管理系统。
  • macOS 平台,可以使用第三方工具(如 rbenv 或 RVM)。
  • Windows 平台,可以使用 RubyInstaller

安装完成测试

    ruby -v

image.png

3、基础使用

"Hello world" 程序代码

helloworld.rb 内容如下:

puts 'hello world'

这就是完整的代码。用到了一个 puts 方法和一个 'Hello world' 字符串。没有文件头或者类定义,也不需要导入其他代码和 main 方法。这真的就是那么简单。

运行结果如下:

image.png

获取并保存输入信息

**使用 irb交互是操作 **

print( 'Enter your name: ' )
name = gets() 
puts( "Hello #{name}" )

操作结果如下:

image.png

首先,注意我输出提示的时候使用的是 print 方法而不是 puts 方法。这是因为 puts 方法会在末尾自动添加一个换行符,但 print 方法则不会;而当前我希望光标和提示能在同一行显示。

在下一行,当用户按下 Enter 键时,使用 gets() 方法读取用户的输入并以字符串类型保存。该字符串会被赋值给 name 变量(variable)。没有预先声明该变量,也没有指定它的类型。在 Ruby 中,你可以根据需要去创建变量,并且 Ruby 会自动去推断该变量的类型。现在我将一个字符串赋值给了 name,因此 Ruby 推断 name 变量的类型一定是字符串(String)。

注意:Ruby 是大小写敏感的。 一个名为 myvar 的变量和名为 myVar 的变量是不同的。一个和示例程序中 name 一样的变量,它的名字必须以小写字母开头(如果以大写字母开头,Ruby 会认为它是一个常量(constant))

字符串与内嵌表达式

puts( "Hello #{name}" )

这里的 name 变量被嵌入到字符串(String)本身中。这是通过将变量放置于两个花括号中并在花括号前面加一个 # 字符实现,也就是 #{} 。这种嵌入式表达式仅限于使用双引号分隔的字符串中起作用。如果你尝试在单引号分隔的字符串中使用它,该变量将不会被执行(解释),恰恰显示的将会是字符串  'Hello #{name}'

不仅仅只有变量可以嵌入到双引号分隔的字符串中。你也可以嵌入非打印(转义)字符,例如换行符 \n 和制表符 \t 。你甚至也可以嵌入程序代码和数学表达式。

数字

数字(Numbers)和字符串一样容易使用。例如,你想基于税率值和合计值来计算一些东西的销售价格或者总的合计值。为此,你需要将合计值乘以合适的税率并将结果加上合计值。假设合计值为 100 美元,税率为 17.5% ,这个 Ruby 程序会进行计算并显示结果:

subtotal = 100.00
taxrate = 0.175 
tax = subtotal * taxrate
puts "Tax on $#{subtotal} is $#{tax}, so grand total is $#{subtotal+tax}"

测试条件语句:if ... then

上面的税率值计算代码的问题是允许负的合计值和税率,这种情况在政府看来可能是不利的。因此,我需要测试负数,如果出现负数将其置为 0 。这是我的新版代码:

taxrate = 0.175
print "Enter price (ex tax): "
s = gets
subtotal = s.to_f

if (subtotal < 0.0) then
subtotal = 0.0
end

tax = subtotal * taxrate
puts "Tax on $#{subtotal} is $#{tax}, so grand total is $#{subtotal+tax}"

Ruby 中的 if 测试语句与其他编程语言中的 if 相似。注意,这里的括号也是可选的,then 也一样。但是,你如果在测试条件之后没有换行符的情况下继续写代码,那么 then 不能省略:

if (subtotal < 0.0) then subtotal = 0.0 end

局部变量与全局变量

在前面的示例中,我将值赋给了变量,例如 subtotaltax 和 taxrate 。这些以小写字母开头的变量都是局部变量(Local variables),这意味着它们只存在于程序的特定部分。换句话说,它们被限制一个定义明确的作用域(scope)内。这是一个实例:

localvar = "hello"
$globalvar = "goodbye"

def amethod
  localvar = 10
  puts(localvar)
  puts($globalvar)
end

def anotherMethod
  localvar = 500
  $globalvar = "bonjour"
  puts(localvar)
  puts($globalvar)
end

这里有三个名为 localvar 的局部变量,一个在 main 作用域内被赋值为 "hello" ;其它的两个分别在独立的方法作用域内被赋值为整数(Integers):因为每一个局部变量都有不同的作用域,赋值并不影响在其它作用域中同名的局部变量。你可以通过调用方法来验证:

amethod           #=> localvar = 10
anotherMethod     #=> localvar = 500
amethod           #=> localvar = 10
puts( localvar )  #=> localvar = "hello"

另一方面,一个以  $  字符开头的全局变量拥有全局作用域。当在一个方法中对一个全局变量进行赋值,同时也会影响程序中其它任意作用域中的同名全局变量:

amethod          #=> $globalvar = "goodbye"
anotherMethod    #=> $globalvar = "bonjour"
amethod          #=> $globalvar = "bonjour"
puts($globalvar) #=> $globalvar = "bonjour"

类与对象

和大多数其它 OOP(面向对象编程)的语言一样,一个 Ruby 对象由类来定义,这个类就像一个从中构建多个单个对象的蓝图。例如,这个类定一只狗:

class Dog
  def set_name( aName )
    @myname = aName
  end
end

注意,类的定义以关键字 class(全部小写)和类名开始,并且类名必须以大写字母开头。这个类包含一个 set_name 方法,它需要传入一个参数 aName,方法体则是将 aName 赋值给一个 @myname 变量。

实例变量

以 @ 符号开头的变量就是“实例变量”——这意味着它们属于单独的对象或者类的实例。实例变量不需要提前声明。我可以通过调用类的 new 方法来创建 Dog 类的实例(即 dog 对象)。在这里我创建两个两个 dog 对象(注意,虽然类名是以大写字母开头的,而实例对象名则是以小写字母开头的):

mydog = Dog.new 
yourdog = Dog.new

目前,这两只狗还没有名字。所以,接下来我将要做的是调用 set_name 方法来给它们起个名字:

mydog.set_name( 'Fido' )
yourdog.set_name( 'Bonzo' )

现在每只狗都有了名字,但是我以后需要通过某些途径能获知它们的名字。我该怎么办?我不能在对象内部获取 @name 变量,因为每个对象的内部细节只能被它自己所知道。这是纯粹的面向对象的根本:每个对象内部的数据是私有的。每个对象都有其对应的被定义的输入(例如,set_name 方法)和输出接口。只有对象自身才能让它的内部状态变得混乱,外部世界是不能做到的。这被称为“数据隐藏”,并且它是“封装”(encapsulation)原理的一部分。

  • 封装(Encapsulation)

  • 在 Ruby 中,封装并不像最初它出现时的那么严格地被遵守,有一些不好的技巧可以让你使一个对象内部变得混乱。为了清楚起见(并确保你和我不会有恶梦),现在我们默默的了解下面这些语言的特性。

因为我们需要每一只狗都能知道它的名字,让我们给 Dog 类提供一个 get_name 方法:

def get_name
    return @myname
end

这里的 return 关键字是可选的。当它被省略时,Ruby 会返回最后一个表达式的值。 为了清楚起见(并为了避免发生意外的结果),我习惯于明确的返回我所期望的值。 最后,我们可以让狗拥有说话的能力。这是最终的类定义:

class Dog
  def set_name( aName )
    @myname = aName
  end

  def get_name
    return @myname
  end

  def talk
    return 'woof!'
  end
end

现在,我们可以创建一个 dog 对象,给它命名、显示它的名字并且让它说话:

mydog = Dog.new
mydog.set_name( 'Fido' )
puts(mydog.get_name)
puts(mydog.talk)

消息、方法与多态

顺便的说一句,这是一个基于经典的 Smalltalk 示例程序的例子,说明了如何将相同的“消息”(例如 talk)发送给不同的对象(例如 cats 和 dogs),并且每个不同的对象会对相同的消息使用它们自己特有的方法(这里是 talk 方法)产生不同的响应。这种不同的类拥有相同的方法的能力有一个面向对象的名字“多态”

构造方法——new 与 initialize

def initialize( aName, aDescription )
    @name = aName
    @description = aDescription
end

当一个类包含名为 initialize 的方法,它会在使用 new 方法创建对象时自动地被调用。使用 initialize 来设置一个对象的实例变量的值是不错的主意。

这相对于使用方法(例如 set_name)设置每个实例变量的值有两个明显的好处。首先,一个复杂的类可能包含许多实例变量,你可以通过一个 initialize 方法设置它们全部的值,而不是通过许多独立的“set”方法。其次,如果这些变量在对象创建时都被自动的初始化,你就不会以空的变量结束程序(例如在前面的程序中我们尝试显示 someotherdog 的名字时会返回 nil 值)。

最后,我创建了一个名为 to_s 的方法用来返回一个表示宝物对象的字符串。这个 to_s 方法名不是随意的,相同的方法名已被在 Ruby 标准对象库中使用。实际上,to_s 方法被定义在 Object 类中,该类是其它类的祖先。通过重新定义 to_s 方法,我添加了新的行为,这比默认的方法更适合于 Treasure 类。换句话说,我已经“覆盖”(overridden)了它的 to_s 方法。

new 方法可以创建一个对象,所以它可以被认为是对象的“构造方法”。然而,你通常不应该实现你自己的 new 方法(这是可能的,但它通常不可取)。相反,当你想要执行任何“设置”操作(例如为对象的内部变量赋值)时,应在 initialize 方法中完成,Ruby 会在一个新对象创建后立即执行 initialize 方法。

  • 垃圾回收(Garbage Collection,GC)

  • 在许多语言中(例如 C++ 和 Delphi for Win32),销毁任何已经创建并且不再需要的对象是程序员的职责。换句话说,对象被赋予析构函数以及构造函数。在 Ruby 中,你不必做这些了,因为 Ruby 有一个内置的“垃圾回收器”,它会在你的程序不再引用对象时销毁它并回收内存。