c++继承

article/2025/6/24 13:46:09

继承的概念及定义介绍
 

什么是继承,继承是面向对象而言的,他的地基是基于类而言的。最简单的介绍就是,一个类继承了另一个的成员,可以使用这个类成员,并且可以在此基础上,定义自己的成员,从而实现自己的功能。

继承这一概念是取自于生活中而言的,面向对象的语言创建就是基于生活中而抽取概念。一般市面上讲继承都会拿Person和Student,这两个类介绍。大体就是说,这个学生既有Person的特性又有Student的特性,那这时候我们就可以提取这个Person特性,把它单独创建成一个类,让Studnet继承他,这样子以后创建其他类时,比如Teacher,它也可以继承Person类,因为这些类都有一个共性就是人的特性,比如名字,身份证,年龄等等,这和我们生活中时很相似的,就像你和你的老师,都有身份证和年龄。我们一般管这个继承类叫做派生类/子类,被继承类叫做基类/父类。

先看看代码

class Person
{protected:string _name;int _id;int _age;
};
class Student : public Person
{
protected:int _num; //学号string _class;//班级
};

他的语法是在派生类的后面加一个冒号 + 继承方式 + 类名,那什么是继承方式呢?继承方式和访问限定符的关键字一样,都是只有三个,private,protected,public。如果你没有指定继承方式,类是默认私有,结构体是默认公有。

这些继承方式和访问限定符搭配有九种不同的情况,详细满足了生活中的各种情况。这里的定义是,访问限定符和继承方式选择访问范围更小的哪一个。对于private的成员,在子类也不可见,也就是无法访问。私有成员只能在本类访问。但除了private成员都可以在子类访问。这里private(访问方式)是在访问限定符和继承方式之间选择权限更高的。拿上面的代码举例,Person的成员访问限定符是protected,继承方式是public,所以访问方式是protected,不是私有,所以他可以在Student里访问。这里九种的搭配方式属于有点太复杂了,一般使用继承就是要在子类里访问父类的成员,可是这里祖师爷居然连私有子类不能在子类里访问,这种情况都给我们想到。有点高估我们的需求了。虽然现实生活中确实有类似于这种情况,父亲去世后,资产没有全部给儿子,而是留去一部分给公益。但是对于我们使用这而言,很少有这种需求。这也可能是因为祖师爷再创建这部分知识的时候,也没有前人借鉴,只能取自于生活,原本是想着满足多种需求,没想到是想的复杂了。我们平常在使用的时候,一般就是protected加public的组合就够用了。

基类和派生类的对象的转换
 

首先支持子类对象自动转换到父类对象,这部分转换语法上称赋值兼容,也被称为切片或切割。子类转换到父类是不会产生临时对象的,这是他和内置类型隐式类型转换的本质区别。之所以,他被叫切片/切割,是一种比喻的手法,就像从子类中把父类的成员切割掉,然后再拷贝到父类中。实际上底层也和这很相似,底层是只会去匹配父类的域,赋值父类的成员。所以,这也被形象的比喻成切片。

Student s;
//没有产生临时对象
Person& p = s;int i = 1;
double& d = i; //产生临时对象

那父类可以转换成子类吗?实际上是可以的。不过这部分知识需要满足特定情形。设计到一些后面的知识。我们先知道他是有办法做到就行。本篇博客不对这部分知识讲解。

继承中的作用域

父类和子类是是一个域吗?还是他们不是一个域。如果不是一个域为啥子类可以访问父类的成员呢?答案是:两个独立的域,子类可以访问父类的成员,是因为他是protected的访问方式,这是c++的语法设计。这里再多嘴一句,这里的涉及的成员包括成员函数和成员变量,我在学习这部分知识的时候,就以为只有成员变量,就搞混了。

既然是两个独立的域?那如果子类和父类的成员重命,那这咋办?外界访问,是访问那个?这里就是就近原则,优先访问子类里的成员。这里语法上c++称为隐藏,也有人称重定义。这也是比喻的手法,仿佛子类的成员覆盖了父类的成员一样。实际上底层,人家就没去父类里去找。

大家看看这段代码结果是啥?

#include <stdio.h>
#include <iostream>
#include <string>
using namespace std;class Person
{
public:void fun(){}protected:string _name;int _id;int _age;
};
class Student : public Person
{
public:void fun(int i){}
protected:int _num; //学号string _class;//班级
};
int main()
{Student s;s.fun();s.fun(1);return 0;
}

是符合重载呢 还是符合隐藏 还是直接无法运行呢 

答案是无法运行,编译直接报错。这里成员函数是构成隐藏的,注意这里构成隐藏只需要满足名字相同就可以,至于其他条件是否有参数(成员函数)不管,这里没有指定域,所以默认在子类去找,首先函数名匹配,但发现形参不匹配,因此会编译报错。

这里建议尽量不要在继承体系中定义同名成员,会有很多坑,实际中也很少这样做。

派生类的默认成员函数

这里所谓的默认是指我们不写,编译器自动生成,这里就不介绍取地址重载和const修饰的取地址重载,这两个的使用少到可怜,基本不用自己实现。

构造函数

这里语法规定,我们不用在子类的构造函数里去再自己写一个父类的构造函数,完成父类的初始化,语法也不允许你这样做,他让你直接调用父类的构造函数,这里调用必须是在初始化列表里,这里是和初始化顺序有关,你必须先初始化父类成员再初始化子类成员,这其实很好理解,你和你父亲的关系,肯定是先有你父亲才有你吧。如果你不显示调用,那编译器会自动调用。要注意,编译器只能调用父类的默认构造函数,如果父类没有默认构造函数,那你需要手动调用。

	Student(string name, int id, int age,int num,string cls):Person(name,id,age),_num(num),_class(cls){}

拷贝构造

和构造函数类似,必须调用父类的拷贝构造函数,但不过你必须要显示调用,因为拷贝构造是有参数的,编译器不能自动调用,如果你不写,那调用的是默认的构造函数,那这效率就不能保证了。

Student(const Student& s):Person(s),_num(s._num),_class(s._class)
{}

赋值重载

赋值重载也是一样,调用父类的赋值重载,如果你不写,编译器会调用父类的赋值重载,但是如果你显示调用就必须指定域名,因为默认会在子类中寻找,这里因为构成隐藏,导致会栈溢出。也是一样,如果不写编译器自动调用父类的赋值重载,但是如果涉及到深拷贝,你必须显式调用。

	Son& operator=(const Son& s){if (&s != this){//你可以直接不调用 //如果涉及到了深拷贝 就要显示调用//Father::operator(s)_c = s._c;}return *this;}

析构函数

析构函数没有上面这么多规矩,因为栈的先进后出的特性,决定必须是父类后被析构。这里祖师爷为了防止你瞎搞,直接语法规定子类析构函数调用完成后,自动调用父类的析构函数。所以,你根本不用自己写,只用管自己子类的析构编写就行了。这里即使你在子类的析构函数调用了父类的析构函数,编译器还是会在子类析构函数执行完毕后,自动调用父类的析构函数。

继承与友元

友元不能继承,这里一句话就能说清楚,你父亲的朋友不一定是你的朋友,友元是双向奔赴,不是单方面继承,和爱情一样,你喜欢人家,人家不喜欢你,那叫舔狗。

继承与静态成员

静态成员可以继承,但是在继承体系中他们只有一个静态成员,也就是说,他们共享一个成员,他和中央空调一样,谁都可以用,但是不单独属于某一个人。

复杂的虚拟继承和菱形继承

一般来讲,学继承到这里就应该结束了。但是c++支持多态继承,这就很复杂了。虽然这很符合生活中的情形,你既有你爸爸的特征又有你妈妈的特征,你不仅可以继承你爸爸的财产还可以继承你妈妈的财产。但是这放在编程里可就复杂了。随便举一个例子,如图

我就问你,D里是有两份A的成员呢?还是只有一份呢?两份不就重复了吗?这不是没意义吗?重复的多继承一份成员, 如果你继承的不是财产是负债呢?按照我们祖师爷原来的语法是这样的,D有两个A的成员,你要赋值,需要指定类域。后面为了补齐这个坑,他增加了一个虚继承的语法,就是说,B和C虚继承A,这样子,B和C就不是直接存储A的成员,而是存储一个指针,这个指针指向一块地址,我们称这块地址指向的空间虚基表,这个指针叫虚基表指针,虚基表存储一个偏移量,这块存储指针的地址加上偏移量就是这个成员的地址。也就是说,他把重复的成员存储提取,只存取一份,B和C里都共用这个,只不过他们的偏移量可能不同,但不过都可以通过偏移量找到那个共同的成员地址,访问该成员。一般这个被提取的成员是在最后或者开头,我们访问这个成员也是可以直接访问,就算通过类域访问,也可以通过偏移量找到,就算切片,也是可以通过偏移量完成赋值。正是因为他的这种偏移量设计,才让访问得到统一。但其实这底层是很复杂,一般底层涉及地址和指针这块结合,就不简单。

我们可以计算一下不加虚拟继承和加上虚拟继承的大小,你会发现加了虚拟继承的还多4字节,这咋还多呢?实际上这是分情况的,你这里数据比较小,但如果你的数据是一个数组呢?数据类型为int,大小为128,你看这不对半节省吗。所以他是能解决数据冗余的问题。这里还涉及第二个问题就是第二语义性的问题,你多出来的成员A到底是算B的还是算C的,还是算D的。你要知道这是一个D类型的对象,应该算D的吧。如果这个A的成员表示一个人的IDCard,你还觉得它可以算C的还是D的,因此直接把它单独提取,谁都可以共用他。

注意这里的语法是在腰部的位置虚继承他,不是在最终类的位置虚继承。这里成员A在这里的继承体系中只有一份,既然只有一份,那他就会只调用一次构造函数。即使你在每个类里都显示调用了A的构造函数,那天也只会在D类的位置里调用A的构造函数。

上面的这种情况我们称为菱形继承。实际上多继承可以认为是c++的一个缺陷,祖师爷设计的时候也是没想好,出发点是很好,可是一到别人使用的时候,就发现你这不对呀。然后祖师爷就苦思冥想,咋改这个坑。这里如果直接把菱形继承禁止掉,多继承就不会有这么多问题,但是他确实保留了,那我们就只能谨慎使用了。如果非要使用菱形继承,就要使用菱形虚拟继承,解决数据冗余和第二语义性问题。其实我们库里的IO流就是这样设计的,这我也不知道他是咋想的。

总结

关于c++的继承确实有点复杂,但这确实证明了c++非常灵活,只要你能把握住,想咋玩就咋玩。一般我们不全使用继承,也会使用组合,主要看那个更符合语义。比如轮胎和车子,这个就更适合组合,就是在车类里私有声明一个 轮胎类。但是有时候还是继承符合语义,那我们就用继承。如果你发现他既符合组合的语义又符合继承的语义,那优先使用组合。这里其实和黑白盒测试相关。黑盒测试(看不见内部实现,通过功能写测试 ) ,白盒测试(看见内部实现,根据内部实现写测试 ),为啥使用组合,这下明白了吧,明显组合使用更简单,也就是我们的黑盒测试。如果使用的是继承,那你就要全方面考虑情况,搭配白盒测试,你就要看懂内部实现的代码。优先使用组合还有一个原因就是,高内聚(功能单一)低耦合(相关程度低),这是软件开发的一个评判的黄金原则。


http://www.hkcw.cn/article/WbRnqMeOuw.shtml

相关文章

【后端高阶面经:架构篇】56、大数据技术架构的思想和原理是什么?

一、大数据架构的核心思想:分而治之的分布式哲学 (一)分布式存储与计算的本质 大数据架构的终极目标是解决单机存储与计算的性能瓶颈,其核心在于将数据与计算任务分散到多台廉价服务器,通过协同工作突破物理限制。这一思想的实现依赖两大基石: 数据分片(Data Sharding…

联想发布C55数码相机 6400万像素新选择

联想在京东上架了一款型号为C55的6400万像素数码相机,现已开售。该相机提供64GB和128GB两种版本,售价分别为499元和559元,首发价则为424元和475元。部分地区还支持叠加15%的国家补贴。这款相机采用了6400万像素的索尼CMOS传感器,尺寸为1/3英寸,配备2.8英寸液晶屏,支持ISO…

律师:厂家远程锁电动车侵权 经销商财产权受损

厂家远程锁电动车侵权 经销商财产权受损!最近,盐城市民王先生遇到了一个棘手的问题。去年他加盟销售一款品牌电动车,但由于销量不佳,最终自行关闭了店铺。在与厂家协商店铺装修赔偿事宜时,厂家通过技术手段将王先生已经付全款的库存电动车全部锁死,导致这批车辆无法正常销…

武汉女足主帅:王霜是核心,展现强大心脏

率队夺得女足亚冠冠军后,武汉车谷江大女足主教练常卫魏在接受采访时说:“赢得亚冠的历程,值得我一辈子回忆。陈晨成为扑点球的专家了,王霜展现了核心的价值。”关于门将陈晨在决赛中的表现,常卫魏提到,首发门将丁旋在上半场与对方球员有一次碰撞,中场休息时她表示腰部非…

【软件测试】火狐驱动下载镜像

CNPM Binaries Mirrorhttps://registry.npmmirror.com/binary.html?pathgeckodriver/

windows 缓冲区溢出实验关于VS的一些配置 (逆向与漏洞分析 (魏强) (Z-Library))

使用vs编写缓冲区溢出demo 的配置 最近在看 逆向与漏洞分析 (魏强) (Z-Library) 这本书&#xff0c;书上的关于缓冲区溢出的实验代码&#xff0c;使用vs 编写代码编译出来的可执行程序默认情况下就会存在一系列保护&#xff0c;如何不修改的话真的就调试不了书上的实验。主要是…

西红柿番茄成熟度目标检测数据集介绍

随着智能农业的发展&#xff0c;果蔬成熟度识别逐渐成为自动化采摘与质量控制中的核心问题之一。为了支持基于深度学习的目标检测算法对西红柿不同成熟阶段进行识别与分类&#xff0c;我们构建了一个西红柿成熟度目标检测数据集&#xff0c;该数据集包含三种成熟度类别&#xf…

大数据-275 Spark MLib - 基础介绍 机器学习算法 集成学习 随机森林 Bagging Boosting

点一下关注吧&#xff01;&#xff01;&#xff01;非常感谢&#xff01;&#xff01;持续更新&#xff01;&#xff01;&#xff01; 大模型篇章已经开始&#xff01; 目前已经更新到了第 22 篇&#xff1a;大语言模型 22 - MCP 自动操作 FigmaCursor 自动设计原型 Java篇开…

贪心算法应用:线性规划贪心舍入问题详解

贪心算法应用&#xff1a;线性规划贪心舍入问题详解 贪心算法是一种在每一步选择中都采取当前状态下最优的选择&#xff0c;从而希望导致结果是全局最优的算法策略。在线性规划问题中&#xff0c;贪心算法特别是贪心舍入技术有着广泛的应用。下面我将全面详细地讲解这一主题。…

【LLM vs Agent】从语言模型到智能体,人工智能迈出的关键一步

目录 一、什么是 LLM&#xff1f;语言的天才&#xff0c;思维的起点 ✅ 特点小结&#xff1a; 二、什么是 Agent&#xff1f;智能的执行者&#xff0c;自主的决策者 ✅ 特点小结&#xff1a; 三、LLM 与 Agent 的关系&#xff1a;是工具&#xff0c;更是大脑 四、案例实战…

esp32 platformio lvgl_gif的使用和踩坑情况

踩坑一&#xff1a;白屏 不显示 开启custom内存这里 以及 gif显示 踩坑二&#xff1a;只有图片 不显示动态 没开时间 要打开这个时基 网站:转成c数组的官方网站 Image Converter — LVGL 以及显示代码&#xff1a;在setup里面调用 LV_IMG_DECLARE(my_gif);lv_obj_t *img;img…

华南沿海等地较强降雨持续 局地伴有强对流天气

今明两天(6月3日至4日),我国降雨主要出现在云南和华南沿海、东北地区等地,局地还可能伴有强对流天气。随着高压脊东移,北方大部气温逐渐升高,华北、黄淮等地高温天气将发展增多,南方多地5日起也将加入高温行列。昨天,冷空气南下导致南方强降雨区域南压至华南和云南一带…

黄金白银原油大涨 市场热度持续升温

黄金原油市场收盘大涨,贵金属与能源品种展现出强劲涨势。COMEX黄金期货和白银期货分别以显著涨幅收盘,其中黄金期货收涨2.74%,报3406.4美元/盎司;白银期货收涨5.76%,报34.93美元/盎司。原油市场同样表现抢眼,WTI原油期货收于每桶62.52美元,上涨1.73美元,涨幅为2.85%;布…

“蹦床外长”当选联大主席 外交新挑战

蹦床外长当选联大主席!一夜醒来,联合国大会发生了重大变化。周一下午,在纽约联合国总部举行的全体会议上,被称为“蹦床外长”的安娜莱娜贝尔伯克以167票当选为联合国大会主席。这距离她离开德国外长之位不到一个月。这份工作需要相当强的外交技巧,一些批评人士认为这是贝尔…

python依赖库管理工具

软件名称&#xff1a;Python依赖管理工具 版本&#xff1a;1.0适用系统&#xff1a;Windows 7/10/11&#xff0c;Python 项目环境管理辅助工具 开发语言&#xff1a;Python 3 PyQt5 开发者&#xff1a;Thebzk 软件功能简介&#xff1a; 本工具是专为Python 开发者设计的图…

labuladong刷题之前缀和与差分数组技巧(空间换时间)

Q1题目链接&#xff1a;https://leetcode.com/problems/range-sum-query-immutable/ leetcode 303 看到题目的第一思路&#xff1a; 代码随想录之后的想法和总结&#xff1a; 如何用 prefix 查询区间和&#xff1f; 如果你想查询&#xff1a;nums[left] nums[left1] ... …

基于爬取的典籍数据重新设计前端界面

1.BooksView(书籍列表页) 2.ClassicsView&#xff08;目录页&#xff09; 3.管理员端

Origin将杂乱的分组散点图升级为美观的带颜色映射的气泡图

图形初步了解 带颜色映射的气泡图&#xff0c;是一种含有三个变量的高级散点图&#xff0c;前两个变量分别为横纵坐标&#xff0c;第三个变量通过气泡的大小和颜色来呈现。在视觉上辅助读者直观地比较三个变量的关系。 在本例图中&#xff0c;通过pH、Group和Solubility三个指…

初识Linux指令(笔记2)

&#xff08;1&#xff09;man指令&#xff08;重要&#xff09; Linux的命令有很多参数&#xff0c;我们不可能全记住&#xff0c;我们可以通过查看联机手册获取帮助。访问Linux手册页的命令是man 语法: man [选项] 命令 &#xff08;2&#xff09;cp指令&#xff08;重要&…

w374预报名管理系统设计与实现

&#x1f64a;作者简介&#xff1a;多年一线开发工作经验&#xff0c;原创团队&#xff0c;分享技术代码帮助学生学习&#xff0c;独立完成自己的网站项目。 代码可以查看文章末尾⬇️联系方式获取&#xff0c;记得注明来意哦~&#x1f339;赠送计算机毕业设计600个选题excel文…