File Handling in C++: Read, Write, Append and Seek with Examples

Learn how C++ file streams work through connected text and binary examples, from safe record parsing to appending data and updating a fixed record.

KnowledgeGate Team

Exam prep & CS education

Updated 29 Aug 20266 min read

Console input disappears when a program ends, but real programs must save marks, logs or settings and read them later. Beginners usually get stuck on three choices: whether to use ifstream, ofstream or fstream, which open mode protects existing data, and how to detect a failed read instead of using bad data. Use ifstream for reading, ofstream for writing, and fstream for both; choose an open mode that preserves or replaces content, and check stream state after important operations.

Build the C++ file-stream mental model

The <fstream> header provides three stream classes. std::ifstream reads from a file, std::ofstream writes to one, and std::fstream can do both. A relative name such as scores.txt is resolved from the program's working directory, which may differ from the directory containing the source file.

Mode

Meaning

std::ios::in

Open for input

std::ios::out

Open for output

std::ios::app

Force every write to the end

std::ios::ate

Start at the end, but allow later seeks

std::ios::trunc

Discard existing content

std::ios::binary

Suppress text-mode translation

A plain std::ofstream out("scores.txt") opens for output and replaces prior content. Use the lifecycle open, verify, use, check, close. A stream closes automatically when its scope ends. Call close() explicitly only when another operation must use the file before that scope ends. The Coding & Skill Development Courses catalogue also includes related programming topics.

Write three records, then read them line by line

Create the file in one scope, then open it again in a second scope:

#include <fstream>
#include <iostream>
#include <string>

int main() {
    {
        std::ofstream out("scores.txt");
        if (!out) {
            std::cerr << "Could not open scores.txt for writing\n";
            return 1;
        }
        out << "101,Asha,78\n"
            << "102,Bharat,91\n"
            << "103,Charu,84\n";
        if (!out) {
            std::cerr << "Write failed\n";
            return 1;
        }
    }

    {
        std::ifstream in("scores.txt");
        if (!in) {
            std::cerr << "Could not open scores.txt for reading\n";
            return 1;
        }
        std::string line;
        while (std::getline(in, line)) {
            std::cout << line << '\n';
        }
        if (!in.eof()) {
            std::cerr << "Read failed before EOF\n";
            return 1;
        }
    }
}

The resulting scores.txt is:

101,Asha,78
102,Bharat,91
103,Charu,84

The console prints those same three lines, once each. while (std::getline(in, line)) is correct because extraction and validation happen together. The loop ends normally at EOF, while the later check distinguishes EOF from an actual I/O failure.

C++ ofstream writes three CSV score records to scores.txt, and ifstream reads the same three lines back to the console.

Parse records safely and calculate a checked result

Add <sstream>, <iomanip> and <exception>, then split each line before converting it. Keep the risky conversion inside a small try block and reject trailing junk such as 91x:

bool parseIntStrict(const std::string& text, int& value) {
    std::size_t used = 0;
    try {
        value = std::stoi(text, &used);
        return !text.empty() && used == text.size();
    } catch (const std::exception&) {
        return false;
    }
}

std::istringstream row(line);
std::string idText, name, markText, extra;

if (!std::getline(row, idText, ',') ||
    !std::getline(row, name, ',') ||
    !std::getline(row, markText, ',') ||
    idText.empty() || name.empty() || markText.empty() ||
    std::getline(row, extra, ',')) {
    std::cerr << "Invalid record: " << line << '\n';
    continue;
}

int id = 0, mark = 0;
if (!parseIntStrict(idText, id) || !parseIntStrict(markText, mark)) {
    std::cerr << "Invalid record: " << line << '\n';
    continue;
}
++count;
total += mark;

For the three valid rows, count is 3, total is 78 + 91 + 84 = 253, and std::fixed << std::setprecision(2) prints the average as 253 / 3 = 84.33. If 104,Dev,not-a-number is added, the parser reports it as invalid. Count remains 3 and total remains 253.

Append without erasing earlier data

First recreate the valid three-line scores.txt. Then append one record:

std::ofstream out("scores.txt", std::ios::app);
if (!out) {
    std::cerr << "Could not open scores.txt\n";
    return 1;
}
out << "104,Dev,88\n";
if (!out) {
    std::cerr << "Append failed\n";
    return 1;
}

The file now contains:

101,Asha,78
102,Bharat,91
103,Charu,84
104,Dev,88

The checked parser now reports count 4, total 78 + 91 + 84 + 88 = 341, and average 341 / 4 = 85.25. Mode app preserves the file and forces writes to its end. A plain output stream would truncate it. Mode ate only chooses the initial position, so a later seek can move elsewhere. For more small practice problems across several languages, use Classic Programs in C, Java and Python as a practice set, not as C++ file-I/O documentation.

Seek to a fixed binary record, read it, and update it

On a system with 8-bit bytes, five std::int32_t values occupy 5 * 4 = 20 bytes. The program reads the third value at byte 8, replaces the fourth at byte 12, and checks each open, seek, read, and write shown:

#include <cstdint>
#include <fstream>
#include <iostream>

int main() {
    std::int32_t values[]{10, 20, 30, 40, 50};
    {
        std::ofstream out("inventory.bin", std::ios::binary);
        if (!out || !out.write(reinterpret_cast<const char*>(values),
                               sizeof(values))) return 1;
    }

    {
        std::ifstream in("inventory.bin", std::ios::binary);
        if (!in || !in.seekg(2 * sizeof(std::int32_t), std::ios::beg)) return 1;
        std::int32_t third = 0;
        if (!in.read(reinterpret_cast<char*>(&third), sizeof(third))) return 1;
        std::cout << "Third: " << third << '\n';
    }

    {
        std::fstream file("inventory.bin",
            std::ios::in | std::ios::out | std::ios::binary);
        if (!file || !file.seekp(3 * sizeof(std::int32_t), std::ios::beg)) return 1;
        std::int32_t replacement = 44;
        if (!file.write(reinterpret_cast<const char*>(&replacement),
                        sizeof(replacement))) return 1;
    }

    std::int32_t result[5]{};
    std::ifstream in("inventory.bin", std::ios::binary);
    if (!in || !in.read(reinterpret_cast<char*>(result), sizeof(result))) return 1;
    for (std::int32_t value : result) std::cout << value << ' ';
}

The read offset is 2 * 4 = 8, so Third: 30 is printed. The write offset is 3 * 4 = 12; re-reading prints 10 20 30 44 50. This native layout suits a controlled demonstration, not portable interchange between machines with different byte ordering or representation rules.

A 20-byte inventory.bin of five 4-byte integers where a seek reads 30 at byte 8 and writes 44 over 40 at byte 12.

Diagnose the file-handling failures beginners actually hit

Symptom

Cause

Fix

File never opens

Wrong working directory or path

Print or verify the working path

Old content disappears

Output defaulted to truncation

Choose std::ios::app deliberately

Last record is duplicated

EOF was checked before extraction

Make extraction the loop condition

getline returns a blank line

operator>> left a newline pending

Consume that newline before getline

105,Esha, becomes a zero mark

Missing field was not validated

Check field count and content first

An operation silently fails

Stream state was ignored

Test the stream after important operations

Destructors close streams automatically, but a successful close cannot rescue an earlier unchecked write. If reinterpret_cast in the binary example feels unfamiliar, Pointers in C: GATE Memory Diagrams is an optional C-based address and memory refresher. The syntax here still belongs to C++.

How assessments and interviews turn this into questions

Three useful question formats test the idea without depending on any named exam pattern:

  1. Output tracing: Write AB to note.txt with a default ofstream, close it, append CD with std::ios::app, then read the file. The exact result is ABCD.

  2. Mode selection: Which mode preserves existing content and forces every new write to the end? The answer is std::ios::app.

  3. Bug repair: Replace while (!in.eof()) { std::getline(in, line); /* use line */ } with while (std::getline(in, line)) { /* use line */ }.

The short version and your next action

ifstream reads. ofstream writes. fstream reads and writes. The open mode decides whether content is replaced, preserved or treated as binary. Make extraction the condition, and check stream state after important operations.

Build the complete foundation with the C++ Programming course. If you also want other languages and competitive-coding practice for placements, Coding for Placements is another option.