Deve-se usar <ou <= em um loop for [fechado]

124

Se você tivesse que repetir um loop 7 vezes, usaria:

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

ou:

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

Há duas considerações:

  • desempenho
  • legibilidade

Para desempenho, estou assumindo Java ou C #. Importa se "menor que" ou "menor ou igual a" é usado? Se você tiver informações sobre um idioma diferente, indique qual.

Para facilitar a leitura, estou assumindo matrizes baseadas em 0.

UPD: Minha menção a matrizes baseadas em 0 pode ter confundido as coisas. Eu não estou falando sobre a iteração através de elementos da matriz. Apenas um loop geral.

Há um bom ponto abaixo sobre o uso de uma constante na qual explicaria qual é esse número mágico. Então, se eu tivesse " int NUMBER_OF_THINGS = 7" então " i <= NUMBER_OF_THINGS - 1" pareceria estranho, não seria?

Eugene Katz
fonte
Eu diria: se você percorrer toda a matriz, nunca subtraia ou adicione qualquer número ao lado esquerdo.
Letterman

Respostas:

287

O primeiro é mais idiomático . Em particular, indica (no sentido 0) o número de iterações. Ao usar algo baseado em 1 (por exemplo, JDBC, IIRC), fico tentado a usar <=. Assim:

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

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

Eu esperaria que a diferença de desempenho fosse insignificantemente pequena no código do mundo real.

Jon Skeet
fonte
30
Você está quase garantido que não haverá diferença de desempenho. Muitas arquiteturas, como o x86, têm instruções de "pular em menor ou igual na última comparação". A maneira mais provável de ver uma diferença de desempenho seria em algum tipo de linguagem interpretada que foi mal implementada.
Wedge
3
Você consideraria usar! = Em vez disso? Eu diria que isso claramente estabelece i como um contador de loop e nada mais.
22710 yungchin
21
Eu normalmente não. É simplesmente muito estranho. Também corre o risco de entrar em um loop muito, muito longo se alguém aumentar acidentalmente i durante o loop.
9139 Jon Skeet
5
A programação genérica com iteradores STL exige o uso de! =. (Incremento duplo acidental) não foi um problema para mim. Concordo que os índices <(ou> descendentes) são mais claros e convencionais.
Jonathan graehl
2
Lembre-se, se você fizer um loop no comprimento de uma matriz usando <, o JIT otimiza o acesso à matriz (remove as verificações vinculadas). Portanto, deve ser mais rápido que usar <=. Eu ainda não verifiquei
configurador
72

Os dois loops repetem 7 vezes. Eu diria que um com 7 é mais legível / claro, a menos que você tenha uma boa razão para o outro.

Steve Losh
fonte
Lembro-me de quando comecei a aprender Java. Eu odiava o conceito de um índice baseado em 0 porque eu sempre usei índices baseados em 1. Então, eu sempre usaria a variante <= 6 (como mostrado na pergunta). Para meu próprio prejuízo, porque isso me confundiria mais quando o loop for realmente saísse. É mais simples usar apenas o <
James Haug
55

Lembro-me dos meus dias em que fizemos a Assembléia do 8086 na faculdade, que era mais eficiente:

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

pois houve uma operação JNS que significa Jump if No Sign. Usar isso significava que não havia pesquisa de memória após cada ciclo para obter o valor de comparação e também nenhuma comparação. Atualmente, a maioria dos compiladores otimiza o uso do registro para que a memória não seja mais importante, mas você ainda recebe uma comparação desnecessária.

A propósito, colocar 7 ou 6 no seu loop é introduzir um " número mágico ". Para melhor legibilidade, você deve usar uma constante com um Nome revelador de intenção. Como isso:

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

EDIT: As pessoas não estão recebendo a coisa de montagem, portanto é obviamente necessário um exemplo mais completo:

Se fizermos por (i = 0; i <= 10; i ++), você precisará fazer o seguinte:

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

Se fizermos por (int i = 10; i> -1; i--), então você pode se safar com isso:

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

Acabei de verificar e o compilador C ++ da Microsoft não faz essa otimização, mas o faz se você:

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

Portanto, a moral é que se você estiver usando o Microsoft C ++ †, e subir ou descer não faz diferença, para obter um loop rápido, você deve usar:

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

em vez de qualquer um destes:

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

Mas, francamente, obter a legibilidade de "for (int i = 0; i <= 10; i ++)" é normalmente muito mais importante do que perder um comando do processador.

† Outros compiladores podem fazer coisas diferentes.

Martin Brown
fonte
4
O caso "número mágico" ilustra bem o motivo pelo qual geralmente é melhor usar <do que <=.
Rene Saarsoo 9/10/08
11
Outra versão é "for (int i = 10; i--;)". Algumas pessoas usam "for (int i = 10; i -> 0;)" e fingem que a combinação -> significa vai para.
Zayenz
2
+1 para o código de montagem
neuro
1
mas quando chegar a hora de realmente usar o contador de loop, por exemplo, para indexação de array, você precisará fazer o 7-ique será necessário para eliminar todas as otimizações que você obtém da contagem inversa.
Lie Ryan
@ Mentira, isso só se aplica se você precisar processar os itens em ordem direta. Com a maioria das operações nesse tipo de loop, você pode aplicá-los aos itens do loop na ordem que desejar. Por exemplo, se você estiver procurando por um valor, não importa se você inicia no final da lista e trabalha para cima ou no início da lista e trabalha para baixo (supondo que você não possa prever qual final da lista seu item é provavelmente e cache de memória não é um problema).
Martin Brown
27

Eu sempre uso <array.length porque é mais fácil ler do que <= array.length-1.

também tendo <7 e considerando que está começando com um índice 0, deve ser intuitivo que o número seja o número de iterações.

Omar Kooheji
fonte
Você deve sempre ter cuidado ao verificar o custo das funções Comprimento ao usá-las em um loop. Por exemplo, se você usar strlen no C / C ++, aumentará bastante o tempo necessário para fazer a comparação. Isso ocorre porque o strlen precisa iterar toda a string para encontrar sua resposta, algo que você provavelmente só quer fazer uma vez e não para cada iteração do seu loop.
Martin Brown
2
@ Martin Brown: em Java (e acredito que C #), String.length e Array.length é constante porque String é imutável e Array tem um comprimento imutável. E como String.length e Array.length é um campo (em vez de uma chamada de função), você pode ter certeza de que eles devem ser O (1). No caso de C ++, bem, por que diabos você está usando C-string em primeiro lugar?
Lie Ryan
18

Visto de um ponto de vista otimizador, não importa.

Visto do ponto de vista do estilo de código, eu prefiro <. Razão:

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

é muito mais legível do que

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

também <fornece o número de iterações imediatamente.

Outro voto para <é que você pode evitar muitos erros acidentais de um por um.

erlando
fonte
10

@ Chris, Sua afirmação sobre o .Length ser caro no .NET é realmente falsa e, no caso de tipos simples, é exatamente o oposto.

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

é realmente mais lento que

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

O posterior é um caso otimizado pelo tempo de execução. Como o tempo de execução pode garantir, i é um índice válido na matriz, nenhuma verificação de limites é feita. No primeiro, o tempo de execução não pode garantir que eu não tenha sido modificado antes do loop e força verificações de limites na matriz para todas as pesquisas de índice.

Jeff Mc
fonte
Em Java .Length pode ser caro em alguns casos. stackoverflow.com/questions/6093537/for-loop-optimization b'coz que chama para .lenghtOR .size. Não tenho certeza, mas não estou confiante, mas quero ter certeza.
Ravi Parekh
6

Não faz diferença efetiva quando se trata de desempenho. Portanto, eu usaria o que for mais fácil de entender no contexto do problema que você está resolvendo.

Phil Wright
fonte
5

Eu prefiro:

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

Eu acho que isso se traduz mais prontamente em "iterando através de um loop 7 vezes".

Não tenho certeza das implicações de desempenho - suspeito que quaisquer diferenças sejam compiladas.

Dominic Rodger
fonte
4

No Java 1.5, você pode simplesmente fazer

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

portanto, para o caso de matriz, você não precisa se preocupar.

JesperE
fonte
4

Não acho que exista diferença de desempenho. A segunda forma é definitivamente mais legível, porém, você não precisa subtrair mentalmente uma para encontrar o último número da iteração.

Edição: Eu vejo outros discordam. Para mim, pessoalmente, gosto de ver os números de índice reais na estrutura do loop. Talvez seja porque é mais uma reminiscência da 0..6sintaxe de Perl , que eu sei que é equivalente (0,1,2,3,4,5,6). Se eu vir um 7, tenho que verificar o operador ao lado para ver se, de fato, o índice 7 nunca é atingido.

Adam Bellaire
fonte
Depende se você acha que o "número da última iteração" é mais importante que o "número de iterações". Com uma API baseada em 0, eles sempre serão diferentes por 1 ...
Jon Skeet
4

Eu diria que use a versão "<7" porque é isso que a maioria das pessoas lerá - portanto, se as pessoas estiverem lendo o seu código, elas poderão interpretá-lo incorretamente.

Eu não me preocuparia se "<" é mais rápido que "<=", basta procurar legibilidade.

Se você deseja aumentar a velocidade, considere o seguinte:

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

Para aumentar o desempenho, você pode reorganizá-lo levemente para:

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

Observe a remoção de GetCount () do loop (porque isso será consultado em cada loop) e a alteração de "i ++" para "++ i".

Mark Ingram
fonte
Eu gosto mais do segundo porque é mais fácil de ler, mas ele realmente recalcula o this-> GetCount () a cada vez? Eu fui pego por isso quando mudar o presente ea contagem remaind o mesmo forçando-me a fazer uma do..while this-> GetCount ()
osp70
GetCount () seria chamado a cada iteração no primeiro exemplo. Só seria chamado uma vez no segundo exemplo. Se você precisar que GetCount () seja chamado toda vez, coloque-o em loop, se não tentar mantê-lo fora.
Mark Ingram
Sim, eu tentei e você está certo, minhas desculpas.
Osp70
Que diferença faz para usar ++ i sobre i ++?
Rene Saarsoo 9/10/08
Um aumento de velocidade menor ao usar ints, mas o aumento pode ser maior se você estiver incrementando suas próprias classes. Basicamente, ++ i incrementa o valor real e depois retorna o valor real. O i ++ cria uma var temporária, incrementa a var real e retorna temp. Nenhuma criação de var é necessária com o ++ i.
Mark Ingram
4

Em C ++, prefiro usar !=, que é utilizável com todos os contêineres STL. Nem todos os iteradores de contêineres STL são menos que comparáveis.

madmoose
fonte
Isso me assusta um pouco, apenas porque há uma chance externa muito pequena de que algo possa iterar o contador sobre meu valor pretendido, o que torna esse loop infinito. As chances são remotas e facilmente detectadas - mas a < parece mais segura.
Rob Allen
2
Se há um erro como aquele em seu código, provavelmente é melhor para destruir e queimar do que silenciosamente continuar :-)
Esta é a resposta certa: coloca menos demanda no seu iterador e é mais provável que apareça se houver um erro no seu código. O argumento para <é míope. Talvez um loop infinito fosse ruim nos anos 70 quando você estava pagando pelo tempo da CPU. '! =' tem menos probabilidade de ocultar um erro.
David Nehme 8/10/08
1
Fazer loop sobre iteradores é um caso totalmente diferente de fazer loop com um contador. ! = é essencial para iteradores.
DJClayworth 8/08
4

Edsger Dijkstra escreveu um artigo sobre isso em 1982, onde ele defende <<i i upper:

Há um menor número natural. A exclusão do limite inferior - como em b) e d) - força para uma subsequência começando no menor número natural, o limite inferior, conforme mencionado no domínio dos números não naturais. Isso é feio; portanto, para o limite inferior, preferimos o ≤ como em a) ec). Considere agora as subsequências que começam no menor número natural: a inclusão do limite superior forçaria o último a não ser natural no momento em que a sequência encolheu para o vazio. Isso é feio, então para o limite superior preferimos <como em a) ed). Concluímos que a convenção a) é a preferida.

Martijn
fonte
3

Primeiro, não use 6 ou 7.

Melhor usar:

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

}

Nesse caso, é melhor do que usar

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

}

Ainda melhor (Java / C #):

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

}

E ainda melhor (C #)

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

}

O loop reverso é realmente mais rápido, mas, como é mais difícil de ler (se não for feito por outros programadores), é melhor evitar isso. Especialmente em C #, Java ...

Carra
fonte
2

Eu concordo com a multidão dizendo que o 7 faz sentido nesse caso, mas eu acrescentaria que no caso em que o 6 é importante, digamos que você queira deixar claro que está agindo apenas em objetos até o sexto índice, depois o <= é melhor, pois facilita a visualização dos 6.

abordagem
fonte
Então, eu <tamanho em comparação com i <= LAST_FILLED_ARRAY_SLOT
Chris Cudmore
2

Lá na faculdade, lembro-me de algo sobre essas duas operações serem semelhantes em tempo de computação na CPU. Claro, estamos falando no nível da montagem.

No entanto, se você estiver falando de C # ou Java, eu realmente não acho que um será um aumento de velocidade em relação ao outro. Os poucos nanossegundos que você ganha provavelmente não valem nenhuma confusão que você apresentar.

Pessoalmente, eu criaria o código que faz sentido do ponto de vista da implementação de negócios e garantiria uma leitura fácil.

casademora
fonte
2

Isso se enquadra diretamente na categoria "Fazendo o código errado parecer errado" .

Em linguagens de indexação baseadas em zero, como Java ou C #, as pessoas estão acostumadas a variações na index < countcondição. Assim, alavancar essa convenção padrão tornaria os erros off-a-one mais óbvios.

Em relação ao desempenho: qualquer bom compilador que valha o espaço ocupado pela memória deve renderizar, como um não problema.

Ryan Delucchi
fonte
2

Como um pequeno aparte, ao percorrer uma matriz ou outra coleção em .Net, acho

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

para ser mais legível do que o loop for numérico. Obviamente, isso pressupõe que o próprio contador Int não seja usado no código do loop. Não sei se há uma alteração no desempenho.

Rob Allen
fonte
Isso também requer que você não modifique o tamanho da coleção durante o loop.
Jeff B
Então, seria for (i = 0, i <myarray.count, i ++)
Rob Allen
1

Existem muitas boas razões para escrever i <7. Ter o número 7 em um loop que itera 7 vezes é bom. O desempenho é efetivamente idêntico. Quase todo mundo escreve i <7. Se você está escrevendo para facilitar a leitura, use o formulário que todos reconhecerão instantaneamente.

DJClayworth
fonte
1

Eu sempre preferi:

for ( int count = 7 ; count > 0 ; -- count )
kenny
fonte
Qual é a sua lógica? Estou realmente interessado.
Chris Cudmore
isso é perfeitamente bem para looping reversa .. Se você precisar de tal coisa um
cadarço
1
Uma razão é, no nível uP, comparar com 0 é rápido. Outra é que me lê bem e a contagem me dá uma indicação fácil de quantas vezes restam.
Kenny
1

Criar o hábito de usar <o tornará consistente para você e o leitor quando estiver iterando através de uma matriz. Será mais simples para todos ter uma convenção padrão. E se você estiver usando um idioma com matrizes baseadas em 0, <é a convenção.

Isso quase certamente importa mais do que qualquer diferença de desempenho entre <e <=. Procure primeiro a funcionalidade e a legibilidade e depois otimize.

Outra observação é que seria melhor ter o hábito de fazer ++ i em vez de i ++, pois a busca e o incremento requerem um temporário e o incremento e a busca não. Para números inteiros, seu compilador provavelmente otimizará a ausência temporária, mas se seu tipo de iteração for mais complexo, talvez não seja possível.

JohnMcG
fonte
1

Não use números mágicos.

Por que é 7? (ou 6 para esse assunto).

use o símbolo correto para o número que deseja usar ...

Nesse caso, acho melhor usar

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

Os operadores '<' e '<=' são exatamente o mesmo custo de desempenho.

O operador '<' é um padrão e mais fácil de ler em um loop baseado em zero.

Usar ++ i em vez de i ++ melhora o desempenho em C ++, mas não em C # - não sei sobre Java.

Jeff B
fonte
1

Como as pessoas observaram, não há diferença em nenhuma das duas alternativas mencionadas. Apenas para confirmar isso, fiz alguns testes comparativos simples em JavaScript.

Você pode ver os resultados aqui . O que não está claro disso é que, se eu trocar a posição dos 1º e 2º testes, os resultados desses 2 testes forem trocados, isso é claramente um problema de memória. No entanto, o terceiro teste, em que eu inverto a ordem da iteração, é claramente mais rápido.

David Wees
fonte
por que você começa com i = 1 no segundo caso?
Eugene Katz
Hrmm, provavelmente um erro bobo? Eu preparei isso rapidamente, talvez 15 minutos.
10138 David Wees
1

Como todo mundo diz, é habitual usar iteradores indexados 0, mesmo para coisas fora das matrizes. Se tudo começa 0e termina em n-1, e os limites inferiores são sempre <=e os limites superiores são sempre <, há muito menos pensamento que você deve fazer ao revisar o código.

efémero
fonte
1

Ótima pergunta. Minha resposta: use o tipo A ('<')

  • Você vê claramente quantas iterações você possui (7).
  • A diferença entre dois pontos de extremidade é a largura do intervalo
  • Menos caracteres tornam mais legível
  • Você tem mais frequentemente o número total de elementos i < strlen(s)do que o índice do último elemento, portanto a uniformidade é importante.

Outro problema é com toda essa construção. iaparece 3 vezes nele, para que possa ser digitado incorretamente. A construção do loop for diz como fazer em vez do que fazer . Sugiro adotar isso:

BOOST_FOREACH(i, IntegerInterval(0,7))

Isso é mais claro, compila exatamente as mesmas instruções ASM, etc. Solicite o código de IntegerInterval, se desejar.

Pavel Radzivilovsky
fonte
1

Tantas respostas ... mas acredito que tenho algo a acrescentar.

Minha preferência é que os números literais mostrem claramente quais valores "i" receberão no loop . Portanto, no caso de iterar por uma matriz baseada em zero:

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

E se você está repetindo, não repetindo uma matriz, contar de 1 a 7 é bastante intuitivo:

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

A legibilidade supera o desempenho até o seu perfil, pois provavelmente você não sabe o que o compilador ou o tempo de execução fará com o seu código até então.

Nick Westgate
fonte
1

Você também pode usar !=. Dessa forma, você obterá um loop infinito se cometer um erro na inicialização, fazendo com que o erro seja percebido mais cedo e quaisquer problemas que causem sejam limitados a ficarem presos no loop (em vez de ter um problema muito mais tarde e não encontrar isto).

Brian
fonte
0

Acho que ambos estão bem, mas quando você escolher, atenha-se a um ou ao outro. Se você está acostumado a usar <=, tente não usar <e vice-versa.

Eu prefiro <=, mas em situações em que você está trabalhando com índices que começam em zero, provavelmente tentaria usar <. É tudo preferência pessoal embora.

seanyboy
fonte
0

Estritamente do ponto de vista lógico, você deve pensar que < countseria mais eficiente do que <= countpelo motivo exato que <=também testará a igualdade.

Henry B
fonte
Acho que não, no assembler tudo se resume a cmp eax, 7 jl LOOP_START ou cmp eax, 6 jle LOOP_START, ambos precisam da mesma quantidade de ciclos.
Treb
<= Pode ser implementado como! (>)
JohnMcG
! (>) ainda tem duas instruções, mas o Treb está correto, pois JLE e JL usam o mesmo número de ciclos de clock, portanto, <e <= levam a mesma quantidade de tempo.
26630 Jacob Krall