前言

这篇文章介绍了一个自制的学习时间计算器程序,主要功能包括时间加减运算和操作记忆功能。程序通过定义时间结构体Time来存储时分秒和操作类型,使用三个vector栈分别记录撤销、重做和清空操作。核心计算部分实现了时间的加减运算,包括进位和借位处理,并确保结果非负。记忆功能支持撤销、重做和清除操作。程序采用交互式界面,用户可以循环输入时间进行计算,每次操作后都会显示当前结果。整体设计简洁实用,满足基本的个人学习时间统计需求。

因为要计算我一天的学习时间来看一看我的学习效率,所以闲暇之余,做了一个自制的时间计算器,现在只能计算到小时,因为我的需求也只有这么大嘛

代码有需要可以在文章开头自取,希望直接使用的读者也可以下载上面的文件,下载完后把根目录下的文件夹删除(只保留根目录下的.exe文件)也可以使用

一.自制思路

1. 整体代码思路

  • 核心操作分为计算部分与记忆部分与其余部分
  • 计算部分分为加法部分与减法部分,使用加法部分时,注意进位操作,减法部分注意借位操作,以及减数大于被减数时的重置操作(确保时分秒始终大于等于0)
  • 记忆部分包括对操作的撤销与重做以及清除功能,这个后面会详细解释
  • 其他部分包括程序的初始化,用户操作的界面显示,开始菜单与结束程序

二.主体代码

1.开始前的准备

正式开始之前,我们需要定义一个时间的结构体以及C++语言中自带的vector(顺序表),还需要一个输入时间的辅助函数

1.定义部分

1.代码
#define MaxTimes 5 // 最大输入错误限制

// 时间结构体
typedef struct Time {
	int hour;
	int minute;
	int second;
	int calcularTag;// 记录当前操作是加还是减,加法操作则为1,减法操作则为0,什么操作都没有就是2,清空操作是-1
}Time;

std::vector<Time> timeUnStack;// 存放已经进行过的操作的时间
std::vector<Time> timeReStack;// 存放进行撤销操作的时间
std::vector<Time> timeClearStcak;// 存放进行清空操作的时间
  • 注意:这三个栈不能定义到用户自制的头文件中,只能定义到源文件中,不然会出现重定义错误
2.代码解释
  1. 最需要注意的是,Time结构体中calcularTag的值,这是我们后面区分各种操作的关键,加法操作则为1,减法操作则为0,什么操作都没有就是2,清空操作是-1
int calcularTag;// 记录当前操作是加还是减,加法操作则为1,减法操作则为0,什么操作都没有就是2,清空操作是-1
  1. 设置撤销栈,重做栈以及清空栈来存放被相应操作的时间
    • 撤销栈:存放已经进行过操作的时间,用于查看历史操作的数据来源
    • 重做栈:存放进行撤销操作的时间
    • 清空栈:存放进行清空操作的时间
// 撤销栈
std::vector<Time> timeUnStack;// 存放已经进行过的操作的时间
// 重做栈
std::vector<Time> timeReStack;// 存放进行撤销操作的时间
// 清空栈
std::vector<Time> timeClearStcak;// 存放进行清空操作的时间

2.输入时间的辅助函数

1.代码
bool InputTime(Time& time) {// 输入时间
	int hour = 0, minute = 0, second = 0;
	
	printf("* 请按照\"小时:分钟:秒钟\"的格式输入:");
	scanf_s("%d:%d:%d", &hour, &minute, &second);// 输入
	printf("\n");
	if (hour < 0 || minute < 0 || second < 0) return false;// 非法输入
	// 存入时分秒
	time.hour = hour;
	time.minute = minute;
	time.second = second;
	return true;
}
2.代码解释
  • 很简单的代码,在输入正确时间时返回输入的时间以及true,未输入正确数据时返回false

2.计算部分

1.加法部分

1. 代码展示
bool TimeAddition(Time& time) {// 时间相加
	printf("********************加法操作**********************\n");
	printf("* 输入任意负数即可退出加法操作                   *\n");
	Time addTime;// 加的时间
	InitTime(addTime);// 初始化时间
	bool tempTag = InputTime(addTime);// 输入的时间是否有误的信号
	while (tempTag) {
		time.calcularTag = 1;// 加法操作
		timeUnStack.push_back(time);// 将原时间入栈

		time.second += addTime.second;// 秒钟相加
		while (time.second >= 60) {// 秒钟化分钟
			time.second -= 60;
			time.minute++;// 每60秒增加一分钟
		}

		time.minute += addTime.minute;// 分钟相加
		while (time.minute >= 60) {// 分钟化小时
			time.minute -= 60;
			time.hour++;// 每60分钟增加一小时
		}
		time.hour += addTime.hour;// 小时相加

		time.calcularTag = 2;// 还未做任何操作
		printf("* 相加后的时间为: %dh %dm %ds                    *\n", time.hour, time.minute, time.second);
		printf("**************************************************\n");
		tempTag = InputTime(addTime);// 是否退出的信号
	}
	printf("**************************************************\n");
	return true;
}
2.拆分代码解释
  1. 输入合法的时间后,先记录下这一次的操作为加法操作,而后将时间入撤销栈,后面我们需要进行撤销的时候就需要用到它
time.calcularTag = 1;// 加法操作
timeUnStack.push_back(time);// 将原时间入栈
  1. 而后就是简单的加法而后进位的操作
time.second += addTime.second;// 秒钟相加
		while (time.second >= 60) {// 秒钟化分钟
			time.second -= 60;
			time.minute++;// 每60秒增加一分钟
		}

		time.minute += addTime.minute;// 分钟相加
		while (time.minute >= 60) {// 分钟化小时
			time.minute -= 60;
			time.hour++;// 每60分钟增加一小时
		}
		time.hour += addTime.hour;// 小时相加
  1. 此时time已经是相加后的时间了,由于此时time未进行任何操作,因此我们需要将其Tag置为2
time.calcularTag = 2;// 还未做任何操作
  1. 最后输出结果到屏幕中
printf("* 相加后的时间为: %dh %dm %ds                    *\n", time.hour, time.minute, time.second);
  1. 为了避免用户需要多次进入加法操作时不停地重新进入加法操作,我做了一个内循环操作,每次输入数据时,都会检测数据是否合法,如果合法则进行加法操作,如果不合法则退出程序
while (tempTag) {
// ...
tempTag = InputTime(addTime);// 是否退出的信号
}

2.减法操作

1.代码展示
bool SubtractTime(Time& time) {// 时间相减
	printf("********************减法操作**********************\n");
	printf("* 输入任意负数即可退出减法操作                   *\n");
	Time subTime;// 减的时间
	InitTime(subTime);// 初始化时间
	bool tempTag = InputTime(subTime);// 输入的时间是否有误的信号
	while (tempTag) {
		time.calcularTag = 0;// 减法操作
		timeUnStack.push_back(time);// 将原时间入栈

		time.second -= subTime.second;// 秒钟相减
		while (time.second < 0) {// 秒钟不够向分钟借
			time.second += 60;
			time.minute--;
		}
		
		time.minute -= subTime.minute;// 分钟相减
		while (time.minute < 0) {// 分钟不够向小时借
			time.minute += 60;
			time.hour--;
		}

		time.hour -= subTime.hour;// 小时相减
		if (time.hour < 0) {// 小时数小于0,说明减数大于被减数,全部归0
			time.hour = 0;
			time.minute = 0;
			time.second = 0;
		}

		time.calcularTag = 2;// 还未做任何操作
		printf("* 相减后的时间为: %dh %dm %ds                    *\n", time.hour, time.minute, time.second);
		printf("**************************************************\n");
		tempTag = InputTime(subTime);// 是否退出的信号
	}
	printf("**************************************************\n");
	return true;
}
2.代码解释

与之前的加法操作类似,只提一下不一样的点

  1. 输入合法的时间后,先记录下这一次的操作为减法操作,而后将时间入撤销栈,后面我们需要进行撤销的时候就需要用到它
time.calcularTag = 0;// 加法操作
timeUnStack.push_back(time);// 将原时间入栈
  1. 由于我们的时间中的分钟和秒钟都不可能超过60,这是由加法操作决定的,因此在减法操作中不可能出现进位操作,我们只需关心借位操作即可
time.second -= subTime.second;// 秒钟相减
		while (time.second < 0) {// 秒钟不够向分钟借
			time.second += 60;
			time.minute--;
		}
		
		time.minute -= subTime.minute;// 分钟相减
		while (time.minute < 0) {// 分钟不够向小时借
			time.minute += 60;
			time.hour--;
		}

		time.hour -= subTime.hour;// 小时相减
		if (time.hour < 0) {// 小时数小于0,说明减数大于被减数,全部归0
			time.hour = 0;
			time.minute = 0;
			time.second = 0;
		}
  1. 最终判断小时数,如果小时数小于0,则说明减数比被减数要大,此时将时分秒都归零(还原到初始状态)

3.记忆部分

1.清空操作

1.代码展示
bool Clear(Time& time) {// 清空当前所有操作还原时间为默认状态
	printf("*********************清空操作*********************\n");
	if (timeUnStack.empty()) {
		printf("* 操作已经全部清空                               *\n");
		printf("**************************************************\n");
		return true;
	}
	Time clearTag;// 存入重做栈中,作为清空栈的信号
	clearTag.hour = -1;
	clearTag.minute = -1;
	clearTag.second = -1;
	clearTag.calcularTag = -1;
	timeClearStcak.push_back(clearTag);// 存放清空栈的边界
	timeClearStcak.push_back(time);// 存入当前元素,以备以后的恢复清空操作
	while (!timeUnStack.empty()) {// 清空栈
		timeClearStcak.push_back(timeUnStack.back());// 将撤销栈中的元素都存入清空栈,以备恢复
		timeUnStack.pop_back();// 出栈
	}
	timeUnStack.push_back(clearTag);// 存放清空操作
	time.hour = 0;
	time.minute = 0;
	time.second = 0;
	time.calcularTag = 2;
	printf("* 清空成功,当前时间为: %dh %dm %ds                  *\n",time.hour, time.minute, time.second);
	printf("**************************************************\n");
	return true;
}
2.代码解释
  1. 为了防止多个清空操作之间的时间混淆,需要设置一个边界,我这里是用了一个特殊的非法时间压入清空栈中表示边界
Time clearTag;// 存入重做栈中,作为清空栈的信号
clearTag.hour = -1;
clearTag.minute = -1;
clearTag.second = -1;
clearTag.calcularTag = -1;
timeClearStcak.push_back(clearTag);// 存放清空栈的边界
  1. 存放当前被操作的时间,以及撤销栈中的所有时间到清空栈中,这是为了后面撤销清空操作做准备,并且将撤销栈中的所有时间出栈
timeClearStcak.push_back(time);// 存入当前元素,以备以后的恢复清空操作
while (!timeUnStack.empty()) {// 清空栈
	timeClearStcak.push_back(timeUnStack.back());// 将撤销栈中的元素都存入清空栈,以备恢复
	timeUnStack.pop_back();// 出栈
}
  1. 在撤销栈中存入清空标志,表示本次操作为清空,也是为了后面撤销清空操作做准备
timeUnStack.push_back(clearTag);// 存放清空操作
  1. 将当前时间更改为初始时间,并且显示最终结果
time.hour = 0;
time.minute = 0;
time.second = 0;
time.calcularTag = 2;
printf("* 清空成功,当前时间为: %dh %dm %ds                  *\n",time.hour, time.minute, time.second);

2.撤销操作

1.代码展示
bool Undo(Time& time) {// 撤销当前操作
	printf("**********************撤销操作********************\n");
	if (timeUnStack.empty()) {// 撤销栈为NULL
		printf("* 已经没有进行过的操作了                         *\n");
		printf("**************************************************\n");
		return false;
	}
	if (!timeUnStack.empty()) {// 当撤销栈不为空且撤销栈的栈顶元素是清空信号时
		if (timeUnStack.back().hour == -1) {
			timeUnStack.pop_back();// 清除本次清空操作
			while (!timeClearStcak.empty() && timeClearStcak.back().hour > -1) {// 恢复清空操作
				timeUnStack.push_back(timeClearStcak.back());
				timeClearStcak.pop_back();
			}
			timeClearStcak.pop_back();// 清除本次清空操作的边界符
			time = timeUnStack.back();// 恢复被操作时间
			timeUnStack.pop_back();
			printf("* 清空操作已撤销                                 *\n");
			printf("**************************************************\n");
			return true;
		}
	}
	time.calcularTag = timeUnStack.back().calcularTag;// 获取上一次的操作
	timeReStack.push_back(time);// 将要撤销的时间入栈
	time = timeUnStack.back();// 获取上一次的时间
	timeUnStack.pop_back();// 销毁上一次的时间
	time.calcularTag = 2;// 重置上一次时间的操作
	printf("* 撤销成功                                       *\n");
	printf("**************************************************\n");
	return true;
}
2.代码解释
  1. 当撤销栈不为空并且栈顶是清空标志时,说明撤销栈此时应该撤销的操作是清空操作
if (!timeUnStack.empty()) {// 当撤销栈不为空且撤销栈的栈顶元素是清空信号时
		if (timeUnStack.back().hour == -1) {
			// ...
		}
	}
  1. 此时我们需要先将清空标志出栈,防止后面访问到非法的时间,在撤销栈中存入清空栈的栈顶时间的同时进行清空栈出栈操作,遇到清空栈为空或者下一个清空标志(边界符)为止,退出循环后,清除本次清空栈中的边界符
timeUnStack.pop_back();// 清除本次清空操作
while (!timeClearStcak.empty() && timeClearStcak.back().hour > -1) {// 恢复清空操作
	timeUnStack.push_back(timeClearStcak.back());
	timeClearStcak.pop_back();
}
timeClearStcak.pop_back();// 清除本次清空操作的边界符
  1. 我们知道,之前在进行清空操作时我们将当时的操作时间压入到了清空栈中,那么在经过第2步后,当时的操作时间应该在撤销栈的栈顶,因此我们需要将撤销栈的栈顶时间存入操作当前时间,并且将撤销栈的栈顶时间出栈
time = timeUnStack.back();// 恢复被操作时间
timeUnStack.pop_back();
  1. 最终提示显示当前操作的结果,并且结束本次撤销操作
printf("* 清空操作已撤销                                 *\n");
printf("**************************************************\n");
return true;
  1. 当撤销栈不为空且栈顶时间不为清空标志时,说明上一次操作只是简单的加减操作,此时我们需要先用当前时间的操作标志暂存上一次操作时间的操作标志,并且将当前时间存入重做栈,这是为了后面的重做操作做准备
time.calcularTag = timeUnStack.back().calcularTag;// 获取上一次的操作
timeReStack.push_back(time);// 将要撤销的时间入栈
  1. 接下来将撤销栈中的栈顶时间赋给当前时间,再将撤销栈进行出栈操作,最后将当前时间的操作标志设置为2(未进行任何操作)这个状态
timeClearStcak.pop_back();// 清除本次清空操作的边界符
time = timeUnStack.back();// 恢复被操作时间
timeUnStack.pop_back();
  1. 最后显示本次操作的结果
printf("* 撤销成功                                       *\n");
printf("**************************************************\n");

3.重做操作

1.代码展示
bool Redo(Time& time) {// 重做操作,取消撤销的操作
	printf("**********************重做操作********************\n");
	if (timeReStack.empty()) {
		printf("* 已经没有进行过的撤销操作了                     *\n");
		printf("**************************************************\n");
		return true;
	}
	time.calcularTag = timeReStack.back().calcularTag;// 还原最近撤销的操作
	timeUnStack.push_back(time);// 正在使用的时间入栈
	time = timeReStack.back();// 还原之前撤销的时间
	time.calcularTag = 2;// 还原为未进行加减操作的状态
	timeReStack.pop_back();// 已经重做的时间出栈
	printf("* 重做成功                                       *\n");
	printf("**************************************************\n");
	return true;
}
2.代码解释
  1. 首先将撤销的操作标志还原
time.calcularTag = timeReStack.back().calcularTag;// 还原最近撤销的操作
  1. 将当前时间压入撤销栈,作为上一次的操作,并且将重做栈中的栈顶时间作为最新的操作时间,并且将操作时间还原为未操作的状态
timeUnStack.push_back(time);// 正在使用的时间入栈
time = timeReStack.back();// 还原之前撤销的时间
time.calcularTag = 2;// 还原为未进行加减操作的状态
  1. 将已经重做的元素出栈,并且显示最终结果
timeReStack.pop_back();// 已经重做的时间出栈
printf("* 重做成功                                       *\n");
printf("**************************************************\n");

三.其他代码

1.查看历史记录

1.代码展示

bool ViewHistoricalModificationRecords(Time time) {// 查看历史加减操作记录
	printf("******************历史操作记录********************\n");
	if (timeUnStack.empty()) {
		printf("* 没有历史操作记录                               *\n");
		printf("**************************************************\n");
		return true;
	}
	for (int i = 0; i < timeUnStack.size(); i++) {// 遍历撤销栈
		if(timeUnStack[i].calcularTag == 1)
			printf("* %d.加法操作,被加的时间为:%dh %dm %ds               *\n", i + 1, timeUnStack[i].hour, timeUnStack[i].minute, timeUnStack[i].second);
		else if(timeUnStack[i].calcularTag == 0)
			printf("* %d.减法操作,被减的时间为:%dh %dm %ds               *\n", i + 1, timeUnStack[i].hour, timeUnStack[i].minute, timeUnStack[i].second);
		else if(timeUnStack[i].calcularTag == -1){
			printf("* %d.清空操作                                        *\n", i + 1);
		}
		else{
			printf("* 数据有误,操作未知,即将退出程序                 *\n");
			Exit(time);
		}
	}
	printf("**************************************************\n");
	return true;
}

2.代码解释

  1. 其实就是遍历撤销栈,当遇到操作标志为1时,说明是加法,操作标志为0时,说明是减法,操作标志为-1时,说明是清空操作,如果是其他标志都视为程序出现了意外情况,直接退出程序
for (int i = 0; i < timeUnStack.size(); i++) {// 遍历撤销栈
	if(timeUnStack[i].calcularTag == 1)
		printf("* %d.加法操作,被加的时间为:%dh %dm %ds               *\n", i + 1, timeUnStack[i].hour, timeUnStack[i].minute, timeUnStack[i].second);
	else if(timeUnStack[i].calcularTag == 0)
		printf("* %d.减法操作,被减的时间为:%dh %dm %ds               *\n", i + 1, timeUnStack[i].hour, timeUnStack[i].minute, timeUnStack[i].second);
	else if(timeUnStack[i].calcularTag == -1){
		printf("* %d.清空操作                                        *\n", i + 1);
	}
	else{
		printf("* 数据有误,操作未知,即将退出程序                 *\n");
		Exit(time);
	}
}

2.初始化

较为简单,大家应该一眼就能看懂了

bool InitTime(Time& time) {// 初始化时间
	time.hour = 0;
	time.minute = 0;
	time.second = 0;
	time.calcularTag = 2;// 什么操作都没做
	return true;
}

3.查看功能菜单

也是比较简单啦

bool FunctionMenu(Time time) {// 查看功能菜单
	printf("********************开始菜单**********************\n");
	printf("* 输入以下数字启动对应功能:                      *\n");
	printf("* 1.返回开始菜单		                 *\n");
	printf("* 2.时间相加			                 *\n");
	printf("* 3.时间相减			                 *\n");
	printf("* 4.查看历史加减操作		                 *\n");
	printf("* 5.撤销上一次的操作	                         *\n");
	printf("* 6.取消上一次的撤销操作(重做)                   *\n");
	printf("* 7.查看当前时间                                 *\n");
	printf("* 8.清空所有操作                                 *\n");
	printf("* 9.退出程序			                 *\n");
	printf("**************************************************\n");
	return true;
}

4.查看当前时间

简单

bool PrintfTime(Time time) {// 打印时间
	printf("********************查看当前时间******************\n");
	printf("* 当前的时间为: %dh %dm %ds                      *\n", time.hour, time.minute, time.second);
	printf("**************************************************\n");
	return true;
}

5.跳转到相应操作

也是非常简单啦

bool Choice(Time& time) {// 用于选择菜单的跳转到相应功能的中间函数
	printf("\n");
	printf("**************************************************\n");
	printf("* 请输入你要进行的操作:");
	int count = 0;
	scanf_s("%d", &count);// 输入跳转数
	switch (count) {
		case 1:FunctionMenu(time); break;
		case 2:TimeAddition(time); break;
		case 3:SubtractTime(time); break;
		case 4:ViewHistoricalModificationRecords(time); break;
		case 5:Undo(time); break;
		case 6:Redo(time); break;
		case 7:PrintfTime(time); break;
		case 8:Clear(time); break;
		case 9:Exit(time); break;
		default:printf("* 输入的数字有误,请重新输入                      *\n"); return false;
	}
	printf("\n");
	return true;
}

6.结束程序

bool Exit(Time time) {// 退出操作
	printf("**************************************************\n");
	printf("* 最终的时间为:%dh%dm%ds                            *\n", time.hour, time.minute, time.second);
	printf("**************************************************\n");
	printf("* 已退出程序                                     *\n");
	printf("**************************************************\n");
	exit(0);
}
  • 简单说一下exit(0)表示退出程序,无论在程序任何地方都能退出,这里不用return主要是觉得有的时候出错了需要在程序内部直接结束程序,如果用return就只能一级一级的向前返回

7.开始程序

1.代码展示

bool Start() {// 启动小程序,包含了初始化时间,打印时间,以及功能菜单
	Time time;
	int maxtimes = 1;// 最大输入错误限制

	// 初始化
	if (InitTime(time)) {
		printf("**************************************************\n");
		printf("* 初始化成功                                     *\n");
		printf("* 初始时间为: %dh %dm %ds                           *\n", time.hour, time.minute, time.second);
		printf("**************************************************\n");
		printf("* 时间计算器:只能计算时分秒这个三个单位          *\n");
		printf("* 初始时间为0h0m0s,进入时间相加得到自己想要的时间*\n");
		printf("**************************************************\n");
	}
	else{
		printf("**************************************************\n");
		printf("* 初始化失败                                     *\n");
		printf("**************************************************\n");
		return false;
	}
	printf("\n");
	printf("**************************************************\n");
	printf("* 显示开始菜单                                   *\n");
	// 显示开始菜单
	FunctionMenu(time);
	// 输入数字
	while (1) {
		bool choiceTag = Choice(time);
		while (!choiceTag) {
			if (maxtimes > MaxTimes) {
				printf("* 超出最大输入错误次数限制,即将退出程序          *\n");
				Exit(time);
			}
			maxtimes++;
			choiceTag = Choice(time);
		}
	}
}

2.代码解释

  1. 当初始化函数返回true时,说明初始化成功,此时我们需要提示用户已经初始化成功并且打印初始的时间
// 初始化
if (InitTime(time)) {
	printf("**************************************************\n");
	printf("* 初始化成功                                     *\n");
	printf("* 初始时间为: %dh %dm %ds                           *\n", time.hour, time.minute, time.second);
	printf("**************************************************\n");
	printf("* 时间计算器:只能计算时分秒这个三个单位          *\n");
	printf("* 初始时间为0h0m0s,进入时间相加得到自己想要的时间*\n");
	printf("**************************************************\n");
}
else{
	printf("**************************************************\n");
	printf("* 初始化失败                                     *\n");
	printf("**************************************************\n");
	return false;
}
  1. 而后显示开始菜单
printf("**************************************************\n");
printf("* 显示开始菜单                                   *\n");
// 显示开始菜单
FunctionMenu(time);
  1. 接下来进入输入数字的循环,需要一个变量存储当前输入数字后函数的返回值是false还是true
while (1) {
	bool choiceTag = Choice(time);
	while (!choiceTag) {
		// ...
		choiceTag = Choice(time);
	}
}
  1. 如果是true则继续输入下一个数字,如果是false则记录当前输入错误的次数,超过最大输入错误限制直接退出程序
if (maxtimes > MaxTimes) {
	printf("* 超出最大输入错误次数限制,即将退出程序          *\n");
	Exit(time);
}
maxtimes++;

四.展示成果

展示视频戳这里

结语

我感觉应该没什么程序上的漏洞吧,其实这个还可以升级,还可以多增加几个标志为表示撤销和重做等操作,这样的话就可以看到历史的更多操作了
但是我对这个功能没啥需求,如果有需要就下载代码尝试更改一下吧

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐