Question # 1: La déclaration d'une variable à l'intérieur d'une boucle est-elle une bonne ou une mauvaise pratique?
J'ai lu les autres discussions pour savoir s'il y a ou non un problème de performances (la plupart ont dit non) et que vous devriez toujours déclarer les variables aussi près de l'endroit où elles vont être utilisées. Ce que je me demande, c'est si cela doit être évité ou si c'est réellement préféré.
Exemple:
for(int counter = 0; counter <= 10; counter++)
{
string someString = "testing";
cout << someString;
}
Question # 2: La plupart des compilateurs se rendent-ils compte que la variable a déjà été déclarée et sautent-ils simplement cette partie, ou créent-ils réellement une place pour elle en mémoire à chaque fois?
c++
loops
variable-declaration
JeramyRR
la source
la source
Réponses:
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.
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:il est plus sûr d'écrire:
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 def1()
àf2()
bloquer. Chacunresult
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é avecf2()
. 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 def1()
sera simplement testé une deuxième fois, confondu avec le résultat def2()
.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.
la source
string
et plusvector
précisément, l'opérateur d'affectation peut réutiliser le tampon alloué à chaque boucle, ce qui (en fonction de votre boucle) peut représenter un énorme gain de temps.Généralement, c'est une très bonne pratique de le garder très près.
Dans certains cas, il y aura une considération telle que la performance qui justifie de retirer la variable de la boucle.
Dans votre exemple, le programme crée et détruit la chaîne à chaque fois. Certaines bibliothèques utilisent une petite optimisation de chaîne (SSO), de sorte que l'allocation dynamique pourrait être évitée dans certains cas.
Supposons que vous vouliez éviter ces créations / allocations redondantes, vous l'écririez ainsi:
ou vous pouvez extraire la constante:
Il peut réutiliser l'espace consommé par la variable et il peut extraire des invariants de votre boucle. Dans le cas du tableau const char (ci-dessus) - ce tableau pourrait être retiré. Cependant, le constructeur et le destructeur doivent être exécutés à chaque itération dans le cas d'un objet (tel que
std::string
). Dans le cas dustd::string
, cet «espace» comprend un pointeur qui contient l'allocation dynamique représentant les caractères. Donc ça:nécessiterait une copie redondante dans chaque cas, et une allocation dynamique et gratuite si la variable se situe au-dessus du seuil de nombre de caractères SSO (et SSO est implémenté par votre bibliothèque std).
Ce faisant:
nécessiterait toujours une copie physique des caractères à chaque itération, mais le formulaire pourrait entraîner une allocation dynamique car vous affectez la chaîne et l'implémentation devrait voir qu'il n'est pas nécessaire de redimensionner l'allocation de sauvegarde de la chaîne. Bien sûr, vous ne feriez pas cela dans cet exemple (car plusieurs alternatives supérieures ont déjà été démontrées), mais vous pouvez le considérer lorsque le contenu de la chaîne ou du vecteur varie.
Alors, que faites-vous avec toutes ces options (et plus)? Gardez-le très près par défaut - jusqu'à ce que vous compreniez bien les coûts et sachiez quand vous devez vous écarter.
la source
Pour C ++, cela dépend de ce que vous faites. OK, c'est du code stupide mais imaginez
Vous attendez 55 secondes jusqu'à ce que vous obteniez la sortie de myFunc. Tout simplement parce que chaque constructeur et destructeur de boucle a besoin de 5 secondes pour terminer.
Vous aurez besoin de 5 secondes jusqu'à ce que vous obteniez la sortie de myOtherFunc.
Bien sûr, c'est un exemple fou.
Mais cela montre que cela peut devenir un problème de performances lorsque chaque boucle a la même construction lorsque le constructeur et / ou le destructeur a besoin de temps.
la source
Je n'ai pas posté pour répondre aux questions de JeremyRR (car elles ont déjà été répondues); à la place, j'ai posté simplement pour donner une suggestion.
Pour JeremyRR, vous pouvez faire ceci:
Je ne sais pas si vous vous rendez compte (je ne l'ai pas fait lorsque j'ai commencé la programmation), que les crochets (tant qu'ils sont en paires) peuvent être placés n'importe où dans le code, pas seulement après "si", "pour", " pendant ", etc.
Mon code compilé dans Microsoft Visual C ++ 2010 Express, donc je sais que cela fonctionne; aussi, j'ai essayé d'utiliser la variable en dehors des crochets dans lesquels elle était définie et j'ai reçu une erreur, donc je sais que la variable a été "détruite".
Je ne sais pas si c'est une mauvaise pratique d'utiliser cette méthode, car beaucoup de crochets non étiquetés pourraient rapidement rendre le code illisible, mais peut-être que certains commentaires pourraient clarifier les choses.
la source
C'est une très bonne pratique, car toutes les réponses ci-dessus fournissent un très bon aspect théorique de la question, permettez-moi de donner un aperçu du code, j'essayais de résoudre DFS sur GEEKSFORGEEKS, je rencontre le problème d'optimisation ...... Si vous essayez de résoudre le code déclarant l'entier en dehors de la boucle vous donnera une erreur d'optimisation.
Maintenant, mettez des entiers dans la boucle, cela vous donnera une réponse correcte ...
cela reflète complètement ce que monsieur @justin disait dans le deuxième commentaire .... essayez ceci ici https://practice.geeksforgeeks.org/problems/depth-first-traversal-for-a-graph/1 . il suffit de lui donner un coup de feu ... vous l'obtiendrez. J'espère que cette aide.
la source
flag
doit être réinitialisé à 0 à chaquewhile
itération. C'est un problème de logique, pas un problème de définition.Chapitre 4.8 Structure des blocs dans K&R Le langage de programmation C 2.Ed. :
J'ai peut-être manqué de voir la description pertinente dans le livre comme:
Mais un simple test peut prouver l'hypothèse retenue:
la source