Skąd wziąć matematyczne stałe
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 :]
Stałe i zmienne pola statyczneModyfikator 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:
// foo.cpp
int Foo::ms_iBar = 0;
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:
// foo.cpp
const int Foo::BAR;
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 ;-)
Liczenie stałychDzisiaj 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łą:
// na końcu
GC_COUNT
};
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ć.