嵌入式C语言练习总结:基于1900年基准的日历程序实现

一、练习需求回顾

  1. 基准规则:以1900年1月1日(星期一)为时间起点,计算目标年月的日期分布;
  2. 核心功能:输入年/月(支持两种输入格式:空格分隔/斜杠分隔),输出该月日历(含星期表头、日期对齐);
  3. 关键逻辑:闰年判断、目标年月1日的星期计算、日期格式化打印;
  4. 边界校验:输入年份≥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月),结合闰年判断修改数组值。

五、优化方向(嵌入式场景进阶)

基于本次练习,可进一步优化程序,使其更贴近嵌入式实际项目需求:

  1. 输入格式兼容:支持空格、斜杠、横杠等多种分隔符(如 2025 11 2025/11 2025-11),提升用户体验;
  2. 资源极致优化:方案二中可将平年/闰年数组改为宏定义或const数组(嵌入式中const变量存储在ROM,节省RAM);
  3. 功能扩展:添加「打印全年日历」「标记今天日期」「计算两个日期差值」等功能;
  4. 错误处理强化:对非数字输入、超出范围的日期(如2025年2月30日)进行拦截,返回明确错误提示;
  5. 适配嵌入式硬件:结合串口外设,实现通过串口输入年/月,输出日历到串口终端(实际项目中常用场景)。

六、总结与感悟

本次日历程序练习看似简单,却覆盖了嵌入式C语言开发的核心能力:

  1. 逻辑严谨性:时间计算类程序对逻辑准确性要求极高,一个微小的偏移错误(如少加1天)会导致整体功能失效,这与嵌入式硬件控制的「精准性要求」高度一致;
  2. 资源优化意识:两种方案的对比让我深刻体会到,嵌入式开发中「内存占用」是关键考量因素——方案二虽代码调试难度略高,但因无额外存储,更适合资源有限的MCU;
  3. 代码可读性与可维护性:方案一的结构化设计虽占用少量内存,但逻辑清晰,便于后续扩展,这提示我在实际项目中需平衡「资源优化」与「可维护性」;
  4. 基础知识点的重要性:闰年判断、数组操作、格式化输出等基础知识点是嵌入式开发的基石,只有熟练掌握,才能在复杂项目中快速定位问题、编写高效代码。
#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;
}
Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐