OO 中的继承分析:主要分析在编译和运行过程中 子类、父类 的字段和方法以及实例化时候在内存中分配 和 执行的先后,以及两个原则

首先给出部分代码,由此来分析一下运行过程中对象、字段的创建过程,和编译过程中方法列表的创建过程。

View Code
public class Animal
{
public virtual void Eat()
{
Console.WriteLine(
"Animal Eat");
}

public virtual void Sleep()
{
Console.WriteLine(
"Animal Sleep");
}

public void Play()
{
Console.WriteLine(
"Animal Play");
}

}

public class Cat : Animal
{
public override void Eat()
{
base.Eat();//调用父类方法
Console.WriteLine("Cat Eat");
}

public override void Sleep()
{
Console.WriteLine(
"Cat Sleep");
base.Sleep();
}

}

以上代码 如果在Main方法中 通过 Animal a = new Cat(); 来实现一个父类引用子类对象。

这句话首先是创建了一个Animal类型的a的引用,然后 new Cat();创建了一个Cat的对象,最后把这个a这个引用指向了
              new Cat();这个对象的地址。在这个对象创建的过程中其实有很多步骤
             
              首次访问:(在此没有显示的写出类中的构造方法)
                   顺序:子类的静态字段==》子类静态构造==》子类非静态字段==》父类的静态字段==》父类的静态构造==》父类的非静态字段
                             ==》父类的构造函数==》子类的构造函数
                         
              非首次访问:顺序是一样的,只不过少了中间静态字段和构造的过程
              
             这个过程依次类推直到递归到Object结束(在次过程中也是依次给父类分配内存的过程),且字段的在内存中的存储顺序是由上到下排列,object类的字段  排在最前面,
              原因是如果父类和子类出现了同名字段,则在子类对象创建时,编译器会自动认为这是两个不同的字段而加以区别。 
              
              说了对象的创建,其次是方法列表的创建:
              方法列表的创建是在编译时创建的,而对象的创建是在运行时,对象的创建是为了给方法列表一个引用的指针,使其它们动态关联起来。
              方法列表的创建顺序跟字段的的顺序是一样的,也是先父类后子类。(override 和 new 的不同 new主要是会阻断继承树,和隐藏父类方法,创建子类和父类同名的方法
              父先子后的原因是:在编译时创建方法列表的过程是,先生成父类的方法列表,而后在生成子类的方法列表的时候,会把父类的方法复制一份
              出来,然后拿子类的方法去和父类的比较,如果发现同名的方法,则看子类的方法修饰符是override 还是 new,如果是override 则覆盖父类
              同名的方法(以上所说的父类方法皆是virtual方法,并且这里说的覆盖只是说覆盖方法的实现,而并没有覆盖父类的方法列表,通过base.父类方法名还是可以调用父类的方法),
              如果是new  则在内存中的不同位置创建一个同名的方法。 不同名的,则直接创建。

     完成之后,我们可以通过 a这个引用来来调用Cat中的方法。

1.思考:如果把上例中Animal的play方法移到Cat中,在Main方法中打算通过a.Paly();来调用子类的Paly方法会发生什么现象? 会编译不通过,为什么呢?

按理说子类就是用来扩展父类的,理论上也允许子类有自己的特性啊(方法、字段……)。但问题不是出在子类,而是出在了调用的位置,不能通过a.Play();来调用这个方法,可能大家又不解了,会想 a就是通过 new Cat();这个对象啊出来的,为什么不能调用自己的方法勒,一层层的,最终我们找到原因是在 Animal a 这个申明引用的位置。

在此要引入OO 的一个原则: 关注对象原则——调用子类还是父类的方法,取决于创建的对象是子类对象还是父类对象,而不是它的引用类型,而引用指针类型不同的区别决定了不同的对象在方法表中不同的访问权限。

由此结合:子类可以调用父类方法和字段,而父类不能调用子类方法和字段  这个概念。就可以知道原因了。

在说说OO中的另一个原则:就近原则——对于同名字段或者方法,编译器是按照其顺序查找来引用的,也就是首先访问离它创建最近的字段或者方法。先贴一段代码,然后通过代码来分析。

View Code
class Program
{
static void Main(string[] args)
{
Animal a
= new Cat();
//MemberInfo[] m = a.GetType().GetMembers();
//foreach (var item in m)
//{
// Console.WriteLine(item.Name);
//}
//a.Eat();
//a.Play();
//a.Sleep();
Console.WriteLine(a.AnimalName);
}
}

public class Animal
{
public string AnimalName = "Animal";
}

public class Cat : Animal
{
public string AnimalName = "Cat";
}

在代码的Main方法中  Animal a = new Cat();  这个A是Animal 类型的,结合文章开始将的 在编译和运行过程中 子类、父类 的字段和方法以及实例化时候在内存中分配的先后位置可以得出:Animal 类中的 AnimalName 在内存中的位置一定位于 Cat中AnimalName在内存中的位置的前面,根据就近原则打印出的应该是Animal。

以上文章大致概括了在继承过程中的 在编译和运行过程中 子类、父类 的字段和方法以及实例化时候在内存中分配 和 执行的先后,以及两个原则,如有错误或者不足的地方请拍砖。后续后更深入的学习oo中的其它内容。

 

posted @ 2011-04-26 23:13  wxifly  阅读(1788)  评论(3编辑  收藏  举报