计算机科学及编程导论模块6_第1页
计算机科学及编程导论模块6_第2页
计算机科学及编程导论模块6_第3页
计算机科学及编程导论模块6_第4页
计算机科学及编程导论模块6_第5页
已阅读5页,还剩80页未读 继续免费阅读

下载本文档

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

文档简介

模块6多态性计算机科学与编程导论2本章主要内容1多态性的概念2函数重载3

运算符重载4虚函数3多态性的概念多态性是面向对象的重要特性,是指不同对象收到相同的消息时产生不同的行为。消息是指调用类的成员函数,不同的行为指不同的实现,即调用不同的函数。4多态性的概念C++支持两种多态性:编译时的多态性和运行时的多态性;编译时的多态是在程序编译过程中确定函数操作的具体对象,通过函数重载来实现;运行时的多态是在程序运行过程中才能确定函数操作的具体对象,通过虚函数来实现。5多态的具体实现在面向对象系统中,将确定操作对象(确定调用哪个函数实现)的过程称为联编,即把一条消息(调用类的成员函数)与一个对象的方法(具体的函数版本)相结合的过程。6多态的具体实现联编有两种:静态联编,在编译阶段确定操作的具体对象,也称早期联编、早绑定,C语言中所有的函数调用都使用早期联编;动态联编,在系统运行过程中确定某一操作对象,也称滞后联编、晚绑定。main(){execute25}triplelt(){execute38}prNames(){}02538main(){execute34025}triplelt(){execute34038}prNames(){}main(){execute45025}triplelt(){execute45038}prNames(){}编译执行(34000)执行(45000)编译:函数地址是相对地址运行:函数地址是绝对地址静态联编和动态联编静态联编和动态联编,都是针对函数调用中的地址而言,而非函数定义本身静态联编意味着编译时就确定了调用函数的相对地址动态联编意味着运行时才能确定调用函数的相对地址92函数重载静态多态性即编译时的多态是指在编译阶段就决定调用哪个同名函数;通过函数重载来实现。10构造函数的重载

一个类可以定义多个构造函数,使用户选用不同的方式完成对象数据的初始化;重载的所有构造函数同名,但其参数类型、参数个数必须有所区别。11例1

构造函数重载。

重载构造函数的例子在类中声明的多种构造函数:classCArea{public:CArea(){x=0;y=0;}//无参数的构造函数

CArea(intrx,intry=0);//带缺省参数的构造函数

CArea(floatrr){rr=0;}//带一个参数的构造函数

CArea(floatrr,char*ra);//带两个参数的构造函数

};123运算符重载运算符重载是对已有的运算符赋予多重含义必要性C++中预定义的运算符其运算对象只能是基本数据类型,而不适用于用户自定义类型(如类)实现机制将指定的运算表达式转化为对运算符函数的调用,运算对象转化为运算符函数的实参。编译系统对重载运算符的选择,遵循函数重载的选择原则。13规则和限制可以重载C++中除下列运算符外的所有运算符:

.*::?:只能重载C++语言中已有的运算符,不可臆造新的。不改变原运算符的优先级和结合性。不能改变操作数个数。经重载的运算符,其操作数中至少应该有一个是自定义类型。14两种形式重载为类成员函数。重载为友元函数。15运算符函数声明形式函数类型operator运算符(形参){......}重载为类成员函数时

参数个数=原操作数个数-1 (后置++、--除外)重载为友元函数时参数个数=原操作数个数,且至少应该有一个自定义类型的形参。16运算符成员函数的设计双目运算符B如果要重载B为类成员函数,使之能够实现表达式oprd1Boprd2,其中

oprd1为A类对象,则B应被重载为A类的成员函数,形参类型应该是oprd2

所属的类型。经重载后,表达式

oprd1Boprd2

相当于oprd1.operatorB(oprd2)运算符重载17

例2重载运算符“+”,实现两个复数相加。将“+”运算重载为复数类的成员函数。规则:实部和虚部分别相加。操作数:两个操作数都是复数类的对象。18#include<iostream.h>classcomplex //复数类声明{public: //外部接口

complex(doubler=0.0,doublei=0.0){real=r;imag=i;//构造函数

complexoperator+(complexc2);//+重载为成员函数

voiddisplay(); //输出复数

private: //私有数据成员

doublereal; //复数实部

doubleimag; //复数虚部}; 1819complexcomplex::operator+(complex&c2)//重载函数实现{ complexc; c.real=c2.real+real; c.imag=c2.imag+imag; returncomplex(c.real,c.imag);}1920voidcomplex::display(){cout<<"("<<real<<","<<imag<<“I”<<“)"<<endl;}intmain()//主函数{ complexc1(3,4),c2(5,-10),c3;//声明复数类的对象

c3=c1+c2; //使用重载运算符完成复数加法

cout<<"c1=";c1.display(); cout<<"c2=";c2.display(); cout<<"c1+c2=";c3.display();return0;}2021程序输出的结果为:c1=(3,4i)c2=(5,-10i)c1+c2=(8,-6i)2122

重载为类的友元函数运算符重载形式与重载为类的成员函数相同,只是参数表中的参数要求不同;单目运算符要求1个操作数,双目运算符要求2个操作数,对同一个运算符,重载为成员函数总比重载为友元函数时少一个参数。友元定义重载运算符时,所有的操作对象需要参数来传递。23运算符友元函数的设计如果需要重载一个运算符,使之能够用于操作某类对象的私有成员,可以此将运算符重载为该类的友元函数。函数的形参代表依自左至右次序排列的各操作数。双目运算符B重载后,

表达式oprd1Boprd2

等同于operatorB(oprd1,oprd2)运算符重载24例3采用友元运算符函数实现复数相加。将+(双目)重载为复数类的友元函数。两个操作数都是复数类的对象。25#include<iostream.h>classcomplex //复数类声明{public: //外部接口

complex(doubler=0.0,doublei=0.0)

{real=r;imag=i;} //构造函数

friendcomplexoperator+(complex&c1,complex&c2);//运算符+重载为友元函数

voiddisplay(); //显示复数的值

private: //私有数据成员

doublereal; doubleimag;}; 2526complexoperator+(complex&c1,complex&c2) //运算符重载友元函数实现{ return

complex(c2.real+c1.real,c2.imag+c1.imag);}//其它函数和主函数同上例2627“++”和“--”的重载前置“++”和“--”重载为类成员函数

com.operator++()重载为友元函数

operator++(Xcom),com是X类的对象例5:计数器中的自加与自减。28“++”和“--”的重载后置“++”和“--”重载为类成员函数

com.operator++(int)重载为友元函数

operator++(Xcom,int)com是X类的对象,int的参数值为029

赋值运算符的重载“=”(赋值)也可以被重载,将赋值号右边的对象数据内容逐域拷贝到赋值号左边的类对象中;只能重载为成员函数,并且不能被继承;一般,系统为每个类都生成一个缺省的赋值运算符,但有些特殊情况下,使用缺省的赋值运算符会出现“指针悬挂问题”,赋值运算符重载可以解决。30

赋值运算符的重载“&”不必重载,它能返回对象在内存的起始地址。31运行时的多态性运行时的多态性是指许多对象及对象的操作不能在编译时就确定下来,需要在运行过程中确定;这种多态性是用虚函数来实现的。324虚函数在类的声明中,在函数原型之前写virtual。virtual

只用来说明类声明中的原型,不能用在函数实现时。具有继承性,基类中声明了虚函数,派生类中无论是否说明,同原型函数都自动为虚函数。调用方式:通过基类指针或引用,执行时会

根据指针指向的对象的类,决定调用哪个函数。4虚函数虚函数提供了OOP方法的动态联编体系进一步学习之前,了解“类家族”的概念,记住一个与虚函数使用相关的规则类家族类家族:同一个继承层次结构中的所有相关的类在一条继承线上发展的类家族从一个父亲派生出多个类的类家族一个简单规则指向基类的指针可以指向派生类的对象公有派生36对象指针对象指针就是对象存储区的首地址。指向对象的指针有:一般对象的指针;引入派生类后的对象指针。37

一般对象的指针定义对象时要初始化,初始化后的对象会占有一定的内存空间,存储空间的地址就是指向对象的指针。例如:Base*p_Base;//声明对象指针Baseobj;//定义对象objp_Base=&obj;//指针p_Base指向对象obj……p_Base->show();//通过对象指针访问成员函数38

引入派生类后的对象指针引入派生类概念后,任何一个被说明为指向基类对象的指针都可以指向它的公有派生类对象;指向基类对象的指针,可以指向它的公有派生类的对象,而不能指向它的私有派生类的对象。39

引入派生类后的对象指针

不能将一个声明为指向派生类对象的指针指向其基类的一个对象;声明为指向基类对象的指针,当其指向公有派生类对象时,只能用它来访问派生类中从基类继承来的成员,不能访问公有派生类中新添的成员。例8计算和显示研究生和本科生的学费。40虚函数的作用在例8中,基类中calfee()和disp()无virtual声明,则当指向基类对象的指针ptr指向其派生类Graduate对象时,通过ptr只能访问Graduate中从基类继承下来的成员如calfee()和disp(),而不能访问派生类的calfee()和disp()。41虚函数的作用给基类calfee()和disp()加上virtual声明(即虚函数)后,当ptr指向其派生类Graduate对象时,调用ptr->calfee()和ptr->disp()时就覆盖了基类的同名函数,调用派生类的成员calfee()和disp();虚函数的定义,使用户可通过将指向基类对象的指针指向不同的派生类对象,来调用派生类中与基类同名、实现算法不同的函数,实现运行时的多态。42虚函数的声明虚函数的定义在基类中进行,即在基类中给想定义为虚函数的成员函数声明前加关键词virtual

,即:virtual类型说明符函数名(参数表)在基类中的某成员函数被声明为虚函数后,此虚函数可以在一个或多个派生类中被重新定义;虚函数重新定义时,不需要virtual声明;43虚函数的声明在派生类中重新定义虚函数,要求函数原型包括返回类型、函数名、参数个数、参数类型的顺序,必须与基类中的原型完全一致,仅函数实现不同;可通过成员函数调用、指针或引用来访问虚函数;定义一个指向基类对象的指针ptr,并让它指向同一类族中要调用此虚函数的对象;通过ptr调用的虚函数就是ptr指向的对象的函数。44例9#include<iostream.h>classB0 //基类B0声明{public: //外部接口

virtualvoiddisplay()//虚成员函数

{cout<<"B0::display()"<<endl;}};classB1:publicB0 //公有派生{public:voiddisplay(){cout<<"B1::display()"<<endl;}};classD1:publicB1 //公有派生{public: voiddisplay(){cout<<"D1::display()"<<endl;}};虚函数voidfun(B0*ptr) //普通函数{ptr->display();}voidmain() //主函数{ B0b0,*p; //声明基类对象和指针

B1b1; //声明派生类对象

D1d1; //声明派生类对象

p=&b0; fun(p); //调用基类B0函数成员

p=&b1; fun(p); //调用派生类B1函数成员

p=&d1; fun(p); //调用派生类D1函数成员}运行结果:B0::display()B1::display()D1::display()4546例10虚函数的例子47

虚函数与重载函数的区别函数重载中要求重载函数的参数类型、参数个数不同,仅是函数名相同;虚函数的重定义,要求函数名、返回类型、参数个数、参数类型和顺序都与基类中的函数原型完全一致(特殊的函数重载)。若不一致,按如下情况处理:

1、函数参数相同,仅返回类型不同,作出错处理;

2、函数参数有差异,仅函数名相同,系统会将它认为是一般的函数重载,将丢失虚特性。48在基类中访问虚函数,可使用指向基类对象的指针,也可以使用对象名直接访问;派生类中没重新定义虚函数时,该类的对象将使用基类中的虚函数代码(继承下来的);虚函数必须是一个类的成员函数;构造函数不能被声明为virtual,但析构函数可以被声明为virtual。

虚函数使用注意:

虚析构函数如果派生类层次中包含析构函数,那么把基类的析构函数定义为虚函数若基类的析构函数声明为虚函数,则由该基类派生的所有派生类的析构函数也都自动成为虚函数,即使派生类的析构函数与基类的析构函数不同名虚析构函数的作用保证使用操作符delete时能够调用正确的析构函数虚析构函数的例子不声明虚析构函数的结果虚函数使用注意在同一个类家族中,希望一个指针能指向不同的类,那么把它定义为指向基类定义了指向类家族中对象的指针,且不同类中有同名函数时,使用虚函数定义基类的析构函数为虚函数,可以保证派生类被正确析构虚函数使用注意(续)成员函数才能声明为虚函数构造函数不能是虚函数内联函数不能是虚函数类家族的存在对象尚未建立内联是代码展开,不是地址54如果基类只表达一些抽象的概念,而不和具体的事物相联系,就不在此基类中定义函数。但希望基类必须为它的派生类提供一个公共的界面,可以通过在基类中加入纯虚函数(pruevirtualfunction)来实现。纯虚函数55纯虚函数是一个在基类中说明的虚函数,它在该基类中没有定义,但要求任何派生类都必须定义自己的虚函数版本。纯虚函数定义形式:

virtualtype

函数名(参数表)

=0;“=0”只表示该成员函数是纯虚的。纯虚函数56例如:#include<iostream.h>classshape//定义基类{public:

virtualfloatarea()=0;

//定义纯虚函数

virtualvoidDisp()=0;

//定义纯虚函数};classrectangles:publicshape//定义图形的派生类矩形类{public:

floatarea();//可以定义

voidDisp();

//可以定义};57

纯虚函数使用注意:纯虚函数只有函数的名字,不具备函数功能,不能被调用。只有在派生类中定义以后,它才有函数的功能,才可以调用;纯虚函数的作用只是在基类中保留一个函数的名字,以便派生类中定义它的版本;如果基类中没有保留名字,无法实现多态性;如果在派生类中没有定义纯虚函数的版本,则该虚函数在派生类中仍然是纯虚函数。58抽象类作用抽象类为抽象和设计的目的而声明,将有关的数据和行为组织在一个继承层次结构中,保证派生类具有要求的行为。对于暂时无法实现的函数,可以声明为纯虚函数,留给派生类去实现。注意抽象类只能作为基类来使用。不能声明抽象类的对象。59例11抽象类的例子#include<iostream.h>classB0//抽象基类B0声明{public://外部接口

virtualvoiddisplay()=0;//纯虚函数成员};classB1:publicB0//公有派生{public: voiddisplay(){cout<<"B1::display()"<<endl;}//虚成员函数};classD1:publicB1//公有派生{public: voiddisplay(){cout<<"D1::display()"<<endl;}//虚成员函数};voidfun(B0*ptr) //普通函数{ ptr->display();}voidmain() //主函数{ B0*p; //声明抽象基类指针

B1b1; //声明派生类对象

D1d1; //声明派生类对象

p=&b1; fun(p); //调用派生类B1函数成员

p=&d1; fun(p); //调用派生类D1函数成员}运行结果:B1::display()D1::display()60编写一个程序,计算正方体和球体的表面积和体积重载、覆盖与隐藏重载成员函数被重载的特征:相同的范围(在同一个类中)函数名字相同参数不同与virtual无关把重载函数的本名和参数的数据结合起来,创造函数的新名字例如有如下两个函数原型intmyAns(floatx,int

j);intmyAns(inti,char

c);用如下语句调用ans1=myAns(14.2,25);ans2=myAns(62,’x’);intmyAnsFLAOTINT(flaot

x,int

i);int

myAnsINTCHAR(int

i,

char

c);ans1=myAnsFLAOTINT(14,2,25);ans2=myAnsINTCHAR(62,’x’);classCube{public:Cube(){height=1;width=1;length=1;}Cube(int

h,intw,intl);{height=h;width=w;length=l;}

private:

int

height;

intwidth;

intlength;};voidmain(){Cubecube1;Cubecube2(4,5,6);}Cube()CubeIntIntInt(4,5,6)覆盖覆盖是指派生类函数覆盖基类函数,特征是:不同的范围(分别位于派生类与基类)函数名字相同参数相同基类函数必须有virtual关键字覆盖规则其实就是C++虚函数表的实现原理虚函数表一个虚拟函数地址表,称为vtable。每个类的vtable中记录的是所有声明为virtual的成员函数的地址如果在派生类中没有覆盖基类的成员函数,则在vtable中记录的是基类成员函数的地址如果在派生类中有覆盖到基类的成员函数,则在vtable中记录的是派生类成员函数的地址classbase{

public:

func();

virtualvfunc1();

virtualvfunc2();

virtualvfunc3();

private:

int_data1;

int_data2;

};

classderived:publicbase{

public:vfunc2();};void

main()

{

Derivedd;

Base*pb=&d;

pb->vfunc2();

}

base对象实例

vtable

--------------------------------------------------------------------------

vptr--------->(*vfunc1)()----------->base::vfunc1();

_data1

(*vfunc2)()----------->base::vfunc2();

_data2

(*vfunc3)()----------->base::vfunc3();

--------------------------------------------------------------------------derived对象实例

vtable

--------------------------------------------------------------------------

vptr

--------->(*vfunc1)()----------->base::vfunc1()

_data1;

(*vfunc2)()----------->derived::vfunc2()

_data2;

(*vfunc3)()----------->base::vfunc3()

--------------------------------------------------------------------------

如果基类中有虚函数,编译器会自动为每个由该基类及其派生类所定义的对象加上一个叫v-pointer的指针,简称vptr。编译器还为每个类加上了一个叫做vtable的表vptr指向vtable开头的地方

A对象实例

vtable

-----------------------------------------------

温馨提示

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

评论

0/150

提交评论