Vous êtes sur la page 1sur 43

2019/2020

ESATIC
Ecole Spérieure Africaine des TIC

UP Informatique

[PROGRAMMATION ORIENTEE OBJET


EN C++]
Ce document est un support de cours d’initiation à la programmation orientée objet appliquée avec
le langage C++. Il présente le paradigme de la POO et ses concepts de base, à travers les définitions,
l’énoncé des principes, la syntaxe de programmation et leur application par des exemples.
Table de matières :
INTRODUCTION A LA PROGRAMMATION C++ ..................................................................................... 2
I. AVANTAGES DU LANGAGE C++ : ..................................................................................................... 2
II. STRUCTURE GENERALE D’UN PROGRAMME C++ : ..................................................................... 2
III. DESCRIPTION DES ELEMENTS D’UN PROGRAMME : .............................................................. 3
IV. LES STRUCTURES DE CONTROLES: ............................................................................................. 5
LA PROGRAMMATION ORIENTEE OBJET (POO).................................................................................. 8
I. AVANTAGES DE LA POO : .................................................................................................................. 8
II. CONCEPTS CARACTERISTIQUES DE LA POO : .............................................................................. 8
III. LES CLASSES EN C++ : ...................................................................................................................11
IV. AUTRES OPTIONS DE LA PROGRAMMATION ORIENTEE OBJET : ......................................12
CONSTRUCTEUR/DESTRUCTEURS ET LES SURCHARGES ..............................................................16
I. LES CONSTRUCTEURS ......................................................................................................................17
II. LES DESTRUCTEURS..........................................................................................................................20
III. LA SURCHARGE ..............................................................................................................................24
HERITAGE .......................................................................................................................................................27
I. PRESENATION DE L’HERITAGE : ....................................................................................................27
II. LES MEMBRES D’UNE SOUS-CLASSE ............................................................................................29
III. MASQUAGE DANS UNE HIERARCHIE :......................................................................................31
IV. CONSTRUCTEURS ET HERITAGE : ..............................................................................................32
V. ATTRIBUTS ET METHODES ABSTRAITS: ......................................................................................34

1
INTRODUCTION A LA PROGRAMMATION C++
Objectifs :
1. Apprendre à programmer de façon plus concise et modulaire mais aussi plus fiable et élégante
en exploitant les concepts orientés-objets
2. Approfondir quelques notions de structuration des données (algorithmique, généricité.)
Historique du langage C++ :
- Extension objet du langage C, mis au point par Ritchie et Kernighan dans les années 70’.
- Développé initialement par Bjarne Stroustrup (bell labs) dans les années 80’.
- Normalisé ISO (International Organization for Standardization) en 1998 puis 2003.

Origines de la POO
• La programmation orientée objet surgit dans les années '80
• Logiciels de plus en plus complexes, travail en équipe
• Contrôler la complexité des logiciels
• Code réutilisable pour contenir les coûts du développement
• Contenir les coûts de la maintenance :
– Ajout de nouvelles fonctionnalités
– Modification de fonctionnalités existantes
– portage sur d'autres plate-formes ou environnements
• Coordonner et répartir le travail de développement
• Modularité

I. AVANTAGES DU LANGAGE C++ :


Le langage C++ est un langage orienté-objet, compilé, fortement typé :

C++ = C + typage fort + objet


Tout ce qui est syntaxiquement valide en C, l’est aussi en C++.
 Un des langages objets les plus utilisés,
 Un langage compilé, ce qui permet la réalisation d’applications efficaces (disponibilité
d’excellents compilateurs open-source (GNU),
 Un typage fort, ce qui permet au compilateur d’effectuer de nombreuses vérifications lors de
la compilation (d’où moins de « bugs »)
 Un langage disponible sur pratiquement toutes les plateformes,
 Similarité syntaxique et facilité d’interfaçage avec C.

II. STRUCTURE GENERALE D’UN PROGRAMME C++ :


La structure très générale d’un programme C++ est la suivante :
#include <des-librairies-utiles>

Using namespace std ; //on y reviendra
(Déclaration d’objets globaux) [à éviter]
Déclaration des fonctions utiles [recommandé]
Int main() // ou int main(int argc, char **argv)
{
Corps du programme principal
}
2
Exemple 1 : Hello.cc

#include
En C++,<iostream>
les fichiers sources se terminent par
l’extension
Using namespace std.cpp
.cc ou ; au lieu de .c en langage C.
Main() {
Cout << "hello world !" << endl ;
}

Exemple 2 : résoudre l’équation du second degré suivante (x2+bx+c=0)


#include <iostream>
#include <cmath>
using namespace std ;
int main() {
double b(0.0) ;
double c(0.0) ;
double delta(0.0) ;
cin >> b >> c ;
delta=b*b-4*c ;
if (delta <0.0) {
cout<< "pas de solution réelle" << endl ;
} else if (delta ==0.0) {
cout << "une solution unique :" <<-b/2.0 << endl ;
} else {
cout << "deux solutions :" <<(-b-sqrt(delta)) /2.0 << " et " <<(-
b+sqrt(delta)) /2.0 <<endl ;
}
}

III. DESCRIPTION DES ELEMENTS D’UN PROGRAMME :


1. Les variables :
Pour être utilisée dans un programme C++, une donnée doit être stockée quelque part en mémoire.
C’est un objet informatique qui pourra être manipulé par le programme.
La zone mémoire utilisée est alors décrite au niveau du langage de programmation par une variable.
En C++, une variable doit être déclarée avant d’être utilisée.
La syntaxe de déclaration d’une variable est : type identificateur ;
Exemple : int val ; double delta ; char a ;
2. Données modifiables/non modifiables :
Par défaut les variables sont modifiables.

3
Comme en C, si on ne veut pas qu’une variable soit modifiable : la définir comme constante par
l’indication du mot réservé const lors de la déclaration :
Exemples : int const couple(2); double const g(9.81); const int model = 90 ; const int v[ ] = {1, 2, 3,
4} ; // v[i] est une constante
Une fois déclarée, une constante ne pourra donc plus être modifiée par le programme (toute tentative
de modification produira un message d’erreur lors de la compilation).
3. Affectation :
L’opération d’application affecte une valeur à une variable.

Identificateur = valeur ; (Exemple : i=3 ;)


4. Initialisation :
En même temps qu’elle est déclarée, une variable peut être initialisée. C’est-à-dire qu’on lui donne
une première valeur avant même toute utilisation.
La syntaxe de la déclaration ou l’initialisation d’une variable est :
type identificateur (valeur_d’initialisation) ;

Exemples :
int val(2) ;
double pi(3.1415) ;
char c(‘a’) ;
int j(2*i+5) ;

5. Types élémentaires :
Les entiers : int, short int, long int, unsigned int, …
Les réels : double (double précision)/float (simple précision),
Les caractères : char,
Les booléens : bool.

6. Opérateurs et expressions :
o Les opérateurs arithmétiques : *, /, %, +, -
o C++ fournit un certain nombre de notations abrégées pour des affectations
particulières.
 x=x+y peut aussi s’écrire x+=y (idem pour -, *, / et %)
 x=x+1 peut aussi s’écrire ++x (idem pour -. Exemple : --x)
o Les opérateurs de comparaison : ==, !=, <, >, <=, >=.
o Les opérateurs logiques : && (et), || (ou), ^ (ou exclusif), ! (négation) .

4
7. Entrée – sortie de base :
écriture :
lecture : #include <iostream>
#include <iostream> Using namespace std ;
Using namespace std ; int main() {
int main() { // ecriture
int x ; int x ;
double y ; double y ;
cin >> x >>y ; //lecture cout <<" un entier : "<< n <<"un
return 0 ; double :<<(12.5+3.0) << endl ;
} return 0 ;
}

Endl provoque un saut de ligne.

IV. LES STRUCTURES DE CONTROLES:

1. Valider une donnée saisie au clavier

#include <iostream>
using namespace std;

int main()
{
int i;
cout <<"Tapez une valeur entre 0 et 20 bornes incluses : ";
cin>>i;
while (i<0 || i>20)
{
cout <<"ERREUR ! ";
cout <<"Tapez une valeur entre 0 et 20 bornes incluses : ";
cin >> i;
}
return 0;
}
 Explications
o On saisit une première fois la valeur de i par un "cin".
o Tant que la valeur saisie ne sera pas comprise entre 0 et 20 (bornes incluses), on
affiche ERREUR et on redemande une nouvelle valeur.
o La condition « la valeur de i n'est pas comprise entre 0 et 20 » s’écrit ( i<0 || i>20 ).
o Ne pas confondre le ET et le OU logique !

2. Branchement conditionnel (if … else) ;


Exemple :
Ecrire un programme qui demande à l'utilisateur de taper un entier et qui affiche GAGNE si l'entier
est entre 56 et 78 bornes incluses PERDU sinon.
#include<iostream>
using namespace std;
int main()
{
int a;

5
cout<<"Tapez un entier : "; cin>>a;
if ((a>=56)&&(a<=78)) cout<<"GAGNE"<<endl; else cout<<"PERDU"<<endl;

return 0;
}

3. Les boucles (for et while) ;


Exemple 1:
Ecrire un programme qui affiche tous les entiers de 8 jusqu’à 23 (bornes incluses) en utilisant un for.
#include<iostream>
using namespace std;

int main()
{
int i;
for (i=8;i<=23;i++) cout<<i<<endl;

return 0;
}

Exemple 2:
Ecrire un programme qui demande à l'utilisateur de taper un entier N et qui calcule la somme des
cubes de 5^3 à N^3.

#include<iostream>
using namespace std;
int main()
{
int N,s=0,i;

cout<<"Tapez la valeur de N : "; cin>>N;

for (i=5 ; i<=N ; i++)


s = s + i*i*i;

cout<<"La somme vaut : "<<s<<endl;


return 0;
}

4. Les structures à choix multiples avec l’imbrication de (if … else) ;


Exemple :
Ecrire un programme qui demande à l’utilisateur de taper 10 entiers et qui affiche le plus petit de ces
entiers.
#include<iostream>
using namespace std;
int main()
{
int i, ppt, x;

for(i=0; i<10; i++)


{
cout<<"Tapez un entier : "; cin>>x;
if(i==0) ppt=x;
6
else if(x<ppt) ppt=x;
}
cout<<"Le plus petit vaut : "<< ppt <<endl;

return 0;
}

5. Les structures à choix multiples avec switch (break, continue)


Exemple :
Ecrire un programme qui permet de faire des opérations sur un entier (valeur initiale à 0). Le
programme affiche la valeur de l'entier puis affiche le menu suivant :
1. Ajouter 1
2. Multiplier par 2
3. Soustraire 4
4. Quitter

#include<iostream>
using namespace std;

int main()
{
int x=0,choix;

do {
cout<<"x vaut "<<x<<endl;
cout<<"1 : Ajouter 1"<<endl;
cout<<"2 : Multiplier par 2"<<endl;
cout<<"3 : Soustraire 4"<<endl;
cout<<"4 : Quitter"<<endl;
cout<<"Votre choix : "; cin>>choix;

switch(choix)
{
case 1: x++; break;
case 2: x=x*2; break;
case 3: x=x-4; break;
}
}while(choix!=4);

cout<<"La valeur finale de x vaut : "<<x<<endl;

return 0;
}

7
LA PROGRAMMATION ORIENTEE OBJET (POO)
Objectifs pédagogiques:
- Introduire les notions d’encapsulation, d’abstraction, d’objets, d’instances et de classes
- Classes en C++
- Variables d’instance
- Méthodes d’instance
- Encapsulation et interface (public : et private :)
- L’objet this et le masquage

Exemples de langages orientées objets :


 Smalltalk (Xerox, PARC, 1980)
 Objective –C (Apple, Brad Cox, 1986)
 Python (open source, Guido Van Rossum, 1990)
 Java (Sun Micro system, 1995)
 C # (Microsoft, 2001)
Par ailleurs, la plupart des langages non-objets admettent une extension objet. Tels que : Caml,
PERL, PHP, etc …

I. Avantages de la POO :
 Façon naturelle de modéliser les données et traitements intervenant dans un programme.
 Modularité : le programme est composé d’un ensemble d’entités (types d’objets) aux rôles
bien déterminés.
 Réutilisabilité : chaque type d’objets peut être réutilisé, sa sémantique peut être étendue par le
biais de l’héritage.
 Polymorphisme : un même code peut être s’appliquer à des types d’objets différents.
 Abstraction : la représentation des données est découplée de leur utilisation. De plus, des
règles précises d’utilisation des données peuvent être imposées.
 D’énormes librairies déjà écrites (Java, C#). Ce qui permet une efficacité dans la conception
des programmes, concision du codage, meilleures fiabilité et maintenabilité des programmes
Un logiciel fiable, souple et facile à maintenir est difficile à otenir, mais sera plus facile avec la POO.

II. Concepts caractéristiques de la POO :

1. Notion d’encapsulation :
Le principe d’encapsulation consiste à regrouper dans le même objet informatique
« concept », données et traitements qui lui sont spécifiques :
o Les données incluses dans un objet seront appelées les attributs de cet objet,
o Les traitements/fonctions défini(e)s dans un objet seront appelées les méthodes de cet
objet.
Les objets sont définis par leurs attributs et leurs méthodes : OBJET= attributs + méthodes

2. Notion d’abstraction des données :


Pour être véritablement intéressant, un objet doit permettre un certain degré d’abstraction.
Le processus d’abstraction consiste à identifier pour un ensemble d’éléments :
 Des caractéristiques communes à tous les éléments
 Des mécanismes communs à tous les éléments

8
Ceci permet de faire une description générique de l’ensemble considéré : se focaliser sur
l’essentiel, cacher les détails.
Les données sont manipulées à l’aide de méthodes qui cachent leur représentation interne :
o On peut imposer que ces méthodes vérifient l’intégralité des données (fiabilité).
o L’utilisation d’une donnée ne dépend plus des choix faits pour sa représentation
(facilité de maintenance).
o Des données différentes peuvent être manipulées de manière identique (concision)

3. Notions de classe, d’objet et d’instance :


En programmation orientée objet :
- Le résultat du processus d’abstraction s’appelle une classe. Classe = catégorie d’objets
- Une classe définit un type (au sens du langage de programmation).
- Une réalisation particulière d’une classe s’appelle une instance. Une instance = un objet

EXEMPLE : Rectangle
 La notion d’objet rectangle n’est intéressante que si l’on peut lui associer des propriétés
et/ou mécanismes généraux. On s’intéresse ainsi aux propriétés et mécanismes valables
pour l’ensemble des rectangles et non pas pour un rectangle particulier.
 Les notions de largeur et hauteur sont des propriétés générales des rectangles (attributs).
 Le mécanisme permettant de calculer la surface d’un rectangle (surface=largeur * hauteur)
est commun à tous les rectangles (méthodes).
 Un intérêt de l’encapsulation est que cela permet d’abstraire :
En plus du regroupement des données et des traitements relatifs à une entité, l’encapsulation permet
en effet de définir deux niveaux de perception :
 Le niveau externe : partie visible par les programmeurs-utilisateurs de l’objet (c’est-à-dire le
prototype des méthodes et attributs hors de l’objet. C’est l’interface.
 Le niveau interne : détails d’implémentation de l’objet :
o Méthodes et attributs accessibles uniquement depuis l’intérieur de l’objet(ou
l’intérieur d’objets similaires).
o Définition de l’ensemble des méthodes de l’objet. C’est le corps de l’objet.

 Rectangle : classe, objet (instance) :

Rectangle
Classe (type abstrait)
Attributs : Méthodes :
largeur dessine()
hauteur surface()

Existence conceptuelle
(Écriture du programme)

Existence concrète
(Exécution du programme)

h : 15 h : 30
h : 30
l : 26 l : 30
l : 12
instance2 instance3
instance1 9
4. Pourquoi abstraire/encapsuler ?
L’intérêt de regrouper les traitements et les données conceptuellement reliés est de permettre une
meilleure visibilité et une meilleure cohérence au programme, d’offrir une plus grande modularité
L’intérêt de séparer les niveaux interne et externe est de donner un cadre plus rigoureux à
l’utilisation des objets utilisés dans un programme.
Les objets ne peuvent être utilisés qu’au travers de leurs interfaces (niveau externe) et donc les
éventuelles modifications de la structure interne restent invisibles à l’extérieur. Même idée que la
séparation prototype/définition d’une fonction.
Règle du masquage : les attributs d’un objet ne doivent pas être accessibles depuis l’extérieur, mais
uniquement par des méthodes.
5. Encapsulation et interface :
Il y a donc deux facettes à l’encapsulation :
1er : regroupement de tout ce qui caractérise l’objet : données (attributs) et traitements (méthodes).
2e : isolement et dissimulation des détails d’implémentation interface.
Interface = ce que le programmeur-utilisateur (hors de l’objet) peut utiliser.
Tout ceci permet la concentration sur les attributs/méthodes concernant l’objet (abstraction).

Exemple : l’interface d’une voiture.


- Volant, accélérateur, pédale de freins, etc.
- Tout ce qu’il faut savoir pour conduire (mais pas la réparer ni comprendre comment
ça marche).
- L’interface ne change pas, même si l’on change de moteur … et même si on change
de voiture (dans certaine mesure) : abstraction de la notion voiture (en tant qu’objet
à conduire)

Tout ce qui n’est pas nécessaire de connaitre à l’extérieur d’un objet devrait être dans le corps de
l’objet et identifié par le mot-clé « private : ». À l’inverse, l’interface qui est accessible de
l’extérieur, se déclare avec le mot-clé « public : ».
NB : si aucun droit n’est précisé, c’est private par défaut.

Exemple :
class Rectangle {
public : // accessible partout
double surface () const {…}
private : // accessible uniquement ici
double hauteur ;
double largeur ;

};

10
III. LES CLASSES EN C++ :
En C++, une classe se déclare par le mot-clé class.
Exemple :
class Rectangle {

} ; //ne pas oublier le ;
La déclaration d’une instance d’une classe se fait de la façon similaire à la déclaration d’une variable
classique : nom_classe nom_instance ;
Exemple : Rectangle rect1 ; // déclare une instance rect1 de la classe Rectangle.
1. Déclaration des attributs :
La syntaxe de la déclaration des attributs est la même que celle des champs d’une Structure :
type nom_attribut ;
Exemple : les attributs hauteur et largeur, de type double, de la classe Rectangle pourront être
déclarés comme suit :
Class Rectangle {
double hauteur ;
double largeur ;
…};

2. Accès aux attributs :


L’accès aux valeurs des attributs d’une instance de nom nom_instance se fait comme pour
accéder aux champs d’une structure : nom_instance.nom_attribut ;
Exemple : la valeur de l’attribut hauteur d’une instance rect1 de la classe Rectangle sera
référencée par l’expression : rect1.hauteur ;

3. Déclaration des méthodes :


La syntaxe de la définition des méthodes d’une classe est la syntaxe normale de définition des
fonctions :
type_retour nom_méthodes (type_arg1 nom_arg1, …)
{
//corps de la méthode

}
Sauf qu’on n’a pas besoin de passer les attributs de la classe comme arguments aux méthodes de
cette classe.
Exemple : une méthode surface () de la classe Rectangle pourrait être définie par :

class Rectangle {

double surface () {
return (hauteur*largeur) ;
}
};

11
REMARQUE : à noter que ce n’est pas parce qu’on n’a pas besoin de passer les valeurs des
attributs de la classe comme arguments aux méthodes de cette classe, que les méthodes n’ont jamais
d’arguments.
Les méthodes peuvent très bien avoir des arguments : ceux qui sont nécessaires (et donc extérieurs
à l’instance) pour exécuter la méthode en question.

Exemple :
class couleur {…} ;
class Figurecoloree {
// …
Void coloree (couleur) {…}
}
Figurecoloree une_figure ;
Couleur rouge ;
// …
Une_figure.coloree (rouge) ;
//…

4. Appel des méthodes :


L’appel aux méthodes définies pour une instance de nom nom_insatnace se fait à l’aide
d’expressions de la forme : nom_instance.nom_methode (val_arg1, …) ;
Exemples :
void surface () const ; définie pour la classe Rectangle peut être appelée pour une instance rect1 de
cette classe par : rect1.surface () ;
Une_figure.coloree (rouge) ;

IV. AUTRES OPTIONS DE LA PROGRAMMATION ORIENTEE OBJET :

1. Actions et prédicats :
En C++, on peut distinguer les méthodes qui modifient l’état de l’objet (appelées actions) de celles
qui ne changent rien à l’objet (appelées prédicats).
On peut pour cela ajouter le mot-clé const après la liste des arguments de la méthode :
Type_retour nom_méthode (type_arg1 nom_arg1, …) const

Exemple :
class Rectangle {

double surface () const {
return (hauteur*largeur) ;
}

};

12
2. Méthodes « get » (accesseurs) et « set » (manipulateur) :
Tous les attributs sont privés. Et si on a besoin de les utiliser depuis l’extérieur de la classe ?
Si le programmeur le juge utile, il inclut les méthodes publiques nécessaires…
1er : manipulateurs (méthodes set) :
o Modification
o Affectation de l’argument à une variable d’instance précise
void setHauteur (double h) {hauteur = h ;}
void setLargeur (double l) {largeur = l ;}
2e : accesseurs (méthodes get) :
o consultation
o Retour de la valeur d’une variable d’instance précise
double getHauteur () const {return hauteur;}
double getLargeur () const {return largeur;}

3. Masquage (shadowing) :
Le masquage consiste au faite qu’un identificateur cache un autre identificateur.
Situation typique : le nom d’un paramètre cache un nom d’attribut.
Void setHauteur (double hauteur) {
hauteur=hauteur ;
}
Si dans une méthode, un attribut est masqué alors la valeur de l’attribut peut quand même être
référencée à l’aide du mot-clé this. « This » est un pointeur sur l’instance courante.
La syntaxe pour spécifier un attribut en cas d’ambiguïté est : this->nom_attribut ;
Exemple : l’utilisation de this est obligatoire dans les situations de masquage.
void setHauteur (double hauteur){
This->hauteur=hauteur ;
}

4. Opérateur « :: » :
Il est possible d’écrire les définitions des méthodes à l’extérieur de la déclaration de la classe. Ceci
pour une meilleure lisibilité du code, la modularité.
Pour relier la définition d’une méthode à la classe pour laquelle est définie, il suffit d’utiliser
l’opérateur « :: » de résolution de portée.
La déclaration de la classe contient les prototypes des méthodes
Les définitions correspondantes spécifiées à l’extérieur de la déclaration de la classe, se font sous la
forme : typeRetour Nomclasse :: nomFonction(arg1, arg2, …) {…}

13
EXEMPLE COMPLET DE CLASSE (1):

#include<iostream>
using namespace std;
// définition de la classe
class Rectangle {
public :
// définition des méthodes
double surface () const {return (hauteur*largeur) ;}
double getHauteur () const {return hauteur ;}
double getLargeur () const {return largeur ;}
void setHauteur (double hauteur) {this->hauteur=hauteur ;}
void setLargeur (double largeur) {this->largeur=largeur ;}
private :
// déclaration des attributs
double hauteur ;
double largeur ;
};
// utilisation de la classe
int main() {
Rectangle rect ;
double lu ;
cout<<"Quelle hauteur ?" ; cin>>lu ;
rect.setHauteur (lu) ;
cout<<"Quelle largeur ?" ; cin>>lu ;
rect.setLargeur (lu) ;
cout<< "surface = "<<rect.surface()<<endl ;
return 0 ;
}

14
EXEMPLE COMPLET DE CLASSE (2):
La déclaration de la classe Rectangle pourrait être (dans le fichier rectangle.h).

#include<iostream>
using namespace std;
// définition de la classe
class Rectangle {
public :
// prototypes des méthodes
double surface () const ;
double hauteur () const ;
double largeur () const ;
void hauteur (double) ;
void largeur (double) ;
private :
// déclaration des attributs
double hauteur_ ;
double largeur_ ;
};
Accompagné des définitions « externes » des méthodes (dans un fichier rectangle.cc)
double Rectangle :: surface () const
{
return (hauteur_*largeur_) ;
}
double Rectangle :: hauteur () const
{return hauteur_ ;}
double Rectangle :: largeur () const
{return largeur_ ;}
void Rectangle :: hauteur (double h)
{hauteur_=h;}
void Rectangle :: largeur (double l)
{largeur_=l ;}
// utilisation de la classe
int main() {
Rectangle rect ;
double lu ;
cout<<"Quelle hauteur ?" ; cin>>lu ;
rect.hauteur (lu) ;
cout<<"Quelle largeur ?" ; cin>>lu ;
rect.largeur (lu) ;
cout<< "surface = "<<rect.surface()<<endl ;
return 0 ;
}

15
CONSTRUCTEUR/DESTRUCTEUR
ET LES SURCHARGES
Objectifs :
- Présenter les notions de constructeur et destructeur.
- aborder une des facilités d’écriture du : la surcharge des opérateurs.

INTRODUCTION
Problématique :
Nous avons vu comment déclarer une classe et par la suite, comment déclarer une instance de la
classe. Exemple : Rectangle rect ; Comment peut-on initialiser (attribuer des valeurs) les attributs de
rect ?
Comment faire l’initialisation des attributs ?
1ère solution : affecter individuellement une valeur à chaque attribut.

Rectangle rect ;
double lu ;
cout << "Quelle hauteur ? " ; cin >> lu ;
rect.setHauteur(lu) ;
cout << "Quelle largeur ? " ; cin >> lu ;
rect.setLargeur(lu) ;

Ceci est une mauvaise solution dans le cas général :


 si le nombre d’attributs est élevé, ou si le nombre d’objets crées est grand (rectangles r[0],
r[1], r[2], r[3], …, r[99]) elle est fastidieuse et source d’erreurs (oublis).
 Elle implique que tous les attributs fassent partie de l’interface (public) ou soient assortis d’un
manipulateur (set).
2ème solution : définir une méthode dédiée à l’initialisation des attributs.
 Rectangle {
Class
 :
private
double hauteur ;
double largeur ;
public :
// Méthode d’initialisation
void init (double h, double L) {
hauteur = h ; largeur = L ;}

};

En fait, C++ fait déjà le travail pour vous en fournissant des méthodes particulières appelées
constructeurs. Un constructeur réalise toutes les opérations requises en « début de vie » de l’objet.

16
I. LES CONSTRUCTEURS

1. Définition :
Un constructeur d’objet est une méthode :
 Invoquée automatiquement lors de l’instanciation d’un objet,
 Assurant l’initialisation des attributs,
Les principales caractéristiques d’un constructeur à prendre en compte lors de son utilisation sont :
- Il porte le même nom que la classe dans laquelle il est placé.
- C’est la première fonction membre à être exécutée.
- Il peut posséder des arguments.
- Il n’a pas de type de retour (pas même void).
- Sa définition n’est obligatoire que si elle est nécessaire au bon fonctionnement de la classe.

Syntaxe de base : exemple :


nomClasse (liste_arguments) rectangle (double h, double L)
{ {
//initialisation des attributs utilisant liste_arguments hauteur = h ;
} largeur = L ;
}
NB : les constructeurs sont des méthodes comme les autres que l’on peut surcharger. C’est-à-dire
qu’une classe peut avoir plusieurs constructeurs pour peu que leurs signatures (liste d’arguments)
soient différentes.
2. Initialisation par constructeur :
La déclaration avec initialisation d’un objet se fait comme une variable ordinaire.
Syntaxe : NomClasse instance (valarg1, …, valargN) ; où valarg1, …, valargN sont les valeurs des
arguments du constructeur.
Exemple : Rectangle rest1(12.0, 5.3) ; //invocation du constructeur
3. Constructeur par défaut
Un constructeur par défaut est un constructeur qui n’a pas d’arguments ou dont les arguments ont des
valeurs par défauts.
Exemples :
// Le constructeur par défaut
Rectangle () : hauteur(1.0), largeur(2.0) {}
//2ème constructeur
Rectangle (double c) : hauteur(c), largeur(2.0*c) {}
//3ème constructeur
1.1- constructeur explicite
Rectangle (double h, double L) : hauteur(h), largeur(L) {}
Autre façon de faire : regrouper les 2 premièrs constructeurs en utilisant les valeurs par défaut des
arguments :
//2 constructeurs dont le constructeur par défaut
Rectangle (double c = 1.0) : hauteur(c), largeur(2.0*c) {}

17
4. Constructeur par défaut par défaut :
Si aucun constructeur n’est spécifié, le compilateur génère automatiquement une version minimale
du constructeur par défaut (c’est donc un constructeur par défaut par défaut) qui :
 Appelle le constructeur par défaut des attributs objets;
 Laisse non initialisés les attributs de type de base.
Dès qu’au moins un constructeur a été spécifié, ce constructeur par défaut par défaut n’est plus fourni.
Si donc on spécifie un constructeur sans spécifier de constructeur par défaut, on ne peut plus construire
d’objet de cette classe sans les initialiser (ce qui est voulu !) puisqu’il n’y a plus de constructeur par
défaut.

Exemple complet : volume et surface d’une pyramide à base carrée :


#include <iostream>
#include <cmath>
using namespace std;
//définition de la classe pyramide
class pyramide{
private:
double c, h;
public:
//constructeur de la classe pyramide
pyramide(double x, double y){
c=2.54*x; h=2.54*y;}
//fonction membre pour le calcul de la surface
double surface (void){
double apotheme;
apotheme=sqrt(pow(h,2)+pow(c/2,2));
return (c*h*apotheme)/2+pow(c,2);}
//fonction membre pour le calcul du volume
double volume (void){
return (pow(c,2)*h/3);
}
};
int main(){
//déclaration des variables cote et hauteur
double cote, hauteur;
//saisie
cout<<"Côté (en pouces) : ";
cin>>cote;
cout<<"Hauteur (en pouces) : ";
cin>>hauteur;
//instanciation de la classe pyramide via la création d’un objet P
pyramide P(cote, hauteur);
//affichage du volume et de la surface via l’appel des méthodes de l’objet P
cout<<"Volume (en cm3) : "<<P.volume()<<endl;
cout<<"Surface (en cm2) : "<<P.surface()<<endl;
return 0;}

18
Commentaires :
Dans l’exemple précédent, la fonction « constructeur » initialise l’objet P en affectant les valeurs
spécifiées à ses données membres.
Quand la déclaration de la classe P a lieu, le constructeur est appelé automatiquement et la conversion
de la hauteur et du côté de la pyramide, de pouces en centimètres, est effectuée.
Le langage C++ offre le moyen d’initialiser les données membres d’un objet par l’intermédiaire de
listes d’initialisation de constructeurs. Des valeurs par défaut sont choisies, si aucun appel n’est
passé lors de l’appel de la fonction membre.

5. Constructeur de recopie :
C++ vous fournit également un moyen de créer la copie d’une instance : le constructeur de copie.
Ce constructeur permet d’initialiser une instance en utilisant les attributs d’une autre instance de
même type.
Syntaxe :
NomClasse (NomClasse const& obj): …
{ …}
Exemple :
Rectangle (Rectangle const& obj):hauteur (obj.hauteur), largeur (obj.largeur){}
L’invocation du constructeur de copie se fait par une instruction de la forme :
Rectangle r1(12.3, 24.5) ;
Rectangle r2(r1) ;

r1 et r2 sont deux instances distinctes mais ayant des mêmes valeurs pour leurs attributs (à ce
moment-là du programme).

Exemple : surface d’un trapèze en cm2 et en pouces2


#include <iostream>
using namespace std;
//déclaration de la classe trapeze
class trapeze {
private:
double b, B, h;
public:
//constructeur avec ses paramètres par défaut
trapeze(double x=1, double y=2, double z=1):b(x),B(y),h(z) {}
//constructeur par copie de trapeze
//le passage se fait par référence constante
//la copie en profite pour effectuer une conversion en pouces des variables
trapeze(const trapeze& T): b(T.b/2.54),B(T.B/2.54), h(T.h/2.54){}
double surface (void){
return ((b+B)*h/2);
}
};

19
int main(){
//création d’un objet T1 instancié depuis la classe trapeze
//petite base b : 15cm, grande base B : 20cm, hauteur h: 6,5cm
trapeze T1(15, 20, 6.5);
//creation d’un objet T2 copie du constructeur T1
trapeze T2(T1);
//affichage de la surface calculée en cm2
cout<<"Surface T1 : "<<T1.surface()<<endl;
//affichage de la surface calculée en pouces2 via le constructeur par copie
cout<<"Surface T2 : "<<T2.surface()<<endl;
return 0;
}

II. LES DESTRUCTEURS


Quand un objet n’est plus utilisé nous allons pouvoir le supprimer de manière analogue à sa
construction. Pour réaliser cette tâche nous utiliserons l’appel d’une fonction nommée
destructeur.

Définition : Un destructeur est une fonction membre qui porte le même nom que la classe. À
la différence d’un constructeur il est précédé du symbole ~ (tilde). Son rôle est de détruire un
objet avant que la mémoire soit libérée. Le destructeur est invoqué automatiquement en fin de
vie de l’instance.

La syntaxe de déclaration d’un destructeur pour une classe nomClasse est :


NomClasse () {
// opérations (de libération)
}
 Le destructeur d’une classe est une méthode sans arguments. Il n’est pas possible de faire une
surcharge de destructeur.
 Son nom est celui de la classe, précédé du signe ~.
 Si le destructeur n’est pas défini explicitement par le programmeur, le compilateur en
génère automatiquement une version minimale.
Exemple : surface de 2 trapèzes
#include <iostream>
using namespace std;
//définition de la classe trapeze
class trapeze {
private:
double b, B, h;
public:
//constructeur
trapeze(double x, double y, double z):b(x), B(y), h(z)
{}
20
//destructeur
~trapeze (){}
//fonction membre surface
double surface (void){
return ((b+B)*h/2);
}
};
int main(){
//instanciation de 2 objets T1 et T2 de la classe trapèze
trapeze T1(10, 20, 2.5);
trapeze T2(50, 100, 12.5);
//affichage et appel de la fonction surface
cout<<"Surface de T1 : "<<T1.surface()<<endl;
cout<<"Surface de T2 : "<<T2.surface()<<endl;
return 0;
}

Exemple : Construction et destruction des objets


#include <iostream>
using namespace std ;
class test
{ public :
int num ;
test (int) ; // déclaration constructeur
~test () ; // déclaration destructeur
};
test::test (int n) // définition constructeur
{ num = n ;
cout << "++ Appel constructeur - num = " << num << "\n" ;
}
test::~test () // définition destructeur
{ cout << "-- Appel destructeur - num = " << num << "\n" ;
}
main()
{ void fct (int) ;
test a(1) ;
for (int i=1 ; i<=2 ; i++) fct(i) ;
}
void fct (int p)
{ test x(2*p) ; // notez l’expression (non constante) : 2*p
}

21
Lorsque certains attributs de la classe sont des pointeurs.

Exemple :
Soit une autre définition (farfelue, mais possible !) de la classe Rectangle :
class Rectangle {
private:
double* largeur; // aie, 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;
...
};
Que se passe-t-il lorsqu’on invoque la fonction suivante ?
void afficher_largeur(Rectangle tmp) {
cout << "Largeur: " << tmp.getLargeur() << endl;
}
void afficher_largeur(Rectangle tmp) { // une copie !..
cout << "Largeur: " << tmp.getLargeur() << endl;
} // destruction de tmp...

Exemple : Définition complète de la classe


class Rectangle {
public:
Rectangle(double l, double h)
: largeur(new double(l)), hauteur(new double(h)) {}
Rectangle(const Rectangle& obj);
~Rectangle();
private:
double* largeur; double* hauteur;
};

// constructeur de copie
Rectangle::Rectangle(const Rectangle& obj)
: largeur(new double(*(obj.largeur))),hauteur(new double(*(obj.hauteur)))
{}
// destructeur
void Rectangle::~Rectangle() {
delete largeur;
delete hauteur;
}

22
Le mécanisme du destructeur est automatiquement appélé lorsqu’un objet de la classe sort de la
portée en cours ou que l’opérateur de supression (delete) est appliqué au pointeur de la classe. Le
mécanisme appelle d’abord la fonction destructeur. Après son exécution, le mécanisme libère la
mémoire associée à cet objet. Un objet créé à partir de l’opérateur new restera cependant toujours
accessible. Il devra donc etre explicitement supprimé.
Exemple :
class Personne {
public:
Personne(int ag, char sex)
{age=ag ; sexe=sex ;} //définition inline de méthode
Personne(){age=0 ; sexe=’b’ ;}
~Personne();
private:
int age; char sexe;
};
// code dans la méthode appelante
main()
{Personne *Ange = new Personne(22, ‘M’) ;
Personne Marie(35,’F’) ;
Personne *Katie=new Personne(50,’F’) ;
Personne Jean ;
Jean=Personne(19,’M’) ;
// Destruction des objets
delete Ange, Katie ; //tous les pointeurs
// les objets Marie et Jean seront supprimés lorsque la fonction sortira de la portée
courante
}

NB :
 Allocation dynamique. L’opérateur new() permet d’allouer dynamiquement l’espace
mémoire nécessaire pour une (ou plusieurs) instance(s) de classe donnée, de la même
manière que pour les types standard :
 Etudiant* pe=new Etudiant; pour un seul exemplaire
 Etudiant* pe=new Etudiant[10]; pour un tableau de 10 étudiants.
La désallocation se fait par l’opérateur delete ou delete[]
 Accès aux membres. Soit e une instance de classe Etudiant déclarée par
 Etudiant e;
1.4.1. L’opérateur ".". Permet un accès direct aux attributs ou méthodes de l’instance :
 cout <<e.nom; accès à l’attribut nom
 e.calculNoteFinale(); exécution de la méthode calculNoteFinale
 L’opérateur "->". Permet l’accès aux attributs ou méthodes de l’instance via un pointeur.
Par exemple, si un pe est un pointeur sur Etudiant,
 . Etudiant* pe = &e;
alors on accède aux membres de e par pe->xxx qui équivaut à e.xxx ou encore à (*pe).xxx :
 cout <<pe->nom;
 pe->calculNoteFinale();

23
III. LA SURCHARGE
1- Surcharge de fonctions membres
De la même façon que pour les fonctions habituelles, il est possible de surdéfinir les fonctions membres
d’une classe. On parle alors de polymorphisme : une même fonction peut avoir plusieurs formes selon
le contexte (l’objet) d’appel.
Exemple 1 : fichier « Point.hpp »
#incude <string>
class Point {
private:
...
public:
...
void Afficher() const;
void Afficher(const string & message) const;
};
Fichier « .cpp »
#include "Point.hpp"
void Point::Afficher(){
cout << "x = " << coord_x << " y = " << coord_y << endl;
}
void Point::Afficher(const string & message){
cout << message << endl;
cout << "x = " << coord_x << " y = " << coord_y << endl;
}
La manœuvre consistant à surcharger une méthode revient à en créer une nouvelle, dont la signature se
différencie de la précédente, uniquement par la liste ou la nature des arguments.
Exemple 2 :
class MaClasse1 {
void maMethode( ){
attribut1 = attribut1 + 1 ;
....
}
void maMethode( int a ){
attribut1 = attribut1 + a ;
....
}
void maMethode( int a, int b ){
attribut1 = a + b ;
....
}
int maMethode( int a, int b ){ /* Il est interdit de définir cette méthode, présentant la même
signature, mais un type de retour différent de la précédente */
}
}

24
2- Surcharge d’opérateurs
Il est pratique de pouvoir utiliser les opérateurs classiques pour effectuer des opérations sur des objets
(par exemple des objets mathématiques : somme de vecteurs, produits de matrices, ...).
Le C++ permet de surcharger la plupart des opérateurs du C :
 + - * / += -= ... ++ --
 == < > <= >= ...
 [] ()
 new, delete
 l’affectation =
L’avantage de la surcharge d’opérateurs est de pouvoir réutiliser les dénominations “naturelles” de
ceux-ci.

Surcharge des opérateurs arithmétiques :


Ce type de surcharge est très souvent utilisé et reste très pratique.
L’écriture de la surcharge d’un opérateur passe par une fonction dont le nom est operator, suivi du
symbole d’opération. La liste des arguments, est identique à celle du constructeur par copie de la
classe ; ils sont passés par référence et sont du type de la classe.
Voici la syntaxe : type operatorOp(paramètres)
A + B est traduite par le compilateur par : A.operator+(B)
A * B est traduite par le compilateur par : A.operator*(B)
etc...
A Op B est traduite par le compilateur par : A.operator Op(B)
Avec cette syntaxe, le premier opérande est toujours l’objet auquel cette fonction s’applique.

class Point {
private:
...
public:
...
Point operator+(const Point & p) const;
bool operator==(const Point & p) const;
};
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#include "Point.hpp"
Point Point::operator+(const Point & p){
Point res;
res.coord_x = coord_x + p.coord_x;
res.coord_y = coord_y + p.coord_y;
return res;
// return Point(coord_x + p.coord_x, coord_y + p.coord_y)
}
bool Point::operator==(const Point & p){
if ((coord_x == p.coord_x)
&& (coord_y == p.coord_y)){
return true;
} else {
return false;
}
// return ((coord_x == p.coord_x) && (coord_y == p.coord_y));
}
25
Exemple : RECTANGLES
#include <iostream>
using namespace std;
//définition de la classe rectangle
class rectangle{
public:
float longueur, largeur
//constructeur
rectangle(double L=0, double l=0){
longueur=L;
largeur=l;}
//constructeur par copie
//il est précisé ici, mais n’est pas obligatoire
//puisque créé de façon automatique
//par le compilateur
rectangle(const rectangle&);
//fonction membre affiche
void affiche(){
cout<<"Longueur rectangle rect3 : "<<longueur<<endl;
cout<<"Largeur rectangle rect3 : "<<largeur<<endl;}
};
//surcharge de l’opérateur +
rectangle operator+(rectangle& r1, rectangle& r2){
rectangle R(r1.longueur+r2.longueur, r1.largeur+r2.largeur);
return R;}
int main () {
//saisie des longueurs (L1, L2) et largeurs (l1, l2) de 2 rectangles rect1 et rect2
double L1, l1, L2, l2;
cout<<"Longueur rectangle rect1 : ";
cin>>L1;
cout<<"Largeur rectangle rect1 : ";
cin>>l1;
cout<<"Longueur rectangle rect2 : “;
cin>>L2;
cout<<"Largeur rectangle rect2 : ";
cin>>l2;
//instanciation de rext1 et rect2
rectangle rect1(L1, l1);
rectangle rect2(L2, l2);
//addition des objets rect1 et rect2 pour créer l’objet rect3
rectangle rect3=rect1+rect2;
//affichage des dimensions de rect3
rect3.affiche();
return 0;
}

26
HERITAGE
Objectifs du cours :
Ce cours a pour but :

 de présenter en détail la notion fondamentale d’héritage


 de vous présenter encore quelques subtilités au sujet des constructeurs et des destructeurs
 d’introduire la notion d’attribut (et méthode) statique à une classe

EXEMPLE DE RELATIONS : a-un, est-un, utilise-un


Dans le cadre d’un programme concernant les transports, supposons que nous déclarions les quatre
classes suivantes : Voiture, Moteur, Route et Véhicule. Quel genre de relation y a-t-il entre la classe
Voiture et les trois autres classes ?
Tout d’abord, on peut dire qu’une voiture a un moteur : le moteur fait partie de la voiture, il est
contenu dans celle-ci.
Ensuite, une voiture utilise une route, mais il n’y a pas d’inclusion : la route ne fait pas partie de la
voiture, de même que la voiture ne fait pas partie de la route.
Enfin, une voiture est un véhicule, d’un genre particulier : la voiture possède toutes les
caractéristiques d’un véhicule, plus certaines caractéristiques qui lui sont propres.

Du point de vue de la programmation, la relation a-un est une inclusion entre classes. Ainsi, un objet
de type Voiture renferme une donnée-membre qui est un objet de type Moteur.
La relation utilise-un est une collaboration entre classes indépendantes. Elle se traduit le plus souvent
par des pointeurs. Par exemple, un objet de type Voiture renfermera une donnée-membre de type
pointeur sur Route, ce qui permettra à la voiture de communiquer avec une route.
La relation est-un s’appelle un héritage : une voiture hérite des caractéristiques communes à tout
véhicule.
On dira que la classe Voiture est dérivée de la classe Véhicule.

I. PRESENATION DE L’HERITAGE :

1. Définition de l’Héritage :
Après les notions d’encapsulation et d’abstraction, le troisième aspect essentiel des objets est la
notion d’héritage.
L’héritage est une technique extrêmement efficace pour créer des classes plus spécialisées, appelées
sous-classes, à partir de classes plus générales déjà existantes, appelées super-classes.
Elle représente la relation «est-un».
Exemple : Hiérarchie de classes
Figure Géométrique

hérite de
Partitionnement

Rectangle Cercle

Spécialisation

Sphère Cylindre

27
Plus précisément, lorsqu’une sous-classe C1 est créée à partir d’une classe C, C1 va hériter de
l’ensemble des attributs et des méthodes de C (sauf les constructeurs/destructeurs).
NB :
 Les attributs et les méthodes de C vont être disponibles pour C1 sans que l’on ait besoin de
les redéfinir explicitement dans C1.
 De plus :
 Le type est aussi hérité : C1 est (aussi) un C. Pour un C x; et un C1 y;, on peut tout à
fait faire x = y; (mais bien sûr pas y = x; !!!)
 Par transitivité, les instances d’une sous-classe possèdent les attributs et méthodes
de l’ensemble des classes parentes (classe parente, classe parente de la parente, etc.)
 Par ailleurs :
 Des attributs et/ou méthodes supplémentaires peuvent être définis par la sous-classe
C1. Ces membres constituent l’enrichissement apporté par cette sous-classe.
 La notion d’enrichissement par héritage :
 crée un réseau de dépendances entre classes,
 ce réseau est organisé en une structure arborescente où chacun des nœuds
hérite des propriétés de l’ensemble des nœuds du chemin remontant jusqu’à la
racine. Ce réseau de dépendance définit une hiérarchie de classes

2. L’avantage de l’héritage :
 d’expliciter des relations structurelles et sémantiques entre classes ;
 de réduire les redondances de description et de stockage des propriétés.
 du point de vue du développemente te de la maintenance, l’héritage permet de gérer le
partage, la réutilisabilition et l’extension du code

3. Exemple de cas pratique :


Supposons par exemple que l’on veuille étendre la classe Rectangle précédemment définie en lui
ajoutant le nouvel attribut couleur.
Une façon de procéder est de créer une nouvelle classe, par exemple RectangleColore, définie
comme une sous-classe de Rectangle et contenant le nouvel attribut couleur.
Classe Rectangle
largeur
hauteur

hérite de

Classe RectangleColore
couleur

On évite ainsi de dupliquer inutilement du code commun aux classes Rectangle et RectangleColore !

4. Définition d’une sous-classe en C++ :


Syntaxe :
class NomClasseEnfant : public NomClasseParente
{
/* Déclaration des attributs et méthodes spécifiques à la sous-classe */
//...};
Exemple :
class RectangleColore : public Rectangle
{
Couleur couleur;
//...};

28
II. LES MEMBRES D’UNE SOUS-CLASSE
1. Accès aux membres d’une sous-classe :
Jusqu’à maintenant, l’accès aux membres (attributs et méthodes) d’une classe pouvait être :
 soit public : visibilité totale à l’intérieur et à l’extérieur de la classe (mot-clé public)
 soit privé : visibilité uniquement à l’intérieur de la classe (mot-clé private ou par défaut)

Un troisième type d’accès régit l’accès aux attributs/méthodes au sein d’une hiérarchie de classes :
L’accès protégé : assure la visibilité des membres d’une classe dans les classes de sa
descendance [et uniquement elles, et uniquement dans ce rôle (de sous classe, voir exemple
plus loin)]. Le mot clé est «protected».

2. Définition des niveaux d’accès :


L’ordre de définition conseillé est le suivant :
// lien d’héritage si nécessaire seulement
class NomClasse : public NomClasseParente
{
// par defaut : private
public:
// attributs et méthodes public
protected:
// attributs et méthodes protected
private:
// attributs et methodes private
};
Mais il peut y avoir plusieurs zones publiques, protégées ou privées dans une même définition de
classe.

3. Accès protégé :
Le niveau d’accès protégé correspond à une extension du niveau privé aux membres des sous-
classes

Exemple :
class Rectangle
{
public:
Rectangle(): largeur(1.0), hauteur(2.0) {}
protected:
double largeur; double hauteur;
};
class RectangleColore : public Rectangle
{
public:
// on profite ici de protected
void carre() { largeur = hauteur; }
protected:
Couleur couleur;
};

Le niveau d’accès protégé correspond à une extension du niveau privé aux membres des sous-
classes... mais uniquement dans ce rôle (de sous-classe) pas dans le rôle de classe extérieure :

29
Exemple :
class A {
//...
protected: int a;
private: int p;
};
class B: public A {
public:
//...
void f(B autreB, A autreA, int x) {
a = x; // OK A::a est protected => accès possible
p = x; // erreur : A::p est private
a += autreB.p; // erreur (même raison)
a += autreB.a; // OK : dans la même classe (B)
a += autreA.a; // INTERDIT ! : this n’est pas de la même classe que autreA (role externe)
}
};

4. Restriction des accès lors de l’héritage :


Les niveaux d’accès peuvent être modifiés lors de l’héritage.
Syntaxe :
class ClasseEnfant: [accès] classeParente
{
/* Déclaration des membres
spécifiques à la sous-classe */
//...
};
Où accès est le mot-clé public, protected ou private. Les crochets entourant un élément [ ] indiquent
qu’il est optionnel.
Les droits peuvent être conservés ou restreints, mais jamais relâchés ! Par défaut, l’accès est privé.

Récapitulatif des changements de niveaux d’accès aux membres hérités, en fonction du niveau initial
et du type d’héritage :

accès initial
public protected private
public public protected pas d’accès héritage
héritage

protected protected protected pas d’accès


private private private pas d’accès

Le type d’héritage constitue une limite supérieure à la visibilité.

5. Utilisation des droits d’accès :


 Membres publics : accessibles pour les programmeurs-utilisateurs de la classe ;
 Membres protégés : accessibles aux programmeurs d’extensions par héritage de la classe ;
 Membres privés : pour le programmeur de la classe, structure interne, modifiable si
nécessaire sans répercussions ni sur les utilisateurs ni sur les autres programmeurs.

30
III. MASQUAGE DANS UNE HIERARCHIE :
1. Présentation du masquage dans une hiérarchie :

 Masquage : un identificateur qui en cache un autre


 Situation possible dans une hiérarchie :
o Même nom d’attribut/méthode utilisé sur plusieurs niveaux
o Peu courant pour les attributs
o Très courant et pratique pour les méthodes
 Exemple :
o Rectangle3D hérite de Rectangle
o calcul de la surface pour les Rectangle3D :
2* (largeur*hauteur) + 2*(largeur*profondeur) + 2*(hauteur*profondeur)
o calcul de la surface pour tous les autres Rectangle : (largeur*hauteur)
 Faut-il re-concevoir toute la hiérarchie ?
o Non, on ajoute simplement une méthode surface spéciale à Rectangle3D.

Rectangle

Double surface() {
Return (largeur*hauteur) ;}

hérite de

Rectangle3D:Rectangle

double surface() {
return(2*largeur*hauteur +
2*largeur*…) ;}

La méthode surface de Rectangle3D masque celle de Rectangle. Ainsi, un objet de type


Rectangle3D n’utilisera donc jamais la méthode surface de la classe Rectangle

class Rectangle {
public:
// les constructeurs seraient ici...
double surface() {return (largeur*hauteur);}}
protected:
double largeur; double hauteur;
// le reste de la classe...
};
class Rectangle3D : public Rectangle {
public:
// les constructeurs seraient ici...
double surface() { // Masquage !
return(2.0*(largeur*hauteur) + 2.0*(largeur*profondeur)+ 2.0*(hauteur*profondeur));
}
protected:
double profondeur;
// le reste de la classe...
};

31
2. Accès à une méthode masquée :
 Il est parfois souhaitable d’accéder à une méthode/un attribut caché(e),
 Exemple :
o surface des Rectangle3D ayant une profondeur nulle (largeur*hauteur). Identique au
calcul de surface pour les Rectangle.
 Code désiré :
1. Objet non-Rectangle3D : Méthode générale (surface de Rectangle)
2. Objet Rectangle3D : Méthode spécialisée (surface de Rectangle3D)
3. Objet Rectangle3D de profondeur nulle :
o D’abord la méthode spécialisée
o Ensuite appel à la méthode générale depuis la méthode spécialisée
 Pour accéder aux attributs/méthodes caché(e)s de la super-classe, on utilise l’opérateur de
résolution de portée :
o Syntaxe : NomClasse ::methode ou attribut
o Exemple : Rectangle ::surface()

class Rectangle3D : public Rectangle {


//… constructeurs, attributs comme avant
double surface () {
if (profondeur == 0.0)
// Acces à la méthode masquée
return Rectangle::surface() ;
else
return(2.0*(largeur*hauteur) + 2.0*(largeur*profondeur) + 2.0*(hauteur*profondeur)) ;
}
};

IV. CONSTRUCTEURS ET HERITAGE :


1. Instanciation d’une sous-classe
Lors de l’instanciation d’une sous-classe, il faut initialiser :
 les attributs propres à la sous-classe
 les attributs hérités des super-classes
Mais il ne doit pas être à la charge du constructeur des sous-classes de réaliser lui-même
l’initialisation des attributs hérités. L’accès à ces attributs peut notamment être interdit ! (private).
L’initialisation des attributs hérités doit donc se faire au niveau des classes où ils sont explicitement
définis.
La solution est que l’initialisation des attributs hérités doit se faire en invoquant les constructeurs des
super-classes.
L’invocation du constructeur de la super-classe se fait au début de la section d’appel aux
constructeurs des attributs.
Syntaxe :
SousClasse(liste d’arguments)
: SuperClasse(Arguments),
attribut1(valeur1),
...
attributN(valeurN)
{
// corps du constructeur
}

32
Lorsque la super-classe admet un constructeur par défaut, l’invocation explicite de ce constructeur
dans la sous-classe n’est pas obligatoire. Par ce que le compilateur se charge de réaliser l’invocation
du constructeur par défaut.
Si la classe parente n’admet pas de constructeur par défaut, l’invocation explicite d’un de ses
constructeurs est obligatoire dans les constructeurs de la sous-classe. Ce qui implique que la sur-
classe doit admettre au moins un constructeur explicite.

Exemple :
class Rectangle {
protected: double largeur; double hauteur;
public:
Rectangle(double l, double h) : largeur(l), hauteur(h) {}
// le reste de la classe...
};
class Rectangle3D : public Rectangle {
protected: double profondeur;
public:
Rectangle3D(double l, double h, double p)
// Appel au constructeur de la super-classe
: Rectangle(l,h), profondeur(p) {}
// le reste de la classe...
};
Autre exemple (qui ne fait pas la même chose) :
class Rectangle {
protected: double largeur; double hauteur;
public:
// il y a un constructeur par defaut !
Rectangle() : largeur(0.0), hauteur(0.0)
{}}
// le reste de la classe...
};
class Rectangle3D : public Rectangle {
protected: double profondeur;
public:
Rectangle3D(double p)
: profondeur(p)
{}
// le reste de la classe...
};
Ici, il n’est pas nécessaire d’invoquer explicitement le constructeur de la classe parente puisque celle-
ci admet un constructeur par défaut.

Encore un exemple :
Il n’est pas nécessaire d’avoir des attributs supplémentaires...
class Carre : public Rectangle {
public:
Carre(double taille) : Rectangle(taille, taille){}
// et c’est tout ! (sauf s’il y avait des "méthodes set")
};

33
2. Ordre d’appel des constructeurs dans le cas de plusieurs héritages :
Hiérachie de classes constructeurs

Classe A A(…)
: a1(…),
a1 m1(…) a2(…)
a2 m2(…) {}

Classe B B(…)
: A(…),
b1 m3(…) B1(…)
m4(…) {}

Classe C C(…)
: B(…),
c1 m5(…) c1(…), c2(…)
c2 {}

Instanciation ;
C monC(…) ;

3. Ordre d’appel des destructeurs dans le cas de plusieurs héritages :


Les destructeurs sont toujours appelés dans l’ordre inverse (/symétrique) des constructeurs.
Par exemple dans l’exemple précédent, lors de la destruction d’un C, on aura appel et exécution de :
- C::˜C()
- B::˜B()
- A::˜A()
(dans cet ordre, puisque les constructeurs avaient été appelés dans l’ordre :
- A::A()
- B::B()
- C::C()
)

V. ATTRIBUTS ET METHODES STATICS:

1. Attributs statiques (attributs de classe) :


Supposons que nous cherchions à resoudre un problème de comptage des instances :
long compteur(0);
class Rectangle {
//...
//constructeur
Rectangle(): hauteur(0.0), largeur(0.0) {
++compteur; }
// destructeur
~{}Rectangle() { --compteur; }
//...
};
Ceci pourrait-il resoudre ce problème ?

34
La solution à ce problème consiste à utiliser un attribut statique :
 un attribut statique est partagé par toutes les instances de la même classe (on parle aussi
d’« attribut de classe »),
 c’est un attribut de la classe qui peut être privé, protégé ou public et dont la déclaration est
précédée du mot clé static,
 il existe même lorsqu’aucune instance de la classe n’est déclarée.

 Déclaration d’une variable statique :


class Rectangle {
private:
double hauteur, largeur;
static int compteur; // un membre statique !
//...
};

 Initialisation des attributs statiques :


Un attribut statique doit être initialisé explicitement à l’extérieur de la classe
/* initialisation de l’attribut statique dans le fichier de définition de la classe (.cc) */
int Rectangle::compteur = 0;
/* Rectangle::compteur existe même si l’on n’a déclaré aucune instance de la classe Rectangle */
Les attributs statiques sont très pratiques lorsque différents objets d’une classe doivent accéder à une
même information.
Ils permettent notamment d’éviter que cette information soit dupliquée au niveau de chaque objet !
Concrètement : réserver cet usage à des constantes utiles pour toutes les instances de la classe.

2. Méthodes statiques (Méthodes de classe):


 Similairement, si on ajoute static à une méthode :
 On peut accéder à la méthode à travers un objet mais aussi sans objet (à partir du nom de la
classe et de l’opérateur ::)
class A {
public:
void methode1 () {
cout << "Methode 1" << endl;
}
static void methode2 () {
cout << "Methode 2" << endl;
}
};
int main () {
A v;
v.methode1(); //OK
v.methode2(); //OK
A::methode2(); //OK, alternative
A::methode1(); //Faux
}

35
COMPLEMENTS SUR L’HERITAGE
 Spécifier une classe dérivée
class Personne{
public :
Personne(char* n, char s, int a);
char* getNom();
char getSexe();
int getAge();
void setNom(char* n);
void setAge(int a);
~Personne() {}
private :
char nom[40];
char sexe;
int age, taille, poids;
};
class Employe : public Personne
{
public :
Employe(char* n, char s, int a, int n=0) :Personne(n, s, a) {salaire = n ;}
int getSalaire(){return=salaire ;}
void setSalire(int a){salaire=a ;}
~Employe() {}
private :
int salaire;
};

 Héritage à partir d’une classe dérivée et implémentation de l’association


class Personne{
public :
Personne(char* n, char s, int a);
char* getNom();
char getSexe();
int getAge();
void setNom(char* n);
void setAge(int a);
~Personne() {}
private :
char nom[40];
char sexe;
int age, taille, poids;
};
class Employe : public Personne
{
public :
Employe(char* n, char s, int a, int n=0) :Personne(n, s, a) {salaire = n ;}
int getSalaire(){return=salaire ;}

36
void setSalire(int a){salaire=a ;}
~Employe() {}
private :
int salaire;
};

const int max=20;


class Manager: public Employe
{
public :
Manager(char* n, char s, int a, int n=0) :Employe(n, s, a, n)
{for (int i=0;i<max; i++) groupe[i] = 0 ;}
Employe *getEmploye(int n) {return groupe[n];}
void setEmploye(Employe *e, int a) {groupe[a] = e;}
~Manager() {delete [ ] groupe;}
private :
Employe *groupe[max];
};
main()
{ // créer comme une instance de Employe
Employe Ed("Edgard", m, 21, 200000);
//Edgard a obtenu une augmentation !!
Ed.setSalaire(250000);
//créer un chef
Manager Ja("Jeanne", f, 25);
//les chefs gagnent beaucoup!!
Ja.setSalaire(500000);
//place Ed dans le groupe de Ja
Ja.setEmploye(&Ed, 0)
//le salaire d’Edgard le plus
Ed.setSalaire(150000);
};

 Ajouter le polymorphisme
class Personne{
public :
Personne(char* n, char s, int a);
char* getNom();
char getSexe();
int getAge();
void setNom(char* n);
void setAge(int a);
~Personne() {}
private :
char nom[40];
char sexe;
int age, taille, poids;
};
class Employe : public Personne
37
{
public :
Employe(char* n, char s, int a, int n=0) :Personne(n, s, a) {salaire = n ;}
int getSalaire(){return=salaire ;}
virtual void setSalire(int a){salaire=a ;}
~Employe() {}
private :
int salaire;
};

const int max=20;


int bonus(){return 50000;} // prime pour les chefs

class Manager: public Employe


{
public :
Manager(char* n, char s, int a, int n=0) :Employe(n, s, a, n)
{for (int i=0;i<max; i++) groupe[i] = 0 ;}
Employe *getEmploye(int n) {return groupe[n];}
void setEmploye(Employe *e, int a) {groupe[a] = e;}
void setSalaire(int s)
{Employe ::setSalaire(s+bonu());} //polymorphisme
~Manager() {delete [ ] groupe;}
private :
Employe *groupe[max];
};
main()
{ // créer comme une instance de Employe
Employe Ed("Edgard", m, 21, 200000);
//Edgard a obtenu une augmentation !!
Ed.setSalaire(250000);
//créer un chef
Manager Ja("Jeanne", f, 25);
//les chefs gagnent beaucoup!!
Ja.setSalaire(500000);
//place Ed dans le groupe de Ja
Ja.setEmploye(&Ed, 0)
//le salaire d’Edgard le plus
Ed.setSalaire(150000);
};

 Classes abtraites
La classe de base aussi appelée classe racine d’une hiérarchie peut contenir des fonctions virtuelles. Il
s’agit souvent de fonctions factices, dont le corps est vide, et qui ne prennent une signaturesécifique
qu’à l’interieur d’une classe dérivée. En C++, cette situation est gérée par le mécanisme virtuel pur.

38
ACTIVITES :

EXERCICE 1 :
1. Présenter la programmation orientée objet à travers ses caractéristiques et son intérêt en quelques points.

2. Donner la définition des concepts et expressions suivants dans la Programmation Orientée Objet
(POO) : La surcharge des opérateurs en C++, une fonction amie dans une classe, une classe abstraite,
le polymorphisme.

EXERCICE 2 :
1. On considère que la classe C hérite de la classe A et de la classe B. Une instance de la classe C
possède à la fois les données et fonctions membres de la classe A et celles de la classe B. VRAI ou
FAUX ? De quel principe s’agit-il ?

2. Quand un objet de la classe C est créé, les constructeurs des classes parentes sont-ils appelés ?
Si oui, dans quel ordre ? Si non, pourquoi ?

3. Quand un objet est détruit, les destructeurs des classes parentes sont-ils appelés ? Au cas où il
y a appel, cet appel se fait-il avant ou après celui de l’objet détruit ?

4. Dans la situation ci-dessus (1), il peut arriver que des données ou fonctions-membres des
classes A et B aient le même nom. Que fait-on pour lever l’ambigüité ? Donnez en un
exemple avec un commentaire en C++.

EXERCICE 3 :
1. Commentez le code en C++ ci-dessous, comme cela est indiqué par l’expression « commentaire ici ».
2. Recensez les concepts de la programmation orientée objet en C++ évoqués dans le programme ci-
dessous.

#include <iostream>
using namespace std;
//commentez ici
class polygone{
public :
void valorise(double a, double b){
l=a; h=b;
}
//commentez ici
virtual double surf(void){
cout<<"Appel de la fonction surf de la classe de
base qui renvoie : ";
return 0;
}
protected:
double l, h;
};
//commentez ici
class rectangle:public polygone
{

39
public:
double surf(void){
cout<<"Appel de la fonction surf de la classe de
base qui renvoie : ";
return l*h; }
};
//définition de la classe triangle
class triangle:public polygone{
public:
double surf(void){
cout<<"Appel de la fonction surf de la classe de
base qui renvoie : ";
return l*h/2;
}
};
int main(){
//commentez ici
rectangle R;
triangle T;
polygone P;
//commentez ici
polygone *ptP1=&R;
polygone *ptP2=&T;
polygone *ptP3=&P;
//commentez ici
ptP1->valorise(5, 3);
ptP2->valorise(2.5, 1.5);
ptP3->valorise(10, 6);
//commentez ici
cout<<ptP1->surf()<<endl;
//commentez ici
cout<<ptP2->surf()<<endl;
//commentez ici
cout<<ptP3->surf()<<endl;
return 0;
}

40
EXERCICE 4 :

1. Définir une classe Copain pour constituer un répertoire téléphonique avec les attributs
suivants : nom, prenom, tel.
2. Définir à l’aide des propriétés les méthodes d’accès aux différents attributs de la classe.
3. Définir un constructeur permettant d’initialiser les attributs de la méthode par des valeurs
saisies par l’utilisateur.
4. Définir la méthode Afficher( ) permettant d’afficher les informations du copain en cours.
5. Écrire un programme testant la classe Copain. Les valeurs des attributs des objets seront
renseignées par l’utilisateur de votre programme.
Attention : l’action de la modification du numéro de téléphone doit être soumise à la
réponse à la question suivante : « Voulez-vous vraiment modifier le numéro de
téléphone ? (O/N) »

EXERCICE 5 :

1. Définir une classe Employé caractérisée par les attributs : Matricule, Nom, Prénom, DateNaissance,
DateEmbauche, Salaire.
2. Définir à l’aide des propriétés les méthodes d’accès aux différents attributs de la classe.
3. Définir un constructeur permettant d’initialiser les attributs de la méthode par des valeurs saisies par
l’utilisateur.
4. Ajouter à la classe la méthode Age( ) qui retourne l’âge de l’employé.
5. Ajouter à la classe la méthode Anciennete( ) qui retourne le nombre d’années d’ancienneté de
l’employé.
6. Ajouter à la classe la méthode AugmentationDuSalaire( ) qui augmente le salaire de l’employé en
prenant en considération l’ancienneté.

NB : Si Ancienneté < 5 ans, alors on ajoute 2%. - Si Ancienneté < 10 ans, alors on ajoute 5%. - Sinon, on
ajoute 10%.

7. Ajouter la méthode AfficherEmployé() qui affiche les informations de l’employé comme suit :

- Matricule : […]

- Nom complet : […]

- Age : […]

- Ancienneté : […]

- Salaire : […]

41
Ecrire un programme de test pour la classe Employé.

42

Vous aimerez peut-être aussi