C++第十三篇:继承

目录

一、继承的格式

二、继承的实现

三、菱形继承

四、虚继承

在 C++ 中,继承是面向对象编程的三大核心特性之一(另外两个是封装和多态),它允许一个类(子类 / 派生类)继承另一个类(父类 / 基类)的属性和方法,从而实现代码复用和层次化设计。

什么时候使用继承呢?

is a的关系。(就是说子类时父类的一个特殊的类型,就好比"圆形 "和"图形 "的关系,圆形是归属图形的一种,圆形有图形的特征,需要继承自图形。在好比打王者荣耀时的"韩信 "和"英雄"的关系,韩信是一个英雄,它有英雄的基本特征,如血条,蓝条等等。)

存在层次关系和实现多态的场景会使用继承。

在没有必要使用继承时,尽量去减少继承的使用,否则可能会导致类关系复杂,难以梳理。

一、继承的格式

cpp 复制代码
class 派生类名:继承方式 基类名1,继承方式 基类名2,....
{
派生类成员;
};

这里继承的有不同方式,分别有public、protected、private继承,继承方式的作用是限制基类成员在派生类中的访问级别。下面是不同继承下的访问权限:

这张图的意思就是,基类的private成员,不论派生类使用哪种继承,都是不可访问的;基类的protected成员,在protected和public继承下是属于protected的,在private继承下属于private;基类的public的成员在,在三种不用的继承下,分别属于不同的级别。

这里顺便在说一下三种级别的区别:

  • public:类内、派生类内、类外(通过对象)都能访问。
  • protected:仅类内和派生类内可访问,类外不可。
  • private:仅类内可访问,派生类和类外都不可。

二、继承的实现

举例:

cpp 复制代码
#include <iostream>
#include <string>
using namespace std;

// 基类:英雄类
class Hero {
protected:
    string name;       // 英雄名称
    int health;        // 生命值
    int attack;        // 攻击伤害
    float moveSpeed;   // 移动速度
public:
    // 构造函数:初始化英雄基本属性
    Hero(string n, int h, int a, float ms) 
        : name(n), health(h), attack(a), moveSpeed(ms) {
        cout << "英雄 [" << name << "] 已创建!" << endl;
    }
    // 析构函数
     ~Hero() {
        cout << "英雄 [" << name << "] 已销毁!" << endl;
    }
};

// 派生类:韩信(继承自英雄类)
class HanXin : public Hero {
private:
    int skillDamage;   // 技能额外伤害
public:
    // 构造函数
    HanXin() : Hero("韩信", 3800, 180, 4.5), skillDamage(350) {
        cout << "韩信:\"到达胜利之前,无法回头!\"" << endl;
    }
    // 析构函数
    ~HanXin()  {
        cout << "韩信:\"我的心,可不冷...\"" << endl;
    }
};

int main() {
    HanXin hanxin;          // 创建韩信对象
    return 0;
}

结果如下:

这就是一个继承的实例,在运行的结果我们可以看到构造函数的调用顺序,先构造基类,然后是派生类,释放时先调用派生类的析构,再调用基类的析构函数。

这就是一个继承,上面介绍的时候就说了,继承也是为了多态的实现。那下来就来来看看什么事多态。

三、菱形继承

菱形继承是多继承中一种特殊且容易引发问题的场景,因类继承关系图呈菱形而得名。

菱形继承的关系。

  • BC 都继承自 A
  • D 同时继承 BC
  • 此时 D 会间接包含两份 A 的成员(一份来自 B,一份来自 C),可能导致问题。

代码示例:

cpp 复制代码
#include <iostream>
using namespace std;

// 基类 A
class A {
public:
    int a;
    A() : a(10) { cout << "A 构造" << endl; }
};

// 基类 B(继承 A)
class B : public A {
public:
    B() { cout << "B 构造" << endl; }
};

// 基类 C(继承 A)
class C : public A {
public:
    C() { cout << "C 构造" << endl; }
};

// 派生类 D(同时继承 B 和 C)
class D : public B, public C {
public:
    D() { cout << "D 构造" << endl; }
};

int main() {
    D d;
    // cout << d.a << endl;   错误:二义性
    // 必须显式指定来源
    cout << "B::a = " << d.B::a << endl; // 来自 B 继承的 A
    cout << "C::a = " << d.C::a << endl; // 来自 C 继承的 A
    return 0;
}

结果如下:

从结果上发现,这个A被构造了两次,同时在访问数据上,必须要指明数据的来源。这就是菱形继承带来的问题,数据冗余和二义性。

怎么解决呢?这里又有了虚继承。

四、虚继承

虚继承是为了解决菱形继承所带来的数据冗余和二义性。直接看代码:

cpp 复制代码
#include <iostream>
using namespace std;

class A {
public:
    int a;
    A() : a(10) { cout << "A 构造" << endl; }
};

// 虚继承 A
class B : virtual public A {
public:
    B() { cout << "B 构造" << endl; }
};

// 虚继承 A
class C : virtual public A {
public:
    C() { cout << "C 构造" << endl; }
};

class D : public B, public C {
public:
    D() { cout << "D 构造" << endl; }
};

int main() {
    D d;
    // 正常访问:仅一份 A::a
    cout << "d.a = " << d.a << endl; // 10
    return 0;
}

结果如下:

可以看到,A类现在只被创建了一次,同时数据也可以直接访问了。

这个虚继承的思想很好理解。实际就是虚基类(A)的实例只被创建一次 ,并被所有间接继承它的中间基类(BC)和派生类(D共享 。有个虚基类表和虚基类指针的作用是记录这个唯一实例的地址 ,确保BCD都能通过统一的路径访问A的成员,从而避免数据冗余和二义性。

相关推荐
Bryce李小白3 小时前
Kotlin Flow 的使用
android·开发语言·kotlin
jarreyer3 小时前
python离线包安装方法总结
开发语言·python
李辰洋4 小时前
go tools安装
开发语言·后端·golang
wanfeng_094 小时前
go lang
开发语言·后端·golang
绛洞花主敏明4 小时前
go build -tags的其他用法
开发语言·后端·golang
ByteCraze4 小时前
秋招被问到的常见问题
开发语言·javascript·原型模式
码银4 小时前
【python】基于 生活方式与健康数据预测数据集(Lifestyle and Health Risk Prediction)的可视化练习,附数据集源文件。
开发语言·python·生活
Pluchon4 小时前
硅基计划5.0 MySQL 叁 E-R关系图&联合/多表查询&三大连接&子查询&合并查询
开发语言·数据库·学习·mysql
kyle~4 小时前
C++---嵌套类型(Nested Types)封装与泛型的基石
开发语言·c++·算法