Dyrektywy preprocesora

Stronę tą wyświetlono już: 909 razy

Dyrektywy preprocesora w C++ są poleceniami, które kompilator wykonuje zanim zacznie on tworzyć kod programu. Dzięki takiemu rozwiązaniu można dość swobodnie sterować tym co zostanie dołączone do kodu źródłowego programu. Wszystkie dyrektywy zaczynają się od znaku #.

Dyrektywa #include

Jest to jedna z podstawowych dyrektyw, która służy do załączania do plików z rozszerzeniem .cpp i .h definicji struktur, klas i nagłówków funkcji zawartych w oddzielnych plikach lub bibliotekach systemowych. Pliki .h mogą być załączane na trzy sposoby:

  1. poprzez odwołanie się do plików znajdujących się w domyślnej lokalizacji podstawowych plików .h kompilatora, odwołanie takie ma następującą postać przykładową:
    Listing 1
    1. #include <iostream>
  2. poprzez odwołanie się do pliku z lokalizacji domyślnej projektu
    Listing 2
    1. #include "moje_funkcje.h"
  3. poprzez podanie bezwzględnej ścieżki do pliku, np.
    Listing 3
    1. #include "C:\\moje_funkcje.h"

Ostatni z sposobów nie jest zbyt często stosowany ani też zalecany.

Dyrektywa #define

Dyrektywa ta jest często stosowana do tworzenia stałych oraz makr. Stałe definiowane są w następujący sposób:

Listing 4
  1. #define ST_PI 3.141592654
  2. #define ST_E 2.718281828

Powyższy kod tworzy stałe w postaci literałów, zanim kod programu zostanie skompilowany kompilator wszędzie tam gdzie wystąpi słowo ST_PI wstawi ciąg znaków 3.141592654 i to samo tyczy się ST_E. Deklarowanie powyżej wypisanych stałych mija się z celem, gdyż wszystkie stałe matematyczne znajdują się w pliku nagłówkowym math.h, aby jednak z nich skorzystać konieczne jest napisanie następującej dyrektywy:

Listing 5
  1. #define _USE_MATH_DEFINES
  2. #include <math.h>

W ten sposób odblokowany zostanie następujący kod znajdujący się w pliku nagłówkowym math.h:

Listing 6
  1. #define M_E 2.71828182845904523536
  2. #define M_LOG2E 1.44269504088896340736
  3. #define M_LOG10E 0.434294481903251827651
  4. #define M_LN2 0.693147180559945309417
  5. #define M_LN10 2.30258509299404568402
  6. #define M_PI 3.14159265358979323846
  7. #define M_PI_2 1.57079632679489661923
  8. #define M_PI_4 0.785398163397448309616
  9. #define M_1_PI 0.318309886183790671538
  10. #define M_2_PI 0.636619772367581343076
  11. #define M_2_SQRTPI 1.12837916709551257390
  12. #define M_SQRT2 1.41421356237309504880
  13. #define M_SQRT1_2 0.707106781186547524401

Dyrektywa #define umożliwia również tworzenie własnych makr, bardzo prosty i niezbyt przydatny przykład obrazujący zasadę definiowania makra napisałem poniżej:

Listing 7
  1. #define POMNOZ(a, b) (a * b)
  2. int a = POMNOZ(2, 3); // poprawne użycie makra
  3. int b = POMNOZ(2 + 1, 3); // niepoprawne użycie makra

Powyżej pokazany został częsty błąd, jaki można popełnić wykorzystując makra tego typu, otóż w przypadku zmiennej int o nazwie b wynikiem będzie 5 a to dlatego, ze kompilator wstawi w miejsce POMNOZ(2 + 1, 3) kod 2 + 1 * 3. Z tego względu należałoby tutaj stworzyć makro następującej postaci:

Listing 8
  1. #define POMNOZ(a, b) ((a) * (b))

Niezbyt zgrabne rozwiązanie, ale poprawniejsze składniowo.

Poniżej podaję przykłady standardowych makr:

  • __DATE__ - data momentu kompilacji;
  • __TIME__ - godzina momentu kompilacji;
  • __FILE__ - łańcuch tekstu zawierający nazwę kompilowanego pliku;
  • __LINE__ - numer linii w kodzie programu;

UWAGA! Przyjęło się, że wszystkie nazwy stałych i makr są pisane DUŻYMI LITERAMI dla odróżnienia od nazewnictwa zmiennych zawartych w kodzie programu.

Dyrektywa #undef

Dyrektywa ta jest wykorzystywana do usunięcia deklaracji jakiejś zmiennej czy też makra utworzonego dyrektywą #define.

Instrukcja warunkowa #if #elif #else #endif

Tego typu instrukcja umożliwia sterowanie kodem programu, prosty przykład jest:

Listing 9
  1. #define COMPILE 1
  2. #if COMPILE == 0 // jeżeli stała COMPILE ma wartość 0 to
  3. // instrukcje do wykonania
  4. #elif COMPILE == 1 // w przeciwnym przypadku jeżeli stała COMPILE ma wartość 1 to
  5. // instrukcje do wykonania
  6. #else // w przeciwnym przypadku
  7. // instrukcje do wykonania
  8. #endif

Wystarczy w jednym miejscu zmienić jedną wartość, aby kod, który zostanie załączony do programu podczas jego kompilacji się zmienił.

Instrukcja warunkowa #ifdef #ifndef #else #endif

Jest to podobny zbiór instrukcji, z tą różnicą że sprawdza on czy dana stała została zadeklarowana, czy też nie. Często wykorzystywanym kodem jest zabezpieczenie przed podwójnym załączaniem pliku nagłówkowego h:

Listing 10
  1. #ifndef NAZWA_PLIKU_H
  2. #define NAZWA_PLIKU_H
  3. // nagłówki funkcji, definicje struktur i klas
  4. #endif

Komentarze