核心问题:
if-else是否按顺序判断?sat -= 20会不会执行多次?switch是否只判断一次?两者能否等价替换?
1. 先看核心结论
1.1 if-else if-else 链
c
if (condition1) {
branch1();
}
else if (condition2) {
branch2();
}
else {
default_branch();
}
执行规则是:
- 从上到下计算条件;
- 遇到第一个结果为真的条件;
- 执行对应分支;
- 跳过后面的所有
else if和else。
因此:
一个完整的
if-else if-else链,最多执行一个分支体。
但是,命中分支之前检查过的条件表达式都会被求值。若条件中包含:
c
sat -= 20
i++
func()
这些具有副作用的表达式可能执行多次。
1.2 多个独立的 if
c
if (condition1) {
branch1();
}
if (condition2) {
branch2();
}
每个 if 都是独立语句,前一个 if 执行结束后,后一个 if 仍会继续判断。
因此:
多个独立
if可以执行多个分支体。
1.3 switch-case
c
switch (expression) {
case VALUE_1:
branch1();
break;
case VALUE_2:
branch2();
break;
default:
default_branch();
break;
}
执行规则是:
switch控制表达式只求值一次;- 使用求值结果选择匹配的
case标签; - 从匹配标签处开始向下执行;
- 遇到
break、return、goto或switch结束时离开当前执行路径。
因此:
switch控制表达式只计算一次,但匹配后可能发生贯穿执行。是否只执行一个分支,取决于控制流中是否及时退出。
2. if-else if 怎样执行
考虑:
c
if (sat > 60) {
printf("A\n");
}
else if (sat > 40) {
printf("B\n");
}
else if (sat > 20) {
printf("C\n");
}
else {
printf("D\n");
}
假设:
c
sat = 50;
执行过程为:
text
sat > 60 → 假
sat > 40 → 真
执行 printf("B")
跳过后续 else if 和 else
#mermaid-svg-ZLjN3EHmOR8z9MwD{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-ZLjN3EHmOR8z9MwD .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-ZLjN3EHmOR8z9MwD .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-ZLjN3EHmOR8z9MwD .error-icon{fill:#552222;}#mermaid-svg-ZLjN3EHmOR8z9MwD .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-ZLjN3EHmOR8z9MwD .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-ZLjN3EHmOR8z9MwD .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-ZLjN3EHmOR8z9MwD .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-ZLjN3EHmOR8z9MwD .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-ZLjN3EHmOR8z9MwD .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-ZLjN3EHmOR8z9MwD .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-ZLjN3EHmOR8z9MwD .marker{fill:#333333;stroke:#333333;}#mermaid-svg-ZLjN3EHmOR8z9MwD .marker.cross{stroke:#333333;}#mermaid-svg-ZLjN3EHmOR8z9MwD svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-ZLjN3EHmOR8z9MwD p{margin:0;}#mermaid-svg-ZLjN3EHmOR8z9MwD .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-ZLjN3EHmOR8z9MwD .cluster-label text{fill:#333;}#mermaid-svg-ZLjN3EHmOR8z9MwD .cluster-label span{color:#333;}#mermaid-svg-ZLjN3EHmOR8z9MwD .cluster-label span p{background-color:transparent;}#mermaid-svg-ZLjN3EHmOR8z9MwD .label text,#mermaid-svg-ZLjN3EHmOR8z9MwD span{fill:#333;color:#333;}#mermaid-svg-ZLjN3EHmOR8z9MwD .node rect,#mermaid-svg-ZLjN3EHmOR8z9MwD .node circle,#mermaid-svg-ZLjN3EHmOR8z9MwD .node ellipse,#mermaid-svg-ZLjN3EHmOR8z9MwD .node polygon,#mermaid-svg-ZLjN3EHmOR8z9MwD .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-ZLjN3EHmOR8z9MwD .rough-node .label text,#mermaid-svg-ZLjN3EHmOR8z9MwD .node .label text,#mermaid-svg-ZLjN3EHmOR8z9MwD .image-shape .label,#mermaid-svg-ZLjN3EHmOR8z9MwD .icon-shape .label{text-anchor:middle;}#mermaid-svg-ZLjN3EHmOR8z9MwD .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-ZLjN3EHmOR8z9MwD .rough-node .label,#mermaid-svg-ZLjN3EHmOR8z9MwD .node .label,#mermaid-svg-ZLjN3EHmOR8z9MwD .image-shape .label,#mermaid-svg-ZLjN3EHmOR8z9MwD .icon-shape .label{text-align:center;}#mermaid-svg-ZLjN3EHmOR8z9MwD .node.clickable{cursor:pointer;}#mermaid-svg-ZLjN3EHmOR8z9MwD .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-ZLjN3EHmOR8z9MwD .arrowheadPath{fill:#333333;}#mermaid-svg-ZLjN3EHmOR8z9MwD .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-ZLjN3EHmOR8z9MwD .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-ZLjN3EHmOR8z9MwD .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ZLjN3EHmOR8z9MwD .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-ZLjN3EHmOR8z9MwD .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ZLjN3EHmOR8z9MwD .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-ZLjN3EHmOR8z9MwD .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-ZLjN3EHmOR8z9MwD .cluster text{fill:#333;}#mermaid-svg-ZLjN3EHmOR8z9MwD .cluster span{color:#333;}#mermaid-svg-ZLjN3EHmOR8z9MwD div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-ZLjN3EHmOR8z9MwD .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-ZLjN3EHmOR8z9MwD rect.text{fill:none;stroke-width:0;}#mermaid-svg-ZLjN3EHmOR8z9MwD .icon-shape,#mermaid-svg-ZLjN3EHmOR8z9MwD .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ZLjN3EHmOR8z9MwD .icon-shape p,#mermaid-svg-ZLjN3EHmOR8z9MwD .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-ZLjN3EHmOR8z9MwD .icon-shape .label rect,#mermaid-svg-ZLjN3EHmOR8z9MwD .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ZLjN3EHmOR8z9MwD .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-ZLjN3EHmOR8z9MwD .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-ZLjN3EHmOR8z9MwD :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 是
否
是
否
是
否
开始
条件1为真?
执行分支1
条件2为真?
执行分支2
条件3为真?
执行分支3
执行else分支
结束
这里的"从上到下"是 C 语言语义保证的,不是编译器碰巧这样实现。
编译器可以优化机器指令,但不能改变程序可观察到的结果和副作用顺序。
3. sat -= 20 表示什么
表达式:
c
sat -= 20;
表示把 sat 当前值减去 20,再把结果写回 sat。
在常见整数场景下,可以直观理解为:
c
sat = sat - 20;
但复合赋值运算符有一个重要特征:
左操作数只求值一次。
例如:
c
array[index++] -= 20;
其中 index++ 只执行一次。
注意有符号整数溢出
如果 sat 是有符号整数,并且减法结果超出该类型可表示范围,会产生未定义行为。
例如,int 类型的 sat 已经接近 INT_MIN 时继续减 20,需要特别注意。
4. sat -= 20 到底会执行几次
关键不是它在源代码中出现了几次,而是程序实际执行了哪些语句和条件表达式。
4.1 写在 if-else if 的分支体中:最多一次
c
if (sat > 60) {
sat -= 20;
}
else if (sat > 40) {
sat -= 20;
}
else if (sat > 20) {
sat -= 20;
}
这是一个互斥分支链,最多只进入一个分支。
假设:
c
sat = 50;
执行过程:
text
sat > 60 → 假
sat > 40 → 真
执行第二个分支中的 sat -= 20
最终 sat = 30
因此,sat -= 20 只执行一次。
4.2 写在多个独立 if 中:可能多次执行
c
if (sat > 60) {
sat -= 20;
}
if (sat > 40) {
sat -= 20;
}
if (sat > 20) {
sat -= 20;
}
假设:
c
sat = 70;
执行过程:
text
第1个if:70 > 60 → 真,sat变为50
第2个if:50 > 40 → 真,sat变为30
第3个if:30 > 20 → 真,sat变为10
最终:
c
sat = 10;
-= 20 共执行三次。
#mermaid-svg-tk2Abzvnr9LK5siZ{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-tk2Abzvnr9LK5siZ .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-tk2Abzvnr9LK5siZ .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-tk2Abzvnr9LK5siZ .error-icon{fill:#552222;}#mermaid-svg-tk2Abzvnr9LK5siZ .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-tk2Abzvnr9LK5siZ .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-tk2Abzvnr9LK5siZ .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-tk2Abzvnr9LK5siZ .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-tk2Abzvnr9LK5siZ .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-tk2Abzvnr9LK5siZ .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-tk2Abzvnr9LK5siZ .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-tk2Abzvnr9LK5siZ .marker{fill:#333333;stroke:#333333;}#mermaid-svg-tk2Abzvnr9LK5siZ .marker.cross{stroke:#333333;}#mermaid-svg-tk2Abzvnr9LK5siZ svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-tk2Abzvnr9LK5siZ p{margin:0;}#mermaid-svg-tk2Abzvnr9LK5siZ .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-tk2Abzvnr9LK5siZ .cluster-label text{fill:#333;}#mermaid-svg-tk2Abzvnr9LK5siZ .cluster-label span{color:#333;}#mermaid-svg-tk2Abzvnr9LK5siZ .cluster-label span p{background-color:transparent;}#mermaid-svg-tk2Abzvnr9LK5siZ .label text,#mermaid-svg-tk2Abzvnr9LK5siZ span{fill:#333;color:#333;}#mermaid-svg-tk2Abzvnr9LK5siZ .node rect,#mermaid-svg-tk2Abzvnr9LK5siZ .node circle,#mermaid-svg-tk2Abzvnr9LK5siZ .node ellipse,#mermaid-svg-tk2Abzvnr9LK5siZ .node polygon,#mermaid-svg-tk2Abzvnr9LK5siZ .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-tk2Abzvnr9LK5siZ .rough-node .label text,#mermaid-svg-tk2Abzvnr9LK5siZ .node .label text,#mermaid-svg-tk2Abzvnr9LK5siZ .image-shape .label,#mermaid-svg-tk2Abzvnr9LK5siZ .icon-shape .label{text-anchor:middle;}#mermaid-svg-tk2Abzvnr9LK5siZ .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-tk2Abzvnr9LK5siZ .rough-node .label,#mermaid-svg-tk2Abzvnr9LK5siZ .node .label,#mermaid-svg-tk2Abzvnr9LK5siZ .image-shape .label,#mermaid-svg-tk2Abzvnr9LK5siZ .icon-shape .label{text-align:center;}#mermaid-svg-tk2Abzvnr9LK5siZ .node.clickable{cursor:pointer;}#mermaid-svg-tk2Abzvnr9LK5siZ .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-tk2Abzvnr9LK5siZ .arrowheadPath{fill:#333333;}#mermaid-svg-tk2Abzvnr9LK5siZ .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-tk2Abzvnr9LK5siZ .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-tk2Abzvnr9LK5siZ .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-tk2Abzvnr9LK5siZ .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-tk2Abzvnr9LK5siZ .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-tk2Abzvnr9LK5siZ .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-tk2Abzvnr9LK5siZ .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-tk2Abzvnr9LK5siZ .cluster text{fill:#333;}#mermaid-svg-tk2Abzvnr9LK5siZ .cluster span{color:#333;}#mermaid-svg-tk2Abzvnr9LK5siZ div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-tk2Abzvnr9LK5siZ .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-tk2Abzvnr9LK5siZ rect.text{fill:none;stroke-width:0;}#mermaid-svg-tk2Abzvnr9LK5siZ .icon-shape,#mermaid-svg-tk2Abzvnr9LK5siZ .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-tk2Abzvnr9LK5siZ .icon-shape p,#mermaid-svg-tk2Abzvnr9LK5siZ .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-tk2Abzvnr9LK5siZ .icon-shape .label rect,#mermaid-svg-tk2Abzvnr9LK5siZ .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-tk2Abzvnr9LK5siZ .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-tk2Abzvnr9LK5siZ .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-tk2Abzvnr9LK5siZ :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 真
假
真
假
真
假
开始
第1个if条件
执行第1个if
第2个if条件
执行第2个if
第3个if条件
执行第3个if
结束
所以:
text
if ... else if ...
与:
text
if ...
if ...
if ...
语义完全不同。
4.3 写在条件表达式中:每检查一次就执行一次
c
if ((sat -= 20) > 60) {
printf("A\n");
}
else if ((sat -= 20) > 40) {
printf("B\n");
}
else if ((sat -= 20) > 20) {
printf("C\n");
}
假设:
c
sat = 70;
执行过程:
text
第1个条件:
sat减20后变为50
50 > 60 → 假
第2个条件:
sat再次减20后变为30
30 > 40 → 假
第3个条件:
sat再次减20后变为10
10 > 20 → 假
因此,sat -= 20 执行三次。
虽然整个 if-else if 链最多执行一个分支体,但在找到该分支之前,可能求值多个条件表达式。
4.4 推荐写法:先修改,再判断
如果真实意图是只减一次,应写成:
c
sat -= 20;
if (sat > 60) {
printf("A\n");
}
else if (sat > 40) {
printf("B\n");
}
else if (sat > 20) {
printf("C\n");
}
else {
printf("D\n");
}
这种写法:
- 执行次数明确;
- 便于调试;
- 不容易因条件顺序变化而改变
sat的修改次数。
5. 条件中的短路求值
if 条件内部还可能使用:
c
&&
||
C 语言规定它们采用短路求值。
逻辑与 &&
c
if (a != 0 && b / a > 2) {
}
如果:
c
a != 0
为假,右侧:
c
b / a > 2
不会执行。
逻辑或 ||
c
if (flag != 0 || check_device()) {
}
如果:
c
flag != 0
为真,check_device() 不会调用。
因此,分析副作用执行次数时,不仅要看 if-else 的分支顺序,还要看单个条件内部的短路规则。
6. switch-case 怎样执行
基本形式:
c
switch (sat) {
case 1:
printf("A\n");
break;
case 2:
printf("B\n");
break;
case 3:
printf("C\n");
break;
default:
printf("D\n");
break;
}
#mermaid-svg-o5ws5gleW12nZziv{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-o5ws5gleW12nZziv .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-o5ws5gleW12nZziv .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-o5ws5gleW12nZziv .error-icon{fill:#552222;}#mermaid-svg-o5ws5gleW12nZziv .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-o5ws5gleW12nZziv .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-o5ws5gleW12nZziv .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-o5ws5gleW12nZziv .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-o5ws5gleW12nZziv .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-o5ws5gleW12nZziv .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-o5ws5gleW12nZziv .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-o5ws5gleW12nZziv .marker{fill:#333333;stroke:#333333;}#mermaid-svg-o5ws5gleW12nZziv .marker.cross{stroke:#333333;}#mermaid-svg-o5ws5gleW12nZziv svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-o5ws5gleW12nZziv p{margin:0;}#mermaid-svg-o5ws5gleW12nZziv .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-o5ws5gleW12nZziv .cluster-label text{fill:#333;}#mermaid-svg-o5ws5gleW12nZziv .cluster-label span{color:#333;}#mermaid-svg-o5ws5gleW12nZziv .cluster-label span p{background-color:transparent;}#mermaid-svg-o5ws5gleW12nZziv .label text,#mermaid-svg-o5ws5gleW12nZziv span{fill:#333;color:#333;}#mermaid-svg-o5ws5gleW12nZziv .node rect,#mermaid-svg-o5ws5gleW12nZziv .node circle,#mermaid-svg-o5ws5gleW12nZziv .node ellipse,#mermaid-svg-o5ws5gleW12nZziv .node polygon,#mermaid-svg-o5ws5gleW12nZziv .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-o5ws5gleW12nZziv .rough-node .label text,#mermaid-svg-o5ws5gleW12nZziv .node .label text,#mermaid-svg-o5ws5gleW12nZziv .image-shape .label,#mermaid-svg-o5ws5gleW12nZziv .icon-shape .label{text-anchor:middle;}#mermaid-svg-o5ws5gleW12nZziv .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-o5ws5gleW12nZziv .rough-node .label,#mermaid-svg-o5ws5gleW12nZziv .node .label,#mermaid-svg-o5ws5gleW12nZziv .image-shape .label,#mermaid-svg-o5ws5gleW12nZziv .icon-shape .label{text-align:center;}#mermaid-svg-o5ws5gleW12nZziv .node.clickable{cursor:pointer;}#mermaid-svg-o5ws5gleW12nZziv .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-o5ws5gleW12nZziv .arrowheadPath{fill:#333333;}#mermaid-svg-o5ws5gleW12nZziv .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-o5ws5gleW12nZziv .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-o5ws5gleW12nZziv .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-o5ws5gleW12nZziv .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-o5ws5gleW12nZziv .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-o5ws5gleW12nZziv .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-o5ws5gleW12nZziv .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-o5ws5gleW12nZziv .cluster text{fill:#333;}#mermaid-svg-o5ws5gleW12nZziv .cluster span{color:#333;}#mermaid-svg-o5ws5gleW12nZziv div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-o5ws5gleW12nZziv .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-o5ws5gleW12nZziv rect.text{fill:none;stroke-width:0;}#mermaid-svg-o5ws5gleW12nZziv .icon-shape,#mermaid-svg-o5ws5gleW12nZziv .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-o5ws5gleW12nZziv .icon-shape p,#mermaid-svg-o5ws5gleW12nZziv .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-o5ws5gleW12nZziv .icon-shape .label rect,#mermaid-svg-o5ws5gleW12nZziv .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-o5ws5gleW12nZziv .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-o5ws5gleW12nZziv .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-o5ws5gleW12nZziv :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} case 1
case 2
case 3
没有case匹配
计算switch控制表达式一次
选择匹配标签
从case 1开始执行
从case 2开始执行
从case 3开始执行
从default开始执行
遇到退出语句或switch结束
从 C 语言语义上看:
switch控制表达式只求值一次。
例如:
c
switch (sat -= 20) {
case 60:
printf("A\n");
break;
case 40:
printf("B\n");
break;
default:
printf("C\n");
break;
}
这里的 sat -= 20 只执行一次。
7. case 是否会被逐个计算
不会。
以下 case:
c
case 1:
case 2:
case 3:
是编译期确定的标签,不是在运行时依次执行的条件表达式。
运行时只计算:
c
switch (expression)
中的 expression。
然后程序根据结果选择对应标签。
case 的基本要求
在标准 C 中:
switch控制表达式必须具有整数类型或枚举类型;- 整数提升会先作用于控制表达式;
- 每个
case必须是整型常量表达式; - 同一个
switch中,转换后数值相同的case不能重复; - 最多只能有一个
default。
例如,下面代码非法:
c
int value = 2;
switch (sat) {
case value: /* value不是整型常量表达式 */
break;
}
8. switch 是否一定直接跳到目标 case
从源代码语义上,可以理解为:
text
控制表达式求值一次
→ 选择匹配标签
→ 从该标签处开始执行
但具体机器代码由编译器决定。
少量或稀疏的 case
c
case 1:
case 1000:
case 50000:
编译器可能生成:
- 线性比较;
- 二分比较;
- 条件跳转。
较多且连续的 case
c
case 0:
case 1:
case 2:
case 3:
case 4:
编译器可能生成跳转表:
text
结果值
→ 计算表下标
→ 间接跳转到目标代码
因此:
switch在语言层面只求值一次,但不能保证机器层面只执行一次比较。
9. switch 的贯穿执行
考虑:
c
switch (sat) {
case 1:
printf("A\n");
case 2:
printf("B\n");
case 3:
printf("C\n");
default:
printf("D\n");
}
假设:
c
sat = 2;
程序从 case 2 开始执行,然后继续向下:
text
输出B
输出C
输出D
这种行为称为:
text
fall-through
也叫贯穿或穿透。
9.1 使用 break 阻止贯穿
c
switch (sat) {
case 1:
printf("A\n");
break;
case 2:
printf("B\n");
break;
case 3:
printf("C\n");
break;
default:
printf("D\n");
break;
}
此时每条执行路径通常只执行一个分支。
default 位于最后时,末尾的 break 可以省略,但保留它通常有利于后续维护。
9.2 有意共享一个分支体
多个值需要执行同一逻辑时,可以写成:
c
switch (sat) {
case 1:
case 2:
case 3:
printf("sat属于1、2、3之一\n");
break;
default:
printf("其他值\n");
break;
}
这里多个 case 标签共同指向同一个分支体,不属于容易误解的贯穿逻辑。
9.3 default 不一定写在最后
下面代码合法:
c
switch (sat) {
default:
printf("other\n");
break;
case 1:
printf("one\n");
break;
}
default 的位置不影响"没有 case 匹配时选择它"的规则。
但是,进入某个标签后,代码仍按文本顺序向下执行。因此:
标签的匹配不依赖书写顺序,贯穿行为依赖书写顺序。
为了可读性,工程代码通常把 default 写在最后。
10. 两种语句的适用范围
10.1 if-else 适合复杂条件
c
if (sat > 20 && sat < 40) {
/* 范围判断 */
}
else if (sat == 50 || flag != 0) {
/* 多变量组合 */
}
else if (is_valid(sat)) {
/* 函数结果 */
}
if 可以判断:
- 大于、小于;
- 范围;
- 多变量组合;
- 函数返回值;
- 指针是否为空;
- 浮点数条件;
- 任意标量表达式的真假。
在 C 中:
text
0 → 假
非0值 → 真
10.2 switch-case 适合离散值分派
c
switch (system) {
case SYS_GPS:
process_gps();
break;
case SYS_GLO:
process_glo();
break;
case SYS_GAL:
process_gal();
break;
default:
process_unknown();
break;
}
它适合:
text
同一个整数或枚举表达式
与多个离散常量值进行相等匹配
10.3 switch 不能直接表达范围
以下写法不符合标准 C:
c
switch (sat) {
case sat > 20:
break;
}
范围判断应使用:
c
if (sat > 60) {
}
else if (sat > 40) {
}
else if (sat > 20) {
}
11. 两者什么时候可以等价替换
下面的 if-else if:
c
if (sat == 1) {
func1();
}
else if (sat == 2) {
func2();
}
else if (sat == 3) {
func3();
}
else {
func_default();
}
可以改写为:
c
switch (sat) {
case 1:
func1();
break;
case 2:
func2();
break;
case 3:
func3();
break;
default:
func_default();
break;
}
成立的主要条件是:
- 判断的是同一个表达式;
- 都是与整型常量进行相等比较;
- 各分支互斥;
switch正确处理贯穿;- 两种写法对控制表达式的求值次数相同;
- 不依赖条件表达式的特殊副作用。
11.1 函数调用可能导致不等价
c
if (get_type() == 1) {
func1();
}
else if (get_type() == 2) {
func2();
}
若第一个条件不成立,get_type() 可能调用两次。
改成:
c
switch (get_type()) {
case 1:
func1();
break;
case 2:
func2();
break;
}
get_type() 只调用一次。
若函数结果会变化或函数有副作用,两段代码不等价。
要保持"只求值一次"的清晰语义,可以先保存结果:
c
int type = get_type();
if (type == 1) {
func1();
}
else if (type == 2) {
func2();
}
11.2 不能直接改成 switch 的情况
范围判断
c
if (sat >= 1 && sat <= 10)
多变量组合
c
if (sat == 1 && flag == 2)
浮点判断
c
if (value > 0.001)
字符串比较
c
if (strcmp(name, "GPS") == 0)
条件带有不同副作用
c
if ((sat -= 20) == 1) {
}
else if ((sat -= 20) == 2) {
}
这里 sat -= 20 可能执行两次,不能直接替换为:
c
switch (sat -= 20)
12. 条件顺序为什么重要
考虑:
c
if (sat > 20) {
printf("大于20\n");
}
else if (sat > 40) {
printf("大于40\n");
}
第二个分支永远无法执行。
原因是任何满足:
c
sat > 40
的值也一定满足:
c
sat > 20
程序会先进入第一个分支。
范围从大到小时通常应写成:
c
if (sat > 60) {
}
else if (sat > 40) {
}
else if (sat > 20) {
}
因此:
if-else if的条件顺序本身就是程序逻辑的一部分。
13. 执行效率有差异吗
可能有,但不能只根据源码使用了 if 还是 switch 下结论。
if-else 的可能实现
编译器可能生成:
- 顺序比较;
- 条件跳转;
- 条件移动;
- 重新排列后的等价机器代码。
switch 的可能实现
编译器可能生成:
- 线性比较;
- 二分查找式比较;
- 跳转表;
- 混合策略。
影响性能的因素
- 分支数量;
case值是否连续;- 各分支命中概率;
- CPU 分支预测;
- 编译器和优化等级;
- 目标处理器;
- 分支体本身的计算量。
因此:
text
switch不一定比if快
if也不一定比switch慢
工程中应优先选择语义清晰的写法。只有在代码确实成为性能瓶颈时,才使用:
- 基准测试;
- 性能分析器;
- 编译器生成的汇编代码;
进行判断。
14. 对比表
| 对比项 | if-else if |
switch-case |
|---|---|---|
| 判断方式 | 任意真假条件 | 同一整数或枚举表达式匹配常量 |
| 表达式求值次数 | 每个实际检查到的条件分别求值 | 控制表达式只求值一次 |
| 选择规则 | 从上到下选择首个真条件 | 按求值结果选择标签 |
| 分支体执行数量 | 最多一个 | 无贯穿时一个,有贯穿时可多个 |
| 范围判断 | 支持 | 标准 C 不直接支持 |
| 多变量组合 | 支持 | 不适合 |
| 枚举分派 | 可以 | 很适合 |
| 字符串比较 | 可配合 strcmp |
不支持 |
| 浮点条件 | 支持条件判断 | 控制表达式不能是浮点类型 |
| 条件或标签顺序 | 条件顺序决定匹配结果 | 匹配不依赖标签顺序,贯穿依赖代码顺序 |
| 常见风险 | 副作用、条件遮蔽、多个独立 if 混用 |
漏写 break、意外贯穿 |
| 是否一定更快 | 否 | 否 |
15. 推荐代码风格
15.1 把副作用与判断分开
不推荐:
c
if ((sat -= 20) > 60) {
}
else if ((sat -= 20) > 40) {
}
推荐:
c
sat -= 20;
if (sat > 60) {
}
else if (sat > 40) {
}
15.2 互斥选择使用 else if
如果业务上只能执行一个分支:
c
if (condition1) {
}
else if (condition2) {
}
else {
}
如果多个条件可以同时成立并需要分别处理,才使用多个独立 if。
15.3 switch 中明确处理贯穿
c
switch (type) {
case TYPE_A:
handle_a();
break;
case TYPE_B:
handle_b();
break;
default:
handle_unknown();
break;
}
如果有意贯穿,应增加明确注释:
c
switch (type) {
case TYPE_A:
prepare_a();
/* fall through */
case TYPE_B:
process_common();
break;
default:
break;
}
15.4 枚举分派优先考虑 switch
c
typedef enum {
MODE_IDLE,
MODE_RUN,
MODE_ERROR
} mode_t;
switch (mode) {
case MODE_IDLE:
handle_idle();
break;
case MODE_RUN:
handle_run();
break;
case MODE_ERROR:
handle_error();
break;
default:
handle_invalid_mode();
break;
}
编译器还可能对遗漏的枚举分支给出警告,有利于代码维护。
16. sat -= 20 执行次数速查
| 写法位置 | 可能执行次数 |
|---|---|
if-else if 的各分支体 |
最多一次 |
多个独立 if 的分支体 |
可能多次 |
if-else if 的各条件中 |
每检查一个对应条件执行一次 |
switch (sat -= 20) |
一次 |
多个带 break 的 case 分支体 |
匹配路径中通常一次 |
没有及时退出的多个 case 分支体 |
可能因贯穿执行多次 |
17. 核心总结
text
if-else if
→ 条件从上到下求值
→ 首个真条件命中后停止
→ 最多执行一个分支体
text
多个独立if
→ 每个if都单独判断
→ 多个分支体都可能执行
text
switch-case
→ 控制表达式只求值一次
→ case标签不在运行时逐个求值
→ 从匹配标签开始执行
→ 需要正确处理贯穿
选择原则:
text
范围、复杂逻辑、多变量组合
→ 使用if-else
同一整数或枚举值的离散分派
→ 使用switch-case
对于带副作用的操作:
c
sat -= 20;
最好单独写成一条语句,再进行条件判断。这样执行次数最明确,也最不容易产生隐藏错误。