循环(loop)是重复执行其他语句(循环体)的一种语句。在C语言中,每个循环都有一个控制表达式(controllingexpression)。每次执行循环体(循环重复一次)时都要对控制表达式求值。如果表达式为真(即值不为零),那么继续执行循环。
C语言提供了3种重复语句,即while语句、do语句和for语句。while循环在循环体执行之前测试控制表达式,do循环在循环体执行之后测试控制表达式,for语句则非常适合那些递增或递减计数变量的循环。
break语句用来跳出循环并把程序控制传递到循环后的下一条语句,continue语句用来跳过本次循环的剩余部分,而goto语句则可以跳到函数内的任何语句上。
while语句
在C语言所有设置循环的方法中,while语句是最简单也是最基本的。while语句的格式如下所示:
[while语句] while (表达式) 语句
圆括号内的表达式是控制表达式,圆括号后边的语句是循环体。下面是一个示例:
while (i < n) /* controlling expression */
i = i * 2; /* loop body */
注意,这里的圆括号是强制要求的,而且在右括号和循环体之间没有任何内容。
执行while语句时,首先计算控制表达式的值。如果值不为零(即真值),那么执行循环体,接着再次判定表达式。这个过程(先判定控制表达式,再执行循环体)持续进行直到控制表达式的值变为零才停止。
下面的例子使用while语句计算大于或等于数n的最小的2的幂:
i = 1;
while (i < n)
i = i * 2;
假设n的值为10。下面的跟踪显示了while语句执行时的情况:
- i = 1;i现在值为1。
- i < n成立吗? 是的,继续。
- i = i * 2;i现在值为2。
- i < n成立吗? 是的,继续。
- i = i * 2;i现在值为4。
- i < n成立吗? 是的,继续。
- i = i * 2;i现在值为8。
- i < n成立吗? 是的,继续。
- i = i * 2;i现在值为16。
- i < n成立吗? 不成立,退出循环。
注意,只有在控制表达式i < n为真的情况下循环才会继续。当表达式值为假时,循环终止,而且就像描述的那样,此时i的值是大于或等于n的。
虽然循环体必须是单独的一条语句,但这只是个技术问题;如果需要多条语句,那么只要用一对花括号构造成一条复合语句就可以了。
while (i > 0) {
printf("T minus %d and counting\n", i);
i--;
}
即使在没有严格要求的时候,一些程序员也总是使用花括号:
while (i < n) { /* braces allowed, but not required */
i = i * 2;
}
再看一个跟踪语句执行的示例。下面的语句显示一串“倒计数”信息。
i = 10;
while (i > 0) {
printf("T minus %d and counting\n", i);
i--;
}
在while语句执行前,把变量i赋值为10。因为10大于0,所以执行循环体,这导致显示出信息T minus 10 and counting,同时变量i进行自减。然后再次判定条件i > 0。因为9大于0,所以再次执行循环体。整个过程持续到显示信息T minus 1 and counting,并且变量i的值变为0时停止。然后判定条件i > 0的结果为假,导致循环终止。
“倒计数”的例子可以引发对while语句的一些讨论。
- 在while循环终止时,控制表达式的值为假。因此,由表达式i> 0控制的循环终止时,i一定是小于或等于0的。(否则还将执行循环!)
- 可能根本不执行while循环体。因为控制表达式在循环体执行之前进行判定,所以循环体有可能一次也不执行。第一次进入倒计数循环时,如果变量i的值是负数或零,那么将不会执行循环。
- while语句常常可以有多种写法。例如,我们可以在printf函数调用的内部进行变量i的自减操作,这样可以使倒计数循环更加简洁:
while (i > 0)
printf("T minus %d and counting\n", i--);
无限循环
如果控制表达式的值始终非零,while语句将无法终止。事实上,C程序员有时故意用非零常量作为控制表达式来构造无限循环:
[惯用法] while (1)…
除非循环体中含有跳出循环控制的语句(break、goto、return)或者调用了导致程序终止的函数,否则上述形式的while语句将永远执行下去。
程序 显示平方表
现在编写一个程序来显示平方表。首先程序提示用户输入一个数,然后显示出行的输出,每行包含一个1~的数及其平方值。
This program prints a table of squares.Enter number of entries in table: 5
1 1
2 4
3 9
4 16
5 25
把期望的平方数个数存储在变量n中。程序需要用一个循环来重复显示数i和它的平方值,循环从i等于1开始。如果i小于或等于n,那么循环将反复进行。需要保证的是每次执行循环时对i值加1。
可以使用while语句编写循环。(坦白地说,现在没有其他更多的选择,因为while语句是目前为止我们唯一掌握的循环类型。)下面是完成的程序。
square.c
#include <stdio.h>
int main(void){
int i, n;
printf("This program prints a table of squares.\n");
printf("Enter number of entries in table: ");
scanf("%d", &n);
i = 1;
while (i <= n) {
printf("%10d%10d\n", i, i * i);
i++;
}
return 0;
}
留意一下程序square.c是如何把输出整齐地排成两列的。窍门是使用类似%10d这样的转换说明代替%d,并利用了printf函数在指定宽度内将输出右对齐的特性。
程序 数列求和
在下面这个用到while语句的示例中,我们编写了一个程序对用户输入的整数数列进行求和计算。下面显示的是用户能看到的内容:
This program sums a series of integers.
Enter integers (0 to terminate): 8 23 71 5 0
The sum is: 107
很明显,程序需要一个循环来读入数(用scanf函数)并将其累加。用n表示当前读入的数,而sum表示所有先前读入的数的总和,得到如下程序:
sum.c
#include <stdio.h>
int main(void){
int n, sum = 0;
printf("This program sums a series of integers.\n");
printf("Enter integers (0 to terminate): ");
scanf("%d", &n);
while (n != 0) {
sum += n;
scanf("%d", &n);
}
printf("The sum is: %d\n", sum);
return 0;
}
注意,条件n != 0在数被读入后立即进行判断,这样可以尽快终止循环。此外,程序中用到了两个完全一样的scanf函数调用,在使用while循环时往往很难避免这种现象。
do语句
do语句和while语句关系紧密。事实上,do语句本质上就是while语句,只不过其控制表达式是在每次执行完循环体之后进行判定的。do语句的格式如下所示:
[do语句] do 语句 while (表达式);
和处理while语句一样,do语句的循环体也必须是一条语句(当然可以用复合语句),并且控制表达式的外面也必须有圆括号。
执行do语句时,先执行循环体,再计算控制表达式的值。如果表达式的值是非零的,那么再次执行循环体,然后再次计算表达式的值。在循环体执行后,若控制表达式的值变为0,则终止do语句的执行。
下面使用do语句重写前面的“倒计数”程序:
i = 10;
do {
printf("T minus %d and counting\n", i);
--i;
} while (i > 0);
执行do语句时,先执行循环体,这导致显示出信息T minus 10and counting,并且i自减。接着对条件i > 0进行判定。因为9大于0,所以再次执行循环体。这个过程持续到显示出信息T minus1 and counting并且i的值变为0。此时判定表达式i > 0的值为假,所以循环终止。正如此例中显示的一样,do语句和while语句往往没有什么区别。两种语句的区别是,do语句的循环体至少要执行一次,而while语句在控制表达式初始为0时会完全跳过循环体。
顺便提一下,无论需要与否,最好给所有的do语句都加上花括号,这是因为没有花括号的do语句很容易被误认为是while语句:
do
printf("T minus %d and counting\n", i--);
while (i > 0);
粗心的读者可能会把单词while误认为是while语句的开始。
程序 计算整数的位数
虽然C程序中while语句的出现次数远远多于do语句,但是后者对于至少需要执行一次的循环来说是非常方便的。为了说明这一点,现在编写一个程序计算用户输入的整数的位数:
Enter a nonnegative integer: <u>60</u>
The number has 2 digit(s).
方法是把输入的整数反复除以10,直到结果变为0停止;除法的次数就是所求的位数。因为不知道到底需要多少次除法运算才能达到0,所以很明显程序需要某种循环。但是应该用while语句还是do语句呢?
do语句显然更合适,因为每个整数(包括0)都至少有一位数字。下面是程序。
numdigit.c
/* Calculates the number of digits in an integer */
#include <stdio.h>
int main(void){
int digits = 0, n;
printf("Enter a nonnegative integer: ");
scanf("%d", &n);
do {
n /= 10;
digits++;
} while (n > 0);
printf("The number has %d digit(s).\n", digits);
return 0;
}
为了说明do语句是正确的选择,下面观察一下如果用相似的while循环替换do循环会发生什么:
while (n > 0) {
n /= 10;
digits++;
}
如果n初始值为0,上述循环根本不会执行,程序将打印出
The number has 0 digit(s).
for语句
现在开始介绍C语言循环中最后一种循环,也是功能最强大的一种循环:for语句。不要因为for语句表面上的复杂性而灰心;实际上,它是编写许多循环的最佳方法。for语句非常适合应用在使用“计数”变量的循环中,当然它也可以灵活地用于许多其他类型的循环中。
for语句的格式如下所示:
[for语句格式]for (表达式1; 表达式2; 表达式3) 语句
其中表达式1、表达式2和表达式3全都是表达式。下面是一个例子:
for (i = 10; i > 0; i--)
printf("T minus %d and counting\n",i);
在执行for语句时,变量i先初始化为10,接着判定i是否大于0。因为判定的结果为真,所以打印信息T minus 10 and counting,然后变量i进行自减操作。随后再次对条件i > 0进行判定。循环体总共执行10次,在这一过程中变量i从10变化到1。
for语句和while语句关系紧密。事实上,除了一些极少数的情况以外,for循环总可以用等价的while循环替换:
表达式1;
while (表达式2) {
语句表达式3;
}
就像这个模式显示的那样,表达式1是循环开始执行前的初始化步骤,只执行一次;表达式2用来控制循环的终止(只要表达式2的值不为零,循环持续执行);而表达式3是每次循环中最后被执行的一个操作。把这种模式用于先前的for循环示例中,可以得到:
i = 10;
while (i > 0) {
printf("T minus %d and counting\n", i);
i--;
}
研究等价的while语句有助于更好地理解for语句。例如,假设把先前的for循环示例中的i–用–i来替换:
for (i = 10; i > 0; --i)
printf("T minus %d and counting\n", i);
这样做会对循环产生什么样的影响呢?看看等价的while循环就会发现,这种做法对循环没有任何影响:
i = 10;
while (i > 0) {
printf("T minus %d and counting\n", i);
--i;
}
因为for语句中第一个表达式和第三个表达式都是以语句的方式执行的,所以它们的值互不相关——它们有用仅仅是因为有副作用。结果是,这两个表达式常常作为赋值表达式或自增/自减表达式。
for语句的惯用法
对于“向上加”(变量自增)或“向下减”(变量自减)的循环来说,for语句通常是最好的选择。对于向上加或向下减共n次的情况,for语句经常会采用下列形式中的一种。
-
从0向上加到n-1:
[惯用法]for ( i = 0; i < n; i++) ...
-
从1向上加到n:
[惯用法]for ( i = 1; i <= n; i++) ...
-
从n-1向下减到0:
[惯用法]for ( i = n - 1; i >= 0; i--) ...
-
从n向下减到1:
[惯用法]for ( i = n; i > 0; i--) ...
模仿这些书写格式将有助于避免C语言初学者常犯的下面一些错误。
- 在控制表达式中把>写成<(或者相反)。注意,“向上加”的循环使用运算符<或运算符<=,而“向下减”的循环则依赖于运算符>或运算符>=。
- 在控制表达式中把<、<=、>或>=写成==。控制表达式的值在循环开始时应该为真,以后会变为假以便能终止循环。类似i== n这样的判定没什么意义,因为它的初始值不为真。
- 编写的控制表达式中把i < n写成i < =n,这会犯“循环次数差一”错误。
在for语句中省略表达式
for语句远比目前看到的更加灵活。通常for语句用三个表达式控制循环,但是有一些for循环可能不需要这么多,因此C语言允许省略任意或全部的表达式。
如果省略第一个表达式,那么在执行循环前没有初始化的操作:
i = 10;
for (; i > 0; --i)
printf("T minus %d and counting\n", i);
在这个例子中,变量i由一条单独的赋值语句实现了初始化,所以在for语句中省略了第一个表达式。(注意,保留第一个表达式和第二个表达式之间的分号。即使省略掉某些表达式,控制表达式也必须始终有两个分号。)
如果省略了for语句中的第三个表达式,循环体需要确保第二个表达式的值最终会变为假。我们的for语句示例可以这样写:
for (i = 10; i > 0;)
printf("T minus %d and counting\n", i--);
为了补偿省略第三个表达式产生的后果,我们使变量i在循环体中进行自减。
当for语句同时省略掉第一个和第三个表达式时,它和while语句没有任何分别。例如,循环
for (; i > 0;)
printf("T minus %d and counting\n", i--);
等价于
while (i > 0)
printf("T minus %d and counting\n", i--);
这里while语句的形式更清楚,也因此更可取。如果省略第二个表达式,那么它默认为真值,因此for语句不会终止(除非以某种其他形式停止)。例如,某些程序员用下列的for语句建立无限循环:
[惯用法]for ( ; ; )...
break语句
前面已经讨论过break语句把程序控制从switch语句中转移出来的方法。break语句还可以用于跳出while、do或for循环。
假设要编写一个程序来测试数n是否为素数。我们的计划是编写一个for语句用n除以2到n-1之间的所有数。一旦发现有约数就跳出循环,而不需要继续尝试下去。在循环终止后,可以用一个if语句来确定循环是提前终止(因此n不是素数)还是正常终止(n是素数):
for (d = 2; d < n; d++)
if (n % d == 0)
break;
if (d < n)
printf("%d is divisible by %d\n", n, d);
else printf("%d is prime\n", n);
对于退出点在循环体的中间而不是循环体之前或之后的情况,break语句特别有用。读入用户输入并且在遇到特殊输入值时终止的循环常常属于这种类型:
for (;;) {
printf("Enter a number (enter 0 to stop): ");
scanf("%d", &n);
if (n == 0)
break;
printf("%d cubed is %d\n", n, n * n * n);
}
break语句把程序控制从包含该语句的最内层while、do、for或switch语句中转移出来。因此,当这些语句出现嵌套时,break
语句只能跳出一层嵌套。思考一下switch语句嵌在while语句中的情况:
while (...) {
switch (...) {
...
break;
...
}
}
break语句可以把程序控制从switch语句中转移出来,但是却不能跳出while循环。后面会继续讨论这一点。
continue语句
continue语句其实不应该放在这里,因为它无法跳出循环。但由于它和break类似,所以将其放在本节也不完全是随意而为的。break语句刚好把程序控制转移到循环体末尾之后,而continue语句刚好把程序控制转移到循环体末尾之前。用break语句会使程序控制跳出循环,而continue语句会把程序控制留在循环内。break语句和continue语句的另外一个区别是:break语句可以用于switch语句和循环(while、do和for),而continue语句只能用于循环。
下面的例子通过读入一串数并求和的操作说明了continue语句的简单应用。循环在读入10个非零数后循环终止。无论何时读入数0都执行continue语句,控制将跳过循环体的剩余部分(即语句sum +=i;和语句n++;)但仍留在循环内。
n = 0;
sum = 0;
while (n < 10) {
scanf("%d", &i);
if (i == 0)
continue;
sum += i;
n++;
/* continue jumps to here */
}
如果不用continue语句,上述示例可以写成如下形式:
n = 0;
sum = 0;
while (n < 10) {
scanf("%d", &i);
if (i != 0) {
sum += i;
n++;
}
}
程序 账簿结算
许多简单的交互式程序都是基于菜单的:它们向用户显示可供选择的命令列表;一旦用户选择了某条命令,程序就执行相应的操作,然后提示用户输入下一条命令;这个过程一直会持续到用户选择“退出”或“停止”命令。
这类程序的核心显然是循环。循环内将有语句提示用户输入命令,读命令,然后确定执行的操作:
for (; ;) {
提示用户录入命令;
读入命令;
执行命令;
}
执行这个命令将需要switch语句(或者级联式if语句):
for (; ;) {
提示用户录入命令;
读入命令;
switch(命令){
case 命令1:执行操作1; break;
case 命令2:执行操作2; break;
. . .
case 命令n:执行操作n; break;
default: 显示错误消息; break;
}
}
为了说明这种格式,开发一个程序用来维护账簿的余额。程序将为用户提供选择菜单:清空账户余额,往账户上存钱,从账户上取钱,显示当前余额,退出程序。选择项分别用整数0、1、2、3和4表示。程序会话类似这样:
*** ACME checkbook-balancing program ***
Commands:
0=clear, 1=credit, 2=debit, 3=balance, 4=exitEnter
command: 1
Enter amount of credit: 1042.56
Enter command: 2
Enter amount of debit: 133.79
Enter command: 1
Enter amount of credit: 1754.32
Enter command: 2
Enter amount of debit: 1400
Enter command: 2
Enter amount of debit: 68
Enter command: 2
Enter amount of debit: 50
Enter command: 3
Current balance: $1145.09
Enter command: 4
当用户录入命令4(即退出)时,程序需要从switch语句以及外围的循环中退出。break语句不可能做到,同时我们又不想使用goto语句。因此,决定采用return语句,这条语句将可以使程序终止并且返回操作系统。
评论区