C++ Kurs

String Konvertierungen

Die Themen:

Das Einlesen von Daten von der Standard-Eingabe kann recht kompliziert werden wenn Fehleingaben abfangen werden sollen. Der sicherste Weg zum Einlesen von Daten führt deshalb in der Regel über das Einlesen einer kompletten Zeile und deren anschließender Auswertung. Hierbei können dann die 'ASCII-Daten' in binäre Daten konvertiert werden.

HinweisZur Konvertierung von C-Strings in binäre Daten gibt es auch entsprechende C-Funktionen, wie z.B. atoi(...) oder atof(...), die wir aber im Verlaufe des Kurses nicht weiter verwenden. Aus diesem Grunde wird hier auf deren Beschreibung verzichtet. Wenn Sie diese Funktionen einsetzen wollen, sehen bitte in der Online-Hilfe ihres Compilers nach.

ASCII nach binär

Wie bekannt, liest der Eingabestream cin Daten von der Standard-Eingabe (i.d.R. Tastatur) und legt diese dann in entsprechenden Variablen ab, d.h. cin führt intern eine Umwandlung von 'ASCII-Daten' in binäre Daten durch. Um nun eine Eingabezeile, welche als String eingelesen wurde, in seine 'Einzelteile' zu zerlegen, benötigen wir fast die gleiche Funktionalität wie cin, nur dass die Eingabe jetzt nicht über die Tastatur erfolgt sondern als String vorliegt.

Und diese Aufgabe erfüllt der Stream istringstream. Wenn der Stream istringstream eingesetzt wird, muss zunächst dessen Header-Datei sstream eingebunden werden. Um den String dann in seine 'Einzelteile' zu zerlegen, ist zunächst ein istringstream Objekt zu definieren, dem anschließend mittels der Memberfunktion str(...) der zu zerlegenden String übergeben wird. Und ab diesem Zeitpunkt verhält sich der istringstream genauso wie der Eingabestream cin, d.h. mit Hilfe des Operators >> werden die einzelnen Wörter bzw. Werte aus diesem Stream extrahiert. Im nachfolgenden Beispiel enthält am Ende die Variable intVar1 den Wert 123, intVar2 den Wert 456 und dblVar den Wert 3.4.

PgmHeader
// Header-Datei für istringstream einbinden
#include <sstream>
...
// Stream-Objekt definieren
std::istringstream is;
// string definieren und initialisieren
std::string s1 = "123 456 3.4";
// String dem Stream-Objekt uebergeben
is.str(s1);
// Variablen für die Konvertierung definieren
int intVar1, intVar2;
double dblVar;
// String nach binär konvertieren
is >> intVar1 >> intVar2 >> dblVar;

Soweit der 'Trivialfall'. Wenn in einem Programm nun mehrere Strings nacheinander zu konvertieren sind, so wird der erster Versuch meistens so verlaufen, dass dem bereits bestehenden Stream-Objekt mittels der Memberfunktion str(...) einfach ein neuer String übergeben und dieser dann in der gleichen Art und Weise ausgewertet wird. Dies wird aber fehlschlagen. Der Grund dafür liegt darin, dass nach dem ersten kompletten Abarbeiten des Strings der Stream istringstream den Status EOF (gleich End Of File) besitzt und sich damit quasi in einem Fehlerzustand befindet. Um diesen Status zurückzusetzen, ist nach der Auswertung des ursprünglichen Strings die Memberfunktion clear() des Stream-Objekts aufzurufen. Danach ist das Stream-Objekt zurückgesetzt und kann erneut verwendet werden.

Da istringstream und cin außerdem vom gleichen Basisstream abstammen, können beim istringstream auch die gleichen Manipulatoren verwendet werden wie beim Eingabestream cin. In letzter Konsequenz heißt dies, dass mit dem istringstream genau das Gleiche erreicht werden kann wie mit dem Eingabestream cin, nur dass die 'Daten' aus einem String stammen und nicht von der Tastatur kommen. So werden im nachfolgenden Beispiel Hex-Zahlen aus einem String ausgelesen und in Variablen abgelegt. Das Hex-Präfix 0x.. kann dabei im String optional vorhanden sein.

PgmHeader
// Header-Datei für istringstream einbinden
#include <sstream>
...
// Stream-Objekt definieren
std::istringstream is;
// String dem Stream-Objekt übergeben
is.str("0x50 10");
// String nach binär konvertieren
int var1, var2;
is >> hex >> var1 >> var2;
cout << var1 << ' ' << var2; 
Programmausgabe 80 16

Und auch ein istringstream Objekt kann bei seiner Definition gleich mit einen (C-)String initialisiert werden. Selbstverständlich kann diesem Stream-Objekt jederzeit ein weiterer String übergeben werden, wenn vorher der Stream mittels clear() zurückgesetzt wird.

PgmHeader
// Header-Datei für istringstream einbinden
#include <sstream>
...
// Stream-Objekt definieren und initialisieren
istringstream is("12 13");
// String nach binär konvertieren
int var1, var2;
is >> var1 >> var2;
// Status des Stream-Objekts zurücksetzen
is.clear();
// Neuen String dem Stream-Objekt zuweisen
is.str("22 33");
...

Fehler bei der Binär-Konvertierung

Der Stream istringstream besitzt unter anderem die beiden Memberfunktionen fail() und eof(), mit deren Hilfe Fehler bei der Auswertung des Strings festgestellt werden können.

Fangen wir mit dem einfachsten Fehlerfall an, dass zu wenige Daten im String enthalten sind. Im Beispiel unten enthält der String nur einen 'Wert', während anschließend versucht wird, aus dem Stream zwei Werte auszulesen. In diesem Fall liefert die Memberfunktion fail() den Wert true, da dies als Fehler interpretiert wird. Dieser Rückgabewert von fail() wird dann mittels einer if-Anweisung ausgewertet.

PgmHeader
// Header-Datei für istringstream einbinden
#include <sstream>
...
// Stream-Objekt definieren und initialisieren
istringstream is("10");
// Konvertieren nach binär
int var1, var2;
is >> var1 >> var2;
// Fehlerabfrage
if (is.fail())
{
   // Fehlerbehandlung
   cout << "Fehler: " << is.str() << endl;
   ...
}

Im Beispiel wird übrigens die bereits erwähnte string Memberfunktion str() aufgerufen, diesmal jedoch ohne Parameter. Wird str() ohne Parameter aufgerufen, so liefert sie eine Kopie des aktuellen Inhalts des istringstream.

HinweisEnthält der String zu viele 'Werte', so werden die überzähligen Werte einfach im String belassen und mit der nächsten Anweisung verarbeitet, die aus dem Stream ausliest. Eine 'Fehlermeldung' erfolgt hierbei also nicht. Das Verhalten ist also genau gleich wie beim Eingabestream cin.

Sehen wir uns den nächsten Fehlerfall an, dass der Stream anstelle eines 'ASCII-Werts' einen Text enthält. Und auch hier liefert die Memberfunktion fail() den Wert true zurück, der dann entsprechend ausgewertet werden kann.

PgmHeader
// Stream-Objekt definieren und initialisieren
istringstream is("Emil");
// Versuch der Konvertierung nach binär
int var;
is >> var;
if (is.fail())
   ...   // Fehlerbehandlung

Sehen wir uns einen weiteren Fehlerfall an, bei welchem am Anfang des Strings zwar 'ASCII-Ziffern' stehen, jedoch unmittelbar darauf ein Text folgt. In diesem Fall hilft uns die Memberfunktion fail() nicht weiter, da die Konvertierung zunächst erfolgreich ist, denn es werden die 'ASCII-Ziffern' am Anfang des Strings korrekt nach binär konvertiert. Um nun feststellen zu können, ob der komplette String konvertiert wurde, wird die Memberfunktion eof() eingesetzt. Sie liefert nur dann true zurück, wenn der komplette Inhalt des Streams konvertiert werden konnte, d.h. keine Reste im Stream verblieben sind.

PgmHeader
// Stream-Objekt definieren und initialisieren
istringstream is("12Emil");
// Konvertieren nach binär
int var; is >> var;
if (is.eof()==false)
   ...  // Fehlerbehandlung
// Stream zurücksetzen
is.clear(); 

Nachfolgend folgt noch ein weiteres Beispiel, das aus einem String eine Zahl nach der anderen nach binär konvertiert. Tritt während dieser Konvertierung ein Fehler auf, so liefert die Memberfunktion fail() true zurück und die Konvertierungsschleife wird verlassen. Ebenfalls beendet wird die Schleife, wenn alle 'ASCII-Zahlen' konvertiert wurden, da in diesem Fall eof() true zurückliefert. Nach dem Verlassen der Schleife kann durch erneutes abprüfen des Streamstatus festgestellt werden, ob die Schleife durch einen Fehler oder durch Erreichen des String-Endes verlassen wurde. In beiden Fällen muss aber der Stream mittels clear() zurücksetzt werden wenn er weiter verwendet wird.

PgmHeader
// Stream-Objekt definieren und initialisieren
istringstream is("11 22 33");

int var;
// Kompletten String nach binär konvertieren
while (is.eof() == false)
{
   is >> var;
   if (is.fail())
      break;
   cout << var << ',';
}
// Falls Fehler aufgetreten
if (is.fail())
   ...  // Fehlerbehandlung
// Stream zurücksetzen
is.clear(); 

Binär nach ASCII

Sehen wir uns nun den umgekehrten Fall, die Konvertierung von binär nach ASCII. Auch diese Konvertierung kennen Sie prinzipiell schon durch den Ausgabestream cout. Er konvertiert für die Ausgabe binäre Daten nach ASCII. Wir müssen nun nur noch einen Stream finden, der die Daten anstatt auf die Standardausgabe auszugeben diese in einem String ablegt.

Und diese Aufgabe erfüllt der Stream ostringstream. Auch für diesen Stream ostringstream muss die gleiche Header-Datei sstream eingebunden werden wie für den istringstream. Um Daten dann in einen String zu konvertieren, ist zunächst wiederum ein ostringstream Objekt zu definieren, in das anschließend die Daten genauso wie beim cout-Stream übertragen werden, nur dass die Daten jetzt nicht auf die Standardausgabe gelangen sondern im Stream abgelegt werden. Um auf den Inhalt des Streams (das ist der entsprechende String mit den konvertierten Daten) zuzugreifen, ist ebenfalls die von istringstream bekannte Memberfunktion str() aufzurufen.

PgmHeader
// Header-Datei für ostringstream einbinden
#include <sstream>
...
// Stream-Objekt definieren
ostringstream os;
// Daten in Stream übertragen
int var = 12;
os << "Wert" << var << '\n';
// Streaminhalt ausgeben
cout << os.str(); 

String zerlegen

Sehen wir uns zuerst einmal an, wie eine Eingabe, die aus mehreren Wörtern bestehen kann, in einzelne Strings zerlegt wird. Nach dem Einlesen einer Zeile mittels der Funktion getline(...) wird ein istringstream Objekt definiert, welches mit dem eingelesenen String (Eingabezeile) initialisiert wird. Wie bereits erwähnt, kann mit Hilfe eines solchen istringstream Objekts ein String in seine 'Einzelteile' zerlegt werden. Der Unterschied zu den vorherigen Beispielen ist nun, dass der Inhalt des Streams nicht in binäre Daten konvertiert sondern in einzelne string Objekte zerlegt wird. Um den gesamten eingelesenen String in einzelne Wörter zu zerlegen, wird so lange aus dem istringstream ausgelesen, solange dessen Memberfunktion eof() false zurückliefert. Die einzelnen ausgelesenen Wörter werden dann mittels des Operators >> in ein string Objekt übertragen. Wenn Sie wollen, können Sie das nachfolgende Beispiel auch einmal übersetzen und laufen lassen.

PgmHeader
//Header-Dateien einbinden
#include <iostream>
#include <string>
#include <sstream>

using namespace std;

// main() Funktion
int main()
{
   // Stringobjekte definieren
   string line, word;

   // Komplette Zeile von der Standard-Eingabe einlesen
   getline(cin,line);
   // Eingabe an istringstream übergeben
   istringstream is(line);

   // Eingabe zerlegen
   while (!is.eof())
   {
      // Einzelnes Wort aus Eingabe-String extrahieren
      is >> word;
      cout << word << endl;
   }

istringstream kann aber auch noch mehr. So können Teile in einem String, die durch ein frei definierbares Trennzeichen voneinander getrennt sind, einzeln extrahiert werden. Der 'Trick' ist dabei Folgender:

Die Funktion getline(...) wurde bisher nur zum Einlesen einer kompletten Zeile von der Standardeingabe verwendet. Doch kann sie noch wesentlich mehr. getline(...) kann aus einem beliebigen Input-Stream eine komplette Zeile auslesen, d.h. die Funktion getline(...) kann sowohl vom Eingabestream cin als auch vom Dateistream ifstream oder sogar vom istringstream Stream eine Zeile auslesen.

Das ist für unseren Fall schon einmal die halbe Miete. Aber wir wollen ja nicht eine ganze Zeile in einem String ablegen, sondern nur den Teil bis zum ersten definierten Trennzeichen. Und dazu kann an getline(...) im dritten Parameter ein Trennzeichen mitgegeben werden. Standardmäßig ist das Trennzeichen der Zeilenvorschub '\n'. Wir brauchen nun anstelle des Zeilenvorschubs nur noch das gewünschte Trennzeichen im dritten Parameter angeben, und das war's dann auch schon. Zugegeben, das ist eigentlich schon etwas für Fortgeschrittene, aber Sie sehen, wenn man sich etwas intensiver mit Strings und Streams befasst kann einem das eine Menge Arbeit sparen.

PgmHeader
// Zu untersuchende Zeile, Trennzeichen ist hier das Komma
const string LINE = "Agathe Mueller,Gansweg 23,12345 ADorf";
// 3 string Objekte definieren
string name, street, city;
// Zeile in einen istringstream übertragen
istringstream is(LINE);
// Aus istringstream einlesen mit Komma als Trennzeichen
getline(is,name,',');
// Weiter einlesen bis zum nächsten Trennzeichen
getline(is,street,',');
// Nun den Rest einlesen
getline(is,city);
...

Beispiel und ÜbungUnd hier geht's zum Beispiel und zur Übung.