Intermediate

I/O Operations in C++

Learn console input and output, formatted printing, file reading and writing, and string streams in C++ with Docker-ready examples

Input and output are how a program communicates with the outside world — printing results to the terminal, reading what a user types, and persisting data to files. In C++, all of this is built on the stream abstraction from the <iostream> and <fstream> libraries. A stream is a sequence of bytes flowing to or from a device, and the same << and >> operators you met in Hello World work uniformly across the console, files, and in-memory strings.

Because C++ is a multi-paradigm language with deep C roots, you actually have several I/O toolkits available: type-safe C++ streams (std::cout, std::ifstream), C-style formatted functions (std::printf), and string streams (std::stringstream) for building and parsing text in memory. The stream approach is idiomatic modern C++ because it is type-safe, extensible to your own types, and manages resources automatically through RAII — when a file stream goes out of scope, the file is closed for you.

This tutorial covers writing to the standard output and error streams, formatting numbers and text, reading typed input from the keyboard, reading and writing files, and using string streams to convert between text and values. Every example is self-contained and runnable with the official GCC Docker image.

Console Output and Formatting

Beyond simple printing, the <iomanip> header gives you manipulators to control precision, field width, alignment, and boolean formatting. C++ also has two output streams: std::cout for normal output (stdout) and std::cerr for errors (stderr).

Create a file named console_output.cpp:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include <iostream>
#include <iomanip>

int main() {
    // Standard output goes to stdout; errors go to stderr
    std::cout << "Standard output (stdout)" << std::endl;
    std::cerr << "Error output (stderr)" << std::endl;

    // Streaming different types with the << operator
    int count = 42;
    double price = 19.99;
    char grade = 'A';
    bool active = true;

    std::cout << "Count: " << count << "\n";
    std::cout << "Price: " << price << "\n";
    std::cout << "Grade: " << grade << "\n";
    std::cout << "Active: " << std::boolalpha << active << "\n";

    // Fixed-point formatting with a set number of decimals
    double pi = 3.14159265;
    std::cout << std::fixed << std::setprecision(2);
    std::cout << "Pi rounded: " << pi << "\n";

    // Field width and alignment for tabular output
    std::cout << std::setw(10) << std::right << "Right" << "|\n";
    std::cout << std::setw(10) << std::left << "Left" << "|\n";

    return 0;
}

std::boolalpha makes booleans print as true/false instead of 1/0. std::fixed combined with std::setprecision(2) forces two digits after the decimal point, and std::setw(10) reserves a field width of ten characters for the next value, aligned with std::left or std::right.

Reading Console Input

The std::cin stream reads from standard input. The >> operator reads whitespace-delimited tokens and converts them to the target type, while std::getline reads an entire line including spaces.

Create a file named console_input.cpp:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
#include <iostream>
#include <string>

int main() {
    std::string name;
    int age;

    std::cout << "Enter your name: ";
    std::getline(std::cin, name);

    std::cout << "Enter your age: ";
    std::cin >> age;

    std::cout << "Hello, " << name << "!\n";
    std::cout << "Next year you will be " << (age + 1) << ".\n";

    return 0;
}

Use std::getline when the input may contain spaces (like a full name), and >> when reading a single typed value such as a number. Be aware that mixing them requires care: >> leaves the newline in the buffer, which a following getline would otherwise consume as an empty line.

Reading and Writing Files

File I/O uses std::ofstream (output file stream) for writing and std::ifstream (input file stream) for reading. Thanks to RAII, you rarely need to call close() manually — the file closes when the stream is destroyed — but doing so explicitly is fine when you want to reopen or check the result immediately.

Create a file named file_io.cpp:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include <iostream>
#include <fstream>
#include <string>

int main() {
    // Writing to a file with an output file stream
    std::ofstream outFile("notes.txt");
    if (!outFile) {
        std::cerr << "Failed to open file for writing\n";
        return 1;
    }
    outFile << "Line 1: C++ file I/O\n";
    outFile << "Line 2: Using fstream\n";
    outFile << "Line 3: RAII closes the file\n";
    outFile.close();

    std::cout << "File written successfully.\n";

    // Reading the file back line by line
    std::ifstream inFile("notes.txt");
    if (!inFile) {
        std::cerr << "Failed to open file for reading\n";
        return 1;
    }

    std::string line;
    int lineNumber = 1;
    while (std::getline(inFile, line)) {
        std::cout << lineNumber << ": " << line << "\n";
        lineNumber++;
    }

    return 0;
}

The if (!outFile) check tests whether the stream is in a good state — file streams convert to false when an operation fails (for example, a missing directory or a permissions problem). The while (std::getline(...)) loop is the idiomatic way to read a file line by line, because getline returns the stream, which evaluates to false at end-of-file.

String Streams

String streams live in the <sstream> header and let you use the same << and >> operators to build strings in memory or parse values out of them. They are the type-safe C++ answer to C’s sprintf/sscanf.

Create a file named string_streams.cpp:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include <iostream>
#include <sstream>
#include <string>
#include <cstdio>

int main() {
    // Building a string with an output string stream
    std::ostringstream oss;
    int items = 3;
    double total = 47.5;
    oss << "Order: " << items << " items, $" << total;
    std::string summary = oss.str();
    std::cout << summary << "\n";

    // Parsing values out of a string with an input string stream
    std::string data = "100 3.5 hello";
    std::istringstream iss(data);
    int quantity;
    double weight;
    std::string label;
    iss >> quantity >> weight >> label;

    std::cout << "Quantity: " << quantity << "\n";
    std::cout << "Weight: " << weight << "\n";
    std::cout << "Label: " << label << "\n";

    // C-style formatted output is still available in C++
    std::printf("Formatted: %d units at %.2f each\n", quantity, weight);

    return 0;
}

std::ostringstream accumulates formatted text you can retrieve with .str(), while std::istringstream tokenizes a string just like std::cin tokenizes keyboard input. The final std::printf shows that C’s formatted output remains available for those who prefer format strings — though the stream approach is type-safe and cannot be caught out by a mismatched format specifier.

Running with Docker

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
# Pull the official GCC image (includes g++)
docker pull gcc:14

# Compile and run the console output example
docker run --rm -v $(pwd):/app -w /app gcc:14 \
    sh -c "g++ -o console_output console_output.cpp && ./console_output"

# Compile and run the file I/O example
docker run --rm -v $(pwd):/app -w /app gcc:14 \
    sh -c "g++ -o file_io file_io.cpp && ./file_io"

# Compile and run the string streams example
docker run --rm -v $(pwd):/app -w /app gcc:14 \
    sh -c "g++ -o string_streams string_streams.cpp && ./string_streams"

# The input example needs stdin; pipe values in with -i
printf 'Alice\n30\n' | docker run --rm -i -v $(pwd):/app -w /app gcc:14 \
    sh -c "g++ -o console_input console_input.cpp && ./console_input"

Expected Output

Running console_output (stdout and stderr both appear in the terminal):

Standard output (stdout)
Error output (stderr)
Count: 42
Price: 19.99
Grade: A
Active: true
Pi rounded: 3.14
     Right|
Left      |

Running console_input with the piped input Alice and 30:

Enter your name: Enter your age: Hello, Alice!
Next year you will be 31.

Running file_io:

File written successfully.
1: Line 1: C++ file I/O
2: Line 2: Using fstream
3: Line 3: RAII closes the file

Running string_streams:

Order: 3 items, $47.5
Quantity: 100
Weight: 3.5
Label: hello
Formatted: 100 units at 3.50 each

Key Concepts

  • Streams unify I/O — the same << (insertion) and >> (extraction) operators work for the console, files, and in-memory strings, so one mental model covers all of C++ I/O.
  • stdout vs stderrstd::cout writes normal output while std::cerr writes errors to a separate stream, so diagnostics can be redirected independently of results.
  • <iomanip> controls formattingstd::fixed, std::setprecision, std::setw, std::left/std::right, and std::boolalpha shape how numbers, alignment, and booleans appear.
  • getline vs >> — use std::getline for whole lines (including spaces) and >> for whitespace-delimited tokens; mixing them requires care about leftover newlines.
  • File streams and RAIIstd::ofstream and std::ifstream close their files automatically when destroyed, and testing the stream in an if or while detects open failures and end-of-file.
  • String streams for conversionstd::ostringstream and std::istringstream build and parse text in memory, the type-safe replacement for C’s sprintf/sscanf.
  • C-style I/O still worksstd::printf and friends remain available for backward compatibility, but C++ streams are preferred for type safety and extensibility to user-defined types.

Running Today

All examples can be run using Docker:

docker pull gcc:14
Last updated:

Comments

Loading comments...

Leave a Comment

2000 characters remaining