Differences

This shows you the differences between two versions of the page.

Link to this comparison view

poo-is-ab:laboratoare:02 [2024/10/12 20:27]
razvan.cristea0106 [Programarea Orientată pe Obiecte (POO)]
poo-is-ab:laboratoare:02 [2025/12/07 13:26] (current)
razvan.cristea0106 [Diferențe C/C++]
Line 1: Line 1:
-===== Laborator 02 - Inițiere în POO =====+===== Laborator 02 - Diferențe C/C++ =====
  
 **Autor: Răzvan Cristea** **Autor: Răzvan Cristea**
Line 7: Line 7:
 Studentul va fi capabil la finalul acestui laborator să: Studentul va fi capabil la finalul acestui laborator să:
  
-  * înțeleagă diferențele ​fundamentale ​dintre ​o structură (struct) și o clasă (class) în C++ +  * recunoască diferențele dintre C și C++ 
-  * definească corect o clasă ​și să explice conceptul acesteia +  * realizeze unui program în C++ care să valorifice caracteristicile și facilitățile specifice limbajului
-  * înțeleagă ce este un obiect și să folosească obiectele în programe ​C++ +
-  * creeze și să implementeze funcții membre pentru o clasă, înțelegând rolul acestora în manipularea datelor +
-  * scrie un program ​complet ​în C++ care să conțină o clasă, respectând principiile POO+
  
 ==== Introducere ==== ==== Introducere ====
  
-Până acum, erați obișnuițsă vă organizați codul folosind funcțiifiecare subprogram având ​un rol bine definit. Paradigma ​**Orientată Obiect (OO)** ​presupune că știți deja cum se scrie funcție, însă diferențconstă în modul în care va fi structurat codul. Pe parcursul acestui laborator, vom observa și înțelege ​cum funcționează această organizare.+În cadrul acestui laborator vom evidenția principalele diferențe dintre limbajele C șC++. Vom analiza, prin exemple simple, modul în care C++ extinde caracteristicile limbajului C și vom pune bazele unei înțelegeri mai profunde a acestuia. Astfelvom avea un punct de plecare solid pentru paradigma ​**Orientată Obiect (OO)** ​pe care vom studia începând cu laboratorul următor. Pentru ​a înțelege ​diferențele dintre cele două limbaje mai întâi ar trebui să știm care sunt asemănările,​ iar pentru asta recomandăm citirea [[poo-is-ab:​laboratoare:​01| laboratorului 1]].
  
-De-a lungul anului întâi ați "​simulat"​ paradigma **OO** prin folosirea **structurii**. Vom vedea însă câteva diferențe semnificative între ce știți deja și ce veți învăța în cadrul laboratorului curent.+==== Diferențe C/C++ ====
  
-==== Cuvântul cheie struct ====+=== Citirea și afișarea variabilelor ​===
  
-**Structura** este un tip de date special ​**definit** de către programatorPrin intermediul ei programatorul poate stoca sub **același nume** mai multe proprietăți ​de care are nevoie atunci când lucrează ​la o aplicație. Pentru a crea o structura de obicei se asociază obiecte ​din viața reală ​în cod.+În C pentru citirea și afișare variabilor se realiza utilizând funcțiile ​**scanf** și **printf**. În C++ vom folosi operatorul ​**%%>>​%%** pentru citirea datelor ​de la tastatură sau din fișiere și operatorul **%%<<​%%** pentru afișarea datelor ​în fișiere sau consolă.
  
-Să urmărim cu atenție exemplul de cod de mai jos unde am alcătuit structura **Avion**.+== Citirea și afișarea în C ==
  
-<​code ​cpp+<​code ​c
-#include <iostream> +#include <stdio.h>
- +
-struct Avion +
-{  +
- int numarLocuri;​  +
- int anFabricatie;​ +
- float capacitateRezervor;​ +
-}; +
- +
-void afisareAvion(const Avion& avion) +
-+
- std::cout << "​Numarul de locuri din avion este: " << avion.numarLocuri << '​\n';​ +
- std::cout << "Anul de fabricatie al avionului este: " << avion.anFabricatie << '​\n';​ +
- std::cout << "​Capacitatea rezervorului avionului este: " << avion.capacitateRezervor << " litri\n";​ +
-}+
  
 int main() int main()
 { {
- Avion avion// in limbajul C era eroare de compilare daca nu foloseam typedef la definirea structurii +    int x;
-        /* struct Avion avion; // ar fi fost declaratia corecta pentru o variabila de tip Avion in C */+
  
- avion.numarLocuri = 230+    printf("​Introduceti un numar: ")
- avion.anFabricatie = 2000; +    ​scanf("​%d",​ &x);
- avion.capacitateRezervor = 755.5f;+
  
- afisareAvion(avion);+    printf("​Numarul introdus de utilizator este: %d\n", x);
  
- return 0;+    ​return 0;
 } }
 </​code>​ </​code>​
  
-Am creat o funcție auxiliară, care primește ca parametru o **referință constantă** de tip **Avion**, pentru a afișa datele parametrului **avion**. Am transmis parametrul prin **referință** pentru a evita copierea inutilă a variabilei avion din funcția main. Referința este **constantă**,​ deoarece nu dorim să modificăm câmpurile avionului ​în funcția de afișare.+== Citirea ​șafișarea în C++ ==
  
-<note important>​Se poate observa deja o diferență între modul de utilizare a structurii în limbajele C și C++. Compilatorul de C++ recunoaște structura **Avion** ca un tip de date definit de către programator,​ permițând crearea de instanțe ale acesteia fără a fi necesară utilizarea cuvântului cheie **struct** în declarație.</​note>​ +<​code ​cpp
- +#include <iostream>
-În limbajul C pentru a putea avea programul identic cu cel de mai sus trebuie să procedăm în felul următor. +
- +
-<​code ​c+
-#include <cstdio> +
- +
-typedef struct Avion Avion; +
- +
-struct Avion +
-+
- int numarLocuri;​ +
- int anFabricatie;​ +
- float capacitateRezervor;​ +
-}; +
- +
-void afisareAvion(Avion avion) +
-+
- printf("​Numarul de locuri din avion este: %d\n", avion.numarLocuri);​ +
- printf("​Anul de fabricatie al avionului este: %d\n", avion.anFabricatie);​ +
- printf("​Capacitatea rezervorului avionului este: %.2f litri\n",​ avion.capacitateRezervor);​ +
-}+
  
 int main() int main()
 { {
- Avion avion;+    int x;
  
- avion.numarLocuri = 230+    std::cout << "​Introduceti un numar: "// cout vine de la console output 
- avion.anFabricatie = 2000; +    ​std::​cin >> x// cin vine de la console input
- avion.capacitateRezervor = 755.5f;+
  
- afisareAvion(avion);+    std::cout << "​Numarul introdus de utilizator este: " << x << '​\n'​;
  
- return 0;+    ​return 0;
 } }
 </​code>​ </​code>​
  
-De acum înainte când vom discuta despre structuri ne vom referi doar la cele din limbajul ​C++. Variabilele **numarLocuri**, ​**anFabricatie** și **capacitateRezervor** poartă denumirea de câmpuri sau membrideoarece se găsesc în interiorul declarației structurii **Avion**. Prin intermediul operatorului **"​."​** am avut acces **direct** la acești membri pe care am putut să îi modificăm ​și mai apoi i-am folosit la afișarea avionului în funcția **main**+<​note>​Este de menționat faptul ca în C++ și funcțiile ​**scanf** și **printf** se pot utilizadar ca și recomandare ar fi mai indicată utilizarea operatorilor de citire ​și afișare ai limbajului, deoarece sunt mai specializați pentru ceea ce vom învăța pe parcursul semestrului.</​note>​
  
-Până aici nu este nimic nou față de ce știațdeja de la disciplinele **PCLP** ​și respectiv **PA**. Cu toate acestea care este diferența fundamentală între structura din C și cea din C++? Răspunsul îl vom afla mai târziu după ce vom înțelege ce este **Programarea Orientată Obiect**.+== Citirea ​și scrierea datelor în fișiere ==
  
-==== Programarea Orientată pe Obiecte ​(POO====+În această secțiune vom prezenta modul de citire și scriere a datelor în fișierele text în limbajul C++. La fel ca în limbajul C aici avem posibilitatea să folosim funcțiile **fscanf** și **fprintf** însă se recomandă folosirea librăriei **fstream** care este specializată pentru lucrul cu fișiere. Dacă dorim să citim date dintr-un fișier vom volosi variabile de tipul **ifstream** (**input file stream**) iar pentru pentru scriere se utilizează tipul **ofstream** ​(**output file stream**).
  
-**Programarea Orientată Obiect**, tradusă din englezescul **Object-Oriented Programming (OOP)**, ​este un nou mod de a scrie și a organiza codul sursă, practic o nouă paradigmă ​de programare așa cum am mai menționat anterior. ​**POO** oferă o modalitate mai intuitivă ​și mai naturală de a învăța programarea,​ deoarece reflectă într-o anumită măsură modul în care percepem lumea reală. În POO, conceptele din viața de zi cu zi, cum ar fi obiectele și relațiile dintre ele, sunt transpuse în cod prin **clase** și **obiecte**. Astfel, programatorii pot modela entități reale, fiecare având **atribute** (**proprietăți/​câmpuri/​membri**) și **comportamente** (**funcții membre/​metode**),​ aceasta fiind o modalitate prin care programarea a devenit mai ușor de înțeles și de gestionat. Această abordare modulară simplifică dezvoltarea de aplicații complexe și încurajează reutilizarea codului.+Mai jos este prezentat ​un exemplu simplu ​de folosire ​datelor ​de tip **ifstream** și **ofstream**.
  
-=== Principiile POO ===+<code cpp> 
 +#include <​fstream>​ 
 +#include <​iostream>​
  
-Ca orice paradigmă de programare aceasta trebuie să aibă și un set de reguli care trebuiesc aplicate în momentul în care se dorește dezvoltarea unei aplicații ​**OOP**. Aceste principii fundamentale sunt 4 la număr și le vom discuta pe fiecare separat.+void scrieInFisier(const charnumeFisier) 
 +
 +    std::​ofstream outFile(numeFisier);​
  
-Cele 4 principii ale POO sunt:+    if (!outFile) // verificam daca fisierul a fost deschis 
 +    { 
 +        std::cerr << "​Eroare la deschiderea fisierului pentru scriere!\n";​ 
 +        return; 
 +    }
  
-  - **Abstractizarea** +    outFile << "​Ana"​ << " " << 20 << " " << 9.5 << '​\n';​ 
-  - **Încapsularea** +    ​outFile << "​Ion"​ << " " << 22 << " " << 8.75 << '​\n';​ 
-  - **Moștenirea** +    ​outFile << "​Maria"​ << " " << 19 << " " << 10.0 << '​\n';​
-  - **Polimorfismul**+
  
-== Abstractizarea ==+    outFile.close();​ 
 +}
  
-Fiind primul principiu ​**OOP** pe care îl învățăm trebuie să știm că acesta este utilizat cu scopul de a defini **clase** care să imite obiectele din realitate. Practic **abstractizarea** este procesul de transpunere în codul sursă a caracteristicilor unui obiect din realitate.+void citesteDinFisier(const charnumeFisier) 
 +
 +    std::​ifstream inFile(numeFisier);​
  
-<note important>​În **POO** subiectul principal despre care vom discuta este **clasa**, deoarece pe baza ei vom putea aplica **toate principiile** enunțate mai sus și vom putea înțelege care este de fapt scopul pentru care există acest stil de scriere ​codului sursă.</note>+    if (!inFile) // verificam daca fisierul ​fost deschis 
 +    { 
 +        std::​cerr ​<< "​Eroare la deschiderea fisierului pentru citire!\n";​ 
 +        return; 
 +    }
  
-În C++ o clasă se declară folosind cuvântul cheie (keyword) **class**. Să urmărim exemplul de mai jos.+    int varsta; 
 +    double nota; 
 +    char nume[50];
  
-<code cpp> +    std::​cout ​<< "​Inregistrarile citite din fisier se pot observa mai jos\n\n"​;
-class Brad +
-+
- int vechime; +
- float inaltime; +
- double pret; +
-}; +
-</​code>​+
  
-Se poate observa că am declarat o clasă denumită **Brad**, care conține ca și **proprietăți ​(membri)** 3 variabile și anume**vechime****inaltime** și **pret**. Astfel am aplicat primul principul **OOP** prin crearea unei clase care imită un obiect din realitate. La prima vedere putem spune că structura și clasa sunt similare diferența fiind doar keyword-ul folosit însă vom vedea mai târziu care este diferența clară între structură și clasă în limbajul C++.+    while (inFile >> nume >> varsta >> nota// citim pana cand nu mai avem date 
 +    { 
 +        std::cout << "Nume: " << nume 
 +            << "Varsta: " << varsta 
 +            << ", Nota: " << nota << '​\n';​ 
 +    }
  
-== Încapsularea ==+    inFile.close();​ 
 +}
  
-Încapsularea se referă la modul în care o clasă își protejează datele, ascunzându-le de mediul exterior. Cu alte cuvinte nu putem modifica membrii unei clase direct, deoarece avem nevoie de acces la aceștia.+int main() 
 +
 +    const char* denumireFisier = "​studenti.txt";
  
-Pentru a înțelege ce presupune încapsularea trebuie să menționăm mai întâi ce sunt **specificatorii de acces** ai unei clase în C++. **Specificatorii de acces**, în C++, sunt cuvinte cheie prin intermediul cărora se poate decide dacă membrul unei clase poate fi vizibil în exteriorul acesteia sau nu.+    scrieInFisier(denumireFisier);​ 
 +    citesteDinFisier(denumireFisier);​
  
-În limbajul C++ există 3 specificatori de acces și anume: **private**,​ **protected**,​ **public**.+    return 0; 
 +
 +</​code> ​
  
-**Specificatorul de acces private**+Iar în consolă output-ul arată ca mai jos.
  
-Acest specificator presupune ca membrii (câmpurile) unei clase să **nu** poată fi accesați direct ​din exterior, singura care poate avea acces direct la propriile câmpuri fiind clasa însăși.+<​file>​ 
 +Inregistrarile citite ​din fisier se pot observa mai jos
  
-**Specificatorul de acces protected**+Nume: Ana, Varsta: 20, Nota: 9.5 
 +Nume: Ion, Varsta: 22, Nota: 8.75 
 +Nume: Maria, Varsta: 19, Nota: 10 
 +</​file>​
  
-Similar cu specificatorul ​**private**, datele marcate cu **protected** nu pot fi accesate direct din exteriorul clasei. Cu toate acesteaspre deosebire ​de specificatorul ​**private**, clasele derivate (moștenitoare) au acces la aceste date.+<note tip>În mod normal nu este necesară apelarea funcției ​**close** pentru a închide fluxurile de **citire** și de **scriere** în fișierdeoarece acestea au un **mecanism automat** ​de închidere atunci când se termină execuția unei funcții. În cazul exemplului de mai sus nu este necesară apelarea funcției ​**close** deoarece variabilele de tip **ifstream** și **ofstream** sunt variabile locale în funcțiile în care apar. Dacă am fi scris tot codul în funcția **main** atunci apelarea funcției **close** devenea obligatorie după ce terminam partea de scriere în fișier.</​note>​
  
-**Specificatorul de acces public**+=== Alocarea dinamică a memoriei ===
  
-Acest specificator anunță faptul că datele membre ale unei clase sunt publice ​și oricine din exterior le poate accesa ​și modifica fără probleme.+Dacă în cazul citirii ​și afișării variabilelor lucrurile nu erau cu mult diferite, dacă discutăm despre alocare dinamică există câteva diferențe seminficative pe care o sa le observăm în exemplele următoare.
  
-<note warning>​Membrii unei clase sunt **by default** privați. Acest lucru înseamnă că **nu** avem acces la ei direct pentru a-i putea modifica.</​note>​+== Alocare dinamică în C ==
  
-Vom rescrie declarația clasei ​**Brad** punând explicit specificatorul private chiar dacă știm deja că cei 3 membri sunt privați din start conform a ceea ce am menționat anterior.+Pentru a aloca dinamic în C folosim la alegere funcția **malloc** sau funcția **calloc**, singura diferență între ele fiind faptul ​că funcția **calloc** initializează cu 0 valorile. Pentru eliberarea memoriei se folosește funcția **free**.
  
 <code cpp> <code cpp>
-class Brad+#include <​iostream>​ 
 + 
 +int main()
 { {
-private: // specificatorul de acces private, nu era necesar sa il punem deoarece membrii deja erau privati+    int* ptr1 = (int*)malloc(sizeof(int));​ 
 +    *ptr1 = 5;
  
- int vechime; +    ​// sau folosind calloc
- float inaltime; +
- double pret; +
-}; +
-</code>+
  
-Dacă dorim să instanțiem clasa **Brad** va trebui să declarăm un **obiect** de tipul acesteia după cum vom putea observa în codul de mai jos.+    intptr2 = (int*)calloc(1, sizeof(int));​
  
-<code cpp> +    std::​cout ​<< ​*ptr2 << '​\n';​
-#​include ​<iostream>​+
  
-class Brad +    *ptr2 = *ptr1;
-+
-private:+
  
- int vechime+    std::cout << *ptr1 << '​\n'​
- float inaltime; +    ​std::​cout << *ptr2 << '​\n'​;
- double pret; +
-};+
  
-int main() +    free(ptr1); 
-+    free(ptr2);
- Brad brad// obiect de tipul clasei Brad (instanta a clasei Brad)+
  
- return 0;+    ​return 0;
 } }
 </​code>​ </​code>​
  
-<note important>​Un obiect nu este altceva decât un reprezentant al clasei, care respectă structura acesteia, adică deține toate proprietățile pe care clasa le pune la dispoziție. Pentru a înțelege puteți face analogia cu oamenii. Toți oamenii au plămâni, aceasta fiind o caracteristică comună speciei umane. Diferențele pot să apară la starea de sănătate ​plămânilor,​ deoarece, ​în general, persoanele mai tinere au plămâni mai sănătoși comparativ cu cei ai persoanelor vârstnice. Starea de sănătate a plămânilor poate varia și în funcție de factori precum stilul de viață, expunerea la poluare sau fumat.</​note>​ +Dacă dorim să alocăm dinamic ​un vector putem proceda ​în felul următor.
- +
-Dacă vom dori să accesăm membrii clasei **Brad** direct din funcția **main** vom primi o eroare de compilare, deoarece membrii sunt **inaccesibili**.+
  
 <code cpp> <code cpp>
 #include <​iostream>​ #include <​iostream>​
- 
-class Brad 
-{ 
- int vechime; 
- float inaltime; 
- double pret; 
-}; 
  
 int main() int main()
 { {
- Brad brad;+    int nrElemente = 5; 
 +    int* vector = (int*)malloc(nrElemente * sizeof(int));
  
- brad.vechime ​= 2; // eroare de compilare, membrul vechime este privat +    vector[0] ​3; 
- brad.inaltime ​5.25f// gresit, membrul inaltime este inaccesibil ​ +    vector[1] = 2; 
- brad.pret ​299.99// membrul pret este marcat ca private+    vector[2] = -2; 
 +    ​vector[3] ​10
 +    ​vector[4] ​8;
  
- return 0;+    std::cout << "​Vectorul alocat dinamic este: "; 
 + 
 +    for (int i = 0; i < nrElemente; i++) 
 +    { 
 +        std::cout << vector[i] << ' '; 
 +    } 
 + 
 +    free(vector);​ 
 + 
 +    ​return 0;
 } }
 </​code>​ </​code>​
  
-O primă soluție pentru a putea face programul ​să nu mai aibă eroare de compilare ar fi ca membrii clasei ​**Brad** să fie publici.+Iar dacă vrem să realocăm spațiul din vector folosim funcția ​**realloc**.
  
 <code cpp> <code cpp>
 #include <​iostream>​ #include <​iostream>​
  
-class Brad+int main()
 { {
-public: // in acest moment cei 3 membri ai clasei sunt vizibili in exteriorul acesteia+    int nrElemente = 5; 
 +    int* vector = (int*)malloc(nrElemente * sizeof(int));​
  
- int vechime+    vector[0] = 3
- float inaltime+    ​vector[1] = 2
- double pret+    ​vector[2] = -2
-};+    ​vector[3] = 10; 
 +    vector[4] = 8;
  
-int main() +    std::cout << "​Vectorul alocat dinamic este: ";
-+
- Brad brad;+
  
- brad.vechime ​2+    for (int i 0i < nrElemente; i++) 
- brad.inaltime = 5.25f+    { 
- brad.pret = 299.99;+        std::cout << vector[i] << ' '
 +    }
  
- return 0; +    nrElemente = 8;
-+
-</​code>​+
  
-<note warning>​Acest lucru însă ​**nu** este o variantă indicatădeoarece ​**am încălcat principiul încapsulării datelor** și în plus, acum oricine poate modifica din exterior membrii clasei. După cum se poate observa, clasa **Brad** scrisă în această manieră se comportă exact ca o structură. Diferența între cele 2 (struct și classconstă în faptul că într-o structură totul este marcat cu **public** by default în timp ce o clasă are membrii by default marcați cu **private**. Vom menționa mai târziu cum putem respecta principiul încaspulării datelor pentru clasa **Brad** când vom discuta despre funcții membre (metode).</​note>​+    vector = (int*)realloc(vectornrElemente ​sizeof(int));
  
-== Moștenirea ==+    vector[5] ​15; 
 +    vector[6] ​20; 
 +    vector[7] ​25;
  
-Moștenirea (**Inheritance**) permite unei clase să **preia** proprietățile și comportamentele unei alte clase, oferind astfel posibilitatea reutilizării codului. În același timp, **clasa derivată (clasa moștenitoare)** poate adăuga funcționalități specifice pe care clasa de bază nu le are, extinzând astfel comportamentul acesteia și adaptându-l la nevoile proprii. ​+    std::cout << "​\nVectorul realocat dinamic este: ";
  
-<note>Ca șexemplu putem lua omul care este un mamifer. Omul la fel ca toate mamiferele are plămâni, dar spre deosebire de alte animale din categoria mamiferelor acesta are o postură bipedă. Vom detalia mai multe despre moștenire peste câteva laboratoare ștot atunci vom vorbi mai mult despre specificatorul de acces **protected**.</note>+    for (int i = 0; i nrElemente; ​i++) 
 +    { 
 +        std::cout << vector[i] <' '; 
 +    }
  
-== Polimorfismul ==+    free(vector);​
  
-Cuvântul **polimorfism** provine din limba greacă, de la **polys (multe)** și **morphos (formă)**. Polimorfismul în **POO** este întâlnit în două forme și anume: **early polymorphism** sau **compile time polymorphism** care apare atunci când realizăm un procedeu numit **supraîncărcare (overloading)** și **run time polymorphism** care apare când este realizat procedeul de **suprascriere (overriding)**. Despre conceptul de overriding vom discuta peste câteva laboratoare.+    return 0; 
 +
 +</​code>​
  
-În C++ conceptul de **overloading** este aplicat pe funcții. Acest lucru presupune faptul că putem avea mai multe funcții cu **același nume**, dar acestea să **difere** prin **numărul și tipul parametrilor**.+== Alocare dinamică în C++ ==
  
-Să urmărim exemplul ​de cod de mai jos.+În C++ alocarea și dezalocarea memoriei sunt mai simple, deoarece aici **nu** mai avem **funcții** ci **operatori specifici**. Pentru a aloca memoria în C++ se folosește **operatorul new**, iar pentru a elibera memoria folosim **operatorul delete**. 
 + 
 +**Alocarea dinamică pentru o singură adresă de memorie** 
 + 
 +În exemplul de mai jos puteți vedea diferite variante de alocare și dezalocare pentru un singur spațiu de memorie.
  
 <code cpp> <code cpp>
 #include <​iostream>​ #include <​iostream>​
- 
-void interschimbare(int* a, int* b) 
-{ 
- if (a == nullptr || b == nullptr) 
- { 
- return; 
- } 
- 
- int auxiliar = *a; 
- *a = *b; 
- *b = auxiliar; 
-} 
- 
-void interschimbare(int&​ a, int& b) 
-{ 
- int auxiliar = a; 
- a = b; 
- b = auxiliar; 
-} 
  
 int main() int main()
 { {
- int 22+    ​int* ptr1 new int// alocare dinamica fara initializare,​ compilatorul va atribui o valoare in mod aleator 
- int 8; +    int* ptr2 new int(10); // alocare dinamica cu initializare
- +
- interschimbare(&x, &y);+
  
- std::cout << ​"​Valoarea lui x este: " << x << '​\n';​ +    ​std::cout << ​*ptr1 << '​\n';​ 
- std::cout << ​"​Valoarea lui y este: " << y << '​\n';​+    std::cout << ​*ptr2 << '​\n';​
  
- interschimbare(xy);+    /*delete ptr1ptr2// desi nu da eroare de compilare va elibera doar spatiul pentru ptr1, nu se recomanda aceasta scriere pentru ca va genera memory leak-uri usor*/
  
- std::cout << "​Valoarea lui x este: " << x << '​\n'​+    delete ptr1
- std::cout << "​Valoarea lui y este: " << y << '​\n'​;+    ​delete ptr2;
  
- return 0;+    ​return 0;
 } }
 </​code>​ </​code>​
  
-Observăm că există două funcții numite ​**interschimbare**, care diferă doar prin tipul parametrilorAcesta ​este un exemplu de **supraîncărcare a funcțiilor ​(function overloading)**, un concept ce demonstrează polimorfismul în C++.+<note warning>​Pentru a face o **eliberare corectă** a memoriei **numărul** de delete-uri trebuie **să fie egal** cu **numărul** de new-uriDacă această regulă ​este respectată atunci nu vor apărea ​**memory leak-uri ​(scurgeri de memorie)**.</​note>​
  
-<note important>​Supraîncărcarea ​**nu** modifică comportamentul general al funcției, ci doar permite funcțiilor cu același nume să gestioneze diferite tipuri de date, așa cum se vede în exemplul anterior.</​note>​+**Alocarea dinamică pentru un bloc de memorie**
  
-=== Lucrul cu clase === +Dacă intenționăsă alocăm dinamic un vector ​în C++ vom folosi tot operatorul new, dar puțin diferit.
- +
-În această secțiune vom descoperi modul în care putem controla accesul la membrii unei clase. Pentru a permite accesul la membrii unei clase din exterior, este necesar să folosim funcții speciale definite în **zona publică** a clasei, cunoscute sub denumirea de **metode** sau **funcții membre**. În continuare, vom prezenta câteva categorii de funcții membre care facilitează interacțiunea controlată cu membrii clasei din exteriorul acesteia. +
- +
-== Constructorii == +
- +
-Așa cum le spune și numele această categorie de metode se ocupă de **construirea (inițializarea) obiectelor**. +
- +
-Constructorii au o serie de trăsături care îi fac pe aceștia foarte ușor de recunoscut după cum urmează: +
- +
-  - **Nu** au tip returnat nici măcar **void** +
-  - Au **același nume** cu clasa din care fac parte +
-  - Se apelează **doar** ​în momentul declarării unui obiect +
-  +
-Constructorii pot fi de mai multe tipuri și anume: fără parametri și cu parametri. Să urmărim exemplul de cod de mai jos pentru clasa **Brad**.+
  
 <code cpp> <code cpp>
 #include <​iostream>​ #include <​iostream>​
  
-class Brad+int main()
 { {
- int vechime+    ​int nrElemente = 5
- float inaltime; +    int* vector = new int[nrElemente]// se folosesc [] pentru a anunta compilatorul ca vrem sa alocam spatiu pentru un bloc de memorie continuu
- double pret;+
  
-public:+    for (int i = 0; i < nrElemente; i++) 
 +    { 
 +        std::cout << "​vector["​ << i << "] = "; 
 +        std::cin >> vector[i];​ 
 +    }
  
- Brad() +    std::cout << "​\nElementele vectorului alocat sunt: ";
-+
- vechime = 0; +
- inaltime = 0.0f; +
- pret = 0.0; +
-+
-};+
  
-int main() +    for (int i = 0; i < nrElemente; i++
-+    
- Brad brad; // echivalent cu scrie direct Brad brad = Brad();+        ​std::​cout << vector[i] << ' '; 
 +    } 
 + 
 +    delete[] vector; // se folosesc [] pentru ​anunta compilatorul ca ne dorim sa eliberam memoria unui bloc contiguu
  
- return 0;+    ​return 0;
 } }
 </​code>​ </​code>​
  
-Se poate observa că am definit și implementat constructorul fără parametri ​pentru ​clasa **Brad**. Constructorul fără parametri se mai numește și **constructor default** și dacă nu îl implementăm explicit ​**în cazul exemplului de mai sus** compilatorul se va ocupa el de generarea unui constructor default în care va popula membrii clasei ​**Brad** cu valori aleatoare. +<note warning>​În C++ **nu** există operator ​pentru **realocarea memoriei**. **Nu** este recomandată utilizarea ​**funcției realloc** pe un vector ​care a fost alocat cu **operatorul new**, deoarece ​va duce la un **comportament nedefinit**. Dacă vrem să realocăm vectorul va trebui mai întâi să îl dezalocăși apoi să îl realocăm cu noua dimensiune.</​note>​
- +
-Ca să nu creștem foarte mult numărul de linii din clasă în practică se dorește implementarea metodelor în afara clasei după cum putem observa mai jos.+
  
 <code cpp> <code cpp>
 #include <​iostream>​ #include <​iostream>​
  
-class Brad // declaratia clasei se va scrie intr-un fisier cu extensia .h+int main()
 { {
- int vechime+    ​int nrElemente = 6
- float inaltime; +    int* vector = new int[nrElemente];
- double pret;+
  
-public:+    for (int i = 0; i < nrElemente; i++) 
 +    { 
 +        std::cout << "​vector["​ << i << "] = "; 
 +        std::cin >> vector[i];​ 
 +    }
  
- Brad(); +    std::cout << "​\nElementele vectorului alocat sunt: ";
-};+
  
-Brad::Brad() // aceasta bucata de cod va exista intr-un fisier cu extensia .cpp +    for (int i = 0; i < nrElementei++) 
-+    { 
- vechime ​= 0; +        std::cout << vector[i] << ' '
- inaltime = 0.0f+    }
- pret = 0.0+
-}+
  
-int main() +    delete[] vector;
-+
- Brad brad; // echivalent cu a scrie direct Brad brad = Brad();+
  
- return 0; +    nrElemente = 3;
-+
-</​code>​+
  
-<​note>​Noi în cadrul laboratoarelor vom organiza codul în **fișiere header** (fișiere care conțin extensia **"​.h"​**) și **fișiere cu extensia "​.cpp"​**. În fișierele header vom scrie declarații pentru funcții, clase și metode din cadrul clasei dar **fără** a implementa nimic, deoarece acest lucru îl vom face în fisierele cu extensia **"​.cpp"​**.</​note>​+    vector = new int[nrElemente];​
  
-Să parcurgem după cum urmează exemplul de cod de mai jos.+    std::cout << "​\n\n===================================================\n\n";​
  
-<code cpp> +    for (int i = 0; i nrElemente; i++) 
-#​include ​<iostream>+    { 
 +        std::​cout ​<< "​vector["​ << i << "] = "; 
 +        std::​cin ​>> vector[i];​ 
 +    }
  
-class Brad +    std::cout << "​\nElementele vectorului realocat sunt: ";
-+
- int vechime; +
- float inaltime; +
- double pret;+
  
-public:+    for (int i = 0; i < nrElemente; i++) 
 +    { 
 +        std::cout << vector[i] << ' '; 
 +    }
  
- // Brad();+    std::cout << '​\n'​;
  
- Brad(const float& inaltime)// constructor cu un parametru+    delete[] vector;
  
- Brad(const int& vechime, const float& inaltime, const double& pret); +    ​return 0;
-}; +
- +
-int main() +
-+
- Brad brad; // eroare de compilare, nu exista constructor default +
- +
- return 0;+
 } }
 </​code>​ </​code>​
  
-Se poate observa că am comentat constructorul fără parametri și am definit alți doi constructori pentru clasa **Brad**, lucru care pune în lumină **polimorfismul**,​ însă ne alegem cu o eroare de compilare atunci când declarăm un obiect de tip **Brad**.+=== Tipul referință ===
  
-<note warning>​Dacă programatorul definește alți constructori compilatorul ​**nu** îl va mai genera pe cel fără parametrideci programatorul va avea datoria să declare ​și să implemeteze constructorul default pentru a putea instanția obiecte cu ajutorul luisau mai are alternativa să se folosească **doar** de versiunile de constructori care au cel puțin un parametru.</​note>​+În C++, referințele reprezintă o extensie esențială pentru gestionarea variabilelor. Practic, o referință este un **alias** pentru o variabilă deja existentă. Spre deosebire de pointericare stochează adresa unei variabile ​și necesită dereferențiere explicităreferințele acționează direct asupra variabilei asociate, eliminând necesitatea manipulării adreselor.
  
-Prin urmare ori de câte ori definim constructori cu parametri trebuie să avem în vedere că tot noi va trebui să definim ​și să implementăm constructorul defaultÎn continuare vom implementa constructorul ​cu un parametru după cum urmează.+Odată ce am asociat o referință unei variabile, orice operație efectuată asupra acesteia se reflectă direct asupra variabilei originale. Putem privi referința ca pe o persoană care are două prenume: indiferent de numele folosit, este vorba despre aceeași persoană. Astfel, deși se aseamănă ​cu pointerii, referințele se disting prin simplitate și prin modul mai natural în care permit accesul la date. Diferențele între referințe și pointeri sunt mai multe la număr însă le vom prezenta pe cele mai importante mai jos. 
 + 
 +  - Referința când este declarată trebuie **instant inițializată** 
 +  - După inițializare referința **nu** mai poate fi schimbată 
 +  - Referințele **nu** pot fi nule 
 +  - Referințele **nu** trebuie dereferențiate
  
 <code cpp> <code cpp>
-Brad::Brad(const float& inaltime)+#include <​iostream>​ 
 + 
 +int main()
 { {
- vechime ​0+    int x 10
- inaltime ​inaltime// eroare de compilare +    ​int&​ ref x;
- pret = 0.0; +
-+
-</​code>​+
  
-La prima vedere suntem mirați de faptul că avem o eroare de compilare, deoarece în mod normal ne-am aștepta ca variabila **inaltime** din partea stângă a egalului să fie de fapt membrul **inaltime** din zona privată a clasei **Brad**, însă din păcate nu este vorba despre membrul din zona privată ci despre însuși parametrul constructorului. Practic noi am încercat să îi atribuim parametrului propria valoare, dar compilatorul ne-a salvat prin atenționarea că nu putem modifica valoarea constantă a unui parametru.+    ref = 16;
  
-În cazul de față ​este vorba despre o situație de ambiguitate în care compilatorul nu știe că de fapt noi vrem să stocăm valoarea parametrului constructorului în membrul clasei **Brad**. Cum putem totuși să rezolvăm această problemă ​pentru ​a ne putea îndeplini obiectivul și anume acela de a stoca valoarea trimisă ca parametru în câmpul clasei?+    std::cout << x << '​\n';​ // x devine 16 deoarece ref este un alias pentru ​el
  
-**Cuvântul cheie this**+    x = 20;
  
-Pentru a putea soluționa problema de mai sus trebuie să introducem un nou keyword specific **POO** pe care îl vom întâlni în aproape toate limbajele de programare care suportă paradigma **OO** și anume cuvântul cheie **this**.+    std::cout << ref << '​\n';​ // ref este 20 datorita faptului ca se refera la x
  
-În C++ **this** este un **pointer** pe care **toate funcțiile membre** îl primesc ca parametru pe **prima poziție**. În C++ acest pointer **nu** este vizibil, **dar asta nu înseamnă că nu există**, în lista de parametri ai metodei, ​dar în limbajul Python acesta ​este prezent și poartă denumirea de **self** și obligatoriu trebuie pus de către **programator** pe **prima poziție** în lista de parametri a fiecărei **funcții membre** dintr-o clasă.+    int y = 0; 
 +    ref = y; // poate parea schimbarea referintei ​dar in realitate ​este doar atribuirea valorii 0 lui ref
  
-Pointerul **this** indică **adresa obiectului curent** și are rolul de a **rezolva** problemele de ambiguitate între **membrii clasei** și **parametrii metodelor**. Acesta ​este utilizat pentru a **diferenția** în mod clar variabilele locale de câmpurile obiectului, permițând atribuirea **corectă** a valorilor parametrilor metodelor **membrilor interni** ai obiectului.+    std::cout << x << '​\n';​ // x este 0 din motive evidente
  
-Să soluționăm problema de mai sus utilizând pointerul **this**. +    return ​0;
- +
-<code cpp> +
-// Brad::​Brad(Brad* this, const float& inaltime) -> asa arata de fapt aceasta functie la compilare +
-Brad::​Brad(const float& inaltime) +
-+
- vechime = 0; // nu este nevoie de this deoarece compilatorul stie ca este vorba despre campul vechime al obiectului curent +
- this->​inaltime = inaltime; +
- pret = 0.0; // aceeasi explicatie ca la vechime+
 } }
 </​code>​ </​code>​
  
-În acest moment compilatorul știe foarte clar ce vrem să facem și anume că ne dorim să stocăm în membrul ​**inaltime** valoarea parametrului.+Pentru a înțelge mai bine, în desenul de mai jos se poate observa de fapt cine este **ref** ​și că nu face altceva decât ​să partajeze aceeași zonă de memorie ca și **x**. 
 + 
 +{{ :​poo-is-ab:​laboratoare:​reference_logic.jpg |}}
  
-Așadar acum putem să implementăm ​și al treilea constructor al clasei **Brad** și să îl utilizăm fără probleme după cum urmează.+Cu ajutorul acestui tip de date putem să ne facem viața mult mai ușoară, deoarece putem trimite parametrii unei funcții prin referință.
  
 <code cpp> <code cpp>
 #include <​iostream>​ #include <​iostream>​
  
-class Brad+void alocareVector(int*&​ v, const int& dim)
 { {
- int vechime+    v = new int[dim]
- float inaltime; +}
- double pret; +
- +
-public: +
- +
- Brad(); // constructor default +
- +
- Brad(const float& inaltime); // constructor cu un parametru+
  
- Brad(const int& ​vechime, const floatinaltime, const double& pret); // constructor cu 3 parametri +void citireVector(int*v, const intdim)
-}; +
- +
-// Brad::​Brad(Brad* this) - asa arata la compilare +
-Brad::Brad()+
 { {
- vechime ​= 0; // nu este nevoie de this, dar nu este gresit nici daca scriem this->​vechime = 0+    for (int i = 0; i < dimi++) 
- inaltime ​0.0f+    { 
- pret = 0.0;+        std::cout << "​vector["​ << i << "​] ​"
 +        ​std::​cin >> v[i]; 
 +    }
 } }
  
-// Brad::Brad(Bradthis, const floatinaltime) - asa arata la compilare +void afisareVector(const int* const& ​v, const intdim)
-Brad::Brad(const floatinaltime)+
 { {
- vechime ​= 0; +    std::cout << "​Elementele vectorului alocat sunt: "; 
- this->​inaltime = inaltime+ 
- pret = 0.0;+    for (int i = 0; i < dim; i++) 
 +    { 
 +        std::cout << v[i] << ' '
 +    } 
 + 
 +    std::cout << '​\n'​;
 } }
  
-// Brad::Brad(Bradthis, const int& vechime, const float& inaltime, const double& pret) - asa arata la compilare +void dezalocareVector(int*& ​v)
-Brad::​Brad(const int& vechime, const float& inaltime, const doublepret)+
 { {
- this->​pret ​pret; +    if (v !nullptr) 
- this->​vechime = vechime+    { 
- this->​inaltime = inaltime;+        delete[] v
 +    }
 } }
  
 int main() int main()
 { {
- Brad brad; // se apeleaza constructorul default +    int nrElemente ​5
- Brad bradut(4.75f);​ // se apeleaza constructorul cu un parametru (echivalent cu a scrie Brad bradut ​Brad(4.75f);) +    int* vector ​nullptr;
- Brad b(2, 2.25f, 399.99); // se apeleaza constructorul cu un parametru (echivalent cu a scrie Brad b Brad(2, 2.25f, 399.99);)+
  
- return 0;+    alocareVector(vector,​ nrElemente);​ 
 +    citireVector(vector,​ nrElemente);​ 
 +    afisareVector(vector,​ nrElemente);​ 
 +    dezalocareVector(vector);​ 
 + 
 +    ​return 0;
 } }
 </​code>​ </​code>​
  
-<note important>​Este bine să utilizați pointerul ​**this** ori de câte ori vreți să vă referiți la câmpurile obiectului curent. În cazul constructorului default nu este necesar să folosiți **this** deoarece compilatorul știe că este vorba despre obiectul curent. Problema apare atunci când în interiorul metodelor definim variabile sau parametri ​care **coincid ca și denumire cu câmpurile clasei**deoarece folosirea lui **this** devine obligatorie pentru ​evita conflictele de nume.</​note>​+Funcțiile folosesc referințe la pointeri (de exemplu, int*v) pentru a permite modificarea efectivă a pointerilor în funcția apelantă. Astfel, funcțiile pot aloca memorie sau modifica adresele pointerilor direct ​în cadrul funcției ​care le-a apelatfără ​returna un nou pointer.
  
-== Funcțiile membre ​de tip get și set ==+Parametrii transmiși ca referințe constante (de exemplu, const int& dim) sunt utilizați pentru a asigura că valoarea acestora nu este modificată în interiorul funcției, protejând astfel valorile originale. Acest lucru ajută la prevenirea modificărilor accidentale ​și la creșterea clarității.
  
-Aceste metode sunt specialedeoarece ele oferă un mod controlat de acces la membrii clasei. Menționăm ​că **nu** este obligatoriu să existe pentru fiecare membru al clasei o pereche de **accesori** de tip **get-set**acest lucru depinzând de cerințele aplicației ​și de modul de implementare. **Getterii** permit accesul la valorile câmpurilor unui obiect, ​în timp ce **setterii** oferă posibilitatea de a modifica valorile acestoraasigurând astfel un control mai strict și securizat asupra proprietăților unui obiect.+Funcția afisareVector folosește referințe constante la pointeri (const int* const& v)pentru a garanta ​că atât adresa vectoruluicât și conținutul acestuia nu vor fi modificate ​în timpul afișăriimenținând integritatea datelor.
  
-Denumirea acestor metode trebuie să înceapă cu prefixul ​**get** sau **set** și să conțină numele câmpului asupra căruia acționează. Să ne îndreptăm atenția spre următoarele exemple ​de declarări pentru getteri și pentru setteri în clasa **Brad**.+<note important>​C++ introduce posibilitatea de a inițializa pointerii cu **nullptr**,​ care este specific doar pentru acest tip de date. Acesta funcționează similar ​cu **NULL**, dar cu un avantaj important: ​**nullptr** este un tip de date dedicat **pointerilor**,​ ceea ce previne atribuirea sa accidentală altor tipuri de variabile. În C++, **NULL** este definit doar ca un **macro define** care reprezintă valoarea 0 și poate fi atribuit chiar și variabilelor care nu sunt de tip pointer, lucru care poate duce la confuzii nedorite.</​note>​
  
 <code cpp> <code cpp>
-class Brad+#include <​iostream>​ 
 + 
 +int main()
 { {
- int vechime+    ​int x = NULL// valid 
- float inaltime; +    int* ptr = NULL// valid
- double pret;+
  
-public:+    ptr = nullptr; // valid 
 +    x = nullptr; // eroare de compilare, x nu este un pointer
  
- Brad()+    return 0
- Brad(const int& vechime, const float& inaltime, const double& pret);+
 +</​code>​
  
- int getVechime() const; // metoda la compilare arata asa: int getVechime(const Brad* this); +=== Funcții cu același nume ===
- float getInaltime() const; +
- double getPret() const;+
  
- void setVechime(const int& vechime); ​// metoda la compilare arata asa: void setVechime(Bradthisconst int& vechime); +În C++ avem posibilitatea de a declara funcții cu **același nume** dar care să difere prin **numărul și/sau tipul parametrilor**, acest procedeu fiind cunoscut și sub numele de **supraîncărcare a funcțiilor**. Această modalitate de declarare a funcțiilor este cunoscută în **Programarea Orientată Obiect** sub numele de **polimorfism**,​ care în limba greacă înseamnă **multe forme**. Vom oferi mai multe detalii pe parcursul întregului semestru despre acest principiu al **POO**.
- void setInaltime(const float& inaltime);​ +
- void setPret(const double& pret); +
-}; +
-</​code>​+
  
-Iar în exemplul de mai jos am implementat aceste metode și le-am testat pentru ​putea evidenția ​funcționalitatea lor.+Să urmărim ​exemplul ​de cod de mai jos care ilustrează polimorfismul ​patru funcții.
  
 <code cpp> <code cpp>
-Brad::​Brad() +#include <​iostream>​
-+
- vechime = 0; +
- inaltime = 0.0f; +
- pret = 0.0; +
-}+
  
-Brad::Brad(const int& vechimeconst float& inaltime, const double& pret)+int suma(int aint b)
 { {
- this->​pret = pret; +    return a + b;
- this->​vechime = vechime; +
- this->​inaltime = inaltime;+
 } }
  
-int Brad::​getVechime() const // acest const este adresat pointerului this (continutul de la adresa spre care pointeaza nu poate fi modificat)+float suma(float a, float b)
 { {
- // this->​vechime = 5; // imposibil din cauza const-ului din semnatura functiei +    ​return ​a + b;
- +
- return ​vechime; // era corect si daca scriam return this->​vechime;+
 } }
  
-float Brad::​getInaltime() const+float suma(float a, int b)
 { {
- return ​inaltime;+    ​return ​a + b;
 } }
  
-void Brad::​setVechime(const int& vechime)+float suma(int a, float b)
 { {
- if (vechime <= 0) // verificam daca valoarea primita ca parametru este una valida +    ​return ​a + b;
-+
- return+
-+
- +
- this->​vechime = vechime; // trebuie folosit this deoarece parametrul are acelasi nume ca si campul clasei +
-+
- +
-void Brad::​setInaltime(const float& inaltime) +
-+
- if (inaltime <= 0) +
-+
- return; +
-+
- +
- this->​inaltime = inaltime;+
 } }
  
 int main() int main()
 { {
- Brad brad; // se apeleaza constructorul default+    std::cout << suma(2, 5) << '​\n'​; // prima functie denumita suma 
 +    std::cout << suma(2.5f, 5.5f) << '​\n';​ // a doua functie denumita suma 
 +    std::cout << suma(2.85f, 8) << '​\n';​ // a treia functie denumita suma 
 +    std::cout << suma(10, 8.5f) << '​\n';​ // // a patra functie denumita suma
  
- brad.setVechime(3);​ +    ​return 0;
- brad.setInaltime(5.5f);​ +
- +
- std::cout << "​Vechimea bradului este: " << brad.getVechime() << " ani\n";​ +
- std::cout << "​Inaltimea bradului este: " << brad.getInaltime() << " metri\n";​ +
- +
- return 0;+
 } }
 </​code>​ </​code>​
  
-==== ==== +<note important>​Trebuie subliniat faptul că **tipul ​de return** al unei funcții **nu** este luat în considerare în contextul **polimorfismului prin supraîncărcare**. Ceea ce definește **polimorfismul** în acest caz este exclusiv **semnătura funcției**, adică **numele împreună ​cu lista și tipurile parametrilor**. De aceeadouă funcții care diferă doar prin tipul valorii returnate **nu** sunt considerate supraîncărcări valide, generând astfel o eroare ​de compilare. În schimb, funcțiile care diferă prin **numărul****tipul** sau chiar **ordinea** parametrilor reprezintă forme acceptate de **polimorfism**,​ așa cum se poate observa ​în cazul ultimelor patru funcții din cadrul exemplului de mai sus.</​note>​
- +
-Pentru a vă familiariza cu noul mod de a scrie codul, declarațși implementați un constructor ​cu doi parametri la alegere ​și în plusimplementați metodele accesor ​de tip **get** și **set** pentru câmpul ​**pret** și apoi testați ce ați implementat ​în funcția **main**. +
 ==== Concluzii ==== ==== Concluzii ====
  
-În acest laborator, ​am explorat în detaliu diferențele fundamentale dintre clase și structuri, subliniind faptul ​că singura deosebire constă în specificatorul de acces implicit: la structuri, membrii sunt publici, iar la clase sunt privați. Totodată, am învățat cum să creăm obiecte ​și cum să le gestionăm proprietățile folosind constructoriprecum ​și accesori prin intermediul getter-ilor ​și al setter-ilor. Acest lucru ne oferă un control ​mai fin asupra datelor ​și ne permite o manipulare clară și sigură a obiectelor într-un program care folosește conceptele ​**OOP**. Astfelam dobândit o înțelegere mai bună a principiilor fundamentale ale **Programării Orientate Obiect**, principii pe care le vom aplica pe întregul parcurs al semestrului.+Prin parcurgerea acestui ​laborator, ​putem spune cu certitudine ​că limbajul C++ reprezintă o evoluție firească a limbajului Cadăugând numeroase concepte și mecanisme care ne fac munca de programatori mai simplă și mai expresivă. Am văzut că librăria **fstream**,​ alocarea dinamică realizată într-un mod simplificat,​ introducerea referințelorposibilitatea de a supraîncărca funcții ​și suportul pentru **Programarea Orientată Obiect** diferențiază C++ de C și ne oferă un cadru mai flexibil pentru dezvoltarea aplicațiilor. Totuși, este important să reținem faptul că C++ rămâne compatibil cu C la nivel de bază, ceea ce îl face un limbaj versatil, potrivit atât pentru stilul ​**procedural**, cât și pentru cel **orientat pe obiecte**.
poo-is-ab/laboratoare/02.1728754031.txt.gz · Last modified: 2024/10/12 20:27 by razvan.cristea0106
CC Attribution-Share Alike 3.0 Unported
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0