Visual C++基础教程课件:多态性和虚函数_第1页
Visual C++基础教程课件:多态性和虚函数_第2页
Visual C++基础教程课件:多态性和虚函数_第3页
Visual C++基础教程课件:多态性和虚函数_第4页
Visual C++基础教程课件:多态性和虚函数_第5页
已阅读5页,还剩77页未读 继续免费阅读

下载本文档

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

文档简介

多态性和虚函数5.1函数重载

5.2运算符重载

5.3虚函数

5.4纯虚函数和抽象类

习题

5.1函数重载所谓函数重载,是指对同一个函数名定义多个不同的实现。另外,在类的继承关系中,在基类和派生类中也可以对成员函数进行重载。在基类和派生类中的函数重载有两种情况:一种是参数类型或个数不同的重载,这种重载通过函数调用时的参数来决定调用的是基类中的成员函数还是派生类中的成员函数;另一种是在基类和派生类中定义的成员函数的函数名、函数参数和返回类型完全相同,只是它们属于不同的类。【例5.1】基类和派生类中同名成员使用示例。

#include<iostream.h>

constdoublePI=3.14159;

classPoint

{

private: intx,y;

public: Point(inta,intb) { x=a;y=b; } doubleArea() { cout<<"Point'sarea:"; return0.0; }

};

classCircle:publicPoint

{

private: intr;

public: Circle(inta,intb,intc):Point(a,b) { r=c; } doubleArea() { cout<<"Circle'sarea:"; returnPI*r*r; }

};

voidmain()

{ Pointp(10,20); cout<<p.Area()<<endl; Circlec(20,30,40); cout<<c.Area()<<endl; cout<<c.Point::Area()<<endl; Point*pp=&p; cout<<pp->Area()<<endl; pp=&c; cout<<pp->Area()<<endl;

}程序运行结果为:

Point'sarea:0

Circle'sarea:5026.54

Point'sarea:0

Point'sarea:0

Point'sarea:0

程序中,在基类Point和派生类Circle中分别定义了函数名、函数参数和返回类型完全相同的成员函数Area()。对于这种同名成员,派生类中的成员覆盖了基类中的同名成员,在派生类中使用这个名字或通过派生类的对象使用这个名字,意味着访问在派生类中的成员。因此,主函数中的函数调用c.Area()调用的是派生类中的成员函数Area()。为了在派生类中或通过派生类的对象使用基类中的同名成员,必须使用成员名限定的方式,即在成员名之前加上基类名和作用域运算符“::”。例如,主函数中的函数调用c.Point::Area()调用的是基类Point中的成员函数Area()。另外,根据赋值兼容规则,在公有派生的情况下,基类的指针可以指向派生类的对象。例如,基类指针pp在主函数中指向了派生类的对象c。但通过这个指针调用基类与派生类中的同名成员时,调用的还是基类的成员,并没有因为当前指针指向派生类的对象而调用派生类中的同名成员。也就是说,无论此指针是指向基类的对象还是派生类的对象,通过这个指针调用的同名成员函数都是定义该指针时的类的成员。5.2运

载运算符重载是对已有的运算符赋予多重含义,同一个运算符作用于不同类型的数据或不同个数的操作数将导致不同的行为发生。实际上,在C++中我们已经见到过运算符的重载。例如,运算符“<<”既可以作为流的插入运算符,又可以作为左移运算符,系统根据运算符操作数的类型自动确定它是何种运算符;运算符“-”既可以作为减法运算符,又可以作为负号运算符,系统根据运算符操作数的个数自动确定它是何种运算符。

C++中预定义的运算符的操作数只能是基本数据类型,但是,对于许多用户自定义类型,比如结构、类等,也需要有类似的运算操作,这时就需要对运算符重新定义,使其适应新的数据类型。在C++中,当表达式中使用预定义的运算符时,编译器将这个表达式作为一个函数调用进行处理。例如,对于表达式

5+8编译器将其中的运算符“+”解释成函数而调用,成为

operator+(5,8)因此,在C++中,运算符的重载实质上就是函数重载,是通过定义一个函数来实现来。运算符的操作数作为函数的参数,系统根据操作数的数据类型或个数来确定需要调用的函数。运算符的重载可以在类的外部,对一般的自定义数据类型进行操作,比如对结构类型等定义运算符,实现对结构变量的操作。但在C++中,最主要的自定义数据类型是类,因此,运算符重载主要是在类中进行重载,用于对对象的操作。

5.2.1运算符重载的规则

C++中对运算符重载制定了以下一些规则:

(1)运算符重载只能对系统已定义的运算符进行重载,不能创造新的运算符。

(2)可以对大多数运算符进行重载,但以下四个运算符不能重载:成员选择运算符“.” 指向成员的指针运算符“.*”作用域运算符“::” 三目运算符“?:”

(3)运算符重载后不改变原来运算符的特性,即优先级和结合性不变。

(4)运算符重载不能改变运算符的操作数个数,即单目运算符只能重载为单目运算符,双目运算符只能重载为双目运算符,唯一的一个三目运算符不能重载。

(5)运算符重载不能指定参数默认值。

(6)运算符重载针对的是新的数据类型的实际需要,对原有运算符进行了适当的改造。一般来讲,重载的功能应当与原有功能类似。例如,将加法运算符重载后完成减法的功能,就会造成使用上的误解。

(7)不能改变运算符对预定义类型数据的操作方式。因此,重载运算符时必须至少有一个自定义类型的形式参数。5.2.2类以外运算符重载

C++中,可以对一般的自定义数据类型重载相应的运算符,使对这种自定义数据类型的操作更加方便。由于C++中唯一的一个三目运算符“?:”不允许重载,因此可以重载的运算符只有单目运算符和双目运算符。

C++中运算符重载必须写一个运算符函数,函数名为关键字operator后加上一个要重载的运算符。单目运算符重载的语法形式如下: 数据类型operator运算符(自定义数据类型形式参数) {

函数体

}双目运算符重载的语法形式如下: 数据类型operator运算符(数据类型1形式参数1,数据类型2形式参数2) {

函数体

}其中,两个形式参数的数据类型必须至少有一个是自定义数据类型。【例5.2】类外运算符重载示例。

#include<iostream.h>

#include<string.h>

structstring

{ char*str; intlen;

};

stringoperator+(strings1,strings2) //重载加法运算符“+”

{ stringtemp; temp.len=strlen(s1.str)+strlen(s2.str); temp.str=newchar[temp.len+1]; inti=0; for(char*p=s1.str;(*p)!='\0';p++,i++) *(temp.str+i)=*p; for(p=s2.str;(*p)!='\0';p++,i++) *(temp.str+i)=*p; *(temp.str+i)='\0'; returntemp;

}

voidmain()

{ strings1,s2; s1.str="thisis"; s1.len=strlen(s1.str); s2.str="astring"; s2.len=strlen(s2.str); strings3; s3=s1+s2; //使用重载的“+”运算符

cout<<s3.str<<','<<s3.len<<endl; strings4; s4=operator+(s1,s2); //使用重载的“+”运算符

cout<<s3.str<<','<<s3.len<<endl;

}程序运行结果为:

thisisastring,16

thisisastring,16

运算符重载后,可以像原来运算符的使用方式那样对新的数据类型的数据进行操作。例如,程序中的语句:s3=s1+s2;将两个字符串进行连接。当然,在程序中也可以使用重载的运算符函数,通过函数调用的方式使用重载的运算符。例如,程序中的语句:

s4=operator+(s1,s2);与上面那条语句是等价的,但显然前者使用更加方便。5.2.3运算符重载为成员函数在C++中,最主要的自定义数据类型是类,运算符重载主要是对类类型进行重载,用来对类的对象进行操作。可以像5.2.2小节中那样在类的外部进行运算符重载,运算符的操作数为类对象。但是这种重载方式下,重载运算符只能访问类中的公有成员,而不能访问类的私有成员。因此,对类的运算符重载一般采用如下两种形式:一是重载为类的成员函数(称为成员运算符函数),二是重载为类的友元函数(称为友元运算符函数)。本小节先介绍重载为成员函数,下一小节介绍重载为友元函数。运算符重载为成员函数的一般语法形式为: 数据类型类名::operator运算符(形参表) {

函数体

}

对于重载的运算符,一般是通过该类的对象来进行访问的。如果是双目运算符,重载为类的成员函数时,形参表中只有一个参数,则这个参数作为运算符的第二操作数,另一个操作数(即第一个操作数)是对象本身,即this指针所指向的对象作为第一操作数。如果是单目运算符,形参表中没有参数,则操作数就是对象本身,即this指针所指向的对象作为单目运算符的操作数。运算符重载为成员函数的定义与普通的成员函数的定义一样,可以在类的说明部分直接定义,也可以只在类的说明部分给出函数原型,在类的实现部分定义。【例5.3】加减运算符重载为类的成员函数。

#include<iostream.h>

classComplex

{

public: Complex() { real=imag=0; } Complex(doubler) { real=r;imag=0.0; } Complex(doubler,doublei) { real=r;imag=i; } Complexoperator+(Complex&c); //重载加法运算符“+”的函数原型

Complexoperator+(double); //重载加法运算符“+”的函数原型

Complexoperator-(Complex&c); //重载减法运算符“-”的函数原型

Complexoperator-(); //重载负号运算符“-”的函数原型

voidDisplay();private: doublereal,imag;

};

ComplexComplex::operator+(Complex&c) //运算符重载函数的定义

{ Complextemp; temp.real=real+c.real; temp.imag=imag+c.imag; returntemp;

}

ComplexComplex::operator+(doubled)

{ Complextemp; temp.real=real+d; temp.imag=imag; returntemp;

}

ComplexComplex::operator-(Complex&c)

{ Complextemp; temp.real=real-c.real; temp.imag=imag-c.imag; returntemp;

}

ComplexComplex::operator-()

{ Complextemp; temp.real=-real; temp.imag=-imag; returntemp;

}

voidComplex::Display()

{ cout<<real; if(imag>0) cout<<'+'; if(imag!=0)cout<<imag<<'i'<<endl;

}

voidmain()

{ Complexc1(2.0),c2(3.0,-1.0),c3; c3=c1.operator+(c2); //调用重载的加法“+”运算符函数

cout<<"c1+c2="; c3.Display(); c3=c1-c2; //调用重载的减法“-”运算符函数

cout<<"c1-c2="; c3.Display(); c3=-c2; //调用重载的负号“-”运算符函数

cout<<"-c2="; c3.Display(); Complexc4; c4=c2+5.0; cout<<“c2+5.0=”;

//调用重载的加法“+”运算符函数

c4.Display();

}程序运行结果为:

c1+c2=5-1i

c1-c2=-1+1i

-c2=-3+1i

c2+5.0=8-1i

当在类中重载运算符后,可以像运算符原有的使用方式一样使用重载的运算符,例如语句:

c3=c1-c2;也可以直接调用运算符函数,例如语句:

c3=c1.operator+(c2);下面举例说明“++”和“--”运算符的重载。运算符“++”和“--”是单目运算符,但这两个运算符既可以放在变量的前面又可以放在变量的后面。为了区分重载“++”和“--”的前置运算和后置运算,在C++中,前置运算符“++”和“--”重载时作为单目运算符,其成员运算符函数没有参数;后置运算符“++”和“--”重载时作为双目运算符,其成员运算符函数有一个整型参数,这个参数在重载时不用,而仅仅作为区分前置和后置的标志。【例5.4】运算符“++”重载为成员函数示例。

#include<iostream.h>

classcounter

{

public: counter(){v=0;} counteroperator++(); //重载运算符++为成员函数的原型(前置运算) counteroperator++(int); //重载运算符++为成员函数的原型(后置运算) voidprint(){cout<<v<<endl;}

private: unsignedv;

};countercounter::operator++(){ v++; cout<<"++counter:"<<endl; return*this;}countercounter::operator++(int){ countert; t.v=v++; cout<<"counter++:"<<endl; returnt;}

voidmain()

{ counterc1,c2; c2=++c1;

//调用重载的前置++运算符函数

cout<<"c1="; c1.print(); cout<<"c2="; c2.print(); c2=c1++; //调用重载的后置++运算符函数

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

cout<<"c2="; c2.print(); cout<<"-------------"<<endl; counterc3,c4; c3.operator++(0);//显式调用重载的后置++运算符函数

cout<<"c3="; c3.print(); ++c4; cout<<"c4="; c4.print();

}程序运行结果如下:

++counter:

c1=1

c2=1

counter++:

c1=2

c2=1

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

counter++:

c3=1

++counter:

c4=1运算符“++”和“--”的操作数只能是变量,当它们与变量组成表达式时,前置和后置运算对表达式将产生不同的结果,但对变量本身来说结果是相同的。因此,在使用运算符“++”和“--”时,如果要使用表达式的值,则要区分前置运算和后置运算,如果只是希望对变量进行自增或自减运算,则前置运算与后置运算的结果相同。程序中,语句:

c2=++c1; 运行时,对象c2取表达式++c1的值,同时,c1自增1。对于前置运算,表达式的值与变量的值相同,因此,对象c2与c1的值相同。语句:

c2=c1++; 运行时,先将对象c1的值作为表达式的值赋值给对象c2,然后将对象c1自增1。因此,对象c2与c1的值不相同。

对于运算符++和--的前置运算,变量的值与表达式的值相同,因此,在重载的前置++运算符函数中,函数的返回值就是this指针所指向对象的值。如下所示:

countercounter::operator++()

{ v++; return*this;

}

对于运算符++和--的后置运算,变量的值与表达式的值不相同,因此,在重载的后置++运算符函数中,函数的返回值与this指针所指向对象的值不相同。如下所示:

countercounter::operator++(int)

{ countert; t.v=v++; returnt;

}5.2.4运算符重载为友元函数运算符也可以重载为类的友元函数,这样,它就可以访问类的任何数据成员和成员函数。运算符重载为友元函数的一般形式如下:

friend数据类型operator运算符(形参表) {

函数体

}

由于友元函数不是类的成员,它没有this指针,运算符所需的操作数都需要通过函数的参数来传递,因此,运算符重载为友元函数时,对于单目运算符,有一个参数,对于双目运算符,有两个参数。【例5.5】将运算符--和+重载为类的友元函数示例。

#include<iostream.h>

classcounter

{

public: counter(){v=0;} friendcounteroperator--(counter&); //友元函数的原型(前置运算) friendcounteroperator--(counter&,int); //友元函数的原型(后置运算) friendcounteroperator+(counter,counter); //友元函数的原型

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

private: intv;

};

counteroperator--(counter&c)

{ c.v--; cout<<"--counter:"<<endl; returnc;

}

counteroperator--(counter&c,int)

{ countert; t.v=c.v--; cout<<"counter--:"<<endl; returnt;

}

counteroperator+(counterc1,counterc2)

{ countert; t.v=c1.v+c2.v; returnt;

}

voidmain()

{ counterc1,c2; c2=--c1; cout<<"c1="; c1.print(); cout<<"c2="; c2.print();

c2=c1--; cout<<"c1="; c1.print(); cout<<"c2="; c2.print(); cout<<"-------------"<<endl; counterc3,c4; operator--(c3,0); //显式调用重载的++后置友元运算符函数

cout<<"c3="; c3.print(); --c4; cout<<"c4="; c4.print(); counterc5; c5=operator+(c3,c4); //显式调用重载的+友元运算符函数

cout<<"c5="; c5.print();}程序运行结果如下:

--counter:

c1=-1

c2=-1

counter--:

c1=-2

c2=-1

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

counter--:

c3=-1

--counter:

c4=-1

c5=-2友元函数没有this指针,运算符的操作数将作为函数的参数传递给运算符。运算符++和--可以改变对象的值,改变后的值需要通过参数返回,因此,运算符++和--重载为友元函数时,应采用对象的引用作为参数。与重载为成员函数一样,运算符++和--重载为友元函数时,前置运算与后置运算的函数返回值不同,因此,一般两个函数的函数体不同。

C++中的大部分运算符既可以重载为成员函数,又可以重载为友元函数。一般而言,对于单目运算符,常将它重载为成员函数;对于双目运算符,将它重载为一个友元函数比重载为一个成员函数更便于使用。例如,对于例5.3中复数加法重载,下述表达式的计算:

c+8.2其中,c是类Complex的一个对象。在将加法运算符重载为友元函数时,该表达式被解释为:

operator+(c,8.2);

在将加法运算符重载为成员函数时,该表达式被解释为:

c.operator+(8.2);

由于类中有单参数的构造函数,单参数构造函数具有类型转换的功能,它能将参数类型转换为构造函数所在类的类型,因此,上述两种解释都是正确的。但是,如果将表达式写为:

8.2+c在将加法运算符重载为成员函数时,该表达式将被解释为:

8.2.operator(c)显然是错误的。可见,如果运算符所需的第一操作数希望有隐式类型转换,则应将双目运算符重载为友元函数。若一个运算符的操作需要改变第一操作数的状态,则应选择重载为成员函数。例如,赋值运算符操作后会改变第一操作数的值,应重载为成员函数。因此,双目运算符是重载为友元函数,还是重载为成员函数,还应具体情况具体分析。5.3虚函数5.3.1虚函数的定义虚函数定义的语法形式如下:

virtual数据类型函数名(参数表) {

函数体

}虚函数的定义实际上就是在原有普通成员函数的前面使用关键字virtual来限定。如果虚函数的定义放在类的说明部分,则在前面加上关键字virtual。如果虚函数的定义在类的实现部分,则需在类的说明部分用关键字virtual限定其函数原型,在定义函数时不能使用关键字virtual。如果在基类中某个成员函数被声明为虚函数,则意味着该成员函数在派生类中可能有其它的实现。当程序运行时,通过基类的指针调用虚函数时,系统能够根据该指针所指向的对象自动调用相应类的成员函数,实现动态多态性。【例5.6】虚函数使用示例。

#include<iostream.h>

constdoublePI=3.14159;

classPoint

{

private: intx,y;

public: Point(inta,intb) { x=a;y=b; } virtualdoubleArea() //定义虚函数

{ cout<<"Point'sarea:"; return0.0; }

};

classCircle:publicPoint

{

private: intr;

public: Circle(inta,intb,intc):Point(a,b) { r=c; } virtualdoubleArea() //派生类中重载虚函数

{ cout<<"Circle'sarea:"; returnPI*r*r; } doubleGetR() { returnr; }

};

classCylinder:publicCircle

{

private: inth;

public: Cylinder(inta,intb,intc,intd):Circle(a,b,c) { h=c; } virtualdoubleArea();

//虚函数的函数原型

};

doubleCylinder::Area()

{ cout<<"surfaceareaofCylinder:"; doubler=GetR(); doublearea=2*PI*r+2*PI*r*r; returnarea;

}

voidDisplayArea(Point&rp)

{ cout<<rp.Area()<<endl;

}

voidmain()

{ Pointp(10,20),*pp,&rp1=p; pp=&p; cout<<pp->Area()<<endl; //① DisplayArea(rp1); //② Circlec(20,30,40); pp=&c; cout<<pp->Area()<<endl; //③ Point&rp2=c; DisplayArea(rp2); //④ Cylindercy(30,40,50,60); pp=&cy; cout<<pp->Area()<<endl; //⑤ Point&rp3=cy; DisplayArea(rp3); //⑥

}程序运行结果为:

Point'sarea:0

Point'sarea:0

Circle'sarea:5026.54

Circle'sarea:5026.54

surfaceareaofCylinder:16022.1

surfaceareaofCylinder:16022.1

程序中定义了三个类Point、Circle和Cylinder,它们之间都是公有继承的关系。在基类Point中定义了虚函数Area(),用来求面积。派生类Circle和Cylinder中都定义了同样的函数来求圆的面积和圆柱体的表面积。主函数中,定义了基类Point的指针pp,在执行语句①、③和⑤时,基类指针分别指向了不同类的对象,通过相同的函数调用得到了不同对象的面积,即根据当时指针所指向的对象,系统调用了相应类的Area()函数。程序中还定义了DisplayArea()函数,它用基类Point的引用作为参数。在主函数调用它时,语句②、④和⑥分别用基类的对象、派生类Circle的对象和Cylinder的对象初始化该引用。从程序运行结果看,系统也根据初始化引用的对象调用了相应类的成员函数Area()。关于虚函数的定义和使用说明如下:

(1)在派生类中对基类的虚函数重新定义时,关键字virtual可以省略。这时派生类中的同名成员函数隐含为虚函数。

(2)在派生类中重新定义虚函数时,其函数原型与基类中的函数原型必须完全相同,即派生类中定义的成员函数应与基类中的虚函数的函数名、参数类型和个数、函数返回类型必须完全一致。

(3)只有通过基类的指针或引用调用虚函数时,对该虚函数的调用才能实现动态多态性。通过基类的对象调用虚函数时,系统采用的是静态联编。如果将例5.6中通过基类的指针调用Area()的方式改为通过基类的对象调用Area(),会有什么结果,请读者自己思考。

(4)构造函数不能声明为虚函数,但析构函数可以是虚函数。

(5)在构造函数中调用虚函数时,采用静态联编,即构造函数中调用的虚函数是自己类中或基类中定义的函数,而不是任何在派生类中重新定义的虚函数。5.3.2虚析构函数构造函数用来在创建对象时初始化对象,而虚函数是实现动态多态性的基础,只有在程序运行过程中,对象创建之后才能实现动态多态性,因此,将构造函数说明为虚函数没有意义。构造函数不能被说明为虚函数,但析构函数可以被定义成虚函数。其语法格式是在析构函数的声明前加上关键字virtual:

virtual~类名();

如果一个基类的析构函数被说明为虚函数,则由它派生的所有派生类的析构函数,无论是否使用virtual进行说明,都将自动成为虚函数。析构函数用来在对象撤销时做一些必要的清理工作。如果使用基类指针指向由new运算符创建的派生类的对象,当程序中使用delete运算符撤销这个对象时,如果析构函数不是虚函数,则只调用基类的析构函数(因为指针类型是基类类型)。【例5.7】非虚析构函数在撤销对象时的示例。

#include<iostream.h>

classA

{

public: A(){}; ~A() { cout<<"A::destructorcalled"<<endl; }

};

classB:publicA

{

public: B(){} ~B() { cout<<"B::destructorcalled"<<endl; }

};

voidmain()

{ A*pa=newB; //其它语句

deletepa;

}程序运行结果为:

A::destructorcalled从程序运行结果看,虽然程序中创建了派生类B的对象,并由基类指针指向了该对象,但在使用delete运算符撤销基类指针指向的派生类对象时,只调用了基类的析构函数,并没有调用派生类的析构函数。这样有可能对派生类的清理工作不完整。例如,如果派生类中使用new运算符分配了存储空间,由于没有调用派生类的析构函数,则这部分存储空间就有可能没有得到释放。产生这种结果的主要原因是析构函数是非虚函数,编译器采用的是静态多态性。如果将析构函数说明为虚函数,则当使用运算符delete撤销对象时,采用动态多态性,能确保析构函数的正确执行。【例5.8】虚析构函数的使用。

#include<iostream.h>

classA

{

public: A(){}; virtual~A() //析构函数被说明为虚函数

{ cout<<"A::destructorcalled"<<endl; }

};

classB:publicA

{

public: B(){} ~B() //派生类的析构函数也为虚函数

{ cout<<"B::destructorcalled"<<endl; }

};

voidmain()

{ A*pa=newB; //其它语句

deletepa;

}程序运行结果为:

B::destructorcalled A::destructorcalled从程序运行结果看,派生类的析构函数被正确执行了。由于使用了虚析构函数,程序采用动态多态性,当撤销基类指针pa所指向的派生类B的对象时,首先调用派生类B的析构函数,然后再调用基类A的析构函数。一般来说,如果一个类中定义了虚函数,则析构函数也应说明为虚函数,尤其是在析构函数中要完成一些有意义的任务时,例如释放内存等。5.4纯虚函数和抽象类5.4.1纯虚函数在许多情况下,在基类中不能为虚函数给出一个有意义的定义。例如,在例5.6的基类Point中的求面积函数Area(),事实上没有实际意义。因而可以在基类中不给出其具体的实现,而将它的定义留给基类的派生类去完成。为此,C++中提供了纯虚函数。纯虚函数是一个在基类中说明的虚函数,它在该基类中没有定义,但要求在它的派生类中进行定义。说明纯虚函数的一般形式为:

virtual数据类型函数名(参数表)=0;

纯虚函数是为了实现动态多态性而存在的。5.4.2抽象类如果一个类中至少包含有一个纯虚函数,则该类称为抽象类。抽象类的主要作用是用来组织一个类的层次结构,由它来作为基类,为其所有的派生类提供共同的操作接口。抽象类具有以下特性:

(1)抽象类只能作为其它类的基类,不能建立抽象类的对象。

(2)可以说明指向抽象类的指针和引用,此指针可以指向其派生类,进而实现动态多态性。从一个抽象类派生的类必须提供纯虚函数的实现,或在该派生类中仍将它说明为纯虚函数,否则编译时将给出错误信息。如果派生类给出了所有纯虚函数的函数实现,那么这个派生类就不再是抽象类了,它可以定义自己的对象。反之,如果派生类中只说明了纯虚函数而没有给出全部纯虚函数的实现,则这个派生类仍然是一个抽象类。【例5.9】纯虚函数和抽象类使用示例。

#include<iostream.h>

constdoublePI=3.1415;

classShape

{

public: virtualdoubleArea()=0; //说明纯虚函数

};

classRectangle:publicShape

{

private: doubleh,w;

public: Rectangle(doublea,doubleb) { h=a;w=b; } doubleArea() //在派生类中实现纯虚函数

{ cout<<"Rectangle'sarea:"; returnh*w; }

};

classCircle:publicShape

{

private: doubler;

public: Circle(doublea) { r=a; } doubleArea() //在派生类中实现纯虚函数

{ cout<<"Circle'sarea:"; returnPI*r*r; }

};

classTrapezoid:publicShape

{

private: doublet,b,h;

public: Trapezoid(doubletop,doublebottom,doublehigh) { t=top;b=bottom;h=high; } doubleArea() //在派生类中实现纯虚函数

{ cout<<"Trapezoid'sarea:"; return(t+b)*h*0.5; }

};

voidmain()

{ Shape*p; Rectanglerectangle(5.0,8.0); p=&rectangle; cout<<p->Area()<<endl; Circlecircle(7.8); p=&circle; cout<<p->Area()<<endl; Trapezoidtrapezoid(12.5,7.5,8.0); p=&trapezoid; cout<<p->Area()<<endl;

}程序运行结果如下:

Rectangle'sarea:40

Circle'sarea:191.129

Trapezoid'sarea:80在程序中定义了一个公共基类Shape为抽象类,在其中说明了求面积的函数Area()为纯虚函数,为其派生类提供统一的操作接口,即规定了其派生类中求面积的成员函数都应为Area()函数,并且其派生类必须实现此函数才能定义对象。类Shape的三个派生类代表不同的图形,其求面积的算法各不相同,但在主函数中,求三个图形面积的操作接口都相同,即都是通过p->Area()来求面积的。习题

1.什么是多态性?C++中有哪几种多态性?它们之间有什么区别?

2.简述运算符重载的规则。

3.C++中哪些运算符不能重载?

4.将运算符重载为成员函数和友元函数分别使用什么格式?它们之间有什么不同?

5.什么是虚函数?虚函数与函数重载有什么异同?

6.什么是纯虚函数?什么是抽象类?

7.要想在程序中实现动态多态性,需要哪些条件?

8.构造函数能否说明为虚函数?虚析构函数有什么功能?

9.分析下列程序,写出程序运行结果。

(1)

#include<iostream.h>

classB{ intb;

p

温馨提示

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

评论

0/150

提交评论