字符栈的入栈、出栈和显示栈中的内容,是需要在多处使用的功能,我们应该将它们写为子程序。 |
子程序:字符栈的入栈、出栈和显示。 参数说明: (ah)=功能号,0 表示入栈,1 表示出栈,2 表示显示; ds:si 指向字符栈空间; 对于 0 号功能:(al)=入栈字符; 对于 1 号功能:(al)=返回的字符; 对于 2 号功能:(dh)、(dl)=字符串在屏幕上显示的行、列位置。 |
显示栈中字符的时候,要注意清除屏幕上上一次显示的内容。(每次显示字符串的时候在后面多显示一个空格,这样删除一个再显示字符串,相应删除的字符就变成空格了,就不用真正的去清空删除前的显示字符串再来显示删除后的字符串) |
assume cs:code
data segment
db 128 dup (0)
data ends
code segment
start:
mov ax , data ; 这些代码不要也能运行,但是会覆盖数据;周全考虑,可事先分配一段空间
mov ds , ax
; 这两句代码和分配空间一定要,不然的话如果输入的字符数据少正常,不然回车就会出错了
; 分配了空间并且加了这两个代码,就在开头预留了一部分空间,以后数据的字符就在这段空间存放。
mov dh , 24
mov dl , 0
mov si , 0
mov ah , 2 ; 我们初始分配的空间中都是0,为了让程序开始的时候就在页面最后一行光标处显示我们设置的背景信息,先调用显示功能
; 因为如果不设置的话程序运行和以前正常等到输入光标提示一样,但是按键输入后就有颜色背景,纯属强迫症
call charstack
call getstr ; 进入字符串输入循环,回车键结束输入,退格键删除一个输入的字符。
mov ax , 4c00h
int 21h
;--------------------------------------------------------------------------
; 接收字符串输入的子程序
getstr:
push ax
getstrs:
mov ah , 0
int 16h ; 从键盘缓冲区读取输入字符的ASCII码到al中
cmp al , 20h ; 20h=32,数字0的ASCII
jb nochar ; ASCII 码小于0,说明不是字符
mov ah , 0 ; 0号功能,字符入栈
call charstack ; 调用功能
mov ah , 2 ; 2号功能,显示
call charstack
jmp getstrs
nochar:
cmp ah , 0eh ; 退格键的扫描码
; 这里换成 cmp al , 8 也可以,8是退格键的ASCII
je backspace
cmp ah , 1ch ; 回车键的扫描码
je enter
jmp getstrs ; 不是特殊按键不处理
backspace:
mov ah , 1 ; 1表示功能号,出栈
call charstack ; 字符出栈
mov ah , 2 ; 2表示功能号,显示
call charstack ; 显示栈中的字符
jmp getstrs
enter:
mov ah , 2 ; 如果是回车,我没有让相应的字符入栈,直接显示栈中已有的字符,完了退出程序就好了
call charstack ; 显示栈中的字符
pop ax ; 恢复ax
ret ; 回车表示输入结束,返回;
;--------------------------------------------------------------------------
; 子功能
charstack:
jmp short charstart
table dw charpush , charpop , charshow ;对应0,1,2==入栈,出栈,显示 ; 我们根据功能号来调用
top dw 0 ; 这个值用来存储字符栈中的字符个数,初始为0
charstart:
push bx
push dx
push di
push es
cmp ah , 2
ja sret1 ; 条件转移指令,只能是段内短转移(8位:-128~127)
; 这里本来写的是ja sret,但是跳转太长,出现:jump out of range by ** byte(s)
; 没办法只能用个小技巧,来一个接力
mov bl , ah ; bl存贮功能号
mov bh , 0
add bx , bx ; dw,0->0,1->2,2->4
jmp word ptr table[bx] ; 找到在 table 对应的地址,取出来里面的值(标号偏移),设置IP,CS是当前代码段的
// 程序刚运行
|
charpush:
mov bx , top ; 字符进栈功能,先进栈,然后指针top在偏移
mov [si][bx] , al
inc top
jmp sret
charpop:
cmp top , 0
je sret ; top==0,表示没有字符在字符栈
mov bx , 0b800h ; 这段代码主要是找到显示位置,根据传进来的参数dh、dl
mov es , bx
mov al , 160
mov ah , 0
mul dh ; dh 存储的行 dh*160-> ax
mov di , ax ; di 暂存行偏移
;add dl , dl ; dl 存储的列 一个字符要两个byte,相加得到具体在显示中的列
;mov dh , 0 ; dh 清0,这样dx只存储了列偏移
;add di , dx ; di 存储具体显示位置的偏移
; 每次显示都从字符栈第一个开始显示,列偏移从0列开始,所以注释了前三行代码也没事
mov bx , top ; 因为字符出栈了,对应字符的显示属性也要去掉,实现统一,不然字符删掉了,然而设置的背景颜色什么的都还在
; 前面backspace标号处的代码,删除一个字符,后面接着就显示,这样看的到显示效果。
add bx , bx
and byte ptr es:[di+bx+1] , 00000000b
dec top
mov bx , top
mov al , [si][bx] ; 保存删除的字符
jmp sret
; 本来删除的时候要把上次显示的全部清空,但是我们charshow程序段,每次都会在字符串后多输出一个空格
; 所以删除一个字符再显示字符串的时候上次删除的字符就会变成空格,但是背景还是要人为的去掉,不然字符不见了,但是背景还跟着在。
sret1:
jmp sret ; 接力,继续跳转到sret
charshow:
mov bx , 0b800h ; 这段代码主要是设置显示位置,根据传进来的参数dh、dl
mov es , bx
mov al , 160
mov ah , 0
mul dh ; dh 存储的行 dh*160-> ax
mov di , ax ; di 暂存行偏移
;add dl , dl ; dl 存储的列 一个字符要两个byte,相加得到具体在显示中的列
;mov dh , 0 ; dh 清0,这样dx只存储了列偏移
;add di , dx ; di 存储具体显示位置的偏移
mov bx , 0 ; bx 永远初始为0,这样下面的操作可以把字符栈中的元素全部显示
; 每次调用都设置bx等于0,表示从字符栈头开始依次显示栈中字符
charshows:
cmp bx , top
jne noempty ; 不相等,也就是bx还没有偏移到top,字符栈中还有没显示完的字符
mov byte ptr es:[di] , ' ' ; 字符栈中的数据全部显示完后在显示一个空格,没数据也显示一个空格
; 所以我们在开头调用charstack的2号功能先显示一下,为了就是实现进入输入字符显示的时候之前,闪烁光标就有一个背景
; 没有开头的调用代码,程序运行时光标没有背景,输入了字符之后才有,而且这个背景一直存在到退出程序
mov byte ptr es:[di+1] , 01001010b ; 设置下颜色属性,为了看到这个空格
cmp top , 0
je cursor ; 考虑特殊情况,如果栈中因为删除没有数据,也要设置显示光标位置。
; 刚进入程序,光标在最后一行开头,但是如果程序执行过人为的代码修改光标后,光标就不会再在自动设置
; 因删除导致没有数据就不会执行到下面的noempty中的设置光标代码。随意这要考虑加上一个判断
jmp sret
noempty:
mov al , [si][bx] ; 初始si、bx都为0,ds:[si+bx](基址变址寻址),si一直为0,指向开辟的数据空间首地址,bx来实现遍历字符栈每一个字符
mov es:[di] , al ; 显示键盘输入的字符
mov byte ptr es:[di+1] , 01001010b ; 设置显示属性
inc bx ; bx 偏移一位,指向下一个要显示的字符
add di , 2 ; 显示位置偏移两个字节,准备显示下一个字符
; 设置光标在显示的字符串后一个位置闪烁
mov ax , bx ; 前面bx偏移指向下一个要显示的字符,同理,也就是光标要偏移的位置
mov dx , ax ; 调用10h号中断设置光标列位置(dl),一行80个字符,al就能表示完,所以ah一定是0,不是0也没有关系,此时dl中的值等于bx
mov dh , 24 ; 设置显示行
mov ah , 2 ; 设置光标是10h号中断例程的2号功能
int 10h
; 最开头已经入栈保存过ax,dx所以即使这里更改了这些寄存器的值,最后面也不会影响最初的值,比如dx,si
jmp charshows
cursor:
mov ah , 2
mov bl , 0
mov dl , 0
int 10h
jmp sret
sret:
pop es
pop di
pop dx
pop bx
ret
code ends
end start
|
// 输入abcd
|
// 两次退格键 |
// 退格删除所有字符
|
// 换行 |
实现输入输出字符串(光标一直在默认的最后一行第一个位置)-》设置显示字符串有背景-》设置光标在显示的字符串末尾的后一个位置-》设置删除字符后,相应删除的字符所在位置的背景也改为默认值-》设置执行程序光标处就有背景,删除完所有字符后,最后一行第一个位置有光标和背景。