Frage 1: Ist das Deklarieren einer Variablen innerhalb einer Schleife eine gute oder eine schlechte Praxis?
Ich habe in den anderen Threads gelesen, ob es ein Leistungsproblem gibt oder nicht (die meisten sagten nein) und dass Sie Variablen immer so nah wie möglich deklarieren sollten, wo sie verwendet werden sollen. Ich frage mich, ob dies vermieden werden sollte oder ob es tatsächlich bevorzugt wird.
Beispiel:
for(int counter = 0; counter <= 10; counter++)
{
string someString = "testing";
cout << someString;
}
Frage 2: Erkennen die meisten Compiler, dass die Variable bereits deklariert wurde, und überspringen diesen Teil einfach, oder erstellt sie tatsächlich jedes Mal einen Platz im Speicher?
c++
loops
variable-declaration
JeramyRR
quelle
quelle
Antworten:
Dies ist eine ausgezeichnete Praxis.
Durch das Erstellen von Variablen in Schleifen stellen Sie sicher, dass deren Gültigkeitsbereich auf die Schleife beschränkt ist. Es kann nicht außerhalb der Schleife referenziert oder aufgerufen werden.
Diesen Weg:
Wenn der Name der Variablen etwas "generisch" ist (wie "i"), besteht kein Risiko, ihn irgendwo später in Ihrem Code mit einer anderen Variablen mit demselben Namen zu mischen (kann auch mithilfe der
-Wshadow
Warnanweisung in GCC verringert werden ).Der Compiler weiß, dass der Variablenbereich auf innerhalb der Schleife beschränkt ist, und gibt daher eine ordnungsgemäße Fehlermeldung aus, wenn auf die Variable versehentlich an anderer Stelle verwiesen wird.
Last but not least kann eine bestimmte dedizierte Optimierung vom Compiler effizienter durchgeführt werden (vor allem Registerzuordnung), da er weiß, dass die Variable nicht außerhalb der Schleife verwendet werden kann. Beispielsweise muss das Ergebnis nicht für eine spätere Wiederverwendung gespeichert werden.
Kurz gesagt, Sie haben Recht, es zu tun.
Beachten Sie jedoch, dass die Variable ihren Wert zwischen den einzelnen Schleifen nicht beibehalten soll . In diesem Fall müssen Sie es möglicherweise jedes Mal initialisieren. Sie können auch einen größeren Block erstellen, der die Schleife umfasst, deren einziger Zweck darin besteht, Variablen zu deklarieren, deren Wert von einer Schleife zur anderen beibehalten werden muss. Dies schließt typischerweise den Schleifenzähler selbst ein.
Zu Frage 2: Die Variable wird beim Aufruf der Funktion einmal zugewiesen. Aus Sicht der Zuordnung entspricht dies (fast) der Deklaration der Variablen zu Beginn der Funktion. Der einzige Unterschied ist der Umfang: Die Variable kann nicht außerhalb der Schleife verwendet werden. Es ist sogar möglich, dass die Variable nicht zugewiesen wird, sondern nur ein freier Steckplatz (von einer anderen Variablen, deren Gültigkeitsbereich beendet wurde) erneut verwendet.
Mit dem eingeschränkten und präziseren Umfang kommen genauere Optimierungen. Noch wichtiger ist jedoch, dass Ihr Code sicherer wird und weniger Zustände (dh Variablen) beim Lesen anderer Teile des Codes berücksichtigt werden müssen.
Dies gilt auch außerhalb eines
if(){...}
Blocks. In der Regel anstelle von:es ist sicherer zu schreiben:
Der Unterschied mag geringfügig erscheinen, insbesondere bei einem so kleinen Beispiel. Bei einer größeren Codebasis hilft dies jedoch: Jetzt besteht kein Risiko mehr, einen
result
Wert vonf1()
zuf2()
Block zu transportieren. Jedesresult
ist streng auf seinen eigenen Umfang beschränkt, wodurch seine Rolle genauer wird. Aus Sicht der Rezensenten ist es viel schöner, da er weniger Zustandsvariablen mit großer Reichweite hat, über die er sich Sorgen machen und die er verfolgen muss.Sogar der Compiler wird besser helfen: vorausgesetzt, dass in Zukunft nach einer fehlerhaften Codeänderung
result
nicht richtig mit initialisiert wirdf2()
. Die zweite Version weigert sich einfach zu arbeiten und gibt beim Kompilieren eine eindeutige Fehlermeldung aus (viel besser als zur Laufzeit). Die erste Version wird nichts erkennen, das Ergebnis vonf1()
wird einfach ein zweites Mal getestet und für das Ergebnis von verwirrtf2()
.Ergänzende Information
Das Open-Source-Tool CppCheck (ein statisches Analysetool für C / C ++ - Code) bietet einige hervorragende Hinweise zum optimalen Umfang von Variablen.
Antwort auf einen Kommentar zur Zuordnung: Die obige Regel gilt für C, gilt jedoch möglicherweise nicht für einige C ++ - Klassen.
Für Standardtypen und -strukturen ist die Größe der Variablen zum Zeitpunkt der Kompilierung bekannt. In C gibt es keine "Konstruktion", daher wird der Platz für die Variable einfach dem Stapel zugewiesen (ohne Initialisierung), wenn die Funktion aufgerufen wird. Aus diesem Grund fallen beim Deklarieren der Variablen innerhalb einer Schleife Kosten von "Null" an.
Für C ++ - Klassen gibt es jedoch diese Konstruktorsache, über die ich viel weniger weiß. Ich denke, die Zuweisung wird wahrscheinlich nicht das Problem sein, da der Compiler klug genug sein wird, um denselben Speicherplatz wiederzuverwenden, aber die Initialisierung wird wahrscheinlich bei jeder Schleifeniteration stattfinden.
quelle
string
undvector
speziell kann der Zuweisungsoperator den zugewiesenen Puffer für jede Schleife wiederverwenden, was (abhängig von Ihrer Schleife) eine enorme Zeitersparnis bedeuten kann.Im Allgemeinen ist es eine sehr gute Praxis, es sehr nah zu halten.
In einigen Fällen wird eine Überlegung wie die Leistung berücksichtigt, die das Herausziehen der Variablen aus der Schleife rechtfertigt.
In Ihrem Beispiel erstellt und zerstört das Programm die Zeichenfolge jedes Mal. Einige Bibliotheken verwenden eine kleine Zeichenfolgenoptimierung (SSO), sodass die dynamische Zuordnung in einigen Fällen vermieden werden kann.
Angenommen, Sie möchten diese redundanten Kreationen / Zuordnungen vermeiden, dann schreiben Sie sie wie folgt:
oder Sie können die Konstante herausziehen:
Es kann den von der Variablen belegten Speicherplatz wiederverwenden und Invarianten aus Ihrer Schleife ziehen. Im Fall des const char-Arrays (oben) könnte dieses Array herausgezogen werden. Der Konstruktor und der Destruktor müssen jedoch bei jeder Iteration im Fall eines Objekts (z. B.
std::string
) ausgeführt werden. Im Fall vonstd::string
enthält dieses 'Leerzeichen' einen Zeiger, der die dynamische Zuordnung enthält, die die Zeichen darstellt. Also das:würde in jedem Fall redundantes Kopieren und dynamische Zuweisung erfordern und frei, wenn die Variable über dem Schwellenwert für die Anzahl der SSO-Zeichen liegt (und SSO von Ihrer Standardbibliothek implementiert wird).
Dies tun:
würde immer noch eine physische Kopie der Zeichen bei jeder Iteration erfordern, aber das Formular könnte zu einer dynamischen Zuordnung führen, da Sie die Zeichenfolge zuweisen und die Implementierung erkennen sollte, dass die Größe der Sicherungszuordnung der Zeichenfolge nicht geändert werden muss. Natürlich würden Sie dies in diesem Beispiel nicht tun (da bereits mehrere überlegene Alternativen demonstriert wurden), aber Sie könnten dies in Betracht ziehen, wenn der Inhalt der Zeichenfolge oder des Vektors variiert.
Was machen Sie mit all diesen Optionen (und mehr)? Halten Sie es standardmäßig sehr nah - bis Sie die Kosten gut verstanden haben und wissen, wann Sie abweichen sollten.
quelle
Für C ++ hängt es davon ab, was Sie tun. OK, es ist dummer Code, aber stell dir vor
Sie werden 55 Sekunden warten, bis Sie die Ausgabe von myFunc erhalten. Nur weil jeder Schleifenkonstruktor und Destruktor zusammen 5 Sekunden benötigt, um fertig zu werden.
Sie benötigen 5 Sekunden, bis Sie die Ausgabe von myOtherFunc erhalten.
Das ist natürlich ein verrücktes Beispiel.
Es zeigt jedoch, dass es zu einem Leistungsproblem werden kann, wenn jede Schleife dieselbe Konstruktion ausführt, wenn der Konstruktor und / oder Destruktor einige Zeit benötigt.
quelle
Ich habe nicht gepostet, um die Fragen von JeremyRR zu beantworten (da sie bereits beantwortet wurden). Stattdessen habe ich nur gepostet, um einen Vorschlag zu machen.
Für JeremyRR könnten Sie dies tun:
Ich weiß nicht, ob Sie erkennen (ich habe es nicht getan, als ich mit dem Programmieren angefangen habe), dass Klammern (solange sie paarweise sind) an einer beliebigen Stelle im Code platziert werden können, nicht nur nach "if", "for", " während "usw.
Mein Code wurde in Microsoft Visual C ++ 2010 Express kompiliert, sodass ich weiß, dass er funktioniert. Außerdem habe ich versucht, die Variable außerhalb der Klammern zu verwenden, in denen sie definiert wurde, und ich habe einen Fehler erhalten, sodass ich weiß, dass die Variable "zerstört" wurde.
Ich weiß nicht, ob es eine schlechte Praxis ist, diese Methode zu verwenden, da viele unbeschriftete Klammern den Code schnell unlesbar machen könnten, aber vielleicht könnten einige Kommentare die Dinge klären.
quelle
Es ist eine sehr gute Praxis, da alle obigen Antworten einen sehr guten theoretischen Aspekt der Frage liefern. Lassen Sie mich einen Blick auf Code werfen. Ich habe versucht, DFS über GEEKSFORGEEKS zu lösen. Ich stoße auf das Optimierungsproblem. Wenn Sie es versuchen Wenn Sie den Code lösen, der die Ganzzahl außerhalb der Schleife deklariert, erhalten Sie einen Optimierungsfehler.
Fügen Sie nun ganze Zahlen in die Schleife ein, damit Sie die richtige Antwort erhalten ...
Dies spiegelt vollständig wider, was Sir @justin im 2. Kommentar gesagt hat. Versuchen Sie dies hier https://practice.geeksforgeeks.org/problems/depth-first-traversal-for-a-graph/1 . Probieren Sie es einfach aus ... Sie werden es bekommen. Hoffen Sie diese Hilfe.
quelle
flag
sollte bei jederwhile
Iteration bei 0 neu initialisiert werden. Das ist ein logisches Problem, kein Definitionsproblem.Kapitel 4.8 Blockstruktur in K & R ist die Programmiersprache C 2.Ed. ::
Möglicherweise habe ich die entsprechende Beschreibung im Buch nicht gesehen:
Ein einfacher Test kann jedoch die Annahme beweisen:
quelle