Doit-on utiliser <ou <= dans une boucle for [fermée]


124

Si vous deviez parcourir une boucle 7 fois, utiliseriez-vous:

for (int i = 0; i < 7; i++)

ou:

for (int i = 0; i <= 6; i++)

Il y a deux considérations:

  • performance
  • lisibilité

Pour les performances, je suppose Java ou C #. Est-ce important que l'on utilise «inférieur à» ou «inférieur ou égal à»? Si vous avez un aperçu d'une autre langue, veuillez l'indiquer.

Pour la lisibilité, je suppose des tableaux basés sur 0.

UPD: Ma mention des tableaux basés sur 0 peut avoir confondu les choses. Je ne parle pas d'itérer à travers des éléments de tableau. Juste une boucle générale.

Il y a un bon point ci-dessous sur l'utilisation d'une constante pour expliquer ce qu'est ce nombre magique. Donc si j'avais " int NUMBER_OF_THINGS = 7" alors " i <= NUMBER_OF_THINGS - 1" aurait l'air bizarre, n'est-ce pas.


Je dirais: si vous parcourez tout le tableau, ne soustrayez ni n'ajoutez aucun nombre sur le côté gauche.
Letterman

Réponses:


287

Le premier est plus idiomatique . En particulier, il indique (dans un sens basé sur 0) le nombre d'itérations. Lorsque j'utilise quelque chose de basé sur 1 (par exemple JDBC, IIRC), je pourrais être tenté d'utiliser <=. Alors:

for (int i=0; i < count; i++) // For 0-based APIs

for (int i=1; i <= count; i++) // For 1-based APIs

Je m'attendrais à ce que la différence de performances soit insignifiante dans le code du monde réel.


30
Vous êtes presque assuré qu'il n'y aura pas de différence de performance. De nombreuses architectures, comme x86, ont des instructions "sauter sur inférieur ou égal lors de la dernière comparaison". La façon la plus probable de voir une différence de performance serait d'utiliser une sorte de langage interprété mal implémenté.
Wedge

3
Envisageriez-vous d'utiliser! = À la place? Je dirais que cela établit le plus clairement i comme un compteur de boucle et rien d'autre.
yungchin le

21
Je ne le ferais pas d'habitude. C'est trop peu familier. Cela risque également d'entrer dans une très, très longue boucle si quelqu'un incrémente accidentellement i pendant la boucle.
Jon Skeet le

5
La programmation générique avec des itérateurs STL nécessite l'utilisation de! =. Cela (double incrémentation accidentelle) n'a pas été un problème pour moi. Je suis d'accord que pour les indices <(ou> pour décroissant) sont plus clairs et conventionnels.
Jonathan Graehl

2
N'oubliez pas que si vous bouclez sur la longueur d'un tableau en utilisant <, le JIT optimise l'accès au tableau (supprime les vérifications liées). Cela devrait donc être plus rapide que d'utiliser <=. Je ne l'ai pas vérifié cependant
configurateur

72

Ces deux boucles sont répétées 7 fois. Je dirais que celui avec un 7 est plus lisible / plus clair, à moins que vous n'ayez une très bonne raison pour l'autre.


Je me souviens quand j'ai commencé à apprendre Java. J'ai détesté le concept d'un index basé sur 0 parce que j'ai toujours utilisé des index basés sur 1. J'utiliserais donc toujours la variante <= 6 (comme indiqué dans la question). À mon propre détriment, parce que cela me dérouterait plus éventuellement quand la boucle for est effectivement sortie. Il est plus simple d'utiliser simplement le <
James Haug

55

Je me souviens de mes jours où nous faisions 8086 Assembly à l'université, c'était plus performant à faire:

for (int i = 6; i > -1; i--)

car il y avait une opération JNS qui signifie Jump if No Sign. L'utilisation de cela signifiait qu'il n'y avait pas de recherche de mémoire après chaque cycle pour obtenir la valeur de comparaison et aucune comparaison non plus. De nos jours, la plupart des compilateurs optimisent l'utilisation des registres, de sorte que la mémoire n'est plus importante, mais vous obtenez toujours une comparaison inutile.

Au fait, mettre 7 ou 6 dans votre boucle introduit un " nombre magique ". Pour une meilleure lisibilité, vous devez utiliser une constante avec un nom révélateur d'intention. Comme ça:

const int NUMBER_OF_CARS = 7;
for (int i = 0; i < NUMBER_OF_CARS; i++)

EDIT: Les gens ne reçoivent pas l'assemblage, donc un exemple plus complet est évidemment nécessaire:

Si nous faisons pour (i = 0; i <= 10; i ++), vous devez faire ceci:

    mov esi, 0
loopStartLabel:
                ; Do some stuff
    inc esi
                ; Note cmp command on next line
    cmp esi, 10
    jle exitLoopLabel
    jmp loopStartLabel
exitLoopLabel:

Si nous faisons pour (int i = 10; i> -1; i--) alors vous pouvez vous en tirer avec ceci:

    mov esi, 10
loopStartLabel:
                ; Do some stuff
    dec esi
                ; Note no cmp command on next line
    jns exitLoopLabel
    jmp loopStartLabel
exitLoopLabel:

Je viens de vérifier et le compilateur C ++ de Microsoft ne fait pas cette optimisation, mais il le fait si vous le faites:

for (int i = 10; i >= 0; i--) 

Donc, la morale est que si vous utilisez Microsoft C ++ †, et que la montée ou la descente ne fait aucune différence, pour obtenir une boucle rapide, vous devez utiliser:

for (int i = 10; i >= 0; i--)

plutôt que l'un ou l'autre:

for (int i = 10; i > -1; i--)
for (int i = 0; i <= 10; i++)

Mais franchement, obtenir la lisibilité de "for (int i = 0; i <= 10; i ++)" est normalement beaucoup plus important que de manquer une commande de processeur.

† D'autres compilateurs peuvent faire des choses différentes.


4
Le cas du "nombre magique" illustre bien pourquoi il est généralement préférable d'utiliser <que <=.
Rene Saarsoo

11
Une autre version est "for (int i = 10; i--;)". Certaines personnes utilisent "for (int i = 10; i -> 0;)" et prétendent que la combinaison -> signifie va vers.
Zayenz

2
+1 pour le code d'assemblage
neuro

1
mais quand vient le temps d'utiliser réellement le compteur de boucles, par exemple pour l'indexation de tableau, alors vous devez faire ce 7-iqui va submerger toutes les optimisations que vous obtenez en comptant à rebours.
Lie Ryan le

@Lie, cela ne s'applique que si vous devez traiter les éléments dans l'ordre suivant. Avec la plupart des opérations de ce type de boucles, vous pouvez les appliquer aux éléments de la boucle dans l'ordre de votre choix. Par exemple, si vous recherchez une valeur, peu importe si vous commencez à la fin de la liste et travaillez vers le haut ou au début de la liste et travaillez vers le bas (en supposant que vous ne pouvez pas prédire la fin de la liste de votre élément probablement et la mise en cache de la mémoire n'est pas un problème).
Martin Brown

27

J'utilise toujours <array.length car il est plus facile à lire que <= array.length-1.

ayant également <7 et étant donné que vous savez qu'il commence par un index 0, il devrait être intuitif que le nombre soit le nombre d'itérations.


Vous devez toujours veiller à vérifier le coût des fonctions Length lorsque vous les utilisez dans une boucle. Par exemple, si vous utilisez strlen en C / C ++, vous allez augmenter massivement le temps nécessaire pour effectuer la comparaison. C'est parce que strlen doit parcourir toute la chaîne pour trouver sa réponse, ce que vous ne voulez probablement faire qu'une seule fois plutôt que pour chaque itération de votre boucle.
Martin Brown

2
@Martin Brown: en Java (et je crois en C #), String.length et Array.length sont constants car String est immuable et Array a une longueur immuable. Et comme String.length et Array.length est un champ (au lieu d'un appel de fonction), vous pouvez être sûr qu'ils doivent être O (1). Dans le cas de C ++, pourquoi diable utilisez-vous C-string en premier lieu?
Lie Ryan le

18

Vu d'un point de vue optimisant, cela n'a pas d'importance.

Vu du point de vue du style de code, je préfère <. Raison:

for ( int i = 0; i < array.size(); i++ )

est tellement plus lisible que

for ( int i = 0; i <= array.size() -1; i++ )

<vous donne également le nombre d'itérations immédiatement.

Un autre vote pour <est que vous pourriez éviter beaucoup d'erreurs accidentelles.


10

@Chris, Votre déclaration selon laquelle .Length est coûteux dans .NET est en fait fausse et dans le cas de types simples, c'est exactement le contraire.

int len = somearray.Length;
for(i = 0; i < len; i++)
{
  somearray[i].something();
}

est en fait plus lent que

for(i = 0; i < somearray.Length; i++)
{
  somearray[i].something();
}

Ce dernier est un cas optimisé par le runtime. Puisque le runtime peut garantir que i est un index valide dans le tableau, aucune vérification des limites n'est effectuée. Dans le premier cas, le runtime ne peut pas garantir que je n'ai pas été modifié avant la boucle et force les vérifications de limites sur le tableau pour chaque recherche d'index.


En Java, .Length peut être coûteux dans certains cas. stackoverflow.com/questions/6093537/for-loop-optimization b'coz il appelle à .lenghtOR .size. Je ne suis pas sûr mais pas confiant mais je veux être sûr seulement.
Ravi Parekh

6

Cela ne fait aucune différence en termes de performances. Par conséquent, j'utiliserais celui qui est le plus facile à comprendre dans le contexte du problème que vous résolvez.


5

Je préfère:

for (int i = 0; i < 7; i++)

Je pense que cela se traduit plus facilement par "itérer dans une boucle 7 fois".

Je ne suis pas sûr des implications sur les performances - je soupçonne que toutes les différences seraient compilées.


4

Dans Java 1.5, vous pouvez simplement faire

for (int i: myArray) {
    ...
}

donc pour le cas du tableau, vous n'avez pas à vous inquiéter.


4

Je ne pense pas qu'il y ait une différence de performance. La deuxième forme est certainement plus lisible, vous n'avez pas à en soustraire mentalement une pour trouver le dernier numéro d'itération.

EDIT: Je vois que les autres ne sont pas d'accord. Personnellement, j'aime voir les numéros d'index réels dans la structure de la boucle. C'est peut-être parce que cela rappelle davantage la 0..6syntaxe de Perl , dont je sais qu'elle est équivalente (0,1,2,3,4,5,6). Si je vois un 7, je dois vérifier l'opérateur à côté pour voir qu'en fait, l'indice 7 n'est jamais atteint.


Cela dépend si vous pensez que le "dernier numéro d'itération" est plus important que le "nombre d'itérations". Avec une API basée sur 0, ils différeront toujours de 1 ...
Jon Skeet

4

Je dirais d'utiliser la version "<7" parce que c'est ce que la majorité des gens liront - donc si les gens lisent votre code, ils pourraient l'interpréter mal.

Je ne me soucierais pas de savoir si "<" est plus rapide que "<=", optez simplement pour la lisibilité.

Si vous souhaitez augmenter la vitesse, tenez compte des éléments suivants:

for (int i = 0; i < this->GetCount(); i++)
{
  // Do something
}

Pour augmenter les performances, vous pouvez le réorganiser légèrement en:

const int count = this->GetCount();
for (int i = 0; i < count; ++i)
{
  // Do something
}

Notez la suppression de GetCount () de la boucle (car cela sera interrogé dans chaque boucle) et le changement de "i ++" en "++ i".


J'aime mieux le second car il est plus facile à lire mais recalcule-t-il vraiment le this-> GetCount () à chaque fois? J'ai été attrapé par ceci en changeant le ceci et le compte est resté le même, me forçant à faire un do .. pendant ce temps-> GetCount ()
osp70

GetCount () serait appelé à chaque itération dans le premier exemple. Il ne serait appelé qu'une seule fois dans le deuxième exemple. Si vous avez besoin que GetCount () soit appelé à chaque fois, mettez-le dans la boucle, si vous n'essayez pas de le garder à l'extérieur.
Mark Ingram

Oui, je l'ai essayé et vous avez raison, mes excuses.
osp70

Quelle différence cela fait-il d'utiliser ++ i sur i ++?
Rene Saarsoo

Une augmentation mineure de la vitesse lors de l'utilisation de ints, mais l'augmentation pourrait être plus importante si vous incrémentez vos propres classes. Fondamentalement ++ i incrémente la valeur réelle, puis renvoie la valeur réelle. i ++ crée un temp var, incrémente real var, puis retourne temp. Aucune création de var n'est nécessaire avec ++ i.
Mark Ingram

4

En C ++, je préfère utiliser !=, qui est utilisable avec tous les conteneurs STL. Tous les itérateurs de conteneurs STL ne sont pas comparables.


Cela me fait un peu peur juste parce qu'il y a une très légère chance extérieure que quelque chose puisse itérer le compteur sur ma valeur prévue, ce qui en fait alors une boucle infinie. Les chances sont faibles et facilement détectables - mais le < se sent plus en sécurité.
Rob Allen

2
S'il y a un bogue comme celui-là dans votre code, il vaut probablement mieux planter et brûler que de continuer silencieusement :-)

C'est la bonne réponse: cela met moins de demande sur votre itérateur et il est plus susceptible de s'afficher s'il y a une erreur dans votre code. L'argument pour <est à courte vue. Peut-être qu'une boucle infinie serait mauvaise dans les années 70 lorsque vous payiez pour le temps CPU. '! =' est moins susceptible de cacher un bogue.
David Nehme

1
La boucle sur les itérateurs est un cas complètement différent de la boucle avec un compteur. ! = est essentiel pour les itérateurs.
DJClayworth

4

Edsger Dijkstra a écrit un article à ce sujet en 1982 dans lequel il plaide pour une valeur inférieure <= i <supérieure:

Il existe un plus petit nombre naturel. L'exclusion de la borne inférieure - comme en b) et d) - force pour une sous-séquence commençant au plus petit nombre naturel la borne inférieure mentionnée dans le domaine des nombres non naturels. C'est moche, donc pour la borne inférieure, nous préférons le ≤ comme en a) et c). Considérez maintenant les sous-séquences commençant au plus petit nombre naturel: l'inclusion de la borne supérieure forcerait alors cette dernière à ne pas être naturelle au moment où la séquence est réduite au vide. C'est moche, donc pour la borne supérieure, nous préférons <comme en a) et d). Nous concluons que la convention a) doit être préférée.


3

Tout d'abord, n'utilisez pas 6 ou 7.

Mieux vaut utiliser:

int numberOfDays = 7;
for (int day = 0; day < numberOfDays ; day++){

}

Dans ce cas, c'est mieux que d'utiliser

for (int day = 0; day <= numberOfDays  - 1; day++){

}

Encore mieux (Java / C #):

for(int day = 0; day < dayArray.Length; i++){

}

Et encore mieux (C #)

foreach (int day in days){// day : days in Java

}

La boucle inverse est en effet plus rapide mais comme elle est plus difficile à lire (sinon par vous par d'autres programmeurs), il vaut mieux éviter dedans. Surtout en C #, Java ...


2

Je suis d'accord avec la foule qui dit que le 7 a du sens dans ce cas, mais j'ajouterais que dans le cas où le 6 est important, disons que vous voulez préciser que vous n'agissez que sur des objets jusqu'au 6ème index, alors le <= est meilleur car il rend le 6 plus facile à voir.


donc, i <taille par rapport à i <= LAST_FILLED_ARRAY_SLOT
Chris Cudmore

2

Au retour à l'université, je me souviens que ces deux opérations étaient similaires en temps de calcul sur le processeur. Bien sûr, nous parlons au niveau de l'assemblage.

Cependant, si vous parlez de C # ou Java, je ne pense vraiment pas que l'un va augmenter la vitesse par rapport à l'autre, les quelques nanosecondes que vous gagnez ne valent probablement pas la confusion que vous introduisez.

Personnellement, je créerais le code qui a du sens du point de vue de la mise en œuvre commerciale et je m'assurerais qu'il est facile à lire.


2

Cela relève directement de la catégorie «Faire paraître un mauvais code» .

Dans les langages d'indexation de base zéro, tels que Java ou C #, les gens sont habitués aux variations de la index < countcondition. Ainsi, tirer parti de cette convention de facto rendrait les erreurs ponctuelles plus évidentes.

En ce qui concerne les performances: tout bon compilateur valant son encombrement mémoire devrait être rendu comme un non-problème.


2

En passant, en passant par un tableau ou une autre collection dans .Net, je trouve

foreach (string item in myarray)
{
    System.Console.WriteLine(item);
}

pour être plus lisible que la boucle for numérique. Cela suppose bien sûr que le compteur réel Int lui-même n'est pas utilisé dans le code de la boucle. Je ne sais pas s'il y a un changement de performance.


Cela nécessite également que vous ne modifiez pas la taille de la collection pendant la boucle.
Jeff B

Il en irait de même pour (i = 0, i <myarray.count, i ++)
Rob Allen

1

Il existe de nombreuses bonnes raisons d'écrire i <7. Avoir le numéro 7 dans une boucle qui répète 7 fois, c'est bien. La performance est effectivement identique. Presque tout le monde écrit i <7. Si vous écrivez pour plus de lisibilité, utilisez le formulaire que tout le monde reconnaîtra instantanément.


1

J'ai toujours préféré:

for ( int count = 7 ; count > 0 ; -- count )

Quelle est votre justification? Je suis vraiment intéressé.
Chris Cudmore

c'est parfaitement bien pour le bouclage inversé ... si jamais vous avez besoin d'une telle chose
ShoeLace

1
Une raison est au niveau uP comparé à 0 est rapide. Une autre est que cela me lit bien et que le décompte me donne une indication facile du nombre de fois qu'il reste.
kenny

1

Prendre l'habitude d'utiliser <le rendra cohérent pour vous et le lecteur lorsque vous parcourez un tableau. Il sera plus simple pour tout le monde d'avoir une convention standard. Et si vous utilisez un langage avec des tableaux basés sur 0, alors <est la convention.

Cela compte certainement plus que toute différence de performances entre <et <=. Visez d'abord la fonctionnalité et la lisibilité, puis optimisez.

Une autre note est qu'il serait préférable d'avoir l'habitude de faire ++ i plutôt que i ++, car la récupération et l'incrémentation nécessitent un temporaire et l'incrémentation et la récupération ne le font pas. Pour les entiers, votre compilateur optimisera probablement le temporaire, mais si votre type d'itération est plus complexe, il ne pourra peut-être pas le faire.


1

N'utilisez pas de nombres magiques.

Pourquoi est-ce 7? (ou 6 d'ailleurs).

utilisez le symbole correct pour le numéro que vous souhaitez utiliser ...

Dans quel cas je pense qu'il vaut mieux utiliser

for ( int i = 0; i < array.size(); i++ )

1

Les opérateurs '<' et '<=' ont exactement le même coût de performance.

L'opérateur '<' est un standard et plus facile à lire dans une boucle de base zéro.

Utiliser ++ i au lieu de i ++ améliore les performances en C ++, mais pas en C # - Je ne connais pas Java.


1

Comme les gens l'ont observé, il n'y a aucune différence entre les deux alternatives que vous avez mentionnées. Juste pour confirmer cela, j'ai fait une analyse comparative simple en JavaScript.

Vous pouvez voir les résultats ici . Ce qui n'est pas clair, c'est que si j'échange la position des 1er et 2ème tests, les résultats de ces 2 tests s'échangent, c'est clairement un problème de mémoire. Cependant le 3ème test, celui où j'inverse l'ordre de l'itération est nettement plus rapide.


pourquoi commencez-vous par i = 1 dans le second cas?
Eugene Katz

Hrmm, probablement une erreur stupide? Je l'ai fouetté assez rapidement, peut-être 15 minutes.
David Wees

1

Comme tout le monde le dit, il est habituel d'utiliser des itérateurs indexés à 0, même pour des choses en dehors des tableaux. Si tout commence à 0et se termine à n-1, et que les bornes inférieures sont toujours <=et que les bornes supérieures le sont toujours <, il y a beaucoup moins de réflexion que vous devez faire lors de la révision du code.


1

Excellente question. Ma réponse: utilisez le type A ('<')

  • Vous voyez clairement combien d'itérations vous avez (7).
  • La différence entre deux extrémités est la largeur de la plage
  • Moins de caractères le rend plus lisible
  • Vous avez plus souvent le nombre total d'éléments i < strlen(s)plutôt que l' index du dernier élément , l'uniformité est donc importante.

Un autre problème est avec toute cette construction. iapparaît 3 fois , il peut donc être mal saisi. La construction de boucle for indique comment faire au lieu de quoi faire . Je suggère d'adopter ceci:

BOOST_FOREACH(i, IntegerInterval(0,7))

Ceci est plus clair, compile exactement les mêmes instructions asm, etc. Demandez-moi le code d'IntegerInterval si vous le souhaitez.


1

Tant de réponses ... mais je crois avoir quelque chose à ajouter.

Ma préférence est que les nombres littéraux indiquent clairement les valeurs que «i» prendra dans la boucle . Donc, dans le cas d'une itération via un tableau de base zéro:

for (int i = 0; i <= array.Length - 1; ++i)

Et si vous faites simplement une boucle, pas une itération dans un tableau, compter de 1 à 7 est assez intuitif:

for (int i = 1; i <= 7; ++i)

La lisibilité l'emporte sur les performances jusqu'à ce que vous la profiliez, car vous ne savez probablement pas ce que le compilateur ou le runtime va faire avec votre code jusque-là.


1

Vous pouvez également utiliser à la !=place. De cette façon, vous obtiendrez une boucle infinie si vous faites une erreur d'initialisation, ce qui fait que l'erreur est remarquée plus tôt et que tous les problèmes qu'elle cause sont limités à rester coincé dans la boucle (plutôt que d'avoir un problème beaucoup plus tard et de ne pas trouver il).


0

Je pense que les deux sont OK, mais quand vous avez choisi, tenez-vous-en à l'un ou à l'autre. Si vous avez l'habitude d'utiliser <=, essayez de ne pas utiliser <et vice versa.

Je préfère <=, mais dans les situations où vous travaillez avec des index qui commencent à zéro, j'essaierais probablement d'utiliser <. C'est toute préférence personnelle cependant.


0

D'un point de vue strictement logique, vous devez penser que ce < countserait plus efficace que <= countpour la raison exacte qui <=testera également l'égalité.


Je ne pense pas, en assembleur, cela se résume à cmp eax, 7 jl LOOP_START ou cmp eax, 6 jle LOOP_START ont tous deux besoin du même nombre de cycles.
Treb

<= peut être implémenté comme! (>)
JohnMcG

! (>) est toujours deux instructions, mais Treb a raison de dire que JLE et JL utilisent tous les deux le même nombre de cycles d'horloge, donc <et <= prennent le même temps.
Jacob Krall
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.