2009-10-27 19 views
11

io non sono molto utilizzati per la programmazione con le bandiere, ma penso di aver appena trovato una situazione in cui sarebbero utili:Bandiere, enum (C)

Ho un paio di oggetti che si registrano come ascoltatori di determinati eventi. Gli eventi per cui si registrano dipendono da una variabile che viene inviata loro quando sono costruiti. Penso che un buon modo per farlo sarebbe quello di inviare variabili bitwise o collegate, come ad esempio: TAKES_DAMAGE | GRABBABLE | LIQUID, ecc. Quindi, nel costruttore, l'oggetto può controllare quali flag sono impostati e registrarsi come listener per quelli che sono.

Ma questo è il posto dove mi confondo. Preferibilmente, le bandiere sarebbero in un enum. Ma anche questo è un problema. Se abbiamo ottenuto questi flag:

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

inviando poi il SOME_OTHER bandiera (3) sarà lo stesso come l'invio di afferrabili | LIQUIDO, non è così?

Come si gestisce esattamente questa roba?

risposta

45

vostro enumerazione ha bisogno di essere potenze di due:

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

O in un modo più leggibile:

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

Perché? Perché si vuole essere in grado di combinare le bandiere, senza sovrapposizioni, e anche essere in grado di fare questo:

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

... Quale funziona se i valori di enumerazione simile a questa:

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

Quindi, dici che hai impostato myVar-GRABBABLE | TAKES_DAMAGE, ecco come funziona quando è necessario controllare per la bandiera afferrabili:

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

Se desideri impostare myVar-LIQUID | SOME_OTHER, l'operazione avrebbe comportato:

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

@jldupont - Confido che intendessi "operatore shiFt"? 8v) –

+0

@jldupont Penso che manchi una lettera ... – Greg

+0

Non credo di aver ricevuto 5 risposte contemporaneamente. o.o Grazie per l'eccellente spiegazione e soluzione. – quano

4

Si dovrebbero rendere i flag solo potenze di due, cioè ognuno è un po 'in qualsiasi tipo di dati in cui si sta memorizzando questo, e nulla si sovrappone quando si esegue un OR bit a bit.

3

è necessario

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

Penso che sia necessario correggere la sintassi su questo. (Lo so, lo stesso errore era nella domanda, ma ora è stato risolto.) –

+0

@fred larson, corretto :-) – Fredou

4

non puoi semplicemente impostare i valori nel enum?

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

In seguito, solo perfom bit-saggio OR su di essi.

5

Sì. Invece, rendi i tuoi membri enum di 2:

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

Per il downvoter: forse ti piacerebbe spiegare cosa hai trovato non utile in questa risposta? –

25

un altro modo di memorizzare le bandiere è di non preoccuparsi del tipo sottostante. quando si utilizza un enum, i valori enum vengono memorizzati di default in un int unsigned, che è 32 bit su un computer comune. questo ti dà solo 32 possibili flag: mentre sicuramente molto, ci sono alcuni casi in cui non è sufficiente.

ora è possibile definire la vostra bandiera impostato in questo modo:

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

se non avete mai riscontrato questo, l' ': 1' parte dice al compilatore di utilizzare solo 1 bit per memorizzare questo utente struct.

ora è possibile definire una variabile per contenere le bandiere, e di lavorare con quelle bandiere:

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(); 

questo metodo consente di definire qualsiasi numero di bandiere, senza limitazione, ed è possibile estendere la vostra bandiera fissato a in qualsiasi momento senza temere un trabocco. lo svantaggio è che testare un sottoinsieme delle bandiere è più ingombrante e richiedere più codice.

+2

Interessante. La prima volta che incontro questo metodo. Sarebbe interessato a leggere i commenti di persone più competenti di me sui vantaggi e gli svantaggi rispetto al potere tradizionale di due enumerazioni. Funziona con tutti i compilatori e tutte le architetture? Quali sono le insidie? – gregschlom

+0

@gregschlom sarei interessato anche a più commenti. è totalmente conforme allo standard quindi dovrebbe funzionare con qualsiasi compilatore. le uniche insidie ​​a cui posso pensare sono: 1. se la struttura non è impacchettata, un flag set può richiedere una grande quantità di memoria, gran parte del quale non è utilizzata. 2. non è possibile specificare più valori di flag contemporaneamente, come 'myflags = grabbable & liquid' (tuttavia, ciò è possibile utilizzando un'unione contenente la struct e un valore int, ma ciò impone molti più vincoli, è più dipendente dal compilatore e non è oggetto di questo post) –

+0

Grazie! Un altro problema (che è collegato al fatto che non è possibile testare un sottoinsieme dei flag, come si fa notare), è che non è possibile testare facilmente se due variabili flag sono uguali. (es: non puoi fare: if (myflags == someOtherFlags) {...}). Un po 'un blocco per me, che si limiterà a enumerare vecchi enumerati ... – gregschlom