替换`while(1)`以简化分支

Phi*_* A. 55 c

我不时会用一个while(1)块来压扁一连串if..else不成比例的东西.它沿着这些方向发展.

而不是做:

// process 
if (success) {
  // process 
  if (success) {
    //process
    if (success) {
      // etc
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

我做:

while (1) {
  // process
  if (!success) break;
  // process
  if (!success) break;
  // process
  if (!success) break;
  // etc
  break;
}
Run Code Online (Sandbox Code Playgroud)

我对这个结尾的隐含跳跃感到有些恼火while.我能用更精简的构造逃脱(即最后没有break)吗?

我可以break用变量(或寄存器?)来交易最终结果.这不是更精简或更清晰.

int once = 1;
while (once--) {
  // process
  if (!success) break;
  // process
  if (!success) break;
  // process
  if (!success) break;
  // etc
}
Run Code Online (Sandbox Code Playgroud)

for循环看起来会好一些(C99):

for (int once = 1 ; once--; once) {
  // process
  if (!success) break;
  // process
  if (!success) break;
  // process
  if (!success) break;
  // etc
}
Run Code Online (Sandbox Code Playgroud)

我想过使用开关盒.尽管它会起作用,但看起来并没有好多少.

switch (1) { default:
  // process
  if (!success) break;
  // process
  if (!success) break;
  // process
  if (!success) break;
  // etc
}
Run Code Online (Sandbox Code Playgroud)

在这种特殊情况下,标签的概念似乎是无与伦比的.

// process
if (!success) goto end;
// process
if (!success) goto end;
// process
if (!success) goto end;
// etc

end:
Run Code Online (Sandbox Code Playgroud)

你们知道/使用的其他方法是什么?

oua*_*uah 146

你们知道/使用的其他方法是什么?

您可以将while循环封装在一个函数中(并在您拥有while循环的地方调用此函数):

static void process(void)
{
   // process
   if (!success) return;
   // process
   if (!success) return;
   // process
   if (!success) return;
   // process
}
Run Code Online (Sandbox Code Playgroud)

如果调用一次,任何中途正常的编译器(例如,即使gcc禁用优化)也会内联static函数.(当然,一些变量可能必须在process函数的词法范围内,在这种情况下,只需将它们作为函数的参数提供).

请注意,从上到下而不是水平编写代码(例如,嵌套的示例if)称为duffing.这里有一篇关于这个主题的好文章:

"从上到下阅读代码"

此外,在Linux内核编码风格中,针对水平代码有一个特定的警告:

"如果你需要超过3级的缩进,你还是搞砸了,应该修复你的程序"

  • +1这也是一个很好的解决方案.也许返回错误代码而不是`void`. (13认同)
  • @ABFORCE - 你真的知道单回归建议的目的是什么,或者你只是跟随邪教? (9认同)
  • @Davor the cult (5认同)
  • 见http://blog.codinghorror.com/flattening-arrow-code/ (2认同)

Fid*_*its 54

以下是一种非常类似于循环的方法,但最后不需要计数器或break语句.

do
{
    // process
    if (!success) break;
    // process
    if (!success) break;
    // process
    if (!success) break;
    ...
    // No need for a break statement here
}
while(0);
Run Code Online (Sandbox Code Playgroud)

  • 像这样的`while`循环是一种避免`goto`的奇怪方法.可以而且应该在OP提到的情况下使用`goto`. (64认同)
  • @FiddlingBits人们被教导不要使用`goto`,因为它可能导致复杂和混淆的代码.在这种特殊情况下,你基本上通过使用`do/while`来复制`goto`自然会做什么.如果不熟悉代码的人开始查看代码,他们将不知道为什么你有一个循环,直到遇到`while(0)`.它没有立即传达代码应该做的事情. (42认同)
  • 对goto的仇恨源于Dijkstra试图将结构化编程强制进入一个不需要它的世界.不使用goto不是"好习惯"; 这是一个40岁以上的无关性脱离了背景_.OP的用例实际上是goto的标准用法 - 一个是_right_语言功能:使代码更简洁,更简单,更快捷. (22认同)
  • 这是无限的转到; 中断的目标是明确的,不可改变的.使用goto,您可以拥有任意数量的目标目标 - break的限制性强制执行结构.我可以想象复制和粘贴代码块,省略更改标签名称,例如. (11认同)
  • @PavanYalamanchili:说得好,它至少值得一个明确的评论 - 对于穷人(经常是没经验的)维护者! (6认同)
  • 理想的控制流抽象不需要读者回溯或"侧钻"来确定语义.这差不多就像你要进入C. (3认同)
  • @JimBalter:`goto`语句只会在导致执行跨越某些语义边界时造成重大麻烦.其他构造对于它们适合的98%的编码结构更具可读性,如果语言具有块状结构,其行为类似于"do {...} while(false)",那将会有所帮助.知道这不是一个没有读者必须检查最后一行的循环.也许就像`做一次{...}`. (3认同)
  • 我已经感觉到没有考虑过那个人的耻辱! (2认同)
  • 这是我第一次看到有人称之为"普通",除了宏写作,所以在我看来,这是一切,但不常见. (2认同)
  • ...而在宏中,它更倾向于将块转换​​为语句而不是允许更简单的跳出 (2认同)
  • 虽然这确实提供了另一种选择,但它并不简单或易于理解.将其转换为函数调用会导致相同的行为,而不会添加逻辑崩溃. (2认同)
  • "Dijkstra试图将结构化编程推向一个不需要它的世界." - 这是一个奇怪的断言.当时,诸如FORTRAN和汇编程序之类的语言缺少结构化控制语句,因此所有代码都是意大利面条代码."这是一个脱离背景的40年的无关紧要" - 不,不是.gotos和标签的非结构化特性使代码更难以阅读和分析.在C中,它们有时难以避免,因为缺少catch/try/finally,析构函数,多级中断/继续以及其他功能."更快的代码" - 呃,不.编译器在40年后进行优化. (2认同)

Cli*_*ord 38

如果您安排每个条件块生成的主体success是如下函数,或者每个// process都可以减少为布尔表达式,例如:

success = f1() ; 
if( success ) 
{
  success = f2() ; 
  if( success ) 
  {
    success = f3() ; 
    if( success ) 
    {
      success = f4()
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

然后,您可以将此减少为使用短路评估的单个布尔表达式:

success = f1() && 
          f2() && 
          f3() && 
          f4() ;
Run Code Online (Sandbox Code Playgroud)

在这里f2(),如果将不会被调用f1()表达式求值的第一中止-返回false,与同为每个连续通话&&操作数子表达式返回false.

  • 同意它通常不适用,因此不是"答案",但值得一提. (3认同)

Pau*_*oub 25

不清楚为什么你需要筑巢或打破.当序列需要在第一次失败时保释时,我会一直这样做:

// process

if (success) {
  // more process
}

if (success) {
  // still more process
}

if (success) {
  // even more process
}
Run Code Online (Sandbox Code Playgroud)

  • 初学者可以节省纳秒.真正的程序员可以节省几毫秒.并使用处理这种情况的编译器. (21认同)
  • "你浪费CPU周期" - 只有你的编译器是由一个无能的人编写的.一个不错的优化编译器将跳过相同条件的测试. (6认同)
  • @FiddlingBits编译器可以在success = false时跳过if语句. (4认同)
  • 不,对于编译器而言,如果对于任何这些条件,一旦"success"为"false",它将是一个相当微不足道的静态分析,因为我们永远不会进入"进程"阻止并改变它. (4认同)
  • "因为成功可以在整个过程中随时改变" - 一旦成功是错误的,所有后续代码都会失败.同样,编译器可以跳过下一次"success"测试,如果它已知为false并且只是分支到false目的地......并且它可以重复执行此操作,因此不会浪费周期."我认为你把这个例子过于字面化" - 不,你只是错误地浪费了CPU周期. (3认同)
  • @JimBalter编译器无法跳过/组合这些`if`语句,因为`success`可以在此过程中随时更改.只有在执行期间才能知道"成功"的状态. (2认同)
  • @FiddlingBits:启用优化后,大多数编译器都会意识到,在这种情况下,一旦标志变为 false,它们就可以返回。[这是 clang 产生的示例](https://godbolt.org/g/yqUQem),gcc 也给出类似的输出。请注意每个 case 之后跳转到函数末尾的情况(`test al, al`, `je .LBB0_4`)。 (2认同)