CHAPTER 11
|
Table of Contents
Learning C++:
An Index of Entry Points
2. The A reference document on the basic elements of C++.
3. The Patterns
|
A. Output The code to instantiate the stream and connect it to the file is as
follows
and a picture of the results might look as follows: DRAW If you review the input-output inheritance structure found in Figure 11.2 above, you will see
that 'ofstream' is a class name. Therefore, what we are doing here is
calling the constructor for this class to instantiate an instance called
'outputData'. We can use any name we want for this instance - including
'cout', but that would not be adviseable since you would then lose access
to the 'built-in' cout. The file name inside the parameters,that is "person.dat", is passed to
the constructor which uses it to make the connection to a file with the
same name. If no such file exists, by default one will be created. Exactly
where in the directory structure that file is created depends on the
compiler you are using and the options you have set. Also, there are
additional, optional parameters one can use in the constructor to control
if and how the file is created, but that is beyond the scope of this text.
Check the "Essentials of C++" for more details. Now that we have our new stream, we need to use it. In other words, we
need to send output to it. In the above code, we sent the output to 'cout'
so all we need to do is change all the "cout's" to 'outputData'. Here is
the code put in the form of a file complete with main and the necessary
'include' statements. Note that we include the header file 'fstream.h'. It is
here that the class 'ofstream' is declared. Technically, the header file
'iostream.h' is not needed here because we do not use cin, and we have
changed all the "cout's" to 'outputData'. However, since we will be using
cin and cout below in a modified version of this file, we will keep the
include statement here. If you run this program, and you are encouraged to
so, be sure to view the output file 'person.dat' to assure yourself that
the output actually went to the file. (You can use the compiler editor or
any text viewer such as NotePad for this purpose.) B. File Input Reading information from a file is much harder than writing to a file.
The reason is that you must write the code to precisely handle the spacing
of the file as well as the order and type of the each data item. In our
case, there are two strings, separated by a variable number of blanks
followed by an integer and a float. Frankly, it often takes a bit of
experimenting to get it right, and often the data in the input file is
carefully arranged to make the process easier. In this example, the data
was arranged more for human processing than computer processing but we
will deal with it. Just as with the output file, the first step is to connect the file to
an input stream. The code looks very similar:
This time we work with the class 'ifstream', also declared in the
header file 'fstream.h'. The variable identifier 'inputData' is the name
for the instance of the stream we are creating, and 'person.dat' is again
the file name passed as a parameter to the class constructor. With the
output file it made little difference what file name we provide because
the system would create a new file if one did not already exist. Indeed,
the only concern we might have had was to make sure we did not overwrite a
file we wanted to keep. The input file is just the opposite. Now we must
make sure that the name we provide matches exactly the name of the file
that holds the data we want to read in. Since we wrote the data to
'person.dat', that is the file we read from. Clearly, it is possible to make a mistake and request a file that does
not exist. The file may never have been created; it may be in a different
directory; or it may have been accidentally deleted. If the file does not
exist and we do not tell the system what else to do in this case, the
program will still continue - with the input requests being ignored
because the file has a 'fail' status. Thus, it is always wise to code in a
check to see if the input file was successfully opened. Here is the code
for this:
This means:
To avoid this somewhat ugly syntax and simply make coding easier, C++
allows one to simply use the stream name. When used where a Boolean value
is expected, a stream name is treated as a request for the status of the
stream. In this case, however, it is equivalent to a call to
inputData.good(). Therefore, true is returned if everything is OK and
false is returned if a failure has occurred. That is the reason for the
'!' symbol. The code inside the 'if' statement, first, causes an error message to
be output. Then, the call to the function 'exit' causes the program to
terminate. The program simply stops and the rest of the code is not
executed. The parameter value of '1' inside the function call causes the
program to return the value 1, indicating an error. By convention programs
return a 0 if there is a problem and some value other than 0 if there is
an error. Up to now we have declared 'main' as a function that does not return
anything. Since some operating systems can take advantage of the value
returned by a program, it is really better to have programs return a
value. Thus, in the code below we will see that 'main' has been
re-declared as:
Before proceeding any further, let's view a whole program based upon
these new ideas. The program below simply reads in the two records output
above, skipping the column headings, and outputs the results to another
file. The first thing to observe is that there is yet another header
file included. The file <stdlib.h> contains the declaration for the
function 'exit'. Note also that we now have a use for the file
'iostream.h' because we use 'cout' to output the error message. Next,
observe that the function 'main' now returns an 'int'. Because of this, we
also have added a new line at the end of the program to force the program
to return a '0' when it has completed. Back to the top of the code: After declaring the input stream, the
program declares the variables needed to hold the data to be read in and
also declares the output stream. This time the output file name is
changed, not because it has to be changed but because it is simply safer.
We do not want to ruin our input file if something happens in the course
of running this modified program. We also add the stream error checking
code for the output file just in case something were to happen - for
example, if there was no room on the disk for a new file. After the declarations, the program outputs the column headings and
begins to read in the first line of data. Before reading the data,
however, the system must skip past the column headings that are out on
disk. That is the purpose of:
This instruction tells the system to skip up to and including the first
newline character it encounters or to skip 133 characters - whichever
comes first. Now, where does '133' come from? We could have counted
exactly how many characters there are in the heading but that is a hassle
and besides what if we change the heading. In one sense the number '133'
is arbitrary - big enough to make sure we skip enough characters - but it
also reveals that the coder has been around for awhile - to say the least.
Since printers tend to print either 80 characters plus 1 control character
or 132 characers plus one control character, back in the days when almost
all output was sent to a printer, the values 81 and 133 were
important. Anyway, with this line executed, the file pointer is
pointing to the first character on the first line of actual data (the
second line of the file), and it is time to read in the name of the first
person in the file. Think of the 'file pointer' as an indicator of where
in the file the next input is to come from. There isn't really a pointer
in the sense that we used pointers earlier in this text. It is more of an
image. One can also think of it as the first unread data in the input
stream. Notice that we use the member function 'get' instead of writing
"inputData >> name;". Names, of course, are usually in multiple
parts separated by blanks, and, as we know, '>>' stops at the first
blank. If we had used the '>>' operator, we would have only read in
the first name. Not only would the variable name have had an incomplete
value, but, when we went to read in the telephone number, we would have,
instead, read in the rest of the name because that is where the file
pointer would have been pointing. Because we know that the output produced
a block of 30 characters via the 'setw(30)' manipulator, we use the same
value of 30 in the 'get' function. Just to show that the '>>' operator can be used (where
appropriate), we use it to read in the phone number - which does not
contain any blanks. Of course, we had better, then, make sure that those
who input telephone numbers do so correctly. The whole issue of data
validation is complex and somewhat tedious, not a good topic for
discussion here where the goal is to simply give you an overview of file
processing. In this case, it might have been better to stick with the
'get' function to avoid this potential problem. Next, we use the '>>' operator to read in the age and height. The
'get' function would not have worked here because it expects to read in
and process characters but we want to process an integer and a double.
Finally, we need to skip past the newline character at the end of the
line. We, therefore, declare a string variable 'dummy' to hold this
character when it is read in and use 'getline' to read it and skip to the
next data line. (The function 'getline' expects a string as its first
parameter so we declared 'dummy' to be a string instead of a char even
though the newline character ('\0') is really only one character.) All
this provides a hint at the need to adapt ones input code to exactly match
the structure of the file holding the data. The rest of the code simply outputs the information, using variables
instead of constants, and then repeats the same process for the second
line. Had this been a real program, where we did not know how many data
lines there were in the file, we would have coded this as a while loop
with a stopping condition that tested for the end of the file. We will
explore that in the next section when we work with the Contract program.
C. Entering File Names One way to accomplish this is to declare a function to ask the user for
the name of a file. This function will receive a prompt string (so that we
can use it both to request the input and output files) and return the file
name. Here is the definition for this file: This function is then called in 'main' and the
value it returns is used in the instance declarations. Below is the code
to request the input file name. The same procedure is used with the output
file. In the file, 'fileio3.cpp', you will find the code for a
complete program involving what we have just discussed. If you look at
that file, you will also observe that an extra line has been added at the
bottom of the file announcing that the program has terminated. This is
just a "user-friendly" feature.
Topics Covered in the "Essentials of C++" |