《C程序设计电子多态性和虚函数.pptx》由会员分享,可在线阅读,更多相关《C程序设计电子多态性和虚函数.pptx(73页珍藏版)》请在taowenge.com淘文阁网|工程机械CAD图纸|机械工程制图|CAD装配图下载|SolidWorks_CaTia_CAD_UG_PROE_设计图分享下载上搜索。
1、9.1 多态性9.1.1 普通成员函数重载9.1.2 构造函数重载9.1.3 派生类指针返回首页第1页/共73页9.1.1 普通成员函数重载1函数重载的方法2函数重载的表示形式(1)在一个类说明中重载。(2)基类的成员函数在派生类中重载。3函数重载的注意事项4函数重载的二义性第2页/共73页1函数重载的方法函数重载的方法例9-1:给出以下程序的运行结果。#include int square(int x)return x*x;double square(double y)return y*y;main()第3页/共73页coutThe square of integer 7 issquare(
2、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第4页/共73页例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()float a,b,
3、r;coutr;第5页/共73页cout圆周长:length(r)endl;coutab;cout矩形周长:length(a,b)endl;运行结果为:输入圆半径:7 圆周长:43.981输入矩形长和宽:3 4 矩形周长:14 第6页/共73页2函数重载的表示形式函数重载的表示形式例9-3:分析以下程序的执行结果。#include class Sampleint i;double d;public:void setdata(int n)i=n;void setdata(double x)d=x;void disp()第7页/共73页couti=i,d=dendl;void main()Samp
4、le s;s.setdata(7);s.setdata(7.5);s.disp();此程序的运行结果为:i=7,d=7.5第8页/共73页有3种编译区分方法:1)根据参数的特征加以区分。2)使用“:”加以区分。3)根据类对象加以区分。第9页/共73页3函数重载的注意事项函数重载的注意事项在C+语言中,编译程序选择相应的重载函数版本时函数返回值类型是不起作用的。不能仅靠函数的返回值来区别重载函数,必须从形式参数上区别开来。例如:void print(int a);void print(int a,int b);int print(float a);这三个函数是重载函数,因为C+编译程序可以从形式
5、参数上将它们区别开来。但:int f(int a);double f(int a);第10页/共73页4函数重载的二义性函数重载的二义性函数重载的二义性(ambiguity)是指C+语言的编译程序无法在多个重载函数中选择正确的函数进行调用。这些二义性错误是致命的,因而编译程序将无法生成目标代码。函数重载的二义性主要源于C+语言的隐式类型转换与默认参数。第11页/共73页例9-4:隐式类型转换造成函数重载二义性示例。#include float abs(float x)return(x0?x:-x);double abs(double x)return(x0?x:-x);int main()co
6、utabs(1.78)endl;/调用abs(double)/coutabs(-7)endl;/错误,编译程序无法确定调用哪一个abs()函数在重载函数中使用默认参数也可能造成二义性。返回本节第12页/共73页9.1.2 构造函数重载构造函数可以像普通函数一样被重载,而且也可能是C+语言应用函数重载最多的地方,因为设计一个类时总是希望创建对象的同时能以多种方式初始化对象的内部状态,而构造函数只能有一个名字,即该类的名字。当建设一个可复用类库时,重载构造函数可以更好地提高类界面的完整性。第13页/共73页例:class X public:X();X(int);X(int,char);X(floa
7、t,char);.;void f()X a;/调用构造函数 X()X b(1);/调用构造函数 X(int)X c(1,c);/调用构造函数 X(int,char)X d(2.3,d);/调 用 构 造 函 数 X(float,char).第14页/共73页例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()第15页/共73页yea
8、r=1999;month=11;day=24;coutyear/month/dayendl;TDate:TDate(int d)year=1999;month=11;day=d;coutyear/month/dayendl;TDate:TDate(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;第16页/共73页coutyear/month/dayendl;void main()TDate aday;TDate bdat
9、e(10);TDate cdate(8,8);TDate ddate(1998,1,1);输出结果为:1999/11/241999/11/101999/8/81999/1/1返回本节第17页/共73页9.1.3 派生类指针指向基类和派生类的指针是相关的。例如:A *p;/指向类型 A 的对象的指针A A_obj;/类型 A 的对象B B_obj;/类型 B 的对象p=&A_obj;/p 指向类型 A 的对象p=&B_obj;/p 指向类型 B 的对象,它是 A 的派生类第18页/共73页例9-8:分析以下程序的执行结果。class A_class char name80;public:void
10、 put_name(char*s)strcpy(name,s);void show_name()coutnamen;class B_class:public A_class char phone_num80;public:void put_phone(char*num)strcpy(phone_num,num);void show_phone()coutphone_numput_name(Zhang San);p=&B_obj;/P指针指向派生类对象,调用继承自基类的成员函数p-put_name(Li Si);A_obj.show_name();B_obj.show_name();bp=&B_
11、obj;bp-put_phone(0731_12345678);bp-show_phone();第20页/共73页(B_class*)p)-show_phone();/用基类指针访问公有派生类的特定成员,必须进行类型转换此程序的运行结果为:Zhang SanLi Si0731_123456780731_12345678第21页/共73页例9-9:写出下面的程序的执行结果。#include class Studentpublic:Student(int xx)x=xx;virtual float calcTuition();protected:int x;float Student:calcTu
12、ition()return float(x*x);第22页/共73页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;/计算研究生gs的
13、学费输出结果为:400400返回本节第23页/共73页9.2 虚函数9.2.1 静态联编与动态联编9.2.2 虚函数的概念9.2.3 动态联编与虚函数9.2.4 虚函数的限制9.2.5 虚函数与重载函数的比较返回首页第24页/共73页9.2.1 静态联编与动态联编1静态联编静态联编是指联编工作出现在编译连接阶段,这种联编又称早期联编,因为这种联编过程是在程序开始运行之前完成的。在编译时所进行的这种联编又称静态绑定。在编译时就解决了程序中的操作调用与执行该操作代码间的关系,确定这种关系又称为绑定,在编译时绑定又称静态束定。下面举一个静态联编的例子。第25页/共73页例9-11:静态联编示例程序。
14、#include class 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);第26页/共73页double Area()const return w*h;private:double w,h;Rectangle:Rectangle(double i,double j,double k,doubl
15、e l):Point(i,j)w=k;h=l;void fun(Point&s)couts.Area()endl;void main()Rectangle rec(3.0,5.2,15.0,25.0);fun(rec);该程序的运行结果为:0第27页/共73页2动态联编从对静态联编的上述分析中可以知道,编译程序在编译阶段并不能确切知道将要调用的函数,只有在程序执行时才能确定将要调用的函数,为此要确切知道该调用的函数,要求联编工作要在程序运行时进行,这种在程序运行时进行的联编工作被称为动态联编,或称动态绑定,又叫晚期联编。动态联编实际上是进行动态识别。返回本节第28页/共73页9.2.2 虚函数
16、的概念虚函数是在基类中冠以关键字 virtual 的成员函数。它是动态联编的基础。虚函数是成员函数,而且是非static的成员函数。它提供了一种接口界面,并且可以在一个或多个派生类中被重定义。说明虚函数的方法如下:virtual()其中,被关键字virtual说明的函数称为虚函数。第29页/共73页例9-12:分析以下程序的执行结果。#include class Basepublic:void who()coutbasen;class first_d:public Basepublic:void who()coutFirst derivationn;第30页/共73页class second_
17、d:public Basepublic:void who()coutwho();/2p=&first_obj;/3p-who();/4p=&second_obj;/5第31页/共73页p-who();/6first_obj.who();second_obj.who();此程序的运行结果为:basebase baseFirst derivationSecond derivation第32页/共73页例9-14:分析以下程序的执行结果。#include class Apublic:virtual void show()coutclass A show()is called.endl;class B
18、:public Apublic:void show()coutclass B show()is called.show();ptr=&demoB;ptr-show();此程序的运行结果为:class A show()is called.class B show()is called.第34页/共73页例9-15:进一步的例子,先考虑下面的代码:#include#include class Creaturepublic:char*KindOf()return Creature;class Animal:public Creaturepublic:char*KindOf()第35页/共73页ret
19、urn Animal;class Fish:public Creaturepublic:char*KindOf()return Fish;void main()Animal animal;Fish fish;Creature*pCreature;Animal*pAnimal=&animal;Fish*pFish=&fish;第36页/共73页 pCreature=pAnimal;coutKindOf():KindOf()endl;coutKindOf():KindOf()endl;pCreature=pFish;coutKindOf():KindOf()endl;coutKindOf():Ki
20、ndOf()KindOf():AnimalpCreature-KindOf():CreaturepFish-KindOf():FishpCreature-KindOf():Creature第38页/共73页例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 computation defined for this class.n;class triangle:public
21、figure public:void show_area()coutTriangle with highx and base y;cout has an area of x*0.5*yn;第39页/共73页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 show_area()coutCircle with radius x;cout has an area
22、of 3.14*x*xset_dim(10.0,5.0);p-show_area();p=&s;p-set_dim(10.0,5.0);p-show_area();p=&c;p-set_dim(9.0);p-show_area();此程序的类层次关系如图9-1所示。第41页/共73页图9-1 类层次关系示意图返回本节第42页/共73页9.2.3 动态联编与虚函数通过下面例9-19可以看到,派生类中对基类的虚函数进行替换时,要求派生类中说明的虚函数与基类中的被替换的虚函数之间满足如下条件:(1)与基类的虚函数有相同的参数个数;(2)其参数的类型与基类的虚函数的对应参数类型相同;(3)其返回值或者
23、与基类虚函数的相同,或者都返回指针或引用,并且派生类虚函数所返回的指针或引用的基类型是基类中被替换的虚函数所返回的指针或引用的基类型的子类型。第43页/共73页总结动态联编的实现需要如下三个条件:(1)要有说明的虚函数;(2)调用虚函数操作的是指向对象的指针或者对象引用;或者是由成员函数调用虚函数;(3)子类型关系的建立。第44页/共73页下面给出一个动态联编的例子。例9-19:动态联编示例程序。#include class Pointpublic:Point(double i,double j)x=i;y=j;virtual double Area()const return 0.0;pri
24、vate:double x,y;class Rectangle:public Pointpublic:Rectangle(double i,double j,double k,double l);/double Area()const return w*h;第45页/共73页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&s)couts.Area()e
25、ndl;void main()Rectangle rec(3.0,5.2,15.0,25.0);fun(rec);第46页/共73页例9-21:构造函数中调用虚函数示例。#include class Apublic:A()virtual void f()coutA:f()called.n;class B:public Apublic:B()f();void g()f();class C:public B第47页/共73页public:C()virtual void f()coutC:f()called.n;void main()C c;c.g();此程序的运行结果为:A:f()called.C
26、:f()called.返回本节第48页/共73页9.2.4 虚函数的限制(1)在类体系中访问一个虚函数时,应使用指向基类类型的指针或对基类类型的引用,以满足运行时多态性的要求。(2)在派生类中重新定义虚函数时,必须保证该函数的值和参数与基类中的说明完全一致,否则就属于重载(参数不同)或是一个错误(返回值不同)。(3)若在派生类中没有重新定义虚函数,则该类的对象将使用其基类中的虚函数代码。(4)虚函数必须是类的一个成员函数,不能是友元,但它可以是另一个类的友元。(5)析构函数可以是virtual的虚函数,但构造函数则不得是虚函数。(6)一个类的虚函数仅对派生类中重定义的函数起作用,对其他函数没有
27、影响。返回本节第49页/共73页9.2.5 虚函数与重载函数的比较(1)重载函数要求函数有相同的返回值类型和函数名称,并有不同的参数序列;而虚函数则要求这三项(函数名、返回值类型和参数序列)完全相同。(2)重载函数可以是成员函数或友元函数,而虚函数只能是成员函数。(3)重载函数的调用是以所传递参数序列的差别作为调用不同函数的依据;虚函数是根据对象的不同去调用不同类的虚函数。(4)虚函数在运行时表现出多态功能,这是C+的精髓;而重载函数则在编译时表现出多态性。返回本节第50页/共73页9.3 纯虚函数和抽象类9.3.1 纯虚函数9.3.2 抽象类9.3.3 虚析构函数返回首页第51页/共73页9
28、.3.1 纯虚函数在许多情况下,在基类中不能给出有意义的虚函数定义,这时可以把它说明成纯虚函数,把它的定义留给派生类来做。定义纯虚函数的一般形式为:class 类名virtual 返回值类型 函数名(参数表)=0;纯虚函数是一个在基类中说明的虚函数,它在基类中没有定义,要求任何派生类都定义自己的版本。纯虚函数为各派生类提供一个公共界面。第52页/共73页下面的代码在类Creature中将虚函数 KindOf 声明为纯虚函数:class Creaturepublic:virtual char*KindOf()=0;char*Creature:KindOf()return Creature;第53
29、页/共73页使用下面的格式也是可以的:class Creaturepublic:virtual char*KindOf()=0return Creature;第54页/共73页例9-22:分析以下程序的运行结果。#include#include class databasepublic:int number;char goodsname20;float price;database(int n,char*s,float p)number=n;strcpy(goodsname,s);price=p;virtual void reporter()=0;第55页/共73页class reporter
30、1:public databasepublic:reporter1(int n,char*s,float p):database(n,s,p)void reporter()coutnumber goodsnameendl;coutnumber goodsnameendl;class reporter2:public reporter1public:第56页/共73页 reporter2(int n,char*s,float p):reporter1(n,s,p)void reporter()coutnumber goodsname priceendl;coutnumber goodsname
31、price;void main()reporter1 p1(100,ink,10.0);reporter2 p2(101,pen,20.0);p1.reporter();p2.reporter();第57页/共73页此程序的运行结果为:number goodsname100inknumber goodsname price101pen 20返回本节第58页/共73页9.3.2 抽象类如果一个类中至少有一个纯虚函数,那么这个类被称为抽象类(abstract class)。抽象类中不仅包括纯虚函数,也可包括虚函数。抽象类中的纯虚函数可能是在抽象类中定义的,也可能是从它的抽象基类中继承下来且重定义的
32、。抽象类有一个重要特点,即抽象类必须用作派生其他类的基类,而不能用于直接创建对象实例。一个抽象类不可以用来创建对象,只能用来为派生类提供一个接口规范,派生类中必须重载基类中的纯虚函数,否则它仍将被看作一个抽象类。第59页/共73页例如:class point/*/;class shape/抽象类 point center;public:point where()return center;void move(point p)enter=p;draw();virtual void rotate(int)=0;/纯虚函数 virtual void draw()=0;/纯虚函数;shape x;/e
33、rror,抽象类不能建立对象shape *p;/ok,可以声明抽象类的指针shape f();/error,抽象类不能作为返回类型void g(shape);/error,抽象类不能作为参数类型shape&h(shape&);/ok,可以声明抽象类的引用第60页/共73页从基类继承来的纯虚函数,在派生类中仍是虚函数。例如:class point /*/;class shape/抽象类 point center;public:point where()return center;void move(point p)enter=p;draw();virtual void rotate(int)=0
34、;/纯虚函数 virtual void draw()=0;/纯虚函数;第61页/共73页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 draw();第62页/共73页例9-23:使用虚函数作出不同的销售报表示例。#include#incl
35、ude 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;第63页/共73页float couter()float t;t=price*number;return(t);virtual void reporter()=0;class reporter1:public databasepublic:reporter1(int n,char*s,f
36、loat p,char a):database(n,s,p,a)void reporter()第64页/共73页 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;第65页/共73页class reporter3:public reporter2 public:reporter3(int n,char*s
37、,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);第66页/共73页coutinput the reporter number you want:endl;cout1.simple pletely styleendl 3.counting stylek;if(k=1)repo
38、rter1*p;coutnumber goodsnameendl;cout-reporter();p=&p2;p-reporter();p=&p3;p-reporter();else if(k=2)第67页/共73页coutnumber goodsname price($)saleornotendl;cout-reporter();p=&p2;p-reporter();p=&p3;p-reporter();else if(k=3)reporter3*p;coutnumber goodsname totalincoming($)endl;cout-reporter();p=&p2;p-repor
39、ter();p=&p1;p-reporter();第68页/共73页此程序的运行结果为:input the reporter number you want:1.simple pletely style3.counting style3numbergoodsname totalincoming($)-200pen 180020paper 200180ink 180返回本节第69页/共73页9.3.3 虚析构函数在析构函数前面加上关键字virtual进行说明,称该析构函数为虚析构函数。例如:class B virtual B();第70页/共73页例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;第71页/共73页private:char*buf;void fun(A*a)delete a;void main()A*a=new B(15);fun(a);执行该程序输出如下结果:B:B()Called.A:A()Called.返回本节第72页/共73页感谢您的观看!第73页/共73页