c++中多重继承与虚继承的实现

 更新时间:2026年03月27日 10:15:46   作者:Ralph_Y  
本文介绍了C++中的多重继承和虚继承机制,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

一、多重继承(Multiple Inheritance)

1. 基本概念

多重继承是指一个派生类同时继承多个基类,允许派生类复用多个基类的属性和方法,是C++区别于Java等语言的重要特性之一。

语法格式

class 派生类名 : 继承方式1 基类1, 继承方式2 基类2, ... {
    // 派生类成员
};
  • 继承方式:public/protected/private(和单继承规则一致);
  • 核心价值:让一个类同时拥有多个类的特性(比如“汽车”同时继承“交通工具”和“机械产品”)。

2. 简单示例(无冲突的多重继承)

#include <iostream>
using namespace std;

// 基类1:飞行动物
class Flyable {
public:
    void fly() {
        cout << "能飞" << endl;
    }
};

// 基类2:游泳动物
class Swimmable {
public:
    void swim() {
        cout << "能游泳" << endl;
    }
};

// 派生类:鸭子(同时继承飞行动物和游泳动物)
class Duck : public Flyable, public Swimmable {
public:
    void quack() {
        cout << "嘎嘎叫" << endl;
    }
};

int main() {
    Duck duck;
    duck.fly();   // 继承自Flyable
    duck.swim();  // 继承自Swimmable
    duck.quack(); // 自身方法
    return 0;
}

输出

能飞
能游泳
嘎嘎叫

这个示例中,Duck同时复用了FlyableSwimmable的方法,无任何冲突,是多重继承的理想场景。

3. 多重继承的核心问题:菱形继承(钻石继承)

当多个基类继承自同一个“共同基类”,而派生类又同时继承这些基类时,会形成菱形结构,导致两个核心问题:

  • 数据冗余:共同基类的成员会在派生类中存在多份副本;
  • 二义性:访问共同基类的成员时,编译器无法确定访问哪一份副本。

菱形继承示例(有问题)

#include <iostream>
using namespace std;

// 共同基类:动物
class Animal {
public:
    int age; // 共同成员
    Animal(int a) : age(a) {}
};

// 基类1:飞行动物(继承Animal)
class Flyable : public Animal {
public:
    Flyable(int a) : Animal(a) {}
    void fly() { cout << "能飞,年龄:" << age << endl; }
};

// 基类2:游泳动物(继承Animal)
class Swimmable : public Animal {
public:
    Swimmable(int a) : Animal(a) {}
    void swim() { cout << "能游泳,年龄:" << age << endl; }
};

// 派生类:鸭子(同时继承Flyable和Swimmable)
class Duck : public Flyable, public Swimmable {
public:
    // 问题1:构造函数需要初始化两次Animal(数据冗余)
    Duck(int a) : Flyable(a), Swimmable(a) {}
    void quack() { cout << "嘎嘎叫" << endl; }
};

int main() {
    Duck duck(2);
    // 问题2:访问age会触发二义性编译错误
    // cout << duck.age << endl; // 报错:ambiguous reference to 'age'
    
    // 强行指定作用域可以访问,但本质是两份age(数据冗余)
    cout << duck.Flyable::age << endl;   // 2
    cout << duck.Swimmable::age << endl; // 2
    return 0;
}

关键问题分析

  • Duck对象中存在两份age(分别来自Flyable和Swimmable),即使初始化值相同,也是独立的内存空间;
  • 直接访问duck.age会编译报错,因为编译器不知道你要访问哪一份age;
  • 这种冗余不仅浪费内存,还可能导致逻辑错误(比如修改其中一份,另一份不变)。

二、虚继承(Virtual Inheritance)

1. 基本概念

虚继承是C++为解决菱形继承的二义性和数据冗余设计的机制:通过virtual关键字声明继承,让“共同基类”成为虚基类,此时无论派生多少次,共同基类在最终派生类中只保留一份实例

语法格式

class 派生类名 : virtual 继承方式 基类名 {
    // 成员
};
  • virtual关键字加在“继承方式”前;
  • 核心作用:让共同基类的实例在最终派生类中唯一。

2. 虚继承解决菱形继承的示例

修改上面的代码,给FlyableSwimmable的继承加virtual

#include <iostream>
using namespace std;
// 共同基类:动物
class Animal {
public:
    int age;
    Animal(int a) : age(a) {
        cout << "Animal构造:age=" << a << endl;
    }
};
// 虚继承Animal:飞行动物
class Flyable : virtual public Animal {
public:
    // 虚继承下,这里的构造函数不会直接初始化Animal(由最终派生类统一初始化)
    Flyable(int a) : Animal(a) {}
    void fly() { cout << "能飞,年龄:" << age << endl; }
};
// 虚继承Animal:游泳动物
class Swimmable : virtual public Animal {
public:
    Swimmable(int a) : Animal(a) {}
    void swim() { cout << "能游泳,年龄:" << age << endl; }
};
// 最终派生类:鸭子
class Duck : public Flyable, public Swimmable {
public:
    // 虚继承下,最终派生类必须直接初始化虚基类Animal(唯一入口)
    Duck(int a) : Animal(a), Flyable(a), Swimmable(a) {}
    void quack() { cout << "嘎嘎叫" << endl; }
};
int main() {
    Duck duck(2);
    // 无二义性:只有一份age
    cout << duck.age << endl; // 输出2
    duck.fly();  // 能飞,年龄:2
    duck.swim(); // 能游泳,年龄:2
    return 0;
}

输出

Animal构造:age=2
2
能飞,年龄:2
能游泳,年龄:2

关键变化:

  1. 数据冗余解决:Duck对象中只有一份age,不再有重复副本;
  2. 二义性解决:直接访问duck.age无编译错误,编译器明确知道访问唯一的age;
  3. 构造规则变化:虚基类的构造函数由最终派生类统一初始化(即使中间基类写了初始化,也会被忽略),保证虚基类只构造一次。

3. 虚继承的底层原理

虚继承的实现依赖编译器的虚基类表(vbtable)

  • 每个虚继承的类会存储一个指向“虚基类表”的指针;
  • 虚基类表记录了当前类到虚基类实例的偏移量;
  • 通过这个表,所有派生类都能找到唯一的虚基类实例,避免冗余和二义性。

提示:虚继承有轻微的性能开销(指针访问+表查询),因此仅在需要解决菱形继承时使用,不要滥用。

三、总结

  1. 多重继承:允许一个类继承多个基类,复用多类特性,但会引发菱形继承的数据冗余二义性问题;
  2. 虚继承:通过virtual关键字声明继承,将共同基类变为虚基类,使其在最终派生类中仅保留一份实例,解决菱形继承的核心问题;
  3. 关键规则:虚基类的构造函数由最终派生类统一初始化,中间基类对虚基类的初始化会被编译器忽略。

补充:多重继承的其他注意事项

  • 如果多个基类有同名方法,即使不是菱形继承,访问时也需要指定作用域(比如duck.Flyable::func());
  • 实际开发中,应尽量避免复杂的多重继承(可通过组合/接口替代),仅在确有必要时使用;
  • C++11后的std::enable_shared_from_this、Qt的QObject等经典场景会用到虚继承。

到此这篇关于c++中多重继承与虚继承的实现的文章就介绍到这了,更多相关c++ 多重继承与虚继承内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • C语言中宏定义的妙用方法

    C语言中宏定义的妙用方法

    今天小编就为大家分享一篇关于C语言中宏定义的妙用方法,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧
    2018-12-12
  • opencv检测直线方法之投影法

    opencv检测直线方法之投影法

    这篇文章主要为大家详细介绍了opencv检测直线之投影法的相关资料,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2018-12-12
  • OpenCV图像特征提取之Shi-Tomasi角点检测算法详解

    OpenCV图像特征提取之Shi-Tomasi角点检测算法详解

    Harris角点检测算法就是对角点响应函数R进行阈值处理,Shi-Tomasi原理几乎和Harris一样的,只不过最后计算角点响应的公式发生了变化。本文将和大家详细说说Shi-Tomasi角点检测算法的原理与实现,需要的可以参考一下
    2022-09-09
  • C++随机点名生成器实例代码(老师们的福音!)

    C++随机点名生成器实例代码(老师们的福音!)

    这篇文章主要给大家介绍了关于C++随机点名生成器的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2018-12-12
  • C语言位段(位域)机制结构体的特殊实现及解析

    C语言位段(位域)机制结构体的特殊实现及解析

    这篇文章主要为大家介绍了C语言位段位域机制结构体的特殊实现讲解有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步早日升职加薪
    2022-02-02
  • C++中的覆盖和隐藏详解

    C++中的覆盖和隐藏详解

    这篇文章主要介绍了C++中重载、重写(覆盖)和隐藏的区别,是C++面向对象程序设计非常重要的概念,需要的朋友可以参考下,希望能够给你带来帮助
    2021-08-08
  • c++插入排序详解

    c++插入排序详解

    插入排序的基本思想是每次将一个待排序的记录,按其关键字大小插入到前面已经排好序的子文件中的适当位置,直到全部记录插入完成为止。下面我们来详细探讨下C++实现插入排序
    2017-05-05
  • C++ 在 Unreal 中为游戏增加实时音视频互动的教程详解

    C++ 在 Unreal 中为游戏增加实时音视频互动的教程详解

    这篇文章主要介绍了C++ 在 Unreal 中为游戏增加实时音视频互动的教程,本文通过图文并茂的形式给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-05-05
  • C语言中continue的用法详解

    C语言中continue的用法详解

    在C语言当中的continue和break语句是有一些类似的,但是它并不是强制进行终止的,下面这篇文章主要给大家介绍了关于C语言中continue用法的相关资料,需要的朋友可以参考下
    2022-11-11
  • c++的virtual和override作用及说明

    c++的virtual和override作用及说明

    这篇文章主要介绍了c++的virtual和override作用及说明,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-11-11

最新评论