enum в С++. Как работать и зачем нужно

Что такое enum в С++, зачем в С++ нужно enum?.. Подобные вопросы возникают у совсем новичков, изучающих язык программирования С или С++.
  • enum — это сгруппированный набор целочисленных констант. Называют это перечислением.
  • По большому счёту, перечисления — это преимущественно просто набор названий из законченной сборки (названия фигур шахмат, названия месяцев, названия дней недели). Могут быть и названия животных, имена людей, названия рас… Главное, чтобы выбранное множество названий было предопределено.
  • Перечисления используются в тех случаях, когда переменные создаваемого типа могут принимать заранее известное конечное (и, как правило, небольшое) множество значений.
Иногда бывает очень удобно объединить несколько констант в одну группу, обозначив каждую из констант символическим, удобочитаемым названием. Простейшим примером могут послужить названия цветов радуги, названия дней недели, названия карточных мастей и др.
  • Перечисления являются удобным для нас компромиссом, когда константное значение, которому присущ тип самое обычное целое число, нам разрешено писать словом (только английским словом).

Чтобы мы могли задействовать перечисление, нам, как вариант, нужно создать объект перечисления (можно и не создавать, но об этом ближе к середине или концу статьи). Как для структуры можно создать объект структуры, а для класса объект класса. Ведь мы, работая со сгруппированными переменными, работаем с объектами.

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


В объект перечисления можно присваивать любое значение из зародившего тот объект перечисления.

Т. е. после того как перечисление описано и от него порождён объект, объект, как и должен объект, может быть использован, при этом объект перечисления умеет присваивать в себя сгруппированные в перечислении обозначения.
В те времена, когда компиляторы С++ не имели встроенного типа bool, значение false заменялось обычным числовым нулём, а true заменялось единицей. Для повышения читаемости кода многие программисты обозначали должный быть булевым true названием перечислителя (внутри перечисления) true, а false как false.

ВНИМАНИЕ! Будьте бдительны, в листинге #4 написан код, который подходит для очень старых компиляторов, если у вас, например, Visual Studio, то этот код не сработает. Во-первых в современные компиляторы встроен тип bool, а во-вторых, заголовочные файлы в современных компиляторых будут другими, и вообще слишком многое изменилось. Код показан только для демонстрации, как имитирую bool там, где его нет.
  • Одним из неудобств для новичков оказывается то, что вытащить обозначение, описанное в перечислении, в виде своего собственного названия напрямую нельзя: поскольку тип каждого индивидуального участника перечисления — это целый тип, то число нельзя представить в виде строки без некоторого рода шаманства.
Каждый участник, описанный внутри перечисления, называется перечислителем. По умолчанию перечислителям присваиваются целочисленные значения, начиная с 0 для первого из них, 1 — для второго и т. д. У нас есть возможность повлиять на номера, даваемые перечислителям, но об этом чуть позднее.

Без шаманства мы можем выводить только целочисленное представление перечислителя, как было уже пояснено, потому что перечислитель сам по себе целочисленное значение. Поэтому на экране мы видим цифры, соответствующие порядку перечислителей, описанных в перечислении: RED — первый, GREEN — второй и BLUE третий.
Для пользователей, изучающих язык программирования С++ на старых компиляторах, предлагаю решить простую задачу: написать программу, проверяющую школьника младших классов на умение складывать числа. Смотрите пример листинга #4, если получится, то усложните задачу: программа должна проверять и складывание, и умножение, и деление и вычитание. В конце статьи я покажу решение. Но тем не менее, сейчас попробуйте решить самостоятельно. Пользователи современных компиляторов тоже могут попробовать написать такое дело, но название перечислению давайте, например, BOOL, чтобы оно не конфликтовало с зарезервированным компилятором словом.
В объекты, порождённые перечисленем, можно присваивать значения. На присваиваемые значения накладываются некоторые ограничения:

  • Единственными допустимыми значениями, которые можно присвоить в объект перечисления без необходимости приведения типов, являются перечислители перечисления. (Не совсем так, но пока что считайте так, если перечислители инициализируются значениями, то в объект перечисления можно присваивать значения, входящие в некоторый диапазон, будет описано дальше)

Если не понимаете, почему нельзя, то поясняю. color — это объект перечисления, хоть он и записывает в себя целочисленные значения, сам по себе он не целочисленный тип, а тип-перечисление. Два абсолютно одинаковых класса в С++ считаются разными типами данных, тут схожая ситуация. Поэтому объект перечисления отвергает прямые целочисленные значения и принимает на хранение только перечислители. Это может быть некоторое время непонятно, но при этом очень удобно. При этом, в листинге #6 объект перечисления PALETTE_COLOR способен принять в себя только 3 значения, любой перечислитель своего перечисления.
Некоторые компиляторы закрывают глаза на попытку ввода в объект перечисления некорректного значения. Если ваш компилятор такой добрый, то это не очень хорошо.

  • Для максимальной переносимости вы должны трактовать присваивание объектам типа enum значений, не входящих в набор собранных вовнутрь enum перечислитилей, как ошибку.
Для перечислений определена только операция присваивания, такие операции как сложение, вычитание и др. не предусмотрены.

Чтобы разрулить некоторую неясность, вам нужно разделить тип PALETTE_COLOR и тип int. Хоть объект типа PALETTE_COLOR и похож на объект int, это не объект int, а объект PALETTE_COLOR, а PALETTE_COLOR в себя запихивать значения int не умеет.
  • Некоторые реализации разрешают перечислениям не только операцию присваивания, но эти разрешения опасны из-за возможного появления трудноуловимых ошибок. Даже если ваш компилятор позволяет вам использовать не только присваивание, для перечислений стоит считать допустимой только операцию присваивания.
На практике, разрешение, например, инкремента, может поспособствовать записи в объект перечисления значения не входящего в значения перечислителей, а это то, чего не должно быть в принципе.

Несмотря на то, что в объекты перечислений нельзя записывать прямые целые числа, в численные переменные записывать значения из объекта, хранящего перечислитель, записывать можно. Это близко к тому, что в объекты пользовательских классов (пользовательских типов) нельзя записывать значения других типов, если классы (пользовательские типы) не научить правильной записи.

Если ваш компилятор даёт скомпилировать показанный код, то просто компилятор добрый. Ограничение есть ограничение и нарушение показанных ограничений по большому счёту ошибка. Лучше ограничение не нарушать.
Если у вас слишком большое желание записать в объект перечисления значение из целого значения, то нужно использовать явное приведение типа. Поскольку у нас проблема в восприятии объектом типов, нужно подсказать нужные типы.

Поскольку внутри перечисления, описанного в листинге #9 находится только 3 значения: 0, 1, 2, — то любое другое значение, записываемое в объект нашего перечисления, приведёт к непрогнозируемому поведению программы. Непрогнозируемое поведение программы страшная штука и иногда, иногда как правило, обнаруживается в самый ненужный момент. Т. е. программа может долгое время показывать, что всё хорошо, а потом, когда очень не надо, сработать неверно.
В листинге #9 используется приведение типов в С++ стиле. Можно использовать другие стили приведения типов.

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

Здесь в моей статье впервые приводится использование очень удобного свойства перечислений: использование перечислителей в switch. Этот приём находит частое применение на практике. Поскольку показанный пример может быть непонятен некоторым читателям, я упрощу код и ещё раз покажу. Но в любом случае для понимания приводимого листингов #10 нужно уметь генерировать случайные или псевдослучайные числа.

Надеюсь, что мой труд помогает вам хорошо понимать тему использования и синтаксис enum. Где-то на заре этой статьи упоминалось, что мы можем влиять на значения, задаваемые перечислителям. По умолчанию перечислителям в значения даются порядковые номера, начиная с нуля. Сейчас мы будем менять эти номера, т. е. устанавливать свои значения перечислителям. Тем не менее, на практике лучше всего выбирать первому перечислителю нулевое значение.


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

Здесь смысл очень простой: вы можете давать перечислителю значение. Каждый последующий неинициализированный перечислитель увеличивается на единицу по сравнению с предыдущим. Если неицианилизирован самый первый перечислитель, то он инициализируется нулём (по умолчанию).

Для нескольких перечислителей допускается устанавливать одинаковое значение.


Расширяем наш кругозор.

  • Изначально правильными значениями перечислений являются лишь те, что названы в объявлении. Однако C++ расширяет список допустимых значений, которые могут быть присвоены перечислимым переменным, за счет использования приведения типа. Каждое перечисление имеет диапазон, и с помощью приведения к типу переменной перечисления можно присвоить любое целочисленное значение в пределах этого диапазона, даже если данное значение не равно ни одному из перечислителей.
Если говорить совсем просто, то переменным-объектам с типом перечисление даётся память, и любое значение, умещающееся в отведённую для переменной-объекта память, считается разрешённым для переменной-объекта.


  • Диапазон определяется следующим образом. Для нахождения верхнего предела выбирается перечислитель с максимальным значением. Затем ищется наименьшее число, являющееся степенью двойки, которое больше этого максимального значения, и из него вычитается единица. (Например, максимальное значение bigstep, как определено выше, равно 101. Минимальное число, представляющее степень двойки, которое больше 101, равно 128, поэтому верхним пределом диапазона будет 127.)
    Для нахождения минимального предела выбирается минимальное значение перечислителя. Если оно равно 0 или больше, то нижним пределом диапазона будет 0. Если же минимальное значение перечислителя отрицательное, используется такой же подход, как при вычислении верхнего предела, но со знаком минус. (Например, если минимальный перечислитель равен -6, то следующей степенью двойки будет -8, и нижний предел получается равным -7.)
    Идея состоит в том, чтобы компилятор мог выяснить, сколько места необходимо для хранения перечисления. Он может использовать от 1 байт или менее для перечислений с небольшим диапазоном, и до 4 байт — для перечислений со значениями типа long.
    Стандарт C++11 расширяет перечисления добавлением формы, которая называется ограниченным перечислением.

Пример, похожий на пример, даваемый в описании книги Лафоре (подсчёт количества слов в строке) с задействованием enum:

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

И расширенный вариант. Более интересный. Его конечно можно улучшить, я постарался выдать максимально просто.

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

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

  • В ранних версиях C++ элементам перечислений можно было присваивать только значения типа int (или неявно преобразуемые к int), но теперь это ограничение снято, и также можно использовать значения типа long или даже long long.
Хороший стиль:

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

Использованные материалы:

Язык программирования C++. Лекции и упражнения. 6-е изд (Стивен Прата)
Объектно-ориентированное программирование в С++.  (Роберт Лафоре)
Программирование. Принципы и практика с использованием C++   (Бьерн Страуструп)
Как программировать на C++ (Пол Дейтел, Харви Дейтел)

11 комментариев на «“enum в С++. Как работать и зачем нужно”»

  1. Andru_X:

    Правильней будет

    Тогда false==0, true==1

    • Да, если в привычном понимании булевых функций, то это правильнее. И вообще так правильнее, чтобы другим было понятно.
      С другой стороны — всё это условности, и ничто не мешать делать что-то для себя своим манером. Главное понимать механизм.

      • tetraksis:

        Мне прислали вот это:» E-NUM оповещает вас об успешной аутентификации.»
        Что это значит, как мне этим пользоваться. Только без терминов если можно. Мне 70 лет я уже не успел их освоить, мне пользоваться как? С уважением.

        • это сообщение к программированию не относится.
          к примеру есть интернет кошельки, на которых хранят реальные деньги. Вот сервис E-num — это их служба.
          это служба, которая предполагает защиту от воровства паролей (авторизация только по мобильному телефону, например)

          сами по себе они вам ничего не высылают. Если вы где-то регистрируетесь, то может быть так, что сервис, в котором вы зарегестрировались использует эту службу, сервис E-Num, тогда они во время регистрации заставляют заполнять поля, нужные этой службе безопасности.
          Откину, кошельки, заменю их почтовым ящиком.
          Вы зарегистрировали почтовый ящик, во время регистрации поставили галочку «использовать E-NUM», после регистрации, например, при каждом входе кроме логина и пароля будет запрашиваться номер телефона, на номер телефона приходить СМС с каки-то там кодом, который надо ввести в соответствующее место на странице входа. Ввели правильно — вход выпонится и вам приходит сообщение об успешной аутентификации. (типа все в порядке, мы убедились, что это вы, а не злоумышленник)
          ==========================

          Но есть вероятность того, что вам приходит сообщение от злоумышленников. Если видите в сообщении текст, требующий отправки смс — просто игнорируйте такие сообщения. — это мошенники.

  2. Sergio:

    27 строка последнего примера — забыли перенос, поэтому закомментили нужный кусок кода…

  3. Andrey:

    Выводим все строковые значения на экран
    for (i=0;i<1;i++)
       {
         cout<<flag_name[i]<<» «; //Для вывода на экран мы обращаемся к массиву.
        }

    выведед только первый элемент массива, для вывода всех значений либо изменить вот это i<1 на i<2, либо использовать i<=1

    Автор сайта отвечает:
    Исправлено

  4. Alex:

    flag = 0;

    странно, приводить к типу boolean не нужно?

    flag = boolean(0);

    int не приводится автоматом к enum

  5. Maskit:

    Подскажите, как вывести перечисление в виде string?

    enum A
    {
    one,
    two,
    three
    };

    void main(){

    A numbers;
    std::cout << std::to_string(numbers( 2 ));   // должно вывести «tree»
    }

    • 1. to_string не преобразует цифры в их строковые названия. Поэтому строки как слова Вы не увидите.
      2. Вы вызываете функцию number, отдавая один аргумент, цифру 2. Функции этой вообще нет. Объект перечисления при этом не используется вообще.
      3. Как вариант, можно использовать конструктор перечисления A, но для этого может быть нужно использовать название перечисления, но не объекта.

    • При этом есть вероятность, что оно Вам вообще не надо, потому что сделать можно вот так:

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

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

Поиск

 
     

Случайная книга в электронном формате

https://www.litres.ru/oleg-valpa/borland-c-builder-ekspress-kurs/?lfrom=15589587
Яндекс.Метрика