Язык Си. Перечисления.

Перечисления(Enum) в языке Си.

enum - перечислимый тип.
Перечисления используются для объявления символических имен, которые являются целочисленными константами. То есть типом перечисления, по факту является целочисленным типом, и эти константы можно использоваться везде где можно использовать целочисленные типы.
Перечисления улучшают читабельность кода, позволяют избегать использование "магических чисел". Ниже 3 варианта создания перечислений.
  {
    // создаем перечисление с именем дескриптора eDirection
    enum eDirection {RIGHT, LEFT, DOWN, UP};

    // создаем переменную dir с перечислимым типом eDirection
    enum eDirection dir;

    // присваиваем переменной dir константу UP
    dir = UP;
  }

  {
    // создаем перечисление без дескриптора, просто набор констант
    enum {RIGHT, LEFT, DOWN, UP};

    // создаем переменную dir с перечислимым типом int
    int dir;

    // присваиваем переменной dir константу UP
    dir = UP;
  }

  {
    // создаем тип перечисления с именем eDirection
    typedef enum {RIGHT, LEFT, DOWN, UP} eDirection;

    // создаем переменную dir с перечислимым типом eDirection
    eDirection dir;

    // присваиваем переменной dir константу UP
    dir = UP;
  }

Конечный тип перечисления зависит от реализации компилятора, также все может зависеть от того какие значения имеют константы. Если значение первой константы не указано, оно по умолчанию равно 0, все следующие за ней, на 1 больше предыдущей.
  {
    enum eDirection 
    {
      RIGHT,  // по умолчанию = 0
      LEFT,   // = 1
      DOWN,   // = 2
      UP      // = 3
    };
  }

  {
    enum eDirection 
    {
      RIGHT,    // по умолчанию = 0
      LEFT = 4, // = 4
      DOWN,     // = 5
      UP        // = 6
    };
  }

  {
    enum eDirection 
    {
      RIGHT = 8,  // = 8
      LEFT,       // = 9
      DOWN = 100, // = 100
      UP          // = 101
    };
  }
В примерах выше, компилятор может принять решение, выделить под хранение этих констант тип unsigned char(0...255) или char(-128...127), так как все эти константы лежат в границах для этих типов, а может выделить и тип int(все рависит от реализации того или иного компилятора).
Так как мы уже знаем, что перечисления используются в основном для повышение читаемости кода, то в примерах выше мы видим что вместо "магических значений" 1, 2, 3, ...
мы используем буквенные обозначения, которые нам говорят о направлении, и в коде, когда мы видем константу DOWN мы понимает что она означает, но когда видим к примеру число 3, то можно только гадать что это(направление, цвет, коэффициент).
Ниже в примерах 3 варианта решения одной и той же задачи(описать состояние игровой единицы и определить функцию для выполнения действий игровой единицей)
// 0 - STOP
// 1 - MOVE
// 2 - RELOAD
// 3 - FIRE
int gUnitState = 0;

void unit_doing()
{
  switch(gUnitState)
  {
    case 0 : { break; }
    case 1 : { break; }
    case 2 : { break; }
    case 3 : { break; }
  }
}

int main()
{
  while(1)
  {
    //some logic ...

    unit_doing();

    // draw ...
  }
  return 0;
}
Этот пример совсем плох. Так как тут вовсю используются "магические числа", нужно везде проставлять комментарии о том что это за числа. Легко сделать ошибку. Легко забыть обработать какое-то состояние. Такой код следует избегать, представьте что у вас больше 20-ти состояний, и все они принимают участие в вычислениях в различных функциях...

// untit state
#define STOP    0
#define MOVE    1
#define RELOAD  2
#define FIRE    3

int gUnitState = STOP;

void unit_doing()
{
  switch(gUnitState)
  {
    case STOP   : { break; }
    case MOVE   : { break; }
    case RELOAD : { break; }
  }
}

int main()
{
  while(1)
  {
    // some logic ...

    unit_doing();

    // draw ...
  }
  return 0;
}
Этот пример немного лучше, но все по прежнему легко забыть обработать новое состояние, к примеру было всего 3 состояния, далее, со временем добавилось четвертое состояние FIRE, но мы его забыли обработать, и компилятор об этом ничего не сказал, да и не должен на самом деле :-)

typedef enum
{
  STOP
 ,MOVE
 ,RELOAD
 ,FIRE
}eUnitState;

eUnitState gUnitState = STOP;

void unit_doing()
{
  switch(gUnitState)
  {
    case STOP:  { break; }
    case MOVE:  { break; }
    case RELOAD:{ break; }
    case 12:    { break; }
  }
}

int main()
{
  while(1)
  {
    // some logic ...

    unit_doing();

    // draw ...
  }
  return 0;
}
Этот пример гораздо лучше, так как здесь будет 2 предупреждения от компилятора:
warning: enumeration value ‘FIRE’ not handled in switch [-Wswitch]
warning: case value ‘12’ not in enumerated type ‘eUnitState {aka enum <anonymous>}’ [-Wswitch]
О том что забыли обработать состояние FIRE, и то что обрабатываем какое-то непонятное состояние, которое не входит в eUnitState. Аналогичные оплошности легко допустить в двух примерах ранее, и не знать об этом :-)
Из примеров выше, мы увидели что перечисления очень хорошо работают в связке с конструкцией switch.

Ниже еще один из вариантов применения перечисления.
#include <stdio.h>

typedef enum
{
  TO_FT
 ,TO_YARD
 ,TO_INCH
 ,END_UNITS
}eMetterTo;

float coefficients[END_UNITS];

void init_coefficients()
{
  for(int i = TO_FT; i < END_UNITS; ++i)
  {
    switch(i)
    {
      case TO_FT:
      {
        coefficients[i] = 3.28083f;
        break;
      }
      case TO_YARD:
      {
        coefficients[i] = 1.09361f;
        break;
      }
      case TO_INCH:
      {
        coefficients[i] = 39.370078f;
        break;
      }
      case END_UNITS:
        break;
    }
  }
}

float metterTo(float aMettersValue, eMetterTo aUnitType)
{
  return aMettersValue * coefficients[aUnitType];
}

int main()
{
  init_coefficients();

  printf("1 metter = %f ft\n", metterTo(1.f, TO_FT));
  printf("1 metter = %f yards\n", metterTo(1.f, TO_YARD));
  printf("1 metter = %f inchs\n", metterTo(1.f, TO_INCH));

  return 0;
}

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



!ВОЗМОЖНЫ ДОПОЛНЕНИЯ!



Комментарии