Appearance
分支:让程序学会选择
预计学习时长:约 3 小时(仅 L1 主线,建议分两次完成)· 运行环境:VS2026 / gcc / clang 均可
🎯 学习目标
学完本讲,你将能够:
- 用 if / else 写出单分支、双分支和多分支程序
- 正确使用 6 个关系操作符,并避开
==与=的陷阱 - 用
&&、||、!组合复杂条件,理解短路求值 - 用条件操作符把简单的 if/else 写成一行
- 用 switch 处理“一个变量对应多个固定值”的分支
🔗 先修知识
- 第 3 讲:数据类型和变量:条件里要比较变量
- 第 4 讲:运算符详解:关系、逻辑、条件操作符都在其中
先修自检(三问)
- C 语言里
0和非0分别表示什么? 3 > 2的结果是多少?2 > 3呢?a += 1等价于什么?
答不上来先点开上面对应链接复习,再回来继续。
一、程序的三种结构
C 语言是结构化的程序设计语言,所有程序都由三种基本结构搭成:
| 结构 | 执行方式 | 例子 |
|---|---|---|
| 顺序结构 | 从上到下依次执行 | 先输入,再计算,最后打印 |
| 选择结构(分支) | 按条件选择走哪条路 | 满 18 岁显示“成年” |
| 循环结构 | 重复执行一段代码 | 猜不对就一直让你猜 |
前几讲写的都是顺序结构。本讲学习选择结构,C 语言用 if 和 switch 实现;循环结构在下一讲。
二、if 语句
2.1 最简单的 if
🧩 示意片段
c
if (表达式)
语句; // 条件成立才执行;想放多条语句要用 { }规则:表达式非 0 就执行语句,为 0 就跳过。🟢 注意非 0 包括负数,所以 if (-1) 条件也成立。
📄 odd.c · ✅ 完整程序(输入一个整数,判断是不是奇数)
c
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
int num = 0;
scanf("%d", &num);
if (num % 2 != 0)
printf("%d 是奇数\n", num);
return 0;
}运行结果(输入 7):
text
7
7 是奇数⚠️ VS 用户提醒:error C4996: 'scanf'
本讲程序要多次使用 scanf,VS2026 下若报 C4996,请在文件最顶部(所有 #include 之前)加 #define _CRT_SECURE_NO_WARNINGS。本讲完整程序都已加好。gcc、clang 不会有这个报错。
2.2 一个坑:判断奇数为什么写 != 0 而不是 == 1
很多资料写成 if (num % 2 == 1),输入正奇数没问题,但输入 -3 时不会输出“奇数”。
原因:从 C99 开始规定整数除法向 0 取整,所以 -3 / 2 等于 -1,-3 % 2 等于 -1,不等于 1。而写成 num % 2 != 0,无论正负都成立。🟢
📜 标准卡(L2 选读)
标准:C11 §6.5.5p6:整数除法结果向 0 取整;(a/b)*b + a%b 恒等于 a。由此 -3 % 2 必为 -1。 gcc / MSVC / clang 实际行为:一致,都是 -1(C99 之前的老编译器才可能出现其他结果)。
2.3 if...else:两条路选一条
🧩 示意片段
c
if (表达式)
语句1; // 条件成立走这里
else
语句2; // 条件不成立走这里📄 odd_even.c · ✅ 完整程序
c
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
int num = 0;
scanf("%d", &num);
if (num % 2 != 0)
printf("%d 是奇数\n", num);
else
printf("%d 是偶数\n", num);
return 0;
}2.4 多条语句必须加
if 和 else 默认只控制紧跟其后的一条语句。下面代码中,第二行 printf 不受 if 控制,无论年龄多大都会打印:
🧩 待改错片段
c
if (age >= 18)
printf("成年了\n");
printf("可以考驾照了\n"); // ❌ 它独立于 if,总会执行要用大括号把多条语句包成一个代码块(也叫复合语句):
📄 adult.c · ✅ 完整程序
c
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
int age = 0;
scanf("%d", &age);
if (age >= 18)
{
printf("成年了\n");
printf("可以考驾照了\n");
}
else
{
printf("未成年\n");
printf("还需要等几年\n");
}
return 0;
}2.5 多分支:else if 链
判断一个数是 0、正数还是负数,用 else 与 if 连用:
📄 sign.c · ✅ 完整程序
c
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
int num = 0;
scanf("%d", &num);
if (num == 0)
printf("零\n");
else if (num > 0)
printf("正数\n");
else
printf("负数\n");
return 0;
}规则:条件从上到下逐个判断,第一个成立的分支执行后,整条链就结束;都不成立才走最后的 else。
2.6 嵌套 if
if 里面还可以再放 if。比如“只对正数进一步判断奇偶”:
🧩 示意片段
c
if (num > 0)
{
if (num % 2 == 0)
printf("正偶数\n");
else
printf("正奇数\n");
}
else
{
printf("非正数\n");
}2.7 悬空 else:它到底属于谁
规则只有一句:else 总是和它前面最近的、还没有匹配的 if 结合。 🟢
📄 dangling.c · ✅ 完整程序
c
#include <stdio.h>
int main()
{
int a = 0;
int b = 2;
if (a == 1)
if (b == 2)
printf("hehe\n");
else
printf("haha\n");
return 0;
}排版让 else 看起来和第一个 if 对齐,实际它和第二个 if 配对。由于 a 是 0,外层 if 不成立,里面整段都没机会执行——所以运行结果是什么都不打印。
想让 else 明确属于第一个 if,用大括号划定范围:
📄 dangling_fix.c · ✅ 完整程序
c
#include <stdio.h>
int main()
{
int a = 0;
int b = 2;
if (a == 1)
{
if (b == 2)
printf("hehe\n");
}
else
{
printf("haha\n");
}
return 0;
}💡 记住:大括号不仅是语法需要,更是给人看的。 if/else 都写大括号,能避开绝大多数配对误会。
三、关系操作符
用于比较的运算符叫关系操作符,共 6 个:
| 操作符 | 含义 | 操作符 | 含义 | |
|---|---|---|---|---|
> | 大于 | <= | 小于等于 | |
< | 小于 | == | 相等(两个等号) | |
>= | 大于等于 | != | 不相等 |
关系表达式的结果只有两个:成立为 1,不成立为 0。🟢
text
20 > 12 → 1
12 > 20 → 03.1 高频坑:把 == 写成 =
== 是比较,= 是赋值,二者完全不同:
🧩 待改错片段
c
if (x = 3) // 这是把 3 赋给 x,整个表达式的值是 3(非 0,永远为真)
...原意是 x == 3。一个防误写的习惯:把常量写在左边,if (3 == x);万一误写成 if (3 = x),编译器会直接报错,因为不能给常量赋值。
3.2 高频坑:关系操作符不能连用
数学里写 18 <= age <= 36 很自然,但 C 语言不能这么写。if (i < j < k) 实际从左到右执行,等价于:
c
if ((i < j) < k)i < j 的结果是 0 或 1,再拿 0 或 1 和 k 比较,通常总成立,逻辑完全不对。想表达“j 在 i 和 k 之间”,要写成两个条件:
c
if (i < j && j < k)四、逻辑操作符
要把多个条件组合起来,用逻辑操作符,共 3 个:
| 操作符 | 含义 | 什么时候为真 |
|---|---|---|
! | 逻辑取反(非) | 操作数为 0 时结果为 1 |
&& | 逻辑与(并且) | 两侧都为真才为真 |
|| | 逻辑或(或者) | 至少一侧为真就为真 |
⚠️ 注意和位操作符区分:&、|、^、~ 是按二进制位运算,&&、||、! 是判断真假。
4.1 逻辑取反 !
📄 not.c · ✅ 完整程序
c
#include <stdio.h>
int main()
{
int flag = 0;
if (!flag) // flag 为 0,!flag 为 1,条件成立
printf("flag 现在是假\n");
return 0;
}4.2 逻辑与 && 和逻辑或 ||
🧩 示意片段
c
if (month >= 3 && month <= 5) // 3、4、5 月:两个条件同时满足
printf("春季\n");
if (month == 12 || month == 1 || month == 2) // 满足任意一个
printf("冬季\n");4.3 综合例子:判断闰年
闰年规则有两条,满足任意一条即可:
- 能被 4 整除,并且不能被 100 整除;
- 或者能被 400 整除。
📄 leap_year.c · ✅ 完整程序
c
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
int year = 0;
scanf("%d", &year);
if ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0))
printf("是闰年\n");
else
printf("不是闰年\n");
return 0;
}注意括号:&& 优先级高于 ||,但加上括号可读性更好。
4.4 短路求值
逻辑操作符保证严格从左向右计算:当左边已经能确定整个表达式的结果时,右边根本不会执行,这叫短路。🟢
a && b:a 为 0(假)时,结果必为假,b 不执行;a || b:a 非 0(真)时,结果必为真,b 不执行。
📄 short_circuit.c · ✅ 完整程序
c
#include <stdio.h>
int main()
{
int a = 0, b = 2, c = 3, d = 4;
int i = a++ && ++b && d++;
printf("a=%d, b=%d, c=%d, d=%d\n", a, b, c, d);
return 0;
}运行结果:
text
a=1, b=2, c=3, d=4分析:a++ 先用 a 的值 0(再让 a 变成 1),0 为假触发短路,后面的 ++b、d++ 全部不执行,所以 b、c、d 保持不变。
📘 提高(L2):把 a 的初值改成 1 再看
若写成 int a = 1, b = 2, c = 3, d = 4; i = a++ || ++b || d++;:a++ 先用值 1,真,|| 短路,输出 a=2, b=2, c=3, d=4。短路是有用的特性,比如 if (p != NULL && *p > 0) 先确认 p 非空,安全后才解引用,这是 C 程序里非常常见的写法。
五、条件操作符
如果 if/else 只是为了给一个变量“二选一”赋值,可以用条件操作符写成一行:
🧩 示意片段
c
exp1 ? exp2 : exp3exp1 为真,整个式子取 exp2 的值;为假取 exp3 的值。它需要三个操作数,也叫三目操作符。
📄 max_two.c · ✅ 完整程序
c
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
int a = 0, b = 0;
scanf("%d %d", &a, &b);
int m = (a > b) ? a : b; // a 大取 a,否则取 b
printf("较大值是 %d\n", m);
return 0;
}六、switch 语句
当分支取决于一个整数的多个固定取值时,用一串 else if 会很啰嗦,switch 更清晰。
6.1 语法
🧩 示意片段
c
switch (整型表达式)
{
case 常量1:
语句;
break;
case 常量2:
语句;
break;
default:
语句;
break;
}两条硬性要求 🟢:switch 后面必须是整型表达式(int、char 等,不能是 float、字符串);case 后面必须是整型常量表达式(不能是变量,比如不能写 case n:)。
6.2 break 与“穿透”
📄 weekday.c · ✅ 完整程序
c
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
int day = 0;
scanf("%d", &day);
switch (day)
{
case 1:
printf("星期一\n");
break;
case 2:
printf("星期二\n");
break;
case 3:
printf("星期三\n");
break;
default:
printf("输入不在 1~3 之间\n");
break;
}
return 0;
}如果某个 case 后漏了 break,执行不会停,会继续往下执行后面 case 的代码,直到遇到 break 或 switch 结束——这叫穿透。比如 case 1 不写 break,输入 1 会把“星期一”“星期二”“星期三”全打出来。
6.3 主动利用穿透:多个值共享一段代码
穿透不只是错误,也可以主动利用。让 1~5 共享“工作日”、6~7 共享“休息日”:
📄 work_rest.c · ✅ 完整程序
c
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
int day = 0;
scanf("%d", &day);
switch (day)
{
case 1:
case 2:
case 3:
case 4:
case 5:
printf("工作日\n");
break;
case 6:
case 7:
printf("休息日\n");
break;
default:
printf("输入错误\n");
break;
}
return 0;
}case 1~4 后面什么都不写、也没有 break,自然落到 case 5 的 printf,这是穿透的正当用法。
6.4 default 子句
表达式的值匹配不到任何 case 时,执行 default,相当于 else。default 可以放在 switch 里的任何位置,不一定在最后;但为了好读,习惯上放最后。
6.5 if 和 switch 怎么选
| 情况 | 选择 |
|---|---|
| 判断范围、大小、多个不同条件 | if / else if |
| 一个整数与若干固定常量逐一比较 | switch |
| 判断字符串、浮点数 | 只能用 if(switch 不支持) |
⚠️ 常见坑与报错(本讲汇总)
| 你看到的现象 | 原因 | 修法 |
|---|---|---|
| 输入负奇数却不识别 | 用了 num % 2 == 1,-3 % 2 是 -1 | 改成 num % 2 != 0 |
| 条件永远为真 | 把 == 写成了 = | 用 ==;或常量写左边让编译器帮你查 |
if (a < b < c) 总成立 | 关系操作符连用,先算左边得到 0/1 | 写成 a < b && b < c |
| 某条语句不受 if 控制 | if 默认只管一条语句 | 用 { } 包成代码块 |
| else 配对和想的不一样 | 悬空 else 跟最近的 if | 加 { } 明确层级 |
| switch 输出了好几个 case | 漏 break 发生穿透 | 需要独立分支的 case 末尾加 break |
| gcc 报 case label does not reduce to an integer constant | case 后写了变量或非整型 | case 后只写整型常量 |
| VS 报 C4996: 'scanf' | VS 的安全警告 | 顶部加 #define _CRT_SECURE_NO_WARNINGS |
更多新手高频错误见附录 C:常见错误清单。
🛠️ 动手练习
先独立做,卡住了再依次展开提示,不要直接看答案。
📊 本讲网页练习进度0 / 5(0%)
进度自动保存在本浏览器;编程题不计数,请在编辑器中完成。
练习 1 · ⭐ 基础 · 读输出
知识点:C 语言的真假
题干:下面代码会打印什么?
📄 ex1.c · ✅ 完整程序
c
#include <stdio.h>
int main()
{
int x = -5;
if (x)
printf("A\n");
else
printf("B\n");
return 0;
}请在下面写出程序输出:
空①
练习 2 · ⭐ 基础 · 读输出
知识点:else if 链的执行顺序
题干:写出程序输出:
📄 ex2.c · ✅ 完整程序
c
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
int score = 75;
if (score >= 90)
printf("优秀\n");
else if (score >= 80)
printf("良好\n");
else if (score >= 60)
printf("及格\n");
else
printf("不及格\n");
return 0;
}请在下面写出程序输出的汉字:
空①
练习 3 · ⭐ 基础 · 读输出
知识点:短路求值
题干:写出输出:
📄 ex3.c · ✅ 完整程序
c
#include <stdio.h>
int main()
{
int a = 1, b = 2, c = 3;
int i = a++ || b++ || c++;
printf("i=%d a=%d b=%d c=%d\n", i, a, b, c);
return 0;
}请写出 printf 的完整输出:
空①
练习 4 · ⭐ 基础 · 找错改错
知识点:== 与 =
题干:下面代码本意是“x 等于 0 时打印”,请填出正确的 if 条件:
🧩 待改错片段
c
int x = 0;
if (x = 0)
printf("x 是零\n");请把括号里正确的表达式填在下方(只需填出 if ( 和 ) 之间的部分):
空①
练习 5 · ⭐ 基础 · 程序填空
知识点:闰年条件
题干:补全判断闰年的条件:
🧩 待补全片段
c
int year = 2024;
if ((year % 4 == 0 ______ year % 100 != 0) ______ (year % 400 == 0))
printf("闰年\n");请填出两个空(每个空填一个逻辑运算符):
空①空②
练习 6 · ⭐⭐ 提高 · 写小程序
知识点:嵌套分支 / 条件操作符
🛠 动手编程题 · 请在 VS2026(或你的编辑器)中完成
输入三个整数,找出其中的最大值并输出。
✅ 过关标准:输入 3 9 5 输出 9;再测 1 1 1、-2 -5 -1 两组。
网页内无法练习写代码,亲手敲、亲手编译才能真正学会。下方按顺序展开 思路 → 步骤 → 答案。
💡 提示 1(思路)
可以先找出前两个的较大值,再拿它和第三个数比。
🧭 提示 2(步骤)
int m = (a > b) ? a : b;m = (m > c) ? m : c;- 打印 m
🔑 参考答案与解释
c
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
int a = 0, b = 0, c = 0;
scanf("%d %d %d", &a, &b, &c);
int m = (a > b) ? a : b;
m = (m > c) ? m : c;
printf("最大值是 %d\n", m);
return 0;
}思路:最大值是“两两比较”比出来的。这个办法可以推广到更多个数,但写法会变长——等学了数组和循环,会有更简洁的做法。
练习 7 · ⭐⭐ 提高 · 写小程序
知识点:switch 与穿透
🛠 动手编程题 · 请在 VS2026(或你的编辑器)中完成
输入 1~12 的月份,输出对应季节(春 3~5、夏 6~8、秋 9~11、冬 12/1/2),其他数字提示输入错误。
✅ 过关标准:输入 4 输出“春季”,输入 1 输出“冬季”,输入 13 输出“输入错误”。
网页内无法练习写代码,亲手敲、亲手编译才能真正学会。下方按顺序展开 思路 → 步骤 → 答案。
💡 提示 1(思路)
连续三个月共享一句打印,用穿透让三个 case 落到同一条 printf。
🧭 提示 2(步骤)
- case 3、4、5 穿透后打印“春季”并 break
- 夏、秋同理
- 冬季是 12、1、2(注意顺序不连续,但 case 可以随意排列)
- default 处理非法输入
🔑 参考答案与解释
c
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
int month = 0;
scanf("%d", &month);
switch (month)
{
case 3:
case 4:
case 5:
printf("春季\n");
break;
case 6:
case 7:
case 8:
printf("夏季\n");
break;
case 9:
case 10:
case 11:
printf("秋季\n");
break;
case 12:
case 1:
case 2:
printf("冬季\n");
break;
default:
printf("输入错误\n");
break;
}
return 0;
}为什么冬季的 case 顺序是 12、1、2 也可以:case 只是值的列表,书写顺序不影响匹配结果。
练习 8 · ⭐⭐⭐ 挑战 · 写小程序
知识点:switch + 输入校验的综合
🛠 动手编程题 · 请在 VS2026(或你的编辑器)中完成
做一个只算两个数的简易计算器:先输入两个数,再输入一个运算符号(+、-、*、/),输出计算结果;做除法且除数为 0 时,提示“除数不能为 0”。
✅ 过关标准:输入 6 2 / 输出 3.00;输入 5 0 / 输出错误提示而不崩溃。
网页内无法练习写代码,亲手敲、亲手编译才能真正学会。下方按顺序展开 思路 → 步骤 → 答案。
💡 提示 1(思路)
运算符号是字符,可以用 switch 匹配 case '+' 这类字符常量;结果用 double 保存,除法才有小数。
🧭 提示 2(步骤)
double a, b; char op;,用 scanf 读入,格式串"%lf %lf %c"- switch(op) 四个分支
- 除法分支里先判断
b == 0
🔑 参考答案与解释
c
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
double a = 0, b = 0;
char op = 0;
scanf("%lf %lf %c", &a, &b, &op);
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)
printf("除数不能为 0\n");
else
printf("%.2f\n", a / b);
break;
default:
printf("不支持的运算符\n");
break;
}
return 0;
}为什么除数为 0 必须单独处理:整数除以 0 是 🔴 未定义行为;这里是浮点除法,多数平台会产生特殊值 inf,但对初学者来说程序行为不可预期,统一拦截最稳妥。这个计算器以后会扩展成多文件的小项目。
📝 小结与自测
核心结论
- if 条件非 0 即真、为 0 即假;if/else 默认只管一条语句,多条语句用
{ }。 - else 总与最近的未匹配 if 结合;多分支用 else if 链,条件顺序要想清楚。
==是比较,=是赋值;关系操作符不能连用,要拆成&&连接的两个条件。&&两侧全真才真,||一侧真即真;二者都从左向右、会短路。- switch 只能判断整型表达式,case 后必须是整型常量;漏 break 会穿透,default 处理其余情况。
自测清单(能做到就打勾)
| 我能独立做到 | ✅ |
|---|---|
| 写出 if / else 和 else if 多分支 | |
| 正确判断正负数的奇偶 | |
| 用 && 和 || 组合条件并说出何时短路 | |
| 用条件操作符求两个数的较大值 | |
| 用 switch 写工作日/休息日判断 | |
| 写出带除零保护的简易计算器 |
🔬 选学拓展(L3)
以下内容为选学
现在跳过完全不影响下一讲学习,等你学完本阶段再回来看。
L3-1:判断浮点数不要用 ==
分支条件里如果出现浮点数相等判断,要小心:0.1 + 0.2 == 0.3 在多数机器上不成立,因为小数用二进制存储时存在舍入误差。应判断两者差值是否足够小:
📄 float_cmp.c · ✅ 完整程序
c
#include <stdio.h>
#include <math.h>
int main()
{
double x = 0.1 + 0.2;
if (fabs(x - 0.3) < 1e-9)
printf("认为相等,x = %.17f\n", x);
return 0;
}text
# gcc / MinGW(用数学库要加 -lm,MinGW 通常可省略)
gcc float_cmp.c -o float_cmp -lmL3-2:switch 中 case 的值不能重复
同一个 switch 里不允许两个 case 使用相同的整型常量值,编译器会直接报错。另外注意字符常量本质是整数,case 'A' 的实际值是 65(ASCII),所以它和 case 65 也不能同时出现。
L3-3:goto 不用于分支
本讲所有分支都能用 if/switch 清晰表达,不要用 goto 来模拟分支。goto 的合理使用场景(一次跳出多层循环)将在下一讲循环介绍。
预告:下一讲学习循环:让程序重复做事,并完成猜数字游戏。