Preprocesor jest jednym z modułów kompilatora. Działa on zwykle w ukryciu, przez co nie każdy zdaje sobie sprawę z jego istnienia. Zanim jeszcze ruszy do pracy kompilator kod programu jest przeglądany przez preprocesor. Preprocesor bada cały kod w poszukiwaniu specjalnych komend. Owe komendy to właśnie dyrektywy. Za ich pomocą można niejako sterować przebiegiem kompilacji. Dyrektywy można umieszczać w dowolnym miejscu programu. Każda dyrektywa rozpoczyna się znaczkiem płotka [#]. Za koniec dyrektywy uważa się koniec wiersza. Zatem nie trzeba już stosować średnika kończącego. Poniżaj przedstawione zostały jedynie wybrane dyrektywy.

dyrektywa #define
Ta dyrektywa służy do definiowania makrodefinicji. Makrodefinicje stosuje się w celu uproszczenia i skrócenia zapisu. Zamiast za każdym razem pisać długie i skomplikowane wyrażenia można to zrobić jednokrotnie. Wystarczy wówczas owe wyrażenie zapisać, jako makrodefinicje i wywoływać w stosownym momencie. Przypuśćmy, że masz pewien fragment kodu, który stosujesz wielokrotnie w programie. Zajmuje on jedną linijkę, ale jest dość skomplikowany. Wielokrotne wklepywanie lub kopiowanie tego samego tekstu jest trochę męczące. Aby sobie uprzyjemnić życie można zapisać powtarzający się kod, jako makrodefinicję. Składnia wygląda tak:

Można powiedzieć, że słowo wyrażenie to jakby nazwa makrodefinicji, natomiast ciąg_znaków_zastępczych to jej wartość. Od tej pory każdorazowe wystąpienie nazwy makrodefinicji zostanie automatycznie zastąpione jej wartością. Zarówno nazwa, jak i wartość mogą być dowolnymi ciągami znakowymi. Należy jednak pamiętać, aby w przypadku kilkuczłonowej nazwy nie oddzielać poszczególnych słów spacją. Chodzi tutaj o taką sytuację:

Taki zapis zostanie błędnie zinterpretowany przez preprocesor. Konkretniej mówiąc zostanie to odczytane następująco. Słowo makrodefinicja będzie nazwą, natomiast służąca wartością. Zatem zupełnie coś innego. Aby uniknąć takich błędów najlepiej korzystać z podkreślnika. Poprawnie wyglądałoby to tak:

Myślę, że jest to zrozumiałe. Jest jeszcze jedna rzecz, o której trzeba pamiętać. Makrodefinicja raz zdefiniowane nie może zostać użyta ponownie. Jest to dość logiczne. Jeżeli masz dwie identyczne makrodefinicje, to jak je rozróżnisz? Nie ma na to sposobu. Zatem raczej trudno tutaj popełnić błąd. Jednak dla formalności o tym powiedziałem.

dyrektywa #undef
Pamiętasz jeszcze, jak mówiłem, że użyta makrodefinicja nie może zostać zdefiniowana na nowo? No cóż.. to nie do końca jest prawdą. Istnieje bowiem sposób, aby użyć tej samej nazwy powtórnie w nowej wersji makrodefinicji. Jednak najpierw należy odwołać istniejącą już makrodefinicję i zdefiniować nową. Aby to zrobić należy skorzystać z dyrektywy undef. Działa to tak. Przypuśćmy, że mamy zdefiniowaną makrodefinicję o nazwie prowizja, która służy do obliczania prowizji. Nagle stwierdzasz, że taka makrodefinicja Ci nie odpowiada. Postanawiasz zdefiniować makrodefinicję, która wykonuje nieco inne obliczenia. Nie możesz usunąć starej wersji, gdyż znajduje się ona w pliku już skompilowanym. Wówczas jedynym wyjściem jest odwołanie starej wersji makrodefinicji prowizja i zdefiniowanie nowej – ulepszonej. Aby odwołać starą wersję należy napisać:

To wystarczy. Preprocesor już zapomniał, że kiedykolwiek istniała makrodefinicja o nazwie prowizja. Zatem użycie jej nazwy spowoduje błąd. Teraz wystarczy zdefiniować nową wersję makrodefinicji. Na koniec chciałem jeszcze zaznaczyć, iż makrodefinicje mają dość mały udział w programowaniu. Zamiast makrodefinicje stosuje się raczej funkcje, gdyż są one znacznie bezpieczniejsze w obsłudze.

dyrektywy #if, #elif, #else i #endif
Wszystkie te dyrektywy służą do kompilacji warunkowej. Załóżmy, że piszesz bardzo uniwersalny program, działający na kilku różnych platformach. W zależności od sprzętu poszczególne moduły programu będą nieco inne. Chcesz teraz sprawdzić działanie programu. Wprowadzasz więc pomocniczą zmienną, której wartość będzie zależna od systemu, na którym kompilujesz program. Za pomocą dyrektyw kompilacji warunkowej możesz określić, która część kodu zostanie skompilowana.

Na początku zdefiniowaliśmy sobie makrodefinicję o nazwie SYSTEM. Jej wartość to jeden. Następnie znajduje się blok kompilacji warunkowej. W zależności od naszej makrodefinicji SYSTEM zostanie skompilowany odpowiedni moduł. U nas SYSTEM wynosi jeden. Zatem skompilowany zostanie moduł dla PC’a. Wartość dwa oznacza moduł dla Mac’a, a wartość 3 dla Apple’a. Każda inna wartość spowoduje, że kompilacja nie odbędzie się. Należy tutaj zaznaczyć, iż wszelkie instrukcje trzeba umieszczać w nowej linijce. Nie mogą one być napisane bezpośrednio za dyrektywą warunkową. Jeżeli nawet o tym zapomnisz kompilator i tak Ci przypomni 🙂 Cały blok trzeba zakończyć dyrektywą endif.

dyrektywy #ifdef i #ifndef
Te dyrektywy również służą do kompilacji warunkowej. Jednakże są znacznie bardziej praktyczniejsze niż opisane wcześniej. Poprzednio warunkiem była wartość makrodefinicji. Teraz jest nieco inaczej. Warunkiem jest sama makrodefinicja. Konkretnie, jeżeli dana makrodefinicja istnieje, czyli jest zdefiniowana kompilacja nastąpi. Tak jest w przypadku ifdef. z dyrektywa ifndef jest dokładnie odwrotnie.

Zauważ, że tym razem makrodefinicja SYSTEM nie posiada żadnej wartości! Posiada jedynie nazwę. Jest to dyrektywa pusta. Następnie znajduje się dyrektywa kompilacji warunkowej. Działa ona tak. Jeżeli makrodefinicja SYSTEM istnieje zostanie skompilowana pierwsza część bloku. Druga część dotyczy sytuacji, gdy makrodefinicja nie jest zdefiniowana. W naszym przypadku SYSTEM istnieje. Wówczas zostaną skompilowane instrukcje umieszczone za dyrektywą ifdef.

dyrektywa #include
Dyrektywa include jest stosowana chyba najczęściej. Krótko mówiąc jej działanie skupia się na dołączeniu do kompilowanego pliku zawartości innego pliku. Jest przydatna w sytuacjach, kiedy chcemy skorzystać z funkcji bibliotecznych. Funkcje biblioteczne to najzwyklejsze funkcje dostarczone przez producenta kompilatora. Są one skompilowane i gotowe do pracy. Aby z nich skorzystać należy dołączyć do programu określony plik nagłówkowy zawierający deklaracje interesujących nas funkcji. Pamiętasz jeszcze, jak mówiłem o klasach? Tam też zdefiniowaliśmy sobie funkcję do kopiowania stringów. Nadmieniłem też, że tak naprawdę wykonaliśmy zbędną pracę, gdyż taka funkcja już istnieje i znajduje się w pliku z funkcjami bibliotecznymi. Zatem skorzystajmy z niej.

Za pomocą dyrektywy do naszego programu dołączyliśmy plik string.h. Zawiera on rozmaite funkcje do dokonywania operacji na tablicach znakowych. Zauważ, że plik podajemy w nawiasach trójkątnych, a sam plik ma rozszerzenie ’.h’. Takie są rozszerzenia dla plików nagłówkowych zawierających deklaracje funkcji. Teraz już możemy korzystać z tych funkcji.

W wyniku użycia funkcji strcpy zawartość tablicy stringu1 została skopiowana do tablicy string2. Dyrektywa include nie służy jedynie do włączania do programu plików nagłówkowych. Za jej pomocą można także wstawić do programu treść innego pliku. Jest to dość praktyczne w przypadku rozbudowanych programów. Można podzielić projekt na kilka modułów. Każdy moduł umieścić w osobnym pliku i wstawić do programu w odpowiednie miejsce. Ułatwia to późniejsze modyfikacje programu. Wówczas podczas włączania takiego pliku zamiast nawiasów trójkątnych należy zastosować cudzysłów.
Oprócz wymienionych dyrektyw istnieją jeszcze inne. Jednak nie będę Ci zawracał nimi głowy, są mało przydatne. Służą do drobnych modyfikacji, jak wstawienie daty kompilacji. Sam ich jeszcze nigdy nie stosowałem.
Jeżeli jesteś ciekaw, jakie jeszcze funkcje oferuje Ci producent kompilatora to przeglądnij sobie pliki z rozszerzeniem ’.h’. Tam właśnie zawarte są deklaracje funkcji bibliotecznych. Niestety ich opisy są w Języku angielskim, jeżeli w ogóle są! 🙁 Na pocieszenie powiem, że w następnym rozdziale pogadamy sobie o wybranych funkcjach bibliotecznych służących do obsługi ekranu, klawiatury oraz dysku.

Autor: Kesay

[Artykuł pochodzi ze strony guidecpp.prv.pl, autor wyraził zgodę na publikację]