Урок 58. Перечисления (типы enum)

   ⁄ 

 Обновлено 5 мая 2017

  ⁄   

⁄  4

C++ позволяет программистам создавать свои собственные типы данных. Самый простой способ — с помощью перечислений. Перечисление (или перечисляемый тип) — это тип данных, где любое возможное значения (перечислитель) определяется как символьная константа. Объявить перечисление можно с помощью ключевого слова enum. Рассмотрим пример:

Объявление перечислений не требует выделения памяти. Только когда переменная перечисляемого типа определена (например, как переменная paint в примере выше), только тогда выделяется память для этой переменной.

Обратите внимание, каждый перечислитель разделяется запятой, а само перечисление заканчивается точкой с запятой.

До C++ 11, конечная запятая после последнего перечислителя (как после COLOR_PURPLE в примере выше) не разрешается (хотя многие компиляторы её все равно принимают). Однако, начиная с C++ 11, конечная запятая допускается.

Имена перечислений

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

Распространено добавление префиксов к перечислителям, например, ANIMAL_ или COLOR_, как для предотвращения конфликтов имен, так и в целях комментирования кода.

Значения перечислителей

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

Результат:

4

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

Обратите внимание, ANIMAL_HORSE и ANIMAL_ZEBRA имеют одинаковые значения. Хоть C++ это разрешает, присваивать одно значение нескольким перечислителям в одном перечислении мы не советуем.

Совет: Не присваивайте свои значения перечислителям.

Правило: Не присваивайте одинаковые значения двум перечислителям в одном enum, если на то нет веской причины.

Обработка перечислений. Ввод/вывод

Поскольку значениями перечислителей являются целые числа, то их можно присваивать целочисленным переменным. Это означает, что их также можно выводить в консоль (как переменные типа int).

Результат:

6

Компилятор не будет неявно преобразовывать целочисленное значение в значение перечислителя. Следующее вызовет ошибку компиляции:

Тем не менее, вы можете это сделать с помощью static_cast:

Компилятор также не позволит вам вводить перечисление, используя std::cin:

Однако вы можете ввести целое число и использовать static_cast, чтобы заставить компилятора поместить целочисленное значение в перечисляемый тип:

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

Как и в случае с константами, перечисления отображаются в отладчике, что делает их еще более полезными.

Вывод перечислителей

Как вы уже могли наблюдать, попытка вывести перечисляемое значение с помощью std::cout приводит к выводу целочисленного значения самого перечислителя (т.е. порядкового номера). Но как вывести значение перечислителя в виде текста? Один из способов — написать функцию с использованием стейтментов if:

Выделение памяти для перечислений. Предварительное объявление

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

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

Польза от перечислений

Перечисляемые типы невероятно полезны для документации кода и улучшения читабельности.

Например, функции часто возвращают целые числа обратно в caller, в качестве кодов ошибок, если что-то пошло не так внутри функции. Как правило, небольшие отрицательные числа используются для представления возможных кодов ошибок. Например:

Однако магические числа, как в примере выше, не очень эффективное решение. Альтернатива — использовать перечисления:

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

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

Или, если вы пишете функцию для сортировки группы значений:

Многие языки используют перечисления для определения логических значений. По сути, логическое значение — это простое перечисление всего лишь с двумя перечислителями: true и false! Однако в C++ значения true и false определены как ключевые слова вместо перечислителей.

Тест

1. Напишите перечисление для выбора между следующими типами монстров: ogre, goblin, skeleton, orc и troll.

2. Объявите переменную перечисляемого типа, который вы определили в задании 1, и присвойте ей тип ogre.

3. Правда или ложь. Перечислителям можно:

а) присваивать целочисленные значения;

б) не присваивать значения;

в) явно присваивать значения типа с плавающей запятой;

г) присваивать значения предыдущих перечислителей (например, COLOR_BLUE = COLOR_GRAY).

Перечислители могут быть:

д) отрицательными;

е) не уникальными.

Ответы

Ответ 1

Ответ 2

Ответ 3

а) Правда.

б) Правда. Перечислителю без значения будет неявно присвоено целочисленное значение предыдущего перечислителя + 1. Если предыдущего перечислителя нет, тогда присвоится значение 0.

в) Ложь.

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

д) Правда.

е) Правда.

Оценить статью:

Звёзд: 1Звёзд: 2Звёзд: 3Звёзд: 4Звёзд: 5 (6 оценок, среднее: 4,33 из 5)
Загрузка...
Поделиться в:
Подписаться на обновления:

Комментариев: 4

  1. Old G.B.:

    вот здесь я впервые понял что ничего не понял

    1. Li4ik:

      Не без этого. Попробуйте внимательнее перечитать этот урок, но уже не следующий день. Поищите дополнительную информацию в Интернете на эту тему, если уж никак не идёт.

  2. Илья:

    Здравствуйте.
    Есть вопрос:
    В примере с задачкой про GUN, ARBALET и SWORD программа не работает как надо, ведь функция getItemName всегда будет возвращать ARBALET, так как он равен 1 (GUN == 0, SWORD == 2). Если в типе данных enum ItemType всем присвоить 1, то выдает первое значение — GUN.
    Как это исправить? Спасибо.

    1. Li4ik:

      В примере функция getItemName возвращает Gun, так как мы передаем аргумент itemType, которому, предварительно в строке 26 (ItemType itemType(ITEMTYPE_GUN);) установили значение ITEMTYPE_GUN. Функция getItemName не будет работать без передаваемого аргумента, т.е. мы сами определяем, что будет выводиться на экран, указывая один из элементов структуры ItemType (ITEMTYPE_GUN, ITEMTYPE_ARBALET или ITEMTYPE_SWORD). Члены ItemType имеют значения от 0 до 2, но определение того, что будет выводить getItemName осуществляется в main, когда мы указываем аргумент. Как вы уже изменили код, что у вас программа выводит что-либо другое, кроме того, что вы указываете в строке 26 (ItemType itemType(ITEMTYPE_GUN);) — это уже под вопросом.

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *