“ 输出1900年之后任意一年的日历 ”完整编程思路!

 采用自顶向下、逐步求精的设计方法。这种分解问题的思想很重要,可以帮助我们更好地分析疑难问题,特别是对于工程项目,较多的功能和耦合会让问题无从下手,只有采用自顶向下、逐步求精、分而治之的思想才能很好地解决这类问题!

题目:根据用户输入年份,显示该年12个月的日历,并对应具体的星期几。

分析:首先根据问题处理的思路,可以分为三个过程:UserInput,Process,Print。UserInput主要用于接收用户输入的年份,并将输入传给Process。Process接收用户输入的年份,根据基准时间(1990年1月1日是星期一)计算输入年份中每一月中每一天对应的是星期几。这里面存在这样几个问题,第一个,需要计算当前年份每个月的日历,然后循环12次即可,每个月的月历需要计算本月之前的月份到1990年之间的天数,再加上本月的天数。由于2月天数的特殊性,所以还需要进行闰年的判断。第二个问题是如何计算一个月的月历。每月的第一天可以通过对7取余数得到对应的是周几,然后依次往后计算,以该月份天数为循环次数。值得注意的一点是每到周六,要进行换行。

程序设计过程:

1.      自顶向下

我们从主程序开始,考虑程序要做什么?一般来说,用户只需要输入年份,并按回车即可,就可以显示该年的日历。在用户输入之前,最好能给用户一些提示性的语句作为Instruction。

所以,void GiveInstruction(void)函数就会得到,函数语句如下:

voidGiveInstruction(void)         //tell theuser how to do

{

printf("This program displays a calendarfor a full\n");

printf("year. The year must not be before1900.\n");

}

这种方式会让用户觉得程序更为友好。下面就需要逐步精化,逐步精化的原则是:一旦你有了某个程序的概要描述,就应该在此结束,并把它写下来,调用其他函数或者过程,以此来表示那些还要继续细化的程序。

这里需要注意的是“过程”指的是不返回值而是起到某些作用的函数,我们称之为过程。

由于函数的一般写法是:

返回值的类型或者无返回值函数名称(形参类型1 形参名1,形参类型2 形参名2……)

所以可以根据函数的一般写法确定过程函数的写法,由于过程函数没有返回值,则void。过程函数的写法如下:

void 函数名称(形参类型1 形参名1,形参类型2 形参名2……)

我们继续回到自顶向下的设计中,根据刚才的描述,主程序可以写成:

main()

{

         intYear;

         GiveInstruction();

         Year=GetYearFromUser();

         PrintCalendar(Year);

}

主函数中包含的三个子函数很容易理解,其中核心函数是PrintCalendar(Year),该函数要能根据用户输入的年份,输出日历。下面采用逐步精化来分解PrintCalendar(Year)函数。

2.      逐步精化PrintCalendar(Year)

虽然上述三个函数都需要编写,但是我们直接进入问题的核心,根据核心函数来布置或者安排其他附属函数的功能。从main的调用来看,PrintCalendar(Year)的原型是

void PrintCalendar(int Year);

我们在抽象级别考虑这个函数,考虑它需要做什么。它需要打印12个月的日历,好了,到了这里,下一步的想法有了,使用for循环再加上每个月的日历函数就可以实现。代码如下:

voidPrintCalendar(int year)

{

int month;

for(month=1;month<=12;month++)

{

           PrintCalendarMonth(month,year);//print the every month is the essence of program

           printf("\n");

}

}

下面的问题就转化成了PrintCalendarMonth(month,year),我们注意这个函数具有两个输入month,year。year来自于用户输入,而month表示一年的12个月。此时显然闰年的问题出现了,在考虑二月份天数的时候,就必须考虑闰年。闰年函数我们已经非常熟悉,如下:

int IsLeapYear(int year) //judge the inputnumber is leapyear or not

{

         return((year%400==0)||((year%4==0)&&(year%100!=0)));

}

闰年的问题考虑清楚之后,就要回头注意日历的输出格式。

由于每个月我们都需要这样输出,所以只要能实现一个,下面的就只是“量”的问题了。这个日历的前两行也相当容易,比较麻烦的是数字和上面的weekday要对上。这里面存在三个问题,第一个,每个月的1号前面的空格要正好合适;第二个,每个月的周六要换行;第三个,每个数字之间有间隔。

先解决第一个问题,由于每个月的1号对应的weekday可能不同,则前面的空格数也不同。所以,我们就要知道每个月1号对应的weekday。假设我们知道每个月的1号对应的weekday,则空格函数就可以写出来了。如下:

voidIndentFirstLine(int weekday) //The spacebar of the first row of every month

{

         int i;

         for(i=0;i<weekday;i++)

         {

                   printf("   ");

         }

}

下面就需要知道每个月的1号对应的是哪个weekday,即intFirstDayOfMonth(month,year)函数。这里就需要一个具体的参照,比如我们知道历史上某年某月某日对应的是周几,就可以根据天数和月数推算出来用户输入的年份里面某个月的第一天是哪个weekday。例如,1900年1月1日是星期一。从这天开始,按是否是闰年,每年加365或者366天。对于当年要处理的月份之前的月,加上这个月的天数。用取模运算计算。比如,我们要计算2018年2月,就可以先按照年计算到2018年1月的第一天是周几,只需要(weekday+365/366)%7,然后再按照月计算到2月,只需要(weekday+month(I,year))%7。由于1900年1月1日是星期一,所以weekday初始值是Monday。intFirstDayOfMonth(month,year)函数代码如下:

intFirstDayOfMonth(month,year) //know first day of every month is which one ofweekday

{

         int weekday,i;

         weekday=Monday;

         for(i=1900;i<year;i++) //firstaccording to the year

         {

                   weekday=(weekday+365)%7; // %can get the weekday

                   if(IsLeapYear(i))  weekday=(weekday+1)%7;

         }

         for(i=1;i<month;i++) //secondaccording to the month

         {

                   weekday=(weekday+MonthDays(i,year))%7;

         }

         return weekday;

}

这里有一个技巧,由于我们使用取模运算,因此可以设定weekday代表的数字,这样进行取模运算时会非常方便。如下:

#defineSunday 0      //easy to qiumo(%)

#defineMonday 1

#defineTuesday 2

#defineWednesday 3

#defineThursday 4

#defineFriday 5

#defineSaturday 6

由于intFirstDayOfMonth(month,year)函数用到了统计每个月天数的函数MonthDays(i,year),这个很简单,因此我们也顺便把这个函数写在下面:

intMonthDays(int month,int year)

{

         switch(month)

         {

                   case 2:

                            if(IsLeapYear(year))

                                     return 29;

                       return 28;

                   case 4: case 6: case 9: case11:

                            return 30;

                   default:

                            return 31;

         }

}

这样的话,第一个问题——每个月的1号前面的空格要正好合适,就可以通过回调每月1号的weekday解决。

下面就要解决换行和间距的问题。换行的问题并不难,以每个月的天数作为for条件,让每个月1号的weekday加1(define的好处是让取模之后0~6范围内的数字和代表的weekday可以随时转换)然后对7取模。当weekday==Saturday时,换行。这里Saturday也可以换成6,define的好处在这里展现无遗!这里由于每个月的日历之间也要换行,因此换行的逻辑以一定要弄清楚。显然当没有到本月的最后一天时,每逢周六要换行,换完行之后weekday加1变成Sunday;当本月最后一天是Saturday时,则由于已经执行加1操作,故下个月1号的Sunday自然会换行;当本月最后一天不是Saturday时,则由于仍在本月的日历中,不能换行,但由于下个月的日历不能贴在一起,则应该在本月日历程序的外面实行换行操作。这部分需要细心调试。

voidPrintCalendarMonth(int month,int year)

{

         int weekday,nDays,day;

         printf("   %s %d\n",MonthName(month),year); //thehead of the monthly calendar

         printf(" Su Mo Tu We Th FrSa\n");

         nDays=MonthDays(month,year); //get thedays of month,which will call IsLeapYear function

         weekday=FirstDayOfMonth(month,year);//know first day of every month is which one of weekday

         IndentFirstLine(weekday); //print thespacebar before the first day

         for(day=1;day<=nDays;day++)

         {

                   printf(" %2d",day);//printf the day

                   if(weekday==Saturday)printf("\n"); 

                   // saturday can be placedwith 6,this will fully display the advantages of "define"

                   //if saturday,line feed;ifnot,the final weekday will line feed

                   weekday=(weekday+1)%7;//calculate next day

         }

         if (weekday!=Sunday)printf("\n");

         //if not sunday,line feed;if sunday,theformer weekday is saturday,has already line feed

}

这里MonthName(month)函数需要注意,由于该函数返回的是字符串,C语言没有返回字符串的string function(int month),好像C++有,处理这个问题通常的做法是使用函数参数传递指针或者申请堆空间,使用完后free掉。这里,使用的是指针的方法。

char*MonthName(int month) //transmit the string of months

{

         switch(month)

         {

                   case 1:return("January");

                   case 2:return("February");

                   case 3:return("March");

                   case 4:return("April");

                   case 5:return("May");

                   case 6:return("June");

                   case 7:return("July");

                   case 8:return("August");

                   case 9:return("September");

                   case 10:return("October");

                   case 11:return("November");

                   case 12:return("December");

                   default:return ("Illegalmonth");

         }

}

3.      组装

现在函数的主体已经有了,下面介绍写注释、修改和测试了。完整代码如下:

输出1900年之后的日历

猜你喜欢

转载自blog.csdn.net/zhanshen112/article/details/79846825