目录
Ⅰ. 引入
Ⅱ. 列轮廓
Ⅲ. 功能的实现
构造函数
判断是否相等 == | !=
➡️==:
➡️!=:
判断大小 > | >= | < | <=
➡️>:
➡️<=:
➡️>=:
➡️<:
加减天数 + | += | - | -=
➡️+=:
➡️+:
➡️-:
➡️-=:
自增/自减 ++ | --
➡️前置++
➡️后置++
➡️前置--
➡️后置--
日期减日期
➡️Way1(推荐)
➡️Way2( 这个思路繁杂很多)
Ⅰ. 引入
本篇我们用C++来实现一个日期计算器。
想知道迄今为止你在地球上一共度过了多少天吗?距离寒假还有多少天呢?一百天后会是几月几号呢?
解开这些问题的答案,只需来写一个日期计算器~👻
日期计算器是C++入门以来的第一个小项目,亲自实践一遍,我们在C++上的经验值将⬆️⬆️⬆️
🚩我们将分三步:
Step1:在头文件中把日期类的大体轮廓列出来
Step2:把声明的功能一一实现
Step3:逐个测试。我们写一点,测一点。
这样,就可顺利把日期计算器写出个七七八八。
在遇到较复杂的算法时,我会提供思路。
至于某些锦上添花的功能,我们后续想到了,再添上去。
Ⅱ. 列轮廓
🤔我们先来定义一个日期类,同时看看要实现哪些功能:
#pragma once
#include<iostream>
using namespace std;
class Date {
public:
Date(int year = 1900, int month = 1, int day = 1); //构造函数:用于初始化
void Print(); //打印日期,便于测试
//功能的具体实现
bool operator==(const Date& d); //判断俩date是否相等
bool operator!=(const Date& d);
bool operator>(const Date& d); //date间比较大小
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++(); //date的自增/自减
Date operatoe++(int);
Date& operator--();
Date operatoe--(int);
int operator-(const Date& d); //算两个date间差多少天
private:
int _year;
int _month;
int _day;
};
Ⅲ. 功能的实现
构造函数
➡️我们实现一个全缺省的构造函数:
class Date{
public:
Date(int year = 1900, int month = 1, int day = 1) {
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
}
每次实例化出一个对象,都要调用构造函数,调用频率非常高。
所以,我们干脆就把这短短的几行定义在类里,做内联函数。
❓你可能会疑惑:为啥_year可以直接拿来用,不需要this->year嘛?
后者当然可以写,但没必要。因为我们在使用类的成员函数or成员变量时,this指针会默认加上的。
我们就不用一一手动加啦✌
➡️Print,写在Date.c里:
void Date::Print() {
printf("%d-%d-%d\n", _year, _month, _day);
}
❓为啥要加Date::呢?
要知道,类定义了一个全新的作用域。类里类外,是有一层屏障的。
正因为类域的存在,我们不能直接从外部访问类的成员。
因此,把成员函数拿到类外定义时,要指明作用域,即加上Date::
❓我们不是学了cout嘛,为啥不直接cout输出,还得用printf?
这个问题我们先保留着,下一趴再讲。🤪
🔬🧪这俩函数先测试一波:
void Test1() {
Date d1(2023, 8, 23);
Date d2;
d1.Print();
d2.Print();
}
int main()
{
Test1();
return 0;
}
结果:
判断是否相等 == | !=
➡️==:
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指针。只有第二个形参可见。
“d1!=d2; ” 就相当于在调用函数 “d1.operator!=(d2); ”
此函数的this指针指向d1,形参的d即d2。
🔬🧪测试一下:
void Test2() {
Date d1(2023, 8, 23);
Date d2(2000, 1, 1);
if (d1 != d2) {
cout << "unequal"<<endl;
}
}
int main()
{
//Test1();
Test2();
return 0;
}
结果:
判断大小 > | >= | < | <=
日期的大小,听着蛮抽象。其实就是日期的先后:2023年1月1日比2000年1月1日要大(后)。
➡️>:
bool Date::operator>(const Date& d) {
if (_year > d._year
|| _year == d._year && _month > d._month
|| _year == d._year && _month == d._month && _day > d._day) {
return true;
}
else {
return false;
}
}
这种算法的思路是:
写完>,先不急着写<,因为>的对立面是<=,那我们可以把这段代码复用到<=👻
➡️<=:
bool Date::operator<=(const Date& d) {
return !(*this > d);
}
➡️>=:
bool Date::operator>=(const Date& d) {
return *this > d || *this == d;
}
➡️<:
bool Date::operator<(const Date& d) {
return !(*this >= d);
}
🔬🧪测试一下:
void Test3() {
Date d1(2023, 8, 23);
Date d2(2000, 1, 1);
cout << (d1 > d2) << endl;
cout << (d1 <= d2)<<endl;
}
int main()
{
//Test1();
//Test2();
Test3();
return 0;
}
结果:
加减天数 + | += | - | -=
➡️+=:
日期加天数要考虑进位的问题。我举个例子,先顺下思路:
2023-12-21往后推40天
61相比当月的31已经溢出了,
怎么判断是否溢出呢?
写个函数GetMonthDay(),取到每月的天数进行比对
GetMonthDay()实现如下:
int Date::GetMonthDay(int year, int month) {
int days[13]={ 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
int ret = days[month];
//考虑闰年的2月是29天的情况
//闰年,要么能被4整除&&不能被100~;要么能被400整除
if (month == 2
&& (year % 4 == 0 && year % 100 == 0
|| year % 400 == 0)) {
ret += 1;
}
return ret;
}
☑️operator+=实现如下:
Date& Date::operator+=(int day) {
_day += day;
int MaxDay = GetMonthDay(_year, _month);
while (_day > MaxDay) {
_day -= MaxDay;
_month++;
//防止month溢出
if (_month == 13) {
_month = 1;
_year++;
}
MaxDay = GetMonthDay(_year, _month);
}
return *this;
}
➡️+:
有了+=,+就直接复用~👻
Date Date::operator+(int day) {
Date ret = *this;
ret+=day;
return ret;
}
🤔❓下面这种写法会导致程序崩溃。为啥不能这样写?
Date Date::operator+(int day) {
Date ret = *this+day;
return ret;
}
我们进入这个函数看一下:
!陷入死循环了。
我写了一个operator+,我现在要用operator+...
用operator+的话,得去调用我写的那个operator+...
调用函数operator+时,我又用到了operator+...
陷入死循环了...
💡这是因为:
上面那位因为有了+=,+就可以直接复用。
而这里我们本身就是在写+,+没法复用。
ret不是内置类型,它是我们自定义的。
自定义类型的加减 是需要我们自己去实现 运算符重载的。
之所以写成这样是正确的:
Date ret = *this;
ret += day;
return ret;是因为我们先前就定义了Date类型的+=。
如果现在把+=给注释掉,那这一段同样会出问题,编译不过去。
➡️-:
加要考虑进位,减要考虑借位。
举例:2023-3-2 往前40天
思路:
🌀你可能有点晕:-38为什么要借2月的28?
可以把2023-3-2往前40天视为2023-3-0往前38天。
此时要借位,我们没法从3月借,因为它是空的。只能从2月借。
☑️实现:
Date& Date::operator-=(int day) {
_day -= day;
while (_day <= 0) {
_month--;
if (_month == 0) {
_month = 12;
_year--;
}
int BorrowDay = GetMonthDay(_year, _month);
_day += BorrowDay;
}
return *this;
}
➡️-=:
同样,复用🥰
Date Date::operator-(int day) {
Date ret = *this;
ret -= day;
return ret;
}
🔬🧪测试一下:
void Test4() {
Date d1(2023, 12, 21); //+ +=
Date d2(d1);
(d1 + 40).Print();
(d2 += 700).Print();
Date d3(d1); //- -=
Date d4(2023, 3, 2);
(d3 -= 400).Print();
(d4 - 40).Print();
}
结果:
自增/自减 ++ | --
我们用参数占位来区分前置/后置++:
前置:operator++( )
后置:operator++( int )
❓前置++和后置++的区别是什么?
这俩都能实现自增,但返回值不同。
前置:++d; 先加加,再使用。返回的是加加后的值。
后置:d++;先使用,再加加。返回的是加加前的值。
假设d=0,d++:返回1,d=1
++d:返回0,同时实现自增,d=1
所以说,后置加加是不能引用返回的。而前置可以。
➡️前置++
Date& Date::operator++() {
return *this += 1;
}
➡️后置++
Date Date::operator++(int) {
Date ret = *this;
*this += 1;
return ret;
}
➡️前置--
Date& Date::operator--() {
return *this -= 1;
}
➡️后置--
Date Date::operator--(int) {
Date ret = *this;
*this -= 1;
return ret;
}
🔬🧪测试一下:
void Test5() {
Date d1(2023, 1, 1); //++
Date d2(d1);
(++d1).Print();
(d2++).Print();
Date d3(2023, 1, 1); //--
Date d4(d3);
(--d3).Print();
(d4--).Print();
}
结果:
日期减日期
距离新年还有多少天呢?
Date(2024,1,1) - Date(2023,8,24) =❓天
➡️Way1(推荐)
我们刚刚不是写了好多功能嘛,复用起来~👻
实现:
int Date::operator-(const Date& d) {
Date More = *this; //先把date标个大小
Date Less = d;
if (Less > More) {
More=d;
Less=*this;
}
int count = 0; //用计数法算差值
while (Less<More) {
Less++; //复用🥰👻
count++;
}
int flag = 1; //我们不知道是大-小or小-大
if (More == d) { //为了区分结果的正负,引入flag
flag = -1;
}
return count*flag;
}
这种方法虽然思路简单,但是深度复用了代码,效率会下降。
➡️Way2( 这个思路繁杂很多)
(❗这个方法效率会⬆️,但是较复杂,可略过不看!)
Q: 2023-2-13到2024-1-15,要过多少天?、
思路:
Step1:把月、日转化成总天数;
Step2:年与年之间相减,天与天之间相减
Step3:全化成天
实现:
Step1 我们先写一个把月、日转换成天数的函数ConverttoDay( )
如下:
int Date::ConverttoDay(int year, int month, int day) {
int MonthtoDay = 0;
month -= 1;
while (month) {
MonthtoDay += GetMonthDay(year, month);
month--;
}
int ret = MonthtoDay + day;
return ret;
}
Step2 实现operator-函数
int Date::operator-(const Date& d) {
//先判断日期的大小
Date BigDate = *this;
Date SmallDate = d;
if (BigDate < SmallDate) {
Date tmp = SmallDate;
SmallDate = BigDate;
BigDate = tmp;
}
//把月、日都转换成天
int BigDay = ConverttoDay(BigDate._year, BigDate._month, BigDate. _day);
int SmallDay = ConverttoDay(SmallDate._year, SmallDate._month, SmallDate._day);
int RetofDay = BigDay - SmallDay; //天之间相减,大天-小天
int BigYear = BigDate._year;
int SmallYear = SmallDate._year;
//年之间相减,大年-小年
int CountDay = 0;
while (SmallYear < BigYear) {
CountDay += 365;
if (SmallYear % 4 == 0 && SmallYear % 100 != 0 //考虑闰年
|| SmallYear % 400 == 0) {
CountDay += 1;
}
SmallYear++;
}
//把两者的天数合一
int ret = RetofDay + CountDay;
int flag = 1;
if (*this == BigDate) {
flag = -1;
}
return flag * ret;
}
🔬🧪测试一下:
void Test6() {
Date d1(2023, 8, 24);
Date d2(2024, 1, 1);
printf("%d\n", d2 - d1);
}
结果:
完整代码
Date.h
#pragma once
#include<iostream>
using namespace std;
class Date {
public:
Date(int year = 1900, int month = 1, int day = 1) { //构造函数:用于初始化
_year = year;
_month = month;
_day = day;
}
void Print(); //打印日期,便于测试
int GetMonthDay(int year, int month);
//功能的具体实现
bool operator==(const Date& d); //判断俩date是否相等
bool operator!=(const Date& d);
bool operator>(const Date& d); //date间比较大小
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++(); //date的自增/自减
Date operator++(int);
Date& operator--();
Date operator--(int);
int operator-(const Date& d); //算两个date间差多少天
private:
int _year;
int _month;
int _day;
};
Date.cpp
#include"Date.h"
void Date::Print() {
printf("%d-%d-%d\n", _year, _month, _day);
}
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) {
if (_year > d._year
|| _year == d._year && _month > d._month
|| _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);
}
int Date::GetMonthDay(int year, int month) {
int days[13]={ 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
int ret = days[month];
//考虑闰年的2月是29天的情况
//闰年,要么能被4整除&&不能被100~;要么能被400整除
if (month == 2
&& (year % 4 == 0 && year % 100 != 0
|| year % 400 == 0)) {
ret += 1;
}
return ret;
}
Date Date::operator+(int day) {
Date ret = *this;
ret += day;
return ret;
}
Date& Date::operator+=(int day) {
_day += day;
int MaxDay = GetMonthDay(_year, _month);
while (_day > MaxDay) {
_day -= MaxDay;
_month++;
//防止month溢出
if (_month == 13) {
_month = 1;
_year++;
}
MaxDay = GetMonthDay(_year, _month);
}
return *this;
}
Date Date::operator-(int day) {
Date ret = *this;
ret -= day;
return ret;
}
Date& Date::operator-=(int day) {
_day -= day;
while (_day <= 0) {
_month--;
if (_month == 0) {
_month = 12;
_year--;
}
int BorrowDay = GetMonthDay(_year, _month);
_day += BorrowDay;
}
return *this;
}
Date& Date::operator++() {
return *this += 1;
}
Date Date::operator++(int) {
Date ret = *this;
*this += 1;
return ret;
}
Date& Date::operator--() {
return *this -= 1;
}
Date Date::operator--(int) {
Date ret = *this;
*this -= 1;
return ret;
}
int Date::operator-(const Date& d) {
Date More = *this; //先把date标个大小
Date Less = d;
if (Less > More) {
More=d;
Less=*this;
}
int count = 0; //用计数法算差值
while (Less<More) {
Less++;
count++;
}
int flag = 1;
if (More == d) {
flag = -1; //*this-d,如果*this更小,那结果应为负值
}
return count*flag;
}
Test.cpp
#include"Date.h"
void Test1() {
Date d1(2023, 8, 23);
Date d2;
d1.Print();
d2.Print();
}
void Test2() {
Date d1(2023, 8, 23);
Date d2(2000, 1, 1);
if (d1 != d2) {
cout << "unequal"<<endl;
}
}
void Test3() {
Date d1(2023, 8, 23);
Date d2(2000, 1, 1);
cout << (d1 > d2) << endl;
cout << (d1 <= d2)<<endl;
}
void Test4() {
Date d1(2023, 12, 21); //+ +=
Date d2(d1);
(d1 + 40).Print();
(d2 += 700).Print();
Date d3(d1); //- -=
Date d4(2023, 3, 2);
(d3 -= 400).Print();
(d4 - 40).Print();
}
void Test5() {
Date d1(2023, 1, 1); //++
Date d2(d1);
(++d1).Print();
(d2++).Print();
Date d3(2023, 1, 1);
Date d4(d3);
(--d3).Print();
(d4--).Print();
}
void Test6() {
Date d1(2023, 8, 24);
Date d2(2024, 1, 1);
printf("%d\n", d2 - d1);
}
void Test7() {
Date d1(2023, 2,13);
Date d2(2025, 1,15);
printf("%d\n", d1-d2);
}
int main()
{
//Test1();
//Test2();
//Test3();
//Test4();
//Test5();
//Test6();
Test7();
return 0;
}
OK, 到这我们的日期计算器已经完成啦~🥰👻