Diferență între revizuiri ale paginii „C++ POO Lab Lucrarea 4”
(Nu s-au afișat 20 de versiuni intermediare efectuate de același utilizator) | |||
Linia 4: | Linia 4: | ||
* Ascunderea metodelor | * Ascunderea metodelor | ||
* Polimorfismul | * Polimorfismul | ||
− | * Metode virtuale | + | * Metode virtuale și suprascrierea metodelor |
− | + | * Metode pur virtuale și clase abstracte | |
− | * Metode pur virtuale | ||
− | = | + | = Moștenirea = |
− | Moștenirea este mecanismul prin care o clasă preia structura ( | + | == Introducere == |
+ | Moștenirea este mecanismul prin care o clasă preia structura (câmpurile) și comportamentul (metodele) unei alte (sau mai multor) clase, la care poate adăuga alți membri specifici. | ||
− | Clasa | + | Clasa de la care se preiau membrii se numește: |
+ | * clasă de bază | ||
+ | * superclasă | ||
− | Clasa | + | Clasa nouă, care preia membrii de la clasa de bază se numește: |
+ | * clasă derivată | ||
+ | * clasă extinsă | ||
+ | * subclasă | ||
− | În C++, moștenirea poate fi multiplă, în sensul | + | În C++, moștenirea poate fi multiplă, în sensul că o clasă derivată poate moșteni mai multe clase de bază. Nu în toate limbajele există acest concept (în Java, de exemplu, există doar moștenire simplă - adică o clasă poate moșteni o singură altă clasă). |
− | + | Utilitatea moștenirii în programarea orientată pe obiecte este: | |
+ | * reutilizarea codului existent fără modificarea acestuia; | ||
+ | * extinderea a unei clase deja scrise, fără a fi necesară recompilarea ei; | ||
+ | * utilizarea polimorfismului în timpul execuției, prin folosirea metodelor virtuale. | ||
− | + | == Exemplu == | |
− | + | <syntaxhighlight lang="C++" line highlight="29"> | |
− | + | #include <string> | |
+ | #include <cstdio> | ||
− | + | class Animal { | |
+ | std::string mName; | ||
+ | std::string mColor; | ||
+ | int mAge; | ||
+ | protected: | ||
+ | bool mHasFeathers; | ||
− | |||
− | |||
public: | public: | ||
− | + | void makeSound() const { | |
− | { | + | printf("Animal %s makes a sound!\n", mName.c_str()); |
− | + | } | |
− | } // | + | |
− | } | + | // this is a getter method |
+ | std::string getName() const { | ||
+ | return mName; | ||
+ | } | ||
+ | |||
+ | // this is a setter method | ||
+ | void setAge(int age) { | ||
+ | if(age > 0) { | ||
+ | mAge = age; | ||
+ | } | ||
+ | } | ||
+ | }; | ||
+ | |||
+ | class Cat : public Animal { | ||
+ | unsigned int mLives; | ||
+ | public: | ||
+ | Cat() : mLives(9) { | ||
+ | mHasFeathers = false; | ||
+ | } | ||
+ | }; | ||
+ | |||
+ | int main() { | ||
+ | Cat cat; | ||
+ | cat.makeSound(); // makeSound este mostenita din clasa Animal | ||
+ | return 0; | ||
+ | } | ||
</syntaxhighlight> | </syntaxhighlight> | ||
− | + | În exemplul de mai sus, clasa <code>Cat</code> preia toți membrii din clasa <code>Animal</code> și adaugă un câmp <code>mLives</code> și un constructor fără argumente. S-a introdus, de asemenea, un nou modificator de acces, <code>protected</code>, care permite accesul la membrul respectiv atât din clasa curentă cât și din orice clasă derivată din clasa curentă, dar nu și din alte clase sau funcții. | |
+ | |||
+ | Se observă, de asemenea, că moștenirea a fost declarată publică. Există două timpuri de moșteniri: | ||
+ | # publică | ||
+ | # privată | ||
+ | |||
+ | Relativ la tipul de moștenire, mai jos este prezentat modul în care se preiau membrii din clasa de bază: | ||
{| class="wikitable" | {| class="wikitable" | ||
|+ Accesul asupra membrilor moșteniți | |+ Accesul asupra membrilor moșteniți | ||
|- | |- | ||
− | ! Protecția in clasa de baza !! | + | ! Protecția in clasa de baza !! Modificatorul de acces utilizat în lista claselor de bază !! Dreptul de acces în clasa derivată |
|- | |- | ||
| public || public || public | | public || public || public | ||
Linia 55: | Linia 98: | ||
|} | |} | ||
− | = | + | <div class="regula"><font color="#ff0000">Atenție: </font>A doua diferență între <code>class</code> și <code>struct</code> este că pentru prima, moștenirea implicită este '''private''' iar pentru a doua este '''public'''.</div> |
− | + | == Apelul constructorului superclasei == | |
− | + | Când se instanțiază o clasă derivată, constructorul acestei clase se ocupă de inițializarea câmpurilor definite în clasa derivată. Pentru a inițializa câmpurile din clasa de bază, este necesar să apelăm constructorul ei. Prin urmare: | |
− | + | <div class="regula"><font color="#ff0000">Atenție: </font>Obligatoriu, constructorul unei clase derivate trebuie să apeleze constructorul clasei de bază. Dacă acest apel nu se face explicit, compilatorul va introduce un apel către constructorul clasei de bază fără argumente. Dacă acesta lipsește, apelul către constructorul clasei de bază trebuie făcut explicit, altfel se va genera o eroare de compilare.</div> | |
− | |||
− | |||
− | + | Apelul constructorului superclasei se face folosind aceeași sintaxă ca pentru inițializarea câmpurilor: | |
− | |||
+ | <syntaxhighlight lang="C++" line highlight="32"> | ||
class Animal { | class Animal { | ||
− | |||
std::string mName; | std::string mName; | ||
std::string mColor; | std::string mColor; | ||
int mAge; | int mAge; | ||
+ | protected: | ||
+ | bool mHasFeathers; | ||
+ | |||
+ | public: | ||
+ | Animal(const std::string & name, const std::string & color) : mName(name), mColor(color) { | ||
+ | } | ||
+ | |||
+ | void makeSound() const { | ||
+ | printf("Animal %s makes a sound!\n", mName.c_str()); | ||
+ | } | ||
+ | |||
+ | // this is a getter method | ||
+ | std::string getName() const { | ||
+ | return mName; | ||
+ | } | ||
+ | |||
+ | // this is a setter method | ||
+ | void setAge(int age) { | ||
+ | if(age > 0) { | ||
+ | mAge = age; | ||
+ | } | ||
+ | } | ||
+ | }; | ||
+ | class Cat : public Animal { | ||
+ | unsigned int mLives; | ||
+ | public: | ||
+ | Cat(const std::string & name, const std::string & color) : Animal(name, color), mLives(9) { | ||
+ | mHasFeathers = false; | ||
+ | } | ||
+ | }; | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | = Ascunderea metodelor = | ||
+ | |||
+ | În anumite situații, există nevoia ca o anumită metodă care este moștenită din clasa de bază să se comporte altfel în clasa derivată. Putem realiza acest lucru reimplementând metoda respectivă, cu aceeași semnătură, în clasa derivată. Acest mecanism se numește ascundere (hiding): | ||
+ | |||
+ | <syntaxhighlight lang="C++" line highlight="39-41"> | ||
+ | #include <string> | ||
+ | #include <cstdio> | ||
+ | |||
+ | class Animal { | ||
+ | std::string mName; | ||
+ | std::string mColor; | ||
+ | int mAge; | ||
protected: | protected: | ||
− | bool mHasFeathers; | + | bool mHasFeathers; |
public: | public: | ||
− | + | Animal(const std::string & name, const std::string & color) : mName(name), mColor(color) { | |
− | |||
} | } | ||
− | void | + | void makeSound() const { |
− | mName | + | printf("Animal %s makes a sound!\n", mName.c_str()); |
} | } | ||
// this is a getter method | // this is a getter method | ||
− | std::string getName() { | + | std::string getName() const { |
return mName; | return mName; | ||
} | } | ||
Linia 99: | Linia 182: | ||
}; | }; | ||
− | class | + | class Cat : public Animal { |
+ | unsigned int mLives; | ||
+ | public: | ||
+ | Cat(const std::string & name, const std::string & color) : Animal(name, color), mLives(9) { | ||
+ | mHasFeathers = false; | ||
+ | } | ||
+ | |||
+ | void makeSound() const { | ||
+ | printf("Cat %s meows!\n", getName().c_str()); | ||
+ | } | ||
+ | }; | ||
+ | |||
+ | int main() { | ||
+ | Animal animal("Rex", "black"); | ||
+ | animal.makeSound(); | ||
+ | Cat cat("Spot", "tabby"); | ||
+ | cat.makeSound(); | ||
+ | return 0; | ||
+ | } | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | Ieșire: | ||
+ | <syntaxhighlight lang="text"> | ||
+ | Animal Rex makes a sound! | ||
+ | Cat Spot meows! | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | = Polimorfismul = | ||
+ | |||
+ | == Introducere == | ||
+ | Polimorfismul -- din greacă "poly" (mai multe) and "morphe" (forme) -- se referă la faptul că un obiect care este de tipul unei clase derivate, este în același timp și de tipul clasei de bază. Altfel spus: | ||
+ | |||
+ | <div class="regula"><font color="#ff0000">Atenție: </font>Dacă o clasă <code>B</code> extinde (direct sau indirect) o clasă <code>A</code>, atunci un obiect <code>b</code> de tipul <code>B</code> este în același timp și de tipul <code>A</code>.</div> | ||
+ | |||
+ | Folosind acest concept, care este implementat și în limbajul C++, se pot utiliza pointeri sau referințe de un tip <code>T</code> pentru a referi obiecte de tipuri derivate din <code>T</code>. | ||
+ | |||
+ | == Exemplu == | ||
+ | |||
+ | <syntaxhighlight lang="C++" line highlight="50"> | ||
+ | #include <string> | ||
+ | #include <cstdio> | ||
+ | |||
+ | class Animal { | ||
+ | std::string mName; | ||
+ | std::string mColor; | ||
+ | int mAge; | ||
+ | protected: | ||
+ | bool mHasFeathers; | ||
+ | |||
public: | public: | ||
− | + | Animal(const std::string & name, const std::string & color) : mName(name), mColor(color) { | |
+ | } | ||
− | + | void makeSound() const { | |
− | + | printf("Animal %s makes a sound!\n", mName.c_str()); | |
− | void makeSound() | + | } |
− | + | ||
+ | // this is a getter method | ||
+ | std::string getName() const { | ||
+ | return mName; | ||
} | } | ||
− | // | + | // this is a setter method |
void setAge(int age) { | void setAge(int age) { | ||
− | if (age >= | + | if(age > 0) { |
+ | mAge = age; | ||
+ | } | ||
+ | } | ||
+ | }; | ||
− | + | class Cat : public Animal { | |
+ | unsigned int mLives; | ||
+ | public: | ||
+ | Cat(const std::string & name, const std::string & color) : Animal(name, color), mLives(9) { | ||
+ | mHasFeathers = false; | ||
} | } | ||
− | }; | + | |
+ | void makeSound() const { | ||
+ | printf("Cat %s meows!\n", getName().c_str()); | ||
+ | } | ||
+ | }; | ||
int main() { | int main() { | ||
+ | Animal animal("Rex", "black"); | ||
+ | Cat cat("Spot", "tabby"); | ||
+ | |||
+ | Animal & animalRef = animal; | ||
+ | Cat & catRef = cat; | ||
+ | Animal & catRef2 = cat; | ||
+ | |||
+ | animalRef.makeSound(); | ||
+ | catRef.makeSound(); | ||
+ | catRef2.makeSound(); | ||
+ | |||
+ | return 0; | ||
+ | } | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | Ieșire: | ||
+ | <syntaxhighlight lang="text"> | ||
+ | Animal Rex makes a sound! | ||
+ | Cat Spot meows! | ||
+ | Animal Spot makes a sound! | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | Se vede că pe linia 50 de mai sus, avem o referință de tip <code>Animal</code> la un obiect de tip <code>Cat</code>. Acest lucru este posibil pentru că o <code>Cat</code> este în același timp și un <code>Animal</code> (deoarece <code>Cat</code> extinde <code>Animal</code>). Totuși, se poate observa din textul de ieșire că deși referințele <code>catRef</code> și <code>catRef2</code> sunt către același obiect (de tip <code>Cat</code>), apelul metodei <code>makeSound</code> se face în funcție de tipul referinței, nu de tipul obiectului. Există însă scenarii în care se dorește comportamentul invers, anume apelul metodei în funcție de tipul obiectului, nu de tipul referinței cu care este accesat. Astfel, se introduce conceptul de '''metodă virtuală'''. | ||
+ | |||
+ | = Metode virtuale și suprascrierea metodelor = | ||
+ | == Introducere == | ||
+ | |||
+ | Dacă o metodă este declarată virtuală, folosind cuvântul cheie <code>virtual</code> plasat în fața tipului întors, atunci reimplementarea acesteia în clasele derivate se numește '''suprascriere'''. Apelul unei metode virtuale folosind o referință de alt tip decât al clasei din care face parte obiectul, va apela metoda corespunzătoare obiectului în sine, nu cea care aparține tipului referinței. | ||
− | + | == Exemplu == | |
− | + | <syntaxhighlight lang="C++" line highlight="15,39"> | |
− | + | #include <string> | |
− | + | #include <cstdio> | |
− | + | class Animal { | |
− | + | std::string mName; | |
+ | std::string mColor; | ||
+ | int mAge; | ||
+ | protected: | ||
+ | bool mHasFeathers; | ||
+ | |||
+ | public: | ||
+ | Animal(const std::string & name, const std::string & color) : mName(name), mColor(color) { | ||
+ | } | ||
+ | |||
+ | virtual void makeSound() const { | ||
+ | printf("Animal %s makes a sound!\n", mName.c_str()); | ||
+ | } | ||
+ | |||
+ | // this is a getter method | ||
+ | std::string getName() const { | ||
+ | return mName; | ||
+ | } | ||
+ | |||
+ | // this is a setter method | ||
+ | void setAge(int age) { | ||
+ | if(age > 0) { | ||
+ | mAge = age; | ||
+ | } | ||
+ | } | ||
+ | }; | ||
− | + | class Cat : public Animal { | |
− | + | unsigned int mLives; | |
− | + | public: | |
+ | Cat(const std::string & name, const std::string & color) : Animal(name, color), mLives(9) { | ||
+ | mHasFeathers = false; | ||
+ | } | ||
− | + | void makeSound() const override { | |
− | + | printf("Cat %s meows!\n", getName().c_str()); | |
− | + | } | |
− | + | }; | |
− | |||
− | + | int main() { | |
− | + | Animal animal("Rex", "black"); | |
− | + | Cat cat("Spot", "tabby"); | |
− | |||
− | + | Animal & animalRef = animal; | |
− | + | Cat & catRef = cat; | |
− | + | Animal & catRef2 = cat; | |
− | |||
− | |||
− | + | animalRef.makeSound(); | |
+ | catRef.makeSound(); | ||
+ | catRef2.makeSound(); | ||
return 0; | return 0; | ||
Linia 154: | Linia 355: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
− | + | Ieșire: | |
+ | <syntaxhighlight lang="text"> | ||
+ | Animal Rex makes a sound! | ||
+ | Cat Spot meows! | ||
+ | Cat Spot meows! | ||
+ | </syntaxhighlight> | ||
− | < | + | <div class="sfat"><font color="darkgreen">Sfat:</font> Când se suprascrie o metodă într-o clasă derivată, este recomandat să plasați înainte de acolada deschisă care implementează metoda cuvântul cheie <code>override</code>. Deși acest lucru nu este obligatoriu, vă va avertiza printr-o eroare de compilare dacă suprascrierea nu se face corect (de exemplu dacă metoda din clasa de bază nu este declarată virtuală).</div> |
− | |||
− | |||
− | |||
− | + | = Metode pur virtuale și clase abstracte = | |
− | |||
− | |||
− | |||
− | |||
− | + | == Introducere == | |
− | + | ||
− | < | + | În anumite situații, o metodă virtuală nu poate avea o implementare în clasa de bază, pentru că nu este cunoscut modul în care aceasta se comportă. În această situație, metoda poate fi definită ca fiind '''pur virtuală''', adică o metodă fără implementare, dar care este totuși declarată în cadrul clasei. |
+ | |||
+ | <div class="regula"><font color="#ff0000">Atenție: </font>Dacă o clasă conține cel puțin o metodă pur virtuală aceasta se numește '''clasă abstractă''' și nu poate fi instanțiată.</div> | ||
+ | |||
+ | Dacă am crea un obiect de tipul respectiv, atunci nu am putea apela metoda pur virtuală pentru că implementarea aceasteia lipsește. Clasele abstracte se pot folosi numai pentru a fi extinse, și metodele pur virtuale trebuie suprascrise în clasele derivate. Dacă cel puțin una din metodele pur virtuale nu este suprascrisă, atunci clasa derivată este la rândul ei abstractă. | ||
+ | |||
+ | == Exemplu == | ||
+ | |||
+ | <syntaxhighlight lang="C++" line highlight="24,45"> | ||
+ | #include <string> | ||
+ | #include <cstdio> | ||
− | + | class Animal { | |
+ | std::string mName; | ||
+ | std::string mColor; | ||
+ | int mAge; | ||
+ | protected: | ||
+ | bool mHasFeathers; | ||
− | + | public: | |
+ | Animal(const std::string & name, const std::string & color) : mName(name), mColor(color) { | ||
+ | } | ||
− | virtual | + | virtual void makeSound() const { |
+ | printf("Animal %s makes a sound!\n", mName.c_str()); | ||
+ | } | ||
− | + | // this is a getter method | |
+ | std::string getName() const { | ||
+ | return mName; | ||
+ | } | ||
− | + | virtual int getNumberOfLegs() const = 0; | |
− | + | // this is a setter method | |
+ | void setAge(int age) { | ||
+ | if(age > 0) { | ||
+ | mAge = age; | ||
+ | } | ||
+ | } | ||
+ | }; | ||
− | + | class Cat : public Animal { | |
− | class | + | unsigned int mLives; |
public: | public: | ||
− | + | Cat(const std::string & name, const std::string & color) : Animal(name, color), mLives(9) { | |
− | + | mHasFeathers = false; | |
− | + | } | |
− | } | + | |
− | }; // | + | void makeSound() const override { |
+ | printf("Cat %s meows!\n", getName().c_str()); | ||
+ | } | ||
+ | |||
+ | int getNumberOfLegs() const override { | ||
+ | return 4; | ||
+ | } | ||
+ | }; | ||
+ | |||
+ | int main() { | ||
+ | Animal animal("Rex", "black"); // eroare de compilare, Animal este clasa abstracta | ||
+ | |||
+ | Cat cat("Spot", "tabby"); | ||
+ | Cat & catRef = cat; | ||
+ | Animal & catRef2 = cat; | ||
+ | |||
+ | catRef.makeSound(); | ||
+ | catRef2.makeSound(); | ||
+ | |||
+ | return 0; | ||
+ | } | ||
</syntaxhighlight> | </syntaxhighlight> |
Versiunea curentă din 12 aprilie 2022 22:01
Această lucrare are ca scop familiarizarea cu următoarele noțiuni:
- Moștenirea
- Ascunderea metodelor
- Polimorfismul
- Metode virtuale și suprascrierea metodelor
- Metode pur virtuale și clase abstracte
Moștenirea
Introducere
Moștenirea este mecanismul prin care o clasă preia structura (câmpurile) și comportamentul (metodele) unei alte (sau mai multor) clase, la care poate adăuga alți membri specifici.
Clasa de la care se preiau membrii se numește:
- clasă de bază
- superclasă
Clasa nouă, care preia membrii de la clasa de bază se numește:
- clasă derivată
- clasă extinsă
- subclasă
În C++, moștenirea poate fi multiplă, în sensul că o clasă derivată poate moșteni mai multe clase de bază. Nu în toate limbajele există acest concept (în Java, de exemplu, există doar moștenire simplă - adică o clasă poate moșteni o singură altă clasă).
Utilitatea moștenirii în programarea orientată pe obiecte este:
- reutilizarea codului existent fără modificarea acestuia;
- extinderea a unei clase deja scrise, fără a fi necesară recompilarea ei;
- utilizarea polimorfismului în timpul execuției, prin folosirea metodelor virtuale.
Exemplu
1#include <string>
2#include <cstdio>
3
4class Animal {
5 std::string mName;
6 std::string mColor;
7 int mAge;
8protected:
9 bool mHasFeathers;
10
11public:
12 void makeSound() const {
13 printf("Animal %s makes a sound!\n", mName.c_str());
14 }
15
16 // this is a getter method
17 std::string getName() const {
18 return mName;
19 }
20
21 // this is a setter method
22 void setAge(int age) {
23 if(age > 0) {
24 mAge = age;
25 }
26 }
27};
28
29class Cat : public Animal {
30 unsigned int mLives;
31public:
32 Cat() : mLives(9) {
33 mHasFeathers = false;
34 }
35};
36
37int main() {
38 Cat cat;
39 cat.makeSound(); // makeSound este mostenita din clasa Animal
40 return 0;
41}
În exemplul de mai sus, clasa Cat
preia toți membrii din clasa Animal
și adaugă un câmp mLives
și un constructor fără argumente. S-a introdus, de asemenea, un nou modificator de acces, protected
, care permite accesul la membrul respectiv atât din clasa curentă cât și din orice clasă derivată din clasa curentă, dar nu și din alte clase sau funcții.
Se observă, de asemenea, că moștenirea a fost declarată publică. Există două timpuri de moșteniri:
- publică
- privată
Relativ la tipul de moștenire, mai jos este prezentat modul în care se preiau membrii din clasa de bază:
Protecția in clasa de baza | Modificatorul de acces utilizat în lista claselor de bază | Dreptul de acces în clasa derivată |
---|---|---|
public | public | public |
private | public | inaccesibil |
protected | public | protected |
public | private | private |
private | private | inaccesibil |
protected | private | private |
class
și struct
este că pentru prima, moștenirea implicită este private iar pentru a doua este public.Apelul constructorului superclasei
Când se instanțiază o clasă derivată, constructorul acestei clase se ocupă de inițializarea câmpurilor definite în clasa derivată. Pentru a inițializa câmpurile din clasa de bază, este necesar să apelăm constructorul ei. Prin urmare:
Apelul constructorului superclasei se face folosind aceeași sintaxă ca pentru inițializarea câmpurilor:
1class Animal {
2 std::string mName;
3 std::string mColor;
4 int mAge;
5protected:
6 bool mHasFeathers;
7
8public:
9 Animal(const std::string & name, const std::string & color) : mName(name), mColor(color) {
10 }
11
12 void makeSound() const {
13 printf("Animal %s makes a sound!\n", mName.c_str());
14 }
15
16 // this is a getter method
17 std::string getName() const {
18 return mName;
19 }
20
21 // this is a setter method
22 void setAge(int age) {
23 if(age > 0) {
24 mAge = age;
25 }
26 }
27};
28
29class Cat : public Animal {
30 unsigned int mLives;
31public:
32 Cat(const std::string & name, const std::string & color) : Animal(name, color), mLives(9) {
33 mHasFeathers = false;
34 }
35};
Ascunderea metodelor
În anumite situații, există nevoia ca o anumită metodă care este moștenită din clasa de bază să se comporte altfel în clasa derivată. Putem realiza acest lucru reimplementând metoda respectivă, cu aceeași semnătură, în clasa derivată. Acest mecanism se numește ascundere (hiding):
1#include <string>
2#include <cstdio>
3
4class Animal {
5 std::string mName;
6 std::string mColor;
7 int mAge;
8protected:
9 bool mHasFeathers;
10
11public:
12 Animal(const std::string & name, const std::string & color) : mName(name), mColor(color) {
13 }
14
15 void makeSound() const {
16 printf("Animal %s makes a sound!\n", mName.c_str());
17 }
18
19 // this is a getter method
20 std::string getName() const {
21 return mName;
22 }
23
24 // this is a setter method
25 void setAge(int age) {
26 if(age > 0) {
27 mAge = age;
28 }
29 }
30};
31
32class Cat : public Animal {
33 unsigned int mLives;
34public:
35 Cat(const std::string & name, const std::string & color) : Animal(name, color), mLives(9) {
36 mHasFeathers = false;
37 }
38
39 void makeSound() const {
40 printf("Cat %s meows!\n", getName().c_str());
41 }
42};
43
44int main() {
45 Animal animal("Rex", "black");
46 animal.makeSound();
47 Cat cat("Spot", "tabby");
48 cat.makeSound();
49 return 0;
50}
Ieșire:
Animal Rex makes a sound!
Cat Spot meows!
Polimorfismul
Introducere
Polimorfismul -- din greacă "poly" (mai multe) and "morphe" (forme) -- se referă la faptul că un obiect care este de tipul unei clase derivate, este în același timp și de tipul clasei de bază. Altfel spus:
B
extinde (direct sau indirect) o clasă A
, atunci un obiect b
de tipul B
este în același timp și de tipul A
.Folosind acest concept, care este implementat și în limbajul C++, se pot utiliza pointeri sau referințe de un tip T
pentru a referi obiecte de tipuri derivate din T
.
Exemplu
1#include <string>
2#include <cstdio>
3
4class Animal {
5 std::string mName;
6 std::string mColor;
7 int mAge;
8protected:
9 bool mHasFeathers;
10
11public:
12 Animal(const std::string & name, const std::string & color) : mName(name), mColor(color) {
13 }
14
15 void makeSound() const {
16 printf("Animal %s makes a sound!\n", mName.c_str());
17 }
18
19 // this is a getter method
20 std::string getName() const {
21 return mName;
22 }
23
24 // this is a setter method
25 void setAge(int age) {
26 if(age > 0) {
27 mAge = age;
28 }
29 }
30};
31
32class Cat : public Animal {
33 unsigned int mLives;
34public:
35 Cat(const std::string & name, const std::string & color) : Animal(name, color), mLives(9) {
36 mHasFeathers = false;
37 }
38
39 void makeSound() const {
40 printf("Cat %s meows!\n", getName().c_str());
41 }
42};
43
44int main() {
45 Animal animal("Rex", "black");
46 Cat cat("Spot", "tabby");
47
48 Animal & animalRef = animal;
49 Cat & catRef = cat;
50 Animal & catRef2 = cat;
51
52 animalRef.makeSound();
53 catRef.makeSound();
54 catRef2.makeSound();
55
56 return 0;
57}
Ieșire:
Animal Rex makes a sound!
Cat Spot meows!
Animal Spot makes a sound!
Se vede că pe linia 50 de mai sus, avem o referință de tip Animal
la un obiect de tip Cat
. Acest lucru este posibil pentru că o Cat
este în același timp și un Animal
(deoarece Cat
extinde Animal
). Totuși, se poate observa din textul de ieșire că deși referințele catRef
și catRef2
sunt către același obiect (de tip Cat
), apelul metodei makeSound
se face în funcție de tipul referinței, nu de tipul obiectului. Există însă scenarii în care se dorește comportamentul invers, anume apelul metodei în funcție de tipul obiectului, nu de tipul referinței cu care este accesat. Astfel, se introduce conceptul de metodă virtuală.
Metode virtuale și suprascrierea metodelor
Introducere
Dacă o metodă este declarată virtuală, folosind cuvântul cheie virtual
plasat în fața tipului întors, atunci reimplementarea acesteia în clasele derivate se numește suprascriere. Apelul unei metode virtuale folosind o referință de alt tip decât al clasei din care face parte obiectul, va apela metoda corespunzătoare obiectului în sine, nu cea care aparține tipului referinței.
Exemplu
1#include <string>
2#include <cstdio>
3
4class Animal {
5 std::string mName;
6 std::string mColor;
7 int mAge;
8protected:
9 bool mHasFeathers;
10
11public:
12 Animal(const std::string & name, const std::string & color) : mName(name), mColor(color) {
13 }
14
15 virtual void makeSound() const {
16 printf("Animal %s makes a sound!\n", mName.c_str());
17 }
18
19 // this is a getter method
20 std::string getName() const {
21 return mName;
22 }
23
24 // this is a setter method
25 void setAge(int age) {
26 if(age > 0) {
27 mAge = age;
28 }
29 }
30};
31
32class Cat : public Animal {
33 unsigned int mLives;
34public:
35 Cat(const std::string & name, const std::string & color) : Animal(name, color), mLives(9) {
36 mHasFeathers = false;
37 }
38
39 void makeSound() const override {
40 printf("Cat %s meows!\n", getName().c_str());
41 }
42};
43
44int main() {
45 Animal animal("Rex", "black");
46 Cat cat("Spot", "tabby");
47
48 Animal & animalRef = animal;
49 Cat & catRef = cat;
50 Animal & catRef2 = cat;
51
52 animalRef.makeSound();
53 catRef.makeSound();
54 catRef2.makeSound();
55
56 return 0;
57}
Ieșire:
Animal Rex makes a sound!
Cat Spot meows!
Cat Spot meows!
override
. Deși acest lucru nu este obligatoriu, vă va avertiza printr-o eroare de compilare dacă suprascrierea nu se face corect (de exemplu dacă metoda din clasa de bază nu este declarată virtuală).Metode pur virtuale și clase abstracte
Introducere
În anumite situații, o metodă virtuală nu poate avea o implementare în clasa de bază, pentru că nu este cunoscut modul în care aceasta se comportă. În această situație, metoda poate fi definită ca fiind pur virtuală, adică o metodă fără implementare, dar care este totuși declarată în cadrul clasei.
Dacă am crea un obiect de tipul respectiv, atunci nu am putea apela metoda pur virtuală pentru că implementarea aceasteia lipsește. Clasele abstracte se pot folosi numai pentru a fi extinse, și metodele pur virtuale trebuie suprascrise în clasele derivate. Dacă cel puțin una din metodele pur virtuale nu este suprascrisă, atunci clasa derivată este la rândul ei abstractă.
Exemplu
1#include <string>
2#include <cstdio>
3
4class Animal {
5 std::string mName;
6 std::string mColor;
7 int mAge;
8protected:
9 bool mHasFeathers;
10
11public:
12 Animal(const std::string & name, const std::string & color) : mName(name), mColor(color) {
13 }
14
15 virtual void makeSound() const {
16 printf("Animal %s makes a sound!\n", mName.c_str());
17 }
18
19 // this is a getter method
20 std::string getName() const {
21 return mName;
22 }
23
24 virtual int getNumberOfLegs() const = 0;
25
26 // this is a setter method
27 void setAge(int age) {
28 if(age > 0) {
29 mAge = age;
30 }
31 }
32};
33
34class Cat : public Animal {
35 unsigned int mLives;
36public:
37 Cat(const std::string & name, const std::string & color) : Animal(name, color), mLives(9) {
38 mHasFeathers = false;
39 }
40
41 void makeSound() const override {
42 printf("Cat %s meows!\n", getName().c_str());
43 }
44
45 int getNumberOfLegs() const override {
46 return 4;
47 }
48};
49
50int main() {
51 Animal animal("Rex", "black"); // eroare de compilare, Animal este clasa abstracta
52
53 Cat cat("Spot", "tabby");
54 Cat & catRef = cat;
55 Animal & catRef2 = cat;
56
57 catRef.makeSound();
58 catRef2.makeSound();
59
60 return 0;
61}