• Aucun résultat trouvé

[PDF] Outils de base pour programmer facilement avec le langage C++ | Cours informatique

N/A
N/A
Protected

Academic year: 2021

Partager "[PDF] Outils de base pour programmer facilement avec le langage C++ | Cours informatique"

Copied!
87
0
0

Texte intégral

(1)

Support de cours

Mohamed EL WAFIQ Version 2017

Programmation Orientée Objet

en Langage C++

(2)

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.

(3)

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

(4)

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

(5)

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; }

(6)

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.

(7)

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.

(8)

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.

(9)

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.

(10)

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.

(11)

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(); }

(12)

- 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

(13)

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; }

(14)

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; }

(15)

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; ………. ……… ……….. };

(16)

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; }

//---

(17)

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();

(18)

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;} }

(19)

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();

}

(20)

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 : ″); }

(21)

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() { ……….. ; ………….. ; }

(22)

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 *);

};

(23)

int point::coincide(point *O) {

if(x==O->x) && (y==O->y) return 1; return 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; }

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

(24)

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(); }

(25)

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 p1(2, 7) ; est une déclaration correcte.

- point 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; } ...

(26)

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

(27)

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); }

(28)

- 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 n=2; int m=3*n-7;

int 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 :

(29)

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; }

(30)

~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;

}

(31)

{

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();

}

(32)

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″;

(33)

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 B; class 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

(34)

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.

(35)

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

(36)

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(); }

(37)

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); o1.affiche(); point o2(40,50); o2.affiche();

point o3; o3.affiche();

o3=o1+o2; o3.affiche();

o3=o3+o1+o2; o3.affiche();

(38)

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é Opérateur Associativité

Binaire ( )◊ [ ]

Unaire + - ++ -- ! & new◊ delete

Binaire * / % 

Binaire + - 

Binaire << >> 

Binaire < <= > >= 

Binaire == != 

Binaire & (niveau bit) 

Binaire ^ (ou exclusif) 

Binaire || 

Binaire && 

Binaire | (niveau bit) 

Binaire =◊ += -= *= /= %= &= ^= |= <<= >>=

Binaire , 

: 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).

(39)

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;} };

(40)

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.

(41)

 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(); }

(42)

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

(43)

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.colorer(7); p.affiche();

p.deplace(1,2); p.affiche();

}

(44)

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; }

(45)

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 point {

int couleur; 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(); }

(46)

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.

(47)

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();

(48)

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.

(49)

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 :

Héritage simple

Héritage complexe

7. Conversion d'un objet dérivé dans un objet d'un type de base Si on a :

point o1;

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

- ……… A B D E F C G H A C D B

(50)

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

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";}

point

pointcolore

(51)

~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 :

Donc les déclarations suivantes : class A { int x,y; ……… }; class B:public A { ……… }; class C:public A { ……… };

class D:public B, public C { ……… };

A

D

(52)

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

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

A D B C D B C A1 A2

(53)

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

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’).

B E C D A liste_point liste point

(54)

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;

(55)

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; //---

(56)

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();

}

(57)

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; }

};

Références

Documents relatifs

I, Elda de Carvalho, as the coordinator of Moris Foun Group, which weave Tais in Uani Uma Village, declare that our group is surely ready that we agree to the government’s program and

dans la balise du paragraphe (méthode sans fichier css) pour changer la couleur du mot test en rouge… puis affecter la classe soustitrecolor à la balise. &lt;p&gt; en

Ecrire une fonction ´ int simul(int m) qui compare le nombre de couleurs utilis´ ees par l’algo exact et par DSATUR pour colorier m graphes al´ eatoires pour n et p fix´

a - Choisir des 7 mots de telle sorte qu'ils ontiennent tous les aratères aentués possibles. b - Erire une page HTML ontenant une ou deux phrases onstitués des

[r]

[r]

[r]

[r]