思路
- 创建1个临时区域,用于存放dtoc转换后的字符串
- 区域命名为temp
- 考虑到容量要足够容纳 “十进制待转换数字” 的每一位转换后的ASCII,对比原始数据后,用16个内存单元足可容纳
- 此区域的每个字符串均用1个值为00的单元结尾,此功能由dtoc实现
- 主程序循环21次,每次对每个字段的其中1个数据进行处理,包含的动作内容为:
- 将data中 年份 字段中1个数据(dword型),传递到temp,并手动补末尾0
- 将data中 总收入 字段中1个数据(dword型),通过mov给AX和DX,再调用dtoc,转换为末尾为0的字符串,存储到temp。再调用show_str从temp处读取字符串并传递到显示缓冲区
- 将data中 雇员 字段中1个数据(word型),通过mov给AX和DX,再调用dtoc,转换为末尾为0的字符串,存储到temp。再调用show_str从temp处读取字符串并传递到显示缓冲区
- 先计算 平均收入 字段的值,将此值通过mov给AX和DX,再调用dtoc,转换为末尾为0的字符串,存储到temp。再调用show_str从temp处读取字符串并传递到显示缓冲区
- (额外注意):
- 栈的空间大小,要设置足够大,否则过程中会出现向栈顶方向溢出,从而覆盖temp甚至data的情况。本次使用了64个单元
- 主程序要跳转到循环开始的距离,越过了loop的范围,所以改用jcxz配合jmp near ptr
关于此思路存在的问题
- 过于繁琐,例如,对每个数据的处理都要调用show_str。替代方案完全可以先将每个字段中的1个数据,组成1个组合,然后21行构成table段。最终只调用一次show_str从table中读取即可
- 因为这是我想到的其中一种思路,所以先实现它,目的是为了练习
最终效果:
代码:
;课程设计1 P211
assume cs:code,ds:data,ss:stack
;ds
data segment
;年份:dword, ds:[0]
db '1975','1976','1977','1978','1979','1980','1981','1982','1983'
db '1984','1985','1986','1987','1988','1989','1990','1991','1992'
db '1993','1994','1995'
;年收入:dword, ds:[84]
dd 16,22,382,1356,2390,8000,16000,24486,50065,97479,140417,197514
dd 345980,590827,803530,1183000,1843000,2759000,3753000,46749000,5937000
;雇员数:word,ds:[168]
dw 3,7,9,13,28,38,130,220,476,778,1001,1442,2258,2793,4037,5635,8226
dw 11542,14430,15257,17800
data ends
;es
temp segment
db 16 dup(0);16个单元容纳任何一个数转换出来的ASCII字符串,绰绰有余
temp ends
;ss。注意空间要足够
stack segment stack
db 64 dup (0)
stack ends
code segment
start:
mov ax,data
mov ds,ax
mov ax,temp
mov es,ax
mov ax,stack
mov ss,ax
mov sp,64
;第一部分:data - temp - buffer
mov bx,0;年份 年收入 index
mov bp,0;雇员数 平均收入 index
mov si,0;temp index
;show_str参数设置,显示属性cl,位置行dl在循环中单独赋值
mov dh,0
mov cx,21
transfer_start:
push cx
;过程中会多次修改dx值,而dh位于dx中,先保存
push dx
;后面的dtoc,show_str运行后都不会改变CX的值。只有divdw会改变CX,但它又位于dtoc内部,dtoc会恢复CX
mov cl,2
;(1)处理年份
mov ax,[bx]
mov es:[0],ax
mov ax,[bx+2]
mov es:[2],ax
mov al,0
mov es:[4],al
push ds
mov ax,temp
mov ds,ax
mov dl,0
call show_str
pop ds
;(2)处理总收入
mov ax,[bx+84]
mov dx,[bx+84+2]
call dtoc
push ds
mov ax,temp
mov ds,ax
mov dh,ss:[61]
mov dl,20
call show_str
pop ds
;(3)处理雇员
mov ax,ds:[bp+168]
mov dx,0
call dtoc
push ds
mov ax,temp
mov ds,ax
mov dh,ss:[61]
mov dl,40
call show_str
pop ds
;(4)处理平均收入
mov ax,[bx+84]
mov dx,[bx+84+2]
div word ptr ds:[bp+168];此后步,AX存平均收入
;构建由AX,DX组成的被除数
mov dx,0
call dtoc
push ds
mov ax,temp
mov ds,ax
mov dh,ss:[61]
mov dl,60
call show_str
pop ds
add bx,4
add bp,2
pop dx
inc dh
pop cx
dec cx
;没有使用loop,因为超出跳转范围
jcxz transfer_end
jmp near ptr transfer_start
transfer_end:
mov ax,4c00h
int 21h
;第二部分:3个子程序
;名称:dtoc
;功能:将DEC数字转换为字符串
;参数:AX和DX表示待转换的DEC数字,向es:[si]写入字符串
;返回:无
dtoc:
push ax
push bx
push cx
push dx
push si
;末尾0。此处不能用AX,因为是被除数的组成部分
mov cx,0
push cx
dtoc_push:
mov cx,10
call divdw
;转换过程中得到的每个余数,都一一对应DEC数值中的某位(包括0),都需要入栈
add cx,30h
push cx
;判断商是否为0,分别对AX和DX进行判断。与余数是否为0没有关系,即使余数为0也要入栈,因为DEC数值某位有0很正常
mov cx,dx
jcxz next
jmp short dtoc_push
next:
mov cx,ax
jcxz dtoc_pop
jmp short dtoc_push
dtoc_pop:
pop cx
mov es:[si],cl
jcxz dtoc_end
inc si
jmp short dtoc_pop
dtoc_end:
pop si
pop dx
pop cx
pop bx
pop ax
ret
;名称:divdw
;功能:将dword类型的被除数,除以word类型除数,防止溢出
;接受参数:AX和DX构成被除数,CX为除数
;返回:AX存储商的低16位,DX存储商的高16位,CX存储余数
divdw:
push bx
;int(H/N)
push ax
mov ax,dx
mov dx,0
div cx
mov bx,ax
;(rem(H/N)*65536+L)/N
pop ax
div cx
mov cx,dx
mov dx,bx
pop bx
ret
;名称:show_str
;功能:将字符串显示到屏幕上
;接受参数:从ds:[si]读取字符串,写入buffer的位置由dh(行),dl(列)给出,cl指出显示属性
;返回:无
show_str:
push ax
push cx
push dx
push si
push di
push es
mov ax,0b800h
mov es,ax
;计算di,dh*160+dl*2
mov al,160
mul dh
;因用dx相加计算dl*2,需将dh清零
mov dh,0
add dx,dx
add ax,dx
mov di,ax
;cl将用于jcxz,属性值由al代持
mov al,cl
;copy source to buffer
show_copy:
mov ch,0
mov cl,[si]
jcxz show_end
mov es:[di],cl
mov es:[di+1],al
inc si
add di,2
jmp short show_copy
show_end:
pop es
pop di
pop si
pop dx
pop cx
pop ax
ret
code ends
end start