TeqStudy ยท C++ Track ยท Phase 11

File I/O

ofstream and ifstream, checking for genuine success before trusting a file, line-by-line reading, and parsing structured CSV-style data โ€” genuinely persisting data between program runs for the first time.

๐Ÿ’พ โœ๏ธ ๐Ÿ“– ๐Ÿ“Š ๐Ÿ
Course Progress
0%
MODULE 01

๐Ÿ’พ Why File I/O Matters

Every program in this entire roadmap has lost all its data the instant it closed. This phase fixes that โ€” genuinely persisting data between runs.

๐Ÿ˜– The Problem โ€” Everything Disappears

Phase 5's BankAccount, Phase 7's inventory system, Phase 9's generic Stack โ€” every single one loses all its data the moment the program exits. A real, useful program almost always needs to SAVE data somewhere and LOAD it back later. File I/O ("input/output") is how.

๐Ÿ“„ Genuinely the Same Idea as cin/cout, Just a Different Destination

Phase 1 Module 6's std::cin and std::cout read from and write to the console. File I/O uses genuinely similar tools โ€” << and >> still work the same way โ€” just pointed at a FILE instead of the screen/keyboard.

ToolPurpose
std::ofstream"output file stream" โ€” writing TO a file (Module 1)
std::ifstream"input file stream" โ€” reading FROM a file (Module 2)
๐Ÿ’ก
Everything you know about << and >> transfers directlyYou're not learning a new I/O concept โ€” you're learning that the SAME operators from Phase 1 work with a different kind of stream. This phase will feel genuinely familiar much faster than earlier phases did.
๐Ÿง  Quick Check
What's the fundamental relationship between std::cin/std::cout and file I/O's ifstream/ofstream?
MODULE 02

โœ๏ธ Writing to a File โ€” ofstream

Your first genuinely persistent program โ€” data that survives after the program closes.

โœ๏ธ Opening, Writing, Closing
C++
#include <fstream>

int main() {
    std::ofstream outFile("scores.txt");

    outFile << "Alex: 85" << std::endl;
    outFile << "Jordan: 92" << std::endl;

    outFile.close();
    return 0;
}

Creating an std::ofstream with a filename opens (creating it if it doesn't exist) that file for writing. << works EXACTLY like Phase 1's std::cout << โ€” except the text goes into the file instead of the screen. .close() explicitly closes the file, ensuring everything is properly saved.

๐ŸŽ RAII Applies Here Too
๐Ÿ’ก
ofstream's destructor closes the file automaticallyGenuinely the exact same Phase 8 Module 1 principle โ€” if you never call .close() explicitly, the file is still safely closed when outFile goes out of scope, since ofstream's own destructor handles it. Calling .close() explicitly is still good practice for clarity, but you're not risking a leak either way.
โž• Appending Instead of Overwriting
C++
std::ofstream outFile("scores.txt", std::ios::app);  // append mode โ€” adds to the end, doesn't erase existing content

By default, opening a file for writing ERASES any existing content first. Passing std::ios::app as a second argument switches to append mode โ€” new writes are added to the end, preserving what was already there.

๐Ÿง  Quick Check
What happens by default when you open an existing file for writing with std::ofstream, without specifying append mode?
MODULE 03

๐Ÿ“– Reading from a File โ€” ifstream

Getting Module 1's saved data back โ€” the read side of persistence.

๐Ÿ“– Reading Word by Word
C++
#include <fstream>

std::ifstream inFile("scores.txt");
std::string word;

while (inFile >> word) {   // reads one word at a time, exactly like cin >>
    std::cout << word << std::endl;
}

inFile.close();

inFile >> word reads one whitespace-separated word at a time โ€” genuinely the exact same behavior as Phase 1 Module 6's std::cin >> word, including the "stops at spaces" limitation from that lesson. Using it as a while loop's condition works because the stream itself becomes "false" once there's nothing left to read.

๐Ÿ”— Why the while (inFile >> word) Pattern Works

A stream, when used as a condition, evaluates to true if the last read succeeded and false once it fails (typically, reaching the end of the file). This directly connects to Phase 2's loop patterns โ€” genuinely the sentinel pattern from Phase 2 Module 6, except the "sentinel" here is the file simply running out of content, rather than a specific value the user types.

๐Ÿง  Quick Check
Why does while (inFile >> word) correctly stop once the file has been fully read?
MODULE 04

โœ… Checking If a File Opened Successfully

Files fail to open constantly in the real world โ€” wrong path, missing permissions, the file genuinely doesn't exist. A real program has to handle this.

โš ๏ธ A File That Doesn't Exist Doesn't Crash โ€” It Just Silently Fails
C++ โ€” a genuine, common trap
std::ifstream inFile("does_not_exist.txt");
std::string word;
inFile >> word;   // doesn't crash โ€” word just stays unchanged, silently

Opening a nonexistent file for reading doesn't throw an exception (Phase 10) or crash by default โ€” it just quietly fails, and subsequent reads simply don't do anything. This is a genuinely common source of confusing bugs for beginners: the program runs, produces no error, and just... does nothing useful.

โœ… The Real Habit โ€” Always Check
C++
std::ifstream inFile("scores.txt");

if (!inFile.is_open()) {
    std::cout << "Error: could not open scores.txt" << std::endl;
    return 1;
}
// safe to proceed โ€” the file genuinely opened

.is_open() returns true if the file genuinely opened successfully. This directly connects to Phase 4 Module 9's null-checking discipline โ€” verify success BEFORE trusting the resource is usable, exactly the same defensive habit applied to a different kind of resource.

๐Ÿง  Quick Check
What happens if you try to open a file that doesn't exist with std::ifstream, without checking .is_open()?
MODULE 05

๐Ÿ“ƒ Reading Line by Line

Module 2's word-by-word reading breaks apart at every space โ€” genuinely wrong for data like "Jordan Smith: 92" where you want the whole line intact.

๐Ÿ“ƒ std::getline for Files
C++
std::ifstream inFile("scores.txt");
std::string line;

while (std::getline(inFile, line)) {
    std::cout << "Line: " << line << std::endl;
}

This is genuinely the exact same std::getline from Phase 1 Module 6, just given a file stream instead of std::cin โ€” it reads one entire line at a time, spaces and all, stopping only at the newline character. The while (std::getline(...)) pattern works identically to Module 3's while (inFile >> word) โ€” the function itself returns something that evaluates to false once there's nothing left to read.

๐ŸŽฏ word-by-word vs. line-by-line โ€” Choosing the Right One
SituationBetter Fit
Data is naturally space-separated tokens (like individual numbers)inFile >> value (Module 2)
Each line represents one complete, meaningful record (a name, a sentence)std::getline(inFile, line)
๐Ÿง  Quick Check
Why would reading "Jordan Smith: 92" with inFile >> word instead of std::getline produce an unwanted result?
MODULE 06

๐Ÿ“Š Working With Structured Data

Real files often store multiple pieces of information per line, in a consistent format. Here's the standard approach for parsing them.

๐Ÿ“Š CSV-Style Data โ€” Comma-Separated Values
contacts.txt
Alex,25,alex@example.com
Jordan,30,jordan@example.com

Each line has three pieces of data (name, age, email), separated by commas โ€” a genuinely common, simple real-world format. To parse it, you need to split each line at the commas.

โœ‚๏ธ Splitting a Line with std::stringstream
C++
#include <sstream>

std::string line = "Alex,25,alex@example.com";
std::stringstream ss(line);
std::string name, ageStr, email;

std::getline(ss, name, ',');      // reads up to the first comma: "Alex"
std::getline(ss, ageStr, ',');    // reads up to the next comma: "25"
std::getline(ss, email, ',');     // reads to the end: "alex@example.com"

int age = std::stoi(ageStr);   // convert the text "25" into a real int

std::stringstream treats a std::string AS IF it were a stream โ€” letting you reuse Module 4's std::getline, but with a custom delimiter (',' instead of the default newline) to split the line at each comma. std::stoi ("string to int") converts the resulting text into a real number, exactly like Phase 10's capstone used it.

๐Ÿง  Quick Check
What does passing ',' as a third argument to std::getline(ss, name, ',') actually change?
MODULE 07 ๐Ÿ

๐Ÿ Capstone โ€” A Contact Book

Every idea from this phase, combined: writing structured data to a file, reading it back, and genuinely persisting a Phase 5-style class between separate program runs.

๐ŸŽฏ The Goal

Build a Contact class (Phase 5), a function to save a vector<Contact> (Phase 7) to a CSV file, and a function to load them back โ€” genuinely surviving between separate runs of the program, not just within one execution.

๐Ÿ’ป A Reference Solution
main.cpp
#include <iostream>
#include <fstream>
#include <sstream>
#include <vector>

class Contact {
public:
    std::string name;
    int age;
    std::string email;

    Contact(std::string n, int a, std::string e) : name(n), age(a), email(e) {}
};

void saveContacts(const std::vector<Contact>& contacts, std::string filename) {
    std::ofstream outFile(filename);
    for (const auto& c : contacts) {
        outFile << c.name << "," << c.age << "," << c.email << std::endl;
    }
}

std::vector<Contact> loadContacts(std::string filename) {
    std::vector<Contact> contacts;
    std::ifstream inFile(filename);

    if (!inFile.is_open()) {
        std::cout << "No existing contact file found. Starting fresh." << std::endl;
        return contacts;
    }

    std::string line;
    while (std::getline(inFile, line)) {
        std::stringstream ss(line);
        std::string name, ageStr, email;
        std::getline(ss, name, ',');
        std::getline(ss, ageStr, ',');
        std::getline(ss, email, ',');
        contacts.push_back(Contact(name, std::stoi(ageStr), email));
    }
    return contacts;
}

int main() {
    std::vector<Contact> contacts = loadContacts("contacts.csv");

    contacts.push_back(Contact("Jude", 30, "jude@example.com"));

    saveContacts(contacts, "contacts.csv");

    std::cout << "Contacts saved. Total: " << contacts.size() << std::endl;
    return 0;
}
๐Ÿ” Why It's Built This Way
  • loadContacts checks .is_open() (Module 4) and handles a missing file gracefully โ€” the FIRST time this program ever runs, there's genuinely no file yet, and that's a normal, expected case, not an error.
  • Each line is parsed with Module 6's stringstream/comma-delimiter pattern into a real Contact object.
  • Run this program, close it, and run it again โ€” the contact you added persists, genuinely proving the data survived between separate executions, not just within one run.
๐Ÿ Extend It Yourself

Add a Search Function

Write a function that takes the loaded vector<Contact> and a name, using Phase 7 Module 6's std::find (with a lambda, since you're searching by a specific FIELD rather than the whole object) to locate a matching contact and print their email. This combines file loading with STL searching โ€” genuinely two different phases' tools working together on the same data.

Roadmap