C++类的继承(非常详细)
继承(inheritance)是 C++ 面向对象的三大特征之一,它使一个类可以从现有类中派生,而不必从头开始定义。
类继承的实质是以旧类为基础创建新类,新类包含旧类的数据成员和成员函数,且可添加新的数据成员和成员函数。其中,旧类被称为基类或父类,新类被称为派生类或子类。
例如,定义员工类 CEmployee,包含 3 个公有成员变量——员工 ID、员工姓名和所属部门:
再定义一个操作员类 COperator,使其继承自 CEmployee 类。此时,COperator 类会自动继承 CEmployee 类的员工 ID、员工姓名和所属部门信息,同时可以新增员工密码和登录方法。
两种使用继承的典型场景:
【实例】以公有方式继承员工类,得到操作员类,并新增成员信息,实现打卡功能。代码如下:
关于继承方式的几点说明如下:
1) 基类成员在派生类中的访问权限不能高于继承方式指定的权限。例如:
2) 基类中的 private 成员在派生类中不可见,即不能在派生类的成员函数中访问或调用。因此,如果希望基类成员能够被派生类继承且毫无障碍地使用,可将其声明为 public 或 protected 属性,只有那些不希望在派生类中使用的成员才声明为 private。
3) 如果希望基类成员既不向外暴露(不能通过对象访问),还能在派生类中使用,可将其声明为 protected 属性。protected 成员可被基类的所有派生类使用,这一性质将沿继承树无限向下传播。
需要注意,protected 类型在派生类定义时可以访问,用派生类声明的对象不能访问,即类体外不能访问。先来看一下 public 继承方式的访问示例:
下面是 private 继承方式的访问示例:
private 和 protected 继承方式会改变基类成员在派生类中的访问权限,导致继承关系复杂,所以实际开发中最常使用的是 public 继承方式。
事实上,创建派生类对象时,先调用基类构造函数,再调用派生类构造函数;释放派生类对象时则恰好相反,先调用派生类的析构函数,再调用基类的析构函数。
【实例】定义基类 CEmployee 和派生类 COperator,观察构造函数和析构函数的访问顺序,具体代码如下:
【实例】要求派生类显式调用基类的构造函数。具体代码如下:
事实上,基类的同名成员函数(包括重载函数)在派生类中会被屏蔽,因此通过派生类对象调用的是派生类成员函数。但使用域限定符“::”,显式地指定基类名,可以调用基类的同名成员函数。
【实例】基类和派生类中都定义了 OutputName() 成员函数,观察派生类对象的调用结果。代码如下:
如果用户想访问基类的 OutputName() 函数,需要在成员函数名前使用域限定符“::”,显式地指定基类名。例如:
注意,假设派生子类后,定义了一个基类指针,通过派生类的构造函数为其创建对象。例如:
使用 pWorker 对象调用 OutputName() 函数,如执行代码:
类继承的实质是以旧类为基础创建新类,新类包含旧类的数据成员和成员函数,且可添加新的数据成员和成员函数。其中,旧类被称为基类或父类,新类被称为派生类或子类。
C++类的继承
通过继承可以得到派生类,其定义形式如下:class 派生类名 : [继承方式] 基类名 { [访问控制修饰符:] [成员声明列表] }
- “:”运算符说明这是一个派生类,“:”后为继承的基类;
- 继承方式有 3 种,分别为 public(公有)、protected(保护)和 private(私有),默认为是 private;
- 访问控制修饰符也有 3 种,同样是 public、protected 和 private,默认为是 private;
- 成员声明列表中给出的是派生类中新增的成员变量和成员函数。
例如,定义员工类 CEmployee,包含 3 个公有成员变量——员工 ID、员工姓名和所属部门:
class CEmployee { public: int m_ID; // 员工 ID char m_Name[128]; // 员工姓名 char m_Depart[128]; // 所属部门 };
再定义一个操作员类 COperator,使其继承自 CEmployee 类。此时,COperator 类会自动继承 CEmployee 类的员工 ID、员工姓名和所属部门信息,同时可以新增员工密码和登录方法。
class COperator : public CEmployee { public: char m_Password[128]; // 新增公有成员变量 Password,表示员工密码 bool Login(); // 新增公有成员函数 Login(),表示登录方法 };
两种使用继承的典型场景:
- 待创建的类与当前类相似,只是多出若干成员变量或成员函数时,可以使用继承;
- 需要创建多个类,这些类拥有相似的成员变量或成员函数时,可以使用继承。将共同成员提取出来,定义为基类,然后通过继承快速得到多个类,不但节省代码,还便于后续进行修改。
【实例】以公有方式继承员工类,得到操作员类,并新增成员信息,实现打卡功能。代码如下:
#include<iostream> using namespace std; class CEmployee { public: int m_ID; char m_Name[128]; char m_Depart[128]; CEmployee() { memset(m_Name, 0, 128); memset(m_Depart, 0, 128); } void OutputName() { cout << "员工姓名:" << m_Name << endl; } }; class COperator : public CEmployee { public: char m_Password[128]; bool Login() { if (strcmp(m_Name, "MR") == 0 && strcmp(m_Password, "KJ") == 0) { cout << "登录成功!" << endl; return true; } else { cout << "登录失败!" << endl; return false; } } }; int main(int argc, char* argv[]) { COperator optr; strcpy(optr.m_Name, "MR"); strcpy(optr.m_Password, "KJ"); optr.Login(); optr.OutputName(); return 0; }程序运行结果为:
登录成功!
员工姓名:MR
C++继承后的可访问性
公有、私有、保护 3 种继承方式下,基类中的成员在派生类中的可访问性如下表所示。继承方式 | 基类 public 成员 | 基类 protected 成员 | 基类 private 成员 |
---|---|---|---|
public 继承方式 | public | protected | 不可见 |
protected 继承方式 | protected | protected | 不可见 |
private 继承方式 | private | private | 不可见 |
关于继承方式的几点说明如下:
1) 基类成员在派生类中的访问权限不能高于继承方式指定的权限。例如:
- 继承方式为 public 时,基类成员在派生类中的访问权限将保持不变;
- 继承方式为 protected 时,基类成员在派生类中的访问权限最高为 protected;
- 继承方式为 private 时,基类成员在派生类中的访问权限最高为 private。
2) 基类中的 private 成员在派生类中不可见,即不能在派生类的成员函数中访问或调用。因此,如果希望基类成员能够被派生类继承且毫无障碍地使用,可将其声明为 public 或 protected 属性,只有那些不希望在派生类中使用的成员才声明为 private。
3) 如果希望基类成员既不向外暴露(不能通过对象访问),还能在派生类中使用,可将其声明为 protected 属性。protected 成员可被基类的所有派生类使用,这一性质将沿继承树无限向下传播。
需要注意,protected 类型在派生类定义时可以访问,用派生类声明的对象不能访问,即类体外不能访问。先来看一下 public 继承方式的访问示例:
class CEmployee { public: void Output() { cout << m_ID << endl; cout << m_Name << endl; cout << m_Depart << endl; } private: int m_ID; char m_Name[128]; char m_Depart[128]; }; class COperator : public CEmployee { public: void Output() { cout << m_ID << endl; // 错误,不能引用基类的私有成员 m_ID cout << m_Name << endl; // 错误,不能引用基类的私有成员 m_Name cout << m_Depart << endl; // 错误,不能引用基类的私有成员 m_Depart cout << m_Password << endl; // 正确,可引用自己的私有成员 m_Password } private: char m_Password[128]; bool Login(); };
下面是 private 继承方式的访问示例:
class CEmployee { public: void Output() { cout << m_ID << endl; cout << m_Name << endl; cout << m_Depart << endl; } public: int m_ID; protected: char m_Name[128]; private: char m_Depart[128]; }; class COperator : private CEmployee { public: void Output() { cout << m_ID << endl; // 正确,可引用基类的公有成员 m_ID cout << m_Name << endl; // 正确,可引用基类的保护成员 m_Name cout << m_Depart << endl; // 错误,不能引用基类的私有成员 m_Depart cout << m_Password << endl; // 正确,可引用自己的私有成员 m_Password } private: char m_Password[128]; bool Login(); };
private 和 protected 继承方式会改变基类成员在派生类中的访问权限,导致继承关系复杂,所以实际开发中最常使用的是 public 继承方式。
C++构造函数的访问顺序
基类和派生类中都有构造函数和析构函数,那么在创建派生类对象时是先构造基类,还是先构造派生类?同样,在释放派生类对象时是先释放基类,还是先释放派生类?事实上,创建派生类对象时,先调用基类构造函数,再调用派生类构造函数;释放派生类对象时则恰好相反,先调用派生类的析构函数,再调用基类的析构函数。
【实例】定义基类 CEmployee 和派生类 COperator,观察构造函数和析构函数的访问顺序,具体代码如下:
#include<iostream> using namespace std; class CEmployee { public: int m_ID; char m_Name[128]; char m_Depart[128]; CEmployee() { cout << "CEmployee类构造函数被调用" << endl; } ~CEmployee() { cout << "CEmployee类析构函数被调用" << endl; } }; class COperator : public CEmployee { public: char m_Password[128]; COperator() { strcpy(m_Name, "MR"); cout << "COperator类构造函数被调用" << endl; } ~COperator() { cout << "COperator类析构函数被调用" << endl; } }; int main(int argc, char* argv[]) { COperator optr; return 0; }运行结果为:
CEmployee类构造函数被调用
COperator类构造函数被调用
COperator类析构函数被调用
CEmployee类析构函数被调用
C++派生类显式调用基类构造函数
创建派生类对象时,会自动调用基类默认的构造函数。若想使用基类带参数的构造函数,则需要使用显式调用的方式。【实例】要求派生类显式调用基类的构造函数。具体代码如下:
#include<iostream> using namespace std; class CEmployee { public: int m_ID; char m_Name[128]; char m_Depart[128]; CEmployee(char name[]) { strcpy(m_Name, name); cout << m_Name << "调用了CEmployee类带参数的构造函数" << endl; } CEmployee() { strcpy(m_Name, "MR"); cout << m_Name << "CEmployee类无参构造函数被调用" << endl; } ~CEmployee() { cout << "CEmployee类析构函数被调用" << endl; } }; class COperator : public CEmployee { public: char m_Password[128]; COperator(char name[]) : CEmployee(name) { cout << "COperator类构造函数被调用" << endl; } COperator() : CEmployee("JACK") { cout << "COperator类构造函数被调用" << endl; } ~COperator() { cout << "COperator类析构函数被调用" << endl; } }; int main(int argc, char* argv[]) { COperator optr1; COperator optr2("LaoZhang"); return 0; }程序运行结果为:
JACK调用了CEmployee类带参数的构造函数
COperator类构造函数被调用
LaoZhang调用了CEmployee类带参数的构造函数
COperator类构造函数被调用
COperator类析构函数被调用
CEmployee类析构函数被调用
COperator类析构函数被调用
CEmployee类析构函数被调用
C++派生类屏蔽基类的同名成员函数
如果派生类中定义了一个和基类成员同名的成员函数,那么派生类对象调用成员时,调用的是基类成员函数,还是派生类成员函数呢?事实上,基类的同名成员函数(包括重载函数)在派生类中会被屏蔽,因此通过派生类对象调用的是派生类成员函数。但使用域限定符“::”,显式地指定基类名,可以调用基类的同名成员函数。
【实例】基类和派生类中都定义了 OutputName() 成员函数,观察派生类对象的调用结果。代码如下:
#include<iostream> using namespace std; class CEmployee { public: int m_ID; char m_Name[128]; char m_Depart[128]; CEmployee() {} ~CEmployee() {} void OutputName() { cout << "调用CEmployee类的OutputName成员函数" << endl; } }; class COperator : public CEmployee { public: char m_Password[128]; void OutputName() { cout << "调用COperator类的OutputName成员函数" << endl; } }; int main(int argc, char* argv[]) { COperator optr; optr.OutputName(); // 调用派生类 COperator 的 OutputName() 成员函数 return 0; }程序运行结果为:
调用COperator类的OutputName成员函数
可见,代码“optr.OutputName();”调用的是派生类 COperator 的 OutputName() 成员函数,而不是基类 CEmployee 的 OutputName() 成员函数。如果用户想访问基类的 OutputName() 函数,需要在成员函数名前使用域限定符“::”,显式地指定基类名。例如:
optr.CEmployee::OutputName(); // 调用 CEmployee 类的 OutputName() 成员函数
注意,假设派生子类后,定义了一个基类指针,通过派生类的构造函数为其创建对象。例如:
CEmployee *pWorker = new COperator(); // 定义 CEmployee 类指针,调用派生类的构造函数
使用 pWorker 对象调用 OutputName() 函数,如执行代码:
pWorker->OutputName();则调用的是 CEmployee 类的 OutputName() 函数。编译器对 OutputName() 成员函数是静态绑定,即根据对象定义时的类型确定调用哪个类的成员函数。pWorker 属于 CEmployee 类型,因此调用的是 CEmployee 类的 OutputName() 成员函数。