阅读:5200次
评论:3条
更新时间:2011-05-26
本章最重要的话题是“变量存在哪里以及如何存储?”,也就是,数据结构。
第二重要的在于如何搜索值。Ruby变量和常量的范围相当复杂,因为变量和常量有时可以继承,有时可以在局部范围外看到……为了有个更好的理解,你应该先从行为上猜测一下其如何实现,然后与真正的实现进行对比。
第二重要的在于如何搜索值。Ruby变量和常量的范围相当复杂,因为变量和常量有时可以继承,有时可以在局部范围外看到……为了有个更好的理解,你应该先从行为上猜测一下其如何实现,然后与真正的实现进行对比。
本章提纲
Ruby的变量
Ruby中有相当多不同类型的变量和常量。让我们列举一下,从范围最大的开始。
* 全局变量
* 常量
* 类变量
* 实例变量
* 局部变量
实例变量已经在第2章《对象》中解释过了。在本章中,我们要讨论的是:
* 全局变量
* 类变量
* 常量
局部变量会在本书的第三部分讨论。
变量API
本章的分析目标是variable.c。我们先来看一下可用的API。
我们已经讨论过这些函数,但是必须再次提起它们,因为它们都在variable.c中。当然,它们是用来访问实例变量的。
这些函数是访问类变量的API。类变量直接属于类,因此函数需要用一个类作为参数。根据名字是以rb_Xv还是rb_Xvar开头,函数分为两组。差别在于变量“名”。稍短名字的函数通常更易用,因为它们以char*为参数。稍长名字的参数更多在内部使用,它们以ID为参数。
这些函数用于访问常量。常量属于类,因此它们要以类为为参数。 rb_const_get()会沿超类链查询,而rb_const_get_at()不会(它只查询klass)。
最后的这些函数用以访问全局变量。因为使用struct global_entry了,它们显得与众不同。我们会在描述实现的时候对此进行解释。
Ruby中有相当多不同类型的变量和常量。让我们列举一下,从范围最大的开始。
* 全局变量
* 常量
* 类变量
* 实例变量
* 局部变量
实例变量已经在第2章《对象》中解释过了。在本章中,我们要讨论的是:
* 全局变量
* 类变量
* 常量
局部变量会在本书的第三部分讨论。
变量API
本章的分析目标是variable.c。我们先来看一下可用的API。
VALUE rb_iv_get(VALUE obj, char *name) VALUE rb_ivar_get(VALUE obj, ID name) VALUE rb_iv_set(VALUE obj, char *name, VALUE val) VALUE rb_ivar_set(VALUE obj, ID name, VALUE val)
我们已经讨论过这些函数,但是必须再次提起它们,因为它们都在variable.c中。当然,它们是用来访问实例变量的。
VALUE rb_cv_get(VALUE klass, char *name) VALUE rb_cvar_get(VALUE klass, ID name) VALUE rb_cv_set(VALUE klass, char *name, VALUE val) VALUE rb_cvar_set(VALUE klass, ID name, VALUE val)
这些函数是访问类变量的API。类变量直接属于类,因此函数需要用一个类作为参数。根据名字是以rb_Xv还是rb_Xvar开头,函数分为两组。差别在于变量“名”。稍短名字的函数通常更易用,因为它们以char*为参数。稍长名字的参数更多在内部使用,它们以ID为参数。
VALUE rb_const_get(VALUE klass, ID name) VALUE rb_const_get_at(VALUE klass, ID name) VALUE rb_const_set(VALUE klass, ID name, VALUE val)
这些函数用于访问常量。常量属于类,因此它们要以类为为参数。 rb_const_get()会沿超类链查询,而rb_const_get_at()不会(它只查询klass)。
struct global_entry *rb_global_entry(ID name) VALUE rb_gv_get(char *name) VALUE rb_gvar_get(struct global_entry *ent) VALUE rb_gv_set(char *name, VALUE val) VALUE rb_gvar_set(struct global_entry *ent, VALUE val)
最后的这些函数用以访问全局变量。因为使用struct global_entry了,它们显得与众不同。我们会在描述实现的时候对此进行解释。
类变量
类变量是属于类的变量。在Java或C++中,它们叫做静态变量。它们既可以从类中访问,也可以从实例中访问。但是“从实例中”或“从类中”,只是求值器可用的信息。我们此刻并不知道。因此,从C的层次上来看,它像是没有访问的范围。我们只关注这些变量存储的方式。
读取
获取类变量的函数是rb_cvar_get()和rb_cv_get()。名字稍长的函数以ID为参数,短一些的以char*为参数。因为以ID为参数看上去和内核部分更接近,我们来看一下。
▼ rb_cvar_get()
这个函数读取klass的一个类变量。
正如我之前所说,像rb_raise()这样的错误处理函数可以简单的忽略。这次出现的rb_name_error()是一个生成异常的函数,因此,基于同样的原因,也可以忽略它。在ruby中,你可以假设所有以_error结尾的函数都会产生异常。
去除所有这些之后,我们看到的是,沿着klass的超类链,在iv_tbl中搜索。在这里,你会说“什么?iv_tbl不是实例变量表吗?”事实上,类变量就是存在实例变量表中。
我们可以这么做,因为创建ID时,考虑的是变量的全名,包括前缀: rb_intern()对“@var”和“@@var”返回的是不同的ID。在Ruby的层次上,变量类型有前缀唯一确定,因此在Ruby中无法访问一个叫做@var的类变量。
读取
获取类变量的函数是rb_cvar_get()和rb_cv_get()。名字稍长的函数以ID为参数,短一些的以char*为参数。因为以ID为参数看上去和内核部分更接近,我们来看一下。
▼ rb_cvar_get()
1508 VALUE 1509 rb_cvar_get(klass, id) 1510 VALUE klass; 1511 ID id; 1512 { 1513 VALUE value; 1514 VALUE tmp; 1515 1516 tmp = klass; 1517 while (tmp) { 1518 if (RCLASS(tmp)->iv_tbl) { 1519 if (st_lookup(RCLASS(tmp)->iv_tbl,id,&value)) { 1520 if (RTEST(ruby_verbose)) { 1521 cvar_override_check(id, tmp); 1522 } 1523 return value; 1524 } 1525 } 1526 tmp = RCLASS(tmp)->super; 1527 } 1528 1529 rb_name_error(id,"uninitialized class variable %s in %s", 1530 rb_id2name(id), rb_class2name(klass)); 1531 return Qnil; /* not reached */ 1532 } (variable.c)
这个函数读取klass的一个类变量。
正如我之前所说,像rb_raise()这样的错误处理函数可以简单的忽略。这次出现的rb_name_error()是一个生成异常的函数,因此,基于同样的原因,也可以忽略它。在ruby中,你可以假设所有以_error结尾的函数都会产生异常。
去除所有这些之后,我们看到的是,沿着klass的超类链,在iv_tbl中搜索。在这里,你会说“什么?iv_tbl不是实例变量表吗?”事实上,类变量就是存在实例变量表中。
我们可以这么做,因为创建ID时,考虑的是变量的全名,包括前缀: rb_intern()对“@var”和“@@var”返回的是不同的ID。在Ruby的层次上,变量类型有前缀唯一确定,因此在Ruby中无法访问一个叫做@var的类变量。
常量
有些唐突,但是我希望你能记住struct RClass的成员。如果我们排除basic, struct RClass包括:
* VALUE super
* struct st_table *iv_tbl
* struct st_table *m_tbl
然后,考虑一下:
1. 常量属于类
2. 在struct RClass中,我们没有看到任何常量专用的表
3. 类变量和实例变量都在iv_tbl中
或许常量也……
赋值
rb_const_set()是一个为常量设置值的函数:它将类klass的常量id设为值val。
▼ rb_const_set()
mod_av_set()完成了所有艰苦的工作:
▼ mod_av_set()
这里你可以再次忽略警告检查(rb_raise(),rb_error_frozen()和rb_warn())。这是剩下的部分:
▼ mod_av_set() (只有重要的部分)
if (!RCLASS(klass)->iv_tbl) {
RCLASS(klass)->iv_tbl = st_init_numtable();
}
st_insert(RCLASS(klass)->iv_tbl, id, val);
我们现在可以确定,常量是在实例表中。这也就意味着,在struct RClass的iv_tbl中,下面这些东西混在一起:
1. 类自己的实例变量
2. 类变量
3. 常量
读取
我们现在知道了常量如何存储。这回,我们把目光转向常量如何工作。
rb_const_get()
我们现在来看一下rb_const_get(),一个读取常量的函数。这个函数从klass中返回id所引用的常量。
▼ rb_const_get()
一路上有许多代码。首先,我们至少该去除后半部分的rb_name_error()。在中间部分, mod_entry附近好像是对模块的特殊处理,我们也暂时去掉。函数化简为这样:
▼ rb_const_get (简化版)
现在应该很容易看懂了。这个函数沿着klass的超类链在iv_tbl中搜索常量。这就意味着:
唯一剩下的问题就是top_const_get()。这个函数仅限于rb_cObject调用。 top一定是“顶层”。先和你确认一下,顶层的类是Object。 “在定义C的类语句中,类成为C”,这等同于“顶层的类就是Object”。
因此,可以预期,top_const_get()是专门针对顶层进行的处理。
top_const_get()
我们来看看这个top_const_get函数。它查询id,把值写入klassp,然后返回。
▼ top_const_get()
rb_class_tbl在第四章《类与模块》中已经提到过了。它是一个表,用来存储已经定义的顶层类。比如内建类String或Array都登记在其中。因此,搜索顶层变量时,不要忘了这个表。
下一块同自动加载相关。这允许我们在第一次访问顶层常量时自动加载一个库。比如这样使用:
之后,第一次访问VeryBigClass时,就会加载verybigclass(以require方式)。只要VeryBigClass定义在库中,就可以顺利的执行。当库特别大,需要很长时间加载时,这种方式就显得很高效了。
这种自动加载由rb_autoload_xxxx()完成。我们就不在本章进一步讨论自动加载了,因为很快它的工作方式就会发生巨大的改变(译注:自动加载确实在1.8中发生了改变:自动加载的常量不再需要定义在顶层了)。
其它的类?
对了,查询其它类常量的代码到底在哪?毕竟,常量的查询应该先从外面的类开始,然后才是超类。
实际上,对此,我们还没有足够的知识。外部类的改变依赖于在程序中的位置。总之,它与程序上下文相关。因此,我们先来理解求值器的内部状态。特别是, eval.c中的ev_const_get()函数,它完成在其它类中的搜索。常量故事的结局会在本书的第三部分中出现。
* VALUE super
* struct st_table *iv_tbl
* struct st_table *m_tbl
然后,考虑一下:
1. 常量属于类
2. 在struct RClass中,我们没有看到任何常量专用的表
3. 类变量和实例变量都在iv_tbl中
或许常量也……
赋值
rb_const_set()是一个为常量设置值的函数:它将类klass的常量id设为值val。
▼ rb_const_set()
1377 void 1378 rb_const_set(klass, id, val) 1379 VALUE klass; 1380 ID id; 1381 VALUE val; 1382 { 1383 mod_av_set(klass, id, val, Qtrue); 1384 } (variable.c)
mod_av_set()完成了所有艰苦的工作:
▼ mod_av_set()
1352 static void 1353 mod_av_set(klass, id, val, isconst) 1354 VALUE klass; 1355 ID id; 1356 VALUE val; 1357 int isconst; 1358 { 1359 char *dest = isconst ? "constant" : "class variable"; 1360 1361 if (!OBJ_TAINTED(klass) && rb_safe_level() >= 4) 1362 rb_raise(rb_eSecurityError, "Insecure: can't set %s", dest); 1363 if (OBJ_FROZEN(klass)) rb_error_frozen("class/module"); 1364 if (!RCLASS(klass)->iv_tbl) { 1365 RCLASS(klass)->iv_tbl = st_init_numtable(); 1366 } 1367 else if (isconst) { 1368 if (st_lookup(RCLASS(klass)->iv_tbl, id, 0) || 1369 (klass == rb_cObject && st_lookup(rb_class_tbl, id, 0))) { 1370 rb_warn("already initialized %s %s", dest, rb_id2name(id)); 1371 } 1372 } 1373 1374 st_insert(RCLASS(klass)->iv_tbl, id, val); 1375 } (variable.c)
这里你可以再次忽略警告检查(rb_raise(),rb_error_frozen()和rb_warn())。这是剩下的部分:
▼ mod_av_set() (只有重要的部分)
if (!RCLASS(klass)->iv_tbl) {
RCLASS(klass)->iv_tbl = st_init_numtable();
}
st_insert(RCLASS(klass)->iv_tbl, id, val);
我们现在可以确定,常量是在实例表中。这也就意味着,在struct RClass的iv_tbl中,下面这些东西混在一起:
1. 类自己的实例变量
2. 类变量
3. 常量
读取
我们现在知道了常量如何存储。这回,我们把目光转向常量如何工作。
rb_const_get()
我们现在来看一下rb_const_get(),一个读取常量的函数。这个函数从klass中返回id所引用的常量。
▼ rb_const_get()
1156 VALUE 1157 rb_const_get(klass, id) 1158 VALUE klass; 1159 ID id; 1160 { 1161 VALUE value, tmp; 1162 int mod_retry = 0; 1163 1164 tmp = klass; 1165 retry: 1166 while (tmp) { 1167 if (RCLASS(tmp)->iv_tbl && st_lookup(RCLASS(tmp)->iv_tbl,id,&value)) { 1168 return value; 1169 } 1170 if (tmp == rb_cObject && top_const_get(id, &value)) return value; 1171 tmp = RCLASS(tmp)->super; 1172 } 1173 if (!mod_retry && BUILTIN_TYPE(klass) == T_MODULE) { 1174 mod_retry = 1; 1175 tmp = rb_cObject; 1176 goto retry; 1177 } 1178 1179 /* Uninitialized constant */ 1180 if (klass && klass != rb_cObject) { 1181 rb_name_error(id, "uninitialized constant %s at %s", 1182 rb_id2name(id), 1183 RSTRING(rb_class_path(klass))->ptr); 1184 } 1185 else { /* global_uninitialized */ 1186 rb_name_error(id, "uninitialized constant %s",rb_id2name(id)); 1187 } 1188 return Qnil; /* not reached */ 1189 } (variable.c)
一路上有许多代码。首先,我们至少该去除后半部分的rb_name_error()。在中间部分, mod_entry附近好像是对模块的特殊处理,我们也暂时去掉。函数化简为这样:
▼ rb_const_get (简化版)
VALUE rb_const_get(klass, id) VALUE klass; ID id; { VALUE value, tmp; tmp = klass; while (tmp) { if (RCLASS(tmp)->iv_tbl && st_lookup(RCLASS(tmp)->iv_tbl,id,&value)) { return value; } if (tmp == rb_cObject && top_const_get(id, &value)) return value; tmp = RCLASS(tmp)->super; } }
现在应该很容易看懂了。这个函数沿着klass的超类链在iv_tbl中搜索常量。这就意味着:
class A Const = "ok" end class B < A p(Const) # 可以被访问 end
唯一剩下的问题就是top_const_get()。这个函数仅限于rb_cObject调用。 top一定是“顶层”。先和你确认一下,顶层的类是Object。 “在定义C的类语句中,类成为C”,这等同于“顶层的类就是Object”。
# 顶层的类是`Object` class A # 类是A class B # 类是B end end
因此,可以预期,top_const_get()是专门针对顶层进行的处理。
top_const_get()
我们来看看这个top_const_get函数。它查询id,把值写入klassp,然后返回。
▼ top_const_get()
1102 static int 1103 top_const_get(id, klassp) 1104 ID id; 1105 VALUE *klassp; 1106 { 1107 /* pre-defined class */ 1108 if (st_lookup(rb_class_tbl, id, klassp)) return Qtrue; 1109 1110 /* autoload */ 1111 if (autoload_tbl && st_lookup(autoload_tbl, id, 0)) { 1112 rb_autoload_load(id); 1113 *klassp = rb_const_get(rb_cObject, id); 1114 return Qtrue; 1115 } 1116 return Qfalse; 1117 } (variable.c)
rb_class_tbl在第四章《类与模块》中已经提到过了。它是一个表,用来存储已经定义的顶层类。比如内建类String或Array都登记在其中。因此,搜索顶层变量时,不要忘了这个表。
下一块同自动加载相关。这允许我们在第一次访问顶层常量时自动加载一个库。比如这样使用:
autoload(:VeryBigClass, "verybigclass") # VeryBigClass定义在其中
之后,第一次访问VeryBigClass时,就会加载verybigclass(以require方式)。只要VeryBigClass定义在库中,就可以顺利的执行。当库特别大,需要很长时间加载时,这种方式就显得很高效了。
这种自动加载由rb_autoload_xxxx()完成。我们就不在本章进一步讨论自动加载了,因为很快它的工作方式就会发生巨大的改变(译注:自动加载确实在1.8中发生了改变:自动加载的常量不再需要定义在顶层了)。
其它的类?
对了,查询其它类常量的代码到底在哪?毕竟,常量的查询应该先从外面的类开始,然后才是超类。
实际上,对此,我们还没有足够的知识。外部类的改变依赖于在程序中的位置。总之,它与程序上下文相关。因此,我们先来理解求值器的内部状态。特别是, eval.c中的ev_const_get()函数,它完成在其它类中的搜索。常量故事的结局会在本书的第三部分中出现。
全局变量
总论
全局变量可以在任何地方访问。反过来说,没有必要限制对它们的访问。因为它们与上下文无关,有个地方用来存放表就可以,没有必要做任何检查。因此,实现非常简单。
尽管如此,仍有一大堆代码。原因是全局变量与普通变量有很大差异。下面的功能只用于全局变量。
* 你可以对全局变量的访问设置钩子
* 你可以用alias为它们起个别名
我们来简单解释一下。
变量的别名
随后,你就可以用$newname代替$oldname。
变量的alias主要是为了“符号变量”而准备的。“符号变量”是一种源自Perl的变量,比如$=或$0。$=用以确定字符串比较是是否区分大小写。$0表示Ruby主程序的名字。还有一些其它的符号变量,但它们的名字只有一个字符长,对于不了解Perl的人来说,它们难于记忆。因此才创建了别名,让它们好记一点。
现在不推荐使用符号变量,它们也一个个的移到适当模块的singleton方法中。现在的氛围是2.0会废弃$=和其它符号变量。
钩子
你可以对全局变量的读写设置“钩子”。
钩子可以在Ruby的层次上设置,但是我一直在想:为什么不在C层次上看看系统用的特殊变量,比如$KCODE?$KCODE是一个包含了编码信息的变量,解释器用它来处理字符串。它只能设置为特殊的值,比如"EUC"或"UTF8"。但是这很讨厌,因此,也可以设为"e"或"u"。
我想如果能够对全局变量的赋值设置钩子,就能更好的理解这里的所做的。顺便说一下, $KCODE的K来自“kanji”(日语中中文字符的名字)。
你也许会说,即便有了alias或是钩子,全局变量也不要用得那么多,所以,它的功能没多大关系。不用的函数就让它适当的结束,我还需要篇幅分析解析器和求值器。因此,下面的讨论中我抛弃一些不重要的东西。
数据结构
看变量结构的时候,我说过,它们的存储方式很重要。希望你可以好好掌握全局变量的结构。
▼ 全局变量的数据结构
rb_global_tbl是主要的表。所有的全局变量都存在这个表里。这个表的键值当然就是变量名(ID)。值由struct global_entry和struct global_variable表示。(图1)
图1: 执行时的全局变量表
表示变量的结构分为两个部分,以便创建别名。如果创建了别名,两个global_entry会指向同一个struct global_variable。
这里,引用计数器(struct global_variable的counter)是必需的。我在前一章《垃圾回收》中解释过引用计数的概念。简单回顾一下,生成新的引用时,计数器加1。引用不再使用时,计数器减1。计数器为0时,结构不再使用,调用free()。
如果在Ruby的层次上设置了钩子,就有一个struct trace_var列表存在struct global_variable 的trace成员中,这个就不说了,省略struct trace_var。
读取
只要看一下如何读取全局变量,就可以对全局变量有个大概的认识。读取的函数是rb_gv_get()和rb_gvar_get()。
▼ rb_gv_get() rb_gvar_get()
实际的内容都在rb_global_entry()中,但这并不妨碍我们理解。 global_id函数将char*转成ID,并检查它是否是全局变量的ID。 (*var->getter)(...)当然就是使用函数指针var->getter进行的函数调用。如果p是一个函数指针,(*p)(arg)就在调用函数。
剩下的主要部分还是rb_global_entry()。
▼ rb_global_entry()
主要处理由开始的st_lookup()完成。随后只是创建(和存储)了一个新项。访问的项不存在时,便自动创建一项,rb_global_entry()不会返回NULL。
这主要是考虑速度。解析器找到一个全局变量时,它得到是相应的struct global_entry。读取变量值时,解析器就是取该项的值(用rb_gv_get()),不必做任何检查。
让我们稍微前进一步,接下来的代码,var->getter等几个设为undef_xxxx。对于当前未定义的全局变量,其setter/getter/marker用undef表示。
即便读取未定义的全局变量,undef_getter()也只是显示一个警告,返回nil。 undef_setter()则稍微有趣一些,来看一下。
▼ undef_setter()
val_getter()得到entry->data的值,返回它。val_getter()只是把值放到entry->data中。这样设置处理器使我们不必对为定义变量进行特殊处理(图2)。很有技巧,不是吗?
图2: 全局变量的设置和访问
全局变量可以在任何地方访问。反过来说,没有必要限制对它们的访问。因为它们与上下文无关,有个地方用来存放表就可以,没有必要做任何检查。因此,实现非常简单。
尽管如此,仍有一大堆代码。原因是全局变量与普通变量有很大差异。下面的功能只用于全局变量。
* 你可以对全局变量的访问设置钩子
* 你可以用alias为它们起个别名
我们来简单解释一下。
变量的别名
alias $newname $oldname
随后,你就可以用$newname代替$oldname。
变量的alias主要是为了“符号变量”而准备的。“符号变量”是一种源自Perl的变量,比如$=或$0。$=用以确定字符串比较是是否区分大小写。$0表示Ruby主程序的名字。还有一些其它的符号变量,但它们的名字只有一个字符长,对于不了解Perl的人来说,它们难于记忆。因此才创建了别名,让它们好记一点。
现在不推荐使用符号变量,它们也一个个的移到适当模块的singleton方法中。现在的氛围是2.0会废弃$=和其它符号变量。
钩子
你可以对全局变量的读写设置“钩子”。
钩子可以在Ruby的层次上设置,但是我一直在想:为什么不在C层次上看看系统用的特殊变量,比如$KCODE?$KCODE是一个包含了编码信息的变量,解释器用它来处理字符串。它只能设置为特殊的值,比如"EUC"或"UTF8"。但是这很讨厌,因此,也可以设为"e"或"u"。
p($KCODE) # "NONE" (缺省) $KCODE = "e" p($KCODE) # "EUC" $KCODE = "u" p($KCODE) # "UTF8"
我想如果能够对全局变量的赋值设置钩子,就能更好的理解这里的所做的。顺便说一下, $KCODE的K来自“kanji”(日语中中文字符的名字)。
你也许会说,即便有了alias或是钩子,全局变量也不要用得那么多,所以,它的功能没多大关系。不用的函数就让它适当的结束,我还需要篇幅分析解析器和求值器。因此,下面的讨论中我抛弃一些不重要的东西。
数据结构
看变量结构的时候,我说过,它们的存储方式很重要。希望你可以好好掌握全局变量的结构。
▼ 全局变量的数据结构
21 static st_table *rb_global_tbl; 334 struct global_entry { 335 struct global_variable *var; 336 ID id; 337 }; 324 struct global_variable { 325 int counter; /* 引用计数 */ 326 void *data; /* 变量值 */ 327 VALUE (*getter)(); /* 取值函数 */ 328 void (*setter)(); /* 设置函数 */ 329 void (*marker)(); /* 标记函数 */ 330 int block_trace; 331 struct trace_var *trace; 332 }; (variable.c)
rb_global_tbl是主要的表。所有的全局变量都存在这个表里。这个表的键值当然就是变量名(ID)。值由struct global_entry和struct global_variable表示。(图1)
图1: 执行时的全局变量表
表示变量的结构分为两个部分,以便创建别名。如果创建了别名,两个global_entry会指向同一个struct global_variable。
这里,引用计数器(struct global_variable的counter)是必需的。我在前一章《垃圾回收》中解释过引用计数的概念。简单回顾一下,生成新的引用时,计数器加1。引用不再使用时,计数器减1。计数器为0时,结构不再使用,调用free()。
如果在Ruby的层次上设置了钩子,就有一个struct trace_var列表存在struct global_variable 的trace成员中,这个就不说了,省略struct trace_var。
读取
只要看一下如何读取全局变量,就可以对全局变量有个大概的认识。读取的函数是rb_gv_get()和rb_gvar_get()。
▼ rb_gv_get() rb_gvar_get()
716 VALUE 717 rb_gv_get(name) 718 const char *name; 719 { 720 struct global_entry *entry; 721 722 entry = rb_global_entry(global_id(name)); 723 return rb_gvar_get(entry); 724 } 649 VALUE 650 rb_gvar_get(entry) 651 struct global_entry *entry; 652 { 653 struct global_variable *var = entry->var; 654 return (*var->getter)(entry->id, var->data, var); 655 } (variable.c)
实际的内容都在rb_global_entry()中,但这并不妨碍我们理解。 global_id函数将char*转成ID,并检查它是否是全局变量的ID。 (*var->getter)(...)当然就是使用函数指针var->getter进行的函数调用。如果p是一个函数指针,(*p)(arg)就在调用函数。
剩下的主要部分还是rb_global_entry()。
▼ rb_global_entry()
351 struct global_entry* 352 rb_global_entry(id) 353 ID id; 354 { 355 struct global_entry *entry; 356 357 if (!st_lookup(rb_global_tbl, id, &entry)) { 358 struct global_variable *var; 359 entry = ALLOC(struct global_entry); 360 st_add_direct(rb_global_tbl, id, entry); 361 var = ALLOC(struct global_variable); 362 entry->id = id; 363 entry->var = var; 364 var->counter = 1; 365 var->data = 0; 366 var->getter = undef_getter; 367 var->setter = undef_setter; 368 var->marker = undef_marker; 369 370 var->block_trace = 0; 371 var->trace = 0; 372 } 373 return entry; 374 } (variable.c)
主要处理由开始的st_lookup()完成。随后只是创建(和存储)了一个新项。访问的项不存在时,便自动创建一项,rb_global_entry()不会返回NULL。
这主要是考虑速度。解析器找到一个全局变量时,它得到是相应的struct global_entry。读取变量值时,解析器就是取该项的值(用rb_gv_get()),不必做任何检查。
让我们稍微前进一步,接下来的代码,var->getter等几个设为undef_xxxx。对于当前未定义的全局变量,其setter/getter/marker用undef表示。
即便读取未定义的全局变量,undef_getter()也只是显示一个警告,返回nil。 undef_setter()则稍微有趣一些,来看一下。
▼ undef_setter()
385 static void 386 undef_setter(val, id, data, var) 387 VALUE val; 388 ID id; 389 void *data; 390 struct global_variable *var; 391 { 392 var->getter = val_getter; 393 var->setter = val_setter; 394 var->marker = val_marker; 395 396 var->data = (void*)val; 397 } (variable.c)
val_getter()得到entry->data的值,返回它。val_getter()只是把值放到entry->data中。这样设置处理器使我们不必对为定义变量进行特殊处理(图2)。很有技巧,不是吗?
图2: 全局变量的设置和访问
3 楼 jactive 2012-05-26 22:28
2 楼 weilJava 2010-11-27 08:33
偶是初学者,只想知道实例变量和类变量的区别
最近在写自动化测试的时候出现一个小问题,如下
class TempClass < Watir::TestCase
@a=TempClass2.new()
def test
@a...
end
end
另外一个
class TempClass < Watir::TestCase
def test_init
@@a=TempClass2.new()
end
def test
@@a...
end
end
以上两种功能是相同的,我只想知道那种使用更常见一些,相对来说有什么好处
作为一个java程序员很肯定的会选第一种,但是第二种又没发现没什么不好的,
我是刚接触ruby,他们之前写的测试代码里面好多地方都是用的后者
1 楼 airacle 2008-12-25 22:35