Больше не используйте перечисления C ++, как в C

Перечисление C ++ - это определяемый пользователем тип, который позволяет нам давать текстовые имена для целочисленных значений. Используя перечисления, вы можете повысить читаемость кода и снизить вероятность ошибок из-за использования жестко запрограммированных значений. Я расскажу здесь о различных типах перечислений и их использовании в C ++. Обратите внимание, что требуется как минимум C ++ 11.

В C ++ существует два типа перечислений - перечисления с областью действия и перечисления без области действия. Перечисления без области видимости - также называемые простыми перечислениями - очень похожи на перечисления языка C. Мы начнем с демонстрации их использования в стиле C, а затем представим новые функции, добавленные к ним на C ++.

Перечисления без области видимости

Вы можете использовать перечисления в C ++ так же, как и в C. Следующий код создает перечисление, которое представляет цвета дорожных знаков.

// Creating an enum for the traffic sign light colors
enum trafficSignColors {
    red,
    yellow,
    green,
};

У счетчиков есть целочисленный тип, который, по крайней мере, может содержать наибольшее значение счетчиков. По умолчанию первый перечислитель будет иметь значение 0, и значение будет увеличиваться на 1 для каждого последующего перечислителя. Вы можете изменить начальное значение или даже явно присвоить значения всем перечислителям.

// Creating an enum for the traffic sign light colors
enum trafficSignColors {
    red = 10, // Initial value of 10
    yellow, // yellow = 11 (initial + 1)
    green = 5, // Values don’t need to be in order
};

Используя перечисления, вы можете избежать использования жестко запрограммированных значений, которые представляют цвета дорожных знаков. Вы также можете использовать определения предварительной обработки для достижения этого, но перечисления могут улучшить читаемость кода, создав тип, который представляет состояние дорожного знака.

typedef enum {
    red,
    yellow,
    green,
} trafficSignState;

Теперь вы можете использовать новый тип для создания переменных в качестве типа входного параметра функции или типа возвращаемого значения.

/* A function to print the name of the traffic sign state */
void printTrafficSignState(trafficSignState tsColor) {
    switch(tsColor) {
        case red:
            cout << “Traffic sign red state” << endl;
            break;
        case yellow:
            cout << “Traffic sign yellow state” << endl;
            break;
        case green:
            cout << “Traffic sign green state” << endl;
            break;
        default:
            cout << “Traffic sign unknown state” << endl;
    }
}

Этот тип перечисления называется без области действия, потому что перечислители - именованные константы - являются частью их области действия перечисления. Ключевое слово enum не создает новую суб-область видимости, а это означает, что у нас не может быть двух перечислителей с одинаковым именем, даже если они принадлежат к разным перечислениям.

// Creating an enum for the traffic sign light colors
enum trafficSignColors {
    red,
    yellow,
    green,
};
// Creating an enum for the colors
enum colors {
    red, // Invalid, red is already defined in this scope
    black,
    white,
}

Здесь использование красного цвета в перечислении цветов недопустимо, поскольку красный цвет уже является частью текущей области после определения в trafficSignState.

Обычные перечисления имеют неявное преобразование по умолчанию в целое число, но обратное неверно, и вам нужно использовать явное приведение. Следующий код повторно реализует функцию printTrafficSignState, используя целочисленное значение в качестве параметра вместо типа trafficSignState.

enum trafficSignColors {
    red,
    yellow,
    green,
};
/* A function to print the name of the traffic sign state */
void printTrafficSignState(int tsColor) {
    switch(tsColor) {
        case red:
            cout << “Traffic sign red state” << endl;
            break;
        case yellow:
            cout << “Traffic sign yellow state” << endl;
            break;
        case green:
            cout << “Traffic sign green state” << endl;
            break;
        default:
            cout << “Traffic sign unknown state” << endl;
    }
}
int main() {
    int tsColor = green;
    printTrafficSignState(tsColor);
    return(0);
}

Представьте себе случай, когда у вас есть некоторый объект класса / структуры с одним из его полей некоторого типа перечисления, и вы переносите этот объект в двоичном формате между двумя приложениями, созданными с помощью разных компиляторов или для разных архитектур. Проблема здесь в том, что объект может иметь разный размер и внутренние смещения в двух приложениях, что приведет к загрузке полученных двоичных данных в неправильном формате. C ++ предоставляет решение для этого, позволяя определить базовый тип перечислителей, как в этой строке ниже typedef enum: int.

/* We can determine the base type of the enum. This is new to C++.
We can’t use red, yellow and green as they are already used */
typedef enum : int {
    red_t,
    yellow_t,
    green_t,
} trafficSignState_t;

Еще одна функция C ++, которую можно использовать с простыми перечислениями, - это перегрузка оператора C ++. Это может упростить жизнь программиста, сделать код более чистым и избавить от необходимости иметь дело с счетчиками как целочисленными значениями.

/* Overloading the prefix ++ operator for the trafficSignState enum */
trafficSignState &operator++(trafficSignState& orig) {
    /* Enum can be implicitally converted to integers, integers needs
    to be explicitally converted to enums */
    orig = static_cast<trafficSignState>(orig + 1);
    if(orig > green) {
        orig = red;
    }
    return(orig);
}

Проверьте этот полный пример, который показывает основное использование простых перечислений:

#include <iostream>
#include <string>
using namespace std;
/* Define the traffic sign enum as a type. This is the same way
we can create an enum in C */
typedef enum {
    red,
    yellow,
    green,
} trafficSignState;
/* Overloading the prefix ++ operator for the trafficSignState enum */
trafficSignState &operator++(trafficSignState& orig) {
    /* Enum can be implicitally converted to integers, integers needs
    to be explicitally converted to enums */
    orig = static_cast<trafficSignState>(orig + 1);
    if(orig > green) {
        orig = red;
    }
    return(orig);
}
/* We can determine the base type of the enum. This is new to C++.
We can’t use red, yellow and green as they are already used */
typedef enum : int {
    red_t,
    yellow_t,
    green_t,
} trafficSignState_t;
/* Override the stream operator */
ostream &operator<<(ostream &output, const trafficSignState_t &state) {
    string stateStr;
    switch(state) {
        case red:
            stateStr = “red”;
            break;
        case yellow:
            stateStr = “yellow”;
            break;
        case green:
            stateStr = “green”;
            break;
        default:
            stateStr = “invalid”;
    }
    output << stateStr;
    return output;
}
/* A function to print the name of the traffic sign state */
void printTrafficSignState(trafficSignState tsColor) {
    switch(tsColor) {
        case red:
            cout << “Traffic sign red state” << endl;
            break;
        case yellow:
            cout << “Traffic sign yellow state” << endl;
            break;
        case green:
            cout << “Traffic sign green state” << endl;
            break;
        default:
            cout << “Traffic sign unknown state” << endl;
    }
}
int main()
{
    /* Create a variable to hold the traffic sign state */
    trafficSignState tsState = green;
    /* Pass the value to be printed */
    printTrafficSignState(tsState);
    /* Unscoped enums can be implicitily converted to integer types */
    cout << “Traffic sign state value: “ << tsState << endl;
    int stateAsInteger = ++tsState;
    cout << “Traffic sign state value again: “ << stateAsInteger << endl;
    /* Using stram overloaded operator */
    trafficSignState_t tsState_2 = green_t;
    cout << “Final traffic sign state: “ << tsState_2 << endl;
    return 0;
}

Перечисления с областью видимости

В приведенных выше примерах простого перечисления мы не можем использовать одно и то же имя для перечислителей из разных перечислений, поскольку все они находятся в одной области. Перечисление с областью действия решает эту проблему, добавляя перечислители в новую область, к которой необходимо обращаться с использованием имен перечислений. Единственная разница между объявлением enum с ограниченным и незаданным диапазоном состоит в том, что вам нужно использовать ключевое слово class или struct после ключевого слова enum.

/* Scoped enum, we can use either enum class or enum struct */
enum class TraficSignState {
    red,
    yellow,
    green,
};
/* red, yellow and green can be used again normally */
enum class colors: int {
    red,
    black,
    white,
}

Обратите внимание, что имя красного перечислителя используется в обоих перечислениях без конфликтов. Перечисления с областью видимости также устраняют необходимость использования typedef для создания нового типа, поскольку перечисление с областью видимости может использоваться непосредственно как параметр функции или возвращаемый тип.

void printTrafficSignState(TraficSignState tsColor) {
    switch(tsColor) {
        /* Enum name should be used to access enum value */
        case TraficSignState::red:
            cout << “Traffic sign red state” << endl;
            break;
        case TraficSignState::yellow:
            cout << “Traffic sign yellow state” << endl;
            break;
        case TraficSignState::green:
            cout << “Traffic sign green state” << endl;
            break;
        default:
            cout << “Traffic sign unknown state” << endl;
    }
}

В отличие от простых перечислений, перечисления с ограниченной областью видимости не имеют неявных преобразований в целые числа, и вам необходимо использовать явное приведение для преобразования. Это обеспечивает безопасность типов и делает наш код менее подверженным ошибкам. Проверьте следующий код, чтобы получить полный пример перечислений с заданной областью

#include <iostream>
using namespace std;
/* Scoped enum, we can use either enum class or enum struct */
enum class TraficSignState {
    red,
    yellow,
    green,
};
/* red, yellow and green can be used again normally */
enum class TraficSignState_t : int {
    red,
    yellow,
    green,
};
void printTrafficSignState(TraficSignState tsColor) {
    switch(tsColor) {
        /* Enum name should be used to access enum value */
        case TraficSignState::red:
            cout << “Traffic sign red state” << endl;
            break;
        case TraficSignState::yellow:
            cout << “Traffic sign yellow state” << endl;
            break;
        case TraficSignState::green:
            cout << “Traffic sign green state” << endl;
            break;
        default:
            cout << “Traffic sign unknown state” << endl;
    }
}
int main()
{
    /* Create a variable to hold the traffic sign state */
    TraficSignState tsState = TraficSignState::yellow;
    /* Pass the value to be printed */
    printTrafficSignState(tsState);
    /* Scoped enums need an explicit cast and will not compile without it */
    cout << “Traffic sign state value: “ << static_cast<int>(tsState) << endl;
    TraficSignState_t tsState_2 = TraficSignState_t::yellow;
    return 0;
}

Вывод

В заключение, enum - очень полезный определяемый пользователем тип, который предоставляют нам как C, так и C ++. Знание того, как его правильно использовать и что C ++ добавляет к нему, может улучшить читаемость нашего кода и помочь уменьшить количество ошибок. Вы можете использовать простые перечисления в ситуациях, когда требуется только перечисление, подобное C, но вы можете улучшить читаемость кода, безопасность типов и избежать конфликтов имен с помощью перечислений с ограниченной областью видимости.

использованная литература

• Язык программирования C ++ (4-е издание), Бьярн Страуструп

• Экскурсия по C ++ (2-е издание), Бьярн Страуструп

• cppreference.com

Первоначально опубликовано на https://blog.nipraas.com 21 июня 2020 г.