Sunteți pe pagina 1din 47

Cours dinformatique

2003/2004

C++ : PROGRAMMATION-OBJET
SOMMAIRE :
Chapitre 1 : Le concept dobjet 1.1 1.2 1.3 1.4 1.5 Objet usuel . . Objet informatique Encapsulation . Strat egie D.D.U Mise en uvre . . . . . Classe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 1 2 3 4 5 8 8 9 10 11 13 17

Chapitre 2 : Programmation des classes 2.1 2.2 2.3 2.4 2.5 2.6 Un exemple . . . . . . Fonctions-membres . . . Constructeurs et destructeurs Surcharge des op erateurs . R ealisation dun programme Pointeurs et objets . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

Chapitre 3 : H eritage 3.1 3.2 3.3 3.4 3.5 3.6 Relations a-un, est-un, Classes d eriv ees . Polymorphisme . Exemple . . . H eritage multiple . Classes abstraites .

. 19 . . . . . . 19 19 22 24 29 30

utilise-un . . . . . . . . . . . . . . . . . . . . . . . . . . . et << . . . . . . . .

Chapitre 4 : Entr ees & sorties 4.1 4.2 4.3 4.4 4.5 La librairie iostream.h La librairire fstream.h Fonctions de contr ole . Surcharge des op erateurs Formatage des donn ees . . . . . . . . . . . >> . . . .

. 33 . . . . . 33 35 36 37 38

Appendice .

. 39 . 39 . 40

A.1 Relation damiti e A.2 Patrons . . .

Chapitre 5 : Introduction a ` la programmation Windows . 42 5.1 Outils . . . . . . 5.2 Premier exemple . . . 5.3 Am elioration de linterface i . . . . . . . . . . . . . . . . . . . . . . . . . 42 . 43 . 43

ii

Chapitre 1

LE CONCEPT DOBJET

Apparue au d ebut des ann ees 70, la programmation orient ee objet r epond aux n ecessit es de linformatique professionnelle. Elle ore aux concepteurs de logiciels une grande souplesse de travail, permet une maintenance et une evolution plus ais ee des produits. Mais sa pratique passe par une approche radicalement di erente des m ethodes de programmation traditionnelles : avec les langages a ` objets, le programmeur devient metteur en sc` ene dun jeu collectif o` u chaque objet-acteur se voit attribuer un r ole bien pr ecis. Ce cours a pour but dexpliquer les r` egles de ce jeu. La syntaxe de base du langage C++, expos ee dans un pr ec edent cours, est suppos ee connue.

1.1 Objet usuel


(1.1.1) Comment d ecrire un objet usuel ? Prenons exemple sur la notice dutilisation dun appareil m enager. Cette notice a g en eralement trois parties : a. une description physique de lappareil et de ses principaux el ements (boutons, voyants lumineux, cadrans etc.), sch emas a ` lappui, b. une description des fonctions de chaque el ement, c. un mode demploi d ecrivant la succession des manuvres a ` faire pour utiliser lappareil. Seules les parties a et b sont intrins` eques ` a lappareil : la partie c concerne lutilisateur et rien nemp eche celui-ci de se servir de lappareil dune autre mani` ere, ou ` a dautres ns que celles pr evues par le constructeur. Nous retiendrons donc que pour d ecrire un objet usuel, il faut d ecrire ses composants, ` a savoir : 1. les di erents el ements qui le constituent, 2. les di erentes fonctions associ ees ` a ces el ements. (1.1.2) Les el ements qui constituent lobjet d enissent ` a chaque instant l etat de lobjet on peut dire : son aspect spatial. Les fonctions, quant a ` elles, d enissent le comportement de lobjet au cours du temps. Les el ements qui constituent lobjet peuvent se modier au cours du temps (par exemple, le voyant dune cafeti` ere peut etre allum e ou eteint). Un objet peut ainsi avoir plusieurs etats. Le nombre d etats possibles dun objet donne une id ee de sa complexit e. (1.1.3) Pour identier les composants dun objet usuel, une bonne m ethode consiste ` a faire de cet objet une description litt erale, puis de souligner les principaux noms communs et verbes. Les noms communs donnent les el ements constituants, les verbes donnent les fonctions. Illustrons cette m ethode dans le cas dun objet tr` es simple, un marteau :

On peut en faire la description suivante : Ce marteau comporte un manche en bois, une extr emit e plate en m etal et une extr emit e incurv ee egalement en m etal. Le manche permet de saisir le marteau, lextr emit e plate permet de frapper quelque chose et lextr emit e incurv ee permet darracher quelque chose. Do` u la che descriptive : nom : marteau el ements : fonctions : manche saisir extr emit e plate frapper extr emit e incurv ee arracher Pour v erier que nous navons rien oubli e dimportant dans une telle che descriptive, il faut imaginer lobjet a ` luvre dans une petite sc` ene. Le d eroulement de laction peut alors r ev eler des composants qui nous auraient echapp e en premi` ere analyse (dans notre exemple, quatre acteurs : un marteau, un clou, un mur et un individu ; pour planter le clou dans le mur, lindividu saisit le marteau par son manche, puis frappe sur le clou avec lextr emit e plate ; il saper coit alors que le clou est mal plac e, et larrache avec lextr emit e incurv ee). (1.1.4) Prenons comme deuxi` eme exemple un chronom` etre digital :
B A

12:18

Ce chronom` etre comporte un temps qui sache et deux boutons A et B . Quand on presse sur A, on d eclenche le chronom` etre, ou bien on larr ete. Quand on presse sur B , on remet a ` z ero le chronom` etre. Do` u la che descriptive : nom : chronom` etre el ements : fonctions : boutons A, B acher temps presser sur un bouton d eclencher arr eter remettre ` a z ero Remarquons que lutilisateur ne peut pas modier directement le temps ach e : il na acc` es ` a ce temps que de mani` ere indirecte, par linterm ediaire des fonctions de lobjet. Cette notion dacc` es indirect jouera un r ole important dans la suite (1.3).

1.2 Objet informatique Classe


(1.2.1) Lordinateur est un appareil poss edant une tr` es grande complexit e li ee ` a un tr` es grand nombre d etats et un comportement tr` es vari e li e` a la fa con dont on le programme. Il nous servira dobjet universel capable de simuler la plupart des objets usuels. (1.2.2) Programmer un ordinateur, cest lui fournir une s erie dinstructions quil doit ex ecuter. Un langage de programmation evolu e doit simplier le travail du programmeur en lui orant la possibilit e: 2

d ecrire son programme sous forme de petits modules autonomes, de corriger et faire evoluer son programme avec un minimum de retouches, dutiliser des modules tout faits et ables. De ce point de vue, les langages ` a objets comme le C++ sont sup erieurs aux langages classiques comme le C, car ils font reposer le gros du travail sur des briques logicielles intelligentes : les objets. Un programme nest alors quune collection dobjets mis ensemble par le programmeur et qui coop` erent, un peu comme les joueurs dune equipe de football supervis es par leur entra neur. (1.2.3) Transpos e en langage informatique, (1.1.1) donne : Un objet est une structure informatique regroupant : des variables, caract erisant l etat de lobjet, des fonctions, caract erisant le comportement de lobjet. Les variables (resp. fonctions) sappellent donn ees-membres (resp. fonctions-membres ou encore m ethodes) de lobjet. Loriginalit e dans la notion dobjet, cest que variables et fonctions sont regroup ees dans une m eme structure. (1.2.4) Un ensemble dobjets de m eme type sappelle une classe. Tout objet appartient a ` une classe, on dit aussi quil est une instance de cette classe. Par exemple, si lon dispose de plusieurs chronom` etres analogues ` a celui d ecrit en (1.1.4), ces chronom` etres appartiennent tous a ` une m eme classe chronom` etre, chacun est une instance de cette classe. En d ecrivant la classe chronom` etre, on d ecrit la structure commune ` a tous les objets appartenant a ` cette classe. (1.2.5) Pour utiliser les objets, il faut dabord d ecrire les classes auxquelles ces objets appartiennent. La description dune classe comporte deux parties : une partie d eclaration, che descriptive des donn ees et fonctions-membres des objets de cette classe, qui servira dinterface avec le monde ext erieur, une partie impl ementation, contenant la programmation des fonctions-membres.

1.3 Encapsulation
(1.3.1) Dans la d eclaration dune classe, il est possible de prot eger certaines donn ees-membres ou fonctions-membres en les rendant invisibles de lext erieur : cest ce quon appelle lencapsulation. A quoi cela sert-il ? Supposons quon veuille programmer une classe Cercle avec comme donn eesmembres : un point repr esentant le centre, un nombre repr esentant le rayon, un nombre repr esentant la surface du cercle. Permettre lacc` es direct ` a la variable surface, cest sexposer ` a ce quelle soit modi ee depuis lext erieur, et cela serait catastrophique puisque lobjet risquerait alors de perdre sa coh erence (la surface d epend en fait du rayon). Il est donc indispensable dinterdire cet acc` es, ou au moins permettre ` a lobjet de le contr oler. (1.3.2) Donn ees et fonctions-membres dun objet O seront d eclar ees publiques si on autorise leur utilisation en dehors de lobjet O, priv ees si seul lobjet O peut y faire r ef erence. Dans la d eclaration dune classe, comment d ecider de ce qui sera public ou priv e ? Une approche simple et s ure consiste ` a d eclarer syst ematiquement les donn ees-membres priv ees et les fonctions-membres publiques. On peut alors autoriser lacc` es aux donn ees-membres (pour consultation ou modication) par des fonctions pr evues ` a cet eet, appel ees fonctions dacc` es . Ainsi, la d eclaration de la classe Cercle ci-dessus pourrait ressembler ` a: 3

classe : Cercle priv e: public : centre Fixer centre rayon Fixer rayon surface Donner surface Tracer

Dans le cas dune classe chronometre (1.1.4), il surait de ne d eclarer publiques que les seules fonctionsmembres Afficher et Presser sur un bouton pour que les chronom` etres puissent etre utilis es normalement, en toute s ecurit e.

1.4 Strat egie D.D.U


(1.4.1) En C++, la programmation dune classe se fait en trois phases : d eclaration, d enition, utilisation (en abr eg e : D.D.U).

D eclaration : cest la partie interface de la classe. Elle se fait dans un chier dont le nom se termine par .h Ce chier se pr esente de la fa con suivante : class Maclasse { public: d eclarations des donn ees et fonctions-membres publiques private: d eclarations des donn ees et fonctions-membres priv ees };

D enition : cest la partie impl ementation de la classe. Elle se fait dans un chier dont le nom se termine par .cpp Ce chier contient les d enitions des fonctions-membres de la classe, cest-` a-dire le code complet de chaque fonction.

Utilisation : elle se fait dans un chier dont le nom se termine par .cpp

(1.4.2) Structure dun programme en C++ Nos programmes seront g en eralement compos es dun nombre impair de chiers : pour chaque classe : un chier .h contenant sa d eclaration, un chier .cpp contenant sa d enition, un chier .cpp contenant le traitement principal. Ce dernier chier contient la fonction main, et cest par cette fonction que commence lex ecution du programme. Sch ematiquement : 4

CLASS1.H
class Classe1 { .......... };

CLASS2.H
class Classe2 { .......... };

d cclaration

CLASS1.CPP
#include "CLASS1.H" ..........

CLASS2.CPP
#include "CLASS2.H" ..........

dcfinition

MAIN.CPP
#include "CLASS1.H" #include "CLASS2.H" .......... void main() { .......... }

utilisation

(1.4.3) Rappelons que la directive dinclusion #include permet dinclure un chier de d eclarations dans un autre chier : on ecrira #include <untel.h> sil sagit dun chier standard livr e avec le compilateur C++, ou #include "untel.h" sil sagit dun chier ecrit par nous-m emes.

1.5 Mise en uvre


(1.5.1) Nous donnons ici un programme complet an dillustrer les principes expos es au paragraphe pr ec edent. Ce programme simule le fonctionnement dun parcm` etre. Le programme se compose de trois chiers : parcmetr.h qui contient la d eclaration de la classe Parcmetre, parcmetr.cpp qui contient la d enition de la classe Parcmetre, simul.cpp qui contient lutilisation de la classe Parcmetre.

// ------------------------ parcmetr.h -----------------------// ce fichier contient la d eclaration de la classe Parcmetre class Parcmetre { public: Parcmetre(); void Affiche(); void PrendsPiece(float valeur); private: int heures, minutes; };

// constructeur de la classe // affichage du temps de stationnement // introduction dune pi` ece // chiffre des heures... // et des minutes

// ------------------------ parcmetr.cpp --------------------// ce fichier contient la d efinition de la classe Parcmetre #include <iostream.h> #include "parcmetr.h" Parcmetre::Parcmetre() { heures = minutes = 0; } // pour les entr ees-sorties // d eclaration de la classe Parcmetre // initialisation dun nouveau parcm` etre

void Parcmetre::Affiche() { cout cout cout cout cout cout cout cout } << << << << << << << <<

// affichage du temps de stationnement restant // et du mode demploi du parcm` etre

"\n\n\tTEMPS DE STATIONNEMENT :"; heures << " heures " << minutes << " minutes"; "\n\n\nMode demploi du parcm` etre :"; "\n\tPour mettre une pi` ece de 10 centimes : tapez A"; "\n\tPour mettre une pi` ece de 20 centimes : tapez B"; "\n\tPour mettre une pi` ece de 50 centimes : tapez C"; "\n\tPour mettre une pi` ece de 1 euro : tapez D"; "\n\tPour quitter le programme : tapez Q";

void Parcmetre::PrendsPiece(float valeur) // introduction dune pi` ece { minutes += valeur * 10; // 1 euro = 50 minutes de stationnement while (minutes >= 60) { heures += 1; minutes -= 60; } if (heures >= 3) // on ne peut d epasser 3 heures { heures = 3; minutes = 0; } }

// ------------------------ simul.cpp --------------------// ce fichier contient lutilisation de la classe Parcmetre #include <iostream.h> #include "parcmetr.h" void main() { Parcmetre p; char choix = X; // pour les entr ees-sorties // pour la d eclaration de la classe Parcmetre // traitement principal // d eclaration dun parcm` etre p

while (choix != Q) // boucle principale d ev enements { p.Affiche(); cout << "\nchoix ? --> "; cin >> choix; // lecture dune lettre switch (choix) // action correspondante { case A : p.PrendsPiece(1); break; case B : p.PrendsPiece(2); break; case C : p.PrendsPiece(5); break; case D : p.PrendsPiece(10); } }

(1.5.2) Op erateurs . et :: Dans une expression, on acc` ede aux donn ees et fonctions-membres dun objet gr ace ` a la notation point ee : si mon objet est une instance de Ma classe, on ecrit mon objet.donnee (` a condition que donnee gure es en soit possible : voir (1.3)). dans la d eclaration de Ma classe, et que lacc` Dautre part, dans la d enition dune fonction-membre, on doit ajouter <nom de la classe>:: devant le nom de la fonction. Par exemple, la d enition dune fonction-membre truc() de la classe Ma classe aura la forme suivante :
<type> Ma classe::truc(<d eclaration de param` etres formels>) <instruction-bloc>

Lappel se fait avec la notation point ee, par exemple : mon obj.truc() ; en programmation-objet, on dit parfois quon envoie le message truc() ` a lobjet destinataire mon obj. Exceptions : certaines fonctions-membres sont d eclar ees sans type de r esultat et ont le m eme nom que celui de la classe : ce sont les constructeurs. Ces constructeurs permettent notamment dinitialiser les objets d` es leur d eclaration. (1.5.3) R ealisation pratique du programme Elle se fait en trois etapes : 1) cr eation des chiers sources parcmetr.h, parcmetr.cpp et simul.cpp. 2) compilation des chiers .cpp, a ` savoir parcmetr.cpp et simul.cpp, ce qui cr ee deux chiers objets parcmetr.obj et simul.obj (ces chiers sont la traduction en langage machine des chiers .cpp correspondants), 3) edition des liens entre les chiers objets, pour produire nalement un chier ex ecutable dont le nom se termine par .exe. Dans lenvironnement Visual C++ de Microsoft, les phases 2 et 3 sont automatis ees : il sut de cr eer les chiers-sources .h et .cpp, dajouter ces chiers dans le projet et de lancer ensuite la commande build. Remarque. On peut ajouter directement dans un projet un chier .obj : il nest pas n ecessaire de disposer du chier source .cpp correspondant. On pourra donc travailler avec des classes d ej` a compil ees.

Chapitre 2

PROGRAMMATION DES CLASSES

2.1 Un exemple
(2.1.1) Voici la d eclaration et la d enition dune classe Complexe d ecrivant les nombres complexes, et un programme qui en montre lutilisation.

// ------------------------ complexe.h -----------------------// d eclaration de la classe Complexe class Complexe { public: Complexe(float x, float y); Complexe(); void Lis(); void Affiche(); Complexe operator+(Complexe g); private: float re, im; };

// // // // // // //

premier constructeur de la classe : fixe la partie r eelle a ` x, la partie imaginaire a ` y second constructeur de la classe : initialise un nombre complexe a ` 0 lit un nombre complexe entr e au clavier affiche un nombre complexe surcharge de lop erateur daddition +

// parties r eelle et imaginaire

// ------------------------ complexe.cpp --------------------// d efinition de la classe Complexe #include <iostream.h> #include "complexe.h" // pour les entr ees-sorties // d eclaration de la classe Complexe // constructeur avec param` etres

Complexe::Complexe(float x, float y) { re = x; im = y; } Complexe::Complexe() { re = 0.0; im = 0.0; } void Complexe::Lis() { cout << "Partie r eelle ? "; cin >> re; cout << "Partie imaginaire ? cin >> im; } void Complexe::Affiche() { cout << re << " + i " << im; }

// constructeur sans param` etre

// lecture dun complexe

";

// affichage dun complexe

Complexe Complexe::operator+(Complexe g) {

// surcharge de lop erateur +

return Complexe(re + g.re, im + g.im); }

// appel du constructeur

// ------------------------ usage.cpp --------------------// exemple dutilisation de la classe Complexe #include <iostream.h> #include "complexe.h" void main() { Complexe z1(0.0, 1.0); Complexe z2; // pour les entr ees-sorties // pour la d eclaration de la classe Complexe // traitement principal // appel implicite du constructeur param etr e // appel implicite du constructeur non param etr e

z1.Affiche(); // affichage de z1 cout << "\nEntrer un nombre complexe : "; z2.Lis(); // saisie de z2 cout << "\nVous avez entr e : "; z2.Affiche(); // affichage de z2 Complexe z3 = z1 + z2; // somme de deux complexes gr^ ace a ` lop erateur + cout << "\n\nLa somme de "; z1.Affiche(); cout << " et "; z2.Affiche(); cout << " est "; z3.Affiche(); }

(2.1.2) Remarques Les constructeurs permettent dinitialiser les objets. Nous verrons plus pr ecis ement leur usage au paragraphe 3. Nous reviendrons egalement sur la surcharge des op erateurs (paragraphe 4). Dans ce programme, nous donnons lexemple de lop erateur + qui est red eni pour permettre dadditionner deux nombres complexes. Cela permet ensuite d ecrire tout simplement z3 = z1 + z2 entre nombre complexes. Cette possibilit e de red enir (on dit aussi surcharger) les op erateurs usuels du langage est un des traits importants du C++.

2.2 Fonctions-membres
(2.2.1) Lobjet implicite Rappelons que pour d ecrire une classe (cf (1.2.5)), on commence par d eclarer les donn ees et fonctionsmembres dun objet de cette classe, puis on d enit les fonctions-membres de ce m eme objet. Cet objet nest jamais nomm e, il est implicite (au besoin, on peut y faire r ef erence en le d esignant par *this). Ainsi dans lexemple du paragraphe (2.1.1), lorsquon ecrit les d enitions des fonctions-membres de la classe Complexe, on se r ef` ere directement aux variables re et im, et ces variables sont les donn ees-membres du nombre complexe implicite quon est en train de programmer et qui nest jamais nomm e. Mais sil y a un autre nombre complexe, comme g dans la d enition de la fonction operator+, les donn ees-membres de lobjet g sont d esign ees par la notation point ee habituelle, a ` savoir g.re et g.im (1.5.2). Notons au passage que, bien que ces donn ees soient priv ees, elles sont accessibles a ` ce niveau puisque nous sommes dans la d enition de la classe Complexe. (2.2.2) Flux de linformation Chaque fonction-membre est une unit e de traitement correspondant ` a une fonctionnalit e bien pr ecise et qui sera propre a ` tous les objets de la classe. Pour faire son travail lors dun appel, cette unit e de traitement dispose des informations suivantes : les valeurs des donn ees-membre (publiques ou priv ees) de lobjet auquel elle appartient, 9

les valeurs des param` etres qui lui sont transmises. En retour, elle fournit un r esultat qui pourra etre utilis e apr` es lappel. Ainsi : Avant de programmer une fonction-membre, il faudra identier quelle est linformation qui doit y entrer (param` etres) et celle qui doit en sortir (r esultat).

2.3 Constructeurs et destructeurs


(2.3.1) Un constructeur est une fonction-membre d eclar ee du m eme nom que la classe, et sans type : etres>); Nom classe(<param` Fonctionnement : a ` lex ecution, lappel au constructeur produit un nouvel objet de la classe, dont on peut pr evoir linitialisation des donn ees-membres dans la d enition du constructeur. Exemple : avec la classe Complexe d ecrite en (2.1.1), lexpression Complexe(1.0, 2.0) a pour valeur un nombre complexe de partie r eelle 1 et de partie imaginaire 2. Dans une classe, il peut y avoir plusieurs constructeurs ` a condition quils di` erent par le nombre ou le type des param` etres. Un constructeur sans param` etre sappelle constructeur par d efaut. (2.3.2) Initialisation des objets Dans une classe, il est possible ne pas mettre de constructeur. Dans ce cas, lors de la d eclaration dune variable de cette classe, lespace m emoire est r eserv e mais les donn ees-membres de lobjet ne re coivent pas de valeur de d epart : on dit quelles ne sont pas initialis ees. Au besoin, on peut pr evoir une fonctionmembre publique pour faire cette initialisation. En revanche : Sil y a un constructeur, il est automatiquement appel e lors de la d eclaration dune variable de la classe. Exemples avec la classe Complexe d eclar ee en (2.1.1) :
Complexe z; // appel automatique du constructeur par d efaut // equivaut a ` : Complexe z = Complexe(); // appel du constructeur param etr e // equivaut a ` : Complexe z = Complexe(1.0, 2.0);

Complexe z (1.0, 2.0);

On retiendra que : Lutilit e principale du constructeur est deectuer des initialisations pour chaque objet nouvellement cr e e. (2.3.3) Initialisations en cha ne Si une classe Class A contient des donn ees-membres qui sont des objets dune classe Class B, par exemple :
class Class A { public: Class A(...); ... private: Class B b1, b2; ... };

// constructeur

// deux objets de la classe Class B

alors, ` a la cr eation dun objet de la classe Class A, le constructeur par d efaut de Class B (sil existe) est automatiquement appel e pour chacun des objets b1, b2 : on dit quil y a des initialisations en cha ne. Mais pour ces initialisations, il est egalement possible de faire appel a ` un constructeur param etr e de ` condition de d enir le constructeur de Class A de la mani` ere suivante : Class B, a Class A :: Class A(...) <instruction-bloc> : b1 (...), b2 (...)

10

Dans ce cas, lappel au constructeur de Class A provoquera linitialisation des donn ees-membres b1, b2 (par appel au constructeur param etr e de Class B) avant lex ecution de l<instruction-bloc>. (2.3.4) Conversion de type Supposons que Ma classe comporte un constructeur ` a un param` etre de la forme : Ma classe(Mon type x); o` u Mon type est un type quelconque. Alors, chaque fois que le besoin sen fait sentir, ce constructeur assure la conversion automatique dune expression e de type Mon type en un objet de type Ma classe (` a savoir Ma classe(e)). Par exemple, si nous avons dans la classe Complexe le constructeur suivant :
Complexe::Complexe(float x) { re = x; im = 0.0; }

alors ce constructeur assure la conversion automatique float Complexe, ce qui nous permet d ecrire des instructions du genre : z1 = 1.0; z3 = z2 + 2.0; (z1, z2, z3 suppos es de type Complexe). (2.3.5) Destructeurs Un destructeur est une fonction-membre d eclar ee du m eme nom que la classe mais pr ec ed e dun tilda (~) et sans type ni param` etre : ~Nom classe(); Fonctionnement : a ` lissue de lex ecution dun bloc, le destructeur est automatiquement appel e pour eclar e dans ce bloc. Cela permet par exemple de programmer la chaque objet de la classe Nom classe d restitution dun environnement, en lib erant un espace-m emoire allou e par lobjet. Nous nen ferons pas souvent usage.

2.4 Surcharge des op erateurs


(2.4.1) En C++, on peut surcharger la plupart des op erateurs usuels du langage, cest-` a-dire les reprogrammer pour que, dans un certain contexte, ils fassent autre chose que ce quils font dhabitude. Ainsi dans lexemple (2.1.1), nous avons surcharg e lop erateur daddition + pour pouvoir lappliquer a ` deux nombres complexes et calculer leur somme. Notons egalement que les op erateurs dentr ees-sorties << et >> sont en r ealit e les surcharges de deux op erateurs de d ecalages de bits (appel es respectivement shift left et shift right). La surcharge dun op erateur <op> se fait en d eclarant, au sein dune classe Ma classe, une fonctionmembre appel ee operator <op>. Plusieurs cas peuvent se pr esenter, selon que <op> est un op erateur unaire (cest-` a-dire a ` un argument) ou binaire (cest-a-dire ` a deux arguments). Nous allons voir quelques exemples. (2.4.2) Cas dun op erateur unaire Nous voulons surcharger lop erateur unaire - pour quil calcule loppos e dun nombre complexe. Dans la classe Complexe d ecrite en (2.1.1), nous d eclarons la fonction-membre publique suivante :
Complexe operator-();

11

que nous d enissons ensuite en utilisant le constructeur param etr e de la classe :


Complexe Complexe::operator-() { return Complexe(-re, -im); }

Par la suite, si z est une variable de type Complexe, on pourra ecrire tout simplement lexpression -z pour d esigner loppos e de z, sachant que cette expression est equivalente a ` lexpression z.operator-() (message operator- destin e` a z). (2.4.3) Cas dun op erateur binaire Nous voulons surcharger lop erateur binaire - pour quil calcule la di erence de deux nombres complexes. Dans la m eme classe Complexe, nous d eclarons la fonction-membre publique suivante :
Complexe operator-(Complexe u);

que nous d enissons ensuite en utilisant egalement le constructeur param etr e de la classe :
Complexe Complexe::operator-(Complexe u) { return Complexe(re - u.re, im - u.im); }

Par la suite, si z1 et z2 sont deux variables de type Complexe, on pourra ecrire tout simplement lexpression z1 - z2 pour d esigner le nombre complexe obtenu en soustrayant z2 de z1, sachant que cette expression est equivalente a ` lexpression z1.operator-(z2) (message operator- destin e` a z1, appliqu e avec le param` etre dentr ee z2). (2.4.4) Autre cas dun op erateur binaire. Cette fois, nous d esirons d enir un op erateur qui, appliqu e` a deux objets dune m eme classe, donne une valeur dun type di erent. Ce cas est plus compliqu e que le pr ec edent. On consid` ere la classe culinaire suivante :
class Plat { public: float Getprix(); private: char nom[20]; float prix; } // d ecrit un plat propos e au menu dun restaurant

// fonction dacc` es donnant le prix : voir (1.3.2) // nom du plat // et son prix

Nous voulons surcharger lop erateur + pour quen ecrivant par exemple poulet + fromage, cela donne le prix total des deux plats (poulet et fromage suppos es de type Plat). Nous commen cons par d eclarer la fonction-membre :
float operator+(Plat p);

que nous d enissons par :


float Plat::operator+(Plat p) { return prix + p.Getprix(); }

et que nous pouvons ensuite utiliser en ecrivant par exemple poulet + fromage. Nous d enissons ainsi une loi daddition + : (Plat Plat) float. Mais que se passe-t-il si nous voulons calculer salade + poulet + fromage ? Par associativit e, cette expression peut egalement s ecrire : salade + (poulet + fromage) (salade + poulet) + fromage donc il nous faut d enir deux autres lois : - une loi + : (Plat float) float, - une loi + : (float Plat) float. La premi` ere se programme en d eclarant une nouvelle fonction-membre : 12

float operator+(float u);

que nous d enissons par :


float Plat::operator+(float u) { return prix + u; }

La deuxi` eme ne peut pas se programmer avec une fonction-membre de la classe Plat puisquelle sadresse a un float. Nous sommes contraints de d ` eclarer une fonction libre (cest-` a-dire hors de toute classe) :
float operator+(float u, Plat p);

que nous d enissons par :


float operator+(float u, Plat p) { return u + p.Getprix(); }

2.5 R ealisation dun programme


(2.5.1) Sur un exemple, nous allons d etailler les di erentes etapes qui m` enent a ` la r ealisation dun programme. Il sagira de simuler le jeu du cest plus, cest moins o` u un joueur tente de deviner un nombre choisi par le meneur de jeu. Le fait de programmer avec des objets nous force ` a mod eliser soigneusement notre application avant daborder le codage en C++.
ere (2.5.2) 1` etape : identication des classes

Conform ement a ` (1.1.3), nous commen cons par d ecrire le jeu de mani` ere litt erale : a un meneur. Le meneur choisit un num ero secret (entre 1 et 100). Le Le jeu oppose un joueur ` joueur propose un nombre. Le meneur r epond par : cest plus, cest moins ou cest exact. Si le joueur trouve le num ero secret en six essais maximum, il gagne, sinon il perd. Le jeu r eunit deux acteurs avec des r oles di erents : un meneur et un joueur. Nous d enirons donc deux classes : une classe Meneur et une classe Joueur.
eme etape : ches descriptives des classes (2.5.3) 2`

Il nous faut d eterminer les donn ees et fonctions-membres de chaque classe. Le meneur d etient un num ero secret. Ses actions sont : choisir ce num ero, r epondre par un diagnostic (cest plus, cest moins ou cest exact). Do` u la che descriptive suivante : classe : Meneur priv e: public : numsecret Choisis Reponds Le joueur d etient un nombre (sa proposition). Son unique action est de proposer ce nombre. Mais au cours du jeu, il doit garder a ` lesprit une fourchette dans laquelle se situe le num ero ` a deviner, ce qui nous am` ene ` a la che descriptive suivante : 13

classe : Joueur priv e: public : proposition Propose min max

eme (2.5.4) 3` etape : description d etaill ee des fonctions-membres

Nous allons d ecrire le fonctionnement de chaque fonction-membre et en pr eciser les informations dentr ee et de sortie (voir (2.2.2)). Choisis (de Meneur) : entr ee : rien sortie : rien choisit la valeur de numsecret, entre 1 et 100. On remarque que ce choix ne se fait quune fois, au d ebut de la partie. Il est donc logique que ce soit le constructeur qui sen charge. Nous transformerons donc cette fonction en constructeur. Reponds (de Meneur) : entr ee : la proposition du joueur sortie : un diagnostic : exact, plus ou moins (que nous coderons respectivement par 0, 1 ou 2) compare la proposition du joueur avec le num ero secret et rend son diagnostic. Propose (de Joueur) : entr ee : le diagnostic du pr ec edent essai sortie : un nombre compte tenu des tentatives pr ec edentes, emet une nouvelle proposition.
eme etape : description du traitement principal (2.5.5) 4`

La fonction main() sera le chef-dorchestre de la simulation. Son travail consiste a `: d eclarer un joueur et un meneur faire : prendre la proposition du joueur la transmettre au meneur prendre le diagnostic du meneur le transmettre au joueur jusqu` a la n de la partie acher le r esultat Remarquons que nos deux objets-acteurs ne communiquent entre eux que de mani` ere indirecte, par linterm ediaire de la fonction main() :
MENEUR JOUEUR

main()

On pourrait mettre directement en rapport les objets entre eux, a ` laide de pointeurs (paragraphe 6).
eme (2.5.6) 5` etape : d eclaration des classes

Nous en arrivons a ` la programmation proprement dite. Nous commen cons par ecrire les chiers de d eclarations des classes Meneur et Joueur :

14

// ------------------------ meneur.h -----------------------// ce fichier contient la d eclaration de la classe Meneur class Meneur { public: Meneur(); int Reponds(int prop);

// // // //

initialise un meneur re coit la proposition du joueur renvoie 0 si cest exact, 1 si cest plus et 2 si cest moins

private: int numsecret; };

// num ero secret choisi au d epart

// ------------------------ joueur.h -----------------------// ce fichier contient la d eclaration de la classe Joueur class Joueur { public: Joueur(); int Propose(int diag); private: int min, max, proposition; };

// initialise un joueur // re coit le diagnostic du pr ec edent essai // renvoie une nouvelle proposition // fourchette pour la recherche // dernier nombre propos e

eme (2.5.7) 6` etape : ecriture du traitement principal

Ce chier contient lutilisation des classes.


// ------------------------ jeu.cpp --------------------// programme de simulation du jeu "cest plus, cest moins" #include <iostream.h> #include "joueur.h" #include "meneur.h" // pour les entr ees-sorties // pour la d eclaration de la classe Joueur // pour la d eclaration de la classe Meneur

void main() // g` ere une partie ... { Joueur j; // ... avec un joueur ... Meneur m; // ... et un meneur int p, d = 1, // variables auxiliaires cpt = 0; // nombre dessais do // simulation du d eroulement du jeu { p = j.Propose(d); // proposition du joueur d = m.Reponds(p); // diagnostic du meneur cpt++; } while (d && cpt < 6); if (d) // d efaite du joueur cout << "\nLe joueur a perdu !"; else // victoire du joueur cout << "\nLe joueur a gagn e !"; }

Nous pourrions d` es ` a pr esent compiler ce chier jeu.cpp, alors que les classes Joueur et Meneur ne sont pas encore d enies.
eme etape : d enition des classes (2.5.8) 7`

Cest lultime etape, pour laquelle nous envisagerons deux sc enarios di erents. Dans le premier, lutilisateur du programme tiendra le r ole du joueur tandis que lordinateur tiendra le r ole du meneur. Dans le second, ce sera le contraire : lutilisateur tiendra le r ole du meneur et lordinateur celui du joueur. Nous ecrirons donc deux versions des classes Meneur et Joueur.

15

Premi` ere version :


// ------------------------ meneur.cpp --------------------// ce fichier contient la d efinition de la classe Meneur // le r^ ole du meneur est tenu par lordinateur #include #include #include #include <iostream.h> <stdlib.h> <time.h> "meneur.h"

// pour les nombres al eatoires

Meneur::Meneur() { srand((unsigned) time(NULL)); numsecret = 1 + rand() % 100; } int Meneur::Reponds(int prop) { if (prop < numsecret) { cout << "\nCest plus"; return 1; } if (prop > numsecret) { cout << "\nCest moins"; return 2; } cout << " \nCest exact"; return 0; }

// initialisation du g en erateur al eatoire // choix du num ero secret

// prop = proposition du joueur

// ------------------------ joueur.cpp --------------------// ce fichier contient la d efinition de la classe Joueur // le r^ ole du joueur est tenu par lutilisateur du programme #include <iostream.h> #include "joueur.h" Joueur::Joueur() { cout << "\nBonjour ! }

Vous allez jouer le r^ ole du joueur.";

int Joueur::Propose(int diag) { int p; cout << "\nProposition ? "; cin >> p; return p; }

// la valeur de diag est ignor ee

Seconde version :
// ------------------------ meneur.cpp --------------------// ce fichier contient la d efinition de la classe Meneur // le r^ ole du meneur est tenu par lutilisateur du programme #include <iostream.h> #include "meneur.h" Meneur::Meneur() { cout << "\nBonjour ! Vous allez jouer le r^ ole du meneur."; cout << "\nChoisissez un num ero secret entre 1 et 100"; } int Meneur::Reponds(int prop) // la valeur de prop est ignor ee { int r; cout << "\n0 - Cest exact"; cout << "\n1 - Cest plus"; cout << "\n2 - Cest moins"; cout << "\nVotre r eponse (0,1,2) ? ";

16

cin >> r; return r; } // ------------------------ joueur.cpp --------------------// ce fichier contient la d efinition de la classe Joueur // le r^ ole du joueur est tenu par lordinateur #include <iostream.h> #include "joueur.h" Joueur::Joueur() { min = 1; max = 100; proposition = 0; }

// fixe la fourchette dans laquelle // se trouve le num ero a ` deviner // premi` ere proposition fictive

int Joueur::Propose(int diag) { if (diag == 1) // ajuste la fourchette min = proposition + 1; else max = proposition - 1; proposition = (min + max) / 2; // proc` ede par dichotomie cout << "\nJe propose : " << proposition; return proposition; }

Remarque. Quand nous aurons abord e les notions dh eritage et de polymorphisme, nous serons en mesure d ecrire une version unique et beaucoup plus souple de ce programme, o` u la distribution des r oles pourra etre d ecid ee au moment de lex ecution.

2.6 Pointeurs et objets


(2.6.1) On peut naturellement utiliser des pointeurs sur des types classes. Nous pourrons ainsi utiliser des objets dynamiques, par exemple de la classe Complexe (cf (2.1.1)), en ecrivant :
Complexe *pc = new Complexe; // etc...

De la m eme mani` ere quavec les objets statiques : - si new est utilis e avec un type classe, le constructeur par d efaut (sil existe) est appel e automatiquement, - il est possible de faire un appel explicite a ` un constructeur, par exemple :
Complexe *pc = new Complexe(1.0, 2.0);

(2.6.2) Lacc` es aux donn ees et fonctions-membres dun objet point e peut se faire gr ace ` a lop erateur ` eche ->. Par exemple, avec les d eclarations pr ec edentes, on ecrira : pc -> re pc -> Affiche() (2.6.3) Liens entre objets Supposons d eclar ees deux classes avec :
class A { public: machin truc(); // fonction-membre ..... };

plut ot que plut ot que

(*pc).re (*pc).Affiche()

17

class B { private: A *pa; ..... };

// donn ee-membre :

pointeur sur un objet de la classe A

Comme un objet obj B de la classe B contient un pointeur sur un objet de la classe A, cela permet ` a obj B de communiquer directement avec cet objet en lui envoyant par exemple le message pa -> truc(). Ainsi : En programmation-objet, lutilit e principale des pointeurs est de permettre a ` deux objets de communiquer directement entre eux. On peut egalement construire des listes cha n ees dobjets de la m eme classe, en d eclarant :
class C { private: C *lien; ..... };

// donn ee-membre :

pointeur sur un objet de la classe C

Une telle liste peut etre repr esent ee par le sch ema suivant :
objet de type C objet de type C objet de type C

....

18

Chapitre 3

HERITAGE

3.1 Relations a-un, est-un, utilise-un


(3.1.1) Dans le cadre dun programme concernant les transports, supposons que nous d eclarions les quatre classes suivantes : Voiture, Moteur, Route et Vehicule. Quel genre de relation y a-t-il entre la classe Voiture et les trois autres classes ? Tout dabord, on peut dire quune voiture a un moteur : le moteur fait partie de la voiture, il est contenu dans celle-ci. Ensuite, une voiture utilise une route, mais il ny a pas dinclusion : la route ne fait pas partie de la voiture, de m eme que la voiture ne fait pas partie de la route. ehicule, dun genre particulier : la voiture poss` ede toutes les caract eristiques Enn, une voiture est un v dun v ehicule, plus certaines caract eristiques qui lui sont propres. (3.1.2) Du point de vue de la programmation, la relation a-un est une inclusion entre classes. Ainsi, un objet de type Voiture renferme une donn ee-membre qui est un objet de type Moteur. La relation utilise-un est une collaboration entre classes ind ependantes. Elle se traduit le plus souvent par des pointeurs. Par exemple, un objet de type Voiture renfermera une donn ee-membre de type pointeur sur Route, ce qui permettra ` a la voiture de communiquer avec une route (cf. (2.6.3)). La relation est-un sappelle un h eritage : une voiture h erite des caract eristiques communes ` a tout v ehicule. On dira que la classe Voiture est d eriv ee de la classe Vehicule.

3.2 Classes d eriv ees


(3.2.1) Le principe est dutiliser la d eclaration dune classe appel ee classe de base ou classe parente comme base pour d eclarer une seconde classe appel ee classe d eriv ee. La classe d eriv ee h eritera de tous les membres (donn ees et fonctions) de la classe de base. Consid erons par exemple la d eclaration suivante :
class Base { public: short membreBase; void SetmembreBase(short valeurBase); };

On d eclare une classe d eriv ee de la classe Base gr ace au qualicatif public Base. Par exemple :
class Derivee : public Base // h eritage public { public: short membreDerivee; void SetmembreDerivee(short valeurDerivee); };

Un objet de la classe Derivee poss` ede alors ses propres donn ees et fonctions-membres, plus les donn eesmembres et fonctions-membres h erit ees de la classe Base : 19

classe : Base membreBase SetmembreBase()

classe : Derivee membreDerivee SetmembreDerivee() membreBase SetmembreBase()

(3.2.2) Contr ole des acc` es Il est possible de r eserver lacc` es ` a certaines donn ees (ou fonctions) membres de la classe de base aux seules classes d eriv ees en leur mettant le qualicatif protected: A retenir : Les donn ees et fonctions-membres priv ees sont inaccessibles aux classes d eriv ees. (3.2.3) Premier exemple
# include <iostream.h> class Base { public: void SetmembreBase(short valeurBase); protected: short membreBase; }; void Base::SetmembreBase(short valeurBase) { membreBase = valeurBase; } class Derivee : public Base { public: void SetmembreDerivee(short valeurDerivee); void AfficheDonneesMembres(void); private: short membreDerivee; }; void Derivee::SetmembreDerivee(short valeurDerivee) { membreDerivee = valeurDerivee; } void Derivee::AfficheDonneesMembres(void) { cout << "Le membre de Base a la valeur " << membreBase << "\n"; cout << "Le membre de Derivee a la valeur " << membreDerivee << "\n"; } void main() { Derivee *ptrDerivee; ptrDerivee = new Derivee; ptrDerivee -> SetmembreBase(10); ptrDerivee -> SetmembreDerivee(20); ptrDerivee -> AfficheDonneesMembres(); }

// message 1 // message 2 // message 3

A lex ecution, ce programme achera les deux lignes suivantes :


Le membre de Base a la valeur 10 Le membre de Derivee a la valeur 20

(3.2.4) H eritage public ou priv e Il est possible de d eclarer : 20

class Derivee :

public Base private Base

// h eritage public // h eritage priv Y

ou :

class Derivee :

Il faut savoir que : - dans le premier cas, les membres h erit es conservent les m emes droits dacc` es (public ou protected) que dans la classe de base, - dans le second cas (cas par d efaut si rien nest pr ecis e), tous les membres h erit es deviennent priv es dans la classe d eriv ee. On conseille g en eralement dutiliser lh eritage public, car dans le cas contraire on se prive de pouvoir cr eer de nouvelles classes elles-m emes d eriv ees de la classe d eriv ee. (3.2.5) Constructeurs et destructeurs Quand un objet est cr e e, si cet objet appartient a ` une classe d eriv ee, le constructeur de la classe parente est dabord appel e. Quand un objet est d etruit, si cet objet appartient a ` une classe d eriv ee, le destructeur de la classe parente est appel e apr` es . Ces m ecanismes se g en eralisent ` a une cha ne dh eritages. Voici un exemple :
# include <iostream.h> class GrandPere { ..... // donn ees-membres public: GrandPere(void); ~GrandPere(); }; class Pere : public GrandPere { ..... // donn ees-membres public: Pere(void); ~Pere(); }; class Fils : public Pere { ..... // donn ees-membres public: Fils(void); ~Fils(); }; void main() { Fils *junior; junior = new Fils; // appels successifs des constructeurs de GrandPere, Pere et Fils ........ delete junior; // appels successifs des destructeurs de Fils, Pere et GrandPere }

(3.2.6) Cas des constructeurs param etr es Supposons d eclar ees les classes suivantes :
class Base { ..... Base(short val); }; class Derivee : public Base { ..... Derivee(float x); };

21

Dans la d enition du constructeur de Derivee, on pourra indiquer quelle valeur passer au constructeur de la classe Base de la mani` ere suivante (` a rapprocher de (2.3.3)) :
Derivee::Derivee(float x) : { ..... } Base(20)

(3.2.7) Deuxi` eme exemple


# include <iostream.h> class Rectangle { public: Rectangle(short l, short h); void AfficheAire(void); protected: short largeur, hauteur; }; Rectangle::Rectangle(short l, short h) { largeur = l; hauteur = h; } void Rectangle::AfficheAire() { cout << "Aire = " << largeur * hauteur << "\n"; } class Carre : public Rectangle { public: Carre(short cote); }; Carre::Carre(short cote) : { } Rectangle(cote, cote)

void main() { Carre *monCarre; Rectangle *monRectangle; monCarre = new Carre(10); monCarre -> AfficheAire(); monRectangle = new Rectangle(10, 15); monRectangle -> AfficheAire(); }

// affiche 100 // affiche 150

Nous retiendrons ceci : Gr ace ` a lh eritage, avec peu de lignes de code on peut cr eer de nouvelles classes ` a partir de classes existantes, sans avoir ` a modier ni recompiler ces derni` eres. Le g enie logiciel fait souvent usage de librairies toutes faites, contenant des classes quil sut de d eriver pour les adapter a ` ses propres besoins.

3.3 Polymorphisme
(3.3.1) Dans lexemple pr ec edent, AfficheAire() de Carre r eutilise le code de AfficheAire() de Rectangle. Mais dans dautres cas, il peut etre n ecessaire d ecrire un code di erent. Par exemple, dans une hi erarchie de classes de ce genre :
Forme Rectangle Cercle Triangle

Carre

22

on a une version de AfficheAire() pour chacune de ces classes. Si ensuite on cr ee une collection dobjets de type Forme, en demandant AfficheAire() pour chacune de ces formes, ce sera automatiquement la version correspondant ` a chaque forme qui sera appel ee et ex ecut ee : on dit que AfficheAire() est polymorphe. Ce choix de la version ad equate de AfficheAire() sera r ealis e au moment de lex ecution. Toute fonction-membre de la classe de base devant etre surcharg ee (cest-` a-dire red enie) dans une classe d eriv ee doit etre pr ec ed ee du mot virtual. (3.3.2) Exemple
# include <iostream.h> class Forme { // donn ees et fonctions-membres.... public: virtual void QuiSuisJe(void); // fonction destin ee ` a e ^tre surcharg ee }; void Forme::QuiSuisJe() { cout << "Je ne sais pas quel type de forme je suis !\n"; } class Rectangle : public Forme { // donn ees et fonctions-membres.... public: void QuiSuisJe(void); }; void Rectangle::QuiSuisJe() { cout << "Je suis un rectangle !\n"; } class Triangle : public Forme { // donn ees et fonctions-membres.... public: void QuiSuisJe(void); }; void Triangle::QuiSuisJe() { cout << "Je suis un triangle !\n"; } void main() { Forme *s; char c; cout << "Voulez-vous cr eer 1 : un rectangle ?\n"; cout << " 2 : un triangle ?\n"; cout << " 3 : une forme quelconque ?\n"; cin >> c; switch (c) { case 1 : s = new Rectangle; break; case 2 : s = new Triangle; break; case 3 : s = new Forme; } s -> QuiSuisJe(); // (*) cet appel est polymorphe }

Remarque. Pour le compilateur, a ` linstruction marqu ee (*), il est impossible de savoir quelle version de QuiSuisJe() il faut appeler : cela d epend de la nature de la forme cr e ee, donc le choix ne pourra etre fait quau moment de lex ecution (on appelle cela choix di er e, ou late binding en anglais).

23

(3.3.3) Remarques : - Une fonction d eclar ee virtuelle doit etre d enie, m eme si elle ne comporte pas dinstruction. - Un constructeur ne peut pas etre virtuel. Un destructeur peut l etre.

(3.3.4) Compatibilit e de types Supposons d eclar e:


class A { ..... }; class B : public A { ..... }; A a; B b; A *pa; B *pb; void *pv; // ou private A

Alors : laectation a = b est correcte ; elle convertit automatiquement b en un objet de type A et aecte le r esultat a `a - laectation inverse b = a est ill egale de la m eme mani` ere, laectation pa = pb est correcte - laectation inverse pb = pa est ill egale ; on peut cependant la forcer par lop erateur de conversion de type (), en ecrivant pb = (B*) pa laectation pv = pa est correcte, comme dailleurs pv = pb - laectation pa = pv est ill egale, mais peut etre forc ee par pa = (A*) pv.

(3.3.5) Op erateur de port ee :: Lorsquune fonction-membre virtuelle f dune classe A est surcharg ee dans une classe B d eriv ee de A, un objet b de la classe B peut faire appel aux deux versions de f : la version d enie dans la classe B elle s ecrit simplement f ou la version d enie dans la classe parente A elle s ecrit alors A::f, o` u :: est lop erateur de port ee .

3.4 Exemple
(3.4.1) Reprenons le programme de jeu cest plus, cest moins du chapitre 2, 5. Nous pouvons maintenant ecrire une version dans laquelle, au moment de lex ecution, les r oles du joueur et du meneur seront attribu es soit ` a un humain, soit a ` lordinateur. Lordinateur pourra donc jouer contre lui-m eme ! Il est important de noter que le d eroulement de la partie, qui etait contr ol e par la boucle :
do { p = j.Propose(d); d = m.Reponds(p); } while (d && cpt < 6); // voir la fonction main(), paragraphe (2.5.7) // proposition du joueur // diagnostic du meneur

s ecrira de la m eme fa con dans cette nouvelle version, quelle que soit lattribution des r oles. Cela est possible gr ace au polymorphisme des fonctions Propose() et Reponds(). 24

Nous utiliserons la hi erarchie de classes suivante :


Acteur

Meneur

Joueur

MeneurHumain

MeneurOrdinateur

JoueurHumain

JoueurOrdinateur

(3.4.2) Conform ement a ` (1.4.2), nous ecrirons un chier de d eclaration .h et un chier de d enition .cpp pour chacune de ces sept classes. An d eviter que, par le jeu des directives dinclusion #include, certains chiers de d eclarations ne soient inclus plusieurs fois (ce qui provoquerait une erreur a ` la compilation), nous donnerons aux chiers .h la structure suivante :
#ifndef SYMBOLE // si SYMBOLE nest pas d efini ... #define SYMBOLE // ... d efinir SYMBOLE .... // ici, les d eclarations normalement pr evues #endif // fin du si

#ifndef est une directive de compilation conditionnelle : elle signie que les lignes qui suivent, jusquau #endif, ne doivent etre compil ees que si SYMBOLE nest pas d ej` a d eni. Or, en vertu de la deuxi` eme ligne, ceci narrive que lorsque le compilateur rencontre le chier pour la premi` ere fois. (3.4.3) D eclarations des classes
//---------------------- acteur.h ---------------------#ifndef ACTEUR H #define ACTEUR H class Acteur { public: void AfficheNom(); protected: char nom[20]; }; #endif //---------------------- meneur.h ---------------------#ifndef MENEUR H #define MENEUR H #include "acteur.h" class Meneur : public Acteur { public: virtual int Reponds(int prop); }; #endif //---------------------- joueur.h ---------------------#ifndef JOUEUR H #define JOUEUR H #include "acteur.h" class Joueur : public Acteur { public: virtual int Propose(int diag); }; #endif

25

//---------------------- meneurhu.h ---------------------#ifndef MENEURHU H #define MENEURHU H #include "meneur.h" class MeneurHumain : public Meneur { public: MeneurHumain(); int Reponds(int prop); }; #endif //---------------------- meneuror.h ---------------------#ifndef MENEUROR H #define MENEUROR H #include "meneur.h" class MeneurOrdinateur : public Meneur { public: MeneurOrdinateur(); int Reponds(int prop); private: int numsecret; }; #endif //---------------------- joueurhu.h ---------------------#ifndef JOUEURHU H #define JOUEURHU H #include "joueur.h" class JoueurHumain : public Joueur { public: JoueurHumain(); int Propose(int diag); }; #endif //---------------------- joueuror.h ---------------------#ifndef JOUEUROR H #define JOUEUROR H #include "joueur.h" class JoueurOrdinateur : public Joueur { public: JoueurOrdinateur(); int Propose(int diag); private: int min, max, proposition; }; #endif

(3.4.4) D enitions des classes


//---------------------- acteur.cpp ---------------------#include "acteur.h" #include <iostream.h>

26

void Acteur::AfficheNom() { cout << nom; }

//---------------------- meneur.cpp ---------------------#include "meneur.h" int Meneur::Reponds(int prop) { // rien a ` ce niveau } //---------------------- joueur.cpp ---------------------#include "joueur.h" int Joueur::Propose(int diag) { // rien a ` ce niveau } //---------------------- meneurhu.cpp ---------------------#include "meneurhu.h" #include <iostream.h> MeneurHumain::MeneurHumain() { cout << "Vous faites le meneur. Quel est votre nom ? "; cin >> nom; cout << "Choisissez un num ero secret entre 1 et 100.\n\n"; } int MeneurHumain::Reponds(int prop) { AfficheNom(); cout << ", indiquez votre r eponse :"; int r; cout << "\n0 - Cest exact"; cout << "\n1 - Cest plus"; cout << "\n2 - Cest moins"; cout << "\nVotre choix (0, 1 ou 2) ? "; cin >> r; return r; } //---------------------- meneuror.cpp ---------------------#include #include #include #include #include "meneuror.h" <iostream.h> <string.h> <stdlib.h> <time.h>

MeneurOrdinateur::MeneurOrdinateur() { strcpy(nom, "lordinateur"); srand((unsigned) time(NULL)); numsecret = 1 + rand() % 100; } int MeneurOrdinateur::Reponds(int prop) { cout << "R eponse de "; AfficheNom(); if (prop < numsecret) { cout << " : cest plus\n"; return 1; } else if (prop > numsecret) { cout << " : cest moins\n"; return 2;

27

} cout << " : return 0; }

cest exact\n";

//---------------------- joueurhu.cpp ---------------------#include "joueurhu.h" #include <iostream.h> JoueurHumain::JoueurHumain() { cout << "Vous faites le joueur. Quel est votre nom ? cin >> nom; } int JoueurHumain::Propose(int diag) { AfficheNom(); int p; cout << ", votre proposition ? cin >> p; return p; }

";

";

//---------------------- joueuror.cpp ---------------------#include "meneuror.h" #include <iostream.h> #include <string.h> JoueurOrdinateur::JoueurOrdinateur() { strcpy(nom, "lordinateur"); min = 1; max = 100; proposition = 0; } int JoueurOrdinateur::Propose(int diag) { if (diag == 1) min = proposition + 1; else max = proposition - 1; proposition = (min + max) / 2; AfficheNom(); cout << " propose : " << proposition << "\n"; return proposition; }

(3.4.5) Traitement principal


//---------------------- jeu.cpp ---------------------#include #include #include #include #include #include #include "meneur.h" "meneurhu.h" "meneuror.h" "joueur.h" "joueurhu.h" "joueuror.h" <iostream.h>

void main() { cout << "\n\n\tJEU DU C++, C--\n\n"; Joueur *j; Meneur *m; // distribution des r^ oles char rep; cout << "Qui est le meneur (h = humain, o = ordinateur) ? cin >> rep; if (rep == h) m = new MeneurHumain; else

";

28

m = new MeneurOrdinateur; cout << "Qui est le joueur (h = humain, o = ordinateur) ? cin >> rep; if (rep == h) j = new JoueurHumain; else j = new JoueurOrdinateur; // d eroulement de la partie int p, d = 1, cpt = 0; do { p = j -> Propose(d); d = m -> Reponds(p); cpt++; } while (d && cpt < 6); // affichage du r esultat cout << "\nVainqueur : "; if (d) m -> AfficheNom(); else j -> AfficheNom(); }

";

3.5 H eritage multiple


(3.5.1) En C++, il est possible de faire d eriver une classe de plusieurs autres classes simultan ement. On d eclarera par exemple :
class A { ..... }; class B { ..... }; class C : public A, public B { ..... };

La classe C h erite alors de A et B : une instance de la classe C poss` ede ` a la fois les donn ees et fonctionsmembres de la classe A et celles de la classe B. (3.5.2) Quand un objet de la classe C ci-dessus est cr e e, les constructeurs des classes parentes sont appel es : dabord celui de A, ensuite celui de B. Quand un objet est d etruit, les destructeurs des classes parentes sont appel es, dabord celui de B, ensuite celui de A. (3.5.3) Dans la situation ci-dessus, il peut arriver que des donn ees ou fonctions-membres des classes A et B aient le m eme nom. Pour lever lambigu t e, on utilise lop erateur de port ee en ecrivant par exemple A :: x pour d esigner la donn ee-membre x h erit ee de la classe A, et B :: x pour d esigner celle qui est h erit ee de la classe B.

29

3.6 Classes abstraites


(3.6.1) Une fonction-membre virtuelle dune classe est dite purement virtuelle lorsque sa d eclaration est suivie de = 0, comme ci-dessous :
class A { ..... virtual truc machin() = 0; ..... };

Une fonction purement virtuelle na pas de d enition dans la classe. Elle ne peut qu etre surcharg ee dans les classes d eriv ees. (3.6.2) Une classe comportant au moins une fonction-membre purement virtuelle est appel ee classe abstraite. A retenir : Aucune instance dune classe abstraite ne peut etre cr e ee. Lint er et dune classe abstraite est uniquement de servir de canevas ` a ses classes d eriv ees, en d eclarant linterface minimale commune a ` tous ses descendants. (3.6.3) Exemple Il nest pas rare quau cours dun programme, on ait besoin de stocker temporairement des informations en m emoire, pour un traitement ult erieur. Une structure de stockage doit permettre deux actions principales : - mettre un nouvel el ement, - extraire un el ement. On parle de pile lorsque l el ement extrait est le dernier en date ` a avoir et e mis (structure LIFO pour Last In, First Out) et de queue ou le dattente lorsque l el ement extrait est le premier en date ` a avoir et e mis (structure FIFO pour First In, First Out). Voulant programmer une classe Pile et une classe Queue, nous commencerons par ecrire une classe abstraite Boite d ecrivant la partie commune a ` ces deux classes :
//---------------------- d eclaration de la classe Boite ---------------------class Boite // classe abstraite d ecrivant une structure de stockage // les e l ements stock es sont de type "pointeur sur Objet" // la classe Objet est suppos ee d ej` a d eclar ee

{ public: Boite(int n = 10); ~Boite(); virtual void Mets(Objet *po) = virtual Objet *Extrais() = 0; int Vide(); int Pleine(); protected: int vide, pleine; int taille; Objet **T; }; // // 0; // // // construit une bo^ te destructeur // met dans la extrait un pointeur indique si la bo^ te indique si la bo^ te contenant au maximum n pointeurs bo^ te le pointeur po de la bo^ te est vide est pleine

// indicateurs // capacit e de la bo^ te // consid er e comme tableau de pointeurs sur Objet

//---------------------- d efinition de la classe Boite ---------------------Boite::Boite(int n) { taille = n; T = new Objet* [taille]; vide = 1; pleine = 0; }; Boite::~Boite() { delete [] T; };

// on peut pr evoir ici un test de d ebordement m emoire

// lib` ere lespace point e par T

30

int Boite::Vide() { return vide; }; int Boite::Pleine() { return pleine; }; //---------------------- d eclaration de la classe Pile ---------------------class Pile : public Boite { public: Pile(int n = 10); void Mets(Objet *po); Objet *Extrais(); protected: int nbelements; }; // classe d ecrivant une pile

// construit une pile contenant au maximum n pointeurs // met dans la pile le pointeur po // extrait un pointeur de la pile // nombre effectif d el ements contenus dans la pile

//---------------------- d efinition de la classe Pile ---------------------Pile::Pile(int n) : Boite(n) { nbelements = 0; }; void Pile::Mets(Objet *po) { T[nbelements++] = po; vide = 0; pleine = nbelements == taille; } Objet *Pile::Extrais() { Objet *temp; temp = T[--nbelements]; pleine = 0; vide = nbelements == 0; return temp; } //---------------------- d eclaration de la classe Queue ---------------------class Queue : public Boite { public: Queue(int n = 10); void Mets(Objet *po); Objet *Extrais(); protected: int tete, queue; }; //---------------------- d efinition de la classe Queue ---------------------Queue::Queue(int n) : Boite(n) { tete = queue = 0; }; void Queue::Mets(Objet *po) { T[queue++] = po; queue %= taille; vide = 0; pleine = tete == queue; } Objet *Queue::Extrais() { Objet *temp; // classe d ecrivant une queue

// construit une queue contenant au maximum n pointeurs // met dans la queue le pointeur po // extrait un pointeur de la queue // indice o` u se trouve l el ement le plus ancien // indice o` u se mettra le prochain e l ement // (T est utilis e comme un tableau circulaire)

// retour au d ebut du tableau si n ecessaire

31

temp = T[tete++]; tete %= taille; pleine = 0; vide = tete == queue; return temp; }

// idem

Voici par exemple comment nous pourrions utiliser la classe Queue : cr eation dune le dattente contenant au plus 1000 el ements :
Queue *q = new Queue (1000);

mise dun el ement dans la le :


if (! q -> Pleine()) q -> Mets(pObjet); // pObjet pointe sur un Objet suppos e cr ee par ailleurs

extraction dun el ement de la le :


if (! q -> Vide()) pObjet = q -> Extrais();

destruction de la le :
delete q;

32

Chapitre 4

ENTREES & SORTIES

ios

istream

ostream

ifstream

ofstream

4.1 La librairie iostream.h


(4.1.1) La classe istream En C++, un chier est consid er e comme un ot (en anglais : stream), cest-` a-dire une suite doctets repr esentant des donn ees de m eme type. Si ces octets repr esentent des caract` eres, on parle de chiertexte ; si ces octets contiennent un codage en binaire, on parle de chier binaire. Les organes logiques (clavier, console, ecran) sont vus comme des chiers-textes. Les ots en entr ee sont d ecrits par la classe istream. Lobjet cin est une instance de cette classe, automatiquement cr e e et destin e aux entr ees depuis le clavier. (4.1.2) En plus de lop erateur de lecture >> que nous avons d ej` a utilis e, la classe istream dispose de nombreuses fonctions, dont les suivantes :
ere forme, d eclar ee ainsi : get() 1` istream &get(char &destination);

Cest la lecture dun caract` ere. La fonction renvoie une r ef erence sur le ot en cours, ce qui permet dencha ner les lectures. Exemple :
char c; short nb; cin.get(c) >> nb; // si on tape 123 entr ee , c re coit 1, nb re coit 23

eme get() 2` forme, d eclar ee ainsi : istream &get(char *tampon, int longueur, char delimiteur = \n);

Lit au plus longueur caract` eres, jusquau d elimiteur (inclus) et les loge en m emoire a ` ladresse point ee par tampon. La cha ne lue est compl et ee par un \0. Le d elimiteur ny est pas inscrit, mais est remis dans le ot dentr ee. Exemple :
char tampon[10]; cin.get(tampon, 10, *); eme get() 3` forme, d eclar ee ainsi : int &get();

33

Lit un seul caract` ere, transtyp e en int. On peut par exemple r ecup erer le caract` ere EOF (marque de n de chier) qui correspond a ` lentier -1. Exemple :
int c; while ((c = cin.get()) != q) cout << (char) c;

getline() d eclar ee ainsi :


istream &getline(char *tampon, int longueur, char delimiteur = \n); eme Lit une ligne. A la di erence du get() 2` forme, le d elimiteur est absorb e au lieu d etre remis dans le ot dentr ee.

ignore() d eclar ee ainsi :


istream &ignore(int longueur = 1, int delimiteur = EOF);

Elimine des caract` eres du ot dentr ee (fonctionne comme getline()). Exemple :


char tampon[80]; cin.ignore(3).getline(tampon,80);

peek() d eclar ee ainsi :


int peek(); eme Lit le caract` ere suivant sans lenlever (fonctionne comme get() 3` forme).

putback() d eclar ee ainsi :


istream &putback(char c);

Remet le caract` ere d esign e par c dans le ot (ce caract` ere doit etre le dernier a ` avoir et e lu). seekg() d eclar ee ainsi :
istream &seekg(streampos p);

Acc` es direct au caract` ere num ero p, ce qui permettra sa lecture ; les caract` eres sont num erot es ` a partir de 0. On peut pr eciser une position relative en mettant en second param` etre ios::beg , ios::cur ou ios::end. read() d eclar ee ainsi :
istream &read(void *donnees, int taille);

Lecture de taille octets depuis le ot, et stockage ` a ladresse donnees. gcount() d eclar ee ainsi :
size t gcount();

Renvoie le nombre doctets lus avec succ` es avec read(). (4.1.3) La classe ostream Cette classe est destin ee ` a d ecrire les ots en sortie. Lobjet cout est une instance de cette classe, automatiquement cr e e et destin e aux sorties ` a l ecran. cerr et clog en sont egalement deux instances, g en eralement associ ees ` a la console. (4.1.4) En plus de lop erateur d ecriture << que nous avons d ej` a utilis e, la classe ostream dispose de nombreuses fonctions, dont les suivantes : put() d eclar ee ainsi :
ostream &put(char c);

34

Ecrit le caract` ere sp eci e et renvoie une r ef erence sur le ot en cours, ce qui permet dencha ner les ecritures. Exemple :
cout.put(C).put(+).put(+); // affiche C++

seekp() d eclar ee ainsi :


ostream &seekp(streampos p);

Acc` es direct ` a la position p, pour ecriture. Comme pour seekg(), on peut mettre un second param` etre (voir (6.1.2)). write() d eclar ee ainsi :
ostream &write(const void *donnees, size t taille);

Ecrit taille octets provenant de ladresse donnees. pgcount() d eclar ee ainsi :


size t pcount();

Renvoie le nombre doctets ecrits avec write(). (4.1.5) Utilitaires sur les caract` eres Voici quelques fonctions d eclar ees dans <ctype.h> concernant les caract` eres : tolower() toupper() isalpha() islower() isupper() isdigit() isalnum() convertit une lettre majuscule en minuscule convertit une lettre minuscule en majuscule teste si un caract` ere est une lettre teste si un caract` ere est une lettre minuscule teste si un caract` ere est une lettre majuscule teste si un caract` ere est un chire entre 0 et 9 teste si un caract` ere est une lettre ou un chire.

4.2 La librairie fstream.h


(4.2.1) La classe ifstream Cette classe d ecrit les chiers en lecture. Elle d erive de istream, donc dispose des fonctions du paragraphe pr ec edent, ainsi que des fonctions suivantes : un constructeur d eclar e:
ifstream(const char *nom, int mode = ios::in);

Cr ee un nouvel objet de type ifstream, lui attache le chier-disque appel e nom et ouvre ce chier en lecture. Exemple dutilisation :
ifstream monfic("A:TOTO.TXT");

quon peut ecrire de mani` ere equivalente :


ifstream monfic; monfic.open("A:TOTO.TXT"); // variante avec la fonction open()

Il est possible douvrir le chier en mode ajout (en anglais : append) pour pouvoir y ajouter des el ements ` la n. Il sut pour cela de passer au constructeur comme second param` a etre ios::app. close() qui ferme le chier en n de traitement.

35

(4.2.2) La classe ofstream Cette classe d ecrit les chiers en ecriture. Elle d erive de ostream, donc dispose des fonctions du paragraphe pr ec edent, ainsi que des fonctions suivantes : un constructeur d eclar e:
ofstream(const char *nom, int mode = ios::out);

Cr ee un nouvel objet de type ofstream, lui attache un chier-disque appel e nom et ouvre ce chier en ecriture. close() qui ferme le chier en n de traitement. (4.2.3) Exemple Voici une fonction qui recopie un chier-texte.
void Copie(char *nomSource, char *nomDestination) { ifstream source(nomSource); ofstream destination(nomDestination); char c; cout << "\nD ebut de la copie..."; while (source.get(c)) // explication en (4.3.2) destination << c; source.close(); destination.close(); cout << "\nCopie achev ee."; }

Remarquer quil ne faudrait pas lire les caract` eres de source par :
source >> c;

car lop erateur >> sauterait les espaces et les marques de n de ligne. (4.2.4) Remarques 1. Pour cr eer un chier binaire, il faut passer le mode constructeur (ou dans la fonction open()). ios::binary en second param` etre dans le

2. On peut combiner plusieurs modes douverture avec lop erateur |, par exemple ios::in | ios::out.

4.3 Fonctions de contr ole


(4.3.1) Pour tout ot, il est possible de contr oler le bon d eroulement des op erations dentr ee-sortie, gr ace aux fonctions suivantes : good() vraie si tout va bien et quen principe, la prochaine op eration dentr ee-sortie devrait se d erouler normalement, eof() vraie si la derni` ere op eration a fait atteindre la n du chier, fail() vraie sil y a echec apr` es une op eration, bad() vraie sil y a echec et si le chier-disque est endommag e, clear() permettant de r einitialiser les bits d etat du ot. (4.3.2) Exemple 1 La fonction get() renvoie en principe une r ef erence de stream (voir (4.1.2)). Toutefois, dans le cas o` u une expression conditionnelle consiste en un appel ` a une fonction de iostream et lorsque cette fonction a pour valeur une r ef erence de stream, le compilateur substitue a ` cette valeur le r esultat de good(). Voici pourquoi la boucle de lexemple (4.2.3) fonctionne correctement. 36

(4.3.3) Exemple 2 : saisie prot eg ee Voici un fragment de programme permettant de contr oler quune donn ee introduite au clavier est correcte :
#include <iostream.h> ..... short nombre; cout << "Entrez un entier court : cin >> nombre; if (cin.good()) ..... // else if (cin.fail()) // { cin.clear(); // ..... // }

";

traitement normal ce nest pas une expression de type short on revient a ` l etat normal message davertissement

4.4 Surcharge des op erateurs >> et <<


Le programme suivant montre comment on peut surcharger les op erateurs dentr ee-sortie habituels >> et <<, rendant ainsi possible la lecture (ou l ecriture) dun objet depuis (ou vers) nimporte quel ot (chier ou organe logique).
#include <iostream.h> #include <fstream.h> #include <string.h> const short MAX = 40; class Plat { public: void Setnom(char *name); char *Getnom(); void Setprix(float montant); float Getprix(); private: float prix; char nom[MAX]; }; void Plat::Setnom(char *name) { ..... } char *Plat::Getnom() { ..... } void Plat::Setprix(float montant) { ..... float Plat::Getprix() { ..... } // d etails omis }

istream &operator>>(istream &is, Plat &article) { float montant; char chaine[MAX]; is.getline(chaine, MAX); // mieux que is >> chaine article.Setnom(chaine); is >> montant; article.Setprix(montant); is. ignore(1, \n); return is; // pour pouvoir encha^ ner les entr ees : } ostream &operator<<(ostream &os, Plat &article) { os << article.Getnom() << " (F " << article.Getprix() << ")"; return os; // idem }

is >> a >> b ...

void main() // lit des plats depuis un fichier et les affiche a ` l ecran { ifstream menu("MENU.TXT"); Plat article; while (menu >> article) cout << article << "\n"; menu.close(); }

37

4.5 Formatage des donn ees


(4.5.1) Indicateurs Les indicateurs suivants permettent de contr oler laspect des donn ees en sortie : left right fixed scientific showpoint showpos alignement ` a gauche alignement ` a droite ottants en virgule xe ottants en notation scientique force lachage avec virgule dun ottant force lachage dun + devant un entier positif.

Lindicateur suivant permet de modier le comportement de lop erateur de lecture : skipws saute les blancs Ces indicateurs de format sont membres de la classe ios et peuvent etre r egl es par les fonctions-membres setf() et unsetf() : setf(ios::<ag>) unsetf(ios::<ag>) Exemple :
cin.setf(ios::skipws); cin.unsetf(ios::skipws); // saute les blancs en lecture au clavier // ne saute pas les blancs en lecture au clavier

active lindicateur <ag> d esactive lindicateur <ag>

(4.5.2) Fonctions utiles width() d etermine le nombre minimum de caract` eres de la prochaine sortie fill() pr ecise le caract` ere de remplissage precision() d etermine le nombre de chires. Exemple :
cout.width(12); cout.fill(*); cout.setf(ios::right); cout << "Bonjour";

// affiche *****Bonjour

Autre exemple :
cout.width(8); cout.precision(5); cout << 100.0 / 3.0; // affiche 33.333 (avec deux blancs devant)

(4.5.3) Manipulateurs Ils permettent de modier lapparence des sorties et sont contenus dans la librairie <iomanip.h> : endl marque une n de ligne et r einitialise le ot setfill(c) correspond a ` fill() setprecision(p) correspond a ` precision() setw(n) correspond a ` width() setbase(b) xe la base de num eration Sauf pour setprecision(), ces manipulateurs nagissent que sur la prochaine sortie. Exemple :
cout << setbase(16) << 256 << endl; cout << setprecision(5) << 123.45678; // affiche 100 et passe a ` la ligne // affiche 123.46

38

APPENDICE

A.1 Relation damiti e


(A.1.1) On sait que lorsque des donn ees et fonctions-membres dune classe sont priv ees ou prot eg ees, elles sont inaccessibles depuis lext erieur de la classe : cest le principe de lencapsulation. Si on a besoin dy acc eder, on peut ecrire des fonctions r eserv ees ` a cet eet (fonctions dacc` es) et pr evoir ainsi des contr oles et actions suppl ementaires. Une autre possibilit e, propre au C++, est dutiliser une d eclaration damiti e avec le mot friend. (A.1.2) Fonctions amies Une fonction amie dune classe est une fonction qui, sans etre membre de la classe, a n eanmoins acc` es ` a toutes les donn ees et fonctions-membres de cette classe, quelles soient publiques, prot eg ees ou priv ees. Une telle fonction amie doit etre d eclar ee ` a lint erieur de la d eclaration de la classe. Sil sagit dune fonction libre (i.e. ext erieure a ` toute classe) :
class A { friend truc machin(); public: ..... private: ..... }; // fonction libre amie de la classe A

Sil sagit dune fonction-membre dune autre classe :


class B; // informe le compilateur quil existe une classe nomm ee B class A { friend truc B::machin(); // fonction-membre de la classe B, amie de la classe A public: ..... private: ..... };

(A.1.3) Il peut etre pratique dutiliser une d eclaration damiti e pour surcharger certains op erateurs. Voici un exemple avec lop erateur de sortie << (voir (4.4)) :
class A { friend ostream &operator<<(ostream &os, A &monObj); private: int T[10]; ...... }; // d eclaration

ostream &operator<<(ostream &os, A &monObj) // d efinition de la surcharge { for (int i = 0; i < 10; i++) os << monObj.T[i]; // donn ee accessible gr^ ace a ` lamiti e return os; }

39

(A.1.4) Classes amies Lorsque toutes les fonctions-membres dune classe B sont amies dune classe A, on dit que la classe B est amie de A. Au lieu de d eclarer dans la classe A chaque fonction-membre de B comme amie, on ecrit plus simplement :
class B; // informe le compilateur quil existe une classe nomm ee B class A { friend class B; // la classe B est d eclar ee amie de la classe A public: ..... private: ..... };

Remarque. Il ne faut pas abuser de la relation damiti e, car elle constitue une entorse au principe dencapsulation.

A.2 Patrons
(A.2.1) En C++, il est possible de d eclarer des classes param etr ees par des types, gr ace au m ecanisme des patrons (template). Supposons par exemple que nous voulions ecrire une classe Tableau permettant de ranger aussi bien des entiers que des r eels ou des cha nes de caract` eres. Au lieu d ecrire autant de classes Tableau quil y a de types a ` ranger, la solution consiste a ` ecrire une unique classe Tableau param etr ee par un type a priori inconnu quon appelle T :
template <class T> // signale que T est un type param` etre de ce qui suit class Tableau { public: Tableau(short dim); ~Tableau(); T &operator[](short index); // surcharge de lop erateur [] private: short taille; T *ptab; };

Voici la d enition de la classe Tableau :


template <class T> Tableau<T>::Tableau(short dim) { taille = dim; ptab = new T [taille]; }; template <class T> Tableau<T>::~Tableau() { delete [] ptab; };

// lib eration-m emoire pour un tableau dynamique

template <class T> T &Tableau<T>::operator[](short index) { if (index < 0 || index > taille) { cout << "\nindice hors du rang..."; exit(1); // interrompt lex ecution } return ptab[index]; };

(A.2.2) Exemple dutilisation de la classe Tableau pr ec edente :


Tableau<int> t(10); // d eclare un tableau t contenant 10 entiers

40

int z; z = t[1]; t[0] = 1;

// ici, lindice est automatiquement contr^ ol e // possible car la surcharge de [] est d eclar ee de type T&

Ou bien :
Tableau<float> u(3); .... // d eclare un tableau u contenant 3 r eels

Ou encore :
typedef char Mot [20]; Tableau<Mot> t(100); // d eclare un tableau t contenant 100 mots ....

Remarques : - le param` etre de type T peut etre nimporte quel type : type de base, type d eni ou classe - chacune des d eclarations pr ec edentes provoque en r ealit e la recompilation de la classe Tableau, o` u le type param` etre T est remplac e par le type v eritable - une autre mani` ere d ecrire une classe Tableau pouvant contenir di erents types dobjets est dutiliser lh eritage et le polymorphisme, comme nous lavons fait pour les classes Boite, Pile et Queue au paragraphe (3.6.3).

41

Chapitre 5

INTRODUCTION A LA PROGRAMMATION WINDOWS AVEC VISUAL C++

5.1 Outils
(5.1.1) Bibliothque MFC Toute application Windows doit sexcuter dans un univers coopratif (multi-tche) et rpondre des vnements prcis : clics de souris, frappe du clavier etc. Pour faciliter la programmation dune telle application, Microsoft distribue une bibliothque de classes toutes faites, les Microsoft Foundation Classes (MFC). Ces classes dcrivent notamment des objets de type fentre (CWnd), document (CDoc), vue (CView) et application (CWinApp).

(5.1.2) AppWizard Sous Visual C++, la plus grosse partie du code peut tre crite automatiquement par lassistant dapplication (AppWizard) : il suffit pour cela de crer un projet de type MFC AppWizard (exe). Si lapplication est de type SDI (Single Document Interface), trois classes importantes sont alors pr-programmes, qui dcrivent : - La fentre principale de lapplication (classe drive de CWnd), - Un document (classe drive de CDoc), vide au dpart, - Une vue (classe drive de CView), charge de reprsenter le document lcran, A partir de ces classes, le travail consiste gnralement ajouter des donnes et fonctions-membres afin de personnaliser lapplication. Pour cela on a le choix entre modifier directement les fichiers-sources .h et .cpp, ou alors utiliser le menu contextuel (clic droit de la souris sur le nom dune classe figurant dans le browser, onglet ClassView).

(5.1.3) Contexte graphique Tout trac doit imprativement tre effectu par la fonction-membre OnDraw() de la classe CView. Cela permet lapplication de refaire automatiquement le trac ds que le besoin sen fait sentir, par exemple lorsque la fentre passe au premier plan aprs avoir t partiellement cache par une autre fentre. Le reprage dun pixel sur lcran se fait grce un couple dentiers (h,v) form dune coordonne horizontale et dune coordonne verticale. Lorigine (0,0) est en haut gauche de la vue, laxe vertical est dirig vers le bas. Les instructions graphiques sont donnes un objet-dessinateur appel contexte graphique (ou Device Context, de la classe CDC).

(5.1.4) ClassWizard Cet assistant de classe permet, en cours de dveloppement, de crer des classes ou de les modifier. On sen sert notamment pour ajouter donnes, fonctions-membres et gestionnaires, cest--dire des fonctions charges de rpondre des vnements comme : clic sur la souris, slection dun menu, choix dun bouton de contrle etc. On peut activer ClassWizard tout moment par la combinaison de touches <CTRL> W.

42

5.2 Premier exemple


Nous commenons par crire un programme qui dessine un triangle. Voici les principales tapes : a) Crer un nouveau projet appel Triangle, de type MFC AppWizard (exe), Single Document

b) Dans longlet ClassView, en cliquant sur les +, faire apparatre la classe CTriangleView et double-cliquer sur la fonction OnDraw() c) Ajouter dans la fonction OnDraw() le code suivant :
COLORREF couleur = RGB(0, 0, 0); // couleur noire int epaisseur = 10; // paisseur du trait CPen crayon (PS_SOLID, epaisseur, couleur); // cration dun crayon CPen *ancien = pDC->SelectObject(&crayon); // slection du crayon pDC->MoveTo(200, 100); // dplacement du crayon pDC->LineTo(400, 380); // trac dun segment pDC->LineTo(180, 250); pDC->LineTo(200, 100); pDC->SelectObject(ancien); // restitution de lancien crayon

d) Compiler, puis excuter le projet. Lapplication est oprationnelle. e) Modifier la couleur du trac pour quelle soit choisie alatoirement :
COLORREF couleur = RGB(rand() % 256, rand() % 256, rand() % 256);

f)

Ajouter dans le constructeur de la vue linitialisation du gnrateur de nombres alatoires :


srand((unsigned) time(NULL));

g) Compiler, puis excuter. On peut remarquer que lorsquon redimensionne la fentre, ou lorsquon en dcouvre une partie aprs lavoir recouverte par une autre fentre, le triangle change partiellement de couleur : lordre OnDraw() est envoy directement la vue par le systme dexploitation, avec indication dune rgion de mise jour.

5.3 Amlioration de linterface


Pour ajouter un lment linterface, le principe consiste : - ajouter ou modifier une ressource (menu, dialogue etc.) ; celle-ci dcrit laspect de llment - ajouter ventuellement une classe pour grer llment (cas dun dialogue) - ajouter le gestionnaire adquat dans la classe destinataire du message envoy par llment.

(5.3.1) Ajouter un menu a) Dans longlet ResourceView, ouvrir le dossier Menu et double-cliquer sur IDR_MAINFRAME (identificateur du menu principal)

b) Cliquer lendroit du nouveau menu, taper son intitul : Triangles, puis valider par entre c) Entrer de la mme manire les intituls des trois articles de ce menu : Nombre, Fond, Go !

d) Compiler et excuter. A ce stade, les commandes sont au menu mais dsactives : tant que nous navons pas programm, pour chacune delle, le gestionnaire correspondant, ces commandes ne font rien.

43

(5.3.2) Lancer le dessin a) Dans la classe CTriangleView, ajouter la donne-membre prive m_actif de type boolean (pour cela, on peut ajouter directement sa dclaration dans le fichier TriangleView.h ou alors, dans longlet ClassView, cliquer avec le bouton droit de la souris sur le nom de la classe CTriangleView et utiliser le menu contextuel qui apparat).

b) Dans le constructeur, crire : m_actif = false ; c) Dans OnDraw(), ajouter les instructions suivantes, pour que le dessin ne se fasse que si m_actif est vrai :
if (m_actif) ...... // instructions dessinant le triangle m_actif = false;

d) Ajouter le gestionnaire correspondant larticle de menu Go ! Pour cela, activer ClassWizard (<CTRL> W), onglet Message Maps, et slectionner : en haut le nom de la classe destinataire : CTriangleView gauche : lID de la commande de menu (ici : ID_TRIANGLES_GO) droite : le type de message : COMMAND puis demander Add Function, accepter le nom propos par lassistant, et demander Edit Code. e) Dans cette fonction, ajouter les deux instructions :
m_actif = true; InvalidateRect(NULL); // invalide la vue : elle sera redessine

f) Compiler et excuter : la commande Go ! du menu Triangles lance le trac.

(5.3.3) Utiliser un dialogue prdfini Nous dsirons choisir la couleur du fond grce un dialogue standard de slection de couleur. a) Dans la vue, ajouter la donne-membre prive m_couleurfond de type COLORREF

b) Dans le constructeur, crire :


m_couleurfond = RGB(255, 255, 255); // initialement blanc

c)

Dans OnDraw(), ajouter le code suivant :


CRect r; // dclare un rectangle r GetClientRect(r); // r reoit le rectangle de la vue CBrush pinceau (m_couleurfond); // construit un pinceau CBrush *pvieux = pDC->SelectObject(&pinceau); // le slectionne pDC->FillRect(r, &pinceau); // peint le rectangle r ...... // trac du triangle pDC->SelectObject(pvieux); // restitue lancien pinceau

d) Activer ClassWizard et ajouter le gestionnaire, comme en (5.3.2) d), pour la commande Fond, avec le code suivant :
CColorDialog d; // d dialogue de la classe CColorDialog if (d.DoModal() == IDOK) // si on a cliqu sur le bouton OK m_couleurfond = d.GetColor() ; // on rcupre la couleur InvalidateRect(NULL) ; // on retrace la vue

e)

Compiler et tester.

44

(5.3.4) Crer un nouveau dialogue Nous dsirons tracer plusieurs triangles, leur nombre tant saisi dans un dialogue. a) Dans longlet ResourceView, cliquer avec le bouton droit de la souris sur le dossier Dialog. Au menu contextuel, demander InsertDialog. Un nouveau dialogue apparat, avec deux boutons.

b) Cliquer avec le bouton droit sur ce nouveau dialogue. Au menu contextuel, demander Properties. Entrer dabord lintitul du dialogue (champ Caption), puis taper ID_DIALOGNOMBRE (champ ID). c) A la souris et avec laide de la palette doutils, placer une tiquette (Static Text) dintitul : Nombre :

d) A ct, placer une zone de texte ditable (Edit Box) et lui attribuer lidentificateur IDC_NOMBRE e) Demander Tab Order au menu Layout, et cliquer sur les lments du contrle dans lordre o nous voulons pouvoir les activer lexcution avec la touche <TAB> Double-cliquer sur le dialogue. Cela active ClassWizard et permet de crer une nouvelle classe grant le dialogue. Nommer cette classe CDialogNombre

f)

g) Sous ClassWizard, onglet Member Variables, double-cliquer sur IDC_NOMBRE. ClassWizard nous propose de crer une donne-membre associe la zone de texte ditable. Entrer son nom : m_nb, sa catgorie : Value, son type : int. h) Ajouter linitialisation de cette donne-membre dans le constructeur de la classe CDialogNombre. i) Dans la classe CTriangleView, ajouter une donne-membre m_nb de type int, linitialiser dans le constructeur. Dans cette mme classe, ajouter et programmer le gestionnaire associ la commande de menu Nombre (sinspirer de (5.3.2) d), ainsi que la directive dinclusion #include "DialogNombre.h"

j)

k) Modifier enfin le code de OnDraw() pour tracer m_nb triangles alatoires, chacun tant obtenu par :
int h, v ; pDC->MoveTo(h = rand() % 400, v = rand() % 400); pDC->LineTo(rand() % 400, rand() % 400); pDC->LineTo(rand() % 400, rand() % 400); pDC->LineTo(h, v);

l)

Compiler et excuter.

45

S-ar putea să vă placă și