【C++】-关于类和对象的默认成员函数(中)-拷贝构造函数和赋值运算符重载函数

news2025/1/11 2:19:05

💖作者:小树苗渴望变成参天大树
❤️‍🩹作者宣言:认真写好每一篇博客
💨作者gitee:gitee
💞作者专栏:C语言,数据结构初阶,Linux,C++
在这里插入图片描述

如 果 你 喜 欢 作 者 的 文 章 ,就 给 作 者 点 点 关 注 吧!

文章目录

  • 前言
  • 一、案例引入
  • 二、拷贝构造
  • 三、案例引入
  • 四、运算符重载
  • 五、总结


前言

今天博主又来更新新的文章了,今天我们接着上面的内容就下两个默认成员函数,讲完这两个,剩下来的两个就简单了,因为用到也不多,今天讲的这个两个也特别的关键,尤其是第一个也不好理解,我尽量使用易懂的语言给大家讲解,而且要用到之前的栈类,日期类,myQueue类,话不多说,我们开始进入正文。


一、案例引入

在我们之前学习的内置类型我定义一个整型变量

int a=10;

此时我想定义一个和a是一样的变量怎么做:

int b=a;

内置类型是这样就可以解决问题了。


对于自定义类型我们如果也这样呢??

Date d1(2023,5,1);
Date d2=d1;

在C++里面是不允许这么赋值的,在传营参的时候也不是直接把对象1直接赋值给对象2,必须要通过调用拷贝构造函数去实现。

拷贝构造函数其实是特殊的构造函数,也是完成初始化操作的,所以有些特性和构造函数一样,无返回值,函数名和类名相同,形参是固定的
拷贝构造函数: 只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存在的类类型对象创建新对象时由编译器自动调用。
我们来看具体写法:
在这里插入图片描述
大家可以看到完成我们想要的效果。

解决困惑:
1.为什么要加引用
我给大家举一个例子:

class Date
{
public:

	void print()
	{
		cout << _year << " " << _month << " " << _day << endl;
	}
	Date()//无参构造函数
	{
		_year = 1;
		_month = 1;
		_day = 1;
	}

	Date(const Date& d)//拷贝构造函数
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}
private:
	int _year;
	int _month;
	int _day;
};

void func(int m){}
void func(Date d){}
int main()
{

	Date d1;//调用无参构造器初始化d1;
	func(10);//对于传内置类型是直接把值赋给形参,在一开始就讲过了
	func(d1);//这个传给形参需要调用拷贝构造,然后拷贝构造里面的形参也是通过实参传过去有会形成新的拷贝构造,我们一起来看调用会出现什么情况

	//Date d2(d1);//使用d1对象初始化d2,是两个不同的对象,但是里面的内容是一样的
	//d1.print();
	/*d2.print();*/
	return 0;
}

在这里插入图片描述
大家看到我们箭头指向第二个func的时候不是直接跳到函数体里面,而是直接跳到拷贝构造那里了,因为现在的编译器都强制检查,不使用引用就会报错,如果不加能调试你就会看到箭头一致在拷贝构造哪一行,永远进不去拷贝构造体里面,无线递归下去,就像下面的情况一样:
在这里插入图片描述

这时候就需要使用到引用,形参就是实参的别名,不需要像传值一样,还要创建历史变量在赋值过去,所以之前的传值的拷贝构造不行,传引用就直接使用也不需要创建临时变量,所以可以直接进入函数体里面,这一定是最不好理解的,所以大家一定对于调用函数的传参机制要弄明白,而且要之前自定义类型和内置类型的赋值方式是不一样的(传参就是一种赋值)

2.为什么要加const
加const是怕有人写反,例如:

在这里插入图片描述
这样不仅仅没有给d2进行初始化,反而让自己的值也改变了,所以加一个const就刚好的解决了这个问题

通过上面的例子,我们大致知道了拷贝构造函数的用法,以及拷贝构造函数的特征有那些,也解释了拷贝构造使用时候该注意的细节是什么,算是入门了,但拷贝构造的细节往往不止这些,让我们正式进入拷贝构造的讲解

二、拷贝构造

在案例引用那一块我已经介绍了拷贝拷贝构造的一部分细节,在文章的开头,我提到过,拷贝构造函数也是默认成员函数,不写,编译器会默认生成的,那我们来看看不写会出现什么情况:

在这里插入图片描述
大家发现这写不写拷贝构造效果都一样啊
大家在来看一下栈类如果这样会出现什么情况:
在这里插入图片描述
我们发现我们不写构造函数就会出现问题,我们通过调试来看看什么时候出现这样的错误:
在这里插入图片描述
在这里插入图片描述
我们来看一下图解:
在这里插入图片描述

报错的原因是对同一块地址析构了两次,在st1结束时调用析构释放了那块空间,在st2结束时有调用了析构函数,对已经释放的空间在次释放,在动态内存管理那一节明确说过这样是不可以的。

那为什么会出现这样的情况呢??
拷贝构造在不显示的时候,并不会像构造函数一样对内置类型不做任何处理,而是会将内置类型按照字节拷贝去进行拷贝的,也就是值拷贝或者叫浅拷贝,跟memcpy类似,刚才那种情况是,那三个成员变量都是内置类型,指针也属于内置类型,所以才会出现刚才的问题,那么怎么解决这个问题,显然默认生成的肯定不行,就需要自己写一个拷贝构造函数,采取深拷贝,这里就提一下,后面的博客会重点介绍,我们来看一下深拷贝是怎么解决这个问题的:

Stack(const Stack& st)
	{
		_array = (int*)malloc(sizeof(int) * st._capacity);
		if (_array == NULL)
		{
			perror("malloc:");
			exit(-1);
		}
		memcpy(_array, st._array, sizeof(int) * st._size);
		_size = st._size;
		_capacity = st._capacity;
	}

在这里插入图片描述
相信大家应该知道怎么处理了吧,并且一开始那种不止是析构两次的问题,在操作的是另一个会影响另一个,因为公用一块空间

可以简单的理解,拷贝构造函数就是为深拷贝而生的,也体会到了创造者的厉害之处


但是刚才对于默认生成的拷贝构造函数,对内置类型会做处理,对自定义类型呢?我们来看效果:
在这里插入图片描述
在这里插入图片描述

对于自定义类型,编译器默认生成的拷贝构造会自动调用自定义类中的拷贝构造,这一点和构造函数,析构函数类似希望大家可以更好的理解

什么时候需要自己写拷贝构造呢??

  1. 都是内置类型并且没有动态申请资源的,就可以不用自己写拷贝构造。
  2. 全部都是自定义类型的时候也不需要写,典型就是两个栈实现队列的时候
    具体问题具体分析,希望大家可以理解。

到这几乎把拷贝构造讲解清楚了,大家一定要好好消化,接下来我将讲解运算符重载函数

三、案例引入

我们来看一下整型怎么比较大小的:

int a=10;
int b=20;
a<b;

那我们来看一下自定义类型:

Date d1(2023,5,1);
Date d2(2022,4,30);
d1>d2;

我们看到显然这样是不行的,我们需要写一个函数来进行比较大小,我直接将函数体内容写出来:

bool Less(const Date& x1, const Date& x2)
{
	if (x1._year < x2._year)//年小就小
	{
		return true;
	}
	if (x1._year == x2._year && x1._month < x2._month)//年不小,月小就小
	{
		return true;
	}
	if (x1._year == x2._year && x1._month == x2._month && x1._day < x2._day)//年月不小,天小就小
	{
		return true;
	}
	return false;//所以小的都找到,剩下的就是大的
}

在这里插入图片描述

大家看我们上面都是报错,原因就是,我在类外写的函数体,恰好成员变量是私有的,只能在类里面使用,所以这个时候就会报错,就将成员变量的权限改成共有的就可以了,也可以将函数放到类里面但是还有好多细节,一会在说

大家可能认为这么解决问题很简单,但是如果有人的函数名写的千奇百怪的怎么办,又不写注释,那么我们使用起来就非常的难受,我们希望的是一开始那种,直接使用运算符来进行比较,清晰明了,这时候就要使用运算符重载来做,
在这里插入图片描述

  1. 我先将成员变量的权限变成共有的,方便运行,平时都是私有的,安全性高
  2. 我们在运算符前面加一个operator就可以重载运算符
  3. 因为输出流插入的优先级高于运算符所以要加括号
cout << (d1<d2)<<endl;
cout << operator<(d1, d2) << endl;//这两种写的效果一样

在这里插入图片描述
在底层的汇编两者是一样的指令。

到现在大家应该已经算是初步了解的运算符重载,他的目的就是将原来的运算符进行一个新的定义,因为自定义类型的大小比较只有设计者自己知道,但为了让代码看的显而易懂,创造者就引出了运算符重载。接下来正式开始介绍运算符重载,也是给一个新的知识做铺垫

四、运算符重载

为什么要讲运算符重载,一是他非常重要,二是方便讲赋值运算符重载,他是默认的成员函数。在案例引用的时候,我们发现将函数体写在外面,成员函数出现了无法访问的情况,那我们将函数写在类中,看看需要注意什么:
在这里插入图片描述
出现参数太多的情况,原因是成员函数都会有一个隐含的this,所以这里面报参数过多,==我们大部分的运算符都是二元运算符,所以在运算符重载几乎都是只有一个参数
在这里插入图片描述
这样就可以了,上面那种方式就不可以这么写了

cout << (d1<d2)<<endl;
cout << d1.operator<(d2) << endl;//必须写成这样的形式,d1.才能将d1的地址传给this指针

大家应该知道元素在类中是怎么使用的吧,接下来讲解一个重要的知识,赋值运算符重载,上面是小于运算符重载,这也是默认成员函数只有,不写就会自动生成,让我们这个函数是怎么使用的,和注意的细节

赋值运算符重载:
对于赋值运算符重载,我们只能卸载类里面,不能写成全局的

用一个已经存在的对象去初始化另一个的对象,这是拷贝构造
已经存在的两个对象之间的赋值拷贝,这是赋值运算符重载

大家一定要理解这两个,不然一开始很容易将拷贝构造和赋值运算符搞混,觉得是同一个东西,实际上还是有本质的差别

在这里插入图片描述
d1成功被d2赋值了,这是目前写的一个最简单版本的赋值运算符重载,相比较拷贝构造,他又返回值,而拷贝构造没有,但是这种写的不太完美,万一我想连续赋值呢??

d4=d3=d2=d1;

在这里插入图片描述
我们以整型为例:

int i,j,k=0;
i=j=k=0;

在整型的时候可以这样写,原因是k=0,返回的是k,j=k返回的是j,i=j返回的是i,每个运算符返回的都是对应类型的,那么我们赋值运算符重载是不也要有类型返回值:
在这里插入图片描述

这么写还是不太完美,用值返回,我们在函数那一将说过,返回的值,需要创建临时变量,先将值拷贝到临时变量上,在返回,而我们上面说过,对于自定义类型的拷贝,需要调用拷贝构造,我们来看看效果:
在这里插入图片描述
有四次返回就要四次调用拷贝构造,怎么解决这个问题呢??我们就需要使用到引用返回,对于引用返回我们需要主要几个点,局部变量不能哟个引用很危险,静态的可以用引用,对于这里,我们的this是局部变量出了我们的赋值运算符函数就会被销毁,但是我们返回的是*this,*this就是对象了,他的生命周期是main函数,所以不会随着赋值运算符的结束而销毁,所以可以使用引用返回

在这里插入图片描述
这里就不用调用拷贝构造减少消耗,提高效率,但是我们还需要完善,有的人会这么写:

d1=d1;

这样没有什么意义,避免这样我们需要加一个if判断:
在这里插入图片描述
相同对象的地址肯定能够是一样的,有的人会这么写:

if(*this!=d)

这样写的前提是重载了!=运算符,那这样成本太高,不如直接用地址来判断。

说到这里,大家应该可以体会到我之前写的C++入门那篇博客的主要性了吧,前后知识都是连贯的


赋值运算符重载也是默认的成员函数,不写会默认生成,我们看看默认生成的会干那些事:
在这里插入图片描述
大家看到我们没有写,居然达到了同样的效果,那我们写他为了干什么,对于内置类型我们会完成浅拷贝,但是又自定义类型,我们就会去调用他的赋值运算符重载,我们来看效果:
在这里插入图片描述
我们来看看效果,会去调用栈里面的赋值运算符重载

所以赋值运算符重载的操作行为和构造函数的行为一样,对内置类型完成浅拷贝,对于自定义类型去调用他的赋值运算符。

什么时候需要写赋值运算符重载??

  1. 全部都是内置类型的时候不需要写(日期类)
  2. 有动态开辟的空间需要写(栈类)
  3. 都是自定义类型的不需要写(MyQueue类)

注意:

  1. 不能通过连接其他符号来创建新的操作符:比如operator@
  2. 重载操作符必须有一个类类型参数
  3. 用于内置类型的运算符,其含义不能改变,例如:内置的整型+,不 能改变其含义
  4. 作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏的this
  5. .* :: sizeof ?: . 注意以上5个运算符不能重载。这个经常在笔试选择题中出
    现。

五、总结

今天重点讲解了拷贝构造和运算符重载,这都是默认成员函数比较重要的知识点,大家一定要学号,后面的学习都是围绕这些基础展开讲解的,接下来我会写一篇博客,来完善我们的日期类,把我们这两篇总结的知识点运用一下,我们一个六个默认成员函数,目前已经讲解了四个,剩下来的两个不是重点,后面我在单独写一篇博客,给大家简单介绍一下即可,现在我们的任务就是完成这两篇博客的学习和练习。希望大家都来学到知识。我们下篇再见
在这里插入图片描述

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

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

相关文章

带你玩转单向链表(学习必备)

本文概要 本篇文章主要介绍数据结构中单向链表各种操作&#xff0c;适合有C语言基础的同学&#xff0c;文中描述和代码示例很详细&#xff0c;干货满满&#xff0c;感兴趣的小伙伴快来一起学习吧&#xff01; &#x1f31f;&#x1f31f;&#x1f31f;个人简介&#x1f31f;&…

Redis入门到实战(实战篇)

Redis基础篇 实战篇Redis 开篇导读 亲爱的小伙伴们大家好&#xff0c;马上咱们就开始实战篇的内容了&#xff0c;相信通过本章的学习&#xff0c;小伙伴们就能理解各种redis的使用啦&#xff0c;接下来咱们来一起看看实战篇我们要学习一些什么样的内容 短信登录 这一块我们会使…

携程:一个被严重低估了的在线旅游平台?

来源&#xff1a;猛兽财经 作者&#xff1a;猛兽财经 携程和旅游行业面临的不利因素依然存在 在疫情期间&#xff0c;由于全球范围内的旅行受到了限制&#xff0c;所以整个旅游行业都受到了巨大打击。休闲旅游和商务旅行也陷入了停顿&#xff0c;此后一直在缓慢恢复。 而当疫情…

Java8流式操作——最终操作

什么是最终操作&#xff1f; 当我们通过最终方法对流对象进行操作&#xff0c;说明stream流操作也完成&#xff0c;最后我们将对象汇总成一个结果&#xff08;总数、对象、集合……&#xff09; 方法 collect&#xff1a;将Stream中的元素汇总&#xff08;转化&#xff09;成…

探索三维世界【2】:Three.js 的 Texture 纹理

缤纷三维世界大揭秘&#xff1a;探索 Three.js 的 Texture 纹理 1、Texture纹理2、TextureLoader 纹理加载器2.1、创建纹理加载器2.2、纹理属性设置2.3、设置纹理渲染2.4、打光 3、完整代码与展示 1、Texture纹理 Texture 是 three.js 中的“纹理”概念。纹理是指将一张图像映…

ESP8266基于Lua开发使用U8g2模块驱动 i2c ssd1306 OLED显示

ESP8266基于Lua开发使用U8g2模块驱动 i2c ssd1306 OLED显示 &#x1f4cd;相关篇《ESP8266基于Lua开发点灯示例》 &#x1f4d6;U8g2对应的API接口函数&#xff1a;https://nodemcu.readthedocs.io/en/release/modules/u8g2/ &#x1f4fa;驱动显示效果&#xff1a; &#…

Winform从入门到精通(38)——StatusStrip(史上最全)更新中

一、属性 1、Name 获取StatusStrip控件对象 2、AllowDrop 允许用户拖拽数据到控件上 3、AllowItemReorder 当用于按下alt键时,是否允许对项进行排列,如下图: 4、AllowMerge 5、Anchor 6、AutoSize 7、BackColor 设置StatusStrip的背景色 8、BackgroundImage 设置背…

解决wordpress 没有“add new“按钮

文章目录 问题描述解决方案两个参数的详细解释DISALLOW_FILE_EDITDISALLOW_FILE_MODS 问题描述 新安装的wordpress发现没有“add new“按钮&#xff0c;很奇怪。 解决方案 修改wp-config.php文件&#xff0c;增加如下两行即可修复问题&#xff1a; define(‘DISALLOW_FILE_E…

【实战详解】如何快速搭建接口自动化测试框架?:Python + Requests

摘要&#xff1a; 本篇文章将介绍如何基于Python和Requests库快速搭建一个简单而高效的接口自动化测试框架。您将会了解到设计测试用例、准备测试数据、编写自动化脚本等步骤&#xff0c;以及如何使用断言来检查测试结果&#xff0c;并最终构建一个可重用、易扩展的自动化测试…

【前端每日一问002】jquery中each与data方法的用法与概念

在 jQuery 中&#xff0c;.each() 方法和 .data() 方法都是常用的工具。这两个方法的用法和概念如下&#xff1a; &#x1f319;一、.each() 方法&#xff1a; .each() 方法是 jQuery 对象的方法&#xff0c;用于遍历匹配元素集合中的所有元素&#xff0c;并对每个元素执行一个…

【LeetCode】1000题挑战(225/1000)

1000题挑战 没有废话&#xff0c;直接开刷&#xff01; 目录 1000题挑战 没有废话&#xff0c;直接开刷&#xff01; 第一题&#xff1a;202. 快乐数 - 力扣&#xff08;Leetcode&#xff09; 题目接口&#xff1a; 解题思路&#xff1a; 代码&#xff1a; 过过过过啦…

基于W1R3S的渗透测试

您被雇来对W1R3S做渗透测试&#xff0c;他们要求您获得root权限并找到标志(位于/root目录中)。 目标&#xff1a;得到root权限&找到flag.txt 目录 1、信息收集 &#xff08;1&#xff09;定位靶机IP &#xff08;2&#xff09;端口扫描 &#xff08;3&#xff09;脚本…

【HTMLCSSJS】写实验发现的一些注意点

&#x1f38a;专栏【 前端易错合集】 &#x1f354;喜欢的诗句&#xff1a;更喜岷山千里雪 三军过后尽开颜。 &#x1f386;音乐分享【如愿】 大一同学小吉&#xff0c;欢迎并且感谢大家指出我的问题&#x1f970; 目录 &#x1f6a5;innerHtml和innerText的区别 &#x1f3f…

28-Servlet API

1.HttpServlet 我们写 Servlet 代码的时候&#xff0c;⾸先第⼀步就是先创建类&#xff0c;继承⾃ HttpServlet&#xff0c;并重写其中的某些⽅法。 1.1.核心方法 实际开发的时候主要重写 doXXX ⽅法&#xff0c;很少会重写 init / destory / service。 这些⽅法的调⽤时机&…

c++基础-运算符

目录 1关系运算符 2运算符优先级 3关系表达式的书写 代码实例&#xff1a; 下面是面试中可能遇到的问题&#xff1a; 1关系运算符 C中有6个关系运算符&#xff0c;用于比较两个值的大小关系&#xff0c;它们分别是&#xff1a; 运算符描述等于!不等于<小于>大于<…

2 异或位运算大厂必刷题

文章目录 如何不用额外变量交换两个数一个数组中有一种数出现了奇数次&#xff0c;其他数都出现了偶数次&#xff0c;怎么找到并打印这种数怎么把一个int类型的数&#xff0c;提取出最右侧的1来怎么把一个int类型的数,获取位数为1的数量一个数组中有两种数出现了奇数次&#xf…

开发问题记录20230502

4 Docker普通用户无权限 普通用户执行docker命令提示信息&#xff1a; docker images docker ps -adocker: Got permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock: Post “http://%2Fvar%2Frun%2Fdocker.sock/v1.24/c…

Java 的简要介绍及开发环境的搭建(超级详细)

图片来源于互联网 目录 | CONTENT Java 简介 一、什么是 Java 二、认识 Java 版本 三、选择哪个版本比较好 搭建 Java 开发环境 一、下载 Java 软件开发工具包 JDK 二、配置环境变量 自动配置 手动配置 三、下载合适的 IDE IntelliJ IDEA Visual Studio Code Eclip…

Flutter 组件使用:使用 Stack 替代 GlobalKey 的定位 tip-widget 实现

场景 有时候需要在指定位置进行 tip-widget 的弹出与展示&#xff0c;常见的方式是通过给指定位置上的指定 widget 添加 GlobalKey 来实现&#xff1b; 但是&#xff0c;使用这种方式的话&#xff0c;【一】大多数时候都需要进行全局定位转换&#xff08;localToGlobal&#…

c++ 11标准模板(STL) std::vector (三)

定义于头文件 <vector> template< class T, class Allocator std::allocator<T> > class vector;(1)namespace pmr { template <class T> using vector std::vector<T, std::pmr::polymorphic_allocator<T>>; }(2)(C17…