C++ allows programmers to create their own data types. Perhaps the simplest method for doing so is via an enumerated type. An enumerated type is a data type where every possible value is defined as a symbolic constant (called an enumerator). Enumerated types are declared via the enum keyword. Let’s look at an example:
// define a new enum named Color
enum Color
{
// Here are the enumerators
// These define all the possible values this type can hold
COLOR_BLACK,
COLOR_RED,
COLOR_BLUE,
COLOR_GREEN,
COLOR_WHITE,
COLOR_CYAN,
COLOR_YELLOW,
COLOR_MAGENTA
};
// Declare a variable of enumerated type Color
Color eColor = COLOR_WHITE;
Defining an enumerated type does not allocate any memory. When a variable of the enumerated type is declared (such as eColor in the example above), memory is allocated for that variable at that time.
Enum variables are the same size as an int variable. This is because each enumerator is automatically assigned an integer value based on it’s position in the enumeration list. By default, the first enumerator is assigned the integer value 0, and each subsequent enumerator has a value one greater than the previous enumerator:
enum Color
{
COLOR_BLACK, // assigned 0
COLOR_RED, // assigned 1
COLOR_BLUE, // assigned 2
COLOR_GREEN, // assigned 3
COLOR_WHITE, // assigned 4
COLOR_CYAN, // assigned 5
COLOR_YELLOW, // assigned 6
COLOR_MAGENTA // assigned 7
};
Color eColor = COLOR_WHITE;
cout << eColor;
The cout statement above prints the value 4.
It is possible to explicitly define the value of enumerator. These integer values can be positive or negative and can be non-unique. Any non-defined enumerators are given a value one greater than the previous enumerator.
// define a new enum named Animal
enum Animal
{
ANIMAL_CAT = -3,
ANIMAL_DOG, // assigned -2
ANIMAL_PIG, // assigned -1
ANIMAL_HORSE = 5,
ANIMAL_GIRAFFE = 5,
ANIMAL_CHICKEN // assigned 6
};
Because enumerated values evaluate to integers, they can be assigned to integer variables:
int nValue = ANIMAL_PIG;
However, the compiler will not implicitly cast an integer to an enumerated value. The following will produce a compiler error:
Animal eAnimal = 5; // will cause compiler error
It is possible to use a static_cast to force the compiler to put an integer value into an enumerated type, though it’s generally bad style to do so:
Animal eAnimal = static_cast<Animal>(5); // compiler won't complain, but bad style
Each enumerated type is considered a distinct type. Consequently, trying to assign enumerators from one enum type to another enum type will cause a compile error:
Animal eAnimal = COLOR_BLUE; // will cause compile error
Enumerated types are incredibly useful for code documentation and readability purposes when you need to represent a specific number of states.
For example, functions often return integers to the caller to represent error codes when something went wrong inside the function. Typically, small negative numbers are used to represent different possible error codes. For example:
int ParseFile()
{
if (!OpenFile())
return -1;
if (!ReadFile())
return -2;
if (!Parsefile())
return -3;
return 0; // success
}
However, using magic numbers like this isn’t very descriptive. An alternative method would be through use of an enumerated type:
enum ParseResult
{
SUCCESS = 0,
ERROR_OPENING_FILE = -1,
ERROR_READING_FILE = -2,
ERROR_PARSING_FILE = -3,
};
ParseResult ParseFile()
{
if (!OpenFile())
return ERROR_OPENING_FILE;
if (!ReadFile())
return ERROR_READING_FILE;
if (!Parsefile())
return ERROR_PARSING_FILE;
return SUCCESS;
}
This is much easier to read and understand than using magic number return values. Furthermore, the caller can test the function’s return value against the appropriate enumerator, which is easier to understand than testing the return result for a specific integer value.
if (ParseFile() == SUCCESS)
{
// do something
}
else
{
// print error message
}
Another use for enums is as array indices, because enumerator indices are more descriptive than integer indices. We will cover this in more detail in the section on arrays.
Finally, as with constant variables, enumerated types show up in the debugger, making them more useful than #defined values in this regard.
Quiz
1) Define an enumerated type to choose between the following monster types: orcs, goblins, trolls, ogres, and skeletons.
2) Declare a variable of the enumerated type you defined in question 1 and assign it the troll type.
3) True or false. Enumerators can be:
3a) explicitly assigned integer values
3b) not explicitly assigned a value
3c) explicitly assigned floating point values
3d) negative
3e) non-unique
3f) assigned the value of prior enumerators (eg. COLOR_MAGENTA = COLOR_RED)
Quiz answers
4.6 — Typedefs
|
Index
|
4.4 — Type conversion and casting
|
4.6 — Typedefs
Index
4.4 — Type conversion and casting
When are the enumerators created? Can they be used independently of the enum-type variable? It appears that they are declared as -static const int-
Enums are handled at compile time, and they are generally treated as a const int. There is no such thing as a static type, so the static keyword is not applicable. The scope of enum type declarations is the same as normal variables: Enums declared outside of a class or function are treated as global, those inside a block are scoped to that block, and those in a class are considered part of that class.
For example:
{ // start of a block enum Colors { COLOR_BLACK, COLOR_WHITE, COLOR_RED }; } // Colors goes out of scope here Colors cValue; // not valid because Colors is out of scopeEnum values can be used independently of the enum-type variable, as they are essentially treated as integers. For example, the following is valid:
enum Colors { COLOR_BLACK, COLOR_WHITE, COLOR_RED, COLOR_GREEN, COLOR_BLUE, }; int nValue = COLOR_RED;Note that in the specification for the next version of C++ (C++0x), there will be a new type of enum (called a strongly typed enum) that will be treated as a unique type. These strongly typed enums will not be implicitly convertable to integers.
// define a new enum named Animal enum Animal { ANIMAL_CAT = -3; ANIMAL_DOG; // assigned -2 ANIMAL_PIG; // assigned -1 ANIMAL_HORSE = 5; ANIMAL_GIRAFFE = 5; ANIMAL_CHICKEN; // assigned 6 };Alex, are the semicolons here a mistake?
My program wouldn’t compile until I changed them to commas:
#include "stdafx.h" #include int main() { using namespace std; // define an enum named Animal - local (inside block) enum Animal { ANIMAL_CAT = -3, ANIMAL_DOG, // assigned to -2 ANIMAL_PIG, // assigned to -1 ANIMAL_HORSE = 5, ANIMAL_GIRAFFE = 5, ANIMAL_CHICKEN, // assigned to 6 }; Animal eAnimal = ANIMAL_HORSE; cout << eAnimal << endl; return 0; }Yup, that was a mistake on my part. Should have been commas. I corrected the example.
For some reason your comment got caught by my spam filter. I have no idea why though!
That’s strange. The first time I commented (a couple of days ago), it went through okay.
Anyway, thanks for the tutorials. They’re really good. ;)