本章還有一個實驗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、對顯示時的起始行和列偏移的數據,我都統一放在數據段中,可以很方便的修改來查看效果。