Sunteți pe pagina 1din 52

PROGRAMAREA ORIENTATĂ PE OBIECTE

Proiectarea orientatã obiect

POO utilizează elementele programării structurate

extindere
organizarea de resurse soft ca şi o
colecţie de obiecte, distincte şi
se bazează pe orientarea pe obiecte discrete, care înglobează atât structuri
de date cât şi funcţiile de prelucrare
ale acestora.

programarea clasică “ce trebuie făcut cu datele?”


(proceduri care să transforme datele în rezultate)

orientarea pe obiecte datele şi legăturile între acestea


(elemente prin care se modelează obiectele lumii reale)

Ingineria software identificarea şi dezvoltarea de componente software.

caracteristici ale componentelor: entităţi abstracte care


realizează anumite acţiuni,
• o componentă trebuie să aibă un set redus bine definit de
adică îndeplinesc anumite
responsabilităţi;
responsabilităţi.
• interacţiunea dintre componente trebuie să fie minimă;
• identificarea componentelor se realizează în momentul
în care se imaginează procesul de execuţie a sistemului; setul de acţiuni pe care le
realizează;
• o componentă reprezintă o pereche constituită din: comportament descrierea comportamentului
unei componente se mai
numeşte şi protocol.

toate informaţiile
stare memorate
în interiorul ei.

Implementarea componentelor software se face prin intermediul construcţiilor de tip


clasă, puse la dispoziţie de limbajele de programare orientate obiect.

În general, pentru modelul de date orientat pe obiect, se consideră definitorii următoarele


concepte:

abstractizare, obiect, atribut, metodă, clasă, încapsulare, moştenire, polimorfism.


Abstractizarea
- procesul de simplificare a realităţii prin reţinerea caracteristicilor şi comportamentelor esenţiale şi
constituirea lor într-un model adecvat rezolvării problemelor.
- prin abstractizare se izolează aspectele esenţiale de cele neesenţiale, în funcţie de perspectiva de
abordare a problemei de rezolvat
- pentru o aceeaşi problemă, pot exista mai multe abstractizări, deci mai multe modele.
- Selectarea obiectelor abstracte şi a claselor este un punct important în realizarea unui program.

Obiectul
- o entitate individualizată prin nume
- conţine o mulţime de date descriu proprietăţile şi nivelul acestora
funcţii definesc comportamentul.
- obiectele pot fi clasificate în mulţimi. O mulţime de obiecte de acelaşi fel constituie o clasă de
obiecte, descrisă prin modelul comun al obiectelor sale.

Clasa
- grup de obiecte care fac parte din ea
- o clasă este o descriere a proprietăţilor şi comportamentului unui tip obiect; o clasă poate fi
considerată ca un tip special de dată, iar obiectele sale ca variabile de acest tip.
- pentru identificarea claselor unui sistem trebuie luate în consideraţie următoarele aspecte:
¤ Există informaţii care trebuiesc stocate sau analizate? Dacă da, ele sunt posibile candidate
pentru o clasă.
¤ Există sisteme externe cu care va comunica sistemul construit? Dacă da, este normal să fie
luate în considerare la modelarea sistemului.
¤Există biblioteci sau componente din proiecte anterioare care pot fi folosite? În caz afirmativ
acestea conţin clase candidate.
¤ Sunt device-uri în sistem care vor trebui gestionate? Orice device conectat la sistem
înseamnă o clasă candidat care sa-l gestioneze.
¤ Există părţi organizaţionale în sistem? Va fi nevoie de clase în care să fie implementate.

Definirea unei clase se realizează prin:


• precizarea operanzilor sau a atributelor;
• funcţiile membre;
• proprietăţile asociate componentelor pentru a stabili modalităţi de referire.
Obs:
- Atributele sunt de un anumit tip (de exemplu întregi, reale, caractere etc.).
- Setul de valori ale atributelor unui obiect la un moment dat formează starea curentă a
obiectului respectiv.
- Funcţiile care definesc comportamentul obiectelor sunt cunoscute ca metode ale clasei.
- Împreună, atributele şi metodele sunt membrii clasei, identificabili prin nume.

O clasă este un tip de dată definit de utilizator printr-o construcţie de forma:

Date sau atribute

Membrii
Funcţii sau metode

Aşa cum variabilele în programe sunt de tip global şi local sau după modul de alocare sunt statice şi
dinamice, analog, pentru a defini o anumită disciplină în manipularea claselor se asociază grupurilor
de elemente din clasă un specificator de control al cărui câmp de acţiune este anulat de un altul.
Specificatorii de control sunt:
• public – ceea ce înseamnă că elementul respectiv este accesibil oricărei clase externe;
• private – membrul este accesibil numai de funcţii din interiorul clasei sau de către funcţii prietene
(friend) ale clasei;
• protected – similar cu private dar accesul se extinde pentru funcţiile membre şi prietene derivate
din clasa respectivă.

Atunci, forma generală de declaraţie a unei clase devine:

Obs:
- Efectul unui specificator de acces dureaza pâna la urmatorul specificator de acces.
- Implicit, daca nu se declara nici un specificator de acces, datele sau functiile membre sunt de tip
private.
- O funcţie membră a unei clase are acces la toate datele membre din clasa respectivă, indiferent de
specificatorul de acces.
- La proiectarea unei clase se au în vedere următoarele aspecte:
• funcţiile membru să acopere întreaga gamă de prelucrări;
• între variabilele şi funcţiile membre să fie o concordanţă perfectă pentru a nu apare erori în
execuţia programelor ce folosesc clase.
- Întrucât o clasă este un tip de dată, declararea unui obiect se face asemănător oricărei declaraţii de
dată:
nume_clasă nume_obiect;
- Declararea unui obiect mai este numită şi instanţierea clasei, în sensul că se creează o instanţă a
acelei clase, o entitate concretă din mulţimea descrisă de clasa respectivă.
- Pentru a pune în evidenţă faptul că un membru aparţine unui obiect se utilizează formularea:
nume_obiect. nume_membru.
- O clasă conţine deci:
- o parte protejata/privata care asigura implementarea clasei;
- o parte publica care reprezintă interfaţa clasei respective cu celelalte secţiuni din program

Exemplul 1: pentru implementarea calculului cu numere raţionale, se defineşte clasa Rational:


#include<iostream.h>
class rational main ( )
{ {
int p,q; rational a;
public: a.nr (4 , 5);
void nr (int x=0 , int y=1) cout<<a.numarator( )<<a.numitor( );
{ }
p=x;
q=y;
}
int numarator ( )
{
return p;
}
int numitor ( )
{
return q;
}
};
Exemplul 2: pentru implementarea calculului cu numere complexe, se defineşte clasa Complex care
trebuie să aibă o structură de tip articol pentru a grupa partea reală şi partea imaginară a numărului
complex şi să conţină funcţii membre pentru operaţiile de adunare, scădere, înmulţire, împărţire,
ridicare la putere şi extragere de radical.

Obiect al clasei Complex


(ca şi variabilele, obiectele pot fi parametrii
pentru funcţii)
(componenta c.img a obiectului cpl)
Obs: Funcţiile vor fi descrise în cadrul clasei numai dacă au codul extrem de scurt şi nu conţin
instrucţiuni de ciclare. Altfel, clasa va conţine numai prototipul funcţiei.
Atunci când se descrie ulterior corpul unei metode (funcţii), pentru a specifica apartenenţa sa la clasa
respectivă, numele metodei este prefixat cu numele clasei din care face parte, folosind operatorul de
rezoluţie (::), astfel:
tip_rezultat nume_clasă :: nume_metodă(lista parametrilor)
corp metodă

Exemplul 3: prin modificarea programului din exemplul 1 se obţine:


#include<iostream.h>
class rational
{
int p,q;
public:
void nr (int x, int y);
int numarator ( );
int numitor ( );
};
main ( )
{
rational a;
a.nr (4 , 5);
cout<<a.numarator( )<<a.numitor( );
}
void rational :: nr (int x=0 , int y=1)
{
p=x; q=y;
}
int rational :: numarator ( )
{
return p;
}
int rational :: numitor ( )
{
return q;
}
};
CREAREA, INITIALIZAREA SI DISTRUGEREA OBIECTELOR UNEI CLASE

Pentru crearea si distrugerea obiectelor in C++ functii membre speciale constructori


destructori.

are ca efect
Declararea obiectelor alocarea de spaţiu în memorie, la fel ca în cazul declarării
oricărei variabile.
nu este iniţializat

Pentru rezolvarea problemei iniţializării obiectelor există posibilitatea utilizării unor metode speciale,
numite constructori.

La terminarea ciclului de viaţă al obiectelor, este necesară dezalocarea lor.

Problema încheierii ciclului de viaţă al obiectelor este rezolvată prin utilizarea unor metode speciale
numite destructori.

Constructorii si destructorii se declara si se definesc similar cu celelalte functii membre, dar prezinta
o serie de caracteristici specifice:
*) numele lor coincide cu numele clasei careia ii apartin; destructorii se disting de constructori prin
faptul ca numele lor este precedat de caracterul ~
*)nu pot returna nici un rezultat
*)nu se pot utiliza pointeri catre constructori sau destructori
*)constructorii pot avea parametri, destructorii insa nu. Un constructor fara parametri poarta
denumirea de constructor implicit.
*)in cazul in care o clasa nu dispune de constructori sau destructori, compilatorul de C++ genereaza
automat un constructor respectiv destructor implicit.

Sintaxa: id_clasa (lista_parametri);//prototip

Un constructor poate fi:


a) implicit:
- fara parametri (definit de utilizator) care poate contine in corpul sau orice instructiune valida. Un
astfel de constructor „inline“ nu poate contine instructiuni cu caracter repetitiv.
- generat de compilator (cand nu exista constructori definiti de utilizator in clasa respectiva).
Corpul unui astfel de constructor nu contine nici o instructiune.

b) cu parametri care, in totalitate sau partial, iau valori in mod implicit;


c) cu parametri care nu iau valori in mod implicit;

d) cu conversie de tip;

e) de copiere:

- definit de utilizator;

- generat implicit de compilator;

Obs: În cazul în care clasa prezintă constructori expliciţi valorile pentru iniţializare sunt transmise
acestuia la declararea obiectului, asemănător listei de parametri reali la apelul unei funcţii:

nume_clasă nume_obiect(lista_valori);

Exemple de costructori definiti de catre programator:

Ex1:

#include<math.h>
#include<iostream.h>
#include<conio.h>

class complex
{public:
float x,y,m; //datele clasei
void display();
float modul(); //metoda clasei
complex() //constructor fara parametri definit de catre programator
{cout<<endl<<"mesaj de la constructor"<<endl;}
};
Testand programul se afiseaza pe ecran de
float complex::modul()
trei ori mesajul: mesaj de la constructor,
{return sqrt(x*x+y*y);}
corespunzator fiecarei declarari a celor trei
instante ale clasei complex.
void complex::display()
{cout<<x<<"+"<<y<<"*i";}

complex q;

void main()
{complex q1, q2;
getch(); clrscr();
}
Urmatorul exemplu contine in plus inca un constructor definit de catre programator, constructor
ce are un parametru.

Ex2
#include<math.h>
#include<iostream.h>
#include<conio.h>

class complex
{public:
float x,y,m; //datele clasei
void display();
float modul(); //metoda clasei
complex()
{cout<<endl<<"mesaj de la constructor"<<endl;}

complex(float yy);
};

complex::complex(float xx)
{x=xx;
y=0;
}

float complex::modul()
{return sqrt(x*x+y*y);
}

void complex::display()
{cout<<endl<<x<<"+"<<y<<"*i";
cout<<endl;
}

complex q;

void main()
{cout<<"Obiectul global q=";
q.display();
complex q1;
cout<<"Obiectul local q1, retine valori reziduale";
q1.display();
complex q2(6.7); //se apleleaza constructorul cu un parametru
q2.display();
getch();
clrscr();
}
Urmatorul exemplu permite prin intermediul mecanismului de supraincarcare definirea unui
constructor cu doi parametri:

Ex3:
#include<math.h>
#include<iostream.h>
#include<conio.h>

class complex
{public:
float x,y,m; //datele clasei
void display();
float modul(); //metoda clasei
complex()
{cout<<endl<<"mesaj de la constructor"<<endl;}

complex(float yy);
complex(float xx, float yy);
};

complex::complex(float xx)
{x=xx;
y=0;
}

complex::complex(float xx, float yy)


{x=xx; y=yy;
}

float complex::modul()
{return sqrt(x*x+y*y);
}

void complex::display()
{cout<<endl<<x<<"+"<<y<<"*i";
cout<<endl;
}

complex q;

void main()
{cout<<"Obiectul global q=";
q.display();
complex q1;
cout<<"Obiectul local q1, retine valori reziduale";
q1.display();
complex q2(6.7);
cout<<"q2=";
q2.display();
complex q3(1.2,1.3);
cout<<"q3=";
q3.display();
getch();
clrscr();
}
Obs:
Ca orice alta functie în limbajul C++, constructorii pot avea parametri impliciti , fiind numiti
constructori cu parametri impliciti.
Varianta constructorului cu parametri impliciti poate fi adoptata în toate cazurile în care
constructorul nu necesita argumente.
Daca toti parametrii unui constructor sunt impliciti, apelul constructorului are forma unei simple
declaratii .

Ex 4: Pentru clasa complex s-a definit un constructor cu parametri impliciti; din acest motiv s-a
putut face declaratia "complex q;"

#include<math.h>
#include<iostream.h>
#include<conio.h>

class complex
{public:
float x,y,m; //datele clasei
void display();
float modul(); //metoda clasei

complex(float xx=0, float yy=0)


{cout<<endl<<"mesaj de la constructorul cu parametri impliciti "<<endl;
x=xx;
y=yy;
}
};

float complex::modul()
{return sqrt(x*x+y*y);
}

void complex::display()
{cout<<endl<<x<<"+"<<y<<"*i";
cout<<endl;
}

complex q; //obiect neinitializat ; apelul nu se face in mod explicit

void main()
{cout<<"Obiectul global q=";
q.display();
complex q1; //obiect neinitializat ; apelul nu se face in mod explicit
cout<<"Obiectul local q1 ";
q1.display(); La apelul explicit al constructorului:
complex q2(6.7); // obiect initializatt. complexq4=complex() evaluarea expresiei complex()
cout<<"q2="; conduce la:
q2.display(); - Crearea unui obiect temporar de tip complex (obiect cu o
complex q3(1.2,1.3); // obiect initializat. adresa precisa, dar inaccesibil);
cout<<"q3="; - Apelul constructorului pentru acest obiect temporar;
q3.display(); - Copierea acestui obiect temporar în q4.
complex q4=complex(); // constructorul este apelat în mod explicit.
q4.display();
getch();
clrscr();}
Constructori de copiere

Functia principala a unui constructor este aceea de a initializa datele membre ale obiectului
creat, folosind pentru aceasta operatie valorile primite ca argumente.

O alta forma de initializare care se poate face la crearea unui obiect este prin copierea datelor
unui alt obiect de acelasi tip.

operatie posibila prin intermediul constructorului de copiere

Pentru o clasa, se poate defini un contructor de copiere, care sa permita copierea obiectelor.
constructorul de copiere pentru clasa nume_clasa, are, de obicei, prototipul:

nume_clasa (nume_clasa & ob);

Parametrul transmis prin referinta este obiectul a carui copiere se realizeaza.

Obs: Daca programatorul nu defineste un constructor de copiere, compilatorul genereaza un


asemenea constructor, implicit.

Ex 5:

#include<math.h>
#include<iostream.h>
#include<conio.h>

class complex
{public:
float x,y,m; //datele clasei
void display();
float modul(); //metoda clasei

complex(float xx=0, float yy=0)


{cout<<endl<<"mesaj de la constructorul cu parametri impliciti "<<endl;
x=xx;
y=yy;
}

complex(complex &ob) //constructor de copiere


{ cout<<endl<<"operatie de copiere ";
x=ob.x;
y=ob.y;
}
};

float complex::modul()
{return sqrt(x*x+y*y);
}

void complex::display()
{cout<<endl<<x<<"+"<<y<<"*i";
cout<<endl;
}
void main()
{complex q1(1.2,1.3);
cout<<"q1=";
q1.display();
complex q2=q1; //se apeleaza constructorul de copiere
cout<<"q2=";
q2.display();
complex q3=complex(q1); //se apeleaza constructorul de copiere

cout<<"q3=";
q3.display();
getch();
clrscr();
}

Destructori

Destructorii sunt metode ale claselor care actioneaza în sens invers, complementar, fata de
constructori.

Constructorii sunt folositi pentru alocarea memoriei, initializarea datelor membru sau alte
operatii (cum ar fi, incrementarea unui contor pentru instantele clasei).

Destructorul elibereaza memoria alocata de constructori.

Constructorul este apelat în momentul declararii obiectelor.

Destructorul este apelat automat, la iesirea din blocul în care este recunoscut acel obiect.

Proprietatile destructorilor :

*)Destructorul are acelasi nume ca si clasa a carui metoda este;

*)Numele destructorului este precedat de semnul ~;

*)O clasa are un singur destructor;

*)Destructorul nu are parametri si nu returneaza nici o valoare (antetul nu contine cuvântul cheie
void, iar în corpul destructorului nu apare instructiunea return;);

*)Daca programatorul nu a definit un destructor, compilatorul genereaza automat un destructor


pentru clasa respectiva;

*)Destructorii se apeleaza la încheierea timpului de viata a obiectelor, în ordine inversa apelurilor


constructorilor;
Ex 6:
#include<math.h>
#include<iostream.h>
#include<conio.h>

class complex
{public:
float x,y,m; //datele clasei
void display();
float modul(); //metoda clasei

complex(float xx=0, float yy=0)


{cout<<endl<<"mesaj de la constructorul cu parametri impliciti "<<endl;
x=xx;
y=yy;
}

complex(complex &ob)
{ cout<<endl<<"operatie de copiere ";
x=ob.x;
y=ob.y;
}

~complex ()
{cout<<endl<<"am apelat destructorul pentru"<<endl; //destructor
display();}
};

float complex::modul()
{return sqrt(x*x+y*y);
}

void complex::display()
{cout<<endl<<x<<"+"<<y<<"*i";
cout<<endl;
}

void main()
{complex q1(1.1,1.1);
cout<<"q1=";
q1.display();
complex q2(2.2,2.2);
cout<<"q2=";
q2.display();
complex q3(3.3,3.3);
cout<<"q3=";
q3.display();
// getch();
}
Funcţii inline alternativă la utilizarea excesivă a macro-definiţiilor din limbajul C.

Compilatorul înlocuieşte orice apel al unei funcţii inline cu codul acesteia. În felul acesta se elimină
operaţiile suplimentare de apel şi revenire din funcţie.

Utilizarea funcţiilor inline măreşte viteza de execuţie a programului prin evitarea operaţiilor pe stivă,
ce au loc în cazul apelurilor de funcţii obişnuite, dar plăteşte drept preţ pentru aceasta creşterea
dimensiunii codului executabil.

Declaraţia unei funcţii inline este formată din declaraţia unei funcţii obişnuite precedată de cuvântul
cheie inline:

Sintaxa pentru definiţia claselor permite declararea implicită a unei funcţii inline. O funcţie membră
a unei clase definită (nu doar declarată) în interiorul clasei este implicit funcţie inline:

Alt exemplu:

Metoda “tipareste” este declarată explicit, ea fiind doar declarată în cadrul clasei, iar în locul unde
este definită este prefixată de cuvântul cheie “inline”.
O funcţie membră definită în afara clasei este implicit o funcţie normală (nu este inline). Dar şi o
astfel de funcţie poate fi declarată explicit inline:
Fie exemplul următor:

În acest exemplu, funcţia set( ) din clasa Complex poate fi declaratã explicit inline :

inline void Complex::set(double x, double y){ … }

Obs:

1) Metodele care nu sunt membru al unei clase nu pot fi declarate inline decât explicit.
2)Funcţiile inline nu pot fi declarate funcţii externe, deci nu pot fi utilizate decât în modulul de
program în care au fost definite şi nu pot conţine instrucţiuni ciclice (while, for, do-while).

Încapsularea datelor (ascunderea informaţiei) ascunderea detaliilor de implementare


ale unui obiect faţă de lumea exterioară (aplicaţiile care îl folosesc).

separarea elementelor unui tip de date abstract (clasă) în două părţi:

structura comportarea

dată de implementarea acestuia accesată prin interfaţă

definirea datelor şi a funcţiilor membre declaraţiile datelor şi funcţiilor membre


de tip public

În C++ încapsularea este îndeplinită prin două aspecte:

1. folosirea claselor pentru unirea structurile de date şi a funcţiilor destinate manipulării lor;

2. folosirea secţiunilor private şi publice, care fac posibilă separarea mecanismului intern de interfaţa
clasei;
Obs:

1) În general, respectând principiul încapsulării, datele membre sunt declarate private sau
protected şi nu pot fi accesate direct (pentru citire sau scriere) din funcţii nemembre ale clasei care
nu sunt de tip friend (sau nu aparţin unei clase friend a clasei respective).

2) Pentru citirea sau modificarea unora dintre datele membre protejate în clasa respectivă se pot
prevedea funcţii membre de tip public, care pot fi apelate din orice punct al domeniului de definiţie
al clasei şi fac parte din interfaţa clasei.

De exemplu, pentru clasa Complex, o implementare care respectă principiul încapsulării, dar, în
acelaşi timp permite accesul la datele private ale clasei poate arăta astfel:

Datele membre ale clasei (re şi im) sunt date de tip private, iar accesul la acestea este posibil prin
intermediul funcţiilor membre publice set(), setre(), setim(), etc.

Date şi funcţii membre de tip static

Cuvântul cheie “static” poate fi utilizat în prefixarea membrilor unei clase. Odată declarat “static”,
membrul în cauză are proprietăţi diferite, datorită faptului că membrii statici nu aparţin unui anumit
obiect, ci sunt comuni tuturor instanţierilor unei clase.

1) date membre statice:

Cea mai frecventă utilizare a datelor membre statice este de a asigura accesul la o variabilă comună
mai multor obiecte, deci pot înlocui variabilele globale.
Datele membru statice, care sunt utilizate de către toate obiectele clasei, se definesc prin
specificatorul static astfel:

Datele membru statice, se declară ( se iniţializează) explicit în afara clasei :

Referirea (utilizarea) datelor membru statice se poate face astfel :

Exemplul 1:

Se consideră o clasă S care conţine o variabilă normală v şi o variabilă statică s. Data de tip static
este declarată în clasa S şi definită în afara acesteia, folosind operatorul de rezoluţie.

Se poate urmări evoluţia diferită a celor două variabile v şi s prin crearea a două obiecte x şi y de tip
S, şi prin apelul funcţiilor incs() şi incv() pentru obiectul x astfel:

La execuţia acestui program se afişează conţinutul variabilelor s şi v pentru obiectele x şi y. Mesajele


afişate sunt următoarele:
Diferenţa între comportarea unei date membre de tip static şi a unei date normale este evidentă: după
incrementarea variabilelor s şi v pentru obiectul x, obiectele x şi y văd aceeaşi valoare a variabilei
statice s şi valori diferite ale variabilei normale v.

Exemplul 2:

În următorul exemplu, se numără câte Puncte sunt utlizate la un moment dat în program, utilizând ca
dată comună pentru toate punctele ( întreaga clasă) Nr_Ob:

2) funcţii membre statice:

O funcţie membră de tip static se declară în interiorul clasei şi se defineşte în interiorul clasei sau în
afara acesteia, folosind operatorul de rezoluţie.
O funcţie membră statică are vizibilitatea limitată la fişierul în care a fost definită şi este
independentă de instanţele (obiectele) clasei.

Metodele statice, se definesc prin specificatorul static astfel:


Referirea ( utilizarea) metodelor statice se poate face astfel :

Exemplu:

Fie, de exemplu clasa Task care conţine o dată membră statică şi o funcţie membră statică:

Apelul unei funcţii statice se poate face fie ca funcţie membră a unui obiect din clasa respectivă, aşa
cum apare în primul apel din funcţia fs2(), fie prin specificarea clasei căreia îi aparţine, folosind
operatorul de rezoluţie.

Obs:

Specificatorul static are în C++ două semnificaţii: aceea de vizibilitate restricţionată la nivelul
fişierului în care sunt definite variabilele sau funcţiile şi aceea de alocare statică, adică obiectele
există şi-şi menţin valorile lor de-a lungul execuţiei întregului program.
Funcţii şi Clase Prietene (Friend)

O funcţie este prietenă cu o clasă dacă are acces la datele membru private ale acelei clase. O
funcţie prietenă poate fi o funcţie globală sau chiar o funcţie membru a altei clase.
O clasă este prietenă cu o altă clasă dacă ea are acces la datele membru ale acesteia.
O funcţie, respectiv o clasă prietenă se declară utilizând cuvântul friend astfel:
a) friend Tip_funcţie Nume_funcţie ( Listă_parametri_formali ); // Funcţie friend globală
b) friend Tip_funcţie Nume_clasă::Nume_funcţie(Listă_par._formali);// Funcţie friend membru
c) friend Nume_clasă; // Clasă friend

Obs:
1) O funcţie de tip friend este în esenţă o funcţie standard, care nu este membră a clasei ci are numai
acces la membrii de tip privat ai acelei clase. Orice functie poate fi prietenă a unei clase, indiferent
de natura acesteia.

2) Declaraţia unei funcţii prietene se poate face oriunde în cadrul declaraţiei clasei şi oferă funcţiei
posibilitatea de a avea acces la oricare dintre membri.

3) Sunt posibile următoarele situaţii:


- funcţie independentă este prietenă a unei clase;
- funcţie membră a unei clase este prietena altei clase;
- funcţie ce este prietena mai multor clase;
- toate funcţiile unei clase y sunt prietene ale altei clase x; în acest caz se spune că clasa y este
prietenă a clasei x.

4) Modificatorii de acces nu au nicio influenţă asupra funcţiilor prieten, de aceea ele pot fi specificate
oriunde în cadrul descrierii clasei. Deci declararea unei funcţii prietene se poate face oriunde în
cadrul declaraţiei clasei (atât in partea privata cat si in cea publica) si oferă funcţiei privilegiul de a
avea acces la oricare dintre membri.

Exemplul 1:
În acest exemplu funcţia Afişează va fi scoasă în afara clasei Complex şi va fi declarată ca funcţie
prieten.

class Complex { float p_reala, p_imaginara;


public:
friend void Afiseaza(Complex x);
Complex(float a=0,float b=0);
Complex(Complex &x);
};
void Afiseaza(Complex x)
{ cout<<x.p_reala<<x.p_imaginara;
}
void main()
{ Complex a(1,2);
Afiseaza(a);
}
Exemplul 2:
//Să considerăm un tip definit de utilizator:

#include <iostream.h>
class X
{
int u,v;
public:
X(int k, int q){u=k;v=q;}
int dif(){return u*u-v*v;}
};
void main()
{
X z(6,3);
int p=z.dif();
cout<<"p="<<p;
}
Se obţine pentru p valoarea 27, folosind funcţia membră, dif( ).
La acelaşi rezultat se va ajunge folosind funcţia friend dif( ) a clasei X, care nu mai este
funcţie membră a acestei clase.

#include <iostream.h>
class X
{
int u,v;
public:
X(int k, int q){u=k;v=q;}
friend int dif(X w);
};
int dif(X w){return w.u*w.u-w.v*w.v;}
void main()
{
X z(6,3);
cout<<dif(z);
}

Obs : Există două situaţii în declararea funcţiilor friend ale unei clase:

a) se specifică faptul că funcţia membră a unei clase este funcţie friend a unei alte clase. Mai
exact, se declară funcţia membră din prima clasă ca fiind de tip friend în a doua clasă.

Exemplu:
class Y;
// se predeclară clasa care conţine funcţii prietene ale unei alte clase X
class X
{
……………
void F1(Y &a); // funcţie membră a clasei X
…………….
};

class Y
{
………………
friend void X::F1(Y &a); //funcţie friend pentru clasa Y
………………..
};

b) se poate declara o întreagă clasă prietenă , în care toate funcţiile membre ale sale sunt funcţii
prietene ale unei alte clase.
Exemplu:
class Y;
class X
{
………………..
void F1(Y &a);
………………..
};

class Y
{
………………..
friend X;
// se declară clasa X ca prietenă a clasei Y(toate funcţile membre
// ale clasei X sunt funcţii friend pentru clasa Y)
………………..
};

Exemplul 3:
Sa se defineasca tipul abstract de date complex care sa aiba functii membru pentru:
- modul, argument
- accesul la partea reala si imaginara a obiectelor de tip complex
- afisarea obiectelor complexe
- constructori pentru initializare si copiere

Sa se scrie un program care realizeaza urmatoarele:


• citeste perechi de n numere care reprezinta fiecare, partea reala si respectiv imaginara a unui numar
complex
• afiseaza:
- numarul complex citit
- suma numerelor complexe citite
Temă: Sa se defineasca o functie care ridica un numar complex la o putere intreaga pozitiva.

Un numar complex se poate ridica la o putere intreaga folosind formula lui MOIVRE. Aceasta se
exprima prin relatia:

((r*cos(a)+i*sin(a)))n = rn * (cos(n*a)+i*sin(n*a))
unde:

r - este modulul numarului complex


a – este argumentul nr. Complex
#include <iostream.h>
#include <conio.h>
#include <math.h>
#define PI 3.14159265358979
class complex
{
double re; double im;
public:
double modul;
double arg();
void afis();
friend void cp(complex&,int);
void init(double x,double y)
{
re=x; im=y;
modul=sqrt(re*re+im*im);
}
};
inline void complex::afis(){cout<<re<<"+i*"<<"("<<im<<")";}
double complex::arg()
{
if(re==0.0&&im==0.0)return 0.0;
if(im==0.0)
if(re>0.0) return 0.0;
else
return PI;
if(re==0.0)
if(im>0.0)return (PI/2);
else
return (3*PI)/2;
double a=atan(im/re);
if(re<0.0)return PI+a;
if(im<0.0)return 2*PI+a;
return a;
}
void cp(complex &z,int n)
{
double r; double a;
r=z.modul;
a=z.arg();
double r_la_n=pow(r,(double)n);
double aa=n*a;
z.re=r_la_n*cos(aa);
z.im=r_la_n*sin(aa);
}
void main()
{
complex u;
u.init(-1,-1);
cout<<"\n U=";
u.afis();
cout<<"\n Modulul nr.U:"<<u.modul;
cout<<"\n Nr. ridicat la putere:";
cp(u,3);
u.afis(); }
Exemplul 4:
// functii prietene (friend)

#include <iostream.h>
Din cadrul functiei duplicate, care
class CRectangle { este prietena cu CRectangle, nu este
int width, height; posibil accesul asupra membrilor
public: width si height ale diverselor
void set_values (int, int); obiecte de tip CRectangle.
int area (void) {return (width * height);}
friend CRectangle duplicate (CRectangle);
}; De observat ca nici in declaratia lui
duplicate() nici in utilizarea lui
void CRectangle::set_values (int a, int b) { ulterioara din main() nu s-a
width = a;
height = b;
considerat ca functia duplicate() ar
} fi membra a clasei CRectangle.

CRectangle duplicate (CRectangle rtparam)


{
CRectangle rtres;
rtres.width = rtparam.width*2;
rtres.height = rtparam.height*2;
return (rtres);
}

int main () {
CRectangle rt, rtb;
rt.set_values (2,3);
rtb = duplicate (rt);
cout << rtb.area();
}

// clasa prietena (friend)


#include <iostream.h> S-a declarat clasa CRectangle ca prietena a
clasei CSquare astfel incat CRectangle poate
class CSquare; accesa mebrii protected si private ai clasei
CSquare, mai concret, data membru
class CRectangle {
int width, height; CSquare::side, care defineste latura
public: patratului.
int area (void)
{return (width * height);} Prima instructiune reprezinta un prototip vid
void convert (CSquare a);
};
al clasei CSquare. Aceasta este necesara pe
motivul ca in cadrul functiei CResctangle se
class CSquare { face referire la CSquare (ca parametru in
private: functia convert()).
int side;
public:
void set_side (int a) Definitia lui CSquare este inclusa mai tarziu,
{side=a;} iar daca nu ar fi fost instructiunea
friend class CRectangle; prototipului vid, clasa CSquare nu ar fi fost
}; vizibila din cadrul definitiei lui CRectangle -
void CRectangle::convert (CSquare a) {
compilatorul ar fi sesizat o eroare, in acest
width = a.side; caz.
height = a.side;
}

int main () {
CSquare sqr;
CRectangle rt;
sqr.set_side(4);
rt.convert(sqr);
cout << rt.area();
return 0;
}
Exemplul 5:

// functii prietene (friend)


class Point {
friend unsigned long Calcul(unsigned X, unsigned Y);
public:
fiend unsigned long AltaClasa::Calcul(unsigned X, unsigned Y);
...
};
unsigned long Calcul(unsigned X, unsigned Y)
{
return X * Y / 2;
}
unsigned long AltaClasa::Calcul(unsigned X, unsigned Y);
{
...
}

Obs:

Relatia de prietenie nu este tranzitiva. Daca o clasa A este prietena cu o clasa B, iar clasa B este
prietena cu o clasa C, aceasata nu inseama ca A este prietena cu C.

De asemenea, proprietatea de prietenie nu se mosteneste in clasele derivate !


Clase derivate. Moşteniri

Moştenirea reprezintă o relaţie între clase

elementul definitoriu al OOP.

Relaţia permite constituirea unei noi clase, numită derivată (sau fiu) pornind de
la clase existente, denumite de bază (sau părinte).

Moştenirea simplă - în procesul de construire participă o singură clasă


de bază
multiplă - în procesul de construire participă mai multe
clase de bază

Derivarea permite definirea într-un mod simplu, eficient şi flexibil a


unor clase noi prin adăugarea unor funcţionalităţi claselor deja existente,
fără să fie necesară reprogramarea sau recompilarea acestora.

Derivarea claselor este legată de implementarea conceptului de moştenire.

O clasă care asigură proprietăţi comune mai multor clase se defineşte ca o clasă
de bază.
O clasă derivată moşteneşte de la una sau mai multe clase de bază toate
caracteristicile acestora, cărora le adaugă alte caracteristici noi, specifice ei.
Se spune că o clasă D moşteneşte o clasă A, dacă obiectele din clasa D conţin
toate atributele clasei A şi au acces la toate metodele acestei clase.
Dacă D moşteneşte A, atunci obiectele din D vor avea toate atributele şi acces la
toate metodele lui A, dar în plus: - D poate defini noi atribute şi metode;
- D poate redefini metode ale clasei de bază;
- metodele noi şi cele redefinite au acces la toate
atributele dobândite sau nou definite.
Deci prin derivare sunt construite clasele din aproape în aproape, organizându-
se pe niveluri de agregare.
Clasele agregate de pe nivelul k preiau operanzii şi funcţiile membre ale
claselor de pe nivelul k-1, care intră prin derivare în componenţa lor, precum şi
proprietăţile acestora.
În limbajul C++ este permisă moştenirea multiplă. Pentru a defini o clasă fiu ca
fiind derivată dintr-o clasă părinte (sau mai multe clase părinte), se procedează astfel:

class nume_clasa_fiu : lista_clase_părinte


{ descriere membri noi ai clasei fiu};

În lista claselor părinte se specifică numele claselor părinte, separate prin


virgulă şi, eventual, precedate de modificatori de acces – se pot folosi modificatorii
public sau private.
Obs:
1) Pentru fiecare clasă părinte se poate specifica un modificator de acces.
Dacă nu se specifică niciun modificator de acces atunci, implicit, se consideră
modificatorul public.

2) O clasă derivată are acces la membrii clasei părinte care au fost definiţi ca fiind
publici sau protejaţi şi nu are acces la membrii privaţi. Prin derivare se construiesc
ierarhii de clase, deci din clasa fiu se pot deriva alte clase noi.

3) Întrucât ierarhiile de clase nu sunt definitive, ci oferă posibilitatea extinderii prin


adăugarea de noi clase derivate, se preferă ca la derivare să se folosească modificatorul
de acces public. De aceea acesta este modificatorul explicit.

Exemplul 1:

Fie clasa Punct care implementează entitatea punct geometric. Aceasta are
atributele x şi y, care reprezintă coordonatele punctului în plan. Este inclusă o singură
metodă, care desenează punctul pe ecran.
class punct { int x,y;
public:
void deseneaza();
};
void punct::deseneaza();
{ //corpul nu este descris in acest exemplu
}

Fie clasa Cerc care implementează entitatea geometrică cerc. Aceasta este
descrisă prin coordonatele centrului cercului şi raza sa. Ca urmare clasa Cerc poate fi
derivată din clasa Punct, adăugând un nou atribut (raza) şi o nouă metodă, pentru
desenarea cercului.

Clasa Cerc moşteneşte clasa Punct, deci un obiect de tipul Cerc va avea ca
membri coordonatele x,y moştenite şi ca atribut propriu Raza.

class cerc: punct


{ float raza;
public:
void deseneaza();
};
void cerc::deseneaza()
{ //corpul nu este descris in acest exemplu
}

Clasa cerc o să aibă ca membri atributele x, y şi raza şi metodele deseneazã


(moştenită de la clasa Punct, care va fi folosită pentru desenarea centrului cercului) şi
deseneazã (nou definită, care va fi folosită pentru desenarea cercului).

Funcţia Distanţa, definită pentru calculul distanţei dintre punctul curent şi


punctul p, dat ca parametru, este accesibilă şi pentru obiectele Cerc şi va calcula
distanţa dintre centrul cercului şi un alt punct, primit ca parametru.
Funcţia Arie calculează aria din interiorul cercului, fiind nou definită.
Funcţia Desenează este redeclarată de clasa Cerc, lucru impus de codul diferit
pe care trebuie să-l aibă pentru desenarea obiectelor din această clasă (cerc sau altă
figură).
Moştenirea simplă

Exemplul 2:
Se consideră un program care descrie organizarea personalului unei instituţii
fără folosirea claselor derivate. O clasă numită Angajat deţine date şi funcţii
referitoare la un angajat al instituţiei:

class Angajat{
char *nume;
float salariu;
public:
Angajat();
Angajat(char *n, float sal);
Angajat(Angajat& r);
void display();
};
Angajat::display(){
cout << nume << “ ” << salariu << endl;
}

Diferite categorii de angajaţi necesită date suplimentare faţă de cele definite în


clasa Angajat, corespunzătoare postului pe care îl deţin. De exemplu, un şef de secţie
(administator) este un angajat (deci sunt necesare toate datele care descriu această
calitate) dar mai sunt necesare şi alte informaţii, de exemplu precizare secţiei pe care o
conduce. De aceea, clasa Administator trebuie să includă un obiect de tipul Angajat, la
care adaugă alte date:
class Administrator{
Angajat ang;
int sectie;
public:
void display();
};

Un administrator este un angajat, de aceea clasa Administrator se poate construi prin


derivare din clasa Angajat astfel:

class Administrator : public Angajat {


int sectie;
public:
void display();
}

Această operaţie care se poate reprezenta schematic prin indicarea derivării cu o


săgeată (arc direcţionat) de la clasa derivată la clasa de bază.

Moştenirea multiplă: Este posibilă moştenirea din mai multe clase de bază. De
exemplu:

class X : public Y, public Z, ….public W {


// corpul clasei
};

Evident, specificatorii de acces pot să difere (pot fi oricare din public, private,
protected).
O clasă de bază se numeşte bază directă dacă ea este menţionată în lista de
clase de bază.
O clasă de bază se numeşte bază indirectă dacă nu este bază directă dar este
clasă de bază pentru una din clasele menţionate în lista claselor de bază.
Într-o clasă derivată se moştenesc atât membrii bazelor directe cât şi membrii
bazelor indirecte.
De exemplu, se completează programul de descriere a organizării personalului
unei instituţii şi pentru alte categorii de personal (personal temporar, consultant,
secretară, director), prin adăugarea câte unei clase pentru fiecare categorie de personal.
Modul în care aceste clase se derivează din anumite clase de bază evidenţiază
relaţiile de ierarhie între categoriile pe care le reprezintă:
class Personal { /* … */ };
class Angajat : public Personal{ /* …*/ };
class Administrator : public Angajat { /* … */ };
class Director : public Administrator { /* … */ };
class Secretara : public Angajat { /* … */ };
class Temporar : public Personal { /* … */};
class Consultant : public Temporar, public Administrator
{ /* … */ };

Această organizare a claselor se poate descrie printr-un graf aciclic direcţionat


astfel:

Clasa (nodul) din vârful ierarhiei reprezintă categoria cea mai generală a
ierarhiei, iar toate celelalte moştenesc, direct sau indirect, din această clasă de bază,
fiind clase mai specializate.
Controlul accesului la membrii clasei de bază

Accesul la membrii clasei de bază moşteniţi în clasa derivată este controlat de


specificatorul de acces (public, protected, private) din declaraţia clasei derivate.

O regulă generală este că, indiferent de specificatorul de acces declarat la


derivare, datele de tip private în clasa de bază nu pot fi accesate dintr-o clasă
derivată. O altă regulă generală este că, prin derivare, nu se modifică tipul datelor în
clasa de bază.

Moştenirea de tip public a clasei de bază

Dacă specificatorul de acces din declaraţia unei clase derivate este public, atunci:
• Datele de tip public ale clasei de bază sunt moştenite ca date de tip public în
clasa derivată şi deci pot fi accesate din orice punct al domeniului de definiţie al clasei
derivate.
• Datele de tip protected în clasa de bază sunt moştenite protected în clasa
derivată, deci pot fi accesate numai de funcţiile membre şi friend ale clasei derivate.

Exemplu:
Diverse situaţii de acces la membrii clasei de bază din clasa derivată atunci când
specificatorul de acces este public:
class Base {
int a;
protected:
int b;
public:
int c;
void seta(int x){a = x; cout << "seta din baza\n";}
void setb(int y){b = y; cout << "setb din baza\n";}
void setc(int z){c = z; cout << "setc din baza\n";}
};
class Derived : public Base {
int d;
public:
void seta(int x) {
a = x; // eroare, a este private
}
void setb(int y) {
b = y;
cout << "setb din derivata\n";
}
void setc(int z) {
c = z;
}
};
}

Moştenirea de tip protected a clasei de bază

Dacă specificatorul de acces din declaraţia clasei derivate este protected, atunci
toţi membrii de tip public şi protected din clasa de bază devin membri protected în
clasa derivată.
Bineînţeles, membrii de tip private în clasa de bază nu pot fi accesaţi din clasa
derivată.

Moştenirea de tip private a clasei de bază

Dacă specificatorul de acces din declaraţia clasei derivate este private, atunci
toţi membrii de tip public şi protected din clasa de bază devin membri de tip private în
clasa derivată şi pot fi accesaţi numai din funcţiile membre şi friend ale clasei derivate.
Bineînţeles, membrii de tip private în clasa de bază nu pot fi accesaţi din clasa
derivată.

Moştenirea multiplă:

O clasă poate avea mai multe clase de bază directe, dacă acestea sunt specificate
în declaraţia clasei.

În exemplul următor este prezentată clasa Derived care moşteneşte două clase
de bază, Base1 şi Base2.
class Base1 {
protected:
int x;
public:
Base1(int i) {
cout << "Constructor baza 1\n"; x = i;
}
~Base1() { cout <<"Destructor baza 1\n"; }
int getx(){return x;}
};
class Base2{
protected:
int y;
public:
Base2(int j){
cout << "Constructor baza 2\n"; y = j;
}
int gety() { return y;}
~Base2() { cout <<"Destructor baza 2\n"; }
};
class Derived : public Base1, public Base2 {
int d;
public:
Derived (int i, int j);
~Derived(){ cout << "Destructor derivata\n"; }
};
Derived::Derived(int i, int j): Base1(i), Base2(j){
cout << "Constructor derivata\n";
}
void fbm(){ În funcţia fbm() este creat
Derived obd(3,4); obiectul obd de clasă
cout << obd.getx() << " "<< obd.gety() << endl; Derived.
}

Constructorul clasei Derived transferă câte un argument constructorilor fiecărei


clase de bază, care iniţializează datele membre x şi y.
La execuţia funcţiei fbm() se afişează următoarele mesaje, care indică ordinea în
care sunt apelaţi constructorii şi destructorii clasei derivate şi ai claselor de bază:
Constructor baza 1
Constructor baza 2
Constructor derivata
3 4
Destructor derivata
Destructor baza 2
Destructor baza 1
Avantajele folosirii moştenirii:

• reutilizabilitatea – când o funcţionalitate este moştenită din altă clasă, codul


respectiv nu trebuie rescris, el trebuie doar apelat în noul context; o altă implicaţie este
legată de fiabilitatea codului, deoarece prin moştenire, o anumită funcţionalitate este
scrisă doar la nivelul unei clase şi apoi moştenită şi utilizată în toate clasele derivate;
• consistenţa interfeţei – când două sau mai multe clase sunt derivate din aceeaşi
clasă părinte, se asigură faptul că comportamentul moştenit este acelaşi pentru toate
clasele;
• componentele software – moştenirea dă posibilitatea programatorilor să
construiască componente software reutilizabile şi gruparea lor în biblioteci; în acest
fel, efortul de dezvoltare al unui produs nou este diminuat prin utilizarea de librării cu
funcţionalitate deja implementată;
• dezvoltarea rapidă de prototipuri – atunci când sistemul software este construit
folosindu-se componente reutilizabile, timpul de dezvoltare este concentrat pe
înţelegerea elementelor specifice ale sistemului; astfel se construiesc versiuni de
sistem, numite prototipuri, care pun accent pe aspectele critice ale sistemului.
Un prototip este dezvoltat, utilizatorii îl folosesc, iar a doua versiune a sistemului este
realizată pe baza experienţei acumulată cu prima şi a feedbackului de la utilizatori.

Deşi moştenirea prezintă foarte multe avantaje, există şi o serie de costuri de


care trebuie să se ţină seama în momentul proiectării ierarhiilor de clase:
• viteza de execuţie – este influenţată prin prisma faptului că metodele moştenite, care
au un caracter mai general, sunt de regulă mai lente decât codul specializat; însă
afectarea vitezei de execuţie este compensată cu creşterea vitezei de dezvoltare;
• dimensiunea programelor – este influenţată în sensul că devine mai mare în cazul
programelor care folosesc librării de componente, decât programele care folosesc cod
specializat, deoarece nu toate componentele dintr-o librărie sunt folosite în proiect, dar
librăria trebuie adăugată în totalitatea sa;
• complexitatea programelor – o ierarhie de clase introduce un anumit grad de
complexitate în sistem; cu cât ierarhia este mai mare, nivelurile de abstractizare mai
multe, cu atât sistemul care foloseşte această ierarhie este mai complex.
Polimorfism

Polimorfismul se referă la posibilitatea de a folosi acelaşi nume cu înţelesuri diferite.


În C++, polimorfismul poate fi realizat în mai multe moduri.

Polimorfismul funcţiilor reprezintă posibilitatea de a asocia acelaşi nume la diferite


funcţii. Evitarea ambiguităţii în procesul de referire se obţine prin:
• atribuirea de liste de parametrii de lungimi diferite;
• atribuirea de liste de parametrii cu aceeaşi lungime, dar parametrii
corespunzători ca poziţie au tipuri diferite;
În cazul funcţiilor, polimorfismul poartă şi numele de supraîncărcare.

Exemplul 1:
int unu (int i)
In acest caz functia unu ( )
{return i;}
este redefinita folosind un
int unu (int i, int j)
numar diferit de parametrii
{return i+j;}

Exemplul 2:
int doi (int i) In acest caz functia doi ( )
{return i;} este supraincarcata folosind
int doi (float i) tipuri diferite de parametrii.
{return floor(i);}

Obs:
1) Nu pot fi supraincarcate doua functii care difera doar prin tipul rezultatului returnat
Deci urmatoarea "supraincarcare" a functiilor este gresita:
int trei (int i); float trei (int i);

2) Deoarece constructorul este si el o functie, poate fi supraincarcat:


complex ( ); //constructor
{real=0; imag=0;}
complex (float real, float imag) // constructor supraincarcat
{real=a; imag=b;}
Obs:
1) Supraincarcarea constructorilor ii face mai usor de utilizat in clase diferite. De aici
rezulta flexibilitatea limbajului C++.
2) De cate ori este definita o functie supraincarcata, ea trebuie declarata complet.
3) Importanta functiilor supraincarcate consta in aceea ca ele permit ca seturi de
functii inrudite sa fie accesibile printr-un singur nume. Datorita polimorfismului se pot
declara mai multe "versiuni" ale unei functii diferite prin numarul si/sau tipul
parametrilor, eventual prin tipul rezultatului, iar compilatorul va alege versiunea
corecta ce urmeaza a fi utilizata.
4) La redefinirea functiilor trebuie sa se tina seama de utilizarea unor nume sugestive
de functii, care au sens in cadrul problemei, pentru a usura intelegerea codului de alti
programatori.
5) Supraincarcarea functiilor se mai numeste si polimorfism functional si are rolul de
a reduce numarul denumirilor functiilor folosite.

O altă situaţie în care se pune problema polimorfismului o reprezintă


suprascrierea metodelor. Prin moştenire, clasa derivată preia metodele expuse de
clasa părinte, conform cu regulile de derivare. Însă, ea are posibilitatea să furnizeze o
altă implementare pentru o metoda moştenită.
Practic, se defineşte o metodă în clasa derivată cu aceeaşi semnătură cu cea din
clasa părinte, dar care are o altă implementare. O operaţie a unei clase copil cu acelaşi
prototip (signatură) cu cea a unei operaţii a clasei părinte suprascrie operaţia clasei
părinte.
În momentul execuţiei, metoda din clasa derivată este identificată şi executată
înaintea metodei din clasa părinte.

Exemplu:
Considerăm ierarhia Persoana. Presupunem că dorim să adăugăm o metodă
semneaza(). Un student va semna adaugând prefixul "Student" iar un profesor va
semna adaugând prefixul "Profesor". Urmează ca fiecare dintre cele trei clase să aibă
propria sa metodă semneaza():
class Persoana
{
...
void semneaza();
};
class Student : public Persoana
{
...
void semneaza();
};
class Profesor : public Persoana
{
...
void semneaza();
};
void Persoana::semneaza()
{
cout << getNume() << endl;
}

Supraincarcarea operatorilor
Prin supraincarcarea operatorilor se extinde tipul obiectelor asupra carora se pot aplica
acesti operatori.
Se pot supraincarca aproape toti operatorii in C++.
Operatorii care pot fi supradefiniti sunt:
Următorii operatori nu se supraîncarcă:
• ([]) operatorul de selectare a unei componente membre într-o structură de tip articol;
• (*) operatorul de deferire a unei componente din clasă;
• (::) operatorul de selecţie a funcţiei membru dintr-o clasă; operatorul de tratare variabile globale;
• (?:) operatorul condiţional.
Alte restrictii la supraincarcarea operatorilor:
- Se pot supraincarca doar operatorii existenti; nu se pot crea noi operatori.
- Nu se poate modifica numarul de operanzi ai operatorilor limbajului (operatorii unari
nu pot fi supraincarcati ca operatori binari, si invers).
- Nu se poate modifica precedenta si asociativitatea operatorilor.

Limbajul C++ permite supradefinirea operatorului op prin definirea unei functii


numite operator op

Unde op este un operator ca : +, -, [] , & etc.

Supradefinirea se realizeaza astfel:


tip_val_intoarsa operator op (lista_declar_parametri)
{
// . . . . corpul functiei
}

Exemplul 1: Pentru clasa complex , putem atribui operatorului + semnificatia:


expresia a+b (a, b sunt obiecte din clasa complex) reprezinta "suma" a doua numere
complexe si este un numar complex. Astfel, supradefinirea operatorului + consta in
definrea unei functii cu numele: operator +
Supraincarcarea operatorului +
#include<iostream.h>
#include<conio.h>

class complex
{public:
float x,y;
void display()
{cout<<x<<"+"<<y<<"*i";
cout<<endl;
}

complex operator+(complex z)
{complex s;
s.x=x+z.x;
s.y=y+z.y;
return s;
}

};

void main()
{clrscr();
complex a,b,aux;
cout<<"primul numar "<<endl;
cout<<"partea reala ";
cin>>a.x;
cout<<"partea imaginara ";
cin>>a.y;
cout<<"al doilea numar "<<endl;
cout<<"partea reala ";
cin>>b.x;
cout<<"partea imaginara ";
cin>>b.y;
aux=a+b;
cout<<endl<<"Suma celor doua numere "<<endl;
aux.display();
cout<<endl;
cout<<"primul numar"<<endl;
a.display();
cout<<endl<<"al doilea numar "<<endl;
b.display();
a=a+b;
cout<<endl<<"primul numar trece in suma celor doua:"<<endl;
a.display();
cout<<"cel de al doilea numar ramane neschimbat "<<endl;
b.display();
getch();
}

Exercitii propuse:
Sa se supraincarce operatorii: -, *, / pentru clasa complex

Exemplul 2: Supraincarcarea operatorului unar ! se poate utiliza de exemplu pentru


a obtine conjugata unui numar complex:

complex operator!()
{complex c;
c.x=x;
c.y=-y;
return c;
}

Apelul se va face astfel:


b=!b;
Sau :
complex q;
q=!b;
Moştenirea multiplă – completări şi exemple

Exemplu:

class B1
{
//...
}
class B2
{
//...
}
class B3
{
//...
}
class D1 : B1
{
B2 b1;
B3 b2;
};

Din punct de vedere al structurii de date, clasa D1 este echivalenta cu forma:

class D1 : B1, B2, B3


{
//...
};

Din punct de vedere funcţional există totuşi diferenţe, cum ar fi, de exemplu, modul de
apel al metodelor claselor membre.

Există totuşi şi unele restricţii la moştenire:

1. O clasă derivată D nu poate moşteni direct de doua ori aceeaşi clasă B.


2. O clasă derivată D nu poate moşteni simultan o clasă B şi o altă D1, derivată tot din
"B". De asemenea, nu pot fi moştenite simultan două clase D1 şi D2 aflate în relaţia de
moştenire B ->... ->D1->...->D2.
Exemplu:
class B
{
//...
};
class C1 : B
{
//…
};
class C2 : C1
{
//…
};
class D1 : B, C2 //EROARE
{
//…
};
class D2 : C1, C2 //EROARE
{
//…
};
În schimb, este corectă o moştenire de genul :
class B
{
//...
};
class D1 : B
{
//…
};
class D2 : B
{
//…
};
class D : D1, D2
{
//…
};
3. Ordinea claselor de bază nu este indiferentă. Apelul constructorilor respectivi se va
face exact în ordinea enumerării numelor claselor de bază. Ca şi în cazul moştenirii
simple, apelul constructorilor tuturor claselor de bază se va efectua înaintea
eventualelor iniţializări de variabile membru.
CLASE VIRTUALE
Exemplu :
class B
{
//…
};
class D1 : B
{
//...
};
class D2 : B
{
//…
};
class M1 : D1,public D2
{
//…
};
class M2 : virtual D1, virtual public D2
{
//…
};
În urma moştenirii claselor D1 şi D2, clasa M1 va îngloba două obiecte de tipul
B (câte unul pentru fiecare din cele două clase de bază).
În practică, ar putea exista situaţii în care este de dorit moştenirea unui singur
obiect de tip B. Pentru aceasta se va proceda ca şi în cazul clasei M2: se vor prefixa
cele două clase de bază cu cuvântul cheie virtual. În urma acestei decizii, clasa M2 nu
va conţine decât O SINGURA INSTANTIERE A CLASEI "B".
Cui aparţine această unică instanţiere (lui D1 sau D2)? Instanţierea în cauză va
aparţine lui D1, adică primei clase virtuale (din lista claselor de bază) care a fost
derivată din B.

Reguli de apelare a constructorilor claselor de bază:


R1. Constructorii claselor de bază virtuale vor fi apelaţi INAINTEA celor
corespunzând unor clase de bază ne-virtuale.
R2. În caz că o clasă posedă mai multe clase de bază virtuale, constructorii lor vor fi
apelaţi în ordinea în care clasele au fost declarate în lista claselor de bază. Apoi vor fi
apelaţi (tot în ordinea declarării) constructorii claselor de bază ne-virtuale, şi abia în
cele din urmă constructorul clasei derivate.
R3. Chiar dacă în ierarhia claselor de bază există mai mult de o instanţiere a unei clase
de bază virtuale, constructorul respectiv va fi apelat doar o singură dată.

Alt exemplu moştenire- derivare clase:

Moştenirea de tip public a clasei de bază


Datele de tip protected în clasa de bază
sunt moştenite protected în clasa derivată,
deci pot fi accesate numai de funcţiile
membre şi friend ale clasei derivate.
Funcţia „functie” nu este nici una nici alta.
Prin enunţul:
class hard_disk : public floppy_disk
se indicã compilatorului urmãtoarele informaţii:
- se creeazã o clasã numitã “hard_disk”, care este derivatã (moştenitã) din clasa
“floppy_disk”;
- toţi membrii de tip “public” ai clasei “floppy_disk” vor fi moşteniţi (şi deci vor
putea fi folosiţi) ca “public” de cãtre clasa “hard_disk”;
- toţi membrii de tip “protected” ai unui obiect de tip “floppy_disk” vor putea fi
utilizaţi ca fiind “protected” în cadrul clasei “hard_disk”;

Tabelul următor sintetizează drepturile de accces a membrilor unei clase


derivate în funcţie de drepturile de accesare a membrilor clasei de bază şi valoarea lui
modificator_acces:

Obs1. Cuvântul cheie “protected” nu poate fi folosit ca modificator de acces în cadrul


unei relaţii de moştenire.

Obs2. Orice friend (funcţie sau clasă) a unei clase derivate are exact aceleaşi drepturi
şi posibilităţi de a accesa membrii clasei de bază ca oricare alt membru al clasei
derivate.
Alt exemplu moştenire- derivare clase:
Obs1. Cuvântul cheie const folosit dupa lista de parametri ai unei funcţii declară
funcţia membru ca şi funcţie “read-only” – funcţia respectivă nu modifică obiectul
pentru care este apelată.

Obs2. Funcţia calcul_plata () poate fi scrisă pentru diferitele tipuri de angajaţi :

Această tehnică este folosită de obicei atunci când se redefineşte o funcţie membru
într-o clasă derivată. Versiunea din clasa derivată apelează versiunea din clasa de bază
şi apoi efectuează celelalte operaţii necesare.

Conversii între clasa de bază şi clasa derivată


Limbajul C++ permite conversia implicită a unei instanţieri a clasei derivate
într-o instanţiere a clasei de baza.
De exemplu:

Temă: Să se implementeze o aplicaţie care ţine evidenţa studenţilor pe secţiuni: în


campus respectiv în oraş.
O posibilă structură este cea prezentată mai jos:

S-ar putea să vă placă și