【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 属…...
7.4.分块查找
一.分块查找的算法思想: 1.实例: 以上述图片的顺序表为例, 该顺序表的数据元素从整体来看是乱序的,但如果把这些数据元素分成一块一块的小区间, 第一个区间[0,1]索引上的数据元素都是小于等于10的, 第二…...
OkHttp 中实现断点续传 demo
在 OkHttp 中实现断点续传主要通过以下步骤完成,核心是利用 HTTP 协议的 Range 请求头指定下载范围: 实现原理 Range 请求头:向服务器请求文件的特定字节范围(如 Range: bytes1024-) 本地文件记录:保存已…...
ESP32 I2S音频总线学习笔记(四): INMP441采集音频并实时播放
简介 前面两期文章我们介绍了I2S的读取和写入,一个是通过INMP441麦克风模块采集音频,一个是通过PCM5102A模块播放音频,那如果我们将两者结合起来,将麦克风采集到的音频通过PCM5102A播放,是不是就可以做一个扩音器了呢…...
第25节 Node.js 断言测试
Node.js的assert模块主要用于编写程序的单元测试时使用,通过断言可以提早发现和排查出错误。 稳定性: 5 - 锁定 这个模块可用于应用的单元测试,通过 require(assert) 可以使用这个模块。 assert.fail(actual, expected, message, operator) 使用参数…...
解决本地部署 SmolVLM2 大语言模型运行 flash-attn 报错
出现的问题 安装 flash-attn 会一直卡在 build 那一步或者运行报错 解决办法 是因为你安装的 flash-attn 版本没有对应上,所以报错,到 https://github.com/Dao-AILab/flash-attention/releases 下载对应版本,cu、torch、cp 的版本一定要对…...
相机Camera日志分析之三十一:高通Camx HAL十种流程基础分析关键字汇总(后续持续更新中)
【关注我,后续持续新增专题博文,谢谢!!!】 上一篇我们讲了:有对最普通的场景进行各个日志注释讲解,但相机场景太多,日志差异也巨大。后面将展示各种场景下的日志。 通过notepad++打开场景下的日志,通过下列分类关键字搜索,即可清晰的分析不同场景的相机运行流程差异…...
Linux C语言网络编程详细入门教程:如何一步步实现TCP服务端与客户端通信
文章目录 Linux C语言网络编程详细入门教程:如何一步步实现TCP服务端与客户端通信前言一、网络通信基础概念二、服务端与客户端的完整流程图解三、每一步的详细讲解和代码示例1. 创建Socket(服务端和客户端都要)2. 绑定本地地址和端口&#x…...
iview框架主题色的应用
1.下载 less要使用3.0.0以下的版本 npm install less2.7.3 npm install less-loader4.0.52./src/config/theme.js文件 module.exports {yellow: {theme-color: #FDCE04},blue: {theme-color: #547CE7} }在sass中使用theme配置的颜色主题,无需引入,直接可…...
Python 实现 Web 静态服务器(HTTP 协议)
目录 一、在本地启动 HTTP 服务器1. Windows 下安装 node.js1)下载安装包2)配置环境变量3)安装镜像4)node.js 的常用命令 2. 安装 http-server 服务3. 使用 http-server 开启服务1)使用 http-server2)详解 …...
日常一水C
多态 言简意赅:就是一个对象面对同一事件时做出的不同反应 而之前的继承中说过,当子类和父类的函数名相同时,会隐藏父类的同名函数转而调用子类的同名函数,如果要调用父类的同名函数,那么就需要对父类进行引用&#…...






