Day 16
Until now,
you've been using cout to write to the screen and cin to read from the keyboard, without a full understanding
of how they work. Today, you will learn
What
streams are and how they are used.
How to manage input and output using streams.
How to
write to and read from files using streams.
C++ does
not, as part of the language, define how data is written to the screen or to a
file, nor how data is read into a program. These are
clearly essential parts of working with C++, however, and the standard C++
library now includes the iostream library, which
facilitates input and output (I/O).
The
advantage of having the input and output kept apart from the language and
handled in libraries is that it is easier to make the language
"platform-independent." That is, you can write C++ programs on a PC
and then recompile them and run them on a Sun Workstation. The compiler
manufacturer just supplies the right library, and everything works. At least
that's the theory.
--------------------------------------------------------------------------------
NOTE: A
library is a collection of OBJ files that can be
linked to your program to provide additional functionality. This is the most
basic form of code reuse, and has been around since ancient programmers chiseled 1s and 0s into the walls of caves.
--------------------------------------------------------------------------------
The iostream classes view the flow of data from your program to
the screen as being a stream of data, one byte following another. If the
destination of the stream is a file or the screen, the source is usually some
part of your program. If the stream is reversed, the data can come from the
keyboard or a disk file and be "poured" into your data variables.
One
principal goal of streams is to encapsulate the problems of getting the data to
and from the disk or the screen. Once a stream is created, your program works
with the stream and the stream sweats the details. Figure 16.1 illustrates this
fundamental idea.
Writing to
the disk (and to a lesser extent the screen) is very "expensive." It
takes a long time (relatively speaking) to write data to the disk or to read
data from the disk, and execution of the program is generally blocked by disk
writes and reads. To solve this problem, streams provide "buffering."
Data is written into the stream, but it is not written back out to the disk
immediately. Instead, the stream's buffer fills and fills, and when it is full
it writes to the disk all at once.
Picture water trickling into the top of a tank, and the tank filling and
filling, but no water running out of the bottom. Figure 16.2 illustrates this idea.
When the
water (data) reaches the top, the valve opens and all the water flows out in a
rush. Figure 16.3 illustrates this.
Once the
buffer is empty, the bottom valve closes, the top valve opens, and more water
flows into the buffer tank. Figure 16.4 illustrates this.
Every once
in a while you need to get the water out of the tank even before it is full.
This is called "flushing the buffer." Figure 16.5 illustrates this
idea.
As you
might expect, C++ takes an object-oriented view toward implementing streams and
buffers.
The streambuf class manages the buffer, and its member
functions provide the capability to fill, empty, flush, and otherwise
manipulate the buffer.
The ios class is the base class to the input and output stream
classes. The ios class has a streambuf
object as a member variable.
The istream and ostream classes
derive from the ios class and specialize
input and output stream behavior, respectively.
The iostream class is derived from both the istream
and the ostream classes and provides input and output
methods for writing to the screen.
The fstream classes provide input and output from files.
Standard
I/O Objects
When a C++
program that includes the iostream classes starts,
four objects are created and initialized:
--------------------------------------------------------------------------------
NOTE: The iostream class library is added automatically to your
program by the compiler. All you need to do to use these functions is to put
the appropriate include statement at the top of your program listing.
--------------------------------------------------------------------------------
cin (pronounced "see-in") handles input
from the standard input, the keyboard.
cou (pronounced "see-out") handles
output to the standard output, the screen.
cer (pronounced "see-err") handles unbuffered output to the standard error device, the screen.
Because this is unbuffered, everything sent to cerr is written to the standard error device immediately,
without waiting for the buffer to fill or for a flush command to be received.
clo (pronounced "see-log") handles
buffered error messages that are output to the standard error device, the
screen. It is common for this to be "redirected" to a log file, as
described in the following section.
Each of the
standard devices, input, output, and error, can be redirected to other devices.
Standard error is often redirected to a file, and standard input and output can
be piped to files using operating system commands.
--------------------------------------------------------------------------------
New Term:
Redirecting refers to sending output (or input) to a place different than the
default. The redirection operators for DOS and UNIX are (<) redirect input
and (>) redirect output.
--------------------------------------------------------------------------------
Piping
refers to using the output of one program as the input of another.
DOS
provides rudimentary redirection commands, such as redirect output (>) and
(>)redirect input (<). UNIX provides more
advanced redirection capabilities, but the general idea is the same: Take the
output intended for the screen and write it to a file, or pipe it into another
program. Alternatively, the input for a program can be extracted from a file
rather than from the keyboard.
Redirection
is more a function of the operating system than of the iostream
libraries. C++ just provides access to the four standard devices; it is up to
the user to redirect the devices to whatever alternatives are needed.
The global
object cin is responsible for input and is made
available to your program when you include iostream.h.
In previous examples, you used the overloaded extraction operator (>>) to
put data into your program's variables. How does this work? The syntax, as you
may remember, is the following:
int someVariable;
cout << "Enter a number: ";
cin >> someVariable;
The global
object cout is discussed later today; for now, focus
on the third line, cin >> someVariable;. What can you guess about cin?
Clearly it
must be a global object, because you didn't define it in your own code. You
know from previous operator experience that cin has
overloaded the extraction operator (>>) and that the effect is to write
whatever data cin has in its buffer into your local
variable, someVariable.
What may
not be immediately obvious is that cin has overloaded
the extraction operator for a great variety of parameters, among them int&, short&, long&, double&, float&,
char&, char*, and so forth. When you write cin
>> someVariable;, the
type of someVariable is assessed. In the example
above, someVariable is an integer, so the following
function is called:
istream & operator>> (int
&)
Note that
because the parameter is passed by reference, the extraction operator is able
to act on the original variable. Listing 16.1 illustrates the use of cin.
View Code
1: //Listing 16.1 -- character strings and cin
2:
3: #include <iostream.h>
4:
5: int main()
6: {
7: int myInt;
8: long myLong;
9: double myDouble;
10: float myFloat;
11: unsigned int myUnsigned;
12:
13: cout << "int: ";
14: cin >> myInt;
15: cout <<
"Long: ";
16: cin >> myLong;
17: cout <<
"Double: ";
18: cin >> myDouble;
19: cout <<
"Float: ";
20: cin >> myFloat;
21: cout <<
"Unsigned: ";
22: cin >> myUnsigned;
23:
24: cout <<
"\n\nInt:\t" << myInt
<< endl;
25: cout <<
"Long:\t" << myLong
<< endl;
26: cout <<
"Double:\t" << myDouble
<< endl;
27: cout << "Float:\t" << myFloat
<< endl;
28: cout <<
"Unsigned:\t" << myUnsigned
<< endl;
29: return 0;
30: }
Output: int: 2
Long:
70000
Double:
987654321
Float:
3.33
Unsigned:
25
Int: 2
Long: 70000
Double:
9.87654e+08
Float: 3.33
Unsigned: 25
Analysis:
On lines 7-11, variables of various types are declared. On lines 13-22, the
user is prompted to enter values for these variables, and the results are
printed (using cout) on lines 24-28.
The output
reflects that the variables were put into the right "kinds" of
variables, and the program works as you might expect.
Strings
cin can also handle character pointer (char*)
arguments; thus, you can create a character buffer and use cin
to fill it. For example, you can write this:
char YourName[50]
cout << "Enter your name: ";
cin >> YourName;
If you
enter Jesse, the variable YourName will be filled
with the characters J, e, s, s, e, \0. The last character is a null; cin automatically ends the string with a null character,
and you must have enough room in the buffer to allow for the entire string plus
the null. The null signals "end of string" to the standard library
functions discussed on Day 21, "What's Next."
String
Problems
After all
this success with cin, you might be surprised when
you try to enter a full name into a string. cin believes that white space is a separator. When it
sees a space or a new line, it assumes the input for the parameter is complete,
and in the case of strings it adds a null character right then and there.
Listing 16.2 illustrates this problem.
View Code
1: //Listing 16.2 -- character strings and cin
2:
3: #include <iostream.h>
4:
5: int main()
6: {
7: char YourName[50];
8: cout <<
"Your first name: ";
9: cin >> YourName;
10: cout <<
"Here it is: " << YourName << endl;
11: cout <<
"Your entire name: ";
12: cin >> YourName;
13: cout <<
"Here it is: " << YourName << endl;
14: return 0;
15: }
Output:
Your first name: Jesse
Here it
is: Jesse
Your
entire name: Jesse Liberty
Here it is: Jesse
Analysis:
On line 7, a character array is created to hold the user's input. On line 8,
the user is prompted to enter one name, and that name is stored properly, as
shown in the output.
On line 11,
the user is again prompted, this time for a full name. cin reads the input, and when it sees the space
between the names, it puts a null character after the first word and terminates
input. This is not exactly what was intended.
To
understand why this works this way, examine Listing 16.3, which shows input for
a number of fields.
View Code
1: //Listing 16.3 - character strings and cin
2:
3: #include <iostream.h>
4:
5: int main()
6: {
7: int myInt;
8: long myLong;
9: double myDouble;
10: float myFloat;
11: unsigned int myUnsigned;
12: char myWord[50];
13:
14: cout <<
"int: ";
15: cin >> myInt;
16: cout <<
"Long: ";
17: cin >> myLong;
18: cout <<
"Double: ";
19: cin >> myDouble;
20: cout <<
"Float: ";
21: cin >> myFloat;
22: cout <<
"Word: ";
23: cin >> myWord;
24: cout <<
"Unsigned: ";
25: cin >> myUnsigned;
26:
27: cout <<
"\n\nInt:\t" << myInt
<< endl;
28: cout <<
"Long:\t" << myLong
<< endl;
29: cout <<
"Double:\t" << myDouble
<< endl;
30: cout <<
"Float:\t" << myFloat
<< endl;
31: cout <<
"Word: \t" << myWord << endl;
32: cout <<
"Unsigned:\t" << myUnsigned
<< endl;
33:
34: cout <<
"\n\nInt, Long, Double, Float, Word, Unsigned: ";
35: cin >> myInt >> myLong >> myDouble;
36: cin >> myFloat >> myWord >> myUnsigned;
37: cout <<
"\n\nInt:\t" << myInt
<< endl;
38: cout << "Long:\t"
<< myLong << endl;
39: cout <<
"Double:\t" << myDouble
<< endl;
40: cout <<
"Float:\t" << myFloat
<< endl;
41: cout <<
"Word: \t" << myWord << endl;
42: cout <<
"Unsigned:\t" << myUnsigned
<< endl;
43:
44:
45: return 0;
46: }
Output: Int: 2
Long:
30303
Double:
393939397834
Float: 3.33
Word: Hello
Unsigned:
85
Int: 2
Long: 30303
Double:
3.93939e+11
Float: 3.33
Word: Hello
Unsigned: 85
Int,
Long, Double, Float, Word, Unsigned: 3 304938 393847473 6.66 bye -2
Int: 3
Long: 304938
Double:
3.93847e+08
Float: 6.66
Word: bye
Unsigned: 65534
Analysis:
Once again, a number of variables are created, this time including a char
array. The user is prompted for input and the output is faithfully printed.
On line 34,
the user is prompted for all the input at once, and then each "word"
of input is assigned to the appropriate variable. It is in order to facilitate
this kind of multiple assignment that cin must
consider each word in the input to be the full input for each variable. If cin was to consider the entire input to be part of one
variable's input, this kind of concatenated input would be impossible.
Note that
on line 35 the last object requested was an unsigned integer, but the user entered
-2. Because cin believes it is writing to an unsigned
integer, the bit pattern of -2 was evaluated as an unsigned integer, and when
written out by cout, the value 65534 was displayed.
The unsigned value 65534 has the exact bit pattern of the signed value -2.
Later in
this chapter you will see how to enter an entire string into a buffer,
including multiple words. For now, the question arises, "How does the
extraction operator manage this trick of concatenation?"
The return
value of cin is a reference to an istream
object. Because cin itself is an istream
object, the return value of one extraction operation can be the input to the
next extraction.
int VarOne, varTwo, varThree;
cout << "Enter three numbers: "
cin >> VarOne
>> varTwo >> varThree;
When you
write cin >> VarOne
>> varTwo >> varThree;, the first extraction is evaluated (cin
>> VarOne). The return value from this is
another istream object, and that object's extraction
operator gets the variable varTwo. It is as if you
had written this:
((cin >> varOne) >> varTwo) >> varThree;
You'll see
this technique repeated later when cout is discussed.
In addition
to overloading operator>>, cin has a number of
other member functions. These are used when finer control over the input is
required.
operator>>
taking a character reference can be used to get a single character from the
standard input. The member function get() can also be
used to obtain a single character, and can do so in two ways. get() can be used with no parameters, in which case the
return value is used, or it can be used with a reference to a character. Using get() with No Parameters The first form of get() is without
parameters. This returns the value of the character found, and will return EOF (end of file) if the end of the file is reached. get() with no parameters is not often used. It is not
possible to concatenate this use of get() for multiple
input, because the return value is not an iostream
object. Thus, the following won't work:
cin.get() >>myVarOne
>> myVarTwo; // illegal
The return
value of (cin.get() >> myVarOne) is an
integer, not an iostream object.
A common
use of get() with no parameters is illustrated in
Listing 16.4.
1: // Listing 16.4 - Using get()
with no parameters
2: #include <iostream.h>
3:
4: int main()
5: {
6: char ch;
7: while ( (ch = cin.get()) != EOF)
8: {
9: cout << "ch: " << ch << endl;
10: }
11: cout <<
"\nDone!\n";
12: return 0;
13: }
--------------------------------------------------------------------------------
NOTE: To
exit this program, you must send end of file from the keyboard. On DOS
computers use Ctrl+Z; on UNIX units use Ctrl+D.
--------------------------------------------------------------------------------
Output:
Hello
ch: H
ch: e
ch: l
ch: l
ch: o
ch:
World
ch: W
ch: o
ch: r
ch: l
ch: d
ch:
(ctrl-z)
Done!
Analysis:
On line 6, a local character variable is declared. The while loop assigns the
input received from cin.get() to ch, and if it is not EOF the string is printed out. This output is buffered
until an end of line is read, however. Once EOF is
encountered (by pressing Ctrl+Z on a DOS machine, or Ctrl+D on a UNIX machine), the loop exits.
Note that
not every implementation of istream supports this
version of get(). Using get()
with a Character Reference Parameter When a character is passed as input to
get(), that character is filled with the next character in the input stream.
The return value is an iostream object, and so this
form of get() can be concatenated, as illustrated in
Listing 16.5.
View Code
1: // Listing 16.5 - Using get()
with parameters
2: #include <iostream.h>
3:
4: int main()
5: {
6: char a, b, c;
7:
8: cout <<
"Enter three letters: ";
9:
10: cin.get(a).get(b).get(c);
11:
12: cout <<
"a: " << a << "\nb: "
<< b << "\nc: " << c
<< endl;
13: return 0;
14: }
Output:
Enter three letters: one
a: o
b: n
c: e
Analysis:
On line 6, three character variables are created. On line 10, cin.get()
is called three times, concatenated. First cin.get(a) is called. This
puts the first letter into a and returns cin so that
when it is done, cin.get(b) is called, putting the next letter into b. The end
result of this is that cin.get(c) is called and the
third letter is put in c.
Because cin.get(a)
evaluates to cin, you could have written this:
cin.get(a) >> b;
In this
form, cin.get(a) evaluates to cin, so the
second phrase is cin >> b;.
DO use the
extraction operator (>>) when you need to skip over white space. DO use get() with a character parameter when you need to examine
every character, including white space. DON'T use get()
with no parameters at all; it is more or less obsolete.
--------------------------------------------------------------------------------
The
extraction operator (>>) can be used to fill a character array, as can
the member functions get() and getline().
The final
form of get() takes three parameters. The first
parameter is a pointer to a character array, the second parameter is the
maximum number of characters to read plus one, and the third parameter is the
termination character.
If you
enter 20 as the second parameter, get() will read 19
characters and then will null-terminate the string, which it will store in the
first parameter. The third parameter, the termination character, defaults to newline (`\n'). If a termination character is reached
before the maximum number of characters is read, a null is written and the
termination character is left in the buffer.
View Code
1: // Listing 16.6 - Using get()
with a character array
2: #include <iostream.h>
3:
4: int main()
5: {
6: char stringOne[256];
7: char stringTwo[256];
8:
9: cout << "Enter string one: ";
10: cin.get(stringOne,256);
11: cout <<
"stringOne: " << stringOne
<< endl;
12:
13: cout <<
"Enter string two: ";
14: cin >> stringTwo;
15: cout <<
"StringTwo: " << stringTwo
<< endl;
16: return 0;
17: }
Output:
Enter string one: Now is the time
stringOne: Now is the time
Enter
string two: For all good
StringTwo: For
Analysis:
On lines 6 and 7, two character arrays are created. On line 9, the user is
prompted to enter a string, and cin.get() is called on line 10. The first parameter is the buffer
to fill, and the second is one more than the maximum number for get() to accept (the extra position being given to the null
character, (`\0')). The defaulted third parameter is a newline.
The user enters
Now is the time. Because the user ends the phrase with a newline,
that phrase is put into stringOne, followed by a
terminating null.
The user is
prompted for another string on line 13, and this time the extraction operator
is used. Because the extraction operator takes everything up to the first white
space, the string For, with a terminating null
character, is stored in the second string, which of course is not what was
intended.
Another way
to solve this problem is to use getline(), as illustrated in Listing 16.7.
View Code
1: // Listing 16.7 - Using getline()
2: #include <iostream.h>
3:
4: int main()
5: {
6: char stringOne[256];
7: char stringTwo[256];
8: char stringThree[256];
9:
10: cout <<
"Enter string one: ";
11: cin.getline(stringOne,256);
12: cout << "stringOne: " << stringOne << endl;
13:
14: cout << "Enter string two: ";
15: cin >> stringTwo;
16: cout << "stringTwo: " << stringTwo << endl;
17:
18: cout <<
"Enter string three: ";
19: cin.getline(stringThree,256);
20: cout <<
"stringThree: " << stringThree << endl;
21: return 0;
22: }
Output:
Enter string one: one two three
stringOne: one two three
Enter
string two: four five six
stringTwo: four
Enter
string three: stringThree: five six
Analysis:
This example warrants careful examination; there are some potential surprises.
On lines 6-8, three character arrays are declared.
On line 10,
the user is prompted to enter a string, and that string is read by getline().
Like get(), getline() takes
a buffer and a maximum number of characters. Unlike get(),
however, the terminating newline is read and thrown
away. With get() the terminating newline
is not thrown away. It is left in the input buffer.
On line 14,
the user is prompted again, and this time the extraction operator is used. The
user enters four five six, and the first word, four, is put in stringTwo. The string Enter string three is then displayed,
and getline() is called again. Because five six is still in the input
buffer, it is immediately read up to the newline; getline()
terminates and the string in stringThree is printed
on line 20.
The user
has no chance to enter string three, because the second getline() call is fulfilled
by the string remaining in the input buffer after the call to the extraction
operator on line 15.
The
extraction operator (>>) reads up to the first white space and puts the
word into the character array.
The member
function get() is overloaded. In one version, it takes
no parameters and returns the value of the character it receives. In the second
version, it takes a single character reference and returns the istream object by reference.
In the
third and final version, get() takes a character
array, a number of characters to get, and a termination character (which
defaults to newline). This version of get() reads
characters into the array until it gets to one fewer than its maximum number of
characters or it encounters the termination character, whichever comes first.
If get() encounters the termination character, it
leaves that character in the input buffer and stops reading characters.
The member
function getline() also takes three parameters: the buffer to fill, one more
than the maximum number of characters to get, and the termination character. getline()functions
exactly like get() does with these parameters, except getline()
throws away the terminating character.
At times
you want to ignore the remaining characters on a line until you hit either end
of line (EOL) or end of file (EOF).
The member function ignore() serves this purpose. ignore() takes two parameters, the maximum number of
characters to ignore and the termination character. If you write ignore(80,'\n'), up to 80 characters will be thrown away
until a newline character is found. The newline is then thrown away and the ignore()
statement ends. Listing 16.8 illustrates the use of ignore().
View Code
1: // Listing 16.8 - Using ignore()
2: #include <iostream.h>
3:
4: int main()
5: {
6: char stringOne[255];
7: char stringTwo[255];
8:
9: cout <<
"Enter string one:";
10: cin.get(stringOne,255);
11: cout <<
"String one" << stringOne << endl;
12:
13: cout <<
"Enter string two: ";
14: cin.getline(stringTwo,255);
15: cout <<
"String two: " << stringTwo << endl;
16:
17: cout <<
"\n\nNow try again...\n";
18:
19: cout <<
"Enter string one: ";
20: cin.get(stringOne,255);
21: cout <<
"String one: " << stringOne<< endl;
22:
23: cin.ignore(255,'\n');
24:
25: cout <<
"Enter string two: ";
26: cin.getline(stringTwo,255);
27: cout <<
"String Two: " << stringTwo<< endl;
28: return 0;
29: }
Output:
Enter string one:once upon a
time
String oneonce upon a time
Enter
string two: String two:
Now try
again...
Enter
string one: once upon a time
String
one: once upon a time
Enter
string two: there was a
String Two: there was a
Analysis: On
lines 6 and 7, two character arrays are created. On line 9, the user is
prompted for input and types once upon a time, followed by Enter. On line 10, get() is used to read this string. get()
fills stringOne and terminates on the newline, but leaves the newline
character in the input buffer.
On line 13,
the user is prompted again, but the getline() on line 14 reads the newline
that is already in the buffer and terminates immediately, before the user can
enter any input.
On line 19,
the user is prompted again and puts in the same first line of input. This time,
however, on line 23, ignore() is used to
"eat" the newline character. Thus, when the
getline()
call on line 26 is reached, the input buffer is empty, and the user can input
the next line of the story.
The input
object cin has two additional methods that can come
in rather handy: peek(), which looks at but does not
extract the next character, and putback(), which
inserts a character into the input stream. Listing 16.9 illustrates how these
might be used.
1: // Listing 16.9 - Using peek()
and putback()
2: #include <iostream.h>
3:
4: int main()
5: {
6: char ch;
7: cout <<
"enter a phrase: ";
8: while ( cin.get(ch) )
9: {
10: if (ch ==
`!')
11: cin.putback(`$');
12: else
13: cout
<< ch;
14: while (cin.peek() == `#')
15: cin.ignore(1,'#');
16: }
17: return 0;
18: }
Output: enter
a phrase: Now!is#the!time#for!fun#!
Now$isthe$timefor$fun$
Analysis:
On line 6, a character variable, ch, is declared, and
on line 7, the user is prompted to enter a phrase. The purpose of this program
is to turn any exclamation marks (!) into dollar signs ($) and to remove any
pound symbols (#).
The program
loops as long as it is getting characters other than the end of file (remember
that cin.get() returns 0 for end of file). If the current character is
an exclamation point, it is thrown away and the $ symbol is put back into the
input buffer; it will be read the next time through. If the current item is not
an exclamation point, it is printed. The next character is "peeked"
at, and when pound symbols are found, they are removed.
This is not
the most efficient way to do either of these things (and it won't find a pound
symbol if it is the first character), but it does illustrate how these methods
work. They are relatively obscure, so don't spend a lot of time worrying about
when you might really use them. Put them into your bag of tricks; they'll come
in handy sooner or later.
--------------------------------------------------------------------------------
TIP: peek() and putback() are typically
used for parsing strings and other data, such as when writing a compiler.
--------------------------------------------------------------------------------
You have
used cout along with the overloaded insertion
operator (<<) to write strings, integers, and other numeric data to the
screen. It is also possible to format the data, aligning columns and writing
the numeric data in decimal and hexadecimal. This section will show you how.
You've
already seen that using endl will flush the output
buffer. endl calls cout's member function flush(), which writes all of the
data it is buffering. You can call the flush() method
directly, either by calling the flush() member method or by writing the
following:
cout << flush
This can be
convenient when you need to ensure that the output buffer is emptied and that
the contents are written to the screen.
Just as the
extraction operator can be supplemented with get() and
getline(), the insertion operator can be supplemented
with put() and write().
The
function put() is used to write a single character to
the output device. Because put() returns an ostream reference, and because cout
is an ostream object, you can concatenate put() just
as you do the insertion operator. Listing 16.10 illustrates this idea.
1: // Listing 16.10 - Using put()
2: #include <iostream.h>
3:
4: int main()
5: {
6: cout.put(`H').put(`e').put(`l').put(`l').put(`o').put(`\n');
7: return 0;
8: }
Output:
Hello
Analysis:
Line 6 is evaluated like this: cout.put(`H') writes the letter H to the screen and returns the cout object. This leaves the following:
cout.put(`e').put(`l').put(`l').put(`o').put(`\n');
The letter
e is written, leaving cout.put(`l'). This process repeats, each letter being written and
the cout object returned until the final character
(`\n') is written and the function returns.
The
function write() works just like the insertion
operator (<<), except that it takes a parameter that tells the function
the maximum number of characters to write. Listing 16.11 illustrates its use.
1: // Listing 16.11 - Using write()
2: #include <iostream.h>
3: #include <string.h>
4:
5: int main()
6: {
7: char One[] =
"One if by land";
8:
9:
10:
11: int fullLength = strlen(One);
12: int tooShort = fullLength -4;
13: int tooLong = fullLength + 6;
14:
15: cout.write(One,fullLength) <<
"\n";
16: cout.write(One,tooShort) <<
"\n";
17: cout.write(One,tooLong) <<
"\n";
18: return 0;
19: }
Output: One
if by land
One if by
One if by
land i?!
NOTE: The
last line of output may look different on your computer.
Analysis:
On line 7, one phrase is created. On line 11, the integer fullLength
is set to the length of the phrase and tooShort is
set to that length minus four, while tooLong is set
to fullLength plus six.
On line 15,
the complete phrase is printed using write(). The
length is set to the actual length of the phrase, and the correct phrase is
printed.
On line 16,
the phrase is printed again, but is four characters shorter than the full
phrase, and that is reflected in the output.
On line 17,
the phrase is printed again, but this time write() is
instructed to write an extra six characters. Once the phrase is written, the
next six bytes of contiguous memory are written.
The output
stream maintains a number of state flags, determining which base (decimal or
hexadecimal) to use, how wide to make the fields, and what character to use to
fill in fields. A state flag is just a byte whose individual bits are each
assigned a special meaning. Manipulating bits in this way is discussed on Day
21. Each of ostream's flags can be set using member
functions and manipulators.
The default
width of your output will be just enough space to print the number, character,
or string in the output buffer. You can change this by using width().
Because width() is a member function, it must be
invoked with a cout object. It only changes the width
of the very next output field and then immediately reverts to the default.
Listing 16.12 illustrates its use.
1: // Listing 16.12 - Adjusting the width of
output
2: #include <iostream.h>
3:
4: int main()
5: {
6: cout <<
"Start >";
7: cout.width(25);
8: cout <<
123 << "< End\n";
9:
10: cout <<
"Start >";
11: cout.width(25);
12: cout <<
123<< "< Next >";
13: cout <<
456 << "< End\n";
14:
15: cout <<
"Start >";
16: cout.width(4);
17: cout <<
123456 << "< End\n";
18:
19: return 0;
20: }
Output:
Start > 123< End
Start
> 123< Next
>456< End
Start
>123456< End
Analysis:
The first output, on lines 6-8, prints the number 123 within a field whose
width is set to 25 on line 7. This is reflected in the first line of output.
The second
line of output first prints the value 123 in the same field whose width is set
to 25, and then prints the value 456. Note that 456 is printed in a field whose
width is reset to just large enough; as stated, the effect of width() lasts only as long as the very next output.
The final
output reflects that setting a width that is smaller than the output is exactly
like setting a width that is just large enough.
Normally cout fills the empty field created by a call to width() with spaces, as shown above. At times you may want
to fill the area with other characters, such as asterisks. To do this, you call
fill() and pass in as a parameter the character you
want used as a fill character. Listing 16.13 illustrates this.
1: // Listing 16.3 - fill()
2:
3: #include <iostream.h>
4:
5: int main()
6: {
7: cout <<
"Start >";
8: cout.width(25);
9: cout <<
123 << "< End\n";
10:
11:
12: cout <<
"Start >";
13: cout.width(25);
14: cout.fill(`*');
15: cout <<
123 << "< End\n";
16: return 0;
17: }
Output:
Start > 123< End
Start
>******************123< End
Analysis: Lines
7-9 repeat the functionality from the previous example. Lines 12-15 repeat this
again, but this time, on line 14, the fill character is set to asterisks, as
reflected in the output.
The iostream objects keep track of their state by using flags.
You can set these flags by calling setf() and passing in one or another of the predefined
enumerated constants.
--------------------------------------------------------------------------------
New Term:
Objects are said to have state when some or all of their data represents a
condition that can change during the course of the program.
--------------------------------------------------------------------------------
For
example, you can set whether or not to show trailing zeros (so that 20.00 does not become truncated to 20). To turn trailing zeros on,
call setf(ios::showpoint).
The
enumerated constants are scoped to the iostream class
(ios) and thus are called with the full qualification
ios::flagname, such as ios::showpoint.
You can
turn on the plus sign (+) before positive numbers by using ios::showpos.
You can change the alignment of the output by using ios::left,
ios::right, or ios::internal.
Finally,
you can set the base of the numbers for display by using ios::dec
(decimal), ios::oct (octal--base eight), or ios::hex (hexadecimal--base sixteen). These flags can also
be concatenated into the insertion operator. Listing 16.14 illustrates these
settings. As a bonus, Listing 16.14 also introduces the setw
manipulator, which sets the width but can also be concatenated with the
insertion operator.
View Code
1: // Listing 16.14 - Using setf
2: #include <iostream.h>
3: #include <iomanip.h>
4:
5: int main()
6: {
7: const int
number = 185;
8: cout << "The number is "
<< number << endl;
9:
10: cout <<
"The number is " << hex << number << endl;
11:
12: cout.setf(ios::showbase);
13: cout <<
"The number is " << hex << number << endl;
14:
15: cout <<
"The number is " ;
16: cout.width(10);
17: cout <<
hex << number << endl;
18:
19: cout <<
"The number is " ;
20: cout.width(10);
21: cout.setf(ios::left);
22: cout <<
hex << number << endl;
23:
24: cout <<
"The number is " ;
25: cout.width(10);
26: cout.setf(ios::internal);
27: cout <<
hex << number << endl;
28:
29: cout <<
"The number is:" << setw(10) << hex << number << endl;
30: return 0;
31: }
Output:
The number is 185
The
number is b9
The
number is 0xb9
The
number is 0xb9
The
number is 0xb9
The
number is 0x b9
The number is:0x b9
Analysis:
On line 7, the constant int number is initialized to the value 185. On line 12, the flag showbase is set. This causes the prefix 0x
to be added to all hexadecimal numbers, as reflected in the output.
This is
displayed on line 8.
The value
is displayed again on line 10, but this time the manipulator hex is
concatenated, causing the value to be displayed in hexadecimal as b9. (b=11;
11*16=176+9=185).
On line 16,
the width is set to 10, and the value is pushed to the extreme right. On line
20, the width is again set to 10, but this time the alignment is set to the
left, and the number is again printed flush left.
On line 25,
once again the width is set to 10, but this time the alignment is internal.
Thus the 0x is printed flush left, but the value, b9, is printed flush right.
Finally, on
line 29, the concatenation operator setw() is used to set the width to 10, and the value is printed
again.
Streams
Versus the printf() Function
Most C++
implementations also provide the standard C I/O libraries, including the printf()
statement. Although printf() is in some ways easier to use than cout,
it is far less desirable.
printf() does not provide type safety, so it is easy
to inadvertently tell it to display an integer as if it was a character and
vice versa. printf() also does not support classes, and so it is not possible
to teach it how to print your class data; you must feed each class member to printf() one by one.
On the
other hand, printf() does make formatting much easier, because you can put the
formatting characters directly into the printf()
statement. Because printf() has its uses and many programmers still make extensive
use of it, this section will briefly review its use.
To use printf(),
be sure to include the STDIO.H header file. In its
simplest form, printf() takes a formatting string as its first parameter and then
a series of values as its remaining parameters.
The
formatting string is a quoted string of text and conversion specifiers.
All conversion specifiers must begin with the percent
symbol (%). The common conversion specifiers are
presented in Table 16.1.
Table 16.1.
The Common Conversion Specifiers.
Specifier
Used For
%s strings
%d integers
%l long
integer
%ld long integers
%f float
Each of the
conversion specifiers can also provide a width
statement and a precision statement, expressed as a float, where the digits to
the left of the decimal are used for the total width, and the digits to the
right of the decimal provide the precision for floats. Thus, %5d is the specifier for a five-digit-wide integer, and %15.5f is the specifier for a 15-digit-wide float, of which the final
five digits are dedicated to the decimal portion. Listing 16.15 illustrates
various uses of printf().
View Code
1: #include <stdio.h>
2: int main()
3: {
4: printf("%s","hello
world\n");
5:
6: char *phrase = "Hello again!\n";
7: printf("%s",phrase);
8:
9: int x = 5;
10: printf("%d\n",x);
11:
12: char *phraseTwo
= "Here's some values: ";
13: char *phraseThree
= " and also these: ";
14: int y = 7, z =
35;
15: long longVar =
98456;
16: float floatVar = 8.8;
17:
18: printf("%s %d %d %s %ld %f\n",phraseTwo,y,z,phraseThree,longVar,floatVar);
19:
20: char *phraseFour
= "Formatted: ";
21: printf("%s %5d %10d
%10.5f\n",phraseFour,y,z,floatVar);
22: return 0;
23: }
Output:
hello world
Hello
again!
5
Here's
some values: 7 35 and
also these: 98456 8.800000
Formatted: 7 35 8.800000
Analysis:
The first printf() statement, on line 4, uses the standard form: the term printf, followed by a quoted string with a conversion specifier (in this case %s), followed by a value to insert
into the conversion specifier.
The %s
indicates that this is a string, and the value for the string is, in this case,
the quoted string "hello world".
The second printf() statement is just like the first, but this time a
char pointer is used, rather than quoting the string right in place in the printf() statement.
The third printf(),
on line 10, uses the integer conversion specifier,
and for its value the integer variable x. The fourth printf() statement, on line
18, is more complex. Here six values are concatenated. Each conversion specifier is supplied, and then the values are provided,
separated by commas.
Finally, on
line 21, format specifications are used to specify width and precision. As you
can see, all of this is somewhat easier than using manipulators.
As stated
previously, however, the limitation here is that there is no type checking and printf()
cannot be declared a friend or member function of a class. So if you want to print
the various member data of a class, you must feed each accessor
method to the printf() statement explicitly.
Streams
provide a uniform way of dealing with data coming from the keyboard or the hard
disk and going out to the screen or hard disk. In either case, you can use the
insertion and extraction operators or the other related functions and
manipulators. To open and close files, you create ifstream
and ofstream objects as described in the next few
sections.
The particular
objects used to read from or write to files are called ofstream
objects. These are derived from the iostream objects
you've been using so far.
To get
started with writing to a file, you must first create an ofstream
object, and then associate that object with a particular file on your disk. To
use ofstream objects, you must be sure to include fstream.h in your program.
NOTE:
Because fstream.h includes iostream.h,
there is no need for you to include iostream
explicitly.
The iostream objects maintain flags that report on the state of
your input and output. You can check each of these flags using the Boolean
functions eof(), bad(), fail(), and good(). The function eof()
returns true if the iostream object has encountered EOF, end of file. The function bad()
returns TRUE if you attempt an invalid operation. The function fail() returns TRUE anytime bad() is true or an operation
fails. Finally, the function good() returns TRUE
anytime all three of the other functions are FALSE.
To open the
file myfile.cpp with an ofstream
object, declare an instance of an ofstream object and
pass in the filename as a parameter:
ofstream fout("myfile.cpp");
Opening
this file for input works exactly the same way, except it uses an ifstream object:
ifstream fin("myfile.cpp");
Note that fout and fin are names you assign; here fout
has been used to reflect its similarity to cout, and
fin has been used to reflect its similarity to cin.
One
important file stream function that you will need right away is close(). Every file stream object you create opens a file
for either reading or writing (or both). It is important to close()
the file after you finish reading or writing; this ensures that the file won't
be corrupted and that the data you've written is flushed to the disk.
Once the
stream objects are associated with files, they can be used like any other
stream objects. Listing 16.16 illustrates this.
View Code
1: #include <fstream.h>
2: int main()
3: {
4: char fileName[80];
5: char buffer[255]; // for user input
6: cout <<
"File name: ";
7: cin >> fileName;
8:
9: ofstream fout(fileName); // open
for writing
10: fout <<
"This line written directly to the file...\n";
11: cout <<
"Enter text for the file: ";
12: cin.ignore(1,'\n'); // eat the newline after the file name
13: cin.getline(buffer,255); // get
the user's input
14: fout <<
buffer << "\n"; // and
write it to the file
15: fout.close(); //
close the file, ready for reopen
16:
17: ifstream fin(fileName); // reopen for reading
18: cout <<
"Here's the contents of the file:\n";
19: char ch;
20: while (fin.get(ch))
21: cout
<< ch;
22:
23: cout <<
"\n***End of file contents.***\n";
24:
25: fin.close(); //
always pays to be tidy
26: return 0;
27: }
Output: File name: test1
Enter
text for the file: This text is written to the file!
Here's the contents of the file:
This line
written directly to the file...
This text
is written to the file!
***End of file contents.***
Analysis:
On line 4, a buffer is set aside for the filename, and on line 5 another buffer
is set aside for user input. The user is prompted to enter a filename on line
6, and this response is written to the fileName
buffer. On line 9, an ofstream object is created, fout, which is associated with the new filename. This opens
the file; if the file already exists, its contents are thrown away.
On line 10,
a string of text is written directly to the file. On line 11, the user is
prompted for input. The newline character left over
from the user's input of the filename is eaten on line 12, and the user's input
is stored into buffer on line 13. That input is written to the file along with
a newline character on line 14, and then the file is
closed on line 15.
On line 17,
the file is reopened, this time in input mode, and the contents are read, one
character at a time, on lines 20 and 21.
The default
behavior upon opening a file is to create the file if
it doesn't yet exist and to truncate the file (that is, delete all its
contents) if it does exist. If you don't want this default behavior,
you can explicitly provide a second argument to the constructor of your ofstream object.
Valid
arguments include:
ios::app--Appends to the end of existing files rather
than truncating them.
ios::at--Places you at the end of the file, but you
can write data anywhere in the file.
ios::trun--The default. Causes
existing files to be truncated.
ios::nocreat--If the file does not exist, the open fails.
ios::noreplac--If the file does already exist, the open
fails.
Note that
app is short for append; ate is short for at end, and trunc
is short for truncate. Listing 16.17 illustrates using append by reopening the
file from Listing 16.16 and appending to it.
View Code
1: #include <fstream.h>
2: int main() // returns 1
on error
3: {
4: char fileName[80];
5: char buffer[255];
6: cout <<
"Please re-enter the file name: ";
7: cin >> fileName;
8:
9: ifstream fin(fileName);
10: if (fin) // already exists?
11: {
12: cout
<< "Current file contents:\n";
13: char ch;
14: while (fin.get(ch))
15: cout
<< ch;
16: cout
<< "\n***End of file contents.***\n";
17: }
18: fin.close();
19:
20: cout <<
"\nOpening " << fileName <<
" in append mode...\n";
21:
22: ofstream fout(fileName,ios::app);
23: if (!fout)
24: {
25: cout
<< "Unable to open " << fileName << " for appending.\n";
26: return(1);
27: }
28:
29: cout <<
"\nEnter text for the file: ";
30: cin.ignore(1,'\n');
31: cin.getline(buffer,255);
32: fout <<
buffer << "\n";
33: fout.close();
34:
35: fin.open(fileName); // reassign existing fin object!
36: if (!fin)
37: {
38: cout
<< "Unable to open " << fileName << " for reading.\n";
39: return(1);
40: }
41: cout <<
"\nHere's the contents of the file:\n";
42: char ch;
43: while (fin.get(ch))
44: cout
<< ch;
45: cout <<
"\n***End of file contents.***\n";
46: fin.close();
47: return 0;
48: }
Output:
Please re-enter the file name: test1
Current
file contents:
This line
written directly to the file...
This text
is written to the file!
***End of
file contents.***
Opening
test1 in append mode...
Enter
text for the file: More text for the file!
Here's the contents of the file:
This line
written directly to the file...
This text
is written to the file!
More text
for the file!
***End of file contents.***
Analysis:
The user is again prompted to enter the filename. This time an input file
stream object is created on line 9. That open is tested on line 10, and if the
file already exists, its contents are printed on lines 12 to 16. Note that if(fin) is synonymous with if (fin.good()).
The input
file is then closed, and the same file is reopened, this time in append mode,
on line 22. After this open (and every open), the file is tested to ensure that
the file was opened properly. Note that if(!fout) is the same as testing if (fout.fail()).
The user is then prompted to enter text, and the file is closed again on line
33.
Finally, as
in Listing 16.16, the file is reopened in read mode; however, this time fin
does not need to be redeclared. It is just reassigned
to the same filename. Again the open is tested, on line 36, and if all is well,
the contents of the file are printed to the screen and the file is closed for
the final time.
--------------------------------------------------------------------------------
DO test
each open of a file to ensure that it opened successfully. DO reuse existing ifstream and ofstream objects. DO
close all fstream objects when you are done using
them. DON'T try to close or reassign cin or cout.
--------------------------------------------------------------------------------
Some
operating systems, such as DOS, distinguish between text files and binary
files. Text files store everything as text (as you might have guessed), so
large numbers such as 54,325 are stored as a string of numerals (`5', `4', `,', `3', `2', `5'). This can be inefficient, but has the
advantage that the text can be read using simple programs such as the DOS
program type.
To help the
file system distinguish between text and binary files, C++ provides the ios::binary flag. On many systems, this flag is ignored
because all data is stored in binary format. On some rather prudish systems,
the ios::binary flag is illegal and won't compile!
Binary
files can store not only integers and strings, but entire data structures. You
can write all the data at one time by using the write()
method of fstream.
If you use write(), you can recover the data using read(). Each of
these functions expects a pointer to character, however, so you must cast the
address of your class to be a pointer to character.
The second
argument to these functions is the number of characters to write, which you can
determine using sizeof(). Note that what is being written is just the data, not
the methods. What is recovered is just data. Listing 16.18 illustrates writing
the contents of a class to a file.
View Code
1: #include <fstream.h>
2:
3: class Animal
4: {
5: public:
6: Animal(int weight, long days):itsWeight(weight),itsNumberDaysAlive(days){}
7: ~Animal(){}
8:
9: int GetWeight()const
{ return itsWeight; }
10: void SetWeight(int weight) { itsWeight = weight;
}
11:
12: long GetDaysAlive()const {
return itsNumberDaysAlive;
}
13: void SetDaysAlive(long days) { itsNumberDaysAlive = days; }
14:
15: private:
16: int itsWeight;
17: long itsNumberDaysAlive;
18: };
19:
20: int main() // returns 1
on error
21: {
22: char fileName[80];
23: char buffer[255];
24:
25: cout <<
"Please enter the file name: ";
26: cin >> fileName;
27: ofstream fout(fileName,ios::binary);
28: if (!fout)
29: {
30: cout
<< "Unable to open " << fileName << " for writing.\n";
31: return(1);
32: }
33:
34: Animal Bear(50,100);
35: fout.write((char*) &Bear,sizeof Bear);
36:
37: fout.close();
38:
39: ifstream fin(fileName,ios::binary);
40: if (!fin)
41: {
42: cout
<< "Unable to open " << fileName << " for reading.\n";
43: return(1);
44: }
45:
46: Animal BearTwo(1,1);
47:
48: cout <<
"BearTwo weight: " << BearTwo.GetWeight() << endl;
49: cout <<
"BearTwo days: " << BearTwo.GetDaysAlive() << endl;
50:
51: fin.read((char*) &BearTwo, sizeof BearTwo);
52:
53: cout <<
"BearTwo weight: " << BearTwo.GetWeight() << endl;
54: cout <<
"BearTwo days: " << BearTwo.GetDaysAlive() << endl;
55: fin.close();
56: return 0;
57: }
Output:
Please enter the file name: Animals
BearTwo weight:
1
BearTwo
days: 1
BearTwo
weight: 50
BearTwo
days: 100
Analysis:
On lines 3-18, a stripped-down Animal class is declared. On lines 22-32, a file
is created and opened for output in binary mode. An animal whose weight is 50
and who is 100 days old is created on line 34, and its
data is written to the file on line 35.
The file is
closed on line 37 and reopened for reading in binary mode on line 39. A second
animal is created on line 46 whose weight is 1 and who is only one day old. The
data from the file is read into the new animal object on line 51, wiping out
the existing data and replacing it with the data from the file.
Many
operating systems, such as DOS and UNIX, enable the user to pass parameters to
your program when the program starts. These are called command-line options,
and are typically separated by spaces on the command line. For example:
SomeProgram
Param1 Param2 Param3
These
parameters are not passed to main() directly. Instead,
every program's main() function is passed two
parameters. The first is an integer count of the number of arguments on the
command line. The program name itself is counted, so every program has at least
one parameter. The example command line shown previously has four. (The name SomeProgram plus the three parameters make a total of four
command-line arguments.)
The second
parameter passed to main() is an array of pointers to
character strings. Because an array name is a constant pointer to the first
element of the array, you can declare this argument to be a pointer to a
pointer to char, a pointer to an array of char, or an array of arrays of char.
Typically,
the first argument is called argc (argument count),
but you may call it anything you like. The second argument is often called argv (argument vector), but again this is just a
convention.
It is
common to test argc to ensure you've received the
expected number of arguments, and to use argv to
access the strings themselves. Note that argv[0] is the name of the program, and argv[1]
is the first parameter to the program, represented as a string. If your program
takes two numbers as arguments, you will need to translate these numbers to
strings. On Day 21 you will see how to use the standard library conversions.
Listing 16.19 illustrates how to use the command-line arguments.
View Code
1: #include
<iostream.h>
2: int main(int
argc, char **argv)
3: {
4: cout << "Received "
<< argc << " arguments...\n";
5: for (int i=0; i<argc; i++)
6: cout <<
"argument " << i << ": " << argv[i] << endl;
7: return 0;
8: }
Output: TestProgram Teach Yourself C++
In 21 Days
Received 7 arguments...
argumnet 0: TestProgram.exe
argument 1: Teach
argument 2: Yourself
argument 3: C++
argument 4: In
argument 5: 21
argument 6: Days
Analysis:
The function main() declares two arguments: argc is an integer that contains the count of command-line
arguments, and argv is a pointer to the array of
strings. Each string in the array pointed to by argv
is a command-line argument. Note that argv could just
as easily have been declared as char *argv[] or char argv[][]. It is a
matter of programming style how you declare argv;
even though this program declared it as a pointer to a pointer, array offsets
were still used to access the individual strings.
On line 4, argc is used to print the number of command-line arguments:
seven in all, counting the program name itself.
On lines 5
and 6, each of the command-line arguments is printed, passing the
null-terminated strings to cout by indexing into the
array of strings.
A more
common use of command-line arguments is illustrated by modifying Listing 16.18
to take the filename as a command-line argument. This listing does not include
the class declaration, which is unchanged.
View Code
1: #include
<fstream.h>
2: int main(int
argc, char *argv[]) // returns 1 on error
3: {
4: if (argc !=
2)
5: {
6: cout << "Usage: " << argv[0]
<< " <filename>" << endl;
7: return(1);
8: }
9:
10: ofstream fout(argv[1],ios::binary);
11: if (!fout)
12: {
13: cout << "Unable to open "
<< argv[1] << " for
writing.\n";
14: return(1);
15: }
16:
17: Animal
Bear(50,100);
18: fout.write((char*)
&Bear,sizeof Bear);
19:
20: fout.close();
21:
22: ifstream fin(argv[1],ios::binary);
23: if (!fin)
24: {
25: cout << "Unable to open "
<< argv[1] << " for reading.\n";
26: return(1);
27: }
28:
29: Animal
BearTwo(1,1);
30:
31: cout << "BearTwo
weight: " << BearTwo.GetWeight() << endl;
32: cout << "BearTwo days:
" << BearTwo.GetDaysAlive() << endl;
33:
34: fin.read((char*)
&BearTwo, sizeof BearTwo);
35:
36: cout << "BearTwo
weight: " << BearTwo.GetWeight() << endl;
37: cout << "BearTwo days:
" << BearTwo.GetDaysAlive() << endl;
38: fin.close();
39: return
0;
40: }
Output: BearTwo weight: 1
BearTwo days: 1
BearTwo weight: 50
BearTwo days: 100
Analysis:
The declaration of the Animal class is the same as in Listing 16.18, and so is
left out of this example. This time, however, rather than prompting the user
for the filename, command-line arguments are used. On line 2, main() is declared to take two parameters: the count of the
command-line arguments and a pointer to the array of command-line argument
strings.
On lines
4-8, the program ensures that the expected number of arguments (exactly two) is
received. If the user fails to supply a single filename, an error message is
printed:
Usage TestProgram <filename>
Then the
program exits. Note that by using argv[0] rather than hard-coding a program name, you can compile
this program to have any name, and this usage statement will work
automatically.
On line 10,
the program attempts to open the supplied filename for binary output. There is
no reason to copy the filename into a local temporary buffer. It can be used
directly by accessing argv[1].
This
technique is repeated on line 22 when the same file is reopened for input, and
is used in the error condition statements when the files cannot be opened, on
lines 13 and 25.
Today
streams were introduced, and the global objects cout
and cin were described. The goal of the istream and ostream objects is to
encapsulate the work of writing to device drivers and buffering input and
output.
There are
four standard stream objects created in every program: cout,
cin, cerr, and clog. Each
of these can be "redirected" by many operating systems.
The istream object cin is used for
input, and its most common use is with the overloaded extraction operator
(>>). The ostream object cout
is used for output, and its most common use is with the overloaded insertion
operator (<<).
Each of
these objects has a number of other member functions, such as get() and put(). Because the common forms of each of these
methods returns a reference to a stream object, it is easy to concatenate each
of these operators and functions.
The state
of the stream objects can be changed by using manipulators. These can set the
formatting and display characteristics and various other attributes of the
stream objects.
File I/O
can be accomplished by using the fstream classes,
which derive from the stream classes. In addition to supporting the normal
insertion and extraction operators, these objects also support read() and write() for storing and retrieving large binary
objects.
Q. How do
you know when to use the insertion and extraction operators and when to use the
other member functions of the stream classes?
A. In
general, it is easier to use the insertion and extraction operators, and they
are preferred when their behavior is what is needed. In those unusual circumstances when these operators don't do the
job (such as reading in a string of words), the other functions can be used.
Q. What is
the difference between cerr and clog?
A. cerr is not buffered. Everything written to cerr is immediately written out. This is fine for errors to
be written to the screen, but may have too high a performance cost for writing
logs to disk. clog buffers its output, and thus can be
more efficient.
Q. Why were
streams created if printf() works well?
A. printf()
does not support the strong type system of C++, and it does not support
user-defined classes.
Q. When
would you ever use putback()?
A. When one
read operation is used to determine whether or not a character is valid, but a
different read operation (perhaps by a different object) needs the character to
be in the buffer. This is most often used when parsing a file; for example, the
C++ compiler might use putback().
Q. When
would you use ignore()?
A. A common
use of this is after using get(). Because get() leaves the terminating character in the buffer, it is
not uncommon to immediately follow a call to get() with a call to
ignore(1,'\n');. Once again, this is often used in parsing.
Q. My
friends use printf() in their C++ programs. Can I?
A. Sure.
You'll gain some convenience, but you'll pay by sacrificing type safety.
The
Workshop contains quiz questions to help solidify your understanding of the
material covered and exercises to provide you with experience in using what
you've learned. Try to answer the quiz and exercise questions before checking
the answers in Appendix D, and make sure you understand the answers before
going to the next chapter.
1. What is
the insertion operator, and what does it do?
2. What is
the extraction operator, and what does it do?
3. What are
the three forms of cin.get(), and what are their differences?
4. What is
the difference between cin.read() and cin.getline()?
5. What is
the default width for outputting a long integer using the insertion operator?
6. What is
the return value of the insertion operator?
7. What
parameter does the constructor to an ofstream object
take?
8. What
does the ios::ate argument do?
1. Write a
program that writes to the four standard iostream
objects: cin, cout, cerr, and clog.
2. Write a
program that prompts the user to enter her full name and then displays it on
the screen.
3. Rewrite
Listing 16.9 to do the same thing, but without using putback() or ignore().
4. Write a
program that takes a filename as a parameter and opens the file for reading.
Read every character of the file and display only the letters and punctuation
to the screen. (Ignore all nonprinting characters.) Then close the file and
exit.
5. Write a
program that displays its command-line arguments in reverse order and does not
display the program name.