All pastes #443867 Raw Edit

Something

public text v1 · immutable
·published 2007-04-17 01:51 UTC
rendered paste body
    *  Once defined, can be used the same as any other ordinal type:

           // Define an enumerated type
           enum breakfast { BAGEL, DONUT, OMELETTE, MUFFIN};
           // Now declare a variable of that enumerated type
           breakfast myBrkfst;
           // All of these are legal:
               myBrkfst = BAGEL;         // assignment
               if (myBrkfst == DONUT)    // comparison
               switch( myBrkfst)         // selection
               {
                   case BAGEL:
                               // do something
                               break;
                   case OMELETTE:
                               // etc....
               } 


How They Work

    * Assigned integer values.
          o In above example, BAGEL = 0, DONUT = 1, OMELETTE = 2, MUFFIN = 3
    * Default is to start with 0, increment by 1 each time.
    * Can override values:
          o enum breakfast { BAGEL, DONUT = 5, OMELETTE, MUFFIN}
          o If value is specified, following values incremented by one.
          o In above example, BAGEL = 0, DONUT = 5, OMELETTE = 6, MUFFIN = 7
    * Though values are represented by symbolic names, these are not strings:

         if ( myBrkfst == "BAGEL")    // this won't work; compares enum & string
          cout << myBrkfst << endl;    // prints numeric value

    * In general, operations relying on numeric values of enums, while legal, should be avoided.
          o The entire point is to hide the numeric values and work on a higher level of abstraction.
          o If we need to carry out arithmetic operations, enumerated types are probably a poor design choice.
          o If enum definition is changed, the code that relies on it must change as well.
          o If only symbolic names are used, this is automatic.
                + E.g. in original definition (default values), if we added a new type PANCAKE so our definition was
                enum breakfast { BAGEL, PANCAKE, DONUT, OMELETTE, MUFFIN };
                     then the code that uses it (e.g. if (myBrkfst == DONUT)) is updated correctly to new
                     values, automatically.
          o On the other hand, if (myBrkfst == 1) now tests for PANCAKE rather than DONUT.
    * Using symbolic names allows later expansion of types.
    * Using symbolic names makes intent of code clearer. Goal, as always, is self-documenting code.
    * These examples use ALL CAPS for type names.
          o This is a coding convention: That all symbolic constants be in all caps to identify them as something that can't be changed during program execution.
          o This is widely used but not universal.
    * Enumerated types can be used as an index into arrays:

          int x[5];
          cout << x[myBrkfst] << endl;

          o Note that if we'd redefined some of the values (say, DONUT = 5, OMELETTE = 6, etc.), the above code might lead to an out-of-bounds index problem.
          o Moral: Be careful!
    * Although they are stored as integers, there are some operations defined for integers that aren't defined for enumerated types:
          o Particularly, increment operator ++ or decrement operator --
    *           myBrkfst++;    // THIS WON'T WORK As a workaround, you can use cast operators to cast the variable to and from the enumerated type:

for ( myBrkfst = BAGEL; myBrkfst < MUFFIN; myBrkfst = breakfast(int(myBrkfst)+1))
// Convert to int, then back to breakfast
// It's ugly, but it works.
// Is this more trouble than it's worth? Quite possibly.

    * NOTE: If values have been overridden (BAGEL == 0, DONUT == 5, etc.), the increment operator will NOT skip the 'missing' values. It will still increment 0 (BAGEL), 1, 2, 3, 4 (ALL UNDEFINED), etc.
    * Moral: Don't override values without a good reason. If you do override values, remember that you've done so & code accordingly elsewhere. Document everything thoroughly.


A Practical Example

Suppose we have a file of the following struct:
struct zipRec
{
    string city;
    string state;
    string zipcode;
};
(Zip code is stored as string so leading zeros aren't stripped off.)
 

    * The user may need to sort the file by city, by state, or by zipcode.
          o Q: Do we need three sort routines?
                + May have to sort on any of three string values.
                + But...Our current algorithm breaks "find min" into separate function. Swapping items is handled the same.  And we're doing essentially the same thing for all three.
                + Therefore, if we could figure out a way to tell minIndex() which field to compare, we'd be done. It could find the minimum value on whichever field we told it to.
                + minIndex would compare on the appropriate data members, and tell the main program which item needed to be swapped to the top of the list.
          o A: No, we don't need three sort functions. One sort routine can handle all three cases, if we can get minIndex() to select which fields to compare on.
          o Whatever we decide on, we'd like it to be easy for other programmers to use, with a clear, consistent syntax.
    * We settle on this:


// Define enumeration of what types of sort we want to do
enum sortType { sortCITY, sortSTATE, sortZIP };
// New function prototypes
void sort( zipRec array[], int n, sortType type);
int minIndex( zipRec array[], int first, int last, sortType type);
void swap( zipRec a, zipRec b);
    // exactly same as swap(int a, int b) function above, modified to swap
    //   zipRecs instead of integers.

====In main():====
    zipRec mailingList[1000];
    // .. input, etc...
    // Sort mailing list by city:
    sort( mailingList, 1000, sortCITY);
    // Sort by zip code:
    sort( mailingList, 1000, sortZIP);
===================

void sort( zipRec array[], int n, sortType type)
// pre: array contains n zipRecs
// post: elements of array are sorted into ascending order
//   on field specified by type
{
    int j, index;
    for (j = 0; j < n-1; j++)
    {
        // Find location of smallest item from j to end
        index = minIndex( array, j, n-1, type);
        // Place smallest at beginning of sublist
        swap( array[j], array[index]);
    }
}

int minIndex( zipRec array[], int first, int last, sortType type)
// Pre: 0 <= first, first <= last, array[first]..array[last] are
//    initialized
// Post: Returns index of smallest entry in array between
//    first and last inclusive
{
    int smallIndex = first;    // smallest so far
    int k;
    switch( type)        // Select which comparison we want to do,
    {                    // based on parameter that got passed in
        case sortCITY:
            for k = (first +1; k <= last; k++)
            {
                if ( array[k].city < array[smallIndex].city)
                    smallIndex = k;
            }
            break;
        case sortSTATE:
            for k = (first +1; k <= last; k++)
            {
                if ( array[k].state < array[smallIndex].state)
                    smallIndex = k;
            }
            break;
        case sortZIP:
            for k = (first +1; k <= last; k++)
            {
                if ( array[k].zipcode < array[smallIndex].zipcode)
                    smallIndex = k;
            }
    }
    return smallIndex;
}
 
 

    * Note that if we expand our record, we can add the ability to sort by the new field, by adding one extra enumerated value and an extra case to the minIndex() function.
    * If for some reason we want to deny the possibility of sorting by state, we just remove that enumerated value from the definition of sortType, remove that case from minIndex(), and recompile.
    * Attempting the recompilation will cause errors any place there was an attempt to use the value sortSTATE, if that value has been removed.
    * Alternatively, we could just remove the case from minIndex() but leave it in the definition. Any call trying to sort by state will have no effect. (Trace the above code and see what would happen).