Si dovrebbe usare <o <= in un ciclo for [chiuso]


124

Se dovessi scorrere 7 volte un ciclo, useresti:

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

o:

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

Vi sono due considerazioni:

  • prestazione
  • leggibilità

Per prestazioni sto assumendo Java o C #. Importa se si utilizza "minore di" o "minore di o uguale a"? Se hai informazioni dettagliate su un'altra lingua, indica quale.

Per leggibilità sto assumendo array basati su 0.

UPD: La mia menzione di array basati su 0 potrebbe avere cose confuse. Non sto parlando di iterare attraverso gli elementi dell'array. Solo un ciclo generale.

Di seguito è riportato un buon punto sull'uso di una costante che spiegherebbe cos'è questo numero magico. Quindi se avessi " int NUMBER_OF_THINGS = 7" allora " i <= NUMBER_OF_THINGS - 1" sembrerebbe strano, no.


direi: se si esegue l'intero array, non sottrarre o aggiungere alcun numero sul lato sinistro.
Letterman,

Risposte:


287

Il primo è più idiomatico . In particolare, indica (in senso 0) il numero di iterazioni. Quando uso qualcosa basato su 1 (es. JDBC, IIRC) potrei essere tentato di usare <=. Così:

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

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

Mi aspetto che la differenza di prestazioni sia insignificantemente piccola nel codice del mondo reale.


30
Sei quasi sicuro che non ci saranno differenze di prestazioni. Molte architetture, come x86, hanno istruzioni "salta su meno o uguale nell'ultimo confronto". Il modo più probabile in cui vedresti una differenza di prestazione sarebbe in una sorta di linguaggio interpretato mal implementato.
Cuneo

3
Considereresti di usare! = Invece? Direi che ciò stabilisce chiaramente come contatore loop e nient'altro.
yungchin,

21
Di solito non lo farei. È troppo poco familiare. Rischia anche di entrare in un ciclo molto, molto lungo se qualcuno accelera accidentalmente i durante il ciclo.
Jon Skeet,

5
La programmazione generica con iteratori STL richiede l'uso di! =. (Doppio incremento accidentale) non è stato un problema per me. Concordo sul fatto che per gli indici <(o> per discendente) sono più chiari e convenzionali.
Jonathan Graehl,

2
Ricordare, se si esegue il loop sulla lunghezza di un array usando <, JIT ottimizza l'accesso all'array (rimuove i controlli associati). Quindi dovrebbe essere più veloce che usando <=. Non l'ho verificato però
configuratore

72

Entrambi questi loop ripetono 7 volte. Direi che quello con un 7 è più leggibile / più chiaro, a meno che tu non abbia una buona ragione per l'altro.


Ricordo quando ho iniziato a studiare Java. Ho odiato il concetto di un indice basato su 0 perché ho sempre usato indici basati su 1. Quindi userei sempre la variante <= 6 (come mostrato nella domanda). A mio svantaggio, perché alla fine mi confonderebbe di più quando il ciclo for è effettivamente uscito. È più semplice usare <
James Haug il

55

Ricordo dai miei giorni quando facevamo l'Assemblea 8086 al college, era più performante fare:

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

poiché c'era un'operazione JNS che significa Salta se nessun segno. L'uso di questo significava che dopo ogni ciclo non vi era alcuna ricerca di memoria per ottenere il valore di confronto e neppure un confronto. Oggigiorno la maggior parte dei compilatori ottimizza l'utilizzo dei registri, quindi la memoria non è più importante, ma si ottiene comunque un confronto non necessario.

A proposito, mettere 7 o 6 nel tuo ciclo sta introducendo un " numero magico ". Per una migliore leggibilità dovresti usare una costante con un Nome rivelatore di intenti. Come questo:

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

EDIT: le persone non ottengono il montaggio, quindi è ovviamente necessario un esempio più completo:

Se lo facciamo per (i = 0; i <= 10; i ++) devi farlo:

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

Se lo facciamo per (int i = 10; i> -1; i--) allora puoi cavartela con questo:

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

Ho appena controllato e il compilatore C ++ di Microsoft non esegue questa ottimizzazione, ma lo fa se lo fai:

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

Quindi la morale è se stai usando Microsoft C ++ †, e crescente o decrescente non fa alcuna differenza, per ottenere un ciclo rapido dovresti usare:

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

piuttosto che uno di questi:

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

Ma ottenere francamente la leggibilità di "for (int i = 0; i <= 10; i ++)" è normalmente molto più importante della mancanza di un comando del processore.

† Altri compilatori possono fare cose diverse.


4
Il caso del "numero magico" illustra bene, perché di solito è meglio usare <di <=.
Rene Saarsoo,

11
Un'altra versione è "for (int i = 10; i--;)". Alcune persone usano "for (int i = 10; i -> 0;)" e fanno finta che la combinazione -> significhi andare a.
Zayenz,

2
+1 per il codice assembly
neuro

1
ma quando arriva il momento di utilizzare effettivamente il contatore di loop, ad esempio per l'indicizzazione dell'array, è necessario fare 7-iciò che sommergerà tutte le ottimizzazioni ottenute dal conteggio all'indietro.
Lie Ryan,

@ Lie, questo vale solo se è necessario elaborare gli articoli in ordine anticipato. Con la maggior parte delle operazioni in questo tipo di loop, è possibile applicarli agli elementi del loop nell'ordine desiderato. Ad esempio, se stai cercando un valore, non importa se inizi alla fine dell'elenco e lavori in alto o all'inizio dell'elenco e lavori in basso (supponendo che non puoi prevedere quale fine dell'elenco è il tuo articolo è probabile che sia e la memorizzazione nella memoria cache non è un problema).
Martin Brown,

27

Uso sempre <array.length perché è più facile da leggere di <= array.length-1.

anche avendo <7 e dato che sai che inizia con un indice 0, dovrebbe essere intuitivo che il numero sia il numero di iterazioni.


Dovresti sempre fare attenzione a controllare il costo delle funzioni Lunghezza quando le usi in un ciclo. Ad esempio, se usi strlen in C / C ++, aumenterai notevolmente il tempo necessario per fare il confronto. Questo perché strlen deve iterare l'intera stringa per trovare la sua risposta che è qualcosa che probabilmente vorresti fare solo una volta piuttosto che per ogni iterazione del tuo ciclo.
Martin Brown,

2
@Martin Brown: in Java (e credo C #), String.length e Array.length è costante perché String è immutabile e Array ha una lunghezza immutabile. E poiché String.length e Array.length è un campo (anziché una chiamata di funzione), puoi essere sicuro che devono essere O (1). Nel caso di C ++, beh, perché diavolo stai usando C-string in primo luogo?
Lie Ryan,

18

Visto da un punto di vista ottimizzante, non importa.

Visto da un punto di vista in stile codice preferisco <. Motivo:

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

è molto più leggibile di

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

anche <ti dà il numero di iterazioni immediatamente.

Un altro voto per <è che potresti prevenire molti errori accidentali off-by-one.


10

@ Chris, la tua affermazione sul fatto che .Lunghezza sia costosa in .NET è in realtà falsa e nel caso di tipi semplici l'esatto contrario.

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

è in realtà più lento di

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

Il successivo è un caso ottimizzato dal runtime. Poiché il runtime può garantire che i sia un indice valido nell'array, non viene eseguito alcun controllo dei limiti. Nel primo, il runtime non può garantire che non sia stato modificato prima del ciclo e forza i controlli dei limiti sull'array per ogni ricerca dell'indice.


In Java .Lunghezza potrebbe essere costosa in alcuni casi. stackoverflow.com/questions/6093537/for-loop-optimization b'coz richiama alla .lenghtOR .size. non sono sicuro ma e non sono fiducioso, ma voglio assicurarmene solo.
Ravi Parekh,

6

Non fa alcuna differenza per quanto riguarda le prestazioni. Pertanto vorrei usare qualunque sia più facile da capire nel contesto del problema che si sta risolvendo.


5

Preferisco:

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

Penso che ciò si traduca più facilmente in "iterare attraverso un ciclo 7 volte".

Non sono sicuro delle implicazioni sulle prestazioni - sospetto che eventuali differenze vengano eliminate.


4

In Java 1.5 puoi semplicemente farlo

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

quindi per il caso dell'array non devi preoccuparti.


4

Non penso che ci sia una differenza di prestazioni. Il secondo modulo è sicuramente più leggibile, tuttavia, non è necessario sottrarlo mentalmente per trovare l'ultimo numero di iterazione.

EDIT: vedo gli altri in disaccordo. Personalmente, mi piace vedere i numeri di indice effettivi nella struttura del ciclo. Forse è perché ricorda più la 0..6sintassi di Perl , a cui so equivale (0,1,2,3,4,5,6). Se vedo un 7, devo controllare l'operatore accanto per vedere che, di fatto, l'indice 7 non viene mai raggiunto.


Dipende se pensi che "l'ultimo numero di iterazioni" sia più importante del "numero di iterazioni". Con un'API basata su 0, differiranno sempre di 1 ...
Jon Skeet,

4

Direi che usa la versione "<7" perché è quello che leggerà la maggior parte delle persone, quindi se le persone scremano leggendo il tuo codice, potrebbero interpretarlo in modo errato.

Non mi preoccuperei se "<" è più veloce di "<=", basta andare per leggibilità.

Se vuoi aumentare la velocità, considera quanto segue:

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

Per aumentare le prestazioni è possibile riorganizzarle leggermente in:

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

Notare la rimozione di GetCount () dal loop (perché verrà interrogato in ogni loop) e la modifica di "i ++" in "++ i".


Mi piace di più il secondo perché è più facile da leggere ma ricalcola davvero questo-> GetCount () ogni volta? Sono stato colto da questo quando ho cambiato questo e il conto è rimasto lo stesso costringendomi a fare un do..while this-> GetCount ()
osp70

GetCount () verrebbe chiamato ogni iterazione nel primo esempio. Sarebbe chiamato solo una volta nel secondo esempio. Se hai bisogno che GetCount () sia chiamato ogni volta, fallo nel loop, se non provi a tenerlo fuori.
Mark Ingram,

Sì, l'ho provato e hai ragione, le mie scuse.
osp70

Che differenza fa usare ++ i su i ++?
Rene Saarsoo,

Un aumento di velocità minore quando si usano gli ints, ma l'aumento potrebbe essere maggiore se si stanno incrementando le proprie classi. Fondamentalmente ++ i incrementa il valore effettivo, quindi restituisce il valore effettivo. i ++ crea un var temp, incrementa il var reale, quindi restituisce temp. Non è necessaria la creazione di var con ++ i.
Mark Ingram,

4

In C ++, preferisco usare !=, che è utilizzabile con tutti i contenitori STL. Non tutti gli iteratori di contenitori STL sono meno che comparabili.


Questo mi spaventa un po 'solo perché c'è una leggera possibilità esterna che qualcosa possa ripetere il contatore sul mio valore previsto, il che rende questo un ciclo infinito. Le possibilità sono remote e facilmente rilevabili, ma < sembra più sicuro.
Rob Allen,

2
Se nel tuo codice è presente un bug del genere, probabilmente è meglio arrestarsi e masterizzarlo piuttosto che continuare in silenzio :-)

Questa è la risposta giusta: mette meno richiesta sul tuo iteratore ed è più probabile che si presenti se c'è un errore nel tuo codice. L'argomento per <è miope. Forse un loop infinito sarebbe andato male negli anni '70 quando pagavi per il tempo della CPU. '! =' ha meno probabilità di nascondere un bug.
David Nehme,

1
Il looping su iteratori è un caso completamente diverso dal looping con un contatore. ! = è essenziale per gli iteratori.
DJClayworth,

4

Edsger Dijkstra ha scritto un articolo su questo nel 1982 in cui sostiene il <= i <superiore:

C'è un numero naturale più piccolo. Esclusione del limite inferiore - come in b) ed) - forza per una sottosequenza a partire dal numero naturale più piccolo il limite inferiore come menzionato nel regno dei numeri innaturali. È brutto, quindi per il limite inferiore preferiamo ≤ come in a) ec). Considera ora le sottosequenze che iniziano con il numero naturale più piccolo: l'inclusione del limite superiore costringerebbe quest'ultimo a essere innaturale quando la sequenza si è ridotta a quella vuota. È brutto, quindi per il limite superiore preferiamo <come in a) ed). Concludiamo che la convenzione a) deve essere preferita.


3

Innanzitutto, non usare 6 o 7.

Meglio usare:

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

}

In questo caso è meglio dell'uso

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

}

Ancora meglio (Java / C #):

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

}

E ancora meglio (C #)

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

}

Il ciclo inverso è davvero più veloce ma poiché è più difficile da leggere (se non da te da altri programmatori), è meglio evitarlo. Soprattutto in C #, Java ...


2

Concordo con la folla dicendo che il 7 ha senso in questo caso, ma aggiungerei che nel caso in cui il 6 sia importante, dire che vuoi chiarire che stai agendo solo su oggetti fino al sesto indice, quindi il <= è migliore poiché rende i 6 più facili da vedere.


quindi, i <dimensione rispetto a i <= LAST_FILLED_ARRAY_SLOT
Chris Cudmore,

2

Già al college, ricordo qualcosa di simile a queste due operazioni in termini di tempo di calcolo sulla CPU. Certo, stiamo parlando a livello di assemblea.

Tuttavia, se stai parlando in C # o Java, non credo davvero che uno sarà un aumento di velocità rispetto all'altro, I pochi nanosecondi che guadagni molto probabilmente non meritano alcuna confusione che introduci.

Personalmente, vorrei creare il codice che ha senso dal punto di vista dell'implementazione aziendale e assicurarmi che sia di facile lettura.


2

Questo rientra direttamente nella categoria "Far sembrare sbagliato il codice sbagliato" .

Nei linguaggi di indicizzazione a base zero, come Java o C #, le persone sono abituate alle variazioni della index < countcondizione. Pertanto, sfruttare questa convenzione defacto renderebbe più ovvi gli errori off-by-one.

Per quanto riguarda le prestazioni: qualsiasi buon compilatore degno della sua impronta di memoria dovrebbe essere reso non problematico.


2

A parte questo, trovo in loop attraverso un array o altra raccolta in .Net

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

per essere più leggibile rispetto al ciclo numerico for. Questo ovviamente presuppone che il vero contatore Int stesso non sia usato nel codice del ciclo. Non so se c'è un cambiamento nelle prestazioni.


Ciò richiede anche che non si modifichino le dimensioni della raccolta durante il ciclo.
Jeff B,

Così sarebbe per (i = 0, i <myarray.count, i ++)
Rob Allen,

1

Ci sono molte buone ragioni per scrivere i <7. Avere il numero 7 in un ciclo che scorre 7 volte è buono. Le prestazioni sono effettivamente identiche. Quasi tutti scrivono i <7. Se stai scrivendo per leggibilità, usa il modulo che tutti riconosceranno all'istante.


1

Ho sempre preferito:

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

Qual è la tua logica? Sono sinceramente interessato.
Chris Cudmore,

va benissimo per il reverse looping ... se mai hai bisogno di una cosa del genere
ShoeLace

1
Un motivo è a livello di uP rispetto a 0 è veloce. Un altro è che mi legge bene e il conteggio mi dà una facile indicazione di quante altre volte sono rimaste.
Kenny,

1

Prendere l'abitudine di usare <lo renderà coerente sia per te che per il lettore quando stai iterando attraverso un array. Sarà più semplice per tutti avere una convenzione standard. E se stai usando una lingua con array basati su 0, allora <è la convenzione.

Questo quasi sicuramente conta più di qualsiasi differenza di prestazioni tra <e <=. Cerca prima funzionalità e leggibilità, quindi ottimizza.

Un'altra nota è che sarebbe meglio avere l'abitudine di fare ++ i piuttosto che i ++, poiché il recupero e l'incremento richiedono un temporaneo e l'incremento e il recupero no. Per i numeri interi, il compilatore probabilmente ottimizzerà il temporaneo, ma se il tipo di iterazione è più complesso, potrebbe non essere possibile.


1

Non usare numeri magici.

Perché è 7? (o 6 del resto).

usa il simbolo corretto per il numero che vuoi usare ...

Nel qual caso penso sia meglio usarlo

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

1

Gli operatori '<' e '<=' hanno esattamente lo stesso costo prestazionale.

L'operatore '<' è uno standard e più facile da leggere in un ciclo a base zero.

L'uso di ++ i anziché i ++ migliora le prestazioni in C ++, ma non in C #: non conosco Java.


1

Come hanno osservato le persone, non vi è alcuna differenza in nessuna delle due alternative menzionate. Giusto per confermare, ho fatto alcuni semplici benchmarking in JavaScript.

Puoi vedere i risultati qui . Ciò che non è chiaro da ciò è che se cambio la posizione del 1 ° e del 2 ° test, i risultati per questi 2 test si scambiano, questo è chiaramente un problema di memoria. Tuttavia, il terzo test, quello in cui invertisco l'ordine dell'iterazione, è chiaramente più veloce.


perché inizi con i = 1 nel secondo caso?
Eugene Katz,

Hrmm, probabilmente un errore sciocco? L'ho montato abbastanza velocemente, forse 15 minuti.
David Wees,

1

Come dicono tutti, è consuetudine usare iteratori con indice 0 anche per cose al di fuori delle matrici. Se tutto inizia 0e finisce a n-1, e i limiti inferiori sono sempre <=e i limiti superiori sono sempre <, c'è molto meno da pensare quando si deve rivedere il codice.


1

Ottima domanda La mia risposta: usa il tipo A ('<')

  • Vedi chiaramente quante iterazioni hai (7).
  • La differenza tra due endpoint è la larghezza dell'intervallo
  • Meno personaggi lo rendono più leggibile
  • Più spesso hai il numero totale di elementi i < strlen(s)anziché l' indice dell'ultimo elemento, quindi l'uniformità è importante.

Un altro problema riguarda l'intero costrutto. iappare 3 volte in esso, quindi può essere errato. Il costrutto for-loop dice come fare invece di cosa fare . Suggerisco di adottare questo:

BOOST_FOREACH(i, IntegerInterval(0,7))

Questo è più chiaro, compila per eseguire esattamente le stesse istruzioni asm, ecc. Chiedimi il codice di IntegerInterval se vuoi.


1

Tante risposte ... ma credo di avere qualcosa da aggiungere.

La mia preferenza è che i numeri letterali mostrino chiaramente quali valori "i" prenderà nel ciclo . Quindi, nel caso di iterare attraverso un array a base zero:

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

E se stai solo eseguendo il ciclo, non iterando attraverso un array, contare da 1 a 7 è piuttosto intuitivo:

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

La leggibilità supera le prestazioni fino a quando non le si profila, poiché probabilmente non si sa che cosa farà il compilatore o il runtime fino a quel momento.


1

Puoi anche usare !=invece. In questo modo, otterrai un ciclo infinito se commetti un errore nell'inizializzazione, facendo in modo che l'errore venga notato in precedenza e tutti i problemi che causa vengono limitati a rimanere bloccati nel ciclo (piuttosto che avere un problema molto più tardi e non trovare esso).


0

Penso che sia OK, ma quando hai scelto, attenersi all'uno o all'altro. Se sei abituato a usare <=, prova a non usare <e viceversa.

Preferisco <=, ma nelle situazioni in cui stai lavorando con indici che iniziano da zero, probabilmente proverei ad usare <. Sono tutte preferenze personali però.


0

Dal punto di vista logico, devi pensare che < countsarebbe più efficiente che <= countper la ragione esatta che <=metterà alla prova anche l'uguaglianza.


Non credo, nell'assemblatore si riduce a cmp eax, 7 jl LOOP_START o cmp eax, 6 jle LOOP_START necessitano entrambi della stessa quantità di cicli.
Treb,

<= può essere implementato come! (>)
JohnMcG

! (>) sono ancora due istruzioni, ma Treb ha ragione che JLE e JL usano entrambi lo stesso numero di cicli di clock, quindi <e <= impiegano lo stesso tempo.
Jacob Krall,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.