看了一篇文章http://www.cnblogs.com/anytao/archive/2007/09/10/must_net_15.html
看完还是晕晕的说的很是不详细,
有没有哪个高人,能详细解释下编译运行的角度来解释继承,多态
包括用new隐藏父类方法和 用重写父类的抽象方法等不同情况时内存的变化,或说是指针的变化
谢谢了
看完还是晕晕的说的很是不详细,
有没有哪个高人,能详细解释下编译运行的角度来解释继承,多态
包括用new隐藏父类方法和 用重写父类的抽象方法等不同情况时内存的变化,或说是指针的变化
谢谢了
如这张图所示,new隐藏父类方法和 用重写父类的抽象方法等不同情况时 的变化
下面首先以一个简单的动物继承体系为例,来进行说明: public abstract class Animal { public abstract void ShowType(); public void Eat() { Console.WriteLine("Animal always eat."); } } public class Bird: Animal { private string type = "Bird"; public override void ShowType() { Console.WriteLine("Type is {0}", type); } private string color; public string Color { get { return color; } set { color = value; } } } public class Chicken : Bird { private string type = "Chicken"; public override void ShowType() { Console.WriteLine("Type is {0}", type); } public void ShowColor() { Console.WriteLine("Color is {0}", Color); } }
然后,在测试类中创建各个类对象,由于Animal为抽象类,我们只创建Bird对象和Chicken对象。 public class TestInheritance { public static void Main() { Bird bird = new Bird(); Chicken chicken = new Chicken(); } }
下面我们从编译角度对这一简单的继承示例进行深入分析,从而了解.NET内部是如何实现我们强调的继承机制。 (1)我们简要的分析一下对象的创建过程: Bird animal = new Bird();Bird bird创建的是一个Bird类型的引用,而new Bird()完成的是创建Bird对象,分配内存空间和初始化操作,然后将这个对象赋给bird引用,也就是建立bird引用与Bird对象的关联。 (2)我们从继承的角度来分析在编译器编译期是如何执行对象的创建过程,因为继承的本质就体现于对象的创建过程。 在此我们以Chicken对象的创建为例,首先是字段,对象一经创建,会首先找到其父类Bird,并为其字段分配存储空间,而Bird也会继续找到其父类Animal,为其分配存储空间,依次类推直到递归结束,也就是完成System.Object内存分配为止。我们可以在编译器中单步执行的方法来大致了解其分配的过程和顺序,因此,对象的创建过程是按照顺序完成了对整个父类及其本身字段的内存创建,并且字段的存储顺序是由上到下排列,object类的字段排在最前面,其原因是如果父类和子类出现了同名字段,则在子类对象创建时,编译器会自动认为这是两个不同的字段而加以区别。 然后,是方法表的创建,必须明确的一点是方法表的创建是类第一次加载到CLR时完成的,在对象创建时只是将其附加成员TypeHandle指向方法列表在Loader Heap上的地址,将对象与其动态方法列表相关联起来,因此方法表是先于对象而存在的。类似于字段的创建过程,方法表的创建也是父类在先子类在后,原因是显而易见的,类Chicken生成方法列表时,首先将Bird的所有方法拷贝一份,然后和Chicken本身的方法列表做以对比,如果有覆写的虚方法则以子类方法覆盖同名的父类方法,同时添加子类的新方法,从而创建完成Chicken的方法列表。这种创建过程也是逐层递归到Object类,并且方法列表中也是按照顺序排列的,父类在前子类在后,其原因和字段大同小异,留待读者自己体味。
.重写(Override)与虚方法调用
在上述隐藏的示例中,由于子类隐藏了父类的同名方法,如果不进行强制转换,就无法通过父类变量直接调用子类的同名方法,哪怕父类变量引用的是子类对象。这是不太合理的。我们希望每个对象都只干自己职责之内的事,即如果父类变量引用的是子类对象,则调用的就是子类定义的方法;而如果父类变量引用的就是父类对象,则调用的是父类定义的方法。这就是说,希望每个对象都“各人自扫门前雪,莫管他人瓦上霜”。为达到这个目的,可以在父类同名方法前加关键字virtual,表明这是一个虚方法,子类可以重写此方法(即在子类同名方法前加关键字override),表明对父类同名方法进行了重写。及和new
3种情况时 内存 指针的的变化
例如父类的静态构造器依次压入方法体A()B()C(),子类的静态构造器继续压入方法体D()E()F(),这样子类的对象体所包含的父类对象体的方法指针指向C(),而子类对象的方法体指针指向F()。一旦需要把子类对象当作子类使用,调用方法时,就会从指向F()的指针依次往栈顶寻找方法,甚至找到A()方法。
一旦需要把子类对象当作父类使用,调用方法时,就会从指向C()的指针依次往栈顶寻找方法,不会寻找D()E()F()。假如B()方法声明为允许被重写。而子类中用了override B(),这样子类的静态构造器就会往堆栈顶端寻找,找到父类已经初始化好的B()方法,将它抹掉,改写成自己的B(),这样,不论以父类的身份从C()开始找B(),还是以子类的身份从F()开始找B(),都会执行被子类改写的B()方法。假如子类中用了new B(),这样子类的静态构造器就不会往堆栈顶端寻找已经存在的B(),而是在方法体堆栈内新初始化一个B(),当然这个B()在父类初始化的A()B()C()之后,形成 A()B()C()/界线/B()D()E()F() 这样的堆栈。一旦需要把子类对象当作子类使用,调用方法时,就会从指向F()的指针依次往栈顶寻找方法,找到第一个找到的 B()方法。
一旦需要把子类对象当作父类使用,调用方法时,就会从指向C()的指针依次往栈顶寻找方法,找到第一个找到的 B()方法。所以看似用new会阻断继承。