Moştenire
Moştenirea
este un mecanism prin care se pot clase noi (numite derivate) prin extinderea
unor clase existente (numite clase de bază). Clasa derivată este, de
obicei, o specializare a clasei de bază; între cele două trebuie să existe o
relaţie de tip este un/o.
Exemple: Student este o Persoană, Vector este
o Colecţie, Autoturism
este un Vehicul …
Sintaxa
utilizată pentru crearea unei clase derivate este
class ClasaDerivata : tip_derivare ClasaDeBaza {……};.
Clasa derivată
va moşteni funcţiile şi valorile membre de la clasa de
bază. Tipul de derivare determină vizibilitatea membrilor
moşteniţi în clasa derivată. Efectele tipului de derivare sunt
sintetizate în tabelul următor:
Tip derivare ® |
public |
protected |
private |
Ż Vizibilitate iniţială |
|||
public |
public |
protected |
private |
protected |
protected |
protected |
private |
private |
inaccesibil |
inaccesibil |
inaccesibil |
Restricţiile
impuse membrilor moşteniţi prin derivarea de tip protected şi private pot fi relaxate prin
mecanismul de publicizare. Acesta implică citarea în secţiunea
publică a clasei derivate a membrilor care se doresc a fi
publicizaţi.
Este permisă
convertirea unei instanţe a clasei derivate într-o instanţă a
clasei de bază. În felul acesta se pot apela metodele publice ale clasei
de bază indiferent de tipul de derivare utilizat.
Exemple:
// Clasa de
baza
class Baza
{
public:
void Metoda1() {}
void Metoda2() {}
int atribut1;
protected:
void Metoda3() {}
int atribut2;
private:
void Metoda4() {}
int atribut3;
};
// Clasa
derivata public (contine toate
// metodele si
atributele clasei Baza)
class Derivata1 : public Baza
{
public:
void Metoda5()
{
// poate accesa metodele
publice
// si protected ale clasei de
baza
Metoda1();
Metoda3();
// dar nu are acces la
membrii
// privati ai acesteia
// Metoda4(); => eroare la compilare
// atribut3++; => eroare
la compilare
}
};
// Clasa
derivata protected
class Derivata2 : protected
Baza
{
public:
void Metoda5()
{
// poate accesa metodele
publice
// si protected ale clasei de
baza
Metoda1();
Metoda3();
// dar nu are acces la
membrii
// privati ai acesteia
// Metoda3(); => eroare la compilare
// atribut3++; => eroare
la compilare
}
// publicizare a metodei
using Baza::Metoda2;
};
void main()
{
Derivata1 d1;
// sunt accesibile toti membrii
// publici ai clasei de baza si
// ai clasei derivate
d1.atribut1++;
d1.Metoda1(); // OK: metoda
mostenita public din clasa de baza
d1.Metoda2(); // OK: metoda
mostenita public din clasa de baza
// d1.Metoda3(); => eroare la
compilare (accesare functie protected)
d1.Metoda5(); // OK: metoda publica
in clasa derivata
Derivata2 d2;
// sunt accesibili doar membrii
publici ai clasei derivate
// d2.atribut1++; => eroare:
atributul devine protected in clasa derivata
// d2.Metoda1(); => eroare:
metoda devine protected in clasa derivata
d1.Metoda2(); // OK: metoda este
publicizata in clasa derivata
d2.Metoda5(); // OK: metoda publica
in clasa derivata
// convertire la clasa de baza
Baza b =
(Baza)d2;
b.Metoda1(); // acum este permisa
accesarea metodei
//
publice 1 din clasa de baza
}
Iniţializarea
instanţelor claselor care compun o ierarhie se face începând de la clasa
de bază si continuă în ordine cu clasele derivate. În cazul în care
clasa de bază nu are un constructor implicit, clasa derivată este
obligată să iniţializeze clasa de bază folosind
constructorul acesteia şi lista de iniţializare.
Exemplu:
// Clasa de baza Persoana: Contine datele
referitoare la o persoana.
class Persoana
{
public:
// constructor
Persoana(int cod, char* nume)
{
// copiem datele primite ca parametri
_cod =
cod;
strcpy(_nume,
nume);
}
// functii de acces
int GetCod()
const { return
_cod; }
const char* GetNume()
const { return
_nume; }
private:
// date membre
int _cod;
char _nume[DIM_MAX_NUME];
};
// Clasa derivata Student : contine toate datele
// si functiile din clasa Persoana la care adauga
// datele regeritoare la grupa si an de studiu
class Student : public
Persoana
{
public:
// constructorul clasei Student este obligat sa
// utilizeze constructorul clasei de baza Persoana
// pentru initializarea atributelor mostenite
Student(int cod, char* nume, int an, int grupa)
:
Persoana(cod, nume), _an(an), _grupa(grupa){}
// adaugam functiile de acces pentru atributele adaugate
int GetAn()
{ return _an; }
int GetGrupa() { return
_grupa; }
private:
int _an, _grupa;
};
În general,
clasele derivate moştenesc toate funcţiile şi atributele membre
din clasa de bază. Există însă câteva excepţii:
Clasele
derivate pot suprascrie funcţiile clasei de bază după cum se
poate vedea din exemplul următor:
#include <iostream>
using namespace std;
// Clasa de
baza
class Baza
{
public:
void Functie()
{
cout << "[Baza] Functie()" <<
endl;
}
};
// Clasa
derivata
class Derivata : public Baza
{
// Suprascrierea functiei
void Functie()
{
cout << "[Derivata] Functie()" <<
endl;
}
};
// Functia
primeste ca parametru o
// referinta
la o instanta a clasei
// de baza sau
a clasei derivate
void Apelator(Baza& obiect)
{
obiect.Functie();
}
void main()
{
Baza b;
Derivata d;
Apelator(b);
Apelator(d);
}
Implicit,
funcţia care va fi apelata de funcţia Apelator este fixată la compilare. Din acest motiv, în ambele
cazuri va fi apelată metoda Functie
din clasa de bază. Limbajul C++ conţine şi un mecanism de legare
întârziată care permite selectarea funcţiei care trebuie
executată la momentul execuţiei în funcţie de tipul obiectului
primit ca parametru. Acest mecanism poate fi utilizat prin folosirea cuvântului
cheie virtual la declararea metodei.
În exemplul prezentat clasa de baza va deveni:
// Clasa de baza
class Baza
{
public:
virtual
void Functie()
{
cout
<< "[Baza] Functie()" << endl;
}
};
După
efectuarea modificării va fi apelată versiunea corectă a
funcţiei.
Mecanismul de legare întârziată
funcţionează numai când apelul este făcut printr-un pointer sau
printr-o referinţă la obiect.
În cazul în care nu
se doreşte specificarea unei implementări în clasa de bază se
pot folosi funcţii virtuale pure declarate cu sintaxa virtual
prototip_funcţie =0. Clasele care includ astfel de funcţii se
numesc clase abstracte şi nu pot fi instanţiate direct. Acestea se
folosesc atunci când nu există nici o implementare posibilă pentru
clasa de bază.
Exemplu de
utilizare clasa abstractă pentru calculul salariului în condiţiile în
care există două tipuri de angajaţi:
#include <iostream>
using namespace std;
// clasa
abstracta Anagajat
class Angajat
{
public:
// functie virtuala pura pentru
calculul salariului
virtual double Salariu() = 0;
};
// clsa
concreta AngajatOra (pentru angajatii platiti per ora)
class AngajatOra : public
Angajat
{
public:
// constructorul
AngajatOra(double salariuOra, int numarOre)
: _salariuOra(salariuOra), _numarOre(numarOre) {}
// suprascrierea functiei de calcul
pentru salariu
double Salariu()
{
return _salariuOra *
_numarOre;
}
private:
double _salariuOra;
int _numarOre;
};
// clsa
concreta AngajatContact (pentru angajatii cu salariu fix)
class AngajatContact : public
Angajat
{
public:
// constructorul
AngajatContact(double
salariuLunar)
: _salariuLunar(salariuLunar){}
// suprascrierea functiei de calcul
pentru salariu
double Salariu()
{
return _salariuLunar;
}
private:
double _salariuLunar;
};
// Folosirea
clasei abstracte pentru calculul
// si afisarea
salariului
void AfisareSalariu(Angajat& angajat)
{
// folosim functia virtuala pentru
calculul
// salariului (indiferent de tipul
de anagajat)
cout << "Salariu: " << angajat.Salariu()
<< " lei." << endl;
}
void main()
{
// construire obiecte
AngajatOra anOra(40, 120);
AngajatContact anContr(3900);
// apelare functie de calcul salariu
// pentru tipuri diferite de
angajati
AfisareSalariu(anOra);
AfisareSalariu(anContr);
}
Se observă
faptul că funcţia de afişare pentru salarii poate primi o
referinţă la un obiect din orice clasă derivată din Angajat. Astfel, putem adăuga noi
tipuri de angajaţi fără să fie necesare modificări în
codul deja existent.
Să se
construiască o bibliotecă pentru rezolvarea ecuaţiilor neliniare
pe un interval dat şi să se exemplifice utilizarea acesteia pentru
două funcţii diferite.
Rezolvare:
Vom construi o clasă abstractă care conţine interfaţa corespunzătoare unei funcţii reale de un parametru real. Pentru Rezolvarea ecuaţiei vom implementa o funcţie care va primi ca parametrii o referinţă la un obiect reprezentând ecuaţia de rezolvat şi marginile intervalului.
Codul sursă
al bibliotecii este:
#ifndef ECUATIE_H
#define ECUATIE_H
// precizia
const double
eps = 0.000001;
// clasa abstracta care reprezinta o functie
// de un parametru real
class Functie
{
public:
// operatorul () - functie virtuala pura
virtual double operator() (double) =
0;
};
// functie pentru gasirea solutiei folosind
// metota bisectiei succesive
double Bisectie(Functie& f, double x1, double x2)
{
double x;
do
{
// obtinem mijlocul intervalului
x = (x1 +
x2) / 2;
// retinem intervalul cu solutia
if (f(x1) * f(x) < 0)
x2
= x;
else
x1
= x;
}
// continuam pana cand intervalul se
// incadreaza in precizia stabilita
while (x2 - x1 > eps);
// intoarcem solutia
return x;
}
#endif //ECUATIE_H
Pentru
exemplificarea modului de utilizare vom construi două funcţii (o
funcţie liniară şi una neliniară). Construirea
funcţiilor se face prin derivare din clasa abstractă Funcţie şi suprascrierea
operatorului (). După construirea claselor derivate corespunzătoare
ecuaţiilor de rezolvat este utilizată funcţia din
bibliotecă pentru obţinerea soluţiei.
Codul
sursă al programului este:
#include <iostream>
#include <iomanip>
#include <cmath>
using namespace
std;
// includem biblioteca pentru rezolvarea
ecuatiilor
#include "ecuatie.h"
// definim ecuatiile de rezolvat prin derivare
class FunctieLiniara : public Functie
{
public:
double operator() (double x)
{
return 2*x + 3;
}
};
class FunctieNeliniara : public Functie
{
public:
double operator() (double x)
{
return log(x) + 3;
}
};
void main()
{
// construim functiile
FunctieLiniara
f1;
FunctieNeliniara
f2;
// setam modul de afisare
cout <<
fixed << setprecision(4);
// afisam solutiile
cout <<
"Solutia pentru functia liniara este: " << Bisectie(f1, -20,
20)
<<
endl;
cout <<
"Solutia pentru functia neliniara este: " << Bisectie(f2, 0, 5)
<<
endl;
}