继承的概念及定义
概念
继承(inheritance)机制是面向对象程序设计使代码复用的最重要的手段,它允许在保持原有类特性的基础上进行扩展,增加新特性,从而生成新的类,称为派生类。继承呈现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程。
继承定义
定义格式:class Child : Parent
{…};
继承方式:分为public继承、protected继承和private继承。
类成员/继承方式 | public继承 | protected继承 | private继承 |
---|---|---|---|
基类的public成员 | 派生类的public成员 | 派生类的protected成员 | 派生类的private成员 |
基类的protected成员 | 派生类的protected成员 | 派生类的protected成员 | 派生类的private成员 |
基类的private成员 | 在派生类中不可见 | 在派生类中不可见 | 在派生类中不可见 |
注意:
- 基类的private成员在派生类中虽然不可见,但确实被继承到了派生类中。
- 使用关键字class时默认的继承方式是private,使用struct时默认的继承方式是public。
- 实际应用中一般使用public继承,几乎很少使用protected/private继承,也不提倡使用protected/private继承,因为protected/private继承下来的成员都只能在派生类的类里面使用,实际中扩展维护性不强。
基类和派生类对象赋值
- 派生类对象可以赋值给基类对象/基类的指针/基类的引用。
- 基类对象不能赋值给派生类对象
继承中的作用域
- 在继承体系中基类和派生类都有独立的作用域。
- 子类和父类中有同名成员,子类成员将屏蔽父类对同名成员的直接访问,这种情况叫做隐藏,也叫重定向。(在子类成员函数中,可以使用 基类::基类成员 显式访问)
- 如果是成员函数的隐藏,只需要函数名相同就构成隐藏。
- 注意实际中在继承体系里面最好不要定义同名的成员。
派生类的默认成员函数
- 派生类的构造函数必须调用基类的构造函数来初始化基类的那一部分成员。如果基类没有默认的构造函数,则必须在派生类构造函数的初始化列表中显式调用。
- 派生类的拷贝构造函数必须要调用基类的拷贝构造完成基类部分成员的拷贝初始化。
- 派生类的operator=必须要调用基类的operator=完成基类的复制。
- 派生类的析构函数会在被调用完成后自动调用基类的析构函数清理基类成员。因为这样才能保证派生类对象先清理派生类成员再清理基类成员的顺序。
- 派生类对象初始化先调用基类构造再调用派生类构造。
- 派生类对象析构清理先调用派生类析构再调基类的析构。
继承与友元
友元关系不能被继承,也就是说基类友元不能访问子类私有和保护成员。
继承与静态成员
基类定义了static静态成员,则整个继承体系里面只有一个这样的成员。无论派生出多少个子类,都只有一个static成员实例。
菱形继承问题
菱形继承是多继承的一种特殊情况,即派生类的多个基类共同继承了同一个基类。派生类中有多个其基类的基类成员,在派生类中访问这些基类成员时会产生二义性问题。
虚拟继承可以解决菱形继承的数据冗余和二义性问题。在派生类的多个基类继承其基类时使用关键词virtual:
class Parent : virtual public Prandparents
{};
虚继承原理
虚继承的对象模型为一个虚基表和成员内存空间。虚基表中存放的是指针,指向从基类继承下来的基类成员相对于该类对象首字节地址的偏移量,根据这个偏移量,类对象就能找到基类这一部分成员的地址,从而进行访问。菱形继承中,派生类多继承的多个基类共同虚继承同一个基类时,每个派生类多继承的多个基类中的虚基表存有一个指向它们共同基类成员的指针,所以它们从其共同基类中继承下来的成员在对象模型中则只有一份,这样数据冗余和二义性问题都解决了。