cs1ch11sec4.htm
 CHAPTER 11

INPUT/OUTPUT
A MORE DETAILED LOOK
 


Section IV: File Handling Section I: The C++ Perspective on Input and Output Section II: Basic Input Using Instances of the Class istream Section III: Basic Output Using Instances of the Class ostream
Section V : The Design and Analysis for a Contract Program Using Files Section VI: Modifying the Code for a Contract Program Using Files Section VII: Run Time Parameters - Passing Values to 'main'

  


Table of Contents

Learning C++:
An Index of Entry Points


2. The

of C++

A reference document on the basic elements of C++.



3. The Patterns



Index!



A. Output
We finally get to that part of the chapter that is of most interest to you, as a programmer on the 'Olympia' project, since it describes how to finally give our contract program the ability to handle files. As usual, before we get to the specifics of the Contract program, let's learn how to work with files in general. Suppose you wanted to output the simple set of data used in the previous section to a file called 'person.dat'. Remember, all output is the in the form of streams. To output the data to the file 'person.dat', you need to instantiate a stream and connect it to the file 'person.dat'. (Once a stream is instantiated, it is already connected to the program by the nature of a C++ stream.)

The code to instantiate the stream and connect it to the file is as follows

ofstream outputData ("person.dat");

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.

    // Basic File Handling
    // File: fileio1.cpp
    
    #include <iostream.h>
    #include <iomanip.h>
    
    #include <fstream.h>
    
    void main()
    {	ofstream outputData ("person.dat");
    
    outputData << setiosflags(ios::fixed);
    outputData << setprecision(2);
    
    	outputData << setw(30) << setiosflags(ios::left) << "Name"
    		   << setiosflags(ios::right)
    		   << setw(16) << "Phone Number"
    		   << setw(6)  << "Age" << setw(10) << "Height" << endl;
    
    	outputData << setw(30) <<  setiosflags(ios::left) << "Curtis"
    		   << setiosflags(ios::right)
    		   << setw(16) << "505-454-3302"
    		   << setw(6)  << 6 << setw(10) << 5.75 << endl;
    
    	outputData << setw(30) << setiosflags(ios::left) << "Fred Smith"
    		   << setiosflags(ios::right)
    		   << setw(16) << "505-454-3302"
    		   << setw(6)  << 102 << setw(10) << 5.128<< endl;
    }
    

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
Now that we have a file with data in it, we can work on reading the data back into a program. Actually, one can make a data file in many ways, for example by entering the data by hand using the editor that came with your compiler or through Notepad. However, since we already have a data file, why make a new one.

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:

ifstream inputData ("person.dat");

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:

    ifstream inputData ("person.dat");
    if (!inputData)
    {
      cout << "Cannot open input file\n";
      exit(1);
    }
The test in the if statement here needs to be explained. Technically, the test should be:

if (inputData.fail())

This means:

  1. call the member function 'fail' associated with the instance 'inputData' of the class 'ifstream';

  2. Since 'fail' returns TRUE if a failure has occurred, the code will execute the code inside the 'if' statement if a failure has occurred.

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:

int main()

************************

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.


// Basic File Handling
// File: fileio2.cpp

#include <iostream.h>
#include <iomanip.h>
#include <stdlib.h>
#include <fstream.h>


typedef char String[40];

int main()
{
	ifstream inputData ("personabc.dat");
	if (!inputData)
	{	cout << "Cannot open input file\n";
		exit(1);
	}


	String name;
	String phoneNo;
	String dummy;
	int age;
	double height;


	ofstream outputData ("personout.dat");
	if (!outputData)
	{	cout << "Cannot open output file\n";
		exit(1);
	}


	outputData << setiosflags(ios::fixed);
	outputData << setprecision(2);

	outputData <<  setiosflags(ios::left) << setw(30) << "Name"
		  << setiosflags(ios::right)
		  << setw(16) << "Phone Number"
		  << setw(6) << "Age" << setw(10) << "Height" << endl;

	inputData.ignore(133, '\n');

	inputData.get(name, 30);
	inputData >> phoneNo;
	inputData >> age;
	inputData >> height;
	inputData.getline(dummy, 80);


	outputData << setw(30) <<  setiosflags(ios::left) << name
		  << setiosflags(ios::right)
		  << setw(16) << phoneNo
		  << setw(6) << age << setw(10) << height << endl;

	inputData.get(name, 30);
	inputData >> phoneNo;
	inputData >> age;
	inputData >> height;
	inputData.getline(dummy, 80);


	outputData << setw(30) << setiosflags(ios::left) << name
		  << setiosflags(ios::right)
		  << setw(16) << phoneNo
		  << setw(6) << age << setw(10) << height << endl;
	return 0;
}

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:

inputData.ignore(133, '\n');

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
Before we get to that, however, there is one other issue. As coded, if we want to use different files for input or output on different occasions, we need to modify the actual code for each new file name and recompile the program. It would be much better if the program asked the user for the names of the input and output files. And, now that we know how to read in strings, that is easy to do.

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:

    void GetFileName(String prompt, String fileName)
    {
      cout << endl; // just to make space
      cout << prompt << endl;
      cin.getline(fileName, 40);
    }

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.

    String fileName;
    GetFileName("Please enter the file containing the person database.", fileName);
    ifstream inputData (fileName);

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++"

File Processing

Top of Section Main Menu Next Section