1.概述
很多系统都会内置一个美观且实用的日历程序,现在的日历一般都使用公历,这次课程设计便会使用c语言制作一个命令行的日历输出程序,要实现题目中的图片效果,需要具体到每个月的每一天所对应的星期,这就需要设计算法来实现需要的功能。
2.需求分析
要知道每个月的具体日历,首先要选定一个起始年份,在Java中有Date类和LocalDate类提供给程序员使用,使用的时候不必知道其中的具体实现,但其中的时间是用距离一个固定时间点的毫秒数(可正可负)表示的,这个时间点就是所谓的纪元。而到了C中没有这样方便的类(库)可供调用,需要我们自己来实现,题目1中给到的是1940-2040年的范围,所以这个纪元便是1940年。1940年1月1日是星期一,之后便算出1940年到输入年份的总天数,通过算出间隔的天数再取模7,它的余数+1便是当年1月1日的周几。之后就变得明了了起来,只要跟在第一天之后铺排日期序号就可以了。我们还需要一个函数来判断每个月有多少天,按照“一月大,二月平……”的口诀来建立一个函数来返回请求月份的天数。循环输出日期截止到当月总天数即可。由于数据量小,操作简单,性能需求较小,只需注意避免不必要的重复函数调用和循环造成额外的性能开销即可。
3.系统设计
根据上述的问题分析,声明whetherLeapYear(), getDaysOfTheMonth(), intervalDays(), printMonth(),printSpecifiedMonth() 五个自定义函数,其中:
- main():程序入口函数,接收用户输入的年份,判断输入合法之后(年份满足范围),调用 printMonth() 函数输出日历,不合法则清空输入缓冲区来让用户重新输入;
- whetherLeapYear():根据闰年的定义判断是否为闰年,并返回结果,闰年返回1,非闰年返回0(c语言中没有布尔值,而是使用int来判断,0以外的数字都为真);
- getDaysOfTheMonth():获取指定月份的天数,根据不同的月份返回不同的天数,其中,2月会调用wheterLeapYear()函数判断是否闰年来返回28或29;
- intervalDays():返回从1940年到输入年份间隔的天数,这里也要调用whetherLeapYear()来返回366或者365;
- printMonth():输出指定年份的日历,调用intervalDays()函数并取模7,它的余数+1便是当年1月1日的周几,此后循环打印日期,调用 getDaysOfTheMonth() 函数获取月份的天数来作为每个月的截止日期。其中每个月开始日期的不同需要多加注意,也是这个函数中比较费功夫的地方。此外,还需要注意用户输入可能会出错的地方,例如输入年份为-1或者超过题目范围的数据,都需要对用户进行提示并重新读入年份。
- printSpecifiedMonth():输出指定年份月份的日历,调用intervalDays()函数来得到与纪元间隔的天数,再调用getDaysOfTheMonth()函数来计算出当年元旦与所给月份之间的间隔天数一并增加给startCount,此后取模7,便和printMonth()中的输出日历大同小异了,注意格式化输出即可。
4.软件详细设计、编码
#include <stdio.h> int static inputYear; int whetherLeapYear(int year){ if( (year %4 == 0 && year %100 != 0) || year % 400 == 0) return 1; else return 0; } int getDaysOfTheMonth(int month){ if(month == 1 || month == 3 || month == 5 || month == 7 || month == 8 || month == 10 || month == 12){ return 31; }else if(month == 4 || month == 6 || month == 9 || month == 11){ return 30; }else if(month == 2){ if(whetherLeapYear(inputYear)){ return 29; }else{ return 28; } } } int intervalDays(){ //返回从1940年到输入年份间隔的天数 int daysCount = 0; for(int i = 1940; i<inputYear; i++){ if(whetherLeapYear(i)){ daysCount += 366; }else{ daysCount += 365; } } return daysCount; } void printMonth(){ int startCount = intervalDays()%7; //startCount就是day of the week的意思,应该在星期几开始打印 //通过算出从1940年到输入年份间隔的天数再取模7,它的余数+1便是当年1月1日的周几 for(int i = 1;i<=12;i++){ int totalDaysOfTheMonth = getDaysOfTheMonth(i); if(i>1) printf("\n"); printf("-----------------MONTH%d-----------------\n",i); printf("%s\n","MON TUE WED THU FRI SAT SUN"); for(int blankCount = 0; blankCount<startCount;blankCount++){ printf(" ");//定位到该月1日的初始星期几 } for(int j = 1;j<=totalDaysOfTheMonth;j++){ printf("%-6d",j); startCount++; if(startCount == 7){ startCount = 0; if(j==totalDaysOfTheMonth){ break;//为了防止某月1日正好就是1号从而产生两个空行的情况(例如2004年第10月) } printf("\n"); }//输出第j个月的月历 } } } void printSpecifiedMonth(int month){ int startCount = intervalDays(); for(int i =1;i<month;i++){ startCount += getDaysOfTheMonth(i); } startCount = startCount % 7; printf("-----------------MONTH%d-----------------\n",month); printf("%s\n","MON TUE WED THU FRI SAT SUN"); for(int blankCount = 0; blankCount<startCount;blankCount++){ printf(" ");//定位到该月1日的初始星期几 } for(int j = 1;j <= getDaysOfTheMonth(month);j++){ printf("%-6d",j); startCount++; if(startCount == 7) { startCount = 0; printf("\n"); if (j == getDaysOfTheMonth(month)) { break;//为了防止某月1日正好就是1号从而产生两个空行的情况(例如2004年第10月) } } } } int main(){ int mod = 0; int month; int temp;//用于清空输入缓冲区 printf("邦邦咔邦!欢迎使用日历程序╰(*°▽°*)╯\n" "1.输入你想要在屏幕上显示当年日历的年份(范围1940-2040年)\n" "2.输入你想要在屏幕上显示某年某月的日历(范围1940-9999年)\n" "输入功能前的序号来选择功能:"); scanf("%d",&mod); loop: switch(mod){ case 1: printf("请输入年份:"); scanf("%4d",&inputYear); //由于scanf函数处限制了4个宽度,可以预防缓冲区溢出攻击 while(inputYear<1940||inputYear>2040) { printf("输入年份错误,范围在1940-2040年!"); printf("\n输入你想要在屏幕上显示当年日历的年份:"); scanf("%4d", &inputYear); } printf("\n"); printf("|======The Calender of Year %d ======|\n" ,inputYear); printMonth(); printf("\n|=======================================|"); break; case 2: printf("请输入年份:"); scanf("%4d",&inputYear); //由于scanf函数处限制了4个宽度,可以预防缓冲区溢出攻击 while(inputYear<1940||inputYear>2040) { printf("输入年份错误,范围在1940-9999年!"); printf("\n输入你想要在屏幕上显示当年日历的年份:"); scanf("%4d", &inputYear); } printf("请输入月份:"); scanf("%d",&month); while(month<1 || month>12) { printf("输入月份错误,一年有几个月就不用我说了吧!"); printf("\n输入你想要在屏幕上显示的月份:"); scanf("%4d", &month); } printf("|======The Calender of Year %d ======|\n" ,inputYear); printSpecifiedMonth(month); break; default: printf("输入功能序号错误,请输入功能前的序号:"); while((temp = getchar()) != '\n');//清空输入缓冲区,防止错误输入刷屏 scanf("%d",&mod); goto loop; } }
5.问题分析与解决
在编写代码的过程中,我们也注意到了一些需要注意的细节和问题:
- 我们发现输入的年份需要进行范围的限制,否则程序可能会崩溃或者输出错误的结果。例如输入年份为-1会导致后续算法出错,或者假设输入一个极大的数据会导致缓冲区溢出,为了解决这些可能出现的问题,我在scanf中都进行了一些限制,这被称为程序的防御性设计,清空输入缓冲区也是一个要注意的地方。这里在程序中也得到体现。
- 我们需要注意输出格式的问题,例如月历的对齐问题等。在这里我自己用printf输出下划线以及自定义的函数printMonth()中的判断来对其输出月份格式和示意图保持一致。
- 此外,我们还需要注意在循环中对变量的赋值不要出错,我个人习惯是0base,在月份中因为涉及到实际对象因此从1开始,此外条件语句的判断也要注意,这些都可能影响到程序的正确性和效率。
6.程序运行
7.总结
本次课程设计通过一个命令行小程序实现了显示指定年份的日历或指定年份和月份输出日历的功能。在编写代码的过程中,我也发现了自己的不足和问题。经过分析后得到了有效的解决。此外,我们还深入理解了C语言的语法和程序设计的思想,提高了自己对于面向过程程序的编程能力。
除此之外,该程序是在Winodws上使用mingw编译器套件和visual studio code编写的,经过测试也可以在Ubuntu-20.04-lts上使用gcc编译器编译并成功运行,实现预期功能。体现了该程序较好的可移植性。 总而言之,本次课程设计让我们更深入地了解了C语言的程序设计和开发过程,同时也提高了我们的实践经验。通过不断的练习和探索。相信我们自己会变得越来越优秀。
MMD,无语死了,快提交了让我们换模板写,打印两次都白打印了(这是新模板的内容)