(1)实验楼C语言万年历

背景:以前学习C语言也没做什么东西出来,之前看哈佛的CS50课程倒跟着课程进度写了一些C语言作业题。在看CS50的过程中,写了一些python和html以及css,对比发现其他语言的反馈效果要比C以及C++要好,更容易做出好看的成果出来,相比之下更容易得到激励并形成正反馈。后来看北京大学程序与设计实习课程,这么久写代码倒一直在做题,以前做作业题,后来刷OJ题,学数据结构。最近感到遇到瓶颈了,光做题恐怕很难坚持下去了,需要给自己些看得到的成果,我打算把自己写的一些有趣的小东西或算法写成一个系列,就当给自己一个纪念以及方便自己回看。

这几天跟着实验楼写了一个简单的C语言小项目—万年历(https://www.shiyanlou.com/courses/126),虽然很简单,但算法很精妙,值得分析并记录一下,在写的过程中对部分代码加入自己的注释以方便理解。
以下是源代码:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/*
设计思路

问题来了。设计一个万年历的思路是什么呢,其实很简单,
万年历的主要功能是提供给使用者查询日期时间。那么万年历具有什么特点呢?

(1)平年365天(52周+1天),闰年366天(52周+2天),平年2月28天,闰年2月29天。
     由于公元1月1日设为星期六,故3月1日为星期三。为使算法达到最简,故本算法以“星期”为计算单位,且选3月1日为基月。

(2)每400年整一闰,或每4年且不为百年的一闰,即凡能被400整除,或不能被100整除但能被4整除的年份为润年。

(3)每 4年(3个平年+1个闰年)共208周+5天 每百年共100(208周+5天)-1天=5217周+5天
     每400年共4(5217周+5天)+1天(整400年闰)=20871周+0天,即每400年一个轮回。

搜索到的解释:非整百的年份除以4,或者整百的年份除以400,若不能整除的,即为平年。

*/
int main(int a, char **date)
{
    int year = 0, month = 0, day = 0, week;
    int d, i, dm, dy, m2;
    char WEEK[9];

    if (a == 1)
    {
        printf("\n ERROR! you forget to enter the date you want to view\n");
        exit(0);
    }
    i = 0;
    d = -1;
    while (date[1][i]) // 遍历传入的参数日期,计算出year,month,day
    {
        if ((date[1][i]=='/' || date[1][i]=='.') && d==-1)
        {
            d = 0;
            i++;
            continue;
        }
        if ((date[1][i]=='/' || date[1][i]=='.') && d == 0)
        {
            d = 1;
            i++;
            continue;
        }
        if (d == -1)
        {
            year = year*10 + (date[1][i] - '0');
        }
        if (d == 0)
        {
            month = month*10 + (date[1][i] - '0');
        }
        if (d == 1)
        {
            day = day*10 + (date[1][i] - '0');
        }
        i++;
    }
    if (month<1 || month>12) // 若月份传入错误数字
    {
        printf("\n ERROR! the entered MONTH is invalid\n");
        exit(0);
    }
    if (year == 2000)
    {
        dy = 0; // 年引起的星期差为0个
        m2 = 1; // 2月引起的星期差为1个,2000年闰年则2月有29天=4*8+1
        goto la_100;
    }
    if (year > 2000)
    {// d统计目标年与2000之间闰年的个数
        d = (year-1-2000)/4 - (year-1-2000)/100 + (year-1-2000)/400 + 1;
    }
    else
    {       // 不为百年且能被4整除            // 能被400整除
        d = (year-2000)/4 - (year-2000)/100 + (year-2000)/400;
    }
    dy = (year-2000) + d; // 该年1月1号到2000年1月1号的“星期差”
    if ((year%4==0 && year%100!=0) || (year%100==0 && year%400==0)) // 是闰年
    {
        m2 = 1; // 二月引起的星期差
    }
    else
    {
        m2 = 0;
    }

la_100:
    // 该月以前的月所引起的“星期差”
    switch(month)
    {
        case 1: dm = 0; month = 31; break; // month在此存放月天数
        case 2: dm = 3; month = d==1?29:28; break;
        case 3: dm = 3 + m2; month = 31; break;
        case 4: dm = 6 + m2; month = 30; break;
        case 5: dm = 1 + m2; month = 31; break;
        case 6: dm = 4 + m2; month = 30; break;
        case 7: dm = 6 + m2; month = 31; break;
        case 8: dm = 2 + m2; month = 31; break;
        case 9: dm = 5 + m2; month = 30; break;
        case 10: dm = m2; month = 31; break;
        case 11: dm = 3 + m2; month = 30; break;
        case 12: dm = 5 + m2; month = 31; break;
    }
    if (day<0 || day>month)
    {
        printf("\n ERROR! the entered DAY is invalid\n");
        exit(0);
    }
    week = (dy + dm + day - 1 + 6) % 7;
    if (week < 0)
    {
        week += 7;
    }
    if (day < 0)
    {
        switch (week)
        {
            case 0: strcpy(WEEK, "SUNDAY"); break;
            case 1: strcpy(WEEK, "MONDAY"); break;
            case 2: strcpy(WEEK, "TUESDAY"); break;
            case 3: strcpy(WEEK, "WEDNESDAY"); break;
            case 4: strcpy(WEEK, "THURSDAY"); break;
            case 5: strcpy(WEEK, "FRIDAY"); break;
            case 6: strcpy(WEEK, "SATURDAY"); break;
            default: break;
        }
    }
    else
    {
        week = ++week%7;
        printf("\n the calendar of this month as following\n");
        printf("\n *********************************\n");
        printf(" SUN  MON  TUE  WEN  THU  FRI  STA\n");
        for (i = 0; i < week; i++)
        {
            printf("     ");
        }
        for (i = 1; i <= month; i++)
        {
            printf(" %2d  ", i);
            week++;
            if (week%7==0 && i!=month)
            {
                printf("\n");
            }
        }
        printf("\n**********************************\n");
        printf("\n OK!\n");
    }
    return 0;
}

程序是以Linux命令行形式运行的,将日期以命令行参数传入,然后计算出年月日。

实验楼的步骤中对算法的解释较为简略,我将自己的理解的思路阐述一下:万年历需要解决的主要问题是,确定日期的星期。此算法以2000年1月1日(星期六)为基点,计算其他日期与基点相差的星期数,如果相差一整周则说明星期相同。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值