You know what a stream is. You know why endl costs you. You know the buffer.

Now let’s talk about what you can actually do with streams — format output precisely, read and write files without ceremony, and parse strings like they’re cin. Same pipe. More control.


Manipulators: Functions in Disguise

You already know one manipulator: endl. It looks like data but it’s actually a function call — it takes the stream, does something to it, and returns it.

Every manipulator works this way. They’re not values being inserted. They’re instructions to the stream.

cpp
1cout << hex << 255 << "\n";  // ff

hex doesn’t insert anything. It flips a formatting flag on cout. Every number printed after that comes out in hexadecimal — until you change it.

That’s the key thing about manipulators: some are sticky, some aren’t.

Sticky vs Non-Sticky

cpp
1// setw — non-sticky. Resets after one insertion.
2cout << setw(10) << 42 << "\n";   // "        42"
3cout << 42 << "\n";               // "42" — setw is gone
4
5// setprecision — sticky. Persists until changed.
6cout << fixed << setprecision(2) << 3.14159 << "\n";  // 3.14
7cout << 2.71828 << "\n";                              // 2.72 — still active

Mix these up and your formatting silently breaks two lines later. No warning. No error. Just wrong output.

Resetting Sticky Manipulators

Most sticky manipulators have a no-prefixed counterpart. The ones that don’t, get reset manually to their default value.

cpp
 1// Number base — use dec to reset
 2cout << hex;          // switched to hex
 3cout << dec;          // back to decimal (default)
 4
 5// Float format — use defaultfloat to reset
 6cout << fixed;
 7cout << defaultfloat; // back to default
 8
 9// Precision — no reset keyword, set back to default manually
10cout << setprecision(2);
11cout << setprecision(6);  // 6 is the default
12
13// Fill character — no reset keyword, set back to default manually
14cout << setfill('0');
15cout << setfill(' ');  // space is the default
16
17// Alignment
18cout << left;
19cout << right;        // right is the default
20
21// Booleans
22cout << boolalpha;
23cout << noboolalpha;  // back to 1/0
24
25// Sign
26cout << showpos;
27cout << noshowpos;
28
29// Base prefix
30cout << showbase;
31cout << noshowbase;

Rule of thumb: if it starts with show or ends in alpha, there’s a no version. Otherwise, set it back to its default value explicitly.

Number Formatting

cpp
1cout << hex << 255 << "\n";   // ff
2cout << oct << 255 << "\n";   // 377
3cout << dec << 255 << "\n";   // 255  ← back to decimal
4
5// uppercase hex digits
6cout << uppercase << hex << 255 << "\n";  // FF
7
8// show base prefix
9cout << showbase << hex << 255 << "\n";   // 0xff

All sticky. Once you switch to hex, every integer prints in hex until you say dec.

Floating Point

cpp
1cout << fixed << setprecision(2) << 3.14159 << "\n";      // 3.14
2cout << scientific << setprecision(3) << 3.14159 << "\n"; // 3.142e+00
3cout << defaultfloat << 3.14159 << "\n";                  // 3.14159 — back to default

fixed and scientific are sticky. setprecision is sticky. Switch back to defaultfloat when you’re done or every float in your program prints with that precision.

Width, Fill, Alignment

cpp
1// Right-align (default), padded with spaces
2cout << setw(10) << 42 << "\n";             // "        42"
3
4// Left-align
5cout << left << setw(10) << 42 << "\n";     // "42        "
6
7// Custom fill character
8cout << setw(10) << setfill('0') << 42 << "\n";  // "0000000042"
9cout << setw(10) << setfill('-') << 42 << "\n";  // "--------42"

setfill is sticky. setw is not. A common pattern — set fill once, set width before every insertion.

Booleans and Signs

cpp
1cout << boolalpha << true << "\n";   // true (not 1)
2cout << boolalpha << false << "\n";  // false (not 0)
3
4cout << showpos << 42 << "\n";  // +42

boolalpha and showpos are sticky. Reset with noboolalpha and noshowpos.

The Full Sticky/Non-Sticky Cheatsheet

ManipulatorSticky?
setwno
setprecisionyes
setfillyes
hex, oct, decyes
fixed, scientificyes
left, rightyes
boolalphayes
showbase, showposyes
endl, flush— (one-time action)

When in doubt — assume sticky and reset explicitly.


File Streams: Same Interface, Different Destination

This is where the stream abstraction pays off. Everything you know about cout and cin works on files. Same operators. Same manipulators. Same state flags.

cpp
1#include <fstream>

Writing

cpp
 1ofstream out("output.txt");
 2
 3if (!out) {
 4    cerr << "Failed to open file\n";
 5    return 1;
 6}
 7
 8out << "Line one\n";
 9out << "Line two\n";
10// File closes when out goes out of scope — RAII

Always check if the file opened. ofstream doesn’t throw by default — it just silently fails and every write becomes a no-op.

Reading

cpp
 1ifstream in("data.txt");
 2
 3if (!in) {
 4    cerr << "File not found\n";
 5    return 1;
 6}
 7
 8// Read line by line
 9string line;
10while (getline(in, line)) {
11    cout << line << "\n";
12}
13
14// Read word by word
15string word;
16while (in >> word) {
17    cout << word << "\n";
18}
19
20// Read token by token with type
21int n;
22double d;
23in >> n >> d;

getline and >> work exactly like they do with cin. The same ghost newline trap applies if you mix them.

Open Modes

cpp
 1// Truncate — clear the file on open (default for ofstream)
 2ofstream out("log.txt", ios::trunc);
 3
 4// Append — add to existing content
 5ofstream out("log.txt", ios::app);
 6
 7// Read + write
 8fstream f("data.txt", ios::in | ios::out);
 9
10// Binary mode — no newline translation
11ofstream out("image.bin", ios::binary);

Combine modes with |. The most common mistake: forgetting ios::app and wiping a log file on every run.

RAII — The Destructor Does the Work

cpp
1void writeReport() {
2    ofstream out("report.txt");
3    out << "Done\n";
4    // out destructor runs here — file is closed
5}

You don’t need to call out.close() explicitly in most cases. The destructor handles it when the object leaves scope. This means no leaked file handles, even if an exception fires mid-function.

Call close() explicitly only when you need to reopen the file or check for write errors before the function ends.

cpp
1out.close();
2if (!out) {
3    cerr << "Write may have failed\n";
4}

String Streams: Streams in Memory

Sometimes you don’t want to write to the terminal or a file. You want to build a string, or parse one. String streams give you the full stream interface — but backed by memory.

cpp
1#include <sstream>

Three types: ostringstream (write), istringstream (read), stringstream (both).

Building Strings

cpp
1ostringstream oss;
2oss << "Order #" << 1042 << " — total: $" << fixed << setprecision(2) << 99.5;
3string msg = oss.str();
4// "Order #1042 — total: $99.50"

Every manipulator works here too. fixed, setprecision, hex — all of it. This is cleaner than concatenating strings manually, especially when you’re mixing types.

Parsing Strings

cpp
1string record = "Alice 28 9.5";
2istringstream iss(record);
3
4string name;
5int age;
6double score;
7
8iss >> name >> age >> score;
9// name="Alice", age=28, score=9.5

Think of it as cin but for a string you already have. Parse CSV lines, config entries, log records — same >> operator, no file needed.

cpp
1// Parsing a CSV line
2string line = "10,3.14,hello";
3replace(line.begin(), line.end(), ',', ' ');  // swap commas for spaces
4istringstream iss(line);
5
6int n; double d; string s;
7iss >> n >> d >> s;  // n=10, d=3.14, s="hello"

Type Conversion

A common use case — converting anything to a string and back:

cpp
 1// To string
 2template<typename T>
 3string toString(const T& val) {
 4    ostringstream oss;
 5    oss << val;
 6    return oss.str();
 7}
 8
 9string s = toString(3.14);   // "3.14"
10string s2 = toString(255);   // "255"
11
12// From string
13template<typename T>
14T fromString(const string& str) {
15    istringstream iss(str);
16    T val;
17    iss >> val;
18    return val;
19}
20
21int n = fromString<int>("42");        // 42
22double d = fromString<double>("3.14"); // 3.14

In modern C++ (C++11 onwards) you’d use to_string() and stoi()/stod() for simple cases. But stringstream shines when you need formatting control — precision, bases, padding — during the conversion.

Reusing a String Stream

cpp
1ostringstream oss;
2oss << "first";
3oss.str("");       // clear the content
4oss.clear();       // reset state flags
5oss << "second";
6cout << oss.str(); // "second"

str("") clears the buffer. clear() resets the flags. You need both — str("") alone leaves the stream in whatever state it was in.


When a Stream Breaks

Every stream — cin, cout, ifstream, ofstream, all of them — tracks its own health through four state flags: good(), fail(), eof(), bad(). They live on the base class, so the same flags and the same recovery steps apply everywhere. Reading a file? Same flags. Writing to a string stream? Same flags.

Normal flow — everything is fine, good() is true, reads work. The moment something goes wrong, the stream raises a flag and stops reading. Not an exception. Not a crash. Just silence. Every >> after that point is a no-op.

The most common trigger: type mismatch.

cpp
1int x;
2cin >> x;  // user types "abc"

cin expected digits. Got letters. It sets fail(), leaves "abc" sitting in the buffer, and shuts down. Now this:

cpp
1cin >> x;  // user types "42"

Never executes. cin is failed. It doesn’t matter what the user types — nothing gets through.

To recover, you need two steps:

cpp
1if (cin.fail()) {
2    cin.clear();                                      // step 1: reset the fail flag — cin is "healthy" again
3    cin.ignore(numeric_limits<streamsize>::max(), '\n'); // step 2: throw away the garbage in the buffer
4}

clear() alone isn’t enough. The bad input is still in the buffer. The next read picks it up, fails again, and you’re back where you started. Both lines, in order.

Don’t forget the header:

cpp
1#include <limits>

bad() is a different beast. It doesn’t mean wrong input — it means the stream itself is broken. Underlying hardware failure, corrupted file, OS-level I/O error. The kind of thing you can’t recover from in user code.

cpp
1if (cin.bad()) {
2    // No point trying to clear and continue.
3    // The stream is physically broken.
4    throw std::runtime_error("Stream corrupted");
5}

The four flags — on every stream:

FlagMeaningRecoverable?
good()all clear, reads will work
fail()bad input or format mismatchyes — clear() + ignore()
eof()end of input reachedsometimes — clear() if more input is expected
bad()stream physically corruptedno

fail() is the everyday one. bad() is the one you hope never fires.


The Same Pipe, Every Time

Manipulators, file streams, string streams — different tools, same foundation. Learn the abstraction once and the rest follows.

Four rules that carry across all three:

  • Assume manipulators are sticky. Reset explicitly when you’re done.
  • Always check if a file stream opened. Silent failures are worse than loud ones.
  • str("") + clear() to reuse a string stream. One without the other leaves a mess.
  • The ghost newline trap from cin applies to istringstream too. Same pipe, same rules.

The stream doesn’t care where it’s writing. You just push data in. That’s the whole idea.