Eu recebi essa ideia dessa pergunta no stackoverflow.com
O seguinte padrão é comum:
final x = 10;//whatever constant value
for(int i = 0; i < Math.floor(Math.sqrt(x)) + 1; i++) {
//...do something
}
O que estou tentando enfatizar é que a declaração condicional é algo complicado e não muda.
É melhor declará-lo na seção de inicialização do loop, como tal?
final x = 10;//whatever constant value
for(int i = 0, j = Math.floor(Math.sqrt(x)) + 1; i < j; i++) {
//...do something
}
Isso é mais claro?
E se a expressão condicional for simples, como
final x = 10;//whatever constant value
for(int i = 0, j = n*n; i > j; j++) {
//...do something
}
java
c++
coding-style
language-agnostic
Celeritas
fonte
fonte
x
é grande em magnitude,Math.floor(Math.sqrt(x))+1
é igual aMath.floor(Math.sqrt(x))
. :-){ x=whatever; for (...) {...} }
ou, melhor ainda, considere se há o suficiente acontecendo para que ele precise ser uma função separada.Respostas:
O que eu faria é algo como isto:
Honestamente, a única boa razão para empilhar a inicialização
j
(agoralimit
) no cabeçalho do loop é mantê-lo no escopo correto. Tudo o que é necessário para fazer com que um problema não seja um bom escopo de fechamento.Posso apreciar o desejo de ser rápido, mas não sacrifique a legibilidade sem uma boa razão.
Certamente, o compilador pode otimizar, inicializar vários vars pode ser legal, mas os loops são difíceis o suficiente para depurar como estão. Por favor, seja gentil com os humanos. Se isso realmente estiver nos atrasando, é bom entender o suficiente para corrigi-lo.
fonte
for(int i = 0; i < n*n; i++){...}
você não atribuirian*n
a uma variável, sim?final
). Quem se importa se uma constante que tem imposição do compilador impedindo que ela seja alterada esteja acessível posteriormente na função?Um bom compilador irá gerar o mesmo código de qualquer maneira; portanto, se você estiver buscando desempenho, faça apenas uma alteração se estiver em um loop crítico e você realmente o criou e fez o perfil e descobriu que faz a diferença. Mesmo que o compilador não possa otimizá-lo, como as pessoas apontaram nos comentários sobre o caso com chamadas de função, na grande maioria das situações, a diferença de desempenho será muito pequena para valer a consideração de um programador.
Contudo...
Não devemos esquecer que o código é principalmente um meio de comunicação entre seres humanos, e as duas opções não se comunicam muito bem com outros seres humanos. O primeiro dá a impressão de que a expressão precisa ser calculada a cada iteração, e o segundo na seção de inicialização implica que ela será atualizada em algum lugar dentro do loop, onde é realmente constante o tempo todo.
Na verdade, eu preferiria que fosse retirado acima do loop e tornado
final
isso imediato e abundantemente claro para quem lê o código. Isso também não é ideal porque aumenta o escopo da variável, mas sua função de fechamento não deve conter muito mais do que esse loop.fonte
Como o @Karl Bielefeldt disse em sua resposta, isso geralmente não é um problema.
No entanto, era um problema comum em C e C ++, e um truque surgiu para contornar o problema sem reduzir a legibilidade do código - iterar para trás, até
0
.Agora, o condicional em cada iteração é exatamente o
>= 0
que todo compilador compilará em 1 ou 2 instruções de montagem. Cada CPU fabricada nas últimas décadas deve ter verificações básicas como estas; fazendo uma verificação rápida na minha máquina x64, vejo que isso se previsivelmente se transforma emcmpl $0x0, -0x14(%rbp)
(valor de comparação de longa duração 0 vs. registro rbp compensado -14) ejl 0x100000f59
(pule para a instrução que segue o loop, se a comparação anterior for verdadeira para "2nd-arg <1-arg ") .Observe que eu removi o
+ 1
deMath.floor(Math.sqrt(x)) + 1
; para que a matemática funcione, o valor inicial deve serint i = «iterationCount» - 1
. Também digno de nota é que seu iterador deve ser assinado;unsigned int
não funcionará e provavelmente alertará o compilador.Depois de programar em linguagens baseadas em C por ~ 20 anos, agora apenas escrevo loops de iteração de índice reverso, a menos que haja um motivo específico para iterar a indexação direta. Além de verificações mais simples nos condicionais, a iteração reversa geralmente também evita o que seria mutável de matriz de array enquanto iterava.
fonte
unsigned
contadores funcionarão aqui se você modificar a verificação (a maneira mais fácil é adicionar o mesmo valor aos dois lados); por exemplo, para qualquer decréscimoDec
, a verificação(i + Dec) >= Dec
deve sempre ter o mesmo resultado que asigned
verificaçãoi >= 0
, com ambossigned
eunsigned
contadores, desde que o idioma tenha regras abrangentes bem definidas paraunsigned
variáveis (especificamente,-n + n == 0
deve ser verdade para ambassigned
eunsigned
). Observe, no entanto, que isso pode ser menos eficiente que uma>=0
verificação assinada , se o compilador não tiver uma otimização para isso.Dec
constante ao valor inicial e ao valor final funciona, mas o torna muito menos intuitivo e, se estiver usandoi
como um índice de matriz, você também precisará fazer umunsigned int arrayI = i - Dec;
no corpo do loop. Eu apenas uso a iteração direta quando preso a um iterador não assinado; geralmente com ai <= count - 1
condição de manter a lógica paralela aos loops de iteração reversa.Dec
especificamente os valores inicial e final, mas alterar a verificação da condiçãoDec
dos dois lados.for (unsigned i = N - 1; i + 1 >= 1; i--) /*...*/
Isso permite que você usei
normalmente dentro do loop, garantindo ao mesmo tempo o menor valor possível no lado esquerdo da condição0
(para evitar interferências na envolvente). É definitivamente muito mais simples usar a iteração direta ao trabalhar com contadores não assinados.Torna-se interessante quando Math.sqrt (x) é substituído por Mymodule.SomeNonPureMethodWithSideEffects (x).
Basicamente, meu modus operandi é: se algo deve sempre dar o mesmo valor, avalie-o apenas uma vez. Por exemplo List.Count, se a lista não mudar durante a operação do loop, coloque a contagem fora do loop em outra variável.
Algumas dessas "contagens" podem ser surpreendentemente caras, especialmente quando você está lidando com bancos de dados. Mesmo se você estiver trabalhando em um conjunto de dados que não deve ser alterado durante a iteração da lista.
fonte
for( auto it = begin(dataset); !at_end(it); ++it )
Na minha opinião, isso é altamente específico do idioma. Por exemplo, se estiver usando C ++ 11, eu suspeitaria que, se a verificação da condição fosse uma
constexpr
função, o compilador provavelmente otimizaria as várias execuções, pois sabe que produzirá o mesmo valor todas as vezes.No entanto, se a chamada de função for uma função de biblioteca que não é
constexpr
o compilador, quase certamente a executará em todas as iterações, pois não pode deduzir isso (a menos que esteja em linha e, portanto, possa ser deduzida como pura).Eu sei menos sobre Java, mas, como é compilado pelo JIT, acho que o compilador tem informações suficientes em tempo de execução para provavelmente incorporar e otimizar a condição. Mas isso depende de um bom design do compilador e o compilador que decide esse loop é uma prioridade de otimização, a qual podemos adivinhar.
Pessoalmente, eu sinto que é um pouco mais elegante para colocar a condição dentro do loop for, se puder, mas se ele é complexo vou escrevê-lo em um
constexpr
ouinline
função, ou suas línguas equivalant sugerir que a função é pura e optimisable. Isso torna a intenção óbvia e mantém o estilo de loop idiomático sem criar uma linha enorme e ilegível. Também fornece um nome à verificação de condição, se for sua própria função, para que os leitores possam ver imediatamente de que serve a verificação sem lê-la, se for complexa.fonte