【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;
}
注意:
in
和 d
这两个形参都不能使用 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;
}
完。
今天的分享就到这里,感谢各位大佬的关注,我会继续努力,写出更加通俗易懂的文章,大家互相学习,共同进步呀!