执行
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。然后生成该对象的特殊类(若尚未生成的话)。最后生成新框架,向顶层块的self和class中设定新生成的特殊类。在新框架的基础上对定义句中的表达式进行计算。
特殊类定义句的值取决于类的内容中的最后一个表达式的值。若没有可计算的表达式时,其值为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的话,块将被忽视,不会执行。
将块传给方法时,该块将会继承调用方的块的self和class。只有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)
- 执行方法
- 类/模块的定义句
- BEGIN和END句
生成框架时自动搭载一个块,因此可以在这些语句中使用局部变量。
编译时,写入程序代码的局部变量将赋值给框架中的尚未定义的局部变量。局部变量被赋值时所在的块就是它的归属块。由此可知,编译时局部变量的定义过程已经完成(请注意,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。