C++继承(详解版)

在现实世界中,可以找到许多对象,这些对象是其他对象的特殊版本。

例如,术语"昆虫"描述了具有许多特征的非常普遍的生物类型。由于蚱蜢和大黄蜂都是昆虫,所以它们都具有昆虫的一般特征。但是它们有自己的特点,例如,蚱蜢有跳跃的能力,而大黄蜂则有螫针。

蚱蜢和大黄蜂是昆虫的特殊版本,如图 1 所示。

蚱蜢和大黄蜂是昆虫的特殊版本
图 1 蚱蜢和大黄蜂是昆虫的特殊版本

继承与 Is-a 关系

当一个对象是另一个对象的特殊版本时,它们之间存在 Is-a 的关系。例如,蚱蜢是一种昆虫。以下是 Is-a 关系的其他示例:
  • 狮子狗是一种狗。
  • 汽车是一种运载工具。
  • 矩形是一种形状。

当对象之间存在 Is-a 关系时,这意味着特殊对象具有通用对象的所有特征,以及使其变得特殊的附加特征。在面向对象编程中,继承用来创建类之间的 Is-a 关系。

继承涉及基类和派生类。基类是通用类,派生类则是特殊化类。派生类基于或派生自基类,可以将基类视为父类,而将派生类视为子类,如图 2 所示。

基类和派生类
图 2 基类和派生类

派生类继承基类的成员变量和成员函数,而不会重写它们中的任何一个。此外,可以将新成员变量和函数添加到派生类,使其比基类更特殊化。

现在来看一个具体的例子。有一所大学,其中既有学生也有教职员工,假设有一个 Person 类及其数据成员 name,此外还有一些处理 name 的成员函数,代码如下:
class Person
{
    private:
        string name;
    public:
        Person () { setName (""); }
        Person(string pName) {setName(pName);}
        void setName(string pName) { name = pName; }
        string getName() const { return name;}
};
进一步假设还有以下枚举类型:
enum Discipline {ARCHEOLOGY, BIOLOGY, COMPUTER_SCIENCE};
enum Classification {FRESHMAN, SOPHOMORE, JUNIOR, SENIOR};
这些枚举类型定义了学科范围和学生分类。可以使用这些类型来定义 Student 和 Faculty 类,这两个类都继承自 Person 类,这样做当然是有道理的,因为一个 Student 是一个 Person,一个 Faculty 也是一个 Person。

为了通过继承来定义一个派生类,需要指定该派生类所属的基类,以及派生类自己的新成员。比方说,除了拥有一个 Person 的所有特征之外,Student 还必须申报某一学科作为专业,并且还要有一个指导老师,而指导老师也是一个 Person。Student 类可以定义如下:
class Student : public Person
{
    private:
        Discipline major;
        shared_ptr<Person> advisor;
    public:
        void setMajor(Discipline d) {major = d;}
        Discipline getMajor() const {return major;}
        void setAdvisor(Person *p) {advisor = p;}
        shared_ptr<Person> getAdvisor() const {return advisor;}
};
假设很多不同的学生可能有相同的指导老师,所以,如果每个 Student 对象都存储一个指导老师的副本将导致不必要的重复,并且在指导老师的数据更新时也会导致其每个 Student 对象不得不更新。为了避免这些问题,可以让 Student 对象存储一个指向指导老师的指针

类声明的第一行的第一部分指定了 Student 作为被定义类的名称,并指定了现有类 Person 作为其基类,如下图所示:

派生类和基类
图 3 派生类和基类

基类名称之前的关键词 public 是基类访问规范。它影响基类的成员被派生类的成员函数以及两个类之外的代码访问的方式。

一个类可以用作多个派生类的基类。例如,在本示例中,Faculty 类也可以从 Person 类派生,如下所示:
class Faculty : public Person
{
    private:
        Discipline department;
    public:
        void setDepartment(Discipline d) {department = d;}
        Discipline getDepartment( ) const {return department;}
};
因此,一个 Faculty 对象也是一个 Person 对象,而且有自己的学科部门。还有一点很重要,派生类的每个对象都将包含基类的所有成员。如图 4 所示是 Student 类和 Person 类所包含成员的示意图。

Student 类包含 Person 类的所有成员
图 4 Student 类包含 Person 类的所有成员

继承和指针

无论何时,只要涉及继承,则所有对象都应该动态分配并通过指针访问。要理解其中原因,不妨以一个同时包含 Student 和 Person 类型的程序为例,因为 Student 是一个 Person,所以程序员应该能够传递 Student 对象给函数(该函数需要一个 Person 类型的形参),或者将一个 Student 对象赋值给一个 Person 类型的变量:
Person p;
Student s;
p = s;
但是,Student 对象有它自己的成员,无法纳入为 Person 对象分配的内存位置中。为了执行这样的赋值语句,编译器将需要裁剪掉 Student 中不属于 Person 的部分,仅保留继承的部分,这样就导致了信息的丢失。如果使用指针的话就不会出现这种问题了,因为所有的指针都是一样大小的。

下面的程序通过创建一个 Faculty 对象演示了派生类的对象的创建和使用。程序中使用字符串数组将枚举类型的值映射到字符串,以便以字符串的形式打印枚举类型的值。其中的 inheritance.h 文件包含 Person、Student 和 Faculty 类的声明,以及枚举类型 Discipline 和 Classification。
//Inheritance.h的内容
#include <string>
#include <memory>
using namespace std;

enum Discipline { ARCHEOLOGY, BIOLOGY, COMPUTER_SCIENCE };
enum Classification { FRESHMAN, SOPHOMORE, JUNIOR, SENIOR };

class Person
{
    private:
        string name;
    public:
        Person() { setName(""); }
        Person(const string& pName) { setName(pName); }
        void setName(const string& pName) { name = pName; }
        string getName() const { return name; }
};

class Student:public Person
{
    private:
        Discipline major;
        shared_ptr<Person> advisor;
    public:
        void setMajor(Discipline d) { major = d; }
        Discipline getMajor() const { return major; }
        void setAdvisor(shared_ptr<Person> p) { advisor = p; }
        shared_ptr<Person> getAdvisor() const { return advisor; }
};

class Faculty:public Person
{
    private:
        Discipline department;
    public:
        void setDepartment(Discipline d) { department = d; }
        Discipline getDepartment( ) const { return department; }
};

//main函数
// This program demonstrates the creation and use
// of objects of derived classes.
#include <iostream>
#include "inheritance.h"
using namespace std;

// These arrays of string are used to print the
// enumerated types.
const string dName[] = {"Archeology", "Biology", "Computer Science"};
const string cName[] = {"Freshman", "Sophomore", "Junior", "Senior"};

int main()
{
    // Create a Faculty object
    shared_ptr<Faculty> prof = make_shared<Faculty>();
    // Use a Person member function to set name
    prof->setName ("Indiana Jones");
    // Use a Faculty member function to set Department
    prof->setDepartment(Discipline::ARCHEOLOGY);
    cout << "Professor " << prof->getName() << " teaches in the " << "Department of ";
    // Get department as an enumerated type
    Discipline dept = prof->getDepartment();
    // Print out the department in string form
    cout << dName[static_cast<int>(dept)] << endl;
    return 0;
}
程序输出结果:

Professor Indiana Jones teaches in the Department of Archeology

超类和子类

可以将一个类视为具有某些特性的所有对象的集合。派生类的对象继承了基类的所有特性,所以可以认为它属于基类。因此,派生类的对象只是基类的特殊对象。出于这个原因,派生类通常称为基类的子类,而基类称为派生类的超类