cs1ch8p2.htm
 

CHAPTER 8

AN EVEN BETTER CONTRACT PROGRAM

Part 2


  


Table of Contents

1. The Story
of C++!

(The basics for any beginner!)


2. The

of C++

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



3. The Patterns





VI. Analyzing How to Fix the Problems
The previous section described three problems with our design of 'ListOfContracts'. First, if we are to display a contract, we need to have access to that contract; second, if we are to modify a contract, we need to again have access to that contract; and third, if we are to display all the contracts, we need to have access to all the contracts without knowing the contract ID's for individual contracts.

Two general approaches seem to exist for solving these problems. Note that the key issue is access to the contracts in the list. One answer would be to have the class 'ListOfContracts' do all the work. That is, we could declare the functions 'DisplayInfo', 'ChangeInfo' and 'DisplayAll' as member functions of 'ListOfContracts'. This would work because any member function of 'ListOfContracts' has access to the array. However, many software engineers would be uncomfortable forcing a class whose purpose is to handle list processing to handle changes to individual elements of the list.

A second solution would be to provide a member function of 'ListOfContracts' that when given a contract ID, returns a contract if one exists with the desired ID or returns some failure indicator. If we do this, we will also need a member function to replace a contract after it has been changed. This solution handles the first two problems because it provides access to individual contracts but does nothing to fix 'DisplayAll'. We could provide a member function that returns the contracts one at a time when some function in the main program says 'next'. This would work but is getting quite complex.

How about a compromise! The 'DisplayAll' function does seem to be appropriate as a member function of 'ListOfContracts' since it involves the whole list. Therefore, the second solution could be used for the 'DisplayInfo' and ChangeInfo' functions while 'DisplayAll' becomes a member function of 'ListOfContracts'. In other words, 'DisplayInfo' and ChangeInfo' would remain part of the main program (with 'ListOfContracts' providing functions to return and replace contracts) while 'ListOfContracts' would take on the responsibility of displaying all the contracts. Some software engineers might argue that we have arbitrarily split the functionality here but that is the approach we shall use.

It seems then that we have discovered three new responsibilities for the 'ListOfContracts' class:

  • FindContract (which returns a contract when given a valid contract ID)

  • ReplaceContract (which replaces on the list a modified contract).

  • DisplayAll

Here is a modified CRC card:


Figure 8.3


 

That finishes the design stage. Hopefully, you are becoming aware of the fact that this process is not cut and dried and that there are many ways to accomplish the same goals. As you work on more complex problems you will find that members of the design team do not always agree about what kinds of classes are needed, their relationships to each other as well as their properties and responsibilities. The real world is not so clean cut. One author of this text remembers spending a whole week-end negotiating with two associates about the design for a game they were going to develop. And, even then, the design they came up with was changed in a year!

VII. Back to the Beginning
The good news is that we are finally ready to code. The bad news is that the program will have to be extensively modified to deal with the problems we just discussed. The bad news, however, is not as bad as it seems. Usually, when a program needs to be rewritten, it turns out that much of the code can be used - just in a modified form. The difficulty in the rewriting process comes more from being careful to write code that works than in the actual typing.

A. Rewriting "common.h"
To make sure that this program works we should start from the VERY beginning. That means starting with the file 'common.h' which we last worked with in chapter six. It includes the declarations necessary for the user defined type, Boolean. A potential problem occurs in the fact the Boolean type is used in both classes and in the main program.

Suppose we include the line:

#include "common.h"

in both class declaration files. Remember that the #include preprocessor directive (see the C++ Essentials text for more information on such directives) instructs the system to copy the contents of a file into the file containing the directive. In other words, both class declaration files will have the code found in 'common.h'. There is no problem here until we write the main program with '#include' commands to copy in both class declaration files. Now, all the code found in both files is copied into the main program which means that the code from 'common.h is included twice! That file has the lines:

const Boolean TRUE = 1;
const Boolean FALSE = 0;

which will result in an error because the constants TRUE and FALSE would be defined twice!

This problem occurs all the time in C++ so there is a simple solution. You should place another set of preprocessor directives:

#ifndef some_name
#define some_name

at the top of all '.h' files ( 'dot h' files - meaning files that end in '.h' and are used as 'include' files).

And, at the bottom of all '.h' files include the line:

#endif

Here is 'common.h' with the added code:

// A set of basic type and function declarations common to many programs

    // file: common.h

    #ifndef COMMON_H
    #define COMMON_H
    typedef int Boolean;
    const Boolean TRUE = 1;
    const Boolean FALSE = 0;
    #endif

When the system sees the new lines it acts as follows:

The line

#ifndef COMMON_H

asks the question, "Has the term 'COMMON_H' been defined yet?" The first time the file 'common.h' is read into a piece of code, the answer is no and the next line is executed. This line,

'#define COMMON_H'

defines the term 'COMMON_H'. Afterwards, the rest of the lines are executed ending with the line

'#endif'

and the system goes to the rest of the code in the file that has directly or indirectly included 'common.h'.

If these lines are encountered again in the file, as they would be in the case of our main program, the line,

#ifndef COMMON_H

is again processed but this time 'COMMON_H' has already been defined so the lines from here to the line,

'#endif'

are skipped! Thus, the constants TRUE and FALSE are not declared and defined again and there is no error. Note the purpose of the '#endif'. Without it the system would not know how many lines to skip.

That is the only change in the file 'common.h'. Again, it is strongly recommended that you get in the habit of always including the three lines:

    #ifndef some_name #define some_name
    .
    .
    #endif

in your '.h' files. Any term can be substituted for 'some_name' but it is common to use a set of letters made up of the file name followed by _H. That is where 'COMMON_H' came from in our example. Using such a convention makes it clear what you are doing and helps you avoid using a similar name elsewhere in your code.

B. Redeclaring the Contract Class
We now move onto the declarations for the class 'Contract'. The need for an ID property in the this class causes a few additions to the class declaration:

  • a new data member (instance variable) called 'contractID' of type 'int';
  • a new function member, 'ChangeID';
  • a new function member, 'ProvideID';
  • a new parameter, representing the contract ID, for the first constructor

This new code can be viewed under the file name contrct2.h.

The file containing the definitions for this class, 'contrct2.cpp' also has some changes:

  • code for the new functions, 'ChangeID' and 'ProvideID,' must be coded. These take exactly the same form as their sister 'Change...' and 'Provide...' functions.
  • assignment statements to the two constructors to initialize the value of 'contractID'.

VIII. On to the 'ListOfContracts' Class

A. The Class Declaration
Now it is time to declare the class 'ListfOfContracts'. There is no new C++ here but the way C++ features are used is different and should be carefully studied. This is very common in programming languages as it is in any language - the same basic building blocks can be put together in many novel ways.

// Class Declarations for ListOfContracts
// File: ch8list1.h

#include "common.h"
#include "contrct2.h"  // Uses the modified version of the contract class.

const
	MAX_CONTRACTS = 5;// arbitrary number chosen for test purposes

typedef
	Contract ContractArray[MAX_CONTRACTS];


class ListOfContracts
{
public:
	ListOfContracts();
   	/*
		Purpose: To instantiate (create) an instance of the class 'ListOfContracts'.
		Goal:	A new instance of class 'ListOfContracts' now exists

		Inputs: 	NONE
		Outputs:	NONE
		Receives:	NONE
		RETURNS:	NONE
	*/

	void Add(Contract contract);
	/*
		Purpose: To add an instance of the class 'Contract' to the list.
		Goal:	A new instance of class 'contract' is on the list

		Inputs: 	NONE
		Outputs:	Error message if list is full
		Receives:	a contract
		Returns: 	NONE:
	*/


	void Delete(int contractNum);
	/*
		Purpose:	To delete an instance of the class 'Contract' from the list.
		Goal:		The instance of class 'contract' is deleted

		Inputs: 	NONE
		Outputs:	Error message if contractNum is not a valid contract number
		Receives:	 A contract number
		Returns: 	NONE:
	*/

	Boolean FindContract(int ID, Contract& contract);
	/*
		Purpose: To find out if a contract is in the list and, if so, return it
		Goal:	 If the list holds a contract with id 'ID', the function 
			 returns that contract in the variable 'contract' and 
			 the function returns true
			 If not, the function returns false and 'contract' holds garbage.'

		Inputs:	NONE
		Outputs:  NONE
		Receives: A contract ID
		Returns:  Whether or not the contract was found AND, if found, the 
			  contract itself.
	*/

	void ReplaceContract (Contract contract);
	/*
		Purpose: To replace the properties of one contract in the list
		Goal:    The properties of the contract in the list with the ID of
			 'contract' have been replaced

		Inputs:	NONE
		Outputs:	NONE
		Receives:	A contract
		Returns:	NONE
	*/

	void DisplayAll();
	/*
		Purpose:	To display all the contracts in the list
		Goal:		All the contracts are displayed on the screen

		Inputs:	NONE
		Outputs:	All the contracts in the list
		Receives:	NONE
		Returns:	NONE
	*/


private:
	int numContracts;  	// indicates next available slot in list
	int limit;         	// The maximum number of contracts
	ContractArray listOfContracts;

};

The first thing to observe is that the constant and type definitions have been moved from the file containing 'main' to here. The main program will no longer have any use for the array itself. It will only concern itself with the user defined types 'Contract' and 'ListOfContracts'. Being more precise, the programmer for the main program will have no use for arrays. Remember the idea behind the object oriented paradigm: Once a class, along with its properties and behaviors, are defined by some programmer, other programmers can use that class without caring at all how the internal structure or behavior of that class is implemented.   

Students have a tendency to fight this idea but it really is key to the way we function as humans. How many people know the principles of aerodynamics that allow a plane to fly? Does that stop people from flying? How many of us can explain how a microwave works? Yet most of us use one on a regular basis. How is that different from one programmer designing a class and others using it as if it were a 'black box' - which is what the idea of encapsulation is all about?

What confuses people sometimes is that it is possible for one programmer to act as both the designer and user of a class. However, even this is typical human behavior. The person who designs an airplane probably does not think about it as he or she is flying it to visit grand-ma. As a matter of fact, it is likely that he or she does everything possible to keep from thinking about the design so as to accomplish other things while flying. Maintaining awareness of the internal workings of the airplane - or worse, listening for 'noises' - only makes it difficult for him or her to focus on other tasks. Similarly, once you as a programmer are finished with work on a class or a function, it is best to forget about the inner workings of your work or you will not be able to put your full attention on the next task.

C. Viewing Data Members as a Structure
Before we look at the member functions for 'ListOfContracts, we need to understand the data structure employed here. By this is meant how the data members of the class are related to and support each other. Up to now, the data members for our classes have been pretty much independent from each other. They were simply unrelated properties. With 'ListOfContracts' this is no longer true.

We have already talked in general about the three data members for this class. The last one, 'listOfContracts' is an array. We know this from the code because it is declared to be of type 'ContractArray' - the type we declared at the top of the file. By the way, note that the class is named 'ListOfContracts' with an upper case 'L' while our array has the same name except that we use a lower case 'l'. Some people think this is neat, others feel it just adds to the confusion. You decide for yourself in your own work.

The first data member, 'numContracts' is the counter we talked about above. When the list is empty, it has the value 0, when one element is in the list, it has the value 1, etc. (If you would like to see some pictures demonstrating this, click here.) Note the relationship between 'numContracts' and 'listOfContracts'. The data member 'numContracts' tells the system which elements of 'listOfContracts' hold actual contracts and which ones hold garbage.

The one other data member 'limit' is not really useful but we included it because we said we would in earlier discussion. Remember, it will have the same value as the constant, MAX_CONTRACTS. Later, in an expanded version of this program, we will see a real value for this data member and it's relationship to both other data members.

These three data members make up a structure that we can use to represent a list. Such a structure is only useful, however, if there are also functions to manipulate it. Developing these is our next step.

D. The Member Functions - Declarations and Definitions
The declaration along with the design of the six member functions is based on the following:

  • The constructor will set the data member 'numContracts' to 0, indicating an empty list, and will set 'limit' to MAX_CONTRACTS. Nothing needs be done to the array of contracts. As long as 'numContracts' is zero, the program 'knows' (if we write our code correctly) that there can not be any contracts in the list.

  • 'Add' works by receiving a contract and adding it to the list.

  • 'Delete' works by receiving the ID of the contract to be deleted and searching the list of contracts for a match. If a match is found, the deletion takes place.
     

  • 'FindContract' also searches through the list looking for a match with the ID it has been given. The code is very similar to the search in 'Delete' and 'Add' but is just different enough that it is probably easier to develop its code separately.

  • 'ReplaceContract' does just what it says as does 'DisplayAll'.

We are now ready for the function definitions so let's move onto 'ch8list1.cpp'.

// Class Definitions for ListOfContracts
// File: ch8list1.cpp

#include 
#include "common.h"
#include "ch8list1.h"

ListOfContracts :: ListOfContracts()
{
	numContracts = 0;
	limit = MAX_CONTRACTS;
}


void ListOfContracts :: Add(Contract contract)
{
	if (numContracts < MAX_CONTRACTS)
	{	int index = 0;
		Boolean found;
		found = FALSE;
		int contractNum = contract.ProvideID();
		while ((!found) && (index < numContracts)) // keep searching until ID found	
// or no more contracts in list { if (contractNum == listOfContracts[index].ProvideID()) { found = TRUE; } else { index++; } } if (!found) // contract with this ID does not exist { listOfContracts[numContracts] = contract; // copy contract into list numContracts++; } else { cout<< "\nA contract with this ID already exists.\n"; } } else { cout << "There was no room to add the contract\n\n"; } } void ListOfContracts :: Delete(int contractNum) { int index = 0; Boolean found; found = FALSE; while ((!found) && (index < numContracts)) // keep searching until ID found
// or no more contracts in list. { if (contractNum == listOfContracts[index].ProvideID()) { found = TRUE; } else { index++; } } if (found) // the contract to be deleted exists { for (int i = index; i < numContracts - 1; i++) { listOfContracts[i] = listOfContracts[i+1]; } numContracts--; } else { cout << "Invalid Contract ID\n\n"; } } Boolean ListOfContracts :: FindContract(int ID, Contract& contract) { int index = 0; while (index < numContracts) // keep searching until ID found // or no more contracts in list. { if (ID == listOfContracts[index].ProvideID()) { contract = listOfContracts[index]; return TRUE; } else { index++; } } return FALSE; // contract ID not found in list } void ListOfContracts :: ReplaceContract (Contract contract) { int index = 0; Boolean found; found = FALSE; int ID = contract.ProvideID();
// Get ID of the contract so we can search for it in // in the list. while ((! found) && (index < numContracts))
// keep searching until ID found
// or no more contracts in list. { if (ID == listOfContracts[index].ProvideID()) { found = TRUE; } else { index++; } } if (found) // the contract to be replaced exists { listOfContracts[index] = contract; } else { cout << "\nContract to be replaced does not exist in the list.\n"; } } void ListOfContracts :: DisplayAll() { int contractID; int sqFootage; int numDesks; int numDays; double charge; cout << endl << endl; // To set off the output for (int contract = 0; contract < numContracts; contract++) { contractID = listOfContracts[contract].ProvideID(); sqFootage = listOfContracts[contract].ProvideSquareFootage(); cout << "The square footage for contract " << contractID << " is: " << sqFootage << endl; numDesks = listOfContracts[contract].ProvideNumberOfDesks(); cout << "The number of desks for contract " << contractID << " is: " << numDesks << endl; numDays = listOfContracts[contract].ProvideNumberOfDays(); cout << "The number of days for contract " << contractID << " is: " << numDays << endl; charge = listOfContracts[contract].ProvidePerWeekCharge(); cout << "The per week charge for contract " << contractID <<" is: " << charge << endl; cout << endl << endl; } }
First, take a look at the constructor. All that visibly happens when the constructor is called is that the count for the number of contracts to is set to 0 and the limit is set to MAX_CONTRACTS. However, because each instance of 'ListOfContracts', by declaration, contains an array of contracts, calling the constructor for 'ListOfContracts' implies the creation of a number of individual contract instances. This further implies that a constructor for 'Contract' must be called as many times as their are elements in the array. Since no property values are being passed for each contract, the constructor with no parameters (see section II.B above) is called and every instance will have zero for all their property values. In other words, declaring an instance of class 'Contract' causes the system to set aside enough memory for two integers (numContracts and limit) and an array of Contracts. Then, the act of declaring an array of Contracts calls a Contract constructor. Since each instance in the array is instantiated without any property values, the version of the constructor with no parameters is called. This version sets all property values to 0.

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

We now move to the Add member function and explore the role of the data member, 'numContracts'. This variable has been designed to act both as a count of the number of elements in the array and as an index into the array. When the first contract is added, it should be placed into the zero-th element of the array and note that this is the value of 'numContracts'. Thus the line of code:

listOfContracts[numContracts] = contract;

This causes the contract in the variable 'contract' to be copied to the memory location 'listOfCOntracts[0]'. After this, 'numContracts' is incremented to 1 and the next time an add takes place, the new contract will be placed in the memory location symbolized by ' 'listOfContracts[1]'.

(Be sure that you understand that the values 0 and 1 used here refer to the values held by 'numContracts' at different times.)

Let's step back and go over this function from the top - The first step is to make sure that there is not already a contract in the list with the ID of the new contract. (Duplicates can't be allowed if we want to be able to delete a contract based on it's ID.) The line

int contractNum = contract.ProvideID();

gets the ID of the contract to be added and the while loop examines all the contracts to check for a duplicate. Here we see another use of a boolean variable. A search can end for two reasons:

  1. The item searched for has been found;
  2. There are no more items to search

Thus there are two test conditions in the while loop. Note that 'found' is set to FALSE to indicate that a match has not been found when the loop starts. One by one the loop compares the contracts in the list (using 'index' as the subscript into the array of contracts) with the ID of the new contract (as stored in 'contractNum'). If a match occurs, found is set to TRUE and this is used to stop the search loop. If there is no match, 'index' is incremented and the next element in the array is used in the comparison. Eventually, if some contract in the list has an ID equal to the ID in 'contractNum', the loop will stop with the 'index' acting as an indicator of which element in the array had the match. If there is no match in the list, the loop will eventually stop when 'index' reaches 'numContracts'. Note that in this case, 'found' is never set to TRUE.

The second step is to insert the contract if no match was found. The line:

if (!found)

performs the check to make sure there was no match. If so, the contract is added to the list and the number of contracts is incremented. The rest of the code simply adds appropriate messages.

Delete is somewhat the same as 'Add' but with a few twists. The code again does a search followed by a test. This time, however, the test is to see if a match was found and is written as:

if (found)

The test here is the opposite of the test in 'Add' because the action takes place only if a match exists - one cannot delete something if it is not there to be deleted.

The other main difference in the code is in the lines:

    for (int i = index; i < numContracts - 1; i++)
    {
      listOfContracts[i] = listOfContracts[i+1];
    }
    numContracts--;

The way the delete takes place is by shifting down all contracts starting at the point of the deleted contract.. For this reason the variable 'i' is initialized to 'index' and not to zero as is usually the case. For example, if the contract in the fifth array location is to be deleted, 'i' will be set to the corresponding value in 'index', and the code will copy the 6th contract (i + 1) into the fifth contract's place; the seventh contract into the sixth contract's place, etc. In this way the contract that was in the fifth array element is replaced by the contract in the sixth array location and all subsequent contracts are also shifted down one. (Remember, contract ID's no longer have any relationship with a contract's position in the array.)

If you work this out by hand (and you are encouraged to do that), you may notice that the last contract seems to be in the list twice! Suppose there are originally 10 contracts in array locations 0..9. The contract in the ninth place is copied to the eighth spot but there is no contract copied into the ninth spot. Is this an OOPS? The answer is 'No' and the reason is the value found in 'numContracts'. It did have the value 10 but after being decremented it has the value 9. Since this '9' refers to the next available location in the array, the code, if written correctly, will see the contract in array location 9 as left over garbage and not part of the list. This is a subtle point so consider it carefully and review your own walk throughs and the demo.

The member function 'FindContract' uses essentially the same search code. (Mmmm, do we have a pattern here?) It does, however, take advantage of the way the 'return' statement works to simplify the while loop test condition. In the 'Add' and 'Delete modules the variable 'found' was used as a signal to leave the loop. These functions needed to do something after the loop - depending on whether a contract was found or not found. 'FindContract' has no further responsibility and thus can stop as soon as a contract match is found.

When a system encounters a 'return' statement it immediately leaves whatever function it is in. Thus, when the line:

return TRUE;

is encountered in 'FindContract', the code leaves the loop in the process of leaving the whole function. So, if a match is found, the function stops executing and returns TRUE. (Notice that if a match is found, this function also places the matching contract into the parameter 'contract' and this contract is then also returned.) As long as a match is not found, the index variable is incremented and the loop continues. If the loop terminates, it can only be because all contracts in the list have been examined. This means that no matching ID was found in the list and the function returns FALSE.

'ReplaceContract' is yet another example of the same search/action pattern. This time the goal is to find the location of the contract to be replaced and then copy into that location the contract passed as a parameter. The 'else' part of the 'if' statement in the code for this function is mostly for debugging. Used properly, there should always be a match. However, one should always code defensively and consider the possibility of the impossible!

The last function 'DisplayAll' is different but very simply. The code shows us why we decided to make this a member function. It is so easy to simply walk through the list using a 'for' loop to output the property values of all the contracts. Note that 'contract' is used here in the way 'index' was used above and that the loop tests for:

contract < numContracts;

instead of comparing ' contract' with MAX_CONTRACTS or 'limit'. The goal is to display all the contracts that actually exist not the values in all possible contract locations.

E. Variations on a Theme
That is all there is to the function definitions for 'ListOfContracts'. As is so often the case, however, such code can be written in more than one way. One possible change is in the search code and involves the 'for' statement which is actually much more powerful than we have let on so far. Using this statement, the search routines we saw above could have been written as follows:

    for (index = 0; index < numContracts, !found; index++)
    {
      if (contractNum == listOfContracts[index].ProvideID())
      {
        found = TRUE;
      }
    }

When we discussed the 'for' statement in chapter 3, section 5, we saw that it had three distinct parts within the parentheses with the first part used to initialize any variables, the second part used to test the stopping conditions, and the third part used to increment variables if necessary. In the code above, there are two possible stopping conditions so each is tested in the second part. Note how the parts are separated by semicolons while separate elements within a part are separated by commas.

Note also that in this example, the variable index is used in the loop but not declared in the loop. Up to now we have usually written code such as:

for (int index = 0; index < numContracts, !found; index++)

with the word 'int' included in the parentheses. This is simply a matter of convenience - if one needs a variable for a 'for' loop and the variable has not been declared, do so when you initialize the variable. In the example here, however, we are making a quick change to existing code. In that code 'index' is already declared so let's take advantage of that fact and not declare it again.

It turns out that the 'for' statement is one of the most versatile statements in C++. We shall see more of this versatility later.

A second change we might consider making involves the repeated use of the same search code in 'Add', 'Delete' and 'ReplaceContract'. You will see that each of these member functions has the same lines of code:

while ((!found) && (index < numContracts))
	   // keep searching until ID found
					// or no more contracts
	{	if (contractNum == listOfContracts[index].ProvideID())
		{	found = TRUE;
		}
		else
		{	index++;
		}
	}
Why not make this a separate function called by all three functions using this code. This new function would only be used by member functions so it could be declared as private to the class. (Code using instances of this class would still have access to 'FindContract' for doing searches.) Note that this idea of private functions is not something foreign to the way the 'real world' operates. Organizations often have procedures known only within the organization. That is part of what makes organizations different from each other.

The first step in this change is to declare our new function, call it 'Search', as private to the class. Here is a fragment of the modified .h file for 'ListOfContracts' showing this declaration:

   private:
      int numContracts;  // indicates next available slot in list
      int limit;                  // The maximum number of contracts
      ContractArray listOfContracts;

      int Search(int contractID);
      /*   Purpose:   To search through the list of contracts for a contract with an ID
                      that matches 'contractID'.
           Goal:      The list had been searched and the contract found if it exists
	
           Inputs:    NONE
           Outputs:   NONE

           Receives:  a contract's ID
           Returned:   the index of the contract with a matching ID or the value of
                       'numContracts' to indicate that there was no match
      */
  
Note that 'Search' is the only function declared in the private part of the class. Because all the other functions must be available to programs using this class, they are declared as public functions. 'Search' should be unavailable outside the class just as the data members are unavailable so it is declared as private. In other words, 'Search' and the data members are all encapsulated.

Regarding the declaration itself: all the searches use the contractID so it must be received by the function. A careful look at the old code might lead you to expect that this function should return a Boolean since all the searches set a variable 'found' to TRUE or FALSE. Unfortunately, there is a small complication here that won't let us do that. Note that 'Delete' and 'ReplaceContract' both need to know the location of the contract to be deleted or replaced. If our 'Search' function just returns TRUE or FALSE, these functions will not know where the contract was found. Therefore, we will return the location where the contract was found or return the value in 'numContracts' as an indicator that the contract ID was NOT found. (Remember, 'numContracts' holds the location of the next free element of the array of contracts. Thus, if 'Search' returns 'numContracts' it is an indication that all contracts were examined and none matched.)

A look at the definition for 'Search' might help. We will use a 'for' loop version just to show some variation.

	int ListOfContracts ::  Search(int contractID)
	{
		int index;
		for (index = 0; index < numContracts; index++)
		{	if (contractID == listOfContracts[index].ProvideID())
				{	return index;
				}
		}
		return index;
	}

Since 'Search' is a member function of 'ListOfContracts' (even if it is a private member function) we must include ' ListOfContracts ::' in the first line of the definition. Inside, the 'for' statement only has one test condition because we again take advantage of the effect of the 'return' statement to end processing. As you can see, the value of 'index' is returned. If the sought after contract ID is found, the index of the matching element is found. If the loop completes, it is because index has reached the value of 'numContracts' and this is returned to indicate the failure to find a match.

Now that we have this function we can simplify each of the functions that used to do their own searching. Consider 'ReplaceContract':

	void ListOfContracts :: ReplaceContract (Contract contract)
	{
		int index;;
		int ID = contract.ProvideID();  
// Get ID of the contract so we can find it in // in the list. index = Search(ID); if (index < numContracts) // the contract to be replaced exists { listOfContracts[index] = contract; } else { cout << "\nContract to be replaced does not exist in the list.\n"; } }
The ID of the contract to be inserted is retrieved; the search is made; and, if the value returned by "Search (index)" is less than 'numContracts', the replacement takes place. Note that it is much easier to understand this function with the details of the search moved out. As noted earlier, this is an important advantage in using functional decomposition.

The other two functions, as rewritten, are included below.

	void ListOfContracts :: Add(Contract contract)
	{
		if (numContracts < MAX_CONTRACTS)	   // is there room in the list?
		{

			int contractNum = contract.ProvideID();
			int index = Search(contractNum);

			
			if (index == numContracts) //contract with this ID does not exist
			{
				listOfContracts[numContracts] = contract; 
					// copy contract into list
				numContracts++;
			}
			else
			{	cout << "\nA contract with this ID already exists.\n";
			}
		}
		else
		{	cout << "There was no room to add the contract\n\n";
		}
	}

	void ListOfContracts :: Delete(int contractNum)
	{
		int index;
		index = Search(contractNum);
		if (index <  numContracts) // the contract to be deleted exists
		{	for (int i = index; i < numContracts - 1; i++)
			{	listOfContracts[i] = listOfContracts[i+1];
			}
			numContracts--;
		}
		else
		{	cout << "Invalid Contract ID\n\n";
		}
	}

Now the code is cleaner. Of course, we need to declare and define the new functions such as 'DisplayContract' and 'AddContract'. Since all of these work somehow with the list of contracts, they are all passed the list. In each case, the list is passed by reference for two reasons. One reason we will not discuss until the next chapter. The second reason involves writing efficient code. As we have noted, when copying something by value, one is making a copy of the data being passed. A list of contracts represents a potentially huge amount of data that would all have to be copied if the list were passed by value. Passing the list by reference avoids this copy process and speeds up the code. (By the way, this is one reason why arrays themselves are always automatically passed by reference.)

  The one thing to point out about this code is the way the 'Add' function checks to see if a contract already exists with the ID of the proposed new contract. This is accomplished in the line:

if (index == numContracts)

In other words, if 'index' equals 'numContracts' we know the search routine went through the whole list of contracts without finding a match.

IX. Rewriting the Main Program to Use This List Class

A. The Overall Design
We have new user options to add and a whole new class to work with. It's time to integrate all this with the file containing 'main'. In chapter six we developed a structure chart for this program. The first part of this chart remains the same but 'ProcessUserChoices' has gotten much more complex. Here and there in this chapter we have added functionality to this module but we have never considered all the pieces together. In Section III we discussed the need for an 'InitializeInstances' function; in section IV.B we added 'DisplayAll; in section V we discussed the 'Add' and 'Delete functionality; and finally, in section VI we determined that we also needed to use two other functions, 'FindContract' and ReplaceContract'.

This might be a good time to review the structure chart for this problem. There are enough functions calling each other here that a pictorial representation of their organization might be useful. We start at the top with the function 'main'. Nothing has changed here from the version we wrote in chapter 6 and it is quite simple:


Figure 8.4

'DisplayIntro' and 'DisplayClosingMessage' do not have any function calls in them so they will not have any boxes hanging off them. 'ProcessUserChoices' is at the heart of the program and it does have a number of boxes hanging off it, representing the functions it calls:

If the complexity of the function itself has not made you wonder yet if this function is too big, perhaps this structure chart has. One way to simplify this would be to create a separate function for each of the user choices and have those functions deal with the loops and if statements we saw in earlier versions of this function as well as with calling the functions specific to that choice.

Here is the simplified version of 'ProcessUserChoices':

void ProcessUserChoices()
{

// Code to create the list of contracts
ListOfContracts contracts;
InitializeInstances(contracts);
Boolean done = FALSE;   // Is the user finished processing contracts?
char choice;
while (!done)
	{	choice = GetOperatorChoice();
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 want to really want top quit (Y) or (N)\n";
				char answ;
				cin >> answ;
				if ((answ =='Y') || (answ == 'y'))
				{
					done = TRUE;
				}

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

The code for each of the new functions for handling the options is simply what used to be under each case. That code can simply be copied to the appropriate function definition. In addition each function has to have its own set of local variables to replace the ones such 'contractID' that used to be declared in ProcessUserChoices'.

For example, here is the definition for 'DisplayContract':

void DisplayContract(ListOfContracts& contracts)
{ int contractID;
  Boolean found;
  Contract contract;
  contractID = GetContractID();
  while (contractID != 0)
  {  found = contracts.FindContract(contractID, contract);
	  if (found)
	  {  clrscr();
		  DisplayInfo(contract);
	  }
	  else
	  {	cout << "\nContract " << contractID << " is not in list.\n";
	  }
	  contractID = GetContractID();
	}
}


Inside, the code is exactly the same as it used to be inside the 'D' case statement except that local variables declarations had to be added. Again, note that the one parameter, 'contracts' is passed by reference to avoid copying. The list of contracts could be quite large and it simply saves CPU time to pass it by reference.

Not only is the code cleaner, but the structure chart is simpler. Here is the partial structure chart for this modified version:



Figure 8.6

This is much easier to follow. One gets a sense of a cleaner design. Let's proceed to the next level. A number of the functions call other functions and our chart could get quite cluttered very quickly. To avoid clutter we will use a separate chart for each function which we will further decompose. Remember that each of these charts is really a part of the larger chart.

We start with 'InitializeInstances'. It is a simple function but it does call a number of functions over and over:
DRAW


Figure 8.7

Note that although these functions are called many times, they are only listed once in the structure chart. Remember that structure charts do not indicate the order of function calls or number of times a function is called. We could rearrange the order of the functions above and the structure chart would still be correct.

Should this part of the structure chart go any deeper? We know, for example, that the member function 'Add' calls a number of other functions. However, this is a structure chart for the main program and the inner workings of 'Add', a member function of the class 'ListOfContracts', is technically off limits to the code in this file. In other words, at this level, we are not interested in the 'how' of 'Add'. Therefore, we do not extend the structure chart to show what 'Add' calls in the process of completing its function.

That finishes the sub-chart for 'InitializeInstances'. We proceed then to the function next to it under 'ProcessUserChoices'. This is 'GetOperatorChoice' and it has no function calls so we do nothing. Next is 'DisplayContract'. It does have function calls so we could proceed to work with it. We shall not continue here but leave this as an exercise. The same for 'DisplayAllContracts' and 'DeleteContract'.

X. Summary
We have now designed and implemented a program with real potential. It allows users to view, modify, add, and delete contracts. We also have a general pattern - the list pattern - to handle a whole range of problems. Whether one wants to keep track of houses, swimming pools, people or whatever, the general framework for lists described here could be used. In fact, if the class types were changed to represent 'houses' or swimming pools or whatever, the main program would require little change. Try and remember that much of programming involves reusing what you or someone else has already designed, implemented, and tested. Saving work and avoiding bugs is part of what it is all about.

Of course, the final step is to compile, link, run, and test this code. Follow the procedure you used in chapters 5 and 6 to get your program running. The only trick is that now there are three .cpp files to link. (Borland C++ users can click here to get further instructions.

Next page