【C++】日期类运算符重载实战

欢迎拜访Madison-No7个人主页
文章主题: 实现日期计算器
隶属专栏我的 C++ 成长日志
写作日期:2025年9月12号

前言:

本文以日期类为例,演示常用运算符的重载实现(包括前置++、后置++、+=、-=等)。通过实现日期加减天数、日期相减等功能,帮助回顾和应用类与对象的相关知识。

一、实现日期类框架:

在Date.h文件中定义日期类,在类中实现运算符重载函数的声明,在类外(Date.cpp)实现运算符重载函数的定义,test.cpp文件用于测试。

cpp 复制代码
using namespace std;
#include<assert.h>
class Date
{
public:
	//构造函数:用于对象的初始化
	Date(int year=2000,int month=1,int day=1)
	{
		_year = year;
		_month = month;
		_day = day;
	}



	Date operator+(int day);

	Date& operator+=(int day);

	Date& operator-=(int day);

	Date operator-(int day);

	bool operator<(const Date& d);

	bool operator<=(const Date& d);

	bool operator>(const Date& d);

	bool operator>=(const Date& d);

	bool operator==(const Date& d);

	bool operator!=(const Date& d);

	//后置++
	Date operator++(int);
	//前置++
	Date& operator++();
	//后置--
	Date operator--(int);
	//前置--
	Date& operator--();

	//d1-d2
	int operator-(const Date& d);
private:
	int _year;
	int _month;
	int _day;
};;

二、实现+、-、+=、-=、==、!=和关系运算符的重载:

📖2.1 重载+、+=、-、-=:

日期+天数会涉及到日期的进位,需要知道要进位的月份天数,所以需要一个获取月份天数的函数。

要把这个函数写成成员函数,因为它会被频繁调用到,写到类里面,编译器默认会加上inline

,变成内联函数,减少函数栈帧的创建,提高程序的效率。

cpp 复制代码
	int GetMonthDay(int year,int month)
	{
		//断言一下确保传入的月份正确
		assert(month>0&&month<13);
		//把数组创建为静态变量,因为后面会频繁调用,不创建为静态变量的话,会频繁的开辟数组
		static int MonthDay[] = {-1,31,28,31,30,31,30,31,31,30,31,30,31};
		//要判断闰年,闰年二月29天
		//小细节:month==2最好写在前面,因为如果判断是闰年很麻烦,如果不是2月,那么判断闰年就白判断了呀!
		// 如果先判断是不是2月,再去判断是否是闰年,效率会提高不少
		
		if ((month==2) && ((year%4==0&&year%100!=0)|| (year%400==0)))
		{
			return 29;
		}
		return MonthDay[month];
	}

注意判断闰年条件的优先级问题,判断是否闰年是一个整体,需加上括号。

📖重载+:

日期+天数,如果天数大于当月天数,先要减去当月的总天数,然后要在月份上进位,如果月份数==13,就得向年上进位了。如此循环,直到天数小于当月总天数。

注意:

i+1,i的值是不变的,对象也一样,这里要把对象拷贝给一个新对象,对新对象操作,返回的是新对象的拷贝。

cpp 复制代码
Date Date::operator+(int day)
{
	Date temp = *this;
	temp._day = _day + day;
	while (temp._day>GetMonthDay(temp._year, temp._month))
	{
		temp._day -= GetMonthDay(temp._year,temp._month);
		temp._month++;
		if (temp._month==13)
		{
			temp._year++;
			temp._month = 1;
		}
	}
	return temp;
}

📖重载+=:

+=的重载与+的过程差不多,甚至更简单, 因为+=可以改变对象的内容,所以 +=重载不用创建新对象,可以返回对象的引用,减少拷贝,提升效率。

但是有更简单的写法,在+=的成员函数里面可以使用已经实现好的+的重载。

cpp 复制代码
Date& Date::operator+=(int day)
{
	//if (day<0)
	//{
	//	return *this -= (-day);
	//}
	//else
	//{
	//	_day += day;
	//	while (_day > GetMonthDay(_year, _month))
	//	{
	//		_day -= GetMonthDay(_year, _month);
	//		_month++;
	//		if (_month == 13)
	//		{
	//			_year++;
	//			_month = 1;
	//		}
	//	}
	//}
	//
	//return *this;//因为出作用域,对象还没有销毁,可以返回对象的别名

	*this = *this + day;
	return *this;
	
}

当然重载+也可以使用实现好的+=。

📖重载-=:

cpp 复制代码
Date& Date::operator-=(int day)
{
	if (day<0)
	{
		return *this += (-day);
	}

	_day -= day;
	while (_day <= 0)
	{
		//_month==0时不能减了,所以要判断_month是否为0
		_month--;
		if (_month == 0)
		{
			_year--;
			_month = 12;
		}
        //我们天数加的是上一个月的总天数。
		_day += GetMonthDay(_year, _month);	
	}
	return *this;
}

重载-=要考虑到日期上的借位,当月份等于0时,就要向年借位了,年份减一年,然后把月份重置为12月。

📖重载-:

同样我们可以复用实现好的-=,重载-。

cpp 复制代码
Date Date::operator-(int day)
{
	Date temp = *this;
	temp -= day;
	return temp;

	//另一种写法:
	// 这里=号是赋值,因为两个对象都存在
	//*this=*this-=day;
	//return *this;
}

综上:+和+=可以相互复用,-和-=也可以相互复用。

📖2.2重载>、<、>=、<=、==、!=:

通过重载>、<、>=、<=、==、!=来比较两个日期的大小。

我们同样可以通过复用<、==重载>、>=、<=、!=。

📖重载<:

cpp 复制代码
bool Date::operator<(const Date& d)
{
	if (_year<d._year)
	{
		return true;
	}
	else if (_year==d._year)
	{
		if (_month<d._month)
		{
			return true;
		}
		else if (_month==d._month)
		{
			return _day < d._day;
		}
	}
	return false;
}

📖重载==:

cpp 复制代码
bool Date::operator==(const Date& d)
{
	return _year == d._year && _month == d._month && _day == d._day;
}

📖重载<=:

通过使用已经实现好的<和=复用重载>=。

cpp 复制代码
bool Date::operator<=(const Date& d)
{
	return *this < d || *this == d;
}

📖重载>:

通过使用已经实现好的 < 复用重载 > 。

cpp 复制代码
bool Date::operator>(const Date& d)
{
	return !(*this < d);
}

📖重载>=:

cpp 复制代码
bool Date::operator>=(const Date& d)
{
	return !(*this <= d);
}

📖重载!=:

cpp 复制代码
bool Date::operator!=(const Date & d)
{
	return !(*this==d);
}

📖2.3 重载 前后置++和前后置--:

因为++和--是单目操作符,所以在重载++和--时,并不需要传参,隐含的this指针帮我们进行了传参。

但是在重载++运算符时,有前置++和后置++,运算符重载函数名都是operator++,⽆法很好的区分。所以C++规定,后置++重载时,增加⼀个int形参,跟前置++构成函数重载,⽅便区分。

📖重载后置++:

cpp 复制代码
 //后置++,返回++之前的值
Date Date::operator++(int)
{
	Date temp = *this;
	*this += 1;
	return temp;
}

📖重载后置++:

cpp 复制代码
//前置++
Date& Date::operator++()
{
	(*this) += 1;
	return *this;
}

📖重载后置--:

cpp 复制代码
//后置--
Date Date::operator--(int)
{
	Date temp = *this;
	*this -= 1;
	return temp;
}

📖重载前置--:

cpp 复制代码
//前置--
Date& Date::operator--()
{
	*this -= 1;
	return *this;
}

三、实现日期-日期:

在现实生活中,我们通常只进行日期间隔的计算。例如计算当前距离新年还有多少天,这种需求只需要做日期减法。而日期相加的实际应用场景非常有限,几乎找不到有意义的用途。

📖思路:

我们可以采用以下方法计算日期间隔天数:首先利用关系运算符比较两个日期大小,然后通过循环让较小的日期逐步自增,同时用变量n记录天数变化。当较小日期与大日期相等时,循环结束,此时的n值即为所求的天数差。

📖实现:

cpp 复制代码
int Date::operator-(const Date& d)
{
	int n = 0;        //记录间隔天数
	int flag = 1;     //表明距离以前日期的天数间隔
	//假设法
	Date max = *this;
	Date min = d;
	//假设不成立
	if (*this<d)
	{
		max = d;
		min = *this;
		flag = -1;    //表明距离以后日期的天数间隔
	} 
	while (min!=max)
	{
		min++;
		n++;
	}
	return n*flag;
}

四、实现<<和>>重载:

在C++中不能通过cin和cout来直接输入和输出自定义类型,所以我们得通过自己实现流插入(>>)和流提取(<<)的重载来实现自定义类型的输入和输出。

C++之所以能够对内置类型实现输入和输出,是因为在C++库中已经重载好了,直接用就行。

能够自动识别类型,是因为函数重载。

***比如:***cout<<i<<d;

cout<<i 是一个函数调用,调用 ostream& operator<< (int val); 隐含的this指针接收cout,val接收i,函数返回值类型ostream&,即cout,返回cout,是为了支持连续调用,<<的结合性是从左往右。

📖重载<<:

我们可能会这样写:

cpp 复制代码
ostream& Date::operator<<(const Date& d)
{
	cout << d._year << "年" << d._month << "月" << d._day << "日";
	return *this;
}

存在的问题:

隐含的this指针不能接收流对象,因为this 指针是一个隐含的、指向当前对象实例的指针,其指向的必须是一个已经实例化的对象。

也有可能这样写:

cpp 复制代码
void Date::operator<<(ostream& out)
{
	cout << _year << "年" << _month << "月" << _day << "日";
}

存在问题:

左操作数 会被隐式绑定为 this 指针(即 Date 对象),这与实际使用时 cout << date 的顺序矛盾,就会这样调用了:d1<<cout,用着挺别扭的。

正确的重载方式:

operator<< 声明为全局函数,并将该函数在类中通过友元(friend)声明,才能让对象访问到类中的成员变量。

cpp 复制代码
ostream& operator<<(ostream& out,const Date& d)
{
	out << d._year << "年" << d._month << "月" << d._day << "日";
	return out;
}

注意:

不能对形参out使用const修饰符,因为我们需要向out写入数据,而const修饰会禁止修改操作。此外,为了实现连续输出功能,函数返回ostream对象out。由于out在函数作用域外仍然有效,因此可以采用引用返回的方式。

📖重载>>:

与重载<<类似。

cpp 复制代码
istream& operator>>(istream& in,Date& d)
{
	cout << "请重新输入:";
	in >> d._year >>d._month >> d._day;
	return in;
}

注意:

ind 这两个形参都不能使用 const 修饰。原因在于:in 本质上是一个对象,在进行流插入操作时会修改其内部状态值;而 d 参数的设计目的就是通过流插入操作向其写入数据,因此同样不能添加 const 修饰。

五、实现日期类计算器源码:

Date.h:

cpp 复制代码
#pragma once
#include<iostream>
#include<assert.h>
using namespace std;
class Date
{
public:
	//友元声明
	friend ostream& operator<<(ostream& out, const Date& d);
	friend istream& operator>>(istream& in,Date& d);
	//日期函数构造
	Date(int year=2000, int month=1, int day=1);
	//获取当月天数
	int GetMonthDay(int year,int month)
	{
		//断言一下确保传入的月份正确
		assert(month>0&&month<13);
		//把数组创建为静态变量,因为后面会频繁调用,不创建为静态变量的话,会频繁的开辟数组
		static int MonthDay[] = {-1,31,28,31,30,31,30,31,31,30,31,30,31};
		//要判断闰年,闰年二月29天
		//小细节:month==2最好写在前面,因为如果判断是闰年很麻烦,如果不是2月,那么判断闰年就白判断了呀!
		// 如果先判断是不是2月,再去判断是否是闰年,效率会提高不少
		if ((month==2) && ((year%4==0&&year%100!=0)|| (year%400==0)))
		{
			return 29;
		}
		return MonthDay[month];
	}
	//检查日期是否正确函数
	bool CheckDay();

	void Print();
	//实现日期加天数重载
	Date operator+(int day);

	Date& operator+=(int day);

	Date& operator-=(int day);

	Date operator-(int day);

	bool operator<(const Date& d);

	bool operator<=(const Date& d);

	bool operator>(const Date& d);

	bool operator>=(const Date& d);

	bool operator==(const Date& d);

	bool operator!=(const Date& d);


	//后置++
	Date operator++(int);
	//前置++
	Date& operator++();
	//后置--
	Date operator--(int);
	//前置--
	Date& operator--();

	//d1-d2
	int operator-(const Date& d);

private:
	int _year;
	int _month;
	int _day;
};

Date.cpp:

cpp 复制代码
#include"Date.h"
//构造函数声明和定义分离,要指定类域
bool Date::CheckDay()
{
	if (_month<1 || _month>12 || _day<1 || _day>31 || _day> GetMonthDay(_year,_month))
	{
		return true;
	}
	else
	{
		return false;
	}
}

Date::Date(int year,int month,int day)
{
	_year = year;
	_month = month;
	_day = day;

	if (CheckDay())
	{
		cout << "输入日期非法" ;
	}
}

//返回值加引用,减少拷贝,提升效率
Date Date::operator+(int day)
{
	//i+1 i的值是不变的,所以要拷贝一个新对象
	//拷贝构造一个新对象
	Date temp = *this;
	
	//获取当月天数
	//满月加1
	//满年加一
	
	temp._day =_day+ day;
	while (temp._day > temp.GetMonthDay(temp._year, temp._month))
	{

		temp._day -= temp.GetMonthDay(temp._year, temp._month);
		temp._month++;
		if (temp._month == 13)
		{
			temp._year++;
			temp._month = 1;
		}
	}
	

	//也可以复用+=的逻辑,重载运算符在类的成员函数里也可使用。
	//temp += day;

	return temp;//返回temp的拷贝,因为出作用域对象就销毁了
}

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

Date& Date::operator+=(int day)
{
	//if (day<0)
	//{
	//	return *this -= (-day);
	//}
	//else
	//{
	//	_day += day;
	//	while (_day > GetMonthDay(_year, _month))
	//	{
	//		_day -= GetMonthDay(_year, _month);
	//		_month++;
	//		if (_month == 13)
	//		{
	//			_year++;
	//			_month = 1;
	//		}
	//	}
	//}
	//
	//return *this;//因为出作用域,对象还没有销毁,可以返回对象的别名


	if (day<0)
	{
		*this = *this - (-day);
	}
	else
	{
		*this = *this + day;
	}
	return *this;
	
}

Date& Date::operator-=(int day)
{
	if (day<0)
	{
		return *this += (-day);
	}
	_day -= day;

	while (_day <= 0)
	{
		//_month==0不能减,所以要判断_month是否为0
		_month--;
		if (_month == 0)
		{
			_year--;
			_month = 12;
		}
		_day += GetMonthDay(_year, _month);	
	}
	return *this;
}


Date Date::operator-(int day)
{
	Date temp = *this;
	temp -= day;
	return temp;

	//---=也可以复用---
	// 这里=号是赋值,因为两个对象都存在
	/**this=*this-=day;
	return *this;*/
}

//结论:-复用-=更好
//d1 < d2
bool Date::operator<(const Date& d)
{
	if (_year<d._year)
	{
		return true;
	}
	else if (_year==d._year)
	{
		if (_month<d._month)
		{
			return true;
		}
		else if (_month==d._month)
		{
			return _day < d._day;
		}
	}
	return false;
}

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

//可以用 < 和 = 去重载<=运算符

bool Date::operator<=(const Date& d)
{
	return *this < d || *this == d;
}

bool Date::operator>=(const Date& d)
{
	return !(*this <= d);
}

bool Date::operator>(const Date& d)
{
	return !(*this < d);
}

bool Date::operator!=(const Date & d)
{
	return !(*this==d);
}

 //后置++,返回++之前的值
Date Date::operator++(int)
{
	Date temp = *this;
	*this += 1;
	return temp;
}

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

//后置--
Date Date::operator--(int)
{
	Date temp = *this;
	*this -= 1;
	return temp;
}
//前置--
Date& Date::operator--()
{
	*this -= 1;
	return *this;
}

//两种方法
 
int Date::operator-(const Date& d)
{
	int n = 0;        //记录间隔天数
	int flag = 1;     //表明距离以前日期的天数间隔
	//假设法
	Date max = *this;
	Date min = d;
	//假设不成立
	if (*this<d)
	{
		max = d;
		min = *this;
		flag = -1;   //表明距离以后日期的天数间隔
	}
	while (min!=max)
	{
		min++;
		n++;
	}
	return n*flag;
}

ostream& operator<<(ostream& out,const Date& d)
{
	out << d._year << "年" << d._month << "月" << d._day << "日";
	return out;
}

istream& operator>>(istream& in, Date& d)
{
	while (1)
	{
		cout << "请输入日期->";
		in >> d._year >> d._month >> d._day;
		if (d.CheckDay())
		{
			cout << "日期输入错误:"<<d<<endl;
			cout << "请重新输入!!!!!" << endl;
		}
		else
		{
			break;
		}
	}
	return in;
}
	

完。

今天的分享就到这里,感谢各位大佬的关注,我会继续努力,写出更加通俗易懂的文章,大家互相学习,共同进步呀!

相关推荐
椰子今天很可爱2 小时前
线程分离和线程同步互斥
linux·c++
cici158742 小时前
基于K-SVD的稀疏编码去噪算法
算法
电力程序小学童2 小时前
基于密集型复杂城市场景下求解无人机三维路径规划的Q-learning算法研究(matlab)
算法·matlab·无人机
小柯J桑_2 小时前
Linux:线程控制
linux·c++·算法
1白天的黑夜12 小时前
栈-1047.删除字符串中的所有相邻重复项-力扣(LeetCode)
c++·leetcode·
im_AMBER2 小时前
Leetcode 18 java
java·算法·leetcode
wzx_Eleven2 小时前
【论文阅读】Towards Privacy-Enhanced and Robust Clustered Federated Learning
论文阅读·人工智能·算法·机器学习·支持向量机·网络安全
旭意3 小时前
C++微基础蓝桥杯之旅9.9-9.12
c++·算法·蓝桥杯
地平线开发者3 小时前
工具链部署实用技巧 7|模型设计帧率推理时耗时与带宽分析
算法·自动驾驶