Bjarne Stroustrup (créateur de C ++) a dit un jour qu’il évite les boucles “do / while” et préfère écrire le code en termes de boucle “while”. [Voir la 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 “faire / faire” est beaucoup plus propre et plus facile à comprendre que si vous utilisiez plutôt un “temps”?
En réponse à certaines des réponses: oui, je comprends la différence technique entre “faire / pendant” et “tout”. C’est une question plus profonde sur la lisibilité et la structuration du code impliquant des boucles.
Permettez-moi de vous poser une autre question: supposez que vous ne soyez pas autorisé à utiliser “do / while” – existe-t-il un exemple réaliste où cela ne vous donnerait d’autre choix que d’écrire du code non conforme en utilisant “while”?
De “Le langage de programmation C ++”, 6.3.3:
D’après mon expérience, la déclaration de conduite est source d’erreurs et de confusion. La raison en est que son corps est toujours exécuté une fois avant que la condition soit évaluée. Cependant, pour que le corps fonctionne correctement, quelque chose de très semblable à la condition doit se maintenir même la première fois. Plus souvent que je ne l’aurais deviné, j’ai constaté que cette condition ne devait pas être respectée, que ce soit lorsque le programme a été écrit et testé pour la première fois, ou plus tard, après la modification du code précédent. Je préfère également la condition “à l’avant où je peux le voir”. Par conséquent, j’ai tendance à éviter les déclarations de faits. -Bjarne
Oui, je suis d’accord sur le fait que les boucles while peuvent être réécrites dans une boucle while, mais je ne suis pas d’accord sur le fait que toujours utiliser une boucle while est préférable. faire toujours se faire au moins une fois et c’est une propriété très utile (l’exemple le plus typique étant la vérification des entrées (à partir du clavier))
#include int main() { char c; do { printf("enter a number"); scanf("%c", &c); } while (c < '0' || c > '9'); }
Cela peut bien sûr être réécrit en boucle, mais cela 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 lorsque 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 la boucle puisse être évaluée de manière sensible. Avec while loop, vous devrez appeler le code d’initialisation à partir de deux sites, avec do-while, vous ne pouvez l’appeler qu’à partir d’un seul site.
Un autre exemple est lorsque vous avez déjà un object valide lors du démarrage de la première itération, vous ne voulez donc rien exécuter (évaluation de l’état de la boucle incluse) avant le début de la première itération. Un exemple est avec les fonctions FindFirstFile / FindNextFile Win32: 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 ce n’est pas important dans le code réel (avec lequel je ne suis pas nécessairement d’accord), il est important de corriger certaines des faiblesses du préprocesseur.
Edit: Je me suis trouvé dans une situation où les choses étaient plus propres aujourd’hui dans mon propre code. Je faisais une abstraction multi-plateforme des instructions LL / SC appariées. 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 réaliser que l’ancienne valeur 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 de situation dans laquelle do / while est nettement plus propre que l’équivalent alors 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 ait choisi de supprimer la structure do-while .
C’est utile lorsque vous voulez “faire” quelque chose “jusqu’à ce qu’une condition soit satisfaite”.
Il peut être modifié en boucle comme ceci:
while(true) { // .... code ..... if(condition_satisfied) break; }
(En supposant que vous connaissez la différence entre les deux)
Do / While convient au démarrage / pré-initialisation du code avant la vérification de votre condition et l’exécution de la boucle while.
Dans nos conventions de codage
Donc nous n’avons presque jamais 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 ); }
L’idiome commun suivant me semble très simple:
do { preliminary_work(); value = get_value(); } while (not_valid(value));
La réécriture à éviter 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é à la première fois. En d’autres termes, le test est toujours superflu la première fois. Si ce test superflu n’était pas là, on pourrait également omettre l’assignation initiale. Ce code donne l’impression qu’il se bat.
Dans des cas comme ceux-ci, la construction est une option très utile.
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 (comme l’optimisation) sont de loin moins importantes dans la plupart des cas.
Je vais élaborer, car 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 , alors je voterais pour A. Si vous préférez B , puisque vous voyez la condition de la boucle “au premier plan”, et que vous pensez que c’est plus lisible (et donc maintenable, etc.) – alors allez tout de suite, utilisez B.
Mon point est le suivant: utilisez le code qui est plus lisible à vos yeux (et à vos collègues). Le choix est subjectif, bien sûr.
Ce n’est que choix personnel à mon avis.
La plupart du temps, vous pouvez trouver un moyen de réécrire une boucle do … while dans une boucle while; mais pas forcément toujours En outre, il 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, surtout est plus désordonné à mon avis.
C’est l’alternative la plus propre à ce 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 continue
dans le
car cela sauterait la condition de rupture, mais aucun des exemples qui montrent les avantages du do-while nécessite une poursuite avant la condition.
while (true) { if (!condition) break; }
Ici, il est appliqué à 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 car la condition est maintenue près du 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. }
Je les utilise rarement à cause de ce qui suit:
Même si la boucle recherche une post-condition, vous devez toujours vérifier cette post-condition dans votre boucle afin de ne pas traiter la condition de publication.
Prenez l’exemple de code pseudo:
do { // get data // process data } while (data != null);
Cela semble simple en théorie, mais dans des situations réelles, il se pourrait que cela se présente comme suit:
do { // get data if (data != null) { // process data } } while (data != null);
La vérification “if” supplémentaire ne vaut pas la peine IMO. J’ai trouvé très peu d’exemples où il est plus compliqué de faire un do-time plutôt qu’une boucle while. YMMV.
En réponse à une question / un commentaire inconnu (google) sur 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 ().
Lisez le théorème du programme structuré . Un do {} while () peut toujours être réécrit à while () do {}. La séquence, la sélection et l’itération sont tout ce dont vous avez besoin.
Comme tout ce qui est contenu dans le corps de la boucle peut toujours être encapsulé dans une routine, la saleté de devoir utiliser while () ne {} doit jamais être pire que
LoopBody() while(cond) { LoopBody() }
Une boucle do-while peut toujours être réécrite en tant que boucle while.
Que ce soit pour utiliser uniquement des boucles, ou des boucles, ou des combinaisons, dépend 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 IMHO.
Pour 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 de faire du temps si vous ne le pouvez pas pour une raison quelconque. (Bien sûr, cet exemple viole DRY, mais ce n’est qu’une preuve de concept. Dans mon expérience, il existe généralement un moyen de transformer une boucle do-while en boucle while et de ne pas violer DRY dans un cas d’utilisation concret.)
“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, la déclaration 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. Pour le bon fonctionnement du corps, cependant, une condition similaire à la condition finale doit être maintenue lors de la première parsing. Plus souvent que prévu, j’ai trouvé que ces conditions n’étaient pas vraies. Ce fut le cas à la fois lorsque j’ai écrit le programme en question à partir de zéro et que je l’ai ensuite testé ainsi qu’après un changement de code. De plus, je préfère la condition “d’avant, où je peux le voir”. J’ai donc tendance à éviter les déclarations de faits.
(Note: Ceci est ma traduction de l’édition allemande. Si vous possédez l’édition anglaise, n’hésitez pas à modifier la citation pour correspondre à 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 suis d’accord pour dire que le do-while
est moins lisible que le while
.
Mais je suis étonné qu’après tant de réponses, personne n’ait réfléchi à la raison d’ do-while
de la langue. La raison en est l’efficacité.
Disons que nous avons une boucle do-while
while avec N
vérifications de condition, où le résultat de la condition dépend du corps de la boucle. Alors si nous le 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 la boucle ne contient qu’une vérification 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 soaps déjà que x
n’est pas encore défini. Alors, pourquoi exécuter un code inutile?
J’utilise souvent do-time à cette fin lorsque je code des systèmes embarqués temps réel, où le code à l’intérieur de la condition est relativement lent (vérification de la réponse de certains périphériques matériels lents).
considérer quelque chose comme ceci:
int SumOfSsortingng(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 aurez compris.
Mon problème avec do / while est ssortingctement lié à son implémentation en C. En raison de la réutilisation du mot-clé while , il déclenche souvent des erreurs car cela ressemble à une erreur.
Si while avait été réservé uniquement aux boucles while et do / while avait été changé en do / until ou repeat / until , je ne pense pas que la boucle (ce qui est certainement pratique et la bonne façon de coder certaines boucles) provoquerait Tant de problèmes.
J’en ai déjà parlé en ce qui concerne JavaScript , qui a également hérité de ce choix regrettable de C.
Eh bien peut-être cela remonte à quelques pas, mais dans le cas de
do { output("enter a number"); int x = getInput(); //do stuff with input }while(x != 0);
Ce serait possible, mais pas nécessairement lisible à 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 que l’utilisation d’une instruction d’affectation dans une conditionnelle est considérée comme une mauvaise pratique. Je voulais simplement souligner qu’il existe des moyens plus compacts d’assigner une valeur “réservée” pour indiquer à la boucle que c’est la première traversée.
J’aime l’exemple de David Božjak. Pour jouer le rôle de l’avocat du diable, cependant, je pense que vous pouvez toujours intégrer le code que vous souhaitez exécuter au moins une fois dans une fonction distincte, en obtenant 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; }
(pardonnez toute syntaxe incorrecte; je ne suis pas un programmeur C)