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的成员,从而避免数据冗余和二义性。

相关推荐
Molesidy32 分钟前
【VSCode】【Clangd】Win下的基于LLVM/Clangd+Clangd插件+MINGW+CMake的VSCode配置C/C++开发环境的详细教程
c++·ide·vscode·clangd·llvm
ᐇ9592 小时前
Java LinkedList集合全面解析:双向链表的艺术与实战
java·开发语言·链表
码银2 小时前
【数据结构】顺序表
java·开发语言·数据结构
Mr_WangAndy2 小时前
C++_chapter13_C++并发与多线程_多线程概念,死锁,unique_lock(),lock_guard()使用
c++·lock·死锁·并发与多线程·unlock·lock_guard·unique_lock
小欣加油2 小时前
leetcode 946 验证栈序列
c++·算法·leetcode·职场和发展
神仙别闹2 小时前
基于QT(C++) 实现哈夫曼压缩(多线程)
java·c++·qt
Python私教2 小时前
Python 开发环境安装与配置全指南(2025版)
开发语言·python
百锦再3 小时前
第12章 测试编写
android·java·开发语言·python·rust·go·erlang
无敌最俊朗@3 小时前
C++ 并发与同步速查笔记(整理版)
开发语言·c++·算法
Elastic 中国社区官方博客3 小时前
Observability:适用于 PHP 的 OpenTelemetry:EDOT PHP 加入 OpenTelemetry 项目
大数据·开发语言·人工智能·elasticsearch·搜索引擎·全文检索·php