好文档就是一把金锄头!
欢迎来到金锄头文库![会员中心]
电子文档交易市场
安卓APP | ios版本
电子文档交易市场
安卓APP | ios版本

C程序设计第8章1.ppt

44页
  • 卖家[上传人]:cn****1
  • 文档编号:585042556
  • 上传时间:2024-09-01
  • 文档格式:PPT
  • 文档大小:206.03KB
  • / 44 举报 版权申诉 马上下载
  • 文本预览
  • 下载提示
  • 常见问题
    • 本周习题:本周习题: P307 习题习题8.6 实践教程:实践教程:P89 实验二十四实验二十四 2本周实验:本周实验: 习题上机调试习题上机调试 预习:预习:第第9章流类库与输入章流类库与输入/输出输出 9.1~9.3((1)理解)理解C++的基本流类体系;的基本流类体系;((2)提高标准输入)提高标准输入/输出的健壮性;输出的健壮性;((3)掌握提取与插入运算符的重载)掌握提取与插入运算符的重载((4)掌握文件的输入)掌握文件的输入/输出:文件的打开与关闭,文本输出:文件的打开与关闭,文本 文件与二进制文件文件与二进制文件5)掌握在构造函数中通过文件建立对象,在析构函数)掌握在构造函数中通过文件建立对象,在析构函数 中由文件保存对象的技术中由文件保存对象的技术 类似用实型变量描述整数,这条规则称类似用实型变量描述整数,这条规则称赋值兼容规则赋值兼容规则赋值兼容容包括以下三种情况:包括以下三种情况: 派生类与基类(赋值兼容)派生类与基类(赋值兼容)1.派生类对象赋值给基类的对象。

      反之不行如:派生类对象赋值给基类的对象反之不行如: Person p; Student s; p=s;2.派生类对象的地址赋给基类指针反之不行如:派生类对象的地址赋给基类指针反之不行如: Person *pointP; pointP=&s; 3.派生类对象可以初始化基类的引用反之不行如:派生类对象可以初始化基类的引用反之不行如: Person &refP=s;常见用法是函数形参为基类引用,实参是派生类对象常见用法是函数形参为基类引用,实参是派生类对象注意:注意:以上赋值后都只能间接使用到以上赋值后都只能间接使用到继承来继承来的成员 #include class A { public: void show( ){ cout<<"A::show()"<show();p=&b;//A类指针指向类指针指向B类对象类对象p->show();p=&c;//A类指针指向类指针指向C类对象类对象p->show();}输出输出::A::display( )A::display( )A::display( )解决办法:虚函数解决办法:虚函数 如果一个派生类有多个直接基类,而这些直接基类又如果一个派生类有多个直接基类,而这些直接基类又有一个共同的基类,则在最终的派生类中会保留该间接共有一个共同的基类,则在最终的派生类中会保留该间接共同基类数据成员的多份同名成员。

      同基类数据成员的多份同名成员 二义性二义性解决办法:解决办法: 定义虚基类,使得在继承间接共同基类时只保留一份定义虚基类,使得在继承间接共同基类时只保留一份成员 为保证虚基类在派生类中只继承一次,应当在该基类为保证虚基类在派生类中只继承一次,应当在该基类的所有直接派生类中声明为虚基类否则仍然会出现对的所有直接派生类中声明为虚基类否则仍然会出现对基类的多次继承基类的多次继承声明虚基类格式:声明虚基类格式: 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){ }}; 注意:注意:1. 1.前介绍,在派生类的构造函数中只需对其直接基类初始前介绍,在派生类的构造函数中只需对其直接基类初始化,再由其直接基类负责对间接基类初始化。

      化,再由其直接基类负责对间接基类初始化2. 2. 现在,由于虚基类在派生类中只有一份数据成员,所以现在,由于虚基类在派生类中只有一份数据成员,所以这份数据成员的初始化必须由派生类直接给出这份数据成员的初始化必须由派生类直接给出 派生类不仅要负责对其直接基类初始化还要负责对虚派生类不仅要负责对其直接基类初始化还要负责对虚基类初始化基类初始化在派生类对象的在派生类对象的创建创建中:中:首先是虚基类的构造函数并按它们声明的顺序构造首先是虚基类的构造函数并按它们声明的顺序构造第二批是非虚基类的构造函数按它们声明的顺序调用第二批是非虚基类的构造函数按它们声明的顺序调用第三批是成员对象的构造函数第三批是成员对象的构造函数最后是派生类自己的构造函数被调用最后是派生类自己的构造函数被调用构造函数执行次序:构造函数执行次序:析构的次序与构造的次序相反析构的次序与构造的次序相反 例:例:通过构造函数的初始化表对虚基类进行初始化通过构造函数的初始化表对虚基类进行初始化include class A{ // //定义基类定义基类A Apublic: int x; A(int a=0){ x=a; } // //基类构造函数,有一个参数基类构造函数,有一个参数~A( ){ cout<<"A析构析构"<show(); // //动态调用动态调用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(); // //静态调用静态调用} 【【例例8.6】】计算学分。

      计算学分 由本科生类派生出研究生类由本科生类派生出研究生类GradeStudent,但它们各自,但它们各自的从课程学时数折算为学分数的算法是不同的,本科生是的从课程学时数折算为学分数的算法是不同的,本科生是1616个学时个学时一学分,而研究生是一学分,而研究生是2020个学时一学分个学时一学分class Student{char coursename[20];// //课程名课程名int classhour;// //学时学时int credit; // //学分学分, ,未考虑未考虑0.50.5学分学分public:Student();;// //构造函数,将类成员赋值为构造函数,将类成员赋值为0 0void Calculate();;// // 计算学分(考虑该函数的设置)计算学分(考虑该函数的设置)void SetCourse(char *str,int hour);;// //设置课程和学时设置课程和学时int GetHour();;// //获取学时数获取学时数void SetCredit(int cred);; // //获取学分获取学分void Print();;// //显示学时和学分显示学时和学分}; class Student{ char coursename[20]; // //课程名课程名 int classhour; // //学时学时 int credit; // //学分学分public: Student(){ coursename[0]='\0'; classhour=0; credit=0; } virtual void Calculate(){ credit=classhour/16; } void SetCourse(char *str,int hour){ // //设置课程和学时设置课程和学时 strcpy(coursename,str); classhour=hour; } int GetHour(){ return classhour; } // //获取学时数获取学时数 void SetCredit(int cred){ credit=cred; } // //获取学分获取学分 voidPrint(){ // //显示学时和学分显示学时和学分 cout<Calculate(); cout<<"本科生本科生::"<<'\t'; ps->Print(); ps=&g; ps->Calculate(); cout<<"研究生研究生::"<<'\t'; ps->Print(); }输出结果为输出结果为:本科生:物理本科生:物理 80 学时学时 5学分学分研究生:物理研究生:物理 80 学时学时 4学分学分本科生:数学本科生:数学 160学时学时 10学分学分研究生:数学研究生:数学 160学时学时 8学分学分 使用基类引用去指向不同对象,同样可实现运行时多态性。

      使用基类引用去指向不同对象,同样可实现运行时多态性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,“物理物理”,80);}【【例例8.7】】计算学分,计算学分,基类与派生类定义同基类与派生类定义同【【例例8.6】】(基类派生类(基类派生类函数不变),增加函数:函数不变),增加函数: 几点提示:几点提示:5.虚函数重构不同于重载虚函数重构不同于重载派生类中定义虚函数必须与基类中派生类中定义虚函数必须与基类中的虚函数同名外,还必须同参数表,同返回类型否则被认为是重的虚函数同名外,还必须同参数表,同返回类型否则被认为是重载,而不是虚函数载,而不是虚函数 1.实现动态多态性需要三个条件:派生类体系、虚函数、指针实现动态多态性需要三个条件:派生类体系、虚函数、指针或引用。

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

      其功能的,一般应该将它声明为虚函数2) (2) 如果成员函数在类被继承后功能不需修改,或派生类如果成员函数在类被继承后功能不需修改,或派生类用不到该函数,则不要把它声明为虚函数用不到该函数,则不要把它声明为虚函数3) (3) 应考虑对成员函数的调用是通过对象名还是通过基类应考虑对成员函数的调用是通过对象名还是通过基类指针或引用去访问,如果是通过基类指针或引用去访问指针或引用去访问,如果是通过基类指针或引用去访问的,则应当声明为虚函数的,则应当声明为虚函数 虚析构函数虚析构函数析构函数的作用:析构函数的作用: 在对象撤销之前做必要的在对象撤销之前做必要的“清理现场清理现场”的工作当派生的工作当派生类的对象从内存中撤销时一般先调用派生类的析构函数,类的对象从内存中撤销时一般先调用派生类的析构函数,然后再调用基类的析构函数然后再调用基类的析构函数 如果用如果用new运算符建立了临时对象,若基类中有析构运算符建立了临时对象,若基类中有析构函数,并且定义了一个指向该基类的指针变量在程序用函数,并且定义了一个指向该基类的指针变量在程序用带指针参数的带指针参数的delete运算符撤销对象时,会出现运算符撤销对象时,会出现: 系统会只系统会只执行基类的析构函数,而不执行派生类的析构函数。

      执行基类的析构函数,而不执行派生类的析构函数解决办法:解决办法: 把析构函数定义为虚函数,实现撤消对象时的多态性把析构函数定义为虚函数,实现撤消对象时的多态性 虚虚析构函数声明:析构函数声明: virtual ~~类名类名( );#includeclass 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析构析构 把基类的析构函数声明为虚函数。

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

      决定调用哪个函数 【【例例8.5_1】】把把【【例例8.1】】析构函数改造为虚函数析构函数改造为虚函数 一般为了将类设计成通用的,必须把析构函数定义为虚函数一般为了将类设计成通用的,必须把析构函数定义为虚函数在动态分配内存时所有在动态分配内存时所有C++的标准库函数都采用这种格式的标准库函数都采用这种格式 class Person{ // //数据成员略数据成员略public: virtual ~Person(); // //其他成员函数略其他成员函数略 }; 在主函数中添加以下内容:在主函数中添加以下内容: Person *per4; Student *stu4=new Student; *stu4=stu1; //把把stu1的数据拷入的数据拷入*stu4 stu4->PrintStudentInfo(); per4=stu4; delete per4; // //基类指针撤销派生类对象,必须显式撤销基类指针撤销派生类对象,必须显式撤销 例:例: PointArea()Show()double x,y; CircleArea()Show()doubl radius;class Point{public: ……. double Area(){return 0;} …..};class Circle :public Point{public: …… double Area(){ return PI*radius*radius; } …… };基类函数为空或可以不要,在实现部分仍然要写出函数体,纯虚函数可以解决--接口基类函数为空或可以不要,在实现部分仍然要写出函数体,纯虚函数可以解决--接口8.7.2 8.7.2 纯虚函数纯虚函数 1. 纯虚函数不同于空函数,纯虚函数不能调用。

      纯虚函数不同于空函数,纯虚函数不能调用2.派生类中必须重新定义纯虚函数的函数体派生类中必须重新定义纯虚函数的函数体纯虚函数纯虚函数((pure virtual function)当基类中某虚函数无法具)当基类中某虚函数无法具体实现,可定义为纯虚函数,具体实现依赖于派生类体实现,可定义为纯虚函数,具体实现依赖于派生类定义格式为:定义格式为:定义纯虚函数要注意:定义纯虚函数要注意: virtual 返回类型返回类型 函数名(参数表)函数名(参数表)=0;;含有纯虚函数的基类不能用来定义对象,称为含有纯虚函数的基类不能用来定义对象,称为抽象类抽象类抽象类的意义在于定义框架的意义在于定义框架 注:注:①①纯虚函数没有函数体;纯虚函数没有函数体;②②最后面的最后面的“=0=0”并不表示函数返回值为并不表示函数返回值为0 0,它只起形式,它只起形式 上的作用,告诉编译系统上的作用,告诉编译系统“这是纯虚函数这是纯虚函数”; ; ③③这是一个声明语句,最后应有分号这是一个声明语句,最后应有分号 含有纯虚函数的基类是不能用来定义对象的纯虚含有纯虚函数的基类是不能用来定义对象的。

      纯虚函数没有实现部分,不能产生对象,含有纯虚函数的类函数没有实现部分,不能产生对象,含有纯虚函数的类是抽象类是抽象类 抽象类抽象类带有纯虚函数的类称为抽象类带有纯虚函数的类称为抽象类: class 类名类名 { virtual 类型类型 函数名函数名(参数表参数表)=0; //纯虚函纯虚函数数 ... } 作用:作用:1. 1.抽象类为抽象和设计的目的而建立,将有关的数据和抽象类为抽象和设计的目的而建立,将有关的数据和 行为组织在一个继承层次结构中,保证派生类具有要行为组织在一个继承层次结构中,保证派生类具有要 求的行为求的行为2.2.对于暂时无法实现的函数,可以声明为纯虚函数,留对于暂时无法实现的函数,可以声明为纯虚函数,留 给派生类去实现给派生类去实现注意注意l抽象类只能作为抽象类只能作为基类基类来使用l不能声明抽象类的对象不能声明抽象类的对象l构造函数不能是虚函数,析构函数可以是虚函数构造函数不能是虚函数,析构函数可以是虚函数 #include class B0 { // //抽象基类抽象基类B0B0声明声明public: // //外部接口外部接口virtual void display( )=0; // //纯虚函数成员纯虚函数成员};class B1: public B0 { // //公有派生公有派生public:void display( ){ // //虚成员函数虚成员函数 cout<<"B1::display( )"<display( ); } void main( ) {B0 *p;// //声明抽象基类指针声明抽象基类指针B1 b1;// //声明派生类对象声明派生类对象D1 d1;// //声明派生类对象声明派生类对象p=&b1;fun(p);// //调用派生类调用派生类B1B1函数成员函数成员p=&d1;fun(p);// //调用派生类调用派生类D1D1函数成员函数成员}程序的运行结果为:程序的运行结果为:B1::display( )D1::display( ) 结论结论: :(1)(1)一个基类如果包含一个或一个以上纯虚函数,就是抽一个基类如果包含一个或一个以上纯虚函数,就是抽(2)(2) 象基类。

      象基类2) (2) 派生类如果没有实现基类中的全部派生类如果没有实现基类中的全部纯虚函数,则该派纯虚函数,则该派 生类仍然是抽象类;生类仍然是抽象类;(3) (3) 在类的层次结构中,顶层或最上面的几层可以是抽象在类的层次结构中,顶层或最上面的几层可以是抽象 基类抽象基类体现了本类族中各类的共性,把各类基类抽象基类体现了本类族中各类的共性,把各类 中共有的成员函数集中在抽象基类中声明中共有的成员函数集中在抽象基类中声明4) (4) 抽象基类是本类族的公共接口抽象基类是本类族的公共接口5) (5) 区别静态关联和动态关联区别静态关联和动态关联 通过对象调用虚函数--静态关联通过对象调用虚函数--静态关联 通过基类指针调用虚函数--动态关联通过基类指针调用虚函数--动态关联 (6) (6) 如果在基类声明了虚函数,则在派生类中凡是与该函如果在基类声明了虚函数,则在派生类中凡是与该函 数有相同的函数名、函数类型、参数个数和类型的函数有相同的函数名、函数类型、参数个数和类型的函 数,均为虚函数数,均为虚函数( (不论在派生类中是否用不论在派生类中是否用virtualvirtual声明声明) )。

      把类的声明与类的使用分离对于设计类库的软件开把类的声明与类的使用分离对于设计类库的软件开发商来说尤为重要开发商设计了各种各样的类,但不向发商来说尤为重要开发商设计了各种各样的类,但不向用户提供源代码,用户可以不知道类是怎样声明的,但是用户提供源代码,用户可以不知道类是怎样声明的,但是可以使用这些类来派生出自己的类可以使用这些类来派生出自己的类7) (7) 使用虚函数提高了程序的可扩充性使用虚函数提高了程序的可扩充性 class Person{ int MarkAchieve; // //业绩分业绩分 char Name[20];public: Person(char *name){ strcpy(Name,name); MarkAchieve=0; } void SetMark(int mark){ MarkAchieve=mark; } virtual void CalMark()=0;void Print(){ cout<

      学校对在册人员进行奖励,依据是业绩分 业绩分的计算方法只能对具体人员进行,各类人员算法不同,业绩分的计算方法只能对具体人员进行,各类人员算法不同,所以将在册人员类定义为抽象类,业绩计算方法为纯虚函数所以将在册人员类定义为抽象类,业绩计算方法为纯虚函数 class Student:public Person{ int credit,grade; // //学历和成绩学历和成绩public: Student(char *name,int cred,int grad):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,30人一班人一班,15,15人以下不开课人以下不开课 switch(K){ case 1: SetMark(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+24+12+6+(studnum-150)*1/10)); } } }; int main(){Person *pp;Student s1("张成张成",20,80);Teacher t1("范英明范英明",64,125),t2("李凯李凯",80,85);pp=&s1;pp->CalMark();pp->Print();pp=&t1;pp->CalMark();pp->Print();pp=&t2;pp->CalMark();pp->Print();return 0;} 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)/2000; Intevalue=fun(a)+fun(b); for(i=1;i<2000;i+=2) Intevalue+=4*fun(a+dx*i); for(i=2;i<2000;i+=2) Intevalue+=2*fun(a+dx*i); Intevalue*=dx/3; }【【例例8.9】】用虚函数来实现辛普生法求函数的定积分。

      用虚函数来实现辛普生法求函数的定积分P210 例例6.11)) void Print(){ cout<<"积分值积分值="<Integrate(); // //动态动态 B b1(0.0,1.0); b1.Integrate(); // //静态静态 s->Print(); b1.Print(); return 0;} 。

      点击阅读更多内容
      相关文档
      2025国开山东开大《土质学与土力学》形成性考核123答案+终结性考核答案.docx 中学综合素质知识点梳理【中学教师资格证】.docx 2025国开山东开大《特许经营概论》形成性考核123答案+终结性考核答案.doc 2025年高考英语全国一卷真题(含答案).docx 2025国开山东《农民专业合作社创建与管理》形成性考核123答案+终结性考核答案.docx 2025国开山东开大《自然现象探秘》形成性考核123答案+终结性考核答案.docx 2025国开山东《消费心理学》形成性考核123答案+终结性考核答案.doc 2025国开山东《小微企业管理》形成性考核123答案+终结性考核答案.doc 2025国开山东开大《资本经营》形成性考核123答案+终结性考试答案.docx 2025国开山东《小学生心理健康教育》形考123答案+终结性考试答案.docx 2025国开《视频策划与制作》形考任务1-4答案.docx 2025国开《亲子关系与亲子沟通》形考任务234答案+期末大作业答案.docx 2025国开电大《煤矿地质》形成性考核123答案.docx 2025国开电大《冶金原理》形考任务1234答案.docx 2025国开《在线学习项目运营与管理》形考任务1234答案.doc 2025国开电大《在线教育的理论与实践》阶段测验1-4答案.docx 2024 年注册环保工程师《专业基础考试》真题及答案解析【完整版】.docx 环保工程师---2023 年注册环保工程师《专业基础考试》真题及答案解析【完整版】.docx 2025国开《液压与气压传动》形考任务一参考答案.docx 2025年春江苏开放大学教育研究方法060616计分:形成性作业2、3答案.docx
      关于金锄头网 - 版权申诉 - 免责声明 - 诚邀英才 - 联系我们
      手机版 | 川公网安备 51140202000112号 | 经营许可证(蜀ICP备13022795号)
      ©2008-2016 by Sichuan Goldhoe Inc. All Rights Reserved.