⚡【C++要笑着学】(9) 类和对象练习:日期类的实现 | 从零设计日期类各个接口 | 感受代码复用的便捷性 | 体会测试代码的重要性 | 提供完整代码

🔥 订阅量破千的火热 C++ 教程
👉 火速订阅
《C++要笑着学》 

 🔥 CSDN 累计订阅量破千的火爆 C/C++ 教程的 2023 重制版,C 语言入门到实践的精品级趣味教程。
了解更多: 👉 "不太正经" 的专栏介绍 试读第一章
订阅链接: 🔗《C语言趣味教程》 ← 猛戳订阅!

 本篇博客全站综合热榜最高排名:4

💭 写在前面:啊,朋友们好啊。我是亦优叶子,上一章我们讲解了运算符重载,本篇将手把手从零开始一步步实现一个 Date类,将会对每个步骤进行详细的思考和解读。

目录

Ⅰ.  实现日期类

0x00 引入:如何实现?

0x00 设计构造函数

0x01 判断大于 operator>

0x02 日期加等天数 operator+=

0x03  日期加天数 operator+

0x04  日期减等天数 operator -=

0x05 日期减天数 operator -

0x06  体会测试代码的重要性

0x07  日期加加 operator++

0x08 日期减减 operator--

0x09  判断日期是否相同 operator==

0x0A  体会复用的威力

0x0B  判断大于等于 operator>=

0x0C  判断小于 operator<

0x0D  判断小于等于 operator<=

0x0E  判断日期是否不相等 operator!=

0x0F  日期减日期 operator- 

0x10  打印今天是周几 PrintWeekDay

Ⅱ.  完整代码

0x00  Date.h

0x01  Date.cpp

0x02  test.cpp

0x03 更多实现方式(自行参考)


Ⅰ.  实现日期类

0x00 引入:如何实现?

为了能够更好地讲解运算符重载的知识,我们将手把手、一步一步地实现 "日期类" ,

因为通过日期类去讲解运算符重载是比较合适的。

日期类的拷贝构造、赋值、析构我们都可以不用写,让编译器自己生成就行了。

0x00 设计构造函数

 规范一点,我们声明与定义分离开来。

💬 Date.h

#include <iostream>
using namespace std;

class Date {
public:
	Date(int year = 1, int month = 1, int day = 1);    // 全缺省构造
	void Print() const;                                // 打印函数

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

💬 Date.cpp

include "Date.h"

Date::Date(int year, int month, int day) {
	this->_year = year;
	this->_month = month;
	this->_day = day;
}

void Date::Print() const {
	printf("%d-%d-%d\n", this->_year, this->_month, this->_day);
}

int main(void)
{
	Date d1;
	d1.Print();

	Date d2(2022, 3, 14);
	d2.Print();

	return 0;
}

🚩 运行结果如下:

 构造函数我们用了全缺省,这样我们不给值的时候也可以打印默认的值。

通过打印函数我们有可以把日期打印出来,这里可以加 const 修饰,

上一篇我们说过,加 const 是很好的,只要不改变都建议加上 const 。

 比如这里的 Print 是可以加 const 的,

而构造函数这里修改了成员变量,是不能加 const 的。

现在我们现在来思考一个特殊的问题……

如果,我是说如果!我们输入的日期是一个非法的日期呢?

💬 比如:

Date d3(2022, 13, 15);  // 作为地球人,怎么会有13月呢?
d3.Print();

🚩 运行结果如下:

 如果有人输入了这种日期,还是能给他打印出来,

  这合理吗?这不合理!

有人又觉得,谁会拿这种日期初始化啊,这种日期不是一眼就能看出来有问题嘛……

💬 说得好,那这些呢?

int main(void)
{
	Date d4(2022, 2, 29);
	d4.Print();

	Date d5(2009, 2, 29);
	d5.Print();

	Date d6(2000, 2, 29);
	d6.Print();

	return 0;
}

 是不是没有那么容易一眼看出来了?这都涉及到闰年的问题了。

所以我们需要设计一个函数去判断,用户输入的日期到底合不合法。

💬 Date.h

#include <iostream>
using namespace std;

class Date {
public:
	Date(int year = 1, int month = 1, int day = 1);    // 全缺省构造
	int GetMonthDay(int year, int month) const ;       // 获取某年某月对应的天数
	void Print() const;                                // 打印函数


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

 因为每个月天数不统一,所以在写判断日期合法的算法前,

我们需要先设计一个可以获取一年中每个月对应天数的 GetMonthDay 函数。

如何设计呢?写12个 if else

 可以是可以,但是好像有点搓啊。

switch case 可能还好一点。我们这里可以写个简单的哈希来解决,

我们把每个月的天数放到一个数组里,为了方便还可以把下标为 0 的位置空出来,

  这样我们就可以直接按照 "月份" 读出对应的 "日期" 了。

根据我们小时候背的口诀,依次把日期填到数组中就行了。

这里还要考虑到我们上面提到的闰年问题,闰年二月会多一天。

如果月份为 2,我们就进行判断,如果是闰年就让获取到的 day + 1 即可。

💬 Date.c

int Date::GetMonthDay(int year, int month) {
	static int monthDatArray[13] = { 0, 
		31, 28, 31, 30, 31, 30, 
		31, 31, 30, 31, 30, 31 
	};                                         
	int day = monthDatArray[month];	            // 获取天数

	if (month == 2                              // 先判断是否为二月
		&& ((year % 4 == 0 && year % 100 != 0)  // 是二月再判断是否是闰年
		|| (year % 400 == 0))) {
		day += 1;                               // 是闰年,天数+1
	}

	return day;                                 // 返回计算的天数
}

🔑 解读:

创建一个 day 去获取月份对应的天数,然后进行判断。

如果传入的月份是2月,就判断是否是闰年。

这里的一个小细节就是我们是先判断传入的月份2月的,

如果不是2月我们是压根没有必要进行闰年判断的,

根据 && 的特性,碰到假后面后不会判断了,所以我们先判断传入的月份是否是2月,

 是2月 —— 为真,再继续判断是否是闰年。

 如果是闰年,让天数+1,就完事了。

 有了GetMonthDay 函数,就解决了每个月天数不统一的问题了。

💬 我们可以写判断部分了:

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

	// 判断日期是否合法
	if ( ! (_year >= 0
		   && (month > 0 && month < 13)
		   && (day > 0 && _day <= GetMonthDay(year, month)))
		) {
		cout << "非法日期: ";
		this->Print();  // 在类里面不仅仅可以访问成员变量,还可以访问成员函数(this可省略)
	}
}

🚩 运行结果如下:

判断用户输入的日期是否合法的功能就写出来了,如果用户输入的日期不合法,

   就把他铐起来!   我们就提示用户这是一个非法日期。

0x01 判断大于 operator>

  比较两个日期的大小。

💬 Date.h

#include <iostream>
using namespace std;

class Date {
public:
	Date(int year = 1, int month = 1, int day = 1);    // 全缺省构造
	int GetMonthDay(int year, int month) const ;       // 获取某年某月对应的天数
	void Print() const;                                // 打印函数
	bool operator>(const Date& d) const;               // d1 > d2

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

💬 Date.cpp

/* d1 > d2 */
bool Date::operator>(const Date& d) const {
	if (this->_year > d._year) {
		return true;
	} 
	else if (this->_year == d._year 
		&& this->_month > d._month) {
		return true;
	} 
	else if (this->_year == d._year
		&& this->_month == d._month
		&& this->_day > d._day) {
		return true;
	} 
	else {
		return false;
	}
}

🔑 解读:日期的判断很简单,我们只需要挨个对比就可以了,

结果返回布尔值,没什么好说的。

 为了方便测试,我们再开一个 test.cpp 来放测试用例,方便后续测试我们的代码。

💬 test.cpp

void DateTest1() {
	Date d4(2022, 2, 29);
	d4.Print();

	Date d5(2009, 2, 29);
	d5.Print();

	Date d6(1998, 2, 29);
	d6.Print();
}

void DateTest2() {
	Date d1(2022, 2, 1);

	Date d2(2012, 5, 1);
	cout << (d1 > d2) << endl;

	Date d3(2022, 3, 15);
	cout << (d1 > d3) << endl;

	Date d4(d1);
	cout << (d1 > d4) << endl;
}

int main(void)
{
	// DateTest1();
	DateTest2();

	return 0;
}

🚩 运行结果如下:

当然,这里我们还可以实现的更简单一些,效果是一样的:

bool Date::operator>(const Date& d) const {
    return (this->_year > d._year 
    || this->_month > d._month
    || this->_day > d._day);
}
  • 逻辑或运算符(||)具备短路原则,它在进行条件判断时,只要第一个条件为真,就会立即返回true,并且不再继续计算后面的条件。

0x02 日期加等天数 operator+=

合并一个日期似乎没什么意义,但是加天数的场景就很多了。

比如我们想让当前日期加100天:

加完后,d1 的日期就是加了100天之后的日期了,我们这里要实现的就是这个功能。

💬 Date.h

class Date {
public:
    // ...
    Date& operator+=(int day);				  // d1 += 100

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

 日期加天数没有难的,就是一个 "进位" 而已。

把进位搞定就可以了,在动手前我们可以先画个图来分析分析怎么个进法:

很明显,只需要判断加完日期后天数合不合法,

看它加完后的天数有没有超出这个月的天数,如果超过了就不合法。

这个我们刚才已经实现过 GetMonthDay 了,这里就直接拿来用就行了。

如果不合法,我们就进位。天满了往月进,月再满就往年进。

💬 Date.cpp

Date& Date::operator+=(int day) {
	this->_day += day;  // 把要加的天数倒进d1里
	while (this->_day > GetMonthDay(this->_year, this->_month)) {   // 判断天数是否需要进位
		this->_day -= GetMonthDay(this->_year, this->_month);   // 减去当前月的天数
		this->_month++;  // 月份+1

		if (this->_month == 13) {   // 判断月份是否需要进位
			this->_month = 1;  // 重置月份
			this->_year++;   // 年份+1
		}
	}

	return *this;
} 

  首先把天数都倒进 d1 里,之后检查一下天数是否溢出了。

 如果溢出就进位,这里的逻辑部分通过我们刚才画的图可以很轻松地实现出来,

天满了就往月进位,月满了就往年进位。

最后返回 *this ,把我们加好的 d1 再递交回去就ok了。

因为出了作用域对象还在,我们可以使用引用返回减少拷贝,岂不美哉?

 

💬 test.c

void DateTest3() {
	Date d1(2022, 1, 16);
	d1 += 100;   // 让当前天数+100天
	d1.Print();
}

int main(void)
{
	// DateTest1();
	// DateTest2();
	DateTest3();

	return 0;
}

🚩 运行结果如下:

0x03  日期加天数 operator+

还是和 += 一样,日期加日期没有什么意义,但是 "日期 + 天数" 还是用得到的。

所以我们重载 operator+ 为一个日期加天数的。

💬 Date.h

class Date {
public:
    // ...
    Date operator+(int day) const;                     // d1 + 100

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

+= 是改变 "本体",但是 + 并不会,所以这里可以加个 const 修饰一下。

++= 很类似,也是通过 "判断" 就可以实现的,

因为我们刚才已经实现过 += 了,所以我们可以做一个巧妙地复用。

 复用我们刚才实现的 += ,我们来看看是怎么操作的。

💬 Date.c

/* d1 + 100 */
Date Date::operator+(int day) const {
	Date ret(*this);       // 拷贝构造一个d1
	// ret.operator+=(day);
    ret += day;            // 巧妙复用+=

	return ret;            // 出了作用域ret对象就不在了,所以不能用引用返回
}

 我们只需要做 "加" 的工作,把算好的日期返回回去就行了,

我们将 "本体" 复制一个 "替身" 来把结果返回去,利用拷贝构造复制出一个 ret 出来,

我们对这个 "替身" 进行加的操作,这样就不会改 "本体" 了

顺便提一下,因为它出了作用域会死翘翘,

所以我们 —— 不能用引用返回!不能用引用返回!不能用引用返回!

重点来了,这里巧妙地复用我们刚才已经实现好的 += ,就可以轻松搞定了:

ret.operator+=(day);

如果觉得看起来不爽,我们甚至可以直接这么写:

ret += day;

就是这么浅显易懂,可读性真的是强到炸。

+= 之后 ret 的值就是加过 day 的值了,并且是赋到 ret 身上的,所以 —— 

  直接  重仓  return 回去,就大功告成了,我们来测试下代码。

💬 test.cpp

void DateTest4() {
	Date d1(2022, 1, 16);
	Date ret = d1 + 100;

	ret.Print();
}



int main(void)
{
	// DateTest1();
	// DateTest2();
	// DateTest3();
	DateTest4();

	return 0;
}

🚩 运行结果如下:

复用真的是一件很爽的事情,我们下面的讲解还会疯狂地复用的。

0x04  日期减等天数 operator -=

 我们刚才实现了 operator+= ,现在我们来实现一下 -=

+= 进位,那 -= 自然就是借位。

日期如果不合法,往月去借,月不够了,就往年去借。

💬 Date.h

class Date {
public:
    // ...
	Date& operator-=(int day);				  // d1 -= 100

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

我们先把日期减一下,此时如果天数被减成负数了,那我们就需要进行借位操作。

💬 Date.cpp

/* d1 -= 100 */
Date& Date::operator-=(int day) {
	this->_day -= day;
	while (this->_day <= 0) {   // 天数为0或小于0了,就得借位,直到>0为止。
		this->_month--;  // 向月借
		if (this->_month == 0) {   // 判断月是否有得借
			this->_year--;   // 月没得借了,向年借
			this->_month = 12;  // 年-1了,月份置为12月
		}

		this->_day += GetMonthDay(this->_year, this->_month);  // 把借来的天数加到_day上
	}

	return *this;
}

 如果减完后的天数小于等于 0,就进入循环,向月 "借位" ,

因为已经借出去了,所以把 月份 - 1 。还要考虑月份会不会借完的情况,

月份为 0 的时候就是没得借了,这种情况就向年借,

之后加上通过 GetMonthDay 获取当月对应的天数,就是所谓的 "借",

循环继续判断,直到天数大于0的时候停止,返回 *this 

出了作用域 *this 还在,所以我们可以使用引用返回 Date&

💬 test.cpp

void DateTest5() {
	Date d1(2022, 3, 20);
	d1 -= 100;  // 2021, 12, 10
	d1.Print();
}

int main(void)
{
	// DateTest1();
	// DateTest2();
	// DateTest3();
	// DateTest4();
	DateTest5();

	return 0;
}

🚩 运行结果如下:

0x05 日期减天数 operator -

 一样的,没什么好说的,我们复用一下 -= 就可以把 - 实现出来了。

💬 Date.h

class Date {
public:
    // ...
	Date operator-(int day) const;			      // d1 - 100
	

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

直接复用就完事了,和 operator+ 思路一样。

💬 Date.cpp

/* d1 - 100 */
Date Date::operator-(int day) const {
	Date ret(*this);   // 拷贝构造一个d1
	ret -= day;        // ret.operator-=(day);
	
	return ret;
}

 为了顺便带大家体验测试代码的重要性,这里的测试部分我们单独拿出来举例。

0x06  体会测试代码的重要性

我们来好好测试一下刚才写的 operator- ,这里我们进行一个详细的测试,

测试减去的 day 跨月,跨年甚至跨闰年的情况,这样哪里出问题我们可以一目了然。

💬 test.cpp

void DateTest6() {
	Date d1(2022, 1, 17);

	Date ret1 = d1 - 10;
	ret1.Print();

	Date ret2 = d1 - 17;
	ret2.Print();

	Date ret3 = d1 - 30;
	ret3.Print();

	Date ret4 = d1 - 400;
	ret4.Print();
}

int main(void)
{
	// DateTest1();
	// DateTest2();
	// DateTest3();
	// DateTest4();
	// DateTest5();
	DateTest6();

	return 0;
}

🚩 运行结果如下:

Tips:我们在验证的时候可以再网上找这种在线的日期推算器,来验证一下我们写的对不对。

刚才我们正常测试,确实没什么问题,我们来测试个极端的情况,

❓ 如果我们给的是 d1 - -100 呢?

void DateTest6() {
	Date d1(2022, 1, 17);

	Date ret = d1 - -100;
	ret.Print();
}

 🚩 运行结果如下:

我们这里代码是复用 operator-= 的,所以我们得去看看 operator-=

我们发现,在设计 operator-= 的时候是 <= 0 才算非法的,所以这种情况就没考虑到。

我们可以这么设计,在减天数之前对 day 进行一个特判,

因为你减负的100就相当于加正的100,就变成加了,

⚡ Date.cpp

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

	this->_day -= day;
	while (this->_day <= 0) {   // 天数为0或小于0了,就得借位,直到>0为止。
		this->_month--;  // 向月借
		if (this->_month == 0) {   // 判断月是否有得借
			this->_year--;   // 月没得借了,向年借
			this->_month = 12;  // 年-1了,月份置为12月
		}

		this->_day += GetMonthDay(this->_year, this->_month);  // 把借来的天数加到_day上
	}

	return *this;
}

这样就正常了。

我们再把 operator+= 处理一下:

⚡ Date.cpp

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

	this->_day += day;  // 把要加的天数倒进d1里
	while (this->_day > GetMonthDay(this->_year, this->_month)) {   // 判断天数是否需要进位
		this->_day -= GetMonthDay(this->_year, this->_month);   // 减去当前月的天数
		this->_month++;  // 月份+1

		if (this->_month == 13) {   // 判断月份是否需要进位
			this->_month = 1;  // 重置月份
			this->_year++;   // 年份+1
		}
	}

	return *this;
}

 所以多写几个测试用例,来测一测各种情况,是非常有必要的。

0x07  日期加加 operator++

📚 日期++ 分为 "前置++" 和 "后置++"  

d1++;
++d1;

因为都是 operator++ ,为了能让编译器直到我们实现的到底是 前置++ 还是 后置++,

这里就到用到一个叫做 "参数占位" 的东西 ——,即:

后置++ 带  " int " ,构成函数重载。

operator++(int);     // 带int,表示后置加加   d1++
operator++();        // 不带, 表示前置加加   ++d1

实现前置++ :

💬 Date.h

Date& operator++();				          // ++d1;

因为 前置++ 返回的是加加之后的值,所以我们使用引用返回。

加不加引用就取决于它出了作用域在不在。

💬 Date.cpp

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

 这里我们直接复用 +=,加加以后的值就是 *this ,我们返回一下 *this 就行。

实现后置++ :

💬 Date.h

Date operator++(int);				      // d1++;

因为后置++返回的是加加之前的值,所以我们不用引用返回。

💬 Date.cpp

/* d1++ */
Date Date::operator++(int) {
	Date ret(*this);   // 为了能返回++之前的值,我们拷贝构造一个d1
	*this += 1;  // 复用+=,让本体+1

	return ret; 
}

我们在加加之前先拷贝构造一个 "替身" 出来,"本体" 加加后,

把替身 ret 返回回去,就实现了返回加加之前的值。

这里要拷贝构造两次,所以我们推荐以后自定义类型++,使用前置++

💬 test.cpp

void DateTest7() {
	Date d1(2022, 3, 20);

	Date ret1 = d1++;   // d1.operator++(&d1, 0);
	Date ret2 = ++d1;   // d1.operator++(&d1);
}


int main(void)
{
	DateTest7();

	return 0;
}

🐞 监测结果如下:

0x08 日期减减 operator--

 📚 日期-- 分为 "前置--" 和 "后置--"  

operator++ 一样,operator-- 为了能够区分前置和后置,也要用 int 参数占位

 后置-- 带  " int " ,构成函数重载。

operator--(int);     // 带int,表示后置减减   d1--
operator--();        // 不带, 表示前置减减   --d1

实现前置-- :

💬 Date.h

Date& operator--();				          // --d1

对应的, 前置-- 返回的是减减之后的值,所以我们使用引用返回。

💬 Date.cpp

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

这里我们直接复用 -=,减减以后的值就是 *this ,然后返回 *this

实现后置-- :

💬 Date.h

Date operator--(int);				      // d1--

因为 后置-- 返回的是减减之前的值,所以我们不用引用返回。

💬 Date.cpp

/* d1-- */
Date Date::operator--(int) {
	Date ret(*this);   // 拷贝构造一个d1
	*this -= 1;

	return ret;
}

我们在减减之前先拷贝构造一个 "替身" ,待本体加加后,

把替身 ret 返回回去,就实现了返回减减之前的值。

0x09  判断日期是否相同 operator==

💬 Date.h

bool operator==(const Date& d) const;           // d1 == d2 

💬 Date.cpp

/* d1 == d2 */
bool Date::operator==(const Date& d) const {
	return this->_year == d._year
		&& this->_month == d._month
		&& this->_day == d._day;
}

 只有年月日都相等才是 true,否则就是 false

0x0A  体会复用的威力

📚 引入:

比如我们要实现 operator<,因为大于我们已经实现过了,

我们现在来写 <,可以直接把大于改成小于:

bool Date::operator<(const Date& d) const {
	if (this->_year < d._year) {
		return true;
	}
	else if (this->_year == d._year 
		  && this->_month < d._month) {
		return true;
	}
	else if (this->_year == d._year 
		  && this->_month == d._month 
		  && this->_day < d._day) {
		return true;
	}
	else {
		return false;
	}
}

…… 既然都能这样了,那为什么不用用神奇的复用呢?

我们已经把 operator> == 实现了,剩下的这些我们都可以复用解决。

 技巧:对于类的比较,实现一个 >== 其它直接复用就完事了。

(当然,实现一个 < 和 == 也行,随你便)

这个技巧不仅仅针对于日期类的比较,所有类要实现比较,都可以用这种方式,

除非它们不互斥(  >= 取反是 < ,这就是互斥 )

bool operator>(const Date& d) const;            // d1 > d2
bool operator==(const Date& d) const;           // d1 == d2 

0x0B  判断大于等于 operator>=

💬 Date.h

bool operator>=(const Date& d) const;            // d1 >= d2

💬 Date.cpp

/* d1 >= d2 */
bool Date::operator>=(const Date& d) const {
	return *this > d || *this == d;
}

 直接复用解决就完事了,这波真是秦始皇被雷劈,嬴麻了。

0x0C  判断小于 operator<

💬 Date.h

bool operator<(const Date& d) const;            // d1 < d2

小于,不就是既不大于也不等于嘛。

 所以我们直接复用 operator>= 就完事了。

💬 Date.cpp

/* d1 < d2 */
bool Date::operator<(const Date& d) const {
	return !(*this >= d);
}

这里取反就可以了,如果 d1 >= d2 为真,取反后 ! 则返回假。反之返回真。

0x0D  判断小于等于 operator<=

💬 Date.h

bool operator<=(const Date& d) const;			  // d1 <= d2

💬 Date.cpp

/* d1 <= d2 */
bool Date::operator<=(const Date& d) const {
	return !(*this > d);
}

小于等于,就是 > 取反,没什么好说的。

这里你还可以用  <  || == ,也可以的。

0x0E  判断日期是否不相等 operator!=

💬 Date.h

bool operator!=(const Date& d) const;			  // d1 != d2

 不等于和等于逻辑是相反的,直接反 就完事了。

 💬 Date.cpp

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

0x0F  日期减日期 operator- 

 我们刚才实现的 operator- 是日期减天数的:

/* d1 - 100 */
Date Date::operator-(int day) const {
	Date ret(*this);   // 拷贝构造一个d1
	ret -= day;        // ret.operator-=(day);
	
	return ret;
}

❓ 如果我们想要计算一下距离暑假还有多少天呢?

 那我们就需要写一个日期减日期版本的 operater-

   

💬 Date.h

Date operator-(int day) const;			      // holiday - today

 我们现在想的是日期减日期,我们可以让小的天数去加大的天数。 

💬 Date.cpp

/* holiday - today */
Date Date::operator-(const Date&d) const {
	// 我默认认为第一个大,第二个小
	Date max = *this;
	Date min = d;
	int flag = 1;      // 立个flag

	// 如果第一个小,第二个大
	if (*this < d) {
		max = d;
		min = *this;
		flag = -1;     // 说明我们立的flag立错了,改成-1
	}

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

	return count * flag;
}

 我们可以用立 flag 法。

我们先立个flag 假设 —— 第一个日期大,第二个日期小。

flag 为 1 表示第一个大第二个小,flag 为 -1 表示第一个小第二个大。

分别创建 maxmin 表示第一个日期(d1)和第二个日期(d2)。

然后走一遍判断,核实一下立的 flag 对不对,不对的话就纠正一下。

之后我们用计数器的方式来实现就可以了。

💬 test.cpp

void DateTest8() {
	Date today(2022, 1, 17);
	Date holiday(2022, 7, 1);

	cout << (holiday - today) << endl;
	cout << (today - holiday) << endl;   
}

0x10  打印今天是周几 PrintWeekDay

❓ 打印今天是周几什么什么意思?

" 打印今天是周几,就是打印今天是星期几。"   —— 节选自《 节选自 <节选> 》

💬 Date.h

void PrintWeekDay() const;

💬 Date.cpp

void Date::PrintWeekDay() const {
	// 1900.1.1 日开始算
	const char* week[] = { "周一", "周二", "周三", "周四", "周五", "周六", "周日" };
	Date start(1900, 1, 1);
	int count = *this - start;
	cout << week[count % 7] << endl;
}

我们拿 1900年1月1号 作为起始点,两个日期相减得到的结果,

%7 作为下标去访问 week 数组里我们已经准备好的周几,打印出来就可以了。

⚡ 这个 start 反正我们就在这用一下而已,后面也用不着了,这里就可以使用匿名对象:

void Date::PrintWeekDay() const {
	// 1900.1.1 日开始算
	const char* week[] = { "周一", "周二", "周三", "周四", "周五", "周六", "周日" };
	
	int count = *this - Date(1900, 1, 1);  // 可以使用匿名对象
	cout << week[count % 7] << endl;
}

💬 test.cpp

void DateTest9() {
	Date d1(2021, 3, 19);
	d1.PrintWeekDay();
}

int main(void)
{
	DateTest9();

	return 0;
}

 大功告成!!!

Ⅱ.  完整代码

0x00  Date.h

#include <iostream>
using namespace std;

class Date {
public:
	Date(int year = 1, int month = 1, int day = 1);    // 全缺省构造
	int GetMonthDay(int year, int month) const ;       // 获取某年某月对应的天数
	void Print() const;                                // 打印函数

	bool operator>(const Date& d) const;               // d1 > d2
	bool operator==(const Date& d) const;              // d1 == d2 
		bool operator>=(const Date& d) const;          // d1 >= d2
		bool operator<(const Date& d) const;           // d1 < d2
		bool operator<=(const Date& d) const;          // d1 <= d2
		bool operator!=(const Date& d) const;          // d1 != d2

	Date& operator+=(int day);				           // d1 += 100
	Date& operator-=(int day);				           // d1 -= 100
		Date operator+(int day) const;                 // d1 + 100
		Date operator-(int day) const;			       // d1 - 100

	Date& operator++();				                   // ++d1;
	Date operator++(int);				               // d1++;
    Date& operator--();                                // --d1;
    Date operator--(int);                              // d1--;

	int operator-(const Date& d) const;                // 日期减日期
	void PrintWeekDay() const;                         // 返回星期几

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

0x01  Date.cpp

#include "Date.h"

/* 获取月对应的天数 */
int Date::GetMonthDay(int year, int month) const {
	static int monthDatArray[13] = { 0, 
		31, 28, 31, 30, 31, 30, 
		31, 31, 30, 31, 30, 31 
	};                                          // 简陋哈希
	int day = monthDatArray[month];	            // 获取天数

	if (month == 2                              // 先判断是否为二月
		&& ((year % 4 == 0 && year % 100 != 0)  // 是二月,再判断是否是闰年
		|| (year % 400 == 0))) {
		day += 1;                               // 是闰年,天数+1
	}

	return day;
}

/* 全缺省构造函数 */
Date::Date(int year, int month, int day) {
	this->_year = year;
	this->_month = month;
	this->_day = day;

	// 判断日期是否合法
	if (!(_year >= 0
		&& (month > 0 && month < 13) 
		&& (day > 0 && _day <= GetMonthDay(year, month)))) {
		cout << "非法日期: ";
		this->Print();  // 访问成员函数(this可省略)
	}
}

/* 打印函数 */
void Date::Print() const {
	printf("%d-%d-%d\n", this->_year, this->_month, this->_day);
}


/* d1 > d2 */
bool Date::operator>(const Date& d) const {
	if (this->_year > d._year) {
		return true;
	} 
	else if (this->_year == d._year 
		&& this->_month > d._month) {
		return true;
	} 
	else if (this->_year == d._year
		&& this->_month == d._month
		&& this->_day > d._day) {
		return true;
	} 
	else {
		return false;
	}
}

/* d1 == d2 */
bool Date::operator==(const Date& d) const {
	return this->_year == d._year
		&& this->_month == d._month
		&& this->_day == d._day;
}

/* d1 >= d2 */
bool Date::operator>=(const Date& d) const {
	return *this > d || *this == d;
}

/* d1 < d2 */
bool Date::operator<(const Date& d) const {
	return !(*this >= d);
}

/* d1 <= d2 */
bool Date::operator<=(const Date& d) const {
	return !(*this > d);
}

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


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

	this->_day += day;  // 把要加的天数倒进d1里
	while (this->_day > GetMonthDay(this->_year, this->_month)) {   // 判断天数是否需要进位
		this->_day -= GetMonthDay(this->_year, this->_month);   // 减去当前月的天数
		this->_month++;  // 月份+1

		if (this->_month == 13) {   // 判断月份是否需要进位
			this->_month = 1;  // 重置月份
			this->_year++;   // 年份+1
		}
	}

	return *this;
}

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

	this->_day -= day;
	while (this->_day <= 0) {   // 天数为0或小于0了,就得借位,直到>0为止。
		this->_month--;  // 向月借
		if (this->_month == 0) {   // 判断月是否有得借
			this->_year--;   // 月没得借了,向年借
			this->_month = 12;  // 年-1了,月份置为12月
		}

		this->_day += GetMonthDay(this->_year, this->_month);  // 把借来的天数加到_day上
	}

	return *this;
}

/* d1 + 100 */
Date Date::operator+(int day) const {
	Date ret(*this);  // 拷贝构造一个d1
	ret += day;       // ret.operator+=(day);

	return ret;   // 出了作用域ret对象就不在了,所以不能用引用返回
}

/* d1 - 100 */
Date Date::operator-(int day) const {
	Date ret(*this);   // 拷贝构造一个d1
	ret -= day;        // ret.operator-=(day);
	
	return ret;
}


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

/* d1++ */
Date Date::operator++(int) {
	Date ret(*this);   // 拷贝构造一个d1
	*this += 1;

	return ret;
}

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

/* d1-- */
Date Date::operator--(int) {
    Date ret(*this);   // 拷贝构造一个d1
    *this += 1;

    return ret;
}

/* 日期减日期 */
int Date::operator-(const Date& d) const {
	// 我默认认为第一个大,第二个小
	Date max = *this;
	Date min = d;
	int flag = 1;      // 立个flag

	// 如果第一个小,第二个大
	if (*this < d) {
		max = d;
		min = *this;
		flag = -1;     // 说明我们立的flag立错了,改成-1
	}

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

	return count * flag;
}

/* 返回星期几 */
void Date::PrintWeekDay() const {
	// 1900.1.1 日开始算
	const char* week[] = { "周一", "周二", "周三", "周四", "周五", "周六", "周日" };
	
	int count = *this - Date(1900, 1, 1);  // 可以使用匿名对象
	cout << week[count % 7] << endl;
}

0x02  test.cpp

#define _CRT_SECURE_NO_WARNINGS 1
#include "Date.h"

void DateTest1() {
	Date d4(2022, 2, 29);
	d4.Print();

	Date d5(2009, 2, 29);
	d5.Print();

	Date d6(1998, 2, 29);
	d6.Print();
}

void DateTest2() {
	Date d1(2022, 2, 1);

	Date d2(2012, 5, 1);
	cout << (d1 > d2) << endl;

	Date d3(2022, 3, 15);
	cout << (d1 > d3) << endl;

	Date d4(d1);
	cout << (d1 > d4) << endl;
}

void DateTest3() {
	Date d1(2022, 1, 16);
	d1 += 100;   // 让当前天数+100天
	d1.Print();
}

void DateTest4() {
	Date d1(2022, 1, 16);
	Date ret = d1 + 100;

	d1.Print();
	ret.Print();
}

void DateTest5() {
	Date d1(2022, 3, 20);
	d1 -= 100;  // 2021, 12, 10
	d1.Print();
}

void DateTest6() {
	Date d1(2022, 1, 17);

	Date ret = d1 - -100;
	ret.Print();
}

void DateTest7() {
	Date d1(2022, 3, 20);

	Date ret1 = d1++;   // d1.operator++(&d1, 0);
	Date ret2 = ++d1;   // d1.operator++(&d1);
}

void DateTest8() {
	Date today(2022, 1, 17);
	Date holiday(2022, 7, 1);

	int ret1 = holiday - today;
	int ret2 = today - holiday;

	cout << ret1 << endl;
	cout << ret2 << endl;   
}

void DateTest9() {
	Date d1(2022, 3, 19);
	d1.PrintWeekDay();
}


int main(void)
{
	// DateTest1();
	// DateTest2();
	// DateTest3();
	// DateTest4();
	// DateTest5();
	// DateTest6();
	// DateTest7();
	// DateTest8();
	// DateTest9();

	return 0;
}


0x03 更多实现方式(自行参考)

#include <iostream>
using namespace std;

class Date {
public:
    // 全缺省构造函数
    Date(int year = 1, int month = 1, int day = 1)
        : _year(year), _month(month), _day(day) {}

    // 获取某年某月对应的天数
    int GetMonthDay(int year, int month) const {
        static const int monthDays[] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
        int days = monthDays[month];
        if (month == 2 && IsLeapYear(year)) {
            days++;
        }
        return days;
    }

    // 打印日期
    void Print() const {
        cout << _year << "-" << _month << "-" << _day << endl;
    }

    // 判断是否为闰年
    bool IsLeapYear(int year) const {
        return (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0);
    }

    // 比较操作符重载
    bool operator>(const Date& d) const {
        return ToDays() > d.ToDays();
    }

    bool operator==(const Date& d) const {
        return ToDays() == d.ToDays();
    }

    bool operator>=(const Date& d) const {
        return ToDays() >= d.ToDays();
    }

    bool operator<(const Date& d) const {
        return ToDays() < d.ToDays();
    }

    bool operator<=(const Date& d) const {
        return ToDays() <= d.ToDays();
    }

    bool operator!=(const Date& d) const {
        return ToDays() != d.ToDays();
    }

    // 加减操作符重载
    Date& operator+=(int day) {
        int daysToAdd = day;
        while (daysToAdd > 0) {
            int daysInMonth = GetMonthDay(_year, _month);
            int daysToMonthEnd = daysInMonth - _day + 1;

            if (daysToAdd >= daysToMonthEnd) {
                _month++;
                if (_month > 12) {
                    _year++;
                    _month = 1;
                }
                _day = 1;
                daysToAdd -= daysToMonthEnd;
            } else {
                _day += daysToAdd;
                break;
            }
        }
        return *this;
    }

    Date& operator-=(int day) {
        int daysToSubtract = day;
        while (daysToSubtract > 0) {
            if (daysToSubtract >= _day) {
                _month--;
                if (_month < 1) {
                    _year--;
                    _month = 12;
                }
                daysToSubtract -= _day;
                _day = GetMonthDay(_year, _month);
            } else {
                _day -= daysToSubtract;
                break;
            }
        }
        return *this;
    }

    Date operator+(int day) const {
        Date result = *this;
        result += day;
        return result;
    }

    Date operator-(int day) const {
        Date result = *this;
        result -= day;
        return result;
    }

    // 前缀递增操作符重载 (++d1)
    Date& operator++() {
        *this += 1;
        return *this;
    }

    // 后缀递增操作符重载 (d1++)
    Date operator++(int) {
        Date temp = *this;
        *this += 1;
        return temp;
    }

    // 前缀递减操作符重载 (--d1)
    Date& operator--() {
        *this -= 1;
        return *this;
    }

    // 后缀递减操作符重载 (d1--)
    Date operator--(int) {
        Date temp = *this;
        *this -= 1;
        return temp;
    }

    // 日期减日期操作
    int operator-(const Date& d) const {
        return ToDays() - d.ToDays();
    }

    // 返回星期几
    void PrintWeekDay() const {
        static const string weekDays[] = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"};
        int daysSinceEpoch = ToDays();
        int dayOfWeek = daysSinceEpoch % 7;
        cout << "Day of the week: " << weekDays[dayOfWeek] << endl;
    }

private:
    int _year;
    int _month;
    int _day;

    // 计算自纪元以来的天数
    int ToDays() const {
        int days = 0;
        for (int y = 1; y < _year; y++) {
            days += IsLeapYear(y) ? 366 : 365;
        }
        for (int m = 1; m < _month; m++) {
            days += GetMonthDay(_year, m);
        }
        days += _day;
        return days;
    }
};


int main() {
    // 创建日期对象
    Date d1(2023, 10, 3);
    Date d2(2023, 10, 1);

    // 打印日期
    cout << "d1: ";
    d1.Print();
    cout << "d2: ";
    d2.Print();

    // 比较操作符测试
    cout << "d1 > d2: " << (d1 > d2) << endl;
    cout << "d1 == d2: " << (d1 == d2) << endl;
    cout << "d1 >= d2: " << (d1 >= d2) << endl;
    cout << "d1 < d2: " << (d1 < d2) << endl;
    cout << "d1 <= d2: " << (d1 <= d2) << endl;
    cout << "d1 != d2: " << (d1 != d2) << endl;

    // 加减操作符测试
    d1 += 2;
    cout << "d1 += 2: ";
    d1.Print();
    d2 -= 1;
    cout << "d2 -= 1: ";
    d2.Print();
    Date d3 = d1 + 5;
    cout << "d3 = d1 + 5: ";
    d3.Print();
    Date d4 = d2 - 3;
    cout << "d4 = d2 - 3: ";
    d4.Print();

    // 递增递减操作符测试
    ++d1;
    cout << "++d1: ";
    d1.Print();
    d2++;
    cout << "d2++: ";
    d2.Print();
    --d3;
    cout << "--d3: ";
    d3.Print();
    d4--;
    cout << "d4--: ";
    d4.Print();

    // 日期减日期操作测试
    int daysDiff = d1 - d2;
    cout << "Days difference between d1 and d2: " << daysDiff << endl;

    // 打印星期几
    cout << "Weekday of d1: ";
    d1.PrintWeekDay();

    return 0;
}

  ​​

📌 [ 笔者 ]   王亦优
📃 [ 更新 ]   2022.3.20 | 2023.10.3(重制)
❌ [ 勘误 ]   Star丶北辰:某处标题有误,某处声明写反了 (已修正)
📜 [ 声明 ]   由于作者水平有限,本文有错误和不准确之处在所难免,
              本人也很想知道这些错误,恳望读者批评指正!

📜 参考资料 

Microsoft. MSDN(Microsoft Developer Network)[EB/OL]. []. .

百度百科[EB/OL]. []. https://baike.baidu.com/.

比特科技. C++[EB/OL]. 2021[2021.8.31]

基础概念) C++开始(二)——何谓表达式(说明各操作符的用处,但不是全部,剩余的会 在其它文章提到) C++开始(三)——何谓变量(说明电脑的工作方式,阐述内存、地址等极其重 要的概念) C++开始(四)——赋值操作符(《C++开始(二)》的延续,并为指针的 解释打一点基础) C++开始(五)——何谓指针(阐述指针、数组等重要的概念) C++开始(六)——何谓语句(讲解C++提供的各个语句,说明各自存在的理 由) C++开始(七)——何谓函数(说明函数及其存在的理由) C++开始(八)——C++样例一(给出一两个简单算法,一步步说明如何从算法 编写出C++代码C++开始(九)——何谓结构(简要说明结构、枚举等及其存在的理由) C++开始(十)——何谓类(说明类及其存在的理由,以及声明、定义、头文件 等概念) C++开始(十一)——类的相关知识(说明派生、继承、名字空间、操作符重载 等) C++开始(十二)——何谓面向对象编程思想(阐述何谓编程思想,重点讲述面 向对象编程思想) C++开始(十三)——C++样例二(说明如何设计基于面向对象编程思想的C+ +程序) C++开始(十四)——何谓模板(说明模板技术及其存在的理由) C++开始(十五)——何谓异常(说明异常技术及其存在的理由) C++开始(十六)——何谓预编译指令(说明预编译指令的概念及几个常用指令 的应用) C++开始(十七)——C++中的一些生僻关键字(explicit、mutable、volatile等的说 明) C++开始(十八)——何谓SDK(说明为什么没有放音乐的指令却依然可以编出 放音乐的程序) C++开始(十九)——何谓C运行时期库(说明C运行时期库这一大多数问题的元 凶) C++开始(二十)——关于VC的一点点基础知识(说明VC的几个基本概念和一 些常用设置) C++开始(二十一)——C++样例三(使用VC编写一个通过DLL实现多态性的简 单程序)
评论 31
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

王平渊

喜欢的话可以支持下我的付费专栏

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

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

打赏作者

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

抵扣说明:

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

余额充值