CSS Prepare

optional · CSS study notes

20. Random Access Files

Allows jumping directly to any position in the file (via seekg()/seekp()) without reading through preceding data — requires fixed-size records for the math to work.

#include <fstream>
using namespace std;

struct Record { int id; char name[10]; };

int main()
{
    ofstream fout("records.dat", ios::binary);
    Record r1 = {1, "Ali"}, r2 = {2, "Sara"};
    fout.write((char*)&r1, sizeof(Record));
    fout.write((char*)&r2, sizeof(Record));
    fout.close();

    fstream file("records.dat", ios::in | ios::out | ios::binary);
    file.seekg(1 * sizeof(Record), ios::beg);   // jump straight to record index 1
    Record r;
    file.read((char*)&r, sizeof(Record));       // r.id == 2, r.name == "Sara"
    file.close();
    return 0;
}

Key exam point: seekg() moves the read pointer, seekp() moves the write pointer. n * sizeof(Record) always lands exactly on record n's starting byte because every record is the same fixed size.

Why Random Access Needs Fixed-Size Records

seekg()/seekp() compute a byte offset as n * sizeof(Record). This arithmetic only lands exactly on record n's starting byte if every record occupies exactly the same number of bytes — which is why random access files almost always store fixed-size binary structs rather than variable-length text lines (a text file with lines of different lengths has no fixed "record size" to multiply by).

seekg vs seekp — the distinction examiners test

  • seekg(offset, dir) — moves the get pointer (read position).
  • seekp(offset, dir) — moves the put pointer (write position).
  • Both take a direction: ios::beg (from the start — most common), ios::cur (from the current position), ios::end (from the end, usually with a negative offset).

Full worked example — updating one record in place

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

struct Record { int id; char name[10]; };

int main()
{
    // 1. Create the file with 3 fixed-size records
    ofstream fout("records.dat", ios::binary);
    Record r1 = {1, "Ali"}, r2 = {2, "Sara"}, r3 = {3, "Bilal"};
    fout.write((char*)&r1, sizeof(Record));
    fout.write((char*)&r2, sizeof(Record));
    fout.write((char*)&r3, sizeof(Record));
    fout.close();

    // 2. Jump DIRECTLY to record index 1 (Sara) and read it -- no need to read record 0 first
    fstream file("records.dat", ios::in | ios::out | ios::binary);
    file.seekg(1 * sizeof(Record), ios::beg);
    Record r;
    file.read((char*)&r, sizeof(Record));
    cout << "Read: " << r.id << " " << r.name << endl;   // 2 Sara

    // 3. Now UPDATE that same record in place, using seekp
    strcpy(r.name, "Sarah");
    file.seekp(1 * sizeof(Record), ios::beg);   // move the WRITE pointer back to record 1
    file.write((char*)&r, sizeof(Record));

    file.close();
    return 0;
}

Why this matters over sequential access

If this were a sequential file, updating record index 1 out of a million records would require reading and rewriting the entire file. With random access, the update touches only the exact bytes of that one record — the core practical reason random access exists.

MCQ Trap

  • n * sizeof(Record) only works correctly when every record has the exact same size — mixing variable-length data (like a raw string instead of a fixed char[10]) breaks the offset math.

Important MCQs

  1. seekg() moves which pointer?
    • a) The write pointer b) The read ("get") pointer c) Both pointers always d) Neither
  2. seekp() moves which pointer?
    • a) The write ("put") pointer b) The read pointer c) Both pointers always d) Neither
  3. Random access files require records to be:
    • a) Variable length b) Fixed size, so offset arithmetic (n * sizeof(Record)) works correctly c) Text-only d) Compressed
  4. The main practical advantage of random access over sequential access is:
    • a) Smaller file size b) Ability to read/update a specific record directly without scanning preceding ones c) Automatic sorting d) No need for ios::binary

Self-Test Questions

  1. Why must every record be the same fixed size for random access to work correctly?
  2. What is the difference between seekg() and seekp()?
  3. If you wanted to update record index 5 in a file of Record structs, what expression would you pass to seekp()?

📌 Sample & Repeated FPSC Questions (2016–2026)

2020, Paper I, Q5(b)(v) — "Update a student" (part of a 15-mark question; full solution on the Classes and Objects — Exam Practice → "University Records System" page)

FPSC's own real-world example of exactly this page's seekg/seekp technique: locate a fixed-size record by scanning, then seek backward and overwrite it in place.

void updateStudent(int id, float newCGPA) {
    fstream f("students.dat", ios::binary | ios::in | ios::out);
    Student s;
    while (f.read(reinterpret_cast<char*>(&s), sizeof(Student))) {
        if (s.ID == id) {
            s.CGPA = newCGPA;
            f.seekp(-(int)sizeof(Student), ios::cur);   // step back over the record just read
            f.write(reinterpret_cast<char*>(&s), sizeof(Student));
            break;
        }
    }
}

Exam angle: notice the offset direction here is ios::cur (relative to the current position, stepping back one record-width after reading it), not ios::beg. FPSC's own model answer uses this pattern because the target record's index isn't known in advance — only a matching id is. Reach for n * sizeof(Record) from ios::beg (as in this page's main worked example) only when you already know the target index directly; use ios::cur when you find the record by scanning first and need to "back up" to overwrite what you just read.