C++继承特性(3)——类的默认成员函数

news2024/11/15 4:23:20

  目录

前情回顾:

一.构造函数和析构函数:

        情况1:子类没有写构造和析构函数时:

        运行结果:

       构造函数:

        析构函数:     

        情况2:父类的构造函数并没有为成员变量初始化赋值,而子类中的构造函数为父类继承过来的成员变量赋了值

        解决办法:

对于子类的析构函数而言:

二.拷贝构造和赋值重载: 

        子类的拷贝构造

        子类的赋值重载函数:

         正确的解决方法: 

有了继承后的子类的几个成员函数简单总结:

三.子类成员变量的初始化赋值:


       在前两篇博客中,我们对C++继承特性有了比较深刻的理解,今天来看看继承在类的默认六大函数中又会发生什么情况。

前情回顾:

        之前提到过类有六大默认成员函数:构造函数、析构函数、拷贝构造函数、赋值重载函数、取地址函数、。在这六大函数中,前四个是重点要学习的。在一个类中,我们主动写了这几大成员函数,那么编译器也就不会主动生成;若我们不写,编译器则自动生成——这就是默认行为。

        但对于类的成员变量,有两种类别:1是内置类型(int、double、char、size_t等);2是自定义类型(String、vector、自己写的一个类......)。我们自己写的构造/拷贝构造函数,能对该类的内置成员和自定义成员都有详细而又具体的初始化赋值;但若不写,编译器生成的这几个函数对内置类型的成员变量是采取放任不管理的方式——给随机值。对自定义类型则会自动调用该自定义类中的构造/拷贝构造函数。

        若是成员变量中有指向堆区空间的、打开文件的指针变量,默认行为的成员函数会造成浅拷贝,导致多个类对象在使用编译器默认生成的成员函数时会指向同一块空间,最后会发生多次析构和内存泄漏等异常情况,导致系统崩溃。所以有时需要我们自己去写这些成员函数,完成深拷贝。

        回顾完类的这几大成员函数后,我们来学习在继承状态下,子类和父类的这几大成员函数直接又会擦出怎样的火花吧!

一.构造函数和析构函数:

        情况1:子类没有写构造和析构函数时:

class Person {
    public:
	    Person(int age = 18, string name = "王圆")
		:_age(age)
		, _name(name){
		    cout << "Person()构造函数" << endl;
	    }

	    ~Person() {
		    cout << "~Person()的析构函数" << endl;
	    }    
    public:
	    int _age;
	    string _name;
    };

class Student :public Person {
    public:
	    //子类并没有写构造函数,也没有写析构函数
    public:
	    int _id;
};

int main() {
	Student s1;	    //创建子类对象
	return 0;
}

        在上方代码中,子类的构造函数和析构函数全是默认生成的函数,在创建子类对象后,会调用默认构造函数,子类中的成员变量有三个,一个是自家类定义的_id,剩下的是从父类继承过来的_age和_name。

        
运行结果:

 

    构造函数:

       因为子类Student并没有自己的构造函数,而且Student类是继承的Person类,说明白点就是:Student是Person的子女,Student既然继承了Person,那么Student也拥有了父类成员_name的使用权,但是当Student类创建对象,并为对象的成员变量初始化的时候,子类并不能给_name(父类的成员变量)赋值,它需要调用父类的构造函数为_name赋值才行。

        总结:对于从父类继承过来的成员变量而言,子类对象只有使用权,那么在使用之前,需要让父类进行创建初始化!!!子类是没有这个权限的! 

        析构函数:     

        在程序即将结束时,子类对象需要析构自家类的成员变量,又因为子类继承了父类的_name和_age,子类不能去析构父类传承下来的成员,所以它只能去调用父类的析构函数去解决(说白了就是打狗还得看主人呢!小狗是父类的,父类传参给子类——只是给了子类养狗的权力,但子类没有权利决定小狗的生死)

        总结:子类对象使用完从父类继承来的成员变量后,在作用域即将销毁之际,需要让父类进行销毁!!!子类没有这个权限!

        情况2:父类的构造函数并没有为成员变量初始化赋值,而子类中的构造函数为父类继承过来的成员变量赋了值

错误案例示范:

class Person {
public:
	//注:下面构造函数中,并没有给成员变量赋初值
	Person(const char* name="小李", int age=20)
		:_name(name)
		,_age(age)
		 {
		cout << "Person()构造函数" << endl;
	    }

	~Person() {
		cout << "~Person()的析构函数" << endl;
	}

public:
	const char* _name;
	int _age;
};


class Student :public Person {
public:
	//子类的构造函数为父类继承过来的_name,_age做了赋初值操作
	Student(const char* name="zzz", int age=5, int id=0105)
		:_name(name)
		,_age(age)
		,_id(id) {
		cout << "Student()的构造函数" << endl;
	}

public:
	int _id;
};

int main() {
	Student s1("张三",25,202001);		//报错
		return 0;
}

        上面就是子类对继承父类的成员所做的初始化操作——错误示范。 

        _name,_age是父类继承过来的成员变量,第一次赋初值仍得由父类的构造函数去实现,子类的这种做法,越界了,不该你做的事抢着做,就会付出代价!!!

解决办法:

class Person {
public:
	Person(const char* name = "王圆", int age = 18)
		:_name(name)
		, _age(age)
	{
		cout << "Person()构造函数" << endl;
	}

	~Person() {
		cout << "~Person()的析构函数" << endl;
	}

public:
	const char* _name;
	int _age;
};


class Student :public Person {
public:
	Student(const char* name,int age, int id)
		//改进具体:让父类的成员调用它自己的构造函数
		:Person(name, age)
		, _id(id) {
		cout << "Student()的构造函数" << endl;
	}

public:
	int _id;
};

int main() {
	Student s1("张三", 25, 202001);
	return 0;
}

  运行结果:

        从图上看,子类对象创建时,进入Student构造函数,先初始化父类成员,进入父类的构造函数中,之后才轮到子类的成员初始化。 

对于子类的析构函数而言:

        作用域即将结束时,子类对象会销毁,其子类析构函数被编译器调用,但是子类析构函数体中不用显示调用父类的析构,编译器会自动调用的,若是强行写上,万一父类的成员变量是指针类指向的空间,多次析构会造成崩溃!! !

总结:子类对象被创建后,构造的顺序:先构父后构子;  析构的顺序:先析子后析父    


二.拷贝构造和赋值重载: 

        子类的拷贝构造

        由于子类继承了父类成员,子类的拷贝构造只能解决自己类中的成员,对于继承过来的还是得需要调用父类的拷贝构造去解决。

class Person {
public:
	Person(const char* name = "王圆",int age=18)
		:_age(age)
		, _name(name) {
		cout << "Person()构造函数" << endl;
	}

	//拷贝构造
	Person(const Person& p) {
		cout << "Person(const Person & p)的拷贝构造函数" << endl;
		}

	~Person() {
		cout << "~Person()的析构函数" << endl;
	}
public:
	int _age;
	const char* _name;
};

class Student :public Person {
public:
	Student(const char* name,int age,int id)
		:Person(name,age)
		,_id(id) {
		cout << "Student()的构造函数" << endl;
	}
	
	//子类的拷贝构造函数
	Student(const Student& s)
	:Person(s) {	
		cout << "Student(const Student& )的拷贝构造函数" << endl;
	}

	~Student() {
		cout << "~Student()的析构函数" << endl;
	}
public:
	int _id;
};

在子类的拷贝构造中,初始化列表的Person()中为什么能填形参s呢?

        原因:之前讲了子类对象是能够赋值给父类对象的——切片操作,回顾C++继承——子类对象赋值转换父类对象。那么调用子类对象的拷贝构造,从父类继承的成员变量也需要让父类调用它自己的拷贝构造完成变量初始化!而形参s只是把它从父类继承来的成员变量切片出来传给了父类对象,由父类完成这些成员的拷贝构造

子类对象赋值父类对象向上转换知识点: 

(489条消息) C++——继承(2)详解_橙予清的zzz~的博客-CSDN博客https://blog.csdn.net/weixin_69283129/article/details/132011160?spm=1001.2014.3001.5502

 运行结果: 


         子类的赋值重载函数:

        子类的赋值重载运算符函数与拷贝构造也是同理,在子类的赋值重载中,调用父类的赋值重载函数。

class Person {
public:
	Person(const char* name = "王圆", int age = 18)
		:_age(age)
		, _name(name) {
		cout << "Person()构造函数" << endl;
	}
	//赋值重载
	Person& operator=(const Person& p) {
		cout << "Person operator=()赋值重载函数" << endl;
		if (this != &p)
			_name = p._name;
			_age = p._age;
		return *this;
	}
	~Person() {
		cout << "~Person()的析构函数" << endl;
	}
public:
	int _age;
	const char* _name;
};


class Student :public Person {
public:
	Student(const char* name, int age, int id)
		:Person(name, age)
		, _id(id) {
		cout << "Student()的构造函数" << endl;
	}

	Person& operator=(const Student& s) {
		cout << "Student operator=()赋值重载函数" << endl;
		if (this != &s) {
			operator=(s);
			_id = s._id;
		}
		return *this;
	}

	~Student() {
		cout << "~Student()的析构函数" << endl;
	}

public:
	int _id;
};


int main() {
	Student s1("武七",10,12306);
	Student s2("李九",46,00000);
	s2 = s1;	

运行结果: 

        由运行结果知:s1在赋值s2的过程中发生了栈溢出,系统崩溃了,从最右边的图可知,编译器一直在重复调用operator=()赋值重载函数, 这是为什么?

原因:

 正确思路如上!

 错误的解决方法如上!

     

        我们原本想着子类自己的成员可以自行处理,继承过来的就要交给父类的赋值重载,由于operator=函数是父类和子类都有的函数,它们俩形成了隐藏关系,但是编译器是优先找子类中的operator=函数,所以造成了自己递归自己的情形。

 

正确的解决方法: 

         给编译器指定operator=函数的位置去调用。

有了继承后的子类的几个成员函数简单总结:

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

        2. 子类的拷贝构造函数必须调用父类的拷贝构造完成父类成员的拷贝初始化。

        3. 子类的operator= 必须要调用父类的operator = 才能完成父类成员的赋值。

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

        5. 子类对象初始化先调用父类构造,再调用子类构造。

        6. 子类对象析构清理先调用子类析构再调父类的析构。


三.子类成员变量的初始化赋值:

子类继承了父类,那么子类中的成员变量就划分为了三部分:

a.父类继承过来的成员变量
b.子类的内置类型的成员变量    

c.子类的自定义类型的成员变量

对于这三部分的成员变量,我们在初始化赋值的时候,就会有限制:

 一.当子类对象被创建后,这三部分的成员变量需要干什么?


        1.父类继承过来的成员,子类无能为力,子类有使用权,但子类没有生杀权,父类继承来的成员,仍得由父类去赋值初始化,结束时也仍得由父类去销毁。

        2.子类创建出的内置类型成员,就可以通过自身的构造函数去初始化;


        3.子类创建出的自定义类型成员,需要调用自定义类的构造函数去初始化;

二.当程序即将结束时,子类对象会被销毁:


        1.首先编译器在消除子类对象时,优先调用子类的析构函数,析构子类的内置类型成员,然后调用自定义类的析构函数销毁自定义类型成员变量;


        2.最后才会轮到父类继承过来的成员变量被析构。

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

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

相关文章

论文阅读-BotPercent: Estimating Twitter Bot Populations from Groups to Crowds

目录 摘要 引言 方法 数据集 BotPercent架构 实验结果 活跃用户中的Bot数量 Bot Population among Comment Sections Bot Participation in Content Moderation Votes Bot Population in Different Countries’ Politics 论文链接&#xff1a;https://arxiv.org/pdf/23…

BES2700 SDK绝对时间获取方法

1 代码 2 实验 log 需要换算下

CLIP-GCD: Simple Language Guided Generalized Category Discovery(论文翻译)

CLIP-GCD: Simple Language Guided Generalized Category Discovery 摘要1 介绍2 相关工作2.1 NCD2.2 无监督聚类2.3 自监督和多模态预训练 3 方法3.1 GCD 问题设置3.2 我们的方法3.2.1 使用CLIP 在GCD 4 实验4.1 模型架构细节4.2 数据集和评估4.3 和最先进水平比较4.4 分析4.5…

echarts柱状图横坐标文字过长的解决办法

背景&#xff1a;echarts图中横坐标显示的文字过长&#xff0c;导致字都堆积在一块如下图所示 解决办法 一&#xff1a;可以尝试修改‘axisLabel’的‘rotate’和‘interval’参数&#xff0c;‘rotate’参数可以设置标签的旋转角度&#xff0c;可以避免标签之间的重叠&#x…

9.环境对象和回调函数

9.1环境对象 指的是函数内部特殊的变量this&#xff0c;它代表着当前函数运行时所处的环境 作用&#xff1a; 弄清楚this的指向&#xff0c;可以让我们代码更简洁 ➢函数的调用方式不同&#xff0c;this指代的对象也不同 ➢[谁调用&#xff0c;this 就指代谁] 是判断this指向的…

三十一章 uboot顶层Makefile详解

编译后的uboot源码文件 1、 arch 文件夹 这个文件夹里面存放着和架构有关的文件&#xff0c;进入后打开arm文件 2、 board 文件夹 board 文件夹就是和具体的板子有关的&#xff0c;打开此文件夹&#xff0c;里面全是不同的板子&#xff0c;毫无疑问正 点原子的开发板肯定也在…

如何使用Postman创建Mock Server?

这篇文章将教会大家如何利用 Postman&#xff0c;通过 Mock 的方式测试我们的 API。 什么是 Mock Mock 是一项特殊的测试技巧&#xff0c;可以在没有依赖项的情况下进行单元测试。通常情况下&#xff0c;Mock 与其他方法的主要区别就是&#xff0c;用于取代代码依赖项的模拟对…

python与深度学习(十二):CNN和猫狗大战二

目录 1. 说明2. 猫狗大战的CNN模型测试2.1 导入相关库2.2 加载模型2.3 设置保存图片的路径2.4 加载图片2.5 图片预处理2.6 对图片进行预测2.7 显示图片 3. 完整代码和显示结果4. 多张图片进行测试的完整代码以及结果 1. 说明 本篇文章是对上篇文章猫狗大战训练的模型进行测试。…

【构造】CF1758 D

Problem - D - Codeforces 题意&#xff1a; 思路&#xff1a; 如果需要构造一个和为定值的序列&#xff0c;那么考虑n-d,n-d1,.....nd-1,nd这种形式 如果要保证不能重复&#xff0c;那么先考虑一个排列&#xff0c;然后在排列上操作 如果根据小数据构造出了一些简单情形&a…

给初学嵌入式的菜鸟一点建议.学习嵌入式linux

学习嵌入式&#xff0c;我认为两个重点&#xff0c;cpu和操作系统&#xff0c;目前市场是比较流行arm&#xff0c;所以推荐大家学习arm。操作系统很多&#xff0c;我个人对开始学习的人&#xff0c;特别不是计算机专业的&#xff0c;推荐学习ucos。那是开源的&#xff0c;同时很…

CSDN 一周年创作纪念日(PS:vnjohn)

&#x1f52d; 嗨&#xff0c;您好 &#x1f44b; 我是 vnjohn&#xff0c;在互联网企业担任 Java 开发&#xff0c;CSDN 优质创作者 &#x1f4d6; 推荐专栏&#xff1a;Spring、MySQL、Nacos、Java&#xff0c;后续其他专栏会持续优化更新迭代 &#x1f332;文章所在专栏&…

【方法】PDF可以转换成Word文档吗?如何操作?

很多人喜欢在工作中使用PDF&#xff0c;因为PDF格式可以准确地保留文档的原始格式&#xff0c;比如字体、图像、布局和颜色等。 但如果编辑文档的话&#xff0c;PDF还是没有Word文档方便。那可以将PDF转换成Word格式&#xff0c;再来编辑吗&#xff1f;如何操作呢&#xff1f;…

HttpRunner自动化工具之实现参数化传递

参数化实现及重复执行 参数化测试&#xff1a;在接口测试中&#xff0c;为了实现不同组数据对同一个功能模块进行测试&#xff0c;需要准备多组测试数据对模块进行测试的过程。 在httprunner中可以通过如下方式实现参数化&#xff1a; 1、在YAML/JSON 中直接指定参数列表 2、…

用Apache Echarts展示数据

目录 1.后端代码 1.1 实体类&#xff1a; 1.2 SQL语句&#xff1a; 2.前端代码 2.1 安装 Apach Echarts安装包&#xff1a; 2.2 查找数据并赋值给Echarts 思路&#xff1a;后端查到数据&#xff0c;包装为map&#xff0c;map里有日期和每日就诊人数&#xff0c;返回给前端…

[操作系统] 进程的详细认识----从概念到调度

目录 前言 一.进程的概念 二.进程和程序之间的关系 2.1二者的关系 2.2资源的占用 三.进程的任务 四.进程的管理 五.PCB中的信息 5.1pid进程标识 5.2内存指针 5.3文件描述符表 六.进程的调度 6.1CPU的简单认识 6.2调度的方式 6.3PCB中调度相关属性 七.进程的…

【计算机网络】网络层协议 -- IP协议

文章目录 1. 网络层做了什么事2. IP协议的简介3. IP协议格式4. 分片与组装5. 网段划分6. 特殊的IP地址7. IP地址的数量限制8. 私网IP地址和公网IP地址9. 路由 1. 网络层做了什么事 保证数据可靠地从一台主机到另一台主机 当双方在进行基于TCP的网络通信时&#xff0c;要保证将数…

redis高级篇2 springboot+redis+bloomfilter实现过滤案例

一 bloomfilter的作用 1.1 作用 Bloomfilter&#xff1a;默认是有0组成bit数组和hash函数构成的数据结构&#xff0c;用来判断在海量数据中是否存在某个元素。 应用案例&#xff1a;解决缓存穿透。Bloomfilter放在redis前面&#xff0c;如果查询bf中没有则直接返回&#xff0…

opencv中轮廓相关属性

一、介绍 findContours() &#xff1a;The function retrieves contours from the binary image。 二、代码 void main() {Mat src imread("match00.bmp", IMREAD_GRAYSCALE);Mat mask;threshold(src, mask, 128, 255, cv::THRESH_BINARY_INV);Mat element cv::g…

tcl学习之路(一)(Vivado与Tcl)

学习第一步&#xff1a;安装tcl编译软件 点击这里进入activestate的官网&#xff0c;下载你喜欢的操作系统所需的安装包。这里我下载的是windows下的安装包。一步一步安装即可。   那么&#xff0c;安装后&#xff0c;我们可以在开始的菜单栏处看到三个应用程序。      …

Python爬取微博相册, 批量下载

xpath插件解析到所有图片的url地址 xpath下载地址: https://www.crxsoso.com/webstore/detail/hgimnogjllphhhkhlmebbmlgjoejdpjl 快捷键: CtrlShiftX 不会xpath语法可以看这里: https://www.w3school.com.cn/xpath/xpath_syntax.asp //div[class"woo-box-item-inlineBl…