CS253: Software Development with C++

Spring 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, clog is buffered. Use cerr for error messages, clog if you use standard error for logging purposes.

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 << "alpha\n";
cout << "beta" << endl;  // 🦡
cout << "gamma" << "\n";
cout << "delta" << '\n';
alpha
beta
gamma
delta

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. You may recognize it as the right shift operator. It’s that, too. Isn’t operator overloading wonderful?

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.

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
1764352.69 20870867.85
ifstream in("/proc/uptime");
double d;
while (in >> d)
    cout << "▻" << d << '\n';
▻1.76435e+06
▻2.08709e+07
What value does in >> d return?

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

Error checking

Really?

Now with error checking!

% cat /proc/uptime
1764352.70 20870867.90
const 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";
▻1.76435e+06
▻2.08709e+07

Now with error checking!

% cat /proc/uptime
1764352.71 20870867.95
const 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";
▻1764353
/proc/uptime contains non-int data.  😠