继承与多态性的理解
| |
|
Bird bird2创建的是一个Bird类型的引用,而new Bird()完成的是创建Bird对象,分配内存空间和初始化操作,然后将这个对象赋给bird引用,也就是建立bird引用与Bird对象的关联。
在bird2为Bird类型的指针,指向的是Chicken的实例,指向的是Chicken的方法表,而且在类Chicken中的showtype()方法Override了Bird类中的showtype()方法,所以当bird2.showtype()调用时,调用的是该override的方法,输出的是
type is Chicken
PS:关注对象原则与就近原则应该结合使用,不可套句使用。否则很容易陷入含糊不清的陷阱;
例如,还是Bird bird3 = new Chicken()
bird3.showtype()
会有怎么样的输出呢
如果按照关注对象原则的话,那new的对象是Chicken,那么按照这样使用的话应该是type is chicken咯。
如果按照就近原则的话,那么bird3是Bird类型,那么按照该使用方式则是type is Bird咯。
那么该怎么分析:
关于这一点,anytao有解答,我直接copy出:
呵呵,其实这一块的确比较难于理解,不过我认为并不需要对“执行就近”和“关注对象”这两个名词弄迷惑,这两个词语算是我自己的创造,其他书上应该看不到这种说法,所以是非“官方”的。本来的目的是想通过两个名词利于理解,反倒让你迷惑了。
具体分析吧:
首先: Bird bird2 = new Chicken();
根据分析可知,bird2是一个指向内存布局为Chicken,但是本身为Bird引用,这就设计到从“关注对象”和“执行就近”两方面来分析。因此,必须重申关于方法表创建过程的描述(来自正文):
然后,是方法表的创建,必须明确的一点是方法表的创建是类第一次加载到CLR时完成的,在对象创建时只是将其附加成员TypeHandle指向方法列表在Loader Heap上的地址,将对象与其动态方法列表相关联起来,因此方法表是先于对象而存在的。类似于字段的创建过程,方法表的创建也是父类在先子类在后,原因是显而易见的,类Chicken生成方法列表时,首先将Bird的所有方法拷贝一份,然后和Chicken本身的方法列表做以对比,如果有覆写的虚方法则以子类方法覆盖同名的父类方法,同时添加子类的新方法,从而创建完成Chicken的方法列表。这种创建过程也是逐层递归到Object类,并且方法列表中也是按照顺序排列的,父类在前子类在后,其原因和字段大同小异,留待读者自己体味。
可见,在创建方法表时,如果该方法实现为虚方法,则子类方法将覆盖父类方法。所以,具体是执行Bird.showType还是Chicken.showType要看具体的内存布局。结合文章的实例和示例,我想你能得道准确的答案,不必局限于这两个名词:-)
好,这部分问题基本已经结束,但问题还是不断出现的,接着看:
public class Brid:Animal
{
public void showI()
{
console.writeline("stay in Brid class");
}
}
public class Chicken:Bird
{
public void showI()
{
console.writeline("stay in Chicken class");
}
}
public static void Mian()
{
Brid brid2 = new Chicken();
brid2.showI();
如果再次按照上面的思路来做的话,关注对象原则,那么很简单,Chicken对象,那么应该为
stay in Chicken class
然后实际输出为:stay in Brid class
为什么呢,难道你没有发现基类中没有virtual吗,子类中没有override对基类中方法的覆写吗,问题就处在这了:
其实,分别存在与基类和子类的2个同名函数,在子类中没有声明对其基类的覆写,那其实是相当于在子类的方法中添加了一个new的关键字:如public new void showl()
为什么呢?anytao在另外一文中又给出了解释:(牛啊!)
ShowInfo方法在基类Number中已经有定义,但不是虚方法,所以子类方法中如果定义了同样的名称的方法,从设计者的角度来看,这样做的目的不是重写父类的ShowInfo,因为不是虚方法。所以,只能是表示该方法ShowInfo是区别于父类的方法,你可以理解为ShowInfo2,虽然同名但是在编译器看来这两个方法是完全不同的两个方法。因此父类对象number在调用ShowInfo时,它调用的显然是Number::ShowInfo(),而子类对象intNumber在调用ShowInfo时,调用的就是IntNumber::ShowInfo()。
这就是new关键字作为隐藏基类方法时的作用。.NET默认就是提供了new的,因此在子类中如果没有显式指明,会给出警告同时编译器会自动加上new来隐藏基类的同名方法。
另外,如果在基类的ShowInfo被定义为virtual,而在子类中如果想隐藏而不是覆写父类方法,则还是使用new关键字来实现,并且执行结果是同样的,你可以试试看。(此处的showinfo相当于showl)
这里就引出了new关键字:
有以下几个问题:
我的解答:
1.class是引用类型,struct与enum是值类型,值类型与引用类型在内存中存储的位置不一样,那new的时候必然会不一样
2.new做运算符,创建对象与跳用构造函数,调用构造函数的顺序是先父后子,这一点要注意。
new做修饰符,用于向基类成员隐藏继承成员,就象上面new以后实际是2个不同的方法了。
new做约束,约束指定泛型类声明中的任何类型参数都必须有公共的无参数构造函数。(这个我倒是还不太清楚)
当然,使用new可以实现多态。
3.new是不可以重载的
4.约束作用,无参数的构造函数啊
5.new相当与一个不同名的新方法了,override为覆写为了该方法
6.int i;如果不初始话无法通过编译
而int i =new int()至少能通过编译,且其值默认初始化为0
下来几个题,算是对继承以及new字的一些理解了:
题1:
class A
{
public virtual void Foo()
{
Console.WriteLine("Call on A.Foo()");
}
}
class B : A
{
public virtual void Foo()
{
Console.WriteLine("Call on B.Foo()");
}
}
class C : B
{
public override void Foo()
{
Console.WriteLine("Call on C.Foo()");
}
}
class D
{
static void Main()
{
A c1 = new C();
c1.Foo();
Console.ReadLine();
}
}
题2:
class A
{
public virtual void Foo()
{
Console.WriteLine("Call on A.Foo()");
}
}
class B : A
{
public override void Foo()
{
Console.WriteLine("Call on B.Foo() " );
}
}
class C : B
{
public new void Foo()
{
Console.WriteLine("Call on C.Foo()");
}
}
class D
{
static void Main()
{
A c1 = new C();
c1.Foo();
Console.ReadLine();
}
}
题3:
class A
{
public virtual void Foo()
{
Console.WriteLine("Call on A.Foo()");
}
}
class B : A
{
public virtual new void Foo()
{
Console.WriteLine("Call on B.Foo() " );
}
}
class C : B
{
public override void Foo()
{
Console.WriteLine("Call on C.Foo()");
}
}
class D
{
static void Main()
{
A c1 = new C();
c1.Foo();
Console.ReadLine();
}
}
结果不重要,关键是思路以及问题的入口,我想多态继承这方面这几天也是收获很多吧,继续加油!
以上答案为:
题1:call on A.Foo()
题2:call on B.Foo()
题3:call on A.Foo()
感觉哈:方法名称相同则是对象原则,方法名称不同则是就近原则。