【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。

五、逻辑操作符

运算符名字规则
!逻辑取反真变假、假变真
&&逻辑与两侧都真才真
||逻辑或至少一个真就真

真值表:

aba && ba || b
真真11
真假01
假真01
假假00
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");
年份4100400结果
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 替代谁",而是流程本身要变——写代码变便宜了,需求、设计、评审、测试的权重反而上升。

七、本篇踩坑清单

#坑现象正确做法
1if 后多条语句不加括号第二条无条件执行每层都加 { }
2多分支从小到大写45.65 命中"满 20 减 5"按范围从大到小判断
3悬空 else什么都不输出else 配最近 if,加括号
4if (n = 5)输入 4 也走真分支写 ==,常量放左边
518 <= age <= 3680 岁也输出"青年"用 && 连接
6month == 12 || 1 || 2条件恒为真每项写完整比较
7以为 && 两边都会执行右侧自增"没生效"记住短路规则
8三目嵌套太深难读、难调试超过两层改 if
9把 || 和 \&\& 优先级记反复杂条件判断错&& 高于 ||,加括号

八、小结

  1. 分支的难点在边界和顺序,不在语法:多分支的判断顺序本身就是业务逻辑;
  2. 把自然语言翻译成代码要拆成"原子比较 + 逻辑连接",不能整句照搬;
  3. 短路是特性也是陷阱:逻辑表达式里不写自增、不写赋值、不调用有副作用的函数;
  4. 悬空 else 说明一件事——别相信缩进,if 一律加括号。

标签:C语言 if语句 逻辑操作符 短路求值 分支结构 软件工程 学习笔记

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐