| |
Main Menu | Next Section |
A. A Second Look at the 'for' Statement
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:
When we discussed the 'for' statement in chapter 3, Section VIII. B,
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. One can read this as "Keep looping as long as 'index' is less than 'numContracts' AND 'found' is FALSE". Note how the parts of a 'for' statement 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:
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 can 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 code 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
ContractArray listOfContracts;
int Search(int contractID);
/* Purpose: To search through the list of contracts for a contract with an ID
that matches 'contractID'.
Receives: a contract's ID
Returns: 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. The member functions, '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, that is, the value of 'index'. Note that if 'index' has been incremented so much that it has the value found in 'numContracts', we know that the list has been fully searched and the ID was not found. (Remember, 'numContracts' holds the location of the next free element of the array of contracts. Thus, if 'Search' returns the value in 'numContracts' (through the value of 'index'), 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 returned.
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";
}
}
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:
In other words, if 'index' equals 'numContracts', we know the search
routine went through the whole list of contracts without finding
a match.
You might have noticed that we did not modify 'FindContract' to use 'Search'. You are encouraged to try this as an exercise.
Topics Covered in the "Essentials of C++"
| |
Main Menu | Next Section |