Enumerations in C

An important construct of the C language are enumerations. These allow us to define a new data type that can only assume values defined within a list. In this way, we can create variables that can only assume specific values.

In this lesson we will see how to define and use enumerated types in C language. We will also see what their relationship with integers is and how we can use them to create mixed data structures.

Enumerations

Often, in our programs, the need arises to declare variables that can assume values only in a restricted set. Think, for example, of boolean variables, whose purpose is to store one of the two possible values true or false. Another example could be storing the days of the week, months, suits of playing cards, etc.

In such cases, one of the possible choices can be to use integer type variables, assigning to each value a particular meaning. For example, wanting to store the day of the week, one could assign the value 1 to Monday, 2 to Tuesday, etc. We could write code like the following:

int day; /* Stores the day of the week */

day = 3; /* 3 = Wednesday */

This code works. However, it is difficult to read and maintain. At first glance, in fact, it is not immediately clear what the range of values that the variable can assume is. Is it the day of the week, so values range from 1 to 7, or the day of the month? The second problem is that the association of the numerical value to the day of the week is not immediately evident. In this case 3 means Wednesday, but it is not immediate. Finally, we could, by mistake, assign to the variable an invalid value, such as 8 or 0. In that case, the compiler will not signal any error, as it is not a syntax error, but a logic error.

A first solution could be to use simple macros to define type and days:

#define DAY_OF_THE_WEEK int

#define MONDAY 1
#define TUESDAY 2
#define WEDNESDAY 3
#define THURSDAY 4
#define FRIDAY 5
#define SATURDAY 6
#define SUNDAY 7

DAY_OF_THE_WEEK day;

day = WEDNESDAY;

Although it is a valid solution, it is not the optimal one for two reasons.

Firstly, macros are nothing but text substitutions. This means that the compiler will replace the text of the macro with its value wherever it is used. Therefore, in the debugging phase, we will never see the type DAY_OF_THE_WEEK but only int.

Furthermore, nowhere is it indicated that the macros from MONDAY to SUNDAY pertain to the same type. They are simply a list of numerical constants.

To overcome these problems, the C language provides enumerated types.

An enumerated type is a particular type of the C language for which possible values are listed (are enumerated). The developer must define both the name of the enumerated type and give a label to all possible values.

For example, wanting to define an enumerated type for the days of the week, we can write:

enum {
    MONDAY,
    TUESDAY,
    WEDNESDAY,
    THURSDAY,
    FRIDAY,
    SATURDAY,
    SUNDAY
} day;

day = WEDNESDAY;

The variable day is an enum variable and can assume exclusively the values defined in the list.

Definition

Enumerated Types

Enumerated types are a particular data type of the C language whose variables can only assume values defined within a list or set.

The syntax for defining an enumerated variable is the following:

enum {
    VALUE_1,
    VALUE_2,
    ...
    VALUE_N
} variable_name;

Although they have nothing in common with struct or union variables, enum variables are declared in a very similar way. The difference is that the constants present in the set must have a different name from any identifier at the same scope level. For example, the following code is not valid:

enum {
    MONDAY,
    TUESDAY,
    WEDNESDAY,
    THURSDAY,
    FRIDAY,
    SATURDAY,
    SUNDAY
} day;

int MONDAY = 1; /* Error: MONDAY is already defined */

Furthermore, the constants defined within an enum must follow the same visibility rules as variables. For example, the following code is not valid:

int function1() {
    enum {
        MONDAY,
        TUESDAY,
        WEDNESDAY,
        THURSDAY,
        FRIDAY,
        SATURDAY,
        SUNDAY
    } day;

    /* ... */
}

int function2() {
    int day = MONDAY; /* Error: MONDAY is not defined */
}

In this case, the constant MONDAY is not visible within function2. It is only visible within function1.

Tagged Enumerations and typedef

In the previous example, we defined an anonymous enumerated type. This means that the enum type does not have a name. This represents a problem in case, in multiple points of our code, we want to declare variables of the same enumerated type.

To overcome this problem, as in the case of struct and union, we can create tagged enumerations. These are type definitions that associate a name with an enumerated type. For example, we can write:

enum week_day {
    MONDAY,
    TUESDAY,
    WEDNESDAY,
    THURSDAY,
    FRIDAY,
    SATURDAY,
    SUNDAY
};

enum week_day day;

day = WEDNESDAY;

In this case, week_day is the name of the enumerated type. The variable day is of type enum week_day.

To declare a variable of this type we must always precede the name of the enumerated type with the keyword enum.

Another solution is to use the typedef construct to create an alias of the enumerated type. For example, we can write:

typedef enum {
    MONDAY,
    TUESDAY,
    WEDNESDAY,
    THURSDAY,
    FRIDAY,
    SATURDAY,
    SUNDAY
} week_day;

week_day day;

day = WEDNESDAY;

In this case, week_day is an alias for the anonymous enumerated type defined immediately after the keyword enum.

Definition

Definition of Enumerated Types

In C language, to assign a name to an enumerated type, one can proceed in two ways:

  1. Define a tagged enumerated type:

    enum type_name {
        VALUE_1,
        VALUE_2,
        ...
        VALUE_N
    };
    

    In that case, to declare a variable of that type, one must write enum type_name variable_name.

  2. Use typedef to create an alias of the enumerated type:

    typedef enum {
        VALUE_1,
        VALUE_2,
        ...
        VALUE_N
    } type_name;
    

    In that case, to declare a variable of that type, one must write type_name variable_name.

Relationship between Enumerates and Integers

From a semantic point of view, an enumerated type represents a new type in every respect.

Internally, however, the C language considers all enumerated types as integers.

Let's return to the example of the days of the week:

enum week_day {
    MONDAY,
    TUESDAY,
    WEDNESDAY,
    THURSDAY,
    FRIDAY,
    SATURDAY,
    SUNDAY
};

enum week_day day;

day = THURSDAY;

The compiler, internally, transforms the constants MONDAY, TUESDAY, etc. into integer values. In particular, the compiler starts from 0 and assigns an increasing value to each constant. Therefore, MONDAY will be 0, TUESDAY will be 1, etc.

Similarly, the variable day will internally contain the value 4 which corresponds to THURSDAY.

This correspondence between enumerates and integers goes to the point where we can assign to a variable of enumerated type an integer value and vice versa. Based on this, the following code is perfectly legal:

enum week_day day;

day = 4; /* THURSDAY */

int integer_day = THURSDAY;
Definition

Enumerated Types are Integers

In C language, enumerated types are considered as integers. This means that it is possible to assign to a variable of enumerated type an integer value and vice versa.

Furthermore, the constants defined within an enumerated type are considered as integer constants starting from 0.

enum {
    VALUE_1,       /* 0 */
    VALUE_2,       /* 1 */
    ...
    VALUE_N        /* N - 1 */
} variable_name;

The assignment of integer values to the constants of an enumerated type can be modified in a direct way.

Suppose, for example, we want to assign to the constant MONDAY the value 1, to TUESDAY the value 2, etc. We can write:

enum week_day {
    MONDAY = 1,
    TUESDAY = 2,
    WEDNESDAY = 3,
    THURSDAY = 4,
    FRIDAY = 5,
    SATURDAY = 6,
    SUNDAY = 7
};

In this way we have made explicit the values to be assigned to the constants. Furthermore, we can assign non-consecutive values:

enum week_day {
    MONDAY = 1,
    TUESDAY = 3,
    WEDNESDAY = 5,
    THURSDAY = 7,
    FRIDAY = 9,
    SATURDAY = 11,
    SUNDAY = 13
};

There are no constraints on the values that the constants of the enumerate can assume. We can, in fact, also assign the same values to two or more constants. This is useful when we want to create constants that are aliases of each other.

For example, we could create our enumerate of the days of the week in such a way that we have constants both in English and Italian:

enum week_day {
    MONDAY = 1,
    LUNEDI = 1,
    TUESDAY = 2,
    MARTEDI = 2,
    WEDNESDAY = 3,
    MERCOLEDI = 3,
    THURSDAY = 4,
    GIOVEDI = 4,
    FRIDAY = 5,
    VENERDI = 5,
    SATURDAY = 6,
    SABATO = 6,
    SUNDAY = 7,
    DOMENICA = 7
};

In this way, we can use indifferently MONDAY or LUNEDI to refer to the first day of the week.

Definition

Assignment of Values to the Constants of an Enumerate

In C language, it is possible to assign values to the constants of an enumerated type. This can be done in a direct way:

enum {
    CONSTANT_1 = 1,
    CONSTANT_2 = 2,
    CONSTANT_3 = 3,
    ...
    CONSTANT_N = N
};

The assignment of values to the constants of an enumerated type can also be done in a partial way. In doing so, the compiler's behavior is to automatically assign successive values starting from the last assigned value. For example:

enum week_day {
    MONDAY = 1,
    TUESDAY,        /* 2 */
    WEDNESDAY,      /* 3 */
    THURSDAY = 10,
    FRIDAY,       /* 11 */
    SATURDAY,        /* 12 */
    SUNDAY       /* 13 */
};

In the example, what happens is that the compiler assigns 1 to MONDAY; subsequently, since no value has been assigned to TUESDAY, the compiler assigns 2, that is the value immediately following the last assigned value. The same applies to WEDNESDAY. To THURSDAY is assigned 10 and, from that point on, the compiler assigns the successive values.

Definition

Assignment of Partial Values to the Constants of an Enumerate

In C language, it is possible to assign partial values to the constants of an enumerated type. In that case, the compiler will automatically assign the successive values starting from the last assigned value.

enum {
    CONSTANT_1 = 1,
    CONSTANT_2,     /* 2 */
    CONSTANT_3,     /* 3 */

    ...

    CONSTANT_N      /* N */
};
Note

Avoid Partial Assignments

Our advice on the style to adopt in assigning values to the constants of an enumerated type is to avoid partial assignments. In other words, either leave all constants without value, or assign values to all constants.

This is because, in case of partial assignments, we could introduce errors that are difficult to identify.

Being enumerated types, essentially, hidden integers, we can perform on them all the typical operations of integers.

For example:

enum week_day day = MONDAY;

day++; /* TUESDAY */

The great power also lies in the fact that we can use enumerates in a switch statement or in a conditional expression if. For example, we can create a simple function to print the day with a switch:

void print_day(enum week_day day) {
    switch (day) {
        case MONDAY:
            printf("Monday\n");
            break;
        case TUESDAY:
            printf("Tuesday\n");
            break;
        case WEDNESDAY:
            printf("Wednesday\n");
            break;
        case THURSDAY:
            printf("Thursday\n");
            break;
        case FRIDAY:
            printf("Friday\n");
            break;
        case SATURDAY:
            printf("Saturday\n");
            break;
        case SUNDAY:
            printf("Sunday\n");
            break;
        default:
            printf("Invalid day\n");
    }
}

Enumerates as Determinants of a Mixed Structure

In the previous lesson we studied unions and we saw how to use them to build Mixed Data Structures. These are structures that contain fields of different types and that can be used to represent heterogeneous data.

In the example of the lesson we created a mixed data structure that represented different geometric figures: circles, rectangles, squares and trapezoids. The resulting structure had this form:

#define CIRCLE 1
#define RECTANGLE 2
#define SQUARE 3
#define TRAPEZOID 4

struct geometric_shape {
    int type;

    union {
        struct {
            double radius;
        } circle;

        struct {
            double height;
            double base;
        } rectangle;

        struct {
            double side;
        } square;

        struct {
            double height;
            double major_base;
            double minor_base;
        } trapezoid;
    } data;
};

In this example, we introduced in the data structure a field named type that acts as a discriminant for the type of stored data. This field allows us to understand which of the fields of the union is active at a given moment.

In these cases, rather than using integers, we can use enumerated types that lend themselves better to the purpose. This makes the code more readable and safer. For example, we can write:

enum shape_type {
    CIRCLE,
    RECTANGLE,
    SQUARE,
    TRAPEZOID
};

struct geometric_shape {
    enum shape_type type;

    union {
        struct {
            double radius;
        } circle;

        struct {
            double height;
            double base;
        } rectangle;

        struct {
            double side;
        } square;

        struct {
            double height;
            double major_base;
            double minor_base;
        } trapezoid;
    } data;
};

In this way, the type field of the structure geometric_shape can only assume the values defined in the enumerate shape_type.

In Summary

In this lesson we studied the enumerated types of the C language.

A variable of enumerated type can only assume the values defined within a list. That is, the possible values it can assume are enumerated in brackets.

Enumerated types are considered as integers by the compiler. This means that we can assign to a variable of enumerated type an integer value and vice versa. Furthermore, we can use enumerates in all the typical operations of integers.

Finally, we saw how enumerates can be used as determinants of a mixed data structure. In that case, enumerates allow us to understand which field of the union is active at a given moment.