web-dev-qa-db-fra.com

Une boucle "do {...} while ()" est-elle nécessaire?

Bjarne Stroustrup (créateur de C++) a dit un jour qu'il évitait les boucles "do/while" et préférait écrire le code sous la forme d'une boucle "while". [Voir citation ci-dessous.]

Depuis que j'ai entendu cela, j'ai trouvé que c'était vrai. Quelles sont vos pensées? Existe-t-il un exemple où un "do/while" est beaucoup plus propre et plus facile à comprendre que si vous utilisiez un "while" à la place?

En réponse à certaines des réponses: oui, je comprends la différence technique entre "do/while" et "while". C'est une question plus profonde sur la lisibilité et la structuration de code impliquant des boucles.

Permettez-moi de vous poser une autre question: supposez qu'il vous soit interdit d'utiliser "do/while" - existe-t-il un exemple réaliste où cela ne vous donnerait pas d'autre choix que d'écrire du code non propre en utilisant "while"?

Dans "Le langage de programmation C++", 6.3.3:

D'après mon expérience, la déclaration est une source d'erreurs et de confusion. La raison en est que son corps est toujours exécuté une fois avant l'évaluation de la condition. Cependant, pour que le corps fonctionne correctement, quelque chose de très similaire à la condition doit tenir même la première fois. Plus souvent que je ne l'aurais deviné, j'ai constaté que cette condition n'était pas remplie comme prévu lorsque le programme avait été écrit et testé pour la première fois, ou plus tard après que le code le précédant ait été modifié. Je préfère aussi la condition "à l'avant où je peux le voir." En conséquence, j’ai tendance à éviter les déclarations faites. -Bjarne

65
Dustin Boswell

Oui, je suis d’accord pour dire que les boucles while peuvent être réécrites en boucles while, mais je ne suis pas d’accord pour dire qu’utiliser toujours une boucle while est préférable. faire tout en étant toujours exécuté au moins une fois et c'est une propriété très utile (l'exemple le plus typique étant la vérification d'entrée (à partir du clavier))

#include <stdio.h>

int main() {
    char c;

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

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

Ceci peut bien sûr être réécrit en boucle while, mais ceci est généralement considéré comme une solution beaucoup plus élégante.

86
David Božjak

do-while est une boucle avec une post-condition. Vous en avez besoin dans les cas où le corps de la boucle doit être exécuté au moins une fois. Cela est nécessaire pour le code qui nécessite une action avant que la condition de boucle puisse être évaluée de manière raisonnable. Avec la boucle while, vous devez appeler le code d'initialisation à partir de deux sites. Avec do-while, vous ne pouvez l'appeler que d'un seul site.

Un autre exemple est lorsque vous avez déjà un objet valide lorsque la première itération doit être démarrée, vous ne voulez donc rien exécuter (évaluation de la condition de boucle incluse) avant le début de la première itération. Un exemple est avec les fonctions WinFind FindFirstFile/FindNextFile: vous appelez FindFirstFile qui renvoie une erreur ou un descripteur de recherche au premier fichier, puis vous appelez FindNextFile jusqu'à ce qu'il renvoie une erreur.

Pseudocode:

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

do { ... } while (0) est une construction importante pour que les macros se comportent bien.

Même si cela n'a pas d'importance dans le code réel (avec lequel je ne suis pas nécessairement d'accord), il est important de corriger certaines des carences du pré-processeur.

Edit: Je suis tombé sur une situation où do/while était beaucoup plus propre aujourd'hui dans mon propre code. Je faisais une abstraction multiplateforme des instructions appariées LL/SC . Celles-ci doivent être utilisées en boucle, comme ceci:

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

(Les experts peuvent se rendre compte que oldvalue n’est pas utilisée dans une implémentation SC, mais elle est incluse pour que cette abstraction puisse être émulée avec CAS.)

LL et SC sont un excellent exemple d'une situation dans laquelle do/while est nettement plus propre que son équivalent, tandis que la forme:

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

Pour cette raison, je suis extrêmement déçu du fait que Google Go a opté pour la suppression de la construction en attente .

17
Dan Olson

C'est utile lorsque vous voulez "faire" quelque chose "jusqu'à ce que" une condition soit remplie.

Il peut être falsifié dans une boucle while comme ceci:

while(true)
{

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

    if(condition_satisfied) 
        break;
}
7
hasen

Le langage commun suivant me semble très simple:

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

La réécriture pour éviter do semble être:

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

Cette première ligne est utilisée pour s'assurer que le test est toujours évalué comme vrai la première fois En d'autres termes, le test est toujours superflu la première fois. Si ce test superflu n’existait pas, on pourrait aussi omettre l’affectation initiale. Ce code donne l'impression qu'il se bat tout seul.

Dans de tels cas, la construction do est une option très utile.

6
Svante

(En supposant que vous connaissez la différence entre les deux)

Do/While est utile pour initialiser/pré-initialiser le code avant que votre condition ne soit vérifiée et que la boucle while ne soit exécutée.

6
Suvesh Pratapa

Dans nos conventions de codage

  • si/tant que/... conditions ne pas avoir d'effets secondaires et
  • les variables doivent être initialisées.

Nous n’avons donc presque jamais de do {} while(xx) Parce que:

int main() {
    char c;

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

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

est réécrit en:

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

et 

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

est réécrit en:

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

Tout est question de lisibilité .
Un code plus lisible entraîne moins de maux de tête dans la maintenance du code et une meilleure collaboration.
D'autres considérations (telles que l'optimisation) sont de loin moins importantes dans la plupart des cas.
Je vais élaborer, puisque j'ai un commentaire ici:
Si vous avez un extrait de code A qui utilise do { ... } while() et qu'il est plus lisible que son équivalent while() {...}B, je voterais pour A. Si vous préférez B, puisque vous voyez l'état de la boucle "à l'avant", et que vous pensez que c'est plus lisible (et donc maintenable, etc.) - alors continuez, utilisez B.
Mon problème est le suivant: utilisez un code plus lisible à vos yeux (et à ceux de vos collègues). Le choix est subjectif, bien sûr.

5
Ron Klein

Ce n'est qu'un choix personnel à mon avis.

La plupart du temps, vous pouvez trouver un moyen de réécrire une boucle do ... while en une boucle while; mais pas nécessairement toujours. En outre, il serait peut-être plus logique d’écrire une boucle do while parfois pour s’adapter au contexte dans lequel vous vous trouvez.

Si vous regardez ci-dessus, la réponse de TimW, elle parle d'elle-même. Le second avec Handle est surtout plus salissant à mon avis.

3
ϹοδεMεδιϲ

C'est l'alternative la plus propre à faire pendant que j'ai vu. C'est l'idiome recommandé pour Python qui n'a pas de boucle do-while. 

Une mise en garde est que vous ne pouvez pas avoir une continue dans le <setup code> car elle sauterait la condition de rupture, mais aucun des exemples montrant les avantages de ce qu'il faut faire n'a pas besoin de continuer avant la condition.

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

Ici, il s’applique à certains des meilleurs exemples de boucles Do-While ci-dessus.

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

Cet exemple suivant est un cas où la structure est plus lisible qu'un do-while puisque la condition est conservée vers le haut, car //get data est généralement court, mais la partie //process data peut être longue.

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

Lisez le théorème du programme structuré . Un do {} while () peut toujours être réécrit en while () do {}. La séquence, la sélection et l'itération sont tout ce dont vous avez besoin.

Puisque tout ce qui est contenu dans le corps de la boucle peut toujours être encapsulé dans une routine, la saleté de devoir utiliser while () do {} n'a jamais besoin d'être pire que

LoopBody()
while(cond) {
    LoopBody()
}
2
John Pirie

Une boucle do-while peut toujours être réécrite en tant que boucle while.

Que vous utilisiez uniquement des boucles While ou des boucles Do-While et For-Loops (ou toute combinaison de celles-ci) dépend en grande partie de votre goût pour l'esthétique et des conventions du projet sur lequel vous travaillez.

Personnellement, je préfère les boucles while, car cela simplifie le raisonnement sur les invariants de boucle, à mon humble avis.

Quant à savoir s’il existe des situations où vous avez besoin de boucles do-while: au lieu de

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

tu peux toujours 

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

alors, non, vous n'avez jamais besoin d'utiliser do-while si vous ne pouvez pas le faire pour une raison quelconque. (Bien sûr, cet exemple viole DRY, mais ce n'est qu'une preuve de concept. Selon mon expérience, il existe généralement un moyen de transformer une boucle do-while en une boucle while et de ne pas violer DRY dans un usage concret Cas.)

"Quand à Rome, fais comme les Romains."

BTW: La citation que vous recherchez est peut-être celle-ci ([1], dernier paragraphe de la section 6.3.3):

D'après mon expérience, le do-statement est une source d'erreur et de confusion. La raison en est que son corps est toujours exécuté une fois avant que la condition ne soit testée. Cependant, pour le bon fonctionnement du corps, une condition similaire à la condition finale doit être remplie lors de la première manche. Plus souvent que prévu, j'ai trouvé que ces conditions n'étaient pas vraies. C'était le cas à la fois lorsque j'ai écrit le programme en question à partir de rien et que je l'ai ensuite testé, ainsi qu'après un changement de code. De plus, je préfère la condition "de face, où je peux la voir". J'ai donc tendance à éviter les déclarations fausses.

(Remarque: Ceci est ma traduction de l'édition allemande. Si vous possédez l'édition anglaise, n'hésitez pas à modifier la citation afin qu'elle corresponde à son libellé d'origine. Malheureusement, Addison-Wesley déteste Google.)

[1] B. Stroustrup: Le langage de programmation C++. 3ème édition. Addison-Wessley, Reading, 1997.

2
Tobias

Tout d'abord, je conviens que do-while est moins lisible que while.

Mais je suis étonné qu'après tant de réponses, personne ne se soit demandé pourquoi do-while existe même dans la langue. La raison est l'efficacité. 

Disons que nous avons une boucle do-while avec des vérifications de condition N, dans lesquelles le résultat de la condition dépend du corps de la boucle. Ensuite, si nous la remplaçons par une boucle while, nous obtenons à la place des vérifications de condition N+1, où la vérification supplémentaire est inutile. Ce n'est pas grave si la condition de boucle ne contient que le contrôle d'une valeur entière, mais disons que nous avons

something_t* x = NULL;

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

Ensuite, l'appel de fonction dans le premier tour de la boucle est redondant: nous savons déjà que x n'est pas encore défini. Alors, pourquoi exécuter un code supplémentaire inutile? 

J'utilise souvent do-while à cette fin lors du codage de systèmes embarqués en temps réel, où le code à l'intérieur de la condition est relativement lent (vérification de la réponse d'un périphérique matériel lent).

2
Lundin

Je ne les utilise presque jamais simplement pour les raisons suivantes:

Même si la boucle recherche une post-condition, vous devez toujours vérifier cette post-condition dans votre boucle pour ne pas la traiter.

Prenez l'exemple de pseudo-code:

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

Cela semble simple en théorie, mais dans des situations réelles, cela ressemblerait probablement à ceci:

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

Le chèque supplémentaire "si" ne vaut tout simplement pas la peine, IMO. J'ai trouvé très peu de cas où il est plus difficile de faire une boucle do-while au lieu d'une boucle while. YMMV.

2
rein

En réponse à une question/commentaire de unknown (google) à la réponse de Dan Olson:

"do {...} while (0) est une construction importante pour que les macros se comportent bien."

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

Voyez-vous ce qui se passe sans le do { ... } while(0)? Il exécutera toujours doAnother ().

2
Markus Schnell

Mon problème avec do / while est strictement avec son implémentation en C. En raison de la réutilisation de tandis que mot clé, il fait souvent trébucher les gens parce qu’il ressemble à une erreur.

Si alors que avait été réservé pour seulement tandis que effectue une boucle et do / alors que avait été remplacé par do / jusqu'à ou répéter / jusqu'au , je ne pense pas que la boucle (ce qui est certainement pratique et la "bonne" façon de coder certaines boucles) causerait tant de problèmes.

J'ai déjà critiqué cela à propos de JavaScript , qui a également hérité de ce mauvais choix de C.

1
Nosredna

Peut-être que cela remonte quelques pas en arrière, mais dans le cas de 

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

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

Il serait possible, bien que pas nécessairement lisible, d'utiliser

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

Maintenant, si vous voulez utiliser un nombre autre que 0 pour quitter la boucle

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

Mais encore une fois, il y a une perte de lisibilité, sans parler du fait qu’il est considéré comme une mauvaise pratique d’utiliser une instruction d’affectation dans un conditionnel, je voulais simplement souligner qu’il existe des moyens plus compacts de le faire que d’attribuer une valeur "réservée". à la boucle que c'est la première course à travers.

1
Felps

considérez quelque chose comme ceci:

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

Il se trouve que '\ 0' vaut 0, mais j'espère que vous avez compris.

1
Nefzen

J'aime l'exemple de David Božjak. Toutefois, pour défendre l’avocat du diable, j’estime que vous pouvez toujours intégrer au moins le code que vous souhaitez exécuter au moins une fois dans une fonction distincte, pour obtenir peut-être la solution la plus lisible. Par exemple:

int main() {
    char c;

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

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

pourrait devenir ceci:

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

(excusez toute syntaxe incorrecte; je ne suis pas un programmeur C)

0
Niko Bellic