Möglichkeiten mit Strings zu arbeiten
-
C++ bietet mehrere Möglichkeiten mit Strings zu arbeiten. Da blick ich nicht so ganz durch. Gibt es sowas wie eine "Comparison between techniques A,B,C, ..."?
-
Es gibt ja eigentlich nur rohe char-Arrays und std::string. Alles andere ist Bestandteil von zusätzlichen Bibliotheken.
Nimm std::string, damit kannst du nicht viel falsch machen.
-
Es gibt 3 Möglichkeiten:
const char* s1 = "Hallo, Welt"; char s2[] = "Hallo, Welt"; std::string s3 = "Hallo, Welt";
"Hallo, Welt" ist ein Stringliteral. Es wird aufgelöst zu {'H', 'a', 'l', ..., 't', '\0' }.
s1 ist jetzt ein Zeiger auf ein solches Literal. (Der Schreibzugriff auf ein Literal erzeugt undefiniertes Verhalten, daher const char*. (Gesprochen "Pointer to const char"))
s2 ist ein Array auf dem Stack. Der Inhalt des Stringliterals wird kopiert. Die Arraylänge wird automatisch bestimmt. (strlen("Hallo, Welt") + 1).
Diesen String kannst du ohne Probleme wie ein Array beschreiben. Du musst aber aufpassen, nicht über die Arraygrenzen hinauszuschreiben. Das Array kann nicht vergrößert werden. (Auch nicht verkleinert, aber du kannst den String ja mit '\0' abschließen.)s3 ist vom Typ std::string, also eine Klasse. Diese Klasse hat einen Konstruktor, der einen const char* nimmt. Hat eine Klasse einen Konstruktor mit nur einem Parameter, kann man statt den Klammern auch ein = schreiben, deswegen funktioniert das so wie oben.
std::string reserviert also ausreichend Speicher auf dem Heap, und kopiert den Inhalt dann da rein. std::string bietet außerdem weitere Funktionen, z.B. für das Anhängen, Suchen, oder sonstige Operationen.
(Zudem ist der operator [] überladen, sodass du wie auf ein Array zugreifen kannst. Achtung: Auch hier wird kein Größencheck gemacht!)Edit: Nutzen:
Falls du wirklich nur einen rohen String haben möchtest, kannst du auch const char* nehmen. Da gehen die Meinungen aber auseinander, es gibt auch Leute, die lieber std::string sehen. Einfluss hat das insbesondere auf Dinge wievoid foo1(const char* s) { some_c_api_function(s); } // vs void foo2(const std::string& s) { some_c_api_function(s.c_str()); } int main() { std::string s2 = "Hallo, Welt"; // Diese Aufrufe unterscheiden sich kaum, was Performance angeht. f1(s2.c_str()); f1("Hallo, Welt"); // Diese schon f2(s); // Kein Problem, da per Referenz übergeben, wie oben. f1("Hallo, Welt"); // Hier wird ein neuer std::string erstellt. }
Ich persönlich finde es in so einem Fall schöner, gleich einen const char* zu nutzen. Man hat immer die "bessere" Performance, aber auch wenn die egal ist, braucht man den <string> Header nicht.
Falls du allerdings etwas mit dem String machen möchtest (insbesondere vergrößern oder verkleinern) bietet ein std::string einfach mehr Komfort und damit mehr Sicherheit als C Funktionen, und ist deshalb zu bevorzugen.
Wenn man lange Texte aus Dateien liest o.Ä. ist der std::string auch zu bevorzugen, weil der Speicher auf dem Heap wesentlich größer als der auf dem Stack ist. Allgemein würde ich den std::string bei Benutzereingaben einem rohen Array gegenüber deswegen bevorzugen, weil dieser von den Funktionen beliebig vergrößert/kleinert werden kann, was halt ganz praktisch ist, wenn man nicht gerade die maximale Performance braucht.
-
Danke für die ausführliche Erklärung!
-
Eine Frage hab ich noch.
cooky451 schrieb:
s1 ist jetzt ein Zeiger auf ein solches Literal. (Der Schreibzugriff auf ein Literal erzeugt undefiniertes Verhalten, daher const char*. (Gesprochen "Pointer to const char"))
Wann kommt es denn zu dem undefinierten Verhalten? Wenn ich jetzt sowas
char * s = "123"; cout << s << endl; s = "000"; cout << s << endl; s = "1234"; cout << s << endl; s = ""; cout << s << endl; s = "asdfasdfasdf"; cout << s << endl; s = "123"; cout << s << endl;
mache, passiert nichts. Auf der Konsole werden die strings alle richtig angezeigt ...
-
Du veränderst doch bloß den Zeiger s. Mit dem darfst du machen was du willst. Etwas wie
s[0]='b';
wäre undefiniert. Verwechsel nicht Zeichenkettenliterale mit Zeigern auf diese.
-
Warum verwendet ihr die Vokabel "undefiniert" und nicht "illegal" oder "Laufzeitfehler"? "undefiniert" hört sich so an als ob zur Laufzeit vielleicht was schief gehen könnte.
In meinem Codebeispiel ändert
s
immer wieder sein Literal worauf es zeigt. Was passiert mit den strings? Werden die alten Strings automatisch vom Speicher entfernt oder kommt es zum "Speicherschmutz"?
-
inc7 schrieb:
Warum verwendet ihr die Vokabel "undefiniert" und nicht "illegal" oder "Laufzeitfehler"? "undefiniert" hört sich so an als ob zur Laufzeit vielleicht was schief gehen könnte.
Weil es so ist. Es ist undefiniert. Es könnte nichts passieren, das Programm könnte abstürzen, oder es könnte online gehen und für dich 20 mal Pizza Diablo bestellen.
inc7 schrieb:
In meinem Codebeispiel ändert
s
immer wieder sein Literal worauf es zeigt. Was passiert mit den strings? Werden die alten Strings automatisch vom Speicher entfernt oder kommt es zum "Speicherschmutz"?Stringliterale liegen im statischen Speicher, deine Zuweisungen ändern daran nichts.
-
Dann müssten es doch Umstände dafür geben. Wovon ist es abhängig ob es klappt oder nicht?
Was meinst du mit statischem Speicher? Code-Speicher?
-
inc7 schrieb:
Dann müssten es doch Umstände dafür geben. Wovon ist es abhängig ob es klappt oder nicht?
Es ist undefiniert.
inc7 schrieb:
Was meinst du mit statischem Speicher? Code-Speicher?
Es gibt drei mögliche "Lebensräume" für Speicher in C++:
- Automatischer Speicher: Nur innerhalb des Scopes gültig. (Oft als Stack implementiert.)
- Dynamischer Speicher: Gültig bis du ihn wieder frei gibst. (Oft als Heap implementiert.)
- Statischer Speicher: Nach dem erreichen des Scopes (bei globalen Variablen nach dem erreichen von main()) und ab dann die gesamte Laufzeit hindurch gültig.
-
inc7 schrieb:
Dann müssten es doch Umstände dafür geben. Wovon ist es abhängig ob es klappt oder nicht?
Das hängt von der Platform, dem Betriebsystem und den Umständen ab. Wenn du z.B. auf eine ungültige Arrayposition schreibend zugreifst, kann unter anderem folgendes passieren:
Wenn das Array auf dem Stack liegt, schreibst du an eine Stackposition, die nicht mehr zum Array gehört. Es kann sein, dass dahinter zufällig Platz ist, dann funktioniert das, und du kannst es sogar später wieder auslesen. Es kann sein, dass dort irgendeine andere Variable liegt, dann wird die überschrieben. Oder du überschreibst damit die Rücksprungadresse, dann wird es ganz lustig. Oder irgend etwas anderes, je nachdem, wie der Stack aufgebaut ist.
Wenn das Array auf dem Heap liegt, wird bei der Reservierung meist eine ganze Seite angefordert, so dass der Zugriff da meist funktioniert, wenn du nicht weit drüber bist. Auch hier können aber auch andere Variablen oder Adressen oder anderes überschrieben werden. Oder du bist über der Seite hinaus, dann bekommst du meist einen Speicherzugriffsfehler.
Wenn das Array im statischen Speicher liegt, bekommst du schreibend fast immer einen Zugriffsfehler, Lesezugriffe können funktionieren, dann bekommst du eben den Wert, der zufällig an dieser Adresse steht.Es kann also funktionieren, es muss nicht, das Programm kann abstürzen oder auch nicht, es kann sich bei unterschiedlichen Arrays unterschiedlich verhalten, es kann sich auf unterschiedlichen Systemen unterschiedlich verhalten. Der Standard definiert das Verhalten nicht, damit der Compiler nicht extra Code generieren muss, sondern das dem System überlassen kann. Dass das Programm Pizza bestellt, ist eher unwahrscheinlich, trotzdem darfst du kein definiertes Verhalten erwarten.
-
Danke für die ausführliche Antwort und guten Hunger!