C'est une excellente pratique.
En créant des variables à l'intérieur des boucles, vous vous assurez que leur portée est limitée à l'intérieur de la boucle. Il ne peut pas être référencé ni appelé en dehors de la boucle.
Par ici:
Si le nom de la variable est un peu "générique" (comme "i"), il n'y a aucun risque de le mélanger avec une autre variable du même nom quelque part plus tard dans votre code (peut également être atténué en utilisant l' -Wshadow
instruction d'avertissement sur GCC)
Le compilateur sait que la portée de la variable est limitée à l'intérieur de la boucle et émettra donc un message d'erreur approprié si la variable est référencée par erreur ailleurs.
Enfin et surtout, une optimisation dédiée peut être effectuée plus efficacement par le compilateur (plus important encore, l'allocation des registres), car il sait que la variable ne peut pas être utilisée en dehors de la boucle. Par exemple, pas besoin de stocker le résultat pour une réutilisation ultérieure.
Bref, vous avez raison de le faire.
Notez cependant que la variable n'est pas censée conserver sa valeur entre chaque boucle. Dans ce cas, vous devrez peut-être l'initialiser à chaque fois. Vous pouvez également créer un bloc plus grand, englobant la boucle, dont le seul but est de déclarer des variables qui doivent conserver leur valeur d'une boucle à l'autre. Cela inclut généralement le compteur de boucle lui-même.
{
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 */
}
Pour la question # 2: La variable est allouée une fois, lorsque la fonction est appelée. En fait, du point de vue de l'allocation, c'est (presque) la même chose que de déclarer la variable au début de la fonction. La seule différence est la portée: la variable ne peut pas être utilisée en dehors de la boucle. Il peut même être possible que la variable ne soit pas allouée, simplement en réutilisant un emplacement libre (à partir d'une autre variable dont la portée est terminée).
Une portée restreinte et plus précise s'accompagne d'optimisations plus précises. Mais plus important encore, cela rend votre code plus sûr, avec moins d'états (c'est-à-dire des variables) à prendre en compte lors de la lecture d'autres parties du code.
Cela est vrai même en dehors d'un if(){...}
bloc. En règle générale, au lieu de:
int result;
(...)
result = f1();
if (result) then { (...) }
(...)
result = f2();
if (result) then { (...) }
il est plus sûr d'écrire:
(...)
{
int const result = f1();
if (result) then { (...) }
}
(...)
{
int const result = f2();
if (result) then { (...) }
}
La différence peut sembler mineure, surtout sur un si petit exemple. Mais sur une base de code plus grande, cela aidera: maintenant, il n'y a aucun risque de transporter une result
valeur de f1()
à f2()
bloquer. Chacun result
est strictement limité à sa propre portée, ce qui rend son rôle plus précis. Du point de vue de l'examinateur, c'est beaucoup plus agréable, car il a moins de variables d'état à longue portée à s'inquiéter et à suivre.
Même le compilateur aidera mieux: en supposant qu'à l'avenir, après un changement de code erroné, il result
ne soit pas correctement initialisé avec f2()
. La deuxième version refusera simplement de fonctionner, indiquant un message d'erreur clair au moment de la compilation (bien mieux que lors de l'exécution). La première version ne verra rien, le résultat de f1()
sera simplement testé une deuxième fois, confondu avec le résultat de f2()
.
Information complémentaire
L'outil open-source CppCheck (un outil d'analyse statique pour le code C / C ++) fournit d'excellentes astuces concernant la portée optimale des variables.
En réponse au commentaire sur l'allocation: La règle ci-dessus est vraie en C, mais peut-être pas pour certaines classes C ++.
Pour les types et structures standard, la taille de la variable est connue au moment de la compilation. Il n'y a pas de "construction" en C, donc l'espace pour la variable sera simplement alloué dans la pile (sans aucune initialisation), lorsque la fonction sera appelée. C'est pourquoi il y a un coût "nul" lors de la déclaration de la variable à l'intérieur d'une boucle.
Cependant, pour les classes C ++, il y a cette chose constructeur que je connais beaucoup moins. Je suppose que l'allocation ne sera probablement pas le problème, car le compilateur sera suffisamment intelligent pour réutiliser le même espace, mais l'initialisation aura probablement lieu à chaque itération de boucle.