Dlaczego wyliczenia flag są zwykle definiowane za pomocą wartości szesnastkowych


123

Wiele razy widzę deklaracje wyliczeń flag, które używają wartości szesnastkowych. Na przykład:

[Flags]
public enum MyEnum
{
    None  = 0x0,
    Flag1 = 0x1,
    Flag2 = 0x2,
    Flag3 = 0x4,
    Flag4 = 0x8,
    Flag5 = 0x10
}

Kiedy deklaruję wyliczenie, zwykle deklaruję to w ten sposób:

[Flags]
public enum MyEnum
{
    None  = 0,
    Flag1 = 1,
    Flag2 = 2,
    Flag3 = 4,
    Flag4 = 8,
    Flag5 = 16
}

Czy istnieje powód lub uzasadnienie, dlaczego niektórzy ludzie wybierają zapisywanie wartości w systemie szesnastkowym, a nie dziesiętnym? Z mojego punktu widzenia łatwiej się pomylić, używając wartości szesnastkowych i przypadkowo Flag5 = 0x16zamiast pisać Flag5 = 0x10.


4
Co sprawiłoby, że byłoby mniej prawdopodobne, że napiszesz, 10zamiast 0x10używać liczb dziesiętnych? Zwłaszcza, że ​​mamy do czynienia z liczbami binarnymi, a hex jest trywialnie zamienialne na / z binarnego? 0x111jest o wiele mniej denerwujące w tłumaczeniu w głowie niż 273...
— cHao

4
Szkoda, że ​​C # nie ma składni, która nie wymaga jawnie zapisywania potęg dwóch.
— Colonel Panic

Robisz tu coś bezsensownego. Celem flag jest to, że będą one połączone bitowo. Ale kombinacje bitowe nie są elementami typu. Wartość Flag1 | Flag2to 3, a 3 nie odpowiada żadnej wartości domeny MyEnum.
— Kaz,

Gdzie to widzisz? z odbłyśnikiem?
— giammin

@giammin To ogólne pytanie, a nie konkretna implementacja. Możesz na przykład wziąć projekty open source lub po prostu kod dostępny w sieci.
— Adi Lester

Odpowiedzi:


184

Racjonalne uzasadnienia mogą się różnić, ale widzę zaletę, że szesnastkowy przypomina: „OK, nie mamy już do czynienia z liczbami w arbitralnym, wymyślonym przez ludzi świecie o podstawie dziesiątki. Mamy do czynienia z bitami - światem maszyny - i będziemy grać według jego zasad ”. Szesnastkowy jest rzadko używany, chyba że masz do czynienia z tematami stosunkowo niskiego poziomu, w których liczy się układ pamięci. Używanie go wskazuje na fakt, że w takiej sytuacji jesteśmy teraz.

Ponadto nie jestem pewien co do języka C #, ale wiem, że w języku C x << yjest to poprawna stała czasu kompilacji. Korzystanie z przesunięć bitowych wydaje się najbardziej jasne:

[Flags]
public enum MyEnum
{
    None  = 0,
    Flag1 = 1 << 0,
    Flag2 = 1 << 1,
    Flag3 = 1 << 2,
    Flag4 = 1 << 3,
    Flag5 = 1 << 4
}

7
To bardzo interesujące iw rzeczywistości jest to również prawidłowe wyliczenie C #.
— Adi Lester

13
+1 z tym zapisem nigdy nie popełnisz błędu w obliczeniach wartości wyliczeniowej
— Sergey Berezovskiy

1
@AllonGuralnek: Czy kompilator przypisuje unikalne pozycje bitów na podstawie adnotacji [Flags]? Zwykle zaczyna się od 0 i zwiększa się o 1, więc każda wartość wyliczenia przypisana 3 (dziesiętnie) będzie miała wartość binarną 11, ustawiając dwa bity.
— Eric J.

2
@Eric: Huh, nie wiem dlaczego, ale zawsze byłem pewien, że przypisuje wartości potęgi dwa. Właśnie sprawdziłem i chyba się myliłem.
— Allon Guralnek

38
Kolejny fajny fakt z x << ynotacją. 1 << 10 = KB, 1 << 20 = MB, 1 << 30 = GBI tak dalej. To naprawdę fajne, jeśli chcesz zrobić 16-kilobajtową tablicę dla bufora, który możesz po prostu iśćvar buffer = new byte[16 << 10];
— Scott Chamberlain,

43

Dzięki temu łatwo zauważyć, że są to flagi binarne .

None  = 0x0,  // == 00000
Flag1 = 0x1,  // == 00001
Flag2 = 0x2,  // == 00010
Flag3 = 0x4,  // == 00100
Flag4 = 0x8,  // == 01000
Flag5 = 0x10  // == 10000

Chociaż postęp czyni to jeszcze wyraźniejszym:

Flag6 = 0x20  // == 00100000
Flag7 = 0x40  // == 01000000
Flag8 = 0x80  // == 10000000

3
Właściwie dodaję 0 przed 0x1, 0x2, 0x4, 0x8 ... Więc otrzymuję 0x01, 0x02, 0x04, 0x08 i 0x10 ... Uważam, że jest to łatwiejsze do odczytania. Czy ja coś zepsuję?
— LightStriker

4
@Light - wcale nie. Jest to bardzo częste, więc możesz zobaczyć, jak się wyrównują. Po prostu sprawia, że ​​bity są bardziej wyraźne :)
— Oded

2
@LightStriker po prostu go wyrzuci, co ma znaczenie, jeśli nie używasz hex. Wartości, które zaczynają się tylko od zera, są interpretowane jako ósemkowe. Tak 012jest w rzeczywistości 10.
— Jonathon Reinhart

@JonathonRainhart: To wiem. Ale zawsze używam hex, kiedy używam bitfields. Nie jestem pewien, czy intzamiast tego czułbym się bezpiecznie . Wiem, że to głupie ... ale nawyki ciężko umierają.
— LightStriker

36

Myślę, że dzieje się tak dlatego, że sekwencja zawsze wynosi 1, 2, 4, 8, a następnie dodaj 0.
Jak widać:

0x1 = 1 
0x2 = 2
0x4 = 4
0x8 = 8
0x10 = 16
0x20 = 32
0x40 = 64
0x80 = 128
0x100 = 256
0x200 = 512
0x400 = 1024
0x800 = 2048

i tak dalej, o ile pamiętasz sekwencję 1-2-4-8, możesz zbudować wszystkie kolejne flagi bez konieczności pamiętania o potęgach 2


13

Ponieważ [Flags]oznacza, że ​​wyliczenie to naprawdę pole bitowe . Ze [Flags]można użyć bitowego AND ( &) i OR ( |) Operatorzy połączyć flagi. W przypadku takich wartości binarnych prawie zawsze bardziej oczywiste jest użycie wartości szesnastkowych. To jest właśnie powód, dla którego używamy szesnastkowego w pierwszej kolejności. Każdy znak szesnastkowy odpowiada dokładnie jednemu półbajtowi (cztery bity). W przypadku liczb dziesiętnych to mapowanie od 1 do 4 nie jest prawdziwe.


2
Możliwość korzystania z operacji bitowych nie ma właściwie nic wspólnego z atrybutem flag.
— Mattias Nordqvist,

4

Ponieważ istnieje mechaniczny, prosty sposób na podwojenie potęgi dwóch w hex. W liczbach dziesiętnych jest to trudne. Wymaga długiego mnożenia w twojej głowie. W hex to prosta zmiana. Możesz to zrobić aż do momentu, w 1UL << 63którym nie możesz tego zrobić w systemie dziesiętnym.


1
Myślę, że to ma największy sens. W przypadku wyliczeń z dużym zestawem wartości jest to najłatwiejsza droga (z możliwym wyjątkiem przykładu @ Hypercube).
— Adi Lester

3

Ponieważ łatwiej jest śledzić ludziom, gdzie bity są we fladze. Każda cyfra szesnastkowa może pasować do 4-bitowej liczby dwójkowej.

0x0 = 0000
0x1 = 0001
0x2 = 0010
0x3 = 0011

... and so on

0xF = 1111

Zazwyczaj chcesz, aby twoje flagi nie nakładały się na bity, najłatwiejszym sposobem zrobienia tego i wizualizacji jest użycie wartości szesnastkowych do deklarowania flag.

Tak więc, jeśli potrzebujesz flag z 16 bitami, użyjesz 4-cyfrowych wartości szesnastkowych i w ten sposób unikniesz błędnych wartości:

0x0001 //= 1 = 000000000000 0001
0x0002 //= 2 = 000000000000 0010
0x0004 //= 4 = 000000000000 0100
0x0008 //= 8 = 000000000000 1000
...
0x0010 //= 16 = 0000 0000 0001 0000
0x0020 //= 32 = 0000 0000 0010 0000
...
0x8000 //= 32768 = 1000 0000 0000 0000

To wyjaśnienie jest dobre ... jedyne, czego brakuje, to binarny odpowiednik pokazujący ostateczne zestawienie bitu,
— skubania
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.