>C++ 7< |
LHERITAGE
La P.O.O. permet de définir de nouvelles classes (classes filles) dérivées de classes de base (classes mères), avec de nouvelles potentialités. Ceci permettra à lutilisateur, à partir dune bibliothèque de classes donnée, de développer ses propres classes munies de fonctionnalités propres à lapplication.
On dit quune classe fille DERIVE dune ou de plusieurs classes mères.
La classe fille na pas accès aux données (privées) de la
classe mère.
II- DERIVATION DES FONCTIONS MEMBRES
Exemple (à tester) et exercice VII-1:
Lexemple ci-dessous illustre les mécanismes de base :
#include <iostream.h>
#include <conio.h>
class vecteur // classe mère
{float x,y;
public: void initialise(float,float);
void homotethie(float);
void affiche();
};
void vecteur::initialise(float abs =0.,float ord = 0.)
{x=abs;y=ord;}
void vecteur::homotethie(float val)
{x = x*val; y = y*val;}
void vecteur::affiche()
{cout<<"x = "<<x<<" y =
"<<y<<"\n";}
class vecteur3:public vecteur // classe fille
{float z;
public:
void initialise3(float,float,float);
void homotethie3(float);
void hauteur(float ha){z = ha;}
void affiche3(); } ;
void vecteur3::initialise3(float abs=0.,float ord=0.,float haut=0.)
{initialise(abs,ord); z = haut;} // fonction membre de la classe vecteur
void vecteur3::homotethie3(float val)
{homotethie(val); z = z*val;} // fonction membre de la classe vecteur
void vecteur3::affiche3()
{affiche();cout<<"z = "<<z<<"\n";} // fonction
membre de la classe vecteur
void main()
{vecteur3 v, w;
v.initialise3(5, 4, 3);v.affiche3(); // fonctions de la fille
w.initialise(8,2); w.hauteur(7); w.affiche(); // fonctions de la mère
cout<<"*******************************\n";
w.affiche3(); w.homotethie3(6);w.affiche3(); // fonctions de la fille
getch() ;
}
Lexemple ci-dessus présente une syntaxe assez lourde. Il serait plus simple, pour
lutilisateur, de donner aux fonctions membres de la classe fille, le même nom que
dans la classe mère, lorsque celles-ci jouent le même rôle (ici fonctions initialise et
homotethie).
Ceci est possible en utilisant la propriété de surdéfinition des fonctions membres.
Exemple (à tester) et exercice VII-2:
#include <iostream.h>
#include <conio.h>
class vecteur // classe mère
{float x,y;
public: void initialise(float,float);
void homotethie(float);
void affiche();
};
void vecteur::initialise(float abs =0.,float ord = 0.)
{x=abs;y=ord;}
void vecteur::homotethie(float val)
{x = x*val; y = y*val;}
void vecteur::affiche()
{cout<<"x = "<<x<<" y =
"<<y<<"\n";}
class vecteur3:public vecteur // classe fille
{float z;
public:
void initialise(float,float,float);
void homotethie(float);
void hauteur(float ha){z = ha;}
void affiche(); } ;
void vecteur3::initialise(float abs=0.,float ord=0.,float haut=0.)
{vecteur::initialise(abs,ord); z = haut;} // fonction membre de la classe vecteur
void vecteur3::homotethie(float val)
{vecteur::homotethie(val); // fonction membre de la classe vecteur
z = z*val;}
void vecteur3::affiche()
{vecteur::affiche(); // fonction membre de la classe vecteur
cout<<"z = "<<z<<"\n";}
void main()
{vecteur3 v, w;
v.initialise(5, 4, 3);v.affiche();
w.initialise(8,2); w.hauteur(7);
w.affiche();
cout<<"*******************************\n";
w.affiche();
w.homotethie(6);w.affiche();
getch() ;}
Exercice VII-3:
A partir de lexemple précédent, créer un projet. La classe mère sera
considérée comme une bibliothèque. Définir un fichier mere.h contenant les lignes
suivantes :
class vecteur // classe mère
{float x,y;
public: void initialise(float,float);
void homotethie(float);
void affiche();};
Le programme utilisateur contiendra la définition de la classe fille, et le programme
principal.
Exercice VII-4:
Dans le programme principal précédent, mettre en uvre des pointeurs de vecteur.
Remarque :
Lamitié nest pas transmissible: une fonction amie de la classe mère ne sera
amie que de la classe fille que si elle a été déclarée amie dans la classe fille.
III- DERIVATION DES CONSTRUCTEURS ET DU DESTRUCTEUR
On suppose la situation suivante :
class A class B : public A
{ ... { ...
public : public :
A ( ...... ) ; // constructeur B ( ...... ) ; // constructeur
~A ( ) ; // destructeur ~B( ) ; // destructeur
....... ........
} ; } ;
Si on déclare un objet B, seront exécutés
- Le constructeur de A, puis le constructeur de B,
- Le destructeur de B, puis le destructeur de A.
Exemple (à tester) et exercice VII-5:
#include <iostream.h>
#include <conio.h>
class vecteur // classe mère
{float x,y;
public: vecteur(); // constructeur
void affiche();
~vecteur(); // destructeur
};
vecteur::vecteur()
{x=1;y=2; cout<<"Constructeur mere\n";}
void vecteur::affiche() {cout<<"x = "<<x<<" y =
"<<y<<"\n";}
vecteur::~vecteur() {cout<<"Destructeur mere\n";}
class vecteur3:public vecteur // classe fille
{float z;
public:
vecteur3(); // Constructeur
void affiche();
~vecteur3();} ;
vecteur3::vecteur3()
{z = 3;
cout<<"Constructeur fille\n";}
void vecteur3::affiche()
{vecteur::affiche();
cout<<"z = "<<z<<"\n";}
vecteur3::~vecteur3()
{cout<<"Destructeur fille\n";}
void main()
{vecteur3 v;
v.affiche();
getch();}
Lorsque il faut passer des paramètres aux constructeurs, on a la possibilité de
spécifier au compilateur vers lequel des 2 constructeurs, les paramètres sont
destinés :
Exemple (à tester) et exercice VII-6:
Modifier le programme principal, pour tester les différentes possibilités de passage
darguments par défaut.
#include <iostream.h>
#include <conio.h>
// Héritage simple
class vecteur // classe mère
{float x,y;
public: vecteur(float,float); // constructeur
void affiche();
~vecteur(); // destructeur
};
vecteur::vecteur(float abs=1, float ord=2)
{x=abs;y=ord;
cout<<"Constructeur mere\n";}
void vecteur::affiche() {cout<<"x = "<<x<<" y =
"<<y<<"\n";}
vecteur::~vecteur() {cout<<"Destructeur mere\n";}
class vecteur3:public vecteur // classe fille
{float z;
public:
vecteur3(float, float, float); // Constructeur
void affiche();
~vecteur3();} ;
vecteur3::vecteur3(float abs=3, float ord=4, float haut=5):vecteur(abs,ord)
{z = haut; // les 2 1ers paramètres sont
cout<<"Constructeur fille\n";} // pour le constructeur de la classe mère
void vecteur3::affiche()
{vecteur::affiche();
cout<<"z = "<<z<<"\n";}
vecteur3::~vecteur3()
{cout<<"Destructeur fille\n";}
void main()
{vecteur u;
vecteur3 v, w(7,8,9);
u.affiche();v.affiche(); w.affiche();
getch();}
Cas du constructeur par recopie (***)
Rappel : Le constructeur par recopie est appelé dans 2 cas :
- Initialisation dun objet par un objet de même type :
vecteur a (3,2) ;
vecteur b = a ;
- Lorsquune fonction retourne un objet par valeur :
vecteur a, b ;
b = a.symetrique() ;
Dans le cas de lhéritage, on peut définir un constructeur par recopie pour la
classe fille, qui appelle le constructeur par recopie de la classe mère.
Exemple (à tester) et exercice VII-7:
#include <iostream.h>
#include <conio.h>
class vecteur // classe mère
{float x,y;
public: vecteur(float,float); // constructeur
vecteur(vecteur &); // constructeur par recopie
void affiche();
~vecteur(); // destructeur
};
vecteur::vecteur(float abs=1, float ord=2)
{x=abs;y=ord; cout<<"Constructeur mere\n";}
vecteur::vecteur(vecteur &v)
{x=v.x; y=v.y;
cout<<"Constructeur par recopie mere\n";}
void vecteur::affiche() {cout<<"x = "<<x<<" y =
"<<y<<"\n";}
vecteur::~vecteur() {cout<<"Destructeur mere\n";}
class vecteur3:public vecteur // classe fille
{float z;
public:
vecteur3(float, float, float); // Constructeur
vecteur3(vecteur3 &); // Constructeur par recopie
void affiche();
~vecteur3();} ;
vecteur3::vecteur3(float abs=3, float ord=4, float haut=5):vecteur(abs,ord)
{z = haut;
cout<<"Constructeur fille\n";}
vecteur3::vecteur3(vecteur3 &v):vecteur(v) // par recopie
{z = v.z; // appel au constructeurpar recopie de
cout<<"Constructeur par recopie fille\n";} // la classe vecteur
void vecteur3::affiche()
{vecteur::affiche();
cout<<"z = "<<z<<"\n";}
vecteur3::~vecteur3()
{cout<<"Destructeur fille\n";}
void main()
{vecteur3 v(5,6,7);
vecteur3 w = v;
v.affiche(); w.affiche();
getch();}
IV- LES MEMBRES PROTEGES
On peut donner à certaines données dune classe mère le statut
" protégé ".
Dans ce cas, les fonctions membres, et les fonctions amies de la classe fille auront
accès aux données de la classe mère :
class vecteur // classe mère
{
protected : float x,y;
public: vecteur(float,float); // constructeur
vecteur(vecteur &); // constructeur par recopie
void affiche();
~vecteur(); // destructeur
};
void vecteur3::affiche()
{cout<< "x = "<<x<<" y= "<<y<<
" z = "<<z<<"\n";}
La fonction affiche de la classe vecteur3 a accès aux données x et y de la classe
vecteur.
Cette possibilité viole le principe dencapsulation des données, on lutilise
pour simplifier le code généré.
V- EXERCICES RECAPITULATIFS
On dérive la classe chaîne de lexercice V-10:
class chaine
{int longueur; char *adr;
public:
chaine();chaine(char *);chaine(chaine &); //constructeurs
~chaine();
void operator=(chaine &);
int operator==(chaine);
chaine &operator+(chaine);
char &operator[](int);
void affiche();};
La classe dérivée se nomme chaine_T.
class chaine_T :public chaine
{int Type ;
float Val ;
public :
.......} ;
Type prendra 2 valeurs : 0 ou 1.
1 si la chaîne désigne un nombre, par exemple " 456 " ou
" 456.8 ", exploitable par atof la valeur retournée sera Val.
0 dans les autres cas, par exemple " BONJOUR " ou
" XFLR6 ".
Exercice VII-8: Prévoir pour chaine_T
- un constructeur de prototype chaine_T() ; qui initialise les 3 nombres à 0.
- un constructeur de prototype chaine_T(char *) ; qui initialise les 3 nombres à 0
ainsi que la chaîne de caractères.
- une fonction membre de prototype void affiche() qui appelle la fonction affiche de
chaine et qui affiche les valeurs des 3 nombres.
Exercice VII-9 (***): Prévoir un constructeur par recopie pour chaine_T , qui initialise
les 3 nombres à 0.
Exercice VII-10: Déclarer " protected " la donnée adr de la classe
chaîne. Ecrire une fonction membre pour chaine_T de prototype void calcul() qui donne les
bonnes valeurs à Type, Val.
VI- CONVERSIONS DE TYPE ENTRE CLASSES MERE ET FILLE
Règle : La conversion dun objet de la classe fille en un objet de la classe
mère est implicite.
La conversion dun objet de la classe mère en un objet de la classe fille est
interdite.
Autrement dit :
vecteur u ;
vecteur3 v ;
u = v ; // est autorisée, il y a conversion fictive de v en un vecteur
v = u; // est interdite
Exercice VII-11 : Tester ceci avec le programme VII-6
Avec des pointeurs :
vecteur *u ;
u = new vecteur ; // réservation de place, le constructeur de vecteur est exécuté
vecteur3 *v ;
v = new vecteur3 ; // réservation de place, le constructeur de vecteur3 est
exécuté
u = v ; // est autorisée, u vient pointer sur la même adresse que v
v = u; // est interdite
delete u ;
delete v ;
On obtient la configuration mémoire suivante :
Exemple (à tester) et exercice VII-12 :
Reprendre lexemple VII-11 avec le programme principal suivant :
void main()
{vecteur3 *u;
u = new vecteur3; u->affiche();
vecteur *v;
v = new vecteur; v->affiche();
v = u; v->affiche();
delete v ; delete u ;
getch();}
Conclusion : quelle est la fonction affiche exécutée lors du 2ème appel à
v->affiche() ?
Le compilateur C++ a-t-il " compris " que v ne pointait plus sur un
vecteur mais sur un vecteur3 ?
Grâce à la notion de fonction virtuelle on pourra, pendant lexécution du
programme, tenir compte du type de lobjet pointé, indépendamment de la
déclaration initiale.
VI- SURDEFINITION DE LOPERATEUR DAFFECTATION (***)
Rappels :
- Le C++ définit lopérateur = par défaut.
- Il est souhaitable de le surdéfinir via une fonction membre, lorsque la classe contient
des données de type pointeur.
A est la classe mère, B est la classe fille, on exécute les instructions
suivantes :
B x, y ;
y = x ;
Dans ce cas :
a) Si ni A, ni B nont surdéfini lopérateur = , le mécanisme
daffectation par défaut est mis en uvre.
b) Si = est surdéfini dans A mais pas dans B, la surdéfinition est mise en uvre
pour les données A de B, le mécanisme daffectation par défaut est mis en
uvre pour les données propres à B.
c) Si = est surdéfini dans B, cette surdéfinition doit prendre en charge la TOTALITE des
données (celles de A et celles de B).
Exemple (à tester) et exercice VII-13 :
#include <iostream.h>
#include <conio.h>
class vecteur
{float x,y;
public: vecteur(float,float);
void affiche();
void operator=(vecteur &); // surdefinition de l'operateur =
};
vecteur::vecteur(float abs=1, float ord=2)
{x=abs;y=ord;
cout<<"Constructeur mere\n";}
void vecteur::affiche()
{cout<<"x = "<<x<<" y =
"<<y<<"\n";}
void vecteur::operator=(vecteur &v)
{cout<<"operateur egalite mere\n"; x = v.x; y = v.y;}
class vecteur3:public vecteur // classe fille
{float z;
public:
vecteur3(float, float, float); // Constructeur
void operator=(vecteur3 &); // surdefinition de l'operateur =
void affiche();} ;
void vecteur3::operator=(vecteur3 &v)
{cout<<"operateur egalite fille\n";
vecteur *u, *w;
u = this;
w = &v;
*u = *w;
z = v.z;}
vecteur3::vecteur3(float abs=3, float ord=4, float haut=5):vecteur(abs,ord)
{z = haut; // les 2 1ers paramètres sont
cout<<"Constructeur fille\n";} // pour le constructeur de la classe mère
void vecteur3::affiche()
{vecteur::affiche();
cout<<"z = "<<z<<"\n";}
void main()
{vecteur3 v1(6,7,8),v2;
v2 = v1;
v2.affiche();
getch();
}
VII- LES FONCTIONS VIRTUELLES
Les fonctions membres de la classe mère peuvent être déclarées virtual.
Dans ce cas, on résout le problème invoqué dans le §V.
Exemple (à tester) et exercice VII-14 :
Reprendre lexercice VII-12 en déclarant la fonction affiche, virtual :
class vecteur // classe mère
{float x,y;
public:
vecteur(float,float); // constructeur
virtual void affiche();
~vecteur(); // destructeur
};
Quelle est la fonction affiche exécutée lors du 2ème appel à v->affiche() ?
Le programme a-t-il " compris " que v ne pointait plus sur un vecteur
mais sur un vecteur3 ?
Ici, le choix de la fonction à exécuter ne sest pas fait lors de la compilation,
mais dynamiquement lors de lexécution du programme.
Exemple (à tester) et exercice VII-15 :
Modifier les 2 classes de lexercice précédent comme ci-dessous :
class vecteur // classe mère
{float x,y;
public:
vecteur(float,float); // constructeur
virtual void affiche();
void message() {cout<<"Message du vecteur\n";}
~vecteur(); // destructeur
};
void vecteur::affiche()
{message();
cout<<"x = "<<x<<" y =
"<<y<<"\n";}
class vecteur3:public vecteur // classe fille
{float z;
public:
vecteur3(float, float, float); // Constructeur
void affiche();
void message(){cout<<"Message du vecteur3\n";}
~vecteur3();} ;
void vecteur3::affiche()
{message();
vecteur::affiche();
cout<<"z = "<<z<<"\n";}
Remarques :
- Un constructeur ne peut pas être virtuel,
- Un destructeur peut-être virtuel,
- La déclaration dune fonction membre virtuelle dans la classe mère, sera comprise
par toutes les classes descendantes (sur toutes les générations).
Exercice VII-16 :
Expérimenter le principe des fonctions virtuelles avec le destructeur de la classe
vecteur et conclure.
VIII- CORRIGE DES EXERCICES
Exercice VII-3: Le projet se nomme exvii_3, et contient les fichiers exvii_3.cpp et
mere.cpp ou bien mere.obj.
Fichier exvii_3.cpp:
#include <iostream.h>
#include <conio.h>
#include "c:\bc5\cours_cpp\teach_cp\chap7\mere.h"
// Construction d'un projet à partir d'une classe mère disponible
class vecteur3:public vecteur // classe fille
{float z;
public:
void initialise(float,float,float);
void homotethie(float);
void hauteur(float ha){z = ha;}
void affiche(); } ;
void vecteur3::initialise(float abs=0.,float ord=0.,float haut=0.)
{vecteur::initialise(abs,ord); z = haut;} // fonction membre de la classe vecteur
void vecteur3::homotethie(float val)
{vecteur::homotethie(val); // fonction membre de la classe vecteur
z = z*val;}
void vecteur3::affiche()
{vecteur::affiche(); // fonction membre de la classe vecteur
cout<<"z = "<<z<<"\n";}
void main()
{vecteur3 v, w;
v.initialise(5, 4, 3);v.affiche();
w.initialise(8,2); w.hauteur(7);
w.affiche();
cout<<"*******************************\n";
w.affiche();
w.homotethie(6);w.affiche();}
Fichier mere.cpp:
#include <iostream.h>
#include <conio.h>
#include "c:\bc5\cours_cpp\teach_cp\chap7\mere.h"
void vecteur::initialise(float abs =0.,float ord = 0.)
{x=abs;y=ord;}
void vecteur::homotethie(float val)
{x = x*val; y = y*val;}
void vecteur::affiche()
{cout<<"x = "<<x<<" y =
"<<y<<"\n";}
Exercice VII-4 :
Programme principal :
void main()
{vecteur3 v, *w;
w = new vecteur3;
v.initialise(5, 4, 3);v.affiche();
w->initialise(8,2);w->hauteur(7);
w->affiche();
cout<<"*******************************\n";
w->affiche();
w->homotethie(6);w->affiche();
delete w;}
Exercice VII-8 : Seuls la classe chaine_T et le programme principal sont listés
class chaine_T :public chaine
{int Type;
float Val ;
public :
chaine_T(); // constructeurs
chaine_T(char *);
void affiche();
};
chaine_T::chaine_T():chaine() // constructeurs
{Type=0;Val=0;} // dans les 2 cas le constructeur
// correspondant de chaine est appelé
chaine_T::chaine_T(char *texte):chaine(texte)
{Type=0;Val=0;}
void chaine_T::affiche()
{chaine::affiche();
cout<<"Type= "<<Type<<" Val=
"<<Val<<"\n";}
void main()
{chaine a("Bonjour ");
chaine_T b("Coucou "),c;
cout<<"a: ";a.affiche();
cout<<"b: ";b.affiche();
cout<<"c: ";c.affiche();
getch();}
Exercice VII-9 : Seules les modifications ont été listées
class chaine_T :public chaine
{int Type ;
float Val ;
public :
chaine_T(); // constructeurs
chaine_T(char *);
chaine_T(chaine_T &ch); // constructeur par recopie
void affiche();
};
puis :
chaine_T::chaine_T(chaine_T &ch):chaine(ch) //constructeur par recopie
{Type=0;Val=0;} // il appelle le constructeur
// par recopie de chaine
void main()
{chaine_T b("Coucou ");
chaine_T c = b;
cout<<"b: ";b.affiche();
cout<<"c: ";c.affiche();
getch();}
Exercice VII-10 : Seules les modifications ont été listées
void chaine_T::calcul()
{
Val = atof(adr); // possible car donnée "protected"
if(Val!=0)Type = 1;
}
puis :
void main()
{chaine_T b("Coucou "),c("123"), d("45.9"),
e("XFLR6");
b.calcul();c.calcul();d.calcul();e.calcul();
b.affiche();c.affiche();d.affiche();e.affiche();
getch();}
Exercice VII-11 : Seules les modifications ont été listées
void main()
{vecteur u;
vecteur3 v(7,8,9);
u.affiche();v.affiche();
u=v; u.affiche();
getch();}