第05章 流程控制
5.1 if-else条件语句
5.1.1 if语句
语法:
if (条件表达式)
{
// 条件为真时执行的语句
}
示例:
#include <stdio.h>
int main(void)
{
int age;
printf("请输入年龄: ");
scanf("%d", &age);
if (age >= 18)
{
printf("您已成年\n");
}
return 0;
}
5.1.2 if-else语句
语法:
if (条件表达式)
{
// 条件为真时执行
}
else
{
// 条件为假时执行
}
示例: 判断奇偶
#include <stdio.h>
int main(void)
{
int num;
printf("请输入整数: ");
scanf("%d", &num);
if (num % 2 == 0)
{
printf("%d 是偶数\n", num);
}
else
{
printf("%d 是奇数\n", num);
}
return 0;
}
5.1.3 if-else if-else多分支
语法:
if (条件1)
{
// 语句1
}
else if (条件2)
{
// 语句2
}
else if (条件3)
{
// 语句3
}
else
{
// 以上条件都不满足时执行
}
示例: 成绩等级判断
#include <stdio.h>
int main(void)
{
int score;
printf("请输入成绩: ");
scanf("%d", &score);
if (score < 0 || score > 100)
{
printf("成绩无效!\n");
}
else if (score >= 90)
{
printf("等级: A (优秀)\n");
}
else if (score >= 80)
{
printf("等级: B (良好)\n");
}
else if (score >= 70)
{
printf("等级: C (中等)\n");
}
else if (score >= 60)
{
printf("等级: D (及格)\n");
}
else
{
printf("等级: E (不及格)\n");
}
return 0;
}
5.1.4 嵌套if语句
示例: 判断闰年
#include <stdio.h>
int main(void)
{
int year;
printf("请输入年份: ");
scanf("%d", &year);
if (year % 4 == 0)
{
if (year % 100 == 0)
{
if (year % 400 == 0)
{
printf("%d 是闰年\n", year);
}
else
{
printf("%d 不是闰年\n", year);
}
}
else
{
printf("%d 是闰年\n", year);
}
}
else
{
printf("%d 不是闰年\n", year);
}
return 0;
}
优化版本(使用逻辑运算符):
#include <stdio.h>
int main(void)
{
int year;
printf("请输入年份: ");
scanf("%d", &year);
if ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0))
{
printf("%d 是闰年\n", year);
}
else
{
printf("%d 不是闰年\n", year);
}
return 0;
}
5.1.5 悬空else问题
问题代码:
int a = 5, b = 10;
if (a > 0)
if (b > 0)
printf("a和b都是正数\n");
else // 这个else属于哪个if?
printf("a不是正数\n");
⚠️ 规则: else总是与最近的未匹配的if配对。
正确理解:
if (a > 0)
{
if (b > 0)
{
printf("a和b都是正数\n");
}
else // 属于内层if
{
printf("a是正数,b不是正数\n");
}
}
建议: 总是使用花括号{},避免混淆。
5.2 switch-case分支语句
5.2.1 switch基本用法
语法:
switch (表达式)
{
case 常量1:
// 语句1
break;
case 常量2:
// 语句2
break;
default:
// 默认语句
break;
}
示例: 菜单选择
#include <stdio.h>
int main(void)
{
int choice;
printf("========菜单========\n");
printf("1. 新建文件\n");
printf("2. 打开文件\n");
printf("3. 保存文件\n");
printf("4. 退出\n");
printf("请选择: ");
scanf("%d", &choice);
switch (choice)
{
case 1:
printf("新建文件\n");
break;
case 2:
printf("打开文件\n");
break;
case 3:
printf("保存文件\n");
break;
case 4:
printf("退出程序\n");
break;
default:
printf("无效选择!\n");
break;
}
return 0;
}
5.2.2 case穿透
如果case后面没有break,会继续执行后面的case:
#include <stdio.h>
int main(void)
{
int day;
printf("输入星期几(1-7): ");
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;
}
5.2.3 switch的限制
限制1: 表达式必须是整型或字符型
// 正确
switch (num) // int
switch (ch) // char
// 错误
switch (f) // float不可以
switch (str) // 字符串不可以
限制2: case后面必须是常量表达式
#define MAX 100
const int N = 10;
int a = 5;
switch (a)
{
case 1: // 正确:字面常量
break;
case MAX: // 正确:宏常量
break;
case N: // 错误:const变量不是常量表达式!
break;
case a + 1: // 错误:变量表达式
break;
}
5.2.4 switch vs if-else
switch适用场景:
- 等值判断
- 分支较多
- 条件是离散的常量值
if-else适用场景:
- 范围判断
- 复杂的逻辑条件
- 浮点数判断
示例对比:
// 使用switch(不推荐)
switch (score / 10)
{
case 10:
case 9:
printf("A\n");
break;
case 8:
printf("B\n");
break;
// ...
}
// 使用if-else(推荐)
if (score >= 90)
printf("A\n");
else if (score >= 80)
printf("B\n");
// ...
5.3 while循环
5.3.1 while基本用法
语法:
while (条件表达式)
{
// 循环体
}
特点: 先判断条件,再执行循环体(可能一次都不执行)。
示例: 计算1到100的和
#include <stdio.h>
int main(void)
{
int i = 1;
int sum = 0;
while (i <= 100)
{
sum += i;
i++;
}
printf("1到100的和: %d\n", sum); // 5050
return 0;
}
5.3.2 while实现输入验证
#include <stdio.h>
int main(void)
{
int num;
int ret;
printf("请输入1-10之间的整数: ");
while (1) // 无限循环
{
ret = scanf("%d", &num);
// 清空缓冲区
int c;
while ((c = getchar()) != '\n' && c != EOF);
if (ret != 1)
{
printf("输入格式错误!请重新输入: ");
continue;
}
if (num >= 1 && num <= 10)
{
break; // 输入正确,跳出循环
}
printf("数字超出范围!请重新输入: ");
}
printf("您输入的数字是: %d\n", num);
return 0;
}
5.3.3 死循环
常见死循环形式:
// 形式1
while (1)
{
// ...
}
// 形式2
while (true)
{
// ...
}
// 形式3
for (;;)
{
// ...
}
示例: 菜单程序
#include <stdio.h>
int main(void)
{
int choice;
while (1)
{
printf("\n========菜单========\n");
printf("1. 选项1\n");
printf("2. 选项2\n");
printf("0. 退出\n");
printf("请选择: ");
scanf("%d", &choice);
if (choice == 0)
{
printf("退出程序\n");
break;
}
switch (choice)
{
case 1:
printf("执行选项1\n");
break;
case 2:
printf("执行选项2\n");
break;
default:
printf("无效选择!\n");
}
}
return 0;
}
5.4 do-while循环
5.4.1 do-while基本用法
语法:
do
{
// 循环体
} while (条件表达式);
特点: 先执行循环体,再判断条件(至少执行一次)。
示例: 猜数字游戏
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main(void)
{
int secret, guess;
srand(time(NULL));
secret = rand() % 100 + 1; // 1-100随机数
printf("猜数字游戏(1-100)\n");
do
{
printf("请输入你的猜测: ");
scanf("%d", &guess);
if (guess > secret)
{
printf("太大了!\n");
}
else if (guess < secret)
{
printf("太小了!\n");
}
else
{
printf("恭喜你,猜对了!\n");
}
} while (guess != secret);
return 0;
}
5.4.2 while vs do-while
| 特性 | while | do-while |
|---|---|---|
| 判断时机 | 先判断再执行 | 先执行再判断 |
| 最少执行次数 | 0次 | 1次 |
| 使用场景 | 可能不需要执行 | 至少需要执行一次 |
示例对比:
// while: 可能一次都不执行
int i = 10;
while (i < 10)
{
printf("不会执行\n");
}
// do-while: 至少执行一次
int j = 10;
do
{
printf("会执行一次\n");
} while (j < 10);
5.5 for循环
5.5.1 for基本用法
语法:
for (初始化; 条件判断; 更新)
{
// 循环体
}
执行流程:
- 执行初始化(只执行一次)
- 判断条件
- 如果条件为真,执行循环体
- 执行更新
- 返回步骤2
示例: 打印1到10
#include <stdio.h>
int main(void)
{
for (int i = 1; i <= 10; i++)
{
printf("%d ", i);
}
printf("\n");
return 0;
}
5.5.2 for循环的灵活性
省略初始化:
int i = 1;
for (; i <= 10; i++)
{
printf("%d ", i);
}
省略更新:
for (int i = 1; i <= 10; )
{
printf("%d ", i);
i++;
}
省略条件(死循环):
for (int i = 1; ; i++)
{
if (i > 10) break;
printf("%d ", i);
}
全部省略(死循环):
for (;;)
{
// 等价于 while(1)
}
多个初始化和更新:
for (int i = 0, j = 10; i < j; i++, j--)
{
printf("i=%d, j=%d\n", i, j);
}
5.5.3 常用for循环模式
正向遍历:
for (int i = 0; i < n; i++)
{
// ...
}
反向遍历:
for (int i = n - 1; i >= 0; i--)
{
// ...
}
步长为2:
for (int i = 0; i < n; i += 2)
{
// ...
}
嵌套循环:
// 打印九九乘法表
for (int i = 1; i <= 9; i++)
{
for (int j = 1; j <= i; j++)
{
printf("%d*%d=%2d ", j, i, i * j);
}
printf("\n");
}
输出:
1*1= 1
1*2= 2 2*2= 4
1*3= 3 2*3= 6 3*3= 9
...
5.5.4 for vs while
// for循环
for (int i = 0; i < 10; i++)
{
printf("%d ", i);
}
// 等价的while循环
int i = 0;
while (i < 10)
{
printf("%d ", i);
i++;
}
选择建议:
- 循环次数确定 → 使用for
- 循环次数不确定 → 使用while
5.6 break语句
5.6.1 break的作用
- 在循环中使用:跳出当前循环
- 在switch中使用:跳出switch语句
示例: 查找第一个负数
#include <stdio.h>
int main(void)
{
int arr[] = {1, 2, 3, -4, 5, 6};
int n = sizeof(arr) / sizeof(arr[0]);
for (int i = 0; i < n; i++)
{
if (arr[i] < 0)
{
printf("第一个负数在索引 %d\n", i);
break; // 找到后立即退出循环
}
}
return 0;
}
5.6.2 break只跳出一层循环
#include <stdio.h>
int main(void)
{
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 3; j++)
{
if (j == 1)
{
break; // 只跳出内层循环
}
printf("i=%d, j=%d\n", i, j);
}
}
return 0;
}
输出:
i=0, j=0
i=1, j=0
i=2, j=0
5.6.3 跳出多层循环
方法1: 使用标志变量
#include <stdio.h>
int main(void)
{
int found = 0;
for (int i = 0; i < 5 && !found; i++)
{
for (int j = 0; j < 5; j++)
{
if (i * j == 6)
{
printf("找到: i=%d, j=%d\n", i, j);
found = 1;
break;
}
}
}
return 0;
}
方法2: 使用goto(不推荐,但有时很实用)
#include <stdio.h>
int main(void)
{
for (int i = 0; i < 5; i++)
{
for (int j = 0; j < 5; j++)
{
if (i * j == 6)
{
printf("找到: i=%d, j=%d\n", i, j);
goto end; // 跳到标签位置
}
}
}
end:
printf("结束\n");
return 0;
}
5.7 continue语句
5.7.1 continue的作用
跳过本次循环剩余部分,开始下一次循环。
示例: 打印奇数
#include <stdio.h>
int main(void)
{
for (int i = 1; i <= 10; i++)
{
if (i % 2 == 0)
{
continue; // 跳过偶数
}
printf("%d ", i);
}
printf("\n");
return 0;
}
输出: 1 3 5 7 9
5.7.2 break vs continue
| 语句 | 作用 | 循环是否继续 |
|---|---|---|
| break | 终止循环 | 否 |
| continue | 跳过本次循环 | 是 |
示例对比:
// 使用break
for (int i = 0; i < 5; i++)
{
if (i == 2) break;
printf("%d ", i); // 输出: 0 1
}
// 使用continue
for (int i = 0; i < 5; i++)
{
if (i == 2) continue;
printf("%d ", i); // 输出: 0 1 3 4
}
5.8 goto语句
5.8.1 goto基本用法
语法:
goto 标签;
标签:
// 语句
示例:
#include <stdio.h>
int main(void)
{
int i = 0;
loop:
printf("%d ", i);
i++;
if (i < 5)
{
goto loop;
}
printf("\n");
return 0;
}
5.8.2 goto的合理使用
场景1: 错误处理
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
FILE *fp1 = NULL;
FILE *fp2 = NULL;
char *buf = NULL;
fp1 = fopen("file1.txt", "r");
if (fp1 == NULL)
{
goto cleanup;
}
fp2 = fopen("file2.txt", "w");
if (fp2 == NULL)
{
goto cleanup;
}
buf = malloc(1024);
if (buf == NULL)
{
goto cleanup;
}
// 正常处理...
cleanup:
if (buf) free(buf);
if (fp2) fclose(fp2);
if (fp1) fclose(fp1);
return 0;
}
场景2: 跳出多层循环
for (int i = 0; i < 100; i++)
{
for (int j = 0; j < 100; j++)
{
if (condition)
{
goto end;
}
}
}
end:
// ...
⚠️ 注意: 尽量少用goto,优先使用结构化控制语句。
5.9 实践练习
练习1: 素数判断
编写程序判断一个数是否为素数:
#include <stdio.h>
#include <stdbool.h>
bool isPrime(int n)
{
if (n < 2) return false;
if (n == 2) return true;
if (n % 2 == 0) return false;
for (int i = 3; i * i <= n; i += 2)
{
if (n % i == 0)
{
return false;
}
}
return true;
}
int main(void)
{
int num;
printf("请输入一个正整数: ");
scanf("%d", &num);
if (isPrime(num))
{
printf("%d 是素数\n", num);
}
else
{
printf("%d 不是素数\n", num);
}
return 0;
}
练习2: 打印图案
打印空心正方形:
#include <stdio.h>
int main(void)
{
int n;
printf("请输入正方形边长: ");
scanf("%d", &n);
for (int i = 0; i < n; i++)
{
for (int j = 0; j < n; j++)
{
if (i == 0 || i == n - 1 || j == 0 || j == n - 1)
{
printf("* ");
}
else
{
printf(" ");
}
}
printf("\n");
}
return 0;
}
输入5时输出:
* * * * *
* *
* *
* *
* * * * *
练习3: 最大公约数
使用辗转相除法求最大公约数:
#include <stdio.h>
int gcd(int a, int b)
{
while (b != 0)
{
int temp = b;
b = a % b;
a = temp;
}
return a;
}
int main(void)
{
int a, b;
printf("请输入两个正整数: ");
scanf("%d%d", &a, &b);
printf("%d 和 %d 的最大公约数是: %d\n", a, b, gcd(a, b));
return 0;
}
5.10 常见错误
错误1: if后面多了分号
if (a > 0); // 错误!分号表示空语句
{
printf("a是正数\n"); // 这个块总是执行
}
// 正确写法
if (a > 0)
{
printf("a是正数\n");
}
错误2: switch中忘记break
switch (choice)
{
case 1:
printf("选项1\n");
// 忘记break,会继续执行case 2
case 2:
printf("选项2\n");
break;
}
错误3: for循环变量作用域
for (int i = 0; i < 10; i++)
{
// ...
}
printf("%d\n", i); // 错误!i在for循环外不可见(C99)
错误4: 无限循环
// 错误:循环变量未更新
int i = 0;
while (i < 10)
{
printf("%d ", i);
// 忘记 i++
}
// 错误:判断条件永远为真
for (int i = 0; i >= 0; i++) // i永远>=0
{
// ...
}
错误5: continue用在switch中
switch (choice)
{
case 1:
if (error)
continue; // 错误!switch不是循环
break;
}
// 正确:switch中应该用break
switch (choice)
{
case 1:
if (error)
break;
// ...
break;
}
5.11 小结
本章学习了:
if-else条件语句及嵌套 switch-case多分支语句 while循环(先判断后执行) do-while循环(先执行后判断) for循环及其灵活用法 break跳出循环 continue跳过本次循环 goto语句的合理使用
重点提示:
- 总是使用花括号
{},提高代码可读性 - switch的case后不要忘记break
- 注意死循环的条件
- break只跳出一层循环
- continue跳过本次循环,但循环继续
- goto谨慎使用,主要用于错误处理和跳出多层循环