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