实验4 继承多态的理解和运用.docx
- 文档编号:25678995
- 上传时间:2023-06-11
- 格式:DOCX
- 页数:24
- 大小:66.76KB
实验4 继承多态的理解和运用.docx
《实验4 继承多态的理解和运用.docx》由会员分享,可在线阅读,更多相关《实验4 继承多态的理解和运用.docx(24页珍藏版)》请在冰豆网上搜索。
实验4继承多态的理解和运用
实验4继承、多态的理解和运用
一.实验目的
1.通过阅读本课件的知识点和例题,理解面向对象的两个重要概念——继承和多态。
2.在深入理解继承和多态意义和编程方法的基础上,模仿例题中的思路和编程模式,尝试运用继承和多态解决本课件给出两个问题,以训练面向对象方法的综合运用能力。
二.知识点和例题
1.继承
1继承的概念
继承(inheritance)是面向对象程序设计中的一个重要的方法。
从字面意义上说,继承就是获得父辈的遗产,在这个基础之上发展自己的事业,而不必从头做起。
在类的设计中,我们会发现许多类都存在着某些共性,一方面,我们要将这些共性抽象出来,做成一些称为基类(父类、超类)的类,另一方面,我们可以利用面向对象的继承机制,在基类之上设计出一些称为子类(派生类)的类来拓展基类的功能。
现实生活中,继承的例子很多。
自然科学中,狼、狗、狐等存在着某些共性,生物学家把它们抽象为“犬类”来描述它们的共性,再在该大类基础上进一步细分,并分别描述它们的特性。
犬类便是基类(在其之上还有哺乳类等更高层次的基类),狼、狗、狐便是子类;社会科学中,“学生”是一个基类,他们具有学号、姓名、成绩等共性,小学生、中学生、大学生、研究生等则是子类,它们各自有各自的特性。
就像自然界和人类社会的分类都是相对的一样,程序中的类也是相对的,一个基类可能是另一个基类的子类,一个子类的下面可能还有子类,从而构成一个树状的层次结构,称为“类族”,如下图所示:
2继承的实现
程序中的继承体现在类与类之间的关系上。
要将一个类定义为另一个类的子类,只需在类名之后加上关键字extends,再加上其父类名即可:
访问权限class类名extends父类名{
属性和方法定义
}
例如,圆是一般几何图形的一个特例,如果描述一般几何图形的类为Shape,则
classCircleextendsShape{
属性和方法定义
}
一个子类继承了其父类的所有成员,包括所有属性和方法(构造方法除外),除此之外,子类还可以有自己的成员,例如:
classShape{
intx,y;//图形位置(图形的共同属性)
Shape(intx,inty){//构造方法
this.x=x;
this.y=y;
}
intgetX(){returnx;}//出口方法
intgetY(){returny;}//出口方法
}
classCircleextendsShape{
intr;//圆的半径(圆的特殊属性)
Circle(intx,inty,intr){//构造方法
super(x,y);
this.r=r;
}
intgetRadius(){returnradius;}//出口方法
}
既然子类继承了父类的所有成员,这就意味着子类对象中也包含着父类的属性和方法,子类方法据此是否就可以随意访问父类成员呢?
否!
子类对父类成员的访问仍受到访问权限的限制,一个类的private成员同样不允许子类的方法访问,就像儿子不能随意动用父辈的私有财产一样(尽管他已继承了这部分财产)。
关键字protected作为介于private和public之间的一种访问权限,则允许子类的方法访问父类的成员。
要设计出一个好的面向对象程序,就要从基类的设计开始,它应当在对所涉及对象充分抽象的基础上设计出来,设计的主线是属性,方法作为服务于属性的角色出现,就像动物的行为是为了延续其生命一样。
然后,通过增加新的属性和方法,在基类之上逐层设计出子类。
父类体现出一类对象的共性,子类则体现出一类对象的特性。
注意:
类应以作为服务对象的属性来命名,而不应以方法所完成的功能来命名。
3super关键字
与前面所述this关键字代表本类对象相似,super关键字代表父类对象,它有两种用法,一是像this一样当子类成员或局部变量与父类成员重名时,作为前缀来标识父类成员,例如super.x,二是代替父类名调用父类构造函数,为父类对象的属性赋初值,例如super(x,y)。
因此在前面例子中,子类Circle的构造函数对父类属性x和y的赋值既可以用super(x,y),又可以用super.x=x;super.y=y;(注意父类x,y属性的访问权限为protected)。
⑷final关键字
从理论上讲,一个类族继承的层次可以是无限的。
但在某些情况下,当我们不想让一个类再有子类时,可以用final关键字来为这个类“绝育”,例如:
finalclassCircleextendsShape{
intr;//圆的半径
Circle(intx,inty,intr){super(x,y);this.r=r;}
intgetRadius(){returnradius;}
}
使得Circle类不能再被继承。
⑸继承实例
classShape{
protectedintx,y,width,height;//所有形状的共同属性
Shape(intx,inty,intwidth,intheight){
this.x=x;
this.y=y;
this.width=width;
this.height=height;
}
intgetX(){returnx;}
intgetY(){returny;}
intgetWidth(){returnwidth;}
intgetHeight(){returnheight;}
}
classRectangleextendsShape{
Rectangle(intx,inty,intwidth,intheight){
super(x,y,width,height);//对父类构造方法的调用
}
doublegetArea(){//计算矩形面积
returnwidth*height;
}
StringshapeName(){
return"矩形";
}
}
classOvalextendsShape{
Oval(intx,inty,intwidth,intheight){
super(x,y,width,height);//对父类构造方法的调用
}
doublegetArea(){//计算圆形面积
returnMath.PI*width*height/4.0;
}
StringshapeName(){
return"椭圆";
}
}
classRoundRectextendsShape{
intarcWidth,arcHeight;//子类增加的特殊属性
RoundRect(intx,inty,intwidth,intheight,intarcWidth,intarcHeight){
super(x,y,width,height);//初始化父类成员
this.arcWidth=arcWidth;//初始化本类成员
this.arcHeight=arcHeight;//初始化本类成员
}
intgetArcWidth(){returnarcWidth;}
intgetArcHeight(){returnarcHeight;}
doublegetArea(){//计算圆角矩形面积
returnwidth*height-(arcWidth*arcHeight*4-arcWidth*arcHeight*Math.PI);
}
StringshapeName(){
return"圆角矩形";
}
}
publicclassUseShape{
publicstaticvoidmain(String[]args){
Rectanglerect=newRectangle(0,0,6,8);
System.out.println(rect.shapeName()+"面积="+rect.getArea());
Ovaloval=newOval(0,0,6,8);
System.out.println(oval.shapeName()+"面积="+oval.getArea());
RoundRectroundrect=newRoundRect(0,0,6,8,1,2);
System.out.println(roundrect.shapeName()+"面积="+roundrect.getArea());
}
}
2.多态
1继承的局限性
从对上一节例题的观察我们会发现,虽然基类Shape概括了几何形状的共性,但作为一个独立的类来说,它是不完整的。
例如,它无法计算形状的面积(一个抽象的形状显然无面积可言),因此它的功能必须在子类中得到拓展。
我们希望在定义基类时也给出实现类族功能的基本方法的说明,并且允许子类对基类同原型方法的功能进行重定义。
这一切都可以通过面向对象的另一重要机制——多态(polymorphism)来实现。
2父类方法的覆盖
我们可以在基类中定义一些与子类同原型的方法来描述未来类族的功能,无论这些方法是否有实际意义。
父、子类同原型方法在通过不同类的对象被调用时会给出不同的实现。
例如Shape类中有如下定义:
doublegetArea(){return0;}
当UseShape类的方法main分别通过Shape和Rectangle的对象对方法getArea产生调用时,将分别返回0和48。
此时我们称父类的方法被子类的方法“覆盖”(override)。
3abstract关键字
如果基类中的某些方法确无法实现,或实现也无实际意义,我们可以用关键字abstract将其定义为抽象方法,从而避免了在基类中给出实现代码的必要性,例如将getArea定义为:
abstractdoublegetArea();
包含抽象方法的类必须定义为抽象类:
abstractclassShape{
…
abstractdoublegetArea();
…
}
4父类与子类的关系
由于抽象类中的抽象方法未曾给出实现代码,因此抽象类的对象是无法创建的。
然而,面向对象程序设计语言允许声明一个父类对象名来引用其子类对象,例如:
Shapes=newRectangle(0,0,6,8);
我们称父类与子类存在着“isa”关系,即子类的对象也是父类的对象(AnobjectofRectangleisanobjectofShape),因此可以为父类的对象赋值。
或者说,子类对象允许被应用在允许其父类对象出现的任何地方。
5Shape类族的重新实现
abstractclassShape{
protectedintx,y,width,height;//所有形状的共同属性
Shape(intx,inty,intwidth,intheight){
this.x=x;
this.y=y;
this.width=width;
this.height=height;
}
intgetX(){returnx;}
intgetY(){returny;}
intgetWidth(){returnwidth;}
intgetHeight(){returnheight;}
abstractdoublegetArea();//父类中的抽象方法
abstractStringshapeName();//父类中的抽象方法
}
classRectangleextendsShape{
Rectangle(intx,inty,intwidth,intheight){
super(x,y,width,height);//对父类构造方法的调用
}
doublegetArea(){//计算矩形面积
returnwidth*height;
}
StringshapeName(){return"矩形";}
}
classOvalextendsShape{
Oval(intx,inty,intwidth,intheight){
super(x,y,width,height);//对父类构造方法的调用
}
doublegetArea(){//计算圆形面积
returnMath.PI*width*height/4.0;
}
StringshapeName(){return"椭圆";}
}
classRoundRectextendsShape{
intarcWidth,arcHeight;//子类增加的特殊属性
RoundRect(intx,inty,intwidth,intheight,intarcWidth,intarcHeight){
super(x,y,width,height);//对父类构造方法的调用
this.arcWidth=arcWidth;
this.arcHeight=arcHeight;
}
intgetArcWidth(){returnarcWidth;}
intgetArcHeight(){returnarcHeight;}
doublegetArea(){//计算圆角矩形面积
returnwidth*height-(arcWidth*arcHeight*4-arcWidth*arcHeight*Math.PI);
}
StringshapeName(){return"圆角矩形";}
}
publicclassUseShape{
publicstaticvoidmain(String[]args){
Shapes;//声明基类对象s
s=newRectangle(0,0,6,8);//用Rectangle对象为s赋值
System.out.println(s.shapeName()+"面积="+s.getArea());
s=newOval(0,0,6,8);//用Oval对象为s赋值
System.out.println(s.shapeName()+"面积="+s.getArea());
s=newRoundRect(0,0,6,8,1,2);//用RoundRect对象为s赋值
System.out.println(s.shapeName()+"面积="+s.getArea());
}
}
6多态应用的第二种情形
在数值计算领域,常常会遇到用某种算法求得某个问题的近似解,其核心是某个函数的问题。
例如求解定积分,最简单的算法是梯形法,它是将积分区间[a,b]分为n个小区间,用函数f(x)曲线之下由这些小区间组成的一系列梯形面积之和近似地代替函数曲线与横轴之间曲顶梯形的面积,即
用梯形法计算定积分的C函数为:
doubleIntegral(doublea,doubleb,intn,double(*f)(doublex))
{
doublex,h,A;intk;
h=(b-a)/(double)n;
x=a;
A=h*(f(a)+f(b))/2.;
for(k=1;k x+=h; A+=h*f(x); } returnA; } 其中double(*f)(doublex)是作为积分函数Integral形参的被积函数f(x)的指针。 我们可以用下面的主函数调用这个积分函数,来计算正弦函数在区间(0,π)上的定积分: #include #include voidmain() { printf("I=%f\n",Integral(0,3.14,100,sin)); } 在Java中,我们可以采用面向对象的继承机制而不是面向过程的函数指针向Integral提供被积函数的计算。 首先在基类中定义积分方法Integral和一个作为被积函数的抽象方法f(x),后者将在各层子类中被覆盖: abstractclassA{ … doubleIntegral(intn){ … } abstractdoublef(doublex); } classBextendsA{ doublef(doublex){ returnsin(x); } } 现在问题是: 父类A中的方法Integral能否调用子类B中的方法f(x)呢? 我们知道,由于子类继承了父类,因此子类B中的方法必能调用父类A中的方法,但反过来,父类A中的方法是否能调用子类B中的方法呢? 面向对象的多态机制使得这种“回调”成为可能,只要在父类A中定义了与子类B同原型的方法f(x)(可以是抽象的),并且通过已创建的子类B的对象调用父类A的方法Integral,便可以实现父类A中方法对子类B中方法的回调(callback)。 7多态实例: 梯形法计算定积分 abstractclassAB{ protecteddoublea,b;//用于积分下限和上限 AB(doublea,doubleb){//构造方法 this.a=a; this.b=b; } doublegetA(){returna;}//出口方法 doublegetB(){returnb;}//出口方法 doubleIntegral(intn){//梯形法计算定积分的方法 doublex,h,A; intk; h=(b-a)/(double)n; x=a; A=.5*h*(f(a)+f(b)); for(k=1;k x+=h; A+=h*f(x); } returnA; } abstractdoublef(doublex);//被积函数(抽象方法) abstractStringfuncName();//返回函数名(抽象方法) } classPrimaryextendsAB{//第一层子类,被积函数为无常量初等函数 protectedintflag;//用于选择被积函数的变量 Primary(doublea,doubleb,intflag){ super(a,b); this.flag=flag; } intgetFlag(){returnflag;} doublef(doublex){//定义被积函数: 根据flag返回不同初等函数的函数值 switch(flag){ case0: returnMath.sin(x); case1: returnMath.cos(x); case2: returnMath.exp(x); case3: returnMath.log(x); case4: returnMath.sqrt(x); default: return-1; } } StringfuncName(){//根据flag返回不同的函数名 switch(flag){ case0: return"sin(x)"; case1: return"cos(x)"; case2: return"exp(x)"; case3: return"log(x)"; case4: return"√(x)"; default: return"退出"; } } } finalclassSecondaryextendsPrimary{//第二层子类,被积函数有2个常量 protecteddoublec1,c2;//定义被积函数中的2个常量 Secondary(doublea,doubleb,intflag,doublec1,doublec2){ super(a,b,flag); this.c1=c1; this.c2=c2; } doublegetC1(){returnc1;} doublegetC2(){returnc2;} doublef(doublex){//定义被积函数: 根据flag返回幂函数或高斯函数值 switch(flag){ case0: returnMath.pow(x,c1); case1: returnGauss(x,0,1); case2: returnGauss(x,c1,c2); default: returnx; } } StringfuncName(){//根据flag返回不同的函数名 switch(flag){ case0: return"pow(x,y)"; case1: return"φ(x;0,1)"; case2: return"φ(x;μ,σ)"; default: return"退出"; } } //计算高斯(正态)密度函数函数值的私有方法,由f(x)调用: privatedoubleGauss(doublex,doublemean
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 实验4 继承多态的理解和运用 实验 继承 理解 运用
![提示](https://static.bdocx.com/images/bang_tan.gif)