1. 基类和派生类
c++中的继承:从既有类(基类)产生新类(派生类)
1.1 派生类的生成步骤
- 吸收基类的成员
- 改造基类的成员
- 新增自己的成员
1.2 派生类的局限
- 数据成员可以完全吸收,但成员函数不能
- 不能被继承:构造、析构函数、基类重载的operator=/new/delete函数、友元关系
1.3 派生方式
派生方式 | 基类中访问权限 | 派生类访问权限 | 派生类对象访问权限 |
public | public | public | 可以直接访问 |
protected | protected | 不可以直接访问 | |
private | 不可以直接访问 | ||
protected | public | protected | |
protected | protected | ||
private | 不可以直接访问 | ||
private | public | private | |
protected | private | ||
private | 不可以直接访问 |
总结:无论哪种派生方式,都不能访问基类的私有成员(体现封装性);无论哪种派生方式,都可直接访问基类的非私有成员;只有公有派生类对象可以访问基类的公有成员。
2. 派生类对象的构造和销毁
2.1 构造的四种情况
派生类必须重新定义构造函数和析构函数
① 派生类显式定义了构造函数,而基类没有显式定义构造函数
调用派生类构造函数,自动调用基类的缺省的构造函数
② 派生类没有显式定义构造函数,而基类显式定义了构造函数
不合法,基类必须有默认的无参构造函数 ???
③ 派生类显式定义了构造函数,基类有默认构造函数
默认调用基类的无参构造函数,如果想调用有参构造函数,则必须在派生类构造函数的初始化列表中显式调用。
④ 派生类和基类都定义了构造函数,但基类没有无参构造函数
派生类必须在构造函数的初始化列表中显式调用基类的构造函数。
(×) 创建派生类对象时先调用基类构造函数再调用派生类构造函数
(√) 创建派生类对象时先调用派生类构造函数,由于存在继承关系,此时会初始化从基类中吸收的数据成员,继而调用基类构造函数,再执行其他操作。
2.2 构造函数的调用顺序
- 系统在调用拷贝构造函数时自动开辟对象所需空间
- 调用基类构造函数 --- 初始化基类成员
- 调用派生类对象成员、const成员或引用成员的初始化
- 执行派生类构造函数的函数体
2.3 析构函数的调用顺序
- 调用派生类析构函数
- 调用派生类中成员对象的析构函数
- 自动调用基类的析构函数
3. 多基继承
3.1 继承方式
- 在创建派生类对象时,基类构造函数执行顺序与其在派生类初始化列表中的顺序无关,至于继承顺序有关。
- 当派生类继承基类时,必须在每一个派生类前加继承方式,否则默认为私有继承。
3.2 两个问题
① 成员同名的二义性问题 --- 多个基类中存在同名成员
解决:调用时使用类名+作用域限定符
② 菱形继承的二义性问题 --- 在多条继承路径上有一个共同的基类
解决:使用虚拟继承
4. 基类和派生类的相互转换
“类型适应” 两种类型之间的关系; A类适应B类: A类的对象能直接用于B类对象能应用的场合
4.1 向上转型(派生类=>基类)
- 可以把派生类对象赋值给基类对象
- 可以把基类的引用绑定到派生类对象
- 可以声明基类的指针指向派生类对象
4.2 向下转型(基类=>派生类)
C++强制转换 --- static_cast关键字
Base base2(10);
Derived derived2(20, 30);
Derived *pderived2 = static_cast<Derived *>(&base2);//不安全的向下转型
Base *pbase3 = &derived2;
Derived *pderived3 = static_cast<Derived *>(pbase3);//安全的向下转型
5. 派生类对象间的复制控制
① 用户定义了基类拷贝构造函数,没有定义派生类拷贝构造函数
基类部分执行基类的拷贝构造函数,派生类部分执行缺省的拷贝构造函数
② 用户重载了基类的赋值运算符函数,没有重载派生类赋值运算符函数
基类部分执行基类的赋值运算符函数,派生类部分执行缺省的赋值运算符函数
③ 用户定义了派生类的拷贝构造函数或重载了派生类的赋值运算符函数
需要在派生类的拷贝构造函数或赋值运算符函数中显式调用派生类的拷贝构造函数和赋值运算符函数