四、继承(下)

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)”特性。

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

由此给出以下结论:

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

时间: 2024-10-29 19:13:29

四、继承(下)的相关文章

别样JAVA学习(六)继承下(2.1)Object类equals()

上一节继承下(一)我们进行抽象类.接口以及多态的学习, 接下来大家我们讲点特殊的东西就是object类, 我们一直在说继承,子继承了父,父还有没有父类呢, 为什么这么思考,大家想构造函数的第一行是不是都有 一个隐式的super()语句,它们是不是也都会去访问自己的 父类呢?其实是的,这个父类叫什么呢? 叫做Object类,传说JAVA对象中的上帝,哈哈. 1.定义 所有对象的超类,所有对象的直接后者间接父类. 肯定是所有对象都具备的功能. 随便定义一个类 class Demo{}除了隐式的构造方

Bootstrap<基础十四> 按钮下拉菜单

使用 Bootstrap class 向按钮添加下拉菜单.如需向按钮添加下拉菜单,只需要简单地在在一个 .btn-group 中放置按钮和下拉菜单即可.也可以使用 <span class="caret"></span> 来指示按钮作为下拉菜单. 下面的实例演示了一个基本的简单的按钮下拉菜单: <!DOCTYPE html> <html> <head> <title>Bootstrap 实例 - 基本的按钮下拉菜单&

C++语言笔记系列之十四——继承后的访问权限

1.析构函数不继承:派生类对象在析构时,基类析构函数的调用顺序与构造函数相反. 注:派生类对象建立时要调用基类构造函数,派生类对象删除时要调用基类析构,顺序与构造函数严格相反. 2.例子 example 1 #include <iostream.h> #include <math.h> class Point { public: Point(double a, double b, doule c) { x = a; y = b; z = c; } double Getx() {re

S3C2416裸机开发系列十四_GCC下UCGUI的移植(2)

S3C2416裸机开发系列十四 GCC下UCGUI的移植(2) 象棋小子    1048272975 现在主要讲解一下在GCC移植UCGUI,Makefile工程如何加入目录,加入源码,c标准库,编译选项的设置. 笔者的Makefile模板提取自uboot,工程中加入目录,加入源码都是很简单的,详细的介绍请参考前面章节" GCC启动代码工程应用实例".下面主要介绍UCGUI目录下很多的源码文件Makefile的编写,一种可行的方式就是把GUI目录上所有的c文件,不管有无用到,均加入工程

多重虚继承下的对象内存布局

<深入C++对象模型>绝对是一本值得深读的一本书,书里多次出现一句话,“一切常规遇见虚继承,都将失效”.这是一个有趣的问题,因为C++标准容忍对象布局的实现有较大的自由,出现了各编译器厂商实现的方式不同. 今天谈谈visual studio2013多重虚继承下对象布局.有错不要客气,不要吝啬你的留言,请直接开喷. class y和class z都是从class x虚继承来的子类(也叫派生类),class A是class y和class z的多重继承子类.为了简化问题,下面的data membe

S3C2416裸机开发系列十四_GCC下UCGUI的移植(1)

S3C2416裸机开发系列十四 GCC下UCGUI的移植(1) 象棋小子    1048272975 GUI(图形用户界面)极大地方便了非专业用户的使用,用户无需记忆大量的命令,取而代之的是可以通过窗口.菜单.按键等方式进行操作.在某些场合,设计一款人机界面丰富友好的嵌入式产品能赢得更多的用户.笔者此处就s3c2416基于UCGUI图形用户界面的使用作一个简单的介绍. 1. 代码准备 UCGUI 3.98源码,这个版本的UCGUI是开放源码的最高版本,之后版本只提供库文件,不再开源.笔者以UCG

分析多继承下的动态多态。

一.首先我们先了解一下三个概念: 1.重载.2.隐藏.3.覆盖(重写) 如何实现重载?--2个条件: 1-在同一作用域内. 2-两个函数函数名相同,参数不同,返回值可以不同. 此时两个函数就实现了重载,当然这是C++对于C特有的,因为C的时候对参数并没有太多的考虑,C++的编译器在编译时对函数进行了重命名,所以就算是函数名相同的函数,如果参数不同,就会是不同的函数,对应不同的情况. 如何实现隐藏/重定义?--2个条件: 1-在不同作用域下,大多在继承上体现. 2-函数名相同即可. 例如在 B类公

5.2-全栈Java笔记:面向对象的特征(一)继承 | 下

上节我们聊到「Java面向对象的特征:继承」这节我们继续聊一下继承的应用. Object类 Object类基本特性 Object类是所有Java类的根基类,也就意味着所有的JAVA对象都拥有Object类的属性和方法.如果在类的声明中未使用extends关键字指明其父类,则默认继承Object类. [示例1]Object类 public class Person { ... } //等价于: public class Person extends   Object { ... } toStrin

JAVA学习第十三课(继承下:抽象类abstract)

抽象:模糊 == 看不懂 继承:发现事物之间的共性关系,并向上抽取,以提高代码的复用性 事物里都具备这个功能,但是这个功能是什么,怎么去做,不知道,这就涉及到了抽象 1.继承:A,B两个类,都有show的功能,实现方式也一样那么就可以向上抽取其共性,做新类,使A,B继承于新类. 2.抽象:A,B两个类,都有show的功能,但是功能的实现方式不一样,那么向上抽取其方法生明,但是内容不一样,那么就用关键字abstract标识,表示抽象函数,那么这个类也必须用abstract标识,表示抽象类 abst

黑马程序员——JAVA学习笔记四(继承、接口、内部类)

1,    通过extends关键字让类与类之间产生继承关系.多个类中存在相同属性和行为时,将这些内容抽取到单独的一个类中,那么多个类无需定义这些属性和行为,只要继承那个类即可,已存在的类叫做超类,基类,或父类.新类称为子类,派生类,孩子类. 子类可以直接访问父类中的非私有的属性和行为.子类无法继承父类中私有的内容.JAVA不支持多继承,只支持单继承,多实现. 继承提高了代码复用性,让类与类之间产生了关系.为多态提供了前提. 2,    super关键字代表父类中成员变量内存空间的标示.两个作用