Флаги, перечисление (C)

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

У меня есть несколько объектов, которые регистрируются как слушатели определенных событий. Какие события они регистрируют, зависит от переменной, которая отправляется им при их создании. Я думаю, что хороший способ сделать это - отправить побитовые ИЛИ связанные переменные, такие как: TAKES_DAMAGE | GRABBABLE | LIQUID и т.д. Затем в конструкторе объект может проверить, какие флаги установлены, и зарегистрировать его как слушателя для тех, которые есть.

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

enum
{
    TAKES_DAMAGE,/* (0) */
    GRABBABLE, /* (1) */
    LIQUID, /* (2) */
    SOME_OTHER /* (3) */
};

Затем отправка флага SOME_OTHER (3) будет таким же, как отправка GRABBABLE | ЖИДКОСТЬ, не так ли?

Как именно вы имеете дело с этим материалом?

Ответ 1

Ваше перечисление должно быть двух:

enum
{
    TAKES_DAMAGE = 1,
    GRABBABLE = 2,
    LIQUID = 4,
    SOME_OTHER = 8
};

Или более читаемым образом:

enum
{
    TAKES_DAMAGE = 1 << 0,
    GRABBABLE = 1 << 1,
    LIQUID = 1 << 2,
    SOME_OTHER = 1 << 3
};

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

if(myVar & GRABBABLE)
{
    // grabbable code
}

... Что работает, если значения перечисления выглядят следующим образом:

 TAKES_DAMAGE: 00000001
 GRABBABLE:    00000010
 LIQUID:       00000100
 SOME_OTHER:   00001000

Итак, скажем, вы установили myVar в GRABBABLE | TAKES_DAMAGE, здесь, как это работает, когда вам нужно проверить флаг GRABBABLE:

 myVar:     00000011
 GRABBABLE: 00000010 [AND]
 -------------------
            00000010 // non-zero => converts to true

Если вы установили myVar в LIQUID | SOME_OTHER, операция привела бы к:

 myVar:     00001100
 GRABBABLE: 00000010 [AND]
 -------------------
            00000000 // zero => converts to false

Ответ 2

другой способ хранения флагов - вообще не беспокоиться о базовом типе. при использовании перечисления значения перечисления сохраняются по умолчанию в unsigned int, который составляет 32 бита на общем компьютере. это дает вам всего 32 возможных флага: в то время как, конечно, много, есть случаи, когда этого недостаточно.

теперь вы можете определить свой флаг таким образом:

typedef struct
{
    int takes_damage : 1;
    int grabbable    : 1;
    int liquid       : 1;
    int some_other   : 1;
} flags;

если вы никогда не сталкивались с этим, часть ': 1' сообщает компилятору использовать только 1 бит для хранения этого элемента структуры.

теперь вы можете определить переменную для хранения флагов и работать с этими флагами:

flags myflags = {1,0,0,1}; // defines a variable holding a set of flags, with an initial value of takes_damage & some_other

myflags.liquid = 1; // change the flags to include the liquid

if ( myflags.takes_damage ) // test for one flag
    apply_damage();
if ( myflags.liquid && myflags.some_other ) // test for multiple flags
    show_strange_behavior();

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

Ответ 3

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

Ответ 4

Не можете ли вы просто установить значения в перечислении?

enum {
 TAKES_DAMAGE = 1,
 GRABBABLE    = 2,
 LIQUID       = 4
}

Затем, просто выполняйте бит-мудрый ИЛИ на них.

Ответ 5

Да. Вместо этого сделайте полномочия перечисления членами 2:

enum
{
    TAKES_DAMAGE = (1 << 0),
    GRABBABLE = (1 << 1),
    LIQUID = (1 << 2),
    SOME_OTHER = (1 << 3)
};

Ответ 6

вам нужно

enum
{
    TAKES_DAMAGE = 1,
    GRABBABLE = 2,
    LIQUID = 4,
    SOME_OTHER = 8
};