C++运算符重载
运算符重载
上一期我们讲解了拷贝函数,这期我们继续深入,来学习什么是运算符重载。为了更好理解运算符重载,用Date
类这个小项目来帮助大家更好理解运算符重载
入门
C++为了增强代码的可读性引入的运算符重载,运算符重载是具有特殊函数名的函数,也具有其返回值类,函数名字以及参数列表,其返回值类型列表与普通的函数类似。
函数名字:关键字operator
后面接需要重载的运算符符号
函数原型:返回值类型 operator操作符(参数列表)
类型 operator操作符(参数列表)
注意:
一、不能通过连接其他符号来创建新的操作符:比如operator@ 或者其他
二、重载操作符必须有一个类型型参数
三、用于内置类型的运算符,其含义不能改变,例如:内置类型+,不能改变其含义(如int a++改变成int a–)
四、作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏的this(类本身)
五、
.* :: sizeof ?: .
注意这五个运算符不能重载。(经常出现在笔试面试题中)
看到这里很多同学可能还是有点懵逼。什么是运算符重载???我们看下面的Date
类
class Date {
public:
Date(int year=1970,int month=1,int day=1) {
_year = year;
_month = month;
_day = day;
}
Date(const Date& d) {
_year = d._year;
_month = d._month;
_day = d._day;
}
//private:
int _year;
int _month;
int _day;
};
//重载==
bool operator==(const Date& x,const Date& y) {
return x._year == y._year
&& x._month == y._month
&& x._day == y._day;
}
写了一个Date
类,有x
和y
两个对象,怎么比较他们俩之间的大小呢?直接用x>y
吗。这个是不可以的,因为Date
类是我们自定义的类型,> < == !=
这些运算符在没有重载前只能用在内置类型。因此我们需要重载这些运算符,符合自定义类型的比较。
在以上代码,重载==
是写在全局的,需要private
开放限制,改成public
,这样子才能在类的外面访问到内部成员
如何解决这个问题呢?直接写在类里面是简单的方法,或者用到后面学习的友元
copy到类里面,又提示错误此运算符的参数太多
。回到前面的注意事项的第四条!第一个参数是隐藏的*this
。需要把const Date& x
删掉,因为是多余的。
//重载==
bool operator==(const Date& d) {
return _year == d._year
&& _month == d._month
&& _day == d._day;
}
确定好是逻辑返回值(True or False
)后,于是最简单的运算符重载operator
便写好了
重载格式
- **参数类型:**
const &T
,传递引用可以提高效率 - **返回值类型:**
T&
,返回引用可以提高返回的效率,有返回值的目的是为了支持连续赋值 - 检测是否自己给自己赋值
- 返回*this: 要符合连续赋值的含义
逻辑运算符重载
上面实现了重载==
这个运算符,接下来我们根据格式来继续实现=
,也很简单,直接来了
=
Date& Date::operator=(const Date& d) {
if (this != &d) {
_year = d._year;
_month = d._month;
_day = d._day;
}
return *this;
}
可以类比int a=10,int b=0,a=b
,如果a
的值和b
的值是一样的,就不需要执行赋值了
这里可能有同学发现了函数名前面怎么会有Date::
?还有为什么用引用返回?
首先,为了更好规范管理项目,我使用了定义和声明分开的写法,在Date.h
里面放函数声明,在Date.cpp
里面放具体的定义,而在test.cpp
里面放测试功能的mian
函数。参考c++类与对象一-优快云博客。这里不过多赘述。
其次,是否使用引用做返回值,看是否改变了自己。例如:
如果你有一个对象
a
,表达式a + b
会返回一个新的对象,a
和b
不会被改变。用于在原有对象的基础上修改自己。使用
a += b
会将b
的值添加到a
中,并改变a
的状态。在作用域中,this对象出了作用域就销毁,返回值是Date时,会调用他的拷贝构造,如果是大对象,会造成性能浪费,为了提高效率,可以用引用返回的方式,并且还可以支持a=b=c=d这种连等的方式,如果说返回值不使用引用,就会调用多次拷贝构造,是很浪费性能的。
因此,在判断是否改变自己时,最好使用引用返回,不改变自己直接使用类型返回
注意事项:
在
Date.h
里面的构造函数声明的缺省值// 全缺省的构造函数 Date(int year = 1900, int month = 1, int day = 1);
是不能和
Date.cpp
的定义一样的// 全缺省的构造函数 Date::Date(int year, int month , int day) { _year = year; _month = month; _day = day; }
否则编译器会分不清使用哪一个。因此根据构造函数的特性c++类与对象三-优快云博客,只需要在头文件里面放缺省值就行了
接下来我们继续实现<
和>=
,返回值依旧是bool
,逻辑就是依次判断年月日。
==
bool Date::operator==(const Date& d) {
return _year == d._year && _month == d._month
&& _day == d._day;
}
>=
bool Date::operator>(const Date& d) {
if (_year > d._year) {
return true;
}
else if (_year == d._year && _month > d._month) {
return true;
}
else if (_year == d._year && _month == d._month && _day > d._day) {
return true;
}
else {
return false;
}
}
根据上面的想法,这里有同学可能会这样写>=
bool Date::operator>=(const Date& d) {
if (_year >= d._year) {
return true;
}
else if (_year == d._year && _month >= d._month) {
return true;
}
else if (_year == d._year && _month == d._month && _day >= d._day) {
return true;
}
else {
return false;
}
}
这样子写是没有问题的,但是这里有更好的方法。换另一个角度想,前面我们先实现了==
和>
,根据逻辑换算(>=就是>或者==),是可以用这两个来实现>=
的,提高代码复用性
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);
}
赋值运算符重载
赋值运算符的重载与逻辑运算符略有差异,逻辑运算符是可以写在全局的(可以用友元来解决私有访问的问题),而赋值运算符无论怎么样都不可以的,我们来看下面的例子
class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
int _year;
int _month;
int _day;
}
Date& operator=(Date& left,const Date& right)
{
if (&left != &right)
{
left._year = right._year;
left._month = right._month;
left._day = right._day;
}
return left;
}
// 编译失败:
// error C2801: “operator =”必须是非静态成员
请大家自行编译一下,高级一点的编译器会提示,operator=
必须是成员函数。
原因:赋值运算符如果不显式实现,编译器会生成一个默认的。此时用户再在类外自己实现
一个全局的赋值运算符重载,就和编译器在类中生成的默认赋值运算符重载冲突了,故赋值
运算符重载只能是类的成员函数
明白了这个规则,我们来实现剩下的运算符+、+=、-、-=
涉及到日期+
xxx,xxx可以是天数,例如2024/10/1+xxx,这个xx可以是任意整数,根据现实来说,1-12月的日期都是固定的,除了2月是不一样的,在平年是28天,闰年是29天。
以上面的日期为例子,我们手算一下2024/10/1+5天后是什么日期
那肯定很简单啊,用口算都可以算出来了,那如果是2024/10/1+100天呢?
为了验证手算是否正确,可以通过网上的日期计算器日期计算器验证一下
根据上面手算的文字提示,我们可以出一个规律,只要当前天数还大于当前月的天数,那就是不合理的,要减去当月的最大天数,直到剩下的天数小于当月的最大天数
为了更方便计算每一个月的天数,可以用一个数组来存储1-12个月的天数,而2月份单独做处理,判断是闰年就返回29天,平年就返回29天
// 获取某年某月的天数
int Date::GetMonthDay(int year, int month) {
assert(year >= 1 && month >= 1 && month <= 12);
//用数组保存平年的月份天数
int day[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };
if (month == 2 && ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)))
return 29;
return day[month];
}
这里问题来了,我们根据上面的思路,== 和!=、>=、<=
是可以通过别的运算符复用的,这里的+、+=
同样也可以复用,但是先写哪一个才能更好的复用另一个呢,这里先不作解释,我们先实现+=
,然后在用+=
来实现=
+=
Date& Date::operator+=(int day) {
if (day < 0) {
return *this += (-day);
}
_day += day;
while (_day > GetMonthDay(_year, _month)) {
//日期减少,月份进位
_day -= GetMonthDay(_year, _month);
++_month;
//月份上限继续进位
if (_month == 13) {
++_year;
_month = 1;
}
}
return *this;
}
+
// 日期+天数
Date Date::operator+(int day) {
Date tmp(*this);
tmp += day;
return tmp;
}
这里的+
是逻辑自洽的,因为+
是生成一个新对象,这里用tmp
拷贝*this
也是生成一个新对象。+=
是改变自己,所以说返回值是*this
同样,-=和-
我们先实现-=
然后再复用到-
这里我们又要重新思考一下,一个日期减去多少天,是怎么样计算的
逐渐相减,通过余数判断是否还大于当月天数,大于就继续减去。最后减去剩余的天数
-=
// 日期-=天数
Date& Date::operator-=(int day) {
if (day < 0) {
return *this += (-day);
}
while (day > _day) {
day -= _day;
if (_month == 1) { // 跨年处理
_month = 12;
_year--;
}
else {
_month--;
}
//结束时更新_day
_day = GetMonthDay(_year, _month);
}
// 剩余天数直接减掉
_day -= day;
return *this;
}
-
Date Date::operator-(int day) {
Date tmp(*this);
tmp -= day;
return tmp;
}
上面这几个的参数都是int day
,是有意义的,那参数换成Date& d
对象呢,日期+日期,这个很奇怪,基本上没有意义,日期-日期可以算出来差值,例如过去了多少天,还有多少天到这个日期。所以说要实现什么运算符,是要根据实际情况来定的,不是所有的都有意义。
日期-日期就需要思考得比较多了
思路一:
2024/10/1-2020/5/20=?
我们可以分布计算,先算年:2024-2020,这里经过了一个完整的三年,23年、22年、21年。判断闰年还是平年后,可以直接算出这三年的总天数,这是第二个部分,然后在计算2024年过了多少天,先计算1月份到9月份的总天数,然后加上剩下10月份的天数。这个是第一部分。到第三部分,2020/5/20到2021年一共有多少天,因为过去的年份是递增的(这里逻辑要捋顺),由于我们实现了GetMonthDay
这个函数,我们可以复用。
int Date::operator-(const Date& d) {
Date D1(*this);
Date D2(d);
int Sum = 0;
int Year_Day = 0;
int Befor_sum = 0;
int Now_sum = 0;
if (D1 > D2) {
//把所有月份拆出来变成天数加起
//2024/10/1-2020/5/20
//拿到今年总天数
Now_sum = GetNowAllDay(D1._year, D1._month, D1._day);
//年份有差值且大于0,就有完整的一年,判断闰年还是平年,平年365,闰年366
//额外-1-1是因为开始和截止时间未满一年
int Dif_year = D1._year - D2._year - 1;
if (Dif_year > 0) {
//有多少个完整的一年就加多少次
for (int i = D1._year - Dif_year; i < D1._year; i++) {
//完整年的总天数
Year_Day += GetNowAllDay(i, 12, 31);
}
}
//加上截止年份的总天数
Befor_sum = GetBeforDay(D2._year, D2._month, D2._day);
Sum = Now_sum + Year_Day + Befor_sum;
return Sum;
}
else if (D1 < D2) {
//拿到今年总天数
Now_sum = GetNowAllDay(D2._year, D2._month, D2._day);
//年份有差值且大于0,就有完整的一年,判断闰年还是平年,平年365,闰年366
//额外-1-1是因为开始和截止时间未满一年
int Dif_year = D2._year - D1._year - 1;
if (Dif_year > 0) {
//有多少个完整的一年就加多少次
for (int i = D2._year - Dif_year; i < D2._year; i++) {
//完整年的总天数
Year_Day += GetNowAllDay(i, 12, 31);
}
}
//加上截止年份的总天数
Befor_sum = GetBeforDay(D1._year, D1._month, D1._day);
Sum = Now_sum + Year_Day + Befor_sum;
return -Sum;
}
else if (D1 == D2) {
return 0;
}
}
//获取本年所有天数
int Date::GetNowAllDay(int year, int month, int day) {
int sum = 0;
//这个是起始日期
for (int i = 1; i <= month - 1; i++) {
//当前月份不计入,因为可能当月未满,只需计算前面的
sum += GetMonthDay(year, i);
}
//最后加上剩余的天数
sum += day;
return sum;
}
//获取过去年份的总日期
int Date::GetBeforDay(int year, int month, int day) {
int sum = 0;
//逐渐递增,因为过去的年份是往后数
for (int i = month + 1; i <= 12; i++) {
sum += GetMonthDay(year, i);
}
sum += (GetMonthDay(year, month) - day);
return sum;
}
上面的思路不复杂,但是实现起来很消耗资源,有没有更快速的方法呢?
思路二:
既然要计算这两个日期的差值,那么就会有最大日期和最小日期之分,让最小的日期不断按天数递增到跟最大的日期一样,用一个值记录递增的次数,那就是两个日期之间的差值了。
既然要日期递增,前面我们实现了+和+=
,可以写成最小日期+=1
。这个不就是前置++
和后置++
了吗。顺着这个思路,我们还可以实现前置--
和后置--
学过c语言的都知道,前置++和后置++是两回事,前置++是先++,在返回,后置++是先返回在++。
// 前置++
//返回++后的值
Date& Date::operator++() {
*this += 1;
return *this;
}
// 后置++
//先返回原来的值,执行完后已经变成+1
//为了区分前置和后置,用int参数构成重载
Date Date::operator++(int) {
Date tmp(*this);
*this += 1;
return tmp;
}
前置++就是直接返回自己。后置++就需要一个tmp对象来保存当前值,然后返回当前值,最后在++。问题来了,怎么区分前置++和后置++呢?而且这两个函数的名字都是一样的Date Date::operator++
,在c++中,为了区分前置++和后置++,用int作参数构成重载(函数重载的概念c++基础入门二_c++创建函数-优快云博客)
前置–和后置–也是同样的规则
// 前置--
Date& Date::operator--() {
*this -= 1;
return *this;
}
// 后置--
//这里不能用引用是因为tmp出作用域就清除了,因此需要返回一个对象
Date Date::operator--(int) {
Date tmp(*this);
*this -= 1;
return tmp;
}
实现了前置++和后置++,就可以让自定义的日期类型自增了,接下来先假设this是最大的,Date& d是最小的。如果不正确,那就重新调整,并且用一个flag来标记是正数还是负数,用n来记录循环了多少次,当最小的日期和最大的日期相等时,n就是这两个日期的差值。
//日期-天数
//考虑到大日期-小日期是正值,小日期-大日期是负值
int Date::operator-(const Date& d) {
int flag = 1;
int n = 0;
//假设左大右小
Date max = *this;
Date min = d;
//假设错了就纠正,flag置为负值
if (*this < d) {
max = d;
min = *this;
flag = -1;
}
//让两个日期趋于相等
while (min != max) {
min++;
n++;
}
return n * flag;
}
让我们来测试一下是否正确
int main() {
/*Date d2(2024,10,1);
Date d1(2020, 5, 20);*/
/*Date d2(2024, 10, 1);
Date d1(2024, 10, 1);*/
Date d1(2024, 10, 1);
Date d2(2020, 5, 20);
//Date d2;
//Date d3;
////=测试
//d2 = d1;
//d2.Print();
////+=测试
//d2 += 45;
//d2.Print();
////+测试
//d3 = d2;
//d3 + 45;
//d3.Print();
/*d1-=200;
d1.Print();*/
/*Date d3=d1--;
Date d4=--d2;
d3.Print();
d4.Print();*/
/*cout << (d2 != d1) << endl;*/
cout << (d1 - d2) << endl;
return 0;
}
得出是1595,通过网上的日期计算器来验证一下
即是是两个日期反过来相减,也是
至此,我们已经重载了Date
类内部需要用到的运算符。
重载流插入和流输出
流输出
cpp的输出语句是cout
,输入是cin
,可以输出输出内置类型
cout<<sizeof(int)<<endl;
cin>>a;
如果我想要输出d1
和d2
这两个类呢?用cout
输出时,会标红报错,说明是不可以的
根据上面我们学到的,这两个也是可以重载的。使用这两个函数必须要带上<<
(流输出)和>>
(流插入),因此我们重载的是这两个双操作数,而不是这两个函数。
如何确定这两个函数的返回值和参数类型呢?参考资料 - C++ 参考资料可以查看这个网站,cin
是属于istream
这个类里面的,cout
是属于ostream
的。因此返回值分别是这两个类类型,参数就是我们需要传入的值,很抽象,实际上理解起来很简单。
理解为什么重载运算符时使用 ostream
和 istream
作为参数,关键在于这两个类的设计和目的。
首先ostream
作为参数
- 输出流:
ostream
是用于输出的流类,代表了可以写入数据的目的地(如控制台或文件)。 - 链式调用: 使用
ostream
作为参数允许我们实现链式调用,例如std::cout << obj1 << obj2;
。这是因为每次调用operator<<
后返回的是同一个ostream
对象的引用,使得可以连续进行输出。 - 灵活性: 可以通过传入不同的输出流(例如
ofstream
)来实现输出到不同的地方,增强了代码的灵活性。
istream
作为参数
- 输入流:
istream
是用于输入的流类,代表了可以读取数据的来源(如键盘输入或文件)。 - 数据解析: 重载
>>
运算符时,使用istream
使得我们可以从流中读取各种格式的数据并将其存入对象中。 - 同样的链式调用: 也可以支持类似于
std::cin >> obj1 >> obj2;
的链式输入。
总结
- 操作的统一性: 使用
ostream
和istream
使得运算符重载与标准输入输出的操作一致,用户可以直观地使用这些运算符。 - 类型安全和明确性: 通过传入流对象,保证了输入输出的类型安全,避免了潜在的错误。
如果实在不明白,可以参考下面这张图
那么这两个重载是写在全局还是类内部的呢?我们可以尝试一下利用上面的Date
类。先写在类内部
ostream& Date::operator<<(ostream& out) {
out << _year << "-" << _month << "-" << _day;
return out;
}
调试输出一下
cout << d2;
d2 << cout;
此时会发现cout<<d2
是会报错
而d2<<cout
是正常输出的,原因是<<
是双操作符,必须要有一个左操作数和右操作数,同理映射到参数也就必须要有左参数和右参数,在类内部实现的函数,*this会默认占据第一个左参数(是隐式的,不能显现),此时ostream& out
就排到了右操作数,而类调用内部函数,是这样子调用的:d2.operator<<(cout)-->d2.operator(&d2,cout)-->d2<<cout
,所以说想要cout在前面,对象在后面,就需要修改传入参数的位置。也就是修改&d2
this指针的位置,ostream& out
是要在左边,右边的参数就是Date& d
,而且为了能访问类内部成员,必须用friend
修饰,所以说声明必须要在类内部,而不能直接放到全局,因为friend
是必须要在类内部修饰的。又不能是类内部的函数,声明又需要在全局,可以多次声明
//Date.h
class Date
{
public:
// 获取某年某月的天数
int GetMonthDay(int year, int month);
// 全缺省的构造函数
Date(int year = 1900, int month = 1, int day = 1);
// 拷贝构造函数
Date(const Date& d);
// 赋值运算符重载
// d2 = d3 -> d2.operator=(&d2, d3)
//d2调用operator方法,this指向d2对象
Date& operator=(const Date& d);
void Print();
Date& operator+=(int day);
// 日期+天数
Date operator+(int day);
Date operator-(int day);
// 日期-=天数
Date& operator-=(int day);
// 前置++
//返回++后的值
Date& operator++();
// 后置++
//先返回原来的值,执行完后已经变成+1
Date operator++(int);
// 后置--
Date operator--(int);
// 前置--
Date& operator--();
// >运算符重载
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);
int operator-(const Date& d);
////获取本年所有天数
//int GetNowAllDay(int year, int month, int day);
//int GetBeforDay(int year, int month, int day);
//为了访问类内部,用friend修饰
friend ostream& operator<<(ostream& out, Date& d);
friend istream& operator>>(istream& in, Date& d);
private:
int _year;
int _month;
int _day;
};
//声明在全局
ostream& operator<<(ostream& out, Date& d);
istream& operator>>(istream& in, Date& d);
所以说要声明两次
最终输出结果
流插入
重载思路跟流输出一致,这里就直接给出格式了
istream& operator>>(istream& in, Date& d) {
in >> d._year >> d._month >> d._day;
return in;
}
测试结果
优化
在重载+和+=的时候,为什么要先重载+=,在复用+=来实现+呢?
我们看下面先实现+,在复用+实现+=
//先实现+,再复用+实现+=
Date Date::operator+(int day) {
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) {
tmp._year++;
tmp._month = 1;
}
}
return tmp;
}
//拷贝0次
Date& Date::operator+=(int day) {
*this = *this + day;
return *this;
}
d1+100:需要拷贝两次
d1+=100:需要拷贝三次(*this + day
调用 operator+
,创建一个新的 Date
对象(拷贝1)在 operator+=
中,*this = ...
会涉及将临时对象赋值给 *this
,这会引起另一个拷贝(拷贝2))
一共拷贝5次
先实现+=,在复用+=实现+
Date& Date::operator+=(int day) {
if (day < 0) {
return *this += (-day);
}
_day += day;
while (_day > GetMonthDay(_year, _month)) {
//日期减少,月份进位
_day -= GetMonthDay(_year, _month);
++_month;
//月份上限继续进位
if (_month == 13) {
++_year;
_month = 1;
}
}
return *this;
}// 日期+天数
Date Date::operator+(int day) {
Date tmp(*this);
tmp += day;
return tmp;
}
d1+100:需要拷贝两次
d1+=100:需要拷贝0次
一共拷贝2次
因此先重载哪一个,是有根据的
源码
//Date.h
#pragma once
#include<iostream>
#include<assert.h>
using namespace std;
class Date
{
public:
// 获取某年某月的天数
int GetMonthDay(int year, int month);
// 全缺省的构造函数
Date(int year = 1900, int month = 1, int day = 1);
// 拷贝构造函数
Date(const Date& d);
// 赋值运算符重载
// d2 = d3 -> d2.operator=(&d2, d3)
//d2调用operator方法,this指向d2对象
Date& operator=(const Date& d);
void Print();
Date& operator+=(int day);
// 日期+天数
Date operator+(int day);
Date operator-(int day);
// 日期-=天数
Date& operator-=(int day);
// 前置++
//返回++后的值
Date& operator++();
// 后置++
//先返回原来的值,执行完后已经变成+1
Date operator++(int);
// 后置--
Date operator--(int);
// 前置--
Date& operator--();
// >运算符重载
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);
int operator-(const Date& d);
////获取本年所有天数
//int GetNowAllDay(int year, int month, int day);
//int GetBeforDay(int year, int month, int day);
friend ostream& operator<<(ostream& out, Date& d);
friend istream& operator>>(istream& in, Date& d);
private:
int _year;
int _month;
int _day;
};
ostream& operator<<(ostream& out, Date& d);
istream& operator>>(istream& in, Date& d);
//Date.cpp
#include "Date.h"
// 获取某年某月的天数
int Date::GetMonthDay(int year, int month) {
assert(year >= 1 && month >= 1 && month <= 12);
//用数组保存平年的月份天数
int day[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };
if (month == 2 && ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)))
return 29;
return day[month];
}
// 全缺省的构造函数
Date::Date(int year, int month , int day) {
_year = year;
_month = month;
_day = day;
}
// 拷贝构造函数
Date::Date(const Date& d) {
_year = d._year;
_month = d._month;
_day = d._day;
}
// 赋值运算符重载
// d2 = d3 -> d2.operator=(&d2, d3)
//d2调用operator方法,this指向d2对象
Date& Date::operator=(const Date& d) {
if (this != &d) {
_year = d._year;
_month = d._month;
_day = d._day;
}
return *this;
}
//// 析构函数
//~Date();
// 日期+=天数
//d1+100天
//2024 10 11+100day
//超过31/30天进位
//+不改变直接,+=改变自己,+是有返回值的
//Date& Date::operator+=(int day) {
// if (day < 0) {
// return *this += (-day);
// }
//
// _day += day;
// while (_day > GetMonthDay(_year, _month)) {
// //日期减少,月份进位
// _day -= GetMonthDay(_year, _month);
// ++_month;
//
// //月份上限继续进位
// if (_month == 13) {
// ++_year;
// _month = 1;
// }
// }
// return *this;
//}
void Date::Print() {
cout << _year << "-" << _month << "-" << _day << endl;
}
//// 日期+天数
//Date Date::operator+(int day) {
// Date tmp(*this);
// tmp += day;
// return tmp;
//}
//没优化版本
//// 日期-天数
//Date operator-(int day) {
// while (day > GetMonthDay(_year,_month)) {
// day -= _day;
// if (_month >= 1) {
// _month--;
// _day = GetMonthDay(_year, _month);
// }
// else if (_month < 0) {
// _month = 12;
// _year--;
// _day = 31;
// }
// }
// //剩余的就减掉
// _day -= day;
// return *this;
//}
//先实现+,再复用+实现+=
Date Date::operator+(int day) {
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) {
tmp._year++;
tmp._month = 1;
}
}
return tmp;
}
Date& Date::operator+=(int day) {
*this = *this + day;
return *this;
}
Date Date::operator-(int day) {
Date tmp(*this);
tmp -= day;
return tmp;
}
// 日期-=天数
Date& Date::operator-=(int day) {
if (day < 0) {
return *this += (-day);
}
while (day > _day) {
day -= _day;
if (_month == 1) { // 跨年处理
_month = 12;
_year--;
}
else {
_month--;
}
//结束时更新_day
_day = GetMonthDay(_year, _month);
}
// 剩余天数直接减掉
_day -= day;
return *this;
}
// 前置++
//返回++后的值
Date& Date::operator++() {
*this += 1;
return *this;
}
// 后置++
//先返回原来的值,执行完后已经变成+1
//为了区分前置和后置,用int参数构成重载
Date Date::operator++(int) {
Date tmp(*this);
*this += 1;
return tmp;
}
// 后置--
//这里不能用引用是因为tmp出作用域就清除了,因此需要返回一个对象
Date Date::operator--(int) {
Date tmp(*this);
*this -= 1;
return tmp;
}
// 前置--
Date& Date::operator--() {
*this -= 1;
return *this;
}
//// >运算符重载
//bool Date::operator>(const Date& d) {
// if (_year > d._year) {
// return true;
// }
// else if (_year == d._year && _month > d._month) {
// return true;
// }
// else if (_year == d._year && _month == d._month && _day > d._day) {
// return true;
// }
// else {
// return false;
// }
//}
//复用
bool Date::operator>(const Date& d) {
if (_year > d._year) {
return true;
}
else if (_year == d._year && _month > d._month) {
return true;
}
else if (_year == d._year && _month == d._month && _day > d._day) {
return true;
}
else {
return false;
}
}
// >=运算符重载
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 _year == d._year && _month == d._month
&& _day == d._day;
}
// <=运算符重载
bool Date::operator <= (const Date& d) {
return !(*this > d);
}
// !=运算符重载
bool Date::operator != (const Date& d) {
return !(*this == d);
}
//int Date::operator-(const Date& d) {
// Date D1(*this);
// Date D2(d);
// int Sum = 0;
// int Year_Day = 0;
// int Befor_sum = 0;
// int Now_sum = 0;
// if (D1 > D2) {
//
// //把所有月份拆出来变成天数加起
// //2024/10/1-2020/5/20
// //拿到今年总天数
// Now_sum = GetNowAllDay(D1._year, D1._month, D1._day);
// //年份有差值且大于0,就有完整的一年,判断闰年还是平年,平年365,闰年366
// //额外-1-1是因为开始和截止时间未满一年
// int Dif_year = D1._year - D2._year - 1;
// if (Dif_year > 0) {
// //有多少个完整的一年就加多少次
// for (int i = D1._year - Dif_year; i < D1._year; i++) {
// //完整年的总天数
// Year_Day += GetNowAllDay(i, 12, 31);
// }
// }
// //加上截止年份的总天数
// Befor_sum = GetBeforDay(D2._year, D2._month, D2._day);
// Sum = Now_sum + Year_Day + Befor_sum;
// return Sum;
// }
// else if (D1 < D2) {
// //拿到今年总天数
// Now_sum = GetNowAllDay(D2._year, D2._month, D2._day);
// //年份有差值且大于0,就有完整的一年,判断闰年还是平年,平年365,闰年366
// //额外-1-1是因为开始和截止时间未满一年
// int Dif_year = D2._year - D1._year - 1;
// if (Dif_year > 0) {
// //有多少个完整的一年就加多少次
// for (int i = D2._year - Dif_year; i < D2._year; i++) {
// //完整年的总天数
// Year_Day += GetNowAllDay(i, 12, 31);
// }
// }
// //加上截止年份的总天数
// Befor_sum = GetBeforDay(D1._year, D1._month, D1._day);
// Sum = Now_sum + Year_Day + Befor_sum;
// return -Sum;
// }
// else if (D1 == D2) {
// return 0;
// }
//}
//
//
////获取本年所有天数
//int Date::GetNowAllDay(int year, int month, int day) {
// int sum = 0;
// //这个是起始日期
// for (int i = 1; i <= month - 1; i++) {
// //当前月份不计入,因为可能当月未满,只需计算前面的
// sum += GetMonthDay(year, i);
// }
// //最后加上剩余的天数
// sum += day;
// return sum;
//}
//
//int Date::GetBeforDay(int year, int month, int day) {
// int sum = 0;
// //逐渐递增,因为过去的年份是往后数
// for (int i = month + 1; i <= 12; i++) {
// sum += GetMonthDay(year, i);
// }
// sum += (GetMonthDay(year, month) - day);
// return sum;
//}
//日期-天数
//考虑到大日期-小日期是正值,小日期-大日期是负值
int Date::operator-(const Date& d) {
int flag = 1;
int n = 0;
//假设左大右小
Date max = *this;
Date min = d;
//假设错了就纠正,flag置为负值
if (*this < d) {
max = d;
min = *this;
flag = -1;
}
//让两个日期趋于相等
while (min != max) {
min++;
n++;
}
return n * flag;
}
ostream& operator<<(ostream& out, Date& d) {
out << d._year << "年" << d._month << "月" << d._day << "日" << endl;
return out;
}
istream& operator>>(istream& in, Date& d) {
in >> d._year >> d._month >> d._day;
return in;
}
//test.cpp
#include "Date.h"
int main() {
/*Date d2(2024,10,1);
Date d1(2020, 5, 20);*/
/*Date d2(2024, 10, 1);
Date d1(2024, 10, 1);*/
//Date d2;
//Date d3;
////=测试
//d2 = d1;
//d2.Print();
////+=测试
//d2 += 45;
//d2.Print();
////+测试
//d3 = d2;
//d3 + 45;
//d3.Print();
/*d1-=200;
d1.Print();*/
/*Date d3=d1--;
Date d4=--d2;
d3.Print();
d4.Print();*/
/*cout << (d2 != d1) << endl;*/
/*cout << (d2 - d1) << endl;*/
Date d1(2024, 10, 1);
Date d2(2020, 5, 20);
/*cout <<(d1)<< endl;
cout << d1;
cout << d2;*/
/*cout << d2;
d2 << cout;*/
/*d2.operator<<(cout);*/
/*cout << d2 << d1;*/
Date d3;
Date d4;
cin >> d3 >> d4;
cout << d3 << d4;
return 0;
}