Ruby 入门教程


Ruby 语言 Grant Ren hyqryq@gmail.com 2006 年 11 月 24 日 前 言 2 目 录 第一部分 Ruby 语言基础 ....................................................... 8 第一章 Ruby 语言概述 .......................................................... 8 §1.1 Ruby 的历史 ....................................................................... 8 §1.2 Ruby 名字的由来 ................................................................... 8 §1.3 Ruby 的特点 ....................................................................... 8 §1.4 Ruby 和 Python 的比较 ............................................................... 9 第二章 Ruby 编程环境 .......................................................... 9 §2.1 Ruby 的安装 ...................................................................... 9 §2.1.1 在 Windows 95/98/Me/XP 上安装 Ruby ........................................... 9 §2.1.2 在 Linux 上安装 Ruby ........................................................ 10 §2.2 运行 Ruby ....................................................................... 10 §2.2.1 使用 Ruby ................................................................. 10 §2.2.2 使用 FreeRIDE 和 SciTE ...................................................... 12 §2.2.3 使用 fxri ................................................................. 13 §2.3 Ruby-irb ........................................................................ 14 §2.4 Ruby-ri ......................................................................... 15 §2.5 RubyGems ........................................................................ 16 类与对象 ..................................................................... 17 §3.1 类的定义 ......................................................................... 17 §3.2 对象,属性和方法 ................................................................. 18 §3.3 继承 ............................................................................. 21 §3.4 特殊方法与特殊类 ................................................................. 23 §3.5 类变量与类方法 ................................................................... 24 §3.4 存取控制 ......................................................................... 25 §3.6 元类 ............................................................................. 27 §3.7 Ruby 的动态性 .................................................................... 28 §3.8 变量 ............................................................................. 29 §3.8.1 局部变量 ................................................................... 29 §3.8.2 实例变量 ................................................................... 30 §3.8.3 类变量 ..................................................................... 30 §3.8.4 全局变量 ................................................................... 31 §3.8.5 常量 ....................................................................... 31 §3.8 与定义有关的操作 ................................................................. 32 §3.8.1 alias ...................................................................... 32 §3.8.2 undef ...................................................................... 33 §3.8.3 defined? ................................................................... 34 第四章 基本类型 .............................................................. 37 §4.1 Array ............................................................................ 37 §4.2 Hash ............................................................................. 38 §4.3 Number ........................................................................... 39 §4.4 String ........................................................................... 40 §4.5 Range ............................................................................ 41 §4.6 Symbol ........................................................................... 42 §4.7 正则表达式 ....................................................................... 43 3 第五章 代码块和迭代器 ........................................................ 44 §5.1 代码块 (Block) .................................................................... 44 §5.1.1 什么是代码块 ................................................................ 44 §5.1.2 代码块与对象 ................................................................ 44 §5.2 迭代器 (Iterator) ................................................................. 46 §5.2.1 什么是迭代器 ................................................................ 46 §5.2.2 使用迭代器 .................................................................. 46 §5.2.3 yield ....................................................................... 47 §5.2.4 编写自己的迭代器 ............................................................ 48 第六章 表达式 ................................................................ 49 §6.1 运算符 ........................................................................... 49 §6.2 命令替换 ......................................................................... 51 §6.3 赋值运算符 ....................................................................... 51 §6.4 并行赋值 ......................................................................... 53 §6.5 嵌套赋值 ......................................................................... 54 §6.6 其他赋值 ......................................................................... 54 §6.7 条件运算 ......................................................................... 54 §6.8 case 表达式 ...................................................................... 56 §6.9 循环 ............................................................................. 57 §6.9.1 Loop ........................................................................ 57 §6.9.2 While ....................................................................... 58 §6.9.3 Until ....................................................................... 58 §6.9.4 Iterator .................................................................... 58 §6.9.5 For..In ..................................................................... 59 §6.9.6 Break , Redo , Next ........................................................... 59 §6.9.7 Retry ....................................................................... 62 第七章 方法 .................................................................. 63 §7.1 运算符重定义 .................................................................. 65 §7.2 变长参数 ...................................................................... 65 §7.3 块调用 ........................................................................ 66 §7.4 方法返回值 .................................................................... 67 第八章 模块 .................................................................. 69 §8.1 名字空间 ......................................................................... 69 §8.2 mixin ............................................................................ 70 §8.3 使用 mixin ....................................................................... 71 §8.3.1 Comparable ................................................................. 71 §8.3.2 Enumerable ................................................................. 72 §8.3.3 Singleton .................................................................. 73 §8.4 Require, load 和 include .......................................................... 74 异常 75 §9.1 异常处理 .......................................................................... 75 §9.2 定义异常类 ........................................................................ 80 §9.3 catch 和 throw ..................................................................... 80 多任务处理 ................................................................... 81 §10.1 多线程处理 ...................................................................... 82 §10.1.1 线程创建 .................................................................. 82 §10.1.2 线程操作 .................................................................. 83 4 §10.1.3 线程和异常 ................................................................ 84 §10.1.4 线程调度 .................................................................. 86 §10.1.5 线程同步 .................................................................. 87 §10.2 多进程处理 ...................................................................... 93 §10.2.1 进程创建 .................................................................. 94 基本 I/O 操作 ................................................................. 95 §11.1 使用 Kernel 模块处理 I/O 操作 ...................................................... 95 §11.2 文件处理 ......................................................................... 95 §11.3 StringIO ......................................................................... 96 §11.4 Socket ........................................................................... 97 第十二章 反射和对象空间 ...................................................... 98 §12.1 ObjectSpace 模块 ................................................................ 98 §12.2 察看类和对象的状态 .............................................................. 99 §12.3 动态方法调用 ................................................................... 102 §12.3.1 使用 send 方法 ............................................................ 102 §12.3.2 使用 Method 类和 UnboundMethod 类 .......................................... 103 §12.3.3 使用 eval 方法 ............................................................ 104 §12.3.4 性能 ..................................................................... 105 §12.4 Hook 和回调方法 ................................................................ 106 §12.4.1 什么是 Hook .............................................................. 106 §12.4.2 Ruby 中的 Hook ............................................................ 106 §11.4.2 回调方法 ................................................................. 107 §12.5 跟踪程序的运行 ................................................................. 108 §12.5.1 set_trace_func ........................................................... 108 §12.5.2 trace_var ................................................................ 109 §12.5.3 caller ................................................................... 109 §12.5.3 __FILE__,__LINE__ 和 SCRIPT_LINES__ ........................................ 109 第十三章 序列化和 YAML ...................................................... 110 §13.1 序列化的概念 ................................................................... 110 §13.2 使用序列化 ..................................................................... 110 §13.2.1 二进制数据保存 ........................................................... 110 §13.2.2 YAML 数据保存 ............................................................ 111 §13.3 定制序列化 ..................................................................... 112 §13.3.1 二进制数据保存 ........................................................... 112 §13.3.2 YAML 数据保存 ............................................................ 113 §13.3 YAML ........................................................................... 114 §13.3.1 集合类型 ................................................................. 115 §13.3.2 单行集合类型 ............................................................. 118 §13.3.3 基本类型 ................................................................. 118 §13.3.4 块 ....................................................................... 119 §13.3.5 别名和锚( Aliases and Anchors ) .......................................... 119 §13.3.6 文档 ..................................................................... 119 §13.3.7 Ruby 中 YAML 的使用 ........................................................ 119 第十四章 安全控制 ........................................................... 120 §14.1 0 级 ........................................................................... 121 §14.1 1 级 ........................................................................... 121 §14.2 2 级 ........................................................................... 121 5 §14.3 3 级 ........................................................................... 121 §14.4 4 级 ........................................................................... 121 第十五章 单元测试 ........................................................... 122 §15.1 什么是单元测试 ................................................................. 122 §15.2 Ruby 单元测试框架 .............................................................. 122 第二部分 内置类与模块 ...................................................... 122 第一章 内置类 ............................................................... 122 §1.1 Array ............................................................................ 122 §1.2 Bignum ........................................................................... 123 §1.3 Binding .......................................................................... 123 §1.4 Class ............................................................................ 123 §1.5 Continuation ..................................................................... 123 §1.6 Dir .............................................................................. 125 §1.7 Exception ........................................................................ 125 §1.8 FalseClass ....................................................................... 125 §1.9 File ............................................................................. 125 §1.10 File::Stat ...................................................................... 125 §1.11 Fixnum .......................................................................... 125 §1.12 Float ........................................................................... 125 §1.13 Hash ............................................................................ 125 §1.14 Integer ......................................................................... 125 §1.15 IO .............................................................................. 125 §1.16 MatchData ....................................................................... 125 §1.17 Method .......................................................................... 125 §1.18 Module .......................................................................... 125 §1.19 NilClass ........................................................................ 126 §1.20 Numeric ......................................................................... 126 §1.21 Object .......................................................................... 126 §1.22 Proc ............................................................................ 126 §1.23 Process::Status ................................................................. 126 §1.24 Range ........................................................................... 126 §1.25 Regexp .......................................................................... 126 §1.26 String .......................................................................... 126 §1.27 Struct .......................................................................... 126 §1.28 Struct::Tms ..................................................................... 126 §1.29 Symbol .......................................................................... 126 §1.30 Thread .......................................................................... 126 §1.31 ThreadGroup ..................................................................... 126 §1.32 Time ............................................................................ 127 §1.33 TrueClass ....................................................................... 127 §1.34 UnboundMethod ................................................................... 127 第二章 内置模块 ............................................................. 128 §2.1 Comparable ....................................................................... 128 §2.2 Enumerable ....................................................................... 128 §2.3 Error ............................................................................ 128 §2.4 FileTest ......................................................................... 128 §2.5 GC ............................................................................... 128 6 §2.6 Kernel ........................................................................... 128 §2.7 Marshal .......................................................................... 128 §2.8 Math ............................................................................. 128 §2.9 ObjectSpace ...................................................................... 128 §2.10 Process ......................................................................... 128 §2.11 Process :: GID .................................................................. 128 §2.12 Process :: Sys .................................................................. 128 §2.13 Process :: UID .................................................................. 129 §2.14 Signal .......................................................................... 129 第三部分 Ruby 语言总结 ..................................................... 130 附录 134 §1 术语对照 .......................................................................... 134 7 第一部分 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 的功能。 8 若您曾经“想要一种简单的面向对象的语言”,或者认为“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 等。 9 §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 可执行文件应该包含在系统搜索路径内。 输入下面的程序: 10 print "Hello World!" 然后按 Ctrl+D 再按回车键,你就会看到 Ruby 执行程序的输出结果: 你也可以先将代码保存为文件,然后使用再 Ruby 解释器执行: 11 §2.2.2 使用 FreeRIDE 和 SciTE FreeRIDE 是一个支持 Ruby 语言的免费 IDE 环境。FreeRIDE 本身就是使用 Ruby 语言开发,它也是 Rubyforge 上的重要项目之一。 可以使用 FreeRIDE 来编写调试和执行 Ruby 代码,FreeRIDE 内置了交互式变成环境和 Ruby 语言在线帮助, 功能十分强大。 12 Scintilla 是一个免费的源代码编辑控件,它完全开放源代码,并允许用户自由地用于开源软件或是商 业软件中。SciTE 是用这个控件开发了一个编辑软件,在“One-Click Ruby Installer”中,SciTE 集成了 Ruby 语言支持,使用起来非常方便。相比 FreeRIDE,它的特点就是使用简单。 13 §2.2.3 使用 fxri Fxri 是一个 Ruby 交互帮助和控制台工具。它不仅可作为语言的在线帮助,而且可以用作交互式 Ruby 解释器 来执行程序。对于学习 Ruby 语言,fxri 是一个非常方便的帮手。 不知你有没有听说过 Fox ToolKit,它是相当轻巧的开放源代码的图形库。FXRuby 是 RubyForge 上的一个项 目,提供了 Ruby 语言使用 Fox ToolKit 的接口。而 Fxri 正是基于 FXRuby 开发,Fxri 同样是 RubyForge 上的项目。 这样你应该可以猜到 Fxri 名字的由来 14 Fxri 同时集成了 Ruby-irb 和 Ruby-ri 的功能,有了它,你可以抛开 Ruby-irb,Ruby-ri 了,但如果你 用的不是 Windows 系统的话,算我没说 §2.3 Ruby-irb Ruby-irb 是交互式 Ruby(Interactive Ruby)的简称,用来从标准输入读入并执行 Ruby 代码的工具, 像一个 shell。 使用命令“irb”进入交互式模式,然后可以象输入命令行命令一样输入 Ruby 代码,代码执行的结果会 立刻显示: 15 §2.4 Ruby-ri 和 Perl 一样,Ruby 也设计了嵌入式文档。 ruby-ri 就是查看文档的工具。Ruby-ri 的执行命令为 “ri”,例如你可以通过“ri String.new”来查询 String 类的 new 方法: 16 §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 的版本号 17 类与对象 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 若某个类已经被定义过,此时又用相同的类名进行类定义的话,就意味着对原有的类的定义进行追加。 18 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 19 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 20 也可以用成员变量写控制符 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 21 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 的机制可以用来实现多继承。 22 可以使用 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 23 §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 24 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 25 @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 26 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 ... 27 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 # ... 28 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 29 可以使用 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 # (NoMethodError) §3.8 变量 变量名长度只受内存大小的限制。可以通过区分 Ruby 变量名的首字符来区分它是局部变量、实例变量、类 变量、全局变量还是常量。通常情况下,变量名的第二位字符以后是数字、字母或下划线,但有的内部变量名 比较特殊,如“$?”。 §3.8.1 局部变量 局部变量以小写字母或下划线开始。 num = 1 foo 局部变量的作用域起始于声明处,结束于该声明所在的块、方法定义、类/模块定义的结尾。 2.times { p defined?(num) num = 10 p num } 30 输出为: 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 31 @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 32 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 #设定别名 alias :orig_meth :meth 33 #重定义 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) 34 end obj1 = Test1.new obj1.meth 执行结果为: Tes1.rb:22: undefined method `meth' for # (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,若已经定义,则返回一个字符串 描述该表达式的种类。 35 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 36  判断 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 37 第四章 基本类型 §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] 38 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" 39 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 类的对象。 40 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" 你也可以使用“Here Document”的方法来生成字符串,这种方法规定 << 之后的字符串作为结束标志。 string = < (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" 42 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 43 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 $' 44 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 方法,后边的参数为一个代码块: 45 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 46 执行结果为: 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 #代码块的返回值可以被调用者使用 47 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, " " } 48 执行结果为: 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| 49 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) 50 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 中,可以使用反引号(`)执行命令替换。 51 `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 52 另一种形式的赋值运算左边是一个类的实例的某一属性,这时候是执行这个类的方法,方法名称为 “属性=”。方法的返回值就是右值的值,你可以重写这个方法从而赋予它新的含义。 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 => # irb(main):007:0> t.num = 10 => 10 53 §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 54 => 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" 55 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.” 56 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+)/ 57 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 58 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 。 59 许多容器类,例如数组,提供了 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 60 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 61 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 只对最内侧的循环起作用。 62 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 print "hello #{i}\n" break if count == 1 if i > 1 count += 1 redo end 63 end 执行结果为: hello 1 hello 2 hello 1 hello 2 hello 1 第七章 方法 Ruby 中的方法使用关键字 def 来定义。方法名应该以小写字母开始,如果你使用大写字母开始,Ruby 解 释器会认为它是一个常量,这样可能会带来名称解析错误。 在定义方法时可以使用圆括号也可以不用。 def method1 puts "Hello World!" end def method2 arg1, arg2 puts "The arguments is: #{arg1}, #{arg2}" end 一般的习惯是如果方法含有参数,那么就使用圆括号将参数括起来,否则的不要需要圆括号。 def method2(arg1, arg2) puts "The arguments is: #{arg1}, #{arg2}" end 定义方法时可给方法默认参数,注意默认参数必须位于方法的尾部。 def method3(arg1=5, arg2=9) puts "The arguments is: #{arg1}, #{arg2}" end def method3(arg1=5, arg2) #错误 方法的返回值为方法最后一个表达式的值,或者由 return 语句的返回的值。 和 C/C++不同,Ruby 中的方法总是从属于某一个对象。Ruby 中没有全局函数。虽然 Ruby 中可以象全局函 数一样定义和使用方法,但是你应当明白,Ruby 中的方法总是会从属于某一个对象。 看到这里,细心的读者会提出一个问题,如果在顶层定义一个方法,那么这个方法属于谁? def meth end 在本书中,我们多次说,Ruby 是一种面向对象的语言,在 Ruby 中的一切都是对象。那么 meth 方法从属 于什么对象呢?我们看一个例子: 64 public #为什么使用 public 看后边的解释 def meth puts self.class end puts self.class self.meth 执行结果为: Object Object 在顶层,当我们定义方法时,将自动将我们定义的方法作为 Object 类的私有实例方法。所以这些方法可 以在任何地方调用。所以我们可以在任何地方使用 Object 类的任何方法和 Object 类所包含的模块中的任何 方法,例如 Kernel 模块中的方法在任何地方可以随意使用。 上面的例子中,meth 将作为 Object 类的私有方法,所以我们使用 public 改变它的存取属性,否则 self.meth 将会产生无法访问私有对象的错误。 在 Ruby 语言中,方法是存放到 Hash 表中,而键值就是方法名。定义一个方法就是在 Hash 表中添加一项 的过程,所以,后定义的同名方法就会替换掉之前定义的方法。 def meth puts "first" end meth def meth puts "second" end meth 执行结果为: first second Ruby 语言支持缺省参数,但不支持方法重载。方法重载会加重解释器语法解析的复杂度,影响执行速度。 C++的选择是二者都支持,而 Java 的选择刚好与 Ruby 相反,即支持方法重载,而不支持缺省参数。 方法名可以以问号“?”,叹号“!”,等于号“=”结尾。这样的方法有约定的含义。以问号结尾的方法 返回布尔值,以叹号结尾的方法表示会改变调用者的内部数据,以等于号结尾的方法表示可以作为左值。问 号“?”,叹号“!”,等于号“=”是方法名的一部分。 (to-do 例子) 当一个方法被调用时,运行时环境按照如下顺序搜索: 1、 在当前对象的特殊方法中搜索 65 2、 在当前对象类中定义的实例方法中搜索 3、 在当前对象所包含的模块中搜索 4、 在当前对象所在类的父类中搜索 5、 在当前对象所在类的父类所包含的模块中搜索 6、 继续 4 和 5 的过程,直到顶层 Object 类 §7.1 运算符重定义 Ruby 支持重定义运算符,下表列出了 Ruby 支持重定义的运算符和他们默认的含义: 运算符 默认含义 [] []= 元素索引和赋值 ** 幂运算 ! ~ 否定,求补 + - 正,负,注意他们的方法名为+@,-@以便和 加减运算区分 * / % 乘法,除法,取余运算 + - 加减运算 >> << 左移,右移 & | ^ 按位与,按位或,按位异或 <= < > >= 比较运算 === Case 表达式中的相等测试 <=> 范围测试 == != 正则表达式匹配相等和不等测试 =~ !~ 相等和不等测试 注意,!=和!~ 作为==和=~的否定形式,并没有相应的方法。他们经过语法分析后将被转化为肯定形式 调用。例如: a != b 实际是调用 !(a==b),同样,a !~ b 将被转化为 !(a=~b)。 §7.2 变长参数 Ruby 语言支持定义方法时,方法的参数数目不确定。只要给参数前加星号就表示参数数目不定。所有的 不定数目的参数被作为一个数组。 def varadd(*num) sum = 0 num.each { |i| sum+=i } puts sum end varadd() varadd(1,2,3) varadd(1,2,3,4,5,6) 执行结果为: 0 6 66 21 你也可以反过来使用星号,在调用方法时,如果一个类型为数组的参数前有星号作为前缀,那么这个数组 将被展开。 def meth1(arg1, arg2, arg3, arg4, arg5) print "Parameters:#{arg1},#{arg2},#{arg3},#{arg4},#{arg5} \n" end meth1(1, 2, 3, 4, 5) meth1(1, 2, 3, *['4', '5']) meth1(*(10..14).to_a) 执行结果为: Parameters:1,2,3,4,5 Parameters:1,2,3,4,5 Parameters:10,11,12,13,14 §7.3 块调用 当调用一个方法时,可以在方法后连接一个块。在方法内可以使用 yield 执行连接的块。 def test_block1(arg1) print arg1,"\n" yield end test_block1("Test") { print "Hello World!" } 执行结果为: Test Hello World! Kernel 模块有一个 block_given?的方法,可以判断方法后是否有块连接。 def test_block2(arg1) if block_given? print yield(arg1), "\n" else print arg1, "\n" end end test_block2("no block") test_block2("no block") {|s| s.sub(/no /, '') } 执行结果为: no block 67 block 如果方法的最后一个参数以&开始,表示方法后连接的块会被转化为 Proc 对象,然后传递给这个参数。 def meth1(count, &block) value = 1 1.upto(count) do | i | value = value * i block.call(i, value) end end meth1(4) do | i, f_i | puts "meth1(#{i}) = #{f_i}" end 执行结果为: meth1(1) = 1 meth1(2) = 2 meth1(3) = 6 meth1(4) = 24 §7.4 方法返回值 每一个方法都有返回值,当然,你不一定要使用这个返回值。方法的返回值就是在调用方法时方法内最 后一个表达式执行的结果。你也可以使用 return 语句,这样方法的返回值就是 return 语句的参数。Ruby 语言习惯省略 return 语句,能少写的尽量少写,这也是 Ruby 的哲学。 def meth1() "meth1" end def meth2(arg) if arg == 0 "Zero" else if arg > 0 "Positive" else "Negative" end end 使用 return 语句时返回值可以是多个,这时候返回值会被转化为一个数组,你可以用多重赋值的形式 来使用这个返回值。 def meth1 return "meth1", 6 end a, b = meth1 68 69 第八章 模块 模块提供了一种组织常量,类和方法的手段。你可以使用模块来提供一个名字空间以避免名字冲突,你也可 以使用模块来提供 mixin 的功能。 §8.1 名字空间 当程序代码越来越多,工程越来越大,开发者不可避免的会将一些常用的代码以库或别的形式重用。一 般情况下,我们可以用类来组织代码,但有时候使用类组织代码并不是十分合适。这样在一个大工程中,就 有可能发生名字冲突。 例如,开发者 A 在文件 a.rb 中写了如下代码,用来输出自己代码文件的版本信息, def print_version # … end 同一个项目中的另一个开发者 B 在文件 b.rb 中用同样的方法来实现同样的功能, def print_version # … end 第三个开发者 C 需要使用 a.rb 和 b.rb 中的一些方法,这样,当他使用 print_version 方法时,就产生 了名字冲突,到底他调用的是哪一个文件中的 print_version 方法呢? 我们可以使用模块机制来解决这样的名字冲突。定义一个模块相当于定义了一个名字空间,名字空间内 的元素在全局空间并不直接可见。 开发者 A 定义模块 A_FILE, module A_FILE def print_version # … end end 开发者 B 定义模块 A_FILE, module B_FILE def print_version # … end end 这样对于开发者 C,可以这样使用 print_version require ‘A’ require ‘B’ A. print_version 70 B. print_version 类和模块的区别是,模块不能生成实例,而类不能被 include。 §8.2 mixin Mix-in 的意思是混合插入、糅合,就像在冰淇淋中混合多种配料可以做成美味的混合冰淇淋一样,在类 中混合插入各种模块就可以添加相应的功能。模块还有另一个重要的作用,可以使用模块来实现多继承,可 以在类中包含模块,这样模块中的所有方法和类中其他方法一样可以使用。 Matz 坚信滥用多重继承会导致继承关系的混乱,因此 Ruby 中不允许使用多重继承。同时为充分发挥继 承功能的优势,Ruby 支持两种继承关系:1.使用 is-a 语句的继承;2.使用 Mix-in 来共享并继承模块中的 功能。 module Debug Define print_info print "Class: #{self.class.name} Object ID: #{self.id}" end end class A include Debug #... end class B include Debug #... end obj1 = A.new obj2 = B.new obj1.print_info obj2.print_info 通过这样的手段我们可以实现多继承的功能,这样的模块我们称为 mixin。 在 Ruby 中,Object,Class 和 Module 是三个特殊的类。 71 Class 是一个 Module,而 Module 是一个 Object,所以说 Class 是一个 Object,因此,所有的数据都是 Object。 §8.3 使用 mixin §8.3.1 Comparable Comparable mixin 提供了比较的功能。要使用 Comparable mixin 必须提供<=>方法,<=>的返回值为- 1,0,+1 用来表示元素之间的小于,等于,大于的关系。 class Person include Comparable attr :age def <=>(aPerson) @age <=> aPerson.age end def initialize(name, gender, age) @name = name @gender = gender @age = age end end aPerson = Person.new("Tom", "male", 18) bPerson = Person.new("Mike", "male", 10) cPerson = Person.new("Henry", "male", 40) puts aPerson > bPerson puts aPerson < bPerson puts aPerson >= bPerson puts aPerson <= bPerson Object Module Class 72 puts aPerson == bPerson puts aPerson.between?(bPerson, cPerson) 执行结果为: true false true false false true §8.3.2 Enumerable Enumerable mixin 提供了遍历,查找和排序的功能。 要使用 Enumerable mixin 必须提供 each 方法, 标准做法是在 each 方法内对每一个元素使用 yield 操作。如果使用了 Enumerable mixin 中的 max,min,或 sort,那么还必须提供<=>方法,用来实现元素之间的比较关系。 以下是一个使用 Enumerable mixin 的例子,IntegerFinder 是一个查找字符串中整数的类。 class IntegerFinder include Enumerable def initialize(aString) @string = aString end def each @string.scan(/[1-9]\d*/) { |integer| yield integer } end end aDigitFinder = IntegerFinder.new("This is 123, 234, 98 and 10") aDigitFinder.collect {|i| print i, " "} aArray = aDigitFinder.find_all {|i| i.to_i > 50 } puts "\n", aArray.to_s 执行结果为: 123 234 98 10 12323498 Enumerable mixin 中含有许多与集合遍历查找相关的方法,许多标准类也使用了 Enumerable mixin,借助 Enumerable mixin 中的方法可以方便的实现一些强大的功能,请看以下一些例子: #察看数组中的所有单词的长度是否大于 4 %w{ ant bear cat}.all? {|word| word.length >= 4} #=> false 73 #返回 range 中所有不符合条件的元素 (1..10).reject {|i| i % 3 == 0 } #=> [1, 2, 4, 5, 7, 8, 10] #求 5 到 10 的和 #inject 方法第一次会把 Range 中的前两个元素作为参数传递给 sum 和 n, #以后每次会把 sum 设置为块计算后的返回值。 (5..10).inject {|sum, n| sum + n } #=> 45 # 找出数组中最长的单词 longest = %w{ cat sheep bear }.inject do |memo,word| memo.length > word.length ? memo : word end longest #=> "sheep" §8.3.3 Singleton 在设计模式中,Singleton 技术通常称为单件,是一种常见的设计技术,它保证在系统的某个类在任一 时刻最多只有一个实例在运行。你可以参见设计模式这本书获得有关单件技术更详细的信息。在 Ruby 中,使 用 Singleton Mix-in,你可以很容易的实现单件类。 在单件类中不能使用 new 方法,因为在单件类中 new 方法的属性是私有的。需要使用 instance 方法得到 类的实例对象。 require 'singleton' class SingletonClassTest attr_accessor :data include Singleton end # a = SingletonClassTest.new #错误,new 方法为私有方法 a = SingletonClassTest.instance b = SingletonClassTest.instance puts a.inspect puts b.inspect a.data = 8 puts b.data 执行结果为: # # 8 74 可以看到,a 和 b 其实指向同一个对象。 §8.4 Require, load 和 include 在 Ruby 中,可以使用 load 和 require 来包含另一个文件。每次运行到 load 时,load 后的文件会被载入并执 行。 4.times do |i| File.open("temp.rb","w") do |f| f.puts "def test" f.puts "#{i}" f.puts "end" end load "temp.rb" puts test end 执行结果为: 0 1 2 3 在上面的小程序里 load "temp.rb"执行了 4 次,每一次 temp.rb 文件都不同,所以 test 方法执行后的 结果也不同。 使用 Load 方法的这种特性可以实现一些强大的功能,例如:  可以用来处理配置文件,在程序运行期间配置文件可以被动态改变。  可以用来实现程序的无缝升级,在升级时你不需要重启程序,只要将所需要的代码重新 load。 Require 和 load 不同,它只加载文件一次,即在第一次执行到 require 时载入,以后碰到 require 同一 文件时自动忽略。已经被加载的文件保存在$”中。另外,require 还可以用来加载二进制格式的库。Require 可以使用绝对路径或相对路径,如果使用了相对路径,那么系统会在$:变量包含的目录中搜寻。Require 和 load 的另一个不同是当包含文件是 Ruby 代码时,require 可以不加后缀名。Require 将当前所有加载的文件 保存在$"变量中。 注意,在当前版本中,$”是一个数组,保存着使用 require 已经加载过的文件。但如果 require 使用不 同的路径去包含同一个文件,这个文件就有可能被加载多次。 File.open("temp.rb","w") do |f| f.puts "def test" f.puts "1" f.puts "end" end require "temp" puts test File.open("temp.rb","w") do |f| f.puts "def test" f.puts "2" 75 f.puts "end" end require "./temp" puts test 执行结果为: 1 2 这样就违背了 require 只加载一次的初衷,一些人认为这是一个 bug,这个问题在 Ruby 的后续版本中 可能被修复。所以,不要使用不同的路径去加载同一个文件。 require, load,include 都是 Kernel 模块中的方法,他们的区别如下:  require,load 用于包含文件,include 则用于包含的模块。  require 加载一次,load 可加载多次。  require 加载 Ruby 代码文件时可以不加后缀名,load 加载代码文件时必须加后缀名。  require 一般情况下用于加载库文件,而 load 用于加载配置文件。 异常 异常(Exception)顾名思义,指的是在程序处理过程中遇到不能处理的非预期条件,它会打乱正 常的执行流程。 许多种类的错误会触发异常,典型的异常例如整数除零,Ruby 提供了一套处理异常的机制。如果错 误发生,方法将创建一个异常对象并把它抛出到运行时系统。创建一个异常对象并将其交给运行时系统 叫做抛出异常。异常对象包含了异常的信息,包括异常类型,异常发生时的堆栈状态等。运行时系统负 责找到一些代码处理这个错误。 当某个方法抛出一个异常后,运行时系统需要找到一些代码来处理这个异常。运行时系统从发生异 常的代码处开始,依次查找调用堆栈,直到找到相应的代码处理这个异常,然后继续执行其下的代码。 注意处理异常过后程序不会返回到异常抛出处继续往下执行。 和传统错误管理技术相比,使用异常来管理错误具有如下优点:  将正常处理逻辑与错误处理逻辑分开  错误沿着调用堆栈向上传递,异常处理的代码简洁容易理解  错误具有相应得类型,可以使用类型来区分不同的错误 §9.1 异常处理 Ruby 处理异常的语法如下: begin # 正常处理流程代码 rescue …… # 处理异常 ensure 76 # 这里的代码总会被执行 End 我们看一个例子,以下代码产生一个除零错误: begin numerator = 10 denominator = 0 value = numerator / denominator rescue ZeroDivisionError => ex puts "Error, divide zero" ensure puts "Finish the process!" end 执行结果: Error, divide zero Finish the process! 在异常产生后,如果相应的 rescue 语句被匹配到,那么这个异常对象会被复制到 ex 中。而 ensure 后的 代码永远都会被执行到。 在 Ruby 中,异常类的基类是 Exception,也就是说所有异常类都是 Exception 类直接或间接的子类 。 Ruby 预定义了一个层次结构用来处理常见的异常。 (to-do Ruby 异常层次图) 在捕捉异常时,可以使用多个 rescue 语句: begin # 正常处理流程代码 rescue ExceptionClass1 => exception1 # 处理当发生 ExceptionClass1 类型的异常 rescue ExceptionClass2 => exception2 # 处理当发生 ExceptionClas21 类型的异常 rescue # 处理其他异常 ensure # 这里的代码总会被执行 end 注意,rescue 不加参数默认是匹配 StandardError 类型的异常。 Ruby 使用最先匹配的规则,如果一个异常和 rescue 语句匹配成功,那么接下来的 rescue 语句都不会 被匹配。在搜寻匹配的时候可以做从子类到父类的转换。 begin number1 = number2 rescue SyntaxError, NameError => ex 77 print "The code doesn't compile: " + ex.class.to_s rescue StandardError => ex print "Error running script: " + ex.class.to_s end 执行结果为: The code doesn't compile: NameError 上面的例子中使用到 number2,但是 number2 并没有被定义,所以产生了一个 NameError。第一个 rescue 语句被匹配到,于是放弃继续搜寻,直接执行相应的 rescue 语句后的代码。 begin number1 = number2 rescue StandardError => ex print "Error running script: " + ex.class.to_s rescue SyntaxError, NameError => ex print "The code doesn't compile: " + ex.class.to_s end 执行结果为: Error running script: NameError 这个例子和第一个例子区别就是 rescue 语句的顺序,从例子中可以看到,由于 StandardError 是 NameError 和 SyntaxError 的 父 类 , 而 按 顺 序 StandardError 会 先 被 匹 配 到 , 所 以 会 放 弃 继 续 搜 寻 , StandardError 下 面 的 代 码 会 被 执 行 。 上 面 的 例 子 中 , print "The code doesn't compile: " + ex.class.to_s 永远不会被执行到。 当抛出一个异常时,可以使用 Ruby 内置的异常类,也可以自定义自己的异常类。如果使用自定义的异常 类,那么这个异常类应该是 StandardError 类直接或间接的子类。如果不这么做,那么默认情况下,这个异 常不能正确地被捕捉。 在 rescue 处理逻辑之后可以加一个 else 子句,只有没有任何异常发生的时候程序才会运行到那里,这 种用法比较少见。 begin # 正常处理流程代码 rescue ExceptionClass1 => exception1 # 处理当发生 ExceptionClass1 类型的异常 rescue ExceptionClass2 => exception2 # 处理当发生 ExceptionClas21 类型的异常 rescue # 处理其他任何异常 else # 没有任何异常 ensure # 这里的代码总会被执行 end 可以使用 raise 显式抛出一个异常。raise 后可以跟异常说明信息,这种情况下抛出的异常为 RuntimeError 类型: 78 # 抛出一个 RuntimeError 类型的异常,异常说明信息"FTP Server down" raise "FTP Server down" 可以使用 raise 抛出特定类型的异常: # 抛出一个 FTPServerError 类型的异常,异常说明信息" Server not responding” raise FTPServerError, "Server not responding" 还可以附加第三个参数表示调用栈的信息: # 抛出一个 FTPServerError 类型的异常,异常说明信息" Server not responding” #第三个参数为 Kernel#caller 方法的处理结果,用来指定调用栈的层次 raise FTPServerError, "Server not responding", caller Kernel#caller 方法可以返回一个数组包含了当前调用栈的信息,方法可以携带参数,表示从调用栈内 部开始,跳过堆栈的层次,默认值为 1。 def meth1(skip) meth2(skip) end def meth2(skip) meth3(skip) end def meth3(skip) caller(skip) end puts meth1(0) puts meth1(1) puts meth1(2) puts meth1(3) 可以看到,puts meth1(0)的结果为: test.rb:10:in `meth3' test.rb:6:in `meth2' test.rb:2:in `meth1' puts meth1(1)的结果为: test.rb:6:in `meth2' test.rb:2:in `meth1' puts meth1(2)的结果为: test.rb:2:in `meth1' 79 puts meth1(3)的结果为空。 通过 caller 方法,当抛出异常时,我们可以指定调用栈的层次: def meth1 meth2 end def meth2 meth3 end def meth3 begin raise RuntimeError, "Test Error", caller[1..2] rescue RuntimeError => ex puts ex.backtrace end end meth1 执行结果为: test.rb:2:in `meth1' 上面的例子中,通过调用 caller 方法得到当前调用栈,然后通过 caller[1..2]指定异常发生时传递的 调用堆栈信息,最后使用异常对象的 backtrace 方法来得到调用堆栈。 使用 raise 不加参数表示重新抛出当前异常,如果当前没有异常,那么将抛出一个 RuntimeError 类型 的异常: begin numerator = 10 denominator = 0 value = numerator / denominator rescue ZeroDivisionError => ex puts "Error, divide zero" raise #重新抛出当前异常 ensure puts "Rethow the error!" end 执行结果: Error, divide zero Finish the process! 如果没有任何相应的 rescue 语句处理异常,那么这个异常会交给运行时环境。运行时环境会输入异常并 中止程序的执行。 80 另外,全局变量“$!”保存了最近抛出的异常对象,而“$@”保存了最近发生的异常发生异常的调用 栈信息。 除了循环以外,还可以在 rescue 部分中使用 retry。这时将从 begin 表达式开始重新执行。使用 retry 可 以在某处理过程成功之前,一直循环该处理过程。 begin do_something # exception raised rescue # handles error retry # restart from beginning end 若在 rescue 部分、迭代器块或 for 语句之外使用 retry 的话会引发 LocalJumpError 异常。 §9.2 定义异常类 Exception 类提供了堆栈和异常的描述信息,在自定义的异常类中,你可以添加额外的信息。自定义的 异常类应该是 StandardError 类直接或间接的子类。否则这个异常可能不会正确地被捕捉。 看下面的例子,以下代码位于 MyExceptionTest.rb 中。 class MyException < RuntimeError end begin raise MyException, "MyException Testing..." rescue MyException => ex puts ex.to_s puts ex.backtrace.join("\n") ensure puts "Finish the process!" end 执行结果为: MyException Testing... MyException.rb:5 Finish the process! §9.3 catch 和 throw 使用 raise,rescue 用来处理出错的情况,同时也可以使处理流程从很深的嵌套结构中跳出。catch 和 throw 也能使处理流程从很深的嵌套结构中跳出。 81 Catch 定义了一个代码块,并给这个代码块一个名字作为标注。当 Ruby 遇到一个 throw 时会展开调用堆 栈来查找相应的 catch 块,当找到时,Ruby 在相应得 catch 处展开堆栈并中止块。 def method1(n) puts n throw :done if n <= 0 method1(n-1) end catch(:done) { method1(3) puts "Can not reach here!" } puts "Reach here!" 执行结果: 3 2 1 0 Reach here! 上面的例子中,我们首先用“catch(:done)”标注了一个块,当执行到“throw :done”时,中止当前 “catch(:done)”所标注的块,处理流程继续向下处理。 在 catch/throw 中,当碰到 throw 时,Ruby 会展开调用堆栈来查找匹配的 catch,当找到后,堆栈会被 展开,catch 块剩余的代码不会被执行。 Ruby 中没有 goto 语句,但你可以使用 catch/throw 或异常来实现 goto 的功能。 多任务处理 当你要同时处理多件任务时 Ruby 提供了两种基本方式,一种方式是在一个程序协调需要同时执行 的任务,使用多线程处理需要同时执行的操作。另一种方式是将需要同时执行的操作分散到多个程序中 使用多进程。 按照教科书上的定义,进程是资源管理的最小单位,线程是程序执行的最小单位。 一般来说,我们把正在计算机中执行的程序叫做“进程”(Process),而所谓“线程”(Thread), 是“进程”中某个单一顺序的控制流。 线程之间独立运行,每个线程有它自己的堆栈、自己的程序计数器和自己的局部变量。但是与进程 相比,线程之间的隔离程度要小。它们彼此共用变量,也有可能会同时操作一片内存。 一个进程至少需要一个线程作为它的指令执行体,进程管理着资源(比如 CPU、内存、文件等等), 而将线程分配到某个 CPU 上执行。一个进程可以拥有多个线程,此时,如果进程运行在多处理器的机器 上,它就可以同时使用多个 CPU 来执行各个线程,达到最大程度的并行,以提高效率;同时,即使是在 单 CPU 的机器上,采用多线程模型来设计程序,可以使设计更简洁、功能更完备,程序的执行效率也更 高。例如采用多个线程响应多个输入,而此时多线程模型所实现的功能实际上也可以用多进程模型来实 现,而与后者相比,线程的上下文切换开销就要小多了。 在操作系统设计上,从进程演化出线程,最主要的目的就是更好的支持多处理器以及减小上下文 切换开销。 82 §10.1 多线程处理 Ruby 的多线程是用户级多线程,这样使得 Ruby 的多线程移植非常容易,你并不需关心具体的操作系统; 这样做也使线程容易控制,程序不容易产生死锁这类严重的线程问题。 但是同时,由于 Ruby 的多线程并不是真正意义上的操作系统级多线程,不管代码使用了多少个 Thread 类的 实例,都只会在启动解释器这一个进程内执行,由 Ruby 解释器进行具体的线程切换管理,其效率要低于由 操作系统管理线程的效率,且不能使用多个 CPU。 在 Ruby 中同时做多件事最简单的方式就是使用 Thread 类,Thread 类提供了一种高效和轻量级的手段 来同时处理多件任务。 Thread 类由 Ruby 解释器具体实现,提供了一种同时处理多个任务的方法, Thread 类实现的并不是操 作系统级多线程。 Ruby 多线程的优点和缺点同样明显,缺点是效率不如操作系统级多线程,不能使用多个 CPU,但其优点 也很明显,即可移植性很高。这就需要设计人员综合考虑。 §10.1.1 线程创建 我们可以使用 Thread.new 方法去创建一个线程,可以随后代码块中列出线程执行的代码: x = Thread.new { sleep 0.1; print "x"; print "y"; print "z" } a = Thread.new { print "a"; print "b"; sleep 0.2; print "c" } 执行结果为: ab 上面的示例程序中使用 Thread.new 创建了二个线程,线程随即开始运行。但是运行结果很奇怪,为什么 程序运行结果是“ab”呢?我们预期的执行结果应该是 “abxyzc”。 当 Ruby 程序执行完毕的时候,他会杀掉所有的线程,不管其它的线程的运行状态如何。如果没有使用 join 方法,那么主程序执行完毕后会把所有没有执行完毕的线程杀掉。 上面的实例程序中,当主程序运行完毕时,两个线程都没有运行结束就被中止掉了。我们可以使用 join 方法来让主程序等待某个特定线程结束,对每一个线程使用 join 方法,可以确保在程序结束前所有的线程 可以运行完毕。 x = Thread.new { sleep 0.1; print "x"; print "y"; print "z" } a = Thread.new { print "a"; print "b"; sleep 0.2; print "c" } x.join a.join 我们使用 Thread.new 方法创建两个新的线程并开始运行, 然后使用 join 方法等待线程结束。执行结果 为: abxyzc 可以看到通过使用 join 方法等待线程结束,程序运行结果和我们预期结果相符。 另一个例子: x = Thread.new { sleep 0.1; print "x"; print "y"; print "z" } a = Thread.new { sleep 0.1; print "a"; print "b"; sleep 0.2; print "c" } 83 执行没有任何输出,因为主程序执行完毕杀死两个线程的时候这两个线程没有运行到输出语句。 也可以给 join 方法添加时间用来指明最大等待时间。如果超时 join 返回 nil。 x = Thread.new { sleep 0.1; print "x"; print "y"; print "z" } a = Thread.new { sleep 0.1; print "a"; print "b"; sleep 10; print "c" } x.join(5) a.join(5) 执行结果为: abxyz 上面的例子中对于每一个线程的最大等待时间是 5 秒,由于 a 线程需要执行 10 秒以上,所以 a 线程没 有运行完毕程序就将返回。 §10.1.2 线程操作 可以使用 Thread.current 方法来访问当前线程,也可以使用 Thread.list 方法列出所有线程;可以使 用 Thread#status 和 Thread#alive?方法得到线程的状态;也可以使用 Thread#priority 方法得到线程的优 先级和使用 Thread#priority=方法来调整线程的优先级。 test_thread = Thread.new {} test_thread.join Thread.current.alive? => true Test_thread.alive? => false 上面的例子中,使用 alive?方法得线程的状态,由于主线程并没有执行完毕所以状态为 true,由于 Test_thread 已经执行完毕所以返回 false。 Thread.new { sleep(200) } Thread.new { 1000000.times {|i| i*i } } Thread.new { Thread.stop } Thread.list.each {|t| p t} 执行结果为: # # # # 上面的例子中创建了三个线程,然后列出所有的线程,可以看到,列出的线程数目是四个而不是三个 , 因为除了创建的三个线程之外还有主线程。 number1 = 0 number2 = 0 thr1 = Thread.new { loop { number1 += 1 } } print "th1:", thr1.priority, "\n" 84 thr1.priority = -1 thr2 = Thread.new { loop { number2 += 1 } } print "th2:", thr2.priority, "\n" thr2.priority = -2 sleep 1 Thread.critical = 1 puts number1 puts number2 输出结果为: th1:0 th2:0 174100 98 上面的例子中使用 priority 方法得到线程的优先级,使用 priority=方法设置线程的优先级。县城的 优先级默认为 0。可以看到同样的代码,优先级较高的执行的要快。 我们可以使用 critical 方法和 critical=方法得到和设置全局线程临界区(Thread.critical)的值, 如果这个值为 true,那么所有存在的线程将停止运行。 一个 Thread 可以访问自己作用域内的所有数据,但如果有需要在某个线程内访问其他线程的数据应该 怎么做呢?Thread 类提供了线程数据互相访问的方法,你可以简单的把一个线程作为一个 Hash 表,可以在 任何线程内使用[]=写入数据,使用[]读出数据。 athr = Thread.new { Thread.current["name"] = "Thread A"; Thread.stop } bthr = Thread.new { Thread.current["name"] = "Thread B"; Thread.stop } cthr = Thread.new { Thread.current["name"] = "Thread C"; Thread.stop } Thread.list.each {|x| puts "#{x.inspect}: #{x["name"]}" } 可以看到,把线程作为一个 Hash 表,使用[]和[]=方法,我们实现了线程之间的数据共享。 §10.1.3 线程和异常 当一个线程运行时如果发生异常会怎样?这和 abort_on_exception 标志和解释器的 debug 标志有关, abort_on_exception 默认被设置为 false,这时候发生异常的那个线程会被杀死,其他线程继续运行。只有 当你对发生异常的线程使用 join 操作时你才会察觉到有异常发生。 a = Thread.new { raise "Error A" } b = Thread.new { puts "Thead B" } c = Thread.new { puts "Thead C" } b.join c.join 85 执行结果为: Thead B Thead C a = Thread.new { raise "Error A" } b = Thread.new { puts "Thead B" } c = Thread.new { puts "Thead C" } a.join b.join c.join 执行结果为: Thead B Thead C test.rb:1: Error A (RuntimeError) from test.rb:4:in `join' from test.rb:4 我们也可以在使用 join 时捕获这个异常: threads = [] threads[0] = Thread.new { raise "Error A" } threads[1] = Thread.new { puts "Thead B" } threads[2] = Thread.new { puts "Thead C" } threads.each do |t| begin t.join rescue RuntimeError => e puts "#{e.message}" end end 执行结果为: Thead B Thead C Error A 然而设置了 abort_on_exception 标志或者使用-d 打开了 debug 开关,如果抛出了未被捕获的异常,程 序将结束,所有的线程都将被终止。 Thread.abort_on_exception = true threads = [] threads[0] = Thread.new { raise "Error A" } threads[1] = Thread.new { puts "Thead B" } threads[2] = Thread.new { puts "Thead C" } threads.each do |t| begin t.join 86 rescue RuntimeError => e puts "#{e.message}" end end 执行结果为: test.rb:3: Error A (RuntimeError) from test.rb:3:in `initialize' from test.rb:3:in `new' from test.rb:3 §10.1.4 线程调度 Thread 类提供了一系列的方法用来控制线程调度,Thread#run 方法用来运行一个线程;Thread#stop 方法停止线程的运行。 Thread#pass 方法取消运行当前线程,继续其他线程的运行;Thread#join 方法将当前线程挂起直到指 定线程运行结束;Thread#value 方法和 Thread#join 类似,将线程挂起直到指定线程运行结束并取得返回 值。 a = Thread.new { print "a"; Thread.pass; print "b"; Thread.pass; print "c" } b = Thread.new { print "x"; Thread.pass; print "y"; Thread.pass; print "z" } a.join b.join 执行结果为: axbycz 上面的例子中,我们使用 pass 方法放弃当前的控制权,继续下一个线程的运行。 a = Thread.new { print "a"; Thread.stop; print "c" } Thread.pass print "b" 87 a.run a.join 执行结果为: abc 上面的例子中,线程 a 开始运行后先输出“a”,然后使用 stop 方法停止运行;主线程使用 pass 方法 放弃放弃当前的控制权,保证“a”最先输出。然后我们使用 run 方法重新运行线程 a,所以输出结果为 “abc”。 a = Thread.new { 4 + 5 } puts a.value 执行结果为: 9 上面的例子中,我们是用 value 方法等待线程结束并打印出线程执行的返回值,线程执行的返回值就是 线程执行的最后一个语句的值。 §10.1.5 线程同步 因为线程共享内存空间,所以可以使用普通的变量完成线程间的数据交换工作。如果有多个线程同时工 作,请确保它们之间不存在互相等待以到达某一点或完成的情况。如果操作错误,可能会导致死锁状态,两 个线程都无法完成,因为它们都在相互等待。 使用线程的另一个常见问题是竞争状态。如果一个线程正在将数据写入文件,而另一个线程正在从该文 件中读取数据,如果读取线程快于写入线程,则将返回无法预料的结果。这种情况称为竞争状态。 有时候在运行程序中,一些你认为不需要同步的地方也会出现资源同步的问题: class Counter attr_reader :number def initialize @number = 0 end def plus @number += 1 end end c = Counter.new t1 = Thread.new { 10000.times { c.plus } } t2 = Thread.new { 10000.times { c.plus } } t1.join t2.join puts c.number 88 执行结果为: 14228 这里或许大家会奇怪,为什么 number 的值不是 20000 呢? 原因在于这一句: @number += 1 这一行在解释器内部会分解为多个操作, 得到 number 的值 将 number 的值加一 保存 number 的值 在两个线程运行过程中,因为时机问题,在一个线程读取 number 的数值后还没来得及进行赋值的时候, 另一个线程有可能已经改变了 number 的数值。这样多个进程同时访问共享数据造成了数据混乱从而与我们预 想的结果不符。 §10.1.5.2 Monitor Monitor 是一种资源互斥访问的解决方案。它提供了一种机制,以供不同线程互斥访问指定的共享资源。 我们可以通过使用继承 Monitor 类解决上一节的问题。 require 'monitor' class Counter < Monitor attr_reader :number def initialize @number = 0 super #初始化父类数据 end def plus synchronize do @number += 1 end end end c = Counter.new t1 = Thread.new { 10000.times { c.plus } } t2 = Thread.new { 10000.times { c.plus } } t1.join t2.join puts c.number 89 执行结果为: 20000 可以看到,继承 Monitor 类后,我们可以使用 synchronize 方法,对于一个 Monitor 类的实例,同时只 能有一个线程执行 synchronize 代码块的操作。代码的执行结果为“20000”,和我们期望的一致。 如果不想使 Monitor 成为自己类的父类,也可以使用 MonitorMixin 模块。 require 'monitor' class Counter include MonitorMixin attr_reader :number def initialize @number = 0 super end def plus synchronize do @number += 1 end end end c = Counter.new t1 = Thread.new { 10000.times { c.plus } } t2 = Thread.new { 10000.times { c.plus } } t1.join t2.join puts c.number 执行结果为: 20000 也可以单独使用 Monitor 类的实例对象来完成同步操作。 require 'monitor' class Counter attr_reader :number 90 def initialize @number = 0 super end def plus @number += 1 end end c = Counter.new lock = Monitor.new t1 = Thread.new { 10000.times { lock.synchronize{ c.plus } } } t2 = Thread.new { 10000.times { lock.synchronize{ c.plus } } } t1.join t2.join puts c.number 执行结果为: 20000 也可以将使用 extend 方法将 Monitor 模块的方法引入完成同步操作。 require 'monitor' class Counter attr_reader :number def initialize @number = 0 super end def plus @number += 1 end end c = Counter.new c.extend(MonitorMixin) t1 = Thread.new { 10000.times { c.synchronize{ c.plus } } } t2 = Thread.new { 10000.times { c.synchronize{ c.plus } } } 91 t1.join t2.join puts c.number §10.1.5.2 Mutex Mutex 是 mutual-exclusion lock(互斥锁)的简称。它实现了一种简单的信号量机制,用来协调并发线 程对共享数据的访问。 在多线程并行访问共享数据时,可以使用下列代码(m 是 Mutex 的实例)。 begin m.lock # 访问受 m 保护的共享数据 ensure m.unlock end Mutex 类有个 synchronize 方法可以简化这一过程。 m.synchronize { # 访问受 m 保护的共享数据 } 若对 Mutex 加锁时发现已经处于锁定状态时,线程会挂起直到解锁为止。 我们修改一下上一节的例子, require 'thread' class Counter attr_reader :number def initialize @number = 0 end def plus @number += 1 end end c = Counter.new m = Mutex.new t1 = Thread.new { m.synchronize { 10000.times { c.plus } } 92 } t2 = Thread.new { m.synchronize { 10000.times { c.plus } } } t1.join t2.join puts c.number 输出结果为: 20000 从上一节我们可以知道,若此程序中不使用 Mutex 加以保护的话,因为时机问题,在一个线程读取 number 的数值后还没来得及进行赋值的时候,另一个线程可能已经改变了 number 的数值。这样,通过 Mutex 互斥锁,我们实现了对共享数据的正确使用。 Mutex 主要方法如下: new 生成新的互斥锁 lock 若已经处于加锁状态则会一直等待下去直到解锁 unlock 执行解锁操作,若有其它等锁的线程则会将他们唤醒 synchronize 执行从获得锁到解锁全过程的迭代器 try_lock 尝试执行加锁操作,若已处于加锁状态,则返回 false 且不会挂起 locked? 测试 Mutex 对象是否处于加锁状态 §10.1.5.3 Queue Queue 就像一条读写数据的管道。提供数据的线程在一边写入数据,而读取数据的线程则在另一边读出 数据。若 Queue 中没有可供读取的数据时,读取数据的线程会挂起等待数据的到来。 下面是一个使用 Queue 的简单例子: require "thread" q = Queue.new th = Thread.start { while line = q.pop print line end } while gets q.push $_ end q.push nil # 终止标记 th.join 93 本程序中,一个线程读入一行之后,另一个线程就输出它。若把第 3 行改成数组,即“q = []”后,线 程间失去同步,则程序无法正确运作。 Queue 有下列方法: new 生成新的 Queue empty 测试 Queue 是否为空,若为空则返回真 push 将数据压入 Queue pop 尝试从 Queue 中取数据,如果 Queue 为空当参数为真则引发一个异常,否 则挂起线程 enq 含义与 push 相同 deq 含义与 pop 相同 clear 清空 Queue length 返回 Queue 中的元素个数 size 含义与 length 相同 下面的例子使用 Queue 来解决生产者-消费者问题。生产者-消费者问题是一个经典的进程同步问题,该 问题最早由 Dijkstra 提出,用以演示他提出的信号量机制。生产者线程生产物品,然后将物品放置在一个空 缓冲区中供消费者线程消费。消费者线程从缓冲区中拿走产品。生产者线程在缓冲区满时必须等待,直到缓 冲区有空间才继续生产。消费者线程在缓冲区空时必须等待,直到缓冲区中有产品才能继续读取。在这个问 题上需要考虑缓冲区满和缓冲区空的情况以及竞争条件。 require 'thread' queue = Queue.new consumers = Thread.new do 5.times do |i| obj = queue.deq print "consumer: #{i}\n" sleep(rand(0.05)) end end producers = Thread.new do 5.times do |i| sleep(0.1) print "producer: #{i}\n" queue.enq("Item #{i}") end end producers.join consumers.join §10.2 多进程处理 Thread 类实现的并不是真正意义上的操作系统级多线程,但是有时候,你需要将一个任务分解为几个 进程去执行,或者可能你需要运行的进程并不是 Ruby 语言编写,对于这种情况,Ruby 语言也有办法处理, 94 Ruby 有几种方法创建真正的操作系统级的进程。 §10.2.1 进程创建 §10.2.1.1 system 方法和反引号 可以使用反引号或 system 去执行一个命令。 system(“tar czf samples.tar.gz .”) result = `date` 使用 Kernel.system 方法去执行一个外部命令时,这个外部命令将在一个子进程中被执行。如果命 令无法执行返回 false,否则返回 true。命令执行的返回值保存在全局变量$?中。 使用 system 方法执行外部命令,外部命令的结果输出将和程序的结果输出在一起。使用反引号则不同, 执行的返回值为就是外部命令执行的输出结果。 result = `date` result #=> Thu Jan 4 22:45:49 CST 2007 §10.2.1.2 popen 方法 很多时候,我们需要将输入数据发送给外部命令,然后取回执行结果。可以使用 IO.popen 方法完成这样 的功能。 IO.popen ("date") { |f| puts f.gets }  执行结果为: Thu Jan 4 22:55:19 CST 2007 另一个例子: command1 = IO.popen("dir", "w+") command1.puts "." command1.close_write puts command1.gets §10.2.1.3 fork 方法 也可以使用 fork 方法产生一个新的子进程。产生子进程后原有进程继续向下执行。 fork do 3.times {|i| puts "Child: #{i}" } end 3.times {|i| puts "Parent: #{i}" } 执行结果为: Child: 0 Parent: 0 Child: 1 Parent: 1 Child: 2 Parent: 2 fork 方法调用时返回值如果是 nil 表示子进程 ID,否则表示的是父进程的 ID。可以使用 Process.wait 方法来等待子进程执行结束,Process.wait 方法返回所在进程的 ID。进程的退出状态保存在$?中。 fork{ sleep(3) 95 } Process.wait 注意,如果要使用 fork 方法,那么在 Ruby 解释器所在的操作系统必须支持 fork 系统调用。例如在 Window 下 fork 方法就无法使用。 基本 I/O 操作 I/O 是 Input/Output 的缩写, 类 IO 是所有 IO 处理的基类,定义了 I/O 操作的接口。I/O 流可以是单向 也可以是双向。 IO 类实用 Unix 文件描述符的概念,使用整数代表打开的文件。0 代表标准输入,1 代表标准输出,2 代 表标准错误。 IO 端口可以以多种模式打开,以下表中列出了所有模式: 模式 含义 r 只读,从文件头开始(默认模式) r+ 读写,从文件头开始 w 只写,文件不存在则创建文件,文件存在则清空内容 w+ 读写,文件不存在则创建文件,文件存在则清空内容 a 只写,文件不存在则创建文件,文件存在则在尾部添加 a+ 读写,文件不存在则创建文件,文件存在则在尾部添加 b (只用于 DOS/Windows)二进制文件,可以和上述模式一同出现 §11.1 使用 Kernel 模块处理 I/O 操作 在 Ruby 中你可以使用多种方法操作 I/O。在 Kernel 模块中已经实现了一系列 I/O 相关的方法 gets, open, print, printf, putc, puts, readline, readlines, 和 test 方法,使用他们可以方便快捷的写出程 序。 §11.2 文件处理 我们使用 File 类来操作文件,可以通过 File.new 来创建一个 File 类的实例并打开这个文件。 file = File.new("testfile", "r") # ... process the file file.close testfile 是想要操作的文件名,”r”说明了文件的操作模式为读取。可以使用”w”表示写入,”rw” 表示读写。 最后记得关闭打开的文件,确保所有被缓冲的数据被写入文件,所有相关的资源被释放。 我们也可以使用 File.open 来打开文件,open 和 new 的不同是 open 可以使用其后的代码块而 new 方法 则返回一个 File 类的实例。 File.open("testfile", "r") do |file| # ... process the file end 96 open 操作的另一个优点是处理了异常,如果处理一个文件发生错误抛出了 异常的话,那么 open 操作会自动关闭这个文件,下面是 open 操作的大致实现: class File def File.open(*args) result = f = File.new(*args) if block_given? begin result = yield f ensure f.close end end return result end end 对于文件的路径,Ruby 会在不同的操作系统间作转换。例如,在 Windows 下,/ruby/sample/test.rb 会 被转化为\ruby\sample\test.rb。当你使用字符串表示一个 Windows 下的文件时,请记住使用反斜线先转义: “c:\\ruby\\sample\\test.rb” 你也可以使用 File::SEPARATOR 表示不同系操作统的路径分割符。 §11.3 StringIO StringIO 是与 IO 相同接口的字符串类。StringIO 操作像其它的 I/O 对象,但 StringIO 是对字符串进行 读和写。如果打开一个 StringIO 对象用于读,需要提供给它源字符串,然后在 StringIO 对象上进行所有读 操作并返回操作结果。同样,当对一个 StringIO 对象执行写操作时,需要传递给它一个要被填充的目的字符 串。 #创建一个 StringIO 对象 io = StringIO.new("abc") io.getc #从 io 中读取一个字符,返回 ASCII 码 p io.getc =>98 #当前的位置 p io.pos => 2 p io.size => 3 io << "bar" p io.size => 5 io.rewind p io.gets => "abbar" require 'stringio' ip = StringIO.new("This\nis\na\ndog!") 97 op = StringIO.new("", "w") ip.each_line do |line| op.print "...", line end print op.string 输出结果为: ...This ...is ...a ...dog! §11.4 Socket 使用 Ruby 处理网络十分灵活,Ruby 提供了一系列的库来支持 TCP,UDP,HTTP,SOCKS,Unix 套接字和 各种各样的网络协议。所有的 Socket 的都是 IO 直接或间接的子类。 (to-do 图) require 'socket' socket = UDPSocket.new socket.bind("127.0.0.1", 12345) loop do msg, sender = socket.recvfrom(100) host = sender[3] puts "#{Time.now}: #{host} '#{msg}'" end require 'socket' log = UDPSocket.new log.connect("127.0.0.1", 12121) log.print "Up and Running!" log.print "Done!" 第十二章 反射和对象空间 98 §12.1 ObjectSpace 模块 使用 ObjectSpace 模块可以察看当前系统中的所有对象,也可以和垃圾回收器交互。 include ObjectSpace a = 123 b = 456.789 each_object(Numeric) { |x| p x } 执行结果如下: 456.789 123.456 2.71828182845905 3.14159265358979 2.22044604925031e-016 1.79769313486232e+308 2.2250738585072e-308 100.0 86400000000 30.6001 30.6001 365.25 365.25 122.1 4.0 36524.25 1867216.25 30.6001 365.25 4.0 100.0 each_object 列举系统中的所有指定的对象并返回对象的数目,上述代码列出了系统中所有的数值型数据, 除了我们的数据外还有一些系统预定义的常量。输出结果在你的机器上或许会稍有不同。 注意 each_ojbect 方法并不列举 Fixnum, Symbol, true, false 和 nil 型的数据。 a = 102.7 b = 95 # Won't be returned c = 12345678987654321 count = ObjectSpace.each_object(Numeric) {|x| p x } puts "Total count: #{count}" 执行结果为: 12345678987654321 102.7 2.71828182845905 3.14159265358979 99 2.22044604925031e-016 1.79769313486232e+308 2.2250738585072e-308 100.0 86400000000 30.6001 30.6001 365.25 365.25 122.1 4.0 36524.25 1867216.25 30.6001 365.25 4.0 100.0 Total count: 21 使用 ObjectSpace 模块也可以给对象提供析构方法,这个方法在对象被垃圾回收器摧毁时被调用。 include ObjectSpace a = "A" b = "B" c = "C" define_finalizer(a, proc {|id| puts "Finalizer one on #{id}" }) define_finalizer(a, proc {|id| puts "Finalizer two on #{id}" }) define_finalizer(b, proc {|id| puts "Finalizer three on #{id}" }) 执行结果为: Finalizer three on 537763470 Finalizer one on 537763480 Finalizer two on 537763480 §12.2 察看类和对象的状态 当找到一个感兴趣的对象,如何察看它的内部状态?毕竟,我们往往对它的内部状态更感兴趣。在静态 语言中,对象的类型取决于对象的所属类,察看对象所属的类就可以得到对象支持的方法。和静态语言不同, Ruby 支持更为自由的对象。 (to-do 解释 Ruby 对象的自由性) 可以查看一个对象支持那些方法: 100 str = “123” #创建一个 String 对象 list = str.methods list[0..3] => ["lstrip!", "to_sym", "scan", "instance_variables"] 可以查看某个对象是否支持特定的方法: str.respond_to?(“==”) => true str.respond_to?(“scan”) => true str.respond_to?(“xyz”) => false 也可以查看对象的 ID,对象从属的类,对象和类的关系: num = 6 num.id => 13 num.class => Fixnum num.kind_of? Fixnum => true num.kind_of? Numeric => true num.instance_of? Fixnum => true num.instance_of? Numeric => false 可 以 查 看 class 之 间 的 从 属 关 系 , 可 以 通 过 class#superclass 察 看 类 的 父 类 , 可 以 通 过 Module#ancestors 列出所有的父类和模块。 Fixnum.superclass => Integer Fixnum.ancestors => [Fixnum, Integer, Precision, Numeric, Comparable, Object, PP::ObjectMixin, Fox, Kernel] 我们还可以查看对象中方法的存取属性,可以查看对象中的常量和局部变量。 class InfoTest @@static_variable = 101 CONST = "This is a constant!" private def private_method end protected def protected_method end public def public_method @instance_variable = 100 i = 1 j = 2 local_variables end def InfoTest.class_method end 101 end 我们可以通过以下方法访问 InfoTest 类的信息: InfoTest.private_instance_methods(false) => private_method InfoTest.protected_instance_methods(false) => protected_method InfoTest.public_instance_methods(false) => public_method InfoTest.singleton_methods(false) => class_method InfoTest.class_variables => @@static_variable InfoTest.constants => CONST InfoTest.constants- InfoTest.superclass.constants => CONST 102 这 里 我 们 可 以 通 过 private_instance_methods,protected_instance_methods,public_instance_methods,singleton_meth ods 方法分别访问类的私有方法,保护方法,公有方法和类方法。参数 false 表示不列出父类的方法,如果 参数为 true 那么父类的相关方法也会被列出。class_variables 可以访问类的类变量,constants 类出类的 包括父类的所有常量,所以 InfoTest.constants-InfoTest.superclass.constants 表示只列出 InfoTest 类 的常量。这些方法都是在 Module 类中作的定义,Module 类包含许多可以访问类详细信息的方法。 InfoTest 是 Class 类的实例,Class 类的继承关系如下, Class -> Module -> Object 所以 InfoTest 类可以使用这些方法访问类的详细信息。 info = InfoTest.new info.instance_variables => [] info.public_method info.instance_variables => [@instance_variable] instance_variables 方法在 Object 类作的定义,返回当前实例对象的中的变量,从上面的代码可以看 出,只有当被使用到才会创建相应的变量,从这里也可以看出 Ruby 的动态性。 §12.3 动态方法调用 在 Ruby 中,有多种方法可以实现方法的动态调用。 §12.3.1 使用 send 方法 第一种实现动态方法调用是使用 send 方法,send 方法在 Object 类中定义,方法的第一个参数是一个 符号用来表示所要调用的方法,后面则是所调用方法需要的参数。 "This is a dog1".send(:length) => 14 上面的代码中通过 send 方法去对一个字符串执行 length 操作,返回字符串的长度。 class TestClass def hello(*args) "Hello " + args.join(' ') end end a = TestClass.new puts a.send :hello, "This", "is", "a", "dog!" 执行结果为: Hello This is a dog! 103 §12.3.2 使用 Method 类和 UnboundMethod 类 另一种实现动态方法调用是使用 Object 类的 method 方法,这个方法返回一个 Method 类的对象。我们可 以使用 call 方法来执行方法调用。 test1 = "This is a dog1".method(:length) test1.call => 14 class Test def initialize(var) @var = var end def hello() "Hello, @var = #{@var}" end end k = Test.new(10) m = k.method(:hello) m.call #=> "Hello, @iv = 99" l = Test.new('Grant') m = l.method("hello") m.call #=> "Hello, @iv = Fred" 可以在使用对象的任何地方使用 method 对象,当调用 call 方法时,参数所指明的方法会被执行,这种 行为有些像C语言中的函数指针。你也可以把 method 对象作为一个迭代器使用。 def square(a) a*a end mObj = method(:square) [1, 2, 3, 4].collect(&mObj) => [1 4 9 16] Method 对象都是和某一特定对象绑定的,也就是说你需要通过某一对象使用 Method 对象。你也可以通 过 UnboundMethod 类创建对象,然后再把它绑定到某个具体的对象中。如果 UnboundMethod 对象调用时尚未 绑定,则会引发异常。 class Double def get_value 2 * @side end def initialize(side) @side = side end end 104 a = Double.instance_method(:get_value) #返回一个 UnboundMethod 对象 s = Double.new(50) b = a.bind(s) puts b.call 执行结果为: 100 看下面一个更具体的例子: class CommandInterpreter def do_2() print "This is 2\n"; end def do_1() print "This is 1\n"; end def do_4() print "This is 4\n"; end def do_3() print "This is 3\n"; end Dispatcher = { ?2 => instance_method(:do_2), ?1 => instance_method(:do_1), ?4 => instance_method(:do_4), ?3 => instance_method(:do_3) } def interpret(string) string.each_byte {|i| Dispatcher[i].bind(self).call } end end interpreter = CommandInterpreter.new interpreter.interpret('1234') 执行结果为: This is 1 This is 2 This is 3 This is 4 §12.3.3 使用 eval 方法 我们还可以使用 eval 方法实现方法动态调用。 eval 方法在 Kernel 模块中定义,有多种变体如 class_eval,module_eval,instance_eval 等。Eval 方法将分析其后的字符串参数并把这个字符串参数作为 Ruby 代码执行。 str = "Hello" eval "str + ' World!'" => Hello World! 105 sentence = %q{"This is a test!".length} eval sentence => 15 当我们在使用 eval 方法时,我们可以通过 eval 方法的第二个参数指明 eval 所运行代码的上下文环境, 这个参数可以是 Binding 类对象或 Proc 类对象。Binding 类封装了代码在某一环境运行的上下文,可以供以 后使用。 class BindingTest def initialize(n) @value = n end def getBinding return binding()#使用 Kernel#binding 方法返回一个 Binding 对象 end end obj1 = BindingTest.new(10) binding1 = obj1.getBinding obj2 = BindingTest.new("Binding Test") binding2 = obj2.getBinding puts eval("@value", binding1) #=> 10 puts eval("@value", binding2) #=> Binding Test puts eval("@value") #=> nil 可以看到上述代码中,@value 在 binding1 所指明的上下文环境中值为 10,在 binding2 所指明的上下 文环境中值为 Binding Test。当 eval 方法不提供 binding 参数时,在当前上下文环境中@value 并未定义, 值为 nil。 §12.3.4 性能 从上面我们可以看到,我们有多种实现动态方法调用的方式,但是请注意,在这多种方式中,使用 eval 方法最慢。Ruby 提供了一个 Benchmark 模块可以用来报告代码的执行时间,衡量代码的性能。以下是使 用 Benchmark 模块来显示不同动态方法调用的执行时间: require 'benchmark' test_string = "This is a test!" meth = test_string.method(:length) num = 100000 Benchmark.bm(12) do |x| x.report("call") { num.times { meth.call } } x.report("send") { num.times { test_string.send(:length) } } x.report("eval") { num.times { eval "test_string.length" } } end 执行结果为: 106 all 0.090000 0.000000 0.090000 ( 0.090000) send 0.090000 0.000000 0.090000 ( 0.090000) eval 1.352000 0.000000 1.352000 ( 1.352000) 可以看到,使用 eval 方法的执行时间远远慢于其他两种。 §12.4 Hook 和回调方法 §12.4.1 什么是 Hook 最早是在操作系统中出现的 Hook 概念,在 Unix/Linux/Windows 中 Hook 的概念类似,Hook 的目的在于 允许用户在系统执行原有流程的过程中,插入自己的代码处理一些额外的事情。典型的 Hook 就是使用实现自 己功能的方法替换原有的方法,在所需要的额外处理完成之后,又恢复原有方法的处理流程。 (to-do Hook 的图) §12.4.2 Ruby 中的 Hook 钩子是一种可以跟踪 Ruby 事件的技术,例如设定一个钩子侦测对象创建,设定好后,钩子在发生对象 创建时触发。Ruby 是动态语言,可以在运行时改变类的方法和属性,我们可以很容易实现这样的功能。 下面我们来看一个简单的例子, module Mod alias_method :old_exit, :exit def exit(code=0) puts "Exiting with #{code}" old_exit(code) end end include Mod exit(99) 执行结果为: Exiting with code 99 上面的例子中,先使用 alias_method 给方法添加别名 old_new,然后重写 exit 方法,在 exit 方法使用 方法的别名来调用原有方法来完成原有功能,然后附加额外的信息。 同样,我们利用这种技术可以跟踪对象的创建。 class Class 107 alias_method :old_new, :new def new(*args) result = old_new(*args) puts "The object is: #{result}" result end end class Test def initialize(name) @name = name end def to_s "#{@name}" end end obj1 = Test.new("obj1") obj2 = Test.new("obj2") obj3 = String.new("12345") 执行结果为: The object is: obj1 The object is: obj2 The object is: 12345 §11.4.2 回调方法 Ruby 实现了一些方法,当特定的事件发生时,相应的方法会被调用。你可以重新定义这些方法加入额外 的处理或者改变系统的默认行为。 Module#method_added Module#method_removed Module#method_undefined Kernel#singleton_method_added Kernel#singleton_method_removed Kernel#singleton_method_ undefined Class#inherited Module#extend_object 默认情况下,这些方法什么也不做。 108 §12.5 跟踪程序的运行 §12.5.1 set_trace_func 可以使用 Kernel#set_trace_func 来跟踪程序的运行。set_trace_func 的原型为: set_trace_func(proc) => proc set_trace_func(nil) => nil 创建一个 Proc 作为跟踪的处理器,参数为 nil 的时候关闭处理。Proc 有六个参数,一个事件名称,一 个文件名,一个行号,一个对象 ID,一个 binding(to-do ?),一个类名。只要发生事件,Proc 就会被调用。 事件有这么几种: c-call 调用 C 语言过程 c-return 从 C 语言过程中返回 call 调用一个 Ruby 方法 class 开始一个类或模块的定义 end 结束一个类或模块的定义 line 执行新的一行代码 raise 发起一个异常 return 从 Ruby 方法中返回 class Test def test a = 1 end end set_trace_func proc {|event, file, line, id, binding, classname| printf "%10s %10s:%2d %10s %8s\n", event, file, line, id, classname } t = Test.new t.test 执行结果为: line test.rb:11 false c-call test.rb:11 new Class c-call test.rb:11 initialize Object c-return test.rb:11 initialize Object c-return test.rb:11 new Class line test.rb:12 false call test.rb: 2 test Test line test.rb: 3 test Test return test.rb: 3 test Test 109 §12.5.2 trace_var 可以使用 Kernel#trace_var 方法对全局变量添加钩子,当对一个全局变量执行赋值操作时,这个钩子 会被调用。第一个参数是一个符号,用来指明要监视哪一个全局变量,第二个参数是发生赋值操作时执行的 Proc 对象或块。 trace_var :$_, proc {|v| puts "$_ is now '#{v}'" } $_ = "hello" $_ = "there" 执行结果为: $_ is now 'hello' $_ is now 'there' §12.5.3 caller 可以使用 Kernel#caller 来得到当前的调用堆栈。 def meth1 puts caller.join("\n") end def meth2 meth1 end def meth3 meth2 end meth3 执行结果为: test.rb:6:in `meth2' test.rb:10:in `meth3' test.rb:13 §12.5.3 __FILE__,__LINE__和 SCRIPT_LINES__ __FILE__ 指明当前的原文件名 __LINE__ 当前在源文件中的行号 __FILE__经常和全局变量$0 一起使用来判断一个文件是否由用户直接调用。举个例子,库文件的开发者 经常在所开发的库中包含一些测试代码,这些测试代码在库被其他文件引用时不会执行。 110 # library code # ... if __FILE__ == $0 # tests... end SCRIPT_LINES__默认情况下不会被定义。若将它定义为哈希表后,在解释源代码时,它将依次处理使用 require 或 load 载入的文件,将源文件名作为哈希表元素的索引,源文件的内容会被按行切分转化为数组 作为哈希表的内容。 SCRIPT_LINES__ = {} require 'Benchmark' puts "Files: #{SCRIPT_LINES__.keys.join(', ')}" SCRIPT_LINES__['d:/ruby/lib/ruby/1.8/Benchmark.rb'].each{ |line| puts "Source: #{line}" } 以上代码将输出 Benchmark.rb 的内容。 第十三章 序列化和 YAML §13.1 序列化的概念 序列化是将对象状态转换为可保持或传输的格式的过程。与序列化相对的是反序列化,它将数据流转换 为对象。这两个过程结合起来,就使得数据能够被轻松地存储和传输。 为什么需使用序列化?有两个重要的原因:一个原因是将对象的状态保存在存储介质中,以便在以后 重新创建精确的副本;另一个原因是可以将对象从一个应用程序发送到另一个应用程序中,远程处理还可 以使用序列化将对象从一台机器上的应用程序传递到另一台机器上的应用程序中。 一般程序在运行时产生对象,这些对象随着程序的停止运行而消失,我们可以通过序列化将对象保存 下来,这样在程序终止运行后这些对象仍然存在。以后可以在程序再次运行时通过读取数据重建这些对象, 也可以在在其他程序中利用这些保存下来的对象。这种情况下我们通过使用对象的序列化和反序列化来完成 这样的功能。 §13.2 使用序列化 §13.2.1 二进制数据保存 在 Ruby 中,序列化也称为 marshaling,使用 Marshal 类 R 可以把一个对象转换成字节流,并把它存在 111 应用程序外。这样保存的对象可以在以后被其它的实例或者其它的程序读取使用。 可以使用 Marshal 类的 dump 方法保存对象,以后使用 load 可以读取以保存的这个对象。 class Rectangle def initialize(length, width) @length= length @width = width @area = length * width end def to_s "#@length #@width #@area" end end obj = Rectangle.new(10,20) puts "Before: obj = #{obj}" data = Marshal.dump(obj) obj = Marshal.load(data) puts "After: obj = #{obj}" puts obj.inspect 执行结果为: Before: obj = 10 20 200 After: obj = 10 20 200 # §13.2.2 YAML 数据保存 我们也可以使用 YAML 来实现序列化: require 'yaml' class Rectangle def initialize(length, width) @length= length @width = width @area = length * width end def to_s "#@length #@width #@area" end end obj = Rectangle.new(10,20) puts "Before: obj = #{obj}" 112 data = YAML.dump(obj) obj = YAML.load(data) puts "After: obj = #{obj}" puts obj.inspect 执行结果为: Before: obj = 10 20 200 After: obj = 10 20 200 # §13.3 定制序列化 §13.3.1 二进制数据保存 并不是所有的数据都可以被序列化和适合序列化,有时候你想自己决定哪些东西被序列化,Marshal 类 提供了二个方法来实现这样的功能,一个是 marshal_dump,一个是 marshal_load。当使用 Marshal.load 对 一个对象进行序列化的时候,如果这个对象定义了 marshal_dump 或 marshal_load 方法,那么这个方法将会 被调用。 class Rectangle def initialize(length, width) @length= length @width = width @area = length * width end def marshal_dump [ @length, @width ] end def marshal_load(variables) @length = variables[0] @width = variables[1] @area = "unknown" end def to_s "#@length #@width #@area" end end obj = Rectangle.new(10, 20) puts "Before: obj = #{obj}" data = Marshal.dump(obj) obj = Marshal.load(data) 113 puts "After: obj = #{obj}" 执行结果为: Before: obj = 10 20 200 After: obj = 10 20 unknown 一些对象不能被保存,如果将要被保存的对象包括其它程序调用,方法体,IO 实例等,或者单独的一 个对象,或者当你保存一个匿名的类或者方法时,一个 TypeError 错误就会产生。 §13.3.2 YAML 数据保存 使用 Marshal 模块实现序列化将对象保存为二进制数据,这样的做法有一个显著的缺点,由于保存的数 据是二进制形式,如果解释器发生了重大改变且 Marshal 的二进制数据流处理格式和方式也发生了变化,那 么就不保证原有已经 dump 的文件还可以继续使用。 如果使用文本格式的文件来保存序列化数据,那么就不用担心文件是否可读,在 Ruby1.8 种,可以使用 YAML 来实现这样的功能。 对于 marshaling,除了二进制格式,也可以采用 YAML 的方法,它是一种纯文字又易懂的格式,很有趣 而且比起 XML 简单多了。 修改一下上一节的例子,我们需要定义 to_yaml_properties 方法, require 'yaml' class Rectangle def initialize(length, width) @length= length @width = width @area = length * width end def to_yaml_properties %w{ @length @width } end def to_s "#@length #@width #@area" end end 114 obj = Rectangle.new(10,20) puts "Before: obj = #{obj}" data = YAML.dump(obj) obj = YAML.load(data) puts "After: obj = #{obj}" 执行结果为: Before: obj = 10 20 200 After: obj = 10 20 我们可以看看 YAML 究竟把数据保存成什么样的格式, obj = Rectangle.new(10,20) puts YAML.dump(obj) 执行结果为: -- !ruby/object:Rectangle length: 10 width: 20 §13.3 YAML 看了上一节,大家或许会问,“为什么要命名为 YAML?”已经有许多工具采用了招人喜欢的“YA*” 形式的首字母缩略词,来表示“还有另一种 XXX(Yet Another XXX)”。在开放源码这个充满智慧的领域 中,YAML 没有使用其名称所暗示的首字母缩略词,而是采用了循环的“YAML 不是标记语言(YAML Ain't Markup Language)”的缩写。然而对此产生的某种感觉是:YAML 确实可以做标记语言所做的工作,却不需 要任何标记。 尽管 YAML 与 XML 一样普通,但在阅读、编辑、修改和产生方面,它比 XML 简单得多。可以用 XML 表示的 任何东西,都可以用 YAML 表示,而且几乎总是更紧凑。 XML 是一个典型的由委员会驱动的庞然大物,它试图成为一种文档格式、数据格式、消息包格式、安全的 RPC 通道(SOAP)以及一种对象数据库。而且,XML 为每一类型的访问和操作都提供了大量的 API: DOM、SAX、XSLT、XPATH、JDOM 以及许多不太常见的接口层。非常了不起的是 XML 完成了所有这些工作;令人 失望的是没有一项工作是完美无缺的。 YAML 的关注面则比较窄,它只是清晰地表示在动态编程语言(如 Perl、Python、Ruby 等)中所遇到的数 据结构以及数据类型。 YAML 的优点:  适合人们阅读  适合与脚本语言交流  使用宿主语言的内部数据结构  拥有一致性的信息模型  使基于流式数据的处理成为可能  富于表达和可扩展  容易实现 YAML 做为一个可移植的对象序列化方案,可以在不同的 Ruby 进程中将对象以普通文本传递,此外也适 115 合 ruby 与那些支持 YAML 的语言之间交换数据用。 YAML 中数据主要由序列(sequence),map(有的也叫做 hash)和标量(scalar)来表示。语法比较简单, 易于人们阅读。  注释由#开头  序列由"-"开头  map 用 key:value 的格式  "---"表示一个 yaml 文档的开始  list 和 hash 可以嵌套  block 的概念:一个 block 是一段文本。  Inline Collections:数据都写在一行 §13.3.1 集合类型 §13.3.1.1 序列  基本序列 可以在每一个新行前加“-”构成一个序列。 YAML 中, - Henry - Mike - Tom 在 Ruby 中, [‘Henry’, ‘Mike’, ‘Tom’]  嵌套序列 使用位于空行上的“-”,你可以在一个序列内包含另一个序列。 - - Henry - Mike - Tom 在 Ruby 中, [[‘Henry’, ‘Mike’, ‘Tom’]] 使用缩进表示不同的层次,序列也可以表示更深层次的嵌套 - - - Henry - Mike - Tom 在 Ruby 中, [[[‘Henry’, ‘Mike’, ‘Tom’]]] 116  混合嵌套序列 在序列中也可以嵌套其他的 YAML 数据结构: - Henry - - 13 - male - Mike - Henry 在 Ruby 中, [‘Henry’, [‘13’, ‘male’], ‘Mike’, ‘Henry’] §13.3.1.2 表 你可以使用 Key:value 的形式组织数据,每个 key:value 位于单独的一行,这样的数据结构称为表,也 叫做哈希表或字典。  基本表 Grant:student Tom:teacher 在 Ruby 中, {‘Grant’ => ‘student’, ‘Tom’ => ‘teacher’ }  含有序列的表 Grant:student Tom: - teacher - male 在 Ruby 中, {‘Grant’ => ‘student’, ‘Tom’ => [‘teacher’, ‘male’ }  嵌套表 teacher: Mike students: Grant:13 Henry:14 在 Ruby 中, { ‘teacher’ => ‘Mike’, ‘students’ => { ‘Grant’ => ‘13’, ‘Henry’ => ‘14’ } }  混合表 grade: 1 teachers: 117 Mike:math Jane:english classes: - Grant:13 Henry:14 - Jack:13 Rose:13 在 Ruby 中, { ‘grade’ => ‘1’, ‘teachers’ => { ‘Mike’ => ‘math’, ‘Jane’ => ‘English’ } ‘classes’ => [ { ‘Grant’ => ‘13’, ‘Henry’ => ‘14’ }, { ‘Jack’ => ‘13’, ‘Rose’ => ‘13’ } ] }  序列中嵌套表的缩写 当在序列中添加一个表时,可以 - worked on test.py: - This is a test 在 Ruby 中, [ {‘worked on test.py’ => [‘This is a test’] } ]  表中嵌套序列的缩写 当在表中嵌套一个序列时,可以不需要缩进。 Students: - ‘Grant’ - ‘Henry’ 在 Ruby 中, {‘Students’ => [‘Grant’, ‘Henry’] }  插入键(Merge key) 118 可以使用插入键在一个表中插入另一个表 { information: Name: Jane course: English <<: Age: 25 } 在 Ruby 中, {‘information’ => { ‘name’ => ‘Jane’, ‘course’ => ‘English’, ‘age’ => ‘25’ } } §13.3.2 单行集合类型  单行序列  单行映射 §13.3.3 基本类型  String  Scalar  NULL  Boolean  Integer  Float  Time  Date §13.3.4 块 §13.3.5 别名和锚(Aliases and Anchors) §13.3.6 文档 119 §13.3.7 Ruby 中 YAML 的使用 Ruby 中的 YAML 支持 Ruby1.8 已经包含了 YAML 支持了。只需要 require 进来就行了。 require 'yaml' class Person attr_accessor :name, :sex, :age def initialize(name, gender, age) @name = name @gender = gender @age = age end end #创建一个对象 person=Person.new("Tom", "male", 15) #打印序列化之后的结果 puts person.to_yaml 结果为: --- !ruby/object:Person age: 15 gender: male name: Tom 假设现在有一个 person.xml 文件内容为: --- !ruby/object:Person age: 25 gender: male name: Henry 如下代码: #从 person.yml 创建对象 person2 = YAML::load( File.open('d:\\person.yml') ) puts person2.inspect puts person2.class # 结果是 Person puts person2.name 执行结果为: # Person Henry 120 第十四章 安全控制 Ruby 代码可以方便的移植,也可以方便的对原代码进行分发。这样带来了灵活性的同时也带来了巨大的 隐患,如何才能防止不安全代码的执行呢? 我们可以利用 Ruby 语言含有的安全控制功能来实现这一目标。Ruby 安全控制可以用来锁住有嫌疑的数 据,这样的数据被称作“tainted”。Ruby 安全控制系统引入了一种机制,可以由用户决定如何处理有潜在 危险的数据。 全局变量$SAFE 用来控制安全级别。默认的级别是 0。可以通过改变$SAFE 的值或使用命令行参数-T 来指 定安全级别。安全级别数值越低表示数据越可信。数值低的安全级别的约束同样对数值高的安全级别起作用。 不能在代码中降低当前安全级别。 $SAFE 约束 0 不对 tainted 数据作检验,是 Ruby 的默认安全级别 1 不允许有危险的操作使用 tainted 数据 2 不允许加载可写路径之外的代码 3 所有新建立的对象都被认为是 tainted 数据 4 将 tainted 数据和非 tainted 数据隔离开,非 tainted 数据不允许改 变 新建立的线程会继承当前$SAFE 的值。在新建立的线程中,可以改变$SAFE 的值而并不影响其它线程。这 样可以容易的实现“沙箱”的功能。例如在一个线程中,当需要从外部加载代码并加以执行时,可以通过降 低$SAFE 的值来限制代码执行一些有危险的操作,从而降低代码的潜在风险。 什么是 tainted 数据?简单的说,就是这个数据受到了污染而变得不可信。当一个 Ruby 对象起源自一些 外部资源,例如一个字符串保存了被读取外部文件的内容,这个对象被自动标记为“tainted”。如果使用 这个 tainted 对象而得 到其 他的 对象 ,那么这 个新 得到的对 象也 被标 记为 “ tainted”。 可以使用 Object#tainted?方法来判断一个对象是否被“tainted”。 str1 = "This is a string" str1.tainted? #=> false str2 = str1 + “!” str2.tainted? #=> false str3 = gets #str1 的值从输入流读取 str3.tainted? #=> true str4 = str3 + “!” str4.tainted? #=> true 可以看到,由于 str3 的内容来自外部,所谓 str3 被认为是不可信的,对 str3 作操作的结果同样被认 为不可信的。 §14.1 0 级 0 级是默认的安全级别,不对 tainted 数据作任何检查。环境变量 PATH 的值会被检查,只有 PATH 内包含 121 的目录对所有人都可写 PATH 才会被置为 tainted。 其他任何来自 IO 的数据,环境变量和 ARGV 自动被标记为 tainted。 §14.1 1 级 对 tainted 数据有潜在危险的操作被禁止。这一级别适合用来处理输入包含一些不可信的数据,例如 CGI 程序。 §14.2 2 级 除了 1 级的约束之外,对文件有潜在危险的操作被禁止。 §14.3 3 级 除了 2 级的约束之外,所有新产生的对象都被标记为 tainted。 §14.4 4 级 除了 3 级的约束之外,不能改变全局变量的值。 第十五章 单元测试 §15.1 什么是单元测试 §15.2 Ruby 单元测试框架 第二部分 内置类与模块 这一部分我们将列出 Ruby 语言标准内置的类与模块,这些类与模块可以在任何 Ruby 程序中使用而不需 要额外使用 require 语句。 122 第一章 内置类 §1.1 Array Array 类是有序的对象集合。Array 类以整数作为索引,索引从 0 开始,负数索引表示从尾部逆序开始。 例如-1 表示最后一个元素,-2 表示倒数第二个元素,依次类推。  父类 Array 类的父类为 Object 类。  Mix-in Enumerable  类方法 §1.2 Bignum §1.3 Binding §1.4 Class §1.5 Continuation Continuation 对象由 Kernel#callcc 方法产生,功能有些类似 C 语言中的 setjmp/longjmp,它保存了 执行环境,可以供代码以后使用。 Continuation 对象只有一个 call 方法,当执行了 call 方法后,当前执行语句会转到 Continuation 对 象的块后面,继续执行下面的代码。 obj = callcc{|continuation| continuation} puts "start..." obj.call if obj puts "end..." 执行结果为: start... start... end... 上面的例子中,首先使用 callcc 生成一个 Continuation 对象,然后输出“start…”在调用 call 后, 当前执行语句跳转到 callcc 语句紧接的块后,所以“start…”被输出了两次。在执行了一次 call 方法后, 123 obj 对象会被置为 nil。 下面一个例子是在方法内发生跳转: def method callcc {|cont| return cont} puts "in method..." end puts "start..." obj = method() puts "end..." obj.call if obj 执行结果为: start... end... in method... end... 124 §1.6 Dir §1.7 Exception §1.8 FalseClass §1.9 File §1.10 File::Stat §1.11 Fixnum §1.12 Float §1.13 Hash §1.14 Integer §1.15 IO §1.16 MatchData §1.17 Method §1.18 Module 125 §1.19 NilClass §1.20 Numeric §1.21 Object §1.22 Proc §1.23 Process::Status §1.24 Range §1.25 Regexp §1.26 String §1.27 Struct §1.28 Struct::Tms §1.29 Symbol §1.30 Thread §1.31 ThreadGroup 126 §1.32 Time §1.33 TrueClass §1.34 UnboundMethod 127 第二章 内置模块 §2.1 Comparable §2.2 Enumerable §2.3 Error §2.4 FileTest §2.5 GC §2.6 Kernel §2.7 Marshal §2.8 Math §2.9 ObjectSpace §2.10 Process §2.11 Process::GID §2.12 Process::Sys 128 §2.13 Process::UID §2.14 Signal all?方法 方法原型: enum.all? [ {|obj| block} ] 方法依次将集合内的每个元素传递给块,如果所有的执行结果都为真那么方法返回 true,任意一次执 行结果为假那么方法返回 false。如果忽略了 block 块,则 Ruby 会自动添加一个{|obj|obj}块。 %w{ ant bear cat}.all? {|word| word.length >= 3} #=> true %w{ ant bear cat}.all? {|word| word.length >= 4} #=> false [ nil, true, 99 ].all? #=> false Inject 方法 方法原型: enum.inject(initial) {| memo, obj | block } => obj enum.inject {| memo, obj | block } => obj memo 被设置为块计算后的返回值。首先,用 initial 初始化 memo,并将 memo 与 obj 参数传递给块进行 计算,然后将上步块计算后的值赋值给 memo 后,再继续计算块,以此类推。 若省略了初始值 initial,开始时会把第一和第二个元素传递给块。若只有一个元素时,将直接返回首 元素,而不会执行块。 若没有元素,则返回 nil。 # 求 5 到 10 的和 (5..10).inject {|sum, n| sum + n } #=> 45 # 求 5 到 10 的积 (5..10).inject(1) {|product, n| product * n } #=> 151200 # 找出最长的单词 longest = %w{ cat sheep bear }.inject do |memo,word| memo.length > word.length ? memo : word end longest #=> "sheep" # 找出最长单词的长度 longest = %w{ cat sheep bear }.inject(0) do |memo,word| memo >= word.length ? memo : word.length end longest #=> 5 这个方法之所以用 inject 这个名称,是因为这个方法有注射动作,即将 initial 及后来的块计算结果注入 129 到 memo 中。 第三部分 Ruby 语言总结 130  书写习惯 Ruby 是一种面向行的语言,一行可以写多个语句,使用“;”隔开。一条语句也可以写在多行,行 之间使用“\”连接。  语言注释 Rub 中提供两种形式的注释: 1. 单行注释,以“#”开始直到行末尾。 2. 多行注释,在“=begin”和“=end”之间的行会被解释器忽略。  BEGIN 和 END 块 每一个 Ruby 源文件可以声明 BEGIN 块和 END 块,BEGIN 块在文件载入被执行,END 块在程序执行完毕被 执行。 BEGIN { #Begin code } END { #End code }  常用分隔符输入 分隔符输入格式以一个“%”开始,紧接着一个字符表明输入类型,随后的字符表示分隔符。分隔符可 以是任何的非字母单字节字符。如果分隔符是“(,[,{,<”,则表示结束的分隔符为相应的 “),],},>”,而且可以嵌套出现。其它的分隔符则是分隔字符下一次出现的位置。 %Q/This is a string/ %Q a string> 使用分隔符也可以跨越多行: string = %q{ BEGIN { puts "===BEGIN===" } } %q 单引号包围的字符串 % 双引号包围的字符串 %Q 双引号包围的字符串 %w 每一个元素都是字符串的数组 %W 每一个元素都是字符串的数组 %r 正则表达式 %x Shell 命令  命名规则 Ruby 中的对象和常量保存了对实际对象的引用,对象自己并没有类型,所谓的对象的类型是指它 引用的实际对象的类型。 常量可以在类或模块中定义,访问时需要使用类名加上“::”。 131  变量 在 Ruby 中使用不同的前缀来区分变量的不同类型: 类型 示例 解释 全局变量 $foo 全局变量以$开始,未初始化的全局变量值为 nil 实例变量 @foo 实例变量以@开始,未初始化的实例变量值为 nil 类变量 @@foo 类变量以@@开始,必须初始化 局部变量 foo 局部变量以小写字母或下划线开始 常量 Foo 常量以大写字母开始  预定义变量  异常相关 名称 类型 解释 $! Exception 最近被抛出的异常对象,可以在 rescue 语句中使 用=>访问 $@ Array 最 近 被抛出异 常相关 的堆 栈信 息,可 以使 用 Exception#backtrace 方法访问  正则表达式匹配相关 名称 类型 解释 $& String 已经匹配到的字符串 $@ String 最后一个使用括号匹配到的字符串 $` String 前一个匹配到的字符串 $’ String 下一个匹配到的字符串 $= Object (已废弃)如果内容不为 nil 或 failse,那么正 则表达式匹配,字符串比较和 Hash 表的值不区分 大小写 $1 到$9 String 正则表达式匹配使用圆括号相应的匹配串 $~ MatchData 保存正则表达式匹配结果  输入输出相关 名称 类型 解释 $/ String 输入分隔符 $-0 String 和$/同义 $\ String 输出分隔符 $, String 使用 Array#join 方法输出时的分隔符,默认为 nil $. Fixnum $; String 使用 String#split 方法时的分隔符,默认为 nil $< Object ARGF 缩写 $> IO $defout 缩写 $_ String $defout IO $deferr IO $-F String $stderr IO $stdin IO $stdout IO  环境相关 名称 类型 解释 $0 String 当前执行应用程序名 $* Array 保存了命令行执行的参数 $” Array $$ Fixnum $? Process::status 132 $: Array $-a Object $-d Object $DEBUG Object __FILE__ String $F Array $FILENAME String $-i String $-I Array $-K String $-l Object __LINE__ String $LOAD_PATH Array $-p Object $SAFE Fixnum $VERBOSE Object $-v Object $-w Object  标准对象 名称 类型 解释 ARGF Object 和$<同义 ARGV Array 和$*同义 ENV Object false FalseClass nil NilClass self Object true TrueClass  全局常量 名称 类型 解释 DATAIO 和$<同义 FALSE FalseClass 和$*同义 NIL NilClass RUBY_PLATFORM String RUBY_RELEASE_DATE String RUBY_VERSION String STDERRIO STDINIO STDOUTIO SCRIPT_LINES__ Hash TOPLEVEL_BINDING Binding TRUE TrueClass 133 附录 §1 术语对照 Singleton Method 单例方法 Singleton Class 单件类 iterator 迭代器 handler 处理器 callback 回调 class Integer def get_debug_info "This is #{self}" end end puts (0..5).map { |i| i.get_debug_info } 执行结果为: This is 0 This is 1 This is 2 This is 3 This is 4 This is 5 134
还剩133页未读

继续阅读

下载pdf到电脑,查找使用更方便

pdf的实际排版效果,会与网站的显示效果略有不同!!

需要 10 金币 [ 分享pdf获得金币 ] 0 人已下载

下载pdf

pdf贡献者

baijunfen

贡献于2012-11-06

下载需要 10 金币 [金币充值 ]
亲,您也可以通过 分享原创pdf 来获得金币奖励!
下载pdf