从键盘输入某计算机应用班35人的《计组》成绩(百分制),按成绩从高到低排序。并计算全班总成绩、平均成绩、统计低于平均成绩的学生人数,并将结果在屏幕上显示。
主程序:
程序结构上可分为6部分,首先是主程序main,主程序调用5个实现不同功能的子程序。子程序可分为输入成绩功能的子程序,按成绩降序排序功能的子程序,统计成绩总和功能的子程序,统计平均分功能的子程序,统计低于平均分人数功能的子程序。
流程图:
Sum1子程序:
Sum1子程序用于计算全班总成绩,对score变量中保存的成绩进行遍历,将35名学生成绩进行相加,求得的结果再通过调整加指令转换成压缩BCD码格式存储到sum变量中,因为35名学生成绩总和最大为3500,所以sum变量为字类型(dw),求得的总和sum再通过02功能号分别将sum千百十个位的输出到屏幕中。
流程图:
Average子程序:
Average子程序用于统计全班平均成绩,将sum从压缩的BCD码格式转换成二进制形式,然后以sum作为被除数,35作为除数进行除法操作。
压缩BCD码转换二进制:
假设ax存的是sum压缩BCD码,A、A′分别表示AH高4位、AL高4位,B、B′分别表示AH的低4位、AL的低四位。可视为AX <==> ABA′B′(压缩BCD),下面公式中0ah为十进制的权值10。
转换公式:
( ( A * 0ah + B) * 0ah + A′) * 0ah + B′
流程图:
NoPass子程序:
NoPass子程序用于统计低于平均成绩的学生人数,对score变量中的成绩进行遍历,小于平均分的人数用count变量进行统计,最后将count转换成非压缩BCD格式分别用02号功能进行输出操作。
stack segment stack
d1 dw 128 dup(?)
top equ length d1
stack ends
data segment
score db 35 dup(0);store 35 scores of stduents
slength db 23h;store 35 as length of stduents
CRLF db 0dh,0ah,'$';newline
print_menu db '********************STUDENT_SCORE_SYSTEM********************',0dh,0ah,'$'
print_end db '****************************END*****************************',0dh,0ah,'$'
input_stduent db 'Please input 35 students score:',0dh,0ah,0dh,0ah,'$'
print_student db 'Student$'
print_colons db ': $'
check db 'OK!',0dh,0ah,'$';check for input success
print_output db 'Here are your input(Descending Order):',0dh,0ah,'$'
printsum db ' Sum of students scores: $'
printavg db ' Average of total scores: $'
print_fails db ' The count of no pass: $'
stu db 0;student[stu]:
temp db 0;temp in Procedure of Sort
sum dw 0;sum in Procedure of Sum1
avg dw 0;avg in Procedure of Average
count db 0;count of no pass (uncompressed BCD)
data ends
code segment
assume cs:code, ds:data, ss:stack
main proc far
push ds
xor ax,ax
push ax
start:
mov ax,data
mov ds,ax
mov ax,stack
mov ss,ax
mov sp,top
xor cx,cx
mov cl,slength;将计数35次用于输入成绩次数
lea si,score;将si取到保存成绩的偏移地址
xor ax,ax
;********************STUDENT_SCORE_SYSTEM********************
lea dx,print_menu;字符串入口为dx
mov ah,09h;9号功能输出字符串
int 21h;软中断
;please input 35 students score:
lea dx,input_stduent
mov ah,09h
int 21h
aa1:
;Student
lea dx,print_student
int 21h
;[stu]
inc stu;设置stu的目的:输出显示屏Student[stu]中的stu,用来计数 范围[1,35]
push ax;入栈保护
push bx;同上
push cx;同上
xor ax,ax
xor bx,bx
mov al,stu;1---------------------------
mov bl,0ah;2 这3行将stu从16进制转10进制
div bl ;3---------------------------
;---打印Student[]中的stu---
mov bx,ax
mov ah,02h
mov dl,bl
add dl,30h
int 21h
mov dl,bh
add dl,30h
int 21h
;-------------------------
pop cx;恢复现场
pop bx;同上
pop ax;同上
;:
lea dx,print_colons
int 21h
call far ptr Input
;tab
push dx
mov dl,09h;09h对应的字符为tab
push ax
mov ah,02h
int 21h
pop ax
pop dx
loop aa1;如果计数器CX为0,则退出循环,表示输入完35名学生成绩
;-----输出显示屏OK----
lea dx,check
mov ah,09h
int 21h
;--------------------
call far ptr Sort;调用选择排序算法
;newline
lea dx,CRLF
mov ah,09h
int 21h
;Here are your input:
lea dx,print_output
mov ah,09h
int 21h
call far ptr Output;输出学生成绩子函数(排序后)
;newline
lea dx,CRLF
mov ah,09h
int 21h
call far ptr Sum1;统计成绩总和子函数
;newline
lea dx,CRLF
mov ah,09h
int 21h
;Average of total scores:
lea dx,printavg
int 21h
call far ptr Average;计算平均分子函数
;newline
lea dx,CRLF
mov ah,09h
int 21h
;The count of no pass:
lea dx,print_fails
int 21h
call far ptr NoPass;低于平均分人数统计子函数,将低于平均分结果以非压缩BCD码保存到变量count中
;newline
lea dx,CRLF
mov ah,09h
int 21h
int 21h;just for beauty
;****************************END*****************************
lea dx,print_end
int 21h
mov ah,4ch
int 21h
ret
main endp
;procedure name:Input
;function:input 35 students'score
;entry:store BCD score by al
;exit:none
Input proc far
push ax
push bx
push cx
mov ah,01h
int 21h
mov cl,04h
shl al,cl
mov bh,al
int 21h
and al,0fh
add al,bh
mov [si],al
inc si
pop cx
pop bx
pop ax
ret
Input endp
;procedure name:Output
;function:output 35 students'score
;entry:none
;exit:
Output proc far
push ax
push bx
push cx
xor cx,cx
mov cl,slength;count 23h-->35d
lea si,score;db
aa2: mov al,[si]
mov bl,al;copy
push cx
mov cl,04h
shr al,cl
pop cx
mov bh,30h
add al,bh
mov dl,al
mov ah,02h
int 21h
and bl,0fh
add bl,30h
mov dl,bl
int 21h
inc si
mov dl,00h
mov ah,02h
int 21h
int 21h
loop aa2
pop cx
pop bx
pop ax
ret
Output endp
Sort proc far
push ax
push bx
push dx
xor cx,cx
xor bx,bx
xor dx,dx
mov ax,0000h
mov si,ax
xor ax,ax
mov cl,slength
dec cl
rotate2:mov dx,0000h
mov al,[si]
mov temp,al
rotate1:inc dl
mov bx,dx
mov bl,[si+bx]
cmp al,bl;al >= bl, sort_next
jnc sort_next;
;has carry
mov al,bl
mov bl,temp
mov temp,al
push ax
push si
mov ax,si
add ax,dx
mov si,ax
mov [si],bl
pop si
pop ax
sort_next:
cmp dl,cl;dl < slength, rotate1
jnc for2;dl >= slength, out
jmp rotate1
for2:
push ax
push bx
mov ax,si
push si
lea si,score
mov bx,si
pop si
sub ax,bx
push cx
mov cl,slength
dec cl
dec cl
cmp al,cl;i < slength - 0
pop cx
pop bx
pop ax
mov dl,temp
mov [si],dl
jnc last
inc si
dec cx
jmp rotate2
last:
pop dx
pop bx
pop ax
ret
Sort endp
;caculate sum of score
;entry:score
;exit:none
Sum1 proc far
push ax
xor cx,cx
xor ax,ax
xor bx,bx
mov cl,slength
dec cl;34 add time
lea ax,score
mov si,ax
mov al,[si]
xor ah,ah
rotate3:inc si
mov bl,[si]
add al,bl
daa
adc ah,00h
loop rotate3
mov temp,al;
mov al,ah
xor ah,ah
mov bl,0ah
div bl
push cx
mov cl,04h
shl al,cl
pop cx
add ah,al
mov al,temp
;newline
lea dx,CRLF
push ax
mov ah,09h
int 21h
pop ax
mov sum,ax
mov ah,09h
mov dx,offset printsum
int 21h
;mov ah,02h
mov ax,sum
cmp ah,1;judge ah has carry?
jnc ah_carry
sum_next:
mov ax,sum
mov cl,04h
shr al,cl
add al,30h
mov dl,al
mov ah,02h
int 21h
mov ax,sum
and al,0fh
add al,30h
mov dl,al
mov ah,02h
int 21h
pop ax
ret
ah_carry:
mov dl,ah
push cx
mov cl,04h
shr dl,cl
pop cx
add dl,30h
mov ah,02h
int 21h
mov ax,sum
mov dl,ah
and dl,0fh
add dl,30h
mov ah,02h
int 21h
jmp sum_next
Sum1 endp
Average proc far
push ax
push dx
push cx
xor cx,cx
mov ax,sum
mov al,ah
xor ah,ah
mov bl,al
mov cl,04h
shr al,cl
and bl,0fh
mov dl,0ah
mul dl
add al,bl
mov bx,sum
shr bl,cl
xor bh,bh
mov dx,000ah
mul dx
add ax,bx
mov bx,sum
xor bh,bh
and bl,0fh
mov dx,000ah
mul dx
add ax,bx
mov bx,0023h
div bx
aam
mov avg,ax; uncompressed BCD
mov dl,ah
add dl,30h
mov ah,02h
int 21h
mov ax,avg
mov dl,al
mov ah,02h
add dl,30h
int 21h
pop cx
pop dx
pop ax
ret
Average endp
NoPass proc far
push ax
push dx
xor cx,cx
mov cl,slength
lea si,score
mov bx,avg;0605h
push cx
mov cl,04h
shl bh,cl;6005h
add bl,bh; bl= 65h compression
pop cx
rotate4:
mov al,[si]
cmp al,bl;score[i] < avg?
jnc rotate5
inc count
rotate5:
inc si
loop rotate4
mov cl,04h
xor ax,ax
mov al,count;15h
mov bl,0ah
div bl;0102h
mov dl,al
mov bl,ah;temp store
add dl,30h
mov ah,02h
int 21h
mov dl,bl
add dl,30h
int 21h
pop dx
pop ax
ret
NoPass endp
code ends
end start
在本次调试过程中,由于代码量过于庞大,debug工具调试不为方便,只能通过单步T命令进行调试,尤其是再调试排序子程序的时候,理论上需要单步调试接近n平方次数(DosBox环境调试选择排序算法),所以为了跳过与问题无相干的代码,我往常会缩小代码调试的范围,将认为与问题相关的程序代码单独放到另一个汇编文件里进行调试,从而减少调试的次数。而在这次调试排序子程序过程中,由于边界上的问题,排序中偏后的数据发生错误的顺序,这就意味着从进入该排序子程序开始,就要遍历将近n的平方次,即使将该子程序单独放到另一个文件中进行调试,也要进行这样的单步调试过程。为了快速的匹配到关键问题所在,我将代码移至到VScode编辑器中进行调试,VScode编辑器中配置插件masm/tasm,本质上也是启动DosBox,就可以利用Vscode的打断点方式,来快速匹配到错误的具体范围,创建监视点数据查看寄存器中数据的变化。
还需要改进的地方
- 要求输入百分制成绩,目前只实现了[0,99]
- 输入成绩的合法性