>> Inapoi <<

========
Capitolul 3
========
==================
Controlul instructiunilor
==================

------------------------------------------------------
Operatori relationali, de egalitate si logici
------------------------------------------------------

Operatori relationali : <, >, <=, >=
Operatori de egalitate: ==, !=
Operatori logici      : !, &&, ||

Ca si ceilalti operatori, acesti operatori au reguli de precedenta si 
asociativitate care determina precis modul de evaluare a acestor expresii. 

------------------------------------------------------------------------
|                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 |  
          <    <=   >    >=                  | de la stanga la dreapta |
               ==    !=                      | de la stanga la dreapta |
                  &&                         | de la stanga la dreapta |
                  ||                         | de la stanga la dreapta |
                  ?:                         | de la dreapta la stanga |
     =    +=    -=    *=    /=    etc        | de la dreapta la stanga |
           , (operatorul virgula)            | de la stanga la dreapta |
------------------------------------------------------------------------

Operatorul ! este unar, spre deosebire de toti operatori (relationali, de 
egalitate si logici) care sunt binari. Toti operatorii vor fi prezenti in 
expresii ce pot lua valoarea intreaga 1 sau 0. Motivul este ca C reprezinta 
"false" orice expresie egala cu zero, si "true" orice expresie diferita de 
zero. 

------------
Exemple: In continuare, dam o lista de expresii ce se evaluaza la false
------------
1. O expresie de tip int ce are valoarea 0;
2. O expresie de tip float ce are valoarea 0.0;
3. Caracterul null '\0';
4. Pointerul NULL.


-----------------------------------------
Operatori si expresii relationale
-----------------------------------------

Am vazut ca operatorii <, >, <=, >= sunt toti binari. Expresiile ce contin 
acesti operatori pot lua valoarea 0 sau 1. 

-----------
Exemple. Primele patru exemple sunt corecte, restul sunt gresite:
-----------
1. a < 3
2. a > b
3. -1.1 >= (2.2 * x + 3.3)
4. a < b < c              (corecta, dar confuza)
5. a =< b
6. a < = b
7. a >> b

Fie expresia relationala "a < b". Daca valoarea lui a este mai mica decat 
valoarea lui b, atunci expresia va avea valoarea 1, pe care o gandim ca fiind 
"true". Daca valoarea lui a este mai mare decat valoarea lui b, atunci expresia
va avea valoarea 0, pe care o gandim ca fiind "false". Observam ca valoarea lui
"a < b" este aceeasi cu valoarea lui "a - b < 0". Folosind precedenta 
operatorilor aritmetici, aceasta este deci echivalenta cu "(a - b) < 0". De 
altfel, pe multe masini, expresii cum sunt "a < b" sunt implementate ca 
fiind "a - b < 0".

-----------
Exemple: Vom considera urmatorul tabel cu declaratii si initializari.
-----------
Presupunem ca avem declaratiile:
int    i = 1, j = 2, k = 3;
double x = 5.5, y = 7.7;

--------------------------------------------------------------------
|      Expresie       |     Expresie echivalenta     |  Valoare    |
--------------------------------------------------------------------
     i < j - k                      i < (j - k)            0       |
- i + 5 * j >= k + 1   ((- i) + (5 * j)) >= (k + 1)        1       |
  x - y <= j - k -1       (x - y) <= ((j - k) - 1)         1       |
  x + k + 7 < y / k      ((x + k) + 7) < (y / k)           0       |
----------------------|------------------------------|-------------|  


------------------------------------------
Operatori si expresii de egalitate
-----------------------------------------
Expresiile pot contine si operatorii de egalitate == si !=. Expresiile ce le 
contin au valoarea 0 sau 1. 

-----------
Exemple. Primele trei exemple sunt corecte, restul sunt gresite:
-----------
1. c == 'A'
2. k != -2
3. x + y == 2 * x - 5
4. a = b
5. a = = b - 1
6. (x + y) =! 44

Intuitiv, o expresie de egalitate cum ar fi a == b este sau "true" sau "false". 
Mai precis, daca a este egal cu b, atunci a == b intoarce valoarea 1 (true); 
altfel, aceasta intoarce valoarea 0 (false). O expresie echivalenta este a - b
== 0 (aceasta este ceea ce se implementeaza la nivel masina). 

Expresia "a != b" ilustreaza folosirea operatorului "diferit de" (sau "nu este
egal cu"). 

-----------
Exemple: Vom considera urmatorul tabel cu declaratii si initializari.
-----------
Presupunem ca avem declaratiile:

int    i = 1, j = 2, k = 3;

--------------------------------------------------------------------
|      Expresie       |     Expresie echivalenta       |  Valoare  |
--------------------------------------------------------------------
       i == j         |                j == i          |    0      |
       i != j         |                j != i          |    1      |
i + j + k == - 2 * - k| ((i + j) + k) == ((-2) * (- k))|    1      |
----------------------|--------------------------------|-----------|  


------------------------------------------
Operatori logici si expresii logice
------------------------------------------
Operatorul logic ! este unar, iar && si || sunt binari. Expresiile ce contin 
acesti operatori intorc valoarea 0 sau 1. Negarea logica poate fi aplicata unei
expresii aritmetice sau unui tip pointer. Daca o expresie are valoarea 0, 
atunci expresia negata are valoarea 1. Daca expresia are o valoare diferita de
0, atunci expresia negata intoarce valoarea 1.

-----------
Exemple. Primele trei exemple sunt corecte, restul sunt gresite:
-----------
1. !a
2. !(x + 7.7)
3. !(a < b || c < d)

4. a!
5. a != b (este corecta, dar se refera la operatorul "diferit")

Unele identitati logice (din matematica) nu se "transmit" in C. De exemplu, se
stie ca "not (not s) =s", in timp ce valoarea lui "!!5" nu este 5, ci 1. 
Motivul este ca operatorul "!" se asociaza de la dreapta la stanga, si deci 
"!!5" este echivalent cu "!(!5)", care echivalent cu "!(0)", ce intoarce 
valoarea 1. 

------------
Exemple: Vom considera urmatorul tabel cu declaratii si initializari.
------------
Presupunem ca avem declaratiile:

int    i = 7, j = 7;
double x = 0.0, y = 999.9;

--------------------------------------------------------------------
|      Expresie       |     Expresie echivalenta     |  Valoare    |
--------------------------------------------------------------------
    ! (i - j) + 1               (! (i - j)) + 1            2       |
     ! i - j + 1                ((! i) - j) + 1           -6       |
   ! ! (x + 3.3)                ! (! (x + 3.3))            1       |
   ! x * ! ! y                 (! x) * (! (! y))           1       |
----------------------|------------------------------|-------------|  

Operatorii logici binari && si || pot fi folositi in expresii care intorc 0 sau
1. 

-----------
Exemple. Primele patru exemple sunt corecte, restul sunt gresite:
-----------
1. a && b
2. a || b
3. !(a < b) && c
4. 3 && (-2 * a + 7)
5. a &&
6. a | | b 
7. a & b        (corecta, dar se refera la operatii peste biti)
8. &b           (corecta, dar se refera la adresa lui b)

-----------
Exemple: Vom considera urmatorul tabel cu declaratii si initializari.
-----------
Presupunem ca avem declaratiile:

int    i = 3, j = 3, k = 3;
double x = 0.0, y = 2.3;

--------------------------------------------------------------------
|      Expresie       |     Expresie echivalenta     |  Valoare    |
--------------------------------------------------------------------
     i && j && k                (i && j) && k              1       |
   x || i && j - 3            x || (i && (j - 3))          0       |
    i < j && x < y            (i < j) && (x < y)           0       |
    i < j || x < y            (i < j) || (x < y)           1       |
|----------------------|------------------------------|------------|  


--------------------------------------
Evaluare rapida (short-circuit)
--------------------------------------
Pentru expresiile ce contin && sau ||, evaluarea are loc cand s-a stabilit deja
valoarea expresiei, eventual fara parcurgerea intregii expresii. Astfel, 
presupunem ca "expr1" se evalueaza la 0 (false). Atunci expresia 

                expr1 && expr2
                
se va evalua la 0, fara a se mai face evaluarea expresiei "expr2".
Alt exemplu, daca "expr1" se evalueaza la 1 (true), atunci expresia

                expr1 || expr2
                
se va evalua la true fara a se mai evalua expresia "expr2". 

Uneori se mai spune ca operatorii && si || sunt lazy (adica le este lene sa 
mai evalueze toti operanzii din expresie).


-----------------------------
Instructiunea compusa
-----------------------------
O instructiune compusa este un sir de declaratii si instructiuni delimitate de
acolade. Ceea ce acoladele delimiteaza se numeste "bloc". O instructiune 
compusa este ea insasi o instructiune. 

-----------
Exemplu:
-----------

  {
   a = 1;
    {
     b = 2;
     c = 3;
    }
  }
  

-----------------------
Instructiunea vida
-----------------------
Instructiunea vida se reprezinta cu semnul ; (punct si virgula). Ea se 
foloseste cand se doreste folosirea ei sintactica, si nu neaparat folosire 
semantica. Dupa cum vom vedea, aceasta se foloseste in constructii "if-else" 
si "for". O expresie urmata de ; se numeste "instructiune expresie".

-----------
Exemplu:
-----------

  a = b;
  a + b + c;
  ;
  printf("%d\n", a);
  

-------------------------------------
Instructiunile "if" si "if-else"
-------------------------------------
Forma generala a instructiunii "if" este

        if (expresie)
          instructiune

Semantica intuitiva este simpla. Astfel, daca valoarea expresiei este true 
(diferita de zero), atunci se executa instructiunea, altfel nu.

-----------
Exemplu:
-----------
Instructiunea "if" de mai jos va testa daca se poate face impartirea cu y (ce 
trebuie sa fie diferit de 0):

        if (y != 0.0)
          x /= y;
          
Urmatoarele doua instructiuni

        if (j < k)
          min = j;
        if (j < k)
          printf("j este mai mic decat k\n");
          
se pot scrie intr-una singura

        if (j < k)
          {
           min = j;
           printf("j este mai mic decat k\n");
          }          

Instructiunea "if-else" de mai jos este foarte apropiata de instructiunea "if".
Aceasta are forma generala

        if (expresie)
          instructiune1
        else
          instructiune2
          
Semantica intuitiva este de asemenea clara. Daca valoarea expresiei este 
diferita de zero, atunci se executa instructiune1 si "se sare" peste 
instructiune2. Daca valoarea expresiei este zero, atunci "se sare" 
instructiune1, si se executa instructiune2.

------------
Exemplu:
------------
Urmatorul subprogram C de mai jos calculeaza si afiseaza minimul dintre x si y.

        if (x < y)
          min = x;
        else
          min = y;
        printf("Valoarea minima = %d\n", min);
        

---------------------------
Instructiunea "while"
---------------------------
"While", "for" si "do" sunt cele trei instructiuni repetitive din limbajul C.
Consideram urmatorul format general al instructiunii "while" (iteratia sau 
bucla "while"). 

                while (expresie)
                  instructiune
                instructiune_urmatoare
                
Mai intai se evalueaza expresie. Daca aceasta nu este zero (deci este "true"),
atunci se executa instructiunea, si control trece la inceputul buclei "while".
Astfel, corpul buclei se executa de cate ori expresie se evalueaza la "true". 
Terminarea buclei are loc cand expresie ia valoarea zero (adica "false"). In 
acest punct, controlul se paseaza catre "instructiune_urmatoare". 

-----------
Exemplu:
-----------
                while (i <= 10) 
                 {
                  suma += i;
                  ++i;
                 }


------------------------
Instructiunea "for"
-----------------------
Ca si instructiunea "while", instructiunea "for" se foloseste pentru descrierea
structurilor iterative (repetitive). Astfel constructia

        for (expresie1; expresie2; expresie3)
          instructiune
        instructiune_urmatoare
        
este semantic echivalenta cu

        expresie1;
        while (expresie2)
         { 
          instructiune;
          expresie3;
         } 
        instructiune_urmatoare;
        
Deci, se va evalua expresie1. De obicei, aceasta se foloseste pentru 
initializarea buclei. Apoi, se evalueaza expresie2. Daca aceasta nu este zero 
("true"), atunci se executa instructiune, se evalueaza expresie3, si controlul
buclei se "paseaza" la inceputul buclei (cu deosebirea ca nu se mai evalueaza 
expresie1). De obicei, expresie2 este o expresie logica care controleaza bucla.
Acest proces continua pana cand expresie2 este 0 (false), punct in care se 
plaseaza controlul catre instructiune_urmatoare.

------------
Exemplu: Exemplul de mai jos calculeaza factorialul numarului n.
------------
        factorial=1;
        for (i = 1; i <= n; i++)
          factorial *= i;

Orice sau toate expresiile dintr-o instructiune "for" pot lipsi, dar nu poate 
lipsi ;. 

-----------
Exemple:
-----------
Exemplul de mai jos calculeaza suma numerelor intregi de la 1 la 10.

        i = 1;
        suma = 0;
        for ( ; i <= 10; ++i)
          suma += i;
          
Acesta se poate scrie echivalent:

        i = 1;
        suma = 0;
        for ( ; i <= 10; )
          suma += i++;

Daca, in schimb, lipseste expresie2, atunci obtinem o bucla infinita.


-------------------
Operatorul ","
-------------------
Operatorul "," are cea mai mica prioritate dintre toti operatorii din C.  Este
un operator binar ce are ca operanzi drept expresii si se asociaza de la stanga
la dreapta. Intr-o expresie de forma  

                expresie1 , expresie2

se evalueaza mai intai expresie1, apoi expresie2. Expresia "," intoarce 
valoarea si tipul operandului din dreapta. 

-----------
Exemplu: Presupunem ca a, b sunt de tip int. Atunci expresia ","
------------
                a = 0, b = 1

intoarce valoarea 1 de tipul int.

Operatorul "," este deseori folosit in instructiunea "for".

-----------
Exemplu: Exemplul de mai jos calculeaza factorialul numarului n (reluare).
-----------
        for (factorial = 1, i = 1; i <= n; i++)
          factorial *= i;

------------
Exemplu:Revenim asupra unui exemplu precedent (suma primelor N numere naturale)
------------

        for (suma = 0, i = 1; i <= n; ++i)
          suma += i;

se poate scrie, echivalent, in

        for (suma = 0, i = 1; i <= n; suma += i, ++i);

-------------
Intrebare: Ce se intampla cu valoarea lui suma daca intervertim instructiunile
------------
                suma += i     cu     ++i

-----------
Exemplu:
-----------
       for (i=0, p = head; p != NULL; p=p -> next )
         .....
         

------------------------
Instructiunea "do"
-----------------------
Instructiunea "do" poate fi considerata o varianta a instructiunii "while". 
Deosebirea consta in faptul ca pentru instructiunea "while" testul se face  
la inceputul ciclului, iar pentru "do" la sfarsit. Consideram constructia de 
forma

                do
                  instructiune
                while (expresie);
                instructiune_urmatoare

La inceput se executa instructiune, apoi se evalueaza expresie. Daca valoarea 
lui expresie este diferita de 0 ("true"), atunci controlul se paseaza la 
inceputul instructiunii "do", si procesul se repeta. Daca expresie se evalueaza
la 0 (false), atunci controlul se paseaza la instructiune_urmatoare.

-----------
Exemplu: Suma unor numere intregi diferite de 0
-----------
        suma = i = 0;
        do
         {
          suma += i;
          scanf("%d", &i);      
         }
        while (i > 0);


--------------------------
Instructiunea "goto"
-------------------------
Instructiunea "goto" (salt neconditionat) este considerata opusa programarii 
structurate. Sfatul general valabil este evitarea acestei instructiuni. Totusi,
in unele cazuri se poate folosi (cand simplifica controlul, cand face codul mai
eficient). O instructiune de etichetare are forma: 

                eticheta : instructiune

unde eticheta este un identificator. 

------------
Exemple:
------------
  bye: exit(1);
  eticheta1: a = b + c;

  333: a = b + c;  (exemplu gresit, de ce ?) 

Controlul programului poate fi transferat neconditionat catre o instructiune 
de etichetare astfel

                goto eticheta;


----------------------------------------------
Instructiunile "break" si "continue"
----------------------------------------------
Cele doua instructiuni

                break;        si       continue;

intrerup controlul normal al programelor. Instructiunea "break" va cauza 
iesirea din bucla in care se afla sau din instructiunea "switch". Instructiunea
"continue" se poate afla numai in instructiuni "for", "while" si "do". Ea are 
rolul de a trasmite controlul catre sfarsitul buclei respective.

-----------
Exemple:
-----------
        while (1)
         {
          scanf("%lf", &x);
          if (x < 0.0) 
            break;              /* iesim cand x este negativ */
          printf("%lf\n", sqrt(x));
         }
         while (contor < n)
         {
          scanf("%lf", &x);
          if (x > -0.01 && x < =0.01)
            continue;     /* valorile mici nu se iau in considerare */
          ++contor;
          suma += x;
         }


------------------------------
Instructiunea "switch"
-----------------------------
"switch" este o instructiune conditionala ce generalizeaza o instructiune 
"if-else".

-----------
Exemplu:
-----------
        switch (val)
         {
          case 1: 
            ++contor_a;
            break;
          case 2:
          case 3:
            ++contor_b;
            break;
          default:
            ++contor_c; 
         }

Corpul unei instructiuni "switch" este un exemplu de instructiune compusa. 
Expresia de control dintre paranteze (ce urmeaza cuvantului switch) trebuie sa
fie de tip integral (vom reveni intr-un alt capitol). Dupa evaluarea lui val, 
controlul sare la eticheta corespunzatoare valorii lui val. De obicei, ultima 
instructiune dintr-un "case" este de obicei "break". Daca nu exista "break", 
atunci se vor executa si instructiunile din urmatoarele "case"-uri.  

Atentie ! Omiterea scrierii lui "break" este foarte frecventa !!

Poate apare cel mult un "default" (in general pe ultima pozitie). Cuvintele 
rezervate "case" si "default" pot apare numai in interiorul unui "switch".


------------------------------
Operatorul conditional
------------------------------
Operatorul "?:" este mai putin obisnuit deoarece este ternar (cu trei 
argumente). Forma generala este 

        expresie1 ? expresie2 : expresie3

Mai intai, se evalueaza expresie1. Daca aceasta este diferita de 0 (true), 
atunci se evalueaza expresie2, si aceasta va fi valoarea returnata de intreaga
expresie conditionala. Daca expresie1 este 0 (false), atunci se evalueaza 
expresie3, si aceasta va fi valoarea intregii expresii conditionale. 

------------
Exemplu: Instructiunea
-----------
                if (y < z)
                  x = y;
                else
                  x = z;

este echivalenta cu

                x = (y < z) ? y : z;

Operatorul ?: are aceeasi prioritate cu operatorul de asignare si se asociaza 
de la dreapta la stanga.



-----------------------------------------------
Exercitii propuse spre implementare
-----------------------------------------------

1. Sa se scrie un program care sa calculeze minimul a trei numere (folosind o 
 instructiune "if-then" si una "if" sau doua "if-then" (fara variabila 
 suplimentara)). Generalizare: Sa se gaseasca primele doua numere (cele mai 
 mici) dintr-un vector de n elemente (cu numar minim de comparatii).

2. Cititi n numere de la tastatura si afisati maximul lor. Incercati sa cititi
 un numar arbitrar de numere (deci fara a citi acest n).

3. Folosind structura for, scrieti un program care calculeaza urmatoarele 
 formule logice (sub forma unei tabele de adevar):

        b1 || b3 || b5    si   b1 && b2 || b4 && b5

4. Fie functia lui Collatz: 

                { n/2   daca n este par
        f(n) =
                { 3*n+1 daca n este impar

Sa se scrie un program C care determina k natural minim astfel incat 

                (f o f o ... o f)(n)=1.
                   de k ori

5. Scrieti un program C care calculeaza suma divizorilor naturali ai unui numar
natural n. Un numar este perfect daca este egal cu suma divizorilor proprii 
pozitivi (ex: 28 = 1 + 2 + 4 + 7 + 14). Sa se  genereze primele k numere 
perfecte (k < 5 !).

6. Operatia matematica min(x,y) se poate reprezenta ca o expresie conditionala: 

                (x < y) ? x : y
                
Intr-un mod similar, descrieti operatiile aritmetice

             min(x, y, z)    si   max(x, y, z, t)
             
7.  Se stie ca un procedeu de interschimbare a valorii a doua variabile (a si 
b) se poate face folosind o variabila auxiliara (se foloseste in metodele de 
sortare, arbori, sisteme de ecuatii, etc):

                aux =  a ;
                  a = b  ;
                 b  = aux;

Sa se arate ca in limbajul C se poate face acest lucru in mod echivalent fara 
utilizarea explicita a unei variabile suplimentare. Asadar intervertirea 
valorilor a si b se poate face si astfel:

                a = b + a - (b = a);
                
Aratati ca aceasta instructiune este echivalenta cu:

                aux = b + a  ;
                  b = a      ;
                  a = aux - a;  (sau a = aux - b;)

Echivalent, fara variabile suplimentare, se pot considera instructiunile:

	a = a + b;
	b = a - b;
	a = a - b;