File I/O in C++ is like having a filing system - you can read documents (input), write new ones (output), or do both! Streams are the pipelines that connect your program to files.
#include
#include
#include
using namespace std;
// Writing to a file
void writeToFile() {
ofstream outFile("output.txt");
if (!outFile) {
cerr << "Error opening file for writing!" << endl;
return;
}
outFile << "Hello, File I/O!" << endl;
outFile << "Line 2" << endl;
outFile << "The answer is: " << 42 << endl;
outFile.close(); // Optional - destructor closes automatically
}
// Reading from a file
void readFromFile() {
ifstream inFile("input.txt");
if (!inFile.is_open()) {
cerr << "Error opening file for reading!" << endl;
return;
}
string line;
while (getline(inFile, line)) {
cout << "Read: " << line << endl;
}
inFile.close();
}
// Reading different data types
void readStructuredData() {
ifstream inFile("data.txt");
string name;
int age;
double salary;
while (inFile >> name >> age >> salary) {
cout << name << " is " << age << " years old, earns $"
<< salary << endl;
}
}
// Binary file I/O
void binaryFileOperations() {
// Writing binary data
ofstream binOut("data.bin", ios::binary);
int numbers[] = {1, 2, 3, 4, 5};
binOut.write(reinterpret_cast(numbers), sizeof(numbers));
binOut.close();
// Reading binary data
ifstream binIn("data.bin", ios::binary);
int readNumbers[5];
binIn.read(reinterpret_cast(readNumbers), sizeof(readNumbers));
for (int n : readNumbers) {
cout << n << " ";
}
}
File opening modes are like different ways to open a door - you can open it just to look (read), to put things in (write), or both!
// Different ways to open files
ofstream file1("output.txt"); // Default: ios::out
ofstream file2("output.txt", ios::app); // Append mode
ofstream file3("output.txt", ios::out | ios::trunc); // Explicit truncate
fstream file4("data.txt", ios::in | ios::out); // Read and write
fstream file5("binary.dat", ios::binary | ios::in | ios::out);
// Checking if file opened successfully
ifstream inFile("maybe_exists.txt");
if (inFile.is_open()) {
// File opened successfully
} else {
// Handle error
}
// Alternative error checking
if (!inFile) {
cerr << "Failed to open file!" << endl;
}
// Using exceptions for file errors
ifstream exFile;
exFile.exceptions(ifstream::failbit | ifstream::badbit);
try {
exFile.open("important.txt");
// Read file...
} catch (const ifstream::failure& e) {
cerr << "Exception opening file: " << e.what() << endl;
}
File pointers are like bookmarks - they remember where you are in a file and let you jump to specific locations!
Exceptions are like smoke detectors - they alert you when something goes wrong so you can handle it gracefully instead of letting your program crash!
// Basic try-catch structure
try {
// Code that might throw an exception
int result = riskyOperation();
cout << "Success: " << result << endl;
}
catch (const exception& e) {
// Handle the exception
cerr << "Error: " << e.what() << endl;
}
// Multiple catch blocks
try {
processFile("data.txt");
}
catch (const ifstream::failure& e) {
cerr << "File error: " << e.what() << endl;
}
catch (const runtime_error& e) {
cerr << "Runtime error: " << e.what() << endl;
}
catch (const exception& e) {
cerr << "General error: " << e.what() << endl;
}
catch (...) {
cerr << "Unknown error occurred!" << endl;
}
// Throwing exceptions
void validateAge(int age) {
if (age < 0) {
throw invalid_argument("Age cannot be negative");
}
if (age > 150) {
throw out_of_range("Age seems unrealistic");
}
}
// Custom exception class
class FileFormatException : public runtime_error {
private:
int lineNumber;
public:
FileFormatException(const string& msg, int line)
: runtime_error(msg), lineNumber(line) {}
int getLineNumber() const { return lineNumber; }
};
RAII (Resource Acquisition Is Initialization) is like having automatic cleanup - resources are tied to object lifetime, so they're always properly released!
// RAII file wrapper
class SafeFile {
private:
FILE* file;
public:
SafeFile(const string& filename, const string& mode) {
file = fopen(filename.c_str(), mode.c_str());
if (!file) {
throw runtime_error("Failed to open file: " + filename);
}
}
~SafeFile() {
if (file) {
fclose(file);
}
}
// Delete copy operations
SafeFile(const SafeFile&) = delete;
SafeFile& operator=(const SafeFile&) = delete;
// Move operations
SafeFile(SafeFile&& other) noexcept : file(other.file) {
other.file = nullptr;
}
FILE* get() { return file; }
};
// Exception-safe file processing
void processFilesSafely(const string& input, const string& output) {
ifstream inFile(input);
if (!inFile) {
throw runtime_error("Cannot open input file");
}
ofstream outFile(output);
if (!outFile) {
throw runtime_error("Cannot open output file");
}
try {
string line;
int lineNum = 0;
while (getline(inFile, line)) {
lineNum++;
// Process line - might throw
string processed = processLine(line);
outFile << processed << endl;
if (!outFile) {
throw runtime_error("Write failed at line " +
to_string(lineNum));
}
}
}
catch (...) {
// Files automatically closed by destructors
throw; // Re-throw the exception
}
// Files automatically closed here too
}
Let's build a complete example that processes CSV files with proper error handling!
class CSVProcessor {
private:
string filename;
char delimiter;
public:
CSVProcessor(const string& file, char delim = ',')
: filename(file), delimiter(delim) {}
vector> readCSV() {
vector> data;
ifstream file(filename);
if (!file) {
throw runtime_error("Cannot open file: " + filename);
}
string line;
int lineNum = 0;
while (getline(file, line)) {
lineNum++;
vector row = parseLine(line, lineNum);
data.push_back(row);
}
return data;
}
private:
vector parseLine(const string& line, int lineNum) {
vector result;
stringstream ss(line);
string item;
while (getline(ss, item, delimiter)) {
// Trim whitespace
item.erase(0, item.find_first_not_of(" \t"));
item.erase(item.find_last_not_of(" \t") + 1);
result.push_back(item);
}
if (result.empty()) {
throw runtime_error("Empty line at line " +
to_string(lineNum));
}
return result;
}
};
// Usage with exception handling
int main() {
try {
CSVProcessor processor("data.csv");
auto data = processor.readCSV();
// Process data
for (const auto& row : data) {
for (const auto& field : row) {
cout << field << " | ";
}
cout << endl;
}
}
catch (const runtime_error& e) {
cerr << "Error: " << e.what() << endl;
return 1;
}
catch (const exception& e) {
cerr << "Unexpected error: " << e.what() << endl;
return 2;
}
return 0;
}
Create a program that analyzes server log files with these features:
struct LogEntry {
time_t timestamp;
string level; // INFO, WARNING, ERROR
string message;
};
class LogAnalyzer {
private:
vector entries;
public:
void loadLogFile(const string& filename);
void filterByDateRange(time_t start, time_t end);
void filterByLevel(const string& level);
map getLevelCounts();
map getErrorsByHour();
void exportReport(const string& filename);
private:
LogEntry parseLogLine(const string& line);
// TODO: Implement parsing with exception handling
};
// Custom buffering for better performance
class BufferedFileWriter {
private:
ofstream file;
vector buffer;
size_t bufferSize;
size_t currentPos;
public:
BufferedFileWriter(const string& filename, size_t bufSize = 65536)
: bufferSize(bufSize), currentPos(0) {
buffer.resize(bufferSize);
file.open(filename, ios::binary);
if (!file) {
throw runtime_error("Cannot open file for writing");
}
}
~BufferedFileWriter() {
flush();
}
void write(const string& data) {
for (char c : data) {
buffer[currentPos++] = c;
if (currentPos >= bufferSize) {
flush();
}
}
}
void flush() {
if (currentPos > 0) {
file.write(buffer.data(), currentPos);
currentPos = 0;
}
}
};
// Memory-mapped file reading (platform specific)
#ifdef _WIN32
// Windows implementation
#else
// Unix implementation using mmap
#endif
Build a simple file-based database with: