Académique Documents
Professionnel Documents
Culture Documents
Chapitre 1
SPECIFICITES DU 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 ;}
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
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++.
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 :
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.
Pr. J. BOUMHIDI
8
Langage C++ 2EME ANNÉE IGA / 2011-2012
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
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
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)
};
- 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
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
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.
a-> nom-fonction(arguments) ;
Qui est équivalent à :
(*a).nom-fonction(arguments) ;
#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
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.
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.
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.
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 :
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 ( ) ;
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.
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
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
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.
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.
Pr. J. BOUMHIDI
19
Langage C++ 2EME ANNÉE IGA / 2011-2012
Pr. J. BOUMHIDI
20
Langage C++ 2EME ANNÉE IGA / 2011-2012
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.
- 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
- 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é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 :
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.
class vecteur {
int * a ;
int nmax ;
public :
vecteur (int k) {
a=new int[nmax=k] ;}
...
} ;
vecteur x(n) ;
vecteur y(m) ;
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 :
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
Pr. J. BOUMHIDI
25
Langage C++ 2EME ANNÉE IGA / 2011-2012
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.
Pr. J. BOUMHIDI
26
Langage C++ 2EME ANNÉE IGA / 2011-2012
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
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
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.
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 ( ) ;
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 ( ) ;}
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
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.
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
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 :
Pr. J. BOUMHIDI
31
Langage C++ 2EME ANNÉE IGA / 2011-2012
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
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.
A *pta;
B *ptb;
Pr. J. BOUMHIDI
33
Langage C++ 2EME ANNÉE IGA / 2011-2012
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
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.
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){ }
Pr. J. BOUMHIDI
36
Langage C++ 2EME ANNÉE IGA / 2011-2012
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)
{
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.
class erreur{ };
int division(int a,int b) { erreur e;
if (b= =0) throw e;
else return a/b; }
int main() {
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
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 :
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.
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é.
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.
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;
~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.
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.
class A { class B {
... }; ...};
Exemple:
Soit les classes Salaire et Identificateur suivantes :
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{
... } ;
Pr. J. BOUMHIDI
42
Langage C++ 2EME ANNÉE IGA / 2011-2012
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 :
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)
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 :
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 {
…};
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:
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
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:
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