简体   繁体   English

是否需要“do {...} while ()”循环?

[英]Is there ever a need for a "do {...} while ( )" loop?

Bjarne Stroustrup (C++ creator) once said that he avoids "do/while" loops, and prefers to write the code in terms of a "while" loop instead. Bjarne Stroustrup(C++ 的创造者)曾经说过他避免使用“do/while”循环,而是更喜欢用“while”循环来编写代码。 [See quote below.] [请参阅下面的报价。]

Since hearing this, I have found this to be true.自从听到这个,我发现这是真的。 What are your thoughts?你怎么看? Is there an example where a "do/while" is much cleaner and easier to understand than if you used a "while" instead?有没有一个例子,其中“do/while”比使用“while”更清晰、更容易理解?

In response to some of the answers: yes, I understand the technical difference between "do/while" and "while".回应一些答案:是的,我理解“do/while”和“while”之间的技术区别。 This is a deeper question about readability and structuring code involving loops.这是一个关于可读性和结构化涉及循环的代码的更深层次的问题。

Let me ask another way: suppose you were forbidden from using "do/while" - is there a realistic example where this would give you no choice but to write unclean code using "while"?让我问另一种方式:假设你被禁止使用“do/while”——是否有一个现实的例子,这会让你别无选择,只能使用“while”编写不干净的代码?

From "The C++ Programming Language", 6.3.3:来自“C++ 编程语言”,6.3.3:

In my experience, the do-statement is a source of errors and confusion.根据我的经验,do-statement 是错误和混乱的根源。 The reason is that its body is always executed once before the condition is evaluated.原因是它的主体总是在评估条件之前执行一次。 However, for the body to work correctly, something very much like the condition must hold even the first time through.然而,为了让身体正常工作,即使第一次通过,也必须保持非常类似的情况。 More often than I would have guessed, I have found that condition not to hold as expected either when the program was first written and tested, or later after the code preceding it has been modified.比我预想的更多的是,我发现无论是在程序第一次编写和测试时,还是在其前面的代码被修改之后,这种情况都没有按预期保持。 I also prefer the condition "up front where I can see it."我也更喜欢“在我可以看到的地方”的条件。 Consequently, I tend to avoid do-statements.因此,我倾向于避免使用 do 语句。 -Bjarne -Bjarne

Avoiding the do/while loop is a recommendation included in the C++ Core Guidelines as ES.75, avoid do-statements .避免 do/while 循环是包含在C++ 核心指南中的建议,ES.75,避免 do-statements

Yes I agree that do while loops can be rewritten to a while loop, however I disagree that always using a while loop is better.是的,我同意 do while 循环可以重写为 while 循环,但是我不同意总是使用 while 循环更好。 do while always get run at least once and that is a very useful property (most typical example being input checking (from keyboard)) do while 总是至少运行一次,这是一个非常有用的属性(最典型的例子是输入检查(从键盘))

#include <stdio.h>

int main() {
    char c;

    do {
        printf("enter a number");
        scanf("%c", &c);

    } while (c < '0' ||  c > '9'); 
}

This can of course be rewritten to a while loop, but this is usually viewed as a much more elegant solution.这当然可以重写为 while 循环,但这通常被视为更优雅的解决方案。

do-while is a loop with a post-condition. do-while 是一个带有后置条件的循环。 You need it in cases when the loop body is to be executed at least once.在循环体至少要执行一次的情况下,您需要它。 This is necessary for code which needs some action before the loop condition can be sensibly evaluated.这对于在合理评估循环条件之前需要一些操作的代码来说是必要的。 With while loop you would have to call the initialization code from two sites, with do-while you can only call it from one site.使用 while 循环,您必须从两个站点调用初始化代码,而使用 do-while 则只能从一个站点调用它。

Another example is when you already have a valid object when the first iteration is to be started, so you don't want to execute anything (loop condition evaluation included) before the first iteration starts.另一个例子是当你在第一次迭代开始时已经有一个有效的对象,所以你不想在第一次迭代开始之前执行任何东西(包括循环条件评估)。 An example is with FindFirstFile/FindNextFile Win32 functions: you call FindFirstFile which either returns an error or a search handle to the first file, then you call FindNextFile until it returns an error.一个例子是 FindFirstFile/FindNextFile Win32 函数:您调用 FindFirstFile,它返回一个错误或第一个文件的搜索句柄,然后调用 FindNextFile 直到它返回一个错误。

Pseudocode:伪代码:

Handle handle;
Params params;
if( ( handle = FindFirstFile( params ) ) != Error ) {
   do {
      process( params ); //process found file
   } while( ( handle = FindNextFile( params ) ) != Error ) );
}

do { ... } while (0) is an important construct for making macros behave well. do { ... } while (0)是使宏表现良好的重要结构。

Even if it's unimportant in real code (with which I don't necessarily agree), it's important for for fixing some of the deficiencies of the preprocessor.即使它在实际代码中并不重要(我不一定同意),但对于修复预处理器的一些缺陷很重要。

Edit: I ran into a situation where do/while was much cleaner today in my own code.编辑:我遇到了今天在我自己的代码中 do/while 更干净的情况。 I was making a cross-platform abstraction of the paired LL/SC instructions.我正在对成对的LL/SC指令进行跨平台抽象。 These need to be used in a loop, like so:这些需要在循环中使用,如下所示:

do
{
  oldvalue = LL (address);
  newvalue = oldvalue + 1;
} while (!SC (address, newvalue, oldvalue));

(Experts might realize that oldvalue is unused in an SC Implementation, but it's included so that this abstraction can be emulated with CAS.) (专家可能会意识到在 SC 实现中没有使用 oldvalue,但它被包含在内,以便可以用 CAS 模拟这种抽象。)

LL and SC are an excellent example of a situation where do/while is significantly cleaner than the equivalent while form: LL 和 SC 是一个很好的例子,说明 do/while 比等效的 while 形式要干净得多:

oldvalue = LL (address);
newvalue = oldvalue + 1;
while (!SC (address, newvalue, oldvalue))
{
  oldvalue = LL (address);
  newvalue = oldvalue + 1;
}

For this reason I'm extremely disappointed in the fact that Google Go has opted to remove the do-while construct .出于这个原因,我对 Google Go选择删除 do-while 结构这一事实感到非常失望。

It's useful for when you want to "do" something "until" a condition is satisfied.当您想“做”某事“直到”满足条件时,它很有用。

It can be fudged in a while loop like this:它可以像这样在一个 while 循环中被篡改:

while(true)
{

    // .... code .....

    if(condition_satisfied) 
        break;
}

The following common idiom seems very straightforward to me:以下常用习语对我来说似乎很简单:

do {
    preliminary_work();
    value = get_value();
} while (not_valid(value));

The rewrite to avoid do seems to be:避免do的重写似乎是:

value = make_invalid_value();
while (not_valid(value)) {
    preliminary_work();
    value = get_value();
}

That first line is used to make sure that the test always evaluates to true the first time.第一行用于确保测试在第一次总是评估为真 In other words, the test is always superfluous the first time.换句话说,第一次测试总是多余的。 If this superfluous test wasn't there, one could also omit the initial assignment.如果没有这个多余的测试,也可以省略初始分配。 This code gives the impression that it fights itself.这段代码给人的印象是它在自我战斗。

In cases such like these, the do construct is a very useful option.在这种情况下, do构造是一个非常有用的选项。

(Assuming you know the difference between the both) (假设您知道两者之间的区别)

Do/While is good for bootstrapping/pre-initializing code before your condition is checked and the while loop is run. Do/While 有利于在检查条件和运行 while 循环之前引导/预初始化代码。

In our coding conventions在我们的编码约定中

  • if / while / ... conditions don't have side effects and if / while / ... 条件没有副作用并且
  • varibles must be initialized.必须初始化变量。

So we have almost never a do {} while(xx) Because:所以我们几乎从来没有do {} while(xx)因为:

int main() {
    char c;

    do {
        printf("enter a number");
        scanf("%c", &c);

    } while (c < '0' ||  c > '9'); 
}

is rewritten in:改写为:

int main() {
    char c(0);
    while (c < '0' ||  c > '9');  {
        printf("enter a number");
        scanf("%c", &c);
    } 
}

and

Handle handle;
Params params;
if( ( handle = FindFirstFile( params ) ) != Error ) {
   do {
      process( params ); //process found file
   } while( ( handle = FindNextFile( params ) ) != Error ) );
}

is rewritten in:改写为:

Params params(xxx);
Handle handle = FindFirstFile( params );
while( handle!=Error ) {
    process( params ); //process found file
    handle = FindNextFile( params );
}

This is cleanest alternative to do-while that I have seen.这是我见过的最干净的 do-while 替代方法。 It is the idiom recommended for Python which does not have a do-while loop.这是推荐用于没有 do-while 循环的 Python 的习惯用法。

One caveat is that you can not have a continue in the <setup code> since it would jump the break condition, but none of the examples that show the benefits of the do-while need a continue before the condition.一个警告是,您不能在<setup code>使用continue ,因为它会跳过中断条件,但是显示 do-while 好处的示例中没有一个需要在条件之前执行 continue。

while (true) {
       <setup code>
       if (!condition) break;
       <loop body>
}

Here it is applied to some of the best examples of the do-while loops above.在这里,它应用于上述 do-while 循环的一些最佳示例。

while (true) {
    printf("enter a number");
    scanf("%c", &c);
    if (!(c < '0' ||  c > '9')) break;
} 

This next example is a case where the structure is more readable than a do-while since the condition is kept near the top as //get data is usually short yet the //process data portion may be lengthy.下一个示例是结构比 do-while 更具可读性的情况,因为条件保持在顶部附近,因为//get data通常很短,而//process data部分可能很长。

while (true) {
    // get data 
    if (data == null) break;
    // process data
    // process it some more
    // have a lot of cases etc.
    // wow, we're almost done.
    // oops, just one more thing.
} 

It's all about readability .这都是关于可读性的
More readable code leads to less headache in code maintenance, and better collaboration.更易读的代码可以减少代码维护的麻烦,并更好地协作。
Other considerations (such as optimization) are, by far, less important in most cases.到目前为止,其他考虑因素(例如优化)在大多数情况下并不重要。
I'll elaborate, since I got a comment here:我会详细说明,因为我在这里得到了评论:
If you have a code snippet A that uses do { ... } while() , and it's more readable than its while() {...} equivalent B , then I'd vote for A .如果您有一个使用do { ... } while()的代码片段A ,并且它比它的while() {...}等效B更具可读性,那么我会投票给A If you prefer B , since you see the loop condition "up front", and you think it's more readable (and thus maintainable, etc.) - then go right ahead, use B .如果您更喜欢B ,因为您在“前面”看到循环条件,并且您认为它更具可读性(因此可维护等) - 然后继续使用B
My point is: use the code that is more readable to your eyes (and to your colleagues').我的观点是:使用对你的眼睛(和你的同事)来说更具可读性的代码。 The choice is subjective, of course.当然,选择是主观的。

First of all, I do agree that do-while is less readable than while .首先,我同意do-while的可读性不如while

But I'm amazed that after so many answers, nobody has considered why do-while even exists in the language.但令我惊讶的是,在这么多答案之后,没有人考虑为什么do-while甚至存在于语言中。 The reason is efficiency.原因是效率。

Lets say we have a do-while loop with N condition checks, where the outcome of the condition depends on the loop body.假设我们有一个带有N条件检查的do-while循环,其中条件的结果取决于循环体。 Then if we replace it with a while loop, we get N+1 condition checks instead, where the extra check is pointless.然后,如果我们用while循环替换它,我们会得到N+1条件检查,而额外的检查毫无意义。 That's no big deal if the loop condition only contains a check of an integer value, but lets say that we have如果循环条件只包含对整数值的检查,那没什么大不了的,但是可以说我们有

something_t* x = NULL;

while( very_slowly_check_if_something_is_done(x) )
{
  set_something(x);
}

Then the function call in first lap of the loop is redundant: we already know that x isn't set to anything yet.然后循环第一圈中的函数调用是多余的:我们已经知道x尚未设置为任何内容。 So why execute some pointless overhead code?那么为什么要执行一些毫无意义的开销代码呢?

I often use do-while for this very purpose when coding realtime embedded systems, where the code inside the condition is relatively slow (checking the response from some slow hardware peripheral).在编写实时嵌入式系统时,我经常为此目的使用 do-while,其中条件内部的代码相对较慢(检查来自某些慢速硬件外围设备的响应)。

It is only personal choice in my opinion.在我看来,这只是个人选择。

Most of the time, you can find a way to rewrite a do ... while loop to a while loop;大多数时候,你可以找到一种方法将 do ... while 循环重写为 while 循环; but not necessarily always.但不一定总是。 Also it might make more logical sense to write a do while loop sometimes to fit the context you are in.此外,有时编写 do while 循环以适应您所处的上下文可能更合乎逻辑。

If you look above, the reply from TimW, it speaks for itself.如果你看上面,来自 TimW 的回复,它不言自明。 The second one with Handle, especially is more messy in my opinion.带有 Handle 的第二个,尤其是在我看来更凌乱。

Read the Structured Program Theorem .阅读结构化程序定理 A do{} while() can always be rewritten to while() do{}. do{} while() 总是可以重写为 while() do{}。 Sequence, selection, and iteration are all that's ever needed.序列、选择和迭代都是需要的。

Since whatever is contained in the loop body can always be encapsulated into a routine, the dirtiness of having to use while() do{} need never get worse than由于循环体中包含的任何内容始终可以封装到例程中,因此必须使用 while() do{} 的肮脏性永远不会比

LoopBody()
while(cond) {
    LoopBody()
}

A do-while loop can always be rewritten as a while loop. do-while 循环总是可以重写为 while 循环。

Whether to use only while loops, or while, do-while, and for-loops (or any combination thereof) depends largely on your taste for aesthetics and the conventions of the project you are working on.是只使用 while 循环,还是使用 while、do-while 和 for 循环(或它们的任意组合)在很大程度上取决于您对美学的品味以及您正在处理的项目的约定。

Personally, I prefer while-loops because it simplifies reasoning about loop invariants IMHO.就我个人而言,我更喜欢 while 循环,因为它简化了关于循环不变量恕我直言的推理。

As to whether there are situations where you do need do-while loops: Instead of至于是否存在确实需要 do-while 循环的情况:而不是

do
{
  loopBody();
} while (condition());

you can always你总是可以

loopBody();
while(condition())
{
  loopBody();
}

so, no, you never need to use do-while if you cannot for some reason.所以,不,如果由于某种原因不能使用,则永远不需要使用 do-while。 (Of course this example violates DRY, but it's only a proof-of-concept. In my experience there is usually a way of transforming a do-while loop to a while loop and not to violate DRY in any concrete use case.) (当然,这个例子违反了 DRY,但这只是一个概念验证。根据我的经验,通常有一种方法可以将 do-while 循环转换为 while 循环,并且在任何具体用例中都不会违反 DRY。)

"When in Rome, do as the Romans." “在罗马时,做罗马人。”

BTW: The quote you are looking for is maybe this one ([1], last paragraph of section 6.3.3):顺便说一句:您正在寻找的报价可能是这个([1],第 6.3.3 节的最后一段):

From my experience, the do-statement is a source of error and confusion.根据我的经验,do-statement 是错误和混乱的根源。 The reason is that its body is always executed once before the condition is tested.原因是它的主体总是在测试条件之前执行一次。 For the correct functioning of the body, however, a similar condition to the final condition has to hold in the first run.然而,为了身体的正常运作,在第一次运行中必须保持与最终条件类似的条件。 More often than I expected I have found these conditions not to be true.我发现这些条件不正确的次数比我预期的要多。 This was the case both when I wrote the program in question from scratch and then tested it as well as after a change of the code.当我从头开始编写有问题的程序然后对其进行测试时以及在更改代码之后都是这种情况。 Additionally, I prefer the condition "up-front, where I can see it".此外,我更喜欢“预先,我可以看到它”的条件。 I therefore tend to avoid do-statements.因此,我倾向于避免使用 do 语句。

(Note: This is my translation of the German edition. If you happen to own the English edition, feel free to edit the quote to match his original wording. Unfortunately, Addison-Wesley hates Google.) (注意:这是我对德文版的翻译。如果您碰巧拥有英文版,请随时编辑引用以匹配他的原始措辞。不幸的是,Addison-Wesley 讨厌 Google。)

[1] B. Stroustrup: The C++ programming language. [1] B. Stroustrup: C++ 编程语言。 3rd Edition.第 3 版。 Addison-Wessley, Reading, 1997.艾迪生-韦斯利,雷丁,1997 年。

I hardly ever use them simply because of the following:我几乎从不使用它们只是因为以下几点:

Even though the loop checks for a post-condition you still need to check for this post condition within your loop so that you don't process the post condition.即使循环检查后置条件,您仍然需要在循环中检查此后置条件,以便不处理后置条件。

Take the sample pseudo code:以示例伪代码为例:

do {
   // get data 
   // process data
} while (data != null);

Sounds simple in theory but in real world situations it would probably turn out to look like so:理论上听起来很简单,但在现实世界中,它可能看起来像这样:

do {
   // get data 
   if (data != null) {
       // process data
   }
} while (data != null);

The extra "if" check just isn't worth it IMO.额外的“如果”检查不值得IMO。 I have found very few instances where it's more terse to do a do-while instead of a while loop.我发现执行 do-while 而不是 while 循环更简洁的例子很少。 YMMV.天啊。

In response to a question/comment from unknown (google) to the answer of Dan Olson:回应未知(谷歌)对丹奥尔森的回答的问题/评论:

"do { ... } while (0) is an important construct for making macros behave well." “do { ... } while (0) 是使宏表现良好的重要结构。”

#define M do { doOneThing(); doAnother(); } while (0)
...
if (query) M;
...

Do you see what happens without the do { ... } while(0) ?你看到没有do { ... } while(0)什么吗? It will always execute doAnother().它将始终执行 doAnother()。

Well maybe this goes back a few steps, but in the case of好吧,也许这可以追溯到几步,但在这种情况下

do
{
     output("enter a number");
     int x = getInput();

     //do stuff with input
}while(x != 0);

It would be possible, though not necessarily readable to use有可能,但不一定可读

int x;
while(x = getInput())
{
    //do stuff with input
}

Now if you wanted to use a number other than 0 to quit the loop现在,如果您想使用 0 以外的数字退出循环

while((x = getInput()) != 4)
{
    //do stuff with input
}

But again, there is a loss in readability, not to mention it's considered bad practice to utilize an assignment statement inside a conditional, I just wanted to point out that there are more compact ways of doing it than assigning a "reserved" value to indicate to the loop that it is the initial run through.但是同样,可读性有所损失,更不用说在条件中使用赋值语句被认为是不好的做法,我只是想指出,与分配“保留”值相比,有更紧凑的方法来表示到它是初始运行的循环。

I like David Božjak's example.我喜欢 David Božjak 的例子。 To play devil's advocate, however, I feel that you can always factor out the code that you want to run at least once into a separate function, achieving perhaps the most readable solution.然而,我认为你总是可以将你想要至少运行一次的代码分解到一个单独的函数中,从而实现可能是最易读的解决方案。 For example:例如:

int main() {
    char c;

    do {
        printf("enter a number");
        scanf("%c", &c);

    } while (c < '0' ||  c > '9'); 
}

could become this:可能变成这样:

int main() {
    char c = askForCharacter();
    while (c < '0' ||  c > '9') {
        c = askForCharacter();
    }
}

char askForCharacter() {
    char c;
    printf("enter a number");
    scanf("%c", &c);
    return c;
}

(pardon any incorrect syntax; I'm not a C programmer) (请原谅任何不正确的语法;我不是 C 程序员)

consider something like this:考虑这样的事情:

int SumOfString(char* s)
{
    int res = 0;
    do
    {
        res += *s;
        ++s;
    } while (*s != '\0');
}

It so happens that '\\0' is 0, but I hope you get the point.碰巧 '\\0' 是 0,但我希望你明白这一点。

My problem with do / while is strictly with its implementation in C. Due to the reuse of the while keyword, it often trips people up because it looks like a mistake.我的do / while问题严格在于它在 C 中的实现。由于while关键字的重用,它经常使人们绊倒,因为它看起来像一个错误。

If while had been reserved for only while loops and do / while had been changed into do / until or repeat / until , I don't think the loop (which is certainly handy and the "right" way to code some loops) would cause so much trouble.如果while仅用于while循环并且do / while已更改为do / untilrepeat / until ,我认为循环(这当然很方便,并且是编写某些循环的“正确”方式)不会导致这么多麻烦。

I've ranted before about this in regards to JavaScript , which also inherited this sorry choice from C. 我之前就 JavaScript 对此进行了咆哮,它也从 C 继承了这个遗憾的选择。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM