📄 汇010.txt
字号:
了解的内容:打印机输出、磁盘输入输出、通信口输入输出和异常情况处理等程序的编程方法,程序段前缀PSP的含义及其应用。
掌握的内容:驻留程序的设计方法,结构和记录在汇编语言中的应用方法,文件操作的编程,鼠标(Mouse)应用程序的编程。
熟练掌握的内容:处理键盘输入的各种方法,屏幕的定位和显示方法,字符串的处理方法,二进制数据和字符串之间的转换方法。
建议学习时间:12小时。
第10章 应用程序的设计
在前面各章节中,我们侧重介绍了汇编语言程序设计中各组成部分的作用,本章的重点是对前面所学知识的综合运用。希望通过各种不同类型的例子,使读者能够掌握用汇编语言编程的基本技巧。
10.1 字符串的处理程序
字符或字符串是一类重要的非数值计算的处理对象。许多编辑软件都具有字符串查找、替换、大小写的转换、单词的自动识别等功能,网络上的信息搜索也是现在一种常用的功能等,这些功能的实现无疑都要涉及到字符串的处理功能。
为了方便对字符串的处理,各种常用的编程环境也都给予了足够的支持。如:C语言编程环境提供了大量处理字符串的标准函数,象strlen、strcmp和strcpy等函数;C++、VC或VB等编程环境提供了字符串类String等。这些函数或类大大方便了程序员的编程。
在计算机系统内,为了加快字符串的处理,在其指令系统中设置了多条处理字符串的指令,其详细内容请参阅第5.2.11节中的介绍。
下面我们将通过几个例子来学习汇编语言处理字符串的方法。
例10.1 编写一个求字符串长度的子程序Strlen,要求字符串的首地址为入口参数,且以ASCII码0为结束符,CX为出口参数,其存放该字符串的长度。
解:
.MODEL SMALL, C
.DATA
buff DB "This is a example.", 0
.CODE
Strlen PROC USES AX BX, String:PTR BYTE
MOV BX, String
XOR CX, CX
MOV AL, [BX]
.WHILE AL!=0
INC
CX
INC
BX
MOV
AL, [BX]
.ENDW
RET
Strlen ENDP
.STARTUP
INVOKE Strlen, ADDR buff
.EXIT 0
END
例10.2 编写一个把字符串中的所有小写字符转换成大写字符的子程序Strupr,要求字符串的首地址和结束符为其入口参数。
解:
.MODEL SMALL, C
.DATA
buff
DB "This is a example.", 0
.CODE
Strupr PROC USES AX BX, String:PTR BYTE, Tail:BYTE
MOV BX, String
.REPEAT
MOV
AL, [BX]
.IF AL>='a' && AL<='z'
SUB
AL, 20H
MOV
[BX], AL
.ENDIF
INC
BX
.UNTIL AL==Tail
RET
Strupr ENDP
.STARTUP
INVOKE Strupr, ADDR buff, 0
.EXIT 0
END
例10.3 编写一个从字符串中拷贝子串的子程序Strncpy,它有四个参数str1、str2、idx和num,其具体功能为把字符串str2中从第idx个(从0开始记数)字符开始、num个字符传送给str1,字符串str1和str2都是以ASCII码0为结束符。
解:
.MODEL SMALL, C
.DATA
str1
DB "12345ABCDEF", 0
str2
DB 20 DUP('A')
.CODE
Strlen PROC USES AX BX, String:PTR BYTE
…… ;参见例10.1
Strlen ENDP
Strncpy PROC USES AX CX DI SI DS ES, str1:FAR PTR BYTE, str2:FAR PTR BYTE, idx:WORD, num:WORD
LES DI, str1
LDS SI, str2 ;取两个字符串的首地址
INVOKE Strlen, SI ;计算源字符串的长度,在CX中
MOV AX, idx
.IF AX >= CX ;若字符起点就超过源串的长度
MOV
BYTE PTR ES:[DI], 0 ;拷贝的字符串为“空”
JMP
over
.ENDIF
ADD SI, AX ;定源串中字符的起点SI
MOV CX, num
CLD
.REPEAT
LODSB
STOSB
.UNTILCXZ AL==0
.IF AL!=0 ;设置目标串的结束符
MOV
BYTE PTR[DI], 0
.ENDIF
over: RET
Strncpy ENDP
.STARTUP
INVOKE Strncpy, ADDR str2, ADDR str1, 3, 5
.EXIT 0
END
例10.4 编写一个把字符串中空格和TAB压缩掉的子程序Compress,字符串String是以ASCII码0为结束符。
解:
.MODEL SMALL, C
.DATA
SPACE
EQU 20H
TAB
EQU 9H
Buff
DB "12 3 4 Ab cdef", 0
.CODE
Compress PROC USES AX BX SI DS, String:FAR PTR BYTE
LDS SI, String ;SI用于扫描字符串的指针
MOV BX, SI ;BX用于存放结果的指针
.REPEAT
MOV
AL, [SI]
INC
SI
.IF AL!=SPACE && AL!=TAB
MOV [BX], AL
INC BX
.ENDIF
.UNTIL AL==0
RET
Compress ENDP
.STARTUP
INVOKE Compress, ADDR Buff
.EXIT 0
END
从上面四个例子,我们不难看出处理字符串的一般方法,感兴趣的读者可自行编写实现字符串变小写、整体拷贝、逆转和查找等功能的子程序,甚至还可以建立起自己的字符串处理库文件。
10.2 数据的分类统计程序
数据的分类和统计也是一类非数值计算,数据的分类统计方法在例6.10中已介绍,下面通过一个例子介绍数据的分类存储问题。
例10.5 统计从地址0040H:0000H开始的100个字中,把正数和负数按照它们先后出现的次序分别存储在缓冲区Data1和Data2,并把每类的个数存入相应缓冲区的第一个字单元中。
解:由于在指定地址之后的100个字中,可能存在全是正数或负数的情况,所以,缓冲区Data1和Data2的容量都应是100个字。
.MODEL SMALL
.DATA
Num = 100
Data1
DW ?, Num dup(?)
Data2
DW ?, Num dup(?)
.CODE
.STARTUP
MOV
AX, 40H
MOV
ES, AX
LEA
SI, Data1+2 ;指向存储正数的缓冲区
LEA
DI, Data2+2 ;指向存储负数的缓冲区
XOR
BX, BX ;BX用于扫描存储单元
MOV
CX, 100 ;字符个数
.REPEAT
MOV
AX, ES:[BX]
ADD
BX, 2
CMP
AX, 0
.CONTINUE .IF ZERO?
JL
next1
MOV
[SI], AX ;向正数缓冲区内存储数据
ADD
SI, 2
.CONTINUE
next1:
MOV
[DI], AX ;向负数缓冲区内存储数据
ADD
DI, 2
.UNTILCXZ
SUB
SI, OFFSET Data1+2
SUB
DI, OFFSET Data2+2
SHR
SI, 1
SHR
DI, 1
MOV
Data1, SI
MOV
Data2, DI ;把每类的统计个数存入缓冲区的第一个字单元
.EXIT
0
END
例10.6 用键盘输入任意一字符串,分类统计该字符串中每个数字和字母的出现次数。
解:
.MODEL SMALL
.DATA
N = 80
Buff
DB N, ?, N DUP(?)
Num
DW 36 DUP(0) ;每个字用于存放'0'~'9','A'~'Z'出现的个数
.CODE
.STARTUP
LEA
DX, Buff
MOV
AH, 0AH
INT
21H ;输入一个字符串
XOR
CH, CH
MOV
CL, Buff+1 ;CX=输入字符串的个数
LEA
SI, Buff+2
XOR
BX, BX
.REPEAT
MOV
BL, [SI] ;考虑下面的思考题
INC
SI
.IF BL>='0' && BL<='9'
;分类统计'0'~'9'中的每个数字的个数
SUB
BL, '0'
ADD
BX, BX
INC
Num[BX]
.CONTINUE
.ENDIF
.IF BL>='a' && BL<='z'
SUB
BL, 20H ;小写变大写
.ENDIF
.IF BL>='A' && BL<='Z'
;分类统计'A'~'Z'中的每个字母的个数
SUB
BL, 'A'-10
ADD
BX, BX
INC
Num[BX]
.ENDIF
.UNTILCXZ
.EXIT 0
END
思考题:在本例中,用指令“MOV BL, [SI]”来把当前检测的字符存入BL,当然,我们也可以用AL来代替BL,有关指令要作相应的改动,但这样做,会更方便吗?希望读者能知道:为什么要用BL,而不用AL?
10.3 数据转换程序
数据类型转换是输入输出过程中经常遇到的问题。输入时,计算机系统要把用户从键盘上输入的字符串转变成相应的数值,并存储在内存中;输出时,要把计算机内部的二进制数据形式转换成相应的十进制字符串,然后再输出。
在高级语言编程环境中,程序员能用各种输入输出语句,按一定的格式进行交互式操作,很少或根本不关心输入输出是如何实现的。有的程序员甚至认为其输入的就是十进制数值,输出数据也就是把内存中存储的数据直接输出出来。其实,输入输出过程并不是如此简单,计算机系统要进行复杂而又细致的数据类型转换和格式化等工作。
本节试图通过用汇编语言实现数据类型的转换来反映输入输出的本来面目,使程序员在用高级语言编程时,对其输入输出语句的实现过程有所了解,也知道有别人(或编译程序)帮他完成了输入输出的准备工作。
例10.7 编写一个程序,它能把字类型变量的数值以十进制形式输出出来。若该数值为负数,则需要输出负号"-",否则,不输出符号。
解:
鉴于按二进制输出的特殊性,我们可以把它优化成例10.8的形式,按十六进制输出也可以按“四位二进制对应一位十六进制”的规则进行优化的。
例10.7是用“用16位除10”的方法从低向高依次得到每位的数值,但若待输出的数据是32位,用10除之后,其商很可能会超过16位,所以,不能简单地引用例10.7的方法来输出32位二进制。
假设:32位二进制数Z为A×216+B,其中:A和B都是16位二进制数。 用10去除A,得:A=A1×10+A2,于是,
(1)
假设A2×216+B被10除后所得的商和余数分别为B1和C1(B1≥0,C1≥0)。
利用式(1)和“A2<10”,我们不难看出:Z的个位就是C1和B1<216。
令Z1=A1×216+B1,显然,Z1就是Z/10所得到的商。
对于Z1,再利用式(1)得到商Z2和C2。……,重复上面的步骤,直到所得商为0为止。
下面的例10.9就是利用上面方法来输出32位二进制数值。
例10.9 编写一个子程序,该子程序能把32位二进制变量的数值以十进制形式输出出来。若该数值为负数,则需要输出负号"-",否则,不输出符号。
解:
.MODEL SMALL, C
.DATA
CR = 13
LF = 10
Data1
DD 908976789
.CODE
;子程序Display是按十进制输出32位二进制数值SOURCE
Display PROC USES AX BX CX DX SI DI SOURCE:DWORD
LOCAL FLAG:BYTE ;定义一个字节类型的局部变量FLAG
MOV BX, WORD PTR [SOURCE]
MOV CX, WORD PTR [SOURCE+2]
MOV FLAG, 0 ;FLAG=0——正数
CMP CX, 0
JGE next
INC FLAG ;FLAG=1——负数
NOT BX
NOT CX
ADD BX, 1 ;能否用指令INC BX?
ADC CX, 0 ;上四条指令把32位数CX-BX变为正数
next:
XOR DI, DI ;压入堆栈字符的个数
MOV SI,10 ;用10来除
.REPEAT ;本循环把32位二进制数转换成十进制
XOR
DX, DX ;数的字符串存入堆栈之中
MOV
AX, CX
DIV
SI
MOV
CX, AX
MOV
AX, BX
DIV
SI
ADD
DL, '0'
PUSH
DX
INC
DI
MOV
BX, AX
.UNTIL BX==0 && CX==0
.IF FLAG==1 ;判断前面转换的数是否为负数
MOV
AL, '-' ;若是,把符号'-'压入堆栈
PUSH
AX
INC
DI
.ENDIF
MOV CX, DI
.REPEAT ;本循环把堆栈中的字符串显示出来
POP
DX
MOV
AH, 2
INT
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -