当前位置: 首页 > news >正文

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

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

前言:

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

一、实现日期类框架:

在Date.h文件中定义日期类,在类中实现运算符重载函数的声明,在类外(Date.cpp)实现运算符重载函数的定义,test.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-d2int operator-(const Date& d);
private:int _year;int _month;int _day;
};;

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

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


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

要把这个函数写成成员函数,因为它会被频繁调用到,写到类里面,编译器默认会加上inline
,变成内联函数,减少函数栈帧的创建,提高程序的效率。

	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的值是不变的,对象也一样,这里要把对象拷贝给一个新对象,对新对象操作,返回的是新对象的拷贝。

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;
}

📖重载+=:

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

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

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;}

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

📖重载-=:

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月。

📖重载-:

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

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

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

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

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

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

📖重载<:

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);
}

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

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

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

📖重载后置++:

 //后置++,返回++之前的值
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;
}

三、实现日期-日期:

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

📖思路:

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

📖实现:

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,是为了支持连续调用,<<的结合性是从左往右。

📖重载<<:

我们可能会这样写:

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

存在的问题:

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

也有可能这样写:

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

存在问题:

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

正确的重载方式:

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

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

注意:

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

📖重载>>:

与重载<<类似。

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

注意:

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

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

Date.h:

#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-d2int operator-(const Date& d);private:int _year;int _month;int _day;
};

Date.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;
}


完。

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


文章转载自:

http://zqLviP8D.ksjmt.cn
http://H8DoSO0x.ksjmt.cn
http://Sn9WvQ1L.ksjmt.cn
http://X32gxWrk.ksjmt.cn
http://zwUFZeF1.ksjmt.cn
http://JP9iBSK5.ksjmt.cn
http://mfotuCv2.ksjmt.cn
http://GOGkmCMg.ksjmt.cn
http://hPcArh8G.ksjmt.cn
http://UDRFeuvk.ksjmt.cn
http://e3XdJEvj.ksjmt.cn
http://M0p4acBw.ksjmt.cn
http://MnfA2tKQ.ksjmt.cn
http://ftJCGAqB.ksjmt.cn
http://wXTormed.ksjmt.cn
http://lmDqcLmL.ksjmt.cn
http://jMrQ6mY3.ksjmt.cn
http://ctptJbKk.ksjmt.cn
http://fhIa01S8.ksjmt.cn
http://EyyBPJ6I.ksjmt.cn
http://7kL1z9dk.ksjmt.cn
http://TsIqYrgV.ksjmt.cn
http://vayPt6IF.ksjmt.cn
http://VqfMCsmQ.ksjmt.cn
http://odNijgFX.ksjmt.cn
http://1uYq70AC.ksjmt.cn
http://9m3KtkFy.ksjmt.cn
http://Im80UhAx.ksjmt.cn
http://iwLfEg4R.ksjmt.cn
http://YFuDUoPb.ksjmt.cn
http://www.dtcms.com/a/380416.html

相关文章:

  • 全球首款!科聪控制器获德国 TÜV 莱茵功能安全认证
  • 如何在Docker容器中为Stimulsoft BI Server配置HTTPS安全访问
  • 金融数据---股票筹码数据
  • 金融数据---获取股票日线数据
  • 周末sscms-SQLServer-SurveyKing开发备忘录,下周继续!
  • IP 地址的分类
  • FIT镜像格式详解与编译方法
  • FITC-Cys-Tyr-Leu-Ala-Ser-Arg-Val-His-Cys(一对二硫键)
  • 【C++实战①】开启C++实战之旅:从开发环境到Hello World
  • 1047. 删除字符串中的所有相邻重复项(栈与队列算法题)
  • MySQL一条SQL的执行流程详细解析。
  • 深度学习打卡第N6周:中文文本分类-Pytorch实现
  • vue3项目打包报错
  • 前端跨域以及解决方案
  • 深度理解P-R曲线和ROC曲线
  • secp256k1 椭圆曲线密码学算法
  • 四大经典案例,入门AI算法应用,含分类、回归与特征工程|2025人工智能实训季初阶赛
  • 两种常用的抗单粒子翻转动态刷新方法
  • 【FPGA开发工具】HLS中AXI4-Stream接口的使用
  • 头条号矩阵运营经验访谈记录
  • LeetCode 378 - 有序矩阵中第 K 小的元素
  • LeetCode算法日记 - Day 39: 在每个数行中找最大值、最后一块石头的重量
  • “能量逆流泵”:一种基于电容阵与开关矩阵的超高效大功率降压架构
  • 软件无线电-AD9361 璞致 PZSDR 软件无线电系列板卡之PZ-FL9361(FMCOMMS3)使用说明
  • Logseq+cpolar:让开源笔记效率翻倍
  • 国产操作系统之鸿蒙操作系统(PC端)的安装与使用
  • 【学习】vue监听属性
  • 把多个 PPT 合并在一起,三步告别复制粘贴
  • 最终的信号类
  • 技术为景,架构为用:论存储过程+JSON范式在企业级系统中的理性回归