和正常的for循环一样,我们首先从表达式1开始,接着进行条件判断,如果为真,进入循环体执行表达式2-1,执行continue--这里出现了变化:我们遇到了continue之后,会中断本次循环(表达式2-2不被执行),跳去执行表达式3,然后接着进行条件判断,等等。在上面的for循环中,由于continue的存在,表达式2-2不会被执行。
如果把continue换成了break,那么执行到break之后就不仅仅是中止这一次循环了,而是直接跳出整个for循环,执行for循环之后的代码;如果continue换成了return,那么在main函数中程序直接结束。
可以看出不加限制地使用continue,break和return会让一些代码永远不被执行到,这显然是没有意义的。因此一般continue,break和return都会和条件判断语句结合使用--当满足一定条件时,跳过循环中剩下的代码,否则还继续正常执行。
我们用while循环来举一个例子:
int sum = 0;
while (true)
{
if (sum == 100)
break;
sum++;
printf("%d\n", sum);
}
这段代码打印了前100个正整数(当然,你也可以用for循环100次来实现它),我们来单步跟踪一下它的执行过程:
1. 初始化sum;
2. 条件判断通过(这是个永真循环,条件判断永远为true);
3. 判断sum是否为100,结果为否,因此不执行break;
4. sum++;
5. printf;
6. 进行下一次条件判断(永真);
7. 判断sum是否为100,继续为否,不执行break;
8. sum++;
9. printf;
等等。直到sum等于100了,进入循环体内部之后我们的条件判断为真了,因此执行break,直接跳出循环,不再执行随后的sum++和printf.如果把break换成continue,那么sum等于100时执行continue,跳过随后的sum++和printf直接跳到条件判断语句true那里,开始下一次循环(当然你应该意识到在这里把break改成continue是有风险的--这个循环停不下来了!)
循环嵌套
无论是for循环,while循环还是do while循环,它们的内部都是可以嵌套新的循环的。最常见的例子就是多重for循环:
:动手体验:两重for循环
假设有人找你开发一个4人在线打牌的程序,需要你帮助他们实现一个非常简单的自动发牌的功能:4个人打两副扑克牌用108张,要求你用程序模拟正常的抽牌过程:
第1次:
玩家1抽一张
玩家2抽一张
玩家3抽一张
玩家4抽一张
第2次:
玩家1抽一张
玩家2抽一张
玩家3抽一张
玩家4抽一张
……
第27次:
玩家1抽一张
玩家2抽一张
玩家3抽一张
玩家4抽一张
他们给了你一个简单的函数:
void dealCards(int playerId)
{
printf("发一张牌给玩家%d\n", playerId);
}
来体现给编号为playerId的玩家发一张牌的功能。比如,如果你在main函数中输入:
dealCards(1);
那么就会在屏幕上输出:发一张牌给玩家1.
试着运行下面的程序,猜猜它会输出什么?
#include
void dealCards(int playerId)
{
printf("发一张牌给玩家%d\n", playerId);
}
int main()
{
for (int round = 1; round <= 27; round++)
{
printf("第%d轮\n", round);
for (int id = 1; id <= 4; id++)
{
dealCards(id);
}
}
return 0;
}
两重for循环并不比简单的一重for循环要复杂多少。它只是在外层的for循环执行的每一步中完整地又执行了一个新的for循环而已。在上面这个简单的例子中,我们首先写了一个循环27次的外循环,用来模拟发牌的每一轮。在这个循环里,我们要模拟给四个玩家发牌的过程,直观地说,这样写会比较容易理解:
for (int round = 1; round <= 27; round++)
{
printf("第%d轮\n", round);
dealCards(1);
dealCards(2);
dealCards(3);
dealCards(4);
}
在每一轮中,我们干了四件事:分别发牌给玩家1到4号。但是,如果你仔细观察,你会发现这四句话也是一个天然的循环结构:我们只需要用一个循环来遍历1到4这4个自然数就可以了!于是我们又有了第二重循环:
for (int round = 1; round <= 27; round++)
{
printf("第%d轮\n", round);
for (int id = 1; id <= 4; id++)
{
dealCards(id);
}
}
除了两重for循环,你也可以在for循环里嵌套while,或者在while循环里嵌套for,等等。你还可以写出更多重的for循环。不过,如果一个程序员的程序里有连续四五重for循环的话,这段循环很有可能会非常费解,尤其是如果里面还包含了条件判断,continue,break等等语句的话。因此,如果你写出了这样的多重for循环,最好还是考虑把你的程序稍微修改一下。
goto语句
在循环结构的最后,我们介绍一种古老的语句:goto语句。goto语句用来在程序内进行跳转,它具有非常大的灵活性。我们以之前的发牌机为例:
#include
void dealCards(int playerId)
{
printf("发一张牌给玩家%d\n", playerId);
}
int main()
{
int round = 1;
start:
printf("第%d轮\n", round);
dealCards(1);
dealCards(2);
dealCards(3);
dealCards(4);
round++;
if (round <= 27)
goto start;
return 0;
}
这个程序和之前的发牌机程序输出结果是一样的。虽然程序中没有任何循环结构的关键字,但是它实际上执行了一段循环。我们来逐一讲解它和此前的for循环的不同之处:
首先,在进入main函数之后,我们定义了一个round变量,并初始化为1.这一步和for循环的初始化是一样的。
随后,我们定义了一个叫做start的标签:
start:
这个标签没有任何实际含义,只是用来给随后的goto语句定位。我们暂时先忽略它。
接下来程序打印了当前的轮次:
printf("第%d轮\n", round);
并给四个玩家发牌:
dealCards(1);
dealCards(2);
dealCards(3);
dealCards(4);
发牌结束后,我们把round的值自增1:
round++;
这一步是不是很像for循环中的更新语句?
接下来我们去执行一个条件判断:
if (round <= 27)
goto start;
这句话的意思是:如果round还没有超过27轮,我们就跳到到start标签的位置继续执行。也就是说,执行完goto之后,我们继续回到:
printf("第%d轮\n", round);
开始执行,此时的round已经自增为2了,我们接着执行发牌,自增round,判断的过程,知道round自增到了28,这个时候if判断不通过,我们不用goto了,而是直接执行return跳出程序。
如果你把整个程序完整地走一遍,你会发现它其实在干和for循环一样的事情,但是,使用循环语句比使用goto要好懂多了。在大多数情况下,我们强烈建议初学者谨慎使用goto语句。
本章小结
C语言的语句以分号结尾,用花括号括起来的一系列语句被称为一个语句块。
变量分为局部变量和全局变量。变量必须先定义再使用。局部变量的作用域从定义开始到函数结束为止。在语句块内的变量会使得语句块之外的同名变量失效。
顺序结构是最常见的程序结构。printf和scanf用来完成基本的输入输出功能。使用scanf的时候需要传入变量的地址。
判断结构包括if和switch两种。if之后可以带else if或else.如果遇到分支数特别多的情况请使用switch,记得在每个分支结束前加上break.
循环结构包括for循环,while循环和do while循环。for循环适合循环次数已知的情况,while循环适合循环次数未知的情况。如果要求循环必须执行至少一次,请使用do while循环。continue用于执行下一次循环,break用于跳出循环,return可以跳出整个函数。循环之内可以嵌套循环。goto语句用于在程序内进行跳转,慎用goto语句。