使用switch
语句与使用if
30个unsigned
枚举的语句的最佳实践是什么,其中大约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(); }
Nils Pipenbr.. 152
使用开关.
在最坏的情况下,编译器将生成与if-else链相同的代码,因此您不会丢失任何内容.如果有疑问,请将最常见的案例放在switch语句中.
在最好的情况下,优化器可能会找到更好的方法来生成代码.编译器所做的常见事情是构建二进制决策树(在一般情况下保存比较和跳转)或者只是构建一个跳转表(根本没有比较).
使用开关.
在最坏的情况下,编译器将生成与if-else链相同的代码,因此您不会丢失任何内容.如果有疑问,请将最常见的案例放在switch语句中.
在最好的情况下,优化器可能会找到更好的方法来生成代码.编译器所做的常见事情是构建二进制决策树(在一般情况下保存比较和跳转)或者只是构建一个跳转表(根本没有比较).
对于您在示例中提供的特殊情况,最清晰的代码可能是:
if (RequiresSpecialEvent(numError)) fire_special_event();
显然,这只会将问题移到代码的不同区域,但现在您有机会重用此测试.您还有更多选择如何解决它.你可以使用std :: set,例如:
bool RequiresSpecialEvent(int numError) { return specialSet.find(numError) != specialSet.end(); }
我并不是说这是RequiresSpecialEvent的最佳实现,只是它是一个选项.您仍然可以使用开关或if-else链,或查找表,或对值进行一些位操作,无论如何.您的决策过程越模糊,您在孤立函数中获得的价值就越大.
该开关是更快.
只需在循环中尝试if/else-ing 30个不同的值,并使用开关将其与相同的代码进行比较,以查看开关的速度.
现在,交换机有一个真正的问题:交换机必须在编译时知道每种情况下的值.这意味着以下代码:
// 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 ; } }
不会编译.
然后大多数人将使用定义(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 ; } }
因此,最终,开发人员必须在"速度+清晰度"与"代码耦合"之间进行选择.
(并不是说开关不能被写成令人困惑的地狱......我目前看到的大多数开关都是这个"令人困惑"的类别"......但这是另一个故事......)
编辑2008-09-21:
bk1e添加了以下注释:" 将常量定义为头文件中的枚举是另一种处理此问题的方法".
当然如此.
extern类型的要点是将值与源分离.将此值定义为宏,作为简单的const int声明,或者甚至作为枚举具有内联值的副作用.因此,如果define,enum值或const int值发生变化,则需要重新编译.extern声明意味着在价值变化的情况下不需要重新编译,但另一方面,使得无法使用开关.结论是使用开关将增加开关代码和用作情况的变量之间的耦合.如果是,则使用开关.如果不是,那就不足为奇了.
.
编辑2013-01-15:
Vlad Lazarenko评论了我的答案,给出了他对交换机生成的汇编代码的深入研究的链接.非常enlightning:http://741mhz.com/switch/
编译器无论如何都会对它进行优化 - 选择最易读的开关.
Switch,如果只是为了可读性.在我看来,巨大的if语句难以维护且难以阅读.
ERROR_01://故意落空
要么
(ERROR_01 == numError)||
后者更容易出错,需要比第一次更多的打字和格式化.
可读性代码.如果您想知道哪些性能更好,请使用分析器,因为优化和编译器各不相同,性能问题很少出现在人们认为的情况下.
使用开关,它是它的用途和程序员的期望.
我会把冗余的案例标签放进去 - 只是为了让人们感觉舒服,我试图记住什么时候/什么规则让他们离开.
你不希望下一个编程人员不得不对语言细节做任何不必要的思考(可能是你几个月后!)