Structuri de control

Fiecare instrucțiune a unui program este o propoziție simplă C++, precum declarațiile de variabile și expresiile văzute în secțiunile anterioare. Ele se termină întotdeauna cu punct și virgulă (;) și sunt executate exact în ordinea în care apar în program.

Dar programele nu sunt limitate la o înșiruire secvențială de instrucțiuni. În timpul execuției sale, un program poate repeta unele secvențe de cod sau pot lua decizii, în funcție de care să se bifurce. În acest scop, C++ furnizează instrucțiuni de control al fluxului de execuție care ne permit să precizăm ce trebuie să facă programul, când și în ce circumstanțe.

Multe dintre structurile de control explicate în această secțiune necesită (sub)instrucțiuni generice ca parte a sintaxei lor. Acestea pot fi simple propoziții C++ - precum o singură instrucțiune, terminată cu punct punct și virgulă (;) - sau o instrucțiune compusă. O instrucțiune compusă este un grup de instrucțiuni (fiecare dintre ele terminată cu punct și virgulă), dar toate grupate într-un singur bloc cuprins între acolade: {}:

{ instructiune1; instructiune2; instructiune3; }

Întregul bloc este considerat ca o singură instrucțiune (compusă, la rândul său, din mai multe instrucțiuni). De câte ori avem o instrucțiune generică în sintaxa unei structuri de control, aceasta poate fi o instrucțiune simplă sau una compusă.

Instrucțiuni de decizie: if și if-else

Cuvântul cheie if este folosit pentru a executa o instrucțiune sau un bloc, dacă și numai dacă este îndeplinită o condiție. Sintaxa ei este:

if (conditie) instructiune

Aici, conditie este expresia ce trebuie evaluată. În cazul în care această conditie este adevărată, se execută instructiune. Dacă ea este falsă, instructiune nu va fi executată (pur și simplu este ignorată) și programul continuă cu ceea ce urmează imediat după întreaga structură decizională.
De exemplu, următorul fragment de cod afișează mesajul (x este 100), numai dacă valaorea memorată în variabila x este exact 100:

1
2
if (x == 100)
  cout << "x este 100";

Dacă x este diferit de 100, această instrucțiune este ignorată și nu se va afișa nimic.

Dacă doriți să se execute mai mult de o instrucțiune când condiția este îndeplinită, atunci ar trebui să includeți aceste instrucțiuni între acolade ({}), astefel încât să formeze un bloc:

1
2
3
4
5
if (x == 100)
{
   cout << "x este ";
   cout << x;
}

Ca de obicei, indentarea și întreruperile de linii din cod nu au efect, deci codul de mai sus este echivalent cu:

1
if (x == 100) { cout << "x este "; cout << x; }

Deciziile cu if pot să precizeze ce se întâmplă și dacă nu este îndeplinită condiția cu ajutorul cuvântului cheie else care adaugă instrucțiuni alternative. Sintaxa este:

if (conditie) instructiune1 else instructiune2

unde instructiune1 se execută în cazul în care condiția este adevărată, iar în caz negativ se execută instructiune2.

De exemplu:

1
2
3
4
if (x == 100)
  cout << "x este 100";
else
  cout << "x nu este 100";

Aceasta afișează x este 100, dacă, într-adevăr, x are valoarea 100 și, dacă nu, afișează x nu este 100.
Putem concatena mai multe structuri if + else pentru a verifica un domeniu de valori. De exemplu:

1
2
3
4
5
6
if (x > 0)
  cout << "x este pozitiv";
else if (x < 0)
  cout << "x este negativ";
else
  cout << "x este 0";

Această secvență afișează un mesaj în funcție de valaorea lui x (pozitivă, negativă sau zero) prin concatenarea a două structuri two if-else. Și aici am fi putut executa mi mult de o instrucțiune pentru fiecare situație prin gruparea într-un bloc cu ajutorul acoladelor: {}.

Instrucțiuni repetitive (bucle)

Buclele repetă o instrucțiune de un anumit număr de ori sau cât timp este îndeplinită o anumită condiție. Ele se implementează cu ajutorul cuvintelor cheie while, do și for.

Bucla while

Cea mai simplă buclă este while. Sintaxa ei este:

while (expresie) instructiune

Bucla while pur și simplu repetă instructiune cât timp expresie este adevărată. Dacă după o execuție a instructiunii, expresie nu mai este adevărată, bucla se sfârșește și programul continuă exact cu prima instrucțiune de după buclă. De exemplu, să ne uităm puțin la o numărătoare inversă folosind bucla while:

// o numaratoare inversa cu while
#include <iostream>
using namespace std;

int main ()
{
  int n = 10;

  while (n>0) {
    cout << n << ", ";
    --n;
  }

  cout << "listare terminata!\n";
}
10, 9, 8, 7, 6, 5, 4, 3, 2, 1, listare terminata!

Prima instrucțiune din main îi atribuie lui n valoarea 10. Acesta este primul număr din numărătoarea inversă. Apoi începe bucla while: dacă această valoare începlinește condiția n>0 (adică n este mai mare decât zero), atunci se execută blocul care urmează după condiție și se va repeta atât timp cât condiția (n>0) își păstrează valoarea adevărată.

Întregul proces din programul anterior poate fi interpretat conform algoritmului următor (începând cu main):

  1. n ia o valoare
  2. Se testează condiția lui while (n>0). În acest moment avem două posibilități:
    • conditie are valoarea true: se execută instrucțiunea (la pasul 3)
    • conditie are valoarea false: se ignoră instrucțiunea și se continuă cu instrucțiunea de după (la pasul 5)
  3. Execută instrucțiune:
    cout << n << ", ";
    --n;
    (afișează valoarea lui n și micșorează n cu 1)
  4. Sfârșit bloc. Întoarecere automată la pas 2.
  5. Continuă programul imediat după bloc:
    afișează listare terminata! și programul se termină.

Să subliniem că folosirea unei bucle while presupune că există un punct în care bucla să se termine, adică instrucțiunea trebuie să altereze cumva valorile verificate în condiție,astfel încât să devină falsă la un moment dat. Altfel, bucla s-ar continua la infinit.
În exemplul de mai sus, bucla include --n, adică valoarea variabilei (n) evaluate în condiție se micșorează cu unu - ceea ce va face ca, după un anumit numă de iterații, condiția (n>0) să aiba valoarea fals. Mai exact, după 10 iterații, n devine 0, iar condiția nu mai este adevărată și bucla se termină.

Evident, complexitatea acestei bucle este banală pentru calculator, iar numărătoarea inversă se execută aproape instantaneu, fără niciun fel de întârziere între elementele contorului (dacă vă interesează, vedeți sleep_for pentru un exemplu de numărătoare cu întârzieri).

Bucla do-while

Foarte asemănătoare este bucla do-while, cu sintaxa:

do instructiune while (conditie);

Se comportă ca și o buclă while, exceptând faptul că se evaluează conditie după ce se execută instrucțiune și nu înainte, ceea ce garantează execuția cel puțin o dată a instructiunii, chiar și atunci când conditir nu este îndeplinită deloc. De exemplu, următorul program redă pe ecran orice text introduce utilizatorul, până când acesta introduce la revedere:

// program ecou
#include <iostream>
#include <string>
using namespace std;

int main ()
{
  string sir;
  do {
    cout << "Tastati un text: ";
    getline (cin,sir);
    cout << "Ati tastat: " << sir << '\n';
  } while (sir != "la revedere");
}
Tastati un text: hello
Ati tastat: hello
Tastati un text: cine-i acolo?
Ati tastat: cine-i acolo?
Tastati un text: la revedere
Ati tastat: la revedere

În general, alegem bucla do-while în loc de bucla while când instructiune trebuie executată cel puțin o dată. Așadar, valoarea pe care o are condiția la sfârșitul buclei este determinată chiar de instrucțiune. În exemplul anterior, de datele furnizate de utilizator în interiorul blocului depinde sfârșitul buclei. Și totuși, chiar dacă utilizatorul dorește terminarea buclei cât mai repede tastând la revedere, blocul din buclă trebuie executat cel puțin o dată pentru a-i permite tastarea, deci condiția va putea fi îndeplinită numai după executare.

Bucla for

Bucla for este proiectată să repete de un anumit număr de ori. Sintaxa ei este:

for (initializari; conditie; actualizari) instructiune;

Ca și bucla while, aceasta repetă instructiune cât timp este îndeplinită conditie. Dar, în plus, bucla for prevede ce să conțină anumite zone de memorie prin initializari și expresii de tip actualizari, executate înainte de prima execuție a buclei, respectiv după fiecare repetare a acesteia. De aceea, este foarte utilă folosirea de variabile contor în conditie.

Mecanismul de execuție este următorul:

  1. Se execută initializari. În general, aici se declară o variabilă contor și i se setează valoarea inițială. Acest cod este executat o singură dată, la începutul buclei.
  2. Se evaluează conditie. Dacă este îndeplinită, bucla va continua; dacă nu, se va termina și se sare peste instructiune direct la pasul 5.
  3. Se execută instructiune. Ca de obicei, ea poate fi o singură instrucțiune sau un bloc cuprins între acolade { }.
  4. Se execută actualizari și bucla revine la pasul 2.
  5. Bucla se termină: programul continuă cu instrucțiunea imediat următoare.

Iată și exemplul numărătorii inverse folosind o buclă for:

// numărătoare inversa folosind bucla for
#include <iostream>
using namespace std;

int main ()
{
  for (int n=10; n>0; n--) {
    cout << n << ", ";
  }
  cout << "listare terminata!\n";
}
10, 9, 8, 7, 6, 5, 4, 3, 2, 1, listare terminata!

Cele trei câmpuri dintr-o buclă for sunt opționale. Ele pot fi lăsate necompletate, dar semnul punct și virgulă dintre ele trebuie să apară (este obligatoriu). De exemplu, for (;n<10;) este o buclă fără initializari sau actualizari (echivalent cu o buclă while); și for (;n<10;++n) este o buclă cu actualizari, dar fără initializari (este posibil ca variabilei să i se fi atribuit deja o valoare înainte de buclă). O buclă fără conditie este echivalentă cu o buclă a cărei condiție are întotdeauna valoarea true (adică o buclă infinită).

Deoarece fiecare câmp este executat la un anumit moment de timp al buclei, poate ar fi utile mai multe expresii de tip initializari, conditie sau actualizari. Din păcate, acestea nu sunt instrucțiuni, ci doar simple expresii, deci nu pot fi înlocuite cu un bloc. Ca și expresiile, ele pot conține operatorul virgulă (,): acesta este un separator de expresii și poate fi utilizat acolo unde se așteaptă o singură expresie, dacă dorim să scriem mai mult. De exemplu, am putea să îl folosim într-o buclă for pentru a gestiona două variabile contor, pe care să le inițializăm și să le actualizăm:

1
2
3
4
for ( n=0, i=100 ; n!=i ; ++n, --i )
{
   // orice aici...
}

Această buclă s-ar executa de 50 de ori dacă nici n nici i nu ar fi modificate în interiorul buclei:



n începe cu valoarea 0 și i cu 100, condiția este n!=i (adică n este diferit de i). Deoarece n crește cu unu și i scade cu unu la fiecare repetiție, condiția buclei va avea valoarea fals după a 50-a repetiție, când atât n cât și i vor fi egale cu 50.

Buclă for pe mulțimi ordonate

Bucla for are și o altă sintaxă, care se folosește numai cu mulțimi ordonate:

for ( declaratie : multime ) instructiune;

Acest tip de buclă for repetă pentru toate elementele din multime, unde declaratie declară o variabilă ce poate lua valori ale elementelor din această mulțime. Mulțimile ordonate sunt secvențe de elemente, inclusiv tablouri, containere și alte tipuri care suportă funcțiile begin și end; cele mai multe dintre aceste tipuri nu au fost încă prezentate în acest tutorial, dar suntem pregătiți cu cel puțin un tip de mulțime ordonată: string-uri, care sunt secvențe de caractere.

Un exemplu de buclă for pe mulțimi ordonate pentru string-uri:

// bucla for pe multime ordonata
#include <iostream>
#include <string>
using namespace std;

int main ()
{
  string sir {"Hello!"};
  for (char c : sir)
  {
    std::cout << "[" << c << "]";
  }
  std::cout << '\n';
}
[H][e][l][l][o][!]

Observăm că înainte de două puncte (:) în bucla for avem declarația unei variabile de tip char (elementele dintr-un string sunt de tip char). Apoi, vom folosi această variabilă, c, în blocul de instrucțiuni pentru a reprezenta valoarea fiecărui element din mulțimea ordonată.

Aceasta este o buclă automatizată și nu necesită declarea explicită a vreunei variabile contor.

Buclele pe mulțimi ordonate pot deduce tipul elementelor cu auto. Mai exact, bucla de mai sus poate fi scrisă:

1
2
for (auto c : sir)
  std::cout << "[" << c << "]";

Aici, tipul lui c este dedus automat ca fiind tipul elementelor din sir.

Instrucțiuni de salt

Instrucțiunile de salt permit schimbarea fluxului unui program prin realizarea de salturi la anumite locații.

Instrucțiunea break

break părăsește o buclă, chiar și în cazul în care condiția sa de terminare nu este îndeplinită. Poate fi utilizată pentru a întrerupe o buclă infinită sau pentru a forța terminarea înainte de sfârșitul normal. De exemplu, să oprim numărătoarea inversă înainte de sfârșitul natural:

// exemplu de intrerupere bucla
#include <iostream>
using namespace std;

int main ()
{
  for (int n=10; n>0; n--)
  {
    cout << n << ", ";
    if (n==3)
    {
      cout << "numaratoare parasita!";
      break;
    }
  }
}
10, 9, 8, 7, 6, 5, 4, 3, numaratoare parasita!

Instrucțiunea continue

Instrucțiunea continue face ca programul să sară peste restul iterației curente, ca și cum s-ar fi ajuns la sfârșitul blocului, causând saltul la următoarea iterație. De exemplu, să sărim număru 5 în numărătoarea noastră:

// exemplu bucla cu continue
#include <iostream>
using namespace std;

int main ()
{
  for (int n=10; n>0; n--) {
    if (n==5) continue;
    cout << n << ", ";
  }
  cout << "listare terminata!\n";
}
10, 9, 8, 7, 6, 4, 3, 2, 1, listare terminata!

Instrucțiunea goto

goto permite saltul la un alt punct din program. Acest salt necondiționat ignoră nivelurile de includere și nu cauzează ...This unconditional jump ignores nesting levels, and does not cause any automatic stack unwinding. De aceea, este o caracteristică ce trebuie folosită cu grijă și, preferabil, în interiorul aceluiași bloc de instrucțiuni, ami ales în prezența variabilelor locale.Therefore, it is a feature to use with care, and preferably within the same block of statements, especially in the presence of local variables.

Punctul destinație este identificat de o etichetă, care va fi folosită ca argument pentru instrucțiunea goto. O etichetă este formată ca un identificator valid urmat de două puncte (:).

goto este socotită ca o caracteristică de tip low-level, fără a fi folosită concret în paradigma programării moderne de tip higher-level regăsită și în C++. Doar ca exemplu, iată o versiune a numărătorii inverse cu o intrucțiune goto:

// exemplu bucla cu goto
#include <iostream>
using namespace std;

int main ()
{
  int n=10;
eticheta_mea:
  cout << n << ", ";
  n--;
  if (n>0) goto eticheta_mea;
  cout << "salt afara!\n";
}
10, 9, 8, 7, 6, 5, 4, 3, 2, 1, salt afara!

Altă instrucțiune de selecție: switch.

Sintaxa instrucțiunii switch este puțin ciudată. Scopul ei este de a verifica dacă valoarea unei variabile sau expresii se regăsește printre mai multe expresii constante date. Este similară cu concatenarea mai multor instrucțiuni if-else, dar se limitează la expresii constante. Cea mai utilizată sintaxă este:

code>switch (expresie)
{
case constanta1:
grup-de-instructiuni-1;
break;
case constanta2:
grup-de-instructiuni-2;
break;
.
.
.
default:
grup-implicit-de-instructiuni
} /code>

Mecanismul de funcționare este următorul: switch evaluează expresie și verifică dacă este echivalentă cu constanta1; dacă da, se execută grup-de-instructiuni-1 până întâlnește o instrucțiune break. Când găsește această instrucțiune break, programul sare la sfârșitul întregii instrucțiuni switch (acolada închisă).

Dacă expresie nu are valoarea constanta1, atunci se va compara cu constanta2. Dacă sunt egale, atunci se execută grup-de-instructiuni-2 până se întâlnește un break și atunci sare la sfârșitul lui switch.

În final, dacă valoarea expresiei nu este egală cu niciuna dintre constantele specificate (pot fi oricâte), programul execută instrucțiunile scrise după eticheta default:, dacă există (aceasta este opțională).

Cele două secvențe de cod care urmează au același comportament, arătând o instrucțiune if-else echivalentă cu o instrucțiune switch:

exemplu switchif-else echivalent
code>switch (x) {
case 1:
cout "x este 1";
break;
case 2:
cout "x este 2";
break;
default:
cout "valoarea lui x este necunoscuta";
} /code>
code>if (x == 1) {
cout "x este 1";
}
else if (x == 2) {
cout "x este 2";
}
else {
cout "valoarea lui x este necunoscuta";
}
/code>

Instrucțiunea switch are sintaxa ciudată fiind moștenită din timpul primelor compilatoare C, deoarece folosește etichete în loc de blocuri. Cel mai adesea (așa cum am arătat mai sus), înseamnă că sunt necesare instrucțiuni break după fiecare grup de instrucțiuni, pentru fiecare etichetă. Dacă nu se include break, se vor executa toate instrucțiunile care urmează după acel case (inclusiv cele de la celelalte etichete), până la sfârșitul blocului switch sau până la întâlnirea unei instrucțiuni de salt (precum break).

Dacă în exemplul de mai sus ar fi lipsit instrucțiunea break după primul grup, pentru cazul în care x ar fi fost 1 programul nu ar fi sărit automat la sfârșitul instrucțiunii switch după tipărirea mesajului x este 1, ci ar fi continuat execuția instrucțiunilor și din case 2 (deci ari fi tipărit și x este 2). Și ari fi continuat până la întâlnirea unei instrucțiuni break sau până la sfârșitul blocului switch. Așadar, nu este necesar să includem între acolade {} instrucțiunile pentru fiecare ramură case. Poate fi util atunci când dorim să executăm același grup de instrucțiuni pentru mai multe valori posibile. De exemplu:

1
2
3
4
5
6
7
8
9
switch (x) {
  case 1:
  case 2:
  case 3:
    cout << "x este 1, 2 sau 3";
    break;
  default:
    cout << "x nu este nici 1, nici 2 si nici 3";
  }

Menționăm că switch este limitată la compararea expresiei evaluate cu expresii constante. Nu putem folosi pentru comparație variabile sau mulțimi ordonate, deoarece acestea nu reprezintă expresii constante valide în C++.

Pentru a verifica apartenența la mulțimi sau calori care nu sunt constante, vom concatena instrucțiuni if și else if.
Index
Index