软件的可重用性,指的是在不同软件的开发过程中重复使用相同或相似软件元素的过程。C++强调软件的可重用性,并且提供了继承机制来解决软件的可重用性问题。
4.2 单继承
4.2.1 单继承的定义方式
定义派生类的语法形式为:
class 派生类名:继承方式 基类名{
派生类中的新增成员
};
继承方式也称访问方式(默认为私有),有三种:
若继承方式是public,基类成员在派生类中的访问权限保持不变,也就是说,基类中的成员访问权限,在派生类中仍然保持原来的访问权限;
若继承方式是private,基类所有成员在派生类中的访问权限都会变为私有(private)权限,但其实基类的私有成员变为不可访问;
若继承方式是protected,基类的共有成员和保护成员在派生类中的访问权限都会变为保护(protected)权限,私有成员在派生类中的访问权限仍然是私有(private)权限。
4.2.2 派生类的成员构成
派生一个新类的过程有以下三步:
(1)接受基类成员。基类的构造函数和析构函数不能被派生类继承,其他成员函数和数据成员则必须全部继承,不能有选择地继承。
(2)改造继承的基类成员。首先是对基类成员的访问方式的控制,这在上节已提到过。第二项改造是对基类数据成员或成员函数的覆盖,如果是成员函数,则要求参数的个数和类型也要相同,否则就成了函数重载而不是同名覆盖。
(3)添加新成员。一般都需要重新定义派生类的构造函数和析构函数。
4.2.3 派生类成员的访问属性
基类中的私有成员不能直接访问,但可以通过基类的公有或保护成员函数来访问或修改。
4.2.4 派生类的构造函数
已知基类的构造函数是不能被派生类继承的,所以这时就需要有一种机制来完成基类构造函数的调用,那就是派生类的构造函数需要调用基类的构造函数来完成初始化继承成员的工作。
(1)派生类的构造函数
派生类的构造函数的一般定义格式为:
派生类名::派生类构造函数名(总参数表):基类构造函数名(参数表){
派生类的构造函数体
}
如果基类的构造函数参数为空或基类中没有定义构造函数则可以省略对基类构造函数的调用。
如果基类存在有参数的构造函数但派生类构造函数为空,这时派生类构造函数一定不可以省略。
(2)有子对象的派生类的构造函数
含有子对象的派生类的构造函数的定义格式为:
派生类名::派生类构造函数名(总参数表):基类构造函数名(参数表),子对象名(参数表){
派生类的构造函数体
}
#include<iostream> using namespace std; class Worker { public: int number; Worker(int i) { cout << "基类Worker类的构造函数被执行" << endl; number = i; } }; class Time { private: int hour; int minute; int sec; public: Time(int h, int m, int s) { cout << "子对象Time类的构造函数被执行" << endl; hour = h; minute = m; sec = s; } void show() { cout << hour << ":" << minute << ":" << sec << endl; } }; class WorkerB :public Worker { public: int salary; Time time; WorkerB(int i, int h1, int m1, int s1, int j) :Worker(i), time(h1, m1, s1) { cout << "派生类WorkerB类的构造函数被执行" << endl; salary = j; } }; int main() { WorkerB w(60, 8, 0, 0, 3000); cout << "工人w的工号是:" << w.number << endl; cout << "工人w的工作时间是:"; w.time.show(); cout << "工人w的薪水是:" << w.salary << endl; getchar(); } ------------------------------------------------------------------------- 基类Worker类的构造函数被执行 子对象Time类的构造函数被执行 派生类WorkerB类的构造函数被执行 工人w的工号是:60 工人w的工作时间是:8:0:0 工人w的薪水是:3000
4.2.5 派生类的析构函数
析构函数的调用顺序与构造函数的调用顺序正好相反:首先执行派生类的析构函数,其次调用子对象类的数据成员的析构函数,最后调用基类的析构函数。
#include<iostream> using namespace std; class Worker { public: int number; Worker(int i) { cout << "基类Worker类的构造函数被执行" << endl; number = i; } ~Worker() { cout << "基类Worker类的析构函数被执行" << endl; } }; class Time { private: int hour; int minute; int sec; public: Time(int h, int m, int s) { cout << "子对象Time类的构造函数被执行" << endl; hour = h; minute = m; sec = s; } ~Time() { cout << "子对象类Time的析构函数被执行" << endl; } void show() { cout << hour << ":" << minute << ":" << sec << endl; } }; class WorkerB :public Worker { public: int salary; Time time; WorkerB(int i, int h1, int m1, int s1, int j) :Worker(i), time(h1, m1, s1) { cout << "派生类WorkerB类的构造函数被执行" << endl; salary = j; } ~WorkerB() { cout << "派生类WorkerB类的析构函数被执行" << endl; } }; void a() { WorkerB w(60, 8, 0, 0, 3000); cout << "工人w的工号是:" << w.number << endl; cout << "工人w的工作时间是:"; w.time.show(); cout << "工人w的薪水是:" << w.salary << endl; } int main() { a(); getchar(); }
基类Worker类的构造函数被执行 子对象Time类的构造函数被执行 派生类WorkerB类的构造函数被执行 工人w的工号是:60 工人w的工作时间是:8:0:0 工人w的薪水是:3000 派生类WorkerB类的析构函数被执行 子对象类Time的析构函数被执行 基类Worker类的析构函数被执行
4.3 多重继承
多重继承是指一个派生类由多个基类派生出来,并继承多个基类的特征,派生类与每个基类的关系仍可看作是一个单继承。
同一层次的各基类构造函数,按照定义派生类时所指定的各基类顺序执行,而与派生类构造函数的初始化列表中的顺序无关。析构函数的执行顺序与构造函数相反。
4.3.3 多重继承的二义性
在多重继承过程中由于继承的成员同名而产生的问题称为二义性问题,多重继承的二义性主要有以下两种情况。
(1)被继承的多个基类中有同名的成员,则在派生类中调用这个成员时,会产生二义性。
class WorkerA { public: int number; }; class WorkerB { public: int number; }; class WorkerC :public WorkerA, public WorkerB { public: int salary; }; int main() { WorkerC w; w.number = 60; return 0; } //严重性 代码 说明 项目 文件 行 禁止显示状态 //错误 C2385 对“number”的访问不明确 Project1 x : c++_codeproject1project1 est.cpp 15
为了避免这种错误的发生,可以用基类名来进行限定:
int main() { WorkerC w; w.WorkerA::number = 60; return 0; }
(2)被继承的多个基类中有一个共同的基类,在派生类中调用这个共同基类的成员时产生二义性,为了避免这种错误,也可以使用上述这种用基类名来进行限定的方法。
4.3.4 虚基类
如果被继承的多个基类中有一个共同的基类,消除这种二义性问题的另一种方法是使用虚基类。同一个基类不能被继承多次,否则会产生二义性。因此,要将共同基类设置为虚基类,这样,它虽然被多次继承,但派生类却只继承一份该基类的成员。
虚基类的声明形式为:
class 派生类名:virtual 继承方式 基类名
vitual 是声明虚基类的关键字,加到继承方式的前面,这样声明后,当基类通过多条路径被一个派生类继承时,该派生类只继承该基类一次,即基类成员只保留一份。
在使用虚基类时,派生类的构造函数除了调用直接基类的构造函数外,还要调用虚基类的构造函数对其初始化。
#include<iostream> using namespace std; class Worker { public: int number; Worker(int i) { cout << "基类Worker类的构造函数!" << endl; number = i; } }; class WorkerA :virtual public Worker { public: int level; WorkerA(int i, int j); }; WorkerA::WorkerA(int i, int j):Worker(i){ level = j; cout << "调用类WorkerA的构造函数!" << endl; } class WorkerB :virtual public Worker { public: int salary; WorkerB(int i, int j); }; WorkerB::WorkerB(int i, int j) :Worker(i) { salary = j; cout << "调用类WorkerB的构造函数!" << endl; } class WorkerC :public WorkerA, public WorkerB { public: int age; WorkerC(int i, int j1,int j2,int j3); void show(); }; WorkerC::WorkerC(int i, int j1, int j2, int j3) :WorkerA(i,j1),WorkerB(i,j2),Worker(i){ age = j3; cout << "调用类WorkerC的构造函数!" << endl; } void WorkerC::show() { cout << "工号:" << number << endl; cout << "级别:" << level << endl; cout << "薪水:" << salary << endl; cout << "年龄:" << age << endl; } void a() { WorkerC w(60, 10, 3000, 20); w.show(); } int main() { a(); getchar(); return 0; }
基类Worker类的构造函数! 调用类WorkerA的构造函数! 调用类WorkerB的构造函数! 调用类WorkerC的构造函数! 工号:60 级别:10 薪水:3000 年龄:20
参考博客: