Kurs C++ #4 - dodatek
Kurs C++ #4 - dodatek
|======= #04 add. 1 ======|
+-------------------------+
| K U R S C + + |
+-------------------------+
s y s t e m y
l i c z b o w e
i o p e r a c j e
b i t o w e
Tak jak obiecałem w drugiej części kursu, prezentuję dodatek omawiający systemy liczbowe oraz operacje bitowe. Zacznę naturalnie od systemów liczbowych, a konkretnie do czterech z nich: dziesiętnego, ósemkowego, szesnastkowego oraz dwójkowego.
System dziesiętny (decymalny)
Jak zapewne wszyscy z was wiedzą, z systemu dziesiętnego korzystamy na co dzień - jest to naturalny dla nas system. Dla nas, ale niestety nie dla urządzeń elektronicznych. Ale żeby poznać system komputerowy i w ogóle inne systemy liczbowe, dowiedzmy się nieco, jak zbudowane są liczby w naszym rodzimym systemie systemem dziesiętnym. Spójrzmy, jak inaczej można zapisać liczbę 13049:
13049 = 1*104 + 3*103 + 0*102 + 4*101 + 9*100
Widzimy tu kolejne cyfry mnożone przez kolejne potęgi 10. Mamy więc:
1*104 = 1*10000 = 10000
+ 3*103 = 3*1000 = 3000
+ 0*102 = 0*100 = 0
+ 4*101 = 4*10 = 40
+ 9*100 = 9*1 = 9
Trochę skomplikowane rachunki, ale w rzeczywistości takich rachunków dokonujemy nieświadomie podczas liczenia. Ważne jest to, że w systemie dziesiętnym mnożymy cyfry przez potęgi 10 - jest to podstawa systemu liuczbowego. A co się stanie, gdy zmienimy podstawę? To proste - zmienimy system liczbowy. A więc do dzieła!
System ósemkowy (oktagonalny)
System ósemkowy ma w swej podstawie liczbę 8. Wobec tego do zapisu liczby wystarcza 8 cyfr - od 0 do 7. Nie będę się dużo na ten temat rozpisywał - aby przeliczyć liczbę ósemkową na dziesiętną, wystarczy ją rozpisać w sposób identyczny, jak zrobiliśmy to przy omawianiu systemu dziesiętnego - z tym, że zamiast potęg 10 używamy potęg 8. Dodam jeszcze, że aby wprowadzić liczbę w systemie ósemkowym w C++, wcale nie musimy nic przeliczać - kompilator z chęcią zrobi to za nas. Wystarczy właściwą liczbę zapisać z zerem na początku i zostanie ona zinterpretowana jako ósemkowa. Działa to tylko na liczbach wpisanych w kod programu - przy wprowadzaniu danych podczas wykonania programu liczba zawsze jest brana jako dziesiętna. Chyba, że programista chce inaczej i wyraźnie to zapisze.
System szesnastkowy (heksadecymalny)
Tu też nie ma się nad czym rozpisywać - jest to po prostu liczba o podstawie 16. W związku z tym wykorzystuje 16 cyfr. Ponieważ cyfr arabskich mamy tylko 10, od 0 do 9, to system szesnastkowy "pożycza" sobie pierwsze sześć liter alfabetu. Używa więc cyfr 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E, F. Litera A dziesiętnie ma wartość 10, B - 11, C - 12, D - 13, E - 14 a F - 15. W C++ liczby szesnastkowe możemy wprowadzać, poprzedzając je znakami (tzw. prefiksem) 0x, np. 0x1B. W zasadzie wielkość liter nie ma znaczenia, jednak bardziej czytelny jest mały "iks" w prefiksie.
System dwójkowy (binarny)
I kolejny już system, najważniejszy w informatyce. Dlatego najważniejszy, że urządzenia elektroniczne rozróżniają tylko dwa stany - jest prąd i nie ma prądu, co w zasadzie można zinterpretować jako dwie cyfry. System dwójkowy wykorzystuje właśnie dwie cyfry, a więc pasuje tu jak ulał. Niestety, większe liczby binarne nie są zbyt czytelne (dla ludzi), dlatego właśnie poznaliśmy dwa poprzednie systemy. C++ nie umożliwia wprowadzania liczb w formie binarnej. Ale dlaczego zamiennikami systemu dwójkowego są ósemkowy i szesnastkowy? Ponieważ zachodzi między nimi interesująca zależność - jedna cyfra systemu ósemkowego odpowiada niepowtarzalnej sekwencji trzech bitów (cyfr dwójkowych), natomiast cyfra szesnastkowa odpowiada czterem bitom. Oto dwie tabelki, które mają za zadanie to zilustrować:
Oct Bin
================
0 000
1 001
2 010
3 011
4 100
5 101
6 110
7 111
Hex Bin
================
0 0000
1 0001
2 0010
3 0011
4 0100
5 0101
6 0110
7 0111
8 1000
9 1001
A 1010
B 1011
C 1100
D 1101
E 1110
F 1111
Wobec tego przeliczmy szybko liczbę 0x15A0 na system dwójkowy:
Hex: 1 5 A 0
Bin: 0001 0101 1010 0000
Mając pod ręką te tabelki (lub ucząc się ich na pamięć, co nie jest tak bardzo skomplikowane - ja pisałem z pamięci :)) można w łatwy sposób przeliczać odpowiednie wartości. Natomiast nie ma najmniejszego sensu przeliczanie ręcznie każdej wartości - odpowiednie funkcje ma np. windowsowy kalkulator.
Operatory bitowe
Po co nam wiedza o systemie dwójkowym (bo to on jest najważniejszy w informatyce)? Przecież możemy bez problemu operować na liczbach dziesiętnych, a kompilator sam wszystko przelicza. Jednak w przyszłości możecie spróbować napisać bibliotekę niskopoziomową, np. sterowniki do urządzenia, które zaprojektuje wasz kolega elektronik. Komunikacja w komputerze - nawet między procesorem, a pamięcią i w ogóle jakimikolwiek peryferiami - odbywa się za pomocą portów o określonym numerze. Na te porty wysyłane są informacje i instrukcje. Ale przepustowość portu jest ograniczona. Nie możemy przesłać sobie struktury czy klasy. Dane wysyłane są do portów poprzez zwykłe liczby całkowite, a w nich są spakowane potrzebne informacje. Powiedzmy, że pierwszy bit mówi, czy urządzenie należy włączyć czy wyłączyć; kolejne trzy bity to parametr przekazywany do urządzenia. W ten sposób można umieścić naprawdę dużo informacji.
Podam następny przykład, może nieco bardziej zrozumiały i przystępny. Załóżmy, że budujemy strukturę. Ma ona szesnaście pól typu logicznego - bool, jedno pole short oraz trzy pola long. Policzmy sobie jej rozmiar w bajtach (bajt to 8 bitów): 16*1 + 1*2 + 3*4 = 30. Niby nie tak dużo, ale zauważmy - typ bool zajmuje 1 bajt, ale przyjmuje tylko dwie wartości: true i false. Wobec tego wystarczyłby mu jeden bit! Nie możemy zadeklarować zmiennej o rozmiarze 1 bita, najmniejszy rozmiar zmiennej to bajt. Ale znając operacje na bitach możemy stworzyć zmienną typu short, nazywając ją np. flags. Ma ona rozmiar 2 bajtów, a więc 16 bitów. Kolejne bity tej liczby możemy interpretować jako kolejne wartości logiczne. Teraz rozmiar naszej struktury zmniejszył się, i to znacznie - teraz wynosi on 2*2 + 3*4 = 16. Zmniejszyliśmy ją prawie dwukrotnie!
Poznamy teraz operacje na bitach, których mamy cztery: ustawianie bitu, kasowanie bitu, odwracanie bitu oraz sprawdzanie bitu. Przedtem jednak poznamy operatory bitowe, bo w przeciwieństwie do np. Pascala, C++ używa osobnych operatorów do operacji logicznych i bitowych.
Operatory przesunięcia bitowego (SHL i SHR)
Mamy dwa operatory tego typu: przesunięcie bitowe w lewo - << i przesunięcie bitowe w prawo - >>. Znamy już te operatory, jednak z nieco innej funkcji, czyli operacji wejścia/wyjścia z użyciem obiektów cin i cout. Działają one inaczej, ponieważ zostały przeciążone - co to znaczy i jak to się robi, dowiemy się wkrótce. Oto jak można przedstawić przesunięcie bitowe:
przesunięcie w lewo (SHL): 26 << 2
0 0 0 1 1 0 1 0 // przed przesunięciem
0 1 1 0 1 0 0 0 // po przesunięciu
Przy przesuwaniu bity, które wykraczają poza zakres liczby są porzucane (gubione), natomiast bity dodane są uzupełniane zerami. Podobnie wygląda przesunięcie bitowe w prawo:
przesunięcie w prawo (SHR): 26 >> 2
0 0 0 1 1 0 1 0 // przed przesunięciem
0 0 0 0 0 1 1 0 // po przesunięciu
Widzimy tu, że bity wykraczające poza zakres są ignorowane. Ważna informacja: operacja x << 2 nie zmienia wartości zmiennej x, więc sama w sobie nic nie robi. Jeżeli chcemy rzeczywiście zmienić wartość x, to musimy połączyć przesunięcie z przypisaniem: x = x << 2. Oczywiście istnieją również skrócone wersje operatorów: <<= to przesunięcie w lewo z przypisaniem oraz >>= to przesunięcie w prawo z przypisaniem.
Operator negacji bitowej (NOT)
Negacja bitowa znacząco różni się od logicznej negacji. Negacja bitowa polega na zanegowaniu (odwróceniu) każdego bitu zmiennej. Operator bitowej negacji ma postać tyldy (~). Negacja bitowa wygląda tak:
char x = 134
1 0 0 0 0 1 1 0
~x
0 1 1 1 1 0 0 1
Oczywiście znów - samo wyrażenie ~x niczego nie zmieni. Istotna jest tylko zwracana przez niego wartość. Dlatego aby bitowo zanegować x musimy przypisać: x = ~x. Nie ma skróconego operatora negacji bitowej - bo i jak miałby on funkcjonować?...
Operatory bitowej alternatywy i koniunkcji (OR i AND)
Tutaj różnica między operatorami bitowymi i logicznymi jest taka sama, jak w negacji bitowej i logicznej - brana jest pod uwagę nie wartość zmiennej, a jej pojedyncze bity. Może najpierw na przypomnienie tabelka wartości operacji boolowskich:
p q p | q // alternatywa
0 0 0
1 0 1
0 1 1
1 1 1
p q p & q // koniunkcja
0 0 0
1 0 0
0 1 0
1 1 1
Wobec tego bierzemy dwie zmienne typu char: x1 = 12 i x2 = 245:
x1 = 12
0 0 0 0 1 1 0 0
x2 = 245
1 1 1 1 0 1 0 1
x1 | x2 // bitowa alternatywa - odpowiednik logicznego ||
1 1 1 1 1 1 0 1
x1 & x2 // bitowa koniunkcja - odpowiednik logicznego &&
0 0 0 0 0 1 0 0
W tych przypadkach istnieją operatory skrócone: |= i &=.
Operator bitowej różnicy symetrycznej (XOR)
Ten operator nie ma swojego logicznego odpowiednika. Przedstawię najpierw tabelkę, która pokaże, jak działa operator bitowej różnicy symetrycznej:
p q p ^ q
0 0 0
1 0 1
0 1 1
1 1 0
Mówiąc prosto - jeśli oba wyrażenia mają tę samą wartość, to wynikiem jest fałsz. Pokażę teraz, jak to funkcjonuje - znów zaczniemy od dwóch zmiennych typu char:
x1 = 43
0 0 1 0 1 0 1 1
x2 = 69
0 1 0 0 0 1 0 1
x1 ^ x2 // bitowa różnica symetryczna
0 1 1 0 1 1 1 0
Ten operator, podobnie jak dwa poprzednie, posiada odpowiednik skrócony: ^=.
Operacje na bitach
Teraz, gdy poznaliśmy już wszystkie operatory bitowe, możemy wreszcie poznać operacje na bitach. Do ich omawiania potrzebujemy dwóch zmiennych - nazwiemy je flags oraz bit. Obie zmienne są tego samego rozmiaru. Bity zmiennej flags są przechowywanymi wartościami, natomiast zmienna bit jest odpowiednim (pojedynczym) bitem lub bitami, które chcemy sprawdzić lub zmienić (tzw. maska bitowa). Teraz po kolei omówię poszczególne operacje.
Sprawdzanie wartości bitu
Sposób zapisu tej operacji wygląda tak:
if (flags & bit == bit)
W praktyce zapis ten można skrócić:
if (flags & bit)
Jeśli bit lub bity odpowiadające masce bitowej są ustawione, wyrażenie zwraca true, w przeciwnym wypadku - false.
Ustawianie bitu
Poniższe operacje pozwalają na ustawienie w zmiennej flags bitu lub bitów maski bit:
flags = flags | bit;
flags |= bit;
Niezależnie od tego, czy do tej pory bit miał wartość 1 czy 0, po wykonaniu tej operacji będzie miał wartość 1.
Kasowanie bitu
flags = flags & ~bit;
flags &= ~bit;
Te operacje kasują bit niezależnie od jego poprzedniego stanu.
Przełączanie (odwracanie) bitu
flags = flags ^ bit;
flags ^= bit;
Te operacje odwracają bit - jeżeli dotychczas miał wartość 0, zostanie ustwiony na 1, a jeśli miał wartość 1, to zostanie ustawiony na 0.
To tyle jeśli chodzi o operatory i operacje bitowe. Nie jest to temat niezwykle istotny - dlatego znalazł się w dodatku - jednak z pewnością kiedyś spotkacie się z operacjami bitowymi. I to może nawet szybciej, niż myślicie...
autor("ArchiE","archie007@wp.pl")
Wyszukiwarka
Podobne podstrony:
k cplk cpl2k cplk cpl1k cpl?k cpl?k cplk cplr08 cpl t (3)t p cplk cpl0k cpl0k cplk cplk cpl1k cplawięcej podobnych podstron