Vous êtes sur la page 1sur 150

Langage C++

Introduction

Les caractéristiques du C++


Différences entre C et C++

1
Les caractéristiques du C++
C++ est un langage objet sur-ensemble de C ANSI :
 les programmes C sont a priori compilables avec un compilateur
C++ ;
 le développeur C++ peut ne pas utiliser les concepts d’objets;
 performances équivalentes d’un programme écrit en C et du
même programme écrit en C++ (aucun mécanisme sous-jacent
qui réduit la vitesse d’exécution ou qui augmente la mémoire
utilisée) ;
Les mécanismes objets sont parfois consommateur en temps et en
place mémoire : exception, typage dynamique, polymorphisme ;
 Maintenance plus aisée qu’en C :
• bénéficie de la robustesse, de l’extensibilité et de la
réutilisabilité apportées par l’approche objet ;
• meilleure portabilité (conversions plus rigoureuses) ;
 compilateur plus strict.
Langage complexe dû à son histoire et à la richesse du C.

2
Les entrées / sorties

En C :
#include <stdio.h> En C++ :
void main(){ #include <iostream.h>
int entier ;
void main(){
char chaine[ 20 ] ;
int entier ;
// préciser l’adresse
scanf( "%d", &entier ) ; char chaine[ 20 ] ;
// préciser le format // plus d’adresse…
scanf( "%s", chaine ) ; cin >> entier ;
printf("\nl’entier saisi est // formatage par défaut
égale à %d", entier ) ;
cin >> chaine ;
printf("\nla chaine saisie est
égale à %s", chaine); cout << endl << "l’entier saisi
} est égale à" << entier ;
cout<<endl<<"la chaine
saisie est égale à"<<chaine ;
}

3
Les références

C++ propose une alternative à l’utilisation des


pointeurs qui est délicate :
les références.
Faire référence à une variable c’est lui donner
un nom différent (alias) :
int i = 0 ;
int & j = i ; // j fait référence à i
j = 1 ; // équivalent à i = 1 ;
i et j représente la même case mémoire.

4
Intérêt des références
Les références sont utilisées pour passer ou retourner des paramètres à une
fonction :
void EchangeParPointeur( int* x, int* y ) {
// x est une copie de l’adresse de a, y est une copie de l'adresse de b
int tampon = *x ;
*x = *y ;
*y = tampon ;
}
void EchangeParReference( int& x, int& y ) {
// a porte de nom de x, b porte le nom de y
int tampon = x ;
x = y ;
y = tampon ;
}
void main(){
int a, b ;
a = 1 ; b = 2 ;
EchangeParPointeur( &a, &b ) ; // a vaut 2, et b vaut 1
EchangeParReference( a, b ) ; // a vaut 1, et b vaut 2
}

5
Inconvénient des références

Passage d’argument par référence identique passage par valeur


=> l’appel d’une fonction ne renseigne pas si les arguments
sont modifiés ou pas par cette fonction.
void PassageParValeur( int x )  { // x est une copie de a
...
}
void PassageParReference( int& x )  { // a porte de nom de x,
...
}
void main(){
int a ;
a = 1 ;
PassageParValeur( a ) ;
PassageParReference( a ) ;
// identique au passage par valeur
}

6
Les limitations des références

Une référence ne peut pas être initialisée avec une valeur :

void PassageParReference( int& x ){


...
}

void main(){
PassageParReference( 2 ) ; // erreur à la compilation

int &j = 2 ; // erreur à la compilation


}

7
Les dangers des références

Règle :
ne jamais retourner de référence sur une variable locale à une
fonction.
int & RetourParReference( void ){
int i ; // i est créée ici …
...
return i ;
} // et détruite là.
int & RetourParReference( int i ) {   // i est créée ici ...
...
return i ; // et détruite là.
}
int & RetourParReference( int & i ) {
// i est le nouveau nom d’une variable
// créée dans le programme appelant
...
return i ;
}

8
Arguments par défaut

En C++, on peut donner des valeurs par défaut à certains


arguments (dans le prototype de la fonction ) :
void fonction( int, int = 0 ) ; // prototype
void main(){
fonction( 1 ) ; // => fonction( 1, 0 ) ;
fonction( 1, 2 ) ; // la valeur par défaut ne convient pas et
dans fonction, y = 2
}
void fonction( int x , int y ){
...
}
Les arguments par défaut doivent être les derniers de la
liste des arguments.

9
Fonctions en ligne « inline »

Les macros du C :
#define ABS(x) (x)>0 ? (x) : -(x)
void main(){
float f1, f2=-2.3 ;
f1 = ABS( f2 ) ; // f1=2.3
}

Les fonctions inline du C++ :


inline float abs( float f ){
return f>0 ? f : -f ;
}
void main(){
float f1, f2=-2.3 ;
f1 = abs( f2 ) ; // f1=2.3
}

10
Fonctions en ligne « inline »

Les fonctions «inline» : Les fonctions inline du C+


 le code de la fonction est +:
inséré à la place de chaque inline float abs( float f ){
appel ;
return f>0 ? f : -f ;
 intéressant pour des petites
fonctions ; }
 le compilateur peut void main(){
l’implémenter comme une float f1, f2=-2.3 ;
fonction classique.
f1 = abs( f2 ) ;
// f1=2.3
}

11
ordinateur / Processus?

La mémoire vive a trois utilisations majeures :


 Stockage des instructions (le code) des processus
 Pile d’exécution des processus
 Espace de tas des processus (là où (m|c)alloc et new
allouent les blocs demandés)
Le processeur contient des registres, dans lesquels sont
mémorisés, pour le processus courant :
 Le pointeur sur l’instruction en cours
 Le pointeur courant de la pile
 Les adresses de base des segments de code, de pile, de tas
 Les arguments des fonctions appelées, la valeur de retour

Les registres servent aussi de "variables intermédiaires"  dans les


calculs.

12
Processus / Appel de fonction ?
La mémoire de pile sert à :
 Préserver (empiler) les valeurs des registres pour
pouvoir les restaurer (dépiler) ultérieurement,
par exemple avant et après un appel de fonction
 Passer les arguments des fonctions qui ne sont
pas passés par des registres
 Allouer de l’espace pour les variables locales et
les objets locaux (+ alloca)
 …
Par la suite, nous considèrerons que nous travaillons
sur une architecture 32 bits Intel, donc avec un
support de pile d’exécution fourni par le
processeur (ce qui n’est pas le cas de tous les
processeurs). Petit rappel : C/C++ garantissent
des relations d’ordre entre les tailles des types
primitifs…

13
Processus / Registres ?

processeur code segment


...
mov reg1, [a]
pc
add reg1, [b]
cmp reg1, [c]
sp jge +5Fh
stack segment mov [d], 69h
jmp main+43h
...
reg1 reg2
reg3 reg4
… …

PC : Program Counter (pointeur d’instruction)


SP : Stack Pointer (pointeur de pile)

14
Appel de fonction simple
stack
XXX f( … ) { … }
sp
L’adresse de f est connue à la compilation. registres
Schéma d’un appel à f : caller-save
Préservation des registres caller-save
arguments
Empilement des arguments
Saut à la fonction après empilement de l’adresse de retour return address
Réservation d’espace de pile pour les variables/objets locaux variables
Préservation des registres callee-save locales
f fait son travail…
Restauration des registres callee-save utilisés par la fonction registres
callee-save
Dépilement des variables locales
Retour à l’instruction suivant l’appel de f
Dépilement des arguments
Restauration des registres caller-save

15
Appel de fonction simple

 Les registres caller-save sont les registres utilisés par la


fonction appelante et que les fonctions appelées ont le droit
de modifier.
 Les registres callee-save sont les registres utilisés par la
fonction appelée, qui sont donc préservés par la fonction
appelée même si les fonctions appelantes ne les utilisent pas…
 Le stockage des variables locales dans la pile est le
mécanisme permettant, par exemple, aux différentes
"instances" d’une fonction récursive d’avoir chacune leurs
propres variables locales. Les variables locales static ne sont
que des variables globales à visibilité réduite.
 Les conventions d’appels (passage des arguments par registre
ou dans la pile, ordre de passage des arguments,
responsabilité des dépilements d’arguments, registres callee-
save ou caller-save, …) dépendent à la fois de l’architecture
matérielle et du language

16
Surcharge des noms de fonctions

Plusieurs fonctions peuvent avoir le même nom.


Elles doivent se différencier par la liste de leurs arguments :

void fonction( int i, int j ){


...
}
void fonction(int i, char j ){
...
}
void main(){
// appel de “ void fonction( int i, int j ) ; ”
fonction( 1, 2 ) ;
// appel de “ void fonction( int i, char j ) ; ”
fonction( 1, ‘b’ ) ;
}

17
Surcharge des noms de fonctions

 Le type de retour d’une fonction n’est pas suffisant pour


différencier deux fonctions portant le même nom (une erreur
est alors générée à la compilation) :

void fonction( int i ){


...
}

int fonction( int i ){


...
}

 Emploi de la surcharge : un utilisateur dispose de plusieurs


fonctions réalisant le même traitement pour des arguments
différents.

18
Allocation de mémoire

Plutôt que d’utiliser malloc on peut Conseil : ne pas


utiliser l’opérateur new :
mélanger les
int* tab ; "new" et les
int nb = 10 ; "malloc", car
// allocation d’un tableau de 10 rien n'indique
entiers :
tab = new int[ nb ] ; qu'un "new"
… est
// libération du tableau : implémenté
delete [] tab ; comme un
"malloc".

19
Allocation de mémoire

struct nomDeStructure{

};
...

struct nomDeStructure* ptrStruct ;

// allocation d’une structure


ptrStruct = new nomDeStructure ;...

delete ptrStruct ; // libération de la structure

20
Exercice 1 : Ennoncé

 Exercice 1 - Différence entre inline et


#define
1. Ecrire la macro-fonction qui calcule
factoriel de n. Puis la fonction inline qui
fait de même.
2. Montrer et expliquer sur un exemple que
l'appel n'est pas réalisé correctement
avec la macro-fonction alors qu'il l'est
avec la fonction.

21
Exercice 2 : Ennoncé

 Exercice 2 - Utilisation des références


1. Ecrire une fonction qui incrémente une date sous
forme de jour, mois et année.
Pour le passage des paramètres, il y a trois
solutions à ce problème. Les mettre en oeuvre et
expliquer les différences.
2. Ecrire le synopsis d'une fonction qui reçoit une
grande structure sachant qu'on ne veut pas perdre
de place mémoire et pas la modifier.

22
Corrigé de l’exercice 1

 Ecrire la macro-fonction qui calcule factoriel de n.


#define factM(n) (((n) > 1) ? (n) * factM((n) - 1) : 1)
Notez les parenthèses autour de la pseudo-variable n !

 Souvenez-vous que le preprocesseur travaille à la manière


d'un "copier-coller" sans comprendre ce qu'il fait et que
cela peut parfois poser problème... Exemple: soit la
définition suivante :

#define f(x) (x * 2)
l'appel int y = f(1 + 2);
sera remplacé par le préprocesseur par :
int y = (1 + 2 * 2);
soit 5 et non pas 6 comme attendu! Il faut donc corriger la
définition en : #define f(x) ((x) * 2)

23
Corrigé de l’exercice 1

 Puis la fonction inline qui fait de même.

inline unsigned long factI(unsigned long n){


return (n > 1) ? n * factI(n - 1) : 1;
}

24
Corrigé de l’exercice 1

 Appel de la macro-fonction :
void main(){
cout << factM(4) << endl;
} -> Erreur à la compilation : "'factM' : undeclared
identifier'
 Cela est parfaitement normal. En effet, faisons à
la main les remplacements effectués par le
préprocesseur (souvenez-vous, c'est du "copier-
coller" !).
Nous aurions dans un premier temps :
cout << (((4) > 1) ? (4) * factM((4) - 1) : 1) <<
endl;
 mais en raison de la récursivité de la macro-
fonction il faudrait recommencer...
cout << (((4) > 1) ? (4) * ((((4) - 1) > 1) ? ((4) -
1) * factM(((4) - 1) - 1) : 1) : 1) << endl;
25
Corrigé de l’exercice 2
 Ecrire une fonction qui modifie le jour, le mois ou l'année d'une date
donnée. Il y a trois solutions à ce problème. Les mettre en oeuvre et
expliquer les différences.
 Passage de l'objet date par recopie ; la date résultat est également retourné
par recopie.
Date change_date(Date d, /* paramètres liés à la modification */);
 Passage de l'objet date par pointeur. La modification porte sur l'objet au
travers du pointeur passé.
void change_date(Date* d, /* paramètres liés à la modification */);
 Passage de l'objet date par référence. La modification porte directement sur
l'objet passé.
void change_date(Date& d, /* paramètres liés à la modification */);

 Pour des raisons d'efficacité (au moins 2 recopies de l'objet date), la version 1
est à bannir.
 La version 2 semble donc plus appropriée, mais la manipulation de pointeurs
reste une opération délicate : attention aux pointeurs NULL !
 On préférera donc la version 3 qui présente tous les avantages : lisibilité,
efficacité - on manipule directement l'objet - et sécurité, l'objet passé existant
forcément (sauf bidouillage hazardeux au niveau de l'appelant !).

26
Corrigé de l’exercice 2

 Ecrire le synopsis d'une fonction qui reçoit une


grande structure sachant qu'on ne veut pas
perdre de place mémoire et pas la modifier.
struct big_Struct{
// ...
char large_array[10000000000000];
// ...};

void read_only(const big_Struct& the_struct){


// ...
}

27
Corrigé de l’exercice 2

 Le passage par référence permet d'accéder


à partir de la fonction à une instance de la
structure volumineuse sans pour autant la
recopier. Souvenez-vous créer une
référence, c'est donner un deuxième nom
à une instance déjà existante.
 Enfin, l'attribut const permet de protéger
l'instance identifiée par la référence contre
toute tentative d'écriture. La structure
passée par référence est donc de type
"read only".

28
L’approche objet de C++

 Notions de classes et d’objets


 Différences entre classes et structures
 Les données et les fonctions membres
 Les membres particuliers
 Les membres de classes

29
Notion de classe et d’objet

L’état d’un objet (données membres) est caché en son sein. La seule façon
de lire ou de changer l’état d’un objet est de lui envoyer un message
(appeler une fonction).

Principe d’encapsulation des données : pour accéder aux données


membres, il faut passer par des fonctions membres.

30
Implémentation d’une classe : les limitations des structures

Les classes en C++ ont été définies à partir des structures du C :


struct STableau{
int* tableau ; // pointeur vers un tableau d’entiers
int nombreMaxiElement ; // dimension du tableau
int nombreCourantElement ; // nombre d’élément courant
};

void AllouerTableau (STableau* ptrStruct,


int nombreElement){
ptrStruct->nombreMaxiElement = nombreElement ;
ptrStruct->nombreCourantElement = 0 ;
ptrStruct->tableau =
(int*) malloc( nombreElement * sizeof( int ) ) ;
}

31
Implémentation d’une classe : les limitations des structures

void main(){
STableau tab ;

// alloue un tableau de 10 éléments


AllouerTableau( &tab, 10 ) ;

// pb : changer le nombre d’éléments.


tab. nombreMaxiElement = -100 ;
}

32
La solution du C++

L'idée est de ne pas confier à devient :


n'importe qui l'accès aux class CTableau{
données importantes d'un private :
programme. Ainsi, les int* tableau ;
données sont souvent int nombreMaxiElement ;
cachées à un utilisateur.
int nombreCourantElement ;
La structure : } ;

struct STableau{
int* tableau ; Le mot clef « private » cache
int nombreMaxiElement ; les données à un utilisateur
int nombreCourantElement;
} ;

33
la solution du C++ : les données membres d’une classe

class CTableau{
private :
Données membres de la
int* tableau ;
classe CTableau.
int nombreMaxiElement ;
int nombreCourantElement ;
} ;

Grâce au mot clef « private », les données membres sont


inaccessibles de l’extérieur de la classe :

void main(){
CTableau tab ;
// erreur à la compilation
tab.nombreMaxiElement = -100 ;
}

34
Fonctions membres
 Pour garantir l’intégrité des données tout en
pouvant les modifier, il faut forcer l’utilisateur à
passer par des fonctions qu’on a écrite pour lui.
class CTableau{
private :
int* tableau ;
int nombreMaxiElement ;
int nombreCourantElement ;
public :
void AllouerTableau(int nombreElement ) ;
} ;

35
Fonctions membres

void Ctableau::AllouerTableau ( int nombreElement ){


nombreMaxiElement = nombreElement ;
nombreCourantElement = 0 ;
tableau =(int*)malloc(nombreElement * sizeof( int )) ;
}

void main(){
CTableau tab ;

// erreur à la compilation
tab.nombreMaxiElement = -100 ;

tab.AllouerTableau( 10 ) ; // OK
}

36
Le concepteur et l’utilisateur de la classe

Le concepteur de la classe L’utilisateur de la classe


fichier : *.h L ’utilisateur de la classe créé des
class NomDeClasse{ objets. On parle d’instances de
private : classe (réservation de
mémoire):
int i ; void main(){
public : // un objet
void fonction() ; NomDeClasse nomObjet ;
} ;
fichier : *.cpp nomObjet.fonction() ;
Void NomDeClasse::fonction(){ nomObjet.i ; // erreur
i=0; }
}

Partie privée
Vision du concepteur
Partie publique Vision de l’utilisateur

37
Notion de constructeur et de destructeur

 L’initialisation des données membres est tellement


importante qu’on ne peut la confier à l’utilisateur.
 constructeur :
 fonction membre portant le même nom que la classe ;
 fonction appelée automatiquement lors de la création d un
objet.
 La libération des ressources est tellement
importante qu’on ne peut la confier àl’utilisateur.
 destructeur :
 fonction membre portant le même nom que la classe
précédée de ~;
 fonction appelée automatiquement lors de la destruction
d’un objet.

38
Les constructeurs

class CTableau {
private :
int* tableau ;
int nombreMaxiElement ;
int nombreCourantElement ;
public :
CTableau( int ) ;
};

CTableau::CTableau ( int nombreElement ) { // constructeur


nombreMaxiElement = nombreElement ;
nombreCourantElement = 0 ;
tableau = (int*) malloc( nombreElement * sizeof( int ) ) ;
}
void main(){
CTableau tab = CTableau( 10 );//<=>CTableau tab( 10 ) ;
}

39
Le destructeur

class CTableau{
...
public :
CTableau( int ) ;
~CTableau() ;
};

CTableau::~CTableau() { // destructeur
free( tableau ) ;
}

void main(){
CTableau tab( 10 );//appel du constructeur
} // appel du destructeur

40
Données et fonctions membres statiques

 Les membres sont crées pour chaque


instances d’une classe (les objets).
 Si on désire un membre commun pour
tous les objets donc il doit être crée une
seule fois et dans ce cas il n’appartiendra
pas aux objet mais à la classe et sera
donc nommé « Membre de classe » ou «
membre static »

41
Partager une donnée membre entre toutes les instances
d’une classe

 Chaque objet à ses


propres données CCompteur::CCompteur(int n ){
membres :
class CCompteur { i=n;
int i ; }
public :
CCompteur( int ) ; void main(){
};
CCompteur objet1( 1 );
CCompteur objet2( 2 ) ;
}

Objet 1 Objet 2
i=1 i=2

42
Partager une donnée membre entre toutes les instances d’une
classe

Pour partager une données CCompteur::CCompteur(int n ){


entre plusieurs objets : i=n;
static cpt++;
class CCompteur { }
int i ; CCompteur:: ~CCompteur(){
static int cpt; cpt--;
}
public :
CCompteur( int ) ;
void main(){
~CCompteur() ;
CCompteur objet1( 1 );
};
CCompteur objet2( 2 ) ;
}
Objet 1 Objet 2
i=1 i=2

cpt = 2

43
Fonction membre statique

 commune à toutes les instances d’une classe ;


 ne peut accéder qu’aux données membres statiques.

class CCompteur{ CCompteur::CCompteur( int n ){


int i ; i=n;
static int cpt ; cpt++ ;
public : }
CCompteur( int ) ;
~CCompteur() ; void main(){
Static void combien(); CCompteur objet1(1);
}; CCompteur::combien();
// à l’écran : 1
CCompteur::~CCompteur(){
CCompteur objet2(2) ;
cpt-- ;
CCompteur::combien();
}
// à l’écran : 2
void CCompteur::combien(){ }
cout << cpt << endl ;
}

44
Exemple d’une classe pour gérer des nombres complexes

 Commencer par définir les données membres : deux


flottants pour les parties réelle et imaginaire.
class CComplexe{
float reel ;
float imag ;
public :
} ;
 Se placer du coté utilisateur pour définir des fonctions
membres utiles :
void main(){
CComplexe z1( 1, 2.2 ), z2( 5 ), z3 ;
}
 Pour pouvoir initialiser les données membres, il faut définir
un constructeur

45
Classe nombres complexes : constructeur

class CComplexe{
float reel ;
float imag ;
public :
// déclaration (prototype) du constructeur
CComplexe( float=0.0, float=0.0 ) ;
} ;
// définition du constructeur
CComplexe::CComplexe( float r, float i ){
reel = r ;
imag = i ;
}
void main(){
Ccomplexe z1( 1, 2.2 ), z2( 5 ), z3 ;
}
Pour chaque objet créé il y a appel du constructeur.

46
Classe nombres complexes : ascesseurs

 Pour respecter le principe d’encapsulation : offrir à


l’utilisateur, des accès aux données membres reel et imag  :
class CComplexe{
float reel ;
float imag ;
public :
CComplexe( float=0.0, float=0.0 ) ;
// affecte une valeur à la partie réelle
void SetPartieReelle( float ) ;
// affecte une valeur à la partie imag.
void SetPartieImaginaire( float ) ;
} ;
void CComplexee : :SetPartieReelle( float x){
if( x < 0)
reel = 0 ;
else
reel = x ;
}

47
Classe nombres complexes : ascesseurs

class CComplexe{
float reel ;
float imag ;
public :
CComplexe( float=0.0, float=0.0 ) ;
// affecte une valeur à la partie réelle
void SetPartieReelle( float ) ;
// affecte une valeur à la partie imag.
void SetPartieImaginaire( float ) ;
// retourne la partie réelle
float GetPartieReelle() const ;
// retourne la partie imaginaire
float GetPartieImaginaire() const ;
} ;

48
Classe nombres complexes : ascesseurs

float CComplexee : :GetPartieReelle() const{


return reel ;

void main(){
CComplexe z ;
z.SetPartieReelle( 1 ) ; // reel de z vaut 1
float r = z.GetPartieReelle() ; // r = 1

 Dans une définition de classe C++, l’utilisation modificateur
const derrière une déclaration de fonction membre signifie
que cette fonction membre ne pourra alors pas modifier les
données de la classe.
 L'utilisation du mot clé const de cette manière est une
fonction sécurisée du C++.

49
Classe nombres complexes : addition de deux vecteurs

En partant d’un programme principal :


void main(){
Ccomplexe z1( 1.2, 1 ), z2( 3, 1 ), z3 ;
z3 = z1.plus( z2 ) ;
}
class CComplexe{
float reel ;
float imag ; Passage par valeur de z2
public :
CComplexe plus( CComplexe z ) ;
} ;
CComplexe CComplexee ::plus( CComplexe z){
CComplexe res ;
res = CComplexe( reel+z.reel, imag+z.imag ) ;
return res ;

Ici reel et imag sont les parties réelles et imaginaires de z1

50
Classe nombres complexes

 Définition d’une fonction permettant de savoir lequel, parmi


deux vecteurs, à la plus grande partie imaginaire.
void main(){
Ccomplexe z1( 1.2, 1 ), z2( 3, 1 ), z3 ;
z3 = z1.partieImgPlusGrandeQue( z2 ) ;
}
class CComplexe{
float reel ;
float imag ;
public :
… partieImgPlusGrandeQue( ... ) ;
} ;
... CComplexee ::partieImgPlusGrandeQue( ... z){
if( imag > z.imag ) return ...
else return z ;

51
définition du pointeur this

void main(){
Ccomplexe z1( 1.2, 1 ), z2( 3, 1 ), z3 ;
z3 = z1.partieImgPlusGrandeQue( z2 ) ;
}
 Pour retourner l’objet ayant appelé la fonction (ici z1) le
pointeur caché this a été défini.
 this : adresse de l’objet ayant appelé une fonction
 Pour éviter la recopie d’un objet lors d’un retour par valeur,
on peut retourner une référence :
CComplexe &
CComplexee::partieImgPlusGrandeQue(CComplexe & z){
if( imag > z.imag )
return *this ;
return z ;

52
Constructeurs -Destructeurs

Intérêt

Construireun objet agrégé


Constructeur par recopie

53
Constructeur / destructeur

class CFichier {
FILE* ptrFile ;
public :
CFichier( const char* nomFichier ) ;
~CFichier() ;
};
 Un constructeur est une fonction membre qui porte le même
nom que la classe. Un constructeur sert à initialiser les
données membres.
CFichier::CFichier(const char* nomFichier){
ptrFile = fopen( nomFichier ) ;
}
 Un destructeur est une fonction membre qui porte le même
nom que la classe précédé du symbole « ~ ». Il permet de
« nettoyer » un objet avant que la mémoire soit libérée.
CFichier::~CFichier() {
fclose( ptrFile ) ;
}

54
Constructeur / destructeur (Appels ?)

 Les constructeurs et les destructeurs sont


appelés automatiquement pour faciliter
l’utilisation des objets :
 void main(){
 // appel du constructeur pour fichier
 CFichier fichier( "NomFichier" ) ;
 CFichier *ptrFich ;
 // appel du constructeur pour ptrFich
 ptrFich = new CFichier( "bidon" ) ;
 …
 // appel du destructeur pour ptrFich
 delete ptrFich ;
} // appel du destructeur pour fichier

55
Construire un objet agrégé

class CA{  Pour une agrégation, il


... faut :
public :  construire l’objet agrégé (ici
a) ;
CA( int ) ;  puis construire l’objet
}; agrégat (ici b).
CA::CA( int i ){  constructeur de CB :
... CB::CB( int i ) : a( i ){
} ...
}
class CB{ void main(){
CA a ; CB b( 1 ) ;
public : /* objet de type CB incluant un
CB( int ) ; objet de type CA*/
}; }

56
Construire un objet agrégé (2)

class CA{
...
public :
CA( int=0 ) ;
};  Si le constructeur de CA a
CA::CA( int i ){ une valeur par défaut l’appel
... du constructeur de CA n’est
} plus nécessaire :

class CB{ CB::CB( int i ){


CA a ; ...
public :
}
CB() ;
};

57
Construire une classe ayant des parties dynamiques

class CA{
int* tableau ; // partie dynamique
int taille ;
public :
CA( int ) ; // constructeur
~CA() ; // destructeur
} ;
CA : :CA( int dimension ){
tableau = new int[ taille=dimension];
}
CA : :~CA(){
delete [] tableau ;
}
void main(){
CA a( 10 ) ;//CA a = CA( 10 ) ;
} // appel du destructeur

58
La Copie d’un objet ayant des parties dynamiques
 Avec un passage par valeur : création d’une variable locale à la
fonction (copieDeA) qui est une copie, membre à membre, de la
variable ayant appelée la fonction (a).
void PassageParValeur( CA copieDeA ){

} // appel du destructeur pour copieDeA
void main(){
CA a( 10 ) ;
PassageParValeur( a ) ;
}

 2 objets mais 1 seul tableau.


Le tableau est détruit en
sortant de la fonction (appel
du destructeur pour
copieDeA) :
CA::~CA(){
delete [] tableau ;
}

59
Intérêt d’un constructeur par recopie

 avoir 2 objets et 2 tableaux :


void PassageParValeur(CA copieDeA){

}//appel du destructeur pour copieDeA
void main(){
CA a( 10 ) ;
PassageParValeur( a ) ;
}

Pour créer un clone d’un objet, il


faut définir un constructeur par
recopie.

60
Constructeur par recopie

class CA{ CA::CA( const CA & a ){


int taille ;
taille = a.taille ;
int* tableau ;
public ; tableau =
CA( int ) ; new int[ taille ] ;
CA( const CA & ) ;
int i ;
// par recopie
}; for( i=0; i<taille; i++ )
tableau[ i ] =
a.tableau[ i ] ;
}

61
Constructeur par recopie

 Le constructeur par
recopie est appelé
automatiquement quand
on clone un objet :
 objet passé par valeur à
une fonction ;
 objet retourné par
valeur d’une fonction ;
 objet déclaré et initialisé
avec un autre objet :
CA a( 10 ) ;
CA b = a ;

62
Constructeur par recopie

 Règles :
 si une classe a des parties dynamiques, il
faut définir le constructeur par recopie ;
 Pour interdire le clonage : déclarer le
constructeur par recopie comme privé :
class CA{
CA( const CA & ) ;
public :
...
};

63
La surcharge des opérateurs

Surcharger les opérateurs : +, [], <, ++, << et =


Commutativité des opérateurs

Fonctions amies

Affichage de l’état d’un objet

64
Surcharge des opérateurs

C++ permet d’étendre les opérateurs aux objets :


void main(){
CChaine chaine( "azerty" ) ;
char c = chaine[ 0 ] ; // c = ‘ a ’ ;
chaine[ 0 ] = ‘q’ ;
cout << chaine << endl ; // à l ’écran : qzerty
CChaine autreChaine( "qsdfg" ) ;
chaine = chaine + " " + autreChaine ;
cout << chaine << endl ; // à l ’écran : azerty qsdfg
if( chaine < "zsdzzd" )
cout << "plus petit" << endl ;
else
cout << "plus grand" << endl ;
}

65
Surcharge de l’opérateur + : addition de 2 complexes

class CComplexe {
float reel ;
float imag ;
public :
CComplexe plus( CComplexe z ) ;
} ;
CComplexe CComplexee : :plus( CComplexe z){
res = CComplexe( reel+z.reel, imag+z.imag ) ;
return res ;

void main(){
CComplexe z1( 1, 2 ), z2( 2, 4 ), z3 ;
z3 = z1.plus( z2 ) ;
z3 = z1 + z2; //pouvoir écrire l’addition naturellement
}

66
Surcharge de l’opérateur + : addition de 2 complexes

class CComplexe {
float reel ;
float imag ;
public :
CComplexe plus( CComplexe z ) ;
//Déclaration de l’opérateur +
CComplexe operator+ ( CComplexe z );
} ;
void main(){
CComplexe z1( 1, 2 ), z2( 2, 4 ), z3 ;
z3 = z1.plus( z2 ) ;
z3 = z1 + z2 ; // z3 = z1.operator+( z2 ) ;
z3 = z1 + z2 + z3 ;
// z3 = ( z1.operator+(z2) ).operator+(z3) ) ;
}
 Conservation de l’ordre de priorité : + de gauche à droite

67
Surcharge de l’opérateur + : addition de 2 complexes

CComplexe CComplexe : :plus( CComplexe z) {


res = CComplexe( reel+z.reel, imag+z.imag ) ;
return res ;

CComplexe CComplexe : :operator + ( CComplexe z){
res = CComplexe( reel+z.reel, imag+z.imag ) ;
return res ;

+ : opérateur prédéfini pour


des flottants

68
Surcharge de l’opérateur [] : lire l’état d’un objet

class CVecteur{
float tab[10] ;
public :
... operator [] ( … ) ;
} ;
void main(){
CVecteur v ;
float f ;
f = v[1]  ;
// lire tab[1] ; <=> f = v.operator[] ( 1 ) ;
}
 Comment consulter en lecture tab ?

69
Surcharge de l’opérateur [] : lire l’état d’un objet

class CVecteur{
float tab[10] ;
public :
float operator[](int);
} ;

float Vecteur::operator [](int i){


if( (i<0) || (i>=10) )
return tab[0] ;
return tab[i] ;
}

70
Surcharge de l’opérateur [] : modifier l’état d’un objet

class CVecteur {
float tab[10] ;
public :
float operator [] ( int ) ; // retour par valeur
};
void main(){
CVecteur v ;
v[1] = 1 ; // erreur à la compilation
}

impossible d’avoir une valeur à


gauche d’un opérateur
d’affectation = (?????)

71
retour par référence

 Solution = retour par référence


class CVecteur{
float tab[10] ;
public :
float & operator []( int );
} ;

float & CVecteur : : operator [] (int i){


if( (i<0) || (i>=10) ){
return tab[0] ;
}
return tab[i] ;
}

72
Opérateur « chaînables » : retour par référence. Exp. <

 class CPile{
 int tab[10] ;
 int nb ; // initialisé à 0 dans le constructeur
 public :
 ... operator < ( int ) ;
 } ;
 void main(){
 CPile t ;
 t < 2 < 3 ; //(t.operator<(2) ).operator<(3) ;
 }

73
Opérateur « chaînables » : retour par référence. Exp. <

class CPile{
float tab[10] ;
public :
CPile & CPile ::operator < (int )  ;
} ;

CPile & CPile : :operator < ( int val ){


if( nb < 10 ){
tab[ nb ] = val ;
nb++ ;
}
return *this ;
// retour de la pile modifiée
}

74
Surcharge de l’opérateur ++

void main(){
CComplexe z ;
z++ ; // z.operator++() ;
++z ; // z.operator++( int ) ;
}
class CComplexe{
float reel ;
float imag ;
public :
// opérateur préfixé
CComplexe operator ++ () ;
// opérateur postfixé
CComplexe operator ++ ( int );
};

75
Surcharge de l’opérateur ++

 CComplexe Ccomplexe::operator ++ (){


 ++reel ;

 return *this ;

 }

CComplexe CComplexe::operator ++ ( int i ){


CComplexe res = *this ;
reel++ ;
return res ;
}

76
Commutativité des opérateurs : fonctions amies

 additionner un réel et un complexe.


void main(){
CComplexe z1( 1, 2 ), z2( 2, 4 ), z3 ;
z3 = z1 + 2 ;
}
 Deux solution:
 convertir 2 en un complexe ;
 Utiliser:
CComplexe CComplexe::operator + ( Ccomplexe )

77
Commutativité des opérateurs : fonctions amies

 conversion avec un constructeur à 1 argument :


class CComplexe{
float reel ;
float imag ;
public :
CComplexe( float, float=0 ) ;
CComplexe operator + ( CComplexe ) ;
};
CComplexe::CComplexe( float r, float i ){
reel = r ;
imag = i ;
}

78
Commutativité des opérateurs : fonctions amies

 assurer la commutativité de l’opérateur +


:
void main(){
CComplexe z1( 1, 2 ), z2( 2, 4 ), z3 ;
z3 = 2 + z1 ;
// on ne peut pas écrire z3=2.operator+(z1) ;
}
 operator + ne peut pas être une fonction
membre.
 fonction amie : fonction qui n’est pas
membre d’une classe mais qui peut
accéder aux données membres.
79
Commutativité des opérateurs : fonctions amies

class CComplexe {
float reel ;
float imag ;
public :
CComplexe( float, float=0 ) ;
friend CComplexe operator + (CComplexe,CComplexe);
};

CComplexe operator + ( CComplexe z1, CComplexe z2 ){


CComplexe res(z1.reel + z2.reel, z1.imag + z2.imag);
return res ;

80
Afficher l’état d’un objet (opérateur <<) : fonctions amies

void main(){
CComplexe z1( 1, 2 ) ;
cout << z1 ; // 1+2i à l’écran
}
 En écrivant « cout.operator<<(z1) ; » => cout n’est pas un complexe !
ostream & operator << (ostream & s, const CComplexe & z ) {
s << z.x << “ + ” << z.y << “ i ” ;
return s ;
}
#include <iostream.h>
class CComplexe { …
public :

friend ostream & operator <<(ostream &, const CComplexe &);
} ;
 Retour par référence pour écrire :
 cout << z1 << "," << z2 ;

81
Surcharge de l’opérateur d’affectation =

class CTableau {
int* tab ;
int taille ;
public :
CTableau( int ) ;//constructeur
CTableau( const CTableau & );// recopie
~CTableau() ; // destructeur
} ;
void main(){
CTableau t1( 10 )
CTableau t2 ( 20 );
// t1 et t2 ont chacun leur tableau
t1 = t2 ;
//t1 et t2 ont le même tableau

82
Surcharge de l’opérateur d’affectation =

void main() {
CTableau t1( 10 )
CTableau t2 ( 20 );
t1 = t2 ;
//t1.operator=(t2)
}

class CTableau{
...
public :

CTableau & operator = ( const CTableau & ) ;
} ;

83
Surcharge de l’opérateur d’affectation =

CTableau & CTableau::operator =(const CTableau & t){


if( this != &t ) // si t1 = t1{
delete [] tab ; // ≠ constructeur par recopie
tab = new int [ taille = t.taille ] ;
int i ;
for( i=0; i<NombreElement; i++ )
tab[ i ] = t.tab[ i ] ;
}
return *this ; // si t = t1 = t2 = ...
}

84
Surcharge de l’opérateur d’affectation =

 Règles :
 si une classe possède des parties dynamiques,
l’opérateur d’affectation doit être redéfini ;
 pour interdire l’utilisation de l’opérateur =, il faut le
déclarer comme privé.
 Cas particulier :
 il n’y a pas appel de l’opérateur d’affectation dans le
cas :
void main(){
CTableau t1( 10 ) ;
CTableau t2 = t1 ;
}
 mais appel du constructeur par recopie s’il a été
redéfini.

85
Liste des opérateurs surchargeables

 tous les opérateurs peuvent être


surchargés sauf :
 . .* :: ?: sizeof # ##
 impossible de créer des opérateurs à
partir de symboles ;
 impossible de modifier les opérations sur
les types prédéfinis ;
 la surcharge d’un opérateur conserve :
 sa pluralité ;
 sa priorité.

86
Résumé : retour par valeur, par référence, fonctions amies

 Retour par valeur :


 opérateurs arithmétiques binaires (*, /, +, -, etc);
 opérateurs de lecture d’objets ( [], etc ).
 Retour par référence :
 opérateurs d’affectation composés (+=, -=, etc) ;
 opérateurs d’écriture dans un objet ( [], etc ) ;
 opérateurs chaînables.
 Fonctions amies (violation du principe
d’encapsulation) limités à :
 surcharge des opérateurs << et >> ;
 assurer la commutativité des opérateurs.

87
L’héritage

Principe
Modes: Protégé, privé et public
Redéfinition des fonctions membres
Fonctions Virtuelles et polymorphisme
Classes Abstraites
Héritage multiple

88
L’héritage : principe
 L’héritage permet de créer facilement des nouvelles classes à
partir de classes existantes :
class CPixel{
int x ;
int y ;
public :
CPixel( int=0, int=0 ) ;
void affiche() ;
};

CPixel est la classe de base.


void main(){
class CPixelCouleur : public CPixel{
CPixelCouleur p( 255 ) ;
int couleur ;
p.affiche()
public :
//Affichage de p
CPixelCouleur( int=0 ) ;
}
};

CPixelCouleur qui hérite de CPixel est la classe dérivée.

89
L’héritage : principe

class CA{ class CB : public CA{


int a ; int b ;
public : public :
// ... // ...
}; };

void main(){
CB b ; // Comment b est stocké en mémoire ?
}

Un objet de type CA est crée avant l’objet b

90
Accès aux données membres de la classe de base

class CPixel{ class CPixelCouleur :


int x ; public CPixel{
int y ; int couleur ;
public : public :
CPixel( int=0, int=0 ) ;
CPixelCouleur( int=0 ) ;
void affiche() ;
void deplacer( int, int ) ;
};
};

91
Accès aux données membres de la classe de base

 Le concepteur de CPixelCouleur n’a pas


accès aux données privées de CPixel :
void CPixelCouleur::deplacer( int DeplacX, int DeplacY ){
x = x + DeplacX ; //Erreur à la compilation
y = y + DeplacY ; //x et y inaccessibles
}

92
Accès aux données membres de la classe de base

class CPixel{ class CPixelCouleur :


protected: public CPixel{
int x ; int couleur ;
int y ;
public :
public :
CPixel( int=0, int=0 ); CPixelCouleur( int=0 );
void affiche() ; void deplacer( int, int );
}; };

void CPixelCouleur::deplacer( int deplacX, int deplacY){


x = x + deplacX ; // OK
y = y + deplacY ; // OK
}

93
Accès aux données membres de la classe de base

94
1er type d’héritage : privé

class CTableau{ class CPile : CTableau{


int *tab, nb ; int sommet ;
public : public :
Ctableau(int=10 ); CPile() { sommet = -1 ; }
int & operator[](int); void Empiler(int i){(*this)[++sommet]=i ;}
}; int Depiler() {return (*this)[sommet-- ];}
bool Vide() { return sommet==-1 ; }
void main(){ };
CPiler p ;
p.Empiler( 1 ) ;
p[ 3 ] = 1 ;
//erreur à la compilation
}

95
1er type d’héritage : privé

 Héritage par défaut


class CDerive : (private) CBase{

};
 la classe dérivée restreint les
fonctionnalités de la classe de base ;
 la classe de base n’est pas une
généralisation de la classe dérivée (la
classe dérivée n’est pas une sorte de la
classe de base).

96
2eme type d’héritage : protégé

 Restreindre les droits des utilisateurs de la classe


dérivée ;
 Conserver les droits maximum aux concepteurs
des classes dérivées de la classe de base.
class CDerive : protected CBase{

};

97
3eme type d’héritage : public

 Conserver des droits maximum aux


concepteurs et aux utilisateurs des
classes dérivées de CDerivee.

98
héritage public

 le plus utilisé ;
 le seul qui permet d’implémenter la généralisation (relation
du type « est un »ou « est une sorte de ») ;
 le seul qui permet d’utiliser le polymorphisme : utiliser un
objet dérivé à la place d’un objet de base.

99
Redéfinition des fonctions membres

 Quand une fonction est redéfinie dans une classe dérivée =>
la fonction de la classe de base est masquée :

class CPixel{
protected : class CPixelCouleur :
int x ; public CPixel{
int y ; int couleur ;
public : public :
CPixel(int=0, int=0) ; redéfinition CPixelCouleur( int=0 ) ;
void affiche() ; void affiche() ;
};
};
void main(){
CPixel p ;
p.affiche() ;
// appel de affiche de CPixel
CPixelCouleur pc ;
pc.affiche() ;
// appel de affiche de CPixelCouleur
}

100
Compatibilité entre objets dérivés et objets de la classe de
base
 La conversion d’un objet dérivé, vers un objet d’une classe de
base est possible :
void main(){
CPixel p ;
CPixelCouleur pCoul ;
p = pCoul ;
}
puisque un pixel coloré « est un » pixel : on ne retient que la
partie pixel.
 La conversion d’un objet de la classe de base vers un objet
dérivée est impossible :
void main(){
CPixel p ;
CPixelCouleur pCoul ;
pCoul = p ; // erreur à la compilation
}
puisque un pixel « n’est pas » un pixel coloré.

101
Compatibilité entre pointeurs vers classe de base et classe
dérivée

 Un pointeur sur une classe de base peut pointer sur un


objet dérivé :
void main(){
CPixelCouleur p ;
CPixel *ptrPixel
ptrPixel = &p ;
}
 Un pointeur sur une classe dérivée ne peut pas pointer sur
un objet de la classe de base :
void main(){
CPixel p ;
CPixelCouleur *ptrPixelCouleur ;
ptrPixelCouleur = &p ; // erreur à la compilation
}

102
Appeler une fonction via un pointeur sur une classe de base

class CPixel{
protected : class CPixelCouleur :
int x ; public CPixel{
int y ; int couleur ;
public : public :
CPixel(int=0, int=0) ; CPixelCouleur( int=0 ) ;
void affiche() ; void affiche() ;
};
};
void main(){
CPixel p ;
p.affiche() ; // appel de affiche de CPixel
CPixelCouleur pc ;
pc.affiche() ; // appel de affiche de CPixelCouleur
CPixel* ptrPix = &p ;
ptrPix->affiche() // appel de affiche de CPixel
ptrPix = &pc ;
ptrPix->affiche() // appel de affiche de CPixel !!!!
}

103
Fonction virtuelle et polymorphisme

class CPixel{ class CPixelCouleur :


protected : public CPixel{
int x ; int couleur ;
int y ; public :
public : CPixelCouleur( int=0 ) ;
CPixel(int=0, int=0) ;
void affiche() ;
virtual void affiche() ;
}; };
void main(){
CPixel p ;
CPixel* ptrPix = &p ;
ptrPix->affiche() // appel de affiche de Cpixel
CPixelCouleur pc ;
ptrPix = &pc ;
ptrPix->affiche() ; //appel de affiche de CPixelCouleur
}

104
Intérêt des fonctions virtuelles

On peut ajouter des


classes sans modifier
le code :
void main(){ void
Polygone poly ; dessiner(FigureGeom*
Parallelogramme para ; fig[], int nb ){
... L’ajout d’une
classe modifie int i ;
FigureGeom* fig[ N ] ; for( i=0; i<nb; i++ )
fig[ 0 ] = &poly ; fig[ i ]->Tracer() ;
fig[ 1 ] = &para ; }

}
105
Les fonctions virtuelles pures

 Comment écrire le corps de Tracer() de


FigureGeom ?
 Une fonction virtuelle pure est une

fonction qui n’est pas définie :


class FigureGeom{ ...
public :
virtual void Tracer() = 0 ; // pas de corps
};

106
Les classes abstraites

 Si une classe contient au moins une


fonction virtuelle pure :
 c’est une classe abstraite ;
 elle ne peut pas être instanciée :
 FigureGeom fig ; // erreur à la compilation
 FigureGeom* ptrFig ; // OK
 elle ne peut être utilisée que par héritage.

107
L’héritage multiple
 Une classe peut hériter de plusieurs sous-classes :
class CPoint{
int x, y ;
public :
CPoint( … ) ;
~CPoint() ;
};
class CCouleur{
short couleur ;
public :
CCouleur( … ) ;
~CCouleur() ;
};
class CPointCouleur : public CPoint, public CCouleur{
public :
CPointCouleur( … ) ;
~CPointCouleur() ;
};

108
Le conflit des noms dans l’héritage multiple
 Si les classes de base possèdent une fonction ayant le même nom :
class CPoint{
...
public :
… Il faut utiliser l’opérateur de résolution de
void affiche() ;
portée ::
};
void CPointCouleur::affiche(){
class CCouleur{
CPoint::affiche() ; // affiche() de CPoint
...
public :
CCouleur::affiche() ; //affiche() de CCouleur
… }
void affiche() ;
};
class CPointCouleur : public CPoint, public CCouleur{
...
public :
void affiche() ;
};

109
L’héritage en diamant

 CC hérite deux fois :


 des fonctions membres de
CBase : sans importance
puisque le code n’est pas
réellement dupliqué ;
 des données membres qui
sont dupliquées.

 Incorporer une seule fois les membres de CBase


dans CC :
 class CA : public virtual CBase { … } ;

 class CB : public virtual CBase { … } ;

 class CC : public CA, public CB { … } ;

110
Fonctions et classes génériques

Modèles de fonctions
Patron de classes

111
Généricité : Introduction

 La généricité permet d'avoir des fonctions et


des classes paramétrables, c'est-à-dire que,
au moment où nous en avons besoin, nous
précisons le type à utiliser pour ladite fonction
ou ladite classe.
 C'est le type qui est paramétrable.
 Ce concept nous permettra d'avoir des écritures
plus concises et ainsi d'éviter de nombreuses
surdéfinitions.
 La généricité est souvent appelée «  template
 » ( patron – évocation de la haute couture),
ou également «  modèle  ».

112
Fonctions génériques

 Prenons l'exemple de la fonction qui


retourne la valeur minimum de deux
nombres.

113
Choix du bon type ?

 La difficulté, dans ce genre de situation, est


de proposer les bons types à la fois pour les
paramètres et pour la valeur de retour.
 A priori, il semble que se soit le type réel qui
soit le plus adapté. En effet, le compilateur
accepte les écritures prévues pour «  y  » et
pour «  z  ».
 En prenant le type le plus haut dans la
hiérarchie, nous sommes sûrs que le
compilateur acceptera des écritures
proposant des types dits «  plus petit  »
puisque le langage effectue le changement
de type automatique. De plus, en prenant le
type le plus fort, nous n'obtenons aucune
perte d'informations.
114
Problèmes avec le type « plus fort »

 Dans le deuxième cas, les


arguments sont des
entiers.
 changements de type
pour les paramètres.
 Changement de type
pour la valeur de retour.
 Ce code n'est pas très
optimisé:
 Une perte de temps au
moment des
changements de type
automatiques.
 Solution: proposer des
fonctions qui soient
adaptées à chaque
situation
particulière(surdéfinition).
115
Solution C++: fonction générique (min)

 template : indique que nous


mettons en place un nouveau
modèle, il est suivi des
séparation « <> » dans
lesquels nous spésifions les
paramètres qui doivent être
séparés par des virgules.
 Paramètre de type: le plus
fréquent, le compilateur
détermine le type et fabrique la
fonction en conséquence. Ce
paramètre est identifié par le
mot réservé « class ».
 Paramètre non type: il
représente une expression
constante et il est déclaré
d’une manière ordinaire (ex:
int taille).

116
Utilisation des modèles

117
Fonction générique « inline »

118
Surdéfinition de fonctions génériques

 On veut
récupérer la
valeur minimale
d'un tableau
d'entiers passé en
argument, Nous
pouvons donc
faire coexister
des fonctions
génériques avec
des fonctions
classiques .

119
Surdéfinition de fonctions génériques

120
Classes génériques - Conception

 Lorsque nous consultons le code interne, nous remarquons


que nous avons très peu de référence au type entier, nous
pouvons le remplacer par un autre type pour retrouver un
fonctionnement identique pour d'autres types de tableau.
 Dans ce cas, il est plus avantageux de proposer un tableau
générique, et c'est l'utilisateur qui décidera le type qu'il
désire.
121
Utilisation

122
Définition des méthodes

123
Définition des méthodes (2)

 Il s'agit d'un prototype (d'un patron) qui


servira à la fabrication (réelle) de
plusieurs classes de types différents.
Dans ce contexte, la déclaration de la
classe ainsi que la définition des
méthodes doivent se trouver entièrement
dans le fichier en-tête.
 En effet, tout ce qui est générique est
utilisé uniquement par le « 
préprocesseur  » du compilateur.

124
Définition des méthodes (3)

 Les réels prennent


plus de places que
les entiers d’où
modification du
constructeur;

125
Classe générique–Paramètre non type

 Dans le cas des classes génériques, le paramètre «  non


type  » peut s'avérer particulièrement utile, notamment
pour implémenter un tableau de type quelconque en
spécifiant, dès le départ, la dimension du tableau.

126
Classe générique–Paramètre non type

127
Classe générique–Paramètre non type

 En ajoutant des valeurs par défaut

128
Gestion des exceptions

129
Introduction

 Les exceptions sont des anomalies qu'un programme


détecte en cours d'exécution, telles des divisions par 0, un
accès à l'extérieur des bornes d'un tableau ou l'épuisement
de la mémoire. De telles exceptions sortent du
fonctionnement normal du programme et requièrent de sa
part une gestion immédiate.
Gestion des exceptions en C++
 Le langage C++ dispose d'un mécanisme très souple
nommé gestion d'exception , qui permet à la fois :
 de dissocier la détection d'une anomalie de son traitement,
 de séparer la gestion des anomalies du reste du code, donc de
contribuer à la lisibilité des programmes

130
Mécanisme de gestion des exceptions

Ce mécanisme s'effectue toujours en deux temps.


1. la détection de l'anomalie. Càd déclencher une exception
correspondant à l'anomalie. Cette phase s'appelle souvent
lever ou lancer une exception par la directive throw.
2. Ensuite, s’occuper de ce qui s'appelle la gestion
d'exception, qui consiste à proposer un certain nombre
d'actions pour gérer le problème lié à une ou plusieurs
anomalies. Chaque exception est caractérisée par un type,
et le choix du bon gestionnaire se fait en fonction de la
nature de l'expression mentionnée à throw. Cette phase
est réalisée par les directives try – catch (essayer et
capturer).

131
Détecter les anomalies

Dans l'exemple ci-dessus, nous voyons


apparaître deux anomalies possibles :
 D'une part, une mauvaise proposition
d'indice qui nous envoie en dehors des
limites du tableau.
 D'autre part, une taille de tableau
négative.

132
Détecter les anomalies

133
Lever une exception (avec un entier)

 il faut lever une exception qui correspond


à l'anomalie. Pour cela, on utilise
l'instruction throw suivie d'une valeur
d'un type quelconque.
 Par exemple, une valeur numérique
entière qui indique l'erreur correspondant
à l'anomalie, comme -1 pour le problème
de construction, et -2 pour le problème
lié à l'indice.
 Cette démarche n'est pas très élégante.

134
Lever une exception (avec un entier)

135
Lever une exception (avec un type par Enum)

 nous pouvons fabriquer un nouveau type.


Il est, en effet, préférable d'utiliser une
énumération dont chacun des
énumérateurs donne explicitement le
type de l'erreur

136
Lever une exception (avec un type par Enum)

137
Lever une exception(Avec une classe)

 le mieux est de créer une classe


correspondant à chaque type d'erreur. La
valeur à envoyer dans l'exception est
alors un objet.
 Pour le moment, il suffit de fabriquer des
classes sans rien à l'intérieur, le but est
juste de créer des nouveaux types
adaptés aux anomalies rencontrées.

138
Lever une exception(Avec une classe)

139
Interception et gestion des exceptions

 Lorsqu'une exception est levée, il serait


souhaitable de maîtriser la situation et de
proposer une alternative de fonctionnement.
Pour cela, il faut mettre en œuvre ce que l'on
appelle une gestion d'exception qui se déroule
finalement en trois phases :
1. Tentative d'exécution d'un ensemble
d'instructions,
2. Capture de l'exception, si un problème est
rencontré durant cette tentative,
3. Gestion de l'exception en proposant une
nouvelle suite d'instructions.

140
Tentative

 Consiste à entourer les instructions qui


sont susceptibles de lever des exceptions
par un bloc try. Un bloc try commence
par le mot clé try suivi d'une séquence
d'instructions entourées d'accolades{ }.
 Le bloc try est suivi d'une liste de
gestionnaires appelés clauses catch. En
fait, le bloc try regroupe un ensemble
d'instructions et leur associe un ensemble
de gestionnaires pour gérer les
exceptions que peuvent lever les
instructions.

141
Tentative

 deux clauses catch sont associées au bloc try. Le nombre


de clauses catch dépend du nombre de type d'erreur
possible lors de l'exécution d'un ensemble d'instructions.
 Si aucune exception ne survient, l'ensemble du code à
l'intérieur du bloc try est exécuté et les gestionnaires
associés au bloc try sont ignorés. Le programme exécute
ensuite les instructions qui sont placées à la suite des
clauses catch.
 Si une exception est levée à l'intérieur d'un bloc try, les
instructions qui suivent l'instruction lançant l'exception ne
sont pas exécutées. L'exécution du programme reprend
dans la clause catch gérant l'exception.

142
143
gestionnaire d'exception

 Un gestionnaire d'exception C++ est une clause


catch . Quand une exception est levée depuis des
instructions dans un bloc try, la liste des clauses catch
qui suit le bloc try est recherchée afin d'y trouver une
clause catch qui soit capable de gérer l'exception.
 Une clause catch se compose de trois parties :
1. le mot clé catch,
2. la déclaration d'un type unique ou d'un objet unique entre
parenthèses (appelée déclaration d'exception),
3. et un ensemble d'instructions dans une instruction
composée (accolades).

144
gestionnaire d'exception

145
Déroulement de pile

 Si l'expression throw se trouve dans un bloc try, les clauses catch


associées à ce bloc sont examinées pour voir si l'une d'elles peut
gérer l'exception. Si une clause catch est détectée, l'exception est
gérée. Si aucune clause catch n'est détectée, la recherche se poursuit
dans le bloc try-catch de niveau supérieur (celui qui englobe le try-
catch imbriqué).
 Ce processus se poursuit en remontant l'imbrication des blocs try-
catch jusqu'à ce qu'une clause catch pour l'exception soit trouvée.
Dès qu'une clause catch pouvant gérer l'exception est rencontrée, on
entre dans la clause catch et l'exécution du programme continue
dans ce gestionnaire.
 Si aucun gestionnaire n'est trouvé, le programme appelle la fonction
terminate() définie dans la bibliothèque du C++ standard. Cette
fonction propose un comportement par défaut, qui appelle
notamment la fonction abort() qui elle-même indique que le
programme se termine anormalement «  Abnormal program
termination  ».

146
Propager une exception

 Il est possible qu'une clause unique ne puisse pas gérer


une exception complètement. Après quelques actions
correctives, une clause catch peut décider que
l'exception sera gérée par un bloc try-catch de niveau
supérieur. Il suffit pour cela de propager l'exception.
Dans un gestionnaire, l'instruction throw (sans
expression) retransmet (propage) l'exception au
niveau englobant.

147
Gestionnaire pour toutes les exceptions

 vous pouvez capturer toutes les exceptions dans


une seule clause catch. Cette clause catch possède
une déclaration d'exception de la forme (…), où les
trois points sont une ellipse.
 il sera toujours placé en dernier de la liste des
gestionnaires d'exception.

148
Les objets exception

 Quand la déclaration d'exception dans


une clause catch déclare-t-elle un objet ?
Un objet sera déclaré lorsque nous
devons obtenir la valeur ou manipuler
l'objet exception créé par l'expression
throw.

149
150

Vous aimerez peut-être aussi