It is better to have 100 functions operate on one data structure than 10 functions on 10 data structures. 1
[TOC]
本章将介绍 C 语言的重复语句(迭代语句),这种语句允许用户设置循环。
循环(loop)时重复的执行其他语句(循环体)的一种语句。在 C 语言中,每个循环都有自己的控制表达式(controlling expression)。循环每执行一次,都要对控制表达式求值。如果表达式为真(表达式的值非 0),那么循环继续执行;否则,退出循环。
C 提供了三种迭代语句:
- while 语句
- do 语句(do while 语句)
- for 语句
其中以 for 语句最为常用。
本节后面还会讨论循环相关的 C 语言特性。如 break,continue,goto语句
while 的基本格式如下:
while(控制表达式){
循环体
}
执行 while 语句时,首先计算控制表达式的值。如果值不为零,那么执行循环体,接着再次判定 控制表达式的真值,如果为真,再次执行循环体。直到控制表达式的真值为假,才会结束 while 语句。
**例1-1:**倒计数程序
int i = 10;
while(i > 0){
printf("%d\n", i);
i--;
}
关于这个例子,我们可以对 while 进行深度思考:
-
while 循环终止时,控制表达式的值一定为假。
int i = 10; while(i > 0){ printf("%d\n", i); i--; } printf("%d", i); // i is now 0
-
可能根本不执行 while 循环体。
int i = 0; while(i > 0){ printf("%d\n", i); i--; } // Nothing is printed.
-
while 语句常有多种写法
int i = 10; while(i > 0){ printf("%d\n", i--); // 将 i-- 写在 printf 内,简化循环 }
如果控制表达式的值始终非零,while 循环将无法终止。
while(1){
printf("Hello World\n");
}
除非循环体内有控制循环的语句(break,return,goto)或者调用了导致程序终止的函数,非则上面的循环永远不会结束。
现编写一个程序来显示平方表。首先程序提示用户输入一个数 n,然后显示出 n 行的输出,每行包含 一个 1 ~ n 的数及其平方值。
This program prints a table of squares.
Enter number of entries in table: 5
1 1
2 4
3 9
4 16
5 25
参考答案:
#include<stdio.h>
int main(void){
int n;
int i = 1;
printf("This program prints a table of squares.\n");
printf("Enter number of entries in table: ");
scanf("%d", &n);
while(i <= n){
printf("\t%d\t%d\n", i, i * i);
i++;
}
return 0;
}
This program sums a series of integers.
Enter integers(0 to terminate):8 23 71 5 0
The sum is:107
参考答案:
#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;
}
我写的,仅供参考:
#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): ");
while(scanf("%d", &n) && n){
sum += n;
}
printf("The sum is:%d\n", sum);
return 0;
}
或者 while 循环可以这样写:
int n = 1, sum = 0;
while(n != 0){
scanf("%d", &n);
sum += n;
}
do 语句 和 while 语句其实本质上是相同的。只不过 do 语句至少会执行一次循环体。
基本形式:
do{
循环体
}while(控制表达式);
例2-1倒计数程序
int i = 10;
do{
printf("%d\n", i);
i--;
}while(i > 0);
顺便一提,do 语句最好都加上花括号。
虽然 do while 没有 while 语句使用的那么多,但是前者对于至少需要执行一次的循环来说是十分方便的。
Enter a integer: 60
The number has 2 digit(s).
参考答案:
#include<stdio.h>
int main(void){
int n, count = 0;
printf("Enter a nonnegative integer:");
scanf("%d", &n);
do{
count++;
n /= 10; // 除法 10 的次数就是 n 的位数
}while(n != 0);
printf("The number has %d digit(s).\n", count);
return 0;
}
如果我们用 while 循环:
while(n != 0){
count++;
n /= 10;
}
如果你输入的是 0 ,这个循环会直接退出。这不符合我们的预期,0 也是整数啊,而且它有 1 位数。
现在开始介绍 C 语言最后一种循环,也是功能最强大的一种循环:for 语句。它是我们用的最多的一种循环,一定要熟练掌握。
for(表达式1; 表达式2; 表达式3){
循环体
}
例3-1 倒计数程序
for(i = 10; i > 0; i--){
printf("%d\n", i);
}
在执行上面这个 for 语句时,i 先初始化为 10;然后判定 i 是否大于 0 ;因为结果为真,执行循环体;然后对变量 i 进行自减操作;然后再次判断 i 是否大于 0 ... 直到最后一次 i 自减后,i > 0 不成立了,退出循环。
for 循环如果我们用 while 语句 也可以模拟:
i = 10;
while(i > 0){
printf("%d\n", i);
i--;
}
抽象一下即为:
表达式1;
while(表达式2){
循环体;
表达式3;
}
for 循环中的 i--
可以写成 --i
吗?
即:
for(i = 10; i > 0; --i){
printf("%d\n", i);
}
这种做法对循环没有任何影响。i++
和 i--
在 for 循环的 表达式3 中是完全等价的。
1.省略第一个表达式
i = 10;
for(; i > 0; --i){
printf("%d\n", i);
}
注意:
- 省略第一个表达式相当于不初始化 i
- 即便 第一个表达式省略了,也不能省略后面的分号
2. 省略第三个表达式
for(i = 10; i > 0;){
printf("%d\n", i);
--i
}
3. 省略第一个和第三个表达式
i = 10;
for(; i > 0; ){
printf("%d\n", i);
--i
}
是不是很像我们的 while 语句?
4. 省略第二个表达式
for (; ;) {
printf("Hello\n");
}
省去控制表达式,默认为真,因此 for 语句会不断循环下去。
它相当于:
while (1) {
printf("Hello\n");
}
C99 中第一个表达式可以替换成一个声明:
for(int i = 0; i < 10; i++){
}
变量 i 不需要在该句之前进行声明。事实上,如果变量 i 在之前已经声明了,这个语句会创建一个新的 i 且该值仅用于循环内。(新的 i 会覆盖之前的 i 。for 语句的花括号相当于一个块,新 i 的作用域仅在这个块内。)
例如:
int main(void) {
int i = 20;
for (int i = 0; i < 10; i++) {
printf("%d\n", i);
}
// 循环结束新的 i 应该等于 10
printf("%d\n", i);
return 0;
}
输出:
0
1
2
3
4
5
6
7
8
9
20
最后输出的 20 不符合我们的预期,这说明了一个问题:
旧的 i 始终存在,只不过在 for 语句内,新的 i 相对旧的 i 显“显性”(可以用高中生物的基因来理解一下;或者你就理解为覆盖,但是新的 i 开辟的是新的内存,它不会真的覆盖旧的 i )。
for 语句声明的变量不可以在循环外访问(生存期仅在 for 语句内)。例如:
for (int i = 0; i < 10; i++) {
printf("%d\n", i);
}
printf("%d\n", i);// 在这里编译会报错,"未定义标识符 i"
让 for 语句声明自己的循环控制变量通常是一个好办法:这样方便且程序的可读性更强,但是如果在 for 循环退出后还要使用该变量,则只能使用以前的 for 语句格式。
另外,for 语句可以声明多个变量,只要它们的类型相同:
for(int i = 0, j = 10; i < j; i++, j--){
}
逗号表达式(comma expression)
表达式1,表达式2,表达式3,...,表达式n;
- 逗号表达式的优先级低于所有其他运算符
- 从左向右依次执行:先计算表达式1,然后是表达式2 ...
- 最终整个逗号表达式的值为最后一个表达式的值
例如:
i = 1, j = 2, k = i + j;
相当于是:
((i = 1), (j = 2), (k = i + j));
整个逗号表达式的值为 k = i + j 的值,也就是 3
假如在进入 for 语句时希望初始化两个变量
sum = 0;
for(i = 1; i <= N; i++){
sum += i;
}
改写为:
for(sum = 0, i = 1; i <= N; i++){
sum += i;
}
#include<stdio.h>
int main(void){
int n;
printf("This program prints a table of squares.\n");
printf("Enter number of entries in table: ");
scanf("%d", &n);
for(int i = 1; i <= n; i++){
printf("\t%d\t%d\n", i, i * i);
}
return 0;
}
#include<stdio.h>
int main(void){
int n;
printf("This program prints a table of squares.\n");
printf("Enter number of entries in table: ");
scanf("%d", &n);
for(int i = 1, square = 1, odd = 3; i <= n; i++){
printf("\t%d\t%d\n", i, square);
square += odd;
odd += 2;
}
return 0;
}
有时,循环还没有结束,但我们需要在循环的中间设置退出点,甚至可能需要对循环设置多个退出点。break 语句可以满足这种需求。
continue 语句用来跳过某次迭代的部分内容,但是不会跳过整个循环。
goto 语句允许程序从一条语句直接跳到另一条语句。
break 语句不但可以把程序控制从 switch 语句中转移出来;还可以用于跳出 while,do while,和 for 循环。
假如要编写一个程序判断 n 是否为素数。我们计划是编写一个 for 语句用 n 除以 2 到 n - 1 之间的所有数。一旦发现有约数就跳出循环,没不需要继续尝试下去。循环终止后,可以用 if 判断循环是提前终止(不是素数)还是正常终止(是素数)。
#include<stdio.h>
int main(void){
int n, i;
printf("Enter a number:");
scanf("%d", &n);
for(i = 2; i < n; i++){
if(n % i == 0){
break;
}
}
if(i < n){
printf("%d is not a prime\n", n);
}else{
printf("%d is a prime\n", n);
}
return 0;
}
程序 2 中,要求我们当用户输入 0 时,结束求和。我们用很多中方法去实现了,我们可以用 break 语句让程序的意图更加直观。
#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): ");
while(1){
scanf("%d", &n);
if(n == 0){
break;
}else{
sum += n;
}
}
printf("The sum is:%d\n", sum);
return 0;
}
一个 break 只能跳出一个循环,如果循环嵌套,那么可能需要多个 break 才能从里层的循环跳出。
int i;
while(1){
for(i = 1; i < 10; i++){
if(i == 5){
printf("%d\n", i);
break;
}
}
}
运行一下这个程序,发现,屏幕上一直在输出 5,说明外层的循环没有跳出。
break 语句刚好将程序控制转移到循环体的末尾之后;而 continue 语句刚好将程序控制转移到循环体末尾之前。
break 语句会跳出循环;而 continue 语句会将程序控制留在循环体之内。
其实 continue 的作用就是跳过本次循环点剩下内容,重新开始下一轮循环。
编写一个程序。先提示用户输入一个数 n,然后将 1 ~ n 的所有奇数相加,然后输出最终的和
不使用 continue
#include<stdio.h>
int main(void){
int n, sum = 0;
printf("Enter a number:");
scanf("%d", &n);
for(int i = 1; i <= n; i++){
if(i % 2 != 0){
sum += i;
}
}
printf("Sum of odds is :%d\n", sum);
return 0;
}
使用 continue
只需要改变 for 循环即可
for(int i = 1; i <=n; i++){
if(i % 2 == 0){
continue;
}
sum += i;
}
break 和 continue 语句都是跳转语句:它们可以把控制从程序的一个位置转移到另一个位置。然而,这两者都是受限制的。
goto 语句则可以跳转到函数中任何有标号的语句处。(C99 增加了一条限制:goto 不能用于绕过变长数组(后面的章节会讲)的声明。)
标识符:语句;
goto 标识符;
#include<stdio.h>
int main(void){
int n, i;
printf("Enter a number:");
scanf("%d", &n);
for(i = 2; i < n; i++){
if(n % i == 0){
goto done;
}
}
done:
if(i < n){
printf("%d is not a prime\n", n);
}else{
printf("%d is a prime\n", n);
}
return 0;
}
goto 语句在早期的编程语言中很常见,但是日常 C 语言编程却很少使用它了。break,continue,return 语句(本质上都是受限制的goto 语句)和 exit 函数足以应付大多数需要使用 goto 的情况。
而且 goto 语句不建议滥用,能不用就不用,因为 goto 语句可以打乱程序原本的执行顺序,这大大降低了程序的可读性,提高了改错成本。(这里程序圆是有切身体会的,大一 做C语言的课设的时候,那时候对 C 掌握的并不是很好。我在一个函数内大量的使用 goto 语句,导致我最后都不知道我的 goto 跳到哪里了。)
但是 goto 语句偶尔还是有用的,比如上面的例子:循环嵌套的情况,使用goto 语句就很好跳出多重循环了:
int i;
while(1){
for(i = 1; i < 10; i++){
if(i == 5){
printf("%d\n", i);
goto find;
}
}
}
find:
这个程序帮你理解一种简单的交互式程序设计,我们可以通过这种方式设计菜单。
题目:
开发一个程序用来维护账簿的余额。程序将为用户提供选择菜单:清空余额账户,向账户存钱,从账户取钱,显示当前余额,退出程序。选项用 0,1,2,3,4表示。程序的会话类似这样:
**** ACME checkbook-balancing program ****
Comands: 0 = clear, 1 = credit, 2 = debit, 3 = balance, 4 = exit
Enter command: 1
Enter amount of credit: 1042.56
Enter command: 2
Enter amount of debit : 133.56
Enter command: 3
Current balance: 909
Ener command: 4
Goodbye~
参考程序:
#include<stdio.h>
int main(void) {
double balance = 0; // 余额
double credit, debit;
// 菜单,形式可以自己设计,尽量美观一点嘛,不过不用纠结这种界面,不要舍本逐末。
printf("**** ACME checkbook-balancing program ****\n");
printf(" Comands: \n");
printf(" 0 = clear \n");
printf(" 1 = credit \n");
printf(" 2 = debit \n");
printf(" 3 = balance \n");
printf(" 4 = exit \n");
// 题目中已经规定了这些功能用 0,1,2,3,4 代替,其实是想让我们用 switch
// 如果你想用 if else 也完全 ok
// 死循环让用户可以重复选择
while (1) {
int choice;
printf("Enter command: ");
scanf("%d", &choice);
switch (choice) {
// 清除账户是一种很“危险”的举动,可以让用户确认一次
case 0: printf("Are you sure to clear your balance?\n");
printf("1 = yes, 0 = no\n");
int isClear;
scanf("%d", &isClear);
if (isClear == 1) {
balance = 0;
printf("clear successfully!\n");
}
break;
case 1: printf("Enter amount of credit: ");
scanf("%lf", &credit);
balance += credit;
break;
case 2: printf("Enter amount of debit : ");
scanf("%lf", &debit);
balance -= debit;
break;
case 3: printf("Current balance: %.2f\n", balance);
break;
case 4: printf("Are you sure to quit?\n");
printf("1 = yes, 0 = no\n");
int isQuit;
scanf("%d", &isQuit);
if (isQuit == 1) {
printf("Goodbye~\n");
return 0;
}
else {
break;
}
default: printf("Illeagl option!\n");
break;
}
}
}
i = 0; ; j = 1;
第二个语句就是一个空语句:除了末尾的分号,什么符号也没有。
空语句主要有一个好处:编写空循环体的循环。
判断素数的循环我们可以这样改写:
for(d = 2; d <= n && n % d != 0; d++)
;
注意不要写成:
for(d = 2; d <= n && n % d != 0; d++) ;
程序员习惯将空语句单独放在一行,否则,一般人阅读程序时可能会混淆后面的语句是否是其循环体。
for(d = 2; d <= n && n % d != 0; d++) ;
if(d < n)
printf("%d is divisible by %d", n, d);
一般情况下,将普通循环转化为带空循环体的语句不会提高效率,但是会让程序更加简洁。
但是在一些情况下,可能带空循环体的循环会更加高效。如:读取字符数据时(后面会讲)。
1.if 语句
if(d == 0);
printf("Error: division by zero\n");
如果输入的 d 不是 0,后面的这条消息一样会被打印。
2.while 语句
i = 10;
while(i > 0);{
printf("%d\n", i);
i--;
}
程序会进入死循环,因为 while 没有循环体,而 i 的的值不会减小。
i = 11;
while(--i > 0);{
printf("%d\n", i);
i--;
}
循环终止,但是花括号内的语句只被执行一次。
输出:0
3. for 语句
for(i = 10; i > 0; i--);
printf("%d\n", i);
printf 语句被执行一次
输出:0
参考资料:《C Primer Plus》《C语言程序设计:现代方法》
Footnotes
-
用100个函数操作一个数据结构比仅用10个函数但是操作10个不同的数据结构要好。Epigrams on Programming 编程警句 ↩