第五章 循环与分支程序设计 ch5 2019/9/19
● 教学重点 1. 掌握基本程序结构――顺序结构、循环结构、分支结构及其汇编语言程序设计 2. 熟悉常见程序设计问题: 1. 掌握基本程序结构――顺序结构、循环结构、分支结构及其汇编语言程序设计 2. 熟悉常见程序设计问题: 数据范围判断(0~9、A~Z、a~z) 字母大小写转换; 字符串传送、比较等操作 求最大最小值、数据求和、统计字符个数 数组排序,查找,插入,删除
● 程序结构 (1) 顺序结构 (2) 循环结构 (3)分支结构 (4) 子程序结构 … (5)复合结构:多种程序结构的组合 ch5 ● 程序结构 (1) 顺序结构 (2) 循环结构 (3)分支结构 (4) 子程序结构 … (5)复合结构:多种程序结构的组合 ch5 2019/9/19
● 编制汇编语言程序的步骤 (1) 分析题意,确定算法 (2) 根据算法画出程序框图 (3) 根据框图编写程序 (4) 上机调试程序 ch5 (1) 分析题意,确定算法 (2) 根据算法画出程序框图 (3) 根据框图编写程序 (4) 上机调试程序 ch5 2019/9/19
顺序程序完全按指令书写的前后顺序执行每一条指令,是最基本、最常见的程序结构 5.0 顺序程序设计 顺序程序完全按指令书写的前后顺序执行每一条指令,是最基本、最常见的程序结构 一般纯粹的顺序结构的程序设计较少。 ch5 2019/9/19
assume cs:code,ds:data WX+Y+Z 例 data segment X dw 5 Y dw 6 Z dw 7 W dw ? data ends code segment main proc far assume cs:code,ds:data start: push ds xor ax,ax push ax mov ax,data mov ds,ax mov ax,X add ax,Y add ax,Z mov W,ax ret main endp code ends end start ch5 2019/9/19
;查表法,实现一位16进制数转换为ASCII码显示 data segment 例 代码转换 ;查表法,实现一位16进制数转换为ASCII码显示 data segment ASCII db 30h,31h,32h,33h,34h,35h db 36h,37h,38h,39h ;0~9的ASCII码 db 41h,42h,43h,44h,45h,46h;A~F的ASCII码 hex db 0bh ;任意设定一个待转换的一位16进制数 data ends code segment main proc far ch5 2019/9/19
assume cs:code,ds:data 例 代码转换 assume cs:code,ds:data start: push ds xor ax,ax push ax mov ax,data mov ds,ax ;------------- mov bx,offset ASCII ;BX指向ASCII码表 mov al,hex ;AL取得一位16进制数,正是ASCII码表中位移 ch5 2019/9/19
例 代码转换 and al,0fh ;只有低4位是有效的,高4位清0 xlat ;换码:AL←DS:[BX+AL] mov dl,al ;入口参数:DL←AL mov ah,2 ;02号DOS功能调用 int 21h ;显示一个ASCII码字符 ret main endp code ends end start ch5 2019/9/19
;查表法,实现一位16进制数转换为ASCII码显示 data segment ASCII db 30h,31h,32h,33h,34h,35h db 36h,37h,38h,39h ;0~9的ASCII码 db 41h,42h,43h,44h,45h,46h;A~F的ASCII码 hex db 0bh ;任意设定一个待转换的一位16进制数 data ends code segment main proc far assume cs:code,ds:data start: push ds xor ax,ax push ax mov ax,data mov ds,ax ;------------- mov bx,offset ASCII ;BX指向ASCII码表 mov al,hex ;AL取得一位16进制数,正是ASCII码表中位移 and al,0fh ;只有低4位是有效的,高4位清0 xlat ;换码:AL←DS:[BX+AL] mov dl,al ;入口参数:DL←AL mov ah,2 ;02号DOS功能调用 int 21h ;显示一个ASCII码字符 ret main endp code ends end start
5.1 循环程序设计 循环结构一般是根据某一条件判断为真或假来确定是否重复执行循环体 循环指令和转移指令可以实现循环控制 ch5 2019/9/19
● 循环程序结构形式 DO-WHILE 结构 DO-UNTIL 结构 初始化 初始化 循环体 控制条件 控制条件 循环体 N N Y Y ch5 2019/9/19
● 循环程序结构说明 初始化: 设置循环的初始状态 循环体: 循环的工作部分及修改部分 控制条件:计数控制(LOOP) 初始化: 设置循环的初始状态 循环体: 循环的工作部分及修改部分 控制条件:计数控制(LOOP) 特征值控制(LOOPZ/LOOPNZ/ 条件跳转指令) ch5 2019/9/19
例:把 BX 中的二进制数以十六进制的形式显示在屏幕上 如:1011 0010 1111 1010 B B2FAH BX 1 2 3 4 ch5 2019/9/19
循环体应该包括:二进制到所显示字符的ASCII之间的转换,以及每个字符的显示。 分析:(1)程序结构的确定 由题意应该把BX的内容从左到右每4位为一组在屏幕上显示出来,显然这可以用循环结构来完成,每次显示一个十六进制数位,因而循环次数是已知的,计数值为4。 (2)循环体的构成(算法确定) 循环体应该包括:二进制到所显示字符的ASCII之间的转换,以及每个字符的显示。 需要了解相关知识:◆字符和其ASCII码之间的关系? “0”~“9” 30H~39H, “A”~”F” 41H~5AH ◆如何显示一个字符? (a)将显示字符的ASCII码放入DL寄存器;(b)将AH的内容置为2(功能号);(c)执行INT 21H(DOS 功能调用)。 ch5 2019/9/19
●除了可以使用LOOP指令之外,还可以使用条件跳转指令来实现。 (3)循环控制条件分析 ●因为循环次数已知,可以使用LOOP指令实现,但是必须注意:由于循环移位指令中使用CL寄存器作为移位次数寄存器,而LOOP 指令的循环次数隐含在CX寄存器中,因此,必须注意这两者之间的冲突。 ●除了可以使用LOOP指令之外,还可以使用条件跳转指令来实现。 LOOP AGAIN DEC 计数器 JNZ AGAIN ch5 2019/9/19
…… 方法1 (LOOP) mov cx, 4 ;初始化 rotate: push cx mov cl, 4 rol bx, cl mov al, bl and al, 0fh add al, 30h ; ’0’~’9’ ASCII 30H~39H cmp al, 3ah jl printit add al, 7h ; ’A’~’F’ ASCII 41H~46H printit: mov dl, al mov ah, 2 int 21h pop cx loop rotate 方法1 (LOOP) ch5 2019/9/19
…… 方法2 (条件跳转指令) mov ch, 4 ;初始化 rotate: mov cl, 4 rol bx, cl mov al, bl and al, 0fh add al, 30h ; ’0’~’9’ ASCII 30H~39H cmp al, 3ah jl printit add al, 7h ; ’A’~’F’ ASCII 41H~46H printit: mov dl, al mov ah, 2 int 21h dec ch jnz rotate 方法2 (条件跳转指令) ch5 2019/9/19
例:将正数 n 插入一个已整序的正数字数组。该数组的 首地址和末地址分别为ARRAY_HEAD, ARRAY_END。 分析:题目要求在已经排好序的正数数组中插入一个正数n,因此,解决问题的关键是找到要出入正数n的位置。需要考虑如下问题? (1)如何找到插入位置及软件实现? (2)如何插入正数n及软件实现? (3)数组边界问题考虑? ch5 2019/9/19
●由于数组已经排好序,因此可以将正数n依次和数组中的数进行比较,比较有个方向问题,这里假设数组在存储单元中按地址递增的方向从小到大依次存放。不妨从大数开始进行比较,当遇到第一个比n小的数,记下该位置,该位置就是要插入n 的位置。 ●找到出入位置后,如何在不破坏原来数据顺序基础上插入n呢?打个比方:9个同学按高矮依次做在1~10号椅子上,现在第10个同学按照高矮要做在第5号椅子上,那么如何空出第5号椅子呢,但仍然保持高矮次序?只要9号同学移到10号, 9 10, 8 9, 7 8, 6 7, 5 6就可以了。同样,我们可以如法炮制,数组中将要插入数n位置前的数依次前移一个位置(两个字节),空出要插入位置,将n放入即可。 ●在插入数时,可能遇到特殊情况,即数n比数组中所有的数都要大,或者小。若比所有的数都大,就不需要移动原数组中的数,直接插入即可;若比所有的数都小,就将数n放在数组的首位置。 ch5 2019/9/19
很显然,查找位置和空出位置的过程就是循环比较的过程,因此采用循环结构来实现,那么,循环条件如何确定呢?其中一种比较容易想到循环条件就是:数组长度(或数组首地址)及K<=n,其中K为依次从数组中取出的一个数。 另外,可以充分利用题目中的已知条件即数组中的数均为正数,所以我们可以在数组的开始的前一个位置存放一个负数,不妨存放数-1,这样,在循环控制时就不需要用数组长度来进行控制,可以进一步简化程序的设计。而且需要注意的是,有可能一次都不需要移动数组中的数。因此,应选择DO—WHILE 结构形式。 ch5 2019/9/19
ch5 2019/9/19
…… mov ax, n mov array_head-2, 0ffffh mov si, 0 compare: x dw ? array_head dw 3,5,15,23,37,49,52,65,78,99 array_end dw 105 n dw 32 …… mov ax, n mov array_head-2, 0ffffh mov si, 0 compare: cmp array_end [si], ax jle insert mov bx, array_end [si] mov array_end [si+2], bx sub si, 2 jmp short compare insert: mov array_end [si+2], ax x -1 3 array_head 5 15 23 37 49 52 65 78 99 105 array_end n 32 ch5 2019/9/19
1 减法 0 加法 例:有数组 x(x1,x2,……,x10) 和 y(y1,y2,……,y10), 编程计算 z(z1,z2,……,z10) z1 = x1 + y1 z2 = x2 + y2 z3 = x3 - y3 z4 = x4 - y4 z5 = x5 - y5 z6 = x6 + y6 z7 = x7 - y7 z8 = x8 - y8 z9 = x9 + y9 z10= x10 + y10 逻辑尺:0 0 1 1 0 1 1 1 0 0 1 减法 0 加法 ch5 2019/9/19
ch5 2019/9/19
x dw x1,x2,x3,x4,x5,x6,x7,x8,x9,x10 y dw y1,y2,y3,y4,y5,y6,y7,y8,y9,y10 z dw z1,z2,z3,z4,z5,z6,z7,z8,z9,z10 logic_rule dw 00dch ;0000,0000,1101,1100 …… mov bx, 0 mov cx, 10 mov dx, logic_rule next: mov ax, x[bx] shr dx, 1 jc subtract add ax, y[bx] jmp short result ; 向前引用 subtract: sub ax, y[bx] result: mov z[bx], ax add bx, 2 loop next ch5 2019/9/19
5.1.3 多重循环 例: 将首地址为A的字数组从小到大排序 32,85,16,15, 8 (冒泡算法,多重循环) 5.1.3 多重循环 多重循环程序设计的基本方法和单重循环程序设计是一致的,应分别考虑各重循环的控制条件及其程序实现,相互之间不能混淆。特别注意的是在每次通过外循环再次进入内循环时,初始条件必须重新设置。 例: 将首地址为A的字数组从小到大排序 32,85,16,15, 8 (冒泡算法,多重循环) ch5 2019/9/19
冒泡法 “冒泡法”是一种排序算法,不是最优的算法,但它易于理解和实现 冒泡法从第一个元素开始,依次对相邻的两个元素进行比较,使前一个元素不大于后一个元素;将所有元素比较完之后,最大的元素排到了最后;然后,除掉最后一个元素之外的元素依上述方法再进行比较,得到次大的元素排在后面;如此重复,直至完成就实现元素从小到大的排序。n个数需要n-1遍比较,第一遍比较出一个最大(或最小)数,第二遍对剩下的数进行比较,得到一个次最大(或次最小)数 ,第n-1遍比较出最后两个数的大小顺序,至此整个数组全部排好序。每一遍比较需要比较的次数为要比较数减一。如n=5,第一遍比较次数为4(内循环),第二遍比较次数为3 (内循环) ,第三遍比较次数为2 (内循环) ,第四遍比较次数为1 (内循环) 。 ch5 2019/9/19
冒泡法的排序过程 序号 数 比 较 遍 数 1 2 3 4 1 32 2 85 3 16 4 15 5 8 32 16 15 8 85 ch5 2019/9/19
mov cx, 5 ;元素个数 dec cx ;比较遍数 loop1: mov di, cx ;比较次数 mov bx, 0 loop2: mov ax, A[bx] ;相邻两数 cmp ax, A[bx+2] ; 比较 jle continue xchg ax, A[bx+2] ;交换位置 mov A[bx], ax continue: add bx, 2 loop loop2 mov cx, di loop loop1 ch5 2019/9/19
5.2 分支程序设计 分支程序根据条件是真或假决定执行与否 判断的条件是各种指令,如CMP、TEST等执行后形成的状态标志 ch5 5.2 分支程序设计 分支程序根据条件是真或假决定执行与否 判断的条件是各种指令,如CMP、TEST等执行后形成的状态标志 ch5 2019/9/19
5.2.1 分支程序结构形式 CASE 结构 IF-THEN-ELSE 结构 ? ? case 1 case 2 case n … ch5 2019/9/19
5.2.2 分支程序设计方法 (1) 逻辑尺控制 (2) 条件控制 (3) 地址跳跃表(值与地址有对应关系的表) 程序的分支用条件转移指令Jxx和JMP可以实现分支控制; 常用的控制方法有 (1) 逻辑尺控制 (2) 条件控制 (3) 地址跳跃表(值与地址有对应关系的表) ch5 2019/9/19
(1) 逻辑尺控制 例:有数组 x(x1,x2,……,x10) 和 y(y1,y2,……,y10), (1) 逻辑尺控制 例:有数组 x(x1,x2,……,x10) 和 y(y1,y2,……,y10), 编程计算 z(z1,z2,……,z10) z1 = x1 + y1 z2 = x2 + y2 z3 = x3 - y3 z4 = x4 - y4 z5 = x5 - y5 z6 = x6 + y6 z7 = x7 - y7 z8 = x8 - y8 z9 = x9 + y9 z10= x10 + y10 逻辑尺:0 0 1 1 0 1 1 1 0 0 1 减法 0 加法 ch5 2019/9/19
x dw x1,x2,x3,x4,x5,x6,x7,x8,x9,x10 y dw y1,y2,y3,y4,y5,y6,y7,y8,y9,y10 z dw z1,z2,z3,z4,z5,z6,z7,z8,z9,z10 logic_rule dw 00dch ;0000,0000,1101,1100 …… mov bx, 0 mov cx, 10 mov dx, logic_rule next: mov ax, x[bx] shr dx, 1 jc subtract add ax, y[bx] jmp short result ; 向前引用 subtract: sub ax, y[bx] result: mov z[bx], ax add bx, 2 loop next ch5 2019/9/19
(2) 条件控制 例:折半查找算法.在数据段中,有一个按从小到大顺序排列的无符号数字数组ARRAY,数组中的第一个单元存放着数组的长度。在AX中有一个无符号数,要求在数组中查找number,如果找到,则使CF=0,并在SI中给出该元素在数组中的偏移地址;如未找到,则使CF=1。 data segment array dw 12,11,22,33,44,55,66 dw 77,88,99,111,222,333 number dw 55 low_idx dw ? high_idx dw ? data ends ch5 2019/9/19
折半算法 (ax)=55 low_idx 1 4 5 high_idx 12 12 11 22 33 44 55 1 2 3 4 5 6 7 8 9 10 11 12 12 11 22 33 44 55 66 77 88 99 111 222 333 (si)=0ah Cf=0 (ax)=90 low_idx 1 7 8 9 high_idx 12 (si)=10h Cf=1 ch5 2019/9/19
在一个长度为n 的有序数组r中,查找元素k的折半查找算法可描述如下: (1)初始化被查找数组的首尾下标,low1,highn; (2)若low>high,则查找失败,置CF=1,退出程序。否则,计算中点mid[(low+high)/2]; (3)k与中点元素r[mid]比较。若k=r[mid],则查找成功,程序结束;若k<r[mid],则转(4);若k>r[mid],则转(5); (4)低半部分查找,highmid-1,返回(2),继续查找; (5)高半部分查找,lowmid+1,返回(2),继续查找。 ch5 2019/9/19
算法 …… lea di, array mov ax, number ;要查找数 cmp ax, [di+2] ; (ax)与第一个元素比较 cmp ax, [di+2] ; (ax)与第一个元素比较 ja chk_last lea si, [di+2] je exit ; (ax)=第一个元素,找到退出 stc jmp exit ; (ax)<第一个元素,未找到退出 chk_last: mov si, [di] ;元素个数 shl si, 1 ; 计算最后一个元素 add si, di ; 的地址 cmp ax, [si] ; (ax)与最后一个元素比较 jb search je exit ; (ax)=最后一个元素,找到退出 jmp exit ; (ax)>最后一个元素,未找到退出 算法 ch5 2019/9/19
search: compare: cmp ax, [bx+si] mov low_idx, 1 je exit mov bx, [di] ;个数 mov high_idx, bx mov bx, di mid: mov cx, low_idx mov dx, high_idx cmp cx, dx ja no_match add cx, dx shr cx, 1 mov si, cx shl si, 1 compare: cmp ax, [bx+si] je exit ja higher dec cx mov high_idx, cx jmp mid higher: inc cx mov low_idx, cx no_match: stc exit: …… ch5 2019/9/19
(3) 地址跳跃表(值与地址有对应关系的表) (3) 地址跳跃表(值与地址有对应关系的表) 例:根据 AL 寄存器中哪一位为 1(从低位到高位), 把程序转移到 8 个不同的程序分支 branch_table dw routine1 dw routine2 dw routine3 dw routine4 dw routine5 dw routine6 dw routine7 dw routine8 ch5 2019/9/19
(寄存器间接寻址) …… cmp al, 0 ;AL为逻辑尺 je continue lea bx, branch_table L: shr al, 1 ;逻辑右移 jnc add1 jmp word ptr [bx] ;段内间接转移 add1: add bx, type branch_table ;add bx,2 jmp L continue: routine1: routine2: ch5 2019/9/19
(寄存器相对寻址) …… cmp al, 0 je continue mov si, 0 L: shr al, 1 ;逻辑右移 jnc add1 jmp branch_table[si] ;段内间接转移 add1: add si, type branch_table jmp L continue: routine1: routine2: ch5 2019/9/19
…… (基址变址寻址) cmp al, 0 je continue lea bx, branch_table mov si, 7 * type branch_table mov cx, 8 L: shl al, 1 ;逻辑左移 jnc sub1 jmp word ptr [bx][si] ;段内间接转移 sub1: sub si, type branch_table ;(si)-2 loop L continue: routine1: routine2: (基址变址寻址) ch5 2019/9/19
试编一程序,求三个带符号字数据中的最大值,并将最大值存入MAX字单元中。 大家做一个题目 试编一程序,求三个带符号字数据中的最大值,并将最大值存入MAX字单元中。 设 三个带符号数分别在三个字变量X、Y、Z中存储。 ch5 2019/9/19
ASSUME DS:DATA,CS:CODE START: MOV AX,DATA MOV DS,AX MOV AX,X 程序如下: DATA SEGMENT X DW 00ABH Y DW –5 Z DW 200 MAX DW ? DATA ENDS CODE SEGMENT ASSUME DS:DATA,CS:CODE START: MOV AX,DATA MOV DS,AX MOV AX,X CMP AX,Y ;X>Y? JG L1 MOV AX,Y ;Y>Z? CMP AX , Z JG EXIT L2: MOV AX, Z JMP EXIT L1: CMP AX,Z ;X>Z? JLE L2 EXIT:MOV MAX,AX MOV AH,4CH INT 21H CODE ENDS END START ch5 2019/9/19
第五章 作业 5.1, 5.3, 5.2 , 5.19 ch5 2019/9/19