The basic enumeration syntax forms a list of simple named integers or symbolic constants. The compiler can set default values for each constant, or programmers can establish the values. Programmers can name enumerations, creating a new program type specifier. Enumerations further extend the number of ways that programmers can create symbolic constants, each with relative advantages and disadvantages.
The terms symbolic constant, named constant, and manifest constant are synonyms for naming a constant value. As the term implies, a constant is a value that does not change while the program runs. For example, 5 is an integer constant, 5.0 is a double constant, '5' is a character constant, and "5" and "Hello World" are string constants (also known as string literals). C++ provides three ways of naming constants:
for (int i = 0; i < NUMBER_OF_STUDENTS; i++) . . .
The original C-style enumerations were simple and unscoped, but they have evolved with C++, growing to support the object-oriented paradigm and additional data types. Although the text continues to focus on simple, unscoped enumerations, the following figure presents a more complete description of the modern syntax. By now, we are familiar with the idea that a program consists of a pattern of keywords, symbols, and programmer-supplied names. The specification of an enumeration follows this same basic approach. Some parts of an enumeration are required, while others are optional.
| Element | Role | Required |
|---|---|---|
enumenum classenum struct |
Keyword | yes (1 of 3) |
| name | Also called a tag, this is the name or identifier a programmer gives to the enumeration, forming a new data type. The name is optional for traditional enumerations but required for scoped ones | no |
: type |
A kind of an integer: char, short, int, etc. The ANSI 2003 standard allows the implementation to choose the integer size, but this feature overrides that choice | no |
{ |
Delimiter | yes |
| element list | Comma-separated list of names (these are the names of the symbolic constants). Strictly speaking, this list is not required, but we're going to treat it as if it was | yes - one or more |
= value |
If a value is specified, it overrides the default value. Default values begin at 0 for the first element and then count sequentially for subsequent elements. The value can be a constant expression; e.g., if "circle" appears to the left of "square," then square = circle + 4 is legal |
no |
} |
Delimiter | yes |
| variable list | Comma-separated list of enumeration variables | no |
; |
Statement terminator | yes |
The many varied paths through the syntax diagram notwithstanding, the enumerations programmers use most frequently in practice are generally quite simple. The following examples illustrate common and a few less common uses.
| (a) | enum { NUMBER_OF_STUDENTS = 100 }; |
| (b) | enum { alpha = 3, beta = 10, gamma = 15 }; |
| (c) | enum { alpha = 3, beta, gamma }; |
| (d) | enum { alpha = 3, beta = alpha + 2, gamma = alpha + beta }; |
|
enum shape { circle, square, triangle }; shape my_shape; shape your_shape; |
my_shape = square; // all compilers my_shape = 1; // some compilers |
| (a) | (b) |
As the size and complexity of programs increase, programmers typically break them into multiple files. Programmers frequently put enumerations in header files (files ending with a .h extension) and #include them in source code files (files ending with a .cpp extension). We'll explore this organization in detail later in the chapter. With enumerations in header files and variable definitions in source code files, programmers use variable lists infrequently in practice.
It's easier to understand enumerations by seeing them in a concrete example demonstrating how and why programmers use them.
A "magic number" is an arbitrary value the program uses to pass information about events, notifications, or other simply-encoded information around a program. The exact value is insignificant, but it is essential that the value is unique within the given context and used consistently throughout the program. The following example illustrates magic numbers and the importance of their uniqueness and consistency.
| "Magic Number" Version | Enumeration Version |
|---|---|
int command = get_command();
switch(command)
{
case 0:
exit(0);
case 1:
search();
break;
case 2:
input();
break;
case 3:
import();
break;
case 4:
help();
break;
default:
cerr << "Unknown command\n";
break;
}
|
enum { EXIT, SEARCH, INPUT, IMPORT, HELP };
int command = get_command();
switch(command)
{
case EXIT:
exit(0);
case SEARCH:
search();
break;
case INPUT:
input();
break;
case IMPORT:
import();
break;
case HELP:
help();
break;
default:
cerr << "Unknown command\n";
break;
}
|
| (a) | (b) |
if (command == 2) do_one_thing(); else if (command == 3) do_another_thing(); |
if (command == INPUT) do_one_thing(); else if (command == IMPORT) do_another_thing(); |
| (c) | (d) |
case 1: does little to inform a reader what that particular case does - 1 is a "magic number" that doesn't have a natural connection to the operation it represents.return SEARCH;. Each case now has a symbolic constant as its target, clarifying the case's purpose to a program reader: case SEARCH: conveys more meaning than case 1:.Unix and early versions of Linux managed file and directory access based on three users, each with three distinct permissions. This technique classifies the users as the file owner (called the user), group, and others and names the three permissions as read, write, and execute. The technique can represent each permission with a single bit: 1 indicates that the user has that permission, and 0 indicates that the user does not. That means that a sequence of nine bits summarizes a given file's access permissions.
enum { uread = 1, // 000 000 001 user
uwrite = 2, // 000 000 010
uexe = 4, // 000 000 100
gread = 8, // 000 001 000 group
gwrite = 16, // 000 010 000
gexe = 32, // 000 100 000
oread = 64, // 001 000 000 others
owrite = 128, // 010 000 000
oexe = 256 // 100 000 000
};