《CC++语言程序设计》课件第12章 多态性_第1页
《CC++语言程序设计》课件第12章 多态性_第2页
《CC++语言程序设计》课件第12章 多态性_第3页
《CC++语言程序设计》课件第12章 多态性_第4页
《CC++语言程序设计》课件第12章 多态性_第5页
已阅读5页,还剩83页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

第十二章多态性12.1多态性概述12.2函数重载12.3运算符重载12.4虚函数12.5抽象类12.6综合实例——俄罗斯方块游戏12.7小结12.1多态性概述静态联编动态联编静态多态性(编译时多态性)动态多态性(运行时多态性)12.2函数重载编译时的多态性可以通过函数重载实现.函数重载有两种情况:参数有所差别的重载函数所带参数完全相同,只是它们属于不同的类【例12.1】在基类和派生类中重载函数#include<iostream.h>classbase{ intx,y;public: base(inta,intb) {x=a;y=b;} voidshow()//基类中的show()函数 { cout<<"执行基类中的show()函数"<<endl; cout<<x<<","<<y<<endl; }};classderived:publicbase{ intz;public: derived(inta,intb,intc):base(a,b) { z=c;} voidshow()//派生类中的show()函数 { cout<<"执行派生类中的show()函数"<<endl; cout<<z<<endl; }};voidmain(){ baseb(20,20); derivedd(8,8,30); b.show();//执行基类中的show()函数 d.show();//执行派生类中的show()函数 d.base::show();//执行基类中的show()函数 }

在基类和派生类中进行函数重载时,在编译时可以用下面两种方法区别重载函数:使用对象名加以区分。例如:b.show()和d.show()分别调用类base和derived的show()函数。使用“类名::”加以区分。例如:d.base::show()调用的是base的show()函数。12.3运算符重载自定义的类的运算往往用运算符重载函数来实现运算符重载的目的:扩充语言的功能,即将运算符扩充到用户定义的类型上去。运算符重载通过创建运算符函数operator()来实现。可以重载成为类的成员,也可是类的友元。12.3.1运算符重载的规则为了表达方便,人们希望能对自定义的类型进行运算,希望内部运算符(如“+”、“-”、“*”、“/”等)在特定的类对象上以新的含义进行解释,即实现运算符的重载。C++为运算符重载提供了一种方法,使用以下形式进行运算符重载:

其中,@表示要重载的运算符,type是返回类型。typeoperator@(参数表);除了“.”、“.*”、“::”、“?:”、“#”、“##”其它都可以重载。delete、new、指针、引用也可以重载。运算符函数可以定义为内联函数。用户定义的运算符不改变运算符的优先次序。不可以定义系统定义的运算符集之外的运算符。不能改变运算符的的语法结构。【例12.2】使用运算符重载函数将类对象相加。

#include<iostream.h> classcomplex{ public: doublereal; doubleimag; complex(doubler=0,doublei=0) {real=r;imag=i;} }; complexoperator+(complexco1,complexco2) { complextemp; temp.real=co1.real+co2.real; temp.imag=co1.imag+co2.imag; returntemp; } main() { complexcom1(1.1,2.2),com2(3.3,4.4),total1,total2; total1=operator+(com1,com2);//调用运算符函数operater+()的第一种方式 cout<<"real1="<<total1.real<<"“

<<"imag1="<<total1.imag<<endl; total2=com1+com2;//调用运算符函数operater+()的第二种方式

cout<<"real2="<<total2.real<<"“

<<"imag2="<<total2.imag<<endl; return0; }12.3.2运算符重载为成员函数成员运算符定义的语法形式

classX{//…typeoperator@(参数表);//…};typeX::operator@(参数表){//函数体}1.双目运算符重载为成员函数成员运算符函数的参数表中只有一个参数,它作为运算符的右操作数,此时当前对象作为运算符的左操作数,它是通过this指针隐含的传递给函数的。aa@bb;//隐式调用aa.operator@(bb);//显式调用例12.3

双目运算符重载为成员函数应用。2.单目运算符重载为成员函数成员运算符函数的参数表中没有参数,此时当前对象作为运算符的一个操作数

@aa//隐式调用aa.operator@();//显式调用例12.4

单目运算符重载为成员函数应用。12.3.3友元运算符函数友元运算符函数定义的语法形式类内:friendtypeoperator@(参数表);

类外:

typeoperator@(参数表){//函数体}1.双目运算符重载为友元函数当用友元函数重载双目运算符时,两个操作数都要传递给运算符函数,调用时可采用以下两种方式:

aa@bb;//隐式调用,aa和bb分别为左、右操作数

operator@(aa,bb);//显式调用双目友元运算符函数operator@所需要的两个操作数都在参数表中由对象aa和bb显式调用。例12.5双目运算符重载为友元函数。2.单目运算符重载为友元函数当用友元函数重载单目运算符时,需要一个显式的操作数,调用时可采用以下两种方式:@aa;//隐式调用,aa为操作数operator@(aa);//显式调用例12.6单目运算符重载为友元函数应用。注意:使用友元运算符重载“++”、“--”这样的运算符,可能会出现一些问题。不能用友元函数重载的运算符是:=、()、[]、->,其余的运算符都可以使用友元函数来实现成员运算符函数与友元运算符函数的比较对双目运算符而言,成员运算符函数带有一个参数,友元运算符函数带由两个参数;对单目运算符而言,成员运算符函数不带参数,而友元运算符函数带一个参数。双目运算符一般可被重载为友元运算符函数或成员运算符函数,但有一种情况,必须使用友元函数。在左操作数为系统预定义数据类型,或必须为一个不同的类对象时。一般而言,对于双目运算符,将它重载为一个友元函数比重载为一个成员函数便于使用。若一个运算符的操作需要修改类对象的状态,则选择成员运算符函数较好。如果运算符所需的操作数(尤其是第一个操作数)希望有隐式转换,则运算符重载必须使用友元函数。12.3.4“++”和“--”的重载前缀方式和后缀方式声明不同运算符“++”和“--”作前缀和后缀是有区别的。但是,C++V2.1之前的版本在重载“++”或“--”时,不能显示区分是前缀方式还是后缀方式。在C++V2.1及以后的版本中,编辑器可以通过在运算符函数表中是否插入关键字int来区分这两种方式。例12.7运算符“++”和“--”作前缀和后缀重载。12.4虚函数虚函数是重载的另一种表现形式。虚函数允许函数调用与函数体之间的联系在运行时才建立,也就是在运行时才决定如何动作,即所谓的动态联编。12.4.1引入派生类后的对象指针声明为指向基类对象的指针可以指向它的公有派生的对象,但不允许指向它的私有派生对象。允许将一个声明为指向基类的指针指向其公有派生类的对象,但是不能将一个声明为指向派生类对象的指针指向其基类的对象。声明为指向基类对象的指针,当其指向其公有派生类对象时,只能用它来直接访问派生类中从基类继承来的成员,而不能直接访问公有派生类中定义的成员。12.4.2虚函数的定义及使用虚函数的作用

“同一接口,多种方法”虚函数的定义在基类中被关键字virtual说明,并在派生类中重新定义的函数。在派生类中重新定义时,其函数原型,包括返回类型、函数名、参数个数与参数类型的顺序,都必须与基类中的原型完全相同。例12.9使用虚函数实现动态多态性。说明:在基类中,用关键字virtual可以将其public或protected部分的成员函数声明为虚函数。在派生类对基类中声明的虚函数进行重新定义时,关键字virtual可以写也可以不写。虚函数被重新定义时,其函数的原型与基类中的函数原型必须完全相同。一个虚函数无论被公有继承多少次,它仍然保持虚函数的特性。虚函数必须是其所在类的成员函数,而不能是友元函数,也不能是静态成员函数,因为虚函数调用要靠特定的对象来决定该激活哪个函数。但是虚函数可以在另一个类中被声明为友元函数。12.4.3虚析构函数构造函数不能是虚函数,但析构函数可以是虚函数。例12.10析构函数定义不当造成内存泄漏。#include"iostream.h"classA{public: int*a; A(){a=new(int);} virtualvoidfunc1(){} ~A(){deletea;cout<<"deletea"<<endl;}};classB:publicA{public: int*b; B(){b=new(int);} virtualvoidfunc1(){} ~B(){deleteb;cout<<"deleteb"<<endl;}};voidmain(){ A*pb=newB(); deletepb;}

在main函数中,动态创建了一个B类对象。当B对象创建时,调用的是B类的构造函数。但是,当对象析构时,却调用的是A类的析构函数,B类的析构函数没有被调用,因而发生了内存泄漏,这是不希望看到的。造成这种问题的原因是:当A类指针指向的内存单元(即B类对象的数据)被释放时,编译器看到指针类型是A类的,所以调用A类的析构函数。其实,这个时候需要调用指针所指向的对象类型的析构函数即B类的析构函数。虚函数能够满足这个要求。所以,可以使用虚析构函数来解决上面遇到的问题:【例12.11】虚析构函数的使用。

#include"iostream.h" classA{ public: int*a; A(){a=new(int);} virtualvoidfunc1(){} virtual~A(){deletea;cout<<"deletea"<<endl;} };

classB:publicA{ public: int*b; B(){b=new(int);} virtualvoidfunc1(){} virtual~B(){deleteb;cout<<"deleteb"<<endl;} }; voidmain() { A*pb=newB(); deletepb; }应用举例应用C++的多态性,计算三角形、矩形和圆的面积。12.5抽象类纯虚函数是一个在基类中说明的虚函数,它在基类中没有定义,但要求在它的派生类中定义自己的版本,或重新说明为纯虚函数。纯虚函数的一般形式如下:virtualtype函数名(参数表)=0;【例12.12】用虚函数求三角形和矩形面积。#include<iostream.h>classShape{protected:intwidth,height;public:voidset_values(inta,intb){width=a;height=b;}virtualintarea(){cout<<"Noarea"<<endl;}};classCRectangle:publicShape{public:intarea(void){return(width*height);}};classCTriangle:publicShape{public:intarea(void){return(width*height/2);}};voidmain(){CRectanglerect;CTriangletrgl;Shape*ppoly1=▭Shape*ppoly2=&trgl;ppoly1->set_values(4,5);ppoly2->set_values(4,5);cout<<ppoly1->area()<<endl;cout<<ppoly2->area()<<endl;}抽象类如果一个类中至少包含一个纯虚函数,那么就称该类为抽象类。对于抽象类有以下几点规定:由于抽象类中至少包含有一个没有定义功能的纯虚函数,因此抽象类只能用作其它类的基类,不能建立抽象类对象。抽象类不能用作参数类型、函数返回类型或显示转换的类型。但可以声明指向抽象类的指针或引用,此指针可以指向它的派生类,进而实现多态性。如果在抽象类的派生类中没有重新说明纯虚函数,而派生类只是继承基类的纯虚函数,则这个派生类仍然还是一个抽象类。classshape{ //…public: //…virtualvoidshow()=0;}; shapes1; shape*ptr; shapef(); shapeg(shapes); shape&h(shape&);思考12.6综合实例——俄罗斯方块游戏综合前面所学的知识,熟悉面向对象程序设计的全过程,即面向对象的分析、面向对象的设计、面向对象的实现三个过程,创建一个实用的游戏程序。游戏说明及规则俄罗斯方块是由俄罗斯人阿列克谢·帕基特诺夫发明的。俄罗斯方块原名是俄语Тетрис(英语是Tetris),这个名字来源于希腊语tetra,意思是“四”,而游戏的作者最喜欢网球(tennis)。于是,他把两个词tetra和tennis合而为一,命名为Tetris,这也就是俄罗斯方块名字的由来。游戏规则Tetris游戏在一个m*n的矩形框内进行。游戏开始时,矩形框的顶部会随机出现一个由四个小方块构成的砖块,每过一个很短的时间(我们称这个时间为一个tick),它就会下落一格,直到它碰到矩形框的底部,然后再过一个tick它就会固定在矩形框的底部,成为固定块。接着再过一个tick顶部又会出现下一个随机形状,同样每隔一个tick都会下落,直到接触到底部或者接触到下面的固定块时,再过一个tick它也会成为固定块,再过一个tick之后会进行检查,发现有充满方块的行则会消除它,同时顶部出现下一个随机形状。直到顶部出现的随机形状在刚出现时就与固定块重叠,表示游戏结束。操作说明:a—左移

d—右移

w—翻转

s—下移Tetris游戏的分析、设计与实现1.Tetris游戏的矩形框首先描述Tetris游戏的矩形框。矩形框类——CBin名称:矩形框属性:高度

宽度映像方法(操作):

构造函数析构函数

获取矩形高度值获取矩形宽度值获取映像数据设置映像数据删除整行classCBin{private:unsignedintwidth;unsignedintheight;unsignedchar**image;public:CBin(unsignedintw,unsignedinth);~CBin();unsignedintgetWidth(){returnwidth;}unsignedintgetHeight(){returnheight;}voidgetImage(unsignedchar**destImage);voidsetImage(unsignedchar**srcImage);unsignedintremoveFullLines();};矩形框类方法的实现构造函数算法描述:用来初始化数据成员width和height为image分配空间并初始化CBin::CBin(unsignedintw,unsignedinth){ width=w; height=h; image=newunsignedchar*[height];for(unsignedinti=0;i<height;i++) {image[i]=newunsignedchar[width]; for(unsignedintj=0;j<width;j++) image[i][j]=0;}}析构函数算法描述:删除在构造函数中为image分配的空间CBin::~CBin(){ for(unsignedinti=0;i<height;i++){deleteimage[i];}delete[]image;}函数功能:获取映像数据算法描述:将游戏面板的源映像数据数据拷贝到目的映像中源映像:CBin类数据成员image:二维数组目的映像:同image大小相同的二维数组voidCBin::getImage(unsignedchar**destImage){for(unsignedinti=0;i<height;i++) for(unsignedintj=0;j<width;j++) destImage[i][j]=image[i][j];}函数功能:设置映像数据算法描述:将源映像数据数据拷贝到目的映像中源映像:同image大小相同的二维数组目的映像:CBin类数据成员image:二维数组voidCBin::setImage(unsignedchar**srcImage){ for(unsignedinti=0;i<height;i++) for(unsignedintj=0;j<width;j++) image[i][j]=srcImage[i][j];}自顶向下,逐步求精函数功能:删除整行算法描述:第一步:根据函数功能,程序可以写为:unsignedintCBin::removeFullLines(){检查image的每一行是否被填满,如果任何一行完全填满,则删除这一行,并让上面行的数据下移一行,记录删除的总行数。返回删除的总行数}第二步,对程序进一步细化:unsignedintCBin::removeFullLines(){ unsignedintflag,EmptyLine=0;unsignedinti,j,m; for(i=0;i<height;i++)//循环遍历image的每一行 {

检查一行是否被填满,如果任何一行完全填满,则删除这一行,并让上面行的数据下移一行,记录删除的总行数 }returnEmptyLine;//返回删除的总行数}第三步对“检查一行是否被填满,如果任何一行完全填满,则删除这一行,并让上面行的数据下移一行,记录删除的总行数”进行细化。

//检查一行是否被填满

flag=0;

for(j=0;j<width;j++)

{

if(image[i][j]==0) flag=1; }//如果一行完全被填满if(flag==0){

//删除整行,依次下移

for(m=i;m>0;m--)for(j=0;j<width;j++) image[m][j]=image[m-1][j]; //最上面一行补0

for(j=0;j<width;j++)

image[0][j]=0;

EmptyLine++;//记录删除的行数 }unsignedintCBin::removeFullLines(){ unsignedintflag,EmptyLine=0;unsignedinti,j,m; for(i=0;i<height;i++) { flag=0;

//检查一行是否被填满 for(j=0;j<width;j++) { if(image[i][j]==0) flag=1; }//一行完全被填满if(flag==0){

//删除整行,依次下移

for(m=i;m>0;m--)for(j=0;j<width;j++) image[m][j]=image[m-1][j]; for(j=0;j<width;j++) image[0][j]=0; EmptyLine++;//记录删除的行数 }}returnEmptyLine;}现在来完成一些Tetris砖块类。Tetris的常用砖块见下图。经分析所有的砖块都有共同的特征,因此可以定义一个所有砖块共同的基类CBrick(可定义为抽象类)以“I”砖块为例分析:CIBrick类有四个数据成员:orientation,posX,posY,andcolour。orientation表示了“I”砖块的四个状态,可能的取值为{0,1,2,3},砖块的状态如图所示,由状态0到状态1是“I”砖块固定一个特定点顺时针旋转90度。依此类推,状态3的下一个状态是状态0。posX,posY记录了特定点(图中黑点)的坐标。Colour为砖块的颜色值。砖块类——CBrick名称:砖块类属性:状态

位置X坐标颜色位置Y坐标方法(操作):

设置状态;获取状态

设置位置X坐标;获取位置X坐标

设置位置Y坐标;获取位置Y坐标

设置颜色;获取颜色左移;右移;下移;旋转;冲突检查;设置游戏面板;置顶(可设置成纯虚函数)砖块类——CBrickclassCBrick{protected:intorientation;intposX;intposY;unsignedcharcolour;public:intgetOrientation(){returnorientation;};intgetPosX(){returnposX;};intgetPosY(){returnposY;};unsignedchargetColour(){returncolour;};

砖块类——CBrickvoidsetOrientation(intnewOrient){orientation=newOrient;};voidsetPosX(intnewPosX){posX=newPosX;};voidsetPosY(intnewPosY){posY=newPosY;};voidsetColour(unsignedcharnewColour){colour=newColour;};砖块类——CBrickintshiftLeft(CBin*bin);intshiftRight(CBin*bin);intshiftDown(CBin*bin);introtateClockwise(CBin*bin);virtualintcheckCollision(CBin*bin)=0;virtualvoidoperator>>(unsignedchar**binImage)=0;virtualvoidputAtTop(intnewOrient,intnewPosX)=0;};函数功能:左移算法描述:获取特定点X坐标重设特定点X坐标为当前值减1检查冲突如果冲突,则不能左移,恢复特定点X坐标,返回0值否则,返回1值intCBrick::shiftLeft(CBin*bin){ intposX; posX=getPosX();setPosX(posX-1);if(checkCollision(bin)==0){setPosX(posX);return0;}return1;}函数功能:右移算法描述:获取特定点X坐标重设特定点X坐标为当前值加1检查冲突如果冲突,则不能右移,恢复特定点X坐标,返回0值否则,返回1值intCBrick::shiftRight(CBin*bin){ intposX; posX=getPosX();setPosX(posX+1);if(checkCollision(bin)==0){ setPosX(posX);return0;}return1;}函数功能:下移算法描述:获取特定点Y坐标重设特定点Y坐标为当前值加1检查冲突如果冲突,则不能下移,恢复特定点Y坐标,返回0值否则,返回1值intCBrick::shiftDown(CBin*bin){ intposY; posY=getPosY();setPosY(posY+1);if(checkCollision(bin)==0){ setPosY(posY);return0;}return1;}函数功能:旋转算法描述:获取砖块状态如果状态为3,则旋转后状态为0;否则,旋转后状态为当前值加1检查冲突如果冲突,则不能旋转,恢复原状态,返回0值否则,返回1值intCBrick::rotateClockwise(CBin*bin){ intorientation,oldOrientation;orientation=getOrientation(); oldOrientation=orientation; if(orientation==3) orientation=0; else orientation=orientation+1;setOrientation(orientation); if(checkCollision(bin)==0){setOrientation(oldOrientation);return0;}return1;}“I”型砖块类——CIBrickCIBrick从CBrick继承而来需重载3个纯虚函数classCIBrick:publicCBrick{public: intcheckCollision(CBin*bin); voidoperator>>(unsignedchar**binImage); voidputAtTop(intnewOrient,intnewPosX);};函数功能:检查冲突算法描述:获取游戏面板映像获取当前砖块的状态和特定点针对砖块不同的状态进行检查如果冲突,返回0值;否则,返回1值CIBrick::checkCollision函数的实现获取游戏面板映像intCIBrick::checkCollision(CBin*bin){intwidth;intheight;unsignedchar**image; intorientation; intposX; intposY; width=bin->getWidth(); height=bin->getHeight(); image=newunsignedchar*[height];for(inti=0;i<height;i++) {image[i]=newunsignedchar[width];} bin->getImage(image);CIBrick::checkCollision函数的实现获取当前砖块的状态和特定点

orientation=getOrientation();posX=getPosX(); posY=getPosY();CIBrick::checkCollision函数的实现针对砖块不同的状态进行检查(以0状态为例

if(orientation==0) {//砖块碰到边界

if( (posX<0)||(posX>width-1)|| (posY<1)||(posY+2>height-1)) return0;

//砖块碰到其他固定砖块

if((image[posY-1][posX]!=0)||(image[posY][posX]!=0)||(image[posY+1][posX]!=0)||(image[posY+2][posX]!=0))return0; }CIBrick::checkCollision函数的实现//I砖块状态1

if(orientation==1)

{

if((posX<2)||(posX+1>width-1)|| (posY<0)|| (posY>height-1)) return0; if((image[posY][posX-2]!=0)||(image[posY][posX-1]!=0)||(image[posY][posX]!=0)|| (image[posY][posX+1]!=0))return0; }CIBrick::checkCollision函数的实现

//“I”砖块状态2

if(orientation==2)

{

if((posX<0)||(posX>width-1)|| (posY<2)||(posY+1>height-1)) return0; if((image[posY-2][posX]!=0)|| (image[posY-1][posX]!=0)||(image[posY][posX]!=0)|| (image[posY+1][posX]!=0))return0; }CIBrick::checkCollision函数的实现//状态3

if(orientation==3)

{

if((posX<1)||(posX+2>width-1)|| (posY<0)||(posY>height-1)) return0; if((image[posY][posX-1]!=0)|| (image[posY][posX]!=0)|| (image[posY][posX+1]!=0)|| (image[posY][posX+2]!=0))return0; }函数功能:设置游戏面板算法描述:获取当前砖块的状态、特定点和颜色针对砖块不同的状态进行设置CIBrick::operator>>函数的实现获取当前砖块的状态、特定点和颜色voidCIBrick::operator>>(unsignedchar**binImage){intorientation; intposX; intposY; unsignedcharcolour; posX=getPosX(); posY=getPosY(); orientation=getOrientation(); colour=getColour();CIBrick::operator>>函数的实现针对砖块不同的状态进行设置

if(orientation==0)//状态0

{

binImage[posY-1][posX]=colour;binImage[posY][posX]=colour; binImage[posY+1][posX]=colour; binImage[posY+2][posX]=colour;} if(orientation==1)//状态1

{

binImage[posY][posX-2]=colour;binImage[posY][posX-1]=colour; binImage[posY][posX]=colour; binImage[posY][posX+1]=colour;} CIBrick::operator>>函数的实现

if(orientation==2)//状态2

{

binImage[posY-2][posX]=colour; binImage[posY-1][posX]=colour;binImage[posY][posX]=colo

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

最新文档

评论

0/150

提交评论