
简介这份汇编语言万年历显示程序文档面向正在学习《微机接口与汇编语言》并需要完成课程设计的学生解决从需求分析到源码实现一整套写作与编码参考的问题。压缩包内仅含1个doc文件体积约581KB属于以设计报告为主体、附带源程序清单的文档型资料可直接用于对照排版与内容组织。内容按需求与思路分析、概要设计、详细设计、程序实现四部分展开输入模块借助INT 21H的0AH功能接收年月日并校验合法性闰年判断子程序结合月份天数表确定当月最大天数显示模块用蔡勒公式推算当月第一天是星期几再按行列定位输出日历并调用INT 10H的06H功能将星期六、日两列置为红色遇到错误输入则提示重新输入。目前已有837人学习适合需要理解蔡勒公式、光标定位与彩色显示细节的汇编初学者参考。1. 用汇编写万年历真正卡住人的不是打印 1 到 31提起用汇编实现万年历多数人的第一反应是「不就是循环打印 1 到 31」。真坐到屏幕前敲代码最先崩掉的往往是另一件事本月 1 号到底该落在第几列。这个位置由输入的年月决定中间要穿过闰年判断、每月天数表和一条把年月日换算成星期几的公式任何一个环节错一位整月的格子就全部错位。更棘手的是汇编里没有现成的字符串拼接、光标对象和颜色属性每一次光标跳格、每一段红色区间都要用 INT 10H 和 INT 21H 自己拼出来。这份程序把输入缓冲区、闰年判断、蔡勒公式和颜色窗口逐段拆开正好能看清一个日历从键盘敲入到字符上屏是怎么一层层被组织起来的。2. 输入缓冲区INT 21H 0AH 的「账头」结构为什么不能省键盘输入看似只是「读一行字符」但在 DOS 中断体系里它是一个带账头、带结束符的固定结构。程序没有用 INT 21H 的 01H 逐字符读取而是选了 0AH 一次性接收整行——这背后有一个很现实的原因逐字符读取需要自己处理退格、回显、换行稍有遗漏就会让输入界面出现残影0AH 把这些细节全部包进了中断里程序只需要读缓冲区。2.1 9 个字节怎么划分缓冲区在数据段里是这样定义的STR1 DB 9 ; 允许输入的最大字符数含回车 DB ? ; 实际输入的长度不含回车由 0AH 回填 DB 9 DUP(?) ; 9 字节接收区单独看这三行没有感觉把它和 0AH 的约定对照起来才清楚。0AH 要求缓冲区的第一个字节预置「最大字符数包含回车键」调用时把首地址偏移交给 DX按下回车后中断会把「实际字符数不含回车」写进第二个字节字符本身从第三个字节开始依次存放。也就是说 9 个字节的容量对应最多 8 个有效字符加 1 个回车刚好装下输入的YYYYMMDD这一串。这就是为什么容量规划必须加 2一个字节给最大长度一个字节给实际长度剩下的才是数据。少了这两个字节的账头0AH 就会把长度写进数据区后面的字符解析全部错位。2.2 字符到数值循环乘 10 加当前位缓冲区里躺着的仍然是 ASCII 码要变成年份、月份、日这三个整数需要单独做一次转换。程序用的是一个很典型的「乘 10 累加」循环LEA DI, STR12 ; DI 指向字符区起点 MOV CX, 4 ; 年份固定取 4 位 MOV AX, 0 ; AX 作为累加器 P1: MOV BL, [DI] ; 取一位字符 SUB BL, 30H ; 0~9 的 ASCII 减 0x30 得到真值 MOV BH, 0 MOV P, 10 MUL P ; AX AX * 10DX 高位忽略 ADD AX, BX ; AX 本位数字 INC DI DEC CX JNZ P1 MOV YEAR, AX ; 例如 2009 → 0x07D9逻辑本身不复杂关键在于三个隐含约束。第一SUB BL, 30H只在输入字符确实是数字时才成立如果键盘上敲进了字母这一步得到的就是一个巨大的偏移量累加结果会飞到年份字段之外所以后面的合法性校验不能省。第二MUL P用的是 16 位无符号乘法结果放在 DX:AX 中程序只用 AX说明它默认四年份不会超过 65535这也就限定了年份的有效范围。第三字符串偏移STR12和STR16、STR18是写死的意味着输入格式必须是严格的 8 位YYYYMMDD多一位少一位都会读错位置。这三条约束不是缺陷而是这类小工具在汇编里为了省寄存器、省指令而做出的硬性约定。2.3 校验的顺序比校验本身更容易出错输入转换成数字后程序没有立刻拿去算星期几而是先做了一轮边界检查CMP MONTH, 1 JC ERROR ; 月份 1 CMP MONTH, 13 JNC ERROR ; 月份 13 CMP DAY, 1 JC ERROR ; 日 1 CALL GETN ; 先算出当月 1 号是星期几 → N MOV DI, OFFSET BUF1 DEC MONTH DEC MONTH ADD DI, MONTH MOV CL, [DI] ; CL 当月最大天数 CMP BYTE PTR DAY, CL JA ERROR ; 日 当月最大天数这里有一个顺序上的讲究日期上限必须放在闰年判断和BUF1改写之后才能比较。原因是BUF1里二月那一格初始是一个问号程序先靠CALL PR判断闰年再把 28 或 29 写进BUF1[1]只有这时「当月最大天数」才是可靠的。注意上面DEC MONTH出现了两次严格意义上会让 1 月索引退到BUF1之前一个字节属于原稿遗留的边界问题。正确写法应当只DEC一次或直接用DI OFFSET BUF1 MONTH - 1。自己复现这段逻辑时这里必须确认。3. 蔡勒公式从年月日到星期几为什么 1 月和 2 月要借上一年的年份日历显示的核心是一个映射给定年月日算出它是星期几。程序选择了蔡勒公式原因很直接——它只用整数四则运算不依赖查表也不需要事先知道前一个月的排布非常适合放进一段子程序里反复调用。3.1 每一项在算什么程序里实现的版本是W [C/4] - 2C y [y/4] [13(M1)/5] d - 1其中 C 取「年份 / 100」的整数部分y 取年份后两位M 是月份d 是日。这个式子里最有意思的是[13(M1)/5]这一项——它不是某种玄学常数而是一个把 12 个月的天数差异压缩成一个线性项的近似3 月到 12 月的月长在 30 和 31 之间摆动用13(M1)/5取整就能刚好贴合这种摆动从而避免为每个月单独建一张偏移表。[C/4] - 2C则是把百年跨度和世纪闰年规则一起折进去让公式在跨世纪时依然成立。程序实现这段时没有做任何浮点运算全部走移位和整数除法MOV BL, 100 MOV AX, YEAR DIV BL ; AL 年份/100世纪AH 年份后两位 MOV C2, AL MOV Y, AH MOV CL, 2 MOV BL, C2 SHR BL, CL ; BL [C/4]除以 4 用右移两位完成 SHL C2, 1 ; C2 2C SUB BL, C2 ; BL [C/4] - 2C ADD BL, Y ; BL y SHR Y, CL ; Y [y/4] ADD BL, Y ; BL [C/4] - 2C y [y/4]这里用SHR代替除法是因为公式要求的就是「向下取整」而对正整数来说右移两位和整除 4 是等价的于是省掉了一次DIV调用。注意DIV BL这一步的副作用AX 的低字节被替换成商、高字节被替换成余数。这是 8086 除法的默认行为利用它可以一次拿到 C 和 y也是这段代码能写得这么紧凑的原因。3.2 1 月和 2 月回退一年的真正原因公式里的13(M1)/5项是按 3 月到次年 2 月这个顺序拟合的因为公历的闰日恰好落在 2 月末把 1 月和 2 月挪到上一年的 13 月和 14 月就能让整年的月长序列变得本文还有配套的精品资源点击获取