Für die Ein- und Ausgabe sollten nicht die
stdio-Funktionen wie putchar(), printf() etc.
genutzt werden sondern die im folgenden beschriebene Bibliothek.
Für Ein- und Ausgabe bietet C++ die sogenannten Streams an. Ein
großer Vorteil der Streams ist, dass die Ein- und Ausgabe über die
überladenen Operatoren >> und <<
realisiert wird, womit eine übersichtliche Programmierung und eine
einfache Erweiterung für benutzerdefinierte Datentypen ermöglicht wird.
Im einfachsten Fall werden Sie die Ausgabe auf den Bildschirm und die
Eingabe von der Tastatur benötigen. Dazu stellt C++ die Klassen
istream und ostream sowie die Objekte
| Objekt | Verwendung |
|---|---|
cin |
Standardeingabe (Tastatur) |
cout |
Standardausgabe (Bildschirm) |
cerr |
Fehlerausgabe (Bildschirm) |
zur Verfügung. Um die IO-Streams zu benutzen, müssen Sie die Bibliothek über
#include <iostream>einbinden (ohne Endung .h).
Eine Ausgabe erfolgt durch die Anweisung:
std::cout << object;Mehrere Ausgaben können auch direkt miteinander verbunden werden:
std::cout << object << object << ... << object;Der Operator << ist bereits für alle eingebauten
Datentypen definiert, die Ausgabe erfolgt immer entsprechend des
jeweiligen Objekts.
Beispiel:
#include <iostream>
#include <iomanip>
#include <fstream>
double x = 25.391;
int i = 10;
char s[] = "Test";
std::cout << "Ausgabe im Terminal:\n";
std::cout << "Dies ist die Zahl x: " << x << std::endl;
std::cout << "Dies ist ein " << s << " mit i = " << i << std::endl;
std::endl steht für neue Zeile, stattdessen kann auch
\n innerhalb eines Strings verwendet werden. Bei Verwendung
von \n werden Ausgaben erst angezeigt, wenn das Programm
endet oder std::flush aufgerufen wird.
Die Stream-Klassen bieten folgende Methoden zur Manipulation der Ausgabe:
| Methode | Verwendung |
|---|---|
width() |
Lesen/Setzen der Feldbreite für Ausgabe |
fill() |
Lesen/Setzen des Füllzeichens für Ausgabe |
flags() |
Lesen/Setzen der Flags für Ausgabe |
setf() |
Setzen von einzelnen Flags |
unsetf() |
Löschen von einzelnen Flags |
precision() |
Nachkommastellen bei Fließkommaausgabe |
Beachte: Das Setzen der Feldbreite hat nur Einfluss
auf das nächste Ausgabeelement. Ohne Angabe eines Parameters liefern
width(), fill(), flags() und precision()
jeweils den aktuellen Wert.
Für die Ausgabeformatierung stehen folgende Flags zu Verfügung:
| Flag | Verwendung |
|---|---|
ios::left |
Linksbündige Ausgabe |
ios::right |
Rechtsbündige Ausgabe |
ios::internal |
Vorzeichen linksbündig, Wert rechtsbündig |
ios::dec |
Dezimale Ausgabe |
ios::oct |
Oktale Ausgabe |
ios::hex |
Hexadezimale Ausgabe |
ios::showbase |
Ausgabe der Zahlenbasis |
ios::showpoint |
Ausgabe aller Nachkommazahlen, auch wenn 0 |
ios::showpos |
Ausgabe von + vor positiven Werten |
ios::uppercase |
"E" und "X" statt "e" und "x" |
ios::scientific |
Darstellung: 1.2345e67 |
ios::fixed |
Darstellung: 1234.56 |
ios::unitbuf |
Ausgabe nach jeder Operation |
ios::stdio |
Ausgabe nach jedem Zeichen |
Als Masken für setf() stehen außerdem zur Verfügung:
| Flag | Verwendung |
|---|---|
ios::basefield |
Alle Zahlenbasis-Flags |
ios::adjustfield |
Alle Ausrichtungs-Flags |
ios::floatfield |
Alle Fließkomma-Flags |
Beispiele für die Anwendung der Methoden:
double x = 19.275;
int i = 125;
std::cout.width(6);
std::cout.precision(3);
std::cout.setf(std::ios::fixed, std::ios::floatfield);
std::cout << x << std::endl;
std::cout.width(10);
std::cout.precision(5);
std::cout.setf(std::ios::fixed, std::ios::floatfield);
std::cout << x << std::endl;
std::cout.width(10);
std::cout << i << std::endl;
std::cout.width(6);
std::cout << i << std::endl;
Um das Ganze etwas zu vereinfachen, existieren die sogenannten
IO-Manipulatoren, die das Setzen der Ausgabeparameter direkt als
Ausgabeelemente über << ermöglichen. Sie sind in
iomanip definiert. Um sie zu verwenden, muss
#include <iomanip>eingebunden werden. Die zur Verfügung stehenden IO-Manipulatoren sind:
| Flag | Verwendung |
|---|---|
setbase() |
Setzen der Zahlenbasis |
setfill() |
Setzen des Füllzeichens für die Ausgabe |
setprecision() |
Nachkommastellen bei Fließkommaausgabe |
setw() |
Setzen der Feldbreite für die Ausgabe |
setiosflags() |
Setzen der ios-Flags |
resetiosflags() |
Rücksetzen der ios-Flags |
Die Parameter entsprechen denen der IO-Methoden. Am Beispiel ist nochmals die obige Ausgabe realisiert:
double x = 19.275;
int i = 125;
std::cout << std::setw(6) << std::setprecision(3)
<< std::setiosflags(std::ios::fixed) << x << std::endl;
std::cout << std::setw(10) << std::setprecision(5)
<< std::setiosflags(std::ios::fixed) << x << std::endl;
std::cout << std::setw(10) << i << std::endl;
std::cout << std::setw(6) << i << std::endl;
Die Parameter sind voneinander unabhängig . Das bedeutet zum
Beispiel, dass man für einen Wechsel von links- auf rechtsbündige
Ausgabe left zurücksetzen und
right setzen muss. Ansonsten ist das Resultat nicht
vorhersehbar.
Möchte man den Wert einer Variablen einlesen, so existiert dafür der
>>-Operator. Dieser funktioniert analog zur
Ausgabe:
cin >> variable;Die Eingabe muss mit der Taste Enter abgeschlossen werden.
Ausgewertet wird die Eingabe für jede Variable soweit, wie die gelesenen
Zeichen zum Typ der Variable passen: z.B. endet die Eingabe für
int beim ersten Zeichen das keine Ziffer ist, bei
string bei einem Leerzeichen. Alle Leerzeichen innerhalb
der Eingabe werden als Trennzeichen behandelt, soweit nicht das
skipws-Flag gesetzt ist.
Ein als enum definierter Datentyp kann nicht einfach mit
dem entsprechenden Operator eingelesen oder ausgegeben werden.
Stattdessen muss eine Variable (z.B. ein int) eingelesen
werden und dann konvertiert werden. Mit einem static_cast
kann der Wert ohne Überprüfung konvertiert werden. Besser ist es, mit
einem switch die Eingabe zu prüfen:
enum class Farbe { Rot = 1, Blau = 2 };
std::cout << "Farbe? (1=Rot, 2=Blau, sonst=Rot)" << std::endl;
int i;
std::cin >> i;
Farbe f;
// oder ungeprüft: f = static_cast<Farbe>(i);
switch (i) {
case int(Farbe::Blau):
f = Farbe::Blau;
break;
case int(Farbe::Rot):
default:
f = Farbe::Rot;
break;
}
std::cout << "Farbe: " << int(f) << std::endl;
Analog zur Ausgabe, lässt sich auch die Eingabe durch Flags beeinflussen.
| Flag | Verwendung |
|---|---|
ios::skipws |
führende Leerzeichen beim Einlesen ignorieren (default) |
ios::noskipws |
führende Leerzeichen beim Einlesen nicht ignorieren |
C++ unterstützt zwei Arten von Dateien: sequentielle Dateien und Dateien mit wahlfreiem Zugriff. In diesem Praktikum werden nur erstere behandelt.
Mit sequentiellen Dateien kann auf drei Arten interagiert werden: Daten einlesen, in eine neue Datei schreiben oder an eine existierende Datei anhängen. Bei einem sequentiellen File können die Daten nicht umgeordnet werden, ohne das ganze File zu löschen.
Die Standardbibliothek zum Lesen und Schreiben von Dateien kann mit
#include <fstream>eingebunden werden.
Die Ausgabe von Daten in ein File erfolgt analog zur Ausgabe von Daten auf dem Bildschirm. Betrachten Sie zunächst folgendes Beispielprogramm:
// Testvariablen
std::string name = "Arthur";
int semester = 42;
double schnitt = 4.2;
const std::string& dateiname = "Ausgabedatei.dat";
// Öffnen des Files für die Ausgabe
std::ofstream outfile(dateiname);
// Schreibe die Daten in das File
outfile << "Name: " << name << std::endl;
outfile << "Semester: " << semester << std::endl;
outfile << "Notenschnitt: " << std::setprecision(1)
<< std::setiosflags(std::ios::fixed) << schnitt << std::endl;
Nach der Initialisierung einiger Testvariablen erzeugt das Programm
ein Objekt mit Namen outfile, welches den Datentyp
ofstream (für output file stream) hat.
ofstream ist eine Unterklasse von ostream, der
allgemeinen Klasse, für die der <<-Operator definiert
wird. Als Parameter erhält der Konstruktor eines
ofstream-Objektes den Namen des zu erzeugenden Files (hier:
Ausgabedatei.dat). Das Objekt outfile
repräsentiert nun das File, in das die Ausgabe erfolgen soll.
Selbstverständlich können Sie auch einen beliebigen anderen Namen für
das Objekt verwenden. Ganz analog zur Ausgabe auf dem Bildschirm können
die Daten nun mit Hilfe des Ausgabeoperators << in
das File geschrieben werden.
Beachten Sie, dass das File beim Aufruf des Konstruktors automatisch geöffnet wird und der Destruktor das File wieder schließt, wenn der Gültigkeitsbereich des Objektes verlassen wird.
Das Programm erzeugt bei seiner Ausführung die Datei
Ausgabedatei.dat mit folgendem Inhalt:
Name: Arthur
Semester: 42
Notenschnitt: 4.2
Das folgende Programm liest die Daten aus der Datei
Ausgabedatei.dat wieder ein und gibt sie auf dem Bildschirm
aus:
std::string name;
int semester;
double schnitt;
std::string info[3];
const std::string& dateiname = "Ausgabedatei.dat";
// Öffnen des Files zum Lesen
std::ifstream infile(dateiname);
// Lese die Daten aus dem File
infile >> info[0] >> name;
// info[0] = "Name:"; name = "Arthur";
infile >> info[1] >> semester;
// info[1] = "Semester:"; semester = 42;
infile >> info[2] >> schnitt;
// info[2] = "Notenschnitt:"; schnitt = 4.2;
// Schreiben auf den Bildschirm
std::cout << info[0] << name << std::endl;
std::cout << info[1] << semester << std::endl;
std::cout << info[2] << schnitt << std::endl;
Nach der Definition der Variablen, welche die einzulesenden Daten
aufnehmen sollen, wird ein Objekt vom Typ ifstream (für
input file stream) erzeugt, welches automatisch das gewünschte
File zum Lesen öffnet. ifstream ist eine Unterklasse von
istream, der allgemeinen Klasse, für die der
>>-Operator definiert wird. Der überladene
Eingabeoperator >> wird dann verwendet um die Daten
aus dem File in die Variablen einzulesen. Zu beachten ist hierbei, dass
Leerzeichen, Tabulatoren und Zeilenumbrüche als Trennzeichen dienen um
die einzelnen Daten im File voneinander abzugrenzen. Ein in der Datei
gespeicherter String darf also keine Leerzeichen enthalten, da er beim
Lesen sonst als zwei (oder mehr) Strings interpretiert wird. Dieses
Verhalten kann durch spezielle Funktionen geändert werden. Auch ist
darauf zu achten, dass die Datentypen der Variablen mit den Datentypen
der gelesenen Werte übereinstimmen, da es ansonsten zu einem Lesefehler
kommt. Das Programm gibt folgendes auf dem Bildschirm aus:
Name:Arthur
Semester:42
Notenschnitt:4.2
Ist die Menge der einzulesenden Daten nicht von vornherein bekannt,
so muss es eine Möglichkeit geben zu erkennen, wann das Dateiende
erreicht ist. Dies kann mittels der Funktion eof() getestet
werden.
// Variable zum Einlesen
int zahl;
// Öffnen der Datei
std::ifstream fin("Testdatei.dat"); // Lies , bis Dateiende erreicht fin >> zahl;
fin >> zahl; // Einlesen vor erstem eof Test
while (!fin.eof())
{
std::cout << "Gelesene Zahl : " << zahl << std::endl;
fin >> zahl; // Versuch, nächste Zahl zu lesen
}
Beachten Sie, dass das Dateiende nicht beim Lesen der letzten Zeile,
sondern erst beim darauffolgenden (erfolglosen) Versuch, eine Zeile zu
lesen, erkannt wird. Bevor auf eof() getestet werden kann,
muss also eingelesen werden.
Zur Verdeutlichung der Zusammenhänge sei hier noch einmal die Klassenstruktur der IO dargestellt.

Beim Lesen von Files und Schreiben in Files können Fehler auftreten,
die abgefangen werden müssen. Beispielsweise könnte man versuchen, aus
einem nichtexistierenden File zu lesen, das [usb]{acronym-label="usb"
acronym-form="singular+short"}-Laufwerk könnte nicht verfügbar sein oder
man könnte für die Datei nicht genügend Rechte haben. Ein
ifstream- oder ofstream-Objekt besitzt vier
Funktionen, über die der Zustand abgefragt werden kann:
good() (Operation erfolgreich), eof() (Ende
der Datei erreicht), fail() (Fehler z.B. Formatfehler) und
bad() (Gegenteil von good()). Dieser Zustand
bezieht sich immer auf die letzte ausgeführte Leseoperation.
Fehler bei Streams können auf zwei Weisen behandelt werden. Die eine
Möglichkeit ist, nach jeder Operation abzufragen, ob der Stream noch
good() ist. Falls ein Fehlerzustand vorliegt, kann der
Fehler durch eine entsprechende Aktion bearbeitet werden oder eine
Exception geworfen werden.
Die Stream-Klassen unterstützen das Werfen der Exception auch direkt:
Mit der exceptions-Methode wird aktiviert, dass der Stream
Exceptions wirft, wenn eine Operation fehlschlägt. Diese Methode nimmt
eine Bitmaske entgegen und wird wie folgt aufgerufen:
ifstream f("dateiname");
f.exceptions(ios_base::eofbit | ios_base::failbit | ios_base::badbit);ios_base ist dabei die Basisklasse aller Eingabe- und
Ausgabe-Streams. Die Zustände, die zu Exceptions führen sollen, werden
mit einem bitweisen Oder (|) verknüpft. Bei der Ausführung
werden dann bei einem Fehler Ausnahmen vom Typ ios::failure
-- einer Unterklasse von std::exception -- geworfen. Falls
schon ein Fehler existiert, wenn diese Methode aufgerufen wird, wird
sofort eine Ausnahme geworfen.
int liesFile(std::string dateiname) {
// Öffne die Datei zum Lesen
std::ifstream fin(dateiname);
fin.exceptions(std::ios::eofbit | std::ios::failbit | std::ios::badbit);
// Exceptions definieren.
// Lies den Wert ein
int i;
fin >> i;
// Exception geworfen, falls Einlesen fehlschlug
return i;
}
Der Unterschied zwischen den Zuständen fail und bad ist klein, aber durchaus von Interesse: fail wird i.A. gesetzt, wenn ein Vorgang nicht korrekt durchgeführt werden konnte, der Stream aber prinzipiell in Ordnung ist. Typisch dafür sind Formatfehler beim Einlesen: Wenn z.B. ein Integer eingelesen werden soll, das nächste Zeichen aber ein Buchstabe ist, kommt der Stream in diesen Zustand. bad wird gesetzt, wenn der Stream prinzipiell nicht mehr in Ordnung ist oder Zeichen verlorengegangen sind. Dann ist der Stream nicht mehr benutzbar. Dieses Flag wird z.B. beim Positionieren vor einen Dateianfang gesetzt.
Für den genauen Modus der Dateibearbeitung existieren einige Flags,
die in der Klasse ios_base wie folgt definiert werden:
| Flag | Verwendung |
|---|---|
ios_base::in |
Lesen (Default bei
ifstream) |
ios_base::out |
Schreiben (Default bei
ofstream) |
ios_base::app |
Anhängen |
ios_base::ate |
ans Ende Positionieren
("at end") |
ios_base::trunc |
alten Dateiinhalt löschen |
ios_base::nocreate |
Datei muss existieren |
ios_base::noreplace |
Datei darf nicht existieren |
Die Flags können mit dem |-Operator verknüpft werden.
Übergeben werden sie dann dem Konstruktor als optionales zweites
Argument. Die folgende Anweisung öffnet z.B. eine Datei, die bereits
existieren muss, zum Schreiben und sorgt dafür, dass der geschriebene
Text an das Ende der Datei angehängt wird:
fstream datei("xyz.out",
ios_base::out | ios_base::app | ios_base::nocreate);Mit einem String der Standardbibliothek von wird die Bearbeitung von
Zeichenketten gegenüber C-Strings (char[]) wesentlich
erleichtert. Voraussetzung für die Benutzung ist die Einbindung der
Bibliothek <string> durch
#include <string>Es können dann Elemente der Klasse string mit
verschiedenen Konstruktoren erstellt werden:
string myString; erstellt einen leeren
String.
string myString(10, 'A');
erstellt einen String mit 10 Zeichen und dem Inhalt
"AAAAAAAAAA". Üblicher ist natürlich eine Initialisierung
mit Leerzeichen.
string myString("Dies ist ein String");
erstellt einen String mit 19 Zeichen und dem Inhalt
"Dies ist ein String".
Die Ein-/Ausgabe der Strings erfolgt über die entsprechenden
<< bzw. >>-Operatoren.
std::string s1(10, 'A');
std::string s2("Dies ist ein String.");
std::string s3;
std::cout << s1 << std::endl;
std::cout << s2 << std::endl;
std::cout << "Geben Sie einen String ein:" << std::endl;
std::cin >> s3;
std::cout << "Der String " << s3 << " wurde eingegeben." << std::endl;
Auch die Vergleichsoperatoren, sowie der + - und der
+= -Operator sind intuitiv für Strings definiert. Die
Vergleichsoperatoren liefern das Ergebnis des zeichenweisen Vergleichs
entsprechend der zugrundeliegenden Codierung. Da die Codierung von
Buchstaben alphabetisch ist, sind auch die Vergleichsoperatoren für
Strings alphabetisch. Die + -Operatoren dienen zur
Aneinanderreihung (Konkatenation) zweier Strings.
Beispiel:
std::string s1("ABC");
std::string s2;
std::string s3;
s2 = "AC";
s3 = s1 + s2;
std::cout << s3 << std::endl;
std::cout << "Maximum von: " << s1 << " und " << s2 << ": ";
if (s1 > s2)
std::cout << s1;
else
std::cout << s2;
Der Zugriff auf die einzelnen Zeichen eines Strings erfolgt mit dem
[]-Operator. Auch hier wird mit dem Index 0 auf das erste
Zeichen zugegriffen. Im Gegensatz zu den C-Strings wird
string nicht durch ein Null-Byte (\0) beendet.
Das Ende wird durch die aktuelle Länge des Strings, die mitgespeichert
wird, bestimmt. Die Länge kann durch die Methode length()
abgefragt werden.
Beispiel:
std::string s1("ABC");
// [] fängt bei 0 an
std::cout << "Zweites Zeichen: " << s1[1] << std::endl;
// length() gibt die Anzahl der Zeichen an
std::cout << "Länge: " << s1.length() << std::endl;
Beachte: s1[s1.length() - 1] ist das letzte
Zeichen des Strings, s1[s1.length()] erzeugt einen
Zugriffsfehler.
Neben diesem direkten Zugriff auf die einzelnen Elemente kann auch sequentiell auf die Zeichen eines Strings zugegriffen werden.
Beispiel:
std::string s1("ABCD");
for (auto c : s1)
std::cout << c << ' ';
Für die komfortable Bearbeitung von Stringobjekten stehen diverse Methoden zur Verfügung. Die in aufgeführte Auswahl ist nicht vollständig und für einige Methoden gibt es neben den dargestellten auch noch weitere Aufrufvarianten mit anderen Parametertypen (siehe dazu die Literatur oder Online-Hilfe).
| Methode | Funktionalität |
|---|---|
length(), size() |
Liefert die aktuelle Länge des Strings |
empty() |
Liefert true, falls leer |
insert(pos, str) |
Fügt strvor pos
im Strin ein |
erase(pos, anzahl) |
Löscht im String ab
pos anzahl Zeichen |
replace(pos, anzahl, str) |
Ersetzt ab pos bis zu
anzahl Zeichen durch die Zeichen von str |
find(str, pos) |
Liefert die Position des ersten Auftretens
von str im String ab pos, oder -1 falls nicht
vorhanden |
rfind(str, pos) |
Liefert die Position des letzten
Auftretens von str im String ab pos, oder -1
falls nicht vorhanden |
find_first_of(str, pos) |
Liefert die Position des ersten Auftretens
eines Zeichens von str im String ab pos, oder
-1 falls nicht vorhanden |
find_last_of(str, pos) |
Liefert die Position des letzten
Auftretens eines Zeichens von str im String ab
pos, oder -1 falls nicht vorhanden |
find_first_not_of(str, pos) |
Liefert die erste Position des Zeichens im
String, das nicht in str vorkommt, oder -1 falls alle
vorkommen |
find_first_not_of(str, pos) |
Liefert die letzte Position des Zeichens
im String, das nicht in str vorkommt, oder -1 falls alle
vorkommen |
substr(pos, laenge) |
Liefert einen Substring ab
pos im String mit der angegebenen laenge |
compare(str) compare(pos, anz, str) compare(pos, anz, str, pos1, anz1 |
Vergleicht den String zeichenweise mit
str und liefert als Ergebnis: -1 falls str < aktuellem String 0 falls str =
aktuellem String1 falls str > aktuellem String Durch die übrigen Parameter kann der Vergleich auf einen Teilstring des aktuellen Strings (pos, anz) bzw. des Vergleichsstrings
(pos1, anz1) eingeschränkt werden. |
Es ist zu beachten, dass Stringobjekte und C-Strings nicht verwechselt oder gemischt werden dürfen, auch wenn für die meisten obigen Stringmethoden auch entsprechende überlagerte Methoden für C-Strings als Parameter existieren. Für Funktionen, die C-Strings erwarten (z.B. Funktionen der Windowsbibliothek) existieren keine entsprechenden Aufrufe für Stringobjekte. Allerdings können sie in beide Richtungen umgewandelt werden:
C-Strings in Stringobjekte durch Aufruf des entsprechenden Konstruktors
Stringobjekte in C-Strings durch die Methode
c_str()
Mit den String-Stream-Klassen kann man unter Verwendung der
Formatierungsmöglichkeiten der IO-Streams aus einem String lesen (wie
z.B. von der Standardeingabe cin) oder in einen String
schreiben (wie auf die Standardausgabe cout). Solche
Streams werden String-Streams (Klasse stringstream)
genannt. Sie werden in <sstream> definiert.
Wie bei den Stream-Klassen für Dateien gibt es analog Stream-Klassen für Strings:
istringstream - für Strings, aus denen gelesen wird
(input string stream)
ostringstream - für Strings, in die geschrieben wird
(output string stream)
stringstream - für Strings, die zum Lesen und
Schreiben verwendet werden
Die Klassen erben von iostream und übernehmen daher auch
den Funktionsumfang der Oberklasse, wie z.B. die Ein-/Ausgabeoperatoren.
Weiterhin stellt stringstream folgende Funktionen zur
Verfügung:
str() - liefert den Ausgabestrom als String
zurück
rdbuf() - liefert die Adresse des internen
String-Buffers
Beispiel:
#include <sstream>
using namespace std;
string namen[3];
for (int i = 0; i < 3; i++) {
// Beachte: stringstream lokal, keine Datei
stringstream s;
// Ausgabe "Objekt" + Nummer, z.B. "Objekt1"
s << "Objekt" << i + 1;
namen[i] = s.str();
}
// Kontrollausgabe
for (const auto& name : namen){
cout << name << endl;
}