类的继承与派生
1. 基类与派生类
什么是继承
- 在一个已存在类的基础上建立一个新的类,已存在的类称为基类(父类),新生成的类称为派生类(子类)
为什么要有继承
- 类的对象各自封闭,若没有继承,会出现大量重复代码
- 继承可以吸收现有类的数据和函数来创建新类,并添加新成员增强此类,节约开发时间
继承的种类
- 单一继承:只有一个基类
-
多重继承:有两个及以上的基类
在这里插入图片描述
什么是派生
- 从已有的父类产生一个新的子类称为类的派生。派生类继承基类的所有数据成员和成员函数,且可以对成员作必要的增加或调整,定义自己的新特性
派生的种类
- 单级派生
- 多级派生
基类与派生类的关系
- 基类是对派生类的抽象,派生类是对基类的具体化
- 派生类是基类的简单组合,可以把多重继承看作是多个单一继承的简单组合
2. 派生类的定义
派生类的定义形式
class 派生类名:类派生列表{
成员列表
};
说明:
除增加类派生列表外,派生类的定义与类定义并无区别
类派生列表指定了一个或多个基类,形式如下:
访问标号 基类名
访问标号表示继承方式,类派生列表可以指定多个基类,中间用逗号分隔
3. 派生类的构成
派生的说明
- 用作基类的类必须是已定义的
- 声明一个派生类,声明类名即可
- 一个派生类成员包含两部分:一部分从基类继承得到,另一部分是自己定义的新成员
- 友元关系不能继承
- 如果基类定义了静态成员,则整个继承层次只有一个这样的成员,即无论从基类派生出多少个派生类,每个静态成员只有一个实例
- 静态成员遵循常规访问控制
- 一般地可以使用作用域运算符(::)也可以使用对象成员运算符(.)或指针成员引用运算符(->)访问静态成员
如何设计一个派生类
- 从基类接收成员
- 调整基类成员的访问
- 修改基类成员
- 在定义派生类时增加新的成员
#include <iostream>
using namespace std;
class Base{
private:
int b_number;
public:
Base(){}
Base(int i):b_number(i) { }
int get_number() {return b_number;}
void print() { cout << b_number << endl;}
};
class Derived : public Base
{
private:
int d_number;
public:
Derived(int i, int j):Base(i),d_number(j1){ }
void print()
{
cout << get_number() << " ";
cout << d_number << endl;
}
};
int main()
{
Base a(2);
Derived b(3, 4);
cout << "a is ";
a.print();
cout << "b is ";
b.print();
}
4. 类的保护成员
类的三种访问者
- 类用户
- 类成员
- 派生类成员
为什么要有protected这种访问属性
- 派生类通常需要访问基类成员,为了允许这种访问而仍然禁止外部对基类的一般访问,使用protected访问标号,类的protected部分仍然不能被类用户访问,但可以被派生类访问
protected访问权限说明
- 派生类可以访问基类的protected和public属性成员,不能访问任何privat属性成员
5. 派生类的访问权限
继承方式决定了基类成员在派生类中的访问属性
- 公有继承:基类的公有成员和保护成员在派生类中保持原有属性,派生类的成员和友元可访问二者,基类的私有成员仍为基类私有,派生类中不可访问
- 私有继承:基类公有成员和保护成员在派生类中变为私有属性,基类私有成员在派生类中不可访问
- 保护继承:基类公有成员和保护成员在派生类中变为保护属性,基类私有成员在派生类中不可访问
说明:保护继承和私有继承在实际编程中极少使用
6. 赋值兼容规则
赋值兼容规则是什么意思
- 赋值兼容规则是指在需要基类对象的任何地方,都可以用公有派生类的对象替代。通过公有继承,派生类得到了基类中除了构造函数和析构函数的所有成员。
替代所指的情况:
- 派生类的对象可以赋值给基类对象
- 派生类的对象可以初始化基类的引用
- 派生类的对象的地址可以赋给指向基类的指针
7. 派生类的构造函数和析构函数
为什么要有派生类的构造函数和析构函数
- 派生类没有把基类的构造函数和析构函数继承下来,因此继承的基类成员初始化的工作需要由派生类的构造函数和析构函数承担
派生类的构造函数如何定义
派生类名(形参列表):基类名(基类构造函数实参列表),派生类初始化列表
{
派生类初始化函数体
}
派生类构造函数调用顺序:
- 调用基类构造函数
- 执行派生类初始化列表
- 执行派生类初始化函数体
组合关系的派生类的构造函数
-
假定派生类A和类B的关系式组合关系,类A中有类B的子对象,那么以下情况编译器会自动调用B的构造函数进行初始化,而不用类A显式的调用构造函数
- 类B有默认构造函数
- 类B有参数全是默认参数的构造函数
- 类B有无参数的构造函数
-
显式调用形式如下
类名(形参表):子对象名(子对象构造函数实参列表),类初始化列表 { 类初始化函数体 }
调用顺序:
- 调用基类构造函数
- 调用子对象构造函数
- 执行派生类初始化列表
- 执行派生类初始化函数体
派生类的析构函数
派生类中可以根据需要定义自己的析构函数,用来对派生类中所增 加的成员进行清理工作。基类的清理工作仍然由基类的析构函数负责。
8. 多重继承
多重继承派生类如何定义
class 派生类名:访问标号1 基类名1, 访问标号2 基类名2, ...{
成员列表//类体
};
多重继承派生类的构造函数如何定义
派生类名(形参表):基类名1(基类1构造函数实参表),
基类名2(基类2构造函数实参表),
...,
子对象名1(子对象1属类构造函数实参表),
派生类初始化列表
{
派生类初始化函数体
}
调用顺序:
- 调用基类构造函数
- 调用子对象构造函数
- 执行派生类初始化列表
- 执行派生类初始化函数体
二义性问题
-
多重继承是,多个基类可能出现同名的成员,在派生类中如果使用一个表达式的含义能解释为可以访问的多个基类的成员,则这种对基类成员的访问就是不确定的,称这种访问具有二义性
-
可以使用成员名限定消除二义性
对象名.基类名::成员名 对象指针名->基类名::成员名
名字支配规则
基类的成员和派生类新增的成员都具有类 作用域,二者的作用域是不同的:基类在外层,派生类在内层。 如果派生类声明了一个和基类成员同名的新成员,派生的新成员就 覆盖了基类同名成员,直接使用成员名只能访问到派生类的成员。
虚基类
- C++提供虚基类(virtual base class)的机制,使得在继承间接共 同基类时只保留一份成员。
-
定义形式
class 派生类名:virtual 访问标号 虚基类名,...{ 成员列表 };
-
虚基类的初始化
如果在虚基类中定义了带参数的构造函数,而且没有定义默认构造 函数,则在其所有派生类(包括直接派生和间接派生)中,都要通过 构造函数的初始化表对虚基类进行初始化。