为了解决老贾提出的,弄清楚为何继承的子类继承了类变量而没有继承实例变量的原因,需要弄清楚实例变量和类变量的存取方式。下面是我简单的一些分析,希望大家多多斧正。
类和对象的存储方式
对象的结构在 mateclass 的讲解中有了简单的介绍,下面是对象的结构体的示意图 1-1 :
图 1-1 ruby 对象的结构体
每个结构体上面都是有 Struct RBasic 结构,定义如下图 1-2 :
图 1-2 Struct RBasic 结构体
接着看下类的结构,定义如图 1-3 所示
图 1-3 RClass 的结构体
其中 iv_tbl 为实例变量表, m_tbl 为方法表, super 指向父类的指针
实例变量的设置与获取
下面是存储实例变量的代码图 1-4 ,主要看红色的部分。如果对象的类型是 Object , Class , Module ,如果实例变量表不存在,则创建,否则将变量的 ID 和 Value 插入到实例变量表中。
图 1-4 实例变量的存储方式
接下来看下实例变量是如何获取的如图 1-5 。注意我下面标注红色的部分,此处, 如果对象的类型是 Object , Class , Module ,则搜索实例变量表中的实例变量,找到返回,终止 switch 。未找到返回 nil 。
图 1-5 实例变量的获取方式
当然这里的实例变量表,如果是struct RClass
,那么就代表这
是一个类对象,这个实例变量表是用于类对象本身。
下面我们分析为何下面的代码无法获得预期效果
实例变量在执行 new 后,生成了一个新的对象,我们暂且叫它 obj ,这里 obj 其实本身有自己的实例变量 @a ,当我们调用 obj.a 方法的时候, obj 首先找他的类里的方法 a , a 返回的是实例变量,这里的实例变量其实是 obj 本身的实例变量,如图 1-6 所示。
图 1-6 类与对象的实例变量
因此,对于上面的代码执行 @a = “ abc ”操作后,其实 Struct RClass 中实现了 @a = “ abc ”并没有改变 Struct Robject 实例变量。所以通过 a 方法调用的值会为 nil 。
所以下面的代码也就很好分析出了
输出结构是 abc ,因为调用自身的实例变量。
下一个问题,为什么采用了 Initialize 后就可以实现初始化呢,我的理解是这样子的,首先类调用 new ,在 new 里做了什么动作呢,依据面向对象的经验,估计是先做了一些内存分配的任务,内存分配完成实际上对象已经存在了,接着调用 Initialize ,此时的该方法应该是在类中,查找类的 Initialize 方法,初始化本身的实例变量。所以可以实现对实例变量的初始化。
类变量的存储方式
看下面的代码图1-7 是获取类变量的函数,同样注意红色的部分while 一重循环,下面查找RClass 结构中的iv_tbl ,如果找到返回找到的值,未找到则有 tmp = CLASS(tmp)->super ,指向父类继续寻找。 因为创建 ID
时,考虑的是变量的全名,包括前缀: rb_intern()
对“ @var
” 和“ @@var
” 返回的是不同的 ID
,因此不可能把@var 当成了类变量的情况。
细心的你,一定会发现,类变量存储在了类的实例变量表里,其实正是如此。
图 1-7 类变量的获取函数
至此,我们已经知道了为何下面的类变量是可以继承,而实例变量无法继承了,因为实例变量没有回溯父类查找( tmp = CLASS(tmp)->super ),只是查找了对象或类对象本身。
因此解释了老贾的第二个问题。