一、继承的概念
- 继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加功能,这样产生新的类,称派生类。继承呈现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程。以前我们接触的复用都是函数复用,继承是类设计层次的复用。
- 一般设计的时候会将公共部分提取出来,放到父类中。
二、继承的定义
1、定义格式
class Student:public Person
{
public:
int _stuid;
int _major;
}
2、继承关系和访问限定符
3、继承基类成员访问方式的变化
- 基类的private成员在派生类中无论以什么方式继承都是不可见的,但是还是被集成到了派生类的对象中,但是语法上限制派生类对象不管在类里面还是类外面都不能去访问它。
- 基类private成员在派生类中是不能被访问,但是基类的protected成员在派生类中是可以被访问的,保护成员限定符是因为继承才出现的。
- 基类成员在子类访问方式:成员在基类的访问限定符和继承方式中小的那个。public > protected > private。
- 使用关键字class时默认的继承方式是private,使用struct时默认的继承方式是public,不过最好显示的写出继承方式。
- 在实际运用中一般使用都是public继承,几乎很少使用protetced/private继承,也不提倡使用protetced/private继承,因为protetced/private继承下来的成员都只能在派生类的类里面使用,实际中扩展维护性不强。
三、基类和派生类对象赋值转换
1、公有继承可以比拟成is-a关系,代表一种特殊的关系。
2、切片:派生类对象 可以赋值给 基类的对象 / 基类的指针 / 基类的引用。切片寓意把派生类中父类那部分切来赋值过去。
(1)这个过程我们认为是天然的,中间不产生临时对象,这个也叫父子类的赋值兼容规则。在拷贝的时候自定义类型还是会调用拷贝构造。
(2)基类对象不能赋值给派生类对象,因为派生类中会有一些成员和成员函数是基类中没有的。
3、基类的指针或者引用可以通过强制类型转换赋值给派生类的指针或者引用。但是必须是基类的指针是指向派生类对象时才是安全的。这里基类如果是多态类型,可以使用RTTI(RunTime Type Information)的dynamic_cast 来进行识别后进行安全转换。
四、继承中的作用域
1、在继承体系中基类和派生类都有独立的作用域。
2、父类和子类可以有同名成员,因为他们是独立作用域。默认情况直接访问的是子类,因为子类的同名成员隐藏了父类的同名成员。
(1)想访问父类的同名变量,可以指定作用域。
(2)对于基类和派生类中的同名函数成员,并不构成重载,因为二者并不在同一个作用域中。
(3) 继承中的同名函数成员,只要函数名相同就隐藏,不论参数和返回值
(4)建议不要写同名成员
五、派生类的默认成员函数
1、派生类的默认成员函数,只要用户不写,编译器就会默认生成一个。
2、 构造函数
(1)默认的派生类构造函数会调用基类的构造函数。
(2)自己实现构造函数的时候需要在初始化列表中调用基类的构造函数
(3)在派生类中成员分为内置类型、自定义类型、父类成员,父类成员是当成一个整体处理。
(4)构造的顺序是先父后子,因为初始化列表是按照声明的顺序走的。
3、拷贝构造函数
派生类的拷贝构造函数必须调用基类的拷贝构造完成基类的拷贝初始化
4、析构函数
(1)基类和派生类的析构函数会构成隐藏关系,由于多态的原因,析构函数统一会被处理成destructor。
(2)析构的顺序必须是先子后父,因为子类的析构可以析构父的,并且子类析构的时候可能访问父类的成员,先父后子会存在一定的风险。
(3)在子类中父类析构函数不用显式调用,子类析构函数结束的时候调用父类的析构函数,保证先子后父,因此只用写子类的析构函数即可。
5、派生类的operator=必须要调用基类的operator=完成基类的复制。
6、如何实现一个不能被继承的类
方法:构造函数私有化,私有的在派生类中不可见
六、继承于友元
友元关系不能继承,也就是说基类友元不能访问子类私有和保护成员。
七、继承于静态成员
基类定义了static静态成员,则整个继承体系里面只有一个这样的成员。无论派生出多少个子类,都只有一个static成员实例 。可以认为继承的是使用权,和成员函数的继承很像。
八、复杂的菱形继承及菱形虚拟继承
1、单继承与多继承
(1)单继承:单继承:一个子类只有一个直接父类时称这个继承关系为单继承。
(2)多继承:一个子类有两个或以上直接父类时称这个继承关系为多继承。
2、菱形继承
(1)菱形继承的问题:从下面的对象成员模型构造,可以看出菱形继承有数据冗余和二义性的问题。在Assistant的对象中Person成员会有两份。
(2)解决方法:虚拟继承可以解决菱形继承的二义性和数据冗余的问题
如上面的继承关系,在Student和Teacher的继承Person时使用虚拟继承,即可解决问题。需要注意的是,虚拟继承不要在其他地方去使用。
3、虚拟继承解决数据冗余和二义性的原理
下图是菱形虚拟继承的内存对象成员模型:这里可以分析出D对象中将A放到的了对象组成的最下面,这个A同时属于B和C,那么B和C如何去找到公共的A呢?这里是通过了B和C的两个指针,指向的一张表。这两个指针叫虚基表指针,这两个表叫虚基表。虚基表中存的偏移量。通过偏移量可以找到下面的A