Sie sind auf Seite 1von 143

Anne universitaire 2001-2002

ISTIA-Universit dAngers

Programmation oriente objet en C++

-1Dpartement Automatisation et Informatisation - Bertrand Cottenceau

-2-

Ce support de cours de C++ a t ralis partir dun document de Serge Tah crit en 1993 pour lISTIA. Je tiens donc le remercier ici pour la base de travail que son document a fournie.

Les exemples donns dans ce document ont t valus avec le compilateur de Visual C++ 6. Pour toute documentation supplmentaire : Programmer en langage C++ C. Delannoy - Eyrolles (597 pages). Comprendre et utiliser C++ pour programmer objets G. Clavel I. Trillaud et L.Veillon Masson (234 pages) Pont entre C et C++ P.-N. Lapointe Addison-Wesley (184 pages)

Bertrand Cottenceau ISTIA Bureau 311 Email : bertrand.cottenceau@istia.univ-angers.fr URL :http://www.univ-angers.fr/~cottence

-3-

-4-

TABLE DES MATIERES


1 INTRODUCTION 1.1 Rappels sur le langage C 1.2 Introduction aux concepts de la Programmation Oriente Objet 1.2.1 La programmation procdurale 1.2.2 Les ides de la POO 1.3 Exemples de classes en C++ 1.3.1 Une classe point 1.3.2 Une seconde classe point 2 EXTENSIONS AU LANGAGE C 2.1 Les oprations d'entres/sorties 2.1.1 Le flot cout 2.1.2 Le flot cin 2.2 Nouvelle forme de commentaire 2.3 Emplacement des dclarations et porte des variables 2.4 La notion de rfrence 2.4.1 La rfrence comme nouveau type de donnes 2.4.2 Transmission d'arguments par rfrence 2.4.3 Fonction retournant une rfrence 2.5 Arguments dune fonction avec valeur par dfaut 2.6 Surcharge (ou surdfinition) de fonctions 2.7 Les oprateurs de gestion de mmoire 2.7.1 L'oprateur new 2.7.2 L'oprateur delete 2.8 Incompatibilits entre C++ et C 2.8.1 Prototypes de fonctions 2.8.2 Conversion d'un pointeur de type void * 2.8.3 Expressions constantes 2.8.4 Identification d'une structure 2.9 Le modificateur const et les rfrences constantes 2.9.1 Le modificateur const 2.9.2 Les rfrences constantes 2.10 Conclusion 3 LA SURCHARGE DOPERATEURS EN C++ 3.1 La pluralit des oprateurs du C++ 3.2 Les oprateurs membres dune classe 3.3 Les oprateurs avec traitement par dfaut 3.3.1 Le traitement par dfaut de loprateur = 3.3.2 Le traitement par dfaut de loprateur & 3.4 Les oprateurs que lon peut surcharger en C++ 3.5 Le mcanisme de surdfinition des oprateurs en C++ 3.6 Exemple de surdfinition doprateurs 3.6.1 Surcharge des oprateurs + et * pour des structures 3.6.2 Choix du paramtrage des oprateurs. 3.6.3 Utilisation de loprateur = par dfaut. 3.7 Conclusion 4 LES CLASSES D'OBJETS 4.1 4.2 4.3 4.4 Les structures Les classes Les mthodes d'une classe Constructeur et destructeur d'une classe -59 9 10 11 11 13 13 14 17 17 17 18 19 19 19 20 20 23 24 25 26 26 27 27 28 28 28 29 29 29 30 33 35 35 35 35 35 36 36 36 37 37 38 39 39 41 41 43 47 48

4.5 Exemple 1 49 4.6 Exemple 2 50 4.7 Exemple 3 52 4.8 Objets avec des donnes en profondeur 54 4.9 Les mthodes constantes 56 4.10 Le pointeur this 58 4.11 Affectation d'un objet un autre objet 58 4.11.1 Affectation d'un objet un autre objet dune mme classe lorsque les objets nont pas de donnes en profondeur 59 4.11.2 Affectation d'un objet un autre objet dune mme classe lorsque les objets ont des donnes en profondeur 60 4.12 Redfinition de loprateur = pour des objets avec donnes en profondeur. 62 4.12.1 Paramtrage de loprateur daffectation = 62 4.12.2 Etapes ncessaires la copie dobjets avec donnes en profondeur. 63 4.13 Les objets temporaires 65 4.14 L'initialisation d'un objet sa dclaration l'aide du signe = 66 4.14.1 Exemple 66 4.14.2 Notion de constructeur par recopie dit aussi constructeur-copie 67 4.14.3 Le constructeur-copie par dfaut 68 4.14.4 Exemple : ajout dun constructeur-copie la classe personne 68 4.15 Passage/retour d'un objet /par une fonction 70 4.15.1 Passage dun objet par valeur une fonction 72 4.15.2 Passage d'un objet une fonction par rfrence 74 4.15.3 Retour d'un objet par valeur 74 4.15.4 Utilisation d'un objet retourn par valeur par une fonction 75 4.15.5 Retour d'un objet par rfrence 76 4.15.6 Pourquoi largument du constructeur-copie ne peut-il pas tre pass par valeur ? 76 4.15.7 Conclusion 77 4.16 Les tableaux d'objets 77 4.16.1 Dclaration et initialisation d'un tableau d'objets 77 4.16.2 Utilisation de la syntaxe delete[] 78 5 ELABORATION D'UNE CLASSE DE CHAINES DE CARACTERES. 5.1 Introduction 5.2 Lutilisation et linterface de la classe 5.3 Les donnes de la classe chaine. 5.4 Les constructeurs et le destructeur de la classe chaine 5.5 Factorisation dun traitement. 5.6 Les oprateurs membres ou non membres dune classe. 5.7 Les oprateurs de la classe chaine 5.7.1 Loprateur = pour laffectation entre objets de type chaine 5.7.2 Les oprateurs dindexation [] 5.7.3 Loprateur de concatnation (oprateur +) 5.7.4 Loprateur << 5.8 La classe chaine au complet 6 AMITIE : FONCTIONS AMIES, CLASSES AMIES. 6.1 6.2 6.3 6.4 6.5 6.6 7 Introduction Fonction amie dune classe. Fonction membre dune classe amie dune autre classe. Fonction amie de plusieurs classes. Classe amie dune autre classe. Exemple : loprateur << de la classe chaine 81 81 82 83 83 85 86 87 87 88 90 91 93 97 97 97 98 99 99 99 101 101 101

LES CHANGEMENTS DE TYPE. 7.1 7.2 Introduction Conversion dun type de base vers un type classe par un constructeur -6-

7.3 7.4 7.5 8

Conversion dun type classe vers un type de base Exemple Conclusion

102 103 106 107 107 107 108 110 111 111 112 113 113 113 114 114 115 115 116 117 119 121 123 125 126 127 128 129

UTILISATION DE CLASSES EXISTANTES PAR COMPOSITION 8.1 Introduction 8.2 Ecriture dune nouvelle classe personne 8.2.1 Dfinition de la classe personne 8.2.2 Les constructeurs de la classe personne et la liste dinitialisation 8.2.3 Loprateur = de la classe personne 8.2.4 Le constructeur-copie et loprateur = par dfaut. 8.2.5 Loprateur << pour laffichage des objets de type personne

UTILISATION DE CLASSES EXISTANTES PAR DERIVATION 9.1 9.2 9.3 9.4 9.5 9.6 9.7 9.8 9.9 9.10 9.11 9.12 9.13 9.14 9.15 9.16 Introduction classe point Drivation de la classe point en pointC Accs aux champs de lobjet pre Dfinition de la classe pointC Les constructeurs de la classe pointC Compatibilit drive -> base : le constructeur-copie de la classe pointC Modification de la classe de base point et complment de la classe pointC Ordre dappel des constructeurs de la classe de base et de la classe drive Que retenir sur la drivation publique ? Formes canoniques Accs aux donnes et aux mthodes dans le cadre de lhritage Les diffrentes formes de drivation Le typage statique (ou ligature statique) Fonctions virtuelles : typage dynamique Application du typage dynamique

10 FONCTIONS GENERIQUES ET CLASSES GENERIQUES. PATRONS DE FONCTIONS ET PATRONS DE CLASSES 133 10.1 10.2 10.3 10.4 10.5 10.6 10.7 10.8 11 11.1 11.2 Introduction Patron de fonctions : un exemple Les limites de ce patron Les paramtres expression Spcialisation dun patron de fonctions Patron de classes Utilisation de patrons dans la cadre de lhritage Conclusion ANNEXE Travailler sous forme de projet en C++ Les directives de compilation conditionnelle. 133 134 135 135 135 136 138 140 141 141 141

-7-

-8-

INTRODUCTION

Le langage C++ constitue une sur-couche du langage C. Il utilise la syntaxe du langage C laquelle a t ajoute la notion de classe qui reprsente un modle dobjets, de mme quun type reprsente un modle de variables. Il sagit dun langage dit orient objet (et non objet pur). En C++, on peut sans problme mlanger programmation dite procdurale et programmation objet. De ce fait, le caractre objet dun programme dpendra essentiellement de la bonne volont du programmeur se conformer aux concepts de la programmation objet quon prsente brivement dans ce chapitre. 1.1 Rappels sur le langage C

Puisque le C++ est greff sur le langage C, il est important de matriser certaines notions de base du C. On utilisera notamment dans ce cours : les types scalaires du C :
int, float, long, double, short, char,

les tableaux :
char chaine[26]= "bonjour"; int tab[3]={1,2,5};

les types structurs (la notion de classe gnralise dailleurs la notion de structure) :
struct fiche { char nom[30]; char prenom[30]; unsigned int age; };

Aprs cette dfinition de type, lidentificateur fiche reprsente un type (et non une variable). On peut ensuite dclarer une variable du type fiche avec la syntaxe
fiche f; f.age = 15; // f est une variable de type fiche // on accde au champ age de la variable f

la notion de variable pointeur


int var = 12; int * ptr; ptr = &var; *ptr = 14; // // // // // // var est une variable de type entier p est une variable de type pointeur sur entier on affecte ptr ladresse de var loprateur * (oprateur dindirection) dsigne la variable de type int dont ladresse figure dans le pointeur ptr. Ici, cette variable est donc var.

Une variable pointeur est une variable qui permet de stocker ladresse dun objet. Loprateur dindirection permet de dsigner lobjet dont ladresse figure dans une variable pointeur.

-9-

Remarque (loprateur ->) : lorsquun pointeur est initialis avec ladresse dune structure, loprateur -> a un sens particulier.
fiche f; f.age=15; fiche * ptr = &f ; ptr->age=16; // f est une variable de type fiche

// ptr est un pointeur sur une fiche, initialis avec ladresse de f // quivalent (*ptr).age = 16. // quivaut ici f.age = 16

les structures de contrle du C. La structure if(){}else{}


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

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


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

La structure for( ; ;){}


int tab[10]; int indice; tab[0]=0; // rserve un tableau de 10 entiers (non initialiss) // initialise le premier lment 0

for(indice=1;indice<10;indice++) { tab[indice]=tab[indice-1]+indice; }

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

1.2

Introduction aux concepts de la Programmation Oriente Objet

Il convient tout dabord de se rappeler en quoi consiste la programmation dite procdurale pour comprendre ce qui diffre en Programmation Oriente Objet (POO).

- 10 -

1.2.1

La programmation procdurale

Avec des langages tels que le C ou le Pascal, la rsolution dun problme informatique passe gnralement (mais pas ncessairement) par lanalyse descendante1. On dcompose ainsi un programme en un ensemble de sousprogrammes appels procdures qui cooprent pour la rsolution dun problme. Les procdures et fonctions sont gnralement des outils qui produisent et/ou modifient des structures de donnes.

Structure de donnes Procdure Entre

Structure de donnes

Sortie

La programmation procdurale suit lquation de Wirth : ALGORITHMES + STRUCTURES DE DONNEES = PROGRAMME Autrement dit, on distingue clairement les structures de donnes des algorithmes qui les manipulent. Inconvnient de la programmation procdurale Lvolution dune application dveloppe suivant ce modle nest pas vidente. En effet, la moindre modification des structures de donnes dun programme conduit la rvision de toutes les procdures manipulant ces donnes. En outre, pour de trs grosses applications, le simple fait de rpertorier toutes les fonctions manipulant une structure de donnes peut dj tre problmatique. 1.2.2 1.2.2.1 Les ides de la POO Objet = mthodes + donnes

Lapproche objet consiste mettre en relation directement les structures de donnes avec les algorithmes qui les manipulent. Un objet regroupe la fois des donnes et des algorithmes de traitement de ces donnes. Au sein dun objet, les algorithmes sont gnralement appels des mthodes. Ainsi, par analogie avec lquation de Wirth, on peut dire que la POO suit lquation

DONNEES

METHODES + DONNEES = OBJET

METHODES

Premier avantage On matrise plus facilement lincidence dune volution de la structure de donnes dun objet. Les rpercussions principales se situent au niveau des mthodes de lobjet.

L'analyse descendante est en quelque sorte la mise en pratique du Discours de la Mthode de Descartes appliqu aux algorithmes. Elle consiste dcomposer un problme en sous-problmes et dcomposer ceux-ci, eux-mmes, en sous-problmes, et ainsi de suite jusqu' descendre au niveau des actions dites primitives. - 11 -

1.2.2.2

Lencapsulation des donnes

Au sein dun objet, les donnes sont protges. Celles-ci ne sont pas accessibles directement depuis lextrieur de lobjet. Laccs aux donnes dun objet se fait toujours via les mthodes de lobjet qui, de ce fait, jouent le rle dinterface entre les donnes (internes) et lextrieur.

Les donnes de lobjet ne sont accessibles que par ses mthodes

DONNEES

METHODES

Les mthodes servent dinterface

Cette protection des donnes au sein dun objet sappelle lencapsulation de donnes. En raison de cette encapsulation des donnes, on peut dire quun objet ralise une abstraction de donnes dans ce sens quun utilisateur de lobjet ne peut pas savoir prcisment comment les donnes sont implmentes. A quoi sert lencapsulation de donnes ? Lencapsulation assure un meilleur contrle des structures de donnes et de leur intgrit. Pour expliquer ceci, on va tout dabord analyser un exemple en C.
#include <iostream.h> void main() { int tab[4]={1,2,3,4}; char chaine1[8]="abcdefg"; chaine1[12]='a'; for(int i=0;i<4;i++) { printf("%d ",tab[i]); } }

Rsultat
1 97 3 4

On rserve ici deux tableaux : le premier de 4 entiers intialiss, le second de 8 caractres galement intialiss. Dans cet exemple, rien ne nous empche dcrire la ligne suivante.
chaine1[12]='a';

Et pourtant, il est vident que cette chane ne peut pas stocker plus de 8 caractres ! Autrement dit, le langage C nous autorise crire nimporte o en mmoire, dans le voisinage du tableau chaine1[]. En loccurrence, on crit ici directement dans le tableau dentiers tab[]. Lorsque lon crit lemplacement chaine1[12], on modifie en fait lentier tab[1] (Il faut noter que 97 est le code ASCII de la lettre a). En langage C, cest la charge du programmeur de grer les indices correctement pour que ceux-ci ne dpassent pas la taille du tableau initialement rserv. - 12 -

Grce lencapsulation, lapproche objet palie ce problme en implmentant les tableaux de la manire suivante : Donnes encapsules :
Ptr N

adresse de base dune zone de stockage dune taille donne taille de la zone de stockage mthode dcriture dune valeur dans le tableau lindice i mthode de lecture de llment dindice i

Mthodes :
Ecriture(valeur,i) Lecture(i)

Puisquen programmation objet laccs aux donnes se fait via linterface de lobjet (ses mthodes), il suffit ici de vrifier dans chacune des mthodes de lecture et dcriture que lindice i est bien infrieur N pour autoriser laccs aux donnes du tableau. Grce ce filtre, lutilisateur de lobjet ne peut pas crire nimporte o dans le voisinage proche de la zone mmoire rserve pour le tableau. 1.2.2.3 La classe

La classe est un modle pour gnrer des objets. Une classe permet de spcifier, pour tous les objets faits partir de cette classe, comment les donnes sont structures et quelles mthodes permettent de manipuler ces donnes. Ensuite, la valeur des donnes peut videmment diffrer dun objet lautre dune mme classe. La notion de classe gnralise ainsi la notion de type et la notion dobjet gnralise celle de variable. On dira galement quun objet est une instance dune classe ou, plus simplement, quun objet est une variable dune classe. 1.2.2.4 Lhritage

Lhritage correspond la possibilit de crer une classe dobjets, dite classe drive, partir dune classe existante, dite classe de base. Dans le processus dhritage, la classe drive hrite des caractristiques (structures de donnes + mthodes) de la classe de base. En plus de cet hritage, on peut ajouter dans la classe drive des donnes et des fonctionnalits nouvelles. Lhritage conduit ainsi une spcialisation de la classe de base. Cette caractristique des langages objets amliore la rutilisabilit logicielle. En effet, lexprience montre quil est plus facile de rutiliser des classes que de rutiliser des bibliothques de fonctions qui ont, la plupart du temps, t crites pour des cas prcis, et donc des structures de donnes bien particulires. 1.3 Exemples de classes en C++

On anticipe un peu sur les chapitres suivants pour pouvoir illustrer ce qui a t prsent dans ce premier chapitre. 1.3.1 Une classe point
// point.h #include<stdio.h> class point { private : float x; float y; public: void Init(float abs,float ord){ x = abs; y = ord;} float GetX() { return x; } float GetY() { return y; } void Affiche(){ printf("( %f , %f )\n", x , y ); } };

- 13 -

Utilisation de la classe :
#include "point.h"

Sortie cran
void main() { point p1; p1.Init(10,20); p1.Affiche(); printf("abscisse de p1 : %f\n", p1.GetX()); } (10,20) abscisse de p1 : 10

Ce premier exemple en C++ dfinit une classe appele point. Les objets de cette classe reprsentent tout simplement des points du plan 3. Les donnes membres : les champs x et y de type float reprsentent les donnes membres des objets de cette classe. Ces champs permettront simplement de stocker des coordonnes. Lattribut private indique que ces champs ne sont pas accessibles depuis lextrieur de lobjet (les donnes sont encapsules). Les mthodes : les fonctions Init(), GetX(), GetY() et Affiche() sont les mthodes de cette classe. Ces fonctions joueront le rle dinterface.
Init() : permet linitialisation des coordonnes dun point GetX(), GetY() : retournent respectivement labscisse et lordonne dun point Affiche() : permet laffichage lcran dun point sous la forme (x,y)

Laccs un membre dun objet se fait par loprateur . , comme pour les types structurs. Par exemple, la syntaxe
p1.Affiche();

correspond lappel de la mthode Affiche() sur lobjet p1, ce qui permet lobjet p1 de safficher lcran. Cela revient en quelque sorte envoyer le message Affiche-toi ! lobjet p1. Remarque : ce changement de rle est toujours droutant au dbut. En C, on aurait crit une fonction qui reoit lobjet p1 comme argument. Ici, cest linverse. Cest au sein mme de lobjet que le mcanisme daffichage est prvu.
Affiche(point p)

Notons que les donnes sont dclares prives, la ligne suivante nest donc pas compile avec succs :
p1.x = 12;

Il nest pas possible datteindre directement les champs privs. Dans cet exemple, les modifications des champs x et y ne peuvent donc seffectuer que par une seule mthode : la mthode Init().

1.3.2

Une seconde classe point

Spcification : on souhaite cette fois-ci disposer dune classe de points pour laquelle tous les objets ont des coordonnes entires et uniquement dans lensemble [0, 1023] [0,767]. Cette classe nous permettra de reprsenter les pixels dun moniteur vido. Lencapsulation autorise un meilleur contrle des donnes. On montre dans lexemple suivant quel traitement effectuer pour sassurer que les donnes restent toujours conformes la spcification.

- 14 -

Dfinition de la classe point :

//point.h #include<stdio.h> class point { private: int x; int y; public: void Init(int abs,int ord) { if(abs >= 0 && abs<1024) x = abs; // contrle de la validit de labscisse else { if(abs>=1024) x=1023; else x= 0; } if(ord >= 0 && ord<768) y = ord; // contrle de la validit de lordonne else { if(ord>=768) y=767; else y= 0; } } void Affiche(){ printf("(%d,%d)\n",x,y); } };

Utilisation de la classe point :

#include "point.h" void main() { point p1,p2,p3; p1.Init(45,89); p2.Init(2045,96); p1.Affiche(); p2.Affiche(); }

Sortie cran
(45,89) (1023,96)

La mthode Init(), qui est la seule mthode permettant la modification des donnes de lobjet, ralise un filtre interdisant lutilisateur de la classe de corrompre les donnes de lobjet. Mme si lutilisateur de la classe cherche initialiser un point avec des coordonnes non valides, la mthode ralise une troncature des coordonnes.

- 15 -

- 16 -

EXTENSIONS AU LANGAGE C

Nous prsentons dans ce chapitre les nouveauts apportes par le C++ en dehors des objets. Certaines dentre elles amliorent la facilit d'utilisation du langage et peuvent justifier elles-seules l'utilisation du C++ la place du C. L'aspect objet du langage sera abord ultrieurement. 2.1 Les oprations d'entres/sorties

En C, il existe une grande varit de fonctions de lecture et d'criture d'informations : . pour la lecture : scanf(), gets(), cgets(), getchar(), getch(), getche() . pour lcriture : printf(), puts(), putchar(), putch() ... Le C++ offre la possibilit de faire ces oprations l'aide d'objets appels flots. Le flot cin permet de lire les informations provenant du fichier stdin (clavier normalement). Le flot cout permet d'crire des informations sur le fichier stdout (cran normalement). 2.1.1 Le flot cout

Le flot cout permet d'crire des expressions de n'importe quel type comme le fait printf() mais sans avoir prciser de format d'criture. La syntaxe est la suivante :
cout << expr1 << expr2 << ... << exprn ;

pour crire les expressions expri. Le flot cout est dfini dans le fichier iostream.h qui devra donc faire l'objet d'un
#include. #include <iostream.h> #include <conio.h> void main(void) { int i=-3; unsigned int ui=4; char c='A'; char chaine[10]="Martin"; long l = 100000; unsigned long ul=111111; float f=3.5; double d=4.0; long double ld=165.7e+10; int *adr=&i; clrscr(); cout << "Valeur cout << "Valeur cout << "Valeur cout << "Valeur cout << "Valeur cout << "Valeur cout << "Valeur cout << "Valeur cout << "Valeur cout << "Valeur } /* ncessaire pour grer les flots cin et cout */

Rsultats (sortie stdout)

Valeur Valeur Valeur Valeur Valeur Valeur Valeur Valeur Valeur Valeur

de de de de de de de de de de

i :-3 ui :4 c :A chaine :Martin l :100000 ul :111111 f :3.5 d :4 ld :1.657e+12 adr :0x8ed1fff4

de de de de de de de de de de

i :" << i << "\n"; ui :" << ui << "\n"; c :" << c << "\n"; chaine :" << chaine << "\n"; l :" << l << "\n"; ul :" << ul << "\n"; f :" << f << "\n"; d :" << d << "\n"; ld :" << ld << "\n"; adr :" << adr << "\n";

Exemple 1. Utilisation du flot cout. - 17 -

2.1.2

Le flot cin

Le flot cin permet de lire des donnes de tout type comme le fait scanf(), avec cependant les diffrences suivantes: . Il n'y a pas lieu de prciser de format de lecture . Il n'y a pas lieu de donner l'adresse de la variable lue. La syntaxe est la suivante :
cin >> var1 >> var2 >> ... >> varn ;

pour lire n donnes et les affecter aux n variables vari.


#include <iostream.h> void main(void) { int i; unsigned int ui; char c; char chaine[10]; long l; unsigned long ul; float f; double d; long double ld; cout << "Valeur de cin >> i; cout << "Valeur de cin >> ui; cout << "Valeur de cin >> c; cout << "Valeur de cin >> chaine; cout << "Valeur de cin >> l; cout << "Valeur de cin >> ul; cout << "Valeur de cin >> f; cout << "Valeur de cin >> d; cout << "Valeur de cin >> ld; cout << " cout << "Valeur de cout << "Valeur de cout << "Valeur de cout << "Valeur de cout << "Valeur de cout << "Valeur de cout << "Valeur de cout << "Valeur de cout << "Valeur de } /* ncessaire pour grer les flots cin et cout */

Rsultats

i (int) :"; ui (unsigned) :"; c (char) :"; chaine (char *) :";

Valeur Valeur Valeur Valeur Valeur Valeur Valeur Valeur Valeur

de de de de de de de de de

i (int) :-5 ui (unsigned) :40 c (char) :B chaine (char *) :CHARON l (long) :100000 ul (unsigned long) :2222222 f (float) :4.5e+10 d (double) :5e+50 ld (long double) :6e+100

Valeurs lues l (long) :"; ul (unsigned long) :"; f (float) :"; d (double) :"; ld (long double) :"; Valeurs lues \n\n\n"; i :" << i << "\n"; ui :" << ui << "\n"; c :" << c << "\n"; chaine :" << chaine << "\n"; l :" << l << "\n"; ul :" << ul << "\n"; f :" << f << "\n"; d :" << d << "\n"; ld :" << ld << "\n"; Valeur Valeur Valeur Valeur Valeur Valeur Valeur Valeur Valeur de de de de de de de de de i :-5 ui :40 c :B chaine :CHARON l :100000 ul :2222222 f :4.5e+10 d :5e+50 ld :6e+100

Exemple 2. Utilisation du flot cin. - 18 -

2.2

Nouvelle forme de commentaire

Le commentaire s'crit en C sous la forme


/* commentaire .... */

En C++, un commentaire en fin de ligne peut tre crit sous la forme :


// commentaire ...

A la rencontre du double caractre //, le compilateur considre le reste de la ligne comme un commentaire. 2.3 Emplacement des dclarations et porte des variables

La dclaration d'une donne en C++ peut se faire n'importe o dans le programme pour peu qu'elle soit faite avant l'utilisation de la donne dclarer. Sa porte est celle du bloc o se trouve la dclaration.
#include <iostream.h> void main(void) { int x=30; int y=40; cout << x << y << endl; { int aux = x; // la variable aux nest dfinie qu lintrieur de ce bloc x = y; y = aux; } cout << x << y << endl; cout << aux << endl; } // << cette ligne produirait une erreur de compilation

Exemple 3. Dclaration et porte des variables en C++ Note : le modificateur de flot endl produit un passage au dbut de la ligne suivante (comme cout << "\n" ;) Commentaire sur lexemple 3 : . les variables x et y sont locales la fonction main(). Leur porte va du dbut de la fonction main() jusqu la dernire accolade. . la variable aux est dclare dans le bloc allant de la deuxime accolade { la premire accolade }. Sa porte est donc limite ce bloc. En dehors de ce bloc, la variable aux nexiste pas. 2.4 La notion de rfrence

En C++, la notion de rfrence consiste pouvoir donner plusieurs identificateurs (noms) une mme variable. Cette caractristique, prsente galement dans la langage PASCAL, facilitera par la suite la dfinition des paramtres de sortie (cest--dire modifis), ou des paramtres dentre/sortie (cest--dire utiliss puis modifis), pour les fonctions.

- 19 -

2.4.1

La rfrence comme nouveau type de donnes

La notation
T var1; T &var2 = var1;

fait de var2 une rfrence l'objet var1 de type T. Cela signifie que les identificateurs var1 et var2 dsignent le mme objet. Manipuler l'un, revient manipuler l'autre. On peut dire que les identificateurs var1 et var2 sont des synonymes, ou bien que var2 est un alias de var1. L'initialisation d'une rfrence lors de sa dclaration est obligatoire (dans la mesure o cest lalias dune variable existante)
#include <iostream.h> void main(void) { int I=3; int &refI = I;

Rsultats

// refI est une rfrence I

cout << "I = " << I << endl; cout << "refI = " << refI << endl; cout << "adresse de I : " << &I << endl; cout << "adresse de refI : " << &refI << endl; cout << "Incrment de I (I++)" << endl; I++; cout << "refI = " << refI << endl; cout << "Incrment de refI (refI++)" << endl; refI++; cout << "I = " << I << endl; }

I = 3 refI = 3 adresse de I : 0x0065FDF4 adresse de refI : 0x0065FDF4 Incrment de I (I++) refI = 4 Incrment de refI (refI++) I = 5

Exemple 4. Notion de rfrence en C++ Commentaire sur lexemple 4 : . on constate que les variables I et refI ont la mme adresse. Elles dsignent donc bien le mme objet. . puisque I et refI sont des synonymes, modifier lun revient modifier lautre.

2.4.2

Transmission d'arguments par rfrence

Lutilisation de la notion de rfrence faite dans lexemple 4 ne prsente pas, sous cette forme, 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 Rappels sur la tansmission darguments en langage C En langage C, il ny a quune faon de transmettre des informations une fonction : la copie. Le paramtre formel dune fonction peut tre considr comme une variable locale la fonction, diffrente du paramtre dappel, et qui prend pour valeur, au dbut de lexcution de la fonction, celle du paramtre dappel. Lexemple (classique) suivant illustre cette particularit.

- 20 -

#include <iostream.h> void f(int); void main(void) { int pAppel=5; cout << "pAppel = " << pAppel << endl; cout << "f(pAppel)" << endl; f(pAppel); cout << "Aprs la fonction " << endl ; cout << "pAppel = "<< pAppel << endl; } void f(int pFormel) { cout << "pFormel = "<< pFormel << endl; cout << "Incrment de pFormel " << endl; pFormel++; cout << "pFormel = "<< pFormel << endl; }

Rsultats
pAppel = 5 f(pAppel) pFormel = 5 Incrment de pFormel pFormel = 6 Aprs la fonction pAppel = 5

Exemple 5. La transmission darguments en langage C. Commentaire sur lexemple 5 : La variable pAppel dsigne le paramtre avec lequel on appelle la fonction dans le programme principal et pFormel reprsente formellement le paramtre dans la fonction. Au moment de lappel de la fonction f(pAppel), le paramtre formel reoit une copie du paramtre dappel. On peut considrer quil se passe une dclaration de variable locale du style : int pFormel = pAppel. Dans notre exemple, au dbut de lexcution de la fonction, pFormel contient donc la valeur 5. On peut dailleurs incrmenter pFormel au sein de la fonction et constater son volution. Malgr tout, cette modification du paramtre formel na aucune incidence sur le paramtre dappel. Par consquent, aprs lappel de la fonction, la variable pAppel contient toujours la valeur quelle avait avant lappel de la fonction. Dun point de vue algorithmique, cette caractristique ne pose aucun problme pour les procdures ayant des paramtres dentre2. En revanche, lorsquune procdure a des paramtres de sortie, la technique appele souvent passage par adresse simpose. Lorsquune fonction du C doit modifier une variable, on adopte la convention suivante : . ct programme principal , on passe cette fonction (une copie de) ladresse de cette variable. . ct fonction, puisquon rcupre une adresse, on sait modifier la variable que cette adresse dsigne grce loprateur dindirection *. Par exemple, si lon souhaite que la fonction f() de lexemple 5 modifie la variable pAppel, on adopte la dmarche suivante : . largument de la fonction doit alors tre de type pointeur sur entier : le prototype devient void f(int *) . lors de lappel de la fonction, on ne transmet pas pAppel mais ladresse de pAppel, cest--dire &pAppel.
2

On appelle - paramtre dentre (ou donne), dune fonction ou dune procdure, un paramtre dont la valeur est utilise par la procdure mais nest pas modifie par celle-ci. - paramtre de sortie (ou rsultat) un paramtre dont la valeur nest pas utilise par la procdure mais qui contient, en sortie de la procdure, une valeur produite par la procdure. - paramtre dentre/sortie, un paramtre dont la valeur est utilise par la procdure, par exemple pour initialiser unn calcul, puis finalement ventuellement modifie pour contenir une nouvelle valeur. - 21 -

Cette convention conduit lexemple 6.


#include <iostream.h> void f(int *); void main(void) { int pAppel=5; cout << "pAppel = " << pAppel << endl; cout << "f(&pAppel)" << endl; f(&pAppel); // lappel est modifi cout << "Aprs la fonction... " << endl ; cout << "pAppel = "<< pAppel << endl; } void f(int * pFormel) { cout << "pFormel = "<< pFormel << endl; cout << "variable dsigne par pFormel = "<< *pFormel << endl; cout << "Incrment de la variable pointe par pFormel " << endl; (*pFormel)++; cout << "variable dsigne par pFormel = "<< *pFormel << endl; }

Rsultats
pAppel = 5 f(&pAppel) pFormel = 0x0065FDF4 variable dsigne par pFormel=5 Incrment de la variable pointe par pFormel variable dsigne par pFormel=6 Aprs la fonction pAppel = 6

Exemple 6. Le passage par adresse. Intrt des rfrences dans la transmission darguments La notion de rfrence va nous affranchir, dans beaucoup de cas, de la technique de transmission par adresse rappele prcdemment. Examinons lexemple suivant :
#include <iostream.h> void f(int&); void main(void) { int pAppel=5; cout << "pAppel = " << pAppel << endl; cout << "f(pAppel)" << endl; f(pAppel); cout << "Aprs la fonction... " << endl ; cout << "pAppel = "<< pAppel << endl; } void f(int &pFormel) { cout << "pFormel = "<< pFormel << endl; cout << "Incrment de pFormel " << endl; pFormel++; cout << "pFormel = "<< pFormel << endl; } pFormel est une rfrence au paramtre dappel.

Rsultats
pAppel = 5 f(pAppel) pFormel = 5 Incrment de pFormel pFormel=6 Aprs la fonction pAppel = 6

Exemple 7.

Passage par rfrence. - 22 -

Commentaire sur lexemple 7 : Il convient de remarquer la syntaxe au niveau du prototype de la fonction :


void f(int &);

Par cette syntaxe, on indique que le paramtre de cette fonction est du type rfrence un entier. Autrement dit, le paramtre formel pFormel de la fonction f() sera un alias du paramtre dappel. Dans ce cas, manipuler le paramtre formel pFormel aura dsormais une incidence sur le paramtre dappel, puisquil sagit prcisment du paramtre dappel, mais avec un identificateur diffrent. Par la suite, on appellera ce mode de transmission passage par rfrence. Au moment du passage dargument, le passage par rfrence de lexemple 7 peut sinterprter de la manire suivante : lorsque lon crit f(pAppel), on peut imaginer quau sein du bloc de la fonction, on dfinit une nouvelle rfrence de la manire suivante :
int & pFormel = pAppel;

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. En outre, lutilisation dune rfrence est moins lourde que la technique du passage dargument par adresse du C. 2.4.3 Fonction retournant une rfrence

En C++, une fonction peut retourner une rfrence une variable. Il sagit dune convention dcriture. Dans lexemple 8, la syntaxe float & Element(float *, int) signifie que la fonction retourne une rfrence une variable de type float.
#include <iostream.h> float & Element(float*, int); void main(void) { float tab1[5]={1.2, 2, 3.8, 4.5, 5.7}; // affichage de tab1[] cout << "tab1[]="; for(int i=0;i<5;i++) cout << tab1[i] <<" "; cout << endl; cout << "Element(tab1,2) ="<< Element(tab1,2)<<endl; // Modification du tableau via la rfrence retourne par la fonction Element(tab1,3)=5; // affichage de tab1[] for(i=0;i<5;i++) cout << tab1[i] <<" "; cout << endl; } float & Element(float * tableau,int indice) { return tableau[indice]; }

Rsultats
tab1[]= 1.2 2 3.8 4.5 5.7 Element(tab1,2) = 3.8 tab1[]= 1.2 2 3.8 5 5.7

Exemple 8. Renvoi dune rfrence. - 23 -

Commentaire sur lexemple 8 : La fonction Element() reoit deux arguments : ladresse dun tableau dlments de type float et un entier qui servira dindice dans le tableau. Cette fonction retourne une rfrence la variable tableau[indice]. Quest-ce que cela signifie exactement ? Lorsque dans le programme on crit
Element(tab1,3)=5; Element(tab1,3), qui reprsente ce que renvoie la fonction, est une rfrence tab1[3]. Autrement dit, si lon modifie Element(tab1,3), on modifie par consquent llment tab1[3], cest--dire le contenu du tableau. Puisque Element(tab1,3) est une rfrence une variable, on peut placer cette expression gauche dune affectation. Il faut admettre que cest dailleurs assez droutant que lappel dune fonction soit gauche de laffectation !

Attention : on ne peut retourner une rfrence une variable qu la condition que la variable existe lextrieur du bloc de la fonction. Le compilateur MS Visual C++ produit une mise en garde (justifie) lors de la compilation du source suivant.
#include <iostream.h> int & f(); void main(void) { f()=3; } int & f() { int A=12; return A; }

// << cest sur cette ligne que le compilateur gnre un warning !!

Exemple 9. Une fonction ne doit pas retourner une rfrence une variable locale. Le compilateur indique : warning C4172 : returning address of local variable or temporary Explication : la variable A de type entier est locale la fonction f() (cf. la porte des variables). Autrement dit, en dehors du corps de la fonction, cette variable nexiste plus. Par consquent, lorsque lon crit
f()=3;

on tente de modifier une variable qui nexiste plus, ou plus trs longtemps ! Lerreur de programmation qui consiste retourner une rfrence une variable locale la fonction conduit des comportements trs tranges lors de lexcution. 2.5 Arguments dune fonction avec valeur par dfaut

En C, l'appel d'une fonction doit comporter autant de paramtres effectifs qu'il y a de paramtres formels dans la dfinition de la fonction. En C++, certains paramtres effectifs peuvent tre omis. Dans ce cas, les paramtres formels de la fonction prennent une valeur par dfaut.

- 24 -

#include <iostream.h> float Addition(float, float =3); void main(void) { cout << Addition(10,5) << endl; cout << Addition(7) << endl; } float Addition(float a, float b) { return a+b; }

le second argument vaut 3 sil fait dfaut

Rsultats
15 10

Exemple 10. Arguments avec valeur par dfaut. En cas dabsence dun second paramtre dappel pour la fonction Addition(), le second argument prend pour valeur 3.

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

float float float float

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

OK NON !!! NON PLUS !!! OK

Remarque : la valeur quun paramtre doit prendre par dfaut doit figurer au niveau du prototype mais pas au niveau de len-tte de la fonction. 2.6 Surcharge (ou surdfinition) de fonctions

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

Note : le type de la valeur de retour nappartient pas la signature. En C++, il est possible de dfinir plusieurs fonctions avec le mme identificateur (mme nom) pour peu quelles aient des signatures diffrentes. On appelle cela la surcharge de fonction. Lexemple suivant prsente un programme o deux fonctions Addition() sont dfinies. Il faut noter quil sagit bien de fonctions diffrentes et quelles ne ralisent donc pas le mme traitement. Le compilateur ralise lappel de la fonction approprie partir du type des paramtres dappel. - 25 -

#include<iostream.h> int Addition(int, int=4); float Addition(float, float =3); void main(void) { float fA=2.5; float fB=3.7; int iA=2; int iB=3; cout << Addition(fA,fB) << endl; cout << Addition(iA,iB) << endl; cout << Addition(fA) << endl; cout << Addition(iA) << endl; } float Addition(float a, float b) { return a+b; } int Addition(int a,int b) { const int offset=12; return a+b+offset; }

Rsultats
6.2 17 5.5 18

Exemple 11. Surcharge de fonctions. 2.7 Les oprateurs de gestion de mmoire

En C, on demande de la mmoire avec la fonction malloc() et on la libre avec la fonction free(). En C++, on peut faire la mme chose avec deux oprateurs (et non des fonctions) appels new et delete. 2.7.1 L'oprateur new

La syntaxe d'utilisation de loprateur new est la suivante : 1 new TYPE 2 new TYPE[n]
o TYPE

est un identificateur de type quelconque, n est une expression entire quelconque.

La syntaxe 1 rserve de la place pour 1 lment ayant le type TYPE, alors que la syntaxe 2 rserve de la place pour n lments de type TYPE. Dans les deux cas, le rsultat de l'opration est . l'adresse de l'espace mmoire rserv sous la forme d'un pointeur de type (TYPE *) . le pointeur NULL si l'espace demand n'a pu tre obtenu. On a donc quelque chose de trs proche de ce que fait malloc() avec cependant une syntaxe plus simple.

- 26 -

2.7.2

L'oprateur delete

La syntaxe d'utilisation de delete est la suivante : 1 delete pointeur 2 delete [] pointeur o pointeur est un pointeur ayant obtenu sa valeur actuelle par new. La syntaxe 1 libre l'espace mmoire point par pointeur. La syntaxe 2 sert librer l'espace occup par un tableau d'objets. Lintrt de la seconde syntaxe napparatra quaprs avoir prsent les constructeurs/destructeurs. Remarque : le fonctionnement du programme est indtermin si pointeur pointe sur une zone dj libre ou si la valeur de pointeur a t obtenue autrement que par new (malloc() par exemple). Il est dconseill de mlanger lutilisation des oprateurs new/delete avec celle de malloc()/free(). Durant ce cours, on utilisera exclusivement les oprateurs de gestion de mmoire new/delete.

#include <iostream.h> void AlloueTableau ( int * & ptr, int taille) { ptr = new int[taille]; // alloue taille entiers et place ladresse de la zone // dans ptr } void main() { int * ptrInt; AlloueTableau( ptrInt, 12 ); for(int i=0; i<12; i++) ptrInt[i]=i; int * ptrTemp = ptrInt; AlloueTableau(ptrInt, 5); delete ptrInt; delete ptrTemp; }

Exemple 12. Les oprateurs de gestion de mmoire. Commentaire sur lexemple 12 : La fonction AlloueTableau() rserve une zone dentiers et place ladresse de cette zone dans ptr. On remarquera que, logiquement, ptr est un pointeur sur des lments de type entier, mais puisque ce pointeur doit tre modifi par la fonction (cest un paramtre de sortie), largument ptr est pass par rfrence. 2.8 Incompatibilits entre C++ et C

Un programme crit en C pur n'est pas toujours accept en C++. Suivent quelques cas o Turbo C++ de Borland n'accepte pas une forme du langage Turbo C du mme diteur ou l'inverse.

- 27 -

2.8.1 C

Prototypes de fonctions

On peut rencontrer dans un programme l'appel une fonction avant d'avoir rencontr sa dfinition ou son prototype. C++ Ce n'est plus possible

Le compilateur C rencontre l'appel printf() sans avoir rencontr sa dfinition ou son prototype, et ne peut alors vrifier la validit de l'appel. Cependant aucune erreur n'est signale. En C++, cela n'est plus autoris afin que le compilateur puisse vrifier la validit de l'appel. Le prototype de printf() tant dans le fichier stdio.h, on aura en C++ le programme suivant :
// en C++ #include <stdio.h> void main(void) { ..... printf("coucou"); ..... }

// obligatoire pour prototype de printf

2.8.2

Conversion d'un pointeur de type void *

En C, la valeur d'un pointeur de type void* peut tre affecte directement un pointeur de type T* diffrent. En C++, ce n'est plus autoris et il faut convertir explicitement le pointeur de type void* en un pointeur de type T*.
/* en langage C */ #include <alloc.h> void main(void) { int *tableau; tableau=malloc(100); }

La fonction malloc() rend un pointeur de type void * qui est affect un pointeur de type int *. Aucune erreur n'est signale. En C++, une erreur est signale et le programme doit tre corrig comme suit :
// en C++ #include <alloc.h> void main(void) { int *tableau; tableau=(int *)(malloc(100)); }

// conversion explicite

Lcriture (int *)(malloc(100)) permet une conversion du type de retour de malloc(), cest--dire void *, vers le type int*. Une telle conversion explicite dun type donn vers un autre type est galement appele transtypage ou cast.

2.8.3

Expressions constantes

En langage C, une variable dclare constante par le modificateur const, ne peut pas servir dimensionner un tableau. En C++, c'est rendu possible.

- 28 -

/* en C */ #include <stdio.h> void main(void) { const N=100; const P=2*N+1; int tableauN[N], tableauP[P]; }

<< Erreur lors de la compilation << Erreur lors de la compilation

Ce programme gnre une erreur pour chacune des deux dclarations de tableaux. Le mme programme en C++ est accept. Remarque : en C, tableauN pourra tre dimensionn par N si cet identificateur fait l'objet d'un #define N 100. Il n'y a pas de solution pour tableauP, si ce n'est de dire explicitement que P vaut 201 l'aide d'un #define P 201. 2.8.4 Identification d'une structure

Considrons le programme suivant :


void main(void) { struct chaine { char *valeur; unsigned taille; }; chaine nom; }

Ce programme est accept par le compilateur C++ mais pas par le compilateur C. En C++, le type struct chaine prcdent peut tre identifi par struct chaine ou plus simplement par chaine. Ce n'est pas le cas en C, o il faudrait crire :
struct chaine nom;

2.9 2.9.1

Le modificateur const et les rfrences constantes Le modificateur const

On verra au fil du cours que le modificateur const est, pour diverses raisons quon ne peut pas encore exposer, demploi trs frquent en C++. La premire utilisation de ce modificateur apparat, comme nous lavons dj vu, lors de la dclaration de constantes dans un programme. Ces constantes sont souvent des valeurs scalaires, mais peuvent galement tre des tableaux ou des structures. A quoi correspondent ces constantes ? Elles reprsentent des lments de programmation dont la valeur est fixe pour lensemble de la dure dexcution du programme (on peut penser par exemple la valeur de ). A quoi cela sert-il ? Le compilateur empche toute dtrioration de la valeur dune constante (normal). On vite ainsi certaines erreurs de programmation. Pour illustrer ceci, examinons le programme de lexemple 13. Ce programme est cens demander lutilisateur de saisir une lettre diffrente de la lettre n. Tant que lutilisateur persiste saisir un n, le programme boucle jusqu ce que lutilisateur saisisse une autre lettre. On dclare donc - 29 -

une variable test qui reprsente ce fameux caractre n. Le programme suivant compile parfaitement mais ne sexcute pas normalement. En effet, il boucle sans fin sur la saisie dun caractre. Pourquoi ? Parcequen C++, comme en C, le test dgalit seffectue avec un double == et que dans ltat actuel, il sagit dune affectation ! Puisque le caractre saisi sera toujours de valeur numrique non nulle, le programme boucle sans fin.
#include<iostream.h> void main() { char test='n'; char saisie; do { cout << "saisissez une lettre diffrente de n : "<<endl; cin>>saisie; }while(test=saisie); //erreur classique de programmation }

Exemple 13. Erreur classique : confusion de laffectation = avec le test dgalit == Une attitude de programmation diffrente nous aurait mis la puce loreille. Reprenons ce programme et dclarons la variable test comme une constante (ce qui parat dailleurs assez naturel). Que se passe-t-il ?
#include<iostream.h> void main() { const char test='n'; char saisie; do { cout << "saisissez une lettre diffrente de n : "<<endl; cin>>saisie; }while(test=saisie); << le compilateur indique quil est impossible daffecter << quelque chose une constante !!! }

Exemple 14. Utilisation dune constante Pour ce programme, le compilateur gnre un message qui attire notre attention sur cette erreur classique. Cette dmarche de dclarer ds que ncessaire un objet comme tant constant vite donc quelques piges du C/C++. 2.9.2 Les rfrences constantes

Considrons lexemple suivant.


#include<iostream.h> Rsultats void main() { val =13 ref =13 constref =13 int val=12; val =14 ref =14 constref =14 int &ref=val; //rfrence val const int& constref=val; //rfrence constante val val++; cout << "val = " << val<< " ref= " << ref << " constref = " << constref<< endl; ref++; cout << "val = " << val<< " ref= " << ref << " constref = " << constref << endl; // constref++; << impossible de modifier la variable "val" via sa reference constante }

- 30 -

Dans le programme prcdent, on dclare tout dabord une variable val de type entier. Il faut bien comprendre que cest dailleurs la seule variable du programme. Les deux autres dclarations ne sont que des rfrences cette variable, cest--dire des alias. Cela explique pourquoi laffichage du triplet val-ref-constref fournit systmatiquement trois sorties identiques. La premire rfrence (ref) est, comme nous lavons dj vu, une rfrence classique la variable val. Il est donc possible de modifier la variable val via sa rfrence (ce qui est fait en excutant ref++). La seconde rfrence (constref) est appele rfrence constante. La syntaxe est
const int & nom_de_la_reference = nom_de_la_variable,

ou, de manire plus gnrale


const TYPE & nom_de_la_reference = nom_de_la_variable.

Une rfrence constante est avant tout une rfrence, avec en plus la particularit suivante : elle interdit toute modification de la variable laquelle elle fait rfrence. Il est donc impossible ici dcrire constref++. A quoi les rfrences constantes servent-elles ? Leur intrt apparat, l encore, dans le cadre du passage darguments une fonction. La rgle gnrale retenir est la suivante : le passage darguments par rfrence est plus rapide que le passage par valeur.

#include<iostream.h> struct personne { char nom[40]; char prenom[40]; int age; }; void affiche(personne); void main() { personne jean={"Martin","Jean",45},pierre={"Dubecque","Pierre",24}; affiche(jean); affiche(pierre); } void affiche(personne p) { cout << p.prenom <<" "<< p.nom <<", " <<p.age << " ans"<<endl; }

Rsultats
Jean Martin, 45 ans Pierre Dubecque, 24 ans

Exemple 15. Passage dune structure une fonction (par copie) Commentaire : dans cet exemple, le seul argument de la fonction affiche() est un paramtre dentre. Il peut donc tre pass par valeur. En revanche, ce seul argument est une structure contenant au moins 82 octets (80 caractres pour les chanes et au moins 2 octets pour lentier). Toute la structure est copie chaque appel de telle sorte que le paramtre formel p nest quune copie du paramtre dappel. Il y a donc 82 octets copis chaque appel de affiche() !

- 31 -

Si lon adopte un passage par rfrence, il ny a plus ce problme de copie.


#include<iostream.h> struct personne { char nom[40]; char prenom[40]; int age; }; void affiche(personne &); // passage par rfrence void main() { personne jean={"Martin","Jean",45}; affiche(jean); } void affiche(personne & p) { cout << p.prenom <<" "<< p.nom <<", " <<p.age << " ans"<<endl; }

Exemple 16. Passage dune structure une fonction (par rfrence) Commentaire : cette fois-ci, le paramtre formel p nest pas une copie du paramtre dappel, mais simplement une rfrence ce paramtre dappel. Il ny a donc pas duplication des structures jean et pierre lors des appels. En pratique, la fonction reoit seulement ladresse de chacune de ces structures (le passage par rfrence est en fait un passage par adresse transparent pour le programmeur). Le passage par rfrence est donc plus rapide que le passage par valeur (par copie). On vite ici la copie de 80 octets. Pourquoi passage par rfrence constante et pas uniquement passage par rfrence ? Dans notre exemple, largument de la fonction est un paramtre dentre. En effet, la fonction utilise les valeurs des champs de la structure passe en argument, sans les modifier. Et puisque la structure est dsormais passe par rfrence on a, au sein de la fonction affiche, la possibilit de modifier la structure argument. Ceci est viter ! En effet, il nest pas exclu que par inadvertance (pensez la confusion possible entre laffectation et le test dgalit) on modifie le paramtre p qui devrait normalement ntre quun paramtre dentre. Aussi, pour viter ce genre derreur, on peut utiliser le passage dargument par rfrence constante.
#include<iostream.h> struct personne{ char nom[40]; char prenom[40]; int age; }; void affiche(const personne &); //passage par rfrence constante void main() { personne jean={"Martin","Jean",45}; affiche(jean); } void affiche(const personne & p) { cout << p.prenom <<" "<< p.nom <<", " <<p.age << " ans"<<endl; }

Exemple 17. Passage dune structure une fonction (par rfrence constante) - 32 -

Dans ce dernier exemple, lappel de la fonction affiche(jean) revient faire du paramtre formel p une rfrence constante la structure jean. Puisque p est une rfrence constante, toute tentative de modification de p au sein de la fonction affiche() sera interdite par le compilateur. On ne risque donc plus de modifier la structure (qui ne doit pas ltre) par mgarde sans que le compilateur ne gnre une erreur. Pour rsumer, le passage par rfrence constante . vite une copie de largument lors de lappel de la fonction . empche toute modification de largument au sein de la fonction Ce passage dargument est donc idal lorsquune fonction reoit des paramtres dentre occupant beaucoup de place (c--d beaucoup de mots mmoire). Ce sera donc recommand lorsque les paramtres dentre dune fonction seront des structures ou des objets avec beaucoup de donnes internes. 2.10 Conclusion

Qu'avons-nous appris ? En C, lors de l'appel d'une fonction, . on passe les paramtres dentre par valeur (cest--dire quil y a duplication des arguments lors de lappel). . on passe les paramtres de sortie par adresse. En C++, . on peut passer les paramtres dentre par valeur (cest--dire quil y a duplication des arguments lors de lappel) si ceux-ci noccupent pas trop de mmoire (sans quoi la duplication est coteuse en temps). . on passe les paramtres dentre par rfrence constante si ceux-ci sont volumineux . on peut passer les paramtres de sortie par adresse (mais cest devenu obsolte) . on passe les paramtres de sortie par rfrence (non constante) car cest aussi efficace que le passage par adresse avec une syntaxe plus lgre.

- 33 -

- 34 -

LA SURCHARGE DOPERATEURS EN C++

Il est possible, en C++, de surcharger la plupart des oprateurs. Cela signifie quon va pouvoir dcrire quels traitements doivent raliser certains oprateurs. 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.

3.1

La pluralit des oprateurs du C++

Selon le nombre doprandes dont loprateur a besoin, loprateur sera qualifi doprateur unaire (1 seul oprande) ou doprateur binaire (2 oprandes). Par exemple, loprateur = en C++ 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 ya en fait deux oprateurs ++ . Celui qui pr-incrmente et qui sutilise ++Op1 et celui qui postincrmente 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.h> 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). 3.2 Les oprateurs membres dune classe

Avant de prsenter un tableau rcapitulatif des oprateurs que lon peut surcharger en C++, il faut savoir que tous ne peuvent pas tre surchargs de la mme manire. Certains ne peuvent tre surchargs que sils appartiennent une classe. Il sagit alors de mthodes dune classe. Dans la mesure o lon na pas encore prsent les classes, la prsentation faite ici nest que partielle et se raffinera au cours des chapitres suivants. 3.3 Les oprateurs avec traitement par dfaut

Lorsque lon dfinit de nouveaux types, par exemple des types structurs, certains oprateurs ralisent un traitement par dfaut. Cest le cas de loprateur = et de loprateur & . 3.3.1 Le traitement par dfaut de loprateur =

Lorsque lon dfinit un nouveau type structur, le traitement ralis par dfaut pour loprateur = est une copie des champs dune structure dans lautre structure. Cest une copie membre--membre.

- 35 -

Lexemple ci-dessous illustre cette caractristique.


#include<iostream.h> struct S { int a; float b; }; void main() { S x={1,2.4},y={3,4.5}; // x et y deux structures de type S initialises x = y; // utilise loprateur = par dfaut x.a << " x.b =" << x.b<<endl;

Rsultat
x.a = 3 x.b = 4.5

cout << "x.a = "<< }

3.3.2

Le traitement par dfaut de loprateur &

Lorsque lon dfinit un nouveau type, loprateur & (oprateur unaire) retourne ladresse de lobjet auquel il sapplique.

3.4

Les oprateurs que lon peut surcharger en C++

Les diffrents oprateurs que lon peut surcharger en C++ sont : Unaire Binaire Binaire Binaire Binaire Binaire Binaire Binaire
+ - ++ -- ! ~ * & new new[] delete (cast) * / % + << >> < > <= >= == != & | && || += -= *= /= %= &= ^= |= <<= >>= ,

Doivent tre dfinis comme membres dune classe : Binaire


= ( ) [ ] -> (affectation) (oprateur fonction) (oprateur dindexation)

3.5

Le mcanisme de surdfinition des oprateurs en C++

Lorsquun oprateur est utilis sur un type dfini par le programmeur, lemploi de cet oprateur est quivalent lappel dune fonction particulire. - 36 -

Si lon dfinit un nouveau type T, et deux variables x et y de type T, soit


T x,y;

les deux syntaxes suivantes sont quivalentes pour le compilateur C++ :


(1) (2) x+y; operator+(x,y);

Autrement dit, les oprateurs sont vus comme des fonctions avec des identifiants particuliers : operator suivi du signe de loprateur. Dans le cas prcdent, si lon souhaite que loprateur + ralise un traitement particulier sur les variables de type T, il suffit de dfinir une fonction appele operator+() qui accepte deux arguments de type T. 3.6 Exemple de surdfinition doprateurs

On dfinit ici un type mat2x2 qui est un type structur reprsentant des matrices 2 lignes-2 colonnes. En ralit, la structure reprsente seulement les lments de la matrice : Mij est llment de numro de ligne i et de numro de colonne j. 3.6.1 Surcharge des oprateurs + et * pour des structures

On va ici surcharger les oprateurs + et * pour quils ralisent les oprations de somme et de produit matriciel sur les matrices de taille 2x2.
#include<iostream.h> struct mat2x2 { float M11; float M12; float M21; float M22; };

void mat2x2 mat2x2

affiche(const mat2x2 &); operator+(const mat2x2 &,const mat2x2 &); operator*(const mat2x2 &,const mat2x2 &);

void affiche(const mat2x2 & m) { cout << "|"<< m.M11 << " " << m.M12 << "|"<< endl; cout << "|"<< m.M21 << " " << m.M22 << "|"<< endl; }

mat2x2 operator+(const mat2x2 & A,const mat2x2 & B) { mat2x2 resultat; resultat.M11=A.M11+B.M11; resultat.M12=A.M12+B.M12; resultat.M21=A.M21+B.M21; resultat.M22=A.M22+B.M22; return resultat; }

- 37 -

mat2x2 operator*(const mat2x2 { mat2x2 resultat; resultat.M11=A.M11*B.M11 + resultat.M12=A.M11*B.M12 + resultat.M21=A.M21*B.M11 + resultat.M22=A.M21*B.M12 + return resultat; }

& A,const mat2x2 & B)

Rsultat
A.M12*B.M21; A.M12*B.M22; A.M22*B.M21; A.M22*B.M22; m1= | 1 2 | | 3 4 | m2= | 1 4 | | 8 1| m1+m2= | 2 6 | | 11 3 | m1*m2= | 17 2 | | 35 8 |

void main() { mat2x2 m1={1,2,3,4},m2={1,4,8,-1}; cout << "m1 = " << endl; affiche(m1); cout << "m2 = " << endl; affiche(m2);

cout << "m1+m2=" << endl; affiche(m1+m2); // affiche ce que retourne loprateur + cout << "m1*m2=" << endl; affiche(m1*m2); // affiche ce que retourne loprateur * }

Exemple 18. Surcharge doprateurs Il faut garder lesprit que lorsquon crit m1+m2 dans le programme, cest interpret comme operator+(m1,m2) par le compilateur. Cela appelle donc la fonction que lon a dfinie. 3.6.2 Choix du paramtrage des oprateurs.

Lorsque lon crit m1+m2 ou m1*m2, loprateur doit raliser un calcul partir des deux oprandes et retourner une structure correspondant au rsultat du calcul. Mais, la somme ou le produit de m1 et m2 ne doit en aucun cas modifier les oprandes m1 et m2. On en dduit : Les arguments des oprateurs + et * sont des paramtres dentre de loprateur. Il est donc important que ces arguments soient passs par rfrence constante (voir chapitre prcdent). Le prototype de ces oprateurs doit donc tre (le type du retour reste tablir): ? operator+(const mat2x2 &, const mat2x2 &) ? operator*(const mat2x2 &, const mat2x2 &) Dun autre ct, loprateur retourne le rsultat du calcul. Dailleurs, dans les deux oprateurs, ce calcul est ralis grce une variable resultat, locale loprateur. Puisque le calcul est stock dans une variable locale loprateur, il nest pas possible de retourner une rfrence cette variable. Le retour de loprateur doit donc tre un retour par valeur. Finalement, les prototypes de ces oprateurs doivent tre : mat2x2 operator+(const mat2x2 &, const mat2x2 &) mat2x2 operator*(const mat2x2 &, const mat2x2 &) - 38 -

Remarque : il est important de mener cette reflexion au moment du choix du paramtrage des fonctions et en particulier des oprateurs. 3.6.3 Utilisation de loprateur = par dfaut.

Dans lexemple de surcharge doprateurs prcdent, nous navons jamais stock le rsultat rendu par les oprateurs + et * dans une variable. Le programme principal suivant fonctionne pourtant correctement :
void main() { mat2x2 m1={1,2,3,4},m2={1,4,8,-1},m3; m3=m1+m2 ; affiche(m3); cout << endl ; m3=m1*m2; affiche(m3); }

Rsultat
| 2 6 | | 11 3 | | 17 2 | | 35 8 |

Pourquoi cela fonctionne-t-il sans surcharger loprateur = ? Loprateur = ralise, si lon ne le surcharge pas explicitement, une copie membre--membre. Or cest exactement ce qui est ncessaire lorsque lon souhaite faire laffectation de deux matrices de taille 2x2. Prcisment, lorsque lon crit
m3 = m1+m2 ;

il y a, dans lordre 1) appel de la fonction operator+(m1,m2) 2) la structure retourne par cet oprateur est utilise pour raliser la copie membre--membre dans m3. Ici, cela convient parfaitement. Mais pour certaines classes, le traitement par dfaut ne convient pas du tout. Il est alors indispensable de redfinir explicitement ce que doit raliser loprateur daffectation. Remarque : lorsque lon surcharge loprateur = (ce qui nest possible que pour les classes), on annule le traitement par dfaut. 3.7 Conclusion

On a commenc ici la prsentation de la surcharge doprateurs en C++. Cette surcharge est en fait trs simple mettre en uvre. Des prcisions seront nanmoins donnes aprs avoir prsent la dfinition de classes dobjets. Notons que le point le plus dlicat reste le choix du type des arguments de ces oprateurs (passage/retour par valeur, rfrence ou rfrence constante).

- 39 -

- 40 -

LES CLASSES D'OBJETS

Nous abordons dans ce chapitre la notion d'objet qui est sans nul doute l'apport essentiel de C++. Cependant, C++ n'est qu'un langage orient objet. Destin tre une sur-couche de C, il n'a pas t conu ds l'origine comme un langage objets pur. Il n'a donc pas toutes les caractristiques d'un tel langage. Avant d'aborder la notion de classe, nous revenons tout dabord sur les structures en C++. 4.1 Les structures

La structure en C, ne contient que des donnes. En C++, elle peut dsormais contenir galement des fonctions appeles mthodes. En voici un exemple : Dfinition de la structure dans un fichier personne.h :
// personne.h #ifndef __PERSONNE__ #define __PERSONNE__ #include <iostream.h> #include <string.h> struct personne { char nom[20]; unsigned age; void identifie(void); };

Consulter lannexe pour lexplication de ces directives de compilation conditionnelle

// nom de la personne // son ge // fonction permettant de l'identifier

void personne :: identifie(void) // dfinition de la mthode { cout << "Je m'appelle "<< nom << " et j'ai " << age << " ans\n"; } #endif

Utilisation de cette structure :


#include "personne.h" void main(void) { personne agent;

Sortie cran
Je m'appelle Jacques et j'ai 20 ans

strcpy(agent.nom,"Jacques"); // on donne un nom l'agent agent.age=20; // un ge agent.identifie(); // on cherche l'identifier }

Exemple 19. Type structur contenant des fonctions membre

Le programme prcdent utilise une structure dfinie par :


struct personne{ char nom[20]; unsigned age; void identifie(void); };

// nom de la personne // son ge // fonction permettant de l'identifier

- 41 -

Ce qui est nouveau ici, c'est la possibilit de mettre une fonction dans une structure. En fait, on peut mettre soit la dfinition de la fonction, soit son prototype. C'est ce dernier cas qui est prsent ici. La dfinition de identifie() se trouve l'extrieur de la structure :
// dfinition de la mthode identifie de la structure personne void personne::identifie(void) { cout << "Je m'appelle "<< nom << " et j'ai " << age << " ans\n"; }

Plusieurs structures diffrentes pourraient avoir une mthode appele identifie(). Pour rsoudre cet ventuel conflit d'identificateurs, l'entte de la mthode est prcd de l'identificateur de la structure qui la contient, selon la syntaxe:
identificateur propritaire::identificateur mthode()

On a donc ici l'entte :


void personne::identifie(void)

Notez qu'on n'a pas crit


void struct personne :: identifie(void)

qui est une dclaration errone en C++. Le propritaire d'une mthode ne pouvant tre qu'une structure (struct) ou une classe (class), cette rgle entrane qu'une structure ne pourra porter le mme nom qu'une classe. Revenons au corps de notre mthode personne::identifie() :
// dfinition de la mthode identifie de la structure personne void personne::identifie(void) { cout << "Je m'appelle "<< nom << " et j'ai " << age << " ans\n"; }

On remarque que la mthode identifie() fait rfrence des variables appeles nom et age non dclares dans identifie() et qui ne sont pas non plus des variables globales (ce sont les champs d'une structure donc d'un type de donne). On ne sait donc pas ce que rfrence identifie() avec ces idenficateurs nom et age. On en apprend un peu plus en regardant l'appel qui est fait dans le programme dutilisation :
void main(void) { personne agent;

// une variable de type struct personne

strcpy(agent.nom,"Jacques"); // on donne un nom l'agent agent.age=20; // un ge agent.identifie(); // on cherche l'identifier }

Une variable agent de type personne est dfinie puis initialise. La mthode identifie est appele par :
agent.identifie();

- 42 -

L'appel donne donc une information qu'on n'avait pas dans la dfinition de la mthode : on appelle la mthode identifie() de la variable agent. Les champs nom et age rfrencs par identifie() sont alors ceux de cette variable agent. En ralit, l'appel agent.identifie() appelle la fonction identifie() en lui passant l'adresse de la variable agent, ce qui permet identifie() de travailler sur les champs nom et age de cette variable. Dans l'appel agent.identifie(), on dira que agent est l'objet courant manipul par la mthode identifie(). Remarque : dans l'appel agent.identifie() tout se passe comme si lon avait un appel identifie(&agent). Autrement dit, mme quand une mthode na pas dargument explicite, elle reot toujours lobjet courant comme argument implicite.

Qu'avons-nous appris ? . Une structure peut dfinir, outre des donnes, des fonctions membres (ou mthodes). . L'appel une mthode se fait selon la syntaxe
propritaire.mthode( .... )

. La dfinition de la mthode peut rfrencer des champs propritaire sans nommer celui-ci. La mthode reoit automatiquement son adresse lors de l'appel.

4.2

Les classes

Un objet associe donnes et mthodes qui les manipulent dans un mme moule (voir chapitre 1). En C++, les structures sont en quelque sorte des objets. Cependant, les structures nempchent pas laccs direct leurs champs de donnes, ce qui n'est normalement pas autoris en langage objet pur. Autrement dit, les structures nencapsulent pas les donnes. Ainsi on a pu crire dans l'exemple prcdent :

personne agent; // une variable de type personne strcpy(agent.nom,"Jacques"); // on donne un nom l'agent agent.age=20; // un ge

On a pu modifier directement les champs nom et age de lobjet agent, sans passer par les mthodes. Or, un objet nest vraiment sr que dans la mesure o lon interdit l'accs direct ses champs de donnes. Par exemple, si le programmeur manipule directement le champ nom et y met des caractres en oubliant le caractre de fin de chane, la mthode identifie() va avoir un comportement aberrant. Pour viter ce problme, il nous faut interdire l'accs direct aux champs de l'objet. La structure ne nous permet pas de le faire, puisque cela remettrait en cause le type struct du C. Le C++ a donc introduit la classe. Reprenons le mme exemple que prcdemment dans lequel nous remplaons la structure par une classe :

- 43 -

// personne.h #ifndef __CLS_PERSONNE__ #define __CLS_PERSONNE__ #include <iostream.h> #include <string.h> class personne { private: char nom[20]; // nom de la personne unsigned age; // son ge public: void identifie(void); void initialise(char *,unsigned); }; void personne::identifie(void) { cout << "Je m'appelle "<< nom ; cout << " et j'ai " << age << " ans\n"; } void personne::initialise( char *nom_p, unsigned age_p) { nom[19]='\0'; strncpy(nom,nom_p,19); age=age_p; } #endif

Partie Prive de la classe Il sagit ici des donnes membres :


nom age

: tableau de caractres : entier non sign

Partie Publique de la classe Il sagit ici des mthodes de la classe.


initialise() identifie()

Programme dutilisation de cette classe :


#include "personne.h" void main(void) { personne agent,standardiste; // agent, standardiste agent.initialise("Jacques",20); standardiste.initialise("Pierre",25); agent.identifie(); standardiste.identifie(); }

= objets de la classe personne

Exemple 20. Premier exemple de classe Commenons par la dfinition de la classe personne (dans le fichier personne.h):
class personne { private : char nom[20]; unsigned age; public : void identifie(void); void initialise(char *,unsigned); };

- 44 -

Par cette syntaxe, lidentificateur personne dsigne une classe, cest--dire en quelque sorte un type. Autrement dit, toute variable du type personne sera un objet. Par exemple, lorsque lon crit
personne agent, technicien, standardiste; agent, technicien et standardiste sont trois objets de la classe personne. Parce que la notion de classe gnralise celle de type, on dira indiffremment : . . . . standardiste standardiste standardiste standardiste

est un objet de la classe personne est une instance de la classe personne est du type personne est une variable de la classe personne

Les membres (ou champs) d'un objet peuvent tre des donnes ou des mthodes (fonctions). Ces champs peuvent avoir l'un des trois attributs suivants : priv Un champ priv (private) dun objet n'est accessible que par les seules mthodes de l'objet, par les mthodes dun autre objet de sa classe et par certaines fonctions dites amies (friend). Un champ public est accessible par toute fonction dfinie ou non au sein de l'objet. Un champ protg (protected) n'est accessible que par les seules mthodes internes de l'objet ou d'un objet driv (voir ultrieurement le concept d'hritage) et par les fonctions amies (friend).

public protg

La syntaxe de dfinition d'une classe est la suivante :


class nom_classe{ private : ..... donnes/mthodes prives protected : ..... donnes/mthodes protges public : ..... donnes/mthodes publiques }; // <- ne pas oublier le ; ici

Remarques : . L'ordre de dclaration des attributs private, protected et public est quelconque. . Un mme attribut peut apparatre plusieurs fois dans la dclaration de l'objet. . Des donnes ou mthodes prcdes d'aucun attribut sont dclares prives. Quel est le rle d'initialise() ? Maintenant que nom et age sont devenues des donnes prives de la classe personne, les instructions
strcpy(agent.nom,"Jacques"); agent.age=20;

pour initialiser l'objet agent deviennent illgales : les rfrences agent.nom et agent.age sont interdites car elles dsignent des donnes prives de l'instance agent. Il nous faut donc une mthode pour accder aux donnes prives, mthode publique de plus car elle doit pouvoir tre rfrence l'extrieur de l'objet personne : c'est le rle de la mthode initialise().

- 45 -

void personne::initialise( char *nom_p, unsigned age_p) { nom[19]='\0'; strncpy(nom,nom_p,19); // copie nom_p dans nom age=age_p; // copie age_p dans age }

La mthode initialise() initialise les champs nom et age d'un objet avec les valeurs de nom_p et age_p qu'on lui passe en paramtres. L'appel devient alors celui-ci :
void main(void) { personne agent; agent.initialise("Jacques",20); agent.identifie(); }

// une variable de type personne // on initialise agent // on cherche l'identifier

Qu'avons-nous gagn ? En rendant prives les donnes de la classe personne, nous avons rendu ce dernier plus sr. Dans notre exemple, le champ nom dun objet personne est un tableau de 20 caractres. Les chanes de caractres sont une source frquente d'erreurs en C : il y a risque de dbordement du tableau qui leur est allou. Voyons ce qui se passe dans les deux cas (donnes publiques/donnes prives) lorsqu'un programmeur veut affecter la chane "Ceci est un trs long nom" au champ nom : Si le champ nom est une donne publique : le programmeur a accs au champ nom de l'objet agent et rien ne l'empche de faire son initialisation par strcpy(agent.nom,"Ceci est un trs long nom"); Il y aura alors dbordement du tableau nom et plantage court terme du programme. Si le champ nom est une donne prive : le programmeur est oblig de passer par la mthode initialise(), pour initialiser le champ nom de l'objet agent. Il crira donc :
agent.initialise("Ceci est un trs long nom", 20);

Or, si on regarde la mthode initialise()


void personne::initialise( char *nom_p, unsigned age_p) { strncpy(nom,nom_p,19); // copie nom_p dans nom age=age_p; // copie age_p dans age }

elle ne copie que 19 caractres au plus dans le champ nom de l'objet agent : il n'y aura donc pas dbordement du tableau, mais simplement troncature du nom : le programme peut continuer normalement. Conclusion Un objet bien construit, assurant l'intgrit de ses donnes, facilite le travail du programmeur qui l'utilise car une source importante d'erreurs est ainsi limine. Le contrle tant gr au niveau mme de lobjet. Le terme bien construit est important : si le concepteur de l'objet agent avait utilis dans la mthode initialise() l'instruction
strcpy(nom,nom_p);

au lieu de
strncpy(nom,nom_p,19);

- 46 -

on aurait eu un objet mal construit car n'vitant pas le dbordement du tableau nom. En gnral, les donnes d'un objet sont dclares prives alors que ses mthodes sont dclares publiques. Cela signifie que l'utilisateur3 de l'objet n'aura pas accs directement aux donnes de l'objet, mais pourra faire appel aux mthodes de l'objet et notamment celles qui donneront accs aux donnes prives. Notons quen C++ lencapsulation de donnes nest malgr tout pas impose par le langage (on nest pas oblig de dclarer les donnes comme des membres privs), mais est vivement recommande. 4.3 Les mthodes d'une classe

Les mthodes d'une classe sont des fonctions comme les autres et supportent donc les points suivants : . le passage d'arguments par rfrence . l'utilisation de paramtres par dfaut . la surcharge Voici un exemple illustrant ces trois points :
// personne.h #ifndef __CLS_PERSONNE_ #define __CLS_PERSONNE_ #include <iostream.h> #include <string.h> class personne { private : char nom[20]; unsigned age; public : void identifie() const; void initialise(char * ="?",unsigned =0); void initialise(const personne &); };

La prsence de ce const fera lobjet dune explication ultrieurement.

void personne::identifie() const { cout << "Je m'appelle "<< nom << " et j'ai " << age << " ans\n"; } void personne::initialise(char *nom_p, unsigned age_p) { nom[19]='\0'; strncpy(nom,nom_p,19); age=age_p; } void personne::initialise(const personne &individu) { nom[19]='\0'; strncpy(nom,individu.nom,19); age=individu.age; } #endif

On distingue celui qui a crit la classe, que lon appelle concepteur de classe, de celui qui lutilise. Il peut sagir de deux individus diffrents. Dans la fonction void main() du programme, on se situe ct utilisateur de la classe. Grce lencapsulation, lutilisateur ne sait pas ncessairement comment est implmente la classe. - 47 -

#include "personne.h" void main(void) { personne agent1,agent2,agent3;

Sortie cran
Je m'appelle Jacques et j'ai 20 ans Je m'appelle Jacques et j'ai 20 ans Je m'appelle ? et j'ai 0 ans

agent1.initialise("Jacques",20); agent1.identifie(); agent2.initialise(agent1); // initialisation de agent2 avec agent1 agent2.identifie(); agent3.initialise(); // initialisation de agent3 avec les valeurs par dfaut agent3.identifie(); }

Exemple 21. Surcharge des mthodes dune classe, valeurs par dfaut, passage par rfrence Dans l'exemple prcdent la mthode initialise() est surcharge, a des paramtres par dfaut (version 1) et accepte un paramtre par rfrence constante (version 2). Revenons sur cette version :
void personne::initialise(const personne &individu) { nom[19]='\0'; strncpy(nom,individu.nom,19); age=individu.age; }

L'appel a t le suivant :
personne agent1,agent2; agent2.initialise(agent1);

// on initialise agent2 avec agent1

Dans l'appel prcdent, agent1 est un paramtre dentre de la mthode initialise() : ses champs vont tre lus mais non modifis par la mthode. On pouvait donc passer cet argument par valeur. Comme nous lavons vu dans le chapitre prcdent, le passage par valeur implique une recopie de toutes les donnes de lobjet. On prfre donc dans ce cas le passage par rfrence constante. On remarque que la mthode initialise() fait rfrence directement aux champs des deux objets de la classe impliqus :
nom age

personne

et individu.nom et individu.age

On en dduit qu'une mthode d'un objet a accs aux champs, mme privs, de tous les objets de sa classe, et non aux seuls champs de l'objet courant. 4.4 Constructeur et destructeur d'une classe

Il existe des mthodes particulires pour un objet : ses constructeurs et son destructeur. Dfinition (constructeur) : un constructeur est une mthode qui porte le mme nom que la classe. Cette mthode est appele juste aprs la cration de l'objet. On s'en sert gnralement pour l'initialiser. C'est une mthode qui peut accepter des arguments mais qui ne rend aucun rsultat. Son prototype ou sa dfinition ne sont prcds d'aucun type (pas mme void).

- 48 -

Si une classe maClasse a un constructeur acceptant n arguments argi, la dclaration dun objet de cette classe se fera sous la forme :
maClasse unObjet(arg1,arg2, ... argn);

C'est--dire que l'utilisateur d'une classe avec constructeur doit obligatoirement initialiser les objets lors de leur instanciation (sinon le compilateur signalera une erreur). . Un constructeur peut tre surcharg et avoir des arguments par dfaut. Dfinition (destructeur) : le destructeur est une mthode de la classe qui porte comme nom celui de la classe prcd d'un tilde ~ . Cette mthode est appele juste avant la destruction de l'objet. Un destructeur n'accepte aucun argument et ne rend aucun rsultat. Son prototype ou sa dfinition ne sont prcds d'aucun type (pas mme void). . Un destructeur ne peut pas tre surcharg. 4.5 Exemple 1

Reprenons notre exemple sur la classe personne, crons un constructeur charg d'initialiser l'objet lors de sa cration. Nous introduisons galement un destructeur. Le constructeur et le destructeur font des critures afin de rvler les moments o ils sont appels.
// personne_v1.h #ifndef __CLS_PERSONNE_V1_ #define __CLS_PERSONNE_V1_ #include <iostream.h> #include <string.h> class personne { private : char nom[20]; unsigned age; public : void identifie(void) const; personne(char*,unsigned ); ~personne(void); };

// constructeur pour initialiser l'objet personne // destructeur

void personne::identifie(void) const{ cout << "Je m'appelle "<< nom << " et j'ai " << age << " ans\n"; } personne::personne(char *nom_p, unsigned age_p){ strncpy(nom,nom_p,19); nom[19]='\0'; age=age_p; cout << "Le constructeur de la personne (" << nom << "," << age << ") a t appel\n"; } personne::~personne (void){ cout << "Le destructeur de la personne (" << nom << "," << age << ") a t appel\n"; } #endif

- 49 -

#include "personne_v1.h" void main(void) { personne agent1("Jacques",20), agent2("Louise",18);

Sortie cran
Le Le Le Le constructeur de la personne (Jacques,20) a t appel constructeur de la personne (Louise,18) a t appel destructeur de la personne (Louise,18) a t appel destructeur de la personne (Jacques,20) a t appel

Exemple 22. Classe personne version 1 Dans le programme prcdent, rien n'est fait dans la fonction main() et pourtant il y a des affichages cran ! Explication : agent1 et agent2 sont des variables locales ou automatiques de la fonction main() et sont donc cres lorsque l'excution de main() commence. Leur constructeur est donc automatiquement appel. Il a deux arguments :
personne::personne(char *,unsigned );

Les variables agent1 et agent2 doivent donc tre dclares avec deux arguments qui seront transmis au constructeur, ce qui explique la syntaxe suivante:
class personne agent1("Jacques",20), agent2("Louise",18);

Le constructeur initialise chaque variable agent1 et agent2 avec les arguments passs et fait une criture : ceci explique les deux premiers affichages cran : Le constructeur de la personne (Jacques,20) a t appel Le constructeur de la personne (Louise,18) a t appel Ensuite, le corps de la fonction main() se termine (il n'y a pas dautre instruction). Les variables locales main() sont donc dtruites. Comme la classe personne a un destructeur, celui-ci est automatiquement appel et ralise galement une criture. Ceci conduit aux affichages : Le destructeur de la personne (Louise,18) a t appel Le destructeur de la personne (Jacques,20) a t appel 4.6 Exemple 2

Dans l'exemple prcdent les objets automatiques agent1 et agent2 taient crs dans la pile et leur dure de vie tait celle de la fonction main(). Ici, nous crons les objets agent1 et agent2 dans le tas. Cest--dire que lon va crer dynamiquement des objets. Leur dure de vie est alors contrle par les oprateurs new et delete et n'est plus dpendante de la dure de vie de main(). Nous rappelons que pour crer un objet de type T_objet dans le tas la syntaxe est alors :
T_objet * ptr = new T_objet;

Pour restituer la zone alloue pour cet objet , on crit

- 50 -

delete ptr;

Si l'objet cr a un constructeur avec des arguments, les arguments peuvent tre passs au constructeur au moment de lallocation de mmoire. La syntaxe devient : On passe ici les arguments au constructeur de lobjet. T_objet * ptr = new T_objet(arg1,arg2,,argN); La libration de la mmoire quant elle ne change pas.
#include "personne_v1.h" void main() { personne *ptr1, *ptr2;

Rsultats cran
cout << "instant t1"<<endl; ptr1 = new personne("jean",25); cout << "instant t2"<<endl; ptr2 = new personne("jacques",18); cout << "instant t3"<<endl; delete ptr1; cout << "instant t4"<<endl; } instant t1 Le constructeur de la personne (jean,25) a t appel instant t2 Le constructeur de la personne (jacques,18) a t appel instant t3 Le destructeur de la personne (jean,25) a t appel instant t4

Exemple 23. Allocation dynamique dobjets de type personne. On remarque les points suivants : La syntaxe suivante
personne * ptr1 = new personne("jean",25);

cre un objet personne dans le tas. Cet objet est point par ptr1 et est initialis avec les arguments ("jean",25). Le constructeur est donc appel et est l'origine de l'affichage de : Le constructeur de la personne (jean,25) a t appel Il est noter que, contrairement aux objets automatiques, les objets dynamiques ne sont construits quau moment de lallocation de la mmoire. En outre, la dclaration dune variable pointeur sur un objet de la classe personne ne cre pas dobjet de cette classe. La syntaxe
delete ptr1;

dtruit l'objet point par ptr1. Puisqu'il y a destruction d'objet, il y a appel du destructeur qui affiche alors : Le destructeur de la personne (Jean,25) a t appel

- 51 -

4.7

Exemple 3

Dans les exemples prcdents, le destructeur n'avait aucun intrt. Les exemples 1 et 2 ne servent qu la comprhension du mcanisme dappel des constructeurs/destructeur. L'exemple suivant utilise un objet pour lequel un destructeur en prsente un : celui de restituer de la mmoire obtenue par le constructeur. La diffrence essentielle rside dans le fait que la chane de caractres nest plus stocke dans un tableau de taille fixe par la dclaration de la classe, mais alloue dynamiquement selon les besoins de chaque objet de la classe.
//personne_v2.h - Deuxime version de la classe personne #ifndef __CLS_PERSONNE_V2_ #define __CLS_PERSONNE_V2_ #include <iostream.h> #include <string.h> #include <process.h> class personne { private: char * ptrNom; // pointeur sur une chane de caractres unsigned age; public: void identifie() const; personne(char *,unsigned ); ~personne(void); }; void personne::identifie(void) const { cout << "Je m'appelle "<< ptrNom << " et j'ai " << age << " ans\n"; } personne::personne(char *nom_p, unsigned age_p) { ptrNom=new char[strlen(nom_p)+1]; // demande de la mmoire pour le nom if(ptrNom==NULL) { cout << "Mmoire insuffisante ... Abandon ...\n"; exit(1); } strcpy(ptrNom,nom_p); // initialise la chane pointe par ptrNom age=age_p; // initialise age cout << "Le constructeur de la personne (" << ptrNom << "," << age << ") a t appel\n"; } personne::~personne() { cout << "Le destructeur de la personne (" << ptrNom << "," << age << ") a t appel\n"; delete ptrNom; // restitue la mmoire pointe par ptrNom } #endif

- 52 -

// programme dutilisation de la classe personne (deuxime version) #include "personne_v2.h" void main(void) { personne *ptrAgent= new personne("Jacques",20); personne agent("Louise",18); delete ptrAgent;

Rsultats cran
Le Le Le Le } constructeur de la personne (Jacques,20) a t appel constructeur de la personne (Louise,18) a t appel destructeur de la personne (Jacques,20) a t appel destructeur de la personne (Louise,18) a t appel

Exemple 24. Deuxime version de la classe personne Commentaires En comparant la premire version de la classe, la dfinition de la classe personne a ici vritablement chang. La structure de chacun des objets instancis partir de cette classe est rsolument diffrente. En effet, l o nous avions auparavant un tableau de caractres appel nom dans la premire version de la classe, nous navons plus quun pointeur sur une chane de caractres. O sera stocke la chane ? Le tableau de caractres ncessaire au stockage des caractres du nom de la personne est dsormais allou dynamiquement par le constructeur de lobjet. Le constructeur de cette classe est donc:
personne::personne ( char *nom_p, unsigned age_p) { ptrNom=new char[strlen(nom_p)+1]; // demande de la mmoire pour le nom if(ptrNom==NULL) { cout << "Mmoire insuffisante ... Abandon ...\n"; exit(1); } strcpy(ptrNom,nom_p); // initialise la chane pointe par ptrNom age=age_p; // initialise age cout << "Le constructeur de la personne (" << ptrNom << "," << age << ") a t appel\n"; }

Avant d'affecter un nom l'objet de la classe personne, le constructeur demande de l'espace pour loger ce nom et fait pointer le champ ptrNom dessus. Un test d'insuffisance mmoire est galement fait. Le destructeur volue alors comme suit :
personne::~personne () { cout << "Le destructeur de la personne (" << ptrNom << "," << age << ") a t appel\n"; delete ptrNom; // restitue la mmoire pointe par ptrNom }

- 53 -

Rappelons-nous que le destructeur est appel avant la destruction dfinitive de l'objet. Ici, il prend soin de restituer l'espace mmoire point par le champ ptrNom devenu inutile puisque l'objet va tre dtruit. Le constructeur et le destructeur participent la sret d'utilisation d'un objet. Leur prsence assure que l'objet est toujours initialis et dtruit proprement. 4.8 Objets avec des donnes en profondeur

On discute ici les diffrences fondamentales entre les deux versions de la classe personne prsentes prcdemment. Le lecteur est donc invit se reporter aux deux dfinitions de la classe personne faites respectivement p.49 et p.52. Les exemples prcdents mettent en vidence deux techniques trs diffrentes pour grer des donnes lies un objet. On va ici illustrer et comparer ces mthodes. Premire version de la classe personne
class personne { private : char nom[20]; unsigned age; public: // };

// tableau de 20 caractres

En dclarant un objet automatique de cette premire version de la classe,


personne p1("martin",25);

on obtient la structure suivante :


objet p1 de la classe personne

nom

m a r t i n \0 25

age

Donnes Mthodes

On peut en effet voir un objet comme une structure avec plusieurs champs de donnes. Avec la premire version de la classe, chaque objet contient 20 caractres + 1 entier non sign. Toutes ces donnes sont stockes au sein mme de lobjet. Inconvnients de cette version de la classe : . les noms stocks sont forcment limits la taille du tableau. Ici, les noms doivent faire au plus 19 caractres. . en outre, si le nom stock est trs court, une grande partie du tableau nest pas utilise. Dans le cas de lobjet p1, 13 caractres sont rservs inutilement. Avec cette technique de stockage des donnes, soit on manque de place dans lobjet, soit on gaspille des mots mmoire inutilement. - 54 -

Seconde version de la classe personne avec donnes en profondeur


class personne { private: char * ptrNom; unsigned age; public: // };

// pointeur sur une chane de caractres

Les objets instancis partir de la seconde version de la classe ont la structure suivante :

ptrNom 25

age Donnes Mthodes

m a r

zone de stockage "en profondeur"

t i n \0

Un objet de cette version de la classe personne ne contient plus directement les donnes lies au nom dune personne. Chaque objet de cette classe ne contient que ladresse o est stocke la chane de caractres en mmoire. La zone de stockage de ces caractres est alloue par le constructeur de lobjet et dsalloue par le destructeur de lobjet. On dit que la chane de caractre est stocke en profondeur. En effet, la diffrence de la premire version, on voit bien que les caractres ne sont plus vraiment dans lobjet, mais dans une zone mmoire distante. On retrouve nanmoins sans problme les donnes en profondeur par lintermdiaire du pointeur qui, lui, est tout de mme dans lobjet (ptrNom). Intrt de cette gestion des donnes En stockant les donnes en profondeur (cest--dire dynamiquement), la zone mmoire rserve pour lobjet correspond effectivement prcisment au besoin de lobjet. Il ny a donc plus de limitation de la taille des noms stocks et, linverse, un nom court ne gaspille pas de la mmoire. Au vu de cette comparaison entre les deux versions de la classe, il apparat que la premire version de la classe nest pas une bonne solution, bien que plus simple mettre en ouvre. Aussi, ds que les objets dune classe nauront pas tous des donnes de mme taille, il conviendra de stocker ces donnes en profondeur, et donc dutiliser la technique prsente dans lexemple 3 de ce chapitre.

personne

- 55 -

4.9

Les mthodes constantes

Nous allons ici analyser un nouveau motif demploi du modificateur const. Rexaminons la classe personne laquelle nous avons ajout une mthode de modification de lge setAge().
//personne_v3.h version 3 de la classe personne #ifndef __CLS_PERSONNE_V3_ #define __CLS_PERSONNE_V3_ #include <iostream.h> #include <string.h> #include <process.h> class personne { private: char * ptrNom; unsigned age; public: personne(char *,unsigned ); ~personne(void); void identifie() const; void setAge(unsigned); unsigned getAge() const; };

Ce const indique quil sagit dune mthode constante. Une mthode constante peut tre appele sur un objet variable ou constant.

Cette mthode, qui nest pas constante, ne peut pas tre appele sur un objet constant.

void personne::identifie(void) const { cout << "Je m'appelle "<< ptrNom << " et j'ai " << age << " ans\n"; } void personne::setAge(unsigned a) { age=a; } unsigned personne::getAge() const { return age ; }

Le const doit figurer ici galement. Il fait partie de la signature dune mthode.

personne::personne ( char *nom_p, unsigned age_p) { ptrNom=new char[strlen(nom_p)+1]; if(ptrNom==NULL) { cout << "Mmoire insuffisante ... Abandon ...\n"; exit(1); } strcpy(ptrNom,nom_p); age=age_p; cout << "Le constructeur de la personne (" << ptrNom << "," << age << ") a t appel\n"; }

- 56 -

personne::~personne() { cout << "Le destructeur de la personne (" << ptrNom << "," << age << ") a t appel\n"; delete ptrNom; } #endif

Exemple 25. Les mthodes constantes dune classe. La prsence dun modificateur const la suite de la liste des arguments indique quil sagit dune mthode constante, cest--dire une mthode qui peut tre invoque indiffremment sur un objet variable ou constant. En revanche, une mthode non constante ne peut tre appele que pour un objet variable. La tentative dappel dune mthode non constante sur un objet constant produit une erreur de compilation ! Remarque : notons que les constructeurs et destructeurs peuvent toujours tre appels sur les objets constants ou variables sans quil soit ncessaire de le prciser. Dans la classe prcdente, les mthodes identifie() et getAge() sont des mthodes constantes, tandis que la mthode setAge() ne lest pas. Quelle en est la consquence ? Dans lexemple suivant dutilisation de cette classe, lobjet agentV est un objet variable et agentC un objet constant .
#include personne_v3.h void main() { personne agentV("Jean",23); const personne agentC("Gerard",18); agentC.identifie() ; agentV.identifie() ; cout << " ge de agentV : " << agentV.getAge() << endl ; cout << " ge de agentC : " << agentC.getAge() << endl ; agentV.setAge(13) ; agentC.setAge(15) ; << erreur de compilation. }

// objet variable // objet constant

Tous les appels sont lgaux sauf lappel de la mthode setAge() sur un objet constant.
agentC.setAge(15) ; << erreur de compilation.

Pourquoi avoir dclar la mthode setAge() comme mthode constante ? La mthode setAge() modifie les donnes de lobjet. Aussi, si un objet est dclar constant, il nest pas souhaitable que ses donnes puissent tre modifies au travers dune mthode. Par consquent, on empche la mthode setAge() dtre appele sur un objet constant en ne la dclarant pas comme mthode constante. On adoptera donc la discipline de programmation suivante : . mthode daccs aux donnes ou accesseur : il sagit des mthodes qui retournent ou affichent des donnes de lobjet sans les modifier. Dans lexemple prcdent, les mthodes identifie() et getAge() sont des accesseurs. Les accesseurs doivent tre des mthodes constantes.

- 57 -

. mthode de modification des donnes ou modificateur ou mutateur : il sagit des mthodes qui modifient les donnes de lobjet. Un modificateur ne doit donc pas tre appel sur un objet constant. Dans lexemple prcdent, setAge() est un modificateur. Par consquent, un modificateur ne doit surtout pas tre dclar comme mthode constante. 4.10 Le pointeur this

En C++, tous les objets possdent un champ priv particulier nomm this, sans quil soit ncessaire de le dclarer. Ce champ contient ladresse de lobjet courant.
#include <iostream.h>

class C { public: C* GetAdress(){ return this;} };

Rsultat
adresse adresse adresse adresse de de de de l'objet l'objet l'objet l'objet obj1 obj2 obj1 obj2 :0x0065FDF4 :0x0065FDF0 via le pointeur this :0x0065FDF4 via le pointeur this :0x0065FDF0

void main() { C obj1,obj2;

cout << "adresse de l'objet obj1 : " << &obj1 << endl; cout << "adresse de l'objet obj2 : " << &obj2 << endl; cout << "adresse de l'objet obj1 via le pointeur this : " << obj1.GetAdress() << endl; cout << "adresse de l'objet obj2 via le pointeur this : " << obj2.GetAdress() << endl; }

Exemple 26. Le pointeur this. La classe C ne contient quune seule mthode retournant, pour chaque objet, la valeur du pointeur this, cest-dire ladresse de lobjet qui appelle cette mthode. Comme on pouvait sy attendre, on obtient la mme adresse en crivant soit &obji, soit obji.GetAdress(). 4.11 Affectation d'un objet un autre objet

Soient deux objets de mme type T :


T obj1, obj2;

L'affectation
obj1 = obj2;

est lgale (mais pas ncessairement souhaitable comme on le verra ici). Cette affectation utilise le traitement par dfaut de loprateur = pour les types dfinis par le programmeur (voir chapitre sur les oprateurs). Comme pour les structures, laffectation entre objets de mme classe est une copie membre--membre (ou champ par champ) des champs de donnes de lobjet. Ici, tous les champs de donne de l'objet obj2 sont simplement recopis dans ceux de l'objet obj1.

- 58 -

Que cela signifie-t-il exactement ? Que contient un objet ? Soit une classe d'objets qui, pour schmatiser, est dclare ainsi
class C { private: //donnes public: //mthodes };

et deux objets dclars par :


C obj1,obj2;

Chaque objet obji contient ses propres donnes. En revanche, les mthodes sont les mmes pour tous les objets de la classe C et n'existent qu'en un seul exemplaire en mmoire. Dans certains cas, l'objet obji possde un pointeur sur ses mthodes. Mais ce n'est pas le cas le plus frquent : le compilateur sait en gnral quelle mthode est appele et o elle se trouve en mmoire. Il n'a donc nul besoin de ce pointeur qui aurait la mme valeur pour tous les objets d'une mme classe. Nous verrons cependant que dans certains cas d'hritage, il y a incertitude, pour le compilateur, sur la mthode rellement appele. Il ne peut alors gnrer lui-mme l'appel la mthode. Dans ce cas, chaque objet obji aura un pointeur sur les mthodes de sa classe. Retenons simplement ici, qu'un objet ne contient que des donnes et pas de mthodes. Revenons l'affectation entre objets d'une mme classe : affecter un objet obj2 un autre objet obj1 d'une mme classe entrane la recopie des donnes de l'objet obj2 dans l'objet obj1. Voyons-en les consquences pour les deux versions de la classe personne, selon quelle a ou pas des donnes en profondeur. 4.11.1 Affectation d'un objet un autre objet dune mme classe lorsque les objets nont pas de donnes en profondeur

La premire version de la classe personne, celle ne disposant pas de donnes en profondeur, est dclare ainsi
class personne { private : char nom[20]; unsigned age; public : // mthodes };

Ecrire
personne p1("louis",15),p2(" jean",23) ; p1 = p2;

permet de copier les donnes de p1 dans p2, cest--dire tous les caractres du tableau p2.nom[] dans le tableau p1.nom[], ainsi que p2.age dans p1.age.

- 59 -

Vrifions-le sur un exemple :


#include "personne_v1.h" void main(void) { personne agent1("Jacques",20); personne agent2("Louise",18); agent1.identifie(); agent2.identifie();

Rsultat
Le Le Je Je Je Je Le Le constructeur de la personne (Jacques,20) a t appel constructeur de la personne (Louise,18) a t appel m'appelle Jacques et j'ai 20 ans m'appelle Louise et j'ai 18 ans m'appelle Jacques et j'ai 20 ans m'appelle Jacques et j'ai 20 ans destructeur de la personne (Jacques,20) a t appel destructeur de la personne (Jacques,20) a t appel

agent2 = agent1; agent1.identifie(); agent2.identifie(); }

// affectation de agent1 agent2

Exemple 27. Laffectation par dfaut est une copie membre--membre Il ny a donc aucun problme dans ce cas (et mme dans tous les cas o les donnes sont toutes stockes au sein de lobjet). 4.11.2 Affectation d'un objet un autre objet dune mme classe lorsque les objets ont des donnes en profondeur

Maintenant voyons ce qui se passe avec la seconde version de la classe personne :


class personne { private : char * ptrNom; unsigned age; public : // mthodes };

On sait que les donnes sont ici en profondeur. Nous avons vu ce que cela entranait : un constructeur qui demande de la mmoire pour stocker le nom et un destructeur qui la restituait. Voyons maintenant ce qu'implique l'affectation suivante :
personne p1("louis",15),p2(" jean",23) ; p1 = p2;

Le traitement ralis par loprateur = est toujours le mme : il ralise une copie membre--membre. Les donnes de p1 sont recopies dans p2. Cela entrane que p2.ptrNom est recopi dans p1.ptrNom ! Autrement dit, les deux pointeurs p1.ptrNom et p2.ptrNom contiennent maintenant la mme adresse, cest--dire pointent sur le mme espace mmoire.

- 60 -

Illustrons ce qui se passe lors de cette affectation 1re tape : construction des objets p1 et p2
personne p1("louis",15),p2("jean",23) ;

ptrNom 23 p2 j e a n ptrNom 15 p1 l o u i s \0 zone alloue par le concstructeur de p1 \0 zone alloue par le concstructeur de p2

age

age

Chaque objet dispose de sa propre zone de donnes en profondeur. Jusquici, tout va bien. 2nde tape : affectation par dfaut
p1 = p2;

ptrNom 23 p2 j e a n ptrNom 23 p1 l o u i s \0 \0

age

age

Comme prvu, les deux pointeurs p1.ptrNom et p2.ptrNom contiennent la mme adresse. Celle de la zone contenant la chane "jean".

- 61 -

Quels problmes cela pose-t-il ? 1) La zone alloue par le constructeur de p1, celle qui contient la chane "louis", ne pourra jamais tre rendue au gestionnaire de mmoire. En effet, on a perdu son adresse au cours de laffectation ! Cette situation se produit sur le programme suivant.
#include "personne_v2.h" void main() { personne p1("louis",15),p2(" jean",23) ; p1 = p2; }

Cela posera trs vite des problmes lorsque beaucoup dobjets seront construits et que beaucoup daffectations auront lieu au sein dun programme. La mmoire sera vite puise par ces blocs qui ne sont jamais restitus. 2) Plus grave, supposons que lobjet p1 soit dtruit mais pas p2. Au moment de la destruction de p1, le destructeur de p1 restitue la zone mmoire pointe par p1.ptrNom. Le pointeur p2.ptrNom, qui est gal p1.ptrNom, conserve nanmoins ladresse de cette zone. Lobjet p2 conserve donc ladresse dune zone qui ne lui est plus rserve et qui sera donc utilise par la suite par dautres objets. Cette situation se produit sur le programme suivant.
#include "personne_v2.h" void main() { personne p2("jean",23) ; { personne p1("louis",15) ; p1 = p2; } }

Ici, p1 est dtruit, mais pas p2!

4.12 4.12.1

Redfinition de loprateur = pour des objets avec donnes en profondeur. Paramtrage de loprateur daffectation =

Au vu de ce qui prcde, on usera avec prudence de l'affectation par dfaut entre objets. Notamment, elle ne convient pas lorsque des objets ont des donnes en profondeur. Dans ce cas, nous devons redfinir ce que doit faire loprateur daffectation. Comme nous le disions dans le chapitre sur les oprateurs (chapitre 3), loprateur daffectation = doit tre surcharg dans la classe, cest--dire en tant que fonction membre. Si loprateur = est une fonction membre de la classe personne, lorsque lon crit p1 = p2 le compilateur interprte cet appel comme lappel de la mthode operator=() sur lobjet p1 soit
p1.operator=(p2);

- 62 -

autrement dit, il cherche une mthode membre de la classe personne avec le nom operator= et acceptant un argument de type personne. Paramtrage de cet oprateur . loprateur = sera une fonction membre de la classe personne et didentifiant operator= . loprateur = recevra un objet de type personne comme argument . largument est un paramtre dentre (laffectation ne modifie jamais lobjet droite du =) Aprs cette petite analyse, on en dduit que loprateur aura lentte suivant: surtout pas de modificateur const ici. Ne doit en aucun cas tre une mthode constante puisquelle modifie lobjet qui lappelle. void personne::operator=(const personne &p) paramtre dentre de type
personne

la fonction sappelle operator= elle appartient la classe personne 4.12.2 Etapes ncessaires la copie dobjets avec donnes en profondeur.

On dcrit le traitement que doit raliser loprateur = pour la ligne suivante:


p1.operator=(p2); // qui quivaut p1=p2;

1re tape : Les objets avant la copie

2ime tape: Lobjet rcepteur , cest--dire lobjet courant (p1), doit librer sa zone mmoire

ptrNom

ptrNom
age 23 p2

age
j e a n

23 p2 j e a n

ptrNom 15 p1

\0

ptrNom
age

\0

l o u i s \0

age

15 p1 l o u i s \0

- 63 -

3ime tape : Lobjet rcepteur doit allouer une zone de la mme taille que celle de lobjet donneur

4ime tape : copie de toutes les donnes, celles situes dans lobjet et celles en profondeur.

ptrNom 23 p2 j e a

ptrNom 23 p2 j e a
n

age

age

n
ptrNom 15 p1 \0

ptrNom
age

\0

age

23 p1

? ?

Allocation d'une nouvelle zone mmoire de taille identique celle de p2

j e

? ? ?

copie de toutes les donnes de p2 vers p1

a n \0

Cette analyse nous conduit modifier la classe comme suit :

//personne_v4.h #ifndef __CLS_PERSONNE_V4_ #define __CLS_PERSONNE_V4_ #include <iostream.h> #include <string.h> #include <process.h> class personne { private: char * ptrNom; unsigned age; public: personne(char *,unsigned ); ~personne(void); void operator=(const personne &); void identifie() const; void setAge(unsigned); unsigned getAge() const; };

- 64 -

void personne::operator=(const personne &p) { si lobjet courant est diffrent de lobjet argument if(this!=&p) { delete ptrNom; // tape 2 ptrNom=new char[strlen(p.ptrNom)+1]; // tape 3 if(ptrNom==NULL) { cout << "Mmoire insuffisante ... Abandon ...\n"; exit(1); } strcpy(ptrNom,p.ptrNom); // tape 4 (donnes en profondeur) age=p.age; // tape 4 (donnes dans lobjet) } } // le reste est identique. #endif

Exemple 28. Surcharge de loprateur daffectation Pourquoi avoir ajout if(this != &p) ? En effet, dans cet oprateur, tout le traitement nest ralis que si ladresse this (ladresse de lobjet courant) est diffrente de ladresse de lobjet argument. Autrement dit, on ne ralise ce traitement que si lobjet courant est diffrent de lobjet argument. Pourquoi ? Si lon ne conditionne pas le traitement par le test if(this!=&p), en crivant
p1 = p1;

ltape 2 pose problme, puisquon supprime en mme temps les donnes de lobjet donneur et de lobjet rcepteur (il ny a dans ce cas quun seul objet !).

4.13

Les objets temporaires

Dans une expression, on peut faire appel explicitement au constructeur d'un objet. Un objet est alors construit. Il sagit dun objet temporaire construit pour les besoins d'valuation de l'expression. Il sera dtruit un moment dcid par le compilateur.
#include "personne_v4.h" void main(void) { cout << " Affichage test 1 "; cout << endl; personne("Sylvie",20).identifie(); cout << " Affichage test 2 " << } endl;

Rsultat
Affichage test 1 Constructeur de la personne (Sylvie,20) Je m'appelle Sylvie et j'ai 20 ans Destructeur de la personne (Sylvie,20) Affichage test 2

Exemple 29. Les objets temporaires - 65 -

Remarques La syntaxe personne("Sylvie",20) construit un objet temporaire (sans nom). On peut nanmoins accder ses mthodes, notamment la mthode identifie(). La personne ("Sylvie",20) n'a t construite que pour permettre l'valuation de l'expression
personne("Sylvie",20).identifie();

On voit que cet objet a t dtruit ds que l'valuation de l'expression a t mene son terme. 4.14 L'initialisation d'un objet sa dclaration l'aide du signe =

On s'intresse ici une dclaration d'objet faite sous la forme :


type_classe objet = valeur;

Il sagit dune convention dcriture (dailleurs assez ambigu). La syntaxe suivante est quivalente :
type_classe objet(valeur);

Les deux syntaxes permettent la construction de lobjet objet en appelant un constructeur 1 argument de la classe type_classe, et qui accepte un argument du type de valeur. Attention : la premire syntaxe fait appel un constructeur et nutilise donc pas loprateur = de la classe type_classe ! 4.14.1 Exemple

Le programme suivant dclare et utilise une classe appelle Entier. Cet exemple vous permet en outre de vrifier que vous matrisez quelques points : surcharge des constructeurs, valeurs par dfaut, notions de mthode accesseur (qui doit tre une mthode constante) et modificateur (qui ne doit surtout pas tre une mthode constante).
#include <iostream.h> class Entier { int valeur; public: Entier(int arg=0); Entier(double arg); ~Entier(); void Set(int arg); int Get() const; };

// // // // //

constructeur (int) constructeur (double) destructeur mutateur accesseur

Entier::Entier(int arg) { cout << "constructeur Entier(int=0) objet:"<< this <<endl; valeur=arg; } Entier::Entier(double arg) { cout << "constructeur Entier(double) objet:"<< this <<endl; valeur=(int) arg; // opration de conversion de type float->int }

- 66 -

Entier::~Entier() { cout << "destructeur ~Entier() objet:"<< this <<endl; } void Entier::Set(int arg) Rsultat { valeur=arg; constructeur Entier(int=0) objet :0x0066FDE8 } i1 = 0 int Entier::Get() const constructeur Entier(int=0) objet :0x0066FDE4 { i2 = 3 return valeur; constructeur Entier(double) objet :0x0066FDE0 } i3 = 4 constructeur Entier(int=0) objet :0x0066FDDC void main() i4 = 2 { destructeur ~Entier() objet : 0x0066FDDC Entier i1; destructeur ~Entier() objet : 0x0066FDE0 cout << "i1 = " << i1.Get() << endl; destructeur ~Entier() objet : 0x0066FDE4 Entier i2(3); destructeur ~Entier() objet : 0x0066FDE8 cout << "i2 = " << i2.Get() << endl; Entier i3=4.5; cout << "i3 = " << i3.Get() << endl; Entier i4=2; cout << "i4 = " << i4.Get() << endl; }

Exemple 30. Initialisation des objets avec la syntaxe TYPE obj=valeur. Remarques On vrifie facilement que les deux syntaxes sont en effet quivalentes. Le constructeur appel dpend chaque fois du type de largument plac derrire le signe =. 4.14.2 Notion de constructeur par recopie dit aussi constructeur-copie

Parmi les constructeurs acceptant un seul argument dont peut disposer une classe, il en est un particulier. Celui qui accepte un argument du type de la classe. Cest le constructeur qui est appel par exemple lorsque le compilateur rencontre une dclaration du genre :
classe objet2(objet1);

// o objet1 est un objet de la classe classe.

D'aprs ce qui vient d'tre dit, on s'attend ce que le compilateur cherche un constructeur de prototype :
classe::classe(classe)

ou encore (puisque largument dun constructeur est toujours un paramtre dentre)


classe::classe(const classe &)

ou encore des variantes avec des valeurs par dfaut pour les paramtres autres que le premier. En fait, la premire syntaxe classe::classe(classe) est interdite : on ne peut pas utiliser un tel constructeur. Ceci sera expliqu ultrieurement.

- 67 -

Le constructeur
classe::classe(const classe &)

porte un nom particulier : le constructeur par recopie, encore appel constructeur-copie. Il est charg de dupliquer la valeur d'un objet existant dans un objet en cours de cration. 4.14.3 Le constructeur-copie par dfaut

Si la classe classe ne dfinit pas explicitement un constructeur-copie, alors la dclaration


classe objet2(objet1);

sera nanmoins accepte. Lobjet objet1 sera recopi membre--membre dans objet2. Ceci rappelle normment ce que ralise loprateur = par dfaut. On dit que cette forme de construction est opre par le constructeur-copie par dfaut. Bien videmment4, cette solution ne sera satisfaisante que si les objets ne comportent pas de donnes en profondeur. Sans quoi, la copie membre--membre nous ramne dans la situation o deux objets ont un pointeur sur une mme zone de mmoire. En rsum, si une classe possde des donnes en profondeur, le constructeur-copie doit tre dfini explicitement : il aura pour mission de rserver une zone de mmoire de mme taille que celle de lobjet dont il sera la copie, puis de raliser la copie des donnes (en profondeur et en surface). 4.14.4 Exemple : ajout dun constructeur-copie la classe personne

On va, une fois de plus, complter la classe personne (avec donnes en profondeur) pour illustrer ce point. On ne rappelle pas toute la dfinition. On a nanmoins laiss le code de loprateur = pour montrer les diffrences qui existent entre le constructeur-copie et loprateur daffectation (on fait trop souvent lamalgame) . Diffrences entre constructeur-copie et oprateur= . lorsque lon construit un objet, celui-ci na encore allou aucune zone mmoire. Il ny a pas de dsallocation dans un constructeur-copie . il nest pas ncessaire de vrifier si largument et lobjet courant sont identiques puisque la ligne suivante nest pas compile
personne p1(p1);

Ce qui est identique pour les deux mthodes . allocation dune zone mmoire de mme taille que celle de largument . copie des donnes (en surface et en profondeur) de lobjet argument vers lobjet courant
//personne_v5.h #ifndef __CLS_PERSONNE_V5_ #define __CLS_PERSONNE_V5_ #include <iostream.h> #include <string.h> #include <process.h>

si ce nest pas une vidence, je vous invite revoir ce que lon a dit sur loprateur = par dfaut pour des objets avec des donnes en profondeur ( 4.12). - 68 -

class personne { private: char * ptrNom; unsigned age; public: personne(char *,unsigned ); personne(const personne & ); ~personne(void); void operator=(const personne &); void identifie() const; void setAge(unsigned); unsigned getAge() const; }; personne::personne(const personne &p) //constructeur-copie { ptrNom=new char[strlen(p.ptrNom)+1]; // allocation if(ptrNom==NULL) { cout << "Mmoire insuffisante ... Abandon ...\n"; exit(1); } strcpy(ptrNom,p.ptrNom); // copie (donnes en profondeur) age=p.age; // copie (donnes en surface) cout << "Le constructeur-copie de la personne (" << ptrNom << "," << age << ") a t appel\n"; } void personne::operator=(const personne &p) //oprateur= { if(this!=&p) { delete ptrNom; ptrNom=new char[strlen(p.ptrNom)+1]; if(ptrNom==NULL) { cout << "Mmoire insuffisante ... Abandon ...\n"; exit(1); } strcpy(ptrNom,p.ptrNom); age=p.age; } } // le reste est identique. #endif // constructeur-copie

Exemple 31. Dfinition dun constructeur-copie pour la classe personne

- 69 -

Cette modification autorise dsormais lexcution correcte et fiable du programme suivant


#include "personne_v5.h" void main(void) { personne agent1("Jacques",20) ; personne agent2=agent1; // utilise le constructeur-copie explicitement dfini }

4.15

Passage/retour d'un objet /par une fonction

Nous allons observer dans cette partie quels mcanismes le C++ met en uvre lorsque des objets sont passs des fonctions ou retourns par des fonctions. Pour tout ce qui suit, nous allons utiliser une nouvelle version de la classe personne complte par certaines mthodes : setName(), getName(). La mthode daffichage dun objet, nomme identifie() jusqu prsent, a t renomme affiche() dans cette version.

//personne_v6.h #ifndef __CLS_PERSONNE_V6 #define __CLS_PERSONNE_V6 #include <iostream.h> #include <string.h> #include <process.h> //dclaration/dfinition de la classe class personne { private: char* ptrNom; unsigned age; public: personne(const char*="",unsigned=0); personne(const personne &); ~personne(void); void operator=(const personne &); void affiche() const; void setAge(unsigned); unsigned getAge() const; //modificateur // accesseur

void setName(const char *); //modificateur const char * getName() const; //accesseur };

- 70 -

//dfinition des fonctions membre personne::personne ( const char *nom_p, unsigned age_p) { ptrNom=new char[strlen(nom_p)+1]; if(ptrNom==NULL) { cout << "Mmoire insuffisante ... Abandon ...\n"; exit(1); } strcpy(ptrNom,nom_p); age=age_p; //sortie :personne(const char*="",unsigned=0) objet: adresse (nom,age) cout << "personne(const char*=\"\",unsigned=0)"; cout << "\t objet : " << this << " \t("<< ptrNom << "," << age << ")"<<endl; } personne::personne(const personne &p) { ptrNom=new char[strlen(p.ptrNom)+1]; if(ptrNom==NULL) { cout << "Mmoire insuffisante ... Abandon ...\n"; exit(1); } strcpy(ptrNom,p.ptrNom); age=p.age; //sortie : personne(const personne &) objet: adresse cout << "personne(const personne &)"; cout << "\t\t objet : " << this; cout << " \t("<< ptrNom << "," << age << ")"<<endl; } void personne::operator=(const personne &p) { if(this!=&p) { delete ptrNom; ptrNom=new char[strlen(p.ptrNom)+1]; if(ptrNom==NULL) { cout << "Mmoire insuffisante ... Abandon ...\n"; exit(1); } strcpy(ptrNom,p.ptrNom); age=p.age; } } void personne::affiche(void) const { //sortie : affiche() objet: adresse (nom,age) cout << "affiche() \t\t \t \t objet :"<<this; cout << "\t("<< ptrNom <<","<< age << ")"<<endl; }

(nom,age)

- 71 -

void personne::setAge(unsigned a) { age=a; } unsigned personne::getAge() const { return age; } void personne::setName(const char * str) { delete this->ptrNom; this->ptrNom=new char[strlen(str)+1]; strcpy(ptrNom,str); } const char * personne::getName() const { return ptrNom; } personne::~personne() { // ~personne() objet : adresse cout << "~personne() \t\t\t\t objet :"<<this << endl; delete ptrNom; } #endif

Exemple 32. Sixime version de la classe personne 4.15.1 Passage dun objet par valeur une fonction

Observons lexcution du programme suivant :


#include "personne_v6.h" void recopie(personne); void main() { personne agent("Louis",15); recopie(agent); agent.affiche(); Rsultats cran } personne(const char*="",unsigned =0) objet :0x0065FDE4 (Louis,15) personne (const personne &) objet :0x0065FD88 (Louis,15) affiche() objet :0x0065FD88 (Jean,15) void recopie (personne p) ~personne objet :0x0065FD88 { affiche() objet :0x0065FDE4 (Louis,15) p.setName("Jean"); ~personne objet :0x0065FDE4 p.affiche(); }

Exemple 33. Passage dun objet par valeur - 72 -

Ce programme utilise une fonction recopie() dont le seul argument est un objet de la classe personne pass par valeur. L'exemple prcdent montre le mcanisme de recopie de l'objet agent, variable locale de main(), dans l'objet p, variable locale de la fonction recopie(). L'objet p est initialis avec la valeur de agent. Pourquoi ces diffrents affichages ? Il y a 6 sorties cran en tout. 1ire sortie cran : lobjet agent est construit en passant deux arguments au constructeur. Le premier de type char * et le second de type unsigned. Le premier affichage est donc produit par le constructeur
personne::personne(const char*="",unsigned=0)

2nde sortie cran : lobjet agent est pass la fonction recopie(). Puisquil sagit dun passage par valeur, le paramtre formel p de la fonction recopie() est diffrent du paramtre dappel agent. En fait, il faut voir le paramtre formel p comme un objet local la fonction recopie(). Lobjet p est donc contruit chaque appel de la fonction. En outre, puisque p doit tre une copie du paramtre dappel (car passage par valeur), cest le constructeurcopie qui est ici appel pour dupliquer lobjet agent dans p. La seconde sortie cran est donc la construction de lobjet p comme copie de lobjet agent. Il y a donc appel du constructeur
personne::personne(const personne &)

On remarque dailleurs que ladresse de p (0x0065FD88) est diffrente de ladresse de agent (0x0065FDE4) 3ime sortie cran : au sein de la fonction recopie(), lobjet p (local la fonction) est tout dabord modifi par linstruction p.setName("Jean") puis affich. La 3ime sortie cran correspond donc laffichage de p, cest--dire lappel de la mthode
void personne::affiche() const

sur lobjet p. 4ime sortie cran : lorsque lexcution de la fonction recopie() se termine, lobjet p (local la fonction) est dtruit. Il y a donc appel du destructeur de p
personne::~personne()

5ime sortie cran : on affiche lobjet agent. Trs naturellement, ce dernier na pas chang. 6ime sortie cran : lobjet agent est dtruit la fin de la fonction main. Que retenir de cet exemple ? Lorsquun objet est pass une fonction par valeur, il y a appel du constructeur-copie pour crer le paramtre manipul par la fonction (local la fonction). Remarque Si le constructeur-copie nest pas explicitement dfini, cest le constructeur-copie par dfaut qui est appel. Il est donc indispensable de redfinir le constructeur-copie lorsque les objets ont des donnes en profondeur, sans quoi tous les passages dobjets par valeur conduiront des fonctionnements erratiques.

- 73 -

4.15.2

Passage d'un objet une fonction par rfrence

Afin d'illustrer la diffrence entre passage par valeur et passage par rfrence pour un objet, nous reprenons l'exemple prcdent en effectuant un passage par rfrence. La fonction pas_de_recopie(personne &) reoit un argument de type personne pass par rfrence.
#include "personne_v6.h" void pas_de_recopie(personne &); void main() { personne agent("Louis",15); pas_de_recopie(agent); Rsultats cran agent.affiche(); } personne(const char*="",unsigned=0) affiche() affiche() ~personne void pas_de_recopie (personne & p) { p.setName("Jean"); p.affiche(); }

objet objet objet objet

:0x0065FDE4 :0x0065FDE4 :0x0065FDE4 :0x0065FDE4

(Louis,15) (Jean,15) (Jean,15)

Exemple 34. Passage dun objet par rfrence Remarque : clairement, il ny a quun seul objet de construit au cours de ce programme : il sagit de lobjet agent. Dans le passage par rfrence, lobjet p n'est quune rfrence. Il nengendre donc pas de construction de nouvel objet comme c'est le cas lors dun passage par valeur. De plus, on voit bien que la modification de la rfrence p par la mthode setName() a modifi lobjet agent, puisquil sagit du mme objet. Remarque : largument de la fonction pas_de_recopie() est ici un paramtre de sortie, puisque lobjet pass la fonction est effectivement modifi par celle-ci. 4.15.3 Retour d'un objet par valeur

Un autre cas "invisible" d'initialisation d'objet est celui o une fonction rend un objet par valeur. Examinons l'exemple suivant :
#include "personne_v6.h" personne recopie(personne &); void main() Rsultats cran { personne(const char*="",unsigned=0) objet :0x0065FDE4 personne agent("Louis",15); affiche() objet :0x0065FDE4 recopie(agent); personne (const personne &) objet :0x0065FDDC agent.affiche(); ~personne objet :0x0065FDDC } affiche() objet :0x0065FDE4 personne recopie (personne & p) ~personne objet :0x0065FDE4 { p.setName("Charles"); p.affiche(); return p; }

(Louis,15) (Charles,15) (Charles,15) (Charles,15)

Exemple 35. Retour dun objet par valeur

- 74 -

La fonction recopie() reoit une rfrence : le paramtre effectif agent et le paramtre formel p vont donc dsigner la mme personne. La fonction recopie() change la valeur de p, donc d'agent, par la mthode setName(), puis rend la valeur de p. Pourquoi ces sorties cran ? 1ire sortie cran : lobjet agent est construit en passant deux arguments au constructeur. 2nde sortie cran : la rfrence p, cest--dire lobjet agent, est modifie puis affiche. On remarque dailleurs que ladresse de p (0x0065FDE4) est identique celle de agent. 3ime et 4ime sortie cran : lobjet p est ici retourn par valeur. Ce nest donc pas vraiment lobjet p qui est retourn mais une copie de p. Cest pourquoi un objet est construit. Il sagit de lobjet retourn par la fonction. Il y a donc construction, puis destruction, dun objet dont ladresse est 0x0065FDDC. Il y a ici appel du constructeur-copie pour crer cet objet qui est une copie de lobjet p (et donc de lobjet agent). Remarque : mme si lobjet retourn par la fonction nest pas utilis, il est nanmoins construit puis dtruit. 5ime sortie cran : on affiche lobjet agent. On vrifie ainsi quil a bien t modifi au sein de la fonction. 6ime sortie cran : lobjet agent est dtruit la fin de la fonction main(). 4.15.4 Utilisation d'un objet retourn par valeur par une fonction

Dans l'exemple prcdent, nous n'utilisions pas la valeur retourne par la fonction recopie(). Nous pourrions par exemple l'utiliser de la faon suivante :
#include "personne_v6.h" personne recopie(personne &); void main() { personne agent("Louis",15); recopie(agent).affiche(); agent.affiche(); } personne recopie (personne & p) { p.setName("Charles"); p.affiche(); Rsultats cran return p; } personne (const char*="",unsigned =0) objet :0x0065FDE4 affiche() objet :0x0065FDE4 personne (const personne &) objet :0x0065FDDC affiche() objet :0x0065FDDC ~personne objet :0x0065FDDC affiche() objet :0x0065FDE4 ~personne objet :0x0065FDE4

(Louis,15) (Charles,15) (Charles,15) (Charles,15) (Charles,15)

On voit quil y a un affichage supplmentaire : celui de lobjet retourn par la fonction recopie(). - 75 -

4.15.5

Retour d'un objet par rfrence

Nous reprenons l'exemple prcdent en rendant l'objet par rfrence. Le programme devient celui-ci :
#include "personne_v6.h" personne & pas_de_recopie(personne &); void main() { personne agent("Louis",15); pas_de_recopie(agent).setName("Jean"); agent.affiche(); } personne& pas_de_recopie (personne & p) { p.setName("Charles"); p.affiche(); return p; Rsultats cran } personne (const char*="",unsigned =0) objet :0x0065FDE4 affiche() objet :0x0065FDE4 affiche() objet :0x0065FDE4 ~personne objet :0x0065FDE4

(Louis,15) (Charles,15) (Jean,15)

Exemple 36. Retour dun objet par rfrence Remarque Il ny a plus quun objet de construit au cours du programme : lobjet agent. Ce dernier est tout dabord construit. Do le premier affichage personne (const char*="",unsigned =0) objet :0x0065FDE4 (Louis,15) puis modifi au sein de la fonction pas_de_recopie(), affiche() objet :0x0065FDE4 (Charles,15) Mais, lobjet agent est galement modifi au moment du retour de la fonction. La ligne
pas_de_recopie(agent).setName("Jean");

signifie que lon modifie le nom de lobjet retourn par la fonction pas_de_recopie(). Puisquil sagit dun retour par rfrence, la fonction retourne une rfrence p qui est elle-mme une rfrence agent. Par transitivit, la fonction retourne donc une rfrence agent, ce qui permet de modifier lobjet agent. Le troisime affichage confirme que lobjet agent a bel et bien t modifi. 4.15.6 Pourquoi largument du constructeur-copie ne peut-il pas tre pass par valeur ?

Pourquoi le constructeur-copie de la classe personne ne peut-il pas tre dclar de la manire suivante ?
personne::personne(personne p){}

Au vu de ce que lon a dit prcdemment, si tel tait le cas, le passage de largument par valeur au constructeur-copie dclencherait un autre appel du constructeur-copie pour faire la copie de largument, et ainsi de suite rcursivement. - 76 -

Autrement dit, une telle dclaration conduit un nombre dappels infini du constructeur-copie. En pratique, cela sarrte quand la pile dborde ! 4.15.7 Conclusion

Soit une classe C. Le constructeur de prototype


C(const C &)

est appel le constructeur de recopie ou constructeur-copie. S'il existe, il est appel dans les cas suivants : . initialisation d'un objet par un autre objet dans une dclaration. . initialisation d'un paramtre objet formel par un paramtre objet effectif lors du passage par valeur dun objet une fonction. . retour d'un objet par valeur. S'il n'est pas explicitement dfini, dans les deux cas prcdents, une recopie bit bit est effectue par le constructeurcopie par dfaut. Ce mcanisme par dfaut nest toutefois pas convenable si les objets contiennent des donnes en profondeur. 4.16 4.16.1 Les tableaux d'objets Dclaration et initialisation d'un tableau d'objets

La classe gnralise la notion de type en C++. Il est donc possible de dclarer des tableaux dobjets. Se pose nanmoins la difficult de la construction et de la destruction des objets dun tableau. Illustrons ce point par un exemple. En sappuyant sur la version 6 de la classe personne, examinons lexcution du programme suivant :
#include "personne_v6.h" void main() { personne tableau[3];

Rsultats cran
personne (const char*="",unsigned =0) objet :0x0065FDE0 personne (const char*="",unsigned =0) objet :0x0065FDE8 personne (const char*="",unsigned =0) objet :0x0065FDF0 ~personne objet :0x0065FDF0 ~personne objet :0x0065FDE8 ~personne objet :0x0065FDE0 } (,0) (,0) (,0)

La fonction main() ne contient que la dclaration dun tableau de 3 objets de type personne. On remarque que tous les objets du tableau font appel un constructeur. Ici, en labsence dinformations supplmentaires, seul le constructeur pouvant tre appel sans argument est utilis. En C++, tout objet fait ncessairement appel un constructeur, y compris dans un tableau On remarque que naturellement tous les objets du tableau sont galement dtruits lorsque la fonction main() se termine.

- 77 -

Remarque : pour pouvoir construire un tableau avec cette syntaxe, il est donc indispensable que la classe contienne un constructeur pouvant tre appel sans argument. Ici, cest rendu possible par la prsence darguments avec valeurs par dfaut. Une syntaxe diffrente, galement utilise en C, permet de passer des informations aux constructeurs de chacun des objets du tableau.
#include "personne_v6.h" void main() { personne agent("Louis",15); personne tableau[3]={"Louis",personne("Charles",9),agent};

Rsultats cran
personne (const personne (const personne (const personne (const ~personne ~personne ~personne ~personne char*="",unsigned =0) objet :0x0065FDE4 char*="",unsigned =0) objet :0x0065FDCC char*="",unsigned =0) objet :0x0065FDD4 personne & ) objet :0x0065FDDC objet :0x0065FDDC objet :0x0065FDD4 objet :0x0065FDCC objet :0x0065FDE4 (Louis,15) (Louis,0) (Charles,19) (Louis,15)

Le premier affichage rsulte de la construction de lobjet agent. Pour les trois affichages suivants, il sagit de lappel des constructeurs des 3 objets du tableau. Lobjet tableau[0] est construit avec le constructeur deux arguments, le second argument prenant pour valeur la valeur par dfaut 0. Lobjet tableau[1] est construit avec le constructeur deux arguments "Charles" et 9. Lobjet tableau[2] est construit avec le constructeur-copie. Il est construit comme une copie de lobjet agent. 4.16.2 Utilisation de la syntaxe delete[]

Lorsque lon a prsent loprateur delete, nous avons prcis quil existait une seconde syntaxe delete[] dutilisation de cet oprateur, sans pouvoir en expliquer lutilisation.
#include "personne_v6.h" void main() { personne * ptr; ptr=new personne[3];

Rsultats cran
delete ptr; personne (const char*="",unsigned =0) objet :0x0065FD84 personne (const char*="",unsigned =0) objet :0x0065FD8C personne (const char*="",unsigned =0) objet :0x0065FD94 ~personne objet :0x0065FD84 (,0) (,0) (,0)

- 78 -

Dans cet exemple, on dclare un pointeur sur un objet de type personne. En ralit, on y stocke ladresse dun tableau de 3 objets. Puisquaucune information ne peut tre passe au constructeur de chacun de ces 3 objets, cest le constructeur qui peut tre appel sans argument qui est utilis. Remarque : pour pouvoir utiliser la syntaxe new personne[n], la classe doit avoir un constructeur sans argument. Que remarquons-nous ? Lorsque lon fait delete ptr, seul un objet est correctement dtruit. Les autres objets ne sont pas dtruits. Pourquoi ? La ligne delete ptr; rend la mmoire alloue par la commande new personne[3], cest--dire rend au gestionnaire de mmoire la zone occupe par les donnes des 3 objets. Par contre, seul un des destructeurs de ces trois objets est appel, celui de lobjet ptr[0] (le premier objet du tableau). Autrement dit, la mmoire alloue pour les donnes en profondeur des objets ptr[1] et ptr[2] nest pas restitue. Si lon utilise la seconde syntaxe de loprateur delete, il ny a plus de problme.
#include "personnev6.h" void main() { personne * ptr; ptr=new personne[3];

Rsultats cran
delete [] ptr; personne (const char*="",unsigned =0) objet :0x0065FD84 personne (const char*="",unsigned =0) objet :0x0065FD8C personne (const char*="",unsigned =0) objet :0x0065FD94 ~personne objet :0x0065FD94 ~personne objet :0x0065FD8C ~personne objet :0x0065FD84 (,0) (,0) (,0)

Exemple 37. Restitution dun tableau dobjets allou dynamiquement par la syntaxe delete []

- 79 -

- 80 -

5
5.1

ELABORATION D'UNE CLASSE DE CHAINES DE CARACTERES.


Introduction

Dans ce chapitre, nous prsentons llaboration dune classe. Ce chapitre va servir de synthse des lments prsents prcdemment. Cette laboration permettra galement de revenir un peu sur la surdfinition doprateurs en tant que membres de classes. Nous souhaitons crer une classe de chanes de caractres qui aurait les avantages des chanes de caractres des langages Pascal et C mais pas leurs inconvnients. Prsentons les uns et les autres : Les chanes de caractres en Pascal Inconvnient : les chanes sont limites en taille 255 caractres. Avantages : elles sont d'un usage simple et sr. En Pascal, on peut raliser l'opration
chaine1=chaine2

qui recopie chaine1 dans chaine2. Si chaine2 dborde de chaine1, elle est tronque. On peut comparer directement deux chanes avec les oprateurs habituels : <, <=, >, >=, =, <> On peut concatner deux chanes par l'opration
chaine1+chaine2

Les chanes de caractres en C Avantage : les chanes ne sont pas limites en taille. Inconvnients : Aucune vrification de dbordement n'est faite. La copie de chanes ncessite lappel dune fonction
strcpy(chaine1,chaine2)

Si chaine2 est plus longue que chaine1, il y aura dbordement. On ne peut comparer deux chanes avec les oprateurs habituels : il faut passer par les fonctions strcmp (string compare). On ne peut concatner deux chanes par l'opration
chaine1+chaine2

Il faut passer par la fonction strcat. Il y a l aussi risque de dbordement. La lecture d'une chane par la fonction standard gets (la plus utile puisqu'elle accepte les blancs dans la chane, ce que ne fait pas scanf) peut l encore entraner un dbordement.

- 81 -

5.2

Lutilisation et linterface de la classe

La conception dune classe commence gnralement par spcifier quelle interface on en attend. On prsente donc tout dabord un programme dutilisation de la classe (qui nest toutefois pas encore crite).

#include "chaine.h" void main() { chaine s1; chaine s2="une chaine "; chaine s3=s2; cout << s3 << endl; chaine s4; s4=chaine("de caracteres"); s2=s4; cout << s2 << endl; s2 = s3 + s4; cout << s2 << endl; s2[0]='C'; cout << s2 << endl; // affectation entre deux objets de type chaine // chane vide // chane initialise par une chane du C // chane initialise par copie // affichage dune chane lcran

// concatnation de deux chaines

// une chane sera vue galement comme un tableau

s3=s2; if(s3==s1) cout << "chaines identiques" << endl; }

Exemple 38. Spcification pour la classe chaine On doit pouvoir dclarer : - une chane vide (objet s1) - une chane initialise par une chane du langage C (objet s2) - une chane initialise par un autre objet de type chaine (objet s3) On va crire la classe chaine de faon pouvoir afficher une chane de manire homogne avec celle utilise par le C++, cest--dire en surdfinissant loprateur <<.
cout << s3 << endl; // affichage dune chane lcran

On va dfinir loprateur = pour laffectation et loprateur + pour la concatnation de chanes.


s2=s4; s2 = s3 + s4; // affectation entre chanes // concatnation de deux chaines

On va munir les chaines dun accs direct, comme sil sagissait aussi de tableaux. On va munir la classe chaine dun oprateur de test dgalit entre deux chanes (oprateur ==).
if(s3==s1) cout << "chaines identiques" << endl;

- 82 -

5.3

Les donnes de la classe chaine. On analyse ici quelles donnes doivent tre stockes et comment les stocker. Il est clair que chaque objet de la classe chaine doit disposer dune zone de mmoire (un tableau) permettant de stocker de manire contigu un ensemble de caractres. Au vu de ce que lon a dj rencontr dans les chapitres prcdents, il y a deux solutions : . chaque objet dispose dun tableau de taille identique pour tous les objets . chaque objet se charge dallouer/restituer une zone de donnes de taille en adquation avec les besoins de lobjet.

La premire solution conduirait une dclaration du type


class chaine { private: char str[n]; public: // etc. };

// avec n une constante

Cette premire solution nest absolument pas satisfaisante pour les raisons suivantes : . une chane aurait ncessairement une taille limite (cest le problme des chanes en Pascal que lon voulait rsoudre) . on gaspille de lespace mmoire lorsque les chanes stockes sont de faible taille. Seule la deuxime solution est satisfaisante. Elle requiert pour chaque objet dallouer de la mmoire au moment de la construction et de la restituer au moment de la destruction. Ainsi, les chanes nauront plus dautres limites de taille que celle impose par la mmoire disponible dans le tas. Chaque objet doit donc disposer dune variable pointeur permettant de mmoriser ladresse dun tableau de caractres. En outre, le caractre de fin de chane du C (caractre \0 de valeur numrique 0) nest plus indispensable si lon mmorise galement la taille de la chane. La dclaration de la classe chaine est alors
class chaine { private: char * _ptrChaine; int _taille; public: };

// adresse du tableau de caractres // taille du tableau

Note : certains programmeurs identifient les donnes par des noms commenant pas le caractre _ . Ceci permet dutiliser le mme nom sans caractre _ comme nom de mthode : ici on pourra appeler taille() la mthode renvoyant le nombre de caractres dune chane. 5.4 Les constructeurs et le destructeur de la classe chaine

Le programme dutilisation donn en dbut de chapitre oriente la faon dcrire les constructeurs. Il doit y avoir un constructeur sans argument (ou avec des arguments ayant des valeurs par dfaut), un constructeur avec argument de type char *, un constructeur-copie et un destructeur.

- 83 -

Ceci conduit lbauche suivante :


class chaine { private: char * _ptrChaine; int _taille; public: chaine(); chaine(const char *); chaine(const chaine &); ~chaine(); };

Remarque : on pourrait galement dfinir un constructeur avec un argument ayant une chane vide comme argument par dfaut :
class chaine { private: char * _ptrChaine; int _taille; public: chaine(const char *=""); chaine(const chaine &); ~chaine(); };

Nous ne dvelopperons pas cette dernire version qui ne prsente toutefois aucune difficult supplmentaire. Les dfinitions des constructeurs et du destructeur sont les suivantes : Le constructeur sans argument (construction dune chane vide)
chaine::chaine() { _ptrChaine=NULL; _taille=0; }

// facultatif

Le constructeur avec un argument de type pointeur sur caractre.


chaine::chaine(const char * str) { _taille=strlen(str); _ptrChaine=new char[_taille];

// allocation dun tableau

if(!_ptrChaine) // si _ptrChaine==0 { cerr << "Manque de mmoire..." << endl; exit(1); } memmove(_ptrChaine,str,_taille*sizeof(char)); }

// copie de la chane

- 84 -

Remarque : . largument de ce constructeur est un paramtre dentre (do lemploi du const) . si lallocation nest pas possible, loprateur new retourne le pointeur NULL (valeur 0). Si tel est le cas, on quitte le programme en affichant un message derreur sur le flot derreur cerr. . la fonction memmove() permet la copie dune zone mmoire vers une autre. En effet, puisque lon a supprim le caractre de fin de chane, il est exclus de pouvoir utiliser la fonction strcpy(). Le constructeur-copie
chaine::chaine(const chaine & c) { _taille=c._taille; _ptrChaine=new char[_taille]; if(!_ptrChaine) { cerr << "Manque de mmoire..." << endl; exit(1); } memmove(_ptrChaine,c._ptrChaine,_taille*sizeof(char)); }

Le destructeur
chaine::~chaine() { delete _ptrChaine; }

5.5

Factorisation dun traitement.

On voit dans la dfinition des mthodes prcdentes que les traitements raliss par les constructeurs prsentent certaines similitudes. Aussi, il parat intressant de regrouper tous les traitements semblables au sein dune mme fonction dont laccs sera restreint la classe. On appelle cette technique une factorisation de code. La factorisation concerne ici lallocation plus la copie dune chane de caractres. On ajoute donc une mthode la classe. Il sagit dune mthode prive car seuls les objets de la classe en ont besoin. Il serait mme risqu den laisser laccs public. Ceci conduit aux modifications suivantes :
class chaine { private: char * _ptrChaine; int _taille; void alloueAffecte(const char *,int); // allocation + copie public: chaine(); chaine(const char *); chaine(const chaine &); ~chaine(); };

alloueAffecte()

- 85 -

Les dfinitions des fonctions membres voluent de la manire suivante


// factorisation de lallocation + la copie (mthode prive) void chaine::alloueAffecte(const char * p,int t) { _ptrChaine = new char [_taille=t]; if(_ptrChaine==NULL) { cerr << "erreur d'allocation "; exit(1); } memmove(_ptrChaine,p,t); } //constructeur sans argument chaine::chaine() { _ptrChaine=NULL; _taille=0; } // constructeur avec un argument de type pointeur sur char chaine::chaine(const char * str) { alloueAffecte(str,strlen(str)); } // constructeur-copie chaine::chaine(const chaine & c) { alloueAffecte(c._ptrChaine,c._taille); } // destructeur chaine::~chaine() { delete _ptrChaine; }

5.6

Les oprateurs membres ou non membres dune classe.

On fait ici une petite parenthse sur les oprateurs pour pouvoir poursuivre lcriture de la classe chaine. On sait quil est possible de surcharger les oprateurs pour les nouveaux types. On la par exemple appliqu sur des structures et cela se gnralise sans problme aux classes. Dans le chapitre 3, les oprateurs apparaissaient comme des fonctions non membres dune classe. Lorsque des oprateurs sappliquent des objets dune classe, il est possible de les faire apparatre comme fonctions membres de celle-ci. On a dailleurs illustr ceci en surchargeant loprateur = pour laffectation entre objets de type personne. En pratique, mis part les quelques oprateurs quil est ncessaire de faire apparatre comme membres dune classe (voir chap. 3), on a toujours le choix de surcharger un oprateur soit comme fonction membre dune classe, soit comme fonction non membre. (cest--dire une fonction classique ne dpendant daucune classe). Il convient de prendre un exemple pour comprendre ceci.

- 86 -

Par exemple, si C est un type classe et que c1 et c2 sont deux objets de la classe C,
C c1,c2;

lorsque le compilateur rencontre une ligne telle que


c1+c2;

celui-ci a deux faons de pouvoir interprter cet appel doprateur.


operator+(c1,c2);

c1+c2 c1.operator+(c2);

Premire possibilit : il sagit dune fonction operator+( , ) deux arguments de type C (ou rfrence C) puisque loprateur + est ici un oprateur binaire. Cette fonction nest membre daucune classe. Seconde possibilit : il sagit dune fonction membre de la classe C et qui est appele sur lobjet c1 (loprande de gauche). Cette mthode na plus quun seul argument (le 2ime oprande). Le premier oprande tant alors lobjet courant. Le concepteur de la classe C choisira donc entre ces deux possibilits. Ce qui est dit ici vaut galement pour tous les oprateurs binaires quil nest pas ncessaire de dclarer comme membres dune classe. Comment choisir entre ces deux possibilits ? Dans la pratique, comme les oprateurs ont gnralement besoin daccder aux donnes de lobjet, les oprateurs sont autant que possible dclars comme fonctions membres de la classe laquelle ils sappliquent. Cest dans cet esprit que lon va continuer lcriture de la classe chaine. Nanmoins, la surcharge de certains oprateurs, comme loprateur << pour laffichage, conduit des situations o loprateur ne peut pas tre dfini comme membre dune classe. 5.7 5.7.1 Les oprateurs de la classe chaine Loprateur = pour laffectation entre objets de type chaine

Il y a ici peu de nouveaut par rapport au cas trait dans les chapitres prcdents. Puisque lobjet contient des donnes en profondeur, laffectation doit tre redfinie, et ce de la manire suivante. Tout dabord, au sein de la classe le prototype de loprateur est :
class chaine { public: // chaine& operator=(const chaine &); // } ;

- 87 -

Sa dfinition est ensuite :


chaine & chaine::operator =(const chaine & c) { if(this!=&c) { delete _ptrChaine; alloueAffecte(c._ptrChaine,c._taille); } return *this; }

Seule nouveaut : loprateur retourne un objet de type chaine. Pourquoi ? Tout simplement pour que la ligne suivante soit compile et excute avec succs : Ce qui est interprt comme
s1.operator=(s2.operator=(s3)); s1=s2=s3 ;

Lobjectif est de garder une certaine homognit avec le C++ o de telles affectations en cascade sont possibles. Il est donc ncessaire que loprateur = retourne lobjet courant. Loprateur doit donc se terminer par
chaine & chaine::operator =(const chaine & c) { // return *this; } // retourne lobjet courant

De plus, puisque cet objet nest pas local la fonction, on peut le retourner par rfrence et viter ainsi une duplication de lobjet au moment du retour. 5.7.2 Les oprateurs dindexation []

Nous souhaitons pouvoir conserver laccs direct aux caractres dune chane, comme sil sagissait dun tableau. Il convient pour cela de redfinir loprateur []. Nous souhaitons pouvoir crire
chaine s1="abcd"; s1[2]=e;

Cette dernire ligne est interprte par le compilateur comme lappel suivant :
s1.operator[](2)=e;

Autrement dit, loprateur dindexation, qui dailleurs ne peut tre surcharg qu la condition dtre membre de la classe, doit avoir un prototype du style
type_retour chaine::operator[](int indice);

Il sagit donc dune fonction membre de la classe chaine dont le seul argument correspond lindice figurant entre les crochets.

- 88 -

Il reste rgler le problme du retour de loprateur. Il faut distinguer deux cas. Loprateur ne doit pas proposer les mmes services pour les objets constants et les objets variables.
chaine svar ="modifiable "; const chaine sconst = "immuable"; cout << svar[1] << endl ; cout << sconst[1] << endl ; svar[2]=c ; sconst[2]=c ; // << doit tre interdit par le compilateur.

Il doit y avoir en fait deux oprateurs [ ] au sein de la classe. Lun pour traiter les objets constants, lautre les objets variables. Loprateur [ ] pour les objets variables. Loprateur dindexation traitant les objets variables doit pouvoir tre appel gauche dune affectation. Dans le jargon du C++, ce doit tre un left-operand.
svar[2]=c; // quivaut svar.operator[](2)=c;

Autrement dit, il est ncessaire que le retour de loprateur soit un retour par rfrence. Loprateur dindexation doit retourner une rfrence un caractre du tableau. Pour loprateur dindexation non-constant lentte est donc
char & chaine::operator[](int indice){}

Loprateur [ ] pour les objets constants. Loprateur dindexation traitant les objets constants ne doit pas pouvoir tre appel gauche dune affectation.
sconst[2]=c; // << doit tre interdit par le compilateur sconst.operator[](2)=c; // << interdit

autrement dit, il suffit que le retour de loprateur soit un retour par valeur. Loprateur dindexation constant doit donc retourner un caractre du tableau par valeur. Pour loprateur dindexation constant, lentte est donc :
char chaine::operator[](int indice) const {}

Contrle des indices Puisque laccs aux caractres dun objet chaine passe par lappel dun oprateur que lon matrise, on peut ajouter un contrle supplmentaire vrifiant la validit de lindice. En effet, si lindice est ngatif ou suprieur _taille-1, le programme doit sarrter et afficher un message derreur indiquant lutilisateur que le programme contient certainement un problme dordre algorithmique.

- 89 -

Finalement, les deux oprateurs sont dfinis de la manire suivante.


class chaine { public: char& operator[](int); char operator[](int) const; // }; // oprateur [] (pour objets variables) char & chaine::operator [](int indice) { if(indice<0 || indice>=_taille) // contrle d'indice { cerr << "indice hors de la plage hors de la plage dindices"<<endl; exit(2); } else return _ptrChaine[indice]; } // oprateur [] (pour objets constants) char chaine::operator [](int indice) const { if(indice<0 || indice>=_taille) // contrle d'indice { cerr << "indice hors de la plage dindices"<< endl; exit(2); } else return _ptrChaine[indice]; }

5.7.3

Loprateur de concatnation (oprateur +)

La concatnation de chanes de caractres doit tre rendue possible par la syntaxe


s3 = s1+s2;

ce qui est en fait interprt comme


s3.operator=(s1.operator+(s2));

dans le cas o, comme nous lavons dit, nous choisissons de dfinir loprateur + comme membre de la classe chaine. Compte-tenu de nos habitudes de programmation, lorsque lon lit la ligne suivante
s3 = s1+s2;

on comprend que, dune part, s3 va contenir la concatnation des chanes s1 et s2, mais aussi que ni s1 ni s2 nest modifie par cette concatnation. En ce qui concerne loprateur +, on en dduit que ni lobjet courant, ni lobjet argument nest modifi par cet oprateur.

- 90 -

Lbauche de lentte de cet oprateur est donc


type_retour chaine::operator+(const chaine &) const {}

Quel est le retour de cet oprateur ? Quel en est le type ? Clairement, cet oprateur doit retourner un objet de type chaine ralisant la concatnation de lobjet courant avec lobjet argument. Est-ce un retour par valeur ou par rfrence ? Pour que le retour soit un retour par rfrence, il faut que lobjet retourn ne soit pas local loprateur +. Or, ici nous navons pas dautre choix que de crer lobjet rsultat au sein mme de loprateur (puisque le retour nest ni lobjet courant, ni lobjet argument). Le retour est donc ici forcment un retour par valeur. Lentte complet de cet oprateur de concatnation est donc
chaine chaine::operator+(const chaine &) const

La dclaration et la dfinition de cet oprateur sont donc :


class chaine { public: chaine operator+(const chaine &) const; //.. }; chaine chaine::operator+(const chaine & c) const { chaine resultat; //chaine rsultat (initialement vide) resultat._taille=_taille + c._taille; resultat._ptrChaine = new char[resultat._taille]; // allocation de mmoire pour stocker // la concatnation if(resultat._ptrChaine==NULL) { cerr << "erreur d'allocation" << endl; exit(1); } memmove(resultat._ptrChaine,_ptrChaine,_taille); memmove(resultat._ptrChaine+_taille,c._ptrChaine,c._taille); return resultat; }

5.7.4

Loprateur <<

On souhaite pouvoir crire


cout << s1 << endl;

pour afficher un objet chaine lcran. La ligne prcdente peut tre interprte de deux manires selon que loprateur est membre ou non dune classe.

- 91 -

Si loprateur << est membre dune classe, ce doit tre un membre de la classe dont est issu lobjet cout. Lappel est alors interprt de la manire suivante par le compilateur
cout.operator<<(s1).operator<<(endl);

Si loprateur << nest pas membre dune classe, lappel de loprateur est interprt
operator<<(operator<<(cout,s1),endl);

Loprateur << ne peut pas tre surcharg dans la classe de cout. Lobjet cout est un objet de la classe ostream (out stream = flot de sortie) qui est dfinie dans iostream.h. On ne peut pas dfinir loprateur << pour afficher des objets chaine dans la classe ostream puisque lon nen pas le fichier source. Il est donc impossible ici de surcharger loprateur pour laffichage comme membre de la classe ostream.. Il ne reste plus que lautre solution. La ligne
cout << s1 << endl;

sera interprte
operator<<(operator<<(cout,s1),endl);

Le premier oprande de cet oprateur est un objet de la classe ostream. Il faut considrer quune criture modifie le flot cout. Aussi, le premier oprande est un paramtre de type entre-sortie. Le passage par rfrence simpose. Le second oprande est un objet de la classe chaine qui lui ne sera pas modifi par laffichage. Le second oprande peut donc tre pass par rfrence constante.

Loprateur doit retourner le flot cout ! Attention ! Loprateur doit retourner le flot de type ostream par rfrence pour autoriser des insertions successives dans le flot du style
cout << s1 << s2 << endl;

Le prototype de cet oprateur est donc


ostream & operator<<(ostream &, const chaine & );

et sa dfinition est
ostream & operator<<(ostream & flot,const chaine & c) { for(int i=0;i<c.taille();i++) { flot << c[i]; } return flot; }

- 92 -

5.8

La classe chaine au complet

Aprs avoir dtaill, mthode par mthode, lcriture de cette classe, on donne ici le rsultat de cette tude. On a en outre ajout des mthodes qui ne doivent plus, ce stade, poser de problme. Dclaration / Dfinition de la classe
//chaine.h #ifndef __CLASSE_CHAINE__ #define __CLASSE_CHAINE__ #include<iostream.h> #include<string.h> #include<process.h> #include<memory.h> class chaine { private: char * _ptrChaine; int _taille; void alloueAffecte(const char *,int); public: chaine(); chaine(const char *); chaine(const chaine &); ~chaine(); chaine& operator=(const chaine &);

int taille() const; chaine operator+(const chaine &) const;

char& operator[](int); char operator[](int) const; bool }; ostream & operator<<(ostream & ,const chaine & ); #endif operator==(const chaine &) const;

Remarque En pratique, le fichier de dfinition dune classe (ici chaine.h) est trs utile. Il donne suffisamment dinformations pour pouvoir utiliser la classe. On y voit, entre autres, quels constructeurs sont disponibles (ici 3 constructeurs) ou encore si lon peut raliser des affectations avec loprateur =. Mme si cela na pas t fait ici, il est donc judicieux de bien commenter ce fichier (sans le rendre illisible) en expliquant comment fonctionne linterface de la classe, puisque cest tout ce dont a besoin lutilisateur.

- 93 -

Dfinition des fonctions membres


// fichier chaine.cpp #include "chaine.h" // fonction accs priv void chaine::alloueAffecte(const char * p,int t) { _ptrChaine = new char [_taille=t]; if(_ptrChaine==NULL) { cerr << "erreur d'allocation "; exit(1); } memmove(_ptrChaine,p,t); } //constructeur sans argument chaine::chaine() { _ptrChaine=NULL; _taille=0; } // constructeur avec un argument de type pointeur sur char chaine::chaine(const char * str) { alloueAffecte(str,strlen(str)); } // constructeur-copie chaine::chaine(const chaine & c) { alloueAffecte(c._ptrChaine,c._taille); } // destructeur chaine::~chaine() { delete _ptrChaine; } int chaine::taille() const { return _taille; } // oprateur = chaine & chaine::operator =(const chaine & c) { if(this!=&c) { delete _ptrChaine; alloueAffecte(c._ptrChaine,c._taille); } return *this; }

- 94 -

chaine chaine::operator+(const chaine & c) const { chaine resultat; resultat._taille=_taille+c._taille; resultat._ptrChaine = new char[resultat._taille]; if(resultat._ptrChaine==NULL) { cerr << "erreur d'allocation" << endl; exit(1); } memmove(resultat._ptrChaine,_ptrChaine,_taille); memmove(resultat._ptrChaine+_taille,c._ptrChaine,c._taille); return resultat; } // oprateur [] : accs un caractre d'une chane char & chaine::operator [](int indice) { if(indice<0 || indice>=_taille) // contrle d'indice { cerr << "indice hors de la plage"<<endl; exit(2); } else return _ptrChaine[indice]; } // oprateur [] (pour objets constants) char chaine::operator [](int indice) const { if(indice<0 || indice>=_taille) // contrle d'indice { cerr << "indice hors de la plage"<< endl; exit(2); } else return _ptrChaine[indice]; } // oprateur == : retourne true si chane courante = chane argument bool chaine::operator==(const chaine & c) const { if(_taille==c._taille) { if(_taille==0) return true; else return !strncmp(_ptrChaine,c._ptrChaine,_taille); } else return false; }

- 95 -

// oprateur << pour cout << chaine("bonjour"); ostream & operator<<(ostream & flot,const chaine & c) { for(int i=0;i<c.taille();i++) { flot << c[i]; } return flot; }

- 96 -

6
6.1

AMITIE : FONCTIONS AMIES, CLASSES AMIES.


Introduction

La programmation objet impose lencapsulation de donnes, ne rendant celles-ci accessibles quaux membres de la classe. En C++, lunit de protection est la classe, ce qui signifie quune fonction membre dune classe peut accder la partie prive de tout objet de cette classe.

class A { private: int data; public: int EstEgal(A x) { return (data==x.data); } }; void main() { A obj1,obj2; // if(obj1.EstEgal(obj2)) { // } }

Dans cet exemple, la fonction EstEgal() appele sur lobjet obj1 a accs galement la donne prive data de obj2. En revanche, lencapsulation interdit une fonction membre dune classe daccder des membres privs dune autre classe. Cette contrainte peut-tre gnante dans certaines circonstances, par exemple lorsque lon surcharge loprateur << pour laffichage dun objet lcran. La notion damiti va permettre certaines fonctions non membres dune classe davoir nanmoins accs la partie prive dune classe. Une fonction f() amie dune classe A a accs la partie prive de tout objet de la classe A. Comment se dclare lamiti ? Lors de la dclaration/dfinition dune classe, il est possible dy dclarer quune ou plusieurs fonctions, extrieures la classe, sont amies de la classe. Remarque : puisque lamiti est dclare dans la classe, une fonction quelconque ne peut pas se proclamer amie dune classe. Cest la classe qui attribue ce droit. 6.2 Fonction amie dune classe.

Il y a plusieurs situations damiti. La premire consiste rendre une fonction extrieure toute classe amie dune ou de plusieurs classes. Chaque lien damiti est dclar dans une classe. - 97 -

Dans lexemple suivant, la fonction f() acceptant un argument de type A est dclare amie de la classe A.

class A; int f(A); // prototype

class A { friend int f(A); private: int data; public: // };

// la fonction f est dclare amie de la classe A

int f(A x) { return x.data; // OK puisque f est amie de A, elle a accs x.data }

Exemple 39. Fonction amie dune classe 6.3 Fonction membre dune classe amie dune autre classe.

Cette deuxime situation prsente une fonction dune classe B amie dune classe A. Soient deux classes A et B. Dans la classe B, une fonction membre a lentte suivant
int B::f(char c,A x) { // }

On souhaite que cette fonction f() ait accs aux membres privs des objets de la classe A. Il faut pour cela quelle soit amie de la classe A. La dclaration damiti dans la classe A est la suivante :
class A; // dclaration anticipe class B { private: // public: int f(char, A); }; class A { friend int B::f(char,A); // };

Ici, le compilateur a besoin de savoir que A est une classe, do la dclaration anticipe.

// la fonction B::f() est dclare amie de la classe A

Exemple 40. Fonction dune classe amie dune autre classe - 98 -

6.4

Fonction amie de plusieurs classes.

Soient deux classes A et B et une fonction


int f(A x,B y) { // }

Il est souvent pratique quune telle fonction f() ait accs aux membres privs de A et B sans restriction. Il suffit de dclarer f() amie de chacune des classes A et B. Il y a donc deux dclarations damiti.
class B; class A { friend int f(A,B); // }; class B { friend int f(A,B); // };

// la fonction f est dclare amie de la classe A

// la fonction f est aussi dclare amie de la classe B

int f(A x ,B y) { // les champs privs des objets x et y sont ici accessibles. }

Exemple 41. Fonction amie de deux classes. 6.5 Classe amie dune autre classe.

Pour dclarer que toutes les fonctions membres dune classe B sont amies dune classe A, on crit
class B; // dclaration anticipe de la classe B class A { friend class B; };

Toutes les fonctions de la classe B sont amies de la classe A.

6.6

Exemple : loprateur << de la classe chaine

Dans la classe chaine dcrite au chapitre prcdent, comme loprateur << tait extrieur la classe chaine, il lui tait impossible daccder la partie prive de la classe chaine. On aurait pu modifier la classe de la manire suivante.

- 99 -

Dclaration / Dfinition de la classe


//chaine.h #ifndef __CLASSE_CHAINE__ #define __CLASSE_CHAINE__ #include<iostream.h> #include<string.h> #include<process.h> #include<memory.h> class chaine { private: char * _ptrChaine; int _taille; void alloueAffecte(const char *,int); public: chaine(); chaine(const char *); chaine(const chaine &); ~chaine(); int taille() const; chaine& operator=(const chaine &); chaine operator+(const chaine &) const; char& operator[](int); char operator[](int) const; bool operator==(const chaine &) const;

Loprateur << est dclar ami de la classe chaine.

friend ostream & operator<<(ostream &, const chaine &); }; #endif

#include "chaine.h" // // oprateur << pour cout << chaine("bonjour"); // fonction amie de la classe chaine ostream & operator<<(ostream & flot,const chaine & c) { _taille et _ptrChaine ne for(int i=0;i < c._taille;i++) seraient pas accessibles sans la { dclaration damiti. flot << c._ptrChaine[i]; } return flot; }

- 100 -

7
7.1

LES CHANGEMENTS DE TYPE.


Introduction

En langage C, il existe des situations o des variables dun certain type sont attendues par des fonctions ou des oprateurs et que dautres types leur sont passs.
int i; float f=3.2 ; i=f; // conversion implicite float->int

Dans lexemple prcdent, il y a une conversion dun type float vers le type int. Ce changement de type est dailleurs dit dgradant dans la mesure o une partie de linformation est perdue en route : la variable i se voit affecter la partie entire de la variable f. Dans le cas prcdent, on dit quil y a eu une conversion de type implicite puisque le programmeur ne spcifie rien et le compilateur met nanmoins une rgle de conversion en oeuvre. Une syntaxe particulire permet de demander explicitement au compilateur une conversion de type lorsquune telle conversion a un sens.
int i; float f=3.2 ; i=(int)f; // conversion explicite float -> int

Nous cherchons ici explorer ce qu'amnent les classes dans les changements de type. Nous tudions deux sortes de conversions : . les conversions entre un type classe et un type de base . les conversions dun type classe vers un autre type classe 7.2 Conversion dun type de base vers un type classe par un constructeur

Reprenons la classe chaine labore au chapitre 5.


class chaine { private: char * _ptrChaine; int _taille; void alloueAffecte(const char *,int); public: chaine(); chaine(const char *); chaine(const chaine &); ~chaine(); chaine& operator=(const chaine &); // };

Partant de cette classe, les lignes suivantes sont compiles et excutes sans problme :
chaine s1; s1="bonjour";

- 101 -

Pourtant il apparat clairement dans la dfinition de la classe que loprateur = attend un objet de type chaine droite. Que se passe-t-il ? Effectivement, un objet de type chaine est attendu droite de loprateur =. Puisquon passe un argument de type char *, le compilateur cherche sil est possible de convertir une chane de caractres en un objet de type chaine. Or, il est effectivement possible de construire un objet de type chaine partir dune chane de caractres du langage C. Chronologiquement, sur la ligne
s1="bonjour";

il se droule la mme chose quen crivant


s1.operator=(chaine("bonjour"));

cest--dire . construction dun objet temporaire de type chaine avec largument "bonjour" de type char * . appel de s1.operator=() avec cet objet temporaire comme argument

La rgle suivante sapplique en C++ : lorsquun objet de type classe CLS (ici chaine) 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


chaine::chaine(const char *)

dfinit la conversion dun type const char * (ou char *) vers le type chaine. Remarque : 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. On peut donner un autre exemple utilisant la classe chaine et illustrant ces conversions implicites. Pour les mmes raisons que prcdemment, la ligne suivante est compile sans aucun problme.
s1=s1+"ajout";

Elle produit le mme comportement que si lon avait crit : s1=s1.operator+(chaine("ajout")); En effet, l o loprateur + attend un objet de type chaine, 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 chaine. 7.3 Conversion dun type classe vers un type de base

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. - 102 -

Considrons lexemple suivant


chaine s1="abc"; int i; i=(int)s1; // conversion explicite du type chaine en type int

Sur cette ligne, le compilateur C++ cherche si une fonction membre appele operator int a t dfinie dans la classe chaine. Pour que les lignes prcdentes soient compiles avec succs, il est donc ncessaire que lajout de loprateur int soit effectu la classe chaine.
class chaine { private: char * _ptrChaine; int _taille; void alloueAffecte(const char *,int); public: // operator int() {return _taille;} };

Ici, nous avons dfini la conversion chaine -> int de sorte de convertir un objet chaine en sa taille. Note : il nest pas trs vident de comprendre toujours prcisment quelles conversions le compilateur ralise implicitement. Il peut mme y avoir plusieurs conversions en cascade. Aussi, il est recommand de ne pas abuser de ces oprateurs de conversion. 7.4 Exemple

On va illustrer toutes ces rgles de conversions sur une classe complexe pour laquelle nous avons dfini loprateur + pour la somme de deux nombres complexes.
//complexe.h #ifndef __COMPLEXE__ #define __COMPLEXE__ #include<iostream.h> class complexe { private: double re; // partie relle double im; // partie imaginaire public: complexe(double r=0,double i=0) { cout << "complexe(double=0,double=0) \t objet : "<<this << endl; re=r; im=i; } ~complexe() { cout << "~complexe() \t\t\t objet : "<<this << endl; }

- 103 -

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() { cout << "complexe::operator double() \t obj:"<<this<<endl; return re; } friend ostream & operator<<(ostream & flot,const complexe & c); }; ostream & operator<<(ostream & flot, const complexe & c) { flot << c.re; if(c.im) { flot << "+"<<c.im<<"i"; } return flot; } #endif

Le programme dutilisation est le suivant :


#include"complexe.h"

Rsultats
void main() { complexe c1,c2(1,3); c1=2.4; cout << c1 << endl; c2=c1+c2; cout << c2 << endl; c1=c1+(complexe)3.5; cout << c1 << endl; c1=(double)c1+3.5; cout << c1 << endl; double d; d=c1; cout << d << endl; complexe(double=0,double=0) objet : 0x0066FDDC complexe(double=0,double=0) objet : 0x0066FDCC complexe(double=0,double=0) objet : 0x0066FDB4 ~complexe() objet : 0x0066FDB4 2.4 complexe::operator+() obj :0x0066FDDC + obj :0x0066FDCC complexe(double=0,double=0) objet : 0x0066FDA4 ~complexe() objet : 0x0066FDA4 3.4+3i complexe(double=0,double=0) objet : 0x0066FD94 complexe::operator+() obj :0x0066FDDC + obj :0x0066FD94 complexe(double=0,double=0) objet : 0x0066FD84 ~complexe() objet : 0x0066FD84 ~complexe() objet : 0x0066FD94 5.9 complexe::operator double() obj :0x0066FDDC complexe(double=0,double=0) objet : 0x0066FD74 ~complexe() objet : 0x0066FD74 9.4 complexe::operator double() obj :0x0066FDDC 9.4 ~complexe() objet : 0x0066FDCC ~complexe() objet : 0x0066FDDC 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23

// c1=c1+3.5; }

// << error c2666 : 2 overloads have similar conversions

- 104 -

Les lignes de la sortie cran ont t numrotes : il y a 23 affichages en tout. 1) construction de c1 (objet dadresse 0x0066FDDC) 2) construction de c2 (objet dadresse 0x0066FDCC)
c1=2.4 ; //c1.operator=(complexe(2.4));

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

4) Cet objet est dtruit aprs lexcution de loprateur = 5) Affichage du contenu de c1 grce la surdfinition de loprateur <<
c2=c1+c2 ; //c2.operator=(c1.operator+(c2));

6) Loprateur + de la classe complexe est appel. 7) Un objet temporaire dadresse 0x0066FDA4 est construit pour retourner le rsultat de loprateur car cest un retour par valeur. Cest dailleurs cet objet temporaire qui est utilis comme argument de loprateur =. 8) Destruction de lobjet retourn par loprateur + 9) Affichage de c2
c1=c1+(complexe)3.5 ; //c1.operator=(c1.operator+(complexe(3.5)));

10) On utilise ici la syntaxe dune conversion de type explicite. La valeur 3.5 doit tre convertie en un objet complexe. Il y a donc construction dun objet de la classe complexe dadresse 0x0066FD94. 11) Appel de loprateur + de la classe complexe. On remarque que largument de loprateur est bien lobjet dadresse 0x0066FD94 12) Loprateur + retourne un objet par valeur. Il y a construction dun objet dadresse 0x0066FD84. 13) Aprs laffectation lobjet c1, cet objet temporaire est dtruit. 14) Lobjet de la classe complexe construit pour convertir 3.5 en complexe est dtruit. 15) Affichage de c1
c1=(double)c1+3.5 ; //c1.operator=(complexe((double)c1+3.5));

16) L encore, il ya une conversion explicite. On souhaite convertir c1 en type double, ce qui fait appel loprateur double de la classe complexe sur lobjet c1 (dont ladresse est 0x0066FDDC) Note : par consquent, cest loprateur + correspondant au type double qui est utilis ici, et non celui du type complexe. 17) Puisque (double)c1+3.5 est de type double, il doit y a voir une conversion implicite double-> complexe avant laffectation. Il y a donc construction dun objet temporaire dadresse 0x0066FD74. 18) Cet objet est pass loprateur = puis dtruit. 19) Affichage de c1
d=c1; // d =(double) c1;

20) Il y a conversion implicite du type complexe vers le type double. Cette conversion implicite utilise loprateur double de la classe complexe sur lobjet c1. 21) Affichage de c1 22) Destruction de c2 (car fin de main()) 23) Destruction de c1 (car fin de main())

- 105 -

7.5

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 !

- 106 -

8
8.1

UTILISATION DE CLASSES EXISTANTES PAR COMPOSITION


Introduction

On prsente dans ce chapitre une premire manire de rutiliser des classes existantes pour concevoir de nouvelles classes. La composition : on parle de composition lorsque les donnes membres dun objet sont elles-mmes des instances dune classe. Par exemple dans cette bauche de la classe cercle, le centre du cercle est un objet de la classe point.
class cercle { private: point centre; int rayon ; public: // };

8.2

Ecriture dune nouvelle classe personne

Crer une classe au sein de laquelle les champs sont eux-mmes des objets ne pose pas de problme majeur. La seule nouveaut concerne la liste dinitialisation pour les constructeurs de la classe compose. Cette prsentation sappuie simplement sur une rcriture de la classe personne dcrite dans la premire partie de ce document. Dans le chapitre 4, nous avions conu une classe personne de la manire suivante
class personne { private: char * ptrNom ; int age ; public : // };

Aprs avoir dvelopp une classe de chanes de caractres au chapitre 5, il parat plus naturel de concevoir la classe personne de la manire suivante :
class personne { private: chaine _nom ; chaine _prenom ; int _age ; public: // };

Chaque objet de la classe personne sera compos de deux objets de type chaine pour les noms et prnoms et dun champ _age de type entier. Note : on a galement ajout un champ _prenom qui ne figurait pas dans la version du chapitre 4.

- 107 -

8.2.1

Dfinition de la classe personne

// personne.h #ifndef __PERSONNE__ #define __PERSONNE__ #include<iostream.h> #include "chaine.h" class personne { chaine _nom; chaine _prenom; int _age; public: personne(); personne(const char *,const char *,int); personne(const chaine &,const chaine &,int); personne(const personne &); ~personne(); personne & operator=(const personne &);

chaine getNom() const; chaine getPrenom() const; int getAge() const;

void setNom(const chaine &); void setPrenom(const chaine &); void setAge(int);

friend ostream & operator<<(ostream &,const personne &); }; #endif

- 108 -

#include "personne.h" personne::personne():_nom("neant"),_prenom("neant"),_age(0) { }

liste dinitialisation

personne::personne(const char * strN,const char * strP,int a):_nom(strN), _prenom(strP),_age(a) { } personne::personne(const chaine & N,const chaine & P,int A):_nom(N),_prenom(P),_age(A) { } personne::personne(const personne & p):_nom(p._nom),_prenom(p._prenom),_age(p._age) { } personne::~personne() { } personne & personne::operator=(const personne & p) { if(this!=&p) { _nom=p._nom; _prenom=p._prenom; _age=p._age; } return *this; } chaine personne::getNom() const { return _nom; } chaine personne::getPrenom() const { return _prenom; } int personne::getAge() const { return _age; } void personne::setNom(const chaine & N) { _nom=N; } void personne::setPrenom(const chaine & P) { _prenom=P; } void personne::setAge(int A) { _age=A; } ostream & operator<<(ostream & flot,const personne & p) { flot << p._prenom << ' '<< p._nom << " " << p._age << " ans"; return flot; }

- 109 -

8.2.2

Les constructeurs de la classe personne et la liste dinitialisation

La nouveaut se trouve au niveau de la syntaxe des constructeurs de cette classe. En effet, construire un objet de la classe personne requiert galement la construction des deux objets de type chaine qui le composent. Il faut donc spcifier quelque part comment ces objets membres doivent tre initialiss. Cest le rle de la liste dinitialisation. Liste dinitialisation : il sagit de la liste qui suit la liste des arguments du constructeur.
personne::personne():_nom("neant"),_prenom("neant"),_age(0) { }

On spcifie aprs les : comment les diffrents membres doivent tre initialiss. En ce qui concerne les objets membres, a dtermine quels constructeurs appeler. Dans lexemple prcdent, on indique que pour le constructeur sans argument de la classe personne, les objets membres _nom et _prenom seront construits en appelant le constructeur
chaine::chaine(const char *)

avec la chane "neant" comme argument. Le champ _age quant lui sera initialis avec la valeur 0. Pour le constructeur
personne::personne(const chaine & N,const chaine & P,int A):_nom(N),_prenom(P),_age(A) { }

on voit que, dans la liste dinitialisation, cest le constructeur-copie de la classe chaine qui est utilis pour initialiser les membres _nom et _prenom. Enfin, le constructeur suivant est tout simplement le constructeur copie de la classe personne :
personne::personne(const personne & p):_nom(p._nom),_prenom(p._prenom),_age(p._age) { }

Remarque : la liste dinitialisation ne sapplique quaux constructeurs dune classe et pas aux autres mthodes de la classe. Quelle diffrence y-a-t-il entre ces deux dfinitions du mme constructeur ? Version 1
personne::personne():_nom("neant"),_prenom("neant"),_age(0) { }

Version 2
personne::personne() { _nom="neant"; _prenom="neant"; _age=0; }

- 110 -

Bien que ces deux constructeurs conduisent au mme rsultat, ces deux versions ne ralisent pas le mme traitement aussi efficacement. Dans la premire version (la plus rapide), les objets membres _nom et _prenom sont directement construits avec les bonnes valeurs en utilisant directement le constructeur adquat. Dans la seconde version (la moins efficace), le constructeur ne spcifie aucune information pour linitialisation des objets membres. Les deux objets membres sont donc construits tout dabord avec le constructeur sans argument de la classe chaine. Lorsque la premire ligne du constructeur sexcute, les objets membres existent dj avec les valeurs prvues par le constructeur sans argument de la classe chaine. Les lignes suivantes utilisent donc loprateur = de la classe chaine.
_nom="neant"; _prenom="neant";

En synthse : version 1 : appel du constructeur chaine::chaine(const char *) pour les deux membres _nom et _prenom version 2 : appel du constructeur chaine::chaine() pour les deux membres _nom et _prenom puis appel de loprateur chaine & chaine::operator=(const chaine &)

8.2.3

Loprateur = de la classe personne

Cet oprateur daffectation a t explicitement dfini pour remplacer loprateur = par dfaut.
personne & personne::operator=(const personne & p) { if(this!=&p) Equivaut { _nom=p._nom; _nom.operator=(p._nom) _prenom=p._prenom; _age=p._age; } return *this; }

Il convient simplement de noter que cet oprateur utilise loprateur = de la classe chaine pour les deux membres _nom et _prenom.

8.2.4

Le constructeur-copie et loprateur = par dfaut.

Il ntait pas indispensable ici de donner une dfinition explicite de loprateur = pour la classe personne. Dans ce cas prcis, loprateur = par dfaut suffisait. En effet, le traitement par dfaut de loprateur = dune classe est de raliser une copie membre--membre. Or, en prsence dobjets membres, cette copie membre--membre utilise par dfaut loprateur = de la classe laquelle appartiennent ces objets membres. Ici, la copie des membres _nom et _prenom utiliserait donc par dfaut loprateur = de la classe chaine. Cett remarque vaut galement pour le constructeur-copie. Le constructeur-copie par dfaut dune classe, dans le cas o la classe dispose dobjets membres, utilise pour les membres de type classe le constructeur-copie de la classe. Ainsi, dans le cadre de cette classe personne, le constructeur-copie par dfaut donnerait un rsultat tout fait satisfaisant dans la mesure o il raliserait la construction des membres _nom et _prenom avec le constructeur-copie de la classe chaine.

- 111 -

Conseil : dans le doute, il est prfrable de redfinir ces deux mthodes explicitement plutt que de ne pas bien comprendre ce que fait le traitement par dfaut. 8.2.5 Loprateur << pour laffichage des objets de type personne
class personne { // friend ostream & operator<<(ostream &,const personne &); // }; ostream & operator<<(ostream & flot,const personne & p) { flot << p._prenom << ' '<< p._nom << " " << p._age << " ans"; return flot; }

Rappelons quil sagit ncessairement dun oprateur non membre de la classe personne. Il est alors plus pratique de dclarer cet oprateur ami de la classe personne pour quil ait accs aux champs privs sans aucune restriction.
flot << p._prenom << ' '<< p._nom << " " << p._age << " ans";

Il convient de remarquer que cet oprateur utilise la surcharge de loprateur << appliqu aux objets de type chaine. En effet, grce notre classe chaine, on peut crire
flot << p._nom;

sachant que flot est une rfrence lobjet cout.

- 112 -

9
9.1

UTILISATION DE CLASSES EXISTANTES PAR DERIVATION


Introduction

La drivation : la seconde faon de rutiliser une classe consiste la rutiliser en bloc en lui ajoutant des donnes/fonctions. On parle dhritage ou de drivation. La classe drive hrite des fonctionnalits de la classe de base. Exemple : dans le cadre de la reprsentation des pixels dun moniteur vido, on souhaite crer une classe de points avec un attribut supplmentaire : la couleur. On peut rutiliser la classe point comme classe de base et ajouter un champ de couleur. On dit que la classe pointC drive de la classe point.
class pointC : public point { private: int couleur; // ajout dun champ couleur public: // };

La classe pointC drive (ou hrite) de la classe point

9.2

classe point

Rappelons tout dabord quelle classe point lon utilise.


// point.h #ifndef __POINT__ #define __POINT__ #include<iostream.h> class point { private: float x,y; public: point(float=0,float=0); point operator+(const point &) const; friend ostream & operator<<(ostream & flot, const point & p); }; #endif // point.cpp #include"point.h" point::point(float abs,float ord) { x=abs; y=ord; } point point::operator+(const point & p) const { point local; local.x = x + p.x; local.y = y + p.y; return local; } ostream & operator<<(ostream & flot, const point & p) { flot << '(' << p.x << ','<< p.y <<')'; return flot; }

- 113 -

9.3

Drivation de la classe point en pointC Dans cette premire approche de la drivation , on essaie de rutiliser la classe point en ltat. Syntaxe : pour driver la classe point en classe pointC (point color) on crit simplement
class pointC : public point { private: int couleur; public: pointC(float =0,float=0,int=0); pointC(const point &, int=0); pointC(const pointC &); void colorie(int c); };

La classe pointC drive (ou hrite) de la classe point

Remarque : on se contente pour linstant de traiter le cas de la drivation publique (attribut public) qui est le mode de drivation le plus couramment utilis. Ds lors que lon dfinit la classe pointC comme drivant de la classe point, il faut avoir lesprit que tout objet de la classe pointC contiendra ncessairement les donnes et les mthodes de la classe point. Autrement dit, une partie dun objet de type pointC est hrit de la classe point. Schmatiquement, on peut voir les objets point et pointC comme ayant les structures suivantes :
pointC pc; point p; x y operator+() p partie hrite x y operator+()

partie incrmentale

couleur colorie(int)

pc

Un objet de la classe point contient deux champs de donnes privs pour les coordonnes ainsi que des mthodes. Puisque la classe pointC est dfinie par drivation (publique) de la classe point, tout objet de la classe pointC contiendra galement ces fonctionnalits. Cest ce qui est dcrit en pointill sur la partie droite du schma prcdent. Cest la partie hrite dun objet de type pointC. Tout ce que lon dfinit de nouveau dans la classe pointC vient sajouter cette base. La partie ajoute est appele partie incrmentale. 9.4 Accs aux champs de lobjet pre

Un objet de type pointC contient des champs x et y hrits de la classe point. Nanmoins, il nest pas possible pour lobjet fils daccder aux champs privs de lobjet pre. Dans la classe pointC, les mthodes nont pas accs directement aux champs privs x et y. Pourquoi ? Il suffirait de driver une classe pour contourner lencapsulation de donnes.

- 114 -

9.5

Dfinition de la classe pointC


// pointcol.h #ifndef __POINTCOL__ #define __POINTCOL__ #include<iostream.h> #include "point.h" class pointC:public point { private: int couleur; public: pointC(float =0,float=0,int=0); pointC(const point &, int=0); pointC(const pointC &); void colorie(int c); }; #endif

// pointcol.cpp #include "pointcol.h" pointC::pointC(float abs,float ord,int coul):point(abs,ord),couleur(coul) { }

liste dinitialisation
pointC::pointC(const point & p,int coul):point(p),couleur(coul) { } pointC::pointC(const pointC & pc):point(pc),couleur(pc.couleur) { } void pointC::colorie(int c) { couleur=c; }

9.6

Les constructeurs de la classe pointC

Le premier constructeur est dfini de la manire suivante :


pointC::pointC(float abs,float ord,int coul):point(abs,ord),couleur(coul) {

La partie hrite est construite en appelant le constructeur point::point(float,float)


}

La construction dun objet de type pointC requiert avant tout la construction de la partie hrite qui, elle, est de type point. Aussi, dans le constructeur de la classe pointC, on doit spcifier comment la partie hrite est construite. Ceci est fait dans la liste dinitialisation. On peut galement spcifier dans cette liste dinitialisation comment le champ couleur doit tre initialis. - 115 -

Le second constructeur est dfini de la manire suivante :


pointC::pointC(const point & p,int coul):point(p),couleur(coul) {

La partie hrite est construite en appelant le constructeur point::point(const point &)


}

La liste dinitialisation indique que la partie hrite sera construite en utilisant le constructeur-copie de la classe point. 9.7 Compatibilit drive -> base : le constructeur-copie de la classe pointC

Le constructeur-copie de la classe pointC est dfini comme suit :


pointC::pointC(const pointC & pc):point(pc),couleur(pc.couleur) { }

Il convient de relever une particularit : dans la liste dinitialisation, on spcifie que lobjet pc est pass au constructeur de la partie hrite. Or, aucun constructeur de la classe point nest prvu pour recevoir un argument de type pointC. Conversion classe drive -> classe de base Il y a en fait une rgle de conversion que le compilateur peut toujours mettre en uvre : la conversion type classe drive vers classe de base (ou fils vers pre). Ici, il est possible de convertir un objet de type pointC en un objet de type point. Quel est le sens de cette conversion de type ? Il sagit dune conversion dgradante. Dans notre exemple, on peut considrer quun point color est avant tout un point , si lon oublie la couleur (c--d la partie incrmentale).

conversion

pointC -> point

x y partie hrite operator+()

partie incrmentale

couleur colorie(int)

pc

De manire gnrale, un objet de la classe drive peut tre converti en un objet de la classe de base. Seule la partie hrite est prserve par cette conversion de type. La partie incrmentale est perdue lors de la conversion.

- 116 -

Revenons au constructeur de la classe pointC : lcriture suivante est donc quivalente celle donne dans la dfinition de la classe (avec une conversion explicite) conversion explicite pointC->point

pointC::pointC(const pointC & pc):point( (point)pc ),couleur(pc.couleur) {

utilise le constructeur-copie de la classe point, soit


} point::point(const point &)

9.8

Modification de la classe de base point et complment de la classe pointC

Modification de lattribut des donnes membres de la classe point Il peut tre gnant que les mthodes de la classe pointC naient pas accs aux coordonnes x et y de la partie hrite. Ceci est d lattribut private dans la classe point. Lattribut protected offre les services suivants : . un membre protg est inaccessible de lextrieur (En labsence de drivation, un membre protg a les mmes proprits quun membre priv). . par contre, en drivation publique, les membres protgs de la classe de base sont accessibles par les mthodes de la classe drive. Ici, il est prfrable de redfinir les champs x et y comme membres protgs de la classe point.

// point.h #ifndef __POINT__ #define __POINT__ #include<iostream.h> class point modification { protected: float x,y; public: point(float=0,float=0);

// point.cpp #include"point.h" #include <iostream.h> point::point(float abs,float ord) { x=abs; y=ord; } point point::operator+(const point & p) const { point local; local.x = x + p.x; local.y = y + p.y; return local; } ostream & operator<<(ostream & flot, const point & p) { flot << '(' << p.x << ','<< p.y <<')'; return flot; }

de lattribut

point operator+(const point &) const; friend ostream & operator<<(ostream &, const point & p); }; #endif

- 117 -

Oprateur + de la classe pointC On ajoute un oprateur + dans la classe drive pointC : cet oprateur doit raliser la somme des coordonnes et le maximum des couleurs. Puisque les donnes de la classe de base ont dsormais lattribut protected, elles deviennent visibles dans la classe drive, ce qui rend possible lcriture de cet oprateur. On dfinit galement un oprateur << pour afficher les points colors.
#ifndef __POINTCOL__ #define __POINTCOL__ #include "point.h" class pointC:public point { protected: int couleur; public: pointC(float =0,float=0,int=0); pointC(const point &, int=0); pointC(const pointC &); void colorie(int c); pointC operator+(const pointC &) const; friend ostream & operator<<(ostream &, const pointC &); }; #endif

#include "pointcol.h" pointC::pointC(float abs,float ord,int coul):point(abs,ord),couleur(coul) { } pointC::pointC(const point & p,int coul):point(p),couleur(coul) { } pointC::pointC(const pointC & pc):point(pc),couleur(pc.couleur) { } void pointC::colorie(int c) { couleur=c; } pointC pointC::operator +(const pointC & pc) const { pointC local; local.x = x + pc.x; local.y = y + pc.y; local.colorie(pc.couleur>couleur ? pc.couleur : couleur); return local; } ostream & operator<<(ostream & flot, const pointC & pc) { flot << (point)pc << '['<<pc.couleur<<']'; return flot; }

- 118 -

La dfinition de loprateur + ne prsente aucune originalit hormis lutilisation de loprateur ternaire


op1 ? op2 : op3

pour raliser le maximum de deux couleurs. Cet oprateur retourne op2 si op1 vaut true ou retourne op3 sinon.
pointC pointC::operator +(const pointC & pc) const { pointC local; local.x = x + pc.x; local.y = y + pc.y; local.colorie(pc.couleur>couleur ? pc.couleur : couleur); return local; }

On peut galement noter quau sein de la dfinition de loprateur


ostream & operator<<(ostream & flot, const pointC & pc)

il y a une conversion de type explicite du type pointC vers le type point.


ostream & operator<<(ostream & flot, const pointC & pc) { flot << (point)pc << '['<<pc.couleur<<']'; return flot; } conversion de type pointC->point

Ceci permet de rutiliser loprateur << qui a dj fait lobjet dune surcharge pour les objets de type point. Autrement dit, il est de cette faon possible dafficher la partie hrite du point color, cest--dire ses coordonnes, en utilisant loprateur << dj surcharg pour la classe point. Il ne reste plus ensuite qu afficher la couleur. 9.9 Ordre dappel des constructeurs de la classe de base et de la classe drive

Dans la contruction dun objet de type pointC, il y a galement la construction dun objet de type point. Nous redfinissons deux classes point et pointC (simplifies) pour observer lordre dappel des constructeurs.
class point { protected: float x,y; public: point(float abs=0,float ord=0) { cout << "point::point(float,float) obj: " << this << endl; x=abs; y=ord; } ~point() { cout << "point::~point() obj:" << this << endl; } };

- 119 -

class pointC:public point { protected: int couleur; public: pointC(float abs,float ord,int coul):point(abs,ord),couleur(coul) { cout<<"pointC::pointC(float,float,int) obj: "<< this << endl; } pointC(const point & p,int coul):point(p),couleur(coul) { cout<<"pointC::pointC(const point &,int) obj: "<< this << endl; } pointC(const pointC & pc):point(pc),couleur(pc.couleur) { cout<<"pointC::pointC(const pointC &) obj: "<< this << endl; } ~pointC() { cout << "pointC::~pointC() obj:" << this << endl; } void colorie(int c) { couleur=c;} };

void main() { pointC p1(10,20,15); pointC p2(p1);

Rsultat
point::point(float,float) obj :0x0066FDE0 pointC::pointC(float,float,int) obj :0x0066FDE0 pointC::pointC(const pointC &) obj :0x0066FDD4 pointC::~pointC() obj :0x0066FDD4 point::~point() obj :0x0066FDD4 pointC::~pointC() obj :0x0066FDE0 point::~point() obj :0x0066FDE0

Que remarquer ? Lors de la construction dun objet de la classe drive il y a chronologiquement . appel du constructeur de la classe de base . appel du constructeur de la classe drive Inversement, lors de la destruction dun objet de la classe drive il y a chronologiquement . appel du destructeur de la classe drive . appel du destructeur de la classe de base Remarque Pour lobjet p2, on ne voit pas dappel au constructeur de la classe point. Pourquoi ? En fait, le constructeur-copie de la classe pointC utilise le constructeur-copie de la classe point. Or, ce dernier nest pas explicitement dfini. Il y a donc utilisation du constructeur-copie par dfaut de la classe point qui, lui, ne produit pas de sortie cran. - 120 -

9.10

Que retenir sur la drivation publique ?

Considrons une classe BASE et une classe DERIVEE drivant publiquement de la classe BASE.
class DERIVEE : public BASE { };

1) Conversion classe drive vers classe de base La conversion du type DERIVEE vers le type BASE est toujours possible, quil sagisse dune conversion explicite avec la syntaxe (BASE) ou implicite. Ex :
BASE b; DERIVEE d; b=d; b=(BASE)d; // conversion implicite // conversion explicite

2) Conversion pointeur sur classe drive vers pointeur sur classe de base Dans le mme esprit, une conversion dun pointeur sur objet de type DERIVEE vers pointeur sur objet de type BASE est toujours possible. Ex :
BASE * ptrB; DERIVEE *ptrD; DERIVEE D; ptrD = &D; ptrB = ptrD; ptrB = &D; // objet de la classe DERIVEE // aucune conversion de type // conversion implicite DERIVEE * vers BASE * // conversion implicite DERIVEE * vers BASE *

ptrB = (BASE*) ptrD; // conversion explicite par la syntaxe (BASE*)

Si lon revient la classe pointC qui drive de la classe point, on peut donner lexemple suivant. Ex :
pointC pc(10,25,12); point * ptrP ; ptrP=&pc; cout << (*ptrP); // objet pc coordonnes (10,25) couleur [12] // pointeur ptrP de type point * // conversion implicite pointC* -> point* // (*ptrP) est un objet de type point // laffichage est donc (10,25)

3) Surdfinition possible, dans la classe drive, des mthodes de la classe de base Dans le cas de la classe pointC, on surcharge une mthode operator+() dj prsente dans la classe de base. En ralit, les enttes complets de ces deux mthodes sont diffrents.

- 121 -

Pour loprateur + de la classe point, lentte est


point point::operator+(const point &) const

Pour loprateur + de la classe pointC, lentte est


pointC pointC::operator+(const pointC &) const

Remarque : on se rappelle dailleurs que la signature dune mthode est constitue, non seulement de lidentificateur de la mthode, mais aussi de la liste des types des arguments et de la classe propritaire. 4) Constructeur-copie par dfaut de la classe drive Comme dans toute classe, une classe cre par drivation dune classe de base possde un constructeur-copie par dfaut qui ralise une copie membre--membre. En pratique, la partie hrite de la classe est considre comme un membre part entire. Plus prcisment, le constructeur-copie par dfaut de la classe drive fait appel au constructeur-copie de la classe de base pour la construction de la partie hrite.
class point { protected: float x,y; public: point(float abs=0, float ord=0):x(abs),y(ord) { } point(const point & p) { cout << "point::point(const point &)"; cout << " obj: " ; cout << this << endl; x=p.x; y=p.y; } }; class pointC:public point { protected: int couleur; public: pointC(float abs, float ord, int coul):point(abs,ord),couleur(coul) { } void colorie(int c) { couleur=c;} };

Sur lexemple suivant, on voit effectivement que le constructeur-copie de la classe point est appel lors de la construction de lobjet p2 par le constructeur-copie par dfaut de la classe pointC.
void main() { pointC p1(10,25,12); pointC p2(p1); }

Rsultat
point::point(const point & ) obj 0x0066FDE0

Conclusion : par consquent, si la partie incrmentale de la classe drive ne possde pas de donnes en profondeur, il nest pas indispensable de dfinir explicitement le constructeur-copie de la classe drive. Le traitement par dfaut convient. Dans le doute, ne pas hsiter redfinir le constructeur-copie de la classe drive.

- 122 -

5) Oprateur = par dfaut de la classe drive De mme que dans toute classe, une classe cre par drivation possde un oprateur = par dfaut qui ralise une affectation membre--membre. Dans le cadre de la drivation, la partie hrite constitue un membre part entire. Loprateur = par dfaut de la classe drive utilise loprateur = de la classe de base pour raliser laffectation. Cest ce que lon voit sur lexemple suivant.
class point { protected: float x,y; public: point(floatabs=0,float ord=0):x(abs),y(ord) { } point & operator=(const point & p) { cout << "point::op=()"<< endl; if(this!=&p) { x=p.x; y=p.y; } return *this; } class pointC:public point { protected: int couleur; public: pointC(float abs=0,float ord=0, int coul=0):point(abs,ord),couleur(coul) { } friend ostream & operator<<(ostream &, const pointC &); }; ostream & operator<<(ostream & flot, const pointC & p) { flot << '(' << p.x; flot << ',' << p.y; flot << ")["<<p.couleur<<']'; return flot; }

};

void main() { pointC p1(10,20,56),p2; p2=p1; cout << p2; }

Rsultat
point : :op=() (10,20)[56]

9.11

Formes canoniques

On a vu que certains traitements sont faits par dfaut. Il est difficile (mis part lusage) de mmoriser quels mcanismes sont mis en uvre dans certaines situations. Il est donc plus sage de dfinir systmatiquement quelques mthodes dusage trs frquent. On dit quune classe est sous forme canonique si elle contient au minimum un constructeur sans argument, un constructeur-copie explicitement dfini (en remplacement du constructeur-copie par dfaut), le destructeur et un oprateur = dfini (en remplacement de loprateur = par dfaut).

Pourquoi est-il recommand de dfinir systmatiquement ces 4 mthodes ?

- 123 -

Le constructeur sans argument : il est indispensable la cration dun tableau dobjets allou dynamiquement. Par exemple, dans lexpression
ptr = new BASE[10];

il est impossible de passer une information au constructeur des 10 objets de type BASE du tableau. Le constructeur sans argument est alors indispensable Le constructeur-copie : Le constructeur-copie par dfaut ne convient pas lorsquil y a des donnes en profondeur. Loprateur = : Idem. Loprateur = par dfaut ne convient pas lorsquil y a des donnes en profondeur. Le destructeur : Dans le cas de classes avec donnes en profondeur, cette mthode restitue la mmoire. Ce qui suit est un exemple type de classe de base sous forme canonique et de classe drive galement sous forme canonique. La classe de base contient des donnes en profondeur. On considre que CLS est une classe qui contient des donnes en profondeur.
class Base { protected: CLS * ptr; int taille; public: Base(int t=0); Base(const Base & B); ~Base(); Base & operator=(const Base & B); }; Base::Base(int t){ ptr=new CLS[taille=t]; } Base::Base(const Base &B){ ptr=new CLS[taille=B.taille]; for(int i=0;i<taille;i++) { ptr[i]=B.ptr[i]; } } Base::~Base(){ delete [] ptr; } Base & Base::operator=(const Base & B){ if(this!=&B) { delete [] ptr; ptr=new CLS[taille=B.taille]; for(int i=0;i<taille;i++) { ptr[i]=B.ptr[i]; } } return *this; class Derivee:public Base { protected: int ajout; public: Derivee(int t=0,int aj=0); Derivee(const Derivee & D); ~Derivee(); Derivee& operator=(const Derivee &); }; Derivee::Derivee(int t,int aj):Base(t){ ajout=aj; } Derivee::Derivee(const Derivee & D):Base(D){ // initialisation de la partie // incrmentale ajout = D.ajout; } Derivee::~Derivee(){ // destruction partie incrmentale // Note :la partie hrite est dtruite // par ~BASE() } Derivee& Derivee::operator=(const Derivee& D) { if(this!=&D) { // affectation partie hrite Base * p1 = this; const Base * p2 = & D; (*p1)=(*p2); // affectation partie incrmentale ajout=D.ajout; } return *this;

- 124 -

A propos de la classe de base Puisque la classe CLS contient des donnes en profondeur, il est important de restituer la mmoire alloue par la classe Base avec la syntaxe delete [] ptr; A propos de la classe drive Il convient de remarquer la syntaxe du constructeur-copie
Derivee::Derivee(const Derivee & D):Base(D) { // etc. }

construction de la partie hrite en utilisant le constructeur-copie de la classe de base

La construction de la partie hrite est faite en appelant le constructeur-copie de la classe de base. On utilise implicitement la conversion de type Derivee-> Base. Il convient galement de noter, dans loprateur =, comment est effectue la copie de la partie hrite. On utilise ici la conversion de type Derivee * -> Base *.
Derivee& Derivee::operator=(const Derivee& D) { if(this!=&D) { Base * p1 = this; const Base * p2 = & D; (*p1)=(*p2); // utilise loprateur = de la classe Base // etc. } return *this; }

Il ne reste plus ensuite qu copier la partie incrmentale. 9.12 Accs aux donnes et aux mthodes dans le cadre de lhritage

Il sagit ici de donner des prcisions sur laccs aux donnes et fonctions membres de la classe de base et de la classe drive dans le cadre dune drivation publique.
class Base { protected: int dataBase; public: Base() { dataBase=8; } void show() { cout << "Base::show() :" << dataBase; cout << " obj :" << this << endl; } void showB() { cout << "Base::showB() :" << dataBase; cout <<" obj :" << this <<endl; } }; class Derivee:public Base { protected: float dataDerivee; public: Derivee() { dataDerivee=12; } void show() { cout << "Derivee::show() :" << dataBase; cout << " "<< dataDerivee <<" obj :"; cout << this <<endl; } void showD() { cout << "Derivee::showD() :" <<dataBase<< " "; cout << dataDerivee<<" obj :" << this <<endl; } };

- 125 -

On sappuie une fois de plus sur un exemple. On dfinit une classe appele Base et une classe drive de cette classe. Chacune de ces classes possde des fonctions membres daffichage des donnes membres.
void main() { Base B; Derivee D; B.showB(); // mthode de Base B.show(); // mthode de Base

Rsultats
Base::showB() :8 obj :0x0066FDF4 Base::show() :8 obj :0x0066FDF4 Derivee::showD() :8 12 obj :0x0066FDEC Base::showB() :8 obj :0x0066FDEC Derivee::show() :8 12 obj :0x0066FDEC Base::show() :8 obj :0x0066FDEC

D.showD(); // mthode de Derivee D.showB(); // mthode de Base D.show(); // mthode de Derivee D.Base::show(); // mthode de Base }

Remarque : On peut placer dans la classe drive une mthode ayant mme identificateur quune mthode de la classe de base. On voit quil y a alors un phnomne de masquage. Par exemple, lorsque lon appelle la mthode show() sur un objet de la classe Derivee, la mthode show() de la classe de base nest plus directement visible. Pour accder la mthode show() de la classe de base sur un objet de la classe drive, on doit ajouter le nom de la classe propritaire au moment de lappel, soit :
D.Base::show();

On a donc quivalence entre


B.show(); D.show(); et et B.Base::show(); D.Derivee::show();

9.13

Les diffrentes formes de drivation

On prsente ici les diffrentes formes de drivation en C++. Lexpos est ici fait sous forme de tableaux de synthse des droits daccs selon les diffrentes formes de drivation. Rappel : les attributs possibles des membres (donne ou fonction) dune classe : . membre public : visible par les membres de la classe et par lutilisateur de la classe ( lextrieur ) . membre priv : visible par les fonctions membres et amies de la classe mais pas par un utilisateur de la classe ( lextrieur ) ni par les fonctions membres dune classe drive. . membre protg : visible par les fonctions membres et amies de la classe mais pas par lutilisateur de la classe ( lextrieur ) . A la diffrence des membres privs, les membres protgs sont visibles par les fonctions membres et amies des classes drives.

- 126 -

Trois formes de drivation sont possibles : la drivation publique, prive ou protge. La drivation publique
class Derivee : public Base { };

Statut dans la classe de Accs aux fonctions base membre et amies de la classe drive Public Oui Protg Oui Priv Non La drivation prive

Accs un utilisateur de la Nouveau statut dans la classe drive classe drive, en cas de nouvelle drivation Oui Public Non Protg Non Priv

class Derivee : private Base { };

Dans ce mode de drivation, les membres publics de la classe de base ne sont plus visibles par un utilisateur de la classe drive. La drivation protge ( partir de la version 3 du C++)
class Derivee : protected Base { };

Dans ce mode de drivation, les membres publics de la classe de base seront considrs comme protgs lors des drivations ultrieures. Synthse Accs FMA : accs aux fonctions membres et amies de la classe Nouveau statut : statut quaura ce membre dans une ventuelle classe drive 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

Classe de base Statut Accs initial FMA Public Oui Protg Oui Priv Oui 9.14

Accs utilisateur Oui Non Non

Le typage statique (ou ligature statique)

Reprenons lexemple de drivation suivant :

class Base { protected: int dataBase; public: Base(); void show(); void showB(); };

class Derivee:public Base { protected: float dataDerivee; public: Derivee(); void show(); void showD(); };

- 127 -

Nous savons quil y a les compatibilits suivantes : Derivee->Base et Derivee*->Base* On peut donc crire :
void main() { Derivee D; Base * ptrB; Derivee * ptrD=&D; ptrB = ptrD; // conversion Derivee*->Base * ptrB->show(); } // appel de la fonction Base::show()

Lappel ptrB->show() correspond lappel de la mthode show() de la classe de base. Or, ptrB contient pourtant ladresse dun objet de type Derivee. Par dfaut, le langage C++ ralise une ligature statique. Cest--dire que le compilateur se fie au type du pointeur (ici ptrB est de type pointeur sur Base) pour dterminer la mthode appeler. On formule la mme remarque lorsque lon manipule des rfrences :
void main() { Derivee D; Base & refB=D; refB.show(); // appel de la fonction Base::show() }

Le typage statique pose un problme puisque, mme si lon possde ladresse dun objet de type Derivee dans le pointeur ptrB, on ne peut atteindre que les mthodes de la classe de base (mthodes hrites) via ce pointeur. Le typage dynamique offre une solution ce problme. 9.15 Fonctions virtuelles : typage dynamique

Lorsque lon dclare des mthodes avec lattribut virtual, on indique au compilateur quune ligature dynamique doit tre mise en uvre pour ces fonctions. Par exemple, on cre une ligature dynamique de la manire suivante pour la mthode show() :
class Base { protected: int dataBase; public: Base(); virtual void show(); void showB(); }; class Derivee:public Base { protected: float dataDerivee; public: Derivee(); void show(); void showD(); };

- 128 -

void main() { Base B; Derivee D; Base * ptrB=&B; ptrB->show(); ptrB=&D; ptrB->show(); }

Rsultats
Base::show() :8 obj :0x0066FDF0 Derivee::show() :8 12 obj :0x0066FDE4

Quelle diffrence observe-t-on ? Lors du premier appel


Base * ptrB=&B; ptrB->show();

le pointeur ptrB contient ladresse dun objet de type Base. Cest donc la mthode show() de la classe de base qui est appele. Lors du second appel
ptrB=&D; ptrB->show();

le pointeur ptrB contient ladresse dun objet de type Derivee. Cest donc la mthode show() de la classe drive qui est appele. Dans le cadre du typage dynamique, obtenu par la dfinition de mthodes virtuelles, le choix de la mthode appele est effectu au moment de lexcution et non pas pendant la phase de compilation. 9.16 Application du typage dynamique

Nous dfinissons une hirarchie de classes. Les classes point, cercle et rectangle drivent toutes dune mme classe de base appele forme. La dfinition de la classe forme est la suivante :
// classe abstraite class forme { public: virtual void show()=0; // fonction virtuelle pure virtual ~forme(){}; }; // destructeur virtuel

Une fonction membre virtuelle dont la dclaration est suivie de =0 est appele fonction virtuelle pure. La fonction show() de cette classe est une fonction virtuelle pure. - 129 -

Une classe contenant une fonction virtuelle pure est appele classe abstraite. Quest-ce quune classe abstraite ? Une classe abstraite ne peut pas crer dobjets. Elle sert uniquement de modle pour la drivation. En effet, lorsquune classe de base contient une fonction virtuelle pure, celle-ci doit ncessairement tre dfinie dans les classes drives. Cette dclaration impose donc une contrainte aux classes drives. Notons galement quune fonction virtuelle pure na pas de dfinition dans la classe abstraite (pas de code). Dans notre exemple, on dfinit une classe abstraite appele forme servant de modle la drivation de plusieurs classes reprsentant des formes gomtriques en vue dtre affiches lcran. Puisque chaque forme devra safficher lcran, la fonction daffichage est une fonction virtuelle pure dans la classe abstraite. Cette fonction doit donc tre explicitement dfinie dans les classes drives. Les dfinitions des autres classes sont les suivantes
class point:public forme { protected: int x; int y; public: point(int abs=0,int ord=0):x(abs),y(ord) { } void show() { cout<< "point " <<'('<<x<<','<<y<<')'; } };

class cercle:public forme { protected: point centre; int rayon; public: cercle(point c=point(10,10),int r=5):centre(c),rayon(r) { } void show() { cout << "cercle "<<'['; centre.show(); cout <<rayon<<']'; } };

- 130 -

class rectangle:public forme { protected: point A; point B; public: rectangle(point a=point(20,20),point b=point(40,40)):A(a),B(b) { } void show() { cout << "rectangle " << '['; A.show(); B.show(); cout << ']'; } };

Le programme principal utilisant ces diffrentes classes est le suivant


void main() { forme * tabForme[5]; tabForme[0]=new tabForme[1]=new tabForme[2]=new tabForme[3]=new tabForme[4]=new rectangle; point(30,45); cercle; cercle(point(30,35),30); cercle;

rsultats
rectangle [point (20,20)point (40,40)] point (30,45) cercle [point (10,10)5] cercle [point (30,35)30] cercle [point (10,10)5]

for(int i=0;i<5;i++) { tabForme[i]->show(); cout << endl; } for(i=0;i<5;i++) { delete tabForme[i]; } }

On dclare un tableau de pointeurs sur des objets de type forme en crivant


forme * tabForme[5];

Notons que la dclaration suivante naurait dailleurs pas t possible puisquil est impossible dinstancier des objets de type forme.
forme tabForme[5]; //<< impossible

- 131 -

Puisquil y a possibilit de convertir nimporte quel pointeur sur un objet de classe drive en un pointeur sur un objet de type classe de base, les diffrentes conversions point *->forme *, cercle * -> forme * et rectangle * -> forme * sont toutes possibles. Les critures suivantes sont donc lgales.
tabForme[0]=new tabForme[1]=new tabForme[2]=new tabForme[3]=new tabForme[4]=new rectangle; // rectangle * -> forme * point(30,45); // point * -> forme * cercle; // cercle * -> forme * cercle(point(30,35),30); cercle;

Grce la fonction virtuelle pure show() (cest--dire avant tout virtuelle) dans la classe de base, il y une ligature dynamique sur lappel de cette mthode. Par consquent, dans la boucle suivante, la mthode show() appele chaque itration dpend du type dobjet point par le pointeur tabForme[i]. Par consquent, il y a appel de la mthode show() approprie chaque objet point, do laffichage.
for(int i=0;i<5;i++) { tabForme[i]->show(); cout << endl; }

La dernire boucle permet de restituer la mmoire alloue par chacun des objets.
for(i=0;i<5;i++) { delete tabForme[i]; }

Destructeur virtuel On doit noter que le destructeur de la classe forme est galement une fonction virtuelle. Pourquoi ? Lorsque lon dtruit les objets allous dynamiquement, si le destructeur possde un lien statique, seul le destructeur de la classe forme est appel. Pour que le destructeur de la classe laquelle appartient rellement lobjet dynamique soit appel, il convient de dclarer virtuel le destructeur de la classe de base. Cest ce qui est fait ici. Cela est important si les classes drives possdent des donnes en profondeur dans leur partie incrmentale. Dans lexemple prcdent, il ny a aucune incidence. Intrt du typage dynamique Lavantage du typage dynamique est de pouvoir traiter de la mme manire (appel de mthodes ayant le mme nom) des objets de types diffrents. Dans lexemple prcdent, le tableau de pointeurs contient en fait les adresses dobjets de types diffrents (point, cercle et rectangle). Cette caractristique est appele polymorphisme.

- 132 -

10 FONCTIONS GENERIQUES ET CLASSES GENERIQUES. PATRONS DE FONCTIONS


ET PATRONS DE CLASSES

10.1

Introduction

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

Nous voulons dsormais, dans la mme application, permuter deux valeurs de type float. Grce la surdfinition de fonctions, on peut complter le programme de la manire suivante
#include <iostream.h> void permute(int & a,int & b); void permute(float & a,float & b); void main() { int x=2,y=3; float X=2.3,Y=3.6; permute(x,y); permute(X,Y); } void permute(int & a,int & b){ int c=a; a=b; b=c; } void permute(float & a,float & b){ float c=a; a=b; b=c; }

En quoi les deux fonctions diffrent-elles ? La seule diffrence entre ces deux fonctions concerne les types utiliss. En effet, lalgorithme (si lon peut parler dalgorithme) est rigoureusement identique. Il en serait de mme pour toute fonction de permutation de deux variables de mme type. Si lon pouvait paramtrer les types utiliss, on naurait quune seule dfinition donner. Cest ce que permet la notion de template (patron).

- 133 -

10.2

Patron de fonctions : un exemple

Le C++ permet la dfinition de patrons de fonctions. Un patron est un modle dans lequel un des paramtres reprsente un type, et non une variable. On parle galement de fonction gnrique. On souhaite par exemple raliser un patron de fonctions ralisant la somme de variables de mme type. Pour voir o apparat le paramtre de type, on peut commencer par crire deux fonctions ralisant la somme pour les types int et double. On obtient pour ces deux types :
int somme(int a,int b) { return a+b ; } double somme(double a, double b) { return a+b ; }

On voit alors clairement o apparat le paramtre de type. Le patron de fonctions scrit de la manire suivante :
template <class T> T somme(T a,T b) { return a+b; }

La syntaxe template <class T> indique que T est un paramtre de type (et non une variable). Un programme dutilisation de ce patron est le suivant :
#include <iostream.h> template <class T> T somme(T a,T b); void main() { int u=2,v=5; double X=2.4,Y=3.7; cout << somme(u,v)<<endl; cout << somme(X,Y)<<endl; } // patron de fonctions template <class T> T somme(T a,T b) { return a+b; }

Un patron de fonctions ne conduit pas directement du code. Par exemple, sil ny avait pas dappel de la fonction somme(), aucun code ne serait gnr (contrairement une fonction du C). 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.

- 134 -

10.3

Les limites de ce patron

Avec le patron dfini dans la section prcdente, la ligne suivante nest pas compile
int u=2,v=5; double X=2.4,Y=3.7; cout << somme(u,Y)<<endl; //<< impossible

En raison de la dfinition du patron, qui spcifie que les deux arguments doivent tre rigoureusement de mme type, il nest pas possible dinstancier une fonction avec deux arguments de types diffrents. Si lon souhaitait que lappel prcdent puisse tre pris en charge par le patron, il faudrait dfinir le patron avec un autre paramtre de type, par exemple de la manire suivante
template <class T,class U> T somme(T a,U b) { return a+b; }

Remarque : le type de la valeur retourne est alors systmatiquement le mme que celui du premier argument. Autrement dit, les deux appels suivants ne conduisent pas au mme rsultat :
int u=2; double X=2.4; cout << somme(u,X)<<endl; // retourne 4 cout << somme(X,u)<<endl; // retourne 4.4

Un patron de fonctions peut aussi sappliquer des objets. Nanmoins, dans lexemple de patron donn ici, il est ncessaire que tout le code du patron puisse sexcuter. En particulier, il est ncessaire ici que loprateur + soit surcharg pour le type T. Par exemple, si la classe point est dfinie et que loprateur + est dfini pour les objets de type point, on pourra crire
point p1(10,23),p2(25,40); cout << somme(p1,p2)<<endl;

10.4

Les paramtres expression

Dans un patron de fonctions, on peut aussi faire apparatre des arguments typs normalement. Ces paramtres sont parfois appels paramtres expression. Sur le patron de fonctions somme(), rien ne nous interdit de fixer le type du second argument :
template <class T,class U> T somme(T a, int b) { return a+b; }

10.5

Spcialisation dun patron de fonctions

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

- 135 -

char*.

Nous avons donc dfini une spcialisation pour le type char *. Cette spcialisation remplace le code que gnrerait le patron.
#include<iostream.h> #include<string.h> //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 }

10.6

Patron de classes

De mme que lon peut paramtrer certains types dans une fonction, on peut galement donner des paramtres de type dans la dfinition dun patron de classes. La prsentation va une fois de plus reposer sur un exemple. On propose un patron de classes de piles. Note : la syntaxe de dfinition dun patron de classes nest pas trs intuitive. Il est donc important davoir un exemple sous les yeux lorsque vous ferez vos premiers essais
#include<iostream.h> const defaultDim = 10;

patron de classes o T est un paramtre de type.

template <class T> class pile { protected : T* ptr; unsigned nbElem; unsigned dim; public : pile<T>(int taille = defaultDim); pile<T>(const pile<T> &); ~pile<T>(); pile<T> & operator=(const pile<T> &); pile<T> & operator<(const T &); pile<T> & operator>(T &); friend ostream & operator<<(ostream &, const pile<T> &); };

- 136 -

template <class T> pile<T>::pile(int taille) { ptr=new T[dim=taille]; nbElem=0; } template <class T> pile<T>::~pile() { delete [] ptr; } template <class T> pile<T>::pile(const pile<T> & P) { ptr=new T[dim=P.dim]; nbElem=P.nbElem; for(unsigned i=0;i<nbElem;i++) ptr[i]=P.ptr[i]; } template <class T> pile<T> & pile<T>::operator=(const pile<T> & P) { if(!(this==&P)) { delete [] ptr; ptr=new T[dim=P.dim]; nbElem=P.nbElem; for(unsigned i=0;i<nbElem;i++) ptr[i]=P.ptr[i]; } return *this; } template <class T> pile<T> & pile<T>::operator<(const T & Source) { if(nbElem<dim) ptr[nbElem++]=Source; return *this; } template <class T> pile<T> & pile<T>::operator>(T & Destination) { if(nbElem>0) Destination=ptr[--nbElem]; return *this; } template <class T> ostream & operator<<(ostream& flot,const pile<T>&P) { for(unsigned i=0; i<P.nbElem; i++) flot << P.ptr[i] << " "; return flot; } void main() { pile<int> p1(3); // p1 est bun objet de la classe pile<int> pile<double> p2(4); // p2 est un objet de la classe pile<double> p1<1<7<13; pile<pile<int> > p3(7); // p3 est une pile dlments de type pile dentiers. p3<p1<p1; // on peut donc empiler des piles dentiers sur p3. cout << p3; }

- 137 -

Au moment de le dfinition du patron de classes, template <class T> indique que T est un paramtre de type . Une classe obtenue partir du patron pour le type X est dsigne par la syntaxe pile<X>. Par exemple, une classe de piles dentiers sera dsigne par lexpression pile<int>. Dans le programme dutilisation du patron, lobjet p1 est un objet de la classe pile<int> et p2 un objet de la classe pile<double>. Observons la dfinition de loprateur = de ce patron. Loprateur retourne une rfrence un objet de la classe pile<T>
template <class T> pile<T> & pile<T>::operator=(const pile<T> & P) { }

Largument est une rfrence constante un objet de la classe


pile<T>

Rappelle que T est un paramtre de type

La classe propritaire est pile<T>

Remarque : on peut galement spcialiser un patron ou faire apparatre des types par dfaut.

10.7

Utilisation de patrons dans la cadre de lhritage

Il est facile de combiner la notion de patron de classes avec celle dhritage. Classe ordinaire drive dune classe patron (une instance particulire dun patron) La bibliothque STL5 (standard template library) fournit diffrents patrons de classes, dont un patron de listes doublement chanes. Une classe list<X> est une classe de listes chanes pouvant stocker des lments de type X. Lexemple suivant montre comment lon peut rutiliser une liste dentiers pour faire une classe de piles dentiers. En effet, si lon se contente dajouter des lments et de les retirer la mme extrmit de la liste, une liste chane a un comportement de pile (gestion LIFO).

#include<list> // dfinition du patron de listes #include<iostream.h> using namespace std; // pour lutilisation de la bibliothque STL sous VisualC++ 6

class pile:private list<int> { public: void push(int); void pop(int &); void affiche(); };

La classe pile drive dune instance du patron list<T>

Il sagit dune bibliothque de patrons de classes dveloppe chez Hewlett Packard et appartenant au domaine public. Cette bibliothque est disponible notamment sur Visual C++ 6. - 138 -

void pile::push(int val) { list<int>::push_front(val); // ajoute un lment en tte de liste } void pile::pop(int & val) { if(!empty()) { val = *begin(); // val ppv le premier lment de la liste pop_front(); // supprime le premier lment } } void pile::affiche() { list<int>::iterator i; cout << endl; for(i=begin();i!=end();i++) cout<<*i; // affichage des lments de la liste }

Remarque : le code des fonctions dfinies prcdemment utilise des caractristiques des classes de la bibliothque STL, notamment la notion ditrateur. Sans rentrer dans les dtails, un itrateur gnralise la notion de pointeur sur un lment. Par exemple, un itrateur sur une liste correspond un pointeur sur un lment de la liste. Si i est un itrateur sur un lment de la liste, *i reprsente llment point et i++ permet de passer llment suivant de la liste.
void main() { pile p; p.push(1); p.push(3); p.affiche(); int temp; p.pop(temp); p.pop(temp); p.affiche(); }

Cration dun nouveau patron de classes par drivation dun patron existant Dans le mme esprit que prcdemment, on peut dfinir un patron de piles pour diffrents types partir dun patron de listes chanes. La dfinition devient la suivante :
#include<list> #include<iostream.h> using namespace std; template <class T> class pile:private list<T> { public: void push(T); void pop(T &); void affiche(); }; pile<T>

est un patron cr par drivation du patron list<T>

- 139 -

template <class T> void pile<T>::push(T val) { list<T>::push_front(val); } template <class T> void pile<T>::pop(T & val) { if(!empty()) { val = *begin(); pop_front(); } } template <class T> void pile<T>::affiche() { list<int>::iterator i; cout << endl; for(i=begin();i!=end();i++) cout<<*i; } void main() { pile<int> p; p.push(1); p.push(3); p.affiche(); int temp; p.pop(temp); p.pop(temp); p.affiche(); }

10.8

Conclusion

La bibliothque STL constitue une motivation particulire pour lutilisation de patrons. En effet, cette bibliothque met disposition divers patrons de conteneurs (tableaux, listes, files, piles,) qui peuvent faciliter le dveloppement de certaines applications. Il est noter que dautres bibliothques de patrons sont parfois disponibles avec certains compilateurs. Par exemple, une biliothque de patrons de conteneurs (listes, tableaux, tableaux associatifs) est fournie avec la bibliothque MFC6 (Microsoft Foundation Classes).

Les MFC permettent de dvelopper des applications Windows. - 140 -

11 ANNEXE
11.1 Travailler sous forme de projet en C++

Pour les applications ncessitant beaucoup de classes, il est souhaitable que celles-ci figurent dans des fichiers diffrents. On travaille alors de la manire suivante : - les dclarations des classes sont faites dans des fichiers dextension .h (fichiers dentte) - les dfinitions des fonctions membres sont dans des fichiers dextension .cpp - lutilisation de ces classes apparat finalement dans un fichier dextension .cpp contenant la fonction void
main(void).

classA.h class A { public: A(); void };

classB.h class B { public: B(); int g(float ); };

f();

classA.cpp #include "classA.h" void A::A() { // } void A::f() { // }

utilisationAB.cpp #include "classA.h" #include "classB.h" void main(void) { A a; B b; b.g(3.5); }

classB.cpp #include "classB.h" void B::B() { // } int B::g(float x) { // }

On suppose ici que le projet contient classA.cpp, classB.cpp et utilisationAB.cpp. #include "classA.h" est une directive dinclusion de fichier. Elle permet dinclure tout le contenu du fichier classA.h (du rpertoire courant) lemplacement de la directive, et ce juste avant la compilation. Si lon compile par exemple uniquement le fichier classA.cpp, la dclaration de la classe A apparat ainsi avant la dfinition de ses fonctions membre. Les trois fichiers classA.cpp, classB.cpp et utilisationAB.cpp appartiennent au mme projet. Cest un peu comme si lon avait crit le contenu de ces trois fichiers dans un mme fichier source. Que remarque-t-on alors ? Il y a deux fois la directive dinclusion #include "classA.h" et deux fois #include "classB.h" ce qui revient dire que lon a dfini deux fois la classe A et deux fois la classe B. Ceci ne peut pas fonctionner. En fait, on ne devrait voir apparatre quune seule fois la dclaration de chacune des classes, mme si lon utilise plusieurs fois la directive #include "classA.h" dans lensemble du projet. 11.2 Les directives de compilation conditionnelle.

Il est possible dutiliser plusieurs fois une directive dinclusion dun fichier de dclaration de classe (.h) dans un projet sans quil y ait de problme si lon munit la dclaration de classe de directives de compilation conditionnelle. Dans lexemple suivant, tout ce quil y a entre la directive #ifndef MACHIN et #endif nest considr par le compilateur que si lidentificateur MACHIN nest pas dfini par une directive du type #define MACHIN.

- 141 -

#ifndef MACHIN //si MACHIN nest pas dfini, voil ce que lon compile //on ne compile donc cette partie que si MACHIN nest pas dfini ! #endif

Que se passe-t-il lors de la compilation dun fichier tel que le suivant ?


#ifndef MACHIN #define MACHIN //si MACHIN nest pas dfini, on dfinit MACHIN class A { public: A(); void f(); }; #endif #ifndef MACHIN #define MACHIN class A { public: A(); void f(); }; #endif // ici MACHIN est dfini, on ne compile donc pas la suite

Rponse : grce aux directives de compilation conditionnelle, la classe A nest dclare quune seule fois. Cest cette technique qui est utilise pour lcriture des fichiers de dclaration des classes. Pour que tout se passe bien, le fichier de dclaration dune classe est gnralement crit ainsi :
classA.h #ifndef __CLASS_A__ #define __CLASS_A__ class A { public: A(); void }; #endif classB.h #ifndef __CLASS_B__ #define __CLASS_B__ class B { public: B(); int }; #endif

f();

g(float );

Notons que le choix des noms __CLASS_A__ et __CLASS_B__ membres peut se faire dans dautres fichiers dextension .cpp.
classA.cpp #include "classA.h" A::A() { // } void A::f() { // }

est arbitraire. Ensuite, la dfinition des fonctions

classB.cpp #include "classB.h" B::B() { // } int B::g(float a) { // }

- 142 -

INDEX

#define,141 #endif,141 #ifndef,141 accesseur (mthode),57 affectation entre objets,58 amiti classes amies,97 fonctions amies,97 cast,102 changements de type,101 cin,18 classe,13, 41 abstraite,130 gnrique,133 patron de classes,133 compilation conditionnelle,141 composition,107 const,28, 29 constructeur,48 constructeur-copie,67 constructeur-copie par dfaut,68, 111, 122 conversion classe drive vers classe de base,116 cout,17 delete,27 delete[],78 drivation,113 prive,126 protge,126 publique,126 destructeur,49 donnes en profondeur,54 encapsulation,12 endl,19 factorisation de code,85 fonction gnrique,133 patron de fonctions,133 virtuelle,129 virtuelle pure,129 fonction membre,43 friend,97 hritage,13, 113 instance dune classe,13 left-operand,89 liste d'initialisation,107 mthodes,11, 43, 45 accesseur,57 mthodes constantes,56

modificateur,57 modificateur (mthode),58 mutateur,58 new,26 objet courant,43 objets dynamiques,50 objets temporaires,65 oprateurs,36 <<,91 d'indexation [ ],88 membres,86 non membres,86 oprateur =,111 oprateur = par dfaut,111, 123 oprateurs binaires,35 oprateurs unaires,35 operator int(),103 oprateurs oprateur =,62 paramtre dentre,21 paramtre dentre/sortie,21 paramtre de sortie,21 partie incrmentale,114 patron de classes,136 de fonctions,134 pointeur this,58 POO,10 porte des variables,19 private,45 protected,45, 117 public,45 rfrences,20 rfrences constantes,30 transmission d'arguments par rfrence,20 retour d'une fonction par rfrence,76 retour d'une fonction par valeur,74 signature,25 structure,41 surcharge de fonction,25 transmission d'arguments,20, 72, 74 passage par adresse,21 passage par rfrence,23 passage par rfrence constante,32 transtypage,102 typage dynamique,128 typage statique,128 valeurs par dfaut,25

- 143 -

Das könnte Ihnen auch gefallen