如何显示汇编中两个字符串之间相同和不同字符的数量?

How do I display the number of equal and different characters between two strings in assembly?

程序将要求 2 个字符串(例如,最大长度为 150 个字符)。

输入

字符串 1: "Hello this is the first string"

字符串 2: "Woops this string is different and longer!"

输出

Number of equal characters = 26
Number of different characters = 15

我记得我们不必“计算”不同的字符,因为我们可以获得最长字符串的大小并减去相等字符的数量,但我不知道如何先做这个比较(相等字符的个数)。

我该怎么做?我可以为那个比较做一个宏吗?

更新

我用的是EMU8086,MASM。我不想使用字符串指令。

这是我当前的代码,按照评论中的说明进行操作,但我仍然无法正常工作。

.model small
.stack 100h
.data
    Text1 DB "Please enter the first phrase: ",13,10,'$'
    Text2 DB "Please enter the second phrase: ",13,10,'$'
    
    max1 DB 151 ;We add one to 150 for the ENTER
    charRead1 DB 0       
    
    max2 DB 151 ;We add one to 150 for the ENTER
    charRead2 DB 0
    
    TextoEquals DB "Number of equal characters: ",13,10,'$'
    TextoDiffer DB "Number of different characters: ",13,10,'$'
     
    linefeed DB 13, 10, "$" 
    size1 dw 0000h
    size2 dw 0000h
.code

AllMacros:
        
    MagicFunction MACRO
        cld                     ; Clear direction flag
        xor bx, bx              ; Reset counter
        mov si, OFFSET max1     ; Pointer to shorter string
        mov dx, size1           ; Length of the shorter string
        NextChar: 
            lodsb                   ; Fetch next character from shorter string
            mov di, offset max2     ; Pointer to longer string
            mov cx, size2              ; Length of the longer string
            repne 
            scasb
            jne NotFound 
            inc bx                  ; Increment counter
            mov byte ptr [di-1], 0  ; Strike by replacing with zero   
            
            NotFound:
                dec dx               ; Repeat for all characters in shorter string
                jnz NextChar   
    ENDM
    
    
    PrintText MACRO str
        MOV AH, 09h
        LEA DX, str
        INT 21h
    ENDM 
    
    PrintCls MACRO
        mov ah, 09h
        mov dx, offset linefeed
        int 21h 
    ENDM


Inicio:
    mov ax, @data
    mov ds, ax
    
    PrintText Text1
    
    mov ah, 0Ah        
    lea dx, max1 
    mov size1, offset max1
    int 21h
                           
    PrintCls  
    
    PrintText Text2
    
    mov ah, 0Ah        
    lea dx, max2 
    mov size2, offset max2
    int 21h  
    
    PrintCls  
    
    MagicFunction
    
    PrintText TextoEquals
    
    mov ah, 9
    lea dx, bx
    int 21h
     
    PrintCls
    
    PrintText TextoDiffer
    
    mov ah, 9
    lea dx, dx
    int 21h
    
END Inicio 

你会如何在纸上做到这一点?

您将依次考虑较短字符串中的每个字符,并尝试在较长字符串中找到它。如果您找到了该字符,那么您将递增计数器,并从较长的字符串中删除该字符,这样就无法再次找到该字符。

发件人:

 -- ----------- --------- ----
Hello this is the first string

Woops this string is different and longer!
 -  ----------------- -- --- --    -

收件人:

Hello这是the第一个st环(剩余4个字符)
==> 30 - 4 = 26 是 'equal'

Woops 这个字符串是 diff erent and longer!(剩余 16 个字符)
==> 16 个是 'different'

在汇编代码中它可能如下所示:

S1 db "Hello this is the first string"
S2 db "Woops this string is different and longer!"

  ...

  cld                ; Clear direction flag
  xor bx, bx         ; Reset counter
  mov si, S1         ; Pointer to shorter string
  mov dx, 30         ; Length of the shorter string
NextChar:
  lodsb              ; Fetch next character from shorter string
  mov di, S2         ; Pointer to longer string
  mov cx, 42         ; Length of the longer string
  repne scasb
  jne NotFound 
  inc bx             ; Increment counter
  mov byte [di-1], 0 ; Strike by replacing with zero
NotFound:
  dec dx             ; Repeat for all characters in shorter string
  jnz NextChar

scasbAL[es:di] 设置标志处的字节进行比较,并递增 DI 寄存器(前提是方向标志已清除)。

repne scasb 只要 CX 中的计数未用完且未设置零标志,就会一直比较。

I have in mind that we don't have to "calculate" the different characters as we can get the size of the longest string and subtract the number of equal characters...

没错。
上面的代码应该会产生下一个结果:

相等字符数 = 26
不同字符的数量 = 42 - 26 = 16

感谢您在问题中包含代码并适当地标记它。这次我可以写一篇完整的评论。请注意,另一个答案并不像您似乎暗示的那样半个答案。这个答案与你当时的问题是一致的。


How can I do this? Could I do a macro for that comparison?

我在之前的回答中已经提供的代码,并且您已经采用了,并不是真的要用作宏。如果有的话,它可以成为一个子例程,但我认为在您的程序中这样做没有任何好处。只需内联代码。

Inicio:
    mov ax, @data
    mov ds, ax

正如我在之前的回答中所写,scasb 指令使用了 ES 段寄存器。您应该将其设置为与 DS 段寄存器相同:

Inicio:
    mov ax, @data
    mov ds, ax
    mov es, ax

正在输入

详细解释了 DOS.BufferedInput 函数 0Ah 的工作原理。
你写的地方:

max1 DB 151 ;We add one to 150 for the ENTER
charRead1 DB 0       

您忘记为您希望从用户那里收到的实际字符预留空间。接下来是它需要的样子:

max1      DB 151
charRead1 DB 0       
chars1    DB 151 dup(0)

当您调用此 DOS 函数 0Ah 时,您还将在 charRead1 字节中收到实际输入的长度。这是您需要存储在 size1 变量中的值(不是像您的代码现在所做的输入结构的地址!):

mov ah, 0Ah        
lea dx, max1 
mov size1, offset max1   <<<< is wrong
int 21h

当然,您只能在 DOS 调用完成后执行此操作:

mov  dx, offset max1  ; Address of the input structure
mov  ah, 0Ah
int  21h
mov  al, charRead1    ; Length of the string
mov  ah, 0            ; Extending is needed because size1 is a word-sized variable
mov  size1, ax

第二个字符串也是如此。

正在输出

Displaying numbers with DOS 详细解释了如何打印 16 位寄存器的值 AX

mov ah, 9
lea dx, bx
int 21h

当您使用 DOS.PrintString 函数 09h 时,DOS 期望在 DX 中有一个指向字符串的指针。 BX 代码中的寄存器包含一个数字,您仍然需要将其转换为一串数字。 link 中的 'method 2' 代码段会执行此操作,并且还会使用 DOS.PrintCharacter 立即显示字符 函数 02h。 Displaying characters with DOS or BIOS 有很多输出函数的信息。您只需在省略号处插入代码片段即可。

mov  ax, bx
push bx             ;(0)
...
pop  bx             ;(0)

由于这是我的原始代码,我会为您复制...

    mov     ax,bx
    push    bx             ;(0)
    mov     bx,10          ;CONST
    xor     cx,cx          ;Reset counter
.a: xor     dx,dx          ;Setup for division DX:AX / BX
    div     bx             ; -> AX is Quotient, Remainder DX=[0,9]
    push    dx             ;(1) Save remainder for now
    inc     cx             ;One more digit
    test    ax,ax          ;Is quotient zero?
    jnz     .a             ;No, use as next dividend
.b: pop     dx             ;(1)
    add     dl,"0"         ;Turn into character [0,9] -> ["0","9"]
    mov     ah,02h         ;DOS.DisplayCharacter
    int     21h            ; -> AL
    loop    .b
    pop     bx             ;(0)

(0) 必须保留 BX,因为您需要再次使用该值来显示不同字符的数量。
您知道您不必“计算”不同的字符,因为您可以获得最长字符串的大小并减去相等字符的数量。但是你为什么不在你的程序中做那个减法?

    mov     ax,size2       ;SizeLongestString
    sub     ax,bx          ; minus EqualCharacters

    mov     bx,10          ;CONST
    xor     cx,cx          ;Reset counter
.a: xor     dx,dx          ;Setup for division DX:AX / BX
    div     bx             ; -> AX is Quotient, Remainder DX=[0,9]
    push    dx             ;(1) Save remainder for now
    inc     cx             ;One more digit
    test    ax,ax          ;Is quotient zero?
    jnz     .a             ;No, use as next dividend
.b: pop     dx             ;(1)
    add     dl,"0"         ;Turn into character [0,9] -> ["0","9"]
    mov     ah,02h         ;DOS.DisplayCharacter
    int     21h            ; -> AL
    loop    .b

所以好像我们插入了两次代码来转换和显示一个数字。这是将它放入子例程然后 call 两次的一个很好的理由。见下文。


将所有内容放在一起,我们得到以下为 emu8086 (MASM) 制作的程序

.model small
.stack 100h
.data
    Text1       DB "Please enter the shorter phrase: ",13,10,'$'
    Text2       DB "Please enter the longer phrase: ",13,10,'$'
    
    max1        DB 151
    charRead1   DB 0       
    chars1      DB 151 dup(0)

    max2        DB 151
    charRead2   DB 0       
    chars2      DB 151 dup(0)
    
    TextoEquals DB "Number of equal characters: ",13,10,'$'
    TextoDiffer DB "Number of different characters: ",13,10,'$'
     
    linefeed    DB 13, 10, "$" 
    size1       DW 0
    size2       DW 0

.code
AllMacros:
    PrintText MACRO str
        MOV AH, 09h
        LEA DX, str
        INT 21h
    ENDM 
    
    PrintCls MACRO
        mov ah, 09h
        mov dx, offset linefeed
        int 21h 
    ENDM

Inicio:
  mov  ax, @data
  mov  ds, ax
  mov  es, ax
    
  PrintText Text1
    
  mov  dx, offset max1
  mov  ah, 0Ah
  int  21h
  mov  al, charRead1
  mov  ah, 0
  mov  size1, ax

  PrintCls  
  PrintText Text2
    
  mov  dx, offset max2
  mov  ah, 0Ah
  int  21h
  mov  al, charRead2
  mov  ah, 0
  mov  size2, ax

  PrintCls  

  cld                     ; Clear direction flag
  xor  bx, bx             ; Reset counter
  mov  si, offset chars1  ; Pointer to shorter string
  mov  dx, size1          ; Length of the shorter string
NextChar:
  lodsb                   ; Fetch next character from shorter string
  mov  di, offset chars2  ; Pointer to longer string
  mov  cx, size2          ; Length of the longer string
  repne scasb
  jne  NotFound 
  inc  bx                 ; Increment counter
  mov  byte ptr [di-1], 0 ; Strike by replacing with zero
NotFound:
  dec  dx                 ; Repeat for all characters in shorter string
  jnz  NextChar

  PrintText TextoEquals
    
  mov  ax, bx
  call DisplayAX

  PrintCls
  PrintText TextoDiffer
    
  mov  ax, size2
  sub  ax, bx
  call DisplayAX

  mov  ax, 4C00h         ; DOS.Terminate
  int  21h
    
DisplayAX:
    push    bx             ;(0)
    mov     bx,10          ;CONST
    xor     cx,cx          ;Reset counter
.a: xor     dx,dx          ;Setup for division DX:AX / BX
    div     bx             ; -> AX is Quotient, Remainder DX=[0,9]
    push    dx             ;(1) Save remainder for now
    inc     cx             ;One more digit
    test    ax,ax          ;Is quotient zero?
    jnz     .a             ;No, use as next dividend
.b: pop     dx             ;(1)
    add     dl,"0"         ;Turn into character [0,9] -> ["0","9"]
    mov     ah,02h         ;DOS.DisplayCharacter
    int     21h            ; -> AL
    loop    .b
    pop     bx             ;(0)
    ret

END Inicio 

永远不要忘记正确退出程序。对于 DOS,首选方法是使用 DOS.Terminate 函数 4Ch.


[最近加题]

I don't want to use string instructions

这对我来说没问题。只需用等效代码替换 lodsbrepne scasb 字符串指令:

  xor  bx, bx             ; Reset counter
  mov  si, offset chars1  ; Pointer to shorter string
  mov  dx, size1          ; Length of the shorter string
NextChar:
  mov  al, [si]           ; Fetch next character from shorter string
  inc  si
  mov  di, offset chars2  ; Pointer to longer string
  mov  cx, size2          ; Length of the longer string
Scan:
  cmp  al, [di]
  je   Found
  inc  di
  loop Scan
  jmp  NotFound
Found:
  inc  bx                 ; Increment counter
  mov  byte ptr [di], 0   ; Strike by replacing with zero
NotFound:
  dec  dx                 ; Repeat for all characters in shorter string
  jnz  NextChar

严格来说已经不用设置了ES,但是留着对程序没有影响