Ich habe folgende Fragen zu Strings in C ++
1 & gt; & gt; Welches ist eine bessere Option (unter Berücksichtigung der Leistung) und warum?
1.
%Vor%ODER
2.
%Vor%2 & gt; & gt;
%Vor%Wie genau wird die Speicherverwaltung in C ++ gehandhabt, wenn eine größere Zeichenfolge in eine kleinere Zeichenfolge kopiert wird? Sind C ++ - Zeichen veränderbar?
Das Folgende ist, was ein naiver Compiler tun würde. Natürlich, solange es das Verhalten des Programms nicht ändert, kann der Compiler keine Optimierung vornehmen.
%Vor%Zuerst initialisierst du a, um die leere Zeichenfolge zu enthalten. (Legen Sie die Länge auf 0 und eine oder zwei andere Operationen fest). Dann weisen Sie einen neuen Wert zu und überschreiben den bereits eingestellten Längenwert. Es muss auch überprüft werden, wie groß der aktuelle Puffer ist und ob mehr Speicher zugewiesen werden soll.
%Vor%Beim Aufruf von new müssen das Betriebssystem und der Speicherzuordner einen freien Speicherblock finden. Das ist langsam. Dann initialisierst du es sofort, so dass du nichts doppelt zuweisen musst oder die Größe des Puffers ändern musst, wie du es in der ersten Version getan hast. Dann passiert etwas Schlimme, und Sie vergessen zu löschen, und Sie haben einen Speicherverlust, zusätzlich zu einer Zeichenfolge, die extrem langsam zuzuweisen ist. Das ist also schlecht.
%Vor%Wie im ersten Fall initialisieren Sie zuerst a, um die leere Zeichenfolge zu enthalten. Dann weisen Sie eine neue Zeichenfolge und dann eine andere zu. Jedes dieser kann erfordert einen Aufruf an new, um mehr Speicher zuzuweisen. Für jede Zeile müssen außerdem Länge und möglicherweise andere interne Variablen zugewiesen werden.
Normalerweise würden Sie es wie folgt zuweisen:
%Vor%In einer Zeile führen Sie die Initialisierung einmal durch, anstatt zuerst die Standardinitialisierung durchzuführen und dann den gewünschten Wert zuzuweisen.
Es minimiert auch Fehler, weil Sie nirgends in Ihrem Programm eine sinnlose leere Zeichenfolge haben. Wenn die Zeichenfolge existiert, enthält sie den gewünschten Wert.
Über die Speicherverwaltung, google RAII. Kurz gesagt, string ruft intern neu auf / löscht intern den Puffer. Das bedeutet, dass Sie nie eine Zeichenfolge mit new verknüpfen müssen. Das Zeichenfolgenobjekt hat eine feste Größe und ist so konzipiert, dass es im Stapel zugeordnet wird, sodass der Destruktor automatisch aufgerufen wird , wenn er den Gültigkeitsbereich verlässt. Der Destruktor garantiert dann, dass jeder zugewiesene Speicher freigegeben wird. Auf diese Weise müssen Sie in Ihrem Benutzercode nicht new / delete verwenden, was bedeutet, dass Sie keinen Speicher verlieren.
Es ist fast nie notwendig oder wünschenswert zu sagen
%Vor%Schließlich würden Sie (fast) nie sagen:
%Vor%Sie sollten stattdessen
sagen %Vor%oder
%Vor%Und ja, C ++ - Zeichenfolgen sind änderbar.
Gibt es einen bestimmten Grund, warum Sie ständig die Zuweisung anstelle der Initialisierung verwenden? Warum schreibst du nicht
? %Vor%usw.? Dies vermeidet eine Standardkonstruktion und macht semantisch mehr Sinn. Das Erstellen eines Zeigers für eine Zeichenfolge, nur um sie auf dem Heap zuzuweisen, ist niemals sinnvoll, d. H. Ihre Groß- / Kleinschreibung 2 ist nicht sinnvoll und etwas weniger effizient.
Was Ihre letzte Frage betrifft, ja, sind Zeichenketten in C ++ änderbar, wenn sie nicht als const
deklariert sind.
2 operations: ruft den Standardkonstruktor std: string () auf und ruft dann den Operator :: =
auf %Vor%nur eine Operation: ruft den Konstruktor std: string (const char *) auf, aber Sie sollten nicht vergessen, den Zeiger loszulassen.
Was ist los? Zeichenkette a ("Hallo");
Im Fall 1.1 werden Ihre Mitglieder (die Zeiger auf die Daten enthalten) in stack
gehalten, und der von der Klasseninstanz belegte Speicher wird freigegeben, wenn a
den Gültigkeitsbereich verlässt.
In Fall 1.2 wird Speicher für die Mitglieder dynamisch auch vom Heap zugewiesen.
Wenn Sie einer Zeichenfolge eine char*
-Konstante zuweisen, wird der Speicher, der die Daten enthält, realloc
'ed sein, um den neuen Daten zu entsprechen.
Sie können sehen, wie viel Speicher zugewiesen wird, indem Sie string::capacity()
aufrufen.
Wenn Sie string a("hello")
aufrufen, wird Speicher im Konstruktor zugewiesen.
Sowohl der Konstruktor als auch der Zuweisungsoperator rufen intern die gleichen Methoden für den zugewiesenen Speicher auf und kopieren dort neue Daten.
Wenn Sie sich die Dokumente für die STL-String-Klasse ansehen (ich glaube, dass die SGI-Dokumente kompatibel sind) zur Spezifikation), listen viele der Methoden Komplexitätsgarantien auf. Ich glaube, dass viele der Komplexitätsgarantien bewusst vage bleiben, um verschiedene Implementierungen zu ermöglichen. Ich denke, einige Implementierungen verwenden tatsächlich eine Kopie-auf-Änderung-Methode, so dass das Zuweisen einer Zeichenfolge zu einer anderen eine Operation mit konstanter Zeit ist, aber wenn Sie versuchen, eine dieser Instanzen zu ändern, können unerwartete Kosten entstehen. Nicht sicher, ob das in der modernen STL immer noch stimmt.
Sie sollten auch die Funktion capacity()
auschecken, die Ihnen die maximale Länge der Zeichenfolge angibt, die Sie in eine bestimmte Zeichenfolgeninstanz eingeben können, bevor sie gezwungen wird, Speicher neu zuzuweisen. Sie können reserve()
auch verwenden, um eine Neuzuweisung auf einen bestimmten Wert zu bewirken, wenn Sie wissen, dass Sie zu einem späteren Zeitpunkt eine große Zeichenfolge in der Variablen speichern werden.
Wie andere gesagt haben, sollten Sie, was Ihre Beispiele betrifft, die Initialisierung gegenüber anderen Ansätzen bevorzugen, um die Erstellung temporärer Objekte zu vermeiden.
Das Erstellen einer Zeichenfolge direkt im Heap ist normalerweise keine gute Idee, genauso wie das Erstellen von Basistypen. Es lohnt sich nicht, da das Objekt leicht auf dem Stapel bleiben kann und alle Kopierkonstruktoren und den Zuweisungsoperator für eine effiziente Kopie benötigt.
Die std: string selbst hat einen Puffer im Heap, der je nach Implementierung von mehreren Strings geteilt werden kann.
Für die Microsoft-STL-Implementierung könnten Sie das tun:
%Vor%Und beide Zeichenfolgen teilen den gleichen Puffer, bis Sie ihn geändert haben:
%Vor%Deshalb war es sehr schlecht, das c_str () für die spätere Verwendung zu speichern; c_str () garantiert nur Gültigkeit, bis ein anderer Aufruf dieses String-Objekts erfolgt.
Dies führte zu sehr unangenehmen Nebenläufigkeitsfehlern, bei denen diese Freigabefunktionalität mit einem Define deaktiviert werden musste, wenn Sie sie in einer Multithreadanwendung verwendeten
Sie kommen aus Java, richtig? In C ++ werden Objekte (in den meisten Fällen) als Grundwerktypen behandelt. Objekte können auf dem Stapel oder im statischen Speicher leben und nach Wert übergeben werden. Wenn Sie eine Zeichenfolge in einer Funktion deklarieren, weist dies dem Stapel jedoch viele Bytes zu, die das Zeichenfolgenobjekt benötigt. Das Zeichenfolgenobjekt selbst verwendet dynamischen Speicher, um die tatsächlichen Zeichen zu speichern, aber das ist für Sie transparent. Die andere Sache zu erinnern ist, dass, wenn die Funktion beendet und die Zeichenfolge, die Sie deklariert haben, nicht mehr im Bereich ist, wird der gesamte Speicher freigegeben. Keine Müllsammlung nötig (RAII ist dein bester Freund).
In Ihrem Beispiel:
%Vor%Dies setzt einen Speicherblock auf den Stack und nennt ihn a, dann wird der Konstruktor aufgerufen und a wird auf eine leere Zeichenfolge initialisiert. Der Compiler speichert die Bytes für "less" und "moreeeeeee" in (denke ich) den .rdata-Abschnitt Ihrer exe. String a wird einige Felder haben, wie ein Längenfeld und ein char * (ich vereinfache sehr). Wenn Sie "a" "less" zuweisen, wird die Methode operator = () aufgerufen. Es weist dynamisch Speicher zu, um den Eingabewert zu speichern, und kopiert es dann. Wenn Sie später "moreeeeeee" zu a zuweisen, wird die operator = () -Methode erneut aufgerufen, und es wird genügend Speicher zugewiesen, um den neuen Wert bei Bedarf zu speichern in den internen Puffer.
Wenn der String-Bereich endet, wird der String-Destruktor aufgerufen und der Speicher, der dynamisch zugewiesen wurde, um die tatsächlichen Zeichen aufzunehmen, wird freigegeben. Dann wird der Stapelzeiger dekrementiert und der Speicher, der a enthielt, ist nicht mehr "auf" dem Stapel.