下面我将为你提供一个完整、详细、可运行的C语言万年历程序,并逐步解析其核心原理和代码结构。

(图片来源网络,侵删)
核心算法:基姆拉尔森计算公式
万年历的核心问题是如何根据给定的年、月、日,计算出这一天是星期几,目前最常用和最高效的方法是 基姆拉尔森计算公式 (Zeller's Congruence)。
这个公式可以计算从公元1年1月1日以来的任意一天是星期几。
公式:
W = (d + 2m + 3(m+1)/5 + y + y/4 - y/100 + y/400) % 7
W是计算出的星期结果。0代表星期日,1代表星期一,...,6代表星期六。d是日。m是月。注意:如果月份是1月或2月,则需要将其视为上一年的13月或14月,2025年1月,计算时m应为 13,y应为 2025。y是年。同样,如果月份是1月或2月,y需要减1。
C语言完整代码
下面是完整的C语言代码,包含了注释,方便你理解每一部分的功能。

(图片来源网络,侵删)
#include <stdio.h>
#include <stdbool.h>
// 函数声明
bool isLeapYear(int year);
int getDaysInMonth(int year, int month);
int getWeekday(int year, int month, int day);
void printCalendar(int year, int month);
int main() {
int year, month;
printf("欢迎使用万年历!\n");
printf("请输入年份 ( 2025): ");
scanf("%d", &year);
printf("请输入月份 (1-12): ");
scanf("%d", &month);
// 验证输入的月份是否有效
if (month < 1 || month > 12) {
printf("错误:月份必须在1到12之间,\n");
return 1; // 非正常退出
}
printf("\n");
printf("----------------------------\n");
printf(" %d年 %d月\n", year, month);
printf("----------------------------\n");
// 打印星期标题
printf(" 日 一 二 三 四 五 六\n");
// 调用函数打印日历
printCalendar(year, month);
return 0;
}
/**
* @brief 判断是否为闰年
* @param year 年份
* @return 如果是闰年返回 true,否则返回 false
*/
bool isLeapYear(int year) {
if ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)) {
return true;
}
return false;
}
/**
* @brief 获取某年某月的天数
* @param year 年份
* @param month 月份
* @return 该月的天数
*/
int getDaysInMonth(int year, int month) {
// 使用数组来存储每个月的天数,索引0不用,从1开始
int daysInMonth[] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
// 如果是闰年的2月,天数加1
if (month == 2 && isLeapYear(year)) {
return 29;
}
return daysInMonth[month];
}
/**
* @brief 使用基姆拉尔森公式计算星期几
* @param year 年份
* @param month 月份
* @param day 日
* @return 返回 0 (星期日), 1 (星期一), ..., 6 (星期六)
*/
int getWeekday(int year, int month, int day) {
int y, m, d;
// 如果月份是1月或2月,需要转换为上一年的13月或14月
if (month < 3) {
m = month + 12;
y = year - 1;
} else {
m = month;
y = year;
}
d = day;
// 应用基姆拉尔森公式
int weekday = (d + 2*m + 3*(m+1)/5 + y + y/4 - y/100 + y/400) % 7;
// 公式返回的0代表星期六,我们需要调整一下,让0代表星期日
// 公式结果: 0=Sat, 1=Sun, 2=Mon, 3=Tue, 4=Wed, 5=Thu, 6=Fri
// 我们想要: 0=Sun, 1=Mon, ..., 6=Sat
// 如果结果是0(星期六),我们让它变成6,其他情况减1。
if (weekday == 0) {
return 6;
} else {
return weekday - 1;
}
}
/**
* @brief 打印指定年月的日历
* @param year 年份
* @param month 月份
*/
void printCalendar(int year, int month) {
int daysInMonth = getDaysInMonth(year, month);
int weekdayOfFirstDay = getWeekday(year, month, 1); // 获取该月1日是星期几
int dayCounter = 1;
// 打印前面的空格
for (int i = 0; i < weekdayOfFirstDay; i++) {
printf(" "); // 4个空格,对齐日期
}
// 打印日期
while (dayCounter <= daysInMonth) {
printf("%4d", dayCounter); // 使用%4d保证每个日期占4个字符宽度,对齐
dayCounter++;
// 如果当前是星期六(即6),或者已经打印完该月最后一天,则换行
if ((dayCounter - 1 + weekdayOfFirstDay) % 7 == 0 || dayCounter > daysInMonth) {
printf("\n");
}
}
}
代码分步解析
主函数 main()
- 输入:程序首先提示用户输入年份和月份。
- 验证:检查月份是否在1-12的有效范围内,如果无效则报错退出。
- 调用:调用
printCalendar(year, month)函数来执行核心的打印任务。 - 输出:打印一个简单的标题,然后调用函数打印日历主体。
辅助函数
这些函数负责完成具体的计算任务,使主函数更简洁。
-
bool isLeapYear(int year)- 功能:判断给定的年份是否是闰年。
- 逻辑:遵循闰年规则:能被4整除但不能被100整除,或者能被400整除。
- 返回值:
true(是闰年) 或false(不是闰年)。
-
int getDaysInMonth(int year, int month)- 功能:获取某年某月的天数。
- 逻辑:
- 定义一个数组
daysInMonth,存储了“非闰年”中每个月的天数,使用数组可以方便地查询,效率高。 - 如果月份是2月,
isLeapYear(year)返回true,则返回29天,否则返回数组中的值。
- 定义一个数组
- 返回值:该月的天数(28, 29, 30, 或 31)。
-
int getWeekday(int year, int month, int day)
(图片来源网络,侵删)- 功能:万年历的核心,计算给定日期是星期几。
- 逻辑:
- 处理1月和2月的情况:将它们视为上一年的13月和14月。
- 代入基姆拉尔森公式进行计算。
- 对公式结果进行调整,使其符合
0=星期日的习惯。
- 返回值:0到6,分别代表星期日到星期六。
核心打印函数 printCalendar(int year, int month)
这个函数负责将日历格式化输出到屏幕上。
-
获取信息:
- 调用
getDaysInMonth()获取该月总共有多少天。 - 调用
getWeekday()获取该月第一天是星期几,这个信息至关重要,它决定了日历第一行前面需要空出多少个位置。
- 调用
-
打印空格:
- 使用一个
for循环,循环次数等于第一天是星期几(weekdayOfFirstDay)。 - 每次循环打印4个空格,用于对齐日期,如果1号是星期三(
weekdayOfFirstDay = 3),那么就在前面打印3组空格。
- 使用一个
-
打印日期:
- 使用一个
while循环,从dayCounter = 1开始,循环到该月总天数。 - 使用
printf("%4d", dayCounter)来打印每一天。%4d确保每个数字都占据4个字符宽度,这样无论是个位数还是两位数,都能完美对齐。 - 换行逻辑:这是打印的关键,每当打印完一个星期六(即
(dayCounter - 1 + weekdayOfFirstDay) % 7 == 0这个条件成立时),或者打印完该月最后一天时,就执行一次换行 (\n),这个公式巧妙地结合了当前日期和起始星期,来判断是否应该换行。
- 使用一个
如何编译和运行
- 保存代码:将上面的代码复制并粘贴到一个文本文件中,命名为
calendar.c。 - 打开终端/命令行:
- 在Windows上,你可以使用
cmd或PowerShell。 - 在macOS或Linux上,打开
Terminal。
- 在Windows上,你可以使用
- 编译代码:使用GCC编译器进行编译。
gcc calendar.c -o calendar
gcc是编译器命令。calendar.c是你的源文件名。-o calendar指定输出的可执行文件名为calendar。
- 运行程序:
./calendar
- 交互:程序会提示你输入年份和月份,输入后按回车,即可看到该月的日历。
示例运行
欢迎使用万年历!
请输入年份 ( 2025): 2025
请输入月份 (1-12): 10
----------------------------
2025年 10月
----------------------------
日 一 二 三 四 五 六
1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30 31
这个程序结构清晰,逻辑完整,是学习C语言和算法的一个很好的范例,你可以基于这个代码进行扩展,比如增加“打印全年日历”的功能,或者美化输出格式等。
