C Polycop2
C Polycop2
C Polycop2
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
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.
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).
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.
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.
// ------------------------ 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 } << << << << << << << <<
"\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
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 +
// ------------------------ 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; }
";
Complexe Complexe::operator+(Complexe g) {
// 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).
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
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.
11
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);
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
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);
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
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
// ------------------------ 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
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
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; }
// ------------------------ 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 ! }
int Joueur::Propose(int diag) { int p; cout << "\nProposition ? "; cin >> p; return p; }
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.
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 ..... };
(*pc).re (*pc).Affiche()
17
// donn ee-membre :
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 :
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
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
(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(); }
class Derivee :
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)
void main() { Carre *monCarre; Rectangle *monRectangle; monCarre = new Carre(10); monCarre -> AfficheAire(); monRectangle = new Rectangle(10, 15); monRectangle -> AfficheAire(); }
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.
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
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
26
//---------------------- 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
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; }
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(); }
";
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
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
//---------------------- d efinition de la classe Boite ---------------------Boite::Boite(int n) { taille = n; T = new Objet* [taille]; vide = 1; pleine = 0; }; Boite::~Boite() { delete [] 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)
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);
destruction de la le :
delete q;
32
Chapitre 4
ios
istream
ostream
ifstream
ofstream
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;
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++
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);
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.
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");
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.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
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 }
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
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
(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.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; };
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]; };
40
// 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
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
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)
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.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
(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
c)
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