c++继承总结

news2025/2/12 1:11:46

一 继承的由来

    我使用类也有一段时间了,慢慢觉得我们做一件事时,就是要先描述,例如写一个管理系统登记学校成员的信息,我们就要先对在学校内的老师和学生做描述,学生要有年龄,班级,姓名,学号和身份证,描述老师要有年龄,姓名,工号和身份证,分别用一个类来将这些信息封装起来,描述完后,实例化出一个对象可以存一个学生的信息,存多个学生那就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.coloradmin.cn/o/864357.html

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

相关文章

postman接口测试中文汉化教程

想必同学们对于接口测试工具postman的使用并不陌生&#xff0c;以及最近大为流行的国产工具apifox。对于使用过的同学来说&#xff0c;两者区别以及优缺点很容易别展示出来&#xff0c;postman相比apifox来说更加轻量&#xff0c;但是apifox更加符合国人的使用习惯....中国人给…

谷歌云 | BigQuery 现在支持用于查询开放表格式的清单文件

Cloud Ace 是谷歌云全球战略合作伙伴&#xff0c;拥有 300 多名工程师&#xff0c;也是谷歌最高级别合作伙伴&#xff0c;多次获得 Google Cloud 合作伙伴奖。作为谷歌托管服务商&#xff0c;我们提供谷歌云、谷歌地图、谷歌办公套件、谷歌云认证培训服务。 开放表格式依赖嵌…

C++:string类模拟实现

C&#xff1a;string类模拟实现 成员变量构造和析构容量相关1.获取容器大小(_size)和容量(_capacity)2.扩容(reserve)3.更改容器大小 修改相关1.尾插2.指定位置插入3.指定位置删除4.清空5.交换两个对象 比较相关访问相关迭代器相关查找相关其它成员函数1.截取子串2.取得C格式字…

交融动画学习

学习抖音&#xff1a; 渡一前端教科频道 利用 filter 的属性实现交融效果 变成 让后利用这个效果实现一个功能 实现代码&#xff1a; <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><style>* {margin: 0;…

c# .net mvc的IHttpHandler奇妙之旅--图片文件请求安全过滤,图片防盗链

在阅读该文章前,请先阅读该文章 c# .net mvc的IHttpHandler奇妙之旅。.net的生命周期和管道你听说过吗?你可以利用他处理业务如:跳转业务页面,文件请求的安全过滤,等等,还有许多神秘业务等着你去发现_橙-极纪元的博客-CSDN博客 该篇文章延续上面文章的第二小节《二、文件…

StarRocks企业级数据库

第1章 StarRocks简介 1.1 StarRocks介绍 StarRocks是新一代极速全场景MPP数据库 StraRocks充分吸收关系型OLAP数据库和分布式存储系统在大数据时代的优秀研究成果&#xff0c;在业界实践的基础上&#xff0c;进一步改进优化、升级架构&#xff0c;并增添了众多全新功能&…

Cadence OrCAD Capture CIS批量替换GND符号的方法

🏡《总目录》   🏡《宝典目录》 目录 1,概述2,方法3,总结1,概述 如下图所示,有时由于绘图是从多个地方复制粘贴而来,一个图纸中会存在多种GND符号。此时比较容易引起GND网络名不同意的问题,为了避免该问题可对其批量替换。 2,方法 第1步:选择需要替换的GND符号…

【Nginx】Nginx的优化和防盗链

nginx版本迭代比较快 *工作中&#xff0c;在发版期&#xff0c;通常先备份文件并备注时间&#xff0c;方便后期故障后回档 例&#xff1a; cp nginx.conf nginx.conf.bak.2023.0805 隐藏版本号的两种方法*** 1.修改配置文件 vim /usr/local/nginx/conf/nginx.conf 在http模…

分享花店行业小程序平台搭建教程

随着移动互联网的快速发展&#xff0c;花店也开始意识到拥有一个专属的小程序能够提升用户体验、增加销售额。那么&#xff0c;如何快速搭建一个漂亮、实用的花店小程序呢&#xff1f;下面就为大家介绍一下具体的步骤。 第一步&#xff0c;使用第三方制作平台。如乔拓云网是一个…

Linux下C语言调用libcurl库获取天气预报信息

一、概述 当前文章介绍如何在Linux&#xff08;Ubuntu&#xff09;下使用C语言调用libcurl库获取天气预报的方法。通过HTTP GET请求访问百度天气API&#xff0c;并解析返回的JSON数据&#xff0c;可以获取指定城市未来7天的天气预报信息。 二、设计思路 【1】使用libcurl库进…

24届近5年南京大学自动化考研院校分析

今天给大家带来的是南京大学控制考研分析 满满干货&#xff5e;还不快快点赞收藏 一、南京大学 学校简介 南京大学是一所历史悠久、声誉卓著的高等学府。其前身是创建于1902年的三江师范学堂&#xff0c;此后历经两江师范学堂、南京高等师范学校、国立东南大学、国立第四中…

数字孪生和元宇宙之间的差别与联系是什么?

元宇宙和数字孪生是两个引人瞩目的概念&#xff0c;它们在虚拟与现实世界的交汇点上呈现出独特的视角和应用。虽然二者都涉及数字化和模拟技术&#xff0c;但在其差异与联系上&#xff0c;我们可以发现深刻的内涵和潜力。 首先&#xff0c;元宇宙是一个更为宽泛的概念&#xf…

嵌入式是大坑吗?

嵌入式不是坑&#xff0c;但里面遍地是坑。一不小心&#xff0c;你就会掉进去。 嵌入式覆盖的范围太广&#xff0c;低端的产品太多。 单片机叫嵌入式&#xff0c;开发板叫嵌入式&#xff0c;摄像头叫嵌入式&#xff0c;手机、平板电脑、通讯基站、无人机、机器人、自动驾驶汽…

C++之红黑树剖析

博主&#xff1a;拖拉机厂第一代码手 gitee:拖拉机厂第一代码手 已收录到专栏C&#xff0c;点击访问 目录 &#x1f4b4;红黑树简介&#x1f4b5;红黑树的插入操作&#x1f4b6;红黑树的删除操作&#x1f4b7;红黑树的实现&#x1f4b8;红黑树节点的定义&#x1f4b8;红黑树结构…

autoware 之 op_behavior_selector行为选择器状态机代码分析

autoware 1 op_behavior_selector行为选择器状态机代码分析 /这里是整个状态机运行时的结构&#xff1a;/ //停止状态:[#停止状态]//任务完成状态:[#任务完成状态]//转向状态:[*前进状态,#转向状态]//停止信号停止状态:[*停止信号等待状态,#停止信号停止状态]//前进状态 :[*目…

C语言学习系列-->看淡指针(1)

文章目录 一、概述二、指针变量和地址2.1 取地址操作符2.2 指针变量和解引用操作符2.2.1 指针变量2.2.2 拆解指针类型2.2.4 解引用操作符 2.3 指针变量的大小 三、指针变量的意义3.1 指针的解引用指针-整数 四、 const修饰指针五、指针运算5.1 指针- 整数5.2 指针-指针5.3 指针…

OpenText 企业安全 调查 产品简介

关于OpenText OpenText是一家信息软件公司&#xff0c;使企业能够通过市场领先的信息管理解决方案&#xff08;内部或云中&#xff09;获得洞察力。 全球面临的数字风险 市场合力驱动的信息管理 处于风暴中心的信息 →安全漏洞和数据保护 • 防止威胁并将破坏影响降至最低 …

通过PostMan监视提交文件,验证web文件传输

切换文件流,传输文件 找到图片地址 发送请求然后接受 再来一张 哈&#xff0c;谢谢各位同志的阅读&#xff0c;然后呢如果觉得本文对您有所帮助的话&#xff0c;还给个免费的赞捏 Thanks♪(&#xff65;ω&#xff65;)&#xff89;

【二】数据库系统

数据库系统的分层抽象DBMS 数据的三个层次从 数据 到 数据的结构----模式数据库系统的三级模式&#xff08;三级视图&#xff09;数据库系统的两层映像数据库系统的两个独立性数据库系统的标准结构 数据模型从 模式 到 模式的结构----数据模型三大经典数据模型 数据库的演变与发…