5.3.1 程序设计的基本步骤
(1)分析问题。
对题目给出的已知条件和要完成的任务进行详细的了解和分析,将实际问题转化为计算 机可以处理的问题。
(2)确定算法。
算法,即利用计算机解决问题的方法和步骤。计算机一般只能进行最基本的算术运算和 逻辑运算,要完成较为复杂的运算和控制操作,就必须选择合适的算法。
(3)设计流程。
将算法以流程图的方式画出来。
画流程图是指用各种图形、符号、指向线等来说明程序设计的过程。国际通用的图形和 符号说明如下:
1)椭圆框:起止框,在程序的开始和结束时使用,如图 5-3(a)所示。
2)矩形框:处理框,表示要进行的各种操作,如图 5-3(b)所示。
3)菱形框:判断框,表示条件判断,以决定程序的流向,如图 5-3(c)所示。
4)指向线:流程线,表示程序执行的流向,如图 5-3(d)所示。
(a)起止框 (b)处理框 (c)判断框 (d)流程线
图5-3 国际通用的流程图图形
(4)分配空间。
合理分配存储空间,即分段和数据定义,合理地使用寄存器。
(5)编写程序。
根据前面确定的算法流程图,采用汇编程序设计语言编写程序。
(6)调试运行。
程序编写好以后,检查语法错误,上机汇编、连接、调试运行,检验程序是否正确,能 否实现预期功能。
5.3.2 顺序、分支与循环程序设计
利用计算机解决实际问题时,其操作控制执行步骤有时是按顺序执行的,有时需要根据 实际情况选择某一个分支的操作执行,有时需要对某一些操作步骤反复执行,与之相对应,就 有3 种程序结构:顺序结构、分支结构、循环结构。
1.顺序结构
顺序结构程序完全按指令书写的前后顺序,从头至尾逐条执行,是最常用、最基本的程 序结构。常用于处理查表程序、计算表达式程序。
例 5-26 编写程序计算表达式: a * b c f d e
= +
− 。 DATA SEGMENT
A DB 5 B DB 10 C DB 15 D DB 30 E DB 20 F DB ? DATA ENDS
CODE SEGMENT
ASSUME CS: CODE, DS: DATA
START: MOV AX,DATA ;初始化 DS
MOV DS,AX
MOV AX,0
MOV AL,A
MUL B ;AX=A*B MOV BL,C
MOV BH,0
ADD AX,BX ;AX=A*B+C MOV CL, D
SUB CL, E ;CL=D-E
DIV CL ;AL=AX/CL=(A*B+C)/(D-E)
ASSUME DS:DATA,CS:CODE START: MOV AX,DATA
ASSUME DS:DATA,CS:CODE START: MOV AX,DATA
MOV DS,AX ;初始化
MOV AH,4CH
INT 21H ;返回 DOS
CODE ENDS
END START
(a)单分支结构 (b)双分支结构 图5-4 单、双分支结构流程图
(3)多分支程序。需要对多个条件进行判断,每个条件都对应一个分支,满足某个条件 时就进入相对应的分支执行。流程图如图5-5 所示。
图5-5 多分支结构流程图 对于多分支程序结构,可以采用以下两种解决方法:
1)逻辑分解方法。将多分支结构以逻辑等效的方法分解为一串双分支结构。
例 5-29 求符号函数Y 的值:
DATA SEGMENT X DB -10 Y DB ? DATA ENDS CODE SEGMENT
ASSUME DS:DATA,CS:CODE Y=
1 (X>0)
-1 (X<0)
0 (X=0)
满足条件1 满足条件2 满足条件N
分支语句体1 分支语句体2 …… 分支语句体N 条件判断
Y N
分支语句体1
分支语句体2 JMP N
语句体
分支语句体 JXX 条件满足吗? Y
JXX 条件满足吗?
START: MOV AX,DATA
FUN DW FUN0,FUN1,FUN2,FUN3,FUN4
;将标号 FUN0、FUN1、FUN2、FUN3、FUN4 偏移地址存入变量 FUN 中 DATA ENDS
CODE SEGMENT
ASSUME DS:DATA,CS:CODE START: MOV AX,DATA
INT 21H CODE ENDS END START
(4)说明:
①对于既能用双分支结构,又能用单分支结构实现的程序,宜采用单分支结构,以减少 转移次数,程序结构简单。
②对于多分支结构程序,宜采用地址表法,以减少转移次数,程序结构简单。
③对分支结构程序进行测试时,应对每一个分支都进行检测,才能保证整个程序的正 确性。
3.循环结构
根据某一条件是否成立判断是否需要重复执行某个语句组,这种程序结构称为循环结构。
(1)说明:
①一个循环结构一般由循环条件控制、循环体两部分组成。
循环条件控制:对循环条件进行判断,决定是否继续循环。
循环体:重复执行的语句组。
注意:循环体中应对循环条件的值进行修改,否则将会成为死循环(循环无限次)。
②根据循环条件控制所在的位置,可将循环结构分为两种:
z “先判断、后循环”:先判断循环条件,再决定是否执行循环体。
z “先循环、后判断”:先执行循环体(至少一次),再判断循环条件。
③用的循环指令有:
z 循环指令:LOOP、LOOPE/LOOPZ、LOOPNE/LOOPNZ。
z 转移指令:JCXZ、JXX。
其中 LOOP、JCXZ 常用于循环次数固定的循环结构,称这种循环结构为计数循环;
LOOPE/LOOPZ、LOOPNE/LOOPNZ、JXX 常用于循环次数不定的循环结构,称这种循环结构 为条件控制循环。
(2)应用举例。
1)计数循环:循环次数已知,用计数器 CX 计数来控制循环次数,要求在循环之前设置 CX 的值,即将循环次数送入CX 中,然后每循环一次计数器值减 1,直至其值减为 0 则不再循环。
例 5-31 设计一个程序,求1+2+" +99+100 的和,结果保存在变量 RESULT 中。
DATA SEGMENT RESULT DW 0 DATA ENDS CODE SEGMENT
ASSUME CS:CODE,DS:DATA START: MOV AX,DATA
MOV DS,AX
MOV AX,1
MOV CX,100 L0: ADD RESULT,AX
INC AX LOOP L0
MOV AH,4CH
INT 21H CODE ENDS
END START 的值大于MAX(或小于 MIN),则将它送入 MAX(或 MIN)中,这样循环结束后,MAX(或 MIN)里面存放的就是数组的最大值(或最小值)。
例 5-33 数组ARRAY 的长度为 N(即数组 ARRAY 中共有 N 个数,假设均为无符号字 节数),请将数组中的数按升序(从小到大)排序。
分析:排序算法有很多种,这里采用冒泡排序算法,其算法思想为:
①第 1 趟:从数组的最左边开始,依次将相邻两个数作比较,若前者大于后者,则交换 两者的值,经N-1 次两两相邻比较后,最大的数已交换到最后一个位置。
②第2 趟:对前 N-1 个数,按上法两两相邻比较,经 N-2 次比较后得到次大的数,安置 在第N-1 个元素的位置。
③重复上述过程,经过N-1 趟冒泡排序后,数据呈升序排列。
DATA SEGMENT
ARRAY DB 10,58,23,94,85,32,70,5,42,62 N EQU $-ARRAY ;N 为数组长度 DATA ENDS
CODE SEGMENT
ASSUME CS:CODE,DS:DATA START: MOV AX,DATA
MOV DS,AX
MOV CX,N-1 ;设置外层循环次数为 N-1,即冒泡排序趟数 L0: PUSH CX ;保存外层循环次数计数器
LEA SI,ARRAY
L1: MOV AL,[SI]
MOV AH,[SI+1]
CMP AL,AH ;相邻两个数作比较
JLE L2 ;若前者小于等于后者,则不交换,转去比较下一对相邻
;的两个数
MOV [SI+1],AL ;否则(即前者大于后者),交换两者的值
MOV [SI],AH
L2: INC SI
LOOP L1 ;内存循环两两比较,循环次数刚好等于外层循环次数计数器
;的值,所以不需要另外设置内层循环计数器 CX 的值
POP CX ;恢复外层循环次数计数器
LOOP L0 ;继续外层循环
MOV AH,4CH ;返回 DOS
INT 21H
CODE ENDS
END START
5.3.3 子程序设计
通常将一个大的程序按照功能划分为几个子程序(子程序就是一个功能上相对独立的程 序段,可以被多次重复调用。在一个完整的程序中,可以有多个子程序,子程序能被别的程序 所调用,也可以调用其他子程序,也称过程),通过调用各个子程序来实现程序的功能。
在定义子程序时,一般需要包含以下几个部分:
z 保护现场 z 子程序体 z 恢复现场 z 子程序返回
调用子程序时,子程序与主程序之间往往存在着数据的交流,称主程序传递给子程序的
数据为入口参数,称子程序返回给主程序的结果数据为出口参数。
常采用的参数传递方法有:通过寄存器传递、通过共享变量传递、通过堆栈传递。
(1)通过寄存器传递参数。
把入口参数、出口参数存放于约定的寄存器中,这是最常用的参数传递方式。通过寄存 器传递参数时,需要视具体情况来选择是否需要对入口参数、出口参数进行保护和恢复。由于 通用寄存器个数有限,通过寄存器传递参数的方法只适合参数个数较少的场合。
例 5-34 编写一个子程序,在数据块中查找某个指定数据,若找到则把该数据在数据块 中的序号返回,若找不到则返回-1。
DATA SEGMENT
ARRAY DB 10,58,23,94,85,32,70,5,42,62
N EQU $-ARRAY ;N 为数组长度 DATA ENDS
CODE SEGMENT
ASSUME CS:CODE,DS:DATA START: MOV AX,DATA
MOV DS,AX
LEA SI,ARRAY
MOV CX,N
MOV DL,94
CALL LOOKUP ;调用子程序
MOV AH,4CH ;返回 DOS
INT 21H
;子程序名:LOOKUP
;功能:片内 RAM 中的数据检索
;入口参数:SI 存放数据块首地址,CX 存放数据块长度,DL 存放要查找的数据
;出口参数:若找到,则将数据的序号存入 DI,否则存-1 到 DI LOOKUP PROC
PUSH CX
PUSH SI
MOV DI ,-1
L0: CMP DL,[SI]
JZ L1
INC SI
LOOP L0
JMP L2
L1: MOV DI,SI
SUB DI,OFFSET ARRAY L2: POP SI
POP CX
RET LOOKUP ENDP CODE ENDS END START
(2)通过共享变量传递参数。把入口参数、出口参数存放于约定的内存共享变量中。若 子程序和调用程序在同一程序模块中,则子程序可直接访问模块中的变量,进行参数传递;若 子程序和调用程序在两个不同的程序模块中,需要利用PUBLIC、EXTREN 对共享变量进行声 明才能访问共享变量。
若调用程序还要引用共享变量原来的值,则需要对共享变量进行保护和恢复。
BUF DW 10,58,23,94,85,32,70,5,32,62
N EQU ($-BUF)/2 ;N 为数据块长度 DATA ENDS
CODE SEGMENT
ASSUME CS:CODE,DS:DATA START: MOV AX,DATA
MOV DS,AX
MOV AX,OFFSET BUF
PUSH AX
CALL AVERAGE ;调用子程序
MOV AH,4CH ;返回 DOS
INT 21H
;子程序名:AVERAGE
;功能:求数据块中若干数据的平均值
;入口参数:数据块的首地址,压入堆栈
;出口参数:平均值,压入堆栈 AVERAGE PROC
POP BX
POP SI
MOV AX,0
MOV CX,N
L0: ADD AX,[SI]
ADD SI,2
LOOP L0
MOV CL,N
DIV CL
CBW
PUSH AX
PUSH BX
RET AVERAGE ENDP CODE ENDS END START
5.3.4 子程序的嵌套与递归 1.子程序的嵌套
在一个子程序中调用其他的子程序,称为子程序的嵌套,如图5-6 所示。嵌套的层数不限,
只要堆栈空间足够即可。
主程序 子程序SUB1 子程序SUB2 SUB1 PROC SUB2 PROC
……
CALL SUB1
…… ……
CALL SUB2
…… …… ……
……
RET RET 图5-6 子程序嵌套
例 5-37 ALDISP PROC PUSH AX
PUSH CX ;实现 AL 内容的显示 PUSH AX ;暂存 AX
MOV CL,4
SHR AL,CL ;转换 AL 的高 4 位 CALL HTOASC ;子程序调用(嵌套)
POP AX ;转换 AL 的低 4 位 CALL HTOASC ;子程序调用(嵌套)
POP CX
POP AX
RET ALDISP ENDP
;将 AL 低 4 位表达的一位十六进制数转换为 ASCII 码 HTOASC PROC
PUSH AX PUSH BX PUSH DX
MOV BX,OFFSET ASCII ;BX 指向 ASCII 码表 AND AL,0FH ;取得一位十六进制数
XLAT ASCII ;换码:AL←CS:[BX+AL],注意数据在代码段 CS MOV DL,AL ;显示
MOV AH,2 INT 21H
POP DX
POP BX
POP AX
RET ;子程序返回
;子程序的数据区
ASCII DB 30H,31H,32H,33H,34H,35H,36H,37H DB 38H,39H,41H,42H,43H,44H,45H,46H HTOASC ENDP
注意:子程序可以与主程序共用一个数据段,也可以使用不同的数据段(注意修改DS),
还可以在子程序最后设置数据区(利用CS 寻址)。
2.子程序的递归
2.子程序的递归