嵌入式C语言练习总结:基于1900年基准的日历程序实现
本文总结了一个基于1900年基准的日历程序实现,对比了两种C语言方案: 方案一采用二维数组存储日期,结构清晰但内存占用较高(42个int空间),适合需要扩展功能的场景;方案二直接计算偏移打印,内存占用极低但格式控制较难,更适合资源受限的嵌入式设备。两种方案均实现了闰年判断、日期偏移计算和格式化输出等核心功能。 练习中遇到的问题包括星期偏移计算错误和日期对齐混乱,通过基准测试和统一格式解决。总结指出
嵌入式C语言练习总结:基于1900年基准的日历程序实现
一、练习需求回顾
- 基准规则:以1900年1月1日(星期一)为时间起点,计算目标年月的日期分布;
- 核心功能:输入年/月(支持两种输入格式:空格分隔/斜杠分隔),输出该月日历(含星期表头、日期对齐);
- 关键逻辑:闰年判断、目标年月1日的星期计算、日期格式化打印;
- 边界校验:输入年份≥1900、月份1-12,否则提示错误。
二、两种实现方案对比
本次练习编写了两种不同思路的日历程序,核心差异体现在「日期存储与打印逻辑」上,各有优劣,适配不同场景:
方案一:二维数组填充法(结构化存储)
核心思路
用 6x7 二维数组模拟日历表格(最多6行即可覆盖所有月份的日期分布),先计算目标月1日的星期偏移,再将日期按顺序填充到数组对应位置,最后遍历数组打印。
关键代码解析
// 1. 填充二维数组:按星期偏移填充日期
void fill_array(int array[][7], int len, int year, int month) {
int days = getdayofmonth(year, month); // 获取当月总天数
int day1 = form1900days(year, month); // 计算1日是星期几(1-7)
int count = 0, fill_day = 1;
for(j = 0;j<len;j++){
for(i=0;i<7;i++){
if(fill_day>days) continue;
if(count>=day1-1) array[j][i] = fill_day++; // 偏移后开始填充
count++;
}
}
}
// 2. 打印数组:空日期显示空格,日期对齐
void show_array(int a[][7],int len) {
for(j=0;j<6;j++){
for(i=0;i<7;i++){
if(0 == a[j][i]) printf(" \t");
else printf("%3d\t",a[j][i]);
}
printf("\n");
}
}
优点
- 结构清晰:数组直观对应日历表格,逻辑与现实场景一致,易理解和调试;
- 格式可控:通过数组索引精准控制日期位置,对齐效果好;
- 扩展性强:可基于数组轻松添加「标记特定日期」「跨月对比」等功能。
缺点
- 内存占用:需额外分配
6x7=42个int类型空间(嵌入式场景中虽可忽略,但体现了「资源冗余」); - 代码量略多:需单独编写数组填充和打印函数。
方案二:直接计算偏移打印法(无存储优化)
核心思路
不使用数组存储,直接计算目标月1日的星期偏移(y 值),先打印偏移对应的空格,再按顺序打印日期,每7个日期换行(利用 (y + current_day) %7 ==0 判断周日)。
关键代码解析
// 1. 计算1900年到目标年前的总天数偏移
int x=(leap_count*366+normal_count*365)%7;
// 2. 计算目标月1日的星期偏移(0=周一,6=周日)
int y=(x+pre)%7;
// 3. 先打印偏移空格,再打印日期
for (int k = 0; k < y; k++) printf(" "); // 4字符空格对齐
while (current_day <= total_days) {
printf("%4d", current_day);
if ((y + current_day) % 7 == 0) printf("\n"); // 周日换行
current_day++;
}
优点
- 资源高效:无需额外数组存储,内存占用极低(嵌入式开发核心优化点);
- 代码简洁:逻辑连贯,无冗余函数,执行效率高;
- 适配嵌入式:符合嵌入式设备「内存有限」的特点,适合资源紧张的场景。
缺点
- 格式调试难度高:空格数量、日期对齐需手动计算(如
%4d控制宽度),易出现错位; - 扩展性弱:若需修改日期显示规则(如标记节假日),需重构打印逻辑。
两种方案核心差异总结
| 对比维度 | 方案一:二维数组填充法 | 方案二:直接偏移打印法 |
|---|---|---|
| 内存占用 | 略高(数组存储) | 极低(无额外存储) |
| 代码可读性 | 高(结构化) | 中(逻辑连贯但需理解偏移) |
| 格式控制 | 易(数组索引定位) | 难(手动控制空格/宽度) |
| 扩展性 | 强(支持复杂功能扩展) | 弱(重构成本高) |
| 嵌入式适配性 | 一般 | 优秀(资源优化) |
三、核心知识点提炼
本次练习覆盖了嵌入式C语言开发的核心基础知识点,也是实际项目中高频使用的技能:
1. 闰年判断逻辑(时间类程序核心)
闰年规则:能被4整除但不被100整除,或能被400整除,两种实现方案的核心逻辑一致:
// 方案一:完整条件判断
int isleapyear(int year) {
if ((0 == year % 4 && 0 != year % 100) || 0 == year % 400) return 1;
else return 0;
}
// 方案二:简洁表达式(嵌入式常用写法,节省代码量)
int is_leap(int year) {
return (year%4==0&&year%100!=0)||(year%400==0);
}
嵌入式应用场景:实时时钟(RTC)驱动、日期计算类外设(如日志模块)中必备逻辑。
2. 日期偏移计算(数学逻辑与编程结合)
核心是计算「1900年1月1日到目标月1日的总天数」,再通过 %7 得到星期偏移:
- 总天数 = 1900年到目标年前一年的总天数 + 目标年到目标月前一月的总天数;
- 星期偏移 = 总天数 %7(注意基准对齐:1900年1月1日是周一,需确保偏移值对应正确星期)。
关键坑:偏移值与星期的映射关系(如方案一返回 days%7+1 对应1-7,方案二 y 对应0-6),需统一逻辑避免日期错位。
3. 数组与指针应用(C语言核心特性)
- 方案一:二维数组的初始化与遍历(模拟表格结构,嵌入式中常用于数据缓存);
- 方案二:数组指针
int *month_days动态选择平年/闰年数组(避免冗余代码,体现「多态思想」雏形):int *month_days; if(is_leap(year)) month_days=B; // 闰年数组 else month_days=A; // 平年数组
4. 格式化输出(用户交互与调试必备)
嵌入式开发中,串口打印/日志输出需严格控制格式,本次练习用到:
- 空格填充:
printf(" ")实现日期偏移对齐; - 宽度控制:
%3d%4d确保日期占固定宽度,避免错位; - 条件打印:空日期打印空格,避免显示0值影响美观。
5. 输入校验(程序健壮性核心)
方案二添加了完善的输入校验,是嵌入式程序的必备环节(避免非法输入导致程序崩溃):
if(year<1900||month<1||month>12) {
printf("年份要大于等于1900,月份要在1月到12月\n");
return 0;
}
嵌入式场景:外设输入、串口指令解析等场景,需严格校验输入合法性,否则可能导致硬件异常。
四、开发中遇到的问题与解决方案
1. 星期偏移计算错误(高频坑)
- 问题现象:目标月1日的星期与实际不符(如2025年11月1日是周六,但程序显示为周五);
- 原因:总天数计算遗漏/多算1天,或偏移值与星期的映射关系错误;
- 解决:以已知日期验证(如1900年1月1日是周一,输入1900 1 测试),逐步排查总天数计算逻辑。
2. 日期对齐混乱
- 问题现象:日期超出星期列,或空格数量不匹配;
- 原因:格式化输出的宽度控制不一致(如方案一用
\t,方案二用%4d); - 解决:统一宽度单位(如均用4字符宽度),确保空格和日期占比一致。
3. 闰年2月天数错误
- 问题现象:闰年2月显示28天,或平年显示29天;
- 原因:月份数组索引错误(如
mon[1]对应2月,而非mon[2]); - 解决:明确数组索引与月份的对应关系(索引0=1月,索引1=2月),结合闰年判断修改数组值。
五、优化方向(嵌入式场景进阶)
基于本次练习,可进一步优化程序,使其更贴近嵌入式实际项目需求:
- 输入格式兼容:支持空格、斜杠、横杠等多种分隔符(如
2025 112025/112025-11),提升用户体验; - 资源极致优化:方案二中可将平年/闰年数组改为宏定义或const数组(嵌入式中const变量存储在ROM,节省RAM);
- 功能扩展:添加「打印全年日历」「标记今天日期」「计算两个日期差值」等功能;
- 错误处理强化:对非数字输入、超出范围的日期(如2025年2月30日)进行拦截,返回明确错误提示;
- 适配嵌入式硬件:结合串口外设,实现通过串口输入年/月,输出日历到串口终端(实际项目中常用场景)。
六、总结与感悟
本次日历程序练习看似简单,却覆盖了嵌入式C语言开发的核心能力:
- 逻辑严谨性:时间计算类程序对逻辑准确性要求极高,一个微小的偏移错误(如少加1天)会导致整体功能失效,这与嵌入式硬件控制的「精准性要求」高度一致;
- 资源优化意识:两种方案的对比让我深刻体会到,嵌入式开发中「内存占用」是关键考量因素——方案二虽代码调试难度略高,但因无额外存储,更适合资源有限的MCU;
- 代码可读性与可维护性:方案一的结构化设计虽占用少量内存,但逻辑清晰,便于后续扩展,这提示我在实际项目中需平衡「资源优化」与「可维护性」;
- 基础知识点的重要性:闰年判断、数组操作、格式化输出等基础知识点是嵌入式开发的基石,只有熟练掌握,才能在复杂项目中快速定位问题、编写高效代码。
#include <stdio.h>
void show_title()
{
//printf("1\t2\t3\t4\t5\t6\t7\n");
for(int i = 1 ;i<8;i++)
{
printf("%3d\t",i);
}
printf("\n");
}
int isleapyear(int year)
{
if ((0 == year % 4 && 0 != year % 100) || 0 == year % 400)
{
return 1;
}
else
{
return 0;
}
return 0;
}
int getdayofmonth(int year, int month)
{
int mon[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
if (isleapyear(year))
{
mon[1] = 29;
}
return mon[month - 1];
}
int form1900days(int year, int month)
{
int days = 0;
int i = 0;
int j = 0;
for (i = 1900; i < year; i++) // 2025
{
for (j = 1; j <= 12; j++)
{
days += getdayofmonth(i, j);
}
}
for (j = 1; j < month; j++)
{
days += getdayofmonth(year, j);
}
//printf("total days %d\n",days);
return (days ) % 7+1; // 0-6 1 -7
}
void fill_array(int array[][7], int len, int year, int month)
{
int days = getdayofmonth(year, month); // 2025 11 25
//printf(" month is days %d\n", days);
int day1 = form1900days(year, month);
//printf("day1 :%d\n", day1);
int i = 0;
int j = 0 ;
int count = 0 ;
int fill_day = 1;
for(j = 0;j<len;j++)
{
for(i=0;i<7;i++)
{
if(fill_day>days)
{
continue;
}
if(count>=day1-1)
{
array[j][i] = fill_day++;
}
count++;
}
}
}
void show_array(int a[][7],int len)
{
int i = 0 ;
int j = 0 ;
for(j=0;j<6;j++)
{
for(i=0;i<7;i++)
{
if(0 == a[j][i])
{
printf(" \t");
}
else
{
printf("%3d\t",a[j][i]);
}
}
printf("\n");
}
}
int main(int argc, char **argv)
{
int year = 0, month = 0;
int array[6][7] = {0};
printf("input year month:");
scanf("%d%d", &year, &month);
show_title();
fill_array(array, 6, year, month);
show_array(array,6);
return 0;
}
第二种
#include "stdio.h"
int A[] = {31,28,31,30,31,30,31,31,30,31,30,31}; // 平年
int B[] = {31,29,31,30,31,30,31,31,30,31,30,31}; // 闰年
int is_leap(int year)
{
return (year%4==0&&year%100!=0)||(year%400==0);
}
int main()
{
int year=0,month=0;
printf("请输入年/月");
scanf("%d/%d",&year,&month);
if(year<1900||month<1||month>12)
{
printf("年份要大于等于1900,月份要在1月到12月\n");
return 0;
}
int leap_count=0,normal_count=0;
for(int i=1900;i<year;i++)
{
if(is_leap(i))
{
leap_count++;
}
else {
normal_count++;
}
}
int x=(leap_count*366+normal_count*365)%7;
int *month_days;
if(is_leap(year))
{
month_days=B;
}
else {
month_days=A;
}
int pre=0;
for(int j=0;j<month-1;j++)
{
pre+=month_days[j];
}
int y=(x+pre)%7;
printf("------------%d年%d月---------\n",year,month);
printf(" 一 二 三 四 五 六 日\n");
printf("----------------------------\n");
for (int k = 0; k < y; k++)
{
printf(" ");
}
int current_day = 1;
int total_days = month_days[month - 1];
while (current_day <= total_days) {
printf("%4d", current_day);
if ((y + current_day) % 7 == 0) {
printf("\n");
}
current_day++;
}
if ((y + total_days) % 7 != 0) {
printf("\n");
}
printf("-------------------------\n");
return 0;
}
更多推荐
所有评论(0)