【c++】类和对象(4)

目录

[一 回顾与引入](#一 回顾与引入)

[二 实现-和-=运算符重载](#二 实现-和-=运算符重载)

[三 实现前置--和后置--的运算符重载](#三 实现前置--和后置--的运算符重载)

[四 日期比较大小](#四 日期比较大小)

[五 实现日期减日期](#五 实现日期减日期)

[六 流插入操作符的运算符重载](#六 流插入操作符的运算符重载)

[七 流提取操作符夫人运算符重载](#七 流提取操作符夫人运算符重载)

[八 完整代码](#八 完整代码)

Date.h

Date.cpp

Test.cpp


往期回顾:

博主主页有收录之前写的C++专栏,感兴趣的uu可以看看

一 回顾与引入

我们在上一篇中学到了运算符重载,当运算符被用于类类型的对象时,C++语言允许我们通过运算符重载的形式指定新的含义。C++规 定类类型对象使用运算符时,必须转换成调用对应运算符重载,若没有对应的运算符重载,则会编译报错。

在上一篇最后我们学习了用日期类实现 += 和 **+**的运算符重载,以下是回顾。

cpp 复制代码
int GetMonthDay(int year, int month)
	{
		assert(month > 0 && month < 13);
 
		static int monthDayArray[13] = { -1, 31, 28, 31, 30, 31, 30,
	   31, 31, 30, 31, 30, 31 };
		// 365天 5h + 
		if (month == 2 && (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0))
		{
			return 29;
		}
		else
		{
			return monthDayArray[month];
		}
	}
Date& Date::operator+=(int day)
{
	_day += day;
	while (_day > GetMonthDay(_year, _month))
	{
		_day -= GetMonthDay(_year, _month);
		++_month;
		if (_month == 13)
		{
			++_year;
			_month = 1;
		}
	}
 
	return*this;
}
 
//d1 + 100
Date& Date::operator+(int day)
{
	Date tmp(*this);
	tmp += tmp;
 
	return tmp;
}

那么我们可以通过上述的实现,引入**-=** 和**-**的运算符重载

二 实现-和-=运算符重载

-和-= 的区别是:-不能改变自己,而-= 是改变自己

注意:在减去一个日期的时候,如果当前月的日期不够,则需要向上一个月借日期。例如上述的例子:8-50肯定不够,那我们就需要向上一个月借一个月的日期,上一个月是八月,那就是31天。这里有一个易错点:要减去的是上一个月的日期而不是这个月的日期!!!!

下面实现:

Date.cpp

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

	_day -= day;
	while (_day <= 0)
	{
		--_month;
		if (_month == 0)
		{
			--_year;
			_month = 12;
		}

		_day += GetMonthDay(_year, _month);
	}

	return *this;
}


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

	return tmp;
}

Test.cpp:

cpp 复制代码
void TestDate1()
{
	Date d1(2025, 9, 8);
	d1 -= 50;
	d1.Print();

	Date d2 = d1 - 50;
	d2.Print();

	Date d3(2025, 9, 8);
	d3 -= 2000;
	d3.Print();

	Date d4(2025, 9, 8);
	d4 -= -50;
	d4.Print();
}

注意:

1:在-=的运算符重载函数中:第一个while循环的条件为_day <= 0,循环的条件是终止循环的条件,相当于当_day > 0时,跳出循环

2 :在**-**的运算符重载里,没有使用传引用返回。因为使用了临时变量tmp

3:在传参时,参数是int类型,就意味着可以传正数,也可以传负数。需要判断传入的day是否为正。同理可得,在+=中,也需要判断day是否为正。

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

这段代码的意思是:如果day <0,减去了一个负数,就相当于加一个正数(ps:day前是负号)

所以,+=的运算符重载修改为:

cpp 复制代码
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;
}

三 实现前置--和后置--的运算符重载

在定义前置--和后置--的运算符重载时,我们发现都是 operator--(),如果这样写的话就无法区分了。所以祖师爷在设定的时候规定后置--要传Int: operator--(int),同理得,前置++和后置++也适用。

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

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

在调用时,优先推荐调用前置,前置的效率较高。后置会有两次拷贝

四 日期比较大小

以下是我们要实现的有关日期比较大小的运算符重载,我们来一个一个实现

cpp 复制代码
bool operator<(const Date& d) const;
 bool operator<=(const Date& d) const;
 bool operator>(const Date& d) const;
 bool operator>=(const Date& d) const;
 bool operator==(const Date& d) const;
 bool operator!=(const Date& d) const;

(1) <

我们只需要列举出所有为真的情况,其余情况全为假

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)
{
  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 复制代码
// d1 <= d2
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 _year == d._year
		&& _month == d._month
		&& _day == d._day;
}

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

如上所示,例如实现 <=的时候,就相当于实现小于或等于,而这两个运算符的重载我们可以直接拿过来复用 。(大于就是小于等于取反)

这样实现的好处:例如我们要给日期类增加时,分,秒 如果不使用复用的话,就要一次性修改六个函数,但是使用了现在的逻辑,就可以只修改 < 和 =的函数,其他的函数都是他们俩的复用,不需要修改。

复用的好处:增加可维护性

五 实现日期减日期

思路1:

将第一个日期和第二个日期都和这一年的一月一号相减,分别得到一个a月b天,转化为具体的天数,分别为x天和y天。将两个日期的年份相减,得到一个z年,所以两个日期相减之后的天数就是:z*365 + 跨越闰年数 + (x-y)

注:闰年是366天

思路2:

比较两个日期的大小,小的日期不断++,直到和大的日期相等,++了多少次,他们就相差了多少天

我们实现思路2:

cpp 复制代码
// d1 - d2  
int Date::operator-(const Date& d)
{
	Date max = *this;
	Date min = d;
	int flag = 1;

	if (*this < d)
	{
		min = *this;
		max = d;
		flag = -1;
	}

	int day = 0;
	while (min != max)
	{
		++min;
		++day;
	}

	return day * flag;
}

我们先假设d1是max,d2是min(this是d1,d是d2),如果为真,则d1-d2一定是一个正数。如果为假,则d1-d2一定是一个负数,此时我们就用flag标记结果是为正还是为负

test.cpp:

cpp 复制代码
void TestDate2()
{
	Date d4(2025, 9, 8);
	Date d5(2025, 10, 1);
	cout << d4 - d5 << endl;
	Date d6(2025, 12, 1);
	cout << d4 - d6 << endl;
	Date d7(2029, 7, 1);
	cout << d4 - d7 << endl;
}

六 流插入操作符的运算符重载

流插入操作符为**<<**

流插入操作符<<的运算符重载常用于自定义对象的输出,使我们可以像输出基本数据类型一样输出自定义对象。

流插入的cout是属于ostream类型的

我们先来写一下看看:

cpp 复制代码
​​void operator<<(ostream& out)
{
 out << _year << "/" <<_month <<"/" << _day << '\n';
}

void TestDate3()
{
  Date d1(2025,9,8);
 //cout << d1
 d1.operator << (cout);
 d1 << cout;
}

​

​

运行结果为:

cpp 复制代码
2025/9/8

这样乍一看没什么问题,但是流插入应该是日期类d1流向控制台cout,但在这里变成了由控制台流向日期类,为什么呢?

我们在前面学习运算符重载的性质的时候学到:

重载运算符函数的参数个数和该运算符作用的运算对象数量⼀样多。⼀元运算符有⼀个参数,⼆元 运算符有两个参数,⼆元运算符的左侧运算对象传给第⼀个参数,右侧运算对象传给第⼆个参数。

也就是d1传给了ostream,cout传给了out

说明流插入不能这么写。写在成员函数里,第一个参数就会被占(成员函数的this指针是隐藏的),cout只能变成右操作数到第二个参数去。虽然这样代码也能跑,但是不符合可读性,而运算符重载就是为了增强可读性,我们期望的是cout占据第一个参数

但是我们可以不放置到成员函数中,当初放置到成员函数中是为了访问私有的成员变量,但是现在成员函数不符合我们的预期,所以把这个函数放置到全局。

cpp 复制代码
void operator<<(ostream& out,comst Date d)
{
  out << d._year << "/" << d._month << "/" << d._day << '\n';
} 

void TestDate3()
{
  Date d1(2025,9,8);
  operator(cout,d1);
  cout << d1;
}

注意:此处的out是cout的别名,也可以用别的替代,但是别名尽量不要用cout,理论上讲形参和实参可以同名,但是这里用cout作为别名可能会和库里的冲突

这样写就可以了,但是放置在全局此时我们就没办法访问私有成员了,怎么办?

我们就用到了**友元函数,**但是友元函数我们下一篇再讲解,先在这里简单提一下。友元函数其实很简单,只需要再类里加上友元函数的声明就可以了

cpp 复制代码
class Date:
{
  //友元函数的声明
  friend void operator<<(ostream& out,ostream& d);
  //..........
}

friend是一个关键字

其他情况:我们在输出的时候有可能会连续的输出,例如:

cpp 复制代码
cout << d1 << d2;

但是程序不允许:

为什么呢:连续的输出是从左到右,就和连续赋值一样,这个调用就会转化成调用流插入函数,第一个cout << d1的返回值继续调用 << d2,所以第一个调用的返回值应该是cout,此时就需要修改函数,使他的返回值是cout

cpp 复制代码
ostream& operator<<(ostream& out, const Date& d)
{
  out << d._year << "/" << d._month << "/" <<d._day << '\n';
  return out;
}

注意:这里改了之后,友元函数声明那里也要修改

七 流提取操作符夫人运算符重载

cin是istream类型的对象

cin和cout一样,也需要占据第一个参数的位置

cpp 复制代码
istream& operator>>(istream& in, Date& d)
{
  in>>d._year >>d._month >> d._day;
}

注意:这里的第二个参数和cout不同,此处没有const修饰。因为这里是将数据读到d中,d需要时可修改的

但是当前程序还存在一些缺陷。我们再定义和实现的时候可能会输入一些非法的日期,而这些日期再进行一些运算就没有意义,所以我们可以写一个日期类的检查,检查一个日期是否合法

cpp 复制代码
bool CheckDate()
	{
		if (_month < 1 || _month > 12)
			return false;

		if (_day < 1 || _day > GetMonthDay(_year, _month))
			return false;

		return true;
	}

这里cin就可以加上检查函数:

cpp 复制代码
istream& operator>>(istream& in, Date& d)
{
	while (1)
	{
		cout << "请依次输入年月日:>";
		in >> d._year >> d._month >> d._day;

		if (d.CheckDate())
		{
			break;
		}
		else
		{
			cout << "输入日期非法,请重新输入" << endl;
		}
	}

	return in;
}

八 完整代码

Date.h

cpp 复制代码
#pragma once
#include<iostream>
#include<assert.h>
using namespace std;

class Date
{
	// 友元函数声明
	friend ostream& operator<<(ostream& out, const Date& d);
	friend istream& operator>>(istream& in, Date& d);
public:
	Date(int year = 1900, int month = 1, int day = 1);
	void Print();

	int GetMonthDay(int year, int month)
	{
		assert(month > 0 && month < 13);

		int monthDayArray[13] = { -1, 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;
		}
		else
		{
			return monthDayArray[month];
		}
	}

	bool CheckDate()
	{
		if (_month < 1 || _month > 12)
			return false;

		if (_day < 1 || _day > GetMonthDay(_year, _month))
			return false;

		return true;
	}

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

	// d1 += 天数
	Date& operator+=(int day);
	Date operator+(int day);

	// d1 -= 天数
	Date& operator-=(int day);
	Date operator-(int day);

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

	// ++d1 -> d1.operator++()
	Date& operator++();
	// d1++ -> d1.operator++(0)
	// 为了区分,构成重载,给后置++,强行增加了一个int形参
	// 这里不需要写形参名,因为接收值是多少不重要,也不需要用
	// 这个参数仅仅是为了跟前置++构成重载区分
	Date operator++(int);

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

	// 	d1.operator<<(cout);
	/*void operator<<(ostream& out)
	{
		out << _year << "/" << _month << "/" << _day << '\n';
	}*/

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

ostream& operator<<(ostream& out, const Date& d);
istream& operator>>(istream& in, Date& d);

Date.cpp

cpp 复制代码
#define _CRT_SECURE_NO_WARNINGS 1

#include"Date.h"

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

	if (!CheckDate())
	{
		cout << "非法日期:>" << *this;
	}
}

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

// d1 += 100
//Date& Date::operator+=(int day)
//{
//	_day += day;
//	while (_day > GetMonthDay(_year, _month))
//	{
//		_day -= GetMonthDay(_year, _month);
//		++_month;
//		if (_month == 13)
//		{
//			++_year;
//			_month = 1;
//		}
//	}
//
//	return *this;
//}
//
//// d1 + 100
//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;
//}

// d1 += 100
// d1 += -100
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;
}

// d1 + 100
Date Date::operator+(int day)
{
	Date tmp(*this);
	tmp += day;

	return tmp;
}

// d1 += 100
//Date& Date::operator+=(int day)
//{
//	*this = *this + day;
//	return *this;
//}
//
//// d1 + 100
//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;
//}

// d1 -= 100  20:05
// d1 -= -100
Date& Date::operator-=(int day)
{
	if (day < 0)
	{
		return *this += -day;
	}

	_day -= day;
	while (_day <= 0)
	{
		--_month;
		if (_month == 0)
		{
			--_year;
			_month = 12;
		}

		_day += GetMonthDay(_year, _month);
	}

	return *this;
}

// d1 - 100
Date Date::operator-(int day)
{
	Date tmp = *this;
	tmp -= day;

	return tmp;
}

// ++d1 -> d1.operator++();
Date& Date::operator++()
{
	*this += 1;
	return *this;
}

// d1++ -> d1.operator++(0);
Date Date::operator++(int)
{
	Date tmp(*this);
	*this += 1;

	return tmp;
}

Date& Date::operator--()
{
	*this -= 1;
	return *this;
}

Date Date::operator--(int)
{
	Date tmp = *this;
	*this -= 1;

	return tmp;
}

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

// d1 <= d2
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 _year == d._year
		&& _month == d._month
		&& _day == d._day;
}

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

// d1 - d2  -> 9:12
int Date::operator-(const Date& d)
{
	Date max = *this;
	Date min = d;
	int flag = 1;

	if (*this < d)
	{
		min = *this;
		max = d;
		flag = -1;
	}

	int day = 0;
	while (min != max)
	{
		++min;
		++day;
	}

	return day * flag;
}

ostream& operator<<(ostream& out, const Date& d)
{
	out << d._year << "/" << d._month << "/" << d._day << '\n';
	return out;
}

istream& operator>>(istream& in, Date& d)
{
	while (1)
	{
		cout << "请依次输入年月日:>";
		in >> d._year >> d._month >> d._day;

		if (d.CheckDate())
		{
			break;
		}
		else
		{
			cout << "输入日期非法,请重新输入" << endl;
		}
	}

	return in;
}

Test.cpp

cpp 复制代码
#define _CRT_SECURE_NO_WARNINGS 1

#include<iostream>
using namespace std;

void TestDate1()
{
	Date d1(2025, 9, 8);
	d1 -= 50;
	d1.Print();

	Date d2 = d1 - 50;
	d2.Print();

	Date d3(2025, 9, 8);
	d3 -= 2000;
	d3.Print();

	Date d4(2025, 9, 8);
	d4 -= -50;
	d4.Print();
}

void TestDate2()
{
	Date d1(2025, 9, 8);

	//Date d2 = d1.operator--();
	Date d2 = --d1; // d1.operator--();
	d1.Print();
	d2.Print();

	//Date d3 = d1.operator--(1);
	Date d3 = d1--; // d1.operator--(1);
	d1.Print();
	d3.Print();

	Date d4(2025, 9, 8);
	Date d5(2025, 10, 1);
	cout << d4 - d5 << endl;
	Date d6(2025, 12, 1);
	cout << d4 - d6 << endl;
	Date d7(2029, 7, 1);
	cout << d4 - d7 << endl;
}

void TestDate3()
{
	Date d1(2025, 9, 8);
	Date d2(2025, 9, 90);

	//operator<<(cout, d1);
	cout << d1 << d2;
	
	// 虽然可以跑,但是不符合可读性
	//d1.operator<<(cout);
	//d1 << cout;

	cin >> d1 >> d2;
	cout << d1 << d2;
}

int main()
{
	TestDate3();

	//int i = 1;
	//double d = 1.1;
	//cout << i; // cout.operator<<(i)
	//cout << d; // cout.operator<<(d)

	return 0;
}
相关推荐
Sally璐璐2 小时前
Go组合式继承:灵活替代方案
开发语言·后端·golang
码熔burning2 小时前
从 new 到 GC:一个Java对象的内存分配之旅
java·开发语言·jvm
晨非辰2 小时前
#C语言——刷题攻略:牛客编程入门训练(十二):攻克 循环控制(四)、循环输出图形(一),轻松拿捏!
c语言·开发语言·经验分享·笔记·其他·学习方法·visual studio
gou123412343 小时前
Go语言io.Copy深度解析:高效数据复制的终极指南
开发语言·golang·php
白玉cfc3 小时前
【OC】单例模式
开发语言·ios·单例模式·objective-c
十六点五3 小时前
Java NIO的底层原理
java·开发语言·python
猿究院-赵晨鹤3 小时前
Java I/O 模型:BIO、NIO 和 AIO
java·开发语言
little_xianzhong3 小时前
步骤流程中日志记录方案(类aop)
java·开发语言
抓饼先生3 小时前
C++ 20 视图view笔记
linux·开发语言·c++·笔记·c++20