Home / Articles / Introduction to C++ Streams
Introduction to C++ Streams
The C++ stream library provides a clean, type-safe, and extensible mechanism for input and output. Rather than printf-style format strings, streams use operators and a class hierarchy that supports everything from console I/O to files to strings.
The Stream Class Hierarchy
The stream library is organized around a hierarchy of classes. At the top is ios_base, which stores the stream's state and formatting settings. Above that, basic_ios adds character type awareness. Then come the actual stream classes:
istream— input streamostream— output streamiostream— bidirectional (inherits from both)
These are typedef'd for char:
istream=basic_istream<char>ostream=basic_ostream<char>
The library also has wide-character counterparts (wistream, wostream) for Unicode.
The Standard Stream Objects
Four global stream objects are pre-defined and available after including <iostream>:
#include <iostream>
using namespace std;
| Object | Type | Purpose |
|---|---|---|
cin |
istream |
Standard input (keyboard) |
cout |
ostream |
Standard output (screen) |
cerr |
ostream |
Standard error (unbuffered) |
clog |
ostream |
Standard error (buffered) |
The distinction between cerr and clog is buffering: cerr flushes immediately after each output operation, making it suitable for error messages that must appear even if the program crashes. clog is buffered for efficiency.
The Insertion Operator (<<)
The << operator writes data to an output stream:
cout << "Hello, world!" << endl;
cout << "Value: " << 42 << "\n";
cout << "Pi: " << 3.14159 << "\n";
The operator is overloaded for all built-in types. It returns a reference to the stream, which enables chaining:
cout << "x = " << x << ", y = " << y << "\n";
endl outputs '\n' and flushes the buffer. For performance, prefer "\n" when you don't need an immediate flush.
The Extraction Operator (>>)
The >> operator reads data from an input stream:
int age;
string name;
cin >> name >> age; // reads whitespace-delimited tokens
Key behaviors:
- Leading whitespace is skipped by default
- Extraction stops at the first whitespace character after the data
- On failure (e.g., reading an
intwhen the input is "abc"), the stream's fail bit is set
Stream State Flags
Every stream maintains a set of state bits that indicate its status:
| Flag | Meaning |
|---|---|
goodbit |
No errors, stream is ready |
eofbit |
End-of-file reached |
failbit |
Logical error (e.g., type mismatch) |
badbit |
Unrecoverable I/O error |
Query state with:
if (cin.good()) { /* all ok */ }
if (cin.eof()) { /* end of file */ }
if (cin.fail()) { /* extraction failed */ }
if (cin.bad()) { /* serious error */ }
Or use the stream directly as a boolean:
if (cin >> x) {
// extraction succeeded
}
Clearing Error State
Once a stream enters a failed state, further operations are no-ops until you clear the error:
cin.clear(); // reset all error flags
cin.ignore(1000, '\n'); // discard bad input up to newline
Character-Level Input
get()
get() reads a single character, including whitespace (unlike >>):
char c;
cin.get(c); // reads one character
There is also an overload that reads into a buffer:
char buf[100];
cin.get(buf, 100); // reads up to 99 chars, stops at newline (does not consume newline)
getline()
getline() reads an entire line, consuming (but not storing) the newline delimiter:
string line;
getline(cin, line); // reads a full line
With a custom delimiter:
getline(cin, line, ','); // reads until comma
read()
read() reads a specified number of bytes regardless of content — no newline stopping:
char buf[50];
cin.read(buf, 50); // reads exactly 50 bytes (or until EOF)
int actually_read = cin.gcount(); // how many were actually read
This is useful for binary I/O.
peek() and putback()
peek() reads the next character without consuming it:
char c = cin.peek(); // look ahead without advancing
putback() pushes a character back into the stream buffer:
cin.putback(c); // unget the character
Output Flushing
Output streams are typically buffered — data is accumulated and written in batches for efficiency. To force the buffer to be written:
cout << flush; // flush without newline
cout << endl; // newline + flush
cout.flush(); // method form
Excessive flushing hurts performance. Use "\n" for regular newlines and endl only when the output must appear immediately (e.g., before user input).
Tying Streams
cin is "tied" to cout by default, meaning cout is automatically flushed before any cin extraction. This ensures prompts appear before input is requested:
cout << "Enter your name: "; // auto-flushed before cin reads
cin >> name;
You can examine or change the tie with tie():
ostream* old_tie = cin.tie(); // get current tied stream
cin.tie(nullptr); // untie (may improve performance in I/O-heavy code)
File Streams
The stream model extends directly to files via <fstream>:
#include <fstream>
ofstream outfile("output.txt");
outfile << "Hello, file!" << "\n";
outfile.close();
ifstream infile("input.txt");
string line;
while (getline(infile, line)) {
cout << line << "\n";
}
The same operators, state flags, and methods work identically on file streams.
String Streams
<sstream> provides in-memory stream I/O — useful for parsing and formatting:
#include <sstream>
ostringstream oss;
oss << "x = " << 42 << ", y = " << 3.14;
string result = oss.str();
istringstream iss("10 20 30");
int a, b, c;
iss >> a >> b >> c; // a=10, b=20, c=30
String streams are a clean alternative to sprintf/sscanf.