• 四、继承(下)


    4.3 子类父类变量的相互赋值 

         构成继承关系的父类和子类对象之间有一个重要的特性:

         子类对象可以被当成基类对象使用。

         这是因为子类对象本就是一种(IS_A)父类对象,因此,以下代码是合法的:

    Parent p;

    Son c=new Son();

    p=c;   //正确,子类对象可以传给父类变量

         上述代码中Parent是Son类的父类。

         然而,反过来就不可以,以下代码是错误的:

    c=p;   //错误,父类对象变量不可以直接赋值给子类变量

         如果确信父类变量中所引用的对象的确是子类类型,则可以通过类型强制转换进行赋值,其语法格式为:

         子类对象变量=(子类名称)基类对象变量;

         或使用as运算符:

         子类对象变量=基类对象变量 as 子类名称;

         示例代码如下:

    c=(Child)p;   //正确,父类对象变量引用的就是子类对象

         或:

    c=p as Child;

    4.4 方法重载、隐藏与虚方法调用

         由于子类对象同时“汇集了”父类和子类的所有公共方法,而C#并未对子类和父类的方法名称进行过多限制,因此,一个问题出现了:

         如果子类中某个方法与父类方法的签名一样(即方法名和方法参数都一样),那当通过子类对象访问此方法时,访问的是子类还是父类所定义的方法?

         让我们先从子类方法与父类方法之间的关系说起。

         总的来说,子类方法与父类方法之间的关系可以概括为以下三种:

         ① 扩充(Extend):子类方法,父类没有;

         ② 重载(Overload):子类有父类的同名函数,但参数类型或数目不一样;

         ③ 完全相同:子类方法与父类方法从方法名称到参数类型完全一样。

         对于第一种“扩充”关系,由于子类与父类方法不同名,所以不存在同名方法调用的问题,重点分析一下后两种情况。

    (1)重载(overload)

         在前面介绍过方法重载的概念,在同一个类中构成重载的方法主要根据参数列表来决定调用哪一个。这一基本判断方法可以推广到类的继承情况。

         例如,以下代码在子类和父类中定义了一个重载的方法OverloadF():

    class Parent

    {

         public void OverloadF()

         {

         }

    }

    class Child:Parent

    {

         public void OverloadF(int i)

         {

         }

    }

         使用代码如下:

    Child obj=new Child();

    obj.OverloadF();   //调用父类的重载方法

    obj.OverloadF(100);   //调用子类的重载方法

         可以看到,虽然重载的方法分布在不同的类中,但仍然可以将其看成是定义在同一个类中的,其使用方式与调用类的其他方法并无不同。

    (2)隐藏(Hide) 

         当子类与父类拥有完全一样的方法时,称“子类隐藏了父类的同名方法”,请看示例:

    class Parent

    {

         public void HideF()

         {

              System.Console.WriteLine("Parent.HideF()");

         }

    }

    class Child:Parent

    {

         public void HideF()

         {

              System.Console.WriteLine("Child.HideF()");

         }

    }

         请注意现在子类和父类都拥有了一个完全相同的方法HideF(),于是问题发生了,请看以下代码:

    Child c=new Child();

    c.HideF();   //调用父类的还是子类的同名方法?

         上述代码运行时,输出:

    Child.HideF()

         修改一下代码:

    Parent p=new Parent();

    p.HideF();   //调用父类的还是子类的同名方法?

         上述代码运行结果:

    Parent.HideF()

         由此可以得出一个结论:

         当分别位于父类和子类的两个方法完全一样时,调用哪个方法由对象变量的类型决定。

         然而,面向对象的继承特性允许子类对象被当成父类对象使用,这使问题复杂化了,请看以下代码会出现什么结果?

    Child c=new Child();

    Parent p;

    p=c;

    p.HideF();   //调用父类的还是子类的同名方法?

         上述代码的运行结果是:

    Parent.HideF()

         这就意味着即使Parent变量p中实际引用的是Child类型的对象,通过p调用的方法还是Parent类的!

         如果确实希望调用的子类的方法,应这样使用:

    ((Child)p).HideF();

         即使进行强制类型转换。

         回到前面Parent和Child类的定义,Visual Studio在编译这两个类时,会发出一个警告:

    警告1     "HideExamples.Child.HideF()"隐藏了继承的成员"HideExamples.Parent.HideF()"。如果是有意隐藏,请使用关键字new。

         虽然上述警告并不影响程序运行结果,却告诉我们代码不符合C#的语法规范,修改Child类的定义如下:

    class Child:Parent

    {

         public new void HideF()

         {

              System.Console.WriteLine("Child.HideF()");

         }

    }

         “new”关键字明确告诉C#编译器,子类隐藏父类的同名方法,提供自己的新版本。

         由于子类隐藏了父类的同名方法,所以如果要在子类方法的实现代码中调用父类被隐藏的同名方法,请使用base关键字,示例代码如下:

    base.HideF();   //调用父类被隐藏的方法

    (3)重写(override)与虚方法调用

         上述隐藏的示例中,由于子类隐藏了父类的同名方法,如果不进行强制转换,就无法通过父类变量直接调用子类的同名方法,哪怕父类变量引用的是子类对象。

         这是不太合理的。我们希望每个对象都只干自己职责之内的事,即如果父类变量引用的是子类对象,则调用的就是子类定义的方法,而如果父类变量引用的就是父类对象,则调用的是父类定义的方法。这就是说,希望每个对象都“各人自扫门前雪,莫管他人瓦上霜”。

         为达到这个目的,可以在父类同名方法前加关键字virtual,表明这是一个虚方法,子类可以重写此方法:即在子类同名方法前加关键字override,表明对父类同名方法进行了重写。

         请看示例代码:

    class Parent

    {

         public virtual void OverrideF()

         {

              System.Console.WriteLine("Parent.OverrideF()");

         }

    }

    class Child:Parent

    {

         public override void OverrideF()

         {

              System.Console.WriteLine("Child.OverrideF()");

         }

    }

         请看以下使用代码:

    Child c=new Child();

    Parent p;

    p=c;

    p.OverrideF();   //调用父类的还是子类的同名方法?

         上述代码的运行结果是:

    Child.OverrideF()

         这一示例表明,将父类方法定义为虚方法,子类重写同名方法之后,通过父类变量调用此方法,到底是调用父类还是子类的,由父类变量引用的真实对象类型决定,而与父类变量无关!

         换句话说,同样一句代码:

    p.OverrideF();

         在p引用不同对象时,其运行的结果可能完全不一样!因此,如果我们在编程时只针对父类变量提供的对外接口编程,就使我们的代码成了“变色龙”,传给它不同的子类对象(这些子类对象都重写了父类的同名方法),它就干不同的事。

         这就是面向对象语言的“虚方法调用(Virtual Method Invoke)”特性。

         很明显,“虚方法调用”特性可以让我们写出非常灵活的代码,大大减少由于系统功能扩充和改变所带来的大量代码修改工作量。

         由此给出以下结论:

         面向对象语言拥有的“虚方法调用”特性,使我们可以只用同样的一个语句,在运行时根据对象类型而执行不同的操作。

  • 相关阅读:
    光学
    ZYNQ学习笔记2——实例
    ZYNQ学习笔记
    AD使用技巧
    关于浮点运算的一点见解
    解决ccs不能同时导入两个相同工程名的问题
    multisum14 快捷键
    你的进程为什么被OOM Killer杀死了?
    Linux下哪些进程在消耗我们的cache?
    linux 安装python3.7.5
  • 原文地址:https://www.cnblogs.com/mxx0426/p/4300056.html
Copyright © 2020-2023  润新知