//Punct2D.h
class Punct2D
{
double m_x, m_y;
public:
Punct2D() {};
Punct2D (double, double);
double dist (Punct2D p2);
double dist (Segment s); //nu am acces la membrii privati din Segment
friend double dist (Punct2D p, Segment s);
friend double dist (Punct2D, Punct2D);
};
//segment.h
class Segment
{
Punct2D m_p1, m_p2;
public:
double dist(Punct2D p); //nu am acces la membrii privati din Punct2D
friend double dist (Punct2D p, Segment s);
};
Consideram doua clase Punct2D
si o clasa segment
pentru care nu avem metode get (getter-i) implentate. Dorim sa scriem o functie care calculeaza distanta de la un punct la un segment. Aceasta functie avem posibilitatea de a o scrie fie membra clasei Punct2D
, fie membra clasei segment
, fie externa ambelor clase. Niciuna dintre solutii nu pare sa fie viabila deoarece in oricare dintre cele trei functii nu avem acces la toti membrii privati din Punct2D
cat si din segment
.
double dist (Punct2D p, Segment s); //nu am acces la membrii privati ai celor doua clase
Ultima solutie este cea fiabila datorita unui mecanism acceptat in C++.
In C++ se accepta scrierea unei functii externe, nemembra niciunei clase, dar care sa fie prietena cu una sau mai multe clase. Functia respectiva va avea acces la toti membrii claselor cu care este prietena. Fiind o functie prietena uneia sau mai multor clase se implementeaza in exteriorul claselor, dar antetul functiei va fi introdus in fiecare dintre clasele care va fi prietena precedat de cuvantul rezervat friend
.
O functie care prelucreaza datele unei clase avem posibilitatea sa o scriem membra acelei clase sau prietena clasei. Deosebirea esentiala dintre cele doua implementari este ca in cazul functiei prietene va aparea un parametru suplimentar (obiect instanta clasei).
//implementare.cpp
double dist(Punct2D p1, Punct2D p2)
{
return sqrt((p1.m_x - p2.m_x)*(p1.m_x - p2.m_x) + ...);
}
Se observa in cazul functiei prietene ca putem accesa membrii privati functiei prietene dist
ai celor doua puncte p1
si p2
, chiar daca dist
este functie externa.
Pentru o clasa putem supraincarca un operator in general sub doua forme (cu mici exceptii):
- membru clasei respective
- prietene clasei
//Punct2D.h
class Punct2D
{
double m_x, m_y;
public:
Punct2D() {};
Punct2D (double, double);
double dist (Punct2D p2);
double dist (Segment s); //nu am acces la membrii privati din Segment
friend double dist (Punct2D p, Segment s);
friend double dist (Punct2D, Punct2D);
friend double operator!(Punct2D p)
{
return sqrt(p.m_x * p.m_x + p.m_y * p.m_y)
}
double operator-();
Punct2D operator*(double s);
friend Punct2D operator*(double, Punct2D);
friend ostream& operator<<(ostream&, Punct2D);
friend istream& operator>>(istream&, Punct2D);
Punct2D operator=(Punct2D);
};
Supraincarcarea operatorului !
pentru clasa Punct2D
este absolut similiara cu supraincarcarea aceluiasi operator pentru structura complex
. In cazul clasei Punct2D
am fost obligati ca aceasta supraincarcare sa o facem prietena clasei Punct2D
pentru ca altfel nu am fi avut acces la membrii privati ai punctului.
Desi supraincarcarea lui !
pentru Punct2D
este externa clasei, compilatorul C++ accepta implementarea lui in momentul descrierii clasei Punct2D
(in fisierul .h
).
//implementare.cpp
double Punct2D::operator-()
{
return Punct2D(-m_x, -m_y);
}
Punct2D Punct2D::operator*(double s)
{
return Punct2D(s * m_x, s * m_y);
}
Punct2D Punct2D::operator*(double s, Punct2D p)
{
return p*s;
}
ostream& operator<<(ostream& fl, Punct2D p)
{
return fl << p.m_x << " " << p.m_y;
}
istream& operator>>(istream& fl, Punct2D p)
{
return fl >> p.m_x >> p.m_y;
}
In clasa Punct2D
am supraincarcat operatorul unar -
ca fiind membru clasei. In consecinta, aceasta supraincarcare nu are parametri, operatorul -
aplicandu-se obiectului curent.
In general, supraincarcarea unui operator prieten unei clase se face cu un numar de parametri egal cu aritatea operatorului, iar cand supraincarcam un operator membru clasei, numarul de parametri este egal cu aritatea-1, iar primul operand este obligatoriu obiectul curent.
Amplificarea cu scalar la dreapta unui Punct2D
folind operatorul *
poate fi implementata atat membra clasei cat si prietena clasei. Amplificarea la stanga a unui Punct2D
cu un numar real nu poate fi implementata decat prietena clasei deoarece primul operand nu este Punct2D
, ci este un double
.
//source.cpp
void main()
{
double x, y;
cin >> x >> y;
Punct2D p1 (x,y);
cout << !p1 << endl; //operator!(p1);
cout << -p1 << endl; //operator<<(cout, p1.operator-());
double s;
cin >> s;
cout << p1*s << endl; //p1.operator*(s);
cout << s*p1 << endl; //operator*(s,p1);
}
Operatorii <<
si >>
pentru introducerea unui obiect intr-un flux de iesire si respectiv preluarea unui obiect dintr-un fluc de intrare se supraincarca obligatoriu ca fiind prieteni clasei.
TPA: ==
, !=
, <
, >
, <=
, >=
, /
.
In C++, this
este cuvant rezervat si este folosit la descrierea unei clase. El retine atunci cand implementam clasa, pointerul catre obiectul curent.
Ne referim la un membru al clasei sub forma:
this->membru;
Acolo unde nu apare confuzie putem sa ne referim direct la membru fara prefixul this->
, insa in toate aceste situatii la compilare se va adauga prefixul this->
.
Exista situatii in care este neaparat nevoie sa ne referim la pointerul this
; mai exact exista situatii in care avem nevoie de obiectul curent (o referinta la obiectul curent) pe care o obtinem sub forma *this
. Una dintre aceste situatii clasice este atunci cand supraincarcam operatorul =
. Operatorul =
este unul dintre putinii operatori care nu pot fi supraincarcati, decat membrii unei clase.
p1=p2;
```cpp
//implementare.cpp
Punct2D Punct2D::operator=(Punct2D p2)
{
m_x = p2.m_x;
m_y = p2.m_y;
return *this;
}
Campurile m_x
si m_y
ale obiectului curent le-am initializat cu campurile corespunzatoare din p2
, operandul stang fiind obiectul curent, iar operandul drept fiind p2
primit ca parametru. In final, filosofia implementarii lui =
spune ca trebuie sa returnam ceea ce s-a atribuit operandului stang, adica returnam o copie a obiectului curent (o copie a lui *this
).
Campuri cat si metode ale unei clase pot fi declarate ca fiind statice.
Un camp declarat static, folosind cuvantul rezervat static
, este un camp utilizat in comun de catre toate obiectele instanta clasei respective.
class Test
{
static int s_inst;
public:
Test(){s_inst++;}
Test(int x)
{
cout << x << endl;
s_inst++;
}
~Test(){s_inst--;}
static int getinst(){return s_inst;}
};
int Test::s_inst=0;
In clasa Test
avem un camp static s_inst
care se doreste sa retina in permanenta numarul de obiecte aflate in memorie, obiecte instanta clasei Test
. Astfel, de fiecare data cand un obiect nou este creat se apeleaza un constructor care incrementeaza valoarea lui s_inst
. Cand un obiect este distrus s_inst
este decrementat cu valoarea 1
.
In C++, orice camp static (care nu este const
) trebuie declarat de doua ori:
- atunci cand descriem clasa
- in exteriorul descrierii clasei
Campul
s_inst
fiindprivate
nu putem sa il accesam decat in interiorul clasei. Am putea scrie o functie membra care sa returneze valoarea campului. Daca este o functie membru obisnuita ea trebuie apelata obligatoriu dintr-un obiect de tipTest
. Poate exista situatia in care vrem sa afisam valoarea luis_inst
, insa nu avem la dispozitie niciun obiect de tipTest
. Aceasta situatie poate fi elegant rezolvata daca functia membra ce returneaza campuls_inst
este declarata public si static. De oriunde din program unde avem acces la fisierul antetTest.h
putem afla valoarea campuluis_inst
sub forma:
Test::getinst()
Exista situatii in care anumite metode le scriem statice. Acele metode au legatura cu contextul clasei, dar pot fi folosite si ca unelte separate fara a fi nevoie sa apelam la un obiect.
Exemplu: Presupunem ca scriem o clasa Rational
pentru a retine un numar rational sub forma de fractie ireductibila numataror
/
numitor
. Fiind fractie ireductibila avem nevoie de mai multe ori la implementare sa simplificam fractia, de exemplu cand adunam doua numere (sub forma de fractii). La simplificare avem nevoie sa calculam cel mai mare divizor comun dintre numarator si numitor. Pentru aceasta operatie (cmmdc) putem scrie o metoda statica publica pe care o folosim la simplificare atunci cand implementam clasa Rational
, dar lasam posibilitatea sa fie folosita si in alte scopuri in afara clasei Rational
.
Orice membru declarat static este un pas in plus in a strica orientarea pe obiecte, deci trebuie sa limitam la strictul necesar declararea acestor membri ca fiind statici.