Esta é uma excelente prática.
Ao criar variáveis dentro de loops, você garante que seu escopo seja restrito a dentro do loop. Não pode ser referenciado nem chamado fora do loop.
Deste jeito:
Se o nome da variável for um pouco "genérico" (como "i"), não há risco de misturá-la com outra variável com o mesmo nome em algum lugar posteriormente no seu código (também pode ser mitigado usando a -Wshadow
instrução de aviso no GCC)
O compilador sabe que o escopo da variável está limitado ao interior do loop e, portanto, emitirá uma mensagem de erro adequada se a variável for referenciada por engano em outro local.
Por último, mas não menos importante, alguma otimização dedicada pode ser executada com mais eficiência pelo compilador (o mais importante é alocar o registro), pois sabe que a variável não pode ser usada fora do loop. Por exemplo, não há necessidade de armazenar o resultado para reutilização posterior.
Em suma, você está certo em fazê-lo.
Observe, no entanto, que a variável é não deve manter seu valor entre cada loop. Nesse caso, pode ser necessário inicializá-lo sempre. Você também pode criar um bloco maior, abrangendo o loop, cujo único objetivo é declarar variáveis que devem manter seu valor de um loop para outro. Isso normalmente inclui o próprio contador de loop.
{
int i, retainValue;
for (i=0; i<N; i++)
{
int tmpValue;
/* tmpValue is uninitialized */
/* retainValue still has its previous value from previous loop */
/* Do some stuff here */
}
/* Here, retainValue is still valid; tmpValue no longer */
}
Para a pergunta 2: A variável é alocada uma vez, quando a função é chamada. De fato, de uma perspectiva de alocação, é (quase) o mesmo que declarar a variável no início da função. A única diferença é o escopo: a variável não pode ser usada fora do loop. Pode até ser possível que a variável não esteja alocada, apenas reutilizando algum espaço livre (de outra variável cujo escopo terminou).
Com escopo restrito e preciso, otimizações mais precisas. Mais importante, porém, isso torna seu código mais seguro, com menos estados (ou seja, variáveis) com que se preocupar ao ler outras partes do código.
Isso é verdade mesmo fora de um if(){...}
bloco. Normalmente, em vez de:
int result;
(...)
result = f1();
if (result) then { (...) }
(...)
result = f2();
if (result) then { (...) }
é mais seguro escrever:
(...)
{
int const result = f1();
if (result) then { (...) }
}
(...)
{
int const result = f2();
if (result) then { (...) }
}
A diferença pode parecer pequena, especialmente em um exemplo tão pequeno. Mas em uma base de código maior, ajudará: agora não há risco de transportar algum result
valor de f1()
para f2()
bloquear. Cada um result
é estritamente limitado ao seu próprio escopo, tornando seu papel mais preciso. Do ponto de vista do revisor, é muito melhor, pois ele tem menos variáveis de estado de longo alcance para se preocupar e acompanhar.
Até o compilador ajudará melhor: supondo que, no futuro, após alguma alteração incorreta de código, result
não seja adequadamente inicializado f2()
. A segunda versão simplesmente se recusará a trabalhar, declarando uma mensagem de erro clara em tempo de compilação (muito melhor que o tempo de execução). A primeira versão não localizará nada, o resultado de f1()
simplesmente será testado uma segunda vez, sendo confundido pelo resultado de f2()
.
Informação complementar
A ferramenta de código aberto CppCheck (uma ferramenta de análise estática para código C / C ++) fornece algumas dicas excelentes sobre o escopo ideal das variáveis.
Em resposta ao comentário sobre alocação: A regra acima é verdadeira em C, mas pode não ser para algumas classes C ++.
Para tipos e estruturas padrão, o tamanho da variável é conhecido no momento da compilação. Não existe "construção" em C; portanto, o espaço para a variável será simplesmente alocado na pilha (sem nenhuma inicialização), quando a função é chamada. É por isso que existe um custo "zero" ao declarar a variável dentro de um loop.
No entanto, para as classes C ++, existe esse construtor sobre o qual sei muito menos. Acho que a alocação provavelmente não será o problema, pois o compilador deve ser inteligente o suficiente para reutilizar o mesmo espaço, mas é provável que a inicialização ocorra a cada iteração do loop.