一文读懂C++中的继承之菱形继承(案例分析)

前言

我们上一篇说了世间万物都有一个继承体制,或多或少子类继承了父类的某些特征,但大多都是单向继承,但是就有些特例他就是多继承,比如:

我们从图片中就可以看到,两栖动物它既继承了水生动物的一部分特性,也继承了陆地动物的一些特性,那么我们的代码,会不会也会有这种多继承现象呢,我们一起来看一下。

提示:以下是本篇文章正文内容,下面案例可供参考

一、什么是多继承?

1.单继承

我们来看一个图先了解一下单继承,再看有什莫区别

也就是说,一个子类只有一个直接父类时称这个继承关系为单继承

2.多继承

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

我们看一下代码,看看多继承中存在哪些问题。

//基类A
class A
{
public:
  A() :m_data(1)
  {
  }
  ~A(){}

public:
  int m_data;   //同名变量

};
//基类B
class B
{
public:
  B() :m_data(1)
  {
  }
  ~B(){}

public:
  int m_data;   //同名变量

};

class C : public A, public B
{

};

int main()
{
  C Data;
  //Data.m_data = 10;  //错误, 提示指向不明确 不能够分辨m_data到底是谁的
  //只有通过域成员运算符给其明确指出才可以访问,
  Data.A::m_data = 5;
  Data.B::m_data =10;

  std::cout << Data.A::m_data << "  " << Data.B::m_data << std::endl;

  return 0;
}

通过上面的代码我们明显看的出,多继承体系中存在二义性问题。指如果有同名的数据成员 那么就无法直接通过变量名进行读取,需要通过域(::)成员运算符进行区分

就好像一个人说 去给老师送个东西去,那么多老师,你无法确定他所指的是哪一个老师,必须指名道姓,我们才可以区分。一个道理

二、菱形继承

我们先来画张图,理解一下什么叫做菱形继承

我们把这种继承类型叫做菱形继承。

那么这种继承体制,又会出现什么样的问题呢?我们通过下面的代码具体看一下。

// 菱形继承 菱形继承是多继承的一种特殊情况。
//菱形继承的问题:从下面的对象成员模型构造,可以看出菱形继承有数据冗余和二义性的问题。

class A
{
public:
	int m_a = 1;
};
class B :public A
{
public:
	int m_b = 2;
};
class C :public A
{
public:
	int m_c = 3;
};
class D :public B, public C
{
public:
	int m_d = 4;
};
void main()
{
	D d;
	d.m_d = 40;
	d.m_c = 30;
	d.m_b = 20;
	//.m_a = 10;// 二义性
	// 不能够访问 因为B 和C分别继承了A的m_a
	//但是D 继承了B和C的m_a 所以D不能够分辨m_a到底是谁的
	d.B::m_a = 100;
	d.C::m_a = 200;
	// 这样的话 就造成了m_a 有两个空间 一个B的100 一个C的200
}

我们根据上面的情况不难看出,菱形继承中也存在数据的二义性,这里的二义性是由于他们间接都有相同的基类导致的。 这种菱形继承除了带来二义性之外,还会有有数据冗余浪费内存空间

何为空间浪费,数据冗余,我们画图展示一下。

那么该怎样解决这种问题,

1.虚基类的引入

C++中引入了虚基类,其作用是 在间接继承共同基类时只保留一份基类成员。

我么看一下代码

class A
{
public:
	int m_a = 1;
};
class B :virtual public A
{
public:
	int m_b = 2;
};
class C :virtual public A
{
public:
	int m_c = 3;
};
class D :public B, public C
{
public:
	int m_d = 4;
};
void main()
{
	D d;
	d.m_d = 40;
	d.m_c = 30;
	d.m_b = 20;
	d.m_a = 100;// 让B C虚拟继承 A 加关键字virtual
}

我们可以看到很好解决了二义性和数据冗余的问题,那么它是具体怎么来做的,

2.虚基表的引入

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

具体我们画一张图

这里的B  C就像两个人分别在两个不同的地方,但是都要去A这个地方,保存了A的地址,那么就可以用地图去导航,但是因为两个人所在的地方不一样,所以到A的距离肯定也会不一样,也就是A的偏移量肯定会不一样(这样说会比较好理解一点)。

总结

很多人说C++语法复杂,其实多继承就是一个体现。有了多继承,就存在菱形继承,有了菱形继承就有 菱形虚拟继承,底层实现就很复杂。所以一般不建议设计出多继承,一定不要设计出菱形继承。否则在复杂度上就有问题并且菱形继承虚继承也带来了性能上的损耗(因为多开了地址来存放偏移量)。

到此这篇关于C++中的继承之菱形继承的文章就介绍到这了,更多相关C++继承之菱形继承内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • C++ 继承详解及实例代码

    C++继承可以是单一继承或多重继承,每一个继承连接可以是public,protected,private也可以是virtual或non-virtual.然后是各个成员函数选项可以是virtual或non-virtual或pure virtual.本文仅仅作出一些关键点的验证. public继承,例如下: 1 class base 2 {...} 3 class derived:public base 4 {...} 如果这样写,编译器会理解成类型为derived的对象同时也是类型为base的对象

  • C++类继承之子类调用父类的构造函数的实例详解

    C++类继承之子类调用父类的构造函数的实例详解 父类HttpUtil: #pragma once #include <windows.h> #include <string> using namespace std; class HttpUtil { private: LPVOID hInternet; LPVOID hConnect; LPVOID hRequest; protected: wchar_t * mHostName; short mPort; string send

  • 详解C++中实现继承string类的MyString类的步骤

    昨天师兄又出了道测试题,让我们实现类似于string类的没有MyString类,刚开始很头疼,可是真正在自己写代码的时候又很兴奋的发现,这个过程真的是个很宝贵的机会,让我又有机会可以很好的熟悉回顾C++的很多知识-类设计,构造析构函数,成员函数,友元函数,引用,重载,字符串操作,动态内存分布.....于是昨天花了半天时间写了300多行代码,并认真的进行了相关测试.修改和总结.因为内容有点丰富,所以想分几次写出来,条理也清楚些. 类的空间分配:类给它的每个对象都分配了独立的空间去存储它的数据成员,

  • C++中的菱形继承深入分析

    菱形继承 class Person { int _AA; }; class Student:public Person { int _BB; }; class Teacher :public Person { int _CC; }; class Assistant :public Student, public Teacher { int _DD; }; PS: Assistant的对象中存在两份Person成员 菱形继承存在二义性和数据冗余 解决: 使用虚继承 首先不使用虚继承时: #incl

  • 关于C++中菱形继承和虚继承的问题总结

    前言 菱形继承是多重继承中跑不掉的,Java拿掉了多重继承,辅之以接口.C++中虽然没有明确说明接口这种东西,但是只有纯虚函数的类可以看作Java中的接口.在多重继承中建议使用"接口",来避免多重继承中可能出现的各种问题.本文将给大家详细介绍关于C++菱形继承和虚继承的相关内容,分享出来供大家参考学习,话不多说了,来一起看看详细的介绍吧. 继承: 1. 单继承–一个子类只有一个直接父类时称这个继承关系为单继承 2. 多继承–一个子类有两个或以上直接父类时称这个继承关系为多继承 例如下面

  • 一文读懂ava中的Volatile关键字使用

    在本文中,我们会介绍java中的一个关键字volatile. volatile的中文意思是易挥发的,不稳定的.那么在java中使用是什么意思呢? 我们知道,在java中,每个线程都会有个自己的内存空间,我们称之为working memory.这个空间会缓存一些变量的信息,从而提升程序的性能.当执行完某个操作之后,thread会将更新后的变量更新到主缓存中,以供其他线程读写. 因为变量存在working memory和main memory两个地方,那么就有可能出现不一致的情况. 那么我们就可以使

  • 一文读懂JAVA中HttpURLConnection的用法

    针对JDK中的URLConnection连接Servlet的问题,网上有虽然有所涉及,但是只是说明了某一个或几个问题,是以FAQ的方式来解决的,而且比较零散,现在对这个类的使用就本人在项目中的使用经验做如下总结: 1:> URL请求的类别: 分为二类,GET与POST请求.二者的区别在于: a:) get请求可以获取静态页面,也可以把参数放在URL字串后面,传递给servlet, b:) post与get的不同之处在于post的参数不是放在URL字串里面,而是放在http请求的正文内. 2:>

  • 一文读懂C++中的继承之菱形继承(案例分析)

    前言 我们上一篇说了世间万物都有一个继承体制,或多或少子类继承了父类的某些特征,但大多都是单向继承,但是就有些特例他就是多继承,比如: 我们从图片中就可以看到,两栖动物它既继承了水生动物的一部分特性,也继承了陆地动物的一些特性,那么我们的代码,会不会也会有这种多继承现象呢,我们一起来看一下. 提示:以下是本篇文章正文内容,下面案例可供参考 一.什么是多继承? 1.单继承 我们来看一个图先了解一下单继承,再看有什莫区别 也就是说,一个子类只有一个直接父类时称这个继承关系为单继承 2.多继承 我们把

  • 一文读懂go中semaphore(信号量)源码

    运行时信号量机制 semaphore 前言 最近在看源码,发现好多地方用到了这个semaphore. 本文是在go version go1.13.15 darwin/amd64上进行的 作用是什么 下面是官方的描述 // Semaphore implementation exposed to Go. // Intended use is provide a sleep and wakeup // primitive that can be used in the contended case /

  • 一文读懂ES7中的javascript修饰器

    什么是修饰器 修饰器(Decorator)是ES7的一个提案,它的出现能解决两个问题: 不同类间共享方法 编译期对类和方法的行为进行改变 用法也很简单,就是在类或方法的上面加一个@符,在vue in typescript中经常用到 以上的两个用处可能不太明白,没关系,我们开始第一个例子 例子1:修饰类 @setProp class User {} function setProp(target) { target.age = 30 } console.log(User.age) 这个例子要表达的

  • 一文读懂JavaScript 中的延迟加载属性模式

    传统上,开发人员在 JavaScript 类中为实例中可能需要的任何数据创建属性.对于在构造函数中随时可用的小块数据来说,这不是问题.但是,如果在实例中可用之前需要计算某些数据,您可能不想预先支付该费用.例如,考虑这个类: class MyClass { constructor() { this.data = someExpensiveComputation(); } } 在这里,data属性是作为执行一些昂贵计算的结果而创建的.如果您不确定是否会使用该属性,则预先执行该计算可能效率不高.幸运的

  • 一文读懂C++中指针和内存分配

    指针 指针是保存内存位置地址的变量.我们知道声明的所有变量在内存中都有一个特定的地址.声明一个指针变量来指向内存中的这些地址. 声明指针变量的一般语法是: int p, *ptr; //声明变量p和指针变量ptr p = 4; //赋值4给变量p ptr = &p; //将p的地址分配给指针变量ptr 在内存中,这些声明将表示如下: 这是指针在内存中的内部表示.当地址变量分配给指针变量时,它指向的变量如上图所示. 由于 ptr具有变量 p 的地址,*ptr 将给出变量 p 的值(指针变量 ptr

  • 一文搞懂C++中继承的概念与使用

    目录 前言 继承概念及定义 继承概念 继承定义 继承方式 父类和子类对象赋值转换 继承中的作用域 派生类的默认成员函数 派生类的友元与静态成员 继承关系 单继承 多继承 菱形继承 前言 我们都知道面向对象语言的三大特点是:**封装,继承,多态.**之前在类和对象部分,我们提到了C++中的封装,那么今天呢,我们来学习一下C++中的继承. 继承概念及定义 继承概念 继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加功能

  • 一文读懂Python 枚举

    enum 是一组绑定到唯一常数值的符号名称,并且具备可迭代性和可比较性的特性.我们可以使用 enum 创建具有良好定义的标识符,而不是直接使用魔法字符串或整数,也便于开发工程师的代码维护. 创建枚举 我们可以使用 class 语法创建一个枚举类型,方便我们进行读写,另外,根据函数 API 的描述定义,我们可以创建一个 enum 的子类,如下: from enum import Enum class HttpStatus(Enum): OK = 200 BAD_REQUEST = 400 FORB

  • 一文读懂C++ 虚函数 virtual

    探讨 C++ 虚函数 virtual 有无虚函数的对比 C++ 中的虚函数用于解决动态多态问题,虚函数的作用是允许在派生类中重新定义与积累同名的函数,并且可以通过基类指针或引用来访问基类和派生类中的同名函数. 首先写两个简单的类,类 B 继承自类 A,即 A 是基类,B 是派生类. class A{ public: void print(){ cout << "A" << endl; } }; class B : public A { public: void

随机推荐