Vous êtes sur la page 1sur 74

COURS PROGRAMMATION ORIENTEE

OBJETS
APPLICATIONS EN C++
Version 1.2 /09 Novembre 2005

ALI BEN NEJMA


INTRODUCTION

Historique :

Dans un premier temps vinrent des langages bas niveau: les assembleurs. Ces langages sont
spécifiques à chaque machine et s'adressent directement aux composants constituant celle-ci.
C'est en fait une traduction directe du train binaire en instructions simples. (ex: charger en
mémoire une valeur, lire une valeur, faire une addition,...)
Ensuite, dans un souci de simplicité, les langages devinrent de plus en plus complexes,
proches de l'anglais écrit. Se succédèrent alors: BASIC, COBOL, FORTRAN. Notons que
plus un langage est dit évolué plus il devient spécifique et facile d'utilisation (nous parlons ici
uniquement de la syntaxe).

En 1972, Dennis Ritchie créa le C ancêtre du C++. Ce langage peut être qualifié à la fois de bas
niveau car il permet de faire appel à toutes les ressources de la machine mais aussi de langage évolué.
En effet, il introduit une syntaxe assez complexe par rapport aux langages précités ainsi que de
nombreuses fonctions évoluées. Ces fonctions sont regroupées en librairies que tu peux considérer
comme des boîtes à outils.

La mise à jour la plus marquante du C fut apportée par Bjarne Stroustup en 1982. Il y intégra la
programmation objet. Le C++ est en fait une surcouche du C (C++ signifie une incrémentation du C).
Il hérite donc de tous les outils du C

Développé dans les laboratoires d’AT&T Bell au début des années 1982 par
Bjarne Stroustrup, le langage C++ est un langage:
➱ À typage fort,
➱ Compilé (impératif),
➱ Et orienté objet (POO1).
Schématiquement:
C++ = C + typage fort + objets (classes)

Remarquons toutefois que C++ n’est pas purement objet (comme le sont par exemple
Eiffel ou
Smalltak) mais est un langage hybride: on peut très bien programmer en C++ sans pour autant
programmer par objets

Avantages :
• Programmes exempts de bogues «syntaxiques», et [un peu] plus robuste, grâce au typage
fort
• Applications efficaces grâce à la compilation
• Compilateur disponible sur pratiquement toutes les plate-formes et documentation
abondante, grâce à sa large diffusion

Inconvénients :

POO & C++ Page 2


• Effets indésirables et comportement peu intuitif, conséquence de la production automatique
de code.
• Syntaxe parfois lourde et peu naturelle.
• Le langage ne définit pas de techniques de récupération automatique de mémoire (garbage
collector) comme c’est le cas en JAVA

Pour pouvoir être compilé en une séquence binaire exécutable, le code source doit fournir au
compilateur un «point d’entrée». Par convention, ce point d’entrée est en C++ une fonction
intitulée main:

Programme I.1

hello.CPP

#include <iostream.h>
void main()
{
cout << "Hello World !"
<< endl;
}
Hello.exe

0100100101000101110101001
1010101010010100101001010
0000101010100000010001000
0001001001110101010010011
1001000000000000100101011
...

cpp –oxxx hello.cpp hello

CHAPITRE I : UN PEU DE C++

POO & C++ Page 3


I.1. Structures de base

bool : booléen
char : caractère
int : entier
long : entier long
float : reel
double : reel en double precision (2 mots machine)
long double : reel en double precision (3 ou 4 mots machine)
Les constants caractère :
‘a’,’G’,… ‘\n’, ‘\t’, ‘\a’

Chaines de caractères : “ Programmation Orientée Objet” double quote

I.2. Opérateurs et Expressions

Arithmétiques
( + ): addition
( - ): soustraction
( * ) : multiplication
( / ) : division
( %) : modulo

Relationnels :
Comparaison > >= <= <
Égal different == / !=
Négation !
ET logique : &&
OU logique : || (touche ALT GR+6)

L’affectation
• a=1;
• b=2+3;
• c=a+b;

Incrémentation/décrémentation
a=5; b=6;
c=++a-b;
exemple I.2.1
#include <iostream.h>
void main()
{
int a=5;
int b=6;

POO & C++ Page 4


int c=++a-b;

cout <<"LE RESULTAT VAUT : "<<c<<"\n";


}

I.3. Modifier la valeur d’une variable :

exemple I.3.1
#include <iostream.h>
void main()
{
int a=10;
a+=6;
cout <<" a vaut :"<<a<<"\n";
}

I.4. Expressions conditionnelles

Expr1?expr2:expr3

exemple 1.4.1
#include <iostream.h>
void main()
{
int z,a,b,c;
a=20;
b=15;
(a>b)?(a++) : b--;
z=(a>b)?a:b;

cout <<" a vaut : "<<a<<endl;


cout <<" b vaut : "<<b<<endl;
cout <<" z vaut : "<<z<<endl;
}

I.5. Structuration d’un programme C++

C++ a une structure modulaire


Chaque module peut être compile séparément.
Chaque module est formé de variables et de fonctions.
Dans l’ensemble de ces module une function particulière doit exister si on veut executer
main() : appelée souvent programme principal : point d’entrée à l’exécution du programme
(expression plus correcte)

C++ est appelé multi-paradigmes car il permet :


- La programmation objet pure
- La programmation procédurale basée traitement

(contrairement à JAVA où tout est objet)

POO & C++ Page 5


I.6. Instructions et blocs

- Chaque instruction se termine par un “ ; ”


- Un bloc est une suite d’instructions délimités par { et }
- A l’intérieur de tout bloc on peut definer des variables locales à ce bloc

exemple I.6.1
# include <iostream.h>
void main()
{
int n;
n=10;
if (n>0)
{
int cumul=0;
cumul+=n;
cout <<" cumul vaut :"<<cumul<<endl;
}
// cout <<" cumul vaut :"<<cumul<<endl; // ERREUR DE
COMPILATION !!! error C2065: 'cumul' : undeclared identifier

I.7. STRUCTURE CONDITIONNELLE


IF…THEN…ELSE

if (<expression>)
<instruction-1>
[else
<instruction-2> ]

SWITCH

switch (<expression>) {
case <constante-1> : <suite d’instructions> break;
case <constante-2> : <suite d’instructions> break;
case <constante-n> : <suite d’instructions> break;
default : <suite d’instructions>
}
Remarque : Si on ne met pas “break”, l’exécution va continuer à la suite au lieu de sortir du
switch,

I.8. GESTION DYNAMIQUE DE LA MÉMOIRE


Le langage C++ offre de nouvelles possibilités de gestion dynamique de la mémoire : 2
nouveaux opérateurs new et delete.

L'opérateur new
new : Alloue de l'espace mémoire et fournit comme résultat l'adresse

POO & C++ Page 6


int * ip;

char * cp;

compte *c; /*un pointeur sur un objet de la classe compte*/

ip = new int;

cp = new char;

*ip = 35;

*cp = 'f';

initialisation
Nous pouvons initialiser un pointeur lors de sa déclaration
double *dp = new double (3.14);

/* c'est équivalent à

double * dp;

dp = new double;

*dp = 3.14;

*/

allocation d'un tableau dynamique


int * pb;

pb = new int [10]; //alloue une mémoire pour 10 entiers et affecte

//au pointeur l'adresse du début

pb [5] = 32;

*(pb + 5) = 32;

Remarque:

Il est interdit d'initialiser un bloc lors de son allocation.

Exemple:
int * tpb = new int [6] (0,0,0,0,0,0); //erreur

//mais on doit faire par exemple:

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

tpb[i]=0;

Tableaux dynamiques à plusieurs dimensions


Nous pouvons allouer des tableaux dynamiques à plusieurs dimensions (autant qu'on veut)

POO & C++ Page 7


Exemple:
int * p2 = new int [3,5];

L'opérateur Delete
delete: Libère l'espace mémoire alloué par new.
delete pointeur;

delete ip;

delete c;

Pour libérer un tableau:

delete []pb;

Les fonctions
Théoriquement, toute fonction retourne une valeur, qui peut être utilisée ou non. Touefois, un
mot clé particulier void, permet d’indiquer qu’une fonction ne retourne pas de valeur (c’est
une procédure).
L’entête d’une fonction est appelé signature, les paramètres sont de 2 types par valeur ou par
référence. L’exemple qui suit montre ce mode de passage.

#include <iostream.h>
void IncrementerRef(int& a)
{
a++;
}
void IncrementerVal(int a)
{
a++;
}
void main()
{
int b=5;
IncrementerRef(b);
cout<<"Après {IncrementerRef} b vaut maitenant :"<<b<<endl; //6
IncrementerVal(b);
cout<<"Après {IncrementerVal} b vaut maitenant :"<<b<<endl; //6
}

La bibliothèque d’E/S :
Pour utiliser cette bibliothèque il faut inclure le fichier de déclarations

#include <iostream.h>
Les opérations standards d’entrée et de sortie sont fournies par 3 flots (streams):
- cin : désigne le flot d’entrée standard
- cout : flot de sortie standard

POO & C++ Page 8


Différences entre C et C++
Nous allons parler ici d'un certain nombre de différences existant entre le C et le C++. Nous
pourrions d'ailleurs plutôt utiliser le terme d'incompatibilités.

Les fonctions
Les fonctions en C peuvent être définies suivant deux modèles :

int CalculeSomme ( a, b ) int CalculeSomme ( int a, int b )


{
int a; ... /* Fonction */
int b; }
{
... /* Fonction */
}
Il faut simplement savoir que le C++ n'accepte que la seconde méthode.

Const
Le C++ a quelque peu modifié l'utilisation "C" de ce qualificatif. Pour rappel, "const" est
utilisé pour définir une variable constante. C'est une bonne alternative à un define.
La portée en C++ est désormais plus locale. En C, un const permettait pour une variable
globale d'être "visible" partout. C++ limite quant à lui la portée d'une telle variable, au fichier
source contenant la déclaration.

En C/C++ l’utilisation de const signifie qu’un identicateur correspond à quelque chose dont la
valeur ne peut pas changer.

Lorsque const est appliqué à une variable locale automatique, la portée est limitée au bloc
dans lequel s’est effectuée la déclaration.

Lorsque const est appliqué à une variable globale, C++ limite la portée au fichier source.
Exemple :

Soit le fichier calcul.cpp

const int N=1;


int somme(int a,int b)
{
return a+b+N; // OK pas d’erreur de compilation
}

Dans le fichier main.cpp :

#include <iostream.h>
#include "calcul.cpp"

void main()
{
cout <<somme(10,20)<<endl;
cout<<N;
}

Résultat : error C2065: 'N' : undeclared identifier

POO & C++ Page 9


Interprétation : la portée de N ne dépasse pas le fichier calcul.cpp
Remarque : Une expression constante est une expression dont la valeur peut être calculée à la
compilation.

const int MAX = 100;


double tab1[2*MAX+1], tab2[2*MAX+1][MAX];

Les commentaires
Les commentaires d’une source peuvent maintenant être indiqués de deux façons différentes :

/* Ceci est commentaire "typique" venant du C


*/
int nEntier; // Et ceci est la seconde
possibilité

Ces nouveaux commentaites sont utilisables uniquement dans le cas où tout le reste de la ligne
est un commentaire.

Déclarations

En C, vous avez été habitué à déclarer les variables en début de bloc, c'est-à-dire en début de
fonction ou de procédure. En C++, il est possible de déclarer une variable à tout moment dans
le code.

/* Code C */ // Code C++


int FaitQqch( int a, int b) int FaitQqch( int a, int b)
{ {
int nRetour; int fVar = a + b;
int i;
int fVar; for( int i=0; i<20; i++ )
{
fVar = a + b; fVar = fVar + i;
for( i=0; i<20; i++ ) }
{
fVar = fVar + i; int nRetour = fVar - a*b;
} return nRetour;
}
nRetour = fVar - a*b;
return nRetour;
}

Cet exemple est bien entendu dénué de tout intérêt, mais il montre la liberté offerte par C++
en ce qui concerne les déclarations et les initialisations des variables.

Référence

C++ introduit une nouvelle notion fondamentale : les références. C'est une notion qui peut
sembler difficile à assimiler au départ, notamment pour des personnes qui ne sont pas encore
habituées à utiliser des pointeurs en C.

La référence est un nouveau type qui permet de manipuler un alias sur une autre variable
existante.

POO & C++ Page 10


La notion de référence est proche de la notion de pointeur : une variable de type pointeur
pointe sur une autre entité, une variable de type référence fait référence à une autre entité.
Référence = pointeur caché.

Déclaration :

Une référence s’effectue toujours sur une entité d’un type donné.
Une référence s’écrit : type & ;
Exemple :

int & : référence sur un int ;


int * & :référence sur un int *.

int variable;
int & ref = variable; //ref est une variable de type référence sur un int.
int *ptr = &variable; //On récupère l’adresse de variable.

Une variable de type référence doit toujours être initialisée lors de sa déclaration.

Exemple :

//Declaration incorrecte
int & nombre;
//Declaration correcte
int number;
int & nombre = number;

La notion de référence est directement liée au passage de paramètres à des fonctions en C.


Nous savons tous que lorsque nous voulons transmettre à une fonction la valeur d'une variable
ou au contraire la donnée réelle (en fait l'adresse), nous n'utilisons pas les mêmes méthodes.
Par exemple, considérons les fonctions suivantes :

int FaitQqch( int a, int b) int FaitQqch2( int * a, int * b)


{ {
int nRet; int nRet;
if( a==0 ) if( *a==0 )
a=10; *a=10;
nRet = a + b; nRet = *a + *b;

return nRet; return nRet;


} }

Nous voyons tout de suite que dans le cas d'un appel comme celui-ci :

...
int a, b, c, d;
a = 0;
b = 5;
c = FaitQqch(a,b);
d = FaitQqch2(&a,&b);
...

POO & C++ Page 11


c et d auront la même valeur, par contre, ce qui est intéressant, c'est qu'à la sortie de
FaitQqch, la valeur restera inchangée, alors que pour FaitQqch2, a vaudra désormais 10 !
Ceci est dû au passage par adresse, et non par valeur.
Tout cela, vous devez le savoir. En revanche, vous allez apprendre une nouvelle technique qui
est une sorte de mélange des deux précédentes : les références.
Voici ce que devient notre fonction, FaitQqch3 :

int FaitQqch3(int &a, int &b)


{
int nRet;
if( a==0 )
a=10;
nRet = a + b;

return nRet;
}

Le "&" signifie que l'on passe par référence. La ligne de déclaration de la fonction est en fait
la seule différence avec une transmission par valeur, d'un point de vue code. C'est-à-dire que
l'utilisation des variables dans la fonction s'opère sans "*" et l'appel à la fonction sans "&".
C'est ce qui fait la puissance des références : c'est transparent pour l'implémentation, mais cela
possède la puissance des pointeurs.
Nous nous habituerons à leur utilisation au fur et à mesure.

Opérateurs NEW et DELETE

En plus des "anciens" malloc et free du C, C++ possède un nouveau jeu d'opérateurs
d'allocation/désallocation de mémoire : new et delete.
Ils ont été créés principalement pour la gestion dynamique des objets, mais on peut les utiliser
également pour des variables simples.
Voici une comparaison d'utilisation :

...
// pour un simple pointeur
...
/* pour un simple pointeur */
int * pInt; int * pInt;
pInt = (int*)malloc(1*sizeof(int)); pInt = new int;
free(pInt); delete pInt;
... ...
/* pour un tableau */ // pour un tableau
pInt = (int*)malloc(100*sizeof(int)); pInt = new int[100];
delete pInt;
...
free(pInt);
// Tableau de classes
...
pToto = new MyClass[50];
delete []MyClass;

POO & C++ Page 12


Vous remarquerez donc tout de suite les différences, qui sont évidentes. Insistons simplement
sur la désallocation à l'aide de l'opérateur delete, qui change suivant que le pointeur est
simple, ou bien qu'il correspond à un tableau lorsqu'il est composé d'objets.

Les entrées/sorties en C++


Le langage C++ dispose de nouvelles routines d'entrées/sorties qui sont plus simples à utiliser.

La sortie standard "cout" :

// include indispensable pour cout


#include <iostream.h>

void main()
{
cout << "Hello World !";
cout << "Hello World !\n";
cout << "Hello World !" << endl;

int n = 5;
cout << "La valeur est " << n << endl;

float f = 3.14f;
char *ch = "Coucou";
cout << ch << " float = " << f << endl;
}

Ce programme donne en sortie :

Hello World !Hello World !


Hello World !
La valeur est 5
Coucou float = 3.14

Cette nouvelle sortie standard est donc très intuitive à employer du fait qu'il est inutile de lui
préciser le format de la valeur que l'on souhaite afficher.
Le "endl" est en fait disponible pour éviter d'éventuels "\n", en fin de ligne.

L'entrée standard "cin" :

POO & C++ Page 13


// include indispensable pour cout et cin
#include <iostream.h>

void main()
{
int n;
cout << "Entrez un entier : ";
cin >> n;
cout << "Vous avez entré : " << n << endl;

char ch[81];
float f;
cout << "Entrez un entier, une chaine, puis un float :";
cin >> n >> ch >> f;
cout << "Vous avez entré : " << n << ch << f << endl;
}

Cet exemple illustre brièvement comment fonctionne "cin". Bien entendu, aucun contrôle de
type n'est effectué, c'est donc à l'utilisateur qu'il advient de faire attention.

POO & C++ Page 14


CHAPITRE II: LES OBJETS
II.1.PRÉSENTATION DU MONDE DES OBJETS:

• Lorsque des objets ont les mêmes attributs et comportements: ils sont regroupés dans une
famille appelée: Classe.

• Une classe est un modèle à partir duquel on peut générer de nouvelles classes ou de
nouveaux objets. Un objet est une instance d’une classe.

En informatique : Un objet est constitué par l’association d’une quantité de mémoire,


organisée en champs et d’un ensemble de fonctions destinées principalement à la
consultation et à la modification des variables de ce champs.

Voici un exemple d’une conception de la hiérarchie d’objets moyens de transport avec une
vision informatique de ce domaine :

POO & C++ Page 15


POO & C++ Page 16
POO & C++ Page 17
Sachez que:

• Le monde réel est constitué de très nombreux objets en interaction.


• Réduire cette complexité = regrouper les éléments qui se ressemblent et à distinguer des
structures de plus haut niveau, débarrassées de détails inutiles.
• Une Classe est un ensemble d’objets ayant la même structure, le même comportement, les
mêmes relations et la même sémantique.

Classe & objets

• La classe décrit le domaine de définition d’un ensemble d’objets.


• Chaque objet appartient à une classe.
• Les objets informatiques sont construits à partir de la classe, par un processus appelé
instantiation.

Contrat

• Une classe passe un contrat avec d’autres classes elle s’engage à fournir les services publiés
dans sa spécification et les autres classes s’engagent à ne pas faire usage de connaissances
autres que celles connues dans cette spécification.

EN C++:

Une classe est une structure. Sauf que :


• le mot réservé class remplace struct
• Certains champs sont des fonctions.

POO & C++ Page 18


exemple II.1.1
#include <iostream.h>
class Points{

public: // voici les attributs


int x;
int y;

public : // voici les méthodes


void afficher()
{
cout <<x<<','<<y<<endl;
}
void placer (int a ,int b)
{
x=a;
y=b;
}
};

Commentaires :
Les champs x et y sont les attributs de classes, appelés aussi variables membres.
Les fonctions Afficher et Placer sont appelées méthodes ou fonctions membres.

II.2. ACCÈS AU MEMBRE D’UN OBJET:


Cas de déclaration statique

exemple II.2.1
#include <iostream.h>
#include "Points.cpp"
void main()
{
Points p;
p.x=10;
p.y=20;
p.afficher();
p.placer(1,5);
p.afficher();

Cas d’allocation Dynamique

exemple II.2.2
#include <iostream.h>
#include "Points.cpp"
void main()
{
Points *p=new Points;
p->x=10;
p->y=20;
p->afficher();
p->placer(1,5);
p->afficher();

POO & C++ Page 19


}

Objets transmis en argument d'une fonction membre

Nous pouvons maintenant imaginer vouloir comparer deux points, afin de savoir s'ils sont
égaux. Pour cela, nous allons mettre en oeuvre une méthode "Coincide" qui renvoie "1"
lorsque les coordonnées des deux points sont égales :

class Point
{
int x;
int y;

public :
int Coincide(Point p)
{
if( (p.x==x) && (p.y==y) )
return 1;
else
return 0;
}

Cette partie de programme fonctionne parfaitement, mais elle possède un inconvénient majeur
: la passage de paramètre par valeur, ce qui implique une "duplication" de l'objet d'origine.
Cela n'est bien sûr pas très efficace.
La solution qui vous vient à l'esprit dans un premier temps est probablement de passer par un
pointeur. Cette solution est possible, mais n'est pas la meilleure, dans la mesure où nous
savons fort bien que ces pointeurs sont toujours sources d'erreurs (lorsqu'ils sont non
initialisés, par exemple).

La vraie solution offerte par le C++ est de passer par des références. Avec ce type de passage
de paramètre, aucune erreur est possible puisque l'objet à passer doit déjà exister (être
instancié). En plus, les références offrent une simplification d'écriture, par rapport aux
pointeurs :

#include <iostream.h>

class Point
{
int x;
int y;

public :
int Coincide(Point & p)
{
if( (p.x==x) && (p.y==y) )
return 1;
else
return 0;
}

POO & C++ Page 20


void main()
{
Point p;
Point pp;

pp.x=0;

pp.y=1;

p.x=0;

p.y=1;

if( p.Coincide(pp) )
cout << "p et pp coincident !" << endl;
if( pp.Coincide(p) )
cout << "pp et p coincident !" << endl;
}

Accès à sois meme (opérateur this) :

Utilisation de this : lorsque l’on a besoin de pointer l’objet à l’intérieur de la classe même.
This peut toutefois être utilisé partout à l’intérieur de la classe pour accéder aux attributs.

exemple II.2.3
#include <iostream.h>
class Points{
public :
void afficher()
{
//Notation Inutile avec this
cout <<this->x<<','<<this->y<<endl;
}
void placer (int a ,int b)
{
x=a;
y=b;
}
bool PointEgal (Points *p)
{
return(p==this);
}
bool PointCoincidant(Points *p)
{
return ((p->x==this->x)&&(p->y==this->y));
}
public:
int x;
int y;
};
Utilisation dans le fichier main.cpp :

POO & C++ Page 21


#include <iostream.h>
#include "Points.cpp"
void main()
{
Points *p=new Points;
Points *p1=new Points;
p1->x=1;
p1->y=15;
p->x=10;
p->y=20;
p->afficher();
p->placer(1,5);
p->afficher();
cout <<p1->PointEgal(p)<<endl;
cout <<p1->PointCoincidant(p)<<endl;
}

Membres publics et privés :

Par défaut les membres sont privés.


exemple II.2.4
#include <iostream.h>
class Points{
int couleur; //membre privé
void colorier(int pcouleur) //fonction membre publique
{
couleur=pcouleur;
}
public :
void afficher()
{
//Notation Inutile avec this
cout <<this->x<<','<<this->y<<endl;
}
void placer (int a ,int b)
{
x=a;
y=b;
}
bool PointEgal (Points *p)
{
return(p==this);
}
bool PointCoincidant(Points *p)
{
return ((p->x==this->x)&&(p->y==this->y));
}
public:
int x;
int y;
};

Programme principal

#include <iostream.h>
#include "Points.cpp"
void main()
{

POO & C++ Page 22


Points *p=new Points;
Points *p1=new Points;
p1->x=1;
p1->y=15;
p1->couleur=1; // erreur de compilation
p1->colorier(10); // erreur de compilation

Commentaires :
• L’attribut couleur est privé
• La méthode colorier est privée

Les lignes :

p1->couleur=1; // erreur de compilation


p1->colorier(10); // erreur de compilation

provoquent à la compilation le message suivant :


error C2248: 'couleur' : cannot access private member declared in class 'Points'
error C2248: 'colorier' : cannot access private member declared in class 'Points'

II.3. NOTION DE VISIBILITÉ


La visibilité d’une caractéristique détermine si d’autres classes peuvent l’utiliser directement.
On utilise 3 niveaux de visibilité:
+ (Public)

Toute classe extérieure(ou module) ayant une visibilité sur la classe donnée peut utiliser cette
caractéristique.

# (Protected)
Seuls les descendants (qu’on verra plus tard) de la classe peuvent utiliser cette caractéristique.

- (Private)
La classe, et elle seule, peut utiliser cette caractéristique.

Intéressant!!
Les développeurs s’échangent des classes et ne risquent pas d’accéder par erreur à un
membre privé: c’est une protection.

II.4. L'ENCAPSULATION – DÉFINITION :


L'encapsulation ou masquage d'information consiste à séparer les aspects externes d'un
objet accessibles pour les autres, qu'on désigne par publique, des détails d'implémentation
interne, rendus invisibles aux autres, qu'on désigne par privé.

Ainsi l'implémentation d'un objet peut être modifiée sans affecter les applications qui
emploient cet objet. Voici une figue caractéristique :

POO & C++ Page 23


Exemple : Représentation d’une motocyclette !

Le motard ne doit pas connaître la mécanique pour piloter sa motocyclette.

Il dispose de commandes, interfaces, pour démarrer, accélérer et freiner, lui permettent d’utiliser sa
motocyclette.

Pour connaître la vitesse, qui est une propriété de la motocyclette, il dispose d’une autre interface: le
compteur de vitesse.

POO & C++ Page 24


Un autre exemple en C++

class Cpersonne{
public:
char Nom [50],
Prenom [50];
private:
char Adresse [100];
public :
int Age;
void Changer_adresse (char nouv_adresse[])
{ strcpy(Adresse, nouv_adresse); }
};

Nous supposons qu'un programme P utilise des objets de la classe Cpersonne il ne peut
utiliser que les attributs: Nom, Prénom, Age et l'opération Changer_adresse parce que
Adresse est déclarée comme PRIVATE soit:

PROGRAMME P

int main()
{ Cpersonne Per;
strcpy(Per.Nom,"Ben Abdallah");
strcpy(Per.prénom,"Mohamed" );
Per.Age=25;
Per.Changer_Adresse("5, rue des fleures Tunis");
return 0;
}

Nous supposons que l'implémentation de Cpersonne a changé comme suit:


class Cpersonne
{public:
char Nom [50], Prenom [50];
private:
struct t_adresse {
int numero;
char rue_av[75];
char ville [20];
int code;
} Adresse;
public:
int Age;
void Changer_adresse (char nouv_adresse[])
{ Adresse.numero = texte_avant_virgule(nouv_adresse);
strcpy(Adresse.rue_av , texte_après_virgule (nouv_adresse));
strcpy(Adresse.ville , texte_fin_apres_blanc(nouv_adresse));
Adresse.code=dernier_nombres(nouv_adresse);}
} ;

Nous remarquons que ce changement n'affecte pas le programme P qui utilise des objets de
cette classe. Il peut continuer à utiliser Per.Changer_Adresse("5, rue des fleures
Tunis"); sans savoir qu’une modification a été portée. Il n’a pas besoin de le savoir !

POO & C++ Page 25


L’association de membres et de fonctions au sein d’une classe avec la possibilité de rendre
privés certains membres (attributs ou méthodes) est le principe de l’encapsulation.

Donc: pour les membres privés on trouve:


Les attributs: par défaut privés on les déclare privés pour les protéger.
Contre qui ?
- Peut être contre soi-même ou les membres d’une équipe de développement ou même
le grand public des développeurs :
o on ne veut pas avoir un doute que dans un module quelconque on change l’état
de l’objet par une façon autre que prevue.
o On veut cacher le détail d’implémentation de la classe soit pour une raison de
simplicité soit pour des raisons commerciales … etc
o Prendre l’exmple de la DLL (public des développeurs)

Qui peut accéder aux membres PRIVATE? : seuls les fonctions membres de la classe qui en
font appel.

EXERCICE:
Faire le commentaire de ce programme.
exemple II.4.1
class Employe{
public:
float getSalaireNet()
{
setImpots();
setPrime();
return 12*SalBase+Impots;
}

void setSalBase(float SalBase)


{
this->SalBase= SalBase;
}
void setTauxImpots(float TauxImpots)
{
this->TauxImpots=TauxImpots;
}
private:
void setImpots()
{
Impots=TauxImpots*Impots/100;
}
void setPrime()
{
Prime=(SalBase-Impots)/4;
}
public:
float SalNet;//Salaire Net
char* Nom;
char* Fonction;
private:
float SalBase;
float Impots;
float TauxImpots;
float Prime;
};

POO & C++ Page 26


Programme principal

#include <iostream.h>
#include "Employe.cpp"
void main()
{
Employe *emp=new Employe;
emp->setTauxImpots(25);
emp->setSalBase(500.000);
float SalNet=emp->getSalaireNet();
cout<<"Le salaire de l'employé vaut:"<<SalNet;
}

Remarquer les méthodes privées: setImpots(),setPrime();


Les attributs privés:

private:
float SalBase;
float Impots;
float TauxImpots;
float Prime;

DISCUSSION?

II.5. DEFINITION DES CLASSES/FICHIERS ENTETE


Une fonction qui ne retourne rien en C++

Pour des raisons de clarté et d’organisation du code, il est possible en C++ (et non en C) de
définir une fonction en 2 étapes :
1- Prototype de la fonction dans un fichier .h (non compilé)
2- Implémentation de la fonction dans un fichier .cpp

Exemple :
Fichier Calcul.h
int somme(int a,int b);

Fichier Calcul.cpp
#include "calcul.h"
int somme(int a,int b)
{
return a+b;
}

Fichier main.cpp
#include <iostream.h>
#include "calcul.h"
void main()
{
cout <<somme(10,20);
}

POO & C++ Page 27


Définition séparée et opérateurs de résolution de portée

Tous les membres d’une classe sont déclarées normalement à l’intérieur du bloc class
<NomClasse>
{ ….. }
Cependant dans le cas des fonctions, aussi bien publiqus que privées,on peut se limiter à
n’écrire que leur en-tête à l’intérieur de la classe et définir le corps ailleurs, plus loin dans le
même fichier ou dans un fichier séparé.
Il faut un moyen pour indiquer qu’une définition de fonction, écrite en dehors de toute classe,
est en réalité la définition d’une fonction membre d’une classe. Ce moyen s’appelle
opérateur de résolution de portée, dont la syntaxe est
NomDeClasse::

Fichier Points.H : appelé fichier d’entête

exemple II.5.1
class Points{
int couleur;
void colorier(int pcouleur);

public :
void afficher();

void placer (int a ,int b);

bool PointEgal (Points *p);

bool PointCoincidant(Points *p);

public:
int x;
int y;
};

Fichier Points.cpp : appelé fichier d’implémentation

// Points.cpp: implementation of the Points class.


//
//////////////////////////////////////////////////////////////////////
#include <iostream.h>
#include "Points.h"

void Points::colorier(int pcouleur)


{
couleur=pcouleur;
}
void Points::afficher()
{
//Notation Inutile avec this
cout <<this->x<<','<<this->y<<endl;
}

void Points:: placer (int a ,int b)

POO & C++ Page 28


{
x=a;
y=b;
}
bool Points:: PointEgal (Points *p)
{
return(p==this);
}
bool Points:: PointCoincidant(Points *p)
{
return ((p->x==this->x)&&(p->y==this->y));
}

Remarque : L’opérateur de résolution de portée est génèralement utilisé dans sa forme


binaire, mais il peut être utilisé dans sa forme unaire. Dans ce cas la portée c’est le fichier et
non la classe.
#include <iostream>
enum E_booleen{FAUX, VRAI};
// Variable globale
E_booleen verite;
class C_booleen
{
private :
E_booleen verite;
public :
void set_bool(E_booleen);
E_booleen get_bool();
void affiche();
};
void C_booleen::set_bool(E_booleen v)
{
::verite = v; // C’est la variable globale (forme unaire : la portée
considérée est le fichier)
verite = v; // C’est l’attribut (forme binaire, la portée est la classe)
}

II.6. LA SURCHARGE:
La surcharge est une des nouvelles techniques permettant d’améliorer la réutilisabilité en
conception Objet. Elle permet d’attribuer le même nom à plusieurs opérateurs ou à plusieurs
fonctions. L’ambiguïté sur la fonction appelée est alors levée après examen du contexte, c’est
à dire du nombre et/ou des types de paramètres. Cette technique est disponible en C++, qui
offre ainsi la possibilité de définir plusieurs fonctions portant le même nom, à la condition que
ces fonctions aient des profils différents.

La surcharge : overloading permet à plusieurs méthodes ou constructeurs de partager le


même nom mais une signature différente (paramètres de la fonction)

Exemple en C++ (en général):


#include <conio.h>
#include <math.h>

void affiche(int x)
{

POO & C++ Page 29


Cout<<x;
}

void affiche(float z)
{
Cout<<z;
}

void affiche(char *chaine)


{
Cout<<chaine ;
}

void main()
{
int d = 10;
float pi = atan(1) * 4;

affiche(d);
affiche(pi);
affiche("Fin de l'exemple");
}

Utilisation de la surcharge dans les classes en C++:

On considère la classe LeMax qui a pour objectif de donner le maximum entre :


- 2 entiers
- 2 chaines de caractères
- La taille d’un vecteur comparée à un entier

Fichier LeMax.h

exemple II.12.1
#include <iostream.h>
#include <vector>
using std::vector;
class LeMax{
public:
int max(const int a,const int b);
char* max( char *a, char *b);
int max(vector<int> tab,int taille); // ne vous perturbez pas par
le type vector<int> ! analyser juste la surcharge !

};

Fichier Surcharge.cpp

#include " LeMax.h"


#include <string.h>
#include <vector>
using std::vector;

int LeMax::max(int a,int b)


{
return a>b?a:b;
};

POO & C++ Page 30


char* LeMax::max( char *a, char *b)
{
return strlen(a)>strlen(b)?a:b;
};
int LeMax::max(vector<int> tab,int taille)
{
return tab.size()>taille?tab.size():taille;
}

Fichier main.cpp

#include " LeMax.h"


#include <iostream.h>
#include <vector>
using std::vector;
void main()
{

LeMax *s=new LeMax;

// Première Utilisation de la surcharge


char *StrMax=s->max("ALI","SALAH");
cout<<"Le Mot Le Plus long : "<<StrMax<<endl;

// Deuxième Utilisation de la surcharge


int x=s->max(1,5);
cout<<"L'entier Le Plus grand : "<<x<<endl;

//Troisième utilisation de la surcharge


vector<int>tab;
tab.push_back(1); // C’est nouveau ! mais c’est intéressant
tab.push_back(2);
int y=s->max(tab,10);
cout <<"Le maximum entre la taille du tableau et 10 c'est
"<<y<<endl;
}

EXERCICE : on veut reprendre l’exemple du point et construire les méthodes suivantes en


surcharge :
Public double distance (Point Autre); // calculer la distance entre l’objet en cours et
un autre point

Public double distance (double x,double y); //Calculer la distance entre l’objet en
cours et une autre coordonnée.

Arguments par défaut

Tout comme une fonction C++ classique, il est possible de définir des arguments par défaut.
Ceux-ci permettent à l'utilisateur de ne pas renseigner certains paramètres. Dans l’exemple
précédent, on peut écrire :
int LeMax::max(int a=0,int b=0)

Ainsi dans le programme principal on utilise cette méthode comme suit :


void main()

POO & C++ Page 31


{

LeMax *s=new LeMax;


Cout << s->max(); // par défaut a et b valent 0 ; donc renvoie 0
}

II.7. MÉTHODES D’ACCÈS, MÉTHODES DE MODIFICATION:


Les accesseurs : get<Nom Attribut> (get en minuscule par convention) sont les methodes de
lecture
Les Modificateurs(ou mutateurs) : set<Nom Attribut> ou autres fonctions qui modifient
l’état de l’objet, ce sont les méthodes d’écriture.

exemple II.7.1
class Rectangle{
public:
// Les constructeurs/////////////////////
Rectangle()
{
largeur=10;
hauteur=10;
}
Rectangle(int largeurInitiale,int hauteurInitiale)
{
largeur=largeurInitiale;
hauteur=hauteurInitiale;
}
/////////////////////////////////////////

////////////////// Fonctions de Calcul


int Perimetre()
{
return 2*(largeur+hauteur);
}
int surface()
{
return largeur*hauteur;
}
///////////////////////////////////////////

//////////// Les ACCESSEURS


int getLargeur()
{
return largeur;
}
int getHauteur()
{
return hauteur;
}
////////////////////////////////////////////

//////////// Les MODIFICATEURS


void setLargeur(int nouvelleLargeur)
{
largeur=nouvelleLargeur;
}
void setHauteur(int nouvelleHauteur)
{

POO & C++ Page 32


hauteur=nouvelleHauteur;
}
void retaille(int coef) // réutilisation d'autres méthodes
{
setLargeur(coef*largeur);
setHauteur(coef*hauteur);
}
////////////////////////////////////////////
private :
int largeur;
int hauteur;
};

L’OBJET RECTANGLE

II.8. LES CONSTRUCTEURS/DESTRUCTEURS :


II.8.1 CONSTRUCTEURS :

Un constructeur d’une classe est une fonction membre spéciale qui:


- a le même nom que la classe
- n’indique pas de type de retour
- ne contient pas d’instruction return

Le rôle d’un constructeur est d’initialiser un objet, notamment en donnant des valeurs aux
attibuts membres.

Tout objet doit avoir ses valeurs initiales positionnées.Un constructeur permet de fixer ces
valeurs à la création de l’objet.

Toute classe possède un constructeur par défaut implicite, il peut être redéfini.

Une classe peut avoir plusieurs constructeurs qui diffèrent par le nombre et la nature de leur
paramètres(paramètres d’entrée différents), c’est la surcharge de constructeur :

exemple II.6

POO & C++ Page 33


class Rectangle{
public:
Rectangle() // le constructeur est explicite
{
largeur=10;
hauteur=10;
}
Rectangle(int largeurInitiale,int hauteurInitiale) // un autre
constructeur
{
largeur=largeurInitiale;
hauteur=hauteurInitiale;
}
public :
int largeur;
int hauteur;
};

Appel dans le programme principal

#include <iostream.h>
#include "Rectangle.cpp"
void main()
{
Rectangle *rect=new Rectangle();
cout<<"LARGEUR : "<<rect->largeur<<endl;
cout<<"HAUTEUR : "<<rect->hauteur<<endl;
delete rect;
rect=new Rectangle(50,50);
cout<<"LARGEUR : "<<rect->largeur<<endl;
cout<<"HAUTEUR : "<<rect->hauteur<<endl;
delete rect;
}

Un constructeur de la classe C est appelé quand :


- Une variable globale de la classe C est définie. Il est appelé avant l’exécution du main
- Une variable statique de la classe C est définie ;(on verra plus tard)
- Une variable automatique de classe C est définie à l’intérieur d’un bloc et que la
localisation de cette définition est atteinte ;
- Une instance de la classe C est créée par l’opérateur NEW ;
- Une instance d’une classe donnée qui contient un attribut de type C est créée ;
- Une instance d’une classe dérivée à partir de C est créée (on verra plus tard)

Le clonage d’objets (ou constructeurs par recopie)

Attention : Bien comprendre l’affectation A=B avec A et B pointeurs sur objet

Si on affecte B dans A ceci se traduit par: A et B partagent la même adresse mémoire et donc
le même contenu :
exemple II.8.1
#include <string.h>
class PointNomme{
public:
PointNomme(int a,int b,char *s="")
{
x=a;

POO & C++ Page 34


y=b;
label=new char[strlen(s)+1];
strcpy(label,s);
}
public:
int x,y;
char *label;
};

Regardons le Programme principal:


#include <iostream.h>
#include "PointNomme.cpp"
void main()
{
PointNomme *a= new PointNomme(1,1);
PointNomme *b=a; // les deux objets a et b
//partagent les mêmes valeurs : affectation dangereuse!
b->label="Bonjour"; // regardez cette affectation !
a->label="Ali";
cout <<" La chaine de a est changé mais la chaine de b vaut aussi
:"<<b->label<<endl; //affiche Ali !

Commentaires
On remarque que nous avons effectué l’affectation avant de changer l’attribut label de a et
pourtant b->label a aussi changé!

Cependant si on cherche à faire la duplication de a dans le but de gérer séparément les objets
« égaux » pointés par a et b :

exemple II.8.2
#include <string.h>
class PointNomme{
public:
PointNomme(int a,int b,char *s="")
{
x=a;
y=b;
label=new char[strlen(s)+1];
strcpy(label,s);
}
// Constructeur à partir d’un objet existant : cloneur !
PointNomme(const PointNomme &p)
{
x=p.x;
y=p.y;
label=new char[strlen(p.label)+1];
strcpy(label,p.label);
}
public:
int x,y;
char *label;
};
#include <iostream.h>
#include "PointNomme.cpp"
void main()

POO & C++ Page 35


{
PointNomme *a= new PointNomme(1,1);
PointNomme *b=new PointNomme(*a);
b->label="Bonjour";
a->label="Ali";
cout <<" La chaine de a vaut :"<<a->label<<" et la chaine de b vaut
:"<<b->label<<endl;

Remarque : Ne pas confondre la problématique d’affectation des pointeurs avec les


affectations de variables objets déclarés statiquement.

Exercice :
Reprendre l’exemple précédent avec des déclarations statiques.

PointNomme *a= new PointNomme(1,1); PointNomme a (1,1);


PointNomme *b= new PointNomme(1,1); PointNomme b (0,2);

Puis tester a=b ;

Initialisation d’un attribut

L’initialisation (= affectation) d’un attribut se fait à l’aide du constructeur.


class C_entier
{
int nombre;
public :
C_entier (int n = 0) : nombre(n) {}
};

II.8.2. DESTRUCTEUR:

Un destructeur d’une classe donnée est une méthode exécutée automatiquement à chaque
fois qu’une instance de la classe donnée disparait.
L’identificateur est celui de la classe précédé du caractère ~ ;
- C’est une méthode sans type de retour ;
- C’est une méthode sans paramètre, elle ne peut donc pas être surchargée ;
- C’est une méthode en accès public.

class C_crible
{
private :
E_booleen *verite; // Adresse du 1er elts
unsigned nb_element; // Nbre d’elt ds le tableau
public :
// Les constructeurs
C_crible();
C_crible(unsigned);
C_crible(unsigned, E_booleen);
// Le destructeur
~C_crible();

POO & C++ Page 36


};
Le principe général est celui-ci: les objets “contemporains” (attachés à un même contexte,
créés par une même déclaration, etc..) sont détruits dans l’ordre inverse de leur création.
Dans notre exemple, la variable verite est d’abord détruite puis l’instance de la classe
C_Crible qui l’appelle.

Le destructeur d’une classe C (quelconque) est appelé implicitement à la disparition de


chaque objet de classe C.
- A la fin du main() pour toutes les variables statiques,locales au main() et les variables
globales.
- A la fin du bloc dans lequel la variable automatique C est déclarée.
- A la fin d’une fonction ayant un argument de classe C.
- Lorsqu’une instance de classe C est détruite par DELETE.
- Lorsqu’un objet qui contient un attribut de type classe C est détruit (on verra plus tard
les objets membres).
- Lorsqu’un objet d’une classe dérivée de C est détruit (on verra plus tard la notion
d’héritage).

Le programme suivant illustre quelques cas

#include <iostream.h>
class Ctest{
int attr;
public:
Ctest()
{
cout<<"----- contructeur par defaut! "<<endl;

~Ctest()
{
cout<<"----- Le destructeur ! "<<endl;
}
};

void blocLocal()
{
cout<<"BLOC LOCAL : "<<endl;
Ctest CTlocal;
}
void main()
{
cout<<"BLOC MAIN : "<<endl;
Ctest *CTmain=new Ctest;
cout<<"Appel du bloc local dans main : "<<endl;
blocLocal ();
cout<<"APPEL DE DELETE : "<<endl;
delete CTmain;
}

Vous obtenez le résultat d’exécution suivant :

POO & C++ Page 37


II.10. CONSTRUCTION DES OBJETS MEMBRES
1- Se Rappeler des notions variables membres et fonctions membres
2- Ici on parle d’objets membres: lorsqu’un attribut de la classe est lui même de type
une autre classe.
L’initialisation d’un objet de la classe nécessite alors l’initialisation de ses objets membres.
Si les objets membres n’ont que des constructeurs par défaut, le problème ne se pose pas!

Sinon voici la syntaxe :

NomDeLaClasse(paramètres)
: membre1(paramètres),membre2(paramètres),…
{
Corps du constructeur de NomDeLaClasse
…………..
}

Voici un exemple : On considère la classe Point et la classe Segment formée par deux objets
membres Origine de classe Point et extremite de classe Point. L’appel des constructeurs
s’effectue de la façon suivante :
exemple II.9
class Point{
public:
Point(int px,int py)
{
x=px;
y=py;
}

private:
int x;
int y;
};
class Segment{
Point origine; //Objet membre
Point extremite; //Objet membre
int epaisseur;
public:
Segment(int ox,int oy,int ex,int ey,int ep):
origine(ox,oy),extremite(ex,ey)
{
epaisseur=ep;
}
};

POO & C++ Page 38


Voici une Première façon invalide d’appels des constructeurs
On appelle chaque constructeur de la classe membre à part dans le constructeur de la classe
Segment
class Segment{
Point origine; //Objet membre
Point extremite; //Objet membre
int epaisseur;
public:
Segment(int ox,int oy,int ex,int ey,int ep)
{
// FAUX : appel de constructeur non valide
origine(ox,oy);
extremite(ex,ey);

epaisseur=ep;
}
};

Voici une Deuxième façon invalide d’appels des constructeurs

On oublie de les appeler :

class Segment{
Point origine; //Objet membre
Point extremite; //Objet membre
int epaisseur;
public:
Segment(int ox,int oy,int ex,int ey,int ep)
{

epaisseur=ep;
}
};

II.11. MEMBRES CONSTANTS


II.10.1 Attributs Constants

Une donnée membre dans une classe peut être qualifiée const. Il est alors obligatoire de
l’initialiser lors de la construction d’un objet, et sa valeur ne pourra pas être modifiée à la
suite.
Voici un programme que l’on va tester:
exemple II.10.1
class Segment{
Point origine; //Objet membre
Point extremite; //Objet membre
int epaisseur;
const int numeroDeSerie; // membre constant
public:
Segment(int ox,int oy,int ex,int ey,int ep,int num):
origine(ox,oy),extremite(ex,ey)
{
epaisseur=ep;
}
};

POO & C++ Page 39


A la compilation, on obtient le message d’erreur suivant :
error C2758: 'numeroDeSerie' : must be initialized in constructor
base/member initializer list

Commentaires:
1- Le compilateur oblige d’écrire au moins un constructeur.
2- Le constructeur explicite doit initialiser l’attribut constant

Voici la version correcte du programme:

class Segment{
public :
Point origine; //Objet membre
Point extremite; //Objet membre
int epaisseur;
const int numeroDeSerie; // membre constant
public:
Segment(int ox,int oy,int ex,int ey,int ep,int num):
origine(ox,oy),extremite(ex,ey),numeroDeSerie(num)
{
epaisseur=ep;
}
};

Mais Attention vous ne pouvez pas faire ceci au programme principal :

#include "Point.cpp"
void main()
{
Segment s=Segment(0,1,2,5,10,200);
s.numeroDeSerie=200; // erreur de compilation.
}

Erreur de compilation : error C2166: l-value specifies const object

II.10.2 FONCTIONS CONSTANTES

Le mot const placé à la fin de l’entête d’une fonction membre indique que l’état de l’objet à
travers lequel la fonction est appelée ne change pas suite à l’appel: il reste constant. C’est une
manière de dire qu’il s’agit d’une fonction de consultation de l’objet et non d’une fonction de
modification

Regardons l’exemple suivant:


On ajoute la méthode distance à l’objet point

exemple II.10.2
class Point{
public:
Point(int px,int py)
{
x=px;
y=py;
}
void Deplacer(int a,int b)
{

POO & C++ Page 40


x+=a;
y+=b;
}
float distance(Point *p) const
{
int dx=p->x-this->x;
int dy=p->y-this->y;
return sqrt(dx*dx+dy*dy);
}

Dans une classe Geometrie on fait appel à la fonction distance dans une fonction membre
point le plus proche
class Geometrie
{

public:
Point PointLePlusProche (const Point *p,Point *p1, Point *p2)
{
float dp1=p->distance(p1);
float dp2=p->distance(p1);
if (dp1>dp2)
{
return *p2;}
else {
return *p1;}
}
};

On remarque que la fonction exige que le paramètre p de classe point reste constant et ne
change pas de valeur.
Cette utilisation exige que la fonction distance soit constante pour qu’elle ne modifie pas
l’état de l’objet p. C’est une mesure de sécurité prise par le compilateur.
Si on enlève le mot const de distance, le compilateur retourne une erreur.
error C2662: 'distance' : cannot convert 'this' pointer from 'const class
Point' to 'class Point &'

Remarquer que la classe deplacer (a, b): change l’état d’un objet et il est alors impossible de
placer le mot clé const à cette fonction.

- Un objet constant peut appeler une méthode constante ;


- Un objet constant ne peut pas appeler une méthode de modification ;
- Un objet non constant peut appeler une méthode constante ;
- Un objet non constant peut appeler une méthode de modification.

2 exemples incorrects:
class C_booleen
{
E_booleen verite;
public :
void set_bool(E_booleen);
E_booleen get_bool(); const
void mensonge(); const
};
void C_booleen::mensonge() const

POO & C++ Page 41


{
verite = verite == VRAI ? FAUX : VRAI;
return verite;
}
// Fin de l’exemple 1

int main()
{
const C_booleen v;
C_booleen trouve;
// .....
trouve.set_booleen(FAUX);
v.set_booleen(VRAI);
}

Exemple1 : mensonge() est une méthode déclarée const, mais elle modifie l’état de l’objet.
Exemple2 : v est un objet constant sur lequel on applique une méthode de modification !

II.12. MEMBRES STATIQUES


Normalement, chaque objet d’une classe possède son propre exemplaire de chaque
membre ordinaire (nous dirons membre non statique) :
- Pour les attributs membres cela signifie que la mémoire nouvelle est allouée lors de la
création de chaque objet.
- Pour les fonctions membres, cela veut dire qu’elles ne peuvent être appelés qu’en
association avec une instance de la classe.

II.12.1. Attributs membres statiques:

Un attribut static :
- obeit aux règles d’accès des attributs ;
- est partagé par toutes les instances la classe ;
- est créé et initialisé avant le main() ;
- Sa définition se fait dans la partie globale du programme.

Fichier MonPoint.H

exemple II.11.1
class MonPoint
{
public:
MonPoint(int px, int py);
static nombreDePoints;
private:
int x;
int y;
};

Fichier MonPoint.cpp

#include "MonPoint.h"
int MonPoint::nombreDePoints= 20; // initialization du membre statique
MonPoint::MonPoint(int px, int py)
{
x=px;
y=py;

POO & C++ Page 42


}

Fichier Main.cpp

#include <iostream.h>
#include "MonPoint.h"
void main()
{
MonPoint *p=new MonPoint(1,2);
MonPoint *q=new MonPoint(14,72);
int nbstatic=MonPoint::nombreDePoints;
cout <<"Nombre de points statiques :"<<nbstatic<<endl;
cout <<"On vérifie le nombre de points statiques pour p :"<<p-
>nombreDePoints<<endl;
cout <<"On vérifie le nombre de points statiques pour q :"<<q-
>nombreDePoints<<endl;

//Changement du nombre de points statiques

MonPoint::nombreDePoints=10;
cout <<"On vérifie le nombre de points statiques pour p :"<<p-
>nombreDePoints<<endl;
cout <<"On vérifie le nombre de points statiques pour q :"<<q-
>nombreDePoints<<endl;
}

Commentaires :
- L’initialisation du membre statique nombreDePoints ne peut pas se faire dans le
fichier MonPoint.H mains dans un fichier .CPP
- Vérifier que toute instance de la même classe déclarée p et q donne la même valeur.
- Après son changement MonPoint::nombreDePoints=10; dans la fonction
main() on vérifie que les instances prennent tous la dernière valeur changée.
C’est un attribut membre statique
 Exercice : tester le cas où nombreDePoints est un membre privé. Que se passe-t-il?

II.12.2 Attribut Méthode statique

Une fonction membre statique n’est pas attachée à un objet. Par conséquent:
- Elle ne dispose pas du pointeur this.
- Ces fonctions servent parfois à accéder aux membres statiques PRIVATE.
- On les utilise quand il s’agit d’une fonction qui ne dépend pas d’une instance.
- L’exemple suivant le montre :

Fichier MonPoint.H

exemple II.11.2
class MonPoint
{
public:
MonPoint(int px, int py);
static int getNombreDePoints();
private:
int x;
int y;
static nombreDePoints;

POO & C++ Page 43


};

Fichier MonPoint.CPP

#include "MonPoint.h"
int MonPoint::nombreDePoints= 20;
int MonPoint::getNombreDePoints()
{
return nombreDePoints;
}
MonPoint::MonPoint(int px, int py)
{
x=px;
y=py;
}

Commentaires :

Voir qu’on a eu besoin d’une fonction de type static pour accéder au membre statique devenu
PRIVATE : nombredePoints. Mais cette fonction peut ne pas être STATIC !
On considère la classe Circle, on veut comparer 2 cercles par leur rayon. L’utilisation d’une
méthode statique invoque directement la classe lors de l’appel sans avoir besoin à déclarer
une instance. Soit la méthode
Static Circle bigger(Circle *c1,Circle *c2)
L’appel se fait comme suit : Circle::bigger(c1,c2)

Une méthode static :


- n’est pas appelée par une instance de la classe ;
- ne peut manipuler que des attributs statiques ;
- est appelée directement par la classe.

Circle.cpp

exemple II.11.3
class Circle{
public:
static int count;
double x,y,r;

public :
Circle(double r)
{
this->r=r;
this->count++;
}
static Circle bigger(Circle* c1,Circle *c2)
{
if (c1->r>c2->r)
{
return *c1;
}
else
{
return *c2;
}
}

POO & C++ Page 44


};

Fichier main.cpp

#include "Circle.cpp"
#include <iostream.h>
int Circle::count= 0; // Initialisation d'un membre statique
void main()
{

Circle *c1= new Circle(10);


Circle *c2= new Circle(20);
cout <<c1->count<<endl; //Affiche 2;
Circle *c= new Circle(1);
*c=Circle::bigger(c1,c2); // méthode statique
cout<<c->count<<endl; // Affiche 3
cout<<c->r<<endl; // Affiche 20
}

POO & C++ Page 45


CHAPITRE III : L’HERITAGE
III.1. DEFINITIONS :
C’est est une technique qui permet de servir le principe de réutilisabilité. Son objectif est de
permettre la définition aisée de sous-types correspondants à une spécialisation (appelée
extension) de types existants.

Un peu de concepts utiles


Objectifs de l’héritage :
• Organiser les classes dans une hiérarchie de fonctionnement.
• Les classes représentent dans ces relations d’héritage un rapport parent/fils.
• Il n’existe pas de relation d’héritage universelle entre les classes c’est le rôle de
l’architecte d’application de définir la relation qu’il sous-entend.

Le mécanisme de l’héritage consiste en la définition d’une classe par réunion des membres
d’une ou plusieurs classes préexistentes dites classes de base directes et d’un ensemble de
membres spécifiques de la classe nouvelle appelée alors classes dérivées.

La classe de base est appelée aussi super-calsse.


La classe dérivée est appelée sous-classe.

Exemples :

POO & C++ Page 46


La relation d’héritage indique ce que l’objet est. (en anglais : is_a)
Une sous-classe étend les capacités de sa super-classe, elle hérite des capacités de sa parente
et y ajoute les siennes.

POO & C++ Page 47


* String : On suppose que String représente la classe chaine de caractères.

Héritage des données (des attributs) :

POO & C++ Page 48


Héritage des méthodes :

Le mécanisme d’héritage est aussi appelé derivation : on parle de classe mere ou classe de
base et de classe fille ou classe derive.
La dérivation définit l’accessibilité des membres de la classe de base (des classes de bases) ;
class C_mere
{
//....
};
class C_deriveedemere : <mode> C_mere
{
//...
};

- <mode> indique le type de dérivation : private, protected, public ;


- Si aucun mode de dérivation alors dérivation private ;

On prend l’exemple du compte et compteEpargne:

Classe Compte :

exemple III.1
#include <string>
using namespace std;

class Compte{
private:
string numCompte;
int codeClient;
double solde;
public:
// Les accesseurs
string getNumCompte()
{

POO & C++ Page 49


return numCompte;
}
int getCodeClient()
{
return codeClient;
}
double getSolde()
{
return solde;
}
// Les modificateurs
void setNumCompte(string numCompte)
{
this->numCompte=numCompte;
}
void setCodeClient(int codeClient)
{
this->codeClient=codeClient;
}
void setSolde(double solde)
{
this->solde=solde;
}

};

Commentaires:
- Remarquer l’encapsulation du : numCompte,codeClient et solde
- Inconvénients de ce code: absence de constructeur et destructeur. Que ferait le
compilateur ?

Utilisation de ce code:
#include <iostream.h>
#include "Compte.cpp"
void main()
{
Compte *monCpt=new Compte;
cout<<"Le solde de votre compte est :"<<monCpt->getSolde()<<endl;
}

Exécuter ce programme
Le résultat est mauvais. Pourquoi ?
Que faut-il faire ?

Ajouter au code de la classe Compte le code suivant:


C’est un constructeur explicite de la classe Compte
Compte(string numCompte,int codeClient,double solde=0)
{
this->numCompte=numCompte;
this->codeClient=codeClient;
this->solde=solde;
}

POO & C++ Page 50


Changer le main :

#include <iostream.h>
#include "Compte.cpp"
void main()
{
Compte *monCpt=new Compte("05-545-012",133454,800.500);
cout<<"Le solde de votre compte est :"<<monCpt->getSolde()<<endl;
}

Le résultat de l’exécution :

Commentaire :
Le constructeur prévoit que le solde de départ pourrait être 0, on peut par conséquent appeler
le constructeur comme ceci.

#include <iostream.h>
#include "Compte.cpp"
void main()
{
Compte *monCpt=new Compte("05-545-012",133454);// absence du
paramètre solde.
cout<<"Le solde de votre compte est :"<<monCpt->getSolde()<<endl;
}

Parlons d’héritage : La classe CompteEpargne hérite de la classe Public Compte. L’héritage


est donc public. Cette classe partage les mêmes attributs avec la classe Compte et apporte des
spécificités. Regardons un premier code de la classe CompteEpargne:

class CompteEpargne : public Compte{


private:
double txInteret;
public:
void ajoutInteret()
{
// On accède aux membre solde <PRIVATE> au moyen de la
//méthode publique getSolde()
double s=getSolde();
s+=s*txInteret;
setSolde(s);
}
};

Utilisons cette classe dans le main() :


#include <iostream.h>
#include "Compte.cpp"

POO & C++ Page 51


void main()
{
Compte *monCpt=new Compte("05-545-012",133454);
CompteEpargne *cptEpargne=new CompteEpargne;
cout<<"Le solde de votre compte est :"<<monCpt->getSolde()<<endl;
}

Compilez cet exemple et voir le résultat. Le compilateur donne:

error C2512: 'CompteEpargne' : no appropriate default constructor available

Interprétation : la déclaration en mémoire d’une classe CompteEpargne implique le


chargement de la classe parente. Or, pour la classe parente, un constructeur explicite est
défini.
L’appel CompteEpargne *cptEpargne=new CompteEpargne;

Ne prévoit pas cette construction ce qui provoque une erreur de compilation.


Trouvons une solution: on déclare un constructeur de la classe fille CompteEprgne qui fait
appel au constructeur de la classe parente.
Pour cela on va ajouter un constructeur en surcharge à la classe parente Compte:

Compte(double solde=0)
{
this->solde=solde;
}
Et dans la classe fille ajoutons ce code :

CompteEpargne(double txInteret=0.05):Compte()
{
this->txInteret=txInteret;
}

Remarque

Les constructeurs des classes dérivées appellent en premier lieu les constructeurs des classes
de bases. Alors que les destructeurs des classes dérivées s'exécutent puis appellent les
destructeurs des classes de base.

Fenêtre du programme principal :

POO & C++ Page 52


Remarquer que l’éditeur ramène pour la classe cptEpargne les attributs et les méthodes aussi
bien de la classe CompteEpargne que de la classe Compte.

III.2. PUBLIC, PRIVATE ET PROTECTED:


Reprenons le code de la classe Compte et remplaçons le mot public prévu pour les méthodes
par le mot protected. Le code obtenu est comme ceci :

exemple III.2.
class Compte{
private:
string numCompte;
int codeClient;
double solde;
protected: // -> -> private a été changée en protected
Compte(string numCompte,int codeClient,double solde=0)
{
this->numCompte=numCompte;
this->codeClient=codeClient;
this->solde=solde;
}
Compte(double solde=0)
{
this->solde=solde;
}
// Les accesseurs
string getNumCompte()
{
return numCompte;
}
int getCodeClient()
{
return codeClient;
}
double getSolde()
{
return solde;
}
// Les modificateurs

POO & C++ Page 53


void setNumCompte(string numCompte)
{
this->numCompte=numCompte;
}
void setCodeClient(int codeClient)
{
this->codeClient=codeClient;
}
void setSolde(double solde)
{
this->solde=solde;
}

};

POO & C++ Page 54


Main()
#include <iostream.h>
#include "Compte.cpp"
void main()
{
Compte *monCpt=new Compte("05-545-012",133454);
CompteEpargne *cptEpargne=new CompteEpargne;
cout<<"Le solde de votre compte est :"<<monCpt->getSolde()<<endl;

Dans le programme main(), on obtient les erreurs de compilation suivantes:

error C2248: 'Compte::Compte' : cannot access protected member declared in


class 'Compte'

error C2248: 'getSolde' : cannot access protected member declared in class


'Compte'

Interpréter les résultats.

Faisons un autre essai :


Ajouter les lignes suivantes au programme main():
#include <iostream.h>
#include "Compte.cpp"
void main()
{
CompteEpargne *cptEpargne=new CompteEpargne(0.07);
cptEpargne->setSolde(100.000);

Après compilation vous obtenez :


error C2248: 'setSolde' : cannot access protected member declared in class
'Compte'

Interpréter les résultats


Modifier le code de la classe CompteEpargne de cette façon :

class CompteEpargne : public Compte{


private:
double txInteret;
public:
CompteEpargne(double txInteret=0.05):Compte()
{
this->txInteret=txInteret;
}
double getSoldeEpargne()
{
return getSolde();
}
void setSoldeEpargne(double solde)
{
setSolde(solde);

POO & C++ Page 55


ajoutInteret();
}
private:
void ajoutInteret()
{
// On accède aux membre solde <PRIVATE> au moyen de la
//méthode publique getSolde()
double s=getSolde();
s+=s*txInteret;
setSolde(s);
}

};
Progamme main()

#include <iostream.h>
#include "Compte.cpp"
void main()
{
CompteEpargne *cptEpargne=new CompteEpargne(0.07);
cptEpargne->setSoldeEpargne(200.000);
cout<<"Votre solde Epargne est :"<<cptEpargne->getSoldeEpargne();

EXEMPLE et EXERCICE

POO & C++ Page 56


Ecrire un code C++ pour ce diagramme

Statut des membres de la classe dérivée en fonction du statut des membres de la classe de base
et du mode de dérivation.

Statut des membres de base


Public Protected Private
Public Public Protected Inaccessible
Mode de
dérivation Protected Protected Protected Inaccessible
Private Private Private Inaccessible

III.3. HÉRITAGE ET CONSTRUCTEUR

Le constructeur de la classe dérivée doit obligatoirement passer par le constructeur de la


classe de base (classe parente)

Soit le schéma suivant :

POO & C++ Page 57


Classe Article

exemple III.3.
#include <string>
using namespace std;
#include <vector>
using std::vector;
class Article{
public:
string codeArticle;
string desigArticle;
double quantite;
double prixUnitaire;
public:
Article(string codeArticle,string desigArticle,
double quantite,double prixUnitaire)
{
this->codeArticle=codeArticle;
this->desigArticle=desigArticle;
this->quantite=quantite;
this->prixUnitaire=prixUnitaire;
}
Article(Article *article)
{
this->codeArticle=article->codeArticle;
this->desigArticle=article->desigArticle;
this->quantite=article->quantite;
this->prixUnitaire=article->prixUnitaire;
}
double getQuantite()
{
return quantite;
}
double getPrixUnitaire()
{
return prixUnitaire;
}
double getPrix()
{
return getQuantite()*getPrixUnitaire();
}
};

Classe Boissons

class Boissons:public Article{


public:
double volume;
public:
//Constructeur

Boissons(string codeArticle,string desigArticle,


double quantite,double prixUnitaire,double _volume=0):
Article (codeArticle, desigArticle,
quantite, prixUnitaire)
{
volume=_volume;
}

POO & C++ Page 58


};

class BoissonsChaudes:public Boissons{


public:
string nature;
public:
BoissonsChaudes(string codeArticle,string desigArticle,
double quantite,double prixUnitaire,double volume,string
_nature):Boissons(codeArticle,desigArticle,
quantite,prixUnitaire,volume)
{
nature=_nature;
}
};

Commentaires:
Le constructeur de la classe Boissons appelle celui de la classe article et complète les
informations manquantes pour l’attribut volume.
Le constructeur de la classe BoissonsChaudes appelle celui de Boissons et complète pour
l’attribut nature

III.4. LE POLYMORPHISME:
C’est un aspect très puissant du paradigme objet. Il permet à une méthode d’adopter plusieurs
formes sur des classes différentes. Quel avantage ?
Essayons à travers les exemples de comprendre ce mécanisme et pourquoi un tel mécanisme
est puissant.

Revenons à l’exemple précédent :


1- Nous allons créer une autre classe Confitures qui hérite de la classe Article.
2- Enrichir les classes filles Boissons , BoissonsChaudes et Confitures par les méthodes
qui portent toutes les mêmes noms :

getQuantite() et getPrixUnitaire()

On obtient alors le code suivant

Classe Boissons

exemple III.4
class Boissons:public Article{
public:
double volume;
public:
//Constructeur

Boissons(string codeArticle,string desigArticle,


double quantite,double prixUnitaire,double _volume=0):
Article (codeArticle, desigArticle,
quantite, prixUnitaire)
{
volume=_volume;

POO & C++ Page 59


}

double getQuantite()
{
return volume;
}
double getPrixUnitaire()
{
return (prixUnitaire*0.95);
}

}
;

Classe Confitures

class Confitures:public Article{


public:
double poids;
public:
//Constructeur

Confitures(string codeArticle,string desigArticle,


double quantite,double prixUnitaire,double _poids=0):
Article (codeArticle, desigArticle,
quantite, prixUnitaire)
{
poids=_poids;
}

double getQuantite()
{
return poids*0.5;
}
double getPrixUnitaire()
{
return (prixUnitaire*0.75);
}

}
;

Classe BoissonsChaudes

class BoissonsChaudes:public Boissons{


public:
string nature;
public:
BoissonsChaudes(string codeArticle,string desigArticle,
double quantite,double prixUnitaire,double volume,string
_nature):Boissons(codeArticle,desigArticle,
quantite,prixUnitaire,volume)
{
nature=_nature;
}

double getPrixUnitaire()
{
return (prixUnitaire*0.75);

POO & C++ Page 60


}
};

Remarquer que dans chaque classe les méthodes getQuantite() et getPrixUnitaire() portent la
même signature mais contiennent un code différent propre à la spécificité de la classe.
Première Utilisation :

Main.cpp

#include <iostream.h>
#include "Article.cpp"
#include <string>
using namespace std;
#include <vector>
using std::vector;
void main()
{
Boissons *B1=new Boissons("B001","BOGA",3,4,5);
BoissonsChaudes *Bc=new BoissonsChaudes("WX","CAFE",2.5,0.450,10,"AU
LAIT");
Article *A=new Article(B1);
Article *A1=new Article(Bc);

double b1=B1->getPrixUnitaire();
double a=A->getPrixUnitaire();
double a1=A1->getPrixUnitaire();

cout<<"b1 = "<<b1<<endl;
cout<<"a = "<<a<<endl;
cout<<"a1 = "<<a1<<endl;

}
Resultat de l’exécution :

Commentaire : C’est bien d’utliser la même signature mais où est la notion du


polymorphisme ?

L’héritage et le polymorphisme sont des mécanismes très puissants mais jusque là les
méthodes ont une liaison statique à la compilation c’est à dire que les 2 lignes suivantes :
double b1=B1->getPrixUnitaire();
double a=A->getPrixUnitaire();
double a1=A1->getPrixUnitaire();

pour b1 : le contenu de la méthode spécifique à Boissons a été appliqué


pour a : on a appliqué le corps de la méthode du parent Article
pour a1 : même chose que a.

Question : comment faut-il faire pour appliquer le corps spécifique à chaque classe
dynamiquement. C’est à dire qu’à l’exécution, le compilateur applique la méthode
getPrixUnitaire pour chaque objet selon sa propre définition.

POO & C++ Page 61


SOLUTION : les méthodes virtuelles !

III.5. LA PUISSANCE DU POLYMORPHISME :

Rendre les méthodes du parent virtuelles!

Classe Article (avec méthodes virtuelles)

exemple III.5.
class Article{
public:
string codeArticle;
string desigArticle;
double quantite;
double prixUnitaire;
public:
Article(string codeArticle,string desigArticle,
double quantite,double prixUnitaire)
{
this->codeArticle=codeArticle;
this->desigArticle=desigArticle;
this->quantite=quantite;
this->prixUnitaire=prixUnitaire;
}
Article(Article *article)
{
this->codeArticle=article->codeArticle;
this->desigArticle=article->desigArticle;
this->quantite=article->quantite;
this->prixUnitaire=article->prixUnitaire;
}
virtual double getQuantite() //méthode virtuelle
{
return quantite;
}
virtual double getPrixUnitaire() // méthode virtuelle
{
return prixUnitaire;
}
double getPrix()
{
return getQuantite()*getPrixUnitaire();
}
};

Le module main()

#include <iostream.h>
#include "Article.cpp"
#include <string>
using namespace std;
#include <vector>
using std::vector;

POO & C++ Page 62


void main()
{
//Initialisation de différents articles : boissons, boissons
chaudes,confitures
Boissons *B1=new Boissons("B001","BOGA",3,4,5);
BoissonsChaudes *Bc=new BoissonsChaudes("WX","CAFE",2.5,0.450,10,"AU
LAIT");
Confitures *C1=new Confitures("CONF001","ABRICOTS",
20,10.25,2);

//tableau d'articles
vector<Article*> ListeArticles;

//Insertion des différents objets


ListeArticles.push_back(B1);
ListeArticles.push_back(C1);
ListeArticles.push_back(Bc);

//Nombre d'objets dans le tableau


int nbObjets=ListeArticles.size();

// Puissance du polymorphisme !!!


for (int i=0;i<=nbObjets-1;i++)
{
cout <<"LE PRIX UNITAIRE DE L'ARTICLE "<<ListeArticles[i]-
>desigArticle.data()<<" est : "<<ListeArticles[i]-
>getPrixUnitaire()<<endl;
}

A l’exécution du programme principal le compilateur affecte suivant le type d’objet la


méthode getPrixUnitaire() adéquate de façon dynamique.

III.5.1. LA CONVERSION ENTRE OBJETS(TRANSTYPAGE) :


Pour une instance d’un objet b de classe B qui hérite de A.
Implicite : le super
Il est possible de faire une conversion dite implicite pour appeler la superméthode de
A(appelé aussi super classe)
À l’extérieur de la classe (A *a)b->methodeA. Exemple :

Boissons *B1=new Boissons("B001","BOGA",3,4,5);


double c=((Article*)B1)->getPrixUnitaire(); //Conversion vers la super
classe
Le compilateur convertit B1 en la classe article et applique la méthode getPrixUnitaire()
définie dans la classe Article et non la classe Boissons.

Explicite : la cast
Si la conversion implicite marche très bien du type B vers le type A, ce n’est pas le cas de la
conversion inverse : il faut s’assurer que cette conversion a un sens.

III.5.2. Les Classes Abstraites

POO & C++ Page 63


Les fonctions virtuelles sont souvent introduites dans des classes placées à des niveaux si
élevés de la hiérarchie (héritage) qu’on ne peut pas leur donner une implémentation utile.

Une première solution consiste à faire des fonctions vides :

exemple III.5.2
Class Figure {
Public:
virtual float perimetre()=0;
virtual float surface()=0;
virtual void dessiner();
}

La classe figure est une abstraction. Un programme ne créera pas d’objet Figure, mais des
objets Rectangle, Cercle, etc..

On remarque que la fonction perimetre() introduite au niveau de la classe Figure n’est pas un
service rendu aux programmeurs mais une contrainte : son rôle n’est pas de dire ce qu’est le
périmètre d’une figure, mais d’obliger les futures classes dérivées de figure à le dire.

Exemple :

Figure

perimetre()
getNom()

UnRectangle
longueur : float
largeur : float

UnCarre
cote : float

#include <string>
using namespace std;

class Figure{
public:
virtual float perimetre()=0;
virtual string getNom()=0;

};
class UnRectangle:public Figure

POO & C++ Page 64


{
float longueur;
float largeur;
public :
UnRectangle(float longueur,float largeur)
{
this->longueur=longueur;
this->largeur=largeur;
}
float perimetre()
{
return 2*(longueur+largeur);
}
string getNom()
{
return "RECTANGLE";
}
};
class UnCarre:public UnRectangle
{
float cote;
public:
UnCarre(float cote):UnRectangle(cote,cote)
{
this->cote=cote;
}
float perimetre()
{
return cote*4;
}
string getNom()
{
return "CARRE";
}
};

Main()

#include <iostream.h>
#include "Figure.cpp"
#include <vector>
using std::vector;

void main()
{
UnRectangle *Rect=new UnRectangle(10,5);
UnCarre *Carre=new UnCarre(10);
vector<Figure*> ListeFigures;
ListeFigures.push_back(Rect);
ListeFigures.push_back(Carre);
for (int i=0;i<=ListeFigures.size()-1;i++)
{
cout<<"Le PERIMETRE DU "<<ListeFigures[i]->getNom().data()<<"
"<<ListeFigures[i]->perimetre()<<endl;
}
}

POO & C++ Page 65


CHAPITRE IV : RELATIONS ENTRE CLASSES

Relation entre les classes

A chaque famille de liens entre objets correspond une relation entre les classes de ces mêmes
objets.

3 sortes de relations entre les classes :

- L’association
- L’agrégation
- La généralisation / spécialisation (héritage)

IV.1. ASSOCIATIONS
• Les liens permettent d'établir des relations entre objets (ou instances). Les associations
permettent d'établir des relations entre classes.
• Une association est une abstraction des liens qui existent entre les objets instances des
classes associées.
• L’association exprime une connexion entre classes.
• Les associations se représentent de la même manière.

Les associations représentent des relations structurelles entre classes d’objets.


La plupart des associations sont binaires, c’est-àdire qu’elles connectent 2 classes.

POO & C++ Page 66


IV.1.1 La multiplicité

La multiplicité précise le nombre d’instances qui participent à la relation

Exemple1: Association simple : une référence comme attribut.


La solution en programmation est déclarée en dessous.

POO & C++ Page 67


Exemple2 : Association multiple : tableau de références sur des objets.
La solution de programmation en C++ est représentée en dessous :

La description du code est la suivante :


Classe Client (pseudo-code):

POO & C++ Page 68


Classe Agence:

Les associations décrivent des relations structurelles entre des classes, qui deviennent des
liens entre les instances de ces classes. Ces liens représentent une navigation interobjet, c'est-
à-dire le fait qu'une instance puisse extraire une autre instance via le lien navigable.

Lorsque l'extrémité d'une association est navigable, cela signifie que vous souhaitez pouvoir
extraire l'instance de la classe à laquelle cette extrémité est liée, cette instance s'affiche sous
forme d'attribut migré dans l'instance courante d'une classe. Le nom de rôle de cette extrémité
peut être utilisé pour clarifier la structure utilisée pour représenter le lien.

Par exemple, considérons une association entre la classe Société et la classe Personne. La
navigation est possible dans les deux directions pour permettre à Société d'obtenir la liste des
employés, et à chaque employé d'obtenir le nom de sa société.

POO & C++ Page 69


Par défaut, les attributs migrés utilisent la classe dont ils proviennent comme type.

Lorsque la multiplicité d'association est supérieure à un, le type est le plus souvent un tableau
de la classe, affiché avec des signes [ ]. Dans notre exemple, l'attribut Employé de la classe
Société est de type Personne et a un tableau de valeurs. Lorsque vous instanciez la classe
Société, vous obtenez une liste d'employés à stocker pour chaque société.

public class Company


{
public String Name;
public String Catalog;
public String Address;

public Person[] employee; // vous pouvez utiliser vector aussi


}

IV.1.2. Les classes associations

La classe d’association possède à la fois les caractéristiques d’une association et celle d’une
classe et peut à ce titre participer à d’autres relations dans le modèle.

Un étudiant s’inscrit à 0 ou plusieurs cours mais suite à une évaluation.


Un cours est suivi par au moins un étudiant.

En C++ :

Pour résoudre ce problème nous avaons créé 2 champs de type Etudiant et Cours dans la
classe Evaluation. Cette classe gère le lien entrs les Etudiants et les Cours qu’il doivent
passer.
La notion de cours,dateEvaluation et note n’est ni la responsabilité de la classe Etudiant ni la
responsabilité de la classe Cours c’est plutôt une classe qui gère ces notions tout en ayant
l’information quel Etudiant pour quel Cours.

POO & C++ Page 70


Exercice : Réaliser le programme en C++

IV.2. L’AGREGATION
Une agrégation représente une association non symétrique dans laquelle une des extrémités
joue un rôle prédominant par rapport à l’autre extrémité.
Agrégation = relation partie de.
• Par défaut, l’association représente un couplage faible, les classes associées restant
relativement indépendantes l’une de l’autre.
• L’agrégation est une forme particulière d’association qui exprime un couplage plus fort
entre classes.
• L’agrégation permet de représenter des relations de type
– maître et esclaves,
– tout et parties
– ou composés et composants.

Une agrégation peut être perçue comme une association. Cependant une association ne peut
être une agrégation

L’agrégation spécifie la relation «est partie de» (une sémantique de style il faut pouvoir dire
“ est composée de... ” ou “ est une partie de ... ”.

Exemple 1

POO & C++ Page 71


Exemple 2

Cas particulier de l’association : la composition

•Cette forme d’agrégation est appelée composition.


• La composition est une forme d’agrégation avec un couplage plus important.
• Ce couplage de composition indique que les composants ne sont pas partageables.
• La valeur maximale de multiplicité du côté du conteneur ne doit pas excéder 1 puisque les
objets, instances de la classe des composants, doivent tous appartenir au même objet
conteneur.

POO & C++ Page 72


La composition implique, en plus des propriétés d’agrégation, une coïncidence des durées de
vie des composants et du composite: la destruction du composite implique automatiquement
la suppression de tous ses composants :

Exemple:

La suppression d’une commande implique effectivement la suppression de toutes les lignes


qui la composent : il ne peut pas exister une instance ligne de commande indépendante.

– La composition et l’agrégation sont deux vues subjectives qui sont utilisées pour
ajouter de la sémantique au modèle lorsque c’est pertinent de le faire même si cette
pertinence ne reflète pas la réalité.

POO & C++ Page 73


• Une agrégation se fait par référence du ou des "enfant(s)" dans le contexte du "parent"
• Une composition se fait par valeur, c’est-à-dire copie du ou des"enfant(s)" dans le contexte
du "parent"

Le symbole d'agrégation dans un diagramme se présente comme suit :

Le symbole de composition dans un diagramme se présente comme suit :

POO & C++ Page 74

Vous aimerez peut-être aussi