《汇编语言》王爽(第四版) 17.3 输入并显示一个字符串

文章目录

前言

一、实验任务

二、思路分析

三、程序框架

四、一些细节

1.判断键盘输入的内容

2.依据功能号找到子程序中对应子功能

3.入栈与出栈操作

4.出栈时栈顶溢出的问题

5.入栈时栈顶溢出的问题

二、最终成果

1.完整代码

2.效果图

总结


前言

本文是王爽老师《汇编语言》(第四版) 第十七章第三节 “字符串的输入” 的分析及代码,主要任务是输入并显示一个字符串。书上已经给出了讲解,不过我认为需要自己梳理一遍思路。代码给出了详细注释,并且实现逻辑在细节处也与书上的略有不同。


一、实验任务

总体目标:读取用户输入的一个字符串,并实时显示在屏幕指定位置,以回车键结束。

二、思路分析

首先,将任务分解,这个程序要实现的逻辑如下。

(1)从16H端口不断读取用户的键盘输入

(2)有一块空间用来存放当前字符串,并且以栈的方式管理这块空间。

选择栈的原因是,当用户输入一个新字符,新字符应当加在当前字符串的末尾;当用户按退格键,应当从当前字符串的末尾删除一个字符;这是明显的先进后出原则,因此使用栈的方式进行管理。

注意,这里所说的管理,是要手动设置栈顶的变动,而不是用系统提供的SP寄存器。

(3)若用户输入一个新字符,则 ①在字符串栈的末尾添加这个字符;②在屏幕指定位置显示整个字符串。

(4)若用户按退格键,则 判断字符串栈是否为空。

        A:若字符串栈空,则直接返回;

        B:若字符串栈不空,则 ① 将字符串栈最末尾的一个字符删除;②在屏幕指定位置显示整个字符串。

三、程序框架

由上边的分析,可以列出基本的程序框架。字符串的插入、删除、显示功能,应分别封装为子程序。而这几个功能都是与字符串有关的,之前学过了,可以在一个子程序中设计多个功能,这里正好用上。于是,设计一个charstack子程序,提供字符入栈、字符出栈、显示字符串的功能。

以下是程序的框架。代码很少,主要是思路。

assume cs:code  ;17.3 输入并显示一个字符串
stack segment
    db 32 dup(0)    ;通用的栈空间
stack ends
data segment
    db 16 dup(0)    ;用作字符栈的空间
data ends
code segment
start:
    mov ax,stack    ;设置栈顶
    mov ss,ax
    mov sp,20H

    mov ax,data     ;参数:ds:si指向字符栈首地址
    mov ds,ax
    mov si,0
    mov dx,0402H    ;参数:字符串的屏幕显示行号、列号
    call getstr     ;调用 “接收并显示一个字符串”子程序

    mov ax,4c00H
    int 21H
getstr:     ;功能:接收一个字符串的输入,并显示在屏幕指定位置
        ;参数:dh 屏幕显示的行号; dl 屏幕显示的起始列号  ds:si指向字符串栈首地址
        ;返回:无
    
    ;读取用户的键盘输入
    mov ah,0        ;16H中断例程,功能号0:从键盘缓冲区中读取一个键盘输入
    int 16H         ;返回:ah 扫描码;al ASCII码

    ;判断输入的内容

    ;若输入的是字符
    ischar:
    mov ah,0        ;功能号0:将新字符入栈
    call charstack   
    mov ah,2        ;功能号2:在指定位置显示字符串
    call charstack
    jmp short getstr_start    ;再次等待读取字符

    ;若输入的是退格键
    backspace:
    mov ah,1        ;功能号1:将末尾的一个字符删除
    call charstack
    mov ah,2        ;功能号2:在指定位置显示字符串
    call charstack
    jmp short getstr_start    ;再次等待读取字符

    ;若输入的是回车键
    enter:
    mov ah,0        ;功能号0:将新字符入栈
    mov al,0        ;参数:要入栈的新字符为0
    call charstack
    mov ah,2        ;功能号2:在指定位置显示字符串
    call charstack  
    jmp short getstr_ret    ;子程序返回

    getstr_ret:    ;子程序返回
    pop ax
    ret
charstack:  ;功能:以栈的方式管理一个字符串空间,提供新增字符、删除字符、显示字符串功能。
        ;参数说明:ah 功能号
        ;   功能号0:字符入栈  
        ;       参数: al入栈字符  ds:si指向字符串栈  
        ;       返回:无
        ;   功能号1:字符出栈  
        ;       参数:ds:si指向字符串栈  
        ;       返回: al出栈字符
        ;   功能号2:显示字符串  
        ;       参数:dh屏幕显示行号  dl屏幕显示起始列号  ds:si指向字符串栈  
        ;       返回:无
    
    jmp short charstack_start   ;跳转到charstack子程序入口

    table dw charpush,charpop,charshow  ;子功能地址表
    top dw 0        ;字符栈的栈顶

    charstack_start:    ;程序入口

    ;根据参数ah功能号,得到table中对应子功能的地址

    charpush:   ;功能号0:字符入栈
                ;参数:al入栈字符  ds:si指向字符串栈
                ;返回:无
    ;字符入栈代码
    jmp charstack_ret   ;子程序返回

    charpop:    ;功能号1:字符出栈
                ;参数:ds:si指向字符串栈
                ;返回:al出栈字符
    ;字符出栈代码
    jmp charstack_ret   ;子程序返回

    charshow:   ;功能号2:显示字符串
                ;参数:dh屏幕显示行号  dl屏幕显示起始列号   ds:si指向字符串栈
                ;返回:无
    ;显示字符串代码
    jmp charstack_ret   ;子程序返回

    charstack_ret:  ;子程序返回
    ret

code ends
end start

四、一些细节

有一些细节处的实现逻辑,我与老师书上讲的略有不同,这里做个记录。

1.判断键盘输入的内容

书上的实现思路是:①判断是否是字符,若是则立即处理(入栈并显示字符串);②若不是字符,则跳转到非字符判断部分,再接着判断是否是退格键、是否是回车键。主要代码如下。

jmp getstrs     ;接收字符串输入

mov ah,0        ;接收输入的一个字符
int 16H

;下面开始判断
    
cmp al,20H        ;若ASCII码<20H,则不是字符
jb nochar   
 
;接下来就处理“输入的是字符”这种情况

mov ah,0        ;功能号0:字符入栈
call charstack
mov ah,2    ;功能号2:显示字符串
call charstack
jmp getstrs     ;再次等待输入

;这部分用于处理输入的是非字符的情况

nochar:        
cmp ah,0EH    
je backspace    ;处理“输入的是退格键”的情况
cmp ah,1CH
je enter        ;处理“输入的是回车键”的情况
jmp getstrs     ;再次等待输入

backspace:      ;处理“输入的是退格键”的情况

enter:          ;处理“输入的是回车键”的情况

我的思路是:字符与非字符同等对待,以类似java中case语句的方式逐个判断。个人认为这样似乎结构更清晰一些。主要代码如下。

    ;读取用户的键盘输入
    getstr_start:
    mov ah,0        
    int 16H         

    ;判断输入的内容
    cmp al,20H
    jnb ischar      ;若ASCII码>=20H,则输入的是字符
    cmp ah,0EH      
    je backspace    ;若扫描码为0EH,则输入的是退格键
    cmp ah,1CH      
    je enter        ;若扫描码为1CH,则输入的是回车键
    jmp short getstr_ret    ;若以上都不是,则直接返回

    ischar:        ;处理 输入的是字符 的情况
    jmp short getstr_start    ;再次等待读取字符

    backspace:        ;处理 输入的是退格键 的情况
    jmp short getstr_start    ;再次等待读取字符

    enter:            ;处理 输入的是回车键 的情况
    jmp short getstr_ret    ;子程序返回

    getstr_ret:    ;子程序返回

    ret

2.依据功能号找到子程序中对应子功能

老师在书上写的方法是,在程序中设置一个table表用于存放每个子功能的入口地址,然后用一段指令来根据功能号参数计算出相应的子功能入口地址,然后跳转。主要代码如下。

jmp short charstart        ;跳转到程序入口处

table dw charpush,charpop,charshow    ;子功能入口地址表

charstart:        ;程序入口处

;下面计算出子功能的地址

cmp ah,2    ;提高程序容错性,若输入的功能号>2则忽略,直接返回
ja sret
mov bl,ah
mov bh,0
add bx,bx
jmp word ptr table[bx]    ;跳转到对应的子功能入口处

老师在第16章讲过,这样的编程方法使得程序结构比较混乱,不利于功能的扩充。(我怀疑可能是老师故意在17章这样写,让读者自己进行优化,加深理解。)

更好的方法是根据功能号直接查找地址表。于是我将代码优化如下。

    jmp short charstack_start   ;跳转到charstack子程序入口

    top dw 0        ;字符栈的栈顶

    charstack_start:    ;程序入口

    ;根据参数ah功能号,执行对应子功能
    cmp ah,0
    je do1      ;功能号0:字符入栈
    cmp ah,1
    je do2      ;功能号1:字符出栈
    cmp ah,2
    je do3      ;功能号2:显示字符串
    jmp short charstack_ret     ;若功能号输入有误,则子程序返回

    do1:    call charpush   ;功能号0:字符入栈
    jmp short charstack_ret
    do2:    call charpop   ;功能号1:字符出栈
    jmp short charstack_ret
    do3:    call charshow   ;功能号2:显示字符串
    jmp short charstack_ret

    charpush:   ;功能号0:字符入栈
    ret         ;子程序返回

    charpop:    ;功能号1:字符出栈
    ret         ;子程序返回

    charshow:   ;功能号2:显示字符串
    ret         ;子程序返回

    charstack_ret:  ;子程序返回
    ret

3.入栈与出栈操作

汇编语言中的push与pop指令在第三章讲过。入栈push操作中,CPU会先修改栈顶SP的值,然后再复制数据。而这个程序中,要手动实现对字符栈的管理,其中入栈操作,书上给的代码是先复制数据,然后再修改栈顶。这让我读来略有不适。因此我在自己的程序中修改了语句的顺序,使之与push指令中的操作顺序一致。出栈操作同理。主要代码如下。

    charpush:   ;功能号0:字符入栈
                ;参数:al入栈字符  ds:si指向字符串栈  top为字符栈的栈顶位置
                ;返回:top为字符栈的栈顶位置
    push bx
    mov bx,top  ;bx指向当前栈顶的偏移地址
    inc top     ;栈顶+1
    mov ds:[si+bx],al   ;将新字符加入到栈顶位置
    pop bx
    ret         ;子程序返回

    charpop:    ;功能号1:字符出栈
                ;参数:ds:si指向字符串栈  top为字符栈的栈顶位置
                ;返回:al出栈字符 top为字符栈的栈顶位置
    push bx
    mov bx,top  ;bx指向当前栈顶的偏移地址
    mov al,ds:[si+bx-1] ;将栈顶字符赋给al作为返回值
    dec top     ;栈顶-1,即将最末尾一个字符出栈
    pop bx
    ret         ;子程序返回

4.出栈时栈顶溢出的问题

书上之前讲过,在使用与栈有关的指令时需要自己考虑栈顶溢出的问题。这个程序中需要手动实现对字符栈的管理,因此更需要自己考虑这个问题。

先来看出栈操作。

出栈操作,可能出现的情况是栈已经空了。如果字符栈已经空了,而用户又输入了退格键,这时候应当直接忽略而读取下一个字符,绝不能继续修改栈顶(这会导致错误)。书上的代码中没有考虑这个问题,于是我进行了优化。因此,将出栈操作的代码修改如下。

    charpop:    ;功能号1:字符出栈
                ;参数:ds:si指向字符串栈  top为字符栈的栈顶位置
                ;返回:al出栈字符 top为字符栈的栈顶位置

    ;先判断栈是否为空,若为空,则直接返回
    cmp top,0
    jne charpop_do  
    ret

    ;若栈不为空,则进行出栈操作
    charpop_do: 
    push bx
    mov bx,top  ;bx指向当前栈顶的偏移地址
    mov al,ds:[si+bx-1] ;将栈顶字符赋给al作为返回值
    dec top     ;栈顶-1,即将最末尾一个字符出栈
    pop bx
    ret         ;子程序返回

5.入栈时栈顶溢出的问题

再来看入栈操作。

入栈操作,可能出现的情况是栈空间已经满了。但是目前的情况是,字符栈的大小不是在子程序中定义的,而是在调用程序中定义的(比如这里就是在主程序中将data段定义为字符栈的空间)。因此,子程序无法知道这个字符栈的空间有多大(调用子程序时并没有传递关于栈空间大小的参数),因而也就无法处理入栈时发生栈满溢出的情况。

而作为一个实现字符栈出栈、入栈管理功能的子程序,是应当提供对于栈满这种情况的处理功能的。书上也没有这方面的处理,于是只能自己动手优化了。

优化的方法是,在调用子程序的时候,将字符栈的栈空间大小作为参数(cx)传递给子程序getstr。程序在入栈时先判断是否栈满,若栈已满,则不继续入栈,直接返回。

入栈的主要代码如下。

    charpush:    
    ;先判断栈是为已满,若已满,则直接返回
    cmp top,cx
    jne charpush_do
    ret

    ;若栈未满,则进行入栈操作
    charpush_do:
    push bx
    mov bx,top  ;bx指向当前栈顶的偏移地址
    inc top     ;栈顶+1
    mov ds:[si+bx],al   ;将新字符加入到栈顶位置
    pop bx
    ret         ;子程序返回

而主程序中在调用时也要记得用cx传递参数,指明字符栈的栈空间大小。

二、最终成果

1.完整代码

assume cs:code  ;17.3 输入并显示一个字符串
stack segment
    db 32 dup(0)    ;通用的栈空间
stack ends
data segment
    db 16 dup(0)    ;用作字符栈的空间
data ends
code segment
start:
    mov ax,stack    ;设置栈顶
    mov ss,ax
    mov sp,20H

    mov ax,data     ;参数:ds:si指向字符栈首地址
    mov ds,ax
    mov si,0
    mov dx,0503H    ;参数:字符串的屏幕显示行号、列号
    mov cx,16       ;参数:字符栈的栈空间大小
    call getstr     ;调用 “接收并显示一个字符串”子程序

    mov ax,4c00H
    int 21H
getstr:     ;功能:接收一个字符串的输入,并显示在屏幕指定位置
        ;参数:dh 屏幕显示的行号; dl 屏幕显示的起始列号 
        ;       ds:si指向字符串栈首地址  cx 字符栈的空间大小
        ;返回:无
    
    push ax

    ;读取用户的键盘输入
    getstr_start:
    mov ah,0        ;16H中断例程,功能号0:从键盘缓冲区中读取一个键盘输入
    int 16H         ;返回:ah 扫描码;al ASCII码

    ;判断输入的内容
    cmp al,20H
    jnb ischar      ;若ASCII码>=20H,则输入的是字符
    cmp ah,0EH      
    je backspace    ;若扫描码为0EH,则输入的是退格键
    cmp ah,1CH      
    je enter        ;若扫描码为1CH,则输入的是回车键
    jmp short getstr_ret    ;若以上都不是,则直接返回

    ;若输入的是字符
    ischar:
    mov ah,0        ;功能号0:将新字符入栈
    call charstack   
    mov ah,2        ;功能号2:在指定位置显示字符串
    call charstack
    jmp short getstr_start    ;再次等待读取字符

    ;若输入的是退格键
    backspace:
    mov ah,1        ;功能号1:字符出栈
    call charstack
    mov ah,2        ;功能号2:在指定位置显示字符串
    call charstack
    jmp short getstr_start    ;再次等待读取字符

    ;若输入的是回车键
    enter:
    mov ah,0        ;功能号0:将新字符入栈
    mov al,0        ;参数:要入栈的新字符为0
    call charstack
    mov ah,2        ;功能号2:在指定位置显示字符串
    call charstack  
    jmp short getstr_ret    ;子程序返回

    getstr_ret:    ;子程序返回
    pop ax
    ret
charstack:  ;功能:以栈的方式管理一个字符串空间,提供新增字符、删除字符、显示字符串功能。
        ;参数说明:ah 功能号
        ;   功能号0:字符入栈  
        ;       参数: al入栈字符  ds:si指向字符串栈  cx 字符栈的栈空间大小
        ;       返回:无
        ;   功能号1:字符出栈  
        ;       参数:ds:si指向字符串栈  
        ;       返回: al出栈字符
        ;   功能号2:显示字符串  
        ;       参数:dh屏幕显示行号  dl屏幕显示起始列号  ds:si指向字符串栈  
        ;       返回:无
    
    jmp short charstack_start   ;跳转到charstack子程序入口

    top dw 0        ;字符栈的栈顶

    charstack_start:    ;程序入口

    ;根据参数ah功能号,得到table中对应子功能的地址
    cmp ah,0
    je do1      ;功能号0:字符入栈
    cmp ah,1
    je do2      ;功能号1:字符出栈
    cmp ah,2
    je do3      ;功能号2:显示字符串
    jmp short charstack_ret     ;若功能号输入有误,则子程序返回

    do1:    call charpush   ;功能号0:字符入栈
    jmp short charstack_ret
    do2:    call charpop   ;功能号1:字符出栈
    jmp short charstack_ret
    do3:    call charshow   ;功能号2:显示字符串
    jmp short charstack_ret

    charstack_ret:  ;子程序返回
    ret

    ;--------------------------------------
    charpush:   ;功能号0:字符入栈
                ;参数:al入栈字符  ds:si指向字符串栈 
                ;       cx 字符栈的栈空间大小  top为字符栈的栈顶位置
                ;返回:top为字符栈的栈顶位置

    ;先判断栈是为已满,若已满,则直接返回
    cmp top,cx
    jne charpush_do
    ret

    ;若栈未满,则进行入栈操作
    charpush_do:
    push bx
    mov bx,top  ;bx指向当前栈顶的偏移地址
    inc top     ;栈顶+1
    mov ds:[si+bx],al   ;将新字符加入到栈顶位置
    pop bx
    ret         ;子程序返回

    ;-------------------------------------------
    charpop:    ;功能号1:字符出栈
                ;参数:ds:si指向字符串栈  top为字符栈的栈顶位置
                ;返回:al出栈字符 top为字符栈的栈顶位置

    ;先判断栈是否为空,若为空,则直接返回
    cmp top,0
    jne charpop_do  
    ret

    ;若栈不为空,则进行出栈操作
    charpop_do: 
    push bx
    mov bx,top  ;bx指向当前栈顶的偏移地址
    mov al,ds:[si+bx-1] ;将栈顶字符赋给al作为返回值
    dec top     ;栈顶-1,即将最末尾一个字符出栈
    pop bx
    ret         ;子程序返回

    ;-----------------------------------------
    charshow:   ;功能号2:显示字符串
                ;参数:dh屏幕显示行号  dl屏幕显示起始列号   ds:si指向字符串栈
                ;返回:无
    push ax
    push es
    push di
    push dx
    push si

    ;计算,es:di指向显示的起始位置
    mov ax,0B800H   
    mov es,ax
    mov ax,160
    mul dh
    mov dh,0
    add ax,dx
    add ax,dx
    mov di,ax

    ;判断字符栈是否为空,若为空,则跳转
    cmp top,0
    je charshow_ret     ;跳转

    ;显示字符栈中的全部字符
    mov ax,0        
    cli         ;传送方向为正向传送
    s_charshow:
    cmp ax,top
    je charshow_ret ;若已经显示完最后一个字符,则跳转
    movsb           ;传送一个字符进行显示
    inc di          ;di为显存区偏移地址
    inc ax          ;ax为字符栈的索引
    jmp s_charshow

    charshow_ret:
    mov byte ptr es:[di],0    ;将屏幕上当前字符(字符串结尾的下一个字符)清空

    pop si
    pop dx
    pop di
    pop es
    pop ax

    ret         ;子程序返回

code ends
end start

2.效果图


总结

本文是王爽老师《汇编语言》(第四版) 第十七章第三节 输入并显示一个字符串 的分析及代码。通过这个练习,初步熟悉了对int 16H中断例程的使用,提高了设计包含多个功能的子程序的能力。

  • 2
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值