You are on page 1of 10

Mihai TOGAN

Cap. 5 – Moştenirea claselor (1)

Obiective:
 Prezentarea conceptului OOP de mostenire
 Derivarea claselor ȋn C++
 Vizibilitatea membrilor ȋn clasele derivate.
 Drepturi de acces

1
Mihai TOGAN
Ne propunem un exemplu de aplicaţie pentru gestionarea unor studenţi. Fiecare student are
un nume, o colecţie de note, medii, etc.

Într-o primă formă, clasa următoare ar putea gestiona entitatea student:

/*****************************************************************************/
class Student
{
int m_Id;
char *m_Nume;
int m_Note[100]; /* lista de note (prima valoare 0 => finalul listei) */
public:
Student (int id, char *nume, int note[]);
Student (const char *stdinfo); /* init std pe baza unui sir formatat */

void setNume (const char *nume);


float getMedie ();
void print ();
void addNota (int nota);
void modifyNota (int index, int nota);
//...
};

/*****************************************************************************/
Student::Student (int id, char *nume, int note[]) : m_Id(id)
{
this->setNume (nume);

memset (m_Note, 0, sizeof (m_Note));


for (int i = 0; note[i] != 0; i++)
this->addNota (note[i]);
}

Student::Student (const char *stdinfo)


: m_Id (0), m_Nume (NULL)
{
memset (m_Note, 0, sizeof (m_Note));

char *linfo = new char [strlen (stdinfo) + 1];


strcpy (linfo, stdinfo);

int step = 0;
char *p = strtok (linfo, ",");
while (p != NULL)
{
switch (step) {
case 0:
m_Id = atoi (p); break;
case 1:
this->setNume (p); break;
default:
this->addNota (atoi(p));
};

p = strtok (NULL, ",");


step++;
}
}
2
Mihai TOGAN
void Student::setNume (const char *nume) {

if (nume == NULL)
return;

if (m_Nume != NULL)
delete[] m_Nume;

m_Nume = new char [strlen (nume) + 1];


memcpy (m_Nume, nume, strlen (nume) + 1);
}

float Student::getMedie () {

int i;
float s = 0;
for (i = 0; i < sizeof(m_Note) / sizeof(int) && m_Note[i] != 0; i++)
s = s + m_Note[i];

return (i > 0) ? (s / i): 0;


}

void Student::print () {
std::cout << "id: " << m_Id;
std::cout << "\nnume: " << m_Nume;
std::cout.precision (2);
std::cout << "\nmedie: " << std::fixed << this->getMedie() << std::endl;
}

void Student::addNota (int nota) {

int i = 0;
while (i < sizeof(m_Note)/sizeof(int) && m_Note[i] != 0) i++;

if (i < sizeof(m_Note) / sizeof(int))


m_Note[i] = nota;
}

void Student::modifyNota (int index, int nota) {


m_Note[index] = nota; /* @todo: aici, ar trebui sa verificam indexul */
}

/*****************************************************************************/
void main ()
{
/* Instantiem un std cu id=101, nume=Ionescu Vasile, notele 8,9,5,... */
Student S1 ("101,Ionescu Vasile,8,9,5,10,8,9");
S1.print();

std::cout << std::endl;


}
/*****************************************************************************/

3
Mihai TOGAN
Putem rafina puţin problema anterioară. De exemplu, ȋn universitatea ATM există studenţi la
buget (studenţi militari) și studenţi cu taxa (studenţi civili). Ei au anumite caracteristici
comune, cum ar fi informaţiile gestionate de clasa Student, dar și caracteristi diferite, astfel:
 studenţii de la buget pot avea un grad militar, şi/sau alte informatii specifice.
 studenţii de la taxă pot avea o taxă platită până acum, şi/sau alte informatii specifice.

Dacă ne propunem să gestionăm ambele categorii de studenţi (buget şi taxă), putem aborda
următoarele variante de implementare:

V1. Crearea a două clase total diferite (StudentBuget și StudentTaxa) care să gestioneze
cele două categorii diferite de studenţi.

class StudentBuget class StudentTaxa


{ {
typedef enum { int m_Id;
STD_FR,STD_CAP,STD_SG,STD_SGMAJ char *m_Nume;
} t_GRAD; int m_Note[100];
int m_TaxeAchitate[20];
int m_Id;
char *m_Nume; public:
int m_Note[100]; StudentTaxa(int id, char *nume,
t_GRAD m_Grad; int note[], int
taxe[]);
public: StudentTaxa(const char *stdinfo);
StudentBuget(int id, char *nume,
int note[], t_GRAD void setNume (const char *nume);
grad); float getMedie ();
StudentBuget(const char *stdinfo); void print ();
void addNota (int nota);
void setNume (const char *nume); void modifyNota(int index, int
float getMedie (); nota);
void print ();
void addNota (int nota); void payTaxa (int val) {/*...*/}
void modifyNota (int index, int int getSumaAchitata() {/*...*/}
nota); //...
};
void setGrad (t_GRAD grad)
{ m_Grad = grad; }
t_GRAD getGrad (){return m_Grad;}
//...
};

Avantajul: există două clase diferite care gestionează cele două entitați diferite (studentul
aflat la taxă, respectiv studentul la buget).

Dezavantajul: duplicarea codului. Aproximativ 80-90 % din codul sursă specific celor două
clase este identic.

4
Mihai TOGAN
V2. Adăugarea la nivelul claselor Student propuse mai sus a tuturor informaţiilor specifice
(extra Student), cumulate pentru ambele categorii de studenţi, precum și un tip (membru
ȋn clasa Student) care sa menţioneze tipul studentului: BUGET sau TAXĂ.

class StudentAny
{
typedef enum {
STD_BUGET, STD_TAXA } t_STDTYPE;

typedef enum {
STD_FR, STD_CAP, STD_SG, STD_SGMAJ } t_GRAD;

int m_Id;
char* m_Nume;
int m_Note[100];

t_STDTYPE m_Tip;
t_GRAD m_Grad;
int m_TaxeAchitate[20];

public:
StudentAny (int id, char *nume, int note[],
t_STDTYPE tip, t_GRAD grad, int taxe[]);

StudentAny (const char *stdinfo);

void setNume (const char *nume);


float getMedie ();
void print ();
void addNota (int nota);
void modifyNota (int index, int nota);

void setGrad (t_GRAD grad) {/*...*/}


t_GRAD getGrad () {/*...*/}
//...
void payTaxa (int val) {/*...*/}
int getSumaAchitata() {/*...*/}
//...
};

Avantaj: codul comun nu este duplicat.

Dezavantaj: facem un compromis şi amestecăm entităţile. Folosind această abordare, la un


moment dat devine complicat de lucrat mai departe (adăugare de funcţionalităţi noi, reparare
de bug-uri, etc.).

5
Mihai TOGAN
V3. Utilizarea mecanismului OOP de moştenire (derivarea claselor). Din clasa Student se
vor moşteni (deriva) două clase specializate: una specializată pentru studentul de la buget,
şi cealaltă specializată pe studentul la taxă.

class Student
{
int m_Id;
char* m_Nume;
int m_Note[100];

public:
Student (int id, char *nume, int note[]);
Student (const char *stdinfo);

void setNume (const char *nume);


float getMedie ();
void print ();
void addNota (int nota);
void modifyNota (int index, int nota);
//...
};

typedef enum {
STD_FR,STD_CAP,STD_SG,STD_SGMAJ}
t_GRAD;
class StudentTaxa: public Student
class StudentBuget : public Student {
{ int m_TaxeAchitate[20];
t_GRAD m_Grad;
public:
public: StudentTaxa(int id, char *nume,
StudentBuget(int id, char *nume, int note[], int
int note[], t_GRAD grad); taxe[]);
StudentBuget(const char *stdinfo); StudentTaxa(const char *stdinfo);

void setGrad (t_GRAD grad) {/*...*/} void payTaxa (int val)


t_GRAD getGrad () {/*...*/} {/*...*/}
//... int getSumaAchitata()
}; {/*...*/}
//...
};

Observaţii:
 Această soluţie este de fapt o combinaţie între cele două versiuni de mai sus. Codul
comun necesar există o singură dată în clasa Student, iar mai departe avem două clase
diferite StudentBuget şi respectiv StudentTaxa, specifice pentru cele două entităţi diferite.
 Deşi la prima vedere, mărim numărul de clase ȋn cadul programului, rezultatul obţinut
este însă mult mai eficient:
o distingem clar entităţile (fiecare clasă specializată se ocupă de entitatea ei) –
avantajul variantei V1 prezentată anterior.
o nu duplicăm codul – avantajul variantei V2 prezentată anterior.

6
Mihai TOGAN
Câteva elemente privind moştenirea claselor:

 Moştenirea claselor: derivarea claselor.


 Clasa din care se face moştenirea (derivarea): se numeşte clasă de bază, sau clasă părinte,
sau super-clasă, etc.
 Clasa moştenită (clasa derivată): se numeşte clasă copil, sau clasă derivată, sau sub-clasă,
sau clasă specializată.
 Dintr-o clasă de bază se pot realiza una sau mai multe derivări.
 Dintr-o clasă derivată (clasă copil) se poate deriva mai departe un (sub)copil (este clasă
nepot pentru clasa de bază).

 Proprietăţi ale claselor derivate:

 Clasele derivate moştenesc toţi membrii clasei de bază (structura clasei +


comportament), mai puţin metodele de tip constructori.
 Constructorii clasei de bază vor participa (unul din ei) la iniţializarea obiectelor de tip
derivat.
 Metodele se moştenesc întocmai şi ȋn mod implicit, însă se pot suprascrie (folosind
mecanismul de method overriding)
 Metodele moştenite se redeclară ȋn clasa derivată și apoi se reimplementează la nivelul
clasei derivate.

7
Mihai TOGAN

Completăm exemplul anterior:


 Implementăm constructorii specifici claselor noi (StudentBuget și StudentTaxa).
 Restul metodelor print(), setNume(), getMedie(), etc., sunt moştenite direct din clasa
părinte Student.

StudentBuget::StudentBuget (const char *stdinfo)


: Student(stdinfo)
{
}

StudentTaxa::StudentTaxa (const char *stdinfo)


: Student(stdinfo)
{
}

void main ()
{
Student S ("101,Ionescu Vasile,8,9,5,10,8,9");
StudentBuget SB ("102,Popescu Ion,7,9,5,10,8,9,10,4");
StudentTaxa ST ("103,Vasilescu Bogdan,8,9,10,4");

S.print();
SB.print();
ST.print();

std::cout << std::endl;


}

Ȋn constructorii claselor specifice vom adăuga doar iniţializările pentru câmpurile


suplimentare:

StudentBuget::StudentBuget (const char *stdinfo)


: Student(stdinfo)
{
m_Grad = STD_FR;
}

StudentTaxa::StudentTaxa (const char *stdinfo)


: Student(stdinfo)
{
m_TaxeAchitate[0] = 0;
}

8
Mihai TOGAN

Observaţie: putem modifica constructorii de mai sus, astfel încât informaţiile specifice să fie
„preluate” tot din stringul stdinfo.
Pentru a fi corecţi, la nivelul claselor derivate suprascriem metoda print ( ):
class StudentBuget : public Student
{
public:
typedef enum {
STD_FR,STD_CAP,STD_SG,STD_SGMAJ } t_GRAD;
private:
t_GRAD m_Grad;

public:
StudentBuget (int id, char *nume, int note[], t_GRAD grad);
StudentBuget (const char *stdinfo);

void setGrad (t_GRAD grad) {/*...*/}


t_GRAD getGrad () {/*...*/}

const char* getGradStr () const;


void print ();
//...
};

void StudentBuget::print()
{
std::cout << "id: " << m_Id;
std::cout << "\nnume: " << m_Nume;
std::cout.precision (2);
std::cout << "\nmedie: " << std::fixed << this->getMedie() << std::endl;
std::cout << "grad: " << this->getGradStr();
}
const char* StudentBuget::getGradStr () const
{
switch (m_Grad) {
case STD_FR: return "std.fr.";
case STD_CAP: return "std.cap.";
case STD_SG: return "std.sg.";
case STD_SGMAJ: return "std.sg.maj";
default: return "std";
};
}

Ȋn clasa Student, e obligatoriu să schimbăm membrii de tip private ȋn protected


(pentru a-i putea accesa ȋn clasele copil).
class Student
{
protected:
int m_Id;
char *m_Nume;
int m_Note[100];
public:
Student (int id, char *nume, int note[]);
Student (const char *stdinfo);
//...
};
9
Mihai TOGAN
Specificatorul protected ȋn clasa de bază este la fel ca private, însă oferă access către
clasa copil la datele şi funcţiile membre din clasa de bază.

Putem refolosi deja codul scris la nivelul clasei de bază Student:

void StudentBuget::print()
{
Student::print();/* apel metoda print originala (din clasa de baza) */
std::cout << "grad: " << this->getGradStr();
}

Derivarea poate fi de tip public sau private. Ideea rămâne aceeaşi, însă există o serie de
diferenţe în anumite contexte.

class B { /*...*/ };
class D : public B // public derivation
{ /*...*/ };

class B { /*...*/ };
class D : B // private derivation
{ /*...*/ };

class B { /*...*/ };
class D : private B // private derivation
{ /*...*/ };

În tabelul următor este figurat accesul (vizibilitatea) din clasa derivată la membrii moşteniti
din clasa de bază:

Tip derivare Dacă ȋn clasa de bază, În clasa derivată,


un membru este: acel membru este (devine):
Private inaccesibil
public Protected Protected
Public public

Private inaccesibil
private protected private
Public public

10

You might also like