C 语言 if-else 与 switch-case 分支语句对比

核心问题:if-else 是否按顺序判断?sat -= 20 会不会执行多次?switch 是否只判断一次?两者能否等价替换?


1. 先看核心结论

1.1 if-else if-else

c 复制代码
if (condition1) {
    branch1();
}
else if (condition2) {
    branch2();
}
else {
    default_branch();
}

执行规则是:

  1. 从上到下计算条件;
  2. 遇到第一个结果为真的条件;
  3. 执行对应分支;
  4. 跳过后面的所有 else ifelse

因此:

一个完整的 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;
}

执行规则是:

  1. switch 控制表达式只求值一次;
  2. 使用求值结果选择匹配的 case 标签;
  3. 从匹配标签处开始向下执行;
  4. 遇到 breakreturngotoswitch 结束时离开当前执行路径。

因此:

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;
}

成立的主要条件是:

  1. 判断的是同一个表达式;
  2. 都是与整型常量进行相等比较;
  3. 各分支互斥;
  4. switch 正确处理贯穿;
  5. 两种写法对控制表达式的求值次数相同;
  6. 不依赖条件表达式的特殊副作用。

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) 一次
多个带 breakcase 分支体 匹配路径中通常一次
没有及时退出的多个 case 分支体 可能因贯穿执行多次

17. 核心总结

text 复制代码
if-else if
→ 条件从上到下求值
→ 首个真条件命中后停止
→ 最多执行一个分支体
text 复制代码
多个独立if
→ 每个if都单独判断
→ 多个分支体都可能执行
text 复制代码
switch-case
→ 控制表达式只求值一次
→ case标签不在运行时逐个求值
→ 从匹配标签开始执行
→ 需要正确处理贯穿

选择原则:

text 复制代码
范围、复杂逻辑、多变量组合
→ 使用if-else

同一整数或枚举值的离散分派
→ 使用switch-case

对于带副作用的操作:

c 复制代码
sat -= 20;

最好单独写成一条语句,再进行条件判断。这样执行次数最明确,也最不容易产生隐藏错误。

相关推荐
噢,我明白了1 小时前
Java中日期和字符串的处理
java·开发语言·日期
-银雾鸢尾-1 小时前
C#中的泛型约束
开发语言·c#
无相求码1 小时前
指针的5层境界,从入门到劝退:C语言指针进阶全解析
c语言
雪碧透心凉_2 小时前
while 循环与循环嵌套
开发语言·python
乐观勇敢坚强的老彭2 小时前
信奥C++一维数组笔记
开发语言·c++·笔记
这就是佬们吗2 小时前
Python入门⑤-异常处理、文件操作与实战项目
开发语言·数据库·python·算法·pycharm
asdzx672 小时前
Python PDF 拆分实战指南:单页拆分与按需页码范围拆分
开发语言·python·pdf
C++ 老炮儿的技术栈2 小时前
基于MFC+原生GDI手动自绘仪表盘控件
c语言·c++·visual studio·控件·工业控制·gdi·自绘
孬甭_2 小时前
C++ vector
开发语言·c++