Thursday, August 17, 2006

Ruby 程序的执行

Ruby程序的执行就是对代码进行计算的过程。先编译程序文本,遇到BEGIN就对其作出计算;然后计算顶层的一系列的表达式;若遇到END的话,将在最后对其进行处理然后结束程序(关于结束程序时处理步骤的详细过程请参考结束程序时的相关处理)。

语句

if

if句的计算过程如下:先对条件表达式进行计算,若为真则执行相应代码段,若为假则依次计算elseif部分的条件表达式,若遇到值为真的表达式则执行相应的代码段。若所有的条件表达式的值都为假的话,就执行else部分的代码段。

语句的值取决于最后执行的代码块的值。若最后的代码块中没有表达式,或者所有条件表达式的值都是假而且没有else部分的话,则语句的值为nil。

until
if 修饰部分
unless 修饰部分
while
until
while 修饰部分
until 修饰部分
for
begin ~ end
类定义句

定义类的内容。在执行时(而并非编译时)进行计算。

书写样式

class ClassName [< 超类表达式]
表达式
end

在对类定义句进行计算时,将先试图生成类。若有超类表达式就加以计算,其值作为ClassName类的父类,然后生成ClassName类的实例.若没有超类表达式,就把Object作为其父类.

另一方面,若有同名类的话,就先使用那个同名类。然后处理超类表达式,若新生成的超类(在equal?)有所不同的话,就再生成一个新的类。

得到类之后就将其代入常数“ClassName”中,由此决定类名。此时,若同名的常数中被代入一个非Class的实例的话,就会引发异常TypeError。

最后生成新的框架(frame),向顶层块的self以及class设定要进行定义的类,然后在框架的基础上对定义句中的表达式进行计算。我们无法得到类定义句的值。

也就是说,在Ruby中我们可以多次“追加类定义”。

模块定义句

定义模块的内容。在执行时(而并非编译时)进行计算。

书写样式

module ModuleName
模块内容
end

对模块定义句进行计算时,首先会生成新的无名模块。但是,若已经有了一个名为ModuleName的模块的话,就使用该模块。此时就变成“追加模块的定义”了。

得到模块后就将其代入常数ModuleName中。这个常数就成为模块的名称。此时,若向同名常数代入非模块的话就会引发异常TypeError。

最后生成新的框架(frame),向顶层块的self以及class中设定模块ModuleName,然后在框架的基础上对定义句中的表达式进行计算。模块定义句的值就是模块内容的最后一个表达式的值。若模块内容中没有可计算的表达式时,其值为nil。

特殊类定义句

定义对象的特殊类。在执行时(而并非编译时)进行计算。

书写样式

class << EXPR
类的内容
end

先计算想定义特殊类的对象的表达式EXPR。然后生成该对象的特殊类(若尚未生成的话)。最后生成新框架,向顶层块的selfclass中设定新生成的特殊类。在新框架的基础上对定义句中的表达式进行计算。

特殊类定义句的值取决于类的内容中的最后一个表达式的值。若没有可计算的表达式时,其值为nil。

请注意,Fixnum Symbol的实例以及 true false nil 不能定义特殊类

方法定义句

定义方法的内容。在执行时(而并非编译时)进行计算。

书写样式

def method_name(arg, argwithdefault=expr, *restarg, &block)
方法内容
end

对其进行计算时,将向运行块的class中定义该方法。若class中已经存在同名的方法的话,则抛弃旧方法,添加新方法。

方法定义句的值为nil。

特殊方法定义句

向对象的特殊类中定义方法。在执行时(而并非编译时)进行计算。

书写样式

def expr.method_name(arg, argwithdefault=expr, *restarg, &block)
方法内容
end

首先计算表达式expr。然后生成对象的特殊类(若尚未生成的话)。最后向特殊类中定义方法method_name

特殊方法定义句的值为nil。

请注意,Fixnum Symbol的实例以及true false nil不能定义特殊方法。

BEGIN

编译时会用到(执行时首先计算)

END

编译时会用到(执行时最后计算)

方法


方法的调用

首先计算被调(receiver)表达式,得到被调用对象。省略被调表达式时,调用块的self将成为被调。

接下来从左到右地对参数表达式进行计算,检索被调里面的方法。若检索失败则引发异常NameError,成功的话就执行方法。

另外,执行方法的时候还可以添加块(block)。若向方法添加块时,只有当运行中的方法执行yield时才会对块进行计算。若没有执行yield的话,块将被忽视,不会执行。

将块传给方法时,该块将会继承调用方的块的selfclass。只有Module#module_eval/class_eval和Object#instance_eval这三个例外,如下所示。


Module#module_eval, class_eval

self和class都是被调(receiver)


Object.instance_eval

self是被调,class是被调的特殊类


eval

Proc对象和Binding对象传给eval的第二参数时,将在生成时的块的基础上对字符串进行计算。

方法的执行

当框架上只有一个块的情况下,才开始执行方法。下面我们暂时把这个块称作顶层块(top level block)。顶层块的self是被调,class尚未被定义。

首先,若有必选参数的话,就把得到值代入顶层块的局部变量。

若存在可选参数,且已被省略的话,则在顶层块上对默认值表达式进行计算,然后将得到的默认值代入顶层块的局部变量。若可选参数没被省略的话,就把得到的值代入顶层块的局部变量。

若存在*args这样的参数的话,则将剩下的所有参数以数组的形式代入局部变量。

另外,若存在块参数blockvar的话,则将传给方法的块进行Proc对象化,然后代入顶层块的局部变量blockvar中。若没向方法传递块的话,就代入nil。

接下来对方法内容进行计算,先计算方法层(method level)的rescue以及else部分,最后计算ensure部分。

整个方法的值取决于传递给return的值。若没有调用return的话,则取决于 方法内容/rescue/else 中最后被计算的表达式的值。若三个都为空的话,值为nil。

带块的方法调用

若向方法传递一个块的话,这个方法就叫做带块的方法。带块方法遇到yield时会转向块。

可以使用块参数。

break...若块位于堆栈框架(stack frame)上的话,就跳到框架的块的后面。break并结束带块方法,其值为nil。若块不在堆栈框架上,则引发异常LocalJumpError

next 跳到块的终点

retry 这个就复杂了...

eval, instance_eval, module_eval


赋值


赋值是指让变量或常数记住某个对象。从语法的角度来看,虽然[]=和属性赋值的方法调用看起来很像是赋值,但却并非这里谈到的赋值。

我们可以反复地将各种对象赋值给变量。也可以将各种对象赋值给常数,但却只能赋值一次。也就是说,一旦将对象赋值给常数,就不能再更改。但这并不意味着赋值给常数的对象本身不允许更改,请您注意这点。

多重赋值

暂无

变量和常数


我们可以让变量或常数记住一个对象。这叫做“变量(常数)的赋值”。

当对变量或常数进行计算时,它就会返回记住的对象。这叫做“变量(常数)的调用”。

下面我们就分别来看一看变量和常数的赋值与调用过程。

局部变量

局部变量只属于一个块。块是指与代码的某个范围相对应的运行时的结构,可以嵌套。具体说来,它伴随带块的方法调用以及eval系方法的执行而出现。我们只能在局部变量的所属块以及该块的嵌套块中对局部变量进行赋值和引用。

同时,块被放在特定的“框架”上,并归属于该框架。因此,不能调用其他框架上的局部变量。所谓框架是指开始执行下列语句时生成的运行时的结构。


  • 程序文本的顶层(传递给ruby的文件名、-e、load)
  • 执行方法
  • 类/模块的定义句
  • BEGINEND

生成框架时自动搭载一个块,因此可以在这些语句中使用局部变量。

编译时,写入程序代码的局部变量将赋值给框架中的尚未定义的局部变量。局部变量被赋值时所在的块就是它的归属块。由此可知,编译时局部变量的定义过程已经完成(请注意,eval系的方法在执行过程中进行编译)。定义的变量的初始值为nil。

局部变量在定义和调用时,先是在块中从外到内地进行搜索。其结果就是,局部变量不能进行嵌套和屏蔽(shadowing)。但是,当若干的块处于非主从关系时,其内部可以包含不同的局部变量。

调用未定义(即没有在代码中标出)的局部变量时,Ruby会试图把它当作对self的(无参数的)方法调用来处理。若搜索方法失败则引发异常NameError

再来看一下调用块的执行情况,块也可以带参数,但常被看做是在将要执行的块上进行的多重赋值.例如,下面代码的块在开始执行时

some_iterator do |a,b|
....
end

首先会进行下列操作。

a, b = <some_iterator 被yield的值 >

实例变量

实例变量属于一个对象,在self代表的块的范围内可以进行赋值和调用。实例变量的赋值过程同时也就是该变量的定义过程,若调用未定义的实例变量则返回nil。

remove_instance_variable

类变量

类变量为一个特定的类、该类的子类以及该类的实例所拥有。在以这些对象为self的块的范围内,可对其进行赋值和调用。最初的赋值过程也兼做定义。若调用一个未经定义的类变量的话就会引发异常NameError

类变量的继承和“继承中止”

全局变量

在任何地方都可以对全局变量进行赋值和调用。最初的赋值过程兼做变量的定义,若调用一个未经定义的全局变量的话,就会返回nil。

可跟踪(?)

常数

常数属于类/模块。我们可以使用除method以外的方式对其进行赋值。最初的赋值兼做定义。对常数赋值时所在的块的class就是常数的归属类。有个非常特殊的例外,我们可以使用Module#const_set方法来定义常数,同时,还可以使用Module#remove_const来取消常数。

无法对已定义的常数进行再定义或赋值。实际上,只使用警告还可以进行赋值,但这只是一时的应急措施,并不符合规范。所以要少写这样的程序。

可调用范围因写法不同而有所差异。


只写出常数名时 (例: Const)

可调用范围有:常数所属的类、子类、嵌套类的框架内的代码


写出完整路径时 (例: Mod::Cls::Const)

可在任何地方调用


另外,像"::Const"这种前置"::"的写法,只有写成"Object::Const"时才能进行调用。

伪变量


下面这些变量看起来好像是局部变量,但实际上是保留字,返回确定的值。不可赋值。

self

返回该块的self

nil

返回NilClass的唯一的实例--nil。

true

返回TrueClass的唯一的实例--true。

false

返回FalseClass的唯一的实例--false。

Ruby 中的对象

对象概述
Ruby所能处理的值都是对象。Ruby操作对象时的办法只有一种,那就是方法调用。对象所属的类决定了对象所包含的方法。生成对象时,对象的所属类也就确定了,除非导入特殊类以外,对象的所属类不会改变。可以说对象是某个特定类的“实例”。
生成对象一般是通过调用别的对象的方法来实现的。

类决定了归属其下的对象的方法。若能调用归属对象的某方法时,我们就说该方法是“由类定义的”。方法由类和方法名决定。
每个类都必然拥有一个“超类”。若在超类中定义了一个方法,则在当前类中必然也会出现一个相同的方法,这叫做“继承”。继承是发展的,所以超类的超类中的方法也会出现在当前类中。
模块
include

这个有点特殊,它并不是类,但功能相同。
方法
方法是可以执行的。通常我们把执行方法的动作叫做“调用”。调用方法时可以传递对象,这个对象就是“参数”。可接受的参数数量是在定义方法时确定的,不可改变。

Ruby 中的字句构造

    现在Ruby使用的是ASCII字符集。对英文字母的大小区别是敏感的。您可以在任何地方插入空字符或注释,除了标识符和部分字面值的中部。空字符包括 space,tab,垂直tab,backspace,回车,换页。换行比较特殊,若换行后内容是继续上一行内容的话,该换行符就是空字符,除此以外的换行符就被解释为语句的切分。
标识符号
例:
    foobar
    ruby_is_simple
Ruby的标识符的首位由字母或下划线('_')构成,后面部分可以是字母、下划线('_')或数字。对标识符的长度没有限制。
注释
  Ruby的注释由#开头
  # this is a comment line
遵从脚本语言的习惯,Ruby把 除了字符串内部和数值字面值'?#'之外的 以#开始的行当作注释行来处理。
内嵌文档
例:
=begin
the everything between a line beginning with `=begin' and
that with `=end' will be skipped by the interpreter.
=end
可以在Ruby代码中嵌入文档。从行首为=begin的行开始到行首为=end的行为止的部分就是内嵌文档。虽然Ruby解释器对内嵌文档的内容没有什么要求,但是内嵌文档的格式最好是RD
保留字
下列词语就是Ruby的保留字。
BEGIN    class    ensure   nil      self     when
END      def      false    not      super    while
alias    defined? for      or       then     yield
and      do       if       redo     true
begin    else     in       rescue   undef
break    elsif    module   retry    unless
case     end      next     return   until
保留字不可用作类名和变量名。但若这些词语前面出现$,@,@@等前缀的话,就不再是保留字了。当在def定义中或方法调用操作符'.'后面出现这些词语时,则可确定它们是方法名,这时就可以使用这些词语。

Ruby 命令行选项

   Ruby 解释器接收下面的命令行选项(开关)。基本上这些选项和Perl非常相似。
0数字
   以($/)分隔的八进制数字。如果未给出数字分隔符是空字符。其他可选数字未:-00代表将Ruby转换到段落模式-0777(因为这个代码不代表任何文字)表示将文件的全部内容一次性读入(相当于$/=nil)。
-a
与'-n'或'-p'一起使用时,可以打开自动拆分模式(auto split mode)。自动拆分模式将在各个循环前执行以下动作。

$F = $_.split

若没有同时指定'-n'或'-p'选项的话将不起作用。


-C directory

执行脚本之前,先移动到指定目录。


-c

只对脚本进行编译,而并不执行。编译后若没发现语法错误,则显示“Syntax OK”。


--copyright

显示版权信息。


-Kc

指定Ruby要处理的汉字编码。若是'E'或'e',则Ruby认定字符串或访问文件中的汉字编码为EUC。同样,若是'S'或's'的话则认定为SJIS。若是'U'或'u'则当作UTF-8处理。'N'表示不对汉字进行处理。该选项的默认值就是N(NONE)。

将来有可能会改变文字编码处理方式,届时该选项的内容也会有所变化。


-d
--debug

以调试模式执行脚本。将$DEBUG设置成true。


-e script

在命令行中指定脚本。添加-e选项后,就不会从参数中抽取脚本文件名了。

若多次使用-e选项时,系统会按照以下方式处理。

下列各表达式的意义相同。
ruby -e "5.times do |i|" -e "puts i" -e "end"

ruby -e "5.times do |i|
puts i
end"

ruby -e "5.times do |i|; puts i; end"

-Fregexp

regexp指定给输入域分隔符(field separator)。


-h
--help

显示命令行选项的简介。


-i[extension]

对参数中指定的文件内容进行替换(in-place edit)。原始文件将被加上扩展名并保存下来。若没有扩展名的话,将不会进行备份,而且只有替换后的文件会被保留下来。

例:

% echo matz > /tmp/junk
% cat /tmp/junk
matz
% ruby -p -i.bak -e '$_.upcase!' /tmp/junk
% cat /tmp/junk
MATZ
% cat /tmp/junk.bak
matz

-I directory

指定(追加)加载文件的路径。指定的目录将被追加到Ruby的数组变量($:)中。


-l

进行行尾自动处理。首先,将$\改为$/的值,在print输出时添加换行。若使用了-n标志或-p标志的话,将对gets读入的各行末尾进行String#chop!处理。


-n

若使用了该标志,则整个程序会像sed -n或awk一样,被

while gets
...
end

括起来运行。


-p

与-n标志相仿,在各循环后输出变量$_的值。

例:

% echo matz | ruby -p -e '$_.tr! "a-z", "A-Z"'
MATZ

-r feature

执行脚本前,先对feature指定的库执行require操作。与'-n'选项、'-p'选项一起使用时特别奏效。


-s

对跟在脚本名后并且以'-'开头的参数进行解释,并将其值赋值给同名的全局变量。遇到以'--'开头的参数就停止解释,并将该参数从ARGV中删除。

例:

#! /usr/local/bin/ruby -s
# prints "true" if invoked with `-xyz' switch.
print "true\n" if $xyz

-S

该选项表明,当脚本名不是以'/'开头的时候,要使用环境变量PATH的值搜索脚本。若您的机器不支持#!的话,可以使用下列方法模拟#!的运行:

#!/bin/sh
exec ruby -S -x $0 "$@"
#! ruby

因为第1行的关系,系统把脚本交给/bin/sh。/bin/sh执行第2行后启动Ruby解释器。在-x选项的作用下,Ruby解释器把从'#!'到包含'ruby'的行的内容全部读入。

根据系统的不同,$0未必包含完整路径,因此有必要使用'-S'选项来告诉Ruby在必要时搜索脚本。


-T[level]

执行不纯度测试。若给level指定了一个值之后,安全等级也会使用这个值。省略level时,其值为1。对于CGI程序来说,将其指定为-T1比较合适。$SAFE的等级也将被设定。


-v
--verbose

冗长模式。启动时显示版本信息,然后将内部变量$VERBOSE设为true。当此变量为true时,众多的方法在运行时会显示冗长的信息。若只设定'-v'选项,而没有其他参数时,启动后会先显示版本信息,然后就结束运行(不会等待来自标准输入的脚本)。


--version

显示Ruby的版本信息。


-w

不显示版本信息的冗长模式。


-W[level]

ruby 1.8 特性

可以指定3种级别的冗长模式,如下所示。


  • -W0: 不显示警告
  • -W1: 只显示重要警告(默认)
  • -W2 or -W: 显示所有警告

内部变量$VERBOSE被分别设置为nil,false,true。


-x[directory]

从message中取出脚本并执行。读入脚本的范围是从'#!'开始,直到包含'ruby'的行为止。用EOF(文件结束),^D(controlD),^Z(controlZ)或保留字_END_来指定脚本结束。

若指定了目录名的话,则在执行脚本前移动到该指定目录。


-y
--yydebug

编译器调试模式。编译脚本时显示语法分析的过程。该显示过程会很漫长,可能只对那些想调试编译器的人有用。

Ruby 中文手册 前言

Ruby是一个快速、使用简单的面相对象的解释性脚本语言。它提供了很多处理文本文件和处理系统管理任务的功能(就像perl)。它很简单而且扩展性很强。
如果你需要一种简单的面相对象的程序语言,或者你不喜欢perl“丑陋”的语法结构,又或者你喜欢lisp的语言风格,但是讨厌使用很多括号,Ruby也许是你最好的选择。
Ruby的特性如下:
解释性语言
    Ruby是解释性的语言,所以你无需编译就可以运行Ruby写的程序。
变量没有类型(动态类型)
   
Ruby中的变量可以是任何类型的。你无需关心变量的类型。所以变量检查的功能相对较弱。
变量不需要声明
   你可以在不声明变量的情况下直接使用它。变量的名称决定了它的作用范围(私有的,全局的或者实例变量等等)
简单的语法
   Ruby的语法很简单类似于Eiffel。
没有用户层的内存管理
  
Ruby提供自动垃圾回收机制,如果一个对象不在被使用就由内建的垃圾回收器进行处理回收。
一切都是对象
   Ruby从设计之初就是纯粹的面相对象语言。就连基本的数据类型比如整数都是对象。
类,继承,方法
     当然,作为一个面相对象的语言,Ruby具有类,继承和方法的特性。
独特的方法
     Ruby具有把为特定对象定义方法的特性。比如你可以为一个GUI按钮的点击按钮事件定义一个特定的方法。还可以用它进行原型库的编程(如果需要的话)。
用模块进行混合插入
   Ruby不支持多重继承。但拥有混合插入的功能。
迭代器
该功能可以将循环抽象化。
闭包
可以将某过程片段对象化。对象化后的该过程片段就称作闭包。
功能强大的字符串操作/正则表达式
以Perl为样板创造出了功能强大的字符串操作和正则表达式检索功能。
拥有超长整数
添加超长整数功能后,可以计算非常大的整数。例如计算400的阶乘也轻而易举。
具有错误处理功能
错误处理功能可以使您编写代码处理出错情况。
可以直接访问OS
Ruby可以使用(UNIX的)绝大部分的系统调用。单独使用Ruby也可以进行系统编程。
动态加载
若OS支持的话,可以在运行时读入对象文件。

Ruby 中文指南计划

最近想看看ruby 虽然自己用Java已经好多年了,而且对python也略知一二,但是ruby确一直一知半解,呵呵,准备最近花些时间把ruby的参考手册翻译一下,一方面自己学习,一方面也为中国的ruby爱好者提供一些资源。

Wednesday, August 16, 2006

Ruby 可爱的红宝石

可爱的红宝石,今天正式与你拥抱:)