11.1.1 break的作用与用法
循环就象绕圈子。比如,体育课,跑1200米,跑道一圈400米,所以我们要做的事就是一边跑一边在心里计数(当然要已数,否则老师万一少计一圈,我们可就玩完了),当计数到3圈时,“循环”结束。
如果,我在跑步时不幸由于体力不支而晕倒……怎么办?
有两种办法,一种是在判断是否继续循环的条件中加入新增条件的判断:
假设原来的循环表达为:
while(已跑完的圈数 < 3)
{
跑一圈……;
}
那么,加上附加条件后,循环表达为:
while(已跑完的圈数 <3 && 我还跑得好好的) //&& 就是"并且",没忘吧?
{
跑一圈……
}
第二种方法是在循环中使用条件分支,在指定的条件成立时,中途跳出循环,用于实现跳出的关键字为:break。
while(已跑的圈数 < 3 )
{
跑一圈……;
if(我身体感觉不妙)
break;
}
在循环中,每跑完一圈,都检查一下自已是否感觉不妙,如果是,则程序执行break,直接跳出while,而不管此时圈数是否到达3圈。
还记得“小女孩买裙子”的故事吗?那时候,我们将“父母不给买小红裙 && 我还没有哭累”作为循环继续的条件,如果使用break,则可以写成这样:
while(父母不给买小红裙)
{
我哭;
if(我哭累了)
break;
}
在循环中,“我”每哭一次,都想想是否累了,如果是,则程序执行break,直接跳出while,而不管此时爸妈是否已经买了我的裙。
通过这两个例子,你应该注意到了,如果要用break,则if的条件(也就是要执行break分支的条件),正好是把原来放在循环判断中的条件反正过来。比如,原来是判断“我还跑得好好的”,现在则是判断“我身体感觉不妙”;原来是判断“我还没有哭累”,现在是判断“我哭累了”。
一句话,原来是判断“是否继续循环”,现在是判断“是否跳出循环”……
再来看那个“可以多次统计”的统计程序。看看是否也能把它改成使用break来结束循环。
为了节省篇幅同时也是为了突出重点,我们将其中用于实现一次统计的代码,用一句伪代码来实现。(什么叫伪代码?我们用得很经常啊,就是那些用自然语言写的“代码”,这些代码当然无法在计算机上运行,它们只是要方便地表达实际代码要实现的功能)。
int main(int argc, char* argv[])
{
实现统计一个学员的成绩; //伪代码,详细代码请见上章相关部分
do
{
//提问是否继续统计:
cout <<"是否开始新的统计?(Y/N)?";
cin >> c;
}
while( c == 'y' || c == 'Y');
}
改成用 break;
int main(int argc, char* argv[])
{
实现统计一个学员的成绩; //伪代码,详细代码请见上章相关部分
do
{
//提问是否继续统计:
cout <<"是否开始新的统计?(Y/N)?";
cin >> c;
//如果用户输出的不是字母Y,说明他不想继续统计了,我们需要中断循环。
if( c != 'y' && c != 'Y')
break;
}
while (true);
}
请首先 while(true)部分,其条件直接写上真(true),表明这是一个无条件的循环(即,循环将无条地一直持续下去),这岂不犯了程序界的武林大岂:成了一个“死循环”?其实,相信你已明白,在循环体内,有一个break的分支在呢,当判断用户输入的字母既不是小写的y,也不是大写的Y,break就起它能起的作用了。
三个例子,都是从循环判断的条件摘出一部分或全部(最后一个例子),然后循环体中,采用一个if判断,结束break来跳出循环。可能你会问:为什么要break呢?直接用原来的方法,在while处判断条件不是很好吗?
break的长处在于,它可以在循环体内的任意位置进行判断。
继续上一例。假设我们出于慎重,想在用户按入 N 时,再问他一句是否真的退出统计,则此时显示出了break的方便:
int main(int argc, char* argv[])
{
实现统计一个学员的成绩; //伪代码,详细代码请见上章相关部分
do
{
//提问是否继续统计:
cout <<"是否开始新的统计?(Y/N)?";
cin >> c;
//如果用户输出的不是字母Y,说明他不想继续统计了,我们需要中断循环。
if( c != 'y' && c != 'Y')
{
//出于慎重起见,我们要再问一句用户是否真的不统计了?
cout << "您真的不想继续计算了?(Y:真的结束 / N:继续统计)";
cin >> c;
//这回,如果用户输入Y,表明他真的不统计了:
if( c == 'Y' || c == 'y')
break;
}
}
while (true);
}
在上面例子,由于用户的两次输入我们都采用变量 c (char 类型)接收。但如果第一次输入 字母‘Y’时,循环需继续,但如果用户是在第二次输入‘Y',则表示是真的不统计了,循环却必须结束;所以,此时while无法仅凭c的值来做出正确判断,但,采用break,正如上面代码,我们在合适的位置安排一个break,从而直观地实现了。
当然,这里仅为了讲学方便而举此例,如果你真的在程序中为了一个“是否继续统计”而问了用户两遍,可能会被用户骂做“神经质”。不过,如果是删除某些重要数据(直接删除,不可恢复的情况),多问一次就选得很重要了。(比如句神英语删除用户操作就会在最后多问一句“真的要说再见吗?我们会想你的……”)
再举一例,看我们前面关于跑步的例子:
while(已跑的圈数 < 3 )
{
跑一圈……;
if(我身体感觉不妙)
break;
}
这段代码有点问题,因为判断“我身体感觉不妙”是在跑完一圈之后……很可能我在某一圈刚开始跑时就觉得肚子剧痛,极可能是得阑尾炎啊!按照这段程序,我只有坚持跑完一圈后,才能break了……
要完美解决这个问题,我们将在本章再后讲到,现在先采用一个“通融”的办法,我们允许你每跑100米就检查一次吧:
while(已跑完图数 < 3)
{
跑第1个100米;
if(我身体感觉不妙)
break;
跑第2个100米;
if(我身体感觉不妙)
break;
跑第3个100米;
if(我身体感觉不妙)
break;
跑第4个100米;
if(我身体感觉不妙)
break;
}
代码中,我们将1圈拆为4个100米,每跑完1/4,我们就检查一次是否身体不对。看明白这个例子,我想你对break的用途和用法,可以算是理解了。
11.1.2 break 的一个“高级用法”
本小节不是很适于没有多少实际编程经历的初学者,所以初学者可以跳过,以后再回头阅读。当然,所谓的“高级用法”的确是应该加对引号的,所谈的内容只是一个高手们常用小小技巧。
使用do...break...while简化多级条件判断的结构。
如果你写过不少代码,那么一定会不时遇到类似下的情况:
假设要找到文件A,复制该文件为B;然后打开B文件,然后往B文件内写入一些内容;最后在写入成功后,我们需要再进行一些相关操作。
在此过程,遇到以下情况时将放弃后续的操作,认为是操作失败:
1、如果A文件不存在;
2、如果B文件已经存在,并且询问用户是否覆盖时,用户回答“不”;
3、无法复制出B文件;
4、无法打开B文件;
5、无法写入B文件;
6、无法正常关闭B文件。
用伪代码写该段程序为:
if( A文件存在 )
{
执行A文件的相关操作;
if( B文件不存在 || 用户允许覆盖原有B文件)
{
复制A文件为B文件;
if(复制文件成功)
{
打开B文件;
if(打开文件成功)
{
写入文件;
if(写入成功)
{
关闭B文件;
if(关闭成成功)
{
执行其它必须在一切成功后进行的操作。
……
}
}
}
}
}
}
可能有些操作和判断可以同时处理,但这个程序的繁琐仍然不可避免,而现实中程序的复杂性往往要远过于此例。从语法上看,这个例子没有任何错误,但它的一层套一层的条件判断却让人难以书写,阅读,调试,在复杂的情况就容易造成人为的错误(比如最马虎的,花括号匹配不对等……)。
同样一段代码“程序老鸟”是这样写的:
do
{
if(A文件不存在)
break;
执行A文件的相关操作;
if(B文件存在 && 用户不允许覆盖)
break;
复制A文件为B文件;
if(复制不成功)
break;
打开B文件;
if(打开B文件不成功)
break;
写入文件;
if(写入文件不成功)
break;
关闭B文件;
if(关闭不成功)
break;
执行其它必须在一切成功后进行的操作。
……
}
while(false);
看,代码是不是“直”了很多?这里用了do..while,可是根本不是为了循环,而是为了使用它的break功能。每当有操作不成功,就直接用break跳出循环。所以循环条件总是一个“永假” false。
在一个程序中,这种结构相当的多,为了更加一步淡化while的原来的循环用途,我们非常值得在代码加入两个共用的宏:
#define BEG_DOWHILE do {
#define END_DOWHILE } while(false);
这里举的是do...while结构,在某些情况下,可以使用while...来实现类似功能。
11.1.3 break 在for循环中的一点注意
前面举的例子都是do...while或while,break在for循环也一个样。请看下面例题:
例一:从1开始累加,每次递增1,请问累加到哪个数,累加和超过2000?请输出该数,及当时的累加和。
分析:和求1~100的累加和类似,只是在发现累加和已经超过2000时,就输出当前累加的数,然后结束循环。
for(int i=1,sum=0;;i++)
{
sum += i;
if(sum > 2000)
{
cout << i << "," << sum << endl;
break;
}
}
输出结果为:
63,2016
关于这段例子,需要注意三点:1、循环条件初始的位置,我们同时声明两个变量;2、没有循环条件。为了解这两点注意。
最后一点注意是关于break和“条件因子变化”的注意。我们知道,for每执行一遍循环体后,都将执行一次“条件因子变化”语句(见上图③)。现在需要注意的是:
在for循环中,执行break后,“条件因子变化”语句同样被跳过,没有被执行循环就被中断。
至此,break 在 while,do...while,for中的用法我们都已见过。不过,你还记得吗,我们最早学到break是在哪里?在讲条件分支语句中switch里。如果你有点忘了那里的break是起什么作用,现在就去看看吧。
11.1.4 多层循环中的break
break 只能跳出当前层的循环,所以,如果有多层循环,则在内层的break跳出循环后,外层的循环还将继续。
前面说跑步的例子,一圈400米,我们每跑100检查一下是否肚子疼什么的,如果疼得利害就break,不跑了。这和现实不符,我们应该每跑一步就检查一次是否肚子疼最合理。
一圈得分成几步呢?显然不能再像上面分成四次检查那样写代码了。我们加一层循环,也就是把跑一圈的工作用一个循环来实现:
while(一圈未结束)
{
跑一步;
}
然后,我们在每跑完一步时加入一个判断:
while(一圈未完)
{
跑一步;
if(我身体感觉不妙)
break;
}
把这跑一圈的代码加入外层循环:
while(已跑完图数 < 3)
{
while(一圈未完)
{
跑一步;
if(我身体感觉不妙)
break;
}
}
外层的while用于负责一圈一圈循环跑完三圈,内层的while用于负责一步一步地循环跑完一圈,同时负责每跑一步就检查是否身体不妙,若身体不舒服,就跳出循环,不跑了。看起来代码很完美,其实BUG已经产生:问题就在那个break。当“我身体感觉不妙”后,程序遇上break,跳出内层while,落入外层的while,外层的循环可没有被break,所以程序将继续外层的循环。假如你跑第一圈跑了一半时肚子疼,按照这段程序逻辑,那好这第一圈剩下的一半你可以不用跑了,但后面的两圈你还得继续。
解决的第一种方法是:
while(已跑完图数 < 3)
{
while(一圈未完)
{
跑一步;
if(我身体感觉不妙)
break;
}
if(我身体感觉不妙)
break;
}
我们在外层也进行了一次判断,这样当然就可保证从内层跳出来以后,外层的循环也被跳出。但在内层已经做过一次“感觉”的情况下,外层还要重新“感觉”一次,这种代码让人不爽,所以我们可以加一个变量,用于记住现在的身体状态:
bool needBreak = false; //是否需要跳出循环
while(已跑完图数 < 3)
{
while(一圈未完)
{
跑一步;
if(我身体感觉不妙)
{
needBreak = true; //做一标志,需要break;
break;
}
if(needBreak)
break;
}
虽然本人的课程并不是绕什么圈子,可是在这有关break的长篇累牍的文字中,想必各位现在头脑里只有一个词,只有一个念头:break。想 break?后面的课程就不要啦?不可能,我们还是continue吧。
11.2 continue
continue 汉意为继续。它的作用及用法和break类似。重要区别在于,当前循环遇到break,是直接结束循环,而若遇上continue,则是停步当前这一遍循环,然后直接尝试下一遍循环。我把“尝试”加粗以引起注意,为什么要注意原因后面再说,请先看下面关于break和continue的对比:
continue并不结束整个循环,而仅仅是中断的这一遍循环,然后跳到循环条件处,继续下一遍的循环。当然,如果跳到循环条件处,发现条件已不成立,那么循环也将结束,所以我们称为:尝试下一遍循环。
例二:求整数1~100的累加值,但要求跳过所有个位为3的数。
分析:在循环中加一个判断,如果是该数个位是3,就跳过该数不加。
如何判断一个1到100中,哪些整数的个位是3呢?还是 % ,将一个2位以内的正整数,除以10以后,余数是3,就说明这个数的个位为3。
比如: 23 ,除以10,商2,余数3。这里我们不需要商,所以用求余(也称为求模)运算:23 % 10 = 3。
int sum = 0;
for(int i = 1; i<=100;i++)
{
if( i % 10 == 3)
continue;
sum += i;
}
cout << sum << endl;
和break正相反:
在for循环中,执行continue后,“条件因子变化”语句没有被跳过,将被执行一次,然后再尝试循环的下一遍。
在上例中,当条件 i %10 ==3 成立时,continue 被执行,于是,程序首先执行 i++;然后执行 i <= 100 的判断。如果将该段程序改成while,正确答案为:
int sum = 0;
int i = 1;
while(i <= 100)
{
if( i % 10 == 3)
{
i++;
continue;
}
sum += i;
i++;
}
cout << sum << endl;
请注意程序中的两句"i++;",缺一不可。
11.3 goto
臭名昭著的goto出场了。
goto的汉义为“转到”,在计算机语言里,它的完整名称为:“无条件跳转语句”。几乎所有高级语言都会劝你尽量不要使用它goto。因为它会破坏程序的模块性,严重降低一段程序的可读性。若是老外写的书,则比喻使用大量goto的代码:“像意大利面条”。嗯,其实北京的杂酱面也很缠绕……可惜没有走向世界。
goto的用法是,首先要在代码中某处加上一个位标(也称标号),然后在代码中的需处,加上goto,并写让要跳转到位标。比如你在第三行代码加一个位标:A : ,然后可以在第10行写上一个goto A,程序执行到该行时,就将跳到第三行。
加位标的方法是在一空行加上位标的名称,命名规则和变量一样,但最后要加上一冒号“:”。
例如:
int i = 1;
A :
cout << i << endl;
i++;
if(i <= 10)
goto A;
... ..
goto 虽然号称“无条件跳转”,事实上倒是有些条件限制。主要是三条。
1、goto只能在当前的同一程序段内跳转;
2、goto 可以从循环内跳转到循环外的代码,但不能从循环外的代码跳到循环内;
3、在有goto的跳转范围内,不能再使用C++允许的临时变量声明。
好了,其实笔者写程序近10年,惟一用到goto的地方就是:将一段简单的程序故意用goto写得面目全非,以期能让破解程序的人因为眼晕而放弃功击……一句老话:如果没有什么特殊理由,不要在程序里使用goto。