本节内容:字符串操作举例。
■例3:接收字符串去掉空格后逆向输出,t17-3.asm。
■例4:字符串拼接,t17-4.asm。
■例5:判断子串,t17-5.asm。
17.3.1 例3:接收字符串去掉空格后逆向输出
动手实验105:写一个程序,先接收一个字符串,然后抽去其中的空格,最后按相反的顺序显示。
在理解下面示例程序的基础上,自己独立编写源程序。编译完成后,在debug调试器中单步跟踪调试,以验证程序的正确性。
■算法分析:
第一步:DOS 10号功能接收字符串;
第二步:repz scasb过滤空格;
第三步:先入栈后出栈的方式逆向输出。
■流程图:如图17-1所示。
图17-1 接收字符串去掉空格后逆向输出
■示例代码81:
;例3:写一个程序,先接收一个字符串,然后抽去其中的空格,最后按相反的顺序显示
;请注意删除空格的方法和方向标志的变化
;程序名:t17-3.asm
;功能:接收一个字符串,去掉其中的空格后按相反的顺序显示
;=========================================================
;符号常量的定义
maxlen=16 ;字符串最大长度
space=' ' ;空格
cr=0dh ;回车符
lf=0ah ;换行符
;数据段的定义
assume cs:code,ds:data
data segment
buffer db maxlen+1,0,maxlen+1 dup(0)
string db maxlen+3 dup(0)
data ends
code segment
start:
mov ax,data
mov ds,ax
mov es,ax
;
mov dx,offset buffer
mov ah,10
int 21h
;
mov cl,buffer+1
xor ch,ch
jcxz ok
;
cld
mov si,offset buffer+2
mov di,offset string
;预处结尾字符0
mov al,0
stosb
;过滤空格
mov al,space
pp1:
jcxz pp3
xchg si,di ;si=string,di=buffer
repz scasb
xchg si,di ;si=buffer,di=string
dec si ;scasb回退
inc cx ;repz回退
;
pp2:
cmp byte ptr[si],space;si=buffer,di=string
jz pp1
movsb
loop pp2
pp3: ;string存入回车换行
mov al,cr
stosb
mov al,lf
mov [di],al
;倒序显示
std
mov si,di
pp4:
lodsb
or al,al
jz ok
mov dl,al
mov ah,2
int 21h
jmp pp4
ok:
mov ax,4c00h
int 21h
code ends
end start
请注意上述示例中方向标志位的处理,以及删除空格字符的处理方法。
17.3.2 例4:字符串拼接
动手实验106:写一个在字符串1后追加字符串2的程序,设字符串均以0结尾。
在理解下面示例程序的基础上,自己独立编写源程序。编译完成后,在debug调试器中单步跟踪调试,以验证程序的正确性。
■算法分析:
第一步:将变址DI指针移到字符串1结尾;
第二步:计算字符串2的长度;
第三步:repz movsw指令将字符串2复制到字符串1结尾处;
注意奇数字节的处理movsb。
■示例代码82:
;例4:写一个在字符串1后追加字符串2的程序,设字符串均以0结尾
;程序名:t17-4.asm
;功能:字符串1后追加字符串2
;=========================================================
assume cs:code,ds:data
;符号常量的定义
maxlen=16 ;字符串最大长度
data segment
buffer1 db "1234",maxlen dup(0)
buffer2 db "56789",24h,0
data ends
code segment
start:
mov ax,data
mov ds,ax
;
mov si,offset buffer1
mov di,offset buffer2
call strcat
;
mov dx,offset buffer1
mov ah,9
int 21h
;
mov ax,4c00h
int 21h
;-----------------------------------------------------
;写一个在字符串1后追加字符串2的子程序,设字符串均以0结尾
;说明:传送的字符串2包括结束标志,
;并从偶地址开始字传送(偶地址效率高,也可以直接传送),需要考虑可能遗留一个字节
;子程序名:strcat
;功能:在字符串1末尾追加字符串2
;入口参数:DS:SI=字符串1起始地址的段值:偏移
;DS:DI=字符串2起始地址的段值:偏移
;出口参数:无
;说明,不考虑字符串1后是否留有足够的空间
strcat proc
push es
push ax
push cx
push si
push di
cld
push ds
pop es ;使ES同DS
;将si指针移到字符串1的结尾0处
push di
mov di,si ;将字符串1作为目的串
xor al,al
mov cx,0ffffh
repnz scasb ;确定字符串1的尾 zf=1时
lea si,[di-1] ;si指向字符串1的结束标志
;测量字符串2的长度
pop di
mov cx,0ffffh
repnz scasb ;测量字符串2的长度
not cx ;CX包括结束标志字符串2的长度
sub di,cx ;DI再次指向字符串2的首
;将字符串2添加到字符串1结尾处
xchg si,di ;为拼接做准备
strcat1:
shr cx,1 ;移动数据库长度除2
repz movsw ;字移动、ZF=0时结束
jnc strcat2 ;进位标志CF=0
movsb ;补字移动遗留的一字节
strcat2: ;
pop di
pop si
pop cx
pop ax
pop es
ret
strcat endp
code ends
end start
上述示例先将指针移到字符串1的结尾处,然后将字符串2拷贝到字符串1的结尾处。注意,此代码实现为了提高拷贝效率,使用MOVSW指令,但需要考虑奇数个字节,最后一个奇数字节使用MOVSB指令。该示例对这一细节的处理有所冗余,请读者尝试优化代码。
17.3.3 例5:判断子串
动手实验107:写一个判字符串2是否为字符串1的子串的程序,设字符串均以0结尾。
在理解下面示例程序的基础上,自己独立编写源程序。编译完成后,在debug调试器中单步跟踪调试,以验证程序的正确性。
■算法分析:
第一步:分别测量字符串1和字符串2的长度;
第二步:双循环结构匹配字符串2和字符串1;
第三步:输出结果;
■伪代码:参见示例代码47;
■流程图:如图17-2所示。
■示例代码83:
;例5:写一个判字符串2是否为字符串1的子串的程序,设字符串均以0结尾
;程序名:t17-5.asm
;功能:判字符串2是否为字符串1的子串
;=========================================================
assume cs:code,ds:data
data segment
string1 db "12345",0
string2 db "1234",0
result1 db "YES!",24H
result2 db "NO!",24H
data ends
code segment
start:
mov ax,data
mov ds,ax
mov es,ax
;判断子串
mov si,offset string1
push ds
push si
mov di,offset string2
push es
push di
call far ptr strstr
;输出结果
or ax,ax
jz NO
mov dx,offset result1
mov ah,9
int 21h
jmp OVER
NO:
mov dx,offset result2
mov ah,9
int 21h
;
OVER:
mov ax,4c00h
int 21h
;-----------------------------------------------------
;子程序名:strstr
;功能:判字符串2是否为字符串1的子串
;入口参数:指向字符串的远指针
;出口参数:DX:AX返回指向字符串2在字符串1中首次出现处的指针,AX=0不是子串
;说明:调用方法:
;压入字符串2的远指针
;压入字符串1的远指针
;call far ptr strstr
strstr proc far
push bp
mov bp,sp
push ds
push es
push bx
push cx
push si
push di
les bx,[bp+6] ;取str2指针
cmp byte ptr es:[bx],0 ;判断str2是否为空串
jnz strstr1 ;否
jmp short strstr3
strstr1:
cld
les di,[bp+10] ;取str1指针
push es
mov bx,di ;str1偏移送bx寄存器
xor ax,ax
mov cx,0ffffh ;测str1长度
repnz scasb
not cx
mov dx,cx ;dx=str1的长度(含结束标志)
les di,[bp+6] ;取str2指针
push ds
mov bp,di ;str2偏移送bp寄存器
xor ax,ax
mov cx,0ffffh
repnz scasb ;测str2的长度
not cx
dec cx ;cx=str2的长度(不含结尾0)
pop ds ;此时DS:BP指向str2
pop es ;ES:BX指向str1
mov si,bp ;DS:SI指向str2
mov di,bx ;ES:DI指向str1
mov ax,cx ;保存字符串2的长度
strstr2:
mov cx,ax ;cx=str2的长度(不含结尾0)
mov di,bx ;ES:DI指向str1
repz cmpsb ;在str1中搜索str2的字符
jcxz strstr4 ;是子串
cmp byte ptr es:[di],0 ;str1结尾
jz strstr3
mov si,bp ;不相同,DS:SI重新指向str2
inc bx ;继续比对下一个字符
jmp strstr2
strstr3:
xor ax,ax ;不是子串
jmp strstr5
strstr4:
mov ax,1
strstr5:
pop di
pop si
pop cx
pop bx
pop es
pop ds
pop bp
ret
strstr endp
code ends
end start
上述示例采用双循环结构,string2作为子串与string1进行比较。内循环将string2的每个字符与string1指定位置处的字符进行逐一比对。外循环从string1每个字符位置开始逐一与string2进行比对。
练习
1、请说明flag寄存器标志位DF的作用。如何设置DF?
2、 请写出与“LODSB”指令等价的程组合指令。
3、请写出与“STSOW”指令等价的组合指令。
4、请写出与“SCASB”指令等价的组合指令。
5、请写一个程序片段代替指令“REP MOVSW”。
6、请写一个片段代替指令“REPNZ CMPSB”。
7、请在debug调试器中观察重复前缀REP与重复前缀REPZ/REPE的机器码。
8、编写一个实现复制数据块的近过程。
9、编写一个实现字符串拷贝的近过程。堆栈传递作为参数的源和目标字符串首地址的偏移。
10、编写一个把字符串中的小写字母转换为大写(字符串以0结尾)的远过程。
11、改写例1和例2,通过堆栈传递入口参数。
12、编写一个把字符串2插入字符串1指定位置的远过程。
13、编写一个截取字符串某子串的近过程。
14、编写一个去掉字符串前导空格的近过程。
15、利用字符串操作指令写一个清屏的过程。只考虑字符显示方式。
本文摘自编程达人系列教材《X86汇编语言基础教程》。