吃透C++类和对象(中):详解 Date 类的设计与实现

🔥小叶-duck个人主页

❄️个人专栏《Data-Structure-Learning》

《C++入门到进阶&自我学习过程记录》

未择之路,不须回头
已择之路,纵是荆棘遍野,亦作花海遨游


目录

一、Date类+=和+的运算符重载实现与复用

1、构造函数初始化对象

2、+=的运算符重载实现

[2.1 获取月份天数GetMonthDay的实现](#2.1 获取月份天数GetMonthDay的实现)

[2.2 +=的实现与测试](#2.2 +=的实现与测试)

3、+的运算符重载实现

[3.1 +的实现与测试](#3.1 +的实现与测试)

[3.2 复用+=的逻辑实现+](#3.2 复用+=的逻辑实现+)

[3.3 复用+的逻辑实现+=](#3.3 复用+的逻辑实现+=)

二、Date类-=和-的运算符重载实现与复用

1、-=的运算符重载实现

2、-的运算符重载实现

[2.1 -的实现与测试](#2.1 -的实现与测试)

[2.2 复用-=的逻辑实现-](#2.2 复用-=的逻辑实现-)

2.3复用-的逻辑实现-=

三、Date类比较符号的运算符重载实现

[1、< 的运算符重载实现](#1、< 的运算符重载实现)

[2、= 的运算符重载实现](#2、= 的运算符重载实现)

[3、<= 的运算符重载实现](#3、<= 的运算符重载实现)

[4、> 的运算符重载实现](#4、> 的运算符重载实现)

[5、>= 的运算符重载实现](#5、>= 的运算符重载实现)

[6、!= 的运算符重载实现](#6、!= 的运算符重载实现)

[四、Date类前置 ++/-- 与后置 ++/-- 的运算符重载实现](#四、Date类前置 ++/-- 与后置 ++/-- 的运算符重载实现)

[1、前置 ++ 和后置 ++](#1、前置 ++ 和后置 ++)

[1.1 前置 ++ 和后置 ++ 运算符重载的区别](#1.1 前置 ++ 和后置 ++ 运算符重载的区别)

[1.2 前置 ++](#1.2 前置 ++)

[1.3 后置 ++](#1.3 后置 ++)

[2、前置 -- 和后置 --](#2、前置 -- 和后置 --)

[2.1 前置 --](#2.1 前置 --)

[2.2 后置 --](#2.2 后置 --)

[五、Date类日期 - 日期的重载实现](#五、Date类日期 - 日期的重载实现)

六、检查输入日期是否合法

七、流插入、流提取重载输出输入

1、流插入重载输出

[1.1 错误版本](#1.1 错误版本)

[1.1.1 优化方法一(成员变量public)](#1.1.1 优化方法一(成员变量public))

[1.1.2 优化方法二(友元函数)](#1.1.2 优化方法二(友元函数))

[1.2 完整版本(连续输出)](#1.2 完整版本(连续输出))

2、流提取重载输入

八、代码总览

Date.h

Date.cpp

Test.cpp

结束语


一、Date类+=和+的运算符重载实现与复用

1、构造函数初始化对象

cpp 复制代码
//Date.h
#include<iostream>
using namespace std;

class Date
{
public:
	Date(int year = 1900, int month = 1, int day = 1);

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

//Date.cpp
#include"Date.h"
//声明与定义分离时,缺省参数只能写在声明中不能同时出现在声明和定义
//构造函数初始化对象
Date::Date(int year, int month, int day)
{
	_year = year;
	_month = month;
	_day = day;
}

2、+=的运算符重载实现

2.1 获取月份天数GetMonthDay的实现

由于不管是 += 还是 +,还是后面大部分日期功能都需要我们获取对应月份的天数才能保证结果的日期是准确无误的。

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

class Date
{
public:
	Date(int year = 1900, int month = 1, int day = 1);

    //默认是内联inline函数,则不需要为了调用该函数再建立栈帧了
	int GetMonthDay(int year, int month)
	{
		assert(month > 0 && month < 13);
		//创建一个月份数组来存储每月对应的天数
		static int monthDayArr[13] = { -1, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
		//第一个-1是为了补上0这个位置,保证一月对应31天
		if (month == 2 && (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0))
		{
			return 29; //闰年二月为29天
		}
		return monthDayArr[month];
	}

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

这里我们会发现GetMonthDay函数我们是直接在 Date.h头文件将声明和定义写在一起,这是为什么呢?

首先该函数代码量很少 ,而且在之后实现日期很多功能时都要频繁调用该函数直接定义在类里面 则编译器会默认为inline(内敛)函数 ,则在调用该函数时不需要再建立栈帧了。

2.2 +=的实现与测试

cpp 复制代码
//Date.cpp
//d1 += 100
Date& Date::operator+=(int day)
{
	_day += day;
	while (_day > GetMonthDay(_year, _month))
	{
		_day -= GetMonthDay(_year, _month);
		_month++;
		if (_month > 12)
		{
			_year++;
			_month = 1;
		}
	}
	return *this;//首先引用返回目的是避免传值传参而减少拷贝构造的次数提高效率
	             //其次this指针是类的地址,我们要返回类,所以对this指针解引用
}

//Test.cpp
#include"Date.h"
int main()
{
	Date d1(2026, 1, 13);
	d1.Print();
	d1 += 1000;
	d1.Print();
	return 0;
}

为了证明1000天后对应的日期是正确的我们可以在网上用日期计算器来进行验证:

结果的日期正确则说明我们获取日期天数GetMonthDay的代码实现是正确的。

3、+的运算符重载实现

3.1 +的实现与测试

首先我们要知道 += 和 + 的区别在哪?

+=是会对自身的数据进行修改 ,而**+是不会改变自身的数据** ,这就引入了一个问题:+的运算符重载还能像+=那样用引用返回吗?

答案就是不行了。因为如果我们不能对自身数据进行修改则需要创建新的局部对象来存储修改后的日期,但是当出了函数后局部变量都会自动销毁,如果是引用返回则会变成无效返回;而只能用传值返回,因为传值返回会创建一个临时对象来存储数据,不会因为局部变量的销毁而导致返回无效,这在前面已经多次进行了讲解。

cpp 复制代码
//Date.cpp
//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 > 12)
		{
			tmp._year++;
			tmp._month = 1;
		}
	}
	return tmp;
	//由于tmp是在函数内部创建的局部对象,出了函数则会被自动销毁,所以只能用传值返回而不能用引用返回
}

//Test.cpp
int main()
{
	Date d1(2026, 1, 13);
    Date d2 = d1 + 1000;
	d1.Print();
	d2.Print();
	return 0;
}

3.2 复用+=的逻辑实现+

在前面我们已经实现了+=的代码,我们说了+=是会对原日期进行修改,这样的话我们不就可以利用+=的逻辑直接对创建的局部对象tmp进行修改吗,所以我们就不需要重复写上面的代码了:

cpp 复制代码
Date Date::operator+(int day)
{
	Date tmp = *this;
	tmp += day;//直接复用上面的+=运算符重载的逻辑,则不需要重复写
	return tmp;
}

3.3 复用+的逻辑实现+=

虽然上面我们是用+=的逻辑来实现+的运算符重载,那能不能反过来呢?

当然是可以的,主要还是看我们前面实现的是哪个运算符重载,再利用这个运算符重载来复用实现另外一个:

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

通过巧妙的复用前面实现的运算符重载,这样我们就能大大减少代码量了。

二、Date类-=和-的运算符重载实现与复用

1、-=的运算符重载实现

和+=的运算符重载实现逻辑类似,+=如果超过月份的最大天数是进位,则-=如果天数变为负数则需要借位:

cpp 复制代码
//Date.cpp
Date& Date::operator-=(int day)
{
	_day -= day;
	while (_day <= 0)
	{
		_month--;
		if (_month == 0)
		{
			_year--;
			_month = 12;
		}
		_day += GetMonthDay(_year, _month);
	}
	return *this;
}

//Test.cpp
int main()
{
	Date d1(2026, 1, 13);
	d1.Print();
	d1 -= 1000;
	d1.Print();
	return 0;
}

同理,我们也能用日期计算器来验证我们所写的-=运算符重载代码是否正确:

2、-的运算符重载实现

2.1 -的实现与测试

cpp 复制代码
//Date.cpp
Date Date::operator-(int day)
{
	Date tmp = *this;
	tmp._day -= day;
	while (tmp._day <= 0)
	{
		tmp._month--;
		if (tmp._month == 0)
		{
			tmp._year--;
			tmp._month = 12;
		}
		tmp._day += GetMonthDay(tmp._year, tmp._month);
	}
	return tmp;
}

//Test.cpp
int main()
{
	Date d1(2026, 1, 13);
	d1.Print();
	
	Date d2 = d1 - 1000;
	d2.Print();
	return 0;
}

2.2 复用-=的逻辑实现-

和+一样,当我们已经实现了-=运算符重载后,我们就可以利用这个重载来实现-的运算符重载,来大大减少代码量:

cpp 复制代码
Date Date::operator-(int day)
{
	Date tmp = *this;
	tmp -= day;//和+运算符重载一样,直接复用上面的-=运算符重载的逻辑,则不需要重复写
	return tmp;
}

2.3复用-的逻辑实现-=

同理,当我们实现了-运算符重载后,也能利用这个重载来方便实现-=的运算符重载:

cpp 复制代码
//复用-的逻辑来实现-=
Date& Date::operator-=(int day)
{
	*this = *this - day;
	return *this;
}

三、Date类比较符号的运算符重载实现

1、< 的运算符重载实现

cpp 复制代码
//Date.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; //包括_year > d._year 和 _month > d._month
}

2、= 的运算符重载实现

cpp 复制代码
bool Date::operator==(const Date& d)
{
	return _year == d._year
		&& _month == d._month
		&& _day == d._day;
}

3、<= 的运算符重载实现

我们之所以先写上面两个比较大小的运算符重载还是有意义的,前面我们实现了+=和-=的运算符重载后+和-的运算符重载就可以复用这两个重载。那这里也一样如此,通过复用>和=这两个运算符重载我们就可以非常简单的实现以下所有的比较符号的运算符重载了:

cpp 复制代码
bool Date::operator<=(const Date& d)
{
	return *this < d || *this == d; 
    //实现了上面两个比较符号的运算符重载就可以直接进行复用,大大减少代码量
}

4、> 的运算符重载实现

cpp 复制代码
bool Date::operator>(const Date& d)
{
	return !(*this <= d);
}

5、>= 的运算符重载实现

cpp 复制代码
bool Date::operator>=(const Date& d)
{
	return !(*this < d);
}

6、!= 的运算符重载实现

cpp 复制代码
bool Date::operator!=(const Date& d)
{
	return !(*this == d);
}

这样通过复用两个比较符号的运算符重载就可以快速实现其他的所有比较符号,能够大大减少代码量。

四、Date类前置 ++/-- 与后置 ++/-- 的运算符重载实现

1、前置 ++ 和后置 ++

1.1 前置 ++ 和后置 ++ 运算符重载的区别

重载++运算符时,有前置++和后置++,运算符重载函数名都是operator++ ,无法很好的区分。C++规定,后置++重载 时,增加一个 int 形参 ,跟前置++构成函数重载,方便区分。

1.2 前置 ++

++的逻辑还是加1,所以我们就可以复用上面所写的+=的运算符重载来减少代码量:

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

Test.cpp
int main()
{
	//前置++
	Date d1(2026, 1, 13);
	d1.Print();
	Date d3 = ++d1;
	d3.Print();
	return 0;
}

1.3 后置 ++

cpp 复制代码
//Date.cpp
//后置++(d1++)
Date Date::operator++(int)
{
	Date tmp = *this;
	*this += 1;
	return tmp;
}

//Test.cpp
int main()
{
	//前置++
	Date d1(2026, 1, 13);
	Date d2 = d1;
	d2.Print();
	Date d4 = d2++;
	d4.Print();
	d2.Print();
	return 0;
}

2、前置 -- 和后置 --

2.1 前置 --

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

//Test.cpp
int main()
{
	//前置
	Date d1(2026, 1, 13);
	d1.Print();
	Date d3 = --d1;
	d3.Print();
	d1.Print();
	return 0;
}

2.2 后置 --

cpp 复制代码
//Date.cpp
//后置--(d1++)
Date Date::operator--(int)
{
	Date tmp = *this;
	*this -= 1;
	return tmp;
}

//Test.cpp
int main()
{
	//后置--
    Date d1(2026, 1, 13);
    Date d2 = d1;
    d2.Print();
    Date d4 = d2--;
    d4.Print();
    d2.Print();
	return 0;
}

五、Date类日期 - 日期的重载实现

cpp 复制代码
//Date.cpp
//日期 - 日期
int Date::operator-(const Date& d)
{
	int flag = 1; //flag为1是表示假设大的日期减去小的日期
	Date max = *this;
	Date min = d;
	if (*this < d) //假设不成立,则说明是小的日期减去大的日期,则得到的结果为负值
	{
		min = *this;
		max = d;
		flag = -1;
	}
	int n = 0;
	while (min != max)//通过循环复用++运算符重载来得到两个日期之间的天数
	{
		++min;
		++n;
	}
	return n * flag;//如果flag为-1则说明是小的日期减去大的日期,n为正数则需要加负号
}

//Test.cpp
int main()
{
	//日期 - 日期
	Date d1(2026, 1, 13);
	Date d2(2035, 1, 1);
	cout << d2 - d1 << endl;
	cout << d1 - d2 << endl;
	return 0;
}

同理,如果在日期计算器中我们输入这个差值如果得到的结果日期是一样的则说明我们的代码是没问题的:

六、检查输入日期是否合法

到此Date类的基本功能我们就实现的差不多了,但有一个注意点就是如果有些人输入的日期不符合现在的时间规则,虽然不会报错但得到的结果也不会是正确的,所以对此我们可以在Date类中的构造函数进行优化:

cpp 复制代码
//Date.cpp
//检查日期是否合法
bool Date::CheckDate()
{
	if (_month > 0 && _month < 13 && _day > 0 && _day <= GetMonthDay(_year, _month))
	{
		return true;
	}
	else
		return false;
}

Date::Date(int year, int month, int day)
{
	_year = year;
	_month = month;
	_day = day;
	if (!CheckDate())
	{
		cout << "日期非法:";
		Print();
	}
}

//Test.cpp
int main()
{
    Date d1(2026, 1, 32);
	return 0;
}

七、流插入、流提取重载输出输入

1、流插入重载输出

我们知道流插入 cout 可以直接对内置类型进行输出 ,但是并不能直接对自定义类型进行输出,如果直接对 Date 类类型的对象进行输出就会出现下面的报错:

cpp 复制代码
int main()
{
    Date d1;
    cout << d1 << endl;
}

所以我们需要对流插入进行重载才能实现对自定义类型直接进行输出:

在写运算符重载之前我们要先知道流插入和流提取返回的类型是什么,这个在之前学习是很少接触的:

我们会发现流插入 coutostream 类型的对象流提取 cinistream 类型的对象,这样我们就可以写运算符重载了:

1.1 错误版本

cpp 复制代码
//Date.h
class Date
{
public:
	Date(int year = 1900, int month = 1, int day = 1);
	void operator<<(ostream& out); //流插入cout的重载
private:
	int _year;
	int _month;
	int _day;
};

//Date.cpp
//流插入cout的重载
void Date::operator<<(ostream& out)
{
	out << _year << "年" << _month << "月" << _day << "日" << endl;
}

//Test.cpp
int main()
{
    Date d1;
    cout << d1 << endl;
}

但是我们会发现这次写了流插入的运算符重载之后还是会报错,这是为什么呢?

这里我们就要分析一下报错原因了,二元 "<<":没有找到接受"Date"类型的右操作数的运算符

我们会发现 << 运算符和 +/-这些类似都是二元运算符 ,也就是说需要左右两个操作数,而且 << 的左边为 cout,右边为我们的 Date 类对象,但是由于我们的运算符重载声明是写在 Date 类里面的,所以隐含的第一个形参其实是 this 指针 ,第二个形参才是 Date d1;

这就出现了问题:原本我们是想左边为 cout 右边为 Date 类对象,也就是说第一个形参应该是 ostream& out,第二个形参才是 this 指针,但这里却是反过来了,这才是导致报错的原因。

1.1.1 优化方法一(成员变量public)

那怎么去解决呢?

我们知道如果把运算符重载放到 Date 类中第一个形参一定就是隐含的 this 指针,为了让第一个形参为 ostream& out,我们就可以放到全局域 中。

但是这就又引出了一个问题:如果我们放到全局域中,由于年月日这些成员变量是private 私有 的,所以我们不能在类外 的地方对这些成员变量进行访问

第一个解决方法就非常直接:将私有的成员变量改为 public 即可:

cpp 复制代码
//Date.h
class Date
{
public:
	Date(int year = 1900, int month = 1, int day = 1);
	void Print();
	//void operator<<(ostream& out); //流插入cout的重载
	//流插入的重载声明放在类中会导致第一个形参为 this 指针而不是我们想要的ostream& out
	//所以我们只能在全局域中实现流插入的重载
//private:
	int _year;
	int _month;
	int _day;
};

void operator<<(ostream& out, Date& d); //流插入cout的重载

//Date.cpp
//流插入cout的重载
void operator<<(ostream& out, Date& d)
{
	out << d._year << "年" << d._month << "月" << d._day << "日" << endl;
}

//Test.cpp
int main()
{
	Date d1;
	cout << d1;
	return 0;
}

当我们将声明放到全局域形参的位置我们就可以任意调整 来保证左右操作数和形参的位置是一一对应的。

但是最开始讲解类的时候我们就说过,不到万不得已我们一般是不会把成员变量置成 public,因为这些是我们的私人数据不希望被其他人任意篡改,那如果成员变量不置成 public 我们还能怎么获取到成员变量呢?

1.1.2 优化方法二(友元函数)

这个方法是一个我们之前没有接触过的新东西,这个我们后面还会详细讲解,它能够让类外的函数可以直接访问类中的所有成员:关键字 friend(友元声明)

cpp 复制代码
//Date.h
class Date
{
public:
    //友元函数的声明:能够让类外的函数也能直接访问类中的成员
    friend void operator<<(ostream& out, Date& d);

	Date(int year = 1900, int month = 1, int day = 1);
	void Print();
	//void operator<<(ostream& out); //流插入cout的重载
	//流插入的重载声明放在类中会导致第一个形参为 this 指针而不是我们想要的ostream& out
	//所以我们只能在全局域中实现流插入的重载
private:
	int _year;
	int _month;
	int _day;
};

void operator<<(ostream& out, Date& d); //流插入cout的重载

//Date.cpp
//流插入cout的重载
void operator<<(ostream& out, Date& d)
{
	out << d._year << "年" << d._month << "月" << d._day << "日" << endl;
}

//Test.cpp
int main()
{
	Date d1;
	cout << d1;
	return 0;
}

通过友元函数的声明 ,我们就可以在避免成员变量置成 public 的同时也能让类外的函数访问到类中的成员

1.2 完整版本(连续输出)

虽然我们已经成功实现了流插入的运算符重载,但是上面的代码其实只能让我们实现一个 Date 类对象的输出,而不能连续多个对象的输出,如果连续输出多个对象就会出现下面问题:

我们会发现第一个 << 流插入运算符并没有报错而是第二个 << 出现了问题,这就要提到一个知识点:赋值运算符结合性从右往左 的,但是流插入流提取运算符结合性从左往右 的。

所以cout 是先于 d1 进行流插入 ,但是通过上面我们所实现的代码返回的类型 却是void ,然后再与 d2 进行流插入 ,但此时返回的类型 void 与重载的第一个形参类型 ostream& out 不匹配,这就导致了报错。

所以解决方法就是让重载函数的返回类型 与第一个形参对应也就是ostream& ,这样我们就能理解为 cout << d1 返回的结果又是一个 cout ,然后变成 cout << d2,也就是依次输出。

cpp 复制代码
//Date.h
class Date
{
public:
    //友元函数的声明:能够让类外的函数也能直接访问类中的成员
    friend ostream& operator<<(ostream& out, Date& d);

	Date(int year = 1900, int month = 1, int day = 1);
	void Print();
	//void operator<<(ostream& out); //流插入cout的重载
	//流插入的重载声明放在类中会导致第一个形参为 this 指针而不是我们想要的ostream& out
	//所以我们只能在全局域中实现流插入的重载
private:
	int _year;
	int _month;
	int _day;
};

ostream& operator<<(ostream& out, Date& d); //流插入cout的重载

//Date.cpp
//流插入cout的重载
ostream& operator<<(ostream& out, Date& d)
{
	out << d._year << "年" << d._month << "月" << d._day << "日" << endl;
	return out;//返回的 out 其实就是 cout 别名
}

//Test.cpp
int main()
{
	Date d1;
    Date d2;
	cout << d1 << d2;
	return 0;
}

2、流提取重载输入

cpp 复制代码
//Date.h
class Date
{
public:
	//友元函数的声明:能够让类外的函数也能直接访问类中的成员
	friend istream& operator>>(istream& in, Date& d);

	Date(int year = 1900, int month = 1, int day = 1);
	void Print();
private:
	int _year;
	int _month;
	int _day;
};

istream& operator>>(istream& in, Date& d); //流提取cin的重载

/Date.cpp
//流提取cin的重载
istream& operator>>(istream& in, Date& d)
{
	cout << "请依次输入年月日:";
	in >> d._year >> d._month >> d._day;
	while (1)
	{
		if (!d.CheckDate())//如果输入的日期非法则需要重新输入
		{
			cout << "输入的日期非法:";
			d.Print();
			cout << "请重新输入!" << endl;
		}
		else
		{
			break;
		}
	}
	return in;//返回的 in 其实就是 cin 别名
}

//Test.cpp
int main()
{
	//流提取cin的重载
	Date d1;
	Date d2;
	cin >> d1 >> d2;
	cout << d1 << d2;
	cout << d1 - d2 << endl;
	return 0;
}

八、代码总览

Date.h

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

class Date
{
public:
	//友元函数的声明:能够让类外的函数也能直接访问类中的成员
	friend ostream& operator<<(ostream& out, Date& d);
	friend istream& operator>>(istream& in, Date& d);

	//全缺省的构造函数
	Date(int year = 1900, int month = 1, int day = 1);
	void Print();

	//默认是内联inline函数,则不需要为了调用该函数再建立栈帧了
	int GetMonthDay(int year, int month)
	{
		assert(month > 0 && month < 13);
		//创建一个月份数组来存储每月对应的天数
		static int monthDayArr[13] = { -1, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
		//第一个-1是为了补上0这个位置,保证一月对应31天
		if (month == 2 && (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0))
		{
			return 29; //闰年二月为29天
		}
		return monthDayArr[month];
	}

	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 day); //日期+=天数
	Date operator+(int day); //日期+天数

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

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

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

	int operator-(const Date& d);//日期 - 日期

	bool CheckDate(); //检查日期是否合法

	//void operator<<(ostream& out); //流插入cout的重载
	//流插入的重载声明放在类中会导致第一个形参为 this 指针而不是我们想要的ostream& out
	//所以我们只能在全局域中实现流插入的重载
private:
	int _year;
	int _month;
	int _day;
};

ostream& operator<<(ostream& out, Date& d); //流插入cout的重载
istream& operator>>(istream& in, Date& d); //流提取cin的重载

Date.cpp

cpp 复制代码
#define _CRT_SECURE_NO_WARNINGS
#include"Date.h"

//声明与定义分离时,缺省参数只能写在声明中不能同时出现在声明和定义
//构造函数初始化对象
Date::Date(int year, int month, int day)
{
	_year = year;
	_month = month;
	_day = day;
	if (!CheckDate())
	{
		cout << "日期非法:";
		Print();
	}
}

//检查日期是否合法
bool Date::CheckDate()
{
	if (_month > 0 && _month < 13 && _day > 0 && _day <= GetMonthDay(_year, _month))
	{
		return true;
	}
	else
		return false;
}

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

//日期 += 天数
Date& Date::operator+=(int day)
{
	_day += day;
	while (_day > GetMonthDay(_year, _month))
	{
		_day -= GetMonthDay(_year, _month);
		_month++;
		if (_month > 12)
		{
			_year++;
			_month = 1;
		}
	}
	return *this;//首先引用返回目的是避免传值传参而减少拷贝构造的次数提高效率
	             //其次this指针是类的地址,我们要返回类,所以对this指针解引用
}

//复用+的逻辑来实现+=
//Date& Date::operator+=(int day)
//{
//	*this = *this + 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 > 12)
		{
			tmp._year++;
			tmp._month = 1;
		}
	}*/
	tmp += day;//直接复用上面的+=运算符重载的逻辑,则不需要重复写
	return tmp;
	//由于tmp是在函数内部创建的局部对象,出了函数则会被自动销毁,所以只能用传值返回而不能用引用返回
}

//日期 -= 天数
Date& Date::operator-=(int day)
{
	_day -= day;
	while (_day <= 0)
	{
		_month--;
		if (_month == 0)
		{
			_year--;
			_month = 12;
		}
		_day += GetMonthDay(_year, _month);
	}
	return *this;
}

////复用-的逻辑来实现-=
//Date& Date::operator-=(int day)
//{
//	*this = *this - day;
//	return *this;
//}

//日期 - 天数
Date Date::operator-(int day)
{
	Date tmp = *this;
	/*tmp._day -= day;
	while (tmp._day <= 0)
	{
		tmp._month--;
		if (tmp._month == 0)
		{
			tmp._year--;
			tmp._month = 12;
		}
		tmp._day += GetMonthDay(tmp._year, tmp._month);
	}*/
	tmp -= day;//和+运算符重载一样,直接复用上面的-=运算符重载的逻辑,则不需要重复写
	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; //包括_year > d._year 和 _month > d._month
}

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

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

//前置++(++d1)
Date& Date::operator++()
{
	*this += 1;
	return *this;
}

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

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

//日期 - 日期
int Date::operator-(const Date& d)
{
	int flag = 1; //flag为1是表示假设大的日期减去小的日期
	Date max = *this;
	Date min = d;
	if (*this < d) //假设不成立,则说明是小的日期减去大的日期,则得到的结果为负值
	{
		min = *this;
		max = d;
		flag = -1;
	}
	int n = 0;
	while (min != max)//通过循环复用++运算符重载来得到两个日期之间的天数
	{
		++min;
		++n;
	}
	return n * flag;//如果flag为-1则说明是小的日期减去大的日期,n为正数则需要加负号
}

//流插入cout的重载
ostream& operator<<(ostream& out, Date& d)
{
	out << d._year << "年" << d._month << "月" << d._day << "日" << endl;
	return out;//返回的 out 其实就是 cout 别名
}

//流提取cin的重载
istream& operator>>(istream& in, Date& d)
{
	cout << "请依次输入年月日:";
	in >> d._year >> d._month >> d._day;
	while (1)
	{
		if (!d.CheckDate())//如果输入的日期非法则需要重新输入
		{
			cout << "输入的日期非法:";
			d.Print();
			cout << "请重新输入!" << endl;
		}
		else
		{
			break;
		}
	}
	return in;//返回的 in 其实就是 cin 别名
}

Test.cpp

cpp 复制代码
#define _CRT_SECURE_NO_WARNINGS
#include"Date.h"

int main()
{
	//+=和+的运算符重载测试
	//Date d1(2026, 1, 13);
	//d1.Print();
	////d1 += 1000;
	////d1.Print();

	//Date d2 = d1 + 1000;
	//d2.Print();

	//-=和-的运算符重载测试
	//Date d1(2026, 1, 13);
	//d1.Print();
	////d1 -= 1000;
	////d1.Print();

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

	//前置/后置++
	/*Date d1(2026, 1, 13);
	d1.Print();
	Date d2 = d1;
	d2.Print();
	Date d3 = ++d1;
	Date d4 = d2++;
	d3.Print();
	d4.Print();
	d1.Print();
	d2.Print();*/

	//前置/后置--
	/*Date d1(2026, 1, 13);
	d1.Print();
	Date d2 = d1;
	d2.Print();
	Date d3 = --d1;
	Date d4 = d2--;
	d3.Print();
	d4.Print();
	d1.Print();
	d2.Print();*/

	//日期 - 日期
	/*Date d1(2026, 1, 13);
	Date d2(2035, 1, 1);
	cout << d2 - d1 << endl;
	cout << d1 - d2 << endl;*/

	//流插入cout的重载
	/*Date d1;
	Date d2;
	cout << d1 << d2;*/

	//流提取cin的重载
	Date d1;
	Date d2;
	cin >> d1 >> d2;
	cout << d1 << d2;
	cout << d1 - d2 << endl;
	return 0;
}

结束语

到此,Date 类的设计与实现就全部讲解完了,虽然篇幅比较多但是都是基于前面几次所学的默认成员函数实现的,也算是再次对前面所学的知识进行复习巩固。希望这篇文章对大家学习C++能有所帮助!

C++参考文档:
https://legacy.cplusplus.com/reference/
https://zh.cppreference.com/w/cpp
https://en.cppreference.com/w/

相关推荐
玖釉-2 小时前
[Vulkan 学习之路] 03 - 你的守护天使:校验层 (Validation Layers)
c++·windows·图形渲染
冰暮流星2 小时前
c语言如何实现字符串复制替换
c语言·c++·算法
txinyu的博客2 小时前
C++内存池的内存对齐问题
c++
无限进步_2 小时前
【C语言&数据结构】二叉树链式结构完全指南:从基础到进阶
c语言·开发语言·数据结构·c++·git·算法·visual studio
脏脏a2 小时前
STL stack/queue 底层模拟实现与典型算法场景实践
开发语言·c++·stl_stack·stl_queue
deng-c-f2 小时前
Linux C/C++ 学习日记(63):Redis(四):事务
linux·c语言·c++
DYS_房东的猫3 小时前
《 C++ 零基础入门教程》第8章:多线程与并发编程 —— 让程序“同时做多件事”
开发语言·c++·算法
REDcker3 小时前
AIGCJson 库介绍与使用指南
c++·json·aigc·c
setary03013 小时前
c++泛型编程之Typelists
开发语言·c++