În cadrul episodului VIII am văzut că putem întrerupe execuția unei funcții folosind instrucțiunea return. Uneori, s-ar putea să avem nevoie de întreruperea unei bucle. Dacă suntem în interiorul unei funcții și este OK să se înterupă execuția întregii funcții, putem folosi return. Dar, în multe situații vrem să continuăm cu intrucțiunile care urmează după buclă.
Să vedem un exemplu: o modalitate simplă (dar ineficientă) de a verifica dacă un număr este prim este numărarea divizorilor săi. Dacă sunt exact doi, atunci numărul este prim. O funcție care afișează dacă un număr primit ca parametru este prim, ar putea arăta astfel:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
private static void prim(int n) { int divizori = 0; int d = 1; while (d <= n) { if (n % d == 0) { divizori++; } d++; } if (divizori == 2) { System.out.println(n + " este prim!"); } else { System.out.println(n + " nu este prim!"); } } |
Avem o variabilă divizori pe care o folosim pentru a număra divizorii și o variabilă d care va conține potențialii divizori (toate numerele cuprinse între 1 și n). La fiecare pas verificăm dacă n se împarte exact la valoarea curentă a lui d. Dacă da, creștem numărul divizorilor. Indiferent dacă d a fost sau nu divizor a lui n, trecem la următorul potențial divizor incrementând valoarea d. Ne oprim doar atunci când d devine mai mare decât n (cu alte cuvinte, continuăm cât timp d este mai mic sau egal cu n). La sfârșit verificăm dacă numărul divizorilor este exact 2 și scriem un mesaj corespunzător ( n este sau nu prim).
Funcționează pentru orice număr pozitiv; dar, putem observa destul de ușor că, dacă am ajuns să avem trei divizori, am vrea să ne oprim. Nu putem folosi instrucțiunea return fiindcă s-ar întrerupe execuția întregii funcții și nu am mai afișa mesajul de la final. Funcția ar putea arăta așa:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
private static void prim(int n) { int divizori = 0; int d = 1; while (d <= n) { if (n % d == 0) { divizori++; } if (divizori == 3) { return; } d++; } if (divizori == 2) { System.out.println(n + " este prim!"); } else { System.out.println(n + " nu este prim!"); } } |
Mesajul nu va mai fi afișat decât pentru numerele prime (și pentru zero sau unu). Am putea să modificăm variabilele implicate în evaluarea condiției și să forțăm astfel întreruperea. De exemplu, am putea modifica linia 9 astfel încât d să devină mai mare decât n:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
private static void prim(int n) { int divizori = 0; int d = 1; while (d <= n) { if (n % d == 0) { divizori++; } if (divizori == 3) { d = n + 1; } d++; } if (divizori == 2) { System.out.println(n + " este prim!"); } else { System.out.println(n + " nu este prim!"); } } |
Execuția buclei nu se oprește imediat; incrementarea din linia 11 se va execută. În cazul nostru această incrementare este inofensivă ( d va deveni n + 2 și condiția de continuare a buclei nu va mai fi îndeplinită). De fapt, puteam pune doar d = n; în linia 9 și incrementarea din linia 11 ar fi făcut în așa fel încât condiția să nu mai fie îndeplinită. Dar, nu este deloc bine să ne bazăm pe astfel de efecte colaterale.
Dar, în unele situații instrucțiunile care urmează după momentul în care dorim întreruperea ar putea avea efecte nedorite. Poate vrem ca execuția buclei să se oprească de tot.
De obicei, întrerupem execuția în cazul îndeplinirii unei condiții, deci avem un if. Putem pune tot ce urmează în else și atunci totul va funcționa așa cum ne dorim.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
private static void prim(int n) { int divizori = 0; int d = 1; while (d <= n) { if (n % d == 0) { divizori++; } if (divizori == 3) { d = n + 1; } else { d++; } } if (divizori == 2) { System.out.println(n + " este prim!"); } else { System.out.println(n + " nu este prim!"); } } |
Soluția este acceptabilă în cazul în care corpul buclei nu are o structură foarte complicată. Dar, există și altfel de situații: poate condiția de înterupere este verificată în interiorul unui alt if și după acest if exterior există alte instrucțiuni. Soluția noastră cu else nu ar funcționa; am avea nevoie de alte "artificii" pentru a evita executarea acestor instrucțiuni.
Din fericire, avem la dispoziție o instrucțiune care face exact ce vrem: break (Vă amintiți de ea de la switch? - episodul V). După executarea ei, bucla în interiorul căreia a apărut instrucțiunea se întrerupe (nici iterația curentă nu continuă). Codul redevine simplu, fiind aproape identic cu cel de la încercarea cu return, dar de data aceasta mesajul de final nu se mai pierde (singura modificare este în linia 9).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
private static void prim(int n) { int divizori = 0; int d = 1; while (d <= n) { if (n % d == 0) { divizori++; } if (divizori == 3) { break; } d++; } if (divizori == 2) { System.out.println(n + " este prim!"); } else { System.out.println(n + " nu este prim!"); } } |
Atât pentru varianta aceasta cât și pentru unele dintre cele anterioare putem face niște modificări care să îmbunătățească puțin performanța. Nu are niciun rost să verificăm dacă am ajuns la trei divizori decât dacă tocmai am incrementat numărul acestora. Funcția rescrisă ar putea fi următoarea:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
private static void prim(int n) { int divizori = 0; int d = 1; while (d <= n) { if (n % d == 0) { divizori++; if (divizori == 3) { break; } } d++; } if (divizori == 2) { System.out.println(n + " este prim!"); } else { System.out.println(n + " nu este prim!"); } } |
Există numeroase alte optimizări care pot fi făcute; așa cum am spus la început, această modalitate de verificare a primalității unui număr este ineficientă.
Ce se întâmplă dacă avem bucle imbricate? Instrucțiunea break are efect doar în interiorul buclei din care face parte; buclele "exterioare" nu sunt afectate. Dacă dorim să întrerupem toate buclele trebuie să căutăm alte soluții.
Uneori nu dorim să întrerupem execuția buclei, ci doar să oprim execuția iterației curente (să sărim direct la evaluarea condiției). Pentru aceasta avem la dispoziție instrucțiunea continue. Ea este utilă dacă la fiecare pas executăm anumite operațiuni, dar atunci când o anumită condiție este îndeplinită nu are rost să le continuăm.
Având la dispoziție o funcție prim ușor modificată (returnează o valoarea booleană care ne spune dacă un număr este sau nu prim), am putea dori să afișăm suma și produsul numerelor prime cuprinse între 1 și 20. Putem găsi multe soluții, printre care și una care folosește continue:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
class NumerePrime { private static boolean prim(int n) { int divizori = 0; int d = 1; while (d <= n) { if (n % d == 0) { divizori++; if (divizori == 3) { break; } } d++; } if (divizori == 2) { return true; } else { return false; } } public static void main (String[] args) throws java.lang.Exception { int suma = 0; int produs = 1; for (int i = 1; i <= 20; i++) { if (!prim(i)) { continue; } suma += i; produs *= i; } System.out.println("Suma: " + suma); System.out.println("Produsul: " + produs); } } |
Putem face câteva observații, care nu sunt neapărat legate de break sau continue:
- Datorită faptului că prim returnează o valoare booleană și rezultatul evaluării unei condiții este o astfel de valoare, codul din liniile 15 - 19 poate fi înlocuit cu un simplu return divizori == 2;.
- Noua funcție prim returnează doar valoarea booleană, fără a mai afișa nimic; din acest motiv ar fi fost corect ca în linia 10 să folosim direct return false; pentru a întrerupe execuția funcției, fără să ne mai intereseze bucla (evident, și execuția ei ar fi fost întreruptă); la sfârșit am fi putut presupune că numărul este prim dacă nu este zero sau unu și am fi putut avea în linia 15 return n > 1;.
- Am calculat o sumă și un produs de mai multe numere; calculul se efectuează pas cu pas, adăugând sau înmulțind de fiecare dată câte un număr. Valorile inițiale sunt date de elementele neutre ale operațiilor (zero pentru adunare și unu pentru înmulțire). Folosind acest model puteți încerca să scrieți secvențe de cod care efectuează diverse calcule:
- suma numerelor impare cuprinse între 1 și 100
- suma pătratelor numerelor cuprinse între 1 și 1000
- produsul numerelor pare cuprinse între 1 și 10
- diferența dintre suma pătratelor numerelor pare cuprinse între 1 și 100 și suma pătratelor numerelor impare cuprinse între 1 și 100.
Va urma
Am văzut în cadrul acestui episod că putem lucra cu mai multe numere, dar o variabilă poate conține la un moment dat unul singur. Dar, în episodul II am văzut că un string conține mai multe caractere. În cadrul următorului episod vom vedea cum poate fi extins acest concept și pentru alte tipuri.