C++运算符重载

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​,这样子才能在类的外面访问到内部成员

image

如何解决这个问题呢?直接写在类里面是简单的方法,或者用到后面学习的友元

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天后是什么日期

image

那肯定很简单啊,用口算都可以算出来了,那如果是2024/10/1+100天呢?

image

为了验证手算是否正确,可以通过网上的日期计算器日期计算器验证一下

image

根据上面手算的文字提示,我们可以出一个规律,只要当前天数还大于当前月的天数,那就是不合理的,要减去当月的最大天数,直到剩下的天数小于当月的最大天数

为了更方便计算每一个月的天数,可以用一个数组来存储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

同样,-=和-​我们先实现-=​然后再复用到-

这里我们又要重新思考一下,一个日期减去多少天,是怎么样计算的

image

逐渐相减,通过余数判断是否还大于当月天数,大于就继续减去。最后减去剩余的天数

-=
// 日期-=天数
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;
}

image

得出是1595,通过网上的日期计算器来验证一下

image

即是是两个日期反过来相减,也是image

至此,我们已经重载了Date​类内部需要用到的运算符。

重载流插入和流输出

流输出

cpp的输出语句是cout​,输入是cin​,可以输出输出内置类型

cout<<sizeof(int)<<endl;

cin>>a;

如果我想要输出d1​和d2​这两个类呢?用cout​输出时,会标红报错,说明是不可以的

image

image

根据上面我们学到的,这两个也是可以重载的。使用这两个函数必须要带上<<​(流输出)和>>​(流插入),因此我们重载的是这两个双操作数,而不是这两个函数。

image

如何确定这两个函数的返回值和参数类型呢?参考资料 - 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​ 使得运算符重载与标准输入输出的操作一致,用户可以直观地使用这些运算符。
  • 类型安全和明确性: 通过传入流对象,保证了输入输出的类型安全,避免了潜在的错误。

如果实在不明白,可以参考下面这张图

image​​​

那么这两个重载是写在全局还是类内部的呢?我们可以尝试一下利用上面的Date​类。先写在类内部

ostream& Date::operator<<(ostream& out) {
	out << _year << "-" << _month << "-" << _day;
	return out;
}

调试输出一下

cout << d2;
d2 << cout;

此时会发现cout<<d2​是会报错image

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

所以说要声明两次

image

最终输出结果

流插入

重载思路跟流输出一致,这里就直接给出格式了

istream& operator>>(istream& in, Date& d) {
	in >> d._year >> d._month >> d._day;
	return in;
}

image

测试结果

优化

在重载+和+=的时候,为什么要先重载+=,在复用+=来实现+呢?

我们看下面先实现+,在复用+实现+=

//先实现+,再复用+实现+=
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;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

吃椰子不吐壳

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值