类的继承与派生
类的继承,是新的类从已有类那里得到已有的特性
从已有类产生心类的过程就是类的派生
原有的类称为基类或父类,产生的心类称为派生类或子类
派生类的定义
class 派生类名:继承方式 基类名1,继承方式 基类名2,……,继承方式 基类名n
{
派生类成员声明;
}
例:
class Derived:public Base1,private Base2
{
public:
Derived();
~Derived();
}
一个派生类,可以同时有多个基类,这种情况称为多继承
一个派生类只有一个直接基类的情况,称为单继承
派生类成员是指除了从基类继承来的所有成员之外,新增加的数据和函数成员
派生类生成过程
吸收基类成员
将基类的成员全盘接收,派生类就包含了全部基类中除构造和析构函数之外的所有成员
改造基类成员
1.基类成员的访问控制问题,主要依靠派生类定义时的继承方式来控制
2.另一个是对基类数据或函数成员的覆盖或隐藏。
如果派生类声明了一个和某基类成员同名的新成员(如果是成员函数,则参数表也要相同,参数不同的情况属于重载),派生类的新成员就隐藏了外层同名成员。
这时,在派生类中或派生类的对象,直接使用成员名就只能访问到派生类中声明的同名成员,这称做同名隐藏
添加新的成员
根据实际需要,给派生类添加适当的数据和函数成员,在派生类中加入新的构造和析构函数
访问控制
公有继承
私有继承
私有继承之后,基类的成员再也无法在以后的派生类中直接发挥作用,实际是相当于中止了基类功能的继续派生,出于这种原因,一般情况下私有继承的使用比较少。
保护继承
基类的公有成员和保护成员都以保护成员的身份出现在派生类中,而基类的私有成员不可直接被访问
在直接派生类中,(保护继承和私有继承)所有成员的访问属性都是完全相同的
如果派生类作为新的基类继续派生时,二者的区别就出现了:
私有继承在进一步派生后,基类的成员无法被直接访问
而保护继承在进一步派生后,仍然可以
关于保护成员
类中的保护成员有可能被它的派生类访问,但是不可以被其他外部使用者访问
总结
以上,都无法直接访问基类的私有成员
所以,可以推理——公有继承,将基类中的公有和保护成员的访问属性原封不动地继承下来,作为本身(派生类)的相应访问属性的成员,而私有成员不能被访问(若继续派生,则以此类推)
其他的同理
实际上私有成员还是被继承了,但是——可以理解成没被继承(纯粹为了便于理解)
类型兼容原则
class B{……};
class D:public B{……};
B b1,*pb1;
D d1;
派生类的构造和析构函数
构造函数
构造派生类的对象时,就要对基类的成员对象和新增成员对象对象进行初始化
派生类的构造函数指负责对派生类新增的成员进行初始化,对所有从基类继承下来的成员,其初始化工作还是由基类的构造函数完成
如果对基类初始化时,需要调用基类的带有形参表的构造函数时,派生类就必须声明构造函数
复制构造函数
Derived::Derived(const Derived &v):Base(v){……}
Base(v)用到了类型兼容规则
析构函数
声明方法与没有继承关系的类中析构函数的声明方法完全相同,只要在函数体中负责把派生类新增的非对象成员的清理工作做好就够了,系统胡izj调用基类即对象成员的析构函数来对基类及对象成员进行清理
执行次序和构造函数正好完全相反
派生类成员的标识与访问
作用域分辨符
隐藏规则
如果派生类中声明了与基类成员函数同名的新函数,即使函数的参数表不同,从基类继承的同名函数的所有重载形式也都会被隐藏
如果某派生类的多个基类拥有同名的成员,同时,派生类又新增这样的同名成员,在这种情况下,派生类成员将隐藏所有基类的同名成员。但是如果派生类没有声明同名成员,这时,从不同基类继承过来的成员具有相同的名称,同时具有相同的作用域,系统无法判断到底是调用哪个基类的成员,这时必须通过基类名和作用域分辨符来标识成员。
例
d.Base1::var=2;
d.Base1::fun();
p->Base2::var=3;
p->Base2::fun();
using
特殊情况
虚基类
相比之下:
作用域分辨符可以存放不同的数据、进行不同的操作
而虚基类只维护一份成员副本,使用更为简洁,内存空间更为节省
虚基类及其派生类构造函数
若虚基类声明又非默认形式(即带形参的)构造函数,并且没有声明默认形式的构造函数时,在整个继承关系中,直接或间接继承虚基类的所有派生类,都必须在构造函数的成员初始化表中列出对虚基类的初始化
程序例子
建立一个对象,如果这个对象中含有从虚基类继承来的成员,则虚基类的成员是由最远派生类的构造函数通过调用虚基类的构造函数进行初始化。只有最远派生类的构造函数会调用虚基类的构造函数,该派生类的其他基类对虚基类构造函数的调用会被自动忽略
这里,Derived是最远派生类,会忽略Base1和Base2类(如果不忽略,就是调用3次对虚基类的构造函数)