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 rawstringinstead of a fixedchar[10]) breaks the offset math.
Important MCQs
seekg()moves which pointer?- a) The write pointer b) The read ("get") pointer c) Both pointers always d) Neither
seekp()moves which pointer?- a) The write ("put") pointer b) The read pointer c) Both pointers always d) Neither
- 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
- a) Variable length b) Fixed size, so offset arithmetic (
- 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
- a) Smaller file size b) Ability to read/update a specific record directly without scanning preceding ones c) Automatic sorting d) No need for
Self-Test Questions
- Why must every record be the same fixed size for random access to work correctly?
- What is the difference between
seekg()andseekp()? - If you wanted to update record index 5 in a file of
Recordstructs, what expression would you pass toseekp()?
📌 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.