Part 1
|
Table of Contents
1. The Story
of C++! (The basics for any beginner!)
2. The A reference document on the basic elements of C++.
3. The Patterns
|
I. Analyzing the Code Implications of an Improved
Program The solution requires that we make an array of contracts. In the last
chapter we created an array of 10,000 inventory items where each element
of the array represented an existing product. We will start the same way
here by creating an array of contracts where each element of the array
represents an existing contract. Later we will deal with the issue of
adding and deleting contracts. In the inventory program we had a code for each item in the inventory.
Our original analysis of Olympia's contract program did not worry about
such a code or contract ID since we only had five contracts. However, if
we are going to have many contracts and later be adding and deleting them
we need to have some way of uniquely identifying each contract. We can use contract ID's as we used the ID numbers 1 through 5 in the
code we completed in chapter 6 in the file ch6cntr2.cpp. There, we used switch statements
to determine which contract to pass as a parameter to the 'DisplayInfo' or
'ChangeInfo' functions. A glance at that code, however, will indicate the
problems that would arise if we decided that Olympia was to have 20 or a
100 contracts. Our switch statements would get much bigger. The problem is
that we have to pass a different variable depending on which contract we
wish to handle. Examine the code carefully to see the truth of this. In the last chapter we saw that it is possible to have access to the
individual elements of an array simply by using the appropriate value for
the subscript. For simplicity's sake, we used the item code as the
'appropriate value' for the subscript. Note that this was somewhat
artificial because in any real environment, item codes are not sequential.
The same will also be true for contract ID's, especially when we add the
ability to delete contracts. However, for now, we will use contract ID's
as subscripts. This will make our code simpler while we focus on other
issues. II. An Array of Contracts and Constructor
Issues
typedef Contract ContractArray[MAX_CONTRACTS]; Good assumption! However, things are a bit more complex when we proceed
to declare a variable of this type. Consider the line:
As an array declaration it is perfectly legal. However, consider what
it does. It creates an array of 100 instances of the class
'Contract'. But, remember, as we have written the constructor for the class 'Contract'
, every time an instance is created, the information on square
footage, number of desks, and number of days must be provided. That is not
being done in this declaration! A. Default Values For example, if we are dealing with instances of bank accounts where
each instance has one property representing the amount of money in the
account and a second property representing the interest rate, we might say
that the default values are 0 for the amount and 4% for the interest rate
- assuming:
In the case of the Contract class it makes most sense to set the values
for the three instance variables to 0 if not told otherwise. We could accomplish this by simply changing the constructor for the
'Contract' to class so that it receives no parameters and sets the values
of all properties to 0. This would mean, however, that we could not
instantiate an instance of the class with specific values as we did
before. When creating an individual instance using this new constructor,
one would have to write code that first instantiated the instance with
default values and then went back in and changed the values one by one
using the 'change... functions. B. Function Overloading and These constructor functions must have the same name because they are
both to act as constructors for the "Contract' class and by definition a
constructor for a class has the same name as the class. In general the
compiler knows which function to use (among a set of functions all with
the same name) by looking at the number and type of actual parameters used
when the constructor is called. In our case, since one constructor expects
three integer parameters and the other none, if there are three actual
parameters, all of which are integers, the first constructor is called;
but, if there are no parameters, the second constructor is called. And, of
course, the compiler will generate an error if we try to call the
constructor any other way. Be aware that constructor calls are somewhat implicit. The call takes
place when the instance is declared. In the case of the array declaration
above, 100 instances are declared and therefore 100 calls are made to a
constructor for the class 'Contract'. Since each call has no parameters,
the constructor with no formal parameters is used. The constructor
definition for this second version would look as follows:
{
numberOfDesks = 0; numberOfDaysPerWeek = 0; Compare this with the first constructor's
definition. The code, of course is slightly different to reflect the
different values being assigned the three data members. You are allowed to overload functions in C++ as long as the there is
something different about the parameter lists. The difference can be in
terms of the number of parameters in the two functions, in terms of
different types of parameters, or in terms of both. Note that one can have
different types returned by the two functions but the compiler does not
look at this in deciding which function to use. In other words, if the
number and type of parameters are the same but the return types are
different, the compiler will have no way to determine which one to call at
run time. For example, the declarations:
are valid since the first parameter in one declaration is of type 'int'
while in the second declaration it is of type 'double'. On the other hand,
the declarations: are not valid because the only difference between them is in the return
type. Constructor overloading is the most common form of function
overloading. Later we will look at a second form of overloading involving
operators. This is a very powerful addition to C++. The new 'Contract' class declaration is shown below.
// Declaration of the class Contract
// File: contract.h
class Contract
{public:
Contract(int sqFootage, int numDesks, int numDays);
/*
Purpose: to instantiate (create) an instance of the class 'contract'.
Goal: a new instance of class 'contract' now exists
Inputs: NONE
Outputs: NONE
Receives: Square Footage, number of Desks, number of Days (all
Integers)
Returns: NONE
*/
Contract();
/*
Purpose: to instantiate (create) an instance of the class 'contract'
with default values.
Goal: a new instance of class 'contract' now exists
Inputs: NONE
Outputs: NONE
Receives: NONE
Returns: NONE
*/
void ChangeSquareFootage(int sqFootage);
/*
Purpose: To change the value of the square footage data member
Goal: The square footage data member has a new value
Inputs: NONE
Outputs: NONE
Receives The New Square Footage (an integer)
Returns: NONE
*/
void ChangeNumberOfDesks( int numDesks);
// Design comments go here.
void ChangeNumberOfDays( int numDays);
// Design comments go here.
int ProvideSquareFootage();
/*
Purpose: To Return the Square Footage of an Office
Goal: The Square Footage is returned
Inputs: NONE
Outputs: NONE
Receives: NONE
Returns the Square Footage (an integer)
*/
int ProvideNumberOfDesks();
// Design comments go here.
int ProvideNumberOfDays();
// Design comments go here.
double ProvidePerWeekCharge ();
/*
Purpose: To calculate and return the per week charge for an office
Goal: The per week charge is returned
Inputs: NONE
Outputs: NONE
Receives: Square Footage, number of Days Per Week, number of Desks
(all integers)
Returns: The Per Week Charge (a double)
*/
private:
int squareFootage;
int numberOfDaysPerWeek;
int numberOfDesks;
};
With this second constructor the original array declaration is now valid. Each of the 100 instances held in the array, has the same
value of 0 for all three properties. Of course, the program should
immediately fill in the correct property values for all 100 instances. As
we have already said, ordinarily this would be done by reading the
information about old contracts from a file. Since we do not yet know how
to do this and since any other approach involves a lot of extra typing,
let's change the program to involve only 5 instances. Remember this can be
easily done by simply changing the value of the constant,
'MAX_CONTRACTS'. Now we only need input 3 values for each of five
instances for a total of 15 values. To save typing as we test our program,
we will hard code in the same values we used in the chapter 6 version of
this program and not ask the user for the values. A quick review of the final version of the Contract program in chapter
6 reveals that 'main' was very simple and the real work began in the
function ' ProcessUserChoices'. Here the five instances of Contract were
instantiated so here is where we should declare the array of
instances: void ProcessUserChoices()
{
// Code to create the five instances
ContractArray contracts;
InitializeInstances(contracts);
// Rest of the code goes here
}
The function needs one parameter, the array of contracts. It will
return this array but as we have already learned, a function cannot return
an array using the standard return mechanism. We also learned that in C++
array parameters are automatically set up to allow the parameter to be the
return vehicle. In other words, we don't need to do anything to have this
function return whatever values are placed into the array. We will look at the simple code for this function's definition later. Assuming that it works when we get arouind to coding it(- as is standard practice in modularized coding) we can proceed with 'ProcessUserChoices'. After some variable declarations, the older version of the code used a switch statement to handle the user's choices. Note the use of the imbedded switch statement to allow us to call the same function but with the different contracts as actual parameters. Using our array variable we can drastically simplify this code. Here is the code fragment dealing with the 'D' and 'C' choices: choice = GetOperatorChoice(); or Remember that the array elements are all instances of the class
'Contract'. Therefore, if we call 'DisplayInfo' or 'ChangeInfo' passing an
element of the 'contracts' array, we are passing an instance of
'Contract'. But, why use 'contractID - 1' as the index value? This is
because we are prompting the user to enter contract ID's between 1 and 5
(MAX_CONTRACTS) but arrays number their elements from 0 to one less than
the max. Thus, if the user enters the value 1 - meaning the first contract
- the code should look for that contract in the zero-th spot in the array.
Fun, isn't it! Another example: if the user enters a 3 to display the information on
the third contract, the third instance in the array of contracts (element
2 of the array) is passed to the function 'DisplayInfo'. Because
'DisplayInfo' was written to expect to receive an instance of 'Contract'
in the original design of this program, it does not need to change at all.
In fact, very little of this program changes with the introduction of
arrays. This is again an example of the power of both modularized code and
the object oriented paradigm.
IV. Calling Function Members With Array Elements A. Defining 'InitializeInstances' In the one yet undefined function 'InitializeInstances' for our new version, we need to call these same three member functions to change the values of the newly instantiated instances. Since the instances all exist as array elements, we need to use the names for the array elements in place of 'contract' at the beginning of each call. The first contract has the array element index of 0 so we write: to set its values. Be sure to compare this code with the code just above it. The
only difference is that 'contracts[0]' has been substituted for
'contract'. In other words, we are again using an array element in place
of a variable name. This is legal since 'contract' and 'contracts[0]' are
both of type 'Contracts'. The complete code for 'InitializeInstances' is therefore: void InitializeInstances(ContractArray contracts)
} B. A New Function, 'DisplayAll'
case 'a':
break; The declaration for 'DisplayAll' would be the same as for 'InitializeInstances': And, here is the definition: void DisplayAll (ContractArray contracts)
{
int sqFootage;
int numDesks;
int numDays;
double charge;
for (int contract = 0; contract < MAX_CONTRACTS; contract++)
{
sqFootage = contracts[contract].ProvideSquareFootage();
cout << "The square footage for contract " << (contract+1) << " is: "
<< sqFootage << endl;
numDesks = contracts[contract].ProvideNumberOfDesks();
cout << "The number of desks for contract " << (contract+1) << " is: "
<< numDesks << endl;
numDays = contracts[contract].ProvideNumberOfDays();
cout << "The number of days for contract " << (contract+1) << " is: "
<< numDays << endl;
charge = contracts[contract].ProvidePerWeekCharge();
cout << "The per week charge for contract " << (contract+1) <<" is: "
<< charge << endl;
cout << endl << endl;
}
}
There is a set of parentheses around 'contract+1' to clarify that we
want the addition to take place before we output the value of 'contract'.
By the way, it would have been incorrect to write 'contract++' in this
case because that would have incremented the value of 'contract' itself.
If you are puzzled about this statement, TRY IT!
V. The
List: An Initial Look at Knowledge Representation What we need now is a more complex data structure, a structure that not
only allows us to store elements as the array does but also includes the
new functionality of adding and deleting. Many people call such a
structure a list so that is the name we will use. Be aware that what we
are about to create here is a specific example of the very general type
'list'. We are bordering on having yet another pattern! A. A Revised Set of Specifications Olympia owns a small office cleaning business. She wishes to have a program to keep track of the contracts she has with various businesses. Each contract involves one office complex and should keep track of the square footage of the office, the number of desks in the office, and the number of days per week the office is to be cleaned. A user of this program should be able to change any of this information and get all of it plus the per week charge which is calculated based on the formula:
Users should also be able to add new contracts and
delete existing ones. . When adding contracts the contract
ID, square footage of the office, the number of desks in the office,
and the number of days per week the office is to be cleaned all must be
provided. To delete a contract, the user enters the contract ID. No
contract can exist that does not have all this information
provided initially. Olympia has set a limit on her business of
100 different contracts in order to have time for her family and
the other real things of life. All the changes relevant here are in the second
paragraph. First, we can drop the nouns 'family' and 'things of life'
since they are really editorial comments. Likewise, the new verb
'provided' has nothing to do with an action of the program. It talks about
what the user will do. Don't let such things confuse you. There are two new verbs that we do need to pay
attention to - 'add' and 'delete'. Our earlier analysis discovered one
class - contract. Are add and delete operations performed by a
contract? If you think carefully, you will realize that nothing is added
to or deleted from the contracts themselves . Rather, contracts are added
to or delete from something else. What is that 'something else' - what
object handles the adding and deleting? The introduction to this section gave away the answer but it is
interesting to note here that the problem narrative only indirectly
indicates the need for a new object type, a new class. Something needs to
hold all these contracts and allow them to manipulated. That 'something'
is a list. Thus, in a bit we will design a new class that we might call
'ListOfContracts'. This class will be special because in this program we
will only have one instance of this class. Whenever you have a problem that involves grouping together a
collection of items, consider the possibility of creating a new class one
of whose properties is the collection itself. We could actually have
created a new class to hold the contracts in the last few chapters. We
didn't because the built-in C++ type 'array' met our needs quite well and
there was no special functionality to be attached to this array type. As
we shall see, a list is more complex than simply an array and does have
extra functionality. Let's start, however, by trying to create the list type using only an
array:
{ } and thus have kept garbage in the values of the instances but why
change what we have if it works? And, it is probably better that we know
what an empty contract looks like in case we accidentally try and use one.
We start then with zero contracts in the list. How do we keep track of
this and update the amount as we add contracts? There has to be some kind
of counter that can be incremented when a new contract is added and
decremented when a contract is deleted. Here we see that an array by
itself will not work. There are at least two aspects to a list - the
collection of objects and a counter. We now have two properties (data members) for our list data structure -
the collection and the counter - and two function members - 'add' and
'delete'. Note that the approach just described here for discovering the
class(es) needed as well as their responsibilities and properties is
somewhat different from what we did in chapter 5. Remember, object
oriented analysis is still more of an art than a science so do what works
but CHECK YOUR WORK CAREFULLY!!! Actually, we can and should take advantage of what we learned in
chapter 5. To check our work so far, see if there is more to this new
class, and formalize our conclusions, we need to create a CRC card for our
new class. So far the two responsibilities for the class
'ListOfContracts', in addition to constructing itself, are 'add' and
'delete'. Now, there was a third italicized verb in the second paragraph
of the problem narrative, - 'exist' - and, of course, instances do exist.
However, beyond stating the obvious, this verb indicates no new
functionality or responsibility for the class so our CRC card looks as
follows:
When doing the CRC card for 'Contract' we did not proceed to discuss
collaborators because there was only one class to begin with. Now,
however, we have two classes. Do either of these classes use the other as
part of its work. Note that the class 'contract' can get along quite well
without having any lists - we have written a program that demonstrates
this. However, the class 'ListOfContracts' can't really exist without
having a 'Contract' class to hold instances of. In other words, 'Contract'
is a collaborator of 'ListOfContracts' and we will see in the code for
'ListOfContracts' that the class 'Contract' must be known.
Figure 8.2 With the CRC card in hand, we finish the analysis by checking to see if
there are any new properties for our list class. The properties we have
discovered so far (the collection of contracts and the counter to hold the
number of contracts in the collection) were not found by looking at the
underlined nouns in the problem narrative. Of course, they could have been
discovered that way if, for example, the narrative had included a sentence
such as, "The program will keep track of the number of contracts in the
collection of contracts". But, no such sentence was included in the
narrative. This points to the need to use every tool at one's disposal -
including working as a team -in hopes that someone on the team will notice
missing details. The narrative does have two new nouns - 'limit' and 'contract ID'. The
word 'limit' refers to the maximum number of contracts and it could be
represented as a property of the class. However, it is already needed in
the code as a constant indicating the maximum size of the array.
Therefore, it is somewhat redundant. On the other hand, it is possible
that later we will represent a list using a mechanism other than an array
in which case explicitly representing the maximum would be beneficial. For
that reason, let's include it even that it has no immediate use. The notion of a 'contract ID' clearly is not a property of
'ListOfContracts'. As we noted earlier, it is the Individual contract that
has an ID. Technically, we probably should have made this ID a property of
the 'Contract' class when we first discovered it but, as long as we were
not deleting or adding contracts, it was unnecessary. Deletes change
everything. When a user wants to delete a contract, they should not have
to know where in the array the specific contract they want to delete is
stored. In fact, given the notion of encapsulation, neither the user nor
the programmer of the main program even knows that we are using an array
to implement the list of contracts. What users should provide is some way
of identifying the contract they want deleted - thus the contract ID. This
ID will be used to search through the list looking for the contract to be
deleted. VI. Re-Evaluating the Implementation of the User Interface and the
Design of 'ProcessUserChoices' At this point it is good practice for the design team to walk through
the various processes involved in this program and see if the various
member and non-member functions still work together as expected. It is
amazing how beginners, who often have trouble visualizing how the various
components of a program work together, will avoid this step while
experienced programmers, who would seem to have a better grasp of the
interactions involved, will take advantage of this. Let's start with an analysis of the processes involved in simply
displaying the information about a contract. After a 'D' has been entered
to indicate that the user wants to display the information about a
contract, the contract ID is requested. Once this is typed, the function
'ProcessUserChoices', as
designed in this chapter, used the value entered by the user as an
index into an array of contracts and passed an element of the array of
contracts to 'DisplayInfo'. First hint of trouble: the re-designed program
does not provide the main program with direct access to any array. (The
array is now encapsulated inside the list.) Nor does the new version use
contract ID's as indices. How then will the function 'ProcessUserChoices'
get a contract to pass to 'DisplayInfo'? So, we know there is a problem. Before running off to solve it, we
should look at more of the program. We might gain some information that
will guide us in devising a solution. What happens, then, when the user
chooses to change the information about some contract. Again, a contract
ID is entered and we run into the same problem. We need a way to get back
the contract to be passed to the function that makes the changes. Not only
that but there is the added problem that once the contract has been
changed it needs to be put back into the list and no mechanism seems to
exist to do that. We could have the system delete the old contract and
then add the new contract but that seems a bit convoluted.
Moving on, let's look at 'DisplayAll'. When the user chooses to display
all the contracts, there is no need for any specific contract ID. But, we
still have a problem - how does the function 'DisplayAll' get access to
the contracts in the list! Remember, the array of contracts is
encapsulated and, therefore, is unavailable to functions that are not
members of 'ListOfContracts'. The design for the processes to delete and add contracts seem to be the
only parts that are OK . In the case of deleting, the user will enter the
contract ID and this will be passed to the member function 'Delete' which
will do all the work. To add a contract the user will
enter all the information necessary, a contract instance will be created,
and finally, the member function 'Add' will be called with the newly
instantiated contract being passed as a parameter. It makes sense that 'Add' and "Delete' work since we have yet to commit
to much by writing code for them and because they were planned as part of
the new class, 'ListOfContracts'. The rest of the functions are leftovers
from a program that existed before we worried about adding and deleting or
about using a list. Take a moment to think about what you might do to fix
these functions before proceeding to the next
section .
| |