【C++面向对象】--- 继承 的奥秘(下篇)
个人主页:平行线也会相交💪
欢迎 点赞👍 收藏✨ 留言✉ 加关注💓本文由 平行线也会相交 原创
收录于专栏【C++之路】💌
本专栏旨在记录C++的学习路线,望对大家有所帮助🙇
希望我们一起努力、成长,共同进步。🍓
目录
- 一、作用域
- 出个小题
- 小总结
- 二、派生类的默认成员函数
- 构造函数
- 拷贝构造函数
- 赋值运算符重载
- 析构函数
- 小总结
- 三、继承与友元
- 四、继承和静态成员
一、作用域
接下来对C++继承体系中的作用域展开分析。
在C++继承体系中,子类和父类有各自的作用域,所以子类和父类可以定义同名的成员。
请看针对不同作用域的举例:
局部域和当前类域
这里有个小概念:
隐藏/重定义
:子类和父类有同名成员时,子类的成员隐藏了父类的成员。(如上左图所示)
指定当前的父域:
作用域当然也对成员函数起作用,请看:
出个小题
类B和类A中的fun()函数有什么关系。
class A
{
public:void fun(){cout << "fun()" << endl;}
};
class B : public A
{
public:void fun(int i){cout << "fun(int i)" << endl;}
};
B中的fun和A中的fun构成隐藏,成员函数满足函数名相同就构成隐藏。并不会构成函数重载(因为函数重载针对的是不同的作用域)
小总结
- 在继承体系中基类和派生类都有独立的作用域。
- 子类和父类中有同名成员,子类成员将屏蔽父类对同名成员的直接访问,这种情况叫隐藏,
也叫重定义。(在子类成员函数中,可以使用基类::基类成员
进行显示访问,举个例子就比如说:B b; b.A::fun();
) - 需要注意的是如果是成员函数的隐藏,只需要函数名相同就构成隐藏。
- 但是其实在实际中在继承体系里面最好不要定义同名的成员(省的给自己添麻烦)。
二、派生类的默认成员函数
再来回顾一下C++中的6个默认成员函数:构造函数、析构函数、拷贝构造函数、赋值运算符重载、取地址及const取地址运算符重载。
构造函数
class Person
{
public:Person(const char* name = "peter"): _name(name){cout << "Person()" << endl;}~Person(){cout << "~Person()" << endl;}
protected:string _name; // 姓名
};
class Student : public Person
{
public:Student(const char* name = "李四",int id = 0):_id(0){}
protected:int _id;
};
int main()
{Student s;return 0;
}
运行结果如下:
上述代码中我们并没有定义Person类对象,但是却调用了Person类中的默认构造函数,为什么呢?
因为C++规定了派生类必须调用父类的成员函数来初始化父类的成员变量。
这里是在初始化列表来调用父类中的默认成员函数的。
在来看下面的情况,请看:
解释:在创建Student对象时,先调用Person类的构造函数来初始化Person类的成员变量_name,然后再调用Student类的构造函数来初始化Student类的成员变量_id。
所以这里是Person类中的成员函数先进行初始化,然后再对Student中的成员进行初始化。即派生类的构造函数在执行之前,基类的构造函数必须首先完成。
重点:通过使用初始化列表,并在其中调用基类的构造函数来初始化基类的成员变量,可以确保在派生类的构造函数中正确初始化基类的数据成员。这是由于派生类的构造函数在执行之前,基类的构造函数必须首先完成。
拷贝构造函数
class Person
{
public:Person(const char* name = "peter"): _name(name){cout << "Person()" << endl;}Person(const Person& p): _name(p._name){cout << "Person(const Person& p)" << endl;}~Person(){cout << "~Person()" << endl;}
protected:string _name; // 姓名
};
class Student : public Person
{
public://构造函数Student(const char* name = "李四",int id = 0):_id(0),Person(name){}//拷贝构造函数Student(const Student& s):Person(s), _id(s._id){}
protected:int _id;
};
int main()
{Student s1;Student s2(s1);return 0;
}
运行结果如下:
如果我们去掉基类拷贝构造函数中的Person(s)会怎样呢(即没有显式调用基类中的拷贝构造函数)?
解析:去掉Person(s)将导致基类Person的成员变量_name不会被复制,而是会调用基类中的默认构造函数,而倘若此时基类也没有提供默认构造函数的话就会直接报错。
所以,我们应该显式调用拷贝构造函数。如下:
//拷贝构造函数
Student(const Student& s):Person(s)//这里要显式调用拷贝构造函数,否则会调用基类中的默认构造函数, _id(s._id)
{}
一句话总结:派生类的拷贝构造函数必须调用基类的拷贝构造完成基类的拷贝初始化。
赋值运算符重载
//父类赋值运算符重载
Person& operator=(const Person& p)
{cout << "Person operator=(const Person& p)" << endl;if (this != &p)_name = p._name;return *this;
}
//子类赋值运算符重载
Student& operator=(const Student& s)
{cout << "Student& operator= (const Student& s)" << endl;if (this != &s){Person::operator=(s);_id = s._id;}return *this;
}
运行结果如下:
这里有的小伙伴看到Student s2 = s1;
可能会产生疑惑,为什么这里不调用赋值运算符重载函数。
解答
:
因为在语句Student s2 = s1;中,发生的是对象的初始化,而不是赋值操作。
当使用Student s2 = s1;来初始化一个已存在的对象s2时,会调用拷贝构造函数而不是赋值运算符重载函数。拷贝构造函数用来创建一个新对象,并将其内容初始化为另一个同类型对象的副本。
如果要调用赋值运算符重载函数,需要使用赋值操作符=来对已存在的对象进行赋值,例如s2 = s1;。这样才会调用赋值运算符重载函数,将s1的值赋给s2。
析构函数
//父类析构函数
~Person()
{cout << "~Person()" << endl;
}
//子类析构函数
~Student()
{cout << "~Student()" << endl;
}
在C++中,无法显式调用父类的析构函数。当一个派生类对象被销毁时,首先会自动调用派生类的析构函数,然后再自动调用基类的析构函数(即按照先父后子的顺序来完成对对象的析构)。
如果要显式调用是没有办法保证先子后父进行析构的。
小总结
- 派生类的构造函数必须调用基类的构造函数初始化基类的那一部分成员。如果基类没有默认
的构造函数,则必须在派生类构造函数的初始化列表阶段显示调用。 - 派生类的拷贝构造函数必须调用基类的拷贝构造完成基类的拷贝初始化。
- 派生类的operator=必须要调用基类的operator=完成基类的复制。
- 派生类的析构函数会在被调用完成后自动调用基类的析构函数清理基类成员。即按照先清理派生类对象,再清理基类对象的顺序。
- 派生类对象初始化先调用基类构造再调派生类构造;同时派生类对象初始化先调用基类构造再调派生类构造。
三、继承与友元
友元关系不能继承,即基类友元不能访问子类私有和保护成员,基类的友元只能访问基类的成员而不能访问派生类的成员。
class Student;
class Person
{
public:friend void Display(const Person& p, const Student& s);
protected:string _name; // 姓名
};
class Student : public Person
{friend void Display(const Person& p, const Student& s);
protected:int _stuNum; // 学号
};
void Display(const Person& p, const Student& s)
{cout << p._name << endl;cout << s._stuNum << endl;
}
int main()
{Person p;Student s;Display(p, s);return 0;
}
解释:Person类和Student类互相引用对方作为友元函数,因此需要先进行一次前向声明(即开头的class Student;
)。这样可以确保在实际定义这两个类的成员函数之前,编译器已经知道这两个类的存在。
四、继承和静态成员
class Person
{
public:Person() { ++_count; }
//protected:string _name; // 姓名
public:static int _count; // 统计人的个数。
};
int Person::_count = 0;
class Student : public Person
{
protected:int _stuNum; // 学号
};
class Graduate : public Student
{
protected:string _seminarCourse; // 研究科目
};
int main()
{Person p;Student s;cout << &p._name << endl;cout << &s._name << endl;cout << &p._count << endl;cout << &s._count << endl;cout << &Person::_count << endl;cout << &Student::_count << endl;
}
运行结果如下:
静态成员变量是一种属于类而不是类的实例的变量。它在所有类的实例之间共享,并且在整个程序的生命周期中只存在一个副本。静态成员变量是在类定义外部进行初始化的。
静态成员变量适用于在类的多个实例之间共享数据,并且可以通过类名直接访问,而无需实例化类对象。它们在数据共享和数据统计方面非常有用。需要注意的是,静态成员变量仅属于类,而不属于类的任何特定实例。
静态成员变量的访问方式:静态成员变量可以使用类名::成员变量名的方式进行访问(即类名::成员变量名
),例如Person::_count
。
下面请看下面代码,要统计Person类及其Person派生类对象总共创建了多少个
class Person
{
public:Person() { ++_count; }
//protected:string _name; // 姓名
public:static int _count; // 统计人的个数。
};
int Person::_count = 0;
class Student : public Person
{
protected:int _stuNum; // 学号
};
class Graduate : public Student
{
protected:string _seminarCourse; // 研究科目
};
int main()
{Person p;Student s1;Student s2;Student s3;Graduate s4;cout << Person::_count << endl;
}
运行结果:Person类及其派生类对象总共创建了4个对象
。
解释:在代码中,将_count定义为静态成员变量是为了在整个类层级中共享同一个计数变量。当创建派生类对象时,构造函数会依次调用每个类的构造函数,包括父类的构造函数。所以在父类的构造函数中进行++_count
操作,可以确保每个派生类对象的创建都能正确地增加计数。
好了,本文到这里就结束了,希望对大家学习C++继承体系有所帮助。
再见啦,友友们!!!
相关文章:

【C++面向对象】--- 继承 的奥秘(下篇)
个人主页:平行线也会相交💪 欢迎 点赞👍 收藏✨ 留言✉ 加关注💓本文由 平行线也会相交 原创 收录于专栏【C之路】💌 本专栏旨在记录C的学习路线,望对大家有所帮助🙇 希望我们一起努力、成长&…...

Android 面试笔记整理-Binder机制
作者:浪人笔记 面试可能会问到的问题 从IPC的方式问到Binder的优势为什么zygote跟其他服务进程的通讯不使用BinderBinder线程池和Binder机制 等等这些问题都是基于你对Binder的理解还有对其他IPC通讯的理解 IPC方式有多少种 传统的IPC方式有Socket、共享内存、管道…...

编程小白的自学笔记十三(python办公自动化读写文件)
系列文章目录 编程小白的自学笔记十二(python爬虫入门四Selenium的使用实例二) 编程小白的自学笔记十一(python爬虫入门三Selenium的使用实例详解) 编程小白的自学笔记十(python爬虫入门二实例代码详解)…...

【Mariadb高可用MHA】
目录 一、概述 1.概念 2.组成 3.特点 4.工作原理 二、案例介绍 1.192.168.42.3 2.192.168.42.4 3.192.168.42.5 4.192.168.42.6 三、实际构建MHA 1.ssh免密登录 1.1 所有节点配置hosts 1.2 192.168.42.3 1.3 192.168.42.4 1.4 192.168.42.5 1.5 192.168.42.6 …...
网络五层协议
应用层(http,https),传输层(udp,tcp),网络层(ip),数据链路层,物理层 什么是http?http 与https 的区别_日晞的博客-CSDN博客 TCP 与UDP 区别_互联网业务udp小包传输_日晞的博客-CSDN博客...

零售行业供应链管理核心KPI指标(一) – 能力、速度、效率和成本
有关零售行业供应链管理KPI指标的综合性分享,涉及到供应链能力、速度、效率和成本总共九大指标,是一个大框架,比较核心也比较综合。 衡量消费品零售企业供应链管理效率和水平的核心KPI通常有哪些? 图片来源-派可数据(…...
MySQL面试题二
1、关系型和非关系型数据库的区别? 关系型数据库的优点 容易理解,因为它采用了关系模型来组织数据。 可以保持数据的一致性。 数据更新的开销比较小。 支持复杂查询(带 where 子句的查询) 非关系型数据库(NOSQL&#x…...

码银送书第五期《互联网广告系统:架构、算法与智能化》
广告平台的建设和完善是一项长期工程。例如,谷歌早于2003年通过收购Applied Semantics开展Google AdSense 项目,而直到20年后的今天,谷歌展示广告平台仍在持续创新和提升。广告平台是负有营收责任的复杂在线平台,对其进行任何改动…...
分布式理论
CAP和BASE CAP C一致性(Consistency) 在分布式环境下,一致性是指数据在多个副本之间能否保持一致性的特征。在一致性的需求下,当一个系统在数据一致的状态下执行更新操作后,应该保证系统的数据仍然处于一致性的状态…...

Excel设置某列或者某行不某行不可以编辑,只读属性
设置单元格只读的三种方式: 1、通过单元格只读按钮,设置为只为 设置行或者列的只读属性,可以设置整行或者整列只读 2、设置单元格编辑控件为标签控件(标签控件不可编辑) 3、通过锁定行,锁定行的修改。锁定的行与只读行的区别在于锁定的行不…...

vue elementui v-for 循环el-table-column 第一列数据变到最后一个
这个动态渲染table表格时发现el-table-column 第一列数据变到最后一个 序号被排到后面 代码 修改后 <el-table:data"tableData"tooltip-effect"dark"style"width: 100%"height"500"><template v-for"(item, index) i…...

宝塔部署阿里云盘webdav
安装Docker 我的系统是CentOS8,如果直接安装会出错,可以看这篇文章:Failed to download metadata for repo ‘appstream‘ docker 国内镜像: http://hub-mirror.c.163.com/下载镜像 宝塔安装docker管理器,然后搜索…...

Ceph分布式存储系统优化分析
Ceph支持多种存储访问接口,现有的多种性能测试工具都可用于Ceph的性能测试,如测试块接口性能的fio,iometer等;测试CephFS接口的filebench,fio等;测试对象接口的cosbench等。Ceph有专用的基准测试集CBT,其包…...
supOS APP开发者课程练习册创建服务(命名:getPropertiesHistory)
创建服务(命名:getPropertiesHistory),调用getPropertiesHistory()服务,获取“催化裂化一车间”对象的“重质馏分油_进”最近5分钟内的历史值,每一分钟取一个值,开始时间和结束时间需要调用时间格式化功能集…...

认识excel篇3之数据的有效性(数据验证)
数据有效性不仅能够对单元格的输入数据进行条件限制,还可以在单元格中创建下拉列表菜单方便用户选择输入。如果没有做数据验证,单元格内默认可以输入任意类型的数据。数据验证就是限制单元格输入数据(必须输入符合要求的才能输入)…...

adb 命令行执行单元测试
文章目录 1、配置 adb 环境变量2、adb 执行测试3、官方文档解读 adb 使用(1)第一条执行测试的adb命令(2)am instrument 参数(3)-e 参数 的 key-value键值对(4)用法用例 4、存在问题 …...
Ceph入门到精通-Linux下Ceph源码编译和GDB调试
Ceph版本:14.2.22 Linux版本:ubuntu-server 18.04 第一部分 下载Ceph源码 1.1 配置Ceph源码镜像源 Ceph源码是托管在Github上,由于某些原因,国内访问Github网站很慢,所以需要从其他途径加速获取源码。Github官方给出…...

【c语言】动态内存管理(超详细)
他治愈了身边所有人,唯独没有治愈他自己—超脱 csdn上的朋友你们好呀!!今天给大家分享的是动态内存管理 👀为什么存在动态内存分配 我们定义的局部变量在栈区创建 int n 4;//在栈上开辟4个字节大小int arr[10] { 0 };//在栈上开…...

Linux/centos上如何配置管理NFS服务器?
Linux/centos上如何配置管理NFS服务器? 1 NFS基础了解1.1 NFS概述1.2 NFS工作流程 2 安装和启动NFS服务2.1 安装NFS服务器2.2 启动NFS服务 3 配置NFS服务器和客户端3.1 配置NFS服务器3.2 配置NFS客户端 4 实际示例4.1 基本要求4.2 案例实现 1 NFS基础了解 NFS&…...
Element组件浅尝辄止5:Empty 空状态组件
Empty空状态组件:空状态时的占位提示。 如第一次进入当前功能模块时,数据状态为空,则展示空状态,可用到Empty组件 1.How? <el-empty description"描述文字"></el-empty> 2.自定义图片 通过设置 image 属…...

19c补丁后oracle属主变化,导致不能识别磁盘组
补丁后服务器重启,数据库再次无法启动 ORA01017: invalid username/password; logon denied Oracle 19c 在打上 19.23 或以上补丁版本后,存在与用户组权限相关的问题。具体表现为,Oracle 实例的运行用户(oracle)和集…...
设计模式和设计原则回顾
设计模式和设计原则回顾 23种设计模式是设计原则的完美体现,设计原则设计原则是设计模式的理论基石, 设计模式 在经典的设计模式分类中(如《设计模式:可复用面向对象软件的基础》一书中),总共有23种设计模式,分为三大类: 一、创建型模式(5种) 1. 单例模式(Sing…...
Spring Boot 实现流式响应(兼容 2.7.x)
在实际开发中,我们可能会遇到一些流式数据处理的场景,比如接收来自上游接口的 Server-Sent Events(SSE) 或 流式 JSON 内容,并将其原样中转给前端页面或客户端。这种情况下,传统的 RestTemplate 缓存机制会…...

【大模型RAG】Docker 一键部署 Milvus 完整攻略
本文概要 Milvus 2.5 Stand-alone 版可通过 Docker 在几分钟内完成安装;只需暴露 19530(gRPC)与 9091(HTTP/WebUI)两个端口,即可让本地电脑通过 PyMilvus 或浏览器访问远程 Linux 服务器上的 Milvus。下面…...
【JavaSE】绘图与事件入门学习笔记
-Java绘图坐标体系 坐标体系-介绍 坐标原点位于左上角,以像素为单位。 在Java坐标系中,第一个是x坐标,表示当前位置为水平方向,距离坐标原点x个像素;第二个是y坐标,表示当前位置为垂直方向,距离坐标原点y个像素。 坐标体系-像素 …...

Mac下Android Studio扫描根目录卡死问题记录
环境信息 操作系统: macOS 15.5 (Apple M2芯片)Android Studio版本: Meerkat Feature Drop | 2024.3.2 Patch 1 (Build #AI-243.26053.27.2432.13536105, 2025年5月22日构建) 问题现象 在项目开发过程中,提示一个依赖外部头文件的cpp源文件需要同步,点…...

论文阅读笔记——Muffin: Testing Deep Learning Libraries via Neural Architecture Fuzzing
Muffin 论文 现有方法 CRADLE 和 LEMON,依赖模型推理阶段输出进行差分测试,但在训练阶段是不可行的,因为训练阶段直到最后才有固定输出,中间过程是不断变化的。API 库覆盖低,因为各个 API 都是在各种具体场景下使用。…...
Modbus RTU与Modbus TCP详解指南
目录 1. Modbus协议基础 1.1 什么是Modbus? 1.2 Modbus协议历史 1.3 Modbus协议族 1.4 Modbus通信模型 🎭 主从架构 🔄 请求响应模式 2. Modbus RTU详解 2.1 RTU是什么? 2.2 RTU物理层 🔌 连接方式 ⚡ 通信参数 2.3 RTU数据帧格式 📦 帧结构详解 🔍…...
xmind转换为markdown
文章目录 解锁思维导图新姿势:将XMind转为结构化Markdown 一、认识Xmind结构二、核心转换流程详解1.解压XMind文件(ZIP处理)2.解析JSON数据结构3:递归转换树形结构4:Markdown层级生成逻辑 三、完整代码 解锁思维导图新…...

麒麟系统使用-进行.NET开发
文章目录 前言一、搭建dotnet环境1.获取相关资源2.配置dotnet 二、使用dotnet三、其他说明总结 前言 麒麟系统的内核是基于linux的,如果需要进行.NET开发,则需要安装特定的应用。由于NET Framework 是仅适用于 Windows 版本的 .NET,所以要进…...