Documente Academic
Documente Profesional
Documente Cultură
Motenirea claselor
Obiective
- nelegerea modului n care se pot crea clase noi prin motenirea din clasele
existente
- nelegerea modului n care motenirea promoveaz reutilizarea codului
- nelegerea noiunilor de clas de baz i clas derivat
- Introducere
n acest capitol vom studia una dintre cele mai importante faciliti pe care le
ofer programarea orientat pe obiecte: motenirea claselor. Motenirea este o
form de reutilizare a codului n care noile clase sunt create din clase existente prin
absorbirea atributelor i comportamentelor lor, prin nlocuirea unor comportamente i
prin adgarea unor atribute i comportmante noi. Reutilizarea codului economisete
timp preios n dezvoltarea software. Este ncurajat reutilizarea secvenelor de cod
testate i de calitate pentru reducerea problemelor care ar putea aprea dup ce
sistemul ar deveni funcional.
Atunci cnd creeaz o nou clas, n loc s rescrie complet noile date membre
i funcii membre, programatorul poate arta c noua clas va moteni datele
membre i funciile membre dintr-o clas de baz definit anterior. Noua clas este o
clas derivat. Fiecare clas derivat poate deveni, mai departe, un candidat pentru
derivri ulterioare. Atunci cnd folosim motenirea simpl, o clas este derivat dintro singur clas de baz. Motenirea multipl presupune posibilitatea ca o clas s
fie derivat din mai multe clase de baz. Motenirea simpl este uor de folosit i o
vom ilustra prin cteva exemple. Motenirea multipl este mai complex i vom
studia doar un exemplu simplu.
O clas derivat poate aduga noi date membre i funcii membre, astfel nct
poate fi mai cuprinztoare dect clasa ei de baz. O clas derivat este mai
particular dect clasa ei de baz i reprezint un grup mai mic de obiecte. Fora
mecanismului motenirii vine din posibilitatea de a defini n clasele derivate adugiri,
nlocuiri sau rafinri ale elementelor motenite din clasa de baz.
Limbajul C++ ofer trei tipuri de moteniri: public, protected i private. n
acest capitol ne vom concentra pe motenirea public i le vom explica pe scurt pe
celelalte dou. Motenirea private poate fi folosit ca o modalitate alternativ de
compunere a claselor iar motenirea protected este folosit rar. Cnd derivarea
este public, fiecare obiect al unei clase derivate poate fi folosit oriunde se folosete
un obiect al clasei de baz din care a fost derivat acea clas. Operaia invers nu
este posibil, deci obiectele clasei de baz nu sunt i obiecte ale clasei derivate.
Avantajul acestei relaii rezumat prin expresia un obiect din clasa derivat este i
obiect al clasei de baz va fi ilustrat n capitolul n care vom introduce o alt facilitate
fundamental a programrii orientate pe obiecte, polimorfismul. Acesta permite
procesarea generic a obiectelor care fac parte din clase derivate din aceeai clas
de baz.
Experiena dezvoltrii aplicaiilor software arat c adeseori segmente
semnificative de cod sunt dedicate tratrii cazurilor speciale. Devine dificil
proiectarea acestor sisteme pentru c proiectantul i programatorul devin preocupai
de ele. Programarea orientat pe obiecte prin procesul numit abstractizare este o
soluie la aceste situaii. Dac programul este suprancrcat de cazuri speciale, o
soluie la ndemn este utilizarea secvenelor switch care pot oferi posibilitatea de
a scrie secvene logice de procesare pentru a trata fiecare caz individual. Vom vedea
ntr-unul dintre capitolele urmtoare c motenirea i polimorfismul sunt alternative
mai simple ale logicii switch.
Trebuie s facem distincia ntre relaiile tip este un sau este o (is a) i are
un sau are o (has a). Aa cum am vzut deja, o relaie has a nseamn
compunere de clase, adic un obiect al unei clase are ca membri unul sau mai multe
obiecte ale altor clase. O relaie is a este o motenire. Acesta este tipul de relaie n
care obiectele unei clase derivate pot fi tratate i ca obiecte ale clasei de baz.
n acest capitol discutm specificatorul de acces la membri numit protected.
O clas derivat nu poate accesa membrii private ai clasei sale de baz pentru c
n caz contrar s-ar nclca ideea de ncapsulare a clasei de baz. O clas derivat
poate, ns, s acceseze membrii public i protected ai clasei de baz. O clas
derivat poate accesa membrii private ai clasei sale de baz doar dac aceasta a
implementat funcii de acces public sau protected pentru ei.
O problem a motenirii este c o clas derivat motenete i implementri
ale funciilor membre public din clasa de baz de care nu are nevoie. Cnd
implementarea unui membru din clasa de baz nu este potrivit clasei derivate, acest
membru poate fi suprascris n clasa derivat cu o implementare corespunztoare.
Clase derivate
Cerc
Triunghi
Dreptunghi
CreditAuto
CreditImobiliar
ContCurent
ContDepozit
ContPlati
Forma
FormaBidimensionala
Cerc
Triunghi
FormaTridimensionala
Patrat
Sfera
Cub
- Membrii protected
Membrii public ai unei clase de baz pot fi accesai de orice funcie din
program. Membrii private al unei clase de baz sunt accesibili doar funciilor
membre sau prietenilor clasei.
Nivelul de acces protected este un nivel intermediar ntre accesul public i
cel private. Membrii protected ai unei clase de baz pot fi accesai doar de
membrii i de prietenii clasei de baz i de membrii i prietenii claselor derivate.
Membrii claselor derivate pot referi membrii public i protected ai clasei de baz
folosind numele acestor membri. Datele protected depesc ideea de ncapsulare
pentru c o schimbare a membrilor protected din clasa de baz poate influena
toate clasele derivate. n general, se recomand ca datele membre s fie declarate
private, iar protected trebuie folosit numai atunci cnd este strict necesar.
#include "point.h"
class Circle : public Point //Circle derivata din Point
{
friend ostream& operator<<(ostream&, const Circle&);
public:
//constructor implicit
Circle(double r = 0.0, int x = 0, int y = 0);
void setRadius(double); //seteaza radius
double getRadius() const; //intoarce radius
double area() const; //calculeaza aria
protected:
double radius;
};
#endif
circle.cpp
#include "circle.h"
//Constructorul clasei Circle apeleaza
//constructorul pentru Point si apoi
//initializeaza raza
Circle::Circle(double r, int a, int b)
: Point(a, b)
{ setRadius(r); }
//Seteaza raza cercului
void Circle::setRadius(double r)
{ radius = (r > 0 ? r : 0); }
//Returneaza raza cercului
double Circle::getRadius() const
{ return radius; }
//Calculeaza aria cercului
double Circle::area() const
{ return 3.14159 * radius * radius; }
//Afiseaza datele despre cerc in forma
//Centrul = [x, y]; Raza = #.##
ostream& operator<<(ostream& output, const Circle& c)
{
output << "Centrul = " << static_cast<Point>(c)
<< "; Raza = "
<< setiosflags(ios::fixed | ios::showpoint)
<< setprecision(2) << c.radius;
return output;
}
test_point_circle.cpp
#include <iostream>
using std::cout;
using std::endl;
#include <iomanip>
#include "point.h"
#include "circle.h"
5
int main()
{
Point *pointPtr = 0, p(30, 50);
Circle *circlePtr = 0, c(2.7, 120, 89);
cout << "Punctul p: " << p << "\nCercul c: " << c << '\n';
//Trateaza Circle ca un Point
//Este vizibila doar partea care provine din clasa de baza
pointPtr = &c; //asigneaza adresa unui Circle lui pointPtr
cout << "\nCercul c (via *pointPtr): "
<< *pointPtr << '\n';
//Trateaza un Circle ca un Circle
//cast de la pointer la clasa de baza la pointer
//la clasa derivata
circlePtr = static_cast<Circle*>(pointPtr);
cout << "\nCercul c (via *circlePtr):\n" << *circlePtr
<< "\nAria lui c (via circlePtr): "
<< circlePtr->area() << '\n';
//PERICULOS: Trateaza Point ca un Circle
pointPtr = &p; //asigneaza adresa unui Point la pointPtr
//cast al clasei de baza la clasa derivata
circlePtr = static_cast<Circle*>(pointPtr);
cout << "\nPunctul p (via *circlePtr):\n" << *circlePtr
<< "\nAria obiectului la care pointeaza circlePtr: "
<< circlePtr->area() << endl;
return 0;
}
Rulnd acest program obinem urmtorul rezultat:
Punctul p: [30, 50]
Cercul c: Centrul = [120, 89]; Raza = 2.70
Cercul c (via *pointPtr): [120, 89]
Cercul c (via *circlePtr):
Centrul = [120, 89]; Raza = 2.70
Aria lui c (via circlePtr): 22.90
Punctul p (via *circlePtr):
Centrul = [30, 50]; Raza = 0.00
Aria obiectului la care pointeaza circlePtr: 0.00
Interfaa public a clasei Point cuprinde funciile membre setPoint, getX i
getY. Datele membre x i y ale clasei Point sunt protected. Astfel, datele
membre nu pot fi accesate de clienii clasei, dar clasele derivate din Point vor putea
folosi n mod direct aceste date motenite. Dac aceste date ar fi fost private, ar fi
6
fost nevoie de apeluri ale funciilor membre public din clasa Point pentru accesul
acestor date chiar i n clasele derivate.
Clasa Circle este derivat public din clasa Point. Aceast derivare este
specificat prin prima linie din definiia clasei:
class Circle : public Point //Circle mosteneste Point
Semnul : din header-ul definiiei clasei indic aceast motenire. Cuvntul
cheie public arat tipul motenirii. Toi membrii public i protected ai clasei
Point sunt motenii ca public, respectiv protected n clasa Circle. nseamn
c interfaa public a clasei Circle cuprinde membrii public ai clasei Point, dar
i membrii public ai clasei Circle care sunt area, setRadius i getRadius.
Constructorul clasei Circle trebuie s invoce constructorul clasei Point
pentru iniializarea prii din obiect care provine din clasa de baz. Aceas invocare
se face prin lista de iniializare:
Circle :: Circle(double r, int a, int b)
: Point(a, b) //apelul constructorului clasei de baza
n situaia n care constructorul clasei Circle nu ar fi invocat n mod explicit
constructorul clasei Point, atunci ar fi fost apelat automat constructorul implicit al
clasei Point pentru iniializarea datelor membre x i y. n acest caz, n lipsa
constructorului implicit compilatorul semnaleaz eroare.
Programul creeaz pointPtr ca pointer la un obiect tip Point i circlePtr
ca pointer la un obiect Circle i obiectele p i c de tip Pointer i Circle.
Afiarea obiectelor p i c se face prin apelul operatorului << suprancrcat separat
pentru fiecare dintre cele dou tipuri de date.
Operatorul << suprancrcat n clasa Point poate manipula i obiecte din clasa
Circle tiprind partea care provine din clasa Point pentru obiectele clasei
Circle. Apelul se face prin cast de la referina c de tip Circle la un Point.
Rezultatul este apelul operator<< pentru Point i tiprirea coordonatelor x i y
folosind formatarea specific celei definite n clasa Point. Prin asignarea
pointPtr = &c;
este ilustrat operaia de upcasting prin care un obiect al unei clase derivate este
tratat ca obiect al clasei de baz. Rezultatul tipririi pointerului derefereniat
*pointPtr este partea din obiectul c care provine din clasa Point deoarece
compilatorul interpreteaza apelul operator<< ca un apel pentru un obiect tip
Point. Prin pointerul la clasa se baz se vede doar partea din obiectul c care
provine din clasa Point.
Asignarea
circlePtr = static_cast<Circle*>(pointPtr);
ilustreaz operaia de downcasting prin care un pointer la un obiect din clasa de baz
este convertit la un pointer la un obiect dintr-o clas derivat. Operatorul
static_cast din Standard C++ permite implementarea acestei operaii. Pointerul
pointPtr este transformat napoi n Circle* i rezultatul operaiei este asignat
pointerului circlePtr. Acest pointer a provenit iniial din adresa obiectului c, aa
cum se poate vedea n funcia main. Rezultatul afirii este coninutul obiectului c.
n final, programul asigneaz un pointer la clasa de baz (adresa obiectului p)
lui pointPtr i apoi aplic o operaie de cast lui pointPtr pentru a l transforma n
Circle*. Rezultatul celei de-a doua operaii este pstrat n circlePtr. Astfel,
obiectul p de tip Point este tiprit folosind operator<< pentru Circle i pointerul
derefereniat *circlePtr. Valoare tiprit pentru raz este 0 fiindc ea nu exist n
7
#include "hourly.h"
//Constructorul clasei HourlyWorker
HourlyWorker::HourlyWorker(const char* first,
const char* last,
double initHours,
double initWage)
: Employee(first, last)//apel al constructorului
//clasei de baza
{
hours = initHours; //ar trebui validat
wage = initWage;
//ar trebui validat
}
//Returneaza salariul muncitorului
double HourlyWorker::getPay() const
{ return wage * hours; }
//Tipareste numele si salariul
void HourlyWorker::print() const
{
cout << "Se executa HourlyWorker::print()\n\n";
Employee::print();//Apelul functiei print din clasa de baza
cout << " este un lucrator angajat cu ora platit cu "
<< setiosflags(ios::fixed | ios::showpoint)
<< setprecision(2) << getPay() << " EUR" << endl;
}
test_hourlyworker.cpp
#include"hourly.h"
int main()
{
HourlyWorker h("Bob", "Smith", 40.0, 10.00);
h.print();
return 0;
}
Rulnd acest program obinem urmtorul rezultat:
Se executa HourlyWorker::print()
Bob Smith este un lucrator angajat cu ora platit cu
400.00 EUR
Definiia clasei Employee const din dou date membre private char*,
firstName i lastName i trei funcii membre, un constructor, un destructor i
print. Funcia constructor primete dou iruri de caractere i aloc dinamic
tablouri de char pentru stocarea lor. Folosim macro-ul assert pentru a determina
daca fost alocat memoria pentru firstName i lastName. n cazul n care
alocarea nu s-a putut realiza, programul se termin cu un mesaj de eroare. O
alternativ la folosirea lui assert este secvena try/catch tiind c operaia new
care nu se desfoar corect genereaz excepia bad_alloc. Deoarece datele
membre ale lui Employee sunt private, singura modalitate prin care pot fi accesate
este funcia membr print care afieaz numele i prenumele angajatului.
10
Tipul motenirii
motenire
protected
motenire
private
protected n clasa
derivat.
Poate
fi
accesat
direct
prin
orice
funcie
membr
nestatic sau funcie
friend.
private n clasa
derivat.
Poate fi accesat direct
prin
orice
funcie
membr nestatic sau
funcie friend.
protected n clasa
derivat.
Poate
fi
accesat
direct
prin
orice
funcie
membr
nestatic sau funcie
friend.
Inaccesibil n clasa
derivat.
Poate
fi
accesat
direct
prin
orice
funcie
membr
nestatic sau funcie
friend prin funcii
private n clasa
derivat.
Poate fi accesat direct
prin
orice
funcie
membr nestatic sau
funcie friend.
11
Inaccesibil n clasa
derivat.
Poate fi accesat direct
prin
orice
funcie
membr nestatic sau
funcie friend prin
funcii
membre
12