【c++】继承学习(二):探索 C++ 中派生类的默认机制与静态成员共享

Alt

🔥个人主页Quitecoder

🔥专栏c++笔记仓

Alt

目录

  • `1.派生类的默认成员函数`
  • `2.继承与友元`
  • `3.继承与静态成员`

朋友们大家好,本篇文章我们来学习继承的第二部分

1.派生类的默认成员函数

在这里插入图片描述
来看下面的类:

class Person
{
public:Person(const char* name = "jason"): _name(name){cout << "Person()" << endl;}Person(const Person& p): _name(p._name){cout << "Person(const Person& p)" << endl;}Person& operator=(const Person& p){cout << "Person operator=(const Person& p)" << endl;if (this != &p)_name = p._name;return *this;}~Person(){cout << "~Person()" << endl;}
protected:string _name; // 姓名
};class Student : public Person
{
public:
protected:int _num; //学号
};
  1. Student对象生成的默认构造函数,对内置类型不做处理,对自定义类型调用它的默认构造函数,规则和以前一样
    在这里插入图片描述
    在这里插入图片描述

派生类里面,把父类成员当做一个整体

派生类的构造函数必须调用基类的构造函数初始化基类的那一部分成员。如果基类没有默认的构造函数,则必须在派生类构造函数的初始化列表阶段显示调用

比如的父类的构造函数修改成这种:

Person(const char* name): _name(name)
{cout << "Person()" << endl;
}

现在父类没有默认构造,会编译报错

在这里插入图片描述

这时候需要子类来完成构造函数:

Student(const char* name, int num): Person(name), _num(num)
{cout << "Student()" << endl;
}

这段代码功能:

基类初始化:
Person(name) 调用了基类 Person 的构造函数,并传递了 name 参数,这确保了 Person 类的成员 _name 被正确初始化。写成 Person(name) 就是指示编译器使用 Person 类的接受 const char* 参数的构造函数。如果不这样做,基类成员 _nameStudent 对象构建过程中不会被初始化。

注意

这里不能这样初始化:

Student(const char* name, int num): _name(name), _num(num)
{cout << "Student()" << endl;
}

父类成员需要当做一个整体的一个自定义类型的成员,不能单独对它的成员处理

_name 是基类 Person 的一部分,派生类 Student 没有直接的权限去初始化它。应该使用基类构造函数来初始化

确保基类的构造函数被调用是继承中非常重要的一部分,因为只有基类的构造函数知道如何正确初始化基类定义的成员。上面的修改确保当创建Student 类的对象时,它会首先调用 Person 类的构造函数初始化 _name,然后初始化派生类 Student 的 _num 成员

派生类这里分成了两个部分:父类和自己,父类的调用父类构造函数初始化

成员变量的初始化顺序是根据它们在类定义中出现的顺序,而不是初始化列表中的顺序。因此,基类的构造函数总是首先被调用,再是派生类中定义的成员变量

  1. 派生类的拷贝构造函数必须调用基类的拷贝构造完成基类的拷贝初始化一般情况下默认生成的就够用,如果涉及到深拷贝,就需要自己显示实现
Student(const Student& s): Person(s), _num(s._num)
{cout << "Student(const Student& s)" << endl;
}

在这里插入图片描述

  1. 派生类的operator=必须要调用基类的operator=完成基类的复制
Student& operator = (const Student& s)
{cout << "Student& operator= (const Student& s)" << endl;if (this != &s){Person::operator =(s);_num = s._num;}return *this;
}

这里同名函数构成了隐藏

  1. 派生类的析构函数会在被调用完成后自动调用基类的析构函数清理基类成员。因为这样才能保证派生类对象先清理派生类成员再清理基类成员的顺序
~Student()
{cout << "~Student()" << endl;
}

上面的函数我们都进行了显示调用,但是析构函数不可以
在这里插入图片描述
Student 的析构函数被调用并完成执行后,Person 的析构函数将随即被自动而且默认地调用。这样的设计可以防止基类成员被重复释放或者提前释放,从而导致潜在的错误和资源泄漏

  1. 派生类对象初始化:先调用基类构造再调派生类构造

  2. 派生类对象析构清理:先调用派生类析构再调基类的析构。

  3. 因为后续一些场景析构函数需要构成重写,重写的条件之一是函数名相同。那么编译器会对析构函数名进行特殊处理,处理成destrutor(),所以父类析构函数不加virtual的情况下,子类析构函数和父类析构函数构成隐藏关系

所以我们想显示调用就需要这样写:

~Student()
{Person::~Person();cout << "~Student()" << endl;
}

但是这里会导致一个问题,析构多调用了一次,就是因为析构函数先调用子类再调用父类的,子类析构函数结束后会自动调用父类析构

2.继承与友元

友元关系不能继承,基类友元不能访问子类私有和保护成员

class Person
{
public:friend void Display(const Person& p, const Student& s);
protected:string _name; // 姓名
};
class Student : public Person
{
protected:int _stuNum; // 学号
};
void Display(const Person& p, const Student& s)
{cout << p._name << endl;cout << s._stuNum << endl;
}

这里会编译错误:

在这里插入图片描述

基类将某些函数或类声明为友元,这个友元关系不会自动传递给从派生)。派生类需要自己明确声明哪些函数或类是它的友元

如何解决编译错误:

要解决 Display 函数不能访问 Student 类的 _stuNum 成员的问题,可以在 Student 类中也声明 Display 为友元:

class Student : public Person
{
public:friend void Display(const Person& p, const Student& s);
protected:int _stuNum; // 学号
};

现在,Display 函数是 PersonStudent 两个类的友元,可以访问两个类的保护成员

3.继承与静态成员

基类定义了static静态成员,则整个继承体系里面只有一个这样的成员。无论派生出多少个子类,都只有一个static成员实例

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; // 研究科目
};
void TestPerson()
{Student s1;Student s2;Student s3;Graduate s4;cout << " 人数 :" << Person::_count << endl;Student::_count = 0;cout << " 人数 :" << Person::_count << endl;
}

静态成员是与类本身关联的,而不是与类的单个实例相关联。静态成员变量在所有实例中共享,而静态成员函数可以在没有类实例的情况下直接通过类名调用。当静态成员被继承时,派生类共享同一个静态成员副本,因为静态成员是属于类的,不属于类的任何具体对象

在上面代码中,Person 类有一个静态成员 _count,它被用来统计该类的实例数量。每当创建一个 Person 类的实例或者它的派生类的实例时,构造函数都会递增 _count,因此 StudentGraduate 的示例也会递增 _count

分析:

  1. Person::_count 是静态成员变量,并且初始化为 0。它统计 Person 及其派生类 StudentGraduate 的对象个数。

  2. Student 类继承自 Person,没有定义新的静态成员变量,因此它共享 Person 类的静态成员 _count

  3. Graduate 类继承自 Student,也没有定义新的静态成员变量,因此它同样共享 Person 类的静态成员 _count

void TestPerson()
{Student s1; // 在构造时, Person::_count 变为 1Student s2; // Person::_count 变为 2Student s3; // Person::_count 变为 3Graduate s4; // Person::_count 变为 4cout << " 人数 :" << Person::_count << endl; // 输出 " 人数 :4"Student::_count = 0; // 重置 Person::_count 为 0cout << " 人数 :" << Person::_count << endl; // 输出 " 人数 :0"
}

TestPerson 函数中创建了三个 Student 对象和一个 Graduate 对象,每次构造函数调用都会递增 _count,因此打印 _count 的结果为 4。

然后,将静态成员 _count 通过 Student 类重置为 0。注意,这里使用 Student::_count 访问的实际上还是 Person 类的静态成员 _count,因为 Student 并没有重新定义它。这表明无论通过类 Person 还是它的任何派生类访问静态成员 _count,结果都是相同的。因此,第二次打印 _count 的结果是 0

静态成员的继承性质:静态成员在基类及其派生类之间是共享的,而不是每个派生类都有独立的静态成员副本。因此,无论是在基类还是派生类中访问静态成员,访问的都是同一个数据。在设计类层次结构时,这一点非常重要,因为静态成员的行为可能会影响整个类族

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

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

相关文章

Delta lake with Java--分区表

今天尝试一下将昨天的数据操作建立的表换成分区表&#xff0c;参考Delta Lake Up and Running做法用分区表的方式来更新数据。还要比较一下分区表的查询与非分区表的查询&#xff0c;结果显示分区表的查询速度要比非分区表要快。直接上代码&#xff1a; import io.delta.table…

发表博客之:transformer 架构 推理时候运算流程详细讲解,以及变长推理支持,小白都可以看得懂,AI推理工程师必备技能!

文章目录 [发表博客之&#xff1a;transformer 架构 推理时候运算流程详细讲解&#xff0c;以及变长推理支持&#xff0c;小白都可以看得懂&#xff0c;AI推理工程师必备技能&#xff01;](https://cyj666.blog.csdn.net/article/details/138439826)总结一下高性能变长推理 发表…

定点乘除法

目录 一、定点乘法 1.串行乘法器 2.并行乘法器 二、定点除法 1.笔算除法 2.机器除法 一、定点乘法 1.串行乘法器 1.符号位单独处理&#xff0c;两数的符号位按异或运算得到&#xff0c;而乘积的数值部分则是两个正数相乘之积。 2.过程 &#xff08;1&#xff09; 由乘…

「2024年」前端开发常用工具函数总结 TypeScript

前言 在前端开发中&#xff0c;工具函数是提高代码复用率、保持代码整洁和增加开发效率的关键。使用 TypeScript 编写工具函数不仅可以帮助开发者捕捉到更多的类型错误&#xff0c;还可以提供更清晰的代码注释和更智能的代码补全。下面是一些在 TypeScript 中常用的前端开发工…

Java如何获取当前日期和时间?

Java如何获取当前日期和时间&#xff1f; 本文将为您介绍 Java 中关于日期和时间获取的方法&#xff0c;以及介绍 Java 8 中获取日期和时间的全新API。 1、 System.currentTimeMillis() 获取标准时间可以使用 System.currentTimeMillis() 方法来获取&#xff0c;此方法优势是…

【C语言】详解预处理

、 最好的时光&#xff0c;在路上;最好的生活&#xff0c;在别处。独自上路去看看这个世界&#xff0c;你终将与最好的自己相遇。&#x1f493;&#x1f493;&#x1f493; 目录 •✨说在前面 &#x1f34b;预定义符号 &#x1f34b; #define • &#x1f330;1.#define定义常…

ControlNet官方资源链接【ControlNet论文原文】【持续更新中~】

ControlNet官方资源链接 ControlNet论文原文&#xff1a;https://arxiv.org/abs/2302.05543ControlNet官方GitHub&#xff1a;https://github.com/lllyasviel/ControlNetControlNet 1.1官方GitHub&#xff1a;https://github.com/lllyasviel/ControlNet-v1-1-nightlyControlNe…

phpMyAdmin增加自定义IP登录教程

phpMyAdmin增加自定义IP登录教程 1、打开phpMyAdmin目录&#xff0c; 在此目录下是否有config.sample.inc.php文件&#xff0c;如果存在&#xff0c;那么将其改名为config.inc.php&#xff08;为避免修改失误所造成的损失&#xff0c;强烈建议先备份config.sample.inc.php文件…

UnityWebGL使用sherpa-ncnn实时语音识别

k2-fsa/sherpa-ncnn&#xff1a;在没有互联网连接的情况下使用带有 ncnn 的下一代 Kaldi 进行实时语音识别。支持iOS、Android、Raspberry Pi、VisionFive2、LicheePi4A等。 (github.com) 如果是PC端可以直接使用ssssssilver大佬的 https://github.com/ssssssilver/sherpa-ncn…

[嵌入式系统-62]:RT-Thread-内核:多核CPU SMP的支持与移植

目录 RT-Thread SMP 介绍与移植 1. 多核的优点 2. 多核启动 2.1 概述 2.2 CPU0 启动流程 2.3 次级 CPU 启动流程 3. 多核调度 3.1 任务特性 3.2 调度策略 4. SMP 内核接口 处理器间中断 IPI OS Tick 自旋锁 spinlock 任务绑定 4. SMP移植说明 编译环境准备 创…

配置网关,解决本地连接不上Linux虚拟机的问题

在Window环境下&#xff0c;使用远程终端工具连接不了VMware搭建的Linux虚拟机&#xff08;CentOS 7&#xff09;&#xff0c;并且在命令行ping不通该Linux虚拟机的IP地址。下面通过配置网关解决本地与Linux虚拟机连接问题&#xff1a; 1 查看虚拟机网关地址 在VMware虚拟机上…

数据库开发关键之与DQL查询语句有关的两个案例

案例 案例1 条件分页查询 查看项目经理提供给我们的需求文档 模糊匹配的含义是 只要包含"张"就可以 use dduo;-- 按照需求完成员工管理的条件分页查询 根据输入条件 查询第一页的数据 每页展示10条记录 -- 输入条件&#xff1a; -- 姓名&#xff1a; 张 -- 年龄&…

基于YOLOv8的水稻虫害识别系统,加入BiLevelRoutingAttention注意力进行创新优化

&#x1f4a1;&#x1f4a1;&#x1f4a1;本文摘要&#xff1a;基于YOLOv8的水稻虫害识别&#xff0c;阐述了整个数据制作和训练可视化过程&#xff0c;并加入BiLevelRoutingAttention注意力进行优化&#xff0c;最终mAP从原始的 0.697提升至0.732 博主简介 AI小怪兽&#xff…

c语言从入门到函数速成(2)

温馨提醒&#xff1a;本篇文章适合人群&#xff1a;刚学c又感觉那个地方不怎么懂的同学以及以及学了一些因为自身原因停学一段时间后又继续学​​​c的学 好&#xff0c;正片开始&#xff01; 数组 概念&#xff1a;数组中存放的是1个或者多个数据&#xff0c;但是数组元素个…

由于找不到msvcr110.dll,无法继续执行代码的解决方法

在日常使用计算机的过程中&#xff0c;可能会遇到系统提示缺少msvcr110.dll文件的情况&#xff0c;这一问题往往导致某些应用程序无法正常运行。幸运的是&#xff0c;有多种方法可以有效应对这一困境&#xff0c;帮助您的计算机恢复顺畅运作。以下是解决计算机丢失msvcr110.dll…

JavaWeb--1.Servlet

Servlet&#xff08;基础&#xff09; 1、配置依赖&#xff1a; ​ 在pom.xml文件中加入相关依赖 <dependencies><dependency><groupId>jakarta.servlet</groupId><artifactId>jakarta.servlet-api</artifactId><version>5.0.0&l…

Python数据分析案例43——Fama-French回归模型资产定价(三因子/五因子)

案例背景 最近看到要做三因子模型的同学还挺多的&#xff0c;就是所谓的Fama-French回归模型&#xff0c;也就是CAMP资本资产定价模型的升级版&#xff0c;然后后面还升级为了五因子模型。 看起来眼花缭乱&#xff0c;其实抛开金融资产定价的背景&#xff0c;从机器学习角度来…

HTML_CSS学习:常用文本属性

一、文本颜色 相关代码&#xff1a; <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>文本颜色</title><style>div{font-size: 90px;}.atguigu1{color: #238c20;}.atguigu2{color: rgb(2…

【b站vue教程】1 宏观视角下的浏览器——前端大厂面试必刷:前后端必学的网络安全浏览器工作原理:从入门到精通全套【附带所有源码】

课程地址&#xff1a;【前端大厂面试必刷&#xff1a;前后端必学的网络安全浏览器工作原理&#xff1a;从入门到精通全套【附带所有源码】】 https://www.bilibili.com/video/BV1UL41157hP/?share_sourcecopy_web&vd_sourceb1cb921b73fe3808550eaf2224d1c155 目录 1、宏…

vue3中使用crypto-js库进行加密/解密

使用crypto-js库进行加密/解密 安装 npm install crypto-js 基本使用 <template><div>使用crypto-js库进行加密/解密</div> </template><script setup> import CryptoJS from crypto-js; import { onMounted } from vue;// 加密函数 const encr…