This post comes from the first version of this blog.
Please send me an email if anything needs an update. Thanks!
Programiści, którzy “zasmakowali” pracy w językach wysokiego poziomu, takich jak m. in. PHP, bardzo często zapominają o możliwości wykorzystania bardzo niskich mechanizmów do osiągnięcia większej elastyczności kodu. Z reguły wykorzystujemy w kodzie różne wzorce projektowe i inne ułatwienia wprowadzone wraz z paradygmatem obiektowym programowania i myślimy za pomocą dużych komponentów, zamiast skorzystać z tego, co istnieje i ma się dobrze od co najmniej kilkunastu lat. Niniejszy wpis będzie poświęcony jednej z takich właśnie możliwości - wykorzystaniu flag bitowych. Zapraszam do lektury.
Fotografia: Bull3t, CC-BY-SA.
Problem: duża ilość parametrów.
Ci, którzy mieli styczność z językiem C czy C++ na pewno zetknęli się kiedyś z problemem przekazywania ustawień czy opcji do odpowiedniej funkcji. Weźmy pod uwagę przykładowy kod:
|
|
Funkcja ta nie jest specjalnie ambitna, ale dosyć dobrze pokazuje problem - w momencie, kiedy chcielibyśmy, aby jej działanie było sterowane za pomocą kilku binarnych przełączników musimy umieścić w deklaracji funkcji bardzo dużą liczbę parametrów, z których tylko jeden lub kilka ma faktyczne znaczenie i wpływ na wartość wynikową. Oczywiście możemy zmienić listę parametrów na przekazanie jednej tablicy:
|
|
Jednakże w tym przypadku alokujemy wręcz monstrualną ilość pamięci do tak prostej czynności jak wykonanie jednego z wariantów funkcji trim().
Flagi bitowe: najlepsza kompresja przekazywanych informacji.
Czym są flagi bitowe? Otóż, jak każdy wie - najmniejszą jednostką pamięci komputera dostępną dla programisty jest bit. 8 bitów stanowi 1 bajt, a dalej już wchodzą "standardowe" modyfikatory ilości - kilobajt, megabajt, gigabajt, itd. Większość programistów nie zważa na ilość pamięci, jaką zajmują ich programy / skrypty, stąd nie zauważa także potencjału tkwiącego w pojedynczej zmiennej zawierającej po prostu... liczbę całkowitą. Zauważmy, że w przypadku zwykłej zmiennej typu int, zajmującej 1 bajt w pamięci mamy aż 8 możliwych stanów - przełączników dla każdego bitu:1: 00000001
173: 10101101
255: 11111111
Każdy z tych stanów możemy odczytać z liczby z pomocą operatora bitowego AND:
|
|
Flaga musi być potęgą dwójki, tzn. musi zawierać tylko jedną “jedynkę” w reprezentacji binarnej. Wynikiem operacji jest liczba odpowiadająca konfiguracji bitów, jakie były ustawione na tych samych pozycjach w obu liczbach, a więc w zależności od tego, czy trafimy w odpowiednią “jedynkę” zostanie zwrócona wartość flagi lub zero.
Aby nie musieć za każdym razem przemnażać kolejnych flag przez 2, możemy też wykorzystać operator przesunięcia bitowego. W taki przypadku definiujemy tylko pierwszą flagę o wartości jeden, a następnie “przesuwamy jedynkę” o kolejną ilość miejsc w prawo:
|
|
Spróbujmy wykorzystać te informacje, aby zmienić naszą funkcję customTrim() w nieco bardziej konfigurowalny twór.
Rozwiązanie: parametr zawierający flagi bitowe.
Wykorzystując flagi bitowe zdefiniowane wyżej kod funkcji będzie wyglądał następująco:
|
|
Jak widać, zyskujemy zarówno na ilości parametrów [przekazujemy tylko string i flagi], jak i na pamięci [cały parametr zawierający flagi to… pojedynczy “int”]. Mówiąc o optymalizacji za pomocą flag bitowych wypada także powiedzieć kilka słów na temat sposobu wywołania takiej funkcji. Aby stworzyć wartość odpowiadającą odpowiedniemu zbiorowi flag posłużymy się operatorem binarnym OR:
|
|
A zatem aby przekazać kilka wartości poszczególnych przełączników należy je po prostu połączyć operatorem binarnym OR.
BTW. Zdaję sobie sprawę z tego, że omówiony przeze mnie przykład jest “mocno średni”, dlatego jeśli macie nieco lepszy pomysł na funkcję przedstawiającą opisywany problem, proszę podzielcie się nimi w komentarzach. Do zobaczenia w piątek!