写 C 程序时,我们经常要回答两类问题:接下来执行哪段代码?一段代码要重复多少次?前者要用分支,后者要用循环。本文从基本语法讲起,最后写一个成绩统计程序。
0. 语句
C 程序由一条条语句组成,大多数简单语句以分号结束:
c
int score = 90;
printf("score = %d\n", score);
下面会用到 if、switch、for、while、do while,最后再单独讲 goto。花括号包住的一组语句叫语句块。C 允许在只有一条语句时省略花括号,但刚开始学习时最好不要省,少写两个符号换来的往往是更难排查的错误。
1. 分支语句
C 语言没有要求条件表达式必须等于 true 或 false:结果为 0 就是假,非 0 就是真。
1.1 if
c
if (条件) {
/* 条件为真时执行 */
} else {
/* 条件为假时执行 */
}
多重分支示例:
c
if (score >= 90) {
printf("优秀\n");
} else if (score >= 60) {
printf("及格\n");
} else {
printf("不及格\n");
}
这些条件从上往下判断,遇到第一个成立的条件就停下来。这里最容易写错的是等号:= 表示赋值,== 才表示比较。需要同时判断多个条件时,可以用 &&(并且)或 ||(或者)连接。
1.2 switch
如果只是让一个值与几个固定选项比较,switch 通常比一长串 else if 更好读:
c
switch (choice) {
case 1:
printf("开始游戏\n");
break;
case 2:
printf("查看帮助\n");
break;
default:
printf("无效选项\n");
break;
}
break 用于结束当前 switch。忘记它会继续执行下一个 case,称为"贯穿"。下面的程序故意省略第一个 break:
c
#include <stdio.h>
int main(void) {
int choice = 1;
switch (choice) {
case 1:
printf("选择了 1\n");
/* 没有 break,会继续执行 case 2 */
case 2:
printf("执行了 case 2\n");
break;
default:
printf("无效选项\n");
}
return 0;
}
运行结果:
选择了 1
执行了 case 2
如果不是有意让代码贯穿到下一个分支,就要在 case 末尾写 break。switch 常用于整数、字符和枚举,不能直接拿来比较字符串或浮点数。
2. 循环语句
写循环时要看清三件事:从哪里开始、什么时候停、每轮怎样更新。第三项很容易漏掉。一旦条件永远无法变成假,程序就会一直循环。
2.1 for
已经知道大概要循环多少次时,常用 for:
c
for (初始化; 条件; 更新) {
/* 循环体 */
}
c
for (int i = 1; i <= 5; i++) {
printf("%d\n", i);
}
运行结果:
1
2
3
4
5
2.2 while
如果事先不知道要循环多少次,只知道"条件成立就继续",可以用 while:
c
int number;
scanf("%d", &number);
while (number >= 0) {
printf("输入了 %d\n", number);
scanf("%d", &number);
}
例如依次输入 3、1、-1,运行结果为:
输入了 3
输入了 1
2.3 do while
do while 把判断放在循环体之后,所以循环体至少会执行一次:
c
do {
printf("1. 开始 0. 退出\n");
scanf("%d", &choice);
} while (choice != 0);
例如输入 1 后输入 0,菜单会显示两次:
1. 开始 0. 退出
1
1. 开始 0. 退出
0
注意末尾分号不能省略。
break 会立刻结束它所在的循环。比如查找第一个能被 7 整除的数,找到之后便可以停下:
c
for (int i = 1; i <= 100; i++) {
if (i % 7 == 0) {
printf("第一个能被 7 整除的数是:%d\n", i);
break;
}
}
运行结果:
第一个能被 7 整除的数是:7
continue 不会结束整个循环,它只跳过本轮剩下的代码。下面的程序借此跳过偶数,只打印 1 到 10 中的奇数:
c
for (int i = 1; i <= 10; i++) {
if (i % 2 == 0) {
continue; // 偶数跳过,不执行下面的 printf
}
printf("%d ", i); // 输出:1 3 5 7 9
}
printf("\n");
运行结果:
1 3 5 7 9
简单说,break 是"不再循环",continue 是"这一轮先算了"。在 while 循环里使用 continue 时尤其要小心:如果它把变量更新也跳过去了,循环可能永远停不下来。
3. goto 语句
goto 无条件跳转到同一个函数中的标签:
c
goto end;
printf("不会执行\n");
end:
printf("程序结束\n");
运行结果:
程序结束
标签的写法是"名字 + 冒号",而且 goto 只能跳到当前函数里的标签。实际项目偶尔会用它集中处理资源释放,但大量跳转会把执行顺序弄得很难追踪。初学阶段,能用循环或条件语句表达的逻辑,就不必用 goto。
4. 综合实战:成绩统计菜单
现在把前面的语句放进一个完整程序。它先读取学生成绩,然后通过菜单查看所有成绩、平均分和等级统计。
c
#include <stdio.h>
#define MAX 100
int main(void) {
int scores[MAX], count, choice;
printf("学生人数(1-%d):", MAX);
if (scanf("%d", &count) != 1 || count < 1 || count > MAX) {
printf("人数无效\n");
return 1;
}
for (int i = 0; i < count; i++) {
printf("第 %d 位成绩(0-100):", i + 1);
if (scanf("%d", &scores[i]) != 1 || scores[i] < 0 || scores[i] > 100) {
printf("成绩无效\n");
return 1;
}
}
do {
printf("\n1. 所有成绩 2. 平均分 3. 等级统计 0. 退出\n请选择:");
scanf("%d", &choice);
switch (choice) {
case 1:
for (int i = 0; i < count; i++) {
printf("第 %d 位:%d 分\n", i + 1, scores[i]);
}
break;
case 2: {
int sum = 0;
for (int i = 0; i < count; i++) sum += scores[i];
printf("平均分:%.2f\n", (double)sum / count);
break;
}
case 3: {
int excellent = 0, pass = 0, fail = 0;
for (int i = 0; i < count; i++) {
if (scores[i] >= 90) excellent++;
else if (scores[i] >= 60) pass++;
else fail++;
}
printf("优秀:%d 人,及格:%d 人,不及格:%d 人\n",
excellent, pass, fail);
break;
}
case 0:
printf("程序结束\n");
break;
default:
printf("无效选项\n");
}
} while (choice != 0);
return 0;
}
代码逐段解释
#include <stdio.h> 引入标准输入输出功能,程序里的 printf 和 scanf 都来自这个头文件。#define MAX 100 定义了一个符号常量,编译前代码中的 MAX 会被替换成 100。
int scores[MAX], count, choice; 定义了一个整数数组 scores,最多保存 100 个成绩;count 保存学生人数,choice 保存菜单选项。数组下标从 0 开始,因此第一个成绩是 scores[0]。
初学者重点:scanf 和 &
scanf("%d", &count); 从键盘读取一个整数,再把它存进 count。其中,%d 是整数的格式说明符;&count 是变量 count 的内存地址。
为什么这里有一个 &?因为 scanf 需要找到 count 所在的位置,才能把用户输入写进去。printf("%d", count) 只读取 count 的值,所以不需要取地址。
scanf 还会返回成功读到的数据个数。这里仅有一个 %d,返回 1 代表读取成功,返回 0 则说明输入的不是整数。因此,scanf("%d", &count) != 1 检查的正是输入格式。读取成绩时写成 &scores[i],因为这次要把数值存进数组的第 i 个元素。
for (int i = 0; i < count; i++) 会执行 count 次,依次读取所有成绩。提示中使用 i + 1 是为了显示"第 1 位",但数组仍使用从 0 开始的下标。
菜单至少要出现一次,所以这里用了 do...while。用户输入 0 后,choice != 0 为假,循环随即结束。switch (choice) 负责把不同选项分派到对应的 case,末尾的 break 则阻止程序继续落入下一个分支。
计算平均分时不能直接写 sum / count,因为两个整数相除会丢掉小数部分。(double)sum 先把 sum 临时转换成浮点数,计算结果便能保留小数;%.2f 让 printf 显示两位小数。统计人数用到的 excellent++,等价于 excellent = excellent + 1,另外两个计数变量也是同样的道理。
5. 小结与练习
| 场景 | 推荐语句 |
|---|---|
| 根据条件选择 | if |
| 多个固定选项 | switch |
| 重复次数明确 | for |
| 条件成立就继续 | while |
| 至少执行一次 | do while |
| 当前函数内跳转 | goto(谨慎使用) |
练习:
- 用
for输出 1 到 100 之间的所有偶数。 - 用
do while编写"圆面积、矩形面积、退出"菜单。 - 为成绩程序增加最高分和最低分统计。
做练习时别只看答案。先自己写一遍,再故意删掉一个 break、改错一个循环条件,观察程序怎么出问题。流程控制真正难的不是背语法,而是能在运行前判断代码会走到哪里。