做{ ... } while (假)

我看到一个人写的代码注意到他的功能似乎有一个模式:

<return-type> function(<params>)
{
<initialization>


do
{
<main code for function>
}
while(false);


<tidy-up & return>
}

它不是 很糟糕,更奇特(实际的代码相当整洁和不令人惊讶)。这不是我以前见过的东西,我想知道是否有人能想到它背后的逻辑——也许是另一种语言的背景?

43068 次浏览

你可以从 do{...}while(false)中取出 break

也许它的使用使得 break可以在任何时候用于中止进一步代码的执行:

do {
if (!condition1) break;
some_code;
if (!condition2) break;
some_further_code;
// …
} while(false);

我认为这样做是为了使用 breakcontinue语句。某种“ goto”代码逻辑。

我见过这样的代码,所以您可以使用 break作为 goto的排序。

当函数有许多潜在的退出点时,我曾经看到它被用作一种有用的模式,但是无论函数如何退出,总是需要相同的清理代码。

它可以使一个令人厌烦的 if/else-if 树更容易阅读,只要到达一个出口点就必须中断,其余的逻辑在后面内联。

这种模式在没有 goto 语句的语言中也很有用。也许这就是最初的程序员学习模式的地方。

这个技巧被那些太害羞而不敢在代码中使用显式 goto的程序员所使用。上面代码的作者希望能够直接从代码的中间跳到“清理和返回”点。但是他们不想使用标签和显式的 goto。相反,他们可以在上述“假”周期的体内使用 break来达到同样的效果。

很简单: 显然,您可以在任何时候使用 break语句跳出假循环。此外,do块是一个单独的作用域(也可以只使用 { ... }实现)。

在这种情况下,使用 RAII (函数结束时对象自动正确解构)可能是一个更好的主意。另一个类似的结构是使用 goto-是的,我知道这很邪恶,但是它可以用来具有常见的清理代码,如下所示:

<return-type> function(<params>)
{
<initialization>


<main code for function using "goto error;" if something goes wrong>


<tidy-up in success case & return>


error:


<commmon tidy-up actions for error case & return error code or throw exception>
}

(顺便说一句: do-while-false 结构在 Lua 中用于显示缺少的 continue语句。)

答案很可能是分手,但我会提出另一个想法。

也许他想要一个局部定义的变量,并使用这个结构来获得一个新的作用域。

请记住,虽然最近的 C + + 允许在任何地方使用 {...},但情况并非总是如此。

许多人指出,它经常与 break 一起使用,作为一种写“ goto”的笨拙方式。如果它直接写在函数中,那么这可能是正确的。

在宏(OTOH)中,do { something; } while (false)是在宏调用之后强制使用分号的一种方便的方法,绝对不允许跟随其他标记。

另一种可能性是,这里曾经有一个循环,或者迭代预计将在未来被添加(例如,在测试驱动开发中,通过测试不需要迭代,但是逻辑上如果函数需要比当前需要的更通用一些,那么在这里循环是有意义的)

这只是 while的一个反常现象,目的是在不使用 goto 这个词的情况下获得 goto tidy-up的语义。

这是不好的形式,因为当您在外部 while中使用 其他循环时,breaks对于读者来说变得模糊不清。“这是要退出吗? 还是仅仅打算打破内部循环?”

作者多大了?

我这么问是因为我曾经在80年代后期遇到过一些实时的 Fortran 代码。事实证明,这是在没有线程的操作系统上模拟线程的一种非常好的方法。您只需将整个程序(您的调度程序)放入一个循环中,然后逐个调用您的“线程”例程。线程例程本身是循环,它们迭代直到发生一系列条件中的一个(通常一个条件是已经过去了一定的时间)。这就是“协作多任务”,因为这取决于各个线程是否时不时地放弃 CPU,这样其他线程就不会挨饿。您可以嵌套循环子程序调用来模拟线程优先级带宽。

我能想到的另一个原因是它使用了大括号 装饰,而我相信使用新的 C + + 标准裸括号是不行的(ISO C 不喜欢它们)。否则静音静态分析仪喜欢棉绒。

不确定为什么需要它们,可能是变量范围,或者调试器的优势。

参见 琐碎的 Do While 循环牙套很好从 C2。

为了澄清我的术语(我认为这符合标准用法) :

裸露的牙套 :

init();
...
{
c = NULL;
mkwidget(&c);
finishwidget(&c);
}
shutdown();

空括号(NOP) :

{}

例如:。

while (1)
{}  /* Do nothing, endless loop */

:

if (finished)
{
closewindows(&windows);
freememory(&cache);
}

就会变成

if (finished)
closewindows(&windows);
freememory(&cache);

如果去掉大括号,则会改变执行流程,而不仅仅是局部变量的作用域。因此不是“独立”或“裸体”。

裸括号或块可以用来表示代码的任何部分,这些代码可能是您希望标记的(内联)函数的潜在代码,但是当时不能重构。

真有意思。正如其他人所说,循环中可能存在断点。我会这样做:

while(true)
{
<main code for function>
break; // at the end.
}

它看起来像一个 C 程序员。在 C + + 中,自动变量具有用于清理的析构函数,因此在返回之前不需要清理任何东西。在 C 语言中,没有这样的 拉尔习惯用法,所以如果有常见的清理代码,可以将其 goto化,或者像上面那样使用一次性循环。

与 C + + 习惯用法相比,它的主要缺点是,如果在主体中抛出异常,它不会进行整理。C 没有例外,所以这不是一个问题,但是它确实使它成为 C + + 中的一个坏习惯。

这是一种非常普遍的做法。在 C。我试着把它想象成你想用一种“我没有使用 goto”的方式来使用 欺骗自己。仔细想想,使用类似的 goto并没有什么错。事实上,它也会降低压痕水平。

尽管如此,我注意到,do..while循环往往会增长。然后他们得到了内部的 ifelse,使代码实际上不是很可读,更不用说可测试。

这些 do..while通常是用来做 清理现场的。尽一切可能,我宁愿使用 拉尔早点回来太短了的功能。另一方面,C没有像 C + + 那样为您提供很多便利,使得 do..while成为进行清理的最佳方法之一。

除了已经提到的“ goto 示例”,do... while (0)习惯用法有时用在宏定义中,以便在定义中提供括号,并且仍然让编译器在宏调用的末尾添加一个分号。

Http://groups.google.com/group/comp.soft-sys.ace/browse_thread/thread/52f670f1292f30a4?tvc=2&q=while+(0)

这是模仿 GOTO的一种人为的方式,因为这两者实际上是完全相同的:

// NOTE: This is discouraged!
do {
if (someCondition) break;
// some code be here
} while (false);
// more code be here

以及:

// NOTE: This is discouraged, too!
if (someCondition) goto marker;
// some code be here
marker:
// more code be here

另一方面,这两种方法都应该用 if来实现:

if (!someCondition) {
// some code be here
}
// more code be here

虽然嵌套可以得到一个有点丑陋,如果你只是把一个长字符串的正向 GOTO到嵌套的 if。然而,真正的答案是适当的重构,而不是模仿古老的语言结构。

如果您拼命地试图音译一个包含 GOTO的算法,那么您可以使用这个惯用语。不过,这当然是不标准的,也是一个很好的指标,表明您没有严格遵守语言的预期习惯用法。

实际上,我没有听说过任何类似 C 的语言,其中 do/while 是任何事情的惯用解决方案。

您可以将整个混乱重构为更合理的内容,使其更加地道,更加易读。

我同意大多数海报的使用作为一个几乎不加掩饰的 Goto。宏也被认为是以这种风格编写代码的潜在动机。

我还看到这种结构在混合 C/C + + 环境中被用作穷人的例外。带有“ break”的“ do {} while (false)”可用于在循环中遇到异常时跳到代码块的末尾。

我还看到,在实行“单一回报/功能”理念的商店中使用了这种结构。同样,这代替了一个明确的“ goto”——但是动机是为了避免多个返回点,而不是“跳过”代码并在该函数中继续实际执行。

我使用的是 Adobe InDesign SDK,InDesign SDK 示例中几乎每个函数都是这样编写的。这是因为函数通常都很长。您需要执行 QueryInterface (...)来从应用程序对象模型中获取任何内容。所以通常每个 QueryInterface 后面都跟着 if不顺利,中断。

许多人已经陈述了这个结构和 goto之间的相似性,并表达了对 goto 的偏好。也许这个人的背景包含了一个环境,在这个环境中,goto 是被编码指南严格禁止的?

我认为写 break比写 goto end更方便。你甚至不需要为这个标签想出一个名字,就可以让意图更加清晰: 你不想跳转到一个有特定名字的标签。你想离开这里。

而且你可能还需要大括号,这是 do{...}while(false);的版本:

do {
// code
if (condition) break; // or continue
// more code
} while(false);

如果你想使用 goto,这就是你必须要表达的方式:

{
// code
if (condition) goto end;
// more code
}
end:

我认为第一个版本的意思更容易理解。此外,它更容易编写,更容易扩展,更容易翻译成一种语言,不支持 goto,等等。


最常提到的关于使用 break的问题是,它是一个伪装得很糟糕的 goto。但实际上,breakreturn更相似: 两个指令都跳出一个代码块,与 goto相比,这个代码块的结构非常相似。尽管如此,这两个指令都允许在代码块中有多个出口点,这有时会造成混乱。毕竟,我会努力寻求最明确的解决方案,不管具体情况是什么。

许多答案给出了 do{(...)break;}while(false)的原因。我想用另一个现实生活中的例子来补充这张图片。

在下面的代码中,我必须根据 data指针指向的地址设置枚举器 operation。因为 switch-case 只能用于标量类型,所以我这样做效率很低

if (data == &array[o1])
operation = O1;
else if (data == &array[o2])
operation = O2;
else if (data == &array[on])
operation = ON;


Log("operation:",operation);

但是,由于 Log ()和其余代码对于任何选择的操作值都会重复执行,因此,当地址已经被发现时,我不知道如何跳过其余的比较。这就是 do{(...)break;}while(false)派上用场的地方。

do {
if (data == &array[o1]) {
operation = O1;
break;
}
if (data == &array[o2]) {
operation = O2;
break;
}
if (data == &array[on]) {
operation = ON;
break;
}
} while (false);


Log("operation:",operation);

有人可能会问,他为什么不能在 if的声明中对 break做同样的事情,比如:

if (data == &array[o1])
{
operation = O1;
break;
}
else if (...)

break只与最近的封闭 循环或开关交互,无论是 forwhiledo .. while类型,所以不幸的是,这不会工作。

有几种解释。第一种是一般的,第二种是特定于带参数的 C 预处理器宏的:

流量控制

我见过这个在纯 C 代码中使用。基本上,它是 goto 的一个更安全的版本,因为你可以打破它和所有的内存得到正确的清理。

为什么像 goto这样的东西是好的?好吧,如果你的代码几乎每一行都可以返回一个错误,但是你需要以同样的方式对它们作出反应(例如在清理后将错误传递给调用者) ,那么通常它更易于阅读,因为它避免了清理代码的重复,从而避免了 if( error ) { /* cleanup and error string generation and return here */ }

然而,在 C + + 中,正是为了这个目的,你有异常 + RAII,所以我认为这是糟糕的编码风格。

分号检查

如果在类函数的宏调用之后忘记了分号,参数可能会以不希望的方式收缩并编译成有效的语法。想象一下宏观世界

#define PRINT_IF_DEBUGMODE_ON(msg) if( gDebugModeOn ) printf("foo");

意外地被称为

if( foo )
PRINT_IF_DEBUGMODE_ON("Hullo\n")
else
doSomethingElse();

“ else”将被认为与 gDebugModeOn相关联,因此当 foofalse时,将发生与预期完全相反的情况。

为临时变量提供作用域。

因为 do/while 有大括号,所以临时变量有一个明确定义的作用域,它们不能转义。

避免“可能不需要的分号”警告

有些宏只在调试版本中被激活。定义如下:

#if DEBUG
#define DBG_PRINT_NUM(n) printf("%d\n",n);
#else
#define DBG_PRINT_NUM(n)
#endif

现在,如果在条件内部的发布版本中使用这个命令,它将编译为

if( foo )
;

许多编译器认为这与

if( foo );

通常是意外写成的。所以你会得到警告。Do {} while (false)对编译器隐藏了这一点,并被它接受为表示 真的不想在此处执行任何操作。

避免使用条件语句捕获行

上例中的宏:

if( foo )
DBG_PRINT_NUM(42)
doSomething();

现在,在一个调试版本中,由于我们还习惯性地包含分号,所以这样编译就可以了。然而,在版本构建中,这突然变成了:

if( foo )


doSomething();

或者更清晰的格式

if( foo )
doSomething();

这完全不是我的本意。在宏周围添加 do { ... } while (false)会将缺少的分号变成编译错误。

这对观察室来说意味着什么?

通常,您希望在 C + + 中使用异常进行错误处理,并使用模板代替宏。然而,在非常罕见的情况下,您仍然需要宏(例如在使用令牌粘贴生成类名时)或者仅限于纯 C,这是一个有用的模式。

有些程序员喜欢只有一个函数的退出/返回。使用虚拟 do { ... . } while (false) ; 允许您在完成并仍然有一个返回值的情况下“打破”虚拟循环。

我是一个 java 程序员,所以我的例子是这样的

import java.util.Arrays;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;


public class p45
{
static List<String> cakeNames = Arrays.asList("schwarzwald torte", "princess", "icecream");
static Set<Integer> forbidden = Stream.of(0, 2).collect(Collectors.toSet());


public static  void main(String[] argv)
{
for (int i = 0; i < 4; i++)
{
System.out.println(String.format("cake(%d)=\"%s\"", i, describeCake(i)));
}
}




static String describeCake(int typeOfCake)
{
String result = "unknown";
do {
// ensure type of cake is valid
if (typeOfCake < 0 || typeOfCake >= cakeNames.size()) break;


if (forbidden.contains(typeOfCake)) {
result = "not for you!!";
break;
}


result = cakeNames.get(typeOfCake);
} while (false);
return result;
}
}

在这种情况下,我使用

switch(true) {
case condution1:
...
break;
case condution2:
...
break;
}