Object-Oriented Programming: Chapter 12 Object-Oriented Programming: Inheritance
OBJECTIVES To create classes by inheriting from existing classes. How inheritance promotes software reuse. The notions of base classes and derived classes and the relationships between them. The protected member access specifier. The use of constructors and destructors in inheritance hierarchies. The differences between public, protected and private inheritance. The use of inheritance to customize existing software.
Topics 12.1 Introduction 12.2 Base Classes(基类) and Derived Classes (派生类) 12.3 protected Members 12.4 Relationship between Base Classes and Derived Classes 12.5 Constructors and Destructors in Derived Classes 12.6 public, protected and private Inheritance 12.7 Software Engineering with Inheritance
12.1 Introduction --继承的概念 重写? 重用? 软件重用 在已存在的类基础上构建新的类 Graduate Student 吸纳已有类的数据及行为(数据成员及成员函数) 可以强化扩展一些新的功能 Graduate Student String Name int Age ……. Void Exam() Void Research() Student String Name int Age ……. Void Exam() 重写? 重用?
12.1 Introduction --类之间的关系 public Shape 基 类 Is-a(继承) Has-a(组合) protected Inheritance (继承关系) private Circle 派生类 声明格式:class <派生类名>:<继承方式><基类名> {……}; class Circle: public Shape{……}; 图 形 开放图形 闭合图形 多边形 椭圆形 折线 直线 弧线
is-a和has-a的区别? is-a代表继承 has-a代表组合关系 一个类继承自另一个类 属于更加专有的一类对象 可以一个类继承一些行为,也可修改甚至创建新的行为 例如vehicle类,有加速、减速等行为,car继承vehicle,同样有这些行为,也可以有新的行为(如打开后备箱等) has-a代表组合关系 一个对象包含其它的成员对象 例如Employee类中包含firstname、lastname、birthdate、hiredate等对象
Topics 12.1 Introduction 12.2 Base Classes(基类) and Derived Classes (派生类) 12.3 protected Members 12.4 Relationship between Base Classes and Derived Classes 12.5 Constructors and Destructors in Derived Classes 12.6 public, protected and private Inheritance 12.7 Software Engineering with Inheritance
12.2 Base Classes and Derived Classes Class GraduateStudent: public Student{……}; ……
12.2 Base Classes and Derived Classes 继承的层次关系(大学社区成员关系图示例) 继承机制作用: 软件复用;对事物进行分类; 支持软件的增量开发;对概念进行组合。
12.2 Base Classes and Derived Classes 定义派生类时需指出继承方式,如未显式指出,默认为private 单继承:class TwoDimensionalShape : public Shape 多继承:class AdministratorTeacher: public Teacher, public Administrator tetrahedron [5tetrE5hedrEn] n. [数][晶]四面体 tet.ra.he.dron AHD:[tµt”r…-h¶“dr…n] D.J.[7tetr*6hi8dr*n] K.K.[7tWtr*6hidr*n] n.(名词) 【复数】 tet.ra.he.drons或 tet.ra.he.dra [-dr…] A polyhedron with four faces. 四面体:有四个面的多面体
访问规则 派生类吸纳基类的数据成员及成员函数(隐性) 派生类的成员函数如何访问基类的数据成员和成员函数?(public继承)
void setAX( int a) { x = a ; } void setAX( int a) { setX(a) ; } class One{ public: void setX (int a) {x = a;} void print() {cout << x;} private: int x; }; class Two: public One{ void outputX() {print();} Int mian() { Two two; two. outputX(); two.setAX(5); } OK Error void setAX( int a) { x = a ; } void setAX( int a) { setX(a) ; }
访问规则 派生类的成员函数如何访问基类的数据成员和成员函数?(public继承) 可以访问基类中的非private数据成员及成员函数 无法直接访问基类的private成员 可以通过基类中提供的非private成员函数实现对基类中private数据成员进行修改 通常派生类需要重新定义一些成员函数,以实现派生类特有的功能及操作
Topics 12.1 Introduction 12.2 Base Classes and Derived Classes 12.3 Protected Members 12.4 Relationship between Base Classes and Derived Classes 12.5 Constructors and Destructors in Derived Classes 12.6 public, protected and private Inheritance 12.7 Software Engineering with Inheritance
12.3 Protected Members 需求: 类有两种用户:类实例和派生类 如何使派生类可以访问基类的成员,同时又能够使基类的成员不被类的实例用户访问? 基类的protected成员可以被派生类的成员和友元访问 class A { private: int x,y; }; class B: public A { void printXY() {cout<<x<<y;} int main() { A a; a.x=9; return 0; } public protected error C2248: cannot access private member declared in class 'A'
继承中的访问模式(public继承) 基类的public成员能够被程序中所有函数访问 基类的private成员只能被基类的成员函数和友元访问 基类的protected成员只能被基类的成员和友元以及派生类的成员和友元访问。 注意:不能被类的实例(对象)访问。 派生类如何访问基类的数据成员? 默认情况:派生类成员简单地使用成员名就可以引用基类的public成员和protected成员。 当派生类重新定义了基类的成员函数时,访问方式: base-class name:: + 成员函数
error C2660: 'f' : function does not take 0 parameters class A { private: int x,y; public: void f(); void g() const {cout<<x<<‘,’<<y<<endl; } }; class B: public A { int z; void h() { f(); A::f(); } int main() { B b; b.f(); b.A::f(); b.h(); return 0; } error C2660: 'f' : function does not take 0 parameters void f(int); void h(); { f(1); 只要在派生类中重写基类的函数(函数名相同,即使参数不同),就无法默认调用基类的相关函数,此时基类中的函数需要“基类::函数名”来调用。
Topics 12.1 Introduction 12.2 Base Classes and Derived Classes 12.3 Protected Members 12.4 Relationship between Base Classes and Derived Classes 12.5 Constructors and Destructors in Derived Classes 12.6 public, protected and private Inheritance 12.7 Software Engineering with Inheritance
12.4 Relationship between Base Classes and Derived Classes 12.4.1 Creating and Using a CommissionEmployee Class 12.4.2 Creating a BasePlusCommissionEmployee Class Without Using Inheritance 12.4.3 Creating a CommissionEmployee-BasePlusCommissionEmployee Inheritance Hierarchy 12.4.4 CommissionEmployee-BasePlusCommissionEmployee Inheritance Hierarchy Using protected Data 12.4.5 CommissionEmployee-BasePlusCommissionEmployee Inheritance Hierarchy Using private Data
12.4.1 Creating and Using a CommissionEmployee Class Create class CommissionEmployee, which contains as private data members a first name, last name, social security number, commissionrate (percentage) and gross (i.e., total) sales amount. P 12.4 - 6 程序解读
#include <string> using std::string; class CommissionEmployee { public: CommissionEmployee( const string &, const string &, const string &, double = 0.0, double = 0.0 ); void setFirstName( const string & ); string getFirstName() const; // return first name void setLastName( const string & ); string getLastName() const; void setSocialSecurityNumber( const string & ); string getSocialSecurityNumber() const; void setGrossSales( double ); double getGrossSales() const; void setCommissionRate( double ); // set commission rate (percentage) double getCommissionRate() const; // return commission rate double earnings() const; // calculate earnings void print() const; // print CommissionEmployee object private: string firstName; string lastName; string socialSecurityNumber; double grossSales; // gross weekly sales double commissionRate; // commission percentage }; // end class CommissionEmployee
2018年11月30日9时38分 12.4.2 Creating a BasePlusCommissionEmployee Class Without Using Inheritance Define class BasePlusCommissionEmployee, which contains as private data members a first name, last name, social security number, commission rate, gross sales amount and base salary. We create the latter class by writing every line of code the class requires we will soon see that it is much more efficient to create this class simply by inheriting from class CommissionEmployee. P 12.4 - 9 程序解读
注意:从一个类向另一个类复制粘贴代码,可能造成错误在多个源代码文件中扩散! class BasePlusCommissionEmployee { public: BasePlusCommissionEmployee( const string &, const string &, const string &, double = 0.0, double = 0.0, double = 0.0 ); void setFirstName( const string & ); // set first name string getFirstName() const; // return first name void setLastName( const string & ); // set last name string getLastName() const; // return last name void setSocialSecurityNumber( const string & ); // set SSN string getSocialSecurityNumber() const; // return SSN void setGrossSales( double ); // set gross sales amount double getGrossSales() const; // return gross sales amount void setCommissionRate( double ); // set commission rate double getCommissionRate() const; // return commission rate void setBaseSalary( double ); // set base salary double getBaseSalary() const; // return base salary double earnings() const; // calculate earnings void print() const; // print BasePlusCommissionEmployee object private: string firstName; string lastName; string socialSecurityNumber; double grossSales; // gross weekly sales double commissionRate; // commission percentage double baseSalary; // base salary }; // end class BasePlusCommissionEmployee 注意:从一个类向另一个类复制粘贴代码,可能造成错误在多个源代码文件中扩散!
BasePlusCommissionEmployee 12.4.3 Creating a CommissionEmployee-BasePlusCommissionEmployee Inheritance Hierarchy (派生类中不能直接访问基类的私有成员) BasePlusCommissionEmployee class that inherits directly from class CommissionEmployee 基类 CommissionEmployee i.e., a BasePlusCommissionEmployee is a CommissionEmployee who also has a base salary) and attempts to access class CommissionEmployee's private members, this results in compilation errors, because the derived class does not have access to the base class's private data 继承 BasePlusCommissionEmployee 派生类
程序解读 P 12.10 -12 #include <string> // C++ standard string class using std::string; #include "CommissionEmployee.h“ class BasePlusCommissionEmployee : public CommissionEmployee { public: BasePlusCommissionEmployee( const string &, const string &, const string &, double = 0.0, double = 0.0, double = 0.0 ); void setBaseSalary( double ); double getBaseSalary() const; double earnings() const; // calculate earnings void print() const; private: double baseSalary; }; P 12.10 -12 程序解读
关于初始化问题——review 如何进行初始化? 构造函数和析构函数不能继承 在创建子类对象时,为了初始化从父类继承来的数据成员,系统需要调用其父类的构造函数 所有的类成员都可以用构造函数初始化列表进行初始化,而以下情况只能如此: const data member (例外?const static integer/char) reference data member 引用类型的数据成员 member objects, 数据成员是其他类(未提供缺省构造函数)的对象 继承类(利用基类的构造函数)
BasePlusCommissionEmployee::BasePlusCommissionEmployee( const string &first, const string &last, const string &ssn, double sales, double rate, double salary ) : CommissionEmployee( first, last, ssn, sales, rate ) { setBaseSalary( salary ); // validate and store base salary } double BasePlusCommissionEmployee::earnings() const return baseSalary + ( commissionRate * grossSales ); //Error 派生类的成员函数无法访问基类的private成员
注意: 如果派生类的构造函数没有显示调用基类的构造函数,C++将尝试隐式调用默认的构造函数(前提:基类需要有缺省的构造函数) 采用成员初始化器列表显示地初始化成员对象和调用基类的构造函数,可以防止重复初始化
注意: 在派生类头文件中使用#include包含基类头文件 连接过程 告诉编译器基类的存在 让编译器根据类的定义确定对象的大小,派生类的对象大小取决于派生类显式定义的数据成员和继承自基类的数据成员 让编译器能够判断派生类是否正确的使用了基类的成员 连接过程 C++ Standard Library classes CommissionEmployee .obj BasePlusCommissionEmployee .obj client. obj Linker exe code
12.4.4 CommissionEmployee-BasePlusCommissionEmployee Inheritance Hierarchy Using protected Data(使用protected成员) if CommissionEmployee's data is declared as protected, a new version of class BasePlusCommissionEmployee that inherits from class CommissionEmployee can access that data directly. Both the inherited and noninherited BasePlusCommissionEmployee classes contain identical functionality, but we show how the version of BasePlusCommissionEmployee that inherits from class CommissionEmployee is easier to create and manage. P 12.12 -16 程序解读
Notes on protected data (注意事项) 影响数据的有效性检查 派生类可以直接访问基类的protected数据成员 派生类依赖于基类的实现 基类的数据成员发生改变有可能影响派生类的实现 软件“易碎”或“脆弱”,不够健壮 基类仅向派生类提供服务,则可使用protected类型声明,其他情况慎用protected
12.4.5 CommissionEmployee-BasePlusCommissionEmployee Inheritance Hierarchy Using private Data(在派生类中重定义基类成员) Set the CommissionEmployee data members back to private to enforce good software engineering. use base class CommissionEmployee's public member functions to manipulate CommissionEmployee's private data. P 12.17 -20 程序解读
注意: 通过调用基类的public成员函数来访问基类的私有数据成员 当功能相同时,尽量调用成员函数,以避免代码拷贝。 重定义基类成员函数时,一定要使用“::”访问基类成员,否则会引起无限递归,例如:earnings() 注意print()和earnings()的重新定义:调用基类的print()和earnings()函数 符合软件工程要求:使用继承,通过调用成员函数隐藏了数据,保证了数据的一致性。
Topics 12.1 Introduction 12.2 Base Classes and Derived Classes 12.3 Protected Members 12.4 Relationship between Base Classes and Derived Classes 12.5 Constructors and Destructors in Derived Classes 12.6 public, protected and private Inheritance 12.7 Software Engineering with Inheritance
12.5 Constructors and Destructors in Derived Classes 构造顺序 建立派生类的实例对象时、必须调用基类的构造函数来初始化派生类对象的继承成员。 派生类的构造函数既可以隐式调用基类的构造函数,也可以在派生类的构造函数中通过给基类提供初始化值显式地调用。 基类构造函数->派生类构造函数 析构顺序 析构函数的调用顺序和构造函数的顺序相反,因此派生类的析构函数在基类析构函数之前调用。 P 12.22-26 程序解读
Review 全局对象:在任何函数(含main)执行前,构造;在程序结束时,析构. 局部对象: 自动变量:对象定义时,构造;块结束时,析构. 静态变量:首次定义时,构造;程序结束时,析构. 对象析构顺序恰好与构造顺序相反. 特例1:调用exit函数退出程序执行时,不调用剩余自动对象的析构函数. 特例2:调用abort函数退出程序执行时,不调用任何剩余对象的析构函数.
BasePlueCommissionEmployee BasePlueCommissionEmployee int main() { cout << fixed << setprecision( 2 ); { // begin new scope CommissionEmployee employee1( "Bob", "Lewis", "333-33-3333", 5000, .04 ); } // end scope cout << endl; BasePlusCommissionEmployee employee2( "Lisa", "Jones", "555-55-5555", 2000, .06, 800 ); employee3( "Mark", "Sands", "888-88-8888", 8000, .15, 2000 ); return 0; } // end main CommissionEmployee constructor: CommissionEmployee destructor: BasePlueCommissionEmployee constructor: BasePlueCommissionEmployee destructor: CommissionEmployee denstructor: employee3 BasePlueCommissionEmployee CommissionEmployee employee2 BasePlueCommissionEmployee CommissionEmployee Block employee1
12.5 Constructors and Destructors in Derived Classes -- 包含成员对象 假设基类和派生类都包含其他类的对象: 在建立派生类的对象时,首先执行基类成员对象的构造函数,接着执行基类的构造函数,然后执行派生类的成员对象的构造函数,最后才执行派生类的构造函数。析构函数的调用次序与调用构造函数的次序相反。 基类成员对象初始化—>基类的构造函数—>派生类成员对象初始化—>派生类构造函数 建立成员对象的顺序是对象在类定义中的声明顺序。成员初始化器中的顺序不影响建立对象的顺序。 对继承关系,基类构造函数的调用顺序是派生类定义中指定的继承顺序,派生类成员初始化值列表中指定的基类构造函数的顺序不影响对象的建立顺序。
Class1 default constructor! BaseClass default constructor! class Class1{ public: Class1(int a) { cout<<"Class1 constructor!"<<endl; num = a; } Class1() { cout<<"Class1 default constructor!"<<endl; } ~Class1() {cout<<"Class1 deconstructor!"<<endl;} private: int num; }; class BaseClass{ BaseClass(int a, int b): t(a) { cout<<"BaseClass constructor!"<<endl; num = b; }; BaseClass() {cout<<"BaseClass default constructor!"<<endl;} ~BaseClass() {cout<<"BaseClass deconstructor!"<<endl;} private: Class1 t; int num; }; class DerivingClass:public BaseClass{ public: DerivingClass(int a, int b): t1(a) ,BaseClass(a,b) {cout<<"DerivingClass constructor!"<<endl; num = b; }; ~DerivingClass() {cout<<"DerivingClass deconstructor!"<<endl;} private: Class1 t1; int num; }; int main() { DerivingClass obj(10, 20); return 0; } Class1 default constructor! BaseClass default constructor! Class1 constructor! DerivingClass constructor! DerivingClass deconstructor! Class1 deconstructor! BaseClass deconstructor!
Topics 12.1 Introduction 12.2 Base Classes and Derived Classes 12.3 Protected Members 12.4 Relationship between Base Classes and Derived Classes 12.5 Constructors and Destructors in Derived Classes 12.6 public, protected and private Inheritance 12.7 Software Engineering with Inheritance
12.6 public, protected and private Inheritance 2018年11月30日9时38分 继承方式 基 类 派生类 public public成员 protected成员 private成员 不可直接使用 private protected (很少使用) 注意:友元函数是不被继承的
进一步阐述——公有继承 (1) 基类成员对其对象的可见性: (2) 基类成员对派生类的可见性: (3) 基类成员对派生类对象的可见性: 公有成员可见,其他不可见。这里保护成员同于私有成员。 (2) 基类成员对派生类的可见性: 公有成员和保护成员可见,而私有成员不可见。这里保护成员同于公有成员。 (3) 基类成员对派生类对象的可见性: 公有成员可见,其他成员不可见。(见前表) 一定要区分派生类的对象和派生类中的成员函数对基类的访问是不同的。
进一步阐述——私有继承 (1) 基类成员对其对象的可见性: (2) 基类成员对派生类的可见性: (3) 基类成员对派生类对象的可见性: 公有成员可见,其他成员不可见。(同前) (2) 基类成员对派生类的可见性: 公有成员和保护成员可见,私有成员不可见。(同前) (3) 基类成员对派生类对象的可见性: 所有成员都不可见。(见前表) 保护继承与私有继承类似
总结 不论公有继承还是私有继承,基类成员对于派生类的访问权限是不变的。(可访问公有或保护类型成员) 变化的是派生类的对象以及派生类的派生类对基类成员的访问权限。
Topics 12.1 Introduction 12.2 Base Classes and Derived Classes 12.3 Protected Members 12.4 Relationship between Base Classes and Derived Classes 12.5 Constructors and Destructors in Derived Classes 12.6 public, protected and private Inheritance 12.7 Software Engineering with Inheritance
12.7 Software Engineering with Inheritance 派生类的程序员不需了解基类的源代码,只需与目标代码连接即可 ISV(Independent software vendors 独立的软件供应商)为目标代码提供头文件,发放许可 继承实用的类库,提高软件复用 软件工程提示: 提取出共同的属性和行为并把它们放在一个基类中,然后,再通过继承生成派生类。 尽量建立较少的类,避免不必要的类
Summary 基类和派生类的定义 Protected成员 基类和派生类的关系:public,proteced,private 继承关系中构造函数和析构函数顺序 复合(has-a)和继承(is-a)的关系 “使用”和”知道”
Homework! 上机题目 12.10