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, 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
- Some C++ programmers think that endl and
\n
are synonymous.
They are not.
\n
means: Add a newline ('\n') to the output.
- endl means: Add a newline to the output, and flush the output
buffer. 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 to 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.
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
- 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() doesn’t 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
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
- Reading strings or lines is simple—there’s data to read, or not.
- Numbers are harder, with three possibilities:
good data, no data, or bad data.
- These have guaranteed effects on the variable you’re reading:
- good data: the variable gets the value read
- end of file: the variable is unchanged, .eof() set, stream set to
an failed state.
- bad data: the variable is set to the most positive/negative value if
the number was too big/small, or
0
for 🐠 or a malformed number
like -
or 1.2ex
, .eof() maybe set, stream set to failed
state.
- Therefore, set the number to
1
before trying to read. If the
read fails (stream failed), inspect the number.
If it’s still 1
, then the failure was a simple EOF.
- “But what if they read a
1
?”
Then the read didn’t fail.
Really?
- Really.
- “Why not simply check the stream state?”
A read loop will fail eventually, when you run out of data.
The last read will always fail.
- “Why not simply check .eof()?”
In the case of running out of input inside a number.
What if the the last bytes of a file are just +
, or 1e
(an incomplete floating-point number, e.g., 1e6
, 1×10⁶)?
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
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. 😠