本章还有一个实验10 编写子程序。但是本课程设计包括了写的子程序的所有内容,在代码中也包含所有写的子程序的代码,所以直接看本课程设计的代码就可以了。
在前面的学习中,有一个实验是将poweridea公司的数据按格式写到一个table段中,而本课程设计是要求就数据写到显存以显示在屏幕上,所以我们可以在以前的程序基础上修改,比如可以保留以前的程序,先写到table表,然后从table表读到显存。不过我这里的实现是去掉这些不必要的消耗,也删掉了table表,使程序更高效。另外,在code段里面还有几个子程序,代码的关键部分也都做了注释,实现如下:
PowerideaShow
DATAS SEGMENT
;此处输入数据段代码
db '1975','1976','1977','1978','1979','1980','1981','1982','1983'
db '1984','1985','1986','1987','1988','1989','1990','1991','1992'
db '1993','1994','1995'
dd 16,22,382,1356,2390,8000,16000,24486,50065,97479,140417,197514
dd 345980,590827,803530,1183000,1843000,2759000,3753000,4649000,5937000
dw 3,7,9,13,28,38,130,220,476,778,1001,1442,2258,2793,4037,5635,8226
dw 11542,14430,15257,17800
;起始行:4
db 4
;各字段的列:year=8;收入=16;雇员=30;平均收入=42
db 8,16,30,42
;字符缓冲区
db 20 dup (0)
DATAS ENDS
STACKS SEGMENT
;此处输入堆栈段代码
dw 20 dup (0)
STACKS ENDS
CODES SEGMENT
ASSUME CS:CODES,DS:DATAS,SS:STACKS
START:
MOV AX,DATAS
MOV DS,AX
mov ax,STACKS
mov ss,ax
mov sp,40
mov bp,215
mov si,0 ;for year and summ
mov di,0 ;for ne
mov cx,21
show:
push cx
mov cl,0f2h ;颜色
;year 偏移0
mov ax,[si]
mov ds:[bp].0,ax
mov ax,[si+2]
mov ds:[bp].2,ax
mov ds:[bp].4,0
push si
push dx
mov si,bp
mov dh,ds:[210]
mov dl,ds:[211] ;列号
call show_str
pop dx
pop si
;收入summ 偏移5
mov ax,84[si]
mov dx,84[si+2]
push ax
push dx
push si
mov si,bp
call ddtoc
mov dh,ds:[210]
mov dl,ds:[212] ;列号
call show_str
pop si
pop dx
pop ax
add si,4
;雇员数ne 偏移10
mov bx,168[di]
add di,2
push ax
push dx
push si
mov ax,bx
mov si,bp
call dtoc
mov dh,ds:[210]
mov dl,ds:[213] ;列号
call show_str
pop si
pop dx
pop ax
;人均收入 偏移13
div bx
push ax
push dx
push si
mov si,bp
call dtoc
mov dh,ds:[210]
mov dl,ds:[214] ;列号
call show_str
pop si
pop dx
pop ax
mov cl,ds:[210]
inc cl
mov ds:[210],cl
pop cx
;loop show ;报错:jump destination too far ,所以自己手动判断cx来跳转。
dec cx
jcxz finish
jmp show
finish:
mov ah,4CH
int 21H
;名称:show_str
;功能:在指定位置,用指定的颜色,显示一个用0结束的字符串
;参数:(dh)=行号,(dl)=列号,(cl)=颜色,ds:si指向字符串的首地址
show_str:
push ax
push cx
push dx
push es
push si
push di
;根据行号设置基址为bp
mov ax,160
mul dh
mov bp,ax
;根据列号设置基址为di
mov dh,0
add dx,dx ;没个字符对应2个字节,所以列号*2
mov di,dx
;显存段地址
mov ax,0b800h
mov es,ax
;保存颜色属性到al中
mov al,cl
sub cx,cx
showstr:
mov cl,[si] ;取字符
jcxz ok
mov es:[bp+di],cl ;写字符
mov es:[bp+di].1,al ;设置颜色属性
add di,2
inc si
loop showstr
ok:
pop di
pop si
pop es
pop dx
pop cx
pop ax
ret
;divdw
;功能:进行不会产生溢出的除法运算,被除数为dword型,除数为word型,结果为dword型
;参数:ax=dword型数据的低16位,dx=dword型数据的高16位,cx=除数
;返回:dx=结果的高16位,ax=结果的低16位,cx=余数
divdw:
push bx ;以后恢复
push ax
mov ax,dx
sub dx,dx
div cx
mov bx,ax ;bx=int(H/N)
pop ax
div cx
mov cx,dx
mov dx,bx
pop bx ;恢复bx
ret
;dtoc
;功能:将word型数据转变为表示十进制的字符串,字符串以0为结束符
;参数:ax=word型数据;ds:si指向字符串的首地址
;返回:无
dtoc:
push ax
push bx
push cx
push dx
push si
;先插入数据结束标记0
sub dx,dx
push dx
mov bx,10
s1:
div bx
add dx,30h ;转为ASCII码
push dx
mov cx,ax
jcxz ok1
sub dx,dx
jmp s1
ok1:
pop cx
jcxz ok2
mov [si],cl
inc si
jmp ok1
ok2:
;给字符串后面添0
mov [si],cl
pop si
pop dx
pop cx
pop bx
pop ax
ret
;ddtoc
;功能:将dword型数据转变为表示十进制的字符串,字符串以0为结束符
;参数:ax=dword型数据的低16位;dx=dword型数据的高16位;ds:si指向字符串的首地址
;返回:无
ddtoc:
push ax
;push bx
push cx
push dx
push si
;先插入数据结束标记0
sub cx,cx
push cx
dd10:
mov cx,10
call divdw
add cx,30h ;转为ASCII码
push cx
mov cx,ax
jcxz testhigh
jmp dd10
testhigh:
mov cx,dx
jcxz geascii
jmp dd10
geascii:
pop cx
jcxz win
mov [si],cl
inc si
jmp geascii
win:
;给字符串后面添0
mov [si],cl
pop si
pop dx
pop cx
;pop bx
pop ax
ret
CODES ENDS
END START
运行结果如下:
总结:
1、程序实现中基本都用到了栈,主要用于暂存寄存器数据。不过在子程序dtoc和ddtoc中,我还用栈存放参数%10的值,因为形成字符串时需要逆序写到内存。
2、在dtoc和ddtoc中,为了区分保存的寄存器数据和十进制数的ascii值,特意往栈中压入一个0,作为“桩”。
3、主程序中在循环跳转的loop语句被注释掉:“;loop show ;报错:jump destination too far ,所以自己手动判断cx来跳转。”,原因正如说明,偏移超过了一个字节,所以我自己修改判断cx而利用jmp(jmp near)来实现跳转。因为所有有条件跳转都是短转移,是不是把循环中间的一些内容也写到子程序来减少偏移更好?
4、对显示时的起始行和列偏移的数据,我都统一放在数据段中,可以很方便的修改来查看效果。