C++中的继承是类与类之间的关系。继承(Inheritance)可理解为一个类从另一个类获取成员变量和成员函数的过程。在C++中,派生(Derive)和继承是一个概念,只是参照物不同。被继承的类称为父类或基类,继承的类称为子类或派生类。“子类”和“父类”通常放在一起称呼,“基类”和“派生类”通常放在一起称呼。派生类除了拥有基类的成员,还可以定义自己的新成员,以增强类的功能。
1、简介
1.1 使用继承的典型场景
①创建的新类与现有的类相似。可以减少代码量,而且新类会拥有基类的所有功能。
②需要创建多个类,它们拥有很多相似的成员变量或成员函数时。可以将这些类的共同成员提取出来,定义为基类,从基类继承,既可以节省代码,也方便后续修改成员。
继承过来的成员,可以通过子类对象进行访问。
1.2 继承的一般语法
class 派生类名:[继承方式] 基类名{
派生类新增加的成员
};
2、C++继承中的三种方式
继承方式限定了基类成员在派生类中的访问权限,默认是private(私有的)。类成员的访问权限由高到低依次为public --> protected --> private,public 成员可以通过对象来访问,private 成员不能通过对象访问;protected 成员和 private 成员类似,也不能通过对象访问。但是当存在继承关系时,protected 和 private 就不一样:基类中的 protected 成员可以在派生类中使用,而基类中的 private 成员不能在派生类中使用。
2.1 public、protected、private 指定继承方式
2.1.1 public继承方式
- 基类中所有 public 成员在派生类中为 public 属性;
- 基类中所有 protected 成员在派生类中为 protected 属性;
- 基类中所有 private 成员在派生类中不能使用。
2.1.2 protected继承方式
- 基类中的所有 public 成员在派生类中为 protected 属性;
- 基类中的所有 protected 成员在派生类中为 protected 属性;
- 基类中的所有 private 成员在派生类中不能使用。
2.1.3 private继承方式
- 基类中的所有 public 成员在派生类中均为 private 属性;
- 基类中的所有 protected 成员在派生类中均为 private 属性;
- 基类中的所有 private 成员在派生类中不能使用。
从上述三种继承方式可知:
①基类成员在派生类中的访问权限不得高于继承方式中指定的权限。即是继承方式中的 public、protected、private 是用来指明基类成员在派生类中的最高访问权限的。
②不管继承方式如何,基类中的 private 成员在派生类中始终不能使用(不能在派生类的成员函数中访问或调用)。基类的 private 成员是能够被继承的,并且(成员变量)会占用派生类对象的内存,它只是在派生类中不可见。
③若想基类的成员能够被派生类继承并且毫无障碍地使用,那么这些成员只能声明为 public 或 protected;只有那些不希望在派生类中使用的成员才声明为 private。
④若想基类的成员既不向外暴露(不能通过对象访问),还能在派生类中使用,那么只能声明为 protected。
继承方式/基类成员 | public成员 | protected成员 | private成员 |
---|---|---|---|
public继承 | public | protected | 不可见 |
protected继承 | protected | protected | 不可见 |
private继承 | private | private | 不可见 |
实际开发中大多使用public继承。在派生类中访问基类private成员的唯一方法就是借助基类的非 private 成员函数,如果基类没有非private成员函数,那么该成员在派生类中将无法访问。
2.2 改变访问权限
使用using关键字可以改变基类成员在派生类中的访问权限,但是using只能改变基类中public和protected成员的访问权限,不能改变private成员的访问权限,因为基类中private成员在派生类中是不可见的,根本不能使用,所以基类中的private成员在派生类中无论如何都不能访问。用法大概如下:
1 //基类
2 class CXXX{
3 protected: //或pubic
4 char *m_name;
5 int m_age;
6 ...
7 };
8
9
10 //派生类Cxxx
11 class Cxxx: public CXXX{
12 public:
13 using CXXX::m_name; //将protected改为public
14 using CXXX::m_age; //将protected改为public
15 float m_score;
16 };
3、继承时的名字遮蔽问题
遮蔽,就是在派生类中使用该成员(包括在定义派生类时使用,也包括通过派生类对象访问该成员)时,实际上使用的是派生类新增的成员,而不是从基类继承来的。这种情况发生在派生类和基类中存在同名的成员时。注意:基类成员和派生类成员的名字一样时会造成遮蔽,但是对于成员函数而言,不管函数的参数如何,只要名字一样就会造成遮蔽。当创建派生类对象时,若想调用基类的同名成员,需要加上类名和域解析符,如:派生类对象.基类类名::成员;
1 Man.Human::people;
由上述可知,基类成员函数和派生类成员函数不会构成重载,如果派生类有同名函数,那么就会遮蔽基类中的所有同名函数,不管它们的参数是否一样。
4、基类和派生类的构造函数
基类的成员函数可以被继承,可以通过派生类的对象访问,但这仅仅指的是普通的成员函数,类的构造函数不能被继承。因为继承了也没用,不能成为派生类的构造函数,也不能成为其普通成员函数。在派生类中,对继承过来的成员变量的初始化工作也要由派生类的构造函数完成,但是大部分基类都有private属性的成员变量,它们在派生类中无法访问,更不能使用派生类的构造函数来初始化。对于这种情况,可以在派生类的构造函数中调用基类的构造函数。注意:只能将基类构造函数的调用放在函数头部,即可放在参数初始化表的前后,不能放在函数体中。顺序前后没有影响,因为派生类构造函数总是先调用基类构造函数再执行其他代码。函数头部是对基类构造函数的调用,而不是声明,所以括号里的参数是实参。
4.1 构造函数的调用顺序
由上可知,基类构造函数总是被优先调用,这说明创建派生类对象时,会先调用基类构造函数,再调用派生类构造函数,即构造函数的调用顺序为:按照继承的层次自顶向下、从基类再到派生类。注意:派生类构造函数中只能调用直接基类的构造函数,不能调用间接基类的。因为当存在多个派生类时,会间接调用最顶层的基类,因此没有必要再显式调用最终基类的构造函数,只会增加工作,浪费CPU时间和内存。
语法规定,通过派生类创建对象时必须要调用基类的构造函数,即定义派生类构造函数时最好指明基类构造函数;不指明就会调用基类的默认构造函数;如果没有默认构造函数,就会编译失败。
5、基类和派生类的析构函数
析构函数和构造函数一样也不能被继承。与构造函数不同的是,在派生类的析构函数中不用显式地调用基类的析构函数,因为每个类只有一个析构函数,编译器知道如何选择,无需程序员干涉。同时析构函数的调用顺序和构造函数的调用顺序刚好是相反的:即先执行派生类析构函数,再执行基类析构函数。
6、多重继承
派生类可以只有一个基类,称为单继承(Single Inheritance)。但是C++也支持多继承(Multiple Inheritance),即一个派生类可以有两个或多个基类。缺点:容易让代码逻辑复杂、思路混乱。多继承的语法也很简单,将多个基类用逗号隔开即可:
1 class D: public A, private B, protected C{ 2 //类D新增加的成员 3 }
6.1 多继承下的构造函数
多继承形式下的构造函数和单继承形式基本相同,只是要在派生类的构造函数中调用多个基类的构造函数。
1 D(形参列表): A(实参列表), B(实参列表), C(实参列表){ 2 //其他操作 3 }
基类构造函数的调用顺序和和它们在派生类构造函数中出现的顺序无关,而是和声明派生类时基类出现的顺序相同。所有D类构造函数中的基类初始化表顺序不一定是上述顺序。多继承形式下析构函数的执行顺序和构造函数的执行顺序相同。可自行编写代码进行测试。
6.2 命名冲突
当两个或多个基类中有同名的成员时,若直接访问该成员就会产生命名冲突,编译器不知道使用哪个基类的成员。此时需要在成员名字前面加上类名和域解析符::
,显式地指明到底使用哪个类的成员,消除二义性。
1 Cxxx::people;