Vous êtes sur la page 1sur 71

Dpartement Automatisation et Informatisation

Anne 2005-2006

Programmation
en C++

Institut des Sciences et Techniques de lIngnieur dAngers

1
Bertrand Cottenceau

1.
2.

Introduction du cours de C++ .................................................................................................................................. 3


Le C++ en dehors des classes dobjets ....................................................................................................................... 3
2.1.
Les notions du C utilises en C++ ....................................................................................................................... 3
2.2.
Les rfrences en C++........................................................................................................................................... 8
2.3.
Les paramtres avec valeurs par dfaut ............................................................................................................... 11
2.4.
La surcharge de fonctions.................................................................................................................................. 12
3.
Introduction la POO et la reprsentation de classes en UML........................................................................... 12
4.
Les classes en C++................................................................................................................................................... 15
4.1.
Les classes ostream et istream............................................................................................................................. 15
4.2.
Mthodes et attributs......................................................................................................................................... 15
4.3.
Constructeurs : mthodes d'initialisation d'un objet ......................................................................................... 17
4.4.
Diffrence entre slecteurs/accesseurs et modificateurs...................................................................................... 19
4.5.
Constructeur de copie, oprateur daffectation ................................................................................................. 19
5.
Les oprateurs : fonctions globales ou mthodes d'une classe................................................................................. 21
5.1.
La pluralit des oprateurs du C++ .................................................................................................................... 21
5.2.
Les oprateurs avec traitement par dfaut ......................................................................................................... 21
5.3.
Les oprateurs que lon peut surcharger en C++ ................................................................................................ 21
5.4.
Le mcanisme de dfinition des oprateurs en C++........................................................................................... 22
5.5.
Exemple : classe de nombres rationnels............................................................................................................. 22
6.
L'utilisation de classes paramtres en type (introduction la bibliothque STL) .................................................. 24
6.1.
Introduction ...................................................................................................................................................... 24
6.2.
Namespace......................................................................................................................................................... 26
6.3.
Classe string ................................................................................................................................................. 27
6.4.
Conteneurs vector<T>................................................................................................................................. 28
6.5.
Conteneurs list<T> ..................................................................................................................................... 30
7.
Ralisation de la composition en C++.................................................................................................................... 31
7.1.
Reprsentation UML de la composition ............................................................................................................ 31
7.2.
Objets composants comme attributs dune classe.............................................................................................. 32
7.3.
Utilisation d'attributs de type tableau d'objets.................................................................................................. 34
7.4.
Utilisation d'un objet membre de type vector<T> ...................................................................................... 35
8.
Ralisation de la spcialisation en C++................................................................................................................... 35
8.1.
Reprsentation de la spcialisation en UML...................................................................................................... 35
8.2.
Exemple de ralisation de la spcialisation en C++............................................................................................ 36
8.3.
Les conversions de type entre sous-classe et super-classe..................................................................................... 38
8.4.
Le polymorphisme et les mthodes virtuelles..................................................................................................... 39
8.5.
Les classes abstraites........................................................................................................................................... 41
9.
Visibilit des membres dune classe......................................................................................................................... 42
9.1.
Mot-cl friend : fonctions ou classes amies................................................................................................... 42
9.2.
Membres protgs (mot-cl protected) et visibilit des membres................................................................. 43
9.3.
Navigation dans une hirarchie de classes, phnomnes de masquage............................................................... 45
10.
Classes avec donnes en profondeur....................................................................................................................... 46
10.1.
Les oprateurs de gestion de mmoire (new et delete)................................................................................... 46
10.2.
Ralisation de la composition par gestion dynamique de mmoire (donnes en profondeur)...................... 47
10.3.
Ralisation de conteneurs : tableaux, piles, files, listes .................................................................................. 48
11.
Les fonctions et les classes paramtres en type....................................................................................................... 52
11.1.
Les fonctions paramtres en type................................................................................................................ 52
11.2.
Classes paramtres en type........................................................................................................................... 53
12.
Les flots entre/sortie, les fichiers ........................................................................................................................... 55
12.1.
Les dclarations d'numrations au sein des classes ...................................................................................... 55
12.2.
La dclaration d'une classe au sein d'une classe............................................................................................. 55
12.3.
La hirarchie des classes de flots.................................................................................................................... 56
12.4.
Les fichiers .................................................................................................................................................... 57
12.5.
L'accs direct dans les fichiers ....................................................................................................................... 58
13.
Les changements de type......................................................................................................................................... 59
13.1.
Les constructeurs comme convertisseurs de type........................................................................................... 59
13.2.
Conversion dun type classe vers un type primitif ....................................................................................... 61
14.
Conclusion et fiches synthtiques........................................................................................................................... 63

1. Introduction du cours de C++


Ce cours est une prsentation du langage C++ dans le cadre de la programmation oriente objet. Le
langage C++ est une surcouche du langage C. Il peut donc s'utiliser comme un C amlior : en utilisant
les flots dentre-sortie (cin/cout) la place des fonctions scanf et printf, en utilisant des objets de la classe
string plutt que les chanes de caractres du C, ou encore en utilisant les conteneurs vector de la
bibilothque STL la place des tableaux statiques ou dynamiques. De plus, le C++ introduit la notion de
rfrence (notion dj prsente en langage Pascal) qui facilite la gestion des paramtres des fonctions et rend
obsolte le passage dargument par adresse .
Mais la vocation du C++ est avant tout la mise en oeuvre des concepts de la Programmation
Oriente Objet (POO). En POO, un programme ne repose pas seulement sur des appels de fonctions ; il
dcrit la coopration d'objets. Un objet est une variable structure avec diffrents attributs, et laccs aux
attributs se fait par lintermdiaire de fonctions membres, elles-mmes attaches lobjet. Les donnes (les
valeurs des attributs) sont dites encapsules dans les objets. Vu de lextrieur, on ne connat un objet que par
ce quil sait faire, cest--dire son comportement.
Cette approche de la programmation rend lvolution des programmes plus facile. Par exemple, il est
possible de changer limplmentation dun objet sans changer son comportement. On facilite ainsi le
dcouplage de certaines parties du logiciel. En outre, les objets bien conus sont facilement rutilisables dans
dautres contextes.
Pour ces raisons, la POO est trs diffrente de la programmation procdurale. En POO, on ne
cherche pas dcomposer un logiciel en fonctions et sous-fonctions. On cherche plutt identifier quelles
sont les classes d'objets pertinentes pour un problme donn et quelles sont les relations entre ces classes.
Pour appliquer les concepts de la POO, le C++ autorise la dfinition de classes (attributs, fonctions
membres, visibilit des membres), et la mise en uvre des relations (notamment de composition ou de
spcialisation) entre les classes. De plus, des classes standards dobjets sont fournies avec les compilateurs
C++: on peut utiliser des objets string pour grer les chanes de caractres et les classes stream (istream,
ostream, fstream) permettent les entres-sorties de type clavier/cran/fichiers.
L'objectif de ce cours de C++ est de prsenter lcriture de classes en C++, la faon d'implmenter la
composition (voire l'agrgation), ainsi que l'implmentation de la spcialisation (notion de sous-classe). Pour
illustrer ces notions, des exemples simples seront donns. Comme pour tout langage informatique, il
convient de comprendre et retenir un certain nombre de techniques et de rgles d'criture justifies par des
critres de performance, ou de validit du code. Des fiches synthtiques rsumant les points importants sont
fournies.

2. Le C++ en dehors des classes dobjets


Le langage C++ a t conu par Bjarne Stroustrup au dbut des annes 1980. Le C++ est une surcouche
au langage C qui intgre les concepts de la POO. Le C++ prserve donc de minimes exceptions la syntaxe
du langage C. Lobjectif de B. Stroustrup en crant ce langage tait de faciliter aux programmeurs C la
transition vers la programmation oriente objet.
2.1.

Les notions du C utilises en C++

Le C++ est un langage typ. Le compilateur C++ vrifie donc la compatibilit des types lors des
oprations arithmtiques, des affectations et des passages de paramtres aux fonctions.
2.1.1.
double ,

Les types primitifs (ou types de base).

Les types primitifs du C++ sont les mmes quen C, savoir char, short, int, long, float,
les variables non signes unsigned char, unsigned short, unsigned int et les variables pointeur
3

char*, short*, int*, double*

etc. Le nombre doctets assign chacun de ces types peut varier dun

compilateur un autre.
Rappel : la taille dune variable (exprime comme multiple de la taille dun char) sobtient laide
de loprateur sizeof .
int var;
printf("taille de var=%d, taille dun char= %d",sizeof(var),sizeof(char));

2.1.2.

Les pointeurs

Les variables pointeurs sont toujours utilises en C++ notamment pour le passage de tableaux en
paramtre dune fonction. Rappelons quun pointeur reprsente un type.
Une variable de type T* est destine recevoir ladresse dune variable de type T.
Lorsque ptr est une variable pointeur de type T*, loprateur * permet de d-rfrencer ce pointeur :
*ptr est la variable de type T dont ladresse est stocke dans la variable ptr .
Il est noter que ptr est une variable et *ptr en est une autre. Par ailleurs, loprateur & permet de
fournir ladresse dune variable. En synthse on a :
int var=12;
int * ptr;

// variable de type int dclare et initialise


// variable de type pointeur sur int non initialise.

// ne pas excuter *ptr=45 ici, la variable pointeur nest pas initialise !!!
ptr=&var ;
*ptr=45 ;

// ptr initialise avec ladresse de la variable var.


// *ptr d-rfrence ptr. A cet instant, *ptr est la variable var.

Rappel : puisque les pointeurs sont typs, le compilateur ralise aussi des vrifications de type
impliquant les pointeurs :
int varI=12;
int * ptrI=&varI;
char varC=12;
char* ptrC=&varC;
ptrC=&varI ; // erreur de compilation : char * non compatible avec int *
ptrI=&varC ; // idem : int * pas compatible avec char *

2.1.3.

Les pointeurs et les tableaux.

En C et en C++, le nom dun tableau correspond ladresse de sa premire case. De plus, on peut
toujours utiliser une variable pointeur comme sil sagissait de ladresse de la premire case dun tableau :
int tab[3]={1,2,7};
int * ptrI=tab ;
ptrI[2]=3 ;
ptrI=&tab[1] ;
ptrI[1]=2 ;

//
//
//
//
//

tableau de 3 int initialiss


licite, identique ptrI=&tab[0]
licite : ptrI[2] quivaut ici tab[2]
licite aussi , identique ptrI=tab+1
ptrI[1] quivaut maintenant tab[2]

Rappelons que les chanes de caractres en C sont aussi des tableaux:


char chaine2[26]="bonjour"; // tableau de 26 char dont 8 cases sont initialises
char chaine2[]="bonjour";
// tableau de 8 char initialiss
// surtout pas char * chaine3 ="bonjour";

2.1.4.

Les structures de contrle.

Le C++ utilise la mme syntaxe que le langage C pour les diffrentes structures de contrle. Les
structures de contrle sont if/else, for et while (ou do while).
4

La structure if(){}else{}
int entier;
printf("Saisir un entier:\n");
scanf("%d",&entier);
if((entier%2)==0)
{
printf("Entier pair");
}
else
{
printf("Entier impair");
}

La structure while(){} ou do{}while();


int entier;
do
{
printf("Saisir un entier impair :\n");
scanf("%d",&entier);
}
while((entier%2)==0);
//boucle tant que entier pair

La structure for(

; ;){}

int tab[10]; // rserve un tableau de 10 entiers (non initialiss)


int indice;
tab[0]=0;
// initialise le premier lment 0
for(indice=1;indice<10;indice++)
{
tab[indice]=tab[indice-1]+indice;
}

2.1.5.

// tab[indice]=indice + (indice-1)+...

Les conversions de type (transtypage ou cast)

Le compilateur C++ utilise et vrifie les types pour sassurer que les oprations ralises sont
cohrentes et que les paramtres passs une fonction correspondent aux paramtres attendus. Nanmoins,
dans certains cas, le compilateur autorise des conversions implicites (conversions que le compilateur peut
dduire grce au contexte) ou explicites de types. Il faut noter que certaines conversions implicites gnrent
tout de mme des mises en garde (warnings) au moment de la compilation (par exemple lorsque lon
compare un entier sign avec un entier non sign).
Il y a par exemple conversion implicite quand on affecte la valeur dune variable de type float une
variable de type int (ou linverse). Dans cette situation, le compilateur met en place automatiquement une
conversion de type. Cette conversion est dite dgradante (perte dinformation) dans le sens float-> int.
Lexemple ci-dessous prsente quelques conversions de type.
int varI=513;
char varC;
float varF=2.5 ;

// variable entire sur 32 bits sign


// variable entire sur 8 bits sign
// variable utilisant la norme flottant IEEE 32 bits

varC=varI;
// conversion dgradante
varI=varF;
// ici, un warning signale possible loss of data
printf("%d %d",varC,varI);
// affiche 1 (513mod256) et 2(partie entire de 2.5)

Parfois, les incompatibilits de type conduisent lchec de la compilation :


int varI=513;
char * ptrC=&varI;

// conversion int* -> char * impossible.

Lorsque le programmeur souhaite outrepasser les incompatibilits de type, celui-ci peut indiquer
explicitement la conversion raliser. On parle alors de conversion explicite, de transtypage explicite ou
encore de cast. Dans ce cas, le programmeur a la responsabilit de sassurer de la validit de lopration.
int varI=513;
char * ptrC=(char *) &varI;
printf("%d %d",ptrC[0],ptrC[1]);

// conversion int*-> char* demande


// affiche les deux octets de poids faible de varI

Lors des conversions explicites (comme ci-dessus), cest au programmeur de vrifier scrupuleusement
ce quil fait. Cet aspect du langage donne beaucoup de libert au programmeur et permet de grer des
problmes de bas niveau. Mais labus de conversions explicites, notamment pour rgler des warnings
rcalcitrants la compilation, peut conduire de grosses erreurs de programmation. Lexemple suivant
illustre ce quil ne faut pas faire mme si le compilateur lautorise.
int varI=513;
char * ptrC=(char *) &varI;
int adresse=(int) ptrC;
int * pI=(int *) adresse;
*pI=2;
printf("%d",varI);

//
//
//
//

cast int * -> char *


cast char * -> int (a cest vraiment douteux)
cast int -> int * (aussi douteux)
miraculeusement, pI contient ici ladresse de varI.

Les conversions de pointeurs sont parfois utiles pour tromper le compilateur. En revanche les
conversions de pointeurs en valeurs numriques (ou linverse) sont farfelues. Lexemple prcdent montre
que le compilateur accepte tout et nimporte quoi ds quon utilise les conversions de type.
Rappelons quelques cast utiles :
T * -> V* : conversion de pointeurs. Les adresses restent intactes car elles ont la mme
taille. Mais ces adresses ne dsignent pas des variables de mme type ! A utiliser avec
prcaution.
float -> int : (dgradant) conserve la partie entire.
int -> float : passage au format flottant IEEE
char -> int : la variable passe sans altration de 8 bits 32 bits
int -> char : dgradant : les 32 bits sont tronqus aux 8 bits de poids faible.

2.1.6.

Les fonctions et le passage de paramtres en C (et en C++)

En C++, on utilise abondamment les fonctions au sein des classes (on parlera de fonctions membres
ou de mthodes dune classe). Il est donc ncessaire de rappeler brivement le mode de transmission des
paramtres.
En C et en C++, les paramtres sont passs une fonction par copie, cest--dire via la pile. Le
programme qui utilise une fonction recopie dans la pile les paramtres dappel (encore appels paramtres
effectifs). Par consquent, la fonction appele na accs (dans la pile) qu une copie des paramtres effectifs,
et non loriginal. Il faut noter que les variables locales une fonction sont galement des emplacements
mmoire allous dans la pile (comme les paramtres). En rsum, quil sagisse des paramtres ou des
variables locales une fonction, ce sont des variables temporaires qui nexistent que le temps dexcution de
la fonction.
void MaFonction(int param)
// param ppv pAppel
{
param++ ;
// ici, param est modifi mais pas pAppel
}

void main()
{
int pAppel=23;
MaFonction(pAppel);
printf("d",pAppel);
}

// pAppel est recopi dans param


// pAppel vaut toujours 23

Vu diffremment, on peut considrer quau dbut de la fonction le paramtre formel (param) est une
variable locale initialise laide de la valeur du paramtre dappel, mais quil sagit bien de deux variables
distinctes. Ce mode de passage est appel passage par copie ou passage par valeur.
Par consquent, lorsque lon souhaite quune fonction modifie une variable, on na pas dautre
choix que de lui fournir ladresse de la variable modifier. On appelle souvent cette technique passage de
paramtres par adresse.
void MaFonction(int * adrVariable)
{
(*adrVariable)++;
}
void main()
{
int pAppel=23;
int * adrAppel=&pAppel;
MaFonction(adrAppel) ;
printf("pAppel =%d",pAppel); //pAppel vaut ici 24
}

Remarquez bien que cette fois-ci cest ladresse qui est passe par copie, mais on peut dsormais modifier
la variable dsigne par ladresse contenue dans adrAppel, peu importe que la variable pointeur adrAppel ne
puisse pas tre modifie par la fonction !
2.1.7.

Performances lies au mode de transmission des paramtres

Le mode de transmission des paramtres peut avoir des rpercussions sur la rapidit dexcution.
Considrons le programme suivant o un type structur personne est dfini. Une variable de type personne
contient 3 champs : deux tableaux de 30 caractres et un entier non sign. Une variable de type personne a
une taille de 64 (60 char + 1 unsigned cod sur 32 bits).
Rappel : pour les pointeurs sur des variables structures, la notation ptr->nom est quivalente
(*ptr).nom
----------------------------------------------------------------------------------struct personne
{
char nom[30];
char prenom[30];
unsigned age;
};
void Affiche(personne p)
{
printf(" nom : %s prenom : %s age : %d \n",p.nom,p.prenom,p.age);
}
void AfficheBis(personne * ptr)
{
printf(" nom : %s prenom : %s age : %d \n",ptr->nom,ptr->prenom,ptr->age);
}

void main(void)
{
personne AM={"Martin","Arthur",32};
printf("sizeof(AM) = %d\n",sizeof(AM)); // affiche : sizeof(AM)=64
Affiche(AM);
AfficheBis(&AM);
// excution plus rapide que Affiche(AM)
}
-----------------------------------------------------------------------------------

La fonction AfficheBis(personne *) est plus rapide que la fonction Affiche(personne) pour un


service quivalent. Pourquoi ? Lappel de la fonction Affiche(personne) ncessite une recopie dune variable
de 64 octets dans la pile, alors que lappel de la fonction AfficheBis(personne *) ne requiert que la copie
dun pointeur de 32 bits (4 octets).
Les soucis de performance imposent au programmeur de bien connatre tous ces aspects afin de
raliser les meilleurs choix. On verra quen C++ on utilisera le passage de rfrence plutt que le passage
dadresse pour amliorer les performances.
2.1.8.

Les paramtres constants : garantie d'un code avec moins de bugs

float Division(int num,int den)


{
if(den=0) return num;
return (float)num/den ;
}

La fonction prcdente contient une erreur de programmation que le compilateur ne peut dtecter.
En effet, den=0 est une affectation et non un test dgalit. Le paramtre den prend donc systmatiquement la
valeur 0. Comment prvenir de telles erreurs ? On peut utiliser le modificateur const pour les paramtres
dentre (paramtres qui ne sont pas censs voluer dans la fonction).
float Division(const int num,const int den)
{
if(den=0) return num;
// error C2166: l-value specifies const object
else return (float)num/den ;
}

Dans ce cas, le compilateur indique quune variable (ou un objet) constante est gauche dune
affectation (l-value). Le compilateur dtecte ainsi trs facilement ce type derreur. Le programmeur qui prend
lhabitude de dclarer comme constants les paramtres dentre prvient ce type derreur.
2.2.

Les rfrences en C++


Loprateur & ci-dessous correspond la dfinition dune rfrence (o T est un type).
T var;
T & ref = variable;

// ref est une rfrence var

Ci-dessus, ref est une rfrence la variable var de type T.1 Cela signifie que les identificateurs var et
ref dsignent la mme variable. Manipuler l'un, revient manipuler l'autre. Ds lors, var et ref sont des
synonymes, ou des alias. L'initialisation d'une rfrence lors de sa dclaration est obligatoire.

Attention ne pas confondre lemploi de loprateur & pour obtenir ladresse dune variable et
pour dclarer une rfrence !
8

----------------------------------------------------------------------------------#include <iostream.h>
void main(void)
Rsultats
{
I
=
3
int I=3;
refI = 3
int &refI = I;
adresse de I : 0065FDF4
adresse de refI : 0065FDF4
printf("I = %d \n",I);
Incrment de I (I++)
printf("refI = %d \n",refI);
refI = 4
printf("adresse de I = %x \n",&I);
printf("adresse de refI = %x \n",&refI);
printf("Incrment de I (I++)");
I++;
printf("refI = %d \n",refI);
}
-----------------------------------------------------------------------------------

On constate que les variables I et refI ont la mme adresse. Elles dsignent donc bien la mme
variable. Puisque I et refI sont des alias, modifier lun revient modifier lautre.
2.2.1.

Les rfrences en C++ : un nouveau mode de transmission de paramtres

Lutilisation des rfrences faite prcdemment ne prsente pas dintrt particulier. Lutilisation la
plus courante en C++ concerne lchange dinformations avec une fonction. Cette partie du cours est
essentielle et doit faire lobjet dune attention toute particulire La notion de rfrence va nous affranchir,
dans beaucoup de cas, de la technique de passage par adresse rappele prcdemment.
#include <stdio.h>
void f(int &); // noter que le pramtre est une rfrence
void main(void)
{
int pAppel=5;
f(pAppel);
printf("pAppel = %d \n",pAppel);
}

Rsultats
pAppel = 6

void f(int & pFormel) // pFormel est un alias de pAppel


{
pFormel++;
// ici on modifie rellement le paramtre dappel, grce lalias
}

Ici le paramtre formel pFormel de la fonction f(int & ) est une rfrence au paramtre dappel.
Dans ce cas, manipuler le paramtre formel pFormel revient manipuler le paramtre dappel. Par la suite,
on appellera ce mode de transmission passage par rfrence
Le passage dargument par rfrence autorise donc la modification du paramtre dappel au sein de la
fonction. Ce mode de passage convient donc aux paramtres de sortie et aux paramtres dentre/sortie. De plus,
lutilisation dune rfrence est moins lourde que la technique du passage par adresse.
En terme de performances, le passage par rfrence est quivalent au passage par adresse. Lors de
lappel de la fonction, seule ladresse du paramtre pass par rfrence est transmise la fonction. Le
compilateur gre les rfrences grce des pointeurs qui sont cachs au programmeur.

2.2.2.

Les paramtres passs par rfrence constante : efficacit et robustesse

Le passage par rfrence est aussi performant que le passage par adresse. Il vite ainsi certaines
duplications dobjets volumineux (ncessitant beaucoup doctets en mmoire). Du coup, cela expose aussi le
programmeur des erreurs de programmation qui se rpercutent sur le programme appelant. Reprenons
lexemple dune fonction ayant un paramtre dentre dont la taille en mmoire est importante. Les
diffrentes fonctions illustrent les modes de passage de paramtres possibles en C++.
----------------------------------------------------------------------------------struct personne
{
char nom[30];
char prenom[30];
unsigned age;
};
void PassageParValeur(personne p)
{
/* inconvnient : duplication du paramtre dappel (64 octets ici)*/
printf(" nom : %s prenom : %s age : %d \n",p.nom,p.prenom,p.age);
}
void PassageParValeurConst(const personne p)
{
printf(" nom : %s prenom : %s age : %d \n",p.nom,p.prenom,p.age);
}
void PassageParAdresse(personne * ptr)
{
/* avantage : plus rapide que par copie
inconvnient : les erreurs peuvent se propager au programme appelant */
printf(" nom : %s prenom : %s age : %d \n",ptr->nom,ptr->prenom,ptr->age);
}
void PassageParAdresseConst(const personne * ptr)
{
printf(" nom : %s prenom : %s age : %d \n",ptr->nom,ptr->prenom,ptr->age);
}
void PassageParReference(personne & p)
{
/* avantage : plus rapide que par copie (aussi bien que par adresse)
inconvnient : les erreurs peuvent se propager au programme appelant */
printf(" nom : %s prenom : %s age : %d \n",p.nom,p.prenom,p.age);
}
void PassageParReferenceConst(const personne & p)
{
/* mode de transmission privilgi en C++ pour les paramtres dentre */
printf(" nom : %s prenom : %s age : %d \n",p.nom,p.prenom,p.age);
}
-----------------------------------------------------------------------------------

2.2.3.

Bilan sur le passage de paramtres en C++

On appelle paramtre dentre dune fonction un paramtre dont la valeur est utilise par la fonction
pour raliser un traitement mais dont la valeur nest pas modifie par la fonction. On appelle paramtre de
sortie un paramtre dont la valeur en entrant dans la fonction est sans importance mais dont la valeur va tre
modifie par la fonction. Enfin, certains paramtres peuvent tre la fois dentre et de sortie. En C++,
daprs la nature entre-sortie dun paramtre et sa taille, on peut choisir le mode de passage de paramtre le
plus adapt.
Les paramtres dentre : si peu volumineux (moins de 4 octets : char, int, float, long ) utiliser le
passage par valeur, sinon le passage par rfrence constante
10

Les paramtres de sortie et dentre/sortie : passage par rfrence (le passage par adresse est
obsolte)
Remarque : les tableaux sont toujours passs aux fonctions sous la forme de deux paramtres :
ladresse de la premire case puis la taille du tableau.
On donne ci-dessous quelques exemples de fonctions simples en C++.
-----------------------------------------------------------------------------------

Fonction Incrementer(E/S param : entier) permettant dajouter 1 au paramtre. Le paramtre est


de type entre-sortie (E/S).
void Incrementer(int & param) { param++ ;}

Fonction PlusUn(E param :entier) retournant la valeur du paramtre (entier) dentre augmente
de 1. Le paramtre nest plus ici quun paramtre dentre.
int PlusUn(int param){return (param+1); }

Fonction Echanger(E/S a:entier,b :entier) permettant dchanger les valeurs des deux paramtres.
Les deux paramtres sont de type E/S.
void Echanger(int & a, int & b){
int c=a ;
a=b ;
b=c ;
}

Fonction Random(S r:entier) gnrant une valeur alatoire. Le paramtre est de type sortie S.
void Random(int & r) { r=rand(); }
-----------------------------------------------------------------------------------

2.3.

Les paramtres avec valeurs par dfaut

En C++, des valeurs par dfaut peuvent tre fournies certains paramtres. Dans ce cas, si le
paramtre dappel fait dfaut, le paramtre formel de la fonction prend pour valeur la valeur par dfaut.
----------------------------------------------------------------------------------#include <stdio.h>
float Addition(float, float =3);
void main(void)
{
printf("%f \n",Addition(10,5));
printf("%f \n",Addition(7));
}

// 2nd paramtre avec valeur par dfaut

Rsultats
15
10

float Addition(float a, float b)


{
return a+b;
}
-----------------------------------------------------------------------------------

En cas dabsence dun second paramtre dappel pour la fonction Addition() , le second argument
prend pour valeur 3.
11

Note : les arguments ayant des valeurs par dfaut doivent tous figurer en fin de liste des arguments.
Voici quelques exemples, bons et mauvais, de prototypes :
float
float
float
float

Addition(float=2, float =3);


Addition(float=2, float);
Addition(float=1, float, float =3);
Addition(float, float=5, float =3);

//
//
//
//

OK
NON !!!
NON PLUS !!!
OK

Remarque : la valeur quun paramtre doit prendre par dfaut doit figurer au niveau du prototype
mais pas au niveau de len-tte de la fonction.
2.4.

La surcharge de fonctions

Dfinition (Signature) : on appelle signature dune fonction la combinaison de sa classe (si elle est
membre dune classe) de son identificateur et de la suite des types de ses paramtres. Les 3 fonctions
suivantes ont des signatures diffrentes :
float Addition(float);
float Addition(int,float);
float Addition(float,float );

Note : le type de la valeur de retour nappartient pas la signature.


En C++, il est possible de dfinir plusieurs fonctions avec le mme identificateur (mme nom) pour
peu quelles aient des signatures diffrentes. On appelle cela la surcharge de fonction.
Lexemple suivant prsente un programme o deux fonctions Addition() sont dfinies. Il faut noter
quil sagit bien de fonctions diffrentes et quelles ne ralisent donc pas le mme traitement. Le compilateur
ralise lappel de la fonction approprie partir du type des paramtres dappel.
----------------------------------------------------------------------------------#include<stdio.h>
int Addition(int, int=4);
float Addition(float, float =3);

Rsultats

void main(void)
{
float fA=2.5;
float fB=3.7;
int iA=2;
int iB=3;
printf("%f \n",Addition(fA,fB));
printf("%i \n",Addition(iA,iB));
printf("%f \n",Addition(fA));
printf("%d \n",Addition(iA));
}
float Addition(float a, float b){

6.2
17
5.5
18

return a+b; }

int Addition(int a,int b){ const int offset=12; return a+b+offset; }


-----------------------------------------------------------------------------------

3. Introduction la POO et la reprsentation de classes en UML


On va tenter de dcrire brivement ce qui distingue la POO (Programmation Oriente Objet) de la
programmation procdurale (C, Pascal).
Les langages procduraux s'appuient sur les capacits de la machine en terme de dcoupage en instructions
lmentaires et sous-programmes (fonctions) (utilisation de CALL/RET pour les processeurs Intel). La
programmation en langage procdural reste donc trs lie aux caractristiques des processeurs. Dans ce
12

contexte, pour raliser un logiciel, on fait une analyse fonctionnelle descendante pour mettre en vidence les
fonctions principales et les structures de donnes manipules par ces fonctions. On dcoupe le problme
initial en sous-problmes, autant de fois qu'il nous parat ncessaire, jusqu' pouvoir rsoudre les sousproblmes par des fonctions de taille raisonnable . En pratique, cette approche prsente de gros
dsavantages dans les projets informatiques. Dune part les fonctions crites spcialement pour un projet
sont rarement utilisables dans un autre projet. Dautre part, une modification des structures de donnes
entrane de multiples points de correction du logiciel. Les logiciels conus avec cette approche voluent
difficilement et leur maintenance nest pas aise.
Les classes dobjets
La Programmation Oriente Objet (POO) cherche s'adapter de manire plus naturelle notre
perception de la "ralit". Dans notre quotidien, nous ralisons naturellement une classification de ce qui nous
entoure. Nous mettons chaque lment de notre entourage dans une classe d'lments qui ont des
caractristiques communes fortes. Un lment dune classe sera appel de manire gnrique objet (mme sil
sagit dun tre vivant) par la suite. Les tres vivants, les vgtaux, les minraux, les vhicules, les tlphones,
les ordinateurs, les tudiants, les systmes lectroniques, les meubles sont autant dexemples de classes
d'objets (on rappelle quobjet prend ici un sens large). Une classe dcrit donc un ensemble dobjets.
Les classes ne sont pas ncessairement disjointes. Par exemple, la classe des tres humains est une
sous-classe des tres vivants, les hommes/les femmes sont deux sous-classes des tres humains, les animaux
une sous-classe des tres vivants, les chiens une sous-classe des animaux etc.
Attributs, comportement, tat
Dans chacune des classes, les objets sont caractriss par des attributs et des comportements. La valeur
des attributs un instant donn caractrise ltat dun objet. Le comportement dun objet peut avoir des
consquences sur son tat et inversement, ltat dun objet peut contraindre ses comportements possibles (
un instant donn).
Objet de la classe des tlviseurs
attributs : taille dcran, marque, tat allum/teint, chane slectionne, volume sonore ...
comportement : allumer, teindre, changer de chane, rgler le volume sonore,
Objet de la classe des voitures
attributs : marque, modle, nergie (essence/diesel), puissance, nombre de portes
comportement : mettre moteur en marche, l'arrter, changer de vitesse,
Dans la notation UML (Unified Modeling Language), les classes sont reprsentes par des rectangles
indiquant quels sont les attributs, leur visibilit (- priv et + public), quelles sont les mthodes accessibles.
Voiture
-marque : string
-modele : string
-energie : string
-nombre de portes : int
-puissance : int
-moteurEnMarche : bool
-vitesseEnclenchee : int
+MettreMoteurEnMarche()
+ArreterMoteur()
+ChangerVitesse(in noVitesse : int)
+SetClignotant(in OnOffDroiteGauche : int)
+Accelerer()
+Freiner()

Televiseur
-tailleEcran : int
-marque : string
-enMarche : bool
-chaineSelectionnee : int
-volumeSon : int
-reglageCouleur : int
+Allumer()
+Eteindre()
+ChangerChaine(in noChaine : int)
+ReglerVolume(in volume : int)
+ReglerCouleur(in couleur : int)

Enumrer les attributs et comportements dun objet nest pas toujours chose possible. On imagine
mal dans un programme de gestion de comptes bancaires, dcrire un client propritaire dun compte par
lensemble de ses caractristiques morphologiques ou psychologiques. Sans doute quon se contentera de le
13

dcrire par son tat civil plus quelques autres renseignements. Toute reprsentation dun objet ou dune
classe dobjets ncessitera disoler les attributs et les comportements pertinents pour lutilisation que lon fera
de cet objet. Cette dmarche dabstraction est arbitraire et est dpendante du contexte dutilisation de la
classe dobjets. En informatique, une classe dobjets va dcrire quels sont les attributs et les comportements
retenus pour dcrire un objet au sein dun programme.
Encapsulation des donnes
Un objet contient des donnes et des mthodes prvues pour accder ces donnes. L'ide de la
POO est de ne pouvoir "dialoguer" avec un objet qu' travers l'interface constitue par l'ensemble de ses
mthodes. Les mthodes jouent donc le rle d'interface entre les donnes et l'utilisateur de l'objet. Elles
filtrent les accs aux donnes. Pour assurer cette encapsulation, des mot-cls dfinissent la visibilit des
membres d'une classe.
Les relations entre les objets (par extension, entre les classes)
Lide de la POO est dutiliser nos aptitudes naturelles classifier des objets et tablir des liens
entre eux. Ds lors, un logiciel va tre le rsultat de linteraction dobjets entre eux. Encore faudra-t-il, avant
de faire collaborer des objets entre eux, tablir quelles sont les classes dobjets pertinentes , et quelles sont
leurs relations.
Il est noter que diffrentes relations peuvent apparatre entre des objets ou entre des classes. Deux
objets peuvent par exemple schanger des donnes. Par exemple, un objet tlcommande communique avec
un objet tlviseur
*

Televiseur

Telecommande

*
+Allumer()
+Eteindre()
+ChangerChaine(in noChaine : int)
+ReglerVolume(in volume : int)
+ReglerCouleur(in couleur : int)

-envoi commandes

Deux objets peuvent tre en relation du type composant /composite. Un objet voiture est compos dun
objet moteur. La notation UML introduit un lien avec un losange du ct du composite.
Voiture
+MettreMoteurEnMarche()
+ArreterMoteur()

Moteur
-moteur
1

-energie
-puissance
+Demarrer()
+Arreter()

Enfin, les diffrentes classes peuvent tre en relation hirarchique. On peut dcrire une classe
comme reprsentant un sous-ensemble d'une classe plus gnrale. La relation entre une classe et une sousclasse est une relation de type spcialisation/gnralisation. La classe des vlos est une sous-classe des vhicules.
La classe des vhicules moteur est une sous-classe des vhicules. Dans ce sens, un vhicule moteur est plus
spcifique quun vhicule, ou inversement, un vhicule est plus gnral quun vhicule moteur. La flche
part d'une sous-classe (ou classe spcialise) et va vers une classe plus gnrale (ou super-classe).
Vehicule

Vlo

VehiculeAMoteur

Moto

Voiture

14

Un programme en POO contiendra donc lcriture des diffrentes classes dobjets et leur utilisation.
Chaque classe sera dfinie par des attributs (accessibles seulement par les mthodes de la classe), et par des
fonctions membres (ou mthodes). Enfin, les relations entre les classes devront galement tre implmentes.
On verra notamment que la relation de gnralisation peut tre naturellement ralise en C++. Parfois, pour
une mme relation, plusieurs ralisations diffrentes peuvent tre implmentes en C++.

4. Les classes en C++


Dans un premier temps, on va sattacher lcriture des classes s'appuyant seulement sur des types
primitifs. Ceci va nous permettre dintroduire la syntaxe et les automatismes acqurir pour des classes plus
complexes.
4.1.

Les classes ostream et istream

Avant daller plus loin, notons quen C++ des classes standard dentre-sortie facilitent la saisie et
laffichage dinformations. Les entres sorties standard seffectuent grce aux objets cin (entre standard) et
cout (sortie standard). Ces objets sont des instances des classes istream (flot dentre) et ostream (flot de
sortie). Ces classes sont dfinies dans le fichier dentte iostream (sans .h).
Les objets cin et cout permettent laffichage et la saisie de tous les types de base char, int, float
et mme les chanes char * .
--------------------------------------------------------------------------------------#include<iostream>
// dfinition des classes ostream et istream
using namespace std; // espace de nom utilis pour ces classes (explication ultrieure)
void main()
{
char chaine[10]="Toto";
int age=12;
cout << chaine << " a " << age << " ans.";
cout << "Saisir un entier : \n";
cin >> age;
cout << "Valeur saisie = " << age;
}
---------------------------------------------------------------------------------------

4.2.

Mthodes et attributs

Le C++ introduit le mot cl class pour dfinir une classe dobjets. Une classe est un type structur
avec des champs de donnes typs (attributs) et des fonctions membres (mthodes). Pour expliquer la
syntaxe, le plus simple est encore de donner un exemple. La classe ci-aprs est une classe de points
coordonnes entires. La dfinition de la classe est situe dans un fichier dentte (point.h), la dfinition des
mthodes dans un fichier source (point.cpp) et enfin lutilisation dans un autre fichier source (main.cpp).
Les mots cl public et private indiquent la visibilit des membres. Les membres privs ne sont accessibles que
par les objets de la classe. Par convention, les attributs sont toujours accs priv.
--------------------------------------------------------------------------------// point.h fichier dentte contenant la dfinition de la classe
#include<iostream>
#ifndef __POINT__
#define __POINT__
class Point
{
public:
//partie publique de la classe
void SetX(const unsigned x);
// les mthodes
void SetY(const unsigned y);
void SetXY(const unsigned x,const unsigned y);

15

unsigned GetX() const;


unsigned GetY() const;
float Distance(const Point & p) const;
void Translate(const unsigned x,const unsigned y);
void Display(std::ostream & flot) const;
private:
// partie prive (encapsule)
unsigned _x;
// les attributs
unsigned _y;
};
// <- ne pas oublier ce point virgule
#endif
---------------------------------------------------------------------------------

Le fichier dentte contient la dfinition de la classe. Celle-ci dcrit les donnes membres (attributs)
et les mthodes. En fait, bien que ce ne soit pas ncessaire, seuls les prototypes des fonctions membres
(mthodes) sont gnralement donns dans la dfinition de la classe.
--------------------------------------------------------------------------------// point.cpp fichier source contenant la dfinition des fonctions membres
#include "point.h"
//inclusion du fichier dentte de la classe
#include<math.h>
void Point::SetX(const unsigned int x)
{
_x=x;
}
void Point::SetY(const unsigned int y)
{
_y=y;
}
void Point::SetXY(const unsigned int x,const unsigned int y)
{
_x=x;
_y=y;
}
unsigned Point::GetX() const{
return _x;
}
unsigned Point::GetY() const{
return _y;
}
void Point::Translate(const unsigned Dx, const unsigned Dy)
{
_x=_x+Dx;
_y=_y+Dy;
}
float Point::Distance(const Point & p) const
{
return sqrt((_x-p._x)*(_x-p._x)+(_y-p._y)*(_y-p._y));
}
void Point::Display(std::ostream & flot) const
{
// affichage sur flot de sortie
flot << "(" << _x << "," << _y << ")";
}
---------------------------------------------------------------------------------

16

Le fichier dimplmentation des mthodes (ici point.cpp) contient le code des mthodes de la classe.
Il faut noter la syntaxe permettant de dfinir une fonction membre. Le nom de la classe propritaire de la
mthode prcde le nom de la mthode. Enfin, lutilisation de la classe est faite dans un autre fichier.
--------------------------------------------------------------------------------// main.cpp : fichier dutilisation de la classe point
#include "Point.h"
// inclusion de la dfinition de la classe Point
using namespace std;
void main()
{
Point p1,p2;

// p1 et p2 sont deux objets de la classe Point

p1.SetXY(10,10);
p2.SetXY(20,20);

// donne ltat (10,10) lobjet p1


// donne ltat (20,20) lobjet p2

p1.Display(cout);
p2.Display(cout);

// affiche p1 sur le flot de sortie


// affiche p2 sur le flot de sortie

cout << "Distance p1 - p2 :" ;


cout << p1.Distance(p2) << endl;

//affiche la distance p1-p2

// p1._x=12 ; est interdit par le compilateur, les donnes sont prives


}
---------------------------------------------------------------------------------

Que faut-il remarquer ?


- les champs privs ne sont pas accessibles par lutilisateur de la classe
- les attributs privs sont nanmoins accessibles aux objets de la classe, cest--dire dans la
dfinition des mthodes (regarder le code de la mthode distance)
- deux objets de la mme classe ont les mmes attributs mais pas ncessairement le mme tat
4.3.

Constructeurs : mthodes d'initialisation d'un objet

Il ne manque pas grand chose pour que la classe Point dfinie prcdemment soit rellement
utilisable. Son plus gros dfaut est que ltat dun objet de la classe Point est indfini avant lutilisation de la
mthode Point::SetXY() . Le C++ prvoit un mcanisme pour raliser une initialisation correcte des attributs
ds la cration dun objet. On peut (on doit) ajouter des mthodes appeles constructeurs. Le rle dun
constructeur est de donner une valeur initiale (tat initial) aux attributs.
Un constructeur est une fonction membre qui porte comme nom le nom de la classe et qui ne retourne
rien. Un constructeur peut avoir zro ou plusieurs arguments, ventuellement avec valeurs par dfaut. Il peut
y avoir plusieurs constructeurs dans la mme classe (surcharge) dans la mesure o ils ont des signatures
diffrentes. On peut donc complter la classe en ajoutant des constructeurs.
--------------------------------------------------------------------------------// point.h : dfinition de la classe
#ifndef __POINT__
#define __POINT__
#include<iostream>
class Point
{
public:
// les constructeurs
Point();
Point(const unsigned x, const unsigned y);

17

void SetXY(const unsigned x,const unsigned y);


le reste est identique
private:
unsigned _x;
unsigned _y;
};
#endif
----------------------------------------------------------------------------------------------------------------------------------------------------------------// point.cpp : dfinition des fonctions membres
#include "Point.h"
#include<math.h>
Point::Point()
{
_x=0;
_y=0;
}

// constructeur sans argument

Point::Point(const unsigned x, const unsigned y)


{
SetXY(x,y);
// pourquoi pas
}

// constructeur 2 arguments

le reste est identique


---------------------------------------------------------------------------------

Il faut bien remarquer, dans lutilisation suivante, la syntaxe permettant dutiliser les constructeurs.
Les valeurs des arguments passs au constructeur sont donnes lors de la dclaration de lobjet.
--------------------------------------------------------------------------------// utilisation de la classe Point munie de constructeurs
#include "Point.h"
using namespace std;
void main()
{
// utilisation du constructeur Point::Point(const unsigned,const unsigned)
Point p1(10,20);
// p1 est initialis laide du constructeur 2 arguments
// ltat de p1 est (10,20)
// utilisation du constructeur Point::Point()
Point p2;
// p2 est initialis avec le constructeur sans argument
// ltat de p2 est (0,0)
p1.Display(cout);
p2.Display(cout);

// affiche (10,20)
// affiche (0,0)

}
---------------------------------------------------------------------------------

De mme, le C++ prvoit un mcanisme pour raliser des oprations juste avant la mort dun objet
(par exemple, la dsallocation de mmoire) . Il sagit du destructeur de la classe. On reviendra sur ce point
ultrieurement.
18

4.4.

Diffrence entre slecteurs/accesseurs et modificateurs.

Parmi les mthodes dune classe, on distingue deux catgories : les mthodes qui modifient ltat de
lobjet, mthodes appeles modificateurs, et les mthodes qui ne modifient pas ltat de lobjet mais qui y
accdent en lecture. Ces dernires sont appeles accesseurs (celles qui utilisent ltat en lecture) ou slecteurs (celles
qui retournent ltat de lobjet). Il est important de dterminer dans quelle catgorie se situe une mthode car
son prototype en dpend. Les slecteurs/les accesseurs sont des mthodes dclares constantes (il y a un const
aprs la liste des arguments). Les modificateurs ont frquemment un nom tel que Set() et les slecteurs
Get().
Pour la classe Point ,
les modificateurs sont SetX(), SetY(), SetXY(),
- les slecteurs sont GetX(), GetY() ,
les accesseurs sont Display(), Distance().

Translate()

Remarque : ni laffichage ni le calcul de distance naffecte ltat de lobjet sur lequel la mthode
sapplique.
class Point
{
public:
// modificateurs
void SetX(const unsigned x);
void SetY(const unsigned y);
void SetXY(const unsigned x,const unsigned y);
void Translate(const unsigned x,const unsigned y);
// slecteurs
unsigned GetX() const;
unsigned GetY() const;
// accesseurs
float Distance(const Point & p) const;
void Display(std::ostream & flot) const;
};

4.5.

Constructeur de copie, oprateur daffectation

Parmi les constructeurs, le constructeur qui a un seul paramtre du mme type que la classe est
appel constructeur de copie. Il sert crer un nouvel objet ayant le mme tat quun objet de la mme classe.
--------------------------------------------------------------------------------// point.h : dfinition de la classe
#ifndef __POINT__
#define __POINT__
#include<iostream>
class Point
{
public:
Point(const Point & p);
// prototype du constructeur de copie
Point & operator=(const Point & p); // prototype de loprateur =
...
};
#endif
---------------------------------------------------------------------------------

19

--------------------------------------------------------------------------------// point.cpp :
#include "Point.h"
Point::Point(const Point & p)
{
// recopie de ltat de p dans lobjet courant
_x=p._x;
_y=p._y;
}
Point & Point::operator=(const Point & p)
{
// operateur = pour laffectation de ltat dun Point
_x=p._x;
_y=p._y;
return (*this);
// retourne lobjet courant.
}
...
---------------------------------------------------------------------------------

Dans le langage C++, laffectation entre variables ou objets de mme type est habituellement ralise
par loprateur =. Le langage C++ autorise la programmation des oprateurs agissant sur des types utilisateur
(voir section suivante). On peut donc programmer le comportement de loprateur = portant sur des objets
dune classe. En C++, un oprateur est simplement trait comme une fonction particulire dont le nom de
fonction comporte le mot rserv operator . Loprateur daffectation est donc simplement une mthode de
la classe Point.
--------------------------------------------------------------------------------// mise en vidence de lutilisation du constructeur de copie et de loprateur =
#include "Point.h"
using namespace std;
void main()
{
Point p1(10,20);
Point p3;
Point p2(p1);

p3=p1;

// p2 est cr par copie de p1


// ltat de p2 est (10,20)
// quivaut p3.operator=(p1);

p3.Display(cout) ;
// affiche (10,20)
}
---------------------------------------------------------------------------------

4.5.1.

Le pointeur this

Dans la dfinition de loprateur =, on voit lutilisation dun membre appel this. Le membre this
dun objet est du type pointeur sur un objet de la classe. Pour la classe Point , this est donc un membre du
type Point * . Ce membre contient ladresse de lobjet courant. Autrement dit, tout objet peut connatre sa
propre adresse. Logiquement, la notation (*this) reprsente lobjet courant, cest--dire lobjet sur lequel la
mthode a t appele.
Point & Point::operator=(const Point & p){
_x=p._x;
_y=p._y;
return (*this);
// retourne lobjet courant.
}

20

En rsum, loprateur = retourne lobjet sur lequel il sapplique. A quoi cela sert-il ? A garder
lhomognit avec le C++ qui autorise les affectations successives telles que ci-dessous
void main(){
Point p1(10,20), p2,p3;
p3=p2=p1;
/* quivaut p3.operator=(p2.operator=(p1)); */
}

Ci-dessus, p2=p1 quivaut p2.operator=(p1) et cette mthode retourne p2. Cest donc p2 (aprs
affectation) qui est affect p3.

5.

Les oprateurs : fonctions globales ou mthodes d'une classe

Il est possible de surcharger la plupart des oprateurs. Cela signifie quon va pouvoir dcrire quels
traitements les oprateurs doivent raliser. Cest notamment le cas pour loprateur = permettant
laffectation entre objets dune mme classe (voir section prcdente). Cette surcharge nest toutefois possible
que sur les types crs par le programmeur : il nest pas possible de redfinir les oprateurs agissant sur les
types lmentaires tels que int, float , etc.
5.1.

La pluralit des oprateurs du C++

Selon le nombre doprandes dont loprateur a besoin, loprateur sera qualifi doprateur unaire
(1 seul oprande) ou doprateur binaire (2 oprandes). Par exemple, loprateur = est un oprateur
binaire, la syntaxe dutilisation de cet oprateur tant Op1 = Op2 . L oprateur ++ en revanche est un
oprateur unaire. En effet, il sapplique un seul oprande : Op1++ .
Remarque : il y a en fait deux oprateurs ++ . Celui qui pr-incrmente et qui sutilise ++Op1 et
celui qui post-incrmente et qui sutilise Op1++ . Idem pour les oprateurs de dcrment -- .
Enfin, de mme quavec loprateur ++ o le mme signe peut reprsenter deux oprateurs
diffrents (la syntaxe dutilisation permet au compilateur de les distinguer), certains oprateurs peuvent avoir
une version unaire et une version binaire. Cest le cas par exemple de loprateur - .
#include<iostream>
using namespace std;
void main()
{
int a=4, b=5;
cout << (a-b) << endl;
cout << -a;
}

// oprateur - binaire
// oprateur - unaire

La syntaxe a-b utilise loprateur binaire (soustraction) tandis que a utilise loprateur unaire
(oppos).
5.2.

Les oprateurs avec traitement par dfaut

Lorsque lon dfinit de nouveaux types, par exemple des types structurs, certains oprateurs ralisent un
traitement par dfaut. Cest le cas de loprateur = et de loprateur & .
Le traitement par dfaut de loprateur = Lorsque lon dfinit un nouveau type structur (cest le cas des
classes), le traitement ralis par dfaut pour loprateur = est une copie membre membre. Cette
caractristique sappliquait dj en langage C sur les types structurs.
Le traitement par dfaut de loprateur & Lorsque lon dfinit un nouveau type, loprateur & (oprateur
unaire) retourne ladresse de lobjet auquel il sapplique.
5.3.

Les oprateurs que lon peut surcharger en C++


Les diffrents oprateurs que lon peut surcharger en C++ sont les suivants :
21

Unaire
Binaire
Binaire
Binaire

++

&

&&

+=

-=

-+

*=

~
<<

&

new

>>

<

>

new[]
<=

>=

delete
==

!=

>>=

(cast)

||
/=

%=

&=

^=

|=

<<=

Les oprateurs membres dune classe On verra ci-aprs que la dfinition des oprateurs passe simplement
par lcriture de fonctions ayant un nom particulier comportant le mot cl operator suivi du signe de
loprateur. En outre, le programmeur aura le choix entre la possibilit de dfinir ces oprateurs comme
fonctions membres dune classe ou comme fonctions non membres. Nanmoins (le C++ est un langage
truff dexceptions!) certains oprateurs ne peuvent tre dfinis que sils appartiennent une classe. Il sagit
alors ncessairement de mthodes dune classe. Cest le cas pour loprateur = vu au chapitre prcdent.
Doivent tre imprativement dfinis comme oprateurs membres dune classe les oprateurs
suivants :
=

(oprateur d'affectation)

( )
[ ]
->

(oprateur fonction)
(oprateur dindexation)

Remarque : rien noblige a priori le programmeur a faire de loprateur = un oprateur daffectation, mais
cest tout de mme recommand. Sans quoi, la classe devient trs difficile comprendre par lutilisateur !
5.4.

Le mcanisme de dfinition des oprateurs en C++

Lorsquun oprateur est utilis sur un type dfini par le programmeur (une classe ou un type
structur), lemploi de cet oprateur est quivalent lappel dune fonction particulire qui peut tre hors
classe ou membre dune classe (ou d'une structure). Si lon dfinit une nouvelle classe CLS, et deux objets x et
y de la classe CLS, alors la syntaxe (1) peut tre quivalente (2) ou (3) pour le compilateur C++ :
(1)
(2)
(3)

x+y;
operator+(x,y);
x.operator+(y);

// oprateur non membre de la classe CLS


// oprateur membre de la classe CLS

Autrement dit, les oprateurs sont vus comme des fonctions avec des identifiants particuliers :
suivi du signe de loprateur. Dans le cas prcdent, si lon souhaite que loprateur + ralise un
traitement particulier sur les variables de type CLS il suffit de dfinir une fonction (hors classe) appele
operator+(CLS op1,CLS op2) qui accepte deux arguments de type CLS (ou des rfrences CLS ) ou bien
dajouter une mthode CLS::operator+(CLS op2) la classe CLS . On retrouve bien, dans les deux cas, les
deux oprandes de l'oprateur +. Dans le premier cas, les deux oprandes sont les paramtres de la fonction.
Dans le second cas, le premier oprande est lobjet sur lequel la fonction membre oprateur est appele et le
second le paramtre de la fonction membre.
operator

5.5.

Exemple : classe de nombres rationnels

Les oprateurs sont dun usage particulirement intressant pour les objets mathmatiques. On
fournit ici une bauche dune classe de nombre rationnels mettant en vidence l'intrt des oprateurs.
Les objets de la classe Rationnel sont simplement des fractions rationnelles o le numrateur et le
dnominateur sont des entiers. On pourra ainsi manipuler des fractions du type 2/3, 27/11 sans erreurs
d'arrondis, ou mme des entiers (le dnominateur doit alors tre gal 1). Il faut noter qu'aucun objet de
cette classe ne doit avoir de dnominateur nul (on teste donc cette ventualit au niveau du constructeur
22

deux arguments). L'exemple donn ici illustre les deux faons de dfinir un oprateur (fonction membre ou
hors classe).
--------------------------------------------------------------------------------rationnel.h : fichier dentte de la classe Rationnel
#ifndef __RATIONNEL__
#define __RATIONNEL__
#include<iostream>
class Rationnel
{
public:
Rationnel(int num=0, int den=1);
Rationnel(const Rationnel & r);
int GetNum() const;
int GetDen() const;
// oprateurs membres de la classe
Rationnel & operator=(const Rationnel & r);
Rationnel operator+(const Rationnel & r) const;
private:
int _num;
int _den;
};
// prototypes des oprateurs hors classe
std::ostream & operator<<(std::ostream & flot, const Rationnel & r);
Rationnel operator*(const Rationnel & r1, const Rationnel & r2);
#endif
----------------------------------------------------------------------------------------------------------------------------------------------------------------// Rationnel.cpp : implmentation de la classe
#include "Rationnel.h"
// Constructeurs
Rationnel::Rationnel(int num,int den){
if(den==0) _den=1;
// viter le dnominateur nul
if(den<0){
// placer le signe au numrateur
_den=-den;
_num=-num;
}
}
Rationnel::Rationnel(const Rationnel & r)
{
_den=r._den;
_num=r._num;
}

// constructeur de copie

// Slecteurs
int Rationnel::GetNum() const {

return _num; }

int Rationnel::GetDen() const {

return _den; }

// oprateur pour laffichage dun rationnel (non membre)


std::ostream & operator<<(std::ostream & flot, const Rationnel & r)
{
flot << r.GetNum() << "/" << r.GetDen();
return flot;
}

23

// oprateur daffectation (obligatoirement membre)


Rationnel & Rationnel::operator=(const Rationnel & r)
{
_num=r._num;
_den = r._den;
return *this;
}
// oprateur pour la somme de nombres rationnels (membre)
Rationnel Rationnel::operator+(const Rationnel & r) const
{
Rationnel resultat(_num*r._den+r._num*_den,_den*r._den);
return resultat;
}
// oprateur pour le produit de nombres rationnels (hors classe )
Rationnel operator*(const Rationnel & r1,const Rationnel & r2) const
{
// Attention, cet oprateur n'a pas accs aux membres privs !
Rationnel resultat(r1.GetNum()*r2.GetNum(),r1.GetDen()*r2.GetDen());
return resultat;
}
----------------------------------------------------------------------------------------------------------------------------------------------------------------// utilisation de la classe Rationnel
#include "Rationnel.h"
using namespace std;
void main()
{
Rationnel r1(2,3),r2(4,5);
cout << " r1=" << r1;
cout << " r2=" << r2 << endl;
Rationnel r;
r=r1+r2;
// equivaut r.operator=(r1.operator+(r2))
cout << "r1+r2=" << r << endl;
r=r1*r2; // equivaut r.opertator=(operator*(r1,r2))
cout << "r1*r2=" << r;
}
---------------------------------------------------------------------------------

Dans lexemple prcdent il faut accorder une attention particulire aux prototypes. Il faut par exemple
remarquer que la somme ne modifie ni lobjet courant (mthode constante) ni lobjet argument (rfrence
constante). En outre, loprateur retourne un objet (diffrent de lobjet courant et de lobjet argument) de
type rationnel correspondant au rsultat de la somme.

6. L'utilisation de classes paramtres en type (introduction la bibliothque STL)


6.1.

Introduction

Le C++ introduit la notion de fonctions et de classes paramtres en type. Cela signifie simplement
quau sein dune classe ou dune fonction, certains types peuvent tre passs en paramtre. Le mot cl
template est introduit en C++ pour pouvoir paramtrer des types. Dans les manuels de C++, on trouvera

24

diffrentes appellations pour le paramtrage de type : on parlera de classes paramtres, de patrons de classes
ou de template-classes, voire de modle de classes.
Une bibliothque standard de classes paramtres en type, libre dutilisation, est fournie avec la
plupart des compilateurs C++. Il sagit de la bibliothque STL (Standard Template Library). Elle regroupe
diffrentes classes paramtres et fonctions paramtres. On y retrouve des classes conteneur paramtres et
des algorithmes paramtrs (recherche de valeurs dans un conteneur, tris de conteneur )
Notion de conteneur
Un conteneur est simplement un objet capable de stocker des donnes avec la possibilit de changer
dynamiquement sa taille. Les conteneurs de la STL reprennent les structures de stockage de donnes les plus
connues, savoir
-

le conteneur vector : tableau unidimensionnel


le conteneur list : liste doublement chane
le conteneur stack :pile (structure LIFO)
le conteneur queue :file (structure FIFO)
le conteneur deque : structure de donnes hybride
le conteneur set : ensemble
le conteneur map : tableau associatif

En plus de toutes ces structures de donnes, la bibliothque STL fournit une classe string de gestion
des chanes de caractres. Le programmeur C++ tire bien des bnfices utiliser les objets string la place
des chanes de caractres habituelles (la mmoire est alloue par les objets) et utiliser des objets vector la
place des tableaux statiques ou mmes dynamiques.
Les primitives daccs, dajout, de suppression diffrent bien videmment entre ces conteneurs. Par
exemple, laccs un lment dun objet vector est un accs direct (indpendant de la taille du vector ) alors
que laccs un lment dune liste chane est squentiel : on doit parcourir la liste pour retrouver un
lment. Par consquent, certaines oprations sont plus efficaces sur certains conteneurs que sur dautres. Ce
sont ces caractristiques qui guident le programmeur dans ses choix. Par exemple, on utilisera une liste
chane plutt quun tableau si lon ajoute/retire souvent des lments aux extrmits (en dbut ou en fin)
puisque ces oprations sont plus efficaces sur une liste.
Tous les conteneurs STL sont paramtrs en type. Cela signifie quon peut choisir le type des
donnes stockes dans le conteneur. On note vector<T> la classe obtenue partir du conteneur vector pour
le type T. Noter que vector<int> est une classe et vector<float> en est une autre.
-------------------------------------------------------------------------------#include<iostream>
// entte pour les classe istream et ostream
#include<vector>
// entte pour le conteneur vector
using namespace std;

// la bibliothque STL utilise le namespace std

void main(void)
{
vector<int> tab1(4);
// objet de la classe vector<int>
vector<float> tab2(2);
// objet de la classe vector<float>
vector<vector<int> > tab3(2); //objet de la classe vector<vector<int> >
tab1[1]=3;
tab2[0]=3.7;

// les lments stocks dans tab1 sont des entiers


// les lments stocks dans tab2 sont des float

25

tab3[0]=tab1; // tab3 stocke des tableaux dentiers (vector<int>)


// affichage des lments de tab1
for(unsigned i=0;i<tab1.size();i++)
{
cout << tab1[i] << " ";
}
}
--------------------------------------------------------------------------------

Ci-dessus tab1 est un objet de la classe vector<int>. La classe vector<int> est une classe de tableaux
dont les lments stocks sont des entiers. Lobjet tab1 est donc un tableau dentiers. La taille initiale du
tableau est passe au constructeur de lobjet. Lobjet tab1 peut donc stocker 4 entiers, tab2 peut stocker 2
float et tab3 peut stocker 2 objets de la classe vector<int> . On peut donc copier tab1 dans une case du
tableau tab3 (tab3 est un tableau de tableaux).
Remarquer que laccs une case dun objet vector<T> se fait simplement avec loprateur [ ] ,
comme sur un tableau classique en C. Mais la classe vector<T> dispose de bien dautres mthodes utilisables
pour changer la taille, insrer des lments, supprimer des lments. Par exemple, on voit ci-dessus
lutilisation de la mthode vector<T>::size() sur lobjet tab1 .
Lobjectif nest pas de dtailler ici toutes les possibilits dutilisation des classes paramtres de la
bibliothque STL, mais de prsenter simplement quelques caractristiques des classes string, vector , et list.
Ces classes paramtres vont fournir des briques intressantes pour crer des classes plus complexes dans les
chapitres suivants.
6.2.

Namespace

Avant daller plus loin dans la prsentation de la STL, il semble temps dexpliquer ce que reprsente
un namespace (espace de noms). Diffrentes classes ou fonctions peuvent tre regroupes dans un espace de
noms particulier pour viter certains conflits didentifiant. Ds lors, pour utiliser une classe dun espace de
noms particulier, on doit faire prcder le nom de la classe du nom de lespace de noms. On peut aussi
prciser un namespace par dfaut pour le programme via la directive using namespace.
Dans lexemple qui suit, il ny a pas de conflit de nom puisque les deux fonctions sont dfinies dans
deux espaces de noms distincts (les namespaces NA et NB). On voit aussi que le flot cout dpend du
namespace std.
--------------------------------------------------------------------------------#include <iostream>
namespace NA{
void MaFonction()
{
std::cout << "Fonction du namespace NA \n";
}
}
namespace NB{
void MaFonction()
{
std::cout << "Fonction du namespace NB \n";
}
}

26

using namespace NA;

// utilise le namespace NA par dfaut

void main(void)
{
NA::MaFonction();
NB::MaFonction();

// fonction du namespace NA
// fonction du namespace NB

MaFonction();
// fonction du namespace par dfaut
}
---------------------------------------------------------------------------------

Toutes les classes de la STL appartiennent au namespace std. Il convient donc de prciser ce
pour lutilisation de ces classes. Ceci explique certaines parties de programme non
comprhensibles jusque l.
namespace

Remarque : il est noter que lon ne peut pas utiliser la directive using
dentte lors des dfinitions de classes.
6.3.

namespace

dans les fichiers

Classe string

La classe string nest pas une classe paramtre. Elle sert uniquement grer des chanes de caractre
de faon un peu plus souple quen langage C. Voici un premier exemple.
--------------------------------------------------------------------------------#include<string>
// pour la classe string
#include<iostream>
using namespace std;
void main(void)
{
string s1("Une chaine"),s2(" Toto");
string s3(s1);
string s4;
s4=s1+s2;

// + : concatnation

= : affectation

cout << s3 << endl;


// insertion possible dans cout
cout << s3[0] << endl;// utilisable comme un tableau : s3[i] de type char
if(s1==s3) cout << "Chaines identiques\n";
if(s1<=s4) cout << "s1<=s4 selon l'ordre lexicographique\n";
}
---------------------------------------------------------------------------------

Lexemple suivant illustre les faits suivants : la classe contient


- un constructeur sans argument pour crer une chane vide
- un constructeur avec un argument de type char * pour assurer la compatibilit avec le C
- un constructeur de copie pour crer une chane identique une chane existante
- des oprateurs de concatnation : + et +=
- un oprateur daffectation : =
- un oprateur dindexation [ ] de sorte quun objet string se comporte aussi comme un tableau
- les oprateurs de test : == !=
- les oprateurs de comparaison selon lordre lexicographique (lordre des mots dans un
dictionnaire) : <= < >= >
27

La classe dispose galement de diffrentes mthodes (prsentation non exhaustive)


--------------------------------------------------------------------------------void main(void)
{
string s1("abcdefg");
cout << "Longueur = " << s1.length() << endl;
cout << "Capacite = " << s1.capacity() << endl;
cout << s1 << endl;
// s1 = abcdefg
s1.insert(1,"ABC");
cout << s1 << endl;

// insre la chane "ABC" l'indice 1


// s1 = aABCbcdefg

s1.insert(2,4,'a');
cout << s1 << endl;

// insre 4 fois la lettre 'a' l'indice 2


// s1 = aAaaaaBCbcdefg

s1.erase(0,1);
cout << s1 << endl;

// supprime 1 caractre l'indice 0


// s1 = AaaaaBCbcdefg

s1.erase(2,5);
cout << s1 << endl;

// supprime 5 caractres l'indice 2


// s1 = Aabcdefg

// possibilit d'obtenir un pointeur compatible const char *


const char * ptr=s1.c_str();
}
---------------------------------------------------------------------------------

6.4.

Conteneurs vector<T>

La classe paramtre vector<T> permet de gnrer des objets tableaux pouvant stocker des objets de
nimporte quel type. On rappelle que vector<int> est une classe (le paramtre de type vaut ici int ) et
vector<float> en est une autre. Par consquent, ces types ne sont pas compatibles. Le premier exemple
suivant met en vidence les principales fonctionnalits de ce patron de classes de tableaux.
--------------------------------------------------------------------------------#include<iostream>
#include<vector>
using namespace std;
void main(void)
{
vector<int> t1(4,-2),t2(5);
vector<float> t3(2),t4(4,1.2);
t1[0]=5;
t4[2]=-2.3;

// t1=-2,2,2,-2
// t3=0,0

t2=0,0,0,0,0
t4=1.2,1.2,1.2

//t1=5,2,2,-2
//t4=1.2,1.2,-2.3

t2=t1; // possible car t1 et t2 de la mme classe


t3=t4; // possible car t3 et t4 de la mme classe
// t1=t4; impossible car t1 et t4 de types diffrents
vector<int> t5(t1);
// t5=5,2,2,-2
vector<float> t6(t3); // t6=1.2,1.2,-2.3
//vector<float> t7(t1); impossible car t1 nest pas de la classe vector<float>
}
---------------------------------------------------------------------------------

28

La classe dispose dun constructeur un argument (la taille initiale du vecteur), dun constructeur
deux arguments (taille et valeurs initiales du vecteur). La classe dispose aussi dun constructeur de copie.
Attention, les copies ne sont possibles quentre objets de mme classe ! La classe dispose dun oprateur
daffectation. L encore, laffectation nest possible quentre objets de mme classe. Enfin, puisque cette classe
implmente le fonctionnement dun tableau, loprateur dindexation [ ] donne accs aux lments du
tableau (comme pour un tableau classique).
Les itrateurs
Dans le but dhomogniser les interfaces des classes de la STL, les classes conteneur implmentent la
notion ditrateur qui gnralise la notion de pointeur. Un itrateur est simplement un objet qui pointe un
emplacement dun conteneur (un peu comme un pointeur). En outre, les oprateurs ++ et - sont
surchargs sur les itrateurs pour passer lemplacement suivant ou prcdent du conteneur (quil sagisse
dun tableau ou dune liste chane). Loprateur * permet lindirection sur un itrateur (comme sur un
pointeur). Grce aux itrateurs, les algorithmes sappliquant aux conteneurs sont moins dpendants de leur
structure interne.
Les classes conteneurs contiennent des classes membres publiques pour instancier des itrateurs
adapts. Il y a deux classes ditrateurs par conteneur : les itrateurs constants et les itrateurs non constants.
Les premiers permettent de parcourir un conteneur sans pouvoir en modifier le contenu.
--------------------------------------------------------------------------------void main(void)
{
vector<int> t1(4);
vector<int>::iterator it;

// it est un iterateur sur vector<int>

for(it=t1.begin();it!=t1.end();it++) (*it)=2;
for(unsigned i=0;i<t1.size();i++) t1[i]=2;
}
---------------------------------------------------------------------------------

Les deux boucles for prcdentes ralisent le mme traitement, lune grce aux itrateurs, lautre
grce loprateur []. Les mthodes begin() et end() retournent des itrateurs. La premire mthode
retourne un itrateur sur le premier lment du conteneur. La seconde pointe juste aprs le dernier lment.
Lutilisation des conteneurs est donc un peu droutante puisque des itrateurs sont utiliss comme
arguments de certaines mthodes dinsertion ou de suppression. Il faut donc avoir une connaissance
minimale sur leur utilisation pour pouvoir exploiter la bibliothque STL. Nanmoins, lutilisation des
itrateurs est la mme pour les autres conteneurs. En consquence, dans la bibliothque STL, lutilisation
dune liste chane nest pas plus complique que celle dun vecteur. Lexemple suivant illustre les capacits
dinsertion et de suppression dans un vecteur (voire dans un conteneur diffrent)
--------------------------------------------------------------------------------void main(void)
{
vector<int> t1(3,2); //t1=2,2,2
cout << "Taille = "<< t1.size() << endl;
// Taille=3
t1.push_back(3);
t1.resize(6,-2);

//t1=2,2,2,3
//t1=2,2,2,3,-2,-2

29

t1.insert(t1.begin(),2,-3); //t1=-3,-3,2,2,2,3,-2,-2
t1.insert(t1.begin()+3,-1); //t1=-3,-3,2,-1,2,2,3,-2,-2
t1.insert(&t1[0],-6); //t1=-6,-3,-3,2,-1,2,2,3,-2,-2
t1.erase(t1.begin()+2,t1.begin()+4); //t1=-6,-3,-1,2,2,3,-2,-2
t1.erase(t1.begin()+3); //t1=-6,-3,-1,2,3,-2,-2
t1.pop_back(); //t1=-6,-3,-1,2,3,-2
}
---------------------------------------------------------------------------------

Ajout/retrait en fin = push_back et pop_back


Ajout/retrait en tte = push_front et pop_front
Premier/dernier lment : front et back
Changement de dimension : resize (nouvelle taille, valeur des cases complmentaires ).
Insertion : plusieurs mthodes.
insert (itrateur, nombre dajouts, valeur insre) : plusieurs cases insres
insert (itrateur, valeur insre) : une seule valeur insre
Remarquer quun pointeur sur entier peut tre converti en itrateur sur entier.
Suppression : plusieurs mthodes.
erase (itrateur debut,iterateur fin) : supprime les cases entre ces itrateurs
erase (itrateur) : supprime la case pointe par litrateur
6.5.

Conteneurs list<T>

Les autres conteneurs ont de nombreux points communs avec ce quon a dj vu. Cest pourquoi un
exemple devrait suffire comprendre lutilisation des listes chanes. Hormis le fait que loprateur [ ] nest
videmment pas dfini pour les listes chanes.
--------------------------------------------------------------------------------void main(void)
{
list<int> l1;
list<float> l2,l4;

//liste vide d'entiers


// liste vide de flottants

l1.push_front(12);
l1.push_back(13);
l1.push_front(-3);
l1.push_front(7);

//
//
//
//

ajout en tte
ajout en fin
ajout en fin
l1=7,-3,12,13

l2.push_back(2.3);
l2.push_back(2.7);
l2.push_back(-1.2);

//l2=2.3,2.7,-1.2

list<int> l3(l1);
//l3=7,-3,12,13
cout << "Taille = "<< l3.size() << endl;

30

l4=l2; //l4=2.3,2.7,-1.2
l1.insert(l1.begin(),2,-3); //l1=-3,-3,7,-3,12,13
cout << l1.front() << l1.back() << endl; // -3 13
list<int>::iterator it=l1.begin();
while(it!=l1.end()){
if((*it)==-3) // suppression des noeuds valant -3
{
it=l1.erase(it);
}
else it++;
}
// affichage de la liste l1
for(it=l1.begin();it!=l1.end();it++) cout << (*it) << " ";
}
---------------------------------------------------------------------------------

7. Ralisation de la composition en C++


Nous avons vu jusquici comment utiliser les types primit ifs pour crire des classes et comment
utiliser des classes standard (en particulier la classe string et les classes paramtres vector<T> et list<T>).
Trs souvent, les classes vont galement contenir comme attributs des objets dune ou de plusieurs autres
classes. Autrement dit, certains objets vont tre composs dobjets dautres classes. On peut par exemple, voir
un objet de type voiture comme la composition (entre autres) dun objet moteur, dun objet carrosserie, de 4
objets roues
La ralisation la plus simple (mais pas ncessairement la seule en C++) de la composition est de faire
apparatre les objets composants comme attributs de lobjet composite.
7.1.

Reprsentation UML de la composition

Dans un premier temps, on va dcrire le reprsentation UML de la composition. On peut dcrire la


composition de deux manires :
- soit les objets composants apparaissent comme attributs des objets composites
- soit une relation de composition (lien avec losange ct composite) lie les classes composant et
composite.
Point

Nous allons considrer deux exemples


pour illustrer cette notion.

+Point(in x : Rationnel, in y : Rationnel)


+GetX() : Rationnel
+GetY() : Rationnel
+SetX(in x : Rationnel)
+SetY(in y : Rationnel)
Point

Classe Point : dans la premire partie, un


point du plan a t reprsent par des
coordonnes entires. De manire un peu
diffrente, on peut aussi reprsenter des points
coordonnes rationnelles (voir la section sur
les oprateurs).

-_x : Rationnel
-_y : Rationnel
+Point(in x : Rationnel, in y : Rationnel)
+GetX() : Rationnel
+GetY() : Rationnel
+SetX(in x : Rationnel)
+SetY(in y : Rationnel)

1
2

-coordonnes

Rationnel
-_numerateur : int
-_denominateur : int
+Rationnel(in num : int, in den : int)
+FormeIrreductible() : Rationnel
+GetNum() : int
+GetDen() : int
+SetNum(in num : int)
+SetDen(in den : int)
+operator+(in r : Rationnel) : Rationnel
+operator-(in r : Rationnel) : Rationnel
+operator*(in r : Rationnel) : Rationnel
+operator/(in r : Rationnel) : Rationnel

Un point peut alors tre vu comme la composition de deux coordonnes de type Rationnel (classe
Rationnel).
31

En notation UML, les coordonnes sont soit reprsentes comme des attributs de type Rationnel dans la
classe Point (reprsentation de gauche) soit par la relation de composition (reprsentation de droite). Dans
le deuxime cas, la relation de composition est reprsente par un losange ct composite.
Classe Segment : on peut dcrire un segment par ses extrmits (objets de la classe Point ). L encore, on
pourra avoir une reprsentation plus ou moins clate selon que lon reprsente la composition comme
attribut ou par une association particulire. Les trois reprsentations UML suivantes reprsentent la mme
classe Segment dont les extrmits sont des points coordonnes rationnelles.
Segment
-_A : Point
-_B : Point
+GetA() : Point
+GetB() : Point
+GetDimension() : Rationnel

Segment
+GetA() : Point
+GetB() : Point
+GetDimension() : Rationnel
1
2

-extrmitsAB

Segment
Point
+GetA() : Point
+GetB() : Point
+GetDimension() : Rationnel
1
2

-extrmitsAB

-_x : Rationnel
-_y : Rationnel
+Point(in x : Rationnel, in y : Rationnel)
+GetX() : Rationnel
+GetY() : Rationnel
+SetX(in x : Rationnel)
+SetY(in y : Rationnel)

Point
-_x : Rationnel
-_y : Rationnel
+Point(in x : Rationnel, in y : Rationnel)
+GetX() : Rationnel
+GetY() : Rationnel
+SetX(in x : Rationnel)
+SetY(in y : Rationnel)

7.2.

-coordonnes

Rationnel
1

-_numerateur : int
-_denominateur : int
+Rationnel(in num : int, in den : int)
+FormeIrreductible() : Rationnel
+GetNum() : int
+GetDen() : int
+SetNum(in num : int)
+SetDen(in den : int)
+operator+(in r : Rationnel) : Rationnel
+operator-(in r : Rationnel) : Rationnel
+operator*(in r : Rationnel) : Rationnel
+operator/(in r : Rationnel) : Rationnel

Objets composants comme attributs dune classe

La ralisation la plus naturelle de la composition en C++ est de faire apparatre les objets
composants comme des attributs de la classe composite. Sur le plan technique, seule lcriture du
constructeur introduit une notation particulire. Prenons lexemple de la classe Point pour laquelle les
coordonnes sont de type Rationnel (classe dfinie dans la section 5).
Seule la dclaration de la classe composant est utile pour lcriture de la classe composite. On a
seulement besoin de connatre les mthodes membre (notamment les constructeurs disponibles)
Rappelons tout dabord le fichier dentte de la classe composant puisque celui-ci contient toute
linformation ncessaire la rutilisation de la classe Rationnel.
--------------------------------------------------------------------------------// rationnel.h : entte de la classe Rationnel (classe composant)
#ifndef _RATIONNEL__
#define _RATIONNEL__
class Rationnel
{
public:
Rationnel(long num=0,long den=1);
Rationnel(const Rationnel &);
Rationnel & operator=(const Rationnel & );
long GetDen() const;
long GetNum() const;

32

Rationnel FormeIrreductible();
void Affiche(std::ostream & ) const;
Rationnel
Rationnel
Rationnel
Rationnel

operator+(const
operator*(const
operator-(const
operator/(const

Rationnel
Rationnel
Rationnel
Rationnel

&
&
&
&

r)
r)
r)
r)

const;
const;
const;
const;

private:
long pgcd(long j,long k);
long _num;
long _den;
};
#endif
---------------------------------------------------------------------------------

Ensuite, la classe Point qui est une classe composite se dfinit de la manire suivante :
--------------------------------------------------------------------------------//point.h : entte de la classe point (classe composite)
#ifndef _POINTRAT__
#define _POINTRAT__
#include "rationnel.h"
// inclusion de lentte de la classe composant
class Point
{
public:
Point();
Point(const Rationnel & x, const Rationnel & y);
Point(const Point & p);
Rationnel GetX() const;
Rationnel GetY() const;
private:
Rationnel _x;
// objets membres de type Rationnel
Rationnel _y;
};
#endif
----------------------------------------------------------------------------------------------------------------------------------------------------------------//point.cpp (implmentation des mthodes de la classe Point)
#include "point.h"
Point::Point():_x(0,1),_y(0,1)
{
}
Point::Point(const Rationnel & x, const Rationnel & y):_x(x),_y(y)
{
}
Point::Point(const Point &p):_x(p._x),_y(p._y)
{
}
Rationnel Point::GetX() const {

return _x; }

Rationnel Point::GetY() const {


return _y; }
---------------------------------------------------------------------------------

33

Liste dinitialisation (en gras dans limplmentation de la classe Point ) : dans le fichier source de la classe
Point , seule lcriture des constructeurs introduit une nouvelle syntaxe. A la suite des arguments dun
constructeur, on peut mettre une liste dinitialisation des objets membres composants. Grce cette liste
dinitialisation, le compilateur sait quel constructeur de la classe composant doit tre invoqu pour
initialiser lobjet membre.
Constructeur sans argument de la classe Point : initialisation de _x et _y comme les rationnels 0/1. Indique
au compilateur que le constructeur deux arguments de la classe Rationnel doit tre utilis.
Constructeur de copie de la classe Point : la liste d'initialisation indique que les attributs _x et _y sont
initialiss grce aux attributs p._x et p._y de l'objet Point pass en argument. Autrement dit, c'est le
constructeur de copie de la classe Rationnel qui est invoqu.
7.3.

Utilisation d'attributs de type tableau d'objets

Une autre ralisation possible de la composition consiste mettre un attribut de type tableau
d'objets. Pour la classe Point , on peut voir les coordonnes comme un tableau d'objets Rationnel deux
cases. Cette autre ralisation est mise en uvre dans la classe PointTab ci-dessous.
--------------------------------------------------------------------------------// pointtab.h
#ifndef _POINTTAB__
#define _POINTTAB__
#include "rationnel.h"
class PointTab
{
public:
PointTab(const Rationnel & x, const Rationnel & y);
Rationnel GetX() const;
Rationnel GetY() const;
private:
Rationnel _XY[2];
// tableau de deux objets Rationnel
};
#endif
----------------------------------------------------------------------------------------------------------------------------------------------------------------//PointTab.cpp
#include "PointTab.h"
PointTab::PointTab(const Rationnel & x, const Rationnel & y)
{
_XY[0]=x;
_XY[1]=y;
}
Rationnel PointTab::GetX() const { return _XY[0]; }
Rationnel PointTab::GetY() const { return _XY[1];}
---------------------------------------------------------------------------------

Remarque : il est noter quici la liste dinitialisation ne permet pas dinitialiser le tableau dobjets. Aussi, il
est ncessaire quun constructeur sans argument existe dans la classe Rationnel .

34

7.4.

Utilisation d'un objet membre de type vector<T>

Une autre ralisation de la composition (o la multiplicit est suprieure 1) consiste utiliser objet
membre de type vector<Rationnel>. Ceci implique assez peu de changements par rapport la solution
prcdente. Seule l'initialisation de l'objet membre vector<Rationnel> doit faire l'objet d'une attention
particulire.
--------------------------------------------------------------------------------#ifndef _POINTTAB__
#define _POINTTAB__
#include "rationnel.h"
#include<vector>
class PointTab
{
public:
PointTab(const Rationnel & x, const Rationnel & y);
Rationnel GetX() const;
Rationnel GetY() const;
private:
std::vector<Rationnel> _XY;
};
#endif
---------------------------------------------------------------------------------

--------------------------------------------------------------------------------//PointTab.cpp
#include "PointTab.h"
// Attention ! Utiliser la liste d'initialisation pour initialiser le vecteur la taille
2
PointTab::PointTab(const Rationnel & x, const Rationnel & y):_XY(2)
{
_XY[0]=x;
_XY[1]=y;
}
Rationnel PointTab::GetX() const {

return _XY[0]; }

Rationnel PointTab::GetY() const { return _XY[1];}


---------------------------------------------------------------------------------

Remarque : une autre ralisation possible de la composition est propose dans la section traitant des classes
avec donnes en profondeur.

8. Ralisation de la spcialisation en C++


Le relation de spcialisation (ou de gnralisation) qui existe entre des classes est ralisable en C++.
La possibilit d'crire une sous-classe (ou classe spcialise) dune classe existante est prvue par ce langage, de
mme que pour tous les langages objet.
8.1.

Reprsentatio n de la spcialisation en UML

Le langage UML propose une notation (une flche pointant la super-classe) pour dcrire la relation
de spcialisation entre classes. La classe spcialise (ou sous-classe dun point de vue ensembliste) possde les
attributs et le comportement de la super-classe. On dit aussi que la sous-classe hrite de la super-classe. En
plus du comportement hrit, la sous-classe peut contenir des attributs et des mthodes spcifiques. La
spcialisation est reprsente par une flche en langage UML.
35

Exemple : une classe de ds 6 faces peut tre vue comme une spcialisation dune classe de ds n faces : il
suffit de fixer le nombre de faces.
PointQn
DeNFaces
-_valeur : unsigned int
-_nbFaces : unsigned int
+DeNFaces(in nbFaces : unsigned int = 6)
+Lancer()
+GetValeur() : unsigned int
+GetNombreDeFaces()() : unsigned int

De6
+De6()

-_coordonnees : vector<Rationnel>
+PointQn(in n : unsigned int)
+PointQn(in p : PointQn)
+operator=(in p : PointQn) : PointQn
+GetCoordonnee(in n : unsigned int) : Rationnel
+SetCoordonnee(in n : unsigned int, in x : Rationnel)

PointQ2

PointQ3

+PointQ2(in x : Rationnel, in y : Rationnel) +PointQ3(in x : Rationnel, in y : Rationnel, in z : Rationnel)

Exemple : on peut aussi voir un point du plan QxQ (lensemble des couples de nombres rat ionnels), classe
note PointQ2 , comme la spcialisation dun point n coordonnes (classe PointQn )
On retrouve dans la bibliographie C++ un certain vocabulaire associ cette notion de
spcialisation/gnralisation. Au sujet de la classification ci-dessus, on dira :
- la classe De6 est une spcialisation de la classe DeNFaces
- la classe De6 drive de la classe DeNFaces
- la sous-classe De6 hrite des fonctionnalits de la super-classe DeNFaces . En effet, un objet d'une
sous-classe dispose galement de l'interface de la super-classe.
- la classe DeNFaces est la super-classe de la classe De6
- la classe DeNFaces est la classe de base de la classe De6 .
8.2.

Exemple de ralisation de la spcialisation en C++

Nous donnons l'exemple de la classe drive De6. Nous rappelons d'abord le fichier header (fichier
dentte) de la classe DeNFaces car il est ncessaire de connatre l'interface de la super-classe pour crire la
classe drive. Ensuite nous donnons la dfinition de la classe drive (la sous-classe) ainsi que l'utilisation de
cette classe. Pour cet exemple, la classe drive n'a pas d'attribut supplmentaire. La classe drive se contente
de contraindre la valeur du nombre de faces. Un d 6 faces est un d N faces o _nbFaces vaut toujours
6.
Pour la classe spcialise, seul le constructeur sans argument est dfini pour initialiser correctement
le nombre de faces. Ensuite, toutes les mthodes de la classe DeNFaces peuvent tre utilises sans problme
sur la classe spcialise De6 .
--------------------------------------------------------------------------------// DeNFaces.h : entte dune classe de ds N faces
#ifndef __CLS_DENFACES__
#define __CLS_DENFACES__
class DeNFaces
{
public:
DeNFaces(unsigned nbFaces=6);
unsigned GetNombreDeFaces() const;
void Lancer();
unsigned GetValeur() const;
private:
unsigned _valeur;
// valeur du d un instant
unsigned _nbFaces;
// nombre de faces du d
};
#endif
---------------------------------------------------------------------------------

36

L'implmentation de la classe drive est ici trs simple puisque seul un constructeur est dfini.
Encore une fois, la liste d'initialisation (revoir la section prcdente) est utilise au niveau des constructeurs.
--------------------------------------------------------------------------------// De6.h : fichier dentte de la classe De6 (drive de DeNFaces)
#ifndef __CLS_DE6__
#define __CLS_DE6__
#include "DeNFaces.h"
class De6:public DeNFaces
// la classe De6 drive de DeNFaces
{
public:
De6();
// constructeur de la classe drive
};
#endif
----------------------------------------------------------------------------------------------------------------------------------------------------------------// De6.cpp :implmentation de la classe spcialise
#include "De6.h"
De6::De6():DeNFaces(6){ }
----------------------------------------------------------------------------------------------------------------------------------------------------------------// Utilisation.cpp : utilisation de la classe spcialise
#include "De6.h"
#include<iostream>
using namespace std;
void main(void)
{
DeNFaces de1(9);
// d 9 faces
De6 de2;
// d 6 faces
cout << de1.GetNombreDeFaces() << "\n";
cout << de2.GetNombreDeFaces() << "\n";
de1.Lancer();
de2.Lancer(); // appel de mthode hrite
cout << d1.GetValeur() << endl;
cout << d2.GetValeur() << endl; // appel de mthode hrite
}
---------------------------------------------------------------------------------

Notons que l'on peut encore poursuivre la hirarchie de classes


d'objets ds. On peut par exemple ajouter un attribut de couleur .
--------------------------------------------------------------------------------#ifndef __CLS_DE6COUL__
#define __CLS_DE6COUL__
#include "De6.h"
class De6Couleur:public De6
{
public:
De6Couleur(unsigned couleur);
unsigned GetCouleur() const;
private:
unsigned _couleur;
};
#endif
-------------------------------------------------------------

DeNFaces
-_valeur : unsigned int
-_nbFaces : unsigned int
+DeNFaces(in nbFaces : unsigned int = 6)
+Lancer()
+GetValeur() : unsigned int
+GetNombreDeFaces()() : unsigned int

De6
+De6()

De6Couleur
-_couleur : unsigned int
+De6Couleur(in couleur : unsigned int)
+GetCouleur() : unsigned int

37

--------------------------------------------------------------------------------// De6Couleur.cpp : implmentation de la classe de De6Couleur


#include "De6Couleur.h"
De6Couleur::De6Couleur(unsigned c):De6(),_couleur(c)
{
}
unsigned De6Couleur::GetCouleur() const
{
return _couleur;
}
---------------------------------------------------------------------------------

8.3.

Les conversions de type entre sous-classe et super-classe.

Une sous-classe reprsente un sous-ensemble d'objets de la classe plus gnrale. Si l'on se rfre la
hirarchie de classes de ds prcdente, on comprend facilement qu'un d 6 faces est avant tout un d N
faces. Aussi, le type De6 peut tre converti en type DeNFaces .
En C++, les conversions de type entre sous-classe et super classe sont lgales. Par exemple, on peut
convertir un objet De6 en objet DeNFaces . On peut de la mme manire convertir un objet de la classe
De6Couleur en un objet de la classe DeNFaces . Nanmoins, ces conversions sont dgradantes (perte de
donnes), comme lorsque l'on convertit un float en int. Naturellement, lorsque lon convertit un d color
en d (sans couleur) on perd linformation de couleur. En C++, les conversions sous-classe vers super-classe
sont possibles, mais elles perdent la partie dite incrmentale cest--dire la partie spcifique dune classe
drive. Lexemple suivant prsente quelques transtypages possibles.
--------------------------------------------------------------------------------#include "De6Couleur.h"
#define rouge 5
void main(void)
{
De6Couleur de6r(rouge);
De6 de6;
DeNFaces denf(9);
((DeNFaces)de6r).GetValeur();

// cast De6Couleur -> DeNFaces

((DeNFaces)de6).GetValeur(); // cast De6 -> DeNFaces


}
---------------------------------------------------------------------------------

Conversion sous-classe * -> super-classe * : les conversions de pointeurs entre sous-classe et super-classe
sont galement lgales en C++.
--------------------------------------------------------------------------------#include "De6Couleur.h"
#define rouge 5
void main(void)
{
De6Couleur de6r(rouge);
De6 *ptrDe6;
DeNFaces *ptrDeNF;
ptrDe6=&de6r;
ptrDe6->Lancer();

// De6Couleur * -> De6 *

38

ptrDeNF=ptrDe6;
ptrDeNF->Lancer();

// De6 * -> DeNFaces *

}
---------------------------------------------------------------------------------

8.4.

Le polymorphisme et les mthodes virtuelles

Le polymorphisme reprsente le fait qu'une fonction ayant le mme nom puisse tre appele sur des
objets de classes diffrentes. Par exemple, le fait qu'on puisse mettre une mthode DessinerDans(fenetre)
dans diffrentes classes peut tre vu comme du polymorphisme. En effet, pour la classification faite cidessous, les classes reprsentent des objets graphiques destins tre dessins dans des fentre sous windows.
La fonction de dessin est donc prsente dans les diffrentes classes.

Polygone
Cercle
+DessinerDans(out fenetre)
+DessinerDans(out fenetre)

Rectangle
+DessinerDans(out fenetre)

En C++, le polymorphisme reprsente aussi la possibilit pour des objets d'une descendance
rpondre diffremment lors de l'appel d'une mthode de mme nom. Le polymorphisme est donc li dans ce
cas aux hirarchies de classes.
Si l'on reprend la classification prcdente et que l'on ajoute une super-classe ObjetGraphique , on
voit que tous les objets graphiques ont besoin d'une mthode permettant de dessiner lobjet dans une
fentre. De plus, la mthode de dessin doit agir diffremment sur les diffrents objets graphiques : un
rectangle ne se dessine pas de la mme manire qu'un cercle. Si la classe de base de la descendance contient
une mthode DessinerDans() , en raison des compatibilits de type entre sous-classe et super-classe, on peut
donc excuter le programme suivant.
--------------------------------------------------------------------------------#include "Rectangle.h"
ObjetGraphique
#include "Cercle.h"
#include<iostream>
+DessinerDans(out fenetre)
using namespace std;
void main(void)
{
Fenetre f; // objet fentre graphique
ObjetGraphique * tab[3];
tab[0]=new Cercle;
tab[1]=new Rectangle;
tab[2]=new Polygone
tab[0]->DessinerDans(f);
tab[1]->DessinerDans(f);
tab[2]->DessinerDans(f);

Cercle

Polygone

+DessinerDans(out fenetre)

+DessinerDans(out fenetre)

Rectangle
+DessinerDans(out fenetre)

// mthode de la classe ObjetGraphique invoque


// car la mthode nest pas polymorphe par dfaut

for(int i=0;i<3;i++) delete tab[i];


}
---------------------------------------------------------------------------------

39

Dans l'exemple prcdent la mthode DessinerDans() peut tre invoque sur tous les objets. Mais, si
la mthode DessinerDans() n'est pas dclare virtual dans la classe de base ObjetGraphique, c'est
ncessairement la mthode de la classe ObjetGraphique qui va tre invoque, mme si l'objet point est de la
classe Rectangle ou Cercle. Par dfaut en C++, un lien statique relie les mthodes aux classes. Autrement
dit, par dfaut, les mthodes ne sont pas polymorphes. Il faut prciser quand on souhaite que le
polymorphisme sapplique.
Remarque : en Java, le polymorphisme sapplique automatiquement. Le C++ laisse la possibilit de mettre
en place ou non le polymorphisme (via les mthodes virtuelles) pour des raisons de performance. Car la
dfinition de mthodes virtuelles (voir ci-dessous) implmente une table dindirection supplmentaire qui
augmente la taille du code et ralentit lexcution. Par consquent en C++, selon le besoin, le programmeur
peut privilgier la vitesse quand le polymorphisme nest pas utile.
Les mthodes virtuelles. Si l'on dfinit la mthode DessinerDans() comme tant virtuelle (mot cl virtual )
dans la classe de base ObjetGraphique , le programme prcdent va bien invoquer les mthodes
DessinerDans() de chacun des objets points. La mthode adapte l'objet est retrouve dynamiquement. Le
mot cl virtual indique au compilateur de mettre en place une ligature dynamique de mthodes. Le
compilateur cre alors une table dindirection (ceci est transparent pour le programmeur) permettant de
retrouver la bonne mthode lexcution. Le choix de la mthode invoque a alors lieu lexcution et non
la compilation.
--------------------------------------------------------------------------------#ifndef __OBJETGRAPHIQUE__
#define __OBJETGRAPHIQUE__
#include "Fenetre.h"
class ObjetGraphique
{
public:
ObjetGraphique();
virtual void DessinerDans(Fenetre &) const;
};
#endif
----------------------------------------------------------------------------------------------------------------------------------------------------------------#ifndef __RECTANGLE__
#define __RECTANGLE__
#include "ObjetGraphique.h"
class Rectangle: public ObjetGraphique
{
public:
Rectangle();
virtual void DessinerDans(Fenetre &) const;
};
#endif
---------------------------------------------------------------------------------

Remarque : il faut noter que la ligature dynamique (obtenue par le mot cl virtual ) ne concerne que la
mthode prcise, et non toutes les mthodes de la classe.
En C++, quand on parle de polymorphisme, on pense en priorit ce lien dynamique mis en place
en utilisant le mot cl virtual .
40

8.5.

Les classes abstraites

Grce au polymorphisme on peut crire du code gnrique et faire des structures de donnes
htrognes. Pour comprendre ces ides, le plus simple est de considrer de nouveau la classification dobjets
graphiques faite prcdemment. On souhaite faire un petit programme de dessin o lutilisateur peut
dessiner des formes gomtriques simples (ellipses, rectangles.), les slectionner, les supprimer, les dplacer
Pour cela, les formes gomtriques doivent tre mmorises dans une structure de donnes. En outre,
toutes les formes gomtriques devront pouvoir ragir des mmes demandes (mmes mthodes).

Linterface de la classe de base ObjetGraphique va contenir tout ce que les diffrentes formes
gomtriques seront capable dexcuter. Mais la notion dobjet graphique reste abstraite. Que signifie
dessiner un objet graphique ? ou mme dplacer un objet graphique ?
En fait, la classe ObjetGraphique va tre une classe dite abstraite. Elle contiendra des fonctions
virtuelles pures, cest--dire des fonctions membres qui ne seront pas dfinies (en outre on ne saurait pas
quoi y mettre) mais pour lesquelles le typage dynamique sappliquera. Les fonctions virtuelles pures nont
pas de code et ont un prototype qui se termine par =0. Une classe qui contient au moins une fonction
virtuelle pure est dite abstraite. On ne peut alors pas crer dobjet dune telle classe (mais on pourra crer des
objets dune classe drive).
--------------------------------------------------------------------------------#ifndef __OBJETGRAPHIQUE__
#define __OBJETGRAPHIQUE__
#include "Fenetre.h"
#include "Point.h"
class ObjetGraphique
// classe abstraite
{
public:
ObjetGraphique();
virtual ~ObjetGraphique();
// destructeur virtuel
// fonctions
virtual void
virtual void
virtual void

virtuelles pures
DessinerDans(Fenetre &) const =0;
Deplacer(int DeltaX, int DeltaY) =0 ;
Contient(const Point &) const =0 ;

};
#endif
---------------------------------------------------------------------------------

On ne peut pas crer dobjet de la classe ObjetGraphique, mais en revanche, on peut faire un
conteneur dobjets graphiques. En effet, on peut faire par exemple un tableau de pointeurs dans lequel on
peut placer les adresses des diffrentes formes gres par le programme. Le programme ci-dessous gre une
41

telle structure de donnes contenant des objets htrognes. Ils sont en revanche tous descendants de la classe
ObjetGraphique.
void main()
{
vector<ObjetGraphique *> FormesGraphiques;
Fenetre F ;
FormesGraphiques.push_back(new Rectangle(10,10,40,55));
FormesGraphiques.push_back(new Ellipse(60,60,140,155));
FormesGraphiques.push_back(new Rectangle(30,30,40,40));
for(int i=0;i<FormesGraphiques.size();i++) FormesGraphiques[i]->DessinerDans(F);
for(int i=0;i<FormesGraphiques.size();i++) delete FormesGraphiques[i];
}

De plus, on voit quil est trs simple de redessiner lensemble des objets (code en gras), puisque on
envoie le mme message chaque objet, ce message tant interprt diffremment par chacun des objets
(grce au typage dynamique). En rsum, une classe abstraite dcrit ce quon attend dune classe
(comportement) sans savoir comment cela va tre ralis. En fait le comportement final est programm dans
la ou les classes descendant de la classe abstraite.

9. Visibilit des membres dune classe


Jusquici, nous avons utilis uniquement des membres (attributs ou mthodes) de type public ou
private. Ces mots-cl rglent la visibilit des membres. Les membres public dune classe sont accessibles la
fois aux objets de la classe ainsi qu lutilisateur de la classe (comme dans les types structurs du C). Toutes
les mthodes de linterface dune classe (cest--dire le comportement dune classe) sont accs public. En
revanche, lencapsulation des donnes dune classe est possible grce au mot-cl private . Les membres
private sont accessibles aux objets de la classe mais pas de lutilisateur de la classe. Lutilisateur ne peut pas
agir directement sur les donnes membres dun objet.
Dans cette section, on prsente tout dabord comment des fonctions non membres dune classe, ou
comment dautres classes, peuvent avoir galement accs la partie encapsule dune classe donne. Il sagit
des fonctions ou classes amies (mot cl friend ).
Ensuite, on prsente un nouveau droit daccs : un membre protg (mot-cl protected ) est comme un
membre priv (non accessible par lutilisateur) mais reste accessible pour une classe drive. Cest donc dans
le cadre de la drivation que ce mot cl a un usage. Enfin, on rvisera les problmes de visibilit dans les
hirarchies de classes.
9.1.

Mot-cl friend : fonctions ou classes amies

On peut dclarer, dans une classe CLS, le fait que dautres fonctions ou dautres classes aient accs
la partie encapsule de la classe CLS . Cette fonctionnalit est particulirement intressante lorsque lon veut
dfinir des oprateurs comme fonctions non membres de la classe.
On illustre ceci en reprenant la classe Rationnel vue dans la section sur les oprateurs (section 5) et
en dfinissant les oprateurs + et * comme des fonctions non membres de la classe Rationnel mais amis de
cette classe. Les membres _den et _num deviennent accessibles ces fonctions non membres.
class Rationnel
{
// fonctions non membres mais amies de la classe
friend Rationnel operator+(const Rationnel & r1,const Rationnel & r2);
friend Rationnel operator*(const Rationnel & r1,const Rationnel & r2);
...
};

42

#include "Rational.h"
#include <iostream>
Rationnel operator+(const Rationnel & r1,const Rationnel & r2){
Rationnel local(r1._num*r2._den+r2._num*r1._den,r1._den*r2._den);
return local;
}
Rationnel operator*(const Rationnel & r1,const Rationnel & r2){
Rationnel local(r1._num*r2._num,r1._den*r2._den);
return local;
}

On peut galement dclarer une classe amie. Dans lexemple suivant, la classe B est dclare amie de
la classe A. Cela signifie que toutes les mthodes de la classe B peuvent avoir accs la partie prive dun
objet de la classe A.
#include "B.h"
class A
{
public:
friend B;

};

// la classe B est amie de la classe A

Remarque : la dclaration damiti permet de contourner lencapsulation des donnes dune classe. Il ne faut
donc pas abuser de ce droit. Il est prfrable de chercher viter autant que possible dutiliser lamiti (on
peut souvent procder diffremment en ajoutant des accesseurs notamment).
9.2.

Membres protgs (mot-cl protected) et visibilit des membres

Un membre (donne ou mthode) dclar protected est accessible par les objets de la classe mais
pas par lutilisateur de la classe (comme pour un membre priv). En revanche, la diffrence dun membre
private, un membre protected est accessible par un objet dune classe drive. On dclare donc protected
un membre que lon souhaite rendre accessible a une classe drive. En rsum, pour une classe (classe Base)
et une classe drive (classe Derivee) donne on a :

-------------------------------------#ifndef ___CLS_BASE__
#define ___CLS_BASE__
class Base
{
public:
Base();
MethodeB();
protected:
bool _indPProtegeB;
void FonctionProtegeeB();
private:
int _dPriveeB;
void FPriveeB();
};
#endif
--------------------------------------

-------------------------------------#ifndef ___CLS_DERIVEE__
#define ___CLS_DERIVEE__
#include "Base.h"
class Derivee : public Base
{
public:
Derivee();
MethodeD();
protected:
bool _indProtegeD;
void FonctionProtegeeD();
private:
int _dPriveeD;
void FPriveeD();
};
#endif
--------------------------------------

43

Tous les membres privs et protgs de la classe Base sont accessibles dans les mthodes (prives ou
protges) de la classe Base. Par contre un utilisateur de la classe Base na accs qu la mthode publique de
cette classe.
De mme, tous les membres privs et protgs de la classe Derivee sont accessibles dans les mthodes
(prives ou protges) de la classe Derivee. En outre, du fait de la drivation, les mthodes publiques de la
classe Base sont utilisables sur les objets de la classe Derivee.
void main()
{
Derivee D ;
D.MethodeD() ;
D.MethodeB() ;
}

// mthode hrite

Dans une mthode prive ou protge de le classe Derivee, on peut accder la partie publique de la
classe de base (normal) et la partie protge de la classe de base.
void Derivee::FPriveeD()
{
MethodeB();
MethodeD();

// implmentation de la classe Derivee


// possible (car publique)
// possible (car publique)

_indPProtegeB=true;
// _dPriveeB=3;

// possible (car membre protg de Base)


// non possible (car membre priv de Base)

FonctionProtegeeB();
//FPriveeB();

// possible (car fonction protge de Base)


// non possible (car fonction prive de Base)

Trois formes de drivation sont possibles : la drivation publique, prive ou protge.


Tout ce qui vient dtre expos ne tient que pour la drivation publique (class Derivee : public
Base). Cest le mode de drivation le plus courant. Il existe aussi une drivation prive (class Derivee :
private Base) et protge (class Derivee : protected Base). La visibilit des membres de la classe de base
dpend du mode de drivation. On rsume ci-dessous la visibilit des membres selon le mode de drivation.
Par la suite, FMA signifie Fonctions Membres et Amies
La drivation publique (class
Statut dans la
classe de base
Public
Protg
Priv

Derivee : public Base )

Accs aux FMA


de la classe drive
Oui
Oui
Non

Accs un utilisateur
de la classe drive
Oui
Non
Non

Nouveau statut
dans la classe drive2
Public
Protg
Priv

La drivation prive (class Derivee : private Base)


Dans ce mode de drivation, pour un utilisateur de la classe drive, tout ce qui est hrit de la classe
de base est encapsul par la drivation. Cest un peu comme si lon dfinissait un membre priv de la classe
de base dans la classe drive. Autrement dit, on nhrite pas vraiment des mthodes.
2

en cas de nouvelle drivation

44

La drivation protge (class Derivee : protected Base)


Dans ce mode de drivation, les membres publics de la classe de base seront considrs comme
protgs lors des drivations ultrieures.
Tableau de synthse
Classe de base
Statut initial Accs FMA
Public
Protg
Priv

Oui
Oui
Oui

Accs
utilisateur
Oui
Non
Non

Drive publique
Nouveau
Accs
statut
utilisateur
Public
Oui
Protg
Non
Priv
Non

Drive protge
Nouveau
Accs
statut
utilisateur
Protg
Non
Protg
Non
Priv
Non

Drive prive
Nouveau
Accs
statut
utilisateur
Priv
Non
Priv
Non
Priv
Non

Ce tableau est difficile assimiler. Seule la pratique de la programmation permet de retenir ces
aspects du langage. Il faut dans un premier temps se concentrer sur la drivation publique qui est le mode de
drivation le plus couramment employ.
9.3.

Navigation dans une hirarchie de classes, phnomnes de masquage

On sintresse ici ce que va pouvoir exploiter un utilisateur dans le cas dune hirarchie de classes.
Considrons les classes partiellement dcrites ci-dessous.
class Base
{
public:
Base();
void Affiche() const;
void AfficheB() const;

};

class Derivee:public Base


{
public:
Derivee();
void Affiche() const;
void AfficheD() const;

};

La classe drive comporte, comme la classe de base, une mthode Affiche() . Sur un objet Derivee, la
mthode Affiche() appele est ncessairement celle de la classe drive. On dit quelle masque celle de la
classe de base. Mais, cette dernire demeure utilisable en prcisant lappel le nom de la classe propritaire.
Dailleurs on peut toujours prciser le nom de la classe lors dun appel de mthode. Par exemple, lappel cidessous est tout fait lgal (mais pas trs concis)
void main()
{
Base B;
B.Base::Affiche();
}

// quivaut B.Affiche();

----------------------------------------------------------------------------------------#include "derivee.h"
void main()
{
Derivee D;
Base B;
B.Affiche();
B.AfficheB();
D.AfficheD();
D.Affiche();

// Derivee::Affiche() masque ici Base::Affiche()

45

D.Base::Affiche();
// appel explicite de la mthode de la classe de base
D.Derivee::Affiche() ;
// quivalent D.Affiche();
B.Base::AfficheB();
// quivalent B.AfficheB() ;
// B.Derivee::AfficheD(); na en revanche pas de sens
}
-----------------------------------------------------------------------------------------

10. Classes avec donnes en profondeur


10.1.

Les oprateurs de gestion de mmoire (new et delete)

En C, on demande de la mmoire avec la fonction malloc() et on la libre avec la fonction free().


En C++, l'allocation dynamique de mmoire s'effectue avec les oprateurs new et delete.
10.1.1. Syntaxe
La syntaxe d'utilisation de loprateur new est la suivante :
T * ptr;
ptr = new T;
ptr = new T[n];

// allocation d'un objet (ou une variable) de type T


// allocation d'un tableau de n objets de type T

o T est un identificateur de type quelconque (type primitif, type structur ou classe), n est une
expression entire quelconque.

La premire syntaxe rserve de la mmoire pour stocker 1 lment ayant le type T, alors que la
seconde rserve de la mmoire pour stocker n lments de type T. Dans les deux cas, le rsultat de l'opration
est une adresse. Soit l'adresse de l'espace mmoire rserv sous la forme d'un pointeur de type (T *), soit le
pointeur NULL si l'espace demand n'a pu tre obtenu.
L'utilisation de l'oprateur delete est la suivante :
delete ptr;
delete [] ptr;

ptr

est un pointeur ayant obtenu sa valeur actuelle par loprateur new.

La premire syntaxe libre l'espace mmoire d'un objet point par ptr. La seconde syntaxe sert
librer l'espace occup par un tableau d'objets. Cette seconde syntaxe assure que tous les destructeurs des
objets du tableau sont appels avant que le tableau soit libr. Ceci est important si les objets du tableau ont
des donnes en profondeur (voir la suite de ce chapitre). Il est noter quil nest pas ncessaire de rappeler la
taille du tableau allou lors de la libration de la mmoire.
Remarque : les objets string de la STL ont des donnes en profondeur. Le code suivant conduit des fuites
mmoire
string * ptr;
ptr = new string[6];
delete ptr;

Remarque : le fonctionnement de l'oprateur delete est indtermin si ptr pointe sur une zone qui n'a pas
t alloue dynamiquement par l'oprateur new , ou si la zone mmoire a dj t libre. Il est dconseill de
mlanger lutilisation des oprateurs new/delete avec celle des fonctions malloc()/free() .
On peut prciser l'utilisation d'un constructeur particulier lors de l'allocation d'un objet (mais pas
pour les tableaux allous dynamiquement). Ci dessous, l'oprateur new alloue la mmoire pour stocker un
objet de la classe string et cet objet est initialis l'aide du constructeur 1 argument de type char* .
ptr = new string("toto");

/* le constructeur 1 argument de type char* de la classe


string ralise l'initialisation de l'objet */

46

10.2. Ralisation de la composition par gestion dynamique de mmoire (donnes en profondeur).


Nous avons vu dans la section 7 diffrentes ralisations de la composition. On peut galement grer
un objet composant dans la zone de mmoire gre dynamiquement (dans le tas). Dans ce cas, l'objet
composant n'est pas rellement dans la classe mais est connu via un pointeur. Les donnes de l'objet
composant n'tant plus dans l'objet composite, on dit que les donnes sont en profondeur, autrement dit
obtenues indirectement par un pointeur. Si l'on reprend l'exemple de la classe Point qui est compose de 2
objets de type Rationnel, on peut donner une nouvelle ralisation de la composition base sur la gestion
dynamique d'objets (mme si la reprsentation UML est la mme)
--------------------------------------------------------------------------------#ifndef _POINT__
Point
#define _POINT__
+Point(in x : Rationnel, in y : Rationnel)
#include "rationnel.h"
+GetX() : Rationnel
+GetY() : Rationnel
+SetX(in x : Rationnel)
+SetY(in y : Rationnel)

class Point
{
1
public:
2
-coordonnes
Point(const Rationnel & x, const Rationnel & y);
Rationnel
Point(const Point & p);
-_numerateur : int
~Point();
//destructeur
-_denominateur : int
+Rationnel(in num : int, in den : int)
Point & operator=(const Point & p);
+FormeIrreductible() : Rationnel
Rationnel GetX() const;
+GetNum() : int
+GetDen() : int
Rationnel GetY() const;
+SetNum(in num : int)
+SetDen(in den : int)
private:
+operator+(in r : Rationnel) : Rationnel
+operator-(in r : Rationnel) : Rationnel
Rationnel * _XY;
// pointeur sur le composant
+operator*(in r : Rationnel) : Rationnel
};
+operator/(in r : Rationnel) : Rationnel
#endif
---------------------------------------------------------------------------------

Remarque : pour la premire fois depuis le dbut de ce manuel, le rle du destructeur dune classe va tre
illustr. Le destructeur est la mthode de la classe dont le nom est celui de la classe prcd dun tilde (~), cidessous la mthode ~Point . Cette mthode est appele juste avant la disparition de lobjet.
--------------------------------------------------------------------------------//Point.cpp
#include "Point.h"
#include <process.h>
#include <iostream>
using namespace std;
Point::Point(const Rationnel & x, const Rationnel & y){
_XY=new Rationnel[2];
if(_XY==NULL){
cerr << "echec d'allocation de mmoire.";
exit(2);
}
_XY[0]=x;
_XY[1]=y;
}
// constructeur de copie
Point::Point(const Point &p){
_XY=new Rationnel[2];
if(_XY==NULL){
cerr << "echec d'allocation.";
exit(2);
}
_XY[0]=p._XY[0];
_XY[1]=p._XY[1];
}

47

// destructeur : mthode appele automatiquement juste avant la disparition de lobjet


Point::~Point()
{
delete [] _XY;
}
Point & Point::operator=(const Point &p)
{
_XY[0]=p._XY[0];
_XY[1]=p._XY[1];
return *this;
}
Rationnel Point::GetX() const {

return _XY[0]; }

Rationnel Point::GetY() const {


return _XY[1]; }
---------------------------------------------------------------------------------

Les donnes lies aux coordonnes ne sont pas directement dans l'objet Point mais dans une zone
mmoire gre dynamiquement par l'objet. Dans ce cas, le constructeur a pour rle dallouer de la mmoire et
le destructeur (mthode ~Point) celui de librer la zone mmoire alloue par le constructeur.
Une telle structuration a des consquences. Il faut crire les constructeurs (qui allouent la mmoire),
le destructeur (qui libre la mmoire) et l'oprateur d'affectation avec soin. Faute de quoi, des fuites mmoires
peuvent avoir lieu.
10.3.

Ralisation de conteneurs : tableaux, piles, files, listes ...

Les conteneurs sont des objets destins stocker/restituer des donnes. Les conteneurs se distinguent
les uns des autres par la faon dont ils grent et accdent aux donnes. Les conteneurs les plus utiliss sont :
- les tableaux : l'accs a une donne est indic et direct.
- les listes : l'accs une donne est squentiel
- les piles/les files : on peut voir ces conteneurs comme des listes particulires
La taille des donnes d'un objet conteneur est susceptible d'voluer durant la vie de l'objet. Un objet
conteneur ne peut donc pas prvoir ds la construction quelle sera la quantit de mmoire qui lui sera
ncessaire. Un conteneur va donc grer dynamiquement la mmoire qui lui sera ncessaire. Les changements
de taille des donnes conduisent des allocations/dsallocations de mmoire. Nous illustrons ceci par une
formulation lmentaire d'une classe d'objets tableaux.
--------------------------------------------------------------------------------#ifndef __CLS_TABLEAU__
#define __CLS_TABLEAU__
class Tableau
{
public:
Tableau(unsigned taille=5);
Tableau(const Tableau & tab);
Tableau & operator=(const Tableau & tab);
virtual ~Tableau();
double & operator[](unsigned idx);
double operator[](unsigned idx) const;
void NouvelleTaille(unsigned taille);
unsigned GetTaille() const;

48

unsigned GetCapacite() const;


private:
double * _tab;
unsigned _capacite;
unsigned _taille;
};
#endif
---------------------------------------------------------------------------------

La mmoire est gre ici selon la technique utilise dans la bibliothque STL pour les vectors . Un
objet alloue une zone mmoire d'une certaine capacit (attribut _capacite ). Dans cette zone, il stocke les
donnes utiles (qui occupent une certaine _taille). Par consquent, _taille doit toujours tre infrieur ou
gal _capacite . La diffrence _capacite-_taille constitue une rserve de mmoire utilisable lors des
changements de taille ou des affectations entre objets tableaux.
--------------------------------------------------------------------------------#include "Tableau.h"
#include<process.h>
#include<iostream>
using namespace std;
Tableau::Tableau(unsigned taille):_taille(taille),
_capacite(2*taille), _tab(new double[2*taille])
{
if(_tab==NULL){
cerr << "echec d'allocation";
exit(2);
}
// les lments du tableau sont initialiss 0
for(unsigned i=0;i<_taille;i++) _tab[i]=0;
}
Tableau::~Tableau(){
delete [] _tab;
}
// constructeur de copie
Tableau::Tableau(const Tableau & t):_taille(t._taille),
_capacite(t._capacite),_tab(new double[t._capacite])
{
if(_tab==NULL){
cerr << "echec d'allocation";
exit(2);
}
for(unsigned i=0;i<_taille;i++) _tab[i]=t._tab[i];
}
double & Tableau::operator [](unsigned idx)
{
if(idx>=GetTaille()){
// verification dindice
cerr << "indice incorrect!";
exit(2);
}
return _tab[idx];
}

49

double Tableau::operator [](unsigned idx) const


{
if(idx>=GetTaille()) {
cerr << "indice incorrect!";
exit(2);
}
return _tab[idx];
}
// oprateur d'affectation
Tableau & Tableau::operator=(const Tableau & t)
{
if(_capacite<t._taille) // rallocation si capacit insuffisante
{
_capacite=2*t._taille;
double * temp=new double[_capacite];
if(_tab==NULL){
cerr << "echec d'allocation";
exit(2);
}
delete [] _tab;
_tab=temp;
}
_taille=t._taille;
for(unsigned i=0;i<t._taille;i++) _tab[i]=t._tab[i];
return *this;
}
unsigned Tableau::GetTaille() const {
return _taille;
}
unsigned Tableau::GetCapacite() const {
return _capacite;
}
// mthode permettant le changement de taille
void Tableau::NouvelleTaille(unsigned taille)
{
if(taille<=GetTaille())
_taille=taille;
else
{
if(taille>_capacite)
{
_capacite=2*taille;
double * temp=new double[_capacite];
if(_tab==NULL){
cerr << "echec d'allocation";
exit(2);
}
for(unsigned i=0;i<_taille;i++) temp[i]=_tab[i];
delete [] _tab;
_tab=temp;
}
for(unsigned i=_taille;i<taille;i++) _tab[i]=0;
_taille=taille;
}
}
---------------------------------------------------------------------------------

50

Fonction retournant une rfrence


Il faut noter que loprateur dindexation [] retourne une rfrence. Ainsi on peut modifier un lment
du tableau via cet oprateur. Dans limplmentation de loprateur simplifie ici
double & Tableau::operator [](unsigned idx){
return _tab[idx];
}

il faut interprter cela comme le fait que la fonction retourne une rfrence _tab[idx] . Dans ce cas, la
fonction ne retourne pas une copie de la case, mais la case elle-mme.
Deux oprateurs dindexation []
Il faut galement remarquer que la classe dispose de deux oprateurs dindexation. Un oprateur constant
qui peut tre appel sur des objets constants (cest--dire des tableaux constants). Celui-ci ne doit pas pouvoir
modifier les lments du tableau et renvoie donc le contenu de la case par valeur (retour classique). Lautre
oprateur (non constant) quant lui retourne une rfrence.
Dailleurs, on ne peut appeler sur un objet constant que les mthodes constantes : ici il sagit de
GetTaille(), GetCapacite() et un oprateur []. Lexemple ci-dessous montre comment lon peut utiliser la
classe Tableau .
--------------------------------------------------------------------------------#include "Tableau.h"
#include<iostream>
using namespace std;
void main()
{
Tableau T1(4);
T1[0]=2;
// modifie la case dindice 0
T1[2]=1.3;
cout << T1[0] << endl ;
cout << T1.GetTaille() << endl;
// ici T1 contient {2,0,1.3,0}, _taille==4 et _capacite==8
T1.NouvelleTaille(10);
// ici T1 contient {2,0,1.3,0,0,0,0,0,0,0}, _taille==10 et _capacite==20
const Tableau T2(T1); T2 est un tableau constant, fait par copie de T2
// ici T2 contient {2,0,1.3,0,0,0,0,0,0,0} et son tat nest pas cens voluer
// on ne peut appeler sur T2 que des mthodes constantes
cout << T2[7] << endl ;
cout << T2.GetTaille() << endl;
}
---------------------------------------------------------------------------------

Cette classe ressemble, en beaucoup moins volue, la classe vector<double> de la bibliothque STL. Il
reste nanmoins prfrable dutiliser la bibliothque STL plutt que de redfinir des conteneurs. Cependant,
crire des conteneurs est un bon exercice pour assimiler les techniques dcriture des classes. Il peut tre
intressant de dvelopper, titre dexercice, les conteneurs suivants (prsents dans la bibliothque STL): classe
Ensemble (permettant de stocker des lments sans doublon possible), classe ListeChainee (classe de listes
chanes), classe Pile( structure LIFO), classe Chaine (classe de chanes de caractres).
51

11. Les fonctions et les classes paramtres en type


On a vu que la bibliothque STL fournit des classes de conteneurs paramtres en type. Par exemple,
on peut faire des tableaux ou des listes d'lments de n'importe quel type. Nous allons prciser ici la syntaxe
permettant de raliser des classes ou des fonctions paramtres en type.
11.1.

Les fonctions paramtres en type

Considrons lexemple classique dune fonction de permutation des valeurs de deux variables entires.
--------------------------------------------------------------------------------#include <iostream>
using namespace std;
void permute(int & a,int & b) { int c=a; a=b; b=c; }
void main()
{
int x=2,y=3;
permute(x,y);
cout << x << y << endl;
}
---------------------------------------------------------------------------------

Nous voulons dsormais, dans la mme application, permuter deux valeurs de type double . Grce la
surcharge de fonctions, on peut complter le programme de la manire suivante
--------------------------------------------------------------------------------void permute(int & a,int & b) { int c=a; a=b; b=c; }
void permute(double & a, double & b) { double c=a; a=b; b=c; }
void main()
{
int x=2,y=3;
double u=1.3,v=1.7;
permute(x,y);
permute(u,v);
}
---------------------------------------------------------------------------------

La seule diffrence entre ces deux fonctions concerne les types utiliss. Si lon peut paramtrer les
types, on na quune seule dfinition donner. Cest ce que permet la notation template (patron).
--------------------------------------------------------------------------------template<class T> void permute(T & a,T & b)
{
T c=a; a=b; b=c;
}
void main()
{
int x=2,y=3;
double u=1.3,v=1.7;
permute(x,y); // T=int
permute(u,v); // T=double
}
---------------------------------------------------------------------------------

La notation template<class T> indique au compilateur que T est un paramtre de type (type primitif,
structur ou classe). La fonction permute peut alors tre appele avec deux arguments d'un type quelconque,
52

dans la mesure o les deux arguments sont du mme type (a n'aurait pas de sens de permuter un caractre
avec un flottant).
Quand un paramtre de type est utilis dans une fonction C++, on parle alors de patron de fonctions,
ou de fonction paramtre en type ou encore de fonction gnrique. Un patron de fonctions ne conduit pas
directement du code. Par exemple, sil ny avait pas dappel de la fonction permute() , aucun code ne serait
gnr (contrairement une fonction classique). Le code des fonctions cres partir du patron nest gnr
que sil y a un appel de la fonction pour un type donn.
Dans lexemple prcdent, le compilateur cre deux fonctions partir du patron. La premire
fonction est cre pour le paramtre de type T=int et la seconde pour le paramtre de type T=double. Le code
des fonctions est gnr selon le besoin. Dans un patron de fonctions, on peut aussi faire apparatre des
arguments typs normalement. Ces paramtres sont parfois appels paramtres expression. L'exemple suivant
reprsente ce que pourrait tre une fonction de tri de tableau paramtre :
template <class T> void tri(T * tab,unsigned taille)
{
// algorithme du tri d'un tableau de taille lments de type T;
// ... //
}

Un patron de fonctions dfinit une famille de fonctions ayant toutes le mme algorithme mais pour
des types diffrents. Il reste nanmoins possible de dfinir explicitement le code dune fonction qui devrait
normalement tre prise en charge par le patron. Dans lexemple suivant, nous ralisons un patron de
fonctions calculant le minimum de deux variables de mme type. La fonction min() obtenue partir du
patron a du sens pour tous les types lmentaires sauf pour le type char* . Nous avons donc dfini une
spcialisation pour le type char * . Cette spcialisation remplace le code que gnrerait le patron.
--------------------------------------------------------------------------------#include<iostream>
#include<string.h>
using namespace std;
//patron de fonctions
template <class T> T min(T a,T b){ return a<b ? a : b; }
//spcialisation du patron pour le type char *
char * min(char * s1,char * s2)
{
if(strcmp(s1,s2)<0)
return s1;
else
return s2;
}
void main()
{
int a=1,b=3;
char chaine1[]="coucou",chaine2[]="salut";
cout << min(a,b); //gnre partir du patron
cout << min(chaine1,chaine2); //spcialisation
}
---------------------------------------------------------------------------------

11.2.

Classes paramtres en type

On peut galement introduire des paramtres de type dans la dfinition d'une classe. Dailleurs, les
conteneurs STL (voir section 6) sont des classes paramtres en type. Pour prsenter la syntaxe, le plus simple
53

est une fois encore de traiter un exemple. On va rcrire la classe Point de sorte de pouvoir instancier des
points dont les coordonnes soient d'un type choisi par l'utilisateur.

entre <

On rappelle que dans l'utilisation d'une classe paramtre, la valeur du paramtre de type est prcise
T > . Par exemple, pour la classe paramtre Point<T> donne ci-aprs, l'utilisation sera la suivante.
--------------------------------------------------------------------------------void main()
{
Point<int> p1(12,16);
// point coordones entires
Point<double> p2(1.3,4.6);
// point coordonnes rationnelles
}
---------------------------------------------------------------------------------

Dfinition de la classe paramtre

Point<T>.

--------------------------------------------------------------------------------//fichier point.h
#ifndef __POINT_TEMPLATE__
#define __POINT_TEMPLATE__
template<class T>
// T est un paramtre de type
class Point
{
public:
Point<T>(){ _XY[0]=0; _XY[1]=0;}
Point<T>(const T & x,const T & y)
{
_XY[0]=x;
_XY[1]=y;
}
Point<T>(const Point<T> & p)
{
_XY[0]=p._XY[0];
_XY[1]=p._XY[1];
}
Point<T> & operator=(const Point<T> & p)
{
_XY[0]=p._XY[0];
_XY[1]=p._XY[1];
return *this;
}
void SetX(const T & x) { _XY[0]=x; }
void SetY(const T & y){ _XY[1]=y; }
const T GetX() const { return _XY[0]; }
const T GetY() const { return _XY[1]; }
private:
T _XY[2];
// tableau de 2 lments de type T
};
#endif
---------------------------------------------------------------------------------

Remarque : pour les classes paramtres en type, toute l'implmentation des mthodes doit tre disponible
quand on utilise la classe paramtre. Aussi, il est habituel que dans ce cas, le corps des mthodes soit fourni
54

dans la dfinition de la classe. Il n'y a donc qu'un fichier header (fichier dntte dextension .h) contenant
toute limplmentation des mthodes (comme ci-dessus). On doit aussi remarquer que la dfinition du
paramtre de type n'est donne qu'une seule fois avant le dbut de la classe.

12. Les flots entre/sortie, les fichiers


Ce chapitre prsente un aperu trs sommaire des flots et de l'utilisation des fichiers (vus comme des
flots particuliers). On trouve une prsentatio n dtaille de ces flots (modification du formatage, les
manipulateurs de flots) dans diffrents livres sur le C++ (dont celui de C.Delannoy) ainsi que sur l'aide
MSDN accessible sur http://msdn.microsoft.com/
12.1.Les dclarations d'numrations au sein des classes
La dclaration d'numrations s'effectue par la syntaxe suivante :
enum bool{false=0,true};

est un type, et false/true sont des valeurs pour le type bool . Les numrations sont gres
comme des variables de type entier. En C++, on peut dfinir des numrations au sein des classes. On dfinit
ainsi une famille de constantes au sein d'une classe (ce qui est une alternative la dclaration de constantes
via la directive #define ).
bool

class Calendrier
{
public:
enum jour{lundi,mardi,mercredi,jeudi,vendredi,samedi,dimanche};
Calendrier();
//...
};

Le type numration, ainsi que ses valeurs possibles, appartiennent la classe Calendrier . C'est donc
l'oprateur de rsolution de porte :: qui permet de les rfrencer.
Calendrier::jour j=Calendrier::mardi;

Cette technique est trs couramment utilise, notamment dans la bibliothque STL pour les modes
d'ouverture des flots et la dfinition des formats.
12.2.

La dclaration d'une classe au sein d'une classe

C'est la technique utilise par les itrateurs dans la bibliothque STL. La classe ci-dessous illustre cette
technique qui peut se gnraliser aux patrons de classes.
class vecteur{
public:
class iterateur{
public:
iterateur();
...
};
vecteur(int taille);
iterateur begin() const;
int & operator[](int idx);
};

55

Ici, la classe iterateur est dfinie dans la classe vecteur. L'utilisation est la suivante.
vecteur v1(3);
vecteur::iterateur i=v1.begin();

12.3.

La hirarchie des classes de flots

Une vue (trs partielle) de la hirarchie des classes de gestion des flots entre/sortie est la suivante.

ios
istream

ostream
fstream

La classe ios est la classe de base pour tous les flots. La classe istream est destine aux flots d'entre et
ostream aux flots de sortie. D'ailleurs, cin et cout sont des objets globaux instancis partir de ces classes.
Enfin, la classe fstream permet la gestion des fichiers. On voit qu'elle drive la fois des deux classes istream
et ostream .
Remarque : l'hritage multiple est possible en C++ mais n'est pas abord dans ce cours.
Par consquent, la gestion des fichiers via la classe fstream utilise des mthodes hrites des classes
mres. C'est pourquoi on va survoler l'interface de ces classes.
: numration dfinie dans la classe ios. Associe en particulier aux modes
d'ouverture des fichiers. Les valeurs possibles sont :
ios::in : ouverture en lecture
ios::out : ouverture en criture
ios::app : ouverture en ajout (mode append)
ios::binary : ouverture en mode binaire. (Par dfaut, on ouvre le fichier en mode texte.)
ios::open_mode

Ces modes peuvent tre combins par l'oprateur |, par exemple ios::out|ios::app.
Mthodes de la classe ostream :
: sortie non formate d'un caractre
ostream::write(char *,int n) : sortie de n caractres
ostream::operator<<( ) : insertion format e dans le flot pour certains types de base (char , int ...)
ostream::put(char)

#include<fstream>
#include<iostream>
using namespace std;
void main()
{
cout.put('a');
cout.put('\n');
char str[]={'a','b','c',0,'f','g','h'};
cout << str << endl;
cout.write(str,7);
}

56

A titre d'exemple, le format de sortie peut tre modifi par certaines mthodes de la classe ainsi que
par des manipulateurs de flot (ci-dessous, endl est un manipulateur de flot)
void main()
{
float f1=1.3698;
cout << f1 << endl;
cout.precision(3);
cout << f1 << endl;
}

// sortie formate -> 1.3698


// changement de la prcision
// sortie formate -> 1.37

Mthodes de la classe istream


istream::get(char &)

: lecture d'un caractre

istream::getline(char * pStr,int nbCarac,char delim='\n') :

lecture d'une suite d'au plus nbCarac


caractres. La lecture s'arrte aussi si le caractre dlimiteur (par dfaut '\n') est rencontr.
istream::read(char * pStr,int nbCarac) : lecture de nbCarac caractres.
istream::operator>>( ) : lecture formate pour les types de base.
//... inclusion des fichiers ...
using namespace std;
void main()
{
char tampon[30];
string str;
cin.getline(tampon,30,'\n'); //la lecture s'arrte sur retour chariot
cin >> str;
// la lecture s'arrte sur les espaces
cout << tampon << endl;
cout << str << endl;
}

12.4.Les fichiers
En C++, les fichiers peuvent tre grs par des flots de type fstream (file stream). La classe fstream
hrite des mthodes des classes istream et ostream, donc de celles prsentes prcdemment. La classe
fstream dispose des mthodes suivantes (ou hrites de ios, istream , ostream )
fstream::fstream() : cre un objet non connect un fichier
fstream::fstream(char * nomFichier, ios::open_mode ) : ouvre un fichier
fstream::~fstream() : fermeture d'un fichier ouvert
fstream::open(char *, ios::open_mode) : ouvre un fichier
fstream::close() : ferme le fichier en cours

Quelques quivalences entre les modes d'ouverture des flots et les modes d'ouverture de fichiers par fopen() en
C
ios::open_mode
ios::out
ios::in
ios::out|ios::app
ios::in|ios::out
ios::in|ios::binary
ios::out|ios::binary
ios::out|ios::app|ios::binary

mode pour la fonction fopen()


w
r
a
r+
rb
wb
ab

57

void main()
{
char tampon[150];
// cration ou remplacement du fichier
fstream f("toto.txt",ios::out);
// criture dans le fichier
f<<"test \t" << 1 << "\t"<< 1.5 << endl;
f.close();
// rouverture du fichier en mode ajout
f.open("toto.txt",ios::out|ios::app);
f<<"ajout \t" << 2 << "\t"<< 2.5 << endl;
f.close();

// criture dans le fichier

// ouverture en mode lecture


f.open("toto.txt",ios::in);
while(!f.eof())
{
f.getline(tampon,150);
cout << tampon << endl;
}
}

12.5.

L'accs direct dans les fichiers

La classe fstream hrite de mthodes de positionnement.


Pour la lecture (hrit de istream) :
fstream::seekg(dplacement, position) : positionne le pointeur
fstream::tellg() : retourne la position courante

Idem pour l'criture (hrit de ostream) :


fstream::seekp(dplacement, position)
fstream::tellp()

La classe ios dfinit pour cela des valeurs ios::beg (dbut du flot), ios::end (fin du flot), ios::cur
(position courante). Il est noter que la lecture ou l'criture font voluer la position courante.
void main()
{
char tampon[150];
// ouverture en criture
fstream f("fichier.txt",ios::out);
for(int i=0;i<3;i++) f<<"ligne numero "<< i << "\n";
// instant t1
f.seekp(0,ios::beg); //positionne en dbut
f<<"premiere ligne \n";
f.close();
// instant t2
// ouverture en lecture
f.open("fichier.txt",ios::in);

buffer fichier.txt
(instant t1)
--ligne numero 0---ligne numero 1---ligne numero 2--

buffer fichier.txt
(instant t2)
premiere ligne
ro 0---ligne numero 1---ligne numero 2--

58

//positionne au 3ime du dbut


f.seekg(2*sizeof(char),ios::beg);
f.getline(tampon,150);
cout << tampon <<endl;
sortie
// dplace de 3 caractres
f.seekg(3*sizeof(char),ios::cur);
f.getline(tampon,150);
cout << tampon <<endl;

re ligne
0-Press any key to continue

13. Les changements de type


13.1.Les constructeurs comme convertisseurs de type
Considrons la classe Entier suivante. Les objets de cette classe encapsulent un entier. Cette classe
sert uniquement mettre en vidence les conversions possibles grce aux constructeurs.
----------------------------------------------------------------#ifndef __CLS_ENTIER__
#define __CLS_ENTIER__
class Entier
{
public:
Entier();
Entier(int val);
Entier(float val);
Entier & operator=(const Entier & e);
int Get() const;
private:
int _data;
};
Entier operator+(const Entier & e1,const Entier & e2);
#endif
--------------------------------------------------------------------------------------------------------------------------------// Entier.cpp
#include "Entier.h"
#include<iostream>
using namespace std;
Entier::Entier()
{
cout << "constructeur Entier::Entier()"<<endl;
_data=0;
}
Entier::Entier(int val) {
cout << "constructeur Entier::Entier(int)"<<endl;
_data=val;
}
Entier::Entier(float val)
{
cout << "constructeur Entier::Entier(float)"<<endl;
_data=(int)val;
}
int Entier::Get() const
{
return _data;
}

59

Entier operator+(const Entier & e1,const Entier & e2)


{
cout << "operator+(Entier,Entier)"<<endl;
return e1.Get()+e2.Get();
}
Entier & Entier::operator =(const Entier & e)
{
cout << "Entier::operator=(Entier)"<<endl;
_data=e._data;
return *this;
}
-----------------------------------------------------------------

de type.

Le programme ci-dessous met en vidence le rle des constructeurs 1 argument pour la conversion

----------------------------------------------------------------#include<iostream>
#include "entier.h"
using namespace std;
void f(Entier e)
{
cout << "valeur de e = " << e.Get() << endl;
}
Sortie
void main(void)
{
int varInt=4;
float varFloat=2.5;
f(varInt);
f(varFloat);

;conversion int->Entier
;conversion float->Entier

Entier e(3);
e=e+varFloat; // conversion float->Entier

constructeur Entier::Entier(int)
valeur de e = 4
constructeur Entier::Entier(float)
valeur de e = 2
constructeur Entier::Entier(int)
constructeur Entier::Entier(float)
operator+(Entier,Entier)
constructeur Entier::Entier(int)
Entier::operator=(Entier)
constructeur Entier::Entier(int)
operator+(Entier,Entier)
constructeur Entier::Entier(int)
Entier::operator=(Entier)

e=1+e; // conversion int->Entier


}
-----------------------------------------------------------------

La rgle suivante sapplique en C++ : lorsquun objet de type classe CLS (ici Entier) est attendu, et
quune variable autre de type autreType est fournie, le compilateur cherche si parmi les constructeurs de la
classe CLS il en exite un qui accepte un seul argument de type autreType . Si un tel constructeur exite, un objet
temporaire est construit avec ce constructeur et est utilis la place de la variable autre.
Autrement dit :
Les constructeurs un argument dfinissent une rgle de conversion.
Par exemple, le constructeur Entier::Entier(float) dfinit la conversion dun type float vers le
type Entier. Aussi, chaque fois qu'un type Entier est attendu, et qu'un type float est fourni, le constructeur
ralise un objet temporaire pour cette conversion. Ce rle des constructeurs doit tre bien compris car en
pratique beaucoup de conversions de type sont ralises tacitement sans que le programmeur en soit tenu
inform.

60

On peut donner un autre exemple utilisant la classe string de la STL. Puisqu'il est possible de crer
un objet string partir d'une chaine de caractres du C, les lignes suivantes sont compiles sans aucun
problme.
string s1("chaine");
s1=s1+"ajout";

// constructeur string::string(char *)
// conversion char * -> string avant appel de l'oprateur +

En effet, l o loprateur + attend un objet de type string , on lui passe une variable de type char *.
Le compilateur cherche donc sil est possible de convertir la chane "ajout" en un objet de type string . C'est
possib le car la classe string dispose d'un constructeur un argument de type char * . La chane "ajout" est
donc concatne ici la chaine s1. Grce cel, on peut utiliser des chanes du C la place des objets string.
Mot cl explicit : par dfaut, les conversions possibles grce aux constructeurs peuvent tre ralises
implicitement (tacitement) par le compilateur (il n'y a pas de warning). Il peut donc y avoir des conversions
sans que le programmeur en soit conscient. On peut nanmoins mentionner dans la classe que les
constructeurs ne soient utiliss comme convertisseurs que de manire explicite (mot cl explicit ).
class Entier
{
public:
Entier();
explicit Entier(int val);
explicit Entier(float val);
...
};

// utiliable seulement pour conversions explicites

Ds lors, toutes les conversions int->Entier et float->Entier doivent tre explicites.


#include<iostream>
#include "entier.h"
using namespace std;
void f(Entier e){
cout << "valeur de e = " << e.Get() << endl;
}
void main(void){
int varInt=4;
float varFloat=2.5;
f((Entier)varInt);
f((Entier)varFloat);
Entier e(3);
e=e+(Entier)varFloat;
e=(Entier)1+e;
}

13.2.

// conversion explicite

Conversion dun type classe vers un type primitif

On peut galement dfinir des rgles permettant de convertir un objet dun type classe en un type de
base. Il sagit en fait de dfinir le traitement ralis par un oprateur de cast. Dans la classe complexe suivante,
l'oprateur double ralise la conversion complexe->double . L o un objet complexe est pass alors qu'un
double est attendu, cet oprateur est appel pour raliser la conversion de type.
----------------------------------------------------------------------------------//complexe.h
#ifndef __COMPLEXE__
#define __COMPLEXE__
#include<iostream>
class complexe
{
private:
double re;
// partie relle

61

double im;
// partie imaginaire
public:
complexe(double r=0,double i=0){
// constructeur
cout << "complexe(double=0,double=0) \t objet : "<<this << endl;
re=r;
im=i;
}
complexe operator+(const complexe & c) const
{
cout << "complexe::operator+() \t obj:"<<this<<" + obj:" <<&c<<endl;
return complexe(re+c.re,im+c.im);
}
operator double(){
// oprateur de cast
cout << "complexe::operator double() \t obj:"<<this<<endl;
return re;
// la conversion complexe->double retourne la partie relle
}
friend ostream & operator<<(std::ostream & flot,const complexe & c);
};
std::ostream & operator<<(std::ostream & flot, const complexe & c)
{
flot << c.re;
if(c.im){flot << "+"<<c.im<<"i";}
return flot;
}
#endif
-----------------------------------------------------------------------------------

Le programme dutilisation suivant illustre les conversions possibles grce cette classe lmentaire.
On y retrouve aussi les conversions ralises par le constructeur de la classe complexe . Il faut noter qu'on peut
utiliser le constructeur de la classe complexe avec un seul argument de type double, l'autre ayant la valeur 0
par dfaut. Ce constructeur peut donc tre utilis pour raliser les conversions double->complexe.
----------------------------------------------------------------------------------#include "complexe.h"
void main()
{
complexe c1,c2(1,3); //1)-2)
c1=2.4;
cout << c1 << endl;

//3)
//4)

Rsultats
complexe(dou ble=0,double=0)

objet : 0x0066FDDC

1)

complexe(double=0,double=0)

objet : 0x0066FDCC

2)

complexe(double=0,double=0)

objet : 0x0066FDB4

3)

2.4
complexe::operator+()

c2=c1+c2;
cout << c2 << endl;

//5)-6)
//7)

4)
obj :0x0066FDDC + obj :0x0066FDCC 5)

complexe(double=0,double=0)

complexe::operator+()

objet : 0x0066FD94

//12)-13)
//14)

complexe(double=0,double=0)

objet : 0x0066FD84

11)
obj :0x0066FDDC

12)

complex e(double=0,double=0)

objet : 0x0066FD74

13)

complexe::operator double()

//15)
//16)

10)

complexe::operator double()

9.4

double d;
d=c1;
cout << d << endl;

8)

obj :0x0066FDDC + obj :0x0066FD94 9)

5.9

c1=(double)c1+3.5;
cout << c1 << endl;

6)
7)

complexe(double=0,double=0)

c1=c1+(complexe)3.5; //8)-9)-10)
cout << c1 << endl; //11)

objet : 0x0066 FDA4

3.4+3i

14)
obj :0x0066FDDC

9.4

15)
16)

// c1=c1+3.5; // << error c2666 : 2 overloads have similar conversions


}
-----------------------------------------------------------------------------------

62

Les lignes de la sortie cran ont t numrotes : il y a 16 sorties cran.


1) construction de c1 (objet dadresse 0x0066FDDC)
2) construction de c2 (objet dadresse 0x0066FDCC)
3) construction dun objet temporaire, partir de largument 2.4, qui est pass loprateur = . Ladresse de
cet objet temporaire est 0x0066FDB4.
Note : puisque loprateur = na pas t explicitement dfini, cest loprateur = par dfaut qui est utilis. Ce dernier fait une
copie membre--membre qui convient parfaitement puisque les objets de la classe complexe nont pas de donnes en profondeur.

Affichage du contenu de c1 grce loprateur <<


5) Loprateur + de la classe complexe est appel.
6) Un objet temporaire dadresse 0x0066FDA4 est construit pour retourner le rsultat de loprateur car cest
un retour par valeur. Cest dailleurs cet objet temporaire qui est utilis comme argument de loprateur
=.
7) Affichage de c2
8) Li la conversion explicite. La valeur 3.5 doit tre convertie en un objet complexe. Il y a donc
construction dun objet de la classe complexe dadresse 0x0066FD94.
9) Appel de loprateur + de la classe complexe . On remarque que largument de loprateur est bien lobjet
dadresse 0x0066FD94
10) Loprateur + retourne un objet par valeur. Il y a construction dun objet dadresse 0x0066FD84.
11) Affichage de c1
12) L encore, il ya une conversion explicite. On souhaite convertir c1 en type double, ce qui fait appel
loprateur double de la classe complexe sur lobjet c1 (dont ladresse est 0x0066FDDC)
Note : par consquent, cest loprateur + correspondant au type double qui est utilis ici, et non celui du
type complexe .
13) Puisque (double)c1+3.5 est de type double , il doit y a voir une conversion implicite double-> complexe
avant laffectation. Il y a donc construction dun objet temporaire dadresse 0x0066FD74.
14) Affichage de c1
15) Il y a conversion implicite du type complexe vers le type double. Cette conversion implicite utilise
loprateur double de la classe complexe sur lobjet c1.
16) Affichage de c1
4)

Conclusion
Ces conversions de type constituent un sujet sensible. Lutilisation de ces moyens de conversions peut
conduire des situations o le compilateur ne sait pas quelle conversion mettre en uvre. Cest le cas pour la
ligne suivante :
c1=c1+3.5;

// c1=c1+(complexe)3.5 ?

ou c1=(complexe)((double)c1+3.5) ?

Le compilateur ne sait pas si lon souhaite que 3.5 soit converti en complexe ou bien que c1 soit
converti en double . Le compilateur indique alors le message derreur : error c2666 : 2 overloads have similar
conversions. Notons que cela ne conduit dailleurs pas au mme rsultat !

14. Conclusion et fiches synthtiques


Certains aspects du C++ ont t prsents dans ce document, mais la prsentation faite ici est loin
d'tre exhaustive. Les notions suivantes ont t laisses de ct pour cette premire approche du C++ :
l'hritage multiple, les nouveaux oprateurs de cast (notamment dynamic_cast), le RTTI (Run Time Type
Information), l'utilisation dtaille de la bibliothque STL.
Pour finir, les fiches suivantes synthtisent certaines parties de ce cours.
63

Le C++ en dehors des classes


Les rfrences En C++, les rfrences sont des alias : plusieurs noms pour une mme variable, un mme objet.
Dclaration d'une rfrence
int & refI=varI;
//
refI est un alias de la variable varI
// refI et varI sont deux identificateurs pour la mme variable de type int

Deux alias fournissent la mme adresse


int * p1=&refI;
// rfrence varI
int * p2=&varI;
cout << p1 << p2 << endl;
// les deux pointeurs contiennent la mme adresse

Ne pas confondre les deux sens de l'oprateur & : adresse de / dclaration d'une rfrence
int & refI=varI;
// dclaration d'une rfrence
int * p=&varI; // oprateur qui fournit l'adresse d'une variable

Paramtre "pass une fonction par rfrence"


// utilisation de la fonction
f(pAppel);
// la sortie de la fonction, le paramtre d'appel a pu tre modifi
// dfinition de la fonction
void f(int & pFormel) // pFormel est ici un paramtre de sortie
{
// le paramtre formel tant un alias du paramtre d'appel ...
pFormel++;
// equivaut pAppel++
}

Paramtre "pass une fonction par rfrence constante"


void f(const GrosType & pEntree) // pEntree est ici un paramtre d'entre
{
// si le paramtre est un gros (en nombre d'octets) paramtre d'entre,
// le passage par rfrence est plus rapide, mais il faut alors ajouter le mot cl const
// pour prvenir toute modification fortuite.
}

Les paramtres en C++ (synthse)


Un petit paramtre (char, int, double, etc.) d'entre ou d'entre/sortie peut tre pass par valeur (ou par valeur
constante.)
Un gros paramtre (objet ou structure avec beaucoup de donnes) d'entre ou d'entre/sortie doit tre pass par
rfrence constante pour des raisons de performance.
Un paramtre de sortie doit tre pass par rfrence (remplace le passage par adresse du langage C)
En C++, les tableaux sont passs aux fonctions comme en langage C, c'est--dire en fournissant l'adresse de leur premire
case et leur taille.
void f(int * tab, unsigned taille){ }

Retour d'une fonction par rfrence


int & f(int pF){ ...}
f(3)=2; // f(3) est une rfrence une "certaine" variable, et est donc modifiable.

Surcharge de fonctions Plu sieurs fonctions peuvent avoir le mme nom mais pas la mme signature
Paramtres avec valeurs par dfaut Valeur attribue au paramtre quand celui-ci fait dfaut
void f(int pI, double d=2.1);
void f(char *, int i=4);

64

Les classes en C++


Dclaration d'une classe (dans un fichier header d'extension .h)
class MaClasse
{
public:
// mthodes accs public
MaClasse();
// constructeur sans argument
MaClasse(int var);
// constructeur un argument de type int
MaClasse(const MaClasse & obj);
// constructeur de copie
~MaClasse();
// destructeur
MaClasse & operator=(const MaClasse & obj); // oprateur d'affectation
void SetVal(int val);
int GetVal() const;

// Mthode modificateur d'tat, donc non constante


// Mthode pour obtenir tout ou partie de l'tat
// donc mthode constante

private:
// donnes / mthodes accs priv
int _val;
// donnes membres encapsules ( accs priv)
double _autre;
};

Dfinition des mthodes dans un fichier spar (fichier d'extension .cpp) [source partielle]
MaClasse::MaClasse()
{
_val=0;
// la donne membre _val est initialise 0;
_autre = 2.1;
}
MaClasse::MaClasse(const MaClasse & obj)
{
// implementation non fournie
...
}
int MaClasse::GetVal() const
{
return _val; // retourne la valeur de la donne membre _val(l'tat de l'objet)
}
void MaClasse::SetVal(int val) const
{
_val=val;
// affecte une nouvelle valeur l'tat
}

A noter
-

un constructeur/un destructeur ne retourne rien (ne pas mettre void non plus)
le destructeur n'a pas d'argument
un constructeur sert donner un tat cohrent l'objet (penser initialiser toutes les donnes membres)
un accesseur est une mthode qui retourne ou donne accs l'tat de l'objet. C'est une mthode constante.
inversement, une mthode qui permet la modification de l'tat d'un objet ne doit pas tre constante.

Utilisation de la classe MaClasse


void main()
{
MaClasse objet1;
// construction d'un objet l'aide du
// constructeur sans argument
// etat = (objet1._val==0, objet1._autre==2.1)
MaClasse objet2(15);

// utilise le constructeur avec 1 argument int pour objet2

MaClasse objet3(objet1); // utilise le constructeur par copie pour objet3


cout << objet1.GetVal() << endl;
objet1.SetVal(12);

// nouvel tat : (objet1._val==12,objet1._autre==2.1)

65

Gestion des projets avec des classes en C++


Pour chaque classe, on place dans un fichier header (extension .h) la dfinition de la classe, c'est--dire son nom,
les noms et types des arguments et les prototypes des mthodes.
On place dans un fichier source (extension cpp) la dfinition des mthodes.
Enfin, pour utiliser la classe, il faut crer un projet incluant le fichier source d'utilisation de la classe (fichier
cpp) et le (ou les) fichier(s) de dfinition des mthodes. Le fichier header doit tre visible par tous les fichiers qui
l'utilisent
Fichier header (fichier d'entte)
-------------------------------------------------------------------------// fichier d'entte maclasse.h
#ifndef __MA_CLASSE__
#define __MA_CLASSE__
// directives pour compilation conditionnelle
#include <iostream>

// inclusion des fichiers ncessaires ici

class MaClasse
{
public:
MaClasse();
void Affiche(std::ostream & sortie) const;
};
#endif
--------------------------------------------------------------------------

Fichier de dfinition des mthodes (implmentation de la classe)


------------------------------------------------------------------------------------------// fichier maclasse.cpp
#include "maclasse.h" // le fichier header doit tre dans le mme rpertoire que ce source
MaClasse::MaClasse()
{
//...
}
void MaClasse::Affiche(std::ostream & sortie) const
{
sortie << "Affichage sur sortie standard";
}
-------------------------------------------------------------------------------------------

Fichier d'utilisation de la classe


------------------------------------------------------------------------------------------// utilisation.cpp
#include "maclasse.h"
// le fichier header doit tre dans le mme rpertoire que ce
source
void main()
{
MaClasse m;
m.Affiche(std::cout);
//affiche sur flot cout
}
-------------------------------------------------------------------------------------------

Projet C++ : pour pouvoir compiler et diter les liens, les deux fichiers maclasse.cp p et utilisation.cpp
doivent tre dans un mme projet.
Note : on peut galement faire un fichier librairie partir d'un ou de plusieurs fichiers de sources de
dfinition des mthodes d'une classe. Pour utiliser une classe, l'utilisateur n'a plus ensuite qu' crer un projet incluant la
librairie ( la place des fichiers sources de dfinition des mthodes).

66

Introduction aux classes standard et aux classes paramtres en type


Les classes standards telles que istream , ostream (pour les objets cin et cout), string utilisent un espace de
nom particulier :
#include <iostream>
#include <string>

// dclaration des classes de flots


// dclaration de la classe de chanes de caractres

using namespace std;

// utiliser l'espace de nom std

void main()
{
cout << "Hello world !" << endl;
string str1("abc");
string str2("def");
string str3(str1);

// construction d'un objet string

str1=str1+str2;

// l'oprateur + ralise une concatnation

// str3 est construit par copie de str1

cout << str1 << endl; // un objet string s'affiche sur le flot cout
cout << "3ime caractre de str1 :"
cout << str1[2] << endl; // un objet string s'utilise comme un tableau
}

Utilisation d'une classe paramtre en type.


La bibliothque STL fournit des classes paramtres en type pour stocker des donnes
vector<Ty>
stack<Ty>

= classe de tableaux stockant des donnes de type Ty


= classe de piles de donnes de type Ty

------------------------------------------------------------------------------------------#include <iostream>
#include <vector>
// dclaration de la classe paramtre vector<Ty>
using namespace std;

// utiliser l'espace de nom std aussi pour ces classes

void main()
{
vector<int> v1(5,2);
vector<int> v2(8);

// v1 est un vecteur de 5 int initialiss 2.


// v2 est de taille 8, valeurs initiales nulles

v1[2]=3;
v1[4]=2;

// v1 s'utilise comme un tableau

// ici, viter v1[5]=7 car les indices valides vont de 0 4 (car la taille est 5)
v1.resize(12);

// mthode qui modifie la taille du vecteur

cout << v1.size() << endl;

// vector<Ty>::size() est l'accesseur de taille

v1[11]=4;

// ici c'est bon, car le vecteur vient d'tre redimensionn

v2=v1;

// l'affectation entre deux objets vector<int> est possible

vector<char> v3(7);
v2[0]='a';

// v2 est un vecteur de caractres de taille 7

// v3 stocke donc des caractres

stack<int> pile;

// objet "pile" pour grer des int

pile.push(12);

// place la valeur 12 sur la pile

stack<float> pileF;

// objet "pile" pour grer des float

pileF.push(3.25);

// place la valeur float 3.25 sur la pile pileF

/* ce qui ne fonctionne pas (voir chapitre ddi aux classes paramtres)


v3=v1;

// les classes vector<int> et vector<char> sont diffrentes

*/
}
-------------------------------------------------------------------------------------------

67

Ralisation de la relation de composition en C++


Certaines classes ont comme attributs des objets d'une autre classe. On reprsente cela en UML soit par la
relation de composition ou bien comme le fait que certains attributs sont d'un type classe donn.
Ci- contre, deux reprsentations de la mme classe
Composite sont donnes. La reprsentation de gauche
indique que la classe Composite est compose d'un objet
_composant . La reprsentation de droite indique qu'un
attribut (_composant) de la classe Composite est de type
Composant.
Dans les deux cas, une ralisation possible en C++
de la classe Composite est la suivante

Composite

Composite
-_composant : Composant

+Composite()

+Composite()

1
1

-_composant

Composant
+Composant(in val : int)
+GetValeur() : int

----------------------------------------------------------------------//fichier composite.h
#ifndef __CLS_COMPOSITE__
#define __CLS_COMPOSITE__
#include "composant.h"
class Composite
{
public:
Composite();
Composant GetComposant() const; // retourne une copie du composant
private:
Composant _composant;
// objet membre
};
#endif
-----------------------------------------------------------------------

Dfinition des mthodes dans un fichier spar (fichier d'extension .cpp)


----------------------------------------------------------------------#include "composite.h"
Composite::Composite():_composant(12)
{
}
Composant Composite::GetComposant() const
{
return _composant;
}
-----------------------------------------------------------------------

Il faut souligner le rle de la liste d'initialisation pour les classes composites. Ci-dessus, l'objet composant est
initialis par le constructeur 1 argument de type int (seul constructeur disponible pour cette classe).
----------------------------------------------------------------------#include "Composite.h"
void main()
{
Composite c1;
Composant c2(c1.GetComposant());
}
-----------------------------------------------------------------------

Remarque : pour l'exemple ci-dessus, la classe Composant doit tre dote d'un constructeur de copie valide :
d'une part pour que l'accesseur Composite::GetComposant() retourne une copie du composant et que la construction de
c2 soit possible dans le programme d'utilisation.

68

Ralisation de la relation de spcialisation en C++


Une classe dcrit ce que les objets d'un ensemble ont en commun en termes de mthodes et d'attributs. D'un
point de vue ensembliste, une sous-classe caractrise donc un sous-ensemble d'objets. Soit cette sous-classe a des attributs
supplmentaires permettant d'apporter une reprsentation plus prcise des objets, soit elle se distingue par le fait que les
attributs ne peuvent prendre que certaines valeurs. La relation sous-classe/super-classe ou relation de spcialisation/
gnralisation se reprsente en UML par une flche. La flche part de la sous-classe (ou classe spcialise) et dsigne la
super-classe (la classe la plus gnrale).
--------------------------------------------// fichier header de la classe Specialisee
#ifndef __CLS_SPECIALISEE__
#define __CLS_SPECIALISEE__
#include "generale.h"
class Specialisee : public Generale
{
public:
Specialisee();
void MethodeA();
void MethodeB();
};

Generale
+Generale(in s : string)
+MethodeA()
+MethodeB(in s : string)
+MethodeC()

Specialisee

#endif
---------------------------------------------

+Specialisee()
+MethodeA()
+MethodeB()

Dfinition des mthodes dans un fichier spar (fichier d'extension .cpp)


----------------------------------------------------------// dfinition des mthodes de la classe Specialisee
#include "specialisee.h"
Specialisee::Specialisee():Generale(const std::string & s)
{
}
void Specialisee::MethodeA()
{
// masque la mthode de la super-classe
// ...
}
-----------------------------------------------------------

L encore, il faut souligner le rle de la liste d'initialisation. Ci-dessus, l'objet spcialis initialise la partie hrite
par le constructeur 1 argument de type string. On peut supposer qu'ici la classe gnrale contient un objet membre de
type string, mais a importe peu. Ce qui importe c'est de regarder l'interface de la super-classe pour savoir quels sont les
constructeurs disponibles.
Toutes les mthode publiques de la super-classe sont utilisables sur la sous-classe. Eventuellement, certains
phnomnes de masquage ncessitent l'utilisation du nom de la classe et de l'oprateur de porte ::
----------------------------------------------------------#include "specialisee.h"
void main()
{
Specialisee s1;
s1.MethodeC();

// la mthode C est accessible pour la sous-classe

s1.MethodeB();
// mthode B de la super-classe
s1.MethodeB(std::string("toto"));
// mthode B de la sous-classe
s1.MethodeA();

// la mthode de la classe spcialise


// masque celle de la super-classe

s1.Generale::MethodeA();

// en raison du masquage, il faut prciser


// si l'on souhaite
// utiliser la mthode de la super-classe.

}
-----------------------------------------------------------

69

Le polymorphisme en C++
Lorsqu'une hirarchie de classes existe, le C++ autorise certaines conversions de type. D'un point de vue
ensembliste, il est normal que les objets d'une sous-classe puissent tre aussi considrs comme des objets de la superclasse. Par exemple, la classe des tres humains tant un sous-ensemble de celle des tres vivants, un tre humain est un
tre vivant.
class EtreVivant
{
public:
void Methode();
};

EtreVivant
+Methode()

class Animal: public EtreVivant


{
public:
void Methode();
};

Animal
+Methode()

EtreHumain
+Methode()

class Europeen: public EtreHumain


{
public:
void Methode();
};

Europeen
+Methode()

Pour la hirarchie dcrite ci-dessus, certaines conversions de types sont possibles. Par ailleurs, faute de prcision,
la ligature des mthodes est statique. Cela signifie que via un pointeur de type EtreHumain *, on n'accde qu'au
mthodes de la classe EtreHumain ou des super-classess (par exemple EtreVivant ).
void main()// conversion sous-classe * -> classe *
{
Europeen E;
Animal A;
EtreHumain * ptrEH=&E;
// Europeen * -> EtreHumain *
ptrEH->Methode();

// appel de la mthode de la classe EtreHumain mme si l'objet


// point est de type Europeen

ptrEH->EtreVivant::Methode();

//mthode galement accessible

EtreVivant * ptrEV = ptrEH; // conversion EtreHumain * -> EtreVivant *


ptrEV->Methode();

// appel de la mthode de la classe EtreVivant

EtrVivant * ptrG2 = &A;


}

// Animal * -> EtreVivant *

Ligature dynamique : on peut prciser dans la classe de base si une recherche dynamique de mthode doit tre
effectue (mot cl virtual).
class EtreVivant
{
public:
virtual void Methode(); // demande au compilateur de mettre en place un lien dynamique
virtual ~EtreVivant();
// le destructeur doit tre virtuel
};
void main()
{
EtreVivant * tableau[4];
tableau[0]=new EtreVivant;
tableau[1]=new Animal;
tableau[2]=new Europeen;
tableau[3]=new EtreHumain;
tableau[0]->Methode();
tableau[1]->Methode();
tableau[2]->Methode();
tableau[3]->Methode();

//
//
//
//

mthode
mthode
mthode
mthode

de
de
de
de

la
la
la
la

classe
classe
classe
classe

EtreVivant
Animal
Europeen
EtreHumain

for(unsigned i=0;i<4;i++) delete tableau[i];


}

70

Les patrons de classes en C++


En C++, on peut introduire des paramtres de type. Pour les classes paramtres, il est prfrable de placer la
dfinition des mthodes (elles-mmes paramtres) dans la dfinition de la classe. Le fichier header (.h) contient alors
toute l'implmentation. Il n'y a pas de fichier de dfinition cpp pour les patrons de classe.
Exemple d'un patron de conteneurs de type tableau (accs direct).
-----------------------------------------------------------------------------#ifndef __CLSPARAM__
#define __CLSPARAM__
template<class T>
class Tableau
{
public:
Tableau<T>(unsigned size=1)
{
_tab=new T[size];
_size=size;
}
Tableau<T>(const Tableau<T> & t)
{
_tab=new T[t._size];
_size=t._size;
for(unsigned i=0;i<_size;i++) _tab[i]=t._tab[i];
}
Tableau<T>& operator=(const Tableau<T> & t)
{
T* ptr=_tab;
_tab=new T[t._size];
_size=t._size;
for(unsigned i=0;i<_size;i++) _tab[i]=t._tab[i];
delete [] ptr;
return *this;
}
~Tableau<T>(){ delete [] _tab;}
void GetElement(unsigned i) const { return _tab[i];}
void SetElement(unsigned i, const T & valeur) { _tab[i]=valeur; }
unsigned GetSize() const { return _size;}
T& operator[](unsigned i) { return _tab[i]; }
const T & operator[](unsigned i) const { return _tab[i]; }
private:
T * _tab;
// T est le paramtre de type
unsigned _size;
};
#endif
------------------------------------------------------------------------------

Utilisation du patron :
-----------------------------------------------------------------------------#include "Tableau.h"
#include "Rationnel.h"
#include "Point.h"
void main()
{
Tableau<int> tab1(3);
Tableau<Rationnel> tab2(4);
Tableau<Point> tab3(10);
tab1.SetElement(2,3);
tab2.SetElement(0,Rationnel(1,2));
tab1[0]=4;
}
------------------------------------------------------------------------------

71

Vous aimerez peut-être aussi