C程序设计第8章1.ppt

上传人:本田雅阁 文档编号:3479307 上传时间:2019-09-01 格式:PPT 页数:44 大小:206.02KB
返回 下载 相关 举报
C程序设计第8章1.ppt_第1页
第1页 / 共44页
C程序设计第8章1.ppt_第2页
第2页 / 共44页
C程序设计第8章1.ppt_第3页
第3页 / 共44页
C程序设计第8章1.ppt_第4页
第4页 / 共44页
C程序设计第8章1.ppt_第5页
第5页 / 共44页
点击查看更多>>
资源描述

《C程序设计第8章1.ppt》由会员分享,可在线阅读,更多相关《C程序设计第8章1.ppt(44页珍藏版)》请在三一文库上搜索。

1、本周习题: P307 习题8.6 实践教程:P89 实验二十四 2 本周实验: 习题上机调试 预习:第9章流类库与输入/输出 9.19.3 (1)理解C+的基本流类体系; (2)提高标准输入/输出的健壮性; (3)掌握提取与插入运算符的重载 (4)掌握文件的输入/输出:文件的打开与关闭,文本 文件与二进制文件。 (5)掌握在构造函数中通过文件建立对象,在析构函数 中由文件保存对象的技术。,类似用实型变量描述整数,这条规则称赋值兼容规则。赋值兼容包括以下三种情况:,派生类与基类(赋值兼容),1.派生类对象赋值给基类的对象。反之不行。如: Person p; Student s; p=s;,2.派

2、生类对象的地址赋给基类指针。反之不行。如: Person *pointP; pointP=,3.派生类对象可以初始化基类的引用。反之不行。如: Person 常见用法是函数形参为基类引用,实参是派生类对象,注意:以上赋值后都只能间接使用到继承来的成员。,#include class A public: void show( ) cout“A:show()“endl; ; class B: public A public: void show() cout“B:show()“endl; ; class C: public B public: void show() cout“C:show()“e

3、ndl; ;,void main( ) A a; /声明A类对象 B b; /声明B类对象 C c; /声明C类对象 A *p; /声明A类指针 p= ,输出: A:display( ) A:display( ) A:display( ),解决办法:虚函数,如果一个派生类有多个直接基类,而这些直接基类又 有一个共同的基类,则在最终的派生类中会保留该间接共 同基类数据成员的多份同名成员。- 二义性,解决办法: 定义虚基类,使得在继承间接共同基类时只保留一份 成员。,为保证虚基类在派生类中只继承一次,应当在该基类 的所有直接派生类中声明为虚基类。否则仍然会出现对 基类的多次继承。,声明虚基类格式:

4、 class 派生类名:virtual 继承方式 基类名,虚基类的初始化: 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) ;,注意: 前介绍,在派生类的构造函数中只需对其直接基类初始 化,再由其直接基类负责对间接基类初始化。,2. 现在,由于虚基类在派生类中只有一份数据成员,

5、所以 这份数据成员的初始化必须由派生类直接给出。,派生类不仅要负责对其直接基类初始化还要负责对虚 基类初始化。,在派生类对象的创建中: 首先是虚基类的构造函数并按它们声明的顺序构造。 第二批是非虚基类的构造函数按它们声明的顺序调用。 第三批是成员对象的构造函数。 最后是派生类自己的构造函数被调用。,构造函数执行次序:,析构的次序与构造的次序相反。,例:通过构造函数的初始化表对虚基类进行初始化。 #include class A /定义基类A public: int x; A(int a=0) x=a; /基类构造函数,有一个参数 A( ) cout“A析构“endl; ; class B:vi

6、rtual public A /A作为B的虚基类 public : int y; B(int a=0,int b=0):A(b) y=a; /B类构造函数,在初始化表中对虚基类初始化 B() cout“B析构“endl; void PB( ) cout“x=“xt“y=“yendl; ;,class C:virtual public A /A作为C的虚基类 public: int z; C(int a=0,int b=0):A(b) z=a; /C类构造函数,在初始化表中对虚基类初始化 C() cout“C析构“endl; void PC( ) cout“x=“xt“z=“zendl; ; c

7、lass D:public B,public C public: int m; D(int a,int b,int d,int e,int f):B(a,b),C(d,e) m=f; D() cout“D析构“endl; void Print( ) PB( ); PC( ); cout“m=“mendl; ;,A( ),如果在虚基类中定义了带参数的构造函数,则在其所有 派生类中(包括直接派生或间接派生的派生类)中,通 过构造函数的初始化表对虚基类进行初始化。,void main( ) D d1(100,200,300,400,500); d1.Print(); d1.x=400; d1.Pri

8、nt(); ,输出: x=0 y=100 /基类构造函数使用了默认值做参数 x=0 z=300 /基类构造函数使用了默认值做参数 m=500 x=400 y=100 /主函数改变了基类参数值 x=400 z=300 /主函数改变了基类参数值 m=500 D析构 C析构 B析构 A析构,D(int a,int b,int d,int e,int f):A( ),B(a,b),C(d,e) m=f; ,8.7 多态性与虚函数,多态性是面向对象程序设计的关键技术之一。指的是调用同样的函数实现不同的功能。,在C+中有两种多态,编译时的 多态性 (静态多态性),运行时的多态性,在程序执行前无法确定调用哪

9、一个函数,必须在程序执行过程中,根据执行的具体情况来动态地确定。通过类继承关系和虚函数来实现的。目的:建立一种通用的程序。,通过函数的重载和运算符的重载实现。,多态性是“一个接口,多种方法”: 通过继承产生了相关的不同的派生类,和基类成员同 名的成员在不同的派生类中有不同的含义。,虚函数的作用: 允许在派生类中重新定义与基类同名的函数,并且 可以通过基类指针或引用来访问基类和派生类中的同名 函数。,虚函数是一个类的成员函数,定义虚函数的格式如下: virtual 返回类型 函数名(参数表);,注:virtual仅用于类定义中,若虚函数在类外定义,不能 加virtual(类中函数说明要加virt

10、ual )。,当某一个类的一个类成员函数被定义为虚函数,则由 该类派生出来的所有派生类中,该函数始终保持虚函数的 特征。,8.7.1 虚函数的定义,在派生类中重新定义虚函数(称超载或覆盖)时,不必再加关键字virtual,但函数头一定要完全相同。,虚函数的使用: (1) 在基类用virtual声明成员函数为虚函数。,(2) 在派生类中重新定义此函数,要求函数名、函数类型、 函数参数个数和类型全部与基类的虚函数相同,并根据 派生类的需要重新定义函数体。,(3) 定义一个指向基类对象的指针变量,并使它指向同一 类族中需要调用该函数的对象。,(4) 通过该指针变量调用此虚函数,此时调用的就是指针 变

11、量指向的对象的同名函数。,例: #include class A public: virtual void show(); ; void A:show() cout“A:show()“endl; class B:public A public: void show() cout“B:show()“endl; ; class C:public B public: void show( ) cout“C:show()“endl; ;,void main() A a1, *p; B b1; C c1; p= /动态调用 ,运行结果: A:show() B:show() C:show(),void m

12、ain() C c; c.A:show(); /静态调用 c.B:show(); /静态调用 c.show(); /静态调用 ,【例8.6】计算学分。 由本科生类派生出研究生类GradeStudent,但它们各自的从课程学时数折算为学分数的算法是不同的,本科生是16个学时一学分,而研究生是20个学时一学分。 class Student char coursename20; /课程名 int classhour; /学时 int credit; /学分,未考虑0.5学分 public: Student();/构造函数,将类成员赋值为0 void Calculate();/ 计算学分(考虑该函数的

13、设置) void SetCourse(char *str,int hour);/设置课程和学时 int GetHour();/获取学时数 void SetCredit(int cred); /获取学分 void Print();/显示学时和学分 ;,class Student char coursename20; /课程名 int classhour; /学时 int credit; /学分 public: Student() coursename0=0; classhour=0; credit=0; virtual void Calculate() credit=classhour/16;

14、void SetCourse(char *str,int hour) /设置课程和学时 strcpy(coursename,str); classhour=hour; ,int GetHour() return classhour; /获取学时数 void SetCredit(int cred) credit=cred; /获取学分 voidPrint() /显示学时和学分 coutcoursenametclasshour“学时“ tcredit“学分“endl; ;,class GradeStudent:public Student public: GradeStudent() ; /基类缺

15、省构造函数调用可省 void Calculate() SetCredit(GetHour()/20); ;,void main() Student s,*ps; GradeStudent g; s.SetCourse(“物理“,80); s.Calculate(); g.SetCourse(“物理“,80); g.Calculate(); cout“本科生:“t; s.Print(); cout“研究生:“t; g.Print(); s.SetCourse(“数学“,160);,g.SetCourse(“数学“,160); ps= ,输出结果为: 本科生:物理 80 学时 5学分 研究生:物理

16、 80 学时 4学分 本科生:数学 160学时 10学分 研究生:数学 160学时 8学分,使用基类引用去指向不同对象,同样可实现运行时多态性。,void Calfun(Student ,【例8.7】计算学分,基类与派生类定义同【例8.6】(基类派生类函数不变),增加函数:,几点提示:,5.虚函数重构不同于重载。派生类中定义虚函数必须与基类中的虚函数同名外,还必须同参数表,同返回类型。否则被认为是重载,而不是虚函数。,1.实现动态多态性需要三个条件:派生类体系、虚函数、指针或引用。,2.析构函数可定义为虚函数,构造函数不能定义虚函数。在基类及其派生类都有动态分配的内存空间时,应当考虑把析构函数

17、定义为虚函数,以便能够用基类指针实现撤消对象的多态性。,3. 虚函数执行速度稍慢。为了实现多态性,每一个派生类中均要保存相应虚函数的入口地址表,函数的调用机制也与一般函数不同。这是多态性为实现通用性付出的代价。,4. 静态成员函数不能作为虚函数,因为它不属于某个对象,而是为所有同类对象共有。内联函数也不能作为虚函数。,声明虚函数的成员函数主要考虑以下几点:,首先看成员函数所在的类是否会作为基类。然后看成 员函数在类的继承后有无可能被更改功能,如果希望更改 其功能的,一般应该将它声明为虚函数。,(2) 如果成员函数在类被继承后功能不需修改,或派生类 用不到该函数,则不要把它声明为虚函数。,(3)

18、 应考虑对成员函数的调用是通过对象名还是通过基类 指针或引用去访问,如果是通过基类指针或引用去访问 的,则应当声明为虚函数。,虚析构函数,析构函数的作用: 在对象撤销之前做必要的“清理现场”的工作。当派生 类的对象从内存中撤销时一般先调用派生类的析构函数, 然后再调用基类的析构函数。,如果用new运算符建立了临时对象,若基类中有析构 函数,并且定义了一个指向该基类的指针变量。在程序用 带指针参数的delete运算符撤销对象时,会出现: 系统会只 执行基类的析构函数,而不执行派生类的析构函数。,解决办法: 把析构函数定义为虚函数,实现撤消对象时的多态性。,虚析构函数声明: virtual 类名(

19、 );,#include class A public: A() cout“A析构n“; ; class B:public A public: B(); B(); private: int *p; ;,B:B() p=new int(); B:B() cout“B析构n“; delete p; void fun(A *a) delete a; void main() A *a=new B; /a是指向基类的指针变量指向new开辟的动态存储空间 fun(a); ,输出:A析构,virtual,输出:B析构 A析构,把基类的析构函数声明为虚函数。可使所有派生类的 析构函数自动成为虚函数(即使函数名

20、不同)。若程序中 显式地用了delete运算符准备删除一个对象,而delete运算 符的操作对象用了指向派生类对象的基类指针,系统会调 用相应类的析构函数。,1. 虚函数是动态关联的基础。,2. 虚函数是非静态的成员函数。,3. virtual 只用来说明类声明中的原型,不能用在函数实 现时。,4. 具有继承性,基类中声明了虚函数,派生类中无论是 否说明,同原型函数都自动为虚函数。,5. 不是重载声明而是覆盖。,6. 通过基类指针或引用,执行时会根据指针指向的对象 的类,决定调用哪个函数。,【例8.5_1】把【例8.1】析构函数改造为虚函数 。,一般为了将类设计成通用的,必须把析构函数定义为虚

21、函数。在动态分配内存时所有C+的标准库函数都采用这种格式。,class Person /数据成员略 public: virtual Person(); /其他成员函数略 ;,在主函数中添加以下内容: Person *per4; Student *stu4=new Student; *stu4=stu1; /把stu1的数据拷入*stu4 stu4-PrintStudentInfo(); per4=stu4; delete per4; /基类指针撤销派生类对象,必须显式撤销,例:,class Point public: . double Area()return 0; ;,class Circl

22、e :public Point public: double Area() return PI*radius*radius; ;,基类函数为空或可以不要,在实现部分仍然要写出函数体,纯虚函数可以解决接口,8.7.2 纯虚函数,1. 纯虚函数不同于空函数,纯虚函数不能调用。 2.派生类中必须重新定义纯虚函数的函数体。,纯虚函数(pure virtual function)当基类中某虚函数无法具体实现,可定义为纯虚函数,具体实现依赖于派生类。定义格式为:,定义纯虚函数要注意:,virtual 返回类型 函数名(参数表)=0;,含有纯虚函数的基类不能用来定义对象,称为抽象类。抽象类的意义在于定义框架

23、。,注: 纯虚函数没有函数体; 最后面的“=0”并不表示函数返回值为0,它只起形式 上的作用,告诉编译系统“这是纯虚函数”; 这是一个声明语句,最后应有分号。,含有纯虚函数的基类是不能用来定义对象的。纯虚 函数没有实现部分,不能产生对象,含有纯虚函数的类 是抽象类。,抽象类,带有纯虚函数的类称为抽象类: class 类名 virtual 类型 函数名(参数表)=0; /纯虚函数 . ,作用: 抽象类为抽象和设计的目的而建立,将有关的数据和 行为组织在一个继承层次结构中,保证派生类具有要 求的行为。,对于暂时无法实现的函数,可以声明为纯虚函数,留 给派生类去实现。,注意,抽象类只能作为基类来使用

24、。 不能声明抽象类的对象。 构造函数不能是虚函数,析构函数可以是虚函数。,#include class B0 /抽象基类B0声明 public: /外部接口 virtual void display( )=0; /纯虚函数成员 ; class B1: public B0 /公有派生 public: void display( ) /虚成员函数 cout“B1:display( )“endl; ;,class D1: public B1 /公有派生 public: void display( ) /虚成员函数 coutdisplay( ); ,void main( ) B0 *p; /声明抽象基

25、类指针 B1 b1; /声明派生类对象 D1 d1; /声明派生类对象 p= /调用派生类D1函数成员 ,程序的运行结果为: B1:display( ) D1:display( ),结论: 一个基类如果包含一个或一个以上纯虚函数,就是抽 象基类。,(2) 派生类如果没有实现基类中的全部纯虚函数,则该派 生类仍然是抽象类;,(3) 在类的层次结构中,顶层或最上面的几层可以是抽象 基类。抽象基类体现了本类族中各类的共性,把各类 中共有的成员函数集中在抽象基类中声明。,(4) 抽象基类是本类族的公共接口。,(5) 区别静态关联和动态关联。 通过对象调用虚函数静态关联 通过基类指针调用虚函数动态关联,

26、(6) 如果在基类声明了虚函数,则在派生类中凡是与该函 数有相同的函数名、函数类型、参数个数和类型的函 数,均为虚函数(不论在派生类中是否用virtual声明)。,把类的声明与类的使用分离。对于设计类库的软件开 发商来说尤为重要。开发商设计了各种各样的类,但不向 用户提供源代码,用户可以不知道类是怎样声明的,但是 可以使用这些类来派生出自己的类。,(7) 使用虚函数提高了程序的可扩充性。,class Person int MarkAchieve; /业绩分 char Name20; public: Person(char *name) strcpy(Name,name); MarkAchiev

27、e=0; void SetMark(int mark) MarkAchieve=mark; virtual void CalMark()=0; void Print() coutName“的业绩分为:“MarkAchieveendl; ;,【例8.8】学校对在册人员进行奖励,依据是业绩分。 业绩分的计算方法只能对具体人员进行,各类人员算法不同,所以将在册人员类定义为抽象类,业绩计算方法为纯虚函数。,class Student:public Person int credit,grade; /学历和成绩 public: Student(char *name,int cred,int grad):

28、Person(name) credit=cred; grade=grad; void CalMark() SetMark(credit*grade); ;,class Teacher:public Person int classhour,studnum; /授课学时和学生人数 public: Teacher(char *name,int ch,int sn):Person(name) classhour=ch; studnum=sn; void CalMark() int K=(studnum+15)/30; /工作量系数,30人一班,15人以下不开课 switch(K) case 1: S

29、etMark(classhour*studnum);break; case 2: SetMark(classhour*(30+(studnum-30)*8/10);break; case 3: SetMark(classhour*(30+24+(studnum-60)*6/10);break; case 4: SetMark(classhour*(30+24+18+(studnum-90)*4/10); break; case 5: SetMark(classhour*(30+24+12+(studnum-120)*2/10); break; default:SetMark(classhour

30、*(30+24+12+6+(studnum-150)*1/10); ;,int main() Person *pp; Student s1(“张成“,20,80); Teacher t1(“范英明“,64,125),t2(“李凯“,80,85); pp= ,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; v

31、oid Integrate() double dx; int i; dx=(b-a)/2000; 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),void Print() cout“积分值=“Intevalueendl; ; 在派生类中加被积函数: class A:public Simpson public: A(double ra,double rb):Simpson(ra,rb) ; double fun(double x) return sin(x) ; ; class B:public Simpson /B也可以说明为由A派生,更利于说明动态多态性 public: B(double ra,double rb):Simpson(ra,rb) ; double fun(double x) return exp(x) ; ;,int main() A a1(0.0,3.1415926535/2.0); Simpson *s= ,

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 其他


经营许可证编号:宁ICP备18001539号-1