Introduction
File handling is one of the most essential features of C++ programming, allowing developers to store, retrieve, and manipulate data permanently. Unlike variables that lose their data when a program terminates, files provide a way to preserve data between executions. Whether you are building an application that stores user preferences, logs system events, or processes large datasets, understanding file handling is critical.
C++ provides a robust set of tools for handling files using its stream-based input/output system, which is part of the <fstream>
library. These tools allow reading from and writing to both text and binary files efficiently.
This post explores the advanced concepts of file handling in C++, focusing on streams, file types, random access, error handling, and serialization.
1. File Input and Output Streams (ifstream, ofstream, fstream)
In C++, files are handled using streams, which represent a flow of data between the program and an external file. The concept of streams comes from the standard I/O system used for reading and writing to the console, but in this case, it applies to files.
C++ provides three primary classes in the <fstream>
header for file operations:
ifstream
– Used for reading from files (input file stream).ofstream
– Used for writing to files (output file stream).fstream
– Used for both reading and writing to files (file stream).
Example: Opening a File
#include <iostream>
#include <fstream>
using namespace std;
int main() {
ofstream outFile("example.txt");
if (outFile.is_open()) {
outFile << "Hello, this is a test file.\n";
outFile << "C++ file handling is powerful!";
outFile.close();
cout << "File written successfully.";
} else {
cout << "Error opening file!";
}
return 0;
}
Here, the ofstream
object outFile
creates and writes to a file named example.txt. If the file does not exist, it will be created automatically.
Opening Modes
C++ allows files to be opened in various modes using flags:
ios::in
– Open for reading.ios::out
– Open for writing.ios::app
– Append mode (write data at the end of the file).ios::ate
– Open and move to the end of the file immediately.ios::trunc
– Delete existing content before writing new data.ios::binary
– Open file in binary mode instead of text mode.
Example:
fstream file("data.txt", ios::in | ios::out | ios::app);
This opens data.txt for both reading and writing, appending new data at the end.
2. Reading and Writing Text Files
Text files store data as human-readable characters. Reading and writing text files in C++ is straightforward using ifstream
and ofstream
.
Writing to a Text File
ofstream file("student.txt");
if (file.is_open()) {
file << "Name: Alice\n";
file << "Age: 21\n";
file << "Grade: A\n";
file.close();
}
Reading from a Text File
ifstream file("student.txt");
string line;
if (file.is_open()) {
while (getline(file, line)) {
cout << line << endl;
}
file.close();
}
This program reads the file line by line using getline()
.
Using fstream
for Both Reading and Writing
fstream file("notes.txt", ios::in | ios::out | ios::app);
file << "Additional notes added here.\n";
file.seekg(0);
string content;
while (getline(file, content))
cout << content << endl;
file.close();
3. Working with Binary Files
While text files are ideal for storing readable data, binary files are used when dealing with raw data such as images, audio, video, or structured data. Binary files are faster and more memory-efficient because they store data in its raw form.
Writing Binary Data
#include <iostream>
#include <fstream>
using namespace std;
struct Student {
char name[50];
int age;
float grade;
};
int main() {
Student s1 = {"John Doe", 20, 88.5};
ofstream outFile("student.dat", ios::binary);
outFile.write(reinterpret_cast<char*>(&s1), sizeof(s1));
outFile.close();
cout << "Binary file written successfully!";
return 0;
}
Reading Binary Data
ifstream inFile("student.dat", ios::binary);
Student s2;
inFile.read(reinterpret_cast<char*>(&s2), sizeof(s2));
inFile.close();
cout << "Name: " << s2.name << endl;
cout << "Age: " << s2.age << endl;
cout << "Grade: " << s2.grade << endl;
Binary files are especially useful for applications that handle structured data and require fast input/output operations.
4. Random Access in Files
C++ allows random access to file data using file pointers. This means you can move the file pointer to a specific location to read or write data without sequentially processing the entire file.
File Pointer Functions
seekg(position)
– Moves the get pointer (used for reading).seekp(position)
– Moves the put pointer (used for writing).tellg()
– Returns the current position of the get pointer.tellp()
– Returns the current position of the put pointer.
Example: Using Random Access
#include <fstream>
#include <iostream>
using namespace std;
int main() {
fstream file("data.txt", ios::in | ios::out | ios::trunc);
file << "ABCDEFGH";
file.seekp(3); // Move write pointer to the 4th character
file << "Z";
file.seekg(0);
string content;
getline(file, content);
cout << "Modified content: " << content << endl;
file.close();
}
Output:
Modified content: ABCZEFGH
Random access is particularly useful for updating specific records in large data files without rewriting the entire file.
5. Handling Errors in File Operations
Error handling is an essential part of robust file management. Files may fail to open, read, or write due to various reasons such as missing files, lack of permissions, or disk issues.
C++ provides several mechanisms to detect and handle such errors.
Common Error Flags
fail()
– Returns true if an input/output operation fails.bad()
– Returns true if a serious error occurs (like loss of data integrity).eof()
– Returns true if the end of file is reached.good()
– Returns true if no error has occurred.
Example: Checking File Status
ifstream file("nonexistent.txt");
if (!file) {
cout << "Error: File not found." << endl;
} else {
cout << "File opened successfully." << endl;
}
Example: Using Exception Handling
You can also use exceptions for error handling in file operations.
fstream file;
file.exceptions(fstream::failbit | fstream::badbit);
try {
file.open("data.txt", ios::in);
cout << "File opened successfully.";
file.close();
}
catch (fstream::failure& e) {
cout << "File operation failed: " << e.what() << endl;
}
This approach ensures that serious I/O errors are caught and handled gracefully.
6. Serialization and Deserialization
Serialization is the process of converting an object into a stream of bytes so that it can be stored in a file or sent over a network. Deserialization is the reverse process — reconstructing the object from that stream.
Example: Object Serialization
#include <fstream>
#include <iostream>
using namespace std;
struct Employee {
char name[50];
int id;
double salary;
};
int main() {
Employee e1 = {"David", 101, 55000.50};
ofstream outFile("employee.bin", ios::binary);
outFile.write(reinterpret_cast<char*>(&e1), sizeof(e1));
outFile.close();
}
Example: Object Deserialization
ifstream inFile("employee.bin", ios::binary);
Employee e2;
inFile.read(reinterpret_cast<char*>(&e2), sizeof(e2));
inFile.close();
cout << "Employee Name: " << e2.name << endl;
cout << "Employee ID: " << e2.id << endl;
cout << "Employee Salary: " << e2.salary << endl;
Serialization is essential in systems like databases, games, and distributed applications where object persistence is required.
7. Practical File Handling Examples
Example 1: Counting Words in a File
#include <iostream>
#include <fstream>
#include <string>
using namespace std;
int main() {
ifstream file("article.txt");
string word;
int count = 0;
while (file >> word)
count++;
cout << "Total words: " << count;
file.close();
}
Example 2: Copying One File to Another
ifstream inFile("source.txt");
ofstream outFile("destination.txt");
string line;
while (getline(inFile, line))
outFile << line << endl;
inFile.close();
outFile.close();
cout << "File copied successfully.";
Example 3: Appending Data to an Existing File
ofstream file("log.txt", ios::app);
file << "New log entry added.\n";
file.close();
Example 4: Updating Specific Records in a Binary File
#include <fstream>
#include <iostream>
using namespace std;
struct Student {
char name[50];
int roll;
float marks;
};
int main() {
fstream file("students.dat", ios::in | ios::out | ios::binary);
Student s;
int record = 1; // update 2nd record (0-based index)
file.seekp(record * sizeof(Student));
s = {"Updated Name", 2, 95.5};
file.write(reinterpret_cast<char*>(&s), sizeof(s));
file.close();
}
This technique allows direct modification of data in large databases.
Leave a Reply