• 通过汇编实现在屏幕中间输入字符,enter结束,退格键删除字符


    俗话说,高级语言,yyds,小手指一敲,就会在屏幕上显示出一串"hello world!"

    但背后的原理是什么,是无数个中断指令,寄存器为之服务.接下来让我们揭开潘多拉的魔盒.

    构思:

    我们现在想在屏幕上输入字符,首先就是要在键盘上按下按键,就在按键的一瞬间,中断指令就触发了

    我们的程序就对应的执行了,

    下面我们写一段能够让我们在屏幕中间输入东西的程序.

    前提:有一定的汇编基础,能够了解中断指令,这里我为你理清思路

     

    第一步:

    我们当然是写入数据,数据段写入,

    assume cs:code,ds:data                


    data segment

                    db 32  dup(0)

    data ends


    定义的数据段

    code segment
    start:
            mov ax,data     ;数据段导入
            mov ds,ax
            mov si,0    ;栈空间偏移地址
            mov dh,12   ;显存第12行    
            mov dl,20   ;显存第二十列
            call getstr   ;开始操作
    return: mov ax,4c00h
            int 21h         ;结束操作


    将数据段导入到ds里,设置显存的位置

    然后call 开始执行操作

    第二步:

    我们调用指令了,现在就是判断键盘的指令,进而进行相应的操作

    getstr:push ax   ;常规操作,需要用到ax,保护ax 不会因为每次的使用而改变数值

         

     响应键盘操作

            

     dis:  pop ax

            ret               

    第三步:

    我们现在设法实现相应的键盘操作的内容

    我们要读取按键指令,就要执行中断指令   int 16h

    这样键盘就会一直读取键盘缓冲区的内容,并且把 Ascll 码存放在寄存器al 里面,我们对应着把相应的内容写到显存上面就行了.

    我们现在要实现三种按键的功能,

    那用什么来存放字符呢?最好的办法采用栈

    ① 按键类型是字符的,


    可以将字符入栈,

    然后显示栈中的字符

     

    ②退格键


    将栈中的字符出栈

    然后显示剩余的字符

    同时将出栈的字符存到al里面(这样我们可以看到是哪个字符被删了)

    ③回车按键

    将零入栈,

    显示栈中的字符包括0,

    然后结束运行

    第四步:

    经过以上步骤我们就实现了字符的写入,字符的删除,和程序的结束

    但是我们会发现这三个键中实现功能的同时,有很多类似的功能

    比如,将字符入栈和出栈,  显示栈中的字符,  这三个功能

    我们所以先将这三个功能定义一下,一会儿再去具体实现, 

    charpush    charpop   charshow

       入栈            出栈      显示栈中的字符

    第五步:

    现在我们的功能就大体实现了,具体框架就是第三步 , 然后框架中的细节就是第四部实现的 , 我们现在把第三步用函数具体去实现

    扫描按键,根据具体的按键去实现对应的功能:

    因为我们按键的Ascll存放在 寄存器 al 里面,

    小于20h就不是字符,那就跳转到非字符处理

      cmp al,20h      ;判断是什么类型的按键信息
      jb nochar       ;如果小于20h就不是字符,
                            ;接着跳转到非字符处理


    不跳转就是字符,我们开始实现①


    call charpush             ;字符入栈

    call   charshow           ;显示字符

    跳转回去,继续判断输入按键信息

    jmp   响应键盘操作

    nochar:

    不是字符则跳转执行,判断是否是退格键   回车键

            cmp ah,0eh             ;这是退格键的扫描码,直接就存放在了ax高位,低位是Ascll码,

            je backspace            ;是退格键,则跳转到退格键的功能



            cmp ah,1ch              ;扫描是否是回车键
            je enter                     ;是则执行对应功能



            jmp getstrs             ;不是以上按键,则仍未结束,接着执行int16h,
                                            ;等待输入
                                            ;接下来是退格键和回车键的处理

     

    现在正式实现②退格键和③回车键的功能

     


    ; 退格键

    backspace:                        
      call charpop            ;将字符出栈,

    call   charshow        ;将剩余字符显示                

     

    ;回车键盘

    enter:          ;这里是回车的功能
            mov al,48        ;这里是要结束,并且在栈里添加一个0,48是零的Ascll 码
                                    ;现在存在al里面
               
            call charpop            ;字符入栈
          
            call charshow         ;显示栈中的字符


            jmp  dis            ;这里应该跳转结束

    dis:    pop ax              ;寄存器出栈
              ret                     ;执行结束

    这里功能都大致实现了,下面的细节就是,小功能的实现了

    字符的入栈 charpop,

    出栈 charpush,

    显示 charshow

    首先我们要知道,我们能够将字符任意的存放,删除,都是依托内存的,我们之前定义了内存data,

    然后要利用栈的指针优势,来实现数据的增删, 但是如果直接利用栈段的话,我们把栈内容写入到显存的时候就麻烦了,

    先进后出,我们刚开始输入的字符,进去了,最后输入的字符也进去了,那出来的时候就,是最后进去的先出来,这样出来的顺序就和进去的顺序颠倒了

    我们现在要用到栈的指针来管理数据,又想让数据出来的时候按照进去的数据一样的顺序输出.

    那我们就需要将数据段增添栈顶指针,然后栈顶指针是从0开始的,每增加一个数据,栈顶指针就会加一,以此来控制数据的增删,并且输出的时候也可以操作数据段来输出数据.

    866c1119f1a840d09e91e9b7536f0e67.png

     

    那我们首先就把栈空间构建好

    说白了,栈空间还是一段空间,我们刚开始就把他定义在了ds里面了,其中,bx是此空间的段偏移地址,

    然后我们这里是为了使用栈的优势,所以在数据段的开始定义了一个top 参数,来代替栈顶指针的功能,从0开始,每增加一个数据,top加一,删除一个数据的话,top减一 . 然后把整段数据输出的话,还可以从开始写入显存,当偏移地址和top 一样时,退出.

     

    top dw 0

     

    用top 来存放栈空间指针,从零开始
     

    ;字符入栈功能
    charpush:
            mov bx,top[0]      ;将栈顶的指针给bx
            mov [si][bx],al ;然后将要输入的数字的阿斯克码,
                            ;放在栈空间的栈顶
            inc top[0]         ;当放入字符后,栈顶加一,这是我们自己做的栈                
            jmp sret  


    这里看此图:

             要将字符加入到内存段里面,位置实际上就是top的位置,然后top 加一,就可以了

    top就是下一个数据的偏移地址,  把al里的字符的Asccl码放到下一个字符的地址就可以了

    此时,数据段是ds, 栈空间的开始的偏移地址是si, 栈段的偏移指针就是top 里的数值

    这里是基址变址寻址

    所以下一个数据的位置就是 ds段,加上栈空间的偏移地址si , 加上栈段的偏移地址top

     

     

    48d4f28ab9e04629888475caee05ec50.png

     

    ;字符出栈
    charpop:
            cmp top[0],0       ;打狗看主人,看看栈里有没有数据,


            je sret                 ;没有数据就出来


            dec top[0]          ;有数据就接着,将栈顶减一 ,


                                      ;此时栈顶对应出栈的字符       


            mov bx,top[0]         ;然后将栈顶交给bx,目的是传数据


            mov al,[si][bx]        ;将要出栈的数据送到al,
                                           ;这里注意top每次都比数据的偏移地址多1


            jmp sret                ;跳转结束
                                    ;这里在第(dh)行,第(dl)列显示字符


    这里出栈,也是一个意思,这里把要出栈的数据的内容送到寄存器 al 里面 , 然后top 指向要出栈的数据,对应的top 里内容减一 

    显示字符

    charshow:
            mov bx,0b800h
            mov es,bx
            mov al,160
            mov ah,0
            mov dh,12
            mul dh
            mov di,ax
            mov dl,20
            add dl,dl
            mov dh,0
            add di,dx
            
            mov bx,0


    这里是把显存的内容送到 0b800h  ,  显示的位置是 第 dh 行,  第  dl  列

     

    charshows:
            cmp bx,top
            jne noempty
            mov byte ptr es:[di],' '        ;让下一个字符变成 空白
            jmp sret

    noempty:
            mov al,[si][bx]
            mov es:[di],al
            mov byte ptr es:[di+2],' '        让下一个字符变成空白 ,优化观感
            inc bx
            add di,2
            jmp charshows

    这里小功能实现了,的确,我们又发现了一个问题,

    我们次程序的调用需要频繁调用小功能,并且不容易拓展,所以我们可以把这几个功能,放在表里,然后通过寻址来调用相应的功能:

    ;接下来就是重磅的功能实现了,把小程序细分了,通过表调用
    ;这里是通过调用charstack 表,然后根据ah里的数值,来寻找表
    ;里的偏移地址,然后实现对应的功能的
    charstack:
            jmp short charstart        
            table dw  charpush,charpop,charshow     
             
            top dw 0
             
    charstart:
            push dx                        ;常规的入栈
            push di
            push bx
            push es
            ;实现各个功能
            cmp ah,2        ;首先查表  
            ja  sret        ;大于二就跳转结束
            mov bl,ah       ;功能交给bl                ;ah里面存放表里的数字
            mov bh,0        ;功能嫁接成bx        
            add bx,bx       ;因为占用两个字节,所以双倍寻址
            jmp word ptr table[bx]                ;通过表,来调用功能

     ;字符入栈

    charpush:

    ;字符出栈                                        ;这里就把之前我们的小功能放在这里就可以了.

    charpop:

    ;显示字符

    charshow: 

    sret:   pop es
            pop dx
            pop di
            pop bx
            ret

     

    前面对应的功能调用,变成

    mov ah,0        ;字符入栈
            call charstack

     

     mov ah,2
            call charstack  ;显示栈中的字符

            mov ah,2
            call charstack  ;再显示剩余的栈中的字符

     说到这里,大致就完成了,下面我把源码给出:

    1. assume cs:code,ds:data
    2. data segment
    3. db 32 dup(0)
    4. data ends
    5. code segment
    6. start:
    7. mov ax,data ;数据段导入
    8. mov ds,ax
    9. mov si,0 ;栈空间偏移地址
    10. mov dh,12 ;显存第12行
    11. mov dl,20 ;显存第二十列
    12. call getstr ;开始操作
    13. return: mov ax,4c00h
    14. int 21h ;结束操作
    15. getstr: push ax ;常规操作
    16. getstrs:mov ah,0
    17. int 16h ;执行中断,等待读取按键信息
    18. cmp al,20h ;判断是什么类型的按键信息
    19. jb nochar ;如果小于20h就不是字符,
    20. ;接着跳转到非字符处理
    21. ; mov al,0h
    22. mov ah,0 ;字符入栈
    23. call charstack
    24. mov ah,2
    25. call charstack ;显示栈中的字符
    26. jmp getstrs ;继续执行键盘指令
    27. nochar: ;这里是处理非字符
    28. cmp ah,0eh ;扫描判断是否是退格键
    29. je backspace ;是退格键,则跳转到退格键的功能
    30. cmp ah,1ch ;扫描是否是回车键
    31. je enter ;是则执行对应功能
    32. jmp getstrs ;不是以上按键,则仍未结束,接着执行int16h,
    33. ;等待输入
    34. ;接下来是退格键和回车键的处理
    35. backspace:
    36. mov ah,1 ;首先完成退格功能,效果就是出栈,然后显示
    37. call charstack
    38. ;然后完成,将字符出栈
    39. mov ah,2
    40. call charstack ;再显示剩余的栈中的字符
    41. jmp getstrs ;执行完之后,接着返回等待输入
    42. enter: ;这里是回车的功能
    43. mov al,48 ;这里是要结束,并且在栈里添加一个0,
    44. ;现在存在al里面
    45. mov ah,0 ;字符入栈
    46. call charstack
    47. mov ah,2
    48. call charstack ;显示栈中的字符
    49. jmp dis ;这里应该跳转吧,存疑
    50. dis: pop ax ;寄存器入栈
    51. ret ;执行结束
    52. ;接下来就是重磅的功能实现了,把小程序细分了,通过表调用
    53. ;这里是通过调用charstack 表,然后根据ah里的数值,来寻找表
    54. ;里的偏移地址,然后实现对应的功能的
    55. charstack:
    56. jmp short charstart
    57. table dw charpush,charpop,charshow
    58. top dw 0
    59. charstart:
    60. push dx ;常规的入栈
    61. push di
    62. push bx
    63. push es
    64. ;实现各个功能
    65. cmp ah,2 ;首先查表
    66. ja sret ;大于二就跳转结束
    67. mov bl,ah ;功能交给bl
    68. mov bh,0 ;功能嫁接成bx
    69. add bx,bx ;因为占用两个字节,所以双倍寻址
    70. jmp word ptr table[bx]
    71. ;字符入栈功能
    72. charpush:
    73. mov bx,top[0] ;将栈顶的指针给bx
    74. mov [si][bx],al ;然后将要输入的数字的阿斯克码,
    75. ;放在栈空间的栈顶
    76. inc top[0] ;当放入字符后,栈顶加一,这是我们自己做的栈
    77. jmp sret
    78. ;字符出栈
    79. charpop:
    80. cmp top[0],0 ;打狗看主人,看看栈里有没有数据,
    81. je sret ;没有数据就出来
    82. dec top[0] ;有数据就接着,将栈顶减一 ,
    83. ;此时栈顶对应出栈的字符
    84. mov bx,top[0] ;然后将栈顶交给bx,目的是传数据
    85. mov al,[si][bx];将要出栈的数据送到al,
    86. ;这里注意top每次都比数据的偏移地址多1
    87. jmp sret ;跳转结束
    88. ;这里在第(dh)行,第(dl)列显示字符
    89. charshow:
    90. mov bx,0b800h
    91. mov es,bx
    92. mov al,160
    93. mov ah,0
    94. mov dh,12
    95. mul dh
    96. mov di,ax
    97. mov dl,20
    98. add dl,dl
    99. mov dh,0
    100. add di,dx
    101. mov bx,0
    102. charshows:
    103. cmp bx,top
    104. jne noempty
    105. mov byte ptr es:[di],' '
    106. jmp sret
    107. noempty:
    108. mov al,[si][bx]
    109. mov es:[di],al
    110. mov byte ptr es:[di+2],' '
    111. inc bx
    112. add di,2
    113. jmp charshows
    114. sret: pop es
    115. pop dx
    116. pop di
    117. pop bx
    118. ret
    119. code ends
    120. end start

     

     

     

     

     

     

     

     

     

     

     

     

  • 相关阅读:
    二叉树定义
    17.2、JavaWeb-简介、JDBC的缺点、入门使用、mybatis的使用、条件查询、mybatis的参数传递
    【算法集训 | 暑期刷题营】7.28题---01背包问题
    2023.10月 面试题目
    C++算法前缀和的应用:分割数组的最大值的原理、源码及测试用例
    c语言一维数组和二维指针
    灾难恢复架构规划要点
    一幅长文细学CSS3
    Windows系统利用cpolar内网穿透搭建Zblog博客网站并实现公网访问内网!
    连接工具和idea能查询出数据库数据,项目中查不到数据库数据:解决办法
  • 原文地址:https://blog.csdn.net/qq_57484399/article/details/126501165