Files and Streams

File streams work just like std::cin and std::cout: std::ofstream writes to a file with <<, std::ifstream reads with >> and std::getline. Files close automatically when the stream object goes out of scope. The <filesystem> library handles paths, folders and file information.

Writing a file

C++
#include <fstream>
#include <iostream>

int main() {
    std::ofstream out{"shopping.txt"};
    if (!out) {
        std::cerr << "could not open shopping.txt for writing\n";
        return 1;
    }
    out << "eggs,12\n";
    out << "milk,2\n";
    out << "bread,1\n";
    std::cout << "saved\n";
}   // out is closed (and flushed) here
Output
saved

A stream converts to false if opening failed. Opening an ofstream replaces any existing file; pass std::ios::app as a second argument to append instead.

Reading line by line

TXT inventory.csv
apples,30,0.45
bananas,24,0.30
cherries,200,0.05
dates,15,0.80
C++
#include <fstream>
#include <iomanip>
#include <iostream>
#include <sstream>
#include <string>

int main() {
    std::ifstream in{"inventory.csv"};
    if (!in) {
        std::cerr << "cannot open inventory.csv\n";
        return 1;
    }
    std::string line;
    double total = 0;
    while (std::getline(in, line)) {
        std::istringstream fields{line};
        std::string name;
        int quantity = 0;
        double price = 0;
        char comma1 = 0;
        std::getline(fields, name, ',');
        if (!(fields >> quantity >> comma1 >> price) || comma1 != ',') {
            std::cerr << "bad line: " << line << '\n';
            continue;
        }
        std::cout << std::left << std::setw(10) << name << std::right << std::setw(5) << quantity
                  << " x " << std::fixed << std::setprecision(2) << price << '\n';
        total += quantity * price;
    }
    std::cout << "Total stock value: " << total << '\n';
}
Output
apples       30 x 0.45
bananas      24 x 0.30
cherries    200 x 0.05
dates        15 x 0.80
Total stock value: 42.70

Reading a whole line, then parsing it with a std::istringstream, keeps one bad line from derailing the rest — the same approach as for keyboard input.

Reading a whole file

C++
#include <fstream>
#include <iostream>
#include <sstream>
#include <string>

std::string read_file(const std::string& path) {
    std::ifstream in{path};
    std::ostringstream contents;
    contents << in.rdbuf();
    return contents.str();
}

int main() {
    {
        std::ofstream{"note.txt"} << "line one\nline two\n";
    }
    std::string text = read_file("note.txt");
    std::cout << text.size() << " characters:\n" << text;
}
Output
18 characters:
line one
line two

Words and numbers

TXT scores.txt
Ana 82
Ben 91
Chloe 77
C++
#include <fstream>
#include <iostream>
#include <string>

int main() {
    std::ifstream in{"scores.txt"};
    std::string name;
    int score = 0, count = 0, sum = 0;
    while (in >> name >> score) {
        sum += score;
        ++count;
    }
    std::cout << count << " students, average " << static_cast<double>(sum) / count << '\n';
}
Output
3 students, average 83.3333

Binary files

Open with std::ios::binary and use write/read for raw bytes. As in C, writing structs directly ties the file to your compiler's layout and byte order — fine for caches, not for files shared between systems:

C++
#include <fstream>
#include <iostream>
#include <vector>

int main() {
    std::vector<double> readings{21.5, 22.0, 21.75};
    {
        std::ofstream out{"readings.bin", std::ios::binary};
        out.write(reinterpret_cast<const char*>(readings.data()),
                  static_cast<std::streamsize>(readings.size() * sizeof(double)));
    }
    std::vector<double> loaded(3);
    std::ifstream in{"readings.bin", std::ios::binary};
    in.read(reinterpret_cast<char*>(loaded.data()), static_cast<std::streamsize>(loaded.size() * sizeof(double)));
    std::cout << "read " << in.gcount() << " bytes: " << loaded[0] << ' ' << loaded[1] << ' ' << loaded[2] << '\n';
}
Output
read 24 bytes: 21.5 22 21.75

std::filesystem

C++17's <filesystem> works with paths, folders and file metadata portably:

C++
#include <filesystem>
#include <fstream>
#include <iostream>

namespace fs = std::filesystem;

int main() {
    fs::path dir = "reports";
    fs::create_directories(dir / "2026");
    std::ofstream{dir / "2026" / "march.txt"} << "sales: 12400\n";
    std::ofstream{dir / "2026" / "april.txt"} << "sales: 13950\n";

    fs::path p = dir / "2026" / "march.txt";
    std::cout << "file: " << p.filename() << ", stem: " << p.stem() << ", extension: " << p.extension() << '\n';
    std::cout << "exists: " << std::boolalpha << fs::exists(p) << ", size: " << fs::file_size(p) << " bytes\n";

    int count = 0;
    for (const auto& entry : fs::directory_iterator(dir / "2026")) {
        if (entry.is_regular_file()) ++count;
    }
    std::cout << count << " files in " << (dir / "2026").string() << '\n';
    fs::remove_all(dir);
}
Output
file: "march.txt", stem: "march", extension: ".txt"
exists: true, size: 13 bytes
2 files in reports/2026

/ joins path parts with the right separator for the platform. Directory iteration order is unspecified, so sort entries if order matters.

Try it yourself

Copy a text file line by line, numbering the lines.

TXT poem.txt
Roses are red,
templates are tricky,
RAII cleans up
so leaks don't get sticky.
Try it yourself · main.cpp
Edit the code, copy it into main.cpp, then compile and run it with c++ -std=c++20 -Wall main.cpp -o main && ./main. How to set up a C++ compiler
Output
  1  Roses are red,
  2  templates are tricky,
  3  RAII cleans up
  4  so leaks don't get sticky.