循环和选择
# 04.循环和选择
# 目录介绍
# 4.2 选择结构
# 4.2.1 if语句
if 语句用于在条件为真时执行代码块。
语法
if (条件) {
// 条件为真时执行的代码
}
2
3
示例
int age = 18;
if (age >= 18) {
printf("You are an adult.\n");
}
2
3
4
# 4.2.2 if-else语句
if-else 语句用于在条件为真时执行一个代码块,否则执行另一个代码块。语法
if (条件) {
// 条件为真时执行的代码
} else {
// 条件为假时执行的代码
}
2
3
4
5
示例
int age = 16;
if (age >= 18) {
printf("You are an adult.\n");
} else {
printf("You are a minor.\n");
}
2
3
4
5
6
# 4.2.3 多条件语句
if-else if-else 语句用于处理多个条件。
语法
if (条件1) {
// 条件1为真时执行的代码
} else if (条件2) {
// 条件2为真时执行的代码
} else {
// 所有条件为假时执行的代码
}
2
3
4
5
6
7
示例
int score = 85;
if (score >= 90) {
printf("Grade: A\n");
} else if (score >= 80) {
printf("Grade: B\n");
} else if (score >= 70) {
printf("Grade: C\n");
} else {
printf("Grade: F\n");
}
2
3
4
5
6
7
8
9
10
# 4.2.4 switch语句
switch 语句用于根据变量的值执行不同的代码块。
switch (表达式) {
case 值1:
// 表达式等于值1时执行的代码
break;
case 值2:
// 表达式等于值2时执行的代码
break;
default:
// 表达式不等于任何值时执行的代码
}
2
3
4
5
6
7
8
9
10
示例
int day = 3;
switch (day) {
case 1:
printf("Monday\n");
break;
case 2:
printf("Tuesday\n");
break;
case 3:
printf("Wednesday\n");
break;
default:
printf("Invalid day\n");
}
2
3
4
5
6
7
8
9
10
11
12
13
14
注意事项
switch表达式必须是整型或字符型。- 每个
case块通常以break结束,否则会继续执行下一个case块。 default块是可选的,用于处理未匹配的情况。
# 4.2.5 嵌套选择结构
选择结构可以嵌套使用。示例
int age = 20;
char gender = 'M';
if (age >= 18) {
if (gender == 'M') {
printf("Adult male\n");
} else {
printf("Adult female\n");
}
} else {
printf("Minor\n");
}
2
3
4
5
6
7
8
9
10
11
12
# 4.2.6 三元运算符
三元运算符是一种简化的选择结构。语法
条件 ? 表达式1 : 表达式2;
示例
int age = 20;
char *status = (age >= 18) ? "Adult" : "Minor";
printf("%s\n", status);
2
3
# 4.2.7 注意事项说明
- 条件表达式: 条件表达式的结果必须是
true(非零)或false(零)。 if和else的匹配**:else总是与最近的if匹配。switch的break:如果忘记写break,程序会继续执行下一个case块。- 代码风格:使用大括号
{}明确代码块的范围,即使只有一行代码。
# 4.2.8 综合示例
#include <stdio.h>
int main() {
int score;
printf("Enter your score: ");
scanf("%d", &score);
if (score >= 90) {
printf("Grade: A\n");
} else if (score >= 80) {
printf("Grade: B\n");
} else if (score >= 70) {
printf("Grade: C\n");
} else if (score >= 60) {
printf("Grade: D\n");
} else {
printf("Grade: F\n");
}
char grade;
switch (score / 10) {
case 10:
case 9:
grade = 'A';
break;
case 8:
grade = 'B';
break;
case 7:
grade = 'C';
break;
case 6:
grade = 'D';
break;
default:
grade = 'F';
}
printf("Grade: %c\n", grade);
return 0;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
# 4.2.9 综合案例与思考
综合案例:用选择结构实现一个简单的计算器
#include <stdio.h>
int main() {
double num1, num2;
char operator;
printf("输入表达式 (如 3.5 + 2.1): ");
scanf("%lf %c %lf", &num1, &operator, &num2);
switch (operator) {
case '+':
printf("%.2f + %.2f = %.2f\n", num1, num2, num1 + num2);
break;
case '-':
printf("%.2f - %.2f = %.2f\n", num1, num2, num1 - num2);
break;
case '*':
printf("%.2f * %.2f = %.2f\n", num1, num2, num1 * num2);
break;
case '/':
if (num2 != 0) {
printf("%.2f / %.2f = %.2f\n", num1, num2, num1 / num2);
} else {
printf("错误: 除数不能为0!\n");
}
break;
default:
printf("不支持的运算符: %c\n", operator);
}
return 0;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
原理说明:if-else 适用于范围判断(如成绩等级),switch 适用于离散值判断(如菜单选项、运算符)。switch 底层通过跳转表(jump table)实现,当分支较多时效率高于连续的 if-else。break 在 switch 中必不可少,否则会发生"贯穿"(fall-through),继续执行下一个 case 的代码。
思考题:
switch语句中如果忘记写break会发生什么?这种"贯穿"特性在什么场景下有用?if-else和switch各自适合什么场景?什么情况下不能用switch替代if-else?- 为什么
switch的表达式必须是整数类型或字符型,不能是浮点型或字符串?
# 4.3 循环结构
在 C 语言中,循环结构用于重复执行一段代码,直到满足特定条件。C 语言提供了三种主要的循环结构:for 循环、while 循环和 do-while 循环。
# 4.3.1 for循环
for 循环用于在已知循环次数的情况下重复执行代码。
语法
for (初始化; 条件; 更新) {
// 循环体
}
2
3
执行流程
- 执行初始化语句。
- 检查条件是否为真:
- 如果为真,执行循环体。
- 如果为假,退出循环。
- 执行更新语句。
- 重复步骤 2 和 3。
示例
for (int i = 0; i < 5; i++) {
printf("%d ", i);
}
2
3
输出:
0 1 2 3 4
# 4.3.2 while循环
while 循环用于在条件为真时重复执行代码。
语法
while (条件) {
// 循环体
}
2
3
执行流程
- 检查条件是否为真:
- 如果为真,执行循环体。
- 如果为假,退出循环。
- 重复步骤 1。
示例
int i = 0;
while (i < 5) {
printf("%d ", i);
i++;
}
2
3
4
5
输出:
0 1 2 3 4
# 4.3.3 do-while循环
do-while 循环先执行循环体,然后检查条件是否为真。
语法
do {
// 循环体
} while (条件);
2
3
执行流程
- 执行循环体。
- 检查条件是否为真:
- 如果为真,重复步骤 1。
- 如果为假,退出循环。
示例
int i = 0;
do {
printf("%d ", i);
i++;
} while (i < 5);
2
3
4
5
输出:
0 1 2 3 4
# 4.3.4 嵌套循环
循环可以嵌套使用,即在一个循环中包含另一个循环。
示例
for (int i = 1; i <= 3; i++) {
for (int j = 1; j <= 3; j++) {
printf("(%d, %d) ", i, j);
}
printf("\n");
}
2
3
4
5
6
输出:
(1, 1) (1, 2) (1, 3)
(2, 1) (2, 2) (2, 3)
(3, 1) (3, 2) (3, 3)
2
3
# 4.3.5 无限循环
无限循环是指条件永远为真的循环,通常用于需要持续运行的程序。
示例
while (1) {
printf("This is an infinite loop.\n");
}
2
3
# 4.3.6 循环注意事项
- 循环条件:确保循环条件最终会变为假,否则会导致无限循环。
- 循环变量:在
for循环中,循环变量的作用域仅限于循环体。 - 代码风格:使用大括号
{}明确循环体的范围,即使只有一行代码。 - 性能优化:避免在循环体内执行不必要的操作,以提高性能。
# 4.3.7 综合案例与思考
综合案例:用循环实现经典算法
#include <stdio.h>
int main() {
// 案例1:求1~100的和(for循环)
int sum = 0;
for (int i = 1; i <= 100; i++) {
sum += i;
}
printf("1~100的和: %d\n", sum); // 5050
// 案例2:猜数字游戏(while循环)
int secret = 42;
int guess = 0;
int attempts = 0;
printf("\n猜数字游戏(1-100),答案是42:\n");
// 模拟猜测过程
int guesses[] = {50, 25, 40, 45, 42};
int idx = 0;
while (guess != secret && idx < 5) {
guess = guesses[idx++];
attempts++;
if (guess > secret) {
printf("猜 %d - 太大了!\n", guess);
} else if (guess < secret) {
printf("猜 %d - 太小了!\n", guess);
} else {
printf("猜 %d - 恭喜,猜对了! 用了%d次\n", guess, attempts);
}
}
// 案例3:打印九九乘法表(嵌套循环)
printf("\n九九乘法表:\n");
for (int i = 1; i <= 9; i++) {
for (int j = 1; j <= i; j++) {
printf("%d*%d=%-4d", j, i, i * j);
}
printf("\n");
}
// 案例4:do-while确保至少执行一次
int input;
do {
input = 3; // 模拟输入
printf("\n输入的值: %d (do-while至少执行一次)\n", input);
} while (input < 0);
return 0;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
原理说明:for 循环适合已知循环次数的场景;while 循环适合条件驱动的场景;do-while 保证循环体至少执行一次。嵌套循环的时间复杂度是各层循环次数的乘积,因此要注意性能。编译器会对简单循环进行优化(如循环展开),但复杂循环可能需要手动优化。
思考题:
for、while、do-while三种循环能否互相替换?什么场景下do-while比while更合适?- 一个二重嵌套循环,外层循环100次,内层循环1000次。如果交换内外层,对性能有影响吗?
- 无限循环
while(1)和for(;;)有区别吗?在实际编程中什么场景需要无限循环?
# 4.4 跳出循环
# 4.4.1 break语句
break 语句用于立即退出循环。
示例
for (int i = 0; i < 10; i++) {
if (i == 5) {
break;
}
printf("%d ", i);
}
2
3
4
5
6
输出:
0 1 2 3 4
# 4.4.2 continue语句
continue 语句用于跳过当前循环的剩余部分,直接进入下一次循环。
示例
for (int i = 0; i < 5; i++) {
if (i == 2) {
continue;
}
printf("%d ", i);
}
2
3
4
5
6
# 4.4.3 goto语句
goto 语句用于无条件跳转到程序中指定的标签位置。虽然 goto 在大多数场景中不推荐使用(会破坏结构化编程),但在某些特定场景下非常有用,如多层嵌套循环的跳出和错误处理时的资源清理。
语法
goto 标签名;
// ...
标签名:
// 跳转到此处执行
2
3
4
示例:跳出多层嵌套循环
#include <stdio.h>
int main() {
for (int i = 0; i < 5; i++) {
for (int j = 0; j < 5; j++) {
if (i == 2 && j == 3) {
goto end_loops;
}
printf("(%d,%d) ", i, j);
}
printf("\n");
}
end_loops:
printf("\n跳出多层循环,在i=2,j=3处退出\n");
return 0;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
示例:错误处理中的资源清理
#include <stdio.h>
#include <stdlib.h>
int main() {
FILE *file = fopen("test.txt", "r");
if (file == NULL) goto cleanup;
int *buffer = (int *)malloc(100 * sizeof(int));
if (buffer == NULL) goto cleanup_file;
// 正常处理逻辑...
printf("资源分配成功\n");
free(buffer);
fclose(file);
return 0;
cleanup_file:
fclose(file);
cleanup:
printf("资源分配失败,已清理\n");
return 1;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
原理说明:goto 的本质是汇编语言中的无条件跳转指令(JMP)。C语言是结构化编程语言,过度使用 goto 会导致"面条代码"(spaghetti code),难以维护。但在Linux内核源码中,goto 被广泛用于错误处理路径,因为它能避免深层嵌套的 if-else,使错误清理逻辑更清晰。
# 4.4.4 综合案例与思考
综合案例:流程控制语句的综合运用
#include <stdio.h>
int main() {
// 案例1:break跳出循环 - 查找第一个能被7整除的数
printf("案例1:查找1~100中第一个能被7整除的数\n");
for (int i = 1; i <= 100; i++) {
if (i % 7 == 0) {
printf("找到: %d\n", i);
break;
}
}
// 案例2:continue跳过 - 打印1~20中的奇数
printf("\n案例2:1~20中的奇数: ");
for (int i = 1; i <= 20; i++) {
if (i % 2 == 0) {
continue;
}
printf("%d ", i);
}
printf("\n");
// 案例3:break跳出内层循环 - 查找素数
printf("\n案例3:2~50中的素数: ");
for (int i = 2; i <= 50; i++) {
int is_prime = 1;
for (int j = 2; j * j <= i; j++) {
if (i % j == 0) {
is_prime = 0;
break; // 只跳出内层循环
}
}
if (is_prime) {
printf("%d ", i);
}
}
printf("\n");
// 案例4:goto跳出多层嵌套 - 在二维数组中查找目标值
printf("\n案例4:在矩阵中查找目标值25\n");
int matrix[5][5];
for (int i = 0; i < 5; i++)
for (int j = 0; j < 5; j++)
matrix[i][j] = i * 10 + j * 3;
int target = 25;
for (int i = 0; i < 5; i++) {
for (int j = 0; j < 5; j++) {
if (matrix[i][j] == target) {
printf("找到 %d 在位置 [%d][%d]\n", target, i, j);
goto found;
}
}
}
printf("未找到 %d\n", target);
found:
// 案例5:综合运用 - 模拟密码验证(最多3次机会)
printf("\n案例5:密码验证(模拟)\n");
int correct_pin = 1234;
int pins[] = {1111, 5678, 1234}; // 模拟输入
int max_attempts = 3;
for (int i = 0; i < max_attempts; i++) {
printf("第%d次尝试: %d ", i + 1, pins[i]);
if (pins[i] == correct_pin) {
printf("- 验证成功!\n");
break;
}
printf("- 密码错误\n");
if (i == max_attempts - 1) {
printf("已达最大尝试次数,账户锁定!\n");
}
}
return 0;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
原理说明:break 只能跳出当前所在的最内层循环或 switch 语句,无法直接跳出多层嵌套。continue 只跳过当前迭代的剩余代码,不会影响外层循环。当需要跳出多层嵌套循环时,有三种方案:1)使用标志变量逐层判断;2)将嵌套循环封装为函数,用 return 跳出;3)使用 goto(在C语言中最简洁)。编译器处理 break 和 continue 时,会将它们转化为条件跳转指令。
思考题:
break在for循环和switch语句中的行为有什么不同?如果switch嵌套在for中,break跳出哪个?- 不使用
goto,如何优雅地跳出三层嵌套循环?请写出至少两种方案。 - Linux内核源码中大量使用
goto进行错误处理,这与"不要使用goto"的编程建议矛盾吗?你怎么看?