'<' versus '! =' como condição em um loop 'for'?

31

Digamos que você tenha o seguinte forloop *:

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

que também poderia ser escrito como:

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

Os resultados finais são os mesmos, então existem argumentos reais para usar um sobre o outro? Pessoalmente, eu uso o primeiro caso, ipor algum motivo, dê errado e pule o valor 10.

* Desculpe o uso de números mágicos, mas é apenas um exemplo.

gablin
fonte
18
A solução superior a qualquer uma delas é usar o operador seta:int i = 10; while(i --> 0) { /* stuff */ }
corsiKa
@glowcoder o operador seta é o meu favorito
Carson Myers
3
@glowcoder, bom, mas atravessa a parte de trás. Você pode nem sempre querer isso.
2
@SuperElectric, ele analisa comowhile ((i--) > 0)
Kristopher Johnson
17
Há uma história (provavelmente apócrifa) sobre um acidente industrial causado por um teste de loop while para uma entrada de sensor sendo! = MAX_TEMP. Infelizmente, um dia a entrada do sensor passou de menos de MAX_TEMP para maior que MAX_TEMP sem passar por MAX_TEMP. O processo superaqueceu sem ser detectado e ocorreu um incêndio. Às vezes, há uma diferença entre! = E <.
Charles E. Grant

Respostas:

59

A razão para escolher um ou outro é por causa da intenção e, como resultado, aumenta a legibilidade .

Intenção: o loop deve ser executado contanto que iseja menor que 10, não contanto que inão seja igual a 10. Embora o último possa ser o mesmo nesse caso específico, não é o que você quer dizer, portanto não deve ser escrito assim.

Legibilidade: resultado de anotar o que você quer dizer é que também é mais fácil de entender. Por exemplo, se você usar i != 10, alguém lendo o código pode se perguntar se, dentro do loop, há alguma maneira de ise tornar maior que 10 e que o loop deve continuar (btw: é péssimo estilo mexer com o iterador em outro lugar que não na cabeça de a fordeclaração, mas isso não significa que as pessoas não o façam e, como resultado, os mantenedores esperam).

Deckard
fonte
3
Não, o loop não deve ser executado desde que iseja "menor que 10", deve ser executado desde que o limite superior (nesse caso) não seja atingido. Ao usar a lógica do intervalo de C ++ / intervalo do iterador, é muito mais lógico expressar isso por meio !=de <.
Konrad Rudolph
14
@ Konrad Eu não discordo disso. O ponto principal não é ser dogmático, mas escolher o construto que melhor expressa a intenção da condição. E assim, se você optar por fazer um loop através de algo começando em 0 e subindo, <realmente expressará isso com precisão.
Deckard
6
@ Konrad, você está perdendo o objetivo. O operador de incremento nesse loop torna óbvio que a condição do loop é um limite superior, não uma comparação de identidade. Se você estivesse diminuindo, seria um limite inferior. Se você estiver iterando sobre uma coleção não ordenada, a identidade poderá ser a condição certa.
Alex Feinman
3
@ Alex, o incremento não era o meu ponto. Um intervalo nem necessariamente tem um pedido. Apenas itera ... bem, algo até chegar ao fim. Por exemplo, elementos em um conjunto de hash. No C ++, isso é feito usando iteradores e um forloop. Usar <aqui seria bobagem. Além disso, Deckard parece reconhecer isso. Eu concordo muito com o comentário dele em resposta ao meu.
Konrad Rudolph
1
Observe que, se você usa um buffer rotativo com ponteiros de perseguição, DEVE usar!=
SF.
48

O <padrão é geralmente utilizável, mesmo que o incremento não seja exatamente 1.

Isso permite uma única maneira comum de fazer loops, independentemente de como isso é realmente feito.


fonte
6
Também permite imodificar o interior do loop de forma totalmente segura, se necessário.
Matthew Scharley
1
ou se 'i' for modificado de maneira totalmente insegura ... Outra equipe teve um problema estranho no servidor. Ele continuava relatando 100% de uso da CPU e deve haver um problema com o servidor ou o sistema de monitoramento, certo? Não, encontrei uma condição de loop escrita por um 'programador sênior experiente' com o mesmo problema que estamos falando.
JQA
12
Exceto que nem todos os C ++ para loops podem usar <, como iteradores em uma sequência, e, portanto, !=seria uma maneira comum de executar loops em C ++.
precisa
@SnOrfus: Não estou analisando bem esse comentário. Em geral, você não obterá exceções confiáveis ​​para incrementar muito um iterador (embora existam situações mais específicas em que você o fará).
David Thornley
Bem, sigo o <padrão porque requer um pressionamento de tecla em vez de dois com o >padrão e quatro com !=. É realmente uma questão de eficiência semelhante ao TOC.
Spoike
21

Em C ++, a recomendação de Scott Myers em C ++ mais eficaz (item 6) é sempre usar o segundo, a menos que você tenha um motivo para não fazê-lo, porque isso significa que você tem a mesma sintaxe para índices iterador e inteiro, para que você possa alternar perfeitamente entre intiterador e sem nenhuma alteração na sintaxe.

Em outras línguas, isso não se aplica, então acho que <é provavelmente preferível por causa do argumento de Thorbjørn Ravn Andersen.

By the way, no outro dia eu estava discutindo isso com outro desenvolvedor e ele disse que a razão para preferir <mais !=é porque ipoder incrementar acidentalmente por mais de um, e que pode causar a condição de pausa para não ser cumprida; isso é IMO um monte de bobagens.


fonte
17
"carga de bobagem" ... até o dia em que você acidentalmente tiver um i ++ extra no corpo do loop.
2
+1, especialmente para "carga de bobagem", porque é. Se o corpo do loop acidentalmente incrementar o contador, você terá problemas muito maiores.
Konrad Rudolph
12
@B Tyler, somos apenas humanos, e erros maiores já aconteceram antes. Considere <-se uma programação mais defensiva do que !=então ...
2
@ Thorbjørn Ravn Andersen - não estou dizendo que não concordo com você, sim; <é mais defensivo e meus colegas odeiam se eu escrevo, !=então não escrevo . No entanto, há um caso !=em C ++ e foi isso que eu apresentei.
9
Um cenário em que se pode acabar com um acréscimo acidental i++é quando um loop while é alterado para um loop for. Não tenho certeza de que ter metade do número de iterações seja melhor do que um loop (quase) infinito; o último provavelmente tornaria o bug mais óbvio.
AK2
12

Usar (i <10) é, na minha opinião, uma prática mais segura. Ele captura o número máximo de possíveis casos de desistência - tudo que é maior que ou igual a 10. Compare isso com o outro caso (i! = 10); ele só captura um possível caso de desistência - quando eu tenho exatamente 10 anos.

Um subproduto disso é que melhora a legibilidade. Além disso, se o incremento for qualquer outro 1, pode ajudar a minimizar a probabilidade de um problema, se cometermos um erro ao escrever o caso de encerramento.

Considerar:

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

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

Embora ambos os casos sejam provavelmente falhos / incorretos, o segundo provavelmente será MAIS errado, pois não será encerrado. O primeiro caso será encerrado e há uma chance maior de que ele seja encerrado no local certo, embora 14 seja provavelmente o número errado (15 provavelmente seria melhor).

Sparky
fonte
O primeiro caso pode estar certo! Se você deseja iterar sobre todos os números naturais menores que 14, não há maneira melhor de expressá-lo - calcular o limite superior "adequado" (13) seria completamente estúpido.
Maaartinus
9

Aqui está outra resposta que ninguém parece ter encontrado ainda. forloops devem ser usados ​​quando você precisar iterar sobre uma sequência . Usar !=é o método mais conciso de indicar a condição de término do loop. No entanto, o uso de um operador menos restritivo é um idioma de programação defensiva muito comum. Para números inteiros, isso não importa - é apenas uma escolha pessoal sem um exemplo mais específico. Fazendo loop nas coleções com iteradores que você deseja usar !=pelos motivos que outras pessoas declararam. Se você considera sequências de floatou double, então deseja evitar !=a todo custo.

O que eu queria ressaltar é que foré usado quando você precisa iterar em uma sequência. A sequência gerada possui um ponto inicial, um intervalo e uma condição final. Eles são especificados de maneira concisa na fordeclaração. Se você (1) não incluir a parte da etapa forou (2) especificar algo como truea condição de guarda, não deverá usar um forloop!

O whileloop é usado para continuar o processamento enquanto uma condição específica é atendida. Se você não está processando uma sequência, provavelmente deseja um whileloop. Os argumentos da condição de guarda são semelhantes aqui, mas a decisão entre a whilee um forloop deve ser muito consciente. O whileloop é subestimado nos círculos C ++ IMO.

Se você estiver processando uma coleção de itens (um foruso muito comum de loops), deverá realmente usar um método mais especializado. Infelizmente, std::for_eaché bastante doloroso em C ++ por vários motivos. Em muitos casos, separar o corpo de um forloop em uma função independente (embora um pouco dolorosa) resulta em uma solução muito mais limpa. Ao trabalhar com coleções, considere std::for_each, std::transformou std::accumulate. A implementação de muitos algoritmos se torna concisa e clara quando expressa dessa maneira. Sem mencionar que o isolamento do corpo do loop em uma função / método separado força você a se concentrar no algoritmo, em seus requisitos de entrada e em resultados.

Se você estiver usando Java, Python, Ruby, ou mesmo C ++ 0x , então você deve estar usando uma coleta adequada foreach loop. À medida que os compiladores C ++ implementam esse recurso, vários forloops desaparecem, assim como esses tipos de discussões.

D.Shawley
fonte
2
"No entanto, usar um operador menos restritivo é um idioma de programação defensiva muito comum". Isso resume mais ou menos.
James P.
1 para discussin as diferenças em comparação com a intenção de while, for, e foreach(ou língua equivilant)
Kevin Peno
Estritamente falando, o whilecircuito é usado para continuar o processamento até um estado específico é não conheci
Alnitak
@Alnitak - D'oh ... Eu vou consertar isso :)
D.Shawley
Fiquei confuso com os dois possíveis significados de "menos restritivo": poderia se referir ao operador ser indulgente nos valores que passa ( <) ou ao operador ser indulgente nos valores que falha ( !=).
Mateen Ulhaq
4

Usar "menor que" é (geralmente) semanticamente correto, você quer dizer contar até inão mais que 10, portanto "menos que" transmite claramente suas intenções. Usar "não igual" obviamente funciona praticamente em casos de chamada, mas transmite um significado ligeiramente diferente. Em alguns casos, isso pode ser o que você precisa, mas, na minha experiência, nunca foi esse o caso. Se você realmente teve um caso em que ipode ser maior ou menor que 10, mas deseja continuar fazendo um loop até que seja igual a 10, esse código realmente precisaria ser comentado com muita clareza e provavelmente poderia ser melhor escrito com alguma outra construção, como como um whilelaço, talvez.

Steve
fonte
2

Uma das razões pelas quais eu preferiria um less thanover a not equalsé atuar como guarda. Em algumas circunstâncias limitadas (programação ruim ou higienização), isso not equalspoderia ser ignorado, enquanto less thanainda estaria em vigor.

Aqui está um exemplo em que a falta de uma verificação de higienização levou a resultados estranhos: http://www.michaeleisen.org/blog/?p=358

James P.
fonte
0

Aqui está uma razão pela qual você pode preferir usar em <vez de !=. Se você estiver usando um idioma com escopo de variável global, o que acontecerá se outro código for modificado i? Se você estiver usando <, em vez de !=, o pior que acontece é que a iteração acabamentos mais rápidos: talvez alguns outros incrementos de código ipor acidente, e você pular algumas iterações no loop for. Mas o que acontece se você fizer um loop de 0 a 10, e o loop chegar a 9, e alguns threads mal escritos aumentarem ipor algum motivo estranho. Em seguida, seu loop conclui a iteração e aumenta ipara que o valor seja agora 11. Como o loop ignorou a condição de saída ( inunca igual a 10), ele será repetido infinitamente.

Não precisa necessariamente ser uma lógica do tipo de variáveis-de-threading e variáveis ​​globais particularmente esquisitas que causa isso. Pode ser que você esteja escrevendo um loop que precisa voltar atrás. Se você está mudando identro do loop e estraga sua lógica, tê-lo com um limite superior em vez de a !=é menos provável que o deixe em um loop infinito.

Tom Morris
fonte
3
Obviamente, isso parece um argumento perfeito para loops for-each e um estilo de programação mais funcional em geral. Mas, por que você gostaria de fazer isso quando variáveis ​​mutáveis ​​são muito mais divertidas ?!
Tom Morris
3
Não acho que seja uma razão terrivelmente boa. De qualquer maneira, você tem um bug que precisa ser encontrado e corrigido, mas um loop infinito tende a tornar um bug bastante óbvio.
AK2
0

No mundo incorporado, especialmente em ambientes ruidosos, você não pode contar com a RAM necessariamente se comportando como deveria. Em um condicional (para, enquanto, se) em que você compara usando '==' ou '! =', Você sempre corre o risco de que suas variáveis ​​pulem esse valor crucial que encerra o loop - isso pode ter consequências desastrosas - Mars Lander conseqüências de nível. Usar '<' ou '>' na condição fornece um nível extra de segurança para capturar as 'incógnitas desconhecidas'.

No exemplo original, se ifosse inexplicavelmente catapultada para um valor muito maior que 10, a comparação '<' capturaria o erro imediatamente e sairia do loop, mas '! =' Continuaria a contar até icontornar 0 e voltar para 10.

oosterwal
fonte
2
Existe outra variação relacionada ao código, como for (i=4; i<length; i++) csum+=dat[i];onde pacotes legítimos sempre teriam lengthpelo menos quatro, mas um pode receber pacotes corrompidos. Se tipos diferentes de pacotes tiverem requisitos de comprimento diferentes, convém validar o comprimento como parte do processamento diferente para cada tipo de pacote, mas valide a soma de verificação como parte da lógica comum primeiro. Se um pacote de menor tamanho for recebido, não importa realmente se o cálculo da soma de verificação relata "bom" ou "ruim", mas não deve travar.
Supercat 13/08