File Handling

File handling is an essential part of any programming language as it allows you to store and retrieve data from external files. C++ provides a comprehensive set of classes and functions in the <fstream> header for file input and output operations.

Introduction to File Handling

File handling in C++ enables you to:

  • Store program output in a file
  • Read data from existing files
  • Process data in files
  • Create data records
  • Update information in files

C++ uses streams for file operations, similar to how cout and cin work for console input/output.

File Stream Classes

C++ provides three main classes for file operations:

  1. ifstream (Input File Stream): Used for reading from files
  2. ofstream (Output File Stream): Used for writing to files
  3. fstream (File Stream): Used for both reading from and writing to files

These classes are derived from ios base class through istream and ostream classes.

Opening and Closing a File

Opening a File

To perform operations on a file, you first need to open it. You can open a file in two ways:

Method 1: Using the constructor

#include <fstream>
#include <iostream>
using namespace std;

int main() {
    // Opening a file for reading
    ifstream inputFile("data.txt");
    
    // Opening a file for writing
    ofstream outputFile("output.txt");
    
    // Opening a file for both reading and writing
    fstream dataFile("records.txt", ios::in | ios::out);
    
    // Check if files were opened successfully
    if (!inputFile) {
        cout << "Error opening input file!" << endl;
        return 1;
    }
    
    if (!outputFile) {
        cout << "Error opening output file!" << endl;
        return 1;
    }
    
    if (!dataFile) {
        cout << "Error opening data file!" << endl;
        return 1;
    }
    
    // File operations will go here...
    
    return 0;
}

Method 2: Using the open() method

#include <fstream>
#include <iostream>
using namespace std;

int main() {
    ifstream inputFile;
    ofstream outputFile;
    fstream dataFile;
    
    // Opening files
    inputFile.open("data.txt");
    outputFile.open("output.txt");
    dataFile.open("records.txt", ios::in | ios::out);
    
    // Check if files were opened successfully
    if (!inputFile.is_open()) {
        cout << "Error opening input file!" << endl;
        return 1;
    }
    
    // File operations will go here...
    
    return 0;
}

Closing a File

Once you’re done with a file, you should close it to release the system resources. You can close a file using the close() method:

inputFile.close();
outputFile.close();
dataFile.close();

File Opening Modes

When opening a file, you can specify the mode that determines how the file should be opened. File modes are defined in the ios class:

ModeDescription
ios::inOpen for input operations (reading)
ios::outOpen for output operations (writing)
ios::appAppend to the end of the file
ios::ateSet the initial position at the end of the file
ios::truncIf the file exists, its contents will be truncated before opening
ios::binaryOpen in binary mode (instead of text mode)

You can combine these modes using the bitwise OR operator (|):

// Open a file for both reading and writing in binary mode
fstream file("data.dat", ios::in | ios::out | ios::binary);

// Open a file for appending
ofstream logFile("log.txt", ios::app);

// Open a file for writing and truncate it if it exists
ofstream newFile("newdata.txt", ios::out | ios::trunc);

Reading from a File

There are several ways to read data from a file in C++:

1. Using extraction operator (>>)

#include <fstream>
#include <iostream>
#include <string>
using namespace std;

int main() {
    ifstream inputFile("data.txt");
    
    if (!inputFile) {
        cout << "Error opening file!" << endl;
        return 1;
    }
    
    string word;
    int number;
    
    // Read a string and a number from the file
    inputFile >> word >> number;
    
    cout << "Word: " << word << endl;
    cout << "Number: " << number << endl;
    
    inputFile.close();
    return 0;
}

The extraction operator skips whitespace, which is useful for reading formatted data.

2. Using getline() for reading lines

#include <fstream>
#include <iostream>
#include <string>
using namespace std;

int main() {
    ifstream inputFile("data.txt");
    
    if (!inputFile) {
        cout << "Error opening file!" << endl;
        return 1;
    }
    
    string line;
    
    // Read the file line by line
    while (getline(inputFile, line)) {
        cout << line << endl;
    }
    
    inputFile.close();
    return 0;
}

3. Using get() for reading characters

#include <fstream>
#include <iostream>
using namespace std;

int main() {
    ifstream inputFile("data.txt");
    
    if (!inputFile) {
        cout << "Error opening file!" << endl;
        return 1;
    }
    
    char ch;
    
    // Read the file character by character
    while (inputFile.get(ch)) {
        cout << ch;
    }
    
    inputFile.close();
    return 0;
}

4. Reading binary data

#include <fstream>
#include <iostream>
using namespace std;

struct Person {
    char name[50];
    int age;
    double salary;
};

int main() {
    ifstream inputFile("people.dat", ios::binary);
    
    if (!inputFile) {
        cout << "Error opening file!" << endl;
        return 1;
    }
    
    Person person;
    
    // Read a Person object from the file
    inputFile.read(reinterpret_cast<char*>(&person), sizeof(Person));
    
    cout << "Name: " << person.name << endl;
    cout << "Age: " << person.age << endl;
    cout << "Salary: " << person.salary << endl;
    
    inputFile.close();
    return 0;
}

Writing to a File

Similar to reading, there are multiple ways to write data to a file:

1. Using insertion operator (<<)

#include <fstream>
#include <iostream>
using namespace std;

int main() {
    ofstream outputFile("output.txt");
    
    if (!outputFile) {
        cout << "Error opening file!" << endl;
        return 1;
    }
    
    string name = "John Doe";
    int age = 25;
    
    // Write formatted data to the file
    outputFile << "Name: " << name << endl;
    outputFile << "Age: " << age << endl;
    
    outputFile.close();
    return 0;
}

2. Using put() for writing characters

#include <fstream>
#include <iostream>
#include <string>
using namespace std;

int main() {
    ofstream outputFile("output.txt");
    
    if (!outputFile) {
        cout << "Error opening file!" << endl;
        return 1;
    }
    
    string message = "Hello, world!";
    
    // Write the string character by character
    for (char ch : message) {
        outputFile.put(ch);
    }
    
    outputFile.close();
    return 0;
}

3. Writing binary data

#include <fstream>
#include <iostream>
#include <cstring>
using namespace std;

struct Person {
    char name[50];
    int age;
    double salary;
};

int main() {
    ofstream outputFile("people.dat", ios::binary);
    
    if (!outputFile) {
        cout << "Error opening file!" << endl;
        return 1;
    }
    
    Person person;
    strcpy(person.name, "Alice Johnson");
    person.age = 30;
    person.salary = 75000.50;
    
    // Write a Person object to the file
    outputFile.write(reinterpret_cast<char*>(&person), sizeof(Person));
    
    outputFile.close();
    return 0;
}

File Pointers and Random Access

C++ provides functions to move the file pointer, allowing you to access data at any position in the file:

1. tellg() and tellp()

These functions return the current position of the file pointer:

  • tellg(): Returns the position of the get pointer (for reading)
  • tellp(): Returns the position of the put pointer (for writing)

2. seekg() and seekp()

These functions move the file pointer to a specified position:

  • seekg(): Moves the get pointer
  • seekp(): Moves the put pointer
#include <fstream>
#include <iostream>
using namespace std;

int main() {
    fstream file("data.txt", ios::in | ios::out);
    
    if (!file) {
        cout << "Error opening file!" << endl;
        return 1;
    }
    
    // Get the current position
    streampos currentPos = file.tellg();
    cout << "Current position: " << currentPos << endl;
    
    // Move to the 10th byte in the file
    file.seekg(10, ios::beg);
    
    // Move 5 bytes forward from the current position
    file.seekg(5, ios::cur);
    
    // Move 3 bytes backward from the end of the file
    file.seekg(-3, ios::end);
    
    file.close();
    return 0;
}

The second parameter in seekg() and seekp() specifies the reference point:

  • ios::beg: Beginning of the file
  • ios::cur: Current position
  • ios::end: End of the file

Practical Examples

Example 1: Copying a file

#include <fstream>
#include <iostream>
using namespace std;

int main() {
    ifstream sourceFile("source.txt");
    ofstream destFile("destination.txt");
    
    if (!sourceFile || !destFile) {
        cout << "Error opening files!" << endl;
        return 1;
    }
    
    char ch;
    
    // Read characters from the source file and write them to the destination file
    while (sourceFile.get(ch)) {
        destFile.put(ch);
    }
    
    cout << "File copied successfully!" << endl;
    
    sourceFile.close();
    destFile.close();
    return 0;
}

Example 2: Counting characters, words, and lines in a file

#include <fstream>
#include <iostream>
#include <string>
using namespace std;

int main() {
    ifstream file("data.txt");
    
    if (!file) {
        cout << "Error opening file!" << endl;
        return 1;
    }
    
    int charCount = 0;
    int wordCount = 0;
    int lineCount = 0;
    string line;
    bool inWord = false;
    char ch;
    
    while (file.get(ch)) {
        charCount++;
        
        // Count words
        if (isspace(ch)) {
            inWord = false;
        } else if (!inWord) {
            inWord = true;
            wordCount++;
        }
        
        // Count lines
        if (ch == '\n') {
            lineCount++;
        }
    }
    
    // If the file doesn't end with a newline, increment the line count
    if (charCount > 0 && ch != '\n') {
        lineCount++;
    }
    
    cout << "Character count: " << charCount << endl;
    cout << "Word count: " << wordCount << endl;
    cout << "Line count: " << lineCount << endl;
    
    file.close();
    return 0;
}

Example 3: Reading and writing records

#include <fstream>
#include <iostream>
#include <string>
#include <vector>
using namespace std;

struct Student {
    int id;
    string name;
    float gpa;
};

void writeStudents(const vector<Student>& students, const string& filename) {
    ofstream outFile(filename);
    
    if (!outFile) {
        cout << "Error opening file for writing!" << endl;
        return;
    }
    
    for (const auto& student : students) {
        outFile << student.id << "," << student.name << "," << student.gpa << endl;
    }
    
    outFile.close();
    cout << "Students written to file successfully!" << endl;
}

vector<Student> readStudents(const string& filename) {
    ifstream inFile(filename);
    vector<Student> students;
    
    if (!inFile) {
        cout << "Error opening file for reading!" << endl;
        return students;
    }
    
    string line;
    while (getline(inFile, line)) {
        Student student;
        size_t pos1 = line.find(',');
        size_t pos2 = line.find(',', pos1 + 1);
        
        if (pos1 != string::npos && pos2 != string::npos) {
            student.id = stoi(line.substr(0, pos1));
            student.name = line.substr(pos1 + 1, pos2 - pos1 - 1);
            student.gpa = stof(line.substr(pos2 + 1));
            students.push_back(student);
        }
    }
    
    inFile.close();
    return students;
}

void displayStudents(const vector<Student>& students) {
    cout << "\nStudent Records:" << endl;
    cout << "-----------------------------------------" << endl;
    cout << "ID\tName\t\tGPA" << endl;
    cout << "-----------------------------------------" << endl;
    
    for (const auto& student : students) {
        cout << student.id << "\t" << student.name << "\t\t" << student.gpa << endl;
    }
}

int main() {
    vector<Student> students = {
        {1001, "John Smith", 3.75},
        {1002, "Mary Johnson", 3.92},
        {1003, "Robert Brown", 3.48}
    };
    
    // Write students to file
    writeStudents(students, "students.csv");
    
    // Read students from file
    vector<Student> loadedStudents = readStudents("students.csv");
    
    // Display loaded students
    displayStudents(loadedStudents);
    
    return 0;
}

Error Handling in File Operations

When working with files, it’s important to handle potential errors:

1. Checking if a file was opened successfully

ifstream file("data.txt");

if (!file) {
    cerr << "Error: Unable to open file!" << endl;
    return 1;
}

2. Using is_open()

ifstream file;
file.open("data.txt");

if (!file.is_open()) {
    cerr << "Error: Unable to open file!" << endl;
    return 1;
}

3. Checking for end of file (EOF)

while (!file.eof()) {
    // Read data from file
}

However, it’s often better to check the result of the read operation directly:

string line;
while (getline(file, line)) {
    // Process the line
}

4. Checking for other errors

if (file.fail()) {
    cerr << "A reading error occurred!" << endl;
}

if (file.bad()) {
    cerr << "A fatal error occurred!" << endl;
}

Best Practices for File Handling

  1. Always check if a file was opened successfully before performing operations on it.

  2. Close files when you’re done with them to free up system resources.

  3. Use appropriate error handling to make your programs robust.

  4. Use binary mode for non-text files to avoid issues with special characters.

  5. Be careful with file paths. Use relative paths when possible, and remember that backslashes in Windows paths need to be escaped in C++ strings.

  6. Handle exceptions that might occur during file operations.

#include <fstream>
#include <iostream>
#include <stdexcept>
using namespace std;

void processFile(const string& filename) {
    try {
        ifstream file(filename);
        
        if (!file) {
            throw runtime_error("Cannot open file: " + filename);
        }
        
        // Process the file...
        
        file.close();
    } catch (const exception& e) {
        cerr << "Error: " << e.what() << endl;
    }
}

Summary

File handling in C++ allows you to work with external files for storing and retrieving data. The main classes for file operations are ifstream, ofstream, and fstream, which provide various methods for reading, writing, and navigating through files.

Understanding file handling is essential for developing applications that need to persist data, process large datasets, or interact with external systems through files.