《《C程序设计》电子教案第9章多态性和虚函数.ppt》由会员分享,可在线阅读,更多相关《《C程序设计》电子教案第9章多态性和虚函数.ppt(73页珍藏版)》请在三一办公上搜索。
1、第9章 多态性与虚函数,9.1 多态性9.2 虚函数9.3 纯虚函数和抽象类,9.1 多态性,9.1.1 普通成员函数重载9.1.2 构造函数重载9.1.3 派生类指针,返回首页,9.1.1 普通成员函数重载,1函数重载的方法2函数重载的表示形式(1)在一个类说明中重载。(2)基类的成员函数在派生类中重载。3函数重载的注意事项4函数重载的二义性,1函数重载的方法,例9-1:给出以下程序的运行结果。#include int square(int x)return x*x;double square(double y)return y*y;main(),coutThe square of inte
2、ger 7 issquare(7)endl;cout The square of double 7.5 issquare(7.5)endl;return 0;此程序的运行结果为:The square of integer 7 is 49The square of integer 7.5 is 56.25,例9-2:用重载函数实现求圆和矩形的周长。#include const double PI=3.1415;double length(float r)return 2*PI*r;double length(float x,float y)return 2*(x+y);void main()fl
3、oat a,b,r;coutr;,coutab;cout矩形周长:length(a,b)endl;运行结果为:输入圆半径:7 圆周长:43.981输入矩形长和宽:3 4 矩形周长:14,2函数重载的表示形式,例9-3:分析以下程序的执行结果。#include class Sampleint i;double d;public:void setdata(int n)i=n;void setdata(double x)d=x;void disp(),couti=i,d=dendl;void main()Sample s;s.setdata(7);s.setdata(7.5);s.disp();此程
4、序的运行结果为:i=7,d=7.5,有3种编译区分方法:1)根据参数的特征加以区分。2)使用“:”加以区分。3)根据类对象加以区分。,3函数重载的注意事项,在C+语言中,编译程序选择相应的重载函数版本时函数返回值类型是不起作用的。不能仅靠函数的返回值来区别重载函数,必须从形式参数上区别开来。例如:void print(int a);void print(int a,int b);int print(float a);这三个函数是重载函数,因为C+编译程序可以从形式参数上将它们区别开来。但:int f(int a);double f(int a);,4函数重载的二义性,函数重载的二义性(ambi
5、guity)是指C+语言的编译程序无法在多个重载函数中选择正确的函数进行调用。这些二义性错误是致命的,因而编译程序将无法生成目标代码。函数重载的二义性主要源于C+语言的隐式类型转换与默认参数。,例9-4:隐式类型转换造成函数重载二义性示例。#include float abs(float x)return(x0?x:-x);double abs(double x)return(x0?x:-x);int main()coutabs(1.78)endl;/调用abs(double)/coutabs(-7)endl;/错误,编译程序无法确定调用哪一个abs()函数在重载函数中使用默认参数也可能造成二
6、义性。,返回本节,9.1.2 构造函数重载,构造函数可以像普通函数一样被重载,而且也可能是C+语言应用函数重载最多的地方,因为设计一个类时总是希望创建对象的同时能以多种方式初始化对象的内部状态,而构造函数只能有一个名字,即该类的名字。当建设一个可复用类库时,重载构造函数可以更好地提高类界面的完整性。,例:class X public:X();X(int);X(int,char);X(float,char);.;void f()X a;/调用构造函数 X()X b(1);/调用构造函数 X(int)X c(1,c);/调用构造函数 X(int,char)X d(2.3,d);/调用构造函数 X(
7、float,char).,例9-6:分析下面程序的执行结果。#include class TDatepublic:TDate();TDate(int d);TDate(int m,int d);TDate(int y,int m,int d);protected:int year;int month;int day;TDate:TDate(),year=1999;month=11;day=24;coutyear/month/dayendl;TDate:TDate(int d)year=1999;month=11;day=d;coutyear/month/dayendl;TDate:TDate(
8、int m,int d)year=1999;month=m;day=d;coutyear/month”/”dayendl;TDate:TDate(int y,int m,int d)year=y;month=m;day=d;,coutyear/month/dayendl;void main()TDate aday;TDate bdate(10);TDate cdate(8,8);TDate ddate(1998,1,1);输出结果为:1999/11/241999/11/101999/8/81999/1/1,返回本节,9.1.3 派生类指针,指向基类和派生类的指针是相关的。例如:A*p;/指向类
9、型 A 的对象的指针A A_obj;/类型 A 的对象B B_obj;/类型 B 的对象p=/p 指向类型 B 的对象,它是 A 的派生类,例9-8:分析以下程序的执行结果。class A_classchar name80;public:void put_name(char*s)strcpy(name,s);void show_name()coutnamen;class B_class:public A_classchar phone_num80;public:void put_phone(char*num)strcpy(phone_num,num);void show_phone()cout
10、phone_numn;,main()A_class*p;/对象指针A_class A_obj;/对象B_class*bp;B_class B_obj;p=,(B_class*)p)-show_phone();/用基类指针访问公有派生类的特定成员,必须进行类型转换此程序的运行结果为:Zhang SanLi Si0731_123456780731_12345678,例9-9:写出下面的程序的执行结果。#include class Studentpublic:Student(int xx)x=xx;virtual float calcTuition();protected:int x;float S
11、tudent:calcTuition()return float(x*x);,class GraduateStudent:public Studentpublic:GraduateStudent(int xx):Student(xx)float calcTuition();float GraduateStudent:calcTuition()return float(x*2);void main()Student s(20);GraduateStudent gs(20);couts.calcTuition()endl;/计算学生s的学费coutgs.calcTuition()endl;/计算研
12、究生gs的学费输出结果为:400400,返回本节,9.2 虚函数,9.2.1 静态联编与动态联编9.2.2 虚函数的概念9.2.3 动态联编与虚函数9.2.4 虚函数的限制9.2.5 虚函数与重载函数的比较,返回首页,9.2.1 静态联编与动态联编,1静态联编静态联编是指联编工作出现在编译连接阶段,这种联编又称早期联编,因为这种联编过程是在程序开始运行之前完成的。在编译时所进行的这种联编又称静态绑定。在编译时就解决了程序中的操作调用与执行该操作代码间的关系,确定这种关系又称为绑定,在编译时绑定又称静态束定。下面举一个静态联编的例子。,例9-11:静态联编示例程序。#include class
13、Pointpublic:Point(double i,double j)x=i;y=j;double Area()const return 0.0;private:double x,y;class Rectangle:public Pointpublic:Rectangle(double i,double j,double k,double l);,double Area()const return w*h;private:double w,h;Rectangle:Rectangle(double i,double j,double k,double l):Point(i,j)w=k;h=l;
14、void fun(Point 该程序的运行结果为:0,2动态联编从对静态联编的上述分析中可以知道,编译程序在编译阶段并不能确切知道将要调用的函数,只有在程序执行时才能确定将要调用的函数,为此要确切知道该调用的函数,要求联编工作要在程序运行时进行,这种在程序运行时进行的联编工作被称为动态联编,或称动态绑定,又叫晚期联编。动态联编实际上是进行动态识别。,返回本节,9.2.2 虚函数的概念,虚函数是在基类中冠以关键字 virtual 的成员函数。它是动态联编的基础。虚函数是成员函数,而且是非static的成员函数。它提供了一种接口界面,并且可以在一个或多个派生类中被重定义。说明虚函数的方法如下:vi
15、rtual()其中,被关键字virtual说明的函数称为虚函数。,例9-12:分析以下程序的执行结果。#include class Basepublic:void who()coutbasen;class first_d:public Basepublic:void who()coutFirst derivationn;,class second_d:public Basepublic:void who()coutwho();/2p=/5,p-who();/6first_obj.who();second_obj.who();此程序的运行结果为:basebase baseFirst deriva
16、tionSecond derivation,例9-14:分析以下程序的执行结果。#include class Apublic:virtual void show()coutclass A show()is called.endl;class B:public Apublic:void show()coutclass B show()is called.endl;void main(),A demoA,*ptr;B demoB;ptr=此程序的运行结果为:class A show()is called.class B show()is called.,例9-15:进一步的例子,先考虑下面的代码:
17、#include#include class Creaturepublic:char*KindOf()return Creature;class Animal:public Creaturepublic:char*KindOf(),return Animal;class Fish:public Creaturepublic:char*KindOf()return Fish;void main()Animal animal;Fish fish;Creature*pCreature;Animal*pAnimal=,pCreature=pAnimal;coutKindOf():KindOf()Kin
18、dOf():KindOf()KindOf():KindOf()KindOf():KindOf()endl;,此程序的运行结果为:pAnimal-KindOf():AnimalpCreature-KindOf():CreaturepFish-KindOf():FishpCreature-KindOf():Creature,例9-17:一个简单应用。#includeclass figureprotected:double x,y;public:void set_dim(double i,double j=0)x=i;y=j;virtual void show_area()coutNo area c
19、omputation defined for this class.n;class triangle:public figure public:void show_area()coutTriangle with highx and base y;cout has an area of x*0.5*yn;,class square:public figure public:void show_area()coutSquare with dimension x*y;cout has an area of x*yn;class circle:public figure public:void sho
20、w_area()coutCircle with radius x;cout has an area of 3.14*x*xn;,;main()figure*p;triangle t;square s;circle c;p=此程序的类层次关系如图9-1所示。,图9-1 类层次关系示意图,返回本节,9.2.3 动态联编与虚函数,通过下面例9-19可以看到,派生类中对基类的虚函数进行替换时,要求派生类中说明的虚函数与基类中的被替换的虚函数之间满足如下条件:(1)与基类的虚函数有相同的参数个数;(2)其参数的类型与基类的虚函数的对应参数类型相同;(3)其返回值或者与基类虚函数的相同,或者都返回指针或引
21、用,并且派生类虚函数所返回的指针或引用的基类型是基类中被替换的虚函数所返回的指针或引用的基类型的子类型。,总结动态联编的实现需要如下三个条件:,(1)要有说明的虚函数;(2)调用虚函数操作的是指向对象的指针或者对象引用;或者是由成员函数调用虚函数;(3)子类型关系的建立。,下面给出一个动态联编的例子。例9-19:动态联编示例程序。#include class Pointpublic:Point(double i,double j)x=i;y=j;virtual double Area()const return 0.0;private:double x,y;class Rectangle:pu
22、blic Pointpublic:Rectangle(double i,double j,double k,double l);/double Area()const return w*h;,virtual double Area()const return w*h;private:double w,h;Rectangle:Rectangle(double i,double j,double k,double l):Point(i,j)w=k;h=l;void fun(Point,例9-21:构造函数中调用虚函数示例。#include class Apublic:A()virtual void
23、 f()coutA:f()called.n;class B:public Apublic:B()f();void g()f();class C:public B,public:C()virtual void f()coutC:f()called.n;void main()C c;c.g();此程序的运行结果为:A:f()called.C:f()called.,返回本节,9.2.4 虚函数的限制,(1)在类体系中访问一个虚函数时,应使用指向基类类型的指针或对基类类型的引用,以满足运行时多态性的要求。(2)在派生类中重新定义虚函数时,必须保证该函数的值和参数与基类中的说明完全一致,否则就属于重载(
24、参数不同)或是一个错误(返回值不同)。(3)若在派生类中没有重新定义虚函数,则该类的对象将使用其基类中的虚函数代码。(4)虚函数必须是类的一个成员函数,不能是友元,但它可以是另一个类的友元。(5)析构函数可以是virtual的虚函数,但构造函数则不得是虚函数。(6)一个类的虚函数仅对派生类中重定义的函数起作用,对其他函数没有影响。,返回本节,9.2.5 虚函数与重载函数的比较,(1)重载函数要求函数有相同的返回值类型和函数名称,并有不同的参数序列;而虚函数则要求这三项(函数名、返回值类型和参数序列)完全相同。(2)重载函数可以是成员函数或友元函数,而虚函数只能是成员函数。(3)重载函数的调用是
25、以所传递参数序列的差别作为调用不同函数的依据;虚函数是根据对象的不同去调用不同类的虚函数。(4)虚函数在运行时表现出多态功能,这是C+的精髓;而重载函数则在编译时表现出多态性。,返回本节,9.3 纯虚函数和抽象类,9.3.1 纯虚函数9.3.2 抽象类9.3.3 虚析构函数,返回首页,9.3.1 纯虚函数,在许多情况下,在基类中不能给出有意义的虚函数定义,这时可以把它说明成纯虚函数,把它的定义留给派生类来做。定义纯虚函数的一般形式为:class 类名virtual 返回值类型 函数名(参数表)=0;纯虚函数是一个在基类中说明的虚函数,它在基类中没有定义,要求任何派生类都定义自己的版本。纯虚函数
26、为各派生类提供一个公共界面。,下面的代码在类Creature中将虚函数 KindOf 声明为纯虚函数:class Creaturepublic:virtual char*KindOf()=0;char*Creature:KindOf()return Creature;,使用下面的格式也是可以的:class Creaturepublic:virtual char*KindOf()=0return Creature;,例9-22:分析以下程序的运行结果。#include#include class databasepublic:int number;char goodsname20;float p
27、rice;database(int n,char*s,float p)number=n;strcpy(goodsname,s);price=p;virtual void reporter()=0;,class reporter1:public databasepublic:reporter1(int n,char*s,float p):database(n,s,p)void reporter()coutnumber goodsnameendl;coutnumber goodsnameendl;class reporter2:public reporter1public:,reporter2(i
28、nt n,char*s,float p):reporter1(n,s,p)void reporter()coutnumber goodsname priceendl;coutnumber goodsname price;void main()reporter1 p1(100,ink,10.0);reporter2 p2(101,pen,20.0);p1.reporter();p2.reporter();,此程序的运行结果为:number goodsname100inknumber goodsname price101pen 20,返回本节,9.3.2 抽象类,如果一个类中至少有一个纯虚函数,那
29、么这个类被称为抽象类(abstract class)。抽象类中不仅包括纯虚函数,也可包括虚函数。抽象类中的纯虚函数可能是在抽象类中定义的,也可能是从它的抽象基类中继承下来且重定义的。抽象类有一个重要特点,即抽象类必须用作派生其他类的基类,而不能用于直接创建对象实例。一个抽象类不可以用来创建对象,只能用来为派生类提供一个接口规范,派生类中必须重载基类中的纯虚函数,否则它仍将被看作一个抽象类。,例如:class point/*/;class shape/抽象类 point center;public:point where()return center;void move(point p)ente
30、r=p;draw();virtual void rotate(int)=0;/纯虚函数 virtual void draw()=0;/纯虚函数;shape x;/error,抽象类不能建立对象shape*p;/ok,可以声明抽象类的指针shape f();/error,抽象类不能作为返回类型void g(shape);/error,抽象类不能作为参数类型shape/ok,可以声明抽象类的引用,从基类继承来的纯虚函数,在派生类中仍是虚函数。例如:class point/*/;class shape/抽象类 point center;public:point where()return cente
31、r;void move(point p)enter=p;draw();virtual void rotate(int)=0;/纯虚函数 virtual void draw()=0;/纯虚函数;,class ab_circle:public shape/继承的 ab_circle:draw()也是一个纯虚函数/ab_circle类仍为抽象类 int radius;public:void rotate(int);要使ab_circle成为非抽象类,必须作以下说明:class ab_circle:public shape int radius;public:void rotate(int);void
32、 draw();,例9-23:使用虚函数作出不同的销售报表示例。#include#include class databasepublic:int number;char goodsname20;float price;char sale;database(int n,char*s,float p,char a)number=n;strcpy(goodsname,s);price=p;sale=a;,float couter()float t;t=price*number;return(t);virtual void reporter()=0;class reporter1:public da
33、tabasepublic:reporter1(int n,char*s,float p,char a):database(n,s,p,a)void reporter(),coutnumber goodsnameendl;class reporter2:public reporter1public:reporter2(int n,char*s,float p,char a):reporter1(n,s,p,a)void reporter()coutnumber goodsname price saleendl;,class reporter3:public reporter2 public:re
34、porter3(int n,char*s,float p,char a):reporter2(n,s,p,a)void reporter()coutnumber goodsname couter()endl;void main()int k;reporter3 p1(200,pen,9.0,s);reporter3 p2(20,paper,10.0,s);reporter3 p3(180,ink,1.0,s);,coutk;if(k=1)reporter1*p;coutreporter();p=else if(k=2),coutreporter();p=,此程序的运行结果为:input the
35、 reporter number you want:1.simple pletely style3.counting style3numbergoodsname totalincoming($)-200pen 180020paper 200180ink 180,返回本节,9.3.3 虚析构函数,在析构函数前面加上关键字virtual进行说明,称该析构函数为虚析构函数。例如:class B virtual B();,例9-24:虚析构函数示例程序。#include class Apublic:virtual A()coutA:A()Called.n;class B:public APublic:B(int i)buf=new chari;virtual B()delete buf;coutB:B()Called.n;,private:char*buf;void fun(A*a)delete a;void main()A*a=new B(15);fun(a);执行该程序输出如下结果:B:B()Called.A:A()Called.,返回本节,