poo-heritage-handout-cpp
poo-heritage-handout-cpp
poo-heritage-handout-cpp
class Personnage Après les notions d’encapsulation et d’abstraction, le troisième aspect essentiel de
string nom la « Programmation Orientée Objet » est la notion d’héritage.
int energie
int duree_vie
void rencontrer(Personnage&)
L’héritage représente la relation «est-un».
Il permet de créer des classes plus spécialisées,
appelées sous-classes, à partir de classes
class Voleur class Magicien class Guerrier
plus générales déjà existantes, appelées super-
void voler(Personnage&) Baguette baguette Arme arme
classes.
Arme arme
Héritage (3) Transitivité de l’héritage
Par transitivité, les instances d’une sous-classe possèdent :
L’héritage permet donc : I les attributs et méthodes (hors constructeurs/destructeur) de l’ensemble des
I d’expliciter des relations structurelles classes parentes (super-classe, super-super-classe, etc.)
et sémantiques entre classes
Enrichissement par héritage :
I de réduire les redondances de class Personnage
hiérarchie de classes
class Personnage {
// ...
};
// ...
class Guerrier : public Personnage {
public:
// constructeurs, etc.
private:
Arme arme;
};
Droit d’accès protected Accès protégé
Le niveau d’accès protégé correspond à une extension du niveau privé
Jusqu’à maintenant, l’accès aux membres (attributs et méthodes) d’une classe
permettant l’accès aux sous-classes.
pouvait être :
Exemple : class Personnage {
I soit public : visibilité totale à l’intérieur et à l’extérieur de la classe
// ...
(mot-clé public) protected:
I soit privé : visibilité uniquement à l’intérieur de la classe int energie;
};
(mot-clé private)
class Guerrier : public Personnage {
Un troisième type d’accès régit l’accès aux attributs/méthodes au sein d’une public:
hiérarchie de classes : // ...
void frapper(Personnage& le_pauvre) {
I l’accès protégé : assure la visibilité des membres d’une classe dans les if (energie > 0) {
classes de sa descendance // frapper le perso
}
Le mot clé est «protected». }
};
class Personnage
string nom
I Pour un personnage non-Guerrier : int energie
int duree_vie
void rencontrer(Personnage& le_perso) const { saluer(le_perso); }
void rencontrer(Personnage&)
I Pour un Guerrier
void rencontrer(Personnage& le_pauvre) const { frapper(le_pauvre); } class Voleur class Magicien class Guerrier
void voler(Personnage&) Baguette baguette Arme arme
void rencontrer(Personnage&)
Pour éviter des problèmes avec les hiérarchies de classes, dans un premier temps :
I Toujours déclarer au moins un constructeur
I Toujours faire l’appel à un constructeur de la super-classe
Ordre d’appel des constructeurs Ordre d’appel des destructeurs
Hiérarchie de classes Constructeurs Instance Les destructeurs sont toujours appelés dans l’ordre inverse (/symétrique) des
constructeurs.
Classe A A(...) 1
a1 m1(...) : a1(...),
a2 a2(...)
a1
Par exemple dans l’exemple précédent, lors de la destruction d’un C, on aura appel
m2(...) {} et exécution de :
a2
I C::˜C()
Classe B B(...) 2 I B::˜B()
b1 m3(...) : A(...),
m4(...) b1(...) b1 I A::˜A()
a1
{}
a2 (et dans cet ordre)
(puisque les constructeurs avaient été appelés dans l’ordre
Classe C C(...)
: B(...),
3 I A::A()
c1 m5(...)
c2 c1(...), c2(...) b1 c1 I B::B()
{} a1
instanciation : a2 c2 I C::C()
C mon_c(...); mon_c )
Exemple Exemple
Que se passe-t-il lorsqu’on invoque la fonction suivante ?
void afficher_largeur(Rectangle tmp)
Soit une autre définition possible (farfelue, mais possible !) de classe Rectangle : {
cout << "Largeur: " << tmp.getLargeur() << endl;
class Rectangle { }
private:
double* largeur; // aïe, un pointeur !
double* hauteur;
public:
Rectangle(double l, double h)
: largeur(new double(l)), hauteur(new double(h)) {}
~Rectangle() { delete largeur; delete hauteur; }
double getLargeur() const;
double getHauteur() const;
// ...
};
Exemple (2) Exemple (3)
Voilà ce qui se produit concrètement :
Avant la copie
hauteur de tmp
Après la destruction de tmp
Attention ! cette portion de mémoire est aussi utilisée par r dans un appel comme
largeur
afficher_largeur(r) ! r tmp
hauteur