C безымянный enum. Как это работает
Как известно, перечисления - это тип который может содержать значения указанные программистом. Целочисленные именованные константы могут быть определены как члены перечисления. Например:
Enum { RED, GREEN, BLUE }; определяет три целочисленные константы и присваивает им значения. По умолчанию, значения присваиваются по порядку начиная с нуля, т.е. RED == 0 , GREEN == 1 и BLUE == 2 . Перечисление также может быть именованным: enum color { RED, GREEN, BLUE }; Каждое перечисление - это отдельный тип, и тип каждого члена перечисления - это само перечисление. Например RED имеет тип color . Объявление типа переменной как color , вместо обычного unsigned , может подсказать и программисту и компилятору о том как эта переменная должна быть использована. Например: void f(color c) { switch(c){ case RED: // do something break; case BLUE: // do something break; } }
В этом случае компилятор может выдать предупреждение о том, что обрабатываются только два значения color из трёх возможных.
Таким образом перечисления это:
Создание именованных констант с автоматическим увеличением значения константы
Предупреждения о возможных ошибках со стороны компилятора
Основные проблемы при использовании enum
На самом деле всё что выше - общие слова, которые нужны только для того чтобы те кто забрёл сюда по ошибке, хотя бы что-то из этой статьи вынесли. А мы сейчас поговорим о сложностях и хитростях с которыми приходится сталкиваться каждому кто более-менее юзает перечисления в нормальном девелопменте. Итак, с чем приходится сталкиваться:
1. Отображение значения перечисления в строку которая совпадает с именем члена перечисления, т.е. что-либо что для enum_map вернёт "RED" .
2. Итерация по членам перечисления и контроль выхода за границы. Т.е. сколько бы вы не добавляли новых элементов в перечисление, у вас всегда есть константа которая ровно на единицу больше последнего члена последовательности.
3. (Тем, кто не прошёл тест по шаблонам, можно не читать) Отображение run-time целочисленной переменной в compile-time переменную или тип (указатель на функцию с определённым значением параметра шаблона etc.).
Разберём вышеозначенные задачи в деталях.
Отображение членов перечисления в строки
Плохое решение: const char* const strs={"RED","GREEN","BLUE"}; void f(color c) { puts(strs[c]); }Оправданий для того чтобы использовать такой код может быть только два:
- Если стоит статическая проверка на равенство количества членов перечисления и количества элементов в массиве строк (о статических проверках можете прочитать у Александреску или дождаться моей следующей статьи, которая как раз будет написана по этой теме). Оправдание довольно слабое.
- Если строки могут не совпадать с названиями членов перечисления (редкий, на самом деле, случай). Т.е. что-то типа такого: const char* const strs={"Red as an egypt rose","Green peace","Blue Screen of Death"}; .
Уберите от экранов детей!
присутствуют сцены аморального и порнографического характера
Превратим то изящное перечисление, которое у нас было:
Enum color { RED, GREEN, BLUE };
вот в этого монстра Франкенштейна:
color.h
#include "enum_helper_pre.h" enumeration_begin (color) declare_member (RED) delimiter declare_member (GREEN) delimiter declare_member (BLUE) enumeration_end ; #include "enum_helper_post.h"
Выглядит пугающе, но на самом деле мы просто добавили возможность переопределить каждый элемент конструкции enum:
enum_helper_pre.h
#ifndef delimiter #define delimiter , #endif #ifndef enumeration_begin #define enumeration_begin (arg) enum arg { #endif #ifndef enumeration_end #ifdef last_enumerator #define enumeration_end delimiter last_enumerator } #else #define enumeration_end } #endif #endif #ifndef declare_member #define declare_member (arg) arg #endif #ifndef member_value #define member_value (arg) = arg #endif Думаю понятно что в конце для всех этих макросов нужно сделать #undef:
enum_helper_post.h
#undef delimiter #undef enumeration_begin #undef enumeration_end #undef last_enumerator #undef declare_member #undef member_value
Теперь если вам нужно заполнить некий массив строковыми представлениями членов перечисления, то выглядеть это будет так:
main.c
#include
Output
RED GREEN BLUE
Вуаля! Вот оно - три строчки кода и вы для любого перечисления, оформленного по нашим правилам, получаете массив из строковых представлений членов перечисления, независимо от количества членов перечисления. Любой человек который будет дебажить код по трейсам, в которых вместо безликих чисел будут стоять названия элементов, скажет вам огромное спасибо.
Итерация по членам перечисления и контроль выхода за границы
Опытный глаз заметит, что пример выше получился немного хромой, потому что перечислениям в крупных проектах свойственно расти с течением времени и верхнюю границу цикла приходится всё время переопределять. Например, если наше перечисление определяло компоненты цвета, то мы может добави в него компонент определяющий яркость цвета:
Enum color { RED, GREEN, BLUE, BRIGHTNESS };
Теперь в примере придётся изменить условие выполнение цикла на while(c <= BRIGHTNESS) . Если такой цикл один на всю программу, то в этом нет ничего страшного, но если подобный цикл встречается в десятке мест, то, рано или поздно, вы начнёте забывать все места где надо поменять предельное значение цикла, что приведёт к трудноуловимым ошибкам.
Другой пример, когда подобные проверки станут нашим кошмаром - валидация данных полученных из некоторого источника. Например сервер третьей стороны может передавать вам через сокет значения какого либо перечисления, и на каждое возможное значение вы выполняете различные действия. Представьте себе если администрация сервера добавила новый элемент в конец перечисления или же в сокет пришёл какой-либо мусор. Тогда вы заметите это лишь когда ваш клиент, слушающий сокет, отправит данные дальше и один из модулей, получив эти данные, рухнет. Решением в этом случае была бы проверка следующего вида:
If(c < RED || c > BRIGHTNESS) throw std::runtime_error("Wrong color received");
И опять же если вы добавите новый элемент в перечисление, вам придётся внести изменения во все файлы где есть такие проверки. Есть решение проще - добавить фиктивный член перечисления, с фиксированным именем, всегда являющийся посленим членом перечисления. С нашим определением файла color.h сделать это - проще простого:
main.c
#include
Output
RED GREEN BLUE BRIGHTNESS
Теперь вы можете добавлять сколько угодно членов в перечисление, не боясь ничего потерять (о неизменённых конструкциях switch вас предупредит компилятор).
Отображение run-time в compile-time
Я уже говорил что будут шаблоны?
Теперь представьте себе, что у вас есть семейство шаблонных функций вида template
Void f(color c)
{
switch(c)
{
case RED:
f
Вопрос только в том сколько элементов может быть в перечислении и, опять же, сколько файлов вам придётся поправить, если в перечислении что-то изменится. Поэтому проще создать карту указателей на все эти функции, и отображать входное значение в указатель на функцию:
Void f(color c)
{
if(c < RED || c >= COLOR_END)
return;
typedef void (*func_type)();
#define enumeration_begin(arg) func_type func_map={
#define declare_member(arg) f
Значительно проще, не правда ли?
Обещанный пример
Усложним задачу. Теперь нам нужно не только отображать член перечисления в строку, но и наоборот. И при этом RED == -2 и BLUE == 5 . Используя стандартные перечисления добиться результата можно лишь напрямую забив данные в карту отображений.
Решение, на самом деле, очень простое, как и всё этой статье:
color.h
#include "enum_helper_pre.h" enumeration_begin(color) declare_member(RED) member_value(-2) delimiter declare_member(GREEN) delimiter declare_member(BLUE) member_value(5) delimiter declare_member(BRIGHTNESS) enumeration_end; #include "enum_helper_post.h"
main.cpp
#include Output
RED
BLUE
-1
6
Собственно получилось простенько, но со вкусом. Таким образом придерживаясь немного громоздкого стиля описания перечислений, вы можете достичь невероятной гибкости при их использовании. Вот собственно и всё что я хотел здесь рассказать, если у кого-то возникнут вопросы - я с радостью на них отвечу. Буду рад если кому-то эта статья облегчит нелёгкую программистскую жизнь. Теги: Си перечисление, enum.
В
си выделен отдельный тип перечисление (enum), задающий набор всех возможных целочисленных значений переменной этого типа. Синтаксис перечисления Enum <имя> {
<имя поля 1>,
<имя поля 2>,
...
<имя поля N>
}; //здесь стоит;!
Например
#include В этой программе объявлено перечисление с именем Gender. Переменная типа enum Gender может принимать теперь только два значения – это MALE И FEMALE. По умолчанию, первое поле структуры принимает численное значение 0, следующее 1, следующее 2 и т.д. Можно задать нулевое значение явно:
#include Будут выведены значения 0 1 0 1 2. То есть, значение SYMBOL равно значению EXPRESSION, а NUMBER равно OPERATOR.
Если мы изменим программу и напишем Enum Token {
SYMBOL, //0
NUMBER, //1
EXPRESSION = 10, //10
OPERATOR, //11
UNDEFINED //12
};
То SYMBOL будет равно значению 0, NUMBER равно 1, EXPRESSION равно 10, OPERATOR равно 11, UNDEFINED равно 12. Принято писать имена полей перечисления, как и константы, заглавными буквами. Так как поля перечисления целого типа, то они могут быть использованы в операторе switch. Заметьте, что мы не можем присвоить переменной типа Token просто численное значение. Переменная является сущностью типа Token и принимает только значения полей перечисления. Тем не менее, переменной числу можно присвоить значение поля перечисления. Обычно перечисления используются в качестве набора именованных констант. Часто поступают следующим образом - создают массив строк, ассоциированных с полями перечисления. Например
#include Так как поля принимают численные значения, то они могут использоваться в качестве индекса массива строк. Команда exit(N) должна получать код ошибки,
отличный от нуля, потому что 0 - это плановое завершение без ошибки. Именно поэтому первое поле перечисления равно единице. Перечисления используются для большей типобезопасности и ограничения возможных значений переменной. Для того, чтобы не писать enum каждый раз, можно объявить новый тип. Делается это также, как и в случае структур. Typedef enum enumName {
FIELD1,
FIELD2
} Name;
Последнее обновление: 03.10.2018 Кроме примитивных типов данных в C# есть такой тип как enum или перечисление. Перечисления представляют набор логически связанных констант.
Объявление перечисления происходит с помощью оператора enum
. Далее идет название перечисления, после которого указывается тип перечисления - он обязательно
должен представлять целочисленный тип (byte, int, short, long). Если тип явным образом не указан, то по умолчанию используется тип int.
Затем идет список элементов перечисления через запятую: Enum Days
{
Monday,
Tuesday,
Wednesday,
Thursday,
Friday,
Saturday,
Sunday
}
enum Time: byte
{
Morning,
Afternoon,
Evening,
Night
}
В этих примерах каждому элементу перечисления присваивается целочисленное значение, причем первый элемент будет иметь значение 0,
второй - 1 и так далее. Мы можем также явным образом указать значения элементов, либо указав значение первого элемента: Enum Operation
{
Add = 1, // каждый следующий элемент по умолчанию увеличивается на единицу
Subtract, // этот элемент равен 2
Multiply, // равен 3
Divide // равен 4
}
Но можно и для всех элементов явным образом указать значения: Enum Operation
{
Add = 2,
Subtract = 4,
Multiply = 8,
Divide = 16
}
При этом контанты перечисления могут иметь одинаковые значения, либо даже можно присваивать одной константе значение другой константы: Enum Color
{
White = 1,
Black = 2,
Green = 2,
Blue = White // Blue = 1
}
Каждое перечисление фактически определяет новый тип данных. Затем в программе мы можем определить переменную этого типа и использовать ее: Enum Operation
{
Add = 1,
Subtract,
Multiply,
Divide
}
class Program
{
static void Main(string args)
{
Operation op;
op = Operation.Add;
Console.WriteLine(op); // Add
Console.ReadLine();
}
}
В программе мы можем присвоить значение этой переменной. При этом в качестве ее значения должна выступать одна из констант, определенных в данном перечислении. То есть несмотря на то, что каждая константа сопоставляется с определенным числом,
мы не можем присвоить ей числовое значение, например, Operation op = 1; . И также если мы будем выводить на консоль значение этой переменной, то мы получим
им константы, а не числовое значение. Если же необходимо получить числовое значение, то следует выполнить приведение к числовому типу: Operation op;
op = Operation.Multiply;
Console.WriteLine((int)op); // 3
Также стоит отметить, что перечисление необязательно определять внутри класса, можно и вне класса, но в пределах пространства имен: Enum Operation
{
Add = 1,
Subtract,
Multiply,
Divide
}
class Program
{
static void Main(string args)
{
Operation op;
op = Operation.Add;
Console.WriteLine(op); // Add
Console.ReadLine();
}
}
Зачастую переменная перечисления выступает в качестве хранилища состояния, в зависимости от которого производятся некоторые действия.
Так, рассмотрим применение перечисления на более реальном примере: Class Program
{
enum Operation
{
Add = 1,
Subtract,
Multiply,
Divide
}
static void MathOp(double x, double y, Operation op)
{
double result = 0.0;
switch (op)
{
case Operation.Add:
result = x + y;
break;
case Operation.Subtract:
result = x - y;
break;
case Operation.Multiply:
result = x * y;
break;
case Operation.Divide:
result = x / y;
break;
}
Console.WriteLine("Результат операции равен {0}", result);
}
static void Main(string args)
{
// Тип операции задаем с помощью константы Operation.Add, которая равна 1
MathOp(10, 5, Operation.Add);
// Тип операции задаем с помощью константы Operation.Multiply, которая равна 3
MathOp(11, 5, Operation.Multiply);
Console.ReadLine();
}
}
Здесь у нас имеется перечисление Operation, которое представляет арифметические операции. Также у нас определен метод MathOp, который
в качестве параметров принимает два числа и тип операции. В основном методе Main мы два раза вызываем процедуру MathOp, передав в нее
два числа и тип операции.Перечисляемый тип