C语言:分支结构(if / else):关系与逻辑操作符、短路求值
【C语言学习笔记】分支结构(if / else):关系与逻辑操作符、短路求值
学习主题:《从 C 语言到 AI 编程实践》
一、三种程序结构
C 语言是结构化的程序设计语言,所有程序都可以拆成三种结构或它们的组合:
| 结构 | 特征 | 实现 |
|---|---|---|
| 顺序 | 一条直线,从上到下 | 顺序执行的语句 |
| 选择 | 分叉再汇合 | if、switch |
| 循环 | 一个闭环 | for、while、do while |
本篇讲选择结构,下一篇讲循环结构。
二、if 语句
if (表达式)
语句
表达式为真则执行,为假则不执行。C 语言里 0 为假,非 0 为真。
判断奇偶:
int main()
{
int num = 0;
scanf("%d", &num);
if (num % 2 == 1) // 只控制这一条语句
printf("奇数\n");
else
printf("偶数\n");
return 0;
}
2.1 多条语句必须加大括号
int main()
{
int score = 0;
scanf("%d", &score);
if (score >= 60)
printf("拿捏拿捏\n");
printf("庆祝一下\n"); // 不在 if 里!输入 50 也会打印
return 0;
}
if / else 默认只控制一条语句,缩进骗人,编译器的眼里只有大括号:
if (score >= 60)
{
printf("拿捏拿捏\n");
printf("庆祝一下\n");
}
else
{
printf("补考补考\n");
printf("学习学习\n");
}
2.2 多分支:判断顺序就是业务逻辑
满 50 减 15、满 30 减 8、满 20 减 5,不满 20 不参与:
int main()
{
float total = 0.0f;
scanf("%f", &total);
if (total >= 50)
printf("满 50,减 15,最终价格:%.2f\n", total - 15);
else if (total >= 30)
printf("满 30,减 8,最终价格:%.2f\n", total - 8);
else if (total >= 20)
printf("满 20,减 5,最终价格:%.2f\n", total - 5);
else
printf("不满 20 无法参与活动\n");
return 0;
}
输入 45.65,输出"满 30,减 8,最终价格:37.65"。
顺序必须从大到小。如果先判
>= 20,45.65 会先命中"满 20 减 5",只减 5 块。else if是"谁先命中谁执行",后面的分支根本不会被检查。
只看"上界"的写法也很常见:minutes <= 10 实际含义是 1 < minutes <= 10,因为下界已经被前面的分支排除了。
2.3 嵌套 if
评奖学金:成绩 ≥ 85、无挂科、无记过处分。
int main()
{
int score = 50;
int has_fail = 1; // 0 表示没挂科,1 表示挂科
int has_warn = 1; // 0 表示没记过,1 表示有记过
if (score >= 85)
{
if (has_fail == 0)
{
if (has_warn == 0)
printf("有奖学金\n");
else
printf("有记过处分,没有奖学金\n");
}
else
printf("有挂科,没有奖学金\n");
}
else
printf("成绩不满足,评奖学金\n");
return 0;
}
每一层
if处理一个失败原因,else就是"这个条件挂了"。比一个巨型&&更好加日志、更好改成逐条提示。
2.4 悬空 else
规则:
else总是与前面最近的、还没有配对的if结合。
int main()
{
int a = 0;
int b = 2;
if (a == 1)
if (b == 2)
printf("hehe\n");
else // 配对的是内层 if (b == 2)
printf("haha\n");
return 0;
}
a == 1 为假,整个外层 if 都不进入,所以什么都不输出。如果本意是"a 不等于 1 就打印 haha",必须加大括号:
if (a == 1)
{
if (b == 2)
printf("hehe\n");
}
else
{
printf("haha\n");
}
这个 bug 的可怕之处:编译通过、运行不报错、只是少打印一行。别相信缩进,每层都加括号。
三、关系操作符
| 运算符 | 含义 |
|---|---|
> < | 大于、小于 |
>= <= | 大于等于、小于等于 |
== | 相等 |
!= | 不相等 |
关系表达式返回 0 或 1:20 > 12 得 1,12 > 20 得 0。
3.1 把 == 写成 =
int main()
{
int n = 0;
scanf("%d", &n);
if (n = 5) // 错误写法:赋值表达式的值是 5,恒为真
printf("hehe\n");
else
printf("haha\n");
return 0;
}
输入 4 也会输出 hehe,而且 n 被悄悄改成 5——最安静的一种 bug。
防御写法:常量放左边。
if (5 == n) // 写成 5 = n 会直接编译报错
printf("hehe\n");
报错信息是 C2106 “=”: 左操作数必须为左值 / E0137 表达式必须是可修改的左值——能在编译期抓到的 bug,就别留给运行时。
3.2 关系运算符不宜连用
if (18 <= age <= 36) // 错误
printf("青年\n");
它等价于 (18 <= age) <= 36:先算 18 <= age 得到 0 或 1,再和 36 比。输入 80 也会输出"青年"。
if (age >= 18 && age <= 36) // 正确:用逻辑与连接两个比较
printf("青年\n");
四、条件操作符
exp1 ? exp2 : exp3
exp1 为真取 exp2,为假取 exp3。需要 3 个操作数,所以叫三目操作符。
int n1 = 0, n2 = 0;
scanf("%d %d", &n1, &n2);
int m = (n1 > n2) ? n1 : n2; // 取较大值,一行替代 if-else
printf("较大值: %d\n", m);
也能嵌套,但要换行对齐,且不建议超过两层:
printf("心情:%s\n",
minutes <= 1 ? "秒回!有戏" :
minutes <= 10 ? "还行,ta 可能在忙" :
minutes <= 60 ? "可能没看到信息" : "别等了");
三目更紧凑,但每层判断都藏在问号里,调试时不好打断点。两三层可以,再深就用
if。
五、逻辑操作符
| 运算符 | 名字 | 规则 |
|---|---|---|
! | 逻辑取反 | 真变假、假变真 |
&& | 逻辑与 | 两侧都真才真 |
|| | 逻辑或 | 至少一个真就真 |
真值表:
| a | b | a && b | a || b |
|---|---|---|---|
| 真 | 真 | 1 | 1 |
| 真 | 假 | 0 | 1 |
| 假 | 真 | 0 | 1 |
| 假 | 假 | 0 | 0 |
int flag = 0;
scanf("%d", &flag);
if (flag) printf("do something 1\n"); // flag 为真
if (!flag) printf("do something 2\n"); // flag 为假
int month = 0;
scanf("%d", &month);
if (month >= 3 && month <= 5) // 春
printf("春季\n");
if (month == 12 || month == 1 || month == 2) // 冬
printf("冬季\n");
month == 12 || 1 || 2是错的,等价于(month == 12) || 1 || 2,恒为真——每一项都要写完整比较。
5.1 练习:闰年
规则:能被 4 整除且不能被 100 整除,或者能被 400 整除。
if ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0))
printf("闰年\n");
else
printf("不是闰年\n");
| 年份 | 4 | 100 | 400 | 结果 |
|---|---|---|---|---|
| 2004 | ✓ | ✗ | – | 闰年 |
| 2000 | ✓ | ✓ | ✓ | 闰年 |
| 1900 | ✓ | ✓ | ✗ | 不是 |
&&优先级高于||,括号可以省,但加上更清楚。
5.2 短路
逻辑运算符先算左边,再算右边,这个顺序是保证的;左边已经能决定结果时,右边不再求值。
A && B:A 为假 → B 不算;A || B:A 为真 → B 不算。
#include <stdio.h>
int main()
{
int i = 0, a = 0, b = 2, c = 3, d = 4;
i = a++ && ++b && d++; // a++ 用 0,短路,b、d 不变
printf("a=%d b=%d c=%d d=%d i=%d\n", a, b, c, d, i);
// a=1 b=2 c=3 d=4 i=0
return 0;
}
把初始值改成 a = 1,结果立刻不同:
int i = 0, a = 1, b = 2, c = 3, d = 4;
i = a++ && ++b && d++;
// a=2 b=3 c=3 d=5 i=1 —— 三个表达式都执行了
|| 同理:
int i = 0, a = 1, b = 2, c = 3, d = 4;
i = a++ || ++b || d++;
// a=2 b=2 c=3 d=4 —— a++ 为真,后面全部短路
| 写法 | 结果 |
|---|---|
a=0 时 a++ && ++b && d++ | 只执行 a++ |
a=1 时 a++ && ++b && d++ | 三个都执行 |
a=1 时 a++ || ++b || d++ | 只执行 a++ |
a=0 时 a++ || ++b || d++ | 执行 a++ 和 ++b |
&&遇假就停,||遇真就停,停下之后右边的副作用(自增、赋值、函数调用)都不会发生。
这个特性可以用来写安全判断:if (p != NULL && p->value > 0) ——左边挡住右边。
六、软件工程与开发流程
软件 = 程序 + 软件工程,后者覆盖需求、设计、实现、测试、维护、项目管理等整个生命周期。
计划 → 需求 → 设计 → 开发实现 → 测试 → 评审 → 发布/部署
↑ │
└── bug ──┘
设计分两层:
- 概要设计:整体架构、模块划分、技术选型、数据库表结构;
- 详细设计:模块内部逻辑、流程图/时序图、接口输入输出、异常处理。
"写代码"在整个流程里只占一环,前后的环节都比它贵——这也解释了为什么需求变更会让人很难受。
AI 能写代码之后,真正的问题变成了:如何保证代码质量、如何真正提升效率。答案不是"AI 替代谁",而是流程本身要变——写代码变便宜了,需求、设计、评审、测试的权重反而上升。
七、本篇踩坑清单
| # | 坑 | 现象 | 正确做法 |
|---|---|---|---|
| 1 | if 后多条语句不加括号 | 第二条无条件执行 | 每层都加 { } |
| 2 | 多分支从小到大写 | 45.65 命中"满 20 减 5" | 按范围从大到小判断 |
| 3 | 悬空 else | 什么都不输出 | else 配最近 if,加括号 |
| 4 | if (n = 5) | 输入 4 也走真分支 | 写 ==,常量放左边 |
| 5 | 18 <= age <= 36 | 80 岁也输出"青年" | 用 && 连接 |
| 6 | month == 12 || 1 || 2 | 条件恒为真 | 每项写完整比较 |
| 7 | 以为 && 两边都会执行 | 右侧自增"没生效" | 记住短路规则 |
| 8 | 三目嵌套太深 | 难读、难调试 | 超过两层改 if |
| 9 | 把 || 和 \&\& 优先级记反 | 复杂条件判断错 | && 高于 ||,加括号 |
八、小结
- 分支的难点在边界和顺序,不在语法:多分支的判断顺序本身就是业务逻辑;
- 把自然语言翻译成代码要拆成"原子比较 + 逻辑连接",不能整句照搬;
- 短路是特性也是陷阱:逻辑表达式里不写自增、不写赋值、不调用有副作用的函数;
- 悬空 else 说明一件事——别相信缩进,
if一律加括号。
标签:C语言 if语句 逻辑操作符 短路求值 分支结构 软件工程 学习笔记
更多推荐

所有评论(0)