程序设计报告(2009/2010学年第一学期)题目:万年历查询系统专业电气信息工程 _学生姓名王XX班级学号 B080510XX指导教师张台国指导单位计算机软件基础教学中心日期 2009.10.30万年历查询系统一、课题内容和要求万年历在现在日常生活工作学习等众多方面被广泛使用。
本课题旨在以“公元2005年10月20日星期四”为已知条件编写一个基本的万年历查询系统,可以查询公元元年至公元9999年间任一年的全年日历,以双月并排显示的方式输出,并要求对用户输入的待查询数据有较强的错误检查能力。
通过该课题,进一步加深对C++面向对象特性的认识,更加深层的理解了类的封装意义,较为熟练的掌握了函数调用、文件读写、对象操作等知识。
二、需求分析功能框架图:(图2-1功能详解:(1)提供界面友好的操作菜单:输出欢迎信息和操作菜单,用于提示用户选择需要的操作。
根据用户输入的选项实现相应的功能。
(2)进行数据的输入和错误检查:要求用户输入待查询数据,并对输入的数据执行错误检查,如果输入有误,则提示用户重新输入,直到输入正确。
(3)计算待求年份元旦的星期数:通过求出待求年份与2005年元旦相距的总天数求出待求年份元旦的星期数,为准确输出万年历奠定基础。
(4)打印日历的标题和日期:可以将万年历以双月并排的形式输出,一方面使得万年历看起来美观;另一方面,也确保了万年历日期与星期的准确对应。
(5)选择菜单:供用户选择需要继续的操作,具有较强的输入数据错误检查能力;同时实现了程序操作的循环性。
三、概要设计1、主要类:class Data{private:int Week[12]; //用于储存每个月1号为星期几int year; //用于存放用户输入的年份public:Data(); //类Data的构造函数int Getyear(); //获取用户输入的年份void FirstDayOfYear(int); //求出任意一年元旦的星期数void FirstDayOfMonth(int); //求出任意年份的每个月一号的星期数int IsLeapyear(int); //判断是否为闰年void PrintYear(int); //显示年份图案void PrintTitle(int); //输出每个月日历的标题部分void PrintMonth(int); //打印月份日历void PrintForm(int); //使日历按照每两个月并排的方式输出void ChooseMenu(); //显示选择操作界面,实现程序的循环void PrintThanks(); //退出程序时显示结束信息};2、主要函数流程图:(1)类的构造函数:用于给类的对象赋值。
提示用户输入数据,并具有较强的错误输入数据检查功能。
(参见图3-1)(2)闰年判断函数:判断哪些年份为闰年。
(图3-2)(3)求待求年份元旦星期函数:求出待求年份元旦距离2005年元旦的总天数,进而求出待求年份元旦的星期数。
(图3-3,其中Week[0]代表元旦星期数)四、程序源码#include<iostream>#include<fstream>#include<iomanip>#include<string>using namespace std;ofstream fout("万年历_执行结果.txt"); //文件输出到“万年历_执行结果.txt文件中”const int Year=2005; //const符号常量,代表2005年int MonNum[12]={31,28,31,30,31,30,31,31,30,31,30,31}; //储存每月的天数void PrintWelcome(); //顶层函数,用于开始执行程序时输出欢迎信息class Data{private:int Week[12]; //用于储存每个月1号为星期几int year; //用于存放用户输入的年份public:Data(); //类Data的构造函数int Getyear(); //获取用户输入的年份void FirstDayOfYear(int); //求出任意一年元旦的星期数void FirstDayOfMonth(int); //求出任意年份的每个月一号的星期数int IsLeapyear(int); //判断是否为闰年void PrintYear(int); //显示年份图案void PrintTitle(int); //输出每个月日历的标题部分void PrintMonth(int); //打印月份日历void PrintForm(int); //使日历按照每两个月并排的方式输出void ChooseMenu(); //显示选择操作界面,实现程序的循环void PrintThanks(); //退出程序时显示结束信息};void main(){static int i=0; //静态全局变量i,控制PrintWelcome()只输出一次if(!i++)PrintWelcome(); //调用顶层函数,输出欢迎信息Data data; //定义类Data的对象dataint yy=data.Getyear();data.PrintYear(yy);data.FirstDayOfYear(yy);data.ChooseMenu();}/*函数功能:判断输入的年份是否为闰年,如果是则返回1,否则返回0函数参数:整型变量mm,存放输入的年份函数返回值:闰年则返回1,否则返回0*/int Data::IsLeapyear(int mm){if(((mm%4==0)&&(mm%100!=0))||(mm%400==0))return 1;elsereturn 0;}/*函数功能:构造函数,用于提示用户输入要查询的年份,如果输入的不是数字或者输入的数字不在0-9999之内,则提示输入错误,并要求用户重新输入函数参数:无函数返回值:无返回值*/Data::Data(){char ch; //定义字符型变量ch,存放错误输入字符cout<<"\n 请输入要查询的年份:";while(1) //对输入值错误处理{cin>>year;scanf("%c",&ch); //将数字后输入的非数字字符读取到ch中if(year<0||year>9999||ch!='\n') //如果输入的年份不在0-9999之列或者含有非数字字符则报错{cout<<" 输入错误,您所输入的年份必须在0~9999之间!"<<endl<<" 请重新输入要查询的年份:";cin.clear();cin.ignore(1000,'\n');}else break;}}/*函数功能:获取用户输入的待查询年份函数参数:无函数返回值:用户输入的待查询年份*/int Data::Getyear(){return year;}/*函数功能:当用户输入正确的可查询年份时,打印出由“*”所组成的待查询年份的数字图案函数参数:整型变量year,存放输入的待查询年份函数返回值:无返回值*/void Data::PrintYear(int year)int m=year/1000; //m代表待查询年份的千位数字int n=(year/100)%10; //n代表待查询年份的百位数字int p=(year/10)%10; //p代表待查询年份的十位数字int q=year%10; //q代表待查询年份的各位数字int num[4]={m,n,p,q}; //数组num用于存放年份的千、十、百、个位数/*3维数组ss用于存放由*组成的0~9这10个数字的图案,其中5表示行,7表示列*/char ss[5][10][7]= {"***** "," * ","***** ","***** ","* * ","***** ","***** ","***** ","***** ","***** ", "* * "," * "," * "," * ","* * ","* ","* "," * ","* * ","* * ","* * "," * ","***** ","***** ","***** ","***** ","***** "," * ","***** ","***** ","* * "," * ","* "," * "," * "," * ","* * "," * ","* * "," * ","***** "," * ","***** ","***** "," * ","***** ","***** "," * ","***** ","***** ",};cout<<"\n =====================公元"<<year<<"年日历====================="<<endl;fout<<"\n =====================公元"<<year<<"年日历====================="<<endl;/*实现3维数组图案的输出*/for(int g=0;g<5;g++){cout<<" ";fout<<" ";for(int i=0;i<4;i++)for(int h=0;h<7;h++){cout<<ss[g][num[i]][h];fout<<ss[g][num[i]][h];}cout<<endl;fout<<endl;}}/*函数功能:控制日历的输出格式,使日历能够以每两个月并排的方式输出函数参数:整型变量n,存放当前打印的月份函数返回值:无返回值*/void Data::PrintForm(int n){if(n%2) //如果是单月,则输出空格{cout<<" ";fout<<" ";}else //若为双月,则输出回车换行{cout<<endl;fout<<endl;}/*函数功能:打印每个月日历的表头部分信息,并为每两个月并列显示函数参数:整型变量n,存放待打印月份函数返回值:无返回值*/void Data::PrintTitle(int n){do{string ss[12]={" Jan "," Feb "," Mar "," Apr "," May "," June "," July "," Aug "," Sept "," Oct "," Nov "," Dec "};cout<<ss[n-1];fout<<ss[n-1];PrintForm(n); //调用PrintForm()函数控制日历输出格式n++;}while(!(n%2)); //输出月份标题,每行打印两个月份标题do{cout<<"____________________________ ";fout<<"____________________________ ";PrintForm(n); //调用PrintForm()函数控制日历输出格式n++;}while(!(n%2)); //在相应的月份标题下打印横线do{cout<<" Sun Mon Tue Wed Thu Fri Sat ";fout<<" Sun Mon Tue Wed Thu Fri Sat ";PrintForm(n); //调用PrintForm()函数控制日历输出格式n++;}while(!(n%2)); //在相应的横线下面打印对应的星期标题}/*函数功能:打印每个月日历的详细日期,并按照每两个月并排的方式显示函数参数:整型变量mon,存放待打印月份函数返回值:无返回值*/void Data::PrintMonth(int mon){int CurDay[2]; //CurrentDay,储存每两个月中当前打印的日期do{int k=0;for(;k<Week[mon-1];k++) //使每个月的一号与星期对应{cout<<" ";fout<<" ";}k++;for(int d=1;k<8;d++,k++) //输入每个月的第一行{cout<<" "<<d<<" ";fout<<" "<<d<<" ";}if(mon%2) //判断,如果为单月,则输出一段空格{cout<<" ";fout<<" ";CurDay[0]=d;}else //若为双月,则换行{cout<<endl;fout<<endl;CurDay[1]=d-1;}mon++;}while(!(mon%2)); //控制每两个月并排打印mon=mon-2;int i=0,k=1,m=mon-1;for(;CurDay[i]<MonNum[m]+1;CurDay[i]++,k++){if(CurDay[i]<10){cout<<" "<<CurDay[i]<<" ";fout<<" "<<CurDay[i]<<" ";}else{cout<<" "<<CurDay[i]<<" ";fout<<" "<<CurDay[i]<<" ";}if(k==7){if(!i){cout<<" ";fout<<" ";i=1;m++;}else{cout<<endl;fout<<endl;i=0;m--;}k=0;}}m=mon-1;/*当双月输出还没结束,但单月输出已结束时,让单月的一边继续以空格形式补充输出*/ if(CurDay[0]==MonNum[m]+1&&CurDay[1]<MonNum[m+1]+1){for(;k<8;k++){cout<<" ";fout<<" ";}cout<<" ";fout<<" ";k=1;for(CurDay[1]++;CurDay[1]<MonNum[m+1]+1;CurDay[1]++,k++){cout<<" "<<CurDay[1]<<" ";fout<<" "<<CurDay[1]<<" ";if(k==7){cout<<endl;fout<<endl;cout<<" ";fout<<" ";}}cout<<endl;fout<<endl;}/*当单月未结束,双月输入结束跳出时最后一行的输出情况*/if(CurDay[0]<MonNum[m]+1&&CurDay[1]==MonNum[m+1]+1){cout<<endl;fout<<endl;k=1;for(CurDay[0]++;CurDay[0]<MonNum[m]+1;CurDay[0]++,k++){cout<<" "<<CurDay[0]<<" ";fout<<" "<<CurDay[0]<<" ";if(k==7){cout<<endl;fout<<endl;cout<<" ";fout<<" ";}}cout<<endl;fout<<endl;}}/*函数功能:在首次查询结束后输出提示信息,询问用户是选择继续查询其它的年份日历还是选择退出万年历查询系统,以此保证查询系统可以循环操作函数参数:无函数返回值:无返回值*/void Data::ChooseMenu(){string ss; //字符型变量ss,存放选择操作命令cout<<"*****************************操作提示*****************************"<<endl<<endl <<" 1.继续查询其它年份的日历"<<endl<<endl<<" 2.退出查询系统"<<endl<<endl <<" 请选择您需要的操作:";do{cin>>ss;if(ss=="1") //选择1时,继续查询其它的年份日历main();else if(ss=="2") //选择2时,执行退出命令{PrintThanks(); //调用PrintThanks()函数输出结束信息exit(0); //正常退出}else{cout<<"\n 您的输入有误!请重新选择:";}}while(1);}/*函数功能:求出待求年份的每个月一号是星期几函数参数:整形参数m,存放待求年份函数返回值:无返回值*/void Data::FirstDayOfYear(int m){long n;if(m>=Year) //当输入年份大于等于2005的时候的情况{n=(m-Year)*365; //按每年365天计算待查年份元旦距05年元旦总天数for(int i=Year;i<=m-1;i++)n+=IsLeapyear(i); //遇到有闰年情况的,总天数中就相应的加上1Week[0]=(n+1)%7-2 ; //计算该年的元旦的星期数}else //当输入年份小于2005的时候的情况{n=(Year-m)*365;for(int i=m;i<Year;i++)n+=IsLeapyear(i);Week[0]=7-(n+1)%7; //计算该年元旦为星期几}if(Week[0]<0) //使Week[0]控制在0-6内,出现负值则加7Week[0]+=7;FirstDayOfMonth(m); //调用FristDayOfMonth()求出其他月份一号为星期几}/*函数功能:求出待求年份的除一月之外的每个月一号是星期几函数参数:整形参数m,存放待求年份函数返回值:无返回值*/void Data::FirstDayOfMonth(int m){if(IsLeapyear(m)==1) //如果该年为闰年,则二月天数为29天,否则28天MonNum[1]=29;elseMonNum[1]=28;for(int n=0;n<11;n++) //计算该年2-12月第一天为星期几{Week[n+1]= Week[n]+MonNum[n]%7;if(Week[n+1]>6) //用0-6来代表星期日至星期六Week[n+1]=Week[n+1]-7;}for(int h=1;h<12;h=h+2) //调用PrintTitle()和PrintMonth()函数输出日历{PrintTitle(h);PrintMonth(h);}}/*函数功能:用户选择退出时显示结束界面函数参数:无函数返回值:无返回值*/void Data::PrintThanks(){cout<<" ********************************************"<<endl<<" * <万年历查询系统> *"<<endl<<" * ================ *"<<endl<<" * 谢谢使用*"<<endl<<" * ================ *"<<endl<<" * 王XX B080510XX *"<<endl<<" ********************************************"<<endl;fout<<" ***************谢**谢**使**用***************"<<endl<<" 王XX\n B080510XX"<<endl; }/*函数功能:程序执行时显示欢迎界面函数参数:无函数返回值:无返回值*/void PrintWelcome(){cout<<"*******************************************************************"<<endl <<"* <欢迎进入万年历查询系统> *"<<endl <<"* 王XX B080510XX *"<<endl<<"* 自动化学院电气信息工程*"<<endl<<"*******************************************************************"<<endl;fout<<"*******************************************************************"<<endl <<"* <欢迎进入万年历查询系统> *"<<endl <<"* 王XX B080510XX *"<<endl<<"* 自动化学院电气信息工程*"<<endl<<"*******************************************************************"<<endl; }五、测试结果及结果分析(1)主程序功能测试:******************************************************************** <欢迎进入万年历查询系统> ** 王XX B080510XX ** 自动化学院电气信息工程********************************************************************请输入要查询的年份:2009=====================公元2009年日历=====================***** ***** ***** ****** * * * * * ****** * * * * ****** * * * * ****** ***** ***** *****Jan Feb____________________________ ____________________________ Sun Mon Tue Wed Thu Fri Sat Sun Mon Tue Wed Thu Fri Sat1 2 3 1 2 3 4 5 6 7 4 5 6 7 8 9 10 8 9 10 11 12 13 14 11 12 13 14 15 16 17 15 16 17 18 19 20 21 18 19 20 21 22 23 24 22 23 24 25 26 27 28 25 26 27 28 29 30 31Mar Apr____________________________ ____________________________ Sun Mon Tue Wed Thu Fri Sat Sun Mon Tue Wed Thu Fri Sat 1 2 3 4 5 6 7 1 2 3 4 8 9 10 11 12 13 14 5 6 7 8 9 10 11 15 16 17 18 19 20 21 12 13 14 15 16 17 18 22 23 24 25 26 27 28 19 20 21 22 23 24 25 29 30 31 26 27 28 29 30May June____________________________ ____________________________ Sun Mon Tue Wed Thu Fri Sat Sun Mon Tue Wed Thu Fri Sat1 2 1 2 3 4 5 6 3 4 5 6 7 8 9 7 8 9 10 11 12 13 10 11 12 13 14 15 16 14 15 16 17 18 19 20 17 18 19 20 21 22 23 21 22 23 24 25 26 27 24 25 26 27 28 29 30 28 29 3031July Aug____________________________ ____________________________ Sun Mon Tue Wed Thu Fri Sat Sun Mon Tue Wed Thu Fri Sat1 2 3 4 1 5 6 7 8 9 10 11 2 3 4 5 6 7 8 12 13 14 15 16 17 18 9 10 11 12 13 14 15 19 20 21 22 23 24 25 16 17 18 19 20 21 22 26 27 28 29 30 31 23 24 25 26 27 28 2930 31Sept Oct____________________________ ____________________________ Sun Mon Tue Wed Thu Fri Sat Sun Mon Tue Wed Thu Fri Sat1 2 3 4 5 1 2 3 6 7 8 9 10 11 12 4 5 6 7 8 9 10 13 14 15 16 17 18 19 11 12 13 14 15 16 17 20 21 22 23 24 25 26 18 19 20 21 22 23 24 27 28 29 30 25 26 27 28 29 30 31Nov Dec____________________________ ____________________________ Sun Mon Tue Wed Thu Fri Sat Sun Mon Tue Wed Thu Fri Sat1 2 3 4 5 6 7 1 2 3 4 58 9 10 11 12 13 14 6 7 8 9 10 11 1215 16 17 18 19 20 21 13 14 15 16 17 18 1922 23 24 25 26 27 28 20 21 22 23 24 25 2629 30 27 28 29 30 31(2)循环程序检错能力测试:*****************************操作提示*****************************1.继续查询其它年份的日历2.退出查询系统请选择您需要的操作:3您的输入有误!请重新选择:f您的输入有误!请重新输入:1请输入要查询的年份:12345输入错误,您所输入的年份必须在0~9999之间!请重新输入要查询的年份:abcd输入错误,您所输入的年份必须在0~9999之间!请重新输入要查询的年份:2kf6输入错误,您所输入的年份必须在0~9999之间!请重新输入要查询的年份:2005=====================公元2005年日历=================***** ***** ***** ****** * * * * ****** * * * * ****** * * * * ****** ***** ***** *****Jan Feb____________________________ ____________________________ Sun Mon Tue Wed Thu Fri Sat Sun Mon Tue Wed Thu Fri Sat1 1234 52 3 4 5 6 7 8 6 7 8 9 10 11 129 10 11 12 13 14 15 13 14 15 16 17 18 1916 17 18 19 20 21 22 20 21 22 23 24 25 2623 24 25 26 27 28 29 27 2830 31Mar Apr____________________________ ____________________________ Sun Mon Tue Wed Thu Fri Sat Sun Mon Tue Wed Thu Fri Sat1 2 3 4 5 1 26 7 8 9 10 11 12 3 4 5 6 7 8 913 14 15 16 17 18 19 10 11 12 13 14 15 1620 21 22 23 24 25 26 17 18 19 20 21 22 2327 28 29 30 31 24 25 26 27 28 29 30……(其它月份略去)(3)程序退出功能测试:*****************************操作提示*****************************1.继续查询其它年份的日历2.退出查询系统请选择您需要的操作:2********************************************* <万年历查询系统> ** ================ ** 谢谢使用** ================ ** 王XX B080510XX *********************************************六、调试过程中的问题在程序的设计及调试过程中,曾遇到诸多问题:1、编写类Data的构造函数时,为使其具有一定的错误检查能力,使用如下的语句:while(1){cin>>year;if(year<0||year>9999||ch!='\n'){cout<<" 输入错误,您所输入的年份必须在0~9999之间!"<<endl<<"请重新输入要查询的年份:";}else break;}但是,在调试中发现,如果输入一个错误的年份(如12345、-2394),则可以检查出输入有误,并提示重新输入;但如果输入了非数字字符(如wefd、jhdv)时,程序便进入死循环。