Am arătat în episodul anterior cum putem face în așa fel încât să executăm în mod repetat instrucțiuni atâta timp cât o condiție este îndeplinită. Să considerăm acum un exemplu simplu: dorim să scriem o funcție care să afișeze toate numerele naturale cuprinse între 1 și o valoare pe care o primește ca parametru. Destul de simplu, nu? Ar fi cam așa:
1 2 3 4 5 6 7 |
private static void Numere(int n) { int i = 1; while (i <= n) { System.Console.WriteLine(i); i++; } } |
Dacă privim cu atenție codul, identificăm patru componente: în linia 2 avem inițializarea unei variabile; în linia 3 avem condiția (influențată și de variabila menționată), în linia 4 avem instrucțiunea care realizează operație care dorim să fie repetată și în linia 5 avem o instrucțiune care modifică variabila pentru ca, la un moment dat, condiția să nu mai fie îndeplinită.
Acesta este un șablon atât de des întâlnit, încât limbajul C# oferă o prescurtare.
Instrucțiunea for
Noua instrucțiune permite specificarea inițializării, a condiției și a modificării într-un format mai compact. Practic, avem cuvântul for, urmat de inițializare, condiție și modificare, cuprinse între paranteze și separate prin semnul ; și apoi de instrucțiunea sau blocul de instrucțiuni care trebuie executate la fiecare iterație. Funcția noastră ar putea fi rescrisă astfel:
1 2 3 4 5 6 |
private static void Numere(int n) { int i; for (i = 1; i <= n; i++) { System.Console.WriteLine(i); } } |
Observăm variabila i declarată în linia 2. Nu are nicio utilitate în afara buclei; limbajul C# ne permite să punem declarația în zona de inițializare:
1 2 3 4 5 |
private static void Numere(int n) { for (int i = 1; i <= n; i++) { System.Console.WriteLine(i); } } |
Există o diferență între cele două versiuni. Vă dați seama care este? Este vorba de domeniul de vizibilitate al variabilei i. În prima variantă ea este vizibilă în interiorul întregii funcții, în cea de-a doua este vizibilă doar în interiorul buclei. Dar, nu e cam și cum ar fi fost declarată în corpul buclei; atunci și-ar fi pierdut valoarea de la o iterație la alta; dacă e declarată în zona de inițializare a buclei, variabila există din momentul în care începe execuția buclei și până se termină (când condiția nu mai este adevărată). Trebuie să facem distincție între execuția întregii bucle și execuția unei iterații a buclei.
Dacă alegem prima variantă, valoarea finală a variabilei va fi disponibilă după încheierea execuției buclei.
Operatorul virgulă
În C# avem la dispoziție un operator interesant, reprezentat printr-o virgulă (semnul ,). Acesta este un operator binar; el evaluează valoarea primului operand, o ignoră și apoi evaluează valoarea celui de-al doilea. Pare inutil, dar nu este deloc așa.
Practic, dacă avem expresii, le putem înlănțui cu virgule (nu spune nimeni că al doilea operand nu poate include, la rândul său, operatorul virgulă).
Un prim efect interesant este că dacă avem doar o serie de atribuiri sau apeluri de funcții, le putem separa prin , în loc de ;. Nu are nicio utilitate practică, dar se poate (atâta timp cât nu avem nevoie de un if, un while, un for, un switch etc.).
Dar, unul dintre locurile în care acest operator este util, este zona de inițializare a instrucțiunii for. Putem inițializa mai multe variabile dacă dorim. De asemenea, putem folosi operatorul și în zona de incrementare / decrementare (aceasta este o denumire utilizată pentru a treia zonă, deoarece de obicei operațiile care apar acolo sunt incrementări sau decrementări, deși nu este obligatoriu). I se mai spune, simplu, zona de incrementare (mult mai des avem incrementări).
Următoarea funcție utilizează aceasta facilitate. Încercați să vă dați seama ce se afișează!
1 2 3 4 5 |
private static void Perechi(int n) { for (int i = 0, j = n; i <= j; i++, j--) { System.Console.WriteLine(i + " " + j); } } |
Trebuie avute în vedere câteva restricții: dacă folosim declarații în zona de inițializare, atunci toate variabilele care apar trebuie să fie noi și trebuie să aibă același tip. Niciuna dintre următoarele variante nu este permisă:
1 2 3 4 5 6 7 8 9 10 |
for (int i = 0, double j = n; i <= j; i++, j--) {...} int i; for (i = 0, int j = n; i <= j; i++, j--) {...} int i; for (int i = 0, j = n; i <= j; i++, j--) {...} int j; for (int i = 0, j = n; i <= j; i++, j--) {...} |
Oricare dintre cele trei zone poate lipsi. Dacă lipsește zona de inițializare, înseamnă că vom opera asupra altor variabile. Dacă lipsește condiția, înseamnă că ne bazăm pe alte mecanisme pentru a întrerupe execuția buclei (de exemplu un return; altele vor fi prezentate în episodul următor). Dacă lipsește zona de incrementare / decrementare, înseamnă că ne bazăm pe alte mecanisme pentru a afecta condiția (în principiu, modificări în corpul buclei).
For în for
Cum era de așteptat, în cadrul corpului buclei pot apărea orice fel de instrucțiuni, inclusiv un alt for. Până acum am spus că este permis if în if (episodul IV) sau while în while (episodul X). Ne putem imagina că este permis și pentru alte instrucțiuni: am putea avea do în do, switch în switch și, acum, for în for. De fapt, știm că nu trebuie să ne limităm doar la un nivel de imbricare (vă mai amintiți termenul?). Și... instrucțiunile nu trebuie să fie de același fel: nimic nu ne oprește să avem if în while în for în switch în for. Nu ar fi prea bine să ajungem să avem așa ceva fiindcă am înțelege cu greu ce se întâmplă exact în zona respectivă, dar este posibil.
Să nu credem totuși că este permis orice! De exemplu, în Java nu putem avea o funcție în interiorul altei funcții. Dar, ca de obicei, există excepții...
Dar, să revenim la for în for; e destul de simplu. Să scriem o funcție care afișează numerele puțin altfel; fiecare va fi reprezentat de niște steluțe, numărul acestora indicând valoarea numărului.
1 2 3 4 5 6 7 8 |
private static void Numere(int n) { for (int i = 1; i <= n; i++) { for (int j = 1; j <= i; j++) { System.Console.Write('*'); } System.Console.WriteLine(); } } |
Dacă am apela funcția cu argumentul 10, la ieșire s-ar scrie:
1 2 3 4 5 6 7 8 9 10 |
* ** *** **** ***** ****** ******* ******** ********* ********** |
În situațiile simple în care avem o singură variabilă care apare în zonele de inițializare și incrementare și este implicată și în condiție, spunem că ea controlează bucla respectivă.
În principiu, buclele imbricate sunt controlate de variabile diferite, chiar dacă o variabilă dintr-o buclă exterioară poate fi folosită într-una interioară. În cazul nostru, variabila i care controlează prima buclă este utilizată în condiția celei de-a doua. Din nou, sunt excepții; putem (în teorie) să folosim aceeași variabilă pentru a controla o buclă interioară, atâta timp cât nu o redeclarăm; totuși, în marea majoritate a cazurilor așa ceva nu este de dorit.
Din nou, este important domeniul de vizibilitate al variabilelor. O buclă interioară poate accesa sau modifica o variabilă declarată într-un exterioară, dar invers nu este posibil.
Va urma
În următorul episod vom vedea cum putem afecta execuția "normală" a instrucțiunilor din interiorul buclelor.