做{…} while(false)
我正在看一个人的一些代码,注意到他似乎有一个function模式:
<return-type> function(<params>) { <initialization> do { <main code for function> } while(false); <tidy-up & return> }
这不坏 ,更奇特(实际的代码是相当整洁,不出所料)。 这不是我以前见过的,我想知道是否有人可以想到背后的逻辑 – 用不同语言的背景吗?
do{...}while(false)
你可以break
do{...}while(false)
。
许多人指出,它经常被用作编写“goto”的尴尬手段。 如果直接写入函数,这可能是真的。
在macros观上,OTOH {做某事; } while(false)是macros调用之后强制分号的一种方便的方式,绝对不允许其他标记跟随。
另外一种可能是,那里有一个循环,或者预计将来会迭代迭代(例如,在testing驱动的开发中,不需要迭代来通过testing,但是逻辑上,如果循环的话是有意义的该function需要比目前要求的更普遍)
rest时间可能是答案,但我会提出一个其他的想法。
也许他想要一个本地定义的variables,并使用这个构造来获得一个新的范围。
请记住,尽pipe最近的C ++允许在任何地方使用{...}
,但情况并非总是如此。
我已经看到,当函数有很多潜在的退出点时,它被用作一个有用的模式,但是无论函数如何退出,总是需要相同的清理代码。
如果/ else-if树更容易阅读,通过只要到达一个退出点就中断,其余的逻辑内联后,就可以使一个令人厌烦的if / else-if树更容易阅读。
这种模式在没有goto语句的语言中也很有用。 也许这就是原始程序员学习模式的地方。
我已经看过这样的代码,所以你可以使用break
作为goto
的种类。
这只是一个颠倒,而不使用goto这个词来获得goto tidy-up
歌词。
这是不好的forms,因为当你在外面使用其他循环, while
breaks
变得模糊的读者。 “这是否应该退出?或者这只是打破内部循环?
这个技巧被程序员使用,他们害怕在代码中使用明确的goto
。 上面的代码的作者希望能够直接从代码中间跳到“清理和返回”点。 但他们不想使用标签和明确的goto
。 相反,他们可以在上面的“假”周期内使用一个break
来达到同样的效果。
这是一个非常普遍的做法。 在C。 我试着想,就好像你想以一种“我不使用goto
”的方式对自己说谎 。 考虑一下,用goto
也不会有什么问题。 实际上它也会降低缩进级别。
尽pipe如此,我注意到,经常这样做,而循环趋于增长。 然后他们得到if
s和else
内部,渲染代码实际上不是很可读,更不用说可testing。
那些做..通常打算做一个清理 。 通过一切手段,我宁愿使用RAII,并从一个简短的function提前返回 。 另一方面, C并没有像C ++那样为您提供更多的便利,而是做了一个do ..而最好的方法之一是做清理。
我认为写下break
而不是goto end
比较方便。 你甚至不需要想出一个标签的名称,使意图更清晰:你不想跳到一个具有特定名称的标签。 你想离开这里。
无论如何,你也可能需要大括号。 所以这是do{...}while(false);
版:
do { // code if (condition) break; // or continue // more code } while(false);
如果你想使用goto
,这就是你必须expression的方式:
{ // code if (condition) goto end; // more code } end:
我认为第一个版本的含义更容易理解。 另外它更容易编写,更容易扩展,更容易翻译成不支持goto
的语言等。
最常被提及的关于使用break
担心是这是一个严重伪装的goto
。 但实际上, break
与return
有更多的相似之处:两条指令都跳出了一段代码,这个代码与goto
相比是非常结构化的。 尽pipe如此,两个指令都允许在一段代码中有多个退出点,这有时会造成混淆。 毕竟,我会尽力寻求最明确的解决scheme,无论是在具体的情况下。
它看起来像一个C程序员。 在C ++中,自动variables具有用于清理的析构函数,所以在返回之前不应该有任何需要整理的东西。 在C中,你没有这个RAII语言,所以如果你有共同的清理代码,你可以goto
它,或者像上面一样使用一个循环。
与C ++习惯用法相比,它的主要缺点是,如果抛出exception,它将不会整理。 C没有例外,所以这不是一个问题,但它确实使它成为C ++中的一个坏习惯。
也许它被使用,以便可以在里面使用break
来在任何点放弃进一步代码的执行:
do { if (!condition1) break; some_code; if (!condition2) break; some_further_code; // … } while(false);
我认为这是为了使用break
或continue
语句。 某种“goto”代码逻辑。
很简单:显然你可以随时用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> }
(旁白:在Lua中使用do-while-false构造提出失踪的continue
语句。)
许多回答者给出了do{(...)break;}while(false)
。 我想通过另一个现实生活中的例子来补充图片。
在下面的代码中,我必须根据data
指针指向的地址设置枚举器operation
。 因为一个switch-case只能用在标量types上,所以我这样做效率不高
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
只与最接近的封闭循环或开关交互,无论是for
, while
还是do .. while
types,所以不幸的是,这是行不通的。
作者多大了?
我问,因为我曾经遇到过一些实时的Fortran代码,那是在80年代后期。 事实certificate,这是一个非常好的方法来模拟没有它们的操作系统上的线程。 你只需把整个程序(你的调度程序)放到一个循环中,然后逐个调用你的“线程”例程,线程例程本身就是循环,直到发生多种情况之一(通常是一定数量的时间已经过去了),它是“协作式多任务”,因为它是由各个线程放弃CPU,所以其他人不会饿死,可以嵌套循环子程序调用来模拟线程优先级带。
除了已经提到的“goto例子”之外,do … while(0)成语在macros定义中有时被用来提供定义中的括号,并且编译器仍然可以在末尾添加一个分号一个macros观调用。
几个解释。 第一个是通用的,第二个是具有参数的C预处理器macros:
stream量控制
我已经看到这用纯C代码。 基本上,这是一个更安全的转换版本,因为你可以打破它,所有的内存得到适当的清理。
为什么会像goto
好? 那么,如果你有代码,几乎每一行都可以返回一个错误,但是你需要以同样的方式对所有的代码作出反应(例如,在清理之后把错误传给你的调用者),通常它更具可读性,以避免if( error ) { /* cleanup and error string generation and return here */ }
因为它避免了重复的清理代码。
但是,在C ++中,你正是为了这个目的而使用了exception+ RAII,所以我认为它是不好的编码风格。
分号检查
如果在函数式的macros调用之后忘记了分号,则参数可能以不希望的方式收缩并编译成有效的语法。 想象一下macros
#define PRINT_IF_DEBUGMODE_ON(msg) if( gDebugModeOn ) printf("foo");
这被无意称为
if( foo ) PRINT_IF_DEBUGMODE_ON("Hullo\n") else doSomethingElse();
“else”将被认为是与gDebugModeOn
相关联的,所以当foo
是false
,准确的反向意思就会发生。
为临时variables提供一个范围。
由于do / while有大括号,所以临时variables有一个明确的范围,他们不能逃脱。
避免“可能不需要的分号”警告
一些macros只能在debugging版本中激活。 你将它们定义为:
#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)隐藏了这个来自编译器的内容,并被它接受为表示你真的不想在这里做任何事情。
避免通过条件来捕获行
上例中的macros:
if( foo ) DBG_PRINT_NUM(42) doSomething();
现在,在一个debugging版本中,由于我们还习惯性地包含了分号,所以编译得很好。 然而,在发布版本中突然变成:
if( foo ) doSomething();
或者更清晰的格式化
if( foo ) doSomething();
这根本不是什么意图。 在macros周围添加do {…} while(false)会将丢失的分号变成编译错误。
OP的含义是什么?
一般来说,你想在C ++中使用exception来进行error handling,并且使用模板而不是macros。 但是,在极less数情况下,您仍然需要macros(例如,使用令牌粘贴生成类名称时)或限制为纯C,这是一个有用的模式。
我同意大多数海报关于这个用法的一个伪装的goto。 macros也被认为是编写代码的潜在动机。
我也看到这种构造在混合C / C ++环境中用作穷人的例外。 如果在循环中遇到通常会遇到exception的情况,可以使用带有“break”的“do {} while(false)”跳过代码块的末尾。
我也曾经在商店中使用这种构造,在那里实行“单function返回”的意识形态。 再次,这是代替一个明确的“goto” – 但动机是避免多个返回点,而不是“跳过”代码,并继续在该函数内的实际执行。
我使用Adobe InDesign SDK工作,InDesign SDK示例几乎具有这样的function。 这是因为这个function通常很长。 在哪里需要执行QueryInterface(…)以从应用程序对象模型中获取任何内容。 所以通常每个QueryInterface都跟着if
不行,就打破。
很多人已经说过这个构build和goto
之间的相似性,并且expression了对goto的偏好。 也许这个人的背景包括了编码准则严格禁止goto的环境?
我能想到的另一个原因是它装饰大括号,而我相信在一个新的C + +标准裸括号不好(ISO C不喜欢他们)。 否则,静音像lint一样的静态分析器。
不知道为什么你会想要它们,也许是可变范围,或debugging器的优势。
请参阅Trivial Do while循环 ,并从括号中select大括号 。
澄清我的术语(我相信遵循标准用法):
赤裸的括号 :
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);
如果花括号被删除,从而改变执行的stream程,而不仅仅局部variables的范围。 因此不是“独立”或“裸体”。
裸括号或块可以用来表示任何代码段,这些代码段可能是您希望标记的(内联)function的可能性,但不是当时的重构。
这是一个仿效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
另一方面,这些都应该用s来完成:
if (!someCondition) { // some code be here } // more code be here
虽然嵌套可以得到一个难看的,如果你只是把一个长的前向GOTO
的string变成嵌套的if
s。 真正的答案是适当的重构,但不是模仿古老的语言结构。
如果你拼命地在GOTO
join一个algorithm,你可以用这个成语来做。 这当然是非标准的,也是一个很好的指标,你不会严格遵守语言的预期成语。
实际上,我不知道任何类似C的语言在哪里/哪个地方是一个习惯性的解决scheme。
你也许可以把整个混乱重构成更明智的东西,使其更具地道性,更具可读性。
这很有趣。 正如其他人所说的那样,循环内部可能会被打断。 我会这样做:
while(true) { <main code for function> break; // at the end. }