《c 程序设计基础》第九章-多态性_第1页
《c 程序设计基础》第九章-多态性_第2页
《c 程序设计基础》第九章-多态性_第3页
《c 程序设计基础》第九章-多态性_第4页
《c 程序设计基础》第九章-多态性_第5页
已阅读5页,还剩127页未读 继续免费阅读

付费下载

下载本文档

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

文档简介

C++程序设计基础第9章多态性北京邮电大学信通院方莉mrs.fangli@

多态性(Polymorphism)是面向对象程序设计的主要特征之一。多态性对于软件功能的扩展和软件重用都有重要的作用。是学习面向对象程序设计必须要掌握的主要内容之一。本章主要内容多态性的基本概念运行多态的实现,虚函数模板第9章多态性2第9章多态性9.1多态性的概念19.2重载、覆盖与静态联编29.3虚函数与运行时的多态39.4纯虚函数和抽象类49.5模板539.1.1面向对象程序设计中的多态多态性是指同样的消息被不同类型的对象接收时导致完全不同的行为。消息:指示要调用类的某个成员函数。行为:成员函数执行的结果被视为对象的行为。4面向对象程序设计中多态性表现为以下几种形式:

①重载多态:通过调用相同名字的函数,表现出不同的行为。运算符重载是一种重载多态。②运行多态:通过基类的指针,调用不同派生类的同名函数(虚函数),表现出不同的行为。许多面向对象程序设计的书籍中所说的多态性,就是这种多态。③模板多态,也称为参数多态:通过一个模板,得到不同的函数或不同的类。这些函数或者类具有不同的特性和不同的行为。9.1.1面向对象程序设计中的多态5一个具有多态性的程序语句,在执行的时候,必须确定究竟是调用哪一个函数。确定具有多态性的语句究竟调用哪个函数的过程称为联编(Binding),有的资料也翻译成“绑定”。联编有两种方式:静态联编动态联编9.1.2多态的实现:联编6在源程序编译的时候就能确定具有多态性的语句调用哪个函数,称为静态联编。对重载函数的调用就是在编译的时候确定具体调用哪个函数,所以是属于静态联编。模板在编译时确定被调函数,属于静态联编。静态联编7动态联编则是在程序运行时,才能够确定具有多态性的语句究竟调用哪个函数。用动态联编实现的多态,也称为运行时的多态。虚函数是支持运行多态的基础。动态联编8第9章多态性9.1多态性的概念19.2重载、覆盖与静态联编29.3虚函数与运行时的多态39.4纯虚函数和抽象类49.5模板599.2.1重载和静态联编以下是一个重载函数的例子。intadd(inta){returna+10;}intadd(inta,intb){returna+b;}intmain(){intx=1,y=2;

add(x);add(x,y);return0;}109.2.2覆盖与静态联编覆盖(Override)现象只能出现在继承树中。在派生类中定义和基类中同名的成员函数,是对基类进行改造,为派生类增加新的行为的一种常用的方法。在某些情况下,覆盖会导致静态联编;另外一些情况下,覆盖会导致动态联编。119.2.2覆盖与静态联编①通过派生类对象调用同名成员函数②通过基类指针调用同名成员函数

12一.通过派生类对象调用同名成员函数

在派生类中可以定义和基类的成员函数同名的成员函数。这是对基类进行改造,是派生类增加新的行为的一种常用的方法。在程序编译的时候,就可以确定派生类对象具体调用哪个同名的成员函数。这是通过静态联编实现的多态。13例9-1用派生类对象访问继承树中的同名函数//以下代码仅为示例,无法编译classB{public:f(){…}}; //基类classP:publicB{public:f(){…}}; //派生类1classQ:publicB{public:f(){…}}; //派生类2voidmain(){ Pp; //创建派生类P的对象Qq; //创建派生类Q的对象

p.f(); //调用P::f()

q.f(); //调用Q::f()}一.通过派生类对象调用同名成员函数

14

例1:定义Circle类和Rectangle类为Shape类的派生类,通过Circle类和Rectangle类的对象调用重载函数getArea()显示对象的面积。

//例1:shape.hclassShape{public:

doublegetArea()const;voidprint()const;};一.通过派生类对象调用同名成员函数

15

classCircle:publicShape{ public:Circle(int=0,int=0,double=0.0);

doublegetArea()const;//返回面积voidprint()const;private:intx,y;//圆心doubleradius;//半径};一.通过派生类对象调用同名成员函数

16

classRectangle:publicShape{ public: Rectangle(int=0,int=0);

doublegetArea()const;//面积

voidprint()const;private: inta,b;//长和宽};一.通过派生类对象调用同名成员函数

17

//例1:shape.cpp#include<iostream>usingnamespacestd;#include"shape.h" doubleShape::getArea()const{cout<<"基类的getArea函数,面积是";return0.0;} voidShape::print()const{ cout<<"BaseclassObject"<<endl;}

一.通过派生类对象调用同名成员函数

18

Circle::Circle(intxValue,intyValue,doubleradiusValue){ x=xValue;y=yValue; radius=radiusValue;} doubleCircle::getArea()const{cout<<"Circle类的getArea函数,面积是";

return3.14159*radius*radius;} voidCircle::print()const{cout<<"centeris";cout<<"x="<<x<<"y="<<y;cout<<";radiusis"<<radius<<endl;}

一.通过派生类对象调用同名成员函数

19

Rectangle::Rectangle(intaValue,intbValue){ a=aValue;b=bValue;} doubleRectangle::getArea()const{cout<<"Rectangle类的getArea函数,面积是";

returna*b;} voidRectangle::print()const{cout<<"hightis"<<a;cout<<"widthis"<<b<<endl;}

一.通过派生类对象调用同名成员函数

20

//例1:chapter9_1.cpp#include<iostream>usingnamespacestd;#include"shape.h"voidmain(){Circlecircle(22,8,3.5);Rectanglerectangle(10,10);

cout<<"调用的是";cout<<circle.getArea()<<endl; //静态联编

cout<<"调用的是";cout<<rectangle.getArea()<<endl;//静态联编}

调用的是Circle类的getArea函数,面积是38.4845调用的是Ractangle类的getArea函数,面积是100一.通过派生类对象调用同名成员函数

21通过派生类对象调用同名成员函数,可以有以下的结论:①派生类对象可以直接调用本类中与基类成员函数同名的函数,不存在二义性;②在编译时就能确定通过对象将调用哪个函数,属于静态联编。Circlecircle(22,8,3.5);Rectanglerectangle(10,10);circle.getArea();rectangle.getArea();一.通过派生类对象调用同名成员函数

22二.通过基类指针调用同名成员函数例9-2用指针访问继承树中的同名函数//以下代码仅为示例,无法编译classB{public:f(){…}}; //基类classP:publicB{public:f(){…}}; //派生类1classQ:publicB{public:f(){…}}; //派生类2voidmain(){

B*b_ptr; Pp; Qq; //定义基类的指针和派生类的对象

b_ptr=&p; //基类指针指向派生类 b_ptr->f(); //通过基类指针调用B::f()

b_ptr=&q; //基类指针指向派生类 b_ptr->f(); //通过基类指针调用B::f()}23二.通过基类指针调用同名成员函数

从继承的角度来看,派生类对象是基类对象的一个具体的特例。或者说,派生类对象是某一种特定类型的基类对象。例如,Circle类是Shape类的公有继承,“圆”是“图形”的一种特例。或者说,圆是一种特定的图形,具有图形的基本特征。

24在关于基类对象和派生类对象的操作上,可以允许以下的操作:①可以用派生类对象给基类对象赋值;

例:Circlecircle(22,8,3.5);Rectanglerectangle(10,10);Shapea=circle;Shapeb=rectangle;二.通过基类指针调用同名成员函数

25②可以用派生类对象初始化基类的引用。例:Shape&sp=circle;

③可以用派生类对象的地址给基类指针赋值。例:Shape*pc=&rectangle;通过该指针,可以访问基类的公有成员。二.通过基类指针调用同名成员函数

26以下这些操作是不能进行的:①不能用基类对象给派生类对象赋值;例:Shapesp;

Circler=sp;//error②不能用基类对象的地址初始化派生类对象的指针;例:Shapesp;Circle*pc=&sp;//error③基类对象不能初始化派生类的引用;例:Shapesp;Circle&pc=sp;//error二.通过基类指针调用同名成员函数

27

例2在例1所定义的类的基础上,观察通过派生类对象地址初始化的基类对象的指针访问getArea函数的结果。#include<iostream>usingnamespacestd;#include"shape.h" voidmain(){Shape*shape_ptr;

Circlecircle(22,8,3.5);

Rectanglerectangle(10,10);

shape_ptr=&circle;

//Circle类对象地址初始化基类指针

cout<<"circle对象初始化shape_ptr指针访问的getArea函数是"<<endl;cout<<shape_ptr->getArea()<<endl;//静态联编

shape_ptr=&rectangle;

//Rectangle类对象地址初始化基类指针

cout<<"rectangle对象初始化shape_ptr指针访问的getArea函数是"<<endl;cout<<shape_ptr->getArea()<<endl;//静态联编}circle对象初始化shape_ptr指针访问的getArea函数是基类的getArea函数,面积是0rectangle对象初始化shape_ptr指针访问的getArea函数是基类的getArea函数,面积是028程序运行结果表明:

①确实可以用派生类对象的地址初始化基类对象的指针;②通过用派生类对象地址初始化的基类对象指针,只能调用基类的公有成员函数。在以上例子中,就是调用基类的getArea函数,而不是派生类的getArea函数。③这种调用关系的确定,也是在编译的过程中完成的,属于静态联编。

二.通过基类指针调用同名成员函数

292023/4/13-30-9.2.2覆盖与静态联编309.2.2覆盖与静态联编例9-3修改例8-5,创建4个类,分别使用各个类的对象调用各类的同名函数,分别使用不同类的对象初始化基类指针,通过基类指针调用同名函数,观察同名函数调用结果。多文件结构:10个文件主函数Main03.cppGlobal03.h类TPoint的声明和定义:TPoint03.h和TPoint03.cpp类TShape的声明和定义:TShape03.h类TRect的声明和定义:TRect03.h类TCircle的声明和定义:TCircle03.h和TCircle03.cpp类TEllipse的声明和定义:TEllipse03.h和TEllipse03.cpp319.2.2覆盖与静态联编例9-3重点:类层次结构:TShape→TRect,TShape→TCircle→TEllipse同名函数Draw();在主函数main中,通过各个类的对象调用Draw()函数,并且通过基类指针调用各个对象的Draw(),结果不同。329.2.2覆盖与静态联编339.2.2覆盖与静态联编349.2.2覆盖与静态联编359.2.2覆盖与静态联编369.2.2覆盖与静态联编379.2.2覆盖与静态联编389.2.2覆盖与静态联编39第9章多态性9.1多态性的概念19.2重载、覆盖与静态联编29.3虚函数与运行时的多态39.4纯虚函数和抽象类49.5模板5409.3虚函数与运行时的多态在同名覆盖现象中,通过某个类的对象(对象指针、对象引用)调用同名函数,编译器会将该调用联编到该类的同名函数。通过基类对象指针(引用)是无法访问派生类普通函数的,即便这个指针(引用)是使用派生类对象初始化的。419.3虚函数与运行时的多态通过指向基类的指针访问基类和派生类的同名函数,是实现运行时的多态的必要条件,但不是全部条件;如果希望利用基类的指针访问派生类的同名函数,必须将基类中的同名函数定义为虚函数。虚函数能够实现运行时的多态。429.3.1虚函数在类的定义中声明虚函数,格式如下:virtual返回值类型函数名(参数表);在函数原型中声明函数是虚函数后,具体定义这个函数时就不需要再说明它是虚函数了。

在基类定义中直接定义虚函数的格式是:virtual返回值类型函数名(参数表){//函数体}439.3.1虚函数基类中的同名函数声明或定义为虚函数后,派生类的同名函数无论是不是用virtual来说明,都将自动地成为虚函数。从程序可读性考虑,一般都会在这些函数的声明或定义时,用virtual来加以说明。442023/4/13-45-

例9-4用指针+虚函数的形式实现动态联编classB{public:virtualf(){}};classP:publicB{public:f(){}};classQ:publicB{public:f(){}};main(){

B*b_ptr; Pp; Qq;b_ptr=&p;b_ptr->f(); //调用的是P::f()b_ptr=&q;b_ptr->f(); //调用的是Q::f()}9.3.1虚函数452023/4/13-46-

例9-5用引用+虚函数的形式实现动态联编classB{public:virtualf(){}};classP:publicB{public:f(){}};classQ:publicB{public:f(){}};main(){ Pp; Qq;

B&b_ref1=p; b_ref.f(); //调用的是P::f()

B&b_ref2=q; b_ref2.f(); //调用的是Q::f()}9.3.1虚函数46

例3:将例2进行修改,使得程序具有运行时的多态的效果。多文件结构

shape1.h:类的声明shape1.cpp:类的定义和实现chapter9_3.cpp:主函数9.3.1虚函数47

例3:将例2进行修改,使得程序具有运行时的多态的效果。

//例3:shape1.h#ifndefSHAPE_H#defineSHAPE_H

classShape{ public:

virtualdoublegetArea()const; voidprint()const; };9.3.1虚函数48

classCircle:publicShape{ public: Circle(int=0,int=0,double=0.0);

virtualdoublegetArea()const;

//doublegetArea()const;

voidprint()const;private: intx,y;

doubleradius; };

9.3.1虚函数49

classRectangle:publicShape{ public: Rectangle(int=0,int=0);

virtual

doublegetArea()const;

//doublegetArea()const;

voidprint()const;private: inta,b;};#endif9.3.1虚函数50//例3:shape1.cpp#include<iostream>usingnamespacestd;#include"shape1.h" doubleShape::getArea()const{cout<<"基类的getArea函数,面积是";return0.0;}voidShape::print()const{ cout<<"BaseclassObject"<<endl;}9.3.1虚函数51

Circle::Circle(intxValue,intyValue,doubleradiusValue){ x=xValue;y=yValue; radius=radiusValue;} doubleCircle::getArea()const{cout<<"Circle类的getArea函数,面积是";

return3.14159*radius*radius;} voidCircle::print()const{cout<<"centeris";cout<<"x="<<x<<"y="<<y;cout<<";radiusis"<<radius<<endl;} 9.3.1虚函数52Rectangle::Rectangle(intaValue,intbValue){a=aValue;b=bValue;} doubleRectangle::getArea()const{cout<<"Rectangle类的getArea函数,面积是";

returna*b;} voidRectangle::print()const{cout<<"hightis"<<a;cout<<"widthis"<<b<<endl;}9.3.1虚函数53

//例3:9_3.cpp#include<iostream>usingnamespacestd;#include"shape1.h" voidmain(){Shape*shape_ptr;

Circlecircle(22,8,3.5);Rectanglerectangle(10,10);

shape_ptr=&circle;

cout<<“circle对象初始化shape_ptr指针访问的getArea函数是"<<endl;

cout<<shape_ptr->getArea()<<endl;

shape_ptr=&rectangle;

cout<<"rectangle对象初始化shape_ptr指针访问的getArea函数是"<<endl;cout<<shape_ptr->getArea()<<endl;}

circle对象初始化shape_ptr指针访问的getArea函数是Circle类的getArea函数,面积是38.4845rectangle对象初始化shape_ptr指针访问的getArea函数是Rectangle类的getArea函数,面积是100一种运行时决定的多态性549.3.1虚函数一种运行时决定的多态性5511.3.1虚函数要实现运行时的多态,需要以下条件:①必须通过指向基类对象的指针访问和基类成员函数同名的派生类成员函数;

shape_ptr=&circle; shape_ptr->getArea();

②或者用派生类对象初始化的基类对象的引用访问和基类成员函数同名的派生类成员函数;

Shape&sp=circle; sp.getArea();

③派生类的继承方式必须是公有继承;④基类中的同名成员函数必须定义为虚函数。569.3.1虚函数虚函数必须正确的定义和使用。否则,即使在函数原型前加了virtual的说明,也可能得不到运行时多态的特性。①必须首先在基类中声明虚函数。在多级继承的情况下,也可以不在最高层的基类中声明虚函数。例如在第二层定义的虚函数,可以和第三层的虚函数形成动态联编。但是,一般都是在最高层的基类中首先声明虚函数。579.3.1虚函数②基类和派生类的同名函数,必须函数名、返回值、参数表全部相同,才能作为虚函数来使用。否则,即使函数用virtual来说明,也不具有虚函数的行为。③静态成员函数不可以声明为虚函数。构造函数也不可以声明为虚函数。④析构函数可以声明为虚函数,即可以定义虚析构函数。

58

例4虚函数的正确使用。分析以下程序,编译时哪个语句会出现错误?为什么?将有错误的语句屏蔽掉以后,程序运行结果如何?其中哪些调用是静态联编,哪些是动态联编?#include<iostream.h>classBB{public:virtualvoidvf1(){cout<<"BB::vf1被调用\n";}virtual

voidvf2(){cout<<"BB::vf2被调用\n";}voidf(){cout<<"BB::f被调用\n";}};classDD:publicBB{public:virtualvoidvf1(){cout<<"DD::vf1被调用\n";}voidvf2(inti){cout<<i<<endl;}voidf(){cout<<"DD::f\n被调用";}};59

voidmain(){DDd;BB*bp=&d;bp->vf1();bp->vf2();bp->vf2(10);bp->f();}将这个语句注释掉后,运行结果将显示:DD::vf1被调用BB::vf2被调用BB::f被调用函数调用bp->vf2(10);是错误的。因为派生类的vf2函数和基类的vf2函数的参数不同,派生类的vf2就不是虚函数。其中bp->vf1()调用是动态联编。bp->vf2()是静态联编。bp->f()也是静态联编。

9.3.1虚函数609.3.2虚析构函数如果用动态创建的派生类对象的地址初始化基类的指针,创建的过程不会有问题:仍然是先调用基类构造函数,再执行派生类构造函数。但是,在用delete运算符删除这个指针的时候,由于指针是指向基类的,通过静态联编,只会调用基类的析构函数,释放基类成员所占用的空间。而派生类成员所占用的空间将不会被释放。61

#include<iostream>usingnamespacestd;classShape{ public:Shape(){cout<<"Shape类构造函数被调用\n";}

~Shape(){cout<<"Shape类析构函数被调用\n";}; }; classCircle:publicShape{ public:Circle(intxx=0,intyy=0,doublerr=0.0){x=xx;y=yy;radius=rr;cout<<"Circle类构造函数被调用\n";}~Circle(){cout<<"Circle类析构函数被调用\n";}private:intx,y; doubleradius; }; voidmain(){Shape*shape_ptr;shape_ptr=newCircle(3,4,5);deleteshape_ptr;}例9.5定义简单的Shape类和Circle类,观察基类指针的创建和释放时如何调用构造函数和析构函数。程序运行后在屏幕上显示:Shape类构造函数被调用Circle类构造函数被调用Shape类析构函数被调用62

#include<iostream>usingnamespacestd;classShape{ public:Shape(){cout<<"Shape类构造函数被调用\n";}

virtual~Shape(){cout<<"Shape类析构函数被调用\n";};

}; classCircle:publicShape{ public:Circle(intxx=0,intyy=0,doublerr=0.0){x=xx;y=yy;radius=rr;cout<<"Circle类构造函数被调用\n";}~Circle(){cout<<"Circle类析构函数被调用\n";}private:intx,y; doubleradius; }; voidmain(){Shape*shape_ptr;shape_ptr=newCircle(3,4,5);deleteshape_ptr;}例9.5定义简单的Shape类和Circle类,观察基类指针的创建和释放时如何调用构造函数和析构函数。程序运行后在屏幕上显示:Shape类构造函数被调用Circle类构造函数被调用Circle类析构函数被调用Shape类析构函数被调用63为了解决派生类对象释放不彻底的问题,必须将基类的析构函数定义为虚析构函数。格式:

virtualShape();此后,无论派生类析构函数是不是用virtual来说明,也都是虚析构函数。再用deleteshape_ptr来释放基类指针时,就会通过动态联编调用派生类的析构函数。

9.3.2虚析构函数64第9章多态性9.1多态性的概念19.2重载、覆盖与静态联编29.3虚函数与运行时的多态39.4纯虚函数和抽象类49.5模板5659.4纯虚函数和抽象类前例,基类Shape并不是一个具体的“形状”的抽象,而是各种实际的“形状”的抽象。在C++中,对于那些在基类中不需要定义具体的行为的函数,可以定义为纯虚函数。

对于那些只是反映一类事物公共特性的类,在C++中可以定义为“抽象类”。凡是带有一个或几个纯虚函数的类,就是抽象类。

669.4纯虚函数和抽象类纯虚函数格式:

virtual返回值类型函数名(参数表)=0;679.4纯虚函数和抽象类纯虚函数的声明和使用有以下的特点:①纯虚函数一定是在基类中声明的。②在多级继承的情况下,纯虚函数除了在最高层基类中声明外,也可以在较低层的基类中声明。③纯虚函数是没有函数体的。函数体是用“=0”来代替了。④纯虚函数是不可以被调用的。凡是需要被调用的函数都不可以声明为纯虚函数。

689.4纯虚函数和抽象类包含纯虚函数的类为抽象类。抽象类定义的一般形式是:class类名{public:

virtual

返回值类型函数名(参数表)=0;//其他函数的声明;};699.4纯虚函数和抽象类抽象类的定义和使用具有以下的特点:①不可以定义抽象类的对象。②可以定义抽象类的指针和抽象类的引用。目的是通过这些指针或引用访问派生类的虚函数,实现运行时的多态。③如果抽象类的派生类中没有具体实现纯虚函数的功能,这样的派生类仍然是抽象类。④抽象类中除了纯虚函数外,还可以定义其他的非纯虚函数。

70//例6:shape2.h#ifndefSHAPE_H#defineSHAPE_H

classShape{ //基类Shape的定义,抽象类public:

virtualdoublegetArea()const=0;//纯虚函数

voidprint()const;

virtual~Shape(){} //虚析构函数}; 9.4纯虚函数和抽象类71classCircle:publicShape{public: Circle(int=0,int=0,double=0.0);

doublegetArea()const;//返回面积

voidprint()const;//输出Circle类对象tprivate: intx,y; //圆心座标

doubleradius;//圆半径};

9.4纯虚函数和抽象类72classRectangle:publicShape{ public: Rectangle(int=0,int=0); //构造函数

doublegetArea()const;//返回面积

voidprint()const;//输出Rectangle类对象private: inta,b;//矩形的长和宽}; #endif9.4纯虚函数和抽象类73

//例6:shape2.cpp#include<iostream>usingnamespacestd;#include"shape2.h"

voidShape::print()const{ cout<<"BaseclassObject"<<endl;} Circle::Circle(intxValue,intyValue,doubleradiusValue){x=xValue;y=yValue;radius=radiusValue;} 74

doubleCircle::getArea()const{cout<<"Circle类的getArea函数,面积是";

return3.14159*radius*radius;} voidCircle::print()const{cout<<"centeris";cout<<"x="<<x<<"y="<<y;cout<<";radiusis"<<radius<<endl;}

75

Rectangle::Rectangle(intaValue,intbValue){ a=aValue;b=bValue;} doubleRectangle::getArea()const{cout<<"Rectangle类的getArea函数,面积是";

returna*b;} voidRectangle::print()const{cout<<"hightis"<<a;cout<<"widthis"<<b<<endl;} 76

//例6:9_6.cpp#include<iostream>usingnamespacestd;#include"shape2.h" voidcreat_object(Shape**ptr);voiddisplay_area(Shape*ptr);voiddelete_object(Shape*ptr);voidmain(){Shape*shape_ptr;creat_object(&shape_ptr);//二级指针将在子函数中申请的地址带回display_area(shape_ptr);delete_object(shape_ptr);} 77

voidcreat_object(Shape**ptr)

//二级指针将在子函数中申请的地址带回{ chartype; *ptr=NULL; do{ cout<<"创建对象。c:Circle类对象;r:Rectangle类对象"<<endl; cin>>type;switch(type) {case'c':intxx,yy; doublerr; cout<<"请输入圆心的座标和圆的半径:";

cin>>xx>>yy>>rr; *ptr=newCircle(xx,yy,rr); break; case'r':intaa,bb; cout<<"请输入矩形的长和宽:";

cin>>aa>>bb; *ptr=newRectangle(aa,bb); break; default:cout<<"类型错误,请重新选择\n";} }while(*ptr==NULL);}78

voiddisplay_area(Shape*ptr){cout<<"显示所创建对象的面积,调用的是"<<endl;cout<<ptr->getArea()<<endl;}

voiddelete_object(Shape*ptr){ deleteptr;}创建对象。c:Circle类对象;r:Rectangle类对象c请输入圆心的座标和圆的半径:115显示所创建对象的面积,调用的是Circle类的getArea函数,面积是78.54

79这个程序中,使用了本章所介绍的虚函数、纯虚函数、虚析构函数、基类指针访问派生类对象等技术,实现了运行时的多态。

程序具有很好的可扩展性。如果需要增加新的派生类,当然要增加和派生类定义有关的代码,和创建对象所需要的语句。但是,对于函数display_area,display_object等函数都不用修改。9.4纯虚函数和抽象类80这个例子还显示了:抽象类中可以为各派生类定义一些通用的接口。这些通用的接口就是抽象类中的纯虚函数。新增加的派生类的对象,都可以使用这样的通用接口,表现派生类对象的行为特性。9.4纯虚函数和抽象类81例7:在例6的基础上,现在需要增加一个Rectangle的派生类:Cube,也就是立方体类。任意创建Circle、Rectangle、Cube类的对象,并显示对象的面积。需要做的只有两件事:定义新的Cube类;在create_object函数中增加创建Cube类对象的内容。其他函数都不需要修改。9.4纯虚函数和抽象类82//例7:cube.h#include“shape2.h”#ifndefCUBE_H#defineCUBE_H

classCube:public

Rectangle

{public: Cube(int,int,int); doublegetArea()const; voidprint()const;private: intc;}; #endif 9.4纯虚函数和抽象类83//例7:cube.cpp#include<iostream>usingnamespacestd;#include"cube.h"Cube::Cube(intaValue,intbValue,intcValue):Rectangle(aValue,bValue){ c=cValue;}

doubleCube::getArea()const{cout<<"Cube类的getArea函数,还要调用";returnRectangle::getArea()*c;} voidCube::print()const{Rectangle::print();cout<<“highis"<<c<<endl;} 9.4纯虚函数和抽象类84//9_7.cpp#include<iostream>usingnamespacestd;

#include"shape2.h" #include"cube.h"

voidcreat_object(Shape**ptr);voiddisplay_area(Shape*ptr);voiddelete_object(Shape*ptr);

voidmain(){Shape*shape_ptr;creat_object(&shape_ptr);display_area(shape_ptr);delete_object(shape_ptr);}

9.4纯虚函数和抽象类85voidcreat_object(Shape**ptr){chartype;*ptr=NULL; do{cout<<"创建对象。请选择:";

cout<<"c:Circle类对象;r:Rectangle类对象;u:Cube类对象"<<endl;cin>>type;switch(type){case'c': //创建Ciecle类对象intxx,yy; doublerr; cout<<"请输入圆心的座标和圆的半径:";

cin>>xx>>yy>>rr; *ptr=newCircle(xx,yy,rr); break;86

case'r': //创建Rectangle类对象intaa,bb; cout<<"请输入矩形的长和宽:";

cin>>aa>>bb; *ptr=newRectangle(aa,bb); break;

case'u': //创建Cube类对象

intaa,bb,cc; cout<<"请输入立方体的长、宽、高:";

cin>>aa>>bb>>cc; *ptr=newCube(aa,bb,cc); break;default:cout<<"类型错误,请重新选择\n";}}while(*ptr==NULL);}Creat_object函数多增加了一个分支87程序执行结果:创建对象。请选择:c:Circle类对象;r:Rectangle类对象;u:Cube类对象u请输入立方体的长、宽、高:456显示所创建对象的面积,调用的是Cube类的getArea函数,还要调用Rectangle类的getArea函数,面积是1209.4纯虚函数和抽象类88第9章多态性9.1多态性的概念19.2重载、覆盖与静态联编29.3虚函数与运行时的多态39.4纯虚函数和抽象类49.5模板5892023/4/13-90-9.5模板模板是C++中的通用程序模块。在这些程序模块中有一些数据类型是不具体的,或者说是抽象的。当这些抽象的数据类型更换为不同的具体数据类型以后,就会产生一系列具体的程序模块。C++中的模板包括函数模板和类模板。函数模板实例化后产生的函数,称为模板函数。类模板实例化后产生的类,称为模板类。902023/4/13-91-9.5.1函数模板1.函数模板定义的基本格式:template<typename参数化类型名>函数返回类型函数名(形式参数列表){函数体}“template”是定义模板的关键字。在一对尖括号<>内,关键字“typename”后面声明所使用的“参数化类型名”。关键字“typename”可以用“class”取代。912023/4/13-92-9.5.1函数模板模板的其余部分和一般的函数定义的格式完全相同。只是在函数定义时可以使用参数化类型来代表各种具体的数据类型。参数化类型可以用于:函数返回值类型;函数参数表内形式参数的类型;函数体内,自动变量的类型。

922023/4/13-93-

例9-10函数模板的定义和使用:定义并使用“求3个数最大值”的函数模板。//Main10.cpp#include<iostream>usingnamespacestd;template<typenameT>Tmax_value(Tx,Ty,Tz){ //函数模板的定义:求x、y、z的最大值

Ttemp;temp=x>y?x:y;returntemp>z?temp:z;}intmain(){cout<<max_value(12,32,21)<<endl; //用整数作实参调用函数模板

cout<<max_value('a','A','9')<<endl; //用字符作实参调用函数模板

return0;}程序执行后,输出: 32

a9.5.1函数模板932023/4/13-94-9.5.1函数模板2.函数模板定义的一般格式

template<typename参数化类型名1,

………typename参数化类型名n>

函数返回类型函数名(形式参数列表){函数体}

942023/4/13-95-

例9-11编写一个函数模板,可以按指定的操作数类型进行乘法。//Main11.cpp#include<iostream>usingnamespacestd;template<typenameP1,typenameP2>P1cal(P1x,P2y){ //函数模板有两个参数化类型名:P1和P2return(x*static_cast<P1>(y));//按x的数据类型执行乘法}voidmain(){unsignedshortw=230;shortz=150;cout<<cal(w,z)<<endl; //按无符号数相乘

cout<<cal(z,w)<<endl; //按有符号数相乘}程序执行后,输出:34500-3103695有符号的运算230*150=34500=32768+1024+512+128+64+4-(214+213+212+211+28+25+24+23+22)=-31036215214213212211210292827262524232221203276810245121286441000011011000100111100100111011111100100111100962023/4/13-97-9.5.1函数模板3.带有确定类型的参数的函数模板函数模板的形式参数表中除了使用参数化类型名以外,还可以使用确定类型的参数。函数模板的参数表中,一定要包含参数化类型名,但不一定都使用参数化类型名。根据需要,可以使用确定类型的参数。如整型的参数、实型的参数,等等。

972023/4/13-98-

例9-12设计和编写一个通用的输入数组元素的函数模板。可以用它来输入各种不同数据类型的数组。//例9-12。包含1个文件Main12.cpp//Main12.cpp#include<iostream>#include<typeinfo>usingstd::cout;usingstd::cin;usingstd::endl;template<classQ1> //函数模板voidArrayInput(Q1array,intnum){cout<<"输入"<<num<<"个"<<typeid(Q1).name()<<'\b'<<"型数据"<<endl;for(intj=0;j<num;j++)cin>>array[j]; //输入数组元素}intmain(){intnumber;floatfloatArray[4];intintArray[3];number=sizeof(floatArray)/sizeof(float);ArrayInput(floatArray,number); //输入整型数组元素number=sizeof(intArray)/sizeof(int);ArrayInput(intArray,number);//输入浮点型数组元素return0;}程序执行后,输出:输入4个float型数据1.2输入3个int型数据123982023/4/13-99-9.5.1函数模板程序说明:使用了C++所定义的typeid运算符。它可以在程序运行时,显示指定的数据的类型。使用的格式是:typeid(表达式).name()typeid(类型标识符).name()为了符合一般的阅读习惯,在程序中通过输出一个“退格”符,将“*”覆盖了。最后看到的就是“输入X个XX型数据”。992023/4/13-100-9.5.2函数模板使用中的问题

1.用户定义的类取代参数化类型:在这种情况下,函数模板实例化后,在函数的表达式中参与运算的就是类的对象。对象可以直接使用的运算符只有赋值运算符“=”。如果表达式中需要对象进行其他的运算,就必须在相应的类的定义中,对于要使用的运算符进行重载。例如,在例11.9中,函数模板是求3个实参的最大值,需要使用运算符“>”。如果要用对象来作实参,相应的类中要对“>”运算符进行重载。

1002023/4/13-101-

例9-13用例9-10的函数模板,求3个Circle类对象的最大值。#include<iostream>usingnamespacestd;template<typenameT>Tmax_value(Tx,Ty,Tz){//函数模板的定义:求x、y、z的最大值Ttemp;if(x>y)temp=x;elsetemp=y;if(z>temp)temp=z;returntemp;}9.5.2函数模板使用中的问题

1012023/4/13-102-

//重载输出“<<”运算符,为了使该运算符能够访问Circle类的私有成员,将该运算符声明为Circle的友元函数classCircle //Circle类的定义{public: friendostream&operator<<(ostream&out,Circle&C1)//重载“<<”运算符 { out<<"x="<<C1.x<<"y="<<C1.y; out<<"radius="<<C1.radius; returnout; } Circle(inta=0,intb=0,doublec=0.0) { x=a;y=b;radius=c; } intoperator>(Circlem2) //重载“>”运算符 {if(radius>m2.radius) return1; elsereturn0; }private: intx,y; //圆心座标 doubleradius; //圆半径}; //类Circle定义结束9.5.2函数模板使用中的问题

1022023/4/13-103-

voidmain(){CircleC1(2,3,5),C2(3,5,8),C3(3,2,6); //定义3个Circle类对象

cout<<max_value(12,32,21)<<endl; //用整数作实参调用函数模板

cout<<max_value('a','A','9')<<endl;//用字符作实参调用函数模板

cout<<max_value(C1,C2,C3)<<endl;//用对象作参数调用函数模板}9.5.2函数模板使用中的问题

1032023/4/13-104-9.5.2函数模板使用中的问题2.函数模板不支持参数自动转换

对于例9.10的函数模板: template<typenameT> Tmax_value(Tx,Ty,Tz);如果用以下的方式来调用: cout<<max_value(12,3.2,21)<<endl;在编译时会出现编译错误:“templateparameter'T'isambiguous”

1042023/4/13-105-9.5.2函数模板使用中的问题要解决这样的问题,就需要函数模板和一般的函数联合使用。即需要函数模板和一般的函数的重载。1052023/4/13-106-9.5.3重载函数模板重载函数模板可以是函数模板和另一个参数数目不同的函数模板的重载,也可以是函数模板和非模板函数的重载。一般所说的函数模板重载是指后一种情况。函数模板和非模板函数重载,可以解决函数模板不支持参数自动转换的问题。当所使用的参数类型(不是类类型)不支持函数模板中某些操作时,就需要为这样的参数专门编写相应的非模板函数。和已经定义的函数模板形成重载的关系。

1062023/4/13-107-9.5.3重载函数模板例如,需要用例9.10的函数模板来比较3个结构体变量,返回其中的最大值。由于结构体变量不支持直接的比较操作“>”。要实现这样的功能,就要再写一个函数。假定结构体的定义是:

structcourse {char*name; floatscore; };1072023/4/13-108-9.5.3重载函数模板3个这样的结构体变量的比较函数如下:coursemax_value(courses1,courses2,courses3){courses4;if(s1.score>s2.score) s4=s1;elses4=s2;if(s3.score>s4.score) s4=s3;returns4;}这样就形成了函数模板和非模板函数的重载:

template<typenameT>Tmax_value(Tx,Ty,Tz);coursemax_value(courses1,courses2,courses3);108重载函数模板的匹配过程①寻找函数名和参数能准确匹配的非模板函数②如果没有找到,选择参数可以匹配的模板函数③如果还没有成功,通过参数自动转换,选择非模板函数。9.5.3重载函数模板1099.5.4类模板1.类模板的定义

template<class

参数化类型名1,……,class参数化类型名n> class类名 {数据成员定义; 成员函数原型;

……};1109.5.4类模板在模板类的外部定义类模板的成员函数时的格式:首先要使用类模板的头部,以表明类模板定义了几个参数化类型名;作用域运算符“::”前面的类名用类模板名代替,而且也要在尖括号中注明所有类模板的参数化类型名;函数的参数表或者自动变量的定义,则是可以使用参数化类型名,也可以不使用。1119.5.4类模板在类外部定义成员函数的格式如下:

template<classT1,…….,classTn>返回值类型类模板名<T1,……,Tn>::成员函数名(参数表){函数体}1129.5.4类模板2.类模板的实例化函数模板的实例化和函数调用是一起完成的。类模板的实例化则是对象的实例化一起完成的:在类模板后面的尖括号中,表明取代参数化类型的实际类型名;再写对象名和构造对象所需要的实参数。一般化的格式如下:

类模板名<类型1,……,类型n>对象名(实参数);1132023/4/13-114-

例9.14类模板的定义和实例化的示例。#include<iostream>usingnamespacestd;template<classT1,classT2>classMyClass{T1x;T2y;public:MyClass(T1a,T2b);voiddisplay();};template<classT1,classT2>MyClass<T1,T2>::MyClass(T1a,T2b){x=a; y=b;}

9.5.4类模板1142023/4/13-115-

template<classT1,classT2>voidMyClass<T1,T2>::display(){cout<<x<<endl; cout<<y<<endl;}voidmain(){MyClass<int,float>Obj1(6,6.6);Obj1.display();MyClass<char,char*>Obj2('x',"Astring");Obj2.display();}9.5.4类模板1159.5.4类模板

示例是带有两个参数化类型的类模板。在类外部定义成员函数时,先要声明使用了两个参数化类型的名字,然后,在类模板名后面注明这个模板要使用这些名字。再具体的定义成员函数。类模板进行两次实例化,分别输出不同类型的数据:第一次输出整型和实型,第二次输出字符和字符串。1162023/4/13-117-9.5.4类模板3.带有确定类型参数的类模板类模板声明时在尖括号内除了声明要使用的参数化类型名外,还可以包括确定类型的类型名。如:

template<classT,inti>classMyStack在类模板实例化和声明对象时,这个参数i要用具体的整型值来代替,如:MyStack<int,5>ss;1172023/4/13-118-9.5.4类模板例9.15用类模板来定义栈类。栈是一种先进后出的数据区。各种数据都可以定义相应的栈类。使用类模板后,就不需要定义各种不同的栈类了。用数组作为栈的存储体。在创建栈对象时,可以指定栈的大小。安排一个栈指针top指向栈顶。定义两个栈的基本操作:进栈(push)和出栈(pop)。定义相应的类模板,并测试其功

温馨提示

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

评论

0/150

提交评论