C++相关概念和易错语法(19)(继承规则、继承下的构造和析构、函数隐藏)

1.继承规则

继承的本质是复用,是结构上的继承而不是内容上的继承,近似于在子类中声明了父类的成员变量。

(1)写法:class student : public person
     派生类(子类),继承方式,基类(父类) 

它们都分public、protected、private三种,但是含义并不相同。

(2)区分访问限定符和继承方式

访问限定符修饰的是在当前类里面是否可以访问,而继承方式是指在继承的类里面的访问方式,并且一个成员继承后的访问限定符是父类的访问限制符和子类继承方式两者间的较小权限如果A有public成员a,而以protectd继承,因此子类成员的访问限制符是protected。

访问权限由大到小是public>protected>private,protected和private在继承语法外没有区别。比如protected成员public继承后是protected,protected成员private继承后是private,public成员public继承后是public,这都非常容易理解。


#include <iostream>
using namespace std;class A
{
public:int _a = 0;
protected:int _b = 1;
private:int _c = 2;
};class B : public A
{
public:void GetNum(){cout << _a << " " << _b  << " " << _d << endl;}int _d = 3;
};int main()
{B().GetNum();return 0;
}

但是要注意private成员无论以何种方式继承,不管是public、protected还是private继承,最终子类成员的访问权限都是private且这个private成员不能被子类使用(包括访问,修改等)

看起来这个成员没有被继承,但实际上子类包含该成员。我们可以从监视窗口和类的大小双重验证这个结论。

(3)在继承里还规定友元关系不能继承,父类的友元不是子类的友元,只有子类自己声明友元

(4)形象理解:父的成员是protected意味着这些成员对外是个秘密,但在自己家庭中不算个秘密,但家庭中的每个人都有义务对外保守这个秘密,即protected无论以何种方式继承对外都不可见。

如果父成员有个private意味着这个成员是自己的秘密,不能给任何人说,比如家里有个隐藏的地下室。但是在继承给自己孩子这个房子时,孩子不知有这个地下室,但这个地下室真实存在,即private无论以何种方式继承对子对外均不可见,但这个private成员真实存在。

父的朋友不是子的朋友,即友元关系不能继承。

(5)protected和private区分

protected在继承以后才有意义。protected和private对外的功能都一样,都是不可见,它们区别在protected对整个继承体系开放(前提没有子类以private继承它),而private是只有该类能访问,其子类也不能访问。

当中途被继承为private后,对子该成员依然不可见。

(6)默认继承方式

我们也可以选择不写继承方式,这个时候class默认私有继承,struct默认以public继承,注意这和默认访问限定符一样,但这是两个概念。

2.继承的函数调用

我们先尝试解读下面的代码,这能帮助我们初步理解继承函数调用的特征:


#include <iostream>
using namespace std;class A
{
public:A(int a, int b, int c):_a(a),_b(b),_c(c){}void GetNum(){printf("%d %d %d\n", _a, _b, _c);}int _a = 0;
protected:int _b = 1;
private:int _c = 2;
};class B : public A
{
public:B():A(7, 8, 9), _d(5){}void GetNum(){printf("%d\n", _d);}int _d = 3;
};int main()
{B().A::GetNum();B().GetNum();return 0;
}

结果是

(1)构造函数

A作为父类它的构造函数正常写就是了,我们重点关注B的构造函数。

我们知道,构造函数实际构造的顺序是声明的顺序而非初始化列表的顺序。这里我们可以理解为B子类的第一个成员声明是匿名的A,在初始化时要根据A的构造函数把A当作一个整体用匿名初始化对象的方式A(7,8,9)来处理,不能直接对A的成员变量直接初始化继承的A对象和创建一个A对象一样都不会复制函数和static变量,在需要时都是直接去A里面取,即整个继承体系共享一套函数和static变量

(2)函数的隐藏

在继承体系中是允许出现同名函数的,但是这并不构成函数重载,因为函数重载的前提是必须在同一个作用域定义两个同名函数,而这里很明显是在两个类域里定义的同名函数,所以一定不构成重载,而是构成函数的隐藏(只需要同名就构成隐藏了,要和后面的多态区分开)。

如果构成了函数隐藏的话,应该怎么区分调用这两个函数呢?很多人会以为能像函数重载的调用那样根据参数匹配程度来调用,但这个逻辑在继承里走不通,万一我就想利用隐式类型转换调用子的成员函数,但是参数却匹配了父的成员函数呢?万一是我参数写错了导致匹配错误的情况呢?这些问题都会导致歧义的发生。所以规定,只要构成函数隐藏,调用父类一定要指明类域,调用当前类的不用指明,如果不指明类域,一律只按调用当前类的函数来处理,就算此时调用匹配父类的函数参数。

我们这里还可以学到,虽然我们不能直接访问父类的私有成员_c == 9,但是我们可以选择调用父类的非private和protected函数,从而达到间接访问的目的。

(3)析构函数与构造函数

析构函数的理解相对复杂,我们先看一下下面的代码,这能帮助我们深入理解构造和析构函数:


#include <iostream>
using namespace std;class A
{
public:~A(){delete _a;}int* _a = new int(1);
};class B : public A
{
public:B():_b(_a){}~B(){A::~A();*_b = 2;}int* _b;
};int main()
{B a;return 0;
}

结果是报错,接下来我会详细分析里面的代码

为什么写作A::~A();

析构函数的名字会被统一处理为destructor(),父子类的析构函数同名构成了函数隐藏,因此想要调用父的析构函数,就必须要指定类域,这个底层细节需要我们记住。这个特殊处理的原因在多态我会提及。

为什么报错?

由于在父类的析构函数调用后间接使用了父类的成员,所以出现越界访问的情况。

我们还能发现B的构造函数借助了A的成员变量。这里要区分开的是,在子类初始化父类时只能以父类为整体去调用它的构造函数如A(7,8,9),而本质上继承的只是它的成员变量,在子类的构造函数中可以直接用父类的成员。

如此一来,我们能发现,子类的构造函数可能会依靠父类的成员变量,若父类先析构,那在子类需要用到父类成员变量时就可能会发生越界访问。所以规定,父类的构造函数一定先于子类,父类的析构一定晚于子类(构造先父后子,析构先子后父)。

在实现层面上,由于子类的第一个成员变量默认是父类A,所以我们可以不用关心,毕竟初始化顺序是按声明而不是初始化列表。但是我们要避免显式地调用父类的析构,即A::~A()不要出现在我们的继承代码中。当子类的析构函数走完后,会去自动调用父类的析构。同理,如果没有显式写构造,编译器也会首先自动调用父类的默认构造。

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

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

相关文章

泛二级泛目录多模板程序程序(泛目录和二级目录的区别)

泛解析站群_优化网站排名吸引百度蜘蛛必备程序主要功能&#xff1a; 1、网站支持无限生成页面不存在死链的风险每个也是不是网站栏目就是文章内容! 2、支持域名泛解析绑定&#xff0c;每个二级域名都是一个独立的 3、支持百度自动提交收录&#xff0c;每天随机自动提交无限自己…

echarts图表:类目轴

category 类目轴&#xff0c;适用于离散的类目数据。 例如商品名称、时间等。 类目轴上的每个刻度代表一个类目&#xff0c;刻度之间没有量的关系&#xff0c;只是简单的分类。 在类目轴上&#xff0c;数据点会对应到相应的类目上。

运行前端项目提示 run `npm fund` for details,如何解决?

经常出现在前端的一个小坑&#xff0c;分享一下技巧。 运行npm install命令终端提示&#xff1a; 107 packages are looking for funding run npm fund for details 解决方案&#xff1a; npm install --no-fund

Linux 进程 PID 管理

文章目录 1. 前言2. 进程 PID 相关数据结构3. 进程 PID 的构建3.1 第一个进程 PID 构建3.2 第二个进程 PID 的构建过程3.2.1 从当前进程复制进程 PID 信息3.2.2 创建每进程的 PID 管理数据 (struct pid) 并初始化3.2.3 绑定进程和其相关的 PID 管理数据 3.3 进程的 PID 建立过程…

【Oracle】实验三 Oracle数据库的创建和管理

【实验目的】 掌握Oracle数据库的创建方法使用DBCA创建数据库在数据库中装入SCOTT用户及其表 【实验内容】 使用DBCA创建数据库&#xff0c;名为MYDB&#xff0c;找到其初始化文件(文本型和服务器型文件都要找到)&#xff0c;查看各类默认位置并记录下来(包括物理文件所在目…

LINUX系统编程:基于环形队列和信号量的生产者消费者模型

目录 1.环形队列 2.加上信号量的理解 3.代码 1.环形队列 环形队列使用vector封装出来的。 环形队列可以实现并发生产和消费&#xff0c;就是在消费的同时也可以生产。 这个是建立在生产者消费者位置不重合的情况下。 因为位置重合之后&#xff0c;环形队列为空或者满&#xf…

Linux DRM 那些事 - HDMI 接口 DTS 配置

本文基于RockPI 4A单板Debian系统 Linux 4.4 内核介绍DRM框架HDMI接口DTS配置。 在DTS中主要实现&#xff1a;HDMI的使能、VOP绑定、IOMUX引脚配置和HDMI控制器配置。 一、HDMI 配置 文件&#xff1a;arch/arm64/boot/dts/rockchip/rk3399-rock-pi-4.dtsi #include "rk3…

C++ 宏

C中的宏是一种预处理指令&#xff0c;用于在编译时将代码中的标识符替换为指定的文本。 #define 指令 1.无参宏定义 无参宏的宏名后不带参数。 其定义的一般形式为&#xff1a; #define 标识符 字符串 其中的“#”表示这是一条预处理命令。凡是以“#”开头的均为预处理命…

LLM 合成数据生成完整指南

大型语言模型是强大的工具&#xff0c;不仅可以生成类似人类的文本&#xff0c;还可以创建高质量的合成数据。这种能力正在改变我们进行 AI 开发的方式&#xff0c;特别是在现实世界数据稀缺、昂贵或隐私敏感的情况下。在本综合指南中&#xff0c;我们将探索 LLM 驱动的合成数据…

C语言——流程控制:if...else、switch...case

控制类语句&#xff1a; 逻辑运算符&#xff1a; 选择语句&#xff1a; if...else&#xff1a; if&#xff08;&#xff09;括号内的内容终究会被转换成0,1&#xff0c;满足的话即为1&#xff0c;不满足的话为0。因此要注意&#xff0c;&#xff08;&#xff09;括号内因为条件…

简单实现一个本地ChatGPT web服务(langchain框架)

简单实现一个本地ChatGPT 服务&#xff0c;用到langchain框架&#xff0c;fastapi,并且本地安装了ollama。 依赖安装&#xff1a; pip install langchain pip install langchain_community pip install langchain-cli # langchain v0.2 2024年5月最新版本 pip install bs4 pi…

ChatGPT摆脱“AI味”:全面提升写作质感

ChatGPT在各种写作、创作场景都发挥了很大的价值&#xff0c;即使中文语料库占比不到5%&#xff0c;也能生成流畅的中文文本。但随着使用的深入&#xff0c;大家也逐渐发现了机器生成的内容&#xff0c;往往带有一种僵硬、刻板的“AI味”&#xff0c;尤其在论文、自媒体写作中&…

算法力扣刷题记录 四十三【最大、最小深度问题】

前言 本文学习树的深度问题&#xff1a;二叉树&#xff08;N叉树&#xff09;最大深度、最小深度&#xff1b; 记录 三十九【层序遍历模版应用二】中解决过二叉树的最大深度和最小深度题目。思路是按层遍历&#xff1a; 最大深度&#xff0c;相当于层序遍历结束&#xff1b;…

ZBLOG程序怎么天收录?本人亲自试过请看以下教程(zblog怎么样)

您为管理员组&#xff0c;请查看下方隐藏内容&#xff01; 先去ZBLOG官网下载ZBLOG程序 直达地址https://www.zblogcn.com/ 安装到宝塔里 安装好了之后打开zblog的后台 点开应用中心搜索CMS自适应资讯主题免费 安装即可 安装了之后配置主题内容 有经验者可以去吧动态改成…

pnpm9.5.0(catalog协议)

catalog(目录协议) 目录是工作区功能&#xff0c;用于将依赖版本范围定义为可重用常量&#xff0c;目录中定义的常量可以在package.json中使用&#xff0c; 结合 pnpm-workspace.yaml使用 定义pnpm-workspace.yaml packages&#xff1a;定义多仓库 packages:- packages/*cata…

Flink异常:org/apache/hadoop/hive/ql/parse/SemanticException

在flink项目中跑 上面这段代码出现如下这个异常&#xff0c; java.lang.NoClassDefFoundError: org/apache/thrift/TException 加上下面这个依赖后不报错 <dependency> <groupId>org.apache.thrift</groupId> <artifactId>libthrift</artifactId…

【逆向基础】十、工具分享之DIE(Detect It Easy)

一、简介 DIE&#xff08;Detect It Easy&#xff09;是一款可以轻松检测PE文件的程序&#xff1b;其主要作用是查壳&#xff0c;并将pe文件的内容解析出来&#xff0c;包括PE文件中包含的导入函数、导出函数的名称及地址&#xff0c;入口函数地址等&#xff0c;是技术人员分析…

lua 脚本语言 : 基础到高级语法

❃博主首页 &#xff1a; 「码到三十五」 &#xff0c;同名公众号 :「码到三十五」&#xff0c;wx号 : 「liwu0213」 ☠博主专栏 &#xff1a; <mysql高手> <elasticsearch高手> <源码解读> <java核心> <面试攻关> ♝博主的话 &#xff1a…

JupyterNotebook中导出当前环境,并存储为requirements.txt

​使用Anaconda管理Python环境时&#xff0c;可以轻松地导出环境配置&#xff0c;以便在其他机器或环境中重新创建相同的环境。可以通过生成一个environment.yml文件实现的&#xff0c;该文件包含了环境中安装的所有包及其版本。但是&#xff0c;常常在一些课程中JupyterNotebo…

微信小程序毕业设计-学习资料库系统项目开发实战(附源码+论文)

大家好&#xff01;我是程序猿老A&#xff0c;感谢您阅读本文&#xff0c;欢迎一键三连哦。 &#x1f49e;当前专栏&#xff1a;微信小程序毕业设计 精彩专栏推荐&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb; &#x1f380; Python毕业设计…