4.3.3 for循环:某个范围内…每个都…
既然while语句和do…while…语句都已经可以满足我们表达循环现象的需要,那为什么C++还要专门提供for语句来表达循环现象呢?在现实世界中,常常有这样一类特殊的循环现象,例如:
在公司100000个员工范围内,每个员工都增加10000元工资;
在从1到100这个范围内,每个数字都累加到总和中。
说这些是循环现象,是因为它们的动作(增加工资、累加)会循环往复反复多次地执行。而说它们特殊,是因为这些动作总是在某个范围内(100000个员工范围内、从1到100范围内)针对其中的每一个元素执行(每个员工都增加,每个数字都累加)。在自然语言中,我们可以用“某个范围内…每个都…”这种句式来表达这类在某个范围内进行的循环现象。又因为其非常普遍,为了更方便地表达这类循环现象,所以C++才专门提供了for循环结构语句。在C++中,for循环结构的语法格式如下:
for( 初始化语句;条件判断语句;更改语句 ) { 循环体语句; }
for循环语句所表达的是在某个范围内的循环现象,所以,其中往往有一个循环索引,它就像一个游标一样在循环范围内移动,从而可以通过它依次访问到循环范围内的各个元素。而for关键字之后的三条语句,就是对这个循环索引进行操作,使其在循环范围内按照某种规律移动,从而遍历整个循环范围。具体而言:
1. 初始化语句
初始化语句会在进入for循环语句执行时被执行一次,主要用来定义循环索引值并进行初始化,将其定位到循环范围的起始位置。也就是说,它确定的是循环范围的起点。比如,“int i = 1”就是一个典型的初始化语句,它定义了循环索引值i并将其赋值为1,也就表示这个循环是从1开始的(在C++中,我们往往用0作为循环起点)。
2. 条件判断语句
作为每次循环的开始,条件判断语句都会被执行一次,如果其值为true,会继续向下执行循环体语句。反之,则直接结束整个for循环的执行。因此,它主要用来判断循环索引值是否在循环范围的结束位置之内。如果在范围之内,条件判断语句的值就是true,for循环语句会开始下一次循环继续向下执行循环体语句。反之,如果循环索引值超出了循环范围的结束位置,条件语句的值就是false,for循环将不再继续而直接结束整个for循环。从这个意义上讲,它确定了循环范围的终点。例如,“i <= 100”就是一个典型的条件判断语句,在每次循环开始之前,它都会判断循环索引i是否在循环范围的结束位置100之内,以决定是否开始这一次循环体语句的执行,以此来将循环限定在循环范围之内。
3. 更改语句
更改语句会在每次循环体语句执行结束后被执行,主要用来对循环索引值进行修改,使其按照某种规律,逐渐地依次从循环起点变化到循环终点。在这个过程中,我们就可以根据这个不断变化的循环索引值访问这个循环范围内的每个元素,这一过程通常也被称为对循环范围内元素的遍历。例如,“++i”这个更改语句,就是让循环索引i在每次循环后增加1,使其向循环范围的结束位置移动一步。这样,循环索引i就可以从循环起点1逐渐递增到循环终点100,这样我们就有机会访问到循环范围内的每一个元素。
最佳实践:注意循环条件的处理,避免形成死循环
所谓死循环,就是指循环没有结束的机会,会一次又一次永远执行下去的循环。任何循环都必须在一定条件下结束循环,或者是因为条件判断语句的值为false而结束循环,或者是在循环内部在一定条件下用break关键字结束循环,总之一定要有结束循环的机会,否则就会成为死循环一直执行下去,导致循环后面的代码得不到执行,自然也就得不到正确的结果。例如,在前面的例子中,“0 != nInput”这个条件判断语句就有其值成为fasle的机会,那就是用户输入的nInput的值为0的时候。所以,这个循环有机会结束而不会成为死循环。
循环的结束往往是由条件判断语句决定的,所以我们在处理条件判断语句时要格外小心。如果处理不当,就可能导致无论怎么执行,其值始终是true,这样循环就永远没有机会结束而形成一个死循环。例如:
// 条件判断语句处理不当形成的死循环 // 忘掉了变更语句,但是仍能编译通过 for(int i = 0; i < 10000;) { cout<<"I LOVE YOU!"<<endl; }
这段程序的本意是想说一万遍“I LOVE YOU!”装情圣,结果却忘了在更改语句中对循环索引i进行更改,这样,循环索引i的值始终是最初的0,而条件判断语句“i<10000”的值也就始终为true,循环始终无法结束而成为一个死循环,结果就成了喋喋不休地说“I LOVE YOU!”,自然只会被人当成傻瓜了。如果不想从情圣变傻瓜,那就好好处理我们的条件判断语句,尽量避免死循环的出现。
for关键字之后的三个语句是对循环条件进行处理,那循环体语句就表达的是循环动作。在执行的时候,for循环会首先执行一次初始化语句,然后再执行条件判断语句,如果其值为true,则意味着当前循环条件满足,就会开始向下执行循环体语句,完成后,接着会执行更改语句对循环索引值进行修改,随后会再次执行条判断语句,以判断循环条件是否仍然满足,如果满足,则开始下一次循环。这个过程会不断循环下去,直到条件判断的值为false,循环条件不再满足为止。for循环的执行过程如图4-5所示。
在整个循环过程中,循环索引的值会按照某种规律依次从循环开始位置变化到循环的结束位置,这也就意味着,在每次循环中,循环索引的值是不断变化的,而利用这些不断变化的索引值,我们就可以访问到循环范围内的各个数据。例如,”nTotal += i;” 就是一个典型的循环体语句,每次循环的时候,它将循环索引值i加和到nTotal中,而循环索引值i的值又是从1(int i = 1)到100(i <= 100)逐渐递增(++i)的,这也就相当于nTotal依次加上了1、2、3…99、100,自然最后得到的就是从1到100所有整数的累加和了。
按照上面我们对for循环各个部分的分析,我们可以得出这个计算从1到100所有整数和的for循环:
// 计算从1到100之间所有整数的和 // 记录和值 int nTotal = 0; // 使用for循环,将1到100之间的整数逐个跟nTotal相加 for( int i = 1 ; // 循环起点 i <= 100 ; // 循环终点 ++i ) // 循环索引值变化规律 { // 将整数累加 nTotal += i; } cout<<"1到100之间所有整数的和是"<<nTotal<<endl;
4.3.4 循环控制:break和continue
虽然循环现象会周而复始不断地执行,但也有遇到意外情况循环被打破的时候。例如:
一直不断地为祖国辛勤工作,只要我还活着。但如果生病了,只好提前退休;
公司100000位员工,除了年龄超过50岁的人之外,其余每个人的工资都增加10000元;
这里,循环现象中出现的“生病”和“年龄超过50岁”,就打破了原来的循环,属于循环现象中的例外情况,需要特殊处理,有的需要提前终止循环(生病退休),有的需要跳过循环体的执行(不增加工资)而直接开始下一次循环。为了描述循环中的例外情况,C++提供了两个关键字:break和continue。
1. break
在介绍switch并列条件选择语句时已经介绍过break关键字,它的作用就是结束整个switch语句的执行。与之类似的,当它用在循环结构中时,其作用也同样是结束当前循环控制语句,继续执行循环语句之后的下一条语句。这样,我们就可以利用它来表达那些因例外情况而需要提前结束的循环现象。例如,用它来表达因“生病”这一例外情况而“提前退休”的循环现象:
// 是否活着 bool bAlive = true; // 是否生病 bool bSick = false; while(bAlive) // 判断是否活着 { if(bSick) // 判断是否生病 { cout<<"生病了提前退休"<<endl; break; // 因为生病这一例外情况而提前结束循环 } cout<<"为祖国辛勤工作"<<endl; // 改变循环控制条件bAlive… } // …
在执行循环体语句的时候,会首先用if条件语句判断是否生病,也就是判断例外情况是否发生。如果例外情况发生,bSick的值会变为true,程序会进入if条件语句内部执行,当遇到break关键字之后,就会跳出循环,结束整个循环的执行。这样,if条件语句负责对是否发生例外情况进行判断,而break关键字则负责在例外情况发生的时候提前结束循环。两者结合起来,就很好地表达了那些因发生例外情况而需要提前结束的循环现象。
我们可以再来看一个实际的例子,还是那个收支统计程序,我们可以用break关键字将其改写为:
int nTotal = 0; int nInput = 0; do { cout << "请输入你的收入或支出:"; cin>>nInput; // 对输入的数据进行判断, // 如果是0这种例外情况,表示输入结束,用break关键字结束整个循环 if( 0 == nInput ) break; // 正常情况,将输入的收支数目加和到收支总数 nTotal += nInput; } while ( true ); // 用true作循环条件,表示循环会在内部因例外情况而结束
在这里,我们用true作为do…while…循环语句的条件判断语句,但这并不是一个“死循环”,因为它有结束循环的机会,那就是当用户输入的nInput为0时,程序会进入“if( 0 == nInput )”条件语句执行,遇到其中的break关键字就会结束整个循环,从而避免了成为死循环。同时,它也表达了对“输入的收支数目为0”这一例外情况的处理:结束整个循环。经过这样的改写后,循环语句可以无限地接受用户的输入,但是当用户想结束输入的时候,只需要输入0满足“0 == nInput”这个意外情况条件,执行break关键字就可以结束整个循环的执行。
2. continue
break关键字是在某种条件下结束整个循环,而continue关键字则是在某种条件下结束本次循环体(也就是跳过continue关键字之后的所有循环体语句)的执行,然后直接继续向下尝试下一次循环。如果是while或do…while…循环,会直接判断循环条件是否满足以开始下一次循环;如果是for循环,则会接着执行更改语句然后再判断循环条件是否满足以开始下一次循环。还是使用上面的收支统计例子,如果一个大款对小于10000元的小钱根本不在乎,只想统计大于等于10000的收入和支出,则可以用continue关键字将程序改写如下:
// 大款的收支统计程序 int nTotal = 0; int nInput = 0; do { cout << "请输入你的收入或支出:"; cin>>nInput; // 如果是小于10000的小钱,不用统计在内 if( (nInput > -10000) && (nInput < 10000) ) continue; // 跳过后面的循环体语句,不进行统计 nTotal += nInput; } while ( 0 != nInput );
在这个大款的收支统计程序中,当nInput接收用户输入数据后,我们用if条件语句判断其数值是否小于10000。如果小于,表示这是不用统计的小钱,则执行if条件语句中的continue关键字,跳过后面的加和统计语句“nTotal += nInput;”的执行不将其统计在内,而直接跳转到对条件表达式“0 != nInput”的计算,以判断是否可以开始下一次循环。这样,就很好地表达了对“收支数目小于10000”这种例外情况的特殊处理:结束本次循环,不将其统计在内。
总结起来,break和continue常常和条件语句混合使用,条件语句负责判断是否出现例外情况,而它们则表达对例外情况的处理:break是跳出整个循环,立刻结束循环控制语句的执行;而continue只跳出本次循环,结束本次循环的执行而继续执行下一次循环。图4-6展示了break和continue之间的区别。
图4-6 break和continue之间的区别