C++ Kurs

String Konvertierungen

Die Themen:

Einleitung
ASCII nach binär
Fehler bei der Binär-Konvertierung
Binär nach ASCII
String zerlegen
Beispiel und Übung

Einleitung

Das Einlesen von Daten von der Tastatur eine recht komplizierte Sache, insbesondere dann, wenn man alle Fehleingaben abfangen möchte. Der sicherste Weg zum Einlesen von Daten führt deshalb meistens über das Einlesen einer kompletten Zeile und deren anschließender Auswertung. Hierbei können dann ASCII-Zahlen in binäre Zahlen konvertiert werden oder auch umgekehrt.

Zur Konvertierung von Strings in binäre Zahlen 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

Den Eingabestream cin haben Sie ja schon kennen gelernt. Er liest im Prinzip Daten von der Tastatur ein und legt die Eingaben dann in den entsprechenden Variablen ab, d.h. auch cin führt intern eine Umwandlung von 'ASCII-Zahlen' in binäre Zahlen (so wie sie der Rechner verarbeitet) durch. Um nun einen String 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 genau diese Aufgabe erfüllt der Stream istringstream. Wenn Sie den Stream istringstream einsetzen müssen Sie die Header-Datei sstream einbinden. Um nun einen String in seine 'Einzelteile' zu zerlegen, definieren Sie zunächst ein istringstream Objekt. Anschließend übergeben Sie dem Objekt mittels der Memberfunktion str(...) den zu zerlegenden String. Und ab diesem Zeitpunkt verhält sich der istringstream genauso wie der Eingabestream cin, d.h. Sie extrahieren mithilfe des Operators >> die einzelnen Wörter bzw. Werte aus diesem Stream. Im nachfolgenden Beispiel enthält danach die Variable intVar1 den Wert 123, intVar2 den Wert 456 und dblVar den Wert 3.4. 


// Header-Datei für istringstream einbinden
#include <sstream>
...
// Stream-Objekt definieren
std::istringstream is;
// string definieren und initalisieren
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 Sie im Programm nun mehrere Strings zu konvertieren haben, so wird Ihr erster Versuch meistens so verlaufen, dass Sie dem bereits bestehenden Stream-Objekt mittels der Memberfunktion str(...) einfach einen neuen String übergeben und diesen dann in der gleichen Art und Weise auswerten wollen. Dies wird mit (an Sicherheit grenzender Wahrscheinlichkeit) fehlschlagen. Der Grund dafür liegt darin, dass nach dem kompletten Auslesen eines Strings der Stream istringstream den Status EOF (=End Of File) besitzt und damit sich quasi in einem Fehlerzustand befindet. Um diesen Status aufzuheben, rufen Sie nach der Auswertung des ursprünglichen Strings die Memberfunktion clear() des Stream-Objekts auf. Danach ist das Stream-Objekt 'wie neu' und kann erneut verwendet werden.

Da istringstream und cin außerdem vom gleichen Grundstream abstammen, können Sie beim istringstream auch die gleichen Manipulatoren verwenden wie beim Eingabestream cin. In letzter Konsequenz heißt dies, dass Sie mit dem istringstream genau das Gleiche erreichen können, was Sie auch mit dem Eingabestream cin können, nur dass die 'Daten' nun aus einem String stammen und nicht von der Tastatur kommen. Nachfolgend sehen Sie ein Beispiel, bei dem Hex-Zahlen aus einem String ausgelesen werden. Beachten Sie dabei, dass der Hex-Präfix 0x.. im String dabei optional vorhanden sein darf.


// 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; 

80 16

Wenn Sie wollen, können Sie einem istringstream Objekt bei seiner Definition auch gleich einen String übergeben. Selbstverständlich kann diesem Stream-Objekt jederzeit ein weiterer String übergeben, wenn Sie, wie vorher erwähnt, den EOF-Status des Streams mittels clear() aufheben.


// 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 u.a 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 (mit dem der Stream initialisiert wird) 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 wird im Beispiel mittels einer if-Anweisung ausgewertet.


// 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 obigen Beispiel finden Sie übrigens ebenfalls die bereits erwähnte string Memberfunktion str(), diesmal jedoch ohne Parameter. Wird str() ohne Parameter aufgerufen so liefert sie Ihnen einen Verweis auf den Eingabepuffer des istringstream-Objekts. Über diesen Verweis können Sie dann z.B. den Inhalt des Eingabepuffers ausgeben.

Enthä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 String 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 einer 'ASCII-Zahl' einen Text enthält. Auch hier liefert die Memberfunktion fail() den Wert true zurück, der dann entsprechend ausgewertet werden kann.


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

Und weiter geht's mit den Fehlern. Wieder soll versucht werden, eine 'ASCII-Zahl' nach binär zu konvertieren. Der String enthält nun aber am Anfang zwar 'ASCII-Zahlen', jedoch folgt unmittelbar darauf ein Text. In diesem Fall hilft uns die Memberfunktion fail() nicht weiter, da die Konvertierung zunächst erfolgreich ist; es werden die 'ASCII-Zahlen' am Anfang des Strings richtig 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 alles konvertiert werden konnte, d.h. keine Reste im Stream 'stehen geblieben' sind.


// 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 sehen noch ein weiteres Beispiel (diesmal ohne Fehler), 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 müssen Sie aber den Stream mittels clear() zurücksetzen wenn Sie ihn weiter verwenden wollen.


// 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 als String ablegt.

Und genau diese Aufgabe erfüllt der Stream ostringstream. Wenn Sie den Stream ostringstream einsetzen müssen Sie wieder die gleiche Header-Datei sstream einbinden wie beim istringstream. Um Daten in einen String zu konvertieren, definieren Sie zunächst ein ostringstream Objekt. Anschließend können Sie dann die Daten genauso wie beim cout-Stream 'ausgeben', nur dass die Daten jetzt nicht auf die Standardausgabe gelangen sondern im Stream verbleiben. Um auf den Inhalt des Streams (das ist der entsprechende String mit den konvertierten Daten) zuzugreifen, verwenden Sie ebenfalls die von istringstream bekannte Memberfunktion str().


// 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 und zuerst einmal an, wie eine Eingabe, die aus mehreren Wörtern bestehen kann, in einzelne Strings zerlegt wird. Nach dem Einlesen der Zeile mittels der Funktion getline(...) wird ein istringstream Objekt definiert, das mit dem eingelesenen String (Eingabezeile) initialisiert wird. Wie Sie bereits erfahren haben, kann mithilfe eines solchen istringstream Objekts ein String in seine 'Einzelteile' zerlegt werden. Der Unterschied zu den vorherigen Beispielen ist, dass nun der String nicht in binäre Daten konvertiert wird sondern in einzelne string Objekte. Um den kompletten eingelesenen String in einzelne Wörter zu zerlegen, wird so lange aus dem istringstream ausgelesen, bis dessen Memberfunktion eof() false zurückliefert. Die einzelnen Wörter werden dann mittels des Operators >> in ein string Objekt geschrieben. Wenn Sie wollen, können Sie das Beispiel auch einmal in Ihren Compiler übernehmen und laufen lassen.


//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;
   }

Mithilfe des istringstream können aber auch zum Beispiel Teile eines Strings, die durch ein frei definierbares Trennzeichen voneinander getrennt sind, extrahiert werden. Der 'Trick' ist dabei Folgender:

Die Funktion getline(...) wurde bisher nur zum Einlesen einer kompletten Zeile verwenden. Aber getline(...) kann eigentlich noch wesentlich mehr. getline(...) liest zunächst aus einem beliebigen Input-Stream eine Zeile aus, d.h. die Funktion getline(...) kann sowohl vom Eingabestream cin wie auch vom Dateistream ifstream oder sogar vom istringstream Stream eine Zeile auslesen.

Aber das ist für unserem Fall nur die halbe Miete; wir wollen ja nicht eine ganze Zeile in einem String ablegen sondern vorerst nur den Teil bis zum ersten Trennzeichen. Und dazu kann an getline(...) im dritten Parameter das Trennzeichen mitgegeben werden, bis zu dem einlesen werden soll. Standardmäßig ist dies 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.


// Zu untersuchende Zeile, Trennzeichen ist hier das Komma
const char* const pLINE = "Agathe Mueller,Gansweg 23,12345 ADorf";
// 3 string Objekte definieren
string name, street, city;
// Zeile in einenm istringstream übertragen
istringstream is(pLINE);
// 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 Übung

Beispiel:

Vorgegeben ist eine 'Datenbank'-Datei adresse.csv mit folgendem Inhalt:

Xaver;Xelsbrot;Xaver@speedy.de;Marmeladen Allee 5; 12345; AStadt
Gustav;Gans;guga@entennet.com;Gluecksstrasse 123; 99999; BDorf
Daisy;Duck;ducky@entennet.com;Duckweg 1; 99999; BDorf
Emilie;Lehmann;emmi@world.de;Allerwelts Gasse 55; 76543; CWeiher

Aus dieser Datei werden die Namen und die Email-Adressen herausgesucht und ausgegeben.

Dazu werden die einzelnen 'Datensätze' zeilenweise eingelesen. Aus der eingelesenen Zeile werden dann über den istringstream die durch Semikolon getrennten Datenfelder extrahiert.

Wenn Sie das Beispiel ausführen wollen, legen Sie sich im Root-Verzeichnis des VC++ Projekts die Datei adresse.csv mit obigem Inhalt an.

Name: Xaver Xelsbrot EMail: Xaver@speedy.de
=========================================
Name: Gustav Gans EMail: guga@entennet.com
=========================================
Name: Daisy Duck EMail: ducky@entennet.com
=========================================
Name: Emilie Lehmann EMail: emmi@world.de
=========================================


// Beispiel zur String-Konvertierungen
#include <iostream>
#include <fstream>
#include <string>
#include <sstream>

using std::cout;
using std::endl;
using std::string;
using std::getline;

// Konstante für Dateiname
const char* const pFILENAME = "adresse.csv";

// main() Funktion
int main()
{
   // Eingabedatei öffnen
   std::ifstream inFile;
   inFile.open(pFILENAME);
   if (!inFile)
   {
      cout << "Datei " << pFILENAME << " kann nicht geöffnet werden!" << endl;
      exit(1);
   }

   // Komplette Datei bearbeiten
   do
   {
      // Zeile aus der Datei
      string line;
      // Zu extrahierende 'Felder' aus der Dateizeile
      string firstname, lastname; string email;
      // Konvertierungsstream
      std::istringstream is;

      // Zeile einlesen
      getline(inFile,line);
      // Bei erreichen des Dateiendes den Rest überspringen
      if (inFile.eof())
         continue;
      // Eingelesene Zeile in Konvertierungsstream übertragen
      is.str(line);
      // Nun Name und Email-Adresse aus der Zeile extrahieren
      getline(is,firstname,';');
      getline(is,lastname,';');
      getline(is,email,';');
      // und ausgeben
      cout << "Name: " << firstname << ' ' << lastname << '\n';
      cout << "EMail: " << email << '\n';
      cout << "=========================================" << endl;
   } while (!inFile.eof());

   // Datei wieder schliessen
   inFile.close();
}

Übung:

Ihre Aufgabe ist es nun, von der Tastatur einen int-Wert und einen double-Wert einzulesen und dabei eventuelle Fehleingaben abzufangen. Wurden die Werte richtig eingegeben, so sind diese dann um 10 erhöht auszugeben. War die Eingabe fehlerhaft, so sind entsprechende Meldungen auszugeben.

Testen Sie Ihr Programm mit dem folgenden, türkis dargestellten, Eingaben. lconv ist im Beispiel das Name des Programms.

lconv
Bitte int und double Wert eingeben: 12 1.2
Die Werte um 10 erhöht sind: 22 und 11.2

lconv
Bitte int und double Wert eingeben: 1.2 12
Zuviele Eingaben! 1.2 12

lconv
Bitte int und double Wert eingeben: 1 emil
Falsche Eingabe! 1 emil

lconv
Bitte int und double Wert eingeben: 1a 1.2
Falsche Eingabe! 1a 1.2

lconv
Bitte int und double Wert eingeben: 1 1.2 emil
Zuviele Eingaben! 1 1.2 emil

Hinweis: Die Eingaben sind türkis dargestellt. 

Lösung ansehen!