Saptamana 04 / 14 Programare Orientata Obiect
Constructori, destructor si metode de acces
Cum primeste un obiect o stare valida din primul moment, cum face curat in urma lui, ce face constructorul de copiere cand clasa are memorie alocata dinamic si cum pastreaza get si set datele private.
- Sa scrii constructori fara parametri, cu toti parametrii si cu mai putini parametri
- Sa initializezi membrii in lista de initializatori
- Sa inlocuiesti mai multi constructori cu unul care are valori implicite
- Sa scrii constructorul de copiere pentru o clasa cu un membru alocat dinamic
- Sa enumeri situatiile in care se apeleaza constructorul de copiere
- Sa explici cand se executa destructorul si ce trebuie sa elibereze
- Sa scrii metode get si set care pastreaza incapsularea
De ce avem nevoie de constructori
Saptamana trecuta obiectul era creat intai si completat abia dupa aceea, cu o functie de initializare sau membru cu membru. Intre cele doua momente obiectul exista intr-o stare invalida. Si nimic nu te obliga sa apelezi functia de initializare.
Constructorul este o metoda cu acelasi nume ca si clasa, fara tip returnat. Se executa automat cand obiectul este creat. Rolul lui este sa faca obiectul valid din primul moment.
Daca nu scrii niciun constructor, compilatorul genereaza unul implicit, care nu face nimic pentru membrii int. Daca scrii macar un constructor, compilatorul nu il mai genereaza pe cel implicit.
Tipuri de constructori
O clasa poate avea mai multi constructori, daca listele lor de parametri difera:
- fara parametri (constructorul implicit):
Punct(); - cu toti parametrii:
Punct(int x, int y); - cu mai putini parametri:
Punct(int v); - de copiere:
Punct(const Punct& p), despre care vorbim mai jos.
Ce constructor se apeleaza pentru fiecare obiect?
#include <iostream>
using namespace std;
class Punct {
int x, y;
public:
Punct() : x(0), y(0) { cout << "A "; }
Punct(int v) : x(v), y(v) { cout << "B "; }
Punct(int x, int y) : x(x), y(y) { cout << "C "; }
};
int main() {
Punct p1;
Punct p2(5);
Punct p3(1, 2);
Punct p4 = 7;
} Punct p4 = 7; apeleaza constructorul cu un parametru int. Nu este o atribuire.
Lista de initializatori
Lista de initializatori vine dupa : si inaintea corpului. Acolo membrii sunt initializati, inainte sa se execute corpul. In corp poti doar sa atribui valori unor membri care exista deja.
class Produs {
const int id; // const: poate fi doar initializat
string nume;
double pret;
public:
Produs(int id, string nume, double pret)
: id(id), nume(nume), pret(pret) {}
};
id(id) nu e ambiguu: in afara parantezelor id este membrul, in interior este parametrul.
Lista de initializatori este obligatorie pentru membrii const, pentru membrii referinta si pentru membrii de tip clasa care nu au constructor implicit.
Valori implicite in lista de parametri
In loc de trei constructori poti scrie unul singur, cu valori implicite:
class Punct {
int x, y;
public:
Punct(int x = 0, int y = 0) : x(x), y(y) {}
};
Punct a; // (0,0)
Punct b(3); // (3,0)
Punct c(3, 4); // (3,4)
Valorile implicite se dau de la dreapta la stanga: daca un parametru are una, toti cei de dupa el trebuie sa aiba. Le scrii doar in declaratie, nu si in definitia din afara clasei.
Destructorul
Destructorul se numeste ~NumeClasa(), nu are parametri si nici tip returnat. Se executa automat cand se termina durata de viata a obiectului:
- obiect local: la acolada
}care inchide blocul lui; - obiect creat cu
new: ladelete; - obiect global sau
static: la sfarsitul programului.
Obiectele locale sunt distruse in ordinea inversa crearii.
#include <iostream>
#include <string>
using namespace std;
class Martor {
string nume;
public:
Martor(string n) : nume(n) { cout << "+" << nume << " "; }
~Martor() { cout << "-" << nume << " "; }
};
int main() {
Martor a("a");
{
Martor b("b");
Martor c("c");
}
Martor d("d");
} Scrii un destructor cand obiectul detine o resursa, de exemplu memorie luata cu new. Destructorul o elibereaza.
Constructorul de copiere si membrii dinamici
Constructorul de copiere construieste un obiect nou dintr-unul existent. Daca nu il scrii, compilatorul genereaza unul care copiaza fiecare membru asa cum este. Pentru un pointer, asta inseamna ca se copiaza adresa, nu datele de la ea.
class Student {
char* nume;
public:
Student(const char* n) {
nume = new char[strlen(n) + 1];
strcpy(nume, n);
}
~Student() { delete[] nume; }
};
Clasa aceasta este gresita din clipa in care copiezi un obiect: dupa Student b = a; ambii pointeri nume arata spre acelasi vector. Primul destructor il elibereaza, al doilea il elibereaza inca o data. Este un double delete, adica comportament nedefinit.
Student a("Ana"); - constructorul aloca textul pe heap.
Pasul 1/4
Solutia este un constructor de copiere care aloca memorie noua si copiaza continutul:
Student(const Student& s) {
nume = new char[strlen(s.nume) + 1];
strcpy(nume, s.nume);
}
Parametrul trebuie sa fie referinta. La Student(Student s), transmiterea lui s prin valoare ar cere ea insasi o copie, asa ca acel cod nu compileaza.
Cand se apeleaza constructorul de copiere
- Cand initializezi un obiect din altul:
Cutie b = a;sauCutie c(a);. - Cand transmiti un obiect prin valoare unei functii.
- Cand o functie intoarce un obiect prin valoare. Aici compilatorul are voie, iar in unele cazuri este chiar obligat, sa sara peste copiere, asa ca nu te baza ca o vezi.
#include <iostream>
using namespace std;
class Cutie {
public:
Cutie() { cout << "implicit" << endl; }
Cutie(const Cutie&) { cout << "copiere" << endl; }
};
void prinValoare(Cutie) {}
void prinReferinta(const Cutie&) {}
int main() {
Cutie a;
Cutie b = a;
Cutie c(a);
prinValoare(a);
prinReferinta(a);
} Metode de acces: get si set
Datele raman private. Clasa decide ce se poate citi si cum se poate modifica.
- Un getter intoarce o valoare si nu modifica obiectul, deci este
const. - Un setter valideaza inainte sa schimbe ceva. Valorile invalide sunt refuzate.
- Nu orice membru are nevoie de setter. Un id care nu se schimba niciodata are doar getter.
const char* getNume() const { return nume; } // doar citire
void setNota(int nt) {
if (nt >= 1 && nt <= 10) nota = nt; // invariantul ramane adevarat
}
Intoarce const char*, nu char*. Altfel apelantul poate scrie prin pointer direct in vectorul tau privat.
Ruleaza programul de mai jos. Apoi adauga setNume(const char*): ce trebuie sa faca cu vectorul vechi inainte sa retina numele nou?
#include <iostream>
#include <cstring>
using namespace std;
class Student {
char* nume;
int nota;
public:
Student(const char* n = "anonim", int nt = 0) : nota(nt) {
nume = new char[strlen(n) + 1];
strcpy(nume, n);
cout << "constructor " << nume << endl;
}
Student(const Student& s) : nota(s.nota) {
nume = new char[strlen(s.nume) + 1]; // copia primeste memoria ei
strcpy(nume, s.nume);
cout << "constructor de copiere " << nume << endl;
}
~Student() {
cout << "destructor " << nume << endl;
delete[] nume;
}
const char* getNume() const { return nume; }
int getNota() const { return nota; }
void setNota(int nt) {
if (nt >= 1 && nt <= 10) nota = nt; // refuzam notele invalide
}
};
void afiseaza(Student s) {
cout << s.getNume() << ": " << s.getNota() << endl;
}
int main() {
Student a("Ana", 9);
Student b = a;
b.setNota(15); // ignorata
b.setNota(7);
afiseaza(b);
cout << a.getNota() << " " << b.getNota() << endl;
}Intrare (stdin)
Verifica-te
Scor: 0/8