Skip to content

Latest commit

 

History

History
183 lines (166 loc) · 9.08 KB

POO_22_C_2022-04-14.md

File metadata and controls

183 lines (166 loc) · 9.08 KB
//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.

Supraincarcarea operatorilor pentru clase

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: ==, !=, <, >, <=, >=, /.

Pointerul this

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).

Membri statici

Campuri cat si metode ale unei clase pot fi declarate ca fiind statice.

Camp static

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 fiind private 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 tip Test. Poate exista situatia in care vrem sa afisam valoarea lui s_inst, insa nu avem la dispozitie niciun obiect de tip Test. Aceasta situatie poate fi elegant rezolvata daca functia membra ce returneaza campul s_inst este declarata public si static. De oriunde din program unde avem acces la fisierul antet Test.h putem afla valoarea campului s_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.