Membri și specificatori de acces
Până acum, toate câmpurile și metodele erau publice (accesibile din orice cod). Dar una dintre ideile-cheie ale OOP e încapsularea: să ascundem detaliile interne și să expunem doar ce trebuie.
Pentru asta folosim specificatori de acces.
Cei 3 specificatori de acces
În C++ există trei specificatori:
public- accesibil de oriundeprivate- accesibil doar din interiorul claseiprotected- accesibil din clasă și din clasele derivate (discutăm la moștenire)
class Cont {
public:
string titular; // oricine poate citi/modifica
private:
double sold; // doar metodele clasei pot accesa
protected:
int id; // doar clasa și descendenții
};De ce private?
Exemplu concret: un cont bancar. Vrei ca soldul să fie modificabil doar prin operații controlate (depunere, retragere), nu să poată fi schimbat direct de oricine.
Varianta naivă (totul public)
class Cont {
public:
string titular;
double sold;
};
int main() {
Cont c;
c.titular = "Ana";
c.sold = 100;
c.sold = -1000000; // aici cineva poate "sparge" soldul!
c.sold += 50; // poate să adauge oricât, fără control
}Problema: oricine poate modifica direct soldul, chiar cu valori aberante.
Varianta corectă (sold privat)
class Cont {
public:
string titular;
void depune(double suma) {
if (suma > 0) sold += suma;
}
void retrage(double suma) {
if (suma > 0 && suma <= sold) sold -= suma;
}
double getSold() {
return sold;
}
private:
double sold = 0;
};
int main() {
Cont c;
c.titular = "Ana";
c.depune(100);
c.retrage(30);
// c.sold = -1000; // EROARE - sold e privat
cout << c.getSold(); // 70
}Acum soldul se modifică doar prin operații validate.
Încapsulare: de ce e importantă?
Încapsularea aduce trei beneficii majore:
1. Validare centralizată
Toate modificările trec prin metode. Poți valida într-un singur loc.
void depune(double suma) {
if (suma <= 0) return; // validare
if (suma > 1000000) return; // limită
sold += suma;
}2. Libertate să schimbi implementarea
Dacă schimbi cum stochezi soldul intern (de ex. din
double în cenți long long),
utilizatorii clasei nu simt. Doar metodele tale
se modifică.
3. Protecție împotriva greșelilor
Nu poți seta soldul aiurea pentru că nu poți accesa variabila direct.
Sintaxa specificatorilor
Specificatorii se aplică până la următorul specificator sau până la finalul clasei:
class Exemplu {
public:
int a, b; // public
void f1(); // public
private:
int c, d; // private
void f2(); // private
public:
int e; // public (din nou)
};Putem alterna, dar de obicei scriem:
public:- tot ce e expusprivate:- detaliile interne
Getters și Setters
Pentru câmpuri private pe care vrem să le expunem controlat, scriem metode speciale:
- Getter: metoda care returnează valoarea câmpului
- Setter: metoda care setează valoarea câmpului (cu validare)
class Persoana {
public:
string getNume() { return nume; }
void setNume(string numeNou) {
if (!numeNou.empty()) nume = numeNou;
}
int getVarsta() { return varsta; }
void setVarsta(int varstaNoua) {
if (varstaNoua >= 0 && varstaNoua <= 150) varsta = varstaNoua;
}
private:
string nume;
int varsta = 0;
};
int main() {
Persoana p;
p.setNume("Ana");
p.setVarsta(25);
cout << p.getNume() << " " << p.getVarsta();
}Când ai nevoie de getter/setter?
- Getter + Setter: acces citire + scriere controlat.
- Doar getter: valoare read-only din exterior.
- Doar setter: valoare write-only (rar, mai mult pentru logging).
- Niciunul: câmpul e complet ascuns (detaliu intern).
Exemplu complet: Cont bancar cu validări
#include <fstream>
#include <string>
using namespace std;
ifstream fin("date.in");
ofstream fout("date.out");
class Cont {
public:
// Constructor simplu (vom aprofunda în lecția 122)
void seteaza(string titularNou, double soldInitial) {
titular = titularNou;
if (soldInitial >= 0) sold = soldInitial;
else sold = 0;
}
bool depune(double suma) {
if (suma <= 0) return false;
sold += suma;
return true;
}
bool retrage(double suma) {
if (suma <= 0 || suma > sold) return false;
sold -= suma;
return true;
}
void afisare() {
fout << titular << ": " << sold << " lei\n";
}
double getSold() { return sold; }
private:
string titular;
double sold = 0;
};
int main() {
Cont c;
c.seteaza("Ana", 1000);
c.afisare();
c.depune(500);
c.afisare();
c.retrage(200);
c.afisare();
if (!c.retrage(5000))
fout << "Retragere esuata: fonduri insuficiente\n";
if (!c.depune(-100))
fout << "Depunere esuata: suma invalida\n";
return 0;
}date.out:
Ana: 1000 lei
Ana: 1500 lei
Ana: 1300 lei
Retragere esuata: fonduri insuficiente
Depunere esuata: suma invalidaCe am câștigat?
- Imposibil să faci sold negativ (retragerea verifică).
- Imposibil să depui sume negative.
- Toate modificările trec prin metode care validează.
Accesul la membrii privați din interiorul clasei
Fiecare metodă a clasei are acces complet la toți membrii, inclusiv privați. Restricția e doar din exterior.
class Test {
public:
void f() {
a = 5; // OK - suntem în clasă
g(); // OK - apel la metoda privată
}
private:
int a;
void g() { /* ... */ }
};
int main() {
Test t;
t.f(); // OK
// t.a = 5; // EROARE - a e privat
// t.g(); // EROARE - g e privat
}friend
- excepții la regula privată
Uneori vrei ca o funcție din afară să aibă
acces la membrii privați. Folosești friend:
class Cont {
public:
void afisare() { cout << sold; }
private:
double sold = 100;
friend void operatiuneSpeciala(Cont &c); // declarăm prieten
};
void operatiuneSpeciala(Cont &c) {
c.sold += 1000; // OK - suntem "prieteni"
}Atenție: friend sparge
încapsularea. Folosește cu parcimonie. Rar e necesar.
Exemplu clasic: clasa Fractie
#include <fstream>
using namespace std;
ifstream fin("date.in");
ofstream fout("date.out");
class Fractie {
public:
void set(int numarator, int numitor) {
if (numitor == 0) {
a = 0;
b = 1;
return;
}
a = numarator;
b = numitor;
simplifica();
}
double valoare() {
return (double)a / b;
}
void afisare() {
fout << a << "/" << b;
}
private:
int a, b; // a/b
int gcd(int x, int y) {
return y == 0 ? x : gcd(y, x % y);
}
void simplifica() {
int d = gcd(a < 0 ? -a : a, b < 0 ? -b : b);
if (d > 0) { a /= d; b /= d; }
if (b < 0) { a = -a; b = -b; } // păstrăm numitorul pozitiv
}
};
int main() {
Fractie f;
f.set(6, 8);
f.afisare();
fout << " = " << f.valoare() << "\n";
f.set(-9, 12);
f.afisare();
fout << "\n";
f.set(1, 0); // validare - numitor 0
f.afisare();
return 0;
}date.out:
3/4 = 0.75
-3/4
0/1Observă:
- Câmpurile
a, bsunt private - nu poți strica fracția din exterior. simplificașigcdsunt private - sunt detalii de implementare, utilizatorul nu le vede.- Fracția se auto-simplifică la orice setare. Utilizatorul nu se ocupă de asta.
Cum decizi ce e public și ce e privat?
Regula de aur
Public: CE poate face obiectul.
Private: CUM face obiectul acele lucruri.
Exemplu
Pentru o clasă Cerc:
- Public:
arie(),perimetru(),getRaza(),setRaza() - Private: câmpul
raza(doar prin getter/setter)
Pentru o clasă Lista:
- Public:
adauga(),scoate(),cauta(),dimensiune() - Private: vectorul intern, variabila de dimensiune, detalii de realocare
Greșeli frecvente
1. Totul public
class Elev {
public:
string nume;
int varsta;
double medie;
};Acum ai un struct. Dacă nu ai metode sau
validări, folosește struct. Dacă vrei încapsulare,
pune câmpurile private.
2. Setter fără validare
void setVarsta(int v) { varsta = v; } // acceptă orice, inclusiv -50Scopul setterului e să valideze. Dacă nu validezi, mai bine faci câmpul public.
3. Getter care returnează referință la membru privat
int& getX() { return x; } // RISC - altcineva poate modifica!Returnarea prin referință permite modificare din exterior, distrugând încapsularea. Returnează prin valoare dacă vrei read-only.
4. Confuzia class
cu struct
class Punct {
int x, y; // implicit PRIVATE
};
Punct p;
p.x = 5; // EROARE - x e privatLa class, implicit e private.
Adaugă explicit public: sau folosește
struct.
5. Date private inutile
Dacă toată logica e publică și toate câmpurile au getter/setter triviali, clasa devine zgomot. Private are sens când ascunzi sau validezi ceva.
Ce să reții
- Specificatori de acces:
public(oricine),private(doar clasa),protected(clasa + derivați). - Încapsulare = ascunderea detaliilor interne + controlarea accesului.
- Private pentru date și pentru metode de implementare.
- Public pentru interfața clasei (ce poate face obiectul).
- Getter / Setter = metode publice care accesează/modifică un câmp privat, adesea cu validare.
- În
classimplicit e private, înstructimplicit e public. friende o excepție - o funcție externă cu acces privat. Folosește rar.- Întreabă-te mereu: “am nevoie ca cineva din afară să modifice asta direct?” - dacă nu, private.