前言
编写汇编语言程序,实现从文件(其中有n位学生的某科成绩)读入成绩后显示各个成绩段程序。
本文参考这两位大佬的文章(十分感谢这两个大佬的启发)
问题点
全部代码在下方,主页的不知道可不可以免费下载
1.读取文件部分
代码如下(示例):本程序是将数据先读入一个数组在转换成bcd码存到另一个要操作的数组
mov dx , offset file ;dx获取file的偏移地址
mov al , 0
mov ah , 3dh
int 21h ;打开文件,只读;ax返回文件代号
;jc error11 ;若打开出错,转error
mov handle , ax ;保存文件句柄
mov bx , ax ;文件句柄
mov cx , 255 ;读取255字节
mov dx , offset buf ;获取buf的偏移地址
mov ah , 3fh
int 21h ;从文件中读255字节→buf
;jc error11 ;若读出错,转error
mov bx , ax ;实际读到的字符数送入bx
mov buf[bx] , '$' ;在文件结束处放置一“$”符
MOV dx , offset buf[0]
MOV AH , 09H
int 21h ;显示文件内容
mov bx , handle ;文件句柄
mov ah , 3eh
int 21h ;关闭文件
mov SI,offset buf ;将读入的数组首地址给源变址寄存器
INPUT: mov AL,[SI] ;将buf数组中第一个数据给AL
inc SI ;SI自增,指向buf第二个数据的位置
CMP AL,44H ;是否为结束字符A
JZ ENDINPUT ;如果是跳转
CMP AL,20H ;是否为空格
JZ SAVE ;如果是跳转
CMP AL,30H
JB ERROR
CMP AL,39H
JA ERROR;确认ascii码对应的是0到9
SUB AL,30H ;变为真数
MOV CL,4
SHL BX,CL ;左移四位.变为BCD码
ADD BL,AL
JMP INPUT
SAVE: MOV SCORE[DI],BX;将bx中的以bcd码存的数据(最多四位bcd码)
;存入SCORE数组中的第一个位置
ADD DI,2 ;将目的变址寄存器加二(也就是16位)指向下一个位置
ADD sii1,1;用变量记录存入数据个数
XOR BX,BX;异或,即清零作用
JMP INPUT
ENDINPUT: MOV SCORE[DI],BX ;保存最后一个数据
XOR DI,DI
ADD sii1,1
mov dx,sii1
ADD DI,dx;将变量给DI
MOV DL,0AH
MOV AH,02H
INT 21H ;留出一行空格(输出换行)
MOV DX,OFFSET ENDTIPS ;显示输出提示
MOV AH,09H
INT 21H ;DI得到个数,DI保持不变化
2.BCD码与十六进制转换
bcd码就是用四位二进制(一位十六进制)表示一位十进制数;
例如:十进制数4567 对应的十六进制为11D7
对应的bcd码为4567(以十六进制(4567)的存储方式保存也就是0100 0101 0110 0111)但是此时代表的数据是十进制四千五百六十七
(1)十六进制转bcd码
MOV AX,BX;初始的十六进制数在BX中
MOV BX,0000H
MOV CX,1000
DIV CX;先将数据除以1000,得到千位的值
;比如数据为4321
;那么此时得到的商4放在AX中,得到的余数321放在DX中
MOV CL,4
AND AX,000FH;取AX中低四位,也就是四位二进制表示一个十进制数即bcd码
ADD BX,AX;把上面结果也就是4放在BX
ROL BX,CL;把得到的结果循环左移4位,也就是放在了第二位十六进制数的位置也就是0040h
MOV AX,DX ;余数再放到AX里面
MOV CL,100;余数再除100,也就是321除100
DIV CL;商放在AL里,余数放在AH里
PUSH AX;入栈,保存AX数值
AND AX,000FH;取低四位也就是3
ADD BX,AX;加和后就是0043
MOV CL,4
ROL BX,CL;再左移,0430
POP AX;把刚才的数据取出
MOV AL,AH;余数再除10
AND AX,00FFH;取刚才的余数
MOV CL,10
DIV CL;商2放在AL,余数1放在AH
AND AX,000FH
ADD BX,AX;取低四位也就是2放在BX,也就是0432
MOV CL,4
ROL BX,CL;再左移
ADD BL,AH;放入,那么此时BX结果为4321
(2)bcd码转换成十六进制
MOV AX, BX ;初始bcd码在BX中
PUSH AX
AND AX,0F000H;取千位数
MOV CL, 4 ;移位取出千位数
SHR AX, CL
MOV DH, AH
POP AX
PUSH AX
AND AX, 0F00H ;取百位数
MOV DL, AH
POP AX
PUSH AX
MOV AX, DX
AAD
;指令是在作除法前用于调整寄存器AH和AL之值,它是把二个寄存器中单BCD码组成一个十进制数值,其调整规则如下:
;AL←AH*10+AL,AH←0
;受影响的标志位:PF、SF和ZF(AF、CF和OF等都是无定义)
;MOV AX, 0502H MOV BL, 10D AAD ;AH=0, AL=52H DIV BL ;AH=2(余数), AL=5(商)
MOV DI, 100 ;乘100
MUL DI
MOV SI, AX
POP AX
PUSH AX
MOV CL, 4 ;移位取出十位数
SHR AX, CL
AND AX, 000FH
MOV DH, AL
POP AX
PUSH AX
AND AX, 000FH ;取AL低四位,个位数
MOV DL, AL
POP AX
MOV AX, DX
AAD
ADD AX, SI
MOV BX, AX ;转换结果给BX
其余部分实现都是基本的操作,一些打印操作参考int 21h有关命令即可
全部代码(需要一个txt文档放在dos目录下的c盘,自己可以更改file声明行的路径)
环境可以使用ASM CoolKits软件,非常方便,也可以配dosbox。
DATA SEGMENT
INPUTTIPS DB 'Please input score,divid with " ":$'
ERRORTIPS DB 'input ERROR!,please try again!$'
ENDTIPS DB 'results:$'
STRINGMAX DB '|max score |$'
STRINGMIN DB '|min score |$'
STRINGNUM DB '|total num |$'
STRING_60 DB '|Less than 60 |$'
STRING60_70 DB '|From 60 to 69 |$'
STRING70_80 DB '|From 70 to 79 |$'
STRING80_90 DB '|From 80 to 89 |$'
STRING90_100 DB '|From 90 to 100 |$'
STRINGAVG DB '|average score |$'
STRINGLINE DB '+===============+=============$'
ALL100 DB '100%$'
BAI00 DB '100.0$'
SCORE DW 60 DUP(0) ;分配存放空间;重复分配60次值为0的16位数据的空间
S_60 DW 0 ;定义变量
S60_70 DW 0
S70_80 DW 0
S80_90 DW 0
S90_100 DW 0
AVG DW 0
file db 'c:\1.txt' , 0 ;文件名,dosbox 设置的c盘下的路径
buf dw 256 dup(0) ;文件内容暂存区
;error_message db 0ah , 'error !' , '$' ;出错时的提示
handle dw ? ;保存文件号
sii1 dw 0
DATA ENDS
STACK SEGMENT
DB 200 DUP(0)
STACK ENDS
CODE SEGMENT
ASSUME CS:CODE,DS:DATA,SS:STACK;段寻址伪指令,指明段名与寄存器的关系
START:
MOV AX,DATA
MOV DS,AX
MOV AX,STACK
MOV SS,AX;让各段指向正确的位置
CALL BEGIN
mov SI,offset buf
CALL INPUT; 显示results
CALL NEXT
CALL LINE
CALL PRINTNUM
CALL NEXT
CALL LINE
CALL PRINTMAX
CALL NEXT
CALL LINE
CALL PRINTMIN
CALL NEXT
CALL LINE
CALL SORT
CALL PRINTSORT
CALL PRINTAVG
CALL LINE
MOV AH,4CH
INT 21H
;========================BEGIN=============================
BEGIN PROC NEAR;段内近调用,调用程序的子程序在同一代码段中
mov dx , offset file ;dx获取file的偏移地址
mov al , 0
mov ah , 3dh
int 21h ;打开文件,只读;ax返回文件代号
;jc error11 ;若打开出错,转error
mov handle , ax ;保存文件句柄
mov bx , ax ;文件句柄
mov cx , 255 ;读取255字节
mov dx , offset buf ;获取buf的偏移地址
mov ah , 3fh
int 21h ;从文件中读255字节→buf
;jc error11 ;若读出错,转error
mov bx , ax ;实际读到的字符数送入bx
mov buf[bx] , '$' ;在文件结束处放置一“$”符
MOV dx , offset buf[0]
MOV AH , 09H
int 21h ;显示文件内容
mov bx , handle ;文件句柄
mov ah , 3eh
int 21h ;关闭文件
XOR AX,AX
XOR BX,BX
XOR CX,CX
;XOR DX,DX
XOR SI,SI
XOR DI,DI ;寄存器清零
MOV S_60,AX
MOV S60_70,AX
MOV S70_80,AX
MOV S80_90,AX
MOV S90_100,AX
MOV AVG ,AX ;数据段清零,循环时使用
RET
BEGIN ENDP
;=======================INPUT==========================
INPUT PROC NEAR
mov AL,[SI]
inc SI
CMP AL,44H
JZ ENDINPUT ;是否为结束字符A
CMP AL,20H ;是否为空格
JZ SAVE
CMP AL,30H
JB ERROR
CMP AL,39H
JA ERROR;确认ascii码对应的是0到9
SUB AL,30H ;变为真数
MOV CL,4
SHL BX,CL ;左移四位.变为BCD码
ADD BL,AL
JMP INPUT
SAVE: MOV SCORE[DI],BX
ADD DI,2
ADD sii1,1;用变量记录存入数据个数
XOR BX,BX;异或,即清零作用
JMP INPUT
ENDINPUT: MOV SCORE[DI],BX ;保存最后一个数据
XOR DI,DI
ADD sii1,1
mov dx,sii1
ADD DI,dx;将变量给DI
MOV DL,0AH
MOV AH,02H
INT 21H ;留出一行空格(输出换行)
MOV DX,OFFSET ENDTIPS ;显示输出提示
MOV AH,09H
INT 21H ;DI得到个数,DI保持不变化
JMP INPUTEND
ERROR: mov dx,offset ERRORTIPS
mov ah,09h
int 21h
CALL NEXT
jmp START
INPUTEND: RET
INPUT ENDP
;========================PRINTNUM========================
PRINTNUM PROC NEAR
MOV SI,0 ;打印个数NUM
XOR BX,BX
MOV DX,OFFSET STRINGNUM ;显示输出NUM:
MOV AH,09H
INT 21H;打印'|total num |$'
MOV BX,DI
AND BX,00FFH
CALL PRINTINT
RET
PRINTNUM ENDP
;========================PRINTMAX========================
PRINTMAX PROC NEAR
MOV SI,0 ;打印最大值
XOR BX,BX
MOV CX,DI
;比较两个无符号数
;CMP结果 ZF CF
;目的操作数 < 源操作数 0 1
;目的操作数 > 源操作数 0 0
;目的操作数 = 源操作数 1 0
;比较两个有符号数
;CMP结果 标志位
;目的操作数 < 源操作数 SF ≠ OF
;目的操作数 > 源操作数 SF=OF
;目的操作数 = 源操作数 ZF=1
LMAX: CMP BX,SCORE[SI]
JAE NEXT1 ;无符号左大于右跳转
MOV BX,SCORE[SI] ;BX里面放最大值
NEXT1:
ADD SI,2
LOOP LMAX;根据DI的值确定循环多少次
MOV DX,OFFSET STRINGMAX ;显示输出最大值
MOV AH,09H;09
;显示字符串
;DS:DX=串地址
;'$'结束字符串
INT 21H
CALL PRINTBCD
RET
PRINTMAX ENDP
;========================PRINTMIN========================
PRINTMIN PROC NEAR
MOV SI,0 ;打印最小值
MOV CX,DI
LMIN: CMP SCORE[SI],BX
JAE NEXT2 ;与上个函数同理
MOV BX,SCORE[SI] ;BX里面放最小值
NEXT2:
ADD SI,2
LOOP LMIN
MOV DX,OFFSET STRINGMIN ;显示输出最小值
MOV AH,09H
INT 21H
CALL PRINTBCD
RET
PRINTMIN ENDP
;========================SORT========================
SORT PROC NEAR
MOV SI,0
XOR BX,BX
MOV CX,DI
COM: AND AX,0000H ;开始比较
MOV AX,SCORE[SI]
COM6: MOV BX,60H
CMP AX,BX ;与60比较
JAE COM7 ;大于就跳到COM7
JMP C_60
COM7: MOV BX,70H
CMP AX,BX;与70比较
JAE COM8
JMP C60_70
COM8: MOV BX,80H
CMP AX,BX ;与80比较
JAE COM9
JMP C70_80
COM9: MOV BX,90H
CMP AX,BX ;与90比较
JAE C90_100
JMP C80_90
C_60: INC S_60 ;小于60时[S_60+1]
JMP RE
C60_70: INC S60_70 ;大于等于60小于70时[S60_70+1]
JMP RE
C70_80: INC S70_80;大于等于70小于80时[S70_80+1]
JMP RE
C80_90: INC S80_90 ;大于等于80小于90时[S80_90+1]
JMP RE
C90_100: INC S90_100 ;大于等于90小于100时[S90_100+1]
JMP RE
RE: ADD SI,2
LOOP COM
RET
SORT ENDP
;========================PRINTSORT========================
PRINTSORT PROC NEAR
MOV DX,OFFSET STRING_60 ;显示输出S_60
;STRING_60 DB '|Less than 60 |$'
;STRING60_70 DB '|From 60 to 69 |$'
;STRING70_80 DB '|From 70 to 79 |$'
;STRING80_90 DB '|From 80 to 89 |$'
;STRING90_100 DB '|From 90 to 100 |$'
MOV AH,09H
;显示字符串
;DS:DX=串地址
;'$'结束字符串
INT 21H
MOV BX,S_60
AND BX,00FFH
CALL PRINTINT
CALL PERCENT
CALL NEXT
CALL LINE
MOV DX,OFFSET STRING60_70 ;显示输出S60_70
MOV AH,09H
INT 21H
MOV BX,S60_70
AND BX,00FFH
CALL PRINTINT
CALL PERCENT
CALL NEXT
CALL LINE
MOV DX,OFFSET STRING70_80 ;显示输出S70_80
MOV AH,09H
INT 21H
MOV BX,S70_80
AND BX,00FFH
CALL PRINTINT
CALL PERCENT
CALL NEXT
CALL LINE
MOV DX,OFFSET STRING80_90 ;显示输出S80_90
MOV AH,09H
INT 21H
MOV BX,S80_90
AND BX,00FFH
CALL PRINTINT
CALL PERCENT
CALL NEXT
CALL LINE
MOV DX,OFFSET STRING90_100 ;显示输出S90_100
MOV AH,09H
INT 21H
MOV BX,S90_100
CALL PRINTINT
CALL PERCENT
CALL NEXT
CALL LINE
RET
PRINTSORT ENDP
;========================PRINTAVG========================
PRINTAVG PROC NEAR
MOV SI,0;
XOR BX,BX;
MOV CX,DI
MOV AX,0
AVERAGE: MOV BX,SCORE[SI] ;把每一个以BCD码存储的数转化为十六进制
CALL BCD_TO_HEN ;调用转换函数
MOV SCORE[SI],BX
ADD AX,SCORE[SI] ;求和
ADD SI,2
LOOP AVERAGE;
MOV CX,100
MUL CX
DIV DI
CMP AX,10000
JAE BAI
ADD AX,5;四舍五入
MOV BX,AX
CALL HEN_TO_BCD ;结果在转换为BCD码
MOV DX,OFFSET STRINGAVG ;显示输出AVG
MOV AH,09H
INT 21H
MOV DL,BH
MOV CL,4
SHR DL,CL
ADD DL,30H
MOV AH,02H;输出千位数
INT 21H
MOV DL,BH ;输出百位数
AND DL,0FH
ADD DL,30H
MOV AH,02H
INT 21H
MOV DL,2EH ;输出小数点
MOV AH,02H
INT 21H
MOV DL,BL ;输出十位数
MOV CL,4
SHR DL,CL
ADD DL,30H
MOV AH,02H
INT 21H
JMP N
BAI: MOV DX,OFFSET STRINGAVG ;显示输出AVG
MOV AH,09H
INT 21H
CALL BAI0
N: CALL NEXT
RET
PRINTAVG ENDP
;========================BCD转换为十六进制========================
BCD_TO_HEN PROC NEAR
PUSH CX
PUSH AX
PUSH DX
PUSH SI
PUSH DI
XOR DX, DX
MOV AX, BX
PUSH AX
AND AX,0F000H;取千位数
MOV CL, 4 ;移位取出千位数
SHR AX, CL
MOV DH, AH
POP AX
PUSH AX
AND AX, 0F00H ;取百位数
MOV DL, AH
POP AX
PUSH AX
MOV AX, DX
AAD
;指令是在作除法前用于调整寄存器AH和AL之值,它是把二个寄存器中单BCD码组成一个十进制数值,其调整规则如下:
;AL←AH*10+AL,AH←0
;受影响的标志位:PF、SF和ZF(AF、CF和OF等都是无定义)
;MOV AX, 0502H MOV BL, 10D AAD ;AH=0, AL=52H DIV BL ;AH=2(余数), AL=5(商)
MOV DI, 100 ;乘100
MUL DI
MOV SI, AX
POP AX
PUSH AX
MOV CL, 4 ;移位取出十位数
SHR AX, CL
AND AX, 000FH
MOV DH, AL
POP AX
PUSH AX
AND AX, 000FH ;取AL低四位,个位数
MOV DL, AL
POP AX
MOV AX, DX
AAD
ADD AX, SI
MOV BX, AX ;转换结果给BX
POP DI
POP SI
POP DX
POP AX
POP CX
RET
BCD_TO_HEN ENDP
;========================4位十六进制转换为BCD========================
HEN_TO_BCD PROC NEAR
PUSH AX
PUSH CX
PUSH DX;保证原函数的寄存器的值不变,因此先放入栈,该子过程结束后在pop
XOR DX,DX
MOV AX,BX
MOV BX,0000H
MOV CX,1000
DIV CX;先将数据除以1000,得到千位的值
;比如数据为4321
;那么此时得到的商4放在AX中,得到的余数321放在DX中
MOV CL,4
AND AX,000FH;取AX中低四位,也就是四位二进制表示一个十进制数即bcd码
ADD BX,AX;把上面结果也就是4放在BX
ROL BX,CL;把得到的结果循环左移4位,也就是放在了第二位十六进制数的位置也就是0040h
MOV AX,DX ;余数再放到AX里面
MOV CL,100;余数再除100,也就是321除100
DIV CL;商放在AL里,余数放在AH里
PUSH AX;入栈,保存AX数值
AND AX,000FH;取低四位也就是3
ADD BX,AX;加和后就是0043
MOV CL,4
ROL BX,CL;再左移,0430
POP AX;把刚才的数据取出
MOV AL,AH;余数再除10
AND AX,00FFH;取刚才的余数
MOV CL,10
DIV CL;商2放在AL,余数1放在AH
AND AX,000FH
ADD BX,AX;取低四位也就是2放在BX,也就是0432
MOV CL,4
ROL BX,CL;再左移
ADD BL,AH;放入,那么此时BX结果为4321
POP DX
POP CX
POP AX;将原过程的寄存器值取出对应首部的push
RET
HEN_TO_BCD ENDP
;========================换行========================
NEXT:
mov dl,0ah;换行对应ASCII码
mov ah,02h
int 21h
mov dl,0dh;回车对应的ASCII码
mov ah,02h;输出DL内容
int 21h
ret
;========================打印换行线========================
LINE:
mov dx,offset STRINGLINE;;STRINGLINE DB
mov ah,09h;
;显示字符串
;DS:DX=串地址
;'$'结束字符串
int 21h
call NEXT
ret
;========================输出100%========================
ALL:
mov dx,offset ALL100;ALL100 DB '100%$'
mov ah,09h
int 21h
ret
;========================输出100.0========================
BAI0:
mov dx,offset BAI00;BAI00 DB '100.0$'
mov ah,09h
int 21h
ret
;========================打印百分数========================
PERCENT PROC NEAR
PUSH BX
PUSH CX
PUSH AX
PUSH DX;保存调用前寄存器值
XOR DX,DX
XOR AX,AX;寄存器清零
MOV AX,BX;BX里存的是对应分数的人数
MOV CX,10000
CWD;把一个字符扩展为双字符,将AX内容扩展到DX AX。
;规则:若AX最高位=1,则执行后DX=FFFFH;若AX最高位=0,则执行后DX=0000H。
MUL CX;防止出小数
;被乘数 乘数 乘积
;AL ;reg/mem8 AX
;AX ;reg/mem16 DX:AX
;EAX ;reg/mem32 EDX:EAX
DIV DI
CMP AX,10000;
JAE ALL0;无符号左大于右跳转说明占比百分之百
ADD AX,5;四舍五入
MOV BX,AX
CALL HEN_TO_BCD ;结果在转换为BCD码
MOV DL,BH
MOV CL,4
SHR DL,CL
ADD DL,30H
MOV AH,02H;输出千位数
INT 21H
MOV DL,BH ;输出百位数
AND DL,0FH
ADD DL,30H;转换为ASCII码
MOV AH,02H
INT 21H
MOV DL,2EH ;输出小数点
MOV AH,02H
INT 21H
MOV DL,BL ;输出十位数
MOV CL,4
SHR DL,CL
ADD DL,30H
MOV AH,02H
INT 21H
MOV DL,25H
MOV AH,02H
INT 21H; 百分号
MOV DL,20H
MOV AH,02H
INT 21H; 空格
JMP M
ALL0: CALL ALL
M: POP DX
POP AX
POP CX
POP BX
RET
PERCENT ENDP
;====================打印16进制数为BCD整数====================
PRINTINT PROC NEAR
PUSH BX
PUSH CX
PUSH AX
PUSH DX;保存原过程寄存器值
MOV AX,BX
MOV CX,99
BX_100: ADD BX,AX
LOOP BX_100;循环一百次,防止出现小数的情况
ADD BX,5;四舍五入
CALL HEN_TO_BCD ;结果在转换为BCD码
MOV DL,BH
MOV CL,4
SHR DL,CL;右移4位就是比如bcd码在BX中是4321,那么现在就是取43放DL,在右移最后是04
ADD DL,30H
MOV AH,02H;输出千位数
INT 21H
MOV DL,BH ;输出百位数
AND DL,0FH;取百位
ADD DL,30H
MOV AH,02H
INT 21H
MOV DL,20H ;输出空格
MOV AH,02H
INT 21H
POP DX
POP AX
POP CX
POP BX
RET
PRINTINT ENDP
;====================打印BCD整数====================
PRINTBCD PROC NEAR ;打印函数
CMP BH,00000001B ;判断百位是否为1;上一步BX中存放最大值
JNC B;cf等于零跳转,也就是没有进位
;比较两个无符号数CMP
;CMP结果 ZF(零标志)CF(进位标志)
;目的操作数 < 源操作数 0 1
;目的操作数 > 源操作数 0 0
;目的操作数 = 源操作数 1 0
; 比较两个有符号数
; CMP结果 标志位
;; 目的操作数 < 源操作数 SF ≠ OF
; 目的操作数 > 源操作数 SF=OF
; 目的操作数 = 源操作数 ZF=1
A: MOV DL,BL ;输出十位数
MOV CL,4
SHR DL,CL
ADD DL,30H;取ASCII码
MOV AH,02H
INT 21H
MOV DL,BL ;输出个位数
AND DL,0FH
ADD DL,30H
MOV AH,02H
INT 21H
MOV DL,20H ;输出空格
MOV AH,02H
INT 21H
JMP D
B: MOV DL,BH ;输出百位数
AND DL,0FH
ADD DL,30H
MOV AH,02H;取百位数显示输出
INT 21H
JMP A
D: RET
PRINTBCD ENDP
CODE ENDS
END START
总结
代码以及源程序可去主页下载