【C++】:菱形继承和虚拟继承

简介: 【C++】:菱形继承和虚拟继承

1. 复杂的菱形继承及菱形虚拟继承

1.1 菱形继承

单继承:一个子类只有一个直接父类时称这个继承关系为单继承

/单继承
class Person
{
protected:
  string _name;
};
class Student : public Person
{
protected:
  int _age;
};
class PostGraduate : public Student
{
  int GPA;
};

多继承:一个子类有两个或以上直接父类时称这个继承关系为多继承

class Student
{
protected:
  int _num;
};
class Teacher
{
protected:
  int _id;
};
class Assistant : public Student, public Teacher
{
protected:
  string _majorCourse;
};

菱形继承:菱形继承是多继承的一种特殊情况

class Person
{
public:
  string _name; // 姓名
};
class Student : public Person
{
protected:
  int _num; //学号
};
class Teacher : public Person
{
protected:
  int _id; // 职工编号
};
class Assistant : public Student, public Teacher
{
protected:
  string _majorCourse; // 主修课程
};

菱形继承的问题:从下面的对象成员模型构造,可以看出菱形继承有数据冗余和二义性的问题。在Assistant的对象中Person成员会有两份。

///菱形继承
class Person
{
public:
  string _name; // 姓名
};
class Student : public Person
{
protected:
  int _num; //学号
};
class Teacher : public Person
{
protected:
  int _id; // 职工编号
};
class Assistant : public Student, public Teacher
{
protected:
  string _majorCourse; // 主修课程
};
void Test()
{
  // 这样会有二义性无法明确知道访问的是哪一个
  Assistant a;
  a._name = "peter";
  // 需要显示指定访问哪个父类的成员可以解决二义性问题,但是数据冗余问题无法解决
  a.Student::_name = "xxx";
  a.Teacher::_name = "yyy";
}

1.2 虚拟继承

虚拟继承可以解决菱形继承的二义性和数据冗余的问题。如上面的继承关系,在Student和
Teacher的继承Person时使用虚拟继承,即可解决问题。需要注意的是,虚拟继承不要在其他地方去使用。

在继承关系的前面加上virtual进行修饰为虚拟继承

//虚拟继承
class Person
{
public:
  string _name; // 姓名
};
class Student : virtual public Person
{
protected:
  int _num; //学号
};
class Teacher : virtual public Person
{
protected:
  int _id; // 职工编号
};
class Assistant : public Student, public Teacher
{
protected:
  string _majorCourse; // 主修课程
};
void Test()
{
  Assistant a;
  a._name = "peter";
}

虚拟继承要加在直接继承的子类上

1.3 虚拟继承解决数据冗余和二义性的原理

为了方便观察,可以先建立一个简易的菱形继承的模型,通过内存窗口进一步观察。

class A
{
public:
  int _a;
};
class B :  public A
{
public:
  int _b;
};
class C :  public A
{
public:
  int _c;
};
class D : public B, public C
{
public:
  int _d;
};
int main()
{
  D d;
  d.B::_a = 1;
  d.C::_a = 2;
  d._b = 3;
  d._c = 4;
  d._d = 5;
  return 0;
}

通过下面的图示就可以看到数据的冗余:

上述是没有进行虚拟继承的菱形继承,那么如果加上虚拟继承会是怎么样子呢?

可以看到加上虚拟继承之后,在内存窗口中就多了两个莫名其妙的地址,那么这两个地址是用来干嘛的呢?我们不妨来观察一下:

这里可以分析出D对象中将A放到的了对象组成的最下面,这个A同时属于B和C,那么B和C如何去找到公共的A呢?

这里是通过了B和C的两个指针,指向的一张表。这两个指针叫虚基表指针,这两个表叫虚基表。虚基表中存的偏移量。通过偏移量可以找到下面的A。

朋友们、伙计们,美好的时光总是短暂的,我们本期的的分享就到此结束,欲知后事如何,请听下回分解~,最后看完别忘了留下你们弥足珍贵的三连喔,感谢大家的支持!  

目录
相关文章
|
3月前
|
编译器 C++
【C++】详解C++的继承
【C++】详解C++的继承
|
4天前
|
C++
c++继承层次结构实践
这篇文章通过多个示例代码,讲解了C++中继承层次结构的实践应用,包括多态、抽象类引用、基类调用派生类函数,以及基类指针引用派生类对象的情况,并提供了相关的参考链接。
|
29天前
|
C++
C++(二十)继承
本文介绍了C++中的继承特性,包括公有、保护和私有继承,并解释了虚继承的作用。通过示例展示了派生类如何从基类继承属性和方法,并保持自身的独特性。此外,还详细说明了派生类构造函数的语法格式及构造顺序,提供了具体的代码示例帮助理解。
|
2月前
|
编译器 C++ 索引
C++虚拟成员-虚函数
C++虚拟成员-虚函数
|
2月前
|
安全 Java 编译器
|
3月前
|
存储 Java 程序员
【c++】继承深度解剖
【c++】继承深度解剖
29 1
|
3月前
|
存储 编译器 数据安全/隐私保护
|
3月前
|
Java C++ 运维
开发与运维函数问题之C++中有哪些继承方式如何解决
开发与运维函数问题之C++中有哪些继承方式如何解决
24 0
|
3月前
|
编译器 C++ 开发者
C++一分钟之-多重继承与菱形问题
【7月更文挑战第19天】C++的多重继承允许类从多个基类派生,但引入了菱形问题,即类D通过B和C(都继承自A)双重继承A,可能导致数据冗余和二义性。解决这个问题的关键是**虚继承**,通过`virtual`关键字确保基类A只被继承一次,消除冲突。理解并适当使用虚继承是处理这类问题的关键,有助于保持代码的清晰和正确性。
30 0
|
3月前
|
存储 编译器 C++
C++基础知识(六:继承)
多态是面向对象编程的四大基本原则之一,它让程序能够以统一的接口处理不同的对象类型,从而实现了接口与实现分离,提高了代码的灵活性和复用性。多态主要体现在两个层面:静态多态(编译时多态,如函数重载)和动态多态(运行时多态,主要通过虚函数实现)。