Console input disappears when a program ends, but real programs must save marks, logs or settings and read them later. Beginners usually get stuck on three choices: whether to use ifstream, ofstream or fstream, which open mode protects existing data, and how to detect a failed read instead of using bad data. Use ifstream for reading, ofstream for writing, and fstream for both; choose an open mode that preserves or replaces content, and check stream state after important operations.
Build the C++ file-stream mental model
The <fstream> header provides three stream classes. std::ifstream reads from a file, std::ofstream writes to one, and std::fstream can do both. A relative name such as scores.txt is resolved from the program's working directory, which may differ from the directory containing the source file.
Mode | Meaning |
|---|---|
| Open for input |
| Open for output |
| Force every write to the end |
| Start at the end, but allow later seeks |
| Discard existing content |
| Suppress text-mode translation |
A plain std::ofstream out("scores.txt") opens for output and replaces prior content. Use the lifecycle open, verify, use, check, close. A stream closes automatically when its scope ends. Call close() explicitly only when another operation must use the file before that scope ends. The Coding & Skill Development Courses catalogue also includes related programming topics.
Write three records, then read them line by line
Create the file in one scope, then open it again in a second scope:
#include <fstream>
#include <iostream>
#include <string>
int main() {
{
std::ofstream out("scores.txt");
if (!out) {
std::cerr << "Could not open scores.txt for writing\n";
return 1;
}
out << "101,Asha,78\n"
<< "102,Bharat,91\n"
<< "103,Charu,84\n";
if (!out) {
std::cerr << "Write failed\n";
return 1;
}
}
{
std::ifstream in("scores.txt");
if (!in) {
std::cerr << "Could not open scores.txt for reading\n";
return 1;
}
std::string line;
while (std::getline(in, line)) {
std::cout << line << '\n';
}
if (!in.eof()) {
std::cerr << "Read failed before EOF\n";
return 1;
}
}
}The resulting scores.txt is:
101,Asha,78
102,Bharat,91
103,Charu,84The console prints those same three lines, once each. while (std::getline(in, line)) is correct because extraction and validation happen together. The loop ends normally at EOF, while the later check distinguishes EOF from an actual I/O failure.

Parse records safely and calculate a checked result
Add <sstream>, <iomanip> and <exception>, then split each line before converting it. Keep the risky conversion inside a small try block and reject trailing junk such as 91x:
bool parseIntStrict(const std::string& text, int& value) {
std::size_t used = 0;
try {
value = std::stoi(text, &used);
return !text.empty() && used == text.size();
} catch (const std::exception&) {
return false;
}
}
std::istringstream row(line);
std::string idText, name, markText, extra;
if (!std::getline(row, idText, ',') ||
!std::getline(row, name, ',') ||
!std::getline(row, markText, ',') ||
idText.empty() || name.empty() || markText.empty() ||
std::getline(row, extra, ',')) {
std::cerr << "Invalid record: " << line << '\n';
continue;
}
int id = 0, mark = 0;
if (!parseIntStrict(idText, id) || !parseIntStrict(markText, mark)) {
std::cerr << "Invalid record: " << line << '\n';
continue;
}
++count;
total += mark;For the three valid rows, count is 3, total is 78 + 91 + 84 = 253, and std::fixed << std::setprecision(2) prints the average as 253 / 3 = 84.33. If 104,Dev,not-a-number is added, the parser reports it as invalid. Count remains 3 and total remains 253.
Append without erasing earlier data
First recreate the valid three-line scores.txt. Then append one record:
std::ofstream out("scores.txt", std::ios::app);
if (!out) {
std::cerr << "Could not open scores.txt\n";
return 1;
}
out << "104,Dev,88\n";
if (!out) {
std::cerr << "Append failed\n";
return 1;
}The file now contains:
101,Asha,78
102,Bharat,91
103,Charu,84
104,Dev,88The checked parser now reports count 4, total 78 + 91 + 84 + 88 = 341, and average 341 / 4 = 85.25. Mode app preserves the file and forces writes to its end. A plain output stream would truncate it. Mode ate only chooses the initial position, so a later seek can move elsewhere. For more small practice problems across several languages, use Classic Programs in C, Java and Python as a practice set, not as C++ file-I/O documentation.
Seek to a fixed binary record, read it, and update it
On a system with 8-bit bytes, five std::int32_t values occupy 5 * 4 = 20 bytes. The program reads the third value at byte 8, replaces the fourth at byte 12, and checks each open, seek, read, and write shown:
#include <cstdint>
#include <fstream>
#include <iostream>
int main() {
std::int32_t values[]{10, 20, 30, 40, 50};
{
std::ofstream out("inventory.bin", std::ios::binary);
if (!out || !out.write(reinterpret_cast<const char*>(values),
sizeof(values))) return 1;
}
{
std::ifstream in("inventory.bin", std::ios::binary);
if (!in || !in.seekg(2 * sizeof(std::int32_t), std::ios::beg)) return 1;
std::int32_t third = 0;
if (!in.read(reinterpret_cast<char*>(&third), sizeof(third))) return 1;
std::cout << "Third: " << third << '\n';
}
{
std::fstream file("inventory.bin",
std::ios::in | std::ios::out | std::ios::binary);
if (!file || !file.seekp(3 * sizeof(std::int32_t), std::ios::beg)) return 1;
std::int32_t replacement = 44;
if (!file.write(reinterpret_cast<const char*>(&replacement),
sizeof(replacement))) return 1;
}
std::int32_t result[5]{};
std::ifstream in("inventory.bin", std::ios::binary);
if (!in || !in.read(reinterpret_cast<char*>(result), sizeof(result))) return 1;
for (std::int32_t value : result) std::cout << value << ' ';
}The read offset is 2 * 4 = 8, so Third: 30 is printed. The write offset is 3 * 4 = 12; re-reading prints 10 20 30 44 50. This native layout suits a controlled demonstration, not portable interchange between machines with different byte ordering or representation rules.

Diagnose the file-handling failures beginners actually hit
Symptom | Cause | Fix |
|---|---|---|
File never opens | Wrong working directory or path | Print or verify the working path |
Old content disappears | Output defaulted to truncation | Choose |
Last record is duplicated | EOF was checked before extraction | Make extraction the loop condition |
|
| Consume that newline before |
| Missing field was not validated | Check field count and content first |
An operation silently fails | Stream state was ignored | Test the stream after important operations |
Destructors close streams automatically, but a successful close cannot rescue an earlier unchecked write. If reinterpret_cast in the binary example feels unfamiliar, Pointers in C: GATE Memory Diagrams is an optional C-based address and memory refresher. The syntax here still belongs to C++.
How assessments and interviews turn this into questions
Three useful question formats test the idea without depending on any named exam pattern:
Output tracing: Write
ABtonote.txtwith a defaultofstream, close it, appendCDwithstd::ios::app, then read the file. The exact result isABCD.Mode selection: Which mode preserves existing content and forces every new write to the end? The answer is
std::ios::app.Bug repair: Replace
while (!in.eof()) { std::getline(in, line); /* use line */ }withwhile (std::getline(in, line)) { /* use line */ }.
The short version and your next action
ifstream reads. ofstream writes. fstream reads and writes. The open mode decides whether content is replaced, preserved or treated as binary. Make extraction the condition, and check stream state after important operations.
Build the complete foundation with the C++ Programming course. If you also want other languages and competitive-coding practice for placements, Coding for Placements is another option.




