Directive preprocesor si metodologie de programare Folosirea lui #include Am discutat deja folosirea directivelor de preprocesare #include
#include
O alta forma pentru #include este #include 'nume_fisier'
Preprocesorul va inlocui aceasta linie cu o copie a fisierului precizat. Mai intai cautarea se face in directorul curent, apoi in alte locuri dependente de sistem. Daca directiva este de forma #include
atunci preprocesorul va cauta in alte locuri (deci nu in directorul curent). De exemplu, sub UNIX, fisierele header standard (cum ar fi 'stdio.h', 'stdlib.h') se gasesc de obicei in directorul /usr/include
Sub MS-DOS, aceste fisiere se gasesc in directorul /include
Folosirea lui #define Directivele de preprocesare declarate cu '#define' au doua forme: - #define identificator sir_atomi
- #define identificator(id,,id) sir_atomi
O definitie lunga (care nu dorim sa o scriem pe aceeasi linie poate fi continuata pe linia urmatoare punand un (backslash) la sfarsitul liniei curente). In primul caz, compilatorul va inlocui fiecare aparitie a 'identificatorului' prin 'sir_atomi' in restul fisierului (de la pozitia curenta in jos) cu exceptia celor care sunt incadrate intre ghilimele sau apostroafe. Exemple: #define NR_SEC_PE_ZI (60 * 60 * 24)
#define PI 3.141592653
#define C 299792.458 /* viteza luminii in km/sec */
#define EOF (-1) /* valoarea uzuala pt sfarsit de fisier */
#define MAXINT 2147483647 /* numarul intreg maxim pe 4 octeti */
#define DIMENS 250 /* dimensiunea unui sir */
#define EPSILON 1.0e-9 /* limita numerica */
Deci, folosirea lui '#define' mareste claritatea si portabilitatea unui program. Sintaxa 'dulce' Se foloseste pentru evitarea unor greseli frecvente sau ca un moft. Exemplu: #define EQ == Aceasta declaratie ajuta programatorul sa nu mai confunde = cu ==. Exemplu: #define do /* spatiu */ De exemplu, acum putem simula instructiunea 'while' din C ca un 'while do' din Pascal sau Algol. De exemplu, daca avem definitiile de sintaxa 'dulce' de mai sus, putem spune ca instructiunile while (i EQ 1) do
si while (i == 1)
sunt echivalente. Macrouri cu argumente Revenim la forma a doua a macrourilor cu argumente: #define identificator(id,,id) sir_atomi
Exemplu: #define SQ(x) ((x) * (x)) Identificatorul x din #define este un parametru care va fi substituit in textul ce urmeaza. Substitutia se face fara considerarea corectitudinii sintactice. De exemplu, SQ(7 + w) este echivalent cu ((7 + w) * (7 + w))
Intr-o maniera similara, SQ(SQ(*p)) este echivalent cu ((((*p) * (*p))) * (((*p) * (*p)))) Observati deci ca folosirea parantezelor (de exemplu, (x)) are o importanta deosebita, altfel nu s-ar respecta ordinea de evaluare. Unde este greseala ? #define SQ(x) ((x) * (x)); Macrourile sunt folosite de obicei pentru a inlocui apelurile functiilor cu cod liniar (scurte si fara variabile suplimentare). Exemplu: Macroul de mai jos defineste minimul a doua valori: #define min(x, y) (((x) < (y)) ? (x) : (y))
Dupa aceasta definitie, o expresie de forma m = min(u, v)
se poate expanda de catre preprocesor la m = (((u) < (v)) ? (u) : (v))
Folosind aceasta definitie, putem defini minimul a patru valori, astfel #define min4(a, b, c, d) min(min(a,b), min(c, d))
O macro-definitie poate folosi functii si macrouri in corpul lor. Exemple: #define SQ(x) ((x) * (x))
#define CUB(x) (SQ(x) * (x))
#define F_POW(x) sqrt(sqrt(CUB(x)))
O directiva de preprocesare de forma #undef identificator
va anula definitia precedenta a identificatorului. Definitii de tipuri si macrouri din C pune la dispozitie facilitatea 'typedef' pentru a asocia (redenumi) un tip cu unul specific. Exemplu: typedef char uppercase; Declaratia de mai sus face tipul 'uppercase' sinonim cu 'char'. De exemplu, declaratiile de mai jos sunt valide: uppercase c, u[100];
Fisierul header contine cateva definitii de tip: typedef int ptrdiff_t; /* tip intors de diferenta pointerilor */ typedef short wchar_t; /* tip caracter mare */ typedef unsigned size_t; /* tipul sizeof */ Tipul 'ptrdiff_t' spune care este tipul returnat de o expresie implicata in diferenta a doi pointeri. In MS-DOS, acesta depinde de modelul de memorie ales (tiny, short, large, far, huge), pe cand in UNIX, tipul folosit este 'int'. Tipul 'wchar_t' se foloseste pentru acele caractere care nu se pot reprezenta pe un octet (char -> int). Reamintim ca operatorul 'sizeof' este folosit pentru determinarea lungimii unui tip sau a unei expresii. De exemplu, 'sizeof(double) = 8'. Tipul 'size_t' este returnat de operatorul 'sizeof'. Un macrou definit in este #define NULL 0
Sortare folosind 'qsort()' Daca avem o multime relativ mica de elemente, atunci putem sa folosim sortare cu bule sau metoda sortarii prin selectie directa (care sunt de ordinul O(n^2)). Daca insa avem multe elemente, atunci este convenabil sa folosim metoda sortarii rapide ('quick sort'). Prototipul functiei 'qsort()' se gaseste in . Acesta este void qsort(void *array, size_t n_els, size_t el_size, int compare(const void *, const void *));
Argumentele acestei functii au rolul: array - sirul care va fi sortat;
n_els - numarul de elemente ale sirului;
el_size - numarul de octeti necesar memorarii unui element;
compare - functia de comparare, ce se declara ca fiind int compare(const void *, const void *)
Functia de comparare are ca argumente doi pointeri catre void. Aceasta returneaza un intreg care este mai mic, egal sau mai mare decat zero dupa cum primul argument este mai mic, egal sau mai mare decat al doilea argument. Exemplu: Vom scrie un program ce foloseste 'qsort()'. Initializam un vector, il tiparim, il sortam cu 'qsort()', apoi il tiparim din nou. #include #include #include #define N 11 /* dimensiunea sirului */ int cmp(const void *vp, const void *vq); /* functia de comparare */ void init(double *a, int n); void tipareste_sir(double *a, int n); void main() int cmp(const void *vp, const void *vq) void init(double *a, int n) void tipareste_sir(double *a, int n) putchar('n'); }
Intrebari: 1. Ce trebuie sa modificati pentru a obtine ordinea crescatoare a sirului ? ---------- 2. Ce rol are 'const' din ddeclaratia lui 'cmp()' ? Un exemplu de utilizare a macrourilor cu argumente Vom relua problema de mai sus, dar vom folosi macrouri cu argumente. Vom scrie programul in doua fisiere, un fisier header 'sort.h' si un fisier 'sort.c'. Fisierul header va contine directive de precompilare (#include, #define), precum si prototipuri pentru functiile noastre. Fisierul 'sort.h' este: #include #include #include #include #define M 32 #define N 11 #define parte_fractionara(x) (x - (int) x) #define caracter_aleator() (rand() % 26 + 'a') #define real_aleator() (rand() % 100 / 10.0) #define INIT(array, sz, type) if (strcmp(type, 'char') == 0) for (i = 0; i < sz; ++i) array[i] = caracter_aleator(); else for (i = 0; i < sz; ++i) array[i] = real_aleator();
#define PRINT(array, sz, sir_control) for (i = 0; i < sz; ++i) printf(sir_control, array[i]); putchar('n') int compara_partea_fractionara(const void *, const void *); int lexico(const void *, const void *); Acum, vom scrie restul codului pentru programul nostru, si anume fisierul 'sort.c'. #include 'sort.h' void main() int compara_partea_fractionara(const void *vp, const void *vq) int lexico(const void *vp, const void *vq) Compilare conditionata Preprocesorul are directive pentru compilare conditionata. Acestea pot fi folosite pentru dezvoltarea programelor si pentru scrierea codului mai portabil de la o masina la alta. Fiecare directiva de forma #if expresie_integrala_constanta
#ifdef identificator
#ifndef identificator
implica compilarea conditionata a codului care urmeaza pana la directiva de precompilare #endif
Pentru compilarea codului de mai sus, in cazul lui #if trebuie ca expresia constanta sa fie diferita de zero (true), in cazul lui #ifdef sau #ifdefined numele identificatorului trebuie sa fie definit anterior intr-o linie #define, fara interventia directivei #undef identificator
In cazul lui #ifndef, numele identificatorului trebuie sa nu fie curent definit. Expresia constanta integrala folosita intr-o directiva de precompilare nu poate contine operatorul 'sizeof' sau un cast. Poate insa, folosi operatorul de precompilare 'defined' (valabil in ANSI C, dar nu si C traditional). Expresia defined identificator este echivalenta cu defined(identificator)
Acesta se evalueaza la 1 daca identificatorul este definit, si 0 in caz contrar. Exemplu: #if defined(HP9000) || defined(SUN4) && !defined(VAX)
. . . . . /* cod dependent de masina */
#endif
Uneori 'printf()' este utila in scopuri de depanare. Presupunem ca la inceputul unui fisier am scris #define DEBUG 1
si in unele zone ale programului am scris #if DEBUG
printf('debug: a = %dn', a);
#endif
Daca dupa ce ne-am convins ca este bine ce se intampla si vrem sa nu mai vizualizam valoarea lui 'a' in acest moment, atunci schimbam DEBUG in 0 (de exemplu). O alta varianta ar fi sa nu initializam DEBUG. Scriem deci la inceputul fisierului #define DEBUG
Putem folosi #ifdef si #if si scriem: #ifdef DEBUG
#endif
Macrouri predefinite In ANSI C sunt 5 macrouri predefinite. Nu pot fi redefinite de catre programator. Ele au la inceput si sfarsit cate doua simboluri 'underscore'. Macro predefinit Valoare __DATE__ Un sir ce contine data curenta
__FILE__ Un sir ce contine numele fisierului
__LINE__ Un intreg reprezentand numarul liniei curente
__STDC__ Daca implementarea=ANSI C, atunci acesta
reprezinta un numar diferit de zero
__TIME__ Un sir ce contine timpul curent
Operatorii # si ## Operatorii de preprocesare # si ## sunt valabili in ANSI C, dar nu si in C traditional. Operatorul unar # cauzeaza transformarea in sir a unui parametru formal dintr-o macro-definitie. #define mesaj_pentru(a, b)
printf(#a ' si ' #b ': Te iubim !n')
void main()
La apelul acestui macrou, fiecare parametru al acestuia este inlocuit cu argumentul corespunzator, iar # cauzeaza ca argumentele sa fie puse intre ghilimele. Altfel spus, dupa preprocesare, in memorie se obtine: void main()
Deoarece sirurile constante separate prin spatiu se concateneaza, instructiunea de mai sus este echivalenta cu: void main()
Operatorul binar ## este folosit la impartirea in tokenuri lexicale. Exemplu: ------------ #define X(i) x ## i X(1) = X(2) = X(3);
va deveni dupa preprocesare x1 = x2 = x3;
Macroul 'assert()' ANSI C pune la dispozitie macroul 'assert()' din biblioteca standard 'assert.h'. Acest macrou poate fi folosit cand vrem sa ne asiguram ca o expresie are o anumita valoare. Vrem sa scriem o functie ale carei argumente satisfaca niste conditii. Exemplu: #include
void f(char *p, int n)
Daca vreo asertiune esueaza, atunci sistemul va tipari un mesaj si va opri executia programului. Iata o implementare posibila a lui 'assert()'. #if defined(NDEBUG)
#define assert(ignore) ((void) 0) /* ignorare */ #else
#define assert(expr) if (!(expr)) #endif
De remarcat ca daca NDEBUG este definit, atunci sunt ignorate toate asertiunile. Aceasta permite programatorului in timpul scrierii programului sa verifice pas cu pas executia programului. Functia 'abort()' se gaseste in biblioteca standard. Folosirea lui #error si #pragma ANSI C contine si directivele de preprocesare #error si #pragma. Exemplu: #if A_SIZE < B_SIZE
#error 'tipuri incompatibile'
#endif Daca in timpul compilarii va apare o eroare prezenta intr-o directiva #error, atunci se va afisa mesajul respectiv. Directiva #pragma se foloseste pentru folosire specifica implementarii. Ea are forma generala: #pragma atomi_lexicali
Aceasta cauzeaza o comportare ce depinde de fiecare compilator C in parte. Numerele liniilor unui program O directiva de preprocesare de forma #line constanta_integrala 'nume_fisier'
va determina compilatorul sa renumeroteze liniile textului sursa astfel incat urmatoarea linie sa aiba valoarea specificata si numele fisierului sursa curent este 'nume_fisier'. Daca nu se precizeaza 'nume_fisier', atunci se va face doar numerotarea liniilor. Bineinteles, numerele asociate liniilor sunt ascunse pentru programator si apar numai la mesaje de eroare sau avertismente. Exercitii propuse spre implementare 1. Scrieti propria voastra functie 'quicksort()' care sa fie echivalenta cu 'qsort()' pus la dispozitie de sistemul C. 2. Definiti o macro-definitie pentru XOR(), numita 'sau exclusiv'. Un apel XOR(a,b)=true <=> a este true si b false, sau a false si b true. Scrieti si o macro-definitie XOR(a,b,c) si una XOR(a,b,c,d). 3. Scrieti un program C in care sa afisati valorile celor 5 macrouri predefinite.