编译并运行
gcc -std=c11 -Wall -Wextra lesson04.c -o lesson04.exe
./lesson04.exeC foundations / Lesson 04
采样正常吗?温度是否到达阈值?用比较和条件表达式决定下一步,再用边界值和失败输入检查判断是否可靠。
if、else if、else 分支,并测试等于阈值的情况。&&、||、!,利用短路先检查除数。switch 的 case、break,以及输入函数返回值的作用。一个模拟温度状态判断的 lesson04.c,以及三组边界和失败输入的检查记录。
在 C 的条件判断中,数值 0 表示假,非零表示真。比较表达式例如 temperature >= 30,成立时得到整数 1,不成立时得到 0。
int temperature = 31;
if (temperature >= 30) {
puts("fan=on");
}执行时先计算括号中的比较;结果非零才进入花括号。puts 输出字符串并换行,需要包含 <stdio.h>。例子中的数值为学习用途,不是任何传感器或风扇的实际控制规范。
新建 lesson04.c。本例使用固定变量,让你先练分支;输入处理在后面单独讲解。
#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;
}gcc -std=c11 -Wall -Wextra lesson04.c -o lesson04.exe
./lesson04.exegcc -std=c11 -Wall -Wextra lesson04.c -o lesson04
./lesson04预期输出:
fan=on
range=valid
mode=autosensor_ok 为 1 代表示例中的传感器状态正常;温度值为 31;模式为 2。这些含义由示例自己约定。ifsensor_ok == 0 不成立,继续判断 temperature >= 30,成立后输出 fan=on,整个 if/else 链不再检查后面的 else。ifrange=valid。switch (mode)case 2 对应的输出;break 离开整个 switch,不继续执行 default。return 0| 写法 | 含义 | 例子 | 结果 |
|---|---|---|---|
== | 等于 | 3 == 3 | 1 |
!= | 不等于 | 3 != 3 | 0 |
<、> | 小于、大于 | 3 < 4 | 1 |
<=、>= | 小于等于、大于等于 | 30 >= 30 | 1 |
= 与 ==。temperature = 30 是赋值;temperature == 30 是比较。条件里意外写成赋值有时仍能编译,并可能改变变量。开启警告,逐条理解它。比较浮点数前还应考虑精度误差和应用的允许范围。比较混合有符号与无符号整数时会发生类型转换,负数不一定按直觉比较;先明确类型和取值范围。
if (condition_a) {
/* A 成立时执行 */
} else if (condition_b) {
/* A 不成立且 B 成立时执行 */
} else {
/* 前面的条件都不成立时执行 */
}从上往下判断,遇到成立的分支后执行其代码,其余分支跳过。可以只有 if,也可以有多个 else if;末尾的 else 是可选的。
本课始终使用花括号:即使分支暂时只有一条语句,清晰的块也更容易防止维护时误判。else 与最近的、尚未匹配的 if 结合;嵌套时用花括号明确意图。
会。>= 包含等于;应同时测试 29、30、31,分别覆盖阈值前、阈值上和阈值后。
| 运算符 | 含义 | 短路规则 |
|---|---|---|
&& | 逻辑与:两边都为真才为真 | 左边为假时,不计算右边 |
|| | 逻辑或:至少一边为真即为真 | 左边为真时,不计算右边 |
! | 逻辑非:把真假反转 | 只需要一个操作数 |
例如 temperature >= 0 && temperature <= 60 表示闭区间 0 到 60。不要写 0 <= temperature <= 60:C 会先得到一个比较结果 0 或 1,再与 60 比较,不能表达数学上的连续比较。
短路可以保护后面的运算。下面先检查除数,只有非零时才执行右侧除法:
if (count != 0 && 12 / count > 1) {
puts("condition met");
}把零检查放在除法后面就来不及了。&& 与 || 的结果是 0 或 1;不要把它们与逐位运算符 &、| 混为一谈,位运算将在面向硬件的课程中展开。
switch 适合根据一个整数类型表达式选择固定分支:case 后是整数常量表达式,各标签值不能重复。default 可处理没有匹配项的情况。
执行进入匹配标签后会按代码顺序继续,直到遇到 break、return 等改变控制流的语句或结束 switch;漏写 break 可能继续执行下一分支,这叫贯穿。它有时是有意设计,但初学时请明确注释并核对。
没有 case 匹配 9,所以执行 default,输出 mode=unknown。
上面的例程使用固定值。若要在另一个程序中用 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 并检查结果;后续课程再完整练习。| 现象 | 可能原因 | 检查方法 |
|---|---|---|
| 阈值处结果不对 | > 和 >= 的边界不同 | 分别测试阈值减 1、阈值本身、阈值加 1 |
| 多个模式同时输出 | switch 分支缺少 break | 从匹配 case 跟踪执行直到跳出 |
| 输入失败后数值不可靠 | 没有检查读取结果或变量没有成功赋值 | 检查返回值,再决定是否使用该变量 |
| 保护判断没有生效 | 将除法写在零检查之前,或误用逐位与 | 先检查条件顺序和 && 拼写 |
| 修改变量值后仍看到旧结果 | 没有保存、重新编译,或运行了旧文件 | 核对当前目录,保存、编译、运行 |
保持 sensor_ok 为 1,分别设置 temperature 为 29、30、31。第一行各是什么?
29 输出 fan=off,30 和 31 输出 fan=on。因为条件使用 >= 30。
把 sensor_ok 改为 0,temperature 仍为 31。前两行是什么?为什么第二组条件不用继续判断范围?
依次为 sensor=error 和 range=invalid。第二组条件的第一个子表达式为假,后续 && 的右侧因短路不再求值。
当 count 为 0 时,count != 0 && 12 / count > 1 的结果是什么?为什么?
结果为 0。左侧 count != 0 为假,&& 不会求右侧,因此不会执行除法。
对于上方 scanf("%d", &temperature) 片段,输入字母时如何避免使用未成功读取的变量?
判断返回值是否为 1;不是 1 时进入错误处理,不读取 temperature。复杂输入还需处理整行内容和范围。
=、==、&& 和 || 的不同作用。