C++之多层继承、多源继承、菱形继承

本文将通过三个经典示例程序,带你彻底理解:

  • 多层继承的访问规则

  • 多源继承的构造与析构顺序

  • 菱形继承中的二义性问题

一,多层继承

概念

多层继承指 一个类继承另一个派生类,形成继承链,例如:

复制代码
A → B → C

类 C 间接继承了 A 的成员。

示例代码:

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

class A
{
public:
    A() { cout << "A" << endl; }
    ~A() { cout << "~A" << endl; }
public:
    void fun_A_public() { cout << "fun_A_public" << endl; }
protected:
    void fun_A_protected() { cout << "fun_A_protected" << endl; }
private:
    void fun_A_private() { cout << "fun_A_private" << endl; }
};

class B : public A
{
public:
    B() { cout << "B" << endl; }
    ~B() { cout << "~B" << endl; }
public:
    void fun_B_public() { cout << "fun_B_public" << endl; }
protected:
    void fun_B_protected() { cout << "fun_B_protected" << endl; }
private:
    void fun_B_private() { cout << "fun_B_private" << endl; }
};

class C : public B
{
public:
    C() { cout << "C" << endl; }
    ~C() { cout << "~C" << endl; }
public:
    void fun_C_public() { cout << "fun_C_public" << endl; }
protected:
    void fun_C_protected() { cout << "fun_C_protected" << endl; }
private:
    void fun_C_private() { cout << "fun_C_private" << endl; }
};

int main()
{
    // 1. 在类外部通过类对象只能访问public成员
    // 2. 对基类成员的访问由继承方式和原访问权限共同决定
    //  (1)只有基类的public成员,以public形式继承,那么派生来的对象在类外部才可以访问
	//  (2)除此之外的情况,派生来的对象在类外部都无法访问
    C c;
    c.fun_A_public();
    c.fun_B_public();
    c.fun_C_public();

    return 0;
}
  1. 在类的内部,只能访问基类的public、protected成员,private无法访问
  2. 如果是多层继承的的话,那么子类对基类成员的访问,只看其直接父类中成员访问权限
  3. 父类成员在子类中存在的的访问权限,是由继承方式和原成员访问权限共同决定

理解方法:

  1. 如果看子类内部是否能访问,那么将继承关系的代码改为单继承来看。
  2. 如果看子类外部是否能访问,那么将继承关系的代码改为单个类来看
  3. 在改代码时,将父类中的代码复制到子类中,然后按照继承方式修改父类成员访问权限

运行结果:

结果分析

  1. 构造顺序: A → B → C

  2. 析构顺序: ~C → ~B → ~A

  3. 访问权限:

    • 类外部只能访问最终类对象中 public继承下来的public成员。

    • 类内部可访问基类的 public 与 protected,不能访问 private。

二,多源继承

概念

多源继承(或称"多重继承")是指一个派生类从多个基类继承:

cpp 复制代码
class C : public A, public B

这种设计可以让子类同时拥有多个基类的功能,但也可能带来复杂性。

示例代码:

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

class A
{
public:
    A(int n) { cout << "A" << endl; }
    ~A() { cout << "~A" << endl; }
};

class B
{
public:
    B(int n) { cout << "B" << endl; }
    ~B() { cout << "~B" << endl; }
};

// 多源继承:构造顺序由继承列表顺序决定,而不是初始化列表顺序
class C : public A, public B
{
public:
    C(int n) : B(n), A(n)
    {
        cout << "C" << endl;
    }
    ~C() { cout << "~C" << endl; }
};

int main()
{
    C c(5);
    return 0;
}

输出结果:

结果分析

  1. 构造顺序

    由继承列表顺序决定:A → B → C

    即使初始化列表写成 B(n), A(n),也无效。

  2. 析构顺序

    与构造相反:~C → ~B → ~A

  3. 注意点

    • 构造顺序只与 类定义时的继承顺序 有关;

    • 初始化列表顺序不会改变这一点;

    • 若多个基类含有同名成员,会产生访问二义性。

三,菱形继承

概念

菱形继承是一种特殊的多重继承结构:

类 D 同时继承 B 和 C,而 B、C 又继承自 A。

这会导致 D 中出现两个 A 子对象,从而引发 二义性问题。

示例代码:

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

class A
{
public:
    A() { cout << "A" << endl; }
    ~A() { cout << "~A" << endl; }

    void fun_A() { cout << "fun_A" << endl; }
};

class B : public A
{
public:
    B() { cout << "B" << endl; }
    ~B() { cout << "~B" << endl; }

    void fun() { cout << "fun_B" << endl; }
};

class C : public A
{
public:
    C() { cout << "C" << endl; }
    ~C() { cout << "~C" << endl; }

    void fun() { cout << "fun_C" << endl; }
};

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

int main()
{
    D d;
    d.B::fun_A();  // 明确指定作用域

    // d.fun_A(); // 错误:二义性,D 中存在两个 A 子对象
    cout << sizeof(D) << endl;

    return 0;
}

运行结果:

结果分析

  1. 类 D 拥有 两个独立的 A 子对象(来自 B 和 C)。

  2. 调用 fun_A() 时会出现二义性错误 ,需指定作用域:d.B::fun_A()。

  3. 内存中 D 的大小包含了两个 A 子对象的空间。

解决办法:虚继承

通过在 B 和 C 中使用 虚继承 ,可以让 D 只保留一个共享的 A 子对象:

cpp 复制代码
class B : virtual public A { ... };
class C : virtual public A { ... };

此时 D 中只有一个 A 实例,二义性问题消失,内存占用也更小。

四、总结对比表

相关推荐
机器人零零壹2 小时前
技术白皮书:iRobotCAM如何解决机器人激光切割应用中的核心难点,从CAD离线编程到双目视觉定位与轨迹自适应提升复杂空间曲线切割的精度、效率与柔性化水平
人工智能·算法
布莱克6053 小时前
C/C++ 中变量的声明与定义:区别、联系与常见误区
c语言·开发语言·c++·声明·定义
LabVIEW开发3 小时前
LabVIEW 局部变量为何引发竞争条件?四种同步机制
开发语言·labview
benchmark_cc4 小时前
A 股、港股、美股如何建立统一的标的主数据格式?
开发语言·python·数据分析·pandas·量化交易·股票数据·quantdash
ZhengEnCi4 小时前
大模型的分类到底怎么分:从基础模型到推理模型、从通用到专用的完整图谱
算法
Zenova EdgeOS5 小时前
Go 工业边缘错误处理实战:用 errors 构建可追溯错误链
开发语言·数据库·golang
取名真是5 小时前
Python 算法知识点从基础到进阶
开发语言·python·算法
罗西的思考6 小时前
【轻量化复刻大模型 / KIMI K3】KDA 模块交互式演示
人工智能·算法·机器学习
FRing036 小时前
vue2干货总结3(computed计算属性、watch监听器)
开发语言·前端·vue.js·vue2
Fatbobman(东坡肘子)7 小时前
快速消失的硬盘容量 -- 肘子的 Swift 周报 #160
开发语言·人工智能·redis·ios·swiftui·iphone·swift