一个小目录
前言
开始C++类的学习,老师布置的作业是要求写一个日期的类——MyDate。
第一次作业要求以下这些函数:
void set(int, int, int); //通过三个值初始化类
void set(const MyDate& date); //通过对另一个MyDate类的常引用初始化
void print(); //打印出日期
int equals(const MyDate& date); //判断两日期是否相等
void tomorrow(); //当前日期向后推演一日
除了这些,因为自己提前看了下课件,又写了两个构造函数;为了实现tomorrow,要用到闰年的判断,于是写了if_leap_year();同时,增加了一些自己想到的功能,最终如下:
class MyDate
{
private:
int year, month, day;
public:
void set(int y_, int m_, int d_);
void set(const MyDate& date);
void print();
int equals(const MyDate& date);
void tomorrow();
int days(int, int);
int if_leap_year()
{
if( (year % 4 ==0 && year % 100 != 0) || year % 400 == 0 ) return 1;
else return 0;
}
int if_leap_year(int year)
{
if( (year % 4 ==0 && year % 100 != 0) || year % 400 == 0 ) return 1;
else return 0;
}
int compare(const MyDate & dst)
{
if (1000 * year + 100 * month + day > 1000 * dst.year + 100 * dst.month + dst.day) return 1;
else if (equals(dst) == 1) return 0;
else return -1;
}
int get_gap(const MyDate& dst);
int gap(const MyDate& dst);
MyDate();
MyDate(int y_, int m_, int d_);
};
其中,compare是判断两个日期之间前后关系的函数;if_leap_year是判断闰年的函数;而gap和get_gap是求日期间隔的函数;
正文
日期间隔计算
自己初中时候接触过一点计算机竞赛,在学了基础语法后,一天闲来无事(其实是写作业写不下去了想玩电脑)写了一段面向过程的日期间隔计算程序,其本质上是分段计算天数并求和,即先判断两个日期是同年还是跨年,如若同年判断是否同月,进而分情况讨论并计算。今天做作业时,想到曾经写的这个小程序,上网搜了一下,似乎没看到我这种笨方法的,于是就想着发出来纪念一下。
为了方便获得每月的天数,我写了一个带有两个参数的days(int, int)函数,它会返回指定年月的总天数;
int MyDate::days(int y, int m)
{
int days[13] ={0,31,28,31,30,31,30,31,31,30,31,30,31};
if (m==2)
{
if(isLeap(y)) return 29;
else return 28;
}
else return days[m];
}
以下是封装在类里面的gap()函数,思路写在了代码片的注释里面:
其中cy,cm,cd的c表示“current”,意为“当前”;同理e为“expected”,用以代指“目标”日期。
另:如果觉得每次调用days()函数会引起过多的开销,可以考虑在gap()函数里设置一个存放每月天数的数组,然后根据需要在年份变化时更新2月天数相关的数据。
int MyDate::gap(MyDate& dst)
{
int cy = year, cm = month, cd = day;
int ey = dst.year, em = dst.month, ed = dst.day;
int sum = 0;
if (compare(dst)==1)
{
swap(cy,ey); swap(cm,em); swap(cd,ed);
}
if (ey == cy)
{
if (em == cm) sum = ed - cd; //如果同月,则日期相减即得
if (em > cm) //如果不同月
{
sum += (days(cy,cm) - cd); //则该月剩余天数,
sum += ed; //加上目标月已过天数,
for (int i = cm + 1; i < em; i++) sum += days(cy,i);
//再加上中间的间隔,即得sum!
}
}
if (ey > cy)
{
//不同年可以分成五个阶段
//1. 累加当前月的剩余天数
sum += (days(cy,cm) - cd);
//2. 累加当前年内,当前日期到年底之间所有整月的天数
for (int i = cm + 1; i <= 12; i++) sum += days(cy,i);
//3. 累加之间年份的整天数
for (int i = cy + 1; i < ey; i++)
{
if (isLeap(i)==1) sum += 366;
else sum += 365;
}
//4. 累加目标年到目标日期之间的所有整月的天数
for (int i = 1;i < em; i++) sum += days(ey, i);
//5. 累加目标月的天数
sum += ed;
}
return sum;
}
注:调试的时候发现自己之前写的代码有一个小问题;这是根据相同思路重新写的版本,相比较之下更清晰易懂一些。
附赠一个调试用的main()函数:
int main()
{
MyDate now(2020,3,12);
MyDate dst(2020,3,12);
for (int i=0; i<2000; i++)
{
now.print();
cout<<" 和 ";
dst.print();
cout<<" 之间间隔 ";
cout<<now.gap(dst);
cout<<" 天。 ";
dst.tomorrow();
system("pause");
cout<<endl;
}
return 0;
}
可见这段面向过程的程序写起来有点长(但是运行速度很快);写完作业后受到tomorrow()的启发——为什么不把“数日子”算法发挥到极致呢?再想到equals(),发现两者很好的配合在了一起,只需要一个while语句就可以完成迭代操作!于是便有:
int MyDate::get_gap(const MyDate& dst)
{
int gap = 0;
MyDate now(year,month,day);
MyDate tgt(dst);
if (compare(dst) == 1)
{
while (tgt.equals(now)==0)
{
tgt.tomorrow();
gap++;
}
}
else
{
while (now.equals(tgt)==0)
{
now.tomorrow();
gap++;
}
}
return gap;
}
注:加入的if,else语句是为了考虑传入日期早于当前日期的情况——那样,则需要反过来执行这个迭代过程。
在main()函数中调用get_gap()和gap()函数:
int main()
{
MyDate now(2020,3,12);
MyDate dst(2099,3,12);
cout<<now.get_gap(dst)<<endl;
cout<<now.gap(dst)<<endl;
return 0;
}
当然,第二种方法肯定远远没有第一种方法快,但单次的计算时长很难察觉到;另外,第二种写起来十分简单。
日期后推1天的函数:tomorrow
这里介绍一下tomorrow函数,这个函数如若采用“进位”的思想,可以避免复杂的if语句的讨论:即
先把日期+1,看日期是否超过了当前月份的最大天数;
如果是,则把月数+1,日期改为1日;
再看月份是否超过了12,如果是,则把年份+1,月份改为1月;
如此便能实现“tomorrow”的功能:
void MyDate::tomorrow()
{
int days[13] ={0,31,28,31,30,31,30,31,31,30,31,30,31};
if(if_leap_year()) days[2]=29;
else days[2] =28;
day++;
if (day > days[month]) {
day = 1;
month++;
if(month > 12)
{
month = 1;
year++;
}
}
}
其他的成员函数
下面是其他成员函数的主体:
void MyDate::set(int y_, int m_, int d_)
{
year = y_; month = m_; day = d_;
}
void MyDate::set(const MyDate& date)
{
year = date.year;
month = date.month;
day = date.day;
}
void MyDate::print()
{
cout<<year<<"年"<<month<<"月"<<day<<"日";
}
int MyDate::equals(const MyDate& date)
{
return (date.year == year && date.month == month && date.day == day);
}
MyDate::MyDate()
{
year = 1970;
month = 1;
day = 1;
}
MyDate::MyDate(int y_, int m_, int d_)
{
year = y_; month = m_; day = d_;
}
另一种思路写成的tomorrow
这里放一下用if条件句写的tomorrow函数;其实本质上是和上面的tomorrow函数一样的,都是分类讨论;有些同学会有不同的写法,写法很多,这里不作展示了。
void tomorrow()
{
if(month==2) //feburary
{
int flag;
if(isleap()) flag = 1;
else flag = 0;
day++;
if(day>(28+flag)) { month++; day=1; }
}
else if((month<8 && month%2==1)||(month>=8&&month%2==0)) // 1,3,5,7,8,10,12
{
day++;
if(day>31) { day=1; month++; }
}
else //if(month==4||month==6||month==9||month==11) //4,6,9,11
{
day++;
if(day>30) { day=1; month++; }
}
if(month>12) { month=1; year++; }
}
值得注意的是,使用if条件语句需要注意逻辑的问题。
有一个很容易出错的地方,而且不容易发现,即在讨论完某种情况后,month++ (月份发生了变化),但是没有return这个函数,因此得以继续进入了接下来的情况中。
举个例子:2013年11月30日,运行tomorrow,第一对if语句执行后变为12月1日,接着,便会满足条件进入下面的判断分支,变成12月2日。
其实聪明的你看到这里应该已经明白问题在哪儿了。这种情况一般是因为使用了if条件并列的结构,换句话说,如果这些条件画出来,不是一分多的情形,而是像依次访问多个门槛(奇妙的比喻),而问题又在于,一开始进入某个条件后,与条件判断相关的数值发生了变化,使得其又得以进入另外的分支。尽管程序运行过程是线性的,但这样的可能依旧存在。(联想一下switch语句后面的break;)
希望看到这里的读者在遇到类似的情况时留个心眼,可以使用return语句,或者改写if语句的结构。
展示功能用的 main 函数
最后是实现老师作业要求(展现程序功能用的)的main()函数:
int main()
{
//MyDate d1(2015,12,31);
//MyDate d2(d1);
MyDate d1,d2;
d1.set(2015, 12, 31);
d2.set(d1);
d1.print();
cout << endl;
d2.print();
cout << endl;
cout << "d1.equals(d2)?" << d1.equals(d2) << endl;
//equals 判断d1和d2是否相等,当年、月、日全相等时返回1;否则返回0
d1.print();
cout << "的明天是:";
d1.tomorrow();
d1.print();
}