C foundations / Lesson 04

让程序做判断,
走向正确的分支。

采样正常吗?温度是否到达阈值?用比较和条件表达式决定下一步,再用边界值和失败输入检查判断是否可靠。

04 / C 语言基础从比较到分支关注边界与输入
01 / 目标

让判断可解释、可验证

  • 读懂比较运算结果和 C 中的真假规则。
  • 写出 if、else if、else 分支,并测试等于阈值的情况。
  • 区分 &&、||、!,利用短路先检查除数。
  • 认识 switch 的 case、break,以及输入函数返回值的作用。
本课产出

一个模拟温度状态判断的 lesson04.c,以及三组边界和失败输入的检查记录。

02 / 判断

条件是一个会产生值的表达式

在 C 的条件判断中,数值 0 表示假,非零表示真。比较表达式例如 temperature >= 30,成立时得到整数 1,不成立时得到 0。

int temperature = 31;
if (temperature >= 30) {
    puts("fan=on");
}

执行时先计算括号中的比较;结果非零才进入花括号。puts 输出字符串并换行,需要包含 <stdio.h>。例子中的数值为学习用途,不是任何传感器或风扇的实际控制规范。

03 / 实验

先预测三行状态,再运行

新建 lesson04.c。本例使用固定变量,让你先练分支;输入处理在后面单独讲解。

lesson04.c完整示例 · C11
#include <stdio.h>

int main(void)
{
    int sensor_ok = 1;
    int temperature = 31;
    int mode = 2;

    if (sensor_ok == 0) {
        puts("sensor=error");
    } else if (temperature >= 30) {
        puts("fan=on");
    } else {
        puts("fan=off");
    }

    if (sensor_ok != 0 && temperature >= 0 && temperature <= 60) {
        puts("range=valid");
    } else {
        puts("range=invalid");
    }

    switch (mode) {
    case 1:
        puts("mode=manual");
        break;
    case 2:
        puts("mode=auto");
        break;
    default:
        puts("mode=unknown");
        break;
    }
    return 0;
}
Windows · MSYS2 UCRT64

编译并运行

gcc -std=c11 -Wall -Wextra lesson04.c -o lesson04.exe
./lesson04.exe
Ubuntu

编译并运行

gcc -std=c11 -Wall -Wextra lesson04.c -o lesson04
./lesson04

预期输出:

fan=on
range=valid
mode=auto
04 / 拆解

逐项走过每个判断

三个变量
sensor_ok 为 1 代表示例中的传感器状态正常;温度值为 31;模式为 2。这些含义由示例自己约定。
第一组 if
sensor_ok == 0 不成立,继续判断 temperature >= 30,成立后输出 fan=on,整个 if/else 链不再检查后面的 else。
第二组 if
状态非零,31 不小于 0 且不大于 60,三个条件均为真,输出 range=valid。
switch (mode)
值为 2,执行 case 2 对应的输出;break 离开整个 switch,不继续执行 default。
return 0
程序正常结束。两组 if 与 switch 依次执行,所以这里产生三行输出。
05 / 比较

比较两个值

写法含义例子结果
==等于3 == 31
!=不等于3 != 30
<、>小于、大于3 < 41
<=、>=小于等于、大于等于30 >= 301
不要混淆 = 与 ==。temperature = 30 是赋值;temperature == 30 是比较。条件里意外写成赋值有时仍能编译,并可能改变变量。开启警告,逐条理解它。

比较浮点数前还应考虑精度误差和应用的允许范围。比较混合有符号与无符号整数时会发生类型转换,负数不一定按直觉比较;先明确类型和取值范围。

06 / 分支

if、else if 与 else

if (condition_a) {
    /* A 成立时执行 */
} else if (condition_b) {
    /* A 不成立且 B 成立时执行 */
} else {
    /* 前面的条件都不成立时执行 */
}

从上往下判断,遇到成立的分支后执行其代码,其余分支跳过。可以只有 if,也可以有多个 else if;末尾的 else 是可选的。

本课始终使用花括号:即使分支暂时只有一条语句,清晰的块也更容易防止维护时误判。else 与最近的、尚未匹配的 if 结合;嵌套时用花括号明确意图。

自检:把示例 temperature 改为 30,会输出 fan=on 吗?

会。>= 包含等于;应同时测试 29、30、31,分别覆盖阈值前、阈值上和阈值后。

07 / 逻辑

组合条件与短路

运算符含义短路规则
&&逻辑与:两边都为真才为真左边为假时,不计算右边
||逻辑或:至少一边为真即为真左边为真时,不计算右边
!逻辑非:把真假反转只需要一个操作数

例如 temperature >= 0 && temperature <= 60 表示闭区间 0 到 60。不要写 0 <= temperature <= 60:C 会先得到一个比较结果 0 或 1,再与 60 比较,不能表达数学上的连续比较。

短路可以保护后面的运算。下面先检查除数,只有非零时才执行右侧除法:

if (count != 0 && 12 / count > 1) {
    puts("condition met");
}

把零检查放在除法后面就来不及了。&& 与 || 的结果是 0 或 1;不要把它们与逐位运算符 &、| 混为一谈,位运算将在面向硬件的课程中展开。

08 / 多选

一个值,对应多个固定选项

switch 适合根据一个整数类型表达式选择固定分支:case 后是整数常量表达式,各标签值不能重复。default 可处理没有匹配项的情况。

执行进入匹配标签后会按代码顺序继续,直到遇到 break、return 等改变控制流的语句或结束 switch;漏写 break 可能继续执行下一分支,这叫贯穿。它有时是有意设计,但初学时请明确注释并核对。

自检:把 mode 改为 9,会输出哪一行模式?

没有 case 匹配 9,所以执行 default,输出 mode=unknown。

09 / 输入

读取之后,先看是否成功

上面的例程使用固定值。若要在另一个程序中用 scanf 接收一个 int,应检查它成功赋值的项目数;不要假定用户一定输入了数字:

int temperature;
int converted = scanf("%d", &temperature);
if (converted != 1) {
    puts("input error");
    return 1;
}
/* 仅在成功读取后使用 temperature */

这段代码应放进包含 <stdio.h> 的 main 函数中。&temperature 把变量地址交给 scanf;成功赋值一个项目时返回 1,匹配失败或输入结束时不为 1。此时不能继续把该变量当作已成功读取的值。

输入范围:这个片段只适合在输入的数值能由 int 表示的前提下学习;超范围数字不能靠检查 scanf 返回值安全处理。并且输入 12abc 可能成功读取 12,剩余字符仍留在输入中。要处理任意一整行、检测超范围与多余字符,应采用 fgets 加 strtol 并检查结果;后续课程再完整练习。
10 / 排错

用边界值找出判断问题

现象可能原因检查方法
阈值处结果不对> 和 >= 的边界不同分别测试阈值减 1、阈值本身、阈值加 1
多个模式同时输出switch 分支缺少 break从匹配 case 跟踪执行直到跳出
输入失败后数值不可靠没有检查读取结果或变量没有成功赋值检查返回值,再决定是否使用该变量
保护判断没有生效将除法写在零检查之前,或误用逐位与先检查条件顺序和 && 拼写
修改变量值后仍看到旧结果没有保存、重新编译,或运行了旧文件核对当前目录,保存、编译、运行
11 / 练习

手工走分支,再运行

01 / 边界

29、30、31 有何不同?

保持 sensor_ok 为 1,分别设置 temperature 为 29、30、31。第一行各是什么?

查看参考答案

29 输出 fan=off,30 和 31 输出 fan=on。因为条件使用 >= 30。

02 / 优先分支

传感器状态无效

把 sensor_ok 改为 0,temperature 仍为 31。前两行是什么?为什么第二组条件不用继续判断范围?

查看参考答案

依次为 sensor=error 和 range=invalid。第二组条件的第一个子表达式为假,后续 && 的右侧因短路不再求值。

03 / 短路

检查除数

当 count 为 0 时,count != 0 && 12 / count > 1 的结果是什么?为什么?

查看参考答案

结果为 0。左侧 count != 0 为假,&& 不会求右侧,因此不会执行除法。

04 / 输入

字母输入与返回值

对于上方 scanf("%d", &temperature) 片段,输入字母时如何避免使用未成功读取的变量?

查看检查要点

判断返回值是否为 1;不是 1 时进入错误处理,不读取 temperature。复杂输入还需处理整行内容和范围。

完成标准

能够解释每条路径,才进入循环

  1. 能解释 =、==、&& 和 || 的不同作用。
  2. 不运行也能追踪示例各分支,并用边界值测试阈值。
  3. 知道检查输入返回值和整数范围分别解决什么问题。