多重继承之虚继承(主要是为了解决产生的数据冗余问题)

虚继承 是面向对象编程中的一种技术,是指一个指定的基类,在继承体系结构中,将其成员数据实例共享给也从这个基类型直接或间接派生的其它类。形式:在继承定义中包含了virtual关键字的继承关系,如下图中,类A就叫做虚基类。
虚拟继承是多重继承中的菱形继承所特有的概念。虚拟基类是为解决多重继承而出现的。

菱形继承中既有多继承,如下图所示:

菱形继承中也有多重继承:

 现实中的例子:



问题来了:在类D的实例中将有两份类A的变量,这种数据冗余的现象我们不能容忍其存在?这就会需要用到虚继承!!!废话不多说直接上代码,直接粘贴可用。

#include <iostream>

#include <stdlib.h>
#include <string>
using namespace std;


/**
 * 定义人类: Person
 */
class Person
{
public:
    Person(string color = "blue"):m_strColor(color)
{
cout << "Person" << endl;
}
~Person()
{
cout << "~Person" << endl;
}
void eat()
{
        cout << m_strColor << endl;
        // cout << m_iAge << endl;
cout << "Person -- eat" << endl;
}
protected:
    string m_strColor;
};


/**
 * 定义工人类: Worker
 * 虚继承人类
 */
class Worker :  virtual public Person
{
public:
Worker(string name,string color):Person("Worker"+color)
{
m_strName = name;
cout << "Worker" << endl;
}
~Worker()
{
cout << "~Worker" << endl;
}
void work()
{
cout << m_strName << endl;
cout << "work" << endl;
}
protected:
string m_strName;
};


/**
 * 定义儿童类:Children
 * 虚继承人类
 */
class Children : virtual public Person
{
public:
Children(int age,string color):Person("Children"+ color)
{
m_iAge = age;
cout << "Children" << endl;
}
~Children()
{
cout << "~Children" << endl;
}
void play()
{
        cout<<
cout << m_iAge << endl;
cout << "play" << endl;
}
protected:
int m_iAge;
};


/**
 * 定义童工类:ChildLabourer
 * 公有继承工人类和儿童类
 */
class ChildLabourer:public Children,public Worker
{
public:
ChildLabourer(string name, int age,string color):Worker(name,color),Children(age,color)
{
cout << "ChildLabourer" << endl;
}


~ChildLabourer()
{
cout << "~ChildLabourer" << endl;
}
};


int main(void)
{
    // 用new关键字实例化童工类对象
ChildLabourer * p = new ChildLabourer("qq",14,"yellow");
    // 调用童工类对象各方法。
//   p->eat();
        p->Worker::eat();
        p->Children::eat();
p->work();
p->play();
delete p;
p = NULL;


return 0;

}

输出:

Person
Children
Worker
ChildLabourer
blue
Person -- eat
blue
Person -- eat
qq
work
0x60314814
play
~ChildLabourer
~Worker
~Children
~Person
如果不加virtual关键字时候的输出:

Person
Children
Person
Worker
ChildLabourer
Workeryellow
Person -- eat
Childrenyellow
Person -- eat
qq
work
0x60310814
play
~ChildLabourer
~Worker
~Person
~Children
~Person
分析:

(1)不加virtual情况:

不加virtual,也就是不是虚继承的情况下,在实例化童工这个类的时候,会按继承顺序,先调用类Children的构造函数再调用类Worker的构造函数,最后调用自己的构造函数,而调用Children和Worker的构造函数的时候又会分别先调用它们的基类Person的构造函数,这样就会生成两个Person的对象,从而生成两份Person所含有的数据成员,即童工类ChildLabourer在实例化的时会生成在内存中会生成两份Person的数据成员,所以在调用Children和Worker的eat()函数的时候,会分别打印出Workeryellow和Childrenyellow,(这里注意Children和Worker里面的eat()都是从Person继承来的,因此分别都会打印出Person --eat;还要注意调用方式:p->Worker::eat();p->Children::eat())

销毁对象时,会先调用自己的析构函数,再调用两个基类的析构函数,两个基类的析构函数调用之前都会先调用Person的析构函数。调用析构函数的顺序和调用构造函数相反。

(2)加virtual的情况:

加virtual以后,从输出结果可以看出,在实例化童工类ChildrenLabourer时,构造函数的调用顺序比较正常,只调用了一次Person,析构函数的调用也只会调用一次Person的析构函数,这说明实例化童工类时只会生成一份Person的对象,表明了在对象的内存空间中仅仅能够包含一份虚基类的对象,而且打印的结果都是blue,即ChildrenLabourer的数据不会再传入虚基类Person。这就讲数据冗余的问题解决了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/385026.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

/proc 虚拟文件系统(实例)

Linux下有一个神奇的目录/proc&#xff0c;经常会运行 cat /proc/cpuinfo 命令查看cpu信息&#xff0c;/proc下的确有cpuinfo文件&#xff0c;但是这个文件不是物理存在的&#xff0c;是软件虚拟出来的&#xff0c;与普通文件不同&#xff0c;该文件是动态的。通过/proc可以实现…

malloc,calloc,realloc

与堆操作相关的两个函数 malloc #include<stdio.h> #include<stdlib.h> #include<string.h>int main() {char *p malloc(10); //内存随机&#xff0c;未做处理int i;for(i 0; i < 10: i){printf(“%d “,p[i]);} free(p);return 0; } 运行结果&…

Linux内核线程kernel thread详解--Linux进程的管理与调度

内核线程为什么需要内核线程Linux内核可以看作一个服务进程(管理软硬件资源&#xff0c;响应用户进程的种种合理以及不合理的请求)。 内核需要多个执行流并行&#xff0c;为了防止可能的阻塞&#xff0c;支持多线程是必要的。 内核线程就是内核的分身&#xff0c;一个分身可以处…

Linux 内核网络协议栈 ------sk_buff 结构体 以及 完全解释 (2.6.16)

在2.6.24之后这个结构体有了较大的变化&#xff0c;此处先说一说2.6.16版本的sk_buff&#xff0c;以及解释一些问题。一、先直观的看一下这个结构体~~~~~~~~~~~~~~~~~~~~~~在下面解释每个字段的意义~~~~~~~~~~~[cpp] view plaincopyprint?struct sk_buff { /* These…

最常用的设计模式---适配器模式(C++实现)

适配器模式属于结构型的设计模式&#xff0c;它是结构型设计模式之首&#xff08;用的最多的结构型设计模式&#xff09;。 适配器设计模式也并不复杂&#xff0c;适配器它是主要作用是将一个类的接口转换成客户希望的另外一个接口这样使得原本由于接口不兼容而不能一起工作的那…

c++简单线程池实现

线程池&#xff0c;简单来说就是有一堆已经创建好的线程&#xff08;最大数目一定&#xff09;&#xff0c;初始时他们都处于空闲状态&#xff0c;当有新的任务进来&#xff0c;从线程池中取出一个空闲的线程处理任务&#xff0c;然后当任务处理完成之后&#xff0c;该线程被重…

Linux 打印可变参数日志

实现了传输进去的字符串所在的文档&#xff0c;函数和行数显示功能。 实现了将传入的可变参数打印到日志功能。 #include<stdio.h> #include<stdarg.h> #include<string.h>const char * g_path "/home/exbot/wangqinghe/log.txt"; #define LOG(fm…

C++强化之路之线程池开发整体框架(二)

一.线程池开发框架 我所开发的线程池由以下几部分组成&#xff1a; 1.工作中的线程。也就是线程池中的线程&#xff0c;主要是执行分发来的task。 2.管理线程池的监督线程。这个线程的创建独立于线程池的创建&#xff0c;按照既定的管理方法进行管理线程池中的所有线程&#xf…

vfprintf()函数

函数声明&#xff1a;int vfprintf(FILE *stream, const char *format, va_list arg) 函数参数&#xff1a; stream—这是指向了FILE对象的指针&#xff0c;该FILE对象标识了流。 format—c语言字符串&#xff0c;包含了要被写入到流stream中的文本。它可以包含嵌入的format标签…

TCP粘包问题分析和解决(全)

TCP通信粘包问题分析和解决&#xff08;全&#xff09;在socket网络程序中&#xff0c;TCP和UDP分别是面向连接和非面向连接的。因此TCP的socket编程&#xff0c;收发两端&#xff08;客户端和服务器端&#xff09;都要有成对的socket&#xff0c;因此&#xff0c;发送端为了将…

UML类图符号 各种关系说明以及举例

UML中描述对象和类之间相互关系的方式包括&#xff1a;依赖&#xff0c;关联&#xff0c;聚合&#xff0c;组合&#xff0c;泛化&#xff0c;实现等。表示关系的强弱&#xff1a;组合>聚合>关联>依赖 相互间关系 聚合是表明对象之间的整体与部分关系的关联&#xff0c…

ESP传输模式拆解包流程

一、 ESP简介ESP&#xff0c;封装安全载荷协议(Encapsulating SecurityPayloads)&#xff0c;是一种Ipsec协议&#xff0c;用于对IP协议在传输过程中进行数据完整性度量、来源认证、加密以及防回放攻击。可以单独使用&#xff0c;也可以和AH一起使用。在ESP头部之前的IPV4…

linux内核netfilter模块分析之:HOOKs点的注册及调用

1: 为什么要写这个东西?最近在找工作,之前netfilter 这一块的代码也认真地研究过&#xff0c;应该每个人都是这样的你懂 不一定你能很准确的表达出来。 故一定要化些时间把这相关的东西总结一下。 0&#xff1a;相关文档linux 下 nf_conntrack_tuple 跟踪记录 其中可以根据内…

网络抓包工具 wireshark 入门教程

Wireshark&#xff08;前称Ethereal&#xff09;是一个网络数据包分析软件。网络数据包分析软件的功能是截取网络数据包&#xff0c;并尽可能显示出最为详细的网络数据包数据。Wireshark使用WinPCAP作为接口&#xff0c;直接与网卡进行数据报文交换。网络管理员使用Wireshark来…

结构体中指针

结构体中带有指针的情况 #include<stdio.h>struct man {char *name;int age; };int main() {struct man m {"tom",20};printf("name %s, age %d\n",m.name,m.age);return 0; } 运行结果&#xff1a; exbotubuntu:~/wangqinghe/C/20190714$ gcc st…

python使用opencv提取视频中的每一帧、最后一帧,并存储成图片

提取视频每一帧存储图片 最近在搞视频检测问题&#xff0c;在用到将视频分帧保存为图片时&#xff0c;图片可以保存&#xff0c;但是会出现(-215:Assertion failed) !_img.empty() in function cv::imwrite问题而不能正常运行&#xff0c;在检查代码、检查路径等措施均无果后&…

线程间通信之eventfd

线程间通信之eventfd man手册中的解释&#xff1a; eventfd()创建了一个“eventfd对象”&#xff0c; 通过它能够实现用户态程序间(我觉得这里主要指线程而非进程)的等待/通知机制&#xff0c;以及内核态向用户态通知的机制&#xff08;未考证&#xff09;。 此对象包含了一个…

定时器timerfd

1.为什么要加入此定时器接口 linux2.6.25版本新增了timerfd这个供用户程序使用的定时接口&#xff0c;这个接口基于文件描述符&#xff0c;当超时事件发生时&#xff0c;该文件描述符就变为可读。我首次接触这个新特性是在muduo网络库的定时器里看到的&#xff0c;那么新增一个…

timerfd与epoll

linux timerfd系列函数总结 网上关于timerfd的文章很多&#xff0c;在这儿归纳总结一下方便以后使用&#xff0c;顺便贴出一个timerfd配合epoll使用的简单例子 一、timerfd系列函数 timerfd是Linux为用户程序提供的一个定时器接口。这个接口基于文件描述符&#xff0c;通过文…

linux僵尸进程产生的原因以及如何避免产生僵尸进程defunct

给进程设置僵尸状态的目的是维护子进程的信息&#xff0c;以便父进程在以后某个时间获取。这些信息包括子进程的进程ID、终止状态以及资源利用信息(CPU时间&#xff0c;内存使用量等等)。如果一个进程终止&#xff0c;而该进程有子进程处于僵尸状态&#xff0c;那么它的所有僵尸…