|
Instructiunile de control al fluxului dintr-un limbaj specifica ordinea in care se fac calculele. Ne-am intilnit deja cu cele mai cunoscute constructii de control al fluxului din limbajul C, in exemplele date in paginile anterioare; in cele ceurmeaza, vomcompletasetul de instructiuni si vom fi mult mai precisi asupra celor discutate mai sus.
O expresie ca de exemplu x = 0 sau i++ sau printf() devine instructiune cind este urmata de punct si virgula, ca in:
x = 0;
i++;
printf();
In limbajul C, punct-virgula este terminator de instructiune, nu separator, cum este in limbajele de tipul ALGOL.
Acoladele sint folosite pentru a grupa impreuna instructiuni si declaratii intr-o instructiunecompusasau bloc, asa ca ele sint sintactic echivalente cu o singura instructiune. Acoladele ce inchid instructiunile unei functii sau cele pentru instructiunile multiple dupa un if, else, while, for sint exempleclare pentru aceasta.(Variabilele pot fi de fapt declarate inlauntrul oricarui bloc; vom discuta despre aceasta in Capitolul 4). Nu se pune niciodata punct si virgula dupa acolada inchisa care termina un bloc.
Instructiunea If-Else estefolositapentruluareade decizii. Formal, sintaxa ei este:
if(expresie)
instructiune-1
else
instructiune-2
unde partea 'else' este optionala.'Expresia' este evaluata; daca este 'adevarata' (adica,are o valoare nenula), 'instructiune-1' este executata. Daca ea este 'falsa' ('expresia' este zero) si daca exista partea cu 'else', se executa in schimb 'instructiune-2'.
Deoarece un 'if' testeaza pur si simplu valoarea numerica a unei expresii, sint posibile anumite prescurtari de cod. Cel mai clar exemplu este scriind
if(expresie)
in loc de
if(expresie != 0)
Citeodata, acest lucru este natural si clar. Altadata poate parea cifrat.
Deoarece partea cu 'else' a unui if-else este optionala, se poate ajunge la o ambiguitate cind se omite un else dintr-o secventa imbricata de if-uri. Aceasta este rezolvata, ca de obicei, asa: else este asociat cu if-ul anterior cel mai apropiat, care nu face pereche cu un 'if'. De exemplu, in:
if (n > 0)
if (a > b)
z = a;
else
z = b;
else face pereche cu if cel mai dinauntru, asa cum am aratat prin tabulare. Dacanu dorim aceasta,trebuie sa folosim acolade pentru a forta asocierea potrivita:
if (n > 0)
else
z = b;
Ambiguitatea este vatamatoare indeosebi in situatii ca urmatoarea:
if (n > 0)
for (i = 0; i < n; i++)
if (s[i] > 0)
else/* WRONG */
printf('error- n is zero n');
Tabularea arata neechivoc ceea ce dorim, dar compilatorul nu intelege acest mesaj, si-l asociaza pe else cu if-ul cel mai dinauntru. Acest tip de eroare poate fi foarte greu de gasit.
Apropo, sa notam ca exista un punct si virgula dupa z = a in:
if (a > b)
z = a;
else
z = b;
Aceasta deoarece, gramatical, dupa if urmeaza o instructiune si o instructiunede asignare de tipul z = a se termina intodeauna cu punct si virgula.
Constructia
if (expresie)
instructiune
else if (expresie)
instructiune
else if (expresie)
instructiune
else
instructiune
apareatitdedes incit este demn de purtat o discutie scurta si separata asupra ei.Aceasta secventa de if-uri este calea cea mai generala de a scrie decizii multiple. Expresiile sint evaluate in ordine; daca o expresie este adevarata, instructiunea asociata cu ea este executata, si aceasta termina intregul lant. Codul pentru fiecare 'instructiune' este fie o instructiune, fie un grup intre acolade.
Ultima parte de 'else' manipuleaza cazul 'niciuna din cele mai de sus' sau implicit, in care nici una din conditii nu este indeplinita. Citeodata nu exista nici o actiune explicita pentru cazul implicit; in acest caz,
else
instructiune
poate fi omisa, sau poate fi utila pentru verificarea de erori, pentru a prinde o conditie 'imposibila'.
Pentrua ilustra o decizie trivalenta,dam o functie binarade cautare, care decide daca o valoare particulara x apare intr-un tablou sortat v. Elementele lui v trebuie sa fie in ordine crescatoare. Functia returneaza pozitia (un numar intre 0 si n-1) daca x apare in v, si -1 daca nu.
binary (x, v, n) /* gaseste pe x in v[0], v[1], , v[n-1] */
int x, v[], n;
return(-1);
Decizia fundamentala este aceea daca x este mai mic decit, mai mare decit sau egal cu elementul din mijloc v[mid] la fiecare pas; aceasta este natural pentru un if-else.
Instructiunea switch este realizator special de decizii multiple care testeaza daca o expresie se potriveste cu una dintr-un numar de valori constante si ramifica corespunzatorprogramul. Incapitolul 1 am scris un program care contorizeaza aparitiile fiecarei cifre, a spatiului, si a tuturor celorlalte caractere, folosind o secventa de if else.Dam in continuare acelasi program cu instructiunea switch.
main() /* contorizeaza cifre , blancuri , alte caractere */
printf('digits =');
for (i = 0; i < 10; i++)
printf(' %d', ndigit[i]);
printf('nwhite space= %d, other= %dn', nwhite,nother);
}
Switch evalueaza expresia intreaga din paranteze (in acest program caracterul c) si compara valoarea ei cu toate cazurile. Fiecare caz trebuie sa fie etichetat cu o constanta intreaga sau caracter sau cu o expresie constanta. Daca un caz se potriveste cu valoarea expresiei, executia incepe la acel caz. Cazul etichetat 'default' este executat daca nici unul din cazuri nu este satisfacut.Un 'default' este optional; daca el nu este prezent si nici unul din cazuri nu se potriveste nu se executa nici o actiune. Cazurile si 'default' pot apare in orice ordine. Cazurile trebuie sa fie
toate diferite.
Instructiunea break declanseaza o iesire imediata din switch. Deoarece cazurile servesc doar ca etichete, dupa ce codul unui caz a fost executat, executia continua spre urmatoarea instructiune daca nu nu luati o actiune explicita spre a iesi. Break si return sint modurile cele mai uzuale de a parasi o instructiune switch. O instructiune switch poate fi deasemenea folosita si pentru a forta o iesire imediata dintr-o bucla while, for sau do, asa cum vom discuta mai departe in acest capitol.
Ramificarea in cazuri este si buna si rea. Pe partea pozitiva, ea permite mai multe cazuri pentru o singura actiune, asa cum sint cazurile pentru blanc, tab sau linie noua inacest exemplu.Dar implica deasemenea faptul ca, in mod normal, fiecare caz trebuie sa se termine cu un break, pentru a preveni ramificarea pe cazul urmator. Iesirea dintr-un caz in altul nu este buna, fiind inclinata spre dezintegrare atunci cind programul este modificat. Cu exceptia etichetelor multiple pentru un singur caz,aceste iesiri dintr-un caz in altul trebuie folosite cu economie.
Ca o problema de forma buna, puneti un break dupa ultimul caz (la noi, cazul default) chiar daca logic nu este necesar. Intr-o zi cind veti adauga la sfirsit un caz nou, aceasta bucatica de programare defensiva va va salva.
Exercitiul 1. Scrieti o functie expand(s, t) care converteste caracterele de tipul lui 'linie noua' si 'tab' in secvente escape vizibile de tipul 'n' si 't' in timp ce se copiaza sirul s in sirul t. Folositi instructiunea switch.
Am intilnit deja buclele while si for. In
while (expresie)
instructiune
'expresie' este evaluata. Daca ea este nenula, 'instructiune' este executata si 'expresie' este reevaluata. Acest ciclu continua atita timp cit 'expresie' nu este zero, iar cind ea devine zero executia se reia de dupa 'instructiune'.
Instructiunea for:
for (expr1; expr2; expr3)
instructiune
este echivalenta cu
expr1;
while (expr2)
Din punct de vedere gramatical, cele trei componente ale unei bucle for sint expresii. In majoritatea cazurilor, expr1 si expr3 sint asignari sau apeluri de functii iar expr2 esteo expresie relationala. Oricare din cele trei parti poate fi omisa, cu toate ca punct-virgula corespunzatoare trebuie sa ramina. Daca expr1 sau expr3 este lasata afara, i nu mai este incrementat. Daca testul, expr2 nu este prezent, el este luat ca fiind permanent adevarat, asa incit:
for (;;)
Avantajulpastrarii centralizate a controlului buclei este si mai clar atunci cind exista mai multe bucle imbricate. Urmatoarea functie este o sortare shell pentru un tablou de intregi. Ideea de baza a sortarii shell este aceea ca in stadiile de inceput se compara elemente indepartate si nu cele adiacente, ca in sortarile simple bazate pe interschimbare. Aceasta tinde sa elimine cantitati mari de dezordine, rapid,asa ca stadiile urmatoare au mai putin de lucru. Intervalul dintreelementelecomparate scade treptat spre unu, punct in care sortarea devine efectiv o metoda de interschimbare adiacenta.
shell(v, n) /* sorteaza v[0], ,v[n-1] in ordine crescatoare */
int v[], n;
Sintaici trei bucle imbricate. Cea mai dinafara contoleaza distanta dintre elementele comparate, contractind-o de la n/2 prin injumatatire la fiecare pas, pina cind devine zero. Bucla din mijloc compara fiecare pereche de elemente care esteseparata deun 'gap'; bucla cea mai din interior le inverseaza pe acele elementecarenu sint in ordine. Deoarece 'gap' poate fi redus la 1 eventual,toate elementele sint eventual ordonate corect. Sa notamca generalitatea lui 'for' face ca bucla exterioara sa aiba aceeasi forma ca celelalte, chiar daca nu este o progresie aritmetica.
Un operator final in limbajul C este virgula ',' care isi gaseste adesea utilizare in instructiunea for. O pereche de expresii separate printr-o virgula este evaluata de la stinga spre dreapta si tipul si valoarea rezultatului sint tipul si valoarea operandului din dreapta. Astfel, intr-o instructiune for este posibil sa plasam expresii multiple in parti variate, de exemplu sa prelucram doi indici in paralel. Acest lucru este ilustrat de functia reverse(s) care inverseaza pe loc un sir s.
reverse(s)/* inverseaza pe loc sirul s */
char s[];
}
Virgulelecare separaargumentelefunctiilor, variabilele din declaratii, etc nici nu sint operatori 'virgula' si nu garanteaza evaluarea de la stinga la dreapta.
Exercitiul 2. Scrieti o functie expand(s1, s2) care expandeaza notatiile scurtede tipul a-z in sirul s1 in lista echivalenta si completa abc.xyz in s2. Sint permise litere mari si mici si cifre; sa fiti pregatiti sa tratati si cazuri de tipul a-b-c si a-z0-9 si -a-z. (O conventie utila este aceea ca '-'la inceput este considerat ca atare).
Buclele while si for impartasesc atributul de testare a conditiei de terminare la inceputul buclei mai degraba decit la sfirsitul ei, asa cum am discutat in Capitolul 1. Al treilea tip de bucle in C - bucla do-while - testeaza conditia la sfirsit,dupa ce a executat intreg corpul buclei; corpul este executat cel putin o data. Sintaxa ei este:
do
instructiune
while (expresie);
'Instructiune'este executata si apoi'expresie' este evaluata. Daca este adevarata, 'instructiune' se executa din nou, s.a.m.d. Daca 'expresie' devine falsa, bucla se termina.
Asa cum este de asteptat, bucla 'do-while' este folosita mai putin decit while si for, probabil 5% din totalul de folosire a buclelor. Cu toate acestea, ea este din timp in timp valoroasa, ca in exemplul urmator, unde functia itoa converteste un numar intr-un sir de caractere (inversa lui atoi). Lucrarea este putin mai complicata decit se pare la prima vedere, deoarece metodele usoare de generare de cifre le genereaza intr-o ordine gresita. Am ales calea de a genera sirul invers apoi de a-l inversa.
itoa (n, s) /* converteste pe n in caractere in s */
char s[];
int n;
while ((n /= 10) > 0); /* sterge-o */
if (sign < 0)
s[i++] = '-';
s[i] = '0';
reverse(s);
}
Bucla do-while este necesara, sau cel putin convenabila deoarece cel putin un caracter trebuie pus in matricea s, indiferent de valoarea lui n. Am folosit de asemenea acoladeleinjurul singurei instructiuni ce compune corpul buclei do-while,chiar dacanusintnecesare pentru ca cititorul grabit sa nu considere gresit partea cu while ca fiind inceputul unei bucle while.
Exercitiul In reprezentarea numerelor ca si complemente fata de 2 versiunea noastra pentruitoanu functioneaza pentru numarul negativ cel mai mic, adica pentru valoarea lui negala cu -(2 la puterea dimensiune cuvint-1). Explicati de ce. Modificati functia pentru a functiona corect si pentru aceasta valoare, indiferent de calculatorul pe care se executa.
Exercitiul 4. Scrieti o functie analoaga itob(n, s) care converteste intregii fara semn n intr-o reprezentare binara pe caracter in s. Scrieti itoh, care converteste un intreg intr-un numar haxazecimal.
Exercitiul 5. Scrieti o versiune a lui itoa care accepta trei argumente in loc de doua. Al treilea argument este un cimp de lungime minima; numarul convertit trebuie completat cu blancuri la stinga, daca e necesar, pentru a se inscrie in cimpul dat.
Adeseaeste convenabil sa controlam iesirile din bucle altfel decit testind conditia la inceputul sau sfirsitul buclei. Instructiunea break ofera o iesire mai devreme din for, while, do si switch. O instructiune break face ca bucla (sau switch-ul) cea mai din interior sa se termine imediat.
Urmatorul program sterge blancurile si taburile de la sfirsitul fiecarei linii de intrare, folosind un break pentru a iesi din bucla la (primul) cel mai din dreapta caracter nonblanc sau nontab
#define MAXLINE 1000 ;
main() /* sterge caracterele albe de la sfirsitul liniei */
getlinereturneaza lungimea liniei. Bucla while din interior incepe cu ultimul caracter al lui line (sa ne amintim ca --n decrementeaza pe n inainte de a-i folosi valoarea)sicauta inapoi primul caracter care nu este blanc, tab sau (newline) linie noua. Bucla este sparta cind este gasit unul din acestea sau cindn devine negativ (adica atunci cind intreaga linie a fost analizata). Ar trebui sa verificati ca este corect si in cazul in care linia este formata numai din caractere albe ( de spatiere).
O alternativa la break consta in a pune testul chiar in bucla:
while ((n = getline(line, MAXLINE)) > 0)
Aceastaeste inferioara versiunii precedente, deoarece testul ste mai greu de inteles. Testele care necesita un amestec de && ,||,! sau paranteze sint in general interzise.
Instructiunea continue este legata de break, dar mult mai putin folosita; ea facesa inceapa urmatoarea iteratie a buclei (while, for, do). In cazul lui while si do aceasta inseamna ca partea de test se executa imediat; in cazul lui for, controlul se trece la faza de reinitializare. (continue se aplica numai la bucle,nu si la switch. Un continue inauntrul unui switch dintr-o bucla declanseaza urmatoarea iteratie a buclei.
Ca exemplu, fragmentul urmator prelucreaza numai elementele pozitive dintr-un tablou a; valorile negative sint sarite:
for (i = 0; i < N; i++)
Instructiunea continue este folosita adesea cind partea din bucla care urmeaza este complicata,astfel ca inversind untest si incluzind inca un nivel, ar imbrica programul si mai mult.
Exercitiul 6. Scrieti un program care copiaza intrarea in iesire, cu exceptia ca el tipareste osinguradata olinie dintr-un grup de linii adiacente identice. (Aceasta este o versiune simpla a utilitarului UNIX uniq.)
Limbajul C ofera instructiunea - de care se poate abuza oricit - goto si etichete pentru ramificare. Formal, goto nu este necesara niciodata si in practica este aproape intodeauna usor sa scriem cod fara ea. Noi nu am folosit goto in aceasta carte.
Cu toate acestea, va sugeram citeva situatii in care goto isi poate gasi locul. Cea mai obisnuita folosire este aceea de a abandona prelucrarea in anumite structuri puternic imbricate, de exemplu de a iesi afara din doua bucle deodata. Instructiunea break nu poate fi folosita deoarece ea paraseste numai bucla cea mai din interior. Astfel:
for ()
for ()
error:
descurca beleaua
Aceastaorganizare este manevrabila daca codul deminuire a erorii este netrivial si daca erorile pot apare in locuri diferite. O eticheta are aceeasi forma ca si un nume de variabila si este urmata de doua puncte. Ea poate fi atasata oricarei instructiuni dintr-o aceeasi functie ca si goto.
Ca un alt exemplu, sa consideram problema gasirii primului element negativ dintr-un tablou bidimensional. (Tablourile multidimensionale sint discutate in Capitolul 5). O posibilitate este:
for (i = 0; i < N; i++)
for (j = 0; j < M; j++)
if (v[i][j] < 0)
goto found;
/* nu s-a gasit */
found:
/* s-a gasit la pozitia i,j */
Codulimplicind un goto poate fi scris intodeauna fara goto, chiar daca pretul pentru aceasta este o variabila suplimentara, sau teste repetate. De exemplu, cautarea in tablou devine:
found = 0;
for (i = 0; i < N && !found; i++)
for (j = 0; j < M && !found; j++)
found = v[i][j] < 0;
if (found)
/* a fost la i-1, j-1 */
else
/* nu a fost gasit */
Cu toate ca nu sintem dogmatici in privinta subiectului, se pare ca e adevarat ca instructiunea goto ar trebui folosita cu economie, daca nu chiar deloc.