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
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.
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 ) );
}
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 .
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;
}
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.
(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.
Dans nos conventions de codage
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 );
}
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.
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.
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.
}
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()
}
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.
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).
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.
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 ().
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.
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.
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.
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)