Support de cours Programmation Orientée Objet en Langage C++ Mohamed EL WAFIQ Version 2017 Avant-propos Ce cours a été développé et amélioré au fil des années afin de donner un support résumé et simplifié aux étudiants ayant un bon niveau en programmation structurée en langage de programmation C. Structure du cours Ce support de cours est structuré pédagogiquement selon une progression simpliste en expliquant les concepts, en donnant des exemples d’application et en proposant des exercices de renforcement dont les solutions sont présentées à titre concis. Compilation des programmes sources Tous les programmes sources de ce cours sont compilés sous Dev-C++, Version 4.9.9.2, en incluant les fichiers entête correspondants. Programmation Orientée Objet en Langage C++ 2 M.E Table des matières Chapitre 0 : Spécificités du Langage C++ ...........................................................................5 1. Commentaire en fin de ligne ........................................................................................5 2. Emplacement des déclarations .....................................................................................5 3. Notion de référence ......................................................................................................5 3.1. Transmission des arguments par valeur .................................................................5 3.2. Transmission des arguments par adresse ...............................................................6 3.3. Transmission des arguments par référence ............................................................6 4. Les arguments par défaut .............................................................................................6 5. Surdéfinition de fonction (overloading : surcharge) ......................................................8 6. Les opérateurs ‘new’ et ‘delete’ ...................................................................................9 6.1. L’opérateur ‘new’ .................................................................................................9 6.2. L’opérateur ‘delete’ ............................................................................................ 10 7. Utilisation des fonctions ‘en ligne’ (inline)................................................................. 10 Chapitre 1 : Notions de Classe et Objet ............................................................................. 11 1. Exemple du type classe .............................................................................................. 11 2. Affectation d’objets ................................................................................................... 12 3. Notion de constructeur et de destructeur..................................................................... 12 3.1. Introduction ........................................................................................................ 12 3.2. Construction et destruction des objets ................................................................. 13 3.3. Rôle de constructeur et de destructeur ................................................................. 14 4. Membres statiques ..................................................................................................... 15 Chapitre 2 : Propriétés des fonctions membres................................................................. 20 1. Surdéfinition des fonctions membres.......................................................................... 20 2. Arguments par défaut ................................................................................................. 21 3. Les fonctions membres en ligne ................................................................................. 21 4. Cas des objets transmis en argument d’une fonction membre ..................................... 22 5. Mode de transmission des objets, arguments d’une fonction....................................... 22 5.1. Transmission de l’adresse d’un objet................................................................... 22 5.2. Transmission par référence ................................................................................. 23 6. Autoréférence : le mot clé ‘this’ ................................................................................. 24 Chapitre 3 : Construction, destruction et initialisation des objets ................................... 25 1. Objets automatiques et statiques................................................................................. 25 1.1. Durée de vie d’allocation mémoire...................................................................... 25 1.2. Appel des constructeurs et des destructeurs ......................................................... 25 2. Les objets temporaires ............................................................................................... 25 3. Les objets dynamiques ............................................................................................... 26 4. Tableaux d’objets....................................................................................................... 26 5. Objets d’objets ........................................................................................................... 27 6. Initialisation d’un objet lors de sa déclaration............................................................. 28 6.1. Un premier exemple............................................................................................ 28 6.2. Constructeur par recopie ..................................................................................... 29 6.3. Exemple d’utilisation du constructeur par recopie ............................................... 29 Chapitre 4 : Les fonctions amies ........................................................................................ 32 1. Exemple de fonction indépendante amie d’une classe ................................................ 32 2. Les différentes situations d’amitié .............................................................................. 33 2.1. Fonction membre d’une classe, amie d’une autre classe ...................................... 33 2.2. Fonction amie de plusieurs classes ...................................................................... 33 2.3. Toutes les fonctions d’une classe sont amies d’une autre classe........................... 34 Programmation Orientée Objet en Langage C++ 3 M.E Chapitre 5 : Surdéfinition des opérateurs ......................................................................... 36 1. Le mécanisme de la surdéfinition des opérateurs ........................................................ 36 1.1. Surdéfinition d’opérateur avec une fonction amie................................................ 36 1.2. Surdéfinition d’opérateur avec une fonction membre .......................................... 37 2. Les possibilités et les limites de la surdéfinition des opérateurs en C++...................... 38 2.1. Il faut se limiter aux opérateurs existants............................................................. 38 2.2. Tableau d’opérateurs surdéfinissabes, classés par priorité décroissante ............... 38 2.3. Choix entre fonction membre et fonction amie .................................................... 38 3. Exemple de surdéfinition de l’opérateur ‘[ ]’ .............................................................. 39 4. Exemple de surdéfinition de l’opérateur ‘=’ ............................................................... 40 Chapitre 6 : La technique de l'héritage ............................................................................. 43 1. Mise en œuvre de l'héritage ........................................................................................ 43 1.1. Exemple simple sans constructeur ni destructeur................................................. 43 1.2. Commentaire ...................................................................................................... 43 2. Utilisation, dans une classe dérivée, des membres de la classe de base ....................... 44 3. Redéfinition des fonctions membres........................................................................... 45 4. Appel des constructeurs et des destructeurs ................................................................ 46 5. Contrôle des accès ..................................................................................................... 47 5.1. L’héritage privé .................................................................................................. 47 5.2. Les membres protégés d’une classe ..................................................................... 48 6. L'héritage en général .................................................................................................. 49 7. Conversion d'un objet dérivé dans un objet d'un type de base ..................................... 49 Chapitre 7 : L’héritage multiple ........................................................................................ 50 1. Mise en œuvre de l’héritage multiple ......................................................................... 50 2. Les classes virtuelles .................................................................................................. 51 3. Appel des constructeurs et des destructeurs dans le cas des classes virtuelles ............. 52 Chapitre 8 : Le polymorphisme ......................................................................................... 57 1. Redéfinition des fonctions.......................................................................................... 57 2. Fonction virtuelle pure et classe abstraite ................................................................... 60 2.1. Fonction virtuelle pure ........................................................................................ 60 2.2. Classes abstraites ................................................................................................ 60 Chapitre 9 : Gestion des flux .............................................................................................. 62 1. Généralités sur les flux............................................................................................... 62 2. Afficher sur l’écran avec ‘cout’ .................................................................................. 63 3. Saisir au clavier avec ‘cin’ ......................................................................................... 65 4. Redéfinir les opérateurs de flux .................................................................................. 67 5. Lire à partir d’un fichier ou écrire dans un fichier....................................................... 69 Chapitre 10 : Les templates................................................................................................ 72 1. Les fonctions templates .............................................................................................. 72 2. Les classes templates ................................................................................................. 73 Chapitre 11 : Gestion des exceptions ................................................................................. 76 1. Gestion des erreurs en utilisant les valeurs de retour des fonctions ............................. 76 2. Mise en œuvre des exceptions .................................................................................... 77 2.1. Définir une classe d’exception ............................................................................ 77 2.2. Lancer l’exception .............................................................................................. 78 2.3. Intercepter l’exception ........................................................................................ 79 3. Hiérarchie des classes d’exception ............................................................................. 84 Références bibliographiques .............................................................................................. 87 Programmation Orientée Objet en Langage C++ 4 M.E Chapitre 0 : Spécificités du Langage C++ C++ dispose d’un certain nombre de spécificités qui ne sont pas obligatoirement relatives à la programmation orientée objet. 1. Commentaire en fin de ligne Exemple //ceci est un commentaire. int a; // déclaration de a. 2. Emplacement des déclarations La déclaration des variables doit s’effectuer avant leur utilisation n’importe où dans un bloc ou dans une fonction. Exemple main() { int a=7, b; b=a; float x, y; x=2*a+y; } Exemple for(int i=0; i<7; i++) 3. Notion de référence 3.1. Transmission des arguments par valeur Exemple main() { void echange(int,int); int a=2, b=5; cout<<”Avant appel : ”<<a<<” - ”<<b<<” \n”; echange(a,b); cout<<”Après appel : ”<<a<<” - ”<<b<<” \n”; } //------------------------------------------------------void echange(int m,int n) { int z; z=m; m=n; n=z; } Programmation Orientée Objet en Langage C++ 5 M.E Exécution 3.2. Transmission des arguments par adresse main() { void echange(int *,int *); int a=2, b=5; cout<<”Avant appel : ″<<a<<″ - ″<<b<< ” \n ”; echange(&a,&b); cout<<”Après appel : ”<<a<<” - “<<b<< ” \n ”; } //-------------------------------------------------------void echange(int *x,int *y) { int z; z=*x; *x=*y; *y=z; } Exécution 3.3. Transmission des arguments par référence En C++, la transmission par référence est une forme simplifiée de la transmission par adresse. main() { void echange(int &,int &); int a=2, b=5; cout<<″Avant appel : ″<<a<<″ - ″<<b<<″\n ″; echange(a,b); cout<<″Apres appel : ″<<a<<″ - ″<<b<<″\n ″; } //---------------------------------------------------------void echange (int & x,int & y) { int z; z=x; x=y; y=z; } 4. Les arguments par défaut En C, il est indispensable que le nombre d’arguments passés correspond au nombre d’arguments déclarés. C++ peut ne pas respecter cette règle. Programmation Orientée Objet en Langage C++ 6 M.E Exemple 1 main() { int m=1, n=5; void f(int,int=7); f(3,5); f(4); } //----------------------------------------------void f(int a,int b) { cout<<″Valeur 1 : ″<<a<<″ - Valeur 2 : ″<<b<<″\n″; } Exécution NB : - La fonction f est déclarée avec un deuxième argument initialisé par défaut par la valeur 7. - Pendant l’appel de la fonction f, si le deuxième argument n’est pas précisé, il est alors égal à la valeur 7. - Un appel du genre f() sera rejeté par le compilateur. Exemple2 main() { void f(int=33,int=77); f(99,55); f(44); f(); } //----------------------------------void f(int a,int b) { cout<<″Valeur 1 : ″<<a<<″ - Valeur 2 : ″<<a<<″\n″; } Exécution Remarque Lorsqu’une déclaration prévoit des valeurs par défaut, les arguments concernés doivent obligatoirement être les derniers de la liste. Programmation Orientée Objet en Langage C++ 7 M.E Exemple La déclaration : int f (int=2, int, int=7) ; est interdite, car un appel du genre f(3, 4); peut être interprété comme : f(2, 3, 4); ou f(3, 4, 7); 5. Surdéfinition de fonction (overloading : surcharge) On parle de surdéfinition ou de surcharge lorsqu’un même symbole possède plusieurs significations différentes, le choix de l’une des significations se faisant en fonction du contexte. Pour pouvoir employer plusieurs fonctions du même nom, il faut un critère permettant de choisir la bonne fonction. En C++, ce choix est basé sur le type des arguments. Exemple void f(int x) { cout<<″fonction numéro 1 : ″<<x<<″\n″; } void f(double x) { cout<<″fonction numéro 2 : ″<<x<<″\n″; } main() { int a=2; double b=5.7; f(a); f(b); f(‘A’); } Exécution La valeur 65 représente le code ASCII de la lettre A. Remarque Le C++ a choisi donc la bonne fonction en fonction de ses arguments. Cas 1 void f(int); //f1 void f(double); //f2 char c; float b; - f(c); appellera la fonction f1 après conversion de la valeur de c en int. - f(b); appellera la fonction f2 après conversion de b en double. Cas 2 void f(char *); //f1 void f(void *); //f2 char *p1; double *p2; - f(p1); appellera la fonction f1. - f(p2); appellera la fonction f2 après conversion de p2 en void *. Programmation Orientée Objet en Langage C++ 8 M.E Cas 3 void f(int, double) ; // f1 void f(double, int) ; // f2 int a, b; double x; char c ; - f(a, x); appellera la fonction f1. - f(c, x); appellera la fonction f1 après conversion de c en int. - f(a, b); conduira à une erreur de compilation (convertir a en double ou b en double). Cas 4 void f(int a=0 ; double c=0) ; // f1 void f(double y=0 ; int b=0) ; //f2 int m; double z ; - f(m, z); appellera la fonction f1. f(z, m); appellera la fonction f2. f (m); appellera la fonction f1. f(z); appellera la fonction f2. f(); conduira à une erreur de compilation. 6. Les opérateurs new et delete En langage C, la gestion dynamique de la mémoire a été assurée par les fonctions malloc(…) et free(…). En C++, elle est assurée par les opérateurs new et delete. 6.1. L’opérateur new Exemple int *p; p=new int; ou int *p=new int; // Allocation dynamique d’un entier. int *p2=new int[5]; // Allocation dynamique de 5 entiers. char *t; t=new char[30]; ou char *t=new char [30]; Remarque new fournit comme résultat : - Un pointeur sur l’emplacement correspondant, lorsque l’allocation réussie. - Un pointeur NULL dans le cas contraire. Programmation Orientée Objet en Langage C++ 9 M.E 6.2. L’opérateur delete delete possède deux syntaxes : - delete p ; - delete [ ]p ; Où p est une variable devant avoir comme valeur un pointeur sur un emplacement alloué par new. 7. Utilisation des fonctions en ligne (inline) Une fonction en ligne se définit et s’utilise comme une fonction ordinaire, avec la seule différence qu’on fait précéder son en-tête de la spécification inline. Exemple inline double norme(double vec[3]) { for(int i=0,double s=0; i<3; i++) s=s+vec[i]*vec[i]; return sqrt(s); } //--------------------------------------------------------main() { double V1[3], V2[3]; for (int i=0; i<3; i++) { V1[i]=i; V2[i]=i*3; } cout<<″Norme de V1 est : ″<<norme(v1); cout<<″ - Norme de V2 est : ″<<norme(v2); } Commentaire - La fonction norme a pour but de calculer la norme d’un vecteur passé comme argument. - La présence du mot inline demande au compilateur de traiter la fonction norme d’une manière différente d’une fonction ordinaire, à chaque appel de norme, il devra incorporer au sein du programme, les instructions correspondantes (en langage machine). Le mécanisme habituel de gestion de l’appel est de retour n’existe plus, ce qui réalise une économie de temps. Remarque Une fonction en ligne doit être définie dans le même fichier source que celui utilisé. Elle ne peut être compilée séparément. Programmation Orientée Objet en Langage C++ 10 M.E Chapitre 1 : Notions de Classe et Objet Une classe est la généralisation de la notion de type défini par l’utilisateur, dont lequel se trouvent associées à la fois des données (données membres) et des méthodes (fonctions membres). En programmation orientée objet pure, les données sont encapsulées et leur accès ne peut se faire que par le biais des méthodes. 1. Exemple du type classe class point // déclaration de la classe point { int x; int y; public : void initialise(int,int); void affiche(); void deplace(int,int); }; //----Définition des fonctions membres----void point::initialise(int a,int b) { x=a; y=b; } //----------------------------------------void point::affiche() { cout<<″on est à : ″<<x<<″ - ″<<y<<endl; } //----------------------------------------void point::deplace(int a,int b) { x=x+a; y=y+b; } Commentaire Le mot clé ‘public’ précise que tout ce qui le suit (données membres ou fonctions membres) sera public, le reste étant privé. Ainsi : - x et y sont deux membre privés. - ‘initialise’, ‘deplace’ et ‘affiche’ sont trois fonctions membres publics. - La classe ‘point’ ci-dessus peut être définie et utilisée comme dans l’exemple suivant : Suite du programme (exemple du type classe) ……… main() { point p1, p2, p3; p1.initialise(1,3); p1.affiche(); p1.deplace(2,4); p1.affiche(); p2.initialise(5,5); p2.affiche(); } Programmation Orientée Objet en Langage C++ 11 M.E - On dit que p1 et p2 sont des instances de la classe ‘point’ ou encore que se sont des objets de type ‘point’. Tous les membres données de ‘point’ sont privés ou encapsulés. Ainsi, on ne peut pas accéder à x ou à y. Toutefois, les membres données ou les fonctions membres peuvent être privées en utilisant le mot clé ‘private’ ou publiques en utilisant le mot clé ‘public’. Exemple class C { public : ………………………………. …………………………….. private : ………………………………. ………………………………. }; ; ; ; ; 2. Affectation d’objets Elle correspond à une recopie de valeurs des membres donnés (publics ou privés). Ainsi, avec les déclarations : class point { int x, y; public : ………………………… ; }; point p1, p2; L’affectation p2=p1 ; provoquera la recopie des valeurs x et y de p1 dans les membres correspondants de p2. 3. Notion de constructeur et de destructeur 3.1. Introduction Un constructeur est une fonction qui sera appelée automatiquement après la création d’un objet. Ceci aura lieu quelque soit la classe d’allocation de l’objet : statique, automatique ou dynamique. - De la même façon, un objet pourra posséder un destructeur, il s’agit également d’une fonction membre qui est appelée automatiquement au moment de la destruction de l’objet correspondant. - Par convention, le constructeur se reconnaît à ce qu’il porte le même nom que la classe. Quand au destructeur, il porte le même nom que la classe précédé du symbole ~. Exemple Reprendre l’exemple utilisant la classe ‘point’ définie avec son constructeur. Programmation Orientée Objet en Langage C++ 12 M.E class point { int x ; int y ; public : point(int,int); void affiche(); void déplace(); }; //----------------------------------------------------point::point(int a , int b) { x=a; y=b; } //---------------------------------------------------void point::affiche() { cout<<″on est à : ″<<x<<″ - ″<<y<<″\n″ ;} //----------------------------------------------------void point::deplace (int a, int b) { x=x+a; y=y+b; } //-------------------------------main() { point p1(1,7), p2(2,5); p1.affiche(); p2.affiche(); p1.deplace(-1,5); p1.affiche(); p2.deplace(3,3); p2.afficxhe(); } 3.2. Construction et destruction des objets Suivre la trace des objets automatiques créés dans le programme suivant en affichant les moments de leur construction et leur destruction. class demo { int num ; public : demo(int); ~demo(); }; //-----------------demo::demo(int n) { num=n; cout<<″Appel constr numéro : ″<<num<<endl; } //----------------------------------------------demo::~demo() { cout<<″Appel destr numéro : ″<<num<<endl; } //-------------------------------------Programmation Orientée Objet en Langage C++ 13 M.E main() { void f(int); demo obj(7); for (int i=0; i<4; i++) f(i); } //--------------------------------------void f(int m) { demo obj(m) ; } Exécution 3.3. Rôle de constructeur et de destructeur Le rôle d’un constructeur ou d’un destructeur peut dépasser celui d’initialisation ou de libération. Exemple Construction d’un tableau de 10 valeurs entières prises au hasard, tel que l’argument passé au constructeur est la valeur entière maximum à ne pas dépasser. Ajouter une fonction membre appelée : ‘affiche’ pour afficher le contenu du tableau. class tableau { int t[10]; public : tableau(); void affiche(); }; //--------------------tableau::tableau(int max) { for(int i=0;i<10;i++) t[i]=i*2; } //-------------------------------------------------void tableau::affiche() { for(int i=0;i<10;i++) cout<<t[i]<<endl; } Programmation Orientée Objet en Langage C++ 14 M.E Règles - Un constructeur peut ou non comporter quelques arguments. par définition, un constructeur ne renvoie pas de valeur et la présence de void (dans ce cas précis) est une erreur. Un destructeur, par définition, ne peut pas disposer d’arguments et ne renvoie pas de valeur. 4. Membres statiques Lorsqu’on crée différents objets d’une même classe, chaque objet possède ces propres données membres. Exemple calss point { int x ; int y ; ………………. ………………. ………………….. }; - Une déclaration telle que : point p1, p2; provoquera : Objet p1 Objet p2 p1.x p2.x p1.y p2.y - Pour pouvoir partager des données entre objets de la même classe, on les déclare statiques. Exemple class point { static int x; int y; …………………. ………………… …………….. }; Programmation Orientée Objet en Langage C++ 15 M.E Une déclaration telle que : point p1, p2; provoquera : p1.x p2.x Objet p1 Objet p2 p1.y p2.y p1.x est identique à p2.x mais p1.y ≠ p2.y. Commentaire - - Les membres statiques existent en un seul exemplaire quelque soit le nombre d’objets de la classe correspondante, et même si aucun objet de la même classe n’a été créé. Les membres statiques sont toujours initialisés par 0. Exemple class cpt_obj { static int c; public : cpt_obj(); ~cpt_obj(); }; //---------------------int cpt_obj::c=0; //---------------------cpt_obj()::cpt_obj() {cout<<″Construction, il y a maintenant : ″<<++c<<″ Objet\n″;} //-----------------------------------------------------------cpt_obj::~cpt_obj() {cout<<″Destruction, il reste : ″ <<--c<<″ Objet\n″;} //----------------------------------------------------main() { void f(); cpt_obj O1; f(); cpt_obj O2; } //------------void f(){ cpt_obj a, b; } Programmation Orientée Objet en Langage C++ 16 M.E Exécution Exercice 1 Ecrire un programme permettant de créer des objets ayant chacun : - un tableau de 5 éléments de type entier en tant que donnée ; - une fonction pour remplir le tableau, une fonction pour trier le tableau et une fonction pour afficher le contenu du tableau en tant que méthodes. Solution (sous Dev-C++ 4.9.9.2) class { tableau int t[5]; public : tableau(){ for(int i=0;i<5;i++) t[i]=0; } void remplir(); void afficher(); void trier(); }; void tableau::remplir() { cout<<"Veuillez remlir le tableau avec 5 entiers :"<<endl; for(int i=0;i<5;i++) cin>>t[i]; } void tableau::afficher() { for(int i=0;i<5;i++) cout<<t[i]<<"\t"; cout<<endl; } void tableau::trier() { int a; for(int i=0;i<4;i++) for(int j=i+1;j<5;j++) if(t[i]<t[j]){ a=t[i]; t[i]=t[j]; t[j]=a; } } //--------------------------------------------------------main() { tableau t1; t1.remplir(); cout<<"Avant tri : "<<endl; t1.afficher(); t1.trier(); cout<<"Apres tri : "<<endl; t1.afficher(); getch(); } Programmation Orientée Objet en Langage C++ 17 M.E Exécution Exercice 2 Reprendre le même programme en remplaçant le tableau de 5 éléments par un tableau dynamique de ‘ne’ éléments et instancier des objets ayant des tableaux dynamiques de différentes tailles. Solution (sous Dev-C++ 4.9.9.2) class { tableau int *t; int ne; public : tableau(int n) { ne=n; t=new int[ne]; for(int i=0;i<ne;i++) t[i]=0;} void remplir(); void afficher(); void trier(); ~tableau(){ delete []t;} }; //----------------------------------------void tableau::remplir() { cout<<"Veuillez remplir le tableau avec "<<ne<<" entiers :"<<endl; for(int i=0;i<ne;i++) cin>>t[i]; } //----------------------------------------void tableau::afficher() { for(int i=0;i<ne;i++) cout<<t[i]<<"\t"; cout<<endl; } //------------------------------------------------------void tableau::trier() { int a; for(int i=0;i<ne-1;i++) for(int j=i+1;j<ne;j++) if(t[i]>t[j]){a=t[i]; t[i]=t[j]; t[j]=a;} } //---------------------------Programmation Orientée Objet en Langage C++ 18 M.E main() { tableau t1(3); t1.remplir(); cout<<"Avant tri : "<<endl; t1.afficher(); t1.trier(); cout<<"Apres tri : "<<endl; t1.afficher(); //----------------------------------------------------tableau t2(5); t2.remplir(); cout<<"Avant tri : "<<endl; t2.afficher(); t2.trier(); cout<<"Apres tri : "<<endl; t2.afficher(); getch(); } Exécution Programmation Orientée Objet en Langage C++ 19 M.E Chapitre 2 : Propriétés des fonctions membres 1. Surdéfinition des fonctions membres La surdéfinition des fonctions s’applique également aux fonctions membres d’une classe, y compris au constructeur (mais pas au destructeur puisqu’il ne possède pas d’arguments). Exemple Surdéfinir les fonctions membres ‘point’ et ‘affiche’ de la classe ‘point’. class point { int x, y; public : point(); point(int); point(int,int); void affiche(); void affiche(char *); }; //-------------point::point() { x=0; y=0; } //----------------------point::point(int a) { x=y=a; } //----------------------point::point(int a,int b) { x=a; y=b; } //------------------------void point::affiche() { cout<<″on est a : ″<<x<<″ - ″<<y<<endl; } //-------------------------------------------void point::affiche(char *t) { cout<<t; affiche(); } //---------------------------main() { point p1; p1.affiche(); point p2(7); p2.affiche(); point p3(44,52); p3.affiche(); p3.affiche(″Troisième point : ″); } Programmation Orientée Objet en Langage C++ 20 M.E Exécution 2. Arguments par défaut Les fonctions membres, et les fonctions ordinaires, peuvent disposer d’arguments par défaut. Exemple Modifier l’exemple précédent de telle façon à ce qu’on remplace les deux fonctions ‘affiche’ par une seule, à un argument de type chaîne : le texte à afficher avant le message : ″on est à : x – y ″. Sa valeur par défaut est la chaîne de caractères vide. 3. Les fonctions membres en ligne Pour rendre ‘en ligne’ une fonction membre, on l’a défini dans la déclaration de la classe même (au lieu de la déclarer dans la classe et de la définir ailleurs). Le mot clé ‘inline’ n’est plus utilisé. Exemple Reprendre le dernier exemple en définissant les trois constructeurs en ligne. class point { int x, y; public : point(){ x=0; y=0; } point(int a){ x=y=a; } point(int a,int b){ x=a; int b; } void affiche(char *); }; //--------------------------void point::affiche(char *t) { cout<<t<<″on est à : ″<<x<<″-″<<y<<endl; } //--------------------------------------------main() { ………………….. ; ………….. ; } Programmation Orientée Objet en Langage C++ 21 M.E 4. Cas des objets transmis en argument d’une fonction membre Une fonction membre peut recevoir un ou plusieurs arguments du type de sa classe. Exemple class point { int x, y; public : point(int a,int b){ x=a; y=b; } int coincide(point); }; //---------------------------int point::coincide(point O) { if(x==O.x && y==O.y) return 1; return 0; } //----------main() { point p1(5,7), p2(5,7), p3(6,6); cout<<″p1 et p2 : ″<<p1.coincide(p2)<<endl; cout<<″p2 et p3 : ″<<p3.coincide(p2)<<endl; cout<<″p1 et p3 : ″<<p1.coincide(p3)<<endl; } Exécution 5. Mode de transmission des objets, arguments d’une fonction Dans l’exemple ci-dessus, le mode de transmission utilisé, était par valeur. 5.1. Transmission de l’adresse d’un objet Reprendre le programme précédent en faisant un passage d’objets par adresse. class point { int x, y; public : point (int a=0,int b=0){ x=a; y=b; } int coincide(point *); }; //---------------------------- Programmation Orientée Objet en Langage C++ 22 M.E int point::coincide(point *O) { if(x==O->x) && (y==O->y) return 1; return 0; } //-----------------------------------------------main() { point p1, cout<<″p1 cout<<″p2 cout<<″p1 } p2(57), et p2 : et p3 : et p3 : p3(57,0); ″<<p1.coincide(&p2)<<endl; ″<<p3.coincide(&p2)<<endl; ″<<p1.coincide(&p3)<<endl; 5.2. Transmission par référence L’emploi des références permet de mettre en place une transmission par adresse, sans avoir à prendre en charge soi même la gestion. Exemple Reprendre le dernier exemple en adaptant la fonction ‘coincide’ de telle façon à ce que son argument soit transmis par référence. class { point int x,y ; public : point(int a=0,int b=0){ x=a; y=b; } int coincide(point &); }; //--------------------------------int point::coincide(point & O) { return (x==O.x && y==O.y) ? 1 : 0; } //---------------------------------------main() { point p1, p2(57), p3(57,0); cout<<″p1 et p2 : ″<<p1.coincide(p2)<<endl; cout<<″p2 et p3 : ″<<p3.coincide(p2)<<endl; cout<<″p1 et p3 : ″<<p1.coincide(p3)<<endl; } Exécution Programmation Orientée Objet en Langage C++ 23 M.E 6. Autoréférence : le mot clé ‘this’ Le mot clé ‘this’ utilisé uniquement au sein d’une fonction membre désigne un pointeur sur l’objet l’ayant appelé. Exemple class point { int x, y; public : point(int a=0,int b=0){ x=a; y=b; } void affiche(){ cout<<″Point : ″<<x<<″ - ″<<y<<″de l’objet dont l’adresse est : ″<<this<<endl; } }; //-----------------------------------------------------------main() { point p1, p2(5,3), p3(6,70); p1.affiche(); p2.affiche(); p3.affiche(); } Exécution Programmation Orientée Objet en Langage C++ 24 M.E Chapitre 3 : Construction, destruction et initialisation des objets 1. Objets automatiques et statiques 1.1. Durée de vie d’allocation mémoire Les objets automatiques sont créés par une déclaration : - Dans une fonction. - Dans un bloc. Les objets statiques créés par une déclaration : - En dehors de toute fonction. - Dans une fonction, mais ‘static’. Remarque Les objets statiques sont créés avant le début de l’exécution de la fonction ‘main’ et ils sont détruits après la fin de son exécution. 1.2. Appel des constructeurs et des destructeurs Exemple class point { int x,y; public : point(int a,int b){ x=a; y=b; } ... }; - point point p1(2, 7) ; est une déclaration correcte. p2 ; et point p3(17) ; sont des déclarations incorrectes. Remarque - Le constructeur est appelé après la création d’un objet. Le destructeur est appelé avant la destruction d’un objet. 2. Les objets temporaires Lorsqu’une classe dispose d’un constructeur, ce dernier peut être appelé explicitement ; dans ce cas, il y a alors création d’un objet temporaire. Exemple class point { int x,y; public : point(int a,int b){ x=a; y=b; } ... }; Programmation Orientée Objet en Langage C++ 25 M.E Supposons que ‘p’ est un objet de la classe ‘point’ avec les paramètres (1,1) : point p(1,1); On peut écrire une affectation telle que : p=point(2,7); L’évaluation de l’expression point(2,7) ; entraîne : - La création d’un objet temporaire de type ‘point’ (qui n’a pas de nom). - L’appel du constructeur ‘point’, pour cet objet temporaire, avec transmission des arguments spécifiés. - La recopie de cet objet temporaire dans l’objet p. Exercice Ecrire un programme permettant de créer un objet automatique dans la fonction ‘main’, deux autres objets temporaires affectés à cet objet tout en affichant le moment de leur création et leurs adresses. ………………. ………………………. main() { point p(1,1); p=point(5,2); p.affiche(); p=point(7,3); p.affiche(); } 3. Les objets dynamiques Exemple ……………………… main() { point *p; p=new point(7,2); p->affiche(); ou (*p).affiche(); p=new point(8,4); p->affiche(); delete p; } 4. Tableaux d’objets Un tableau d’objet est déclaré sous la forme : Exemple point courbe[3]; Programmation Orientée Objet en Langage C++ 26 M.E 5. Objets d’objets Une classe peut contenir des membres données de type quelconque y compris le type ‘class’. Exemple Ecrire un programme permettant de définir une classe appelée ‘point_coul’ à partir de la classe ‘point’ définie précédemment et ayant comme données : x, y et couleur. class point { int x,y; public : point (int a=0,int b=0) { x=a; y=b; cout<<″Construction du point : ″<<x<<″ - ″ <<y<<endl; } ~point() { cout<<″Destruction du point : ″<<x<<″ - ″ <<y<<endl; } }; //-------------------------------------class point_coul { point p; int couleur; public : point_coul(int,int,int); ~point_coul() { cout<<"Destruction du point colore En couleur : " <<couleur<<endl; getch(); } }; point_coul::point_coul(int a,int b,int c):p(a,b) { couleur=c; cout<<″Construction de point_coul en couleur : ″ <<couleur<<endl; } //---------------------------main() { point_coul pc(3,7,8); } Programmation Orientée Objet en Langage C++ 27 M.E - L’entête de point_coul spécifie, après les deux points (:), la liste des arguments qui seront transmis au constructeur ‘point’. Les constructeurs seront appelés dans l’ordre suivant ‘point’, ‘point_coul’. S’il existe des destructeurs, ils seront appelés dans l’ordre inverse. Exécution 6. Initialisation d’un objet lors de sa déclaration En plus d’une éventuelle initialisation par défaut (réservée aux variables statiques), une variable peut être initialisée explicitement lors de sa déclaration. Exemple int int int n=2; m=3*n-7; t[3]={5,12,43}; Remarque La partie suivant le signe ‘=’ porte le nom d’initialiseur, il ne s’agit pas d’un opérateur d’affectation. C++ garantie l’appel d’un constructeur pour un objet créé par une déclaration sans initialisation (ou par ‘new’). Mais, il est également possible d’associer un initialiseur à la déclaration d’un objet. 6.1. Un premier exemple class point { int x,y; public : point(int a) { x=a; y=0;} }; - point p1(3) ; est une déclaration ordinaire d’un objet ‘p1’ aux coordonnées 3 et 0. - point p2=7 ; entraîne : La création d’un objet appelé ‘p2’. L’appel du constructeur auquel on transmet en argument la valeur de l’initialiseur ‘7’. En fin, les deux déclarations : ‘point p1(3) ;’ et ‘point p2=7 ;’ sont équivalentes. Programmation Orientée Objet en Langage C++ 28 M.E 6.2. Constructeur par recopie L’initialiseur d’un objet peut être d’un type quelconque, en particulier, il peut s’agir du type de l’objet lui-même. Exemple point p1(33); Il est possible de déclarer un nouvel objet ‘p3’ tel que : point p3=p1; // équivalente à : point p3(p1); Cette situation est traitée par C++, selon qu’il existe un constructeur ou il n’existe pas de constructeur correspondant à ce cas. a. Il n’existe pas de constructeur approprié Cela signifie que, dans la classe ‘point’, il n’existe aucun constructeur à un seul argument de type ‘point’. Dans ce cas, C++ considère que l’on souhaite initialiser l’objet ‘p3’ après sa déclaration avec les valeurs de l’objet ‘p1’. Le compilateur va donc mettre en place une recopie de ces valeurs. (Analogue à celle qui est mise en place lors d’une affectation entre objets de même type). Ce cas est le seul ou C++ accepte qu’il n’existe pas de constructeur. Une déclaration telle que : ‘point p(x);’ sera rejetée si ‘x’ n’est pas de type ‘point’. b. Il existe un constructeur approprié Cela signifie qu’il doit exister un constructeur de la forme : ‘point (point &);’. Dans ce cas, ce constructeur est appelé de manière habituelle, après la création de l’objet, sans aucune recopie. Remarque C++ impose au constructeur en question que son unique argument soit transmis par référence. La forme ‘point (point) ;’ serait rejetée par le compilateur. 6.3. Exemple d’utilisation du constructeur par recopie 6.3.1. Traitement par défaut class tableau { int ne; double *p; public : tableau(int n) { p=new double[ne=n]; cout<<″Constructeur ordinaire : ″ <<this<<″avec son tableau : ″ <<p<<endl; } Programmation Orientée Objet en Langage C++ 29 M.E ~tableau() { delete p; cout<<″Destruction objet : ″<<this <<″avec son tableau : ″<<p<<endl; } }; //--------------------------------------main() { tableau t1(3); tableau t2=t1; // équivalente à ‘tableau } t2(t1);’ Exécution Commentaire La déclaration : ‘tableau t1 ;’ a créé un nouvel objet sans faire appel au constructeur, dans lequel on a recopié les valeurs des membres ‘ne’ et ‘p’ de l’objet t1. A la fin de l’exécution de ‘main’, il y a appel du destructeur pour ‘t1’ et pour ‘t2’, pour libérer le même emplacement. 6.3.2. Définition d’un constructeur par recopie On peut éviter le problème posé ci-dessus de telle façon à ce que la déclaration ‘tableau t2=t1;’ conduise à créer un nouvel objet de type ‘tableau’ avec non seulement ses membres données ‘ne’ et ‘p’ mais également son propre tableau dynamique. Pour ce faire, on définit un constructeur par recopie de la forme : tableau (tableau &); Programme (avec remplissage du tableau dynamique) class tableau { int ne; double *p; public : tableau(int n) { p=new double[ne=n]; cout<<"Constructeur ordinaire : "<<this<< " avec son tableau dynamique : "<<p<<endl; for(int i=0;i<ne;i++) p[i]=(i+1)*10; } tableau(tableau & t) Programmation Orientée Objet en Langage C++ 30 M.E { ne=t.ne; p=new double[ne]; cout<<"Constructeur par recopie : "<<this<< " avec son tableau dynamique : "<<p<<endl; for(int i=0;i<ne;i++) p[i]=t.p[i]; } ~tableau() { delete []p; cout<<"Destruction objet : "<<this<< " avec son tableau : dynamique"<<p<<endl;getch(); } }; //--------------------------------------main() { tableau t1(3); tableau t2=t1; // équivalente à : tableau t2(t1); getch(); } Exécution Programmation Orientée Objet en Langage C++ 31 M.E Chapitre 4 : Les fonctions amies En principe, l’encapsulation interdit à une fonction membre d’une classe ou toute fonction d’accéder à des données privées d’une autre classe. Mais grâce à la notion d’amitié entre fonction et classe, il est possible, lors de la définition de cette classe d’y déclarer une ou plusieurs fonctions (extérieurs de la classe) amies de cette classe. Une telle déclaration d’amitié les autorise alors à accéder aux données privées, au même titre que n’importe quelle fonction membre. Il existe plusieurs situations d’amitiés : 1. Fonction indépendante, amie d’une classe. 2. Fonction membre d’une classe, amie d’une autre classe. 3. Fonction amie de plusieurs classes. 4. Toutes les fonctions membres d’une classe, amies d’une autre classe. 1. Exemple de fonction indépendante amie d’une classe Pour déclarer une fonction amie d’une classe, il suffit de la déclarer dans cette classe en la précédent par le mot clé ‘friend’. class point { int x,y; public : point(int a=0,int b=0) {x=a; y=b;} friend int coincide(point,point); }; //-----------------------------------------int coincide(point p1,point p2) { if(p1.x==p2.x && p1.y==p2.y) return 1; else return 0; } //-------------------------main() { point o1(15,2), o2(15,2), o3(13,25); if(coincide(o1,o2)) cout<<″les objets coïncident\n″; else cout<<″les objets sont différents\n″; if(coincide(o1,o3)) cout<<″les objets coïncident\n″; else cout<<″les objets sont différents\n″; } Programmation Orientée Objet en Langage C++ 32 M.E Exécution Remarques -L’emplacement de la déclaration d’amitié dans la classe est quelconque. -Généralement, une fonction amie d’une classe possédera 1 ou plusieurs arguments ou une valeur de retour du type de cette classe. 2. Les différentes situations d’amitié 2.1. Fonction membre d’une classe, amie d’une autre classe Syntaxe class class { B; A ………………… public : friend int B::f(int,A); }; //-----------------------------------------------class B { ………………… public: int f(int,A); }; //----------------------------------------------------int B::f(int,A) { ………………… ……………… } Commentaire 1. la fonction membre ‘f’ de la classe ‘B’ peut accéder aux membres privées de n’importe quel objet de la classe ‘A’. 2. Pour compiler correctement la déclaration de la classe ‘A’, contenant une déclaration d’une fonction amie, il faut : - Définir ‘B’ avant ‘A’. - Déclarer ‘A’ avant ‘B’. 2.2. Fonction amie de plusieurs classes Toute fonction membre ou indépendante, peut être amie de plusieurs classes. Programmation Orientée Objet en Langage C++ 33 M.E Syntaxe class { A ……………… public : friend void f(A,B); };//-------------------------------class B { ……………… public : friend void f(A,B); };//---------------------------void f(A,B) { ……… } 2.3. Toutes les fonctions d’une classe sont amies d’une autre classe Au lieu de faire autant de déclarations de fonctions amies qu’il y a de fonctions membres, on peut résumer toutes ces déclarations en une seule. Exemple ‘friend class B;’ déclarée dans la classe ‘A’ signifie que toutes les fonctions membres de la classe ‘B’ sont amies de la classe ‘A’. Remarque Pour compiler la déclaration de la classe ‘A’, il suffit de la faire précéder de : ‘class B ;’ Ce type de fonction évite d’avoir à déclarer, les entêtes des fonctions concernées par l’amitié. Exercice Ecrire un programme permettant de réaliser le produit d’une matrice par un vecteur à l’aide d’une fonction indépendante appelée ‘produit’ amie des deux classes ‘matrice’ et ‘vecteur’. La classe ‘vecteur’ possède : - comme données : un vecteur de 3 éléments entiers. - comme fonctions membres : Un constructeur à 3 valeurs entiers. Une fonction ‘affiche’ pour afficher le contenu du tableau (vecteur). La classe ‘matrice’ possède : - comme donnée : une matrice de 9 éléments (3x3). - comme fonction membre : un constructeur ayant une matrice (3x3) comme paramètre. La fonction ‘produit’ retourne normalement un objet de type ‘vecteur’ résultat du produit d’une matrice par un vecteur. Programmation Orientée Objet en Langage C++ 34 M.E Programme class vecteur; class matrice { int m[3][3]; public : matrice(int ma[3][3]) { for(int i=0;i<3;i++) for(int j=0;j<3;j++) m[i][j]=ma[i][j]; } friend vecteur produit(matrice ma,vecteur ve); };//-----------------------------------------------class vecteur { int v[3]; public : vecteur(int a=0,int b=0,int c=0){v[0]=a; v[1]=b; v[2]=c;} void affiche() {for(int i=0;i<3;i++) cout<<v[i]<<"\t";} friend vecteur produit(matrice ma,vecteur ve); };//---------------------------------------------------------vecteur produit(matrice ma,vecteur ve) { int i,j; vecteur vect; for(int i=0;i<3;i++) for(int j=0;j<3;j++) vect.v[i]+=ma.m[i][j]*ve.v[j]; return vect; }//-----------------------------------------main() { vecteur v1(1,2,3); int mat[3][3]={1,2,3,4,5,6,7,8,9}; matrice m1(mat); vecteur resultat; resultat=produit(m1,v1); resultat.affiche(); getch(); } Exécution Programmation Orientée Objet en Langage C++ 35 M.E Chapitre 5 : Surdéfinition des opérateurs C++ autorise la surdéfinition des fonctions membres ou indépendantes en fonction du nombre et du type d’arguments. C++ autorise également la surdéfinition des opérateurs portant au moins sur un objet, tel que l’addition (+), la soustraction (-) ou l’affectation (=) entre objets. 1. Le mécanisme de la surdéfinition des opérateurs Pour surdéfinir un opérateur ‘op’, il faut définir une fonction de nom : ‘operator op’. Exemple Point operator + (point,point); 1.1. Surdéfinition d’opérateur avec une fonction amie class point { int x,y; public: point(int a=0,int b=0) {x=a; y=b;} void affiche() {cout<<″Point : ″<<x<<″ - ″ <<y<<endl;} friend point operator + (point,point); }; //----------------------------------------point operator + (point p1,point p2) { point p; p.x=p1.x+p2.x; p.y=p1.y+p2.y; return p; } //-------------------------main() { point o1(10,20); o1.affiche(); point o2(45,50); o2.affiche(); point o3; o3.affiche(); o3=o1+o2; o3.affiche(); o3=operator+(o1,o2); o3.affiche(); o3=o1+o2+o3; o3.affiche(); o3=operator+(operator+(o1,o2),o3); o3.affiche(); } Programmation Orientée Objet en Langage C++ 36 M.E Exécution 1.2. Surdéfinition d’opérateur avec une fonction membre L’expression ‘o1+o2’ sera interprétée par le compilateur comme l’expression ‘o1.operator+(o2);’. Le prototype de la fonction membre ‘operator+’ sera donc : ‘point operator+(point)’ . Exemple class point { int x,y; public : point(int a=0,int b=0) {x=a; y=b;} void affiche() { cout<<″Point : ″<<x<<″ - ″<<y<<endl; } point operator + (point); }; //---------------------------------------------point point::operator+(point p1) { point p; p.x=x+p1.x; p.y=y+p1.y; return p; } //--------------------------main() { point o1(10,20); point o2(40,50); point o3; o3=o1+o2; o3=o3+o1+o2; } Programmation Orientée Objet en Langage C++ o1.affiche(); o2.affiche(); o3.affiche(); o3.affiche(); o3.affiche(); 37 M.E Exécution 2. Les possibilités et les limites de la surdéfinition des opérateurs en C++ 2.1. Il faut se limiter aux opérateurs existants Le symbole suivant le mot clé ‘operator’ doit obligatoirement être un opérateur déjà défini pour les types de base, sauf l’opérateur point ‘.’. Il n’est donc pas possible de créer de nouveaux symboles. Il faut conserver la pluralité (unaire, binaire) de l’opérateur initial. Lorsque plusieurs opérateurs sont combinés au sein d’une même expression, ils conservent leurs priorités relatives et leurs associativités. 2.2. Tableau d’opérateurs surdéfinissabes, classés par priorité décroissante Pluralité Binaire Unaire Binaire Binaire Binaire Binaire Binaire Binaire Binaire Binaire Binaire Binaire Binaire Binaire ◊: Opérateur Associativité ( )◊ [ ] ◊ ◊ + - ++ -- ! & new◊ delete◊ * / % + << >> < <= > >= == != & (niveau bit) ^ (ou exclusif) || && | (niveau bit) =◊ += -= *= /= %= &= ^= |= <<= >>= , opérateur devant être surdéfini en tant que fonction membre. 2.3. Choix entre fonction membre et fonction amie Si un opérateur doit absolument recevoir un type de base en premier argument, il ne peut pas être défini comme fonction membre (laquelle reçoit implicitement un premier argument du type de sa classe). Programmation Orientée Objet en Langage C++ 38 M.E 3. Exemple de surdéfinition de l’opérateur ‘[ ]’ Surdéfinir l’opérateur ‘[ ]’ de manière à ce que ‘o[i]’ désigne l’élément du tableau dynamique d’emplacement ‘i’ de l’objet ‘o’ de la class ‘tableau’. Le premier opérande de ‘o[i]’ étant ‘o’. class tableau { int ne; int *p; public : tableau(int n) { p=new int[ne=n]; for(int i=0;i<ne;i++) p[i]=(i+1)*10; } void affiche() {for(int i=0;i<ne;i++) cout<<p[i]<<"\t"; cout<<endl;} int operator[](int n){ return p[n]; } ~tableau(){delete []p;} }; //--------------------------------------main() { tableau t1(3); t1.affiche(); cout<<t1[2]; t1.affiche(); } La seule précaution à prendre consiste à faire en sorte que cette notation puisse être utilisée non seulement dans une expression, mais également à gauche d’une affectation. Il est donc nécessaire que la valeur de retour fournie par l’opérateur ‘[ ]’ soit transmise par référence : class tableau { int ne; int *p; public : tableau(int n) { p=new int[ne=n]; for(int i=0;i<ne;i++) p[i]=(i+1)*10; } void affiche() {for(int i=0;i<ne;i++) cout<<p[i]<<"\t"; cout<<endl;} int & operator[](int n){ return p[n]; } ~tableau(){delete []p;} }; Programmation Orientée Objet en Langage C++ 39 M.E main() { tableau t1(3); t1.affiche(); cout<<t1[2]; cout<<endl; t1[0]=55; cout<<t1[0]; cout<<endl; t1.affiche(); } Exécution Remarque L’opérateur ‘[]’ n’est pas imposé ici par C++, on aurait pu le remplacer par l’opérateur ‘()’ : ‘o(i)’ au lieu de : ‘o[i]’, ou un autre opérateur. 4. Exemple de surdéfinition de l’opérateur ‘=’ Reprenons le cas de la classe ‘tableau’ : class tableau { int ne; int *p; public : ............; }; Le problème de l’affectation est donc voisin de celui de la construction par recopie, mais non identique : Dans le cas de la construction par recopie (tableau t1(3); tableau t2=t1;), on a un seul tableau dynamique de 3 entiers pour les deux objets t1 et t2. Dans le cas d’affectation d’objets, il existe deux objets complets (avec leurs tableaux dynamiques). Mais après affectation (t2=t1), t2 et t1 référencent le même tableau dynamique (celui de t1), le tableau dynamique de t2 n’est plus référencé. Ce problème peut être résolu en surdéfinissant l’opérateur d’affectation ; de manière à ce que chaque objet de type ‘tableau’ possède son propre emplacement dynamique. Dans ce cas, on est sûr qu’il n’est référencé qu’une seule fois, et son éventuel libération peut se faire sans problèmes. Programmation Orientée Objet en Langage C++ 40 M.E Une affectation t2=t1 ; pourrait être traitée de la façon suivante : - Libération de l’emplacement pointé par le pointeur p de t2. - Création dynamique d’un nouvel emplacement dans lequel on recopie les valeurs de l’emplacement pointé par t1. - Mise en place des valeurs des membres données de t2. - Il faut décider de la valeur de retour fournie par l’opérateur d’affectation en fonction de l’utilisation que l’on souhaite faire (void ou autre). - Dans l’affectation t2=t1; t2 est le premier opérande (ici this car l’opérateur ‘=’ est une fonction membre) et t1 devient le second opérande (ici t). class tableau { int ne; int *p; public : tableau(int n) { p=new int[ne=n]; for(int i=0;i<ne;i++) p[i]=(i+1)*10; } void affiche(){for(int i=0;i<ne;i++) cout<<p[i]<<"\t"; cout<<endl; } tableau & operator=(tableau & t) { delete []p; p=new int[ne=t.ne]; for(int i=0;i<ne;i++) p[i]=t.p[i]; return *this; } ~tableau() { delete []p; } }; //--------------------------------------main() { tableau t1(3); t1.affiche(); tableau t2(4); t2.affiche(); tableau t3(5); t3.affiche(); t1=t3; t1.affiche(); t3.affiche(); t1=t2=t3; t1.affiche(); } Programmation Orientée Objet en Langage C++ 41 M.E Exécution class tableau { int ne; int *p; public : tableau(int n) { p=new int[ne=n]; for(int i=0;i<ne;i++) p[i]=(i+1)*10; } void affiche(){for(int i=0;i<ne;i++) cout<<p[i]<<"\t"; cout<<endl;} tableau & operator=(tableau & t) { if(this!=&t) { delete []p; p=new int[ne=t.ne]; for(int i=0;i<ne;i++) p[i]=t.p[i]; } return *this; } ~tableau() { delete []p; } }; //--------------------------------------main() { tableau t1(3); t1.affiche(); t1=t1; t1.affiche(); } Exécution Exercice Refaire le même traitement pour des tableaux dynamiques de caractères. Programmation Orientée Objet en Langage C++ 42 M.E Chapitre 6 : La technique de l'héritage 1. Mise en œuvre de l'héritage 1.1. Exemple simple sans constructeur ni destructeur 1.1.1. La classe de base ‘point’ définie dans le fichier d'entête ‘point.h’ class point { int x, y; public: void initialise(int,int); void deplace(int,int); void affiche(); }; void point::initialise(int a,int b){x=a; y=b;} void point::deplace(int a,int b){x=x+a; y=y+b;} void point::affiche(){cout<<"Point : "<<x<<" - "<<y<<endl;} 1.1.2. La classe 'point_colore' derivée de la class 'point' #include <point.h> Class point_colore : public point { int couleur; public: void colorer(int c) {couleur=c;} }; 1.2. Commentaire - La déclaration ‘class point_colore : public point’ spécifie que la classe 'point_colore' est dérivée de la classe de base 'point'. - Le mot clé ‘public’ signifie que les membres publics de la class de base seront des membres publics de la classe dérivée. Le programme principal main() { point_colore p; p.inisialise(12,27); p.deplace(1,2); } p.colorer(7); p.affiche(); p.affiche(); Exécution Programmation Orientée Objet en Langage C++ 43 M.E 2. Utilisation, dans une classe dérivée, des membres de la classe de base Après avoir fait appel à la fonction 'colorer', la fonction ‘affiche’ ne donne aucune information sur la couleur d'un point. Ce qui nous conduit donc à créer une fonction 'affiche_c' membre de ‘point_colore pour afficher les coordonnées x, y et la couleur c. void affiche_c() { cout<<"Point : "<<x<<" - "<<y; cout<<"En couleur : "<<couleur<<endl; } Or, une classe dérivée n'a pas accès aux membres privés de la classe de base. La solution est donc : void affiche_c() { affiche(); cout<<" en couleur : "<<couleur<<endl; } De même on peut initialiser x, y et couleur en même temps avec une fonction 'initialise_c' de la classe dérivée 'point_colore'. Le programme complet est donc : class point { int x, y; public: void initialise(int a,int b){x=a;y=b;} void deplace(int a,int b){x=x+a; y=y+b;} void affiche(){cout<<"Point : "<<x<<" - "<<y;} } ; //---------------------------------------------------class point_colore:public point { int couleur; public: void initialise_c(int c) {couleur=c;} void colorer(int c) {couleur=c;} void affiche_c() { affiche(); cout<<" En couleur : "<<couleur<<endl; } }; Programmation Orientée Objet en Langage C++ 44 M.E main() { point_colore p; p.initialise(12,9); p.colorer(7); p.affiche(); cout<<endl; p.affiche_c(); p.deplace(1,2);p.affiche(); cout<<endl; p.affiche_c(); } Exécution 3. Redéfinition des fonctions membres Les méthodes 'affiche' de la classe 'point' et 'affiche_c' de la classe 'point_colore' font un travail analogue. De même pour les fonctions 'initialise' et 'initialise_c'. En c++, il est possible de redéfinir la fonction 'affiche' ou la fonction 'inialise’ pour la classe dérivée. Exemple #include <point.h> class point_colore:public { int couleur; point public: void colorer(int c){couleur=c;} void affiche() { point::affiche(); cout<<" en couleur : "<<couleur<<endl; } void initialise(int a,int b,int c) {point::initialise(a,b); couleur=c;} }; //-------------------------------------------main() { point_colore p; p.initialise(12,27,9); p.affiche(); p.deplace(10,20); p.affiche(); p.colorer(7); p.affiche(); } Programmation Orientée Objet en Langage C++ 45 M.E Exécution 4. Appel des constructeurs et des destructeurs Si on a : class { point int x,y; public: point(int,int); }; //--------------------------------------------class point_colore:public point { int couleur; public: point_colore(int,int,int); }; Pour créer un objet de la classe ‘point_colore’, il faut tout d’abord créer un objet de la classe ‘point’, donc faire appel au constructeur de la classe ‘point’, le compléter par ce qui est spécifique a la classe ‘point_colore’ et faire appel au constructeur de la classe ‘point_colore’. Si on souhaite que le constructeur de la classe ’point_clolre’ retransmette au constructeur de la classe ‘point’ les premières informations reçues, on écrira : ‘point_colore(int a, int b, int c):point(a, b) ;’. Ainsi, la déclaration : ‘point_colore(2,4,5) ;’ entraînera : - l’appel du constructeur ’point’ qui recevra les valeurs 2 et 4. l’appel du constructeur ’point_colore’ qui recevra les valeurs 2, 4 et 5. Il est toujours possible de mentionner les valeurs par défaut : point_colore(int a=0, int b=2, int c=5):point(a,b) Donc la déclaration ‘point_colore(17) ;’ entraîne : - appel du constructeur ‘point’ avec les valeurs 17 et 0. - appel du constructeur ‘point_colore’ avec les valeurs 17, 2 et 5. Exercice Reprendre le programme précédent en ajoutant les constructeurs et les destructeurs correspondants, tout en affichant les moments de constriction et de destruction des objets. Prévoir aussi des objets dynamiques. Programmation Orientée Objet en Langage C++ 46 M.E D’une manière générale Si la classe de base ne possède pas de constructeur, aucun problème particulier ne se pose. De même si elle ne possède pas de destructeur. En revanche, si la classe dérivée ne possède pas de constructeur, alors que la classe de base en comporte, le problème sera posé lors de la transmission des informations attendues par le constructeur de la classe de base. La seule situation acceptable est celle où la classe de base dispose d’un constructeur sans arguments. Lors de la transmission d’information au constructeur de la classe de base, on peut utiliser des expressions ou des arguments. Exemple pointcolore(int a=5, int b=5, int c=4):point(a*2, b*5); 5. Contrôle des accès 5.1. L’héritage privé Pour que l’utilisateur d’une classe dérivée n’ait pas accès aux membres publics de sa classe de base, il suffit de remplacer le mot ‘public’ par ‘private’ dans sa déclaration. Exemple class { point int x, y; public: void initialise(int,int) {x=a; y=b;} void deplace(int,int) {x=x+a; y=y+b;} void affiche(){ ……………………… } }; //---------------------------------------class point_colore : private point { int couleur; public: void colorer(int c) {couleur=c;} }; Si on a : point_colore O(12,4,6); Les appels suivants sont rejetés : O.affiche(); ou O.point::affiche(); O.deplace(1,5); ou O.point::depace(1,5); Programmation Orientée Objet en Langage C++ 47 M.E Remarque Cette technique de fermeture d’accès à la classe de base ne sera employée que dans des cas précis, lorsque par exemple toutes les fonctions utiles de la classe de base sont redéfinies dans la classe dérivée, et qu’il n’y a aucune raison de laisser l’utilisateur accéder aux anciennes. 5.2. Les membres protégés d’une classe Les membres protégés restent inaccessibles à l’utilisateur (comme les membres privés). Mais ils seront accessibles aux membres d'une éventuelle classe dérivée, tout en restant inaccessibles aux utilisateurs de cette classe. Exemple Supposons qu'on a : class point { protected: int x,y; public: void initialise(int,int); void deplace(int,int); void affiche(); }; On peut donc déclarer dans la classe 'point_colore' dérivée de la classe 'point' une fonction 'affiche' qui accède aux membres protégés x et y. class { point_colore:public point int couleur; public: void affiche() { cout<<"Point coloré " <<x<<" - " <<y<<endl; cout<<"en couleur : "<<couleur<<endl; } }; Remarque Lorsqu'une classe dérivée possède une classe amie, cette dernière dispose des mêmes droits d'accès que les fonctions membres de la class dérivée. Programmation Orientée Objet en Langage C++ 48 M.E 6. L'héritage en général D'une façon générale, l'héritage peut être représenté par les arbres suivants : A B D E C F G H Héritage simple A B C D Héritage complexe 7. Conversion d'un objet dérivé dans un objet d'un type de base Si on a : point point_colore - o1; o2; l'affectation ‘o1=o2 ;’ est juste. l'affectation ‘o2=o1 ;’ est rejetée (si o2 a des arguments définis par défaut, on n’aura pas de problème). Exercice A. Ecrire un programme écran de veille affichant des points (objets de type 'pointg' à définir) en couleur à tout endroit de l'écran, et ce, jusqu'a ce que l'utilisateur appuie sur une touche quelconque. B. Reprendre le même programme en ajoutant des objets de la classe 'cercle' dérivée de la classe 'pointg' et ayant en plus, la donnée membre 'rayon' et les méthodes : - affiche_cercle : pour afficher un cercle aux coordonnées x, y. - cache_cercle : pour rendre un cercle invisible. - zoom_cercle : pour afficher une série de cercles concentriques. - deplace_cercle : pour déplacer un cercle. - ……… Programmation Orientée Objet en Langage C++ 49 M.E Chapitre 7 : L’héritage multiple 1. Mise en œuvre de l’héritage multiple L’exemple suivant met en évidence la notion d’héritage multiple à travers la classe ‘point_colore’ héritant des classes ‘point’ et ‘coul’. point coul pointcolore class point { int x,y; public: point(int a,int b) { x=a ;y=b ; cout<<"Constructeur de point "; } ~point(){cout<<"Destructeur de point ";} void affiche(){cout<<"point "<<x<<" - "<<y;} }; //----------------------------class coul { int couleur ; public : coul(int c) { couleur=c ; cout<<"Construction de coul "; } ~coul(){cout<<"Destruction de coul ";} void affiche() {cout<<"Couleur : "<<couleur<<endl;} }; //------------------------------------------------class point_colore:public point,public coul { public: point_colore(int a,int b,int c):point(a,b),coul(c) {cout<<"construction de pointcolore\n";} Programmation Orientée Objet en Langage C++ 50 M.E ~pointcolore() {cout<<"destruction de pointcolore\n"; } void affiche() { point::affiche(); coul::affiche(); } }; //--------------------------------------main() { point_colore o(100,200,3); o.affiche(); o.point::affiche(); o.coul::affiche(); } Exécution 2. Les classes virtuelles Si on a : A B C D Donc les déclarations suivantes : class A { int x,y; ……………… }; class B:public A { ……………… }; class C:public A { ……………… }; class D:public B, public C { ……………… }; Programmation Orientée Objet en Langage C++ 51 M.E Impliquent que la classe ‘D’ hérite deux fois de la classe ‘A’, donc les membres de ‘A’ vont apparaître deux fois dans ‘D’. - les fonctions membres ne sont pas réellement dupliquées. - les données membres seront effectivement dupliquées. Donc : - si on veut laisser cette duplication, on utilise : A::B::x ≠ A::C::x ou B::x ≠ C ::x - si on ne veut pas de cette duplication, on précisera la classe ‘A’ comme classe virtuelle dans les déclarations des classes ‘B’ et ‘C’. class A { int x,y; ………………….. }; class B:public virtual A { ……………………. }; class C:public virtual A { ……….. }; class D:public B, public C { ……………. }; Remarque - le mot-clé ‘virtual’ apparaît dans la classe ‘B’ et la classe ‘C’. - le mot-clé ‘virtual’ peut être placé avant ou après le mot-clé ‘public’. 3. Appel des constructeurs et des destructeurs dans le cas des classes virtuelles A1 A2 B C A B C D D - Si ‘A’ n’est pas déclarée ‘virtual’ dans les classes ‘B’ et ‘C’, les constructeurs seront appelés dans l’ordre : ‘A1’, ‘B’, ‘A2’, ‘C’ et ‘D’. - Si ‘A’ a été déclarée ‘virtual’ dans ‘B’ et ‘C’, on ne construira qu’un seul objet de type de ‘A’ (et non pas deux objets). Programmation Orientée Objet en Langage C++ 52 M.E Quels arguments faut-il transmettre alors au constructeur ? Ceux prévus par ‘B’ ou ceux prévus par ‘C’ ? Le choix des informations à transmettre au constructeur de ‘A’ se fait, non plus dans ‘B’ ou ‘C’, mais dans ‘D’. Pour ce faire, C++ autorise (uniquement dans ce cas) à spécifier, dans le constructeur de ‘D’, des informations destinées à ‘A’. Ainsi, on peut avoir : D(int a, int b, int c) : B(a, b, c), A(a, b) Bien entendu, il sera inutile de préciser des informations pour ‘A’ au niveau des constructeurs ‘B’ et ‘C’. En ce qui concerne l’ordre des appels, le constructeur d’une classe virtuelle est toujours appelé avant les autres. Dans notre cas, on a l’ordre ‘A’, ‘B’, ‘C’ et ‘D’. Exemple A B C D E L’ordre des appels des constructeurs est : B, A, C, D et E. Exercice Interpréter et commenter le programme utilisant l’héritage multiple dans l’exemple suivant de liste simplement chaînée de points ( objets appartenant à la classe ‘point’). liste point liste_point Programmation Orientée Objet en Langage C++ 53 M.E struct element { element *suivant; void *contenu; }; //------------------------------------------class liste { element *debut; element *courant; public: liste() { debut=NULL; courant=debut; } ~liste(); void ajouter(); void *premier() { courant=debut; return(courant->contenu); } void *prochain() { if(courant!=NULL) courant=courant->suivant; return(courant->contenu); } int finir() { return(courant==NULL); } }; Solution avec liste simple d’entiers sans objets : typedef struct sm { int don; sm *suiv; } MAILLON; //--------------------typedef MAILLON *LISTE; //--------------------------------------- Programmation Orientée Objet en Langage C++ 54 M.E void Ajouter(LISTE & l, LISTE & p, int n) { if(l==NULL) { l=new MAILLON; l->don=n; l->suiv=NULL; p=l; } else { p->suiv=new MAILLON; p=p->suiv; p->don=n; p->suiv=NULL; } } //----------------void Lire(LISTE l) { LISTE p=l; while(p!=NULL) { cout<<p->don<<endl; p=p->suiv; } } //---------------------------main() { LISTE L1=NULL; LISTE C1=NULL; Ajouter(L1,C1,33); Ajouter(L1,C1,44); Ajouter(L1,C1,55); Ajouter(L1,C1,66); Ajouter(L1,C1,77); Lire(L1); } Solution avec liste simple avec objets : typedef struct sm { sm *suiv; int contenu; } MAILLON; //------------------------------- Programmation Orientée Objet en Langage C++ 55 M.E class liste { MAILLON *debut; MAILLON *courant; public: liste(){ debut=NULL; courant=debut; } ~liste(){ } void Ajouter(int n) { if(debut==NULL) { debut=new MAILLON; debut->contenu=n; debut->suiv=NULL; courant=debut; } else { courant->suiv=new MAILLON; courant=courant->suiv; courant->contenu=n; courant->suiv=NULL; } } void Afficher() { MAILLON *courant=debut; while(courant!=NULL) { cout<<courant->contenu<<endl; courant=courant->suiv; } } }; main() { liste l1; l1.Ajouter(33); l1.Ajouter(44); l1.Ajouter(55); l1.Afficher(); cout<<endl; liste l2; l2.Ajouter(333); l2.Ajouter(444); l2.Ajouter(555); l2.Afficher(); } Exécution Programmation Orientée Objet en Langage C++ 56 M.E Chapitre 8 : Le polymorphisme Le polymorphisme forme avec l’encapsulation et l’héritage les trois piliers des langages orientés objets. 1. Redéfinition des fonctions Exemple Utilisation d’un pointeur de base pour manipuler un objet d’une classe dérivée. class materiel { protected : char ref[20]; char marque[20]; public : materiel(char *r, char *m) { strcpy(ref,r); strcpy(marque,m); } void affiche() {cout<<"Reference : "<<ref<<"\tMarque : "<<marque<<endl;} }; //----------------------------------------------------------class micro:public materiel { char processeur[20]; int disque; public : micro(char *r,char *m,char *p,int d):materiel(r,m) { strcpy(processeur,p); disque=d ; } void affiche() { cout<<"Reference : "<<ref<<"\tMarque : "<<marque <<"\tProcesseur : "<<processeur <<"\tDisque : "<<disque<<endl; } }; //------------------------------------ Programmation Orientée Objet en Langage C++ 57 M.E main () { materiel *p; p=new materiel("HP5010","HP"); p->affiche(); p=new micro("IBM7","IBM","P4",40); p->affiche(); delete p; } Exécution Commentaire - Le pointeur ‘p’ est utilisé pour stocker l’adresse d’un objet de la classe ‘materiel’, appeler la fonction ‘affiche’ et détruire l’objet créé. - Le pointeur ‘p’ est ensuite utilisé pour réaliser les mêmes opérations sur un objet de la classe ‘micro’. -Dans le cas des deux objets créés, c’est la méthode ‘affiche’ de la classe ‘matériel’ qui a été utilisée. Le compilateur a donc tenu compte du type de pointeur (matériel*) et non du type d’objet pointé par ‘p’. - L’idéal serait donc que le compilateur appelle ‘affiche’ de ‘micro’ quand ‘p’ pointe sur ‘micro’ et ‘affiche’ de ‘matériel’ quand ‘p’ pointe sur ‘matériel’. - Pour obtenir ce résultat, il suffit d’indiquer au compilateur que la fonction ‘affiche’ est une fonction polymorphe, c.à.d une fonction virtuelle. Définition Une fonction polymorphe (ou virtuelle) est une méthode qui est appelée en fonction du type d’objet et non en fonction du type de pointeur utilisé. Exemple Pour que le programme précédent fonctionne correctement, il faut déclarer la fonction ‘affiche’ une fonction polymorphe ou virtuelle en précédant son prototype par le mot clé ‘virtual’. Programmation Orientée Objet en Langage C++ 58 M.E class materiel { protected : char ref [20+1]; char marque [20+1]; public : materiel (char *r, char *m) { strcpy(ref,r); strcpy(marque,m); } virtual void affiche() {cout<<"Reference : "<<ref<<"\tMarque : "<<marque<<endl;} }; class micro : public materiel { char processeur [20+1]; int disque; public : micro(char *r,char *m,char *p,int d): materiel(r,m) { strcpy(processeur,p); disque=d ; } void affiche() { cout<<"Reference : "<<ref<<"\tMarque : "<<marque <<"\tProcesseur : "<<processeur <<"\tDisque : "<<disque<<endl; } }; //---------------------------------------main () { materiel *p; p=new materiel("HP5010","HP"); p->affiche(); p=new micro("IBM7","IBM","P4",40); p->affiche(); delete p; } Exécution Programmation Orientée Objet en Langage C++ 59 M.E Remarque Seule la fonction ‘affiche’ de la classe de base ‘matériel’ doit être déclarée virtuelle. Cependant, il est conseillé d’indiquer le mot clé ‘virtual’ pour toutes les fonctions concernées par le polymorphisme, et cela, quel que soit leur niveau dans la hiérarchie des classes. Exercice Ajouter une classe ‘imprimante’ et une classe ‘scanner’ dérivées de la classe ‘matériel’ et écrire un programme global sous forme de menu donnant à l’utilisateur la possibilité de choisir le type de matériel à afficher. Remarque - Lorsqu’on appelle une fonction virtuelle pour un objet, le C++ cherche cette méthode dans la classe correspondante. Si cette fonction n’existe pas dans la classe concernée, le C++ remonte la hiérarchie des classes jusqu'à ce qu’il trouve la fonction appelée. - Une fonction virtuelle ne peut être appelée par une autre méthode de la classe. - Une fonction virtuelle ne peut être utilisée dans le corps d’un constructeur ou d’un destructeur. 2. Fonction virtuelle pure et classe abstraite 2.1. Fonction virtuelle pure - Une fonction virtuelle pure est une fonction telle que par exemple : virtual void affiche()=0; - Définir une fonction virtuelle pure entraîne : Une classe qui contient la définition d’une fonction virtuelle pure devient une classe abstraite. La redéfinition des fonctions virtuelles pures est obligatoire dans toutes les classes dérivées. Dans le cas contraire, les classes dérivées deviennent obligatoirement abstraites. 2.2. Classes abstraites - Une classe abstraite est une classe qui ne peut être instanciée. Par contre, on peut créer des objets de ce type via l’héritage. Programmation Orientée Objet en Langage C++ 60 M.E Le programme complet : class materiel { protected : char ref [20+1]; char marque [20+1]; public : materiel (char *r, char *m) { strcpy(ref,r); strcpy(marque,m); } virtual void affiche()=0; }; void materiel::affiche() {cout<<"Reference : "<<ref<<"\tMarque : "<<marque<<endl;} //---------------------------------------------------class micro : public materiel { char processeur [20+1]; int disque; public : micro(char *r,char *m,char *p,int d): materiel(r,m) { strcpy(processeur,p); disque=d ; } void affiche() { cout<<"Reference : "<<ref<<"\tMarque : "<<marque <<"\tProcesseur : "<<processeur <<"\tDisque : "<<disque<<endl; } }; //---------------------------------------main() { materiel *p; //p=new materiel("HP5010","HP"); //p->affiche(); p=new micro("IBM7","IBM","P4",40); p->affiche(); delete p; } Programmation Orientée Objet en Langage C++ 61 M.E Chapitre 9 : Gestion des flux Pour manipuler les flux d’entrées/sorties, le c++ met à notre disposition des opérateurs surchargés << et >> qui permettent respectivement d’afficher et de lire des données, ces opérateurs pourront parfaitement être redéfinis dans le cas de nos classes, ce qui permettra par exemple d’afficher le contenu des données membres d’une classe à l’écran, ou d’écrire ces mêmes variables dans un fichier. 1. Généralités sur les flux Un flux représente un ensemble de données pouvant être manipulés à la fois en lecture ou à la fois en écriture. Un flux, aussi appelé canal de données, offre une transparence vis-à-vis de la source ou de la destination des données : - Ecran, fichier, mémoire pour les types de flux de sortie. - Clavier, fichier, mémoire pour les types de flux d’entrée. En C++, tous les flux sont symbolisés par des classes qui font partie de la librairie ‘iostream.h’. La classe de base est iostream et regroupe les caractéristiques communes aux flux d’E/S. Les deux principales classes dérivées de la classe iostream sont ostream pour les flux de sortie, et istream pour les flux d’entrées. Toute les classes de la librairie ‘iostream.h’ disposent des deux opérateurs surchargés << et >>. L’opérande de gauche de l’opérateur << doit correspondre à un objet de la classe ostream, pour l’opérateur >> cet opérande doit être un objet de la classe istream. Ces deux opérateurs ont été définis pour les types de données suivants : char, short, int, long, float, double, long double, char*, void*. C++ fournie quatre flux prédéfinis pour afficher ou lire des informations. cout : Flux de sortie standard (l’écran par défaut). Cette variable est un objet d’une classe dérivée de ostream. cin : Flux d’entrée standard (le clavier par défaut). Cette variable est un objet d’une classe dérivée de istream. cerr : Sortie erreur standard (l’écran par défaut). Cette variable est un objet d’une classe dérivée de ostream. clog : Permet à la fois d’envoyer des messages d’erreur vers la sortie erreur standard (l’écran par défaut) et de remplir un fichier d’alerte. Cette variable est un objet de la classe ‘ostream’. Programmation Orientée Objet en Langage C++ 62 M.E 2. Afficher sur l’écran avec ‘cout’ Exemple main() { int e=37; float x=4,5; cout<<"debut \n"; cout<<"Entier : "<<e<<endl; cout<<"Reel : "<<x<<endl; } Commentaire - Ce programme utilise 3 fois l’objet ‘cout’ pour afficher du texte à l’écran. - La librairie ‘iostream.h’ fournit un certain nombre de mots clés qui permettent de modifier les caractéristiques d’un flux. Ces commandes sont utilisées directement avec l’opérateur << en respectant la syntaxe : ‘cout<<manipulateur ;’. Manipulateur dec hex oct ws endl ends flush setbase(int a) setfill(int c) setprecision(int c) setw Rôle Convertir en base décimale Convertir en base hexadécimale Convertir en base octale Supprimer les espaces Ajouter un saut de ligne en fin de flux (\n) Ajouter un caractère de fin de chaîne Vider un flux de sortie Choisir la base (0, 8, 10 ou 16) . 0 est la valeur par défaut Choisir le caractère de remplissage (padding) Indiquer le nombre de chiffres d’un nombre décimal Choisir la taille du champ (padding) Exemple int e=31; cout<<e; cout<<hex<<e; Remarque Le padding consiste à compléter généralement avec des espaces ou des zéros, un élément affiché à l’écran en précisant la taille d’un champ ou d’une colonne (set w). Tout élément affiché dont la taille est insuffisante sera complété par défaut par des espaces. De cette manière chaque élément sera affiché sur la même longueur de caractères. En plus de ces manipulateurs, la classe iostream fournit un ensemble de méthodes. Méthodes fill() fill(char c) Programmation Orientée Objet en Langage C++ Rôle Renvoie le caractère de remplissage modifie le caractère de remplissage 63 M.E précision() précision(int n) setf(long flag) setf(long flag,long champ) width() width(int n) renvoie la précision pour le nombre décimal modifie la précision pour le nombre décimal modifie une propriété de formatage (format) Modifie une propriété de formatage d’un champ Renvoie la valeur d’affichage Renvoie la valeur d’affichage Exemple main() { int e=15; cout<<"conversion"<<endl; cout<<"Entier : "<<e<<endl; cout<<"Hexadecimal : "<<hex<<e<<endl; cout<<"Octal : "<<oct<<e<<endl; cout<<"------------------------------------------\n"; cout<<"Largeur et Padding\n"; float x=3.15; cout.setf(ios::right,ios::adjustfield); cout<<"Decimal : "<<setw(10)<<x<<endl; cout<<"Decimal : "<<setw(10)<<x+1000<<endl; cout<<"Decimal : "<<setw(10)<<setfill('#') <<x<<endl; cout<<"-------------------------------------------\n"; cout<<"Nombre de chiffres\n"; double pi=3.14151617; cout<<"PI : "<<pi<<endl; cout<<"PI : "<<setprecision(1)<<pi<<endl; cout<<"PI : "<<setprecision(6)<<pi<<endl; cout<<"-------------------------------------------\n"; cout<<"Chaînes, Alignement et Padding\n"; char texte[50]; strcpy(texte,"Bonjour tout le monde"); cout<<texte<<endl; cout.setf(ios::left,ios::adjustfield); cout.width(50); cout.fill('-'); cout<<texte<<endl; cout.width(40); cout.fill('*'); cout<<texte; } Programmation Orientée Objet en Langage C++ 64 M.E Exécution 3. Saisir au clavier avec ‘cin’ Dans l’exemple suivant : char chaine[10]; cout<<"saisir une chaine : "; cin.width(sizeof(chaine)); cin>>chaine; La fonction ‘width’ fixe le nombre de caractères à affecter à la variable ‘chaine’ sans débordement. La classe istream contient la fonction membre getline qui permet de saisir des chaînes de caractères comprenant des espaces non acceptés par l’opérateur >>. Le prototype de getline est : istream &getline(char* chaine, int nombre, char fin= ’\n’); Avec : chaine : chaîne à saisir. nombre : nombre maximum de caractères de la chaîne. fin : caractère de fin de saisie (par défaut ‘\n’). Programmation Orientée Objet en Langage C++ 65 M.E Exemple class voiture { char marque[20]; char modele[20]; char prix[20]; public: voiture(char *ma="",char *mo="",char *pr="") { strcpy(marque,ma); strcpy(modele,mo); strcpy(prix,pr); } void saisie(); void affiche(); }; //--------------------------------------------------------void voiture::saisie() { cout<<"Marque : "; cin.getline(marque,sizeof(marque)); cout<<"Modele : "; cin.getline(modele,sizeof(modele)); cout<<"Prix : "; cin.getline(prix,sizeof(prix)); } //--------------------------------------------------------void voiture::affiche() { cout.setf(ios::left,ios::adjustfield); cout<<"Marque : "; cout.width(15); cout<<marque; cout<<"Modele : "; cout.width(15); cout<<modele; cout<<"Prix : "; cout.width(15); cout<<prix; cout<<endl; } //-----------------------------------------------------------main() { voiture v1; v1.saisie(); cout<<"\nAffichage \n"; v1.affiche(); cout<<endl; voiture v2; v2.saisie(); cout<<"\nAffichage \n"; v2.affiche(); } Programmation Orientée Objet en Langage C++ 66 M.E Exécution 4. Redéfinir les opérateurs de flux Pour redéfinir les opérateurs de flux, on doit respecter une syntaxe telle qu’elle est définie dans l’exemple suivant : Exemple class demo { public : friend ostream& operator<<(osream&,demo&); friend istream& operator>>(isream&,demo&); }; Ostream & operator<<(osream& …,demo& …) { … } Istream & operator>>(isream& …,demo& …) { … } Commentaire - - Les deux opérateurs << et >> sont surdéfinis en tant que fonctions amies de la classe ‘demo’, de cette manière, ils pourront accéder à tous ses membres. Dès que l’opérateur << sera utilisé avec comme opérande de gauche un objet de type ‘ostream’ (ou dérivé) et comme opérande de droite, un objet de type ‘demo’ ce sont les instructions de la fonction operator << qui seront exécutées. L’opérateur >> est utilisé d’une façon analogue à celle de l’opérateur <<. Les opérateurs << et >> peuvent être surdéfinis pour des objets dynamiques. Programmation Orientée Objet en Langage C++ 67 M.E Exemple class voiture { char marque[20]; char modele[20]; char prix[20]; public: voiture(char *ma="",char *mo="",char *pr="") { strcpy(marque,ma); strcpy(modele,mo); strcpy(prix,pr); } friend ostream& operator<<(ostream&,voiture&); friend istream& operator>>(istream&,voiture&); }; //-------------------------------------------------------ostream & operator<<(ostream& out,voiture& v) { out.setf(ios::left,ios::adjustfield); out<<"Marque : "<<v.marque; out<<"\tModèle : "<<v.modele; out<<"\tPrix : "<<v.prix<<endl; return out; } //-------------------------------------------------------istream & operator>>(istream& in,voiture& v) { cout<<"Marque : "; in.getline(v.marque,sizeof(v.marque)); cout<<"Modele : "; in.getline(v.modele,sizeof(v.modele)); cout<<"Prix : "; in.getline(v.prix,sizeof(v.prix)); return in; } //----------------------------------------------------------main() { voiture v1("Mercedes","CLK 500","19900DH"); cout<<v1; cout<<"Saisir la marque, le modele et le prix de voiture\n"; cin>>v1; cout<<"\nAffichage"<<endl; cout<<v1; voiture v2; cout<<"Saisir la marque, le modele et le prix de voiture\n"; cin>>v2; cout<<"\nAffichage"<<endl; cout<<v2; cout<<v1<<v2; } Exécution Programmation Orientée Objet en Langage C++ 68 M.E Remarque Dans le cas d’un pointeur vers la classe voiture, il faut redéfinir les opérateurs << et >> comme par exemple : ostream& operator<<(ostream& out,voiture *v) { out.setf(ios::left,ios::adjustfield); out<<"\tMarque : "<<v->marque; out<<"\tModèle : "<<v->modele; out<<"\tPrix : "<<v-> prix<<endl; return out; } 5. Lire à partir d’un fichier ou écrire dans un fichier Il suffit de créer un objet de type ofstream pour écrire dans un fichier et un objet de type ifstream pour lire un fichier. Ces deux classes sont déclarées dans le fichier ‘fstream.h’ et elles dérivent respectivement des classes ostream et istream Exemple 1 Extrait d’un programme qui permet d’écrire dans un fichier. ofstream fs("c:\\demo.txt"); if(!fs) { cout<<"Erreur d'écriture dans ce fichier ! \a\a"; return 1; } fs<<"valeur de la donnée: "; fs<<17.12; fs<<"Fin"; fs.close(); … Commentaire Programmation Orientée Objet en Langage C++ 69 M.E - Un objet ‘fs’ de type ostream est créé pour accéder en écriture au fichier “c:\\demo.txt“. En cas de succès de l’ouverture du fichier ‘demo.txt’, les données sont écrites vers le flux de fichier ‘fs’. Exemple 2 Extrait d’un programme permettant de lire un fichier ifstream fe("c:\\demo.txt"); if(!fe) { cout<<"Erreurs d'ouverture ! \a\a"; return 1; } char texte1[30]; double x; char texte2[30]; fe>>texte1; fe>>x; fe>>texte2; cout<<texte1<<x<<texte2; fe.close(); … Commentaire - Un objet ‘fe’ de type ‘ifstream’ est créé pour accéder en lecture au fichier ‘demo.txt’. Le flux ‘fe’ est utilisé pour récupérer les données qui sont affichées ensuite sur l’écran. Remarque La fonction close, qui permet de fermer un fichier, est appelée automatiquement par le destructeur des classes ofstream et ifstream. Exercice Ecrire un programme permettant de rassembler les deux extraits. Programmation Orientée Objet en Langage C++ 70 M.E Le programme : main() { // Ecriture dans le fichier ofstream fs("c:\\Exemple\\demo.txt"); if(!fs) { cout<<"Erreur d'écriture dans le fichier ! \a\a"; getch(); return 1; } else { cout<<"Fichier cree...\n"; } fs<<"valeur-de-la-donnée-:*"<<endl; fs<<17.12<<endl; fs<<"*Fin"; fs.close(); // Lecture du fichier ifstream fe("c:\\Exemple\\demo.txt"); if(!fe) { cout<<"Erreur de lecture du fichier ! \a\a"; getch(); return 1; } else { cout<<"Fichier ouvert...\n"<<endl; } char texte1[30]; char texte2[30]; double x; fe>>texte1; fe>>x; fe>>texte2; cout<<texte1<<x<<texte2; fe.close(); getch(); } Exécution Programmation Orientée Objet en Langage C++ 71 M.E Chapitre 10 : Les templates Les templates (appelées aussi modèles ou patrons) représentent une technique favorisant la réutilisation et la spécialisation des fonctions et des classes. Les templates utilisent un type paramètré (ou fictif) qui représente le type de donnée qui sera choisi (ou instancié) à l’utilisation du modèle. 1. Les fonctions templates Une fonction template permet de définir une fonction qui réalisera le même traitement pour des types de données différents (type de donnée paramétré qui sera connu au moment de l’appel de la fonction). Syntaxe template<arguments du template> type fonction(arguments) { ………………… } Ou template<arguments du template> type { ………………… } fonction(arguments) Description - ‘arguments du template’ : s’écrit ‘class type1, type2, …’ où type1, type2... représentent les types paramétrés (par convention on prend la lettre T, U …). La fonction déclarée peut utiliser ensuite le ou les types fictifs. Exemple Les fonctions surchargées suivantes : void affiche(short v) { cout<<"Valeur : "<<v<<endl; } //---------------------------void affiche (float v) { cout <<"Valeur : "<<v<<endl; } //-------------------------------void affiche (double v) { cout <<"Valeur : "<<v<<endl; } peuvent être remplacées par la fonction modèle suivante : template <class T> void affiche (T v) { cout <<"Valeur : "<<v<<endl; } Programmation Orientée Objet en Langage C++ 72 M.E Et on peut faire des appels tels que : main() { short n=15; float x=3.7; double y=5.14; affiche(n); affiche(34); affiche(x); affiche(5.8); affiche(y); affiche(‘A’); affiche("BONJOUR"); getch() ; } Exécution Remarque Une fonction template peut employer plusieurs arguments : template <class T,class U> void affiche(T v1,U v2) { cout <<"Valeur 1 : "<<v1<<endl; cout <<"Valeur 2 : "<<v2<<endl; } Exercice Définir une fonction template ou modèle appelée ‘max’ dont l’objectif est de renvoyer le plus grand des deux éléments passés en argument. 2. Les classes templates Syntaxe template <arguments du template> class nom_de_la_classe { ………………; ………………; }; Programmation Orientée Objet en Langage C++ 73 M.E Description C++ impose pour définir le corps des fonctions membres d’employer la syntaxe suivante : template <arguments du template> type nom_de_la_classe<arguments>::fonction(arguments) { ………………… ………… } - Une classe modèle ou template peut également contenir des fonctions membres qui n’utilisent pas les types paramétrés. Cependant, même dans ce cas, le corps de la fonction doit respecter la contrainte liée au nom complet de la fonction membre. Exercice 1 Reprendre l’exemple de la classe ‘tableau’ et écrire un programme global à partir des déclarations suivantes : template <class T> class tableau { int ne; T *p; public : tableau(int n){ p=new T[ne=n];} T & operator[](int n){ return p[n]; } ~tableau(){delete []p;} }; //--------------------------------------main() { tableau<int> t1(5); for(int i=0;i<5;i++) t1[i]=i+1; for(int i=0;i<5;i++) cout<<t1[i]<<"\t"; cout<<endl; //----------------------------------tableau<float> t2(7); for(int i=0;i<7;i++)t2[i]=i*2.3; for(int i=0;i<7;i++)cout<<t2[i]<<"\t"; cout<<endl; //----------------------------------------------------tableau<char> t3(8); for(int i=0;i<8;i++)t3[i]='A'+i; for(int i=0;i<8;i++)cout<<t3[i]<<"\t"; cout<<endl; getch(); } Programmation Orientée Objet en Langage C++ 74 M.E Exécution Exercice 2 Redéfinir la classe ‘point’ pour des coordonnées de type paramétré (short, int ou long) et donner des exemples d’instanciation de points. Programme template <class U> class point { U x; U y; public : point(U a,U b){x=a; y=b;} void affiche(){cout<<"X="<<x<<" - Y="<<y<<endl;} }; //----------------------------------------------------------main () { point<int> p1(10,20); p1.affiche(); point<double> p2(23.45,20.76); p2.affiche(); point<float> p3(1.98,2.907); p3.affiche(); point<char> p4('F','K'); p4.affiche(); } Exécution Programmation Orientée Objet en Langage C++ 75 M.E Chapitre 11 : Gestion des exceptions De nombreux problèmes peuvent survenir pendant l’exécution d’un programme, l’insuffisance de mémoire, la perte d’un fichier, la saisie non valide d’une valeur sont des exemples d’erreurs. Le rôle d’un programme consiste à prévoir ces erreurs, à en informer les utilisateurs et éventuellement à mettre en œuvre des solutions de reprise et de correction de ces erreurs d’exécution. C++ propose, à part l’utilisation de la valeur de retour des fonctions, une nouvelle solution pour intercepter les erreurs : le mécanisme des exceptions. 1. Gestion des erreurs en utilisant les valeurs de retour des fonctions int { positive(int v) if(v<0) return 0; cout<<«Valeur positive \n»; return 1 ; } //--------------------------int inf(int v,int max) { if(v>max) return 0; cout<<"Valeur inférieure à : "<<max<<endl; return 1; } //----------------------------------------------------------int sup(int v,int min) { if(v<min) return 0; cout<<"Valeur supérieure à : "<<min<<endl; return 1; } //-----------------------------------------------------------int main() { int minimum=10; int maximum=100; int n,retour; cout<<"Saisir une valeur entière : "; cin>>n; retour=positive(n); if(retour==1) { retour=inf(n,maximum); if(retour==1) { retour=sup(n,minimum); if(retour==1) { cout<<"Valeur correcte !"; return 1; } else { Programmation Orientée Objet en Langage C++ 76 M.E cout<<"Valeur inférieure à : "<<minimum; return 0; } } else { cout<<"Valeur supérieure à : "<<maximum<<endl; return 0; } } else { cout<<"Valeur négative !\n"; return 0; } } Remarque Dans ce programme le contrôle d’erreurs est alourdie par les ‘if’ imbriqués. Un constructeur ne peut pas renseigner sur sa valeur de retour, qui n’en possède pas, pour indiquer qu’une erreur s’est produite. 2. Mise en œuvre des exceptions Pour mettre en œuvre des exceptions sans se baser sur les valeurs de retour des fonctions, on doit : - Définir une classe d’exception. Lancer l’exception (throw). Intercepter l’exception. 2.1. Définir une classe d’exception Une classe d’exception correspond à une classe C++ qui peut fournir des informations sur une erreur. Exemple class erreur { ……………… }; Remarque On peut ajouter à la classe ‘erreur’ autant de données et de fonctions membres. Programmation Orientée Objet en Langage C++ 77 M.E 2.2. Lancer l’exception Toute fonction qui souhaite lancer une exception doit utiliser un nouvel opérateur du langage C++ throw, suivi par un objet créé à partir d’une classe d’exception, cet opérateur permet de quitter la fonction qui l’utilise et d’informer la fonction appelante qu’une exception à été générée. Exemple void positive(int v) { if(v<0) { erreur er; throw er; } cout<<"Valeur positive \n"; } Remarque Si ‘v’ est < à 0 la fonction construit un objet statique de la classe d’exception ‘erreur’ et envoie cet objet avec l’opérateur throw qui ressemble à return. La fonction précédente peut être simplifiée : void positive(int v) { if(v<0) throw erreur(); cout<<"Valeur positive \n "; } Commentaire Il y a création d’un objet de type ‘erreur’ sans nom, cette solution n’est intéressante que si on n’a pas besoin d’appeler des fonctions membres de la classe d’exception. Remarque Toute fonction susceptible d’envoyer un ou plusieurs exceptions peut l’indiquer dans sa déclaration juste après la liste de ces arguments. Exemple void contrôle(int v) { …………… ……………………… } throw (erreur,invalid) Programmation Orientée Objet en Langage C++ 78 M.E 2.3. Intercepter l’exception Pour intercepter une exception, C++ fournit une syntaxe basée sur l’utilisation des blocs try et d’un ou plusieurs blocs catch : try { //Appels de fonctions pouvant générer des erreurs } catch (classe d’exception) { //Ce bloc s’exécute pour une exception de type //‘classe d’exception’ } catch(…) { //Ce bloc s’exécute pour toutes les exceptions //en dehors de la classe d’exception } Description Si une exception est envoyée par une de ces fonctions appelées dans le bloc ‘try’, le mécanisme d’exception entraînera les étapes suivantes : - Tous les objets créés dans le bloc ‘try’ sont détruits’ Le programme sort du bloc ‘try’ après la fonction qui a entraîné l’exception et n’exécute pas les instructions situées après cette fonction. Le C++ exécute dans l’ordre soit le bloc ‘catch’ correspondant à l’exception interceptée si elle existe, soit le bloc ‘catch(…)’ si aucune de ces conditions n’est remplie, cela entraîne la fin de l’exécution du programme, si un des blocs ‘catch’ a été utilisé, le programme continu à exécuter les instructions situées après ce bloc s’il y en a. Remarque Dans un bloc ‘catch’ on peut par exemple : - arrêter le programme avec ou sans message utilisateur. corriger le problème avec ou sans message utilisateur uniquement informer l’utilisateur. Programmation Orientée Objet en Langage C++ 79 M.E Exemple 1. class erreur { }; 2. void positive(int v) { if(v<0) throw erreur(); cout<<"Valeur positive \n"; } void inf(int v,int max) { if(v>max) throw erreur(); cout<<"Valeur inférieure à : "<<max<<endl; } void sup(int v,int min) { if(v<min) throw erreur(); cout<<"Valeur supérieure à : "<<min<<endl; } //-------------------------------------------------------main() { int minimum=10; int maximum=100; 3. try { int n; cout<<"Saisir une valeur entière : "; cin>>n; positive(n); inf(n,maximum); sup(n,minimum); cout<<"Valeur correcte !\n"; } catch(erreur er) { cout<<"valeur incorrecte! \n"; } catch(...) { cout<<"Erreur inconnue !\n"; } } Exercice Toutes les fonctions de l’exemple précédent utilisent la même classe d’exception ‘erreur’. Proposer plusieurs classes d’exception chacune responsable de l’affichage d’un message d’erreur. Programmation Orientée Objet en Langage C++ 80 M.E Solution class erreur_positive { }; class erreur_inf { }; class erreur_sup { }; void positive(int v) { if(v<0) throw erreur_positive(); cout<<"Valeur positive \n"; } void inf(int v,int max) { if(v>max) throw erreur_inf(); cout<<"Valeur inférieure à : "<<max<<endl; } void sup(int v,int min) { if(v<min) throw erreur_sup(); cout<<"Valeur supérieure à : "<<min<<endl; } main() { int minimum=10; int maximum=100; try { int n; cout<<"Saisir une valeur entière : "; cin>>n; positive(n); inf(n,maximum); sup(n,minimum); cout<<"Valeur correcte !\n"; } catch(erreur_positive er) { cout<<"Erreur ! valeur negative ! \n"; } catch(erreur_inf er) { cout<<"Erreur ! valeur superieure A : "<<maximum<<endl; } catch(erreur_sup er) { cout<<"Erreur ! valeur inferieure A : "<<minimum<<endl; } catch(...) { cout<<"Erreur inconnue !\n"; } } Programmation Orientée Objet en Langage C++ 81 M.E Exécution Prolongement Afficher les messages d’erreurs à l’aide des fonctions membres des classes d’exception. class erreur_positive { public : void affiche(){cout<<"Erreur ! valeur negative ! \n";} }; class erreur_inf { int maxi; public: erreur_inf(int ma){maxi=ma;} void affiche() {cout<<"Erreur ! valeur superieure A : "<<maxi<<endl;} }; class erreur_sup { int mini; public: erreur_sup(int mi){mini=mi;} void affiche() {cout<<"Erreur ! valeur inferieure A : "<<mini<<endl;} }; void positive(int v) { if(v<0) { erreur_positive er; throw er;} cout<<"Valeur positive \n"; } Programmation Orientée Objet en Langage C++ 82 M.E void inf(int v,int max) { if(v>max) { erreur_inf er(max); throw er;} cout<<"Valeur inférieure à : "<<max<<endl; } void sup(int v,int min) { if(v<min) { erreur_sup er(min); throw er;} cout<<"Valeur supérieure à : "<<min<<endl; } //-------------------------------------------------------main() { int minimum=10; int maximum=100; try { int n; cout<<"Saisir une valeur entière : "; cin>>n; positive(n); inf(n,maximum); sup(n,minimum); cout<<"Valeur correcte !\n"; } catch(erreur_positive e) { e.affiche(); } catch(erreur_inf e) { e.affiche(); } catch(erreur_sup e) { e.affiche(); } catch(...) { cout<<"Erreur inconnue !\n"; } } Programmation Orientée Objet en Langage C++ 83 M.E Exécution Remarque Les exceptions sont symbolisées par des classes dans lesquelles on peut intégrer les notions d'héritage et de polymorphisme. 3. Hiérarchie des classes d’exception Reprendre le programme précédent en tenant compte de l’héritage et du polymorphisme. Programme class erreur { public: virtual void affiche(){cout<<"Erreur !";} }; class erreur_positive:public erreur { public : void affiche(){cout<<"Erreur ! valeur negative ! \n";} }; class erreur_inf:public erreur { int maxi; public: erreur_inf(int ma){maxi=ma;} void affiche() {cout<<"Erreur ! valeur superieure A : "<<maxi<<endl;} }; Programmation Orientée Objet en Langage C++ 84 M.E class erreur_sup:public erreur { int mini; public: erreur_sup(int mi){mini=mi;} void affiche() {cout<<"Erreur ! valeur inferieure A : "<<mini<<endl;} }; //----------------------------------------------------------void positive(int v) { if(v<0) { erreur_positive *er; er=new erreur_positive; throw er;} cout<<"Valeur positive \n"; } void inf(int v,int max) { if(v>max) { erreur_inf *er; er=new erreur_inf(max); throw er;} cout<<"Valeur inférieure à : "<<max<<endl; } void sup(int v,int min) { if(v<min) { erreur_sup *er; er=new erreur_sup(min); throw er;} cout<<"Valeur supérieure à : "<<min<<endl; } //-------------------------------------------------------main() { int minimum=10; int maximum=100; try { int n; cout<<"Saisir une valeur entière : "; cin>>n; positive(n); inf(n,maximum); sup(n,minimum); cout<<"Valeur correcte !\n"; } catch(erreur *e) { e->affiche(); delete e ; } catch(...) { cout<<"Erreur inconnue !\n"; } } Programmation Orientée Objet en Langage C++ 85 M.E Exécution Programmation Orientée Objet en Langage C++ 86 M.E Références bibliographiques - La bible du programmeur C/C++, Kris Jamsa et Lars Klander, Editions G. Reynald, 1999. - Comment programmer en C++, Deitel et Deitel, Editions G. Reynald, 2001. - Apprendre le C++, C. Delannoy, Editions Eyrolles, 2007. - Exercices en langage C++, C. Delannoy, Editions Eyrolles, 2007. - C++ pour les programmeurs C, C. Delannoy, Editions Eyrolles, 2007. Programmation Orientée Objet en Langage C++ 87 M.E