软件开发与创新——万年历功能新增与代码优化
一、项目名称与来源
上海海洋大学C语言期末大作业
二、原项目运行
运行环境:
系统:Windows11 24H2
cpu:i7-10750H
编译器:Dev c++ 5.11
运行结果:

点击查看代码
#include <stdio.h>
#include <stdlib.h>
// 定义每个月的天数,第一行为平年,第二行为闰年
int dateofmonth[2][12] = {
{31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
{31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}
};
// 英文月份名称数组
char *month1[] = {
"January", "February", "March", "April", "May", "June",
"July", "August", "September", "October", "November", "December"
};
// 中文月份名称数组
char *month2[] = {
"一月", "二月", "三月", "四月", "五月", "六月",
"七月", "八月", "九月", "十月", "十一月", "十二月"
};
// 中文星期名称数组
char *week[7] = {"日", "一", "二", "三", "四", "五", "六"};
// 判断是否为闰年
int isLeapYear(int year) {
return (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0);
}
// 计算指定日期是星期几
int getWeekday(int year, int month, int date) {
int days = 0;
int leap = isLeapYear(year);
for (int i = 0; i < month - 1; i++) {
days += dateofmonth[leap][i];
}
days += date;
return ((year - 1) + (year - 1) / 4 - (year - 1) / 100 + (year - 1) / 400 + days) % 7;
}
// 打印指定年份的日历
void printCalendar(int year, int month_a, int month_b) {
printf("\n\n\n\t—————————————%d 年日历—————————————\n\n\n", year);
for (int i = month_a - 1; i < month_b; i++) {
int firstWeekday = getWeekday(year, i + 1, 1);
int daysInMonth = dateofmonth[isLeapYear(year)][i];
// 打印月份名称
printf("\t%-8s\t\t\t\t\t%6s\n", month1[i], month2[i]);
printf("\t______________________________________________________\n\t ");
// 打印星期名称
for (int j = 0; j < 7; j++) {
printf("%s\t", week[j]);
}
printf("\n\n");
printf("\t# ");
// 打印空白以对齐第一个日期
if (firstWeekday > 0) {
for (int n = 0; n < firstWeekday; n++) {
printf(" \t");
}
printf(" ");
}
// 打印日期
for (int k = 1; k <= daysInMonth; k++) {
if (firstWeekday == 7) {
printf("\n\t# ");
firstWeekday = 0;
}
if (firstWeekday == 6) {
printf("%2d #", k);
} else {
printf("%2d\t ", k);
}
firstWeekday++;
}
// 补齐行尾
if (firstWeekday < 6) {
while (firstWeekday++ != 6) {
printf(" \t");
}
printf(" #");
} else if (firstWeekday == 6) {
printf(" #");
}
printf("\n\t______________________________________________________\n");
printf("\n\n\n\n\n");
}
}
// 查询指定日期是星期几
void queryWeekday() {
int year, month, day;
printf("请输入要查询的日期 年 月 日(2024 1 1):");
scanf("%d %d %d", &year, &month, &day);
printf("\n\n查询结果如下:\n\n%d年%d月%d日是星期%s\n\n\n\n\n", year, month, day, week[getWeekday(year, month, day)]);
}
// 查询指定年份是否为闰年
void queryLeapYear() {
int year;
printf("请输入要查询的年份(2024):");
scanf("%d", &year);
printf("\n\n查询结果如下:\n\n");
if (isLeapYear(year)) {
printf("%d是闰年\n", year);
} else {
printf("%d不是闰年\n", year);
}
}
// 查询指定月份的最大天数
void queryDaysInMonth() {
int year, month;
printf("请输入要查询的月份(2024 1):");
scanf("%d %d", &year, &month);
printf("\n\n查询结果如下:\n\n");
int leap = isLeapYear(year);
printf("%d年%d月有%d天\n\n", year, month, dateofmonth[leap][month - 1]);
}
// 主菜单函数
void showMenu() {
printf(" 万年历查询系统\n\n");
printf("**************************************\n");
printf("1:查询某年某月某日是星期几\n");
printf("2:查询某年是否是闰年\n");
printf("3:打印某年的日历\n");
printf("4.查询某月的最大天数\n");
printf("5:退出\n");
printf("**************************************\n");
printf("\n请选择: ");
}
int main() {
int choice, year;
while (1) {
showMenu();
scanf("%d", &choice);
printf("\n");
switch (choice) {
case 1:
queryWeekday();
break;
case 2:
queryLeapYear();
break;
case 3:
printf("请输入要打印的年份(2024):");
scanf("%d", &year);
printCalendar(year, 1, 12);
break;
case 4:
queryDaysInMonth();
break;
case 5:
return 0;
default:
printf("\n\n\t输入错误,请重新输入\n\n\n\n");
}
}
return 0;
}
三、主要问题
- 代码冗余与重复
逻辑重复:在多个函数中都存在对闰年判断的逻辑调用,如 getWeekday、printCalendar、queryDaysInMonth 等函数,虽然调用的是同一个 isLeapYear 函数,但多次调用会造成一定的代码冗余。
2.错误处理
输入验证:代码中缺乏对用户输入的验证,例如在 queryWeekday、queryLeapYear 等函数中,用户输入的日期可能不符合实际情况(如月份超出 1 - 12 的范围),但代码没有进行相应的验证和处理,可能会导致程序出现异常。
3.可读性与可维护性
变量命名:部分变量命名不够清晰,例如在 getWeekday 函数中,days 变量虽然能表达其存储的是天数,但没有明确说明是从哪个时间点开始计算的天数,降低了代码的可读性。
注释不足:虽然代码中有一些注释,但整体注释不够详细,特别是一些关键逻辑部分,如计算星期几的公式,没有详细解释,不利于后续的维护和扩展。
代码结构:代码整体结构较为复杂,函数内部的逻辑较多,例如 printCalendar 函数中包含了打印月份名称、星期名称、日期以及处理节假日等多个功能,代码过长,可将一些功能拆分成更小的函数,提高代码的可维护性。
四、优化
1.添加节假日显示
点击查看代码
// 打印指定年份的日历
void printCalendar(int year, int month_a, int month_b) {
printf("\n\n\n\t—————————————%d 年日历—————————————\n\n\n", year);
for (int i = month_a - 1; i < month_b; i++) {
int firstWeekday = getWeekday(year, i + 1, 1);
int daysInMonth = dateofmonth[isLeapYear(year)][i];
// 打印月份名称
printf("\t%-8s\t\t\t\t\t%6s\n", month1[i], month2[i]);
printf("\t______________________________________________________\n\t ");
// 打印星期名称
for (int j = 0; j < 7; j++) {
printf("%s\t", week[j]);
}
printf("\n\n");
printf("\t# ");
// 打印空白以对齐第一个日期
if (firstWeekday > 0) {
for (int n = 0; n < firstWeekday; n++) {
printf(" \t");
}
printf(" ");
}
// 打印日期
for (int k = 1; k <= daysInMonth; k++) {
if (firstWeekday == 7) {
printf("\n\t# ");
firstWeekday = 0;
}
int isHoliday = 0;
for (int h = 0; h < sizeof(holidays) / sizeof(holidays[0]); h++) {
if (holidays[h][0] == i + 1 && holidays[h][1] == k) {
isHoliday = 1;
printf("\033[31m%2d(%s)\033[0m\t ", k, holiday_names[holidays[h][2] - 1]);
break;
}
}
if (!isHoliday) {
if (firstWeekday == 6) {
printf("%2d #", k);
} else {
printf("%2d\t ", k);
}
}
firstWeekday++;
}
// 补齐行尾
if (firstWeekday < 6) {
while (firstWeekday++ != 6) {
printf(" \t");
}
printf(" #");
} else if (firstWeekday == 6) {
printf(" #");
}
printf("\n\t______________________________________________________\n");
printf("\n\n\n\n\n");
}
}
2.添加生肖和星座查询
点击查看代码
// 查询指定日期是星期几
void queryWeekday() {
int year, month, day;
printf("请输入要查询的日期 年 月 日(2024 1 1):");
scanf("%d %d %d", &year, &month, &day);
printf("\n\n查询结果如下:\n\n%d年%d月%d日是星期%s\n", year, month, day, week[getWeekday(year, month, day)]);
// 显示星座
int constellation_index;
if ((month == 1 && day >= 20) || (month == 2 && day <= 18)) {
constellation_index = 1;
} else if ((month == 2 && day >= 19) || (month == 3 && day <= 20)) {
constellation_index = 2;
} else if ((month == 3 && day >= 21) || (month == 4 && day <= 19)) {
constellation_index = 3;
} else if ((month == 4 && day >= 20) || (month == 5 && day <= 20)) {
constellation_index = 4;
} else if ((month == 5 && day >= 21) || (month == 6 && day <= 20)) {
constellation_index = 5;
} else if ((month == 6 && day >= 21) || (month == 7 && day <= 22)) {
constellation_index = 6;
} else if ((month == 7 && day >= 23) || (month == 8 && day <= 22)) {
constellation_index = 7;
} else if ((month == 8 && day >= 23) || (month == 9 && day <= 22)) {
constellation_index = 8;
} else if ((month == 9 && day >= 23) || (month == 10 && day <= 22)) {
constellation_index = 9;
} else if ((month == 10 && day >= 23) || (month == 11 && day <= 21)) {
constellation_index = 10;
} else if ((month == 11 && day >= 22) || (month == 12 && day <= 21)) {
constellation_index = 11;
} else {
constellation_index = 0;
}
printf("星座是:%s\n", constellations[constellation_index]);
// 显示生肖
int zodiac_index = (year - 1900) % 12;
printf("生肖是:%s\n\n\n\n", zodiacs[zodiac_index]);
}
3.倒计时功能
点击查看代码
// 倒计时功能
void countdown() {
int year1, month1, day1, year2, month2, day2;
printf("请输入当前日期 年 月 日(2024 1 1):");
scanf("%d %d %d", &year1, &month1, &day1);
printf("请输入目标日期 年 月 日(2024 1 1):");
scanf("%d %d %d", &year2, &month2, &day2);
int days1 = 0, days2 = 0;
for (int y = 1900; y < year1; y++) {
days1 += isLeapYear(y) ? 366 : 365;
}
int leap1 = isLeapYear(year1);
for (int m = 0; m < month1 - 1; m++) {
days1 += dateofmonth[leap1][m];
}
days1 += day1;
for (int y = 1900; y < year2; y++) {
days2 += isLeapYear(y) ? 366 : 365;
}
int leap2 = isLeapYear(year2);
for (int m = 0; m < month2 - 1; m++) {
days2 += dateofmonth[leap2][m];
}
days2 += day2;
int diff = days2 - days1;
if (diff > 0) {
printf("\n距离 %d年%d月%d日 还有 %d 天\n\n\n", year2, month2, day2, diff);
} else if (diff < 0) {
printf("\n %d年%d月%d日 已经过去 %d 天\n\n\n", year2, month2, day2, -diff);
} else {
printf("\n今天就是 %d年%d月%d日\n\n\n", year2, month2, day2);
}
}
4.对代码的优化和精简
点击查看代码
#include <stdio.h>
// 每月天数,[0]平年 [1]闰年
int dateofmonth[2][12] = {{31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
{31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}};
const char *month1[] = {"January", "February", "March", "April", "May", "June",
"July", "August", "September", "October", "November", "December"};
const char *month2[] = {"一月", "二月", "三月", "四月", "五月", "六月",
"七月", "八月", "九月", "十月", "十一月", "十二月"};
const char *week[7] = {"日", "一", "二", "三", "四", "五", "六"};
const int holidays[][3] = {{1, 1, 1}, {2, 14, 2}, {3, 8, 3}, {4, 1, 4}, {5, 1, 5},
{6, 1, 6}, {9, 10, 7}, {10, 1, 8}, {12, 25, 9}};
const char *holiday_names[] = {"元旦", "情人节", "妇女节", "愚人节", "劳动节",
"儿童节", "教师节", "国庆节", "圣诞节"};
const char *constellations[] = {"摩羯座", "水瓶座", "双鱼座", "白羊座", "金牛座", "双子座",
"巨蟹座", "狮子座", "处女座", "天秤座", "天蝎座", "射手座", "摩羯座"};
const int constellation_bounds[12] = {20, 19, 21, 20, 21, 22, 23, 23, 23, 24, 23, 22};
const char *zodiacs[] = {"鼠", "牛", "虎", "兔", "龙", "蛇", "马", "羊", "猴", "鸡", "狗", "猪"};
// 判断闰年
int isLeapYear(int y) { return (y % 4 == 0 && y % 100 != 0) || (y % 400 == 0); }
// 计算星期
int getWeekday(int y, int m, int d) {
int days = d;
for (int i = 0; i < m - 1; i++) days += dateofmonth[isLeapYear(y)][i];
return (y - 1 + (y - 1) / 4 - (y - 1) / 100 + (y - 1) / 400 + days) % 7;
}
// 打印日历
void printCalendar(int y) {
printf("\n\n\n\t—————————————%d 年日历—————————————\n\n\n", y);
for (int m = 0; m < 12; m++) {
int first = getWeekday(y, m + 1, 1), days = dateofmonth[isLeapYear(y)][m];
printf("\t%-8s\t\t\t\t\t%6s\n", month1[m], month2[m]);
printf("\t______________________________________________________\n\t ");
for (int i = 0; i < 7; i++) printf("%s\t", week[i]);
printf("\n\n\t# ");
for (int i = 0; i < first; i++) printf(" \t");
for (int d = 1; d <= days; d++) {
if ((first + d - 1) % 7 == 0) printf("\n\t# ");
int isHoliday = 0;
for (int h = 0; h < sizeof(holidays) / sizeof(holidays[0]); h++)
if (holidays[h][0] == m + 1 && holidays[h][1] == d) {
isHoliday = 1;
printf("\033[31m%2d(%s)\033[0m\t ", d, holiday_names[holidays[h][2] - 1]);
break;
}
if (!isHoliday) printf("%2d\t ", d);
}
printf("\n\t______________________________________________________\n\n\n\n\n");
}
}
// 查询星期、星座、生肖
void queryWeekday() {
int y, m, d;
printf("请输入要查询的日期 年 月 日(2024 1 1):");
scanf("%d %d %d", &y, &m, &d);
int w = getWeekday(y, m, d), c = (m - 1 + (d < constellation_bounds[m - 1] ? 0 : 1)) % 12, z = (y - 1900) % 12;
printf("\n\n查询结果如下:\n\n%d年%d月%d日是星期%s\n星座是:%s\n生肖是:%s\n\n\n\n", y, m, d, week[w], constellations[c], zodiacs[z]);
}
// 查询闰年
void queryLeapYear() {
int y;
printf("请输入要查询的年份(2024):");
scanf("%d", &y);
printf("\n\n查询结果如下:\n\n%d %s闰年\n\n", y, isLeapYear(y) ? "是" : "不是");
}
// 查询月最大天数
void queryDaysInMonth() {
int y, m;
printf("请输入要查询的月份(2024 1):");
scanf("%d %d", &y, &m);
printf("\n\n查询结果如下:\n\n%d年%d月有%d天\n\n", y, m, dateofmonth[isLeapYear(y)][m - 1]);
}
// 倒计时
void countdown() {
int y1, m1, d1, y2, m2, d2;
printf("请输入当前日期 年 月 日(2024 1 1):");
scanf("%d %d %d", &y1, &m1, &d1);
printf("请输入目标日期 年 月 日(2024 1 1):");
scanf("%d %d %d", &y2, &m2, &d2);
int days1 = 0, days2 = 0;
for (int y = 1900; y < y1; y++) days1 += isLeapYear(y) ? 366 : 365;
for (int m = 0; m < m1 - 1; m++) days1 += dateofmonth[isLeapYear(y1)][m];
days1 += d1;
for (int y = 1900; y < y2; y++) days2 += isLeapYear(y) ? 366 : 365;
for (int m = 0; m < m2 - 1; m++) days2 += dateofmonth[isLeapYear(y2)][m];
days2 += d2;
int diff = days2 - days1;
printf("\n%s %d年%d月%d日 %s %d 天\n\n\n",
diff > 0 ? "距离" : diff < 0 ? "" : "今天就是",
y2, m2, d2,
diff > 0 ? "还有" : diff < 0 ? "已经过去" : "",
diff > 0 ? diff : -diff);
}
// 主菜单
void showMenu() {
printf(" 万年历查询系统\n\n");
printf("**************************************\n");
printf("1:查询某年某月某日是星期几\n");
printf("2:查询某年是否是闰年\n");
printf("3:打印某年的日历\n");
printf("4:查询某月的最大天数\n");
printf("5:倒计时功能\n");
printf("6:退出\n");
printf("**************************************\n");
printf("\n请选择: ");
}
int main() {
int choice, year;
while (1) {
showMenu();
scanf("%d", &choice);
printf("\n");
switch (choice) {
case 1: queryWeekday(); break;
case 2: queryLeapYear(); break;
case 3: printf("请输入要打印的年份(2024):"); scanf("%d", &year); printCalendar(year); break;
case 4: queryDaysInMonth(); break;
case 5: countdown(); break;
case 6: return 0;
default: printf("\n\n\t输入错误,请重新输入\n\n\n\n");
}
}
return 0;
}
五、实现效果




6、总结
难点
1.节假日显示处理
难点:要在日历中准确显示节假日,需要对每个日期进行判断,并且要考虑到不同年份的情况。同时,为了增强显示效果,使用了 ANSI 转义序列来设置节假日日期的颜色,这涉及到对控制台输出格式的控制。
耗时原因:需要设计合理的数据结构来存储节假日信息,并且要编写循环逻辑来遍历每个日期并检查是否为节假日。此外,还要处理不同操作系统和终端对 ANSI 转义序列的支持情况,确保显示效果的一致性。
2.代码优化与精简
难点:在保证代码功能完整的前提下,要对代码进行优化和精简,减少代码冗余,提高代码的可读性和可维护性。这需要对代码结构有深入的理解,并且要掌握一些优化技巧,如函数封装、变量命名规范等。
耗时原因:需要反复审查代码,找出可以优化的部分,并进行多次修改和测试,以确保优化后的代码仍然能够正常工作。
思考
1.代码重构和优化
逆向工程的结果可以为代码重构和优化提供依据。通过分析代码的结构和逻辑,可以找出可以改进的部分,并进行相应的重构和优化。例如,可以将一些重复的代码提取成独立的函数,减少代码冗余;可以优化循环逻辑,提高代码的执行效率。
2.发现潜在问题
逆向工程可以帮助发现代码中存在的潜在问题。例如,在分析代码时,可以发现代码中缺乏输入验证的问题,这可能会导致程序在接收到非法输入时出现异常。此外,还可以发现代码中存在的性能问题,如重复计算、循环嵌套过深等,这些问题可能会影响程序的运行效率。
浙公网安备 33010602011771号