Académique Documents
Professionnel Documents
Culture Documents
CPP Cotten Ce Au 0506
CPP Cotten Ce Au 0506
Anne 2005-2006
Programmation
en C++
1
Bertrand Cottenceau
1.
2.
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 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
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;
// ne pas excuter *ptr=45 ici, la variable pointeur nest pas initialise !!!
ptr=&var ;
*ptr=45 ;
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.
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 ;
//
//
//
//
//
2.1.4.
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 for(
; ;){}
2.1.5.
// tab[indice]=indice + (indice-1)+...
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 ;
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)
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]);
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);
//
//
//
//
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.
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);
}
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.
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 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.
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.
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
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.
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.
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 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.
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));
}
Rsultats
15
10
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
//
//
//
//
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 );
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; }
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++.
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
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.SetXY(10,10);
p2.SetXY(20,20);
p1.Display(cout);
p2.Display(cout);
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
// constructeur 2 arguments
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.
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.
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;
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.
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.
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.
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.
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.
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);
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.
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; }
return _den; }
23
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.
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
-
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;
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;
25
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
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
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
s1.insert(2,4,'a');
cout << s1 << endl;
s1.erase(0,1);
cout << s1 << endl;
s1.erase(2,5);
cout << s1 << endl;
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
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;
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
}
---------------------------------------------------------------------------------
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;
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) << " ";
}
---------------------------------------------------------------------------------
-_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
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; }
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.
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.
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]; }
Remarque : une autre ralisation possible de la composition est propose dans la section traitant des classes
avec donnes en profondeur.
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
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.
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
}
---------------------------------------------------------------------------------
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
8.3.
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();
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();
38
ptrDeNF=ptrDe6;
ptrDeNF->Lancer();
}
---------------------------------------------------------------------------------
8.4.
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)
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.
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.
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;
};
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.
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();
_indPProtegeB=true;
// _dPriveeB=3;
FonctionProtegeeB();
//FPriveeB();
Accs un utilisateur
de la classe drive
Oui
Non
Non
Nouveau statut
dans la classe drive2
Public
Protg
Priv
44
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.
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;
};
};
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();
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
}
-----------------------------------------------------------------------------------------
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
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");
46
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
return _XY[0]; }
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.
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
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
50
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
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.
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
}
---------------------------------------------------------------------------------
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.
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.
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.
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;
}
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
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();
12.5.
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
re ligne
0-Press any key to continue
59
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)
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);
...
};
13.2.
// conversion explicite
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)
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)
3.4+3i
14)
obj :0x0066FDDC
9.4
15)
16)
62
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 !
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
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
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.
65
class MaClasse
{
public:
MaClasse();
void Affiche(std::ostream & sortie) const;
};
#endif
--------------------------------------------------------------------------
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
void main()
{
cout << "Hello world !" << endl;
string str1("abc");
string str2("def");
string str3(str1);
str1=str1+str2;
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
}
------------------------------------------------------------------------------------------#include <iostream>
#include <vector>
// dclaration de la classe paramtre vector<Ty>
using namespace std;
void main()
{
vector<int> v1(5,2);
vector<int> v2(8);
v1[2]=3;
v1[4]=2;
// ici, viter v1[5]=7 car les indices valides vont de 0 4 (car la taille est 5)
v1.resize(12);
v1[11]=4;
v2=v1;
vector<char> v3(7);
v2[0]='a';
stack<int> pile;
pile.push(12);
stack<float> pileF;
pileF.push(3.25);
*/
}
-------------------------------------------------------------------------------------------
67
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
-----------------------------------------------------------------------
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
Generale
+Generale(in s : string)
+MethodeA()
+MethodeB(in s : string)
+MethodeC()
Specialisee
#endif
---------------------------------------------
+Specialisee()
+MethodeA()
+MethodeB()
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();
s1.MethodeB();
// mthode B de la super-classe
s1.MethodeB(std::string("toto"));
// mthode B de la sous-classe
s1.MethodeA();
s1.Generale::MethodeA();
}
-----------------------------------------------------------
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()
Animal
+Methode()
EtreHumain
+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();
ptrEH->EtreVivant::Methode();
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
70
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