#上期传送门#
C++之初识“类”:写一个关于日期的类(附两种日期间隔计算)
https://blog.csdn.net/henry_23/article/details/104832525
第二次课,讲了构造函数,作业要求是在第一次的基础上增添一些功能:
为MyDate类增加功能,要求如下:
(1) 为类增加有参和无参的构造函数,初始化为"合法"的日期(比如:2020年2月30日就不合法)
(2) 增加一个成员函数:MyDate yesterday(),将当前对象日期设为昨天,并返回"昨天"的对象(MyDate yesterday()——这里的MyDate是返回值的类型)
(3) 完善set()函数,设置合法的日期
(4) 求一个日期在指定天数之前/后的日期
(5) 计算两个日期相距的天数
(6) 在main函数中测试新增功能
原来我上周竟猜中了下周的作业……
首先,为了判断日期有效性,我准备写一个函数isLegal()
。
因为发现它和tomorrow()
函数都需要获得某月的总天数,便决定先写一个days()
函数,用于返回当前月份的天数。
int MyDate::days()
{
int days[13] ={0,31,28,31,30,31,30,31,31,30,31,30,31};
if (month==2)
{
if(isLeap()) return 29;
else return 28;
}
else return days[month];
}
下面来写isLegal()
,这里,我觉得年月日三者的检验需要有顺序。因为判断天数时需要调用days()
来判断,而days()
要求月份是合法的,所以调用days()
之前需要先校验月份。从另一个角度来说,不确定年份、月份,何谈日子是否正确?
代码如下:
int MyDate::isLegal()
{
if (year<1) return 0;
if (month<1||month>12) return 0;
if (day<1||day>days()) return 0;
return 1;
}
如果有兴趣,还可以在合法性判断的同时加入自动纠正,比如2月30日自动纠正成2月29日(或者2月28日)、1月-2日纠正为1月2日;要实现也不难,可以令isLegal()
可以返回不同的值,说明不同的出错情况,然后进行相应的修改:
这样,我们就可以在set()
或者构造函数里调用isLegal()
函数进行判断,如果设定的日期不合法,就执行一些操作,比如把他改成某个日期(如“1970年1月1日”),并且在控制台输出一些提示语:
if (isLegal() == 0)
{
cout<<"Illegal date! Date has been set to "; //提示语句
year = 1970; month = 1; day = 1; //执行操作
print();
cout<<endl;
}
# 后期备注:感谢一位同学在课堂上向老师提出的问题,这里通用一点的做法貌似应该是令set()
函数返回一些值,然后在调用处使用一个flag
之类的变量接受这个值,然后执行相应的操作,比如:
int MyDate::set(int y, int m, int d)
{
MyDate backup(year,month,day); //为现有的数据创建一个备份
year = y; month = m; day = d;
if (isLegal() == 0)
{
set(backup);
return 0;
}
return 1;
}
int main()
{
MyDate d1(2020,1,1);
int flag = d1.set(2020,2,30);
if (flag == 0) cout<<" d1 set failed!"<<endl;
else cout<<"d1 set successfully!"<<endl;
return 0;
}
最后,把合法性判断的过程加入到set(int, int, int)
和构造函数里面就好了。
因为考虑到我们现有的类一定是个合法日期(即已经确保了所有set()
、构造函数之类的“入口”不会发生错误),那么便不必在另一个参数为对MyDate
类的引用的 set()
的重载中加入合法性判断了。(当然,也没必要给无参的构造函数加入这个过程,只要保证自己设的default值无误就可以了)
构造函数:
MyDate::MyDate(int y_, int m_, int d_)
{
year = y_; month = m_; day = d_;
if (isLegal() == 0)
{
cout<<"[Construction Failure] ";
print();
cout<<" is illegal! "<<endl;
cout<<"Initialization failed. Date is set to ";
year = 1970; month = 1; day = 1;
print();
cout<<endl;
}
}
set 函数:
值得说明的是,由于我们不能确保传入的y_
、m_
、d_
三个参数是否为合法的组合,并且希望当它们不合法时,原来的数据不会被更改,故事先生成一个名为backup
的类,为原来的数据做个备份。
void MyDate::set(int y_, int m_, int d_)
{
MyDate backup(year,month,day);
year = y_; month = m_; day = d_;
if (isLegal() == 0)
{
cout<<"[Set Failure] ";
print();
cout<<" is illegal! Date is unchanged!";
cout<<endl;
set(backup);
}
}
你可能会想,为什么不把
isLegal()
写成带有三个参数的形式,直接判断三个参数的组合是否合法?
嗯……其实那样也是挺不错的,但是那样可能需要重新写一行语句或者一个print()
的重载用来输出这个不合法的日期;而我恰好比较懒,所以就觉得不如先默认参数是正确的,直接赋值,再进行判断和其他操作,似乎会方便一些。
当然这仅仅代表我个人的想法……
上一期里介绍了两种计算日期间隔的方法,分别为面向对象的和面向过程的,这里不再讲述。有需要的同学戳链接访问上期文档。
C++之初识“类”:写一个关于日期的类(附两种日期间隔计算)
# 后期补充:日期间隔算法的一点点小优化:C++之初识“类” (3):自己写的MyDate类的一些改进
接下来我们来写yesterday()
函数,整体思路和tomorrow()
类似。tomorrow()
采用“进位”的思想,yesterday()
采用“退位”的思想,都能很方便的实现我们想要的功能。
tomorrow()
的思路已经在上期中解释了。而yesterday()
函数中要注意的,就是它里面更改day
的语句要放在对月份的更新后——月份还没有定下来,自然也就不能确定天数。
MyDate MyDate::yesterday()
{
day--;
if (day<1)
{
month--;
if(month<1)
{
year--;
month = 12;
}
day = days();
}
MyDate t(year,month,day);
return t;
}
至于为什么老师要求返回一个MyDate
类,我一时没想到什么实际的意义,就暂且当作是为了练习而写的吧。
有了前面的tomorrow()
和yesterday()
的基础,实现前进 / 倒退指定天数,只需要使用循环,多次调用相应的函数即可,而无需再写复杂的语句(尤其是倒退指定天数,如果用计算的方法来写可能会很复杂)。
rolling()
有两个参数,一个是整型的dif
,用来传入需要前进 / 回滚的天数;另一个是默认值为0
的整型参数mode
,这是由于老师的要求“显示指定天数之前/之后的日期”,没有说明是否需要改变原函数,所以设置了它,用来限定是否修改原日期(默认是不修改的);
函数实现如下:
void MyDate::rolling(int dif, int mode = 0)
{
// 0 for not modify original date;
// 1 for silently rolling the date;
int k = dif;
if (mode == 0)
{
MyDate tmp(year, month, day);
if (k < 0)
{
while (k != 0)
{
tmp.yesterday();
k++;
}
print();
cout<<" 之前 "<<-dif<<" 天是 ";
tmp.print();
}
else
{
while (k != 0)
{
tmp.tomorrow();
k--;
}
print();
cout<<" 之后 "<<dif<<" 天是 ";
tmp.print();
}
cout<<endl;
}
else
{
if (k<0) while(k!=0) {yesterday(); k++;}
else while (k!=0) {tomorrow(); k--;}
}
}
# 后期补充:同样,这里有关于此算法的改进:C++之初识“类” (3):自己写的MyDate类的一些改进
void print_gap(MyDate& A, MyDate& B)
{
A.print(); cout<<" 和 "; B.print();
cout<<" 间隔 ";
cout<<A.get_gap(B);
cout<<" 天 \n";
}
int main()
{
MyDate day1(2020,3,16);
MyDate day2(2020,2,30);
cout<<endl;
day2.set(2001,2,29);
cout<<endl;
day2.print();
cout<<endl<<endl;
print_gap(day1,day2);
cout<<day1.gap(day2);
cout<<endl<<endl;
day1.rolling(3);
day2.rolling(-2);
return 0;
}
输出结果
[Construction Failure] 2020年2月30日 is illegal! Initialization failed.
Date is set to 1970年1月1日
[Set Failure] 2001年2月29日 is illegal! Date is unchanged!
1970年1月1日
2020年3月16日 和 1970年1月1日 间隔 18337 天
18337
2020年3月16日 之后 3 天是 2020年3月19日
1970年1月1日 之前 2 天是 1969年12月30日
觉得输出“x年x月x日”不够酷?试着给print加一个参数,让它能够输出你想要的风格,比如2020.3.16
,2020/3/16
,Mar 16, 2020
,16/3/2020
等等;但是,你可能会发现,很多地方都使用了print()
函数,加一个参数的话,要改很多很多地方,这可怎么办?能不能只修改print()
函数?
嗯……我这次做作业前……又瞟了一眼书,看到了静态成员变量相关的内容,目前想到可以用它来轻松的实现风格的整体更改。
甚至还可以写一个求某日期是星期几的函数。这个,你会怎么做呢?
根据现有的函数,我想到的是设定一个基准日期,计算与之的日期间隔,然后结果对7求余,同时判断日期是在基准日之前还是之后,继而得出该日期是星期几。
最后再看一眼咱们的MyDate类吧,不知不觉都写了这么多功能了呀:
class MyDate
{
private:
int year, month, day;
public:
void set(int y_, int m_, int d_);
void set(const MyDate& );
int days();
void print();
int equals(const MyDate& );
void tomorrow();
int isLeap();
int isLeap(int );
int isLegal();
int compare(const MyDate& );
int get_gap(const MyDate& );
int gap(const MyDate& );
void rolling(int , int );
MyDate yesterday();
MyDate();
MyDate(int y_, int m_, int d_);
};