


Chapter 16
Streams
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. ToChapter,
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.
Overview of 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.
Encapsulation
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.
Figure
16.1. Encapsulation through streams.
Buffering
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.
Figure
16.2. Filling the buffer.
Figure
16.3. Emptying the buffer.
Figure
16.4. Refilling the buffer.
Figure
16.5. Flushing the buffer.
Streams and Buffers
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.
Redirection
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.
Input Using cin
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 toChapter; 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.
Listing 16.1. cin handles
different data types.
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 Chapter 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.
Listing 16.2. Trying
to write more than one word to cin.
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.
Listing 16.3. Multiple
input.
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?"
operator>>
Returns a Reference to an istream Object
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.
Other Member Functions
of cin
In addition to overloading operator>>, cin has a number
of other member functions. These are used when finer control over the input is required.
Single Character
Input
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.
Listing 16.4. Using
get() with no parameters.
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.
Listing 16.5 Using get()
with parameters.
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.
Getting Strings
from Standard Input
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.
Listing 16.6 illustrates the use of this form of get().
Listing 16.6. Using
get() with a character array.
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.
Listing 16.7. Using
getline().
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.
Using cin.ignore()
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().
Listing 16.8. Using
ignore().
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.
peek() and putback()
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.
Listing 16.9. Using
peek() and putback().
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.
Output with cout
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.
Flushing the Output
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.
Related Functions
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.
Listing 16.10. Using
put().
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.
Listing 16.11. Using
write().
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.
Manipulators, Flags,
and Formatting Instructions
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 Chapter 21. Each of
ostream's flags can be set using member functions and manipulators.
Using cout.width()
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.
Listing 16.12. Adjusting
the width of output.
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.
Setting the Fill
Characters
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.
Listing 16.13. Using
fill().
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.
Set Flags
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.
Listing 16.14. Using
setf.
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. 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 12, the flag showbase is set. This causes the prefix 0x
to be added to all hexadecimal numbers, as reflected in the output.
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().
Listing 16.15. Printing
with printf().
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.
Once the stream objects are associated with files, they can be used like any other
stream objects. Listing 16.16 illustrates this.
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.
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.
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.
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.
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 Chapter
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:
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.
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:
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.
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.
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.