cs1ch11sec5.htm
 CHAPTER 11

INPUT/OUTPUT
A MORE DETAILED LOOK
 


Section V : The Design and Analysis 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 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. Looking for New Classes or Modifications to the Existing Classes
Olympia has been complaining for some time now that she wants a program that allows permanent storage of her contract database. With computers, permanent storage implies file storage. Of course, adding this capability will require some modifications to the program. However, most of the program will remain untouched and, therefore, instead of rewriting the whole program narrative, we will simply 'amend' it to state:

    When the program begins, the user will enter a file name indicating the file from which the contract data should be read. This file will be read to create the database used by the program. When the user chooses to end the program, he or she will be asked for the name of the file in which to store the database.
There are a few new nouns and verbs here, specifically:
    -file
    -file name
    -database
    -store
    -read
    -create
However, they all refer to files or file manipulations. Since, as we have discovered, there are a built-in set of file handling classes, these words neither imply any new classes nor add attributes or behaviors to either of the present classes.

The conclusion, then, is that we can use the built-in stream classes to handle file manipulation since these classes have their own instantiation (create), read, and write (store) capabilities. The only word in the list that might not fit this analysis is 'database'. However, we have already learned how to link file names to files and database is strictly another name for our ListOfClasses. In other words, an instance of 'ListOfClasses' can be seen as a manifestation of a contract database in RAM while a file holding the same information represents the same database on disk.

B. Interface Analysis
The only required changes in the program relate to the interface and the functionality to implement that interface. Up to now, all our interface analyses have stated that all inputs came from the keyboard and all outputs go to the screen. This is not the case anymore and we need to be explicit about where each input comes from and each output goes. In the case of the contract program, the initial database data comes from a file named by the user, information for new contracts as well changes to existing contracts come from a keyboard. The full database can be written either to the screen or to a user-named file, while output reflecting modifications to contracts appears on the screen.

In earlier programs we spent some time designing the look and feel of the user interface. For example, we examined how input requests in the form of prompts or menus might look or how output might appear in columns on a screen. We must go through a similar process for file input and output. If the file output is ultimately to be printed for use by human readers, the design process is similar to that used for screen output. If the output is to later be read by a computer as file input, the process should focus on making the input as easy to handle as possible.

In our situation, the contract database will be read back by our program. Therefore, we need to consider such issues as, given that a person's name may have two, three or more parts, how should we indicate the end of a name. The same applies for street address, city, and state. (Consider Las Vegas and New Mexico vs Bismarck and Montana). Notice how grammar rules tell us to use a comma to separate the city from the state in writing out addresses. Without this, one could interpret "Las Vegas New Mexico" as referring, for example, to the city "Las Vegas New" in the state of "Mexico". This gives us a hint - why not use a delimiter, that is, a symbol of some kind, to separate the fields of each contract record.

We have been using the idea of delimiter all along without using the term. For example, the space or blank character is used in C++ to separate identifiers. That is why we must use the 'underbar, '_', to connect words if we want them to be part of one identifier name as in MAX_CONTRACTS. We can't use the space as the delimiter in our contract database because, as we just observed, names usually have spaces in them. Therefore, as a somewhat arbitrary but useful choice, let's follow the idea of using commas. Done! A comma will separate each field of the output.

We also need to determine the order in which the fields of each contract record will be written. Without this information we cannot correctly write the code to read the data back in. The easiest thing to do is to follow the same order used in outputting contract data to the screen. (This will allow us to partially copy the code from 'DisplayInfo' when we write the function to output contract data to a file.) Thus, the contract ID will come first, followed by the name of the contractee and the street address, city, state, and zip code of the office. After these will come the square footage of the office, the number of desks in the office and number of days per week the office is to be cleaned - with all these fields separated by commas. The end of the information concerning each contract will be indicated by a newline character.

The only other addition to the interface analysis involves a brief description of the prompts and inputs to allow the user to state the input and output file names. on this.

C. Implications for the Program Design
Since our analysis indicates that there are no changes to either of the classes used in this program, all the changes must take place in the main program. Previously, we had a function called 'InitializeInstances', whose function was to instantiate a small set of contracts and place them in the list as the initial database. Since we are now going to read the contracts in from a file, we do not need this function. Instead we need a function to read in the contracts and place them in the list. We could call it 'ReadInstances'. It will have the same single parameter as 'InitializeInstances', a list of contracts, which it will return once all the contracts in the file have been placed in it. Notice that we will be able to take advantage of some of the code for 'InitializeInstances' so we should not simply erase it.

Many times in these chapters we have observed that there is more than one way to develop a program. Some software engineers promote an approach based more on prototyping than on careful, systematic pre-analysis and design as has been described in this text. The prototyping approach suggests that one should start with some analysis and design but be willing to move sooner into the coding stage to try out one or more designs. The idea is to experiment with specific designs, perform a careful analysis of the results of the experimentation, and be willing to throw out what does not work. Key to this approach is:

  • the development of well modularized code so that one can test individual parts and determine what worked and didn't work, and
  • a well thought out set of test cases.

Some students, when they hear this, claim they want to try this approach. However, all they often mean is that they want to get down to coding without ANY analysis and design. And, they often ignore the other side of the prototyping approach, the need to:

  • design well modularized code;
  • design test data and procedures that will carefully exercise all parts of the code;
  • carefully analyze the results of first drafts to determine what did and did not work
  • be willing to throw out what does not work, while keeping what did work.

Although we have not really been acting as prototypers here, some of the results are the same. At this point we have code that works in some ways but still needs to be improved. We could see our results so far as indicating that most of the code works but that we need to change that aspect dealing with input and output of the database itself. The point being made here is that we, therefore, know that we can keep most of the code and even that the part that needs to be changed may have elements worth salvaging.

Having said all that, let's review what needs to be modified to allow the program to output the database to a file when the user indicates that he or she wants to quit. Previously, when the user chose to end the program, nothing happened except for the displaying of a closing message. That indicates that, unlike when reading in the database - where we could modify an existing function, some new functionality is needed here. On the other hand, this functionality is somewhat similar to that used to display the database on the screen, so maybe we can borrow some of the code used there.

Where can we cleanly fit in this new functionality? Well, every other user option handled in the function 'ProcessUserChoices' except for the 'quit' option calls a function. Why not have a call to the function that will handle the output placed in the code for the quit option! The other possible choice would be to include the code inside 'DisplayClosingMessage', but the very name of that function discourages its use in this way.

We will call this new function 'OutputData'. Does it receive or return anything? Remember the design pattern for output functions we developed long ago ? We determined there that output functions receive one or more items - the data to be output - and return nothing. Since it is the list of contracts that is to be output, this analysis implies that it is the list that should be received.

Since we have not written a function quite like this before, it would be wise to work a bit on an algorithm before proceeding to code. As you probably have already observed, however, new functions don't usually develop in a vacuum. They can often be built by borrowing ideas from elsewhere. We have already noticed that we might want to borrow from the code for 'DisplayAllContracts' since it matches some of the functionality of this new function - it outputs all of the contracts, even if the output goes to the screen and not to a file. Also, in the previous section we studied file output. So, let's see if we can't put together an algorthm that borrows from both these sets of code.

Reviewing the code in the file 'fileio.cpp, we see that we need get the file name from the user, instantiate a stream connected to that file, and check the results of the instantiation process. Examining 'DisplayAllContracts', we see that one way to accomplish the rest of our goal is to iterate through the list of contracts and call a function to output each individual contract. Our algorithm might then look as follows:

 
	Get file name from user
	Attempt to instantiate output stream using this file
	If the instantiation fails
		display error message
		exit
	Else
		Reset the List to begin iterating from the beginning
		While there are more contracts in the list
			Get the next contract from the list
			Output it to the output stream for the file
		End while
	End if
That's it! Note that this algorithm uses the world 'while' to indicate a loop. That does not mean that we will use a 'while' instead of a 'for loop in the actual code. It is just that in English one does not commonly use the word 'for' to indicate repetition. What looping statement we use in the code depends on what works best and on personal preferences. We have seen that in most cases the 'for' and 'while' are semantically interchangeable.

Writing that algorithm, proved relatively simple but useful. It would be wise if we also wrote an algorithm for 'ReadInstances' since this too is a type of code we have never written before. We know we need to ask the user for the file name where the database was stored and open a stream attached to that file. Thus, this algorithm will begin much the same as the previous one.

Back in chapter three you learned the algorithm for reading data from a keyboard. The algorithm for reading from a file is much the same except that now the stopping condition is the end of the file. Here then is the algorithm:

 
	Get file name from user
	Attempt to instantiate input stream using this file
	If the instantiation fails
		display error message
		exit
	Else
		While there are more contracts in the file
			Get the next contract from the file
			Add it to the list
		End while
	End if
We will discover, when it comes time to implement this algorithm in code, that there are a number of little details to be handled. One, of course, is reading in all the individual fields of each person's record. While such details are important, they are not part of the algorithm, since they are related to the idiosyncrasies of C++ input. This algorithm does, however, represent another general pattern to be used whenever the goal is to read from a file. Be sure you understand it.

Top of Section Main Menu Next Section