当前位置: 首页 > news >正文

C++笔记(面向对象)多重继承 菱形继承

C++中的多重继承(Multiple Inheritance)。这是一个强大但需要谨慎使用的特性。

1. 多重继承的基本语法

cpp

class Base1 {
public:void func1() { cout << "Base1::func1\n"; }
};class Base2 {
public:void func2() { cout << "Base2::func2\n"; }
};// 多重继承:Derived同时继承Base1和Base2
class Derived : public Base1, public Base2 {
public:void derivedFunc() { func1();  // 调用Base1的方法func2();  // 调用Base2的方法}
};int main() {Derived d;d.func1();      // ✅ 来自Base1d.func2();      // ✅ 来自Base2d.derivedFunc();// ✅ 自己的方法
}

2. 多重继承的构造和析构顺序

构造顺序:

  1. 继承声明顺序构造基类

  2. 声明顺序构造成员对象

  3. 最后构造派生类自己

析构顺序:完全相反

cpp

#include <iostream>
using namespace std;class Base1 {
public:Base1() { cout << "Base1构造\n"; }~Base1() { cout << "Base1析构\n"; }
};class Base2 {
public:Base2() { cout << "Base2构造\n"; }~Base2() { cout << "Base2析构\n"; }
};class Member {
public:Member() { cout << "Member构造\n"; }~Member() { cout << "Member析构\n"; }
};// 继承声明顺序:Base1 → Base2
class Derived : public Base1, public Base2 {Member mem;  // 成员对象
public:Derived() { cout << "Derived构造\n"; }~Derived() { cout << "Derived析构\n"; }
};int main() {Derived d;return 0;
}

输出结果:

text

Base1构造
Base2构造
Member构造
Derived构造
Derived析构
Member析构
Base2析构
Base1析构

3. 名字冲突与二义性

当多个基类有同名成员时,会产生二义性:

cpp

class Base1 {
public:void show() { cout << "Base1::show\n"; }int value = 100;
};class Base2 {
public:void show() { cout << "Base2::show\n"; }int value = 200;
};class Derived : public Base1, public Base2 {
public:void test() {// show();    // ❌ 错误:二义性,不知道调用哪个show// value = 5; // ❌ 错误:二义性,不知道访问哪个valueBase1::show();  // ✅ 明确指定Base1的showBase2::show();  // ✅ 明确指定Base2的showBase1::value = 10;  // ✅ 明确指定Base1的valueBase2::value = 20;  // ✅ 明确指定Base2的value}
};

4. 使用using声明解决名字冲突

可以在派生类中使用using声明引入特定基类的成员:

cpp

class Derived : public Base1, public Base2 {
public:using Base1::show;   // 引入Base1的showusing Base1::value;  // 引入Base1的valuevoid test() {show();          // ✅ 现在使用Base1的show(因为using声明)value = 50;      // ✅ 使用Base1的valueBase2::show();   // ✅ 仍然可以明确调用Base2的showBase2::value = 60; // ✅ 仍然可以访问Base2的value}
};

菱形继承(Diamond Inheritance)是C++多重继承中一个经典且重要的问题。我们来详细解析它。

1. 什么是菱形继承?

菱形继承指的是一个类从两个父类继承,而这两个父类又共同继承自同一个基类。

cpp

class Animal {
public:int age;
};class Mammal : public Animal {// 包含Animal的成员
};class Bird : public Animal {// 包含Animal的成员  
};class Bat : public Mammal, public Bird {  // 菱形继承// 问题:包含两个Animal子对象!
};

内存布局示意图:

text

Animal/   \
Mammal   Bird\   /BatBat对象在内存中:
+-------------+
| Mammal部分  |
|   Animal    |  <- 第一个Animal子对象
|     age     |
+-------------+
| Bird部分    |
|   Animal    |  <- 第二个Animal子对象  
|     age     |
+-------------+
| Bat自有成员 |
+-------------+

2. 菱形继承的问题

数据冗余和二义性

cpp

Bat bat;
// bat.age = 5;  // ❌ 错误:二义性,不知道访问哪个age
bat.Mammal::age = 3;    // ✅ 明确指定
bat.Bird::age = 4;      // ✅ 明确指定cout << bat.Mammal::age << endl;  // 输出3
cout << bat.Bird::age << endl;    // 输出4
// 两个不同的age!数据冗余

函数调用的二义性

cpp

class Animal {
public:void breathe() { cout << "Animal breathing\n"; }
};class Mammal : public Animal {};
class Bird : public Animal {};  
class Bat : public Mammal, public Bird {};Bat bat;
// bat.breathe();  // ❌ 错误:二义性
bat.Mammal::breathe();  // ✅
bat.Bird::breathe();    // ✅

3. 解决方案:虚继承(Virtual Inheritance)

虚继承可以解决菱形继承中的数据冗余和二义性问题。

cpp

class Animal {
public:int age;void breathe() { cout << "Animal breathing\n"; }
};// 使用虚继承
class Mammal : virtual public Animal {  // 虚继承
};class Bird : virtual public Animal {    // 虚继承  
};class Bat : public Mammal, public Bird {  // 现在只有一个Animal子对象
};

虚继承后的内存布局:

text

Animal (虚基类)/   \
Mammal   Bird\   /BatBat对象在内存中:
+-------------+
| Mammal部分  |
| (虚基类指针)|
+-------------+
| Bird部分    |
| (虚基类指针)|
+-------------+
| Bat自有成员 |
+-------------+
| Animal部分  |  <- 唯一的Animal子对象
|     age     |
+-------------+

4. 虚继承的效果

cpp

Bat bat;
bat.age = 5;           // ✅ 不再有二义性!
bat.breathe();         // ✅ 可以直接调用bat.Mammal::age = 5;   // ✅ 仍然可以,但访问的是同一个age
bat.Bird::age = 5;     // ✅ 同一个agecout << bat.Mammal::age << endl;  // 输出5
cout << bat.Bird::age << endl;    // 输出5(同一个值)

5. 虚继承的构造顺序

虚继承的构造顺序比较特殊:

cpp

#include <iostream>
using namespace std;class Animal {
public:Animal() { cout << "Animal构造\n"; }
};class Mammal : virtual public Animal {
public:Mammal() { cout << "Mammal构造\n"; }
};class Bird : virtual public Animal {
public:Bird() { cout << "Bird构造\n"; }
};class Bat : public Mammal, public Bird {
public:Bat() { cout << "Bat构造\n"; }
};int main() {Bat bat;return 0;
}

输出结果:

text

Animal构造    ← 虚基类最先构造
Mammal构造
Bird构造  
Bat构造

虚继承构造规则:

  1. 虚基类最先构造(即使不在初始化列表中)

  2. 然后按继承声明顺序构造非虚基类

  3. 最后构造派生类自己


6. 虚继承的析构顺序

与构造顺序完全相反:

cpp

~Bat() { cout << "Bat析构\n"; }
~Bird() { cout << "Bird析构\n"; }
~Mammal() { cout << "Mammal析构\n"; }  
~Animal() { cout << "Animal析构\n"; }

输出:

text

Bat析构
Bird析构
Mammal析构
Animal析构

7. 带参数的虚继承构造函数

虚基类的构造由最底层的派生类负责:

cpp

class Animal {int weight;
public:Animal(int w) : weight(w) { cout << "Animal构造\n"; }
};class Mammal : virtual public Animal {
public:Mammal(int w) : Animal(w) { cout << "Mammal构造\n"; }
};class Bird : virtual public Animal {
public:Bird(int w) : Animal(w) { cout << "Bird构造\n"; }
};class Bat : public Mammal, public Bird {
public:// 必须负责虚基类Animal的构造Bat(int w) : Animal(w), Mammal(w), Bird(w) {  // Animal必须在初始化列表中cout << "Bat构造\n";}
};

重要: 即使MammalBird的初始化列表中有Animal(w),实际执行时只有Bat中的Animal(w)会起作用。


8. 实际应用场景

典型的菱形继承案例:

cpp

// 流类层次
class ios_base { /* 基础功能 */ };class istream : virtual public ios_base { // 输入功能
};class ostream : virtual public ios_base {// 输出功能  
};class iostream : public istream, public ostream {// 输入输出功能
};

人员管理系统:

cpp

class Person {
public:string name;int age;
};class Student : virtual public Person {
public:string studentId;
};class Employee : virtual public Person {
public:string employeeId;
};class TeachingAssistant : public Student, public Employee {// 只有一份Person数据,不会重复
public:TeachingAssistant(const string& n, int a) {name = n;      // ✅ 明确的age = a;       // ✅ 明确的}
};

9. 虚继承的代价

虚继承不是免费的午餐:

  1. 性能开销:通过虚基类指针间接访问

  2. 内存开销:每个虚继承的类需要存储虚基类指针

  3. 构造复杂:最底层派生类负责虚基类构造

  4. 理解成本:代码更复杂,维护难度增加


10. 最佳实践建议

  1. 谨慎使用多重继承,优先考虑组合

  2. 如果必须菱形继承,使用虚继承解决二义性

  3. 保持继承层次简单,避免过深的菱形继承

  4. 明确使用场景

    • ✅ 接口多重继承(Java风格)

    • ✅ 混入类(Mixin)

    • ❌ 过度复杂的类层次

cpp

// 好的使用:接口继承
class Drawable {  // 接口
public:virtual void draw() = 0;
};class Clickable {  // 接口  
public:virtual void click() = 0;
};class Button : public Drawable, public Clickable {  // 多重接口继承
public:void draw() override { /* 实现 */ }void click() override { /* 实现 */ }
};

总结: 菱形继承通过虚继承解决,但要理解其代价,在确实需要时才使用。

http://www.dtcms.com/a/553720.html

相关文章:

  • 淘宝商品详情 API(taobao.item.get)从 0 到 1:申请流程、核心参数与首次调用实战
  • 大连市建设部网站官网权威的网站建设公司
  • 【Day 83】虚拟化-openstack
  • C语言类型转换和溢出常见错误
  • 《计算类云服务》
  • CentOS7 搭建DHCP服务器(一台服务器虚拟机+2台客户端虚拟机演示)
  • 【MySQL】mysqldump使用方法
  • Docker化你的Python应用:从开发到生产
  • 做网站需要多少兆空间安徽网站优化好不好
  • 【weblogic】协议反序列化漏洞
  • LeetCode 287. 寻找重复数
  • C#理论学习-WinForm实践开发教程总结
  • 深入浅出 ES Module
  • 深度学习专题:模型训练的数据并行(二)
  • 企业网站建设方案新闻网站购买域名
  • C++ STL 有序关联容器高频面试题解析
  • 腾讯风铃怎么做网站成都哪家做网站
  • 二叉树核心算法分类精讲:选择、遍历与结构关系
  • 【人工智能系列:走近人工智能05】基于 PyTorch 的机器学习开发与部署实战
  • Arbess零基础学习,创建第一条流水线
  • Linux DNS 深度解析与最佳实践
  • RGB转换为NV12,查表式算法
  • PostIn零基础学习,创建第一个项目
  • 百度网站优化排行做响应式网站应该注意什么
  • 女生化妆品网站建设规划书该网站的域名为
  • 基于NvVideoEncoder的H265视频编码器
  • 淄博网站快照优化公司html5开发网站
  • 厦门门户网站制作服务商保健品商城网站模板
  • day61-devops
  • 【代码随想录算法训练营——Day54】并查集——107.寻找存在的路线