第5章程序控制结构及其程序设计.ppt_第1页
第5章程序控制结构及其程序设计.ppt_第2页
第5章程序控制结构及其程序设计.ppt_第3页
第5章程序控制结构及其程序设计.ppt_第4页
第5章程序控制结构及其程序设计.ppt_第5页
已阅读5页,还剩168页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

第5章 程序控制结构及其程序设计,5.1 汇编语言程序设计概述 5.2 顺序程序设计 5.3 分支程序设计 5.4 循环程序设计 5.5 在实模式下发挥80386及其后继机型的优势 习题5,5.1 汇编语言程序设计概述,5.1.1 汇编语言程序设计的基本步骤 编制汇编语言程序的基本步骤如下: (1) 分析问题,抽象出描述问题的数学模型。遇到一个题目,特别是一个较为复杂的题目,先要对其进行全面的分析,看它给出了什么条件,有什么特点,找出规律,归纳出数学模型。当然,也可能有些问题不用写出数学模型或写不出数学模型。,(2) 确定算法。有了数学模型,或虽然没有数学模型但已把题目分析清楚了,就选择一个合适的算法和适当的数据结构。如果没有可供选用的现成的算法和结构,就需要针对具体问题设计一个算法或结构。 (3) 绘制流程图。流程图就是用图形的方式把解决问题的算法直观地描述出来。对于一个比较复杂的问题,画出流程图,这有助于对问题的理解以及有助于编写出正确的程序。当然,如果算法比较简单,也可不画流程图。,(4) 分配存储空间和工作单元。用汇编语言编写程序时,需要给程序中的变量指定内存单元地址或指定寄存器。 (5) 编写程序。要把题目中需要处理的数据合理地根据(2)、(3)、(4)步的工作,选用适合的指令,并按一定的语法规则编写相应的程序。 (6) 静态检查。静态检查就是用人工的方式检查程序是否有错误,包括算法错误和语法错误等,如果有错误,及时改正过来。处理得好,静态检查能够发现和改正程序中的大部分错误。,(7) 上机调试运行。任何程序必须经过调试,才能检查出解题目的是否正确以及程序是否符合设计思想。在调试程序的过程中,应该善于利用机器提供的调试工具(如debug)和有效的其他工具软件来进行工作,经过反复的“运行发现错误改正错误运行”,才能得到正确的程序。这一点对初学者特别重要,它将给汇编语言编程提供很大的帮助。 程序的编写和调试运行是学好汇编语言的重要手段。只有多编写程序和多调试运行程序,才能有效地提高编写和阅读程序的能力。,5.1.2 程序流程图 表示一个算法,可以用不同的方法。常用的有自然语言、传统流程图、结构化流程图、伪代码和pad图等。 1. 用自然语言表示算法 很多算法是用自然语言表示的,自然语言是指人们日常使用的语言。用自然语言表示算法通俗易懂,但文字冗长,容易出现“歧义性”。自然语言表示的含义往往不大严格,要根据上下文才能判断其正确的含义。假如有这样一句话:“王先生对刘先生说孩子考上了大学。”是王先生的孩子考上大学呢,还是刘先生的孩子考上大学呢? 光从这句话本身难以判断。此外,用自然语言描述包含分支和循环的算法很不方便。因此,除了很简单的问题以外,一般程序设计不用自然语言描述算法。,2. 流程图的组成 流程图是用一些图框表示各种操作。用图形表示算法,直观形象,易于理解。美国国家标准化协会ansi(american national standard institute)规定了一些常用的流程图图元(见图5.1),已被世界各国程序工作者普遍采用。 借助于流程图可以清晰地把程序思路表达出来,有助于编写正确的程序。流程图对于程序设计人员,特别是初学者是一种非常有用的工具。 流程图一般由6种成分组成,如图5.1所示。,图5.1 流程图的组成成分图,1) 执行框(矩形框) 图5.1中的方框,其作用是表示一段程序或一个模块的功能,对于结构化程序,一个执行框只有一个入口和一个出口。 2) 判别框(菱形框) 图5.1中的菱形框,其作用是对一个给定的条件进行判断,根据给定的条件是否成立来决定如何执行其后的操作。它有一个入口,两个出口,如图5.2所示。,图5.2 流程图的绘制示意,3) 开始框和终止框 图5.1中的圆头方框表示程序的起始和终止。 4) 指向线 指向线表示程序执行的顺序。 5) 连接点 图5.1中小圆圈是连接点,用于将画在不同地方的流程线连接起来。如图5.2中有两个以为标志的连接点,它表示这两个点是互相连接在一起的。实际上它们是同一个点,只是当在纸张上画不下时才分开来画。用连接点,可以避免流程线的交叉或过长,使流程图清晰。,流程图是表示算法的较好工具。一个流程图包括以下几部分: (1) 表示相应操作的框; (2) 带箭头的流程线; (3) 框内外必要的文字说明。 绘制流程线不要忘记画箭头,因为它是反映流程执行的先后次序的,如不画出箭头就难以判定各框的执行次序了。,用流程图表示算法直观、形象,能比较清楚地显示出各个框之间的逻辑关系。前一时期国内外计算机书刊都广泛使用这种流程图表示算法,但是,这种流程图占用篇幅较多,尤其当算法比较复杂时,画流程图既费时又不方便,在结构化程序设计方法推广之后,许多书刊已用n-s结构化流程图代替这种传统的流程图。但是每一个程序编制人员都应当熟练掌握传统流程图,做到会看会画。,3. 三种基本结构和改进的流程图 1) 传统流程图的弊端 传统的流程图用流程线指出各框的执行顺序,对流程线的使用没有严格限制。因此,使用者可以不受限制地使流程随意地转来转去,使流程图变得毫无规律。阅读者要花很大精力去追踪流程,使人难以理解算法的逻辑。这种情况如图5.3所示。这种如同乱麻一样的算法称为bs型算法,意为就像一碗面条(a bowl of spaghetti),乱无头绪。,图5.3 杂乱流程示意,这种算法不好,难以阅读,也难以修改,可靠性和可维护性难以保证。如果我们写出的算法能限制流程的无规律任意转向,如同一本书那样,由各章各节顺序组成,那样,阅读起来就很方便,从头到尾顺序地看下去即可。而如果一本书不是由各章节顺序组成,各章节内各节毫无规律地乱排,阅读这种书就很困难。,为了提高算法的质量,使算法的设计和阅读更方便,必须限制滥用箭头,即不允许无规律地使流程随意转向,只能顺序地进行下去。但是,算法上难免会包含一些分支和循环,而不可能全部由一个一个框顺序组成。为了解决这个问题,人们设想,规定出几种基本结构,然后由这些基本结构按一定规律组成一个算法结构(如同用一些基本预制构件来搭成房屋一样),整个算法的结构是由上而下地将各个基本结构顺序排列起来的。如果能做到这一点,算法的质量就能得到保证和提高。,2) 三种基本结构 1966年,bohra和jacopini提出了以下三种基本结构,用这三种基本结构作为表示一个良好算法的基本单元。 (1) 顺序结构。如图5.4所示,虚线框内是一个顺序结构。其中a和b两个框是顺序执行的。即在执行完a框所指定的操作后,必然接着执行b框所指定的操作。顺序结构是最简单的一种基本结构。,图5.4 顺序结构图,(2) 选择结构(也称选取结构或分支结构)。如图5.5所示,虚线框内是一个选择结构。此结构中必包含一个判断框。根据给定的条件p是否成立而选择执行a框或b框。请注意,无论p条件是否成立,只能执行a框或b框之一,不可能既执行a框又执行b框。无论走哪一条路径,在执行完a或b之后,都经过b点,然后脱离本选择结构。a或b两个框中可以有一个是空的,即不执行任何操作。 (3) 循环结构(又称重复结构)。循环结构指反复执行某一部分的操作,有当型(while型)循环、直到型(until型)循环和计数型(for-next)循环。,图5.5 选择结构图,4. 结构化程序设计的特点 三种基本循环结构的共同特点如下: (1) 只有一个入口。 (2) 只有一个出口。尽管一个菱形判断框有两个出口,但由它构成的一个选择结构仍只有一个出口。不要将菱形框的出口和选择结构的出口混淆。 (3) 各功能框均可执行。结构内的每一部分都有机会被执行到,也就是说,对每一个框而言,都应当有一条从入口到出口的路径通过它。,(4) 结构中无死循环。 实践证明,由以上三种基本结构顺序组成的算法结构,可以解决任何复杂的问题。由基本结构所构成的算法属于“结构化”的算法,它不存在无规律的转向,只在本结构内才允许存在分支、向前或向后的跳转。 基本结构不一定只限于上面三种,只要具有上述四个特点的都可以作为基本结构。人们可以自己定义基本结构,并由这些基本结构组成结构化程序。,5.2 顺序程序设计,在顺序程序结构中,完全按顺序逐条执行指令序列,这种情况在程序中大量存在,但仅由顺序结构构成的作为完整的程序则很少见。顺序结构的程序是最简单的程序。,【例5-1】 将两个字节数据相加,并存放到一个结果单元中。 data segment ad1 db 4ch ;定义第1个加数 ad2 db 25h ;定义第2个加数 sum db ? ;定义结果单元 data ends code segment,assume cs: code,ds: data start: mov ax,data mov ds,ax mov al,ad1 ;取出第1个加数 add al,ad2 ;和第2个加数相加 mov sum,al ;存放结果 mov ah,4ch int 21h ;返回dos code ends end start,【例5-2】 两个32位数的乘法程序。 .386 data segment num1 dd 12345678h ;定义第1个乘数 num2 dd 5a4bef06h ;定义第2个乘数 result dd 2 dup(?) ;定义结果单元 data ends code segment,assume cs:code,ds:data start: mov ax,data mov ds,ax mov eax,num1 ;取出第1个乘数 mul num2 ;和第2个乘数相乘,mov result,eax ;存放结果的低4字节部分 mov result+4,edx ;存放结果的高4字节部分 mov ah,4ch ; int 21h ;返回dos code ends end start,【例5-3】 将一个字节压缩bcd码转换为两个ascii码。 分析:一个字节的压缩bcd码就是用一个字节的二进制数表示两位十进制数,如十进制数96表示成压缩bcd码就是96h,转换成ascii码就是把压缩bcd码表示的十进制数的高位和低位分开,并以ascii码表示,即转换成39h和36h。,data segment bcdbuf db 96h ;定义1个字节的压缩bcd码 ascbuf db 2 dup(?) ;定义2个字节的结果单元 data ends code segment assume cs: code,ds: data,start: mov ax,data mov ds,ax mov al,bcdbuf ;取出bcd码 mov bl,al ;送bl暂存 mov cl,4 shr al,cl ;高4位变成低4位,高4位补0(96h09h) add al,30h ;变成ascii码(39h),mov ascbuf,al;存储第1个ascii码 and bl,0fh ;屏蔽掉高4位,只保留低4位(96h06h) add bl,30h ;变成bcd码(36h) mov ascbuf+1,bl ;存储第2个码 mov ah,4ch int 21h code ends end start,【例5-4】 利用直接查表法完成将键盘输入的一位10进制数(09)转换成对应的平方值并存放在sqrbuf单元中。(用p单步调试) 分析:09的平方值分别为0、1、4、9、16、25、36、49、64、81。把平方值放在一起形成一个平方值表,根据输入的值和对应平方值所在单元地址之间的关系(表首地址加上输入的值),查出相应的平方值。,data segment squtab db0,1,4,9,16,25,36,49,64,81 squbuf db ? data ends code segment assume cs: code,ds: data start: mov ax,data,mov ds,ax mov bx,offset squtab ;平方表首地址 mov ah,1 int 21h ;由键盘输入个数,得到其ascii码 sub al,30h ;由ascii码得到相应的数 xlat ;查表 mov squbuf,al ;存储结果,mov ah,4ch int 21h code ends end start,5.3 分支程序设计,5.3.1 分支程序的结构形式 分支程序结构可以有两种形式,如图5.6所示。 不论哪一种形式,它们的共同特点是:运行方向是向前的,在某一种特定条件下,只能执行多个分支中的一个分支。,图5.6 分支程序的结构形式,skip,5.3.2 分支程序设计方法 程序的分支一般用条件转移指令来产生,利用条件转移指令不影响条件码的特性,可以连续使用条件转移指令使程序产生多个不同的分支如下例。 【例5-5】 在附加段中,有一个按从小到大顺序排列的无符号数数组,其首地址存放在di寄存器中,数组中的第一个单元存放着数组长度,在ax中有一个无符号数,要求在数组中查找(ax)。如找到,则使cf=0,并在si中给出该元素在数组中的偏移地址;如未找到,则使cf=1。,上述章节遇到的过多个表格查找的例子,大都使用顺序查找的方法。本例是一个已经排序的数组,可以采用折半查找法以提高查找效率。 折半查找法先取有序数组的中间元素与查找值相比较。如相等则查找成功;如查找值大于中间元素,则再取高半部的中间元素与查找值相比较。如查找值小于中间元素,则再取低半部的中间元素与查找值相比较。如此重复直到查找成功或最终未找到该数为止。,折半查找法的效率高于顺序查找法,对于长度为n的表格,顺序查找法平均要作n2次比较,而折半查找法的平均比较次数为lb n。所以,如果数组长度为100,则顺序查找法平均要作50次比较,而折半查找法平均作7次比较就可以了。 在一个长度为n的有序数组r中,查找元素k的折半查找算法可描述如下:,(1) 初始化被查找数组的首尾下标,lowl,highn。 (2) 若lowhigh,则查找失败,置cf1,退出程序。否则,计算中点:mid(low+high)2。 (3) k与中点元素rmid比较。若k=rmid则查找成功,程序结束;若krmid,则转步骤(5)。 (4) 低半部分查找(lower),highmid-1,返回步骤(2),继续查找。,(5) 高半部分查找(higher),lowmid+1,返回步骤(2),继续查找。 图5.7表示了折半查找算法的程序框图。给出的程序首先把查找值与数组的第一个元素和最后一个元素相比较,如果找到该数小于第一个元素或大于最后一个元素则结束查找,否则从search开始折半查找。search以前的工作在图5.7中未表示出来。折半查找算法的程序实现如程序清单所示。,图5.7 折半查找算法的程序框图,例5-5折半查找算法程序。 dseg segment low_i dw ? high_i dw ? dseg ends cseg segment assume cs: cseg,ds: dseg,es: dseg,b_srch proc near push ds push ax mov ax,dseg mov ds,ax mov es,ax pop ax cmp ax,es:di+2 ja chk_last,lea si,es:di+2 je exit stc jmp exit chk_last:mov si,es:di shl si,1 add si,di cmp ax,es: si jb search je exit,stc jmp exit search: mov low_i,1 mov bx,es: di mov high_i,bx mov bx,di mid: mov cx,low_i mov dx,high_i cmp cx,dx ja no_match,add cx,dx shr cx,1 mov si,cx shl si,1 compare: cmp ax,es: bx+si je exit ja higher dec cx mov high_i,cx jmp mid,higher: inc cx mov low_i,cx jmp mid no_match:stc exit: pop ds ret b_srch endp cseg ends end b srch,若数组元素如下: list dw 12,11,22,33,44,55,66,77,88,99,111,222,333 如果要查找的数为(ax)=55。数组长度为12,第一次比较的是数组的第六个元素。因5533,所以第三次用高半部折半查找,比较的是第四个元素44。因5544,所以第四次用高半部折半查找,比较的是第五个元素55。这样经过四次比较后,因查找成功而退出程序。,如果要查找的数为(ax)=57,则第一次比较的仍是第六个元素66。因5733,所以第三次用高半部折半查找,比较的是第四个元素44。因5744,所以第四次用高半部折半查找,比较的是第五个元素55,因5755,再用高半部折半查找时,因lowhigh而以查找失败退出程序。,可以看出,在这个例子中,同样用cmp指令以及条件转移指令产生两个或多个程序分支。当然,由于多数运算型指令置条件码,所以在条件转移指令之前并不一定要使用cmp或test指令,只要保证使用条件转移指令时的条件码符合要求就可以了。以上多个例子都是既有分支结构又包括循环结构,实际上,多数程序都是各种程序结构的组合。而且,循环结构可以看作分支结构的一种特例,它只是多次走一个分支,只在满足循环结束条件时,走另一个分支罢了。,5.3.3 跳跃表法 分支程序的两种结构形式都可以用上面所述的方法来实现。此外,在实现case结构时,还可以使用跳跃表法,使程序能根据不同的条件转移到多个程序分支中去。下面举例说明。 【例5-6】 试根据al寄存器中哪一位为1(从低位到高位)就把程序转移到8个不同的程序分支中去。,下面列出了用变址寻址方式实现跳跃表法的程序,还可以使用寄存器间接和基址变址寻址方式来达到同一目的,这三种方法并无实质的区别,只是其中关键的jmp指令所用的寻址方式不同而已。 跳跃表法是一种很有用的分支程序设计方法,应当通过例子掌握要领,灵活使用。 用变址寻址方式实现跳跃表法的程序:,data segment datatab dw routine_1 dw routine_2 dw routine_3 dw routine_4 dw routine_5 dw routine_6 dw routine_7 dw routine_8,data ends code segment main proc far assume cs: code,ds: data start: push ds sub bx,bx push bx mov bx,data mov ds,bx cmp al,0,je cont mov si,0 lp: shr al,1 jnb not_yet jmp datatabsi not_yet:add si,type datatab jmp lp cont: routine_1: routine_2:,ret main endp code ends end start,【例5-7】 用间接寻址方式实现跳跃表法的程序: cmp al,0 je continue_main_line lea bx,datatab lp: shr al,1 jnb not_yet jmp word ptrbx not_yet: add bx,type datatab jmp lp continue_main_line:,5.4 循环程序设计,5.4.1 循环程序结构 循环程序结构可以总结为两种结构形式,如图5.8所示。一种是do_while结构形式,另一种是do_until结构形式。,图5.8 循环程序的结构形式,1. do_while结构 do_while结构把对循环控制条件的判断放在循环的入口,先判断条件,满足条件就执行循环体,否则就退出循环。 2. do_until结构 do_until结构则先执行循环体,然后再判断控制条件,不满足条件则继续执行循环操作,一旦满足条件则退出循环。,这两种结构可以根据具体情况选择使用。如果允许循环次数等于0的情况,应选择do_while结构,否则使用do_until结构。不论哪一种结构形式,循环程序都可由如下三部分组成: (1) 循环的初始状态。设置循环次数的计数值,为使循环体正常工作而建立的初始状态等。 (2) 循环体。这是循环工作的主体,由循环的工作部分和修改部分组成。循环的工作部分是为完成程序功能而设计的主要程序段;循环的修改部分则是为保证每一次重复(循环)时,参加执行的信息能发生有规律的变化而建立的程序段。,(3) 循环控制部分。循环控制本来应该属于循环体的一部分,由于它是循环程序设计的关键,所以要对它作专门的讨论。每个循环程序必须选择一个循环控制条件来控制循环的运行和结束,而合理地选择该控制条件就成为循环程序设计的关键问题。,有时,循环次数是已知的,此时可以用循环次数作为循环的控制条件,loop指令使这种循环程序设计能很容易地实现。有时循环次数是已知的,但有可能使用其他特征或条件来使循环提前结束,loopz和loopnz指令又是设计这种循环程序的工具。 然而,有时循环次数是未知的,那就需要根据具体情况找出控制循环结束的条件。循环控制条件的选择是很灵活的,有时可供选择的方案不止一种,此时就应分析比较,选择一种效率最高的方案来实现。,5.4.2 循环程序设计方法 【例5-8】 试编制一个程序,把bx寄存器内的二进制数用十六进制数的形式在屏幕上显示出来。 根据题意应该把bx的内容从左到右每四位为一组在屏幕上显示出来,每次循环显示一个十六进制数位,计数初值为4。,图5.9 二进制数到十六进制数转换的程序框图,程序框图如图5.9所示。采用循环移位的方式把所要显示的4位二进制数移到最右面,以便做数字到字符的转换工作。 此外,由于数字09的ascii值为30h39h,而字母af的ascii值为41h46h,所以在把4位二进制数加上30h后还需再作一次判断。如果是字符af,则还应加上7才能显示出正确的十六进制数。以binihex.asm为文件名建立“二进制到十六进制数转换程序”源文件。,在程序中没有使用loop指令,这是因为循环移位指令要使用cl寄存器,而loop指令要使用cx寄存器,为了解决cx寄存器的冲突问题,这里用ch寄存器存放循环计数值,而用dec及jnz两条指令完成loop指令的功能。这说明使用计数值控制循环结束也可以不用loop指令。当然也可以把计数值初始化为0,用每循环一次加1然后比较次数是否达到要求的方法来实现;或者仍用loop指令,而用堆栈保存其中的一个信息(例如计数值)来解决cx寄存器的冲突问题等。总之,程序设计是很灵活的,只要算法和指令的使用没有错误,都可以达到目的。二进制数到十六进制数转换程序:,code segment main proc far assume cs:code start: push ds sub ax,ax push ax mov ch,4 lp: mov cl,4 rol bx,cl mov al,bl,and al,0fh add al,30h cmp al,3ah jl printa add al,07h printa: mov dl,al mov ah,2 int 21h dec ch,jnz lp ret main endp code ends end start,【例5-9】 在addr单元中存放着数y的地址,试编制一程序把y中1的个数存入count单元中。 要测出y中1的个数就应逐位测试。一个比较简单的办法是可以根据最高有效位是否为1来计数,然后用移位的方法把各位数逐次移到最高位去。,循环的结束可以用计数值为16来控制,但更好的办法是:结合上述方法可以用测试数是否为0来作为结束条件,这样可以在很多情况下缩短程序的执行时间。此外,考虑到y本身为0的可能性,应该采用do_while的结构形式。根据以上考虑,可以画出图5.10的程序框图并编制“数1的程序”。,图5.10 数1的程序框图,title 数1的程序 data segment addr dw number number dw y count dw ? data ends code segment main proc far,assume cs:code,ds:data start: push ds sub ax,ax push ax mov ax,data mov ds,ax mov cx,0 mov bx,addr mov ax,bx,repeat: cmp ax,0 jz exit jns shift inc cx shift: shl ax,1 jmp repeat exit: mov count,cx,ret main endp code ends end start ;运行时,y应赋具体值,【例5-10】 在附加段中,有一个首地址为list和未经排序的字数组。在数组的第一个字中,存放着该数组的长度,数组的首地址已存放在di寄存器中,ax寄存器中存放着一个数。要求编制一程序:在数组中查找该数,如果找到此数,则把它从数组中删除。 这一程序应该首先查找数组中是否有(ax),如果没有则不对数组作任何处理就结束程序。如果找到这一元素,则应把数组中位于其前(指地址比该元素高)的元素后移一个字(即向低地址方向移动),并修改数组长度值。如果找到的元素正好位于数组末尾,则不必移动任何元素,只要修改数组长度值就可以了。,这里,第一部分的查找元素可以使用串处理指令,第二部分的删除元素则可以使用循环结构。由于查找结束时就可以知道元素的位置,因此可以作为循环次数已知的情况来设计。画出程序框图如图5.11所示。程序主体部分见“删除数组中一元素程序“。,图5.11 删除数组中一元素的程序框图,title 删除数组中一元素程序 del_u1 proc near cld push di mov cx,es:di add di,2 repne scasw je delete pop di jmp short exit,delete: jcxz dec_cnt next_el: mov bx,es:di mov es:di-2,bx add di,2 loop next_el dec_cnt: pop di dec word ptr es:di exit: ret del_ul endp,【例5-11】 将正数n插入一个已排序的字数组的正确位置。该数组的首地址和末地址分别为array_hd和array_ed,其中所有数均为正数且已按递增的次序排列。 由于数组的首地址和末地址都是已知的,因此数组的长度是可以确定的。但是,这里只要求插入一个数,并不一定要扫描整个数组,所以可以用找到应插入数的位置作为循环的结束条件。此外,为空出要插入数的位置,其前的全部元素都应前移一个字(即向地址增大的方向移动一个字,,这里的前后是指程序运行的方向为前,反之则为后),所以算法上应该从数组的尾部向头部查找,可逐字取出数组中的一个数k与n作比较。如kn,则把k前移一个字,然后继续往后查找;如kn,则把n插在k之前就可以结束程序了。,在考虑算法时,必须把可能出现的边界情况考虑在内。如例5-10中对有可能出现要删除的元素正好在数组末尾的考虑。这个问题是初学者易于忽略的,应该引起注意。在例5-11中,应该考虑n大于或小于数组中所有数的两种可能性。如果n大于数组中所有数,则第一次比较就可以结束循环,也就是说循环次数有可能等于0,所以应该选用do_while结构形式;如果n小于数组中所有数,则必须使循环及时结束,也就是说不允许查找的范围超过数组的首地址,,这当然可以把数组的首地址也同时作为结束条件来考虑,或者同时用循环次数作为结束条件之一来考虑。本例的更好办法是:可以利用所有数均为正数的条件,在array_head 2单元中存放-1这个数,这样可以保证如果数n小于数组中的所有数,那它必然大于-1,这样就可以正确地把n放在数组之首了,循环的结束仍然可以使用kn这一条件。根据上述有关算法的考虑可画出程序框图如图5.12所示。编制了“数组中插入一元素程序”。,图5.12 数组中插入一元素的程序框图,title 数组中插入一元素程序 data segment x dw ? arr_hd dw 3,5,15,23,37,49 dw 52,65,78,99 arr_ed dw 105 n dw 32 data ends code segment main proc far assume cs: code,ds:data,start: push ds sub ax,ax push ax mov ax,data mov ds,ax mov ax,n mov arr_hd_2,0ffffh mov si,0,comp: cmp arr_edsi,ax jle insert mov bx,arr_edsi mov arr_edsi+2,bx sub si,2 jmp short comp insert: mov arr_edsi+2,ax ret main endp code ends end start,【例5-12】 设有数组x和y,x数组中有x1,x10;y数组中有y1,y10。试编制程序计算 z1x1+y1 z5x5-y5 z8x8-y8 z2x2+y2 z6x6+y6 z9x9+y9 z3x3-y3 z7x7-y7 z10x10+y10 z4x4-y4 结果存入z数组。,对于这种问题,也可用循环程序结构来完成。已知循环计数值为10,每次循环的操作数是可以顺序取出的,但所作的操作却有所不同,这里有加法和减法两种操作。为了区别每次应该做哪一种操作,可以设立标志位,如标志位为0做加法,为1则做减法。这样,进入循环后只要判别标志位就可确定应该做的操作了。显然,这里要做10次操作就应该设立10个标志位,我们把它放在一个存储单元logic_rule中,这种存储单元一般称为逻辑尺。本例设定的逻辑尺为 0000000011011100,从低位开始所设的标志位反映了每次要做的操作顺序,最高的6位没有意义。程序框图如图5.13所示。,图5.13 例5-12的程序框图,title 例5-12程序 data segment 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 log_ru dw 00dch data ends code segment main proc far,assume cs: code,ds: data start: push ds sub ax,ax push ax mov ax,data mov ds,ax mov bx,0 mov cx,10 mov dx,log_ru,next:mov ax,xbx shr dx,1 jc subt add ax,ybx jmp short result subt: sub ax,ybx result: mov zbx,ax add bx,2 loop next ret main endp code ends end start,本例中x1x10,y1y10,z1z10均需采用实际数值才可运行 这种设置逻辑尺的方法是很常用的。例如,在矩阵运算中,为了跳过操作数为0的计算,经常采用这种方法。又如,把一组数据存入存储器时,如果其中数值为0的元素很多,也可用这种方法设立一个每位表示一个下标的逻辑尺(这样的逻辑尺可能占有几个字,由数组的长度确定),0元素就可不占用存储单元了。,在例5-12中,每个标志只占一位,如果要表示的特征数更多,则每个标志可占用几位,而在处理方法上是完全相同的。设立标志位的方法除了如逻辑尺那样可静态地预置外,还可以在程序中动态地修改标志位的值,以达到控制的目的。下例将说明这种方法。,【例5-13】 试编制一程序。从键盘输入一行字符,要求第一个键入的字符必须是空格符。如不是,则退出程序;如是,则开始接收键入的字符并顺序地存放在首地址为buffer的缓冲区中(空格符不存入),直到接收到第二个空格符时退出程序。这一程序要求接收的字符从空格符开始又以空格符结束,因此程序中必须区分所接收的字符是否为第一个字符。为此,设立作为标志的存储单元flag。一开始将其置为0,接收第一个字符后可将其置1。整个程序的框图如图5.14所示。,图5.14 例5-13的程序框图,title 例5-13程序 data1 segment buffer db 80 dup(?) flag db ? data1 ends codes segment main proc far assume cs:codes,ds:data1,start: push ds sub ax,ax push ax mov ax,data1 mov ds,ax lea bx,buffer mov flag,0,next: mov ah,01 int 21h test flag,01h jnz follow cmp al,20h jnz exit mov flag,1 jmp next,follow: cmp al,20h jz exit mov bx,al inc bx jmp next exit: ret main endp codes ends end start,5.4.3 多重循环程序设计 循环可以有多重结构。多重循环程序设计的基本方法和单重循环程序设计的基本方法是一致的,应分别考虑各重循环的控制条件及其程序实现,相互之间不能混淆。此外,应该注意在每次通过外层循环再次进入内层循环时,初始条件必须重新设置。下面举例加以说明。,【例5-14】 有一个首地址为a的n字数组,编制程序使该数组中的数按照从大到小的次序排列。这里采用起泡排序算法,从第一个数开始依次对相邻两个数进行比较。如次序对,则不做任何操作;如次序不对,则使两个数交换位置。表5-1表示了这种算法的例子。从中可以看出,在做了第一遍的(n?1)次比较后,最小的数已经放到了最后,所以第二遍只需要考虑(n?1)个数,即只需要比较(n?2)次。第三遍则只需要做(n?3)次比较总共最多(n?1)遍比较就可以完成排序。图5.15表示了起泡排序算法的程序框图,并编制了起泡排序算法的程序。,表5-1 起泡排序算法举例,图5.15 起泡排序算法的程序框图,data ends program segment main proc far assume cs:program,ds:data start: push ds sub ax,ax push ax mov ax,data mov ds,ax mov cx,n dec cx,loop1: mov di,cx mov bx,0 loop2: mov ax,abx cmp ax,abx+2 jge cotinue xchg ax,abx+2 mov abx,ax,cotinue: add bx,2 loop loop2 mov cx,di loop loop1 ret main endp program ends end start ;执行时n值需更换为实际值,5.4.4 串操作程序 【例5-15】 位串插入程序。程序要求把一个小于32位的位串插入存储器内的一个大位串中的任意位置中去。欲插入的位串存放在bitsg中,它是一个右对齐的位串,可称其为子串,其长度用bitsg_length为符号名的“=”伪指令来说明。大位串存放在string中,并为要插入的子串准备了一个符号名为sg_end的双字单元。这里用双字来定义位串的原因在于用双字处理位串可以获得更快的速度。 这一程序主要由移动大位串位置和插入子串这两个子程序组成,主程序框图见图5.16(a),子程序框图见图5.16(b)和(c),程序则见下面“例5-15位串插入程序”。,图5.16 位串插入程序框图,主程序中除调用子程序外,主要是作进入子程序条件的判断。一是对子串长度的限制:0子串长度32;二是测试子串插入的位偏移(即在大位串中子串插入的起始位偏移值)与大位串位置的关系。如果正好在大位串尾,则可直接把子串接在大位串之后;如果已超出大位串的范围,则退出程序不作处理;只有在大位串的范围之内时,才可调用子程序完成本程序所规定的任务。移动大位串位置子程序要完成的任务是为插入子串而移动大位串的位置。这一工作分两步完成:,第一步,先移动好插入位偏移所在双字之后的其余双字,这些双字应从尾部开始后移,其后移位数应等于插入串长度(参见图5.17)。这部分程序是在该子程序的loop指令之前。其中,要移动的双字数=大位串双字数 - 插入位偏移所在双字地址。,图5.17 大位串和插入子串举例 (位偏移为58,子串长度为15位),第二步,移动插入位偏移所在双字。这里要注意的是,插入位偏移之前的各位应保持原位置不变,而其后各位应后移插入串长度位。 在这一子程序中,多处用到双字长移位指令shld或shrd。应该注意该指令执行完后,目的寄存器得到所移入的新内容,而源寄存器则维持原状不变。,插入位串子程序的任务是按指定的插入位偏移值插入子串。也就是用指定的子串取代原有该位置的值。在程序中,先取出插入位偏移所在的双字和与其相邻的高阶双字,然后用一串移位指令来达到既移入新子串,又不影响子串外原来两个双字的内容的目的。主、子程序都在同一个源文件中,因而参数传送和寄存器的保存和恢复工作都简化了。,例5-15的位串插入程序如下: .model small ;工作模式 .386 ;处理器类型 .stack 200h ;定义堆栈段 .data ;定义数据段,bitsg dd 7fffh ;定义数据 string dd 12345678h,12345678h,12345678h,12345678h sg_end dd ? bit_offset dd 58 bitsg_length = 15 .code ;定义代码段 main proc,start: mov ax,data mov ds,ax mov es,ax mov cx,bitsg_length cmp cx,0 je exit cmp cx,32 jae exit mov edi,bit_offset,mov ecx,(sg_end - string)4 shl ecx,5 cmp edi,ecx ja exit jb move mov esi,bitsg mov sg_end,esi jmp exit,move: call mov_string call insert_bitsg exit: mov ax,4c00h int 21h main endp mov_string proc sub eax,eax std mov si,offset sg_end?4 mov di,offset sg_end,mov ecx,(sg_end - string)/4 mov ebx,bit_offset shr ebx,5 sub ecx,ebx next: mov ebx,si shld eax,ebx,bitsg_length stosd mov eax,ebx sub si,4,loop next sub ebx,ebx sub edx,edx mov ecx,bit_offset and cl,1fh shrd ebx,eax,cl shld edx,ebx,cl shl eax,bitsg_length mov ebx,-1 shl ebx,cl and eax,ebx or eax,edx mov edi,eax ret,mov_string endp insert_bitsg proc mov esi,bitsg mov edi,bit_offset mov ecx,edi shr edi,5 shl edi,2 and cl,1fh,mov eax,stringedi mov edx,string+4edi mov ebx,eax shrd eax,edx,cl shrd edx,ebx,cl shrd eax,esi,bitsg_length rol eax,bitsg_length mov ebx,eax shld eax,edx,cl,shld edx,ebx,cl mov stringedi,eax mov string+4edi,edx ret insert_bitsg endp end start,5.5 在实模式下发挥80386及其 后继机型的优势,在本章的开始,已经说明本书将以实模式为基础来阐述程序设计方法。前4节所给出的都是基于8086的程序,由于80x86的兼容性,这些程序都可以在任何一种80x86机型的实模式下运行,而且它们在高档机上运行可比在低档机上运行获得更高的性能。但是,386及其后继机型除提供更大容量、更高的速度和保护模式的支持外,还提供了一些良好的特性,如能在程序设计中充分利用这些优势,将有利于提高编程质量。在这一节里,将就这方面的问题加以讨论。,5.5.1 充分利用高档机的32位字长特性 80x86系列从80386起就把机器字长从16位增加到32位。字长的增加除有利于提高运算精度外,也能提高编程效率。例如:双字长数的加法和减法,在8086中必须用add和adc序列或sub和sbb序列来完成;而在386及其后继机型中只用一条add或sub指令就可以完成了。因此,不论在空间方面还是时间方面都有利于程序效率的提高。为了更进一步说明问题,看一看下面的程序举例。,【例5-16】 如有两个4字长(64位)数分别存放在data1和data2中,请用8086指令编写一程序求出它们的和,并把结果存放于data3中。 为了得到4字长数的和,在8086中需要分4段计算,每段一个字长(16位),用4次循环可得到4字长数的和。考虑到每次求和可能有进位值,要用adc(而不是add)指令求和,而且在进入循环前应先清除cf位。在循环中修改地址指针时用inc指令而不用add指令,以免影响求和时得到的进位值(inc指令不影响cf位)。,程序如下: .model small .data data1 dq 123456789abcdefh data2 dq 0

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论