Capitolul 2
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.
2.1 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
2.2 Avantajele folosirii comentariilor
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 **/
3. /*****/
4. /*
* Al patrulea
* comentariu
*/
5. /**************
* 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.
2.3. 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.
2.4 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.
2.5 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.
2.6. 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"
2. "" /* 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.
2.7 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.
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()
{
int a, b = 2, c = 3;
a = 17 * (b + c);
...
}
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 +=.
2.8 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
2.9 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=%d\n", 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
2.10 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()
{
int i = 0, power = 1;
while (++i <= 10)
printf("%6d", power *=2);
printf("\n");
}
Iesirea acestui program va fi:
2 4 8 16 32 64 128 256 512 1024
2.11 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 \bc\include sau \tc\include, 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()
{
int i, n;
printf("\n%s\n%s",
"Vom afisa niste intregi aleatori.",
"Cati doriti sa vedeti ? ");
scanf("%d", &n);
for (i = 0; i < n; ++i)
{
if (i % 6 == 0)
printf("\n");
printf("%12d", rand());
}
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.
![]() |
![]() |
![]() |