File I/O and Streams
45 minC++ provides comprehensive file I/O through a stream-based architecture that treats files, strings, and other sources/sinks uniformly. The stream abstraction enables the same operations (reading, writing, formatting) to work consistently across different I/O sources. This design promotes code reuse and makes I/O operations intuitive and consistent throughout C++ programs.
File streams (std::ifstream for input, std::ofstream for output, std::fstream for both) handle file operations. Files must be opened before use, either through constructor parameters or the open() method. File modes control how files are opened: std::ios::in for reading, std::ios::out for writing, std::ios::app for appending, and std::ios::binary for binary mode. Always check if files opened successfully before performing operations.
String streams (std::stringstream, std::istringstream, std::ostringstream) enable in-memory string processing using the same stream interface as file I/O. String streams are invaluable for parsing formatted strings, converting between types, and building formatted output strings. They're particularly useful for parsing CSV data, converting numbers to strings, and processing user input.
Binary I/O reads and writes data in its raw byte representation, without text formatting or conversion. Binary mode is more efficient for large data structures and preserves exact data representation. Use binary mode for non-text data like images, serialized objects, or when exact byte representation matters. Binary I/O uses read() and write() methods that work with raw memory buffers.
Stream manipulators control formatting behavior, such as number base (std::hex, std::dec, std::oct), precision (std::setprecision), field width (std::setw), and fill characters. Manipulators can be chained with insertion/extraction operators, enabling flexible formatting. Understanding manipulators is essential for producing properly formatted output and parsing formatted input.
Error handling in stream operations is crucial for robust programs. Check stream state using methods like good(), eof(), fail(), and bad(). The stream's state can be checked after operations to detect errors. RAII ensures file streams are properly closed when objects go out of scope, but explicit error checking is still necessary to handle I/O failures gracefully.
Key Concepts
- C++ uses stream-based I/O for files, strings, and other sources.
- File streams (ifstream, ofstream, fstream) handle file operations.
- String streams enable in-memory string processing.
- Binary I/O preserves exact byte representation without formatting.
- Stream manipulators control formatting behavior.
Learning Objectives
Master
- Reading from and writing to files using file streams
- Using string streams for parsing and formatting
- Performing binary I/O operations
- Applying stream manipulators for formatting
Develop
- Understanding stream-based I/O architecture
- Handling file I/O errors robustly
- Processing formatted data with streams
Tips
- Always check if file opened: if (file.is_open()) { /* operations */ }.
- Use RAII: files close automatically when stream objects are destroyed.
- Use string streams for parsing: std::istringstream iss(data); iss >> value;
- Use binary mode for non-text data: file.open("data.bin", std::ios::binary);
Common Pitfalls
- Not checking if files opened successfully, causing silent failures.
- Not closing files explicitly (though RAII handles this, explicit close is clearer).
- Mixing text and binary modes incorrectly, corrupting data.
- Not handling stream errors, causing program crashes.
Summary
- C++ provides stream-based I/O for files and strings.
- File streams handle file input and output operations.
- String streams enable in-memory string processing.
- Binary I/O is efficient for non-text data structures.
Exercise
Demonstrate file I/O operations and string stream processing.
#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
#include <vector>
struct Person {
std::string name;
int age;
std::string city;
};
std::ostream& operator<<(std::ostream& os, const Person& person) {
return os << person.name << "," << person.age << "," << person.city;
}
std::istream& operator>>(std::istream& is, Person& person) {
std::string line;
if (std::getline(is, line)) {
std::stringstream ss(line);
std::string token;
std::getline(ss, person.name, ',');
std::getline(ss, token, ',');
person.age = std::stoi(token);
std::getline(ss, person.city);
}
return is;
}
int main() {
// Write to file
std::ofstream outFile("people.txt");
if (outFile.is_open()) {
outFile << "Alice,25,New York" << std::endl;
outFile << "Bob,30,Los Angeles" << std::endl;
outFile << "Charlie,35,Chicago" << std::endl;
outFile.close();
std::cout << "Data written to file" << std::endl;
}
// Read from file
std::ifstream inFile("people.txt");
std::vector<Person> people;
if (inFile.is_open()) {
Person person;
while (inFile >> person) {
people.push_back(person);
}
inFile.close();
}
std::cout << "\nPeople from file:" << std::endl;
for (const auto& person : people) {
std::cout << person << std::endl;
}
// String stream processing
std::string data = "10,20,30,40,50";
std::stringstream ss(data);
std::vector<int> numbers;
std::string token;
while (std::getline(ss, token, ',')) {
numbers.push_back(std::stoi(token));
}
std::cout << "\nNumbers from string stream:" << std::endl;
for (int num : numbers) {
std::cout << num << " ";
}
std::cout << std::endl;
return 0;
}
Exercise Tips
- Try different file modes: std::ios::app for appending, std::ios::ate for seek to end.
- Use manipulators: std::setw(10), std::setprecision(2), std::hex for formatting.
- Parse CSV: use stringstream with getline and comma delimiter.
- Read binary: file.read(reinterpret_cast<char*>(&data), sizeof(data));