• 1. 《C++面向对象程序设计》教学内容 第1章 C++概述 第2章 类和对象 第3章 面向对象程序设计概述 第4章 进一步学习类和对象 第5章 堆与复制构造函数 第6章 继承性:派生类 第7章 运算符重载 第8章 虚函数和多态性 第9章 模板 第10章 类库和C++的标准模板库STL 第11章 输入输出流 第12章 异常处理
  • 2. 第6章 继承性:派生类6.1 派生类的概念 6.2 单继承 6.3 派生类的构造函数和析构函数 6.4 多重继承 6.5 赋值兼容规则 6.6 应用举例
  • 3. 一个现有的类Class Student{ int number; char *name; float score; public: Student(int number1, char* name1, float score1); void modify(float score1); void print( ); };
  • 4. 在现有类的基础上创建新类:派生class Ustudent : public Student { char major; //专业 public: Ustudent(int number1, char *name1, char *major, float score1); void print( ); }; // Ustudent 为大学生类派生类基类
  • 5. 6.1 派生类的概念保持已有类的特性而构造新类的过程称为继承(inheritance)。(OOP术语) 在已有类的基础上新增自己的特性而产生新类的过程称为派生(derive)。(C++术语) 被继承的已有类称为基类(或父类)。 派生出的新类称为派生类(或子类)。
  • 6. 类继承的一个经典例子class shape{ public: float area( ){ return -1;} }; class circle:public shape{ float radius; public: circle(float r){ radius = r; } float area( ){ return 3.14158*radius*radius; } };shapecircle
  • 7. 类继承的一个经典例子(续)class rectangle:public shape{ float length,width; public: rectangle (float l, float w) {length=l, width=w; } float area( ) { return length *width; } };shaperectangle
  • 8. 从同一个基类可以派生出多个新类shapecirclerectangle
  • 9. 类的继承层次与分类结构
  • 10. 继承与派生的目的继承的目的:实现代码重用。 派生的目的:当新的问题出现,原有程序无法解决(或不能完全解决)时,需要对原有程序进行扩充和改造。 C++中类的继承层次自然地表达了人们分析问题时所用的分类结构。大大改善了软件系统的可理解性和可维护性。
  • 11. 派生类声明的一般形式class 派生类名:访问控制符 基类名1, …. { 成员定义; }; 多继承:一个派生类有多个基类; 单继承:一个派生类只有一个基类。
  • 12. 基类的访问控制基类的访问说明符必须下列是三者之一: public(公有), private(私有), protected(保护)。 基类的访问控制符决定了派生类成员以及对象对继承来的基类成员的访问权限。 不同的访问控制影响主要体现在: 1、派生类成员函数对基类成员的访问。 2、其他函数对基类成员的访问。
  • 13. 公有继承 (inherit as public) 公有派生 (derived as public)当被继承的基类的访问说明符是public的时候,所有基类的公共成员都成为派生类的公共成员。 其他函数只能访问基类的public成员。 派生类中的成员函数可以直接访问基类中的public,但不能访问基类的private成员。
  • 14. 一个有争议的问题无论基类被怎样继承,基类中的私有成员都不能被其派生类成员直接访问。只能通过基类的公共接口访问。 继承来的特性不能直接使用似乎于理不通,但是派生类不加限制的访问基类的私有成员,将破坏基类的封装性,这是面向对象方法中有争议的问题之一。(两难问题)
  • 15. protected 关键字除了基类的被保护成员对于基类的任何派生类成员都是可访问的以外,protected 访问说明符与private说明符是完全等效的。 派生类的成员函数不能直接访问基类的私有(private)成员。但派生类的成员函数能访问基类的被保护(protected)成员。
  • 16. 保护继承(inherit as protected)基类的public和protected成员都以protected身份出现在派生类中,但基类的private成员不可访问。 派生类中的成员函数可以直接访问基类中的public和protected成员,但不能访问基类的private成员。 派生类的对象不能访问基类中的任何成员。
  • 17. 私有继承(inherit as private ) 私有派生(derived as private)基类的所有public和protected成员都成为派生类的私有成员。 派生类中的成员函数可以直接访问基类中的public和protected成员,但不能直接访问基类的private成员。 其他函数不能通过派生类的对象访问基类中的任何成员。
  • 18. 6.2 单继承派生类只有一个基类的时称为单继承。pointline
  • 19. 单继承的例子#include using namespace std; class point{ char c; public: point(char ch){c=ch;} void DrawPoint(){cout<
  • 20. 单继承的例子(续1)class line:public point{ int length; public: line(char ch,int i):point(ch){ length=i; } void DrawLine() { length=(length<0?0:(length>80?80:length)); for(int i=0;i
  • 21. 单继承的例子(续2)int main( ) { line my_line('*',30); my_line.DrawLine(); cout<
  • 22. 单继承时类层次为树形结构
  • 23. 6.3 派生类的构造函数和析构函数首先,基类和派生类的构造函数与析构函数在什么时候被调用? 第二,如何将参数传递给基类的构造函数。
  • 24. 构造函数与析构函数的调用次序先调用基类的构造函数,后调用派生类的构造函数; 先调用派生类的析构函数,后调用基类的析构函数;
  • 25. 将参数传递给基类的构造函数问题:派生类中包含的无名对象要初始化。 解决方案: 用派生类的构造函数激活基类的构造函数。 line(char ch,int i):point(ch){ length=i; }
  • 26. 派生类的构造函数和析构函数的例子class point{ char c; public: point(char ch){c=ch;} void DrawPoint(){cout<80?80:length)); for(int i=0;i
  • 27. 派生类的构造函数和析构函数的例子(续)class rectangle : public line{ int width; public: rectangle(char ch,int i,int j) : line(ch,i){ width=j; } void DrawRect() { width=(width<0?0 : (width>25?25:width)); for(int i=0;i
  • 28. 派生类的构造函数和析构函数的例子(续)int main( ) { rectangle rect('X',40,12); rect.DrawRect(); cout<
  • 29. 多层继承所谓多层继承是指从一个类派生出一个类,然后又从该派生类派生出另一个类,如此进行下去,直到最后生成的类满足要求为止。 例如: 从基类point派生出 line(线),然后从 line派生出rectangle(矩形)。 pointlinerectangle
  • 30. 继承是重用的基础面向对象的程序设计中通过继承实现重用reuse——便于重用,扩充。 可以在原有的代码(程序)基础上扩充,不修改原有代码。 即使原来的代码完全不符合当前的要求,也不必修改原有的代码。可以只增加新的代码来实现新的功能。
  • 31. 函数隐藏(function hide)函数隐藏: 如果我们在派生类中重新定义一个函数,其函数原型与基类中的某个函数完全相同,那么基类中的对应函数在派生类中就被隐藏了,我们称之为函数隐藏。(例10 ) 使用这种方式可以改造软件功能而不修改原来的源代码。
  • 32. 函数隐藏的例子#include #include class person { char name[10]; int age; public: person(){age=0;} void set(int i,char *str) { age=i; strcpy(name,str); } void display( ) { cout<
  • 33. 函数隐藏的例子(续)class student : public person{ int math, chinese, english; public: student( ) { math=chinese=english=0; } void SetScore(int i,int j,int k) { math=i; chinese=j; english=k; } void display() { cout<<"The student's score is: " <
  • 34. 函数隐藏的例子(续)int main(void) { student st; st.set(15,"Wang"); st.SetScore(88,85,90); st.person::display(); st.display(); return 0; }
  • 35. 支配规则派生类的对象引用某函数时,编译器先在派生类中找与该函数完全匹配的函数,有则调用之; 否则到其基类中找,依次再到基类的基类中找,……等等,若都没有找到则出错。 若第一次遇到了同名函数但参数不匹配,则对其进行类型转换后再调用之,若无法转换则出错。
  • 36. 6.4 多继承 Inheriting Multiple Base Classes从两个以上的基类派生出新类,例如: class 派生类名:继承方式1 基类名1, 继承方式2 基类名2,... { 派生类的成员定义; }注意:每一个“继承方式”,只用于限制对紧随其后之基类的继承。
  • 37. 输入输出流类库的结构流类的继承层次iosostreamistreamclogfstreamiostreamcerrcinifstreamofstreamcoutiostream.h
  • 38. 多继承举例class A{ public: void setA(int); void showA(); private: int a; }; class B{ public: void setB(int); void showB();private: int b; }; class C : public A, private B{ public: void setC(int, int, int); void showC(); private: int c; };
  • 39. void A::setA(int x) { a=x; } void B::setB(int x) { b=x; } void C::setC(int x, int y, int z) { setA(x); //派生类成员直接访问 setB(y); //基类的公有成员 c=z; } //其它函数实现略int main() { C obj; obj.setA(5); obj.showA(); obj.setC(6,7,9); obj.showC( ); obj.setB(6); // 错误,私有继承 obj.showB( ); //错误,私有继承 return 0; }
  • 40. 多继承的构造函数同单继承一样,多继承的派生类要在其构造函数中激活所有的基类的构造函数。 调用次序: 先基类,后派生类;基类的调用次序以派生类定义中所声明的基类的出现顺序而定。 例:ABDFEC
  • 41. 内存对象分布图 示意性定义为: class A{ …}; class B{ …}; class C{ …}; class D:public B,public A {…}; class E:public C{ …}; class F:public D,public E{ …}; 构造函数的调用次序为:A、B、D、C、E、FFFDEDABABECC
  • 42. 多继承引入的二义性问题举例class B { public: int b; } class B1 : public B { private: int b1; }class B2 : public B { private: int b2; }; class C : public B1,public B2 { public: int f( ); private: int d; }BB1B2C
  • 43. 下面的访问是二义性的: C c; c.b c.B::b 下面是正确的: c.B1::b c.B2::b二义性问题举例(续)
  • 44. bb1bb2dB类子对象B类子对象B1类子对象B2类子对象C类对象由于派生类的对象中多次包含了某个基类的对象,从而产生了二义性。派生类C的对象的存储结构示意图
  • 45. 二义性问题解决方案:虚基类class B{ private: int b;}; class B1 : virtual public B { private: int b1;}; class B2 : virtual public B { private: int b2;}; class C : public B1, public B2{ private: float d;} 下面的访问是正确的: C d; d.b;为了对祖先类只继承一次,C++引入了虚基类的概念。
  • 46. BB1B2Cb1b2dB1类子对象B2类子对象C类对象bB类子对象虚基类的派生类对象存储结构示意图
  • 47. 虚基类的例子class base{ public: int i; }; class derived1:virtual public base{ public: int j; }; class derived2:virtual public base{ public: int k; };Inherit base as virtual.Inherit base as virtual, here too.
  • 48. class derived3:public derived1,public derived2{ public: int product( ){return i*j*k;} }; main( ) { derived3 ob; ob.i=10; // unambiguous because only one copy present ob.j=3; ob.k=5; cout<<"Product is "<
  • 49. 虚基类小结虚基类的应用 用于有共同基类的场合 定义 以virtual修饰说明基类 例:class B1:virtual public B 作用 主要用来解决多继承时可能发生的对同一基类继承多次而产生的二义性问题. 为最远的派生类提供唯一的基类成员,而不重复产生多次拷贝 注意: 在第一级继承时就要将共同基类设计为虚基类。
  • 50. 多继承与单继承 C++的多继承可以看成由多个单继承形成的,这样以来,除了每个派生类的实例对象中包含所有的基类的无名对象外,它与单继承并无区别。 关于多继承问题的争论。
  • 51. 6.5 赋值兼容规则赋值相容规则: 在需要基类对象的任何地方都可以使用公有的派生类对象。 依据: 派生类对象中有一个无名的基类对象。
  • 52. 赋值兼容原则的应用赋值兼容原则 一个公有继承的派生类的对象在使用上可以被当作基类的对象,反之则禁止。具体表现在: 派生类的对象可以被赋值给基类对象。 派生类的对象可以初始化基类的引用。 指向派生类及基类对象的指针 以public方式继承时,指向基类对象的指针也可以指向派生类对象。
  • 53. 注意但不能将一个派生类对象的指针指向其基类对象。 通过指向基类对象的指针可以指向它的公有派生的对象,但不能指向私有派生的对象; 这种指针只能访问从基类中继承的公有成员,不能访问派生类本身的成员;
  • 54. class A //基类 { public: void print1( ); }; class B: public A //派生类,公有继承 { public: void print2( ); }; int main( ) { A op1,*ptr; B op2; ptr = &op2; //基类对象的指针指向派生类对象 ptr->print1( ); //正确,可以调用基类继承的成员 ptr->print2( ); //错误,不能访问派生类的成员 }实例
  • 55. 赋值兼容举例B0类 void display();B1类 void display( );D1类 void display( );
  • 56. #include class B0 { public: void display(){cout<<"B0::display()"<
  • 57. void fun(B0 *ptr) { ptr->display( ); } void main( ) { B0 b0; B1 b1; D1 d1; B0 *p; p=&b0; fun(p); p=&b1; fun(p); p=&d1; fun(p); }执行结果: B0::display( ) B0::display( ) B0::display( )赋值兼容举例(续)以后我们会看到运行时的多态就是通过基类对象的指针指向派生类对象来实现的
  • 58. 6.6 应用举例人员管理系统: 有三类人员:经理、技术人员、推销人员,还有销售经理。 月薪:经理:8000元/月;技术人员:100元/小时;推销人员:4%提成;销售经理:5000元月+5%提成。
  • 59. 应用举例类设计 基类:employee 派生类:technician和salesman 多继承派生类:salesmanager
  • 60. (本页无文本内容)
  • 61. class employee { protected: char *name; //姓名 int individualEmpNo; //个人编号 int grade; //级别 float accumPay; //月薪总额 static int employeeNo; //本公司职员 编号目前最大值 public: employee(); //构造函数 ~employee(); //析构函数 void pay(); //计算月薪函数 void promote(int); //升级函数 void displayStatus(); //显示人员信息 };
  • 62. class technician : public employee //技术人员类 { private: float hourlyRate; //每小时酬金 int workHours; //当月工作时数 public: technician(); //构造函数 void pay(); //计算月薪函数 void displayStatus(); //显示人员信息 };
  • 63. class salesman:virtual public employee //兼职推销员类 { protected: float CommRate; //按销售额提取酬金的百分比 float sales; //当月销售额 public: salesman(); //构造函数 void pay(); //计算月薪函数 void displayStatus(); //显示人员信息 };
  • 64. class manager:virtual public employee //经理类 { protected: float monthlyPay; //固定月薪数 public: manager(); //构造函数 void pay(); //计算月薪函数 void displayStatus(); //显示人员信息 };
  • 65. class salesmanager : public manager,public salesman //销售经理类 { public: salesmanager (); //构造函数 void pay(); //计算月薪函数 void displayStatus(); //显示人员信息 };
  • 66. employee::employee() { char namestr[50]; //输人雇员姓名时首先临时存放在namestr中 cout<<"请输入下一个雇员的姓名:"; cin>>namestr; name=new char[strlen(namestr)+1]; //动态申请用于存放姓名的内存空间 strcpy(name, namestr); //将临时存放的姓名复制到 name individualEmpNo= employeeNo++; //新输人的员工,其编号为目前最大编号加1 grade=1; //级别初值为1 accumPay=0.0; //月薪总额初值为0 }
  • 67. employee :: ~employee() { delete name ; //在析构函数中删除为存放姓名动态分配的内存空间 } void employee :: pay( ) //计算月薪,空函数 { } void employee ::promote(int increment) { grade += increment ; //升级,提升的级数由 increment指定 } void employee :: displayStatus( ) //显示人员信息,空函数 { }
  • 68. technician :: technician() { hourlyRate=100; //每小时酬金100元 } void technician::pay() { cout<<"请输入"<>workHours; accumPay=hourlyRate* workHours; //计算月薪,按小时计酬 cout<<"兼职技术人员"<
  • 69. salesman::salesman() { CommRate=0.04; //销售提成比例4% } void salesman::pay() { cout<<"请输入"<>sales; accumPay=sales*CommRate; //月薪=销售提成 cout<<"推销员"<
  • 70. manager::manager() { monthlyPay=8000; //固定月薪8000元 } void manager :: pay() { accumPay=monthlyPay; //月薪总额即固定月薪数 cout<<"经理"<
  • 71. salesmanager ::salesmanager() { monthlyPay=5000; CommRate= 0.005; } void salesmanager ::pay() { cout<<"请输入"<>sales; accumPay=monthlyPay+CommRate*sales; //月薪=固定月薪十销售提成 cout<<"销售经理"<
  • 72. int main() { manager m1; technician t1; salesmanager sm1; salesman s1; m1.promote(3); //经理m1提升3级 m1.pay(); //计算m1月薪 m1.displayStatus(); //显示m1信息 t1.promote(2); //t1提升2级 t1.pay(); //计算t1月薪 t1.displayStatus(); //显示tl信息 sm1.promote(2); //sml提升2级 sm1.pay(); //计算sml月薪 sm1.displayStatus(); //显示sml信息 s1.pay(); //计算s1月薪 s1.displayStatus(); //显示s1信息 return 0; }
  • 73. 应用例中关于虚基类的问题如果employee不设置为虚基类,则有以下错误: error C2385: 'salesmanager::promote' is ambiguous error C2385: 'salesmanager::accumPay' is ambiguous 即在salesmanager对象使用employee的公有成员都要出错:因为有两个直接基类。
  • 74. 例中的问题及改进方向这个程序有两点不足: ①基类的成员函数pay( )和displayStatus( )的函数体均为空,在实现部分仍要写出函数体,显得冗余。 ②在main( )函数中,建立了四个不同类的对象,对它们进行了类似的操作,但是却重复写了四遍类似的语句,程序不够简洁。
  • 75. 本章小结派生类继承了其父类具有的特性,并显式地表达了重用的手段。 类的继承层次恰好反映了人类认识复杂性问题的分类结构。使得软件系统的结构更加自然、合理、易于维护。
  • 76. 要点回顾什么是继承?继承的目的是什么? protected 关键字与private关键字有何区别? 如何初始化派生类对象中的基类对象? 什么是函数隐藏?