CS253: Software Development with C++

Fall 2022

I/O Streams

Show Lecture.IOStreams as a slide show.

CS253 I/O Streams

Inclusion

To do I/O, you need to:

    
#include <iostream>

If you are reading or writing strings, you need to:

    
#include <string>

To create an ifstream object, you need to:

    
#include <fstream>

A few examples use the function getpwnam(), which requires:

    
#include <pwd.h>

The order of #includes doesn’t matter. Alphabetical order is fine.

Predefined Streams

There are four predefined streams. Don’t open or close them. Just use them.

cerr is unbuffered, so its output appears immediately. clog might or might not be buffered—the standard is silent on the subject. Use cerr for error messages, clog if you use standard error for logging.

Formatted output:

The insertion operator, <<, is used for output. You may recognize it as the left shift operator. It’s that, too. Isn’t operator overloading wonderful?

int i = 5<<4;
double d = 4.5;
char c = 'x';
const char *ccs = "My dog";
string s = " has fleas";

cout << i << ' ' << d << " " << c << "\n"
     << ccs << s << '\n';
80 4.5 x
My dog has fleas
How can you tell if << means bit shift or insertion?

If the left operand is an output stream (or the result of another insertion) then it’s insertion.

endl

cout << "Gryffindor" << '\n';
cout << "Hufflepuff" << "\n";
cout << "Ravenclaw\n";
cout << "Slytherin" << endl;  // 🦡
Gryffindor
Hufflepuff
Ravenclaw
Slytherin

endl demonstration

static int zero = 0;  // fool the optimizer
cout << "Invisible Man\n";
cout << 1/zero << '\n';
SIGFPE: Floating point exception

The Invisible Man was buffered (slide output really goes to a file) and so was lost when the program terminated abnormally.

static int zero = 0;
cout << "Frankenstein" << endl;
cout << 1/zero << '\n';
Frankenstein
SIGFPE: Floating point exception

Division by zero is undefined behavior, so none of this is guaranteed.

Formatted input

The extraction operator, >>, is used for input. It skips leading whitespace.

int i;
cin >> i;               // attempt to read an integer
if (cin)                // Is the stream in a happy state?
    cout << "Read i=" << i << '\n';
else
    cout << "Couldn’t read an int: bad data, or end-of-file\n";
Couldn’t read an int: bad data, or end-of-file

-or-

int i;
while (cin >> i)
    cout << "Read i=" << i << '\n';

Chaining

Input may be chained, just like output:

int a, b, c;
if ((cin>>a) && (cin>>b) && (cin>>c))
    cout << "a=" << a << " b=" << b << " c=" << c << '\n';

-or-

int a, b, c;
if (cin >> a >> b >> c)
    cout << "a=" << a << " b=" << b << " c=" << c << '\n';

The && forces left-to-right evaluation, so the numbers are read in the proper order.

Failure

If you’re trying to read a number, and the next thing in the input (after skipping whitespace) isn’t a number, the reading simply fails. It does not plow through any non-numbers looking for a number:

$ cat /etc/system-release
AlmaLinux release 8.9 (Midnight Oncilla)
ifstream rel("/etc/system-release");
double d;
if (rel >> d)  // try to read the number
    cout << "Read succeeded, we got: " << d << '\n';
else
    cerr << "Read failed.\n";
Read failed.

Read an entire line

Consider this small file:

$ cat ~cs253/pub/ducks
Huey (red)
Dewey (blue)
Louie (green)

To read an entire line, use getline():

const string home = getpwnam("cs253")->pw_dir;
ifstream in(home+"/pub/ducks");
for (string line; getline(in, line); )
    cout << "▻" << line << '\n';
▻Huey (red)
▻Dewey (blue)
▻Louie (green)

Beware

There are two versions of getline():

Read a string

Consider this small file:

$ cat ~cs253/pub/ducks
Huey (red)
Dewey (blue)
Louie (green)

Extracting a string via >> only reads a whitespace-delimited string.

const string home = getpwnam("cs253")->pw_dir;
ifstream in(home+"/pub/ducks");
string s;
while (in >> s)
    cout << "▻" << s << '\n';
▻Huey
▻(red)
▻Dewey
▻(blue)
▻Louie
▻(green)

Read a character

Consider this small file:

$ cat ~cs253/pub/ducks
Huey (red)
Dewey (blue)
Louie (green)

To read a raw char, without skipping whitespace, use istream::get():

const string home = getpwnam("cs253")->pw_dir;
ifstream in(home+"/pub/ducks");
char c;
while (in.get(c))
    cout << "▻" << c;
▻H▻u▻e▻y▻ ▻(▻r▻e▻d▻)▻
▻D▻e▻w▻e▻y▻ ▻(▻b▻l▻u▻e▻)▻
▻L▻o▻u▻i▻e▻ ▻(▻g▻r▻e▻e▻n▻)▻

Unlike getline() in the previous slide, istream::get() is a method.

Read a character

Consider this small file:

$ cat ~cs253/pub/ducks
Huey (red)
Dewey (blue)
Louie (green)

Extracting a char via >> only reads a whitespace-delimited character, which is rarely useful. That is, it skips whitespace.

const string home = getpwnam("cs253")->pw_dir;
ifstream in(home+"/pub/ducks");
char c;
while (in >> c)
    cout << "▻" << c;
▻H▻u▻e▻y▻(▻r▻e▻d▻)▻D▻e▻w▻e▻y▻(▻b▻l▻u▻e▻)▻L▻o▻u▻i▻e▻(▻g▻r▻e▻e▻n▻)

Unreading

$ cat ~cs253/pub/ducks
Huey (red)
Dewey (blue)
Louie (green)

To put a character back, use istream::unget():

const string home = getpwnam("cs253")->pw_dir;
ifstream in(home+"/pub/ducks");
char c;
in.get(c);
cout << "First character: " << c << '\n';
in.unget();     // no argument
string s;
in >> s;
cout << "First string: " << s << '\n';
First character: H
First string: Huey

Peeking

$ cat ~cs253/pub/ducks
Huey (red)
Dewey (blue)
Louie (green)

To look at the next char without consuming it, use istream::peek():

const string home = getpwnam("cs253")->pw_dir;
ifstream in(home+"/pub/ducks");
char c = in.peek();
cout << "First character: " << c << '\n';
string s;
in >> s;
cout << "First string: " << s << '\n';
First character: H
First string: Huey

Stream state

Don’t do this:

% # This file is only one line.
% cat /etc/hostname
beethoven
% wc -l /etc/hostname
1 /etc/hostname
ifstream in("/etc/hostname");
while (!in.eof()) { // 🦡
    string s;
    getline(in, s);
    cout << "▻" << s << '\n';
}
▻beethoven
▻

The better way to do it

% cat /etc/hostname
beethoven
ifstream in("/etc/hostname");
string s;
while (getline(in, s))
    cout << "▻" << s << '\n';
▻beethoven

Don’t try to predict the future. Just ask for a line, and stop when the answer is “no”.

Same for numbers

% cat /proc/uptime
6180189.66 73020938.46
ifstream in("/proc/uptime");
double d;
while (in >> d)
    cout << "▻" << d << '\n';
▻6.18019e+06
▻7.30209e+07
What value does the expression in >> d return?

It returns a reference to the stream in. It does not return the value of d.

Numbers are harder

What happens when a read fails

There are rules about what value gets put into a variable (call it x) when reading fails:

.eof() is set if we hit end-of-file, whether the number was good or bad. Consider a file that ends with 123, no newline. Reading that number will succeed, but it will also set .eof().

Technique for error-checking

Really?

Now with error checking!

% cat /proc/uptime
6180189.67 73020938.55
constexpr auto filename = "/proc/uptime";
ifstream in(filename);
if (!in) {  // Is the stream in a bad state?
    cerr << "Can’t open " << filename << "\n";
    return 1;
}

double d;
while (d=1.0, in >> d)
    cout << "▻" << d << '\n';

// The final read failed.  Bad data, or just no more?
// Overflow (too big in either direction) sets d to ±∞.
// Error sets d to 0.0.
// EOF leaves d alone.
if (d != 1.0)
    cerr << filename << " contains non-double data.  😠\n";
▻6.18019e+06
▻7.30209e+07

Now with error checking!

% cat /proc/uptime
6180189.68 73020938.69
constexpr auto filename = "/proc/uptime";
ifstream in(filename);
if (!in) {  // Is the stream in a bad state?
    cerr << "Can’t open " << filename << "\n";
    return 1;
}

int n;  // OOPS——file contains doubles
while (n=1, in >> n)
    cout << "▻" << n << '\n';

// The final read failed.  Bad data, or just no more?
// Overflow (too big in either direction) sets n to biggest ±value.
// Error sets n to 0.
// EOF leaves n alone.
if (n != 1)
    cerr << filename << " contains non-int data.  😠\n";
▻6180190
/proc/uptime contains non-int data.  😠