Jeśli tylko liczymy coś bardziej skomplikowanego niż rozmieszczenie kontrolek w oknie, to niechybnie zaczniemy potrzebować którejś ze stałych matematycznych – na przykład π. Co wtedy robimy? Ano całkiem często skutkuje to włączeniem do programu deklaracji typu:
Nie jest to oczywiście wielkie zło (a już na pewno mniejsze niż bezpośrednie korzystanie z definicji typu π = 4atan(1)), ale w większości przypadków jest to też wyważanie otwartych drzwi. Potrzebne stałe mamy bowiem często już gdzieś zdefiniowane – trzeba tylko wiedzieć, gdzie ich poszukać:
M_PI
, M_E
, a nawet M_LN10
czy M_SQRT2
zdefiniowane w math.h lub cmath. Definicje te nie są jednak częścią standardu, więc dbające o zgodność kompilatory (czyli większość kompilatorów w ogóle) wymagają pewnych #define
‘ów przed dołączeniem ww. nagłówków. I tak dla Visual C++ jest to _USE_MATH_DEFINES
, a dla GCC bodajże prawie dowolne z makr _SOURCE
(jak _GNU_SOURCE
czy _ALL_SOURCE
).DX3DX_PI
i D3DX1BYPI
(1/π) – co nie dziwi, bo przecież w grafice więcej do szczęścia nie potrzeba ;) Obie te stałe są zdefiniowane w d3dx#math.h (gdzie #
to numer wersji DirectX), który to nagłówek jest dołączany automatycznie jeśli korzystamy z D3DX.PI
i E
zdefiniowane jako statyczne pola klasy System.Math
. W Javie klasa java.lang.Math
zawiera dwa identycznie zdefiniowane pola.Jak zatem łatwo zauważyć, wynajdywanie koła w postaci definicji π czy e jest w wielu przypadkach nieuzasadnione :]
Modyfikator static
ma w C++ kilka różnych funkcji, a jedną z nich jest deklarowanie składników statycznych w klasach. Jak doskonale wiadomo taki składnik jest wspólny dla wszystkich obiektów tejże klasy i można go używać nawet, jeśli takie obiekty w ogóle nie istnieją.
Zwykle nie ma większych problemów ze statycznymi metodami, natomiast w przypadku pól sprawa często potrafi się skomplikować. Zwłaszcza, że statyczne elementy zmienne i stałe deklarujemy nieco inaczej…
Przede wszystkim należy pamiętać, że statyczne zmienne są właściwie pewnym rodzajem zmiennych globalnych, różniącym się prawie wyłącznie pod względem składniowym. A w przypadku zmiennych globalnych (deklarowanych w nagłówkach poprzez extern
) konieczne jest ich przypisanie do jakiegoś modułu kodu, czyli pliku .cpp. Dzięki temu linker może potem powiązać odwołania do tej zmiennej z nią samą niezależnie od miejsca jej użycia.
To samo dotyczy statycznych pól:
Jest jednak drobna różnica między statycznymi stałymi a zmiennymi polami. W tym drugim przypadku możemy zmiennej nadać początkową wartość; robimy to już w pliku .cpp przy jej definicji – tak jak powyżej. Zrobienie tego od razu w deklaracji jest niedopuszczalne.
Natomiast dla stałych wartość podać musimy i zwykle czynimy to dla odmiany właśnie w pliku nagłówkowym, jeszcze wewnątrz bloku class
:
Wówczas z kolei nie możemy wręcz inicjalizować stałej ponownie w pliku .cpp!
Dość dziwne, prawda? Ale w miarę łatwo to wyjaśnić. Otóż stała ma, jak sama nazwa wskazuje stałą wartość. Dzięki temu, że jest podana w nagłówku, kompilator może zoptymalizować każde miejsce jej normalnego użycia. Zamiast odwoływania się do pamięci, wpisze po prostu wartość stałej “na sztywno” w kodzie wynikowym. Będzie więc to podobne do działania #define
.
Tym niemniej jest to pewna niedogodność, jeśli statyczne stałe i statyczne zmienne definiuje się inaczej. Ale przecież to nie jedyna dziwna cecha C++, która sprawia, że tak ten język lubimy ;-)
Dzisiaj pokażę pewną sztuczkę, mogącą nieco ułatwić życie programiście, który – jak wiadomo – zawsze ma za dużo pracy. Nie jest ona zbyt odkrywcza ani pomysłowa, ale ponieważ wielokrotnie zdarzyło mi się z niej korzystać (ostatni raz całkiem niedawno), sądzę że zasługuje na wzmiankę.
Sprawa dotyczy typów wyliczeniowych w C++ – czyli tworów, które pojawiają się często w większości programów. W moim ostatnim przypadku był to prosty typ definiujący wachlarz kolorów używanych w różnych miejscach systemu GUI:
Jest to podobny zestaw do tego, jaki można zobaczyć w Windows w oknie Właściwości: Ekran, na zakładce Wygląd. Naturalnie jest on dość okrojony, jako że nie potrzebujemy tutaj niczego aż tak skomplikowanego. Nie jest jednak wykluczone, że kiedyś się rozrośnie…
Każdemu z tych “systemowych” kolorów trzeba teraz przyporządkować kolor rzeczywisty – żeby wiedzieć, jak narysować konkretne kontrolki. Można zdefiniować do tego sporo zmiennych w stylu clActiveCaptionColor
czy clControlNormalColor
, ale lepszym rozwiązaniem jest tablica:
Problem w tym, że trzeba podać jej rozmiar. Można oczywiście wpisać tam na sztywno 5, ale doskonale wiadomo, jakie są skutki stosowania w kodzie “magicznych liczb”. Można zdefiniować sobie stałą, lecz wtedy też będziemy musieli ręcznie modyfikować jej wartość, jeżeli liczba kolorów się zmieni.
Potrzebujemy więc sposobu na automatyczne określenie liczby stałych zdefiniowanych w typie wyliczeniowym. W językach dysponujących dynamicznymi informacjami o typie – jak C# czy Java – byłoby to zapewne proste, lecz tutaj nie mamy tego komfortu. Zamiast tego możemy sobie jednak poradzić inaczej – dodając do typu wyliczeniowego kolejną stałą:
Kompilator nada jej wartość o jeden większą od poprzedniej. Ponieważ pierwszej stałej przypisał zero, GC_COUNT
będzie odpowiadało liczbie 5 i to będzie właśnie liczba użytecznych stałych w typie wyliczeniowym. Teraz można już zadeklarować potrzebną tablicę:
Podobnie można zrobić dla każdego typu wyliczeniowego. Najlepiej działa to wtedy, gdy kompilator sam numeruje jego stałe. Jeżeli sami to robimy, to oczywiście trik nadal będzie działał (zmienna *_COUNT
będzie zawsze miała największa wartość). W tablicy pojawią się jednak niewykorzystane elementy i te dziury ewentualnie trzeba będzie omijać.