Diferență între revizuiri ale paginii „C++ POO Lab Lucrarea 3”

De la WikiLabs
Jump to navigationJump to search
Linia 211: Linia 211:
  
 
= Supraîncărcarea operatorilor =
 
= Supraîncărcarea operatorilor =
 +
 +
Dacă dorim să tratăm instanțele claselor (i.e. obiectele) așa cum tratăm tipurile de date fundamentale, trebuie să putem folosi aceiași operatori pe care îi folosim si cu tipurile fundamentale. <br>
 +
Cunoaștem, de exemplu, faptul că putem aduna două valori de tip întreg folosind operatorul '+'. În mod similar, am dori să putem calcula și suma a două numere complexe definite de clasa Complex din următorul exemplu:
 +
<syntaxhighlight lang="C++">
 +
class Complex{
 +
    double mReal;      // partea reala
 +
    double mImaginar;  // partea imaginara
 +
 +
public:
 +
 +
    /*
 +
    * Constructorul ce initializeaza campurile obiectului
 +
    *
 +
    * */   
 +
    Complex(double real, double imaginar){
 +
        mReal    = real;
 +
        mImaginar = imaginar;
 +
    }
 +
 +
 +
    /*
 +
    * Constructorul fara argumente
 +
    *
 +
    * */
 +
    Complex() = default;
 +
 +
 +
    /*
 +
    * Metoda: get_suma
 +
    *
 +
    * Intoarce suma (sub forma de obiect de tip Complex) dintre obiectul curent si obiectul primit ca argument
 +
    *
 +
    * */
 +
    Complex get_sum(Complex alt_numar){
 +
        double real_rezultat    = mReal    + alt_numar.mReal;
 +
        double imaginar_rezultat = mImaginar + alt_numar.mImaginar;
 +
 +
        Complex rezultat(real_rezultat, imaginar_rezultat);
 +
 +
        return  rezultat;
 +
    }
 +
 +
 +
    /*
 +
    * Metoda: afisare
 +
    *
 +
    * Afiseaza numarul complex in formatul a+bi
 +
    *
 +
    * */
 +
    void afisare(){
 +
        std::cout << mReal << '+' << mImaginar << 'i' << std::endl;
 +
    }
 +
};
 +
</syntaxhighlight>
 +
 +
Folosind cunoștințele acumulate până acum, putem realiza suma a doua obiecte de tip Complex implementând funcția get_suma din exemplul de mai sus. <br>
 +
Totuși, aceasta nu ne permite să adunăm două obiecte de tip complex folosind operatorul '+', așa cum dorim.
 +
 +
Fie clasa Complex definită anterior. Acesta este un exemplu de utilizare a funcției get_sum:
 +
<syntaxhighlight lang="C++">
 +
int main() {
 +
    Complex complex_1 (5, 10);
 +
    Complex complex_2 (-1, 3);
 +
 +
    Complex suma = complex_1.get_sum(complex_2);
 +
 +
    suma.afisare();
 +
 +
    return 0;
 +
}
 +
</syntaxhighlight>
 +
 +
Output:
 +
<syntaxhighlight>
 +
4+13i
 +
</syntaxhighlight>

Versiunea de la data 29 martie 2022 20:01

Introducere

Această lucrare are ca scop familiarizarea cu următoarele noțiuni:

  • Destructorul
  • Referința
  • Supraîncărcarea operatorilor

Destructorul

Introducere

Așa cum s-a prezentat în laboratorul anterior, crearea unui obiect se face folosind o metodă specială, numită constructor. Printre altele, cu ajutorul constructorului putem:

  • Inițializa câmpurile obiectului pe care îl construim
  • Condiționa valorile cu care inițializăm câmpurile (de exemplu vârsta unei persoane nu poate avea valoare negativa)
  • Aloca memorie în mod dinamic

Pe de altă parte, odată ce nu mai avem nevoie de un obiect, acesta trebuie eliminat într-un mod controlat, astfel încât memoria ocupată să poată fi refolosită. Acest lucru este realizat cu ajutorul unei alte metode speciale, numită destructor, ce este apelată automat atunci când contextul în care a fost declarat obiectul se încheie (out of scope). Pentru mai multe informații legate de context (scope) în C++, accesați acest link.

Exemplu

În exemplul de mai jos sunt evidențiate mecanismele de ’construire’ si ’distrugere’ a obiectelor:

#include <iostream>

// clasa Array modeleaza un array alocat dinamic
class Array{
public:
    int *arr;

    // constructorul aloca memorie si stocheaza adresa primului element in *arr
    Array(int dimensiune){
        arr = (int*) malloc(dimensiune * sizeof(int));
        std::cout << "Acest mesaj provine din constructor" << std::endl;
    }

    // destructorul se asigura ca memoria alocata este eliberata atunci cand obiectul este distrus
    ~Array(){
        free(arr);
        std::cout << "Acest mesaj provine din destructor" << std::endl;
    }
};

int main() {
    Array array(4);
    std::cout << "Obiectul a fost construit!" << std::endl;

    return 0;
}

Output:

Acest mesaj provine din constructor
Obiectul a fost construit!
Acest mesaj provine din destructor

Observăm că, odată ce contextul în care a fost declarat obiectul (contextul este, aici, funcția main), destructorul acestui obiect a fost apelat automat si a fost generat mesajul corespunzător.

Referința

Introducere

O referință reprezintă un nume alternativ cu care poate fi accesat un obiect/o variabilă. Așa cum vom prezenta în continuare, mecanismul de referințe simplifică accesul la date intre diferite entități (de exemplu între funcții).

Sintaxa pentru declararea unei referințe este următoarea: <tip_de_date>& nume_referinta = variabila
Odată declarată, cu ajutorul referinței vom accesa aceeași informație pe care o accesăm cu variabila cu care aceasta a fost legată (cele două făcând referire la aceeași zonă de memorie).

Exemplu:

#include <iostream>

int main() {
    int numar = 25;

    // rNumar va fi o referinta (alt nume) a variabilei numar
    int& rNumar = numar;

    // observam ca au aceeasi valoare
    std::cout << "Numar  initial: " << numar  << std::endl;
    std::cout << "rNumar initial: " << rNumar << std::endl << std::endl;

    rNumar++;
    numar++;

    std::cout << "Numar  final: " << numar  << std::endl;
    std::cout << "rNumar final: " << rNumar << std::endl;

    return 0;
}

Output:

Numar  initial: 25
rNumar initial: 25

Numar  final: 27
rNumar final: 27

În exemplul anterior se observă că deși folosim două variabile diferite, valorile celor doua sunt întotdeauna identice, iar o modificare asupra uneia se reflectă și asupra celeilalte.

Asocierea referințelor

Asocierea între o referință și o variabilă este permanentă și nu putem asocia referința cu altă variabilă! Putem, în schimb, să asociem mai multe referințe cu aceeași variabilă.
Observați datele de ieșire ale următorului exemplu:

#include <iostream>

int main() {
#include <iostream>

int main() {
    int numar_1 = 25;
    int numar_2 = 30;

    int& r1Numar = numar_1;
    int& r2Numar = numar_1;

    r1Numar = numar_2;
    r1Numar++;

    std::cout << "Valoarea lui r1Numar:  " << r1Numar  << std::endl;
    std::cout << "Valoarea lui r2Numar:  " << r2Numar  << std::endl;
    std::cout << "Valoarea lui numar_1:  " << numar_1  << std::endl;
    std::cout << "Valoarea lui numar_2:  " << numar_2  << std::endl;

    return 0;
}

Output:

Valoarea lui r1Numar:  31
Valoarea lui r2Numar:  31
Valoarea lui numar_1:  31
Valoarea lui numar_2:  30

Observăm că expresia r1Numar= numar_2; nu a asociat referința r1Numarcu variabila numar_2, ci a determinat copierea valorii numar_2 în variabila cu care rNumar este asociată.
De asemenea, observăm că variabila numar_1 este asociată cu două referințe.

O referință nu poate fi asociată cu o valoare. Următorul program va genera erori de compilare:

int main() {
    int& rNumar = 20; // aceasta expresie nu este permisa

    return 0;
}

Pasarea argumentelor prin referință

Utilitatea referințelor este evidențiată atunci când avem nevoie sa accesăm o variabilă din contexte diferite (precum două metode diferite). Folosind referințe putem economisi memorie, folosind același spațiu de memorie accesat cu două nume de variabile diferite.

Următorul exemplu nu folosește referințe pentru pasarea argumentelor (pass-by-value).
În acest caz, de fiecare dată când funcția sum este apelată, în memorie este alocat spațiu pentru două variabile noi ce iau valorile argumentelor. Acest lucru nu este eficient atunci când avem nevoie sa pasăm informații de dimensiuni mari.

#include <iostream>

/*
*
* Exemplu pass-by-value
*
**/

int sum(int a, int b){
    return a + b;
}

int main() {
    int a = 5;
    int b = 6;
    int suma = sum(a, b);

    std::cout << "Suma: " << suma << std::endl;

    return 0;
}

Următorul exemplu folosește referințe pentru pasarea argumentelor (pass-by-reference).
În acest caz, de fiecare dată când funcția sum este apelată, argumentele funcției nu sunt copii ale variabilelor, ci referințe către variabilele pasate funcției.

#include <iostream>

/*
*
* Exemplu pass-by-reference
*
**/

int sum(int& a, int& b){
    return a + b;
}

int main() {
    int a = 5;
    int b = 6;
    int suma = sum(a, b);

    std::cout << "Suma: " << suma << std::endl;

    return 0;
}

Atenție! Deși pasarea argumentelor prin referință eficientizează dimensiunea memoriei ocupate la run-time, argumentele funcțiilor vor trebui manipulate cu grijă; o modificare a acestora în corpul funcției se va reflecta și asupra variabilei pasate ca argument!


Supraîncărcarea operatorilor

Dacă dorim să tratăm instanțele claselor (i.e. obiectele) așa cum tratăm tipurile de date fundamentale, trebuie să putem folosi aceiași operatori pe care îi folosim si cu tipurile fundamentale.
Cunoaștem, de exemplu, faptul că putem aduna două valori de tip întreg folosind operatorul '+'. În mod similar, am dori să putem calcula și suma a două numere complexe definite de clasa Complex din următorul exemplu:

class Complex{
    double mReal;       // partea reala
    double mImaginar;   // partea imaginara

public:

    /*
     * Constructorul ce initializeaza campurile obiectului
     * 
     * */    
    Complex(double real, double imaginar){
        mReal     = real;
        mImaginar = imaginar;
    }


    /*
     * Constructorul fara argumente
     * 
     * */
    Complex() = default;


    /*
     * Metoda: get_suma
     *
     * Intoarce suma (sub forma de obiect de tip Complex) dintre obiectul curent si obiectul primit ca argument
     *
     * */
    Complex get_sum(Complex alt_numar){
        double real_rezultat     = mReal     + alt_numar.mReal;
        double imaginar_rezultat = mImaginar + alt_numar.mImaginar;

        Complex rezultat(real_rezultat, imaginar_rezultat);

        return  rezultat;
    }


    /*
     * Metoda: afisare
     *
     * Afiseaza numarul complex in formatul a+bi
     * 
     * */
    void afisare(){
        std::cout << mReal << '+' << mImaginar << 'i' << std::endl;
    }
};

Folosind cunoștințele acumulate până acum, putem realiza suma a doua obiecte de tip Complex implementând funcția get_suma din exemplul de mai sus.
Totuși, aceasta nu ne permite să adunăm două obiecte de tip complex folosind operatorul '+', așa cum dorim.

Fie clasa Complex definită anterior. Acesta este un exemplu de utilizare a funcției get_sum:

int main() {
    Complex complex_1 (5, 10);
    Complex complex_2 (-1, 3);

    Complex suma = complex_1.get_sum(complex_2);

    suma.afisare();

    return 0;
}

Output:

4+13i