文库发布:c第7章多态性_第1页
文库发布:c第7章多态性_第2页
文库发布:c第7章多态性_第3页
文库发布:c第7章多态性_第4页
文库发布:c第7章多态性_第5页
已阅读5页,还剩41页未读 继续免费阅读

下载本文档

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

文档简介

第7章多态性

2011年10月12日主要内容多态性的实现类型联编虚函数抽象类函数重载运算符重载7.1多态性的实现类型多态性就是向不同的对象发送同一个消息,不同的对象在接收时会产生不同的行为。面向对象的多态性分为四类:

7.2联编多态性的实现过程中,确定调用哪一个同名函数的过程就是联编,又称为绑定。按照联编进行的阶段不同,可以分为静态联编和动态联编,这两种联编分别对应C++面向对象技术多态特性的两种实现方式。7.2.1静态联编

静态联编是指在编译阶段完成的联编方式。在编译过程中,编译系统可以根据参数类型和参数数量的不同来确定调用哪一个同名函数。其特点是函数调用速度快、效率高,不足之处是编程不够灵活。例7-1.静态联编使用#include<iostream>usingnamespacestd;classUndergraduate{public: voidDisplay(){

cout<<"CallBaseClass"<<endl;

cout<<"UndergraduateLiMing"<<endl; }};classMaster:publicUndergraduate{public: voidDisplay(){

cout<<"CallMasterClass"<<endl;

cout<<"MasterWangWei"<<endl; }};classDoctor:publicMaster{

public:

voidDisplay(){

cout<<"CallDoctorClass"<<endl;

cout<<"DoctorZhangHua"<<endl;

}

};

voidmain(){

Undergraduates1,*pointer;//定义基类对象s1和指向基类的指针

Masters2;//定义派生类对象s2

Doctors3;//定义派生类对象s3

pointer=&s1;//指针pointer指向基类对象s1

pointer->Display();

pointer=&s2;//指针pointer指向基类对象s2

//期望调用对象s2的函数display,但实际执行却调用了对象s1的输出函数

pointer->Display();

pointer=&s3;

//期望调用对象s3的函数display,但实际执行却调用了对象s1的输出函数

pointer->Display();

}程序运行结果

CallBaseClassUndergraduateLiMingCallBaseClassUndergraduateLiMingCallBaseClassUndergraduateLiMing7.2.2动态联编只有在程序运行时才能确定将要调用哪一个函数。动态联编的主要优点是使编程更具灵活性,对问题的抽象更方便,程序的易维护性更好;其缺点是与静态联编相比,函数调用速度更慢。在编译阶段不能解决的联编问题,需要等到程序运行以后才能确定,则选择动态联编,这就需要虚函数来解决7.3虚函数虚函数是动态联编的主要实现方式,是动态联编的基础。虚函数的作用是允许在派生类中重新定义与基类同名的函数,并且可以通过基类指针或引用来访问基类和派生类中的同名函数,声明方式是:virtual返回值类型函数名(参数表){

函数体;}7.3.1虚函数的使用(1)在基类声明成员函数为虚函数,在类外定义虚函数是不必再加virtual。(2)在派生类中重新定义此函数时,要求函数名、函数类型、参数个数和参数类型全部与虚函数相同,否者会被认为是普通的函数重载。并根据需要重新定义函数体。C++规定当一个成员函数被定义成虚函数后,其派生类中的同名函数都自动成为虚函数,故virtual也可不加,但为了提高可读性,最好都加上。(3)定义一个指向基类对象的指针变量,并使它指向同一类族中的某一对象。

(4)通过该指针调用此虚函数,此时调用的就是指针变量指向的对象的同名函数。例7-2.通过对象引用调用虚函数实现动态联编

#include<iostream.h>classUndergraduate{public: virtualvoidprint(){

cout<<"CallBaseClass"<<endl;

cout<<"UndergraduateLiMing"<<endl; }};classMaster:publicUndergraduate{public: virtualvoidprint(){

cout<<"CallMaterClass"<<endl;

cout<<"MasterWangWei"<<endl; }};

voidfunction(Undergraduate&s){

s.print();//通过对象引用调用虚函数

}

voidmain(){

Undergraduates1;

Masters2;

function(s1);

function(s2);

}

程序运行结果:CallBaseClassUndergraduateLiMingCallMaterClassMasterWangWei应当声明虚函数的情况:(1)首先成员函数所在的类必须会作为基类,然后看成员函数在类的继承之后有无可能被更改功能,如果希望更改其功能的,一般将其声明为虚函数,若不更改,则不需声明虚函数。(2)考虑对成员函数的调用是通过对象名还是通过基类指针或引用,如果是通过基类指针或引用去访问的,则应当声明为虚函数。(3)有时定义虚函数时,函数体是空的,其功能留给派生类去增加。7.3.2虚析构函数如果用new运算符建立了临时对象,若基类中有析构函数,并且定了了一个指向该基类的指针变量。在程序用带指针参数的delete运算符撤销对象是会发生一个情况:系统只会执行基类的析构函数,而不执行派生类的析构函数。为解决此问题,可将基类的析构函数声明为虚函数

virtual~类名(){

函数体

}7.4纯虚函数和抽象类纯虚函数是在声明虚函数时被“初始化”为0的函数,一般形式为Virtual函数类型函数名(参数表)=0;不用来定义对象而只作为一种基本类型用作继承的类,称为抽象类。由于它常用作基类,通常称为抽象基类。凡是包含纯虚函数的类都是抽象类。例7-3使用纯虚函数

#include<iostream.h>classpoint{

public:

point(inti=0,intj=0){x0=i;y0=j;}

virtualvoidset()=0;

virtualvoiddraw()=0;

protected:

intx0,y0;};classline:publicpoint{

public:

line(inti=0,intj=0,intm=0,intn=0):point(i,j){

x1=m;y1=n;

}

voidset(){cout<<"line::set()called.\n";}

voiddraw(){cout<<"line::draw()called.\n";}

protected:

intx1,y1;

};

classellipse:publicpoint{

public:

ellipse(inti=0,intj=0,intp=0,intq=0):point(i,j){

x2=p;y2=q;

}

voidset(){cout<<"ellipse::set()called.\n";}

voiddraw(){cout<<"ellipse::draw()called.\n";}

protected:

intx2,y2;

};

voiddrawobj(point*p){

p->draw();

}

voidsetobj(point*p){

p->set();

}

voidmain(){

line*lineobj=newline;

ellipse*elliobj=newellipse;

drawobj(lineobj);

drawobj(elliobj);

cout<<endl;

setobj(lineobj);

setobj(elliobj);

cout<<"\nRedrawtheobject…\n";

drawobj(lineobj);

drawobj(elliobj);

}

程序运行结果line::draw()called.ellipse::draw()called.

line::set()called.ellipse::set()called.

Redrawtheobject…line::draw()called.ellipse::draw()called.

综上所述,可将纯虚函数归结为:抽象类的唯一用途是为派生类提供基类,纯虚函数的作用是作为派生类的成员函数的基础,并实现动态多态性。7.5函数重载函数重载是指两个或两个以上的函数具有相同的函数名,但参数类型不一致或参数个数不同,从而使重载的函数虽然函数名相同,但功能上却不完全相同。函数重载包括成员函数的重载和普通函数的重载。例7-4构造函数进行重载#include<iostream.h>#include<string.h>classstring{

public:

string(char*s);

string(string&s1);

string(intsize=80);

~string(){deletesptr;}

int

getlen(){returnlength;}

voidprint(){cout<<sptr<<endl;}

private:

char*sptr;

intlength;};string::string(char*s){length=strlen(s);

sptr=newchar[length+1];

strcpy(sptr,s);}string::string(string&s1){length=s1.length;

sptr=newchar[length+1];

strcpy(sptr,s1.sptr);}string::string(intsize){length=size;

sptr=newchar[length+1];*sptr='\0';}voidmain(){stringstr1("Thisisastring.");str1.print();

cout<<str1.getlen()<<endl;char*s1="Thatisaprogram.";stringstr2(s1);stringstr3(str2);str3.print();

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

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

函数体}重载为类成员函数时

参数个数=原操作数个数-1 (后置++、--除外)运算符成员函数的设计双目运算符B如果要重载B为类成员函数,使之能够实现表达式oprd1Boprd2,其中

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

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

oprd1Boprd2

相当于oprd1.operatorB(oprd2)例7-5

将“+”、“-”运算重载为复数类的成员函数。规则:实部和虚部分别相加减。操作数:两个操作数都是复数类的对象。#include<iostream>usingnamespacestd;classcomplex //复数类声明{public: //外部接口

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

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

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

voiddisplay(); //输出复数private: doublereal; //复数实部

doubleimag; //复数虚部}; complexcomplex::operator+(complexc2)//重载函数实现{ complexc;

c.real=c2.real+real;

c.imag=c2.imag+imag; returncomplex(c.real,c.imag);}complexcomplex::operator-(complexc2)//重载函数实现{ complexc;

c.real=real-c2.real;

c.imag=imag-c2.imag; returncomplex(c.real,c.imag);}voidcomplex::display(){cout<<"("<<real<<","<<imag<<")"<<endl;}voidmain()//主函数{ complexc1(5,4),c2(2,10),c3;//声明复数类的对象

cout<<"c1=";c1.display();

cout<<"c2=";c2.display();

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

cout<<"c3=c1-c2="; c3.display();

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

cout<<"c3=c1+c2="; c3.display();}程序输出的结果为:c1=(5,4)c2=(2,10)c3=c1-c2=(3,-6)c3=c1+c2=(7,14)运算符成员函数的设计前置单目运算符U如果要重载U为类成员函数,使之能够实现表达式Uoprd,其中

oprd

为A类对象,则U应被重载为A类的成员函数,无形参。经重载后,

表达式

Uoprd

相当于oprd.operatorU()后置单目运算符++和--如果要重载++或--为类成员函数,使之能够实现表达式

oprd++

或oprd--

,其中

oprd

为A类对象,则++或--应被重载为A类的成员函数,且具有一个int

类型形参。经重载后,表达式

oprd++

相当于oprd.operator++(0)例7-6运算符前置++和后置++重载为时钟类的成员函数。前置单目运算符,重载函数没有形参,对于后置单目运算符,重载函数需要有一个整型形参。操作数是时钟类的对象。实现时间增加1秒钟。#include<iostream>usingnamespacestd;classClock //时钟类声明{public: //外部接口

Clock(int

NewH=0,int

NewM=0,int

NewS=0){ Hour=NewH; Minute=NewM; Second=NewS; } voidShowTime(){cout<<Hour<<":"<<Minute<<":"<<Second<<endl;} Clock&operator++();//前置单目运算符重载

Clockoperator

++(int);//后置单目运算符重载

private: //私有数据成员

int

Hour,Minute,Second;};Clock&Clock::operator++() //前置单目运算符重载函数{ Second++;

if(Second>=60) {Second=Second-60; Minute++;

if(Minute>=60) { Minute=Minute-60; Hour++; Hour=Hour%24; } }return*this;}ClockClock::operator++(int) //后置单目运算符重载{ Clockold=*this;++(*this);

returnold;}voidmain(){ ClockmyClock(23,59,59);

cout<<"Firsttimeoutput:";

myClock.ShowTime();

cout<<"ShowmyClock++:"

温馨提示

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

评论

0/150

提交评论