有没有必要使用“ do { ... } while()”循环?

比雅尼·斯特劳斯特鲁普(c + + create)曾经说过,他避免使用“ do/while”循环,而是更喜欢使用“ while”循环来编写代码。[见下面的引文。]

自从听到这个,我发现这是真的。你有什么想法?有没有一个例子,“ do/while”比使用“ while”更简洁、更容易理解?

对于一些回答: 是的,我理解“ do/while”和“ while”之间的技术差异。这是一个涉及循环的可读性和结构化代码的更深层次的问题。

让我问另一个问题: 假设您被禁止使用“ do/while”——是否有一个现实的例子,让您别无选择,只能使用“ while”编写不干净的代码?

来自“ C++程式语言”,6.3.3:

根据我的经验,do 陈述是错误和混乱的根源。原因是它的主体总是在评估条件之前执行一次。然而,为了让身体正常工作,一些非常类似的情况必须保持,即使是第一次通过。比我想象的更常见的情况是,我发现这个条件在程序首次编写和测试时,或者在程序前面的代码被修改之后,都不能按预期的那样保持。比昂

避免 do/while 循环是 C + + 核心指南中作为 ES.75,避免做陈述包含的一个建议。

42806 次浏览

(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 is a loop with a post-condition. 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.

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.

Pseudocode:

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

A do-while loop can always be rewritten as a while loop.

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.

Personally, I prefer while-loops because it simplifies reasoning about loop invariants IMHO.

As to whether there are situations where you do need do-while loops: Instead of

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. (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.)

"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):

From my experience, the do-statement is a source of error and confusion. 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.

(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.)

[1] B. Stroustrup: The C++ programming language. 3rd Edition. Addison-Wessley, Reading, 1997.

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 always get run at least once and that is a very useful property (most typical example being input checking (from keyboard))

#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.

do { ... } while (0) is an important construct for making macros behave well.

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. I was making a cross-platform abstraction of the paired LL/SC instructions. 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.)

LL and SC are an excellent example of a situation where do/while is significantly cleaner than the equivalent while form:

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.

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(true)
{


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


if(condition_satisfied)
break;
}

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. I have found very few instances where it's more terse to do a do-while instead of a while loop. YMMV.

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. 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.
My point is: use the code that is more readable to your eyes (and to your colleagues'). The choice is subjective, of course.

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."

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

Do you see what happens without the do { ... } while(0)? It will always execute doAnother().

In our coding conventions

  • if / while / ... conditions don't have side effects and
  • varibles must be initialized.

So we have almost never a do {} while(xx) Because:

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 );
}

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; 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.

If you look above, the reply from TimW, it speaks for itself. The second one with Handle, especially is more messy in my opinion.

Read the Structured Program Theorem. A do{} while() can always be rewritten to 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

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

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.

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.

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.

I've ranted before about this in regards to JavaScript, which also inherited this sorry choice from C.

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:

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.

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

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.

This is cleanest alternative to do-while that I have seen. It is the idiom recommended for Python which does not have a do-while loop.

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.

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.

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.

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.
}

First of all, I do agree that do-while is less readable than while.

But I'm amazed that after so many answers, nobody has considered why do-while even exists in the language. 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. Then if we replace it with a while loop, we get N+1 condition checks instead, where the extra check is pointless. 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. 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).

I like David Božjak's example. 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)