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:

These are typedef'd for 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:

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.