|
course.wilkes.edu/CS125Labs |
||||||
Lab 11: Building Enumerations
IntroductionIt is often the case that a programmer needs to represent some real-world object whose possible values are non-numeric. For example:
string type:
cout << "\nEnter your gender: ";
string userGender;
cin >> userGender;
if (userGender == "male")
// ...
For many applications, this approach is perfectly acceptable.
However, for applications where speed is important, this approach has a
major drawbacks -- comparing two string values is a slow
operation.
That is, the string equality operation is typically defined something
like this:
bool operator==(const string & str1, const string & str2)
{
if (str1.size() != str2.size())
return false;
else
{
for (int i = 0; i < str1.size(); i++)
if (str1[i] != str2[i])
return false;
If we trace the execution through a call to this function:
(userGender == "male")then
userGender is compared to the first character in "male".
If the two strings are different,
then the function may return false at that point,
but if the user entered male, then three more trips through
the loop are required to verify that each character in str1
is the same as the corresponding character in str2.
Needless to say, all of this consumes valuable time,
so if time is important in a problem, a different approach is needed.
Today's exercise presents one way to solve this problem.
Getting StartedCreate a new directory in which to store the work for this exercise. Then save copies of Gender.h, Gender.cpp, Gender.doc>, driver11.cpp, and makefile11 in this directory.
Today's exercise has eight parts, which we will do in sequence.
The program in
NOTE: To complete parts 4-8 of this exercise,
you must be using g++ 2.8.0 or later.
(To determine your version of g++, type
1. Declaring An Enumeration Type
To provide a way to use real-world names in a program without using
a
For example, suppose that we want to create a new type named
Season, with the values:
Spring, Summer, Autumn and Winter.
We can do so be creating a
enum Season {SEASON_UNDERFLOW, SPRING, SUMMER,
AUTUMN, WINTER, SEASON_OVERFLOW};
With this statement, we are declaring to the compiler that the identifier
Season is the name of a new type, whose valid values are
SEASON_UNDERFLOW,
SPRING,
SUMMER,
AUTUMN,
WINTER, and
SEASON_OVERFLOW.
(The "overflow" and "underflow" values provide "abnormal" values
for error-handling and other unusual situations.)
Given such a declaration, a programmer can write: Season aSeason = SPRING;to declare a variable named aSeason and initialize it
to SPRING.
Note that since an enumeration is not a string,
no quotation marks are needed when you use an enumeration value
like SPRING, which is called an enumerator.
Since they are essentially constant values,
it is conventional to write enumerators in uppercase.
The general pattern for declaring an enumeration with N values is thus:
enum NewType {Value1, Value2, ... ValueN};
Such a declaration creates NewType as the name of a new type,
whose valid values are Value1, Value2, ...,
ValueN, which must be C++ identifiers.
Using this information, open
2. The Input Operation
In order to input an enumeration, it is important to recognize that
the input operator ( cin >> aGender;and expect to read a Gender value from the keyboard,
because there is no definition of the input operator for type Gender.
We can, however, provide such a definition.
In designing such a function, it is important to remember that
an
Receive: in, an istream,
value, an enumeration variable.
Precondition: in contains the string of a valid enumerator.
Input: the enumerator-string from in.
Passback: in, with the enumerator-string extracted from it;
value, containing the enumerator.
Return: in, for chaining.
Our function must thus read a string corresponding to an
enumerator from in, determine the corresponding enumerator value,
and pass back that enumerator value via parameter value.
Here is an algorithm to solve this problem for our Season enumeration:
0. Receive in and value.
1. Read aString from in.
2. Convert the characters in aString to upper-case, if need be.
3. If aString == "SPRING":
value = SPRING.
Else if aString == "SUMMER":
value = SUMMER.
Else if aString == "FALL" OR aString == "AUTUMN":
value = AUTUMN.
Else if aString == "WINTER":
value = WINTER.
Else
Display error message and terminate.
End if.
4. Return in.
Note that since we are comparing string values,
we cannot use a switch statement,
but must instead use an if-else-if statement to
implement this algorithm.
This function can thus be defined as follows:
istream & operator>>(istream & in, Season & value)
{
string aString;
in >> aString;
Note in particular the difference between a string literal and
an enumerator. The C++ compiler uses the double-quotes surrounding
"SPRING" to distinguish it from an enumerator like SPRING.
If you were to try and assign "SPRING" to value
(or assign SPRING to aString), the compiler would
generate an error, since the types of the objects do not match.
Using this definition as a model, define
0. Receive in and value.
1. Read aString from in.
2. Convert the characters in aString to upper-case, if need be.
3. If aString == "FEMALE":
value = FEMALE.
Else if aString == "MALE":
value = MALE.
Else if aString == "UNKNOWN":
value = UNKNOWN.
Else
Display error message and terminate.
End if.
4. Return in.
Add a prototype for this function in the appropriate places
in
3. The Output OperationAs with the input operator, the output operator cannot be applied to an object unless it has been defined for objects of that type. That is, we cannot simply write cout << aGender;and expect to display a Gender value from the keyboard,
because there is no predefined definition of the output operator
for type Gender.
We can, however, provide such a definition.
As we saw with an
Receive: out, an ostream,
value, an enumeration variable.
Precondition: value contains a valid enumerator.
Output: the string corresponding to that enumerator, via out.
Passback: out, containing the enumerator-string.
Return: out, for chaining.
Our function must thus determine the string that corresponds
to the enumerator it receives from the caller,
and display that string.
Here is an algorithm to solve this problem for our Season enumeration:
0. Receive out and value.
1. If value == SPRING:
Display "SPRING" via out.
Else if value == SUMMER:
Display "SUMMER" via out.
Else if value == AUTUMN:
Display "AUTUMN" via out.
Else if value == WINTER:
Display "WINTER" via out.
Else
Display error message and terminate.
End if.
2. Return out.
Note that since we are comparing enumerator values,
which are integer-compatible,
we can use a switch statement to implement this algorithm.
This function can thus be defined as follows:
ostream & operator<<(ostream & out, const Season value)
{
switch(value)
{
case SPRING:
out << "SPRING";
break;
case SUMMER:
out << "SUMMER";
break;
case AUTUMN:
out << "AUTUMN";
break;
case WINTER:
out << "WINTER";
break;
default:
cerr << "\n*** Invalid enumerator received by <<\n" << endl;
exit(1);
}
return out;
}
Using this definition as a model, define operator<< for
your Gender enumeration in Gender.cpp, so that it implements
the following algorithm:
0. Receive out and value.
1. If value == FEMALE:
Display "FEMALE" via out.
Else if value == MALE:
Display "MALE" via out.
Else if value == UNKNOWN:
Display "UNKNOWN" via out.
Else
Display error message and terminate.
End if.
2. Return out.
Add a prototype for this function in the appropriate place
in Gender.h and Gender.doc.
Then "uncomment" the first and last output steps in driver.cpp
and test your function by translating your driver program.
When it is syntactically correct, continue to the next part of the exercise.
4. The Prefix Increment OperatorIt is often convenient if we can increment an enumeration variable. For example, we might want to display the four seasons by writing
for (Season aSeason = SPRING; aSeason <= WINTER; ++aSeason)
cout << aSeason << ' ';
To write a statement like this, we must provide a definition of
the prefix increment operator ++.
We can specify its behavior as follows:
Receive: value, an enumeration object.
Precondition: value contains a valid enumerator.
Passback: value, containing the next enumerator in the enumeration
(or its OVERFLOW value).
Return: value.
The thing to remember about the prefix increment operator is that its
return-value is the incremented variable.
An algorithm to perform this operation for our Season
enumeration is thus as follows:
0. Receive value.
1. If value == SPRING:
value = SUMMER.
Else if value == SUMMER:
value = AUTUMN.
Else if value == AUTUMN:
value = WINTER.
Else if value == WINTER:
value = SEASON_OVERFLOW.
Else
Display an error message and terminate.
2. Return value.
Since we are comparing enumeration values we can implement this algorithm
using a switch statement:
Season operator++(Season & value)
{
switch (value)
{
case SPRING:
value = SUMMER;
break;
case SUMMER:
value = AUTUMN;
break;
case AUTUMN:
value = WINTER;
break;
case WINTER:
value = SEASON_OVERFLOW;
break;
default:
cerr << "\n*** Invalid enumerator received by prefix++\n" << endl;
exit(1);
}
return value;
}
The algorithm for a version of this operation for the Gender
enumeration is similar:
0. Receive value.
1. If value == FEMALE:
value = MALE.
Else if value == MALE:
value = GENDER_OVERFLOW.
Else
Display an error message and terminate.
2. Return value.
Using this information, implement this algorithm by defining
operator++ in Gender.cpp,
and add prototypes of it to Gender.h and Gender.doc.
Then test what you have written by "uncommenting" the three lines
in driver.cpp that refer to gender1;
and then translating and running your program.
Continue when it works correctly.
5. The Postfix Increment OperatorWhile overloading the prefix operator provides us with the means of incrementing an enumeration, it is also sometimes desirable to be able to use the postfix increment operation. For example, we might want to display the four seasons by writing
for (Season aSeason = SPRING; aSeason <= WINTER; aSeason++)
cout << aSeason << ' ';
To write a statement like this, we must provide a definition of
the postfix increment operator ++,
whose behavior is slightly different from that of the prefix version:
Receive: value, an enumeration object.
Precondition: value contains a valid enumerator.
Passback: value, containing the next enumerator in the enumeration
(or its OVERFLOW value).
Return: the original enumerator of value.
That is, both this and the prefix version pass back the next enumerator
via parameter value; however,
where the prefix version returns that next enumerator,
the postfix version returns the original enumerator of value.
An algorithm to perform this operation for our Season
enumeration is thus slighly different:
0. Receive value.
1. Save value in savedValue.
2. If value == SPRING:
value = SUMMER.
Else if value == SUMMER:
value = AUTUMN.
Else if value == AUTUMN:
value = WINTER.
Else if value == WINTER:
value = SEASON_OVERFLOW.
Else
Display an error message and terminate.
3. Return savedValue.
The definition and prototype of this function must be distinct from
those of the prefix version of operator++.
Put differently, the signatures (the list of parameter types)
of the prefix and postfix versions of the function must be different,
in order for the compiler to tell them apart.
The convention adopted by C++ is to add an unused int parameter
to the postfix version, to distinguish it from the prefix version.
That is, the C++ compiler will associate the prototype
Season operator++(Season & value);with the prefix increment operator, and will associate the prototype Season operator++(Season & value, int );with the postfix increment operator. We can thus define this function for our Season enumeration
as follows:
Season operator++(Season & value, int )
{
Season savedValue = value;
Note that in odd situations like this where a parameter is needed,
but not required by the function's definition, C++ permits the
name of the parameter to be omitted from the function definition.
The algorithm for a version of this operation for the
0. Receive value.
1. Save value in savedValue.
2. If value == FEMALE:
value = MALE.
Else if value == MALE:
value = GENDER_OVERFLOW.
Else
Display an error message and terminate.
3. Return savedValue.
Using this information, implement this algorithm by defining a postfix
version of operator++ in Gender.cpp,
and add prototypes of it to Gender.h and Gender.doc.
Then test what you have written by "uncommenting" the three lines
in driver.cpp that refer to gender3;
and then translating and running your program.
Continue when it works correctly.
6. The Prefix Decrement OperatorJust as we sometimes want to increment, other times we want to decrement. For example, we might want to display the four seasons in reverse order by writing
for (Season aSeason = WINTER; aSeason >= SPRING; --aSeason)
cout << aSeason << ' ';
To write a statement like this, we must provide a definition of
the prefix decrement operator --.
We can specify its behavior as follows:
Receive: value, an enumeration object.
Precondition: value contains a valid enumerator.
Passback: value, containing the previous enumerator in the enumeration
(or its UNDERFLOW value).
Return: value.
The thing to remember about the prefix decrement operator is that its
return-value is the decremented variable.
An algorithm to perform this operation for our Season
enumeration is thus as follows:
0. Receive value.
1. If value == SPRING:
value = SEASON_UNDERFLOW.
Else if value == SUMMER:
value = SPRING.
Else if value == AUTUMN:
value = SUMMER.
Else if value == WINTER:
value = AUTUMN.
Else
Display an error message and terminate.
2. Return value.
Since we are comparing enumeration values we can implement this algorithm
using a switch statement:
Season operator--(Season & value)
{
switch (value)
{
case SPRING:
value = SEASON_UNDERFLOW;
break;
case SUMMER:
value = SPRING;
break;
case AUTUMN:
value = SUMMER;
break;
case WINTER:
value = AUTUMN;
break;
default:
cerr << "\n*** Invalid enumerator received by prefix--\n" << endl;
exit(1);
}
return value;
}
The algorithm for a version of this operation for the Gender
enumeration is similar:
0. Receive value.
1. If value == FEMALE:
value = GENDER_UNDERFLOW.
Else if value == MALE:
value = FEMALE.
Else
Display an error message and terminate.
2. Return value.
Using this information, implement this algorithm by defining
operator-- in Gender.cpp,
and add prototypes of it to Gender.h and Gender.doc.
Then test what you have written by "uncommenting" the three lines
in driver.cpp that refer to gender2;
and then translating and running your program.
Continue when it works correctly.
7. The Postfix Decrement OperatorOur final operation is the postfix increment operation. For example, we might want to display the four seasons in reverse order by writing
for (Season aSeason = WINTER; aSeason >= SPRING; aSeason--)
cout << aSeason << ' ';
To write a statement like this, we must provide a definition of
the postfix decrement operator --,
whose behavior is slightly different from that of the prefix version:
Receive: value, an enumeration object.
Precondition: value contains a valid enumerator.
Passback: value, containing the previous enumerator in the enumeration
(or its UNDERFLOW value).
Return: the original enumerator of value.
That is, both this and the prefix version pass back the next enumerator
via parameter value; however,
where the prefix version returns that previous enumerator,
the postfix version returns the original enumerator of value.
An algorithm to perform this operation for our Season
enumeration is thus slighly different:
0. Receive value.
1. Save value in savedValue.
2. If value == SPRING:
value = SEASON_UNDERFLOW.
Else if value == SUMMER:
value = SPRING.
Else if value == AUTUMN:
value = SUMMER.
Else if value == WINTER:
value = AUTUMN.
Else
Display an error message and terminate.
3. Return savedValue.
As before, the definition and prototype of this function must be distinct from
those of the prefix version of operator--.
Put differently, the signatures (the list of parameter types)
of the prefix and postfix versions of the function must be different,
in order for the compiler to tell them apart.
The same convention is used to distinguish the prefix and postfix versions
of the decrement operator as was used to distinguish the versions of
the input operator.
We can thus define this function for our Season enumeration
as follows:
Season operator--(Season & value, int )
{
Season savedValue = value;
The algorithm for a version of this operation for the Gender
enumeration is similar:
0. Receive value.
1. Save value in savedValue.
2. If value == FEMALE:
value = GENDER_UNDERFLOW.
Else if value == MALE:
value = FEMALE.
Else
Display an error message and terminate.
3. Return savedValue.
Using this information, implement this algorithm by defining a postfix
version of operator-- in Gender.cpp,
and add prototypes of it to Gender.h and Gender.doc.
Then test what you have written by "uncommenting" the three lines
in driver.cpp that refer to gender4;
and then translating and running your program.
Continue when it works correctly.
8. A Code-Generating Tool for Enumerations
For most enumerations a programmer wants to use,
these same seven steps are needed:
(i) declare the enumeration; (ii) implement the input operation,
(iii) implement the output operation, (iv) implement the prefix increment
operation, (v) implement the postfix increment operation,
(vi) implement the prefix decrement operation, and
(vii) implement the postfix decrement operation.
Some enumerations may require additional operations
(e.g., a Since you have just implemented one enumeration, it may not be evident, but the process of defining these operations for a given enumeration is a mechanical one -- aside from the particular enumerator values, the algorithms for each of these operations is exactly the same from one enumeration to another. This process is so mechanical, it is straightforward to devise a program that, given a sequence of enumerators and the desired name of the enumeration, generates the C++ code to declare the enumeration type and its operations. (In fact, there are other languages where these operations are automatically defined for you by the compiler, whenever you create a new enumeration.) Now that you have seen how to declare an enumeration and define its operations, it seems unconscionable to make you mechanically go through these same seven steps each time you want to define a new enumeration. As a result, we have written a little program named "enumGenerator.cpp" that, given the name of an enumeration and a sequence of enumerators stored (one per line) in a file, generates the header, implementation, and documentation files for that enumeration.
Save a copy of
To test the correctness of this code, save a copy of
"dayTester.cpp" in your directory,
translate and run it.
You may wish to study Remember, whenever you find yourself doing the same thing over and over, look for a better way! Automated code generators of this sort are one way to solve such problems.
Phrases you should now understand:Enumeration, Enumerator, Prefix Increment Operation, Postfix Increment Operation, Prefix Decrement Operation, Postfix Decrement Operation, Code Generation.
Submit:
Hard copies of
|
Home Membership
|
|||||
|
Last update: Friday, October 6, 2000 at 12:57:11 PM. |
||||||