切换 if-else 语句的优势

Posted

技术标签:

【中文标题】切换 if-else 语句的优势【英文标题】:Advantage of switch over if-else statement 【发布时间】:2010-09-11 00:15:42 【问题描述】:

对于 30 个 unsigned 枚举使用 switch 语句与使用 if 语句的最佳做法是什么,其中大约 10 个具有预期的操作(目前是相同的操作)。需要考虑性能和空间,但并不重要。我已经抽象了 sn-p,所以不要因为命名约定而讨厌我。

switch声明:

// numError is an error enumeration type, with 0 being the non-error case
// fire_special_event() is a stub method for the shared processing

switch (numError)
  
  case ERROR_01 :  // intentional fall-through
  case ERROR_07 :  // intentional fall-through
  case ERROR_0A :  // intentional fall-through
  case ERROR_10 :  // intentional fall-through
  case ERROR_15 :  // intentional fall-through
  case ERROR_16 :  // intentional fall-through
  case ERROR_20 :
  
     fire_special_event();
  
  break;

  default:
  
    // error codes that require no additional action
  
  break;       

if声明:

if ((ERROR_01 == numError)  ||
    (ERROR_07 == numError)  ||
    (ERROR_0A == numError)  || 
    (ERROR_10 == numError)  ||
    (ERROR_15 == numError)  ||
    (ERROR_16 == numError)  ||
    (ERROR_20 == numError))

  fire_special_event();

【问题讨论】:

当然,您可以从生成最高效代码的角度看到它,但任何现代编译器都应该同样高效。说到底,这更多是自行车棚颜色的问题。 我不同意,我不认为这是主观的。一个简单的 ASM 差异很重要,在许多情况下,您不能忽略几秒钟的优化。在这个问题中,这不是一场宗教战争或辩论,有合理的解释为什么会更快,只需阅读公认的答案。 哪个更快:***.com/questions/6805026/is-switch-faster-than-if @RichardFranks 题外话:谢谢!你是我见过的第一个对 SO 进行审核的人 【参考方案1】:

使用开关。

在最坏的情况下,编译器将生成与 if-else 链相同的代码,因此您不会丢失任何内容。如果有疑问,请将最常见的情况放在 switch 语句中。

在最好的情况下,优化器可能会找到更好的方法来生成代码。编译器所做的常见事情是构建二叉决策树(在一般情况下保存比较和跳转)或简单地构建一个跳转表(根本不进行比较)。

【讨论】:

技术上仍然会有一个比较,以确保枚举的值位于跳转表内。 杰普。确实如此。不过,打开枚举并处理所有情况可能会摆脱最后一次比较。 请注意,理论上可以分析出一系列 if 与编译器的 switch 相同,但为什么要冒险呢?通过使用开关,您可以准确地传达您想要的内容,这确实使代码生成更容易。 jakoben:可以这样做,但仅限于类似 switch 的 if/else 链。实际上,这些不会发生,因为程序员使用 switch。我深入研究了编译器技术并相信我:找到这种“无用”的结构需要很多时间。对于编译器人员来说,这样的优化确实没有意义。 @NilsPipenbrinck 在模板元编程中易于构建伪递归if-else 链,以及生成switchcase 链的难度,映射可能变得更加重要. (是的,古老的评论,但网络是永远的,或者至少到下周二)【参考方案2】:

对于您在示例中提供的特殊情况,最清晰的代码可能是:

if (RequiresSpecialEvent(numError))
    fire_special_event();

显然,这只是将问题转移到代码的不同区域,但现在您有机会重用此测试。对于如何解决它,您还有更多选择。你可以使用 std::set,例如:

bool RequiresSpecialEvent(int numError)

    return specialSet.find(numError) != specialSet.end();

我并不是说这是 RequiresSpecialEvent 的最佳实现,只是它是一个选项。您仍然可以使用 switch 或 if-else 链,或查找表,或对值进行一些位操作,等等。您的决策过程变得越模糊,您从一个独立的函数中获得的价值就越大。

【讨论】:

这是真的。可读性比 switch 和 if 语句都要好得多。我实际上打算自己回答这样的问题,但你打败了我。 :-) 如果你的枚举值都很小,那么你不需要哈希,只需要一个表。例如const std::bitset<MAXERR> specialerror(initializer);if (specialerror[numError]) fire_special_event(); 一起使用。如果您想要边界检查,bitset::test(size_t) 将在超出边界值时抛出异常。 (bitset::operator[] 不进行范围检查)。 cplusplus.com/reference/bitset/bitset/test。这可能会优于编译器生成的实现switch 的跳转表,尤其是。在非特殊情况下,这将是一个未采用的分支。 @PeterCordes 我仍然认为最好将表格放入自己的函数中。正如我所说,当你这样做时,会有 很多 选项打开,我没有尝试将它们全部列举出来。 @MarkRansom:我并不是不同意抽象它。既然您使用std::set 提供了一个示例实现,我想我会指出这可能是一个糟糕的选择。事实证明,gcc 已经编译了 OP 的代码来测试 32 位立即数的位图。神螺栓:goo.gl/qjjv0e。 gcc 5.2 甚至会为if 版本执行此操作。此外,最近的 gcc 将使用位测试指令 bt,而不是将 1 位放在正确的位置并使用 test reg, imm32 这个立即常数位图是一个巨大的胜利,因为位图上没有缓存未命中。如果“特殊”错误代码都在 64 或更小的范围内,它会起作用。 (或 32 用于遗留 32 位代码。)编译器减去最小大小写值,如果它不为零。要点是最近的编译器足够聪明,你可能会从你使用的任何逻辑中得到好的代码,除非你告诉它使用庞大的数据结构。【参考方案3】:

切换更快。

只需在一个循环中尝试 if/else-ing 30 个不同的值,然后使用 switch 将其与相同的代码进行比较,看看 switch 的速度有多快。

现在,switch 有一个真正的问题:switch 必须在编译时知道每个 case 中的值。这意味着下面的代码:

// WON'T COMPILE
extern const int MY_VALUE ;

void doSomething(const int p_iValue)

    switch(p_iValue)
    
       case MY_VALUE : /* do something */ ; break ;
       default : /* do something else */ ; break ;
    

不会编译。

然后大多数人将使用定义(啊!),而其他人将在同一个编译单元中声明和定义常量变量。例如:

// WILL COMPILE
const int MY_VALUE = 25 ;

void doSomething(const int p_iValue)

    switch(p_iValue)
    
       case MY_VALUE : /* do something */ ; break ;
       default : /* do something else */ ; break ;
    

所以,最终,开发者必须在“速度+清晰度”与“代码耦合”之间做出选择。

(并不是说开关不能写得像地狱一样令人困惑......我目前看到的大多数开关都属于这种“令人困惑”的类别”......但这是另一个故事......)

编辑 2008-09-21:

bk1e 添加了以下注释:“在头文件中将常量定义为枚举是另一种处理方式”。

当然可以。

extern 类型的意义在于将值与源解耦。将此值定义为宏、简单的 const int 声明,甚至是枚举,都会产生内联值的副作用。因此,如果定义、枚举值或 const int 值发生变化,则需要重新编译。 extern 声明意味着在值变化的情况下不需要重新编译,但另一方面,使得无法使用 switch。结论是使用 switch 会增加 switch 代码和用作 case 的变量之间的耦合。没问题的时候再用switch。如果不是,那就不足为奇了。

.

编辑 2013-01-15:

Vlad Lazarenko 评论了我的回答,给出了他深入研究 switch 生成的汇编代码的链接。很开明:http://lazarenko.me/switch/

【讨论】:

在头文件中将常量定义为枚举是另一种处理方式。 开关是not always faster。 @Vlad Lazarenko:感谢您的链接!这是一本非常有趣的书。 @AhmedHussein user404725 的链接已失效。谢天谢地,我在 WayBack 机器中找到了它:web.archive.org/web/20131111091431/http://lazarenko.me/2013/01/…。的确,WayBack Machine 可以说是一件好事。【参考方案4】:

编译器无论如何都会对其进行优化 - 选择开关,因为它是最易读的。

【讨论】:

编译器可能不会触及 if-then-else。事实上,gcc 肯定不会这样做(这是有充分理由的)。 Clang 会将这两种情况优化为二分搜索。例如,请参阅this。【参考方案5】:

Switch,如果只是为了可读性。在我看来,巨大的 if 语句更难维护,也更难阅读。

ERROR_01 : // 故意掉线

(ERROR_01 == numError) ||

后者比前者更容易出错,并且需要更多的输入和格式设置。

【讨论】:

【参考方案6】:

代码可读性。如果您想知道什么性能更好,请使用分析器,因为优化和编译器各不相同,而且性能问题很少出现在人们认为的地方。

【讨论】:

【参考方案7】:

编译器非常擅长优化switch。最近的 gcc 还擅长优化 if 中的一堆条件。

我在godbolt上做了一些测试用例。

case 值组合在一起时,gcc、clang 和 icc 都足够聪明,可以使用位图来检查一个值是否是特殊值之一。

例如gcc 5.2 -O3 将switch 编译为(和if 非常相似):

errhandler_switch(errtype):  # gcc 5.2 -O3
    cmpl    $32, %edi
    ja  .L5
    movabsq $4301325442, %rax   # highest set bit is bit 32 (the 33rd bit)
    btq %rdi, %rax
    jc  .L10
.L5:
    rep ret
.L10:
    jmp fire_special_event()

请注意,位图是即时数据,因此没有潜在的数据缓存未命中访问它或跳转表。

gcc 4.9.2 -O3 将switch 编译为位图,但使用 mov/shift 编译1U<<errNumber。它将if 版本编译为一系列分支。

errhandler_switch(errtype):  # gcc 4.9.2 -O3
    leal    -1(%rdi), %ecx
    cmpl    $31, %ecx    # cmpl $32, %edi  wouldn't have to wait an extra cycle for lea's output.
              # However, register read ports are limited on pre-SnB Intel
    ja  .L5
    movl    $1, %eax
    salq    %cl, %rax   # with -march=haswell, it will use BMI's shlx to avoid moving the shift count into ecx
    testl   $2150662721, %eax
    jne .L10
.L5:
    rep ret
.L10:
    jmp fire_special_event()

注意它是如何从 errNumber 中减去 1 的(使用 lea 将该操作与移动相结合)。这让它可以将位图适合 32 位立即数,避免需要更多指令字节的 64 位立即数 movabsq

更短的(机器代码)序列是:

    cmpl    $32, %edi
    ja  .L5
    mov     $2150662721, %eax
    dec     %edi   # movabsq and btq is fewer instructions / fewer Intel uops, but this saves several bytes
    bt     %edi, %eax
    jc  fire_special_event
.L5:
    ret

(使用jc fire_special_event的失败无处不在,是a compiler bug。)

rep ret 用于分支目标和以下条件分支,以使旧的 AMD K8 和 K10(推土机前)受益:What does `rep ret` mean?。没有它,分支预测在那些过时的 CPU 上就无法正常工作。

bt(位测试)带有寄存器 arg 很快。它结合了将 1 左移 errNumber 位和执行 test 的工作,但仍然有 1 个周期延迟并且只有一个 Intel uop。使用内存 arg 很慢,因为它的方式过于 CISC 语义:对于“位字符串”的内存操作数,要测试的字节的地址是根据另一个 arg(除以 8)计算的,并且是'不限于内存操作数指向的 1、2、4 或 8 字节块。

从 Agner Fog's instruction tables 开始,可变计数移位指令比最新 Intel 上的 bt 慢(2 uop 而不是 1,并且 shift 不能完成其他所有需要的操作)。

【讨论】:

【参考方案8】:

使用 switch,这是它的用途和程序员的期望。

不过,我会将多余的案例标签放入其中 - 只是为了让人们感到舒服,我试图记住何时/什么规则将它们排除在外。 你不希望下一个从事它的程序员不得不对语言细节做任何不必要的思考(几个月后可能就是你了!)

【讨论】:

【参考方案9】:

IMO 这是一个完美的例子,说明了切换失败的目的。

【讨论】:

在 c# 中,这是唯一发生跌倒思想的情况。很好的论据。【参考方案10】:

它们同样工作得很好。考虑到现代编译器,性能大致相同。

我更喜欢 if 语句而不是 case 语句,因为它们更易读、更灵活——您可以添加其他不基于数字相等的条件,例如“|| max

【讨论】:

【参考方案11】:

如果您的案例将来可能会保持分组状态(如果多个案例对应一个结果),那么这种转换可能会更易于阅读和维护。

【讨论】:

【参考方案12】:

switch 绝对是首选。查看开关的案例列表并确定它在做什么比阅读长 if 条件更容易。

if 条件中的重复项很难看。假设== 之一写成!=;你会注意到吗?或者如果 'numError' 的一个实例被写成 'nmuError',它恰好编译?

我通常更喜欢使用多态性而不是 switch,但是没有更多的上下文细节,很难说。

至于性能,您最好的办法是使用分析器来衡量您的应用程序在与您在野外所期望的条件相似的条件下的性能。否则,您可能在错误的地方和错误的方式进行优化。

【讨论】:

【参考方案13】:

我同意交换机解决方案的紧凑性,但 IMO 您在此处劫持交换机。 开关的目的是根据值进行不同处理。 如果你必须用伪代码解释你的算法,你会使用 if,因为从语义上讲,就是这样:if whatever_error do this... 因此,除非您打算在某天更改代码以针对每个错误使用特定代码,否则我会使用 if

【讨论】:

我不同意,原因与我不同意失败案例的原因相同。我将开关读作“在 01、07、0A、10、15、16 和 20 火灾特殊事件的情况下”。没有其他部分的内容。,这只是 C++ 语法的产物,您在其中为每个值重复 'case' 关键字。【参考方案14】:

我不确定最佳实践,但我会使用 switch - 然后通过“默认”捕获有意的失败

【讨论】:

【参考方案15】:

在美学上我倾向于这种方法。

unsigned int special_events[] = 
    ERROR_01,
    ERROR_07,
    ERROR_0A,
    ERROR_10,
    ERROR_15,
    ERROR_16,
    ERROR_20
 ;
 int special_events_length = sizeof (special_events) / sizeof (unsigned int);

 void process_event(unsigned int numError) 
     for (int i = 0; i < special_events_length; i++) 
         if (numError == special_events[i]) 
             fire_special_event();
             break;
          
     
  

让数据更智能一点,这样我们就可以让逻辑更笨一点。

我意识到它看起来很奇怪。这是灵感(来自我在 Python 中的做法):

special_events = [
    ERROR_01,
    ERROR_07,
    ERROR_0A,
    ERROR_10,
    ERROR_15,
    ERROR_16,
    ERROR_20,
    ]
def process_event(numError):
    if numError in special_events:
         fire_special_event()

【讨论】:

一种语言的语法确实会影响我们如何实现解决方案... => 它在 C 中看起来很丑,在 Python 中看起来很好。 :) 使用位图?如果 error_0a 是 0x0a 等,您可以将它们作为长长的位。 long long special_events=1LL 呸。您已将 O(1) 最坏情况操作(如果生成跳转表)转换为 O(N) 最坏情况(其中 N 是处理的案例数),并且您使用了breakcase 之外(是的,一个小罪,但仍然是一个罪)。 :) 恶心?他说性能和空间并不重要。我只是提出另一种看待问题的方式。如果我们可以用人类可以少思考的方式来表示问题,那么我通常不在乎这是否意味着计算机必须多思考。【参考方案16】:
while (true) != while (loop)

可能第一个被编译器优化了,这可以解释为什么第二个循环在增加循环次数时会变慢。

【讨论】:

这似乎是对 McAnix 回答的评论。这只是尝试将 ifswitch 计时作为 Java 中的循环结束条件的问题之一。【参考方案17】:

很抱歉不同意当前接受的答案。这是 2021 年。现代编译器及其优化器不应再区分 switch 和等效的 if-chain。如果他们仍然这样做,并且为任一变体创建了优化不佳的代码,那么请写信给编译器供应商(或在此处公开,这具有更高的受尊重变化),但不要让微优化影响您的编码风格。

所以,如果你使用:

switch (numError)  case ERROR_A: case ERROR_B: ... 

或:

if(numError == ERROR_A || numError == ERROR_B || ...)  ... 

或:

template<typename C, typename EL>
bool has(const C& cont, const EL& el) 
    return std::find(cont.begin(), cont.end(), el) != cont.end();


constexpr std::array errList =  ERROR_A, ERROR_B, ... ;
if(has(errList, rnd))  ... 

在执行速度方面应该没有区别。但是根据您正在从事的项目,它们可能会在编码清晰度和代码可维护性方面产生重大影响。例如,如果您必须在代码的许多地方检查某个错误列表,则模板化的has() 可能更容易维护,因为 errList 只需要在一个地方更新。

谈到当前的编译器,我已经用clang++ -O3 -std=c++1z(版本10和11)和g++ -O3 -std=c++1z编译了下面引用的测试代码。两个 clang 版本都给出了类似的编译代码和执行时间。所以我从现在开始只谈论版本 11。最值得注意的是,functionA()(使用if)和functionB()(使用switch)产生与clang完全相同的汇编输出!并且functionC() 使用了跳台,尽管许多其他海报认为跳台是switch 的独有功能。然而,尽管许多人认为跳转表是最佳的,但这实际上是clang 上最慢的解决方案:functionC() 需要比functionA()functionB() 多20% 的执行时间。

手动优化版本functionH() 是迄今为止clang 上最快的版本。它甚至部分展开循环,在每个循环上进行两次迭代。

实际上,clang 计算了位域,它在functionH()functionA()functionB() 中也明确提供。但是,它在functionA()functionB() 中使用了条件分支,这使得这些分支变得缓慢,因为分支预测经常失败,而它在functionH() 中使用了效率更高的adc(“带进位相加”)。虽然它未能在其他变体中应用这种明显的优化,但我不知道。

g++ 生成的代码看起来比clang 的代码复杂得多——但实际上functionA() 的运行速度要快一些,functionC() 的运行速度要快得多。在非手动优化的函数中,functionC()g++ 上最快的,并且比 clang 上的任何函数都快。相反,functionH() 在使用g++ 而不是clang 编译时需要两倍的执行时间,主要是因为g++ 不进行循环展开。

以下是详细结果:

clang:
functionA: 109877 3627
functionB: 109877 3626
functionC: 109877 4192
functionH: 109877 524

g++:
functionA: 109877 3337
functionB: 109877 4668
functionC: 109877 2890
functionH: 109877 982

如果在整个代码中将常量32 更改为63,则性能会发生巨大变化:

clang:
functionA: 106943 1435
functionB: 106943 1436
functionC: 106943 4191
functionH: 106943 524

g++:
functionA: 106943 1265
functionB: 106943 4481
functionC: 106943 2804
functionH: 106943 1038

加速的原因是,如果最高测试值为 63,编译器会删除一些不必要的绑定检查,因为无论如何rnd 的值绑定到 63。请注意,删除绑定检查后,在 g++ 上使用简单的 if() 的非优化 functionA() 的执行速度几乎与手动优化的 functionH() 一样快,并且它还产生相当相似的汇编程序输出。

结论是什么?如果您大量手动优化和测试编译器,您将获得最快的解决方案。任何假设 switchif 更好,都是无效的 - 它们在 clang 上是相同的。用于检查array 值的简单编码解决方案实际上是g++ 上最快的情况(如果省略手动优化和按事件匹配列表的最后一个值)。

未来的编译器版本会越来越好地优化你的代码,更接近你的手工优化。所以不要在这上面浪费你的时间,除非周期对你来说真的很重要。

这里是测试代码:

#include <iostream>
#include <chrono>
#include <limits>
#include <array>
#include <algorithm>

unsigned long long functionA() 
    unsigned long long cnt = 0;

    for(unsigned long long i = 0; i < 1000000; i++) 
        unsigned char rnd = (((i * (i >> 3)) >> 8) ^ i) & 63;
        if(rnd == 1 || rnd == 7 || rnd == 10 || rnd == 16 ||
           rnd == 21 || rnd == 22 || rnd == 63)
        
            cnt += 1;
        
    

    return cnt;


unsigned long long functionB() 
    unsigned long long cnt = 0;

    for(unsigned long long i = 0; i < 1000000; i++) 
        unsigned char rnd = (((i * (i >> 3)) >> 8) ^ i) & 63;
        switch(rnd) 
        case 1:
        case 7:
        case 10:
        case 16:
        case 21:
        case 22:
        case 63:
            cnt++;
            break;
        
    

    return cnt;


template<typename C, typename EL>
bool has(const C& cont, const EL& el) 
    return std::find(cont.begin(), cont.end(), el) != cont.end();


unsigned long long functionC() 
    unsigned long long cnt = 0;
    constexpr std::array errList  1, 7, 10, 16, 21, 22, 63 ;

    for(unsigned long long i = 0; i < 1000000; i++) 
        unsigned char rnd = (((i * (i >> 3)) >> 8) ^ i) & 63;
        cnt += has(errList, rnd);
    

    return cnt;


// Hand optimized version (manually created bitfield):
unsigned long long functionH() 
    unsigned long long cnt = 0;

    const unsigned long long bitfield =
        (1ULL << 1) +
        (1ULL << 7) +
        (1ULL << 10) +
        (1ULL << 16) +
        (1ULL << 21) +
        (1ULL << 22) +
        (1ULL << 63);

    for(unsigned long long i = 0; i < 1000000; i++) 
        unsigned char rnd = (((i * (i >> 3)) >> 8) ^ i) & 63;
        if(bitfield & (1ULL << rnd)) 
            cnt += 1;
        
    

    return cnt;


void timeit(unsigned long long (*function)(), const char* message)

    unsigned long long mintime = std::numeric_limits<unsigned long long>::max();
    unsigned long long fres = 0;

    for(int i = 0; i < 100; i++) 
        auto t1 = std::chrono::high_resolution_clock::now();
        fres = function();
        auto t2 = std::chrono::high_resolution_clock::now();

        auto duration = std::chrono::duration_cast<std::chrono::microseconds>(t2 - t1).count();
        if(duration < mintime) 
            mintime = duration;
        
    

    std::cout << message << fres << " " << mintime << std::endl;



int main(int argc, char* argv[]) 
    timeit(functionA, "functionA: ");
    timeit(functionB, "functionB: ");
    timeit(functionC, "functionC: ");
    timeit(functionH, "functionH: ");
    timeit(functionA, "functionA: ");
    timeit(functionB, "functionB: ");
    timeit(functionC, "functionC: ");
    timeit(functionH, "functionH: ");
    timeit(functionA, "functionA: ");
    timeit(functionB, "functionB: ");
    timeit(functionC, "functionC: ");
    timeit(functionH, "functionH: ");

    return 0;

【讨论】:

【参考方案18】:

为了清晰和惯例,我会选择 if 语句,尽管我确信有些人会不同意。毕竟,您想做某事if 某些条件为真!用一个动作切换似乎有点……不必要。

【讨论】:

【参考方案19】:

我不是告诉你有关速度和内存使用情况的人,但查看 switch 语句比大型 if 语句更容易理解(尤其是 2-3 个月后)

【讨论】:

【参考方案20】:

我会说使用 SWITCH。这样你只需要实现不同的结果。您的十个相同案例可以使用默认值。如果您只需要进行一项更改就是显式实施更改,则无需编辑默认值。在 SWITCH 中添加或删除案例也比编辑 IF 和 ELSEIF 容易得多。

switch(numerror)
    ERROR_20 :  fire_special_event();  break;
    default :  null;  break;

甚至可以根据可能性列表测试您的条件(在本例中为 numerror),可能是一个数组,因此您的 SWITCH 甚至不会被使用,除非肯定会有结果。

【讨论】:

总共有大约 30 个错误。 10 个需要特殊操作,所以我对不需要操作的 ~20 个错误使用默认值...【参考方案21】:

看到你只有 30 个错误代码,编写你自己的跳转表,然后你自己做出所有优化选择(跳转总是最快的),而不是希望编译器会做正确的事情。它还使代码非常小(除了跳转表的静态声明)。它还有一个附带的好处,即使用调试器,您可以根据需要在运行时修改行为,只需直接戳表数据即可。

【讨论】:

哇,这似乎是一种将简单问题变成复杂问题的方法。当编译器会为您做得很好时,为什么还要麻烦。另外,它显然是一个错误处理程序,因此它不太可能对速度如此关键。到目前为止,开关是最容易阅读和维护的东西。 表格并不复杂——事实上它可能比切换到代码更简单。声明确实提到性能是一个因素。 这听起来像是过早的优化。只要您保持枚举值小且连续,编译器就应该为您完成。像 Mark Ransom 在他的回答中建议的那样,将开关放在一个单独的函数中可以使使用它的代码既美观又小巧,同样可以带来小代码的好处。 另外,如果您要自己实现任何东西,请创建std::bitset&lt;MAXERR&gt; specialerror;,然后创建if (specialerror[err]) special_handler(); 。这将比跳转表更快,尤其是。在未采取的情况下。【参考方案22】:

我知道它很旧,但是

public class SwitchTest 
static final int max = 100000;

public static void main(String[] args) 

int counter1 = 0;
long start1 = 0l;
long total1 = 0l;

int counter2 = 0;
long start2 = 0l;
long total2 = 0l;
boolean loop = true;

start1 = System.currentTimeMillis();
while (true) 
  if (counter1 == max) 
    break;
   else 
    counter1++;
  

total1 = System.currentTimeMillis() - start1;

start2 = System.currentTimeMillis();
while (loop) 
  switch (counter2) 
    case max:
      loop = false;
      break;
    default:
      counter2++;
  

total2 = System.currentTimeMillis() - start2;

System.out.println("While if/else: " + total1 + "ms");
System.out.println("Switch: " + total2 + "ms");
System.out.println("Max Loops: " + max);

System.exit(0);


改变循环次数变化很大:

当 if/else: 5ms 开关:1ms 最大循环数:100000

当 if/else: 5ms 开关:3ms 最大循环数:1000000

当 if/else: 5ms 开关:14ms 最大循环数:10000000

当 if/else: 5ms 开关:149ms 最大循环数:100000000

(如果需要,可以添加更多语句)

【讨论】:

说得好,但是对不起,伙计,你用错了语言。改变语言变化很大;) if(max) break 循环以恒定时间运行,而不管循环计数如何?听起来 JIT 编译器足够聪明,可以将循环优化到 counter2=max。如果第一次调用currentTimeMillis 有更多开销,它可能比 switch 慢,因为不是所有的东西都是 JIT 编译的?以其他顺序放置循环可能会产生不同的结果。【参考方案23】:

到了编译程序的时候,不知道有没有什么区别。但至于程序本身以及代码尽量简单,我个人认为还是要看你想做什么。 if else if else 语句有其优点,我认为是:

允许您针对特定范围测试变量 您可以使用函数(标准库或个人)作为条件。

(示例:

`int a;
 cout<<"enter value:\n";
 cin>>a;

 if( a > 0 && a < 5)
   
     cout<<"a is between 0, 5\n";

   else if(a > 5 && a < 10)

     cout<<"a is between 5,10\n";

   else

       "a is not an integer, or is not in range 0,10\n";

但是,If else if else 语句可能会很快变得复杂和混乱(尽管您已尽了最大努力)。 Switch 语句往往更清晰、更简洁、更易于阅读;但只能用于针对特定值进行测试(例如:

`int a;
 cout<<"enter value:\n";
 cin>>a;

 switch(a)
 
    case 0:
    case 1:
    case 2: 
    case 3:
    case 4:
    case 5:
        cout<<"a is between 0,5 and equals: "<<a<<"\n";
        break;
    //other case statements
    default:
        cout<<"a is not between the range or is not a good value\n"
        break;

我更喜欢 if - else if - else 语句,但这完全取决于你。如果您想使用函数作为条件,或者您想针对范围、数组或向量进行测试和/或您不介意处理复杂的嵌套,我建议您使用 If else if else 块。如果您想针对单个值进行测试,或者您想要一个干净且易于阅读的块,我建议您使用 switch() 案例块。

【讨论】:

以上是关于切换 if-else 语句的优势的主要内容,如果未能解决你的问题,请参考以下文章

条件判断语句(if-else)

Javascript 条件返回语句(简写 if-else 语句)

优雅的替换if-else语句

优雅的替换if-else语句

[07 Go语言基础-if-else]

if-else语句