c++类和对象———拷贝构造和赋值运算符重载

衔接上一篇博客构造函数和析构函数c++类和对象————构造函数和析构函数

目录

​编辑

一、拷贝构造是什么?

二、拷贝构造

1.特点

2.代码解释拷贝构造参数类型(重点)

3.代码解释编译器默认拷贝构造 (重点)

4.构造函数、析构函数和拷贝构造的应用

三、const的成员函数

三、赋值运算符重载

1、运算符重载是什么?

2、运算符重载的作用

3、日期的比较

1、运算符的声名

2、函数实现

4、 前置++和后置++重载

 5、日期的+-运算

1、声名在类内

2、函数实现

总结



一、拷贝构造是什么?

拷贝构造函数:只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存 在的类类型对象创建新对象时由编译器自动调用。

二、拷贝构造

1.特点

拷贝构造函数也是特殊的成员函数,其特征如下:

1. 拷贝构造函数是构造函数的一个重载形式

2. 拷贝构造函数的参数只有一个且必须是类类型对象的引用,使用传值方式编译器直接报错, 因为会引发无穷递归调用。

3. 若未显式定义,编译器会生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按 字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝

注意:在编译器生成的默认拷贝构造函数中,内置类型是按照字节方式直接拷贝的,而自定 义类型是调用其拷贝构造函数完成拷贝的。

4. 编译器生成的默认拷贝构造函数已经可以完成字节序的值拷贝了,还需要自己显式实现吗? 当然像日期类这样的类是没必要的。那么下面的类呢?验证一下试试?

// 这里会发现下面的程序会崩溃掉?这里就需要我们以后讲的深拷贝去解决。
typedef int DataType;
class Stack
{
public:Stack(size_t capacity = 10){_array = (DataType*)malloc(capacity * sizeof(DataType));if (nullptr == _array){perror("malloc申请空间失败");return;}_size = 0;_capacity = capacity;}void Push(const DataType& data){// CheckCapacity();_array[_size] = data;_size++;}~Stack(){if (_array){free(_array);_array = nullptr;_capacity = 0;_size = 0;}}
private:DataType *_array;size_t _size;size_t _capacity;
};
int main()
{Stack s1;s1.Push(1);s1.Push(2);s1.Push(3);s1.Push(4);Stack s2(s1);return 0;
}

 

注意:类中如果没有涉及资源申请时,拷贝构造函数是否写都可以;一旦涉及到资源申请 时,则拷贝构造函数是一定要写的,否则就是浅拷贝。

5. 拷贝构造函数典型调用场景:

使用已存在对象创建新对象

函数参数类型为类类型对象

函数返回值类型为类类型对象

class Date
{
public:Date(int year, int minute, int day){cout << "Date(int,int,int):" << this << endl;}Date(const Date& d){cout << "Date(const Date& d):" << this << endl;}~Date(){cout << "~Date():" << this << endl;}
private:int _year;int _month;int _day;
};
Date Test(Date d)
{Date temp(d);return temp;
}
int main()
{Date d1(2022,1,13);Test(d1);return 0;
}

 

为了提高程序效率,一般对象传参时,尽量使用引用类型,返回时根据实际场景,能用引用 尽量使用引用。

2.代码解释拷贝构造参数类型(重点)

当直接传d,而不是传引用传参就会一直循环调用拷贝构造,

代码如下(示例):

class Date
{
public:Date(int year = 1900, int month = 1, int day = 1){_year = year;_month = month;_day = day;}// Date(const Date& d)   // 正确写法Date(const Date& d)   // 错误写法:编译报错,会引发无穷递归
//在main函数中调用了拷贝构造,传递了一个实参,实参需要拷贝各形参就会再次调用拷贝构造,再传一个实参,实参再调拷贝构造,以此循环;{_year = d._year;_month = d._month;_day = d._day;}
private:int _year;int _month;int _day;
};
int main()
{Date d1;Date d2(d1);//调用拷贝构造return 0;
}
//class Date
//{
//public:
//	Date(int year, int month, int day)
//	{
//		_year = year;
//		_month = month;
//		_day = day;
//	}
//	//拷贝构造
//	//如果不加别名,在传参之前还会在调用拷贝构造
//	//理解:实参先没有确定的空间,在开始传参时,临时分配空间,形参在这个空间中去拷贝
//	//这里用了&取别名,给d1取别名d,然后在d这个空间中去找;
//	//去掉& 。系统会先访问这个空间,将给个空间中的数在复制个d,就需要再次调用拷贝构造函数;
//	//调用函数就有形参转实参的过程,就需要拷贝;
//	Date(const Date& d)
//	{
//		_year = d._year+1;
//		_month = d._month+1;
//		_day = d._day+1;
//	}
//	void print(Date d)
//	{
//		cout << d._year << " " << d._month <<" " << d._day << endl;
//		return;
//	}
//private:
//	int _year;
//	int _month;
//	int _day;
//};
//
//void f(Date d)
//{
//
//}
//int main()
//{
//	Date d1(2023, 3, 20);
//	Date d2(d1);
//	//f(d1);
//	d1.print(d2);
//	return 0;
//
//}

 

3.代码解释编译器默认拷贝构造 (重点)

class Time
{
public:Time(){_hour = 1;_minute = 1;_second = 1;}Time(const Time& t)//拷贝构造函数{_hour = t._hour;_minute = t._minute;_second = t._second;cout << "Time::Time(const Time&)" << endl;}
private:int _hour;int _minute;int _second;
};
class Date
{
private:// 基本类型(内置类型)int _year = 1970;int _month = 1;int _day = 1;// 自定义类型Time _t;
};
int main()
{Date d1;// 用已经存在的d1拷贝构造d2,此处会调用Date类的拷贝构造函数// 但Date类并没有显式定义拷贝构造函数,则编译器会给Date类生成一个默认的拷贝构
造函数Date d2(d1);return 0;
}

 如果你看到这,那么请思考一下构造函数和拷贝构造的区别在于什么?都是用类型名构造的函数!

4.构造函数、析构函数和拷贝构造的应用

实现一个某一天过多少天以后是那一天

第一步确定那一天日期,我们就需要先用构造函数构造一个day

第二步实现算法,在代码实现时会用到拷贝构造

代码实现

class Date//构造一个日期类对象出来
{
public:Date(int year, int month, int day)//构造函数的初始化{_year = year;_day = day;_month = month;}int GetMonthDay(int year, int month)//用来计算某一年某一天的天数{assert(month > 0 && month < 13);int monthArray[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };//第一个0就是用来将月份和下标对齐的if (month == 2 && (_year % 4 == 0 || (_year % 100 != 0 && _year % 400 == 0)))//闰年的二月返回29天{return 29;}else//不是闰年的2月就是平年某一月的天数return monthArray[month];}Date GetXDay(int x)//传参时自动传d1的地址{Date tmp(*this);//this 代表d1;//这里就是拷贝构造,编译器自动实现了tmp._day += x;//int y = GetMonthDay(_year, _month);while (tmp._day > GetMonthDay(tmp._year, tmp._month)){tmp._day -= GetMonthDay(tmp._year, tmp._month);tmp._month++;if (tmp._month == 13){tmp._month = 1;tmp._year++;}}return tmp;}void Print(){cout << _year << "年" << _month << "月" << _day <<"日"<< endl;}
private:int _year;int _month;int _day;
};int main()
{Date d1(2023,2,3);Date d2 = d1.GetXDay(100);d1.Print();d2.Print();return 0;
}

三、const的成员函数

在学习赋值运算之前先引入const成员函数

将const修饰的“成员函数”称之为const成员函数,const修饰类成员函数,实际修饰该成员函数 隐含的this指针,表明在该成员函数中不能对类的任何成员进行修改。

当我们不需要修改函数中任何变量时,我们就需要用const修饰

三、赋值运算符重载

1、运算符重载是什么?

C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其 返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。

函数名字为:关键字operator后面接需要重载的运算符符号。

函数原型:返回值类型 operator操作符(参数列表)

注意: 不能通过连接其他符号来创建新的操作符:比如operator@

重载操作符必须有一个类类型参数 、

用于内置类型的运算符,其含义不能改变,例如:内置的整型+,不 能改变其含义

作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐 藏的this .* :: sizeof ?: . 注意以上5个运算符不能重载。这个经常在笔试选择题中出 现。

2、运算符重载的作用

在之前的c中,运算符有 +   -    *    /   +=    -=  <   >   <=   >=等 ;现在想要实现一个日期加上一个天数或者比较两个日期谁大或谁小时,就需要运用到运算符重载,因为在编译器不知道的大小,它不是编译器内置的类型

3、日期的比较

1、运算符的声名

int GetMonthDay(int year, int month, int day);
class Date
{//友元friend ostream& operator<<(ostream& out,  Date& d);friend istream& operator>>(istream& in, Date& d);
public:Date(int year = 1900, int month = 1, int day = 1);//构造函数Date(const Date& d);//拷贝构造void print();bool operator==(Date d)const;//只是读取数据进行比较,不需要修改,所以我们用coust修饰bool operator>(Date d)const;bool operator>=(Date d)const;bool operator<(Date d)const;bool operator<=(Date d)const;bool operator!=(Date d)const;private:int _year;int _month;int _day;};;

2、函数实现

bool Date::operator==(Date d)const//只读并不用修改数值
{return _year == d._year&& _month == d._month&& _day == d._day;//只需要判断年月日是否相等来判断天日期是否相等
}
//d1>d2
bool Date:: operator>(Date d)const//这里括号里是缺省型参数,缺少的是this,*this就是>前面的参数d1
{if (_year < d._year)//第一步判断年的大小//_year等价于this->_year{return false;}else if (_year > d._year){return true;}else//年相等{if (_month < d._month)//判断月{return false;}else if (_month > d._month){return true;}else//月相等{if (_day < d._day)//判断天{return false;}else{return true;}}}
}
bool Date::operator>=(Date d)const//由于我们已经重定义了==和>,所以在这里可以直接使用
{return (*this == d) || (*this > d);//*this就是>=前面的那个日期,
}
bool Date::operator<(Date d)const
{return !(*this >= d);//用了>=只需要取反就是<
}
bool Date::operator<=(Date d)const
{return !(*this > d);//对>取反就是<=
}
bool Date::operator!=(Date d)const
{return !(*this == d);
}
int main()
{Date d1(2024, 3, 3);Date d2(2024, 3, 23);cout <<"<" << d1.operator<(d2) << endl;cout << "=="<<d1.operator==(d2) << endl;cout << (d1 == d2) << endl;//转化为运算符重载,operator==(d1, d2)return 0;
}

4、 前置++和后置++重载

前置++是先对变量++再用,

后置++是先对变量使用再对变量++;

class Date
{
public:Date(int year = 1900, int month = 1, int day = 1){_year = year;_month = month;_day = day;}// 前置++:返回+1之后的结果// 注意:this指向的对象函数结束后不会销毁,故以引用方式返回提高效率Date& operator++(){_day += 1;return *this;}// 后置++:// 前置++和后置++都是一元运算符,为了让前置++与后置++形成能正确重载// C++规定:后置++重载时多增加一个int类型的参数,但调用函数时该参数不用传递,编译器
自动传递// 注意:后置++是先使用后+1,因此需要返回+1之前的旧值,故需在实现时需要先将this保存
一份,然后给this+1//       而temp是临时对象,因此只能以值的方式返回,不能返回引用Date operator++(int){Date temp(*this);_day += 1;return temp;}
private:int _year;int _month;int _day;
};

 5、日期的+-运算

1、声名在类内

class Date
{//友元friend ostream& operator<<(ostream& out,  Date& d);friend istream& operator>>(istream& in, Date& d);
public:Date(int year = 1900, int month = 1, int day = 1);Date(const Date& d);void print();Date& operator+=(int day);//这里由于需要修改*this所以没有加constDate operator+(int day)const;Date& operator-=(int day);Date operator-(int day)const;int operator-(const Date d)const;Date& operator++();Date operator++(int);Date& operator--();Date operator--(int);//void operator << (ostream & out);private:int _year;int _month;int _day;};

2、函数实现

Date& Date::operator+=(int day)
{if (day < 0){*this -= day;return *this;}_day += day;while (_day > GetMonthDay(_year, _month)){_day -= GetMonthDay(_year, _month);_month++;if (_month == 13){++_year;_month = 1;}}return *this;
}//出来作用域*this不存在,所以需要加&返回
Date Date::operator+(int day)const
{ /*Date tmp(*this);tmp._day += day;while (tmp._day > GetMonthDay(tmp._year, tmp._month)){tmp._day -= GetMonthDay(tmp._year, tmp._month);tmp._month++;if (tmp._month == 13){++_year;tmp._month = 1;}}*///优化,由于上面的代码当day<0时,就会导致天数不对Date tmp(*this);if (day < 0){tmp -= day;return tmp;}tmp += day;return tmp;
}
Date& Date:: operator-=(int day)
{if (day < 0){*this += day;return *this;}_day -= day;while (_day <= 0){_month--;if (_month ==0){_year--;_month = 12;}_day += GetMonthDay(_year, _month);}return *this;
}//出来作用域,*this销毁,所以传引用返回进行临时的保存*this的值
Date Date::operator-(int day)const
{Date tmp(*this);if (day < 0)//只需要判断day大于0还是小于0;{tmp += day;return tmp;}tmp -= day;return tmp;
}int Date::operator-(const Date d)const//这里的-和上面的不一样在于参数类型不同,也就是一个-多少天;这里的-是日期-日期返回的是两个日期的差值
{
//找到较大的日期Date max = *this;Date min = d;int flag = 1;if (*this < d){max = d;min = *this;flag = -1;}int n = 0;while (min != max){	++min;++n;}return n*flag;
}
Date& Date:: operator++()//这里代码前置++,先对*this++,在用*this值
{*this += 1;//*this+1就是加上1天返回下一天的日期return *this;
}
Date Date:: operator++(int)//这里为了区分前置++和后置++,在括号中加入了int代表后置++
{Date tmp(*this);//这里是因为是后置++,先用*this的值,在对*this++,(*this)+=1;return tmp;
}
Date& Date:: operator--()
{*this -= 1;return *this;
}
Date Date:: operator--(int)
{Date tmp(*this);(*this) -= 1;return tmp;
}

 

 


总结

提示:这里对文章进行总结:
例如:以上就是今天要讲的内容,本文仅仅简单介绍了pandas的使用,而pandas提供了大量能使我们快速便捷地处理数据的函数和方法。

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

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

相关文章

vite vue3 import.meta.glob动态路由

在Vite中使用Vue 3&#xff0c;你可以使用import.meta.glob来导入目录下的多个Vue组件&#xff0c;并自动生成路由。以下是一个简单的例子&#xff1a; router/index.js // router/index.js import { createRouter, createWebHistory } from vue-router;// 自动导入views目录下…

基于Spring Boot的在线学习系统的设计与实现

基于Spring Boot的在线学习系统的设计与实现 摘 要 在线学习系统是以大学传统线下教学方式不适应信息技术的迅速发展为背景&#xff0c;提高学习效率&#xff0c;解决传统教学问题&#xff0c;并且高效的实现教学信息化的一款软件系统。为了更好的实现对于教学和学生的管理&a…

词令外卖节红包天天神券每天领取直达入口

词令外卖节红包天天领直达入口 1、打开「词令」关键词口令直达微信小程序&#xff1b; 2、输入词令「外卖红包88」关键词直达口令&#xff1b; 3、搜索直达进入外卖红包天天领入口&#xff0c;即可成功领取外卖节红包和天天神券点外卖可享受券后价优惠&#xff1b; *温馨提醒&…

HTML5通过api实现拖放效果 dataTransfer对象

dataTransfer对象 说明&#xff1a;dataTransfer对象用于从被拖动元素向放置目标传递字符串数据。因为这个对象是 event 的属性&#xff0c;所以在拖放事件的事件处理程序外部无法访问 dataTransfer。在事件处理程序内部&#xff0c;可以使用这个对象的属性和方法实现拖放功能…

无药可医还能怎么办?越没本事的人,越喜欢从别人身上找原因!——早读(逆天打工人爬取热门微信文章解读)

无药可医的病该怎么办呢&#xff1f; 引言Python 代码第一篇 洞见 《骆驼祥子》&#xff1a;越没本事的人&#xff0c;越喜欢从别人身上找原因第二篇 人民日报 来啦 新闻早班车要闻社会政策 结尾 “吾日三省吾身&#xff0c;而后深知自助者天助之。” 在人生的迷宫中 遭遇困境时…

uniapp-打包IOS的APP流程

打包前所需配置 在manifest文件内配置 1. APP图标 2. 启动界面 有三种启动界面配置 第一种是 HBuilderX 官方给的通用启动界面&#xff0c;页面单一&#xff0c;屏幕中间就一个圆框图标 第二种是自定义的启动图&#xff0c;无法通过AppStore的审核 第三种是自定义storyboard启动…

论文研读:Transformers Make Strong Encoders for Medical Image Segmentation

论文&#xff1a;TransUNet&#xff1a;Transformers Make Strong Encoders for Medical Image Segmentation 目录 Abstract Introduction Related Works 各种研究试图将自注意机制集成到CNN中。 Transformer Method Transformer as Encoder 图像序列化 Patch Embed…

特殊数据类型

目录 记录类型 定义一个记录类型 myrecord_type&#xff0c;用于存储 emp 数据表中的员工姓名和职务 %TYPE 类型 定义一个变量&#xff0c;存储数据表 emp 中编号为 7369 的员工姓名&#xff0c;并且显示出结果 %ROWTYPE 类型 声明一个用于存储 emp 数据表中每行记录的变…

【力扣hot100】160.相交链表

相交链表 给你两个单链表的头节点 headA和 headB &#xff0c;请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点&#xff0c;返回 null 。 图示两个链表在节点 c1 开始相交&#xff1a; 题目数据 保证 整个链式结构中不存在环。 注意&#xff0c;函数返回…

牛客练习赛123 A~C

A.炸鸡块哥哥的粉丝题 输出字符串的前 ⌈ n 2 ⌉ \lceil \frac{n}{2} \rceil ⌈2n​⌉ 个字符 void solve() {int n;string s;cin >> n >> s;cout << s.substr(0, (n 1) / 2); }B.智乃想考一道鸽巢原理 当小球总个数为奇数时&#xff0c;贪心的留下 1 个…

天梯算法Day3整理

浮点数解析 炸鱼题掠过 冲突值 题面 解析 方法一 —— 并查集 按照边值排序&#xff0c;然后按边值从大到小遍历&#xff0c;通过并查集判断能否将所有点无冲突地归于两个集合。在判断时&#xff0c;若有两个点不得不产生冲突&#xff0c;则输出这两个点之间的边值并结束。…

LeetCode Python - 81. 搜索旋转排序数组 II

目录 题目描述解法运行结果 题目描述 已知存在一个按非降序排列的整数数组 nums &#xff0c;数组中的值不必互不相同。 在传递给函数之前&#xff0c;nums 在预先未知的某个下标 k&#xff08;0 < k < nums.length&#xff09;上进行了 旋转 &#xff0c;使数组变为 […

好用还平价的挂耳式耳机有哪些?五款超平价品牌测评推荐

在数字化时代&#xff0c;耳机已经成为我们日常生活中不可或缺的一部分。而开放式耳机作为一种新兴趋势&#xff0c;以其独有的开放性设计和卓越的音质表现&#xff0c;正在悄然改变着人们的听音习惯。不同于传统的耳机产品&#xff0c;开放式耳机让音乐与外界环境相得益彰&…

我暂停了我的博士学位,尝试了一些不同的东西,然后带着一个新的视角回来了

警报在我耳边响起。静音后&#xff0c;我躺在床上&#xff0c;盯着天花板又看了30分钟。我继续攻读博士学位的动力正在减弱&#xff0c;这使得我越来越难以站起来&#xff0c;面对每一个新的疲惫的日子。我意识到&#xff0c;在我的计划剩下的 2 年里继续奋斗将冒着完全倦怠和崩…

4.C#对接微信Native支付(调用支付下单生成二维码接口)

在完成了前边几篇文章的操作后&#xff0c;我们接下来需要写实际的业务接口。调用微信的native下单接口。 手先看下官网的api文档&#xff0c;https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_4_1.shtml 大概的流程是&#xff1a;商户后台系统先调用微信支付的Nativ…

Matlab与数学计算

原文地址&#xff1a;Matlab与数学计算 - Pleasure的博客 下面是正文内容&#xff1a; 前言 这是一篇笔记。主要用于介绍MatLab的作用以及其作为数学工具的使用方法。 目的是总结学校课件复习自用&#xff0c;但是不可能像相关的书籍那么系统全面&#xff0c;力求简单明了。都…

pygame用自带函数绘制三角形 计算重心坐标

三角形重心坐标公式 三角形重心的坐标可以通过其三个顶点的坐标计算得出&#xff0c;公式为((X1X2X3)/3,(Y1Y2Y3)/3)。12 这是因为在平面直角坐标系中&#xff0c;重心的坐标是顶点坐标的算术平均数 中间黑点是重心坐标 import pygame from pygame.locals import * import sy…

scanf/fscanf/sscanf和printf/fprintf/sprintf的使用和对比

一&#xff1a;函数的对比 scanf&#xff1a;从标准输入流中读取格式化数据&#xff08;通常是键盘&#xff09; printf&#xff1a;将格式化数据输出到标准输出流&#xff08;通常是屏幕&#xff09; fscanf&#xff1a;适用于所有输入流的格式化输入函数&#xff08;一般从…

网络安全入门 5 天速成教程_ WEB 安全渗透攻防技术

前言 随着 Web 技术发展越来越成熟&#xff0c;而非 Web 服务越来越少的暴露在互联网上&#xff0c;现在互联网安全主要指的是 Web 安全。 为了自身不“裸奔”在大数据里&#xff0c;渐渐开始学习 Web 安全&#xff0c;在学习 Web 安全的过程中&#xff0c;发现很大一部分知识…

Codeforces Round 838 (Div. 2) D. GCD Queries

题目 思路&#xff1a; #include <bits/stdc.h> using namespace std; #define int long long #define pb push_back #define fi first #define se second #define lson p << 1 #define rson p << 1 | 1 const int maxn 1e6 5, inf 1e9, maxm 4e4 5; co…