前面的课程中我们学习了分支程序的设计,下面学习程序设计中另一种常用的程序结构——循环结构。
循环程序简介
在一个实用的程序中,循环结构是必不可少的。循环是反复执行某一部分的操作,有两类循环结构:
(1) 当型循环,即当给定的条件成立时,执行循环体部分,执行完毕回来再次判断条件,如果条件成立继续循环,否则退出循环。
(2) 直到型循环,即先执行循环体,然后判断给定的条件,只要条件成立就继续循环,直到判断出给定的条件不成立时退出循环。
下面我们就通过一些例子来看C语言提供的循环语句,及如何利用这些循环语句写循环程序。
例1:
使P1口所接LED以流水灯状态显示
#include “reg51.h”
#include“intrins.h”//该文件包含有-crol-(...)函数的说明
void mDelay(unsigned int De-layTime)
9{unsigned int j=0;
for(;DelayTime>0;Delay-Time——)
9{for(j=0;j<125;j++)};}}}
void main()
9{unsigned char OutData=0xfe;
while(1)
9{P1=OutData;
OutData=-crol-(OutData,1); //循环左移
mDelay(1000);/*延时1000ms*/
}}
程序分析1:
输入源程序,并命名为exam31.c,建立并设置工程,这个例子使用实验仿真板(dpj.dll文件,见本期配刊光盘的“本期程序”文件夹,具体使用方法见今年第1期杂志第38页相关内容)演示的过程请自行完成。如果在演示时,发现灯“流动”的速度太快,几乎不能看清,那么可以将mDelay(1000)中的1000改大一些,如2000、3000或更大。软件仿真无法实现硬件实验同样的速度,这是软件仿真的固有弱点,下面介绍如何用www.mcustudio.com网上介绍的单片机实验电路板(见图1)来实现这个例子。

将实验板带的串口电缆连接到PC机的串口上,设置Keil工程时,应选中De-bug页,点击右侧的“Use Keil Monitor_51 Drive”,然后选中“Load Application at Start”和“Go Till main”,如图2所示。选择完成后,点击“Setting”按钮,选择你所用的PC上的串口(COM1或COM2),波特率(通常可以使用38400),其他设置一般不需要更改,如图3所示。点击“OK”回到Debug页面后即可完成设置。


编译、连接正确后,点击菜单De-bugStart /Stop Debug Session,可以看到在窗口右下角的命令窗口提示正确连接到了Monitor-51,如图4所示。此时,即可使用Keil提供的单步、过程单步、执行到当前行、设置断点等调试方法进行程序的调试。如果全速运行程序,就可看到流水灯的实验效果。
程序分析2:
这段程序中在两处用到了循环语句,首先是主程序中,按前面的分析,主程序应该是一个无限循环的过程,只要通电,程序就能够不断地运行下去。因此主程序使用了“while(1)9邀......9妖”这样的循环语句写法,在9邀9妖中的所有程序将会不断地循环执行,直到断电为止;第二处是延时程序,使用了for循环语句的形式。下面我们就对循环语句作一个介绍。
while语句
while语句用于实现“当型”循环结构,其一般形式如下:
while(表达式) 语句
当表达式为非0值(真)时,执行while语句中的内嵌语句。其特点是:先判断表达式,后执行语句。
在例1中,表达式使用了一个常数“1”,这是一个非零值,即“真”,因此,条件总是满足,所以语句总是会被执行,这样就构成了一个无限循环的过程。下面再举一例说明:
例2:
当K1键被按下时,流水灯工作,否则灯全部熄灭。
#include “reg51.h”
#include“intrins.h”//该文件包含有-crol-(...)函数的说明
void mDelay(unsigned int De-layTime)
9{ ......与例1同9}
void main()
9{ unsigned char OutData=0xfe;
while(1)
9{ P3|=0x3c;
while((P3|0xfb)!=0xff)
9{ P1=OutData;
OutData=_crol_(OutData,1); //循环左移
mDelay(1000);/9*延时1000ms*/}
P1=0xff;
}}
程序分析:
这个程序中的第二个while语句中的表达式用来判断K1键是否被按下,如被按下,则执行循环体内的程序,否则执行“P1=0xff”;程序行,实现题目的要求,有关实验,请读者自己完成。
do-while语句
do-while语句用来实现“直到型”循环,特点是先执行循环体,然后判断循环条件是否成立。其一般形式如下:
do 循环体语句
while(表达式)
对同一个问题,既可以用while语句处理,也可以用do_while语句处理。但是这两个语句是有区别的,下面我们用do_while语句改写例2。
例3:
用do-while语句实现如下功能:K1按下,流水灯工作,K2松开,灯全熄灭。
#include “reg51.h”
#include“intrins.h”//该文件包含有-crol-(...)函数的说明
void mDelay(unsigned int De-layTime)
9{ ......与例1同9}
void main()
9{ unsigned char OutData=0xfe;
while(1)
9{ P3|=0x3c;
do
9{ P1=OutData;
OutData=-crol-(OutData,1); //循环左移
mDelay(1000);/*延时1000ms*/
9} while((P3|0xfb)!=0xff)
P1=0xff;
9}}
程序分析:
这个程序除主程序中将while用do_while替代外,没有其他的变化,初步的想法,如果while()括号中的表达式为“真”即K1键被按下,应该执行程序体,否则不执行,效果应该与例2相同。但是事实上,实际做这个练习就会发现,不论K1是否被按下,流水灯都在工作。为何会有这样的结果呢?
单步运行程序可以发现,如果K1键被按下,的确是在执行循环体内的程序,与设想相同。而当K1没有被按下时,按设想,循环体内的程序不应该被执行,但事实上,do后面的语句至少要被执行一次才去判断条件是否成立,所以程序依然会去执行do后的循环体部分,只是在判断条件不成立(K1没有被按下)时,转去执行P1=0xff;然后又继续循环,而下一次循环中又会先执行一次循环体部分,因此,K1是否被按下的区别仅在于“P1=0xff;”这一程序行是否会被执行到。
for语句
C语言中的for语句使用最为灵活,不仅可以用于循环次数已经确定的情况,而且可以用于循环次数不确定而只给出循环结束条件的情况,它完全可以替代while语句。
for语句的一般形式为:
for(表达式1;表达式2;表达式3) 语句
它的执行过程是:
(1) 先求解表达式1。
(2) 求解表达式2,如果其值为真,则执行for语句中指定的内嵌语句(循环体),然后执行第(3)步;如果为假,则结束循环。
(3) 求解表达式3。
(4) 转回上面的第(2)步继续执行。
典型的应用是这样的一种形式:
for(循环变量初值;循环条件;循环变量增值) 语句
例如上述例子中的延时程序有这样的程序行:“for(j=0;j<125;j++)9{;}”,执行这行程序时,首先执行j=0,然后判断j是否小于125,如果小于125则去执行循环体(这里循环体没有做任何工作),然后执行j++,执行完后再去判断j是否小于125……如此不断循环,直到条件不满足(j>=125)为止。
如果用while语句来改写,应该这么写:
j=0;
while(j<125)
9{……
j++;
9}
可见,用for语句更简单、方便。
如果变量初值在for语句前面赋值,则for语句中的表达式1应省略,但其后的分号不能省略。上述程序中有:“for(;DelayTime>0;DelayTime——)9邀…9妖”的写法,省略掉了表达式1,因为这里的变量DelayTime是由参数传入的一个值,不能在这个式子里赋初值。
表达式2也可以省略,但是同样不能省略其后的分号,如果省略该式,将不判断循环条件,循环无终止地进行下去,也就是认为表达式始终为真。
表达式3也可以省略,但此时编程者应该另外设法保证循环能正常结束。
表达式1、2和3都可以省略,即形成如for(;;)的形式,它的作用相当于是while(1),即构成一个无限循环的过程。
循环可以嵌套,如上述延时程序中就是两个for语句嵌套使用构成二重循环,C语言中的三种循环语句可以相互嵌套。
break语句
在一个循环程序中,可以通过循环语句中的表达式来控制循环程序是否结束,除此之外,还可以通过break语句强行退出循环结构。
例4:
开机后,全部LED不亮,按下K1则从LED1开始依次点亮,至LED8后停止并全部熄灭,等待再次按下K1键,重复上述过程。如果中间K2键被按下,LED立即全部熄灭,返回起始状态。
#include “reg51.h”
#include“intrins.h”//该文件包含有-crol-(...)函数的说明
void mDelay(unsigned int De-layTime)
9{...与例1同9}
void main()
9{ unsigned char OutData=0xfe;
unsigned char i;
while(1)
9{ P3|=0x3c;
if((P3|0xfb)!=0xff)//K1键被按下
9{ OutData=0xfe;
for(i=0;i<8;i++)
9{ mDelay(1000);/*延时1000ms*/
tmp=0xfe;
if((P3|0xf7)!=0xff)//K2键被按下
break;
OutData=_crol_(OutData,i);
P1&=OutData;
9}
9}
P1=0xff;
9}
9}
请读者输入程序、建立工程,使用实验仿真板来验证这一功能,注意,K2按下的时间必须足够长,因为这里每1s才会检测一次K2是否被按下。本期配刊光盘中“本期程序”文件夹下的ex-am34.avi记录了实验过程,可供参考。
程序分析:
开机后,当检测到K1键被按下,执行一个for(i=0;i<8;i++)9邀…9妖的循环,即循环8次后即停止,而在这段循环体中,又用到了如下的程序行:“if((P3|0xf7)!=0xff) break;”即判断K2是否按下,如果K2被按下,则立即结束本次循环。
continue语句
该语句的用途是结束本次循环,即跳过循环体中下面的语句,接着进行下一次是否执行循环的判定。
Continue语句和break语名的区别是:continue语句只结束本次循环,而不是终止整个循环的执行;而break语句则是结束整个循环过程,不会再去判断循环条件是否满足。
例5:
将上述例4中的break语句改为continue语句,会有什么结果?
程序分析:
开机后,检测到K1键被按下,各灯开始依次点亮,如果K2键没有被按下,将循环8次,直到所有灯点亮,又加到初始状态,即所有灯灭,等待K1按键。如果K2键被按下,不是立即退出循环,而只是结束本次循环,即不执行continue语句下面的“OutData=_crol_(OutData,i);P1&=OutData;”语句,但要继续转去判断循环条件是否满足,因此,不论K2键是否被按下,循环总是要经过8次才会终止,差别在于是否执行了上述两行程序。如果上述程序行有一次未被执行,意味着有一个LED未被点亮,因此,如果按下K2过一段时间(1、2s)松开,中间将会有一些LED不亮,直到最后一个LED被点亮,又回到全部熄灭的状态,等待K1被按下。
本期配刊光盘中“本期程序”文件夹下的exam35.avi记录了实验过程,可供参考。该文件夹下还有本期涉及的其他实例的C语言源程序,读者均可直接在Keil软件中打开。
练习:
固定每点亮2个LED后,第三个LED不亮,请编程实现。
凌阳16位单片机暨大学计划研讨会即将召开
2004年3月20日,凌阳16位单片机暨大学计划研讨会,将在北京航空航天大学举行。
届时,包括业界专家、高校教师、台湾凌阳科技(SUNPLUS)资深IC设计专家及系统工程师在内的数百人,将齐聚一堂,就IC设计技术、十六位单片机的发展形势、凌阳十六位单片机原理及开发等内容展开广泛的探讨。同时,还将向与会者发布凌阳系列16位单片机的应用方案,与大家共享。
据悉,本次研讨会是在2002凌阳科技大学计划发布会全国站、2003凌阳16位单片机百所高校巡回讲座后,凌阳大学计划举行的又一系列的全国研讨会。
会议诚邀各高校教师及电子工程师参加,届时会有精美礼品等待您。
详情请参见凌阳大学计划网站:http://www.unsp.com.cn
(周坚)