zl程序教程

您现在的位置是:首页 >  后端

当前栏目

基于C++开发的(控制台)万年历系统【100010447】

C++系统开发 基于 控制台
2023-09-11 14:17:49 时间

一、实验项目名称

​ C语言程序设计大作业转换成C++程序设计课程设计

二、实验目的

​ 要求学生从C语言程序设计大作业转换成C++程序设计课程设计,1人1题(如C语言程序设计大作业由多人完成必须拆分到每一个人),根据需求描述(或自己撰写需求描述),独自完成需求分析,程序模块设计以及程序的编写、调试和测试。

通过设计性、综合性的C++语言程序设计,提高学生编写程序、调试程序的能力,加深C++语言知识的理解和把握,初步掌握面向对象和泛型编程的基本思想以及使用类库进行软件构建能力。

三、实验要求

  1. 采用**面向对象程序设计,**要运用类的封装、继承、多态等机制;

  2. 必须体现多文件结构的思想,类定义和类实现进行分离。

  3. 程序要求做到界面友好,对于用户的输入操作要有明确的提示信息,并对输入的数据进行合法性和正确性的检查;

  4. 做到工程名、类名、变量名、代码格式等合乎规范,代码中必须对所设计的类的功能或函数功能进行规范的注解

  5. 要求具备菜单功能,根据用户的输入,执行相应的操作。在菜单选项中,必须包含“关于”一项,该菜单项用来显示课程设计的基本信息,包括程序名称、版本、完成时间、姓名、班级、学号等信息;

四、主要仪器设备及耗材

计算机、Visual Studio 2010及以上

五、实验步骤

  1. 技术路线(要详细说明所使用的主要技术和实现原理并同时)

  2. 需求分析(项目介绍、功能需求)

  3. 系统分析与设计(阐述项目中需解决的关键技术问题,同时要以类关系图、程序流程图、功能模块示意图等辅助系统设计的描述)

  4. 程序设计与实现(其中内容首先要描述代码工程的文件组织结构,然后以文字的方式阐述代码中每个类的设计意图、主要功能、运用到的关键知识点,主要成员函数的设计意图及功能介绍,对于代码实现中有特色的算法或有创新的地方,可以着重描述(这将视为课程设计的加分点)。如果必须给出实现代码才能更好地说明问题时,也必须先有相关的文字叙述,然后才是代码,代码只是作为例证。)

  5. 系统测试(该部分不能只简单展示几幅程序运行时的截图就完事!要设计几组有代表性的数据来验证程序的各个功能是否达到设计需求。也就是测试是否有不正确或者是遗漏了的功能,在接口上,输入能否正确的接受,能否输出正确的结果;是否有数据结构错误或者外部信息访问错误;性能上是否满足要求;是否有初始化或终止性错误等。建议以表格的形式展示)

  6. 个人小结(课程设计心得体会不少于800字, 该部分必须包含课程设计项目的工作量、开发过程中遇到的困难以及如果克服困难、个人收获、得到的启示或教训等等,切忌空洞无实际内容或千篇一律的敷衍文字)

  7. 参考文献(该部分给出整个程序从选题、需求分析、系统设计到代码实现过程中所参考的书籍、网上资料等)

一、项目技术路线说明

​ 万年历查询系统所使用的编程语言是C++语言。C++语言具有面向对象的特点,给编写程序带来了极大地方便。万年历程序设计通过抽象、封装、继承和多态使程序代码达到了很大限度的可重用和可扩展。而程序中的多种多样的类是此次程序设计的核心,利用这些类可以实现数据的封装,隐蔽。通过这些类的派生与继承,能够实现对问题的抽象描述。

​ 在程序设计中,数据的共享与保护显得尤为重要。设置常成员、常对象、常引用和常指针等对数据进行保护,增强程序代码的安全性可靠性。

​ 在程序的组织上,采用多文件结构。将类的声明和定义在不同文件中实现,可以对不同文件进行单独编写、编译,最后再连接。同时利用类的封装性,在调试程序、修改代码时,只对某一个类的定义和实现进行修改,可以提高程序编写效率。

​ 在程序中适当使用多态机制。声明抽象类,为整个类族提供了统一的对外接口。使用指向派生类的指针就可以对派生类的对象进行统一操作。

​ 万年历算法的实现主要依赖于对蔡勒公式的充分理解和恰当使用。蔡勒公式主要是用来计算星期数的算式。以具体日期所在星期为算法实现的突破口,巧妙使用矩阵(即二维数组)这种数据结构,极大得方便了日期的存储和访问,也方便了万年历格式化输出。

二、项目需求分析
2.1项目介绍

​ 利用C++语言的程序将日常生活中最经常接触到的日历查询编成一个简单的万年历查询系统。该系统具有多项功能,包括当前时间显示,年历查询,月历查询,日历查询功能等。该系统还具有菜单项,方便用户使用相关功能。

2.2功能需求

在万年历查询系统中,日历月历简单明了,方便查询。下面是它可以实现的一些基本功能:

(1)欢迎界面与启动界面,以及显示当前时间

(2)输入年份,判断是否为闰年

(3)输入年份,打印出十二个月的月历

(4)输入年份月份,打印出某个月的月历

(5)输入年份月份日份,打印某天为星期几

(6)对错误输入具有合法性检查,并且输出错误指令提示

三、系统分析与设计
3.1本程序需解决的关键技术问题

(1)如何判断读入年份是平年还是闰年?

(2)如何获得当前时间?

(3)如何连续显示当前时间?

(4)如何计算指定年月日是星期几?

(5)如何按指定格式3×4打印一年日

(6)如何实现定制控制台界面的名称、颜色、尺寸等信息?

(7)需要设计哪些类?需要设计哪些类的成员?如何处理类之间关系?

(8)如何实现模拟用户界面?

3.2程序流程

程序流程如图3.2所示

在这里插入图片描述

​ 图3.2

3.3 功能模块

万年年历查询系统功能模块可以分解为以下5个主要功能:

  1. 启动界面:模拟程序启动以及显示当前时间
  2. 主菜单:包含关于、年历查询、月历查询、日历查询等选项
  3. 年历查询:查询指定年份的年历
  4. 月历查询:查询指定年份月份的月历
  5. 日历查询:查询指定年份月份日的日历
3.4类的设计

​ 依据2.2功能需求分析以及3.1需要解决的关键技术问题,设计一个日期类CDate来表示日期,类内含年、月、日、星期、四个数据成员。为了获得指定年月日的星期数,记y为年数的后两位;c为已经过的世纪数;m为蔡勒月(m大于等于3,小于等于14,即在蔡勒公式中,某年的1、2月要看作上一年的13、14月来计算,比如2003年1月1日要看作2002年的13月1日来计算);w为星期, w对7取模得:0-星期日,1-星期一,2-星期二,3-星期三,4-星期四,5-星期五,6-星期六;d为日。[]代表取整,即只要整数部分。将y年m月d日这一天的星期记为w(y,m,d)。公式(3-4)如下

在这里插入图片描述

本类设计辅助函数ZellerMonth用来计算蔡勒月m,CalculationWeek计算星期,构造对象,重新设置年,月,日会被调用。计算isLeapYear判断平年闰年。

​ 为了实现3.1连续显示当前时间,时间可以用一个时间类表示,时间类继承了日期类,在此基础上添加了小时,分钟,秒三个数据成员。使用windows API获得当前系统时间,缺点:使用了windows API,优点:能精确到毫秒级。可以用循环来连续显示当前时间,用清屏函数来更新时间,在人眼视觉滞留效应的影响下,就实现了时钟效果。

​ 为了实现2.2的功能(3)(4)(5),可以定义三个类CYear、CMonth、CDay。不难发现,这些类都需要功能选择界面Menu函数和相关的查询功能SearchFor函数,但在各自类中的具体实现又不尽相同。不妨,为它们定义一个抽象类CCalendar,在CCalendar中只给出这些函数的声明,在其派生类CYear、CMonth、CDay中给出具体实现。这样,定义一个指向其派生类CCalendar指针也可以方便访问其派生类成员函数,对它们做统一处理。[2].。

​ 由于一个月最多占31天,矩阵排列为6排7列,所以设置二维数组int date[6][7]来存储每一天的日期。

​ 已知每月1日所在星期数w(取值=0,1,2,3,4,5,6),正好符合二维数组列下标,通过遍历二维数组做判断赋值来存储某一月的日期。生成一个月日历的GetDate函数,定义为CCalendar成员函数。

​ 在类CYear、CMonth中定义辅助函数Print,Judge。

​ 定义控制台类CConsole来统一定制控制台界面的名称,颜色,尺寸,位置等信息。CTime类和CCalendar类继承了CConsole。

​ 图3.4通过UML图形表示了设计的类及其相互关系。

在这里插入图片描述

​ 图3.4

四、程序设计与实现
4.1工程文件组织结构

​ Calendar工程文件由头文件CDate.h、CTime.h、CCalendar.h、CConsole.h和源文件CDate.cpp、CTime.cpp、CCalendar.cpp、CConsole.cpp以及main.cpp。

​ CDate.cpp、CTime.cpp、CCalendar.cpp、CConsole.cpp包含对应的头文件CDate.h、CTime.h、CCalendar.h、CConsole.h。

​ main.cpp包含CCalendar.h和CTime.h。

​ CTime.h和CCalendar.h都包含了CDate.h,CConsole.h。

4.2程序设计

​ CDate类 设计意图:为了更好访问、存储和处理日期,用日期类表示日期。其属性为:year,month,day,week,其具有的功能:isLeapYear函数判断某一年是否是闰年,getMaxDay函数计算当月有多少天,以及数据成员的外部接口函数getYear, resetYear等,既可以读出数据成员,也可以写入数据成员。

​ CTime类 设计意图:为了解决3.1的问题(2)(3),用时间类表示时间。其继承了CDate类。具有时,分,秒数据成员。getTime函数可以获得系统当前时间;showTime函数可以连续显示当前时间以及程序启动界面。

​ CCalendar类 设计意图:将不同派生类CYear、CMonth、CDay中的Menu、SearchFor这些用来执行相同类型操作的函数之间建立起联系。CCalendar为抽象类,类中Menu、SearchFor为纯虚函数。这些纯虚函数规定了类族的统一接口形式。在满足类型兼容规则的前提下,通过CCalendar的指针,便可以对其派生类进行统一处理。[2]

​ CYear类 设计意图:为了实现2.2的功能(3)。虚函数Menu、SearchFor覆盖了基类中的虚函数。Menu实现菜单项功能;SearchFor实现年历的生成、存储以及打印功能。辅助函数Print、Judge实现年历打印。

​ CMonth类 设计意图:为了实现2.2的功能(4)。虚函数Menu、SearchFor覆盖了基类中的虚函数。Menu实现菜单项功能;SearchFor实现月历的生成、存储以及打印功能。辅助函数Print、Judge实现月历打印。

​ CDay类 设计意图:为了实现2.2的功能(5)。虚函数Menu、SearchFor覆盖了基类中的虚函数。Menu实现菜单项功能;SearchFor实现日历的生成、存储以及打印功能。辅助函数Print、Judge实现日历打印。

​ CConsole类 设计意图:为了解决3.1的问题(6),类CConsole来统一定制控制台界面的名称,颜色,尺寸,位置等信息。SetCord设置输出的位置、HideCursor光标的开启与关闭、SetTitle设置标题名称、SetSize设置控制台大小、SetColor设置颜色、CleanScreen清屏、PauseScreen冻结屏幕。

4.3程序实现

CDate类

闰年判断

​ 闰年分为普通闰年和世纪闰年。普通闰年:能被4整除但不能被100整除的年份为普通闰年(如2004年就是闰年,1999年不是闰年)。世纪闰年:能被400整除的为世纪闰年(2000年是闰年,1900不是闰年)。

代码实现:

bool isLeapYear()const {
		return year % 4 == 0 && year % 100 != 0 || year % 400 == 0;
	}

计算当月的天数

​ 用数组存储平年中某个月1日之前有多少天,为便于getMaxDay函数的实现,数组多出一项。相邻两元素之差,即为当月天数

const int DAYS_BEFORE_MONTH[] = { 0, 31,59,90,120,151,181,212,243,273,304,334,365 };
int CDate::getMaxDay()const {
		if (isLeapYear() && month == 2)
			return 29;
		else
			return DAYS_BEFORE_MONTH[month] - DAYS_BEFORE_MONTH[month - 1];
}

计算指定年月日的星期数

​ 记y为年数的后两位;c为已经过的世纪数;m为蔡勒月(m大于等于3,小于等于14,即在蔡勒公式中,某年的1、2月要看作上一年的13、14月来计算,比如2003年1月1日要看作2002年的13月1日来计算);w为星期, w对7取模得:0-星期日,1-星期一,2-星期二,3-星期三,4-星期四,5-星期五,6-星期六;d为日。[]代表取整,即只要整数部分。将y年m月d日这一天的星期记为w(y,m,d)。蔡勒公式(3-4)如下

在这里插入图片描述

本类设计辅助函数ZellerMonth用来计算蔡勒月m,CalculationWeek计算星期,构造对象,重新设置年,月,日会被调用。

CCalendar类

日历的生成和存储

​ 由于一个月最多占31天,矩阵排列为6排7列,所以设置二维数组int date[6][7]来存储每一天的日期。已知每月1日所在星期数w(取值=0,1,2,3,4,5,6),正好符合二维数组列下标,通过遍历二维数组做判断,赋值来存储某一月的日期

​ 代码实现如下

void CCalendar::GetDate(int week, int date[][WEEK], int MaxDay){
	int d = 1;	
	for (int raw = 0; raw < 6; raw++) {
		int col;
		for (col = 0; col < WEEK; col++) {
			if ((raw == 0 && col >= week) || (raw > 0 && col >= 0)) {
				date[raw][col] = d;
				d++;
			}
			if (date[raw][col] >= MaxDay)
				break;
		}
		if (date[raw][col] >= MaxDay)
			break;
	}
}

CYear类

打印年历

​ 以4×3格式打印一年日历,需要设置三个二维数组分别来存储3个月的日期。顺序调用3次Judge函数按列打印3个月的日期,再嵌入循环6次for循环中,即可横向打印3个月的日历。再将循环6次的for循环嵌入循环4次的for循环,即可按3×4格式打印出一年的日历。

​ 为了便于SearchFor实现年历的生成、存储以及打印功能。定义辅助函数Print、Judge

​ Print主要用于按格式打印。Judge主要用于判断date数组元素值,打印相应的元素。

CMonth类

打印月历

​ 使用两次for循环遍历二维数组做打印。

​ 为了便于SearchFor实现年历的生成、存储以及打印功能。定义辅助函数Print、Judge

​ Print主要用于按格式打印。Judge主要用于判断date数组元素值,打印相应的元素。

CTime类

获取系统当前时间

​ 使用windows API获得当前系统时间,缺点:使用了windows API,优点:能精确到毫秒级。

​ 代码实现如下

void CTime::getTime()
{
	SYSTEMTIME CurrentTime;
	GetLocalTime(&CurrentTime);

	this->hour = CurrentTime.wHour;
	this->minute = CurrentTime.wMinute;
	this->second = CurrentTime.wSecond;
	resetYear(CurrentTime.wYear);
	resetMonth(CurrentTime.wMonth);
	resetDay(CurrentTime.wDay);
}

连续显示当前时间及启动效果

​ 可以用循环来连续显示当前时间,用清屏函数来更新时间,在人眼视觉滞留效应的影响下,就实现了时钟效果。

​ 用ssecond记录程序开始运行那一刻的描数,second表示当前时间的秒数。假如程序运行启动需要10s,当ssecond取值在区间[0,49]中,second - ssecond <= 10;当ssecond取值在区间[50,59]中,|second - ssecond|>= 50。

​ 程序代码如下

void CTime::showTime() {
	const char* WEEK[7] = { "星期日" ,"星期一","星期二","星期三","星期四","星期五","星期六"};
	getTime();
	int ssecond = second;
	do {
		getTime();
		cout << setw(2) << hour << ":" 
			 << setw(2) << minute << ":" 
			<< setw(2) << second << endl;
		cout << getYear() << "年" << getMonth() << "月" << getDay() << "日" << "   " << WEEK[getWeek()] << endl;
		cout << endl << endl << endl << endl << "       欢迎使用万年历查询系统" << endl<<endl
			<< "              启动中..." << endl;
		for (unsigned int i = 0; i < 335911402; i++) {
		}
		CleanScreen(); 
	} while ((abs(second - ssecond) <= 10 || abs(second - ssecond) >= 50));
}

CConsole类

定制控制台界面的名称、颜色、尺寸等信息

​ 借助系统控制台函数,包含在windows.h头文件中

​ 设置输出的位置:先复位到原始的左上角位置,然后以左上角位置为参照设置新的位置

void CConsole::SetCoord(int x, int y)
{
	
	coord.X = 0;
	coord.Y = 0;
	SetConsoleCursorPosition(HConsole, coord);
	
	coord.X = x;
	coord.Y = y;
	SetConsoleCursorPosition(HConsole, coord);
}

光标开启与关闭:隐藏控制台光标,CONSOLE_CURSOR_INFO结构体第二个成员设为0就不可见。

void CConsole::HideCursor(bool hide)
{
	 	CONSOLE_CURSOR_INFO cursor_info = { 1, 1 };
	if (hide == true) cursor_info.bVisible = 0;
	SetConsoleCursorInfo(GetStdHandle(STD_OUTPUT_HANDLE), &cursor_info);
}

五、系统测试

5.1测试概要

​ 在Visual Studio 2017环境下进行测试,并配置相关工具。在测试中尽可能的选用大量典型数据以及边界数据去测试系统,使测试范围更广,测试数据更全面。将系统分解为多个功能模块,逐一对各个功能模块进行测试。通过5-6个测试人员对系统进行测试,减小测试偶然性,增加系统的可靠性。

5.2测试环境与配置

​ 所使用的测试环境是Visual Studio 2017,配置是实时调试器、VC++2017v141工具集(X86,X64)、C++分析工具、Visual C++ATL支持、用于CMake的Visual C++工具等。

5.3测试方法和工具

​ 使用大量典型数据去测试系统,以及使用一些边界数据去测试系统,记录测试结果,分析测试结果。

5.4系统功能分解

将系统功能分解为以下6部分:

  1. 启动界面:模拟程序启动以及显示当前时间。
  2. 主菜单:包含关于、年历查询、月历查询、日历查询等选项。
  3. 年历查询:查询指定年份的年历。
  4. 月历查询:查询指定年份月份的月历。
  5. 日历查询:查询指定年份月份日的日历。
5.5测试内容

1.功能性测试

​ 1)模块名:启动界面

功能预期输入预期输出实际结果备注
启动界面启动成功PASS
显示当前时间显示成功PASS

测试时间为15:18:22,时间显示正常,程序启动功能正常,测试结果如下:

在这里插入图片描述

2)模块名:主菜单

功能预期输入预期输出实际结果备注
查询选项0、1、2、3、4可查出符合条件的信息PASS

主菜单显示正常,结果如下:

在这里插入图片描述

输入数据为1,程序输出显示如下:

在这里插入图片描述

输入数据依次为2、3、4,程序输出依次为:

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

3)模块名:年历查询

功能预期输入预期输出实际结果备注
查询年历选项0、1可输出指定年份的年历PASS

年历菜单项显示正常

在这里插入图片描述

年历查询测试数据测试如下表:

​ 年历数据测试表1

预期输入预期输出实际结果
1900可输出指定年份的年历PASS
2000可输出指定年份的年历PASS
2008可输出指定年份的年历PASS
2019可输出指定年份的年历PASS
2100可输出指定年份的年历PASS

输出结果显示如下:

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

4)模块名:月历查询

功能预期输入预期输出实际结果备注
查询月历选项0、1可输出指定月的月历PASS

月历菜单项显示正常

在这里插入图片描述

月历查询测试数据测试如下表

月历数据测试表2

预期输入预期输出实际结果
1900 1可输出指定年份的月历PASS
2000 2可输出指定年份的月历PASS
2008 6可输出指定年份的月历PASS
2019 8可输出指定年份的月历PASS
2100 2可输出指定年份的月历PASS

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

5)模块名:月历查询

功能预期输入预期输出实际结果备注
查询日历历选项0、1可查出指定日的所在星期数PASS

日历菜单项显示正常

在这里插入图片描述

年日查询测试数据测试如下表

​ 日历数据测试表3

预期输入预期输出实际结果
1900 1 11900年1月1日 星期一PASS
2000 2 292000年2月29日 星期二PASS
2008 6 52008年6月5日 星期四PASS
2019 8 232019年8月23日 星期五PASS
2100 2 112100年2月11日 星期四PASS

2.性能测试

​ 压力测试:测试数据如下

​ 性能数据测试表4

预期输入预期输出实际结果
1100输出指定年份的年历与实际不符NOPASS
1323输出指定年份的年历与实际不符NOPASS
1500输出指定年份的年历与实际不符NOPASS
1545输出指定年份的年历与实际不符NOPASS
1900可输出指定年份的年历PASS
2000可输出指定年份的年历PASS
2008可输出指定年份的年历PASS
2019可输出指定年份的年历PASS
2100可输出指定年份的年历PASS
3188可输出指定年份的年历NOPASS
10021输出指定年份的年历与实际不符NOPASS

​ 根据测试数据显示万年历查询系统存在一定的瓶颈。通过查询资料以及浏览程序,可知,蔡勒公式适用于计算1582年10月15日之后的任意一天是星期几,

​ 由于程序逻辑算法设计的局限性,程序只能查询9999年12月31日之前的日历。所以,程序可查询1582年10月15日至9999年12月31。

​ 稳定性测试:系统在持续运行过程中系统无异常情况发生,无突发事件发生,在意外断电、事件中断等情况下系统完备性良好。

六、个人小结

​ 在将C语言程序设计大作业转换成用C++实现的过程中,感受颇深,获益良多。体会到C语言程序设计和C++程序程序设计之间的诸多区别与诸多相同之处。深刻意识到C++语法对C语言语法具有极大得兼容性和拓展性。重点学习到了面向对象的编程思想和泛型编程的思想。领会到了抽象、封装、继承以及多态在C++程序设计中占有举足轻重的地位。

​ C++与C语言的不同之一是,C++中,函数的运用与之前学过的C语言相比较,既有相同之处,也有不同的地方。相同的地方在于格式、用法等框架上的不变
个人小结

​ 但是,C语言中对于相同的函数名,只能够申明一种函数的,倘若有相同的函数名出现,系统会报错,所以有时用起来是极为不便的。而C++中,函数的重载使得这一缺点得以补偿。在对于不同的元素或对象进行同一种操作时,可以使用同样的函数名。虽然调用的是不同的函数,但是只要形参的个数、类型不同,是允许同名函数的存在的。这对于编程与审阅都是很方便的。

​ C++与C语言的主要不同是,C++的封装机制,使得相同类型的对象可以抽象成类,类可以实例化成不同的对象。不同的对象又具有相同属性和操作行为。这给编写程序带来了极大得灵活性。类使得数据可以封装,隐蔽起来。程序代码变得更加安全可靠。通过继承与派生能够对问题进行抽象描述,同时也增强了代码的可重用性。C++的多态机制使得运算符重载和虚函数成为可能。同一运算符便可以处理多种数据类型。抽象类为类族定义了统一对外接口,在类型兼容规则下,便可以对同一个类族中不同类的对象做统一处理。这或许就是面向对象的程序设计中的一些重要特点,也是与面向过程的程序设计的重要区别。

​ 在程序设计过程中也遇到诸多的绊脚石。其中既有设计何种类,才能更好地描述事物,也有如何组织类之间的继承和派生关系,才能更好地对问题进行抽象描述等等问题。从一次又一次的实践到一次又一次的失败再到最后的程序设计的实现,理解了程序结构组织和框架搭建虽难,但坚持或许是一种不错的解决方法。

当然,写程序也是一件很快乐和幸福的事。

♻️ 资源

在这里插入图片描述

大小: 462KB
➡️ 资源下载:https://download.csdn.net/download/s1t16/87390792