POO - Seminar 4
Clase abstracte

Clasele abstracte sunt definite doar pentru a furniza o interfata cu utilizatorul. 
O clasa abstracta contine cel putin o metoda virtuala pura, adica o metoda de forma:

	virtual TIP NUME (LISTA DE PARAMETRI) = 0;

O clasa virtuala nu poate fi instantiata, adica nu putem declara obiecte de tipul respectiv. Ea poate fi doar baza
pentru o clasa derivata.

Sa consideram urmatorul exemplu. Dorim o clasa de baza abstracta pentru obiecte grafice "simetrice" (elipse, cercuri etc.).
Clasa abstracta defineste o metoda virtuala pura Arie, care va fi definita in fiecare implementare (deci in fiecare clasa 
derivata din clasa abstracta).

class Simetric {
public:
     virtual double Arie() = 0;
};

Vom mai folosi o clasa Point, care sa specifice centrul obiectului grafic:

class Point {
	double x, y;
public:
	Point(double _x, double _y) : x(_x), y(_y) { }
};

De observat initializarea datelor x si y in constructorul Point, ca si cum acestea ar fi obiecte de baza pentru clasa Point.
Forma clasica ar fi fost:

	Point(double _x, double _y) { x = _x; y = _y; }

Clasele derivate Elipsa si Cerc contin un Point (centrul de simetrie), alte date necesare (semiaxe, raza) si implementeaza  metoda Arie:

class Elipsa : public Simetric {
	Point centru;
	double a, b;
public:
	Elipsa(double x, double y, double _a, double _b) : centru(x, y), a(_a), b(_b) { }
	double Arie() { cout << "Cerc:Arie\n"; return M_PI * a * b; }
};

De observat initializarea obiectului centru. Constanta standard M_PI (definita in math.h) este numarul pi.
S-a inclus si un mesaj la consola pentru identificarea metodei.
Similar se declara si clasa Cerc

class Cerc : public Simetric {
	Point centru;
	double r;
public:
	Cerc(double x, double y, double _r) : centru(x, y), r(_r) { }
	double Arie() { cout << "Elipsa:Arie\n"; return M_PI * r * r; }
};

In programul principal declaram citeva obiecte grafice:

	Cerc c1(1.0, 2.0, 3.0);
	Elipsa e1(1.0, 2.0, 3.0, 4.0);
	Cerc c2(1.5, 2.5, 4.0);
	Elipsa e2(2.0, 3.0, 6.0, 7.0);

Faptul ca Arie este o metoda virtuala permite apeluri ca cele de mai jos:

	Simetric *p = &c1;
	cout << p->Arie() << '\n';
	p = &e1;
	cout << p->Arie() << '\n';

Avem un pointer p la clasa de baza Simetric, care pote fi intializat cu adresa unui obiect derivat (un obiect derivat este 
totdeauna si un obiect al clasei de baza - o Elipsa este un obiect Simetric). La apel se vede ca se apeleaza metoda obiectului
concret, desi p este pointer la clasa de baza. Aceasta ilustreaza faptul ca "legarea" metodelor (care Arie() sa se apeleze cind se scrie
p->Arie() se face la momentul executiei si nu la compilare).

Ne propunem sa calculam aria totala a obiectelor. Sigur ca o prima varianta ar fi:

	double arie = 0.0;
	arie = c1.Arie() + e1.Arie() + c2.Arie() + e2.Arie();
	cout << "Arie totala: " << arie << '\n';

Daca avem insa un numar mare de obiecte grafice (de exemplu citeva zeci), suma de mai sus devine incomoda.
Mult mai bine ar fi sa putem automatiza (indexa) acest proces, printr-un tablou de obiecte grafice.
Trebuie deci sa "inglobam" toate tipurile de obiecte grafice intr-unul singur (tablourile sunt omogene, deci toate elementele
trebuie sa aiba acelasi tip). Tipul comun este evident tipul Simetric, din care deriva Cerc si Elipsa. Cum clasa Simetric este virtuala,
nu putem instantia obiecte Simetric, deci nu putem scrie ceva de genul:

	Simetric tabel [] = { c1, e1, c2, e2 };	// Gresit

dar putem defini pointeri la Simetric, la fel ca mai sus. Solutia este deci de a defini un tabel de pointeri de forma:

	Simetric *tabel [] = { &c1, &e1, &c2, &e2 };

Calculul se poate acum indexa printr-o bucla for:

	arie = 0.0;
	for (int i = 0; i < SIZE(tabel); i++)
		arie += tabel[i]->Arie();
	cout << "Arie totala: " << arie << '\n';

In expresia tabel[i]->Arie(), tabel[i] este un pointer la clasa Simetric, iar apelul metodei Arie() va intoarce aria
obiectului grafic concret.
SIZE este o macroinstructiune care intoarce numarul de elemente dintr-un tabel:

	#define SIZE(x) (sizeof(x)/sizeof(x[0]))

Tema
1) Sa se extinda exemplul cu clasele Dreptunghi si Patrat, derivate similar din Simetric.
2) Sa se modifice ierarhia claselor astfel:

	Simetric <--- Elipsa <--- Cerc (un cerc este un caz particular de elipsa)
	Simetric <--- Dreptunghi <--- Patrat (un patrat este un caz particular de dreptunghi)

Solutie 2)

Se modifica declaratia clasei Cerc:

class Cerc : public Elipsa {
public:
	Cerc(double x, double y, double _r) : Elipsa(x, y, _r, _r) { }
	double Arie() { cout << "Elipsa:Arie\n"; return Elipsa::Arie(); }
};

Constructorul Cerc apeleaza constructorul Elipsa cu parametri adecvati. Metoda Arie este prezenta doar pentru
a scrie mesajul de identificare la consola. Ea apeleaza de fapt metoda Arie a clasei Elipsa.
S-ar fi putut defini foarte bine:

class Cerc : public Elipsa {
public:
	Cerc(double x, double y, double _r) : Elipsa(x, y, _r, _r) { }
};

ceea ce inseamna ca un apel:

	Cerc c1;
	c1.Arie()

va apela de fapt metoda clasei de baza Elipsa.