C++ POO Lab Lucrarea 4: Diferență între versiuni

De la WikiLabs
Jump to navigationJump to search
 
(Nu s-au afișat 13 versiuni intermediare efectuate de același utilizator)
Linia 4: Linia 4:
* Ascunderea metodelor
* Ascunderea metodelor
* Polimorfismul
* Polimorfismul
* Metode virtuale
* Metode virtuale și suprascrierea metodelor
* Suprascrierea metodelor
* Metode pur virtuale și clase abstracte
* Metode pur virtuale


= Moștenirea =
= Moștenirea =
Linia 22: Linia 21:
* subclasă
* subclasă


În C++, moștenirea poate fi multiplă, în sensul ca o clasa 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ă).
În C++, moștenirea poate fi multiplă, în sensul 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:
Utilitatea moștenirii în programarea orientată pe obiecte este:
* reutilizarea codului existent fără modificarea acestuia;
* reutilizarea codului existent fără modificarea acestuia;
* extinderea a unei clase deja scrise, fără a fi necesara recompilarea ei;
* extinderea a unei clase deja scrise, fără a fi necesară recompilarea ei;
* utilizarea polimorfismului în timpul execuției, prin folosirea metodelor virtuale.
* utilizarea polimorfismului în timpul execuției, prin folosirea metodelor virtuale.


== Exemplu ==
== Exemplu ==
<syntaxhighlight lang="C++">
<syntaxhighlight lang="C++" line highlight="29">
#include <string>
#include <cstdio>


class Animal {
class Animal {
Linia 82: Linia 83:
|+ Accesul asupra membrilor moșteniți
|+ Accesul asupra membrilor moșteniți
|-
|-
! Protecția in clasa de baza !! Modif de acces utilizat în lista claselor de bază !! Dreptul de acces în clasa derivată
! 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 149: Linia 150:
Î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):
Î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="36-38">
<syntaxhighlight lang="C++" line highlight="39-41">
#include <string>
#include <string>
#include <cstdio>
#include <cstdio>
Linia 208: Linia 209:
</syntaxhighlight>
</syntaxhighlight>


= Polimorfism =
= Polimorfismul =


Polimorfismul '''parametric''' – mecanismul prin care putem defini o metodă cu același nume în aceași clasă, funcții care trebuie să difere prin numărul și/sau tipul parametrilor și/sau ordinea lor. Selecția funcției se realizează la '''compilare''' (legarea timpurie (early binding)). Se numește foarte des '''supraîncarcare''' pentru a evita confuzia cu polimorfismul de moștenire.
== 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:


Polimorfismul '''de moștenire''' – mecanismul prin care o metodă din clasa de bază este redefinită cu aceiași semnatură (return value, nume, parametri) în clasele derivate. Selecția funcției se va realiza '''la execuție''' (legare întârziată == late binding). Acesta este sensul în care se folosește cel mai des, și deci sensul in care va fi interpretat cuvantul '''polimorfism''' (daca nu este specificat de ce tip este)
<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>


'''Avantaje''':
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>.
* clasa de baza si pointerul de tip clasa de baza pot fi scrise in codul sursa al unei biblioteci compilate cu mult timp inaintea compilarii clasei derivate (eg. ani de zile)
* pointerul de tip clasa de baza poate referi orice obiect, de orice tip clasa derivata a clasei de baza (și isi poate schimba dinamic valoarea, la execuție)


<syntaxhighlight lang="C++">
== Exemplu ==
#include <iostream>
 
<syntaxhighlight lang="C++" line highlight="50">
#include <string>
#include <cstdio>


class Animal {
class Animal {
private:
     std::string mName;
     std::string mName;
     std::string mColor;
     std::string mColor;
     int mAge;
     int mAge;
protected:
protected:
     bool mHasFeathers; // accesibil din eventualele clase derivate
     bool mHasFeathers;


public:
public:
     virtual void makeSound() {
     Animal(const std::string & name, const std::string & color) : mName(name), mColor(color) {
        std::cout << "Animal " << mName << " makes a sound!" << std::endl;
     }
     }


     void setName(std::string str) {
     void makeSound() const {
         mName = str;
         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 252: Linia 252:
};
};


class Pisica : public Animal {
class Cat : public Animal {
    unsigned int mLives;
public:
public:
     Pisica() : Animal() { mHasFeathers = false; }
     Cat(const std::string & name, const std::string & color) : Animal(name, color), mLives(9) {
        mHasFeathers = false;
    }


    // cuvantul '''override''' este optional dar recomandat si produce o eroare de compilare daca metoda makeSound din Pisica nu suprascrie metoda
     void makeSound() const {
    // makeSound din Animal din cauza unei erori de sintaxa (eg. nume usor schimbat, alt return type, alti parametri / ordine sau tip al lor
         printf("Cat %s meows!\n", getName().c_str());
     void makeSound() override {
         std::cout << "Pisica " << getName() << " makes a sound!" << std::endl;
     }
     }
};


     // metoda din clasa Pisica ascunde implementarea din clasa Animal, dar nu o suprascrie polimorfic pentru ca setAge nu este virtuala in Animal
int main() {
    void setAge(int age) {
    Animal animal("Rex", "black");
        if (age >= 20) std::cout << "WARN: Longeviva pisica... sigur nu este o greseala?" << std::endl;
    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;


        Animal::setAge(age); // chemare setAge din clasa de baza Animal
public:
    Animal(const std::string & name, const std::string & color) : mName(name), mColor(color) {
     }
     }
};


int main() {
    virtual void makeSound() const {
        printf("Animal %s makes a sound!\n", mName.c_str());
    }


     std::cout << std::endl << "Demo Animal:" << std::endl;
    // this is a getter method
    Animal *pAnimal;
     std::string getName() const {
     Animal un_animal;
        return mName;
    un_animal.setName("TestoasaNinja");
     }


     un_animal.makeSound(); // metoda makeSound din clasa Animal
     // this is a setter method
     un_animal.setAge(99); // metoda setAge din clasa Animal
     void setAge(int age) {
        if(age > 0) {
            mAge = age;
        }
    }
};


     pAnimal = &un_animal;
class Cat : public Animal {
     pAnimal->makeSound(); // metoda makeSound din clasa Animal
     unsigned int mLives;
    pAnimal->setAge(99); // metoda setAge din clasa Animal
public:
     Cat(const std::string & name, const std::string & color) : Animal(name, color), mLives(9) {
        mHasFeathers = false;
    }


     std::cout << std::endl << "Demo Pisica:" << std::endl;
     void makeSound() const override {
    Pisica tom;
        printf("Cat %s meows!\n", getName().c_str());
    tom.setName("Tom");
     }
    tom.makeSound(); // metoda makeSound din clasa Pisica
};  
     tom.setAge(20); // metoda setAge din clasa Pisica


    Pisica *pPisica;
int main() {
    pPisica = &tom;
     Animal animal("Rex", "black");
     pPisica->makeSound(); // metoda makeSound din clasa Pisica
     Cat cat("Spot", "tabby");
     pPisica->setAge(20); // metoda setAge din clasa Pisica


     // demonstratie POLIMORFISM (de mostenire):
     Animal & animalRef = animal;
    std::cout << std::endl << "Demo Polimorfism (de mostenire):" << std::endl;
     Cat & catRef = cat;
     pAnimal = &tom; // pointer de tip clasa de baza Animal, care contine o adresa de tip clasa derivata, Pisica
     Animal & catRef2 = cat;
     pAnimal->makeSound(); // ATENTIE: medoda makeSound din clasa Pisica (makeSound ESTE virtuala in clasa de baza Animal)
    pAnimal->setAge(20); // ATENTIE: metoda setAge din clasa Animal (setAge NU este virtuala in clasa de baza Animal)


     // pPisica = &un_animal; // eroare de compilare: invalid conversion from ‘Animal*’ to ‘Pisica*’ [-fpermissive]
     animalRef.makeSound();
    catRef.makeSound();
    catRef2.makeSound();


     return 0;
     return 0;
Linia 307: Linia 355:
</syntaxhighlight>
</syntaxhighlight>


Secventa de mai sus afiseaza:
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>


<syntaxhighlight>
= Metode pur virtuale și clase abstracte =
Demo Animal:
Animal TestoasaNinja makes a sound!
Animal TestoasaNinja makes a sound!


Demo Pisica:
== Introducere ==
Pisica Tom makes a sound!
WARN: Longeviva pisica... sigur nu este o greseala?
Pisica Tom makes a sound!
WARN: Longeviva pisica... sigur nu este o greseala?


Demo Polimorfism (de mostenire):
Î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.
Pisica Tom makes a sound!
 
</syntaxhighlight>
<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>


= Functii/Metode virtuale pure, clase abstracte =
class Animal {
    std::string mName;
    std::string mColor;
    int mAge;
protected:
    bool mHasFeathers;


- o functie/metoda virtuala pura este o functie/metoda care nu are corp (body), nici macar gol
public:
    Animal(const std::string & name, const std::string & color) : mName(name), mColor(color) {
    }


virtual std::string toString() {}; // nu este o functie/metoda virtuala pura, are corp
    virtual void makeSound() const {
        printf("Animal %s makes a sound!\n", mName.c_str());
    }


virtual std::string toString() = 0; // este o functie/metoda virtuala pura
    // this is a getter method
    std::string getName() const {
        return mName;
    }


O clasa abstracta este o clasa care contine cel putin o metoda virtuala pura, si deci nu se poate instantia (nu exista obiecte de tip clasa
    virtual int getNumberOfLegs() const = 0;


Exemplu (pentru o clasa mostenita din clasa Animal de mai sus)
    // this is a setter method
    void setAge(int age) {
        if(age > 0) {
            mAge = age;
        }
    }
};


<syntaxhighlight lang="C++">
class Cat : public Animal {
class Pisica : public Animal {
    unsigned int mLives;
public:
public:
     Pisica() : Animal() // constructorul implicit Animal este chemat explicit
     Cat(const std::string & name, const std::string & color) : Animal(name, color), mLives(9) {
    {  
        mHasFeathers = false;
      mHasFeathers = false;
     }
     } // campul mHasFeathers este protected in Animal, deci poate fi accesat de Pisica
 
}; // nu uitati de ; la sfarsitul unei clase
    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

#include <string>
#include <cstdio>

class Animal {
    std::string mName;
    std::string mColor;
    int mAge;
protected:
    bool mHasFeathers;

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;
}

Î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:

  1. publică
  2. privată

Relativ la tipul de moștenire, mai jos este prezentat modul în care se preiau membrii din clasa de bază:

Accesul asupra membrilor moșteniți
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
Atenție: A doua diferență între 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:

Atenție: 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.

Apelul constructorului superclasei se face folosind aceeași sintaxă ca pentru inițializarea câmpurilor:

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) {
    }

    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;
    }
};

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):

#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) {
    }

    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 {
        printf("Cat %s meows!\n", getName().c_str());
    }
}; 

int main() {
    Animal animal("Rex", "black");
    animal.makeSound();
    Cat cat("Spot", "tabby");
    cat.makeSound();
    return 0;
}

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:

Atenție: Dacă o clasă 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

#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) {
    }

    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 {
        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;
}

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

#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;
}

Ieșire:

Animal Rex makes a sound!
Cat Spot meows!
Cat Spot meows!
Sfat: 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 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.

Atenție: Dacă o clasă conține cel puțin o metodă pur virtuală aceasta se numește clasă abstractă și nu poate fi instanțiată.

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

#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;
    }

    virtual int getNumberOfLegs() const = 0;

    // 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 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;
}