切换if-else语句的优势

时间:2020-03-06 14:24:01  来源:igfitidea点击:

对于30个无符号枚举,使用switch语句与if语句的最佳实践是什么,其中约有10个具有预期的动作(目前是相同的动作)。性能和空间需要考虑,但不是关键。我已经摘录了摘要,所以不要因为命名约定而讨厌我。

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

解决方案

编译器将对其进行最优化,因为它最易读。

开关(如果仅出于可读性考虑)。在我看来,如果语句难以维护且难以阅读,那将是一个巨大的选择。

ERROR_01://故意掉线

或者

(ERROR_01 == numError)||

与第一个相比,后者更容易出错,并且需要更多的键入和格式设置。

我不确定最佳做法,但我会使用switch,然后通过"默认"陷阱故意掉线

国际海事组织,这是造成交换机掉线的一个完美示例。

为了清楚和符合惯例,我会选择if语句,尽管我确信有些人会不同意。毕竟,我们想做一些"如果"条件为真的事情!用一个动作进行切换似乎有点...不必要。

如果将来案例可能仍会分组(如果一个案例对应一个以上的案例),那么该开关可能更易于阅读和维护。

请使用开关。 if语句所花费的时间与条件数成正比。

我不是要告诉我们有关速度和内存使用情况的人员,但是查看转换语句非常容易理解,而后会产生大量if语句(尤其是下线2-3个月)

使用开关。

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

在最佳情况下,优化器可能会找到一种更好的方式来生成代码。编译器执行的常见操作是建立一个二进制决策树(在平均情况下保存比较和跳转),或者简单地建立一个跳转表(完全不用比较就可以工作)。

他们同样表现良好。在使用现代编译器的情况下,性能几乎相同。

我更喜欢if语句而不是case语句,因为它们更具可读性,并且更灵活-我们可以添加不基于数字相等性的其他条件,例如" || max <min"。但是对于我们在此处发布的简单案例,这并不重要,只需执行我们最可读的内容即可。

开关绝对是首选。与读取long if条件相比,查看交换机的案例列表并确定其操作要容易得多。

" if"条件下的重复很难看。假设其中一个" =="被写成"!=";你会注意到吗?或者,如果将一个'numError'实例写入为'nmuError',而该实例恰好可以编译?

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

至于性能,最好的选择是使用探查器在与野外期望相似的条件下测量应用程序的性能。否则,我们可能会在错误的位置以错误的方式进行优化。

我会说使用SWITCH。这样,我们只需要实现不同的结果即可。十个相同案例可以使用默认值。如果我们需要做的一项更改是明确实现更改,则无需编辑默认值。与编辑IF和ELSEIF相比,从SWITCH添加或者删除案例还容易得多。

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

甚至可能针对各种可能性(一个数组)测试条件(在这种情况下为numerror),因此除非确定会出现结果,否则甚至不使用SWITCH。

我同意交换机解决方案的兼容性,但是IMO我们正在此处劫持交换机。
开关的目的是根据值进行不同的处理。
如果必须用伪代码解释算法,则可以使用if,因为从语义上讲就是这样:if what_error执行此操作...
因此,除非我们打算有一天将代码更改为每个错误都有特定的代码,否则我将使用if。

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

切换速度更快。

只需尝试/是否在循环内添加30个不同的值,然后使用switch将其与相同的代码进行比较,即可了解switch的速度。

现在,开关有一个真正的问题:开关必须在编译时知道每种情况下的值。这意味着下面的代码:

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

不会编译。

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

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

因此,最后,开发人员必须在"速度+清晰度"与"代码耦合"之间进行选择。

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

Edit 2008-09-21:
  
  bk1e added the following comment: "Defining constants as enums in a header file is another way to handle this".
  
  Of course it is.
  
  The point of an extern type was to decouple the value from the source. Defining this value as a macro, as a simple const int declaration, or even as an enum has the side-effect of inlining the value. Thus, should the define, the enum value, or the const int value change, a recompilation would be needed. The extern declaration means the there is no need to recompile in case of value change, but in the other hand, makes it impossible to use switch. The conclusion being Using switch will increase coupling between the switch code and the variables used as cases. When it is Ok, then use switch. When it isn't, then, no surprise.

Edit 2013-01-15:
  
  Vlad Lazarenko commented on my answer, giving a link to his in-depth study of the assembly code generated by a switch. Very enlightning: http://741mhz.com/switch/

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

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

if (RequiresSpecialEvent(numError))
    fire_special_event();

显然,这只是将问题移到了代码的不同区域,但是现在我们有机会重用此测试。我们还可以选择更多解决方案。我们可以使用std :: set,例如:

bool RequiresSpecialEvent(int numError)
{
    return specialSet.find(numError) != specialSet.end();
}

我并不是说这是RequiresSpecialEvent的最佳实现,只是它是一个选项。无论如何,我们仍然可以使用开关或者if-else链,或者查找表,或者对值进行一些位操作。决策过程越模糊,将其包含在独立的函数中将带来更多的价值。

从美学上讲,我倾向于这种方法。

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()

使用开关,这是它的用途,也是程序员的期望。

我会放入多余的箱子标签,尽管只是为了让人们感到舒适,但我试图记住何时/什么时候将它们排除在外。
我们不希望下一位从事此工作的程序员必须对语言细节进行任何不必要的思考(可能几个月后就可以了!)

我知道它很老但是

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:5毫秒
开关:1ms
最大循环数:100000

而如果/否则:5ms
开关:3ms
最多循环次数:1000000

而if / else:5毫秒
切换时间:14ms
最多循环次数:10000000

而if / else:5毫秒
切换:149ms
最大循环次数:100000000

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

while (true) != while (loop)

可能第一个循环是由编译器优化的,这可以解释为什么增加循环计数时第二个循环较慢。