一、
实验目的
通过本次实验,进一步掌握 c++语言的语言特性,标准库的用法以及面向对象思想的培养。
掌握类的构造函数、私有成员(属性)、公有成员(接口)的相关方法。
二、
实验内容
用 c++语言编写设计一个万年历,要求功能有:
输入一个年份,输出是在屏幕上显示该年的日历。假定输入的年份在 1940-2040 年之间。
输入年月,输出该月的日历。
输入年月日,输出距今天还有多少天,星期几,是否是公历节日。
三、
实验环境
Microsoft
Visual
C++ 6.0
四、
设计思路或方案
这对这个题目以及所要求的功能,我们需要显示一年的日历、一个月的日历以及某一天的信息,我
们需要用数据去保存他们,我们需要抽象出一个好的数据模型来使存储尽可能的小并能满足所有功能要
求,必要时还具有扩展性,但我们的操纵的可以说是每一天的数据,并且要显示一个月、一年的信息,
需要支持显示的范围为 100 年日历。这样的情况下,我们针对 100 年每年大约 365 天的情况,设想如果
每 一 天 对 应 一 个 值 , 那 么 总 共 有 36500 种 变 化 , 我 们 为 每 一 天 都 赋 予 一 个 唯 一 值 ( 变 量 名 :
unique_value)。从 1940 年月 1 日开始唯一值为 0,以后每增加一天我们将为唯一值加 1,这样就可以
把每一天都编上号,使得我们可以精确的定位到任何一天。这样做好处不仅如此,对于跨年跨月的计算
天数,是一件非常困难的工作,而这样对与每一天有对应编号来讲,就变得十分简单,只需要相减就可
以的出相差天数。
所以,我们以此唯一值为基础,只要知道了 1940 年 1 月 1 日是周几就能计算出所有日期的信息!
外加年(变量名:year)月(变量名:month)日(变量名:day)三个无符号整型变量辅助,构成了类
的私有数据,不过这样还不够,我们还需要获取当前的日期,这个可以用库和 Windows API 函数来得
到,并添加到私有数据中去(变量名:Local_date)。
构造类的困难之处在于如何实现年月日三个无符号整型数值和唯一值之间的相互转换,对于这个问
题,我们编写出了两个函数:DateTounique_value()用于将日期转换成唯一值,unique_valueToDate()
用于将唯一值转换成日期,这些都是以 1940 年 1 月 1 日为初始值参照的,同样还需要直到某一天是星
期几,我们使用了 count_week()来通过唯一值来计算,同时在私有数据中也加入了一个 week 变量,他
的取值为 0-6,表示星期天到星期六,实际上只要知道了 1940 年 1 月 1 日是周几和特定日期的唯一值
对 7 取余即可算出,这个算法实现较易。
还有一些细节问题需要注意,例如闰年的情况我们定义了函数 bool IsLeapYear()来确定对象的属
性 year 是不是闰年,类还需要一些重载函数来实现一些不同的功能(见源代码),另外,对于公历节日
显示我们创建了一个文件(文件内容见附录 1)用来存储每年的公历节日,程序在输入了日期后会与文
件中日期的对比,如若对比成功,则输出该节日,否则无任何操作。
最后,就是要显示了,显示的接口定义了 3 中,分别是显示年历(要求输入年)、显示月历(要求
输入年和月)、以及显示某一天的信息(要求输入年月日)。分别用 print_year()、print_month()、
print_day()三个函数以及它们的重载版本实现。
五、
程序清单
#include//输入输出流
#include //操作子
#include//容器
#include//字符串
#include//文件操作,用于读取公历节日
#include
using namespace std; //使用命名空间
//打印年历,月历,日期信息的三个函数
void p_year(); //打印年的函数
void p_month(); //打印月历的函数
void p_day(); //打印日期信息的函数(包括节日)
void input_command(); //键盘命令输入的函数
//两个全局数组 存储闰年与非闰年的每月天数
int NotLeapYear[12]={31,28,31,30,31,30,31,31,30,31,30,31};
int LeapYear[12]={31,29,31,30,31,30,31,31,30,31,30,31} ;
//定义每年节日的数据类型
typedef struct
{
string Name;
//节日名
int mon;
//在几月
int da;
//在几日
}Festival; //别名
typedef struct
{
int year;
int month;
int day;
int value;
}DATE; //存储当前日期的类型
//类的定义,所有的操作都将基于类的数据
//所有的操作都将会基于类的接口函数
class Date
{
public:
//默认构造函数 //1940 年 1 月一日是周一
Date():year(1940),month(1),day(1),unique_value(0),week(1)
{}
//多个版本的重载构造函数
//这个是根据日期来构造的
Date(unsigned short y,unsigned short m,unsigned short d):year(y),month(m),day(d)
//构造后并给 value 赋值
DateTounique_value();
//并计算星期
count_week();
{
}
//这个是根据唯一值来构造的,一般不用,因为无法确定唯一值(只有 1940.1.1 知道是 0)
Date(unsigned short value):unique_value(value)
{
}
//构造后并为给 year month day 赋值
unique_valueToDate();
//并计算星期
count_week();
//改变日期的函数,带默认值的函数
void change_date(unsigned short y=1940,unsigned short m=1,unsigned short d=1)
{
//赋值
year=y;
month=m;
day=d;
//改变其他属性值,因为他们是相关的
DateTounique_value();
count_week();
}
//判断是否为闰年的函数 他还有一个带参数的重载版本,这里面用到的值是对象的私有值
bool IsLeapYear()
{
}
return (year%4==0&&year%100!=0)||(year%400==0);
//另一个版本的带参 判断非成员是否为闰年的函数
bool IsLeapYear(unsigned short other_year)
{
}
return (other_year%4==0&&other_year%100!=0)||(other_year%400==0);
//唯一值转日期的函数,用 unique_value 去计算 year month 和 day 的值
void unique_valueToDate()
{
unsigned i=1940,j=0;
//初始化变量
unsigned short temp=unique_value;
//赋值给临时值
year=0;month=0;day=0;
//转换前清零,这里可能不合适
//开始计算日期
while(temp>365)
{
if(IsLeapYear(i))
{
}
temp-=366;
//是闰年减 366
else
{
}
temp-=365;
//不是闰年减 365
i++;
} //循环继续 直到结果小于一年的天数
j=0;
//如果不是闰年并且剩余 365 天,那么就是下一年的第一天(因为月日未确定)
if((!IsLeapYear(i))&&temp==365)
{
}
//所以年份+1 月 日为一月一日 这是一种特殊情况
year=i+1; month=1;day=1;
//如果是闰年并超过 2 月的话,这也算是一种特殊情况
else if(IsLeapYear(i)&&temp>=31+28)
{
}
while(temp>=LeapYear[j])
//循环减去闰年月份
{
}
temp-=LeapYear[j];
j++;
//最后循环得到的 j 就是多少月,而不是下标
month=j;
//日期需要加 1,因为结果是 0 的话结果就是 1 号,所以需要加 1
day=temp+1;
else
//最后一种普遍情况
while(temp>=NotLeapYear[j]) //同上
{
}
temp-=NotLeapYear[j];
j++;
month=j;
day=temp+1;
//同上
{
}
}
//日期转唯一值的函数,用 year month 和 day 去计算 unique_value
void DateTounique_value()
{
unique_value=0; //清空它 初始值为 0 表示 1940 年 1 月 1 日
for(int i=1940;i
else
unique_value+=365;
}
//年计算完了,计算该年是否是闰年,这关乎该年是否多一天
if(IsLeapYear(year))
{
for(int j=0;j
void print_month();
//打印类内年月信息
private:
//year month day 都不能为 0,虽然这里可以为 0。
unsigned short year;
//年 值从 1940-2040 实际如果 unsigned short 不溢出,那么就不会出错
unsigned short month;
//月 值从 1 到 12
unsigned short day;
//日 值遵从闰年和非闰年数组
unsigned short week;
// 值为 0-6 从周日算起
//从 0 开始,针对 2 个字节的 short 类型 共有 65536 中变化,完全可以表示出 100 年(1940-2040)内
的任意一天
unsigned short unique_value;
//存储每年的公历节日的容器,在构造函数中初始化
vector fes;
//存储当前日期
DATE Local_date;
};
//控制台版本的显示函数,显示当前类内值年的日历,年份根据类内的值确定
void Date::print_year()
{
}
int i=0;
month=1; //把月份和日都设为 1,然后循环遍历输出
day=1;
DateTounique_value();
//转换成唯一值
count_week();
//计算是星期几
while(month<=12)
{
}
print_month();
//打印每个月
month++;
//让月份增加
DateTounique_value();
//更新唯一值
//循环 12 次完成
void Date::print_day(unsigned y,unsigned m,unsigned d)
{
}
change_date(y,m,d); //更新对象的私有值
Date::print_day(); //调用打印
void Date::print_day()
{
Festival temp;
//设置临时变量,接受文件的值
fstream infile("Festival.txt",ios::in); //打开文件
if(!infile) //判断是否成功打开
exit(0); //退出
//循环赋值给 fes 容器,查询日期时需要用它对比
while(!infile.eof()&&(infile>>temp.Name>>temp.mon>>temp.da))
fes.push_back(temp);
infile.close();//关闭文件
DateTounique_value(); //转换唯一值
GetDate(); //获取当前日期
count_week(); //计算星期,这里一定要记住,先转唯一值再计算日期!!!
cout<<"日期是:"<