Skillversitas

53. File I/O

Definition

File I/O (Input/Output) is the mechanism for reading and writing data from/to files on the filesystem. C++ provides consistent stream classes for various data sources: files, strings, and even networks. This stream framework uses the same >> and << operators as cin and cout, so the interface used is very familiar.

Main classes: ifstream (input), ofstream (output), and fstream (both).

ifstream — Reading Files

#include <iostream>
#include <ifstream>
#include <string>
using namespace std;

int main() {
    ifstream file("data.txt");

    if (!file.is_open()) {
        cerr << "Failed to open file!" << endl;
        return 1;
    }

    // Read line by line
    string line;
    while (getline(file, line)) {
        cout << line << endl;
    }

    file.close();
    return 0;
}

Code Explanation

  • ifstream file("name.txt") opens a file for reading.
  • is_open() checks whether the file was opened successfully.
  • getline(file, line) reads one line from the file into a string.
  • close() closes the file — done automatically when the destructor is called.

ofstream — Writing Files

#include <iostream>
#include <ofstream>
#include <string>
using namespace std;

int main() {
    ofstream file("output.txt");

    if (!file.is_open()) {
        cerr << "Failed to create file!" << endl;
        return 1;
    }

    file << "Name: Mike" << endl;
    file << "Age: 25" << endl;
    file << "City: Chicago" << endl;

    // Write with formatting
    for (int i = 1; i <= 5; i++) {
        file << "Line " << i << ": " << i * 10 << endl;
    }

    file.close();
    cout << "File written successfully!" << endl;

    return 0;
}

Code Explanation

  • ofstream file("output.txt") creates a new file or overwrites an existing one.
  • The << operator is used like cout to write to the file.
  • endl or "\n" for newlines.

fstream — Read and Write

#include <iostream>
#include <fstream>
#include <string>
using namespace std;

int main() {
    // Create the file first
    {
        ofstream create("config.txt");
        create << "mode=debug" << endl;
        create << "port=8080" << endl;
        create << "host=localhost" << endl;
    }

    // Read and modify
    fstream file("config.txt", ios::in | ios::out);

    if (!file.is_open()) {
        cerr << "Failed to open file!" << endl;
        return 1;
    }

    // Read all contents
    string line;
    cout << "File contents:" << endl;
    while (getline(file, line)) {
        cout << line << endl;
    }

    // Back to the start of the file
    file.clear(); // clear flags
    file.seekg(0);

    // Find and replace the port
    while (getline(file, line)) {
        if (line.find("port=") != string::npos) {
            streampos pos = file.tellg();
            file.seekp(pos - static_cast<streamoff>(line.length()));
            file << "port=9090";
            break;
        }
    }

    file.close();
    return 0;
}

Code Explanation

  • fstream with ios::in | ios::out opens a file for reading and writing.
  • seekg() moves the get (read) position.
  • seekp() moves the put (write) position.
  • tellg() returns the current position.
  • clear() removes error flags after reaching EOF.

File Modes

#include <iostream>
#include <fstream>
#include <string>
using namespace std;

int main() {
    // ios::out — write (default, overwrites)
    ofstream out1("file1.txt");
    out1 << "New data" << endl;
    out1.close();

    // ios::app — append at the end of the file
    ofstream out2("file1.txt", ios::app);
    out2 << "Added" << endl;
    out2.close();

    // ios::in — read
    ifstream in("file1.txt");
    string line;
    cout << "Contents of file1.txt:" << endl;
    while (getline(in, line)) {
        cout << line << endl;
    }
    in.close();

    // ios::binary — binary mode
    // Write binary data
    struct Biodata {
        char name[50];
        int age;
        double gpa;
    };

    Biodata student = {"Mike Johnson", 21, 375};
    ofstream outBin("biodata.bin", ios::binary);
    outBin.write(reinterpret_cast<char*>(&student), sizeof(Biodata));
    outBin.close();

    // Read binary data
    Biodata studentRead;
    ifstream inBin("biodata.bin", ios::binary);
    inBin.read(reinterpret_cast<char*>(&studentRead), sizeof(Biodata));
    inBin.close();

    cout << "\nData biner:" << endl;
    cout << "Name: " << studentRead.name << endl;
    cout << "Age: " << studentRead.age << endl;
    cout << "GPA: " << studentRead.gpa << endl;

    return 0;
}

Code Explanation

Mode Function
ios::in Read
ios::out Write (overwrite)
ios::app Append at the end
ios::binary Binary mode (no encoding)
ios::trunc Clear file contents when opened
ios::ate Move to end of file on open

getline with Files

#include <iostream>
#include <fstream>
#include <string>
#include <sstream>
using namespace std;

int main() {
    // Create a sample file
    {
        ofstream out("names.txt");
        out << "Mike,John,Anna" << endl;
        out << "Dana,Eric,Frank" << endl;
    }

    // Read with a comma delimiter
    ifstream file("names.txt");
    string line;

    while (getline(file, line)) {
        stringstream ss(line);
        string name;

        while (getline(ss, name, ',')) {
            cout << "Name: " << name << endl;
        }
    }

    file.close();
    return 0;
}

Code Explanation

  • getline(file, line, delimiter) reads up to a specific delimiter.
  • stringstream splits a string based on a delimiter.

Binary File I/O

#include <iostream>
#include <fstream>
#include <vector>
using namespace std;

struct Record {
    int id;
    double grade;
};

int main() {
    // Write several records
    vector<Record> data = {
        {1, 85.5}, {2, 92.0}, {3, 78.5}, {4, 95.0}
    };

    ofstream outFile("records.bin", ios::binary);
    size_t count = data.size();
    outFile.write(reinterpret_cast<const char*>(&count), sizeof(size_t));
    outFile.write(reinterpret_cast<const char*>(data.data()), count * sizeof(Record));
    outFile.close();

    // Read the records
    ifstream inFile("records.bin", ios::binary);
    size_t readCount;
    inFile.read(reinterpret_cast<char*>(&readCount), sizeof(size_t));

    vector<Record> readData(readCount);
    inFile.read(reinterpret_cast<char*>(readData.data()), readCount * sizeof(Record));
    inFile.close();

    cout << "Data that was read:" << endl;
    for (const auto& rec : readData) {
        cout << "ID: " << rec.id << ", Grade: " << rec.grade << endl;
    }

    return 0;
}

Code Explanation

  • write() writes a block of binary data to the file.
  • read() reads a block of binary data from the file.
  • reinterpret_cast<char*> is needed because streams work with bytes (char).

stringstream — String Manipulation

istringstream — Reading from a String

#include <iostream>
#include <sstream>
#include <string>
using namespace std;

int main() {
    string data = "Mike 25 3.75";

    istringstream iss(data);
    string name;
    int age;
    double gpa;

    iss >> name >> age >> gpa;

    cout << "Name: " << name << endl;
    cout << "Age: " << age << endl;
    cout << "GPA: " << gpa << endl;

    return 0;
}

ostringstream — Writing to a String

#include <iostream>
#include <sstream>
#include <iomanip>
using namespace std;

int main() {
    ostringstream oss;

    oss << "Annual Report" << endl;
    oss << string(30, '=') << endl;
    oss << fixed << setprecision(2);
    oss << "Pendapatan: $ " << 12345678.9 << endl;
    oss << "Pengeluaran: $ " << 8765432.1 << endl;
    oss << "Laba: $ " << (12345678.9 - 8765432.1) << endl;

    string result = oss.str();
    cout << result;

    return 0;
}

Code Explanation

  • istringstream reads from a string using the >> operator.
  • ostringstream writes to a string using the << operator.
  • str() returns the result as a string.

Case Study: Log File

#include <iostream>
#include <fstream>
#include <string>
#include <ctime>
#include <sstream>
using namespace std;

class Logger {
    ofstream logFile;
public:
    Logger(const string& filename) {
        logFile.open(filename, ios::app);
    }

    void log(const string& level, const string& message) {
        time_t now = time(nullptr);
        tm* ltm = localtime(&now);

        ostringstream timestamp;
        timestamp << setfill('0') << setw(2) << ltm->tm_hour << ":"
                  << setw(2) << ltm->tm_min << ":"
                  << setw(2) << ltm->tm_sec;

        logFile << "[" << timestamp.str() << "] "
                << "[" << level << "] "
                << message << endl;

        cout << "[" << timestamp.str() << "] "
             << "[" << level << "] "
             << message << endl;
    }

    ~Logger() {
        logFile.close();
    }
};

int main() {
    Logger logger("app.log");

    logger.log("INFO", "application started");
    logger.log("INFO", "Database connection successful");
    logger.log("WARNING", "Memory usage high: 85%");
    logger.log("ERROR", "Failed to send email");
    logger.log("INFO", "application done");

    return 0;
}

Case Study: CSV Reader

#include <iostream>
#include <fstream>
#include <sstream>
#include <vector>
#include <string>
using namespace std;

struct Student {
    string name;
    int age;
    double gpa;
};

vector<Student> readCSV(const string& filename) {
    vector<Student> data;
    ifstream file(filename);

    string line;
    getline(file, line); // skip the header

    while (getline(file, line)) {
        stringstream ss(line);
        string name, umurStr, ipkStr;

        getline(ss, name, ',');
        getline(ss, umurStr, ',');
        getline(ss, ipkStr, ',');

        data.push_back({name, stoi(umurStr), stod(ipkStr)});
    }

    return data;
}

int main() {
    // Create a CSV file
    {
        ofstream out("student.csv");
        out << "name,age,gpa" << endl;
        out << "Mike,20,3.5" << endl;
        out << "John,21,3.8" << endl;
        out << "Anna,19,3.2" << endl;
    }

    // Read the CSV
    vector<Student> data = readCSV("student.csv");

    cout << "Student data:" << endl;
    for (const auto& s : data) {
        cout << s.name << " | Age: " << s.age << " | GPA: " << s.gpa << endl;
    }

    return 0;
}

Case Study: Config File Parser

#include <iostream>
#include <fstream>
#include <sstream>
#include <map>
#include <string>
using namespace std;

map<string, string> parseConfig(const string& filename) {
    map<string, string> config;
    ifstream file(filename);

    string line;
    while (getline(file, line)) {
        // Skip comments and empty lines
        if (line.empty() || line[0] == '#') continue;

        // Find the '=' separator
        size_t pos = line.find('=');
        if (pos != string::npos) {
            string key = line.substr(0, pos);
            string value = line.substr(pos + 1);

            // Trim spaces
            key.erase(0, key.find_first_not_of(" \t"));
            key.erase(key.find_last_not_of(" \t") + 1);
            value.erase(0, value.find_first_not_of(" \t"));
            value.erase(value.find_last_not_of(" \t") + 1);

            config[key] = value;
        }
    }

    return config;
}

int main() {
    // Create a config file
    {
        ofstream out("app.conf");
        out << "# Application configuration" << endl;
        out << "app_name = MyProgram" << endl;
        out << "version = 1.0.0" << endl;
        out << "debug = true" << endl;
        out << "port = 8080" << endl;
        out << "host = localhost" << endl;
    }

    auto config = parseConfig("app.conf");

    cout << "Konfigurasi:" << endl;
    for (const auto& [key, value] : config) {
        cout << "  " << key << " = " << value << endl;
    }

    return 0;
}

Error Handling

#include <iostream>
#include <fstream>
#include <string>
using namespace std;

int main() {
    ifstream file("missing.txt");

    if (!file) {
        cerr << "Error: file not found" << endl;
        return 1;
    }

    // Check the stream state
    string line;
    while (file.good()) {
        getline(file, line);
        if (!file.eof()) {
            cout << line << endl;
        }
    }

    // Check the various error states
    if (file.bad()) cerr << "Critical I/O error" << endl;
    if (file.fail()) cerr << "Logical error" << endl;
    if (file.eof()) cout << "EOF reached" << endl;

    file.close();
    return 0;
}

Code Explanation

  • good() — everything is OK.
  • eof() — end of file reached.
  • fail() — logical error (format mismatch, file not found).
  • bad() — critical I/O error (disk failure).

File I/O Tips

  1. Always check is_open() after opening a file
  2. Use RAII — let the destructor close the file
  3. Use getline() to read full lines (not >>)
  4. For binary files, always use ios::binary
  5. Use clear() after an operation that may fail
  6. Prefer stringstream for complex string manipulation

Summary

File I/O in C++ uses a consistent stream model. ifstream for reading, ofstream for writing, fstream for both. File modes determine how a file is accessed. stringstream allows string manipulation like a file. Error handling is very important — always check whether I/O operations succeeded.