【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 变为 1
    Student s2; // Person::_count 变为 2
    Student s3; // Person::_count 变为 3
    Graduate s4; // Person::_count 变为 4
    cout << " 人数 :" << Person::_count << endl; // 输出 " 人数 :4"

    Student::_count = 0; // 重置 Person::_count 为 0

    cout << " 人数 :" << Person::_count << endl; // 输出 " 人数 :0"
}

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

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

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【自留】运行一个开源项目

运行一个开源项目 首先是运行起来 1. 拿到地址 拿到你想要的项目的地址 2. 克隆 打开编辑器 VSCode &#xff0c;创建一个放项目的文件夹&#xff0c;控制台输入以下代码克隆项目 git clone 克隆地址gitee克隆地址在这看&#xff1a; github上项目的话&#xff0c;在这…

【python的魅力】:教你如何用几行代码实现文本语音识别

文章目录 引言一、运行效果二、文本转换为语音2.1 使用pyttsx32.2 使用SAPI实现文本转换语音2.3 使用 SpeechLib实现文本转换语音 三、语音转换为文本3.1 使用 PocketSphinx实现语音转换文本 引言 语音识别技术&#xff0c;也被称为自动语音识别&#xff0c;目标是以电脑自动将…

SSM+Vue+小程序+基于微信小程序的高校学生事务管理系统

项目配合学校日常生活通知&#xff0c;考试等管理需要&#xff0c;开发学校事务管理系统&#xff0c;maven管理依赖&#xff0c;mybatis处理数据库交互 学校管理 学院管理 班级管理 年级管理 教师管理 通知公告管理 学生资料管理 待办事项管理 教务处通知管理 讲座通…

【Conda】解决无名虚拟环境问题

文章目录 问题描述&#xff1a;无名虚拟环境解决步骤1 添加虚拟环境目录到envs步骤2 成功命名 问题描述&#xff1a;无名虚拟环境 如果不指定创建目录&#xff0c;默认创建在C盘用户目录下&#xff0c;这应该是很多人不愿意的吧。 指定目录创建虚拟环境命令如下&#xff1a; …

vector的使用

1.构造函数 void test_vector1() {vector<int> v; //无参的构造函数vector<int> v2(10, 0);//n个value构造&#xff0c;初始化为10个0vector<int> v3(v2.begin(), v2.end());//迭代器区间初始化,可以用其他容器的区间初始化vector<int> v4(v3); //拷贝…

基于yolov8的苹果腐败检测系统,系统既支持图像检测,也支持视频和摄像实时检测(pytorch框架)【python源码+UI界面+功能源码详解】

更多目标检测和图像分类识别项目可看我主页其他文章 功能演示&#xff1a; 基于yolov8的苹果腐败检测系统&#xff0c;系统既支持图像检测&#xff0c;也支持视频和摄像实时检测_哔哩哔哩_bilibili &#xff08;一&#xff09;简介 基于yolov8的苹果腐败检测系统是在pytorc…

STM32 F103C8T6学习笔记17:类IIC通信(SMBus协议)—MLX90614红外非接触温度计

今日学习配置MLX90614红外非接触温度计 与 STM32 F103C8T6 单片机的通信 文章提供测试代码讲解、完整工程下载、测试效果图 本文需要用到的大概基础知识&#xff1a;1.3寸OLED配置通信显示、IIC通信、 定时器配置使用 这里就只贴出我的 OLED驱动方面的网址链接了&#xff1a…

Golang数组与切片

文章目录 数组数组介绍数组的定义方式访问与修改数组元素遍历数组元素数组指针 切片切片介绍切片的定义方式访问与修改切片元素添加切片元素切片的拷贝遍历切片元素string的切片 数组 数组介绍 数组介绍 在Go中&#xff0c;数组是一个由固定长度的特定类型元素组成的序列&…

[Algorithm][队列][宽搜BFS][N叉树的层序遍历][二叉树的锯齿形层序遍历][二叉树最大宽度][在每个树行中找最大值]详细讲解

目录 1.N 叉树的层序遍历1.题目链接2.算法思路详解3.代码实现 2.二叉树的锯齿形层序遍历1.题目链接2.算法原理详解3.代码实现 3.二叉树最大宽度1.题目链接2.算法原理详解3.代码实现 4.在每个树行中找最大值1.题目链接2.算法原理详解3.代码实现 1.N 叉树的层序遍历 1.题目链接 …

数据结构之链表深度讲解

小伙伴们&#xff0c;大家好呀&#xff0c;上次听我讲完顺序表想必收获不少吧&#xff0c;嘿嘿&#xff0c;这篇文章你也一样可以学到很多&#xff0c;系好安全带&#xff0c;咱们要发车了。 因为有了上一次顺序表的基础&#xff0c;所以这次我们直接进入正题&#xff0c;温馨…

Activiti7 开发快速入门【2024版】

记录开发最核心的部分&#xff0c;理论结合业务实操减少废话&#xff0c;从未接触工作流快速带入开发。假设你是后端的同学学过JAVA和流程图&#xff0c;则可以继续向后看&#xff0c;否则先把基础课程书准备好先翻翻。 为什么要工作流 比起直接使用状态字段&#xff0c;工作…

【 书生·浦语大模型实战营】作业(六):Lagent AgentLego 智能体应用搭建

【 书生浦语大模型实战营】作业&#xff08;六&#xff09;&#xff1a;Lagent & AgentLego 智能体应用搭建 &#x1f389;AI学习星球推荐&#xff1a; GoAI的学习社区 知识星球是一个致力于提供《机器学习 | 深度学习 | CV | NLP | 大模型 | 多模态 | AIGC 》各个最新AI方…

【机器学习】集成方法---Boosting之AdaBoost

一、Boosting的介绍 1.1 集成学习的概念 1.1.1集成学习的定义 集成学习是一种通过组合多个学习器来完成学习任务的机器学习方法。它通过将多个单一模型&#xff08;也称为“基学习器”或“弱学习器”&#xff09;的输出结果进行集成&#xff0c;以获得比单一模型更好的泛化性…

上海计算机学会2021年1月月赛C++丙组T2康托表

题目背景 康托是一名数学家&#xff0c;他证明了一个重要的定理&#xff0c;需要使用一张表&#xff1a; 这个表的规律是&#xff1a; 从上到下&#xff1a;每一行的分子依次增大&#xff1b;从左到右&#xff1a;每一列的分母依次增大。 康托以一种不重复、不遗漏的方式&am…

【深耕 Python】Quantum Computing 量子计算机(1)图像绘制基础

一、绘制静止图像 使用matplotlib库绘制函数图像y sin(pi * x): import math import matplotlib.pyplot as pltx_min -2.0 x_max 2.0N 1000x1 [] y1 []for i in range(N 1):x x_min (x_max - x_min) * i / Ny math.sin(math.pi * x)x1.append(x)y1.append(y)plt.xl…

关于继承~

继承 动物有猫、狗&#xff0c; 猫又分为加菲猫、布偶猫......&#xff1b;狗又有哈士奇、德国牧羊犬...... 我们发现&#xff0c;下一类除了拥有上一类的共性之外&#xff0c;还拥有自己的特性。 于是我们可以利用继承的方式来减少重复的代码 继承的基本语法 class A:p…

二叉树的直径

题目描述&#xff1a;给你一棵二叉树的根节点&#xff0c;返回该树的 直径 。二叉树的 直径 是指树中任意两个节点之间最长路径的 长度 。这条路径可能经过也可能不经过根节点 root 。两节点之间路径的 长度 由它们之间边数表示。 示例 1&#xff1a; 输入&#xff1a;root […

在剪映专业版中新增字体的方法

我一开始以为剪映专业版没有繁体字&#xff0c;结果发现有一个现代繁体&#xff0c;如图所示: 但是我已经下载了字体了&#xff0c;不用就可惜了。 点击汉仪粗黑繁&#xff0c;安装。 安装之后&#xff0c;重启电脑&#xff0c;打开剪映&#xff0c;就可以搜索到这个字体了。 这…

每日OJ题_贪心算法二④_力扣2418. 按身高排序

目录 力扣2418. 按身高排序 解析代码 力扣2418. 按身高排序 2418. 按身高排序 难度 简单 给你一个字符串数组 names &#xff0c;和一个由 互不相同 的正整数组成的数组 heights 。两个数组的长度均为 n 。 对于每个下标 i&#xff0c;names[i] 和 heights[i] 表示第 i 个…

罗宾斯《管理学》第13版/教材讲解/考研真题视频课程/网课

本课程是罗宾斯《管理学》&#xff08;第13版&#xff09;精讲班&#xff0c;为了帮助参加研究生招生考试指定考研参考书目为罗宾斯《管理学》&#xff08;第13版&#xff09;的考生复习专业课&#xff0c;我们根据教材和名校考研真题的命题规律精心讲解教材章节内容。 序号名…