Service d’Informatique - Université de Mons · préparation au master d’Informatique et...

73
Service d’Informatique Mohammed Benjelloun 2 ème Bachelier FACULTE POLYTECHNIQUE DE MONS et Programmation Objet Année académique 2008-2009

Transcript of Service d’Informatique - Université de Mons · préparation au master d’Informatique et...

Page 1: Service d’Informatique - Université de Mons · préparation au master d’Informatique et Gestion. Nous renvoyons, néanmoins le lecteur désireux d'en savoir plus à l'un des

Service d’Informatique

Mohammed Benjelloun

2ème Bachelier

FACULTE POLYTECHNIQUE DE MONS

et Programmation Objet

Année académique 2008-2009

Page 2: Service d’Informatique - Université de Mons · préparation au master d’Informatique et Gestion. Nous renvoyons, néanmoins le lecteur désireux d'en savoir plus à l'un des

M.BENJELLOUN Info IIInfo IIInfo IIInfo II S. Informatique

Table des matières

Avant-propos-------------------------------------------------------------------------- 1

Chap 1 : Les structures et fonctions membres ---------------------------------------- 3

1.1. Principes des structures------------------------------------------------------------------------------------3

1.2. Structures et fonctions membres ------------------------------------------------------------------------4

ExercicesExercicesExercicesExercices-------------------------------------------------------------------------------------------------------6

Chap 2 : Les listes simplement chaînées---------------------------------------------- 7

2.1. Généralités ---------------------------------------------------------------------------------------------------7

2.2. Listes Simplement chaînées -----------------------------------------------------------------------------7

ExercicesExercicesExercicesExercices----------------------------------------------------------------------------------------------------- 10

Chap3 : Les Classes -----------------------------------------------------------------12

3.1. Classes et objets-------------------------------------------------------------------------------------------- 12

3.2. Concept d’encapsulation -------------------------------------------------------------------------------- 13

3.3. Création d'objets------------------------------------------------------------------------------------------- 14

3.4. Affectation d’objets --------------------------------------------------------------------------------------- 16

3.5. Déclaration d’une fonction membre ----------------------------------------------------------------- 16

3.6. Notions de constructeur et de destructeur ---------------------------------------------------------- 20

3.6.1. Constructeurs par défaut ------------------------------------------------------------------------------------------ 21

3.6.2. Constructeur de copie ---------------------------------------------------------------------------------------------- 27

3.6.3. Listes d’initialisation des constructeurs ------------------------------------------------------------------------- 29

3.6.4. Affectation d’un objet par un autre de la même classe --------------------------------------------------------- 29

3.7. Tableau d’objets ------------------------------------------------------------------------------------------- 30

3.8. Surcharge des opérateurs-------------------------------------------------------------------------------- 32

ExercicesExercicesExercicesExercices----------------------------------------------------------------------------------------------------- 34

Chap4 : Les patrons et amis ---------------------------------------------------------36

4.1. Patrons de fonctions -------------------------------------------------------------------------------------- 36

4.2. Classe template : patron de classes ------------------------------------------------------------------- 37

4.3. Utilisation d’un patron de classes--------------------------------------------------------------------- 39

Page 3: Service d’Informatique - Université de Mons · préparation au master d’Informatique et Gestion. Nous renvoyons, néanmoins le lecteur désireux d'en savoir plus à l'un des

M.BENJELLOUN Info IIInfo IIInfo IIInfo II S. Informatique

4.4. Fonctions et classes amies------------------------------------------------------------------------------- 41

4.4.1. Fonctions amies ----------------------------------------------------------------------------------------------------- 42

4.4.2. Méthodes amies------------------------------------------------------------------------------------------------------ 42

4.4.3. Classes amies -------------------------------------------------------------------------------------------------------- 43

ExercicesExercicesExercicesExercices----------------------------------------------------------------------------------------------------- 44

Chap5 : L'héritage -------------------------------------------------------------------45

5.1. Classes de base et classes dérivées-------------------------------------------------------------------- 45

5.2. Mode de dérivation --------------------------------------------------------------------------------------- 45

5.3. Héritage public--------------------------------------------------------------------------------------------- 46

5.4. Redéfinition de membres dans la classe dérivée ------------------------------------------------- 49

5.4.1. Redéfinition des fonctions membres ------------------------------------------------------------------------------ 49

5.4.2. Redéfinition des données membres -------------------------------------------------------------------------------- 50

5.5. Héritage et Constructeurs/destructeurs-------------------------------------------------------------- 50

5.6. Polymorphisme -------------------------------------------------------------------------------------------- 53

ExercicesExercicesExercicesExercices----------------------------------------------------------------------------------------------------- 54

Chap6 : La bibliothèque STL--------------------------------------------------------58

6.1. Introduction------------------------------------------------------------------------------------------------- 58

6.2. Les conteneurs---------------------------------------------------------------------------------------------- 58

6.2.1. Vector----------------------------------------------------------------------------------------------------------------- 59

6.2.2. List -------------------------------------------------------------------------------------------------------------------- 60

6.3. Les itérateurs------------------------------------------------------------------------------------------------ 60

6.4. Les algorithmes -------------------------------------------------------------------------------------------- 63

ExercicesExercicesExercicesExercices----------------------------------------------------------------------------------------------------- 68

Syllabus2d_2 : Transparents des séances d’exercices

Page 4: Service d’Informatique - Université de Mons · préparation au master d’Informatique et Gestion. Nous renvoyons, néanmoins le lecteur désireux d'en savoir plus à l'un des

Avant-propos

Ces notes constituent le support des travaux pratiques et des exercices dans le cadre de l'enseignement du cours d'Informatique II de la deuxième année de bachelier à la Faculté Polytechnique de Mons. Il représente aussi une partie du cours Programmation et Algorithmique dispensé aux étudiants d’IG Charleroi. Lors des séances d'exercices, où la présence est obligatoire, un complément de matière sera abordé. Donc le présent document ne peut être considéré comme complet. Ce cours s’appuie sur des notions préliminaires de programmation procédurale. La syntaxe du langage C++ ainsi que les notions fondamentales de programmation ont été étudiées durant le cours d’Informatique I et sont supposées acquises. Durant ce cours, les notions fondamentales de la programmation orientée objet sont abordées. Nous commençons par un rappel sur les structures auxquelles nous introduisons les fonctions membres. Le chapitre 2 peut être considéré comme une brève introduction aux listes simplement chaînées dont le rôle est de remédier aux limitations des tableaux statiques ou dynamiques. Avec le chapitre sur les classes, généralisation de la notion de type défini par l'utilisateur, nous abordons véritablement les possibilités de "programmation (orientée) objets". Nous introduisons la définition d’une classe, la notion de méthode, l’encapsulation et le type de membre (publique, privé, protégé), ainsi que les notions très importantes de constructeur et de destructeur indispensables à ce type de programmation. Nous montrons aussi comment définir une famille de fonctions ou de classes paramétrées par un ou plusieurs types grâce à la notion de patron de fonctions et de classes. Par la suite nous proposons un principe propre à la programmation orientée objet : l'héritage qui permet de créer une nouvelle classe à partir d'une classe existante. Un autre concept des langages objet qui découle directement de l'héritage est le polymorphisme. Pour terminer, nous esquissons la bibliothèque STL (Standard Template Library : Bibliothèque standard générique.) qui est certainement l’un des atouts de C++ et qui peut être considérée comme un outil très puissant. Comme la programmation par l’exemple vaut mieux que tous les beaux discours, nous avons illustré et agrémenté ces notes, comme les Slides des séances d'exercices, de nombreux programmes permettant à l’étudiant d’assimiler les principales notions présentées. Tous les programmes sont fournis complets avec le résultat de leur exécution. Le code source de ces programmes est accessible sur le site Web de la Faculté. Nous ne pouvons qu’insister sur la quasi-nécessité de les tester, et mieux encore de les comprendre et de les modifier. En ce qui concerne les exercices, nous incitons l’étudiant à les programmer, car la programmation est un art qui s’apprend en pratiquant. Vous l’aurez compris, ces notes n’ont pas la prétention d’expliquer en détail tous les concepts du C++, mais se proposent en tant qu’introduction à la programmation orientée objet. Ce syllabus ne peut donc être considéré comme autosuffisant pour apprendre tout le

Page 5: Service d’Informatique - Université de Mons · préparation au master d’Informatique et Gestion. Nous renvoyons, néanmoins le lecteur désireux d'en savoir plus à l'un des

M. BENJELLOUN Info II S. Informatique

2

langage C++. En effet, la matière est vaste et le temps attribué est court. Mais la programmation orientée objet, sera approfondie durant le cours de 3ème année en préparation au master d’Informatique et Gestion. Nous renvoyons, néanmoins le lecteur désireux d'en savoir plus à l'un des très nombreux ouvrages de référence sur le C++, notamment à:

Claude Delannoy Programmer en langage C++, Eyrolles, 2004 ou l’édition 2007 Très bon livre pour commencer le C++, livre très pédagogique et assez complet. Plusieurs chapitres sont dédiés aux composants de la bibliothèque standard du C++.

John R. Hubbard Programmer en C++, Collection Schaum‘s Ediscience, Dunod 2002. Un ouvrage expliquant les concepts de base de la programmation C++ à l'aide d'exemples. On y trouve entre autre: Classes, Surcharge d'opérateurs, Composition et héritage, Modèles et itérateurs, C++ standard et les vecteurs, Classes conteneur.

John R. Hubbard Structures de données en C++, Collection Schaum‘s Ediscience, Dunod 2003. Ce volume est complémentaire au précédent. Près de 455 exercices et problèmes résolus sont décortiqués pour une meilleure compréhension. On y trouve entre autre : Classes, Listes, Tables, Arbres, classes conteneurs standard, algorithmes génériques. Sur le Web : http://cpp.developpez.com/cours/cpp/ Cours, tutoriels, livres électroniques et Docs sur C++ : http://www.developpez.com/c/megacours/book1.html/

Mega Cours C++ en français Je voudrais remercier ici les collègues du Service d'Informatique qui ont pris soin de relire ces notes et de suggérer corrections et améliorations. Je suis bien conscient qu'il reste des erreurs et/ou des imprécisions. Merci au lecteur assidu de les signaler! Si vous notez la moindre erreur ou si vous souhaitez me proposer vos suggestions, n'hésitez pas à le faire à l’adresse E-mail suivante :

[email protected]

Page 6: Service d’Informatique - Université de Mons · préparation au master d’Informatique et Gestion. Nous renvoyons, néanmoins le lecteur désireux d'en savoir plus à l'un des

Chap 1 : Les structures et fonctions membres Ce chapitre est un prolongement du chapitre 8 du syllabus de la 1ère année de bachelier sur l'utilisation des structures en C++. Certaines notions du chapitre 8 vont être rappelées ici, brièvement ; d'autres, complètement nouvelles comme les fonctions membres, seront introduites.

1.1. Principes des structures

Une structure est un ensemble de variables (de types éventuellement différents), définissant un nouveau type sous un seul nom, adapté à une gestion spécifique et à une manipulation facile des données. Une structure se définit par un identificateur suivant le mot-clé struct, ainsi que par une liste de champs ou membres définis par un identificateur d'un type donné. Par exemple, pour gérer les coordonnées d'un point (abscisse et ordonnée) ou des étudiants, on pourra définir les types suivants :

struct point {

int x;

int y;

};

struct Etudiant {

int Id;

string Nom;

};

Quant à x et y, on dit que ce sont des champs ou des membres de la structure point, alors que Nom et Id sont des champs de la structure Etudiant.

On déclare ensuite des variables du type point (Etudiant) par des instructions telles que :

struct point a, b; // a et b deux variables de type structure point

struct Etudiant Etud1, Etud2;

Etudiant Etud[10] ; // Etud est un tableau de 10 éléments de type structure Etudiant

Celle-ci réserve l'emplacement pour des structures nommées a et b de type point, Etud1 et Etud2 de type Etudiant et finalement un tableau de 10 éléments de type Etudiant. L'accès aux membres de a, b ou de Etud1 et Etud2 se fait à l'aide de l'opérateur point (.). Par exemple, a.y désigne le membre y de la structure a et Etud1.Nom désigne le nom du Etudiant Etud1.

Rq

En C++ le mot clé struct n'est pas nécessaire devant la structure lors de la déclaration.

En C++, nous allons pouvoir, dans une structure, associer aux données constituées par ses membres des méthodes qu'on nommera "fonctions membres".

Page 7: Service d’Informatique - Université de Mons · préparation au master d’Informatique et Gestion. Nous renvoyons, néanmoins le lecteur désireux d'en savoir plus à l'un des

Chap 1 : Les structures et fonctions membres

M. BENJELLOUN Info II S. Informatique

4

1.2. Structures et fonctions membres

Supposons qu’en plus de la déclaration des données Id et Nom, nous souhaitions associer à la structure Etudiant deux fonctions :

• void Saisie() : pour saisir l’identifiant et le nom d’un seul Etudiant ; • void Affiche() : pour afficher les information d’un seul Etudiant.

Voici comment nous pourrions éventuellement déclarer la structure Etudiant :

struct Etudiant {

int Id;

string Nom;

void Saisie() ;

void Affiche() ;

};

Dans la déclaration d'une structure, il est permis (mais généralement peu conseillé) d'introduire les données et les fonctions dans un ordre quelconque (nous avons systématiquement placé les données avant les fonctions). Le programme suivant reprend la déclaration du type Etudiant, la définition de ses fonctions membres et un exemple d’utilisation dans la fonction main :

#include <iostream>

#include <string>

using namespace std ;

struct Etudiant {

int Id;

string Nom;

void Saisie() ; // Saisir un élément

void Affiche() ;

};

// ----- Définition des fonctions membres du type Etudiant ----

void Etudiant::Saisie() {

cout << "donnez un identifiant : ";

cin >> Id;

cout << "donnez un nom : " ;

cin >> Nom;

}

void Etudiant::Affiche() {

cout << " Identifiant ="<< Id << endl;

cout << " Son nom : "<< Nom << endl;

}

void main(){

Etudiant Etud3, Etud[3];

cout << "----> Etud012 ---->"<< endl;

for(int i=0 ; i<3 ; i++){

Etud[i].Saisie();

Etud[i].Affiche();

}

cout << "----> Etud3 ---->"<< endl;

Etud3.Saisie(); // accès à la fonction Saisie du Etudiant Etud1

Etud3.Affiche();

}

Programme 1.1.

Page 8: Service d’Informatique - Université de Mons · préparation au master d’Informatique et Gestion. Nous renvoyons, néanmoins le lecteur désireux d'en savoir plus à l'un des

Chap 1 : Les structures et fonctions membres

M. BENJELLOUN Info II S. Informatique

5

SOLUTION

----> Etud012 ---->

donnez un identifiant : 0

donnez un nom : Etud0

Identifiant =0

Son nom : Etud0

donnez un identifiant : 1

donnez un nom : Etud1

Identifiant =1

Son nom : Etud1

donnez un identifiant : 2

donnez un nom : Etud222

Identifiant =2

Son nom : Etud222

----> Etud3 ---->

donnez un identifiant : 33

donnez un nom : ETUD33

Identifiant =33

Son nom : ETUD33

Dans l'en-tête, le nom de la fonction Etudiant::Saisie(), signifie que la fonction Saisie() est celle définie dans la structure Etudiant. En l'absence de ce "préfixe" (Etudiant::), nous définirions effectivement une fonction nommée Saisie(), mais celle-ci ne serait plus associée à Etudiant; il s'agirait d'une fonction "ordinaire" et non plus de la fonction membre de la structure Etudiant.

Rq Dans les fonctions Saisie() et Affiche(), il faut remarquer l’utilisation de Id et du Nom qui ne sont ni des arguments de fonctions ni des variables locales. En fait, ils désignent les membres Id et Nom de la structure Etudiant. L’association est réalisée par Etudiant:: de l’en-tête des fonctions.

Voici un autre exemple utilisant cette fois ci la structure point.

#include <iostream>

using namespace std ;

struct point {

int x ;

int y ;

// Déclaration des fonctions membres (méthodes)

void initialise(int, int) ;

void affiche() ;

} ;

void Affiche2(point A, point B){ // fonction non membre

cout << "\n ....Je suis dans Affiche2 ...."<<"\n" ;

A.affiche();

B.affiche();

}

// --- Définition des fonctions membres du type point ---

void point::initialise (int abs, int ord) {

x = abs ; y = ord ;

}

void point::affiche () {

cout << "Je suis en " << x << " " << y << "\n" ;

}

Page 9: Service d’Informatique - Université de Mons · préparation au master d’Informatique et Gestion. Nous renvoyons, néanmoins le lecteur désireux d'en savoir plus à l'un des

Chap 1 : Les structures et fonctions membres

M. BENJELLOUN Info II S. Informatique

6

void main() {

point a, b, TabPoint[3];

a.initialise (3, 7) ; a.affiche () ;

b.x = 10; b.y = 20 ; b.affiche ();

for(int i=0 ; i<3 ; i++){

TabPoint[i].initialise(i, i+4);

TabPoint[i].affiche();

}

Affiche2(a, b);

}

Programme 1.2.

SOLUTION

Je suis en 3 7

Je suis en 10 20

Je suis en 0 4

Je suis en 1 5

Je suis en 2 6

....Je suis dans Affiche2 ....

Je suis en 3 7

Je suis en 10 20

Rqs • Dans le cas des structures, un appel tel que a.initialise (3,7) ; pourrait être

remplacé par: a.x = 3 ; a.y = 7 ;

• Dans un programme, nous pouvons évidemment mélanger fonctions membres et fonctions non membres.

ExerciceExerciceExerciceExercicessss

1.1. Ecrire un programme qui gère un ensemble de Personnes (Nmax=10). Chaque Personne aura

une structure avec un identifiant Id de type entier et un nom de type string dont la déclaration est:

struct Personne { int Id; string nom; };

Ce programme doit gérer en boucle le menu suivant : 1 : Saisie et Affichage 2 : Ajouter au (début, milieu ou fin) et Affichage 3 : Supprimer le début et Affichage 4 : Tri selon NOM et Affichage 5 : Tri selon Id et Affichage 6 : Quitter

Le menu sera affiché via une fonction, les choix seront traités via l’instruction case. Votre programme utilisera une série de fonctions permettant de séparer les tâches.

1.2. Transformez l’exercice précédent en utilisant dans la structure deux fonctions membres

saisie() et affichage(). Apporter les modifications nécessaires à la structure, aux fonctions et au programme.

Page 10: Service d’Informatique - Université de Mons · préparation au master d’Informatique et Gestion. Nous renvoyons, néanmoins le lecteur désireux d'en savoir plus à l'un des

Chap 2 : Les listes simplement chaînées

2.1. Généralités

Jusqu'à présent, nous n'avons travaillé qu'avec des structures de données statiques, dont l'espace mémoire occupé pouvait être défini avant l'exécution. Dans le cas d’une déclaration d’un tableau de structure par la syntaxe: struct Etudiant Tab[100] ;

Cette déclaration permet de réserver automatiquement une zone mémoire d’une longueur de 100 éléments de type Etudiant. Les cellules du tableau sont alors accessibles par Tab[0] à Tab[99]. Cependant, le problème réside dans le fait que la déclaration du tableau avec une dimension constante fige le nombre d’éléments de ce tableau. Autrement dit, même si un programme n’a besoin que de 20 éléments, on réservera malgré tout 100 cellules de type Etudiant. Comme nous l’avons déjà vu en 1ère Bachelier, nous pouvons remédier à cela en utilisant les pointeurs. Les instructions deviennent dans ce cas-ci :

struct Etudiant *Tab;

Pour pouvoir stocker en mémoire une valeur de type Etudiant, il faut donc explicitement demander une adresse disponible en utilisant le mot réservé new et la libérer avec delete. L’allocation dynamique de la mémoire correspondant au nombre de cellules est maintenant au gré de l’utilisateur. On peut, par exemple, lui demander d’introduire un nombre N, puis nous réservons l’espace mémoire strictement nécessaire.

cout<< " Dimension du Tableau N : " ;

cin >> N;

// Allocation mémoire : new[] Tab = new Etudiant[N];

// Restituer la mémoire : delete[] delete[] Tab;

Supposons qu’à un moment donné dans le programme même, nous n’ayons plus besoin que de (N-5) éléments. La logique, comme nous désirons optimiser la gestion de la mémoire, est de libérer la place de mémoire pour 5 cellules. Mais cela ne peut se faire ni automatiquement ni directement. Donc cela reste bien sûr limitatif. En effet, cela ne permet pas d'exploiter les structures tels que les listes chaînées, les piles, les arbres, structures fondamentales pour de nombreux algorithmes.

2.2. Listes Simplement chaînées

Une liste chaînée est une séquence d'objets dynamiques dont chacun contient une indication explicite vers l'élément suivant. Une liste est faite de cellules. Chaque cellule contient un élément de la liste et l'adresse de l'élément suivant (structure de même type).

Page 11: Service d’Informatique - Université de Mons · préparation au master d’Informatique et Gestion. Nous renvoyons, néanmoins le lecteur désireux d'en savoir plus à l'un des

Chap 2 : Les listes simplement chaînées

M. BENJELLOUN Info II S. Informatique

8

Considérons donc la définition d'une liste simplement chaînée de structures contenant des strings.

NULL

Chaque cellule de la liste chaînée peut être définie comme une structure:

struct Etudiant {

string Nom;

struct Etudiant *next; // Etudiant suivant

};

Chaque cellule comporte donc une zone mémoire pour stocker son contenu (ici string Nom), ainsi qu'une zone mémoire pour stocker un pointeur vers la cellule suivante (c'est-à-dire l'adresse de la cellule suivante).

Pour pouvoir travailler avec une liste simplement chaînée, il faut connaître l'adresse de sa première cellule. Nous définirons donc un pointeur:

struct Etudiant *debut;

Si les tableaux permettent un accès direct aux éléments, les listes chaînées en sont dépourvues. La définition et la nature même de la liste chaînée font apparaître que l’on ne peut parcourir intégralement la liste qu’en partant du premier élément et en visitant les éléments l’un après l’autre. En contrepartie nous gagnons en souplesse lorsqu’il s’agit de modifier la liste. Des opérations telles que : ajout, suppression d’un élément à une position quelconque dans la liste, la suppression avec libération de mémoire d’un nombre quelconque d’éléments consécutifs, …

Voici un programme simple mais complet, dans lequel nous traitons un certain nombre d’opérations sur la liste. Par exemple : l'insertion d'une nouvelle cellule en début de liste, le parcours de la liste pour l’affichage, l’effacement d'une cellule, la libération de la mémoire.

#include <iostream>

#include <string>

using namespace std ;

typedef struct Etudiant{

string Nom;

struct Etudiant *next; // le lien : pointeur sur la cellule suivante

}liste;

//insérer un nouvel élément en tête de liste et retourner un pointeur sur la liste.

liste *insere_en_tete(string x , liste *lis ) {

liste *c;

c = new liste ; // créer une cellule

if (c !=NULL){

c→Nom=x; // assignation de x

c→next=lis; // assignation de suivant: chaîner avec la première cellule de la liste

lis=c; // chaînage: faire pointer la tête de liste vers la nouvelle cellule.

}

return (lis);

}

"Etud1"

"Etud2" "Etud3" "Etud4"

Page 12: Service d’Informatique - Université de Mons · préparation au master d’Informatique et Gestion. Nous renvoyons, néanmoins le lecteur désireux d'en savoir plus à l'un des

Chap 2 : Les listes simplement chaînées

M. BENJELLOUN Info II S. Informatique

9

void parcours (liste *lis) {

liste *p;

p = lis;

cout << "Affichage : "<< endl;

while ( p != NULL) {

cout << p→Nom << endl;

p = p→next;

}

cout <<endl<<endl;

}

void liberation (liste *lis) {

liste *p;

p = lis;

while ( p != NULL) { // si p=NULL, donc fin de liste

lis=lis→next;

delete p;

p = lis;

}

}

void main() {

int i, n;

string Name;

liste *debut;

debut=NULL; // liste vide

cout << "nombre d'elements N: " ;

cin >> n;

//-----------------------

cout << "\n Creer une liste avec insertion en tete de liste + affichage \n";

for(i=1; i<=n; i++){

cout << "\n Nom "<< i << " : ";

cin >> Name;

debut=insere_en_tete(Name , debut);

}

parcours(debut); // Affichage

//-----------------------

cout << "Inserer une nouvelle cellule apres la cellule debut " << endl;

liste *p;

p = new liste;

p→Nom = "Nouveau";

p→next = debut→next;

debut→next = p;

parcours (debut);

//-----------------------

cout << "Supprimer la 1ere cellule " << endl;

liste *q;

q=debut;

debut=debut→next;

delete q; // mémoire dynamique n'est plus utilisée � doit être libérée.

parcours (debut);

//------------------------

// Libération

cout << " Liberation de la memoire ";

liberation (debut);

}

Programme 2.1.

Page 13: Service d’Informatique - Université de Mons · préparation au master d’Informatique et Gestion. Nous renvoyons, néanmoins le lecteur désireux d'en savoir plus à l'un des

Chap 2 : Les listes simplement chaînées

M. BENJELLOUN Info II S. Informatique

10

S

O

L

U

T

I

N

nombre d'elements N: 3

Creer une liste avec insertion en tete de liste + affichage

Nom 1 : premier

Nom 2 : second

Nom 3 : nom3

Affichage :

nom3

second

premier

Inserer une nouvelle cellule apres la cellule debut

Affichage :

nom3

Nouveau

second

premier

Supprimer la 1ere cellule

Affichage :

Nouveau

second

premier

Liberation de la memoire

ExerciceExerciceExerciceExercicessss

2.1. Transformer l’exercice 1.2. en remplaçant le tableau statique de structures

Personne par une liste simplement chaînée.

Page 14: Service d’Informatique - Université de Mons · préparation au master d’Informatique et Gestion. Nous renvoyons, néanmoins le lecteur désireux d'en savoir plus à l'un des

Chap 2 : Les listes simplement chaînées

M. BENJELLOUN Info II S. Informatique

11

2.2. Parcours Vita

Ecrire un programme permettant de manipuler plusieurs listes

dont le nombre est NL< Nmax=15. Il s’agit d’un Parcours

Vita avec NL activités et un nombre de participants

NP<Nmax. Les informations caractérisant chaque participant

dans la liste sont les suivantes: Nom : chaîne de caractères

Code : un caractère (A, B, …)

Créer la (les) structure(s) nécessaire(s) à ce problème.

Les fonctions Saisie(..) et Affichage(..) pour chaque élément

(participant) sont déclarées dans la structure participant.

Ce programme doit gérer en boucle le menu suivant :

1- SaisieL Liste1 et Affichage

2- Liste1 TO Liste2 et Affichage

3- Parcours et Affichage

4- Fin

1- Saisie Liste1 et Affichage:

Est constitué au moins de deux fonctions :

SaisieL (…….) ;

Dans cette fonction on demandera et on testera

le nombre de participants (NP) à saisir et on

effectuera la saisie des participants dans une liste par

insertion en tête. Par exemple si NP=4 et l’utilisateur introduit au clavier :

(NomD D) puis (NomC C) puis (NB B) et enfin (NA A)

La liste est :

Affichage(…….) ; (NA A) --- (NB B) --- (NomC C) --- (NomD D)

2- Liste1 � Liste2 et Affichage:

Ici, comme si les participants sautent, un par un, de la poutre1

(Liste1) à la poutre2 (Liste2). On applique (FIFO), c'est-à-

dire, on retire d’abord le D de Liste1, on insère D dans Liste2

et on affiche liste1 et liste2.

Liste1 : (NA A) --- (NB B) --- (NomC C) Liste2 : (NomD D)

Puis on transfert (NomC C) de Liste1 vers Liste2. Ce dernier

sera inséré dans Liste2 après (NomD D).

Liste1 : (NA A) --- (NB B)

Liste2 : (NomD D) --- (NomC C)

Tant que liste1 n’est pas vide, on continue.

A la fin on aura l’affichage suivant :

Liste1 : Vide

Liste2 : (NomD D) --- (NomC C) --- (NB B) ----(NA A)

Attention : Ici on affiche l’état des deux listes chaque

fois qu’un participant passe de la liste1 à la liste2. Il faut soigner tous les affichages afin de bien suivre et

comprendre les différentes séquences.

3- Parcours et Affichage:

Ici on demandera à l’utilisateur le nombre NL et on y fera

traverser les NP participants comme le montre la figure générale

(début).

On affiche toutes les listes chaque fois que toute l’équipe passe

d’une liste à l’autre. Si par exemple NL=3 on affichera:

Liste1 : (NA A) --- (NB B) --- (NomC C) --- (NomD D)

Liste2 : Vide Liste3 : Vide - - - - - - - - -

Liste1 : Vide

Liste2 : (NomD D) --- (NomC C) --- (NB B) ----(NA A)

Liste3 : Vide - - - - - - - - -

Liste1 : Vide

Liste2 : Vide

Liste3 : (NA A) --- (NB B) --- (NomC C) --- (NomD D)

RQs :

Utilisez les règles de bonnes pratiques étudiées durant les travaux pratiques telles que l’indentation syntaxique, la programmation

modulaire, libérer la mémoire d’un élément supprimé …

!

!

Page 15: Service d’Informatique - Université de Mons · préparation au master d’Informatique et Gestion. Nous renvoyons, néanmoins le lecteur désireux d'en savoir plus à l'un des

Chap3 : Les Classes Le but de ce chapitre est d’introduire les concepts de base des objets. Ainsi, les termes de "programmation (orientée) objets", de classe, d'objet, de méthode, d’encapsulation et type de membre (publique, privé) vont être définis. Ce chapitre n’a pas la prétention d’expliquer en détail ce type de conception, mais se propose d’en introduire les idées maîtresses en préambule à l’étude du langage C++. Afin d’illustrer par la programmation ces concepts, ce chapitre est agrémenté de nombreux programmes permettant à l’étudiant d’assimiler les principales notions et de comprendre quelques subtilités de cette matière. Pour plus de précisions, le lecteur pourra se reporter aux références. Des notions comme l'héritage, le polymorphisme, … seront abordées dans d’autres chapitres de ce syllabus.

3.1. Classes et objets

Une classe définit un regroupement de données et de fonctions ou méthodes. D'un point de vue syntaxique, une classe ressemble beaucoup à la définition d’une structure, il suffit de

• remplacer le mot clé struct par le mot réservé class; • préciser quels sont les membres publics (fonctions ou données) et les membres

privés en utilisant les mots clés public et private. struct Etudiant {

int Id;

string Nom;

void Saisie();

void Affiche();

};

Class Etudiant {

private : // Cachées aux fonctions externes

int Id;

string Nom;

public : // Accessibles depuis l'extérieur de la classe

void Saisie();

void Affiche();

};

La déclaration d’une classe commence par le mot-clé class suivi du nom de la classe et se termine par le point-virgule obligatoire. Le nom de la classe dans cet exemple est Etudiant. On appelle

• objet, une donnée d'un type class, c’est une instanciation d’une classe. Class Etudiant Etud1 ; // Etudiant est une classe, Etud1 est une instance d’un objet.

• fonction membre ou méthode, un membre d'une classe qui est une fonction (Saisie), • donnée membre, un membre qui est une variable (Id et Nom).

Le C++ introduit des concepts de programmation orientée objet. Un des concepts majeurs est l'encapsulation des données.

Page 16: Service d’Informatique - Université de Mons · préparation au master d’Informatique et Gestion. Nous renvoyons, néanmoins le lecteur désireux d'en savoir plus à l'un des

Chap3 : Les Classes

M. BENJELLOUN Info II S. Informatique

13

3.2. Concept d’encapsulation

Dans la classe Etudiant, toutes les fonctions membres sont désignées comme public et toutes les données membres comme private; les premières étant accessibles depuis l’extérieur de la classe, les secondes l’étant seulement depuis la classe. L'association de membres et fonctions au sein d'une classe, avec la possibilité de rendre privées certains d'entre eux, s'appelle l'encapsulation des données, un des concepts majeurs de la programmation orientée objet. Intérêt de la démarche: puisqu'elles ont été déclarées privées, les données membres Id et Nom ne peuvent être modifiées autrement que par un appel de la fonction Saisie().

En effet, l'encapsulation est un mécanisme consistant à rassembler les données et les méthodes au sein d'une classe en cachant l'implémentation de l'objet: cacher l'information contenue dans un objet et ne proposer que des méthodes ou des fonctions de manipulation de cet objet. Ainsi les propriétés contenues dans l'objet seront assurées/validées par les méthodes de l'objet et ne seront plus de la responsabilité de l'utilisateur extérieur. Les fonctions membres doivent pouvoir servir d'interface pour manipuler les données membres. L'utilisateur extérieur ne pourra pas modifier directement l'information et risquer de mettre en péril les propriétés comportementales de l'objet.

L'encapsulation permet donc de garantir l'intégrité des données contenues dans l'objet. On place l'étiquette public devant les fonctions membres dédiées à la manipulation des données membres. En effet, si l'utilisateur de la classe ne peut pas modifier les données membres directement, il est obligé d'utiliser l'interface (les fonctions membres) pour les modifier, ce qui peut permettre au créateur de la classe d'effectuer des contrôles...

Le C++ implémente l'encapsulation en permettant de déclarer les membres d'une classe avec le mot réservé public, private ou protected. Ainsi, lorsqu'un membre est déclaré :

• public, il sera accessible depuis n'importe quelle classe ou fonction. • private, il sera uniquement accessible d'une part, depuis les fonctions qui sont

membres de la classe et, d'autre part, depuis les fonctions autorisées explicitement par la classe (par l'intermédiaire du mot réservé friend (voir plus loin) ).

• protected, il aura les mêmes restrictions que s'il était déclaré private, mais il sera en revanche accessible par les classes héritières (voir plus loin).

Dans la classe Etudiant, les membres nommés Id et Nom sont privés, tandis que les fonctions membres nommées Saisie() et Affiche() sont publiques. Les expressions public: et private: peuvent apparaître un nombre quelconque de fois dans une classe. Les membres déclarés après private: (resp. public:) sont privés (resp. publics) jusqu'à la fin de la classe, ou jusqu'à la rencontre d'une expression public: (resp. private:).

Page 17: Service d’Informatique - Université de Mons · préparation au master d’Informatique et Gestion. Nous renvoyons, néanmoins le lecteur désireux d'en savoir plus à l'un des

Chap3 : Les Classes

M. BENJELLOUN Info II S. Informatique

14

class XY {

private :

...

public :

...

private :

...

} ;

Dans l’exemple suivant, seule la fonction ft() pourra être appelée à partir de n’importe quelle fonction du programme. Par contre il n’est possible d’accéder aux champs x et y que par l’intermédiaire de la fonction membre public ft(). Ainsi l’accès aux différents champs de la classe est contrôlé. class point {

private :

int x, y; // champs membres privés de la classe

void gt( ); // fonction membre privée public :

void ft( ) ; // fonction membre public };

Le C++ n'impose pas l'encapsulation des membres dans leurs classes. On pourrait donc déclarer tous les membres publics, mais en perdant une partie des bénéfices apportés par la programmation orientée objet. Il est de bon usage de déclarer toutes les données privées, ou au moins protégées, et de rendre publiques les méthodes agissant sur ces données. Ceci permet de cacher les détails de l'implémentation de la classe.

Si l'on rend publics tous les membres d'une classe, on obtient l'équivalent d'une structure. Ainsi, ces deux déclarations définissent le même type point :

struct point {

int x, y;

void gt( );

void ft( );

};

class point {

public :

int x, y;

void gt( );

void ft( );

};

Dans une classe, tout ce qui n’est pas déclaré public est privé par défaut. Ainsi, ces deux déclarations sont équivalentes :

class point {

private :

int x, y;

public :

void gt( );

void ft( );

};

class point {

int x, y;

public :

void gt( );

void ft( );

};

Page 18: Service d’Informatique - Université de Mons · préparation au master d’Informatique et Gestion. Nous renvoyons, néanmoins le lecteur désireux d'en savoir plus à l'un des

Chap3 : Les Classes

M. BENJELLOUN Info II S. Informatique

15

3.3. Création d'objets

En C++, il existe deux façons de créer des objets, c'est-à-dire d'instancier une classe : • de façon statique • de façon dynamique

La création statique d'objets consiste à créer un objet en lui affectant un nom, de la même façon qu'avec une variable : Nom_de_la_classe Nom_de_l_objet;

Etudiant Etud1 ; // Etudiant est une classe, Etud1 est une instance de la classe Etudiant.

Ainsi, l'objet est accessible grâce à son nom...

La création dynamique d'objet est une création d'objet par le programme lui-même en fonction de ses « besoins » en objets. La gestion dynamique des objets se fait de la même manière que la gestion dynamique de la mémoire pour des variables simple (int, float, …) ou structures. C’est la manipulation, comme nous l’avons vu en 1ère année Bachelier, des pointeurs en utilisant les opérateurs spécifiques : new, delete, new[] et delete[]. L’opérateur new permet d'allouer de la mémoire, alors que l’opérateur delete la restitue.

La syntaxe de new est très simple, il suffit de faire suivre le mot clé new du type de la variable à allouer, et l'opérateur renvoie directement un pointeur sur cette variable avec le bon type. Par exemple, l'allocation d'un objet Etudiant se fait comme suit :

Etudiant *Etud; // pointeur vers la classe Etudiant

Etud = new Etudiant; // création de l'objet « dynamique » grâce au mot clé new

L’accès aux "membre public" se fait de la manière suivante : Etud�Saisie(); ou (*Etud).Saisie();

Etud�Affiche();

Etud�Nom ;

La syntaxe de delete est plus simple, puisqu'il suffit de faire suivre le mot clé delete du pointeur sur la zone mémoire à libérer :

delete Etud;

Les opérateurs new[] et delete[] sont utilisés pour allouer et restituer la mémoire pour les types tableaux. L'emploi de l'opérateur new[] nécessite de donner la taille du tableau à allouer. Ainsi, on pourra créer un tableau de 500 objets de la manière suivante :

Etudiant *Tableau=new Etudiant[500];

et détruire ce tableau de la manière suivante :

delete[] Tableau;

Tout objet créé dynamiquement, c'est-à-dire avec le mot-clé new devra impérativement être détruit à la fin de son utilisation grâce au mot clé delete. Dans le cas contraire, une partie de la mémoire (celle utilisée par les objets créés dynamiquement) ne sera pas libérée à la fin de l'exécution du programme...

Page 19: Service d’Informatique - Université de Mons · préparation au master d’Informatique et Gestion. Nous renvoyons, néanmoins le lecteur désireux d'en savoir plus à l'un des

Chap3 : Les Classes

M. BENJELLOUN Info II S. Informatique

16

3.4. Affectation d’objets

On a déjà vu que l’on peut affecter à une structure la valeur d'une autre structure de même type. Ainsi, avec les déclarations suivantes :

struct point {

int x, y;

};

struct point A, B;

Nous pouvons écrire sans problème : A = B ; Cette instruction recopie l'ensemble des valeurs des champs de B dans ceux de A. Elle joue le même rôle que : A.x = B.x ;

A.y = B.y ; En C++, cette possibilité d’affectation globale s'étend aux objets de même type. Elle correspond à une recopie des valeurs des membres données, que ceux-ci soient publics ou non. Ainsi, avec ces déclarations (notez qu'ici nous avons prévu, artificiellement, x privé et y public) :

class point {

int x ;

public :

int y ;

};

point A, B ;

L’instruction : B = A ;

provoquera la recopie des valeurs des membres x et y de A dans les membres correspondants de B. Contrairement à ce qui a été dit pour les structures, il n'est plus possible ici de remplacer cette instruction par :

B.x = A.x ;

B.y = A.y ;

En effet, si la deuxième affectation est légale, puisque ici y est public, la première ne l'est pas, car x est privé. Notez bien que l'affectation B = A est toujours légale, quel que soit le statut (public ou privé) des membres données. On peut considérer qu'elle ne viole pas le principe d'encapsulation, dans la mesure où les données privées de B (c'est-à-dire les copies de celles de A) restent toujours inaccessibles de manière directe.

3.5. Déclaration d’une fonction membre

En ce qui concerne la définition des fonctions membres d'une classe, elle se fait de la même manière que celle des fonctions membres d'une structure (qu'il s'agisse de fonctions publiques ou privées). Il existe deux façons de définir ces fonctions membres, en définissant :

• le prototype et le corps de la fonction à l'intérieur de la classe en une opération ; • le prototype de la fonction à l'intérieur de la classe et le corps de la fonction à l'extérieur.

Page 20: Service d’Informatique - Université de Mons · préparation au master d’Informatique et Gestion. Nous renvoyons, néanmoins le lecteur désireux d'en savoir plus à l'un des

Chap3 : Les Classes

M. BENJELLOUN Info II S. Informatique

17

La seconde solution est généralement celle la plus utilisée. Ainsi, puisque l'on définit la fonction membre à l'extérieur de sa classe, il est nécessaire de préciser à quelle classe cette dernière fait partie. On utilise pour cela l'opérateur de résolution de portée, noté ::. Il suffit ainsi de faire précéder le nom de la fonction par ::, suivi du nom de la classe pour lever toute ambiguïté (deux classes peuvent avoir des fonctions membres différentes

portant le même nom...).

Le programme 3.1 montre ce que devient le programme 1.1 lorsque l'on remplace la structure Etudiant par la classe Etudiant. Ce programme permet aussi d’illustrer un exemple de définitions de fonctions à l’intérieur et à l’extérieur de la classe. La fonction Affiche() est définie à l’intérieur de la classe Etudiant, tandis que la déclaration de la fonction Saisie() est à l’intérieur et sa définition est à l’extérieur de la classe.

#include <iostream>

using namespace std ; class Etudiant {

int Id; // déclaration des membres privés

string Nom;

public:

void Saisie() ;

void Affiche(){ // Définir une fonction membre dans la classe

cout << "identifiant ="<< Id << endl;

cout << "Son nom : "<< Nom << endl;

} };

// Définition de la fonction membre à l’extérieur de la classe

void Etudiant::Saisie() {

cout << "donnez un identifiant"<< endl;

cin >> Id;

cout << "donnez un nom"<< endl;

cin >> Nom;

}

void main(){

Etudiant Etud1, Etud2; // 2 objets de la classe Etudiant

Etud1.Saisie();

Etud1.Affiche();

Etud2.Saisie();

Etud2.Affiche();

}

Programme 3.1.

Dans le programme 2.1, Etud1 et Etud2 sont déclarés comme objets de la classe Etudiant. C’est pourquoi ils possèdent leurs propres données membres Id et Nom, et la capacité d’appeler les deux fonctions membres de la classe : Saisie() et Affiche(). Il faut noter qu’une fonction comme Saisie()est appelée en préfixant son nom du nom de son propriétaire : Etud1.Saisie(). Une fonction membre peut être définie dans la classe (Affiche() ) ou à l’extérieur (Saisie() ). Dans la plupart des cas, il est préférable de définir ces fonctions hors de la déclaration de la classe à l’aide de l’opérateur de résolution de portée (Etudiant::Saisie()). Ceci permet

Page 21: Service d’Informatique - Université de Mons · préparation au master d’Informatique et Gestion. Nous renvoyons, néanmoins le lecteur désireux d'en savoir plus à l'un des

Chap3 : Les Classes

M. BENJELLOUN Info II S. Informatique

18

de séparer physiquement les déclarations de fonctions de leurs définitions et donc une meilleure lisibilité du programme.

Rq

Il faut remarquer que les données membres privés " x et y " sont accessibles dans les fonctions membres (Saisie()et Affiche()) sans quelles passent comme arguments de ces fonctions.

A titre d’exemple, voici ce que devient le programme 1.2 lorsque l'on remplace la structure point par la classe point :

#include <iostream>

using namespace std ;

class point {

private :

int x, y;

public :

// Déclaration des fonctions membres (méthodes) void initialise(int, int) ;

void affiche() ;

} ;

void point::initialise (int abs, int ord) {

x = abs ; y = ord ;

}

void point::affiche () {

cout << "Je suis en " << x << " " << y << "\n" ;

}

void main() {

point a, TabPoint[3];

a.initialise (3, 7) ; a.affiche () ;

for(int i=0 ; i<3 ; i++){

TabPoint[i].initialise(i, i+4);

TabPoint[i].affiche();

}

}

Programme 3.2.

SOLUTION

Je suis en 3 7

Je suis en 0 4

Je suis en 1 5

Je suis en 2 6

Rqs

1. Si pour les structures (programme 1.2) un appel tel que a.initialise(3,7) ; pouvait être remplacé par : a.x = 3 ; a.y = 7 ;

Cela n’est plus possible pour les classes. En effet, comme les données membres de point sont privées, on ne peut pas y accéder directement. Il faut absolument passer par une fonction membre publique. Une tentative d'utilisation directe comme a.x = 3; a.y = 7; au sein de la fonction main, conduirait à une erreur de compilation dont le genre de message ressemblerait à :

error C2248: 'x' : cannot access private member declared in class 'point', see declaration of 'x' error C2248: 'y' : cannot access private member declared in class 'point', see declaration of 'y'

2. Dans l’exemple du Programme 2.2, les deux fonctions membres sont publiques. Il est tout à fait possible d'en rendre l’une ou l’autre, voire les deux privées. Dans ce cas, de telles fonctions ne seront plus accessibles de l’"extérieur" de la classe. Elles ne pourront être appelées que par d'autres fonctions membres.

Page 22: Service d’Informatique - Université de Mons · préparation au master d’Informatique et Gestion. Nous renvoyons, néanmoins le lecteur désireux d'en savoir plus à l'un des

Chap3 : Les Classes

M. BENJELLOUN Info II S. Informatique

19

Voici encore deux exemples pour illustrer le fonctionnement et l’accès aux membres privés :

#include <iostream>

using namespace std;

class CRec {

private:

int Long, Larg;

public:

void initialise(int Lo, int La){

Long = Lo;

Larg = La;

}

int Surf() {

return (Long*Larg);

}

};

1

2

3

4

5

6

7

8

9

10-

11-

12

13

14

void main()

{

CRec Rc1, Rc2, Rc3;

Rc1.initialise(10,20) ;

cout <<"\n Surface rectangle 1 = "<< Rc1.Surf();

cout <<"\n Surface rectangle 2 = "<< Rc2.Surf();

// Rc3.Long = 30;

// Rc3.Larg = 24;

// cout <<"\n Surface rectangle 3 = "<< Rc3.Surf(); }

Programme 3.3. L’ajout des lignes 10 et 11 provoquerait des erreurs à la compilation. En effet, comme Long et Larg sont des membres privés de la classe, on ne peut y accéder de l’extérieur qu’à travers les fonctions publiques de la classe. Donc si l’on désire modifier ces valeurs pour Rc3, il faut faire appel à la fonction initialise().

L’exemple suivant attire l’attention sur le mode d’utilisation des membres privés et l’accès à un objet lorsque nous avons plus d’une classe.

class A {

private : int a ;

public : void fonction_a();

};

class B {

private : int b ;

public : void fonction_b();

};

void A:: fonction_a(){ A a0;

B b0;

a=5; //OK: a et fonction_a() dans la classe A

a0.a = 1 //OK : a0 est un objet de la classe A

b0.b = 3 // OK : b est private dans B, donc pas accessible

// dans une fonction de A.

}

Le principe d’encapsulation interdit à une fonction membre d’une classe d’accéder à des données privées d’une autre classe. Nous verrons plus loin comment contourner cela sans perdre le bénéfice de la protection des objets.

Page 23: Service d’Informatique - Université de Mons · préparation au master d’Informatique et Gestion. Nous renvoyons, néanmoins le lecteur désireux d'en savoir plus à l'un des

Chap3 : Les Classes

M. BENJELLOUN Info II S. Informatique

20

3.6. Notions de constructeur et de destructeur

Dans la version actuelle des programmes 2.2 et 2.3, les données membres x, y (class point) et Long, Larg (class CRec) sont initialisées par l'intermédiaire de la fonction membre initialise(). Dans une classe, cette initialisation peut être simplifiée. En effet, lors de la création d'un objet, une fonction membre particulière est exécutée, si elle existe. Cette fonction est appelée constructeur.

Le C++ offre un mécanisme très performant pour traiter ces problèmes : le constructeur. Il s'agit d'une fonction membre d’initialisation (définie comme les autres fonctions membres) qui sera appelée et exécutée automatiquement à chaque création d'un objet. Ceci a pour effet de simplifier la programmation et assurer l’initialisation des données membres.

Parmi les méthodes d'un objet se distinguent deux types de méthodes bien particulières et remplissant un rôle précis dans sa gestion : les constructeurs et les destructeurs.

Le constructeur : • porte le nom de sa classe, • définit l'initialisation d'une instance, notamment en initialisant ses données membres,

• est appelé implicitement à toute création d'instance, • ne peut retourner aucune valeur, (fonction membre non typée même pas void ), • peut admettre des arguments qui sont en fait les valeurs d’initialisation des

différents champs de la variable.

La définition d'un constructeur n'est pas obligatoire lorsqu'il n'est pas nécessaire. Un objet qui n’a pas de constructeur explicite, le compilateur se charge de créer de manière statique les liens entre champs et méthodes. Un objet peut avoir autant de constructeurs que l’on veut (tant qu’ils diffèrent par leur nombre et types d’arguments), ce qui est très intéressant pour initialiser les variables avec différents types. Dans ce cas c’est l’utilisateur qui décidera du constructeur à appeler.

De même, le destructeur est une fonction membre appelée automatiquement au moment de la destruction de l'objet, il :

• est une fonction membre non typée et sans paramètre, • porte le nom de sa classe précédé d'un tilde (~), • n'a pas de type de retour (même pas void), • définit la "désinitialisation" d'une instance, il se charge de détruire l'instance de l'objet,

• est appelé implicitement à toute disparition d'instance.

Attention, il n’y a qu’un seul destructeur un seul destructeur un seul destructeur un seul destructeur par classe !

Page 24: Service d’Informatique - Université de Mons · préparation au master d’Informatique et Gestion. Nous renvoyons, néanmoins le lecteur désireux d'en savoir plus à l'un des

Chap3 : Les Classes

M. BENJELLOUN Info II S. Informatique

21

3.6.1. Constructeurs par défaut

3.6.1.1. Constructeurs par défaut sans arguments

Le premier type de constructeur par défaut est de type : point::point(). C’est un constructeur qui peut être appelé sans paramètre. Son rôle est de créer une instance "non initialisée" quand aucun autre constructeur fourni n’est applicable. Donc il est appelé chaque fois qu'un objet est créé sans qu'il y ait appel explicite d'un constructeur. Comme les autres fonctions membres, les constructeurs peuvent être déclarés dans la classe et définis soit à l’intérieur ou ailleurs. Le programme ci-dessous est un programme simple mettant en évidence les moments où sont appelés respectivement le constructeur et le destructeur d'une classe. Pour garder une trace de leur appel, nous y affichons un message.

#include <iostream>

#include <string>

using namespace std ;

class Etudiant {

private : int Id ; string Nom ;

public:

Etudiant() { // Constructeur

cout << "Dans constructeur"<<endl;

Id = -1 ; Nom = "Vide";

}

~Etudiant() { // Destructeur

cout << "Dans destructeur";

}

void Affiche(){

cout << " Identifiant ="<< Id << endl;

cout << " Son nom : "<< Nom << endl;

}

};

void main(){

Etudiant Etud; // Création d’un objet Etud

Etud.Affiche();

}

Programme 3.4.

Ce programme montre comment on peut définir les fonctions membres, y compris constructeur et destructeur, à l'intérieur de la définition de la classe. Son exécution affichera à l’écran : Dans constructeur

Identifiant =-1

Son nom : Vide

Dans destructeur

En effet, lors de la création de l’objet Etud de la classe Etudiant, le constructeur est appelé, d’où le commentaire "Dans constructeur". Ensuite on rencontre la fonction Affiche() qui montre les valeurs d’initialisation des données membres. Enfin, comme le

Page 25: Service d’Informatique - Université de Mons · préparation au master d’Informatique et Gestion. Nous renvoyons, néanmoins le lecteur désireux d'en savoir plus à l'un des

Chap3 : Les Classes

M. BENJELLOUN Info II S. Informatique

22

programme n’a plus d’instructions à exécuter, il détruit l’objet avant de quitter la fonction main() en appelant le destructeur. 3.6.1.2. Constructeurs par paramètres

Le constructeur peut posséder des paramètres. Pour illustrer le fonctionnement d’un tel constructeur, le programme 3.2 peut être transformé comme suit :

#include <iostream>

using namespace std ;

class point {

private :

int x, y;

public :

// Déclaration des fonctions membres (méthodes)

point(int, int) ; // Constructeur: lors de la création d’1 objet point

// cette fonction sera appelée

void affiche() ;

} ;

point::point (int abs, int ord) {

x = abs ; y = ord ;

}

void point::affiche () {

cout << " Je suis en " << x << " " << y << "\n" ;

}

void main() {

point a(3,7); // création d'objet a et initialisation des données

cout << "Objet a : " << endl;

a.affiche() ;

for(int i=0 ; i<3 ; i++){

point b(i, i+4);

cout << "Objet b (" << i << ") : "<< endl;

b.affiche();

}

cout << "Objet c : " << endl

point c = point(5, 6);

c.affiche() ;

cout << "Objet *Pt: " << endl;

point *Pt;

Pt = new point(8, 9);

Pt->affiche();

delete Pt;

}

Programme 3.5.

Solution

Objet a : Je suis en 3 7 Objet b (0) : Je suis en 0 4 Objet b (1) : Je suis en 1 5 Objet b (2) : Je suis en 2 6 Objet c : Je suis en 5 6 Objet *Pt : Je suis en 8 9

Page 26: Service d’Informatique - Université de Mons · préparation au master d’Informatique et Gestion. Nous renvoyons, néanmoins le lecteur désireux d'en savoir plus à l'un des

Chap3 : Les Classes

M. BENJELLOUN Info II S. Informatique

23

Un constructeur est toujours appelé lorsqu'un objet est créé, soit explicitement, soit implicitement. Les appels explicites peuvent être écrits sous deux formes :

point a(3, 4); // Création de l’objet « a » + appel du constructeur avec des arguments 3 et 4

point c = point(5, 6);

Dans le cas d'un constructeur avec un seul paramètre, on peut aussi adopter une forme qui appelle l'initialisation des variables de types primitifs :

point e = 7; // équivaut à : point e = point(7) ;

Un objet alloué dynamiquement est lui aussi toujours initialisé, au mois implicitement. Dans beaucoup de cas il peut, ou doit, être initialisé explicitement. Cela s'écrit :

point *Pt;

Pt = new point(8, 9);

delete Pt;

Voici deux autres exemples illustrant la définition du constructeur (initialiser, voire créer dynamiquement un objet) par paramètres et le destructeur.

class CRec {

public:

int Long;

int Larg;

CRec(int Lo, int La){

Long = Lo;

Larg = La;

}

~CRec() ;

};

~CRec()::CRec(){

Long = 0; Larg=0;

}

class Etudiant {

private :

int Id ;

char *Nom ;

public:

Etudiant (int id, string Name) {

Id = id ;

Nom = new char[Name.size()+1];

}

~ Etudiant() { // Destructeur if (Nom!=NULL)

delete [] Nom ;

}

};

Cependant, à partir du moment où un constructeur est défini, il doit pouvoir être appelé (automatiquement) lors de la création de l'objet. Dans le programme 2.5 ou dans les deux exemples, les constructeurs point et CRec ont besoin de deux arguments alors que Tab en a besoin d’un. Ceux-ci doivent obligatoirement être fournis dans notre déclaration.

Dans cet exemple : CRec R1(10, 20) ; // Création d’un objet R1, donc lancement du constructeur �

// � R1.Long=10 etR1.Larg=20 ;

Tab T(10) ; // Création d’un objet T : donc réservation d’un espace mémoire pour 10 entiers

CRec R2 ; // Erreur

Tab T1, T2[10]; // Erreur

point Pt ; // Erreur

les déclarations de R2, T1, T2[10] et Pt provoquent des erreurs à la compilation car il ne possèdent pas le nombre nécessaire d’arguments. Ceci montre un exemple où il est nécessaire de fournir à la classe point un constructeur par défaut de type point::point(). La classe peut dès lors être déclarée comme suit:

Page 27: Service d’Informatique - Université de Mons · préparation au master d’Informatique et Gestion. Nous renvoyons, néanmoins le lecteur désireux d'en savoir plus à l'un des

Chap3 : Les Classes

M. BENJELLOUN Info II S. Informatique

24

class point { int x, y ; // déclaration des membres privés

public : // déclaration des membres publics

point () ; // constructeur

point (int, int) ; // constructeur

~point() ; // destructeur

} ;

Il ne reste qu’à définir le destructeur et les différents constructeurs, par exemple :

point::point() { //constructeur par défaut sans param x = 3 ; y = 4 ;

}

point::point (int abs, int ord) {

x = abs ; y = ord ;

}

point :: ~point () {

cout << "Dans destructeur au revoir \n" ;

x=0; y = 0;

}

Dans cette version, la classe point a deux constructeurs. Le premier n’a pas de paramètres et initialise l’objet déclaré à l’aide des valeurs par défaut 3 et 4. Le deuxième a des paramètres que l’utilisateur peut modifier au moment de la déclaration, mais pas de valeurs par défaut. Les deux constructeurs permettent d’initialiser les objets dès leurs déclarations.

point Pt(10, 20); // Création d’un objet Pt, avec initialisation �

// � Pt.x=10 et Pt.y=20 ;

point T; // Création d’un point T, donc lancement du

// Constructeur par défaut � T.x=3 et T.y=4

point T[5]; // produit 5 appels de point()

point *pt = new point[10]; // produit 10 appels de point()

Le programme suivant montre l’utilisation du destructeur et des deux types de constructeurs.

����

#include <iostream>

using namespace std;

class CRec {

public:

int Long, Larg;

CRec (int Lo, int La){

cout << "In Constructeur Param"<< endl ;

Long = Lo; Larg = La;

}

CRec (){

cout << "Dans Constructeur 0000"<< endl ;

}

~CRec (){

cout << "Lancer Destructeur" << endl;

}

int CalcSurf() { return (Long*Larg); }

};

Page 28: Service d’Informatique - Université de Mons · préparation au master d’Informatique et Gestion. Nous renvoyons, néanmoins le lecteur désireux d'en savoir plus à l'un des

Chap3 : Les Classes

M. BENJELLOUN Info II S. Informatique

25

����

����

void main() {

CRec Rc1(10,20), Rc2, Rc3;

cout <<"\n Surface rectangle 1 = "<< Rc1.CalcSurf();

cout <<"\n Surface rectangle 2 = "<< Rc2.CalcSurf();

Rc3.Long = 30;

Rc3.Larg = 24;

cout <<"\n Surface rectangle 3 = "<< Rc3.CalcSurf()<< endl;

}

Programme 3.6.

Solution

Dans Constructeur Param

Dans Constructeur 0000

Dans Constructeur 0000

Surface rectangle 1 = 200

Surface rectangle 2 = 0

Surface rectangle 3 = 720

Lancer Destructeur

Lancer Destructeur

Lancer Destructeur

Rq

Il faut remarquer que dans ce programme, les données Long et Larg sont définies comme public dans la classe CRec. C’est pour cette raison que les initialisations : Rc3.Long = 30; Rc3.Larg = 24;

sont autorisées.

Si la classe est déclarée comme ceci : class CRec {

private:

int Long, Larg;

public :

CRec (int Lo, int La){

….

les assignations suivantes: Rc3.Long = 30;

Rc3.Larg = 24;

provoqueraient des erreurs à la compilation, du genre : error C2248: 'Long' : cannot access private member declared in class 'CRec'

error C2248: 'Larg' : cannot access private member declared in class 'CRec'

Par souci de compréhension et pour mieux maîtriser l’enclenchement du constructeur et du destructeur, voici comment pourrait être adapté le programme 2.5 afin de tracer les appels de ces fonctions:

Page 29: Service d’Informatique - Université de Mons · préparation au master d’Informatique et Gestion. Nous renvoyons, néanmoins le lecteur désireux d'en savoir plus à l'un des

Chap3 : Les Classes

M. BENJELLOUN Info II S. Informatique

26

#include <iostream>

using namespace std ;

class point {

int x, y ;

public :

point (int abs, int ord){

x = abs ; y = ord ;

cout << "++ Construction d'un point :" << x << " " << y << "\n" ;

}

~point(){

cout << "-- Destruction du point : " << x << " " << y << "\n" ;

}

} ;

point a(1,1) ; // instanciation "Globale" d’un objet de classe point

void main(){

cout << "****** Debut main *****\n" ;

point b(10,10) ; // un objet automatique de classe point

point c(3,3) ;

int i ;

for (i=1 ; i<=3 ; i++) {

cout << "** Boucle tour numero " << i << "\n" ;

point b(i,2*i) ; // objets créés dans un bloc

}

cout << "****** Fin main ******\n" ;

}

Programme 3.7.

Solution

++ Construction d'un point : 1 1

****** Debut main *****

++ Construction d'un point : 10 10

++ Construction d'un point : 3 3

** Boucle tour numero 1

++ Construction d'un point : 1 2

-- Destruction du point : 1 2

** Boucle tour numero 2

++ Construction d'un point : 2 4

-- Destruction du point : 2 4

** Boucle tour numero 3

++ Construction d'un point : 3 6

-- Destruction du point : 3 6

****** Fin main ******

-- Destruction du point : 3 3

-- Destruction du point : 10 10

-- Destruction du point : 1 1

Nous demandons aux étudiants de bien comprendre cette solution. Une discussion à ce sujet sera abordée durant les séances d’exercices. Nous traiterons aussi d’autres exemples afin d’illustrer au mieux la trace des appels des constructeurs et destructeurs.

Page 30: Service d’Informatique - Université de Mons · préparation au master d’Informatique et Gestion. Nous renvoyons, néanmoins le lecteur désireux d'en savoir plus à l'un des

Chap3 : Les Classes

M. BENJELLOUN Info II S. Informatique

27

3.6.1.3. Constructeurs par défaut et par paramètres

Le constructeur par défaut est un constructeur qui peut être appelé sans paramètres : ou bien il n'en a pas, ou bien tous ses paramètres ont des valeurs par défaut. Il joue un rôle remarquable, car il est appelé chaque fois qu'un objet est créé sans qu'il y ait appel explicite d'un constructeur. Voici un exemple de constructeur par défaut pour la classe CRec. Il possède des paramètres avec des valeurs de défaut pour l’initialisation.

CRec :: CRec (int Lo = 0, int La = 0){

Long = Lo;

Larg = La;

}

Dans ce cas, la déclaration d’un objet peut se faire avec deux, un ou aucun argument. L’exemple suivant illustre des créations valides pour des objets de type CRec :

CRec X; // OK Equiv : Z.Long=0 ; Z.Larg = 0

CRec Y(1); // OK Equiv : Z.Long=1 ; Z.Larg = 0

CRec Z(1,2); // OK : Equiv : Z.Long=1 ; Z.Larg = 2

En effet, de la même façon qu'une fonction, un constructeur peut être surchargé. Dans l'exemple suivant, un objet de type CRec, peut être créé en passant 0, 1 ou 2 arguments. Si aucun argument n'est passé, Long et Larg sont initialisés à la valeur 0. Si un argument est passé Y(1), cela correspondra à Long=1 et Larg=0. Si 2 arguments sont passés, ils sont utilisés pour initialiser les données membres.

3.6.2. Constructeur de copie

Le constructeur de copie (ou de recopie) est spécialisé dans la création et l’initialisation d’un objet à partir d’un autre objet pris comme modèle. Il est donc appelé lorsqu’on doit créer une instance d’un objet qui est la copie d’un autre objet. Toute classe dispose d'un constructeur de copie par défaut généré automatiquement par le compilateur, dont le seul but est de copier les champs de l'objet à copier un à un dans les champs de l'objet à instancier. Toutefois, ce constructeur par défaut ne suffira pas toujours, et le programmeur devra parfois en fournir un explicitement. Ce sera notamment le cas lorsque certaines données des objets auront été allouées dynamiquement.

Le constructeur de copie sert à créer un objet identique à l’objet reçu en paramètre. C’est un constructeur dont le premier paramètre est de type « C & » (référence sur un C) ou « const C &» (référence sur un C constant) et dont les autres paramètres, s'ils existent, ont des valeurs par défaut.

La syntaxe habituelle d’un constructeur de copie est la suivante :

point::point(const point& Pt) // Pt est l'objet à copier

point(const point& Pt) // Si la définition est dans la classe

Page 31: Service d’Informatique - Université de Mons · préparation au master d’Informatique et Gestion. Nous renvoyons, néanmoins le lecteur désireux d'en savoir plus à l'un des

Chap3 : Les Classes

M. BENJELLOUN Info II S. Informatique

28

Rqs

• Les attributs de l’objet passé en paramètre sont recopiés dans les attributs de l’objet à créer, en prenant garde d’allouer un nouvel espace mémoire pour stocker le nom de l’objet (Pt, RC1, …) ;

• Le constructeur de copie est aussi appelé lorsqu’une fonction retourne un objet (attention: le constructeur de copie n’est pas appelé lorsqu’une fonction retourne une référence à un objet);

• S’il existe un constructeur au moins, toute instance qui naît est soit construite par un constructeur appelé implicitement, soit initialisée par une instance déjà construite.

Pour illustrer l’utilisation de constructeur par copie, nous reprenons la classe Etudiant qui nous permettra par la même occasion de manipuler les pointeurs. Dans ce programme, nous avons effectué quelques modifications afin d’enrichir cette classe.

#include <iostream>

#include <string>

using namespace std ;

class Etudiant {

private :

int Id ;

char *Nom ;

public:

Etudiant (int id, string Name) { // Constructeur

cout << “Constructeur \n”;

Id = id ;

Nom = new char[Name.size()+1];

for (int i=0; i< Name.size(); i++)

Nom[i]=Name[i];

Nom[i]='\0'; // pour fin de chaîne de caractères

}

Etudiant(const Etudiant& ET) {

cout << “Constructeur de copie\n”;

if (ET.Nom) {

Nom = new char[strlen(ET.Nom) + 1];

strcpy(Nom, ET.Nom);

Id = ET.Id;

}

else{

Nom = 0;

Id = 0;

}

}

~ Etudiant() { // Destructeur

if (Nom!=NULL) delete [] Nom ;

}

void Affiche(){

cout << " Identifiant ="<< Id << endl;

cout << " Son nom : "<< Nom << endl;

}

};

void main(){

string Name;

cout << " Name ....: ";

cin >> Name;

Etudiant E1(100, Name);

Page 32: Service d’Informatique - Université de Mons · préparation au master d’Informatique et Gestion. Nous renvoyons, néanmoins le lecteur désireux d'en savoir plus à l'un des

Chap3 : Les Classes

M. BENJELLOUN Info II S. Informatique

29

E1.Affiche();

Etudiant E2(E1); // copie de E1 dans E2 (1)

E2.Affiche();

Etudiant E3=E1; // copie de E1 dans E3 (2)

E3.Affiche();

}

Programme 3.8.

Solution

Name ....: Ghandi

Constructeur

Identifiant =100

Son nom : Ghandi

Constructeur de copie

Identifiant =100

Son nom : Ghandi

Constructeur de copie

Identifiant =100

Son nom : Ghandi

Comme le montre la solution, dans les deux exemples (1) et (2), c'est le constructeur de copie qui est appelé et pas le constructeur normal.

3.6.3. Listes d’initialisation des constructeurs

La plupart des constructeurs ne font qu’initialiser les données membres de l’objet. Le C++ propose un dispositif appelé liste d’initialisation. L’exemple suivant illustre comment on peut réécrire les constructeurs des différents exemples à l’aide d’une liste d’initialisation :

Etudiant() {

Id = -1 ; Nom = "Vide"; }

≅≅≅≅ Etudiant() : Id (-1) , Nom ("Vide") {}

point (int abs, int ord){

x = abs ; y = ord ; } ≅≅≅≅

point (int abs, int ord) :

x (abs) , y (ord) {} CRec (int Lo = 0, int La = 0){

Long = Lo; Larg = La; } ≅≅≅≅

CRec (int Lo = 0, int La = 0) :

Long (Lo) , Larg (La) {}

Les déclarations d’affectation se trouvant dans les corps des fonctions sont supprimées. Leur action est gérée par la liste d’initialisation en gras dans les exemples. Remarquez que la liste commence par deux points et précède le corps des fonctions qui sont maintenant vides.

Page 33: Service d’Informatique - Université de Mons · préparation au master d’Informatique et Gestion. Nous renvoyons, néanmoins le lecteur désireux d'en savoir plus à l'un des

Chap3 : Les Classes

M. BENJELLOUN Info II S. Informatique

30

3.6.4. Affectation d’un objet par un autre de la même classe

Une partie sur l’affectation d’objets a déjà été traitée dans le paragraphe 3.4.(Affectation d’objets) de ce chapitre. Cependant, maintenant que les constructeurs ont été vus, voici un programme véhiculant un complément d’informations sur le sujet.

#include <iostream>

using namespace std;

class CRec {

int Long, Larg; // membres privés

public:

CRec (int Lo, int La){

cout << "\n Dans Constructeur Param"<< endl ;

Long = Lo; Larg = La;

}

CRec (){

cout << "\n Dans Constructeur 0000"<< endl ;

}

int CalcSurf() { return (Long*Larg); }

};

void main() {

CRec Rc1(10,20), Rc2;

int Surface = 0;

Rc2 = Rc1;

Surface = Rc1.CalcSurf();

cout << "\n Surface rectangle 1 = " << Surface;

cout << "\n Surface rectangle 2 = " << Rc2.CalcSurf();

Rc1=CRec(5,15);

cout << "\n Surface rectangle 1 = " << Rc1.CalcSurf();

CRec Rc3=Rc1; // initialisation

CRec Rc4(Rc1); // idem (syntaxe équivalente)

cout << "\n Surface rectangle 3 = " << Rc3.CalcSurf();

cout << "\n Surface rectangle 4 = " << Rc4.CalcSurf();

}

Programme 3.9.

Solution

Dans Constructeur Param

Dans Constructeur 0000

Surface rectangle 1 = 200

Surface rectangle 2 = 200

Dans Constructeur Param

Surface rectangle 1 = 75

Surface rectangle 3 = 75

Surface rectangle 4 = 75

Rq Remarquez que dans cette solution, le constructeur ne s’est pas enclenché pour :

CRec Rc3=Rc1; CRec Rc4(Rc1);

Page 34: Service d’Informatique - Université de Mons · préparation au master d’Informatique et Gestion. Nous renvoyons, néanmoins le lecteur désireux d'en savoir plus à l'un des

Chap3 : Les Classes

M. BENJELLOUN Info II S. Informatique

31

3.7. Tableau d’objets

Un tableau d’objets est défini et manipulé de la même façon qu’un tableau contenant des éléments issus des types fondamentaux. Lors de la création d’un tableau d’objets, un constructeur est appelé pour chaque élément du tableau (implicitement, c’est celui sans paramètre). Lorsque la classe ne définit pas un tel constructeur, il faut nécessairement initialiser chaque élément en indiquant le constructeur à appeler. Si nous considérons la classe CRec définit comme:

class CRec {

int Long, Larg;

public:

CRec (int Lo, int La){

Long = Lo; Larg = La;

}

int CalcSurf() { return (Long*Larg); }

};

La déclaration d’un tableau de trois objets de type CRec par: CRec Tab[3] ;

conduira à une erreur de compilation. En effet, dans ce cas le constructeur sans paramètre sera appelé successivement pour chacun des éléments. Or, dans cet exemple la classe n’en possède pas. Dans ce cas, il faut éventuellement déclarer le tableau comme suit :

CRec Tab[3] = { CRec(1,2),CRec(5,7),CRec(3,9) );

Une autre solution est d’ajouter un constructeur par défaut sans paramètre ou encore remplacer le constructeur de cette classe par un constructeur par défaut et par paramètres.

CRec (int Lo=0, int La = 0){ // constructeur (0, 1 ou 2 arguments)

Long = Lo; Larg = La;

}

Dans ce cas la déclaration CRec Tab[3]; permet d’initialiser à zéro Long et Larg de chaque élément du tableau. Voici un programme illustrant la construction et l’initialisation d’un tableau d’objets.

#include <iostream>

using namespace std ;

class CRec {

int Long, Larg;

public:

CRec (int Lo=0, int La=0){

Long = Lo; Larg = La;

cout <<"++ Constructeur ---> : " << Long << " et "<< Larg<<"\n";

}

~CRec () {

cout <<"-- Destructeur : --->: " << Long << " et "<< Larg<<"\n";

}

};

void main() {

int n = 2 ;

CRec Tab[5] = { CRec(3,4), 9, n, 5*n+2 } ; (1)

cout << "*** fin du programme ***\n" ;

}

Programme 3.10.

Page 35: Service d’Informatique - Université de Mons · préparation au master d’Informatique et Gestion. Nous renvoyons, néanmoins le lecteur désireux d'en savoir plus à l'un des

Chap3 : Les Classes

M. BENJELLOUN Info II S. Informatique

32

Solution

++ Constructeur ---> : 3 et 4

++ Constructeur ---> : 9 et 0

++ Constructeur ---> : 2 et 0

++ Constructeur ---> : 12 et 0

++ Constructeur ---> : 0 et 0

*** fin du programme ***

-- Destructeur : --->: 0 et 0

-- Destructeur : --->: 12 et 0

-- Destructeur : --->: 2 et 0

-- Destructeur : --->: 9 et 0

-- Destructeur : --->: 3 et 4

On peut remarquer que la liste d’initialisation (1) peut comporter moins de valeurs que le tableau n’a d’éléments. Chaque fois qu’un argument d’initialisation est manquant, il est remplacé par la valeur par défaut (zéro dans cet exemple).

3.8. Surcharge des opérateurs

Avec les outils du langage étudiés jusqu'à présent nous pouvons définir des nouvelles classes, créer des objets de ces classes et les manipuler presque comme les objets des types primitifs.

Néanmoins, avec des objets des types primitifs on peut écrire int i, j;

if (i == j) { .….

}

Autrement dit, on peut utiliser les opérateurs arithmétiques et logiques qui sont prédéfinis par le langage. C++ offre au programmeur la possibilité de définir la plupart des opérateurs pour une classe quelconque. Pour illustrer cela, reprenons un programme ou une partie de programme facile comme le programme 2.4. On désire savoir si 2 objets de la classe point sont identiques. Pour répondre à cette question on peut ajouter une fonction membre qui permet de comparer 2 objets de cette classe.

#include <iostream>

#include <string>

using namespace std ;

class point {

private :

int x, y;

public :

// Déclaration des fonctions membres (méthodes)

point(int, int) ; // Constructeur

void affiche(string ) ;

int EstEgal(point ) ;

};

point::point (int abs, int ord) {

x = abs ; y = ord ;

}

void point::affiche (string T) {

cout << T << "Je suis en " << x << " " << y << "\n" ;

}

Page 36: Service d’Informatique - Université de Mons · préparation au master d’Informatique et Gestion. Nous renvoyons, néanmoins le lecteur désireux d'en savoir plus à l'un des

Chap3 : Les Classes

M. BENJELLOUN Info II S. Informatique

33

int point::EstEgal (point Pt){

if ( (x==Pt.x) && (y==Pt.y) ) return 1 ;

else return 0;

}

void main() {

point a(3,7); // création d'objet a et initialisation des données

a.affiche("Objet a : ") ;

point b(4, 4);

b.affiche("Objet b : ") ;

if (a.EstEgal(b)==1)

cout << " Oui EGALITE "<< endl;

else

cout << " Non EGALITE "<< endl;

point c(3, 7);

c.affiche("Objet c : ") ;

if (a.EstEgal(c)==1)

cout << " Oui EGALITE "<< endl;

else

cout << " Pas d EGALITE "<< endl;

}

Programme 3.11.

Solution

Objet a : Je suis en 3 7

Objet b : Je suis en 4 4

Non EGALITE

Objet c : Je suis en 3 7

Oui EGALITE

Mais il est souhaitable de pouvoir comparer ces 2 objets de la même façon que l'on compare 2 objets d'un type primitif.

if ( a == b) ….

else .…

Nous allons donc redéfinir l'opérateur d'égalité "==" pour les objets de la classe point.

int point::operator==(point Pt) { if ( (x==Pt.x) && (y==Pt.y) ) return 1 ;

else return 0;

}

Pour comparer 2 objets de la classe point, on peut utiliser la fonction operator== à la

place de la fonction membre EstEgal : a.operator==(b)

Mais on peut aussi l'utiliser comme dans le cas d'un objet de type primitif : if (a == b).

Les opérateurs qui peuvent être surchargés sont : + - * / % ^ & | ~ ! ' = < > <= ++ - - << >>

== != && || += -= /= %= ^= &= |= *=

<<= >>= [] () -> ->* new delete

La surcharge des opérateurs les plus utilisés vous sera demandée comme exercices.

Page 37: Service d’Informatique - Université de Mons · préparation au master d’Informatique et Gestion. Nous renvoyons, néanmoins le lecteur désireux d'en savoir plus à l'un des

Chap3 : Les Classes

M. BENJELLOUN Info II S. Informatique

34

ExercicesExercicesExercicesExercices

3.1. Transformez l’exercice 1.2. en remplaçant la structure Personne par une classe Personne.

3.2. Transformez l’exercice 2.2. « Parcours Vita » en remplaçant la structure par une classe.

3.3. Ecrivez la classe Etudiant. Le programme doit gérer en boucle

le menu suivant :

a) Saisie d’1 tableau Tab1 d’Etudiant de 1ere + Affichage b) Saisie d’1 tableau Tab2 d’Etudiant de 2ere + Affichage c) Fusion dans un tableau Dynamique les tableaux Tab1 et Tab2 +Affichage

3.4. Comment concevoir le type classe CLAS de sorte que le programme : void main() {

CLAS x; cout << " Salut \n" ; }

fournisse les résultats suivants : Creation Objet Salut Destruction Objet.

3.5. Saisissez le programme ci-dessous et exécutez le. Quelles sont les valeurs des champs de

chaque variable? Que remarquez-vous au sujet de l’initialisation des champs ? Quand avez vous appelé les constructeurs et destructeurs de chaque variable? Quelles conclusions en tirez vous sur l’apport des constructeurs et destructeurs dans la programmation ?

#include <iostream>

using namespace std ;

class CLS {

int x ;

float y ;

public :

char z ;

CLS( int a = 1 , float b = 2.2 , char c = 'F' );

~CLS( );

} ;

CLS :: CLS( int a , float b , char c ) {

//...

x = a ; y = b ; z = c ;

}

CLS :: ~CLS( ){

// ...

}

void main( ) {

CLS var1 ;

CLS var2( 20 ) ;

CLS var3( 30 , 31.23 ) ;

CLS var4( 40 , 43.21, 'D' );

}

Etudiant

string Nom;

public : void saisie();

void afficher();

Page 38: Service d’Informatique - Université de Mons · préparation au master d’Informatique et Gestion. Nous renvoyons, néanmoins le lecteur désireux d'en savoir plus à l'un des

Chap3 : Les Classes

M. BENJELLOUN Info II S. Informatique

35

3.6. Reprenez l’exercice 3.5. et ajoutez une fonction fct permettant de modifier la valeur des

champs et une fonction Affichage permettant l’affichage des champs de l’objet. Puis utilisez deux variables, l’une statique et l’autre dynamique, en leurs appliquant les appels de fonctions. Quand avez-vous appelé les constructeurs et destructeurs de chaque variable? Quelles conclusions en tirez vous sur l’utilisation des variables dynamiques ? 3.7. Ecrire un programme utilisant une classe Vecteur3d permettant de manipuler des vecteurs à

3 composantes (x, y, z) de types float. On y prévoira : • un constructeur, avec des valeurs par défaut (0), • une fonction d'affichage des 3 composantes du vecteur sous la forme : < composante1, composante2, composante3>

• une fonction permettant d'obtenir la somme de deux vecteurs, • une fonction membre coincide(??) permettant de vérifier si deux vecteurs ont les mêmes composantes,

• la définition d’un opérateur == afin de tester la coïncidence ou pas de deux vecteurs, • la définition d’un opérateur + afin d'additionner deux vecteurs, • la définition d’un opérateur - afin d'additionner deux vecteurs,

Utilisez la fonction main() suivante comme programme d'essai de ce problème et vérifier que les résultats sont conformes à la solution fournie.

void main() {

vecteur3d v1 (1,2,3), v2 (4,5), w ;

cout << "v1 = " ; v1.affiche () ; cout << "\n" ;

cout << "v2 = " ; v2.affiche () ; cout << "\n" ;

cout << "w = " ; w.affiche () ; cout << "\n" ;

w = v1.somme (v2) ;

cout << "w = " ; w.affiche () ; cout << "\n" ;

w = v1 + v1 ; cout << "w = " ; w.affiche () ; cout << "\n" ;

v1.coincide(v2);

v1.coincide(v1);

if (v1==w) cout << "\n <<< v1 = w";

else cout << "\n >>> v1 != w \n";

w = v1 - v2 ; cout << "w = " ; w.affiche () ; cout << "\n"

;

w = v1 - v1 ; cout << "w = " ; w.affiche () ; cout << "\n"

;

}

Affichage de la solution : v1 = < 1, 2, 3> v2 = < 4, 5, 0>

w = < 0, 0, 0>

w = < 5, 7, 3>

w = < 2, 4, 6>

---> === Pas EGALITE ===

---> === EGALITE ===

>>> v1 != w

w = < -3, -3, 3>

w = < 0, 0, 0>

Page 39: Service d’Informatique - Université de Mons · préparation au master d’Informatique et Gestion. Nous renvoyons, néanmoins le lecteur désireux d'en savoir plus à l'un des

Chap4 : Les patrons et amis

Fonctions et classes Parmi les techniques pour améliorer la réutilisabilité des morceaux de code, nous trouvons la notion de généricité. Cette notion permet d’écrire du code générique en paramétrant des fonctions et des classes par un type de données. Un module générique n’est alors pas directement utilisable : c’est plutôt un modèle, patron (template) de module qui sera «instancié » par les types de paramètres qu’il accepte. Dans la suite nous allons montrer comment C++ permet, grâce à la notion de patron de fonctions, de définir une famille de fonctions paramétrées par un ou plusieurs types, et éventuellement des expressions. D'une manière comparable, C++ permet de définir des "patrons de classes". Là encore, il suffira d’écrire une seule fois la définition de la classe pour que le compilateur puisse automatiquement l'adapter à différents types.

4.1. Patrons de fonctions

Pour illustrer les patrons de fonctions, prenons un exemple concret : une fonction min qui accepte deux paramètres et qui renvoie la plus petite des deux valeurs qui lui est fournie. On désire bénéficier de cette fonction pour certains types simples disponibles en C++ (int, char, float). Les notions que nous avons vu en C++ jusqu’à maintenant ne nous permettent de résoudre ce problème qu’avec une seule solution. Cette solution est d’utiliser la surcharge et de définir trois fonctions min, une pour chacun des types considéré.

int min (int a, int b) { if ( a < b) return a ; // return ((a < b)? a : b);

else return b ;

}

float min (float a, float b) {

if ( a < b) return a ;

else return b ;

}

char min (char a, char b) {

if ( a < b) return a ;

else return b ;

}

Définition des fonctions min grâce à la surcharge. Lors d’un appel à la fonction min, le type des paramètres est alors considéré et l’implantation correspondante est finalement appelée. Ceci présente cependant quelques inconvénients : – La définition des 3 fonctions (perte de temps, source d’erreur) mène à des instructions identiques, qui ne sont différenciées que par le type des variables qu’elles manipulent.

Page 40: Service d’Informatique - Université de Mons · préparation au master d’Informatique et Gestion. Nous renvoyons, néanmoins le lecteur désireux d'en savoir plus à l'un des

Chap4 : Les patrons et amis Fonctions et classes

M. BENJELLOUN Info II S. Informatique

37

– Si on souhaite étendre la définition de cette fonction à de nouveaux types, il faut définir une nouvelle implantation de la fonction min par type considéré. Une autre solution est de définir une fonction template, c’est-à-dire générique. Cette définition définit en fait un patron de fonction, qui est instancié par un type de données (ici le type T) pour produire une fonction par type manipulé.

template <class T> // T est le paramètre de modèle

T min (T a, T b) {

if ( a < b) return a

else return b;

}

void main(){

int a = min(1, 7); // int min(int, int)

float b = min(10.0, 25.0); // float min(float, float)

char c = min(’z’, ’c’); // char min(char, char)

}

Définition de la fonction min générique.

Il n’est donc plus nécessaire de définir une implantation par type de données. De plus, la fonction min est valide avec tous les types de données dotés de l’opérateur <. On définit donc bien plus qu’une fonction, on définit une méthode permettant d’obtenir une certaine abstraction en s’affranchissant des problèmes de type.

Rqs – Il est possible de définir des fonctions template acceptant plusieurs types de données en paramètre. Chaque paramètre désignant une classe est alors précédé du mot-clé class, comme dans l’exemple : template <class T, class U> .... – Chaque type de données paramètre d’une fonction template doit être utilisé dans la définition de cette fonction. – Pour que cette fonctionnalité soit disponible, les fonctions génériques doivent être définies au début du programme ou dans des fichiers d’interface (fichiers .h).

4.2. Classe template : patron de classes

Il est possible, comme pour les fonctions, de définir des classes template, c’est-à-dire paramétrées par un type de données. Cette technique évite ainsi de définir plusieurs classes similaires pour décrire un même concept appliqué à plusieurs types de données différents. Elle est largement utilisée pour définir tous les types de containers (comme les listes, les tables, les piles, etc.), mais aussi des algorithmes génériques par exemple. La bibliothèque STL (chapitre 6) en particulier propose une implantation d’un bon nombre de types abstraits et d’algorithmes génériques.

La syntaxe permettant de définir une classe template est similaire à celle qui permet de définir des fonctions template. Pour illustrer cela, nous reprenons notre exemple de création de la classe point. Nous avons souvent été amenés à créer une classe point de ce genre :

Page 41: Service d’Informatique - Université de Mons · préparation au master d’Informatique et Gestion. Nous renvoyons, néanmoins le lecteur désireux d'en savoir plus à l'un des

Chap4 : Les patrons et amis Fonctions et classes

M. BENJELLOUN Info II S. Informatique

38

class point {

int x, y ;

public :

point (int abs=0, int ord=0) ;

void affiche () ;

// .....

} ;

Lorsque nous procédons ainsi, nous imposons que les coordonnées d'un point soient des valeurs de type int. Si nous souhaitons disposer de points à coordonnées d'un autre type (float, double, long ...), nous devons définir une autre classe en remplaçant simplement, dans la classe précédente, le mot clé int par le nom de type voulu. Ici encore, nous pouvons simplifier considérablement les choses en définissant un seul patron de classe de cette façon :

template <class T> class point {

T x , y ;

public :

point (T abs=0, T ord=0) ;

void affiche () ;

} ;

Comme dans le cas des patrons de fonctions, la mention template <class T> précise que l'on a affaire à un patron (template) dans lequel apparaît un paramètre de type nommé T ; rappelons qu’en C++ le mot clé class précise que T est un argument de type (pas forcément classe...). Bien entendu, la définition de notre patron de classes n'est pas encore complète puisqu'il y manque la définition des fonctions membres, à savoir le constructeur point et la fonction affiche(). Pour ce faire, la démarche va légèrement différer selon que la fonction concernée est en ligne ou non. Pour une fonction en ligne, les choses restent naturelles ; il suffit simplement d'utiliser le paramètre T à bon escient. Voici par exemple comment pourrait être défini notre constructeur:

point (T abs=0, T ord=0) {

x = abs ; y = ord ;

}

En revanche, lorsque la fonction est définie en dehors de la définition de la classe, il est nécessaire de rappeler au compilateur :

• que, dans la définition de cette fonction, vont apparaître des paramètres de type ; pour ce faire, on fournira à nouveau la liste de paramètre sous la forme :

template <class T>

• le nom du patron concerné (de même qu'avec une classe "ordinaire", il fallait préfixer le nom de la fonction du nom de la classe...) ; par exemple, si nous définissons ainsi la fonction affiche, son nom sera:

point<T>::affiche ()

Page 42: Service d’Informatique - Université de Mons · préparation au master d’Informatique et Gestion. Nous renvoyons, néanmoins le lecteur désireux d'en savoir plus à l'un des

Chap4 : Les patrons et amis Fonctions et classes

M. BENJELLOUN Info II S. Informatique

39

En définitive, voici comment se présenterait l'en-tête de la fonction affiche si nous le définissions ainsi en dehors de la classe :

template <class T>

void point<T>::affiche ()

En toute rigueur, le rappel du paramètre T à la suite du nom de patron (point) est redondant puisqu’il a déjà été spécifié dans la liste de paramètres suivant le mot clé template. Voici ce que pourrait être finalement la définition de notre patron de classe point :

template <class T> class point { // Création d'un patron de classe

T x , y ;

public :

point (T abs=0, T ord=0) {

x = abs ; y = ord ;

}

void affiche () ;

} ;

template <class T> void point<T>::affiche () {

cout << "Paire : " << x << " " << y << "\n" ;

} Création d'un patron de classes

4.3. Utilisation d’un patron de classes

Comme pour les patrons de fonctions, l’instanciation de tels patrons est effectuée automatiquement par le compilateur selon les déclarations rencontrées. Après avoir créé ce patron, une déclaration telle que :

point <int> ai ;

conduit le compilateur à instancier la définition d'une classe point dans laquelle le paramètre T prend la valeur int. Autrement dit, tout se passe comme si nous avions fourni une définition complète de cette classe. Si nous déclarons :

point <float> ad ;

le compilateur instancie la définition d'une classe point dans laquelle le paramètre T prend la valeur float, exactement comme si nous avions fourni une autre définition complète de cette classe. Si nous avons besoin de fournir des arguments au constructeur, nous procéderons de façon classique comme dans :

point <int> ai (3, 5) ;

point <float> ad (1.5, 9.6) ;

Si nous faisons abstraction de la signification des paramètres (coordonnées d’un point) et nous les déclarons comme des caractères ou chaînes de caractères, le compilateur ne signalera pas d’incohérence et la fonction affiche() remplacera x et y par les arguments, comme le montre l’exemple récapitulatif.

Page 43: Service d’Informatique - Université de Mons · préparation au master d’Informatique et Gestion. Nous renvoyons, néanmoins le lecteur désireux d'en savoir plus à l'un des

Chap4 : Les patrons et amis Fonctions et classes

M. BENJELLOUN Info II S. Informatique

40

Exemple récapitulatif : Voici un programme complet comportant : • la création d'un patron de classes point dotée d’un constructeur en ligne et d’une fonction membre (affiche) non en ligne, • la création d’un patron de fonctions min (en ligne dans la patron de classes) qui retourne le minimum des arguments , • un exemple d'utilisation (main).

#include <iostream>

#include <string>

using namespace std ;

template <class T> class point { // Création d'un patron de classe

T x ;

T y ;

public :

point (T abs=0, T ord=0) {

x = abs ; y = ord ;

}

void affiche () ;

T min () {

if ( x < y) return x;

else return y;

}

};

template <class T> void point<T>::affiche () {

cout << "Paire : " << x << " " << y << "\n" ;

}

void main () {

point <int> ai (3, 5) ; // T prend la valeur int pour la classe point

ai.affiche() ;

cout << " Min : ... " << ai.min() << endl;

point <char> ac ('z', 't') ; ac.affiche() ;

cout << " Min : ... " << ac.min() << endl;

point <double> ad (1.5, 9.6) ; ad.affiche() ;

cout << " Min : ... " << ad.min() << endl;

point <string> as ("Salut", " A vous") ; as.affiche() ;

cout << " Min : ... " << as.min() << endl;

}

Programme 4.1.

Solution

Paire : 3 5

Min : ... 3

Paire : z t

Min : ... t

Paire : 1.5 9.6

Min : ... 1.5

Paire : Salut A vous

Min : ... A vous

Page 44: Service d’Informatique - Université de Mons · préparation au master d’Informatique et Gestion. Nous renvoyons, néanmoins le lecteur désireux d'en savoir plus à l'un des

Chap4 : Les patrons et amis Fonctions et classes

M. BENJELLOUN Info II S. Informatique

41

Rq

Le comportement de point<char> est satisfaisant si nous souhaitons effectivement disposer de points repérés par de vrais caractères. En revanche, si nous avons utilisé le type char pour disposer de "petits entiers", le résultat est moins satisfaisant. En effet, nous pourrons toujours déclarer un point de cette façon : point <char> pc (4, 9) ;

Mais le comportement de la fonction affiche ne nous conviendra plus (nous obtiendrons les caractères ayant pour code les coordonnées du point !). Il est toujours possible de modifier cela en "spécialisant" notre classe point pour le type char ou encore en spécialisant la fonction affiche pour la classe point<char>.

Il faut noter aussi que le nombre de paramètres n'est pas limité à 1, on peut en avoir plusieurs :

template < class A, class B, class C, ... >

class MaClasse

{

// ...

public:

// ...

};

L’exemple suivant montre comment implémenter les fonctions de la classe template. template <class T, class T1, class T2> class Etudiant{

T Nom, Prenom;

T1 Id;

T2 Age;

public:

//...

T getNom(){return nom;}

T1 getId(){return Id;}

T2 getAge();

void Saisie();

void affiche();

//...

};

template <class T, class T1, class T2> T2 Etudiant<T,T1,T2>::getAge(){

return Age;

}

template <class T, class T1, class T2> void Etudiant<T,T1,T2>::affiche () {

cout << "Data : " << Nom << " " << Id << " " << Age <<"\n" ;

}

La suite de cet exemple sera traitée en séances d’exercices. Pour de plus amples informations et plus de détails sur les templates, le lecteur peut se reporter par exemple à l’ouvrage référencé au début de ce syllabus : Claude Delannoy

Programmer en langage C++, Eyrolles, 2004 ou 2007. Chapitre 12.

Page 45: Service d’Informatique - Université de Mons · préparation au master d’Informatique et Gestion. Nous renvoyons, néanmoins le lecteur désireux d'en savoir plus à l'un des

Chap4 : Les patrons et amis Fonctions et classes

M. BENJELLOUN Info II S. Informatique

42

4.4. Fonctions et classes amies

Nous avons vu qu’une classe avait généralement des membres privés et que ceux-ci n’étaient pas accessibles par des fonctions non membres. Cette restriction peut sembler lourde, mais elle est à la base même de la protection des données qui fait une grande partie de la puissance de la programmation par objets en général, et de C++ en particulier.

Dans certains cas, cependant, il peut être intéressant d’accorder des accès aux attributs ou aux méthodes à certaines classes clientes, tout en protégeant ce même accès vis-à-vis des autres classes. Comment faire alors si par exemple on souhaite qu’une fonction f() et/ou une classe B puisse accéder aux données membres privées d’une classe A?

La solution est de déclarer dans la classe A que f() et/ou B sont amies de A. Voici comment.

class A {

private:

int i; … // tout ce qui est nécessaire à A…

friend class B; // B est autorisée :classe amie

friend void f(..); // f() est autorisée : fonction amie; pas membre de la classe A

};

class B {

… // tout ce qui appartient à B, et qui peut se servir des données membres privées de A

};

void f(..) { ..; }

4.4.1. Fonctions amies

Une fonction amie d'une classe est une fonction qui, sans être membre de cette classe, a le droit d'accéder à tous ses membres, aussi bien publics que privés. Une fonction amie doit être déclarée ou définie dans la classe qui accorde le droit d'accès, précédée du mot réservé friend. Cette déclaration doit être écrite indifféremment parmi les membres publics ou parmi les membres privés :

class Tableau {

int tab[20], nbr;

friend void afficher(Tableau t);

public: Tableau(int nbrElements);

...

};

et, plus loin:

void afficher(Tableau t) {

for (int i = 0; i < t.nbr; i++)

cout << ' ' << t.tab[i];

}

Page 46: Service d’Informatique - Université de Mons · préparation au master d’Informatique et Gestion. Nous renvoyons, néanmoins le lecteur désireux d'en savoir plus à l'un des

Chap4 : Les patrons et amis Fonctions et classes

M. BENJELLOUN Info II S. Informatique

43

Notez que bien que déclarée à l'intérieur de la classe Tableau, la fonction afficher n'est pas membre de cette classe. À part le fait qu’elle est amie de la classe Tableau, la fonction est parfaitement ordinaire, et peut être déclarée et définie de la même façon que toute autre.

L’emploi de friend est une relaxation de la règle de portée textuelle des langages à structure de blocs, car il donne aux fonctions amies les mêmes privilèges que ceux des fonctions membres.

4.4.2. Méthodes amies

Dans la plupart des cas, une fonction amie (4.4.1.) peut avantageusement être remplacée par une fonction membre. Si l’on souhaite qu’une méthode d’une classe puisse accéder aux parties privées d’une autre classe, il suffit de déclarer la méthode friend également, en utilisant son nom complet (nom de classe suivi de :: et du nom de la méthode). Par exemple :

class Tableau; // déclaration avant celle de la classe Fenetre sinon ERROR

class Fenetre {

...

public:

void afficher(Tableau t);

...

};

class Tableau {

int tab[20], nbr;

public:

friend void Fenetre::afficher(Tableau t);

...

};

Maintenant, la fonction afficher est membre de la classe Fenetre et amie de la classe Tableau : elle a tous les droits sur les membres privés de ces deux classes, et son écriture :

void Fenetre::afficher( Tableau t) {

for (int i = 0; i < t.nbr; i++)

cout << ' ' << t.tab[i];

}

4.4.3. Classes amies

Lorsqu’on souhaite que tous les membres d’une classe puissent accéder aux parties privées d’une autre classe, on peut déclarer « amie » une classe entière :

class B ; // déclaration avant celle de la classe A qui contient friend B sinon ERROR

class A {

private:

int i; … // tout ce qui est nécessaire à A…

friend B; // B est autorisée :classe amie

};

Page 47: Service d’Informatique - Université de Mons · préparation au master d’Informatique et Gestion. Nous renvoyons, néanmoins le lecteur désireux d'en savoir plus à l'un des

Chap4 : Les patrons et amis Fonctions et classes

M. BENJELLOUN Info II S. Informatique

44

class B {

… // tout ce qui appartient à B, et qui peut se servir des données membres privées de A

};

Les membres de la classe B peuvent tous modifier les parties privées des instances de A. La déclaration de B avant celle de A est obligatoire, sinon on obtient Error : Undefined symbol 'B' (symbole 'B' non défini). Pour l’éviter, on peut éventuellement changer l’ordre de définition, mais il suffit en fait de préciser le sélecteur class derrière friend :

class A {

private:

int i; … // tout ce qui est nécessaire à A…

friend class B; // B est autorisée :classe amie

};

ExercicesExercicesExercicesExercices 4.1. Considérons les deux fonctions suivantes :

void swap(int& a,int& b) {

int tmp=a; a=b; b=tmp;

}

void swap(string& a,string& b) {

string tmp=a; a=b; b=tmp;

} Elles utilisent le même principe, seul le type des deux paramètres (et celui de tmp) changent. Ecrire une seule fois la fonction échange de manière à ce qu’elle soit utilisable pour échanger toute paire de variables d’un même type. 4.2. Créez deux patrons de fonctions, l’un permettant d’afficher les éléments et l’autre de calculer la somme d’un tableau d’éléments de type quelconque. Le nom et le nombre d’éléments du tableau sont fournis en paramètres. 4.3. Soit la déclaration de la classe vecteur3d de l’exercice 3.6.

class vecteur3d { float x, y, z ; public : vecteur3d (float c1=0, float c2=0, float c3=0) { x=c1 ; y=c2 ; z=c3; } … } ;

Ecrire une fonction indépendante coincide( ??), amie de la classe vecteur3d, permettant de vérifier si deux vecteurs ont les mêmes composantes. Si v1 et v2 désignent deux vecteurs de type vecteur3d, comment s’écrit le test de coïncidence de ces deux vecteurs ?

Page 48: Service d’Informatique - Université de Mons · préparation au master d’Informatique et Gestion. Nous renvoyons, néanmoins le lecteur désireux d'en savoir plus à l'un des

Chap5 : L'héritage

5.1. Classes de base et classes dérivées

L'héritage est un principe propre à la programmation orientée objet, permettant de créer une nouvelle classe à partir d'une classe existante. C’est un principe qui permet l’extension d'une classe de base afin de lui ajouter des fonctionnalités particulières tout en conservant les fonctionnalités déjà définies dans la classe de base. En fait c’est une technique de réutilisation de composants (classes) dont l’objectif est la spécialisation de types existants. On parle aussi de la dérivation de classe qui consiste à faire hériter une classe d’une autre. Donc une classe B est dérivée d’une classe A si elle en hérite. La classe A est alors appelée superclasse ou classe de base ou classe mère de la classe B. On dit aussi que B est la classe fille ou B dérive de A ou encore que B est dérivée de A. Quelque soit le terme choisi, si la classe B hérite de la classe A, alors B possède toutes les propriétés de A; la classe B peut bien sûr avoir des propriétés (données membres, méthodes, …) supplémentaires qui reflètent sa spécificité. En C++, il existe l’héritage simple (une classe hérite d’une seule classe) et l’héritage multiple (une classe peut être dérivée de plusieurs classes). Dans ce cours, nous ne nous intéresserons qu’à l'exposé de l’héritage simple.

5.2. Mode de dérivation

La syntaxe pour la définition d'une classe dérivée est :

class ClasseDerivee : < MODE DE DERIVATION > ClasseBase {

...

};

Dans la définition de la classe dérivée, afin d’utiliser l’héritage, on ajoute le symbole : après le nom de la classe en précisant par la suite quelle est la classe de base.

< MODE DE DERIVATION > permet de spécifier le mode de dérivation et les types de protection par l'emploi d'un des mots-clé suivants : public, protected ou encore

private. Le mode de dérivation détermine quels membres de la classe de base sont

accessibles dans la classe dérivée. Au cas où aucun mode de dérivation n'est spécifié, le compilateur C++ prend par défaut le mot-clé private pour une classe.

Si B hérite de A, B hérite des attributs et des "méthodes" de A. Cependant, la classe B ne peut pas accéder aux membres privés (déclarés private) de la classe A. Faut-il pour autant les déclarer public, au risque de violer le principe d'encapsulation ? Le C++ permet de déclarer des membres protégés uniquement accessibles par les objets de la même classe

et par tous les objets des classes dérivées. On utilise pour cela le mot clé protected.

Page 49: Service d’Informatique - Université de Mons · préparation au master d’Informatique et Gestion. Nous renvoyons, néanmoins le lecteur désireux d'en savoir plus à l'un des

Chap5 : L'héritage

M. BENJELLOUN Info II S. Informatique

46

Si le mode de dérivation est : • public : le contrôle d’accès est sans changement ; les données publiques de la

classeBase restent publiques dans la classeDerivee, protégées restent protégées, privées restent privées ;

• protected: c'est-à-dire accessibles aux membres de la classe et à ses classes dérivées (par héritage). Dans ce cas, les données publiques deviennent protégées, les données protégées restent protégées et les privées restent privées,

• private : les données publiques et protégées deviennent privées alors que les données privées restent privées.

Le tableau suivant récapitule les propriétés des différentes situations selon le mode de dérivation :

mode de

dérivation

Statut dans la

classe de base

Statut dans la

classe dérivée

public public

protected protected public

private inaccessible

public protected

protected protected protected

private inaccessible

public private

protected private private

private inaccessible

Une classe dérivée peut accéder aux membres hérités publics ou protégés mais ne peut accéder aux membres privés hérités. En général, on choisit le mode de dérivation public qui est la forme la plus courante d'héritage. C'est d’ailleurs le seul mode que nous traitons dans ce syllabus. Les autres modes seront illustrés par quelques transparents lors des séances d’exercices.

5.3. Héritage public

Ce mode, comme signalé plus haut, donne aux membres publics et protégés de la classe de base le même statut dans la classe dérivée. Pour spécifier en C++ qu’une classe B hérite d’une classe A et que le mode de dérivation est public, on déclare la classe B de la manière suivante :

class B : public A {

// Champs et méthodes };

Plusieurs classes peuvent dériver de la même classe de base. Il est donc possible de représenter sous forme de hiérarchie de classes, parfois appelée arborescence de classes,

Page 50: Service d’Informatique - Université de Mons · préparation au master d’Informatique et Gestion. Nous renvoyons, néanmoins le lecteur désireux d'en savoir plus à l'un des

Chap5 : L'héritage

M. BENJELLOUN Info II S. Informatique

47

la relation de parenté qui existe entre ces différentes classes. L'arborescence commence par la superclasse (classe de base, classe parent, classe mère ou père). Puis les classes dérivées (classe fille ou sous-classe) deviennent de plus en plus spécialisées. Ainsi, on peut généralement exprimer la relation qui lie une classe dérivée à sa classe de base par la phrase "est un".

Un Etudiant (Enseignant, Agent) est une personne mais pas forcément l’inverse. Les déclarations correspondantes à cet arbre peuvent être sous cette forme :

class Personne {

} ;

class Agent : public Personne {

};

class Enseignant : public Personne{

};

Pour vous présenter la mise en œuvre de l’héritage nous commençons à partir d’un exemple simple ne faisant intervenir ni constructeur ni destructeur. Nous reprenons le programme 3.2. que nous modifions afin d’illustrer les possibilités de l’héritage.

#include <iostream>

#include <string>

using namespace std ;

class point {

private :

int x, y;

public :

void initialise(int, int) ;

void affiche(string) ;

};

class Pixel : public point { // Pixel dérive de point

int couleur ;

public :

void colore (int cl) {

couleur = cl;

cout << "\n --- couleur = " << couleur << "\n" ;

}

};

void point::initialise (int abs, int ord) {

x = abs ; y = ord ;

}

Classes dérivées

Page 51: Service d’Informatique - Université de Mons · préparation au master d’Informatique et Gestion. Nous renvoyons, néanmoins le lecteur désireux d'en savoir plus à l'un des

Chap5 : L'héritage

M. BENJELLOUN Info II S. Informatique

48

void point::affiche (string S) {

cout << S << ":- Je suis en " << x << " " << y << "\n" ;

}

void main() {

point a; // objet de type class point

Pixel PC ; // objet de type class Pixel

a.initialise (3, 7) ; a.affiche ("point") ;

PC.initialise (10,20) ; PC.colore (5) ;

PC.affiche("Pixel") ;

}

Programme 5.1.

SOLUTION

point:- Je suis en 3 7

--- couleur = 5

Pixel:- Je suis en 10 20

La classe Pixel dérive de la classe point. Le mode de dérivation étant public, les

membres publics de la classe de base point sont également publics, donc accessibles et

réutilisables dans la classe dérivée Pixel. C’est pour cette raison, que dans la fonction

main(), les fonctions membres de la classe point, initialise et affiche, sont accessibles

par l’objet PC de la classe Pixel. Cet objet, comme tout objet de la classe Pixel, peut

accéder à sa fonction membre colore contrairement aux objets appartenant à la classe

point.

La classe Pixel telle qu’elle est définie ne permet pas d’afficher la couleur d’un objet. Une

amélioration possible est de créer une fonction qui permet d’afficher les coordonnées du point ainsi que sa couleur. Pour afficher les coordonnées on peut réutiliser éventuellement la fonction public affiche de la classe point. Une définition possible de cette fonction est :

void Pixel ::AfficheCol (string S){

affiche(S); // membre de la classe de base point

cout << "\n --- couleur = " << couleur << "\n" ;

}

Il n’est pas possible de remplacer la fonction affiche(S) par son code dans la fonction AfficheCol(S). Autrement dit, la fonction AfficheCol(S) ne peut être définie comme :

void Pixel ::AfficheCol (string S){ cout << S << ":- Je suis en " << x << " " << y << "\n" ;

cout << "\n --- couleur = " << couleur << "\n" ;

}

En effet, x et y sont des membres privés de la classe point, et ne sont nullement accessible par une fonction membre de la classe dérivée Pixel. C’est le principe de l’encapsulation.

Page 52: Service d’Informatique - Université de Mons · préparation au master d’Informatique et Gestion. Nous renvoyons, néanmoins le lecteur désireux d'en savoir plus à l'un des

Chap5 : L'héritage

M. BENJELLOUN Info II S. Informatique

49

5.4. Redéfinition de membres dans la classe dérivée

5.4.1. Redéfinition des fonctions membres

Il est possible, pour une raison ou une autre, de redéfinir une fonction dans une classe dérivée si on lui donne le même nom que dans la classe de base. C’est comme si dans l’exemple précédent de la classe Pixel, on donne le même nom pour les fonctions affiche et AfficheCol . Dans ce cas, cette dernière deviendrait :

void Pixel ::affiche (string S){ // membre de la classe Pixel

affiche(S); // membre de la classe de base point

cout << "\n --- couleur = " << couleur << "\n" ;

}

L’exécution de cette fonction provoquerait des erreurs. En effet, pour le compilateur qui ne fait pas la différence entre les fonctions affiche() de Pixel et de point, c’est un appel récursif de cette fonction. Il faut dons faire appel à l’opérateur de résolution de portée (::) afin de localiser convenablement la méthode voulue. La fonction sera définie comme suit :

void Pixel ::affiche (string S){ // membre de la classe Pixel

point :: affiche(S); // membre de la classe de base point

cout << "\n --- couleur = " << couleur << "\n" ;

}

Le programme 5.1. peut être transformé de la façon suivante :

#include <iostream>

#include <string>

using namespace std ;

class point {

private :

int x, y;

public :

void initialise(int, int) ;

void affiche(string) ;

};

class Pixel : public point { // Pixel dérive de point

int couleur ;

public :

void colore (int cl) {

couleur = cl;

cout << "\n --- couleur = " << couleur << "\n" ;

}

void affiche (string);

};

void point::initialise (int abs, int ord) {

x = abs ; y = ord ;

}

void point::affiche (string S) {

cout << S << ":- Je suis en " << x << " " << y << "\n" ;

}

Page 53: Service d’Informatique - Université de Mons · préparation au master d’Informatique et Gestion. Nous renvoyons, néanmoins le lecteur désireux d'en savoir plus à l'un des

Chap5 : L'héritage

M. BENJELLOUN Info II S. Informatique

50

void Pixel ::affiche (string S){ // membre de la classe Pixel

point :: affiche(S); // membre de la classe de base point

cout << "\n +++ couleur = " << couleur << "\n" ;

}

void main() {

point a; // objet de type class point

Pixel PC ; // objet de type class Pixel

a.initialise (3, 7) ; a.affiche ("point") ;

PC.initialise (10,20) ; PC.colore (5) ; PC.affiche("Pixel") ;

}

Programme 5.2.

SOLUTION

point:- Je suis en 3 7

--- couleur = 5

Pixel:- Je suis en 10 20

+++ couleur = 5

5.4.2. Redéfinition des données membres

C’est beaucoup moins courant de redéfinir les données membres. Cependant, ce que nous avons dit à propos de la redéfinition des fonctions membres s’applique tout aussi bien aux membres données.

class A {

int X, Y ;

};

class B: public A {

float X, Y ;

};

5.5. Héritage et Constructeurs/destructeurs

Les constructeurs, constructeur de copie, destructeurs et opérateurs d'affectation ne sont jamais hérités. En fait les constructeurs par défaut des classes de bases sont automatiquement appelés avant le constructeur de la classe dérivée. Afin d’illustrer ces propos, le programme suivant en donne un exemple. On remarquera que l'appel des destructeurs se fait dans l'ordre inverse des constructeurs.

Page 54: Service d’Informatique - Université de Mons · préparation au master d’Informatique et Gestion. Nous renvoyons, néanmoins le lecteur désireux d'en savoir plus à l'un des

Chap5 : L'héritage

M. BENJELLOUN Info II S. Informatique

51

#include <iostream>

using namespace std ;

class A {

public:

A() {

cout<< "++++ A ++++" << endl;

}

~A() {

cout<< "~~~~ A ~~~~" << endl;

}

};

class B : public A {

public:

B() {

cout<< "++++ B ++++" << endl;

}

~B() {

cout<< "~~~~ B ~~~~ " << endl;

}

};

void main() {

B *ObjB = new B;

// ...

delete ObjB;

}

Programme 5.3.

SOLUTION

++++ A ++++

++++ B ++++

~~~~ B ~~~~

~~~~ A ~~~~

Si l’on ne désire pas appeler les constructeurs par défaut, mais des constructeurs avec des paramètres, on doit utiliser une liste d'initialisation. Le programme qui nous servira d’exemple est une adaptation du programme 5.2.

#include <iostream>

using namespace std ;

class point {

int x, y ;

public :

point (int abs=0, int ord=0) { // constructeur de point ("inline")

cout << "++ constr. point :" << abs << " " << ord << "\n" ;

x = abs ; y =ord ;

}

~point(){

cout<< "~~~~ point ~~~~" << endl;

}

} ;

Page 55: Service d’Informatique - Université de Mons · préparation au master d’Informatique et Gestion. Nous renvoyons, néanmoins le lecteur désireux d'en savoir plus à l'un des

Chap5 : L'héritage

M. BENJELLOUN Info II S. Informatique

52

class Pixel : public point {

int couleur ;

public :

Pixel (int, int, int) ; // déclaration constructeur Pixel

~Pixel(){

cout<< "~~~~ Pixel ~~~~ :" << couleur << endl;

}

} ;

Pixel::Pixel (int abs, int ord, int cl) { cout << "++ constr. Pixel : " << abs << " " << ord << " " << cl << "\n";

couleur = cl ;

}

void main() {

Pixel a(10,15,3);

}

Programme 5.4.

SOLUTION

++ constr. point : 0 0

++ constr. Pixel : 10 15 3

~~~~ Pixel ~~~~ : 3

~~~~ point ~~~~

La déclaration : Pixel a(10, 15, 3) ;

entraînera : • l’appel de point qui recevra les arguments les arguments par défaut 0 et 0, • l’appel de Pixel qui recevra les arguments 10, 15 et 3.

Si maintenant le constructeur de la classe point n’avait pas d’arguments par défaut, donc déclaré comme ceci :

point (int abs, int ord) { …

voici l’erreur de compilation que l’on peut avoir : error C2512: 'point' : no appropriate default constructor available

Si par contre on souhaite que Pixel retransmette à point les deux premières informations reçues, on écrira son en-tête de cette manière :

Pixel::Pixel (int abs, int ord, int cl) : point (abs, ord)

Le compilateur mettra en place la transmission au constructeur de point des informations abs et ord correspondant aux deux premiers arguments de Pixel. Ainsi la déclaration : Pixel a (10, 15, 3) ;

entraînera : • l’appel de point qui recevra les arguments 10 et 15, • l’appel de Pixel qui recevra les arguments 10, 15 et 3.

Page 56: Service d’Informatique - Université de Mons · préparation au master d’Informatique et Gestion. Nous renvoyons, néanmoins le lecteur désireux d'en savoir plus à l'un des

Chap5 : L'héritage

M. BENJELLOUN Info II S. Informatique

53

En revanche, la déclaration : Pixel b (1, 7) ;

provoquera une erreur puisqu’il n’existe pas de constructeur Pixel à deux arguments. Bien entendu, il reste la possibilité d’initialiser les arguments par défaut, par exemple : Pixel::Pixel (int abs=0, int ord=0, int cl=9) : point (abs, ord)

Dans ces conditions, la déclaration : Pixel c (2) ;

entraînera : • l’appel de point qui recevra les arguments 2 et 0, • l’appel de Pixel qui recevra les arguments 2, 0 et 9.

Notez que la présence éventuelle d’arguments par défaut dans point n’a aucune incidence ici.

La déclaration : Pixel * adr ;

adr = new Pixel (20,30) ; // objet dynamique

entraînera : • l’appel de point qui recevra les arguments 20 et 30, • l’appel de Pixel qui recevra les arguments 20, 30 et 9.

5.6. Polymorphisme

Considérons les deux classes CLA et CLB ; class CLA {

public :

void Affiche() {

cout << " Affichage:CLA " << endl ; }

} ;

class CLB : public CLA {

public :

void Affiche() {

cout << " Affichage:CLB " << endl ; }

};

La classe CLB dérive de la classe CLA et redéfinit (surcharge) la fonction Affiche(). Voyons un exemple d’utilisation simple.

CLA *ptr_a = new CLA ;

CLB *ptr_b = new CLB ;

CLA *ptr ;

ptr = ptr_a;

ptr�Affiche(); // Affichage:CLA

ptr = ptr_b;

ptr�Affiche(); // Affichage:CLA

Le problème réside dans le deuxième appel de la méthode Affiche(). En effet, bien que le pointeur ptr fasse référence à une instance de type CLB, c’est la méthode Affiche() de CLA (le type initial de ptr) qui est appelée. Ce comportement n’est généralement pas celui souhaité ; on préfère que la méthode appelée soit celle du type de l’objet auquel le pointeur fait référence. On voudrait que le pointeur puisse être polymorphe (change de forme, de nature).

Page 57: Service d’Informatique - Université de Mons · préparation au master d’Informatique et Gestion. Nous renvoyons, néanmoins le lecteur désireux d'en savoir plus à l'un des

Chap5 : L'héritage

M. BENJELLOUN Info II S. Informatique

54

Le polymorphisme est un concept des langages objet qui découle directement de l'héritage. Si ce dernier permet de réutiliser le code écrit pour la classe de base dans les classes dérivées, le polymorphisme rendra possible l'utilisation d'une même instruction pour appeler dynamiquement des méthodes différentes dans la hiérarchie des classes. Le polymorphisme réside donc dans la capacité d'un objet à modifier son comportement propre et celui de ses descendants au cours de l'exécution.

En C++, Il faut indiquer au compilateur qu'il a affaire à une fonction polymorphe, sinon il serait tenté d'utiliser la fonction de la classe de base. Le polymorphisme est mis en oeuvre par l'utilisation des fonctions virtuelles.

Une fonction virtuelle est une fonction qui possède la capacité de "changer de forme" dans les classes dérivées de la classe de base définissant la méthode virtuelle. Un mot clé est alors introduit : virtual. Ce mot clé est placé devant la déclaration de la méthode.

Dans notre exemple il suffit d’ajouter virtual dans les classes devant les fonctions concernées. Les déclarations des classes deviennent alors : class CLA {

public :

virtual void Affiche() {

cout << " Affichage:CLA " << endl ;

}

} ;

class CLB : public CLA {

public :

virtual void Affiche() {

cout << " Affichage:CLB " << endl ;

}

} ;

Il n’est pas obligatoire de répéter le mot-clef virtual dans CLB mais améliore la lisibilité du code.

Le code qui posait problème a maintenant le comportement voulu, sans rien y changer et le compilateur choisit la bonne méthode, celle qui doit être appelée lors de l'exécution :

ptr = ptr_a;

ptr�Affiche(); // Affichage:CLA

ptr = ptr_b;

ptr�Affiche(); // Affichage:CLB

Rq

Il est généralement conseillé d'utiliser le mot clé virtual devant la déclaration du desctructeur de la classe de base, afin que celui des sous-classes soit appelé également lorsque le programme utilise un pointeur d'instance de la classe de base au lieu d'un pointeur d'instance de la classe dérivée.

Page 58: Service d’Informatique - Université de Mons · préparation au master d’Informatique et Gestion. Nous renvoyons, néanmoins le lecteur désireux d'en savoir plus à l'un des

Chap5 : L'héritage

M. BENJELLOUN Info II S. Informatique

55

ExercicesExercicesExercicesExercices

5.1. Remplacez dans le code du programme 5.4.

Pixel::Pixel (int abs, int ord, int cl) { … par Pixel::Pixel (int abs=0, int ord=0, int cl=9) : point (abs, ord){… puis, donnez un exemple d’instructions dans la fonction main() qui aboutira à la solution suivante :

++ constr. point : 10 15

++ constr. Pixel : 10 15 3

++ constr. point : 2 3

++ constr. Pixel : 2 3 9

++ constr. point : 12 0

++ constr. Pixel : 12 0 9

++ constr. point : 12 25

++ constr. Pixel : 12 25 9

~~~~ Pixel ~~~~ :9

~~~~ point ~~~~

~~~~ Pixel ~~~~ :9

~~~~ point ~~~~

~~~~ Pixel ~~~~ :9

~~~~ point ~~~~

~~~~ Pixel ~~~~ :3

~~~~ point ~~~~

5.2. Saisissez le programme et exécutez-le. Quand est appelé le constructeur de A, de B ?

Quelles sont les valeurs des champs de la variable b à la fin du programme ? Comment l’expliquez-vous ? Que faut-il faire pour modifier uniquement la valeur du champ x? Quel est l’intérêt du mot protected ?

#include <iostream>

using namespace std ;

class ClsA {

protected : int x ;

void f ( int ) ;

public : ClsA ( int a = 3 ) ;

};

ClsA :: ClsA ( int a ) {

x = a ;

}

void ClsA :: f ( int i ) {

x = i;

}

class ClsB : public ClsA {

int y ;

public : ClsB ( int a ) ;

void g ( ) ;

};

ClsB ::ClsB ( int a ) : ClsA ( 2*a ) {

y = a ;

}

void ClsB :: g ( ) { cout << "Les valeurs sont x = " << x << "\ny = " << y << "\n" ;

}

void main ( ) {

ClsB b ( 10 ) ;

b.g ( ) ;

}

Page 59: Service d’Informatique - Université de Mons · préparation au master d’Informatique et Gestion. Nous renvoyons, néanmoins le lecteur désireux d'en savoir plus à l'un des

Chap5 : L'héritage

M. BENJELLOUN Info II S. Informatique

56

5.3. Le poulailler

Implémentez la hiérarchie de classes en face : la classe de

base Volaille et ses classes dérivées : Poule, Coq,

Œuf ainsi que les différentes fonctions membres. On

considère que le poids d’un œuf = 0,1 poids de la poule.

Il est possible d’ajouter des paramètres aux fonctions

et/ou d’ajouter d’autres fonctions.

Ce programme doit gérer en boucle le Menu suivant :

1- Créer un Tableau Dynamique de poules + Affichage

2- Créer un coq + Affichage

3- Le coq fait cocorico et toutes les poules pondent

4- Le coq dit CoCo et une poule/2 pond

5- Fin (on tue tout le poulailler !!)

1- Créer Tableau Dynamique de Poules et Affichage:

Est constitué au moins de deux fonctions : SaisiePoule (…….) ;

Dans cette fonction on demandera et on testera le nombre de

Poules (NP<Max=10) à saisir et on effectuera la saisie dans un

Tableau Dynamique.

Voici un exemple d’exécution du programme

Menu :

******* 1. Créer un Tb Dyn poule + Af.

2. Créer un coq + Affichage

3. Le coq fait cocorico et

toutes les poules pondent

4. Le coq dit CoCo poule/2

5. Fin

Choix : 1

Combien de poules ? : 4

Le Nom ? : Poule1

Le poids ? : 200

Le Nom ? : Poule2

Le poids ? : 180

Le Nom ? : Poule3

Le poids ? : 1200

Le Nom ? : Poule4

Le poids ? : 1000 �

Poule4 (1000)

Poule3 (1200)

Poule2 (180)

Poule1 (200)

Menu :

******* Choix : 2

Le Nom ? : M_CoQ

Description ? : BeauCoQ

Son Nom : M_CoQ

Description : BeauCoQ

Menu : *******

Choix : 3

L'oeuf a bien ete pondu et pese : 100(gr)

Qualite de l'oeuf (A,B ou C)? : A

L'oeuf a bien ete pondu et pese : 120(gr)

Qualite de l'oeuf (A,B ou C)? : B

L'oeuf a bien ete pondu et pese : 18(gr)

Qualite de l'oeuf (A,B ou C)? : A

L'oeuf a bien ete pondu et pese : 20(gr)

Qualite de l'oeuf (A,B ou C)? : C

Menu :

******* Choix : 4 (Une poule sur 2)

L'oeuf a bien ete pondu et pese : 100(gr)

Qualite de l'oeuf (A,B ou C)? : A

L'oeuf a bien ete pondu et pese : 18(gr)

Qualite de l'oeuf (A,B ou C)? : A

Volaille

string Nom ;

public :

void saisie();

void affiche();

Poule int Poids ;

public :

void saisie();

void affiche();

void pondre(??);

Coq char * Description ;

public :

void saisie();

void affiche(); void cocorico( ??); void CoCo( ??);

Oeuf char Qualite;

public :

void saisie();

void affiche();

Rqs : Normalement pas de Classe amie.

strcpy(chaine1,chaine2) permet de

copier la chaine2 (char []) dans

chaine1 (char []). Si par contre

chaine2 est un string alors il faut

écrire strcpy(chaine1,chaine2.c_str())

Utilisez les règles de bonnes pratiques

étudiées durant les travaux pratiques

telles que l’indentation syntaxique, la

programmation modulaire, libération

de mémoire.

Page 60: Service d’Informatique - Université de Mons · préparation au master d’Informatique et Gestion. Nous renvoyons, néanmoins le lecteur désireux d'en savoir plus à l'un des

Chap5 : L'héritage

M. BENJELLOUN Info II S. Informatique

57

5.4. Implémentez la hiérarchie de classes suivante :

Ensuite, implémentez un programme qui doit gérer en boucle le Menu suivant : 1. Saisie et/ou Ajout Etudiants et affichage

2. Saisie et/ou Ajout Profs et affichage

3. Doyen membre de Direction Calcul Moyenne ∀∀∀∀ étudiant et affichage

4. Cherche un Prof selon son nom

5. Cherche un Etudiant selon son nom

6. Quitter;

Dans 1. et 2. on utilisera des tableaux dynamiques. Optimisez votre code et utilisez des templates quand c’est nécessaire.

Personne

string nom, prenom;

public :

void saisie();

void afficher();

Etudiant

int NumCarte;

int AnEtud;

int Note[3];

public :

void saisie();

void afficher()

Prof

string Service;

float Salaire;

public :

void saisie();

void afficher();

Direction

string Titre;

public :

void saisieDir();

void afficherDir();

..

Page 61: Service d’Informatique - Université de Mons · préparation au master d’Informatique et Gestion. Nous renvoyons, néanmoins le lecteur désireux d'en savoir plus à l'un des

Chap6 : La bibliothèque STL

6.1. Introduction

La bibliothèque STL (Standard Template Library : Bibliothèque standard générique.) est certainement l’un des atouts de C++ et peut être considérée comme un outil très puissant. Il s’agit d’un ensemble de structures de données (des conteneurs) et d’algorithmes suffisamment performants pour répondre aux besoins usuels des programmeurs et leur faciliter la tâche. Afin de rendre ces composants génériques, ils sont implémentés pour la plupart à l'aide de patrons (templates) de classe ou de fonction.

Cette bibliothèque fournit ces composants :

• un ensemble de classes conteneurs (collections d'objets), tel que les vecteurs, les tableaux associatifs, les listes chaînées, qui peuvent être utilisées pour contenir n'importe quel type de données à condition qu'il supporte certaines opérations comme la copie et l'assignation.

• une abstraction des pointeurs : les iterators. Ceux-ci fournissent un moyen simple et élégant de parcourir des séquences d'objets et permettent la description d'algorithmes indépendamment de toute structure de données.

• des algorithmes génériques tels que des algorithmes d'insertion/suppression, recherche et tri.

• une classe string (déjà utilisée) permettant de gérer efficacement et de manière sûre les chaînes de caractères.

L’objectif de ce chapitre n’est pas de présenter tous les détails de STL, vu le temps imparti à ce cours, mais simplement de fournir un début de notions sur le sujet. Des compléments d’informations peuvent se trouver par exemple sur

http://www.sgi.com/tech/stl/ : manuel d’utilisation de STL en anglais. http://www.mochima.com/tutorials/STL.html : Un excellent tutorial en anglais.

ou un certain nombre d’ouvrages, en plus des références données au début du syllabus, comme :

La bibliothèque standard STL du C++ Alain Bernard Fontaine chez MASSON Pour mieux développer avec C++ : design patterns, STL, RTTI et smart pointers Aurélien Geron & Fatmé Tawbi chez DUNOD

6.2. Les conteneurs

Les containers ou conteneurs sont des objets décrits par des classes génériques représentant les structures de données logiques les plus couramment utilisées : les listes (list), les vecteurs (vector), les ensembles (set), les listes d'associations (map),... Ces classes sont dotées de méthodes permettant d'organiser un ensemble de données en séquence, puis de parcourir ces données. Ces méthodes permettent de créer, de copier, de détruire ces containers, d’y insérer, de rechercher ou de supprimer des éléments. La

Page 62: Service d’Informatique - Université de Mons · préparation au master d’Informatique et Gestion. Nous renvoyons, néanmoins le lecteur désireux d'en savoir plus à l'un des

Chap6 : La bibliothèque STL

M. BENJELLOUN Info II S. Informatique

59

gestion de la mémoire, l’allocation et la libération, est contrôlée directement par les containers, ce qui facilite leur utilisation.

Tous les conteneurs proposent les méthodes suivantes :

empty() : teste si le conteneur est vide , max_size() : retourne taille limite , size() : retourne taille actuelle , operator=() : affectation d'un conteneur dans un autre , operator<(), operator<=(), operator>() operator>=(), operator==(), operator!=()

opérateurs de comparaison , swap() : échange les éléments de deux conteneurs , erase() : supprime un ou plusieurs éléments , clear() : supprime tous les éléments , begin() : retourne un itérateur qui pointe sur le 1er élément du conteneur ; end() : retourne un itérateur qui pointe après le dernier élément du conteneur ; rbegin() : retourne un itérateur qui pointe sur le dernier élément du conteneur (pour parcours du conteneur en sens inverse) rend() : retourne un itérateur qui pointe avant le 1er élément du conteneur (pour parcours du conteneur en sens inverse)

Dans ce chapitre nous nous intéressons uniquement aux conteneurs list et vector. Après l’introduction de la notion d’itérateur nous donnons un programme exemple dont le but est d’illustrer un certain nombre de méthodes disponibles sur ces conteneurs. Pour les utiliser, il suffit d’inclure dans le programme <list> et <vector>.

6.2.1. Vector

Le but de cette classe est de généraliser la notion de tableau. Sa structure interne est un tableau dynamique qui est redimensionnable, automatiquement ou manuellement. A tout moment, il est possible d'ajouter un élément, il n'y a pas de limitation (hormis l'espace mémoire). Ainsi, si le tableau interne est plein au moment de l'ajout d'un nouvel élément, alors il est réalloué avec une taille plus importante et tous ses éléments sont recopiés dans le nouvel espace.

vector<int> v;

for (int i=0; i<100; i++)

v.push_back(i); // insérer un nouvel élément à la fin du container

Cependant, il est possible de contrôler à l'avance la taille du tableau interne (grâce à la méthode reserve), ce qui permet d'éviter la réallocation du tableau.

vector<int> v;

v.reserve(10);

for (int i=0; i<10; i++) v.push_back(i);

Page 63: Service d’Informatique - Université de Mons · préparation au master d’Informatique et Gestion. Nous renvoyons, néanmoins le lecteur désireux d'en savoir plus à l'un des

Chap6 : La bibliothèque STL

M. BENJELLOUN Info II S. Informatique

60

Il est aussi possible d'initialiser un vecteur rempli d'un certain nombre d'éléments. Ces derniers sont créés à partir du constructeur par défaut de leur classe.

vector<int> v(10); // v est construite avec 10 éléments

for (int i=0; i<10; ++i) v[i]=i;

Il est possible d'accéder directement à un élément d'un vecteur à partir de son index, en utilisant l'opérateur [ ]. La numérotation des éléments débute à zéro, comme pour un tableau classique.

6.2.2. List

La liste propose un ajout et une suppression en début de liste. La structure interne du conteneur est une liste dynamique doublement chaînée dédiée à la représentation séquentielle de données. Si l’opérateur [ ] qui permet d’accéder directement à un élément est disponible sur les objets de type vector, il ne l’est pas sur ceux de type list. L’accès aux éléments ainsi que l'ajout et la suppression en milieu de liste sont rendus possibles par l'intermédiaire des itérateurs. Grâce à la structure de liste chaînée, ces opérations sont très efficaces. Il existe également des fonctions spécifiques à la classe générique list. Voici par exemple comment créer une liste d’entiers :

list<int> Liste1, Liste2; // liste d'entiers vide

Il est aussi possible d'initialiser une liste avec la même valeur. Le premier argument du constructeur fournit le nombre d’éléments de la liste (5), le second la valeur (77) de chaque nœud. list<int> Liste(5, 77); // construite avec 5 éléments de type int ayant tous la valeur 77

Pour accéder à la valeur de tête ou de queue : Liste.front(); // Retourne la valeur en tête de liste

Liste.back(); // Retourne la valeur en queue de liste

Ces 2 fonctions retournent des références, c'est-à-dire qu'on peut s'en servir pour modifier le premier où le dernier élément d'une liste. Par exemple

Liste.front() = 5 ;

6.3. Les itérateurs

Les itérateurs sont des pointeurs spécialisés qui accèdent aux éléments d'un conteneur. Un itérateur sur un conteneur ne peut être créé que par le conteneur même. Chaque conteneur fournit un itérateur portant le nom iterator (même const_iterator, reverse_iterator …) Il est doté d’une méthode begin qui renvoie un itérateur sur le premier de ses éléments, et d’une méthode end qui renvoie un itérateur sur une place se trouvant juste après le dernier élément. Un itérateur peut être incrémenté en utilisant l’opérateur ++ (attention, ne pas utiliser -- pour reculer, ça ne marche pas toujours), et accéder à l'élément pointé par

Page 64: Service d’Informatique - Université de Mons · préparation au master d’Informatique et Gestion. Nous renvoyons, néanmoins le lecteur désireux d'en savoir plus à l'un des

Chap6 : La bibliothèque STL

M. BENJELLOUN Info II S. Informatique

61

l'opérateur *. Les opérateurs == et != sont également disponibles pour vérifier si deux itérateurs pointent au même endroit. L'exemple suivant montre comment effectuer les parcours direct et inverse séquentiellement d’un conteneur de son début à sa fin :

list<int> Liste ;

list<int> ::iterator it ; // itérateur direct sur une liste de int

list<int> ::reverse_iterator rit ; // itérateur inverse sur une liste de int

for (it= Liste.begin() ; it != Liste.end() ; it++) {

cout << *it << endl; // *it désigne l’élément courant de la liste

}

for (rit= Liste.rbegin() ; rit != Liste.rend() ; rit++) {

cout << *rit << endl; // *rit désigne l’élément courant de la liste

}

Le programme suivant présente une application de deux conteneurs de séquence: vector (vecteur) et list (liste). Nous exposons un certain nombre de méthodes qui sont disponibles sur tous les types de conteneurs. Pour le reste des méthodes nous invitons le lecteur motivé à consulter les références ou faire une petite balade sur le net.

#include <iostream>

#include <string>

#include <vector>

#include <list>

using namespace std;

void Affiche_V(vector<int> V){

cout << "VECTOR" << " : ";

for (int i=0; i< V.size() ; i++) {

cout <<V[i] <<" ";

}

cout << endl;

}

void Affiche_L(list<int>::iterator it, list<int> Liste, string S){

cout << S << " : ";

if(Liste.empty()) cout<<"La liste est vide "<<endl;

else

for (it= Liste.begin() ; it != Liste.end() ; it++) {

cout << *it << " "; // *it désigne l'élément courant de la liste

}

cout << endl;

}

void R_Affiche_L(list<int>::reverse_iterator it, list<int> Liste, string S){

cout << S << " : ";

for (it= Liste.rbegin() ; it != Liste.rend() ; it++) {

cout << *it << " "; // *it désigne l'élément courant de la liste

}

cout << endl;

}

Page 65: Service d’Informatique - Université de Mons · préparation au master d’Informatique et Gestion. Nous renvoyons, néanmoins le lecteur désireux d'en savoir plus à l'un des

Chap6 : La bibliothèque STL

M. BENJELLOUN Info II S. Informatique

62

void main() {

// appel de constructeurs sans arguments et construit un conteneur vide

vector<int> Tab; // Crée un tableau d'entiers vide sans dim

list<int> Liste1, Liste2; // Crée deux listes d'entiers vides

list<int> ::iterator it ; // itérateur direct sur une liste de int

list<int> ::reverse_iterator rit ; // itérateur inverse sur une liste de int

int i;

// Saisie des entiers cout << "Saisir le prochain entier (-1 pour finir) : ";

cin >> i;

while (i != -1) {

Liste1.push_back(i);

Liste2.push_front(i);

Tab.push_back(i); // pas de Tab.push_front(i);

cout << "Saisir le prochain entier (-1 pour finir) : ";

cin >> i;

}

// Affichage des données Affiche_V(Tab);

Affiche_L( it, Liste1, "LISTE1");

Affiche_L( it, Liste2, "LISTE2");

R_Affiche_L( rit, Liste1, "LISTE1 Inverse");

// Nombre d'éléments des containers cout << "Il y a " << Tab.size()

<< " Elements dans le tableau" << endl;

cout << "Il y a " << Liste1.size()

<< " Elements dans la liste1" << endl;

// Accès à des éléments cout << "Premier et dernier element du tableau : "

<< Tab.front() << " ; "<< Tab.back()<< endl;

cout << "Premier et dernier element de la liste1 : "

<< Liste1.front() << " ; "<< Liste1.back() << endl; int milieu = Tab.size()/2;

cout << "Element de milieu de tableau : "

<< Tab[milieu] << endl;

// supprimer et ajouter des éléments cout << "Sup Dernier " << endl;

Liste1.pop_back();

Affiche_L( it, Liste1, "LISTE1");

Tab.pop_back();

Affiche_V(Tab);

cout << "Sup premier " << endl;

Liste1.pop_front(); // pas de pop_front() pour Tab

Affiche_L( it, Liste1, "LISTE1");

cout << "ERASE premier+2" << endl;

Tab.erase(Tab.begin()+2);

Affiche_V(Tab);

Page 66: Service d’Informatique - Université de Mons · préparation au master d’Informatique et Gestion. Nous renvoyons, néanmoins le lecteur désireux d'en savoir plus à l'un des

Chap6 : La bibliothèque STL

M. BENJELLOUN Info II S. Informatique

63

cout << "Inserer dernier-1" << endl;

Tab.insert(Tab.end()-1, 99);

Affiche_V(Tab);

cout << "ERASE Segment entre 1er et dernier" << endl;

Tab.erase(Tab.begin()+1, Tab.end()-1);

Affiche_V(Tab);

cout << "Tout Effacer " << endl;

Tab.clear();

Affiche_V(Tab);

}

Résultats

de

l'exécution

Saisir le prochain entier (-1 pour finir) : 1

Saisir le prochain entier (-1 pour finir) : 2

Saisir le prochain entier (-1 pour finir) : 55

Saisir le prochain entier (-1 pour finir) : 33

Saisir le prochain entier (-1 pour finir) : 4

Saisir le prochain entier (-1 pour finir) : 9

Saisir le prochain entier (-1 pour finir) : 7

Saisir le prochain entier (-1 pour finir) : -1

VECTOR : 1 2 55 33 4 9 7

LISTE1 : 1 2 55 33 4 9 7

LISTE2 : 7 9 4 33 55 2 1

LISTE1 Inverse : 7 9 4 33 55 2 1

Il y a 7 Elements dans le tableau

Il y a 7 Elements dans la liste1

Premier et dernier element du tableau : 1 ; 7

Premier et dernier element de la liste1 : 1 ; 7

Element de milieu de tableau : 33

Sup Dernier

LISTE1 : 1 2 55 33 4 9

VECTOR : 1 2 55 33 4 9

Sup premier

LISTE1 : 2 55 33 4 9

ERASE premier+2

VECTOR : 1 2 33 4 9

Inserer dernier-1

VECTOR : 1 2 33 4 99 9

ERASE Segment entre 1er et dernier

VECTOR : 1 9

Tout Effacer

VECTOR :

Rqs • Le parcours de la liste ne peut se faire qu’à l’aide d’un itérateur et ne peut se faire

comme pour vector à l’aide de : for (i=0; i< N ; i++). • L’utilisateur n’a pas à se soucier de l’allocation ou de la libération de la mémoire. C’est

vrai lors de l’insertion d’éléments (.push_back(i); .push_front(i); .insert) et aussi à la fin du programme : aucune instruction particulière n’est nécessaire pour restituer la mémoire occupée par les conteneurs. À la sortie du bloc dans lequel ils sont définis, leur destructeur se charge de libérer toutes les ressources occupées.

Programme 6.1.

Page 67: Service d’Informatique - Université de Mons · préparation au master d’Informatique et Gestion. Nous renvoyons, néanmoins le lecteur désireux d'en savoir plus à l'un des

Chap6 : La bibliothèque STL

M. BENJELLOUN Info II S. Informatique

64

6.4. Les algorithmes

Les algorithmes sont des patrons de fonction dans la STL permettant d’effectuer des opérations et des manipulations des données sur les conteneurs. Afin de pouvoir s’appliquer à plusieurs types de conteneurs, les algorithmes ne prennent pas de conteneurs en arguments, mais des itérateurs qui permettent de désigner une partie ou tout un conteneur.

Nous n'allons pas détailler ici tous les algorithmes, nous invitons le lecteur à consulter la documentation pour plus d'informations. Nous signalons juste qu’il existe :

• sort() : Trie par ordre croissant les éléments entre début et fin à l’intérieur d’un conteneur sort (Itérateur début, Itérateur fin);

Exemple : sort(v.begin(),v.end());

• find() : Trouver la position de Valeur entre début et fin. Retourne comme résultat un itérateur sur cet élément.

find(Itérateur début, Itérateur fin, const Type&Valeur);

Exemple : find(v.begin(),v.end(), 15);

• search() : Trouver la position de la sous-séquence comprise entre début2 et fin2 à l’intérieur de la séquence comprise entre début1 et fin2.

search(Itér début1, Itér fin1, Itér début2, Itér fin2);

Exemple : search(v1.begin(), v1.end(), v2.begin(), v2.end());

• unique() : Supprimer les doublons dans la séquence source (appliquer aux couples d'éléments successifs).

• replace() : Remplacer tous les val1 par val2 entre début et fin. replace(Itér début, Itér fin, const Type&Va11, const Type&Va12);

Exemple:replace(v.begin(),v.end(), 2, 4); // Remplace tous les 2 par des 4

• reverse() : Inverser l'ordre des éléments d'une séquence. Exemple : reverse(v.begin(),v.end()) ;

• min_elementet() et max_element(): Retournent un itérateur référençant respectivement le plus petit et le plus grand des éléments de la séquence.

Exemple : cout << *min_element(v.begin(),v.end()) << endl; cout << *max_element(t, t+10) << endl;

• lower_bound() et upper_bound() : Recherche binaire déterminant la première et la dernière position à laquelle Val peut être insérée dans la séquence ordonnée spécifiée par les itérateurs début et fin sans en briser l'ordre.

lower_bound (Itér début, Itér fin, const Type&Va1); upper_bound (Itér début, Itér fin, const Type&Va1);

Exemple : lower_bound(v.begin(),v.end(), 5) ;

Page 68: Service d’Informatique - Université de Mons · préparation au master d’Informatique et Gestion. Nous renvoyons, néanmoins le lecteur désireux d'en savoir plus à l'un des

Chap6 : La bibliothèque STL

M. BENJELLOUN Info II S. Informatique

65

• merge() : Fusionner deux listes d’éléments ordonnées. Exemple : Liste3.merge(Liste2);

// Liste3 = Liste3 + Liste2 en s'appuyant sur l'opérateur >; à la fin Liste2 est vide

• copy() : Recopie l’intervalle [v.begin(), v.end()] à partir de la position L.begin(). Copy(v.begin(), v.end(), L.begin() ) ;

• remove() : Supprime les éléments valant Val de la séquence [v.begin(), v.end()]. remove(v.begin(), v.end(), Val);

/* En réalité, remove() place les éléments égaux à la fin du conteneur ; la suppression se

fera ultérieurement par la méthode erase du conteneur */

Pour utiliser ces algorithmes, il suffit d’inclure dans le programme <algorithm>. Voici un programme d’utilisation de certains algorithmes.

#include <iostream>

#include <string>

#include <vector>

#include <list>

#include <algorithm>

using namespace std;

void Affiche_V(vector<int> V, string S){

cout << S << " : ";

for (int i=0; i< V.size() ; i++) {

cout <<V[i] <<" ";

}

cout << endl;

}

void Affiche_L(list<int>::iterator it, list<int> Liste, string S){

cout << S << " : ";

for (it= Liste.begin() ; it != Liste.end() ; it++) {

cout << *it << " ";

}

cout << endl;

}

void main() {

int T1[7] = { 3, 5, 9, 1, 4, 18, 5};

int T2[5] = { 9, 1, 7, 2, 5};

int TT[15]={0};

vector<int> V1(T1, T1+7), V2(T2, T2+5), VV(TT, TT+15);

Affiche_V(V1, "Vect1 Initial");

Affiche_V(V2, "Vect2 Initial");

Affiche_V(VV, " VV Initial");

int Seq[2] = {9, 1};

int *p;

p = search(T1, T1+7, Seq, Seq+2);

cout << "........ En Vect1 :{9,1} en position "

<< p - T1 << endl;

Page 69: Service d’Informatique - Université de Mons · préparation au master d’Informatique et Gestion. Nous renvoyons, néanmoins le lecteur désireux d'en savoir plus à l'un des

Chap6 : La bibliothèque STL

M. BENJELLOUN Info II S. Informatique

66

p = search(T2, T2+5, Seq, Seq+2);

cout << "........ En Vect2 :{9,1} en position "

<< p - T2 << endl;

sort(V1.begin(), V1.end());

sort(V2.begin(), V2.end());

Affiche_V(V1, "Vect1 Trie ");

Affiche_V(V2, "Vect2 Trie ");

cout << "Min 1 : " << *min_element(V1.begin(),V1.end()) << endl;

cout << "Max It1: " << *max_element(T1, T1+7) << endl;

cout << "Max T1 : " << *max_element(V1.begin(),V1.end()) << endl;

sort(T1, T1+7);

// Détermine les positions possibles d'insertion d'un 5 : cout << "Pour Vect1 Trie: 5 peut etre insere de " <<

lower_bound(T1, T1+7, 5) - T1 <<

" a " <<

upper_bound(T1, T1+7, 5) - T1 << endl;

reverse(V1.begin(), V1.end());

Affiche_V(V1, "Vect1 Trie Reverse");

replace(V1.begin(), V1.end(), 5, 4);

Affiche_V(V1, "Vect1 Trie Reverse Replace 5 par 4");

Affiche_V(VV, "Vect Avant ");

copy(V1.begin(), V1.end(), VV.begin() ) ;

Affiche_V(VV, "Vect + V1 ");

copy(V2.begin(), V2.end(), VV.begin()+7 ) ;

Affiche_V(VV, "Vect +V1+V2 ");

sort(VV.begin(), VV.end());

Affiche_V(VV, "Vect Trie ");

list<int> L1, L2, L3;

list<int> ::iterator it ;

L1.insert(L1.begin(),VV.begin(), VV.end());

Affiche_L(it, L1, "Liste L1 ");

L1.unique();

Affiche_L(it, L1, "L1 Unique ");

L1.remove(4); // supprime tous éléments égaux à 4

Affiche_L(it, L1, "L1 - 4 ");

L2.push_front(5); // L2 : 5

L2.push_front(7); // L2 : 7 5

L2.push_back(4); // L2 : 7 5 4

L2.push_back(2); // L2 : 7 5 4 2

L2.push_front(3); // L2 : 3 7 5 4 2

Affiche_L(it, L2, "L2 :....... ");

L2.sort();

Affiche_L(it, L2, "L2 Trie ....");

Affiche_L(it, L1, "L1 :....... ");

Page 70: Service d’Informatique - Université de Mons · préparation au master d’Informatique et Gestion. Nous renvoyons, néanmoins le lecteur désireux d'en savoir plus à l'un des

Chap6 : La bibliothèque STL

M. BENJELLOUN Info II S. Informatique

67

L2.merge(L1);

Affiche_L(it, L2, "L2 merge L1 ");

Affiche_L(it, L1, "L1 apres merge....");

L2.unique();

Affiche_L(it, L2, "L2 Unique ");

L3.push_front(99);

L3.splice(L3.end(),L2);

Affiche_L(it, L3, "L3 splice L2 : ");

Affiche_L(it, L2, "L2 apres splice .... ");

}

Programme 6.2.

Résultats

de

l'exécution

Vect1 Initial : 3 5 9 1 4 18 5

Vect2 Initial : 9 1 7 2 5

VV Initial : 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0

........ En Vect1 :{9,1} en position 2

........ En Vect2 :{9,1} en position 0

Vect1 Trie : 1 3 4 5 5 9 18

Vect2 Trie : 1 2 5 7 9

Min 1 : 1

Max It1: 18

Max T1 : 18

Pour Vect1 Trie: 5 peut etre insere de 3 a 5

Vect1 Trie Reverse : 18 9 5 5 4 3 1

Vect1 Trie Reverse Replace 5 par 4 : 18 9 4 4 4 3 1

Vect Avant : 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0

Vect + V1 : 18 9 4 4 4 3 1 0 0 0 0 0 0 0 0

Vect +V1+V2 : 18 9 4 4 4 3 1 1 2 5 7 9 0 0 0

Vect Trie : 0 0 0 1 1 2 3 4 4 4 5 7 9 9 18

Liste L1 : 0 0 0 1 1 2 3 4 4 4 5 7 9 9 18

L1 Unique : 0 1 2 3 4 5 7 9 18

L1 - 4 : 0 1 2 3 5 7 9 18

L2 :....... : 3 7 5 4 2

L2 Trie .... : 2 3 4 5 7

L1 :....... : 0 1 2 3 5 7 9 18

L2 merge L1 : 0 1 2 2 3 3 4 5 5 7 7 9 18

L1 apres merge.... :

L2 Unique : 0 1 2 3 4 5 7 9 18

L3 splice L2 : : 99 0 1 2 3 4 5 7 9 18

L2 apres splice .... :

Page 71: Service d’Informatique - Université de Mons · préparation au master d’Informatique et Gestion. Nous renvoyons, néanmoins le lecteur désireux d'en savoir plus à l'un des

Chap6 : La bibliothèque STL

M. BENJELLOUN Info II S. Informatique

68

ExercicesExercicesExercicesExercices 6.1. Remplacez dans le code de l’exercice 5.3.du poulailler le tableau dynamique par une liste.

6.2. Remplacez dans le code de l’exercice 5.4. les tableaux dynamiques par des listes.

6.3. Complétez la fonction void OrderedList<T> ::add(const T& t) {…

#include <iostream>

#include <list>

#include <string>

using namespace std;

template <class T> class OrderedList : public list<T> {

public:

void add(const T&);

};

void main() {

OrderedList<string> nom;

nom.add(string("Toto"));

nom.add(string("Mimi"));

nom.add(string("Picsou"));

nom.add(string("Aladin"));

nom.add(string("Jasmine"));

list<string>::const_iterator it=nom.begin();

while (it!=nom.end() )

cout << *it++ << endl;

}

template <class T> void OrderedList<T>::add(const T& t) {

...

}

pour que ce programme affiche le résultat suivant sans utiliser la fonction prédéfinie de l’algorithme sort().

Aladin

Jasmine

Mimi

Picsou

Toto

6.4. Donnez le code d’un programme qui crée une liste L1 contenant les 10 premières lettres de l’alphabet, crée une nouvelle liste L2 égale à L1 et efface d’un bloc la première moitié de L2.

6.5. Donnez le code d’un programme qui crée un vecteur v1 de 10 float régulièrement répartis entre 0.0 (inclus) et 1.0 (non compris), crée un nouveau vecteur v2 égal à v1 et efface élément par élément la deuxième moitié de v2.

6.6. Donnez le code d’un programme qui crée un vecteur de 5 string de longueurs différentes, affiche ces chaînes, les trie par ordre lexicographique et réaffiche les chaînes triées.

6.7. Donnez le code d’un programme qui crée une liste de string représentant la phrase ”il fait beau”, trouve la position du mot ”beau” et insère le mot ”tres” à la position précédente.

Page 72: Service d’Informatique - Université de Mons · préparation au master d’Informatique et Gestion. Nous renvoyons, néanmoins le lecteur désireux d'en savoir plus à l'un des

Chap6 : La bibliothèque STL

M. BENJELLOUN Info II S. Informatique

69

6.8. En utilisant l’algorithme find de STL et les templates, donnez le code de la fonction Test pour que ce programme affiche le résultat suivant:

void main() {

vector<string> V(3);

vector<string>::iterator itV;

V[0]="V0";

V[1]="V1";

V[2]="V2";

string St1="V1", St2="V5";

Test(V, itV, St1, "Vecteur");

Test(V, itV, St2, "Vecteur");

list<int> L;

list<int>::iterator itL;

for( int i = 0; i < 5; i++ ) {

L.push_back(i);

}

int x=10, y=2;

Test(L, itL, x, "List");

Test(L, itL, y, "List");

}

6.9. Ecrivez les 2 classes Etudiant et Personne. La classe Etudiant hérite de la classe Personne. Si nécessaire, des fonctions membres peuvent être ajoutées aux classes.

Ce programme doit gérer en boucle le Menu suivant :

1) Saisie d’1 List d’Etudiant de 1ere (insertion en Fin) 2) Saisie d’1 Vector d’Etudiant de 2d (insertion au début) 3) Calculer et afficher le nombre de filles (F) dans la liste List.

Test Vecteur

V1 est dans Vecteur

Test Vecteur

V5 n'est pas dans Vecteur

Test List

10 n'est pas dans List

Test List

2 est dans List

Etudiant

int Note[2];

char sexe ;

public :

void saisie();

void afficher()

Personne

string Nom;

public :

void saisie();

void afficher();

Page 73: Service d’Informatique - Université de Mons · préparation au master d’Informatique et Gestion. Nous renvoyons, néanmoins le lecteur désireux d'en savoir plus à l'un des

Chap6 : La bibliothèque STL

M. BENJELLOUN Info II S. Informatique

70

6.10. Donnez le code d’un programme qui manipule deux classes :

Une classe Etudiant dont les objets sont des étudiants caractérisés par int Id;

string nom; Une seconde classe Prof dont les objets sont des enseignants caractérisés par

int Id; string nom, Service;

Dans les deux classes (étudiants, enseignants), on déclarera au moins deux méthodes, l’une pour saisir et l’autre pour afficher chaque objet de la classe (Saisie() et Affichage() ).

La manipulation des étudiants se fera en utilisant un conteneur List alors que les Profs seront rangés dans un conteneur Vector. Ce programme doit gérer en boucle le menu suivant : A : Etudiants ou B : Profs

1 : Saisie et Affichage d’une List (Etud.) ou Vector (Profs) 2 : Ajouter au (début, milieu ou fin) et Affichage de l’ensemble 3 : Créer Dynamiquement 1 Element (Etud ou Prof) et Chercher s’il existe (Sans utiliser le tri) 4 : Tri selon NOM et Affichage de l’ensemble 5 : Tri selon Id et Affichage de l’ensemble 6 : Quitter

Le menu sera affiché via une fonction, les choix seront traités via l’instruction case. Votre programme utilisera une série de fonctions permettant de séparer les tâches. Si nécessaire, des fonctions membres peuvent être ajoutées aux classes. On y prévoira au moins un constructeur par classe permettant, comme vu aux séances d’exercices, l’incrémentation automatique de l’Id (Prof1 : Id=1, Prof2 : Id=2, … et Etud1 : Id1=1, Etud2 : Id2=2, ….). Optimisez votre code afin de permettre la réutilisation des morceaux de code.