思维导图




一、 if/else:判断条件的本质
1.1 条件的真与假
核心概念:
在 C 语言中,没有原生的布尔类型。计算机判断真假的规则极其简单粗暴:非 0 即真,0 即假。
这意味着,if (1)、if (-5)、if (3.14) 都会被视为真,只有 if (0) 才是假。
语法结构:
c
if (表达式) {
// 表达式为真(非0)时执行
} else if (表达式2) {
// 表达式2为真时执行
} else {
// 所有条件都不满足时执行
}
1.2 逻辑短路的妙用
机制:
对于 A && B,如果 A 为假 (0),计算机绝对不会去计算 B,因为结果注定是假。
对于 A || B,如果 A 为真 (非0),计算机绝对不会去计算 B,因为结果注定是真。
应用场景:
防止除以零或空指针解引用。
代码示例:
c
int denominator = 0;
int x = 100;
// 如果不利用短路,这里会发生除零错误导致程序崩溃
if (denominator != 0 && (x / denominator) > 1) {
printf("计算有效");
} else {
printf("分母不能为零");
}
1.3 悬空 else陷阱
规则:
C 语言规定:else 总是与最近的、未匹配的 if 配对。缩进不能改变逻辑,大括号 {} 才能。
错误示例:
c
if (a > 0)
if (b > 0)
printf("A和B都大于0");
else // 这里的缩进是骗人的!这个 else 属于 if(b>0)
printf("A不大于0?错!其实是B不大于0");
修正: 永远使用 {} 包裹代码块,即使只有一行代码。
1.4 赋值与相等的混淆
if (x = 5) 是合法的 C 语言代码,但它表示"把 5 赋值给 x,然后判断 x 是否为真"。因为 5 非 0,所以条件永远成立。我们要的通常是 if (x == 5)。
防御技巧:
使用 Yoda 表达式 ,将常量写在左边。如 if (5 == x)。如果你不小心写成 if (5 = x),编译器会直接报错,因为常量不能被赋值。
实战代码:成绩分段与合法性检查
c
#include <stdio.h>
int main() {
int score;
printf("请输入成绩 (0-100): ");
// 输入检查
if (scanf("%d", &score) != 1) {
printf("输入格式错误!\n");
return 1;
}
// 逻辑判断
if (score < 0 || score > 100) {
printf("成绩必须在 0 到 100 之间\n");
} else if (score >= 90) {
printf("优秀 (A)\n");
} else if (score >= 80) {
printf("良好 (B)\n");
} else if (score >= 60) {
printf("及格 (C)\n");
} else {
printf("不及格 (F)\n");
}
return 0;
}
二、 switch/case:多分支选择
2.1 适用场景与限制
当需要对同一个变量进行多次等值判断时,switch 比 if/else 更清晰且效率更高。
硬性限制:
switch 括号内的表达式结果必须是整型 (int, char, long等),不能是浮点数或字符串。case 后面必须是常量表达式。
2.2 break
核心概念:
switch 结构最著名的特性(也是最大的坑)是贯穿 。一旦匹配到一个 case,程序会从那个入口进入,并一直向下执行,不管后面的 case 条件是什么,直到遇到 break 或 switch 结束。
策略:
利用贯穿: 多个 case 执行相同逻辑。
防止贯穿: 每个 case 结束后务必加上 break;。
2.3 default 的位置
default 处理所有未匹配的情况。它不一定非要放在最后,但为了可读性,通常放在末尾。
实战代码:菜单系统与月份天数(利用贯穿)
c
#include <stdio.h>
int main() {
int year, month, days;
printf("请输入年份和月份: ");
scanf("%d %d", &year, &month);
switch (month) {
case 2:
// 闰年判断逻辑
if ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0))
days = 29;
else
days = 28;
break;
case 4:
case 6:
case 9:
case 11:
// 利用贯穿:这些月份都是30天
days = 30;
break;
default:
// 剩下的月份默认31天,包括非法月份也暂时归为此类
days = 31;
break;
}
printf("%d 年 %d 月有 %d 天\n", year, month, days);
return 0;
}
三、 for 循环:计数与遍历
3.1 For 循环的三段式结构
语法: for (初始化; 条件判断; 迭代更新) { 循环体 }
执行顺序:
1.初始化 (只执行一次)。
2.条件判断 (若为假,循环结束)。
3.循环体 。
4.迭代更新 (如 i++)。5.回到第 2 步。
3.2 循环变量的作用域
在 C99 标准之前,int i 必须在循环外定义。C99 开始支持 for (int i = 0; ...),此时变量 i 的作用域仅限于循环体内,循环结束后 i 立即销毁。这是一个良好的编程习惯,避免变量污染。
3.3 常见陷阱
分号陷阱:
for (int i=0; i<10; i++); { printf... }。注意那个多余的分号!这会导致循环空转 10 次,然后只打印一次。
边界错误:
遍历长度为 N 的数组,条件应为 i < N。如果写成 i <= N,就会发生越界访问。
死循环:
for(;;) 是标准的死循环写法,等同于 while(1)。
实战代码:打印九九乘法表(嵌套循环)
c
#include <stdio.h>
int main() {
// 外层循环控制行
for (int i = 1; i <= 9; i++) {
// 内层循环控制列
for (int j = 1; j <= i; j++) {
// \t 是制表符,用于对齐
printf("%d*%d=%-2d\t", j, i, i * j);
}
printf("\n"); // 每行结束换行
}
return 0;
}
四、 while / do-while:条件驱动循环
4.1 While 循环
特点: 先判断,后执行。如果一开始条件就不满足,循环体一次都不会执行。
适用: 迭代次数不确定,只知道停止条件的场景。
4.2 Do-While 循环
特点: 先执行,后判断。循环体至少执行一次。
适用: 无论如何都需要先做一次操作的场景,比如输入密码直到正确为止。
注意: do { ... } while (条件); 最后的分号不能少。
4.3 哨兵值与 EOF
在处理输入流时,我们常用 while 循环读取数据,直到遇到特定的哨兵值 (如 -1)或文件结束符 (EOF )。
写法: while (scanf("%d", &num) != EOF) 或 while ((ch = getchar()) != '\n')。
实战代码:输入验证(do-while)
c
#include <stdio.h>
int main() {
int number;
do {
printf("请输入一个正数: ");
scanf("%d", &number);
if (number <= 0) {
printf("错误!必须是正数,请重试。\n");
}
} while (number <= 0); // 如果输入非正数,继续循环
printf("你输入的正数是: %d\n", number);
return 0;
}
五、 break/continue 与循环控制
5.1 break:跳出当前层
作用:
立即终止当前所在的循环(for, while, do-while)或 switch 语句,程序控制权跳到循环体外的下一条语句。
注意: break 只能跳出一层循环。在嵌套循环中,内层的 break 不会影响外层循环。
5.2 continue:跳过本次迭代
作用:
立即结束本次循环体的执行,直接进入下一次迭代(对于 for 循环,会先执行迭代更新部分,再检查条件)。
注意: continue 不能用于 switch 语句,除非该 switch 嵌套在循环中。
5.3 goto:被封印的禁术
goto 可以无条件跳转到函数内的任意标签。虽然它在处理深度嵌套循环的错误跳出时很方便,但滥用会导致代码逻辑像面条一样混乱。新手应尽量避免使用。
实战代码:查找元素与跳过特定值
c
#include <stdio.h>
int main() {
int sum = 0;
for (int i = 1; i <= 10; i++) {
if (i == 5) {
printf("跳过数字 5\n");
continue; // 不执行下面的累加,直接进入 i=6
}
if (sum > 20) {
printf("总和超过 20,提前结束查找\n");
break; // 彻底终止循环
}
sum += i;
printf("加 %d, 当前总和: %d\n", i, sum);
}
return 0;
}
六、 练习题
题目 1: if (-1) 的条件判断结果是真还是假?
题目 2: 代码 int x=0; if(x=5) printf("A"); else printf("B"); 会输出什么?
题目 3: 逻辑表达式 (3 > 5) && (printf("Run")) 执行后,屏幕上会打印 "Run" 吗?
题目 4: switch 语句中,如果 case 后面没有 break,会发生什么现象?
题目 5: switch 括号中的表达式可以是 double 类型吗?
题目 6: case 后面跟的必须是什么类型的表达式?
题目 7: for(int i=0; i<5; i++); printf("%d", i); 这段代码输出结果是多少?(注意分号)
题目 8: 在 while 循环中,初始化变量通常在哪里进行?
题目 9: do-while 循环和 while 循环最大的区别是什么?
题目 10: 想要在循环中直接跳过当次迭代的剩余代码,进入下一次循环,应该用什么关键字?
题目 11: 想要从多层嵌套循环的最内层直接跳出所有循环,除了 goto,通常还可以使用什么变量技巧?
题目 12: 代码 int i=0; while(i<3) { i++; continue; printf("%d", i); } 的输出是什么?
题目 13: break 语句能不能直接用在 if 语句块中(假设 if 不在循环或 switch 里)?
题目 14: 下列循环 for(int i=0; i!=10; i+=2) 是死循环吗?
题目 15: 下列循环 for(int i=0; i!=10; i+=3) 是死循环吗?
七、 解析
题 1 解析
答案: 真。
详解:
C 语言中,非 0 即真。-1 不是 0,所以被视为真。
题 2 解析
答案: 输出 "A"。
详解:
if(x=5)是赋值表达式。x 被赋值为 5,表达式的值为 5(非0),故条件为真。这是经典的赋值混淆错误。
题 3 解析
答案: 不会。
详解:
逻辑与
&&具有短路特性。左边3 > 5为假 (0),整个表达式必为假,右边的printf不会被执行。
题 4 解析
答案: 贯穿 (Fallthrough)。
详解:
程序会继续执行下一个
case的代码块,直到遇到break或 switch 结束。
题 5 解析
答案: 不可以。
详解:
switch只能用于整型 (int, char, enum 等),不支持浮点数。
题 6 解析
答案: 常量表达式。
详解:
case后的值必须在编译时就能确定,不能是变量(如case n:是非法的)。
题 7 解析
答案: 5。
详解:
注意
for后面有一个分号;。这意味着循环体是空的,循环仅负责让i自增到 5。循环结束后执行printf,此时i为 5。
题 8 解析
答案: 循环语句之前。
详解:
while只有条件判断,变量初始化必须在进入while之前完成。
题 9 解析
答案: do-while 保证循环体至少执行一次。
详解:
do-while是后测试循环,先做再判断;while是前测试循环。
题 10 解析
答案: continue。
详解:
continue终止本次迭代,break终止整个循环。
题 11 解析
答案: 标志位 (Flag) 变量。
详解:
设置一个变量(如
int found = 1;),在外层循环中检查这个变量,如果满足则break。
题 12 解析
答案: 什么都不输出。
详解:
每次
i++后立即执行continue,跳过了后面的printf。
题 13 解析
答案: 不能。
详解:
break专门用于循环语句和switch语句。单独的if语句中不能使用break。
题 14 解析
答案: 不是。
详解:
i的序列是 0, 2, 4, 6, 8, 10。当i变为 10 时,i != 10为假,循环正常结束。
题 15 解析
答案: 是死循环(可能导致整数溢出)。
详解:
i的序列是 0, 3, 6, 9, 12... 永远不会等于 10,条件i != 10永远为真。这是一个经典的步长与边界不匹配错误。

日期:2025年2月10日
专栏:C语言