CS 112

Introduction to Data Structures

Week 02

File I/O, Exceptions, 2D Arrays

Eric Araújo

Calvin University · Fall 2026

This Week

  • 1
    Streams: console and files
  • 2
    Opening, reading, closing
  • 3
    End of file: how to detect it KEY
  • 4
    Writing to a file
  • 5
    Exceptions: throw, try, catch
  • 6
    Stack unwinding
  • 7
    Two-dimensional arrays

Everything Is a Stream

Console


#include <iostream>

int    n;
cin  >> n;      // in
cout << n;      // out
      

File


#include <fstream>

ifstream fin("data.txt");
fin  >> n;      // in
ofstream fout("out.txt");
fout << n;      // out
      

Opening a File


#include <fstream>

// A file that isn't there does NOT crash your program.
// The stream quietly fails, so you have to ask.

ifstream fin("numbers.txt");   // open on construction
if (!fin) {                    // ALWAYS check
    cerr << "cannot open it" << endl;
    return 1;
}

ifstream fin2;                 // or open later
fin2.open("numbers.txt");
if (!fin2.is_open()) { ... }   // the same check, spelled out

fin.close();                   // done with it
  
Talk to your neighbor · TTYN
Which class reads from a file?
  • A. fstream
  • B. ifstream
  • C. instream
  • D. fin
✓ Answer
B. ifstream. Read it as input file stream. fin is just a variable name people conventionally use; fstream can do both directions.
Talk to your neighbor · TTYN
One way to open for reading is ifstream fin("file");: what is another?
  • A. ifstream fin.open("file");
  • B. ifstream open("file") as fin;
  • C. ifstream fin;
    fin.open("file");
  • D. ifstream fin().open("file");
✓ Answer
C. Declare the stream, then call .open() on it. Useful when the filename isn't known until later.

Reading: Words vs. Lines

>> reads one word


string word;
fin >> word;
// stops at whitespace
      

getline reads the rest


string line;
getline(fin, line);
// stops at newline
      

Detecting End of File


// How do you know when a file is finished?
// You try to read, and the read fails. There is no
// "am I at the end?" to ask first. So: read, THEN test.

string line;
while (getline(fin, line)) {
    cout << line << endl;
}
  

Watch the Read Position

numbers.txt holds three lines. getline starts at the top and moves the position past each line it takes.

before the loop
12
  7
  30
EOF
position: line 1
getline → "12"
  12
7
  30
EOF
true, body runs
getline → "7", then "30"
  12
  7
  30
EOF
true, true
getline → nothing
  12
  7
  30
EOF
false, loop ends

The fourth read is the one that tells you the file is over. It has to be attempted, and it has to fail. That is why the read goes inside the while and not before it.

Talk to your neighbor · TTYN
What is wrong with this loop?

string s;
while (true) {
    getline(fin, s);
    cout << s << endl;
    if (!fin) { break; }
}
    
  • A. No lines are ever processed
  • B. It loops forever
  • C. The first line is skipped
  • D. The last line is skipped
  • E. The last line is processed twice
✓ Answer
E. the last line prints twice. The final getline fails and leaves s holding the previous line, but cout runs before the check. Test the read immediately: while (getline(fin, s)).

Writing to a File


ofstream fout("results.txt");
assert(fout.is_open());   // or if (!fout), as before

for (int i = 0; i < n; ++i) {
    fout << names[i] << "," << scores[i] << "\n";
}

// Forget close() and the last writes may still be
// sitting in the buffer, not on disk. You can lose them.
fout.close();
  
  • Opening for writing erases the file. To add to it instead, see the next slide

Appending Instead of Erasing


// Opening for writing TRUNCATES by default: the file
// is empty the moment you open it, whatever was in it.
ofstream fout("log.txt");

// ios::app keeps what is there and writes after it.
ofstream fout("log.txt", ios::app);
  

run it twice, no flag

started

run it twice, ios::app

started
started

Same program, same line written. The flag is the only difference, and it decides whether yesterday's run still exists.

\n, endl, flush

You writeNewline?Flushes?Use it when
<< "\n"yesnoalmost always
<< endlyesyesyou need it on disk now
<< flushnoyesmid-line prompts, progress dots

Proof, in Bytes

Type this, run it, and in a second terminal watch buf.txt while it sleeps. Then swap "\n" for endl and run it again.


#include <fstream>
#include <unistd.h>        // sleep()
using namespace std;

int main() {
    ofstream fout("buf.txt");
    fout << "hello" << "\n";   // then try: << endl
    sleep(10);               // ls -l buf.txt   in another terminal
    fout.close();
}
  
Program printsFile after 1 secondAfter the program exits
"hello" << "\n"0 bytes, nothing yet6 bytes
"hello" << endl6 bytes, hello\n6 bytes
"hello" << flush5 bytes, hello (no newline)5 bytes

Demo

$ ./stats terminate called after throwing invalid_argument $

Reading a file that isn't there.

(the file was right there. in the other folder.)

Talk to your neighbor · TTYN
Your function average(int a[], int n) is called with n = 0. There is no average of nothing. What can your function do?
  • A. Print an error message
  • B. Return some value anyway, say -1 or 0
  • C. Carry on and divide by zero
  • D. Refuse to finish, and say why
✓ Answer
Every one of these is used in real code. Three of them cause trouble, and the next slide says which and why. Argue for yours first.

When a Function Can't Do Its Job

What it doesWhy it hurts
A print an error a function that computes averages should not decide how this program talks to its user. In a GUI there is no console to print to
B return -1 -1 is a perfectly good average. The caller cannot tell your failure from a real answer
C carry on the caller never learns anything went wrong, and the bad value spreads into everything computed from it
D throw the function reports what it cannot do; the caller decides what that means. Nobody can ignore it by accident

throw and catch


#include <stdexcept>      // invalid_argument lives here

// ...

double squareRoot(double x) {
    if (x < 0) {
        throw invalid_argument("negative input");
    }
    return sqrt(x);
}

// ...

try {
    cout << squareRoot(value);
}                            // & : catch by REFERENCE. Catching by
catch (invalid_argument &e) {   // value copies the exception, and
    cerr << "cannot do that: "  // slices off anything a derived
         << e.what() << endl;   // type added
}
  

Stack Unwinding


void c() {
    cout << "in c" << endl;
    throw runtime_error("boom");
    cout << "after throw";   // never runs
}

void b() {
    cout << "in b" << endl;
    c();
    cout << "back in b";     // never runs
}

int main() {
    try {
        b();
        cout << "after b";   // never runs
    } catch (runtime_error &e) {
        cout << "caught: " << e.what();
    }
    cout << " done" << endl;
}
      
StackWhat happens
mainenters the try, calls b
main → bprints in b, calls c
main → b → cprints in c, then throws
main → bc abandoned. No handler in b, so b is abandoned too
mainthe try has a handler: catch runs

Output: in b · in c · caught: boom done

Three lines marked never runs. The throw does not return anywhere; it leaves every frame between itself and the handler, and each abandoned frame destroys its locals on the way out.

Talk to your neighbor · TTYN
What does this program print?

void f() { throw invalid_argument("yuck");
           cout << "a"; }
void g() { cout << "b"; f(); cout << "c"; }

int main() { cout << "d"; g(); f(); cout << "e"; }
    
  • A. dbe
  • B. dbc
  • C. dbace
  • D. dbae
  • E. None of the others
✓ Answer
E. It prints db and then crashes. Nothing catches the exception, so it propagates out of g(), out of main(), and the runtime calls terminate(). Neither c nor e is ever reached.

Two-Dimensional Arrays


const unsigned ROWS = 5;
const unsigned COLS = 8;

char board[ROWS][COLS];          // 5 rows, 8 columns

for (unsigned r = 0; r < ROWS; ++r) {
    for (unsigned c = 0; c < COLS; ++c) {
        board[r][c] = '.';
    }
}
  
Row first, then column: board[r][c]. The outer loop walks rows, the inner walks the cells within a row.

How 2D Arrays Really Sit in Memory

int grid[2][4] is not a grid; it is 8 ints in a row.

row 0 [0][0]   [0][1]   [0][2]   [0][3]
row 1 [1][0]   [1][1]   [1][2]   [1][3]
in memory one contiguous block, row after row
This is why a pointer walking the array crosses from the end of one row into the start of the next, and why the compiler needs the column count to do the arithmetic.

Passing a 2D Array


const unsigned COLS = 8;

void printBoard(char b[][COLS], unsigned rows) {
    ...
}
  
  • You may leave the row count off: pass it as a separate argument
  • You may not leave the column count off
  • Without the columns, b[r][c] can't be turned into an address
Same rule as 1D arrays, one dimension up: the function receives an address, so it needs the shape spelled out.

Week 02 Recap

  • File streams use the same operators as cin/cout
  • Always check is_open(): a missing file fails silently
  • Read, then test: while (getline(fin, line))
  • throw hands the problem to whoever can decide what to do
  • An uncaught exception unwinds every frame, then terminates
  • 2D arrays are one contiguous block, the column count is not optional

This deck stands on earlier CS112 materials by
Joel Adams and Victor Norman · adapted and extended by Eric Araújo