-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #13 from tgpetrica/add_2022-05-05
initial commit
- Loading branch information
Showing
2 changed files
with
164 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,157 @@ | ||
## Mostenire | ||
O clasa C++ poate mosteni una sau mai multe clase. | ||
```cpp | ||
class A | ||
{...}; | ||
|
||
class B: public A | ||
{...}; | ||
``` | ||
In momentul in care dorim ca o clasa sa mosteneasca una sau mai multe clase dupa numele ei din declaratie punem `:` si o lista cu numele claselor mostenite. In fata fiecarui nume de clasa mostenita poate sa apara cuvantul rezervat `virtual` si poate aparea unul dintre specificatorii `public`, `protected` sau `private`. | ||
```cpp | ||
class Deriv:[virtual][public/protected/private]Baza1,... | ||
{...}; | ||
``` | ||
Un specificator de acces pus in fata clasei mostenite restrange mai mult sau mai putin accesul la membri clasei de baza. Daca nu se pune niciun specificator de acces implicit se considera mostenire `private`. De regula, mostenirea se face insa `public`, asa cum este exemplu lui `B` mosteneste `A` (de mai sus). | ||
Urmatorul tabel ilustreaza vizibilitatea membrilor in functie de specificatorul de acces folosit la derivare. | ||
|
||
| clasa de baza | specificator derivare | rezultat | | ||
| ------------- | --------------------- | -------- | | ||
| public | public | public | | ||
| public | protected | protected | | ||
| public | private | protected | | ||
| protected | ... | protected | | ||
| private | ... | private | | ||
|
||
```cpp | ||
B b; | ||
B* p1 = new B; | ||
A* p2 = new B; | ||
B* p3 = new A; | ||
``` | ||
Primele doua instantieri sunt valabile pentru orice clasa (nu are legatura cu mostenirea). In prima si a doua instantiere se creeaza in memorie doua obiecte. Un obiect instanta `B` si un obiect instanta `A`. Obiectul instanta `A` poarta denumirea de **subobiect instantei B**. Din obiectul instantei `B` avem acces la toti membri `public` si `protected` din `A`. In general, cand instantiem o clasa se creeaza cate un subobiect instanta fiecarei clase mostenite direct sau indirect. A treia instantiere este posibila deoarece in urma apelului `new B` se creeaza obiect instanta `B` si subobiect instanta `A` , iar `p2` va fi pointer catre subobiect. | ||
```cpp | ||
delete p1; | ||
``` | ||
La instantierea unei clase dintr-o ierarhie intai se creeaza subobiectele de sus in jos in ierarhie, iar ultimul se creeaza obiectul instanta clasei respective. Distrugerea obiectelor si subobiectelor se face in ordine inversa (de jos in sus) in ierarhie. In instantierea a doua (pentru pointerul `p1`) se creeaza subobiectul instantei `A`, apoi obiectul instantei `B`, iar la `delete p1` se sterge mai intai obiectul instantei `B`, apoi subobiectul instantei `A`. | ||
A patra instantiere nu este posibila deoarece `p3` este pointer catre B si nu se creeaza nicio instanta `B`, atunci `p3` nu are pe cine sa retina. | ||
|
||
## Functii virtuale | ||
```cpp | ||
class A | ||
{ | ||
public: | ||
virtual void f() | ||
{ | ||
cout << "f din A" << endl; | ||
} | ||
}; | ||
|
||
class B:public A | ||
{ | ||
void f() | ||
{ | ||
cout << " f din B" << endl; | ||
} | ||
}; | ||
``` | ||
Dupa cum am mai spus, in cadrul unei ierarhii, o functie avand aceiasi semnatura (nume si parametri - ca tip si numar) poate fi imprementata diferit pe nivele diferite. Mecanismul care sta in spate poarta denumirea de **polimorfism**. Avem posibilitatea ca functia `f` sa nu o rescriem in clasa `B`. In aceasta situatie, functia `f` din `A` putea fi apelata de un obiect din `B`, fiind o clasa mostenita; am ales sa suprascriem functia `f` in `B`. Un obiect instanta `B` are posibilitatea de a apela doua functii `f`: cea din `B` si cea mostenita din `A`. Pentru a apela functia din `A` folosim operatorul de rezolutie. | ||
```cpp | ||
B b; //static / stack | ||
b.f(); | ||
/* | ||
* Apelul b.f() se restrange asupra functiei care este cea | ||
* mai apropiata de obiectul b, adica in cazul nostru, se | ||
* apeleaza f din B. | ||
*/ | ||
B* p1 = new B; //dinamic / heap | ||
p1->f(); | ||
/* | ||
* p1->f() se restrange asupra functiei f tot din B | ||
*/ | ||
A* p3 = new B; | ||
p3->f(); | ||
/* | ||
* Apelul pe->f() se restrange asupra functiei f din A, p3 | ||
* fiind pointer catre A. Din cauza ca avem clasa B | ||
* specializata, extinsa, varianta particularizata sau | ||
* imbunatatita ar fi mai bine ca apelul p3->f() sa se duca | ||
* la functia f din B. Pentru a realiza acest lucru, | ||
* declaram functia f din A ca fiind virtuala punand | ||
* cuvantul rezervat virtual in fata functiei. Daca facem | ||
* specificarea virtual in fata functiei f din A, apelul | ||
* p3->f() se duce in clasa B. | ||
``` | ||
O functie poate fi declarata virtuala numai daca este membra unei clase (adica functiile externe nu pot fi virtuale). Intr-o ierarhie, in momentul in care o functie este declarata virtuala, ea va fi virtuala si in clasele derivate. In exemplul nostru, `f` din `B` este automat virtuala. | ||
### Mecanismul de apelare a functiilor virtuale | ||
Adresele functiilor virtuale la compilare sunt introduse intr-o tabela speciala denumita VMT (virtual memory table). Cand este apelata o functie virtuala este cautata adresa functiei cea mai apropiata in VMT, luandu-se de acolo pointerul catre functia indentificata. | ||
Mecanismul de apel al unei functii virtuale in consecinta mai lent decat apelul unei functii membre obisnuite nevirtuale, deoarece inainte de apelul efectiv de functie se face cautare in VMT. | ||
Al treilea tip de instantiere devine util in urmatoarea situatie: dorim ca intr-un vector sa punem laolalta atat obiect instanta `A` cat si obiecte instanta `B`, deoarece obiectele instantei `B` sunt si obiecte de tip `A`. | ||
De exemplu, presupuneam ca avem ierarhia: | ||
- vehicul | ||
- motocicleta | ||
- autoturism | ||
- bicicleta | ||
Presupunem ca avem nevoie sa retinem intr-un vector mai multe vehicule, unele bicicleta, altele motocicleta si alte autoturism. Pentru a putea implementa un astfel de vector avem obligatoriu nevoie sa cream un vector de pointeri catre vehicul. | ||
```cpp | ||
int n; | ||
vehicul** v; | ||
v = new vehicul*[n]; | ||
v[0] = new bicicleta; | ||
v[1] = new motocicleta; | ||
v[2] = ... | ||
``` | ||
In exemplul de mai sus, `v[i]` fiind pointer catre vehicul el retine adresa subobiectului vehicul pentru fiecare instantiere. Daca metodele din clasa vehicul le punem ca fiind virtuale orice apel de astfel de functie din `v[i]` se va duce in clasa de jos (bicicleta, motocicleta, autoturism). | ||
```cpp | ||
cout << v[i].nrRoti() << endl; | ||
// se va duce intr-una dintre clasele derivate daca | ||
// functia nrRoti este privata in functia vehicul si | ||
// suprascrisa in clasele derivate. | ||
``` | ||
Constructorii unei clase nu pot fi declarati virtuali, in schimb, destructorul clasei poate fi virtual. | ||
## Destructori virtuali | ||
Consideram din nou ierarhia `B`, `B` mosteneste `A`. | ||
Scrierea `delete p1;` distruge ce se afla la adresa `p1` adica un obiect instanta `B` si declanseaza in sus distrugerea subobiectelor, adica, in situatia noastra, distruge subobiectul instanta `A`. | ||
In situatia in care scriem `delete p3;` se distruge obiectul de la adresa `p3` care este, de fapt, subobiect instanta `A`, iar obiectul instanta `B` ramane in memorie, rezultand memory-leak. Pentru ca apelul `delete p3;` sa distruga si obiectul instanta `B` trebuie sa scriem desctructor in clasa `A` si sa il punem virtual, in clasa `A` adaugam `virtual~A{}`. | ||
Apelul `delete p3;` nu mai este un apel simplu de destructor din `A`, ci se declanseaza un mecanism de identificare a destructorilor ce trebuie apelati folosind VMT. | ||
### Apelul unui constructor dintr-o clasa derivata | ||
Cand instantiem o clasa derivata dintr-o alta clasa, am vazut ca, o data cu obiectul creat se creeaza si cate un subobiect pentru clase mostenite direct sau indirect. Creearea oricarui obiect se face apeland la un constructor. In consecinta, atunci cand se apeleaza un constructor dintr-o clasa derivata, trebuie sa specificam cum se apeleaza cate un constructor pentru fiecare clasa mostenita direct. | ||
In exemplul de mai sus: `B` mosteneste `A` nescriind niciun constructor in clasele `A` si `B` pentru fiecare dintre aceste clase s-a creat automat constructorul implicit fara parametri. Atat pentru obiectul creat, cat si pentru subobiect s-a apelat constructorul implicit corespunzator. | ||
```cpp | ||
class A | ||
{ | ||
public: | ||
A(){} | ||
A(int x){cout << x << endl;} | ||
virtual void f(){...}; | ||
virtual A{} | ||
}; | ||
void t(int n){cout << n << endl;} | ||
class B:public A | ||
{ | ||
int m_b; | ||
public: | ||
B(){} | ||
B(int x):A(x+1) | ||
{ | ||
cout << x << endl; | ||
} | ||
void f(){...}; | ||
} | ||
``` | ||
Pentru primul constructor al clasei `B` nespecificand apel de constructor din `A` se cauta implicit in `A` sa se apeleze constructorul fara parametri. La al doilea constructor din `B` am aratat ca in `A` se apeleaza al doilea constructor caruia ii trimite ca parametru valoarea `x+1`. De regula, parametri de apel ai constructorului din clase mostenita se specifica pentru constructorul din clasa derivata folosind parametri de apel ai constructorului din clasa derivata. In exemplul nostru, constructorul `A` a fost apelat cu valoarea `x+1` unde `x` este parametru al lui `B`. | ||
Campul `m_b` se poate initializa ori in corpul constructorului sau in lista de apel de dupa `:` pusa dupa constructorul `B`. Tot in aceasta lista de apel pentru constructor pot fi apelate si functii. | ||
```cpp | ||
B b(7); | ||
// se afiseaza: | ||
8 //A(x-1) | ||
6 //t(x-1) | ||
7 //B(x) | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters