本节书摘来自异步社区出版社《C++多线程编程实战》一书中的第1章,第1.6节,作者: 【黑山共和国】Milos Ljumovic(米洛斯 留莫维奇),更多章节内容可以访问云栖社区“异步社区”公众号查看。
1.6 解释继承、重载和覆盖
继承是OOP中非常重要的特性。继承至少关系到两个类(或更多类):如果B类是某一种A类,那么B类的对象就拥有与A类对象相同的属性。除此之外,B类也可以实现新的方法和属性,以代替A类相应的方法和属性。
准备就绪
确定安装并运行了Visual Studio。
操作步骤
现在,执行以下步骤来修改前面的示例。
1.创建一个默认控制台应用程序,命名为InheritanceTest
。
2.打开InheritanceTest.cpp
文件,输入下面的代码:
#include "stdafx.h"
#include <iostream>
using namespace std;
class CPerson
{
public:
CPerson(int iAge, char* sName)
{
this->iAge = iAge;
strcpy_s(this->sName, 32, sName);
}
virtual char* WhoAmI()
{
return "I am a person";
}
private:
int iAge;
char sName[32];
};
class CWorker : public CPerson
{
public:
CWorker(int iAge, char* sName, char* sEmploymentStatus)
: CPerson(iAge, sName)
{
strcpy_s(this->sEmploymentStatus, 32, sEmploymentStatus);
}
virtual char* WhoAmI()
{
return "I am a worker";
}
private:
char sEmploymentStatus[32];
};
class CStudent : public CPerson
{
public:
CStudent(int iAge, char* sName, char* sStudentIdentityCard)
: CPerson(iAge, sName)
{
strcpy_s(this->sStudentIdentityCard, 32, sStudentIdentityCard);
}
virtual char* WhoAmI()
{
return "I am a student";
}
private:
char sStudentIdentityCard[32];
};
int _tmain(int argc, TCHAR* argv[])
{
CPerson cPerson(10, "John");
cout << cPerson.WhoAmI() << endl;
CWorker cWorker(35, "Mary", "On wacation");
cout << cWorker.WhoAmI() << endl;
CStudent cStudent(22, "Sandra", "Phisician");
cout << cStudent.WhoAmI() << endl;
return 0;
}```
示例分析
我们创建了一个新的数据类型`CPerson`来表示人。该类型用`iAge`和`sName`作为特征来描述一个人。如果还需要其他数据类型来表示工人或学生,就可以用OOP提供的一个很好的机制——继承来完成。工人首先是人,然后还有一些其他特征,我们用下面的代码把`CPerson`扩展为`CWorker`:
`class CWorker : public CPerson`
也就是说,`CWorker`从`CPerson`类继承而来。`CWorker`类不仅具有基类`CPerson`的所有特征和方法,还有一个对工人而言非常重要的特征`sEmploymentStatus`。接下来,我们还要创建一个学生数据类型。除了年龄和名字,学生也还具有其他特征。同理,我们用下面的代码把`CPerson`扩展为`CStudent`:
`class CStudent : public CPerson`
声明一个对象时,要调用它的构造函数。这里要注意的是:声明一个派生类的对象时,先调用基类的构造函数,后调用派生类的构造函数。如下代码所示:
CWorker( int iAge, char sName, char sEmploymentStatus )
: CPerson( iAge, sName )
{
strcpy_s( this->sEmploymentStatus, 32, sEmploymentStatus );
}`
注意看CWorker
构造函数的原型,其形参列表后面有一个:(冒号),后面调用的是基类的构造函数,如下代码所示。在创建CPerson
时,需要两个参数iAge
和sName
:
CPerson(iAge, sName)
调用析构函数的顺序要反过来,即先调用派生类的析构函数,后调用基类的析构函数。
一图胜千言,CPerson
、CWorker
和CStuden
t类对象分别如图1.2所示。
图1.2 CPerson、CWorker和CStudent类对象
可以针对用户自定义的类型来定义运算符的含义,如前面例子中的CComplex
。这样做非常好,当c
、c1
和c2
是复数时,c = c1 + c2比c = ComplexAdd(c1, c2)
更直观更容易理解。
要让编译器能处理用户自定义的类型,就必须实现运算符函数或重载相应的函数。假设,有两个矩阵m1
、m2
和一个矩阵表达式m = m1 + m2
。编译器知道如何处理基本类型(如,把两个整数相加),但如果事先没有定义CMatrix operator+(const CMatrix& m1, const CMtrix& m2)
函数,编译器就不知道如何计算矩阵加法。
覆盖(override)方法也是一种特性,允许派生类在基类已经实现某方法的前提下提供自己的特定实现。如前面例子中的WhoAmI
方法所示,其输出如下:
I am a person
I am a worker
I am a student```
虽然每个类中的方法名相同,但它们却是不同的方法,有不同的功能。我们可以说,`CPerson`的派生类覆盖了`WhoAmI`方法。
更多讨论