Vous êtes sur la page 1sur 45

Langage C++ 2EME ANNÉE IGA / 2011-2012

Chapitre 1

SPECIFICITES DU C++

Le C++ est un langage C enrichi de nouveaux concepts (en particulier ceux de la


programmation orientée objet). Il y a néanmoins quelques incompatibilités avec le langage C.
Nous détaillons les principales ci-dessous tout en présentant les nouvelles spécificités de C++.

1) Introduction de commentaires:
La façon d’introduire des commentaires entre /* et */ est toujours valable, mais le C++
introduit une nouvelle forme : tout ce qui suit le délimiteur // sur une ligne est considéré
comme commentaire.
Exemple :
/* programme de calculs arithmétiques*/
int a,b,x ;
x=a/b ; //on effectue une division

2) Constantes et Variables
a) Constantes
En C, on avait l’habitude de définir des constantes symboliques à la compilation par la
directive #define du préprocesseur. Ceci n’est pas une utilisation à conseiller en C++.
En C, une variable déclarée avec l’attribut const n’est pas connu à la compilation, en C++, les
variables déclarées avec l’attribut const sont des constantes symboliques dont la valeur est
connue à la compilation. Cela permet de définir la taille d’un tableau sans passer par le
préprocesseur.

Exemple :
main () {
const int n=3 ;
int tab[n] ; //est valide en C++
tab[0]=99 ; tab[1]=-1 ;tab[2]=2 ;
…}
b) Déclarations de variables
En C++, il n’est plus nécessaire de regrouper les déclarations en début de fonction ou en
début de bloc. Il devient aussi possible d’employer des expressions dans des initialisations,
comme dans cet exemple :
int n ;
….
n=… ;
….
int q=2*n-1 ;
….

Une déclaration de variable peut donc en C++, figurer n’importe où dans le code. La portée
d’une déclaration est limitée à la suite du bloc qui la contient.
Exemple :
#include <iostream.h>
main () {
const int n=3 ;

Pr. J. BOUMHIDI
1
Langage C++ 2EME ANNÉE IGA / 2011-2012

int tab[n] ;
for (int i=0 ;i<n ;i++) //en C: int i; for (i=0 ;i<n ;i++) …
tab[i]=i;
Remarque: La déclaration de la variable i n’est val able que dans la boucle for où elle est
définie.
3) Entrées / Sorties standard
On peut toujours utiliser les fonctions du C, définies dans la bibliothèque <stdio.h>. Mais le
C++ offre des alternatives plus conviviales, à travers la bibliothèque <iostream.h>. Ces
fonctions utilisent le concept de flot d’entrée et de sortie associé à une unité d’entrée/sortie
externe.
cin : flot lié à l’entrée standard, par défaut le clavier
cout : flot lié à la sortie standard, par défaut l’écran
Deux opérateurs sont définis :
>> Cet opérateur extrait une variable ou une expression du flot d’entrée
<< Cet opérateur envoie une variable ou une expression sur le flot de sortie
Ces opérateurs agissent sur tous les types prédéfinis. Il n’est pas nécessaire, comme c’était le
cas avec les fonctions du C, de préciser les types des variables (les codes formats).
Ainsi :
cout<<expression1<<expression2<<…..<<expressio nN
affiche sur l’écran les valeurs des différentes expressions indiquées.
De même :
cin>>lvalue1>>lvalue2>>…..>>lvalueN
lit sur le flot cin les informations de l’un des types char, short, int, long, float, double , ou
char*
Le caractère fin de ligne peut être inséré par le manipulateur endl, prédéfini dans le fichier
<iostream.h>.
Remarque :
La norme utilise aussi le nom iostream, sans extension. Cependant, il faut faire appel à la
notion d’espace de nom selon la syntaxe suivante :
#include<iostream>
using namespace std ;

Exemple :
#include<iostream.h>
main() {
int age;
char nom[15];
char * t= "exemples d’entrées/sorties ";
/*lorsque l’opérateur cout porte sur une expression de type char*, c’est le contenu de la
chaîne qui est transmis.*/
cout<<t<<endl ;
cout<< "Entrez le nom : " ;
cin>>nom ;
cout<< "entrez l’age :" ;
cin>>age ;
cout<<nom<< " ; "<<age<<endl ;}

Pr. J. BOUMHIDI
2
Langage C++ 2EME ANNÉE IGA / 2011-2012

4) Types
a) Type booléen
En C++, le type booléen est un type standard, le type est bool et les valeurs sont true et false.
Une valeur nulle continue à être interprétée comme false, et la valeur 1 (et toute valeur non
nulle) est interprétée comme true.
b) Types dérivés
En C++, on définit comme en C des structures, des énumérations et des unions. Mais,
dans la déclaration d’objets de ces types dérivés, le s mots clés struct, union, enum ne
sont plus nécessaires ;
Exemple :
struct assure{
…} ;
main() {
const int n=50 ;
assure a[n] ;//en C struct assure a[n] ;
…}
c) Conversions de types
Les contrôles sur les types sont plus rigoureux en C++ qu’en C. le program meur a donc
intérêt à prévoir lui-même les conversions de type. En plus de la syntaxe du C :
(type) expression
On peut en C++ utiliser la syntaxe
type(expression)
Exemple :

5) Fonctions
5.1 Déclarations et définitions de fonctions :
En C++, toute fonction utilisée dans un fichier source doit obligatoirement être déclarée sous
forme d’un prototype (il précise à la fois le nom de la fonction, le type de ses arguments et le
type de sa valeur de retour). Et si la fonction ne renvoie rien, elle doit comporter un type
retour void mais une fonction qui ne reçoit aucun argument doit comporter une liste vide.
5.2 Passage d’arguments par défaut :
Dans la déclaration d’une fonction (prototype), il est possible de prévoir pour un ou plusieurs
arguments (obligatoirement les derniers de la liste) des valeurs par défaut, elles sont indiquées
par le signe =, à la suite du type de l’argument comme dans cet exemple :
float fct( char,int=10,float=0.0) ;
Ces valeurs par défaut seront alors utilisées lorsqu’on appellera la dite fonction avec un
nombre d’arguments inférieure à celui prévu. Par exemple, avec la précédente déclaration,
l’appel fct(‘a’) sera équivalent à fct(‘a’,10, 0.0) ; de même l’appel fct(‘x’,12) sera équivalent à
fct(‘x’, 12, 0.0).
5.3 Passage d’arguments par ré férence :
En C++, par défaut, le mode de transmission des arguments est toujours par valeurs, comme
en C, mais il est possible de choisir le mode de passage par adresse, sans être obligé de faire
appel à la notion de pointeur comme en C(mais qui est toujou rs valable). C’est une solution
hybride entre le passage par valeur et par pointeur.
Pour spécifier q’un argument est transmis par adresse, il suffit dans la liste des arguments, de
faire suivre son type du caractère &. Et celle-ci peut donc modifier les valeurs des paramètres

Pr. J. BOUMHIDI
3
Langage C++ 2EME ANNÉE IGA / 2011-2012

définis dans la fonction appelante. Cela signifie que les éventuelles modifications effectuées
au sein de la fonction porteront sur l’argument effectif de l’appel et non plus sur une copie.
Exemple :
Permutation de deux valeurs : en C, pour résoudre ce problème, on est obligé de passer à la
fonction de permutation des pointeurs sur les variables à permuter. En C++, les choses
s’écriront beaucoup plus simplement : il suffit de passer à la fonction d’échange d es adresses
des variables à permuter.
#include<iostream.h>
main() {
void échange( int&,int&) ;
int x=12, y=5;
échange(x,y);
cout<< "x= "<<x<< " "<< "y= "<<y<<endl ;
}
void échange(int& a, int& b){
int t=a ;
a=b ;
b=t ;}

Exercice :
Ecrire une fonction permettant d’échanger les contenus de deux variables de type int fournies
en argument :
 En transmettant l’adresse des variables concernées
 En utilisant la transmission par référence
Ecrire un programme principal de test
5.4 Surdéfinition de fonctions :
En C++, on peut définir des fonctions à nom générique, c'est-à-dire que, à un nom de fonction
sont associées plusieurs définitions qui différent par les types et le nombre de leurs
arguments. A l’appel, le compilateur se base sur la liste des arguments effectifs pour choisir la
définition appropriée.
Exemple :
float test(int i , int j)
{return float(i+j);}
float test(float i , float j)
{return i*j;}

Ces deux fonctions portent le même nom, le compilateur les acceptera toutes les deux, lors de
l’appel de test(2,3) se sera la première qui ser a appelée . Tan disque lors de l’appel de
test(2.5,2.2), ça sera la deuxième qui sera appelée.
5.5 Fonctions en ligne
Le C++ dispose du mot-clé inline , qui permet de modifier la méthode d’implémentation des
fonctions. Placé devant la déclaration d’une fonct ion, il propose au compilateur de ne pas
instancier cette fonction, mais d’incorporer son corps (son code) au moment de la compilation
dans le code de la fonction appelante. Si la fonction est grosse ou si elle est appelée souvent
le programme devient plu s gros, puisque la fonction est réécrite à chaque fois qu’elle est
appelée. En revanche, elle devient plus rapide car les mécanismes d’appel de fonctions, de
passage de paramètres par valeurs sont ainsi évités. De plus le compilateur peut effectuer des
optimisations additionnel qu’il ne pourra pu faire si la fonction n’était pas inlinée. Cette
Pr. J. BOUMHIDI
4
Langage C++ 2EME ANNÉE IGA / 2011-2012

méthode ne peut s’appliquer qu’à des fonctions qui sont définies dans le même fichier source
que les fonctions appelantes.
Exemple : fonction inlinée pour fournir le minimum de 2 entiers :
#include<iostream.h>
inline int min(int a, int b){
return (a<b?a:b);}
main(){
int min(int,int);
int x=10 ,y=-5 ;
cout<<min(x,y)<<endl ;}

Cependant, il faut connaître les restrictions des fonctions inlines :


o Elles ne peuvent pas être récursives
o Elles ne sont pas instanciées, donc on ne peut pas faire de pointeur sur une fonction
inline
6) Gestion dynamique de la mémoire
En plus des fonctions malloc et free du C, le C++ fournit d’autres moyens pour allouer et
libérer la mémoire. Pour cel a, il dispose d’opérateurs spécifiques : new, delete, new[] et
delete[]. Les deux opérateurs new et new[] permettent d’allouer de la mémoire et les deux
opérateurs delete et delete[] de la libérer :
new type : c’est une instruction pour la création d’un objet, elle permet d’allouer un espace en
mémoire pour contenir une valeur de type spécifié elle renvoie l’adresse du premier octet de
la zone alloué
delete pointeur : libère l’espace alloué par new
Exemple :
int *a ;
a=new int ;
delete a;
new type[n] : perm et la création d’un tableau d’objets, alloue la place pour n objets du type
spécifié et renvoie l’adresse du premier octet du tableau alloué
delete [] pointeur : permet la suppression d’un tableau d’objets
Exemple :
int *a =new int[20] ;
delete [] a;
En c as d’échec d’allocation, la norme laisse l’implémentation libre de choisir entre le renvoi
d’un pointeur de valeur nulle ou l’interruption de l’exécution du programme et le
déclenchement d’une exception bad_alloc. C’est le comportement du gestionnaire inst allé par
défaut. En outre, vous pouvez définir une fonction de votre choix et demander qu’elle soit
appelée en cas de manque de mémoire. Il vous suffit d’en transmettre l’adresse en argument à
la fonction set_new_handler dont le prototype figure dans <new.h>

Exercice
Ecrire le programme suivant (correcte en C et en C++) en utilisant les nouvelles possibilités
d’entrées/sorties de C++ (en évitant l’utilisation des fonctions printf, scanf et malloc) :

#include<stdio.h>
Pr. J. BOUMHIDI
5
Langage C++ 2EME ANNÉE IGA / 2011-2012

main(){
int n,*a ; float x,*b ;
printf("Entrer un entier et un float\n ");
scanf("%d %f ",&n,&x) ;
printf("le produit de %d par %f est : %f ",n,x,n*x) ;
a=malloc(sizeof(int)) ;
b=malloc(sizeof(float)*50) ;}
Exercice:
Ecrire un programme allouant des emplacements pour des tableaux d’entiers d ont la taille est
fournie en donnée. Les allocations se poursuivront jusqu’à ce que l’on aboutisse à un
débordement mémoire. L’exécution se présentera ainsi :
Taille souhaitée ? 20000
Allocation bloc numéro : 1
Allocation bloc numéro : 2
Allocation bloc numéro : 3
……………………….
Mémoire insuffisante – arrêt exécution
Utilisez deux solutions :
a) En vérifiant la valeur retournée par new (sans faire alors appel à set_new_handler)
b) En faisant appel à set_new_handler

Pr. J. BOUMHIDI
6
Langage C++ 2EME ANNÉE IGA / 2011-2012

Chapitre 2

Introduction à la Programmation Orientée Objet

Dans les méthodes de conception par traitements, qui constituent l’approche traditionnelle, on
cite à titre d’exemple le langage C, le fortran et le pascal, la base de réflexion est effectuée
autour des traitements (fonctions). C'est-à-dire, la description des données et les procédures
qui effectuent les traitements sont réalisées séparément, avec cette façon de procéder, les
données ne sont pas protégées. Les développeurs ont cherché à réunir dans une même entité
les données et les traitements qui s’y rapportent. Cette entité a reçu le nom d’objet (ou classe).
Les langages qui permettent d’implémenter correctement ces concepts objets sont appelés
langages orientées objets. Le langage orientée objet considéré dans ce cours est le langage
C++.

Les limitations de la programmation traditionnelle:


La base de réflexion est effectuée autour des traitements (fonctions). On peut faire les
remarques suivantes sur ce type d’approche :
 Les fonctions trouvées sont adaptées au type du problème posé au départ et ne
permettent que peu d’extensibilité du système obtenu et peu de réutilisabilité.
 Peu de protection des structures des données
 Les traitements sont généralement beaucoup moins stables que les données
Les approches basées sur les traitements ont quand même quelques avantages, dont celui
d’être facilement applicables. Elles peuvent à ce titre être utilisées pour la réalisation
d’applications de taille raisonnable. Elles se révèlent cependant rapidement inadaptés lors de
la réalisation de systèmes sensibles aux changements de spécifications.
Considérons par exemple la réalisation de logiciels (phases de développement et de
maintenances). Cette réalisation nécessite l’utilisation des règles permettant d’assurer une
meilleure qualité de réalisation celle-ci peut être atteinte à travers certains critères :
 Correction ou validité c'est-à-dire le fait qu’un logiciel effectue exactement les
taches pour lesquelles il a été conçu.
 L’extensibilité
 La réutilisabilité
 La robustesse
La conception par traitement ne favorise pas l’utilisation des principes de qualité mis en
évidence, il semble maintenant plus logique de s’organiser autour des données manipulées
(les objets)

Conception par objet :


L’idée de base est de partir des données à traiter (détermination des variables ou objets
manipulés). Une fois cette étape réalisée, le concepteur n’aura plus qu’à réaliser les fonctions
qui s’appuient sur les objets et sur les familles des objets manipulés.
Pour représenter ou d’écrire de tels objets, nous allons nous int éresser non pas aux objets
directement, mais aux entités qui les représentent qui sont des entités abstraites regroupant les

Pr. J. BOUMHIDI
7
Langage C++ 2EME ANNÉE IGA / 2011-2012

données abstraites dites aussi abstract data type ADT (ou champs ou encore Attributs)+les
traitements (fonctions membres ou méthodes).

Exemple :
On veut afficher la moyenne annuelle de chaque élève d’une classe
Les données abstraites (ADT) peuvent être :
-Un nom
-Un tableau de notes
Les traitements sont :
-L’attribution de valeurs aux différents items de l’ADT
-Le calcul de la moyenne
-L’affichage
Et alors on va réunir dans une même entité ces deux éléments :

Instanciation d’un Abstract Data Type :


Lorsque l’on donne des valeurs à tous les items d’un ADT, on dit qu’on l’a instancié.
Exemple :
Un ADT point qui permet de manipuler des points dans le plan, sa structure de données
abstraite se compose de deux coordonnées cartésiennes.
Point : abscisse, ordonnée
On instancie deux fois cette entité : d’où par exemple (2.5) et (4.9) sont deux instances de
l’entité point.

La classe et l’Objet :
La classe : c’est une représentation d’un ADT et des traitements (les fonctions) à réaliser sur
ces données c'est-à-dire (écriture du code).
L’objet : c’est une instance particulière d’une classe.

Programmation Orientée Objet :


Cela consiste à écrire des classes, d’instancient des objets de type classe et d’envoyer à ces
objets des messages pour leur signifier qu’une méthode doit leur être appliquée.

Forme des programmes dans l’approche objet :


Un programme=un ensemble d’objets échangeant de s messages

Pr. J. BOUMHIDI
8
Langage C++ 2EME ANNÉE IGA / 2011-2012

Métaphore du modèle physique

Concepts principaux de conception par objet


La programmation par objet permet de favoriser plusieurs critères de qualités (Fiabilité,
réutilisabilité, extensibilité, Manipulation de grandes quantités de connaissances, qualité de la
communication homme/machine…). Ceci est réalisé grâce aux concepts de la programmation
orientée objet :
 L’encapsulation des données :
Les donnes sont protégées à l’intérieur de chaque objet . Les données (généralement sont
privées)
 Dérivation et Héritage :
 Dérivation ou héritage simple :
Permet de réutiliser une classe de base, sans avoir besoin de tout réécrire, pour définir une
classe plus complexe.
Exemple : soit une classe point qui permet de manipuler des points dans le plan, sa
structure de données abstraite se compose de deux coordonnées cartésiennes.
Point : abscisse, ordonnée
Une classe cercle peut être considérée comme :
Cercle : point + rayon
On dit que l’on peut dériver la classe cercle de la classe point. La classe déri vée (dite aussi
subclass ou enfant) hérite des composantes de la classe de base (dite aussi superclass ou
parent)
 Héritage multiple :
Permet à une classe de dériver de plusieurs classes.

Pr. J. BOUMHIDI
9
Langage C++ 2EME ANNÉE IGA / 2011-2012

 Classes abstraites :
Ce sont des classes qui ne peuvent pas être directement instanciées. Elles ne peuvent être
utilisées que comme superclasse dans les dérivations.
 Polymorphisme :
Donne la possibilité à un objet de prendre différentes représentations :
a) Le type de l’objet peut varier au cours du programme en fonction d e son contenu
(même principe que le lien dynamique)
b) Une fonction peut prendre plusieurs formes selon le type de ses arguments réels et le
type de l’objet qui l’appelle.
c) combine les deux : un objet, selon son type à un moment donné, choisit les méthodes
adaptées.

Pr. J. BOUMHIDI
10
Langage C++ 2EME ANNÉE IGA / 2011-2012

Chapitre 3

Notions de Classes, Constructeurs et destructeur s d’objets, Propriétés des


fonctions membres

Le concept de classe :

Une classe définie l’implantation d’une entité de données et des actions (fonctions) à réaliser
sur ces données. Définir une classe, c’est définir un nouveau type. Un type classe contient :
- des membres donnés, de différents types, comme une structure en C.
-des membres fonctions, que l’on appelle méthodes, qui opèrent sur les données déclarées
dans la classe.

Du fait que les données et les fonctions qui les traitent sont réunies dans une seule entité, il
devient possible de protéger les données, c’est à dire de les rendres inaccessibles en dehors
de la classe. C’est cette option qui est prise par défaut en C++ (par défaut, en C++, tous les
membres sont privés, fonction et données). On dit alors que les données sont encapsulées.
Pour qu’un utilisateur puisse se servir d’une classe, il faut que le concepteur de la classe ait
prévu de rendre certaines fonctions membres publiques

Déclaration d’une classe

Une classe est déclarée en dehors de toute fonction. Le plus souvent elle constitue un fichier
séparé.
Syntaxiquement, la déclaration d’une classe comporte :
-les déclarations des membres données.
- les déclarations des membres fonctions (par leurs prototypes).
Cela s’écrit en C++ de la façon suivante :
class nom-classe {
... déclaration des membres données
... déclaration des membres fonctions (prototypes)
};

Chaque déclaration peut être précédée d’un statut :

- private mot clé pour rendre la donnée ou la fonction inaccessible en dehors de la classe
- public mot clé pour la rendre accessible
Exemple :
On considère la classe point permettant de manipuler un point dans le plan et qui comporte
deux membres données réels privés nommés abs et ord et trois fonctions membres publiques
initialise, deplace et affiche :
//Déclaration de la classe point
class point {
// déclaration des membres privés
private :
float abs ;
float ord ;
//déclaration des membres publiques
public :

Pr. J. BOUMHIDI
11
Langage C++ 2EME ANNÉE IGA / 2011-2012

void initialise(float, float) ;


void deplace(float, float) ;
void affiche() ;
};

Les fonctions qui ont été déclarées publique, pourront être appelée à partir de n’importe
fonction du programme, y compris main. On dira que ces fonctions sont à la disposition des
utilisateurs de la classe. Vous ne pourrez accéder aux champs x et y que par l’in termédiaire de
des ces fonctions publiques (seules les fonctions qui sont déclarées à l’intérieure de la classe
qui peuvent utiliser les champs x et y). Les fonctions membres ont accès à tous les membres
publiques ou privés de la classe

Définition d’une classe


La définition d’une classe consiste à fournir les définitions des fonctions membre qui peuvent
être définies à l’intérieur de la classe, dans ce cas, elles sont considérées comme inline, même
si cet attribut ne figure pas. Comme elles peuvent aussi être définies en dehors de la classe :

 Définition de la fonction initialise à l’intérieure de la classe point :


class point{
private :
float abs ;
float ord ;
public :
void initialise(float x, float y){abs=x ; ord=y ;}
void deplace(float, float) ;
void affiche() ;
};
Dans ce cas la fonction est inliné, pour chaque appel de cette fonction, le compilateur
recopiera les différentes instructions de la fonction.

 Définition de la fonction initialise en dehors de la classe :


class point{
private :
float abs ;
float ord ;
public :
void initialise(float, float);
void deplace(float, float) ;
void affiche() ;
};
void point :: initialise(float x, float y){abs =x; ord=y ;}

Dans ce deuxième cas, les fonctions membres sont définies comme des fonctions ordinaires.
Il faut simplement indiquer par l’opérateur : : appelé opérateur de résolution de portée , à
quelle classe la fonction appartient
Syntaxe :
Type-retour nom-class : : nom-fonction(argument) {
... instructions
}

Pr. J. BOUMHIDI
12
Langage C++ 2EME ANNÉE IGA / 2011-2012

Cette seconde forme de définition d’une fonction membre est la plus souhaitable
Utilisation d’une classe et instanciation d’un objet:
Nous n’avons encore donné que la définition de la classe, nous allons maintenant déclarer
« l’objet » d’un type class. On parle alors d’instance ou d’objet plutôt que de variable. Un
objet a de type nom_classe est instancié quand il est déclaré dans une fonction externe par :
Nom_classe a ;
Exemple de déclaration de deux objets a et b de type point :
point a,b ;
On peut accéder à n’importe quel membre public (donnée ou fonction) d’une classe en
utilisant l’opérateur ". " selon la syntaxe suivante :
a.nom-fonction (arguments)
Exemple :
a.initialise(4, 1) ;
Affectation entre objets :
Il est possible d’affecter un objet à un au tre objet de même type. Il y a alors recopie des
valeurs des champs des données (qu’ils soient publics ou privés). Toutefois, si parmi ces
champs se trouvent des pointeurs, les emplacements pointés ne seront pas soumis à cette
recopie. Pour cela, il faut « surdéfinisser » l’opérateur d’affectation pour la classe concernée
(voir prochainement le chapitre consacré à la surdéfinition d’opérateurs).

Exercice :
Réaliser une classe point permettant de manipuler un point dans le plan par ses deux
coordonnées cartésiennes x et y et trois fonctions membres publiques initialise, deplace (pour
translater le point) et affiche (pour afficher les nouvelles coordonnées)
Ecrire par ailleurs, un petit programme (main) déclarant un point, l’initialisant, le déplaçant
puis affichant ses coordonnées.

Application à un objet alloué dynamiquement :


Une instance (objet) d’une classe T allouée dynamiquement est déclarée par un pointeur sur le
type T (T *a ;). on récupère dans ce pointeur (a ) l’adresse de la zone allouée par l’o pérateur
new appliqué au type T (a=new T ; ). pour appliquer une fonction à l’objet pointé par le
pointeur a de type T, on utilise la notation :

a-> nom-fonction(arguments) ;
Qui est équivalent à :

(*a).nom-fonction(arguments) ;

Nous pouvons réécrire l’exemple précédent, en allouant dynamiquement le point a. la


déclaration de la classe point et les définitions de fonctions ne changent pas. Nous les avons
regroupées dans un fichier allocpoint.h La fonction utilisatrice peut s’écrire :

#include "allocpoint.h"
main ( ) {

Pr. J. BOUMHIDI
13
Langage C++ 2EME ANNÉE IGA / 2011-2012

point *a ;
float x, y ;
float dx,dy ;
cout << " valeur initiales pour les coordonnées de a ? \n" ;
cin >> x >> y ;
a=new point ;
a->initialise (x, y) ;
a->affiche( ) ;
cout << "déplacements pour les coordonnées de a ? \n";
cin >> dx >> dy ;
a->deplace (dx, dy) ;
a->affiche( ); }

Constructeurs et destructeurs d’objets

Constructeurs

Quand un objet est instancié à partir d’une classe, une fonction appelée constructeur associé à
cette classe est automatiquement exécutée. Elle a pour rôle d’initialiser correctement l’objet.
Ceci augmente la fiabilité des applications, car l’utilisateur n’a plus à se préoccuper de
l’initialisation correcte des objets.

La fonction constructeur peut être fournie par le concepteur de la classe explicitement en tant
que fonction membre. Dans ce cas elle peut avoir pour nom le nom de la classe. Elle ne
comporte pas de type retour (ce n’est pas une fonction qui renvoie un argument de type void).
Elle peut recevoir zéro, un ou plusieurs arguments.

Si le concepteur de la classe n’a pas prévu de fonction constructeur, un constructeur par


défaut est généré. C’était le cas dans les exemples ci-dessus. En effet, la fonction initialise,
qui semble jouer le même rôle qu’un constructeur, n’est pas reconnue comme telle, et n’est
pas appelée automatiquement à chaque instanciation d’un objet de la classe point.

Il est vivement conseillé à tout concepteur de classe de prévoir une fonction constructeur
explicite pour chaque classe.
Un constructeur de la classe point pourrait se déclarer par :

public:
point(float,float) ;
Et être défini en dehors de la classe par :
point : :point(float x, float y){
abs=x; ord=y; }

On peut aussi définir les constructeurs dans la classe. Dans ce cas, elles sont inline par défaut.

Un constructeur de la classe point défini dans la classe s’écrirait :


public :
point (float x, float y) { abs=x ; ord=y ; }

Pr. J. BOUMHIDI
14
Langage C++ 2EME ANNÉE IGA / 2011-2012

Lorsque l’on déclare un objet, si le constructeur est une fonction qui reçoit des arguments
effectifs, les arguments effectifs doivent être fournis dans la déclaration. Pour un objet de type
point, la déclaration prend la forme :
point a(x,y) ;
Où x et y sont deux float définis au préalable. La déclaration point a ; n’est pas acceptée.
Si l’on introduit dans la classe un constructeur de point, la fonction initialise n’est pas utile, et
la programmation est plus simple pour l’utilisateur. C’est aussi plus fiable, car l’appel au
constructeur est automatique.

Le constructeur est toujours appelé automatiquement après l’allocation de l’espace mémoire


destiné à l’objet. Il n’est pas permis de l’appeler e xplicitement.

Constructeur avec arguments par défaut

On utilise souvent dans les constructeurs des arguments avec définition de valeur par défaut.
Ceci permet de rendre valide la déclaration d’un objet sans argument : dans ce cas, les
valeurs par défauts sont utilisées. Le constructeur de la classe point peut être défini dans la
classe par :

Point ( float x=0.f , float y=0. f ) {abs=x ; ord=y ; }

Dans ce cas on peut déclarer dans la fonction utilisatrice :

point a(5.5f , -2. f) ; un point de coordonnées 5.5 et -2. est créé


point b; un point de coordonnées 0. et 0. est créé
point c(3.f) ; un point de coordonnées 3. et 0. est créé

Rappel : la lettre f à la fin d’une constante spécifie le type float.

Destructeurs
De même, lorsqu’un objet va cesser d’exister en mémoire, par exemple parce que l’on sort du
bloc où il a été défini, ou par un opérateur delete, il est vivement souhaitable de libérer
l’espace qu’il occupe, et éventuellement, effectuer certaines taches de maintenance. Une
fonction destructeur associée à la classe est automatiquement appelé chaque fois qu’une
instance d’une classe cesse d’exister. Ceci augmente la fiabilité, car l’utilisateur n’a plus à se
procurer de ces taches.
Le destructeur peut être prévu explicitement par le concept de la classe. Dans ce cas il doit y
avoir pour nom le nom de classe précédé du caractère ~. Il n’a pas d’argument de retour, et ne
reçoit pas d’arguments.
Un destructeur de la classe point pourrait se déclarer par :
public
~point ( ) ;

Et se défini en dehors de la classe par :


point : : ~point( ) { ... }

Exercice :
Réaliser une classe point permettant de manipuler un point dans le plan par ses deux
coordonnées cartésiennes x et y et trois fonctions membres publiques :
Un constructeur recevant en arguments les coordonnées (float) du point,

Pr. J. BOUMHIDI
15
Langage C++ 2EME ANNÉE IGA / 2011-2012

Une fonction membre deplace (pour translater le point) et affiche (pour afficher les nouveaux
coordonnées)
Ecrire par ailleurs, un petit programme (main) déclarant un point, l’initialisant, affichant ses
coordonnées, le déplaçant puis affichant à nouveau ses coordonnées.

Propriétés des fonctions membres


Surdéfinition des fonctions membres
Une fonction membre peut être surdéfinie comme toute fonction ordinaire en C++.
Si nous voulons par exemple dans notre classe point, traiter soit des int, soit des float, nous
pouvons surdéfinir la fonction constructeur et la fonction deplace. Comme nous avons déclaré
les données en type float nous forcerons la conversion par l’opérateur cast pour traiter des
entiers.
Fonctions membre en ligne :
Il s’agit également de la généralisation aux fonctions membres d’une possibilité offerte par
C++ pour les fonctions ordinaires, avec une petite nuance concernant sa mise en œuvre ; pour
rendre en ligne une fonction membre, on peut :
Soit fournir directement la définition de la fonction dans la déclaration même de la classe,
dans ce cas, le qualitatif inline n’a pas à être utilisé.
Soit fournir une définition en dehors de la déclaration de la classe, dans ce cas, le qualitatif
inline doit apparaître, à la fois devant la déclaration et devant l’en -tête.

Transmission des objets en arguments d’une f onction membre :


Une fonction membre reçoit toujours implicitement en argument l’adresse de l’objet de sa
classe qui l’a appelée. Ainsi, pour la classe point déjà réalisée, avec un objet a de type point et
une fonction membre affiche()de cette classe point, lorsq u’on exécute l’instruction
a.affiche() ;
L’adresse de a est transmise en argument à la fonction affiche. Dans la fonction, le pointeur
sur l’objet qui l’a appelée est désigné par le mot clé this
Mais en outre, il est possible de lui transmettre explicitement un (ou plusieurs) arguments de
type de sa classe, ou même de type d’une autre classe. Dans le premier cas, la fonction
membre aura accès aux membres privés de l’argument en question. En revanche dans le
second cas, la fonction membre n’aura accès qu’aux membres publics de l’argument.
Un tel argument peut être transmis classiquement par valeur, par adresse ou par référence.
Avec la transmission par valeur, il y a recopie des valeurs des membres donnée dans un
emplacement local à la fonction appelée. Des problèmes peuvent surgir dès lors que l’objet
transmis en argument contient des pointeurs sur des parties dynamiques ils seront réglés par
l’emploi d’un « constructeur par recopie » (voir les paragraphes suivants).
Exercice1 :
Dans la classe point déjà définie, ajouter une fonction membre qui détermine si deux points
coïncident et renvoie true si c’est le cas et false si non.
Exercice2 :
Soit une classe vect3d définit comme suit :
class vect3d
{float x,y,z ;
public :
vect3d(float c1=0.0, float c2=0.0, float c3=0.0)
{x=c1;y=c2;z=c3;}
…..
};

Pr. J. BOUMHIDI
16
Langage C++ 2EME ANNÉE IGA / 2011-2012

Introduire une fonction membre à un seule argument nommée coincide permettant de savoir
si deux vecteurs de type vect3d ont les mêmes composantes:
a) en utilisant une transmission par valeur
b) en utilisant une transmission par adresse
c) en utilisant une transmission par référence

Fonctions membres statiques


Lorsqu’une fonction membre a une action indépendante d’un objet quelconque de sa classe,
on peut la déclarer avec l’attribut static (c'est-à-dire sont alloués de façon permanente en
mémoire comme les variables globales). Dans ce cas, une telle fonction peut être appelée sans
mentionner d’objet particulier en préfixant simplement son nom du nom de la classe
concernée, suivi de l’opérateur de résolution de portée ( ::). Selon la syntaxe suivante :
Nom_classe ::nom_fonction(arguments) ;
Nous pouvons utiliser par exemple cette notion qui compte le nombre d’objets du type de la
classe qui sont crées et détruits lors de l’exécution d’un programme.

Types d’allocation mémoire pour les objets :


Comme en C, on distingue plusieurs types d’allocation mémoire pour les objets:
1-les objets automatiques :
Ce sont ceux crées par une déclaration dans une fonction ou un bloc. Ils cessent d’exister
lorsque l’on sort de la fonction où ils sont déclarés.

2-les objets dynamiques :


Ils sont crées par l’opérateur new. Ils cessent d’exister lorsqu’ils sont détruits par l’opérateur
delete. (les objets dynamiques n’ont pas de durée de vie définie à priori. Ils sont détruits à la
demande en utilisant l’opérateur delete).

3-les objets statiques :


Ils sont crées par une déclaration située en dehors de toute fonction ou par une déclaration
précédée de l’attribut static (dans une fonction ou dans un bloc).

4-les objets temporaires :


Sont crées implicitement par le compilateur dans certaines circonstances. L’appel explicite, au
sein d’une expression, du constructeur d’un objet provoque la création d’un objet temporaire
(on n’a pas accès à son adresse) qui pourra être automatiquement détruit dès qu’il ne ser a plus
utile. Par exemple, si une classe point possède le constructeur point(float,float) et si a est de
type point, nous pouvons écrire :
a=point (1.5, 2.25) ;
( ne confondez pas une telle affectation avec une initialisation d’un objet lors de sa déclara tion)
Cette instruction provoque la création d’un objet temporaire de type point (avec appel du
constructeur concerné, suivi de l’affectation de cette objet à a).
Remarque :
Dans tous les cas (objets statiques, automatiques ou dynamiques), s’il y a appel d u
constructeur, celui- ci a lieu après l’allocation de l’emplacement mémoire destiné à l’objet. De
même, s’il existe un destructeur, ce dernier est appelé avant la libération de l’espace mémoire
associé à l’objet.

Constructeur de recopie :
Le but de ce type de constructeur est d’initialiser un objet lors de son instanciation à partir
d’un autre objet. Toute classe dispose d’un constructeur de copie par défaut généré par le

Pr. J. BOUMHIDI
17
Langage C++ 2EME ANNÉE IGA / 2011-2012

compilateur, dont le seule but est de recopier les champs de l’objet à recopier un à un dans les
champs de l’objet à instancier. Toutefois, ce constructeur par défaut ne suffira pas toujours, et
le programmeur devra parfois en fournir un explicitement.
Ce sera notamment le cas lorsque certains données des objets auront été allouées. Une copie
brutale des champs d’un objet dans un autre ne ferait que recopier les pointeurs, pas les
données pointées, ainsi la modification de ces données pour un objet entraînerait la
modification pour les données de l’autre objet, ce que ne serait sans doute pas l’effet désiré.
Le constructeur de recopie est destiné alors à recopier explicitement un objet d’une classe
passé en argument par valeur à une fonction.
La définition des constructeurs de copie se fait comme celle des constructeurs normaux. Le
nom doit être celui de la classe, et il ne doit y avoir aucun type. Dans la liste des paramètres
cependant, il devra toujours y avoir une référence sur l’objet à copier.
Syntaxe de déclaration : nom_classe (const nom_classe&) ;
Si l’attribut const est omis, il n’ya pas d’erreur, mais il est préférable de l’utiliser, puisque le
constructeur de recopie ne doit pas modifier l’objet qui lui est passé en argument par adresse.

Pr. J. BOUMHIDI
18
Langage C++ 2EME ANNÉE IGA / 2011-2012

Chapitre 4

Fonctions Amies et Surdéfinition des Opérateurs

I) Fonctions Amies
Déclaration d’amitié
Il peut arriver qu’une fonction n’appartient pas à une classe ait besoin d’accéder à des
données de cette classe. Une façon de procéder serait de déclarer ces données publiques, mais
cela est contradictoire avec la notion de classe, dans la mesur e où l’un des intérêts de
l’utilisation des classes est justement de protéger les données.
La solution adoptée par C++ consiste a permettre au concepteur d’une classe de déclarer que
certaines fonctions extérieures à la classe sont des amies de cette classe et sont donc
autorisées à ce titre à accéder au données privés de la classe. Il y a plusieurs possibilités, qui
sont détaillées ci-dessous.

1) Fonction externe amie d’une classe


La déclaration d’amitié est introduite dans la classe par :
friend type nom-fonction(arguments) ;
La fonction est une fonction externe standard.
Exemple :
Soit une classe point qui comporte comme membres donnés les coordonnées du point dans le
plan, et comme membres fonctions le constructeur et une fonction affiche. Nous ne
fournissons pas dans la classe de destructeur, car il n’ y a pas d’allocations dynamiques, et le
destructeur par défaut peut convenir.
Ajouter une fonction externe amie de la classe point, qui reçoit deux arguments de type point
et renvoie un boolean qui vaut true si les deux points coïncident et false si non.
Ecrire un programme principal d’essai

Remarque: La fonction main ne doit pas déclarer la fonction externe coïncide car celle-ci est
déclarée dans la classe point.

2) Fonction membre d’une classe amie d’une autre classe


Soit une classe dont une fonction membre a besoin d’accéder aux membres privés d’une
autre classe. La classe A qui veut autoriser une fonction d’une classe B à accéder à ses
membres privés doit comporter une déclaration d’ amitié pour cette fonction, en précisant à
quelle classe appartient la fonction amie, on utilise pour cela l’opérateur de résolution de
portée : : selon la syntaxe suivante :
friend type B ::nom-fonction(arguments) ;
La classe qui contient la fonction amie (classe B) doit être compilée avant la classe qui
contient la déclaration d’amitié (classe A). (Le compilateur doit alors connaître la déclaration
de B mais pas nécessairement sa définition)
Par ailleurs, la classe qui comporte une fonction membre (classe B) dont un argument au
moins est du type d’une autre classe (classe A) a besoin d’accéder aux membres privés de
cette autre classe. Elle doit recevoir à la compilation une information supplémentaire
concernant cette classe (classe A). Cette information lui est passée, avant la déclaration de la
classe elle même par la déclaration
class A ;
Soit la classe A qui autorise une fonction fonc de la classe B à accéder à ses membres privés.

Pr. J. BOUMHIDI
19
Langage C++ 2EME ANNÉE IGA / 2011-2012

Le schéma est le suivant :


Class A ;
Class B {
Private :
... membre privés
Public :
... membres publics
Type fonc(arguments) ;
};
Class A {
Private :
... membres privés
Public : membre publics
friend type B : :fonc(arguments) ; } ;
Type B ::fonc(arguments) { . . . }
Exemple :
Refaire la classe point, et ajouter à la classe point une fonction qui définit une translation du
vecteur u. Le vecteur u est défini par une classe vecteur qui a pour membres données ses
composantes en x et en y.
La classe point a pour :
Membres données : deux réels qui représentent la projection sur l’axe des abscisses et des
ordonnées : projx et projy
Et les fonctions membres :
-le constructeur, une fonction affiche et une fonction translate qui renvoie un point et qui
reçoit en argument un vecteur. Cette fonction doit donc faire l’objet d’une déclaration
d’amitié dans la classe vecteur.
La classe vecteur a pour fonctions membres :
-le constructeur, une fonction affiche et elle comporte une déclaration d’amitié pour la
fonction translate de la classe point
Réaliser ces classes avec un programme principal d’essai
3) Fonction amie de plusieurs classes
Cela ne pose aucun problème : une fonction peut faire l’objet de déclaration d’Amitié dans
plusieurs classes, que ce soit une fonction externe ou une fonction membre d’une classe.
4) Classe B amie d’une autre classe A
Toutes les fonctions membres de B sont amies de la classe A. Dans ce cas, au lieu d’utiliser
autant de déclarations que de fonctions, on peut utiliser une dé claration globale d’amitié
comme suit : (dans la déclaration de A)
friend class B ;
et pour compiler la déclaration de A, on précise simplement que B est une classe par :
class B ;
Exercice :
A la classe point de l’exemple précédent, ajouter une fonction multiplie qui remplace et
retourne les coordonnées d’un point par leurs valeurs après les avoir multipliés par les valeurs
des composantes d’un vecteur reçue en argument respectivement en x et en y
Au lieu de déclarer l’amitié de chaque fonction dans la classe vecteur, déclarer toute la classe
point comme amie de la classe vecteur
Ecrire un programme principal d’essai.

Pr. J. BOUMHIDI
20
Langage C++ 2EME ANNÉE IGA / 2011-2012

II) Surdéfinition d’opérateurs


En C++, on peut attribuer un nouveau rôle à un opérateur standard quand celui- ci s’applique à
un objet non standard. Pour cela on associe à cet opérateur une fonction qui décrit l’action
spécifique à réaliser lorsque l’opérateur s’applique à cet objet on dit qu’on a surdéfini cet
opérateur.
La fonction associé à l’opérateur peut être soit une foncti on indépendante, amie de la classe
qui définie l’objet auquel s’applique l’opérateur, soit une fonction membre de la classe.
Le nom de la fonction est le symbole standard, précédé du mot operator.

Exemple : Redéfinition de l’opérateur + : la fonction est déclaré par :


type operator + (arguments) ;

 Surdéfinition par une fonction amie indépendante


Soit un opérateur existant noté op, si op est un opérateur binaire la notation :
a op b est équivalent operator op(a,b)
Exemple: a+b est equivalent operator +(a,b)
 Surdéfinition par une fonction membre
La même notation a op b est équivalent a.operator op(b)
Exemple: a+b est equivalent a.operator +(b)

Exercice:
Utiliser l’opérateur + sur la classe point pour définir un point (comme somme de deux point s)
dont l’abscisse sera la somme des abscisses et l’ordonnée, la somme des ordonnées.
Réaliser la surdéfinition de + de deux façons :
a) surdéfinition par une fonction amie
b) surdéfinition par une fonction membre
N.B) la classe point a pour fonction membres le constructeur et une fonction affiche.

Règles concernant la surdéfinition des opérateurs


-seuls les opérateurs standards peuvent être surdéfinis. On ne peut définir de nouveaux
symboles. Il n’y a jamais d’ambiguïtés : selon le type des opérandes, c’est l’opérateur
standard ou l’opérateur surdéfini qui s’applique. Tous les opérateurs peuvent être surdéfinis
sauf l’opérateur ∙

- Un opérateur unaire ne peut être surdéfini qu’en opérateur unaire. Un opérateur binaire
ne peut être surdéfini qu’en opérateur binaire. Dans le cas d’opérateurs binaires, les 2
arguments ne sont pas forcément du même type.
- les règles de priorité et d’associativité sont les mêmes que pour les opérateurs
standards.
- les opérateurs peuvent être surdéfinis soit par des fonctions externes (dans ce cas, il
faut une déclaration d’amitié dans la classe), soit par des fonctions membres.
- Cas de surdéfinition par une fonction externe : si l’opérateur est unaire, la fonction
reçoit un argument de type auquel elle s’applique. Si l’opérateur est bi naire, elle reçoit
2 arguments explicites. Les opérateurs new, delete, [ ], ( ), -> ne peuvent pas être
surdéfinis par des fonctions externes, mais uniquement par des fonctions membres.

- Cas de surdéfinition par une fonction membre : si l’opérateur est unaire , la fonction
ne reçoit aucun un argument explicite. Elle reçoit implicitement l’objet sur lequel

Pr. J. BOUMHIDI
21
Langage C++ 2EME ANNÉE IGA / 2011-2012

porte l’opérateur. Si l’opérateur est binaire, elle reçoit l’opérande de droite en


argument explicite et l’opérande de gauche implicitement.

- On peut transmettre les arguments par référence. Pour un argument de retour il faut
s’assurer dans ce cas qu’il y’a une allocation fixe en mémoire.
Les opérateurs surdéfinis ne sont pas en général commutatifs.

Opérateurs de préincrémentation et postincrémentation


Pour ces opérateurs, il faut pouvoir distinguer au niveau de la déclaration s’il s’agit de
la préincrémentation ou de la postincrémentation. Pour la préincrémentatiopn le C++ a
choisi de conserver le nom operator ++, et la déclaration est de la forme :
type operateur ++ ( ) ;

pour la postincrémentation, il conserve le nom opertor ++, mais il introduit un


argument fictif de type int, qui ne sert à rien d’autre que de permettre au compilateur
de distinguer formellement la postincrémentation et la préincrémentation. La
déclaration est de la forme :
type operator ++ (int) ;
de plus, dans la programmation de la fonction, il faut bien veiller à sauvegarder l’objet
sur lequel porte l’opérateur avant l’action dans le cas de la postincrémentation.
Exemple :
Dans la classe point, définir un opérateur de préincrémentation qui augmente de 1 les
valeurs des coordonnées d’un point, et renvoie les nouvelles coordonnées, et un
opérateur de postincrémentation, qui augmente de 1 les coordonnées, mais renvoie les
coordonnées initiales du point auquel il s’applique.

Opérateur d’indexation [ ]
Il n’est pas possible d’accéder directement depuis une fonction utilisatrice à un élément d’un
tableau déclaré dans une classe, car les éléments du tableau sont privés à la classe et la
fonction utilisatrice ne peut pas accéder directement.
Il faudra donc penser par une fonction membre de la classe, qui surdéfinira l’opérateur
d’indexation [ ].
La fonction de surdéfinition doit recevoir en argument le rang de l’éléme nt. Elle renvoie sa
valeur. Si l’on veut pouvoir faire figurer l’élément à gauche d’un signe=, il faut utiliser la
transmission par référence pour l’argument de retour.
On ne peut pas ici avoir recours à une fonction externe avec déclaration d’amitié.
Considérons par exemple une classe vecteur d’entiers dont les membres données sont le
nombre d’éléments du vecteur et un pointeur d’entiers. On introduit dans cette classe une
fonction de surdéfinition de [ ]. La classe peut être déclarée de façon suivante :
class vecteur {
int * a ;
int nmax ;
public :
int & operator [ ] (int) ; // fonction de surdéfinition de l’indexation
... };
On peut définir la fonction d’indexation par :

int & vecteur : :operator [ ] (int i) {


return *(a+i) ; }

Pr. J. BOUMHIDI
22
Langage C++ 2EME ANNÉE IGA / 2011-2012

Exercice :

Donner un programme complet pour la classe vecteur ci- dessus, en la dotant d’un
constructeur, d’un destructeur, d’une fonction de remplissage par nombre générés au hasard,
d’une fonction d’affichage et de la fonction de surdéfinition de [ ]. Ici, on n’a pas besoin d’un
constructeur de recopie puisqu’il n’y a pas d’argument de type vecteur transmis par valeur.
La fonction utilisatrice double les éléments de rang impair.
Le programme complet peut s’écrire :

Surdéfinition de l’affectation
L’opération d’affectation peut être surdéfinie. Il est même indispensable de la surdéfinir
lorsqu’elle concerne des objets alloués dynamiquement. En effet, l’opération d’affectation
standard consiste simplement à recopier les membres d onnés de l’objet figurant à droite du
signe égal dans les membres donnés de la variable réceptrice. Elle ne recopie pas les contenus
des zones mémoires allouées dynamiquement.

Considérons par exemple la classe vecteur déclarée par :

class vecteur {
int * a ;
int nmax ;
public :
vecteur (int k) {
a=new int[nmax=k] ;}
...
} ;

Si l’on déclare dans une fonction utilisatrice

vecteur x(n) ;
vecteur y(m) ;

Les membres donnés de x ont pour valeur :


-l’entier n qui définit le nombre d’élément du vecteur de x
-l’adresse de la zone mémoire alloué par le constructeur pour les n éléments de x

Les membres données de y ont pour valeur :


-l’entier m qui définit le nombre d’éléments du v ecteur de y
-l’adresse de la zone mémoire alloué par le constructeur pour les m éléments de y

Si l’on a ensuite une opération d’affectation standard y=x ;


La valeur du membre donnée nmax de y est changée en n, et celle du membre donnée a est
changée en l’adresse du tableau alloué dynamiquement pour x. ceci a pour effet de conserver
en mémoire l’emplacement crée dynamiquement par le constructeur lors de la déclaration
vecteur y(m) , et qui n’est plus référencé par y. cet emplacement risque de n’êtr e jamais libéré.

Si d’autre part cet emplacement venait à être référencé par ailleurs dans le cours du
programme, il désignerait les anciens valeurs du tableau y, ce qui ne serait pas forcément
convenable.

Pr. J. BOUMHIDI
23
Langage C++ 2EME ANNÉE IGA / 2011-2012

Pour éviter ce genre de situation, il faut surdéf inir l’opération d’affectation en prenant soin :
-de libérer l’espace alloué par le constructeur pour le vecteur y
- de créer une nouvelle zone mémoire de la taille du vecteur x, dans laquelle on va recopier
les valeurs des éléments de x.
-d’affecter aux membres données du vecteur y les valeurs qu’ils doivent avoir, c’est a dire
x.nmax pour le nombre d’éléments et l’adresse de la nouvelle zone crée pour y pour le
membre y.a
- de ne rien faire s’il s’agit d’une affectation de l’objet à lui – même.
La fonction de surdéfinition de l’affectation d’un vecteur à un autre s’appellera operator =
Elle recevra en argument explicite le vecteur à droite du signe =. Ici, nous avons choisi de
passer cet argument ar adresse, pour des raisons de performances, en précisant l’attribut const
puisqu’il ne doit pas être modifié. La fonction reçoit implicitement en argument la variable
réceptrice. Nous avons choisi de renvoyer un argument de type vecteur par valeur. Il faudra
donc un constructeur de recopie.
On pourrait la programmer de la façon suivante pour la classe vecteur ci-dessus :

Vecteur& vecteur : : operator = (const vecteur &u) {


if (this !=&u) {
delete [ ] a ;
nmax=u.nmax;
a=new int[nmax];
for (int i=0; i<nmax; i++)
*(a+i) = *(u.a+i) ; }
else cout << " on ne fait rien \n ";
return *this; }
Ici this est l’adresse de l’ opérande de gauche (passé implicitement en argument). Si cette
adresse est la même que celle de l’opérande de droite (argument formel u passé par adresse).
On ne fait rien.

Remarque :
Il faut aussi prévoir un constructeur de recopie, car la fonction operator = renvoie un vecteur
par valeur. Elle utilise donc une variable locale pour l’argument de retour. Si l’on omet cette
fonction, il y a des problèmes pour le renvoi de la valeur du vecteur après l’opération
d’affectation.

Exercice :
Donner un programme complet pour la classe vecteur, avec pour fonctions membres
-le constructeur
-Le constructeur de recopie
-le destructeur
-une fonction de remplissage par génération de nombres au hasard
-une fonction d’affichage
-la fonction operator =
Afficher les adresses des différents objets crées et détruits, et les passages dans les différents
fonctions, pour bien montrer ce qui se passe.

Pr. J. BOUMHIDI
24
Langage C++ 2EME ANNÉE IGA / 2011-2012

Chapitre 5
DERIVATION SIMPLE

1) Création d’une classe dérivée


1. Création d’une classe dérivée en a joutons un membre donnée à une class de base

Le concept d’héritage constitue l’un des fondements de la programmation orientée objet.


Si l’on veut créer une classe en ajoutons un membre donnée à une classe existante, ce
n’est pas la peine de créer une nouvelle classe complètement indépendante de la classe
déjà définie. On peut créer une nouvelle classe en se basant sur une classe existante. On
dit que l’on crée une classe dérivée, qui hérite d’une autre classe. Soit une classe de base
existante A, la syntaxe de définition d’une nouvelle classe B dérive de A :
class B :public A
{// définition des membres supplémentaires (données ou fonctions)

};
Exemple : à partir de la classe point que nous avons déjà utilisée (classe de base), nous
voulons créer une autre classe (classe dérivée) en ajoutant un symbole identifiant le point,
qui sera une suite de un ou plusieurs caractères. Nous appelons la classe dérivée pointcar.
La déclaration des membres donnés de la classe dérivée s’écrit :
class pointcar : public point {
char * sy ;
Public :
// … fonctions membres propres à la classe dérivée
};
La classe dérivée pointcar a accès aux membres publics de la classe de base point, mais
pas à ses membres privés.

2. Constructeur d’un objet d’une classe dérivée


La classe dérivée doit comporter un constructeur qui initialise les membres données
n’existant pas dans la classe de base. Ce constructeur ne peut pas initialiser les membres
donnés de la classe de base, puisque en général, ceux-ci sont privés, et les fonctions de la
classe dérivée n’y ont pas accès. Il faut donc, lorsque l’on passe dans le constructeur de
la classe dérivée, avoir déjà crée un objet du type de base.
Le C++ génère automatiquement l’appel au constructeur de la classe de base lorsqu’un
objet de la classe dérivée est déclaré dans une fonction utilisatrice.
On peut donc écrire :
# include < iostream.h>
class point {
float abs ; float ord ;
public :
point ( float x=0., float y=0.){
cout <<" constructeur de point" << endl;
abs=x ; ord=y ;}
void affiche ( ) {
cout <<" coordonnees : " <<abs<< " " <<ord << endl ; }} ;

Pr. J. BOUMHIDI
25
Langage C++ 2EME ANNÉE IGA / 2011-2012

//Classe derive de la classe point


class pointcar : public point {
char * sy ;
public:
pointcar ( ) {
cout <<" constructeur de pointcar "<< endl ;
sy= " " ;}} ;
// fonction utilisatrice
main ( ) {
pointcar p ;}

Le résultat est le suivant :


constructeur de point
constructeur de pointcar

Mais, s i nous voulons passer des arguments au constructeur de point, ce n’est pas possible
sous cette forme, la déclaration :
pointcar (1.5, 0.9) ; conduit à une erreur de compilation.
Pour résoudre ce problème, le C++ permet au constructeur de la classe dérivée de passer des
arguments au constructeur de la classe de base : il suffit d’ajouter dans la définition du
constructeur de la classe dérivée, l’appel explicite au constructeur de la classe de base, avec
les arguments à lui passer.

Exemple : pour la classe de base point utilisée dans l’exemple ci -dessus, le constructeur de la
classe dérivée pointcar se déclare par :
pointcar ( float , float , char *);
et se définit par:
pointcar: : pointcar ( float x=0., float y=0., char* c="*" ):
point(x,y){
sy=c;}

Ici, nous avons choisi d’initialiser le symbole avec le caractère’*’ par défaut.

3. Appel d’une fonction de base depuis une fonction membre


Une fonction de la classe dérivée peut appeler n’importe quelle fonction pu blique de la classe
de base. Nous allons utiliser cette possibilité pour afficher les valeurs des membres données
de la nouvelle classe pointcar. Il suffit de prévoir dans la classe dérivée pointcar une fonction
d’affichage qui appelle la fonction affiche de la classe point (en utilisant l’opérateur de
résolution de portée :: ), puis affiche le membre donnée sy déclaré dans la classe dérivée.
La classe pointcar et sa fonction d’affichage peuvent être déclarées de la façon suivante :

class pointcar : public point {


char * sy ;
public :
pointcar ( float , float , char* c );
void affichec ( ) ;} ;
pointcar: : pointcar ( float x=0., float y=0., char* c=" *" ):
point(x,y){

Pr. J. BOUMHIDI
26
Langage C++ 2EME ANNÉE IGA / 2011-2012

cout <<" passage dans constructeur de pointcar"<<endl;


sy=new char[strlen(c)+1]
strcpy(syc, c) ;}
void pointcar ::affichec ( ) {
point :: affiche ( ) ;
cout <<" identificateur : "<< sy << endl ;}

La fonction utilisatrice s’écrit, en supposant que la classe de base est définie dans le fichier
point-base.h et la classe dérivée dans carpoint.h

# include " point-base.h"


# include " carpoint.h"
// fonction utilisatrice
main ( ) {pointcar q ;q.affiche ( ) ;
pointcar p(1.5, 2, "ABC" ) ;p. affichec ( ) ;}
Le résultat est le suivant :
passage dans constructeur de point
passage dans constructeur de pointcar
coordonnees : 0 0
identificateur : *
passage dans constructeur de point
passage dans constructeur de pointcar
coordonnees : 1.5 2
identificateur : A

Ici, le point q est crée avec les valeurs des arguments par défaut, et le point p avec les valeurs
passées en argument au constructeur de pointcar.
Remarque :
Le constructeur de pointcar peut aussi être défini inline dans la classe pointcar par :
pointcar ( float x=0., float y=0., char* c=" *" ): point(x,y){
cout <<" passage dans constructeur de pointcar \n " ;
sy=new char[strlen(c)+1]
strcpy(syc, c) ;}
4. Redéfinition d’une fonction de la classe de base

Une classe dérivée peut avoir une fonction membre de même nom que la classe de base et
redéfinir celle-ci. La fonction de la classe dérivée fera appel à la fonction de la classe de base
en utilisant l’opérateur de résolution de portée :: et pourra compléter l’action de celle-ci.

Ainsi, dans l’exemple précédent, au lieu de créer une fonction affichec membre de la classe
dérivée, il est préférable de redéfinir avec le même nom la fonction affiche de la classe point.
De même, si nous avons dans la classe point une fonction move qui réalise un déplacement
d’un point, il est possible de modifier dans l a classe dérivée cette fonction de façon à donner
au point obtenu après le déplacement un nouvel identificateur, par exemple en lui concaténant
le caractère ’.
Exercice1 :

Pr. J. BOUMHIDI
27
Langage C++ 2EME ANNÉE IGA / 2011-2012

Ecrire un programme complet de la classe point et pointcar avec les fonctions membres
constructeur, affiche(), move(float,float). Ecrire un programme principal de test

2) Contrôle des accès à la classe de base

a. Statut protégé aux membres de la classe de base


En plus des statuts private et public, le C++ a introduit le statut protcted.
Exemple :
class point{
protected :
float abs,float ord ;
….};
Les membres qui ont ce statut sont accessibles, comme les membres private, aux fonctions
membres de la classe et aux fonctions amies, et de plus, aux éventuelles classes dérivées, mais
pas aux util isateurs des classes dérivées. L’utilisat ion de ce statut peut simplifier la tâche du
concepteur d’une classe dérivée.
Exercice 2 :
En utilisant ce statut, refaire l’exercice 1 précédent

b. Déclarations d’amitié
Si une classe de base a des fonctions amies, celles –ci ne sont pas amies des classes dérivées.
Les déclarations d’amitié ne s’héritent pas. Une classe dérivée doit déclarer toutes ses
fonctions amies.

3) Conversion
1 Affectation d’un objet dérivé à un objet de base
On peut affecter un objet d’une clase dérivée à un objet de base, mais pas l’inverse.
Exercice
Créer un point et un pointcar, et affecter le pointcar au point, ce qui est licite. Par contre
une affectation d’un point à un pointcar conduit à une erreur de compilation. Dans le
programme principal, créer un point pa(0,0) et un pointcar
p(1.5, 2, "X1"). Que devient pa après l’afféctation pa=p.

Le programme conduit au résultat suivant :


passage dans constructeur de point
coordonnees : 0 0
passage dans constructeur de point
coordonnees : 1.5 2
identificateur : X1
coordonnees : 1.5 2

Nous remarquons qu’il y a une fonction affiche dans la classe dérivée et dans la classe de
base. Il n’y a aucune ambiguïté . Selon le type de l’objet qui appelle la fonction, c’est celle de
la classe point ou celle de la classe pointcar qui est appelée. Il n’est pas nécessaire d’utiliser
l’opérateur de résolution de portée :: en appelant pa.point :: affiche ( ) ;

1. Affectation par pointeur


On peut affecter un pointeur sur un objet dérivé à un pointeur sur un objet de base.

Pr. J. BOUMHIDI
28
Langage C++ 2EME ANNÉE IGA / 2011-2012

Rien n’interdit non plus d’affecter un pointeur sur un objet de base à un pointeur sur un objet
dérivé en utilisant l’opérateur cast. Mais cela n’est pas très heureux car, si par la suite on
applique une fonction à l’objet pointé par ce pointeur, c’est la fonction définie dans la classe
dérivée qui s’applique, comme le montre le programme principal ci-dessous concernant une
classe pointcar qui dérive de la classe point :
// Fonction utilisatrice
main ( ) {
pointcar a( 1.5, 2., " A ") ;point x(-0.5,3.) ;pointcar * b=&a ;point * y=&x ;
y=b ;b->affiche ( ) ;y->affiche ( ) ;
point z(1.,2.7) ;point *u=&z ;
b= (pointcar *)u ;u->affiche ( ) ;b->affiche ( ) ;}

le résultat obtenu est le suivant :

passage dans constructeur de point


passage dans constructeur de pointcar
passage dans constructeur de point
coordonnees : 1.5 2
identificateur : A
coordonnees : 1.5 2
passage dans constructeur de point
coordonnees : 1 2.7
coordonnees : 1 2.7
identificateur: ??

Ce résultat est tout à fait normal car b et y pointent sur le même objet a, mais les membres
données de y se réduisent à ceux de la classe de base. La fonction affiche appliquée à b est
celle de pointcar, et la fonction affiche appliquée à y est celle de point.
Ensuite, u et b pointent sur le même objet z, mais la fonction affiche appliquée à u est celle de
point. Tandis que la fonction affiche appliquée à b est celle de pointcar, mais le membre
donnée sy déclaré dans pointcar n’est pas défini. En fait, il aurait fallu dans ce cas appliquer la
fonction affiche de point en utilisant l’opérateur de résolution de portée. Pour cela, nous
aurions du écrire :
b-> point :: affiche ( ) ;
Ce comportement vient du fait que le choix des fonctions appliquées est au moment de la
compilation, d’après le type déclaré des objets. C’est ce que l’on appelle le typage statique
des objets
3. Appel d’une fonction de base par un objet dérivé
Une fonction de base peut être appelée par un objet d’une classe dér ivée. En effet, il y a
convers ion implicite de l’objet dérivé dans le type de base.

Exemple : dans la classe point, Surdéfinissez l’opérateur + par une fonction qui fait la somme
des abscisses et des ordonnées. Dans la classe pointcar, nous ne changeons rien. Dans la
fonction utilisatrice, nous appliquons l’opérateu r + à des pointcar. Donnez le programme
complet.
4. Constructeur de recopie
On a besoin d’un constructeur de recopie quand on initialise un objet par un autre objet et
quand on transmet un argument par valeur, que ce soit un argument d’entrée ou de retou r.

Pr. J. BOUMHIDI
29
Langage C++ 2EME ANNÉE IGA / 2011-2012

1 er cas utilisation du constructeur de recopie par défaut :


Si l’on ne définit pas de constructeur de recopie, ni dans la classe de base, ni dans la classe
dérivée, c’est le constructeur de recopie par défaut qui est appelé. Dans ce cas , la recopie se
fait membre à membre, le constructeur de recopie par défaut est d’abord appelé pour les
membres de la classe de base, puis pour les membres de la classe dérivée.
2éme cas : la classe dérivée n’a pas de constructeur de recopie :
Le constructeur de recopi e de la classe de base est d’abord appelé pour les membres de la
classe de base: ce sera le constructeur de recopie par défaut s’il n’a pas de constructeur de
recopie explicite, et le constructeur de recopie explicite s’il est défini. Ensuite, le construct eur
de recopie par défaut est appelé pour la classe dérivée.
3éme cas : classe dérivée a un constructeur de recopie :
Dans ce cas,le constructeur de recopie de la classe dérivée est utilisé, et il n’y a pas d’appel au
constructeur de recopie de la classe de base, même s’il y en a un défini explicitement .le
constructeur de recopie par défaut n’est pas non plus appelé pour les membres de base .Il faut
donc prévoir explicitement dans le constructeur de recopie de la classe dévirée la recopie de
tous les membres, y compris ceux de la classe de base( ce qui suppose que la classe dérivée y
a accès). Si non une autre solution est de forcer l’appel au constructeur de recopie de la classe
de base, comme on l’a fait pour le constructeur, et lui passer en argument la partie de l’objet
qui est du type de base .Cela est possible en C++.On passe en argument un objet qui est de
type dérivé, et il y a conversion implicite en un objet du type du base
Soit A la classe de base et B la classe dérivée. Le constructeur de recopie peut être défini par :
Class B{ . . .} ;
...
B: :B(B & a) : A(a) {...}
Exercice:
Ajoutez à la classe pointcar une fonction coïncide, qui détermine si 2 pointcar coincident. Elle
reçoit implicitement en argument l’un des point s ; le second point est transmis explicitement.
Elle renvoie un bool. Ajoutez à la classe point et à la classe poinctar un constructeur de
recopie. Donnez aux membres données de la classe point le statut protected .Ceci nous permet
de les manipuler directement dans la fonction coïncide. Ecrire un programme principal de test
qui crée et affiche un pointcar p(1.5, 2, "A1"), qui crée et affiche un pointcar q(0.7, 2, "A2"),
et permet de tester si p et q coïncident ou non.

5. Surdéfinition de l’opérateur d’affectation

L’opérateur d’affectation peut être celui qui est défini par défaut. Il peut aussi avoir été
surdéfini, soit dans la classe de base, soit dans la classe dérivée, soit dans les deux. Les choses
se passent un peu de la même façon que pour le constructeur de recopie.

1- l’affectation n’est pas surdéfinie dans la classe dérivée


Dans ce cas, les membres propres à la classe dérivée sont traités par l’affectation par défaut.
Les membres de la classe de base sont traités par l’opération d’affectation de la classe de
base : si l’opérateur d’affectation a été surdéfini dans la classe de base, c’est ce dernier qui est
utilisé ; s’il n’y a pas de surdéfinition, c’est l’opérateur par défaut qui est pris.

2- l’affectation est surdéfinie dans la classe dérivée

Pr. J. BOUMHIDI
30
Langage C++ 2EME ANNÉE IGA / 2011-2012

L’opé rateur surdéfini dans la classe dérivée est appelé, mais pas celui de la classe de base, et
ceci même s’il a été surdéfini dans la classe de base. C’est donc à l a fonction de surdéfinition
de l’affectation de la classe dérivée de prendre en charge l’affect ation des membres de la
classe de base.
Exercice :
Nous ajoutons à la classe point la surdéfinition de l’opérateur = nous donnons aux
coordonnées du point à gauche du signe = les valeurs des logarithmes en base 10 des
coordonnées du point à droite du signe =. Dans la classe pointcar, nous surdéfinissons
l’opérateur = en remplaçant le symbole du point à gauche du signe = par celui du point à droit
du signe = en le terminant par le caractère £.

Exemple 2 :
Dans la classe pointcar du programme ci-dessus, supprimons la fonction

pointcar operator = (pointcar &) ;

Le résultat est le suivant :

passage dans constructeur de point


passage dans constructeur de pointcar
passage dans constructeur de point
passage dans constructeur de pointcar
point cible : coordonnées : 1.5 2
symbole : A1

point source : coordonnées : 0.7 3


symbole : B1

opérateur d’affectation de point


après affectation : coordonnées : - 0.154902 0.477121
symbole : B1

Ici, on est bien passé par l’opérateur d’affectation de la casse point. Pour le membre sy propre
à la classe dérivée, c’est l’opérateur d’affectation par défaut qui a été utilisé.
Ce comportement n’est pas très satisfaisant. Ici, on ne peut pas utiliser le passage par
arguments à une fonction de la classe point. Il faudra donc prévoir dans la surdéfinition de
l’affectation de la classe dérivée l’affectation des membres définis dan la classe de base. Cela
ne pose pas trop de problèmes si les membres de la classe de base sont de statut protected.

Exemple 3 : dans l’exemple ci -dessus, si nous modifions la classe point en donnant le statut
protected aux membres données, il suffit de réécrire la fonction de surdéfinition de l’opérateur
= de la classe pointcar comme ci-dessous :

pointcar pointcar : : operator = (pointcar & a) {


Cout << " operateur affectation de point \ n ";
abs = log10 (a. abs ) ; ord=log10(a.ord) ;
sy= a.sy ;
int n ; n=strlen(sy) ;
*(sy+n-1) =' £ ' ;
return *this ;}

Pr. J. BOUMHIDI
31
Langage C++ 2EME ANNÉE IGA / 2011-2012

pour obtenir le résultat correct :

passage dans constructeur de point


passage dans constructeur de pointcol
passage dans constructeur de point
passage dans constructeur de pointcol
point cible : coordonnées : 1.5 2
symbole : A1

point source : coordonnées : 0.7 3


symbole : B1

operateur d’affectation de pointcar


après affectation : coordonnées : - 0.154902 0.477121
symbole : B£

Exemple 4 :
Une autre façon de procéder est de passer par des pointeurs : dans la fonction de surdéfinition
de la classe dérivée, on déclarera 2 pointeur s sur des objets de la classe de base, et l’on
utilisera le fait que l’on peut affecter un pointeur de la classe dérivée à un pointeur de la classe
de base. Ceci permet de provoquer l’appel de la fonction de surdéfinition de = de la classe de
base, sans manipuler directement les membres de la classe de base. Cette méthode marche si
les membres donnés de la classe de base ont le statut private. Donnez le programme complet
dans le cas de la classe point et de la classe pointcar.

Pr. J. BOUMHIDI
32
Langage C++ 2EME ANNÉE IGA / 2011-2012

Chapitre 6

Les fonctions virtuelles

Typage statique des objets


Les règles de compatibilité entre une classe de base et une classe dérivée permettent d’affecter
à un pointeur sur une classe de base la valeur d’un pointeur sur une classe dérivée. Toutefois,
par défaut, le type des objets pointés en C++, est déterminé à la compilation . C’est ce
qu’on appelle le typage statique.
Par exemple, avec :

class A class B: public A


{ ... { ...
public : public :
void fct(…); void fct(…);
… …
}; };

A *pta;
B *ptb;
Une affectation telle que pta=ptb est autorisée. Néanmoins quelque soit le contenu de pta
(autrement dit, quelque soit l’objet pointé par pta), pta ->fct(…) appelle toujours la fonction
fct de la classe A. (pta est toujours considéré comme un pointeur sur A).
Lorsqu’une fonction est appelée le type d’objet est alors considéré être celui qui est déclaré
dans la fonction utilisatrice, et non pas son type effectif .on dit que le type des objets pointés
est déterminé à la compilation.
Il serait intéressant de pouvoir déterminer le type d’un objet à l’exécution. Ceci permettrait
d’affecter à un objet pointé son type effectif et non pas son type déclaré. Ceci est possible en
C++ grâce au mécanisme des fonctions virtuelles. On parle alors du typage dynamique.

Typage dynamique et fonctions virtuelles


On souhaite que, lorsqu’une fonction d’une classe est appelée, la fonction s’applique à l’objet
effectif, et non pas à l’objet déterminé à la compilation. C’est ce que l’on appelle le
polymorphisme. Pour cela, il suffit de déclarer la fonction virtuelle dans la classe de base.
Par exemple, si l’on a :

class A class B: public A


{ ... { ...
public : public :
virtual void fct(…); void fct(…);
… …
}; };

A *pta;
B *ptb;

Pr. J. BOUMHIDI
33
Langage C++ 2EME ANNÉE IGA / 2011-2012

L’ instruction pta->fct(…) appellera la fonction fct de la classe correspondent réellement au


type de l’objet pointé par pta. Ainsi, si pta=ptb. pta ->fct(…) appelle la fonction fct de la
classe B.
Remarque :
Pour que la liaison dynamique puisse se faire, il faut que la fonction soit appelée par un
pointeur et non directement par l’objet lui-même.

Reprenons l’exemple considéré dans le chapitre 6.

# include " point-base.h"


# include " carpoint.h"
main( ) {
pointcar a(1.5, 2., " A "); point x(-0.5,3.); pointcar * b=&a; point * y=&x ;
y=b; b->affiche ( ); y->affiche ( );
}

Le résultat obtenu est le suivant :


coordonnees : 1.5 2
identificateur : A
coordonnees : 1.5 2
Reprenons le même exemple en donnant l’attribut virtual à la fonction affiche() dans la c lasse
de base point.
# include < iostream.h>
class point {
float abs ; float ord ;
public :
point ( float x=0., float y=0.){
abs=x ; ord=y ;}
void virtual affiche ( ) {
cout <<" coordonnees : " <<abs<< " " <<ord << endl ; }} ;

class pointcar : public point {


char * sy ;
public:
pointcar ( float x=0., float y=0., char* c=" *" ):
point(x,y){
cout <<" passage dans constructeur de pointcar"<<endl;
sy= c ;}
void affiche() ;
};
void pointcar ::affiche(){
point ::affiche() ;
cout<<identificateur<<sy<<endl ;
}
// fonction utilisatrice
main ( ) {
pointcar a(1.5, 2, "A") ;
point x(-0.5,3.) ;

Pr. J. BOUMHIDI
34
Langage C++ 2EME ANNÉE IGA / 2011-2012

pointcar *b=&a ;
point * y=&x ;
y=b; b->affiche ( ); y->affiche ( );
}
Le résultat est le suivant :
Coordonnées : 1.5 2
Identificateur : A
Coordonnées : 1.5 2
Identificateur : A

Ici, le type effectif de b est point avant affectation, son type devient pointcar suite à
l’affectation y=b.
Règles concernant les fonctions virtuelles
 Seules peuvent être déclarées virtuelle s des fonctions membres de classes. C’est
pourquoi on parle aussi de méthodes virtuelles, les fonctions membres étant appelées
méthodes dans les langages orientés objet.
 Si une fonction a été déclarée virtuelle dans une classe, que ce soit une classe de base
ou une classe dérivée, le typage sera dynamique pour toutes les fonctions de même
nom dans les classes descendantes.
 Le cas de l’héritage multiple ne pose pas de problème. Une classe dérivant de
plusieurs classes peut redéfinir toute fonction virtuell e d’une classe dont elle dérive.
 Si une fonction déclarée virtuelle dans une classe n’est pas redéfinie dans une classe
descendante, cela ne gène pas : ce sera toujours la fonction de la classe de base qui
sera appliquée
 fonction virtuelle peut être surdéfinie et ce par une fonction qui peut être elle-même
virtuelle ou non, en général quand on surdéfini une fonction virtuelle, on la déclare
également virtuelle.
 Un constructeur ne peut pas être virtuel
 Un destructeur peut être virtuel
Fonction virtuelle pure
Elles sont utiles lorsque l’on définit des classes abstraites, c'est -à-dire, des classes qui ne
peuvent pas être directement instanciés pour un type spécifique, mais sont utilisées
uniquement dans les dérivations.
Une fonction virtuelle pure se déclare avec une initialisation à zéros (à ne pas confondre avec
une fonction qui ne fait rien) :
Syntaxe : virtual type nom_fonc(arguments)=0 ;
Lorsqu’une fonction comporte au moins une fonction virtuelle pure, elle est considérée
abstraite.

Pr. J. BOUMHIDI
35
Langage C++ 2EME ANNÉE IGA / 2011-2012

Chapitre 7

La gestion des exceptions


Une exception est une donnée dont les détails portent sur une action qui s’est mal déro
(interruption de l’exécution du programme).
La gestion des exceptions est un moyen de gérer les différentes erreurs d’implémenta
(non de syntaxe) qui pourraient survenir dans un programme. On peut alors traiter ces
exceptions, les attraper et reprendre l'exécution du programme.
On peut citer comme exemples d’erreurs d’implémentation, le dépassement des borne
tableau (cas où l'on essaye de lire la 15ème case d'un tableau de 10 éléments), une div
par zéro, les violations d’accès mémoire, les exceptions de gestion de fichiers….

Le système détecte automatiquement certains conditions d’erreurs et génère des


exceptions relatives à ces erreurs, si vous les générez pas dans votre application il ya a
de l’exécution.

Principe général pour La gestion des exceptions :

1) Signaler l'erreur en lançant une donnée (exception) qui contient les informations sur
l’erreur comme : Une phrase décrivant l'erreur (string), Le numéro de l'erreur(entier),
une instance de classe (Objet). Le mécanisme est : throw donnée.

throw 5; On lance l'entier 5, par exemple si l'erreur 5 est survenue.


throw string("Erreur fatale. Contactez un administrateur"); On lance une string.
throw erreur; Où erreur est une instance d'une classe.

Exemple1 : Fonction de division de a par b avec lancement d’un entier


int division(int a,int b) { if (b==0) throw 0;
else return a/b;}

2) Indiquer dans le programme principal ou dans une autre fonction appelante la partie du
code où l’erreur peut survenir. Le mécanisme est : try{…}
3) Introduire une partie du code pour attraper l’exception et gérer l’erreur. . Le
mécanisme est : catch(...){...}. Il permet de créer un bloc de gestion d'une exception
survenue. Il faut créer un bloc catch par type d'exception. Chaque bloc try doit
obligatoirement être suivi d'un bloc catch. De manière réciproque, tout bloc catch doit
être précédé d'un bloc try ou d'un autre bloc catch. La syntaxe est la suivante :
catch (type e){ }

Exemple2 : Division de a par b avec lancement d’un entier

int division(int a,int b) { if (b==0) throw 0;


else return a/b; }
void main() {
try{ cout << "1/0 = " << division(1,0) << endl; }
catch(int code) { cerr << "Exception " << endl; } }

Pr. J. BOUMHIDI
36
Langage C++ 2EME ANNÉE IGA / 2011-2012

Exemple3 : Division de a par b avec lancement d’une chaine

int division(int a,int b)


{
if(b==0)
throw std::string("ERREUR : Division par zéro !");
else
return a/b;
}

Void main()
{
int a,b;
cout << "Valeur pour a : ";
cin >> a;
cout << "Valeur pour b : ";
cin >> b;

try{
cout << a << " / "<<b << " = " << division(a,b) <<endl;
}
catch(const std::string& chaine)
{

cerr << chaine <<endl;


}

Vous pouvez mettre autant de blocs catch que vous voulez. Il en faut au moins un par type
d'exception pouvant être lancée.
try {
instructions susceptibles de provoquer, soit directement soit dans des
fonctions appelées, le lancement d'une exception
}
catch(déclarationParamètre1) {
instructions pour traiter les exceptions correspondant au type de paramµetre1
}
catch(déclarationParamètre2) {
instructions pour traiter les exceptions, non attrapées par le
gestionnaire précédent, correspondant au type de paramètre2
}
etc.
catch(...) {
instructions pour traiter toutes les exceptions
non attrapées par les gestionnaires précédents
}

Les gestionnaires catch sont explorés dans l'ordre où ils sont écrits.

Pr. J. BOUMHIDI
37
Langage C++ 2EME ANNÉE IGA / 2011-2012

Le gestionnaire catch(...) attrape toutes les exceptions. Il n'est donc pas toujours présent et,
quand il l'est, il est le dernier de son groupe.
Dés l'entrée dans un gestionnaire catch, l'exception est considérée traitée.
À l'exécution, le programme va se dérouler normalement comme si les instructions try et les
blocs catch n'étaient pas là. Par contre, au moment où l'ordinateur arrive sur une instruction
throw, il va sauter toutes les instructions suivantes, il va chercher le bloc catch qui correspond
à l'objet qui a été lancé ou dont le paramètre indique un type compatible avec celui de
expression. En effet :
Lorsqu’une exception est levée par throw , avec un type T, on cherche, dans cette ordre un
gestionnaire correspondant à l’un des types suivants : type T, type de base de T, pointeur sur
une classe dérivée ( si T est d’un type pointeur sur une classe), type indéterminé indiqué par
catch(…)) dans le gestionnaire. Arrivé au bloc catch approprié, il va exécuter ce qui se trouve
dans le bloc et reprendre l'exécution du programme après le bloc catch.

Exemple4 : Division de a par b avec lance ment d’un objet

class erreur{ };
int division(int a,int b) { erreur e;
if (b= =0) throw e;
else return a/b; }
int main() {

try{ cout << "1/0 = " << division(1,0) << endl; }


catch(const erreur& c) { cerr <<"attention erreur"<< endl; }
}

Lever une exception dans le constructeur


De la même manière, on peut lancer une exception dans le constructeur.
Soit la classe fraction suivante :
Class erreur{ public :
Void affiche(){cout<<"erreur fatale" <<endl ;} ;
class Fraction{
int Nominateur,Denominateur ;
public :
Fraction(int, int) ;
};
Fraction : :Fraction(int Nom, int Den){
if (Den==0){
erreur E ; throw E;}
Denominateur=Den ;
Nominateur=Nom ;}

int main(){
try {
Fraction F1(5,0) ;
}
catch(erreur e)
{ e.affiche();}
cout<< "je continue le reste du programme "<<endl ;}

Pr. J. BOUMHIDI
38
Langage C++ 2EME ANNÉE IGA / 2011-2012

Déclaration des exceptions lancées

La déclaration d'une fonction lançant un nombre limité de type d'exception, telle que la
fonction division de l'exemple précédent, peut être suivie d'une liste de ces types
d'exceptions dans une clause throw :

int division(int a,int b) throw(int) { if (b==0) throw 0; else return a/b; }

Par défaut, la fonction peut lancer n'importe quel type d'exception. La déclaration de la
fonction division sans throw est donc équivalent à la déclaration suivante :

int division(int a,int b) throw(...) // n'importe quel type d'exception { if (b==0) throw 0;
// division par zéro; else return a/b; }

La classe Exception
La classe exception est la classe de base de toutes les exceptions lancées par la bibliothèque
standard. Elle est aussi spécialement utilisée pour qu'on puisse la dériver afin de réaliser notre
propre type d'exception. La définition de cette classe est :

class exception
{
public:
exception() throw(){ } // Constructeur.
virtual ~exception() throw(); // Destructeur.

virtual const char* what() const throw(); // Renvoie une chaîne contenant des infos sur
l'erreur.
};

Pour l'utiliser, il faut inclure le header correspondant, soit ici le fichier exception :
#include <exception>

Remarque :
On peut lancer une exception avec une instance de la classe exception et l’attraper par le
même type.

Les exceptions standards


La bibliothèque standard peut lancer 5 types d'exceptions différents résumés dans le tableau
suivant :

Nom de la classe Description


bad_alloc Lancée s'il se produit une erreur lors d'un new.
bad_cast Lancée s'il se produit une erreur lors d'un dynamic_cast.
bad_exception Lancée si aucun catch ne correspond à un objet lancé.
bad_typeid Lancée s'il se produit une erreur lors d'un typeid.
ios_base::failure Lancée s'il se produit une erreur avec un flux.

Pr. J. BOUMHIDI
39
Langage C++ 2EME ANNÉE IGA / 2011-2012

Il faut attraper l'exception par valeur et pas par référence (c'est-à-dire sans le &), pour que le
polymorphisme soit conservé.

Création de notre propre classe d'exception héritant de la classe exception


On peut alors créer notre propre classe d'exception qui hérite de la classe exception :
#include <conio.h>
#include <iostream>
using namespace std;
#include <exception>

class Erreurr: public exception


{

public:
Erreurr(){ }
};
int division(int a,int b) { Erreurr e;
if (b==0) throw e;
else return a/b; }

int main() {
try{ cout << "1/0 = " << division(1,0) << endl; }
catch(Erreurr& c) { cout <<c.what()<< endl; }
getch();
return 0;
}
Le résultat d’exécution est 7 Erreurr

Remarque :
On peut redéfinir la méthode what() dans notre classe Erreur. Ceci est possible grâce au
polymorphisme, c'est la fonction what() de la classe fille qui sera appelée.

Gérer une exception dans un constructeur


C'est nécessaire lorsqu’on a plusieurs allocations dans le constructeur. C'est la seule manière
d'indiquer que la construction de l'objet a échoué. Si une exception est levée dans un
constructeur, le destructeur n'est pas appelé (puisque l'objet n'est pas construit, on ne peut pas
le détruire), il faut donc libérer la mémoire allouée via des delete dans le catch.
Cela donne par exemple :

class MaClasse{
private:
int* m_tableau1; // MaClasse possède deux tableaux dynamiques alloués via new.
int* m_tableau2;

public:

MaClasse():m_tableau1(0),m_tableau2(0)
{

Pr. J. BOUMHIDI
40
Langage C++ 2EME ANNÉE IGA / 2011-2012

try{
m_tableau1 = new int[1000]; // On alloue le tableau, mais cela peut échouer.
m_tableau2 = new int[1000]; // Idem.
}
catch(const exception& e)
{
// Si on arrive ici c'est qu'il y a une erreur d'allocation.
// On doit donc libérer la mémoire.
delete[] m_tableau1;

// On relance alors l'exception pour indiquer qu'une erreur est survenue.


throw;
}
}

~MaClasse()
{
delete[] m_tableau1;
delete[] m_tableau2;
}
};

S'il n'y a pas d'allocation dynamique dans un constructeur, il n'y a pas besoin d'attraper
l'exception à l'intérieur même du constructeur.

Vous remarquerez que seul le premier tableau est détruit dans le catch. En effet, si une
exception a été levée, c'est que soit le premier soit le deuxième tableau n'a pu être créé. Dans
ces deux cas, le deuxième tableau n'est pas construit, il ne sert donc à rien de le détruire.

Il ne faut jamais lever d'exception dans un destructeur. Une des raisons est la suivante : que
faire si la destruction d'un objet rate ? Reconstruire l'objet ? Ça n'a pas de sens puisqu'on veut
le détruire. Cette question n'a pas de réponse satisfaisante.

Les exceptions non rattrapées


Si une exception n'a pas de catch correspondant, le programme va remonter toutes les
fonctions appelées jusqu'à arriver à la fonction main. Si à ce moment-là, il n'y a toujours pas
de bloc catch, l'ordinateur va appeler la fonction std::terminate().
Cette fonction crée un arrêt brutal du programme.

Exercice :
Ecrire une classe de point ( à deux coordonnées entières) qui dispose d’un constructeur à deux
arguments levant une exception lorsque l es deux composantes sont égales. De plus, l’appel
d’un constructeur sans argument ou à un seul argument devra également lever un autre type
d’exception. Ici, on limitera les fonctions membre de point aux seuls constructeurs.
Ecrire un programme d’utilisation de la classe point, interceptant convenablement les
exceptions prévues, en mentionnant la cause.

Pr. J. BOUMHIDI
41
Langage C++ 2EME ANNÉE IGA / 2011-2012

Chapitre 8
HERITAGE MULTIPLE

Cette possibilité n’existe que depuis la version 2 de C ++. Elle est encore peu utilisée.

Principe : une classe peut dériver de plusieurs classes. On a alors la possibilité d’avoir soit
une arborescence simple dans la hiérarchie des classes, soit un graphe plus ou moins
complexe.

1) Dérivation à partir de plusieurs classes


 Déclaration d’une classe dérivée de plusieurs classes

Soient deux classes A et B déclarées par :

class A { class B {
... }; ...};

Pour définir une nouvelle classe C qui dérive de A et B, on la déclare par :

class C : public A, public B{


... } ;

Exemple:
Soit les classes Salaire et Identificateur suivantes :

class Salaire { class Identificateur {


double S ; char* Nom
public : public :
Salaire(double) ; Identificateur(char*) ;
... }; ...};

Nous pouvons créer une classe employé caractérisé par le nom de l’employé et son salaire en
la dérivant à partir des deux classes précédentes.

Syntaxe de déclaration :
class Employé : public Salaire, public Identificateur{
... } ;

 Constructeur pour la classe dérivée


Comme pour les dérivations simples, les constructeurs des classes de base sont appelés dans
l’ordre où ils sont déclarés dans la classe dérivée.
Le constructeur de la classe dérivée Employé déclarée ci- dessus peut s’écrire :

Employé : : Employé( double a=0, char * c= " ") : Salaire(a), Identificateur(c) {


cout << " passage dans constructeur de Employé\n " ;}

Pr. J. BOUMHIDI
42
Langage C++ 2EME ANNÉE IGA / 2011-2012

 Redéfinition de fonctions définies dans les classes de base


Si une fonction de la classe dérivée appelle des fonctions des classes de base, on utilise
l’opérateur de résolution de portée : : pour spécifier la fonction appelée.

Exemple : Redéfinition de la fonction affiche dans la classe employé : celle- ci n’a rien d’autre
à faire que d’appeler successivement les fonctions affiche des classes Salaire et Identificateur.
On peut l’écrire :

void Employé : : affiche ( ) {


Salaire : : affiche ( ) ; Identificateur : : affiche( ) ;}

Exercice
Donnez programme complet de définition et d’utilisation de la classe Employé à partir des
classes Salaire et Identificateur (avec les constructeurs et les fonctions d’affichage)

 Désignation des membres données

Si des membres donnés accessibles dans une classe dérivée de plusieurs classes, portent le
même nom dans plusieurs classes de base, cela ne pose pas de problème. Il suffit d’ utiliser
l’opérateur de résolution de portée : : pour spécifier la classe dans laquelle il faut prendre
l’objet. Ainsi :

A: : x désigne un objet x membre de la classe A


B::x désigne un objet x membre de la classe B

Exemple: Donner aux membres de la classe Salaire et de la classe Identificateur le statut


protected et introduisez dans la classe Employé une fonction coïncide qui renvoie un bool
valant true si 2 employés possèdent le même nom et même salaire, et false sinon. Nous
supposons que le nom du membre donné de la classe Salaire est x , et le nom du membre
donnée de Identificateur est aussi x. Ecrire le programme complet.

2) Classes Virtuelles
 Problème de la duplication de données
Il peut arriver qu'une classe dérive de plusieurs classes qui dérivent elles-mêmes d'une même
classe de base. Dans ce cas, les membres de la classe de base vont apparaître plusieurs fois
dans la dernière classe obtenue par dérivation.
C'est ce qui se produit dans la configuration suivante :

class A {
…};

class B : public A { class C : public A {


…}; . . . };

class D : public B , public C {


. . .};

On dit que D hérite deux fois de A, les membres donnés seront dupliqués dans tous les objets
de type D.

Pr. J. BOUMHIDI
43
Langage C++ 2EME ANNÉE IGA / 2011-2012

Pour les fonctions membres, cela ne présente pas d'inconvénient majeur, car les fonctions sont
générées à la compilation et leur code est introduit une seule fois à l'édition de liens. Il n'y a
donc pas de duplication. Mais les membres donnés, eux, sont réellement dupliqués.
Le C++ permet de n'incorporer qu'une seule fois les données. Il suffit pour cela de déclarer
dans les classes B et C que la classe A est virtuelle. On spécifie tout simplement l’attribut
virtual dans les déclarations des classes B et C. On écrira:

class B : public virtual A class C : public virtual A


{ ... }; {...};

class D : public B , public C {


. . .} ;

L'attribut virtual n'a pas d'effet sur les classes B et C, mais uniquement sur les descendants
éventuels de B et C.
2. Appel des constructeurs

o 1er cas: il n'y a pas de classes virtuelles:

Le premier constructeur appelé est celui de la classe de base qui est à la racine de
l'arborescence. Lorsqu'il y a une dérivation multiple, on tient compte de l'ordre dans lequel les
différentes classes dont est issue une dérivation sont déclarées. Les constructeurs peuvent
passer des arguments aux constructeurs des niveaux précédents
Ainsi, par exemple, dans la configuration ci-dessus où D dérive de B et C dans cet ordre, B et
C dérivant elles-mêmes de A, les constructeurs sont déclarés par:

A: : A ( arg) {...} pour la classe A


B::B(arg) : A(arg) { … } pour la classe B
C::C(arg) : A(arg) { … } pour la classe C
D::D(arg) : B(arg),C(arg) {...} pour la classe D

Les constructeurs sont appelés dans l'ordre suivant:


- constructeur de A avec la liste d'arguments passés par B
-constructeur de B avec la liste d'arguments passés par D
-constructeur de A avec la liste d'arguments passés par C
-constructeur de C avec la liste d'arguments passés par D
-constructeur de D

On voit qu'il y a bien construction de deux objets de type A

o 2ème cas: il y a des classes virtuelles:

Si les déclarations de B et C comportent l'attribut virtual pour la classe A, on évite d'appeler


deux fois le constructeur de A, d'abord depuis B, puis depuis C.
Le C++ traite les choses de la façon suivante: les classes où une classe est déclarée virtuelle
n'appellent pas le constructeur de la classe virtuelle. Ce dernier est appelé par un descendant,
qui est autorisé à lui passer des arguments.
Les constructeurs seront déclarés ainsi :

A: :A(arg) {...} pour la classe A


B::B(arg) {...} pour la classe B
Pr. J. BOUMHIDI
44
Langage C++ 2EME ANNÉE IGA / 2011-2012

C::C(arg) {...} pour la classe C


D::D(arg} : A(arg), B(arg), C(arg) {...} pour la classe D

Ils sont appelés dans l'ordre suivant: (selon l’ordre dans l’entête de déclaration de classes)
- constructeur de A avec les arguments passés par D
-constructeur de B avec les arguments passés par D
-constructeur de C avec les arguments passés par D
-constructeur de D

Pr. J. BOUMHIDI
45

Vous aimerez peut-être aussi