C++之初识“类”:写一个关于日期的类(附两种日期间隔计算)

一个小目录

前言

正文 

日期间隔计算

日期后推1天的函数:tomorrow

其他的成员函数

另一种思路写成的tomorrow

展示功能用的 main 函数


前言

开始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判断闰年的函数;而gapget_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();
}

猜你喜欢

转载自blog.csdn.net/henry_23/article/details/104832525