跳转至

导学案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;
}

输出:

0
1
1
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 骨架

if (条件) {
    /* 条件成立(非 0)时执行这里 */
} else {
    /* 条件不成立(0)时执行这里 */
}

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:

请输入成绩:75
及格了

3.2.2 三个坑

坑 1:if 后面多写一个分号。 这是 C 语言最阴的坑之一,因为它完全合法,不报错:

/* 反面示例 */
if (score >= 60);        /* 这个分号让 if 管了一句"空语句" */
{
    printf("及格了\n");   /* 于是这一句变成了无条件执行 */
}

分号本身就是一条"什么也不做"的空语句。if 认为它要管的就是这一句,管完就结束了;后面花括号里的内容成了普通代码,无论成绩多少都会打印"及格了"。

坑 2:省略花括号。 如果 if 后面只有一条语句,花括号可以省:

if (score >= 60)
    printf("及格了\n");

语法上没问题,但本实验室要求你永远写上花括号。理由很实在:某天你在下面又加了一句,忘了补花括号,那句就会无条件执行——和坑 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 (a > 0)
    if (a > 100)
        printf("很大\n");
else
    printf("这行其实属于 if (a > 100)\n");

想让它属于外层 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 语言里,它恒为真:

/* 反面示例 */
int age = -100;

if (18 < age < 60) {        /* age = -100 也照样成立! */
    printf("在范围内\n");
}

为什么?C 语言从左往右算:先算 18 < age,18 < -100 不成立,得到 0;再拿这个 0 去和 60 比,0 < 60 成立,于是整个式子是 1(真)。换成 age = 1000 也一样:18 < 1000 得 1,1 < 60 成立。无论 age 是多少,结果都是真。

正确写法:

if (age > 18 && age < 60) {
    printf("在范围内\n");
}

3.4.3 两点补充

  1. 短路求值:&& 只要左边为假,右边根本不会被计算;|| 只要左边为真,右边也不会被计算。为什么要讲这个,因为比如说某个逻辑表达式右侧存在 i++ 的表达式,那么这个 i 是否自增就与逻辑连接词左侧表达式的真值有关了,可能被增加,也可能不增加,取决于是否被短路,这不是一个好现象。
  2. &、|、^(都只有一个符号)是位运算,长得像但不是一回事,别混淆。

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:

请输入 1~7: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

更新说明:无