Atomi lexicali, operatori, sistemul C Ca si alte limbaje, C are un alfabet si reguli pentru scrierea programelor corecte folosind semne de punctuatie. Aceste reguli formeaza sintaxa limbajului C. Compilatorul C are rolul de a testa daca un program C este corect. Daca sunt erori, atunci va afisa o lista de mesaje de eroare si se va opri. Daca nu sunt erori, atunci compilatorul va 'traduce' acest program in cod obiect, folosit de incarcator pentru producerea codului executabil. Mai intai compilatorul imparte multimea caracterelor (programul sursa) in atomi lexicali, care reprezinta vocabularul de baza al limbajului. In ANSI C (ANSI = American National Standards Institute) sunt sase tipuri de atomi lexicali (care se mai numesc si elemente lexicale sau unitati lexicale): 1. cuvinte rezervate (sau cheie);
2. identificatori;
3. constante;
4. siruri constante;
5. operatori;
6. semne de punctuatie.
Caractere si atomi lexicali In fapt, un program C este o secventa de caractere. Caracterele permise in programele C sunt: 1. litere mici : a b z
2. litere mari : A B Z
3. cifre : 0 1 9
4. alte caractere: d f * / = ( ) [ ] < > ' '
! @ # $ % & _ | ^ ~ . , ; : ?
5. spatii : blank, newline si tab
Comentarii Comentariile sunt siruri de caractere cuprinse intre /* si */. Comentariile nu reprezinta atomi lexicali. Compilatorul va traduce comentariile intr-un singur caracter spatiu, de aceea comentariile nu fac parte din codul executabil. Atentie ! Pentru a verifica aceasta, puteti citi lungimea unui cod executabil (fara comentarii) si ----------- apoi sa comparati lungimea ccodului executabil obtinut dupa o noua compilare (cu comentarii).
Exemple de comentarii: 1. /* un comentariu */ 2. /** al doilea comentariu **/ * Al patrulea
* comentariu
*/
* Al cincilea * * comentariu * Avantajele folosirii comentariilor: 1. Principalul scop este usurarea documentarii ulterioare. Scopul documentarii este explicarea clara a folosirii programelor; 2. Uneori un comentariu poate contine informatii ce argumenteaza demonstratia corectitudinii acelui algoritm; 3. Sfat ! Folositi comentariile in timpul introducerii textului programului. Cuvinte rezervate Cuvintele rezervate (cheie) au un inteles strict insemnand un atom individual. Ele nu pot fi redefinite sau utilizate in alte contexte. Iata lista lor: auto do goto signed unsigned
break double if sizeof void
case else int static volatile
char enum long struct while
const extern register switch
continue float return typedef
default for short union
Anumite implementari pot contine si alte cuvinte rezervate: asm cdecl far huge interrupt near pascal
Comparativ cu alte limbaje de programare, C are un numar mic de cuvinte rezervate. Ada, de exemplu, are 62 cuvinte rezervate. Aceasta este o caracteristica a limbajului C de a avea doar cateva simboluri speciale si cuvinte rezervate. Identificatori Un identificator este un atom lexical compus din secventa de litere, cifre sau underscore ('_') cu restrictia ca primul caracter este o litera sau underscore. In multe implementari C, se face distinctie dintre litere mici si mari. In general, se obisnuieste ca identificatorii sa fie scrisi cu nume sugestive care sa usureze citirea si documentarea programului. Exemple: 1. k, _id, contor, un_identificator sunt identificatori; 2. gresit#unu, 100_gresit_doi, -plus nu sunt identificatori. Identificatorii sunt creati pentru a da nume unice pentru diverse obiecte dintr-un program. Cuvintele rezervate pot fi privite ca fiind identificatori. Identificatori precum 'printf()' sau 'scanf()' sunt deja cunoscuti sistemului C ca fiind functii de intrare/iesire. O diferenta majora dintre sistemele de operare si sistemele C o reprezinta lungimea admisa pentru numele identificatorilor. Astfel, pentru unele sisteme vechi, este acceptat un identificator al carui nume are mai mult de 8 caractere, dar numai primele 8 sunt semnificative. De exemplu, identificatorul _23456781 este privit la fel ca _23456782. In ANSI C, primele 31 de caractere sunt luate in considerare. Atentie ! Identificatorii care incep cu underscore pot fi confundati cu numele variabilelor sistem. De exemplu, identificatorul _iob declarat in biblioteca este folosit pentru numele unui vector de structuri. Daca un programator foloseste un identificator cu acelasi nume, dar pentru alte scopuri, atunci ori se va semnala o eroare aparent necunoscuta, ori (si mai rau) compilatorul se va comporta ciudat. Recomandarea este: Nu folositi identificatori care incep cu underscore. Constante C manipuleaza diferite tipuri de valori. Numere precum 0 si 17 sunt exemple de constante intregi, iar numere precum 1.0 si 3.14159 sunt exemple de constante numere zecimale. Ca si multe alte limbaje, C trateaza constantele 'int' si 'float' in mod diferit. Constantele caracter sunt foarte apropiate de tipul 'int' (vom reveni). Un caracter special l-am si intalnit deja. Este vorba de 'n', care se mai cheama 'secventa escape'. In traducere libera, ar insemna 'evadare a lui n din intelesul uzual'. In fapt, el este folosit pentru a trece cursorul curent la linie noua (newline). Constantele de intregi, reali, caractere si enumerare sunt toate colectate de compilator ca fiind atomi lexicali. Din cauza limitelor impuse de memoria masinilor, unele constante care pot fi exprimate sintactic nu pot fi disponibile pe o masina anume. De exemplu, numarul 123456789000000000000 nu poate fi memorat ca fiind un intreg. Siruri constante O secventa de caractere incadrate intre ghilimele, de exemplu 'abc', este un sir constant. Este inteles de compilator ca fiind un singur atom lexical. In capitolele ulterioare, vom vedea ca de fapt sirurile constante se memoreaza ca siruri de caractere. Sirurile constante sunt tratate mereu diferit fata de constantele de tip caracter. De exemplu, 'a' nu este totuna cu 'a'. De mentionat ca ghilimeaua ' reprezinta un singur caracter, nu doua. De aceea, daca dorim sa apara intr-un sir constant, atunci ea trebuie precedata de (backslash). Daca dorim ca intr-un sir sa apara , atunci trebuie sa-l precedam tot cu (devenind astfel ). Exemple: 1. 'sir text' /* sirul vid */
3. ' ' /* sir de spatii */ 4. ' a = b + c ' /* nu se executa nimic */ 5. ' /* acesta nu este un comantariu */ ' 6. ' un sir ce contine ghilimea ' ' 7. ' un sir ce contine backslash ' 8. /* 'gresit' */ /* nu este un sir */ 9. 'gresit doi' /* nici asta nu este sir */
Doua siruri constante care sunt separate doar printr-un spatiu vor fi concatenate de compilator intr-unul singur. De exemplu, 'abc' 'def' este echivalent cu 'abcdef'
Aceasta este o trasatura a limbajului ANSI C, nefiind disponibil in C traditional. Sirurile constante sunt tratate de compilator ca atomi lexicali. Ca si alte constante, compilatorul va rezerva spatiu in memorie pentru pastrarea sirurilor constante. Operatori si semne de punctuatie In C, exista multe caractere speciale cu inteles specific. De exemplu, operatorii aritmetici reprezinta adunarea, scaderea, inmultirea, impartirea, modulul, respectiv. Reamintim (pentru bubulici) ca a % b inseamna restul impartirii intregi a lui a la b (notatie matematica: a mod b; a nu se confunda modul cu valoarea absoluta). De exemplu, 5 % 3 are valoarea 2. Atentie la numere intregi negative (Vezi Exercitiul 1). Anumite simboluri au intelesuri dependente de context. Consideram simbolul % din instructiunile printf('%d', a); si a = b % 7; Primul simbol % este un format de scriere, pe cand al doilea reprezinta operatorul modul. In exemplul de mai jos, parantezele (,) se folosesc atat pentru a preciza ca () este un operator ('main' reprezinta numele unei functii), cat si ca semne de punctuatie. main() Anumite caractere speciale sunt folosite in multe contexte. Fie espresiile a + b ++a a += b Ele folosesc caracterul +, dar ++ este un singur operator, la fel ca si +=. Operatorii de precedenta si asociativitate Operatorii au reguli de precedenta si asociativitate care implica evaluarea expresiilor. Din moment ce expresiile din interiorul parantezelor se evalueaza mai intai, este clar ca parantezele sunt folosite pentru a preciza care operatii se fac mai intai. Consideram expresia 1 + 2 * 3
In C, operatorul * are prioritate (precedenta) mai mare decat +, deci se va face intai inmultirea apoi adunarea. Deci valoarea expresiei este 7. O expresie echivalenta este 1 + (2 * 3)
Pe de alta parte, expresia (1 + 2) *3 este diferita; ea are valoarea 9. Consideram acum expresia 1 + 2 - 3 + 4 - 5. Operatorii + si - au aceeasi precedenta, deci se va folosi regula de asociativitate la stanga. Astfel (((1 + 2) - 3) + 4) -5 este o expresie echivalenta. In continuare vom prezenta un tabel in care precizam regulile de precedenta si asociativitate pentru cativa operatori din C. Operatori | Asociativitate |
| () ++ (postfix) -- (postfix) | de la stanga la dreapta | | +(unar) -(unar) ++(prefix) --(prefix) | de la dreapta la stanga | | * / % | de la stanga la dreapta | | + - | de la stanga la dreapta | | = += -= *= /= etc. | de la dreapta la stanga | Toti operatorii de pe o linie (de exemplu, *, /, %) au aceeasi prioritate intre ei, dar au prioritate mai mare decat cei ce apar in liniile de mai jos. Operatorii + si - pot fi si binari si unari. De remarcat ca cel unar are prioritate mai mare. De exemplu, in expresia - a * b - c
primul operator - este unar, pe cand al doilea binar. Folosind regulile de precedenta, se vede ca aceasta este echivalenta cu ((- a) * b) - c
Operatorii de incrementare si decrementare Operatorii de incrementare si de decrementare (++, --) au o prioritate foarte mare (dupa cum se poate vedea in tabelul de mai sus) si se pot asocia atat de la dreapta la stanga, cat se de la stanga la dreapta. Operatorii ++ si -- se pot aplica variabilelor, dar nu si constantelor. Mai mult, ei pot apare ca notatie prefixata, cat si postfixata. De exemplu, putem avea ++i si contor++, dar nu putem avea 167++ sau ++(a * b - 1). Fiecare din expresiile ++i si i++ au o valoare; mai mult fiecare cauzeaza incrementarea valorii variabilei i cu o unitate. Diferenta este: 1. expresia ++i va implica intai incrementarea lui i, dupa care
expresia va fi evaluata la noua valoare a lui i; 2. expresia i++ va implica evaluarea sa la valoarea lui i, dupa
care se va incrementa i. Exemplu: int a, b, c = 0; a = ++c; b = c++; printf('a=%d b=%d c=%d ++c=%dn', a, b, c, ++c); Intrebare: Ce se va tipari la ecran ? Intr-un mod similar, --i va implica decrementarea valorii lui i cu 1, dupa care expresia --i va avea noua valoare a lui i, pe cand i-- se va evalua la valoarea lui i, dupa care i se va decrementa cu 1. Retineti deci ca, spre deosebire de + si -, operatorii ++ si -- vor determina schimbarea valorii variabilei i din memorie. Se mai spune ca operatorii ++ si -- au efect lateral (side effect). Daca nu folosim valoarea lui ++i sau a lui i++, atunci acestea sunt echivalente. Mai precis, ++i; si i++;
sunt echivalente cu i = i + 1;
Exemple: Presupunem ca avem declaratiile int a = 1, b = 2, c = 3, d = 4; Atunci avem: Expresie Expresie echivalenta parantetizata Valoare a * b / c (a * b) / c 0
a * b % c + 1 ((a * b) % c) + 1 3 ++ a * b - c -- ((++ a) * b) - (c --) 1 7 - - b * ++ d 7 - ((- b) * (++ d)) 17 Operatori de asignare Pentru schimbarea valorii unei variabile, am utilizat deja instructiunea de asignare (atribuire), cum ar fi a = b + c; Spre deosebire de celelalte limbaje, C trateaza = ca un operator. Precedenta sa este cea mai mica dintre toti operatorii si asociativitatea sa este de la dreapta la stanga. O expresie de asignare simpla are forma: variabila = parte_dreapta
unde 'parte_dreapta' este o expresie. Daca punem ; la sfarsitul expresiei de asignare, atunci vom obtine instructiune de asignare. Operatorul = are doua argumente, 'variabila' si 'parte_dreapta'. Valoarea expresiei 'parte_dreapta' este asignata pentru 'variabila' si aceasta valoare se returneaza de catre expresia de asignare (ca un tot unitar). Exemplu: Consideram instructiunile b = 2; c = 3; a = b + c; unde toate variabilele sunt de tipul int. Folosind faptul ca = este un operator, putem condensa aceasta la a = (b = 2) + (c = 3); Explicatia este ca expresia de asignare b = 2 atribuie valoarea 2 atat variabilei b, cat si instructiunii intregi. Daca exemplul de mai sus pare artificial, atunci o situatie frecvent intalnita este asignarea multipla. De exemplu, instructiunea a = b = c = 0;
este echivalenta cu (folosind asociativitatea de la dreapta la stanga) a = (b = (c = 0));
Relativ la =, mai exista inca doi operatori. Este vorba de += si -=. Expresia k = k + 2
va aduna 2 la vechea valoare a lui k si va asigna rezultatul lui k si intregii expresii. Expresia k += 2
face acelasi lucru. Lista operatorilor de asignare: = += -= *= /= %= >>= <<= &= ^= |= Toti acesti operatori au aceeasi precedenta si se asociaza de la dreapta la stanga. Semantica lor este specificata de variabila op= expresie
care este echivalent cu variabila = variabila op (expresie)
cu exceptia faptului ca variabila sa nu fie o expresie. Exemplu: Expresia de asignare j *= k + 3
este echivalenta cu j = j * (k + 3)
si nu cu j = j * k + 3
Fie declaratia int i = 1, j = 2, k = 3, m = 4;
Consideram urmatoarele exemple de evaluari ale expresiilor Expresie Expresie echivalenta Expresie echivalenta Valoare i += j + k i += (j + k) i = (i + (j + k)) 6 j *= k = m + 5 j *= (k = (m + 5)) j = (j * (k = (m + 5))) 18 Exemple: Calculul puterilor lui 2 #include main() Iesirea acestui program va fi: Sistemul C In capitolele precedente am prezentat directiva de preprocesare #include si #define. Directiva #include avea forma generala: #include
si insemna includerea in acest loc a fisierului header specificat din directoarele specifice C (MS-DOS bcinclude sau tcinclude, UNIX /usr/include). O alta forma este #include 'nume_fisier'
ce are drept scop inlocuirea acestei linii cu o copie a fisierului 'nume_fisier' din directorul curent. Deci, atunci cand utilizam o functie C, trebuie sa specificam prototipul ei (scanf() si printf() au prototipul , rand() are prototipul ). Exemplu: #include #include main() printf('n');
}
Daca de exemplu, tastam numarul 11, atunci pe ecran vor apare 11 numere intregi aleatoare. Observatii: 1. Atentie ! ++i < n este diferit de i++ < n; 2. Operatorul == este operatorul de egalitate (test), adica a == b va fi evaluata la true daca si numai daca valoarea lui a este egala cu valoarea lui b (in caz contrar va fi evaluata la false). 3. Functia rand() intoarce un intreg cuprins intre 0 si n, unde n este dependent de sistem. In ANSI C, n este dat de constanta RAND_MAX. Exercitii propuse spre implementare 1. Investigati comportarea operatorilor / si % pentru numere intregi negative. Mentionam ca in unele sisteme C, 7/-2 da rezultatul -3, in altele -4. Verificati daca se pastreaza identitatea din matematica (prevazuta a fi adevarata de ANSI): (a / b) * b + a % b = a
Sugestie: Scrieti un program C care sa contina liniile de cod int a, b;
printf('dati doi intregi nenuli: ');
scanf('%d%d', &a, &b);
printf('%s%4dn%s%4dn%s%4dn%s%4dn%s%4dn',
' a =',a, ' b =',b, ' a / b =', a / b, ' a % b =', a % b, 'Verif. ANSI=', (a / b) * b + a % b - a); 2. Scrieti un program C care sa calculeze cel mai mare divizor comun dintre a si b, unde a, b sunt numere intregi, folosind algoritmul lui Euclid. 3. Din moment ce + si ++ sunt operatori, rezulta ca expresia a+++b poate fi interpretata fie ca a++ + b fie a + ++b
depinzand de modul de grupare semnului +. Scrieti un program scurt pentru a vedea ce interpretare face compilatorul C. 4. Inlocuiti ++i cu i++ in programul de calcul a puterilor lui 2. 5. Un patrat magic (de latura n) are proprietatea ca include in locatiile sale toate numerele intregi din intervalul 1, , n^2 si sumele numerelor de pe fiecare linie, fiecare coloana sau fiecare diagonala sunt egale. De exemplu: 6 1 8
7 5 3
2 9 4
este un patrat magic de dimensiune 3. Sa se scrie un program C care testeaza daca un patrat este magic sau nu. De asemenea, incercati sa generati toate patratele magice de ordin n. 6. Sa se scrie un program C care sa calculeze n!, unde n>0 este un numar natural (iar n! = 1 * 2 * * n).