1. 选择结构程序设计基础
作为一名嵌入式开发者,我深知选择结构在程序设计中的重要性。选择结构就像是程序中的"决策者",它决定了程序在不同条件下应该执行哪些操作。在C语言中,选择结构主要通过if语句和switch语句来实现,而它们的核心就是条件判断。
1.1 程序三大基本结构
在正式讲解选择结构之前,我们需要了解程序的三大基本结构:
- 顺序结构:程序按照代码的书写顺序依次执行
- 选择结构:根据条件判断选择执行不同的代码块
- 循环结构:在满足条件的情况下重复执行某段代码
这三种结构构成了所有程序的基础,而今天我们要重点讨论的就是选择结构。
1.2 选择结构的作用与意义
选择结构的主要作用是根据特定条件是否满足,来决定执行哪一段代码。在实际开发中,这种结构无处不在:
- 用户输入验证
- 系统状态判断
- 异常处理
- 功能选择
在嵌入式系统中,选择结构尤为重要。比如:
- 根据传感器读数决定是否触发警报
- 根据电池电量选择不同的工作模式
- 根据按键输入执行不同的功能
提示:在嵌入式开发中,选择结构的效率直接影响系统响应速度,因此需要特别注意优化条件判断的顺序和方式。
2. 关系运算与逻辑运算
2.1 关系运算符详解
关系运算是构造条件的基础,C语言提供了6种关系运算符:
| 运算符 | 含义 | 示例 | 结果 |
|---|---|---|---|
| > | 大于 | 5 > 3 | 1(真) |
| < | 小于 | 5 < 3 | 0(假) |
| >= | 大于等于 | 5 >= 5 | 1(真) |
| <= | 小于等于 | 5 <= 3 | 0(假) |
| == | 等于 | 5 == 3 | 0(假) |
| != | 不等于 | 5 != 3 | 1(真) |
需要注意的是:
- 关系运算符的优先级低于算术运算符
- 关系表达式的结果是整型值:1表示真,0表示假
- 不要将赋值运算符"="和关系运算符"=="混淆
2.2 逻辑运算符深度解析
逻辑运算符用于组合多个条件,C语言提供了三种逻辑运算符:
| 运算符 | 名称 | 说明 |
|---|---|---|
| ! | 逻辑非 | 单目运算符,将真变假,假变真 |
| && | 逻辑与 | 当两个操作数都为真时结果才为真,具有短路特性(左操作数为假即停止计算) |
| || | 逻辑或 | 当两个操作数有一个为真时结果就为真,具有短路特性(左操作数为真即停止计算) |
运算符优先级备忘:
- ! (逻辑非)
- 算术运算符
- 关系运算符
- && (逻辑与)
- || (逻辑或)
- 赋值运算符
实际经验:在嵌入式开发中,利用逻辑运算符的短路特性可以优化程序性能。例如,在检查指针有效性时,应该把指针非空检查放在前面:
if(p != NULL && p->data > threshold)
2.3 常见陷阱与注意事项
-
浮点数比较:由于浮点数精度问题,不要直接用==比较,应该使用误差范围比较
c复制// 错误做法 if (f == 0.0) {...} // 正确做法 #define EPSILON 1e-6 if (fabs(f - 0.0) < EPSILON) {...} -
运算符优先级混淆:复杂的表达式应该使用括号明确优先级
c复制// 容易混淆的表达式 if (a & b == c) {...} // 实际是 if (a & (b == c)) // 明确写法 if ((a & b) == c) {...} -
赋值与比较混淆:常见的笔误是将==写成=
c复制// 错误但能编译 if (x = 5) {...} // 总是为真,且x被赋值为5 // 正确写法 if (x == 5) {...}
3. if语句的三种形式与应用
3.1 单分支if语句
语法结构:
c复制if (条件表达式) {
// 条件为真时执行的语句
}
执行流程:
- 计算条件表达式的值
- 如果值为真(非0),执行语句块
- 如果值为假(0),跳过语句块
典型应用场景:
- 参数合法性检查
- 前置条件验证
- 特殊情况处理
示例代码:
c复制// 温度过高警告
if (temperature > 50.0) {
turn_on_cooling_system();
}
注意事项:即使语句块只有一行代码,也建议使用大括号{},这样可以避免后续添加代码时可能引发的错误。
3.2 双分支if-else语句
语法结构:
c复制if (条件表达式) {
// 条件为真时执行的语句
} else {
// 条件为假时执行的语句
}
执行流程:
- 计算条件表达式的值
- 如果值为真,执行if后的语句块
- 如果值为假,执行else后的语句块
典型应用场景:
- 二选一决策
- 结果分类
- 异常处理
示例代码:
c复制// 判断奇偶数
if (number % 2 == 0) {
printf("%d是偶数\n", number);
} else {
printf("%d是奇数\n", number);
}
优化技巧:
对于简单的赋值操作,可以使用条件运算符(?:)替代if-else:
c复制// 使用if-else
if (a > b) {
max = a;
} else {
max = b;
}
// 使用条件运算符
max = (a > b) ? a : b;
3.3 多分支if-else if语句
语法结构:
c复制if (条件表达式1) {
// 条件1为真时执行的语句
} else if (条件表达式2) {
// 条件2为真时执行的语句
} else if (条件表达式3) {
// 条件3为真时执行的语句
} else {
// 所有条件都为假时执行的语句
}
执行流程:
- 依次计算条件表达式
- 第一个为真的条件对应的语句块被执行
- 如果所有条件都为假,执行else语句块
典型应用场景:
- 多级分类
- 状态机实现
- 菜单选择
示例代码:
c复制// 成绩等级评定
if (score >= 90) {
grade = 'A';
} else if (score >= 80) {
grade = 'B';
} else if (score >= 70) {
grade = 'C';
} else if (score >= 60) {
grade = 'D';
} else {
grade = 'F';
}
性能优化建议:
- 将最可能为真的条件放在前面
- 互斥条件可以使用if-else if结构
- 非互斥条件可能需要嵌套if
4. switch语句深度解析
4.1 switch语句基本结构
语法结构:
c复制switch (整型表达式) {
case 常量1:
语句1;
break;
case 常量2:
语句2;
break;
...
default:
默认语句;
}
执行流程:
- 计算整型表达式的值
- 将值与各个case后的常量比较
- 匹配成功的case对应的语句被执行
- 遇到break或switch结束则退出
- 没有匹配则执行default语句
关键特点:
- 表达式必须是整型(char, short, int, long等)
- case后必须是整型常量表达式
- break语句用于退出switch,若无break会继续执行下一个case
4.2 switch与if-else的对比选择
| 特性 | switch语句 | if-else语句 |
|---|---|---|
| 适用条件 | 单一变量与多个常量比较 | 复杂条件、范围判断 |
| 效率 | 通常更高(可能使用跳转表) | 需要依次判断 |
| 可读性 | 多分支时更清晰 | 复杂条件时更直观 |
| 条件类型 | 仅整型 | 任意表达式 |
| 默认处理 | 有default分支 | 需要显式写else |
选择建议:
- 当判断单一变量与多个明确值时,优先使用switch
- 当条件复杂或需要范围判断时,使用if-else
- 在嵌入式系统中,对性能敏感的多分支优先考虑switch
4.3 switch使用技巧与陷阱
合并case:
c复制switch (month) {
case 1:
case 3:
case 5:
case 7:
case 8:
case 10:
case 12:
days = 31;
break;
case 4:
case 6:
case 9:
case 11:
days = 30;
break;
case 2:
days = (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0) ? 29 : 28;
break;
default:
days = -1; // 错误月份
}
常见陷阱:
-
忘记break导致case穿透
c复制switch (val) { case 1: printf("1"); // 忘记break case 2: printf("2"); // val为1时会执行到这里 break; } -
default位置不当
c复制switch (val) { default: // 放在开头必须加break printf("default"); break; case 1: printf("1"); break; } -
使用变量作为case常量
c复制const int x = 10; // 正确,const常量 int y = 20; // 错误,非常量 switch (val) { case x: ... break; // 正确 case y: ... break; // 错误 }
5. 实战应用与性能优化
5.1 典型应用案例
案例1:闰年判断
c复制int is_leap_year(int year) {
if ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)) {
return 1;
} else {
return 0;
}
}
案例2:计算月份天数
c复制int get_days_of_month(int year, int month) {
switch (month) {
case 1: case 3: case 5: case 7: case 8: case 10: case 12:
return 31;
case 4: case 6: case 9: case 11:
return 30;
case 2:
return is_leap_year(year) ? 29 : 28;
default:
return -1; // 无效月份
}
}
案例3:简单计算器
c复制void calculator(double a, double b, char 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.0) {
printf("%.2f\n", a / b);
} else {
printf("除数不能为0\n");
}
break;
default:
printf("无效运算符\n");
}
}
5.2 性能优化建议
-
条件顺序优化:
- 将最可能为真的条件放在前面
- 将简单的条件放在前面(计算量小的条件)
-
减少条件嵌套:
c复制// 不推荐 - 深层嵌套 if (cond1) { if (cond2) { if (cond3) { // ... } } } // 推荐 - 提前返回 if (!cond1) return; if (!cond2) return; if (!cond3) return; // ... -
使用查表法替代多重if:
c复制// 传统if-else if (val == 1) return 'A'; else if (val == 2) return 'B'; // ... // 查表法 const char table[] = {'A', 'B', ...}; if (val >= 1 && val <= MAX) { return table[val-1]; } -
利用短路特性优化:
c复制// 安全访问指针成员 if (ptr != NULL && ptr->data > threshold) {...} // 避免不必要的计算 if (x != 0 && y/x > 1) {...}
5.3 调试技巧与常见错误
-
常见错误类型:
- 条件表达式错误(如误用=代替==)
- 缺少break导致case穿透
- 边界条件处理不当
- 浮点数比较精度问题
-
调试方法:
- 使用printf打印关键变量值
- 添加临时变量简化复杂表达式
- 单元测试覆盖边界条件
-
防御性编程建议:
- 总是处理default情况
- 检查输入参数有效性
- 添加断言验证关键假设
c复制// 防御性编程示例
int safe_divide(int a, int b) {
if (b == 0) {
fprintf(stderr, "除数不能为0\n");
return 0;
}
return a / b;
}
在嵌入式系统开发中,选择结构的高效使用直接影响系统性能和可靠性。通过深入理解if和switch的工作原理,掌握它们的适用场景和优化技巧,可以写出更高效、更健壮的代码。
