| |
Main Menu | Next Chapter |
| Section VIII: Modifying the Main Program | Section I: An Expandable List of Contracts | Section II: Declaring the New ListofContracts | Section III: Defining the New ListOfContracts |
| Section IV : Re-Evaluating the Contract Program Design | Section V: Adding String Properties to the Contract Class | Section VI: Declaring the New Contract Class | Section VII: Defining the New Contract Class |
Section VIII: Modifying the Main Program
Notice that our modified version of the problem narrative made no mention of changes to the 'ListOfContracts' class. That is because, here, we are only adding strings and those additions have no effect on the list itself. Therefore, there are no changes to the .h or .cpp files for this class, except to change the include statement to include the new version of the 'Contract' class header file, contrct6.h. The
files with those few changes are called ch10lst1.h and ch10lst1.cpp. (Note that the changes to 'ListOfContracts' required to implement the iterator were completed earlier.)
However, there are changes to the main program - mostly because
of the changes to the interface we worked out in the analysis
phase above. Here is where we use the string input operations
we learned earlier. The changes in the input interface are more
complex so let's start with them.
When a user selects the option to change contract information,
the function 'ChangeInfo' is called. The original algorithm for
this function repeatedly asked the user for a property code (a
character) until a 'N' was entered to indicate that no more property
values were to be changed. For each valid property code entered,
the user was asked for the new value and the change was made.
Our analysis of this interface indicated that it would be faster
if the user was allowed to enter all the codes for changes at
once. Now that we know how to work with strings, we have a data
structure that will allow us to do this. We will use a function
called 'GetProperties' to retrieve the codes entered. However,
instead of returning simply a character, this function will return
a string.
We will explore how to implement this function in a bit but first
let's continue our discussion of 'ChangeInfo'. Once the function
has a string of codes, it needs to execute a loop to process the
codes. The old loop continued until an 'N' was entered and we
could continue to force the user to enter an 'N' in the new version.
However, strings have their own natural ending symbol, the null
character. Thus, we can write the loop to continue until the '\0'
symbol is encountered in the string of codes.
Inside the loop in the old version there was a switch statement
for each of the properties and separate calls to functions to
get the new values. For example, there was the function 'GetSquareFootage'
to ask the user for a new value for the square footage of an office
and a function 'GetNumDesks' to get the new value for the 'numDesks'
data member. As we add more properties, this is going to get ugly
real fast. Unfortunately, we still need the switch statement itself
to process the different codes but we can avoid having to create
unique value requesting functions for each property. Instead we
will create a general purpose input function for all the new string
properties. This function will allow us to ask the user for the
value of a specific property by passing in the name of that property.
It will return the new string value for that property.
Here is the function definition for this function:
It has two string parameters – with type names that must have been declared elsewhere. (We will use the type name approach here – as opposed to using 'char*' – simply to demonstrate that it can be done.)
The first parameter will be used to return the new value while the second will be used to receive and output the name of the property to be changed. Note how the 'cout' line uses this second parameter. For example, if the string 'Street Address' is passed in, the screen will display:
If one wanted completely unique prompts, one could pass in the
whole prompt and rewrite the cout line simply as:
The third line of this function represents the first time we have
used the 'get' input function you were introduced to in Chapter 9, Section IV.
"Get(string, 81)", as you may recall, will read until it encounters
an newline symbol or up to 80 characters - the 81st character
is for the '\0'. Thus the user can type any length of string he
or she wants up to 80 characters. The code is written 'cin.get(....)'
because 'get' is actually a member function of the class istream
of which 'cin' is an instance.
Unlike the '<<' operator, the 'get' member function does
not skip white space or newline characters before reading in data.
Nor, does it actually read the newline character when it encounters
it. Thus, if the system has used the 'get' function and encountered
a newline character, that newline character remains in the input
stream. Therefore, the next 'get' will again encounter that newline
character and stop immediately without reading anything! To avoid
this we include the second line:
As the comment says, this skips past ("consumes") any
left over characters until it has skipped over 80 characters or
the newline character. In our program, there are unlikely to be
any left over characters but there will be newlines left over from
previous inputs. With the newline out of the way, the next 'get'
function will operate safely.
As an aside, here you see another function that is so general
it is really a pattern. You can use this approach of passing a
string that represents part or all of an output anywhere you want
to use one function to generate multiple different outputs.
We return now to 'ChangeInfo'. Since the ' GetNewString' function
we just discussed returns a string, it cannot be used to retrieve
new values for the integer based properties. We could write one
function that would return an integer but the separate functions
for these properties already exist so we will continue to use
them. What we will change is the codes for these properties. We
could easily make up codes that somehow matched the properties
when we had only a few properties but as the number of properties
have grown, too many of the property names have started with the
same letter. Therefore the codes have been changed. It so happens
that we only have eight properties so the codes 1-8 work just
fine. If we had more properties, we would have to start using
letter codes. We can't use a code such as '10' because the combined
set of codes entered are stored as a string and the program would
interpret this is the separate codes '1' and '0'.
Here is the C++ code based on this discussion:
void ChangeInfo( Contract& contract)
{ String80 string;
String80 properties;
GetProperties(properties); // 'properties' is a string representing the codes for
// all the properties to be changed.
int index = 0;
while (properties[index])
{ switch (properties[index])
{ case '1':
GetNewString(string, "ContracteeName");
contract.ChangeContracteeName(string);
break;
case '2':
GetNewString(string, "Office Street Address");
contract.ChangeOfficeStreetAddress(string);
break;
case '3':
GetNewString(string, "Office City");
contract.ChangeOfficeCity(string);
break;
case '4':
GetNewString(string, "Office State");
contract.ChangeOfficeState(string);
break;
case '5':
GetNewString(string, "Office Zip Code");
contract.ChangeOfficeZip(string);
break;
case '6':
int sqFootage;
sqFootage = GetSquareFootage();
contract.ChangeSquareFootage(sqFootage);
break;
case '7':
int numDesks;
numDesks = GetNumDesks();
contract.ChangeNumDesks(numDesks);
break;
case '8':
int numDays;
numDays = GetNumDays();
contract.ChangeNumDays(numDays);
break;
default:
cout << endl << properties[index] << " is an invalid code.\n";
} // end switch
index++;
} // end while
clrscr(); // Clear the screen before displaying the new contract
// Requires that one include the file 'conio.h'
DisplayInfo(contract); // allow user to see changes before proceeding
}
The function 'GetProperties' retrieves at once all the properties
to be changed and then the loop processing begins. Just for the
heck of it, this code is written using a while loop. A 'for' loop
would have worked just as well.
We want to begin by looking at the first element of the string
array ' properties'. Since a while loop does not have a
special mechanism to initialize variables, the line:
comes right before the loop begins. This variable will be used
as an index into the array, allowing the code to examine the individual
characters in the string one by one.
Examine the test condition for the while loop:
Recall that the character '\0' is equivalent to the integer value
0 and therefore it is equivalent to FALSE. (In contrast, the character
'0' has an ASCII code whose integer equivalent is 48!). Therefore,
this while loop will continue to execute until the NULL character
appears in the string.
Inside the loop is a switch statement that allows the program
to perform the appropriate instructions for each of the property
codes. For example, if property code 3 appears in the string,
the program will call the function 'GetNewString' passing the
string "Office City". When 'GetNewString' returns with
the new value for the office city, the member function 'ChangeOfficeCity'
is called for the contract in question and the value of the office
city property is changed. Note that the very last line of the
while loop increments the index variable to insure that the program
examines the next character in the string.
The last two lines of the function are a bonus. First, the screen
is cleared and then the code displays the new property values
for the contract. Strictly speaking, this is not part of the interface
we have developed, but it does make sense to display the changes
in case some mistake was made.
That finishes this discussion except for the code for the function 'GetProperties'. As usual when we use functional decomposition to break our code up into small, manageable functions, the code for this function is relatively simple. The algorithm has two lines:
You have written the code to display a menu many times by now
and we just saw how to read in a stream of characters (the characters
entered by the user) using the 'get' member function. We also
saw that it is wise to use the 'ignore' member function before
using 'get'. Given all this, the following code should come as
no surprise:
void GetProperties(String80 properties)
{ cin.ignore(80, '\n');
cout << "Please enter the codes corresponding to the properties you want to ";
cout << "change\n";
cout << "Enter as many as you desire. When finished, hit Enter.\n";
cout << " '1': Contractee Name\n";
cout << " '2': Office Street Address\n";
cout << " '3': Office City\n";
cout << " '4': Office State\n";
cout << " '5': Office Zip\n";
cout << " '6': square Footage\n";
cout << " '7': number of desks\n";
cout << " '8': number of days per week\n";
cin.get(properties,81);
}
As with 'GetNewString' the user will type any length string he
or she desires - representing the codes for as many properties
as need to be changed. When finished, the user hits the 'Enter'
key, the program stops reading from the input stream and the function
returns. Note that since the string value is returned via a parameter,
there is no reason for an explicit return statement.
The code to add a new contract is also affected by this new interface,
at least by the new string properties. There are more complex
ways of modifying the code for this option ( as in this example)
but one simple way is simply to write repeated calls to 'GetNewString',
passing the appropriate property name string each time. Here then is the
code:
void CreateContract(int contractID, Contract& contract)
{ String80 string;
contract.ChangeID(contractID);
GetNewString(string, "ContracteeName");
contract.ChangeContracteeName(string);
GetNewString(string, "Office Street Address");
contract.ChangeOfficeStreetAddress(string);
GetNewString(string, "Office City");
contract.ChangeOfficeCity(string);
GetNewString(string, "Office State");
contract.ChangeOfficeState(string);
GetNewString(string, "Office Zip Code");
contract.ChangeOfficeZip(string);
int sqFootage;
sqFootage = GetSquareFootage();
contract.ChangeSquareFootage(sqFootage);
int numDesks;
numDesks = GetNumDesks();
contract.ChangeNumDesks(numDesks);
int numDays;
numDays = GetNumDays();
contract.ChangeNumDays(numDays);
clrscr();
DisplayInfo(contract);
}
Changes will also have to be made to the function 'InitializeInstances' to handle the new string properties. These, however, simply involve calls to the new member functions for changing the values of the new properties. The first contract created in the present version of this function has contract ID '1234'. To provide a contractee name for this contract one would simply call the member function 'ChangeContracteeName' with the string constant of the name to be included. The code might be:
The same approach will work for all the other properties as you
can see by examining the code in the file ch10tst1.cpp.
The only other changes required in this program are in the function
'DisplayInfo' - the new string properties need to be output and
we earlier decided to streamline the form of that output. Unlike
string input and manipulation which required serious modifications
to the code, string output is simple. As noted earlier, we have
already been doing string output in the form of prompts and other
messages. The only difference is that now we are working with
string variables instead of string constants. For that reason
the code is self explanatory and you should simply review the
file ch10tst1.cpp.
After you are comfortable with the code in that file, you should compile, link, and run the complete program. The files you will need are:
Probably string handling - inside and outside of classes - was
the hardest part of this material. You may be pleased to know
(and frustrated if you did not discover sooner) that the newer
versions of C++ include a standard string class that builds in
many of the details we had to explicitly code. Why weren't you
told sooner? Well, what you learned here can be applied to other
situations. As a matter of fact, if we were faced with a program,
for example, that dealt with multiple different lists and a class
that included them all, we would have to write exactly the kind
of code you wrote here.
Anyway, if you want to use this new class, there is information in the "C++ Essentials
" document and in a document referenced in chapter 2. You should
also check out your compiler manual for references to the 'string'
class. Once you include the appropriate header file - look for
the file name "cstring.h"- you should be able to declare
variables of type 'string'. (As you have learned, if the class
name is 'string', then there is a new type called 'string'.)
Review the public member functions and constructor descriptions
in your manual for more information on the many ways variables
of this type can be used.
| |
Main Menu | Next Chapter |