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

下载本文档

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

文档简介

1、C+程序设计基础,第9章 多态性 北京邮电大学信通院 方莉 ,2,多态性(Polymorphism)是面向对象程序设计的主要特征之一。 多态性对于软件功能的扩展和软件重用都有重要的作用。是学习面向对象程序设计必须要掌握的主要内容之一。 本章主要内容 多态性的基本概念 运行多态的实现,虚函数 模板,第9章 多态性,3,第9章 多态性,4,9.1.1 面向对象程序设计中的多态,多态性是指同样的消息被不同类型的对象接收时导致完全不同的行为。 消息:指示要调用类的某个成员函数。 行为:成员函数执行的结果被视为对象的行为。,5,面向对象程序设计中多态性表现为以下几种形式: 重载多态:通过调用相同名字的函

2、数,表现出不同的行为。运算符重载是一种重载多态。 运行多态:通过基类的指针,调用不同派生类的同名函数(虚函数),表现出不同的行为。许多面向对象程序设计的书籍中所说的多态性,就是这种多态。 模板多态,也称为参数多态:通过一个模板,得到不同的函数或不同的类。这些函数或者类具有不同的特性和不同的行为。,9.1.1 面向对象程序设计中的多态,6,一个具有多态性的程序语句,在执行的时候,必须确定究竟是调用哪一个函数。 确定具有多态性的语句究竟调用哪个函数的过程称为联编(Binding),有的资料也翻译成“绑定”。 联编有两种方式: 静态联编 动态联编,9.1.2 多态的实现:联编,7,在源程序编译的时候

3、就能确定具有多态性的语句调用哪个函数,称为静态联编。 对重载函数的调用就是在编译的时候确定具体调用哪个函数,所以是属于静态联编。 模板在编译时确定被调函数,属于静态联编。,静态联编,8,动态联编则是在程序运行时,才能够确定具有多态性的语句究竟调用哪个函数。 用动态联编实现的多态,也称为运行时的多态。 虚函数是支持运行多态的基础。,动态联编,9,第9章 多态性,10,9.2.1 重载和静态联编,以下是一个重载函数的例子。 int add(int a) return a+10; int add(int a,int b) return a+b; int main() int x=1,y=2; add

4、(x); add(x,y); return 0; ,11,9.2.2 覆盖与静态联编,覆盖(Override)现象只能出现在继承树中。 在派生类中定义和基类中同名的成员函数,是对基类进行改造,为派生类增加新的行为的一种常用的方法。 在某些情况下,覆盖会导致静态联编; 另外一些情况下,覆盖会导致动态联编。,12,9.2.2 覆盖与静态联编,通过派生类对象调用同名成员函数 通过基类指针调用同名成员函数,13,一.通过派生类对象调用同名成员函数,在派生类中可以定义和基类的成员函数同名的成员函数。这是对基类进行改造,是派生类增加新的行为的一种常用的方法。 在程序编译的时候,就可以确定派生类对象具体调用

5、哪个同名的成员函数。这是通过静态联编实现的多态。,14,例 9-1 用派生类对象访问继承树中的同名函数 /以下代码仅为示例,无法编译 class B public: f() ;/基类 class P: public B public: f() ;/派生类1 class Q: public B public: f() ;/派生类2 void main () P p;/创建派生类P的对象 Q q;/创建派生类Q的对象 p.f();/调用P:f() q.f();/调用Q:f() ,一.通过派生类对象调用同名成员函数,15,例1:定义Circle类和Rectangle类为Shape类的派生类,通过Ci

6、rcle类和Rectangle类的对象调用重载函数getArea()显示对象的面积。 / 例1: shape.h class Shape public: double getArea() const; void print() const; ;,一.通过派生类对象调用同名成员函数,16,class Circle : public Shape public: Circle( int = 0, int = 0, double = 0.0 ); double getArea() const; / 返回面积 void print() const; private: int x,y; / 圆心 doub

7、le radius; / 半径 ;,一.通过派生类对象调用同名成员函数,17,class Rectangle : public Shape public: Rectangle( int = 0, int = 0); double getArea() const; / 面积 void print() const; private: int a,b; / 长和宽 ;,一.通过派生类对象调用同名成员函数,18,/ 例1: shape.cpp #include using namespace std; #include shape.h double Shape:getArea() const cout

8、基类的getArea函数,面积是 ; return 0.0; void Shape:print() const coutBase class Objectendl; ,一.通过派生类对象调用同名成员函数,19,Circle:Circle( int xValue, int yValue, double radiusValue ) x=xValue; y=yValue; radius= radiusValue ; double Circle:getArea() const coutCircle类的getArea函数,面积是 ; return 3.14159 * radius * radius; v

9、oid Circle:print() const cout center is ; coutx=x y=y; cout ; radius is radiusendl; ,一.通过派生类对象调用同名成员函数,20,Rectangle:Rectangle( int aValue, int bValue ) a=aValue; b=bValue; double Rectangle:getArea() const coutRectangle类的getArea函数,面积是 ; return a * b; void Rectangle:print() const cout hight is a; cout

10、width isbendl; ,一.通过派生类对象调用同名成员函数,21,/ 例1: chapter9_1.cpp #include using namespace std; #include shape.h void main() Circle circle( 22, 8, 3.5 ); Rectangle rectangle( 10, 10 ); cout 调用的是 ; coutcircle.getArea() endl; / 静态联编 cout 调用的是; coutrectangle.getArea() endl; / 静态联编 ,调用的是Circle类的getArea函数,面积是38.

11、4845 调用的是Ractangle类的getArea函数,面积是100,一.通过派生类对象调用同名成员函数,22,通过派生类对象调用同名成员函数,可以有以下的结论: 派生类对象可以直接调用本类中与基类成员函数同名的函数,不存在二义性; 在编译时就能确定通过对象将调用哪个函数,属于静态联编。,Circle circle( 22, 8, 3.5 ); Rectangle rectangle( 10, 10 ); circle.getArea(); rectangle.getArea();,一.通过派生类对象调用同名成员函数,23,二. 通过基类指针调用同名成员函数,例 9-2 用指针访问继承树中

12、的同名函数 /以下代码仅为示例,无法编译 class B public: f() ;/基类 class P: public B public: f() ;/派生类1 class Q: public B public: f() ;/派生类2 void main () B* b_ptr;P p;Q q;/定义基类的指针和派生类的对象 b_ptr=/通过基类指针调用B:f() ,24,二. 通过基类指针调用同名成员函数,从继承的角度来看,派生类对象是基类对象的一个具体的特例。或者说,派生类对象是某一种特定类型的基类对象。 例如,Circle类是Shape类的公有继承,“圆”是“图形”的一种特例。或者

13、说,圆是一种特定的图形,具有图形的基本特征。,25,在关于基类对象和派生类对象的操作上,可以允许以下的操作: 可以用派生类对象给基类对象赋值; 例:Circle circle( 22, 8, 3.5 ); Rectangle rectangle( 10, 10 ); Shape a=circle; Shape b=rectangle;,二. 通过基类指针调用同名成员函数,26,可以用派生类对象初始化基类的引用。 例: Shape 通过该指针,可以访问基类的公有成员。,二. 通过基类指针调用同名成员函数,27,以下这些操作是不能进行的: 不能用基类对象给派生类对象赋值; 例: Shape sp;

14、 Circle r=sp; /error 不能用基类对象的地址初始化派生类对象的指针; 例: Shape sp; Circle *pc= /error,二. 通过基类指针调用同名成员函数,28,例2 在例1所定义的类的基础上,观察通过派生类对象地址初始化的基类对象的指针访问getArea函数的结果。 #include using namespace std; #include shape.h void main() Shape *shape_ptr; Circle circle( 22, 8, 3.5 ); Rectangle rectangle( 10, 10 ); shape_ptr =

15、/ 静态联编 ,circle 对象初始化shape_ptr指针访问的getArea函数是基类的getArea函数,面积是 0 rectangle 对象初始化shape_ptr指针访问的getArea函数是基类的getArea函数,面积是 0,29,程序运行结果表明: 确实可以用派生类对象的地址初始化基类对象的指针; 通过用派生类对象地址初始化的基类对象指针,只能调用基类的公有成员函数。在以上例子中,就是调用基类的getArea函数,而不是派生类的getArea函数。 这种调用关系的确定,也是在编译的过程中完成的,属于静态联编。,二. 通过基类指针调用同名成员函数,30,2020/8/12,-3

16、0-,9.2.2 覆盖与静态联编,31,9.2.2 覆盖与静态联编,例9-3 修改例8-5,创建4个类,分别使用各个类的对象调用各类的同名函数,分别使用不同类的对象初始化基类指针,通过基类指针调用同名函数,观察同名函数调用结果。 多文件结构:10个文件 主函数Main03.cpp Global03.h 类TPoint的声明和定义:TPoint03.h和TPoint03.cpp 类TShape的声明和定义:TShape03.h 类TRect的声明和定义:TRect03.h 类TCircle的声明和定义: TCircle03.h和TCircle03.cpp 类TEllipse的声明和定义: TEl

17、lipse03.h和TEllipse03.cpp,32,9.2.2 覆盖与静态联编,例9-3 重点: 类层次结构:TShapeTRect, TShape TCircle TEllipse 同名函数Draw(); 在主函数main中,通过各个类的对象调用Draw()函数,并且通过基类指针调用各个对象的Draw(),结果不同。,33,9.2.2 覆盖与静态联编,34,9.2.2 覆盖与静态联编,35,9.2.2 覆盖与静态联编,36,9.2.2 覆盖与静态联编,37,9.2.2 覆盖与静态联编,38,9.2.2 覆盖与静态联编,39,9.2.2 覆盖与静态联编,40,第9章 多态性,41,9.3

18、虚函数与运行时的多态,在同名覆盖现象中,通过某个类的对象(对象指针、对象引用)调用同名函数,编译器会将该调用联编到该类的同名函数。 通过基类对象指针(引用)是无法访问派生类普通函数的,即便这个指针(引用)是使用派生类对象初始化的。,42,9.3 虚函数与运行时的多态,通过指向基类的指针访问基类和派生类的同名函数,是实现运行时的多态的必要条件,但不是全部条件; 如果希望利用基类的指针访问派生类的同名函数,必须将基类中的同名函数定义为虚函数。 虚函数能够实现运行时的多态。,43,9.3.1 虚函数,在类的定义中声明虚函数,格式如下: virtual 返回值类型 函数名(参数表); 在函数原型中声明

19、函数是虚函数后,具体定义这个函数时就不需要再说明它是虚函数了。 在基类定义中直接定义虚函数的格式是: virtual 返回值类型 函数名(参数表) /函数体 ,44,9.3.1 虚函数,基类中的同名函数声明或定义为虚函数后,派生类的同名函数无论是不是用virtual来说明,都将自动地成为虚函数。 从程序可读性考虑,一般都会在这些函数的声明或定义时,用virtual来加以说明。,45,2020/8/12,-45-,例 9-4 用指针+虚函数的形式实现动态联编 class B public: virtual f() ; class P: public B public: f() ; class Q

20、: public B public: f() ; main () B* b_ptr;P p;Q q; b_ptr=/调用的是Q:f() ,9.3.1 虚函数,46,2020/8/12,-46-,例 9-5 用引用+虚函数的形式实现动态联编 class B public: virtual f() ; class P: public B public: f() ; class Q: public B public: f() ; main () P p;Q q; B /调用的是Q:f() ,9.3.1 虚函数,47,例3:将例2进行修改,使得程序具有运行时的多态的效果。 多文件结构 shape1.h

21、 :类的声明 shape1.cpp:类的定义和实现 chapter9_3.cpp:主函数,9.3.1 虚函数,48,例3:将例2进行修改,使得程序具有运行时的多态的效果。 / 例3: shape1.h #ifndef SHAPE_H #define SHAPE_H class Shape public: virtual double getArea() const; void print() const; ;,9.3.1 虚函数,49,class Circle : public Shape public: Circle( int = 0, int = 0, double = 0.0 ); vi

22、rtual double getArea() const; /double getArea() const; void print() const; private: int x,y; double radius; ;,9.3.1 虚函数,50,class Rectangle : public Shape public: Rectangle( int = 0, int = 0); virtual double getArea() const; /double getArea() const; void print() const; private: int a,b; ; #endif,9.3.

23、1 虚函数,51,/ 例3: shape1.cpp #include using namespace std; #include shape1.h double Shape:getArea() const cout基类的getArea函数,面积是 ; return 0.0; void Shape:print() const coutBase class Objectendl; ,9.3.1 虚函数,52,Circle:Circle( int xValue, int yValue, double radiusValue ) x=xValue; y=yValue; radius= radiusVa

24、lue ; double Circle:getArea() const coutCircle类的getArea函数,面积是 ; return 3.14159 * radius * radius; void Circle:print() const cout center is ; coutx=x y=y; cout ; radius is radiusendl; ,9.3.1 虚函数,53,Rectangle:Rectangle( int aValue, int bValue ) a=aValue; b=bValue; double Rectangle:getArea() const cout

25、Rectangle类的getArea函数,面积是 ; return a * b; void Rectangle:print() const cout hight is a; coutwidth isbendl; ,9.3.1 虚函数,54,/ 例3: 9_3.cpp #include using namespace std; #include shape1.h void main() Shape *shape_ptr; Circle circle( 22, 8, 3.5 ); Rectangle rectangle( 10, 10 ); shape_ptr = ,circle 对象初始化sha

26、pe_ptr指针访问的getArea函数是Circle类的getArea函数,面积是 38.4845 rectangle 对象初始化shape_ptr指针访问的getArea函数是Rectangle类的getArea函数,面积是 100,一种运行时决定的多态性,55,9.3.1 虚函数,一种运行时决定的多态性,56,11.3.1 虚函数,要实现运行时的多态,需要以下条件: 必须通过指向基类对象的指针访问和基类成员函数同名的派生类成员函数; shape_ptr = 派生类的继承方式必须是公有继承; 基类中的同名成员函数必须定义为虚函数。,57,9.3.1 虚函数,虚函数必须正确的定义和使用。否则

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

28、译时哪个语句会出现错误?为什么?将有错误的语句屏蔽掉以后,程序运行结果如何?其中哪些调用是静态联编,哪些是动态联编? #include class BB public: virtual void vf1() coutBB:vf1被调用n; virtual void vf2() coutBB:vf2被调用n; void f() coutBB:f被调用n; ; class DD:public BB public: virtual void vf1() coutDD:vf1被调用n; void vf2(int i) coutiendl; void f() coutDD:fn被调用; ;,60,voi

29、d main() DD d; BB *bp= ,将这个语句注释掉后,运行结果将显示: DD:vf1被调用 BB:vf2被调用 BB:f被调用,函数调用bp-vf2(10);是错误的。因为派生类的vf2函数和基类的vf2函数的参数不同,派生类的vf2就不是虚函数。,其中bp-vf1()调用是动态联编。 bp-vf2()是静态联编。 bp-f()也是静态联编。,9.3.1 虚函数,61,9.3.2 虚析构函数,如果用动态创建的派生类对象的地址初始化基类的指针,创建的过程不会有问题:仍然是先调用基类构造函数,再执行派生类构造函数。 但是,在用delete运算符删除这个指针的时候,由于指针是指向基类的

30、,通过静态联编,只会调用基类的析构函数,释放基类成员所占用的空间。而派生类成员所占用的空间将不会被释放。,62,#include using namespace std; class Shape public: Shape()coutShape类构造函数被调用n; Shape()coutShape类析构函数被调用n; ; class Circle : public Shape public: Circle( int xx= 0, int yy= 0, double rr= 0.0 ) x = xx; y = yy; radius =rr; coutCircle类构造函数被调用n; Circle

31、() coutCircle类析构函数被调用n; private: int x,y; double radius; ; void main() Shape *shape_ptr; shape_ptr = new Circle(3,4,5); delete shape_ptr; ,例9.5 定义简单的Shape类和Circle类,观察基类指针的创建和释放时如何调用构造函数和析构函数。,程序运行后在屏幕上显示: Shape类构造函数被调用 Circle类构造函数被调用 Shape类析构函数被调用,63,#include using namespace std; class Shape public:

32、 Shape()coutShape类构造函数被调用n; virtual Shape()coutShape类析构函数被调用n; ; class Circle : public Shape public: Circle( int xx= 0, int yy= 0, double rr= 0.0 ) x = xx; y = yy; radius =rr; coutCircle类构造函数被调用n; Circle() coutCircle类析构函数被调用n; private: int x,y; double radius; ; void main() Shape *shape_ptr; shape_pt

33、r = new Circle(3,4,5); delete shape_ptr; ,例9.5 定义简单的Shape类和Circle类,观察基类指针的创建和释放时如何调用构造函数和析构函数。,程序运行后在屏幕上显示: Shape类构造函数被调用 Circle类构造函数被调用 Circle类析构函数被调用 Shape类析构函数被调用,64,为了解决派生类对象释放不彻底的问题,必须将基类的析构函数定义为虚析构函数。格式: virtual Shape(); 此后,无论派生类析构函数是不是用virtual来说明,也都是虚析构函数。 再用delete shape_ptr来释放基类指针时,就会通过动态联编调

34、用派生类的析构函数。,9.3.2 虚析构函数,65,第9章 多态性,66,9.4 纯虚函数和抽象类,前例,基类Shape并不是一个具体的“形状”的抽象,而是各种实际的“形状”的抽象。 在C+中,对于那些在基类中不需要定义具体的行为的函数,可以定义为纯虚函数。 对于那些只是反映一类事物公共特性的类,在C+中可以定义为“抽象类”。 凡是带有一个或几个纯虚函数的类,就是抽象类。,67,9.4 纯虚函数和抽象类,纯虚函数格式: virtual 返回值类型 函数名(参数表) = 0;,68,9.4 纯虚函数和抽象类,纯虚函数的声明和使用有以下的特点: 纯虚函数一定是在基类中声明的。 在多级继承的情况下,

35、纯虚函数除了在最高层基类中声明外,也可以在较低层的基类中声明。 纯虚函数是没有函数体的。函数体是用“= 0”来代替了。 纯虚函数是不可以被调用的。凡是需要被调用的函数都不可以声明为纯虚函数。,69,9.4 纯虚函数和抽象类,包含纯虚函数的类为抽象类。 抽象类定义的一般形式是: class 类名 public: virtual 返回值类型 函数名(参数表) = 0; /其他函数的声明; ;,70,9.4 纯虚函数和抽象类,抽象类的定义和使用具有以下的特点: 不可以定义抽象类的对象。 可以定义抽象类的指针和抽象类的引用。目的是通过这些指针或引用访问派生类的虚函数,实现运行时的多态。 如果抽象类的派

36、生类中没有具体实现纯虚函数的功能,这样的派生类仍然是抽象类。 抽象类中除了纯虚函数外,还可以定义其他的非纯虚函数。,71,/ 例6: shape2.h #ifndef SHAPE_H #define SHAPE_H class Shape /基类Shape的定义,抽象类 public: virtual double getArea() const=0; /纯虚函数 void print() const; virtual Shape()/虚析构函数 ;,9.4 纯虚函数和抽象类,72,class Circle : public Shape public: Circle( int = 0, int

37、 = 0, double = 0.0 ); double getArea() const; / 返回面积 void print() const; / 输出Circle 类对象t private: int x,y; / 圆心座标 double radius; / 圆半径 ;,9.4 纯虚函数和抽象类,73,class Rectangle : public Shape public: Rectangle( int = 0, int = 0); / 构造函数 double getArea() const; / 返回面积 void print() const; / 输出Rectangle类对象 pri

38、vate: int a,b; / 矩形的长和宽 ; #endif,9.4 纯虚函数和抽象类,74,/ 例6: shape2.cpp #include using namespace std; #include shape2.h void Shape:print() const coutBase class Objectendl; Circle:Circle( int xValue, int yValue, double radiusValue ) x=xValue; y=yValue; radius= radiusValue ; ,75,double Circle:getArea() cons

39、t coutCircle类的getArea函数,面积是 ; return 3.14159 * radius * radius; void Circle:print() const cout center is ; coutx=x y=y; cout ; radius is radiusendl; ,76,Rectangle:Rectangle( int aValue, int bValue ) a=aValue; b=bValue; double Rectangle:getArea() const coutRectangle类的getArea函数,面积是 ; return a * b; voi

40、d Rectangle:print() const cout hight is a; coutwidth isbendl; ,77,/ 例6: 9_6.cpp #include using namespace std; #include shape2.h void creat_object(Shape *ptr); void display_area(Shape *ptr); void delete_object(Shape *ptr); void main() Shape *shape_ptr; creat_object( ,78,void creat_object(Shape *ptr)

41、/二级指针将在子函数中申请的地址带回 char type; *ptr = NULL; do couttype; switch (type) case c: int xx,yy; double rr; coutxxyyrr; *ptr = new Circle(xx,yy,rr); break; case r: int aa,bb; coutaabb; *ptr = new Rectangle(aa,bb); break; default: cout类型错误,请重新选择n; while(*ptr=NULL); ,79,void display_area(Shape *ptr) coutgetAr

42、ea() endl; void delete_object(Shape *ptr) delete ptr; ,创建对象。c:Circle类对象;r:Rectangle类对象 c 请输入圆心的座标和圆的半径:1 1 5 显示所创建对象的面积,调用的是Circle类的getArea函数,面积是78.54,80,这个程序中,使用了本章所介绍的虚函数、纯虚函数、虚析构函数、基类指针访问派生类对象等技术,实现了运行时的多态。 程序具有很好的可扩展性。如果需要增加新的派生类,当然要增加和派生类定义有关的代码,和创建对象所需要的语句。但是,对于函数display_area,display_object等函数

43、 都不用修改。,9.4 纯虚函数和抽象类,81,这个例子还显示了:抽象类中可以为各派生类定义一些通用的接口。这些通用的接口就是抽象类中的纯虚函数。新增加的派生类的对象,都可以使用这样的通用接口,表现派生类对象的行为特性。,9.4 纯虚函数和抽象类,82,例7:在例6的基础上,现在需要增加一个Rectangle的派生类:Cube,也就是立方体类。任意创建Circle、Rectangle、Cube类的对象,并显示对象的面积。 需要做的只有两件事: 定义新的Cube类; 在create_object函数中增加创建Cube类对象的内容。 其他函数都不需要修改。,9.4 纯虚函数和抽象类,83,/ 例7

44、: cube.h #include “shape2.h” #ifndef CUBE_H #define CUBE_H class Cube : public Rectangle public: Cube(int, int, int); double getArea() const; void print() const; private: int c; ; #endif,9.4 纯虚函数和抽象类,84,/ 例7: cube.cpp #include using namespace std; #include cube.h Cube:Cube(int aValue, int bValue, in

45、t cValue):Rectangle(aValue,bValue) c=cValue; double Cube:getArea() const coutCube类的getArea函数,还要调用 ; return Rectangle:getArea()* c; void Cube:print() const Rectangle:print(); cout “high is cendl; ,9.4 纯虚函数和抽象类,85,/ 9_7.cpp #include using namespace std; #include shape2.h #include cube.h void creat_obj

46、ect(Shape *ptr); void display_area(Shape *ptr); void delete_object(Shape *ptr); void main() Shape *shape_ptr; creat_object( ,9.4 纯虚函数和抽象类,86,void creat_object(Shape *ptr) char type; *ptr = NULL; do couttype; switch (type) case c:/创建Ciecle类对象 int xx,yy; double rr; coutxxyyrr; *ptr = new Circle(xx,yy,

47、rr); break;,87,case r:/创建Rectangle类对象 int aa,bb; coutaabb; *ptr = new Rectangle(aa,bb); break; case u:/创建Cube类对象 int aa,bb,cc; coutaabbcc; *ptr = new Cube(aa,bb,cc); break; default: cout类型错误,请重新选择n; while(*ptr=NULL); ,Creat_object函数多增加了一个分支,88,程序执行结果:,创建对象。请选择:c:Circle类对象;r:Rectangle类对象;u:Cube类对象 u

48、请输入立方体的长、宽、高:4 5 6 显示所创建对象的面积,调用的是 Cube类的getArea函数,还要调用 Rectangle类的getArea函数,面积是 120,9.4 纯虚函数和抽象类,89,第9章 多态性,90,2020/8/12,-90-,9.5 模板,模板是C+中的通用程序模块。在这些程序模块中有一些数据类型是不具体的,或者说是抽象的。当这些抽象的数据类型更换为不同的具体数据类型以后,就会产生一系列具体的程序模块。 C+中的模板包括函数模板和类模板。 函数模板实例化后产生的函数,称为模板函数。 类模板实例化后产生的类,称为模板类。,91,2020/8/12,-91-,9.5.1

49、 函数模板,1.函数模板定义的基本格式 : template 函数返回类型 函数名(形式参数列表) 函数体 “template”是定义模板的关键字。 在一对尖括号内,关键字“typename”后面声明所使用的“参数化类型名”。 关键字“typename”可以用“class”取代 。,92,2020/8/12,-92-,9.5.1 函数模板,模板的其余部分和一般的函数定义的格式完全相同。只是在函数定义时可以使用参数化类型来代表各种具体的数据类型。 参数化类型可以用于: 函数返回值类型; 函数参数表内形式参数的类型; 函数体内,自动变量的类型。,93,2020/8/12,-93-,例 9-10 函

50、数模板的定义和使用:定义并使用“求3个数最大值”的函数模板。 /Main10.cpp #include using namespace std; template T max_value(T x,T y,T z)/函数模板的定义:求x、y、z的最大值 T temp; temp = xy?x:y; return tempz?temp:z; int main() coutmax_value(12,32,21)endl;/用整数作实参调用函数模板 coutmax_value(a,A,9)endl;/用字符作实参调用函数模板 return 0; ,程序执行后,输出: 32 a,9.5.1 函数模板,9

51、4,2020/8/12,-94-,9.5.1 函数模板,2. 函数模板定义的一般格式 template 函数返回类型 函数名(形式参数列表) 函数体 ,95,2020/8/12,-95-,例 9-11 编写一个函数模板,可以按指定的操作数类型进行乘法。 /Main11.cpp #include using namespace std; template P1 cal(P1 x, P2 y) /函数模板有两个参数化类型名:P1和P2 return (x * static_cast(y); /按x的数据类型执行乘法 void main() unsigned short w=230; short z

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

53、使用确定类型的参数。如整型的参数、实型的参数,等等。,98,2020/8/12,-98-,例 9-12 设计和编写一个通用的输入数组元素的函数模板。可以用它来输入各种不同数据类型的数组。 /例9-12。包含1个文件 Main12.cpp /Main12.cpp #include #include using std:cout; using std:cin; using std:endl; template /函数模板 void ArrayInput(Q1 array, int num) cout arrayj;/输入数组元素 int main() int number; float float

54、Array4; int intArray3; number=sizeof(floatArray)/sizeof(float); ArrayInput(floatArray, number);/输入整型数组元素 number=sizeof(intArray)/sizeof(int); ArrayInput(intArray, number ); /输入浮点型数组元素 return 0; ,程序执行后,输出: 输入4个float型数据 1.2 3.4 5.4 1.2 输入3个int型数据 1 2 3,99,2020/8/12,-99-,9.5.1 函数模板,程序说明: 使用了C+所定义的typei

55、d运算符。它可以在程序运行时,显示指定的数据的类型。使用的格式是: typeid(表达式).name() typeid(类型标识符).name() 为了符合一般的阅读习惯,在程序中通过输出一个“退格”符,将“*”覆盖了。最后看到的就是“输入X个XX型数据”。,100,2020/8/12,-100-,9.5.2 函数模板使用中的问题,1. 用户定义的类取代参数化类型 : 在这种情况下,函数模板实例化后,在函数的表达式中参与运算的就是类的对象。 对象可以直接使用的运算符只有赋值运算符“=”。如果表达式中需要对象进行其他的运算,就必须在相应的类的定义中,对于要使用的运算符进行重载。 例如,在例11.

56、9中,函数模板是求3个实参的最大值,需要使用运算符“”。如果要用对象来作实参,相应的类中要对“”运算符进行重载。,101,2020/8/12,-101-,例 9-13 用例9-10的函数模板,求3个Circle类对象的最大值。 #include using namespace std; template T max_value(T x,T y,T z) /函数模板的定义:求x、y、z的最大值 T temp; if(xy) temp = x; else temp = y; if(ztemp) temp =z; return temp; ,9.5.2 函数模板使用中的问题,102,2020/8/1

57、2,-102-,/重载输出“(Circle m2)/重载“”运算符 if(radiusm2.radius) return 1; else return 0; private: int x,y; / 圆心座标 double radius; / 圆半径 ; / 类Circle定义结束,9.5.2 函数模板使用中的问题,103,2020/8/12,-103-,void main() Circle C1(2,3,5),C2(3,5,8),C3(3,2,6);/定义3个Circle类对象 coutmax_value(12,32,21)endl;/用整数作实参调用函数模板 coutmax_value(a,

58、A,9)endl; /用字符作实参调用函数模板 coutmax_value(C1,C2,C3)endl; /用对象作参数调用函数模板 ,9.5.2 函数模板使用中的问题,104,2020/8/12,-104-,9.5.2 函数模板使用中的问题,2. 函数模板不支持参数自动转换 对于例9.10的函数模板: template T max_value(T x,T y,T z); 如果用以下的方式来调用: coutmax_value(12,3.2,21)endl; 在编译时会出现编译错误:“template parameter T is ambiguous”,105,2020/8/12,-105-,9.5.2 函数模板使用中的问题,要解决这样的问题,就需要函数模板和一般的函数联合使用。即需要函数模板和一般的函数的重载。

温馨提示

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

评论

0/150

提交评论