'<' versus '! =' comme condition dans une boucle 'for'?

31

Supposons que vous ayez la forboucle suivante *:

for (int i = 0; i < 10; ++i) {
    // ...
}

qui pourrait aussi s'écrire:

for (int i = 0; i != 10; ++i) {
    // ...
}

Les résultats finaux sont les mêmes, donc y a-t-il des arguments réels pour utiliser l'un sur l'autre? Personnellement, j'utilise l'ancien au cas où, ipour une raison quelconque, se détraquerait et sauterait la valeur 10.

* Excusez l'utilisation des nombres magiques, mais ce n'est qu'un exemple.

gablin
la source
18
La solution supérieure à l'un ou l'autre est d'utiliser l'opérateur fléché:int i = 10; while(i --> 0) { /* stuff */ }
corsiKa
@glowcoder l'opérateur fléché est mon préféré
Carson Myers
3
@glowcoder, sympa mais il traverse de l'arrière. Vous ne voudrez peut-être pas toujours cela.
2
@SuperElectric, il analyse commewhile ((i--) > 0)
Kristopher Johnson
17
Il y a une histoire (probablement apocryphe) sur un accident industriel causé par un test de boucle while pour une entrée de capteur étant! = MAX_TEMP. Malheureusement, un jour, l'entrée du capteur est passée de moins de MAX_TEMP à plus de MAX_TEMP sans chaque passage par MAX_TEMP. Le processus a surchauffé sans être détecté et un incendie s'est déclaré. Parfois, il y a une différence entre! = Et <.
Charles E. Grant

Réponses:

59

La raison de choisir l'un ou l'autre est en raison de l' intention et en conséquence, cela augmente la lisibilité .

Intention: la boucle doit s'exécuter aussi longtemps qu'elle iest inférieure à 10, pas aussi longtemps qu'elle in'est pas égale à 10. Même si cette dernière peut être la même dans ce cas particulier, ce n'est pas ce que vous voulez dire, donc elle ne devrait pas être écrit comme ça.

Lisibilité: en écrivant ce que vous voulez dire, c'est aussi plus facile à comprendre. Par exemple, si vous utilisez i != 10, quelqu'un qui lit le code peut se demander si à l'intérieur de la boucle il y a un moyen qui ipourrait devenir plus grand que 10 et que la boucle devrait continuer (btw: c'est un mauvais style de jouer avec l'itérateur ailleurs que dans la tête de la fordéclaration, mais cela ne signifie pas que les gens ne le font pas et, par conséquent, les responsables s'y attendent).

Deckard
la source
3
Non, la boucle ne doit pas s'exécuter tant qu'elle iest «inférieure à 10», elle doit s'exécuter tant que la limite supérieure (dans ce cas) n'est pas atteinte. Lorsque vous utilisez la logique d'intervalle / intervalle C ++, il est beaucoup plus logique d'exprimer cela via !=que <.
Konrad Rudolph
14
@Konrad Je ne suis pas du tout en désaccord avec cela. Le point principal n'est pas d'être dogmatique, mais plutôt de choisir la construction qui exprime le mieux l'intention de la condition. Et donc, si vous choisissez de parcourir quelque chose en commençant à 0 et en remontant, <cela exprime cela précisément.
Deckard
6
@Konrad, vous manquez le point. L'opérateur d'incrémentation dans cette boucle montre clairement que la condition de boucle est une limite supérieure, pas une comparaison d'identité. Si vous décrémentiez, ce serait une limite inférieure. Si vous parcourez une collection non commandée, l'identité peut être la bonne condition.
Alex Feinman
3
@Alex, l'incrémentation n'était pas mon propos. Un intervalle ne même pas nécessairement avoir un ordre. Il ne fait qu'itérer… enfin, quelque chose jusqu'à la fin. Par exemple, les éléments d'un ensemble de hachage. En C ++, cela se fait à l'aide d'itérateurs et d'une forboucle. Utiliser <ici serait idiot. Deckard semble également le reconnaître. Je suis tout à fait d'accord avec son commentaire en réponse au mien.
Konrad Rudolph
1
Remarque, si vous utilisez un tampon rotatif avec des pointeurs de poursuite, vous DEVEZ utiliser!=
SF.
48

Le <modèle est généralement utilisable même si l'incrément n'est pas exactement 1.

Cela permet une seule façon commune de faire des boucles, quelle que soit la façon dont cela est réellement fait.


la source
6
Il permet également ide modifier l'intérieur de la boucle en toute sécurité si besoin est.
Matthew Scharley
1
ou si 'i' est modifié de manière totalement dangereuse ... Une autre équipe a eu un problème de serveur étrange. Il a continué à signaler 100% d'utilisation du processeur et cela doit être un problème avec le serveur ou le système de surveillance, non? Non, j'ai trouvé une condition de boucle écrite par un «programmeur senior expert» avec le même problème dont nous parlons.
jqa
12
Sauf que tous les C ++ pour les boucles ne peuvent pas utiliser <, tels que les itérateurs sur une séquence, et !=serait donc une seule façon courante de faire des boucles en C ++.
David Thornley
@SnOrfus: Je n'analyse pas tout à fait ce commentaire. En général, vous n'obtiendrez pas d'exceptions de manière fiable pour incrémenter trop un itérateur (bien qu'il y ait des situations plus spécifiques où vous le ferez).
David Thornley du
Eh bien, je suis le <modèle car il nécessite une frappe au lieu de deux avec le >modèle et quatre avec !=. C'est vraiment une question d'efficacité de type OCD.
Spoike
21

En C ++, la recommandation de Scott Myers dans C ++ plus efficace (élément 6) est toujours d'utiliser la seconde, sauf si vous avez une raison de ne pas le faire, car cela signifie que vous avez la même syntaxe pour les index d'itérateur et d'entier afin que vous puissiez échanger de manière transparente entre intet itérateur. sans aucun changement de syntaxe.

Dans d'autres langues, cela ne s'applique pas, donc je suppose que <c'est probablement préférable en raison de l'argument de Thorbjørn Ravn Andersen.

Soit dit en passant, l'autre jour , je discutais avec un autre développeur et il a dit la raison de préférer <plus !=est parce que ipourrait incrémenter accidentellement par plus d'un, et qui pourrait causer la condition de rupture ne soit pas présente; c'est IMO une charge de non-sens.


la source
17
"charge de non-sens" ... jusqu'au jour où vous avez accidentellement un i ++ supplémentaire dans le corps de la boucle.
2
+1, en particulier pour «charge de non-sens», car il l'est. Si le corps de boucle incrémente accidentellement le compteur, vous avez des problèmes bien plus importants.
Konrad Rudolph
12
@B Tyler, nous ne sommes qu'humains et de plus grosses erreurs se sont déjà produites. Considérez <être une programmation plus défensive !=
2
@ Thorbjørn Ravn Andersen - Je ne dis pas que je ne suis pas d'accord avec vous, je le fais; <est plus défensif et mes collègues détestent si j'écris !=donc je ne le fais pas. Cependant, il y a un cas pour !=en C ++ et c'est ce que j'ai présenté.
9
Un scénario où l'on peut se retrouver avec un extra accidentel i++consiste à changer une boucle while en une boucle for. Pas sûr cependant que d'avoir la moitié du nombre d'itérations soit mieux qu'une boucle (presque) infinie; ce dernier rendrait probablement le bug plus évident.
ak2
12

L'utilisation de (i <10) est à mon avis une pratique plus sûre. Il attrape le nombre maximum de cas de sortie potentiels - tout ce qui est supérieur ou égal à 10. Comparez cela avec l'autre cas (i! = 10); il n'attrape qu'un seul cas de sortie possible - lorsque i est exactement 10.

Un sous-produit de cela est qu'il améliore la lisibilité. De plus, si l'incrément est différent de 1, cela peut aider à minimiser la probabilité d'un problème si nous commettons une erreur lors de l'écriture du cas de fermeture.

Considérer:

1) for (i = 1; i < 14; i+=2)

2) for (i = 1; i != 14; i+=2)

Bien que les deux cas soient probablement défectueux / incorrects, le second est probablement plus faux car il ne s'arrêtera pas. Le premier cas se fermera, et il y a plus de chances qu'il s'arrête au bon endroit, même si 14 est probablement le mauvais numéro (15 serait probablement mieux).

Sparky
la source
Le premier cas a peut-être raison! Si vous voulez itérer sur tous les nombres naturels inférieurs à 14, alors il n'y a pas de meilleure façon de l'exprimer - calculer la borne supérieure "correcte" (13) serait tout simplement stupide.
maaartinus
9

Voici une autre réponse que personne ne semble avoir encore trouvée. forles boucles doivent être utilisées lorsque vous devez parcourir une séquence . L'utilisation !=est la méthode la plus concise pour énoncer la condition de fin de la boucle. Cependant, l'utilisation d'un opérateur moins restrictif est un idiome de programmation défensive très courant. Pour les entiers, cela n'a pas d'importance - c'est juste un choix personnel sans exemple plus spécifique. Boucler les collections avec les itérateurs que vous souhaitez utiliser !=pour les raisons que d'autres ont indiquées. Si vous envisagez des séquences de floatou double, alors vous voulez éviter !=à tout prix.

Ce que je voulais souligner, c'est qu'il forest utilisé lorsque vous avez besoin d'itérer sur une séquence. La séquence générée a un point de départ, un intervalle et une condition de fin. Ceux-ci sont spécifiés de manière concise dans la fordéclaration. Si vous vous trouvez soit (1) n'incluant pas la partie étape du forou (2) spécifiant quelque chose comme truela condition de garde, alors vous ne devriez pas utiliser de forboucle!

La whileboucle est utilisée pour poursuivre le traitement lorsqu'une condition spécifique est remplie. Si vous ne traitez pas une séquence, vous voudrez probablement une whileboucle à la place. Les arguments de la condition de garde sont similaires ici, mais la décision entre a whileet une forboucle doit être très consciente. La whileboucle est sous-estimée dans les cercles C ++ IMO.

Si vous fortraitez une collection d'éléments (une utilisation très courante en boucle), vous devriez vraiment utiliser une méthode plus spécialisée. Malheureusement, std::for_eachc'est assez douloureux en C ++ pour un certain nombre de raisons. Dans de nombreux cas, séparer le corps d'une forboucle dans une fonction autonome (bien que quelque peu douloureuse) donne une solution beaucoup plus propre. Lorsque l'on travaille avec des collections, pensez std::for_each, std::transformou std::accumulate. La mise en œuvre de nombreux algorithmes devient concise et limpide lorsqu'elle est exprimée de cette manière. Sans oublier que l'isolement du corps de la boucle dans une fonction / méthode séparée vous oblige à vous concentrer sur l'algorithme, ses exigences d'entrée et ses résultats.

Si vous utilisez Java, Python, Ruby ou même C ++ 0x , vous devez alors utiliser une boucle foreach de collecte appropriée . Au fur et à mesure que les compilateurs C ++ implémentent cette fonctionnalité, un certain nombre de forboucles disparaîtront, tout comme ces types de discussions.

D.Shawley
la source
2
"Cependant, l'utilisation d'un opérateur moins restrictif est un idiome de programmation défensive très courant." Cela résume plus ou moins.
James P.
+1 pour discuter des différences d'intention par rapport à while, foret foreach(ou l'équivalent linguistique)
Kevin Peno
À strictement parler, la whileboucle est utilisée pour continuer le traitement jusqu'à ce qu'une condition spécifique ne soit pas remplie
Alnitak
@Alnitak - D'oh ... je vais arranger ça :)
D.Shawley
J'étais confus par les deux significations possibles de «moins restrictif»: il pouvait se référer à l'opérateur étant indulgent dans les valeurs qu'il passe ( <) ou l'opérateur étant indulgent dans les valeurs qu'il échoue ( !=).
Mateen Ulhaq
4

Utiliser "moins de" est (généralement) sémantiquement correct, vous voulez vraiment dire que compter jusqu'à in'est plus inférieur à 10, donc "moins de" exprime clairement vos intentions. L'utilisation de "non égal" fonctionne évidemment dans des cas d'appels virtuels, mais donne une signification légèrement différente. Dans certains cas, c'est peut-être ce dont vous avez besoin, mais d'après mon expérience, cela n'a jamais été le cas. Si vous aviez vraiment un cas où il ipourrait y avoir plus ou moins de 10 mais que vous voulez continuer à boucler jusqu'à ce qu'il soit égal à 10, alors ce code aurait vraiment besoin de commenter très clairement, et pourrait probablement être mieux écrit avec une autre construction, comme en tant que whileboucle peut-être.

Steve
la source
2

L'une des raisons pour lesquelles je préférerais un less thansur un not equalsest d'agir comme gardien. Dans certaines circonstances limitées (mauvaise programmation ou désinfection), cela not equalspourrait être ignoré alors qu'il less thanserait toujours en vigueur.

Voici un exemple où l'absence d'un contrôle de désinfection a conduit à des résultats étranges: http://www.michaeleisen.org/blog/?p=358

James P.
la source
0

Voici une raison pour laquelle vous préféreriez peut-être utiliser <plutôt que !=. Si vous utilisez un langage qui a une portée globale de variable, que se passe-t-il si un autre code est modifié i? Si vous utilisez <plutôt que !=, le pire qui se passe est que l'itération se termine plus rapidement: peut-être d'autres incréments de code ipar accident, et vous sautez quelques itérations dans la boucle for. Mais que se passe-t-il si vous bouclez de 0 à 10, que la boucle arrive à 9 et que certains incréments de thread sont mal écrits ipour une raison étrange. Ensuite, votre boucle termine cette itération et incrémente de isorte que la valeur soit maintenant 11. Comme la boucle a ignoré la condition de sortie ( ijamais égale à 10), elle va maintenant boucler infiniment.

Cela ne doit pas nécessairement être une logique de type threading-and-global-variables particulièrement bizarre qui provoque cela. Il se peut que vous écriviez une boucle qui doit revenir en arrière. Si vous mutez ià l'intérieur de la boucle et que vous bousillez votre logique, la faire en sorte qu'elle ait une limite supérieure plutôt qu'un a !=est moins susceptible de vous laisser dans une boucle infinie.

Tom Morris
la source
3
Bien sûr, cela semble être un argument parfait pour les boucles for-each et un style de programmation plus fonctionnel en général. Mais pourquoi voudriez-vous faire cela quand les variables mutables sont tellement plus amusantes ?!
Tom Morris
3
Je ne pense pas que ce soit une très bonne raison. De toute façon, vous avez un bug qui doit être trouvé et corrigé, mais une boucle infinie a tendance à rendre un bug assez évident.
ak2
0

Dans le monde embarqué, en particulier dans les environnements bruyants, vous ne pouvez pas compter sur la RAM se comportant nécessairement comme il se doit. Dans une conditionnelle (pour, tandis que, si) où vous comparez en utilisant '==' ou '! =' Vous courez toujours le risque que vos variables sautent cette valeur cruciale qui termine la boucle - cela peut avoir des conséquences désastreuses - Mars Lander conséquences de niveau. L'utilisation de «<» ou «>» dans la condition fournit un niveau de sécurité supplémentaire pour attraper les «inconnues inconnues».

Dans l'exemple d'origine, si elles iétaient inexplicablement catapultées à une valeur bien supérieure à 10, la comparaison '<' intercepterait l'erreur immédiatement et quitterait la boucle, mais '! =' Continuerait à compter jusqu'à ce qu'elle soit ienroulée autour de 0 et retour au 10.

oosterwal
la source
2
Une autre variation connexe existe avec le code comme for (i=4; i<length; i++) csum+=dat[i];où les paquets légitimes auraient toujours lengthau moins quatre, mais l'un pourrait recevoir des paquets corrompus. Si différents types de paquets ont des exigences de longueur différentes, on peut souhaiter valider la longueur dans le cadre du traitement qui est différent pour chaque type de paquet, mais valider d'abord la somme de contrôle dans le cadre de la logique commune. Si un paquet de longueur insuffisante est reçu, peu importe que le calcul de la somme de contrôle signale "bon" ou "mauvais", mais il ne devrait pas planter.
supercat