profil

C++ - wykład 7

poleca 85% 119 głosów

Treść
Grafika
Filmy
Komentarze





Wykład 7 - 3 godz.

Zakres tematyczny

1. Dyrektywy preprocesora



1. Preprocesor

C/C++ pozwala na pewne rozszerzenie języka za pomoca tzw. preprocesora. Pozwala on na:

- definiowanie makrodefinicji pozwalających uprościć postać kodu źródłowego programu, zwiększając jego czytelność,

- włączanie tekstów innych plików, takich jak zbiory nagłówkowe zawierające prototypy standardowych funkcji bibliotecznych, definicje niezbędnych stałych ect.,

- sterowanie przebiegiem kompilacji.



Kompilatory wszystkich języków (zintegrowane jak i zewnętrzne) zanim przystąpią do generowania kodu wynikowego, wykonują wszystkie zadania przewidziane dla preprocesora. O tym co ma zrobić preprocesor decyduje w zasadzie programista za pomoca tzw. dyrektyw preprocesora.

Preprocesor języka C/C++ rozpoznaje następujące dyrektywy:

#define #undef

#elif #else #endif

#error #line #pragma

#if #ifdef #ifndef

Każdy tekst następujący po dyrektywie musi być ujęty w znaki komentarzy, chyba że dyrektywa ma argumenty. Linie kontynuacji w preprocesorze zakończone są znakiem "".

Dyrektywy te umieszcza się w dowolnym miejscu, a ich znakiem szczególnym jest fakt, że pierwszym niebiałym znakiem jest znak #. Przed tym znakiem mogą być tylko spacje i tabulatory. Dyrektywy obejmują tylko zbiór tekstowy w którym się znajdują. Z pewna dyrektywa już się Państwo oswoiliście, mianowicie z dyrektywa #include. O innych pomówimy poniżej.



Dyrektywa # define



Dyrektywa ta służy do przyporządkowywania identyfikatorów :

a) stałym

b)słowom kluczowym, instrukcjom lub wyrażeniom.

Identyfikatory, które reprezentują stałe nazywamy stałymi symbolicznymi, natomiast te które reprezentują instrukcje i wyrażenia - makrami. Każda stała symboliczna i makro mają swoją nazwę.

W momencie, gdy nazwa makra rozpoznawana jest w tekście programu, jest traktowana jako wywołanie makra. Nazwa makra zamieniana jest przez kopie treści (ciała) makra. Jeśli makro jest z argumentami, argumenty aktualne zamieniane są z formalnymi (podobnie jak to było w przypadku funkcji). Proces ten nazywany jest rozwinięciem makra.

Dyrektywa define pozwala tworzyć makra typu:

a)obiekt - bez argumentów

b)funkcja - które przyjmują argumenty i wyglądaja i działaja jak funkcja.

Makra są pozostałościa z języka C. W obecnych wersjach C++ moga być z powodzeniem zastąpione (czasami na dobre to wychodzi) poprzez tzw. funkcje inline. Co to takiego jest funkcja inline.

Wiemy, że za każdorazowe wywołanie funkcji w programie się płaci. Na poziomie języka maszynowego musi zostać wykonane kilka instrukcji obsługujących to przejście z miejsca wywołania w inne miejsce programu. Poza tym po wykonaniu funkcji muszą się wykonać pewne operacje związane z np. zwalnianiem pamięci, a to trochę trwa.

Jeśli więc jakaś funkcja jest wielokrotnie wywoływana w trakcie programu, to czas może być istotnym czynnikiem. Możemy więc zamiast wywoływać funkcje, w miejscach tych wpisać jej treść, co przy dużych funkcjach ( kilkaset linii) jest bardzo żmudne, aczkolwiek program wykona się szybciej.

Opracowano więc sposób postepowania, który charakteryzuje się

1. jasnością zapisu - jak w zwykłej funkcji

2. dużą szybkością działania - jak w przypadku wpisania treści w linii( ang. inline - stąd nazwa funkcji)

Sposób definiowania takiej funkcji jest prosty:

inline int dodaj(int a, int b)

{

return (a + b);

}

Widzimy więc, że nic poza dodatkowym słowem inline się w opisie funkcji nie zmienia. Ilekroć w programie umieścimy nazwę funkcji tylekroć kompilator umieści jej treść w linii, w której to wywołanie nastapiło. Nie będzie więc żadnych dodatkowych działań ze strony procesora - kod wykona się szybciej. Funkcje tego typu w przeciwieństwie do standardowych funkcji muszą być umieszczone na początku programu, razem z jej deklaracją. Cialo takiej funkcji musi być bowiem znane kompilatorowi już na etapie kompilacji.





Przykłady definiowania makr

- makro typu obiekt

#define identyfikator treść

#define ESC 27

#define TRUE 1

#define Imie "ANNA"



#define ROZ 10

void funkcja(...)

{

int tablica[ROZ];

............

}

Dyrektywa w tym przypadku zamienia w kompilowanym tekście identyfikator na ciąg znaków treść.

Makra typu obiekt najczęściej używane są do definiowania stałych . Jest to też przeżytek z języka dawnego C. Lepszym narzędziem jest teraz modyfikator const (lepszym bo jest to normalny obiekt w pamięci do którego można odwoływać się poprzez wskaźnik, można zadeklarowac mu określony zasięg - globalny lokalny itp. inne dodatkowe strony użycia operatora const - prosze przemyśleć samodzielnie).

-makro typu funkcja

#define identyfikator(iden1....identn) treść

Po nazwie identyfikatora, a przed nawiasem nie może być spacji.

#define pole_pow_kuli(x) 4*pi*x*x

wtedy wywołanie makra w treści prgramu:

P = pole_pow_kuli(r);

jest rozwijane do postaci:

P= 4*pi*r*r;

Zastępowanie nazw makr rozwinięciami dokonuje się w całym pliku źródłowym. Nie dokonuje sie wtedy, gdy:

- identyfikator występuje w komentarzu

- identyfikator występuje wewnątrz stringu.

- lub jako część dłuższj dyrektywy.

Czasami użycie makr daje nieoczekiwane wyniki, np. w treści makra występuje kilkakrotnie ten sam parametr i w dodatku na tym parametrze chcemy wykonać operację inkrementacji np.

#define KWADRAT(a) ((a) * (a))

main()

{

..............

int x = 4,p;

p = KWADRAT(x++);

cout<
Dlaczego mamy taki efekt? Po rozwinięciu makrodefinicji wyrażenie ma postać:

p = ((x++) *(x++));

czyli dwukrotnie nastapiła inkrementacja x. W tym przypadku lepiej jest użyć funkcji inline.

Należy też zwrócić uwagę na stosowanie nawiasów w treści makr np.

#define W 80

#define L (W+80)

a) var = L*20 => var = (80+80)*20 = 320;



define L W+80

b) var = L*20 => var = W +80*20 = 240



Powtórne użycie dyrektywy define dla tego samego identyfikatora generuje błąd, chyba że treść makra jest identyczna z poprzednią.

W kontekście dyrektywy define używane są cztery operatory:

operator #

Operator ten po rozwinięciu makra przekształca parametry w stałą stringową, dodając na ich początku i na końcu znaki ".

Używany jest tylko dla makr z argumentami. Np.:

#define string(x) printf(#x,"
");

main()

{

string(Test
);

rozwinięcie makra da efekt:

printf("test
" "
");

operator ##

operator skleja dwie sąsiadujące z nim jednostki leksykalne w jedno usuwając operator i otaczające go spacje. Nie może on być w związku z tym ani pierwszym, ani ostatnim znakiem treści makra np.:

#define CONCAT_STR( str1,str2) (str1##str2)

rozwinięcie wywołania:

CONCAT_STR(AL, DONA)

daje:

ALDONA

operator #@

Używany tylko z argumentami makra, jeśli poprzedza parametr w definicji makra, aktualny parametr ujmowany jest w skowyczki:' i traktowny jak znak w czasie rozwinięcia makra. Jest analogiczny z operatorem # np.:

#define makechar(x) #@x

wtedy instrukcja w programie:

a = makechar(b);

będzie rozwinięta w:

a = 'b';

Znak ' nie może być użyty z tym operatorem.



Dyrektywa #undef



#undef identyfikator

usuwa( odwołuje definicję) identyfikator, ostatnio utworzony dyrektywa define. W konsekwencji , pojawienie się identyfikatora będzie ignorowane przez procesor. Jeśli nie było wcześniej zdefiniowanego takiego identyfikatora, to dyrektywa ignorowana jest przez procesor. Jeśli nie ma użytego undef to identyfikator zdefiniowany przez define ważny jest do końca pliku.



Makrodefinicje mogą być często bardzo pożytecznym narzędziem, o czym często się zapomina. O ich przydatności może świadczyć fakt, iż wiele funkcji bibliotecznych zdefiniowanych jest jako makra.



Dyrektywa #include



Jest to dyrektywa, z którą już częściowo się państwo juz spotkaliście.

#include

#include"nazwa_pliku"

Dyrektywa ta zaleca zastąpienie tego wiersza zawartością pliku o podanej nazwie. W plikach tych można definiować stałe, makra, prototypy funkcji.

W pierwszym przypadku przeszukiwane są standardowe miejsca przeznaczone na tego typu zbiory np. katalogi LIB, INCLUDE. W drugim w pierwszej kolejności przeszukiwany jest bieżący katalog, a potem standardowe katalogi. Jeśli podamy konkretny katalog , szukanie odbędzie się tylko w tym katalogu.





Kompilacja warunkowa



W tym miejscu omówimy mechanizmy sterowania przebiegiem komilacji, czyli tzw. komilację warukową. Dyrektywy kompilacji warunkowej pozwalaja na wykonanie lub nie, komilacji części pliku źródłowego w wyniku testowania stałych wyrażeń lub identyfikatorów, które wyznaczają które bloki tekstu są kompilowane, a które usuwane ze zbioru źródłowego w czasie preprocesingu.

Dyrektywy #if z #elif #else i #endif, kontrolują kompilację części zbioru źródłowego. Jeśli wyrażenie które występuje po #if ma niezerową wartość to wykonywana jest kompilacja warukowa częsci tekstu występującego po nim. Składnia najprostrzej dyrektywy ma ostać:

#if warunek

//.. linie kompilowane warunkowo

#endif (warunek*) *-opcjonalnie - może ale nie musi wystapić

Niektóre kompilatory nie zezwalaja na stosowanie warunku w dyrektywie #endif, ale jeśli jest to możliwe należy korzystać z tej możliwości szczególnie w przypadku zagnieżdżonych dyrektyw - ułatwia to analizę programu

Składnia bardziej skomlikowanej dyrektywy ma postać:

#if warunek

// instrukcje komilowane warunkowo1

#else

// instrukcje kompilowane warunkowo2

#endif

W zależności od warunku do kompilacji wchodzą albo instrukcje 1 albo 2.

Dyrektywa #elif jest konstrukcją analogiczną do grupy else - if.

Generalnie dyrektywy kompilacji warunkowej można zapisać:

#if wyrażenie stałe-1

< sekcja instrukcji - 1>

<#elif wyrażenie stałe-2>

< sekcja instrukcji - 2>

:

<#elif wyrażenie stałe-n>

< sekcja instrukcji - n>

<#else>



#endif

Obszar warunkowej kompilacji zawiera się pomiędzy dyrektywą:#if oraz #endif. Wyrażenia stałe muszą mieć wartość całkowitą. Działanie w/w dyrektyw jest nastepujące:

1. Jeśli wyrażenie stałe-1 ma wartość różną od zera, wówczas preprocesor przetwarza sekcje instrukcji-1, po czym sterowanie przekazywane jest do instrukcji #endif, kończącej obszar kompilacji warunkowej. W przeciwnym razie, sterowanie przejmuje pierwsza dyrektywa #elif.

2. Obliczana jest wówczas wartość wyrażenia stałego -2. Jeśli jest ona niezerowa, preprocesor przetwarza sekcję instrukcji-2 i przekazuje sterowanie do instrukcji #endif. W przeciwnym razie podobnej procedurze poddawane są następne dyrektywy #elif.

3. Sterowanie jest przekazywane do dyrektywy #else, jeśli wszystkie testowane wcześniej wyrażenia, miały wartość zerową. Preprocesor przetwarza wtedy końcową sekcję instrukcji i oddaje sterowanie dyrektywie#endif.

Dowolna liczba dyrektyw #elif może byż zagnieżdżona, ale tylko jeden #else może wystapić przed #endif.

Omówione powyżej dyrektywy zilustuje przykład:

#include



#define stopien 3

double potega(double x)

{

#if stopien <0

#error ujemna potega!!

#elif stopien == 0

return 1;

#elif stopien == 1

return x;

#elif stopien == 2

return x*x;

#elif stopien == 3

return x*x*x;

#else

int i;

double il = 1;

for (i=0;i
il*=x;

return il;

#endif

}

int main(void)

{

double x=10;

cout<
return 0;

}

Wartość stałej określonej przez makrodefinicje stopień wpływa na postać ciała funkcji potęga. W zależności od tego, jaką potęgę zadanej liczby ma obliczać funkcja ciało funkcji przybierać będzie inny zapis. Należy zaznaczyć, że w programie generowana jest jedna funkcja do obliczania jednej konkretnej potęgi, a nie rodzina funkcji obliczających potęgi różnych stopni.

W programie wystapiła inna dyrektywa preprocesora:













Dyrektywa #error



#error tekst

Po napotkaniu tej dyrektywy kompilacja zostaje przerwana i zostaje wypisany komunikat - tekst np:

#if !defined (__bcplusplus)

#errer BC++ compiler required

#endif

Powyżej zdefiniowane dyrektywy można wspomagać czwartym operatorem preprocesora:

operator defined

zwracającym wartość różną od zera gdy wymieniony po nim identyfikator został już wcześniej zdefiniowany:

defined identyfikator

Operator ten może być użyty tylko w dyrektywie #if lub #elif, ale nigdzie indziej:

#if defined (CREDIT)

credit();

#elif defined (DEBIT)

debit();

#else

printerroe();

#endif

W praktyce zamiast niego zwykle używa się dyrektywy #ifdef, jednak używanie operatora define pozwala na używanie w dyrektywie #if bardziej złożonych wyrażeń:

#if defined(_TINY_) && defined(_BCPLUSPLUS_) && !defined(_PASCAL_).

TINY, BCPLUSPLUS, PASCAL sa to predfiniowane makrodefinicje preprocesora.





Dyrektywy #ifdef , #ifndef



Dyrektywy te wykonują to samo zadanie co #if, gdy używana jest z operatorem defined. Czyli zapis:

#ifdef identyfikator #ifndef identyfikator

jest ekwiwalentnu z :

#if defined identyfikator #if !defined identyfikator

Dyrektywy te pozostały ze względu na kompatybilność z poprzednimi wersjami języka, ale ze względów wcześniej podanych poleca się stosowanie dyrejtyw #if w połączeniu z operatorem defined.





Dyrektywy #pragma



Jest to zbiór dyrektyw zależnych od implementacji. Mogą one wykonywać rózne zadania np.: wyłączać ostrzeźenie "Parametr is never used in function ", pozwalają określać jakie funkcje mają być wywołane w momencie rozpoczynania wykonywania programy(przed wywołaniiem funkcji main) lub po jego zakończeniu, informuje że w programie znajdują się framgmenty bezpośrednio w asemblerze- pozwala to skrócić nieco czas kompilacji, itd. itp.

Dyrektywa #line



#line const "file name"*

informuje preprocesor aby zamienił wewnetrznie zapamiętamy numer lini i nazwę zbioru na podane w parametrach dyrektywy. Kompilator używa numeru linii oraz nazwy zbioru w odniesieniu do błędów które napotkał w czasie kompilacji.

Bieżący numer linii i nazwa zbioru są zawsze dostępne poprzez predefiniowane makra __LINE__ oraz __FILE__.

Np. instrukcja:

#line 151 "copy.cpp)

powoduje że wewnętrzy numer linii zamieniany jest na 151 , bieżąca nazwa zbiory na copy.cpp. Dyrektywa ta zwykle stosowana jest przez generatory programów do formułowania komunikatów o błędach nie w kodzie wynikowym, ale w zbiorach wejściowych definiujących problem dla generatora kodu.

#line 151 "copy.cpp"

#define ASSERT(cond)

((cond) ? (void) 0 :

((void)(cerr << "assertion failure "" << #cond <<

""line" << __LINE__ <<

"file (" << __FILE__ << ")
")))

W powyższym przykładzie makro ASSERT używa predefiniowanych identyfikatorów LINE oraz FILE to wysłania komunikatu o błędzie w zbiorze źródłowym jeśli dane"assertion" nie jest prawdziwe.





Czy tekst był przydatny? Tak Nie
Przeczytaj podobne teksty

Czas czytania: 14 minuty