《C程序设计第8章.pptx》由会员分享,可在线阅读,更多相关《C程序设计第8章.pptx(43页珍藏版)》请在taowenge.com淘文阁网|工程机械CAD图纸|机械工程制图|CAD装配图下载|SolidWorks_CaTia_CAD_UG_PROE_设计图分享下载上搜索。
1、#include class A public:void show()coutA:show()endl;class B:public A public:void show()coutB:show()endl;class C:public Bpublic:void show()coutC:show()show();p=&b;/A类指针指向类指针指向B类对象类对象p-show();p=&c;/A类指针指向类指针指向C类对象类对象p-show();输出输出:A:display()A:display()A:display()解决办法:虚函数解决办法:虚函数第2页/共43页 如果一个派生类有多个直接基类
2、,而这些直接基类又如果一个派生类有多个直接基类,而这些直接基类又有一个共同的基类,则在最终的派生类中会保留该间接共有一个共同的基类,则在最终的派生类中会保留该间接共同基类数据成员的多份同名成员。同基类数据成员的多份同名成员。-二义性二义性解决办法:解决办法:定义虚基类,使得在继承间接共同基类时只保留一份定义虚基类,使得在继承间接共同基类时只保留一份成员。成员。为保证虚基类在派生类中只继承一次,应当在该基类为保证虚基类在派生类中只继承一次,应当在该基类的所有直接派生类中声明为虚基类。否则仍然会出现对的所有直接派生类中声明为虚基类。否则仍然会出现对基类的多次继承。基类的多次继承。声明虚基类格式:声
3、明虚基类格式:class 派生类名:派生类名:virtual 继承方式继承方式 基类名基类名第3页/共43页虚基类的初始化:虚基类的初始化:class A public:A(int i);class B:virtual public A public:B(int n):A(n);class C:virtual public A public:C(int n):A(n);class D:public B,public C public:D(int n):A(n),B(n),C(n);第4页/共43页注意:注意:1.1.前介绍,在派生类的构造函数中只需对其直接基类初始前介绍,在派生类的构造函数中只
4、需对其直接基类初始化,再由其直接基类负责对间接基类初始化。化,再由其直接基类负责对间接基类初始化。2.2.现在,由于虚基类在派生类中只有一份数据成员,所以现在,由于虚基类在派生类中只有一份数据成员,所以这份数据成员的初始化必须由派生类直接给出。这份数据成员的初始化必须由派生类直接给出。派生类不仅要负责对其直接基类初始化还要负责对虚派生类不仅要负责对其直接基类初始化还要负责对虚基类初始化。基类初始化。在派生类对象的在派生类对象的创建创建中:中:首先是虚基类的构造函数并按它们声明的顺序构造。首先是虚基类的构造函数并按它们声明的顺序构造。第二批是非虚基类的构造函数按它们声明的顺序调用。第二批是非虚基
5、类的构造函数按它们声明的顺序调用。第三批是成员对象的构造函数。第三批是成员对象的构造函数。最后是派生类自己的构造函数被调用。最后是派生类自己的构造函数被调用。构造函数执行次序:构造函数执行次序:析构的次序与构造的次序相反。析构的次序与构造的次序相反。第5页/共43页例:例:通过构造函数的初始化表对虚基类进行初始化通过构造函数的初始化表对虚基类进行初始化。#include class A /定义基类定义基类A Apublic:int x;A(int a=0)x=a;/基类构造函数,有一个参数基类构造函数,有一个参数A()coutA析构析构endl;class B:virtual public A
6、 /A/A作为作为B B的虚基类的虚基类public:int y;B(int a=0,int b=0):A(b)y=a;/B/B类构造函数,在初始化表中对虚基类初始化类构造函数,在初始化表中对虚基类初始化B()coutB析构析构endl;void PB()coutx=xty=yendl;第6页/共43页class C:virtual public A /A/A作为作为C C的虚基类的虚基类public:int z;C(int a=0,int b=0):A(b)z=a;/C/C类构造函数,在初始化表中对虚基类初始化类构造函数,在初始化表中对虚基类初始化C()coutC析构析构endl;void
7、PC()coutx=xtz=zendl;class D:public B,public Cpublic:int m;D(int a,int b,int d,int e,int f):B(a,b),C(d,e)m=f;D()coutD析构析构endl;void Print()PB();PC();coutm=mendl;A()如果在虚基类中定义了带参数的构造函数,则在其所有如果在虚基类中定义了带参数的构造函数,则在其所有派生类中(包括直接派生或间接派生的派生类)中,通派生类中(包括直接派生或间接派生的派生类)中,通过构造函数的初始化表对虚基类进行初始化。过构造函数的初始化表对虚基类进行初始化。第7
8、页/共43页void main()D d1(100,200,300,400,500);d1.Print();d1.x=400;d1.Print();输出输出:x=0 y=100 /基类构造函数使用了默认值做参数基类构造函数使用了默认值做参数x=0 z=300 /基类构造函数使用了默认值做参数基类构造函数使用了默认值做参数m=500 x=400 y=100 /主函数改变了基类参数值主函数改变了基类参数值x=400 z=300 /主函数改变了基类参数值主函数改变了基类参数值m=500D析构C析构B析构A析构D(int a,int b,int d,int e,int f):A(),B(a,b),C(
9、d,e)m=f;第8页/共43页8.7 8.7 多态性与虚函数多态性与虚函数多态性是面向对象程序设计的关键技术之一。指的是多态性是面向对象程序设计的关键技术之一。指的是调用同样的函数实现不同的功能。调用同样的函数实现不同的功能。在在C+中中有两种多有两种多态态编译时的编译时的多态性多态性 (静态多态性)(静态多态性)运行时的多态运行时的多态性性 在程序执行前无法确定调用哪一个函数,必在程序执行前无法确定调用哪一个函数,必须在程序执行过程中,根据执行的具体情况须在程序执行过程中,根据执行的具体情况来动态地确定。来动态地确定。通过类继承关系和虚函数来通过类继承关系和虚函数来实现的。目的:建立一种通
10、用的程序。实现的。目的:建立一种通用的程序。通过函数的重载和运算符的重通过函数的重载和运算符的重载实现。载实现。多态性是多态性是“一个接口,多种方法一个接口,多种方法”:通过继承产生了相关的不同的派生类,和基类成员同通过继承产生了相关的不同的派生类,和基类成员同名的成员在不同的派生类中有不同的含义。名的成员在不同的派生类中有不同的含义。第9页/共43页虚函数的作用虚函数的作用:允许在派生类中重新定义与基类同名的函数,并且允许在派生类中重新定义与基类同名的函数,并且可以通过基类指针或引用来访问基类和派生类中的同名可以通过基类指针或引用来访问基类和派生类中的同名函数。函数。虚函数是一个类的成员函数
11、,定义虚函数的格式如下:虚函数是一个类的成员函数,定义虚函数的格式如下:virtual 返回类型返回类型 函数名(参数表);函数名(参数表);注:注:virtual仅用于类定义中,若虚函数在类外定义,不能仅用于类定义中,若虚函数在类外定义,不能 加加virtual(类中函数说明要加类中函数说明要加virtual)。当某一个类的一个类成员函数被定义为虚函数,则由当某一个类的一个类成员函数被定义为虚函数,则由该类派生出来的所有派生类中,该函数始终保持虚函数的该类派生出来的所有派生类中,该函数始终保持虚函数的特征。特征。8.7.1 8.7.1 虚函数的定义虚函数的定义在派生类中重新定义虚函数在派生类
12、中重新定义虚函数(称称超载超载或或覆盖覆盖)时,不必再加关键字时,不必再加关键字virtual,但,但函数头一函数头一定要完全相同定要完全相同。第10页/共43页虚函数的使用虚函数的使用:(1)在基类用在基类用virtual声明成员函数为虚函数。声明成员函数为虚函数。(2)在派生类中重新定义此函数,要求函数名、函数类型、在派生类中重新定义此函数,要求函数名、函数类型、函数参数个数和类型全部与基类的虚函数相同,并根据函数参数个数和类型全部与基类的虚函数相同,并根据派生类的需要重新定义函数体。派生类的需要重新定义函数体。(3)定义一个指向基类对象的指针变量,并使它指向同一定义一个指向基类对象的指针
13、变量,并使它指向同一类族中需要调用该函数的对象。类族中需要调用该函数的对象。(4)通过该指针变量调用此虚函数,此时调用的就是指针通过该指针变量调用此虚函数,此时调用的就是指针变量指向的对象的同名函数。变量指向的对象的同名函数。第11页/共43页例:例:#includeclass Apublic:virtual void show();void A:show()coutA:show()endl;class B:public Apublic:void show()coutB:show()endl;class C:public Bpublic:void show()coutC:show()show(
14、);/动态调用动态调用p=&b1;p-show();/动态调用动态调用 p=&c1;p-show();/动态调用动态调用运行结果运行结果:A:show()B:show()C:show()void main()C c;c.A:show();/静态调用静态调用c.B:show();/静态调用静态调用 c.show();/静态调用静态调用第13页/共43页【例例8.6】计算学分。计算学分。由本科生类派生出研究生类由本科生类派生出研究生类GradeStudent,但它们各自,但它们各自的从课程学时数折算为学分数的算法是不同的,本科生是的从课程学时数折算为学分数的算法是不同的,本科生是1616个学时个学
15、时一学分,而研究生是一学分,而研究生是2020个学时一学分。个学时一学分。class Studentchar coursename20;/课程名课程名int classhour;/学时学时int credit;/学分学分,未考虑未考虑0.50.5学分学分public:Student();/构造函数,将类成员赋值为构造函数,将类成员赋值为0 0void Calculate();/计算学分(考虑该函数的设置)计算学分(考虑该函数的设置)void SetCourse(char*str,int hour);/设置课程和学时设置课程和学时int GetHour();/获取学时数获取学时数void Set
16、Credit(int cred);/获取学分获取学分void Print();/显示学时和学分显示学时和学分;第14页/共43页class Student char coursename20;/课程名课程名 int classhour;/学时学时 int credit;/学分学分public:Student()coursename0=0;classhour=0;credit=0;virtual void Calculate()credit=classhour/16;void SetCourse(char*str,int hour)/设置课程和学时设置课程和学时 strcpy(coursenam
17、e,str);classhour=hour;第15页/共43页 int GetHour()return classhour;/获取学时数获取学时数 void SetCredit(int cred)credit=cred;/获取学分获取学分 voidPrint()/显示学时和学分显示学时和学分 coutcoursenametclasshour学时学时 tcredit学分学分endl;class GradeStudent:public Studentpublic:GradeStudent();/基类缺省构造函数调用可省基类缺省构造函数调用可省 void Calculate()SetCredit(G
18、etHour()/20);第16页/共43页void main()Student s,*ps;GradeStudent g;s.SetCourse(物理物理,80);s.Calculate();g.SetCourse(物理物理,80);g.Calculate();cout本科生本科生:t;s.Print();cout“研究生研究生:Calculate();cout本科生本科生:Print();ps=&g;ps-Calculate();cout研究生研究生:Print();输出结果为输出结果为:本科生:物理本科生:物理 80 学时学时 5学分学分研究生:物理研究生:物理 80 学时学时 4学分学
19、分本科生:数学本科生:数学 160学时学时 10学分学分研究生:数学研究生:数学 160学时学时 8学分学分第17页/共43页使用基类引用去指向不同对象,同样可实现运行时多态性。使用基类引用去指向不同对象,同样可实现运行时多态性。void Calfun(Student&ps,char*str,int hour)ps.SetCourse(str,hour);ps.Calculate();ps.Print();void main()Student s;GradeStudent g;cout本科生本科生:;Calfun(s,物理物理,80);cout研究生研究生:;Calfun(g,“物理物理”,8
20、0);【例例8.7】计算学分,计算学分,基类与派生类定义同基类与派生类定义同【例例8.6】(基类派生类函数不变),增加函(基类派生类函数不变),增加函数:数:第18页/共43页几点提示:几点提示:5.虚函数重构不同于重载。虚函数重构不同于重载。派生类中定义虚函数必须与基类中派生类中定义虚函数必须与基类中的虚函数同名外,还必须同参数表,同返回类型。否则被认为是重的虚函数同名外,还必须同参数表,同返回类型。否则被认为是重载,而不是虚函数。载,而不是虚函数。1.实现动态多态性需要三个条件:派生类体系、虚函数、指针或引用。实现动态多态性需要三个条件:派生类体系、虚函数、指针或引用。2.析构函数可定义为
21、虚函数,构造函数不能定义虚函数。析构函数可定义为虚函数,构造函数不能定义虚函数。在基类及其派生类都有动在基类及其派生类都有动态分配的内存空间时,应当考虑把析构函数定义为虚函数,以便能够用基类指针实现撤消态分配的内存空间时,应当考虑把析构函数定义为虚函数,以便能够用基类指针实现撤消对象的多态性。对象的多态性。3.虚函数执行速度稍慢。为了实现多态性,每一个派生类中均要保存相应虚函数的虚函数执行速度稍慢。为了实现多态性,每一个派生类中均要保存相应虚函数的入口地址表,函数的调用机制也与一般函数不同。这是多态性为实现通用性付出的代价。入口地址表,函数的调用机制也与一般函数不同。这是多态性为实现通用性付出
22、的代价。4.静态成员函数不能作为虚函数,因为它不属于某个对象,而静态成员函数不能作为虚函数,因为它不属于某个对象,而是为所有同类对象共有。内联函数也不能作为虚函数。是为所有同类对象共有。内联函数也不能作为虚函数。第19页/共43页声明虚函数的成员函数主要考虑以下几点声明虚函数的成员函数主要考虑以下几点:(1)(1)首先看成员函数所在的类是否会作为基类。然后看成首先看成员函数所在的类是否会作为基类。然后看成员函数在类的继承后有无可能被更改功能,如果希望更改员函数在类的继承后有无可能被更改功能,如果希望更改其功能的,一般应该将它声明为虚函数。其功能的,一般应该将它声明为虚函数。(2)(2)如果成员
23、函数在类被继承后功能不需修改,或派生类如果成员函数在类被继承后功能不需修改,或派生类用不到该函数,则不要把它声明为虚函数。用不到该函数,则不要把它声明为虚函数。(3)(3)应考虑对成员函数的调用是通过对象名还是通过基类应考虑对成员函数的调用是通过对象名还是通过基类指针或引用去访问,如果是通过基类指针或引用去访问指针或引用去访问,如果是通过基类指针或引用去访问的,则应当声明为虚函数。的,则应当声明为虚函数。第20页/共43页虚析构函数虚析构函数析构函数的作用:析构函数的作用:在对象撤销之前做必要的在对象撤销之前做必要的“清理现场清理现场”的工作。当派生的工作。当派生类的对象从内存中撤销时一般先调
24、用派生类的析构函数,类的对象从内存中撤销时一般先调用派生类的析构函数,然后再调用基类的析构函数。然后再调用基类的析构函数。如果用如果用new运算符建立了临时对象,若基类中有析构运算符建立了临时对象,若基类中有析构函数,并且定义了一个指向该基类的指针变量。在程序用函数,并且定义了一个指向该基类的指针变量。在程序用带指针参数的带指针参数的delete运算符撤销对象时,会出现运算符撤销对象时,会出现:系统会只系统会只执行基类的析构函数,而不执行派生类的析构函数。执行基类的析构函数,而不执行派生类的析构函数。解决办法:解决办法:把析构函数定义为虚函数,实现撤消对象时的多态性。把析构函数定义为虚函数,实
25、现撤消对象时的多态性。第21页/共43页虚析构函数声明:虚析构函数声明:virtual 类名类名();#includeclass A public:A()coutA析构析构n;class B:public A public:B();B();private:int*p;B:B()p=new int();B:B()coutPrintStudentInfo();per4=stu4;delete per4;/基类指针撤销派生类对象,必须显式撤销基类指针撤销派生类对象,必须显式撤销 第25页/共43页例:例:PointArea()Show()double x,y;CircleArea()Show()do
26、ubl radius;class Pointpublic:.double Area()return 0;.;class Circle:public Pointpublic:double Area()return PI*radius*radius;基类函数为空或可以不要,在实现部分仍然要写出函数体,纯虚函数可以解决接口基类函数为空或可以不要,在实现部分仍然要写出函数体,纯虚函数可以解决接口8.7.2 8.7.2 纯虚函数纯虚函数第26页/共43页1.纯虚函数不同于空函数,纯虚函数不能调用。纯虚函数不同于空函数,纯虚函数不能调用。2.派生类中必须重新定义纯虚函数的函数体。派生类中必须重新定义纯虚函
27、数的函数体。纯虚函数纯虚函数(pure virtual function)当基类中某虚函数无法具)当基类中某虚函数无法具体实现,可定义为纯虚函数,具体实现依赖于派生类。定义格式为:体实现,可定义为纯虚函数,具体实现依赖于派生类。定义格式为:定义纯虚函数要注意:定义纯虚函数要注意:virtual 返回类型返回类型 函数名(参数表)函数名(参数表)=0;含有纯虚函数的基类不能用来定义对象,称为含有纯虚函数的基类不能用来定义对象,称为抽象类抽象类。抽象类的意义在于定义框架。抽象类的意义在于定义框架。第27页/共43页注:注:纯虚函数没有函数体;纯虚函数没有函数体;最后面的最后面的“=0=0”并不表示
28、函数返回值为并不表示函数返回值为0 0,它只起形式,它只起形式 上的作用,告诉编译系统上的作用,告诉编译系统“这是纯虚函数这是纯虚函数”;这是一个声明语句,最后应有分号。这是一个声明语句,最后应有分号。含有纯虚函数的基类是不能用来定义对象的。纯虚含有纯虚函数的基类是不能用来定义对象的。纯虚函数没有实现部分,不能产生对象,含有纯虚函数的类函数没有实现部分,不能产生对象,含有纯虚函数的类是抽象类。是抽象类。第28页/共43页抽象类抽象类带有纯虚函数的类称为抽象类带有纯虚函数的类称为抽象类:class 类名类名 virtual 类型类型 函数名函数名(参数表参数表)=0;/纯虚函数纯虚函数 .第29
29、页/共43页作用:作用:1.1.抽象类为抽象和设计的目的而建立,将有关的数据和抽象类为抽象和设计的目的而建立,将有关的数据和 行为组织在一个继承层次结构中,保证派生类具有要行为组织在一个继承层次结构中,保证派生类具有要 求的行为。求的行为。2.2.对于暂时无法实现的函数,可以声明为纯虚函数,留对于暂时无法实现的函数,可以声明为纯虚函数,留 给派生类去实现。给派生类去实现。注意注意l抽象类只能作为抽象类只能作为基类基类来使用。来使用。l不能声明抽象类的对象。不能声明抽象类的对象。l构造函数不能是虚函数,析构函数可以是虚函数。构造函数不能是虚函数,析构函数可以是虚函数。第30页/共43页#incl
30、ude class B0 /抽象基类抽象基类B0B0声明声明public:/外部接口外部接口virtual void display()=0;/纯虚函数成员纯虚函数成员;class B1:public B0 /公有派生公有派生public:void display()/虚成员函数虚成员函数 coutB1:display()endl;第31页/共43页class D1:public B1 /公有派生公有派生public:void display()/虚成员函数虚成员函数 coutD1:display()display();第32页/共43页void main()B0*p;/声明抽象基类指针声明抽
31、象基类指针B1 b1;/声明派生类对象声明派生类对象D1 d1;/声明派生类对象声明派生类对象p=&b1;fun(p);/调用派生类调用派生类B1B1函数成员函数成员p=&d1;fun(p);/调用派生类调用派生类D1D1函数成员函数成员程序的运行结果为:程序的运行结果为:B1:display()D1:display()第33页/共43页结论结论:(1)(1)一个基类如果包含一个或一个以上纯虚函数,就是抽一个基类如果包含一个或一个以上纯虚函数,就是抽 象基类。象基类。(2)(2)派生类如果没有实现基类中的全部派生类如果没有实现基类中的全部纯虚函数,则该派纯虚函数,则该派 生类仍然是抽象类;生类
32、仍然是抽象类;(3)(3)在类的层次结构中,顶层或最上面的几层可以是抽象在类的层次结构中,顶层或最上面的几层可以是抽象 基类。抽象基类体现了本类族中各类的共性,把各类基类。抽象基类体现了本类族中各类的共性,把各类 中共有的成员函数集中在抽象基类中声明。中共有的成员函数集中在抽象基类中声明。(4)(4)抽象基类是本类族的公共接口。抽象基类是本类族的公共接口。(5)(5)区别静态关联和动态关联。区别静态关联和动态关联。通过对象调用虚函数静态关联通过对象调用虚函数静态关联 通过基类指针调用虚函数动态关联通过基类指针调用虚函数动态关联第34页/共43页(6)(6)如果在基类声明了虚函数,则在派生类中凡
33、是与该函如果在基类声明了虚函数,则在派生类中凡是与该函 数有相同的函数名、函数类型、参数个数和类型的函数有相同的函数名、函数类型、参数个数和类型的函 数,均为虚函数数,均为虚函数(不论在派生类中是否用不论在派生类中是否用virtualvirtual声明声明)。把类的声明与类的使用分离。对于设计类库的软件开把类的声明与类的使用分离。对于设计类库的软件开发商来说尤为重要。开发商设计了各种各样的类,但不向发商来说尤为重要。开发商设计了各种各样的类,但不向用户提供源代码,用户可以不知道类是怎样声明的,但是用户提供源代码,用户可以不知道类是怎样声明的,但是可以使用这些类来派生出自己的类。可以使用这些类来
34、派生出自己的类。(7)(7)使用虚函数提高了程序的可扩充性。使用虚函数提高了程序的可扩充性。第35页/共43页class Person int MarkAchieve;/业绩分业绩分 char Name20;public:Person(char*name)strcpy(Name,name);MarkAchieve=0;void SetMark(int mark)MarkAchieve=mark;virtual void CalMark()=0;void Print()coutName的业绩分为的业绩分为:MarkAchieveCalMark();pp-Print();pp=&t1;pp-Cal
35、Mark();pp-Print();pp=&t2;pp-CalMark();pp-Print();return 0;第39页/共43页class Simpson double Intevalue,a,b;/Intevalue积分值,积分值,a积分下限,积分下限,b积分上限积分上限public:virtual double fun(double x)=0;/被积函数声明为纯虚函数被积函数声明为纯虚函数 Simpson(double ra=0,double rb=0)a=ra;b=rb;Intevalue=0;void Integrate()double dx;int i;dx=(b-a)/200
36、0;Intevalue=fun(a)+fun(b);for(i=1;i2000;i+=2)Intevalue+=4*fun(a+dx*i);for(i=2;i2000;i+=2)Intevalue+=2*fun(a+dx*i);Intevalue*=dx/3;【例8.9】用虚函数来实现辛普生法求函数的定积分。(用虚函数来实现辛普生法求函数的定积分。(P210 例例6.11)第40页/共43页 void Print()cout积分值积分值=IntevalueIntegrate();/动态动态 B b1(0.0,1.0);b1.Integrate();/静态静态 s-Print();b1.Print();return 0;第42页/共43页感谢您的欣赏!第43页/共43页