继承与派生【C++】
1、基本概念
???????? 在 C++ 中可重用性是通过继承这一机制来实现的。因此,继承是 C++ 的一个重要组成部分。在 C++ 中,继承就是在一个已存在的类的基础上建立一个新的类。已存在的类称为 “基类” 或 “父类”。新建立的类称为 “派生类” 或 “子类”。
? ? ? ? 一个新类从已有的类那里获得其已有特性,这种现象称为类的继承。通过继承,一个新建子类从已有的父类那里获得父类的特性。从另一角度说,从已有的类(父类)产生一个新的子类,称为类的派生。
????????类的继承是用已有的类来建立专用类的编程技术。派生类继承了基类的所有数据成员和成员函数,并可以对成员作必要的增加或调整。一个基类可以派生出多个派生类,每一个派生类又可以作为基类再派生出新的派生类,因此基类和派生类是相对而言的。
? ? ? ? 一个派生类只从一个基类派生,这称为单继承,这种继承关系所形成的层次是一个树形结构。一个派生类有两个或多个基类的称为多重继承。而基类和派生类的关系,可以表示为:派生类是基类的具体化,而基类则是派生类的抽象。
2、派生类的声明方式
? ? ? ? 声明派生类的一般形式为:
class 派生类名:[ 继承方式 ] 基类名 {
? ? ? ? 派生类新增加的成员
};
?????????继承方式是可选的,具体包括:public(公用的)、private (私有的)、protected (受保护的)。若不写,则默认为 private (私有的)。
3、派生类的构成
? ? ? ? 派生类中的成员包括从基类继承过来的成员和自己增加的成员两大部分。如下图所示:
????????在基类中包括数据成员和成员函数两部分,派生类分为两大部分:一部分是从基类中继承来的成员,另一部分是在声明派生类时增加的部分。每一部分均分别包括数据成员和成员函数。
????????实际上,并不是把基类的成员和派生类自己增加的成员简单地加在一起就成为派生类。构造一个派生类包括以下三部分工作:
(1)从基类接收成员。派生类把基类全部的成员(不包括构造所数和析构函数)接收过来,也就是说是没有选择的,不能选择接收其中一部分成员,而舍弃另一部分成员。从定义派生类的一般形式中可以看出是不可选择的。
(2)调整从基类接收的成员。接收基类成员是程序人员不能选择的,但是程序人员可以对这些成员作某些调整。例如可以改变基类成员在派生类中的访问属性,这是通过指定继承方式来实现的。如可以通过继承把基类的公用成员指定为在派生类中的访问属性为私有(在派生类外不能访问)。此外,可以在派生类中声明一个与基类成员同名的成员,则派生类中的新成员会覆盖基类的同名成员,但应注意:如果是成员函数,不仅应使函数名相同,而且函数的参数表(参数的个数和类型)也应相同,如果不相同,就成为函数的重载而不是覆盖了。用这样的方法可以用新成员取代基类的成员。
(3)声明派生类时新增加的成员。要根据需要仔细考虑应该增加哪些成员,精心设计。此外,还需要自己定义派生类的构造函数和析构函数,因为构造函数和析构函数是不能从基类继承的。
4、派生类成员的访问属性
? ? ? ? ?如何确定基类的成员在派生类中的访问属性的问题,不仅要考虑到基类成员所声明的访问属性,而且还要考虑派生类所声明的对基类的继承方式,根据这两个因素共同决定基类成员在派生类中的访问属性。
? ? ? ? 在派生类中,对基类的继承方式有 public (公用的)、private (私有的)、protected (受保护的)这三种。不同的继承方式决定了基类成员在派生类中的访问属性。简单的说:
(1)公用继承
基类的公用成员和保护成员在派生类中保持原有访问属性,而私有成员仍基类私有。
(2)私有继承
基类的公用成员和保护成员在派生类中成了私有成员,而私有成员仍基类私有。
(3)受保护的继承
基类的公用成员和保护成员在派生类中成了保护成员,而私有成员仍基类私有。
(4.1)公用继承
? ? ? ? 在定义一个派生类的时候将基类的继承方式指定为 public 的,称为公用继承。公有基类的成员在派生类中的访问属性如下表:
公用基类的成员
在派生类中的访问属性
私有成员
不可访问
公用成员
公用
保护成员
保护
?【例】访问公有基类的成员
#include <iostream>
#include <cstring>
using namespace std;
class Student {
public:
void get_value() {
cin >> num >> name >> sex;
}
void display() {
cout << "num:" << num << endl;
cout << "name:" << name << endl;
cout << "sex:" << sex << endl;
}
private:
int num;
string name;
char sex;
};
class Student1 :public Student { // 以 public 方式声明派生类 Student1
public:
void get_value() {
Student::get_value(); // 调用基类的公用成员函数
cin >> age >> addr;
}
void display() {
//cout << "num:" << num << endl; // 错误,不能引用基类的私有成员
//cout << "name:" << name << endl; // 错误,不能引用基类的私有成员
//cout << "sex:" << sex << endl; // 错误,不能引用基类的私有成员
Student::display(); // 调用基类的公用成员函数
cout << "age:" << age << endl;
cout << "address:" << addr << endl;
}
private:
int age;
string addr;
};
int main() {
Student1 stud;
stud.get_value();
stud.display();
return 0;
}
(4.2)私有继承
????????在定义一个派生类的时候将基类的继承方式指定为 private 的,称为私有继承。私有基类的成员在派生类中的访问属性如下表:
私有基类的成员
在派生类中的访问属性
私有成员
不可访问
公用成员
私有
保护成员
私有
?【例】访问私有基类的成员。
#include <iostream>
#include <cstring>
using namespace std;
class Student {
public:
void get_value() {
cin >> num >> name >> sex;
}
void display() {
cout << "num:" << num << endl;
cout << "name:" << name << endl;
cout << "sex:" << sex << endl;
}
private:
int num;
string name;
char sex;
};
class Student1 :private Student { // 以 private 方式声明派生类 Student1
public:
void display_1() {
cout << "age:" << age << endl;
cout << "address:" << addr << endl;
}
private:
int age;
string addr;
};
int main() {
Student1 stud;
stud.get_value(); // 错误,私有基类的公用成员函数在派生类中是私有函数
stud.display(); // 错误,私有基类的公用成员函数在派生类中是私有函数
stud.age = 18; // 错误,外界不能引用派生类的私有成员
stud.display_1(); // 正确。display_1() 是 Student1 类的公用函数
return 0;
}
【结论】
(1)不能通过派生类对象引用从私有基类继承过来的任何成员;
(2)派生类的成员函数不能访问私有基类的私有成员,但可以访问私有基类的公用成员;
(3)虽然在派生类外不能通过派生类对象调用私有基类的公用成员函数,但是可以通过派生
类的成员函数调用私有基类公用成员函数。
(4.3)受保护的继承
????????由 protected 声明的成员称为受保护成员,即保护成员。受保护成员不能被类外访问,从类的角度上来看,保护成员等价于私有成员。但是。保护成员可以被派生类的成员函数调用,而私有成员不行。如图所示:
在基类外不能访问保护成员
?????????在定义一个派生类时将基类的继承方式指定为 protected 的,称为保护继承。保护继承的特点是:保护基类的公用成员和保护成员在派生类中都成了保护成员,但私有成员仍为基类私有。保护基类的成员在派生类中的访问属性如下表:
保护基类的成员
在派生类中的访问属性
私有成员
不可访问
公用成员
保护
保护成员
保护
【例】在派生类中引用保护成员
#include <iostream>
#include <cstring>
using namespace std;
class Student {
public:
void display(){
cout << "num:" << num << endl;
cout << "name:" << name << endl;
cout << "sex:" << sex << endl;
}
protected: // 基类保护成员
int num;
string name;
char sex;
};
class Student1 :protected Student { // 用 protected 方式声明派生类 Student1
public:
void display1() {
cout << "num:" << num << endl;
cout << "name:" << name << endl;
cout << "sex:" << sex << endl;
cout << "age:" << age << endl;
cout << "address:" << address << endl;
}
private:
int age;
string address;
};
int main() {
Student1 stud1;
stud1.display1();
//stud1.num = 10023; // 错误,外界不能引用保护成员
return 0;
}
(4.4)多级派生时的访问属性
????????上面讲得都是一级派生的情况,实际上,常常有多级派生的情况。如图:
图中的派生关系:类 A 为基类,类 B 是类 A 的派生类,类 C 是类 B 的派生类,则类 C 也是类 A 的派生类。而类 B 被称为类 A 的直接派生类,类 C? 被称为类 A 的间接派生类;类 A 是类 B 的直接基类,是类 C 的间接基类。
????????在多级派生的情况下,各成员的访问属性仍按照上面的原则确定。
【例】多级派生的访问属性
class A {
public:
int i;
protected:
void fA();
int j;
private:
int k;
};
class B :public A {
public:
void fB1();
protected:
void fB2();
private:
int m;
};
class C :protected B {
public:
void fC();
private:
int n;
};
类 A 是类 B 的公用基类,类 B 是类 C 的保护基类。各成员在不同类的访问属性如下表:
i f j k fB1 fB2 m fC 基类 A 公用 保护 保护 私有 派生类 B 公用 保护 保护 不可访问 公用 保护 私有 派生类 C 保护 保护 保护 不可访问 保护 保护 不可访问 公用 私有
5、派生类的构造函数和析构函数
????????在设计派生类的构造函数时,不仅要考虑派生类所增加的数据成员的初始化,而且还要考虑基类的数据成员的初始化。
(5.1)简单派生类的构造函数
????????简单派生类中只有一个基类,而且只有一级派生(只有直接派生,没有间接派生),在派生类的数据成员中不包括基类的对象(子对象)。
? ? ? ? 一般格式为:
派生类构造函数名(总参数列表):基类构造函数名(参数列表){
? ? ? ? 派生类中新增的数据成员初始化语句
}
?【例】简单派生类的构造函数
#include <iostream>
#include <cstring>
using namespace std;
class Student {
public:
Student(int n, string na, char s) {
num = n;
name = na;
sex = s;
}
~Student() {}
protected:
int num;
string name;
char sex;
};
class Student1 :public Student {
public:
Student1(int n, string na, char s, int a, string addr) :Student(n, na, s) {
age = a;
address = addr;
}
~Student1() {}
void display() {
cout << "num:" << num << endl;
cout << "name:" << name << endl;
cout << "sex:" << sex << endl;
cout << "age:" << age << endl;
cout << "address:" << address << endl << endl;
}
private:
int age;
string address;
};
int main() {
Student1 stud1(10001, "张三", 'm', 18, "湖南");
Student1 stud2(10002, "李四", 'm', 19, "河南");
stud1.display();
stud2.display();
return 0;
}
注意:
(1)在类中对派生类构造函数作声明时,不包括基类构造函数名及其参数表列。只在定义函数时才将它列出。
(2)可以利用初始化表对构造函数的数据成员初始化,也可以利用初始化表调用派生类的基类构造函数,实现对基类数据成员的初始化。也可以在同一个构造函数的定义中同时实现这两种功能。
【例如上例可以写成以下形式】
Student1(int n,string na,char s,int a,string addr):
Student(n,na,s),age(s),address(addr) {}
(3) 在建立一个对象时,执行构造函数的顺序是:派生类构造函数先调用基类构造函数;再执行派生类构造函数本身。
(5.2)有子对象的派生类的构造函数
????????类对象中的内嵌对象,称为子对象,即对象中的对象。一般格式为:
派生类构造函数名(总参数表列):基类构造函数名(参数表列),子对象名(参数表列){
? ? ? ? 派生类中新增数据成员初始化语句;
}
【例】包含子对象的派生类的构造函数
#include <iostream>
#include <cstring>
using namespace std;
class Student {
public:
Student(int n, string na) {
num = n;
name = na;
}
void display() {
cout << "num:" << num << endl;
cout << "name:" << name << endl;
}
protected:
int num;
string name;
};
class Student1 :public Student {
public:
Student1(int n, string na, int n1, string na1, int a, string addr) :
Student(n, na), monitor(n1, na1) { // 派生类构造函数
age = a;
address = addr;
}
void show() {
cout << "This student is:" << endl;
display();
cout << "age:" << age << endl;
cout << "address:" << address << endl << endl;
}
void display_monitor(){
cout << endl << "Class monitor is:" << endl;
monitor.display();
}
private:
Student monitor; // 定义子对象
int age;
string address;
};
int main() {
Student1 stud1(10001, "张三", 10000, "班长", 19, "湖南");
stud1.show();
stud1.display_monitor();
return 0;
}
注意:
(1)派生类构造函数的任务应该包括三部分:
a. 对基类数据成员初始化;
b. 对子对象数据成员初始化;
c. 对派生类数据成员初始化。
(2)执行派生类构造函数的顺序是:
a. 调用基类构造函数,对基类数据成员初始化;
b. 调用子对象构造函数,对子对象数据成员初始化;
c. 再执行派生类构造函数本身,对派生类数据成员初始化。
(5.3)多层派生时的构造函数
? ? ? ? 一个类不仅可以派生出一个派生类,派生类还可以继续派生,形成派生的层次结构。
【例】多级派生情况下派生类的构造函数
#include <iostream>
#include <cstring>
using namespace std;
class Student {
public:
Student(int n, string na) {
num = n;
name = na;
}
void display1() {
cout << "num:" << num << endl;
cout << "name:" << name << endl;
}
protected:
int num;
string name;
};
class Student1 :public Student {
public:
Student1(int n, string na,int a) :Student(n,na){
age = a;
}
void display2() {
display1();
cout << "age:" << age << endl;
}
private:
int age;
};
class Student2 :public Student1 {
public:
Student2(int n, string na, int a, int s) :Student1(n, na, a) {
score = s;
}
void display3() {
display2();
cout << "score:" << score << endl;
}
private:
int score;
};
int main() {
Student2 stud(10001, "张三", 19, 90);
stud.display3();
return 0;
}
注意:不要列出每一层派生类的构造函数,只需要写出上一层派生类(即它的直接基类)的构造函数即可。
(5.4)派生类构造函数的特殊形式
(1)当不需要对派生类新增的成员进行任何初始化操作时,派生类构造函数的函数体可以为空,即构造函数是空函数。 (2)如果在基类中没有定义构造函数,或定义了没有参数的构造函数,那么在定义派生类构造函数时可以不写基类构造函数。 (3)如果在基类和子对象类型的声明中都没有定义带参数的构造函数,而且也不需对派生类自己的数据成员初始化,则可以不必显式地定义派生类构造函数。反之,则必须显式的定义派生类构造函数,并且参数列表要写清楚。 (4)如果在基类中既定义无参的构造函数,又定义了有参的构造函数(构造函数重载),则在定义派生类构造函数时,既可以包含基类构造函数及其参数,也可以不包含基类构造函数。在调用派生类构造函数时,根据构造函数的内容决定调用基类的有参的构造函数还是无参的构造函数。
(5.5)派生类的析构函数
????????析构函数比构造函数简单,没有类型,也没有参数。在派生时,派生类不能继承基类的析构函数,所以需要通过派生类的析构函数来调用基类的析构函数。调用的顺序与构造函数相反:先执行派生类本身的析构函数,对新增的数据成员进行清理;然后再调用子对象的析构函数,对子对象进行清理;最后调用基类的析构函数,对基类进行清理。
6、多重继承
? ? ? ? 一个派生类同时继承多个基类,这种行为称为多重继承。
(6.1)声明多继承的方法
class D:public A,private B,protected C{
? ? ? ? 类 D 新增加的数据成员;
}
(6.2) 多重继承派生类的构造函数
????????多重继承派生类的构造函数和单继承派生类的构造函数形式基本相同,如下:
派生类构造函数名(总参数表列):基类 1 构造函数(参数表列),基类 2 构造函数(参数表列),基类 3 构造函数(参数表列){
? ? ? ? 派生类中新增数据成员初始化语句;
}
【例】多重继承派生类的构造函数
#include <iostream>
#include <cstring>
using namespace std;
class Teacher {
public:
Teacher(string na, int a, string t) {
name = na;
age = a;
title = t;
}
void display1() {
cout << "name:" << name << endl;
cout << "age:" << age << endl;
cout << "title:" << title << endl;
}
protected:
string name;
int age;
string title;
};
class Student {
public:
Student(string na, char s, float sco) {
name = na;
sex = s;
score = sco;
}
void display2() {
void display1();
cout << "name:" << name << endl;
cout << "sex:" << sex << endl;
cout << "score:" << score << endl;
}
protected:
string name;
char sex;
float score;
};
class Graduate:public Teacher,public Student {
public:
Graduate(string na, int a, char s, string t, float sco, float w) :
Teacher(na, a, t), Student(na, s, sco), wage(w) {}
void display3() {
cout << "name:" << Teacher::name << endl;
cout << "age:" << age << endl;
cout << "sex:" << sex << endl;
cout << "score:" << score << endl;
cout << "title:" << title << endl;
cout << "wage:" << wage << endl;
}
private:
float wage;
};
int main() {
Graduate grad1("张三", 19, 'm', "助手", 90.5, 1250.0);
grad1.display3();
return 0;
}
注意:在多重继承时,从不同的基类中会继承一些重复的数据,这是常见的,因为一般情况下使用的是现有的基类。所以在设计派生类时要细致考虑数据成员,尽量减少数据冗余。
(6.3)多重继承引起的二义性问题
?????? ??多重继承最常见的问题就是继承的成员同名而产生的二义性问题。
【三种情况】
?(1)两个基类有同名成员。
?基类名限定:
class A {
public:
int a;
void display();
};
class B {
public:
int a;
void display();
};
class C :public A, public B {
public:
int b;
void show();
};
int main() {
C c1;
c1.a = 3; // 错误
c1.display();; // 错误
c1.A::a = 3; // 正确
c1.A::display();// 正确
c1.B::a = 3; // 正确
c1.B::display();// 正确
}
(2)两个基类和派生类三者都有同名成员。
class A {
public:
int a;
void display();
};
class B {
public:
int a;
void display();
};
class C :public A, public B {
public:
int a;
void display();
};
int main() {
C c1;
c1.a = 3; // 正确,同名成员覆盖
c1.display();; // 正确,同名成员覆盖
c1.A::a = 3; // 正确
c1.A::display();// 正确
c1.B::a = 3; // 正确
c1.B::display();// 正确
}
/*
规则:
基类的同名成员在派生类中被屏蔽,成为“不可见的”,或者说,派生类新增加的同名成员覆盖了基类中的同名成员。
*/
?(3)如果类 A 和类 B 是从同一个基类派生的。
class N {
public:
int a;
void diaplay();
};
class A :public N {
public:
int a1;
};
class B :public N {
public:
int a2;
};
class C :public A, public B {
public:
int a3;
void show();
};
int main() {
C c1;
c1.a = 3; // 错误
c1.diaplay(); // 错误
c1.N::a = 3; // 错误
c1.N::diaplay(); // 错误
// 前面四种方法都无法区别访问的是类 A 中从基类 N 继承下来的成员,还是类 B 中从基类 N 继承下来的成员。
// 应当通过类 N 的直接派生类名来指出要访问是是类 N 的哪一个派生类中的数据成员。
c1.A::a = 3; // 正确
c1.B::diaplay(); // 正确
}
?分析图:
?(6.4)虚基类
(1)虚基类的声明及作用
????????C++ 提供虚基类的方法,使得在继承间接共同基类时只保留一份成员。声明虚基类的一般形式为:
class 派生类名:virtual 继承方式 基类名
经过该声明后,当基类通过多条派生路劲被一个派生类继承时,该派生类只继承该基类一次,即基类成员只保留一次。
? ? ? ? 注意:虚基类不是在声明基类时声明的,而是在声明派生类时,指定继承方式时声明的。因为一个基类可以在生成一个派生类时作为虚基类,而在生成另外一个派生类时不作为虚基类。
(2)虚基类的初始化
????????如果在虚基类中定义了带参数的构造函数,而且没有定义默认构造函数,则在其所有派生类中,通过构造函数的初始化表对虚基类进行初始化。
【例】
#include <iostream>
#include <cstring>
using namespace std;
class Person {
public:
Person(string na, char s, int a) {
name = na;
sex = s;
age = a;
}
protected:
string name;
char sex;
int age;
};
class Teacher :virtual public Person {
public:
Teacher(string na, char s, int a, string t) :
Person(na, s, a) {
title = t;
}
protected:
string title;
};
class Student :virtual public Person {
public:
Student(string na, char s, int a, float sco) :
Person(na, s, a) {
score = sco;
}
protected:
float score;
};
class Graduate :public Teacher, public Student {
public:
Graduate(string na, char s, int a, string t, float sco, float w) :
Person(na, s, a), Teacher(na, s, a, t), Student(na, s, a, sco), wage(w) {}
void display() {
cout << "name:" << name << endl;
cout << "age:" << age << endl;
cout << "sex:" << sex << endl;
cout << "score:" << score << endl;
cout << "title:" << title << endl;
cout << "wages:" << wage << endl;
}
private:
float wage;
};
int main() {
Graduate grad1("张三", 'm', 19, "助手",90.5, 1250.5);
grad1.display();
return 0;
}
?注意:
(1)在最后的派生类中不仅要负责对其直接基类进行初始化,而且还要对虚基类初始化。
(2)C++ 编译系统只执行最后的派生类对虚基类的构造函数的调用,而忽略基类的其他派生类对虚基类的构造函数的调用,这就保证了虚基类的数据成员不会被多次初始化。
(3)使用多重继承时要十分小心,经常出现二义性问题。
7、基类和派生类的转换
? ? ? ? 只有公用的派生类才是基类真正的子类型,它完整的继承了基类的功能。
? ? ? ? 不同类型数据之间的自动转换和赋值,称为赋值兼容。基类和派生类对象之间也有赋值兼容的关系,由于派生类中包含从基类继承的成员,因此可以将派生类的值赋给基类对象,在用到基类对象的时候可以用其子类对象代替。
(7.1)派生类对象可以向基类对象赋值
【例】基类 A,公用派生类 B
A a1;
B b1;
a1 = b1; // 用派生类对象b1对基类对象a1赋值
注意:子类型关系是单向的、不可逆的。只能用子类对象对其基类对象赋值,而不能用基类对象对子类对象赋值。因为基类对象不包含派生类的成员,无法对派生类的成员赋值。
(7.2)派生类对象可以替代基类对象向基类对象的引用进行赋值或初始化
【例】基类 A,公用派生类 B
A a1;
B b1;
A& r = a1; // 定义基类 A 对象的引用变量 r,并用 a1 对其初始化
// 此时 r 是 a1 的别名,r 和 a1 共享同一段存储单元
r = b1; // 用派生类 B 对象 b1 对 a1 的引用变量 r 赋值
// 此时 r 不是 b1 的别名,也不共享同一段存储单元
A& r = b1;// 定义基类 A 对象的引用变量 r,并用派生类 B 对象 b1 对其初始化
(7.3)如果基类的参数是基类对象或基类对象的引用,相应的实参可以用子对象
【例】基类 A,派生类 B
void f(A& r) { // 形参是类 A 对象的引用
cout << r.num << endl;
}
/*
在调用fun函数的时候可以用派生类 B 的对象 b1 做实参:
fun(b1);
*/
(7.4) 派生类对象的地址可以赋给指向基类对象的指针变量,也就是说,指向基类对象的指针变量也可以指向派生类对象
【例】用指向基类对象的指针指向派生类对象
#include <iostream>
#include <cstring>
using namespace std;
class Student {
public:
Student(int n, string na, float sco) {
num = n;
name = na;
score = sco;
}
void display();
private:
int num;
string name;
float score;
};
void Student::display() {
cout << endl << "num:" << num << endl;
cout << "name:" << name << endl;
cout << "score:" << score << endl;
}
class Graduate :public Student {
public:
Graduate(int n, string na, float sco, float p) :Student(n, na, sco), pay(p) {}
void display();
private:
float pay;
};
void Graduate::display() {
Student::display();
cout << "pay:" << pay << endl;
}
int main() {
Student stud1(101, "张三", 90.5);
Graduate grad1(102, "李四", 95.5, 660.6);
Student* p = &stud1;
p->display();
p = &grad1;
p->display();
return 0;
}
/*程序结果:
*
num:101
name:张三
score:90.5
num:102
name:李四
score:95.5
*
*/
【程序结果分析】
由于 p 是指向 Student 类对象的指针变量,即使它指向了 grad1,但实际上 p 指向的是 grad1 中从基类继承的部分。通过指向基类对象的指针,只能访问派生类中的基类成员,而不能访问派生类新增的成员。
8、继承和组合
? ? ? ? 在一个类中以另一个类的对象作为数据成员的,称为类的组合。对象成员的类型可以是本派生类的基类,也可以是另外一个已定义的类。
【例】声明 Professor 类是 Teacher 类的派生类,另有一个类 BirthDate。将 Professor 生日的信息加入到 Professor 类的声明中。
class Teacher {
public:
...
private:
int num;
string name;
char sex;
};
class BirthDate {
public:
...
private:
int year;
int month;
int day;
};
class Professor :public Teacher {
public:
...
private:
...
BirthDate birthday; // BirthDate 类的对象作为数据成员
};
/*
若存在以下两个函数:
void fun1(Teacher &);
void fun2(BirthDate &);
在 main 函数中调用这两个函数:
fun1(prof1);// 正确
// 形参为 Teacher 类对象的引用,实参为 Teacher 类的子类对象,兼容
fun2(prof1.birthday);// 正确
// 实参与形参类型相同,都是 BirthDate 类对象
fun2(prof1);// 错误
// 形参要求是 BirthDate 类对象,而 prof1 是 Professor 类型
*/
注意:如果修改了成员类的部分内容,只要成员类的公用接口不变,组合类可以不修改。但是组合类需要重新编译。