>> Inapoi <<

=========
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.
  

----------------------------------
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 codului 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. 


----------------------
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 
&ls; 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"
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.


-------------------------------------------
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()
 {
  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 +=. 


------------------------------------------------------
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=%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


-----------------------------
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 
        

---------------   
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. 



-----------------------------------------------
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%4d\n%s%4d\n%s%4d\n%s%4d\n%s%4d\n",
     "         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).