- 浏览: 747498 次
- 性别:
- 来自: 北京
文章分类
最新评论
-
梦行Monxin商城系统:
java网上商城与php网上商城比较 -
梦行Monxin商城系统:
java网上商城与php网上商城比较 -
任楚娴:
你好,请问html = nvl(html); 这句中的nvl( ...
java html串转换成文本串 -
u013246812:
,谢拉!
jQuery ui Dialog 讲解参数 -
大宝剑99:
...
前端优化
第一部分 Ruby语言基础
第一章 Ruby语言概述
§1.1 Ruby的历史
Ruby语言的发明人是日本人松本行弘(Matsumoto Yukihiro),大家亲切的称呼他"Matz"。
可能会出乎大家的意料,Ruby并不是一种近年来才诞生的语言,它的历史可以追溯到1993年,Ruby之父Matz开始对脚本语言感兴趣。在通过一些分析和思考之后,Matz认为脚本语言是可以变得很强大和灵活的,于是他准备把脚本语言作为他的发展方向。 和很多人一样,Matz是一个面向对象程序设计的fans,自然而然他想研究一种支持面向对象程序设计的脚本语言。随后的一段时间,他到网络上搜集了一些相关的资料,并且发现了Perl 5,当时Perl 5还没有发布。通过一段时间了解后,Matz.发现Perl 5这并不是他想的东西,所以他放弃了把Perl当作一个面向对象的脚本语言使用的念头。随后Matz转向了Python,Python是一个解释型的、面向对象语言,但是Matz发现Python并不能完全算作“面向对象”语言。Matz认为Python是面向对象和过程化程序设计语言(Procedural Programming Language)的混合产物。Matz希望找到的是一种比Perl更强大、比Python更面向对象的语言,但是很遗憾, 这样的语言当时在地球上并不存在。于是Matz打算自己设计一个全新的编程语言。1993年2月24日是一个值得纪念的日子,在这一天Ruby诞生了。 1995年12月Matz推出了Ruby的第一个版本Ruby 0.95。 在1996年以前,都是Matz.一个人在开发进行Ruby的开发。后来随着Ruby社区的渐渐形成,很多社区成员给了Matz许多有意义的帮助,包括提交bug和patch等。现在,Ruby像其他开源项目一样,有自己的开发团队,任何有能力的个人或团体都可以参与Ruby的开发与进化。
§1.2 Ruby名字的由来
首先明确一点,Ruby并不是其他单词的缩写。受Perl的影响,Matz也想用一种宝石来命名他的新语言,他使用了他的一位同事的生肖石-红宝石。后来,Matz意识到Ruby这个名字十分恰当,首先,在生肖石中,Pearl代表六月,而Ruby代表七月。在字体大小上,Pearl大小是5pt, ruby的大小是5.5pt。所以Ruby这个名字对于一种Perl的后续语言十分合适。
§1.3 Ruby的特点
Ruby是一种功能强大的面向对象的脚本语言,可以使用它方便快捷地进行面向对象程序设计。与Perl类似,而且Ruby具有强大的文本处理功能,使文本处理变得简单。此外还可以方便地使用C语言来扩展Ruby的功能。
若您曾经“想要一种简单的面向对象的语言”,或者认为“Perl的功能虽然好用,但它的语法真让人受不了”,又或者觉得“LISP系列语言的思想不错,但到处都是括号真让人讨厌,最起码算式应该按照通常的样式书写”。那么,Ruby或许能让您满意。
归纳起来,Ruby有以下优点:
解释型执行,方便快捷
Ruby是解释型语言,其程序无需编译即可执行。
语法简单、优雅
语法比较简单,类似Algol系语法。
完全面向对象
Ruby从一开始就被设计成纯粹的面向对象语言,因此所有东西都是对象,例如整数等基本数据类型。
内置正则式引擎,适合文本处理
Ruby支持功能强大的字符串操作和正则表达式检索功能,可以方便的对字符串进行处理。
自动垃圾收集
具有垃圾回收(Garbage Collect,GC)功能,能自动回收不再使用的对象。不需要用户对内存进行管理。
跨平台和高度可移植性
Ruby支持多种平台,在Windows, Unix, Linux, MacOS上都可以运行。Ruby程序的可移植性非常好,绝大多数程序可以不加修改的在各种平台上加以运行。
有优雅、完善的异常处理机制
Ruby提供了一整套异常处理机制,可以方便优雅地处理代码处理出错的情况。
拥有很多高级特性
Ruby拥有很多高级特性,例如操作符重载、Mix-ins、特殊方法等等,是用这些特性可以方便地完成各种强大的功能。
同时,由于是解释型语言,Ruby也有下列缺点:
解释型语言,所以速度较慢
静态检查比较少
§1.4 Ruby和Python的比较
Python是Ruby的劲敌。其功力深厚,可谓“千年蛇妖”。但matz认为Python的功能仍不完美,不然就不会创造Ruby了。
第二章 Ruby编程环境
§2.1 Ruby的安装
Ruby支持多种平台,包括Windows、Linux、各种类UNIX、MacOS X等。
§2.1.1 在Windows 95/98/Me/XP上安装Ruby
对于使用Windows平台的用户,安装Ruby是相当简单直接的事情。最方便的方法是使用“One-Click Ruby Installer”。
不知你有没有听说过SourceForge?SourceForge 是全球最大的开放源代码软件开发平台和仓库。它集成了很多开放源代码应用程序,为软件开发提供了整套生命周期服务。在Ruby世界,也有一个类似的网站,那就是Rubyforge。“One-Click Ruby Installer”是Rubyforge上的一个开源项目,也是Rubyforge上下载量最大的项目之一。这个项目将Ruby语言核心和一系列常用扩展集成到了一起,还包含支持Ruby的免费的IDE工具FreeRIDE和SciTE,除了这些之外还包括帮助文档,示例代码,RubyGems包管理器,Fox GUI库,fxri(Interactive Ruby Help & Console)等。和正如它名字所示,使用它,Ruby安装变得前所未见的容易。你可以在下面的地址下载到它的最新版本:
http://rubyforge.org/projects/rubyinstaller/
§2.1.2 在Linux上安装Ruby
在linux下Ruby的安装要稍微复杂一些,推荐使用源码编译的方式安装,这样可以保证安装的是最新版本。
首先到ruby主站http://www.ruby-lang.org/en/ 下载源代码,下载完毕后解压到目录,然后使用以下命令:
./configure
./make; make install
执行上面的命令需要root权限,默认安装到/usr/local下。你也可以使用“./configure --prefix=自定义路径”来指定安装目录。
windows上的ruby one-click installer默认安装了RubyGems,但在Linux下我们需要手动安装RubyGems。RubyGems是一个Ruby的包管理器,我们后边会讲到它。
首先从Rubyforge下载RubyGems的最近版本,地址如下:
http://rubyforge.org/projects/rubygems/
解压RubyGems以后到相应目录下输入ruby setup.rb,屏幕上打印一些日志以后会告诉你安装成功,执行gem -v可以查看gem安装版本号。
§2.2 运行Ruby
下面,我们将以Windows平台下的Ruby环境举例如何运行Ruby。
§2.2.1 使用Ruby
将“Hello World”作为学习计算机语言第一个学写的程序,现在已经成为一种传统。该程序最早出现在由Brian Kernighan和Dennis Ritchie写的经典计算机程序设计教程《The C Programming Language》。我们来看看Ruby世界的“Hello World”:
在Windows中,打开命令行提示符窗口,在提示符上输入“Ruby”并回车,Ruby解释器就会运行并等候输入程序。Ruby可执行文件应该包含在系统搜索路径内。
输入下面的程序:
print "Hello World!"
然后按Ctrl+D再按回车键,你就会看到Ruby执行程序的输出结果:
你也可以先将代码保存为文件,然后使用再Ruby解释器执行:
§2.2.2 使用FreeRIDE和SciTE
FreeRIDE是一个支持Ruby语言的免费IDE环境。FreeRIDE本身就是使用Ruby语言开发,它也是Rubyforge上的重要项目之一。
可以使用FreeRIDE来编写调试和执行Ruby代码,FreeRIDE内置了交互式变成环境和Ruby语言在线帮助,功能十分强大。
Scintilla是一个免费的源代码编辑控件,它完全开放源代码,并允许用户自由地用于开源软件或是商业软件中。SciTE是用这个控件开发了一个编辑软件,在“One-Click Ruby Installer”中,SciTE集成了Ruby语言支持,使用起来非常方便。相比FreeRIDE,它的特点就是使用简单。
§2.2.3 使用fxri
Fxri是一个Ruby交互帮助和控制台工具。它不仅可作为语言的在线帮助,而且可以用作交互式Ruby解释器来执行程序。对于学习Ruby语言,fxri是一个非常方便的帮手。
不知你有没有听说过Fox ToolKit,它是相当轻巧的开放源代码的图形库。FXRuby是RubyForge上的一个项目,提供了Ruby语言使用Fox ToolKit的接口。而Fxri正是基于FXRuby开发,Fxri同样是RubyForge上的项目。这样你应该可以猜到Fxri名字的由来
Fxri同时集成了Ruby-irb和Ruby-ri的功能,有了它,你可以抛开Ruby-irb,Ruby-ri了,但如果你用的不是Windows系统的话,算我没说
§2.3 Ruby-irb
Ruby-irb是交互式Ruby(Interactive Ruby)的简称,用来从标准输入读入并执行Ruby代码的工具,像一个shell。
使用命令“irb”进入交互式模式,然后可以象输入命令行命令一样输入Ruby代码,代码执行的结果会立刻显示:
§2.4 Ruby-ri
和Perl一样,Ruby也设计了嵌入式文档。 ruby-ri就是查看文档的工具。Ruby-ri的执行命令为“ri”,例如你可以通过“ri String.new”来查询String类的new方法:
§2.5 RubyGems
RubyGems是Ruby社区流行的包管理工具,在以前如果要下载一个Ruby扩展或者应用程序的话,你需要先下载相应的zip包,然后解压缩,再将应用或者扩展安装到Ruby对应的目录中。但是有了RubyGems所有这些麻烦都没有了,你只需要一条命令就可以从远程服务器上下载相应的包,如果相应的应用包含其他扩展,RubyGems会提示你从远程安装所依赖的扩展。安装后 RubyGems会运行相应的程序生成rdoc帮助文档。当然你也可以将软件包下载到本地运行RubyGems本地安装命令。
统一化的管理带来的好处就是简单,有了RubyGems包管理器,Ruby应用的安装将变得前所未见的容易。RubyGems是Rubyforge下载量最大的项目之一,现在Ruby社区的应用都在朝着RubyGems的方向发展,RubyGems也将成为Ruby事实上的包管理器标准。
RubyGems包管理器的可执行命令是“gem”,gem命令包含很多子命令和相应的选项,例如:
gem -h/--help – 显示命令帮助
gem -v/--version – 显示Gems的版本号
第三章 类与对象
Ruby是一种真正的面向对象程序设计语言,面向对象指以对象为中心的理论体系。
封装(Encapsulation)
将内部结构和算法隐藏起来,以确保只有特定的过程(也叫方法)才能直接操作数据,其结果是不能从外部直接使用数据构造,同时一旦内部构造发生变化也不会对外界造成不良影响。这种隔离方法就叫做封装。
继承
多态(Polymorphism)
根据对象的不同选择合适的操作。在Ruby中的实现方法是,根据被调的对象的不同来选择不同的方法。
虽然有很多语言都宣称自己是面向对象的,但是他们往往对面向对象的解释都一样,大多是以自己特有的方式来解释什么是面向对象,而在实际情况中,这些面向对象语言又采用了很多非面向对象的做法。
以 Java 为例:如果你想取一个数字取绝对值,java 的做法是:
int num = Math.abs(-99);
也就是将一个数值传递给 Math 类的一个静态函数 abs 处理。为什么这么做?因为在 java 中,数值是基本类型不是类。
而在 Ruby 中,任何事物都是对象,也就是说,数字–99就是对象,取绝对值这样的操作应该属于数字本身,所以Ruby的做法就是:
c = -99.abs
在Ruby中,你所操作的一切都是对象,操作的结果也是对象。
§3.1 类的定义
类是对具有同样属性和同样行为的对象的抽象,Ruby中类的声明使用class关键字。定义类的语法如下,
class ClassName
def method_name(variables)
#some code
end
end
类的定义要在class…end之间,在上面的格式中,ClassName是类名,类名必须以大写字母开始,也就是说类名要是个常量。
看下面的例子:
class Person
def initialize(name, gender, age)
@name = name
@gender = gender
@age = age
end
end
若某个类已经被定义过,此时又用相同的类名进行类定义的话,就意味着对原有的类的定义进行追加。
class Test
def meth1
puts "This is meth1"
end
end
class Test
def meth2
puts "This is meth2"
end
end
在Test类中,原有meth1方法,我们又追加了meth2方法,这时候,对于Test类的对象,meth1和meth2同样可用。
§3.2 对象,属性和方法
类在实例化后生成对象,在强调对象归属于某类时,有时候我们也使用实例对象一词。
方法(Method)是对对象进行的操作。操作对象(被调)以self来表示。在Ruby中,除去内部类的对象以外,通常对象的构造都是动态确定的。某对象的性质由其内部定义的方法所决定。
看下面的例子,我们使用new方法构造一个新的对象,
class Person
def initialize(name, gender, age)
@name = name
@gender = gender
@age = age
end
end
people = Person.new('Tom', 'male', 15)
我们可以使用Person.new方法来创建一个Person类的实例对象。以@打头的变量是实例变量,他们从属于某一实例对象,Ruby中实例变量的命名规则是变量名以@开始,您只能在方法内部使用它。
initialize方法使对象变为“就绪”状态,initialize方法是一个特殊的方法,这个方法在构造实例对象时会被自动调用。
对实例进行初始化操作时,需要重定义initialize方法。类方法new的默认的行为就是对新生成的实例执行initialize方法,传给new方法的参数会被原封不动地传给initialize方法。另外,若带块调用时,该块会被传给initialize方法。因此,不必对new方法进行重定义。
在Ruby中,只有方法可以操作实例变量,因此可以说Ruby中的封装是强制性的。在对象外部不可以直接访问,只能通过接口方法访问。
class Person
def name
@name
end
def gender
@gender
end
def age
@age
end
end
people = Person.new('Tom', 'male', 15)
puts people.name
puts people.gender
puts people.age
输出结果为:
Tom
male
15
在Ruby中,一个对象的内部属性都是私有的。 上面的代码中,我们定义了方法name,gender,age三个方法用来访问Person类实例对象的实例变量。注意name,gender,age访问只能读取相应实例变量,而不能改变它们的值。
我们也可以用成员变量只读控制符attr_reader来达到同样的效果。
class Person
attr_reader :name, :gender, :age
end
类似地,我们可以定义方法去改变成员变量的值。
class Person
def name=(name)
@name=name
end
def gender=(gender)
@gender=gender
end
def age=(age)
@age=age
end
end
people = Person.new('Tom', 'male', 15)
people.name = "Henry"
people.gender = "male"
people.age = 25
也可以用成员变量写控制符attr_writer来达到同样的效果。
class Person
attr_writer :name, :gender, :age
end
我们也可以使用attr_accessor来说明成员变量既可以读,也可以写。
class Person
attr_accessor :name, :gender, :age
end
也可以使用attr控制符来控制变量是否可读写。attr 只能带一个符号参数, 第二个参数是一个 bool 参数,用于指示是否为符号参数产生写方法。它的默认值是 false,只产生读方法,不产生写方法。
class Person
attr :name, true #读写
attr :gender, true #读写
attr :age, true #读写
attr :id, false #只读
end
注意attr_reader,attr_writer,attr_accessor和attr不是语言的关键字,而是Module模块的方法。
class Test
attr_accessor :value
end
puts Test.instance_methods - Test.superclass.public_methods
执行结果为:
value
value=
上面代码中,我们使用Test.instance_methods得到Test类所有的实例方法,使用Test.superclass.public_methods得到Test父类所有的实例方法,然后相减就得到Test类不包含父类的所有的实例方法。
由于instance_methods方法返回值为一个Array,所以我们作差值运算,Array的具体操作后面章节会讲到。
也可以重定义方法,重定义一个方法时,新的定义会覆盖原有的定义。
下面的例子重定义类中的方法meth1,
class Test
def meth1
puts "This is meth1"
end
end
a = Test.new
a.meth1
class Test
def meth1
puts "This is new meth1"
end
end
a. meth1
执行结果为:
This is meth1
This is new meth1
重定义同一个类时,意味着对原有定义进行补充,不会覆盖原来的定义。而重定义方法时,则会覆盖原有定义。
我们可以使用self标识本身,self和Java中的this有些类似,代表当前对象。
class Person
def initialize(name, gender, age)
@name = name
@gender = gender
@age = age
end
def <=>(other)
self.age <=> other.age
end
end
<=> 方法通常意思为比较,返回值为-1,0或1分别表示小于,等于和大于。
§3.3 继承
Ruby继承的语法很简单,使用 < 即可。
class Student < Person
def initialize(name, gender, age, school)
@name = name
@gender = gender
@age = age
@school = school
end
end
Ruby语言只支持单继承,每一个类都只能有一个直接父类。这样避免了多继承的复杂度。但同时,Ruby提供了mixin的机制可以用来实现多继承。
可以使用super关键字调用对象父类的方法,当super省略参数时,将使用当前方法的参数来进行调用。
class Base
def meth(info)
puts "This is Base #{info}"
end
end
class Derived < Base
def meth(info)
puts "This is derived #{info}"
super
end
end
obj1 = Derived.new
obj1.meth("test")
执行结果为:
This is derived test
This is Base test
如果传入的参数被修改再调用super的话,那么将会使用使用修改后的值。
class Base
def meth(info)
puts "This is Base #{info}"
end
end
class Derived < Base
def meth(info)
puts "This is derived #{info}"
info = "over"
super
end
end
obj1 = Derived.new
obj1.meth("test")
执行结果为:
This is derived test
This is Base over
§3.4 特殊方法与特殊类
特殊方法是指某实例所特有的方法。一个对象有哪些行为由对向所属的类决定,但是有时候,一些特殊的对象有何其他对象不一样的行为,在多数程序设计语言中,例如C++和Java,我们必须定义一个新类,但在Ruby中,我们可以定义只从属于某个特定对象的方法,这种方法我们成为特殊方法(Singleton Method)。
class SingletonTest
def info
puts "This is This is SingletonTest method"
end
end
obj1 = SingletonTest.new
obj2 = SingletonTest.new
def obj2.info
puts "This is obj2"
end
obj1.info
obj2.info
执行结果为:
This is This is SingletonTest method
This is obj2
有时候,我们需要给一个对象定义一系列的特殊方法,如果按照前面的方法,那么只能一个一个定义:
def obj2.singleton_method1
end
def obj2.singleton_method2
end
def obj2.singleton_method3
end
……
def obj2.singleton_methodn
end
这样做非常繁复麻烦,而且无法给出一个统一的概念模型,因此Ruby提供了另外一种方法,
class << obj
……
end
obj是一个具体的对象实例,class << 代表它的特殊类。
class SingletonTest
def meth1
puts "This is meth1"
end
def meth2
puts "This is meth2"
end
end
obj1 = SingletonTest.new
obj2 = SingletonTest.new
class << obj2
def meth1
puts "This is obj2's meth1"
end
def meth2
puts "This is obj2's meth2"
end
end
obj1.meth1
obj1.meth2
obj2.meth1
obj2.meth2
执行结果为:
This is meth1
This is meth2
This is obj2's meth1
This is obj2's meth2
§3.5 类变量与类方法
类变量被一个类的所有实例对象共享,也可以被类方法访问到。类变量名以‘@@’,开始,例如‘@@number’。和全局变量,实例变量不同,类变量在使用前必须初始化:
class Person
@@number = 0 #使用前必须有初值
def initialize(name, gender, age)
@name = name
@gender = gender
@age = age
@@number += 1
end
end
类变量是私有的,在类外无法直接访问,你只能通过实例方法和类方法去访问它。
同样,类方法是属于一个类的方法,定义类方法时需要在方法前加上类名:
class Person
@@number = 0
def initialize(name, gender, age)
@name = name
@gender = gender
@age = age
@@number += 1
end
def Person.getNumber #类方法
return @@number
end
end
除了Person.getNumber这种方式定义类方法外,还可以使用其它方式定义类方法,在后续章节可以陆续见到。
§3.4 存取控制
当你设计一个类时,你需要决定哪些属性和方法可以在类外被访问到,哪些属性和方法在类外被隐藏。如果一个类有过多的属性和方法在类外可以被访问到,那么势必破坏这个类的封装性。幸运的是在Ruby中,只能通过方法去改变一个类的属性,这样我们只需要考虑方法的存取控制。
方法的存取控制有三种:
公有方法(Public Method)
方法在任何地方都可以被调用,这是方法的默认存取控制。除了initialize和initialize_cpoy方法,他们永远是私有方法。
保护方法(Protected Method)
方法只能被定义这个方法的类自己的对象和这个类的子类的对象所访问。
私有方法(private Method)
方法只能被定义这个方法的类的对象自己访问,即使是这个类的其他对象也不能访问。
Ruby中的保护方法和私有方法与一般面向对象程序设计语言的概念有所区别,保护方法的意思是方法只能方法只能被定义这个方法的类自己的对象和子类的对象访问,私有方法只能被对象自己访问。
class Test
def method1 #默认为公有方法
…
end
protected #保护方法
def method2
…
end
private #私有方法
def method3
end
public
def test_protected(arg) #arg是Test类的对象
arg.method2 #正确,可以访问同类其他对象的保护方法
end
def test_private(arg) #arg是Test类的对象
arg.method3 #错误,不能访问同类其他对象的私有方法
end
end
obj1 = Test.new
obj2 = Test.new
obj1.test_protected(obj2)
obj1.test_private(obj2)
可以看到,和C++/Java相比,Ruby提供了更好的封装性。
也可以使用以下更简单的形式:
class Test
def method1
...
end
def method2
...
end
def method3
...
end
def methdo4
...
end
public :method1
protected :method2
private :method3, :method4
end
Ruby和C++/Java的一个显著不同是存取控制是程序运行时决定的而不是静态绑定的。所以只有在访问一个受限制的方法时才会产生运行时错误。
§3.6 元类
在Ruby中一切都是对象。类和实例对象都是对象。这句话听起来有点拗口,让我们来看一个例子:
class Person
def initialize(name, gender, age)
@name = name
@gender = gender
@age = age
end
end
a = Person.new('Tom', 'male', 15)
puts a.object_id => 22429840
puts Person.object_id => 22429960
没错,类也是对象,这是Ruby和C++/Java的一个显著不同,在C++/Java中,类仅仅是一个数据抽象,并没有类也是对象这样的概念。而在Ruby中存在着元类的概念,类也是对象,所有类都是元类的实例对象。和C++/Java相比,Ruby的面向对象程度更高。
可以看到,类对象和实例对象一样有自己的ojbect_id,你可以象调用一个实例对象的方法一样去用它去调用类方法。所有类对象的类是Class类,Oject类是所有类的基类。
irb(main):003:0> Object.class
=> Class
irb(main):004:0> Object.superclass
=> nil
这样,我们可以从另一个角度去理解类变量与类方法,类变量就是一个类对象的实例变量,类方法就是指一个类对象类的特殊方法。
类方法具体可分为两种:第一种是在所有的类的父类Class中定义的,且被所有的类所共享的方法;第二种是各个类所特有的特殊方法。
类方法中的self指的是类本身,这点需要牢记,这样我们可以使用多种方式定义类方法。
class Test
#定义类方法方式1
def Test.meth1
# ...
end
#定义类方法方式2
def self.meth2
# ...
end
#定义类方法方式3
class << Test
def meth3
# ...
end
end
#定义类方法方式4
class << self
def meth4
# ...
end
end
end
§3.7 Ruby的动态性
可以重新定义同一个方法,
class RedefTest
def meth
puts "This is meth"
end
end
obj1 = RedefTest.new
obj1.meth
class RedefTest
def meth
puts "This is new meth"
end
end
obj1.meth
执行结果为:
This is meth
This is new meth
可以使用undef_method取消一个方法的定义,
class UndefTest
def meth
puts "This is meth"
end
end
obj1 = UndefTest.new
obj1.meth
class UndefTest
undef_method(:meth)
end
obj1.meth
执行结果为:
This is meth
test.rb:14: undefined method `meth' for #<UndefTest:0x2ac8240> (NoMethodError)
§3.8 变量
变量名长度只受内存大小的限制。可以通过区分Ruby变量名的首字符来区分它是局部变量、实例变量、类变量、全局变量还是常量。通常情况下,变量名的第二位字符以后是数字、字母或下划线,但有的内部变量名比较特殊,如“$?”。
§3.8.1 局部变量
局部变量以小写字母或下划线开始。
num = 1
foo
局部变量的作用域起始于声明处,结束于该声明所在的块、方法定义、类/模块定义的结尾。
2.times {
p defined?(num)
num = 10
p num
}
输出为:
nil
10
nil
10
即使声明部分未被解释器执行仍有效,因为已经经过解释器的处理。
v = 1 if false
p defined?(v)
p v
输出为:
"local-variable"
nil
但若块已经变成过程对象的话,则局部变量将一直持续到该过程对象终结为止。若多个过程对象引用同一个作用域的话,局部变量将被这些对象所共享。
(to-do例子)
§3.8.2 实例变量
以@开始的变量是实例变量,实例变量属于特定的对象。
class Person
def initialize(name, gender, age)
@name = name
@gender = gender
@age = age
end
end
上面的例子中,@name, @gender,@age都是实例变量。可以在类或子类的方法中引用实例变量。若引用尚未被初始化的实例变量的话,其值为nil。
§3.8.3 类变量
以@@开始的变量是类变量。类变量在类的定义中定义,可以在类的特殊方法、实例方法等处对类变量进行赋值和引用。类变量被类,类的子类和他们的实例对象共享。
class Person
@@number = 0 #使用前必须有初值
def initialize(name, gender, age)
@name = name
@gender = gender
@age = age
@@number += 1
end
end
类变量是私有的,在类外无法直接访问,你只能通过实例方法和类方法去访问它。可以把类变量看作一种被类、子类以及它们的实例所共享的全局变量。
模块中定义的类变量(模块变量)被所有包含该模块的类所共享。
module TestModule
@@foo = 10
end
class Klass
include Foo
p @@foo += 1 # => 11
end
class Base
include Foo
p @@foo += 2 # => 12
end
§3.8.4 全局变量
以$开始的变量是全局变量,全局变量可以在程序的任何地方加以引用。全局变量无需变量声明。引用尚未初始化的全局变量时,其值为nil。
Ruby运行时环境预定义了一系列的全局变量,有关预定义的全局变量的信息,请参见附表。
§3.8.5 常量
常量以大写字母开始,常数的定义和初始化由赋值过程完成。
PI = 3.14
E = 2.71
若对已定义的常数进行赋值的话,会出现警告信息。若引用未定义的常数会引发NameError异常。
PI = 3.14
obj1 = 2 * PI * 10
PI = 3.1415 # warning: already initialized constant PI
obj2 = Foo #uninitialized constant Foo (NameError)
常量可以定义在类和模块中,不能定义在方法中。
class Meth
PI = 3.14 #OK
end
def circle_area(arg)
PI = 3.14 #ERROR
PI * arg * arg
end
若想在外部访问类或模块中的常数时,要使用“::”操作符。
class Meth
PI = 3.14
end
def circle_area(arg)
Math::PI * arg * arg
end
在类定义表达式生成类对象的同时,还会将类对象赋值给一个与该类同名的常数,引用类名也就是引用该常数。
class Test
end
p Test.class #Class
p Test #test
若想访问Object类中的常数(顶层的常数)时,也需要也使用"::"操作符,但操作符左边为空。
§3.8 与定义有关的操作
§3.8.1 alias
Alias关键字给方法或全局变量添加别名。可以给方法名指定一个标识符或Symbol作为别名。给方法添加别名时,别名方法将和此刻的原始方法绑定,此后即使重新定义了原始方法,别名方法仍然保持着重定义前的老方法的特性。若改变了某方法的内容后,又想使用修改前的方法时,别名会很有用。也可以使用Module#alias_method给方法添加别名。
# 定义meth方法
def meth
puts "This is meth"
end
#设定别名
aliasrig_meth :meth
#重定义foo
def meth
puts "This is new meth"
end
p meth
执行结果为:
This is new meth
nil
给全局变量设定别名意味两个名称指向同一个全局变量。当你向一个赋值时,另一个也会被改变。
$abc = 1
alias $xyz $abc
$xyz = 2
p [$abc, $xyz] # => [2, 2]
但是不能给正则表达式中的变量$1,$2等添加别名,另外,有些全局变量对于解释器来说是举足轻重的,若重新定义它们的话,有时会影响解释器的正常工作。
§3.8.2 undef
undef用来取消一个方法的定义,也可以使用Module#undef_method方法取消方法的定义。undef会取消方法名和方法定义之间的关系,即使超类中有同名方法,调用时也会引发异常。
class Base
def meth
puts "This is Base#meth"
end
end
class Derived < Base
def meth
puts "This is Derived#meth"
end
end
class Test1 < Derived
def meth
puts "This is Test1#meth"
end
undef_method(:meth)
end
obj1 = Test1.new
obj1.meth
执行结果为:
Tes1.rb:22: undefined method `meth' for #<Test1:0x2ac7c88> (NoMethodError)
而Module#remove_method方法只负责取消当前类中方法名和方法定义之间的关系,父类的同名方法仍可调用,这点差别非常重要。
class Base
def meth
puts "This is Base#meth"
end
end
class Derived < Base
def meth
puts "This is Derived#meth"
end
end
class Test2 < Derived
def meth
puts "This is Test2#meth"
end
remove_method(:meth)
end
obj2 = Test2.new
obj2.meth
执行结果为:
This is Derived#meth
用alias添加别名或用undef取消定义时,会修改类的接口,而不受父类的限制。继承和Mix-in的功能都是在类中添加方法,而undef则可以取消方法。但是,如果取消了类所必需的方法(被其他方法所调用的方法)的话,其后果不堪设想。
§3.8.3 defined?
Defined?用来判断表达式是否定义。若表达式尚未定义,则返回nil,若已经定义,则返回一个字符串描述该表达式的种类。
defined? Val #=> nil
defined? true #=> “true”
defined? $* #=> "global-variable"
defined? Array #=> "constant"
defined? Math::PI #=> "constant"
defined? num = 0 #=> "assignment"
defined? 100 #=> "expression"
defined? 100.times #=> "method"
虽然defined?看起来像一个方法,实际上是Ruby语法中的操作符,因此不会对参数进行计算。因此下面的表达式并不会输出“abc”。
defined? print("abc\n")
如果是方法未定义,或方法使用undef或Module#remove_method取消了原有定义,defined?都将返回nil。
注意如果一个方法以大写字母开头,使用defined? 判断时需要在方法名后添加"()"时,否则方法名会被当做常数处理。
def Foo(arg)
end
p defined? Foo # => nil
p defined? Foo() # => "method"
Foo = 1
p defined? Foo # => "constant"
还可以使用下列特殊用法:
判断yield是否可用
defined? yield
若yield调用可用,则返回真,具体返回值为字符串"yield"。它的作用同block_given?一样,可以判断能否以带块方式来调用某方法。
class Base
def foo
puts defined? yield
end
end
a = Base.new
a.foo
a.foo {}
执行结果为:
nil
yield
判断super是否可用
defined? super
若super可被调用,则返回真, 具体返回值为字符串"super"。
class Base
def foo
end
end
class Derived < Base
def foo
puts defined? super
end
def fun
puts defined? super
end
end
obj = Derived.new
obj.foo
obj.fun
执行结果为:
super
nil
返回没有赋值但已经定义的局部变量.
defined? a = 1 #=> assignment
p a # => nil
在正则表达式中使用
/(.)/ =~ "foo"
p defined? $& # => "$&"
p defined? $1 # => "$1"
p defined? $2 # => nil
第四章 基本类型
§4.1 Array
Array也称作数组,是一系列元素的有序集合。你可以显式使用Array类的new方法来创建一个数组对象,你也可以用方括号包围起来一些以逗号分隔的数字或字符串构成一个数组。
irb(main):007:0> a = [ "first" "second" "third" ]
=> ["firstsecondthird"]
irb(main):008:0> a = [ "first", "second", "third" ]
=> ["first", "second", "third"]
irb(main):009:0> a.class
=> Array
irb(main):010:0> a.length
=> 3
irb(main):011:0> a[0]
=> "first"
irb(main):012:0> a[1]
=> "second"
irb(main):013:0> a[2]
=> "third"
irb(main):014:0> a[3]
=> nil
irb(main):015:0> b = Array.new
=> []
irb(main):016:0> b.class
=> Array
irb(main):017:0> b.length
=> 0
irb(main):018:0> b[0] = "first"
=> "first"
irb(main):019:0> b[1] = "second"
=> "second"
irb(main):020:0> b
=> ["first", "second"]
数组可以使用 [] 来索引,其实 [] 是Array类的一个方法,它甚至可以被子类覆盖(overridden)。Ruby中比较有趣的是有多种对数组的索引方法,你可以用负数来索引数组。负数表示从尾部开始,例如索引为-1表示最后一个元素,索引为-2表示倒数第二个元素,以此类推。
irb(main):021:0> a = [ 1, 2, 3, 4, 5]
=> [1, 2, 3, 4, 5]
irb(main):022:0> a[-1]
=> 5
irb(main):023:0> a[-2]
=> 4
irb(main):024:0> a[-9]
=> nil
你也可以使用一对数来索引数组,第一个数表示开始位置,第二数表示从开始位置起的元素数目。
irb(main):025:0> a = [ 1, 2, 3, 4, 5]
=> [1, 2, 3, 4, 5]
irb(main):026:0> a[1, 3]
=> [2, 3, 4]
irb(main):027:0> a[3, 1]
=> [4]
irb(main):028:0> a[-3, 1]
=> [3]
你甚至可以用一个范围来索引数组,.. 表示包含尾部元素,... 表示不包含尾部元素。
irb(main):029:0> a = [ 1, 2, 3, 4, 5]
=> [1, 2, 3, 4, 5]
irb(main):030:0> a[1..3]
=> [2, 3, 4]
irb(main):031:0> a[1...3]
=> [2, 3]
§4.2 Hash
Hash也称作哈希表哈希表,类似于数组但是每个元素都有索引,有时候也被称作关联数组,哈希数组或字典。哈希表和数组不同,数组只能使用数字索引,而哈希表则可以使用任何对象索引。哈希表和数组的另一个显著不同是哈希表中的元素是无序的。在Ruby中每个哈希表都是Hash类的对象。
在哈希表中,我们称索引为Key,被索引的元素称为Value。
我们可以使用=>连接的元素来创建一个哈希表,注意哈希表外部是使用大括号包围。
irb(main):032:0> h = { "first" => "Amy", "second" => "Mike", "third" => "Tom" }
=> {"third"=>"Tom", "second"=>"Mike", "first"=>"Amy"}
irb(main):033:0> h.length
=> 3
irb(main):034:0> h["first"]
=> "Amy"
irb(main):035:0> h['second']
=> "Mike"
irb(main):036:0> h[100] = "Henry"
=> "Henry"
irb(main):037:0> h["nine"] = "Rose"
=> "Rose"
irb(main):038:0> h
=> {"third"=>"Tom", "second"=>"Mike", 100=>"Henry", "first"=>"Amy", "nine"=>"Rose"}
§4.3 Number
Ruby支持整数类型和浮点数类型。整数可以是任意长度(这个长度只和内存大小有关)。在一定范围内的整数被视为Fixnum类的对象。超出这个范围的整数被视为Bignum类的对象。
num = 81
6.times do
puts "#{num.class}: #{num}"
num *= num
end
运行结果:
Fixnum: 81
Fixnum: 6561
Fixnum: 43046721
Bignum: 1853020188851841
Bignum: 3433683820292512484657849089281
Bignum: 11790184577738583171520872861412518665678211592275841109096961
和C/C++相同,Ruby规定以0开头的数为八进制数,以0x开头的数为十六进制数,以0b开头的数为二进制数。
irb(main):001:0> 16
=> 16
irb(main):002:0> 020
=> 16
irb(main):003:0> 0x10
=> 16
irb(main):004:0> 0b10000
=> 16
一个数中间可以用下划线连接,下划线自动被忽略。
irb(main):005:0> 123_456_789
=> 123456789
可以使用"?\C-x"或"?\cx"生成控制字符。
如果一个数包含小数点或者包含"e",那么这个数将被转为Float类的对象。
irb(main):012:0> 1.0.class
=> Float
irb(main):013:0> 1.0e3.class
=> Float
§4.4 String
String也称作字符串,是单引号或双引号包围起来的一串字符。单引号和双引号的意义有所不同,双引号包围的字符作变量替换,单引号包围的变量不做替换。可以在字符串中使用 #{expr} 嵌入代码。
irb(main):022:0> "The seconds in a day is: #{24*60*60}"
=> "The seconds in a day is: 86400"
irb(main):023:0> 'The seconds in a day is: #{24*60*60}'
=> "The seconds in a day is: \#{24*60*60}"
也可以使用 %q 和 %Q 来生成字符串对象。%q 相当于单引号,%Q相当于双引号。
irb(main):051:0> %q/Single quote/
=> "Single quote"
irb(main):052:0> %Q/Double quote/
=> "Double quote"
irb(main):053:0> %q/ #{50*50} /
=> " \#{50*50} "
irb(main):054:0> %Q/ #{50*50} /
=> " 2500 "
%q 和 %Q 后面的第一个字符为分隔符。二哥分隔符之间的字符被认为一个是字符串。但是如果这个分隔符是 [ { <, 那么结束标志为匹配的 ] } >。
irb(main):055:0> %q{This is a string}
=> "This is a string"
irb(main):056:0> %Q[This is a string]
=> "This is a string"
irb(main):057:0> %q<This is a string>
=> "This is a string"
你也可以使用“Here Document”的方法来生成字符串,这种方法规定 << 之后的字符串作为结束标志。
string = <<END_OF_STRING
With publication started in June 1948 and a current circulation of 3 million,
People's Daily is the most influential and authoritative newspaper in China.
According to UNESCO, it takes its place among the world top 10.
END_OF_STRING
需要注意,表示结尾的END_OF_STRING必须放在行首。
§4.5 Range
Range也称作范围,用来表示一个都是连续的值的序列。可以使用 .. 和 ... 操作符来产生Range,前者表示包含最后一个元素,后者表示不包含最后一个元素。Range对象所属的类是Range。注意Range和Array是不同的,可以使用Range类的to_a方法将一个Range对象转化为Array对象。
irb(main):003:0> (1..10).to_a
=> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
irb(main):004:0> ('bar'..'bat').to_a
=> ["bar", "bas", "bat"]
irb(main):005:0> (1...10).to_a
=> [1, 2, 3, 4, 5, 6, 7, 8, 9]
Range除了表示一个序列外还可以出现在条件语句中。在条件语句中,可以把Range看作一个双向开关,当第一个条件满足时打开开关,当第二个条件满足时关闭开关。
a = [0, 1, 2, 3, 4, 5, 6]
a.each do |i|
print i, " " if i == 1 .. i == 5
end
执行结果为
1 2 3 4 5
上述代码中if i == 1 .. i == 5表示只有满足i==1且不满足i==5时条件为真,当不满足i==1或满足i==5时条件为假,直观地看,就是表示元素需要位于范围之中。
可以使用 === 来测试一个元素是否在某个范围:
irb(main):093:0> (1..10) === 3
=> true
irb(main):094:0> (1..10) === 30
=> false
irb(main):095:0> (1..10) === 2.71828
=> true
irb(main):096:0> ('a'..'f') == 'c'
=> false
irb(main):097:0> ('a'..'f') == 'g'
=> false
Range也可以位于case语句之内:
score = 98
case score
when 85..100 then puts "A"
when 70...85 then puts "B"
when 60...70 then puts "C"
else puts "D"
end
执行结果为:
A
§4.6 Symbol
Symbol是个简单的对象,它使用名字作为唯一的标识符。Symbol对象代表解释器内部一个唯一的名字。Symbol的产生很简单,只需要给一个字符序列前添加“:”或使用“to_sym”方法。 Symbol对象从属于Symbol类。
String和Symbol两者具有紧密的联系。每个symbol都有个字符串的名字(可以使用to_s方法得到)。而每个String可以请求它的相应symbol(通过to_sym方法)。String和Symbol是紧密相联的,但它们不是同一个东西,他们分别是String类和Symbol类的对象。
有读者可能会问,为什么要存在Symbol对象呢?因为symbol可以大大提高速度。Symbol的内部表示是一个整数,用来做Hash表中检索字符串的关键字,而Ruby语言执行时解析器、运算器需要大量的类名字、方法名字的检索,这可以大大加快解析和执行时字符串查找的速度。
想想,如果没有Symbol,如果需要用方法名称作为参数时,我们必须给一个字符串用来表示方法的名称,解释器处理时首先要作字符串解析,然后才能找到出相应的方法,而如果使用Symbol会大大加快这一速度。
在使用中,Symbol往往表示一个名字,例如一个变量 foo的值为1,那么 :foo可以理解为变量名,如果直接引用foo,会得到1,但如果是 :foo就指变量名本身。
Symbol对象是唯一的。每次你在代码中使用:test, 你是要引用一个名字为"test"的Symbol类的对象。Ruby保证系统中只有一个名字为test的Symbol对象, 所以所有对:test的引用都将引用同一个对象。
irb(main):001:0> module One
irb(main):002:1> class Test
irb(main):003:2> end
irb(main):004:1> $f1 = :Test
irb(main):005:1> end
=> :Test
irb(main):006:0> module Two
irb(main):007:1> Test = 1
irb(main):008:1> $f2 = :Test
irb(main):009:1> end
=> :Test
irb(main):010:0> def Test()
irb(main):011:1> end
=> nil
irb(main):012:0> $f3 = :Test
=> :Test
irb(main):013:0> $1.object_id
=> 4
irb(main):014:0> $2.object_id
=> 4
irb(main):015:0> $3.object_id
=> 4
§4.7 正则表达式
正则表达式的类是Regexp,可以使用/或%r生成正则表达式。
irb(main):103:0> a = /\s*[a-f]/
=> /\s*[a-f]/
irb(main):104:0> a.class
=> Regexp
irb(main):105:0> b = %r{\s*[a-f]}
=> /\s*[a-f]/
irb(main):106:0> b.class
=> Regexp
irb(main):107:0> c = Regexp.new('\s*[a-f]')
=> /\s*[a-f]/
irb(main):108:0> c.class
=> Regexp
你可以使用Regexp#match(string)方法或者=~运算符来匹配正则表达式,你也可以使用!~来测试是否不匹配。
irb(main):113:0> sentence = "This is a dog."
=> "This is a dog."
irb(main):114:0> sentence =~ /dog/
=> 10
irb(main):115:0> sentence =~ /a/
=> 8
irb(main):116:0> /o/ =~ sentence
=> 11
irb(main):117:0> sentence !~ /xyz/
=> true
另外,在匹配正则表达式时,会将匹配到的字符串存放在 $& 变量中,$' 变量中存放已经匹配过的字符序列,$` 变量中存放还未匹配的字符序列。
irb(main):118:0> sentence = "This is a dog."
=> "This is a dog."
irb(main):119:0> sentence =~ /a/
=> 8
irb(main):120:0> puts $&
a
=> nil
irb(main):121:0> puts $'
dog.
=> nil
irb(main):122:0> puts $`
This is
=> nil
第五章 代码块和迭代器
§5.1 代码块(Block)
§5.1.1 什么是代码块
在Ruby中在在大括号之间的代码或放在do/end之间的代码是一个代码块。代码块只能出现在一个方法的后边,它紧接在方法最后一个参数的同一行上。代码块的内容并不会被马上执行,当执行到被调用的方法时,解释器的运行时环境会记住代码块出现的现场,然后执行被调用的方法。
[1,2,3,4,5].each { |i|
puts i
}
[1,2,3,4,5].each do |i|
puts i
end
一般的使用习惯是:(to-do 具体解释)
当关心边际(side effect)效应时使用 do/end。
当关心返回结果应时使用大括号。
§5.1.2 代码块与对象
代码块并不是对象,但可以方便的转化为Proc类的对象。有三种转化的方法:
将一个代码块传递给最后一个参数以&开始的方法。
def meth1(p1, p2, &block)
puts block.inspect
puts block.call
end
meth1(1, 2) { "This is a block" }
使用Proc.new方法,后边的参数为一个代码块:
block = Proc.new { "a block" }
调用Kernel.lambda方法:
block = lambda { "a block" }
前两种方法是等价的,而第三种方法,用 lambda 生成的 Proc 对象和用 Proc.new 生成的 Proc 对象之间是有差别的。这是一个微妙的差别,这个差别与 return 关键字相关。lambda 中的 return 从 lambda 返回。而 Proc 中的 return 从外围方法返回。
可以看以下两个例子:
def test_proc
p = Proc.new { return 1 }
p.call
puts "Never come here"
end
test_proc #=> 1
执行后"Never come here"不会被输出,执行p.call相当于在test_proc方法内执行了return语句。
def test_lambda
p = lambda { return 1 }
result = p.call
puts "The value is: #{result}"
end
test_lambda
执行后的结果为:
The value is: 1
可见使用lambda生成的Proc对象执行call方法调用时,return表示从lambda包围得块内返回。
在一个代码块中执行next语句会导致代码块返回。返回值就是next语句后带的参数。如果next后没有参数,那么返回值为nil。
def meth2
result = yield
"The block result is #{result}"
end
puts meth2 { next 9 }
pr = Proc.new { next 100 }
puts pr.call
pr = lambda { next }
puts pr.call
执行结果为:
The block result is 9
100
nil
§5.2 迭代器(Iterator)
§5.2.1 什么是迭代器
简单的讲,一个迭代器就是一个能接受代码块的方法。当初为了进行迭代操作而设置了带块方法,所以现在它仍然常常被称作迭带器。
[1,2,3,4,5].each { |i|
puts i
}
上述代码中,each方法反复调用代码块,我们称each方法为一个迭代器。
迭代器(Iterator)即指调用带块方法。实际上,在早期版本的 Ruby 中,使用代码块的方法被称为迭代器,因为它们就是被设计来实现循环迭代的。但是在Ruby发展过程中,代码块的用途在后来已经得到了很大的增强,从最初的循环抽象到任何事情。可以将那些进行迭代操作的方法叫做迭代器,但如果将所有带块方法的调用过程都看作迭带器的话,并不合适而且概念上会引起混乱
§5.2.2 使用迭代器
#一个使用迭代器的简单例子,数组中每一个元素作为参数执行其后的代码块
['This', 'is', 'a', 'dog'].each do |entry|
print entry, ' '
end
执行结果为:
This is a dog
#另一个使用迭代器的例子,代码块可以访问其外的数据
factorial = 1
1.upto(10) do |i|
factorial*= i
end
puts factorial
执行结果为:
3628800
#代码块的返回值可以被调用者使用
b = [1, 2, 3, 4, 5].map do |entry|
entry * entry
end
print b.inspect
执行结果为:
[1, 4, 9, 16, 25]
#代码块也可以使用一个以上的参数
result = (0..100).inject(0) do |sum, i|
sum + i
end
print result
执行结果为:
5050
§5.2.3 yield
在方法中可以使用yield来执行代码块的内容,就好像传入的代码块是这个方法的一部分一样。每当碰到一个yield调用,代码块的内容就会被执行一次。当代码块执行结束后,程序会回到yield的那一行继续向下执行。
def twoTimes
yield
yield
end
twoTimes { puts "Hello World!" }
执行结果为:
Hello World!
Hello World!
你可以使用yield操作传参数给一个代码块,并且从代码块取回返回值。
def fibonacii(max)
f1, f2 = 1, 1
while f1 <= max
yield f1
f1, f2 = f2, f1+f2
end
end
fibonacii(1000) { |f| print f, " " }
执行结果为:
1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987
在这个例子中,yield接收一个参数,这个参数将会在执行的时候传递给指定的代码块。在代码块中,接收的参数使用两个竖线括起来放在代码块的头部。yield操作也可以有返回值,yield操作的返回值就是代码块中最后一个表达式的值。
§5.2.4 编写自己的迭代器
def factorial(count, &block)
value = 1
1.upto(count) do |i|
value = value * i
block.call(i, value)
end
end
factorial(5) do |i, sum|
puts "factorial(#{i}) = #{sum}"
end
执行结果为:
factorial(1) = 1
factorial(2) = 2
factorial(3) = 6
factorial(4) = 24
factorial(5) = 120
也可以将传入的代码块保存以供以后使用:
class Mathematics
def initialize(&block)
@block = block
end
def factorial(max)
value = 1
1.upto(max) do |i|
value = value * i
@block.call(value)
end
end
end
the_value = Mathematics.new do |count|
puts "Current value is #{count}"
end
the_value.factorial(5)
执行结果为:
Current value is 1
Current value is 2
Current value is 6
Current value is 24
Current value is 120
第六章 表达式
Ruby语言的一切都有返回值,这是Ruby语言和其他程序设计语言的一个显著不同。
irb(main):006:0> a = b = c = 0
=> 0
irb(main):007:0> print "\n"
=> nil
同样,if和case语句也有返回值,if和case语句的返回值就是if和case中最后一个执行语句的值。
irb(main):014:0> if( 1+1 == 2)
irb(main):015:1> "Like in school."
irb(main):016:1> else
irb(main):017:1* "What a surprise!"
irb(main):018:1> end
=> "Like in school."
§6.1 运算符
和其他程序设计语言一样,Ruby中含有丰富的运算符。但是在Ruby中,大多数运算符实际上是方法调用。例如 a+b,其实真实执行的是 a.+(b),调用a对象的+方法,b作为这个方法的参数。这样带来了相当的灵活性,你可以改变原有运算符的语义从而赋予它新的含义。
以下代码仅仅作为一个例子重写Fixnum类的 + 方法,赋予两个定长整数相加新的含义。
irb(main):001:0> class Fixnum
irb(main):002:1> alias the_plus +
irb(main):003:1* def +(integer)
irb(main):004:2> the_plus(integer) * 2
irb(main):005:2> end
irb(main):006:1> end
=> nil
irb(main):007:0> 1+1
=> 4
irb(main):032:0> 2+2
=> 8
irb(main):132:0> 2+5
=> 14
对于运算符(+ - * / % ** & | ^ << >> && ||),Ruby有相应形式的赋值运算符缩写形式+=, -=等。
运算符优先级:
::
[]
+(一元) -(一元) ! ~
* / %
+ -
<< >>
&
| ^
> >= < <=
<=> == === != =~ !~
&&
||
.. …
?:
= += -= *= /=(所有的赋值运算符缩写)
not
and or
以下运算符不能作为方法调用,也就是说不能改变以下运算符的含义:
…
!
not
&&
And
||
Or
::
=
+= -= *= /=(所有的赋值运算符缩写)
?:
§6.2 命令替换
在Shell中,可以使用反引号(`)执行命令替换。
`date` =〉Mon Nov 27 11:07:22 CST 2006
`pwd` =〉/usr/include
Ruby也有这个功能。在Ruby中,可以使用反引号或%x来执行命令替换。命令替换表达式的返回值就是命令执行的输出结果。命令执行的返回值存储在全局变量$?中。
irb(main):2134:0> %x{echo "Hello World!"}
=> "\"Hello World!\"\n"
反引号的默认行为是执行命令替换,同样,我们也可以重写它,赋予它新的含义。
alias old_backquote `
def `(cmd)
result = old_backquote(cmd)
if $? != 0
fail "Command #{cmd} failed: #$?"
else
puts "Command #{cmd} success."
end
result
end
print `date`
print `data`
执行结果为:
Command date success.
Mon Jan 15 21:48:16 CST 2007
Command uname success.
Linux
§6.3 赋值运算符
to-do 定义。
赋值运算的返回值就是左值的值,所以可以进行链式赋值。
irb(main):001:0> a = b = c = 5
=> 5
irb(main):002:0> a = ( b = 1 + 2 ) + 5
=> 8
Ruby的基本赋值有两种形式,一种左边是一个对象或变量,这时把右边的值或变量的引用赋予左边。这种赋值运算由语言本身提供。
irb(main):003:0> str = "This is a dog."
=> "This is a dog."
irb(main):004:0> num = 100
=> 100
另一种形式的赋值运算左边是一个类的实例的某一属性,这时候是执行这个类的方法,方法名称为“属性=”。方法的返回值就是右值的值,你可以重写这个方法从而赋予它新的含义。
irb(main):001:0> class Test
irb(main):002:1> def num=(num)
irb(main):003:2> @num = num
irb(main):004:2> end
irb(main):005:1> end
=> nil
irb(main):006:0> t = Test.new
=> #<Test:0x2e20568>
irb(main):007:0> t.num = 10
=> 10
§6.4 并行赋值
Ruby中另一个有趣的地方是支持并行赋值。例如,交换两个变量a,b的值可以写为:
a,b = b,a
Ruby会先从左到右依次计算 = 右边的表达式,然后再执行赋值的动作。
irb(main):008:0> x = 0
=> 0
irb(main):009:0> a,b,c = x, x+=1, x+=2
=> [0, 1, 3]
如果左边的变量比右边的多,那么多余的变量会被赋为nil.
irb(main):001:0> x, y, z = 1, 2
=> [1, 2]
irb(main):002:0> print z
nil=> nil
如果右边的变量或值比左边的多,那么多余的会被忽略。
irb(main):001:0> x, y = 1, 2, 3 # 3将被忽略
=> [1, 2, 3]
也可以在数组赋值时使用并行赋值。
irb(main):001:0> a = [1, 2, 3, 4, 5]
=> [1, 2, 3, 4, 5]
irb(main):002:0> x, y = a
=> [1, 2, 3, 4, 5]
irb(main):003:0> puts x, y
1
2
=> nil
在对数组进行并行赋值时可以使用*,*出现在左边最后一个变量时,表示将数组中所有剩余的值赋给这个变量。
irb(main):001:0> a = [1, 2, 3, 4, 5]
=> [1, 2, 3, 4, 5]
irb(main):002:0> x,*y = a
=> [1, 2, 3, 4, 5]
irb(main):003:0> puts x
1
=> nil
irb(main):004:0> puts y
2
3
4
5
=> nil
*出现在右边最后一个变量时,和左边类似。
a = [1, 2, 3, 4]
b, c = 9, a =〉b == 9, c == [1, 2, 3, 4]
b, c = 9, *a =〉b == 9, c == 1
b, *c = 9, a =〉b == 9, c == [[1, 2, 3, 4]]
b, *c = 9, *a =〉b == 9, c == [1, 2, 3, 4]
§6.5 嵌套赋值
在赋值中,左边的变量可以使用括号括起来。这样括号内的变量被视作位于一个层次。
b, (c, d), e = 1,2,3,4 =〉b == 1, c == 2, d == nil, e == 3
b, (c, d), e = [1,2,3,4] =〉b == 1, c == 2, d == nil, e == 3
b, (c, d), e = 1,[2,3],4 =〉b == 1, c == 2, d == 3, e == 4
b, (c, d), e = 1,[2,3,4],5 =〉b == 1, c == 2, d == 3, e == 5
b, (c,*d), e = 1,[2,3,4],5 =〉b == 1, c == 2, d == [3, 4], e == 5
§6.6 其他赋值
Ruby支持自加(+=)和自减运算符。和C/C++/Java一样,a = a + 2可以写成a+=2。其它类似的运算符还有%= ~= /= = += |= &= >>= <<= *= &&= ||= **=。
我们经常可以遇到类似这样的语句words[key] ||= [],他与words[key] = words[key] || []等价,意思是如果Hash表words[key]的值为空时,对words[key]赋值为一个新建的空数组,否则不变。
相应的,对于
num = 1 if num.nil?
num = 1 unless num
Ruby中习惯写为 num ||= 1,这样代码更简洁。
§6.7 条件运算
布尔运算符
在Ruby中定义nil和false为假,其他值为真。注意,和C/C++不同的是0并不被解释为假,空字符串也一样。
Ruby支持常见的布尔运算符,例如 and和 &&,而且还引入一个新的布尔运算符‘defined?’。
和其他程序设计语言一样,and 和 && 代表与关系。
or 和 || 代表或关系。
Not 和 ! 代表非关系。
如果参数没有定义 defined? 返回nil,否则返回一个描述串用来描述参数信息。
irb(main):013:0> defined? 1
=> "expression"
irb(main):014:0> defined? dummy
=> nil
irb(main):015:0> defined? printf
=> "method"
irb(main):016:0> defined? String
=> "constant"
irb(main):017:0> defined? $_
=> "global-variable"
irb(main):018:0> defined? Math::PI
=> "constant"
irb(main):019:0> defined? a = 0
=> "assignment"
irb(main):020:0> defined? 30.abs
=> "method"
条件运算符
Ruby支持一系列条件运算符,==, ===, <=>,=~,eql? 等等,equal?。除了<=> 其他的都是类方法。 == 和 =~ 有否定形式 != 和 !~。
If和unless
Ruby中的if和其他程序设计语言中的if大同小异:
if x == 5 then
print “The value of x is 5.”
elsif x == 0 then
print “The value of x is 0.”
else
print “The value of x is ”, x
end
也可以省略then:
if x == 5
print “The value of x is 5.”
elsif x == 0
print “The value of x is 0.”
else
print “The value of x is ”, x
end
如果为了代码紧凑而将代码放到同一行则不能省略then:
if x == 5 then print “The value of x is 5.”
elsif x == 0 then print “The value of x is 0.”
else print “The value of x is ”, x
end
也可以使用冒号分隔,这样代码更紧凑 :
if x == 5: print “The value of x is 5.”
elsif x == 0: print “The value of x is 0.”
else print “The value of x is ”, x
end
正如我们前面所说的,if是一个表达式,它有自己的返回值。你可以忽略这个返回值,但是他确实存在。If语句的返回值就是最后执行的语句的值。
x = 10
str = if x == 5: "x==5"
elsif x == 0: "x==0"
else "x==?"
end
执行后str的内容为:x== ?
Ruby也支持if的否定形式unless,unless的语法和if没有差别。
unless x != 5
print “The value of x is 5.”
else
print “The value of x is not 5.”
end
Ruby也支持C/C++的 ?:运算符。
str = x == 5? "x==5":"x==?"
Ruby也从Perl那里继承了一个很好的语法,你可以将条件写到表达式的后边。
puts "a = #{a}" if debug
print total unless total.zero?
§6.8 case表达式
Ruby中的case语句非常强大,首先我们来看一个基本用法:
grade = case
when point >= 85: 'A'
when point >= 70 && point < 80: 'B'
when point >= 60 && point < 70: 'C'
when point < 60: 'D'
else 'E'
end
这里case语句的作用和if表达式类似,case语句的返回值就是最后一个执行的表达式的值。和if语句类似,如果写在同一行的话需要加then或冒号。
另一种也是最常用的形式是在case后列出目标,然后每个语句依次和目标比较:
case input_line
when "debug"
print "We are in debug mode."
when /p\s+(\w+)/
dump_variable($1)
when "quit", "exit"
exit
else
print "Illegal command: #{input_line}"
end
另一个例子:
Season = case month
when 3..5 : "Spring"
when 6..8 : "Summer"
when 9..11: "Autumn"
when 12..2: "Winter"
else "Error"
end
Ruby提供了一个运算符===,只要一个类提供了===方法,那这个类的对象就可以出现在case语句中。例如对于正则表达式定义了===为模式匹配。
Ruby中,所有类的基类是Class类,所有类实例都是Class类的实例(to-do)。它定义===的含义为为参数所提供是否为实例的类或父类。
case shape
when Square, Rectangle
# ...
when Circle
# ...
when Triangle
# ...
else
# ...
end
§6.9 循环
§6.9.1 Loop
Loop循环始终执行其后的方法块,直到break退出。
x = 0
loop do
x += 1
if x <= 5: print x, " "
else break
end
end
执行结果为:1 2 3 4 5 。
§6.9.2 While
当条件为真时While循环继续,条件为假时退出循环。
x = 0
while x < 10
x += 1
end
§6.9.3 Until
Until和While厢房当条件为假时While循环继续,条件为真时退出循环。
x = 0
until x == 9
x += 1
end
§6.9.4 Iterator
和C/C++/Java不同,Ruby语言并不支持C/C++/Java中的For循环,但Ruby通过迭代器来提供更为强大的功能。先看一个例子:
4.times do
puts "Hello!"
end
执行结果为:
Hello!
Hello!
Hello!
Hello!
除了times方法之外,整数还提供upto和downto两个方法,看以下例子,
0.upto(9) do |i|
print i, " "
end
执行结果为0 1 2 3 4 5 6 7 8 9 。
也可以使用Step方法,step第二个参数表示步长:
0.step(10, 2) do |i|
print i, " "
end
执行结果为:0 2 4 6 8 10 。
许多容器类,例如数组,提供了each方法依次遍历容器中的数据:
[1, 2, 3, 4, 5].each { |i| print i, " "}
执行结果为:1 2 3 4 5 。
如果一个类支持each方法,那么就可以使用Enumerable模块中的一些方法。
["apple", "orange", "banana", "watermelon"].grep(/an/) do |fruit|
puts fruit
end
执行结果为:
orange
banana
§6.9.5 For..In
如果一个类提供了each方法,那么相应的,这个类的对象可以使用For..in循环。例如Array类和Range类都有each方法:
for fruit in ["apple", "orange", "banana", "watermelon"]
print fruit, " "
end
执行结果为:apple orange banana watermelon 。
for i in 1..9
print i, " "
end
执行结果为:1 2 3 4 5 6 7 8 9 。
§6.9.6 Break,Redo,Next
Break,Redo和Next用来改变循环的流程。
§6.9.6.1 break
Break用来退出当前循环:
times = 0
loop do
times += 1
print "hello #{times}\n"
break if times > 2
end
执行结果为:
hello 1
hello 2
hello 3
与C/C++不同,如果循环有多重的话,break将退出最内层的循环。
outer = 0
loop do
outer += 1
inner = 0
loop do
inner += 1
print "Inner #{inner}\n"
break if inner > 1
end
print "Outer #{outer}\n"
break if outer > 1
end
执行结果为:
Inner 1
Inner 2
Outer 1
Inner 1
Inner 2
Outer 2
另一个与C/C++语言不同的地方是break只能从循环中退出,而不能从case中退出。
§6.9.6.2 redo
redo语句重新执行当前这一次循环。
count = 0
for i in 1..3
print "hello #{i}\n"
break if count == 1
if i > 1
count += 1
redo
end
end
执行结果为:
hello 1
hello 2
hello 2
上面的例子中,使用redo后,循环变量i的值还是2,可见redo语句重新执行了这次循环。
和break语句类似,redo语句只对最内层的循环起作用。
3.times do
count = 0
for i in 1..3
print "hello #{i}\n"
break if count == 1
if i > 1
count += 1
redo
end
end
end
执行结果为:
hello 1
hello 2
hello 2
hello 1
hello 2
hello 2
hello 1
hello 2
hello 2
§6.9.6.3 next
Next类似C/C++中的continue语句,跳转到当前循环的头部,执行下一次循环。
loop do
times += 1
next if times == 2
print "hello #{times}\n"
break if times > 3
end
执行结果为:
hello 1
hello 3
hello 4
与break,redo类似,如果循环有多重,那么next只对最内侧的循环起作用。
outer = 0
loop do
outer += 1
inner = 0
loop do
inner += 1
next if inner == 1
print "Inner #{inner}\n"
break if inner > 1
end
print "Outer #{outer}\n"
break if outer > 1
end
执行结果为:
Inner 2
Outer 1
Inner 2
Outer 2
§6.9.7 Retry
上一节我们看到,可以使用redo重新执行当前这一次的循环,有时候,我们也需要重新执行整个循环而不是仅仅执行当前这次,这时候我们可以用时retry。在迭代、块或for语句中使用retry,意味着重启迭代器。同时迭代器的参数也将被重新计算。
一个示例如下,
for i in 1..5
retry if some_condition # 从 i == 1 开始重新执行
end
看一个完整可执行的例子:
count = 0
for i in 1..3
pr
第一章 Ruby语言概述
§1.1 Ruby的历史
Ruby语言的发明人是日本人松本行弘(Matsumoto Yukihiro),大家亲切的称呼他"Matz"。
可能会出乎大家的意料,Ruby并不是一种近年来才诞生的语言,它的历史可以追溯到1993年,Ruby之父Matz开始对脚本语言感兴趣。在通过一些分析和思考之后,Matz认为脚本语言是可以变得很强大和灵活的,于是他准备把脚本语言作为他的发展方向。 和很多人一样,Matz是一个面向对象程序设计的fans,自然而然他想研究一种支持面向对象程序设计的脚本语言。随后的一段时间,他到网络上搜集了一些相关的资料,并且发现了Perl 5,当时Perl 5还没有发布。通过一段时间了解后,Matz.发现Perl 5这并不是他想的东西,所以他放弃了把Perl当作一个面向对象的脚本语言使用的念头。随后Matz转向了Python,Python是一个解释型的、面向对象语言,但是Matz发现Python并不能完全算作“面向对象”语言。Matz认为Python是面向对象和过程化程序设计语言(Procedural Programming Language)的混合产物。Matz希望找到的是一种比Perl更强大、比Python更面向对象的语言,但是很遗憾, 这样的语言当时在地球上并不存在。于是Matz打算自己设计一个全新的编程语言。1993年2月24日是一个值得纪念的日子,在这一天Ruby诞生了。 1995年12月Matz推出了Ruby的第一个版本Ruby 0.95。 在1996年以前,都是Matz.一个人在开发进行Ruby的开发。后来随着Ruby社区的渐渐形成,很多社区成员给了Matz许多有意义的帮助,包括提交bug和patch等。现在,Ruby像其他开源项目一样,有自己的开发团队,任何有能力的个人或团体都可以参与Ruby的开发与进化。
§1.2 Ruby名字的由来
首先明确一点,Ruby并不是其他单词的缩写。受Perl的影响,Matz也想用一种宝石来命名他的新语言,他使用了他的一位同事的生肖石-红宝石。后来,Matz意识到Ruby这个名字十分恰当,首先,在生肖石中,Pearl代表六月,而Ruby代表七月。在字体大小上,Pearl大小是5pt, ruby的大小是5.5pt。所以Ruby这个名字对于一种Perl的后续语言十分合适。
§1.3 Ruby的特点
Ruby是一种功能强大的面向对象的脚本语言,可以使用它方便快捷地进行面向对象程序设计。与Perl类似,而且Ruby具有强大的文本处理功能,使文本处理变得简单。此外还可以方便地使用C语言来扩展Ruby的功能。
若您曾经“想要一种简单的面向对象的语言”,或者认为“Perl的功能虽然好用,但它的语法真让人受不了”,又或者觉得“LISP系列语言的思想不错,但到处都是括号真让人讨厌,最起码算式应该按照通常的样式书写”。那么,Ruby或许能让您满意。
归纳起来,Ruby有以下优点:
解释型执行,方便快捷
Ruby是解释型语言,其程序无需编译即可执行。
语法简单、优雅
语法比较简单,类似Algol系语法。
完全面向对象
Ruby从一开始就被设计成纯粹的面向对象语言,因此所有东西都是对象,例如整数等基本数据类型。
内置正则式引擎,适合文本处理
Ruby支持功能强大的字符串操作和正则表达式检索功能,可以方便的对字符串进行处理。
自动垃圾收集
具有垃圾回收(Garbage Collect,GC)功能,能自动回收不再使用的对象。不需要用户对内存进行管理。
跨平台和高度可移植性
Ruby支持多种平台,在Windows, Unix, Linux, MacOS上都可以运行。Ruby程序的可移植性非常好,绝大多数程序可以不加修改的在各种平台上加以运行。
有优雅、完善的异常处理机制
Ruby提供了一整套异常处理机制,可以方便优雅地处理代码处理出错的情况。
拥有很多高级特性
Ruby拥有很多高级特性,例如操作符重载、Mix-ins、特殊方法等等,是用这些特性可以方便地完成各种强大的功能。
同时,由于是解释型语言,Ruby也有下列缺点:
解释型语言,所以速度较慢
静态检查比较少
§1.4 Ruby和Python的比较
Python是Ruby的劲敌。其功力深厚,可谓“千年蛇妖”。但matz认为Python的功能仍不完美,不然就不会创造Ruby了。
第二章 Ruby编程环境
§2.1 Ruby的安装
Ruby支持多种平台,包括Windows、Linux、各种类UNIX、MacOS X等。
§2.1.1 在Windows 95/98/Me/XP上安装Ruby
对于使用Windows平台的用户,安装Ruby是相当简单直接的事情。最方便的方法是使用“One-Click Ruby Installer”。
不知你有没有听说过SourceForge?SourceForge 是全球最大的开放源代码软件开发平台和仓库。它集成了很多开放源代码应用程序,为软件开发提供了整套生命周期服务。在Ruby世界,也有一个类似的网站,那就是Rubyforge。“One-Click Ruby Installer”是Rubyforge上的一个开源项目,也是Rubyforge上下载量最大的项目之一。这个项目将Ruby语言核心和一系列常用扩展集成到了一起,还包含支持Ruby的免费的IDE工具FreeRIDE和SciTE,除了这些之外还包括帮助文档,示例代码,RubyGems包管理器,Fox GUI库,fxri(Interactive Ruby Help & Console)等。和正如它名字所示,使用它,Ruby安装变得前所未见的容易。你可以在下面的地址下载到它的最新版本:
http://rubyforge.org/projects/rubyinstaller/
§2.1.2 在Linux上安装Ruby
在linux下Ruby的安装要稍微复杂一些,推荐使用源码编译的方式安装,这样可以保证安装的是最新版本。
首先到ruby主站http://www.ruby-lang.org/en/ 下载源代码,下载完毕后解压到目录,然后使用以下命令:
./configure
./make; make install
执行上面的命令需要root权限,默认安装到/usr/local下。你也可以使用“./configure --prefix=自定义路径”来指定安装目录。
windows上的ruby one-click installer默认安装了RubyGems,但在Linux下我们需要手动安装RubyGems。RubyGems是一个Ruby的包管理器,我们后边会讲到它。
首先从Rubyforge下载RubyGems的最近版本,地址如下:
http://rubyforge.org/projects/rubygems/
解压RubyGems以后到相应目录下输入ruby setup.rb,屏幕上打印一些日志以后会告诉你安装成功,执行gem -v可以查看gem安装版本号。
§2.2 运行Ruby
下面,我们将以Windows平台下的Ruby环境举例如何运行Ruby。
§2.2.1 使用Ruby
将“Hello World”作为学习计算机语言第一个学写的程序,现在已经成为一种传统。该程序最早出现在由Brian Kernighan和Dennis Ritchie写的经典计算机程序设计教程《The C Programming Language》。我们来看看Ruby世界的“Hello World”:
在Windows中,打开命令行提示符窗口,在提示符上输入“Ruby”并回车,Ruby解释器就会运行并等候输入程序。Ruby可执行文件应该包含在系统搜索路径内。
输入下面的程序:
print "Hello World!"
然后按Ctrl+D再按回车键,你就会看到Ruby执行程序的输出结果:
你也可以先将代码保存为文件,然后使用再Ruby解释器执行:
§2.2.2 使用FreeRIDE和SciTE
FreeRIDE是一个支持Ruby语言的免费IDE环境。FreeRIDE本身就是使用Ruby语言开发,它也是Rubyforge上的重要项目之一。
可以使用FreeRIDE来编写调试和执行Ruby代码,FreeRIDE内置了交互式变成环境和Ruby语言在线帮助,功能十分强大。
Scintilla是一个免费的源代码编辑控件,它完全开放源代码,并允许用户自由地用于开源软件或是商业软件中。SciTE是用这个控件开发了一个编辑软件,在“One-Click Ruby Installer”中,SciTE集成了Ruby语言支持,使用起来非常方便。相比FreeRIDE,它的特点就是使用简单。
§2.2.3 使用fxri
Fxri是一个Ruby交互帮助和控制台工具。它不仅可作为语言的在线帮助,而且可以用作交互式Ruby解释器来执行程序。对于学习Ruby语言,fxri是一个非常方便的帮手。
不知你有没有听说过Fox ToolKit,它是相当轻巧的开放源代码的图形库。FXRuby是RubyForge上的一个项目,提供了Ruby语言使用Fox ToolKit的接口。而Fxri正是基于FXRuby开发,Fxri同样是RubyForge上的项目。这样你应该可以猜到Fxri名字的由来
Fxri同时集成了Ruby-irb和Ruby-ri的功能,有了它,你可以抛开Ruby-irb,Ruby-ri了,但如果你用的不是Windows系统的话,算我没说
§2.3 Ruby-irb
Ruby-irb是交互式Ruby(Interactive Ruby)的简称,用来从标准输入读入并执行Ruby代码的工具,像一个shell。
使用命令“irb”进入交互式模式,然后可以象输入命令行命令一样输入Ruby代码,代码执行的结果会立刻显示:
§2.4 Ruby-ri
和Perl一样,Ruby也设计了嵌入式文档。 ruby-ri就是查看文档的工具。Ruby-ri的执行命令为“ri”,例如你可以通过“ri String.new”来查询String类的new方法:
§2.5 RubyGems
RubyGems是Ruby社区流行的包管理工具,在以前如果要下载一个Ruby扩展或者应用程序的话,你需要先下载相应的zip包,然后解压缩,再将应用或者扩展安装到Ruby对应的目录中。但是有了RubyGems所有这些麻烦都没有了,你只需要一条命令就可以从远程服务器上下载相应的包,如果相应的应用包含其他扩展,RubyGems会提示你从远程安装所依赖的扩展。安装后 RubyGems会运行相应的程序生成rdoc帮助文档。当然你也可以将软件包下载到本地运行RubyGems本地安装命令。
统一化的管理带来的好处就是简单,有了RubyGems包管理器,Ruby应用的安装将变得前所未见的容易。RubyGems是Rubyforge下载量最大的项目之一,现在Ruby社区的应用都在朝着RubyGems的方向发展,RubyGems也将成为Ruby事实上的包管理器标准。
RubyGems包管理器的可执行命令是“gem”,gem命令包含很多子命令和相应的选项,例如:
gem -h/--help – 显示命令帮助
gem -v/--version – 显示Gems的版本号
第三章 类与对象
Ruby是一种真正的面向对象程序设计语言,面向对象指以对象为中心的理论体系。
封装(Encapsulation)
将内部结构和算法隐藏起来,以确保只有特定的过程(也叫方法)才能直接操作数据,其结果是不能从外部直接使用数据构造,同时一旦内部构造发生变化也不会对外界造成不良影响。这种隔离方法就叫做封装。
继承
多态(Polymorphism)
根据对象的不同选择合适的操作。在Ruby中的实现方法是,根据被调的对象的不同来选择不同的方法。
虽然有很多语言都宣称自己是面向对象的,但是他们往往对面向对象的解释都一样,大多是以自己特有的方式来解释什么是面向对象,而在实际情况中,这些面向对象语言又采用了很多非面向对象的做法。
以 Java 为例:如果你想取一个数字取绝对值,java 的做法是:
int num = Math.abs(-99);
也就是将一个数值传递给 Math 类的一个静态函数 abs 处理。为什么这么做?因为在 java 中,数值是基本类型不是类。
而在 Ruby 中,任何事物都是对象,也就是说,数字–99就是对象,取绝对值这样的操作应该属于数字本身,所以Ruby的做法就是:
c = -99.abs
在Ruby中,你所操作的一切都是对象,操作的结果也是对象。
§3.1 类的定义
类是对具有同样属性和同样行为的对象的抽象,Ruby中类的声明使用class关键字。定义类的语法如下,
class ClassName
def method_name(variables)
#some code
end
end
类的定义要在class…end之间,在上面的格式中,ClassName是类名,类名必须以大写字母开始,也就是说类名要是个常量。
看下面的例子:
class Person
def initialize(name, gender, age)
@name = name
@gender = gender
@age = age
end
end
若某个类已经被定义过,此时又用相同的类名进行类定义的话,就意味着对原有的类的定义进行追加。
class Test
def meth1
puts "This is meth1"
end
end
class Test
def meth2
puts "This is meth2"
end
end
在Test类中,原有meth1方法,我们又追加了meth2方法,这时候,对于Test类的对象,meth1和meth2同样可用。
§3.2 对象,属性和方法
类在实例化后生成对象,在强调对象归属于某类时,有时候我们也使用实例对象一词。
方法(Method)是对对象进行的操作。操作对象(被调)以self来表示。在Ruby中,除去内部类的对象以外,通常对象的构造都是动态确定的。某对象的性质由其内部定义的方法所决定。
看下面的例子,我们使用new方法构造一个新的对象,
class Person
def initialize(name, gender, age)
@name = name
@gender = gender
@age = age
end
end
people = Person.new('Tom', 'male', 15)
我们可以使用Person.new方法来创建一个Person类的实例对象。以@打头的变量是实例变量,他们从属于某一实例对象,Ruby中实例变量的命名规则是变量名以@开始,您只能在方法内部使用它。
initialize方法使对象变为“就绪”状态,initialize方法是一个特殊的方法,这个方法在构造实例对象时会被自动调用。
对实例进行初始化操作时,需要重定义initialize方法。类方法new的默认的行为就是对新生成的实例执行initialize方法,传给new方法的参数会被原封不动地传给initialize方法。另外,若带块调用时,该块会被传给initialize方法。因此,不必对new方法进行重定义。
在Ruby中,只有方法可以操作实例变量,因此可以说Ruby中的封装是强制性的。在对象外部不可以直接访问,只能通过接口方法访问。
class Person
def name
@name
end
def gender
@gender
end
def age
@age
end
end
people = Person.new('Tom', 'male', 15)
puts people.name
puts people.gender
puts people.age
输出结果为:
Tom
male
15
在Ruby中,一个对象的内部属性都是私有的。 上面的代码中,我们定义了方法name,gender,age三个方法用来访问Person类实例对象的实例变量。注意name,gender,age访问只能读取相应实例变量,而不能改变它们的值。
我们也可以用成员变量只读控制符attr_reader来达到同样的效果。
class Person
attr_reader :name, :gender, :age
end
类似地,我们可以定义方法去改变成员变量的值。
class Person
def name=(name)
@name=name
end
def gender=(gender)
@gender=gender
end
def age=(age)
@age=age
end
end
people = Person.new('Tom', 'male', 15)
people.name = "Henry"
people.gender = "male"
people.age = 25
也可以用成员变量写控制符attr_writer来达到同样的效果。
class Person
attr_writer :name, :gender, :age
end
我们也可以使用attr_accessor来说明成员变量既可以读,也可以写。
class Person
attr_accessor :name, :gender, :age
end
也可以使用attr控制符来控制变量是否可读写。attr 只能带一个符号参数, 第二个参数是一个 bool 参数,用于指示是否为符号参数产生写方法。它的默认值是 false,只产生读方法,不产生写方法。
class Person
attr :name, true #读写
attr :gender, true #读写
attr :age, true #读写
attr :id, false #只读
end
注意attr_reader,attr_writer,attr_accessor和attr不是语言的关键字,而是Module模块的方法。
class Test
attr_accessor :value
end
puts Test.instance_methods - Test.superclass.public_methods
执行结果为:
value
value=
上面代码中,我们使用Test.instance_methods得到Test类所有的实例方法,使用Test.superclass.public_methods得到Test父类所有的实例方法,然后相减就得到Test类不包含父类的所有的实例方法。
由于instance_methods方法返回值为一个Array,所以我们作差值运算,Array的具体操作后面章节会讲到。
也可以重定义方法,重定义一个方法时,新的定义会覆盖原有的定义。
下面的例子重定义类中的方法meth1,
class Test
def meth1
puts "This is meth1"
end
end
a = Test.new
a.meth1
class Test
def meth1
puts "This is new meth1"
end
end
a. meth1
执行结果为:
This is meth1
This is new meth1
重定义同一个类时,意味着对原有定义进行补充,不会覆盖原来的定义。而重定义方法时,则会覆盖原有定义。
我们可以使用self标识本身,self和Java中的this有些类似,代表当前对象。
class Person
def initialize(name, gender, age)
@name = name
@gender = gender
@age = age
end
def <=>(other)
self.age <=> other.age
end
end
<=> 方法通常意思为比较,返回值为-1,0或1分别表示小于,等于和大于。
§3.3 继承
Ruby继承的语法很简单,使用 < 即可。
class Student < Person
def initialize(name, gender, age, school)
@name = name
@gender = gender
@age = age
@school = school
end
end
Ruby语言只支持单继承,每一个类都只能有一个直接父类。这样避免了多继承的复杂度。但同时,Ruby提供了mixin的机制可以用来实现多继承。
可以使用super关键字调用对象父类的方法,当super省略参数时,将使用当前方法的参数来进行调用。
class Base
def meth(info)
puts "This is Base #{info}"
end
end
class Derived < Base
def meth(info)
puts "This is derived #{info}"
super
end
end
obj1 = Derived.new
obj1.meth("test")
执行结果为:
This is derived test
This is Base test
如果传入的参数被修改再调用super的话,那么将会使用使用修改后的值。
class Base
def meth(info)
puts "This is Base #{info}"
end
end
class Derived < Base
def meth(info)
puts "This is derived #{info}"
info = "over"
super
end
end
obj1 = Derived.new
obj1.meth("test")
执行结果为:
This is derived test
This is Base over
§3.4 特殊方法与特殊类
特殊方法是指某实例所特有的方法。一个对象有哪些行为由对向所属的类决定,但是有时候,一些特殊的对象有何其他对象不一样的行为,在多数程序设计语言中,例如C++和Java,我们必须定义一个新类,但在Ruby中,我们可以定义只从属于某个特定对象的方法,这种方法我们成为特殊方法(Singleton Method)。
class SingletonTest
def info
puts "This is This is SingletonTest method"
end
end
obj1 = SingletonTest.new
obj2 = SingletonTest.new
def obj2.info
puts "This is obj2"
end
obj1.info
obj2.info
执行结果为:
This is This is SingletonTest method
This is obj2
有时候,我们需要给一个对象定义一系列的特殊方法,如果按照前面的方法,那么只能一个一个定义:
def obj2.singleton_method1
end
def obj2.singleton_method2
end
def obj2.singleton_method3
end
……
def obj2.singleton_methodn
end
这样做非常繁复麻烦,而且无法给出一个统一的概念模型,因此Ruby提供了另外一种方法,
class << obj
……
end
obj是一个具体的对象实例,class << 代表它的特殊类。
class SingletonTest
def meth1
puts "This is meth1"
end
def meth2
puts "This is meth2"
end
end
obj1 = SingletonTest.new
obj2 = SingletonTest.new
class << obj2
def meth1
puts "This is obj2's meth1"
end
def meth2
puts "This is obj2's meth2"
end
end
obj1.meth1
obj1.meth2
obj2.meth1
obj2.meth2
执行结果为:
This is meth1
This is meth2
This is obj2's meth1
This is obj2's meth2
§3.5 类变量与类方法
类变量被一个类的所有实例对象共享,也可以被类方法访问到。类变量名以‘@@’,开始,例如‘@@number’。和全局变量,实例变量不同,类变量在使用前必须初始化:
class Person
@@number = 0 #使用前必须有初值
def initialize(name, gender, age)
@name = name
@gender = gender
@age = age
@@number += 1
end
end
类变量是私有的,在类外无法直接访问,你只能通过实例方法和类方法去访问它。
同样,类方法是属于一个类的方法,定义类方法时需要在方法前加上类名:
class Person
@@number = 0
def initialize(name, gender, age)
@name = name
@gender = gender
@age = age
@@number += 1
end
def Person.getNumber #类方法
return @@number
end
end
除了Person.getNumber这种方式定义类方法外,还可以使用其它方式定义类方法,在后续章节可以陆续见到。
§3.4 存取控制
当你设计一个类时,你需要决定哪些属性和方法可以在类外被访问到,哪些属性和方法在类外被隐藏。如果一个类有过多的属性和方法在类外可以被访问到,那么势必破坏这个类的封装性。幸运的是在Ruby中,只能通过方法去改变一个类的属性,这样我们只需要考虑方法的存取控制。
方法的存取控制有三种:
公有方法(Public Method)
方法在任何地方都可以被调用,这是方法的默认存取控制。除了initialize和initialize_cpoy方法,他们永远是私有方法。
保护方法(Protected Method)
方法只能被定义这个方法的类自己的对象和这个类的子类的对象所访问。
私有方法(private Method)
方法只能被定义这个方法的类的对象自己访问,即使是这个类的其他对象也不能访问。
Ruby中的保护方法和私有方法与一般面向对象程序设计语言的概念有所区别,保护方法的意思是方法只能方法只能被定义这个方法的类自己的对象和子类的对象访问,私有方法只能被对象自己访问。
class Test
def method1 #默认为公有方法
…
end
protected #保护方法
def method2
…
end
private #私有方法
def method3
end
public
def test_protected(arg) #arg是Test类的对象
arg.method2 #正确,可以访问同类其他对象的保护方法
end
def test_private(arg) #arg是Test类的对象
arg.method3 #错误,不能访问同类其他对象的私有方法
end
end
obj1 = Test.new
obj2 = Test.new
obj1.test_protected(obj2)
obj1.test_private(obj2)
可以看到,和C++/Java相比,Ruby提供了更好的封装性。
也可以使用以下更简单的形式:
class Test
def method1
...
end
def method2
...
end
def method3
...
end
def methdo4
...
end
public :method1
protected :method2
private :method3, :method4
end
Ruby和C++/Java的一个显著不同是存取控制是程序运行时决定的而不是静态绑定的。所以只有在访问一个受限制的方法时才会产生运行时错误。
§3.6 元类
在Ruby中一切都是对象。类和实例对象都是对象。这句话听起来有点拗口,让我们来看一个例子:
class Person
def initialize(name, gender, age)
@name = name
@gender = gender
@age = age
end
end
a = Person.new('Tom', 'male', 15)
puts a.object_id => 22429840
puts Person.object_id => 22429960
没错,类也是对象,这是Ruby和C++/Java的一个显著不同,在C++/Java中,类仅仅是一个数据抽象,并没有类也是对象这样的概念。而在Ruby中存在着元类的概念,类也是对象,所有类都是元类的实例对象。和C++/Java相比,Ruby的面向对象程度更高。
可以看到,类对象和实例对象一样有自己的ojbect_id,你可以象调用一个实例对象的方法一样去用它去调用类方法。所有类对象的类是Class类,Oject类是所有类的基类。
irb(main):003:0> Object.class
=> Class
irb(main):004:0> Object.superclass
=> nil
这样,我们可以从另一个角度去理解类变量与类方法,类变量就是一个类对象的实例变量,类方法就是指一个类对象类的特殊方法。
类方法具体可分为两种:第一种是在所有的类的父类Class中定义的,且被所有的类所共享的方法;第二种是各个类所特有的特殊方法。
类方法中的self指的是类本身,这点需要牢记,这样我们可以使用多种方式定义类方法。
class Test
#定义类方法方式1
def Test.meth1
# ...
end
#定义类方法方式2
def self.meth2
# ...
end
#定义类方法方式3
class << Test
def meth3
# ...
end
end
#定义类方法方式4
class << self
def meth4
# ...
end
end
end
§3.7 Ruby的动态性
可以重新定义同一个方法,
class RedefTest
def meth
puts "This is meth"
end
end
obj1 = RedefTest.new
obj1.meth
class RedefTest
def meth
puts "This is new meth"
end
end
obj1.meth
执行结果为:
This is meth
This is new meth
可以使用undef_method取消一个方法的定义,
class UndefTest
def meth
puts "This is meth"
end
end
obj1 = UndefTest.new
obj1.meth
class UndefTest
undef_method(:meth)
end
obj1.meth
执行结果为:
This is meth
test.rb:14: undefined method `meth' for #<UndefTest:0x2ac8240> (NoMethodError)
§3.8 变量
变量名长度只受内存大小的限制。可以通过区分Ruby变量名的首字符来区分它是局部变量、实例变量、类变量、全局变量还是常量。通常情况下,变量名的第二位字符以后是数字、字母或下划线,但有的内部变量名比较特殊,如“$?”。
§3.8.1 局部变量
局部变量以小写字母或下划线开始。
num = 1
foo
局部变量的作用域起始于声明处,结束于该声明所在的块、方法定义、类/模块定义的结尾。
2.times {
p defined?(num)
num = 10
p num
}
输出为:
nil
10
nil
10
即使声明部分未被解释器执行仍有效,因为已经经过解释器的处理。
v = 1 if false
p defined?(v)
p v
输出为:
"local-variable"
nil
但若块已经变成过程对象的话,则局部变量将一直持续到该过程对象终结为止。若多个过程对象引用同一个作用域的话,局部变量将被这些对象所共享。
(to-do例子)
§3.8.2 实例变量
以@开始的变量是实例变量,实例变量属于特定的对象。
class Person
def initialize(name, gender, age)
@name = name
@gender = gender
@age = age
end
end
上面的例子中,@name, @gender,@age都是实例变量。可以在类或子类的方法中引用实例变量。若引用尚未被初始化的实例变量的话,其值为nil。
§3.8.3 类变量
以@@开始的变量是类变量。类变量在类的定义中定义,可以在类的特殊方法、实例方法等处对类变量进行赋值和引用。类变量被类,类的子类和他们的实例对象共享。
class Person
@@number = 0 #使用前必须有初值
def initialize(name, gender, age)
@name = name
@gender = gender
@age = age
@@number += 1
end
end
类变量是私有的,在类外无法直接访问,你只能通过实例方法和类方法去访问它。可以把类变量看作一种被类、子类以及它们的实例所共享的全局变量。
模块中定义的类变量(模块变量)被所有包含该模块的类所共享。
module TestModule
@@foo = 10
end
class Klass
include Foo
p @@foo += 1 # => 11
end
class Base
include Foo
p @@foo += 2 # => 12
end
§3.8.4 全局变量
以$开始的变量是全局变量,全局变量可以在程序的任何地方加以引用。全局变量无需变量声明。引用尚未初始化的全局变量时,其值为nil。
Ruby运行时环境预定义了一系列的全局变量,有关预定义的全局变量的信息,请参见附表。
§3.8.5 常量
常量以大写字母开始,常数的定义和初始化由赋值过程完成。
PI = 3.14
E = 2.71
若对已定义的常数进行赋值的话,会出现警告信息。若引用未定义的常数会引发NameError异常。
PI = 3.14
obj1 = 2 * PI * 10
PI = 3.1415 # warning: already initialized constant PI
obj2 = Foo #uninitialized constant Foo (NameError)
常量可以定义在类和模块中,不能定义在方法中。
class Meth
PI = 3.14 #OK
end
def circle_area(arg)
PI = 3.14 #ERROR
PI * arg * arg
end
若想在外部访问类或模块中的常数时,要使用“::”操作符。
class Meth
PI = 3.14
end
def circle_area(arg)
Math::PI * arg * arg
end
在类定义表达式生成类对象的同时,还会将类对象赋值给一个与该类同名的常数,引用类名也就是引用该常数。
class Test
end
p Test.class #Class
p Test #test
若想访问Object类中的常数(顶层的常数)时,也需要也使用"::"操作符,但操作符左边为空。
§3.8 与定义有关的操作
§3.8.1 alias
Alias关键字给方法或全局变量添加别名。可以给方法名指定一个标识符或Symbol作为别名。给方法添加别名时,别名方法将和此刻的原始方法绑定,此后即使重新定义了原始方法,别名方法仍然保持着重定义前的老方法的特性。若改变了某方法的内容后,又想使用修改前的方法时,别名会很有用。也可以使用Module#alias_method给方法添加别名。
# 定义meth方法
def meth
puts "This is meth"
end
#设定别名
aliasrig_meth :meth
#重定义foo
def meth
puts "This is new meth"
end
p meth
执行结果为:
This is new meth
nil
给全局变量设定别名意味两个名称指向同一个全局变量。当你向一个赋值时,另一个也会被改变。
$abc = 1
alias $xyz $abc
$xyz = 2
p [$abc, $xyz] # => [2, 2]
但是不能给正则表达式中的变量$1,$2等添加别名,另外,有些全局变量对于解释器来说是举足轻重的,若重新定义它们的话,有时会影响解释器的正常工作。
§3.8.2 undef
undef用来取消一个方法的定义,也可以使用Module#undef_method方法取消方法的定义。undef会取消方法名和方法定义之间的关系,即使超类中有同名方法,调用时也会引发异常。
class Base
def meth
puts "This is Base#meth"
end
end
class Derived < Base
def meth
puts "This is Derived#meth"
end
end
class Test1 < Derived
def meth
puts "This is Test1#meth"
end
undef_method(:meth)
end
obj1 = Test1.new
obj1.meth
执行结果为:
Tes1.rb:22: undefined method `meth' for #<Test1:0x2ac7c88> (NoMethodError)
而Module#remove_method方法只负责取消当前类中方法名和方法定义之间的关系,父类的同名方法仍可调用,这点差别非常重要。
class Base
def meth
puts "This is Base#meth"
end
end
class Derived < Base
def meth
puts "This is Derived#meth"
end
end
class Test2 < Derived
def meth
puts "This is Test2#meth"
end
remove_method(:meth)
end
obj2 = Test2.new
obj2.meth
执行结果为:
This is Derived#meth
用alias添加别名或用undef取消定义时,会修改类的接口,而不受父类的限制。继承和Mix-in的功能都是在类中添加方法,而undef则可以取消方法。但是,如果取消了类所必需的方法(被其他方法所调用的方法)的话,其后果不堪设想。
§3.8.3 defined?
Defined?用来判断表达式是否定义。若表达式尚未定义,则返回nil,若已经定义,则返回一个字符串描述该表达式的种类。
defined? Val #=> nil
defined? true #=> “true”
defined? $* #=> "global-variable"
defined? Array #=> "constant"
defined? Math::PI #=> "constant"
defined? num = 0 #=> "assignment"
defined? 100 #=> "expression"
defined? 100.times #=> "method"
虽然defined?看起来像一个方法,实际上是Ruby语法中的操作符,因此不会对参数进行计算。因此下面的表达式并不会输出“abc”。
defined? print("abc\n")
如果是方法未定义,或方法使用undef或Module#remove_method取消了原有定义,defined?都将返回nil。
注意如果一个方法以大写字母开头,使用defined? 判断时需要在方法名后添加"()"时,否则方法名会被当做常数处理。
def Foo(arg)
end
p defined? Foo # => nil
p defined? Foo() # => "method"
Foo = 1
p defined? Foo # => "constant"
还可以使用下列特殊用法:
判断yield是否可用
defined? yield
若yield调用可用,则返回真,具体返回值为字符串"yield"。它的作用同block_given?一样,可以判断能否以带块方式来调用某方法。
class Base
def foo
puts defined? yield
end
end
a = Base.new
a.foo
a.foo {}
执行结果为:
nil
yield
判断super是否可用
defined? super
若super可被调用,则返回真, 具体返回值为字符串"super"。
class Base
def foo
end
end
class Derived < Base
def foo
puts defined? super
end
def fun
puts defined? super
end
end
obj = Derived.new
obj.foo
obj.fun
执行结果为:
super
nil
返回没有赋值但已经定义的局部变量.
defined? a = 1 #=> assignment
p a # => nil
在正则表达式中使用
/(.)/ =~ "foo"
p defined? $& # => "$&"
p defined? $1 # => "$1"
p defined? $2 # => nil
第四章 基本类型
§4.1 Array
Array也称作数组,是一系列元素的有序集合。你可以显式使用Array类的new方法来创建一个数组对象,你也可以用方括号包围起来一些以逗号分隔的数字或字符串构成一个数组。
irb(main):007:0> a = [ "first" "second" "third" ]
=> ["firstsecondthird"]
irb(main):008:0> a = [ "first", "second", "third" ]
=> ["first", "second", "third"]
irb(main):009:0> a.class
=> Array
irb(main):010:0> a.length
=> 3
irb(main):011:0> a[0]
=> "first"
irb(main):012:0> a[1]
=> "second"
irb(main):013:0> a[2]
=> "third"
irb(main):014:0> a[3]
=> nil
irb(main):015:0> b = Array.new
=> []
irb(main):016:0> b.class
=> Array
irb(main):017:0> b.length
=> 0
irb(main):018:0> b[0] = "first"
=> "first"
irb(main):019:0> b[1] = "second"
=> "second"
irb(main):020:0> b
=> ["first", "second"]
数组可以使用 [] 来索引,其实 [] 是Array类的一个方法,它甚至可以被子类覆盖(overridden)。Ruby中比较有趣的是有多种对数组的索引方法,你可以用负数来索引数组。负数表示从尾部开始,例如索引为-1表示最后一个元素,索引为-2表示倒数第二个元素,以此类推。
irb(main):021:0> a = [ 1, 2, 3, 4, 5]
=> [1, 2, 3, 4, 5]
irb(main):022:0> a[-1]
=> 5
irb(main):023:0> a[-2]
=> 4
irb(main):024:0> a[-9]
=> nil
你也可以使用一对数来索引数组,第一个数表示开始位置,第二数表示从开始位置起的元素数目。
irb(main):025:0> a = [ 1, 2, 3, 4, 5]
=> [1, 2, 3, 4, 5]
irb(main):026:0> a[1, 3]
=> [2, 3, 4]
irb(main):027:0> a[3, 1]
=> [4]
irb(main):028:0> a[-3, 1]
=> [3]
你甚至可以用一个范围来索引数组,.. 表示包含尾部元素,... 表示不包含尾部元素。
irb(main):029:0> a = [ 1, 2, 3, 4, 5]
=> [1, 2, 3, 4, 5]
irb(main):030:0> a[1..3]
=> [2, 3, 4]
irb(main):031:0> a[1...3]
=> [2, 3]
§4.2 Hash
Hash也称作哈希表哈希表,类似于数组但是每个元素都有索引,有时候也被称作关联数组,哈希数组或字典。哈希表和数组不同,数组只能使用数字索引,而哈希表则可以使用任何对象索引。哈希表和数组的另一个显著不同是哈希表中的元素是无序的。在Ruby中每个哈希表都是Hash类的对象。
在哈希表中,我们称索引为Key,被索引的元素称为Value。
我们可以使用=>连接的元素来创建一个哈希表,注意哈希表外部是使用大括号包围。
irb(main):032:0> h = { "first" => "Amy", "second" => "Mike", "third" => "Tom" }
=> {"third"=>"Tom", "second"=>"Mike", "first"=>"Amy"}
irb(main):033:0> h.length
=> 3
irb(main):034:0> h["first"]
=> "Amy"
irb(main):035:0> h['second']
=> "Mike"
irb(main):036:0> h[100] = "Henry"
=> "Henry"
irb(main):037:0> h["nine"] = "Rose"
=> "Rose"
irb(main):038:0> h
=> {"third"=>"Tom", "second"=>"Mike", 100=>"Henry", "first"=>"Amy", "nine"=>"Rose"}
§4.3 Number
Ruby支持整数类型和浮点数类型。整数可以是任意长度(这个长度只和内存大小有关)。在一定范围内的整数被视为Fixnum类的对象。超出这个范围的整数被视为Bignum类的对象。
num = 81
6.times do
puts "#{num.class}: #{num}"
num *= num
end
运行结果:
Fixnum: 81
Fixnum: 6561
Fixnum: 43046721
Bignum: 1853020188851841
Bignum: 3433683820292512484657849089281
Bignum: 11790184577738583171520872861412518665678211592275841109096961
和C/C++相同,Ruby规定以0开头的数为八进制数,以0x开头的数为十六进制数,以0b开头的数为二进制数。
irb(main):001:0> 16
=> 16
irb(main):002:0> 020
=> 16
irb(main):003:0> 0x10
=> 16
irb(main):004:0> 0b10000
=> 16
一个数中间可以用下划线连接,下划线自动被忽略。
irb(main):005:0> 123_456_789
=> 123456789
可以使用"?\C-x"或"?\cx"生成控制字符。
如果一个数包含小数点或者包含"e",那么这个数将被转为Float类的对象。
irb(main):012:0> 1.0.class
=> Float
irb(main):013:0> 1.0e3.class
=> Float
§4.4 String
String也称作字符串,是单引号或双引号包围起来的一串字符。单引号和双引号的意义有所不同,双引号包围的字符作变量替换,单引号包围的变量不做替换。可以在字符串中使用 #{expr} 嵌入代码。
irb(main):022:0> "The seconds in a day is: #{24*60*60}"
=> "The seconds in a day is: 86400"
irb(main):023:0> 'The seconds in a day is: #{24*60*60}'
=> "The seconds in a day is: \#{24*60*60}"
也可以使用 %q 和 %Q 来生成字符串对象。%q 相当于单引号,%Q相当于双引号。
irb(main):051:0> %q/Single quote/
=> "Single quote"
irb(main):052:0> %Q/Double quote/
=> "Double quote"
irb(main):053:0> %q/ #{50*50} /
=> " \#{50*50} "
irb(main):054:0> %Q/ #{50*50} /
=> " 2500 "
%q 和 %Q 后面的第一个字符为分隔符。二哥分隔符之间的字符被认为一个是字符串。但是如果这个分隔符是 [ { <, 那么结束标志为匹配的 ] } >。
irb(main):055:0> %q{This is a string}
=> "This is a string"
irb(main):056:0> %Q[This is a string]
=> "This is a string"
irb(main):057:0> %q<This is a string>
=> "This is a string"
你也可以使用“Here Document”的方法来生成字符串,这种方法规定 << 之后的字符串作为结束标志。
string = <<END_OF_STRING
With publication started in June 1948 and a current circulation of 3 million,
People's Daily is the most influential and authoritative newspaper in China.
According to UNESCO, it takes its place among the world top 10.
END_OF_STRING
需要注意,表示结尾的END_OF_STRING必须放在行首。
§4.5 Range
Range也称作范围,用来表示一个都是连续的值的序列。可以使用 .. 和 ... 操作符来产生Range,前者表示包含最后一个元素,后者表示不包含最后一个元素。Range对象所属的类是Range。注意Range和Array是不同的,可以使用Range类的to_a方法将一个Range对象转化为Array对象。
irb(main):003:0> (1..10).to_a
=> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
irb(main):004:0> ('bar'..'bat').to_a
=> ["bar", "bas", "bat"]
irb(main):005:0> (1...10).to_a
=> [1, 2, 3, 4, 5, 6, 7, 8, 9]
Range除了表示一个序列外还可以出现在条件语句中。在条件语句中,可以把Range看作一个双向开关,当第一个条件满足时打开开关,当第二个条件满足时关闭开关。
a = [0, 1, 2, 3, 4, 5, 6]
a.each do |i|
print i, " " if i == 1 .. i == 5
end
执行结果为
1 2 3 4 5
上述代码中if i == 1 .. i == 5表示只有满足i==1且不满足i==5时条件为真,当不满足i==1或满足i==5时条件为假,直观地看,就是表示元素需要位于范围之中。
可以使用 === 来测试一个元素是否在某个范围:
irb(main):093:0> (1..10) === 3
=> true
irb(main):094:0> (1..10) === 30
=> false
irb(main):095:0> (1..10) === 2.71828
=> true
irb(main):096:0> ('a'..'f') == 'c'
=> false
irb(main):097:0> ('a'..'f') == 'g'
=> false
Range也可以位于case语句之内:
score = 98
case score
when 85..100 then puts "A"
when 70...85 then puts "B"
when 60...70 then puts "C"
else puts "D"
end
执行结果为:
A
§4.6 Symbol
Symbol是个简单的对象,它使用名字作为唯一的标识符。Symbol对象代表解释器内部一个唯一的名字。Symbol的产生很简单,只需要给一个字符序列前添加“:”或使用“to_sym”方法。 Symbol对象从属于Symbol类。
String和Symbol两者具有紧密的联系。每个symbol都有个字符串的名字(可以使用to_s方法得到)。而每个String可以请求它的相应symbol(通过to_sym方法)。String和Symbol是紧密相联的,但它们不是同一个东西,他们分别是String类和Symbol类的对象。
有读者可能会问,为什么要存在Symbol对象呢?因为symbol可以大大提高速度。Symbol的内部表示是一个整数,用来做Hash表中检索字符串的关键字,而Ruby语言执行时解析器、运算器需要大量的类名字、方法名字的检索,这可以大大加快解析和执行时字符串查找的速度。
想想,如果没有Symbol,如果需要用方法名称作为参数时,我们必须给一个字符串用来表示方法的名称,解释器处理时首先要作字符串解析,然后才能找到出相应的方法,而如果使用Symbol会大大加快这一速度。
在使用中,Symbol往往表示一个名字,例如一个变量 foo的值为1,那么 :foo可以理解为变量名,如果直接引用foo,会得到1,但如果是 :foo就指变量名本身。
Symbol对象是唯一的。每次你在代码中使用:test, 你是要引用一个名字为"test"的Symbol类的对象。Ruby保证系统中只有一个名字为test的Symbol对象, 所以所有对:test的引用都将引用同一个对象。
irb(main):001:0> module One
irb(main):002:1> class Test
irb(main):003:2> end
irb(main):004:1> $f1 = :Test
irb(main):005:1> end
=> :Test
irb(main):006:0> module Two
irb(main):007:1> Test = 1
irb(main):008:1> $f2 = :Test
irb(main):009:1> end
=> :Test
irb(main):010:0> def Test()
irb(main):011:1> end
=> nil
irb(main):012:0> $f3 = :Test
=> :Test
irb(main):013:0> $1.object_id
=> 4
irb(main):014:0> $2.object_id
=> 4
irb(main):015:0> $3.object_id
=> 4
§4.7 正则表达式
正则表达式的类是Regexp,可以使用/或%r生成正则表达式。
irb(main):103:0> a = /\s*[a-f]/
=> /\s*[a-f]/
irb(main):104:0> a.class
=> Regexp
irb(main):105:0> b = %r{\s*[a-f]}
=> /\s*[a-f]/
irb(main):106:0> b.class
=> Regexp
irb(main):107:0> c = Regexp.new('\s*[a-f]')
=> /\s*[a-f]/
irb(main):108:0> c.class
=> Regexp
你可以使用Regexp#match(string)方法或者=~运算符来匹配正则表达式,你也可以使用!~来测试是否不匹配。
irb(main):113:0> sentence = "This is a dog."
=> "This is a dog."
irb(main):114:0> sentence =~ /dog/
=> 10
irb(main):115:0> sentence =~ /a/
=> 8
irb(main):116:0> /o/ =~ sentence
=> 11
irb(main):117:0> sentence !~ /xyz/
=> true
另外,在匹配正则表达式时,会将匹配到的字符串存放在 $& 变量中,$' 变量中存放已经匹配过的字符序列,$` 变量中存放还未匹配的字符序列。
irb(main):118:0> sentence = "This is a dog."
=> "This is a dog."
irb(main):119:0> sentence =~ /a/
=> 8
irb(main):120:0> puts $&
a
=> nil
irb(main):121:0> puts $'
dog.
=> nil
irb(main):122:0> puts $`
This is
=> nil
第五章 代码块和迭代器
§5.1 代码块(Block)
§5.1.1 什么是代码块
在Ruby中在在大括号之间的代码或放在do/end之间的代码是一个代码块。代码块只能出现在一个方法的后边,它紧接在方法最后一个参数的同一行上。代码块的内容并不会被马上执行,当执行到被调用的方法时,解释器的运行时环境会记住代码块出现的现场,然后执行被调用的方法。
[1,2,3,4,5].each { |i|
puts i
}
[1,2,3,4,5].each do |i|
puts i
end
一般的使用习惯是:(to-do 具体解释)
当关心边际(side effect)效应时使用 do/end。
当关心返回结果应时使用大括号。
§5.1.2 代码块与对象
代码块并不是对象,但可以方便的转化为Proc类的对象。有三种转化的方法:
将一个代码块传递给最后一个参数以&开始的方法。
def meth1(p1, p2, &block)
puts block.inspect
puts block.call
end
meth1(1, 2) { "This is a block" }
使用Proc.new方法,后边的参数为一个代码块:
block = Proc.new { "a block" }
调用Kernel.lambda方法:
block = lambda { "a block" }
前两种方法是等价的,而第三种方法,用 lambda 生成的 Proc 对象和用 Proc.new 生成的 Proc 对象之间是有差别的。这是一个微妙的差别,这个差别与 return 关键字相关。lambda 中的 return 从 lambda 返回。而 Proc 中的 return 从外围方法返回。
可以看以下两个例子:
def test_proc
p = Proc.new { return 1 }
p.call
puts "Never come here"
end
test_proc #=> 1
执行后"Never come here"不会被输出,执行p.call相当于在test_proc方法内执行了return语句。
def test_lambda
p = lambda { return 1 }
result = p.call
puts "The value is: #{result}"
end
test_lambda
执行后的结果为:
The value is: 1
可见使用lambda生成的Proc对象执行call方法调用时,return表示从lambda包围得块内返回。
在一个代码块中执行next语句会导致代码块返回。返回值就是next语句后带的参数。如果next后没有参数,那么返回值为nil。
def meth2
result = yield
"The block result is #{result}"
end
puts meth2 { next 9 }
pr = Proc.new { next 100 }
puts pr.call
pr = lambda { next }
puts pr.call
执行结果为:
The block result is 9
100
nil
§5.2 迭代器(Iterator)
§5.2.1 什么是迭代器
简单的讲,一个迭代器就是一个能接受代码块的方法。当初为了进行迭代操作而设置了带块方法,所以现在它仍然常常被称作迭带器。
[1,2,3,4,5].each { |i|
puts i
}
上述代码中,each方法反复调用代码块,我们称each方法为一个迭代器。
迭代器(Iterator)即指调用带块方法。实际上,在早期版本的 Ruby 中,使用代码块的方法被称为迭代器,因为它们就是被设计来实现循环迭代的。但是在Ruby发展过程中,代码块的用途在后来已经得到了很大的增强,从最初的循环抽象到任何事情。可以将那些进行迭代操作的方法叫做迭代器,但如果将所有带块方法的调用过程都看作迭带器的话,并不合适而且概念上会引起混乱
§5.2.2 使用迭代器
#一个使用迭代器的简单例子,数组中每一个元素作为参数执行其后的代码块
['This', 'is', 'a', 'dog'].each do |entry|
print entry, ' '
end
执行结果为:
This is a dog
#另一个使用迭代器的例子,代码块可以访问其外的数据
factorial = 1
1.upto(10) do |i|
factorial*= i
end
puts factorial
执行结果为:
3628800
#代码块的返回值可以被调用者使用
b = [1, 2, 3, 4, 5].map do |entry|
entry * entry
end
print b.inspect
执行结果为:
[1, 4, 9, 16, 25]
#代码块也可以使用一个以上的参数
result = (0..100).inject(0) do |sum, i|
sum + i
end
print result
执行结果为:
5050
§5.2.3 yield
在方法中可以使用yield来执行代码块的内容,就好像传入的代码块是这个方法的一部分一样。每当碰到一个yield调用,代码块的内容就会被执行一次。当代码块执行结束后,程序会回到yield的那一行继续向下执行。
def twoTimes
yield
yield
end
twoTimes { puts "Hello World!" }
执行结果为:
Hello World!
Hello World!
你可以使用yield操作传参数给一个代码块,并且从代码块取回返回值。
def fibonacii(max)
f1, f2 = 1, 1
while f1 <= max
yield f1
f1, f2 = f2, f1+f2
end
end
fibonacii(1000) { |f| print f, " " }
执行结果为:
1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987
在这个例子中,yield接收一个参数,这个参数将会在执行的时候传递给指定的代码块。在代码块中,接收的参数使用两个竖线括起来放在代码块的头部。yield操作也可以有返回值,yield操作的返回值就是代码块中最后一个表达式的值。
§5.2.4 编写自己的迭代器
def factorial(count, &block)
value = 1
1.upto(count) do |i|
value = value * i
block.call(i, value)
end
end
factorial(5) do |i, sum|
puts "factorial(#{i}) = #{sum}"
end
执行结果为:
factorial(1) = 1
factorial(2) = 2
factorial(3) = 6
factorial(4) = 24
factorial(5) = 120
也可以将传入的代码块保存以供以后使用:
class Mathematics
def initialize(&block)
@block = block
end
def factorial(max)
value = 1
1.upto(max) do |i|
value = value * i
@block.call(value)
end
end
end
the_value = Mathematics.new do |count|
puts "Current value is #{count}"
end
the_value.factorial(5)
执行结果为:
Current value is 1
Current value is 2
Current value is 6
Current value is 24
Current value is 120
第六章 表达式
Ruby语言的一切都有返回值,这是Ruby语言和其他程序设计语言的一个显著不同。
irb(main):006:0> a = b = c = 0
=> 0
irb(main):007:0> print "\n"
=> nil
同样,if和case语句也有返回值,if和case语句的返回值就是if和case中最后一个执行语句的值。
irb(main):014:0> if( 1+1 == 2)
irb(main):015:1> "Like in school."
irb(main):016:1> else
irb(main):017:1* "What a surprise!"
irb(main):018:1> end
=> "Like in school."
§6.1 运算符
和其他程序设计语言一样,Ruby中含有丰富的运算符。但是在Ruby中,大多数运算符实际上是方法调用。例如 a+b,其实真实执行的是 a.+(b),调用a对象的+方法,b作为这个方法的参数。这样带来了相当的灵活性,你可以改变原有运算符的语义从而赋予它新的含义。
以下代码仅仅作为一个例子重写Fixnum类的 + 方法,赋予两个定长整数相加新的含义。
irb(main):001:0> class Fixnum
irb(main):002:1> alias the_plus +
irb(main):003:1* def +(integer)
irb(main):004:2> the_plus(integer) * 2
irb(main):005:2> end
irb(main):006:1> end
=> nil
irb(main):007:0> 1+1
=> 4
irb(main):032:0> 2+2
=> 8
irb(main):132:0> 2+5
=> 14
对于运算符(+ - * / % ** & | ^ << >> && ||),Ruby有相应形式的赋值运算符缩写形式+=, -=等。
运算符优先级:
::
[]
+(一元) -(一元) ! ~
* / %
+ -
<< >>
&
| ^
> >= < <=
<=> == === != =~ !~
&&
||
.. …
?:
= += -= *= /=(所有的赋值运算符缩写)
not
and or
以下运算符不能作为方法调用,也就是说不能改变以下运算符的含义:
…
!
not
&&
And
||
Or
::
=
+= -= *= /=(所有的赋值运算符缩写)
?:
§6.2 命令替换
在Shell中,可以使用反引号(`)执行命令替换。
`date` =〉Mon Nov 27 11:07:22 CST 2006
`pwd` =〉/usr/include
Ruby也有这个功能。在Ruby中,可以使用反引号或%x来执行命令替换。命令替换表达式的返回值就是命令执行的输出结果。命令执行的返回值存储在全局变量$?中。
irb(main):2134:0> %x{echo "Hello World!"}
=> "\"Hello World!\"\n"
反引号的默认行为是执行命令替换,同样,我们也可以重写它,赋予它新的含义。
alias old_backquote `
def `(cmd)
result = old_backquote(cmd)
if $? != 0
fail "Command #{cmd} failed: #$?"
else
puts "Command #{cmd} success."
end
result
end
print `date`
print `data`
执行结果为:
Command date success.
Mon Jan 15 21:48:16 CST 2007
Command uname success.
Linux
§6.3 赋值运算符
to-do 定义。
赋值运算的返回值就是左值的值,所以可以进行链式赋值。
irb(main):001:0> a = b = c = 5
=> 5
irb(main):002:0> a = ( b = 1 + 2 ) + 5
=> 8
Ruby的基本赋值有两种形式,一种左边是一个对象或变量,这时把右边的值或变量的引用赋予左边。这种赋值运算由语言本身提供。
irb(main):003:0> str = "This is a dog."
=> "This is a dog."
irb(main):004:0> num = 100
=> 100
另一种形式的赋值运算左边是一个类的实例的某一属性,这时候是执行这个类的方法,方法名称为“属性=”。方法的返回值就是右值的值,你可以重写这个方法从而赋予它新的含义。
irb(main):001:0> class Test
irb(main):002:1> def num=(num)
irb(main):003:2> @num = num
irb(main):004:2> end
irb(main):005:1> end
=> nil
irb(main):006:0> t = Test.new
=> #<Test:0x2e20568>
irb(main):007:0> t.num = 10
=> 10
§6.4 并行赋值
Ruby中另一个有趣的地方是支持并行赋值。例如,交换两个变量a,b的值可以写为:
a,b = b,a
Ruby会先从左到右依次计算 = 右边的表达式,然后再执行赋值的动作。
irb(main):008:0> x = 0
=> 0
irb(main):009:0> a,b,c = x, x+=1, x+=2
=> [0, 1, 3]
如果左边的变量比右边的多,那么多余的变量会被赋为nil.
irb(main):001:0> x, y, z = 1, 2
=> [1, 2]
irb(main):002:0> print z
nil=> nil
如果右边的变量或值比左边的多,那么多余的会被忽略。
irb(main):001:0> x, y = 1, 2, 3 # 3将被忽略
=> [1, 2, 3]
也可以在数组赋值时使用并行赋值。
irb(main):001:0> a = [1, 2, 3, 4, 5]
=> [1, 2, 3, 4, 5]
irb(main):002:0> x, y = a
=> [1, 2, 3, 4, 5]
irb(main):003:0> puts x, y
1
2
=> nil
在对数组进行并行赋值时可以使用*,*出现在左边最后一个变量时,表示将数组中所有剩余的值赋给这个变量。
irb(main):001:0> a = [1, 2, 3, 4, 5]
=> [1, 2, 3, 4, 5]
irb(main):002:0> x,*y = a
=> [1, 2, 3, 4, 5]
irb(main):003:0> puts x
1
=> nil
irb(main):004:0> puts y
2
3
4
5
=> nil
*出现在右边最后一个变量时,和左边类似。
a = [1, 2, 3, 4]
b, c = 9, a =〉b == 9, c == [1, 2, 3, 4]
b, c = 9, *a =〉b == 9, c == 1
b, *c = 9, a =〉b == 9, c == [[1, 2, 3, 4]]
b, *c = 9, *a =〉b == 9, c == [1, 2, 3, 4]
§6.5 嵌套赋值
在赋值中,左边的变量可以使用括号括起来。这样括号内的变量被视作位于一个层次。
b, (c, d), e = 1,2,3,4 =〉b == 1, c == 2, d == nil, e == 3
b, (c, d), e = [1,2,3,4] =〉b == 1, c == 2, d == nil, e == 3
b, (c, d), e = 1,[2,3],4 =〉b == 1, c == 2, d == 3, e == 4
b, (c, d), e = 1,[2,3,4],5 =〉b == 1, c == 2, d == 3, e == 5
b, (c,*d), e = 1,[2,3,4],5 =〉b == 1, c == 2, d == [3, 4], e == 5
§6.6 其他赋值
Ruby支持自加(+=)和自减运算符。和C/C++/Java一样,a = a + 2可以写成a+=2。其它类似的运算符还有%= ~= /= = += |= &= >>= <<= *= &&= ||= **=。
我们经常可以遇到类似这样的语句words[key] ||= [],他与words[key] = words[key] || []等价,意思是如果Hash表words[key]的值为空时,对words[key]赋值为一个新建的空数组,否则不变。
相应的,对于
num = 1 if num.nil?
num = 1 unless num
Ruby中习惯写为 num ||= 1,这样代码更简洁。
§6.7 条件运算
布尔运算符
在Ruby中定义nil和false为假,其他值为真。注意,和C/C++不同的是0并不被解释为假,空字符串也一样。
Ruby支持常见的布尔运算符,例如 and和 &&,而且还引入一个新的布尔运算符‘defined?’。
和其他程序设计语言一样,and 和 && 代表与关系。
or 和 || 代表或关系。
Not 和 ! 代表非关系。
如果参数没有定义 defined? 返回nil,否则返回一个描述串用来描述参数信息。
irb(main):013:0> defined? 1
=> "expression"
irb(main):014:0> defined? dummy
=> nil
irb(main):015:0> defined? printf
=> "method"
irb(main):016:0> defined? String
=> "constant"
irb(main):017:0> defined? $_
=> "global-variable"
irb(main):018:0> defined? Math::PI
=> "constant"
irb(main):019:0> defined? a = 0
=> "assignment"
irb(main):020:0> defined? 30.abs
=> "method"
条件运算符
Ruby支持一系列条件运算符,==, ===, <=>,=~,eql? 等等,equal?。除了<=> 其他的都是类方法。 == 和 =~ 有否定形式 != 和 !~。
If和unless
Ruby中的if和其他程序设计语言中的if大同小异:
if x == 5 then
print “The value of x is 5.”
elsif x == 0 then
print “The value of x is 0.”
else
print “The value of x is ”, x
end
也可以省略then:
if x == 5
print “The value of x is 5.”
elsif x == 0
print “The value of x is 0.”
else
print “The value of x is ”, x
end
如果为了代码紧凑而将代码放到同一行则不能省略then:
if x == 5 then print “The value of x is 5.”
elsif x == 0 then print “The value of x is 0.”
else print “The value of x is ”, x
end
也可以使用冒号分隔,这样代码更紧凑 :
if x == 5: print “The value of x is 5.”
elsif x == 0: print “The value of x is 0.”
else print “The value of x is ”, x
end
正如我们前面所说的,if是一个表达式,它有自己的返回值。你可以忽略这个返回值,但是他确实存在。If语句的返回值就是最后执行的语句的值。
x = 10
str = if x == 5: "x==5"
elsif x == 0: "x==0"
else "x==?"
end
执行后str的内容为:x== ?
Ruby也支持if的否定形式unless,unless的语法和if没有差别。
unless x != 5
print “The value of x is 5.”
else
print “The value of x is not 5.”
end
Ruby也支持C/C++的 ?:运算符。
str = x == 5? "x==5":"x==?"
Ruby也从Perl那里继承了一个很好的语法,你可以将条件写到表达式的后边。
puts "a = #{a}" if debug
print total unless total.zero?
§6.8 case表达式
Ruby中的case语句非常强大,首先我们来看一个基本用法:
grade = case
when point >= 85: 'A'
when point >= 70 && point < 80: 'B'
when point >= 60 && point < 70: 'C'
when point < 60: 'D'
else 'E'
end
这里case语句的作用和if表达式类似,case语句的返回值就是最后一个执行的表达式的值。和if语句类似,如果写在同一行的话需要加then或冒号。
另一种也是最常用的形式是在case后列出目标,然后每个语句依次和目标比较:
case input_line
when "debug"
print "We are in debug mode."
when /p\s+(\w+)/
dump_variable($1)
when "quit", "exit"
exit
else
print "Illegal command: #{input_line}"
end
另一个例子:
Season = case month
when 3..5 : "Spring"
when 6..8 : "Summer"
when 9..11: "Autumn"
when 12..2: "Winter"
else "Error"
end
Ruby提供了一个运算符===,只要一个类提供了===方法,那这个类的对象就可以出现在case语句中。例如对于正则表达式定义了===为模式匹配。
Ruby中,所有类的基类是Class类,所有类实例都是Class类的实例(to-do)。它定义===的含义为为参数所提供是否为实例的类或父类。
case shape
when Square, Rectangle
# ...
when Circle
# ...
when Triangle
# ...
else
# ...
end
§6.9 循环
§6.9.1 Loop
Loop循环始终执行其后的方法块,直到break退出。
x = 0
loop do
x += 1
if x <= 5: print x, " "
else break
end
end
执行结果为:1 2 3 4 5 。
§6.9.2 While
当条件为真时While循环继续,条件为假时退出循环。
x = 0
while x < 10
x += 1
end
§6.9.3 Until
Until和While厢房当条件为假时While循环继续,条件为真时退出循环。
x = 0
until x == 9
x += 1
end
§6.9.4 Iterator
和C/C++/Java不同,Ruby语言并不支持C/C++/Java中的For循环,但Ruby通过迭代器来提供更为强大的功能。先看一个例子:
4.times do
puts "Hello!"
end
执行结果为:
Hello!
Hello!
Hello!
Hello!
除了times方法之外,整数还提供upto和downto两个方法,看以下例子,
0.upto(9) do |i|
print i, " "
end
执行结果为0 1 2 3 4 5 6 7 8 9 。
也可以使用Step方法,step第二个参数表示步长:
0.step(10, 2) do |i|
print i, " "
end
执行结果为:0 2 4 6 8 10 。
许多容器类,例如数组,提供了each方法依次遍历容器中的数据:
[1, 2, 3, 4, 5].each { |i| print i, " "}
执行结果为:1 2 3 4 5 。
如果一个类支持each方法,那么就可以使用Enumerable模块中的一些方法。
["apple", "orange", "banana", "watermelon"].grep(/an/) do |fruit|
puts fruit
end
执行结果为:
orange
banana
§6.9.5 For..In
如果一个类提供了each方法,那么相应的,这个类的对象可以使用For..in循环。例如Array类和Range类都有each方法:
for fruit in ["apple", "orange", "banana", "watermelon"]
print fruit, " "
end
执行结果为:apple orange banana watermelon 。
for i in 1..9
print i, " "
end
执行结果为:1 2 3 4 5 6 7 8 9 。
§6.9.6 Break,Redo,Next
Break,Redo和Next用来改变循环的流程。
§6.9.6.1 break
Break用来退出当前循环:
times = 0
loop do
times += 1
print "hello #{times}\n"
break if times > 2
end
执行结果为:
hello 1
hello 2
hello 3
与C/C++不同,如果循环有多重的话,break将退出最内层的循环。
outer = 0
loop do
outer += 1
inner = 0
loop do
inner += 1
print "Inner #{inner}\n"
break if inner > 1
end
print "Outer #{outer}\n"
break if outer > 1
end
执行结果为:
Inner 1
Inner 2
Outer 1
Inner 1
Inner 2
Outer 2
另一个与C/C++语言不同的地方是break只能从循环中退出,而不能从case中退出。
§6.9.6.2 redo
redo语句重新执行当前这一次循环。
count = 0
for i in 1..3
print "hello #{i}\n"
break if count == 1
if i > 1
count += 1
redo
end
end
执行结果为:
hello 1
hello 2
hello 2
上面的例子中,使用redo后,循环变量i的值还是2,可见redo语句重新执行了这次循环。
和break语句类似,redo语句只对最内层的循环起作用。
3.times do
count = 0
for i in 1..3
print "hello #{i}\n"
break if count == 1
if i > 1
count += 1
redo
end
end
end
执行结果为:
hello 1
hello 2
hello 2
hello 1
hello 2
hello 2
hello 1
hello 2
hello 2
§6.9.6.3 next
Next类似C/C++中的continue语句,跳转到当前循环的头部,执行下一次循环。
loop do
times += 1
next if times == 2
print "hello #{times}\n"
break if times > 3
end
执行结果为:
hello 1
hello 3
hello 4
与break,redo类似,如果循环有多重,那么next只对最内侧的循环起作用。
outer = 0
loop do
outer += 1
inner = 0
loop do
inner += 1
next if inner == 1
print "Inner #{inner}\n"
break if inner > 1
end
print "Outer #{outer}\n"
break if outer > 1
end
执行结果为:
Inner 2
Outer 1
Inner 2
Outer 2
§6.9.7 Retry
上一节我们看到,可以使用redo重新执行当前这一次的循环,有时候,我们也需要重新执行整个循环而不是仅仅执行当前这次,这时候我们可以用时retry。在迭代、块或for语句中使用retry,意味着重启迭代器。同时迭代器的参数也将被重新计算。
一个示例如下,
for i in 1..5
retry if some_condition # 从 i == 1 开始重新执行
end
看一个完整可执行的例子:
count = 0
for i in 1..3
pr
- ruby.pdf (1.3 MB)
- 下载次数: 31
相关推荐
这个"Ruby基础语法视频教程1"涵盖了几个关键的概念,包括变量、变量规则以及表达式。以下是对这些主题的详细解释: 1. 变量(Variables): 在Ruby中,变量用于存储数据,并在程序的不同位置使用。Ruby有五种不同...
本教程将深入探讨Ruby的基础语法,并介绍如何下载、安装Ruby,以及使用Ruby教程中文版进行学习。 首先,让我们从Ruby的基础语法开始。Ruby支持多种数据类型,包括整数(Integer)、浮点数(Float)、字符串(String...
### Ruby语法基础教程知识点概览 #### 一、Ruby语言概述 **1.1 Ruby的历史** - **创始人:** Ruby语言是由日本人松本行弘(Matz)在1995年开始开发的一种脚本语言。 - **发展背景:** 在Perl和Python流行的时代背景...
Ruby基础语法经典学习PPT教程. 网上关于ruby的PPT教程不太多,这个个人觉得还可以。
### Ruby语言教程对Ruby语法的总结 #### 一、变量与数据类型 **特点:** - **动态类型:** Ruby是一种动态类型的语言,这意味着你无需为变量声明具体的数据类型。 - **变量命名规则:** 变量名必须以小写字母或...
第三章是对Ruby语法结构的全面概览,涵盖了注释、分隔符、关键字、运算符等基础元素,以及变量作用域、类库、数据类型等核心概念。其中,条件判断语句和循环语句的讲解特别重要,它们是构建逻辑流程的基础。此外,...
在Ruby语言中文教程中,你将学到以下关键知识点: 1. **基础语法**:Ruby的语法简洁明了,易于理解。包括变量(局部变量、实例变量、类变量和全局变量)、常量、数据类型(如字符串、数字、布尔值、数组、哈希)...
对于初学者来说,这将是理解Ruby语法和编程理念的重要资料。可能还会介绍一些常用的Ruby工具和开发环境,比如RubyMine IDE或VSCode的Ruby插件,以及Git版本控制和Rake任务自动化工具。 此外,通过阅读源码部分,...
Ruby是一种面向对象的、动态类型的编程语言,以其简洁、优雅的语法和强大的元编程能力而闻名。本教程针对初学者,旨在帮助读者快速掌握Ruby的基础知识,并通过实例深入理解其用法。 首先,Ruby的基本语法是它的一大...
《Ruby基础教程(第5版)》是一本由日本...总的来说,《Ruby基础教程(第5版)》是一本全面、易懂且充满乐趣的Ruby学习资料,无论你是编程新手还是希望深入理解Ruby的开发者,都可以通过这本书开启或深化你的Ruby之旅。
这是一个短小的Ruby入门,完全读完只需20分钟。这里假设读者已经安装了Ruby,如果你没有安装的话,请在阅读文前访问Ruby官方网站进行下载并安装。
通过本教程的学习,你已经掌握了Ruby的基础语法和一些重要的概念。Ruby作为一种简洁且功能强大的编程语言,在Web开发、脚本编写等多个领域都有着广泛的应用。继续深入学习Ruby,将有助于你更好地理解和掌握其高级...
Ruby是一种面向对象的脚本...通过学习这个教程,你将能够理解Ruby的基本概念,编写简单的程序,并逐渐掌握更高级的编程技巧。在实践过程中,不断加深对Ruby的理解,你将发现它不仅是一种工具,更是一种享受编程的方式。
Ruby是一种面向对象的、动态...通过这个中文教程,你将掌握其基本语法,理解面向对象编程的概念,熟悉常用的类库和工具,并能够运用Ruby进行实际的项目开发。无论你是初学者还是有经验的开发者,都能从这个教程中受益。
排班精美,图文清晰。是市面上最适合初学者入门的 Ruby 教程,由 ruby 创始人亲自编写。...本书适合Ruby初学者学习参考, 有一定Ruby编程基础的读者若想再回顾一下Ruby的各知识点, 本书也能提供不少帮助。