Programare procedurală
Dezvoltarea abilităților de scriere a programelor structurate folosind un limbaj de programare (C++ sau Pascal).
Bacalaureat
Structura unui program și tipuri de date
Un program C++ are o structură fixă: directive de includere (de exemplu `#include <iostream>`), eventuale declarații globale, apoi funcția main, punctul din care începe execuția. Fiecare instrucțiune se termină cu punct și virgulă, iar blocurile de instrucțiuni se delimitează cu acolade.
Tipurile de date fundamentale, cu ce trebuie să știi despre fiecare:
- int — numere întregi; pe majoritatea sistemelor reține valori între aproximativ -2·10⁹ și 2·10⁹. Depășirea acestui interval (overflow) produce rezultate aparent absurde, fără niciun mesaj de eroare;
- long long — întregi mari, până la aproximativ 9·10¹⁸; necesar când produsele sau sumele depășesc limita lui int;
- char — un singur caracter, memorat prin codul său ASCII; de aceea caracterele se pot compara și aduna ca numere;
- float / double — numere reale în virgulă mobilă; double are precizie dublă și e alegerea implicită;
- bool — valorile de adevăr true și false; în C++ orice valoare nenulă e considerată adevărată.
O variabilă este o zonă de memorie cu nume și tip, a cărei valoare se poate schimba; o constantă (declarată cu `const`) primește valoare o singură dată și nu mai poate fi modificată. Capcana de bază: o variabilă neinițializată nu conține 0 din oficiu, ci o valoare reziduală imprevizibilă.
Operatori și expresii
Operatorii aritmetici pe întregi ascund cele două detalii care decid punctajul la BAC:
- împărțirea întreagă: în C++, `7 / 2` este 3, nu 3.5 — când ambii operanzi sunt întregi, rezultatul se trunchiază (se taie partea zecimală);
- restul împărțirii: `7 % 2` este 1. Operatorul `%` funcționează doar pe întregi.
Perechea `/` și `%` este motorul prelucrării cifrelor unui număr: `n % 10` extrage ultima cifră, iar `n / 10` elimină ultima cifră. Aproape orice subiect cu „suma cifrelor”, „oglinditul” sau „prima cifră” se reduce la aceste două operații într-un ciclu.
Operatorii relaționali (`<`, `<=`, `>`, `>=`, `==`, `!=`) produc valori logice. Greșeala clasică de sintaxă cu efect de logică: `=` este atribuire, `==` este comparație. Scris `if (x = 5)`, codul atribuie 5 lui x și condiția devine mereu adevărată.
Operatorii logici: `&&` (și), `||` (sau), `!` (negație). C++ folosește evaluare scurtcircuitată: la `&&`, dacă primul operand e fals, al doilea nu se mai evaluează deloc; la `||`, dacă primul e adevărat, la fel. De aceea condiția `j <= n && v[j] < x` este sigură, pe când ordinea inversă poate accesa un element din afara vectorului.
Incrementarea `i++` și forma prescurtată `s += x` apar constant în subiecte; prioritatea operatorilor urmează regula: aritmetici, apoi relaționali, apoi logici.
Structurile alternative
Structura secvențială execută instrucțiunile în ordine, una după alta. Deciziile se iau cu structura alternativă:
- `if (conditie) instructiune;` — ramura se execută doar dacă condiția e adevărată;
- `if (conditie) instructiune1; else instructiune2;` — exact una dintre ramuri se execută.
Două detalii care schimbă rezultatul urmăririi de cod:
- fără acolade, ramura unui if conține o singură instrucțiune; a doua instrucțiune scrisă sub if, indentată frumos, se execută necondiționat — indentarea nu are nicio valoare sintactică în C++;
- într-o cascadă de if-uri fără acolade, else se leagă de cel mai apropiat if fără else, nu de primul.
Pentru alegeri multiple pe valori discrete există `switch`: fiecare `case` tratează o valoare, iar `break` oprește căderea în cazul următor. Fără break, execuția continuă în case-urile de sub el (fall-through) — comportament testat în grile.
În pseudocodul de examen, structura alternativă apare ca `dacă ... atunci ... altfel ...`, iar traducerea în C++ trebuie să păstreze exact ramurile: o condiție negată neatent (de exemplu `x >= 0` transformat în `x < 0` cu ramurile neschimbate) inversează complet logica programului. Verificarea sigură e testarea pe valori de graniță: chiar valoarea din condiție, una imediat sub și una imediat peste.
Structurile repetitive
C++ oferă trei instrucțiuni repetitive, iar diferențele dintre ele sunt subiect direct de examen:
- while (conditie) — ciclu cu test inițial: condiția se verifică înainte de fiecare execuție, deci corpul se poate executa de 0 ori;
- do ... while (conditie) — ciclu cu test final: corpul se execută o dată, apoi se verifică condiția, deci corpul se execută cel puțin o dată, indiferent de condiție;
- for (initializare; conditie; pas) — ciclul cu contor, echivalent cu un while în care inițializarea se face înainte, iar pasul la sfârșitul fiecărei iterații.
Echivalența cu pseudocodul de BAC: `cât timp ... execută` este while; `repetă ... până când (conditie)` este do-while, dar cu condiția negată — „până când p” înseamnă „cât timp NU p”. Uitarea acestei negări la transformare este una dintre cele mai frecvente greșeli de punctaj.
Un ciclu care nu modifică nimic din condiția sa devine ciclu infinit. La urmărirea execuției, metoda sigură este tabelul de valori: pe fiecare linie, valorile variabilelor după o iterație, până la ieșirea din ciclu — se răspunde apoi din tabel, nu din memorie.
Ciclurile imbricate înmulțesc numărul de iterații: două for-uri de câte n pași fac n² execuții ale corpului interior. Instrucțiunile `break` (ieșire imediată din ciclul curent) și `continue` (salt la iterația următoare) modifică fluxul și apar în subiecte tocmai pentru că sunt ușor de trecut cu vederea.
Funcții definite de utilizator
O funcție C++ se declară cu tip nume(lista de parametri). Tipul din față este tipul valorii returnate; `void` marchează o funcție care nu returnează nimic (o procedură). Instrucțiunea return expresie; încheie imediat execuția funcției și trimite valoarea către apelant; într-o funcție void, `return;` doar iese.
Detalii care se pierd ușor:
- o funcție cu tip de retur diferit de void trebuie să returneze o valoare pe toate ramurile de execuție; o ramură fără return produce comportament imprevizibil;
- după primul `return` executat, restul funcției nu se mai execută — un return pus în interiorul unui for încheie și ciclul, și funcția;
- funcția trebuie declarată sau definită înainte de utilizare; altfel se folosește un prototip (antetul urmat de punct și virgulă) plasat înaintea lui main.
Domeniul de vizibilitate: o variabilă declarată într-un bloc există doar în acel bloc (variabilă locală); una declarată în afara oricărei funcții este globală și accesibilă peste tot. Variabilele globale sunt inițializate implicit cu 0, cele locale nu. Dacă o locală poartă același nume cu o globală, în interiorul funcției numele se referă la locală — globala e ascunsă.
La transmiterea parametrilor, regula practică de examen: rezultatele care trebuie să ajungă înapoi în apelant se transmit prin referință (cu `&`) sau se returnează; datele doar citite se transmit prin valoare. Tablourile se transmit în C++ prin adresă din oficiu, deci modificările asupra elementelor lor se văd întotdeauna în apelant — excepția pe care mulți o uită.
De reținut
- împărțire întreagă
- operația / aplicată pe doi operanzi întregi în C++, al cărei rezultat este câtul trunchiat, fără parte zecimală; 7 / 2 = 3
- operatorul %
- operatorul care returnează restul împărțirii întregi; n % 10 extrage ultima cifră a numărului n, iar n / 10 o elimină
- ciclu cu test inițial
- structura repetitivă while, la care condiția se verifică înaintea fiecărei execuții a corpului, deci corpul se poate executa de zero ori
- ciclu cu test final
- structura repetitivă do-while (repetă ... până când), la care corpul se execută cel puțin o dată, condiția fiind verificată abia după execuție
- variabilă locală
- variabilă declarată într-un subprogram sau bloc, care există doar pe durata execuției acestuia și nu este inițializată implicit
- variabilă globală
- variabilă declarată în afara oricărei funcții, vizibilă în tot programul și inițializată implicit cu 0
- return
- instrucțiunea care încheie imediat execuția funcției și transmite apelantului valoarea expresiei; codul de după return-ul executat nu se mai execută
- prototip de funcție
- antetul funcției urmat de punct și virgulă, plasat înaintea utilizării, care anunță compilatorul de existența funcției definite mai jos
- evaluare scurtcircuitată
- regula prin care la && al doilea operand nu se mai evaluează dacă primul e fals, iar la || nu se mai evaluează dacă primul e adevărat
Greșeli frecvente
Greșit: Folosirea lui = în loc de == în condiții: if (x = 5)
Corect: = atribuie, == compară; if (x = 5) atribuie 5 lui x și condiția devine mereu adevărată — condiția corectă este if (x == 5)
Greșit: Traducerea lui „repetă ... până când p” în do { ... } while (p)
Corect: „până când p” înseamnă că ciclul continuă cât timp p e FALS, deci traducerea corectă este do { ... } while (!p) — condiția se neagă
Greșit: Așteptarea rezultatului 3.5 pentru expresia 7 / 2
Corect: Pe operanzi întregi, / face împărțire întreagă: 7 / 2 = 3; pentru rezultat real, cel puțin un operand trebuie convertit la real, de exemplu 7 / 2.0
Greșit: Presupunerea că instrucțiunile indentate sub un if fără acolade aparțin toate ramurii
Corect: Fără acolade, if controlează o singură instrucțiune; restul se execută necondiționat — indentarea nu are valoare sintactică în C++
Greșit: Considerarea că variabilele locale sunt inițializate automat cu 0
Corect: Doar variabilele globale sunt inițializate implicit cu 0; o locală neinițializată conține o valoare reziduală imprevizibilă și trebuie inițializată explicit
Test — 6 întrebări ca la examen
1. Ce valoare are expresia C++ 17 % 5 + 17 / 5?
- 2
- 3
- 5
- 6.4
Vezi răspunsul
5. 17 % 5 = 2 (restul) și 17 / 5 = 3 (câtul întreg), deci suma este 5. Distractorul 6.4 vine din calculul 17 / 5 ca împărțire reală (3.4) — exact greșeala împărțirii întregi tratate ca reală.
2. Câte iterații execută corpul ciclului while (n > 0) n = n / 10; pentru n = 2049?
- 3
- 4
- 10
- 2049
Vezi răspunsul
4. n devine succesiv 204, 20, 2, 0 — patru împărțiri, adică exact numărul de cifre ale lui 2049. Răspunsul 3 apare când se uită ultima iterație, cea în care n trece de la 2 la 0.
3. Care este diferența esențială dintre while și do-while?
- do-while nu poate conține instrucțiuni compuse
- while se execută întotdeauna cel puțin o dată
- do-while își execută corpul cel puțin o dată, pentru că testul e la final
- while este mai rapid decât do-while
Vezi răspunsul
do-while își execută corpul cel puțin o dată, pentru că testul e la final. La do-while condiția se verifică după execuția corpului, deci corpul rulează minimum o dată; la while testul e inițial și corpul poate să nu ruleze deloc. Varianta b afirmă exact invers — inversarea celor două cicluri e confuzia standard.
4. O funcție C++ are antetul void f(int &x). Apelul f(a) pentru variabila întreagă a are ca efect:
- lucrul pe o copie a lui a, fără efect asupra ei
- modificările făcute asupra lui x se reflectă direct în a
- eroare de compilare, pentru că funcțiile void nu pot avea parametri
- returnarea valorii lui x către apelant
Vezi răspunsul
modificările făcute asupra lui x se reflectă direct în a. Simbolul & din antet marchează transmiterea prin referință: x devine un alt nume pentru a, deci orice modificare a lui x este o modificare a lui a. Prima variantă descrie transmiterea prin valoare — distincția-cheie testată constant la BAC.
5. Ce afișează secvența: int x = 3; if (x > 5) x = x + 10; x = x + 1; cout << x;
- 3
- 4
- 13
- 14
Vezi răspunsul
4. Condiția x > 5 e falsă, deci x = x + 10 se sare; dar x = x + 1 nu aparține if-ului (nu există acolade) și se execută necondiționat: x devine 4. Răspunsul 3 e al celor care cred că ambele instrucțiuni sunt pe ramura if — capcana lipsei acoladelor.
6. Funcția int f(int n) { if (n % 2 == 0) return n / 2; return 3 * n + 1; } este apelată cu f(f(7)). Rezultatul este:
- 11
- 22
- 8
- 7
Vezi răspunsul
11. f(7): 7 e impar, deci returnează 3 * 7 + 1 = 22; f(22): 22 e par, deci returnează 22 / 2 = 11. Distractorul 22 este rezultatul intermediar — greșeala celor care uită că apelul exterior se aplică peste rezultatul celui interior.
Deschide varianta interactivă — cu AI care îți explică