cs1ch11sec6.htm
 CHAPTER 11

INPUT/OUTPUT
A MORE DETAILED LOOK
 


Section VI: Modifying the Code for a Contract Program Using Files 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 IV: File Handling Section V : The Design and Analysis 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. ProcessUserChoices
Having completed our analysis and design, it's time we proceeded to the code. Since most of the program remains the same, we will only show here the new elements. Check out the file 'ch11tst1.cpp' for the complete, modified code.

Using the top down approach, we will look at the highest level function that needs to be changed. That would be 'ProcessUserChoices.


void ProcessUserChoices()
{
// Code to create the list of contracts
ListOfContracts contracts;
ReadInstances(contracts);	// Load the database

bool done = false;   // Is the user finished processing contracts?
char choice;
choice = GetOperatorChoice();

while (!done)
	{	switch (choice)
		{	case 'D':
			case 'd':
				DisplayContract(contracts);
				break; // case 'd'
			case 'C':
			case 'c':
				ChangeContract(contracts);
				break; // case 'c'

			case 'L':
			case 'l':
				DisplayAllContracts(contracts);
				break;

			case 'A':
			case 'a':
				AddContract(contracts);
				break;

			case 'E':
			case 'e':
				DeleteContract(contracts);
				break;

			case 'Q':
			case 'q':
				cout << "Do you really want to want to quit (Y) or (N)\n";
				char answ;
				cin >> answ;
				if ((answ =='Y') || (answ == 'y'))
				{
					done = true;
					OutputData(contracts);
				}

				break;

			default:
				cout << "Invalid choice. Please try again";
				choice = GetOperatorChoice();
		}  // end switch
		if (!done)
		{
			choice = GetOperatorChoice();
		}

	}
}
   
As you would expect, most of the code stays the same. The only changes are:
  1. The call to 'InitializeInstances' at the top of the function is changed to a call to 'ReadInstances'.
  2. Inside "case 'q'" we add a call to 'OutputData', passing the list of contracts.


B. ReadInstances
That was easy enough! Let's now look at the function 'ReadInstances'.


void ReadInstances(ListOfContracts& contracts)
{
	// Get from user the file holding the contract database
	String80 fileName;
	GetFileName("Please enter the file containing the contract database.",
					fileName);

	ifstream inputData (fileName);
	if (!inputData)
	{	cout << "Cannot open input file\n";
		exit(1);
	}
	String80 name;
	String80 streetAddress;
	String80 city;
	String80 state;
	String80 zip;
	int ID;
	int sqFootage;
	int desks;
	int days;

	String80 dummy;
	char chDummy;

	Contract contract;


	while (!inputData.eof())
	{  if (inputData.fail())
	   {	inputData.clear();
		inputData >> chDummy;
		cout << "Invalid value entered\n";

	   }
 	   inputData >> ID;
	   inputData >> chDummy; // to skip comma
	   inputData.get(name, 81,',');
	   inputData >> chDummy; // to skip comma
	   inputData.get(streetAddress, 81,',');
	   inputData >> chDummy; // to skip comma
	   inputData.get(city, 81,',');
	   inputData >> chDummy; // to skip comma
	   inputData.get(state, 81,',');
	   inputData >> chDummy; // to skip comma
	   inputData.get(zip, 81,',');
	   inputData >> chDummy; // to skip comma
	   inputData >> sqFootage;
	   inputData >> chDummy; // to skip comma
	   inputData >> desks;
	   inputData >> chDummy; // to skip comma
	   inputData >> days;
	   inputData.getline(dummy, 80);

	// Create a contract, assign its properties values and insert it into the list.
   	   contract.ChangeContracteeName(name);
	   contract.ChangeOfficeStreetAddress(streetAddress);
	   contract.ChangeOfficeCity(city);
	   contract.ChangeOfficeState(state); 
	   contract.ChangeOfficeZip(zip);
 	   contract.ChangeID(ID);
	   contract.ChangeSquareFootage(sqFootage);
	   contract.ChangeNumDesks(desks);
	   contract.ChangeNumDays(days);

	   contracts.Add(contract);
	}
}

The function starts by getting the file name from the user and attempting to instantiate a stream attached to that file. Note that the algorithm uses an 'if-else' statement while the code only has an 'if'. That is because the 'exit' statement stops the program. In other words, if the branch containing the 'exit' instruction is followed, no instructions after the 'exit' instruction will be executed. The 'else' is provided in C++ to allow one to have instructions that can be skipped if the code follows one branch and executed if the code follows another branch. In our case, if there is a problem connecting the stream to the file, no other code should be executed but, if the attachment is successful, the code below the 'if' should always be executed. This is the same idea we sometimes took advantage of when using the 'return' statement.

Once the stream is successfully attached to the input file, the variables to hold the input values are declared, as is a contract in which we will place those values before adding the contract to the list of contracts. We are now ready to execute the loop that will read in the data. Notice the 'while' loop test condition:

while (!inputData.eof())

The purpose of this is to use the member function 'eof' (declared in the root class 'ios' and inherited by 'ifstream') to find out if the "end of file" symbol has been encountered in the 'inputData' stream. When this symbol is finally encountered, the system will stop reading data.

As a safety precaution, the code to handle invalid data is included next. One might argue that the user should be responsible for making sure that the input file is valid, but that is probably asking too much of users who may not be familiar with the complexities of file management. In addition, this code is useful during program development, when either the input code could be wrong, or the code that produces the output may be writing the file incorrectly.

Now we move to the actual input statements. We observed above that input processing can be very complex. One must pay close attention to how the data is organized. In the case of any contract database file, the contract ID field comes first and all fields are separated by commas. Thus, the code reads in the ID first and then skips past a comma.

    inputData >> ID;
    inputData >> chDummy; // to skip comma
If all the data were numeric, we could use only the '>>' operator. Instead the data includes strings and we must use code such as:

inputData.get(name, 81,',');

As you can see, this instruction says to read up to 81 characters (the size of our strings) but stop when a comma is read. Thus, a full name is read in - unless a name includes a comma or is greater than 80 characters! Since the 'get' function does not read the comma itself, the " inputData >> chDummy;" line must again be inserted.

What follows is a series of lines to read in the each field and by-pass the commas. After the number of days has been read in, the line:

inputData.getline(dummy, 80);

makes sure the file pointer skips past the end of line marker and is 'pointing' to the beginning of the next contract.

Once all the information for a specific contract has been read, the appropriate member functions are called to put that information into a contract and, then, that contract is copied to the list of contracts. Note that this procedure is very similar to the one used in 'InitializeContracts' in the earlier versions of this program. The only difference is that variable names are used in the member function calls instead of string and numeric constants.

That finishes the while loop and this function, except for one final point. Notice that the code assumes that the input file does not end in the middle of a contract record. In other words, there is no check for end-of-file after every request for input. It does make the program susceptible to failure if the input file is corrupt, but, hopefully, any problem will be gracefully handled by the error checking done at the top of the while loop.

C. OutputData
We now move to the file output functions and start with 'OutputData'


void OutputData(ListOfContracts contracts)
{  String80 fileName;
	GetFileName("Please enter the file in which to save the contract database.",
			fileName);
	cin.ignore();   // To clear out \n from rest of manipulations
                                     //  By default 'ignore' extracts 1 character.

	ofstream outFile (fileName);
	if (!outFile)
	{	cout << "Cannot open output file\n";
		exit(1);
	}

	Contract contract;
	contracts.Reset();
	for (; contracts.AreThereMore(); )
	{  contract.CopyContract(contracts.GetNext());
		WriteInfo(outFile, contract);
	}
}

As we observed when designing the file output algorithm, this code borrows extensively from the code we saw earlier in this chapter to output data and from the function 'DisplayAllContracts'. Once a stream has been successfully connected to the output file, the code resets the contract iterator to 'point to' the first contract in the list and loops through all the contracts, calling the function 'WriteInfo' on each one.

D. WriteInfo

The function 'WriteInfo', in turn, borrows extensively from 'DisplayInfo'.


void WriteInfo(ofstream& outFile, Contract contract)
{	outFile << contract.ProvideID();
	outFile << "," << contract.ProvideContracteeName();

	outFile << ",";
	outFile << contract.ProvideOfficeStreetAddress() << ","
		  << contract.ProvideOfficeCity() << ","
		  << contract.ProvideOfficeState() << ","
		  << contract.ProvideOfficeZip();

	int sqFootage;
	sqFootage = contract.ProvideSquareFootage();
	outFile << "," << sqFootage;

	int numDesks;
	numDesks = contract.ProvideNumberOfDesks();
	outFile << "," << numDesks;

	int numDays;
	numDays = contract.ProvideNumberOfDays();
	outFile << "," << numDays;

	double charge;
	charge = contract.ProvidePerWeekCharge();
	outFile << "," << charge << endl;
}
If you compare this code with that of 'DisplayInfo', you will find that the only real differences are that:
  • 'cout' is now 'outFile';
  • the strings describing the output to users are gone
  • commas are now output between each field.

You can be sure that this code was copied and modified. Why type more than you have to!?!

The complete code for this program is found in the files:

Top of Section Main Menu Next Section