c++继承总结

一 继承的由来

    我使用类也有一段时间了,慢慢觉得我们做一件事时,就是要先描述,例如写一个管理系统登记学校成员的信息,我们就要先对在学校内的老师和学生做描述,学生要有年龄,班级,姓名,学号和身份证,描述老师要有年龄,姓名,工号和身份证,分别用一个类来将这些信息封装起来,描述完后,实例化出一个对象可以存一个学生的信息,存多个学生那就new一块大的空间来存,然后就可以写成员函数来对一个个学生信息做增删查改,我们可以发现在描述学生和老师的时候,他们有一些信息是重合的那在老师类和学生类中都要写对应的接口函数对这些相同的信息做处理这就有些冗余,所以我们希望能把这些公共信息分离出来,再封装入类,命名为person类,然后我们学生类想要添加person类的成员函数和成员变量就继承person类,这就是继承的由来,当时我有点疑惑,为什么不直接在student类中定义一个person类的成员变量,后来才知道这叫做组合。

组合和继承的区别我理解比较深刻的就是继承中,子类和父类的关联度更高,因为父类(person类)的公有和保护成员,子类在类内都可以使用,但是组合就只能使用persong类的公有成员,也就只会被公用成员的修改影响,只能说最好就是用组合,如果要实现多态就必须用继承(或许实际工作中我们可以理解地更深刻)。

二 继承的使用

class A
{
public:int _a;
};
class B
{
public:void Print(){cout << "B:Print()" << endl;}int _b;A a1;
};
class C: public B ,public A C要继承B,就在类名后加冒号(:),然后写继承方式,public表示公有继承,再加类名B,要继承多个类就用逗号隔开,C称为子类,A,B称为父类,
{
public:int _c;
};
void test1()
{C c1;c1.Print();//继承
}

   继承一个父类就对应写一个继承方式,如果不写会有有默认的继承方式,如果子类是class定义的,那继承方式默认为private,如果为struct定义的子类,继承方式默认为public, 等会会统一总结继承方式带来的区别。(如下图)

三种继承方式遇到基类(或称父类)的三种成员,使得基类成员的访问方式有九种变化,其实理解记忆也很好记。

基类的私有成员任意继承方式在派生类(或称子类)都是不可见,不可见就是在子类类内都无法访问,但在内存中还是存在,也就是用地址可以强制访问。

而基类的protected和public成员,与不同继承方式也有个规律,就是取权限小的,例如protected成员被public继承,那它就是子类的保护成员,被私有继承那就是子类的私有成员。

是不是都记住了呢?理解记忆即可,根本就不用死记硬背。

二 子类对象和父类对象的赋值(向上转换)

    子类和父类对象是不同的类型,是如何支持赋值的呢?答案是切片

子类对象是有继承父类对象成员的,当要赋值给父类对象时,就把属于父类的切出来赋值给父类对象。

而对于父类的指针p1来说,它仅仅指向子类中的父类对象,

父类的引用也只是子类中属于父类的那一部分的别名。

那好如果反过来呢,父类对象能赋值给子类对象(又称向下转换)吗?父类的指针和引用可以(会存在越界访问),对象不行,可能是大佬觉得父类对象赋值给子类对象更危险,因为剩下的子类成员不知道该放什么数据

注意:切片就意味着要访问子类中的父类成员,如果是私有和保护继承,那在赋值的时候会报错。

三 子类和父类成员函数的作用域

当子类和父类的成员函数重名时,此时子类的成员函数就会对成员函数进行隐藏。也就是当子类对象调用一个同名成员时,会优先调用子类的,除非显示调用。

class C
{
public:void Print(){cout << "C:Print()" << endl;}int _c=3;
};
class E : public C
{
public:void Print(){cout << "E:Print()" << endl;}int _c=4;
};
void test2()
{E e1;cout<<e1._c<<endl;cout << e1.C::_c << endl; 显示调用同名的父类成员e1.Print();e1.C::Print();    显示调用父类的同名成员函数
}

运行结果:

四 继承时默认成员函数

   这里用的都还是原来类的成员函数的知识,如果那部分没掌握,对于继承时成员函数更不容易理解。

   由于子类其实是把继承来的父类成员当成一个整体,当子类对象调用自己的构造函数的时候,初始化列表会先调用父类的构造函数(不写就调用默认构造,显示就自己决定)初始化子类中的父类成员,再初始化子类的。拷贝构造函数也是如此,所以拷贝构造函数我们要自己在初始化列表显示调用父类的拷贝构造函数,不然就会调成默认构造函数了。父类被继承认为声明在子类成员之前,而我们之前在类和对象中学过了,先声明的会先在初始化列表初始化。

   子类赋值成员函数必须要先调用父类的赋值成员函数对子类中的父类成员做处理(这个顺序性是为了保证父类提供给子类的一定是初始化好,或者赋值好的),而且是显示调用,因为赋值成员构成隐藏,如果不显示调用父类的,那就会一直调用子类的,导致栈溢出。

   而析构函数是被编译器做了处理的,名字统一为destructor(原因在多态),所以也构成隐藏,但却不需要我们显示调用父类的,因为编译器需要先用子类的析构函数清理子类资源,再自己调用父类的析构函数。

析构顺序原因有二:

1.遵循后定义的先析构

2.子类中可以使用父类成员,如果先调用父类析构函数,我们又在该函数后访问父类成员会出问题,所以要防止这种情况出现。

五 继承时的友元函数,静态成员

   父类的友元函数继承时是不会变成子类的友元函数的,就像是父辈的朋友不一定是你朋友一样,除非在子类内对该函数进行友元声明。

至于静态成员则更加特殊,在没有提及继承之前,静态成员是属于整个类的,就一份,那继承给子类后会多一份吗?上代码!

class C
{
public:void Print(){cout << "C:Print()" << endl;}static int _c;
};
int C::_c = 3;
class E : public C
{
public:void Print(){cout << "E:Print()" << endl;}int _e=4;
};
void test2()
{E e1;C c1;cout << "C:" << c1._c << endl;cout << "E:" << e1._c << endl;e1._c = 4;cout << "C:" << c1._c << endl;cout << "E:" << e1._c << endl;c1._c=5;cout << "C:" << c1._c << endl;cout << "E:" << e1._c << endl;
}

    当我们用子类和父类对象去打印这个静态成员的时候,他们值是相同的,但这不能说明他们是一个变量,有可能是复制了父类的值,所以我们用子类和父类对象去改这个变量,结果如下图,结论是子类和父类共用这个静态成员。但要强调的一点是,e1,c1对象内都不包含静态成员变量,因为我用sizeof发现子类E继承父类C后大小不变。

 

六 多继承

多继承这个部分的知识点不少,就先来说说多继承的弊端。

1.多继承弊端

上图是多继承中的菱形继承,代码如下。

class A
{
public:int _a;
};
class B:public A
{
public:int _b;
};
class C:public A
{
public:int _c;
};
class E:public C,public B
{
public:int _e;
};

从内存窗口看,我们可以发现e1中有两份A类型,一份是在继承C得来的,一份是继承B得到的。

    特别要说明的是内存排列顺序,E是从左往右继承,所以先继承的C,再继承B,而C显示在上,可以认为先继承的显示在上,显示在上意味着什么呢?这就要再说结构体成员的内存分布了,&e1时的地址是整个结构体的最低地址,这应该是规则,所以显示在上意味着C位于结构体内存块的低地址处。这说明结构体内部是先使用低地址,再使用高地址的,但栈帧是先用高地址。

既然e1中有两份A,那是不是有点冗余呢?当然啦,之前说子类继承父类,是为了更好的描述,如果继承的成员有重复,那对自己的描述不就出现重复了吗,就像有两个身份证号,用哪个呢?二义性不就来了吗?

2 解决办法:虚拟继承

   继承真正的难点才刚刚开始,请做好心理准备。

先来看看虚拟继承的使用

class A
{
public:int _a=1;
};
class B:virtual public A
{
public:int _b=2;
};

 B虚拟继承A类内存图

如果B是正常继承A,内存窗口如下。

    想必大家发现了,监视窗口是没有发生变化的,这是因为监视窗口是编译器想让你看到的,并不是实际的,内存窗口才是真实的,怪不得有句话说大佬的境界是这样的:看代码不是代码,而是内存。

总结虚拟继承对子类内存空间的改变

1.虚拟继承的成员放在存储空间的地址最低处,公共的东西当然放最开始或者最后面了。

2.增加了一个指针,称为虚基表指针,它指向虚基表。

这是对应的虚基表。

虚基表内容后面详谈。

然后我们再来看看一开始提的多继承弊端中的菱形继承如何通过虚拟继承改进。

class A
{
public:int _a;
};
class B: virtual public A
{
public:int _b;
};
class C: virtual public A
{
public:int _c;
};
class E:public C,public B
{
public:int _e;
};

如下图,当C,B类均被E继承时,他们的公共部分A会只保留一份,如果仅仅只是B虚拟继承A类,对于B类对象还没什么变化,但是当两份虚拟继承而来的成员再被E继承时,此时在E类对象只会保留一份,这就是虚拟继承的作用。

大部分的博客都说在继承腰部的位置加个virtual关键字,可是腰部在哪,什么是腰部?下面这个图还好说,其它情况呢?

如下图,解决A类的数据冗余和二义性,应该在哪加virtual关键字?

 

   要回答这个问题我们就要回顾刚刚说的虚拟继承的作用了,我理解就是虚拟继承是将继承而来父类成员都放到一块地方,当出现同样的成员时,取其中一份即可,这两份的数据一定是相同的,因为我们现在是在继承类的声明,不是继承某个类对象,所以要想解决A类的冗余,在B,C继承A时加virtual关键字即可,如果是在继承D,C时加,那就把B,C类的所有成员(包括B,C自己的成员和继承而来的A类的成员)变成公共的了,可我们只需要将A类变为公共的即可,个人认为不要做多余的事。

3 虚基表组成:

第一行内容,有的说是虚表指针指向自己这个指针的距离,所以为零,但我尝试中却发现该值并不都是0,最后我认为是虚表指针距离子类最低地址(&e1)的距离,比如上面那个虚基表,当B虚拟继承A,就会在B类型对象内增加一个虚表指针,此时虚基表第一行就是距离B最低地址的距离,所以为零,当我设计其它场景时,代码如下:

class B
{
public:int _b=2;
};class C
{
public:int _c=3;
};class E :virtual public C, public B
{
public:int _e=4;
};

   从下面的内存窗口可以看出,虚拟继承的C类成员虽然先继承,但虚拟继承的成员统一放在最后面,至于讨论虚基表指针和B类成员谁先先后是没什么意义的,在这里我觉得只需知道一般继承而来的父类成员放子类对象中的低地址处,然后放子类成员,最后放虚拟继承而来的成员就可以啦。我们可以看到的是虚基表中的第一行变成了-4(内存显示的数据是补码), 那虚基表第一行是虚基表指针指向自己的距离就是不成立的,可以我们发现-4不就是虚基表指针距离子类E最低地址的距离吗。(我在此只写了两个例子证明,实际上我自己写了不少更复杂且无用的继承场景来证明,如果你觉得我是错的,可以私聊,非常欢迎)

第一行以外的都是虚基表指针距离父类成员的偏移量。

我写这么多探究虚基表第一行内容是什么,是觉得其他人的博客说虚基表第一行是虚基表指针距离自己的距离不太对,才打算写出来反驳一下他们。

最后我再谈谈对一些问题的看法,例如,为什么要用一个表去存偏移量,而不是直接存偏移量到对象呢?首先偏移量是指向虚继承中的父类成员的,如果子类虚继承了多个父类,那偏移量是不止一个的,如果把偏移量都塞进每个子类对象中,一百个子类对象就有一百份偏移量,而如果放在虚基表中,而虚表指针大小是固定的并且让所有子类对象都可以共用这张表,对,我说的是共用,每个对象的大小是一样的,结构是一样的,当然可以共用这张表格空间上来说当偏移量超过两个,就可以提现出这种设计是节省空间。

下图是两个子类对象的内存窗口图,他们的虚基表地址都是一样的。

 至于为什么存偏移量,这我也不知道啊,大佬就是这么设定的,可能以后随着学习的深入就理解了。

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

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

相关文章

使用C#加载TOOLBLOCK

前言 因为Vpp文件类型包含了以下三种 QuickBuidJobToolBlock 不同类型的打开方式不同&#xff0c;需要提前知道vpp是什么类型 例如 这个TB.vpp文件是TOOLBLOCK&#xff0c;就不能直接在visionpro中打开&#xff08;直接打开需要QuickBuid文件&#xff09;&#xff0c; 可以…

在centos7下通过docker 安装onlyoffice

因为需要调试网盘&#xff0c;所以今天安装一下centos7的onlyoffice 官方介绍如下&#xff1a; 为了方便&#xff0c;还是通过docker方式来安装onlyoffice了&#xff0c;这里我们采用社区版本了。 1、下载docker安装包 如下&#xff1a; docker pull onlyoffice/documentserv…

uniapp 将标题背景更换背景图片 完美解决(附加源码+实现效果图)

问题描述 今天拿到小程序的设计效果图后&#xff0c;标题部分背景需要加背景图片&#xff0c;以往我做的都是标题背景更换颜色等&#xff0c;加背景图片还是第一次遇到&#xff0c;大家可以先看下我的效果图是否与你遇到的问题一致&#xff01; 首页标题的背景是个背景图片。 …

构建高性能的MongoDB数据迁移工具:Java的开发实践

随着大数据时代的到来&#xff0c;数据迁移成为许多企业和组织必须面对的挑战之一。作为一种非关系型数据库&#xff0c;MongoDB在应用开发中得到了广泛的应用。为了满足数据迁移的需求&#xff0c;我们需要一个高性能、稳定可靠的MongoDB数据迁移工具。下面将分享使用Java开发…

Leetcode 977. 有序数组的平方

题目&#xff1a; Leetcode 977. 有序数组的平方 描述&#xff1a; 给你一个按 非递减顺序 排序的整数数组 nums&#xff0c;返回 每个数字的平方 组成的新数组&#xff0c;要求也按 非递减顺序 排序 思路&#xff1a; 双指针法 数组其实是有序的&#xff0c; 只不过负数平方之…

将vsCode 打开的多个文件分行(栏)排列,实现全部显示,便于切换文件

目录 1. 前言 2. 设置VsCode 多文件分行(栏)排列显示 1. 前言 主流编程IDE几乎都有排列切换选择所要查看的文件功能&#xff0c;如下为Visual Studio 2022的该功能界面&#xff1a; 图 1 图 2 当在Visual Studio 2022打开很多文件时&#xff0c;可以按照图1、图2所示找到自…

基于Selenium技术方案的爬虫入门实践

通过爬虫技术抓取网页&#xff0c;动态加载的数据或包含 JavaScript 的页面&#xff0c;需要使用一些特殊的技术和工具。以下是一些常用的技术方法&#xff1a; 使用浏览器模拟器&#xff1a;使用像 Selenium、PhantomJS 或其他类似工具可以模拟一个完整的浏览器环境&#xff0…

Redis实战案例27-UV统计

1. Redis的HyperLogLog的统计功能 示例&#xff1a; 表明HyperLogLog不管加入重复元素多少次都不会让count&#xff0c;不会计数重复元素&#xff0c;所以适合做UV计数 2. 简单实现UV测试 通过单元测试&#xff0c;向 HyperLogLog 中添加 100 万条数据&#xff0c;看看内存占…

python3.6 安装pillow失败

问题描述 python3 安装 pillow 失败 错误原因 python3.6 不支持 pillow9.0 以上的版本 解决方法&#xff1a; 指定版本安装 e.g., pillow8.0 pip3 install pillow8.0

看漫画学python!一天一个小惊喜有趣好用(全彩版)?

新手如何&#xff1a; 搭建Python开发环境 我们在Python官网可以下载Python安装包&#xff0c;在这个安装包里有Python解释器、Python运行所需要的基础库&#xff0c;以及交互式运行工具——Python 在下载完成后就可以安装Python了&#xff0c;在安装过程中会弹出内容选择对话…

水库大坝安全监测系统实施方案

一、方案概述 水库大坝作为特殊的建筑&#xff0c;其安全性质与房屋等建筑物完全不同&#xff0c;并且建造在地质构造复杂、岩土特性不均匀的地基上&#xff0c;目前对于大坝监测多采用人工巡查的方法&#xff0c;存在一定的系统误差&#xff0c;其工作性态和安全状况随时都在变…

怎么学习机械学习相关的技术? - 易智编译EaseEditing

学习DOM&#xff08;文档对象模型&#xff09;相关技术是成为前端开发者的关键一步&#xff0c;因为DOM是用于操作和控制网页内容的基础。以下是学习DOM相关技术的步骤和方法&#xff1a; 了解基础知识&#xff1a; 首先&#xff0c;了解什么是DOM&#xff0c;它如何表示HTML…

刘汉清:从生活到画布,宠物成为灵感源泉

出生于中国镇江的艺术家刘汉清&#xff0c;其作品展现出他对日常生活的深入洞察力&#xff0c;以及对美的独特理解。他的作品通常没有视觉参考&#xff0c;而是通过对他周围环境的理解&#xff0c;尤其是他的宠物&#xff0c;来进行创作。 在刘汉清的创作过程中&#xff0c;他…

从零实战SLAM-第一课(SLAM概览)

在七月算法报的班&#xff0c;老师讲的蛮好。好记性不如烂笔头&#xff0c;关键内容还是记录一下吧&#xff0c; 课程入口&#xff0c;感兴趣的同学可以学习一下。 --------------------------------------------------------------------------------------------------------…

(具体解决方案)训练GAN深度学习的时候出现生成器loss一直上升但判别器loss趋于0

今天小陶在训练CGAN的时候出现了绷不住的情况&#xff0c;那就是G_loss&#xff08;生成器的loss值&#xff09;一路狂飙&#xff0c;一直上升到了6才逐渐平稳。而D_loss&#xff08;判别器的loss值&#xff09;却越来越小&#xff0c;具体的情况就看下面的图片吧。其实这在GAN…

柜柜软件报价单滑动闪屏解决办法

柜柜下载地址:家具设计软件免费下载-家居设计软件手机版下载-柜柜App官网 出现的问题现象: 原因:笔记本使用的集成显卡,切换到独立显卡即可解决 异常修复.

Go context.WithCancel()的使用

WithCancel可以将一个Context包装为cancelCtx,并提供一个取消函数,调用这个取消函数,可以Cancel对应的Context Go语言context包-cancelCtx 疑问 context.WithCancel()取消机制的理解 父母5s钟后出门&#xff0c;倒计时&#xff0c;父母在时要学习&#xff0c;父母一走就可以玩 …

【华秋推荐】新能源汽车中的T-BOX系统,你了解多少?

近几年&#xff0c;新能源汽车产业进入了加速发展的阶段。我国的新能源汽车产业&#xff0c;经过多年的持续努力&#xff0c;技术水平显著提升、产业体系日趋完善、企业竞争力大幅增强&#xff0c;呈现市场规模、发展质量“双提升”的良好局面。同时&#xff0c;通过国家多年来…

基于云平台的智慧养殖远程监控系统

一、项目背景 冬春季节每天的温度和昼夜温差变化很大&#xff0c;为保证养殖动物有一个温暖舒适的生存环境&#xff0c;使动物的生产性能得到较好的发挥&#xff0c;须注意做好温度、湿度、通风等方面的控制。 智慧养殖智能监控系统可以实现对如温度、湿度、气体浓度、光照度…

Spring Profile与PropertyPlaceholderConfigurer实现项目多环境配置切换

最近考虑项目在不同环境下配置的切换&#xff0c;使用profile注解搭配PropertyPlaceholderConfigurer实现对配置文件的切换&#xff0c;简单写了个demo记录下实现。 基本知识介绍 Profile Profile通过对bean进行修饰&#xff0c;来限定spring在bean管理时的初始化情况&#…