E/A: Streams, Formatierung & std::print
Meistere die Ein- und Ausgabe in C++ vom klassischen iostream bis zu modernem std::format und std::print — samt Stream-Zuständen, Datei-E/A und typischen Pufferfallen.
Die Entwicklung der C++-E/A
Die Ein- und Ausgabe in C++ hat sich im Lauf der Sprachgeschichte stark gewandelt. Die klassische iostream-Bibliothek (std::cin, std::cout) ist seit C++98 der Standard, hat aber bekannte Schwächen bei Ergonomie und Performance. C++20 hat std::format eingeführt, eine typsichere Formatierungsbibliothek nach dem Vorbild von Pythons str.format(). C++23 kam std::print hinzu, das Formatierung und Ausgabe in einem einzigen Aufruf vereint.
Alle drei Ansätze zu kennen ist wichtig: iostream steckt in unzähligen bestehenden Codebasen, std::format ist der moderne Weg, Strings zu formatieren, und std::print ist die Zukunft der Ausgabe in C++.
Klassische E/A: cout, cin, cerr
Die iostream-Bibliothek stellt Stream-Objekte für die Standardeingabe, die Standardausgabe und die Fehlerausgabe bereit:
#include <iostream>
#include <string>
#include <limits> // For numeric_limits
int main() {
// Output with cout
std::cout << "Hello, " << "World!" << '\n'; // Chained insertion
std::cout << "The answer is: " << 42 << '\n'; // Automatic type formatting
// Error output — cerr is unbuffered (immediate), clog is buffered
std::cerr << "Error: something went wrong\n";
// Input with cin
std::cout << "Enter your name: ";
std::string name;
std::getline(std::cin, name); // Read entire line (including spaces)
std::cout << "Enter your age: ";
int age{};
std::cin >> age; // Read a single value (stops at whitespace)
// Check if input succeeded
if (std::cin.fail()) {
std::cerr << "Invalid input!\n";
std::cin.clear(); // Reset error flags
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n'); // Discard bad input
}
// The getline-after-cin trap:
// After cin >> age, a '\n' remains in the buffer.
// A subsequent getline() would read an empty string!
// Fix: call cin.ignore() before getline():
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
std::string city;
std::cout << "Enter your city: ";
std::getline(std::cin, city); // Now works correctly
std::cout << name << " (" << age << ") from " << city << '\n';
}Klicke auf Ausführen, um dieses Beispiel zu kompilieren und auszuführen.
Zustandsflags von Streams
Jeder Stream führt eine Reihe von Zustandsflags, die seine aktuelle Lage anzeigen:
- goodbit — Keine Fehler. Der Stream ist bereit für E/A-Operationen.
- eofbit — Das Ende der Datei bzw. Eingabe ist erreicht. Wird gesetzt, wenn der Stream über das Ende hinaus zu lesen versucht.
- failbit — Ein logischer Fehler ist aufgetreten (z. B. der Versuch, eine Ganzzahl zu lesen, wo Text steht). Nach einem Aufruf von clear() ist der Stream weiter benutzbar.
- badbit — Ein nicht behebbarer E/A-Fehler ist aufgetreten (z. B. ein Plattenfehler). Der Stream ist in der Regel unbrauchbar.
Du prüfst sie mit stream.good(), stream.eof(), stream.fail(), stream.bad() oder indem du den Stream in einem booleschen Kontext verwendest: if (std::cin) entspricht if (!std::cin.fail()).
Ein entscheidendes Muster: Prüfe den Stream-Zustand immer nach einer Eingabeoperation, nicht davor. Das verbreitete Idiom while (std::cin >> value) liest und prüft in einem Schritt — es bricht ab, sobald das Lesen fehlschlägt.
Der häufigste E/A-Fehler in C++ entsteht beim Mischen von >> (formatierte Extraktion) mit std::getline(). Der Operator >> liest einen Wert, lässt den abschließenden Zeilenumbruch aber im Puffer zurück. Ein anschließendes std::getline() liest dann genau diesen übrig gebliebenen Zeilenumbruch als leeren String, sodass es scheint, als würde die Eingabe übersprungen.
Die Lösung ist, zwischen >> und std::getline() den Aufruf std::cin.ignore(std::numeric_limits einzufügen, um den übrigen Zeilenumbruch zu verwerfen.
Ein anderer Ansatz ist, sämtliche Eingaben mit std::getline() zu lesen und die Strings selbst mit std::istringstream oder std::stoi/std::stod auszuwerten. Das umgeht das Problem vollständig und gibt dir eine bessere Fehlerbehandlung.
- Nach cin >> x bleibt ein Zeilenumbruch im Puffer — das nächste getline() liest ihn als leeren String
- Lösung: Rufe zwischen >> und getline() cin.ignore(numeric_limits
::max(), '\n') auf - Alternative: Lies ALLE Eingaben mit getline() und werte die Strings mit stoi/stod oder istringstream aus
- Prüfe nach jeder Eingabe den Stream-Zustand: if (cin >> x), statt blind zu lesen
Datei-E/A
Die Datei-E/A nutzt dieselbe Stream-Schnittstelle wie die Konsolen-E/A, mit std::ifstream zum Lesen und std::ofstream zum Schreiben:
#include <fstream>
#include <iostream>
#include <string>
#include <vector>
int main() {
// Writing to a file
{
std::ofstream out("scores.txt"); // Opens for writing (creates or truncates)
if (!out) {
std::cerr << "Failed to open file for writing\n";
return 1;
}
out << "Alice 95\n";
out << "Bob 87\n";
out << "Charlie 92\n";
} // File is automatically closed when 'out' goes out of scope (RAII!)
// Reading from a file
std::ifstream in("scores.txt");
if (!in) {
std::cerr << "Failed to open file for reading\n";
return 1;
}
std::string name;
int score{};
std::vector<std::pair<std::string, int>> results;
while (in >> name >> score) { // Read until failure (EOF or bad data)
results.emplace_back(name, score);
}
for (const auto& [n, s] : results) {
std::cout << n << ": " << s << '\n';
}
// Appending to a file
std::ofstream append("scores.txt", std::ios::app); // Open in append mode
append << "Diana 98\n";
// Reading entire file line by line
std::ifstream in2("scores.txt");
std::string line;
while (std::getline(in2, line)) {
std::cout << "Line: " << line << '\n';
}
}Klicke auf Ausführen, um dieses Beispiel zu kompilieren und auszuführen.
Moderne Formatierung: std::format & std::print
std::format aus C++20 und std::print aus C++23 bieten eine typsichere, Python-ähnliche Formatierung, die sicherer und besser lesbar ist als die Manipulatoren von iostream:
#include <format> // C++20
#include <print> // C++23
#include <string>
#include <vector>
#include <numbers> // C++20: mathematical constants
int main() {
// std::format (C++20) — returns a formatted std::string
std::string msg = std::format("Hello, {}!", "World");
// msg == "Hello, World!"
// Positional arguments
std::string pos = std::format("{1} comes before {0}", "second", "first");
// pos == "first comes before second"
// Formatting specifiers
std::string nums = std::format(
"int: {:>10d}\n" // Right-aligned, width 10, decimal
"hex: {:#06x}\n" // '0x' prefix, width 6, zero-padded hex
"float: {:.3f}\n" // 3 decimal places, fixed notation
"sci: {:.2e}\n" // 2 decimal places, scientific notation
"pi: {:.10f}", // 10 decimal places
42, 255, 3.14159, 12345.6789, std::numbers::pi
);
// std::print (C++23) — format + output in one step
std::print("Name: {}, Age: {}\n", "Alice", 30);
std::print("Pi is approximately {:.6f}\n", std::numbers::pi);
// std::println (C++23) — like print but adds a newline
std::println("Score: {:>5}", 95); // "Score: 95"
std::println("Score: {:>5}", 100); // "Score: 100"
// Formatting containers — print each element
std::vector<int> values = {10, 20, 30, 40, 50};
for (std::size_t i = 0; i < values.size(); ++i) {
std::println("[{:2}] = {:>4}", i, values[i]);
}
// Why prefer std::format/print over iostream?
// 1. Type-safe (unlike printf)
// 2. Readable format strings (unlike iostream manipulators)
// 3. No state leakage (iostream manipulators are sticky!)
// 4. Better performance in many cases
}Klicke auf Ausführen, um dieses Beispiel zu kompilieren und auszuführen.
Modernes C++ bietet dir drei Ansätze für die Ein- und Ausgabe. So wählst du den passenden:
Verwende std::print/std::println (C++23) für neuen Code, sofern dein Compiler das unterstützt. Das ist die sauberste Syntax, sie ist typsicher und vermeidet die Probleme der zustandsbehafteten Manipulatoren von iostream.
Verwende std::format (C++20), wenn du einen formatierten String brauchst (statt direkter Ausgabe), oder wenn dein Compiler C++20, aber noch nicht C++23 beherrscht.
Verwende iostream, wenn du mit Code zusammenarbeitest, der Stream-Objekte erwartet, wenn du die Stream-Abstraktion brauchst (z. B. beim Lesen von cin oder bei Datei-E/A) oder wenn du an einer alten Codebasis arbeitest.
Vermeide printf/scanf aus C — sie sind nicht typsicher (ein falscher Formatbezeichner ist undefiniertes Verhalten) und funktionieren nicht mit C++-Typen wie std::string.
- Bevorzuge std::print/println (C++23) für neuen Ausgabecode — am saubersten und sichersten
- Verwende std::format (C++20), wenn du einen formatierten String brauchst statt direkter Ausgabe
- iostream wird weiterhin für cin, Datei-E/A und streambasierte Abstraktionen gebraucht
- Vermeide printf/scanf — sie sind nicht typsicher und kommen mit std::string nicht direkt zurecht
- Prüfe den Stream-Zustand immer nach einer Eingabeoperation — verwende 'if (cin >> x)' oder 'while (getline(in, line))'
- Das Mischen von cin >> und getline() verursacht den Zeilenumbruch-im-Puffer-Fehler — rufe dazwischen cin.ignore() auf
- Dateistreams nutzen RAII — sie schließen sich automatisch, sobald sie ihren Gültigkeitsbereich verlassen
- std::format (C++20) bietet typsichere, Python-ähnliche String-Formatierung
- std::print/println (C++23) vereint Formatierung und Ausgabe — der bevorzugte Weg für neuen Code
- Die Manipulatoren von iostream sind zustandsbehaftet (wirken fort) — std::format umgeht dieses Problem vollständig
Quiz — Teste dein Wissen
(10 XP)1. Was passiert, wenn du direkt nach `std::cin >> number` ein `std::getline(std::cin, str)` aufrufst?
2. Was ergibt `std::format("{:#06x}", 255)`?
3. Warum schließen sich Dateistreams (ifstream/ofstream) automatisch, sobald sie ihren Gültigkeitsbereich verlassen?