导学案3 · 判断:让程序学会做选择¶
前置要求:导学案2(会用 printf / scanf、会声明变量、知道整数除法的坑) 参考课程:浙江大学 翁恺《C 语言程序设计》第 3 章(判断、多路分支 switch) 预计用时:1.5小时(按需自取) 本节目标:让程序学会"看情况办事"——if / else if / else、
&&||!、switch
3.0 序¶
到目前为止,你写的程序都是从上往下,一条道走到黑:每一行都会执行,一个都不落。
但真实的程序必须能看情况办事:
- 生活里:如果下雨,就带伞,否则不带。
- 实验室里:如果超声波测到的距离小于 20 cm,就让电机停转,否则继续转。
程序设计的三大基本结构是:顺序(导学案1、2)、选择(本节)、循环(导学案4)。有一句很有名的结论:任何逻辑都能用这三种结构拼出来,不需要别的东西。本节讲第二种。
本节路线:关系运算与真假 → if / else → else if 多分支 → 逻辑运算 → switch → 练习
3.1 关系运算与真假¶
3.1.1 判断的结果,也是一个值¶
先破除一个心理障碍:"3 比 5 大吗"这个问句,在 C 语言里是有答案的,而且答案就是一个数。
#include <stdio.h>
int main(void)
{
printf("%d\n", 3 > 5);
printf("%d\n", 3 < 5);
printf("%d\n", 3 == 3);
printf("%d\n", 3 != 3);
return 0;
}
输出:
一点都不玄:成立就是 1,不成立就是 0。
六个关系运算符:
| 运算符 | 意思 | 例子 | 结果 |
|---|---|---|---|
> |
大于 | 3 > 5 |
0 |
>= |
大于等于 | 3 >= 3 |
1 |
< |
小于 | 3 < 5 |
1 |
<= |
小于等于 | 5 <= 3 |
0 |
== |
等于(两个等号) | 3 == 3 |
1 |
!= |
不等于 | 3 != 3 |
0 |
- 没有
=<和=>这种写法,必须是<=和>=。
3.1.2 本节最重要的警示:== 与 =¶
一个等号是赋值(把右边的值放进左边的盒子),两个等号才是判断相等。写错了编译器不一定拦得住:
/* 反面示例:能编译,逻辑全错 */
int a = 5;
if (a = 0) { /* 这是赋值!把 a 改成 0,然后判断 0 —— 0 是假,永远不成立 */
printf("a 是 0\n");
}
if (a == 0) { /* 这才是"判断 a 是否等于 0" */
printf("a 是 0\n");
}
第一句执行完,a 已经被你亲手改成 0 了,而 if 判断的是"赋值表达式的结果"也就是 0,于是花括号里的内容永远不执行。这种 bug 不报错、不崩溃,只是悄悄地什么都不做,是最难查的一类。
3.2 if 与 if-else¶
3.2.1 骨架¶
else 部分可以省略。
#include <stdio.h>
int main(void)
{
int score = 0;
printf("请输入成绩:");
scanf("%d", &score);
if (score >= 60) {
printf("及格了\n");
} else {
printf("不及格,回去补\n");
}
return 0;
}
输入 75:
3.2.2 三个坑¶
坑 1:if 后面多写一个分号。 这是 C 语言最阴的坑之一,因为它完全合法,不报错:
分号本身就是一条"什么也不做"的空语句。if 认为它要管的就是这一句,管完就结束了;后面花括号里的内容成了普通代码,无论成绩多少都会打印"及格了"。
坑 2:省略花括号。 如果 if 后面只有一条语句,花括号可以省:
语法上没问题,但本实验室要求你永远写上花括号。理由很实在:某天你在下面又加了一句,忘了补花括号,那句就会无条件执行——和坑 1 一模一样的 bug。
3.3 else if:多分支¶
要分成好几档时,用 else if 串起来。看例子电池电压分级:
#include <stdio.h>
int main(void)
{
double v = 0.0;
printf("请输入电池电压(V):");
scanf("%lf", &v);
if (v <= 0.0) {
printf("数值不正常,检查接线和测量\n");
} else if (v > 12.6) {
printf("满电\n");
} else if (v >= 11.8) {
printf("电量正常\n");
} else if (v >= 11.2) {
printf("电量偏低,需要充电\n");
} else {
printf("低电量保护,停止执行任务!\n"); // 没错,曾经因为掉电保护真实坠机过
}
return 0;
}
几个输入的实测结果:
| 输入 | 输出 |
|---|---|
12.7 |
满电 |
12.3 |
电量正常 |
11.5 |
电量偏低,需要充电 |
10.7 |
低电量保护,停止执行任务! |
-1 |
数值不正常,检查接线和测量 |
关键点:else if 是从上往下逐条判断,命中一条就执行它、然后整串结束。 所以第三个分支只写了 v >= 11.8,不用写 v >= 11.8 && v <= 12.6——能走到这里,说明上面的条件都不成立,v > 12.6 已经被排除了。顺序很重要,把范围窄的、优先级高的条件写在前面。
3.3.1 else 到底和哪个 if 配对?¶
就近原则:else 永远和它上面最近的、还没配对的 if 绑定,跟缩进无关(# 有些同学中学学了Python,Python是一门逻辑与缩进强相关的语言,在这里可能会弄混)。
想让它属于外层 if,就得加花括号。永远写花括号,就不会有这个问题。
3.3.2 关于嵌套¶
if 里面可以再套 if,语法上没有限制。
3.4 逻辑运算:把条件组合起来¶
| 运算符 | 意思 | 例子 |
|---|---|---|
&& |
并且,两边都真才真 | a > 0 && a < 10 |
|| |
或者,有一边真就真 | c == 'y' || c == 'Y' |
! |
非,真变假、假变真 | !(a > 0) |
真值表(1 表示真,0 表示假):
| a | b | a && b |
a || b |
|---|---|---|---|
| 0 | 0 | 0 | 0 |
| 0 | 1 | 0 | 1 |
| 1 | 0 | 0 | 1 |
| 1 | 1 | 1 | 1 |
3.4.1 经典例子:判断闰年¶
规则是:能被 4 整除但不能被 100 整除,或者能被 400 整除。
#include <stdio.h>
int main(void)
{
int year = 0;
printf("请输入年份:");
scanf("%d", &year);
if ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0) {
printf("%d 是闰年\n", year);
} else {
printf("%d 是平年\n", year);
}
return 0;
}
顺便学一个宝贵的习惯:写完就列测试用例,尤其是边界。
| 输入 | 应该是 | 为什么选它 |
|---|---|---|
2024 |
闰年 | 普通闰年 |
2023 |
平年 | 普通平年 |
1900 |
平年 | 能被 100 整除但不能被 400 整除,最容易写错的一档 |
2000 |
闰年 | 能被 400 整除 |
3.4.2 必错写法:18 < age < 60¶
数学里这么写天经地义,甚至在部分语言如Python中也成立,但在 C 语言里,它恒为真:
为什么?C 语言从左往右算:先算 18 < age,18 < -100 不成立,得到 0;再拿这个 0 去和 60 比,0 < 60 成立,于是整个式子是 1(真)。换成 age = 1000 也一样:18 < 1000 得 1,1 < 60 成立。无论 age 是多少,结果都是真。
正确写法:
3.4.3 两点补充¶
- 短路求值:
&&只要左边为假,右边根本不会被计算;||只要左边为真,右边也不会被计算。为什么要讲这个,因为比如说某个逻辑表达式右侧存在i++的表达式,那么这个 i 是否自增就与逻辑连接词左侧表达式的真值有关了,可能被增加,也可能不增加,取决于是否被短路,这不是一个好现象。 &、|、^(都只有一个符号)是位运算,长得像但不是一回事,别混淆。
3.5 switch:多个等值判断的整齐写法¶
开门见山:switch 不是 if 的替代品,它是"从若干个固定值里挑一个"的整齐写法。如果条件是范围(大于、小于、区间),只能用 if。
#include <stdio.h>
int main(void)
{
int day = 0;
printf("请输入 1~7:");
scanf("%d", &day);
switch (day) {
case 1: printf("星期一\n"); break;
case 2: printf("星期二\n"); break;
case 3: printf("星期三\n"); break;
case 4: printf("星期四\n"); break;
case 5: printf("星期五\n"); break;
case 6: printf("星期六\n"); break;
case 7: printf("星期日\n"); break;
default: printf("输入有误,应该是 1~7\n"); break;
}
return 0;
}
输入 6:
3.5.1 必须知道的三件事¶
一、case 后面必须是"常量",可以是整数、字符;不能是范围(case 1~5: 这种写法根本不存在)、不能是小数、不能是变量。所以想判断"成绩在 90 到 100 之间",只能用 if。
二、break 不能忘。 不写 break,程序会穿透到下一个 case 继续执行:
/* 反面示例:漏写 break */
switch (day) {
case 1: printf("星期一\n"); /* 没有 break */
case 2: printf("星期二\n"); /* 输入 1 也会打印这一行 */
default: printf("其他\n"); /* 然后继续打印这一行 */
}
输入 1 会一口气打印三行。穿透偶尔是老手有意为之的技巧,但现阶段请一律写 break。
三、default 相当于 else,建议永远写上,哪怕只打印一句"输入有误"。它是你抓住意外输入的最后一条网。
3.5.2 if 还是 switch?¶
| 你要判断的是 | 用哪个 |
|---|---|
| 等值:从 1、2、3 里挑一个 | 都行,switch 更整齐 |
| 范围:大于、小于、区间 | 只能 if |
| 小数、字符串 | 只能 if |
3.6 综合练习¶
本节练习用于自练与自查,不强制完成、不需要提交;需要提交的是实验室统一布置的作业(作业一 / 作业二)。 参考代码统一放在本节末尾,做完之前不要翻。
练习 1|三个数从大到小¶
读入三个整数,用 if 和交换,把它们从大到小输出。
练习 2|简易计算器(回收上次的崩溃)¶
还记得导学案2练习 2 里,你故意输入 b = 0 时程序崩溃的事吗? 现在你有能力收拾这个烂摊子了。
读入一个算式,例如 12 + 5,用 switch 按运算符分派,输出结果(保留两位小数)。除数为 0 时给出友好提示,而不是崩溃。
提示:
scanf("%lf %c %lf", &a, &op, &b);可以一次读进"数、运算符、数",注意%c前面的空格(导学案2 2.4)。
练习 3|BMI 计算与分级¶
读入身高(m)和体重(kg),输出 BMI(保留一位小数)和分级。
公式:BMI = 体重 / (身高 * 身高);分级:小于 18.5 偏瘦,18.5 ~ 24 正常,24 ~ 28 偏胖,28 及以上肥胖。
提示:这是
else if的典型场景,注意分支的顺序和"可以省略下界"这个特点。身高为 0 或负数要先拦下来,否则会出现除以 0。
练习 4|纠错¶
下面这段代码有 4 处错误,请修正。
/* 反面示例:有 4 处错误 */
#include <stdio.h>
int main(void)
{
int score = 0;
printf("请输入成绩:");
scanf("%d", score);
if (score >= 60);
{
printf("及格\n");
}
if (score = 100) {
printf("满分\n");
}
switch (score / 10) {
case 10: printf("A\n");
case 9: printf("B\n");
default: printf("C\n");
}
return 0;
}
3.8 练习参考代码(做完再看)¶
练习 1 参考代码¶
#include <stdio.h>
int main(void)
{
int a = 0, b = 0, c = 0;
int t = 0;
printf("请输入三个整数:");
scanf("%d %d %d", &a, &b, &c);
if (a < b) { t = a; a = b; b = t; }
if (a < c) { t = a; a = c; c = t; }
if (b < c) { t = b; b = c; c = t; }
printf("从大到小:%d %d %d\n", a, b, c);
return 0;
}
为了省地方,这里把交换写在一行里。自己写的时候建议展开成三行,更清楚。
练习 2 参考代码¶
#include <stdio.h>
int main(void)
{
double a = 0.0, b = 0.0;
char op = ' ';
printf("请输入算式,例如 12 + 5:");
scanf("%lf %c %lf", &a, &op, &b);
switch (op) {
case '+': printf("%.2f\n", a + b); break;
case '-': printf("%.2f\n", a - b); break;
case '*': printf("%.2f\n", a * b); break;
case '/':
if (b == 0.0) {
printf("除数不能为 0\n");
} else {
printf("%.2f\n", a / b);
}
break;
default: printf("不认识的运算符:%c\n", op); break;
}
return 0;
}
这里用
b == 0.0判断除数为 0,看着和导学案2 2.5 节"不要用 == 比较小数"的结论矛盾。 其实不矛盾:这个b是用户直接敲进来的,要么就是0,要么就不是。只有经过计算得来的小数才不能用==比。 更保险的写法是判断b > -0.000001 && b < 0.000001。
练习 3 参考代码¶
#include <stdio.h>
int main(void)
{
double h = 0.0, w = 0.0, bmi = 0.0;
printf("请输入身高(m)和体重(kg):");
scanf("%lf %lf", &h, &w);
if (h <= 0.0 || w <= 0.0) {
printf("输入不正常,请检查\n");
} else {
bmi = w / (h * h);
printf("BMI = %.1f\n", bmi);
if (bmi < 18.5) {
printf("偏瘦\n");
} else if (bmi < 24.0) {
printf("正常\n");
} else if (bmi < 28.0) {
printf("偏胖\n");
} else {
printf("肥胖\n");
}
}
return 0;
}
练习 6 参考答案¶
| # | 错在哪 | 怎么改 |
|---|---|---|
| 1 | scanf 漏了 & |
scanf("%d", &score); |
| 2 | if 后面多了个分号,花括号里的内容变成无条件执行 |
删掉那个分号 |
| 3 | 用 = 赋值当成了判断,还顺手把 score 改成了 100 |
if (score == 100) |
| 4 | case 10 漏了 break,会穿透打印 A、B、C 三行 |
每个 case 后补 break; |
3.7 本节小结与自查¶
学完本节,你应该能做到:
- 说出关系运算的结果是什么,C 语言里"真"和"假"分别对应什么
- 写
if/else/else if,并解释为什么else if分支可以省略下界 - 解释
if (a = 0)、if (a == 0)、if (a == 0);三者的区别 - 用
&&||!组合条件,并解释18 < age < 60为什么恒为真 - 知道
switch适合什么情况、漏写break会怎样、default有什么用 - 写完一段判断逻辑后,能自己列出至少 4 个测试用例(含边界值)
下一节:导学案4 · 循环。让程序学会重复,三大基本结构就集齐了。
作者:Mike-lin300
最后更新:2026-09-24
更新说明:无