[C++] 类与对象(中)完整讲述运算符重载示例 -- 日期类(Date) -- const成员

news2024/11/17 22:50:07

目录

1、前言

2、全缺省的构造函数

3、打印接口

4、拷贝构造

5、赋值运算符重载(operator=)

5.1赋值重载是默认成员函数,重载格式:

5.2 赋值重载不能为全局函数

5.3 编译器默认生成

6、析构函数

7、operator>

8、operator==

9、operator>=

10、operator<

11、operator<=

12、operator!=

13、operator+= (日期+=天数)

14、operator+ (日期+天数)

15、operator-= (日期-=天数)

16、operator- (日期-天数)

17、前置++,后置++,前置--,后置--

18、日期 - 日期(返回天数)

19、const成员


1、前言

本篇文章我们将主要实现以下的这些接口:

#include <iostream>
using namespace std;

class Date
{
public:

	// 获取某年某月的天数
	int GetMonthDay(int year, int month) const;
	// 全缺省的构造函数
	Date(int year = 1900, int month = 1, int day = 1);
	//打印接口
	void Print() const;


	// 拷贝构造函数
	// d2(d1)
	Date(const Date& d);
	// 赋值运算符重载
	// d2 = d3 -> d2.operator=(&d2, d3)
	Date& operator=(const Date& d);
	// 析构函数
	~Date();


	//总结一下:只读函数可以加const,内部不涉及修改成员的都是只读函数
	// >运算符重载
	bool operator>(const Date& d) const;
	// ==运算符重载
	bool operator==(const Date& d) const;
	// >=运算符重载
	bool operator>=(const Date& d) const;
	// <运算符重载
	bool operator<(const Date& d) const;
	// <=运算符重载
	bool operator<=(const Date& d) const;
	// !=运算符重载
	bool operator!=(const Date& d) const;

	// 日期+=天数
	Date& operator+=(int day);
	// 日期+天数
	Date operator+(int day) const;
	// 日期-天数
	Date operator-(int day) const;
	// 日期-=天数
	Date& operator-=(int day);


	// 函数重载
	// 运算符重载
	// 前置++
	Date& operator++(); //++d1 -> d1.operator()
	// 加一个int参数,进行占位,跟前置++构成函数重载进行区分
	// 后置++
	Date operator++(int); //d1++ -> d1.operator(0)
	// 后置--
	Date operator--(int);
	// 前置--
	Date& operator--();


	// 日期-日期 返回天数
	int operator-(const Date& d) const;

private:

	int _year;
	int _month;
	int _day;

};

本次的项目我们以多文件来写,包含以下三个文件:

接下来我们就对以上的接口一一进行实现:

2、全缺省的构造函数

对于全缺省的构造函数正常写的时候存在一个不足,万一传参传的月份与天是不存在的,虽然对实例化的对象初始化了,但是是违法的,因此我们需要判断一下,这里就需要我们对月份的天数写一个函数,构造的时候先对比一下。

我们这里先实现GetMonthDay接口:

// 获取某年某月的天数
int Date::GetMonthDay(int year, int month) const
{
	static int monthDay[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };

	if(2 == month
		&& ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)))
	{
		return 29;
	}

	return monthDay[month];
}
// 全缺省的构造函数
Date::Date(int year, int month, int day)
{
	if (month < 1 || month > 12
		|| day < 1 || day > GetMonthDay(year, month))// 判断日期是否合法
	{
		cout << "非法日期" << endl;
		exit(-1);
	}
	else
	{
		_year = year;
		_month = month;
		_day = day;
	}
}

对于GetMonthDay接口,我们写了一个数组存每个月有多少天,默认二月是28天,在下面我们会判断一下如果是找二月的天数,对年进行判断,看看是否为闰年,为闰年的时候直接返回29,其他的就直接返回月份对应的天数。我们对数组使用static修饰,因为后面会不断的调用此函数,因此我们将其放到静态区,只开辟一次,节省了时间,一次的时间不多,但是如果是大量的调用会极大的提升效率。

这里我们会有人问,为什么在GetMonthDay接口后面加一个const,这里我们来说明一下:

3、打印接口

打印接口没什么讲的,我们直接一把梭哈:

//打印接口
void Date::Print() const
{
	cout << _year << "年" << _month << "月" << _day << "日" << endl;
}

打印接口对this使用const修饰是因为,有可能我们传过来的对象是const修饰的,如果Print接口不加const就涉及权限放大的问题,导致出错。改为const后就不存在权限问题了。

4、拷贝构造

对于拷贝如果还有不清楚的可以点击后面的链接,里面有对拷贝构造详细讲解哦:点这里哦

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

5、赋值运算符重载(operator=)

5.1赋值重载是默认成员函数,重载格式:

参数类型:const T&,传引用可以提高传参效率
返回值类型:T&,返回引用可以提高返回的效率,有返回值目的是为了支持连续赋值
检测是否自己给自己赋值
返回*this:要复合连续赋值的含义

我们按重载格式来写一下:

// 赋值运算符重载
// d2 = d3 -> d2.operator=(&d2, d3)
Date& Date::operator=(const Date& d)
{
	if (this != &d)// 存在this就是d的情况
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}

	return *this;
}

5.2 赋值重载不能为全局函数

原因:赋值运算符如果不显式实现,编译器会生成一个默认的。此时用户再在类外自己实现一个全局的赋值运算符重载,就和编译器在类中生成的默认赋值运算符重载冲突了,故赋值运算符重载只能是类的成员函数。

我们自己写一下试试:

我这里使用的vs2019编译器,编译器会提示,编译期间直接报错了,因此赋值运算符重载是不可以为全局函数的。

5.3 编译器默认生成

当我们不写的时候编译器会自动生成一个复制重载函数,但是默认生成的函数对内置类型是直接赋值的,对自定义类型的成员变量需要调用对应的类赋值重载函数来完成赋值。

因此,如果成员变量里存在自定义类型(类类型),自定义类型的赋值重载函数必须是正确的,这样才是对的。

如两个栈实现一个队列,队列的赋值重载函数可以默认生成,但是栈的必须自己写,因为栈存在申请资源,如果直接拷贝,两个栈使用的是同一块资源,这样的话,一个是出栈的栈,一个是进栈的栈,不管进出其实是对同一个栈进行操作,这就是错误的。

注意:如果类中未涉及到资源管理,赋值运算符是否实现都可以;一旦涉及到资源管理则必须要实现。

赋值与拷贝的区别:拷贝是一个已经存在的对象去初始化另一个要创建的对象;

赋值是两个已经存在的对象进行拷贝。

6、析构函数

对于析构函数如果还有不清楚的可以点击后面的链接,里面有对析构函数详细讲解哦:点这里哦

// 析构函数
Date::~Date()
{
	_year = 0;
	_month = 0;
	_day = 0;
}

7、operator>

对 日期>日期 的判断中

1、当年小于后,就不用再比了,直接返回false

2、当年相同比较月,月小于后,直接返回false

3、当年月都相同,日小于后,返回false

4、当以上判断都不正确,说明前面的日期 大于 后面的日期,返回true

// >运算符重载
bool Date::operator>(const Date& d) const
{
	if (_year < d._year)
		return false;
	else if (_year == d._year && _month < d._month)
		return false;
	else if (_year == d._year && _month == d._month && _day < d._day)
		return false;
	else
		return true;
}

8、operator==

对 日期==日期 的判断很简单,年月日分别都相等就是正确的,我们看看代码实现:

// ==运算符重载
bool Date::operator==(const Date& d) const
{
	if (_year == d._year && _month == d._month && _day == d._day)
		return true;
	else
		return false;
}

上面的代码还可以再精简一下:

bool Date::operator==(const Date& d) const
{
	return _year == d._year 
		&& _month == d._month 
		&& _day == d._day;
}

9、operator>=

对于 >= 来说,我们其实可以复用上面的 >与== ,不用再去写一套逻辑来判断,>= 的逻辑就是大于或者等于。

我们来看看实现代码:

// >=运算符重载
bool Date::operator>=(const Date& d) const
{
	return (*this > d) || (*this == d);
}

10、operator<

< 与 >= 是相反的逻辑,因此我们对 >= 取反就可以实现。

// <运算符重载
bool Date::operator<(const Date& d) const
{
	return !(*this >= d);
}

11、operator<=

<= 与 > 是相反的逻辑,因此我们对 > 取反就可以实现。

// <=运算符重载
bool Date::operator<=(const Date& d) const
{
	return !(*this > d);
}

12、operator!=

!= 与 == 是相反的逻辑,因此我们对 == 取反就可以实现。

// !=运算符重载
bool Date::operator!=(const Date& d) const
{
	return !(*this == d);
}

13、operator+= (日期+=天数)

+=是在原基础上进行修改,因此隐含的this不能用const修饰,因为在原基础上修改,所以出了+=函数体,对象还在,我们使用引用返回,这样可以减少拷贝。

我们画图对 += 天数分析:

代码实现:

// 日期+=天数
Date& Date::operator+=(int day)
{
	if (day < 0)
	{
		return *this -= (-day);
	}

	_day += day;
	while (_day > GetMonthDay(_year, _month))
	{
		_day -= GetMonthDay(_year, _month);
		//月进位
		_month++;

		//月满了
		if (13 == _month)
		{
			_year++;
			_month = 1;
		}
	}

	return *this;
}

我们来测试一下:

对照看看我们写的+=是否正确

14、operator+ (日期+天数)

+与+= 的区别在于+=是对对象本身+,而+不是对对象本身的改变。所以我们需要实例化一个新的对象,将传来的对象拷贝给新的对象,存放+天数的结果并返回。

// 日期+天数
Date Date::operator+(int day) const
{
	Date tmp(*this);

	tmp += day;

	return tmp;
}

这里我们拷贝了一份之后,就能复用+=的代码。

我们这里看看传的对象与+后的对象的结果:

这里我们可以看到+并没有影响到d1的结果。

15、operator-= (日期-=天数)

-=是在原基础上进行修改,因此隐含的this不能用const修饰,因为在原基础上修改,所以出了-=函数体,对象还在,我们使用引用返回,这样可以减少拷贝。

我们画图对-=天数进行分析:

代码实现:

// 日期-=天数
Date& Date::operator-=(int day)
{
	if (day < 0)
	{
		return *this += (-day);
	}

	_day -= day;
	while (_day <= 0)
	{
		_month--;
		if (0 == _month)
		{
			_year--;
			_month = 12;
		}

		_day += GetMonthDay(_year, _month);
	}

	return *this;
}

运行结果:

对比一下,看我们实现的是否正确:

16、operator- (日期-天数)

-与+ 的逻辑是类似的,-与-= 的区别在于-=是对对象本身-,而-不是对对象本身的改变。所以我们需要实例化一个新的对象,将传来的对象拷贝给新的对象,存放-天数的结果并返回。

// 日期-天数
Date Date::operator-(int day) const
{
	Date tmp(*this);

	tmp -= day;

	return tmp;
}

这里拷贝一份this之后,就可以在拷贝的tmp上复用-=。

我们来测试一下:

17、前置++,后置++,前置--,后置--

对于前置++与后置++,前置--与后置--,它们的函数名是相同的,但是实现的功能是不同的,如果在符号表里找这怎么能分得清楚呢?

对于这样的问题,C++有自己确定的规定,C++规定:后置++重载时多增加一个int类型的参数,但调用函数时该参数不用传递,编译器自动传递。后置--也是如此。

这样就能实现函数重载,符号表中函数名虽然相同,但是一个有参数一个没参数。

下面我们对这几个函数接口分别实现:

前置++的规则是:先++,再使用。因此前置++就相当于+=1,复用+=。

// 前置++
Date& Date::operator++() //++d1 -> d1.operator()
{
	*this += 1;
	return *this;
}

后置++的规则是:先使用,再++。这里我们先将this拷贝一份,然后对this+=1,返回拷贝的对象,这样就做到了先使用,再++。

// 后置++
Date Date::operator++(int) //d1++ -> d1.operator(0)
{
	Date tmp(*this);

	*this += 1;

	return tmp;
}

前置--的规则是:先--,再使用。因此前置++就相当于-=1,复用-=。

// 前置--
Date& Date::operator--()
{
	*this -= 1;

	return *this;
}

后置--的规则是:先使用,再--。这里我们先将this拷贝一份,然后对this-=1,返回拷贝的对象,这样就做到了先使用,再--。

// 后置--
Date Date::operator--(int)
{
	Date tmp(*this);

	*this -= 1;

	return tmp;
}

18、日期 - 日期(返回天数)

此接口是 日期-日期,这里有一个存在一个小细节,当 小日期-大日期 的时候,返回值就是负数,因此我们对这个细节要注意一点,我们来对这个接口的思路梳理一遍:

1、我们用假设法,假设this是大日期,将其赋值给max对象,第二个参数是小日期,将其赋值给min对象,并定义一个flag初始化为 1;

2、比较一下,如果this是小日期那么将max、min两个对象的内容重新赋值,并将flag赋值为 -1;

3、定义一个计数器 n,让小追大,++min一次,计数器也++,追上后就跳出循环,返回 n*flag 即可。

注意:我们让小追大的时候使用前置++,虽然前置++与后置++都可以实现,但是后置++的接口中需要拷贝两次,开始拷贝一次,返回值会再拷贝一次,前置++在大量计算的时候可以实现时间与空间双重优势。

我们来实现代码:

// 日期-日期 返回天数
int Date::operator-(const Date& d) const
{
	int flag = 1;
	Date max = *this;
	Date min = d;
	if (max < min)
	{
		max = d;
		min = *this;
		flag = -1;
	}

	int n = 0;
	while (min != max)
	{
		++n;
		++min;
	}

	return n * flag;
}

测试:

19、const成员

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

这里我们使用 Print 接口来讲:

使用const修饰后,与原本的成员函数是不冲突的,因为是构成重载的。

我们来看看两种版本的 Print 接口:

void Date::Print()
{
	cout << _year << "年" << _month << "月" << _day << "日" << endl;
}

void Date::Print() const
{
	cout << _year << "年" << _month << "月" << _day << "日" << endl;
}

对于打印函数来讲,函数内部是不涉及修改成员的,这种就是只读函数。

因此我们使用了const修饰,还存在一种问题,当我们的对象是const修饰,如果Print函数不用const修饰,在调用的时候就存在权限放大问题。

我们先将const修饰的Print函数频闭掉:

我们再将const修饰的接口放开看看:

总结:相同函数,const修饰的this与不修饰的函数构成重载;

函数内部是不涉及修改成员的就是只读函数,const修饰后更安全,并且对于具有常属性的对象也可以兼容。

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

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

相关文章

Swift async/await 并发中如何将任务组(TaskGroup)转换为异步序列(AsyncSequence)

功能需求 在 Swift 新结构化并行模型的开发中, 提出了任务组(TaskGroup)和异步序列(AsyncSequence)的概念。有时候,为了简洁和效率方面的原因,我们需要将 TaskGroup 的结果转换为异步序列。 如上图所示,我们试图将任务组的结果转换为异步序列,但不幸失败了。 那么…

AI在线取英文名

AI在线取英文名 您是否曾经为了给自己、孩子或者您的产品取英文名字而苦恼&#xff1f;如何保留中文名字的音韵特点&#xff0c;同时又能传达名字的文化含义&#xff1f;别担心&#xff0c;我们为您准备了一款强大而创新的在线工具—— 中英名字创意匹配器&#xff0c;让您在命…

Ubuntu系统下库的相关问题解析

本文通过以下几个问题对Ubuntu系统下库的相关问题进行解析。 1.库是什么? 库是写好的&#xff0c;现有的&#xff0c;成熟的&#xff0c;可以复用的代码。现实中每个程序都要依赖很多基础的底层库&#xff0c;不可能每个人的代码都从零开始&#xff0c;因此库的存在意义非同…

ad+硬件每日学习十个知识点(27)23.8.7 (DCDC的datasheet)

文章目录 1.DCDC拿到手册需要关注的参数2.MOS选型3.TPS54331的ECO模式&#xff08;跳跃模式&#xff0c;损耗小、纹波大&#xff09;4.TPS54331的引脚功能&#xff08;BOOT这里没太看懂&#xff0c;之后回来再看看&#xff09;5.TPS54331的额定值6.TPS54331在test条件下的电气特…

多个配置WebMvcConfigurationSupport失效问题

最近在项目中用类继承WebMvcConfigurationSupport实现拦截器 Configuration RequiredArgsConstructor public class SpringWebSupport extends WebMvcConfigurationSupport {private final ProjectInterceptor projectInterceptor;// 拦截器 //设置拦截器对象和拦截请求Ove…

idea模板的使用(配置xml文件模板)

1. 问题的引出 我们在日常项目中可以发现&#xff0c;sql映射文件和mybatis主配置文件&#xff0c;以及application.yml文件中有很多固定不变的内容&#xff0c;为了方面使用&#xff0c;所以可以把这些xml文件设置为模板 2. 创建模板的步骤 按照图片一步一步进行即可 点击…

【JavaEE】让“单车变摩托”的神级框架—Spring MVC的深入讲解

【JavaEE】Spring MVC 程序开发要点总结 文章目录 【JavaEE】Spring MVC 程序开发要点总结1. 什么是 Spring MVC1.1 MVC的执行流程1.2 为什么要学Spring MVC1.3 Spring MVC 的学习 2. Spring MVC 的创建和连接2.1 创建2.2 连接2.2.1 RestController注解2.2.2 RequestMapping注解…

使用ffmpeg将m4a及wav等文件转换为MP3格式

要使用ffmpeg将m4a及wav等文件转换为MP3格式&#xff0c;您可以按照以下步骤进行操作&#xff1a; 安装 ffmpeg 确保您已经安装了ffmpeg软件。如果没有安装&#xff0c;请访问ffmpeg的官方网站https://ffmpeg.org/ 并按照说明进行安装。 Win10 / Win11 可以通过 winget 命令…

Springboot中创建拦截器

目录 目的 实现过程 1、创建拦截器 2、注册拦截器 完整代码 目的 在Springboot项目中创建拦截器&#xff0c;在进入Controller层之前拦截请求&#xff0c;可对拦截到的请求内容做响应处理&#xff0c;如&#xff1a;校验请求参数、验证证书等操作&#xff1b; 实现过程 1、创…

五金店和MRO工业品超市有什么区别

五金店和MRO工业品超市有一定的相似性&#xff0c;但它们之间还是存在一些区别的。 五金店主要经营各种五金产品&#xff0c;如家具五金、建筑五金、工具、螺丝螺母等&#xff0c;五金店的产品范围相对较窄&#xff0c;通常针对个人消费者和家庭使用&#xff0c;如日常家居维修…

AutoDL服务器的镜像版本太高,配置python3.7 tensorflow1.15版本的框架的步骤

1.选择一个实例&#xff0c;进入后端界面 2. 更新bashrc中的环境变量 conda init bash && source /root/.bashrc查看虚拟环境 conda info --envs可以看到此时有一个base的虚拟环境 但是它的python版本为3.8.10&#xff0c;无法安装tensorflow1.15,所以我们要创建一个…

40% Ubuntu 用户面临着新特权提升漏洞风险

导读Wiz 的研究人员发现&#xff0c;最近被引入 Ubuntu 内核的两个 Linux 漏洞&#xff0c;可能会在大量设备上为非特权本地用户提升权限。这两个漏洞被追踪为 CVE-2023-32629 和 CVE-2023-2640&#xff0c;预计影响了大约 40% 的 Ubuntu 用户。 根据介绍&#xff0c;其中 CVE…

干货分享|Elsevier投稿进度查询功能正式上线,随时获取投稿状态!

想必广大科研学者们都经历过每天登录系统查看投稿进度的煎熬过程&#xff0c;为了方便广大科研人随时获取投稿状态&#xff0c;2023年8月&#xff0c;Elsevier【微信端投稿进度查询功能】正式上线&#xff01; 无论你是通讯作者还是共同作者&#xff0c;只需一次查询&#xff…

第二篇:导读-组件的实现:直击 Vue 核心的实现

相信作为一个 Vue.js 的开发者&#xff0c;最熟悉的应该就是组件了&#xff0c;我们开发 Vue.js 的项目&#xff0c;大部分时间都是在写组件&#xff0c;组件系统是 Vue.js 的一个重要概念&#xff0c;它是一种对 DOM 结构的抽象&#xff0c;我们可以使用小型、独立和通常可复用…

【QT】 QT开发PDF阅读器

很高兴在雪易的CSDN遇见你 &#xff0c;给你糖糖 欢迎大家加入雪易社区-CSDN社区云 前言 本文分享QT开发PDF阅读器技术&#xff0c;希望对各位小伙伴有所帮助&#xff01; 感谢各位小伙伴的点赞关注&#xff0c;小易会继续努力分享&#xff0c;一起进步&#xff01; 你的点…

python流星雨特效代码微信,python流星雨特效代码源

大家好&#xff0c;小编为大家解答python流星雨特效代码需要什么模块的问题。很多人还不知道python流星雨特效代码微信&#xff0c;现在让我们一起来看看吧&#xff01; # -*- coding:utf-8 -*- # 导入系统文件库 import pygame import random from pygame.locals import * fr…

Python爬虫都喜欢用的防封招式!

你是否在爬取数据的时候被网站的IP封锁问题困扰过&#xff1f;别担心&#xff0c;我来教你如何使用爬虫ip&#xff0c;轻松解决这个问题并提升你的爬虫效率&#xff01;快来跟我学&#xff0c;让你的Python爬虫变得更牛&#xff01; 首先&#xff0c;让我来和你解释一下什么是爬…

基于神经网络的心脏病健康系统

基于神经网络的心脏病健康系统 导语 这篇文章旨在记录该系统设计的过程&#xff0c;同时指导从零开始搭建本健康系统的环境&#xff0c;并在自己的电脑上把这个心脏病健康系统run起来。 下面是这个文件夹下各个文件的介绍&#xff1a; .\心脏病预测 ├─build --- 该系…

MYSQL进阶-查询优化- 实战 STATUS

回城传送–》《100天精通MYSQL从入门到就业》 文末有送书活动&#xff0c;可以参加&#xff01; 文章目录 一、练习题目二、SQL思路SQL进阶-查询优化- SHOW STATUS初始化数据解法SHOW STATUS是什么实战经验&#xff1a;常用的mysql状态查询1、QPS(每秒处理的请求数量)计算思路…

PyTorch 微调终极指南:第 1 部分 — 预训练模型及其配置

一、说明 如今&#xff0c;在训练深度学习模型时&#xff0c;通过在自己的数据上微调预训练模型来迁移学习已成为首选方法。通过微调这些模型&#xff0c;我们可以利用他们的专业知识并使其适应我们的特定任务&#xff0c;从而节省宝贵的时间和计算资源。本文分为四个部分&…