首页 > 编程笔记 > C++笔记 阅读:26

C++类的继承(非常详细)

继承(inheritance)是 C++ 面向对象的三大特征之一,它使一个类可以从现有类中派生,而不必从头开始定义。

类继承的实质是以旧类为基础创建新类,新类包含旧类的数据成员和成员函数,且可添加新的数据成员和成员函数。其中,旧类被称为基类或父类,新类被称为派生类或子类。

C++类的继承

通过继承可以得到派生类,其定义形式如下:
class 派生类名 : [继承方式] 基类名
{
    [访问控制修饰符:]
    [成员声明列表]
}

例如,定义员工类 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) 基类成员在派生类中的访问权限不能高于继承方式指定的权限。例如:
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类析构函数被调用

在基类无参构造函数中初始化成员字符串数组 m_Name 为 MR。从运行结果上看,派生类对象创建时没有调用基类无参构造函数,调用的是带参数的构造函数。

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() 成员函数。

相关文章