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.
- cin: standard input
- cout: standard output (for normal output)
- cerr: standard error (for error output)
- clog: standard error (for error output)
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
- Some C++ programmers think that endl and
\n
are synonymous.
They are not.
cout << '\n'
means: Add a newline to standard output,
which ends the current line.
cout << endl
is identical to cout << '\n' << flush
,
where flush is an I/O manipulator that flushes any buffered
output. Terminal output gets flushed automatically upon newline, when
the output buffer gets full, and when the program ends. That’s almost
always good enough.
- Don’t use endl unless you really want the additional effects of
flush. It’s not free, and it confuses those who know what it does.
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.10 (Cerulean Leopard)
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
- A stream is put into an error state if reading a number, string,
or line, fails.
- Test the state by evaluating the stream as a bool:
happy=true, error=false.
cin >> n
returns a reference to cin, so you can treat the
result of that expression as a bool success indicator.
- getline() returns a reference to the stream, so you can treat the
result of getline() as a bool success indicator.
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
▻
- istream::eof() does not ask “Will the next read hit end-of-file?”
- Instead, istream::eof() asks “Did we already hit end-of-file?”
- That is, it doesn’t predict what the next read will do.
- It tells you what the previous read did.
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
3984036.20 47112155.39
ifstream in("/proc/uptime");
double d;
while (in >> d)
cout << "▻" << d << '\n';
▻3.98404e+06
▻4.71122e+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
- Reading strings or lines is simple—there’s data to read, or not.
The string or line is there, or it’s not. There’s no such thing
as a “bad string” or “bad line”.
- Numbers, however, have three possibilities:
good data, no data, or bad data.
- The
>>
operator skips leading whitespace,
but it will NOT skip over bad data.
- If a file contains
123 foobar 456
and you try to read
two ints, the second read will simply fail.
- Good doubles:
123 -45.67 +12.34E-56 NAN infinity
- Bad doubles:
FISH 🐠 -zulu 12.34E + 1e999999
What happens when a read fails
There are rules about what value gets put into a variable (call it
x
) when reading fails:
- end of file before any good stuff:
x
is unchanged, .eof() set,
stream set to a failed state.
- data too big/small (overflow):
x
is set to the most positive/negative value,
failed state
- malformed number, or not a number at all:
x
set to 0
, failed state
.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
- Set
x
to 1
before trying to read.
- If your CPU can’t do that in one instruction, get a better CPU.
1
works because it’s not 0
, and it’s not the biggest
positive or negative value.
- If the read fails (the stream is in a failed state), inspect
x
.
- If
x==1
, then the failure was a simple EOF, and that’s ok.
- “But what if they actually read a
1
?”
- Then the read didn’t fail.
- If
x!=1
, then the read failed for something other than
simple EOF, so we should complain.
Really?
- Really.
- “Why not simply check .fail()?”
A read loop will fail eventually, when you run out of data.
If a file contains five numbers, the sixth read will fail.
That’s not an error; it’s just the end of the data.
- “Why not simply check .eof()?”
.eof() can be set for success or failure—consider running out of input
inside a number. What if the the last bytes of a file are just
+
, or 1e
? That will put the stream in a failed state, because
the read failed, and set .eof(), because it hit end-of-file.
Now with error checking!
% cat /proc/uptime
3984036.21 47112155.45
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";
▻3.98404e+06
▻4.71122e+07
Now with error checking!
% cat /proc/uptime
3984036.22 47112155.50
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";
▻3984036
/proc/uptime contains non-int data. 😠