Chapter 17 of 23

File I/O in C++

Learn how to read from and write to files in C++ using ifstream, ofstream, and fstream. Work with text files, CSV data, and binary files with proper error handling.

Meritshot10 min read
C++File I/OifstreamofstreamCSVBinary Files
All C++ Chapters

File I/O in C++

Almost every real-world C++ program needs to persist data beyond a single run — reading configuration, logging results, importing student records, or writing reports. In C++, file input/output is handled through the <fstream> header, which provides three classes that mirror std::cin and std::cout but operate on files instead of the console.

For a software engineer at an Indian company — whether you are building an attendance system for a school in Pune, a payroll tool for a BPO in Gurgaon, or a data pipeline at Flipkart — understanding file I/O is a foundational skill.


The Three File Stream Classes

ClassHeaderPurpose
std::ifstream<fstream>Read from a file
std::ofstream<fstream>Write to a file
std::fstream<fstream>Both read and write

All three inherit from base classes in <iostream>, so the operators you already know (>>, <<, getline) work exactly the same way.


Opening Files and File Modes

You can open a file either in the constructor or by calling .open().

#include <fstream>
#include <iostream>

int main() {
    // Method 1: open in constructor
    std::ifstream inFile("data.txt");

    // Method 2: open explicitly
    std::ofstream outFile;
    outFile.open("results.txt");
}

File Opening Modes

Modes are bit flags from std::ios. You can combine them with the bitwise OR operator |.

ModeMeaning
std::ios::inOpen for reading (default for ifstream)
std::ios::outOpen for writing; truncates existing file (default for ofstream)
std::ios::appAppend; writes go to the end of file
std::ios::ateSeek to end immediately after opening
std::ios::truncTruncate file to zero length on open
std::ios::binaryOpen in binary mode (no newline translation)
// Append to a log file without overwriting
std::ofstream logFile("app.log", std::ios::app);

// Open for both reading and writing in binary mode
std::fstream dbFile("records.bin", std::ios::in | std::ios::out | std::ios::binary);

Error Handling — is_open() and fail()

Opening a file can fail if the path does not exist, permissions are denied, or the disk is full. Always check before operating on the stream.

std::ifstream inFile("students.csv");

if (!inFile.is_open()) {
    std::cerr << "Error: Could not open students.csv\n";
    return 1;
}

// After reading, check for errors
if (inFile.fail() && !inFile.eof()) {
    std::cerr << "Error: A read error occurred\n";
}

Stream State Flags

FlagMethodMeaning
goodbit.good()No errors
eofbit.eof()End of file reached
failbit.fail()Logical error (bad format)
badbit.bad()Unrecoverable I/O error

Reading Text Files

Reading Word by Word

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

int main() {
    std::ifstream file("words.txt");
    if (!file.is_open()) {
        std::cerr << "Cannot open file\n";
        return 1;
    }

    std::string word;
    while (file >> word) {
        std::cout << word << "\n";
    }
    file.close();  // optional — destructor closes automatically
}

Reading Line by Line with getline

When lines contain spaces (like full names or addresses), use std::getline:

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

int main() {
    std::ifstream file("addresses.txt");
    std::string line;

    while (std::getline(file, line)) {
        std::cout << "Line: " << line << "\n";
    }
}

Writing to Files

#include <fstream>
#include <iostream>

int main() {
    std::ofstream outFile("report.txt");

    if (!outFile) {  // implicit bool conversion
        std::cerr << "Failed to create report.txt\n";
        return 1;
    }

    outFile << "Quarterly Sales Report\n";
    outFile << "Region: Maharashtra\n";
    outFile << "Total Revenue: Rs. 45,20,000\n";

    // File is flushed and closed when outFile goes out of scope
}

Reading and Writing CSV Data

CSV (Comma-Separated Values) is the most common format for structured data in India's enterprise software — from HRMS exports to government portals.

Parsing a CSV Line

#include <fstream>
#include <iostream>
#include <sstream>
#include <string>
#include <vector>

std::vector<std::string> splitCSV(const std::string& line) {
    std::vector<std::string> fields;
    std::stringstream ss(line);
    std::string field;

    while (std::getline(ss, field, ',')) {
        fields.push_back(field);
    }
    return fields;
}

int main() {
    std::ifstream file("students.csv");
    if (!file.is_open()) {
        std::cerr << "Cannot open students.csv\n";
        return 1;
    }

    std::string line;
    std::getline(file, line);  // skip header row

    while (std::getline(file, line)) {
        auto fields = splitCSV(line);
        if (fields.size() >= 3) {
            std::string name = fields[0];
            int roll = std::stoi(fields[1]);
            double marks = std::stod(fields[2]);
            std::cout << name << " | Roll: " << roll << " | Marks: " << marks << "\n";
        }
    }
}

Binary File I/O

Binary mode stores data in its raw memory representation, which is compact and fast — ideal for large datasets where you do not need human-readable files.

#include <fstream>
#include <iostream>

struct Employee {
    char name[50];
    int employeeId;
    double salary;
};

int main() {
    Employee emp = {"Ravi Kumar", 1042, 95000.0};

    // Write binary
    std::ofstream out("employee.dat", std::ios::binary);
    out.write(reinterpret_cast<const char*>(&emp), sizeof(emp));
    out.close();

    // Read binary
    Employee emp2{};
    std::ifstream in("employee.dat", std::ios::binary);
    in.read(reinterpret_cast<char*>(&emp2), sizeof(emp2));
    in.close();

    std::cout << "Name: " << emp2.name << "\n";
    std::cout << "ID: " << emp2.employeeId << "\n";
    std::cout << "Salary: Rs." << emp2.salary << "\n";
}

Note: Binary files are not portable across machines with different endianness or compilers with different struct padding rules. For portable serialisation, prefer text or a serialisation library.


Worked Example: Student CSV Processor

This program reads student records from a CSV file, computes the class average, identifies pass/fail based on Indian university norms (40% passing), and writes a results file.

Input file (students.csv):

Name,RollNo,Marks
Aarav Sharma,101,78
Priya Nair,102,35
Rohan Desai,103,92
Fatima Khan,104,41
Arjun Mehta,105,28
Sneha Patel,106,65

Program:

#include <fstream>
#include <iomanip>
#include <iostream>
#include <sstream>
#include <string>
#include <vector>

struct Student {
    std::string name;
    int rollNo;
    double marks;
};

std::vector<std::string> splitCSV(const std::string& line) {
    std::vector<std::string> fields;
    std::stringstream ss(line);
    std::string field;
    while (std::getline(ss, field, ',')) {
        fields.push_back(field);
    }
    return fields;
}

int main() {
    // ---- READ ----
    std::ifstream inFile("students.csv");
    if (!inFile.is_open()) {
        std::cerr << "Error: Cannot open students.csv\n";
        return 1;
    }

    std::vector<Student> students;
    std::string line;
    std::getline(inFile, line);  // discard header

    while (std::getline(inFile, line)) {
        if (line.empty()) continue;
        auto fields = splitCSV(line);
        if (fields.size() < 3) continue;

        Student s;
        s.name   = fields[0];
        s.rollNo = std::stoi(fields[1]);
        s.marks  = std::stod(fields[2]);
        students.push_back(s);
    }
    inFile.close();

    if (students.empty()) {
        std::cerr << "No student records found.\n";
        return 1;
    }

    // ---- PROCESS ----
    double total = 0.0;
    for (const auto& s : students) {
        total += s.marks;
    }
    double average = total / students.size();

    // ---- WRITE ----
    std::ofstream outFile("results.txt");
    if (!outFile.is_open()) {
        std::cerr << "Error: Cannot create results.txt\n";
        return 1;
    }

    outFile << "=== Examination Results ===\n";
    outFile << std::left
            << std::setw(20) << "Name"
            << std::setw(10) << "Roll No"
            << std::setw(10) << "Marks"
            << std::setw(10) << "Status" << "\n";
    outFile << std::string(50, '-') << "\n";

    int passed = 0, failed = 0;
    for (const auto& s : students) {
        std::string status = (s.marks >= 40.0) ? "PASS" : "FAIL";
        if (s.marks >= 40.0) ++passed; else ++failed;

        outFile << std::left
                << std::setw(20) << s.name
                << std::setw(10) << s.rollNo
                << std::setw(10) << std::fixed << std::setprecision(1) << s.marks
                << std::setw(10) << status << "\n";
    }

    outFile << std::string(50, '-') << "\n";
    outFile << "Class Average : " << std::fixed << std::setprecision(2) << average << "\n";
    outFile << "Total Passed  : " << passed << "\n";
    outFile << "Total Failed  : " << failed << "\n";

    outFile.close();

    std::cout << "Results written to results.txt\n";
    std::cout << "Class average: " << average << "\n";

    return 0;
}

Output in results.txt:

=== Examination Results ===
Name                Roll No   Marks     Status
--------------------------------------------------
Aarav Sharma        101       78.0      PASS
Priya Nair          102       35.0      FAIL
Rohan Desai         103       92.0      PASS
Fatima Khan         104       41.0      PASS
Arjun Mehta         105       28.0      FAIL
Sneha Patel         106       65.0      PASS
--------------------------------------------------
Class Average : 56.50
Total Passed  : 4
Total Failed  : 2

Common Pitfalls

1. Not checking if the file opened successfully

Accessing a stream that failed to open causes silent data corruption or reads of empty strings. Always call is_open() or check the stream's boolean value before proceeding.

2. Using >> when you need getline

The >> operator stops at whitespace. For full lines or fields with spaces, use std::getline. Mixing the two without a file.ignore() call leaves a stray newline in the buffer, causing getline to read an empty line immediately.

int n;
file >> n;
file.ignore();         // discard the leftover '\n'
std::getline(file, line);  // now reads correctly

3. Forgetting std::ios::binary for binary files

On Windows, text mode converts \n to \r\n during writes. For binary data this corrupts the file. Always use std::ios::binary when writing raw structs or images.

4. Using reinterpret_cast for non-trivial types

write/read with reinterpret_cast only works for plain-old-data (POD) types. Writing a std::string or std::vector this way writes a pointer, not the data. Use text serialisation for complex objects.

5. Assuming files close automatically in error paths

While the destructor closes the file, calling close() explicitly is good practice when you need to be sure the data is flushed before the next operation reads the file.


Practice Exercises

  1. Write a program that reads a text file and counts the number of lines, words, and characters. Print a summary similar to the Unix wc command.

  2. Create a program that maintains a simple to-do list stored in todo.txt. Support two operations: --add "task description" (append a new task) and --list (print all tasks with line numbers).

  3. Read a CSV file containing product inventory (name,quantity,price) for a kirana store. Write a new CSV containing only items where the quantity is below 10, as a low-stock alert.

  4. Write a binary file containing an array of 5 double values representing daily temperature readings in Jaipur. Then write a separate program that reads the binary file and computes the weekly average.

  5. Write a program that reads a log file line by line and writes every line containing the word "ERROR" to a separate errors.txt file.

  6. Extend the student CSV example to compute the highest and lowest marks, and add a line at the top of results.txt displaying the topper's name and score.


Summary

  • Use std::ifstream for reading, std::ofstream for writing, and std::fstream for both.
  • File modes like std::ios::app, std::ios::binary, and std::ios::trunc control how the file is opened.
  • Always verify the file opened successfully with is_open() or the stream's boolean conversion before reading or writing.
  • Use std::getline for line-by-line reading; use a std::stringstream with a delimiter to parse CSV fields.
  • Binary mode (std::ios::binary) stores raw memory representation — compact but platform-dependent.
  • Use reinterpret_cast with write/read only for POD (plain-old-data) structs.
  • Mixing >> and getline requires file.ignore() to clear the leftover newline from the buffer.
  • File streams flush and close automatically when they go out of scope, but explicit close() is clearer and ensures data is written before the next read.