C++第三十弹---C++继承机制深度剖析(中)

news2024/9/24 15:16:31

个人主页: 熬夜学编程的小林

💗系列专栏: 【C语言详解】 【数据结构详解】【C++详解】

目录

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

1.1、派生类的构造函数

1.2、派生类的拷贝构造函数

1.3、派生类的赋值重载

1.4、派生类的析构函数

2、继承与友元

3、继承与静态成员


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

6个默认成员函数,“默认”的意思就是指我们不写,编译器会变我们自动生成一个,那么在派生类
中,这几个成员函数是如何生成的呢?

1.1、派生类的构造函数

派生类的构造函数必须调用基类的构造函数初始化基类的那一部分成员。如果基类没有默认的构造函数(无参构造函数,全缺省构造函数,自己没写编译器自动生成的构造函数),则必须在派生类构造函数的初始化列表阶段显示调用。

1.有默认构造

父类

class Person
{
public:
    // 默认构造 全缺省构造函数
	Person(const char* name = "jack")
		: _name(name)
	{
		cout << "Person()" << endl;
	}
protected:
	string _name; // 姓名
};

子类 

class Student : public Person
{
	int _num;	
	string _str;
};

 主函数

int main()
{
    Student s;
    return 0;
}

当父类中有默认构造时,Student s ,实例化子类对象会自动调用自己的默认构造,子类中的内置类型不做处理,自定义类型调用它的构造函数,父类则调用父类的默认构造。

2.没有默认构造

class Person
{
public:
	Person(const char* name)
		: _name(name)
	{
		cout << "Person()" << endl;
	}
protected:
	string _name; // 姓名
};

上述基类中,我们手动写了构造函数,编译器则不会生成默认构造函数。因此必须在派生类构造函数的初始化列表阶段显示调用。

class Student : public Person
{
public:
	// 父类+自己,父类的调用父类构造函数初始化(复用)
	Student(int num, const char* str, const char* name)
		:Person(name)// 父类的初始化需要调用父类的构造函数
		, _num(num)
		, _str(str)
	{
		cout << "Student()" << endl;
	}
protected:
	int _num;	
	string _str;
};

注意: 

需要把父类对象类比成自定义类型来处理,初始化时需要调用父类的构造函数,不能直接初始化父类成员。

下面为错误示范:

Student(int num, const char* str, const char* name)
		:_name(name) // 错误的初始化
		, _num(num)
		, _str(str)
	{
		cout << "Student()" << endl;
	}


1.2、派生类的拷贝构造函数

派生类的拷贝构造函数必须调用基类的拷贝构造完成基类的拷贝初始化。

父类

class Person
{
public:
	Person(const Person& p)
		: _name(p._name)
	{
		cout << "Person(const Person& p)" << endl;
	}
protected:
	string _name; // 姓名
};

 子类

手动写拷贝构造

class Student : public Person
{
public:
	// s2(s1)
	Student(const Student& s)
		:Person(s)// 调用父类拷贝构造,传子类会切片
		, _num(s._num)
		, _str(s._str)
	{}
protected:
	int _num;	 //学号
	string _str;
};

  

如果拷贝中没有深拷贝的情况,那么派生类的拷贝构造可以不写,因为编译器会自动生成,但是如果拷贝中存在深拷贝的情况,那么则需要我们手动写派生类的拷贝构造

1.3、派生类的赋值重载

派生类的operator=必须要调用基类的operator=完成基类的复制。

class Person
{
public:
	Person& operator=(const Person& p)
	{
		cout << "Person operator=(const Person& p)" << endl;
		if (this != &p)
			_name = p._name;
		return *this;
	}
protected:
	string _name; // 姓名
};

手动写派生类的赋值重载 

class Student : public Person
{
public:
	Student& operator=(const Student& s)
	{
		if (this != &s)
		{
			// 派生类与基类构成隐藏
			// operator=(s);//直接调用赋值操作符重载会崩溃,因为构成隐藏,会调用自己
			Person::operator=(s);
			_num = s._num;
			_str = s._str;
		}
		return *this;
	}
protected:
	int _num;	 //学号
	string _str;
};

如果赋值中没有深拷贝的情况,那么派生类的赋值重载可以不写,因为编译器会自动生成,但是如果赋值中存在深拷贝的情况,那么则需要我们手动写派生类的赋值重载。 

1.4、派生类的析构函数

派生类的析构函数会在被调用完成后自动调用基类的析构函数清理基类成员。因为这样才能
保证派生类对象先清理派生类成员再清理基类成员的顺序。

class Person
{
public:
	~Person()
	{
		cout << "~Person()" << endl;
	}
protected:
	string _name; // 姓名
};

手动写派生类的析构函数 

class Student : public Person
{
public:
	// 子类的析构也会隐藏父类
	// 因为后续多态的需要,析构函数名字会被统一处理成destructor
	~Student()
	{
		// 显示写无法先子后父
		Person::~Person();
		//~Person();//无法调用,与父类构成隐藏,根据就近原则会调用自己
		cout << "~Student()" << endl;
		// 注意,为了析构顺序是先子后父,子类析构函数结束后会自动调用父类析构
	}
	// 构造函数是先父后子
protected:
	int _num;	 //学号
	string _str;
};

int main()
{
	Student s(1, "xxxx", "张三");
}

当我们手动写析构函数,会多调用一次父类构造,正确的做法是不写派生类的析构函数,因为编译器会自动调用。 

知识补充:

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


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

因为子类可能子类还没有析构时可能会调用父类成员,如果先析构父类,可能无法正常访问父类成员,因此需要保证析构先子后父。


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

2、继承与友元


友元关系不能继承,也就是说基类友元可以访问基类的私有和保护成员,但是不能访问子类私有和保护成员。

例如下面的代码:

// 友元关系不能继承
class Student;// 必须先声明,否则会报错
class Person
{
public:
	// 声明Display是Person的友元
	friend void Display(const Person& p, const Student& s);
protected:
	string _name = "Bob"; // 姓名
};
class Student : public Person
{
protected:
	int _stuNum = 1021; // 学号
};
// 友元可以在类外调用成员
void Display(const Person& p, const Student& s)
{
	cout << p._name << endl;
	cout << s._stuNum << endl;// 在派生类中加入友元则可以调用
}

注意:

Student类需要先声明,因为Person类中声明友元时不知道Student是类。

主函数 

int main()
{
	Person p;
	Student s;

	Display(p, s);
	return 0;
}

将子类加入友元声明之后:

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

打印函数在父类定义了友元但是在子类函数没有定义友元,且还想访问子类成员的办法是:在子类中也定义一个友元。

3、继承与静态成员


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

统计Person实例化对象的个数?

因为子类实例化对象时会调用父类的构造函数,因此调用多少次父类的构造函数,则实例化了多个个Person对象。

class Person
{
public:
	Person() { ++_count; }// 默认构造++
	Person(const Person& p) { ++_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(s1);
	Graduate g1;

	cout << &Person::_count << endl;//不同类域中访问的地址相同
	cout << &Student::_count << endl;

	cout << Student::_count << endl;//打印结果相同
	cout << Person::_count << endl;
	return 0;
}

测试结果 

统计出了结果且通过打印地址证明了成员变量处于同一块空间。 

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

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

相关文章

TL3568编译Kernel内核 make tl3568-evm.img -j16报错 ‘arch/arm64/boot/Image.lz4‘ failed

在编译Kernel时&#xff0c;遇到报错内容&#xff1a; /bin/sh: lz4c: command not found arch/arm64/boot/Makefile:31: recipe for target arch/arm64/boot/Image.lz4 failed make[1]: *** [arch/arm64/boot/Image.lz4] Error 1 arch/arm64/Makefile:139: recipe for target …

科普文:【支持信创、宣传国产】Alibaba Dragonwell JVM性能提升50%

4月5日&#xff0c;阿里云开放了新一代ECS实例的邀测[1]&#xff0c;Alibaba Dragonwell也在新ECS上进行了极致的优化。相比于之前的dragonwell_11.0.8.3版本&#xff0c;即将发布的dragonwell_11.0.11.6在SPECjbb2015[2] composite模式测试中&#xff0c;系统吞吐量max-jOPS提…

【算法】插值查找(对二分查找的优化)

引言 在二分查找中&#xff0c;对于相对较大的数或较小的数来说&#xff0c;查询效率是很低的&#xff0c;我们希望程序可以自适应待查询的数&#xff0c;使用插值算法 插值查找原理 1.插值查找算法类似于二分查找&#xff0c;不同的是插值查找每次从自适应 mid 处开始查找 2…

Airtest封装的Tidevice接口有多好用(二)

一、前言 上节课我们分享了一批Airtest封装的Tidevice接口&#xff0c;是有关获取设备信息的&#xff0c;还没看到的同学可以戳这里复习一下。那么本周我们继续来看一下Airtest还封装了哪些Tidevice的接口吧~ 二、Airtest封装的Tidevice接口 2.1 list_app(udid ,app_typeuse…

早得农元早享“富”!农元又双叒叕涨了!

农元升值设定的唯一途径&#xff0c;仅随着用户在平台每次的购物而升值&#xff0c;未来农元的价值升值甚至会达到几千、上万元人民币&#xff0c;真正实现了购物乐趣与财富增长的双重盛宴&#xff0c;让每一位平台用户都能享受到数字经济时代带来的红利。 快从消费者变为经营者…

警惕!六西格玛培训中不可不知的六大陷阱

近年来&#xff0c;随着六西格玛的普及&#xff0c;一些常见的培训陷阱也逐渐浮出水面&#xff0c;让不少求学者误入歧途。本文&#xff0c;深圳天行健企业管理咨询公司旨在为大家揭示六西格玛培训中的六大常见陷阱&#xff0c;真正掌握六西格玛的精髓。 陷阱一&#xff1a;速成…

KMP入门与算法题实践

基础知识 参考视频 下面是两个b站上个人借鉴学习的视频 第一个视频用来快速理解KMP&#xff1a; 【最浅显易懂的 KMP 算法讲解】 https://www.bilibili.com/video/BV1AY4y157yL/?share_sourcecopy_web&vd_sourced124eda224bf54d0e3ab795c0b89dbb0 第二、三个视频用来理…

vue3学习day01-vue3的优势、新的脚手架工具create-vue、创建vue3项目、vue3的项目文件内容、插件变化

1、vue3的优势 &#xff08;1&#xff09;更易维护&#xff1a;组合式api&#xff0c;更好的TypeScript支持 &#xff08;2&#xff09;更快的速度&#xff1a;重写diff算法&#xff0c;模版编译优化&#xff0c;更高效的组件化 &#xff08;3&#xff09;更小的体积&#x…

MES系统:生产实时监控与智能反馈,驱动制造业智能化升级

MES系统&#xff08;Manufacturing Execution System&#xff0c;制造执行系统&#xff09;通过集成多种技术手段和管理模块&#xff0c;实现了生产过程的实时监控与反馈。以下是实时监控与反馈具体实现的详细分析&#xff1a; 一、实时监控 1. 数据采集 传感器与设备集成&am…

nrm: npm 镜像源管理工具

nrm 是 “npm registry manager” 的缩写&#xff0c;是一个 npm 镜像源管理工具&#xff0c;用于在不同的 npm 镜像源之间快速切换&#xff0c;帮助开发者根据需要选择不同的源来加速包的下载或解决网络问题。 常用命令 详细介绍 以下是 nrm 的一些主要特性和用法&#xff1…

精美UI三方用户中心 新版QRuser用户中心主题 | 魔方财务模板

内容目录 一、详细介绍二、效果展示1.部分代码2.效果图展示 三、学习资料下载 一、详细介绍 新版QRuser用户中心主题 | 魔方财务模板 本主题支持魔方财务3.5.7版本&#xff01;可自由切换魔方财务3.5.7版本与其他版本。 本主题基于官方default开发&#xff0c;主要面向企业&…

Java语言程序设计——篇十一(1)

&#x1f33f;&#x1f33f;&#x1f33f;跟随博主脚步&#xff0c;从这里开始→博主主页&#x1f33f;&#x1f33f;&#x1f33f; 欢迎大家&#xff1a;这里是CSDN&#xff0c;我的学习笔记、总结知识的地方&#xff0c;喜欢的话请三连&#xff0c;有问题可以私信&#x1f33…

【数据结构-前缀哈希同余运算】力扣974. 和可被 K 整除的子数组

给定一个整数数组 nums 和一个整数 k &#xff0c;返回其中元素之和可被 k 整除的非空 子数组 的数目。 子数组 是数组中 连续 的部分。 示例 1&#xff1a; 输入&#xff1a;nums [4,5,0,-2,-3,1], k 5 输出&#xff1a;7 解释&#xff1a; 有 7 个子数组满足其元素之和可…

仿真入门!Hypermesh焊接处理方法

某抗硫球阀为对焊端结构&#xff0c;需要焊接袖管。焊接过程产生的高温会造成热影响区域&#xff0c;尤其接头区域产生变形及残余应力&#xff0c;同时O型密封圈处的温度也是焊接生产比较关心的问题。因此&#xff0c;为弄清袖管焊接过程中温度场规律&#xff0c;给实际焊接生产…

RAID 级别:0、1、5、6、10 和 50 傻傻分不清?那是你没看过这篇

号主&#xff1a;老杨丨11年资深网络工程师&#xff0c;更多网工提升干货&#xff0c;请关注公众号&#xff1a;网络工程师俱乐部 早上好&#xff0c;我的网工朋友。 大家都知道&#xff0c;无论是企业级服务器还是个人计算机&#xff0c;数据的安全性和可用性都是至关重要的。…

通配符HTTPS证书快速申请流程

通配符HTTPS证书是一种特别类型的HTTPS证书&#xff0c;它可以为一个主域名及其下级所有子域名提供安全加密。这对于拥有多个子域名的企业来说非常有用&#xff0c;因为它简化了管理流程&#xff0c;并降低了成本。具体申请流程&#xff1a; 注册账号填写230919注册码即可获得…

【C++】实验十四

题目&#xff1a; 1、编写程序&#xff0c;输入a&#xff0c;b&#xff0c;c&#xff0c;检查a&#xff0c;b&#xff0c;c是否满足以下条件&#xff0c;如不满足&#xff0c;由cerr输出有关错误信息。 2、从键盘输入一批数值。要求保留3位小数&#xff0c;在输出时上下行小数…

成都夏光汝网络科技有限公司抖音小店品质与创新的完美结合

在数字经济蓬勃发展的今天&#xff0c;电商行业以其独特的魅力和无限的可能性&#xff0c;正深刻改变着我们的消费习惯与生活方式。其中&#xff0c;抖音小店作为短视频与电商结合的典范&#xff0c;更是以其独特的优势迅速崛起&#xff0c;成为广大消费者喜爱的购物渠道。成都…

企业版邮箱如何确保全球畅邮

企业版邮箱如何确保全球畅邮呢&#xff1f;一邮箱通过多项国际隐私认证&#xff0c;加密技术保障数据安全。二是全球网络部署确保邮件畅通。三提供灵活价格方案&#xff0c;支持用户定制化和跨平台。四是第三方工具集成&#xff0c;提升效率。 一、Zoho邮箱的安全保障 1.1 高…

【Stable Diffusion】影楼再也赚不到你的钱,让SD帮你“拍摄”艺术写真

前言 点击上方「蓝字」关注我们 你能看出下图是一张经过AI技术处理过的写真吗&#xff1f; 通过原图AI合成&#xff0c;简单几步操作&#xff0c;一个无需摄影无需后期的0成本摄影工作室就诞生了。 今天就来教大家怎么用Stable Diffusion做出这样的写真效果。 操作过程 第一…