Académique Documents
Professionnel Documents
Culture Documents
C++
Initiez-vous la programmation en C++
Jesse Liberty et Bradley Jones
L E P R O G R A M M E U R
Le langage C++
Pearson Education France a apport le plus grand soin la ralisation de ce livre an de vous fournir une information complte et able. Cependant, Pearson Education France nassume de responsabilits, ni pour son utilisation, ni pour les contrefaons de brevets ou atteintes aux droits de tierces personnes qui pourraient rsulter de cette utilisation. Les exemples ou les programmes prsents dans cet ouvrage sont fournis pour illustrer les descriptions thoriques. Ils ne sont en aucun cas destins une utilisation commerciale ou professionnelle. Pearson Education France ne pourra en aucun cas tre tenu pour responsable des prjudices ou dommages de quelque nature que ce soit pouvant rsulter de lutilisation de ces exemples ou programmes. Tous les noms de produits ou marques cits dans ce livre sont des marques dposes par leurs propritaires respectifs.
Publi par Pearson Education France 47 bis, rue des Vinaigriers 75010 PARIS Tl. : 01 72 74 90 00 www.pearson.fr Mise en pages : TyPAO ISBN : 978-2-7440-4023-8 Copyright 2009 Pearson Education France Tous droits rservs
Titre original : Teach Yourself C++ in 21 Days, fifth edition Traduit de lamricain par Nathalie Le Guillou de Penanros Nouvelle dition franaise revue, corrige et complte par Eric Jacoboni ISBN original : 0-672-32711-2 Copyright 2004 Sams Publishing All rights reserved
Aucune reprsentation ou reproduction, mme partielle, autre que celles prvues larticle L. 122-5 2 et 3 a) du code de la proprit intellectuelle ne peut tre faite sans lautorisation expresse de Pearson Education France ou, le cas chant, sans le respect des modalits prvues larticle L. 122-10 dudit code. No part of this book shall be reproduced, stored in a retrieval system, or transmitted by any means, electronic, mechanical, photocopying, recording, or otherwise, without written permission from the publisher.
Sommaire
Introduction ............................................ Partie I .................................................... 1. Bien dbuter en C++ .......................... 2. Anatomie dun programme C++ ...... 3. Variables et constantes ...................... 4. Expressions et instructions ................ 5. Fonctions ............................................. 6. Programmation oriente objet .......... 7. Droulement dun programme ......... Partie II ................................................... 8. Pointeurs ............................................. 9. Rfrences .......................................... 10. Fonctions avances ........................... 11. Analyse et conception oriente objet 12. Hritage ............................................ 13. Tableaux et chanes ..........................
14. Polymorphisme ................................ Partie III ................................................. 15. Classes et fonctions spciales .......... 16. Concepts avancs dhritage .......... 17. Les ux ............................................. 18. Espaces de noms .............................. 19. Les modles ...................................... 20. Gestion des erreurs et exceptions ... 21. Et maintenant ? ............................... A. Mots-cls C++ ................................... B. Priorit des oprateurs ..................... C. Solutions des exercices ..................... D. tude des listes chanes .................. Index .......................................................
437 481 483 515 575 619 641 699 737 775 777 779 837 849
Introduction ............................................ Public vis ........................................... Conventions typographiques .............. Partie I .................................................... 1. Bien dbuter en C++ .......................... Introduction ........................................ Un peu dhistoire... ............................. La programmation oriente objet (POO) volution de C++ ............................... Est-il ncessaire dapprendre dabord le langage C ? .................................... C++, Java et C# .................................. Norme ANSI ...................................... Prparation la programmation ......... Votre environnement de dveloppement Cration du programme ...................... Cycle de dveloppement ..................... Votre premier programme C++ : BONJOUR.cpp ................................... Comment tirer parti de votre compilateur Erreurs de compilation ........................ Questions-rponses ............................. Testez vos connaissances ....................
1 1 2 3 5 5 5 9 11 11 11 12 13 13 14 15 16 19 20 21 22
Exercices ............................................ 2. Anatomie dun programme C++ ...... Un programme simple ........................ tude rapide de lobjet cout ............... Utilisation de lespace de nom standard Commentaires ..................................... Les fonctions ..................................... Questions-rponses ............................. Testez vos connaissances .................... Exercices ............................................ 3. Variables et constantes ...................... Quest-ce quune variable ? ............... Dnition dune variable .................... Cration de plusieurs variables la fois Affectation de valeurs aux variables .. Cration dalias avec typedef ............. short ou long : que choisir ? ............... Variables caractre .............................. Constantes .......................................... Constantes numres ........................ Questions-rponses ............................. Testez vos connaissances .................... Exercices ............................................
22 23 24 26 28 30 33 36 37 37 39 39 44 48 48 50 51 54 57 59 62 63 64
VI
Le langage C++
4. Expressions et instructions ............... Instructions ......................................... Expressions ........................................ Oprateurs .......................................... Combinaison doprateurs daffectation et doprateurs mathmatiques .......... Incrmentation et dcrmentation ...... Priorit des oprateurs ....................... Parenthses imbriques ..................... Vrai ou faux ....................................... Linstruction if .................................... Utilisation daccolades dans les instructions if imbriques ..... Oprateurs logiques ........................... valuation en court-circuit ................. Priorit des oprateurs relationnels .... Vrai ou faux ....................................... Oprateur conditionnel (ternaire) ....... Questions-rponses ............................ Testez vos connaissances ................... Exercices ............................................ 5. Fonctions ............................................ Quest-ce quune fonction ? ............... Valeurs renvoyes, paramtres formels et effectifs ........................................... Dclaration et dnition de fonctions Excution des fonctions ..................... Porte des variables ............................ Instructions des fonctions .................. Retour sur les paramtres des fonctions Retour sur les valeurs renvoyes ....... Paramtres par dfaut ......................... Surcharge de fonctions ....................... Pour en savoir plus sur les fonctions .. Principe des fonctions ........................ Questions-rponses ............................ Testez vos connaissances ................... Exercices ............................................ 6. Programmation oriente objet ......... C++ est-il orient objet ? ...................
65 66 67 69 72 72 75 76 77 78 86 89 90 90 91 92 93 94 95 97 98 99 99 103 104 111 111 112 115 117 121 128 132 133 133 135 135
Cration de nouveaux types ................ Prsentation des classes et des membres Accs aux membres dune classe ....... Accs priv et accs public ................ Implmentations des mthodes de la classe .......................................... Ajout de constructeurs et de destructeurs Inclure des fonctions membres const .. Interface et implmentation ................ O placer les dclarations de classes et les dnitions de mthodes ? .......... Implmentation en ligne ..................... Classes imbriques ............................. Les structures ...................................... Questions-rponses ............................. Testez vos connaissances .................... Exercices ............................................. 7. Droulement dun programme ......... Les boucles ......................................... Les boucles while ............................... Les boucles do...while ........................ Linstruction do...while ....................... Les boucles for ................................... Rsum des boucles ............................ Contrler le ux avec des instructions switch .................................................. Utilisation de switch dans un menu ... Questions-rponses ............................. Testez vos connaissances .................... Exercices ............................................. Partie II ................................................... 8. Pointeurs ............................................. Quest-ce quun pointeur ? ................. Utilit des pointeurs ............................ La pile et lespace mmoire adressable (tas) ..................................................... En savoir plus sur les fuites mmoire . Cration dobjets sur le tas ................. Suppression dobjets du tas ................
137 138 141 142 148 151 156 156 159 161 163 168 169 170 171 173 173 175 183 184 186 195 198 201 204 205 206 207 209 210 220 220 225 226 226
VII
Accs aux donnes membres .............. Cration de donnes membres dans le tas Le pointeur this ................................... Pointeurs perdus, incontrlables ou pointeurs fous ................................ Pointeurs const .................................... Questions-rponses ............................. Testez vos connaissances .................... Exercices ............................................. 9. Rfrences .......................................... Quest-ce quune rfrence ? .............. Utilisation de loprateur adresse de (&) sur des rfrences ................................ Rfrencement des objets ................... Pointeurs nuls et rfrences nulles ..... Passage de paramtres par rfrence .. En-ttes et prototypes de fonctions ..... Renvoi de plusieurs valeurs ............... Efcacit du passage de paramtres par rfrence ....................................... Choisir entre rfrences et pointeurs .. Mlanger les rfrences et les pointeurs dans une liste de paramtres ............... Renvoi de rfrences dobjet hors de porte ............................................. Renvoi dune rfrence un objet dans le tas ............................................ O le pointeur est-il pass ? ................ Questions-rponses ............................. Testez vos connaissances .................... Exercices ............................................. 10. Fonctions avances ........................... Fonctions membres surcharges ......... Utilisation de valeurs par dfaut ......... Choisir entre des valeurs par dfaut et des fonctions surcharges ............... Le constructeur par dfaut .................. Surcharge des constructeurs ............... Initialisation des objets ....................... Le constructeur de copie .....................
228 229 232 233 237 240 241 241 243 244 245 248 250 251 256 257 260 269 269 271 272 275 275 276 276 279 279 282 284 285 285 287 288
Surcharge des oprateurs .................... Conversion de type (transtypage) ....... Questions-rponses ............................. Testez vos connaissances .................... Exercices ............................................ 11. Analyse et conception oriente objet Les modles ........................................ Conception : le langage de modlisation Conception : le processus .................. tape 1 La conceptualisation : commencer par la vision .................... tape 2 Lanalyse : collecter les besoins tape 3 La conception .................... tapes 4 6 Implmentation, tests et dploiement .................................... Itrations ............................................. Questions-rponses ............................. Testez vos connaissances .................... Exercices ............................................ 12. Hritage ............................................ Quest-ce que lhritage ? .................. Priv ou protg ? ............................... Lhritage avec les constructeurs et les destructeurs ............................... Rednition des mthodes de la classe de base ............................. Mthodes virtuelles ............................ Questions-rponses ............................. Testez vos connaissances .................... Exercices ............................................ 13. Tableaux et chanes .......................... Quest-ce quun tableau ? ................... Accs aux lments de tableau ........... criture dlments hors limite .......... Erreurs dintervalle ............................ Initialisation des tableaux ................... Dclaration de tableaux ...................... Tableaux dobjets ............................... Tableaux multidimensionnels ............
293 312 317 318 319 321 322 322 324 327 327 341 355 355 355 356 356 357 357 362 365 372 378 392 393 393 395 395 396 398 401 402 402 404 406
VIII
Le langage C++
Initialisation de tableaux plusieurs dimensions ........................................ Construire des tableaux de pointeurs . Larithmtique des pointeurs, un sujet complexe ............................... Dclaration de tableaux sur le tas ...... Pointeurs sur tableau et tableaux de pointeurs ........................................ Pointeurs et noms de tableaux ........... Suppression des tableaux stocks sur le tas ............................................. Redimensionner les tableaux en cours dexcution ......................................... Tableaux de caractres et chanes ..... Les fonctions strcpy() et strncpy() ..... La classe String .................................. Listes chanes et autres structures .... Cration de classes tableaux ............. Questions-rponses ............................ Testez vos connaissances ................... Exercices ............................................ 14. Polymorphisme ................................ Problmes lis lhritage simple ..... Hritage multiple ............................... Types abstraits de donnes (TAD) ..... Questions-rponses ............................ Testez vos connaissances ................... Exercices ............................................ Partie III ................................................ 15. Classes et fonctions spciales ..........
407 409 411 414 415 415 417 418 421 423 425 433 434 434 435 436 437 437 445 463 477 478 479 481 483
16. Concepts avancs dhritage ........... Agrgation ......................................... Hritage et Dlgation/Agrgation .... Hritage priv ..................................... Classes amies ...................................... Fonctions amies .................................. Fonctions amies et surcharge doprateur .......................................... Surcharge de operator<<() ............... Questions-rponses ............................. Testez vos connaissances .................... Exercices ............................................. 17. Les ux .............................................. Prsentation des ux ........................... Flux et tampons .................................. Les objets E/S standard ...................... Redirection des ux standard ............. Les entres avec cin ............................ Autres fonctions membres de cin ....... Les sorties avec cout ........................... Manipulateurs, indicateurs dtat et instructions de formatage ................ Flux ou fonction printf() ..................... Entres/sorties sur chier ................... ofstream et ifstream ............................ Fichiers binaires ou chiers texte ....... Traitement de la ligne de commande .. Questions-rponses ............................. Testez vos connaissances .................... Exercices ............................................. 18. Espaces de noms ............................... Utilit des espaces de noms ............... Rsolution des fonctions et des classes par leurs noms ...................................... Cration dun espace de noms ............ Utilisation dun espace de noms ........ Mot-cl using ..................................... Alias despace de noms ..................... Espace de noms anonyme ................... Lespace de noms standard std ..........
515 515 531 541 550 560 560 566 570 571 571 575 575 578 579 579 580 585 594 597 601 604 604 610 612 616 617 617 619 619 620 625 628 630 634 634 636
Partage dinformations entre objets de mme type : donnes membres statiques 484 Mthodes membres statiques ............. Pointeurs sur des fonctions ................ Pointeurs sur des fonctions membres . Questions-rponses ............................ Testez vos connaissances ................... Exercices ............................................ 489 492 507 512 513 513
IX
Questions-rponses ............................. Testez vos connaissances .................... Exercices ............................................. 19. Les modles ...................................... Quest-ce quun modle ...................... Construire une dnition de modle ... Passer des objets modles instancis aux fonctions ....................................... Modles et amis .................................. Utilisation des des modles ................ La bibliothque des modles standard Les conteneurs .................................... Les conteneurs squentiels ................. Les conteneurs associatifs .................. Les classes dalgorithmes ................... Questions-rponses ............................. Testez vos connaissances .................... Exercices ............................................. 20. Gestion des erreurs et exceptions ... Erreurs logiques et fautes de syntaxe .. Les coulisses des exceptions ............... Produire ses propres exceptions .......... Crer une classe dexception .............. Placement des blocs try et catch ......... Donnes dans les exceptions et noms des objets exceptions .......................... Exceptions et modles ........................ Exceptions et erreurs .......................... Recherche des erreurs ......................... Questions-rponses ............................. Testez vos connaissances .................... Exercices ............................................. 21. Et maintenant ? ................................ Le prprocesseur et le compilateur ..... La directive de prprocesseur #dene . Inclusions et clauses dinclusion ........ Les macros ......................................... Manipulation des chanes ...................
637 638 639 641 641 643 651 652 660 676 676 677 687 692 695 696 697 699 700 702 706 708 712 719 727 730 731 733 734 734 737 738 738 741 742 745
Les macros prdnies ....................... La macro assert() ............................... Les fonctions inline ............................ Oprations sur les bits ........................ Styles de programmation .................... Commentaires ..................................... Comment poursuivre votre dveloppement C++ .................. Questions-rponses ............................. Testez vos connaissances .................... Exercices ............................................ A. Mots-cls C++ ................................... B. Priorit des oprateurs ..................... C. Solutions des exercices ..................... Chapitre 1 ........................................... Chapitre 2 ........................................... Chapitre 3 ........................................... Chapitre 4 ........................................... Chapitre 5 ........................................... Chapitre 6 ........................................... Chapitre 7 ........................................... Chapitre 8 ........................................... Chapitre 9 ........................................... Chapitre 10 ......................................... Chapitre 11 ........................................ Chapitre 12 ......................................... Chapitre 13 ........................................ Chapitre 14 ........................................ Chapitre 15 ........................................ Chapitre 16 ......................................... Chapitre 17 ......................................... Chapitre 18 ........................................ Chapitre 19 ......................................... Chapitre 20 ......................................... Chapitre 21 ......................................... D. tude des listes chanes .................. Les composants dune liste chane ... Index .......................................................
747 747 757 758 765 768 770 772 773 774 775 777 779 779 780 782 784 784 787 791 793 794 797 802 804 805 806 808 815 819 822 823 828 834 837 838 849
Introduction
Ce livre a t conu pour vous initier la programmation en C++. Vous y apprendrez les principaux concepts du langage (gestion des entres-sorties, boucles et tableaux, modles, etc.) et de la programmation oriente objet. Chaque chapitre contient des exemples de programmes qui mettent en uvre les concepts tudis et qui sont analyss en dtail. Tous les chapitres se terminent par un jeu de questions-rponses, un quiz et des exercices dont vous trouverez les rponses lAnnexe D. Les listings de code de chacun des chapitres sont disponibles sur le site www.pearson.fr, la page consacre cet ouvrage.
Public vis
Il nest pas ncessaire davoir une exprience pralable de la programmation pour tirer parti de ce livre car il part de zro et vous apprend la fois le langage et les concepts de programmation C++. Les nombreux exemples de syntaxe et les analyses dtailles des extraits de code constituent un excellent guide touristique pour votre voyage dans cet environnement si riche. Que vous soyez un programmeur dbutant ou expriment, vous pourrez constater que cet ouvrage vous permettra dapprendre rapidement et simplement le langage C++.
Le langage C++
Conventions typographiques
ce Astu
Ces paragraphes soulignent des informations qui amlioreront lefcacit de vos programmes C++. Ces notes fournissent des prcisions sur le sujet abord.
Info
ntion Atte
Ces avertissements vous vitent de tomber dans les piges les plus communs qui peuvent vous gcher lexistence.
Les encadrs Ils prcisent la syntaxe dune instruction. Il peut aussi sagir daparts destins attirer votre attention sur un cas particulier ou une situation exceptionnelle.
Faire
Un rappel des conseils suivre.
Ne pas faire
Les erreurs ou confusions viter.
Cet ouvrage utilise galement des polices de caractres diffrentes pour distinguer le code C++ du texte classique :
Les commandes, variables et autres lments de code apparaissent dans le texte dans une police chasse fixe spciale. Les termes nouveaux ou importants sont crits en italique. Les parties variables dans les descriptions de syntaxe apparaissent en police chasse fixe italique. Ce style signale que vous devez remplacer lespace rserv par le nom rel du chier, du paramtre ou de tout autre lment quil reprsente.
En outre, toutes les lignes de code sont numrotes. Vous constaterez parfois que certaines lignes trop longues pour le format de ce livre ont t dcoupes en plusieurs lignes : en ce cas, les lignes de continuation ne seront pas numrotes et vous devrez saisir ces lignes comme une seule ligne dans votre diteur de texte.
Partie I
Dans les sept chapitres qui constituent cette partie, vous ferez vos premiers pas en programmation en gnral et en C++ en particulier. Les Chapitres 1 et 2 abordent les notions de base de la programmation et le droulement dune application. Au Chapitre 3, vous apprendrez grer des variables et des constantes dans un programme. Le Chapitre 4 est consacr aux branchements conditionnels raliss laide dinstructions et dexpressions. Vous vous initierez, au Chapitre 5, lutilisation des fonctions, et celle des classes et des objets au Chapitre 6. Enn, partir du Chapitre 7, vous serez en mesure de comprendre le droulement dune application et dcrire vos premiers programmes orients objet.
1
Bien dbuter en C++
Au sommaire de ce chapitre
Pourquoi choisir le langage C++ ? Les tapes du cycle de dveloppement dun programme crire, compiler et lancer votre premier programme C++
Introduction
Bienvenue dans Le Langage C++ de la collection Le Programmeur ! Cet ouvrage a pour objectif de vous initier efcacement la programmation en langage C++.
Un peu dhistoire...
Les langages de programmation ont considrablement volu depuis les premiers calculateurs qui avaient t conus pour les calculs de trajectoires dartillerie durant la seconde guerre mondiale. cette poque, les programmeurs travaillaient avec le langage informatique le plus primitif qui soit le langage machine ce qui les obligeait grer de longues
Le langage C++
chanes de 1 et de 0. Puis, les premiers assembleurs apparurent, an de rendre les instructions machine plus comprhensibles et plus faciles utiliser puisquelles taient dsormais reprsentes par des instructions mnmoniques comme MOV et ADD. Dans les annes 60 apparurent des langages plus volus comme BASIC et COBOL, qui permettaient aux programmeurs dutiliser une syntaxe proche de la langue anglaise (le code source), avec des instructions et des mots comme Let I = 100. Les lignes dinstructions taient ensuite traduites en langage machine par des interprteurs ou des compilateurs. Un interprteur traduit et excute une une les instructions du programme (ou code source) et les transforme directement en actions. Un compilateur passe par une tape intermdiaire (la compilation) qui produit dabord un chier objet. Le compilateur fait ensuite appel un diteur de liens (ou linker) qui transforme ce chier objet en programme excutable. Les interprteurs lisent les instructions ligne ligne et excutent le code immdiatement, ce qui en simplie lutilisation. Aujourdhui, les programmes interprts sont gnralement appels scripts. Certains langages, comme Visual Basic 6, dsignent linterprteur sous le nom de bibliothque dexcution. Dautres langages, comme Visual Basic .NET et Java, disposent dun autre composant, appel "machine virtuelle" (VM, Virtual Machine) ou excuteur. Bien que la VM soit aussi un interprteur, il ne sagit plus dun interprteur de code source qui se contente de traduire un langage lisible en code objet : la VM interprte et excute un "langage machine indpendant de lordinateur" compil, parfois appel "langage intermdiaire". Les compilateurs ajoutent les tapes supplmentaires de compilation du code source (comprhensible par lhomme) en code objet (lisible par la machine). Ceci pourrait apparatre comme un inconvnient mais, en ralit, cette tape permet de crer un programme dont la vitesse dexcution est optimise puisque la traduction du chier source en langage machine a dj t ralis une fois pour toutes, au moment de la compilation. Il nest donc plus ncessaire de retraduire le programme chaque fois quon lexcute. Lautre avantage des langages compils comme C++ tient la diffusion des programmes puisque lon peut distribuer un chier excutable des personnes qui ne disposent pas du compilateur. Avec un langage interprt, par contre, lutilisateur doit ncessairement possder linterprteur pour pouvoir excuter le programme. C++ est gnralement un langage compil, mme sil existe quelques interprteurs C++. linstar de nombreux langages compils, il a la rputation de produire des programmes rapides et performants. En fait, pendant longtemps, la principale proccupation des programmeurs tait de concevoir des applications trs courtes pouvant sexcuter rapidement, car la mmoire et le temps de calcul cotaient cher. Avec la miniaturisation des ordinateurs, laugmentation de
Chapitre 1
leurs performances et la chute des prix, les priorits ont chang. Dsormais, le cot de dveloppement dpasse largement celui dun ordinateur de type PC ou mini. Limportant est dcrire des programmes performants, bien construits et faciles mettre jour (cest-dire sans surcot excessif). Le mot programme a deux sens. Il dsigne les instructions (ou code source) crites par un dveloppeur, mais galement lensemble dun logiciel excutable. Cette homonymie peut tre une source de confusion et il est important de faire la distinction entre le chier source et le programme excutable.
Info
Le langage C++
concentrerons uniquement sur une partie essentielle de cette volution : le passage de la programmation procdurale la programmation oriente objet.
Chapitre 1
La programmation structure rsout des problmes complexes de manire trs able. Toutefois, la n des annes 80, cette mthode a montr ses limites. Dune part, il est naturel dassocier les donnes (les enregistrements des employs, par exemple) et leur traitement (tri, modication, etc.). Malheureusement, la programmation structure spare les donnes des fonctions qui les manipulent et ne propose pas de moyen naturel de les regrouper. La programmation structure est donc souvent dsigne par le terme de programmation procdurale, car elle met laccent sur les procdures (plutt que sur les objets). Dautre part, les programmeurs devaient souvent rutiliser des fonctions. Or, certaines fonctions qui convenaient un type de donnes ne pouvaient pas toujours tre rutilises avec dautres, ce qui limitait leurs avantages.
Encapsulation
Un technicien ne fabrique pas les composants quil assemble. Il les choisit selon leurs spcications sans se proccuper de leur fonctionnement interne. Lautonomie dun objet est une proprit rsultant dun processus appel encapsulation. Celle-ci permet de masquer les donnes internes dun objet et de lutiliser sans connatre les dtails de son fonctionnement, exactement comme vous utilisez votre rfrigrateur
10
Le langage C++
sans comprendre le principe des compresseurs. Il reste possible de modier ce fonctionnement interne sans affecter celui du programme, condition toutefois que les spcications soient respectes (le compresseur du rfrigrateur peut tre remplac par un autre de conception similaire). Lorsque notre technicien veut utiliser un composant lectronique, il na pas besoin den connatre les rouages internes. Toutes les proprits de ce composant sont encapsules dans lobjet composant, elles ne sont pas communiques au circuit mont. Il nest pas ncessaire de connatre le fonctionnement du composant pour lutiliser efcacement. Ce fonctionnement est masqu par son botier. Le langage C++ gre lencapsulation laide de types dnis par lutilisateur : les classes. Pour en savoir plus sur la conception dune classe, reportez-vous au Chapitre 6. Si elle est correctement dnie, une classe agit comme une entit encapsule elle fonctionne comme un composant autonome. Comme pour un objet du monde rel, son fonctionnement interne peut tre masqu. Les utilisateurs dune classe bien conue nont pas besoin de savoir comment elle fonctionne, mais uniquement comment lutiliser.
Hritage et rutilisabilit
Lorsquils souhaitent concevoir une nouvelle voiture, les ingnieurs de Superauto ont le choix entre monter un projet partir de zro et modier un modle existant. Le modle Pipo est peut-tre parfait pour la ville, mais cette voiture manque de nervosit sur autoroute. Les ingnieurs ont donc dcid de lui adjoindre un turbo-compresseur et une bote six vitesses. Le responsable du projet prfre donc partir dun modle existant, le perfectionner, le tester, et lappeler Star. La Star sera donc une sorte de Pipo, mais il sagira dune version spciale, disposant de nouvelles fonctionnalits. Le langage C++ implmente la notion dhritage. Grce lhritage, vous pouvez dclarer un nouveau type partir dun type existant. On dit que la sous-classe obtenue drive du type existant ; on la nomme quelquefois "type driv". Si la Star est drive de la Pipo et hrite donc de toutes ses qualits, certaines proprits peuvent lui tre ajoutes, dautres modies. Pour en savoir plus sur lhritage et son application en C++, reportez-vous aux Chapitres 12 et 16.
Polymorphisme
La Star ne rpondra pas ncessairement de la mme faon que la Pipo un appui sur lacclrateur. En effet, la premire peut utiliser son injection et son turbo alors que la seconde devra se contenter de sa carburation classique. Quoi quil en soit, il suft au conducteur de dmarrer son vhicule et de se dplacer o bon lui semble. Il nest pas oblig de connatre les dtails du moteur.
Chapitre 1
11
En C++, des objets diffrents peuvent avoir des comportements adquats diffrents en rponse la mme action grce au polymorphisme de fonction et de classe. Poly signie plusieurs et morphe signie forme. Le polymorphisme se traduit donc par un nom unique pouvant prendre plusieurs formes, et il est trait aux Chapitres 10 et 14.
volution de C++
Lorsque les qualits de la programmation, de la conception et de lanalyse orientes objet commencrent tre reconnues, Bjarne Stroustrup cra C++ partir du langage le plus utilis pour le dveloppement des logiciels professionnels, le langage C. Il lui ajouta tous les lments ncessaires la programmation oriente objet. On a coutume de dire que C++ est un surensemble de langage C et que, par consquent, tout programme C est virtuellement un programme C++. Pourtant, ces deux langages sont trs diffrents. Pendant longtemps, C++ a attir les programmeurs C car ces derniers trouvaient sa syntaxe familire. Toutefois, pour tirer le meilleur prot des fonctionnalits de C++, de nombreux dveloppeurs ont compris quils devaient laisser de ct une partie de leurs acquis en C et aborder les problmes diffremment.
C++, Java et C#
C++ est lun des principaux langages pour le dveloppement de logiciels professionnels. Ces dernires annes, Java a eu un temps la faveur des programmeurs. Toutefois, nombre
12
Le langage C++
dentre eux, qui avaient abandonn C++ au prot de Java, ont depuis fait marche arrire. En tout tat de cause, les deux langages sont si semblables quapprendre lun revient connatre 90 % de lautre. C# (prononcez C sharp) est un langage plus rcent dvelopp par Microsoft pour la plateforme .NET. Il utilise la mme syntaxe que C++ et, bien que ces deux langages diffrent en quelques points importants, lapprentissage de C++ apporte la majorit des connaissances ncessaires lutilisation de C#. Si vous dcidiez par la suite dapprendre C#, linvestissement ralis dans lapprentissage de C++ vous sera trs bnque.
Norme ANSI
Le comit daccrditation des standard, qui dpend de lANSI (American National Standards Institute), a labor un standard international pour C++. Le standard C++ est galement appel norme ISO (International Organization for Standardization), norme NCITS (National Committee for Information Technology Standards), norme X3 (ancienne appellation de NCITS), ou norme ANSI/ISO. Dans ce livre, nous continuerons faire rfrence la norme ANSI car cest le terme le plus utilis. Lobjectif du standard ANSI est de garantir la portabilit du C++ an que le code que vous allez crire pour le compilateur de Microsoft, par exemple, ne gnre pas derreur avec un autre compilateur. Le code prsent dans ce livre tant compatible ANSI, il pourra tre compil sans erreur sur des plates-formes Macintosh, Windows ou Unix. Pour la plupart des utilisateurs C++, le standard ANSI est transparent. La version la plus rcente de cette norme est lISO/IEC 14882-2003. La version prcdente, lISO 14882-1998, a bnci dune stabilit durable et tous les fournisseurs de renom la prennent en charge. Nous nous sommes efforcs dassurer une totale compatibilit ANSI du code dans cette dition. Toutefois, noubliez pas que les compilateurs ne sont pas tous totalement compatibles avec la norme. En outre, certaines parties de la norme ont t laisses au choix du concepteur
Chapitre 1
13
du compilateur : il nest donc pas garanti que ces parties se compilent ou fonctionnent de la mme manire avec des compilateurs diffrents. Les extensions gres pour C++ ne sappliquant qu la plate-forme .NET et, ne faisant pas partie de la norme ANSI, elles ne sont pas traites dans cet ouvrage.
Info
Prparation la programmation
Plus que tout autre langage, C++ oblige le dveloppeur concevoir soigneusement une application avant de lcrire. Les programmes gurant dans les premiers chapitres de cet ouvrage ne ncessitent pas danalyse car ils sont triviaux. En revanche, les problmes complexes rencontrs en programmation professionnelle ne peuvent tre rsolus quavec cette approche. La conception et lanalyse permettent de mieux cerner les diffrents aspects du problme. Un programme bien construit ne contient pas derreurs et peut aisment tre mis jour. Daprs des tudes rcentes, on estime que le cot dun programme repose 90 % sur la mise au point et la maintenance. La phase de conception permet de rduire les cots et, par l mme, le prix de revient du logiciel. Avant de passer la conception dun programme, vous devez connatre parfaitement le problme rsoudre. Les programmes les plus simples comme les plus complexes sarticulent autour dun droulement clair et logique. Il convient galement de dterminer si le problme peut tre rsolu laide dun programme existant qui sera modi, ou laide dun logiciel du commerce. Quil choisisse lune ou lautre solution, le programmeur ne manquera de toutes faons pas de travail : trouver des solutions moins coteuses des problmes actuels produira toujours de nouvelles opportunits un peu plus tard. En supposant que le problme rsoudre ait t bien compris et quil faille crire un nouveau programme, vous tes prt commencer votre conception. Le processus dapprhension totale du problme (analyse) et dlaboration dun plan pour une solution (conception) est indispensable lobtention dune application professionnelle de carrure internationale.
14
Le langage C++
graphique comme Windows ou Macintosh. Recherchez une option comme console ou easy window ou consultez la documentation de votre compilateur. Votre compilateur peut faire partie dun environnement de dveloppement intgr (IDE) ou possder son propre diteur de texte pour saisir le code source des programmes. Vous pouvez galement utiliser un diteur de texte spar ou un logiciel de traitement de texte du moment que vous produisez des chiers texte sans formatage ni style particuliers. Le Bloc-notes de Windows, lditeur Edit de DOS, les diteurs Brief, Epsilon, Emacs et vi sont particulirement bien adapts la saisie des codes sources. Si vous disposez dun traitement de texte tel que WordPerfect ou Word (ou autre), utilisez la commande denregistrement au format texte simple. Les documents crs partir dun diteur de texte sappellent des chiers sources. En C++, ils portent traditionnellement lextension .cpp, .cp ou .c. Dans cet ouvrage, nous avons choisi lextension .cpp. Vriez que votre compilateur la prend en charge. La plupart des compilateurs C++ acceptent toutes les extensions et affectent par dfaut lextension .cpp aux chiers sources. Toutefois, soyez prudents, car certains compilateurs traitent les chiers .c comme du code C et les chiers .cpp comme du code C++. Vriez la documentation du compilateur. Dans tous les cas, une utilisation cohrente des .cpp pour les chiers du code source C++ facilitera la tche des programmeurs qui devront comprendre votre code.
Ne pas faire
Utiliser les fonctions de mise en forme dun
Info
Faire
crire le chier source laide de lditeur de
traitement de texte. Si vous utilisez un traitement de texte, sauvegardez le chier source au format texte ASCII.
Utiliser une extension .c si votre compila-
Cration du programme
La premire tape de la cration dun programme consiste crire les commandes adaptes (instructions) dans un chier source. Mme si les instructions du chier source semblent quelque peu mystrieuses pour ceux qui ne connaissent pas C++, il sagit quand mme dun format lisible. Le chier source nest pas un programme : il vous sera impossible de le lancer ou de lexcuter comme vous le feriez avec un programme excutable.
Chapitre 1
15
Cycle de dveloppement
Si chaque programme fonctionnait parfaitement ds le premier coup, le cycle de dveloppement complet serait : criture du programme, compilation du code source, dition des liens et lancement de lapplication. Malheureusement, rares sont les programmes qui ne contiennent aucune erreur. Certaines feront chouer la compilation du programme, dautres ldition des liens, dautres encore ne se dclareront quau moment de lexcution (celles-ci sont souvent appeles "bogues"). Toute erreur, quelle quelle soit doit tre corrige. Ceci implique de modier le code source, de le compiler nouveau, de recrer les liens et de relancer le programme. La Figure 1.1 illustre les diffrentes tapes de ce cycle de dveloppement.
16
Le langage C++
Dbut
Compilation
Oui
Oui
Oui
Erreurs d'excution ?
Non Fin
Chapitre 1
17
le cycle de dveloppement. En fait, les diffrents aspects de ce programme seront traits dans les deux prochains chapitres.
ntion Atte
Le listing suivant est numrot gauche an de vous permettre de suivre pas pas son droulement. Vous ne devez pas taper ces numros. Dans le premier exemple, vous devez uniquement taper pour la premire ligne :
#include <iostream>
Vriez que ce que vous avez tap est conforme ce chier source. Attention la ponctuation ! Pour obtenir le symbole de redirection (<<) la ligne 5, appuyez deux fois sur la touche <. Les termes std et cout sont spars par deux caractres deux-points (:). Noubliez pas non plus le point-virgule qui termine les lignes 5 et 6. La plupart des compilateurs compilent les chiers et ralisent ldition de liens dans la foule. Reportez-vous votre documentation pour voir si vous devez fournir une option particulire ou excuter une commande pour effectuer cette dition des liens. En cas derreur, comparez votre chier source avec celui du Listing 1.1. Si le compilateur signale que le chier iostream est introuvable, vous devrez peut-tre consulter votre documentation pour savoir comment votre compilateur prconise de congurer les chemins daccs des chiers inclure et les variables denvironnement. Si un message vous informe quil nexiste pas de prototype pour la fonction main, tapez linstruction int main(); avant la ligne 3 (cest lune de ces vilaines variations entre compilateurs). Si tel est le cas, vous devrez inclure cette instruction dans tous les programmes de cet ouvrage, bien que la plupart des compilateurs nen auront pas besoin. Votre programme, dans ce cas, ressemblera ceci :
#include <iostream> int main(); // ligne inutile pour la plupart des compilateurs int main() { std::cout <<"Bonjour!\n"; return 0; }
18
Le langage C++
Sous Windows, essayez de lancer bonjour.exe (en remplaant lextension de lexcutable par celle de votre systme dexploitation ; sous Unix, par exemple, excutez bonjour, car les excutables ne possdent pas dextension). Lexcution du programme doit afcher :
Bonjour!
Si cest le cas, bravo ! Tout va pour le mieux. Vous venez de crer votre premier programme C++. Il nest peut-tre pas spectaculaire, mais cest ainsi quont dbut la plupart des programmeurs professionnels. Certains programmeurs utilisant des IDE (comme Visual Studio ou Borland C++ Builder) risquent de voir apparatre brivement le programme dans une fentre qui disparatra rapidement, sans laisser le temps de lire ce quelle contient. Dans ce cas, ajoutez ces lignes votre source, juste avant linstruction return:
char reponse; std::cin >> reponse;
Ces lignes forcent le programme faire une pause jusqu ce que vous ayez saisi un caractre suivi dun appui sur la touche Entre. Elles permettent donc de voir les rsultats de votre test. Si elles sont ncessaires pour le chier bonjour.cpp, elles le seront srement pour la plupart des programmes de cet ouvrage.
Utilisation des bibliothques standard Si votre compilateur est trs ancien, il peut refuser de compiler le code ci-dessus car il ne trouvera pas les nouveaux chier enttes du standard ANSI. Dans ce cas, modiez votre programme de la faon suivante :
1: 2: 3: 4: 5: 6: 7:
Vous remarquerez que le nom du chier entte se termine prsent par .h (point-h) et que nous nutilisons plus std:: devant cout la ligne 5. Ceci correspond lancien style (pr-ANSI) de chiers entte et signie que votre compilateur est trop ancien ; il conviendra pour les premiers chapitres, mais lorsque nous aborderons les modles et les exceptions, il risque dtre inutilisable.
Chapitre 1
19
20
Le langage C++
Le programme sexcute, mais lafchage est si rapide que je nai pas le temps de lire. Quest-ce qui ne va pas ? Consultez la documentation de votre compilateur ; vous devez pouvoir contraindre le programme respecter une pause aprs son excution. Avec les compilateurs Microsoft il suft dutiliser la combinaison de touches Ctrl+F5. Avec nimporte quel compilateur, vous pouvez insrer les lignes suivantes immdiatement avant linstruction return (cest--dire entre les lignes 5 et 6 du Listing 1.1) :
Erreurs de compilation
Les erreurs de compilation peuvent survenir dans un certain nombre de cas. Le plus souvent, il sagit dune faute de frappe ou dun oubli. Les bons compilateurs mettront la ligne incrimine en vidence et vous indiqueront lerreur que vous avez commise. Les plus performants proposeront mme une solution au problme rencontr ! Pour tester les fonctionnalits de votre compilateur, il suft dinsrer dlibrment une erreur dans le programme bonjour.cpp, en tant la ligne 7 o doit se trouver laccolade fermante. Listing 1.2 : Erreur de compilation
1: 2: 3: 4: 5: 6: #include <iostream> int main() { std::cout << "Bonjour!\n"; return 0;
Compilez de nouveau le programme. Votre compilateur afche une erreur similaire celle-ci :
bonjour.cpp, line 7: fatal error C1004: unexpected end of file found.
Chapitre 1
21
Vous constatez que le message est un petit peu nigmatique et quil y est question dune erreur qui correspond la ligne 7. Ici, le compilateur indique quil manque des lignes sources et quil a atteint la n du chier sans dtecter laccolade fermante. En rgle gnrale, les messages derreur contiennent un numro de ligne, ce qui permet de rsoudre le problme plus aisment. Parfois, ces messages ne vous indiqueront que la proximit du problme : si un compilateur pouvait parfaitement identier toutes les erreurs, il les corrigerait lui-mme.
Questions-rponses
Q Quelle est la diffrence entre un diteur de texte et un traitement de texte ? R Contrairement un traitement de texte, un diteur de texte gnre des chiers texte sans code de formatage ni de mise en page. Un simple diteur de texte contient du texte brut, sans caractres gras, italiques, etc. Q Mon compilateur comprend un diteur intgr. Dois-je lutiliser ? R La plupart des compilateurs seront capables de traiter du code tap laide dun diteur de texte. Un diteur intgr permet de se dplacer rapidement dans le chier source et de suivre pas pas le cycle de dveloppement avant et aprs compilation. Les compilateurs modernes prsentent lavantage de pouvoir consulter laide en ligne, de compiler le code en direct et de rsoudre les problmes sans quitter lenvironnement de dveloppement. Q Le compilateur a produit des messages davertissement. Puis-je les ignorer ? R Les compilateurs produisent gnralement des avertissements et des erreurs. En cas derreurs, le programme ne pourra pas tre totalement construit. En ce qui concerne les avertissements, le compilateur poursuivra son travail et crera tout de mme le programme. Bien que de nombreux livres ne donnent pas de rponse cette question, la ntre est non ! Ds maintenant, considrez les messages davertissement comme des messages derreur. Le compilateur vous informe que quelque chose danormal ou de suspect a t dtect. Lisez le message lcran, puis corrigez lerreur. Certains compilateurs disposent mme dun paramtre qui permet de traiter tous les avertissements comme des erreurs et donc dempcher que le programme ne devienne un excutable. Q Quest-ce que la compilation ? R La vritable compilation intervient lorsque vous lancez le compilateur. Il sagit de ltape qui prcde ldition de liens et le lancement du programme. Les programmeurs ont
22
Le langage C++
tendance utiliser ce terme comme un raccourci dsignant la vritable tape de compilation suivie de ldition de liens.
Exercices
1. Examinez le listing qui suit et, sans lexcuter, devinez ce quil permet de faire.
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: #include <iostream> int main() { int x = 5; int y = 7; std::cout << end1; std::cout << x+y << " " << x * y; std::cout << end; return 0; }
2. Tapez le programme de lexercice prcdent. Compilez-le et ditez les liens. Que faitil ? Vous tiez-vous tromp en rpondant la question prcdente ? 3. Tapez le programme suivant, puis compilez-le. Quelle erreur produit-il ?
1: 2: 3: 4: 5: 6: include <iostream> int main() { std::cout << "Salut \n"; return 0; }
2
Anatomie dun programme C++
Au sommaire de ce chapitre
La structure dun programme C++ Les interactions entre ses composants La nature et le rle dune fonction
Un programme C++ se compose dobjets, de fonctions, de variables et dun certain nombre dautres composants. Lessentiel de cet ouvrage est consacr lexplication dtaille de ces diffrents lments mais, pour comprendre comment ils sarticulent ensemble, il convient dtudier un programme complet.
24
Le langage C++
Un programme simple
Le programme bonjour.cpp du Chapitre 1 est intressant bien des gards. Pour information, le Listing 2.1 reproduit la version originale du chier source. Listing 2.1 : Le programme bonjour.cpp prsente une structure type dapplication C++
1: 2: 3: 4: 5: 6: 7: #include <iostream> int main() { std::cout << "Bonjour!\n"; return 0; }
La ligne 1 inclut le chier iostream dans le chier courant. Le principe est le suivant : le premier caractre est le signe dise (#) qui indique une directive adresse au prprocesseur. Chaque fois que vous lancez le compilateur, le prprocesseur sexcute en premier : son rle est de dtecter les lignes commenant par le signe dise et deffectuer certaines oprations en fonction de ce prxe. Le prprocesseur sera tudi en dtail au Chapitre 21. La commande #include est une instruction du prprocesseur qui signie : "Le terme qui suit est un nom de chier. Trouver ce chier et insrer ici son contenu". Les chevrons (< et >) placs autour du nom du chier demandent au prprocesseur de rechercher ce chier aux emplacements habituels. Si linstallation a t ralise correctement, le prprocesseur trouvera le chier iostream dans le rpertoire contenant tous les chiers include. Ce chier (dont le nom est labrviation de Input-Output-Stream) est utilis par cout, qui permet dafcher le rsultat lcran. Le rle de la ligne 1 est dinclure le chier iostream dans ce programme, comme si vous laviez vous-mme saisi. Le prprocesseur est lanc avant le compilateur. Il traduit toutes les lignes commenant par le signe dise (#) en commandes spciales qui rendent votre code directement exploitable par le compilateur. Les compilateurs ne sont pas tous cohrents dans leur prise en charge de #include lorsque lon omet lextension des chiers. Si vous obtenez des messages derreur, vous devrez peut-tre modier le chemin de recherche de include pour votre compilateur ou ajouter lextension au chier inclus.
Info
Info
Chapitre 2
25
Le programme commence vraiment en ligne 3, avec la fonction main(). Cette fonction est obligatoire dans tous les programmes C++. Une fonction est un bloc dinstructions qui effectue une ou plusieurs actions et qui peut tre appele par une autre fonction, mais main() est spciale car elle est appele automatiquement au lancement du programme. Comme toutes les fonctions, main() doit indiquer le type de la valeur quelle renvoie. Dans le programme bonjour.cpp, il sagit de int, ce qui signie que la fonction renverra une valeur entire au systme dexploitation lorsquelle se terminera. Ici, elle renvoie la valeur 0, comme le montre la ligne 6. Cette valeur renvoye a relativement peu dimportance et est assez rarement utilise, mais le standard C++ exige que la fonction main() soit dclare ainsi.
ntion Atte
Certains compilateurs permettent de dclarer main() comme renvoyant void, ce qui signie quaucune valeur nest renvoye. Ceci nest plus autoris en C++ et il est prfrable de ne pas prendre cette mauvaise habitude. Dclarez main() comme renvoyant int et renvoyez simplement la valeur 0 la dernire ligne de la fonction main(). Tous les systmes dexploitation permettent de tester la valeur renvoye par un programme. La convention consiste renvoyer 0 pour indiquer que le programme sest termin correctement, et une valeur diffrente de zro dans le cas contraire.
Info
Toutes les fonctions commencent par une accolade ouvrante ({) et se terminent par une accolade fermante (}). Dans notre programme, les accolades de la fonction main() gurent aux lignes 4 et 7. Toute instruction comprise entre ces deux accolades fait partie de la fonction. Le plat de rsistance du programme se trouve la ligne 5. Lobjet cout permet dafcher un message lcran. Les objets et leurs particularits seront prsents au Chapitre 6 et lobjet cout et son alter ego cin seront dcrits au Chapitre 17. En C++, ces deux objets permettent de grer respectivement les sorties (notamment lcran) et les entres (au clavier, par exemple). cout est un objet fourni par la bibliothque standard. Une bibliothque est une collection de classes. La bibliothque standard est fournie avec tout compilateur compatible ANSI. On indique au compilateur que lobjet cout que lon souhaite utiliser fait partie de la bibliothque standard laide du spcicateur despace de nom std. Vous pouvez en effet avoir des objets portant le mme nom, mais provenant de fournisseurs diffrents et cest la raison pour laquelle C++ prvoit lutilisation des "espaces de noms". Un espace de nom est une faon de dire : "Lorsque je dis std::cout, je fais rfrence lobjet cout faisant
26
Le langage C++
partie de lespace de nom standard, et daucun autre". Vous le faites savoir au compilateur en indiquant le terme std suivi de deux caractres deux-points avant lobjet cout. Nous reparlerons des espaces de noms ultrieurement. La syntaxe de cout est relativement simple. Ce terme est suivi de loprateur de redirection de sortie (<<). La variable ou la chane de caractres gurant immdiatement aprs << sera crite lcran. Si vous voulez produire un message directement lcran, noubliez pas de le mettre entre guillemets ("), comme la ligne 5. Loprateur de redirection est reprsent par deux signes "plus grand que" sans espace entre eux.
Info
Une chane de caractres est une suite de caractres imprimables. Les deux derniers caractres, \n, correspondent un saut de ligne qui sera produit aprs le message. Pour en savoir plus sur les retours chariot et les sauts de ligne, reportez-vous au Chapitre 18. La fonction main() se termine la ligne 7 avec laccolade fermante.
Chapitre 2
27
9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22:
std::cout std::cout std::cout std::cout std::cout std::cout std::cout std::cout std::cout std::cout std::cout std::cout return 0; }
<< << << << << << << << << << << <<
std::endl; "Voici un tres grand nombre:\t" << 70000; std::endl; "8 et 5 font:\t"; 8+5 << std::endl; "Voici une fraction: \t\t"; (float) 5/8 << std::endl; "Et un nombre astronomique: \t"; (double) 7000 * 7000 << std::endl; "Remplacez le nom "; "par le votre...\n"; "Elie Kopter est un programmeur C++! \n";
ntion Atte
Certains compilateurs ont un bogue qui exige que des parenthses soit places autour de laddition avant de la passer cout. La ligne 13 devient alors :
13: cout << (8+5) << std::endl;
la ligne 2, linstruction #include <iostream> intgre le chier iostream au code source. Celui-ci est ncessaire cout et ses fonctions associes. la ligne 5, cout est utilis dans sa version la plus simple pour afcher une chane de caractres. Le symbole \n est un caractre de formatage spcial qui demande cout dafcher un caractre "nouvelle ligne". Trois valeurs sont ensuite passes cout la ligne 6. Chacune dentre elles est spare par loprateur dinsertion. La premire valeur correspond "Je tape 5: ". Notez que lespace situ aprs le caractre deux-points fait partie de la chane. Le chiffre 5 est alors pass loprateur dinsertion, puis le programme saute une ligne (entre guillemets ou apostrophes). La ligne suivante apparat lcran :
Je tape 5: 5
28
Le langage C++
Le chiffre suit immdiatement la chane de caractres, car vous navez pas insr de saut de ligne. Sans le savoir, vous avez concatn deux valeurs. La ligne 7 afche un message puis utilise le manipulateur std::endl qui a pour but dcrire une nouvelle ligne lcran (Pour en savoir plus sur endl, reportez-vous au Chapitre 16). endl faisant aussi partie de la bibliothque standard, on utilise le prxe std::, comme pour cout. endl signie end line (ligne nale). La dernire lettre est bien la lettre "l" et non le chiffre 1. Lutilisation de endl est prfrable celle de \n, car il est adapt au systme dexploitation utilis. En revanche, \n risque de ne pas reprsenter le caractre de nouvelle ligne complet exig sur un systme dexploitation ou une plate-forme particuliers.
Info
la ligne suivante, vous dcouvrez un nouveau caractre de formatage : \t. Il permet dinsrer une tabulation pour aligner les chanes de caractres (lignes 10 16). La ligne 10 montre que lon peut afcher tous les nombres entiers, quel que soit leur type. la ligne 14, on passe une addition (8 + 5) cout, qui afche 13. la ligne 15, la fraction 5/8 est transmise cout. Le terme (float) indique quune valeur dcimale ( virgule ottante) doit safcher. Lexpression 7000 * 7000 est ensuite transmise cout (ligne 17) et le terme double indique cout quil sagit dune valeur virgule ottante. Pour obtenir des informations dtailles sur les types de donnes, reportezvous au Chapitre 3. Aux lignes 18 et 20, vous avez remplac le nom du programmeur par le vtre. Si cest le cas, la sortie conrmera que vous tes un programmeur C++, ce qui doit tre vrai puisque cest lordinateur qui le dit !
Chapitre 2
29
3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25:
int main() { using std::cout; using std::endl; cout << "Salut.\n"; cout << "Je tape 5: " << 5 << "\n"; cout << "Loprateur endl "; cout << "provoque un saut de ligne a lcran."; cout << endl; cout << "Voici un trs grand nombre:\t" << 70000; cout << endl; cout << "8 et 5 font:\t"; cout << 8+5 << endl; cout << "Voici une fraction:\t\t"; cout << (float) 5/8 << endl; cout << "Et un nombre astronomique:\t"; cout << (double) 7000 * 7000 << endl; cout << "Remplacez le nom "; cout << "par le votre...\n"; cout << "Elie Kopter est un programmeur C++!\n"; return 0; }
La sortie est identique. Les lignes 5 et 6 du Listing 2.3 informent le compilateur laide du mot-cl using, que nous allons utiliser deux objets de la bibliothque standard. Il nest dans ce cas plus ncessaire de qualier les objets cout et endl. Une autre mthode consiste informer le compilateur que nous allons utiliser lespace de nom standard en totalit ; cest--dire que tout objet non explicitement quali sera considr comme provenant de lespace de nom standard. Au lieu dcrire std::cout; nous allons simplement prciser, comme au Listing 2.4 : using namespace std;.
30
Le langage C++
La sortie sera ici encore identique celle des prcdentes versions de ce programme. Lintrt dcrire using namespace std; est quil nest plus ncessaire de dsigner nommment les objets que vous allez utiliser (cout et endl). Vous prenez par contre le risque dutiliser par inadvertance des objets dune bibliothque inadquate. Les puristes prfrent crire std:: devant chaque instance de cout et endl. Les paresseux prfreront using namespace std;.
Commentaires
Lors de la saisie dun programme, vous savez avec prcision ce que vous faites. Mais si vous rouvrez le chier source un mois plus tard, certains passages du listing risquent de vous paratre difciles comprendre. Pour viter ce dsagrment et permettre aux autres de comprendre vos chiers, nhsitez pas insrer des commentaires. Les commentaires sont du texte simple qui nest pas trait
Chapitre 2
31
par le compilateur. Ils ont pour but dinformer le lecteur sur telle ou telle action du programme.
Info
32
Le langage C++
Le Listing 2.5 va vous familiariser avec lusage des commentaires. Il dmontre que ceuxci ninuent en rien sur le traitement et sur lafchage des donnes. Listing 2.5 : aide.cpp montre lusage des commentaires
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: #include <iostream> int main() { using std::cout; /* Ceci est un commentaire qui se termine lorsque le symbole toile-slash est rencontr */ cout << "Bonjour!\n"; // Ce commentaire se termine la fin de la ligne cout << "Exemples de commentaires!\n"; // Les commentaires double slash peuvent occuper une seule /* ligne tout comme les commentaires de type slash-toile */ return 0; }
Les commentaires des lignes 7 9 sont ignors par le compilateur, tout comme ceux des lignes 11, 14 et 15. Les commentaires de la ligne 11 sont compris entre le symbole // et le retour la ligne, alors que ceux des lignes 7 et 15 requirent un symbole de fermeture. Certains compilateurs C++ reconnaissent un troisime type de commentaires : les commentaires de documentation, signals par trois slashes (///). Les compilateurs qui les acceptent permettent de produire de la documentation partir de ces commentaires. Cependant, cette syntaxe ne fait pas encore partie de la norme C++ et ne sera donc pas dtaille ici.
Info
Quelques conseils...
vitez dinsrer des commentaires lorsque lopration effectue est vidente. En effet, ils surchargent le chier source et risquent dtre oublis par le programmeur lors de la prochaine mise jour. Cela tant dit, ce qui peut sembler vident une personne peut paratre obscur une autre : vous devez donc faire preuve de discernement lorsque vous placez des commentaires.
Chapitre 2
33
En rsum, les commentaires ne doivent pas dire ce qui se passe, mais expliquer pourquoi cela arrive.
Les fonctions
Bien que main() soit une fonction, ses caractristiques la distinguent des autres. En effet, lutilit des fonctions standard est quelles peuvent tre appeles ou invoques tout moment dans un programme ; or la fonction main() est appele par le systme dexploitation. Le droulement dun programme est squentiel (cest--dire quil suit lordre des lignes de code). Quand il rencontre une instruction dappel dune fonction, il se droute pour excuter cette dernire. Lorsque la fonction se termine, le programme continue son excution avec la ligne place immdiatement aprs lappel de la fonction. Comparons lappel dune fonction une action de la vie courante : alors que vous dessinez un paysage, la mine de votre crayon se casse. Vous cessez immdiatement de dessiner an de tailler votre crayon. Puis vous reprenez votre activit artistique l o vous laviez interrompue. Il en est de mme dans un programme. Lorsque celui-ci a besoin dun service, il fait appel une fonction, puis reprend son droulement une fois que celle-ci est termine. Le Listing 2.6 illustre ce concept. Pour plus dinformations sur les fonctions, reportez-vous au Chapitre 5. Les types de donnes prises en charge seront prsentes au Chapitre 3. Ce chapitre-ci donne uniquement un aperu des fonctions, car on les retrouvera dans la plupart des programmes C++.
Info
34
Le langage C++
La fonction DemoFonction() est dnie aux lignes 6 8. Lorsquelle est appele, elle afche un message, puis rend la main au programme. Le vritable programme commence la ligne 13. la ligne 15, la fonction main() afche un message indiquant que lon est dans la fonction principale. la ligne suivante, la fonction DemoFonction() est appele, ce qui signie que les instructions de cette dernire vont sexcuter. Dans le cas prsent, la fonction se rsume un message (ligne 7). la ligne 8, DemoFonction() se termine. Le programme reprend son excution la ligne 17 et la fonction main() afche son dernier message.
Un paramtre formel est une dclaration du type de la valeur transmise la fonction. La valeur relle passe par la fonction appelante sappelle le paramtre rel (certains programmeurs utilisent galement le terme dargument). Le corps dune fonction se compose dune accolade ouvrante, de zro ou plusieurs instructions et dune accolade fermante. Les instructions constituent le travail rel de la fonction.
Chapitre 2
35
Une fonction peut renvoyer une valeur laide de linstruction return. Le type de cette valeur doit correspondre celui qui a t dclar dans len-tte de la fonction. En outre, lexcution de linstruction return provoque galement la n de lexcution de la fonction. Une fonction qui se termine sans passer par une instruction return renvoie void (rien) lorsquelle atteint son accodade fermante. En outre, une fonction cense renvoyer une valeur mais se terminant sans instruction return peut provoquer un message davertissement ou une erreur avec certains compilateurs. Dans le Listing 2.7, la fonction attend deux paramtres entiers et renvoie une valeur entire. Pour le moment, ne vous attardez pas sur la syntaxe et sur le traitement des valeurs entires (comme int x). Vous dcouvrirez ces sujets au Chapitre 3. Listing 2.7 : Mise en uvre dune fonction simple
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: #include <iostream> int Somme (int x, int y) { std::cout << "Somme() a recu " << x << " et " << y << "\n"; return (x + y); } int main() { using std::cout; using std::cin;
cout << "Vous etes dans main()!\n"; int a, b, c; cout << "Entrez deux nombres: "; cin >> a; cin >> b; cout << "\nAppel de Somme()\n"; c = Somme(a, b); cout << "\nRetour dans main().\n"; cout << "c vaut maintenant " << c; cout << "\nFin du traitement...\n\n"; return 0; }
36
Le langage C++
Dnie la ligne 2, la fonction Somme() reoit deux paramtres entiers et renvoie une valeur entire. Le programme commence la ligne 8 ; il afche un message invitant lutilisateur entrer deux nombres (ligne 16), spars par un espace. Ces nombres sont placs dans les variables a et b aux lignes 17 et 18. Pour valider, tapez sur Entre. La fonction main() passe ces deux nombres en paramtres la fonction Somme() (ligne 20). Le programme se droute dans la fonction Somme() qui commence la ligne 2. Les valeurs de a et b sont reues, respectivement, par les paramtres x et y. La fonction afche ces valeurs puis les additionne ; le rsultat obtenu est renvoy la ligne 5, puis la fonction rend la main la fonction qui la appele la fonction main(), ici. Dans les lignes 17 et 18, lobjet cin permet dobtenir des valeurs pour les variables a et b, qui sont ensuite afches laide de lobjet cout. Nous traiterons en dtail les variables et les diffrents aspects dun programme dans les chapitres suivants.
Mthodes ou fonctions
Il faut noter ici que les diffrents langages de programmation et les diffrentes mthodologies peuvent dsigner les fonctions par un autre nom. Lun des termes les plus habituels est celui de mthode. Une mthode est simplement une fonction qui fait partie dune classe.
Questions-rponses
Q Quelle est la signication du terme #include ? R Il sagit dune directive du prprocesseur qui sexcute lorsque vous appelez le compilateur. Cette directive insre le chier dont le nom est indiqu entre chevrons, comme si vous laviez saisi en totalit, dans le code source du programme. Q Quelle est la diffrence entre les commentaires // et les commentaires de type /* ? R Les commentaires double slash (//) (commentaires sur une ligne) prennent n avec le retour la ligne, alors que les commentaires slash-toile (/*) (commentaires sur plusieurs lignes) se terminent avec un symbole de fermeture (*/). Noubliez pas de fermer tous les commentaires multilignes an dviter toute erreur de compilation.
Chapitre 2
37
Q Quest-ce qui distingue un bon commentaire dun mauvais ? R Un bon commentaire indique les actions qui vont tre ralises par une fonction, une instruction ou un bloc dinstructions. Un mauvais commentaire surcharge le code de formules redondantes et dexplications inutiles. Le code des programmes doit tre crit de faon se comprendre aisment par lui-mme. Une ligne bien crite ne devrait pas avoir besoin dun commentaire pour expliquer ce quelle fait, mais cest vous de distinguer ce qui mrite commentaire et ce qui est vident pour tous.
Exercices
1. crivez un programme qui afche "Jadore C++" lcran. 2. crivez le plus petit programme pouvant tre compil, li, puis lanc. 3. CHERCHEZ LERREUR : compilez ce programme. Pourquoi choue-t-il ? Corrigez lerreur.
1: 2: 3: 4: 5: #include <iostream> main() { std::cout << Y a-t-il un bogue dans la salle?"; }
4. Aprs avoir corrig ce bogue, recompilez le programme puis lancez-le. 5. Incluez dans le Listing 2.7 une fonction de soustraction, nomme Difference() et utilisez-la de la mme manire que la fonction Somme(). Passez-lui les valeurs qui ont t passes la fonction Somme().
3
Variables et constantes
Au sommaire de ce chapitre
Dclarer et dnir des variables et des constantes Affecter des valeurs aux variables, puis grer ces valeurs Afcher le contenu dune variable lcran
Les programmes doivent mmoriser les donnes quils utilisent ou les crer pour les rutiliser lors de lexcution du programme. Pour cela, les variables et les constantes proposent plusieurs mthodes pour reprsenter, stocker et manipuler des informations.
40
Le langage C++
permanent est un autre problme. Gnralement, pour stocker des valeurs de faon persistante, vous devez les enregistrer soit dans une base de donnes, soit dans un chier sur le disque. Cette dernire mthode est prsente au Chapitre 16.
Info
RAM est lacronyme de Random Access Memory (mmoire accs direct). Lorsque vous lancez un programme, il est charg du disque vers la RAM. La mmoire vive est galement le sige de toutes les variables du programme. En programmation, le terme mmoire dsigne le plus souvent la mmoire vive.
Chapitre 3
Variables et constantes
41
Chaque emplacement de la RAM occupe un octet. Si le type de variable cr a une taille de 4 octets, celle-ci occupera donc quatre emplacements en mmoire. Le type de la variable (int, par exemple) indique au compilateur combien despace mmoire (ou demplacements) rserver dans la RAM. une poque, les programmeurs devaient imprativement comprendre la notion de bits et doctets, qui sont en fait les units de base de stockage. Bien que les langages actuels permettent de faire abstraction de ces dtails, il demeure intressant de comprendre comment sont stockes les donnes. Pour en savoir plus, reportez-vous lAnnexe A.
Lorsque vous crivez des programmes, ne faites jamais de supposition sur la taille mmoire utilise pour reprsenter un type particulier.
Pour dterminer la taille exacte des types pris en charge par votre ordinateur, compilez et excutez le programme du Listing 3.1.
42
Le langage C++
Listing 3.1 : Dterminer la taille des types de variables sur votre ordinateur
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: #include <iostream> int main() { using std::cout; cout << "La taille dun entier est:\t\t" << sizeof(int) << " octets.\n"; cout << "La taille dun entier court est:\t" << sizeof(short) << " octets.\n"; cout << "La taille dun entier long est:\t" << sizeof(long) << " octets.\n"; cout << "La taille dun type char est:\t\t" << sizeof(char) << " octets.\n"; cout << "La taille dun type float est:\t\t" << sizeof(float) << " octets.\n"; cout << "La taille dun type double est:\t" << sizeof(double) << " octets.\n"; cout << "La taille dun booleen est:\t\t" << sizeof(bool) << " octets.\n"; return 0; }
Info
Le Listing 3.1 ne prsente pas de difcults majeures. Certaines lignes ont t scindes en deux pour des raisons ddition. Par exemple, les lignes 7 et 8 pourraient normalement tenir sur une seule ligne. Le compilateur ignore les blancs (espace, tabulation, saut de ligne) et considre ces instructions comme une seule ligne. Cest pourquoi vous devez terminer la plupart des lignes par un signe ";".
Chapitre 3
Variables et constantes
43
Les lignes 7 20 font appel loprateur sizeof, qui est utilis comme une fonction. Il renvoie la taille de lobjet qui lui a t pass en paramtre. Par exemple, la ligne 8, le mot cl int est pass sizeof. Vous verrez plus loin dans ce chapitre que int permet de dcrire une variable entire standard. Si lon utilise sizeof sur un Pentium 4 sous Windows XP, la taille dune valeur int quivaut celle dun entier long, soit quatre octets. Les autres lignes du Listing 3.1 afchent les tailles des autres types de donnes. Vous en saurez plus sur les valeurs que ces types de donnes peuvent stocker et ce qui les diffrencie un peu plus loin.
signed et unsigned
Toutes les variables entires ont deux variantes : signe (signed) ou non signe (unsigned). En effet, on a parfois besoin de nombre ngatifs, parfois non. Les entiers dclars sans le mot-cl unsigned sont considrs comme signs. Les entiers signs sont soit ngatifs, soit positifs, alors que les entiers non signs sont toujours positifs. Les entiers, quils soient signs ou non, occupent le mme espace mmoire. De ce fait, une partie de la place de stockage dun entier sign sera donc utilise pour indiquer sil est positif ou ngatif. La valeur la plus leve que vous pouvez stocker dans un entier non sign correspond donc au double du nombre positif le plus lev contenu dans une variable entire signe. Par exemple, si un entier short est stock sur deux octets, un entier short non sign peut contenir une valeur comprise entre 0 et 65 535. Pour un entier short sign, la moiti des valeurs admises correspond des nombres ngatifs. Lintervalle autoris se situe donc entre 32 768 et +32 767.
Types fondamentaux
C++ dispose de plusieurs types prdnis, qui peuvent tre diviss en types entiers (voir plus haut), en types ottants et en type caractre. Les variables virgule ottante peuvent tre exprimes sous la forme de fractions, ce sont des nombres rels. Les variables de type caractre noccupent gnralement quun seul octet et servent souvent stocker les 256 caractres et symboles du jeu ASCII standard et des jeux ASCII tendus (en vigueur dans certaines langues). Le jeu de caractres ASCII est un ensemble normalis de lettres, de nombres et de symboles reconnu par tous les ordinateurs. ASCII est lacronyme de American Standard Code for Information Interchange. Cette norme de codage est prise en charge par la plupart des systmes dexploitation, bien que nombre dentre eux prennent galement en charge dautres jeux de caractres internationaux.
Info
44
Le langage C++
Les types de variables utilises en C++ sont dcrits dans le Tableau 3.1. En face du libell du type C++ gurent la traduction franaise ainsi que sa taille et les valeurs qui peuvent tre stockes. Ces valeurs sont dtermines par la taille des types de variables, conformment la sortie produite par le programme du Listing 3.1 ; consultez son rsultat pour voir si vos types de variables sont de la mme taille. Il est trs probable que ce sera le cas, moins que vous nutilisiez un ordinateur ayant un processeur 64 bits.
Tableau 3.1 : Types de variables
Type C++ bool unsigned short int short int unsigned long int long int int int unsigned int unsigned int char float double
Traduction franaise
boolen entier court non sign entier court entier long non sign entier long entier (16 bits) entier (32 bits) entier non sign (16 bits) entier non sign (32 bits) texte nombre dcimal nombre double
Taille
1 octet 2 octets 2 octets 4 octets 4 octets 2 octets 4 octets 2 octets 2 octets 1 octet 4 octets 8 octets
Info
La taille des variables peut varier en fonction du compilateur et du processeur de votre ordinateur. En fait, le Tableau 3.1 et le listing qui prcde se compltent, car les valeurs ont t extraites de la mme conguration. Si le programme afche un rsultat diffrent, reportez-vous au manuel de votre compilateur.
Chapitre 3
Variables et constantes
45
Pour crer ou dnir une variable, vous devez indiquer son type suivi de un ou plusieurs espaces, puis le nom de la variable suivi dun point-virgule. Un nom de variable peut tre compos de nimporte quelle combinaison de lettres, de chiffres et de symboles, mais ne peut pas contenir despace. Exemple : x, J23qrsnf, monAge. Le nom de la variable doit reter son rle, ce qui permet de suivre plus aisment le droulement du programme. Linstruction suivante dnit la variable entire monAge :
int monAge;
Info
Lorsque vous dclarez une variable, la mmoire correspondante est alloue (rserve) cette variable. La valeur de cette variable sera alors toute valeur se trouvant cet emplacement mmoire ce moment. Vous verrez bientt comment affecter une nouvelle valeur cet emplacement mmoire.
vitez dutiliser des noms barbares qui nvoquent rien comme J23qrsnf et rservez les noms dune seule lettre comme x ou i, des variables qui ne seront utilises quun bref instant. Les noms expressifs comme Total ou monAge sont bien plus faciles comprendre et mettre jour. Nous allons illustrer ces propos par deux exemples. Pour cela, tapez les deux blocs dinstructions suivants :
Exemple 1 int main() { unsigned short x; unsigned short y; unsigned short z; z = x * y; return 0; } Exemple 2 int main() { unsigned short Largeur; unsigned short Longueur; unsigned short Surface; Surface = Largeur * Longueur; return 0; }
Info
Si vous compilez ces programmes, votre compilateur vous signalera que ces valeurs ne sont pas initialises. Nous allons bientt voir comment rsoudre ce problme.
46
Le langage C++
Il est vident que le second programme est plus facile comprendre. En outre, les noms longs donnent un sens au code et facilitent dventuelles modications.
ntion Atte
Conventions de nommage
Il existe plusieurs conventions pour les noms de variables. Peu importe celle que vous choisirez, mais il convient de rester homogne dans lattribution des noms tout au long du programme de manire faciliter la lecture du code par dautres programmeurs. Certains programmeurs dtestent les majuscules et tapent tout le code en minuscules. Si le nom de la variable requiert deux lments (par exemple, sous total), il est possible de les sparer laide dun caractre de soulignement, ou de les accoler. Exemple : sous_total ou sousTotal. La prsence dune lettre majuscule au milieu du nom de la variable facilite la lecture ; elle fait penser une bosse et cest la raison pour laquelle on lappelle camel case. Lutilisation des caractres de soulignement ne fait pas lunanimit. Certains estiment que le code source est plus facile lire, alors que dautres pensent que la touche nest pas bien situe sur le clavier. Dans cet ouvrage, nous avons adopt la notation "dromadaire". Pour une meilleure lisibilit du texte, tous les lments du nom (sauf le premier) commencent par une majuscule. Exemple : monAge, totGeneral, etc. Notez que nous nutilisons pas les caractres accentus, la plupart des versions ne les prenant pas en charge. En programmation avance, on rencontre souvent la notation dite hongroise. Cette pratique consiste utiliser un prxe correspondant au type de la variable. Par exemple, les variables entires peuvent commencer par un i minuscule. Les variables de type long peuvent commencer par un l. Bien entendu, il existe dautres prxes signalant diffrentes constructions en C++ pour les pointeurs, les constantes, etc., que nous verrons par la suite.
Chapitre 3
Variables et constantes
47
Info
Lorigine du nom de la notation hongroise vient de son inventeur, un hongrois du nom de Charles Simonyi, de Microsoft. Vous pouvez trouver sa monographie originale ladresse : http://www.strangecreations.com//library/c/naming.txt.
Microsoft a abandonn rcemment cette notation, et recommande de ne pas lutiliser en C#. Cette recommandation est galement valable pour C++.
Mots-cls
Certains mots sont rservs par C++ et ne peuvent donc pas tre utiliss comme noms de variables. Il sagit de mots-cls ayant une signication particulire pour le compilateur C++, par exemple if, while, for et main. Vous en trouverez une liste complte au Tableau 3.2 ainsi qu lAnnexe B. Il est possible que votre compilateur dispose dautres mots rservs : vous devrez consulter son manuel pour en avoir la liste complte.
Tableau 3.2 : Les mots-cls de C++
asm auto bool break case catch char class const const_cast continue default delete do long double dynamic_cast
else enum explicit export extern false float for friend goto if inline int struct mutable namespace
new operator private protected public register reinterpret_cast return short signed sizeof static static_cast wchar_t switch template
this throw true try typedef typeid typename union unsigned using virtual void volatile
while
48
Le langage C++
Faire
Dnir une variable en indiquant son type et
Ne pas faire
Utiliser des mots-cls de C++ comme noms
son nom.
Utiliser des noms de variables signicatifs. Ne pas oublier que C++ respecte la casse des
de variables.
Prsumer du nombre doctets utiliss pour le
caractres.
Tenir compte de lespace occup par la varia-
ble en mmoire en fonction de son type, pour affecter des valeurs adquates.
Comme vous pouvez le constater, monAge et monPoids sont des variables entires non signes car il est inconcevable quun ge ou un poids soient ngatifs. Sur la deuxime ligne, les variables surface, largeur et longueur sont de type long. Une mme instruction de dnition ne peut comprendre des types de variables diffrents.
Chapitre 3
Variables et constantes
49
long est un raccourci pour long int, comme short pour short int.
Info
Il est possible de combiner les instructions de cration dune variable celles daffectation dune valeur. Vous pouvez par exemple combiner ces deux tapes pour la variable largeur en crivant :
unsigned short largeur = 5;
Linitialisation ressemble beaucoup laffectation prcdente. Les diffrences entre les deux oprations sont mineures en ce qui concerne les entiers. Dans ce chapitre, vous allez apprendre que certaines variables doivent tre initialises, car il nest plus possible de leur attribuer de valeur par la suite. On peut galement initialiser plusieurs variables simultanment. Linstruction suivante, par exemple, cre deux variables du type long et les initialise :
long largeur = 5, hauteur = 7;
Dans cet exemple, trois variables entires sont cres, mais seules la premire et la dernire sont initialises. Vous pouvez compiler le chier source du Listing 3.2. Ce programme calcule la surface dun rectangle, puis afche le rsultat lcran. Listing 3.2 : Utilisation de variables
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: // Dmonstration de variables #include <iostream> int main() { using std::cout; using std::endl; unsigned short int Largeur = 5, Longueur; Longueur = 10; // Cre un entier court non sign // et linitialise avec le rsultat de la
50
Le langage C++
// multiplication de Largeur par Longueur unsigned short int Surface = (Largeur * Longueur); cout << "Largeur:" << Largeur << endl; cout << "Longueur: " << Longueur << endl; cout << "Surface: " << Surface << endl; return 0; }
Comme vous laurez constat dans le prcdent listing, la ligne 2, linstruction include insre la bibliothque iostream, ce qui permet cout de fonctionner. Le programme commence la ligne 4 par la fonction main(). Les lignes 6 et 7 dnissent cout et endl comme faisant partie de lespace de nom standard (std). La ligne 9 dnit les premires variables. Largeur est un entier court non sign, initialis 5. En revanche, la variable Longueur est dnie et non initialise, elle reoit la valeur 10 la ligne 10. La ligne 14 dnit lentier court non sign Surface, qui reoit le produit de Largeur par Longueur. Les diffrentes valeurs sont ensuite afches lcran (lignes 16 18). Le motcl endl permet de passer la ligne.
Le nouveau nom USHORT fait rfrence au type entier court non sign et peut tre utilis chaque fois que vous auriez crit unsigned short int. Le Listing 3.3 ressemble au Listing 3.2, mais nous avons utilis le nouveau type USHORT.
Chapitre 3
Variables et constantes
51
Info
la ligne 4, USHORT est dni comme synonyme de unsigned short int. Par ailleurs, le programme est pratiquement identique au Listing 3.2 et la sortie est la mme.
52
Le langage C++
cette valeur est rpartie entre les valeurs positives et ngatives, et leur valeur maximale est donc la moiti de celle dun entier court non sign. En revanche, les entiers longs non signs prennent en charge une valeur comprise entre 0 et 4 294 967 295. Si vous envisagez de grer une valeur suprieure, vous avez le choix entre le type float et le type double, au dtriment de la prcision numrique. Sur la plupart des ordinateurs, seules les 7 ou 9 premires positions sont prises en compte, ce qui signie que le rsultat sera arrondi aprs ces chiffres. Les variables courtes occupent moins de mmoire mais, de nos jours, la mmoire nest pas chre et la vie est courte, nhsitez donc pas utiliser des variables de type int qui occuperont probablement quatre octets sur votre machine.
Chapitre 3
Variables et constantes
53
la ligne 7, Nombre est une variable de type entier court non sign qui, sur un Pentium 4 sous Windows XP, est dune longueur de deux octets et peut donc contenir une valeur comprise entre 0 et 65 535. la ligne suivante, cette variable reoit la valeur maximale et le rsultat safche correctement (ligne 9). la ligne 10, la variable est incrmente de un, comme le montre le symbole ++. Au passage, vous pouvez constater que le nom de C++ semble indiquer quil sagit dun incrment du langage C. La valeur de Nombre est donc gale 65 536, valeur refuse pour un entier court non sign. La variable est remise zro et son contenu apparat lcran. la ligne 12, Nombre est incrment de nouveau. Sa nouvelle valeur (1) safche.
Lorsque vous ne disposez plus de nombres positifs, vous atteignez le plus grand nombre ngatif puis dcomptez jusqu la valeur 0. Dans le programme du Listing 3.5, le nombre positif maximal est incrment de un.
54
Le langage C++
la ligne 3, Nombre est dclar comme entier court sign (par dfaut, tout entier court est sign, pour dnir un entier non sign, vous devez crire explicitement le mot-cl unsigned). Le programme fonctionne sensiblement comme le prcdent. Bien entendu, les rsultats obtenus sont diffrents. Pour bien assimiler cet exemple, vous devez comprendre comment les nombres signs sont reprsents sous forme binaire dans un entier dune longueur de deux octets. Comme pour un entier non sign, lentier sign reboucle de la plus haute valeur positive la plus haute valeur ngative.
Variables caractre
Les variables caractre (type char) occupent gnralement 1 octet et acceptent jusqu 256 valeurs (voir Annexe C). Ce type char peut tre interprt comme un nombre court (0 255) ou comme un membre du jeu de caractres ASCII. Le jeu de caractres ASCII et son quivalent ISO permettent de coder toutes les lettres, les chiffres et les signes de ponctuation. Les ordinateurs sont incapables de traiter des lettres, des signes de ponctuation ou des phrases. Ils ne comprennent que les nombres. En fait, leur rle se limite examiner une jonction de cbles particulire an de dterminer si une charge
Info
Chapitre 3
Variables et constantes
55
lectrique sufsante peut y tre dtecte. Cette charge est alors reprsente en interne comme un 1, ou comme un 0 si elle est absente. En regroupant lensemble de ces 0 et 1, lordinateur est capable de gnrer des structures interprtes comme des nombres, qui leur tour correspondent des lettres ou des signes de ponctuation. En code ASCII, la valeur 97 correspond la lettre "a" minuscule. Les majuscules, les minuscules, les chiffres et les signes de ponctuation sont associs aux valeurs ASCII comprises entre 1 et 128. Les 128 valeurs restantes sont rserves au constructeur informatique, bien que le jeu IBM tendu soit devenu une norme utilise par de nombreux programmeurs. ASCII se prononce "A-ski".
Info
Caractres et nombres
Tout caractre affect une variable char (comme a) correspond en fait un nombre entre 0 et 255. Le compilateur est capable didentier, de coder et de dcoder tout caractre, nombre ou signe de ponctuation gurant entre des guillemets simples. La relation valeur/lettre est arbitraire. Il sagit dune convention prise en charge par le clavier, le compilateur et lcran. Attention : la valeur 5 et le caractre 5 ne sont pas gaux, ce dernier ayant une valeur ASCII de 53. Cette notion est illustre dans le Listing 3.6. Listing 3.6 : Impression de caractres partir de leurs cSCII
1: 2: 3: 4: 5: 6: 7: } #include <iostream> int main() { for (int i = 32; i<128; i++) std::cout << (char) i; return 0;
56
Le langage C++
Ce programme permet dafcher les valeurs caractres des codes ASCII compris entre 32 et 127. Pour accomplir cette tche, ce listing utilise une variable entire, i, la ligne 4. En ligne 5, le nombre de la variable i est oblig de safcher sous forme de caractre. On aurait aussi pu utiliser une variable caractre, comme dans le Listing 3.7, pour arriver au mme rsultat. Listing 3.7 : Impression de caractres partir de leurs codes ASCII version 2
1: 2: 3: 4: 5: 6: 7: #include <iostream> int main() { for (unsigned char i = 32; i<128; i++) std::cout << i; return 0; }
Comme vous le pouvez le constater la ligne 4, on utilise ici un caractre non sign. Une variable de caractre tant utilise la place dune variable numrique, le cout de la ligne 5 sait afcher la valeur du caractre.
Cet exemple dclare une variable de type char (carTab) qui est initialise avec la valeur \t reconnue comme une tabulation. Les caractres de formatage sont utiliss sur nimporte quel priphrique de sortie : cran, imprimante et chier disque. Le caractre dchappement (\) modie la signication du caractre quil prcde. Par exemple, si vous tapez n, vous afchez la 14e lettre de lalphabet, alors que prcd du caractre dchappement, il correspond une nouvelle ligne.
Tableau 3.3 : Caractres dchappement
Caractre \a \b
Signication
alerte sonore retour arrire
Chapitre 3
Variables et constantes
57
Signication
saut de page nouvelle ligne retour chariot tabulation horizontale tabulation verticale guillemet simple guillemet double point dinterrogation barre oblique inverse notation octale notation hexadcimale
Constantes
Comme les variables, les constantes sont des emplacements de stockage dinformation. Mais, la diffrence des variables, elles ne sont pas modiables : vous devez donc initialiser une constante lorsque vous la dclarez et vous ne pourrez plus ensuite lui affecter de nouvelle valeur. C++ distingue deux types de constantes : les constantes littrales et les constantes symboliques.
Constantes littrales
Une constante littrale est une valeur apparaissant directement dans le code du programme au moment o lon en a besoin. Exemple :
int monAge = 32;
monAge est une variable de type int, alors que 32 est une constante littrale. Vous ne pouvez pas affecter de valeur 32 et sa valeur ne peut pas tre modie.
58
Le langage C++
Constantes symboliques
Comme une variable, une constante symbolique est reprsente par un nom. Toutefois, son contenu ne peut tre modi aprs son initialisation. Supposons que vous ayez dclar deux variables de type entier Eleves et Classes et que souhaitiez connatre le nombre dlves dune cole. Si vous savez que chaque classe est forme de 15 lves, le calcul se prsentera ainsi :
Eleves = Classes * 15;
Dans cet exemple, 15 est une constante littrale. Votre programme serait plus facile lire et mettre jour si cette valeur tait reprsente par une constante symbolique :
Eleves = Classes * elevesParClasse
Si vous dcidez plus tard de modier le nombre dlves par classe, il vous sufra de changer la valeur dinitialisation de la constante elevesParClasse : vous naurez pas besoin de modier toutes les instructions qui utilisent cette valeur. Il existe deux faons de dclarer une constante symbolique en C++. La plus ancienne, dsormais obsolte, consiste utiliser la directive #define du prprocesseur. La seconde, plus approprie, consiste utiliser le mot-cl const.
La constante elevesParClasse na pas de type particulier (int, char, etc.). En fait, le prprocesseur effectuera une simple substitution de texte : chaque fois quil rencontrera le mot elevesParClasse dans le texte du programme, il le remplacera 15. Le prprocesseur sexcutant avant le compilateur, ce dernier ne verra donc jamais votre constante, mais uniquement le nombre 15.
ntion Atte
Mme si #define semble trs simple utiliser, il est prfrable de lviter car la norme C++ la dclar obsolte.
Chapitre 3
Variables et constantes
59
Cet exemple cre galement la constante symbolique elevesParClasse mais, cette foisci, elle est de type entier court non sign. Grce cette mthode, les erreurs sont moins frquentes et le code peut tre mis jour facilement car le mot-cl const permet au compilateur de connatre le type de la constante et de vrier quelle est correctement utilise. Il est impossible de modier le contenu dune constante pendant lexcution dun programme. Pour affecter une autre valeur une constante, vous devez intervenir au niveau du chier source, puis le recompiler.
Ne pas faire
Utiliser des mots-cls comme noms de
Info
Faire
Vrier que la valeur affecte dun entier de
constantes.
Utiliser la directive #define du prproces-
constantes.
Constantes numres
Ces constantes permettent de crer de nouveaux types, puis de dnir des variables dont les valeurs seront limites un ensemble prcis. Vous pourriez par exemple crer une numration pour y stocker des couleurs. En ce cas, il faudrait dclarer lnumration COULEURS, qui serait associe aux cinq valeurs ROUGE, BLEU, VERT, BLANC et NOIR. La syntaxe consiste crire le mot-cl enum, suivi du nom du type, dune accolade ouvrante, des valeurs admises spares par des virgules, dune accolade fermante et dun point-virgule. Exemple :
enum COULEURS { ROUGE, BLEU, VERT, BLANC, NOIR };
Cette instruction effectue deux actions : 1. Elle cre le nom dnumration COULEURS, cest--dire un nouveau type.
60
Le langage C++
2. Elle cre des constantes symbolique comme ROUGE, BLEU, VERT, etc. en les initialisant respectivement avec les valeurs 0, 1, 2, etc. chaque constante correspond une valeur entire. En labsence dindication contraire, la premire constante sera initialise 0, la seconde 1, etc. Vous pouvez toutefois affecter des valeurs explicites ; celles qui nen nont pas recevront la valeur de la constante prcdente, plus un. Ici, par exemple :
enum Couleurs { ROUGE=100, BLEU, VERT=500, BLANC, NOIR=700 };
ROUGE ayant la valeur 100, BLEU sera associe la valeur 101 ; selon le mme principe, BLANC vaudra 501. Vous pouvez dnir des variables du type COULEURS, mais elles ne pourront recevoir que des valeurs de lnumration (ici, ROUGE, BLEU, VERT, BLANC ou NOIR). Il faut bien raliser que les variables numres sont gnralement de type unsigned int et que les constantes de lnumration sont, en fait, des valeurs entires. Il est toutefois trs pratique de pouvoir utiliser des noms vocateurs pour ces valeurs lorsque lon travaille sur des couleurs, des jours de la semaine, ou tout autre ensemble ni de valeurs. Le Listing 3.8 utilise une numration pour reprsenter des jours : Listing 3.8 : Exemple dnumration
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: #include <iostream> int main() { enum Jours { Dimanche, Lundi, Mardi, Mercredi, Jeudi, Vendredi, Samedi }; Jours aujourdhui; aujourdhui = Lundi; if (aujourdhui == Dimanche || aujourdhui == Samedi) std::cout << "\nJadore les week-ends!\n"; else std::cout << "\nAu boulot!\n"; return 0; }
Chapitre 3
Variables et constantes
61
Aux lignes 4 et 5, on dnit la constante numre Jours avec sept valeurs quivalant chacune un entier en partant de 0 ; la valeur de Lundi est donc 1 (Dimanche valait 0). La ligne 7 cre une variable de type Jours qui contiendra une valeur valide partir de la liste des constantes numres dnies aux lignes 4 et 5. La valeur Lundi est affecte cette variable en ligne 8 et le contenu de la variable est teste en ligne 10. La constante numre de la ligne 8 pourrait tre remplace par une srie de constantes entires, comme dans le Listing 3.9. Listing 3.9 : Le mme programme utilisant des constantes entires
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: #include <iostream> int main() { const int Dimanche = 0; const int Lundi = 1; const int Mardi = 2; const int Mercredi = 3; const int Jeudi = 4; const int Vendredi = 5; const int Samedi = 6; int aujourdhui; aujourdhui = Lundi; if (aujourdhui == Dimanche || aujourdhui == Samedi) std::cout << "\nJadore les week-ends!\n"; else std::cout << "\nAu boulot!\n"; return 0; }
ntion Atte
Plusieurs variables dclares dans ce programme ntant pas utilises, votre compilateur risque de vous en avertir au moment de la compilation.
La sortie produite est identique celle du Listing 3.8. Chacune des constantes (Dimanche, Lundi, etc.) a t ici dnie explicitement et il nexiste pas de type numr Jours.
62
Le langage C++
Les constantes numres prsentent lavantage dtre parfaitement claires, lutilit du type numr Jours est vidente.
Questions-rponses
Q Si un entier court peut tre remis zro en cas de dpassement de capacit, pourquoi ne pas toujours utiliser des entiers longs ? R Tous les types dentiers pourront renvoyer des valeurs errones en cas de dpassement de capacit. Toutefois, les valeurs maximales acceptes seront de 65 535 pour un unsigned short int de deux octets, et de 4 294 967 295 pour un unsigned long int de quatre octets. Cependant, sur la plupart des machines, un entier long occupera davantage de mmoire (4 octets contre 2) et, dans ce cas, un programme contenant 100 variables de ce type occupera donc 200 octets de mmoire vive supplmentaires. Dsormais ce problme nest plus crucial, car les PC disposent maintenant de quantits de mmoire considrables. Utiliser des types suprieurs vos besoins peut galement demander plus de traitement de la part du processeur. Q Que se passe-t-il si jaffecte un nombre virgule un entier alors que jaurai d utiliser un float ? Par exemple :
int unNombre = 5.4;
R Dans ce cas, un compilateur efcace enverra un avertissement, mais cette affectation est autorise. Le nombre affect sera tronqu : la valeur 5,4 deviendra la valeur entire 5, ce qui entranera une perte dinformation. Q Pourquoi doit-on utiliser des constantes symboliques et ne pas se contenter des constantes littrale ? R Si vous utilisez la mme valeur en de nombreux endroits de votre programme, vous pourrez la modier en une seule fois si elle est dsigne par une constante symbolique. Par ailleurs une constante symbolique, de par son nom, est plus explicite. Il peut tre difcile de comprendre pourquoi un nombre est multipli par la constante 60, alors que cela deviendra vident sil sagit de la constante secParMinute. Q Que se passe-t-il si jaffecte un nombre ngatif une variable non signe ? Par exemple :
unsigned int unNombrePositif = -1;
Chapitre 3
Variables et constantes
63
R Un bon compilateur renverra un avertissement, mais cette affectation est autorise. La valeur sera alors considre comme une suite de bit reprsentant un nombre non sign et sera affecte la variable. Par exemple -1 dont la reprsentation binaire est 1111111111111111 (0xFF en code hexadcimal) sera interprt comme la valeur 65 535 non signe. Q Est-il possible de travailler en C++ sans comprendre les structures binaires, larithmtique binaire et le systme hexadcimal ? R Oui, mais moins efcacement que si vous matrisez ces notions. Le langage C++ ne masque pas autant que dautres langages les dtails internes de lordinateur. Ceci peut tre un avantage puisque vous disposez ainsi dune bien plus grande marge de manuvre, mais cela peut aussi entraner des rsultats non souhaits en cas derreur de programmation. Bien comprendre ces concepts permet den exploiter pleinement les possibilits. Les programmeurs C++ qui ne matrisent pas les bases du systme binaire sont souvent dconcerts par certains rsultats.
7. Parmi ces noms de variables, quels sont ceux qui sont bons et mauvais, quels sont ceux qui ne sont pas corrects ? a. Age b. !ex c. R79J d. revenuTotal e. __Invalide
64
Le langage C++
Exercices
1. Quel doivent tre les types des variables destines recevoir les informations suivantes ? a. Votre ge b. La surface de votre jardin c. Le nombre dtoiles de la galaxie d. La pluviomtrie moyenne de janvier 2. Proposez des noms de variables vocateurs pour ces informations. 3. Dclarez une constante pour pi = 3.14159. 4. Dclarez une variable de type float, puis initialisez-la avec la constante pi.
4
Expressions et instructions
Au sommaire de ce chapitre
Les instructions Les blocs dinstructions Les expressions Les branchements conditionnels Les conditions logiques
Un programme est, la base, un ensemble de commandes qui sexcutent squentiellement. Il est possible de modier cette squence et dexcuter un jeu de commandes ou un autre selon quune condition particulire est ou nest pas satisfaite.
66
Le langage C++
Instructions
En C++, une instruction a pour rle de contrler la squence dexcution, dvaluer une expression ou de ne rien faire (instruction nulle). Toutes les instructions C++, y compris linstruction nulle, se terminent par un point-virgule. Lune des instructions les plus courantes, laffectation, ressemble ceci :
x = a+b;
la diffrence de lalgbre, cette instruction ne signie pas que x est gal a+b, mais quil faut "affecter la valeur de la somme a plus b x". Cette instruction effectue deux oprations : elle ajoute a et b, puis affecte le rsultat x laide de loprateur daffectation (=). Bien quelle ralise deux oprations, il ne sagit que dune seule instruction, qui se termine donc par un point-virgule. Loprateur daffectation attribue la valeur droite du signe gal la variable situe sa gauche.
Info
Espace ou blanc
Un espace correspond un caractre invisible (tabulation, espace, saut de ligne). On les appelle "caractres blancs", car ils sont invisibles limpression. Gnralement, les instructions ignorent les espaces. Linstruction daffectation ci-dessus, par exemple, pourrait scrire ainsi :
x=a+b;
ou ainsi :
x + =a b ;
Bien que correcte, cette dernire prsentation nest pas trs convaincante. Les espaces sont destins rendre le programme plus lisible, non lparpiller dans le listing. Dans tous les cas, le C++ propose, le programmeur dispose !
Chapitre 4
Expressions et instructions
67
une accolade fermante. Bien que chaque instruction du bloc doive se terminer par un point-virgule, le bloc lui-mme se termine simplement par laccolade fermante. Exemple :
{ temp = a; a = b; b = temp; }
Ne pas faire
Oublier dinsrer une accolade fermante
virgule.
Utiliser judicieusement des espaces pour
Expressions
En C++, tout ce qui correspond une valeur est une expression. On dit quune expression renvoie une valeur : 3+2, par exemple, est une expression qui renvoie la valeur 5. Toutes les expressions sont des instructions. Les expressions sont nombreuses et de formats diffrents. Voici quelques exemples :
3.2 PI SecondesParMinute // renvoie la valeur 3,2 // constante "float" renvoyant la valeur 3,14 // constante entire renvoyant 60
En admettant que PI et SecondesParMinute soient des constantes initialises respectivement 3,14 et 60, ces trois instructions sont des expressions. Voici une expression un peu plus complexe :
x = a+b;
Elle additionne a et b, affecte le rsultat x, mais renvoie aussi la valeur qui vient dtre affecte (celle de x) : cette instruction daffectation est donc une expression. Nimporte quelle expression peut apparatre droite dun oprateur daffectation, ce qui inclut donc linstruction daffectation que nous venons de voir. On peut donc galement crire :
y = x = a+b;
68
Le langage C++
Cette ligne est value dans lordre suivant : 1. Ajouter a b. 2. Affecter le rsultat de lexpression a+b x. 3. Affecter le rsultat de lexpression x = a+b y. Si a, b, x et y sont des entiers et que a et b valent respectivement 9 et 7, x et y recevront toutes les deux la valeur 16, comme le montre le Listing 4.1. Listing 4.1 : valuation dexpressions
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: } int a=0, b=0, x=0, y=35; cout << "a: " << a << " b: " << b; cout << " x: " << x << " y: " << y << endl; a = 9; b = 7; y = x = a+b; cout << "a: " << a << " b: " << b; cout << " x: " << x << " y: " << y << endl; return 0; #include <iostream> int main() { using std::cout; using std::endl;
La ligne 7 dclare et initialise quatre variables dont les valeurs sont afches aux lignes 8 et 9. La ligne 10 affecte la valeur 9 a, la ligne 11, la valeur 7 b. La ligne 12 additionne a et b et affecte le rsultat x. En consquence, lexpression (x = a+b) est value pour produire une valeur (la somme de a et b), qui est son tour affecte y. Aux lignes 13 et 14, ces rsultats sont conrms par lafchage des valeurs des quatre variables.
Chapitre 4
Expressions et instructions
69
Oprateurs
Un oprateur est un symbole qui demande au compilateur deffectuer une action. Les oprateurs agissent sur les oprandes qui sont des expressions en langage C++. Il existe plusieurs catgories doprateurs ; les deux premires que nous tudierons ici sont :
Oprateurs daffectation
Vous avez dj vu loprateur daffectation (=). Il transfre la valeur situe droite de loprateur dans loprande de gauche. Lexpression suivante :
x = a+b;
lvalues et rvalues Tout oprande situ gauche de loprateur daffectation est appel lvalue (left value), alors que loprande situ droite sappelle rvalue (right value). Toutes les lvalues sont des rvalues, mais linverse nest pas toujours vrai. Une rvalue littrale, par exemple, nest pas un lvalue. Vous pouvez donc crire :
x = 5;
mais pas :
5 = x; x peut tre une lvalue ou une rvalue, alors que 5 ne peut tre quune rvalue.
Info
Les constantes sont des rvalues : leurs valeurs ne pouvant tre modies, elles ne peuvent pas se trouver du ct gauche de loprateur daffectation, ce qui signie quelles ne peuvent pas tre des lvalues.
Oprateurs mathmatiques
Lautre catgorie doprateurs regroupe les oprateurs mathmatiques. Il existe cinq oprateurs mathmatiques : addition (+), soustraction (-), multiplication (*), division (/) et modulo (%).
70
Le langage C++
Les oprateurs + et fonctionnent comme lon sy attend. La multiplication utilise le signe astrisque (*) et la division la barre oblique. Les exemples suivants illustrent chacun de ces oprateurs. Dans chaque cas, le rsultat est affect la variable resultat. Les commentaires droite montrent la valeur attendue.
resultat resultat resultat resultat = = = = 56 12 21 12 + / * 32 10 7 4 // // // // resultat resultat resultat resultat = = = = 88 2 3 48
Problmes de soustraction
La soustraction dentiers non signs (unsigned) peut conduire des rsultats tonnants lorsque le rsultat est un nombre ngatif. Dans le chapitre prcdent, nous avons abord le dpassement de capacit dune variable ngative. Dans le Listing 4.2, nous allons soustraire dun nombre non sign un autre nombre non sign de valeur suprieure. Listing 4.2 : Soustraction dentiers et dpassement de capacit
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: } difference = petitNombre - grandNombre; cout << "\nMaintenant la difference est: " << difference <<endl; return 0; difference = grandNombre - petitNombre; cout << "La difference est: " << difference; unsigned int difference; unsigned int grandNombre = 100; unsigned int petitNombre = 50; int main() { using std::cout; using std::endl; // Listing4.2 - soustraction et // dpassement de capacit #include <iostream>
Chapitre 4
Expressions et instructions
71
Le rsultat de la soustraction effectue la ligne 14 safche pour la premire fois la ligne 15. Loprateur est appel de nouveau la ligne 17 mais, comme on a retranch un grand nombre dun nombre plus petit, le rsultat est ngatif. Comme ce rsultat est valu (et afch) comme un nombre non sign, il y a dpassement de capacit, comme on la expliqu au Chapitre 3. Pour plus de dtails, consultez lAnnexe C.
Si je divise 5 par 3 jobtiens 1. Quest-ce qui ne va pas ? Si vous divisez un entier par un autre, vous obtenez un entier comme rsultat. Par consquent 5/3 donne 1 (en ralit la rponse est 1 avec un reste de 2, pour obtenir le reste, faites 5%3 et vous obtiendrez 2). Pour obtenir une valeur fractionnaire, utilisez des chiffres virgule ottante (types float,
72
Le langage C++
Les deux premires lignes crent la variable age ainsi quune variable temporaire. Comme on le voit la troisime ligne, la valeur age se voit ajouter 2 et le rsultat est affect temp. La ligne suivante, cette valeur est replace dans age, an de la mettre jour. Cette mthode est terriblement complique et un peu difcile grer. En C++, vous pouvez utiliser la mme variable des deux cts de loprateur daffectation, ce qui permet dcrire :
age = age+2;
Cette expression est bien plus claire, mme si elle nest pas conforme la notation algbrique. Le C++ linterprte comme : "ajouter 2 la variable age, puis affecter le rsultat cette mme variable". Il est galement possible de faire plus court :
age += 2;
Le rsultat est identique, mais peut-tre un peu moins lisible. Cette ligne utilise loprateur daddition combin laffectation (+=) qui ajoute la valeur droite la valeur gauche puis affecte nouveau le rsultat la valeur gauche. Si age valait 24 au dpart, il vaudra donc 26 aprs cette instruction. Il existe dautres oprateurs combins laffectation pour la soustraction (-=), la division (/=), la multiplication (*=) et le modulo (%=).
Incrmentation et dcrmentation
La valeur que lon ajoute (ou soustrait) le plus souvent est 1. En C++, augmenter une valeur de 1 est appel incrmentation, diminuer une valeur de 1 est appel dcrmentation. Pour effectuer ces actions, le langage C++ propose deux oprateurs spciaux.
Chapitre 4
Expressions et instructions
73
Loprateur dincrmentation (++) augmente de 1 la valeur dune variable, tandis que loprateur de dcrmentation (--) la diminue de 1. Dans lexemple ci-dessous, nous allons incrmenter la variable compteur :
compteur++; // compteur est gal compteur plus 1.
ou encore :
compteur += 1;
Info
Comme vous lavez peut-tre devin, le nom C++ provient de lapplication de loprateur dincrmentation au nom de son prdcesseur, le langage C. Lide est que le C++ est une version suprieure de C.
Prxe et sufxe
Les oprateurs dincrmentation et de dcrmentation existent en deux versions : prxe et sufxe. Un prxe prcde le nom de la variable (++age), alors que le sufxe le suit (age++). Dans une instruction simple, vous pouvez utiliser lun ou lautre, mais dans une instruction complexe o vous incrmentez (ou dcrmentez) une variable puis affectez le rsultat une autre variable, la nuance est importante. Le principe du prxe est que lon effectue lincrmentation avant de renvoyer la valeur alors que, dans le cas du sufxe, on incrmente aprs avoir renvoy la valeur. Un exemple permettra dclaircir ce concept. Supposons que x soit un entier dont la valeur est 5 et que vous utilisiez un oprateur dincrmentation prxe. Si vous crivez :
int a = ++x;
le compilateur va incrmenter x (qui prendra la valeur 6) avant den affecter la valeur a. Ces deux variables auront donc la mme valeur 6. Si vous utilisez ensuite loprateur sufxe pour crire :
int b = x++;
le compilateur va attribuer la valeur de x (6) b, puis incrmenter x qui prendra la valeur 7. b vaut donc 6 et x vaut 7. Le Listing 4.3 est un exemple dutilisation des prxes et des sufxes.
74
Le langage C++
Les lignes 9 et 10 dclarent deux variables entires initialises 39. Leurs valeurs safchent aux lignes suivantes. Aux lignes 13 et 14, monAge et tonAge sont incrmentes respectivement laide dun oprateur sufxe et dun prxe. Le rsultat est gal 40 (lignes 16 et 17). la ligne 19, monAge est incrment dans linstruction dafchage, laide dun oprateur sufxe ; lincrmentation seffectue donc aprs lafchage qui produit nouveau 40, puis
Chapitre 4
Expressions et instructions
75
la variable monAge est incrmente. la ligne 20, tonAge est incrment par prxe, donc avant lafchage qui produit 41. Aux lignes 22 et 23, le rsultat est rafch. Les deux valeurs sont gales puisque linstruction dincrmentation a t excute.
quelle sera lopration effectue en premier, laddition ou la multiplication ? Si cest laddition, le rsultat sera 8 * 8, soit 64. Si cest la multiplication, le rsultat sera 5 + 24, soit 29. La norme C++ ne laisse pas de place au hasard dans lapplication des oprations. Tout oprateur a un niveau de priorit par rapport aux autres (voir lAnnexe C). La multiplication ayant une priorit suprieure laddition, cest elle qui sera excute en premier et la valeur de lexpression sera donc gale 29. Lorsque deux oprateurs sont de priorit identique, les oprations seffectuent de gauche droite. Dans lexpression suivante :
x = 5+3+8 * 9+6 * 4;
Laddition seffectue ensuite de gauche droite : 5 + 3 = 8 ; 8 + 72 = 80 ; 80 + 24 = 104. Attention : certains oprateurs (laffectation par exemple) sont valus de droite gauche ! Que faire si lordre des priorits ne vous convient pas ? Prenons un exemple :
totalSecondes = nbMinutesA+nbMinutesB * 60;
Dans cette expression, nbMinutesB sera multipli par 60, puis ajout nbMinutesA. Or, on souhaite en fait ajouter les deux variables pour obtenir le nombre de minutes, puis multiplier ce rsultat par 60 pour obtenir le nombre de secondes. Les parenthses permettent de dnir lordre des priorits de faon adquate. Lexemple prcdent devrait donc scrire de la faon suivante :
totalSecondes = (nbMinutesA+nbMinutesB) * 60;
76
Le langage C++
Parenthses imbriques
Dans les expressions complexes, vous pouvez imbriquer des parenthses. Par exemple, pour dterminer le nombre total de secondes puis calculer le nombre total de personnes concernes avant de multiplier le nombre de secondes par le nombre de personnes, on crira lexpression suivante :
totalPersonSecondes = ( ( (nbMinutesA + nbMinutesB) * 60) * (personTravail+personVacances) );
Cette expression complexe se lit de lintrieur vers lextrieur. Elle ajoute dabord nbMinutesA nbMinutesB, puis multiplie le rsultat par 60. Ensuite, elle additionne personTravail et personVacances et multiplie cette somme par le nombre de secondes. Cet exemple met en vidence un problme important : cette expression est facile comprendre pour un ordinateur mais trs difcile lire, modier et comprendre pour un humain. On pourrait la dcomposer en utilisant des variables entires temporaires : ,
totalMinutes = nbMinutesA+nbMinutesB; totalSecondes = totalMinutes * 60; totalPersonnel = personTravail+personVacances; totalPersonSecondes = totalPersonnel * totalSecondes;
Cet exemple est plus facile comprendre, mme sil est plus long crire et quil utilise plus de variables temporaires que le prcdent. Si vous prfrez ce type de programmation, noubliez pas dinsrer des commentaires et de remplacer 60 par une constante symbolique : vous aurez alors un code qui sera plus facile comprendre et maintenir.
Faire
Se souvenir que les expressions ont une
Ne pas faire
Avoir recours des imbrications trop profon-
valeur.
Pour incrmenter ou dcrmenter une varia-
ble avant son utilisation dans lexpression, utiliser loprateur prxe (++variable).
Pour incrmenter ou dcrmenter une varia-
ble aprs son utilisation dans lexpression, utiliser loprateur sufxe (variable++).
Modier les priorits de calcul laide de
parenthses.
Chapitre 4
Expressions et instructions
77
Vrai ou faux
Toute expression peut tre value en terme de vrai ou faux. Une expression qui donne un rsultat mathmatique de zro renverra false, et true dans tous les autres cas. , Dans les versions prcdentes de C++, les notions de vrai ou faux taient reprsentes par des entiers, mais la norme ANSI a introduit le type bool. Une variable de ce type peut avoir deux valeurs : true (vrai) ou false (faux). De nombreux compilateurs proposaient dj un type bool, qui tait reprsent en interne par un entier long et occupait donc 4 octets. Aujourdhui, les compilateurs compatibles ANSI proposent souvent un type bool qui noccupe quun seul octet.
Info
Oprateurs relationnels
Les oprateurs relationnels permettent dvaluer lgalit ou la diffrence entre deux nombres. Une instruction relationnelle produit toujours un rsultat true ou false. Les diffrents oprateurs disponibles sont prsents dans le Tableau 4.1. Tous les oprateurs relationnels renvoient une valeur de type bool : soit true, soit false. Dans les prcdentes versions du C++, ces oprateurs renvoyaient 0 (pour false) ou une valeur non nulle (gnralement 1) pour true.
Info
Si la variable entire monAge est gale 45 et que la variable entire tonAge est gale 50, vous pouvez tester si elles sont gales laide de loprateur dgalit (==) :
// La valeur de monAge est-elle gale celle de tonAge? monAge == tonAge;
La valeur renvoye est false puisque les variables sont diffrentes. Vous pouvez vrier que monAge est infrieure tonAge grce lexpression :
// La valeur de moAge est-elle infrieure celle de tonAge? monAge < tonAge;
Certains programmeurs dbutants confondent loprateur daffectation (=) avec loprateur dgalit (==). Il en rsulte des bogues dans les programmes.
78
Le langage C++
Il existe six oprateurs relationnels : gal (==), infrieur (<), suprieur (>), infrieur ou gal (<=), suprieur ou gal (>=) et diffrent de (!=). Le Tableau 4.1 dresse la liste des oprateurs relationnels.
Tableau 4.1 : Oprateurs relationnels
Nom
Egal Diffrent de Suprieur Suprieur ou gal Infrieur Infrieur ou gal
Exemple
Rsultat
faux vrai vrai faux vrai faux vrai vrai faux faux faux vrai
100 == 50;
50 == 50; 100!= 50; 50!= 50; 100 > 50; 50 > 50; 100 >= 50; 50 >= 50; 100 < 50; 50 < 50; 100 <= 50; 50 <= 50;
Faire
Se rappeler que les oprateurs relationnels
Ne pas faire
Confondre loprateur daffectation (=) avec
loprateur gal (==). Cest lune des erreurs les plus frquentes en C++.
Linstruction if
Cette instruction permet de tester une condition (si deux variables sont gales, par exemple) et de se brancher sur un bloc dinstructions ou sur un autre en fonction du rsultat obtenu. La forme la plus simple de linstruction if est :
if (expression) instruction;
Chapitre 4
Expressions et instructions
79
Lexpression entre parenthses peut contenir nimporte quelle expression, mais le plus souvent il sagit dune expression relationnelle. Si le rsultat est faux, linstruction est ignore ; sil est vrai, linstruction est excute. Voici un exemple :
if (grandNombre > petitNombre) grandNombre = petitNombre;
Ce code compare grandNombre et petitNombre. Si grandNombre est suprieur petitNombre, la ligne suivante affecte la valeur de petitNombre grandNombre. Dans le cas contraire, la seconde ligne est ignore. Un bloc dinstruction entour par des accolades tant quivalent une instruction simple, le branchement peut effectuer plusieurs instructions :
if (expression) { instruction1; instruction2; instruction3; }
Exemple :
if (grandNombre > petitNombre) { grandNombre = petitNombre; std::cout << "grandNombre : " << grandNombre << "\n"; std::cout << "petitNombre : " << petitNombre << "\n"; }
Cette fois, si grandNombre est suprieur petitNombre, la valeur de petitNombre sera affecte grandNombre et les valeurs de ces deux variables seront afches. Le Listing 4.4 est un exemple plus complet dutilisation des oprateurs relationnels pour effectuer des branchements. Listing 4.4 : Branchements conditionnels
1: 2: 3: 4: 5: 6: 7: 8: 9: // Branchements conditionnels laide // doprateurs relationnels #include <iostream> int main() { using std::cout; using std::cin; int OMScore, PSGScore;
80
Le langage C++
10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44:
cout << "Score de lOM: "; cin >> OMScore; cout << "\nScore du PSG: "; cin >> PSGScore; cout << "\n"; if (OMScore > PSGScore) cout << "Allez lOM!\n"; if (OMScore < PSGScore) { cout << "Vive le PSG!\n"; } if (OMScore == PSGScore) { cout << "Egalite? Oh non!\n"; cout << "Donne-moi le score du PSG: "; cin >> PSGScore; if (OMScore > PSGScore) cout << "Je le savais, allez lOM!"; if (PSGScore > OMScore) cout << "Je le savais, vive le PSG!"; if (PSGScore == OMScore) cout << "Match nul!"; } cout << "\nOK.\n"; return 0; }
Ce programme demande les scores de deux quipes de football. Les variables sont compares aux lignes 18, 21 et 26.
Chapitre 4
Expressions et instructions
81
Si lun des scores est suprieur lautre, un message apparat. Si les scores sont gaux, le bloc de code de la ligne 26 la ligne 40 sexcute. Le second score est demand nouveau, puis les scores sont compars. Notez que si le score initial du PSG est suprieur celui de lOM, la condition de linstruction if (ligne 18) donne false et la ligne 19 nest pas excute. Si la ligne 21 renvoie une valeur true, linstruction de la ligne 23 sexcute. Le test de la ligne 26 sexcute et si le rsultat est faux, le programme passe directement la ligne 41, sautant tout le bloc dinstructions. Cet exemple montre quun rsultat vrai un test if nempche pas lvaluation des autres instructions if. Notez que laction des deux premires instructions if tient sur une ligne (afchage de "Allez lOM" ou "Vive le PSG"). Dans le premier exemple (ligne 19) nous navons pas utilis daccolades car elles ne sont pas obligatoires pour un bloc dune seule instruction, mais elles peuvent tre ajoutes comme aux lignes 22 24.
viter les erreurs classiques avec les instructions if De nombreux programmeurs C++ dbutants ajoutent par inadvertance un point-virgule aprs les instructions if: ,
Le but ici tait de tester si uneValeur tait infrieure 10 et, dans lafrmative, de lui affecter la valeur 10. Si vous excutez le code tel quil est crit ci-dessus, vous constaterez que uneValeur est toujours mis 10 ! Pourquoi ? Linstruction if se termine par un pointvirgule ce qui correspond un oprateur vide (qui ne fait rien). Lindentation nayant aucun sens pour le compilateur, le code prcdent revient crire :
82
Le langage C++
Lindentation
Le Listing 4.4 prsente un des styles dindentation pour les instructions if. Le choix du meilleur style pour lalignement des accolades est un sujet qui dclenche toujours les passions parmi les programmeurs. Il en existe des douzaines, mais vous rencontrerez le plus souvent les trois suivants :
Laccolade douverture est place la suite de la condition et laccolade de fermeture est aligne sur le if.
if (expression) { instructions }
Les deux accolades sont alignes sous le if et les instructions sont dcales.
if (expression) { instructions }
Cet ouvrage utilise le deuxime style, car les blocs apparaissent plus clairement lorsque leurs accolades sont alignes avec la condition. Cela dit, vous tes tout fait libre dadopter la mthode de votre choix du moment que vous restez cohrent.
Linstruction else
Un programme doit souvent effectuer un branchement lorsquune condition est vraie et un autre si elle est fausse. Cela vite les redondances dans les chiers sources (voir le Listing 4.5). Le mot-cl else permet deffectuer ces branchements de faon claire :
if (expression) instruction; else instruction;
Chapitre 4
Expressions et instructions
83
La ligne 14 value la condition de linstruction if. Si elle est vraie, la ligne 15 sexcute et le programme avance jusqu la ligne 18 (aprs linstruction else). Si la condition de la ligne 14 renvoie false, le ux de contrle passe la clause else et cest la ligne 17 qui sexcute. Si la clause else tait supprime, la ligne 17 sexcuterait chaque fois, quel que soit le rsultat de la condition de linstruction if. Les deux instructions du if et du else peuvent tre remplaces par des blocs entre accolades.
84
Le langage C++
Si expression est vraie, linstruction sexcute et le programme continue jusqu linstruction_suivante. Si expression est fausse, lexcution passe directement linstruction_suivante. Linstruction peut tre une expression simple ou un bloc dinstructions entre accolades. Forme 2
< 10) << "val est inferieur 10"); << "val nest pas inferieur 10!"); "Termine." << endl;
Instructions if imbriques
Une instruction if...else peut contenir une autre instruction if...else. Cette pratique sappelle limbrication de conditions :
if (expression1) { if (expression2) instruction1; else { if (expression3) instruction2; else instruction3; } } else instruction4;
Les choses se compliquent ! Si expression1 est vraie et expression2 est vraie, linstruction1 sexcute. Si expression1 est vraie alors que expression2 est fausse, et que expression3 est vraie, linstruction2 sexcute. Si expression1 est vraie, et que
Chapitre 4
Expressions et instructions
85
les expressions2 et 3 sont fausses, linstruction3 sexcute. Enn, si expression1 est fausse, linstruction4 sexcute. Comme vous pouvez le constater, les instructions if imbriques peuvent devenir assez confuses ! Le Listing 4.6 montre un exemple dutilisation de cette structure. Listing 4.6 : Conditions imbriques
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: // Listing4.6 - instructions if imbriques // #include <iostream> int main() { // Demander deux nombres // Les affecter nb1 et nb2 // Si nb1 est suprieur ou gal nb2, // vrifier sils sont divisibles // Si oui, vrifier sils sont identiques using namespace std; int nb1, nb2; cout << "Entrez deux nombres.\nPremier: "; cin >> nb1; cout << "\nSecond: "; cin >> nb2; cout << "\n\n"; if (nb1 >= nb2) { if ( (nb1% nb2) == 0) // sont-ils divisibles? { if (nb1 == nb2) cout << "Ils sont identiques!\n"; else cout << "Ils sont divisibles!\n"; } else cout << "Ils ne sont pas divisibles!\n"; } else cout << "Le second nombre est suprieur!\n"; return 0; }
86
Le langage C++
Les deux nombres sont saisis et compars. Linstruction if de la ligne 21 vrie que le premier est suprieur ou gal au second. Si ce nest pas le cas, la clause else de la ligne 33 sexcute. Si le premier if est vrai, la ligne 22 sexcute et le second if est test. Il vrie que la division ne produit pas de reste, ce qui signie que les nombres sont divisibles ou gaux. Linstruction if de la ligne 25 vrie sils sont gaux et afche un message appropri . Si linstruction if de la ligne 23 choue, cest la clause else de la ligne 30 qui sexcute.
Les indentations et les espaces sont pratiques pour le programmeur, mais ne changent rien pour le compilateur. Il est trs facile de se tromper et daffecter une clause else au mauvais if, comme le montre le Listing 4.7 Listing 4.7 : Intrt des accolades pour clarier les instructions conditionnelles imbriques
1: 2: 3: // Importance des accolades // dans des instructions if imbriques #include <iostream>
Chapitre 4
Expressions et instructions
87
int main() { int x; std::cout << "Entrez un nombre infrieur 10 ou suprieur 100: "; std::cin >> x; std::cout << "\n"; if (x >= 10) if (x > 100) std::cout << "Suprieur 100, Merci!\n"; else // pas la directive else prvue! std::cout << "Infrieur 10, Merci!\n"; return 0; }
Lintention du programmeur, ici, tait de vrier que le nombre saisi tait infrieur 10 ou suprieur 100 et denvoyer un message de remerciement. Lorsque la condition de linstruction if de la ligne 11 est vraie, la ligne 12 sexcute. Ici, la ligne 12 sexcute si le nombre entr est suprieur ou gal 10. La ligne 12 contient aussi une instruction if dont la condition est vraie si le nombre entr est suprieur 100. Dans ce cas, linstruction de la ligne 13 sexcute et afche le message qui convient. Si le nombre entr est infrieur 10, la condition de la ligne 11 renvoie false. Le programme se poursuit alors la ligne suivant cette instruction if (ici, la ligne 16). Si vous entrez un nombre infrieur 10, vous obtiendrez donc :
Entrez un nombre inferieur a 10 ou superieur a 100: 9
Comme vous le constatez, aucun message ne safche. La clause else de la ligne 14 tait cense correspondre linstruction if de la ligne 11, comme lindiquait son indentation. Malheureusement, elle est en fait associe la clause if de la ligne 12, ce qui entrane le problme que nous venons de constater. Ce bogue est subtil car le compilateur ne le signale pas ! Cest un programme syntaxiquement correct, mais qui ne fait tout simplement pas ce que lon attend de lui. En outre, il fonctionne correctement la plupart du temps, tant que lon entre un nombre suprieur 100. Cependant, avec un nombre compris entre 11 et 99, vous constaterez quil y a un problme vident ! Le Listing 4.8 corrige ce problme en utilisant des accolades.
88
Le langage C++
Les accolades des lignes 12 et 15 regroupent ce quelles contiennent en une seule instruction. La ligne 16 correspond maintenant linstruction if de la ligne 11. Vous pouvez viter bon nombre de problmes lis aux instructions ifelse en plaant toujours des accolades pour les instructions des clauses if et else, mme lorsque la condition nest suivie que dune seule instruction :
if (valeur < 10) { valeur = 10; } else { valeur = 25; };
ce Astu
Chapitre 4
Expressions et instructions
89
Oprateurs logiques
On veut parfois tester plusieurs conditions la fois, par exemple pour savoir si "x est suprieur y et si y est suprieur z". Le programme doit dterminer si ces deux conditions sont vries ou non an dagir en consquence. Imaginons un systme dalarme volu. Si lalarme de la porte sonne ET sil est plus de 18 h ET si nous ne sommes PAS en vacances OU si cest un week-end, appeler la police. Pour ce type dvaluation, il faut utiliser les trois oprateurs logiques de C++, qui sont prsents dans le Tableau 4.2.
Tableau 4.2 : Oprateurs logiques
ET logique
Il permet dvaluer deux expressions. Pour renvoyer un rsultat vrai, les deux expressions doivent tre toutes les deux vraies. Sil est vrai que vous avez faim ET que vous ayez de largent, ALORS vous pouvez acheter une pizza. Par consquent,
if ( (x == 5) && (y == 5) )
sera vrie si x et y sont tous les deux gaux 5. Notez que loprateur logique ET est symbolis par deux symboles &&, ce qui est diffrent de loprateur binaire & prsent au Chapitre 21.
OU logique
Il permet dvaluer deux expressions. Si lune ou lautre est vraie, le rsultat est vrai. Si vous avez de largent liquide OU une carte de crdit (ou les deux), ALORS vous pouvez payer laddition. Par consquent,
if ( (x == 5) || (y == 5) )
sera vrie si x ou y est gal 5, ou si tous les deux sont gaux 5. Loprateur logique OU est symbolis par deux barres verticales (||). La barre verticale simple symbolise un oprateur diffrent qui sera tudi au Chapitre 21.
90
Le langage C++
NON logique
Il donne un rsultat vrai si lexpression teste est fausse. Par exemple, lexpression :
if (!(x == 5) )
valuation en court-circuit
Lors de lvaluation dune instruction ET comme :
if ( (x == 5) && (y == 5) )
le compilateur teste dabord la vracit de la premire instruction (x==5) ; en cas dchec (si x nest pas gal 5), la seconde instruction (y == 5) ne sera pas teste puisque la clause ET requiert que les deux conditions soient vraies. De mme, dans le cas dune clause OR comme :
if ( (x == 5) || (y == 5) )
si la premire instruction donne vrai (x == 5), la seconde ne sera pas excute (y == 5) puisque lune ou lautre des conditions est sufsante. Bien que cela puisse ne pas sembler important, tudiez lexemple suivant :
if ( (x == 5 )|| (++y == 3) )
Si x nest pas gal 5, (++y == 3) ne sera pas valu. Si vous comptiez sur lincrmentation de y, celle-ci risque de ne pas toujours tre excute.
On peut supposer que le programmeur souhaite que lexpression soit vraie si x et y sont suprieurs 5 ou si z est suprieur 5. Cela peut aussi signier que lexpression nest vraie que si x est suprieur 5, et si soit y soit z est suprieur 5.
Chapitre 4
Expressions et instructions
91
Si x est gal 3 et que y et z valent tous les deux 10, la premire interprtation sera vraie (z est suprieur 5, on ignore donc x et y), mais la seconde sera fausse (il nest pas vrai que x soit suprieur 5 et peu importe ce qui se trouve du ct droit du symbole && puisque les deux cts doivent tre vrais). Les parenthses permettent donc daffecter explicitement des priorits et rendent ainsi lexpression plus lisible :
if ( (x > 5) && (y > 5 || z > 5) )
Cette instruction donne un rsultat faux, car x nest pas suprieur 5. La partie gauche de lexpression AND tant fausse, toute lexpression est fausse. Pour clarier vos intentions, nhsitez pas insrer des parenthses. Elles prcisent ce que vous voulez faire et vitent les erreurs nes dune mauvaise comprhension de lordre des oprateurs.
Info
Vrai ou faux
En C++, la valeur zro quivaut false alors que les autres valeurs sont considres comme true. Une expression ayant toujours une valeur, de nombreux programmeurs C++ exploitent cette fonctionnalits dans leurs programmes. Une instruction comme :
if (x) x = 0; // si x est vrai (non zro)
peut ainsi se lire "si x est diffrent de zro, x reoit la valeur 0". Ce qui revient crire :
if (x!= 0) x = 0; // si x est diffrent de zro
Ces deux instructions sont correctes, mais la seconde est plus lisible. Il est donc conseill de rserver la premire forme aux vritables tests logiques plutt que de sen servir pour tester si une valeur est nulle. Ces deux instructions sont galement quivalentes :
if (!x) if (x == 0) // si x est faux (zro) // si x gale zro
La seconde instruction est plus explicite si vous testez la valeur mathmatique de x plutt que son tat logique.
92
Le langage C++
Faire
Mettre des parenthses dans vos tests logi-
Ne pas faire
Utiliser if
ques pour les rendre plus clairs et dnir explicitement les priorits.
Utiliser des accolades pour clarier les
(x) comme synonyme de if (x!= 0);. Utiliser if (!x) comme synonyme de if (x == 0);.
instructions if imbriques.
Cette ligne signie : "Si expression1 est vraie, renvoyer la valeur de lexpression2 ; sinon, renvoyer la valeur de lexpression3". Le rsultat de cet oprateur est gnralement affect une variable. Le Listing 4.9 montre comment lutiliser la place dune instruction if. Listing 4.9 : Exemple doprateur conditionnel
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: // Listing4.9 // #include <iostream> int main() { using namespace std; int x, y, z; cout << "Entrez deux nombres.\n"; cout << "Premier: "; cin >> x; cout << "\nSecond: "; cin >> y; cout << "\n"; if (x > y) z = x; else z = y;
Chapitre 4
Expressions et instructions
93
cout << "Aprs le test if, z: " << z; cout << "\n"; z = (x > y)? x: y;
cout << "Aprs le test conditionnel, z: " << z; cout << "\n"; return 0; }
Trois variables entires sont cres : x, y et z. Les deux premires sont saisies par lutilisateur. Linstruction if de la ligne 16 dtermine la plus grande et laffecte z. Le rsultat safche la ligne 21. Loprateur conditionnel de la ligne 24 ralise le mme test et affecte la valeur la plus leve z. Si x est suprieur y, il renvoie la valeur de x ; sinon, il renvoie la valeur de y. Cette valeur est affecte z et le rsultat safche la ligne 26. Comme vous pouvez le constater, linstruction conditionnelle est en fait un quivalent (raccourci) de linstruction if...else.
Questions-rponses
Q Pourquoi utiliser des parenthses superues lorsque la priorit des oprateurs est vidente ? R Parce que le programme gagne en lisibilit et peut tre mis jour plus aisment. Q Si les oprateurs relationnels renvoient true ou false, pourquoi une valeur diffrente de zro est-elle considre comme vraie ? R Cette convention provient du langage C, qui tait souvent utilis pour crire les logiciels de bas niveau, comme les systmes dexploitation et les logiciels de contrle en temps rel. Il est probable que cette utilisation ait volu pour devenir un raccourci permettant de tester rapidement si tous les bits dun masque ou dune variable sont gaux 0.
94
Le langage C++
Les oprateurs relationnels renvoient true ou false, mais chaque expression renvoie une valeur et ces valeurs peuvent tre values dans une instruction if. Voici un exemple :
if ( (x = a+b) == 35 )
Cette instruction est tout fait correcte en C++. Elle produit une valeur, mme si a+b ne donne pas 35. Notez aussi que la somme obtenue est affecte x dans tous les cas. Q quoi servent les tabulations, les espaces et les sauts de ligne dans un programme ? R Ils nont aucun effet sur le droulement du programme, mais rendent le code plus lisible. Q Les nombres ngatifs peuvent-ils possder les valeurs true ou false ? R Tous les nombres diffrents de zro, quils soient positifs ou ngatifs, sont considrs comme vrais.
6. Quelle est la valeur de 8+2*3 ? 7. Quelle est la diffrence entre if (x = 3) et if (x == 3) ? 8. Les valeurs suivantes sont-elles gales true ou false ? a. 0 b. 1 c. -1 d. x = 0 e. x == 0 // en admettant que x ait la valeur 0
Chapitre 4
Expressions et instructions
95
Exercices
1. crivez une instruction if qui teste deux variables entires et qui affecte la valeur la plus faible la variable la plus leve. Nutilisez quune seule clause else. 2. Lisez ce programme. Imaginez la saisie de trois nombres et crivez le rsultat.
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: #include <iostream> using namespace std; int main() { int a, b, c; cout << "Entrez trois nombres.\n"; cout << "a: "; cin >> a; cout << "\nb: "; cin >> b; cout << "\nc: "; cin >> c; if (c = (a-b)) cout << "a :" << a << " moins b :" << b << " est gal c :" << c << "\n"; else cout << "a-b nest pas gal c :"; return 0; }
3. Saisissez le programme de lExercice 2 ; compilez-le, liez-le et lancez-le. Entrez les nombres 10, 20 et 50. Obtenez-vous le rsultat attendu ? Pourquoi ? 4. Examinez le programme suivant et imaginez le rsultat :
1: 2: 3: 4: 5: 6: 7: 8: 9: #include <iostream> using namespace std; int main() { int a = 2, b = 2, c; if (c = (a-b)) cout << "c vaut: " << c; return 0; }
5
Fonctions
Au sommaire de ce chapitre
Le rle dune fonction et ses diffrents lments Comment dclarer et dnir les fonctions Comment passer des paramtres une fonction Comment renvoyer une valeur partir dune fonction
On pourrait penser que la programmation oriente objet a privilgi les objets au dtriment des fonctions. Il nen est rien : ces dernires demeurent un lment essentiel des applications. Les fonctions globales existent en dehors des objets et des classes, tandis que les fonctions membres (ou mthodes membres) existent au sein dune classe et accomplissent ses diffrentes tches. Nous ne prsenterons ici que les fonctions globales ; nous tudierons le principe des fonctions membres au cours du prochain chapitre.
98
Le langage C++
return;
Lorsquelle est bien conue, une fonction ralise une seule action spcique, identie par le nom de la fonction. Les tches complexes doivent tre divises en fonctions simples qui seront appeles tour tour. Il existe deux catgories de fonctions : les fonctions dnies par lutilisateur et les fonctions prdnies qui sont intgres au compilateur. Les fonctions dnies par lutilisateur sont celles que vous crivez vous-mme.
Chapitre 5
Fonctions
99
vous dclarez donc que maFonction renverra une valeur entire. tudiez maintenant la dclaration suivante :
int maFonction(int unEntier, float unFlottant);
Cette dclaration indique que maFonction renverra une valeur entire et prendra deux valeurs. Lorsque vous envoyez des valeurs une fonction, celles-ci agissent comme des variables que vous pouvez manipuler lintrieur de la fonction. La description des valeurs envoyes est appele liste des paramtres formels. Dans lexemple prcdent, cette liste contient unEntier, qui est une variable entire et unFlottant, une variable de type float. Un paramtre formel dcrit le type de la valeur passe la fonction. La valeur qui sera rellement passe lors de lappel est appele paramtre effectif (ou rel). Dans lexemple suivant :
int valeurRenvoyee = maFonction(5, 6.7);
la variable entire valeurRenvoyee reoit la valeur renvoye par maFonction et les valeurs 5 et 6.7 sont passes en paramtres. Le type des paramtres effectifs doit correspondre au type des paramtres formels correspondants. Dans ce cas, 5 va dans un entier et 6.7 dans une variable float ; les types correspondent donc bien.
100
Le langage C++
crire le prototype dans un chier, que vous incluerez dans votre chier source laide de la directive #include ; crire le prototype de la fonction dans le chier source du programme qui lutilise ; dnir la fonction avant quelle ne soit appele par une autre fonction. Dans ce cas, la dnition tient lieu de prototype.
Bien que lon puisse dnir une fonction avant de lutiliser, ce qui pargne de crer un prototype, cette pratique nest pas conseille pour trois raisons. Tout dabord, cest une mauvaise ide dobliger les fonctions apparatre dans le chier selon un ordre prdtermin, car cela complique la maintenance du programme lorsque ces exigences voluent. Deuximement, il est possible quune fonction A() doive pouvoir appeler une fonction B() qui, elle-mme, a besoin dappeler la fonction A(). Il est donc impossible de dnir A() avant de dnir B() et de dnir B() avant A(). En ce cas, il faut au moins dclarer lune dentre elles. Enn, les prototypes de fonctions facilitent la mise au point du programme. Si le prototype dclare les paramtres de la fonction et le type du rsultat dune fonction et que la fonction ne correspond pas ce prototype, le compilateur peut dtecter cette incompatibilit et produire un message, ce qui vitera que lerreur ne soit visible quau moment de lexcution. Le prototype et la dnition se contrlent mutuellement, ce qui rduit la probabilit quune faute de frappe provoque un bogue dans la programme. Malgr tout, la grande majorit des programmeurs optent pour le troisime choix parce quil rduit le nombre de lignes de code, quil simplie la maintenance (les changements den-tte de la fonction exigent aussi le changement du prototype) et parce que lordre des fonctions dans un chier est gnralement assez stable. Cela dit, les prototypes sont ncessaires dans certains cas.
Prototypes de fonctions
Les prototypes de la plupart des fonctions prdnies sont dj crits et sont stocks dans des chiers que vous pouvez inclure dans vos programmes laide de la directive #include. Vous devrez en revanche fournir un prototype pour les fonctions que vous crivez vous-mme. Le prototype dune fonction est une instruction, ce qui signie quil doit se terminer par un point-virgule. Il est form du type de la valeur renvoye et de la signature. La signature dune fonction est compose de son nom et de la liste de ses paramtres formels.
Chapitre 5
Fonctions
101
La liste des paramtres formels numre tous les paramtres attendus et leurs types respectifs, en les sparant par des virgules, comme dans la Figure 5.2.
Figure 5.2 lments dun prototype de fonction.
unsigned short int calculSurface type du rsultat nom type de paramtre nom du paramtre
Le prototype de la fonction et sa dnition doivent se correspondre, cest--dire contenir les mmes informations (type de la valeur renvoye et signature). Si ce nest pas le cas, cela provoque une erreur de compilation. Notez cependant que le prototype de la fonction ne comprend pas ncessairement le nom des paramtres formels, mais quil peut se contenter de leur type, comme dans cet exemple :
long Surface(int, int);
Ce prototype dclare la fonction Surface() qui renvoie un entier long et attend deux paramtres entiers. Bien que cette syntaxe soit correcte, il est prfrable de mentionner le nom des deux paramtres car cela produit une dclaration plus comprhensible :
long Surface(int longueur, int largeur);
Le but de la fonction et de ses deux paramtres apparat plus clairement. Toutes les fonctions nont pas de type explicite pour leur rsultat. En labsence de ce type, C++ supposera que la fonction renvoit une valeur int (entier), mais il est conseill de lindiquer explicitement, y compris pour la fonction main(). Si votre fonction ne renvoie pas de valeur, son type de rsultat doit tre void (vide), comme dans cet exemple :
void afficheNombre(int unNombre);
Cette ligne dclare une fonction appele afficheNombre possdant un paramtre entier. void tant utilis comme type de rsultat, aucun lment nest renvoy.
Dnir la fonction
La dnition dune fonction est constitue de son en-tte et de son corps. Len-tte ressemble au prototype de la fonction, sauf que les paramtres doivent tre nomms et quil ny a pas de point-virgule nal.
102
Le langage C++
Le corps de la fonction est un ensemble dinstructions inclus entre accolades, comme dans la Figure 5.3.
Figure 5.3 Len-tte et le corps dune fonction.
type du rsultat nom paramtres
accolade ouvrante
Le chier source du Listing 5.1 contient un prototype dclarant une fonction dont le rle est de calculer une surface. Listing 5.1 : Dclaration, dnition et utilisation de la fonction Surface()
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: // Listing5.1 - Utilisation des prototypes de fonctions #include <iostream> int Surface(int longueur, int largeur); // prototype int main() { using std::cout; using std::cin; int longueurCour; int largeurCour; int surfaceCour; cout << "\nLargeur de votre cour? "; cin >> largeurCour; cout << "\nLongueur de votre cour? "; cin >> longueurCour; surfaceCour= Surface(longueurCour,largeurCour); cout << "\nLa surface de votre cour est de "; cout << surfaceCour;
Chapitre 5
Fonctions
103
cout << " mtres carrs\n\n"; return 0; } int Surface(int longue, int large) { return longue * large; }
Le prototype de la fonction Surface() se trouve la ligne 4. Comparez-le la dnition de la fonction la ligne 28. Vous pouvez constater que le nom, le type de la valeur renvoye et les types de paramtres sont identiques. Sil y avait une diffrence, le compilateur produirait un message derreur et ne crerait pas de chier objet. En fait, la seule diffrence est que le prototype se termine par un point-virgule et na pas de corps. Au niveau du prototype, les paramtres sappellent longueur et largeur et correspondent aux paramtres longue et large de la dnition. Les premiers gurent dans le chier titre dinformation alors que les seconds sont traits par la fonction. Il est souhaitable (mais non obligatoire) que les noms des paramtres dans le prototype correspondent ceux de limplmentation. Les paramtres effectifs sont passs la fonction dans lordre exact o ils ont t dclars et dnis, mais les noms ne sont pas pris en compte : vous auriez pass largeurCour suivi de longueurCour, la fonction aurait utilis le premier comme longueur et la second comme largeur. Le corps de la fonction doit toujours tre plac entre accolades, mme sil ny a quune seule instruction.
Info
104
Le langage C++
Chapitre 7). Les fonctions peuvent galement appeler dautres fonctions et sappeler ellesmmes (on parle alors de rcursivit). la n de lexcution dune fonction, la fonction appelante reprend la main. Pour la fonction main(), cest le systme dexploitation qui reprend la main.
Variables locales
Non seulement on peut passer des variables une fonction, mais on peut galement dclarer des variables dans le corps de celle-ci. Les variables dclares dans la fonction sont dites "locales", car elles nexistent que dans le cadre de cette fonction. Lorsque la fonction rend la main au programme, elles disparaissent. Les variables locales sont des variables classiques et sont donc dnies comme toutes les autres variables. Les paramtres passs la fonction sont galement considrs comme des variables locales et peuvent tre utiliss comme sils avaient t dnis dans le corps de la fonction. Le Listing 5.2 montre lutilisation des paramtres et des variables locales dans une fonction. Listing 5.2 : Utilisation des paramtres et des variables locales
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: #include <iostream> float Convert(float); int main() { using namespace std; float tempFar; float tempCel; cout << "Entrez la temprature en degrs Fahrenheit: "; cin >> tempFar; tempCel = Convert(tempFar); cout << "\nTemprature en degrs Celsius: ";
Chapitre 5
Fonctions
105
15: 16: 17: 18: 19: 20: 21: 22: 23: 24:
cout << tempCel << endl; return 0; } float Convert(float tempFar) { float tempCel; tempCel = ((tempFar - 32) * 5) / 9; return tempCel; }
Aux lignes 8 et 9, les deux variables de type float dclares doivent recevoir respectivement la temprature en degrs Fahrenheit et en degrs Celsius. la ligne 11, lutilisateur entre la valeur en degrs Fahrenheit convertir et celle-ci est passe la fonction Convert() la ligne 13. Avec lappel Convert() ligne 13, le programme va directement la ligne 21, cest-dire la premire ligne de la fonction de conversion. La variable locale tempCel est alors dclare. Attention : bien quelle porte le mme nom, cette variable est diffrente de la variable tempCel dclare la ligne 9. Le programme ne risque pas de confondre les deux variables puisque la variable tempCel locale disparat aprs lexcution de la fonction de conversion. Le paramtre tempFar est une copie locale de la variable passe par la fonction main(). Le paramtre formel et la variable locale pourraient sappeler farTemp et celTemp (ou autrement), le programme fonctionnerait de la mme faon. Vous pouvez essayer dentrer dautres noms et recompiler le programme ! La variable locale tempCel reoit la valeur correspondant la temprature en degrs Celsius. Il sagit dter 32 la temprature en degrs Fahrenheit, de multiplier le rsultat par 5 puis de le diviser par 9. Cette valeur renvoye est affecte la variable tempCel dans la fonction main() la ligne 13 et afche la ligne 15. Le rsultat prcdent montre que ce programme a t excut trois fois. La premire fois, il montre que 212 degrs Fahrenheit correspondent au point dbullition de leau, cest--dire
106
Le langage C++
100 degrs Celsius. La deuxime fois, vous pouvez constater que 32 degrs Fahrenheit correspondent au point de gel de leau. Enn, le troisime test passe une valeur quelconque, qui produit un rsultat fractionnaire.
Chapitre 5
Fonctions
107
Ce programme commence la ligne 10 avec linitialisation dune variable locale x dans main(). la ligne 11, sa valeur (5) safche lcran. La ligne 13 appelle la fonction maFct(). la ligne 21, dans maFct(), une variable locale x est initialise 8. Ce chiffre safche lcran. Laccolade ouvrante de la ligne 24 commence un bloc dinstructions. Le contenu de la variable x est afch. Une nouvelle variable galement appele x, mais dont la porte est limite au bloc, est initialise 9 dans la ligne 27 ; cest cette variable qui est afche par la ligne 30. Le bloc local se termine la ligne 30 ; la variable locale dclare la ligne 27 devient hors de porte est nest donc plus visible. La valeur de la variable x afche la ligne 32 est celle qui a t dclare la ligne 21 dans maFct() ; elle est toujours gale 8. En effet, elle na pas t affecte par la dnition de la ligne 27 dans le bloc, puisque les portes des deux variables x taient diffrentes. la ligne 33, maFct() devient hors de port et sa variable locale x disparat en mme temps. Le programme reprend la main la ligne 14. La ligne 15 reprend la valeur de la variable x cre la ligne 10. Vous constaterez quelle na pas t modie par les autres affectations des variables dnies dans maFct(). Bien entendu, ce programme na quune valeur dexemple et serait plus lisible si toutes les variables locales portaient des noms distincts !
108
Le langage C++
Ce programme initialise deux variables dans la fonction principale, puis les passe la fonction swap() qui les intervertit. De retour dans la fonction main(), leur contenu na pas t modi !
Chapitre 5
Fonctions
109
Linitialisation et lafchage des variables seffectuent aux lignes 9 et 11. La fonction swap() est appele la ligne 12 et les variables lui sont passes en paramtre. La fonction afche nouveau les variables la ligne 21. Comme lon pouvait sy attendre, elles apparaissent dans le mme ordre que dans la fonction principale. Puis elles sont interverties aux lignes 23 25, ce qui est conrm par lafchage de la ligne 27. Dans la fonction swap(), les deux valeurs sont donc bien changes. Le programme revient ensuite la ligne 13. Dans la fonction main(), les valeurs nont pas t changes. Comme vous lavez devin, les paramtres sont passs par valeur la fonction appele, ce qui signie quil sagit de copies locales la fonction. Ce sont donc ces copies qui ont t changes aux lignes 23 25, pas les originales. Aux Chapitres 8 et 10, vous verrez quil existe dautres solutions que celle du passage par valeur, qui permettent de modier les valeurs prsentes dans main().
Variables globales
Les variables dnies en dehors de toute fonction ont une porte globale et sont donc disponibles partir de nimporte quelle fonction du programme, y compris main(). Une variable locale peut porter le mme nom quune variable globale : la modication de son contenu ne modiera pas la variable globale, mais la variable locale masquera la variable globale (voir le Listing 5.5). Lorsquune fonction possde une variable locale de mme nom quune variable globale, ce nom fait rfrence la variable locale lorsquelle est utilise dans la fonction. Listing 5.5 : Utilisation de variables locales et de variables globales
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: #include <iostream> void maFonction(); int x = 5, y = 7; int main() { using namespace std;
cout << "x dans main: " << x << endl; cout << "y dans main: " << y << endl << endl; maFunction(); cout << "De retour de maFonction! << endl << endl; cout << "x dans main: " << x << endl; cout << "y dans main: " << y << endl; return 0;
110
Le langage C++
16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26:
} void maFonction() { using std::cout; int y = 10; cout << "x dans maFonction: " << x << endl; cout << "y dans maFonction: " << y << endl << endl; }
Ce programme simple met en vidence la confusion que peuvent causer variables locales et variables globales. La ligne 4 dclare et initialise les deux variables globales x et y avec les valeur 5 et 7. Ces valeurs safchent alors que le programme est encore dans la fonction main(). Vous pouvez constater que la fonction principale ne dnit pas les variables puisque x et y existent au niveau global. La ligne 11 appelle la fonction maFonction(). Le programme saute directement la ligne 18 et lit la premire instruction de cette fonction. La variable locale y est initialise 10. Elle se substitue la variable globale de mme nom. la ligne 25, lorsque la variable y est afche, cest donc le contenu de la variable locale qui apparat et qui masque la variable globale de mme nom. Lorsque la fonction prend n, le programme redonne la main la fonction principale et tient de nouveau compte des variables globales x et y. Vous pouvez remarquer que la variable globale y na pas t affecte par la modication de la variable y locale maFonction().
Chapitre 5
Fonctions
111
112
Le langage C++
Prenons les fonctions monDouble(), triple(), carre() et chacune une valeur. Vous pourriez crire lexpression suivante :
resultat = (monDouble(triple(carre(cube(maValeur)))));
Vous pouvez aborder cette instruction de deux manires. Dune part, la fonction monDouble() prend la fonction triple() comme paramtre. son tour, triple() prend la fonction carre(), qui prend la fonction cube() comme paramtre. La fonction cube() prend la variable maValeur comme paramtre. Si lon part dans lautre sens, cette instruction prend la variable maValeur et la passe en paramtre la fonction cube(), la valeur renvoye est passe en paramtre la fonction carre(), et ainsi de suite. La valeur obtenue la n est copie dans resultat. Vous remarquerez quil est assez difcile de dterminer les priorits dexcution de cette expression. Pour mieux comprendre ce traitement et rendre le code plus lisible, il est donc prfrable de crer une variable intermdiaire recevant le rsultat de chaque appel de fonction :
unsigned unsigned unsigned unsigned unsigned long long long long long maValeur monCube monCarre monTriple resultat = = = = = 2; cube(maValeur); carre(monCube); triple(monCarre); monDouble(monTriple); // // // // monCube = 8 monCarre = 64 monTriple = 192 Resultat = 384
Bien que C++ facilite lcriture dun code compact (voir lexemple prcdent) pour combiner les fonctions cube(), carre(), triple() et monDouble(), cette possibilit nest pas une obligation. Il vaut mieux que le code soit simple lire, donc entretenir, que compact.
Chapitre 5
Fonctions
113
Ces trois instructions return sont valides, condition que Fonction() renvoie une valeur. La valeur de la deuxime instruction return (x > 5) est gale true ou false selon que x est suprieur 5 ou infrieur ou gal 5. La valeur renvoye nest pas gale x, mais au rsultat du test. Lorsque le mot-cl return est dtect, lexpression situe immdiatement aprs est renvoye comme valeur de la fonction. Le code qui a appel la fonction reprend alors la main et les instructions qui sont ventuellement place aprs le mot-cl return ne sont pas prises en compte. Une fonction peut contenir une ou plusieurs instructions return, comme le montre le Listing 5.6. Listing 5.6 : Utilisation de plusieurs instructions return
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 14a: 15: 16: 17: 18: 18a: 19: 20: 21: 22: 23: 23a: 24: 25: 26: // Listing5.6 - Plusieurs instructions return // Dmonstration #include <iostream> int Doubleur(int montantADoubler); int main() { using std::cout; int resultat = 0; int entree; cout << "Entrez un nombre entre 0 et 10000 multiplier par 2: "; std::cin >> entree; cout << "\nAvant dappeler la fonction Doubleur()... "; cout << "\nValeur entre: " << entree << ", Valeur double: " << resultat << "\n"; resultat = Doubleur(entree); cout << "\nDe retour de la fonction Doubleur()...\n"; cout << "\nValeur entre: " << entree << ", Valeur double: " << resultat << "\n"; return 0; }
114
Le langage C++
27: 28: 29: 30: 31: 32: 33: 34: 35: } int Doubleur(int original) { if (original <= 10000) return original * 2; else return -1; std::cout << "Vous natteindrez jamais cette ligne!\n";
Vous tes invit entrer un nombre (lignes 14 et 15) dans une variable locale que lon afche ensuite, accompagne de la valeur courante de resultat (lignes 17 et 18). La ligne 20 appelle la fonction Doubleur() en lui passant en paramtre la valeur saisie. Le rsultat renvoy par la fonction est copi dans la variable locale resultat et les valeurs sont de nouveau afches au niveau de la ligne 23. Dans la fonction Doubleur() (ligne 30), le programme dtermine si le paramtre est infrieur ou gal 10 000. Si cest le cas, le nombre dorigine est renvoy aprs avoir t doubl. Dans le cas contraire, la valeur renvoye -1 indique une erreur. Lexcution de la fonction natteint jamais la ligne 34 car elle sarrte la ligne 31 ou la ligne 33, quelle que soit la valeur saisie. Un bon compilateur dtectera lerreur et mettra un message davertissement et un bon dveloppeur supprimera cette ligne inutile !
Chapitre 5
Fonctions
115
Quelle est la diffrence entre int main() et void main(); ? Quelle forme faut-il utiliser ? Jai utilis les deux avec succs, pourquoi faut-il crire : int main(){ return 0;} ? Les deux fonctionnent sur la plupart des compilateurs, mais seul int main() est compatible ANSI, il est donc assur de continuer fonctionner. La diffrence est la suivante : int main() renvoie une valeur au systme dexploitation. Lorsque votre programme se termine, cette valeur peut tre teste dans un script, par exemple. Dans ce livre, nous nutilisons pas la valeur renvoye, mais le standard ANSI exige que main() en renvoie une.
Ici, la fonction attend une variable entire. Si la dnition ne correspond pas ou si le type est diffrent, le compilateur produit une erreur et ne cre pas de chier objet. Les prototypes qui dclarent une valeur par dfaut pour le paramtre font exception. Une valeur par dfaut est une valeur utilise lorsque aucune autre nest fournie. La dclaration gurant plus haut pourrait tre rcrite ainsi :
long maFonction (int x = 50);
Ce prototype signie que la fonction renvoie un entier long et quel attend un paramtre entier. Si aucun paramtre ne lui est transmis lors de lappel, elle utilisera 50 comme valeur par dfaut. Rappelons que les noms de paramtres ntant pas obligatoires dans les prototypes de fonctions, on pourrait galement crire :
long maFonction (int = 50);
La dnition de la fonction nest pas modie par la prsence dun paramtre par dfaut et son en-tte se prsenterait donc de la faon suivante :
long maFonction (int x)
Si la fonction appelante ne fournit pas de valeur lappel de maFonction(), le nombre 50 sera copi dans le paramtre x. Il nest pas ncessaire de donner le mme nom au paramtre
116
Le langage C++
par dfaut dans le prototype et dans len-tte de fonction, car la valeur par dfaut est affecte en fonction de sa position, pas de son nom. Vous pouvez fournir des valeurs par dfaut selon vos besoins. La seule rgle que vous devez respecter est que si un paramtre na pas de valeur par dfaut, aucun des paramtres qui le prcdent ne peut en avoir (en dautres termes, les paramtres avec des valeurs par dfaut doivent apparatre la n de la liste). Si le prototype de la fonction est de la forme :
long maFonction (int Param1, int Param2, int Param3);
Param2 ne peut avoir une valeur par dfaut que si Param3 en a galement une. De mme, Param1 ne peut avoir une valeur par dfaut que si vous en avez affect une aux deux autres. Le Listing 5.7 illustre cette rgle. Listing 5.7 : Valeurs par dfaut des paramtres
1: 2: 3: 4: 5: 5a: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 15a: 16: 17: 18: 18a: 19: 20: 21: 21a: 22: 23: 24: 25: // Listing5.7 - Utilisation des // valeurs de paramtres par dfaut #include <iostream> int VolumeCube (int longueur, int largeur = 25, int hauteur = 1); int main() { int longueur = 100; int largeur = 50; int hauteur = 2; int volume; volume = VolumeCube(longueur, largeur, hauteur); std::cout << "Le premier volume est gal : " << volume << "\n"; volume = VolumeCube(longueur, largeur); std::cout << "Le deuxime volume est gal a: " << volume << "\n"; volume = VolumeCube(longueur); std::cout << "Le troisime volume est gal : " << volume << "\n"; return 0; } int VolumeCube(int longueur, int largeur, int hauteur)
Chapitre 5
Fonctions
117
la ligne 5, le prototype VolumeCube() indique que la fonction de mme nom attend trois paramtres entiers, dont les deux derniers ont une valeur par dfaut. La fonction calcule le volume du cube dont on lui a pass les dimensions. Si on ne lui fournit pas de largeur explicite, elle utilise une largeur de 25 et une hauteur de 1. Il est impossible de passer la hauteur sans passer aussi une largeur. Aux lignes 9, 10 et 11, on initialise les variables longueur, largeur et hauteur, qui sont ensuite passes la fonction VolumeCube() en ligne 14. Le rsultat de ce calcul est afch la ligne 15. la ligne 17, le programme reprend la main et rappelle la fonction. Cette fois-ci, aucune valeur nest fournie pour la hauteur et cest donc sa valeur par dfaut qui est prise en compte. Le calcul seffectue et le rsultat est de nouveau afch. Lexcution se poursuit la ligne 20, cette fois-ci en ne fournissant ni la largeur ni la hauteur. Les valeurs par dfaut correspondantes sont donc utilises et lon afche le rsultat obtenu.
Faire
Considrer les paramtres comme des variables
Ne pas faire
Crer une valeur par dfaut pour un paramtre
ble globale dans une fonction est rpercute dans toutes les fonctions.
Surcharge de fonctions
C++ permet de crer plusieurs fonctions portant le mme nom. Cette fonctionnalit est appele surcharge de fonction. Une fonction surcharge doit tre distincte au niveau de la
118
Le langage C++
liste de ses paramtres, qui doit diffrer par le type et/ou de le nombre des paramtres. Exemple :
int Fonction (int, int); int Fonction (long, long); int Fonction (long);
La fonction Fonction() est surcharge avec trois listes de paramtres. Dans les deux premires versions, les paramtres changent de type. Dans la dernire, la liste des paramtres ne contient pas le mme nombre de paramtres. Le type des valeurs renvoyes par les fonctions surcharges peut tre identique ou non. Lorsque deux fonctions renvoient des valeurs de types diffrents alors que leur nom et leur liste de paramtres sont identiques, on obtient une erreur de compilation. Pour modier le type du rsultat, vous devez galement modier la signature (nom et/ou liste de paramtres).
Info
La surcharge de fonctions est aussi appele polymorphisme de fonction. Poly signie plusieurs et morphe signie forme : une fonction polymorphe pourra donc prendre plusieurs formes. Le polymorphisme de fonction reprsente la capacit de "surcharger" une fonction avec plusieurs signications. Comme nous lavons vu, il est possible de modier le nombre et le type des paramtres en surchargeant une fonction. Entre deux fonctions surcharges, le compilateur dterminera celle qui doit tre appele en fonction du type et/ou du nombre des paramtres utiliss. Ainsi, grce la surcharge, vous pouvez crer la fonction Moyenne() capable de calculer tour tour la moyenne de valeurs entires, la moyenne de valeurs fractionnaires, etc., ce qui vous vite de devoir dnir les fonctions MoyenInt(), MoyenFloat(), etc. Supposons que vous dnissiez une fonction qui multiplie par 2 la valeur saisie. Il sera souhaitable quelle puisse calculer des valeurs de tout type (int, float ou double). Si vous nutilisez pas la surcharge de fonction, vous devrez crer quatre fonctions distinctes :
int DoubleInt(int); long DoubleLong(long); float DoubleFloat(float); double DoubleDouble(double);
Chapitre 5
Fonctions
119
ce qui est bien plus facile lire et utiliser. Vous navez plus besoin de savoir quelle fonction appeler : il sufra de passer une valeur en paramtre et le programme appellera automatiquement la bonne fonction, comme le montre le Listing 5.8. Listing 5.8 : Exemple de fonction polymorphe
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: // Listing5.8 - Exemple de // fonction polymorphe #include <iostream> int Double(int); long Double(long); float Double(float); double Double(double); using namespace std; int main() { int long float double int long float double cout cout cout cout << << << <<
monInt = 6500; monLong = 65000; monFloat = 6.5F; monDouble = 6.5e20; doubleInt; doubleLong; doubleFloat; doubleDouble; "monInt: " << monInt << "\n"; "monLong: " << monLong << "\n"; "monFloat: " << monFloat << "\n"; "monDouble: " << monDouble << "\n";
doubleInt = Double(monInt); doubleLong = Double(monLong); doubleFloat = Double(monFloat); doubleDouble = Double(monDouble); cout cout cout cout << << << << "doubleInt: " << doubleInt << "\n"; "doubleLong: " << doubleLong << "\n"; "doubleFloat: " << doubleFloat << "\n"; "doubleDouble: " << doubleDouble << "\n";
120
Le langage C++
38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64:
return 0; } int Double(int original) { cout << "Dans Double(int)\n"; return 2 * original; } long Double(long original) { cout << "Dans Double(long)\n"; return 2 * original; } float Double(float original) { cout << "Dans Double(float)\n"; return 2 * original; } double Double(double original) { cout << "Dans Double(double)\n"; return 2 * original; }
La fonction Double() est surcharge avec int, long, float et double. Les prototypes gurent de la ligne 5 la ligne 8, les dnitions de la ligne 42 la ligne 64.
Chapitre 5
Fonctions
121
Notez que, dans cet exemple, linstruction using namespace std; a t ajoute la ligne 10, hors de toute fonction spcique. Linstruction est donc globale au chier et lespace de nom est utilis dans toutes les fonctions dclares. Le corps du programme contient huit variables locales. De la ligne 14 la ligne 17, quatre valeurs sont initialises alors que les quatre autres reoivent le rsultat de la fonction Double() (lignes 29 32). Lorsque la fonction est appele, cest le paramtre qui est pass qui permet au programme de dterminer la bonne version. Pour cela, le compilateur examine les paramtre, puis appelle lune des quatre fonctions Double() surcharges. Le rsultat afch montre quil ne sest pas tromp !
Fonctions en ligne
Lorsque lon dnit une fonction, le compilateur produit gnralement un seul ensemble dinstructions en mmoire. Lorsque cette fonction est appele, le programme saute directement au dbut de ces instructions et, quand la fonction se termine, il revient la ligne qui suit immdiatement lappel. Si vous invoquez dix fois la fonction, le programme excutera autant de fois les mmes instructions, mais il nexistera en fait quune seule copie de la fonction dans le code (et non 10). Ces sauts et ces retours ont un petit impact sur les performances. Certaines fonctions sont trs courtes puisquelles nont quune ou deux lignes de code : il serait alors plus efcace pour le programme dviter de se brancher un autre emplacement mmoire uniquement pour excuter une ou deux instructions. En programmation, efcacit rime avec rapidit : un programme sexcute plus vite sil y a moins dappels de fonctions. C++ propose une solution. Lorsquune fonction est dclare avec le mot-cl inline, le compilateur ne cre pas une vritable fonction et se contente de remplacer lappel de cette fonction par son code dans la fonction appelante. Il ny a donc plus de saut en mmoire lors de lappel : cest exactement comme si vous aviez directement crit les instructions de la fonction dans la fonction appelante.
122
Le langage C++
Cela dit, les fonctions en ligne peuvent coter cher. Si la fonction est appele dix fois, le code inline sera copi autant de fois dans les fonctions appelantes. Cet accroissement de la taille du chier excutable peut compltement occulter le gain minime de rapidit obtenu et, en fait, le programme peut parfois sen trouver ralenti ! De nos jours, les compilateurs peuvent quasiment toujours faire un meilleur travail que vous pour prendre cette dcision lorsquelle simpose et il est gnralement prfrable de ne pas dclarer une fonction inline, sauf si elle ne comporte quune ou deux instructions. Dans le doute, ne le faites pas. Loptimisation des performances est une opration difcile et la plupart des programmeurs ont du mal identier les problmes de leurs programmes sans aide, ce qui implique des programmes spcialiss, comme les dbogueurs et les proleurs. De mme, il vaut toujours mieux crire du code clair et comprhensible plutt que supposer quil sera lent ou rapide, quitte crire un code difcile comprendre. En effet, il est toujours plus facile dacclrer un code clair.
Info
Chapitre 5
Fonctions
123
La ligne 4 dclare la fonction Double() inline avec un paramtre et type de rsultat qui sont tous deux des int. Cette dclaration ressemble nimporte quel autre prototype, sauf que le mot-cl inline prcde le type du rsultat. La compilation revient produire le code suivant :
cible = 2 * cible;
Au moment de lexcution, les instructions de la fonction ont t intgres dans le code, compiles dans le chier .obj. Cela vite un branchement et un retour au cours de lexcution du programme, au prix dun chier excutable plus volumineux. inline est un mot-cl indiquant simplement au compilateur que vous voulez que la fonction soit en ligne. Le compilateur peut ne pas en tenir compte et raliser un appel normal.
Info
Rcursivit
Une fonction peut sappeler elle-mme. Cette fonctionnalit sappelle la rcursivit. Elle peut tre directe ou indirecte. Elle est directe lorsque la fonction sappelle elle-mme et indirecte lorsquune fonction appelle une autre fonction qui appelle son tour la premire. Certains problmes se rsolvent plus simplement laide de la rcursivit : il sagit gnralement de ceux o lon traite des donnes pour obtenir un rsultat et que lon applique le mme traitement ce rsultat. Quelle soit directe ou indirecte, la rcursivit peut se terminer et produire un rsultat ou ne jamais nir et provoquer une erreur dexcution.
124
Le langage C++
Lorsquune fonction sappelle elle-mme, il faut bien comprendre que cest une nouvelle copie de la fonction qui sexcute. Les variables locales de cette copie sont totalement indpendantes de celles de la premire et elles ne peuvent pas les modier directement, exactement comme les variables locales de main() ne peuvent pas affecter les variables locales des fonctions quelle appelle, comme on la montr dans le Listing 5.3. Nous allons utiliser la rcursivit pour traiter la suite de Fibonacci :
1,1,2,3,5,8,13,21,34...
Aprs le deuxime, chaque nombre est la somme des deux prcdents. Un problme de Fibonacci consiste, par exemple, trouver le 12e nombre de la suite. Pour rsoudre ce problme, il faut soigneusement tudier la suite. Les deux premires valeurs sont gales 1. Chacun des nombres qui suit est la somme des deux prcdents. Le septime nombre, par exemple, correspond la somme du sixime et du cinquime. Plus gnralement, le nime nombre est la somme de n-2 et de n-1 pour n > 2. Toute fonction rcursive doit inclure une condition darrt. En son absence, le programme ne se terminerait jamais. Dans notre exemple, la condition darrt correspond n < 3 (cest--dire que lorsque n est infrieur 3, nous pouvons arrter le traitement). Lalgorithme du programme se prsente ainsi : 1. Entrer la position trouver dans la suite. 2. Appeler la fonction fib(), en transmettant la valeur saisie. 3. La fonction fib() teste son paramtre (n). Si n < 3, elle renvoie 1 ; dans le cas contraire, elle sappelle elle-mme deux fois, en passant la valeur n-2 puis la valeur n-1. Enn, elle renvoie la somme des rsultats des deux appels la fonction principale. Si vous appelez fib(1) ou fib(2), la fonction renvoie 1 et sarrte. Si vous appelez fib(3), elle retournera la somme des valeurs calcules lors de lappel fib(2) et de lappel fib(1), cest--dire 2 (1+1). De mme, lappel fib(4) renvoie la somme de lappel de fib(3) et de fib(2). Nous venons de voir que fib(3) renvoyait 2 (en appellant fib(2) et fib(1)) et que fib(2) renvoyait 1. Par consquent, fib(4) additionne ces deux nombres et renvoie 3, qui est le quatrime nombre de la suite. Avec une tape de plus, fib(5) renvoie la somme de fib(4) et fib(3), soit 3 + 2, cest-dire 5. Cette mthode nest pas la plus efcace pour rsoudre le problme (lappel fib(20) appellera 13 529 fois la fonction fib() avant de renvoyer le rsultat !), mais elle fonctionne. Faites attention : si vous utilisez un paramtre trop grand, vous tomberez cours de mmoire car, chaque appel, la mmoire est sollicite et nest libre quaprs la n du
Chapitre 5
Fonctions
125
premier appel. Elle risque donc dtre rapidement sature. Cette fonction est implmente dans le Listing 5.10.
ntion Atte
Dans le programme ci-aprs, vitez de choisir une valeur suprieure 15, car la mmoire de votre PC risque ne pas tre sufsante !
126
Le langage C++
Info
Certains compilateurs ont des difcults avec lusage des oprateurs dans une instruction cout. Si vous recevez un avertissement la ligne 32, mettez des parenthses autour de la soustraction pour que les lignes 32 et 33 deviennent :
std::cout << "Appel de fib(" << (n-2) << ") "; std::cout << "et fib(" << (n-1) << ").\n";
la ligne 9, lutilisateur entre un nombre qui est copi dans n. Cette valeur est transmise fib(). Le programme se branche sur la fonction fib(), comme vous pouvez le vrier lcran (ligne 23). la ligne 25, le programme vrie si largument n est infrieur 3. Si tel est le cas, la fonction fib() renvoie la valeur 1. Dans le cas contraire, elle renvoie la somme des valeurs aprs avoir trait n-2 et n-1. Ces valeurs ne peuvent pas tre renvoyes avant la n de lappel fib(). Vous pouvez donc imaginez que le programme appelle fib de faon rptitive jusqu ce quun de ces appels renvoie une valeur. Les seuls qui renvoient une valeur sont les appels fib(2) et fib(1). Ces valeurs sont ensuite passes aux appelants en attente qui, leur tour, ajoutent la valeur renvoye la leur propre, puis la retournent. Les Figures 5.4 et 5.5 illustrent ces appels rcursifs fib().
Chapitre 5
Fonctions
127
fib(2) return 1
fib(2) return 1
fib(1) return 1
fib(2) return 1
fib(2) return 1
fib(1) return 1
fib(2) return 1
fib(6)
fib(5)
8
return fib(4) + fib(5)
5
return fib(3) + fib(4)
3
fib(4) return fib(2) + fib(3) fib(3)
2
fib(4)
2
fib(2) 1 return 1 1
1 fib(3)
fib(1) return 1
fib(2)
return 1
Dans cet exemple, n est gal 6 et la fonction fib(6) est appele depuis la fonction principale. la ligne 25, le programme vrie si n < 3. Le test choue et la fonction fib(6) renvoie la ligne 34 la somme des valeurs renvoyes par fib (4) et fib(5). Voici la ligne 34 :
return( fib(n-2)+ fib(n-1));
Un appel est fait fib(4) depuis cette instruction de retour (comme n == 6, fib(n-2) est la mme chose que fib(4)) et un autre appel est fait fib(5) (fib(n-1)), puis la fonction dans laquelle vous vous trouvez (fib(6)) attend que ces appels renvoient une valeur. ce moment, la fonction peut renvoyer le rsultat de laddition de ces deux valeurs.
128
Le langage C++
Puisque fib(5) passe un argument qui nest pas infrieur 3, fib() sera de nouveau appele, cette fois avec 4 et 3. fib(4) son tour, appellera fib(3) et fib(2). Grce lafchage des valeurs, vous pouvez suivre le droulement du programme. votre tour de tester le code. Lorsque le chier excutable est prt, entrez 1, puis 2, puis 3 et allez jusqu 6 en tudiant soigneusement ce qui safche. Le moment est tout fait propice au test de votre dbogueur. Placez un point darrt la ligne 21, puis faites un pas pas dtaill (ou step into) dans chaque appel fib, en gardant la trace de la valeur de n chaque appel rcursif fib. La rcursivit nest pas trs utilise en programmation C++. Toutefois, elle savre trs efcace pour rsoudre certains problmes. La rcursivit est un domaine difcile matriser. Nous lavons prsente parce quil fallait que vous connaissiez ce concept. Toutefois, ne vous inquitez pas si certains dtails vous ont chapp.
Info
Chapitre 5
Fonctions
129
Certains programmeurs refusent de connatre en dtail la structure de la mmoire vive, prtextant quil nest pas utile dtre expert en mcanique pour conduire une voiture, par exemple. Vous devez comprendre lorganisation de la mmoire vive. Il est indispensable de savoir comment les variables sont cres et gres et dans quelle partie les paramtres sont transfrs.
Le partitionnement de la RAM
Lorsque vous crez un programme, le systme dexploitation (DOS, Linux/UNIX ou Windows, par exemple) congure les diffrentes zones de la mmoire en fonction des informations fournies par le compilateur. Comme vous tes un dveloppeur C++, vous allez entendre parler despace de noms global, de mmoire libre, de registres, despace de code et de pile. Les variables globales sont dans lespace de noms global. Vous dcouvrirez les notions despace de noms global et despace libre dans les chapitres suivants. Pour le moment, nous allons nous intresser aux registres, lespace de code et la pile. Les registres sont une zone de mmoire spciale, intgre lunit centrale (galement appele UC ou CPU). Ils grent le fonctionnement interne de lordinateur. Leur mode de traitement est complexe et dpasse de loin le cadre de cet ouvrage mais les registres qui nous intressent sont ceux qui pointent en permanence sur la ligne suivante de code et qui forment ce que lon appelle le pointeur dinstruction. Un pointeur dinstruction garde la trace de la prochaine ligne de code excuter. Les instructions se trouvent dans lespace de code, qui est la partie de la mmoire qui stocke le code binaire produit par le compilateur. Chaque ligne de code source est traduite en une suite dinstructions, chacune delles occupant une adresse particulire en mmoire. Le pointeur dinstruction contient ladresse de la prochaine instruction traiter. Tout cela est reprsent dans la Figure 5.6.
Figure 5.6 Le pointeur dinstruction.
Espace de code 100 Int x=5; 101 Int y=7; 102 cout << x; 103 Fonction(x,y); 104 y=9; 105 return; Pointeur d'instruction
102
130
Le langage C++
La pile est une zone spciale de mmoire alloue au programme, dans laquelle celui-ci organise les donnes utilises par les fonctions. On lappelle pile car cest une structure de donne LIFO (Last In, First Out, soit "dernier entr, premier sorti"), que lon pourrait comparer une pile dassiettes (voir Figure 5.7).
Figure 5.7 Une pile.
Comme pour une pile dassiettes ou un tas de pices, le dernier lment pos est le premier retir. Cela diffre de la plupart des les dattente dans lesquelles le premier arriv est le premier sorti. La taille de la pile varie avec le nombre dlments qui entrent, puis sortent. Lorsque lon empile des assiettes, la pile grossit ; lorsquon dpile des assiettes, elle se rduit. Il est impossible de retirer une assiette situe la base dune pile sans retirer dabord toutes les assiettes poses dessus. Une pile dassiettes est une analogie classique, mais il serait plus juste de comparer une pile un ensemble de cases superposes de haut en bas. Le sommet de la pile est la case sur laquelle pointe le pointeur de pile (qui est galement un registre). Chaque case est associe une adresse squentielle, lune delles tant stocke dans le registre du pointeur de pile. Toutes les adresses situes en dessous de cette adresse "magique" appartiennent la pile. Tout ce qui est au-dessus du sommet se trouve en dehors de la pile et est considr comme non valide (voir Figure 5.8). Lorsque des donnes sont poses sur la pile, elles sont places dans une case situe audessus du pointeur, puis ce dernier se dplace vers le haut pour pointer sur ces nouvelles donnes. Lorsque des donnes sont tes de la pile, le pointeur se dplace simplement sur ladresse prcdente (voir Figure 5.9). Les donnes au-dessus du pointeur de pile (hors de la pile) peuvent ou non tre changes tout moment. Ces valeurs sont dsignes sous le nom de garbage (informations incohrentes) car leurs valeurs ne sont plus ables.
Chapitre 5
Fonctions
131
Pile 100 80 101 102 monAge 50 103 104 105 votreAge 37 106 107 108 109 110 Dans la pile Pointeur de pile Hors de la pile 102
Pile 100 laVariable 80 101 102 monAge 50 103 104 105 votreAge 37 106 107 108 109 110 Dans la pile Hors de la pile Pointeur de pile 108
132
Le langage C++
4. Le sommet courant de la pile est marqu laide dun pointeur spcial appel stack frame. Tout ce qui sera ajout la pile jusquau retour de la fonction aura dsormais une porte locale, cest--dire limite la fonction. 5. Tous les paramtres passs la fonction sont placs sur la pile. 6. Linstruction prsente dans le pointeur dinstruction sexcute. Il sagit de la premire instruction de la fonction appele. 7. Les variables locales sont empiles sur la pile au fur et mesure quelles sont dnies. Quand la fonction est prte se terminer, la valeur de son rsultat est place dans la zone de la pile rserve ltape 2. Puis la pile est dpile jusquau stack frame, ce qui revient faire disparatre toutes les variables locales et les paramtres passs la fonction. La valeur renvoye est dpile de la pile et devient la valeur de lappel de la fonction. Ladresse de ltape 1 est rcupre et place dans le pointeur dinstruction. Le programme reprend alors son droulement aprs linstruction dappel de la fonction. Bien entendu, les dtails du processus varient dun compilateur un autre et en fonction des systmes dexploitation ou des processeurs. Nanmoins, lorganisation de la pile est sensiblement la mme sur tous les systmes dexploitation. Au cours des prochains chapitres, nous aborderons dautres emplacements mmoire qui servent stocker des donnes qui doivent persister en dehors de la vie des fonctions.
Questions-rponses
Q Pourquoi ne pas utiliser que des variables globales ? R Il y a quelques annes, ctait la rgle. Cependant, mesure que les programmes sont devenus plus complexes, il est devenu difcile de trouver les bogues dus des variables pouvant tre modies par nimporte quelle fonction. En effet, les variables globales peuvent tre modies de nimporte quel point du programme. Des annes dexprience ont convaincu les programmeurs que les donnes devaient rester les plus locales possibles et que leur accs devait tre restreint. Q Dans un prototype de fonction, quand dois-je utiliser le mot-cl inline ? R Lorsque la fonction nexcde pas deux instructions et quelle est peu appele. Q Pourquoi les modications apportes aux valeurs des paramtres dune fonction ne se retent-elles pas dans la fonction appelante ? R Les paramtres sont passs par valeur la fonction, ce qui signie quils sont dupliqus et que la valeur dorigine est conserve dans la fonction appelante. Pour en savoir plus, reportez-vous la section "Principe des fonctions".
Chapitre 5
Fonctions
133
Q Si les paramtres sont passs par valeur, que faire si lon souhaite que les modications soient retes dans la fonction appelante ? R La solution consiste utiliser les pointeurs ou des rfrences (voir les Chapitres 8 et 9). Vous y trouverez aussi une solution pour quune fonction puisse renvoyer plusieurs valeurs. Q Je rencontre ces deux fonctions : int Surface(int largeur, int longueur = 1); int Surface(int taille); Q Y a-t-il surcharge ? Nous avons un nombre diffrent de paramtres, mais le premier prototype a une valeur par dfaut. R Les dclarations seront compiles sans erreur, mais si vous invoquez la fonction Surface avec un seul paramtre, vous obtiendrez une erreur signalant quil y a ambigut entre Surface(int, int) et Surface(int).
Exercices
1. crivez le prototype de la fonction Perimetre(), qui renvoie un entier long non sign et a deux paramtres entiers courts non signs. 2. crivez la dnition de la fonction Perimetre(), dcrite ci-dessus. Les deux paramtres correspondent la longueur et la largeur dun rectangle. Rappel : le primtre est gal deux fois la largeur + deux fois la longueur.
134
Le langage C++
5. crivez une fonction qui reoit deux paramtres entiers courts non signs, divise le premier par le second et renvoie le rsultat. Si le second nombre est gal zro, la division est impossible et la fonction doit renvoyer la valeur -1. 6. crivez un programme qui invite entrer deux valeurs puis qui appelle la fonction dnie la question 5. Afchez le rsultat ou un message derreur si la valeur renvoye est gale -1. 7. crivez un programme qui demande un nombre et un exposant. Crez une fonction rcursive qui met le nombre la puissance indique. Par exemple, si le nombre entr est 2 et lexposant 4, la fonction doit renvoyer 16.
6
Programmation oriente objet
Au sommaire de ce chapitre
Les classes et les objets La dnition dune classe et la cration dobjets associs La notion de fonction membre et de donne membre Le rle des constructeurs
136
Le langage C++
C a t conu pour tre un compromis entre les langages de programmation de haut niveau, comme COBOL, et lassembleur, qui est extrmement performant, mais difcile utiliser. C mettait en uvre la programmation "structure", o les problmes sont dcomposs en units plus petites qui peuvent tre rptes (les procdures) et les donnes sont regroupes en paquets (appels structures). Or, les langages de recherche, comme Smalltalk et CLU, avaient dj commenc choisir une nouvelle orientation (lorientation objet), qui associait les donnes dans des assemblages comme les structures, mais avec les fonctionnalits des procdures, le tout en une seule unit : lobjet. Le monde qui nous entoure est peupl dobjets : voitures, chiens, arbres, nuages, eurs, etc. Chacun possde ses caractristiques propres (rapide, amical, noir...). La plupart ont un comportement (ils roulent, aboient, poussent...). Gnralement, nous ne faisons pas rfrence aux donnes dun chien, ni la faon de les manipuler : nous voyons un chien comme un lment faisant partie de notre univers (son apparence, ce quil fait). Cela vaut galement pour tout objet du monde rel intgr dans le domaine de linformatique. Les programmes crits en ce dbut de XXIe sicle sont beaucoup plus complexes que ceux crits la n du prcdent. Les programmes crs laide de langages procduraux se rvlent difcile maintenir et coteux modier. Les interfaces utilisateur graphiques, Internet, la tlphonie numrique et sans l, et toutes les nouvelles technologies ont considrablement accru la complexit des projets alors mme que les attentes des utilisateurs augmentaient. Les langages de programmation orients objet constituent un outil prcieux pour les ds du dveloppement logiciel. Bien quil nexiste pas de solution idale pour le dveloppement de logiciels complexes, ces langages crent un lien puissant entre les structures de donnes et les mthodes qui les manipulent, se rapprochant ainsi de notre manire de penser (nous, les programmeurs et les clients). La communication et la qualit des logiciels samliorent en consquence. On ne pense dsormais plus en termes de structures de donnes et de fonctions permettant de les manipuler, mais en termes dobjets, comme sil sagissait de leurs quivalents dans le monde rel, qui apparaissent et agissent dune certaine faon. C++ a t conu comme un pont entre la programmation oriente objet et le langage C. Lobjectif tait de fournir une conception oriente objet une plate-forme de dveloppement de logiciels professionnels rapide, avec un intrt tout particulier pour les performances. Vous verrez plus loin comment C++ a atteint ces objectifs.
Chapitre 6
137
sa taille en mmoire ; les informations quelle peut contenir ; les actions qui peuvent sy appliquer.
Dans des langages traditionnels comme le C, les types taient intgrs au langage. En C++, le programmeur a la possibilit dtendre le langage de base en crant les types dont il a besoin chacun de ces nouveaux types peut avoir toutes les fonctionnalits et la puissance des types prdnis.
Inconvnients de la cration de types avec struct Le langage C permet de crer de nouveaux types en combinant des variables au sein de structures (struct). Ces structures peuvent tre assimiles un nouveau type de donnes via linstruction typedef. Cette fonctionnalit a toutefois quelques lacunes : Les struct et les fonctions qui les manipulent ne sont pas lies ; on ne retrouve ces fonctions quen lisant les chiers den-tte des bibliothques disponibles et en recherchant celles qui ont un paramtre du nouveau type.
138
Le langage C++
La coordination des activits des fonctions lies un struct particulier est plus difcile car tout ce qui se trouve dans le struct peut tre modi tout moment un autre endroit du programme. Il ny a aucun moyen de protger la structure des interfrences extrieures. Les oprateurs prdnis ne fonctionnent pas sur les struct ; il nest pas possible dadditionner deux struct avec le signe plus (+), mme si cela semblerait naturel (par exemple lorsque deux struct reprsentent des lments textuels complexes que lon souhaite concatner ensemble).
Chapitre 6
139
Cette dclaration nalloue pas de mmoire pour un chat. Elle dcrit uniquement ce quest un Chat, quelles sont ses donnes membres (sonAge et sonPoids) et ce quil sait faire (Miauler()). Mme si la mmoire nest pas alloue, elle indique au compilateur la taille de la classe et lespace mmoire ncessaire chaque chat. Si un entier occupe 4 octets sur lordinateur, un objet Chat aura donc une taille de 8 octets : 4 pour sonAge et 4 pour sonPoids. Miauler() noccupera que lespace ncessaire aux stockage des informations permettant de connatre son emplacement en mmoire ; il sagit en fait dun pointeur vers une fonction qui peut occuper 4 octets sur une plate-forme 32 bits.
140
Le langage C++
De mme, de nombreux programmeurs dbutent tous les noms de mthodes par une majuscule et nutilisent que des minuscules pour les noms des variables membres. Les mots les composant sont spars par le caractre de soulignement comme dans Chasser_Souris, ou simplement indiqus avec la premire lettre en majuscule (ChasserSouris ou DessinerCercle). Le plus important est de choisir un style et de lappliquer dans tous les programmes. Avec le temps, votre style voluera, non seulement pour les conventions de noms, mais galement pour lindentation, lalignement des accolades et les styles de commentaires. La plupart des socits de dveloppement ont bien sr adopt un "standard maison" pour rgler les problmes de style. Cela garantit que tous les dveloppeurs pourront relire aisment le code de leurs collgues. Malheureusement, cela sapplique galement aux socits qui dveloppent des systmes dexploitation et des bibliothques de classes rutilisables, ce qui signie gnralement que les programmes C++ doivent travailler simultanment avec plusieurs conventions de nommage. Comme on la dj indiqu, C++ diffrencie les majuscules des minuscules. Les noms de classes, fonctions et variables devront donc tous tre choisis sur le mme modle. Cela vous pargnera davoir rechercher comment le nom de la classe a t orthographi : Rectangle, RECTANGLE ou rectangle ?
Info
ntion Atte
Dans ce code, on dnit la variable PoidsBrut de type entier non sign, ainsi que lobjet Frisky de la classe (ou du type) Chat.
Classes et objets
Vous ne dorlotez pas la dnition dun chat, vous dorlotez un chat particulier. Vous faites une diffrence entre un chat en gnral et celui qui gt langoureusement sur le divan de votre salon. De la mme faon, C++ fait la diffrence entre la classe Chat en gnral et
Chapitre 6
141
chaque objet Chat individuel. Frisky est un objet de la classe Chat, comme PoidsBrut est une variable de type entier non sign. Un objet est une instance particulire dune classe.
Pour utiliser une mthode de la classe, il suft de lappeler. Dans lexemple ci-dessus, vous appelez la fonction Miauler() de Frisky.
Le compilateur dtectera lerreur, car il est impossible daffecter 5 un entier. En revanche, vous pouvez dnir une variable de type entier et lui affecter la valeur 5. Exemple :
int x; // dfinir x comme un entier // affecter la valeur 5 x
x = 5;
Le compilateur produira une erreur, car il est impossible daffecter la valeur 5 au membre sonAge de la classe Chat. Vous devez crer un objet Chat spcique, puis lui attribuer cette valeur. Exemple :
Chat Frisky; Frisky.sonAge = 5; // mme chose que int x; // mme chose que x = 5;
142
Le langage C++
Le compilateur vous rpondra aussi " mais non, idiot, les chats naboient pas" (bien quil utilisera srement dautres termes moins potiques, comme [531] Error: Member function Aboyer not found in class Chat). Le compilateur sait que Frisky ne peut pas aboyer car la classe Chat ne possde pas de mthode Aboyer() et il ne saura pas non plus que Frisky peut miauler si vous navez pas dni de mthode Miauler().
Faire
Dclarer une classe laide du mot-cl
Ne pas faire
Confondre une dclaration avec une dni-
class.
Utiliser loprateur point (.) pour accder aux
tion. Une dclaration dcrit une classe, une dnition rserve de la mmoire pour un objet.
Confondre classe et objet. Affecter des valeurs une classe. En revan-
che, vous pouvez attribuer des valeurs aux donnes membres dun objet.
Chapitre 6
143
Dans cette dclaration, sonAge, sonPoids et Miauler() sont privs, car tous les membres dune classe sont privs par dfaut. moins de prciser le contraire, ils sont privs. Si vous crez un programme et que vous excutez les deux instructions suivantes dans main() :
int main() { Chat Mistigri; Mistigri.sonAge = 5; // erreur! impossible daccder des // donnes prives! ...
le compilateur produira une erreur. En effet, en laissant ces membres privs, vous indiquez au compilateur que lon ne pourra accder sonAge, sonPoids et Miauler() qu partir des fonctions membres de la classe Chat. Pourtant, ici, vous avez tent daccder au membre sonAge de Mistigri depuis lextrieur dune mthode de Chat. Le fait que Mistigri soit un objet Chat ne signie pas que vous pouvez accder ses composants privs (mme sils sont visibles dans la dclaration). Les dbutants en C++ sont souvent troubls par ce fonctionnement. Je vous entends presque hurler "Je viens de dire que Mistigri est un chat ! Pourquoi ne peut-il pas accder son propre ge ?". En fait, il le peut, mais pas vous ! Dans ses propres mthodes, Mistigri peut accder tous ses membres, publics ou privs. Le fait que vous ayez cr un Chat ne signie pas que vous pouvez voir ou modier ses parties prives. Vous pouvez avoir accs aux donnes membres de la classe Chat en rendant publics certains des membres :
class Chat { public: unsigned int sonAge; unsigned int sonPoids; void Miauler(); };
Dans cette dclaration, sonAge, sonPoids et Miauler() sont dsormais publics. Mistigri.sonAge = 5, de lexemple prcdent, peut donc tre compil sans problme.
144
Le langage C++
Info
Le mot-cl public sapplique tous les membres de la dclaration jusqu ce que lon rencontre le mot-cl private et vice versa. Cela vous permet de dclarer facilement des parties de la classe comme prives ou publiques.
Dans le Listing 6.1, les membres de la classe Chat sont publics. Listing 6.1 : Accs aux membres public dune classe simple
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: // Dclaration dune classe Chat et // dfinition dun objet de la classe #include <iostream> class Chat { public: int sonAge; int sonPoids; }; // dclare la classe Chat // // // // les membres qui suivent sont publics variable membre variable membre remarquez le point-virgule
int main() { Chat Frisky; Frisky.sonAge = 5; // affecte 5 la variable membre std::cout << "Frisky est un chat qui a "; std::cout << Frisky.sonAge << " ans.\n"; return 0; }
La ligne 6 contient le mot-cl class, indiquant que ce qui suit est une dclaration de classe. Ici, cette classe sappelera Chat. Le corps de la dclaration commence par une accolade ouvrante la ligne 7 et se termine par une accolade fermante et un point-virgule la ligne 11. la ligne 8, le mot-cl public suivi de deux-points signie que tout ce qui suit est public jusqu la rencontre du mot-cl private ou de la n de la dclaration. Les lignes 9 et 10 contiennent les dclarations des membres sonAge et sonPoids. La fonction principale du programme commence la ligne 13 et la ligne 15 dnit Frisky comme une instance de Chat : cest donc un objet de la classe Chat. Lge est dni la ligne 16, puis afch. Notez comment on accde aux membres de lobjet Frisky aux
Chapitre 6
145
lignes 16 et 18 : sonAge est accd laide du nom dobjet (Frisky ici), suivi dun point puis du nom du membre (sonAge ici). Essayez de mettre en commentaire (cest--dire de dsactiver) la ligne 8, puis recompilez le programme. La ligne 16 produira une erreur car sonAge nest plus public mais est revenu laccs par dfaut, comme les autres membres, et cet accs est priv.
Info
146
Le langage C++
soit gal 1 000, ces valeurs ne devraient donc pas tre autorises. Les mthodes daccs peuvent mettre en place ces types de contraintes et raliser bien dautres oprations. Le Listing 6.2 dclare la classe Chat qui comprend des donnes membres prives et des mthodes daccs publiques. Attention : ce code nest pas excutable sil est compil. Listing 6.2 : Exemples de mthodes daccs
1: 2: 3: 3a: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: // Dclaration de la classe Chat // Les donnes membres sont prives, les mthodes daccs // publiques permettent laffectation et la rcupration // des donnes prives class Chat { public: // mthodes daccs publiques unsigned int LireAge(); void DefAge(unsigned int Age); unsigned int LirePoids(); void DefPoids(unsigned int Poids); // fonctions membres publiques void Miauler(); // donnes membres prives private: unsigned int sonAge; unsigned int sonPoids; };
Cette classe contient cinq mthodes publiques. Les lignes 8 et 9, dune part, et 11 et 12, dautre part, correspondent respectivement aux mthodes daccs de sonAge et de sonPoids. Vous pouvez constater que la ligne 8 dclare une mthode pour obtenir lge et que la ligne 9 en dclare une permettant de le modier. Les lignes 11 et 12 font de mme pour le poids. Ces mthodes daccs permettent donc dinitialiser les valeurs des variables membres et de les lire. La ligne 15 dclare la fonction membre publique Miauler(). Il ne sagit pas dune fonction daccs ; elle ne lit pas ou ne modie pas une variable membre mais effectue une action pour la classe qui consiste, ici, afcher "Miaou", comme vous le verrez dans le programme du Listing 6.3. Les variables membres sont dclares aux lignes 19 et 20.
Chapitre 6
147
Pour dnir lge de Frisky, vous devez passer la valeur la mthode DefAge() :
Chat Frisky; Frisky.DefAge(5); // fixe lge laide de la mthode daccs
Plus loin, vous trouverez le code permettant de faire fonctionner SetAge et les autres mthodes. Dclarer des mthodes ou des donnes prives permet au compilateur de dtecter des erreurs de programmation avant quelles ne deviennent des bogues. Tout programmeur peut contourner les restrictions daccs sil le souhaite. Bjarne Stroustrup, linventeur de C++, a dailleurs lui-mme dclar que les mcanismes des contrles des accs protgeaient des accidents, mais pas de la fraude (ARM, 1990).
Le mot-cl class permet de dclarer de nouveaux types. Une classe est un ensemble de donnes membres, qui sont des variables de diffrents types, y compris dautres classes. La classe contient galement des fonctions (ou mthodes) qui permettent de manipuler les donnes de la classe ou deffectuer un certain nombre de services pour celle-ci. On peut comparer le processus de dnition des objets du nouveau type celui dune variable. On indique le type (le nom de la classe) suivi du nom de la variable (lobjet). Pour avoir accs aux fonctions et aux membres de cet objet, utilisez loprateur point ( .). Les sections dune classe peuvent tre dclares prives ou publiques en utilisant les motscls private ou public. Par dfaut, le contrle daccs est de type priv. Laccs ne change pas tant quun nouveau mot-cl nest pas prcis dans le code source. Les dclarations de classe se terminent par une accolade fermante et un point-virgule. Exemple 1
class Chat { public: unsigned int sonAge; unsigned int sonPoids; void Miauler(); }; Chat Frisky; Frisky.sonAge = 8; Frisky.sonPoids = 9; Frisky.Miauler();
148
Le langage C++
Exemple 2
class Voiture { public: // les cinq membres suivants sont publics void Demarrer(); void Accelerer(); void Freiner(); void DefAnnee(int annee); int LireAnnee(); private: // le reste est priv int sonAnnee; char sonModele [255]; }; // fin de la dclaration Voiture Star5; // cre une instance de Voiture int achat; // variable locale entire Star5.DefAnnee(84); // voiture achete en 84 achat = Star5.LireAnnee(); // extraction du millsime Star5.Demarrer(); // appel de la mthode Demarrer
Faire
Utiliser des mthodes daccs publiques. Accder des variables membres prives
Ne pas faire
Dclarer les variables membres comme publi-
Chapitre 6
149
4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47:
class Chat // dbut de la dclaration de la classe { public: // dbut de section publique int LireAge(); // mthode daccs void DefAge (int age); // mthode daccs void Miauler(); // mthode gnrale private: // dbut de section prive int sonAge; // variable membre }; // LireAge, mthode daccs publique // renvoie la valeur du membre sonAge int Chat::LireAge() { return sonAge; } // dfinition de DefAge, // fonction daccs publique // dfinit le membre sonAge void Chat::DefAge(int age) { // initialise la variable membre sonAge avec // la valeur passe par le paramtre age sonAge = age; } // dfinition de la mthode Miauler // renvoie: void // paramtres: Aucun // rle: afficher "Miaou" lcran void Chat::Miauler() { std::cout << "Miaou.\n"; } // crer un objet Chat, dfinir son ge, le faire miauler, // dire son ge, le faire miauler de nouveau. int main() { Chat Frisky; Frisky.DefAge(5); Frisky.Miauler();
150
Le langage C++
std::cout << "Frisky est un chat qui a "; std::cout << Frisky.LireAge() << " ans.\n"; Frisky.Miauler(); return 0; }
La classe Chat est dnie de la ligne 5 la ligne 13. la ligne 7, le mot-cl public demande au compilateur de crer un ensemble de membres publics. La ligne 8 dclare la mthode daccs publique LireAge(), qui a pour but dextraire la valeur de la variable membre sonAge dclare la ligne 12. la ligne 9, la fonction daccs publique DefAge() est suivie dun paramtre entier qui sera affect la variable membre sonAge. La ligne 10 dclare la mthode Miauler(). Il ne sagit pas dune mthode daccs. Il sagit plutt dune mthode gnrale qui afche "Miaou" lcran. La section private commence la ligne 11 : elle comprend la dclaration de la variable membre sonAge la ligne suivante. Enn, la dclaration de classe se termine par une accolade fermante et un point-virgule la ligne 13. La fonction membre LireAge() est dnie entre les lignes 17 et 20. Cette mthode ne prend pas de paramtre mais renvoie une valeur entire. Vous remarquerez, la ligne 17, quelle contient le nom de la classe suivi de deux caractres deux-points et du nom de la fonction. Le compilateur peut ainsi comprendre que la fonction LireAge() que vous dnissez ici est celle que vous avez dclare dans la classe Chat. Hormis sa ligne den-tte, la fonction LireAge() est cre comme nimporte quelle fonction. Cette fonction noccupe quune seule ligne et renvoie la valeur de sonAge. Notez que la fonction main() na pas accs la variable sonAge puisquelle est dnie comme membre priv de la classe Chat. En fait, la fonction principale ne peut avoir accs la variable sonAge qu travers la fonction publique LireAge() qui est une fonction membre de la classe Chat. Cela permet de connatre lge de Frisky. La fonction membre DefAge() est dnie la ligne 25. On voit que cette fonction prend une valeur entire, appele age, et quelle ne renvoie pas de rsultat car son type est void. SetAge() prend la valeur du paramtre age et lattribue sonAge la ligne 29. Cette fonction est membre de la classe Chat et, ce titre, peut avoir accs la variable prive sonAge. La mthode Miauler() de la classe Chat est dnie la ligne 36. Elle afche le mot "Miaou" lcran. Vous remarquerez la prsence du caractre \n qui permet de passer la
Chapitre 6
151
ligne suivante. Vous pouvez constater que Miauler() est dnie comme les mthodes daccs : elle commence par le type de son rsultat, suivi du nom de la classe, du nom de la fonction et de ses paramtres (aucun dans ce cas). Le programme commence vritablement la ligne 43 avec la fameuse fonction main(). la ligne 45, main() dclare un objet nomm Frisky, du type Chat. Dit autrement, main() dclare un Chat nomm Frisky. La ligne 46, affecte la valeur 5 la variable membre sonAge par lintermdiaire de la fonction daccs DefAge(). Pour appeler cette mthode, le programme utilise le nom de lobjet (Frisky) suivi de loprateur de membre (.) et du nom de la mthode (DefAge()). Les autres mthodes de lobjet peuvent tre appeles de la mme faon. Les termes fonction membre et mthode sont interchangeables.
Info
La fonction membre Miauler() est appele la ligne 47. la ligne suivante, lge du chat safche par le biais de la fonction daccs publique LireAge(). Enn, la fonction Miauler() est appele de nouveau la ligne 50. Mme si ces mthodes font partie dune classe (Chat) et sont utilises via un objet (Frisky), elles agissent comme les fonctions vues prcdemment.
Linitialisation consiste dnir une variable et lui affecter une valeur. Bien entendu, vous pouvez modier cette valeur ultrieurement, car linitialisation nest une opration ni irrversible ni dnitive. Elle garantit que la variable aura toujours une valeur significative. Comment initialiser les donnes membres dune classe ? Vous pouvez le faire en utilisant une fonction membre spciale appele constructeur, qui peut prendre autant de paramtres
152
Le langage C++
que ncessaire, mais qui ne renvoie pas de valeur (pas mme void). En fait, le constructeur est une mthode de la classe qui porte le mme nom que celle-ci. Lorsque vous dclarez un constructeur, il est souhaitable de dclarer galement un destructeur. Alors que les constructeurs crent et initialisent des objets de la classe, les destructeurs librent la mmoire ou les ressources qui ont t alloues (soit dans le constructeur, soit au cours de la vie de lobjet). Un destructeur porte toujours le nom de la classe prcd dun tilde (~). Il ne prend pas de paramtre et ne renvoie pas de valeur. Si vous deviez dclarer un destructeur pour la classe Chat, sa dclaration serait donc la suivante :
~Chat();
Lorsque vous dnissez un objet dune classe, le constructeur est appel. Si le constructeur de Chat attend deux paramtres, la dnition dun objet Chat sera de la forme :
Chat Frisky(5, 7);
Chapitre 6
153
Dans cet exemple, le premier paramtre pourrait tre son ge et le second son poids. Si le constructeur attend un seul paramtre, vous crirez :
Chat Frisky(3);
Si le constructeur ne prend pas de paramtres du tout (il sagit alors du constructeur par dfaut), supprimez les parenthses et crivez :
Chat Frisky;
Cest donc une exception la rgle qui veut que toutes les fonctions doivent tre suivies de parenthses, mme celles qui ne prennent pas de paramtres. Vous pouvez donc crire :
Chat Frisky;
car cela sera interprt comme un appel au constructeur par dfaut. Il ne comprend ni paramtres ni parenthses. Vous ntes pas oblig de vous contenter du constructeur par dfaut produit par le compilateur : vous pouvez crire le vtre, cest--dire un constructeur sans paramtres. Le corps de ce constructeur par dfaut peut ainsi contenir du code vous permettant dinitialiser lobjet comme vous le souhaitez. En ralit, il est toujours conseill de dnir un constructeur et dinitialiser les variables membres avec des valeurs par dfaut appropries an que lobjet se comporte toujours correctement. Lorsque vous dclarez un constructeur, noubliez non plus pas de dnir un destructeur mme sil ne fait rien. Bien que celui fourni par dfaut par le compilateur fonctionnera correctement, il est prfrable de le dnir soi-mme an de rendre le code plus clair. Le Listing 6.4 rcrit la classe Chat pour quelle utilise un constructeur qui nest pas celui par dfaut et qui se charge dinitialiser lge de lobjet avec la valeur quon lui a transmis. Lappel du destructeur est galement clairement indiqu. Listing 6.4 : Utilisation de constructeurs et de destructeurs
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: // Dclaration de constructeurs et de // destructeurs pour la classe Chat // Le programmeur cre un constructeur par dfaut #include <iostream> // pour cout class Chat // dbut de la dclaration de la classe { public: // dbut de section publique Chat(int initialAge); // constructeur ~Chat(); // destructeur int LireAge(); // fonction daccs void DefAge(int age); // fonction daccs
154
Le langage C++
13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59:
void Miauler(); private: int sonAge; }; // constructeur de Chat, Chat::Chat(int initialAge) { sonAge = initialAge; } Chat::~Chat() { }
// LireAge, fonction daccs publique // renvoie la valeur du membre sonAge int Chat::LireAge() { return sonAge; } // Dfinition de DefAge, // fonction daccs publique void Chat::DefAge(int age) { // affecter la variable membre sonAge // la valeur passe par le paramtre age sonAge = age; } // dfinition de la mthode Miauler // renvoie: void // paramtres: Aucun // rle: imprime "Miaou" lcran void Chat::Miauler() { std::cout << "Miaou.\n"; } // Crer un objet chat, dfinir son ge, le faire miauler, // afficher son ge, le faire miauler de nouveau. int main() { Chat Frisky(5); Frisky.Miauler(); std::cout << "Frisky est un chat qui a ";
Chapitre 6
155
std::cout << Frisky.LireAge() << " ans.\n"; Frisky.Miauler(); Frisky.DefAge(7); std::cout << "Maintenant Frisky a "; std::cout << Frisky.LireAge() << " ans.\n"; return 0;
Le Listing 6.4 ressemble trangement au listing prcdent. la ligne 9, pourtant, le constructeur prend en paramtre un entier, tandis que la ligne 10 dclare le destructeur, qui ne prend pas de paramtre. Les destructeurs ne prennent jamais de paramtres et, comme les constructeurs, ils ne renvoient aucune valeur (pas mme void). Les lignes 19 22 montrent limplmentation du constructeur, qui ressemble celle de la mthode daccs DefAge(). Comme vous pouvez le constater, le nom de classe prcde le nom du constructeur, ce qui identie la mthode, Chat() ici, comme une partie de la classe Chat. Cest un constructeur, qui ne renvoie donc pas de valeur, mme pas void. la ligne 21, il prend toutefois une valeur en paramtre qui est affecte la donne membre sonAge. Les lignes 24 26 dcrivent limplmentation du destructeur ~Chat(). Pour linstant, cette fonction ne fait rien mais vous devez inclure sa dnition si vous lavez dclare dans la dclaration de la classe. Comme le constructeur et les autres mthodes, elle est galement prcde du nom de la classe. La ligne 57 dnit lobjet Chat Frisky en passant la valeur 5 au constructeur de cet objet. Il est inutile dappeler DefAge() car Frisky a t cr avec la valeur 5 qui a t affecte la variable membre sonAge la ligne 60. la ligne 62, cette variable est modie pour valoir 7. Cest cette valeur qui safche lcran deux lignes plus loin.
Faire
Initialiser des objets laide de constructeurs. Ajouter un destructeur si vous ajoutez un
Ne pas faire
Attribuer un type de rsultat aux construc-
constructeur.
156
Le langage C++
Cette ligne dclare une mthode membre constante appele Fonction() qui ne prend pas de paramtre et qui ne renvoie rien. Vous savez quelle ne changera aucune des donnes membres de la mme classe car elle a t dclare const. Les mthodes daccs qui ne font que renvoyer des valeurs sont souvent dclares constantes. Nous avons vu plus haut que la classe Chat contenait deux fonctions daccs :
void DefAge(int Age); int LireAge();
La fonction DefAge() ne peut pas tre constante puisquelle modie la variable membre sonAge. En revanche, LireAge() peut et devrait tre constante car elle ne modie pas du tout les objets de la classe : elle ne fait que renvoyer la valeur de sonAge. Il serait donc prfrable de dclarer ces fonctions sous la forme suivante :
void DefAge(int Age); int LireAge() const;
Si une fonction modie la valeur de lun des membres dun objet alors quelle est dclare comme constante, le compilateur produira une erreur. Si, par exemple, la fonction LireAge() devait mmoriser le nombre de fois o un chat a d dire son ge, une erreur de compilation serait produite, puisque vous modieriez alors lobjet Chat lorsque cette mthode serait appele. Les mthodes constantes sont trs intressantes puisquelles permettent au compilateur de dtecter les erreurs avant lexcution du programme. Cest pourquoi il est souhaitable den dclarer aussi souvent que possible dans les applications.
Interface et implmentation
On appelle client toute partie dun programme qui cre et utilise les objets dune classe. Linterface publique (la dclaration de la classe) peut tre considre comme le contrat qui la lie aux clients. Ce contrat prcise le comportement de la classe.
Chapitre 6
157
La dclaration de la classe Chat, par exemple, indique que lge de chaque chat peut tre initialis dans son constructeur, modi par sa fonction daccs DefAge(), puis lu par sa fonction daccs LireAge(). Vous promettez galement que chaque chat saura miauler grce la fonction Miauler(). Notez que vous ne dites rien de la variable membre sonAge dans linterface publique ; il sagit dun dtail dimplmentation qui ne fait pas partie de votre contrat. Vous fournirez un ge (LireAge()) et le dnirez (DefAge()), mais le mcanisme (sonAge) reste invisible. Si vous dnissez LireAge() comme une fonction constante, ce qui est souhaitable, le contrat stipule galement que son appel ne modiera pas lobjet Chat sur lequel elle est appele. C++ tant fortement typ, le compilateur fera respecter ces contrats en produisant une erreur chaque fois que le programme tentera de les violer. Le Listing 6.5 montre un programme qui ne se compilera pas cause de ses violations de contrat.
ntion Atte
// constructeur de Chat, Chat::Chat(int initialAge) { sonAge = initialAge; std::cout << "Constructeur de Chat \n"; }
158
Le langage C++
23a: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63:
// destructeur, ne ralise aucune action Chat::~Chat() { std::cout << "Destructeur de Chat \n"; } // LireAge, fonction constante //, mais il y a violation! int Chat::LireAge() const { return (sonAge++); // violation de const! } // dfinition de DefAge, // fonction daccs publique void Chat::DefAge(int age) { // dfinir la variable membre sonAge la // valeur passe par le paramtre age sonAge = age; } // dfinition de la mthode Miauler // renvoie: void // paramtres: Aucun // rle: Affiche "Miaou" lcran void Chat::Miauler() { std::cout << "Miaou.\n"; } // dmonstration des diffrentes violations de // linterface, et erreurs de compilation correspondantes int main() { Chat Frisky; // ne correspond pas la dclaration Frisky.Miauler(); Frisky.Aboyer(); // la mthode nest pas dclare Frisky.sonAge = 7; // sonAge est prive return 0; }
Le programme ne produit pas de rsultat puisquil nest pas compil. La ligne 10 dclare LireAge() comme une fonction daccs constante alors quelle incrmente la variable membre sonAge la ligne 32. Le compilateur a dtect une erreur et la signale.
Chapitre 6
159
Miauler() nest pas dclare comme une mthode constante en ligne 12, ce qui en soi ne constitue pas une erreur mais est une mauvaise habitude de la part du programmeur. En effet, cette fonction ne doit en rien affecter les variables membres de la classe Chat. Cest pourquoi il est prfrable de la dclarer comme mthode constante. Lobjet Frisky de la classe Chat est cr la ligne 58. Le constructeur associ comprend un paramtre entier, or on nen passe aucun : le compilateur dtecte une erreur. Si vous fournissez ne serait-ce quun constructeur, le compilateur nen crera pas. Cela signie que si vous crez quelque part un constructeur avec un paramtre, vous naurez pas de constructeur par dfaut automatique et vous devrez lcrire vous-mme.
Info
La ligne 60 appelle la mthode Aboyer(). Or, celle-ci na pas t dclare. La variable sonAge reoit la valeur 7 la ligne 61, cest--dire dans le corps du programme, ce qui constitue une erreur puisquil sagit dune donne membre prive.
Pourquoi dtecter des erreurs laide du compilateur ? Il est illusoire desprer crire du code sans erreurs. Toutefois, il est possible den viter un certain nombre, en les dtectant et en les rsolvant ds le dbut du processus. Les erreurs de compilation sont la bte noire des programmeurs. Pourtant, leur dtection rvle la qualit du compilateur. En effet, un langage faiblement typ permet deffectuer des oprations non autorises, ce qui produira des erreurs au moment de lexcution. Pire encore, le test napportera que peu daide pour dcouvrir les erreurs, car il existe trop de possibilits dans les vrais programmes pour avoir lespoir de les tester tous. Les erreurs de compilation sont prfrables aux erreurs dexcution, car elles peuvent tre plus aisment repres et corriges. On reconnat la qualit dun programmeur sa capacit dvelopper des applications exemptes derreurs dexcution. Vous ntes pas infaillible. Cest pourquoi le compilateur est charg de vrier la cohrence du code avant de crer le chier excutable.
160
Le langage C++
La dnition doit se trouver dans un chier dont le chemin daccs doit tre connu du compilateur. La plupart des compilateurs exigent que ce chier se termine par lextension .c ou .cpp cest cette dernire qui a t adopte dans ce livre. Pour connatre les extensions prises en charge par votre compilateur, reportez-vous sa documentation technique. De nombreux compilateurs considrent que les chiers dont les noms se terminent par .c sont des chiers sources C et que ceux qui se terminent par .cpp sont des chiers sources C++. Vous pouvez utiliser lune ou lautre de ces extension, mais lutilisation de .cpp rduira les confusions possibles.
Info
Il est possible de placer galement la dclaration dans ce chier, bien que cette pratique soit peu recommande en POO. Par convention, la plupart des programmeurs crivent la dclaration dans un chier en-tte portant le mme nom que le chier source, mais suivi de lextension .h, .hp ou .hpp. Dans cet ouvrage, nous avons opt pour la dernire. Au besoin, consultez la documentation de votre compilateur. Par exemple, la classe Chat est dclare dans le chier en-tte Chat.hpp et les dnition des mthodes se trouvent dans le chier Chat.cpp. Pour associer le chier en-tte au chier .cpp, ajoutez linstruction suivante au dbut de Chat.cpp :
#include "Chat.hpp"
Le compilateur insrera alors le code du chier Chat.hpp comme sil avait t saisi dans le chier source. Notez que certains compilateurs exigent que la casse des caractres soit la mme dans linstruction #include et dans le systme de chiers. Pourquoi sparer le contenu de votre chier .hpp et de votre chier .cpp pour nalement les runir ? Le plus souvent, les clients de votre classe ne se soucient pas des dtails dimplmentation. La lecture du chier den-tte leur fournit toutes les informations dont ils ont besoin. En outre, vous pouvez galement inclure le mme chier .hpp dans plusieurs chiers .cpp.
La dclaration dune classe est appele interface, parce quelle contient un certain nombre dinformations prcieuses pour lutilisateur. Il sagit, entre autres, de la structure de la classe, du type des donnes utilises et des noms des fonctions appeles. Linterface gure gnralement dans un chier .hpp, plus connu sous le nom de chier en-tte. La dnition dune fonction renseigne le compilateur sur le comportement de cette dernire. Elle est appele implmentation de la mthode et gure dans un chier .cpp. Par ailleurs, les dtails de limplmentation ne concernent que le crateur de la classe ; les clients de la classe (les parties du programme qui lutilisent) nont pas besoin de connatre la faon dont les mthodes sont implmentes.
Info
Chapitre 6
161
Implmentation en ligne
Comme les fonctions classiques, les mthodes des classes peuvent tre dclares en ligne. Dans ce cas, le mot-cl inline doit gurer avant le type du rsultat. Voici un exemple, avec la fonction LirePoids() :
inline int Chat::LirePoids() { return sonPoids; } // renvoie la donne membre Poids
Il est galement possible de dnir une fonction dans la dclaration de la classe, ce qui la dnit automatiquement en ligne. Exemple :
class Chat { public: int LirePoids() const { return sonPoids; } void DefPoids(int unPoids); }; // en ligne
Notez la syntaxe de la dnition de LirePoids(). Le corps de la fonction en ligne commence immdiatement aprs la dclaration de la mthode ; il ny a pas de pointvirgule aprs les parenthses. Comme pour nimporte quelle fonction, la dnition est comprise entre une accolade ouvrante et une accolade fermante. Les tabulations et les espaces ninuant pas sur son droulement, la classe pourrait tre dnie ainsi :
class Chat { public: int LirePoids() const { return sonPoids; } void DefPoids(int unPoids); }; // en ligne
Les Listings 6.6 et 6.7 recrent la classe Chat, mais la dclaration se trouve dsormais dans le chier en-tte Chat.hpp et limplmentation des fonctions dans le chier Chat.cpp. Notez que les fonctions daccs et la fonction Miauler() sont dclares en ligne dans le Listing 6.7.
162
Le langage C++
// constructeur
// Crer un chat, dfinir son ge, le faire miauler, // afficher son ge, le faire miauler de nouveau. int main() { Chat Frisky(5); Frisky.Miauler(); std::cout << "Frisky est un chat qui a "; std::cout << Frisky.LireAge() << " ans.\n"; Frisky.Miauler(); Frisky.DefAge(7); std::cout << "Maintenant Frisky a "; std::cout << Frisky.LireAge() << " ans.\n"; return 0; }
Chapitre 6
163
Le code prsent dans les deux listings ci-dessus ressemble trangement celui du Listing 6.4 mais on a dclar ici trois mthodes en ligne dans le chier en-tte Chat.hpp (Listing 6.6). La fonction LireAge() est dclare et implmente en ligne la ligne 6 de Chat.hpp,. Aux lignes 7 et 8, les autres fonctions sont galement dclares en ligne. Leur comportement na cependant pas t modi. La ligne 4 de Chat.cpp (Listing 6.7) contient la directive #include "Chat.hpp" qui inclut le contenu du chier Chat.hpp dans le chier source. Le prcompilateur va donc inclure le chier Chat.hpp comme si son code avait t tap partir de la ligne 5 de ce programme. Cette technique permet de rpartir les dclarations et limplmentation dans des chiers spars tout en les rendant disponibles pour le compilateur, ce qui est une pratique trs courante en C++. Les dclarations de classe sont gnralement enregistres dans un chier .hpp qui est ensuite inclus dans le chier .cpp associ. Les lignes 18 29 reproduisent la fonction main() du Listing 6.4, ce qui montre quavoir mis ces fonctions en ligne ne modie pas leurs fonctionnement.
Classes imbriques
Il nest pas rare de crer une classe complexe en dclarant plusieurs classes simples et en les incluant dans la dclaration de la classe complexe. Vous pouvez, par exemple, dclarer une classe Roue, une classe Moteur, une classe Transmission, etc., puis les combiner dans la classe Voiture. Ce processus tablit une relation "a-un" ou relation dappartenance. Une voiture a un moteur, elle a des roues et elle a une transmission. Prenons un deuxime exemple. Un rectangle est form de lignes, chaque ligne tant dnie par deux points. Dans lespace, un point est identi laide dune coordonne x et dune coordonne y. Le Listing 6.8 contient la dclaration complte dune classe Rectangle, enregistre dans le chier Rectangle.hpp. Un rectangle tant dni par quatre lignes traces entre quatre points dintersection et chaque point correspondant une position dans un graphique, il faut dclarer en premier la classe Point destine recevoir les coordonnes x et y de chaque point. Les deux classes sont implmentes dans le Listing 6.9.
164
Le langage C++
class Rectangle { public: Rectangle (int haut, int gauche, int bas, int droite); ~Rectangle () {} int int int int GetHaut() const { return sonHaut; } GetGauche() const { return saGauche; } GetBas() const { return sonBas; } GetDroite() const { return saDroite; } GetHautGauche() const { return sonHautGauche ; } GetBasGauche() const { return sonBasGauche; } GetHautDroite() const { return sonHautDroite; } GetBasDroite() const { return sonBasDroite; } SetHautGauche (Point pt) { sonHautGauche = pt; } SetBasGauche (Point pt) { sonBasGauche= pt; } SetHautDroite (Point pt) { sonHautDroite= pt; } SetBasDroite (Point pt) { sonBasDroite= pt; } SetHaut(int haut) { sonHaut = haut; } SetGauche (int gauche) { saGauche = gauche; } SetBas (int bas) { sonBas = bas; } SetDroite(int droite) { saDroite = droite; }
Point Point Point Point void void void void void void void void
Chapitre 6
165
46: 47: 48: 49: 50: 51: 52: 53: 54: 55:
}; // fin de Rectangle.hpp
// calculer la surface du rectangle en trouvant les angles, // dterminer la longueur et la largeur et multiplier int Rectangle::GetSurface() const { int Largeur = saDroite - saGauche; int Hauteur = sonHaut - sonBas; return (Largeur * Hauteur); } int main()
166
Le langage C++
34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44:
{ // initialiser une variable locale Rectangle Rectangle MonRectangle (100, 20, 50, 80 ); int Surface = MonRectangle.GetSurface(); std::cout << "Surface: " << Surface << "\n"; std::cout << "Coordonne X coin suprieur gauche: "; std::cout << MonRectangle.GetHautGauche().GetX(); return 0; }
Les lignes 3 14 de Rectangle.hpp (Listing 6.8) dclarent la classe Point ; cest elle qui va recevoir les coordonnes x et y. Comme vous pouvez le constater, ce programme ne fait gure appel des objets Point ; cependant, ceux-ci peuvent servir dautres mthodes de trac. Certains compilateurs signalent une erreur si vous dclarez une classe appele Rectangle. Ceci est gnralement d lexistence dune classe interne nomme Rectangle. Dans ce cas, renommez simplement la classe monRectangle, par exemple
Info
Les variables membres sonX et sonY sont dnies la ligne 12 et la ligne 13. mesure que la valeur de la coordonne x augmente, on se dplace vers la droite et on se dplace vers le haut lorsque la valeur de la coordonne y augmente. Bien entendu, il est possible dutiliser un autre systme : dans certains programmes de fentrage, la fentre se dplace vers le bas mesure que la coordonne y augmente, par exemple. Pour lire et dnir les points x et y, la classe Point fait appel aux fonctions daccs en ligne dclares aux lignes 7 10. En outre, elle utilise le constructeur et le destructeur par dfaut, ce qui signie que vous devez dnir les coordonnes explicitement. La ligne 17 commence la dclaration de la classe Rectangle. Les quatre angles du rectangle sont alors dnis. Le constructeur de Rectangle prend en charge quatre entiers appels respectivement haut, gauche, bas et droite. Ces paramtres sont affects aux quatre variables membres (voir le Listing 6.9) an de dnir le rectangle.
Chapitre 6
167
Outre les fonctions daccs dcrites plus haut, la classe Rectangle possde une fonction GetSurface() (voir ligne 43). La surface nest pas stocke dans une variable, mais calcule aux lignes 28 et 29 du Listing 6.9. Pour cela, la fonction GetSurface() dtermine la hauteur et la largeur du rectangle, puis multiplie ces deux valeurs. Pour extraire la coordonne x de langle suprieur gauche du rectangle, vous devez lire la valeur x du point HautGauche. La fonction GetHautGauche() tant une mthode de Rectangle, elle a accs directement aux donnes prives, parmi lesquelles sonHautGauche. Cette valeur tant un Point et la valeur de sonX tant prive, elle ne peut pas y accder directement et doit faire appel la fonction daccs publique GetX() pour obtenir cette coordonne. Le programme commence rellement la ligne 33 du Listing 6.9,. Vous pouvez constater que la mmoire nest pas alloue avant la ligne 36. Pour le moment, le compilateur sait crer un point et un rectangle, au cas o il en aurait besoin. la ligne 36, on dnit un rectangle en passant les valeurs pour les quatre paramtres haut, gauche, bas et droite de son constructeur. la ligne 38, le programme cre la variable locale Surface, de type int. Elle reoit la valeur de la surface, renvoye par la fonction GetSurface() de la classe Rectangle. Un client de la classe Rectangle pourrait crer un objet Rectangle et dterminer sa surface sans connatre limplmentation de la mthode LireSurface(). Le chier Rectangle.hpp est prsent dans le Listing 6.8. En examinant simplement la dclaration de la classe Rectangle, le programmeur sait que GetSurface() renvoie un entier. Le calcul de la surface ne concerne pas lutilisateur de cette classe. En fait, lauteur de Rectangle pourrait modier le corps de la mthode GetSurface() sans inuer sur les programmes qui utilise cette classe, du moment quelle renvoie un entier. La ligne 42 du Listing 6.9 peut sembler trange, mais rchissez ce qui se passe. Cette ligne de code fournit les coordonnes x du point suprieur gauche de votre rectangle. Dans cette ligne, vous appelez la mthode GetHautGauche() de votre rectangle, qui vous renvoie un Point. Vous voulez obtenir les coordonnes x partir de ce Point. Vous avez vu que la mthode daccs pour une coordonne x dans la classe Pointsappelle GetX(). La ligne 42 rassemble donc simplement les mthodes daccs GetHautGauche() et GetX() :
MonRectangle.GethautGauche().GetX();
Cet appel permet dobtenir la coordonne x du point suprieur gauche de lobjet MonRectangle.
168
Le langage C++
Quelle est la diffrence entre une dclaration et une dnition ? Une dclaration introduit un nom, mais ne rserve pas demplacement en mmoire. Une dnition alloue la mmoire correspondante. quelques rares exceptions prs, toutes les dclarations sont aussi des dnitions. Les exceptions les plus notables sont la dclaration dune fonction globale (un prototype) et la dclaration dune classe (gnralement dans un chier en-tte).
Les structures
Un proche cousin du mot-cl class est le mot-cl struct qui permet de dclarer une structure. En C++, une structure est une classe dont tous les membres sont publics par dfaut. Une dclaration de structure comprend des donnes membres et des fonctions, comme une dclaration de classe. En fait, si vous respectez les bonnes habitudes de programmation en dclarant toujours explicitement chaque section de classe laide des mots-cls public et private, il ny aura aucune diffrence entre les deux. Modiez le Listing 6.8 :
la ligne 3, remplacez class Point par struct Point. la ligne 17, remplacez class Rectangle par struct Rectangle.
Lancez nouveau le programme et comparez les rsultats obtenus. Il ne devrait pas y avoir de diffrence. Vous vous demandez alors peut-tre pourquoi il existe deux mots cls pour la mme chose. Il sagit dun accident de lhistoire. C++ a t construit comme une extension du langage C, qui dispose de structures, bien quelles ne puissent pas contenir de mthodes. Bjarne Stroustrup, le crateur de C++, a utilis le mot-cl class pour dsigner ces structures tendues et la nouvelle visibilit par dfaut de leurs membres. Cela a galement permis de continuer utiliser la vaste bibliothque des fonctions C dans les programmes C++.
Faire
Dclarer la classe dans un chier en-tte
Ne pas faire
Poursuivre sans avoir compris le fonctionne-
possible.
Chapitre 6
169
Questions-rponses
Q Quelle est la taille dun objet de la classe ? R Elle est dtermine par la somme des tailles des diffrentes variables membres.Les mthodes noccupent quun petit espace mmoire, utilis pour conserver des informations sur lemplacement de la mthode (un pointeur). Certains compilateurs alignent les variables en mmoire de sorte que des variables de deux octets consomment en ralit un espace suprieur. Pour en savoir plus sur la gestion de la mmoire, reportez-vous au manuel utilisateur de votre compilateur. Q Si je dclare une classe Chat comprenant un membre priv sonAge, puis que je dnis deux objets de cette classe, Frisky et Mistigri. Mistigri a-t-il accs la variable membre sonAge de Frisky ? R Les diffrentes instances de la classe peuvent accder aux donnes non publiques de lautre, pas leurs membres privs. Frisky et Mistrigri tant toutes les deux des instances de Chat, les fonctions membres de Frisky peuvent accder aux donnes de Frisky, mais pas celles de Mistigri. Q Pourquoi ne pas rendre toutes les donnes membres publiques ? R Les donnes membres prives sont propres une classe, ce qui rend leur traitement et leur stockage totalement transparents pour les clients. Par exemple, les clients de la classe Chat nont pas accs directement limplmentation de la mthode LireAge(). Peu importe le traitement effectu, lge du chat sera rcupr. Le programmeur de la classe Chat peut changer ensuite son implmentation sans obliger tous les utilisateurs modier aussi leurs programmes. Q Si lutilisation dune fonction const pour modier une classe produit une erreur de compilation. Pourquoi ne pas supprimer ce mot-cl pour viter des erreurs ? R Si votre fonction membre ne doit logiquement pas modier la classe laquelle elle appartient, la prsence du mot-cl const permet de dtecter des erreurs. Par exemple, LireAge() nest pas suppose modier la classe Chat. Or si limplmentation contient la ligne suivante :
if (sonAge = 100) cout << "Tiens, vous tes centenaire!\n";
et que vous dnissez la fonction LireAge() comme constante, le compilateur dtectera lerreur. En effet, linstruction if tait cense vrier si sonAge tait gal 100 alors que vous affectez en ralit 100 cette variable. Le compilateur a trouv lerreur, car vous tentez de modier une mthode constante.
170
Le langage C++
Une erreur de ce type est difcile trouver en parcourant simplement les nombreuses lignes dun chier source. Le programme peut mme sembler tourner correctement alors que sonAge a reu une valeur incorrecte. Cela posera un problme tt ou tard. Q Lutilisation des structures est-elle encore ncessaire en C++ ? R Certains programmeurs C++ rservent le mot-cl struct aux classes dpourvues de fonctions. Cette habitude, hrite du langage C, est proscrire car une structures dnie aujourdhui peut trs bien ncessiter lajout ultrieur de mthodes ; vous devriez alors modier cette struct en class ou violer la rgle que vous vous tes x. La seule bonne raison dutiliser une structure est lorsque vous devez appeler une fonction C existante qui exige un struct particulier. Q Certaines personnes travaillant avec la programmation oriente objet utilisent le terme "instanciation". Que signie-t-il ? R Linstanciation nest quun mot la mode dcrivant la cration dun objet partir dune classe. Un objet spcique dni comme tant du type dune classe constitue une instance de cette classe.
Chapitre 6
171
Exercices
1. crivez la dclaration de la classe Personnel contenant les donnes membres sonAge, sonAnciennete et sonSalaire. 2. Modiez la dclaration de la classe Personnel de sorte que ses donnes membres soient prives et que des mthodes daccs puissent lire et mettre jour chacune delles. 3. crivez un programme qui cre deux objets de la classe Personnel. Pour chacun, dnissez lge, lanciennet et le salaire, puis afchez ces valeurs. Vous devrez aussi ajouter le code des mthodes daccs. 4. En vous inspirant de lexercice prcdent, crivez le code dune mthode qui afche le salaire de chaque employ, arrondi la centaine deuros la plus proche. 5. Modiez de nouveau la classe Personnel de faon initialiser les donnes membres sonAge, sonAnciennete et sonSalaire lors de la cration dun objet. 6. CHERCHEZ LERREUR : pourquoi cette dclaration est-elle errone ?
class Carre { public: int Cote; }
7. CHERCHEZ LERREUR : pourquoi cette dclaration de classe nest-elle pas trs utile ?
class Chat { int GetAge() const; private: int sonAge; };
172
Le langage C++
7
Droulement dun programme
Au sommaire de ce chapitre
La nature et le rle des boucles La cration des diffrentes boucles Une alternative aux instructions imbriques ifelse
Un programme repose principalement sur des branchements et des boucles. Au Chapitre 4, vous avez appris comment le programme effectue un branchement laide de linstruction if.
Les boucles
En programmation, nombreux sont les traitements rptitifs, cest--dire qui traitent plusieurs fois les mmes groupes de donnes. Ils peuvent sexcuter par rcursivit (prsente au Chapitre 5) ou par itration. Litration consiste rpter plusieurs fois des instructions pour traiter des informations. La principale mthode ditration est la boucle.
174
Le langage C++
La ligne 8 initialise le compteur zro. Une tiquette appele boucle marque le dbut de la boucle (ligne 9). Le compteur est incrment et sa nouvelle valeur apparat lcran en ligne 11. La ligne 12 teste la valeur du compteur : si elle est infrieure 5, la condition de linstruction if est vrie et linstruction goto sexcute. Le programme revient sur ltiquette boucle de la ligne 9 et litration continue jusqu ce que le compteur soit
Chapitre 7
175
gal 5. En ce cas, le programme quitte la boucle et excute la ligne 13 qui indique que le traitement est termin.
Linstruction goto Elle doit tre suivie du nom de ltiquette et elle provoque un saut inconditionnel vers cette dernire. Exemple
if (valeur > 10) goto fin; if (valeur < 10) goto fin; cout << "valeur = 10!"; fin: cout << "Fin";
Dautres instructions de boucle plus sophistiques permettent dviter lusage de goto : for, while et do...while.
176
Le langage C++
5: int main() 6: { 7: using namespace std; 8: int compteur = 0; // initialiser la condition 9: 10: while(compteur < 5) // condition toujours vraie? 11: { 12: compteur++; // corps de la boucle 13: cout << "Compteur: " << compteur << endl; 14: } 15: 16: cout << "Termin. Le compteur vaut: " 16a: << compteur << endl; 17: return 0; 18: }
Ce programme illustre le fonctionnement dune boucle while. La ligne 8 initialise zro la variable entire compteur qui est ensuite employe dans la condition de la boucle. La condition est teste : si elle est vraie, linstruction gurant dans le corps de litration sexcute. Dans cet exemple, le test est effectu la ligne 10. Si la valeur du compteur est infrieure 5, lincrmentation a lieu la ligne 12 ; le rsultat est afch la ligne suivante. Dans le cas contraire, le corps de la boucle (lignes 11 14) nest pas excut. Le programme se branche directement sur la ligne 15. Notez quil vaut toujours mieux utiliser des accolades autour du bloc excut par une boucle, mme lorsquil ne contient quune ligne de code. Cela permet dviter lerreur classique consistant placer par inadvertance un point-virgule juste aprs la condition, ce qui cre une boucle sans n, comme ici :
int compteur = 0; while ( compteur < 5 ); compteur++;
Chapitre 7
177
while ( condition ) instruction; condition est une expression C++ tandis que instruction correspond une instruction ou un bloc dinstructions C++ valide. Lorsque la condition est vrie, linstruction sexcute, puis la condition est de nouveau teste. Ce traitement se rpte jusqu ce que la condition soit fausse. La boucle while prend alors n et le programme lit linstruction situe immdiatement aprs linstruction ou le bloc dinstructions. Exemple : // incrmentation du compteur jusqu 10 int x = 0; while (x < 10) cout << "X: " << x++;
178
Le langage C++
20: 21: 22: 22a: 23: 24: 25: 26: 27: 28: 29: 29a: 30: 31: }
while (petit < grand && petit < MAXPETIT) { // crire un point chaque incrment de 5000 if (petit % 5000 == 0) cout << "."; petit++; grand -= 2; } cout << "\nPetit nombre: " << petit << ", Grand nombre: " << grand << endl; return 0;
Il sagit dun jeu. Entrez deux nombres, le premier tant infrieur au second. Le programme incrmente de un point le petit nombre et dcrmente de deux points le grand nombre. Lobjectif est de dterminer le moment auquel les deux valeurs se rejoignent. La saisie a lieu de la ligne 12 la ligne 15. La ligne 20 dnit une boucle while qui sexcute tant que les deux conditions suivantes sont vraies :
petit < grand (le petit nombre est infrieur au grand). petit ne dpasse pas la taille maximale autorise pour un entier court (MAXPETIT).
La ligne 22 calcule la valeur de petit modulo 5 000. Ce calcul ne modie pas la variable, mais extrait simplement le reste de la division. Sil est gal 0, petit est un multiple de 5 000 et un point (.) apparat lcran. La ligne 25 incrmente petit et la ligne suivante dcrmente deux fois grand. Si lune des deux conditions est fausse, la boucle while prend n et le programme se poursuit aprs laccolade fermant la boucle la ligne 27. Pour en savoir plus sur loprateur modulo (%) et les oprateurs logiques, reportez-vous au Chapitre 3.
Info
Chapitre 7
179
180
Le langage C++
26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 45a: 46: 47: }
while (petit < grand && petit < MAXPETIT) { petit++; if (petit % saut == 0) // sauter la dcrmentation? { cout << "Saut " << petit << endl; continue; } if (grand == cible) // galit avec la cible? { cout << "Cible atteinte!"; break; } grand -= 2; } // fin de la boucle while
cout << "\nPetit nombre: " << petit << ", grand nombre: " << grand << endl; return 0;
Dans cet exemple, lutilisateur a perdu, car petit a dpass la valeur de grand avant que la valeur cible (6) ne soit atteinte. La ligne 26 teste les conditions de litration. Si petit est infrieur grand mais quil nest pas suprieur la valeur maximale autorise, le corps de la boucle sexcute. La ligne 30 calcule le reste de la division de la valeur de petit par saut. Si petit est un multiple de saut, linstruction continue sexcute et le traitement revient donc au dbut de la boucle, en sautant le test de la cible et la dcrmentation de grand.
Chapitre 7
181
La ligne 36 compare les valeurs de cible et de grand ; si elles sont gales, lutilisateur a gagn. Le programme afche alors un message et linstruction break est atteinte et excute. Elle provoque une sortie immdiate de la boucle et lexcution se poursuit la ligne 44. Les instructions continue et break doivent tre utilises avec prcaution pour les mmes raisons que linstruction goto. Les programmes qui changent brutalement de direction sont plus difciles relire ; lutilisation exagre de continue et break peut rendre illisible une boucle while, mme simple. Le besoin de placer une instruction break dans une boucle indique souvent que la condition de n de la boucle na pas t dnie avec lexpression boolenne qui convient. Il vaut souvent mieux utiliser une instruction if pour sauter certaines lignes plutt quune instruction break.
Info
Linstruction continue Linstruction continue; oblige le programme revenir au dbut dune boucle while, dowhile ou for. Le Listing 7.4 donne un exemple de son utilisation.
Linstruction break
182
Le langage C++
La ligne 9 congure la boucle while avec une condition qui ne pourra jamais tre fausse. La variable compteur est incrmente la ligne 11, puis le programme vrie si sa valeur est suprieure 10. Dans le cas contraire, la boucle sexcute nouveau. En revanche, si le compteur est suprieur 10, linstruction break interrompt la boucle et va directement la ligne 15. Le rsultat est alors afch. Bien quil fonctionne correctement, ce programme est un bon exemple du mauvais choix dun outil pour effectuer une tche. En effet, il est prfrable, dans ce cas, de tester la valeur du compteur dans la condition while. Les boucles sans n de type while(true) risquent de ger votre ordinateur si la condition de sortie nest jamais atteinte. Il est donc conseill de les utiliser avec prcaution et de les tester soigneusement.
ntion Atte
Avec C++, vous pouvez raliser une mme tche de plusieurs faons. La vritable difcult consiste choisir loutil adapt.
Chapitre 7
183
Faire
Utiliser une boucle while pour rpter une
Ne pas faire
Utiliser linstruction goto. Oublier la diffrence entre continue et
avec prcaution.
Vrier que litration peut prendre n.
184
Le langage C++
Lutilisateur entre une valeur de dpart (ligne 10) qui est copie dans la variable entire compteur dont la valeur est teste la ligne 12, puis dcrmente dans le corps de la boucle while. Dans le rsultat, vous pouvez constater qu la premire excution du programme, lutilisateur a entr 2 et que le corps de la boucle sest excut deux fois. Dans le second exemple, lutilisateur a tap 0. La valeur du compteur a t teste la ligne 12. La condition tait fausse puisque compteur ntait pas suprieur 0. La squence dinstructions ne sest donc pas excute et aucun bip ne sest afch lcran. Comment faire pour afcher au moins un bip ? Vous pouvez placer une instruction if avant la boucle while an daffecter une valeur minimale compteur an dentrer dans la boucle :
if (compteur < 1) compteur = 1; // affecte une valeur minimale
Linstruction do...while
Une boucle do...while garantit que les instructions de la boucle sexcuteront au moins une fois, car le corps de la boucle est excut avant le test de la condition. Le code du Listing 7.7 correspond au programme du listing prcdent, mais utilise une boucle do...while. Listing 7.7 : Exemple de boucle do...while
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 17a: 18: 19: // Listing7.7 // Boucle do while #include <iostream> int main() { using namespace std; int compteur; cout << "Combien de bips voulez-vous afficher? "; cin >> compteur; do { cout << "Bip!\n"; compteur--; } while (compteur >0 ); cout << "Compteur la sortie de la boucle: " << counter << endl; return 0; }
Chapitre 7
185
Comme le programme prcdent, le Listing 7.7 afche le mot "Bip" sur la console le nombre de fois indiqu. Par contre, il lafchera toujours au moins une fois. La ligne 10 demande lutilisateur dentrer une valeur de dpart qui est stocke dans la variable entire compteur. Le corps de la boucle tant excut avant le test de la condition, un bip est afch la ligne 14. La ligne suivante dcrmente le compteur et la ligne 16 teste la condition. Si elle est vrie, le programme retourne au dbut de la boucle, la ligne 14. Dans le cas contraire, cest la ligne 17 qui sexcute. Les instructions continue et break remplissent le mme rle que dans une boucle while. La seule diffrence entre une boucle while et une boucle dowhile concerne le moment o la condition est teste.
// incrmentation jusqu 10 int x = 0; do cout << "X: " << x++; while (x < 10);
Exemple 2
// affichage de lalphabet en minuscules. char lettre = a; do { cout << lettre << ; lettre++; } while ( lettre <= z );
186
Le langage C++
Faire
Utiliser do...while lorsque la boucle doit
Ne pas faire
Utiliser break et continue avec des
boucles, moins que laction du code ne soit vidente. Il y a souvent des mthodes plus claires pour accomplir ces oprations.
Utiliser linstruction goto.
";
std::cout << "\nCompteur la sortie de la boucle: " << compteur << std::endl; return 0; }
Chapitre 7
187
Dans ce listing, on voit bien les trois tapes mentionnes plus haut. La condition de dpart est dnie la ligne 8 : la variable compteur est initialise 0. Deux lignes plus loin, le programme dtermine si elle est infrieure 5. Enn, la variable compteur est incrmente la ligne 12. Cette boucle afche un simple message la ligne 13, mais ce traitement pourrait tre plus compliqu. Une boucle for combine ces trois oprations (initialisation, test et incrmentation) en une seule instruction. La syntaxe est simple : il suft dutiliser le mot-cl for suivi de parenthses () entre lesquelles gurent les trois oprations spares les unes des autres par un point-virgule (;).
for ( initialisation ; test; action ) { ... }
La premire expression, initialisation, est la condition de dpart, ou initialisation ; elle peut contenir nimporte quelle instruction C++ valide, mais lusage veut que lon y cre et initialise une variable compteur. La deuxime expression, test, correspond au test, mais rien ne vous empche dy placer une instruction C++ diffrente. Elle remplit le mme rle que lexpression conditionnelle dune boucle while. Enn, la troisime expression, action, correspond laction raliser. Cette action concerne gnralement laugmentation ou la baisse dune valeur. L aussi, vous pouvez indiquer une instruction permettant deffectuer une autre tche. Le Listing 7.9 utilise une boucle for pour effectuer le mme traitement que le Listing 7.8. Listing 7.9 : Exemple de boucle for
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 12a: 13: 14: // Listing7.9 // Traitement itratif avec for #include <iostream> int main() { int compteur; for (compteur = 0; compteur < 5; compteur++) std::cout << "Bip! "; std::cout << "\nCompteur la sortie de la boucle: " << compteur << std::endl; return 0; }
188
Le langage C++
Linstruction for rassemble trois oprations la ligne 9 : le compteur est initialis zro, le test porte sur la valeur de cette variable qui doit tre infrieure 5 et enn, le compteur est incrment de un point chaque passage. Le corps de la boucle consiste en une seule instruction la ligne 10, mais, il est possible de placer un bloc dinstructions.
// afficher Bonjour 10 fois for (int i = 0; i<10; i++) cout << "Bonjour! ";
Exemple 2
for (int i = 0; i < 10; i++) { cout << "Bonjour!" << endl; cout << "i est gal a " << i << endl; }
Chapitre 7
189
laction par plusieurs instructions C++, spares les unes des autres par une virgule, comme dans le Listing 7.10 Listing 7.10 : Boucle for contenant plusieurs instructions
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: //Listing7.10 // Boucle for comportant plusieurs // instructions #include <iostream> int main() { for (int i=0, j=0; i<3; i++, j++) std::cout << "i: " << i << ", j: " << j << std::endl; return 0; }
La ligne 9 initialise zro les deux variables i et j en sparant par une virgule les deux expressions. Vous pouvez galement constater que ces initialisations sont spares de la condition de test par le point-virgule attendu. Lorsque ce programme sexcute, le test (i<3) est valu. Le rsultat tant vrai, le corps de linstruction for sexcute et les valeurs apparaissent lcran. Puis, la troisime clause de linstruction for est excute. Cette instruction comporte galement deux expressions pour incrmenter i et j. Aprs excution de linstruction de la ligne 10, la condition est de nouveau value. Si elle est toujours vraie, les actions se rptent (i et j sont nouveau incrmentes), puis la boucle sexcute une nouvelle fois. Lorsque la condition est fausse, laction nest pas excute et le programme quitte la boucle.
190
Le langage C++
";
std::cout << "\nCompteur la sortie de la boucle: " << compteur << std::endl; return 0; }
Vous constatez que cette boucle fonctionne exactement comme celle du Listing 7.8. La ligne 8 initialise la variable compteur. la ligne 10, linstruction for ninitialise pas de valeur, mais vrie simplement si le compteur est infrieur 5. Lincrmentation se fait dans le corps de la boucle, ce qui revient crire :
while (compteur < 5)
Cette fois encore, vous pouvez constater que C++ permet de raliser une mme tche de plusieurs faons. Un programmeur expriment nutiliserait pas une instruction for comme celle du Listing 7.11 : cet exemple na quune valeur de dmonstration de la souplesse de linstruction for. En fait, comme le montre le Listing 7.12, il est possible dutiliser des instructions break et continue pour crer une boucle for vide. Listing 7.12 : Instruction for vide
1: 2: 3: //Listing7.12: illustration //dune boucle for vide
Chapitre 7
191
4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23:
#include <iostream> int main() { int compteur = 0; // initialisation int max; std::cout << "Combien de bips voulez-vous afficher? "; std::cin >> max; for (;;) // boucle for sans fin { if (compteur < max) // test { std::cout << "Bip! " << std::endl; compteur++; // incrmentation } else break; } return 0; }
Cette boucle for est dpouille lextrme car linstruction ne contient aucune des trois clauses habituelles initialisation, test et action. Tout dabord, linitialisation seffectue la ligne 8 avant que la boucle for ne commence. Le test est ralis dans une instruction distincte, la ligne 14. Si le rsultat est vrai, lincrmentation seffectue la ligne 17. Dans le cas contraire, le programme quitte la boucle pour aller directement la ligne 20. Bien entendu, ce programme est quelque peu absurde mais, dans certains cas, une boucle for(;;) ou une boucle while (true) rpondront parfaitement vos besoins. Avec linstruction switch, vous allez apprendre utiliser correctement ces types de boucles.
192
Le langage C++
Linstruction for de la ligne 8 comprend trois lments : linstruction dinitialisation dnit le compteur i, puis linitialise zro. Linstruction de test vrie si i est infrieure 5. Enn, linstruction daction afche la valeur de i et incrmente le compteur. Comme il ny a plus rien faire dans le corps de la boucle, on utilise linstruction nulle (;). Notez que cette boucle nest pas trs bien construite, car linstruction daction peut poser des problmes de comprhension au moment de la compilation. Il serait prfrable de taper les instructions comme suit :
8: 9: for (int i = 0; i<5; i++) cout << "i: " << i << endl;
Imbrication de boucles
Nimporte quelle boucle peut tre imbrique dans le corps dune autre. Une boucle interne sexcute en totalit chaque itration de la boucle de niveau immdiatement suprieur. Le Listing 7.14 utilise deux boucles for imbriques pour afcher une matrice.
Chapitre 7
193
Lutilisateur est invit entrer le nombre de lignes et le nombre de colonnes, puis le caractre qui va se rpter dans toutes les cases du tableau. La premire boucle for, la ligne 16, initialise un compteur (i) 0, puis excute le corps de la boucle for externe. La ligne 18 est la premire ligne du corps de la boucle for externe, elle met en place une autre boucle for. Cette boucle imbrique initialise zro un deuxime compteur (j) et excute son corps. La ligne 19 afche le caractre choisi et lexcution revient len-tte de la boucle imbrique. Vous remarquerez que la boucle imbrique na quune seule instruction (lafchage du caractre). Sa condition (j < colonnes) est value ; si elle est
194
Le langage C++
vraie, j est incrmente et le caractre suivant est afch. Ce traitement sarrte lorsque j est gale au nombre de colonnes souhait. Lorsque le test de la boucle imbrique renvoie un rsultat faux (ici, aprs lafchage de 12 X), le programme saute la ligne 20 et afche un retour la ligne. La boucle externe revient alors son en-tte pour tester la condition i < lignes. Si le rsultat est vrai (i est infrieur au nombre de lignes), i est incrmente et le corps de la boucle sexcute. la deuxime itration de la boucle externe, la boucle imbrique sexcute nouveau ; j est rinitialise 0 et le traitement complet de la boucle recommence. Le point important noter, ici, est quune boucle imbrique sexcute compltement chaque itration de la boucle extrieure. Dans notre exemple, le caractre X safche donc colonnes fois pour chaque ligne.
Nombreux sont les programmeurs qui utilisent les lettres i et j comme variables de compteur. Cette habitude provient du FORTRAN, qui nautorisait que les lettres i, j, k, l, m et n comme noms de variables de comptage. Mme si cela peut sembler inoffensif, vos lecteurs pourraient sinterroger sur lobjectif du compteur et lutiliser mauvais escient. Un programme complexe contenant des boucles imbriques devient encore plus confus. En ce cas, il vaut mieux prciser le but de la variable compteur dans son nom, en utilisant par exemple IndiceClient ou CompteurEntrees.
Info
Chapitre 7
195
Si la compilation se droule sans problme, votre compilateur ne prend pas en compte ce nouvel aspect de la norme ANSI. Si votre compilateur signale que i nest pas encore dni ( la ligne i = 7), il tient compte de ce nouveau standard. Le code suivant sera compil dans les deux cas si vous dclarez i en dehors de la boucle, comme ici :
#include <iostream> int main() { int i; // i dclar hors de la boucle for for (i = 0; i < 5; i++) { std::cout << "i: " << i << std::endl; } i = 7; // i est maintenant accessible dans tous les cas return 0; }
Le nombre de Fibonacci n est la somme du nombre n-1 et du nombre n-2. Le Listing 7.15 prsente une solution ce problme en utilisant une itration. Listing 7.15 : Dterminer la valeur dun nombre de Fibonacci laide dune itration
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: // Listing7.15 - Dterminer la valeur du nime nombre // laide dune itration #include <iostream> unsigned int fib(unsigned int position); int main() { using namespace std; unsigned int reponse, position; cout << "Quelle position ? "; cin >> position;
196
Le langage C++
13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36:
cout << endl; reponse = fib(position); cout << reponse << " est le "; cout << position << "e nombre dans la suite. " << endl; return 0; } unsigned int fib(unsigned int n) { unsigned int moinsDeux=1, moinsUn=1, reponse=2; if (n < 3) return 1; for (n -= 3; { moinsDeux moinsUn = reponse = } n!= 0; n--) = moinsUn; reponse; moinsUn+moinsDeux;
return reponse; }
Dans le Listing 7.15, nous avons prfr litration la rcursivit, car cette approche est plus rapide et utilise moins de mmoire que la solution rcursive. La ligne 11 demande lutilisateur de saisir la position du nombre trouver. Puis, on appelle la fonction fib() pour calculer la valeur correspondante. Si la position est infrieure 3, la fonction renvoie 1. partir de la position 3, la fonction utilise lalgorithme suivant : 1. Dterminer la position de dpart : affecter la valeur 2 la variable reponse, la valeur 1 moinsDeux et la valeur 1 moinsUn. On te 3 la position, car les deux premiers nombres sont pris en charge par la position de dpart.
Chapitre 7
197
2. Pour chaque nombre, dterminer la valeur dans la suite. Pour cela : a. Transfrer la valeur de moinsUn dans moinsDeux. b. Copier la valeur de reponse dans moinsUn. c. Faire la somme de moinsUn et de moinsDeux, puis copier le rsultat dans la variable reponse. d. Dcrmenter n. 3. Lorsque n est gal 0, renvoyer le rsultat. Cest comme si vous rsolviez le problme avec du papier et un crayon. Pour le cinquime nombre de Fibonacci, vous cririez dabord :
1, 1, 2,
puis vous vous diriez quil y a encore deux nombres de plus trouver. Vous additionneriez alors 2 et 1 et cririez 3 : il ne resterait alors plus quun nombre trouver. Pour cela, vous additionneriez 3 et 2 et vous cririez 5. En pratique, vous reportez chaque fois votre attention dun nombre vers la droite et vous dcrmentez le nombre de valeurs restant trouver. Remarquez la condition qui est teste la ligne 28 (n!= 0). De nombreux programmeurs utiliseraient plutt cette notation :
for ( n-=3; n; n-- )
Au lieu dutiliser une condition relationnelle, seule la valeur de n sert la condition dans linstruction for. Il sagit dune expression caractristique de C++ ; n est considr comme tant quivalent lexpression n!= 0. Se contenter dutiliser n sappuie sur le fait que lorsque n est gal 0, cette valeur est considre comme fausse par C++. Pour se conformer la norme actuelle de C++, il vaut mieux utiliser une vritable condition pour valuer la valeur false plutt quutiliser une valeur numrique. Compilez, excutez le programme et comparez le avec la solution rcursive du Chapitre 5. Tapez 25 comme position dans la suite de Fibonacci et comparez les temps dexcution des deux versions : vous pouvez constater que le programme itratif tourne bien plus rapidement que le programme rcursif car ce dernier ralise de nombreux appels de fonctions, qui ont un impact sur les performances. En outre, la solution rcursive, bien qulgante, effectue plusieurs fois les mmes calculs. Les ordinateurs modernes optimisant les traitements arithmtiques, la solution itrative devrait tre trs rapide. Attention la taille du nombre saisi car le nombre de Fibonacci augmente rapidement : mme la capacit des entiers longs non signs sera vite dpasse.
198
Le langage C++
expression correspond nimporte quelle expression C++ renvoyant une valeur entire. Les instructions sont des instructions ou des blocs dinstructions C++. Les diffrentes valeurs des clauses case sont des valeurs entires (ou des expressions pouvant tre sans ambigut convertie en une valeur entire). Linstruction switch value la valeur dexpression puis compare le rsultat avec les valeurs des clauses case. Cette comparaison seffectue toujours selon lgalit, les oprateurs de comparaison et boolens sont interdits. Si lune des valeurs de case correspond lexpression, le programme saute vers les instructions correspondantes, puis continue lexcution jusqu la n de linstruction switch, moins quil ne rencontre une instruction break. Si aucune correspondance nest trouve, il va directement linstruction default, qui est facultative ; sil ny a ni valeur case correspondante, ni de clause default, le programme quitte le bloc dinstructions switch. Il est toujours judicieux dutiliser une instruction default dans un bloc dinstructions switch. Mme si vous navez pas de valeur par dfaut traiter, cette instruction pourra servir tester un cas qui survient rarement ou afcher un message derreur. Elle peut galement tre trs pratique lors de la mise au point de lapplication.
Info
Il est important de noter que si une clause case ne se termine pas par break, lexcution du programme se poursuit avec la clause case suivante. Cest parfois ncessaire, mais il sagit gnralement dun oubli du programmeur, qui provoquera un comportement inattendu.
Chapitre 7
199
Si vous dcidez sciemment dautoriser ce passage la clause case suivante, noubliez pas de placer un commentaire pour indiquer quil ne sagit pas dun oubli. Le Listing 7.16 illustre lutilisation de linstruction switch. Listing 7.16 : Utilisation de linstruction switch
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: //Listing7.16 // Linstruction switch #include <iostream> int main() { using namespace std; unsigned short int nombre; cout << "Entrez un nombre entre 1 et 5: "; cin >> nombre; switch (nombre) { case 0: cout << "Trop petit, refaire!"; break; case 5: cout << "Bravo! " << endl; // case 4: cout << "Bon choix! " << endl; // case 3: cout << "Excellent! " << endl; // case 2: cout << "Formidable! " << endl; // case 1: cout << "Incroyable! " << endl; break; default: cout << "Trop grand!" << endl; break; } cout << endl << endl; return 0; }
200
Le langage C++
Aux lignes 9 et 10, lutilisateur est invit entrer un nombre qui sera ensuite transmis linstruction switch en ligne 11. Si la valeur saisie est zro, le programme saute linstruction case de la ligne 13. Le message "Trop petit, refaire !" apparat alors et linstruction break de la ligne 14 met n au switch. Si la valeur est gale 5, lexcution passe la ligne 15 et les instructions case qui suivent sexcutent les unes aprs les autres jusqu linstruction break de la ligne 20, qui met n au switch. Le nombre de messages afchs est donc gal la valeur saisie. Si cette dernire est suprieure 5, la clause default sexcute et le message "Trop grand !" apparat (ligne 21).
switch (expression) { case valeur1: instruction; case valeur2: instruction; .... case valeurN: instruction default: instruction; }
Linstruction switch permet daiguiller le programme en diffrent points en fonction de la valeur de expression. Aprs lvaluation de lexpression, le pointeur dinstruction va directement clause case dont la valeur correspond. Lexcution du programme se poursuit jusqu la n de linstruction switch ou jusqu ce quune instruction break soit rencontre. Si lexpression ne correspond aucune clause case, et quil y a une clause default, le programme excute cette clause ; sinon, linstruction switch se termine. Exemple 1
switch (choix) { case 0: cout << break case 1: cout << break; case 2: cout << default: cout << }
Chapitre 7
201
Exemple 2
switch (choix) { case 0: case 1: case 2: cout << "Infrieur 3!"; break; case 3: cout << "gal 3!"; break; default: cout << "Suprieur 3 !"; }
Info
Une boucle sans n ne contient pas de condition de sortie. Seule une instruction break permet den sortir. Les boucles sans n sont galement appeles boucles innies. Listing 7.17 : Exemple de boucle sans n
1: 2: 2a: 3: 4: //Listing7.17 //Boucle sans fin permettant une interaction // avec lutilisateur laide dun menu #include <iostream>
202
Le langage C++
5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51:
// prototypes int menu(); void Tache(); void AutreTache(int); using namespace std; int main() { bool fin = false; for (;;) // dbut de la boucle sans fin { int choix = menu(); switch(choix) { case (1): Tache(); break; case (2): AutreTache(2); break; case (3): AutreTache(3); break; case (4): continue; // redondant! break; case (5): fin = true; break; default: cout << "Choix non valide, recommencez! " << endl; break; } // fin de switch if (fin == true) break; } return 0; } int menu() { int choix; cout << " **** Menu **** " << endl << endl; cout << "(1) Choix un. " << endl; // fin de la boucle // fin de main()
Chapitre 7
203
52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72:
cout << "(2) Choix deux. " << endl; cout << "(3) Choix trois. " << endl; cout << "(4) Rafficher le menu. " << endl; cout << "(5) Quitter. " << endl << endl; cout << "Votre choix: "; cin >> choix; return choix; } void Tache() { cout << "Tche une! " << endl; } void AutreTache(int combien) { if (combien == 2) cout << "Tche deux! " << endl; else cout << "Tche trois! " << endl; }
Votre choix: 1 Tche une! **** (1) (2) (3) (4) (5) Menu **** Choix un. Choix deux. Choix trois. Rafficher le menu Quitter.
Votre choix: 3 Tche trois! **** Menu **** (1) Choix un. (2) Choix deux.
204
Le langage C++
(3) Choix trois. (4) Rafficher le menu (5) Quitter. Votre choix: 5
Ce programme rassemble des concepts abords dans ce chapitre (comme linstruction switch) et dans les chapitres prcdents. La boucle sans n commence la ligne 15. La fonction menu() afche le menu lcran et rcupre le choix de lutilisateur. Cette valeur est traite dans linstruction switch qui stend de la ligne 18 la ligne 38. Si lutilisateur choisit 1, le programme va directement la clause case (1): de la ligne 20, puis il excute la fonction Tache(), qui afche un message. Linstruction break de la ligne 22 interrompt lexcution de linstruction switch et le programme va alors directement la ligne 39. la ligne suivante, la variable fin est value. Si elle vaut true, linstruction break de la ligne 41 sexcute et la boucle for(;;) prend n. Dans le cas contraire, lexcution reprend au dbut de la boucle (ligne 15). Linstruction continue de la ligne 30 est redondante. Si cette instruction ntait pas mentionne, linstruction break suivante mettrait n linstruction switch, le rsultat du test sur fin serait faux et un nouveau passage dans la boucle aurait lieu, ce qui rafcherait le menu. Linstruction continue permet toutefois dviter le test de fin.
Faire
Documenter toutes les clause case ne conte-
Ne pas faire
Utiliser des instructions ifelse complexes
clauses case, sauf si vous voulez que le traitement se poursuive dans la clause case suivante.
Questions-rponses
Q Comment choisir entre linstruction ifelse et linstruction switch ? R Si la condition comprend plus dune ou deux clauses else testant la mme valeur, prfrez linstruction switch.
Chapitre 7
205
Q Comment choisir entre linstruction while et linstruction do...while ? R Si le corps de litration doit sexcuter au moins une fois, choisissez linstruction do...while. Dans le cas contraire, prfrez linstruction while. Q Comment choisir entre linstruction while et linstruction for ? R Linstruction for permet dinitialiser une variable compteur, de tester cette variable, puis de lincrmenter chaque passage. En revanche, si la variable est dj initialise et na pas besoin dtre incrmente chaque itration, prfrez linstruction while. Les programmeurs expriments sattendent cette utilisation et auront plus de mal comprendre votre code si vous nagissez pas ainsi. Q Doit-on prfrer linstruction while (true) linstruction for(;;) ? R Non. Ces instructions permettent toutes les deux de dnir une boucle sans n, mais il vaut mieux les viter. Q Pourquoi ne faut-il pas utiliser une variable comme condition, par exemple while(n) ? R Dans la norme C++ actuelle, une expression renvoie une valeur boolenne true ou false. Vous pouvez associer false 0 et true une autre valeur, mais il vaut mieux utiliser une expression qui renvoie lune des deux valeurs boolennes.
5. Est-il possible dimbriquer des boucles while dans des boucles for ? 6. Est-il possible de crer une boucle qui ne se termine jamais ? Donnez un exemple. 7. Que se passe-t-il si vous crez une boucle sans n ?
206
Le langage C++
Exercices
1. crivez une boucle for imbrique qui produit une matrice de 10 fois 10 zros. 2. Comptez de 100 200, de deux en deux en utilisant une boucle for. 3. Mme exercice que ci-dessus mais avec une boucle while. 4. Mme exercice que ci-dessus mais avec une boucle do...while. 5. CHERCHEZ LERREUR : pourquoi ce code est-il bogu ?
int compteur = 0 while (compteur < 10) { cout << "Compteur: " << compteur; }
Partie II
La premire partie de cet ouvrage vous a initi aux bases de C++. Vous tes maintenant en mesure de saisir du code, de suivre le droulement dune application, de dnir des objets et des classes et dutiliser le compilateur. Le Chapitre 8 traite des pointeurs. Les pointeurs sont traditionnellement rputs comme lun des sujets les plus ardus pour les programmeurs dbutants. Aussi lapproche est-elle progressive pour vous permettre den assimiler parfaitement les concepts et dcrire vos premiers programmes. Dans le Chapitre 9, vous ferez connaissance avec les rfrences, qui sont des proches cousins des pointeurs. Le Chapitre 10 est consacr la surcharge de fonctions. Le Chapitre 11 traite de la conception et de lanalyse oriente objet. Le Chapitre 12 aborde le concept dhritage, trs important en POO, dont ltude se poursuit au Chapitre 13 qui tudie les tableaux et les collections. Enn, le Chapitre 14 traite du polymorphisme.
8
Pointeurs
Au sommaire de ce chapitre
Nature et fonctionnalit des pointeurs Dclaration et utilisation de pointeurs Espace adressable et gestion de la mmoire
La gestion directe de la mmoire laide de pointeurs constitue une fonctionnalit puissante mais de bas niveau du langage C++. Cest un avantage de C++ par rapport dautres langages, comme C# et Visual Basic, mme si C# permet dutiliser les pointeurs sous certaines conditions. Les pointeurs posent deux problmes aux programmeurs dbutants : dune part, leur approche est ardue et, dautre part, il est difcile de dterminer quand et comment il est souhaitable de les utiliser. Dans ce chapitre, les pointeurs sont prsents de faon progressive, de sorte que vous saurez exactement quand y avoir recours.
210
Le langage C++
Info
La possibilit dutiliser des pointeurs et de manipuler la mmoire bas niveau est lun des facteurs qui font de C++ un langage de choix pour les applications embarques et en temps rel.
Mmoire
Pour comprendre les pointeurs, vous devez avoir quelques notions sur lorganisation de la mmoire dun ordinateur. Cette mmoire est divise en emplacements numrots squentiellement. Chaque variable rside dans un emplacement unique en mmoire, connu par son adresse. La Figure 8.1 reprsente schmatiquement la faon dont la variable unsigned long monAge est stocke en mmoire.
Figure 8.1 Reprsentation schmatique de la variable monAge.
Mmoire monAge
100
101
102
103
104
105
106
107
108
109
110
111
112
113
chaque emplacement = 1 octet entier long non sign monAge = 4 octets = 32 bits la variable monAge pointe sur le premier octet l'adresse de monAge est 102
Chapitre 8
Pointeurs
211
(les
valeurs afches sur votre ordinateur seront probablement diffrentes, en particulier dans la dernire colonne). Le programme commence par la dclaration et linitialisation de trois variables : la premire de type unsigned short (ligne 8), la deuxime de type unsigned long (ligne 9) et la dernire de type long (ligne 10). Leurs valeurs et adresses respectives sont extraites laide de loprateur adresse de (&), puis afches lcran (lignes 12 22). Cet oprateur est simplement plac devant le nom de la variable pour que ladresse soit renvoye. La variable varCourte est initialise la ligne 12. Dans la premire ligne du rsultat, son adresse hexadcimale est gale 00bffff97e lorsque le programme tourne sur un ordinateur
212
Le langage C++
dot dun processeur Intel Core 2 Duo (il a t ici compil sur 32 bits). Cette adresse est propre chaque ordinateur et varie dune session une autre. Les rsultats que vous obtiendrez seront diffrents. Lorsque lon dclare une variable, le compilateur dtermine lespace mmoire ncessaire en fonction du type de la variable. Il soccupe ensuite dallouer automatiquement la mmoire et daffecter une adresse pour cette variable. Un entier long occupant gnralement 4 octets, le compilateur affectera une adresse qui pointera sur 4 octets en mmoire.
Il sagit dun pointeur sur un entier, ce qui signie que pAge est destin recevoir ladresse dun entier. Notez que pAge est une variable. Lorsque vous dclarez une variable entire (de type int), le compilateur rserve la mmoire ncessaire pour le stockage dun entier. Si vous dclarez une variable pointeur telle que pAge, le compilateur rserve sufsamment de mmoire pour contenir une adresse (4 octets sur la plupart des ordinateurs). Un pointeur, et donc pAge, est simplement un type de variable diffrent.
pAge est initialis 0, ce qui caractrise les pointeurs null. Les pointeurs devant toujours tre initialiss, la solution consiste affecter une valeur nulle lorsque ladresse nest pas encore dtermine. Un pointeur qui nest pas initialis est trs dangereux, on lappelle un pointeur fou, car il peut pointer vers nimporte quoi.
Chapitre 8
Pointeurs
213
ntion Atte
Pour viter les erreurs de compilation et dexcution, noubliez pas dinitialiser tous vos pointeurs.
Pour quun pointeur stocke une adresse, cette adresse doit lui tre affecte. Dans lexemple prcdent, vous devez affecter ladresse de MonAge page :
unsigned short int MonAge = 50; // cre une variable unsigned short int * pAge = 0; // cre un pointeur pAge = &MonAge; // affecte ladresse de MonAge pAge
La premire ligne initialise 50 la variable MonAge, de type unsigned short int. La seconde ligne dclare pAge comme un pointeur vers un unsigned short int et linitialise zro. On sait que pAge est un pointeur grce lastrisque (*) place entre le type et le nom de la variable. La dernire ligne affecte ladresse de MonAge au pointeur pAge, grce loprateur adresse de (&). Sans lui, la valeur de MonAge aurait t affecte au pointeur pAge. Cette valeur aurait pu, ou non, tre une adresse valide. La valeur de ladresse de MonAge est prsent stocke dans pAge. MonAge contient la valeur 50. Laffectation de ces valeurs aurait pu tre simplie :
unsigned short int monAge = 50; unsigned short int * pAge = &monAge; // variable // pointeur sur monAge
Info
214
Le langage C++
Dans la dclaration :
unsigned short int * pAge = 0; // cration dun pointeur
pAge est dclar comme un pointeur sur un entier court non sign. Le compilateur sait que le pointeur (qui a besoin de 4 octets pour contenir une adresse) contiendra ladresse dun objet du type entier court non sign, qui lui-mme ncessite 2 octets.
Pour affecter la valeur de MonAge la variable VotreAge, en utilisant le pointeur pAge, vous devez crire :
unsigned short int VotreAge; VotreAge = *pAge;
Loprateur dindirection (*) devant une variable pointeur signie : "valeur stocke ladresse". Dans le cas prsent, cette affectation signie donc : "prend la valeur stocke ladresse de pAge, puis affecte-la la variable VotreAge". Si vous naviez pas inclus loprateur dindirection :
VotreAge = pAge; // Mauvais!!
vous tenteriez daffecter la valeur de pAge, une adresse mmoire, votreAge. Votre compilateur afcherait probablement un avertissement.
Les diffrentes utilisations de lastrisque Lastrisque (*) est utilis de deux faons diffrentes avec les pointeurs : comme composante du pointeur et comme oprateur dindirection. Lorsque vous dclarez un pointeur, lastrisque fait partie de la dclaration et suit le type de lobjet vers lequel on pointe. Exemple :
Chapitre 8
Pointeurs
215
Par ailleurs, loprateur dindirection (ou de drfrencement) appliqu un pointeur permet daccder non pas ladresse elle-mme, mais lemplacement mmoire situ cette adresse. Exemple :
*pAge = 5;
Ce caractre est aussi utilis comme oprateur de multiplication. Rassurez-vous : le compilateur sait, daprs le contexte, de quel oprateur il sagit.
La variable maVariable de type entier reoit la valeur numrique 5. Le pointeur pPointeur est dclar comme pointeur sur un entier et est initialis avec ladresse de maVariable. La valeur de ladresse stocke dans pPointeur est ladresse de maVariable. La Figure 8.2 est une reprsentation graphique de cet exemple. Dans cette gure, la valeur 5 est stocke ladresse mmoire 101, indique par le nombre binaire :
0000000000000101
0000 0000 0000 0101 5 100 101 102 103 104 105
0000 0000 0000 0110 0000 0000 0000 0101 101 106 107 108 109
216
Le langage C++
Il sagit de la reprsentation binaire de la valeur 101, qui est ladresse de maVariable dont la valeur est 5. Bien que la mmoire soit reprsente ici de faon schmatique, elle illustre bien la faon dont les pointeurs stockent une adresse.
Chapitre 8
Pointeurs
217
24: 25: 26: 27: 28: 29: 30: 31: 32: 33:
cout << "monAge: " << monAge << endl << endl; cout << "Affectation moAge de la valeur 9... " << endl; monAge = 9; cout << "monAge: " << monAge << endl; cout << "*pAge: " << *pAge << endl; return 0; }
Ce programme commence par dclarer deux variables : monAge (de type entier court) et son pointeur pAge. La ligne 14 affecte 5 la variable monAge, ce que conrme lafchage produit par la ligne 16. la ligne 17, pAge reoit ladresse de monAge. La ligne 18 "drfrence" le pointeur avec loprateur dindirection (*) et obtient ainsi la valeur stocke dans monAge, cest--dire 5, qui est afche. La valeur 7 est ensuite affecte la variable par lintermdiaire du pointeur (ligne 21). Les messages produits aux lignes 23 et 24 conrment cette opration. Remarquez une fois de plus que laccs indirect la variable a t obtenu avec lastrisque (loprateur dindirection, dans ce contexte). Enn, le chiffre 9 est affect la variable monAge (ligne 27). La ligne 29 lit directement la variable alors que la ligne suivante la lit indirectement, en dfrenant pAge.
218
Le langage C++
cout << "\nRaffectation de pAge = &tonAge... " << endl << endl; pAge = &tonAge; // raffectation du pointeur cout << "monAge:\t" << monAge << "\t\ttonAge:\t" << tonAge << endl; cout << "&monAge:\t" << &monAge << "\t&tonAge:\t" << &tonAge << endl; cout << "pAge:\t" << pAge << endl; cout << "*pAge:\t" << *pAge << endl; cout << "\n&pAge:\t" << &pAge << endl; return 0; }
Chapitre 8
Pointeurs
219
pAge : *pAge :
0xbffff96e 5
Raffectation de pAge = &tonAge... monAge : &monAge : pAge : *pAge : &pAge : 5 0xbffff96e 0xbffff96c 10 0xbffff968 tonAge : &tonAge : 10 0xbffff96c
(Le rsultat que vous obtiendrez sera diffrent.) La ligne 9 dclare les variables entires monAge et tonAge, de type unsigned short, puis dclare le pointeur pAge et linitialise avec ladresse de monAge (ligne 12). Les valeurs numriques et les adresses des deux variables apparaissent alors lcran (lignes 14 18). la ligne 20, vous pouvez remarquer que le contenu de pAge correspond ladresse de monAge. La ligne 21 afche le rsultat du drfrencement du pointeur, ce qui afche la valeur pointe par pAge, qui est celle de la variable monAge, cest--dire 5. Cet exemple illustre parfaitement lextraction de donnes laide dun pointeur. Elle est directe la ligne 20 et indirecte (par drfrencement du pointeur) la ligne suivante. Avant de poursuivre, vous devez comprendre ces deux approches. Au besoin, modiez les valeurs et examinez le rsultat obtenu. La ligne 25 affecte ladresse de tonAge au pointeur : pAge "pointe" donc dsormais vers cette variable. Pour vous aider comprendre lopration, les valeurs et les adresses apparaissent de nouveau lcran. Le pointeur pAge contient dsormais ladresse de tonAge. Par drfrencement, on obtient donc la valeur de tonAge. Ladresse du pointeur pAge apparat lcran (ligne 36). Comme toute variable, le pointeur possde une adresse (qui peut elle-mme tre copie dans un pointeur). Nous prsenterons bientt laffectation dune adresse de pointeur un autre pointeur.
Faire
Accder aux donnes stockes ladresse
Ne pas faire
Confondre ladresse stocke dans un pointeur
220
Le langage C++
Utilisation des pointeurs Pour dclarer un pointeur, vous devez dabord indiquer le type de la variable ou de lobjet point, suivi de loprateur * et du nom du pointeur. Exemple :
grer des donnes dans lespace mmoire adressable ; accder aux donnes membres et aux fonctions des classes ; passer des variables par rfrence des fonctions.
Le reste de ce chapitre est consacr la gestion de donnes dans lespace mmoire adressable et laccs aux donnes membres et aux fonctions dune classe. Le passage de variables laide des pointeurs, appel passage par rfrence, sera tudi au chapitre suivant.
lespace global de noms ; lespace mmoire adressable (appel aussi mmoire "heap" ou tas) ; les registres ;
Chapitre 8
Pointeurs
221
Les variables locales et les paramtres passs aux fonctions sont placs sur la pile. Le code du programme rside dans lespace de code. Quant aux registres, ils permettent au systme de raliser des oprations internes, comme le suivi du contenu de la pile et du pointeur dinstruction. Le reste de la mmoire est essentiellement compos de lespace adressable, aussi appel le tas. Les variables locales disparaissent lorsque la fonction qui les a dnies se termine : le programmeur na donc pas besoin de grer cette partie de la mmoire. Par contre, ces objets locaux ne peuvent pas tre facilement transmis dautres objets ou dautres fonctions : pour ce faire, il faut copier ces objets de la pile vers la valeur de retour de la fonction, puis vers lobjet destinataire dans lappelant et cela a un cot. Les variables globales permettent de pallier cet inconvnient au prix dun accs illimit aux donnes, ce qui peut rendre le code illisible et impossible mettre jour. La solution consiste alors utiliser le tas, mais le programmeur doit alors grer correctement ces donnes. Le tas peut tre considr comme un ensemble de milliers de cases mmoires destines recevoir des donnes. la diffrence de la pile, ces cases ne sont tre nommes : pour accder une donne stocke dans le tas, il faut obtenir ladresse de lemplacement que lon a rserv pour elle, puis copier cette adresse dans un pointeur, an de la mmoriser. Prenons un exemple. Un ami vous communique par crit le numro de tlphone du garage SuperAuto. De retour votre domicile, vous composez ce numro, puis vous jetez le papier la corbeille. Vous ne pouvez donc plus connatre le numro de tlphone de cette entreprise, mais la mmoire de lappareil tlphonique continue de vous permettre de les appeler. SuperAuto correspond aux donnes stockes dans le tas. Vous ne savez pas o le garage se trouve, mais vous savez comment y accder, en utilisant son adresse son numro de tlphone stock dans la mmoire de votre combin, ici. Vous ne connaissez mme pas ce numro, mais vous lavez mis dans un pointeur (la mmoire du combin), qui vous permet de continuer joindre le garage. De la mme faon, dans un programme, un pointeur permet davoir accs une donne sans connatre son emplacement en mmoire. La pile est vide automatiquement quand la fonction prend n : toutes les variables locales deviennent hors de porte et sont supprimes de la pile. En revanche, le tas nest vid quaprs la n du programme. Pendant la session du programme, la gestion du tas et la libration de la mmoire vous incombent. Cest dans ce cas que les destructeurs sont absolument ncessaires : ils fournissent un endroit permettant de librer les emplacements du tas qui ont t allous lors de la construction de lobjet.
222
Le langage C++
Lavantage du tas est que les emplacements que vous avez rservs restent disponibles jusqu ce que vous les libriez explicitement. Si vous allouez de la mmoire sur le tas dans une fonction, cette mmoire reste alloue aprs la n de la fonction. Cette persistance des emplacements rservs est aussi un inconvnient car, si vous oubliez de les librer, vous pouvez vous retrouver dans une situation o il ny aura plus assez de place disponible, auquel cas votre systme peut se planter. Lavantage daccder la mmoire de cette faon au lieu de passer par des variables globales est que seules les fonctions qui ont accs au pointeur (qui contient ladresse approprie) peuvent accder aux donnes sur le tas. Cela impose donc que lobjet contenant le pointeur, ou le pointeur lui-mme, soit explicitement pass toute fonction apportant des changements, ce qui rduit les risques quune fonction puisse subrepticement modier des donnes. Pour que tout cela fonctionne, vous devez donc crer un pointeur vers une zone du tas, puis le passer comme paramtre aux fonctions, comme on lexplique dans la section qui suit.
Bien entendu, on peut faire tout cela en une seule ligne, en initialisant le pointeur lors de sa dclaration :
unsigned short int * pPointeur = new unsigned short int;
Dans les deux cas, pPointeur pointera sur une variable de type entier court non sign rsidant dans le tas. Vous pouvez galement placer une valeur dans cette zone mmoire :
*pPointeur = 72;
Chapitre 8
Pointeurs
223
Cette instruction signie "Affecter 72 la valeur pointe par pPointeur," ou "Affecter la valeur 72 lemplacement du tas sur lequel pPointeur pointe". Sil ne parvient pas rserver de lespace supplmentaire dans le tas (la mmoire est une ressource limite), new lance une exception (voir Chapitre 20).
Info
Cette opration permet de librer la mmoire dont ladresse est stocke dans le pointeur. Cela revient donc dire "libre lemplacement du tas point par ce pointeur". Le pointeur reste un pointeur et peut tre raffect. Dans le Listing 8.4, nous effectuerons trois oprations : dnir une variable et lallouer dans le tas, lutiliser puis la supprimer. Le plus souvent, on alloue des lments sur le tas partir dun constructeur et on les libre dans le destructeur. Dans dautres cas, on initialise les pointeurs dans le constructeur, on alloue les emplacements mesure que lobjet est utilis et, dans le destructeur, on vrie si ces pointeurs sont nuls et on libre les emplacements quils pointent si ce nest pas le cas.
ntion Atte
Lorsque vous appelez delete sur un pointeur, vous librez la mmoire sur laquelle il pointe. Vous ne devez le faire quune seule fois, sous peine dentraner une erreur dexcution. Pour viter ce dsagrment, affectez la valeur nulle (0) au pointeur aussitt aprs lavoir supprim. Appeler delete sur un
224
Le langage C++
pointeur nul naura aucun incidence sur le droulement du programme. Voici un exemple :
Animal *pChien = new Animal; // allouer de la mmoire delete pChien; // libre la mmoire pChien = 0; // affecte la valeur nulle au pointeur //... delete pChien; // sans dommage pour le programme
La ligne 7 dclare et initialise la variable locale (ironiquement nomme variableLocale). La ligne 8 dclare un pointeur appel pLocal et linitialise avec ladresse de la variable locale. La ligne 9 dclare un second pointeur, appel pTas ; comme elle linitialise avec la valeur renvoye par linstruction new int, il dsigne donc un nouvel emplacement rserv pour un entier sur le tas. La valeur 7 est affecte lespace mmoire qui vient dtre rserv (ligne 10).
Chapitre 8
Pointeurs
225
La ligne 11 afche la valeur de la variable locale (variableLocale), la ligne 12 celle de lemplacement point par pLocal et la ligne 13, la valeur de lemplacement point par pTas, On remarque que les valeurs afches par les lignes 11 et 12 se correspondent. La ligne 14 libre avec delete lespace mmoire qui a t allou la ligne 9. Le pointeur est donc dissoci de ladresse sur laquelle il pointait et peut maintenant pointer vers un autre emplacement mmoire. On le raffecte aux lignes 15 et 16 et on afche la valeur du nouvel emplacement quil pointe la ligne 17. Enn, la ligne 18 libre nouveau lemplacement qui a t allou. Bien que la ligne 18 soit redondante car la mmoire sera de toute faon libre la n du programme, il est prfrable de librer explicitement cette mmoire. Si le programme est ensuite modi ou tendu, il sera toujours bnque davoir dj pris en compte cette tape.
La ligne 1 cre pPointeur et lui affecte ladresse dun espace mmoire libre sur le tas. La ligne 2 y stocke la valeur 72. La ligne 3 raffecte pPointeur un autre emplacement mmoire et la ligne 4 y place la valeur 84. Lemplacement dorigine qui contient 72 est donc dsormais indisponible puisque le pointeur vers cet emplacement a t raffect : il nest plus possible daccder cet emplacement et on ne peut plus non plus le librer avant la n du programme. Il aurait fallu utiliser le code suivant :
1: 2: 3: 4: 5: unsigned short int * pPointeur = new unsigned short int; *pPointeur = 72; delete pPointeur; pPointeur = new unsigned short int; *pPointeur = 84;
La mmoire sur laquelle pointait lorigine pPointeur est supprime et par consquent libre la ligne 3.
226
Le langage C++
Info
chaque instruction new devrait correspondre une instruction delete. En effet, il est important de librer lespace mmoire ds quil nest plus ncessaire.
Cette instruction appelle le constructeur par dfaut (cest--dire le constructeur qui ne prend pas de paramtre). Le constructeur sexcute ds quun objet est cr (que ce soit sur la pile ou sur le tas). Vous ntes pas limit au constructeur par dfaut lors de la cration dun objet avec new : vous pouvez employer nimporte quel constructeur.
Chapitre 8
Pointeurs
227
15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38:
}; ChatSimple::ChatSimple () { cout << "Appel du constructeur." << endl; sonAge = 1; } ChatSimple::~ChatSimple () { cout << "Appel du destructeur." << endl; } int main() { cout << "ChatSimple Frisky... " << endl; ChatSimple Frisky; cout << "ChatSimple *pRags = new ChatSimple..." << endl; ChatSimple * pRags = new ChatSimple; cout << "Suppression de pRags... " << endl; delete pRags; cout << "Fin, Frisky disparait... " << endl; return 0; }
Les lignes 8 15 dclarent une classe ChatSimple minimale. La ligne 11 dclare son constructeur, qui est dni par les lignes 17 21. La ligne 12 dclare son destructeur, qui est dni par les lignes 23 26. Le constructeur et le destructeur afchent un message simple indiquant quils ont t appels. La ligne 31 cre Frisky comme une variable locale ordinaire, donc sur la pile. Cette cration fait appel au constructeur. Deux lignes plus loin, on cre galement un objet ChatSimple, qui sera point par pRags ; cette fois-ci, puisque nous utilisons un pointeur, cet objet sera cre sur le tas. L aussi, cette cration fait appel au constructeur.
228
Le langage C++
La ligne 35 appelle delete sur le pointeur pRags ; le destructeur est alors appel et la mmoire qui a t alloue pour contenir cet objet est libre. Lorsque la fonction prend n la ligne 38, lobjet Frisky devient hors de porte et il est supprim automatiquement laide du destructeur.
Les parenthses servent garantir que pRags sera drfrenc avant laccs la fonction GetAge() car les parenthses ont la priorit sur tous les autres oprateurs, y compris le point. Cette syntaxe pouvant devenir fastidieuse, C++ fournit un raccourci ce genre daccs indirect : loprateur daccs au membre dune classe (->). Cet oprateur se compose de deux caractres : un tiret court (-) et le signe suprieur (>). Dans le Listing 8.6, le programme utilise cet oprateur pour accder aux donnes et aux fonctions membres des objets crs sur le tas. Loprateur daccs au membre dune classe (->) pouvant aussi tre employ pour laccs indirect aux membres dun objet (via un pointeur), il peut galement tre considr comme un oprateur dindirection. Certains programmeurs lappellent dailleurs oprateur pointer sur car cest exactement ce quil fait.
Info
Chapitre 8
Pointeurs
229
6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26:
class SimpleChat { public: ChatSimple() {sonAge = 2; } ~ChatSimple() {} int GetAge() const { return sonAge; } void SetAge(int age) { sonAge = age; } private: int sonAge; }; int main() { using namespace std; ChatSimple * Frisky = new ChatSimple; cout << "Frisky a " << Frisky->GetAge() << " ans\n"; Frisky->SetAge(5); cout << "Frisky a " << Frisky->GetAge() << " ans\n"; delete Frisky; return 0; }
La ligne 20 cre une instance de ChatSimple sur le tas, dsigne par le pointeur Frisky. Le constructeur par dfaut de lobjet dnit lge 2. La mthode GetAge() extrait la valeur du pointeur, laide de loprateur -> (ligne 21). Frisky tant un pointeur, on utilise loprateur dindirection -> pour accder ses donnes et ses fonctions membres. La ligne 22 appelle la mthode SetAge() et la ligne suivante appelle nouveau GetAge().
230
Le langage C++
Chapitre 8
Pointeurs
231
La classe ChatSimple contient deux variables membres, qui sont toutes les deux des pointeurs vers des entiers (lignes 18 et 19). Le constructeur les initialise an quils pointent sur des emplacements du tas et affecte des valeurs par dfaut ces emplacements (lignes 22 26). Les lignes 24 et 25 utilisent un pseudo-constructeur sur le nouvel entier, en lui passant une valeur initiale ; cela a pour effet de crer un entier sur le tas et dinitialiser sa valeur ( 2 la ligne 24, et 5 la ligne 25). Le destructeur libre ensuite la mmoire (lignes 28 32). Il ny a pas besoin daffecter 0 ces pointeurs aprs la libration des zones quils pointaient puisquon est dans le destructeur. Cest lun des cas o il est possible de transgresser la rgle que nous avions mentionnes. La fonction appelante (main(), ici) ignore que les variables sonAge et sonPoids sont des pointeurs ; elle se contente dappeler les mthodes GetAge() et SetAge() sans connatre les dtails de la gestion mmoire car celle-ci est cache dans limplmentation de la classe, ce qui est une bonne chose. Lorsque Frisky est supprim la ligne 41, son destructeur est appel. Il libre alors les emplacements points par les deux membres. Si ces derniers pointaient sur des objets de classes dnies par lutilisateur, leurs destructeurs sexcuteraient galement.
Comprendre ce que lon fait Utiliser des pointeurs comme dans le Listing 8.7 serait assez ridicule dans un vrai programme, sauf sil y a de bonnes raisons pour que les objets Chat stockent leurs membres de cette faon. Ici, nous navions aucune raison dutiliser des pointeurs pour accder sonAge et sonPoids, mais cela pourrait tre trs utile dans dautres cas. Ceci implique une question vidente : que voulons-nous faire exactement lorsque lon utilise des pointeurs pour dsigner des variables au lieu de variables normales ? Ceci dmontre galement toute limportance de la conception. Si lon a conu un objet qui fait rfrence un autre objet et que le second objet peut natre avant le premier et continuer dexister aprs lui, le premier objet doit contenir une rfrence au second. Le premier objet pourrait, par exemple, tre une fentre et le second un document. La fentre doit pouvoir accder au document, mais elle ne contrle pas la dure de vie du document. Elle doit, par consquent, contenir une rfrence au document. Cette implmentation est ralise en C++ laide de pointeurs ou de rfrences. Ces dernires seront tudies au Chapitre 9.
232
Le langage C++
Le pointeur this
Toute mthode membre dune classe a un paramtre cach : le pointeur this, qui pointe sur lobjet sur laquelle elle est appele. Lorsque les fonctions GetAge() ou SetAge() sont appeles sur un objet particulier, elles reoivent en paramtre et en coulisses le pointeur this de cet objet Il est possible dutiliser le pointeur this de faon explicite, comme dans le Listing 8.8. Listing 8.8 : Utilisation du pointeur this
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: // Listing8.8 // Utilisation du pointeur this #include <iostream> class Rectangle { public: Rectangle(); ~Rectangle(); void SetLongueur(int longueur) { this->saLongueur = longueur; } int GetLongueur() const { return this->saLongueur; } void SetLargeur(int largeur) { saLargeur = largeur; } int GetLargeur() const { return saLargeur; } private: int saLongueur; int saLargeur; }; Rectangle::Rectangle() { saLargeur = 5; saLongueur = 10; } Rectangle::~Rectangle() {} int main() {
Chapitre 8
Pointeurs
233
36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49:
using namespace std; Rectangle unRect; cout << "unRect mesure " << unRect.LireLongueur() << " cm de long. " endl; cout << "unRect mesure " << unRect.LireLargeur() << " cm de large. " << endl; unRect.SetLongueur(20); unRect.SetLargeur(10); cout << "unRect mesure " << unRect.LireLongueur() << " cm de long. " << endl; cout << "unRect mesure " << unRect.LireLargeur() << " cm de large. " << endl; return 0; }
Les fonctions daccs SetLongueur() et GetLongueur() [lignes 11-12 et 13-14] utilisent toutes les deux explicitement le pointeur this pour accder aux variables membres de lobjet Rectangle, ce qui nest pas le cas de SetLargeur() et GetLargeur() [lignes 16 19]. Leur comportement est pourtant identique aux deux premires et la syntaxe des instructions est plus simple comprendre. Sil ne servait qu a, le pointeur this aurait peu dintrt, mais cest en ralit un outil puissant car, en tant que pointeur, il mmorise ladresse dun objet. Le Chapitre 10 prsentera un cas dutilisation de this, lorsque nous aborderons la surcharge des oprateurs. Pour le moment, vous pouvez vous contenter de savoir quil pointe sur lobjet sur lequel a t appel la mthode. Vous navez pas vous soucier de sa cration ou de sa suppression : le compilateur sen charge pour vous.
234
Le langage C++
Si vous tentez de lutiliser nouveau sans lavoir raffect, le rsultat est imprvisible et, dans le meilleur des cas, le programme plantera. Cest comme si le garage SuperAuto avait dmnag mais que vous continuiez utiliser le numro de tlphone que vous aviez mmoris dans votre combin. Cela pourrait ne pas avoir de consquences fcheuses un tlphone sonnerait dans une pice vide. En revanche, si ce numro a t raffect une usine de munitions, votre appel pourrait dclencher une explosion et soufer toute la ville ! En rsum, faites attention ne pas utiliser un pointeur qui a t supprim laide de delete. Il pointe toujours sur lancienne zone de mmoire, mais le compilateur a pu y mettre dautres donnes ; rutiliser ce pointeur sans lui rallouer un nouvel emplacement mmoire peut provoquer le plantage de votre programme. Pire encore, le programme peut continuer tranquillement son excution et se planter quelques minutes plus tard. Ce type derreur est une vritable bombe retardement. Pour plus de scurit, affectez la valeur nulle (0) tous les pointeurs que vous supprimez an de dsamorcer ces bombes. En jargon informatique, les pointeurs perdus sont parfois appels pointeurs fous, pointeurs incontrlables ou pointeurs pendants.
Info
Le Listing 8.9 illustre la cration dun pointeur perdu. Nexcutez pas ce programme ! Il cre dlibrment un pointeur perdu. Dans le meilleur des cas, il provoquera un plantage !
Info
Chapitre 8
Pointeurs
235
std::cout << "*pLong: " << *pLong << std::endl; *pInt = 20; // Attention, a t supprim!
std::cout << "*pInt: " << *pInt << std::endl; std::cout << "*pLong: " << *pLong << std::endl; delete pLong; return 0; }
(ne tentez pas dexcuter ce programme. Le rsultat que vous obtiendriez serait au mieux diffrent ; au pire, votre ordinateur planterait). La ligne 8 dnit pInt comme un pointeur sur un USHORT. la ligne suivante, la valeur 10 est place dans la mmoire alloue pInt. La valeur dsigne safche alors (ligne 10). Linstruction delete supprime ensuite lemplacement point par pInt, qui devient alors un pointeur perdu (aprs la ligne 11). La ligne 13 dclare un nouveau pointeur, pLong, et le fait pointer sur un nouvel emplacement laide de new. Cet emplacement la valeur 90 000 en ligne 14 et la ligne suivant afche cette valeur. Cest la ligne 17 que les problmes commencent. La ligne 17 affecte la valeur 20 lemplacement point par pInt alors que ce dernier ne pointe plus sur une adresse valide puisque la zone mmoire vers laquelle il pointait a t libre en ligne 11. Cette opration est la porte ouverte provoquera sans aucun doute un dsastre. La ligne 19 afche le contenu point par pInt,, cest--dire 20. La ligne suivante afche le contenu point par pLong et lon saperoit quil a soudainement t chang en 65 556. Ceci nous amne poser deux questions : 1. Pourquoi le contenu de pLong a-t-il chang spontanment alors quon ne la pas touch ? 2. Ou a t le nombre 20 utilis avec pInt la ligne 17 ? Comme vous pouvez le deviner, ces deux questions sont troitement lies. La ligne 17 a affect 20 la zone de mmoire sur laquelle pointait pInt au dbut du programme. Comme elle a t libre la ligne 11, le compilateur a pu la rutiliser. Lorsque le pointeur pLong a t cr la ligne 13, il a t associ lancienne zone mmoire pointe par pInt
236
Le langage C++
(sur certaines machines, il est possible que vous ne remarquiez rien selon les emplacements o ont t stockes ces valeurs). Lorsque la valeur 20 a t affecte lemplacement anciennement point par pInt, elle a donc cras la valeur pointe par pLong. On dit que le pointeur a t "cras" et cest lune des consquences possibles de lutilisation dun pointeur perdu. Il sagit dun bogue particulirement vicieux car la valeur qui a t modie ntait pas associe au pointeur perdu. Cette modication de la valeur pointe par pLong est un effet de bord de la mauvaise utilisation de pInt. Dans un programme trs long, une telle erreur serait trs difcile dtecter et corriger.
Pour la petite histoire, voici pourquoi ladresse mmoire de pLong contient le nombre 65 556 (Listing 8.9). 1. Alors que pInt pointait sur une adresse mmoire particulire, le nombre 10 lui a t affect. 2. Linstruction delete appele sur pInt a indiqu au compilateur quil pouvait rutiliser cet emplacement. Il la affect au pointeur pLong. 3. La valeur 90 000 a t affecte *pLong. Sur lordinateur utilis, cette valeur de type long de 4 octets (00015F 90) est stocke dans lordre inverse, en permutant les octets. Elle a donc t stocke comme 5F 900001. 4. pInt a t initialis 20 (soit 0014 en notation hexadcimale). Comme il pointait sur la mme adresse, les deux premiers octets de pLong ont t crass. Le contenu de ce dernier est donc devenu 00140001. 5. Au moment de lafchage de cette valeur, le systme a effectu une permutation doctets pour retrouver lordre correct 00010014, qui a t traduit dans sa valeur dcimale, 65556.
Quelle est la diffrence entre un pointeur nul et un pointeur perdu ? Lorsque vous supprimez un pointeur, vous demandez au compilateur de librer la mmoire, mais le pointeur lui-mme existe toujours. Il est devenu un pointeur perdu. Si vous crivez alors :
monPtr = 0;
de pointeur perdu, il devient pointeur nul. Normalement, si vous supprimez un pointeur et que vous le supprimez nouveau, votre programme est dans un tat indni, cest--dire que tout peut arriver. Si vous avez de la chance, le programme plantera. Si vous supprimez un pointeur nul, aucune consquence fcheuse nen rsultera.
Chapitre 8
Pointeurs
237
Lutilisation dun pointeur nul ou perdu (en crivant, par exemple monPtr = 5;) est illgal et peut provoquer un plantage. Avec un pointeur nul, le plantage est certain, ce qui est un autre avantage des pointeurs nuls par rapport aux pointeurs perdus car les plantages prvisibles sont plus faciles dboguer.
Pointeurs const
Vous pouvez utiliser le mot-cl const avec les pointeurs en le plaant avant et/ou aprs le type point. Les dclarations suivantes, par exemple, sont toutes correctes :
const int * pUn; int * const pDeux; const int * const pTrois;
pUn est un pointeur sur un entier constant. La valeur sur laquelle il pointe ne peut pas tre modie. pDeux est un pointeur constant sur un entier. La valeur sur laquelle il pointe peut tre modie, mais pDeux est constant et ne peut donc pas pointer sur un autre emplacement. pTrois est un pointeur constant sur un entier constant. La valeur sur laquelle il pointe ne peut pas tre modie et il ne peut pas pointer sur un autre emplacement.
Le mot-cl const sapplique llment qui gure immdiatement aprs lui. Si le type suit le mot-cl const, la valeur est constante. Si la variable se trouve droite du mot-cl const, cest la variable pointeur elle-mme qui est constante.
const int * p1; int * const p2; // lentier point est constant // le pointeur p2 est constant : // il ne peut pas pointer sur autre chose
238
Le langage C++
Chapitre 8
Pointeurs
239
42: 43: 44: 45: 46: 47: 48: 49: 49a: 50: 51: 51a: 52: 53: 54:
pRect->SetLargeur(10); // pRectConst->SetLargeur(10); pPtrConst->SetLargeur(10); cout << "Largeur de pRect: " << pRect->GetLargeur() << " m\n"; cout << "Largeur de pRectConst: " << pRectConst->GetLargeur() << " m\n"; cout << "Largeur de pPtrConst: " << pPtrConst->GetLargeur() << " m\n"; return 0; }
La classe Rectangle est dnie de la ligne 6 la ligne 19. La dclaration de la mthode constante GetLargeur() gure la ligne 14. La ligne 32 dclare un pointeur sur Rectangle, nomm pRect. La ligne 33 dclare un pointeur pRectConst vers un Rectangle constant alors que la ligne 34 dclare un pointeur constant pPtrConst vers un Rectangle. Les valeurs de ces trois pointeurs safchent aux lignes 36 41. la ligne 43, le pointeur pRect est utilis pour modier la largeur de son rectangle (10 mtres). la ligne 44, le pointeur pRectConst devrait faire de mme, mais il na pas le droit dappeler une fonction membre non constante car cest un pointeur vers un Rectangle constant. Ntant pas une instruction valide, nous lavons place en commentaire. la ligne 45, pPtrConst appelle son tour la fonction SetLargeur(). Comme cest un pointeur constant sur un rectangle, il ne peut pas pointer sur autre chose mais le rectangle lui-mme ntant pas constant, les mthodes comme GetLargeur() et SetLargeur() peuvent tre appeles.
240
Le langage C++
Ne pas faire
Utiliser un pointeur dont on a libr lempla-
pointeur.
Nous reviendrons sur les objets constants et les pointeurs constants dans le prochain chapitre, lorsque nous prsenterons les rfrences vers des objets constants.
Questions-rponses
Q Pourquoi les pointeurs jouent-ils un rle si important ? R Les pointeurs sont importants pour de nombreuses raisons. Ils permettent de stocker les adresses des objets et de les passer en paramtre par rfrence. Au Chapitre 14, vous verrez comment on les utilise pour mettre en uvre le polymorphisme de classe. En outre, de nombreux systmes dexploitation et bibliothques de classes crent des objets en votre nom et renvoient des pointeurs vers ces objets. Q quoi sert le tas ? R Les objets stocks sur le tas ne disparaissent pas aprs le retour dune fonction. La possibilit de stocker des objets sur le tas vous permet de dcider lors de lexcution du nombre dobjets dont vous avez besoin au lieu dtre oblig de les dclarer lavance. Ceci sera prsent plus en dtail dans le chapitre suivant. Q Pourquoi dclarer un objet constant si cela limite ce que je peux en faire ? R Comme tous les programmeurs, vous souhaitez que le compilateur dtecte les erreurs avant de crer le chier excutable. Une des erreurs les plus sournoises est le cas dune fonction qui modie un objet dune manire peu dtectable dans la fonction appelante. Pour protger les objets qui ne doivent pas tre mis jour, il suft de les dclarer laide du mot-cl const.
Chapitre 8
Pointeurs
241
Exercices
1. Que signient ces dclarations ? a. int * pUn; b. int vDeux; c. int * pTrois = &vDeux; 2. La variable votreAge est de type unsigned short. Dclarez un pointeur permettant de la manipuler. 3. Affectez la valeur 50 la variable votreAge en utilisant le pointeur dclar prcdemment. 4. crivez un petit programme dans lequel vous allez dclarer un entier et un pointeur vers un entier. Affectez tout dabord ladresse de lentier au pointeur, puis utilisez le pointeur pour affecter une valeur la variable entire. 5. CHERCHEZ LERREUR : o lerreur se cache-t-elle dans ce programme ?
#include <iostream> using namespace std; int main() { int *pInt; *pInt = 9; cout << "Valeur pInt: " << *pInt; return 0; }
242
Le langage C++
9
Rfrences
Au sommaire de ce chapitre
Nature et rle des rfrences Diffrences entre les rfrences et les pointeurs Cration et utilisation des rfrences Limites dutilisation des rfrences Passage par rfrence de valeurs et dobjets des fonctions et rcupration des valeurs renvoyes
Dans le chapitre prcdent, vous avez appris grer des objets sur le tas et vous y rfrer de faon indirecte. Les rfrences prsentent quasiment toutes les fonctionnalits des pointeurs, mais avec une syntaxe beaucoup plus simple.
244
Le langage C++
Dans cette instruction, rReference est une rfrence une variable de type int, VariableEntiere. Les rfrences sont diffrentes des autres variables que vous pouvez dclarer car elles doivent tre initialises lors de leur dclaration. Si vous tentez de crer une variable rfrence sans lui fournir une valeur initiale, le compilateur produit une erreur. Le Listing 9.1 montre comment crer et utiliser des rfrences.
Loprateur rfrence et loprateur adresse de utilisent le mme symbole (&). Bien qutroitement lis, ces oprateurs sont diffrents. Lespace avant loprateur rfrence est obligatoire, mais celui entre loprateur et le nom de la variable est facultatif. Par consquent ces deux instructions sont correctes :
int &rReference = VariableEntiere; // syntaxe OK int & rReference = VariableEntiere; // syntaxe OK
Info
Chapitre 9
Rfrences
245
rUneRef = 7; cout << "intUn: " << intUn << endl; cout << "rUneRef: " << rUneRef << endl; return 0; }
La ligne 8 dclare la variable locale intUn de type entier. La ligne suivante dclare une rfrence un entier, rUneRef, et linitialise pour quelle dsigne intUn. Comme on la dj mentionn, si vous oubliez dinitialiser une rfrence, le compilateur renvoie une erreur et ne cre pas le chier objet. La ligne 11 affecte 5 la variable intUn. Les valeurs contenues dans intUe et rUneRef sont ensuite afches aux lignes 12 et 13. Bien entendu, elles sont gales. La ligne 15 affecte 7 rUneRef. Comme il sagit dun alias de la variable intUn, cette dernire reoit galement la valeur 7, comme le montre les afchages des lignes 16 et 17.
246
Le langage C++
9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19:
int &rUneRef = intUn; intOne = 5; cout << "intUn : " << intUn << endl; cout << "rUneRef : " << rUneRef << endl; cout << "&intUn : " << &intUn << endl; cout << "&rUneRef : " << &rUneRef << endl; return 0; }
ntion Atte
Les deux dernires lignes afchent des adresses mmoire qui risquent dtre particulires votre ordinateur ou une excution du programme ; le rsultat que vous obtiendrez sera srement diffrent.
rUneRef est nouveau initialise pour faire rfrence la variable intUn. Les adresses des deux variables safchent : elles sont identiques (lignes 15 et 16). C++ ne permet pas daccder directement ladresse dune rfrence car cela naurait aucun intrt, contrairement ladresse dun pointeur ou dune variable. Les rfrences tant toujours initialises lors de leur cration, elles se comportent toujours comme un synonyme de leur cible, mme si on leur applique loprateur adresse de. Supposons que lon dispose dune classe President ; vous pourriez crer une instance de celle-ci avec linstruction suivante :
President UnPrez;
Vous pouvez ensuite crer une rfrence de President et linitialiser avec cet objet :
President &ChefNation = unPrez;
Un seul President existe. Les deux identicateurs dsignent le mme objet de la mme classe. Toute action effectue sur ChefNation se rpercutera donc sur UnPrez. Il faut bien faite la distinction entre le symbole & gurant la ligne 9 du Listing 9.2 qui dclare une rfrence rUneRef une variable entire et les mmes symboles aux lignes 15 et 16 qui renvoient les adresses de la variable entire intUn et de la rfrence
Chapitre 9
Rfrences
247
rUneRef. Le compilateur fait la distinction entre ces deux utilisations grce leur contexte dutilisation. Lutilisation dune rfrence ne ncessite pas loprateur adresse de. Il suft dutiliser la rfrence comme vous utiliseriez la variable cible.
Info
" << intUn << endl; " << rUneRef << endl; " << &intUn << endl; " << &rUneRef << endl;
int intDeux = 8; rUneRef = intDeux; // diffrent de ce que vous croyez! cout << "\nintUn : " << intUn << endl; cout << "intDeux : " << intDeux << endl; cout << "rUneRef : " << rUneRef << endl; cout << "&intUn: " << &intUn << endl; cout << "&intDeux : " << &intDeux << endl; cout << "&rUneRef : " << &rUneRef << endl; return 0; }
248
Le langage C++
Cette fois encore, une variable entire et une rfrence un entier sont dclares aux lignes 8 et 9. La ligne 11 affecte 5 lentier et les valeurs et leurs adresses sont afches aux lignes 12 15. La ligne 17 dnit et initialise la variable intDeux. la ligne suivante, le programmeur tente de raffecter rUneRef pour quelle devienne un alias de cette nouvelle variable, mais ce nest pas ce qui se passe en ralit : rUneRef continue dtre un alias de intUn et cette affectation est donc quivalente linstruction suivante :
intUne = intDeux;
Comme lafchage des lignes 19 21 le prouve, intUn, rUneRef et intDeux sont dsormais gales. Lafchage des adresses prouve galement que rUneRef fait toujours rfrence intUn, pas intDeux.
Faire
Utiliser les rfrences pour crer des alias
Ne pas faire
Tenter de raffecter une rfrence. Confondre loprateur adresse de et lop-
dobjets.
Initialiser toutes les rfrences.
rateur rfrence.
Chapitre 9
Rfrences
249
La rfrence rRefInt doit tre initialise avec un entier spcique, comme ici :
int Valeur = 200; int & rRefInt = Valeur;
De la mme faon, il est interdit dinitialiser une rfrence avec la classe Chat :
Chat & rRefChat = Chat; // erreur
Vous devez initialiser une rfrence avec un objet de la classe Chat, comme ici :
Chat Mistigri; Chat & rRefChat = Mistigri;
La rfrence un objet est utilise comme lobjet lui-mme. Ainsi, les donnes et les mthodes membres sont accessibles comme avec nimporte quel objet de la classe, laide de loprateur point (.), comme le montre le Listing 9.4. Listing 9.4 : Rfrences aux objets
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: // Listing9.4 - Rfrences des objets dune classe #include <iostream> class SimpleChat { public: SimpleChat (int age, int poids); ~SimpleChat() {} int GetAge() { return sonAge; } int GetPoids() { return sonPoids; } private: int sonAge; int sonPoids; }; SimpleChat::SimpleChat(int age, int poids) { sonAge = age; sonPoids = weight; } int main() { SimpleChat Frisky(5,4); SimpleChat & rChat = Frisky;
250
Le langage C++
"Frisky a "; Frisky.GetAge() << " ans. " << std::endl; "Frisky pse "; rChat.GetPoids() << " kilos." std::endl;
La ligne 25 dclare Frisky comme un objet de la classe SimpleChat. La ligne suivante cre une rfrence rChat vers un SimpleChat et linitialise avec lobjet Frisky. Les lignes 29 31 font appel aux mthodes accesseurs en utilisant dabord lobjet lui-mme, puis sa rfrence. On remarque ce ces deux accs utilisent la mme syntaxe puisquici aussi, une rfrence est un alias de lobjet rel.
Rfrences Les rfrences agissent comme un alias de variable. Dclarer une rfrence consiste en indiquer le type, suivi de loprateur rfrence (&) et du nom de la rfrence. Les rfrences doivent tre initialises lors de leur cration. Exemple 1
Chapitre 9
Rfrences
251
donc dinitialiser une rfrence zro ou une autre valeur numrique ; le programme ne se plantera que si vous tentez dutiliser lobjet et que cette rfrence est invalide. Il nest cependant pas conseill dutiliser cette possibilit pour une programmation classique. En effet, si un tel programme est port dune machine une autre, de mystrieuses erreurs pourraient apparatre si vous utilisez des rfrences nulles.
252
Le langage C++
10: 11: 11a: 12: 13: 13a: 14: 15: 16: 17: 18: 19: 20: 21: 21a: 22: 23: 24: 25: 26: 27: 27a: 28: } cout << "Dans Swap. Aprs change, x : " << x << " y : " << y << endl; temp = x; x = y; y = temp; cout << "Dans Swap. Avant change, x : " << x << " y: " << y << endl; void swap (int x, int y) { int temp; } cout << "Dans Main. Avant change, x : " << x << " y : " << y << endl; swap(x, y); cout << "Dans Main. Aprs change, x : " << x << " y : " << y << endl; return 0;
Ce programme initialise deux variables dans la fonction main(), puis les passe la fonction swap() qui semble les intervertir. De retour dans la fonction principale, ces valeurs nont pas t modies ! Le problme, ici, est que x et y ont t passes par valeur et que la fonction en a donc cr des copies locales. Ces copies ont donc t modies et ont t ensuite supprimes la n de la fonction, en mme temps que ses variables locales. Il est prfrable de les passer par rfrence, an de modier les valeurs sources des variables, et non des copies locales. Pour ce faire, vous avez deux solutions en C++ : soit vous faites en sorte que les paramtres de la fonction swap() soient des pointeurs sur les valeurs dorigine, soit vous passez en paramtre des rfrences aux valeurs originales.
Chapitre 9
Rfrences
253
254
Le langage C++
a marche ! la ligne 5, le prototype de la fonction swap() indique que les paramtres sont dsormais des pointeurs vers des entiers au lieu de variables entires. Lors de lappel la fonction, en ligne 12, on passe en paramtre les adresses de x et de y : on peut voir que ce sont des adresses qui sont passes car lon utilise loprateur adresse de (&). La ligne 19 dclare la variable locale temp dans la fonction swap(). Il ne sagit pas dun pointeur, mais dune variable qui contiendra la valeur de *px (cest--dire la valeur de lemplacement point par px, qui correspond celle de x dans la fonction appelante). Lorsque la fonction se termine, cette variable locale disparait de la pile. La valeur ladresse px est affecte temp la ligne 24, lemplacement point par px recevant ensuite la valeur situe ladresse py (ligne 25). La valeur stocke dans la variable temp est ensuite copie dans lemplacement point par py. Les valeurs de la fonction appelante dont on a transmis les adresses la fonction swap() sont ainsi interverties.
Chapitre 9
Rfrences
255
cout << "Dans Swap. Aprs change, rx : " << rx << " ry : " << ry << endl; }
256
Le langage C++
Comme dans lexemple prcdent, le programme commence par dclarer deux variables (ligne 10) et afche leurs valeurs (ligne 12). La ligne 15 appelle la fonction swap() en lui passant en paramtre les variables x et y (et non leurs adresses). Le programme va alors directement la ligne 23, o ces paramtres sont identis comme des rfrences. Les valeurs de ces variables sont afches la ligne 27 et vous pouvez constater quil nest pas ncessaire dutiliser des oprateurs spciaux : ces variables sont des alias des variables dorigine et peuvent donc tre utilises comme telles. Une fois interverties (lignes 30 32), on afche nouveau les valeurs puis le programme revient en la ligne 17 pour afcher les variables de la fonction main(). Les paramtres de swap() tant des rfrences, les oprations ralises dans la fonction appele se sont rpercutes au variables initiales de la fonction appelante. Comme on le voit dans ce listing, les rfrences permettent donc dutiliser des variables normales et de disposer de toute la puissance du passage par rfrence avec les pointeurs !
Chapitre 9
Rfrences
257
mise en uvre des matriaux, sans soccuper de savoir comment ils ont t fabriqus du moment quils correspondent au cahier des charges. La dmarche est la mme en C++. Les programmeurs utilisent des classes et des fonctions prouves sans se proccuper de leur fonctionnement internes. Ces "composants logiciels" peuvent tre assembls pour produire un programme, exactement comme les cbles, les briques et le ciment sont assembls pour fabriquer des immeubles et des ponts. Comme un ingnieur des ponts et chausses se reporte la documentation technique pour connatre la capacit, le volume et le dbit dun tuyau dvacuation, par exemple, le dveloppeur examine la dclaration dune fonction ou dune classe pour connatre les services quelle fournit, les paramtres quelle attend et les rsultats quelle produit.
258
Le langage C++
18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41:
if (!erreur) { cout << "Nombre: " << nombre << endl; cout << "Carr: " << carre << endl; cout << "Cube: " << cube << endl; } else cout << "Erreur!" << endl; return 0; } short Facteur(int n, int *pCarre, int *pCube) { short Valeur = 0; if (n > 20) Valeur = 1; else { *pCarre = n*n; *pCube = n*n*n; Valeur = 0; } return Valeur; }
La ligne 10 dnit les variables de type entier court, nombre, carre, et cube. Puis nombre reoit la valeur saisie par lutilisateur la ligne 14. La ligne 16 transmet cette valeur et les adresses de carre et cube la fonction Facteur(). La ligne 32 examine le premier paramtre, qui a t pass par valeur. Sil est suprieur 20 (valeur maximale gre par la fonction), la valeur renvoye (Valeur) correspondra une erreur, 0 indiquant que tout sest bien pass. Valeur est renvoye la fonction appelante la ligne 40. Les valeurs correspondant au carr et au cube du nombre saisi ne sont pas renvoyes de manire traditionnelle mais en modiant les emplacements points par les deux paramtres pointeurs passs la fonction. Les valeurs de renvoi sont donc affectes aux pointeurs (lignes 36 et 37). Ces valeurs sont affectes aux variables initiales au moyen du mcanisme dindirection, que lon peut
Chapitre 9
Rfrences
259
reprer par lutilisation de loprateur de drfrencement (*) sur les noms de pointeurs. la ligne 38, Valeur reoit la valeur 0 signiant une n normale du traitement et elle est renvoye lappelant la ligne 40. Le passage par rfrence ou par pointeur permettant un accs non contrl aux attributs dobjets et aux mthodes, il est conseill de ne passer que le minimum requis pour que la fonction ralise sa tche. Cest un gage de scurit et de lisibilit.
ce Astu
260
Le langage C++
25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43:
cout << "Carr: " << carre << endl; cout << "Cube: " << cube << endl; } else cout << "Erreur!" << endl; return 0; } CODE_ERREUR Facteur(int n, int &rCarre, &rCube) { if (n > 20) return ECHEC; // simple code derreur else { rCarre = n*n; rCube = n*n*n; return SUCCES; } }
Le Listing 9.9 est identique au prcdent, deux exceptions prs : lnumration CODE_ERREUR permet de renvoyer un code derreur bien plus explicite (lignes 36 et 41) et la gestion des erreurs est plus complte (ligne 22). Notez que la fonction Facteur() est dsormais dclare pour attendre des rfrences aux variables carre et cube, et non des pointeurs. Ceci facilite la fois la manipulation de ces paramtres et la relecture du code.
Chapitre 9
Rfrences
261
En revanche, ils ne le sont plus du tout pour les objets plus volumineux, crs par lutilisateur. Dans la pile, la taille dun tel objet correspond en effet la somme de toutes les variables membres qui le composent. Ces variables sont parfois elles-mmes des objets dnis par lutilisateur. Passer en paramtre une structure aussi massive en la copiant sur la pile peut avoir un impact important sur les performances et la consommation mmoire du programme. Un autre cot sajoute encore. Avec les classes que vous crez, chacune de ces copies temporaires est cre par un appel un constructeur spcial, appel constructeur de copie, qui est automatiquement appel par le compilateur . Au Chapitre 10, vous apprendrez les utiliser et crer vos propres constructeurs de copies. Pour linstant, il vous suft de savoir que le constructeur de copie est appel chaque fois quune copie temporaire de lobjet est plac sur la pile. Lorsque cet objet temporaire est dtruit la n de lexcution de la fonction, son destructeur est appel. Si la fonction renvoie un objet par valeur, il faut galement faire une copie de cet objet puis le dtruire. Avec des objets volumineux, ces appels de constructeurs et de destructeurs peuvent avoir un impact sur la vitesse dexcution du programme et sur son utilisation de la mmoire. Pour illustrer ce phnomne, le Listing 9.10 prsente une version pure de la classe SimpleChat les vritables objets manipuls par les programmes sont plus gros et plus coteux, mais cet exemple suft montrer la frquence des appels du constructeur de copie et du destructeur. Listing 9.10 : Passage dobjets par rfrence
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: //Listing9.10 - Passage de pointeurs sur des objets #include <iostream> using namespace std; class SimpleChat { public: SimpleChat (); SimpleChat(SimpleChat&); ~SimpleChat(); };
262
Le langage C++
19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55:
SimpleChat::SimpleChat(SimpleChat&) { cout << "Constructeur de copie de SimpleChat..." << endl; } SimpleChat::~SimpleChat() { cout << "Destructeur de SimpleChat..." << endl; } SimpleChat FonctionUn (SimpleChat leChat); SimpleChat* FonctionDeux (SimpleChat *leChat); int main() { cout << "Cration dun objet Chat..." << endl; SimpleChat Frisky; cout << "Appel de FonctionUn..." << endl; FonctionUn(Frisky); cout << "Appel de FonctionDeux..." << endl; FonctionDeux(&Frisky); return 0; } // FonctionUn, passage par valeur SimpleChat FonctionUn(SimpleChat leChat) { cout << "Retour de FonctionUn..." << endl; return leChat; } // FonctionDeux, passage par rfrence SimpleChat* FonctionDeux(SimpleChat *leChat) { cout << "Retour de FonctionDeux..." << endl; return leChat; }
Chapitre 9
Rfrences
263
Constructeur de copie de SimpleChat... Destructeur de SimpleChat... Destructeur de SimpleChat... Appel de FonctionDeux... Retour de FonctionDeux... Destructeur de SimpleChat...
Le Listing 9.10 cre lobjet SimpleChat, puis appelle deux fonctions. La premire reoit leChat par valeur, puis le renvoie par valeur. La seconde reoit un pointeur vers lobjet, et non lobjet lui-mme, puis renvoie un pointeur vers lobjet. Les lignes 6 12 dclarer une classe SimpleChat minimale. Le constructeur, le constructeur de copie et le destructeur se contentent de produire un message pour vous informer du droulement du programme. la ligne 34, la fonction main() afche le premier message qui apparat dans le rsultat. La ligne suivante instancie un objet SimpleChat, ce qui provoque lappel du constructeur et donc lafchage de la deuxime ligne du rsultat La ligne 36 signale lappel de FonctionUn(). Cette dernire reoit lobjet SimpleChat par valeur, ce qui provoque la cration dune copie locale sur la pile et donc lappel du constructeur de copie, qui afche la quatrime ligne du rsultat. Le programme va ensuite directement la ligne 46, dans la fonction appele qui afche la cinquime ligne du rsultat. La fonction principale reprend la main et rcupre lobjet SimpleChat renvoy par valeur, ce qui a pour effet de crer une autre copie de lobjet et donc dappeler le constructeur de copie qui afche la sixime ligne du rsultat. La valeur renvoye par FonctionUn() ntant affecte aucun objet, le destructeur supprime lobjet temporaire et afche la septime ligne. FonctionUn() stant termine, sa copie locale devient hors de porte et est dtruite par le destructeur qui afche la huitime ligne. La fonction principale reprend le contrle et appelle la fonction FonctionDeux() en passant le paramtre par rfrence, ce qui ne produit pas de copie de lobjet sur la pile. FonctionDeux() afche le message qui apparat la dixime ligne du rsultat et renvoie lobjet SimpleChat par rfrence, ce qui vite l aussi dappeler le constructeur et le destructeur. Enn, le programme se termine et Frisky sort de sa porte, ce qui cause un dernier appel au destructeur, qui afche la dernire ligne du rsultat. La fonction FonctionUn() ayant reu le paramtre SimpleChat par valeur a donc provoqu deux appels du constructeur de copie et deux appels du destructeur, alors que FonctionDeux() nen a produit aucun.
264
Le langage C++
Chapitre 9
Rfrences
265
25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68:
SimpleChat::SimpleChat(SimpleChat&) { cout << "Constructeur de copie de SimpleChat..." << endl; } SimpleChat::~SimpleChat() { cout << "Destructeur de SimpleChat..." << endl; } const SimpleChat * const FonctionDeux (const SimpleChat * const leChat); int main() { cout << "Cration dun objet Chat..." << endl; SimpleChat Frisky; cout << "Frisky a "; cout << Frisky.GetAge(); cout << " ans" << endl; int age = 5; Frisky.SetAge(age); cout << "Frisky a "; cout << Frisky.GetAge(); cout << " ans" << endl; cout << "Appel de FonctionDeux..." << endl; FonctionDeux(&Frisky); cout << "Frisky a "; cout << Frisky.GetAge(); cout << " ans" << endl; return 0; } // FonctionDeux attend un pointeur const const SimpleChat * const FunctionTwo (const SimpleChat * const leChat) { cout << "Fin de FonctionDeux..." << endl; cout << "Frisky a maintenant " << leChat->GetAge(); cout << " ans " << endl; // leChat->SetAge(8); const! return leChat; }
266
Le langage C++
SimpleChat contient deux fonctions daccs : lune est constante (GetAge(), la ligne 13), lautre, non constante (SetAge(), la ligne 14). La variable membre sonAge est dnie la ligne 17. Les appels respectifs au constructeur, au constructeur de copie et au destructeur sont signals par des messages. On constate que le constructeur de copie nest jamais appel puisque lobjet est pass par rfrence et quaucune copie nest donc ralise. La ligne 42 cre un objet dont lge par dfaut est afch par la ligne suivante. La ligne 47 modie la variable sonAge en appelant la mthode daccs SetAge() et la ligne suivante afche cette nouvelle valeur. FonctionDeux() a t lgrement modie : son paramtre, comme sa valeur de retour, est dsormais un pointeur constant sur un objet constant (ligne 36). Le paramtre et la valeur de renvoi tant toujours passs par rfrence, aucune copie nest ralise et le constructeur de copie nest donc pas sollicit. Cependant, lobjet point dans la fonction FonctionDeux() est dsormais constant et ne peut donc pas appeler la fonction SetAge() qui, elle, ne lest pas. Si la ligne 66 navait pas t mise en commentaire, le programme naurait pas pu pourrait pas tre compil. Lobjet cr dans la fonction main() ntant pas constant, Frisky peut appeler SetAge(). Ladresse de cet objet non constant est passe la fonction FonctionDeux() mais, comme cette fonction dclare son paramtre comme tant un pointeur constant vers un objet constant, lobjet est trait comme sil tait constant !
Chapitre 9
Rfrences
267
Comme on sait que ces objets ne seront jamais nuls, il serait plus simple de travailler sur des rfrences, comme le montre le Listing 9.12. Listing 9.12 : Passage de rfrences des objets
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: //Listing9.12 - Passage de rfrences des objets #include <iostream> using namespace std; class SimpleChat { public: SimpleChat(); SimpleChat(SimpleChat&); ~SimpleChat(); int GetAge() const { return sonAge; } void SetAge(int age) { sonAge = age; } private: int sonAge; }; SimpleChat::SimpleChat() { cout << "Constructeur de SimpleChat..." << endl; wAge = 2; } SimpleChat::SimpleChat(SimpleChat&) { cout << "Constructeur de copie de SimpleChat..." << endl; } SimpleChat::~SimpleChat() { cout << "Destructeur de SimpleChat..." << endl; } const SimpleChat & FonctionDeux(const SimpleChat & leChat); int main() { cout << "Cration dun objet Chat..." << endl; SimpleChat Frisky; cout << "Frisky a " << Frisky.GetAge() << " ans" << endl;
268
Le langage C++
43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60:
int age = 5; Frisky.DefAge(age); cout << "Frisky a " << Frisky.GetAge() << " ans" << endl; cout << "Appel de FonctionDeux...\n"; FonctionDeux(Frisky); cout << "Frisky a " << Frisky.GetAge() << " ans" << endl; return 0; } // FonctionDeux, attend une rfrence un objet const const SimpleChat & FonctionDeux(const SimpleChat & leChat) { cout << "Fin de FonctionDeux..." << endl; cout << "Frisky a maintenant " << leChat.GetAge(); cout << " ans " << endl; // leChat.SetAge(8); const ! return leChat; }
Le rsultat est identique celui du Listing 9.11. La fonction FonctionDeux() attend et renvoie dsormais une rfrence un objet constant. Vous pouvez constater une nouvelle fois quil est plus simple de travailler avec des rfrences quavec des pointeurs et que lon en tire les mmes avantages en termes de performances, tout en conservant la scurit offerte par const.
Rfrences const En gnral, les programmeurs C++ ne font pas la diffrence entre une "rfrence constante un objet de la classe" et une "rfrence un objet constant de la classe". Il est vrai quune rfrence est toujours constante, puisquelle ne peut jamais tre raffecte un autre objet. Lorsque le mot-cl const sapplique une rfrence, il concerne donc lobjet rfrenc.
Chapitre 9
Rfrences
269
Dans cet exemple, le pointeur pInt est dclar et initialis avec ladresse mmoire renvoye par loprateur new. Ladresse contenue dans ce pointeur est ensuite teste. Si elle nest pas nulle, pInt est drfrenc. Le rsultat de lopration produit un objet entier et rInt est initialis pour y faire rfrence. rInt est donc devenu un alias de lentier renvoy par new.
Faire
Passer des paramtres par rfrence le plus
Ne pas faire
Utiliser des pointeurs lorsquil est possible de
souvent possible.
Protger les rfrences et les pointeurs
se servir de rfrences.
Tenter de raffecter une rfrence une autre
270
Le langage C++
Cette dclaration indique que Fonction attend trois paramtres. Le premier est une rfrence un objet Utilisateur, le deuxime est un pointeur sur un objet Objet et le troisime est un entier pass par valeur. Elle renvoie un pointeur vers un objet Chat. Lemplacement de loprateur dindirection (*) et de loprateur de rfrence (&) dans ces dclarations de paramtres est sujet controverse parmi les programmeurs. Les trois critures suivantes sont tout fait correctes lorsque vous voulez dclarer une rfrence :
1: 2: 3: Chat& rMistigri; Chat & rMistigri; Chat &rMistigri;
Les espaces ntant pas pris en compte au niveau syntaxique, vous pouvez insrer autant de tabulations, despaces et de lignes que vous le souhaitez. La question est de savoir quelle est lexpression la plus adapte ? Voici des arguments pour ces trois syntaxes : Dans le premier cas, le paramtre est une variable appele rMistigri, dont le type peut tre considr comme une "rfrence un objet Chat". Loprateur & doit donc tre associ au type. Le contre-argument est que le type est Chat. Loprateur & fait partie du "dclarateur", qui comprend le nom de la variable et lesperluette. Lorsquil est coll au nom du type, loprateur est source derreur dans lexpression suivante :
Chat& rMistigri, rFrisky;
A priori, rMistigri et rFrisky laissent penser quil sagit de rfrences des objets de la classe Chat. En fait, le premier lment est ici une rfrence un objet Chat, alors que rFrisky (malgr son nom) est une variable classique de la classe Chat. Cette ligne devrait donc tre crite de la faon suivante :
Chat &rMistigri, rFrisky;
La rponse cette objection est quil ne faudrait jamais mlanger de cette faon les dclarations de rfrences et de variables. Voici les dclarations quivalentes correctes :
Chat& rMistigri; Chat Frisky;
La plupart des programmeurs optent gnralement pour une solution intermdiaire, en plaant loprateur entre les lments de la liste (voir la deuxime proposition). Bien entendu, ce qui sapplique loprateur rfrence (&) concerne galement loprateur dindirection (*). Chacun utilise les oprateurs sa faon, lessentiel tant de rester homogne et cohrent dans les programmes.
Chapitre 9
Rfrences
271
Info
Les deux conventions suivantes sont frquemment adoptes pour la dclaration de pointeurs et de rfrences : Lesperluette et lastrisque occupent une position mdiane, en tant prcds et suivis dun espace. Ne jamais dclarer des rfrences, des pointeurs et des variables sur la mme ligne.
272
Le langage C++
26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39:
int main() { SimpleChat &rChat = UneFonction(); int age = rChat.GetAge(); std::cout << "rChat a " << age << " ans!" << std::endl; return 0; } SimpleChat &UneFonction() { SimpleChat Frisky(5, 9); return Frisky; }
ntion Atte
Ce programme ne pourra pas tre compil avec un compilateur Borland, alors quil le sera avec un compilateur Microsoft ou avec le compilateur GNU. Cette faon de procder nest toutefois pas recommande.
La classe SimpleChat est dclare de la ligne 7 la ligne 17. Une rfrence un objet SimpleChat est ensuite initialise avec le rsultat de lappel la fonction UneFonction() (ligne 29), qui est dclare comme devant renvoyer une rfrence cette classe, comme lindique la ligne 25. Le corps de la fonction UneFonction() [lignes 35-39] dclare un objet local de type SimpleChat, et initialise son ge et son poids. Cet objet est ensuite renvoy par rfrence la fonction appelante. Certains compilateurs sont capables de dtecter cette erreur et donc dempcher la cration du chier excutable, alors que dautres compilent le chier source (en produisant ventuellement un message davertissement) et crent ainsi un programme qui produira des rsultats imprvisibles. Lorsque la fonction UneFonction() se termine, lobjet local Frisky est dtruit. La rfrence renvoye par cette fonction est donc un alias dun objet qui nexiste plus, ce qui est incorrect.
Chapitre 9
Rfrences
273
274
Le langage C++
ntion Atte
Une fois compil, ce programme semble fonctionner correctement. Il sagit en fait dune vritable bombe retardement.
La fonction UneFonction() [lignes 39 44] ne renvoie plus de rfrence une variable locale. La mmoire est alloue sur le tas et affecte un pointeur la ligne 41. Ladresse correspondante est afche, puis le pointeur est drfrenc, ce qui permet de renvoyer lobjet SimpleChat par rfrence. La ligne 28 affecte le rsultat de cette fonction une rfrence de SimpleChat et cet objet est utilis pour obtenir lge du chat, qui est afch la ligne 30. La rfrence dclare dans la fonction main() dsigne lobjet plac sur le tas par UneFonction(). Pour le prouver, on applique loprateur adresse de rChat. Les deux adresses qui safchent renvoient la mme valeur : lobjet cit par rfrence et son adresse dans le tas sont identiques. Pour linstant, tout va bien, mais comment librer cet espace mmoire ? Souvenez-vous quil est interdit dappeler delete sur une rfrence. Une solution astucieuse consiste crer un autre pointeur, pour linitialiser ensuite avec ladresse obtenue par rChat. La mmoire est libre et on vite ainsi une fuite mmoire. Mais un autre problme subsiste : sur quel objet la rfrence rChat porte-t-elle aprs la ligne 34 ? Comme vous lavez vu plus haut, une rfrence doit toujours tre lalias dun objet existant non nul. Si lobjet est nul ou nexiste pas (comme cest le cas ici), le programme nest pas valide. Nutilisez jamais de rfrences des objets nuls. En effet, les programmes peuvent parfois tre compils, mais ils produiront des erreurs inattendues lors de leur excution.
Info
Il existe trois solutions ce problme. La premire consiste dclarer un objet SimpleChat la ligne 28, puis le renvoyer par valeur la fonction appelante. La deuxime consiste dclarer un objet SimpleChat sur le tas dans UneFonction() mais faire en sorte que celle-ci renvoie un pointeur vers cet emplacement : la fonction appelante pourra alors librer la zone pointe par ce pointeur lorsquelle nen aura plus besoin. Enn, la dernire solution (de loin la meilleure) consiste dclarer lobjet dans la fonction appelante, puis le passer par rfrence UneFonction().
Chapitre 9
Rfrences
275
Ne pas faire
Renvoyer un lment par rfrence si sa
si ncessaire.
la mmoire est alloue car vous risquez ainsi de ne pas savoir si elle est libre.
Questions-rponses
Q quoi bon utiliser des rfrences alors que les pointeurs sont aussi performants ? R Les rfrences sont plus faciles employer et comprendre. Lindirection est cache, et il est inutile de drfrencer la variable chaque accs. Q Si les rfrences sont plus faciles mettre en uvre, pourquoi utilise-t-on encore des pointeurs ? R Les rfrences ne peuvent jamais tre nulles et ne peuvent pas tre raffectes. Les pointeurs offrent plus de souplesse, mais sont un peu plus difciles utiliser.
276
Le langage C++
Q Pourquoi le rsultat dune fonction doit-il tre renvoy par valeur ? R Si lobjet renvoy est local la fonction, vous devez le renvoyer par valeur sous peine de renvoyer une rfrence vers un objet inexistant. Q Puisque le renvoi par rfrence est dangereux, pourquoi ne pas toujours renvoyer un rsultat par valeur ? R Le renvoi par rfrence est plus efcace. Il conomise la mmoire et le programme sexcute plus vite.
Exercices
1. Dans un programme, dclarez une variable entire, une rfrence cet objet et un pointeur vers un entier. Utilisez le pointeur et la rfrence pour manipuler la valeur de lentier. 2. crivez un programme dclarant un pointeur constant sur un entier constant. Initialisez le pointeur avec ladresse dune variable entire Var1. Affectez 6 Var1 et utilisez le pointeur pour affecter 7 Var1. Crez ensuite une autre variable entire Var2 et raffectez le pour quil pointe sur Var2. Ne compilez pas encore cet exercice. 3. Compilez maintenant le programme de lExercice 2. Produit-il des erreurs ? Quels sont les messages davertissement ? 4. crivez un programme qui cre un pointeur perdu.
Chapitre 9
Rfrences
277
5. Corrigez ce programme. 6. crivez un programme provoquant une fuite mmoire. 7. Corrigez ce programme. 8. CHERCHEZ LERREUR : o se cache lerreur dans ce programme ?
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: #include <iostream> using namespace std; class CHAT { public: CHAT(int age) { sonAge = age; } ~CHAT(){} int GetAge() const { return sonAge;} private: int sonAge; }; CHAT & CreerChat(int age); int main() { int age = 7; CHAT Felix = CreerChat(age); cout << "Felix a " << Felix.GetAge() << " ans" << endl; return 0; } CHAT & CreerChat(int age) { CHAT * pChat = new CHAT(age); return *pChat; }
10
Fonctions avances
Au sommaire de ce chapitre
La surcharge de fonctions membres La surcharge des oprateurs Les fonctions grant des classes avec des variables membres alloues dynamiquement
Au Chapitre 5, vous avez appris utiliser des fonctions. Vous connaissez prsent le fonctionnement des pointeurs et des rfrences. Dans ce chapitre, nous allons approfondir notre connaissance des fonctions.
280
Le langage C++
Dans le Listing 10.1, la classe Rectangle contient deux fonctions DessinerForme(). Lune ne prend aucun paramtre et trace le rectangle en fonction de ses valeurs courantes alors que lautre prend deux valeurs (largeur et hauteur) et les utilise pour tracer le rectangle sans tenir compte de ses valeurs actuelles. Listing 10.1 : Surcharge de fonctions membres
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 30a: 31: 32: 33: 34: 35: 36: //Listing10.1 Surcharge de fonctions membres de la classe #include <iostream> // Dclaration de la classe Rectangle class Rectangle { public: // constructeurs Rectangle(int largeur, int hauteur); ~Rectangle(){} // fonction DessinerForme surcharge void DessinerForme() const; void DessinerForme(int uneLargeur, int uneHauteur) const; private: int saLargeur; int saHauteur; }; // implmentation du constructeur Rectangle::Rectangle(int largeur, int hauteur) { saLargeur = largeur; saHauteur = hauteur; }
// fonction DessinerForme surcharge sans paramtres // Dessine en fonction des valeurs membres courantes // de lobjet void Rectangle::DessinerForme() const { DessinerForme(saLargeur, saHauteur); }
Chapitre 10
Fonctions avances
281
37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 51a: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61:
// fonction DessinerForme surcharge - prend deux valeurs // Dessine en fonction des paramtres passs void Rectangle::DessinerForme(int largeur, int hauteur) const { for (int i = 0; i < hauteur; i++) { for (int j = 0; j < largeur; j++) { std::cout << "*"; } std::cout << std::endl; } } // Programme principal de dmonstration // des fonctions surcharges int main() { // initialise un rectangle de 30x5 Rectangle unRect(30,5 ); std::cout << "DessinerForme():" << std::endl; unRect.DessinerForme(); std::cout << "\nDessinerForme(40, 2):" << std::endl; unRect.DessinerForme(40,2); return 0; }
La partie la plus importante du programme rside dans la surcharge de la fonction DessinerForme() (lignes 13 et 14). La mise en uvre des mthodes surcharges seffectue de la ligne 31 la ligne 49. Vous remarquerez que la version de DessinerForme() qui ne prend aucun paramtre se contente dappeler lautre version en lui passant les variables membres courantes. Essayez toujours dviter de dupliquer le code dans deux fonctions car cela complique les modications ultrieures et multiplie les risques derreur.
282
Le langage C++
Le programme commence rellement la ligne 52 et se poursuit jusqu la ligne 61. Il cre un objet Rectangle, appelle la premire version de la mthode DessinerForme() (sans paramtre) puis la seconde (en lui passant deux paramtres entiers courts non signs). Le compilateur dtermine la mthode qui doit tre appele en utilisant le nombre et le type de paramtres passs lappel. On pourrait imaginer une troisime mthode DessinerForme() qui prendrait en paramtre une dimension et une numration qui indiquerait sil sagit de la hauteur ou de la largeur.
Chapitre 10
Fonctions avances
283
28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 39a: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 60a: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70:
// valeurs par dfaut utilises pour le 3me paramtre void Rectangle::DessinerForme( int largeur, int hauteur, bool ValeursActuelles ) const { int printLargeur; int printHauteur; if (ValeursActuelles == true) { // utiliser les valeurs actuelles printLargeur = saLargeur; printHauteur = saHauteur; } else { printLargeur = largeur; // utiliser les paramtres printHauteur = hauteur; }
for (int i = 0; i < printHauteur; i++) { for (int j = 0; j < printLargeur; j++) { cout << "*"; } cout << endl; } } // Programme principal pour la dmonstration // des fonctions surcharges int main() { // Initialise un rectangle de 30x5 Rectangle unRect(30, 5); cout << "DessinerForme(0, 0, true)..." << endl; unRect.DessinerForme(0, 0, true); cout <<"DessinerForme(40, 2)..." << endl; unRect.DessinerForme(40, 2); return 0; }
284
Le langage C++
Dans ce listing, la fonction surcharge DessinerForme() est remplace par une unique fonction avec trois paramtres par dfaut (ligne 14). Les deux premiers, uneLargeur et uneHauteur, sont de type entier, alors que le troisime, de type bool (ValsActuelles), est gal false par dfaut. Cette fonction quelque peu complexe est implmente partir de la ligne 29. Vous savez que les espaces nont pas dimportance en C++ ; len-tte de la fonction se trouve donc aux lignes 29 33. Dans la mthode, le troisime paramtre (ValeursActuelles) est valu la ligne 38. Sil vaut true, les variables locales printLargeur et printHauteur reoivent respectivement les valeurs courantes des membres saLargeur et saHauteur. Dans le cas contraire, soit que le paramtre ait t initialis par dfaut false ou dni par lutilisateur, les deux premiers paramtres servent initialiser les variables printLargeur et printHauteur. Si ValeursActuelles vaut true, les valeurs des deux autres paramtres ne sont pas prises en compte.
Chapitre 10
Fonctions avances
285
Comment choisir entre la surcharge de fonction et lutilisation de valeurs par dfaut ? Voici une rgle trs simple : Prfrez la surcharge de fonction dans les cas suivants :
Il nexiste pas de valeurs par dfaut signicatives. Vous devez utiliser des algorithmes diffrents. Vous devez pouvoir grer des types diffrents dans la liste des paramtres.
Info
286
Le langage C++
Chapitre 10
Fonctions avances
287
cout << "\nLargeur de Rect2: " << Rect2.GetLargeur() << endl; cout << "Longueur de Rect2: " << Rect2.GetLongueur() << endl; return 0; }
La classe Rectangle est dclare de la ligne 6 la ligne 17. Le programme comprend deux constructeurs : le constructeur "par dfaut" (dni la ligne 9) et un constructeur (ligne 10) prenant en paramtre deux valeurs entires. La ligne 33 cre un rectangle laide du constructeur par dfaut et les lignes 34 et 35 afchent ses dimensions. Lutilisateur est ensuite invit entrer une largeur et une longueur (lignes 38 41) qui sont transmises au constructeur qui attend deux paramtres (ligne 43). Enn, la largeur et la hauteur de ce rectangle sont afches. Comme avec toute fonction surcharge, le compilateur appelle le constructeur qui convient en fonction du nombre et du type des paramtres.
288
Le langage C++
La parenthse fermante de la liste des paramtres du constructeur doit tre suivie du caractre deux-points. Placez ensuite le nom de la variable membre, suivi de deux parenthses entre lesquelles vous pouvez indiquer une ou plusieurs expressions qui initialisent la variable membre. Sil y plusieurs initialisations, sparez-les par une virgule. Dans le Listing 10.4, nous avons choisi dinitialiser les variables membres dans la partie dinitialisation du constructeur au lieu de leur affecter des valeurs dans le corps, comme nous lavions fait dans le Listing 10.3. Listing 10.4 : Extrait de code initialisant des variables membres
1: Listing10.4 Initialisation de variables membres 2: Rectangle::Rectangle(): 3: saLargeur(5), 4: saLongueur(10) 5: { 6: } 7: 8: Rectangle::Rectangle (int largeur, int longueur): 9: saLargeur(largeur), 10: saLongueur(longueur) 11: { 12: }
Le Listing 10.4 ntant quun extrait de code, il nafche donc rien. La ligne 2 dbute le constructeur par dfaut. Aprs len-tte standard, on a ajout un caractre deux-points, suivi des dnitions des valeurs par dfaut 5 et 10 pour saLargeur et saLongueur aux lignes 3 et 4. La ligne 8 dbute la dnition du deuxime constructeur. Dans cette version surcharge, on attend deux paramtres qui servent initialiser les membres de la classe aux lignes 9 et 10. Certaines variables, comme les rfrences ou les constantes, doivent tre initialises, mais pas affectes. Le corps dun constructeur contient souvent dautres affectations ou des instructions diverses, mais il est toujours prfrable dutiliser le plus possible la phase dinitialisation.
Le constructeur de copie
Outre un constructeur par dfaut et un destructeur, le compilateur produit galement un constructeur de copie par dfaut. Le constructeur de copie est appel chaque fois quune copie dobjet est cre sur la pile ou sur le tas.
Chapitre 10
Fonctions avances
289
Comme nous lavons vu au Chapitre 9, le passage dun objet par valeur en paramtre une fonction ou comme valeur de retour dune fonction provoque la cration dune copie temporaire de cet objet. Sil sagit dune instance dune classe dnie par lutilisateur, cest le constructeur de copie de cette classe qui est appel (voir le Listing 9.6 dans le chapitre prcdent). Un constructeur de copie ne prend toujours quun seul paramtre : une rfrence un objet de la mme classe. De prfrence, ce sera une rfrence constante an dviter toute modication malencontreuse de lobjet qui a t pass. Exemple :
Chat(const Chat & leChat);
Le constructeur de Chat attend ici une rfrence constante un objet existant de la classe Chat. Le rle de ce constructeur est de crer une copie de lobjet leChat. Le constructeur de copie par dfaut se contente de copier chaque variable membre de lobjet pass en paramtre dans les variables membres du nouvel objet. Cest ce que lon appelle une copie de surface : bien quelle convienne la plupart des variables membres, elle pose de srieux problmes lorsque ces membres sont des pointeurs sur des objets allous sur le tas. Une copie de surface duplique les valeurs exactes des variables membres dun objet dans un autre objet. Les pointeurs de ces deux objets nissent donc par pointer sur le mme espace mmoire. Une copie en profondeur, au contraire, copie les valeurs alloues sur le tas dans une nouvel emplacement, diffrent du premier. Si la classe Chat contient une variable membre appele sonAge, qui pointe sur un entier allou sur le tas, le constructeur de copie par dfaut copiera le contenu de la variable sonAge de lobjet pass en paramtre dans la variable membre sonAge de lobjet nouvellement cr. Ces deux objets pointeront alors sur la mme adresse mmoire (voir Figure 10.1).
Figure 10.1 Utilisation du constructeur de copie par dfaut.
Tas 5 ancien objet Chat nouvel objet Chat
sonAge
sonAge
Cette situation mnera au dsastre si lun des deux objets Chat sort de sa porte. Si le destructeur du Chat initial libre son emplacement alors que lobjet dupliqu pointe
290
Le langage C++
toujours sur cette zone, on a cr un pointeur perdu et le programme court un danger mortel. La Figure 10.2 illustre ce problme.
Figure 10.2 Cration dun pointeur perdu.
ancien objet Chat
sonAge
sonAge
La solution consiste donc crer votre propre constructeur de copie et allouer lespace mmoire en fonction de vos besoins. Une fois que vous avez allou une nouvelle zone mmoire, vous pouvez copier les anciennes valeurs dans ce nouvel emplacement : vous avez ralis une copie en profondeur, comme on le montre dans le Listing 10.5. Listing 10.5 : Constructeurs de copie
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: // Listing10.5 - constructeurs de copie #include <iostream> using namespace std; class Chat { public: Chat(); // Chat (const Chat &); // ~Chat(); // int GetAge() const { int GetPoids() const void SetAge(int age) { private: int *sonAge; int *sonPoids; }; Chat::Chat() { sonAge = new int; sonPoids = new int;
constructeur par dfaut constructeur de copie destructeur return *sonAge; } { return *sonPoids; } *sonAge = age; }
Chapitre 10
Fonctions avances
291
25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60:
*sonAge = 5; *sonPoids = 9; } Chat::Chat(const Chat & rhs) { sonAge = new int; sonPoids = new int; *sonAge = rhs.GetAge(); *sonPoids = *(rhs.sonPoids); } Chat::~Chat() { delete sonAge; sonAge = 0; delete sonPoids; sonPoids = 0; } int main() { Chat Frisky; cout << "ge de Frisky: " << Frisky.GetAge() << endl; cout << "Lge de Frisky est fix 6 ans...\n"; Frisky.SetAge(6); cout << "Cration de lobjet Boots partir de Frisky\n"; Chat Boots(Frisky); cout << "ge de Frisky: " << Frisky.GetAge() << endl; cout << "ge de Boots: " << Boots.GetAge() << endl; cout << "Lge de Frisky est fix 7 ans...\n"; Frisky.SetAge(7); cout << "ge de Frisky: " << Frisky.GetAge() << endl; cout << "ge de Boots: " << Boots.GetAge() << endl; return 0; }
292
Le langage C++
La classe Chat est dclare de la ligne 6 la ligne 19. Vous pouvez constater quun constructeur par dfaut et un constructeur de copie sont dclars respectivement la ligne 9 et la ligne 10. On reconnat que le constructeur de la ligne 10 est un constructeur de copie, car il prend en paramtre une rfrence (une rfrence de constante dans ce cas) un objet du mme type. Les deux variables membres dclares aux lignes 17 et 18 sont chacune associes un pointeur sur un entier. En rgle gnrale, les variables membres sont rarement stockes comme des pointeurs, mais on la fait ici pour illustrer la gestion de variables membres sur le tas. Le constructeur par dfaut rserve de lespace mmoire pour les deux variables entires sur le tas, puis leur affecte une valeur (lignes 21 27). Le constructeur de copie commence la ligne 29. Vous remarquerez que nous avons appel son paramtre rhs, ce qui est assez courant en C++ (rhs est labrviation de righthand side). Si vous examinez les lignes 33 et 34, vous remarquerez en effet que lobjet pass en paramtre se trouve droite du signe gal. Les lignes 31 32 allouent deux emplacements sur le tas, puis les deux lignes suivantes y affectent les valeurs des membres de lobjet Chat pass en paramtre. Le paramtre rhs est un objet Chat pass au constructeur de copie en tant que rfrence constante. tant un objet de la classe Chat, cette rfrence a les mmes membres que nimporte quel autre objet Chat. Tout objet Chat peut accder toutes les variables membres prives dun autre objet Chat ; il est toutefois prfrable dutiliser les mthodes daccs publiques dans la mesure du possible. La fonction membre rhs.GetAge() renvoie la valeur stocke dans lemplacement mmoire point par la variable membre sonAge de rhs. Dans une vritable application, vous obtiendrez la valeur de sonPoids de la mme manire, grce une mthode daccs. La ligne 34, toutefois, montre que les diffrents objets dune mme classe ont accs aux membres privs des autres. Une copie est alors ralise directement depuis le membre sonPoids priv de lobjet rhs. La Figure 10.3 montre ce qui sest donc pass : les valeurs pointes par les variables membres de lobjet Chat pass en paramtre ont t copies dans la mmoire alloue au nouvel objet Chat. La ligne 47 cre lobjet Frisky de la classe Chat. Son ge apparat lcran puis il reoit la valeur 6 (ligne 50). la ligne 52, le constructeur de copie duplique Frisky et baptise le nouvel objet Boots. Si Frisky avait t pass par valeur (et non par rfrence) une fonction quelconque, le compilateur aurait appel ce constructeur de copie de la mme faon.
Chapitre 10
Fonctions avances
293
Tas 5
sonAge
sonAge
Les ges respectifs des deux objets Chat apparaissent alors lcran (lignes 53 et 54). Boots a 6 ans comme Frisky, puisquil a hrit des proprits de ce dernier. En revanche, lorsque lge de Frisky est redni avec la valeur 7 ( la ligne 56), lge de Boots nest pas modi, ce qui prouve que les deux objets rsident dans des zones distinctes en mmoire. Lorsque les objets Chat deviennent hors de porte, ils sont effacs automatiquement par leurs destructeurs respectifs (lignes 37 43). Le destructeur dun Chat utilise delete pour librer les emplacements rservs sur le tas pour sonAge et sonPoids et affecte zro ces deux pointeurs pour plus de scurit.
294
Le langage C++
5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: } int main() { Compteur i; cout << "La valeur de i est " << i.GetVal() << endl; return 0; {} Compteur::Compteur(): saVal(0) }; private: int saVal; class Compteur { public: Compteur(); ~Compteur(){} int GetVal()const { return saVal; } void SetVal(int x) {saVal = x; }
Telle quelle est crite, cette classe est assez inutile. Dclare de la ligne 6 la ligne 16, elle ne contient quune variable membre de type int. Le constructeur par dfaut, dclar la ligne 9, initialise lunique variable membre saVal zro (ligne 18). la diffrence dun objet standard de type int, lobjet Compteur ne peut pas tre incrment, dcrment, additionn, etc. En outre, lafchage de sa valeur est bien plus compliqu que celle dun entier !
Chapitre 10
Fonctions avances
295
La fonction Increment est dnie la ligne 13. Bien quelle fonctionne, elle est assez lourde utiliser. Il serait ici plus judicieux dajouter un oprateur ++.
296
Le langage C++
Ici, op correspond loprateur qui doit tre surcharg. Loprateur ++ prxe peut donc tre surcharg en utilisant la syntaxe suivante :
void operator++()
Cette instruction indique que vous surchargez loprateur ++ et quil ne renverra pas de rsultat cest la raison pour laquelle le type du rsultat est void. Le Listing 10.8 modie la classe prcdente pour intgrer cette nouvelle fonctionnalit. Listing 10.8 : Surcharge de loprateur ++
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: // Listing10.8 - La classe Compteur // oprateur prfixe dincrmentation #include <iostream> using namespace std; class Compteur { public: Compteur(); ~Compteur(){} int GetVal()const { return saVal; } void SetVal(int x) {saVal = x; } void Increment() { ++saVal; } void operator++ () { ++saVal; } private: int saVal; }; Compteur::Compteur (): saVal(0) {} int main() { Compteur i; cout << "La valeur de i est " << i.GetVal() << endl; i.Increment(); cout << "La valeur de i est " << i.GetVal() << endl; ++i; cout << "La valeur de i est " << i.GetVal() << endl; return 0; }
Chapitre 10
Fonctions avances
297
La ligne 15 surcharge operator++. Vous pouvez constater que cette surcharge incrmente simplement la valeur du membre priv saVal. Il est ensuite utilis la ligne 31 et vous remarquerez que cette syntaxe est bien plus proche de celle dun type prdni comme int. Vous pourriez galement en proter pour ajouter les fonctionnalits supplmentaires pour lesquelles on a t amen crer une classe Compteur, comme la dtection du dpassement de capacit. Cela dit, cette criture de loprateur dincrmentation recle une erreur car, si vous lutilisez droite dune expression daffectation, vous obtiendrez une erreur. Voici un exemple :
Compteur a = ++i;
Cette instruction est suppose crer un nouvel objet a de la classe Compteur, puis lui affecter la valeur de i aprs son incrmentation. Le constructeur de copie par dfaut va grer cette affectation, mais votre oprateur dincrmentation renverra void, pas un objet Compteur. Or, il est interdit daffecter la valeur void quoi que ce soit (on ne peut rien crer partir de rien).
298
Le langage C++
13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46:
void SetVal(int x) {saVal = x; } void Increment() { ++saVal; } Compteur operator++(); private: int saVal; }; Compteur::Compteur(): saVal(0) {} Compteur Compteur::operator++() { ++saVal; Compteur temp; temp.SetVal(saVal); return temp; } int main() { Compteur i; cout << "La valeur de i est " << i.GetVal() << endl; i.Increment(); cout << "La valeur de i est " << i.GetVal() << endl; ++i; cout << "La valeur de i est " << i.GetVal() << endl; Compteur a = ++i; cout << "Valeur de a: " << a.GetVal(); cout << " et de i: " << i.GetVal() << endl; return 0; }
Ici, la fonction operator ++ dclare la ligne 15 et dnie aux lignes 26 32 renvoie un objet Compteur. Pour cela, on cre la variable temporaire temp la ligne 29 an daccueillir la valeur de lobjet incrmenter. Aprs lincrmentation, cette valeur temporaire est renvoye. la ligne 42, vous pouvez constater que la variable temporaire est immdiatement affecte a.
Chapitre 10
Fonctions avances
299
300
Le langage C++
39: 40: 41: 42: 43: 44: 45: 46: 47: 48:
cout << "La valeur de i est " << i.GetVal() << endl; i.Increment(); cout << "La valeur de i est " << i.GetVal() << endl; ++i; cout << "La valeur de i est " << i.GetVal() << endl; Compteur a = ++i; cout << "Valeur de a: " << a.GetVal(); cout << " et de i: " << i.GetVal() << endl; return 0; }
Le nouveau constructeur de la ligne 11 attend un paramtre de type int. Il est implment aux lignes 26 28 et initialise la variable saVal avec la valeur quon lui a transmise. Vous pouvez constater que limplmentation doperator++ est simplie. En effet, saVal est incrmente la ligne 32 puis la ligne suivante cre un objet Compteur temporaire partir de la nouvelle valeur de saVal. Cet objet est renvoy comme rsultat de operator++. Cette procdure est plus lgante, mais elle soulve la question suivante : "ne pourrait-on pas se passer des objets temporaires ?". Noublions pas que chaque objet temporaire doit tre cr puis supprim et que ces deux oprations peuvent tre coteuses. En outre, lobjet i existe dj et a dj la bonne valeur : pourquoi ne pas le renvoyer ? Ce problme peut tre rsolu grce au pointeur this.
Chapitre 10
Fonctions avances
301
4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43:
using namespace std; class Compteur { public: Compteur(); ~Compteur(){} int GetVal()const { return saVal; } void SetVal(int x) {saVal = x; } void Increment() { ++saVal; } const Compteur& operator++(); private: int saVal; }; Compteur::Compteur(): saVal(0) {}; const Compteur& Compteur::operator++() { ++saVal; return *this; } int main() { Compteuri; cout << "La valeur de i est " << i.GetVal() << endl; i.Increment(); cout << "La valeur de i est " << i.GetVal() << endl; ++i; cout << "La valeur de i est " << i.GetVal() << endl; Compteur a = ++i; cout << "Valeur de a: " << a.GetVal(); cout << " et de i: " << i.GetVal() << endl; return 0; }
302
Le langage C++
Dans ce programme, operator++ drfrence le pointeur this an de renvoyer lobjet courant (lignes 25 29). Le rsultat de cette opration est donc un objet Compteur qui peut tre affect la variable a. Comme nous lavons dj voqu, si lobjet Compteur allouait de la mmoire, il faudrait surcharger le constructeur de copie par dfaut mais, ici, il suft amplement. La valeur renvoye est une rfrence un objet Compteur, ce qui vous pargne davoir crer un objet temporaire supplmentaire. Cette rfrence est constante car la valeur ne doit pas pouvoir tre modie par la fonction qui utilisera le Compteur renvoy. Lobjet Compteur renvoy doit tre constant. Dans le cas contraire, il serait possible den changer la valeur. Sil ntait pas constant, vous pourriez par exemple crire la ligne 39 de la faon suivante :
39: Compteur a = ++++i;
On sattend alors ce que loprateur dincrmentation (++) soit appel sur le rsultat de lappel de++i. Cela reviendrait en fait appeler deux fois loprateur dincrmentation sur lobjet i, ce quil est gnralement conseill dviter. Faites un essai : remplacez la valeur renvoye pour quelle ne soit plus constante la fois dans la dclaration et limplmentation (lignes 15 et 25), puis remplacez la ligne 39 par (++++i). Placez un point darrt dans votre dbogueur la ligne 39 et effectuez un pas pas dtaill (step into). Vous constaterez que loprateur dincrmentation est appel deux fois. Il est appliqu la valeur renvoye (devenue non constante). Cest pour viter ce type de comportement que vous devez dclarer la valeur renvoye comme constante. Si vous remettez les lignes 15 et 25 en ltat (const), sans modier la ligne 39 (++++i) le compilateur indiquera que vous ne pouvez pas appeler loprateur dincrmentation sur un objet constant.
Chapitre 10
Fonctions avances
303
Loprateur prxe peut donc se contenter dincrmenter la valeur puis de renvoyer lobjet modi alors que loprateur sufxe doit renvoyer la valeur telle quelle tait avant lincrmentation. Pour ce faire, il faut donc crer un objet temporaire qui contiendra la valeur initiale, incrmenter ensuite la valeur de lobjet original puis renvoyer lobjet temporaire. Examinez la ligne suivante :
a = x++;
Si la x valait 5, a aura la valeur 5 et x la valeur 6 aprs lexcution de cette ligne. La valeur stocke dans x a donc bien t renvoye et attribue a avant dtre incrmente. Si x est un objet, son oprateur dincrmentation sufxe doit enregistrer la valeur initiale (5) dans un objet temporaire, affecter x la valeur 6, puis renvoyer lobjet temporaire pour attribuer sa valeur dorigine a. Comme on renvoie lobjet temporaire, celui-ci doit tre renvoy par valeur et non par rfrence puisque sa porte se limite la fonction. Les oprateurs prxe et sufxe sont implments dans le Listing 10.12. Listing 10.12 : Oprateurs prxe et sufxe
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: // Listing10.12 - Oprateurs prfixe et suffixe #include <iostream> using namespace std; class Compteur { public: Compteur(); ~Compteur(){} int GetVal()const { return saVal; } void SetVal(int x) {saVal = x; } const Compteur& operator++ (); // prfixe const Compteur operator++ (int); // suffixe private: int saVal; }; Compteur::Compteur(): itsVal(0) {} const Compteur& Compteur::operator++()
304
Le langage C++
26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53:
{ ++saVal; return *this; } const Compteur Compteur::operator++(int flag) { Compteur temp(*this); ++saVal; return temp; } int main() { Compteur i; cout << "La valeur de i est " << i.GetVal() << endl; i++; cout << "La valeur de i est " << i.GetVal() << endl; ++i; cout << "La valeur de i est " << i.GetVal() << endl; Compteur a = ++i; cout << "Valeur de a: " << a.GetVal(); cout << " et de i: " << i.GetVal() << endl; a = i++; cout << "Valeur de a: " << a.GetVal(); cout << " et de i: " << i.GetVal() << endl; return 0; }
Dclar la ligne 15, loprateur sufxe est implment de la ligne 31 la ligne 36. Loprateur prxe est dclar la ligne 14. Le paramtre pass loprateur sufxe la ligne 32 (flag) sert simplement indiquer quil sagit de loprateur sufxe, mais il nest jamais utilis.
Chapitre 10
Fonctions avances
305
la classe courante et lautre de nimporte quelle classe). La surcharge doprateurs comme les oprateurs daddition (+), de soustraction (), de multiplication (*), de division (/) et de modulo (%) sera donc videmment diffrente de la surcharge des oprateurs de prxe et de sufxe. Voyons, par exemple, comment surcharger loprateur + pour les objets Compteur. Lobjectif est de pouvoir dclarer deux variables Compteur, puis de les additionner, comme ici :
Compteur VarUne, VarDeux, VarTrois; VarTrois = VarUne+VarDeux;
L encore, on peut commencer par crire une mthode Add() qui prendra un Compteur en paramtre, ajoutera les valeurs et renverra un Compteur correspondant au rsultat de laddition. Cest cette approche que nous utilisons dans le Listing 10.13. Listing 10.13 : La mthode Add()
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: // Listing10.13 - Mthode Add #include <iostream> using namespace std; class Compteur { public: Compteur(); Compteur(int ValInit); ~Compteur(){} int GetVal()const { return saVal; } void SetVal(int x) {saVal = x; } Compteur Add(const Compteur&); private: int saVal; }; Compteur::Compteur(int ValInit): saVal(ValInit) {} Compteur::Compteur(): saVal(0) {}
306
Le langage C++
29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43:
Compteur::Add(const Compteur & rhs) { return Compteur(saVal+ rhs.GetVal()); } int main() { Compteur varUne(2), varDeux(4), varTrois; varTrois = varUne.Add(varDeux); cout << "varUne: " << varUne.GetVal()<< endl; cout << "varDeux: " << varDeux.GetVal() << endl; cout << "varTrois: " << varTrois.GetVal() << endl; return 0; }
Dclare la ligne 15, la fonction Add() prend en paramtre une rfrence constante au Compteur correspondant au nombre additionner lobjet en cours. Elle renvoie un objet Compteur qui sera affect la partie gauche de laffectation ralise la ligne 37. varUne et varDeux sont respectivement lobjet et le paramtre passs la fonction Add(), le rsultat de lopration tant affect varTrois Pour crer varTrois sans devoir linitialiser, vous avez besoin dun constructeur par dfaut. Celui-ci initialise saVal zro (lignes 25 et 27). En revanche, varOne et varTwo doivent tre initialises avec une valeur non nulle, ce qui oblige crer un autre constructeur (lignes 21 23). Une autre solution au problme serait de passer une valeur de zro par dfaut au constructeur dclar la ligne 11. Les lignes 29 32 ralisent laddition des deux objets. La mthode fonctionne, mais son utilisation nest pas trs naturelle.
Chapitre 10
Fonctions avances
307
308
Le langage C++
operator+ est dclar la ligne 15 et dni de la ligne 28 la ligne 31. On remarque que cela ressemble beaucoup la dclaration et la dnition de la fonction Add() du programme prcdent. En revanche, leur syntaxe dutilisation est radicalement diffrente. Il est bien plus naturel dcrire :
varTrois = varUne+varDeux;
que :
varTrois = varUne.Add(varDeux);
Les deux expressions donnent le mme rsultat, mais la premire est plus facile comprendre et utiliser. Ce nouvel oprateur est utilis la ligne 36 :
36: varTrois = varUne+varDeux;
(vous auriez dailleurs pu lcrire galement sous cette forme). La mthode operator+() est appele sur loprande gauche et reoit loprande droit en paramtre.
Chapitre 10
Fonctions avances
309
Info
Ne pas faire
Crer des oprateurs arithmtiques "non
intuitifs".
Confondre les oprateurs de prxe et de
oprateurs surchargs.
310
Le langage C++
Loprateur daffectation
La quatrime et dernire mthode fournie par le compilateur, si vous ne la dnissez pas vous-mme, est loprateur daffectation operator=(). Cet oprateur est appel chaque fois que vous affectez une valeur un objet. Voici un exemple :
Chat chatUn(5, 7); Chat chatDeux(3, 4); // ... autres instructions chatDeux = chatUn;
Lobjet chatUn est cr et initialis avec sonAge gal 5 et sonPoids gal 7. Un autre objet, chatDeux, est cr et initialis avec les valeurs 3 et 4. Aprs un certain nombre doprations, les valeurs de chatUn sont affectes chatDeux. Deux problmes se posent ici : que se passe-t-il si sonAge est un pointeur et que deviennent les valeurs initiales de chatDeux ? La gestion des variables membres qui stockent leurs valeurs sur le tas a t voque plus haut, lors de la prsentation du constructeur de copie. Les mmes problmes risquent de survenir lissue de cette opration, comme on la vu avec les Figures 10.1 et 10.2. En C++, il faut faire la diffrence entre une copie de surface et une copie en profondeur. La premire copie simplement les membres et les deux objets pointent nalement sur le mme emplacement du tas, alors que la seconde alloue la mmoire ncessaire pour y dupliquer les contenus des objets, comme on la vu avec la Figure 10.3. Cependant, un autre problme se pose avec loprateur daffectation. Lobjet chatDeux existe dj et sa mmoire est dj alloue lorsque laffectation est ralise. Pour viter toute fuite mmoire, il faut donc supprimer lemplacement quil occupait, mais que se passe-t-il alors si lon affecte chatDeux lui-mme, comme ici ?
chatDeux = chatDeux;
Cette opration est le plus souvent le rsultat dune erreur. Elle peut galement tre accidentelle et dcouler de rfrencements et de drfrencements successifs de pointeurs cachant cette auto-affectation. Si vous ne traitez pas correctement ce problme, chatDeux librera lespace quil occupe en mmoire. Il se posera alors un gros problme lorsquil sera prt recevoir les valeurs de la partie droite de laffectation puisquelles auront disparu ! Pour viter cela, loprateur daffectation doit tester si la partie droite de loprateur daffectation est lobjet lui-mme. Pour cela, il examine la valeur du pointeur this, comme le montre le Listing 10.15. Ceci vite galement le problme que nous venons dvoquer.
Chapitre 10
Fonctions avances
311
Chat & Chat::operator=(const Chat & rhs) { if (this == &rhs) return *this; *sonAge = rhs.GetAge(); *sonPoids = rhs.GetPoids(); return *this; }
int main() { Chat Frisky; cout << "ge de Frisky: " << Frisky.GetAge() << endl; cout << "Lge de Frisky est fix 6...\n";
312
Le langage C++
Frisky.SetAge(6); Chat Minou; cout << "ge de Minou : " << Minou.GetAge() << endl; cout << "Copie de Frisky dans Minou...\n"; Minou = Frisky; cout << "ge de Minou : " << Minou.GetAge() << endl; return 0; }
Ce programme reprend nouveau la classe Chat, mais ne dnit ni le constructeur de copie ni le destructeur an gagner de la place. Loprateur daffectation surcharg est dclar la ligne 15 et dni de la ligne 31 la ligne 38. La ligne 33 compare les deux parties de laffectation. Pour cela, on teste si ladresse de lobjet Chat de droite (rhs) est gale ladresse stocke dans le pointeur this. Si elles sont identiques, il ny a rien faire, car lobjet de gauche est identique lobjet de droite. Du coup, la ligne 34 renvoie lobjet courant. Si lobjet de droite est diffrent, les membres sont copis dans lobjet aux lignes 35 et 36 avant quil soit renvoy. Lutilisation de loprateur daffectation est illustre la ligne 50 du programme principal, lorsquun objet Chat appel Frisky est affect lobjet Chat Minou. Le reste devrait vous tre familier. Ce listing suppose que deux objets pointant vers la mme adresse sont identiques. Bien entendu, il est galement possible de surcharger loprateur dgalit (==) an de dnir ce que vous entendez par "galit" de deux objets de cette classe.
Chapitre 10
Fonctions avances
313
se passera-t-il si vous lui affectez un entier ? Le Listing 10.16 tente deffectuer cette opration.
ntion Atte
En dautres termes, le compilateur est incapable de convertir un type prdni en un objet dune classe dnie par lutilisateur.
314
Le langage C++
Dclare de la ligne 7 la ligne 16, la classe Compteur na quun constructeur par dfaut. Elle ne dclare aucune mthode permettant de transformer un type prdni en Compteur. La ligne 24 de la fonction main() dclare un entier qui est ensuite affect un objet Compteur, mais cette ligne produit une erreur de compilation. En effet, le compilateur est incapable de savoir comment affecter un int la variable membre saVal, sauf si vous lui expliquez. Le Listing 10.17 corrige le problme en ajoutant un oprateur de conversion, cest--dire un constructeur prenant un int en paramtre et renvoyant un objet Compteur. Listing 10.17 : Conversion dun objet de type int en objet de la classe Compteur
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: // Listing10.17 Constructeur/oprateur de conversion #include <iostream> using namespace std; class Compteur { public: Compteur(); Compteur(int val); ~Compteur(){} int GetVal()const { return saVal; } void SetVal(int x) {saVal = x; } private: int saVal; }; Compteur::Compteur(): saVal(0) {} Compteur:: Compteur(int val): saVal(val) {} int main() { int unInt = 5; Compteur unCpt = unInt; cout << "unCpt: " << unCpt.GetVal() << endl; return 0; }
Chapitre 10
Fonctions avances
315
Deux modications majeures ont t apportes ce programme. Le constructeur est surcharg pour pouvoir prendre en paramtre un objet de type int (ligne 11). Il est ensuite implment de la ligne 23 la ligne 25. Ce constructeur permet de crer un objet Compteur partir dun int. Le compilateur peut donc dsormais appeler le constructeur en lui passant un int en paramtre : tape 1 : Cration dun compteur appel unCpt. Cest comme crire int x = 5; pour crer une variable entire x et linitialiser la valeur 5. Ici, nous crons un objet Compteur appel unCpt et nous linitialisons laide de la variable entire unInt. tape 2 : Affectation de la valeur de unInt unCpt Cependant, unInt est un entier, pas un compteur ! Nous devons donc dabord le convertir en Compteur. Le compilateur tentera automatiquement plusieurs conversions pour vous, mais vous devez lui indiquer comment faire en crant un constructeur. Ici, il faut donc un constructeur prenant un entier pour seul paramtre :
class Compteur { Compteur(int x); // .. };
Ce constructeur fabrique des objets Compteur partir dentiers en crant un compteur temporaire anonyme. Pour les besoins de notre explication, nous supposerons que lobjet Compteur temporaire cr partir de lentier court sappelle tempInt. tape 3 : Affectation de tempInt unCpt, ce qui est quivalent :
unCpt = tempInt;
Ici, tempInt (le compteur temporaire cr par le constructeur) remplace ce qui se trouvait droite de laffectation. Le compilateur utilise maintenant le Compteur temporaire pour initialiser unCpt. Pour mieux comprendre ce mcanisme, il faut savoir que toutes les surcharges doprateurs fonctionnent de la mme faon : vous dclarez un oprateur surcharg laide du
316
Le langage C++
mot cl operator. Avec des oprateurs binaires (comme = ou +) la variable place droite devient le paramtre de ces surcharges. Cest ce que fait le constructeur. Par consquent :
a = b;
devient :
a.operator=(b);
Pourtant, que se passerait-il si vous tentiez de remplacer laffectation par les instructions suivantes ?
1: 2: 3: Compteur unCpt(5); int unInt = unCpt; cout << "unInt: " << unInt
<< endl;
Le compilateur produirait une erreur car, bien quil sache maintenant crer un Compteur partir dun int, il ne sait pas raliser lopration inverse.
Oprateurs de conversion
Pour grer la conversion des objets de votre classe vers un autre type, C++ propose un certain nombre doprateurs de conversion que vous pouvez ajouter vos classes an quelles sachent se convertir implicitement vers des types prdnis. Le Listing 10.18 donne un exemple dun tel oprateur. Vous pourrez remarquer que les oprateurs de conversion ne prcisent pas le type de leur rsultat, bien quils renvoient une valeur convertie (en fait, le type du rsultat est le nom mme de la mthode de conversion). Listing 10.18 : Conversion dun objet Compteur en unsigned short()
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: // Listing10.18 - Oprateurs de conversion #include <iostream> class Compteur { public: Compteur(); Compteur(int val); ~Compteur(){} int GetVal()const { return saVal; } void SetVal(int x) {saVal = x; } operator unsigned int(); private: int saVal; };
Chapitre 10
Fonctions avances
317
17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36:
Compteur::Compteur(): saVal(0) {} Compteur::Compteur(int val): saVal(val) {} Compteur::operator unsigned int() { return ( int(saVal) ); } int main() { Compteur cpt(5); int unInt = cpt; std::cout << "unInt: " << unInt << std::endl; return 0; }
La ligne 12 dclare loprateur de conversion. Cette dclaration commence par le mot-cl operator et ne nindique pas le type du rsultat. La fonction de conversion est implmente de la ligne 25 la ligne 28. La ligne 27 renvoie la valeur de saVal convertie en int. Le compilateur tant en mesure de convertir un int en Compteur et vice-versa, les variables de ces deux types peuvent tre affectes librement lune lautre. Vous pouvez videmment utiliser la mme approche pour grer galement dautres types.
Questions-rponses
Q Pourquoi utiliser des valeurs par dfaut lorsquon peut surcharger une fonction ? R Il est souvent plus ais de grer une seule fonction comprenant des valeurs par dfaut que dtudier le droulement de deux fonctions. En outre, si vous modiez une fonction surcharge, vous devez galement mettre jour toutes les autres fonctions surcharges correspondantes an dviter toute erreur dexcution ou de compilation. Q Au vu de toutes ces contraintes, doit-on prfrer les valeurs par dfaut ? R Non. Les fonctions surcharges offrent des fonctionnalits qui ne sont pas disponibles avec les valeurs par dfaut. Par exemple, il est possible de modier non seulement le
318
Le langage C++
nombre de paramtres dans une liste, mais galement leur type, ou encore de fournir une implmentation diffrente pour diverses combinaisons de types de paramtres. Q Lors de la cration dun constructeur, comment les oprations doivent-elles se rpartir entre la phase dinitialisation et la phase dexcution ? R En rgle gnrale, il faut utiliser le plus possible la phase dinitialisation cest--dire initialiser toutes les variables membres cet endroit. Certaines oprations, comme les calculs (y compris ceux qui servent linitialisation) et les afchages doivent avoir lieu dans le corps du constructeur. Q Une fonction surcharge peut-elle inclure un paramtre par dfaut ? R Oui. Il est possible dassocier une liste de valeurs par dfaut une fonction surcharge. La syntaxe employe et les rgles respecter sont identiques celles utilises pour les variables par dfaut de nimporte quelle fonction. Q Pourquoi certaines fonctions membres sont-elles dnies dans les dclarations de classe alors que dautres ne le sont pas ? R Les fonctions dont limplmentation est dnie lors de la dclaration dune classe sont traduites par des fonctions en ligne. Gnralement, cela ne concerne que les fonctions dont le corps est trs simple. Vous pouvez galement demander ce quune mthode soit en ligne laide du mot-cl inline, mme si elle est dnie en dehors de la dclaration de la classe.
Chapitre 10
Fonctions avances
319
Exercices
1. crivez la dclaration dune classe Cercle comprenant une variable membre SonRayon. Associez un constructeur par dfaut, un destructeur et plusieurs mthodes daccs cette variable. 2. Dans la classe cre lors de lexercice prcdent, implmentez le constructeur par dfaut, qui affecte la valeur 5 la variable sonRayon. Faites-le dans la phase dinitialisation du constructeur et non dans le corps. 3. Ajoutez ensuite un deuxime constructeur prenant un paramtre permettant daffecter une valeur la variable sonRayon. 4. Dans la classe Cercle, crez un oprateur prxe et un oprateur sufxe permettant dincrmenter la variable sonRayon. 5. Modiez la classe Cercle pour quelle stocke sonRayon sur le tas. Pour cela, vous devrez modier les mthodes existantes. 6. Ajoutez un constructeur de copie la classe Cercle. 7. Ajoutez un oprateur daffectation la classe Cercle. 8. Crez deux objets Cercle. Utilisez le constructeur par dfaut pour le premier et affectez la valeur 9 au second. Appelez loprateur dincrmentation pour chacun deux, puis afchez leurs valeurs. Enn, aprs avoir affect la valeur du second au premier, afchez leurs valeurs. 9. CHERCHEZ LERREUR : limplmentation de loprateur daffectation est errone.
CARRE CARRE::operator=(const CARRE & rhs) { sonCote = new int; *sonCote = rhs.GetCote(); return *this; }
11
Analyse et conception oriente objet
Au sommaire de ce chapitre
Comment utiliser lanalyse oriente objet pour comprendre le problme rsoudre Comment obtenir, grce la conception oriente objet, une solution robuste, extensible et able Comment documenter lanalyse et la conception de vos applications laide de UML (Unied Modeling Language)
force de se concentrer sur la syntaxe du langage C++, on risque de perdre de vue limportance de ces techniques de construction des programmes.
322
Le langage C++
Les modles
Lobjectif dun modle est de crer une abstraction signicative du monde rel. Cette abstraction doit tre plus simple que la ralit, mais aussi la reter dlement an que le modle puisse servir prvoir le comportement des choses dans la ralit. Un globe terrestre pos sur un bureau en est un exemple classique. Le modle nest pas la Terre elle-mme et il ne risque pas dtre confondu avec la chose relle. Cependant lun est une reprsentation sufsamment dle de lautre pour nous permettre lapprentissage de la Terre en tudiant le globe. Il y a, bien sr, des simplications importantes : il ne pleut jamais sur le globe de mon ls et il nest pas sujet aux mares, aux tremblements de terre, etc. Cependant, je peux lutiliser pour prvoir le temps quil me faudra pour me rendre de chez moi New York. Un modle qui ne serait pas plus simple que ce quil reprsente ne serait pas dune grande utilit. La conception oriente objet impose la construction de modles efcaces, elle met en jeu deux lments importants : un langage et un processus de modlisation.
Chapitre 11
323
Fleur
Granium
Il serait encore plus pratique davoir recours un langage commun dni une fois pour toutes. Le langage universel en matire danalyse et de conception logicielle sappelle UML (Unied Modeling Language). Son rle est de rpondre des questions comme "Comment reprsenter une relation dhritage ?". En UML, le granium de notre exemple aurait ainsi t reprsent par la Figure 11.2.
Figure 11.2 Reprsentation UML de la spcialisation.
Fleur
Granium
En UML, les classes sont reprsentes par des rectangles et lhritage sous la forme dune che partant de la classe la plus spcialise vers la plus gnrale. Le sens de cette che peut sembler peu intuitif, mais il na pas vraiment dimportance ; lessentiel, aprs avoir appris la reprsentation, consiste utiliser une convention commune pour communiquer. Les dtails de UML sont assez simples. Les reprsentations schmatiques sont gnralement assez faciles comprendre et appliquer et vous les apprendrez mesure que nous les prsenterons. Bien quil soit possible dcrire un livre entirement consacr UML, la vrit est que lon nen utilise la plupart du temps quun petit sous-ensemble, qui sapprend rapidement.
324
Le langage C++
Conception : le processus
Le processus de lanalyse et de la conception oriente objet est bien plus complexe et plus important que le langage de modlisation. Il est donc assez amusant que lon en entende beaucoup moins parler. En effet, le dbat sur les langages de modlisation est plus ou moins termin lindustrie a choisi UML comme standard, alors que le dbat sur le processus fait toujours rage. Une mthode est un langage et un processus de modlisation. On la dsigne souvent par le terme de "mthodologie", bien que ce soit une erreur puisque ce mot dsigne ltude des mthodes. Il existe plusieurs mthodes, parmi lesquelles la mthode de Grady Booch, celle de Ivar Jacobson et la mthode OMT de James Rumbaugh (Object Modeling Technologt). Ces trois hommes ont concoct Objectory, dsormais appel Rational Unied Process, qui est la fois une mthode et un produit commercial distribu par Rational Software, Inc. Lorsquils travaillaient pour cette division dIBM, ils taient affectueusement appels les Three Amigos. Ce chapitre respecte en gros leur processus, sans le suivre aveuglment. Dautres mthodes sont intressantes diffrents points de vue, lassociation des unes et des autres permettant dobtenir une schmatique assez efcace. Tout le monde nadopte pas cette approche ; il est donc conseill de vous documenter sur la pratique de lingnierie logicielle pour vous faire votre propre ide. Le processus de la conception peut tre itratif, ce qui signie quau cours du dveloppement du logiciel, la totalit du processus peut tre rpte mesure que vous ajoutez des amliorations dcoulant dune meilleure comprhension des besoins. La conception guide limplmentation, mais les dtails absents de limplmentation ralimentent la conception. Il est important de ne pas tenter, ds le dpart, de dvelopper un projet important de faon rectiligne et squentielle, mais plutt de boucler dans diffrentes parties du projet en amliorant constamment sa conception et en afnant son implmentation.
Chapitre 11
325
326
Le langage C++
6. Livraison. La livraison est la remise au client. Ce ne sont pas les mmes tapes que les phases de Rational Unied Process, qui sont les suivantes :
Mise en route ; laboration ; Construction ; Transition.
Info
Il doit tre bien clair que chacune de ces tapes sera rpte de nombreuses fois pendant le dveloppement dun produit donn. Pour bien comprendre le processus de conception itratif, il est toutefois plus simple de les prsenter lune aprs lautre. Ce processus est simple. Le reste du chapitre ne fait quen prsenter les dtails.
Controverses Ce qui se passe au cours de chacune des tapes dun processus de conception itratif, mme le nom attribu ces tapes, fait lobjet de controverses sans n. En fait, cela na aucune importance. Les tapes essentielles sont les mmes dans tous les processus : savoir ce que vous voulez construire, concevoir une solution et limplmenter. Le but est de produire un code qui respecte les besoins noncs, qui soit able, extensible et facilement mis jour, tout en respectant les dlais et le budget impartis.
Chapitre 11
327
328
Le langage C++
Llaboration dun jeu complet de cas dutilisation peut reprsenter le travail le plus important de lanalyse. Les experts du domaine sont les plus qualis pour vous aider dans cette tche car ce sont eux qui ont le plus dinformations sur les besoins que vous tentez de cerner. Les cas dutilisation ne tiennent pas compte des dtails de linterface utilisateur, pas plus que des rouages internes du systme que vous tes en train de construire. Ils devraient plutt se consacrer aux interactions qui doivent survenir et aux personnes et aux systmes (les acteurs) qui devront collaborer pour produire le rsultat attendu. . Voici quelques dnitions pour rsumer cette partie :
Cas dutilisation. Une description de la faon dont le logiciel sera utilis. Experts du domaine. Comme le nom lindique, ce sont les experts du domaine auquel le produit est destin. Acteur. Tout utilisateur ou systme interagissant avec le systme que vous dveloppez.
Un cas dutilisation est une description de linteraction entre un acteur et le systme luimme. Pour les besoins de lanalyse des cas dutilisation, le systme est considr comme une "bote noire". Un acteur "envoie un message au systme", et une raction se produit : des informations sont renvoyes, lengin spatial change de trajectoire, etc. Les cas dutilisation ne sufsent pas connatre tous les besoins, mais ils sont essentiels et reoivent souvent la plus grande attention. On peut galement inclure les rgles dentreprise, les donnes et les besoins techniques en matire de performances, de scurit, etc.
dtre externes au systme ; dinteragir avec le systme. Le dmarrage est souvent la partie la plus difcile de lanalyse des cas dutilisation. Le meilleur point de dpart peut tre une sance de brainstorming. Notez la liste des personnes et des systmes qui interagiront avec votre nouveau systme. Noubliez pas que Personnes signie en ralit rles le guichetier, le directeur, le client, etc. Une mme personne peut avoir plusieurs rles.
ce Astu
Chapitre 11
329
Dans notre exemple de guichet automatique, nous pourrions avoir les rles suivants :
le client ; le personnel de la banque ; un systme back-ofce ; la personne qui alimente le guichet automatique.
vrier son solde ; dposer de largent sur son compte ; retirer de largent ; faire des virements entre ses comptes ; ouvrir un compte ; fermer un compte.
Doit-on faire la distinction entre "dposer de largent sur son compte courant" et "dposer de largent sur son compte pargne", ou ces actions doivent-elles tre combines en une seule (comme nous lavons fait) : "dposer de largent sur son compte" ? Cela dpend si la diffrence est signicative dans le domaine en question. Pour le savoir, vous devez demander si les mcanismes (actions entreprises par le client) et les sorties (rponse du systme) sont diffrents. La rponse aux deux questions est "non" : le client dpose de largent de la mme faon sur ces deux comptes et la sortie est quasiment la mme ; le guichet rpond en incrmentant le solde du compte concern. Lacteur et le systme se comportant et rpondant de faon quasiment identique dans le cas dun versement sur lun ou lautre compte, ces deux cas dutilisation nen forment rellement quun seul. Vous pourrez plus tard tenter deux variantes pour voir sil existe une quelconque diffrence. En examinant chaque acteur, vous pouvez dcouvrir des cas dutilisation supplmentaires en rpondant aux questions suivantes :
Pourquoi lacteur utilise-t-il ce systme ? Le client lutilise pour obtenir du liquide, faire un dpt ou vrier son solde.
330
Le langage C++
Quelle est la sortie que lacteur veut ou attend ? Ajout dargent sur un compte ou obtention de liquide pour faire un achat. Quest-ce qui incite lacteur utiliser maintenant le systme ? Il peut avoir reu de largent sur son compte ou avoir lintention de faire un achat. Que doit faire lacteur pour utiliser le systme ? Sidentier en insrant une carte dans la machine. Ah ! Nous avons donc besoin dun cas dutilisation supplmentaire pour la connexion du client au systme.
Quelles informations lacteur doit-il fournir au systme ? Entrer un code personnel. Ah ! Nous avons donc encore besoin dune cas dutilisation supplmentaire pour obtenir et modier son code personnel.
Quelles sont les informations que lacteur attend du systme ? Solde du compte, etc.
Vous pouvez aussi vous concentrer sur les attributs des objets du domaine. Le client a un nom, un code et un numro de compte. Avez-vous des cas dutilisation pour grer ces objets ? Un compte a un numro, un solde et un historique des oprations. Ces lments gurent-ils dans les cas dutilisation ? Aprs avoir explor en dtail les cas dutilisation du client, ltape suivante consiste dvelopper ceux des autres acteurs. Dans notre exemple :
Le client vrie son ou ses soldes. Le client dpose de largent sur son ou ses comptes. Le client retire de largent de son ou ses comptes. Le client fait des virements. Le client ouvre un compte. Le client ferme un compte. Le client se connecte son ou ses comptes. Le client vrie ses dernires transactions. Lemploy de banque se connecte un compte spcial de gestion. Lemploy de banque modie un compte client. Un systme back-ofce met un compte client jour en fonction dune activit externe.
Chapitre 11
331
Les modications dun compte client sont retes dans un systme back ofce. Le guichet automatique signale quil est court dargent. Le technicien alimente le guichet automatique en argent et en imprims.
Il est important de raliser que les objets dcrits ne sont pas la classe qui sera utilise dans la conception (mme sil y aura srement des classes similaires), mais des classes dobjets du domaine. Il sagit dune documentation des besoins de fonctionnement du systme, pas dune documentation de la faon dont celui-ci va rpondre ces besoins. Les relations entre les objets du domaine de notre exemple peuvent tre reprsentes en UML avec les mmes conventions que celles que nous utiliserons plus tard pour dcrire
332
Le langage C++
les relations entre les classes de la conception. Cest lun des principaux avantages de UML : vous pouvez utiliser la mme reprsentation chaque tape du projet. Par exemple, la Figure 11.5 prcise que les vrications des comptes courant ou pargne sont des spcialisations du concept plus gnral de compte bancaire.
Figure 11.5 Spcialisation.
Compte bancaire Gnralisation
Objet du domaine
Compte courant
Compte pargne
Dans la Figure 11.5, les rectangles reprsentent les diffrents objets du domaine et les ches indiquent une gnralisation. Le sens des ches va de la classe spcialise vers la classe de base plus gnrale. Les comptes courant et pargne sont donc tous deux une forme spcialise de compte bancaire. Nous montrons ici la relation entre les classes du domaine des besoins. Vous pourrez ultrieurement, au moment de la conception, dcider davoir un objet CompteCourant et un objet CompteBancaire, et implmenter cette relation laide de lhritage, mais ce seront des dcisions de conception. Lors de lanalyse, nous nous contentons de documenter notre comprhension de ces objets dans le domaine.
Info
UML est un langage de modlisation riche permettant de reprsenter toutes les relations. Les principales, captures lors de lanalyse, sont les suivantes :
Chapitre 11
333
Gnralisation Elle est souvent assimile lhritage, mais il existe une distinction signicative entre les deux. La gnralisation dcrit la relation ; lhritage est limplmentation de la gnralisation dans le code. Le corollaire la gnralisation est la spcialisation. Un chat est une forme spcialise danimal ; lanimal est un concept gnralis qui runit un chien ou dun chat. La spcialisation implique que lobjet driv est-un sous-type de lobjet de base. Un compte courant est-un compte bancaire. Cette relation est symtrique : un compte bancaire gnralise le comportement et les attributs communs dun compte courant et dun compte pargne. Dans la phase danalyse du domaine, vous devez chercher capturer ces relations telles quelles existent dans la ralit. Composition Un objet est souvent compos de plusieurs sous-objets. Un compte courant comprend un solde, un historique des transactions, une identication du client, etc. On dit que le compte courant a ces lments ; la composition modlise la relation a-un. En UML, elle est reprsente par une che avec un losange partant de lobjet contenant vers lobjet contenu, comme le montre la Figure 11.6.
Figure 11.6 Composition.
Compte courant
Agrgation
Solde
Le compte courant a-un solde. Vous pouvez combiner ces diagrammes pour reprsenter un ensemble de relations assez complexe. La Figure 11.7 montre quun compte courant et un compte pargne sont tous les deux des comptes bancaires et que tous les comptes bancaires ont la fois un solde et un historique des transactions.
334
Le langage C++
Compte bancaire
Solde
Association La troisime relation frquemment reprsente lors de lanalyse du domaine est une simple association. Une association suggre linteraction quelconque de deux objets, sans prciser vraiment son objectif. Cette dnition sera afne lors de la phase de conception. Pour lanalyse, elle indique simplement quun objet A et un objet B sont lis, mais quaucun deux ne contient, ni est une spcialisation de lautre. La notation UML la reprsente comme une simple ligne entre les objets (voir Figure 11.8).
Figure 11.8 Association.
Objet A Objet B
Association
Chapitre 11
335
Chaque cas dutilisation peut tre dcompos en une srie de scnarios. Un scnario est une description dun ensemble prcis de circonstances qui se distinguent parmi les lments du cas dutilisation. Par exemple, le cas "Le client retire de largent de son ou ses comptes" peut avoir les scnarios suivants :
Le client demande un retrait de 300 de son compte courant, il prend les billets, et le systme imprime un reu. Le client demande un retrait de 300 de son compte courant, mais son solde est de 200 . Il est avis que son compte nest pas sufsamment approvisionn. Le client demande un retrait de 300 de son compte courant, mais il a dj retir 100 aujourdhui et la limite est de 300 par jour. Il est inform du problme et doit dcider sil ne veut prlever que 200 . Le client demande un retrait de 300 de son compte courant, mais le reu ne peut tre imprim par manque de papier. Il est inform du problme et doit dcider sil veut ou non poursuivre lopration sans recevoir de reu.
Ainsi de suite... Chaque scnario explore une variante du cas original et reprsente parfois des cas exceptionnels (pas assez dargent sur le compte, plus de papier, etc.). Ils peuvent aussi impliquer des dcisions nuances dans le cas lui-mme (le client veut-il transfrer de largent avant de procder au retrait ?). Il est impossible dexplorer tous les scnarios. En ralit, on recherche plutt ceux qui expriment les besoins du systme ou les dtails de linteraction avec lutilisateur.
Des prconditions. Ce qui doit tre vrai pour que le scnario commence. Des dclencheurs. Les vnements qui provoquent le dmarrage du scnario. Des actions ralises par lacteur. Des rsultats ou modications provoqus par le systme. Un retour reu par lacteur. Des actions rptitives se produisent-elles ? Ce qui leur fait prendre n. Une description du ux logique du scnario. Ce qui provoque la n du scnario. Des postconditions. Ce qui doit tre vrai lorsque le scnario est termin.
336
Le langage C++
En outre, vous devrez nommer chaque cas dutilisation et chaque scnario. Par exemple :
Cas dutilisation : Le client demande retirer du liquide. Scnario : Prconditions : Dclencheur : Description : Retrait russi du compte courant. Le client est dj connect au systme. Le client demande un "retrait". Le client choisit de retirer du liquide dun compte courant. Le guichet automatique est approvisionn en argent liquide et en papier pour le reu, le systme fonctionne. Le guichet automatique demande au client le montant retirer, le client demande 300 , ce quil est autoris faire. La machine lui donne 300 et imprime un reu, le client prend largent et le reu. Le compte du client est dbit de 300 , et le client possde 300 en liquide.
Postconditions :
Retire du liquide
Client
Association
La seule information capture ici est une interaction abstraite entre un acteur (le client) et le systme. Le diagramme devient un peu plus utile lorsque lon montre linteraction entre les cas dutilisation. Nous avons crit un peu plus utile parce quil nexiste que deux interactions possibles : <<utilise>> et <<tend>>. La premire indique quun cas dutilisation est un surensemble dun autre. Il nest, par exemple, pas possible de retirer de largent sans stre identi. Cette relation est reprsente dans la Figure 11.10. Cette gure indique que le cas dutilisation "Retire de largent" utilise le cas "Sidentie" et que ce dernier fait donc partie du premier. En fait, le strotype <<tend>> tait cens indiquer une relation conditionnelle ressemblant un peu lhritage, mais il y a tant de confusion entre <<utilise>> et <<tend>> dans la modlisation objet que de nombreux dveloppeurs ont simplement mis de ct <<tend>> en estimant que sa signication ntait pas sufsamment bien comprise.
Chapitre 11
337
Retire de l'argent
Client
<<utilise>>
Se connecte
Personnellement, nous utilisons <<utilise>> lorsque nous pourrions simplement copier et coller sur place le cas dutilisation existant et <<tend>> lorsque lon utilise le cas dutilisation sous certaines conditions. Diagrammes dinteraction Bien que le diagramme du cas dutilisation ait, en lui-mme, un intrt limit, vous pouvez associer des diagrammes aux cas dutilisation pour amliorer la documentation et la comprhension des interactions. Nous savons, par exemple, que le scnario Retirer de largent reprsente les interactions des objets suivants du domaine : client, compte courant et interface utilisateur. Vous pouvez donc documenter cette interaction avec un diagramme dinteraction (galement appel diagramme de collaboration) comme celui de la Figure 11.11.
Figure 11.11 Diagramme dinteraction UML.
Client Interface utilisateur (guichet automatique) 1: Demande de retrait 2: Affichage options 3: Indication montant et compte 4: Vrification solde et tat 5: Renvoi autorisation 300 7: Distribution du liquide 8: Demande de reu 9: Impression du reu Compte courant
338
Le langage C++
Le diagramme dinteraction de la Figure 11.11 capture les dtails du scnario qui pourraient ne pas apparatre clairement la lecture du texte. Les objets qui interagissent sont des objets du domaine, et lensemble "guichet automatique/Interface utilisateur" est trait comme un unique objet, avec seulement le compte bancaire demand en dtail. Cet exemple de guichet assez simple ne montre quun ensemble dinteractions, mais dtailler leurs spcicits est un outil puissant, permettant de comprendre la fois le problme du domaine et les besoins du nouveau systme.
Analyse de lapplication
Outre les cas dutilisation, la spcication de besoins doit capturer les suppositions du client et toutes les contraintes ou besoins concernant le matriel, les systmes dexploitation, la scurit, les performances, etc. Ces besoins sont les prrequis particuliers du client vous pourriez les dcouvrir au moment de la conception et de limplmentation, mais votre client a dcid pour vous. Les besoins de lapplication (quelquefois appels "besoins techniques") sont souvent dicts par la ncessit de communiquer avec des systmes existants. La comprhension de ce que font les systmes existants et de la faon dont ils le font est donc une composante essentielle de lanalyse. Dans lidal, vous analysez le problme, concevez la solution, puis choisissez les platesformes et systmes dexploitation qui sont les mieux adapts au besoin. Cependant, en pratique, cest le client qui dcide en fonction de son systme existant et cest vous de vous adapter.
Chapitre 11
339
vous allez interagir. Votre systme sera-t-il un serveur fournissant des services au systme existant ou sera-t-il un client ? Devrez-vous prvoir une interface nouvelle entre les systmes ou en adapter une existante ? Les autres systmes sont-ils stables ou devrez-vous continuellement tenter datteindre une cible mouvante ? Vous devez avoir la rponse toutes ces questions et bien dautres lors de la phase danalyse, avant de dmarrer votre conception. En outre, vous devez essayer de capturer les contraintes et les limites implicites des interactions avec les autres systmes. Ralentiront-ils la ractivit du vtre ? Imposeront-ils une charge importante au niveau ressources et temps de traitement ?
Documents de planning
Une fois que vous savez ce que doit faire votre systme et comment il doit se comporter, il est temps dtablir une prvision en ce qui concerne le budget et les dlais de ralisation. Le plus souvent, cest le client qui dnit la date butoir : "Vous avez 18 mois." Idalement, vous valuez le temps et le budget ncessaires limplmentation de votre solution, mais, en pratique, vous devez le plus souvent vous arranger pour raliser le travail dans les dlais et avec le budget impartis. Partez du principe que :
Si on vous octroie une fourchette, la limite suprieure est probablement optimiste. Toute ralisation prendra plus de temps que prvu.
Vous devez donc imprativement tablir des priorits an de commencer par ce qui est le plus important. Nesprez pas avoir le temps de nir, cest aussi simple que a. Lorsque vous aurez atteint la limite du temps imparti, il faut que ce que vous avez fait fonctionne et puisse reprsenter une premire version satisfaisante. Si vous construisez un pont et que le temps vous manque, vous devez pouvoir y faire circuler au moins une bicyclette. Cest toujours mieux que davoir un beau pont bien gnol, qui sarrte la moiti du chemin. Un autre point essentiel concernant les prvisions est quelles sont gnralement fausses. ce stade du processus, il est pratiquement impossible de faire une estimation able de la dure du projet. Lorsque vous disposerez des spcications des besoins, vous pourrez avoir une ide plus prcise et tablir une estimation en ajoutant au moins 20 25 % de marge de scurit que vous pourrez rduire mesure que vous progresserez et que vous en saurez plus.
Visualisation
La dernire pice de la spcication des besoins est la visualisation, qui est un joli nom pour dsigner les diagrammes, les images, les captures dcrans, les prototypes et toutes
340
Le langage C++
les autres reprsentations visuelles qui vous permettent dimaginer et de concevoir linterface graphique de votre produit. Pour les trs gros projets, vous pouvez dvelopper un prototype complet pour vous aider (ainsi que vos clients) comprendre comment se comportera le systme. Dans certaines quipes, ce prototype devient mme la spcication des besoins ; le "vrai" systme est alors conu pour implmenter les fonctionnalits prsentes dans le prototype.
Artefacts
la n de chaque phase danalyse et de conception, vous allez crer une srie de documents ou (souvent appels "artefacts"). Le Tableau 11.1 dcrit certains artefacts de la phase danalyse. Ces documents sont utiliss par plusieurs groupes. Le client sen servira pour vrier que vous comprenez ses besoins, lutilisateur nal pour vous fournir un retour dinformations et des conseils, lquipe projet pour concevoir et implmenter le code. La plupart de ces documents fournit galement des informations cruciales pour la documentation et indique au service qualit comment devrait se comporter le systme.
Tableau 11.1 : Documents crs pendant la phase danalyse du projet
Document
Rapport de cas dutilisation Analyse du domaine Diagrammes danalyse de collaboration Diagrammes danalyse de lactivit Analyse des systmes Analyse de lapplication Rapport des contraintes oprationnelles Budget et planning
Description
Rapport dtaillant les cas dutilisation, scnarios, strotypes, prconditions, postconditions et documents visuels Document et diagrammes dcrivant les relations entre les objets du domaine Diagrammes de collaboration dcrivant les interactions entre les objets du domaine Diagrammes dactivit dcrivant les interactions entre les objets du domaine Rapport et diagrammes dcrivant les systmes matriels et de bas niveau sur lesquels sera construit le projet Rapport et diagrammes dcrivant les besoins spciques du client pour ce projet particulier Rapport dcrivant les caractristiques et contraintes de performance imposes par ce client Rapport avec diagrammes et graphiques donnant les prvisions en matire de planning, de repres et de cots
Chapitre 11
341
tape 3 La conception
Lanalyse met laccent sur le domaine du problme, alors que ltape suivante, celle de la conception, se concentre sur la cration de la solution, cest--dire la transformation de notre comprhension des besoins en un modle pouvant tre implment par un programme. Le rsultat de ce processus est la production dun document de conception. Ce document peut tre divis en deux sections : les mcanismes darchitecture et la conception des classes. Cette dernire est son tour divise en conception statique (dtail des classes, de leurs relations et de leurs caractristiques) et conception dynamique (dtail des interactions entre classes). La section mcanismes darchitecture fournit des dtails sur la faon dont vous implmenterez la persistance des objets, les conits daccs, un systme objet distribu, etc. Le reste de ce chapitre se concentre sur laspect conception de classe du document de conception. Limplmentation des diffrents mcanismes darchitecture sera prsente dans dautres chapitres.
342
Le langage C++
le rseau est oprationnel. Le guichet automatique demande au client dindiquer un montant pour le retrait et le client demande 300 ce qui est autoris. La machine distribue 300 et imprime un reu, le client prend largent et le reu. Vous pouvez dduire de ce scnario les classes suivantes :
Client Liquide CompteCourant Compte Recus GuichetAuto Reseau Montant Retrait Machine Argent
On peut ensuite regrouper les synonymes pour ne conserver que la liste suivante, puis crer des classes pour chacun de ces noms :
Client Liquide (argent, montant, retrait) CompteCourant Compte Recus GuichetAuto (machine) Reseau
Ce nest pas un mauvais dbut. Vous pouvez ensuite schmatiser les relations videntes entre certaines de ces classes comme sur la Figure 11.12.
Transformations
Cette extraction des noms du scnario dans la prcdente section est surtout le dbut de la transformation des objets provenant de lanalyse du domaine. Ce nest quune premire tape. Bien souvent, nombre des objets du domaine auront des substituts dans la conception. Un objet est appel substitut pour distinguer le reu physique distribu par le guichet de lobjet de la conception, qui nest quune abstraction implmente dans le programme.
Chapitre 11
343
CompteCheque
Client
GuichetAuto
distribue
distribue
Liquide
Recu
Vous dcouvrirez probablement que la plupart des objets du domaine ont une reprsentation dans la conception cest--dire quil existe une relation unun entre lobjet du domaine et lobjet de conception. Dautres fois, un mme objet du domaine est reprsent dans la conception par une srie dobjets de conception. Il peut mme arriver quun ensemble dobjets du domaine soit reprsent par un seul objet de conception. Vous remarquez, dans la Figure 11.12, que lon a dj captur le fait que CompteCourant soit une spcialisation de Compte. De mme, partir de lanalyse du domaine, nous savons que le GuichetAuto distribue la fois du liquide et des recus et nous lavons aussitt captur dans la conception. La relation entre le Client et CompteCourant est moins vidente. Nous savons quune telle relation existe, mais les dtails ne sont pas clairs et il est donc prfrable de la laisser de ct.
Autres transformations
Souvent, chaque acteur possde une classe. Aprs avoir transform les objets du domaine, vous pouvez commencer rechercher dautres objets utiles au moment de la conception, commencer par linterface entre votre nouveau systme et tous les systmes existants cela devrait tre encapsul dans une classe de linterface. Prenez garde toutefois lorsque vous traitez des bases de donnes et autres supports de stockage externes. Il est gnralement prfrable de donner la responsabilit chaque classe de grer sa propre "persistance" (son stockage et sa rcupration entre les sessions utilisateurs). Ces classes de conception peuvent bien entendu utiliser des classes communes pour accder aux chiers
344
Le langage C++
ou aux bases de donnes mais, le plus souvent, ce sera le fournisseur du systme dexploitation ou de la base de donnes qui vous les procurera. Ces classes dinterface permettent dencapsuler les interactions de votre systme avec lautre systme et protgent donc votre code des modications dans ce dernier. Elles vous permettent galement de modier votre propre conception ou de satisfaire les changements dans la conception des autres systmes, sans altrer le reste du code. Tant que les deux systmes continuent de respecter linterface convenue, ils peuvent voluer indpendamment lun de lautre. Manipulation des donnes De la mme faon, vous devrez peut-tre crer des classes pour la manipulation des donnes. Si vous devez transmettre des donnes dun format vers un autre (de degrs Fahrenheit en Celsius par exemple), vous encapsulerez ces manipulations dans une classe spciale. Cette technique peut servir chaque fois que vous devez transformer des donnes dans les formats exigs par les autres systmes ou pour les transmettre sur Internet en rsum, chaque fois que vous devez manipuler des donnes dans un format prcis, il faut encapsuler le protocole dans une classe de manipulation. Vues et rapports Chaque "vue" ou "tat" produit par votre systme (ou, si vous produisez de nombreux tats, chaque ensemble dtats) est un candidat pour une classe. Les rgles conditionnant la production de ltat comment les informations sont rassembles et afches peuvent tre encapsules dans une classe Vue. Priphriques Si votre systme utilise des priphriques (imprimantes, appareils photo, modems, scanners, etc.), les caractristiques de leurs protocoles doivent tre encapsules dans une classe. Ici encore, en crant des classes pour linterface vers le priphrique, vous pouvez en brancher de nouveaux avec de nouveaux protocoles sans toucher au reste du code ; il suft de crer une nouvelle classe dinterface avec la mme interface (ou drivant de celle-ci).
Chapitre 11
345
Les trois points essentiels du modle statique sont : les responsabilits, les attributs et les relations. Le principal celui, quil faut considrer en premier lieu est lensemble des responsabilits de chaque classe. Le principe essentiel est : chaque classe doit tre responsable dune seule chose. Ceci ne signie pas quune classe ne possde quune mthode, loin de l. De nombreuses classes en ont des douzaines, mais toutes ces mthodes doivent tre cohrentes et cohsives ; elles doivent tre mutuellement apparentes et contribuer la capacit de la classe exercer un unique domaine de responsabilit. Chaque objet doit tre une instance dune classe bien dnie charge dune responsabilit particulire. Les classes dlguent gnralement les responsabilits annexes dautres classes connexes, ce qui facilite la maintenance dun programme. Pour baucher les responsabilits de vos classes, vous pouvez runir un groupe de trois six personnes, dont un informaticien ayant une exprience de lanalyse et de la conception oriente objet, un ou deux experts du domaine, et un "meneur" qui gardera un il sur lobjectif et recentrera la discussion. Le but est de dnir les classes laide de ches, didentier leurs responsabilits et de comprendre leurs interactions. Prvoyez une srie de ches denviron 10 15 cm, sur lesquelles vous placerez le nom dune classe en titre, puis sparez la che en deux colonnes : Responsabilits et Collaboration. Commencez par complter les ches pour les classes les plus importantes que vous avez identies, crivez au dos une simple phrase pour les dnir. Si vous pouvez galement savoir quelle est la classe que cette classe spcialise, crivez simplement "Superclasse:" sous le nom de la classe, suivi du nom de la classe dont elle drive. Ne vous attardez pas sur les attributs des classes, ne capturez que ceux qui sont vidents et essentiels. Concentrez-vous sur leurs responsabilits, en mentionnant simplement dans la colonne Collaboration si la classe doit dlguer une autre. Si la place vient manquer sur une che, demandez-vous si vous ne dlguez pas trop cette classe. Les mthodes dune classe doivent tre cohrentes et cohsives et contribuer lachvement de la responsabilit globale de la classe. ce stade, ne vous proccupez pas des relations ni de linterface, pas plus que de savoir si telle ou telle mthode sera publique ou prive. Une fois cette srie de ches tablie, distribuez-les arbitrairement dans le groupe et reprenez votre scnario prcdent : Le client choisit de retirer du liquide de son compte courant. Le compte est sufsamment approvisionn, le guichet automatique est aliment en liquide et en reus, et le rseau est oprationnel. Le guichet automatique demande au client dindiquer un
346
Le langage C++
montant pour le retrait, et le client demande 300 ce qui est autoris. La machine distribue 300 et imprime un reu, le client prend largent et le reu. La suite ressemble un jeu de rles o chaque participant se met dans la peau du personnage qui donnerait vie chaque classe. Par exemple, le participant possdant la che CompteCourant va dire : "Je dis au client la somme qui est disponible sur le compte. Il me demande 300 , jenvoie un message la machine pour lui demander de le faire." Le titulaire de la che GuichetAuto prend alors la parole : "Je suis le distributeur, je donne 300 et jenvoie CompteCourant un message pour lui dire de dbiter le compte de 300 . La machine dispose de 300 de moins. qui dois-je le dire ? Est-ce que je dois en garder la trace ?, etc. Cette premire approche permet de clarier les responsabilits de chaque classe et leurs interactions. Cette mthode a toutefois ses limites. Elle nest tout dabord par utilisable grande chelle ; pour un projet trs complexe, vous serez vite dpass. Par ailleurs, ce systme de ches ne permet pas dapprhender les relations interclasses. Bien que les collaborations soient captures, leur nature nest pas bien modlise. Il est difcile de passer des ches au code et, plus important encore, les ches sont statiques. Pour rsumer, les ches sont un bon dbut, mais vous devrez voluer vers la notation UML pour pouvoir modliser un modle complet et able de votre conception. Cette transition vers UML nest pas trs difcile, mais elle est irrversible. Vous devez dnitivement renoncer aux ches, car il est tout simplement impossible de conserver les deux modles synchroniss. Chaque che peut tre traduite directement en une classe UML, les responsabilits en mthodes de la classe, et les attributs capturs ajouts. La dnition de classe, inscrite au dos de la che, devient la documentation de la classe. La Figure 11.13 montre la relation entre la che CompteCourant et la classe UML correspondante.
Classe : SuperClasse : Responsabilits : CompteCourant Compte Garder la trace du solde Accepter les dpts et virements crditer crire les chques Transfrer le liquide demand Mettre jour le solde du jour des retraits de la machine Collaborations : Autres comptes Systmes back-ofce Distributeur de liquide
Chapitre 11
347
<<Abstrait>> Compte
CompteCourant Solde : int RetraitsGuichetAutoJour LireSolde() : int Deposer(int montant)() : void VirementCredit(int montant)() : bool VirementDebit() : int EcrireCheques(int montant)() : bool
La relation de gnralisation est implmente en C++ par le biais de lhritage public. Du point de vue conception, cest plus la smantique qui nous importe (ce quimplique cette relation) que son mcanisme. Nous avons vu la relation de gnralisation dans la phase danalyse, mais nous nous concentrons prsent sur les objets dans notre conception plutt que simplement sur les objets dans le domaine. Nous devons "factoriser" les fonctionnalits communes des classes connexes dans les classes de base pour encapsuler les responsabilits partages. Cette "factorisation" consiste dplacer la fonctionnalit des classes spcialises vers la classe plus gnrale. Si vous remarquez que vos comptes courant et pargne ont tous les deux besoin de mthodes pour transfrer de largent dans et dehors du compte, vous allez dplacer la mthode TransfrerFonds() dans la classe de base Compte. Cette pratique permet daccentuer le caractre polymorphe de votre conception. Une fonctionnalit de C++, non disponible dans Java, est le concept dhritage multiple (Java dispose dune fonctionnalit similaire, bien que limite, les interfaces multiples).
348
Le langage C++
Lhritage multiple permet une classe dhriter de membres et mthodes de plusieurs classes de base. Lhritage multiple doit tre utilis judicieusement, car il peut compliquer la fois la conception et limplmentation. Pour cette raison, de nombreux problmes autrefois rsolus par lhritage multiple le sont aujourdhui par lagrgation. Ceci dit, lhritage multiple est un outil puissant si votre conception ncessite quune unique classe spcialise le comportement de deux ou plusieurs autres classes. Hritage multiple ou composition Un objet est-il la somme de ses parties ? Est-il logique de modliser un objet Auto en tant que spcialisation de Volant, Porte, et Pneu, comme sur la Figure 11.14 ?
Figure 11.14 Faux hritage.
Volant Porte Pneu
Auto
Petit rappel : lhritage public doit toujours modliser la gnralisation. On dit que lhritage doit modliser une relation est-un. Pour modliser une relation a-un par exemple, une auto a-un volant il faut utiliser lagrgation (voir Figure 11.15).
Figure 11.15 Agrgation.
Auto
1 2..5
Volant
Porte
Pneu
Chapitre 11
349
Le diagramme de la Figure 11.15 indique quune auto a-un volant, 4 roues et 2 5 portes, ce qui est un meilleur modle de la relation entre une auto et ses lments. Remarquez que le losange est vid puisquil sagit dune agrgation et non dune composition. Cette dernire implique le contrle de la dure de vie de lobjet. Bien quune auto ait des pneus et une porte, ceux-ci existent avant de faire partie de lauto et existent encore mme sils nen font plus partie. La Figure 11.16 modlise la composition. Ce modle indique que le corps nest pas seulement une agrgation dune tte, de deux bras et deux jambes, mais que ces objets sont crs en mme temps que le corps et disparaissent en mme temps que lui. Leur existence dpend de celle du corps et leur dure de vie est inextricablement lie.
Figure 11.16 Composition.
Corps
1 2
Tte
Bras
Jambes
Discriminateurs Comment procder pour dnir les classes retant les diffrents modles dun fabricant dautomobiles ? Supposons que vous ayez dans la Socit UniversAuto les modles suivants : la Pluton (petite avec un petit moteur), la Vnus (berline 4 portes avec un moteur moyen), la Mars (coup sport avec le moteur le plus puissant), la Jupiter (mini-fourgon avec le mme moteur que le coup, mais conu pour avoir plus de couple an de grer le poids plus lev de ce modle), et la Globe (un 44). Vous pouvez commencer par crer les sous-types retant les diffrents modles, puis crer des instances de chaque la sortie des chanes de montage (voir Figure 11.17). Quest-ce qui diffrencie ces modles ? La taille du moteur, le type de carrosserie et les performances. Ces caractristiques peuvent tre combines pour crer les diffrents modles. En UML cela se traduit par le strotype de discrimination (voir Figure 11.18). Le diagramme de la Figure 11.18 indique que les classes peuvent tre drives de Auto en combinant trois attributs. Vous pouvez avoir un 44 puissant et sport, une berline familiale peu puissante, etc.
350
Le langage C++
Auto
Globe
Jupiter
Mars
Venus
Pluton
puissant
berline
coup
Familiale
peu puissant
Sport
Chaque attribut peut tre implment laide dune simple numration dans le code. Par exemple :
enum TypeCarrosserie = { berline, coupe, miniFourgon, quatre4 };
Une seule valeur peut savrer insufsante pour modliser un discriminateur particulier : les performances, par exemple, forment une caractristique assez complexe. Le discriminateur peut alors tre modlis comme une classe et la discrimination encapsule dans une instance de ce type. Les caractristiques de performance seront donc modlises dans un type performance, qui contiendra toutes les informations techniques du moteur. Le strotype UML pour une classe qui encapsule un discriminateur et qui peut tre utilis pour crer des instances dune classe (Auto) de diffrents types est <<typepuissance>>. Ici la classe Performance est un typepuissance pour Auto. Vous instanciez un objet Performance en mme temps quun Auto, et vous associez un objet Performance donn un objet Auto donn, comme sur la Figure 11.19. Les typepuissance permettent de crer des types logiques sans utiliser lhritage et de grer un ensemble important et complexe de types.
Chapitre 11
351
Auto moteur performance:CaracteristiquesPerformance carrosserie <<typepuissance>> Caractristiques de performances palier vitesses tours/mn puissant berline coup Familiale Acclrer
En C++, ils sont gnralement implments laide de pointeurs. Ici, la classe Auto contient un pointeur sur une instance de la classe CaracteristiquesPerformance (voir Figure 11.20).
Figure 11.20 La relation entre un objet Auto et son typepuissance.
moteur carrosserie
puissant
berline
coup
peu puissant
ntion Atte
La cration de nouveaux types au moment de lexcution avec cette mthode peut rduire les avantages du typage fort en C++, dans lequel le compilateur peut faire appliquer la justesse des relations interclasses. utiliser avec prcaution.
Class Car: public Vehicule { public: Auto(); ~Auto(); // autres mthodes publiques private: CaracteristiquesPerformance * pPerformance; };
352
Le langage C++
Enn, les typepuissance permettent de crer de nouveaux types (pas seulement des instances) au moment de lexcution. Chaque type logique tant diffrenci seulement par les attributs du typepuissance associ, ils peuvent tre les paramtres du constructeur de typepuissance et vous pouvez crer de nouveaux types dauto la vole au moment de lexcution. En passant des tailles de moteur ou des paliers de vitesse diffrents au typepuissance, vous pouvez crer de nouvelles caractristiques de performance. En affectant ces caractristiques aux diffrentes autos, vous pouvez agrandir le jeu des types de voitures au moment de lexcution.
Modle dynamique
Outre les relations entre classes, il est important de modliser leurs interactions. Par exemple, les classes CompteCourant, GuichetAuto et Recu peuvent interagir avec le Client par lintermdiaire du cas dutilisation "Retirer liquide" (voir Figure 11.21).
Figure 11.21 Diagramme de squence.
Client
GuichetAuto
CompteCourant
Recu
3: Afficher solde
6: Imprimer
Ce diagramme montre linteraction entre plusieurs classes. La classe GuichetAuto va dlguer la classe CompteCourant toute la responsabilit de gestion du solde, alors que CompteCourant va dlguer GuichetAuto la gestion de lafchage lutilisateur. Le diagramme de la Figure 11.21 est un diagramme de squence. Il existe aussi des diagrammes de collaboration qui insistent sur les interactions intemporelles entre les classes
Chapitre 11
353
et que vous pouvez directement produire partir du prcdent, notamment avec Rational Rose (voir Figure 11.22).
Figure 11.22 Diagramme de collaboration.
1: Vrifier soldes Client GuichetAuto 4 : Retirer liquide
3: Afficher solde
5 : Distribuer
2: Lire solde
CompteCourant Recu
6: Imprimer
Connect Fin
Chaque diagramme dtat commence par un simple tat de dpart et se termine avec zro ou plusieurs tats de fin. Les tats individuels sont nomms et les transitions peuvent tre tiquetes. Le garde indique une condition qui doit tre satisfaite pour quun objet passe dun tat un autre.
354
Le langage C++
Super tats Le client peut changer davis tout moment et dcider de ne pas sauthentier. Le systme doit toujours accepter cette annulation et revenir ltat "non authenti" (voir Figure 11.24).
Figure 11.24 Lutilisateur peut annuler.
Dbut
Non connect
Annul
Annul
Connect
Fin
Le diagramme devient un peu plus compliqu, mais il peut tre simpli laide dun super tat (voir Figure 11.25).
Figure 11.25 Super tat.
Dbut
Connect
Fin
Chapitre 11
355
Le diagramme de la Figure 11.25 fournit les mmes informations que celui de la Figure 11.24, mais il est plus clair et plus facile lire. Entre le moment de la demande dauthentication jusqu la n de cette opration, le processus peut tre annul, vous revenez alors ltat "non authenti".
Itrations
Dans le cadre de Rational Unied Process, les activits que nous venons dnumrer sont appeles workows ("ux de travail") ; elles varient dans les phases de mise en route, dlaboration, de construction et de transition. La modlisation dactivit est son apoge lors de la mise en route, mais elle peut toujours survenir pendant la construction. Limplmentation, quant elle, est forte pendant la construction, mais elle peut survenir au moment de la cration des prototypes pour la phase dlaboration. Dans chaque phase, la construction par exemple, il peut y avoir plusieurs itrations. Dans la premire itration de construction, il est ainsi possible de dvelopper les fonctions cls du systme. Dans la deuxime, on les approfondit et on en ajoute dautres. Lapprofondissement et lajout sont accentus dans la troisime, jusqu parvenir une itration produisant le systme complet.
Questions-rponses
Q Je nai appris aucun lment de programmation C++ dans ce chapitre. Quel est son objectif ? R Pour rdiger des programmes C++ efcaces, vous devez savoir les structurer. En planiant votre projet avant de commencer le codage, vous construirez de meilleurs programmes. Q En quoi lanalyse et la conception orientes objet diffrent-elles fondamentalement des autres approches ? R Avec les autres techniques, les analystes et programmeurs tendaient penser aux programmes comme des groupes de fonctions agissant sur des donnes. Dans la
356
Le langage C++
programmation oriente objet, les donnes et fonctionnalits sont des units ayant la fois la connaissance (les donnes) et les capacits (les fonctions). Les programmes procduraux mettent laccent sur les fonctions et sur la faon dont elles agissent sur les donnes. On dit souvent que les programmes crits en Pascal ou en C sont des collections de procdures alors que ceux crits en C++ sont des collections de classes. Q La programmation oriente objet est-elle la solution miracle tous les problmes ? R Non, mais en ce qui concerne les problmes importants et complexes, lanalyse et la conception orientes objet fournissent des outils apprciables et ingals.
Exercices
1. Un systme informatique est constitu de plusieurs lments : clavier, souris, cran, unit centrale. Dessinez un diagramme de composition pour illustrer la relation entre lordinateur et ses lments. Astuce : il sagit dune agrgation. 2. Supposons que vous deviez simuler lintersection de deux rues avec des feux tricolores et des passages pour pitons. Lobjectif est de dterminer si le chronomtrage des feux permettra un ux normal de la circulation. Quelles sortes dobjets faut-il modliser ? Quelles seraient les classes ?
12
Hritage
Au sommaire de ce chapitre
Nature de lhritage Comment utiliser lhritage pour driver dune classe partir dune autre Rle et utilisation des accs protgs Nature et rle des fonctions virtuelles
Vous avez vu, au chapitre prcdent, un certain nombre de concepts lis la programmation oriente objet qui incluent les principes de spcialisation/gnralisation implments en C++ par le biais de lhritage.
358
Le langage C++
Nous nous intresserons ici cette dernire faon de voir. Un chien fait partie de lespce canine, un canin est un mammifre, etc. Les zoologistes classent les tres vivants en Rgne, Ligne, Classe, Ordre, Famille, Genre et Espce. Cette hirarchie de spcialisation/gnralisation tablit une relation est-un. Un Homo Sapiens (un homme) est une sorte de primate et cette relation apparat partout : un monospace est une sorte de voiture, qui est elle-mme une sorte de vhicule. Une glace est une sorte de dessert, qui est lui-mme une sorte daliment. Lorsque lon peut dire que quelque chose est une sorte dautre chose, cela signie quil est une spcialisation de cette autre chose. Une voiture, par exemple, est un type spcial de vhicule.
Hritage et drivation
Un chien hrite de cest--dire reoit automatiquement toutes les caractristiques dun mammifre. tant un mammifre, vous savez donc quil se dplace et quil respire laide de poumons car tous les mammifres, par dnition, le font. Le concept de chien ajoute lide quil aboie, quil remue la queue, quil mange mes chaussons, etc. Vous pouvez diviser les chiens en chien de travail, en chien de garde et en chien de chasse, et vous pouvez encore subdiviser les chiens de chasse en pagneuls, en retrievers, etc. Finalement, chacun dentre eux peut encore tre spcialis ; les retrievers, par exemple, peuvent tre diviss en labradors et en goldens. Un labrador est une sorte de retriever, qui est un chien de chasse, qui est un chien et donc une sorte de mammifre, qui est une sorte danimal et, par consquent, un tre vivant. Cette hirarchie est reprsente par la Figure 12.1. C++ essaie de reprsenter ce type de relations en dnissant des classes qui drivent les unes des autres. La drivation est un moyen dexprimer une relation est-un. Par exemple, la classe Chien drive de la classe Mammifere. Ainsi, vous navez pas besoin de dnir explicitement les mouvements des chiens, puisquils hritent cette caractristique des mammifres. Une classe qui ajoute une fonctionnalit une classe existante drive de cette classe dorigine qui est donc la classe de base de la nouvelle classe. Si la classe Chien drive de la classe Mammifere, Mammifere est la classe de base de Chien. Les classes drives sont des surensembles de leurs classes de base. De la mme faon quun chien est un mammifre spcialis, la classe Chien inclut des mthodes et des donnes supplmentaires par rapport la classe Mammifere.
Chapitre 12
Hritage
359
Animal
Mammifere
Reptile
Cheval
Chien
de garde
de chasse
d'agrment
Retriever
Epagneul
Labrador
Golden
En gnral, une classe de base est associe plusieurs classes drives. Les classes Chien, Chat et Cheval drivent toutes de la classe Mammifere.
Le rgne animal
Pour clarier les concepts de drivation et dhritage, les diffrentes classes prsentes dans ce chapitre portent des noms danimaux. Supposez que vous deviez concevoir un jeu de simulation dune exploitation agricole. Vous allez dvelopper un ensemble danimaux domestiques, parmi lesquelles gurent des chevaux, des vaches, des cochons, des moutons, etc. Pour que les animaux ralisent des actions, vous implmentez des mthodes pour ces classes mais, pour le moment, vous allez vous crer de simples squelettes de fonctions qui se contenteront dafcher un simple message. crire des squelettes de fonctions signie que vous crirez le strict minimum pour montrer quelles sont appeles et que vous laisserez les dtails pour plus tard, lorsque vous aurez plus de temps. Vous tes videmment libre dtendre le code minimal de ce chapitre pour permettre aux animaux de se comporter plus naturellement.
360
Le langage C++
Les exemples qui utilisent des animaux sont simples, mais ces concepts sappliquent dautres domaines. Si vous construisez un distributeur automatique, par exemple, vous aurez un compte chques, qui est un type de compte bancaire, qui est un type de compte.
Syntaxe de la drivation
Pour dclarer une classe drive, vous devez indiquer le nom de la classe, suivi dun caractre deux-points, du type de la drivation (public, ou autre) et de la classe de base. Exemple :
class classeDerivee: typeAcces classeBase
Par exemple, pour crer une nouvelle classe appele Chien qui hrite dune classe Mammifere existante :
class Chien: public Mammifere
Les diffrents types de drivations (typeAcces) sont dcrits dans la suite du chapitre. Pour linstant, nous utiliserons le plus courant : public. La classe de base doit avoir t dclare avant la classe drive ; sinon, le compilateur sera incapable de crer le chier excutable. Dans le Listing 12.1, la classe Chien est drive de la classe Mammifere. Listing 12.1 : Hritage simple
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: //Listing12.1 Hritage simple #include <iostream> using namespace std; enum RACE { GOLDEN, CAIRN, DANDIE, SHETLAND, DOBERMAN, LAB }; class Mammifere { public: // constructeurs Mammifere(); ~Mammifere(); // mthodes daccs int GetAge() const; void SetAge(int); int GetPoids() const; void SetPoids(); // autres mthodes void Crier() const; void Dormir() const;
Chapitre 12
Hritage
361
23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48:
protected: int sonAge; int sonPoids; }; class Chien: public Mammifere { public: // constructeurs Chien(); ~Chien(); // mthodes daccs RACE GetRace() const; void SetRace(RACE); // autres mthodes RemuerQueue(); Quemander(); protected: RACE saRace; };
Ce programme ne produit pas de rsultat visible car il ne contient quun ensemble de dclarations de classes sans implmentation, mais cela nenlve rien son intrt. La classe Mammifere est dclare de la ligne 6 la ligne 27. Dans cet exemple, elle nest pas drive dune autre classe. Dans le monde rel, les mammifres driveraient dune autre classe puisquen ralit un mammifre est une sorte danimal. En C++, vous ne pouvez reprsenter quune partie des informations relatives un objet donn : la ralit est trop complexe pour tre cerne dans tous ses dtails et sa globalit. Une hirarchie C++ est donc toujours une reprsentation partielle des informations disponibles. Pour crer efcacement des classes drives en C++, le meilleur moyen est de regrouper le plus de caractristiques communes aux objets de la vie courante, sans ajouter de complexit inutile. Il fallait une origine la hirarchie : nous lavons trouve dans la classe Mammifere. Pour cette raison, certaines variables membres qui devraient appartenir des classes de base situes en amont sont reprsentes dans cette classe. Les animaux ont tous un poids et un ge, par exemple : si Mammifere drivait de Animal, elle hriterait de ces attributs au lieu de les dnir.
362
Le langage C++
Si lon devait plus tard ajouter un autre animal partageant certaines de ces fonctionnalits (par exemple Insecte), les attributs concerns pourraient tre dirigs vers une nouvelle classe Animal qui deviendrait la classe de base de Mammifere et Insecte. Cest ainsi quvoluent les hirarchies de classes. Pour faciliter la comprhension et la gestion des applications, seules six mthodes ont t intgres la classe Mammifere : quatre mthodes publiques daccs ainsi que Crier(), et Dormir(). Comme lindique la ligne 30, la classe Chien hrite de la classe Mammifere. Vous le savez grce aux deux-points qui suivent le nom de la classe (Chien), suivis du nom de la classe de base (Mammifere). Chaque objet Chien a trois variables membres : sonAge, sonPoids et saRace. Vous remarquerez que les variables sonAge et sonPoids ne gurent pas dans la dclaration de la classe Chien ; elles sont hrites de la classe Mammifere, tout comme loprateur de copie, les constructeurs et le destructeur.
Priv ou protg ?
Vous avez certainement dcouvert un nouveau mot-cl aux lignes 24 et 45 du Listing 12.1 : il sagit de protected. Jusqu prsent, les donnes des classes taient prives, mais les membres privs ne sont pas accessibles en dehors de la classe existante. Cette condentialit empche galement laccs ces membres partir des classes drives. Les variables sonAge et sonPoids pourraient tre publiques, mais ce nest pas souhaitable car vous ne voulez pas que dautres classes accdent directement ces donnes. Stroustrup (le crateur de C++), dans The Design and Evolution of C++, nonce que toutes les donnes membres devraient tre dclares comme prives, jamais protges. Les mthodes protges, cependant, ne posent gnralement pas de problmes et peuvent tre trs utiles.
Info
En ralit, vous souhaitez les rendre visibles uniquement cette classe et ses classes drives et cest exactement ce fait le mot-cl protected. Il existe trois identicateurs daccs : public, protected et private. Si un objet de votre classe est utilis par une fonction, celui-ci permet daccder toutes les donnes et fonctions membres publiques. leur tour, les fonctions membres peuvent manipuler toutes les donnes et fonctions membres prives de leur classe et toutes les donnes et fonctions membres protges de toutes les classes dont elles hritent. En consquence, la fonction
Chapitre 12
Hritage
363
Chien::RemuerQueue() a accs la donne prive saRace et aux donnes protges sonAge et sonPoids de la classe Mammifere. Mme si dautres classes sintercalaient entre Mammifere et Chien (AnimalDomestique, par exemple), la classe Chien aurait quand mme accs aux membres protgs de la classe Mammifere condition que ces autres classes utilisent toutes un hritage public. Pour en savoir plus sur lhritage priv, reportez-vous au Chapitre 16. Le Listing 12.2 cre des objets de type Chien, puis accde aux donnes et aux mthodes membres de ces objets. Listing 12.2 : Utilisation dun objet driv
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 22a: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: //Listing12.2 Utilisation dun objet driv #include <iostream> using std::cout; using std::endl; enum RACE { GOLDEN, CAIRN, DANDIE, SHETLAND, DOBERMAN, LAB }; class Mammifere { public: // constructeurs Mammifere():sonAge(2), sonPoids(5){} ~Mammifere(){} // mthodes daccs int GetAge() const { return sonAge; } void SetAge(int age) { sonAge = age; } int GetPoids() const { return sonPoids; } void GetPoids(int Poids) { sonPoids = Poids; } // autres mthodes void Crier()const { cout <<"Le cri du mammifre!\n"; } void Dormir()const { cout << "Chut. Je dors.\n"; } protected: int sonAge; int sonPoids; }; class Chien: public Mammifere { public:
364
Le langage C++
34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 43a: 44: 44a: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57:
// constructeurs Chien():saRace(GOLDEN){} ~Chien(){} // mthodes daccs RACE GetRace() const { return saRace; } void SetRace(RACE race) { saRace = race; } // autres mthodes void RemuerQueue() const { cout << "Je remue la queue...\n"; } void Quemander() const { cout << "Je mendie de la nourriture...\n"; } private: RACE saRace; }; int main() { Chien Fido; Fido.Crier(); Fido.RemuerQueue(); cout << "Fido a " << Fido.sonAge() << " ans" << endl; return 0; }
La classe Mammifere est dclare de la ligne 8 la ligne 28 (pour raccourcir le listing, les fonctions membres ont t dnies inline). La classe Chien est une classe drive de la classe Mammifere (lignes 30 48), ce qui confre les mmes attributs tous les chiens : un ge, un poids et une race. Comme on la indiqu plus haut, les deux premiers proviennent de la classe de base, Mammifere. La ligne 52 dclare un objet Chien nomm Fido. Il hrite de tous les attributs de la classe Mammifere et de la classe Chien. Ainsi, Fido peut remuer la queue, crier et dormir, puisquil dispose des fonctions RemuerQueue(), Crier() et Dormir(). Aux lignes 53 et 54, Fido appelle deux de ces mthodes de la classe de base Mammifere. La ligne 55 appelle la mthode daccs LireAge() de la classe de base.
Chapitre 12
Hritage
365
366
Le langage C++
29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 41a 42: 42a: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73:
{ public: // constructeurs Chien(); ~Chien(); // mthodes daccs RACE GetRace() const { return saRace; } void SetRace(RACE race) { saRace = race; } // autres mthodes void RemuerQueue() const { cout << "Je remue la queue...\n"; } void Quemander() const { cout << "Je mendie de la nourriture...\n"; } private: RACE saRace; }; Mammifere::Mammifere(): sonAge(3), sonPoids(5) { std::cout << "Constructeur de Mammifere... " << endl; } Mammifere::~Mammifere() { std::cout << "Destructeur de Mammifere... " << endl; } Chien::Chien(): saRace(GOLDEN) { std::cout << "Constructeur de Chien... " << endl; } Chien::~Chien() { std::cout << "Destructeur de Chien... " <<endl; } int main() { Chien Fido; Fido.Crier();
Chapitre 12
Hritage
367
Fido.RemuerQueue(); std::cout << "Fido a " << Fido.GetAge() << " ans" << endl; return 0; }
Ce listing ressemble beaucoup au Listing 12.2, mais les lignes 48 69 signalent dsormais les appels aux constructeurs et destructeurs lcran. Le constructeur de Mammifere est appel avant le constructeur de Chien. La classe Chien est alors compltement dnie et les mthodes sont implmentes. Lorsque Fido devient hors de porte, le destructeur de Chien, puis celui de Mammifere sont appels. Cela est conrm par le rsultat du listing.
368
Le langage C++
10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 22a: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 48a: 49: 49a: 50: 51: 52: 53:
// constructeurs Mammifere(); Mammifere(int age); ~Mammifere(); // mthodes daccs int GetAge() const { return sonAge; } void SetAge(int age) { sonAge = age; } int GetPoids() const { return sonPoids; } void SetPoids(int Poids) { sonPoids = Poids; } //autres mthodes void Crier() const { cout << "Le cri du mammifre!\n"; } void Dormir() const { cout << "Chut. Je dors.\n"; }
protected: int sonAge; int sonPoids; }; class Chien: public Mammifere { public: // constructeurs Chien(); Chien(int age); Chien(int age, int poids); Chien(int age, RACE race); Chien(int age, int poids, RACE race); ~Chien(); // mthodes daccs RACE GetRace() const { return saRace; } void SetRace(RACE race) { saRace = race; } // autres mthodes void RemuerQueue() const { cout << "Je remue la queue...\n"; } void Quemander() const { cout << "Je mendie de la nourriture...\n"; } private: RACE saRace; };
Chapitre 12
Hritage
369
54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89: 90: 91: 92: 93: 94: 95: 96: 97: 98: 99:
Mammifere::Mammifere(): sonAge(1), sonPoids(5) { cout << "Constructeur de Mammifere..." << endl; } Mammifere::Mammifere(int age): sonAge(age), sonPoids(5) { cout << "Constructeur de Mammifere(int)..." << endl; } Mammifere::~Mammifere() { cout << "Destructeur de Mammifere..." << endl; } Chien::Chien(): Mammifere(), saRace(GOLDEN) { cout << "Constructeur de Chien..." << endl; } Chien::Chien(int age): Mammifere(age), saRace(GOLDEN) { cout << "Constructeur de Chien(int)..." << endl; } Chien::Chien(int age, int poids): Mammifere(age), saRace(GOLDEN) { sonPoids = poids; cout << "Constructeur de Chien(int, int)..." << endl; } Chien::Chien(int age, int poids, RACE race): Mammifere(age), saRace(race) {
370
Le langage C++
100: 101: 102: 103: 104: 105: 106: 107: 108: 109: 110: 111: 112: 113: 114: 115: 116: 117: 118: 119: 120: 121: 122: 123: 124: 125: 126: 127: 128: 129:
sonPoids = Poids; cout << "Constructeur de Chien(int, int, RACE)..." << endl; } Chien::Chien(int age, RACE race): Mammifere(age), saRace(race) { cout << "Constructeur de Chien(int, RACE)..." << endl; } Chien::~Chien() { cout << "Destructeur de Chien..." << endl; } int main() { Chien Fido; Chien Rover(5); Chien Buster(6, 8); Chien Yorkie (3, GOLDEN); Chien Dobbie (4, 20, DOBERMAN); Fido.Crier(); Rover.RemuerQueue(); cout << "Yorkie a " << Yorkie.GetAge() << " ans" << endl; cout << "Dobbie pse "; cout << Dobbie.GetPoids() << " kilos" << endl; return 0; }
Info
Chapitre 12
Hritage
371
10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24:
Constructeur de Chien(int, int, RACE)... Le cri du mammifre! Je remue la queue... Yorkie a 3 ans. Dobbie pse 20kilos. Destructeur de Chien... Destructeur de Mammifere... Destructeur de Chien... Destructeur de Mammifere... Destructeur de Chien... Destructeur de Mammifere... Destructeur de Chien... Destructeur de Mammifere... Destructeur de Chien... Destructeur de Mammifere...
Le constructeur de Mammifere a t surcharg la ligne 12 pour prendre en charge un entier (lge de lanimal). La variable sonAge est initialise avec la valeur passe au constructeur et sonPoids est dnie 5 (lignes 62 67). Chien surcharge cinq constructeurs (lignes 36 40). Le premier est le constructeur par dfaut. la ligne 37, le deuxime prend un ge, qui est le mme paramtre que celui du constructeur de Mammifere. Le troisime constructeur prend la fois un ge et un poids, le quatrime un ge et une race ; enn, le dernier prend un ge, un poids et une race. La ligne 74 contient le code du constructeur par dfaut de Chien et vous pouvez constater que quelque chose a chang. Lorsque ce constructeur est appel, il invoque son tour le constructeur par dfaut de Mammifere, comme on le voit la ligne 75. Bien que ce ne soit pas strictement obligatoire de procder ainsi, cela permet dindiquer explicitement que lon appelle le constructeur sans paramtre de la classe de base. Ce constructeur serait automatiquement appel de toutes faons, mais cette faon de procder explicite clairement vos intentions. Limplmentation du constructeur de Chien, qui prend une valeur entire, gure de la ligne 81 la ligne 86. Lors de ltape dinitialisation (lignes 82 et 83), Chien initialise sa classe de base en lui passant le paramtre, puis initialise sa race (saRace). Le deuxime constructeur de Chien est implment de la ligne 88 la ligne 94. Il prend en charge deux paramtres. Cette fois encore, la classe de base est initialise laide du constructeur appropri (ligne 89) mais on en prote galement pour affecter un poids la variable sonPoids de la classe de base. Vous ne pouvez pas effectuer cette opration lors de la phase dinitialisation car Mammifere ne possde pas de constructeur prenant un poids en paramtre : cest la raison pour laquelle on initialise cette variable dans le corps du constructeur de Chien.
372
Le langage C++
Examinez le fonctionnement des autres constructeurs pour tre certain de bien le comprendre. Distinguez notamment ce qui est initialis lors de la phase dinitialisation de ce qui doit tre trait dans le corps du constructeur. Le rsultat obtenu a t numrot an de pouvoir faire rfrence chacune des lignes. Les deux premires correspondent la cration de linstance de Fido, laide du constructeur par dfaut. Les lignes 3 et 4 de la sortie correspondent la cration de Rover, alors que Buster est reprsent aux lignes 5 et 6. Le constructeur de Mammifere appel prend en paramtre un seul entier, alors que le constructeur de Chien prend deux valeurs entires. Une fois que les objets ont t crs, ils sont traits puis dtruits. Pour cela, le destructeur de Chien est appel avant celui de Mammifere, cinq fois chacun.
Chapitre 12
Hritage
373
10: 11: 12: 13: 14: 15: 15a: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 31a: 32: 32a: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46:
// constructeur et destructeur Mammifere() { cout << "Constructeur de Mammifere...\n"; } ~Mammifere() { cout << "Destructeur de Mammifere...\n"; } // autres mthodes void Crier()const { cout << "Le cri du mammifre!\n"; } void Dormir()const { cout << "Chut. Je dors.\n"; } protected: int sonAge; int sonPoids; }; class Chien: public Mammifere { public: // constructeur et destructeur Chien(){ cout << "Constructeur de Chien...\n"; } ~Chien(){ cout << "Destructeur de Chien...\n"; } // autres mthodes void RemuerQueue() const { cout << "Je remue la queue...\n"; } void Quemander() const { cout << "Je mendie de la nourriture...\n"; } void Crier() const { cout << "Ouah Ouah!\n"; } private: RACE saRace; }; int main() { Mammifere grosAnimal; Chien Fido; grosAnimal.Crier(); Fido.Crier(); return 0; }
374
Le langage C++
On voit une mthode Crier() dnie la ligne 15 dans la classe Mammifere. La classe Chien dclare aux lignes 23 37 hrite de Mammifere (ligne 23) et a donc accs cette mthode Crier(). Elle la rednit toutefois ligne 33 an que les objets Chien aboient lors de lappel cette mthode. La ligne 41 cre lobjet Mammifere grosAnimal, comme le montre la premire ligne du rsultat. La ligne suivante cre Fido, ce qui dclenche lappel du constructeur de Mammifere et du constructeur de Chien. la ligne 43, lobjet grosAnimal appelle sa mthode Crier(), puis cest au tour de lobjet Chien dappeler sa mthode Crier(). Lafchage du programme rete bien lappel aux deux mthodes. Enn, les deux objets sont supprims laide de leurs destructeurs respectifs. Vous ne devez pas confondre les termes surcharge et rednition. Lorsque vous surchargez une mthode, vous crez plusieurs mthodes de mme nom avec des signatures diffrentes dans la mme classe. La rednition dune mthode consiste crer dans une classe drive une mthode de mme nom et de mme signature que celle de la classe de base.
Info
Chapitre 12
Hritage
375
5: 6: 7: 8: 8a: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 22a: 23: 23a: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35:
class Mammifere { public: void Bouger() const { cout << "Les mammifres se dplacent dun pas\n"; } void Bouger(int distance) const { cout << "Les mammifres se deplacent de "; cout << distance <<" pas.\n"; } protected: int sonAge; int sonPoids; }; class Chien: public Mammifere { public: // Avertissement possible signalant // que vous masquez une fonction! void Bouger() const { cout << "Le chien se dplace de 5 pas.\n"; } }; int main() { Mammifere grosAnimal; Chien Fido; grosAnimal.Bouger(); grosAnimal.Bouger(2); Fido.Bouger(); // Fido.Bouger(10); return 0; }
Toutes les mthodes et les donnes non indispensables ont t supprimes de ces classes. Le programme commence par la dclaration des mthodes Bouger() surcharges dans la classe Mammifere (lignes 8 et 9). la ligne 23, la classe Chien rednit la version de la fonction Bouger() sans paramtre. Les appels ces mthodes (lignes 30 32) sont signals dans le rsultat.
376
Le langage C++
Vous pouvez constater que la ligne 33 a t mise en commentaire (dsactive) car elle produit une erreur de compilation. Aprs avoir redni lune des mthodes, vous ne pouvez plus utiliser aucune des mthodes de base du mme nom. Bien quun objet Chien aurait pu appeler la mthode Bouger(int) sil navait pas redni la mthode Bouger() sans paramtre, il doit dsormais rednir les deux sil compte utiliser les deux. Sinon, il cache la mthode qui nest pas rednie. Ce comportement est analogue la rgle qui veut que si vous fournissez un constructeur quelconque, le compilateur ne vous fournira plus de constructeur par dfaut. La rgle est donc la suivante : si vous rednissez une mthode surcharge, tous les autres surcharges de cette mthode sont masques. Si tel nest pas votre souhait, vous devez toutes les rednir. Les programmeurs dbutants oublient souvent le mot-cl const lorsquils rednissent une mthode de la classe de base et la masque donc involontairement. En effet, le mot-cl const fait partie de la signature : loublier revient modier la signature et donc cacher la mthode, au lieu de la rednir.
Rednir ou cacher La section qui suit dcrit les mthodes virtuelles. La rednition dune mthode virtuelle autorise le polymorphisme, alors que le masquage dune mthode le dtriore. Vous allez trs vite en apprendre davantage.
Vous pouvez donc appeler la mthode Bouger() de la classe Mammifere comme suit :
Mammifere::Bouger().
Vous pouvez utiliser ces noms qualis comme avec tout autre nom de mthode. Dans le Listing 12.6, il aurait donc t possible de modier la ligne 33, de sorte que le programme puisse tre compil :
Fido.Mammifere::Bouger(10);
Cette ligne appelle explicitement la mthode de la classe Mammifere. Le Listing 12.7 exploite cette syntaxe.
Chapitre 12
Hritage
377
Listing 12.7 : Appel dune mthode de base depuis une mthode rednie
1: 1a: 2: 3: 4: 5: 6: 7: 8: 8a: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: // Listing12.7 Appel dune mthode de base // depuis une mthode redfinie. #include <iostream> using namespace std; class Mammifere { public: void Bouger() const { cout << "Les mammifres se dplacent dun pas\n"; } void Bouger(int distance) const { cout << "Les mammifres se dplacent de " << distance; cout << " pas." << endl; } protected: int sonAge; int sonPoids; }; class Chien: public Mammifere { public: void Bouger() const; }; void Chien::Bouger() const { cout << "Mthode Bouger de Chien...\n"; Mammifere::Bouger(3); } int main() { Mammifere grosAnimal; Chien Fido; grosAnimal.Bouger(2); Fido.Mammifere::Bouger(6); return 0; }
378
Le langage C++
Les lignes 34 et 35 crent les objets grosAnimal et Fido, respectivement de la classe Mammifere et de la classe Chien. La mthode appele la ligne 36 est la mthode Bouger() de la classe Mammifere qui attend un paramtre entier. Dans le listing prcdent, le programmeur souhaitait galement faire bouger lobjet Chien en appelant la fonction Bouger(int) de la classe Chien, or cette classe a redni la mthode (sans paramtre) sans rednir galement celle qui en attend un elle ne dispose donc pas dune version de cette mthode prenant un entier en paramtre. Pour rsoudre ce problme, la ligne 37 appelle explicitement la mthode Bouger(int) de la classe de base. Lors de lappel des mthodes rednies des classes anctres laide de "::", si une nouvelle classe est insre dans la hirarchie dhritage entre le descendant et son anctre, le descendant fera un donc appel qui ignorera la classe intermdiaire et risque de ne pas appeler certaines capacits essentielles, implmentes par celle-ci.
Ne pas faire
Masquer une fonction de la classe de base en
ce Astu
Faire
Driver des classes existantes et bien testes
modiant sa signature.
Oublier que const fait partie de la signature. Oublier que le type du rsultat ne fait pas
partie de la signature.
Mthodes virtuelles
Nous avons vu quun objet Chien est-un objet Mammifere. Jusqu maintenant, cela signiait simplement quil hritait de tous les attributs (donnes) et de toutes les fonctionnalits (mthodes) de sa classe de base. Cependant, en C++ la relation est-un va beaucoup plus loin que cela. En C++, il est en effet possible dtendre le polymorphisme pour permettre aux pointeurs vers les classes de base de recevoir des objets des classes drives. On peut donc crire :
Mammifere* pMammifere = new Chien;
Cette instruction cre un nouvel objet Chien sur le tas et renvoie un pointeur vers cet objet, que lon peut affecter un pointeur sur un objet Mammifere. Ceci est possible car un chien est un mammifre.
Chapitre 12
Hritage
379
Info
Il sagit de lessence mme du polymorphisme. Par exemple, vous pouvez crer plusieurs types de fentres (botes de dialogue, fentres de dlement, zones de listes, etc.) et associer chacune delles une fonction virtuelle dessiner(). En affectant les types drivs un pointeur de fentre, le programme est capable dappeler la fonction dessiner() approprie, quel que soit le type de fentre trait.
Vous pouvez ensuite utiliser ce pointeur pour appeler nimporte quelle mthode de la classe Mammifere. Lobjectif est que les mthodes rednies dans la classe Chien() appellent la fonction adquate et cest justement ce que permettent les fonctions virtuelles. Pour crer une fonction virtuelle, ajoutez le mot-cl virtual devant sa dclaration. Le Listing 12.8 illustre parfaitement ce processus. Listing 12.8 : Utilisation de fonctions virtuelles
1: 2: 3: 4: 5: 6: 7: 8: 8a: 9: 9a: 10: 10a: 11: 11a: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 24a: 25: //Listing12.8 Utilisation de fonctions virtuelles #include <iostream> using std::cout; class Mammifere { public: Mammifere():wAge(1) { cout << "Constructeur de Mammifere...\n"; } virtual ~Mammifere() { cout << "Destructeur de Mammifere...\n"; } void Bouger() const { cout << "Les mammifres se dplacent dun pas\n"; } virtual void Crier() const { cout << "Le cri du mammifre!\n"; } protected: int sonAge; }; class Chien: public Mammifere { public: Chien() { cout << "Constructeur de Chien...\n"; } virtual ~Chien() { cout << "Destructeur de Chien...\n"; } void RemuerQueue() { cout << "Je remue la queue...\n"; } void Crier()const { cout << "Ouah Ouah!\n"; } void Bouger()const { cout << "Le chien se dplace de 5 pas...\n"; } };
380
Le langage C++
La ligne 11 dnit la mthode virtuelle Crier() dans la classe Mammifere. Le concepteur de cette classe indique donc quil sattend ce quelle devienne la classe de base dune autre et que la classe drive voudra srement rednir cette mthode La ligne 29 cre un pointeur vers un Mammifere (pChien), mais lui affecte ladresse dun nouvel objet Chien. Cette affectation est autorise puisquun chien est-un mammifre. La ligne 30 se sert ensuite de ce pointeur pour appeler la mthode Bouger(). Comme il sait que pChien est un Mammifere, le compilateur examine lobjet Mammifere pour trouver sa mthode Bouger(). La ligne 10 montre quil sagit dune mthode classique, non virtuelle, et cest la raison pour laquelle il appelle la version de Mammifere. La ligne 31 appelle la mthode Crier() sur ce mme pointeur. Celle-ci tant virtuelle (ligne 11), cest la mthode rednie dans la classe Chien qui est cette fois-ci invoque. Tout ceci a un aspect presque magique. La fonction appelante sait quelle dispose dun pointeur vers un Mammifere, pourtant cest une mthode de Chien qui est appele. En fait, si lon avait un tableau de pointeurs de Mammifere, pointant chacun sur une sous-classe diffrente de celle-ci, on pourrait les appeler tour de rle et, chaque fois, ce serait la mthode approprie qui serait invoque. Cest ce que lon fait dans le Listing 12.9. Listing 12.9 : Appels conscutifs de plusieurs fonctions virtuelles
1: 1a: 2: 3: 4: 5: 6: //Listing12.9 Appels conscutifs de // plusieurs fonctions virtuelles #include <iostream> using namespace std; class Mammifere {
Chapitre 12
Hritage
381
7: public: 8: Mammifere():sonAge(1) { } 9: virtual ~Mammifere() { } 10: virtual void Crier() const 10a: { cout << "Le cri du mammifre!\n"; } 11: 12: protected: 13: int sonAge; 14: }; 15: 16: class Chien: public Mammifere 17: { 18: public: 19: void Crier() const { cout << "Ouah Ouah!\n"; } 20: }; 21: 22: class Chat: public Mammifere 23: { 24: public: 25: void Crier() const { cout << "Miaou!\n"; } 26: }; 27: 28: 29: class Cheval: public Mammifere 30: { 31: public: 32: void Crier() const { cout << "Hihiiiii!\n"; } 33: }; 34: 35: class Cochon: public Mammifere 36: { 37: public: 38: void Crier() const { cout << "Grouiiiik !\n"; } 39: }; 40: 41: int main() 42: { 43: Mammifere* Tableau[5]; 44: Mammifere* ptr; 45: int choix, i; 46: for ( i = 0; i < 5; i++) 47: { 48: cout << 48a: "(1)Chien (2)Chat (3)Cheval (4)Cochon - Votre choix: "; 49: cin >> choix; 50: switch (choix) 51: {
382
Le langage C++
52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68:
case 1: ptr = new Chien; break; case 2: ptr = new Chat; break; case 3: ptr = new Cheval; break; case 4: ptr = new Cochon; break; default: ptr = new Mammifere; break; } Tableau[i] = ptr; } for (i = 0; i < 5; i++) Tableau[i]->Crier(); return 0; }
Ce programme allg illustre les mthodes virtuelles sous leur forme la plus pure. Les quatre classes qui sont dclares (Chien, Chat, Cheval et Cochon) drivent toutes de Mammifere. La ligne 10 dclare la fonction membre Crier() de la classe Mammifere comme virtuelle. Les quatre classes drives rednissent toutes cette mthode (lignes 19, 25, 32 et 38). Le programme boucle cinq fois entre les lignes 46 et 64. chaque itration, lutilisateur est invit choisir un objet crer et lon ajoute automatiquement au tableau un nouveau pointeur vers ce type dobjet grce linstruction switch des lignes 50 62. Au moment de la compilation, il est donc impossible de connatre les objets qui seront crs et par consquent les mthodes Crier() qui seront appeles. Le pointeur ptr est associ son objet au moment de lexcution : cest ce que lon appelle une liaison dynamique (ou liaison retarde), par opposition la liaison statique (qui lieu au moment de la compilation).
Chapitre 12
Hritage
383
Le programme boucle nouveau sur le tableau entre les lignes 65 et 66. Cette fois-ci, chaque objet appelle sa mthode Crier(). Celle-ci tant virtuelle dans la classe de base, ce seront les mthodes Crier() appropries chaque type qui seront appeles. Vous voyez dans le rsultat que si vous choisissez des types diffrents, la mthode correspondante est appele chaque fois.
Le fait de marquer une mthode membre comme virtuelle dans la classe de base implique-t-il de devoir faire de mme dans les classes drives ? Non, lorsquune mthode est virtuelle, elle le reste si vous la rednissez dans les classes drives. Il est cependant conseill (bien que non obligatoire) de la signaler comme virtuelle, an de rendre le code plus comprhensible.
Partie Mammifere
Lorsque lon cre une mthode virtuelle dans un objet, celui-ci doit garder la trace de cette fonction. Beaucoup de compilateurs crent une table de fonction virtuelle, nomme v-table. chaque type correspond une table et chaque objet dun type dispose dun pointeur de table virtuel (appel vptr ou v-pointeur), qui pointe sur cette table. Bien que les implmentations puissent varier, tous les compilateurs doivent accomplir la mme tche. Le v-pointeur de chaque objet pointe sur la v-table qui contient elle-mme des pointeurs vers chaque mthode virtuelle (les pointeurs de fonctions sont traits au Chapitre 15). Lorsque la partie Mammifere de lobjet Chien est cre, le vptr est donc initialis pour pointer sur la zone approprie de la v-table, comme le montre la Figure 12.3.
384
Le langage C++
VPTR
& Bouger
Mammifere
& Crier
Lorsque le constructeur de Chien est appel et que la partie Chien de cet objet est ajoute, le v-pointeur est modi pour pointer sur la fonction virtuelle rednie (le cas chant) dans lobjet Chien (voir Figure 12.4).
Figure 12.4 La v-table dun objet Chien.
VPTR & Mammifere : Bouger() & Chien : Crier() Chien
Mammifere
Lorsque lon utilise un pointeur vers un objet Mammifere, le v-pointeur continue pointer sur la fonction approprie au type "rel" de lobjet. Ainsi, lorsquon va appeler Crier(), on obtiendra bien la fonction adquate.
Chapitre 12
Hritage
385
On pourrait transtyper le pointeur de Mammifere en pointeur sur un Chien. Toutefois, cette pratique est dconseiller, car elle nest pas sre si le Mammifere point nest pas un Chien : il existe un mthode bien plus able dappeler RemuerQueue(). Les transtypages doivent tre utiliss avec la plus grande prudence, car ils engendrent des erreurs dexcution. Pour en savoir plus, reportez-vous aux Chapitres 15 et 20.
386
Le langage C++
30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73:
void FoncRef(Mammifere&); int main() { Mammifere* ptr = 0; int choix; while (1) { bool fQuit = false; cout << "(1)Chien (2)Chat (0)Quitter - Votre choix: "; cin >> choix; switch (choix) { case 0: fQuit = true; break; case 1: ptr = new Chien; break; case 2: ptr = new Chat; break; default: ptr = new Mammifere; break; } if (fQuit) break; FoncPtr(ptr); FoncRef(*ptr); FoncVal(*ptr); } return 0; } void FoncVal(Mammifere ValeurMammifere) { ValeurMammifere.Crier(); } void FoncPtr(Mammifere * pMammifere) { pMammifere->Crier(); } void FoncRef(Mammifere & rMammifere) { rMammifere.Crier(); }
Chapitre 12
Hritage
387
Le programme commence par les dclarations des versions rduites de Mammifere, Chien et Chat. Les fonctions FoncPtr(), FoncRef() et FoncVal() sont ensuite dclares et prennent respectivement un pointeur sur un objet Mammifere, une rfrence un objet Mammifere et un objet Mammifere. Comme vous pouvez le constater aux lignes 60 73, elles appellent toutes les trois la mthode Crier(). Lutilisateur a le choix entre un chien et un chat. En fonction de la valeur saisie, les lignes 44 46 crent un pointeur vers le type correspondant. Sur la premire ligne du rsultat, lutilisateur a choisi loption 1. Un objet Chien est alors cr sur le tas (ligne 44), puis pass successivement aux trois fonctions comme pointeur en ligne 53, comme rfrence en ligne 54 et par valeur en ligne 55. Le pointeur et les appels de rfrence permettent dappeler la fonction virtuelle Chien>Crier() (voir les deux premires lignes du rsultat aprs le choix de lutilisateur). Par contre, on a pass par valeur le pointeur drfrenc la fonction dnie aux lignes 60 63. Celle-ci attendant un objet Mammifere, le compilateur dcoupe la partie de base contenue dans lobjet Chien et ne conserve que la partie Mammifre. Lorsque lon appelle la mthode Crier() de Mammifere la ligne 72, seules les informations sur les mammifres sont donc disponibles, tout ce qui concerne les chiens a disparu. On peut le constater avec la troisime ligne du rsultat, aprs le choix de lutilisateur. Cet effet est appel dcoupage, car les parties Chien (celles de votre classe drive) de votre objet ont t supprimes lors de la conversion en Mammifere (la classe de base). Cette opration est ensuite rpte pour lobjet Chat, avec les mmes consquences.
Destructeurs virtuels
Il est courant et classique de passer un pointeur sur un objet dune classe drive lorsque le programme attend un pointeur sur un objet de la classe de base. Que se passe-t-il si le pointeur de lobjet driv est supprim ? Si le destructeur est virtuel, comme cela devrait
388
Le langage C++
tre le cas, tout se passera bien cest le destructeur de la classe drive qui sera appel. Ce destructeur appelant automatiquement celui de la classe de base, tout lobjet sera correctement dtruit. La rgle est la suivante : si lune des mthodes dune classe est virtuelle, le destructeur de cette classe doit ltre lui aussi. Les listings de ce chapitre comprenaient des destructeurs virtuels. Vous savez maintenant pourquoi ! En gnral, il vaut toujours mieux que les destructeurs soient virtuels.
Info
Chapitre 12
Hritage
389
13: 14: 15: 16: 17: 18: 18a 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58:
int GetAge()const { return sonAge; } protected: int wAge; }; Mammifere::Mammifere (const Mammifere & rhs): sonAge(rhs.GetAge()) { cout << "Constructeur de copie de Mammifere...\n"; } class Chien: public Mammifere { public: Chien() { cout << "Constructeur de Chien...\n"; } virtual ~Chien() { cout << "Destructeur de Chien...\n"; } Chien(const Chien & rhs); void Crier()const { cout << "Ouah Ouah !\n"; } virtual Mammifere* Clone() { return new Chien(*this); } }; Chien::Chien(const Chien & rhs): Mammifere(rhs) { cout << "Constructeur de copie de Chien...\n"; } class Chat: public Mammifere { public: Chat() { cout << "Constructeur de Chat...\n"; } ~Chat() { cout << "Destructeur de Chat...\n"; } Chat(const Chat &); void Crier()const { cout << "Miaou!\n"; } virtual Mammifere* Clone() { return new Chat(*this); } }; Chat::Chat(const Chat & rhs): Mammifere(rhs) { cout << "Constructeur de copie de Chat...\n"; } enum ANIMALS { MAMMIFERE, CHIEN, CHAT}; const int NbreTypesAnimaux = 3; int main() {
390
Le langage C++
59: 60: 61: 62: 63: 64: 64a: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: }
Mammifere *Tableau[NbreTypesAnimaux ]; Mammifere* ptr; int choix, i; for ( i = 0; i < NbreTypesAnimaux; i++) { cout << "(1)Chien (2)Chat (3)Mammifre - Votre choix: "; cin >> choix; switch (choix) { case CHIEN: ptr = new Chien; break; case CHAT: ptr = new Chat; break; default: ptr = new Mammifere; break; } Tableau[i] = ptr; } Mammifere *AutreTableau[NbreTypesAnimaux]; for (i = 0; i < NbreTypesAnimaux; i++) { Tableau[i]->Crier(); AutreTableau[i] = Tableau[i]->Clone(); } for (i = 0; i < NbreTypesAnimaux; i++) AutreTableau[i]->Crier(); return 0;
Chapitre 12
Hritage
391
Le Listing 12.11 ressemble beaucoup aux deux prcdents, sauf que la ligne 12 ajoute la classe Mammifere une nouvelle mthode virtuelle appele Clone(). Cette mthode renvoie un pointeur sur un nouvel objet Mammifere. Pour cela, elle appelle le constructeur de copie en passant en paramtre lobjet lui-mme (*this) comme rfrence constante. Chien et Chat rednissent toutes les deux la mthode Clone() en initialisant leurs donnes et en passant des copies de leurs objets leurs propres constructeurs de copie. Cette mthode tant virtuelle, cela revient donc crer un constructeur de copie "virtuel". Vous pouvez le constater lorsque la ligne 81 sexcute. Lutilisateur a le choix entre chiens, chats et mammifres. Les objets correspondants sont crs de la ligne 68 la ligne 73. Pour chaque choix, un pointeur est stock dans un tableau (ligne 75). Lorsque le programme boucle sur le tableau (lignes 78 82), chaque objet appelle ses mthodes Crier() et Clone(). Le rsultat de lappel Clone() la ligne 81 est un pointeur vers une copie de lobjet, qui est ensuite stock dans un second tableau. la premire ligne du rsultat, lutilisateur a choisi loption 1 pour crer un objet Chien. Les constructeurs de Mammifere et de Chien sont alors appels. La procdure est semblable lorsquil choisit un Chat (ligne 4) et un Mammifere (ligne 8). La ligne 9 du rsultat est le rsultat de lappel Crier() sur le premier objet, le Chien. La mthode virtuelle Crier() est appele et invoque donc la version correcte de cette mthode. Puis, on appelle la mthode Clone() et, puisquelle est galement virtuelle, cest la mthode Clone() de Chien qui est invoque. Le constructeur de Mammifere et le constructeur de copie de Chien sont donc appels. Le mme traitement sexcute pour le chat et le mammifre (lignes 12 14 et lignes 15 et 16) Pour nir, le nouveau tableau est parcouru (lignes 83 et 84) et chaque lment appelle sa mthode Crier() (lignes 17 19 du rsultat).
392
Le langage C++
taille). Vous avez donc pay lessentiel du billet dentre et vous pouvez alors en proter : le destructeur devra tre virtuel et toutes les autres mthodes devront srement ltre aussi. Prenez le temps dexaminer chacune des mthodes non virtuelles et assurez-vous davoir bien compris pourquoi elles ne le sont pas.
Faire
Utiliser des mthodes virtuelles si vous envi-
Ne pas faire
Dclarer un constructeur virtuel. Tenter daccder des donnes prives dans
Questions-rponses
Q Les membres et les mthodes hrits sont-ils transmis aux gnrations suivantes ? Si Chien drive de Mammifere et que cette dernire classe drive elle-mme de Animal, Chien hrite-t-il des mthodes et des donnes de Animal ? R Oui. Les classes drives hritent de lensemble des mthodes et des donnes de toutes leurs classes de base, mais elles ne peuvent accder qu celles qui sont protges ou publiques. Q Si, dans lexemple prcdent, Mammifere rednit une mthode de la classe Animal, la classe Chien accdera-t-elle la fonction dorigine ou la fonction rednie ? R Si Chien hrite de Mammifere, cest la fonction rednie qui sera appele. Q Est-ce quune classe drive peut rendre prive une mthode publique de la classe de base ? R Oui, la classe drive peut rednir la mthode et la rendre prive. Elle le demeure ensuite pour toutes les classes drives suivantes. Cela doit toutefois tre vit autant que possible, car les utilisateurs de votre classe sattendront ce quelle contienne la somme des mthodes fournies par ses anctres. Q Pourquoi toutes les fonctions de classe ne sont-elles pas virtuelles ? R La premire fonction cre dans la table virtuelle consomme davantage de ressources que les autres, mais ce surcot augmente peu ensuite. De nombreux programmeurs C++ pensent que si une mthode est virtuelle, toutes les autres doivent ltre galement. Dautres ne sont pas daccord et pensent quil doit toujours exister une bonne raison de le faire.
Chapitre 12
Hritage
393
Q Supposons que la fonction Fonc() soit virtuelle dans une classe de base et surcharge pour prendre en paramtre un ou deux entiers. La classe drive rednit la fonction qui na quun paramtre. Laquelle des deux fonctions sera appele lorsquun pointeur vers un objet driv invoquera la fonction avec deux paramtres ? R La rednition de la fonction avec un seul paramtre a masqu les deux fonctions de la classe de base, ce qui les rend inutilisables. Le compilateur va produire une erreur indiquant que la fonction nattend quun seul paramtre de type entier.
Exercices
1. Dclarez une fonction virtuelle qui prend un entier en paramtre et renvoie void. 2. Dclarez une classe Carre, drive de Rectangle, elle-mme drive de Forme. 3. Dans lExercice 2, le constructeur de Forme ne prend pas de paramtres, alors que celui de Rectangle attend une longueur et une largeur. Celui de Carre attend seulement une longueur. Montrez linitialisation dun Carre ralise par son constructeur. 4. crivez un constructeur de copie virtuel pour la classe Carre de lExercice 3. 5. CHERCHEZ LERREUR dans cet extrait de programme :
void Fonction(Forme);
394
Le langage C++
13
Tableaux et chanes
Au sommaire de ce chapitre
Nature, rle et dclaration des tableaux Nature des chanes et cration partir de tableaux de caractres Relations entre tableaux et pointeurs Utilisation de larithmtique des pointeurs Les listes chanes
Cette ligne dclare un tableau de 25 entiers longs, nomm TableauLong. Le compilateur rservera un espace sufsant en mmoire pour contenir ces 25 lments. Si un entier long
396
Le langage C++
occupe 4 octets, cette dclaration rserve donc 100 octets contigus en mmoire vive (voir Figure 13.1).
Figure 13.1 Dclaration dun tableau.
4 octets
100 octets
Info
Chapitre 13
Tableaux et chanes
397
int monTableau[5]; // Tableau de cinq entiers int i; for ( i = 0; i < 5; i++) // 0 4 { std::cout << "Valeur de monTableau[" << i << "]: "; std::cin >> monTableau[i]; } for (i = 0; i < 5; i++) std::cout << i << ": " << monTableau[i] << std::endl; return 0; }
Le Listing 13.1 cre un tableau, vous fait saisir les valeurs de chaque lment, puis les afche lcran. La dclaration de la ligne 5 indique que monTableau est un tableau dentiers ; elle contient le chiffre 5 entre crochets, ce qui signie que le tableau contient cinq lments entiers, chacun pouvant tre considr comme une variable entire. la ligne 7, la variable de boucle sincrmente de 0 4 an dinitialiser les lments du tableau avec la valeur saisie par lutilisateur. Si lon se penche sur la ligne 10, on voit que laccs chaque lment se fait laide du nom du tableau suivi de crochets, contenant le dcalage. Chaque lment peut ensuite tre trait comme une variable du type des lments du tableau. La premire valeur est stocke dans monTableau[0], la deuxime dans monTableau[1], etc. La deuxime boucle permet dafcher toutes les valeurs lcran. La numrotation des lments dun tableau commence 0 (jamais 1), ce qui est une cause frquente derreurs pour les dbutants en C++. Lindice doit tre considr comme un dcalage. Par exemple, un tableau contenant 10 lments sera numrot de Tableau[0] Tableau[9], llment Tableau[10] correspondant un onzime lment situ hors limite.
Info
398
Le langage C++
Ce programme contient des erreurs classiques ne pas commettre. Ne lexcutez pas, car il risquerait de faire planter votre systme.
Listing 13.2 : Stockage dune valeur au-del de la limite suprieure dun tableau
0: 1: 2: 3: 4: 5: 6: // Listing13.2 - criture dune valeur au-del de la limite // suprieure dun tableau #include <iostream> using namespace std; int main() {
Chapitre 13
Tableaux et chanes
399
7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 19a: 20: 21: 22: 22a: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 39a: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: }
// sentinelles long SentinelUne[3]; long TableauCible[25]; // Tableau remplir long SentinelDeux[3]; int i; for (i = 0; i < 3; i++) { SentinelUne[i] = 0; SentinelDeux[i] = 0; } for (i = 0; i < 25; i++) TableauCible[i] = 10; // test cout << cout << cout << << de la valeur actuelle (devrait tre 0) "Test 1: \n"; "TableauCible[0]: " << TableauCible[0] << endl; "TableauCible[24]: " << TableauCible[24] endl << endl;
for (i = 0; i < 3; i++) { cout << "SentinelUne[" << i << "]: "; cout << SentinelUne[i] << endl; cout << "SentinelDeux[" << i << "]: "; cout << SentinelDeux[i]<< endl; } cout << "\Affectation en cours..."; for (i = 0; i <= 25; i++) // Va un peu trop loin! TableauCible[i] = 20; cout cout cout cout "\nTest 2: \n"; "TableauCible[0]: " << TableauCible[0] << endl; "TableauCible[24]: " << TableauCible[24] << endl; "TableauCible[25]: " << TableauCible[25] << endl << endl; for (i = 0; i < 3; i++) { cout << "SentinelUne[" << i << "]: "; cout << SentinelUne[i]<< endl; cout << "SentinelDeux[" << i << "]: "; cout << SentinelDeux[i]<< endl; } return 0; << << << <<
400
Le langage C++
Les deux tableaux de trois entiers longs dclars aux lignes 8 et 10 vont jouer le rle de sentinelles autour du tableau TableauCible. Ces sentinelles sont initialises 0 par les lignes 12 16. Comme elles sont dclares avant et aprs le tableau cible, il y a de fortes chances quelles soient places en mmoire de part et dautre de ce dernier. Si le programme crit des donnes aprs la n de TableauCible, les sentinelles seront donc probablement modies. Certains compilateurs allouent vers le bas de la mmoire alors que dautres allouent vers le haut, cest pour cette raison que lon a encadr le tableau cible par deux sentinelles. Lors du premier test, ralis de la ligne 20 la ligne 30, les valeurs des sentinelles sont correctes, comme leur afchage, tout comme le premier et le dernier lment de TableauCible. la ligne 34, les membres de TableauCible sont tous raffects avec la valeur 20, mais le compteur atteint (ligne 34) la valeur de dcalage 25, qui nexiste pas le tableau cible. Les valeurs de TableauCible apparaissent lcran (lignes 37 39) sous forme de deuxime test. Vous pouvez constater (ligne 39) que TableauCible[25] contient bien le nombre 20. Cependant, lorsque les sentinelles sont afches, on constate que la valeur de SentinelUne[0] a t modie. En effet, la zone mmoire situe 25 lments de
Chapitre 13
Tableaux et chanes
401
TableauCible[0] est la mme que SentinelUne[0]. Lorsquon a tent daccder la valeur inexistante TableauCible[25], on a donc accd en ralit au contenu de SentinelUne[0]. Les compilateurs utilisant diffremment la mmoire, vos rsultats peuvent varier. Il se peut que les sentinelles ne soient pas crases. Dans ce cas, essayez de modier la ligne 33 pour utiliser un autre indice (passez de 25 26). Ceci augmentera la probabilit dcraser une sentinelle, mais vous risquez bien sr dcraser autre chose ou de faire planter votre systme.
Info
Cette erreur est difcile dtecter, car la valeur de SentinelUne[0] est modie dans une partie du programme qui naccde pas directement SentinelUne.
Erreurs dintervalle
Il est si courant dcrire une valeur aprs la limite suprieure dun tableau que cette erreur a son propre nom : on lappelle erreur dintervalle ou erreur de borne. Lorsque lon demande combien il y a de piquets sur une barrire de 10 mtres, sachant que lespacement entre les piquets est de un mtre, la plupart des gens rpondent sans rchir : "10 !", or la bonne rponse est 11 (voir Figure 13.2).
Figure 13.2 Erreurs dintervalle.
1 2 3 4 5 6 7 8 9 10 11
1m
2m
3m
4m
5m
6m
7m
8m
9m
10m
Cette faon de compter "en partant par dfaut de un" est la bte noire des programmeurs C++ dbutants. Avec le temps, vous vous habituerez lide quun tableau de 25 lments est numrot de 0 24. Certains programmeurs dsignent Tableau[0] comme llment 0. Cette habitude est proscrire, car si Tableau[0] est llment 0, quoi correspond Tableau[1] ? au premier lment ? Dans ce cas, lorsque vous verrez Tableau[24], raliserez-vous quil ne sagit pas du vingt-quatrime lment du tableau, mais du vingt-cinquime ? Pour viter toute confusion, il est prfrable de dire que Tableau[0] est au dcalage 0 et correspond donc au premier lment.
Info
402
Le langage C++
Cette instruction dclare TableauInt comme un tableau de cinq entiers et affecte la valeur 10 TableauInt[0], la valeur 20 TableauInt[1], etc. Si vous omettez de prciser la taille du tableau, le compilateur produira un tableau contenant autant dlments que la liste et leur affectera les valeurs respectives. Par exemple, lexpression :
int TableauInt[] = { 10, 20, 30, 40, 50 };
produit le mme tableau que prcdemment, un tableau de cinq lments. Il est interdit dinitialiser plus dlments que nen contient le tableau. Exemple :
int TableauInt[5] = { 10, 20, 30, 40, 50, 60 };
Cette expression produira une erreur de compilation car vous avez tent daffecter six valeurs aux cinq lments du tableau. En revanche, vous avez le droit dcrire :
int TableauInt[5] = { 10, 20 };
Ici, vous avez dclar un tableau de cinq lments et initialis uniquement les deux premiers, TableauInt[0] et TableauInt[1].
Faire
Laisser le compilateur calculer la taille des
Ne pas faire
crire au-del de la limite suprieure du
tableaux initialiss.
Se rappeler que le premier lment dun
tableau.
Donner des noms confus aux tableaux. Les
noms doivent tre vocateurs, exactement comme pour toute autre variable.
Dclaration de tableaux
Pour linstant, nous avons utilis des "nombres magiques" : 3 pour la taille des tableaux de sentinelles et 25 pour la taille de TableauCible. Il vaut mieux utiliser des constantes pour pouvoir modier toutes ces valeurs un seul endroit.
Chapitre 13
Tableaux et chanes
403
Un tableau peut porter un nom de variable, condition quil ne sagisse pas du nom dune variable ou dun tableau existant dans la mme porte. Vous ne pouvez donc pas avoir la fois un tableau Chats[5] et une variable Chats. En outre, lors de la dclaration du nombre dlments, vous pouvez utiliser une constante ou une numration. Cest mme prfrable des nombres littraux car cela permet de regrouper au mme endroit le contrle du nombre des lments. Le Listing 13.2 utilisait des nombres littraux : si vous souhaitez changer le TableauCible pour quil ne contienne plus que vingt lments, vous deviez modier plusieurs lignes de code. Si vous aviez utilis une constante, il sufsait de ne modier que la valeur de cette constante. Indiquer le nombre dlments, ou la dimension, avec une numration est un peu diffrent. Le Listing 13.3 montre la cration dun tableau contenant une valeur par jour de la semaine. Listing 13.3 : Utilisation de constantes et dnumrations dans les tableaux
0: 1: 2: 3: 4: 5: 6: 7: 8: 8a: 9: 10: 10a 11: 12: // Listing13.3 // Dimensionnement de tableaux // laide de constantes et dnumrations #include <iostream> int main() { enum Semaine { Dim, Lun, Mar, Mer, Jeu, Ven, Sam, NbJoursSemaine }; int TabSemaine[NbJoursSemaine] = { 10, 20, 30, 40, 50, 60, 70 }; std::cout << "La valeur de Mardi est: " << TabSemaine[Mar]; return 0; }
La ligne 6 cre lnumration Semaine, qui contient huit membres. Le premier lment (Dim) et le dernier lment (NbJoursSemaine) correspondent respectivement 0 et 7. La ligne 8 dclare le tableau TabSemaine de NbJoursSemaines lments, cest--dire 7. La ligne 10 utilise la constante numre Mar comme indice du tableau. Cette constante correspondant 2, llment dsign est donc le troisime tableau, TabSemaine[2]. La valeur obtenue est afche la ligne 10.
404
Le langage C++
Tableaux Pour dclarer un tableau, indiquez le type des objets stocker, suivi du nom du tableau et de la valeur dindice correspondant au nombre dlments. Exemple 1
int TableauInt[90];
Exemple 2
long * TabPointeursSurLongs[100];
Pour accder aux lments du tableau, indiquez lindice souhait. Exemple 1
Tableaux dobjets
Nimporte quel objet, dun type prdni ou non, peut tre stock dans un tableau. Il suft dindiquer au compilateur le type des lments stocker et leur nombre : le compilateur pourra alors calculer lespace mmoire ncessaire pour chaque lment en fonction de la dclaration de la classe. La classe des lments dun tableau doit comprendre un constructeur par dfaut sans paramtre, an que les objets puissent tre crs au moment de la dnition du tableau. Laccs aux donnes membres dun tableau dobjets se fait en deux tapes. On identie dabord llment du tableau laide de loprateur dindexation ([ ]), puis on lui ajoute loprateur daccs aux membres (.). Le Listing 13.4 montre comment crer et manipuler un tableau de cinq objets Chat. Listing 13.4 : Cration dun tableau dobjets
0: 1: 2: 3: // Listing13.4 - Un tableau dobjets #include <iostream> using namespace std;
Chapitre 13
Tableaux et chanes
405
4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32:
class Chat { public: Chat() { sonAge = 1; sonPoids = 5; } ~Chat() {} int GetAge() const { return sonAge; } int GetPoids() const { return sonPoids; } void SetAge(int age) { sonAge = age; } private: int sonAge; int sonPoids; }; int main() { Chat Portee[5]; int i; for (i = 0; i < 5; i++) Portee[i].SetAge(2*i +1); for (i = 0; i < 5; i++) { cout << "Chat numro " << i+1 << ": "; cout << Portee[i].GetAge() << endl; } return 0; }
La classe Chat est dclare de la ligne 5 la ligne 17. Elle doit possder un constructeur par dfaut pour que ses objets puissent tre crs dans un tableau. Ici, ce constructeur par dfaut est dclar et dni la ligne 8. Pour chaque Chat, lge par dfaut est x 1 et le poids par dfaut 5. Noubliez pas que si vous dnissez un autre constructeur, le constructeur par dfaut ne sera pas produit par le compilateur (ce qui vous oblige le crer vous-mme).
406
Le langage C++
La premire boucle for xe lge des cinq objets du tableau (lignes 23 et 24), la seconde (lignes 26 30) accde chaque lment et appelle sa mthode GetAge(), ce qui permet dafcher lge de tous les chats. La mthode GetAge() de chaque chat est appele en accdant chacun des lments du tableau Portee, suivi de loprateur point (.) et du nom de la fonction membre. Vous pouvez accder dautres membres et mthodes de la mme manire.
Tableaux multidimensionnels
Un tableau peut avoir plus dune dimension. Chacune delles est reprsente par un indice du tableau : un tableau bidimensionnel aura donc deux indices, un tableau tridimensionnel trois indices, et ainsi de suite. Vous pouvez crer autant de dimensions que vous le souhaitez, bien quen pratique la plupart de vos tableaux nauront quune ou deux dimensions. Un chiquier est un bon exemple de tableau deux dimensions. Une dimension reprsente les 8 lignes, lautre les 8 colonnes (voir Figure 13.3).
Figure 13.3 Lchiquier est un tableau bidimensionnel.
4 5 6 7
8
1 2 3
8 7 6 5 4 3 2 1 0 1 1 2 3 4 5 6 7
8 6 5 4 3 2 1 2
8 7 6 5 4 3 2 1 3 7
8 7 6 5 4 3 2 1 4
8 7 6 5 4 3 2 1 5
8 7 6 5 4 3 2 1 6
8 7 6 5 4 3 2 1 7
8 7 6 5 4 3 2 1 8
Supposons que vous ayez une classe CARRE. La dclaration dun tableau Echiquier serait alors :
CARRE Echiquier[8][8];
Vous pourriez galement reprsenter cet chiquier comme un tableau une dimension de 64 carrs :
CARRE Echiquier[64];
Cependant, ce tableau ne correspondrait pas autant un vritable chiquier quun tableau deux dimensions. Lorsque la partie commence, le roi se situe la quatrime position de
Chapitre 13
Tableaux et chanes
407
la premire ligne. La numrotation commenant zro, cette position sexprime donc ainsi :
Echiquier[0][3];
en supposant que le premier indice correspond aux lignes et le second aux colonnes.
les trois premiers lments iront donc dans Tableau[0], les trois suivants dans Tableau[1], etc. :
int Tableau[5][3] = { 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15 };
Pour plus de clart, vous pouvez regrouper les diffrentes initialisations entre accolades. Exemple :
int Tableau[5][3] = { {1,2,3}, {4,5,6}, {7,8,9}, {10,11,12}, {13,14,15} };
Le compilateur ne tient pas compte des accolades internes, mais elles clarient la rpartition des nombres. Lorsque vous initialisez les lments dun tableau, chaque valeur doit tre spare de la suivante par une virgule, indpendamment des accolades. Linitialisation complte doit gurer entre accolades et se terminer par un point-virgule. Le Listing 13.5 cre un tableau deux dimensions, dont la premire est un ensemble de nombres de 0 4. La seconde regroupe les doubles des valeurs de la premire. Listing 13.5 : Cration dun tableau plusieurs dimensions
0: 1: 2: // Listing13.5 - Cration dun tableau plusieurs dimensions #include <iostream> using namespace std;
408
Le langage C++
int main() { int UnTableau[2][5] = { {0,1,2,3,4}, {0,2,4,6,8}}; for (int i = 0; i < 2; i++) { for (int j = 0; j < 5; j++) { cout << "UnTableau[" << i << "][" << j << "]: "; cout << UnTableau[i][j]<< endl; } } return 0; }
La ligne 6 dclare UnTableau comme un tableau deux dimensions. La premire dimension indique quil y aura deux ensembles de donnes et la deuxime est forme de cinq entiers. Cela cre donc une matrice de 2 lignes par 5 colonnes, comme le montre la Figure 13.4
Figure 13.4 Tableau de 2 par 5.
4 2 1 0 0 UnTableau[5] [2] 3 1 2 3 4
Les valeurs sont initialises sur la base des deux ensembles de nombres. Le premier comprend les nombres initiaux, le deuxime les nombres doubls. Dans ce listing, les valeurs sont simplement dnies, mais elles pourraient tre calcules. Les lignes 7 et 9
Chapitre 13
Tableaux et chanes
409
crent deux boucles for imbriques. La boucle externe ( partir de la ligne 7) traite chaque membre de la premire dimension (chacun des deux ensembles), puis appelle la boucle interne ( partir de la ligne 9) qui traite chaque lment de la seconde dimension, comme le montrent les valeurs afches lcran. UnTableau[0][0] est donc suivi de UnTableau[0][1]. Lindice de la premire dimension est incrment uniquement aprs lincrmentation de lindice de la seconde dimension. Le traitement recommence alors.
Tableaux et mmoire La dclaration dun tableau indique explicitement le nombre dlments qui le composent. Le compilateur se charge alors de rserver un espace sufsant en mmoire vive, mme si vous nen faites pas usage. Cette opration ne pose aucun problme lorsque vous savez exactement combien le tableau contiendra dobjets. Par exemple, un chiquier se compose de 64 cases, alors quune porte comprend entre un et dix matous. Si vous navez aucune ide du nombre dlments que devra contenir un tableau, vous devez faire appel des structures de donnes plus complexes. Cet ouvrage traite des tableaux de pointeurs, des tableaux allous sur le tas et dun certain nombre dautres collections. Vous dcouvrirez quelques structures de donnes complexes, mais vous en apprendrez plus en lisant C++ Unleashed, publi par Sams Publishing. Vous pouvez galement consulter lannexe E de cet ouvrage. Deux aspects importants de la programmation sont quil y a toujours plus de choses apprendre et quil existe toujours des livres permettant de les apprendre.
410
Le langage C++
5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37:
class Chat { public: Chat() { sonAge = 1; sonPoids = 5; } ~Chat() {} // destructeur int GetAge() const { return sonAge; } int GetPoids() const { return sonPoids; } void SetAge(int age) { sonAge = age; } private: int sonAge; int sonPoids; }; int main() { Chat * Famille[500]; int i; Chat * pChat; for (i = 0; i < 500; i++) { pChat = new Chat; pChat->SetAge(2*i +1); Famille[i] = pChat; } for (i = 0; i < 500; i++) { cout << "Chat numro " << i+1 << ": "; cout << Famille[i]->GetAge() << endl; } return 0; }
Lobjet Chat dclar de la ligne 5 la ligne 17 est identique celui du Listing 13.4. Cette fois-ci, cependant, le tableau sappelle Famille et comprend 500 lments, qui sont des pointeurs vers des objets de la classe Chat.
Chapitre 13
Tableaux et chanes
411
Dans la boucle initiale (de la ligne 24 la ligne 29), les 500 objets Chat sont crs sur le tas. Lge de chacun est gal deux fois la valeur dindice, plus un : lge du premier objet Chat est gal 1, le deuxime 3, le troisime 5, etc. Aprs la cration du pointeur, la ligne 28 affecte le pointeur au tableau. Le tableau ayant t dclar comme un tableau de pointeurs, cest le pointeur et non la valeur drfrence de ce dernier qui est ajout au tableau. La seconde boucle permet dafcher les valeurs du tableau (lignes 31 35). La ligne 33 afche un nombre pour indiquer lobjet concern. Les dcalages dindice partant de zro, la ligne 33 ajoute 1 pour un comptage commenant 1. la ligne 34, on accde au pointeur laide de lindice i du tableau Famille. Cette adresse permet alors dappeler la mthode GetAge() de lobjet concern. Dans cet exemple, le tableau Famille et ses pointeurs sont stocks dans la pile, alors que les 500 objets Chat sont crs sur le tas.
412
Le langage C++
13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 35a: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58:
int motDecale = 0;
// commencer au dbut
std::cout << "Entrez une chane: "; std::cin.getline(tampon, tailleTampon); while (LireMot(tampon, mot, motDecale)) { std::cout << "Mot obtenu: " << mot << std::endl; } return 0; } // Fonction pour analyser les mots dune chane. bool LireMot(char* chaine, char* mot, int& motDecale) { if (chaine[motDecale] == 0) // fin de la chane? return false; char *p1, *p2; p1 = p2 = chaine + motDecale; // pointe sur le mot suivant // effacer les premiers espaces for (int i = 0; i < (int)strlen(p1) &&!isalnum(p1[0]); i++) p1++; // voir sil y a un mot if (!isalnum(p1[0])) return false; // p1 pointe maintenant pour commencer le mot suivant // point p2 ici aussi p2 = p1; // amener p2 la fin du mot while (isalnum(p2[0])) p2++; // p2 est maintenant la fin du mot // p1 est au dbut du mot // la longueur du mot = la diffrence int lg = int (p2 - p1); // copier le mot dans le tampon strncpy (mot, p1, lg); // null termine la chane
Chapitre 13
Tableaux et chanes
413
59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71:
mot[lg] = \0; // trouver le dbut du mot suivant for (int j = int(p2 - chaine); j < (int)strlen(chaine) &&!isalnum(p2[0]); j++) { p2++; } motDecale = int(p2 - chaine); return true; }
Ce programme permet de saisir une phrase, qui est ensuite dcompose en mots (ensembles de caractres alphanumriques). La ligne 15 demande lutilisateur dentrer une chane, qui est ensuite passe la mthode LireMot() ligne 18 avec un tampon permettant de conserver le premier mot et une variable entire appele motDecale, initialise zro la ligne 13. LireMot() renvoie chaque mot de la chane. Ces mots sont afchs la ligne 20 jusqu ce que LireMot() renvoie false. Chaque appel LireMot() entrane un saut la ligne 26. La ligne 28 teste si la valeur de chaine[motDecale] est gale zro. Ce sera vrai si vous tes la n de la chane, auquel cas LireMot() renvoie false. cin.getline() vrie que la chane entre se termine par un caractre nul, cest--dire le caractre \0. La ligne 31 dclare deux pointeurs de caractres, p1 et p2. Ils sont initialis la ligne 32 an de pointer lindice motDecale de la chane. Initialement, motDecale valant zro, ces pointeurs pointent donc au dbut de la chane.
414
Le langage C++
Les lignes 35 et 36 parcourent la chane, en poussant p1 jusquau premier caractre alphanumrique. Les lignes 39 et 40 sassurent que lon en trouve un, sinon false est renvoy. p1 pointe maintenant sur le dbut du mot suivant et la ligne 44 initialise p2 de sorte quil pointe vers cette mme position. Les lignes 47 et 48 font parcourir le mot par p2 an quil sarrte au premier caractre qui nest pas alphanumrique. p2 pointe donc maintenant la n du mot dont le dbut est point par p1. En soustrayant p1 de p2 (ligne 53) et en transtypant le rsultat en un entier, on peut donc connatre la longueur du mot, qui est ensuite copie dans le tampon mot laide dune fonction de copie de chane fournie par la bibliothque standard. La ligne 59 ajoute un caractre nul pour marquer la n du mot. Puis, p2est incrment pour pointer au dbut du mot suivant et le dcalage de ce mot est pouss dans la rfrence entire motDecale. Enn, true est renvoy pour indiquer quon a trouv un mot. Il sagit dun exemple classique de code qui est plus comprhensible si lon utilise un dbogueur an de suivre son excution pas pas. Larithmtique des pointeurs est employe divers endroits de ce listing. En soustrayant un pointeur dun autre (comme la ligne 53), on dtermine le nombre dlments qui les spare. En outre, la ligne 55 montre que lincrmentation dun pointeur le fait passer llment suivant du tableau au lieu de simplement lui ajouter un. Larithmtique des pointeurs est trs utilise avec les pointeurs et les tableaux, mais elle reprsente aussi une activit dangereuse et doit donc tre considre avec circonspection.
dclare Famille comme un pointeur sur le premier lment dun tableau de 500 objets Chat. En dautres termes, Famille pointe sur ou contient ladresse de Famille[0]. Lavantage de dclarer Famille de cette faon est que cela vous permet dutiliser larithmtique des pointeurs pour accder chaque lment de Famille. Vous pouvez par exemple crire :
Chat *Famille = new Chat[500]; Chat *pChat = Famille; pChat->SetAge(10); pChat++; pChat->SetAge(20); // // // // pChat pointe sur Famille[0] dfinit Famille[0] 10 pointe sur Famille[1] dfinit Famille[1] 20
Chapitre 13
Tableaux et chanes
415
Ces lignes dclarent un tableau de 500 objets Chat et un pointeur vers le premier lment de ce tableau. En utilisant ce pointeur, on peut appeler la mthode SetAge() en lui passant la valeur 10. Puis, ce pointeur est incrment pour dsigner lobjet Chat suivant du tableau. On appelle ensuite nouveau la mthode SetAge() en lui passant cette fois-ci la valeur 20.
Famille1 est un tableau de 500 objets Chat, alors que Famille2 est un tableau de 500 pointeurs sur des objets de la classe Chat. Enn, Famille3 est un pointeur sur un tableau contenant 500 objets Chat. Les diffrences entre ces trois lignes de code affectent normment le comportement de ces tableaux. Ce qui peut sembler le plus surprenant est que Famille3 est une variante de Famille1, alors quil est trs diffrent de Famille2. Ceci soulve le problme pineux de la relation entre les pointeurs et les tableaux. Dans le troisime exemple, Famille3 est un pointeur sur un tableau. Ladresse contenue dans ce pointeur correspond ladresse du premier lment du tableau et cest exactement la mme chose pour Famille1.
Famille est un pointeur sur &Famille[0], qui est ladresse du premier lment du tableau Famille. Vous avez le droit dutiliser des noms de tableaux comme des pointeurs constants et rciproquement. Famille+4, par exemple, est une expression permettant daccder llment Famille[4]. Le compilateur se charge deffectuer les calculs lorsque vous ajoutez, incrmentez et dcrmentez des pointeurs. Lorsque vous crivez Famille+4, ladresse obtenue ne se
416
Le langage C++
situe pas 4 octets plus loin, mais 4 objets plus loin. Si chaque objet occupe 4 octets, Famille+4 indiquera donc un dplacement de 16 octets par rapport au dbut du tableau. Si chaque objet Chat contient 4 variables membres de type long et 2 variables membres de type short, la taille dun chat sera donc de 20 octets ((44)+(22)). En ce cas, Famille+4 correspondra un dplacement de 80 octets aprs le dbut du tableau. Le Listing 13.8 montre comment dclarer et utiliser un tableau stock sur le tas. Listing 13.8 : Cration dun tableau sur le tas laide de new
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: // Listing13.8 - Cration dun tableau sur le tas #include <iostream> class Chat { public: Chat() { sonAge = 1; sonPoids = 5; } ~Chat(); int GetAge() const { return sonAge; } int GetPoids() const { return sonPoids; } void SetAge(int age) { sonAge = age; } private: int sonAge; int sonPoids; }; Chat:: ~Chat() { // std::cout << "Appel du destructeur!\n"; } int main() { Chat * Famille = new Chat[500]; int i; for (i = 0; i < 500; i++) { Famille[i].SetAge(2*i +1); } for (i = 0; i < 500; i++) { std::cout << "Chat numro " << i+1 << ": ";
Chapitre 13
Tableaux et chanes
417
La ligne 25 dclare Famille, qui est un pointeur vers un tableau de 500 objets Chat. Le tableau complet est cr sur le tas par un appel new Chat[500]. En ligne 30, vous pouvez constater que le pointeur que vous avez dclar peut tre utilis avec loprateur dindexation [] et quil peut donc tre manipul comme un tableau ordinaire. La ligne 36 lutilise nouveau pour appeler la mthode GetAge(). Dun point de vue pratique, vous pouvez considrer ce pointeur vers le tableau Famille comme un nom de tableau, mais il ne faut pas oublier de librer la mmoire que vous aviez alloue lors de la conguration du tableau. Cette opration est ralise la ligne 39 par un appel delete.
418
Le langage C++
Lorsquun lment est cr sur le tas laide du mot-cl new, il doit toujours tre supprim et sa mmoire libre avec mot-cl delete. De mme, si vous crez un tableau laide de new <classe>[taille], vous devez supprimer le tableau et librer sa mmoire laide de delete[]. Les crochets indiquent au compilateur que lobjet supprimer est un tableau. Si vous omettez les crochets, seul le premier objet du tableau sera supprim. Amusez-vous supprimer les crochets de linstruction delete gurant la ligne 39. Si vous avez modi la ligne 20 pour que lappel du destructeur soit afch, vous pourrez constater quun seul objet Chat est supprim. Flicitations ! Vous venez de produire une fuite mmoire.
Vous avez maintenant un pointeur vers un tableau dobjets Chat. Vous pouvez ensuite crer un pointeur vers le premier lment et parcourir ce tableau laide dun pointeur et de larithmtique des pointeurs :
Chat *pChatActuel = Famille[0]; for ( int i = 0; i < TailleFamille; i++, pChatActuel++ ) { pChatActuel->SetAge(i); };
C++ considrant les tableaux comme des pointeurs spciaux, vous pouvez ignorer le deuxime pointeur et utiliser simplement lindexation classique des tableaux :
for (int i = 0; i < TailleFamille; i++) { pFamille[i].SetAge(i); };
Lutilisation des crochets drfrence automatiquement le pointeur et le compilateur ralise le calcul appropri en utilisant larithmtique des pointeurs. Vous pouvez galement utiliser une technique semblable pour redimensionnement le tableau en cours dexcution si vous manquez de place, comme dans le Listing 13.9.
Chapitre 13
Tableaux et chanes
419
420
Le langage C++
Dans cet exemple, les nombres sont saisis les uns aprs les autres et stocks dans un tableau. Lorsquun nombre saisi est infrieur ou gal 0, le tableau qui a t constitu est afch. Les lignes 6 9 dclarent plusieurs variables. Plus prcisment, la taille initiale du tableau est xe 5 la ligne 6, puis le tableau est allou la ligne 7 et son adresse est affecte pTableauDeNombres. Les lignes 12 et 13 rcuprent le premier nombre entr et le placent dans la variable NombreSaisi. la ligne 15, si le nombre entr est suprieur zro, le traitement a lieu. Dans le cas contraire, le programme passe la ligne 38. La ligne 17 place NombreSaisi dans le tableau. Ceci ne posera pas de problme la premire fois car vous savez quil y a de la place. La ligne 19 teste si cest le dernier lment pour lequel le tableau a de la place. Sil reste de la place, le contrle passe la ligne 35 ; sinon, le corps de linstruction if est excut pour augmenter la taille du tableau (lignes 20-34). Un nouveau tableau est cr la ligne 21 pour contenir cinq lments (TailleAllocation) de plus que le tableau courant. Les lignes 24 29 copient ensuite lancien tableau dans le nouveau, laide de la notation des tableaux (vous pourriez utiliser larithmtique des pointeurs).
Chapitre 13
Tableaux et chanes
421
La ligne 31 supprime lancien tableau et la ligne 32 remplace lancien pointeur par celui du nouveau tableau. La ligne 33 augmente ElementsMaximumAutorises pour ladapter la nouvelle taille. Les lignes 39 42 afchent le rsultat.
Faire
Se rappeler quun tableau de n lments est
Ne pas faire
crire ou lire aprs la n dun tableau. Confondre un tableau de pointeurs et un poin-
indic de 0 n-1.
Utiliser lindexation des tableaux avec les
avec new.
cr sur le tas. delete (sans les crochets) ne supprime que le premier lment.
Vous pouvez dclarer et initialiser une chane de type C comme vous le feriez pour nimporte quel tableau. Voici un exemple :
char Salut[] = { b, o, n, j, o, u ,r,\0 };
Ici, Salut est dclar comme un tableau de caractres et initialis avec plusieurs caractres. Le dernier caractre, \0, est le caractre nul, qui est le caractre de terminaison des chanes de type C. Bien que cette approche caractre par caractre fonctionne, elle est difcile saisir et est bien trop sujette aux erreurs de frappe. C permet dutiliser la forme suivante, qui est un quivalent raccourci de la prcdente :
char Salut[] = "Bonjour";
Les caractres entre apostrophes simples, spars par des virgules, ont t remplacs par une chane de caractres entre guillemets, sans accolades. Il nest pas ncessaire dajouter le caractre nul la n car le compilateur sen charge pour vous.
422
Le langage C++
Lorsque vous dclarez une chane, vous devez vrier quelle pourra contenir ce que vous souhaitez y mettre. La longueur dune chane de type C est le nombre de ses caractres, y compris le caractre nul. La chane Bonjour occupe donc 8 octets : le mot occupe 7 octets et le caractre nul 1 octet. Vous pouvez aussi crer des tableaux de caractres non initialiss. Comme pour tout autre tableau, vous devez vrier que vous nen mettez pas plus quil y a dlments rservs. Le Listing 13.10 met en uvre un tampon non initialis. Listing 13.10 : Remplissage dun tableau
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: //Listing13.10 Tampons de caractres #include <iostream> int main() { char tampon[80]; std::cout << "Entrez la chane: "; std::cin >> tampon; std::cout << "Contenu : " << tampon << std::endl; return 0; }
La ligne 6 indique que le tampon peut recevoir 80 caractres, soit 79 caractres et 1 caractre nul. La ligne suivante invite lutilisateur entrer une chane qui est stocke dans le tampon (ligne 8). cin place un caractre nul immdiatement aprs la n du mot transfr dans le tampon. Ce programme contient deux erreurs potentielles. La premire est que si lutilisateur tape plus de 79 caractres, cin crira aprs la n du tampon. La seconde est quun caractre despacement est considr par cin comme une n de chane et quil arrte alors dcrire dans le tampon. Pour rsoudre ces problmes, vous devez appeler la mthode spciale get() sur cin, en lui passant les trois paramtres suivants :
Chapitre 13
Tableaux et chanes
423
Par dfaut, le dlimiteur est le caractre nouvelle ligne, comme le montre le Listing 13.11. Listing 13.11 : Remplir un tableau avec un nombre maximal de caractres
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: //Listing13.11 - Utiliser cin.get() #include <iostream> using namespace std; int main() { char tampon[80]; cout << "Entrez une chane: "; cin.get(tampon, 79); // Lit 79 car max ou nouvelle ligne cout << "Contenu du tampon: " << tampon << endl; return 0; }
Le tampon dclar la ligne 7 est pass comme premier paramre la mthode get() de cin (ligne 9). Le second paramtre correspond au nombre maximum de caractres lire. Ici, il ne doit pas dpasser 79 caractres pour laisser une place au caractre nul. Il est inutile de fournir un caractre de terminaison car, ici, la valeur par dfaut (nouvelle ligne) suft. Si vous entrez des espaces, des tabulations ou dautres sortes despaces, ils seront affects la chane. La saisie se terminera si vous entrez un caractre de nouvelle ligne ou que vous avez saisi 79 caractres. Vous pouvez le vrier en relanant ce programme et en essayant dentrer une chane suprieure 79 caractres.
424
Le langage C++
Ce programme est relativement simple. Il copie les donnes dune chane dans une autre. Le chier en-tte string.h inclus la ligne 3 contient le prototype de la fonction strcpy(). Cette dernire prend en paramtre deux tableaux de caractres, le tableau cible suivi du tableau source de la copie. La ligne 11 appelle cette fonction pour Chaine1 en Chaine2. Le fonction strcpy() prsente un inconvnient majeur puisquelle peut copier les caractres au-del du dernier lment du tableau cible si le tableau source est plus grand. strncpy() permet de remdier ce problme puisquelle permet de limiter le nombre des caractres copis. Elle effectue donc la copie dans le tampon de destination jusqu rencontrer un caractre nul ou atteindre le nombre maximal de caractres dans le tampon source. Le Listing 13.13 montre comment utiliser cette fonction. Listing 13.13 : Utilisation de la fonction strncpy()
0: 1: 2: 3: 4: //Listing13.13 Utiliser strncpy() #include <iostream> #include <string.h>
Chapitre 13
Tableaux et chanes
425
int main() { const int LongMax = 80; char Chaine1[] = "Nul nest prophete en son pays"; char Chaine2[LongMax+1]; strncpy(Chaine2, Chaine1, LongMax); std::cout << "Chaine1: " << Chaine1 << std::endl; std::cout << "Chaine2: " << Chaine2 << std::endl; return 0; }
Comme le prcdent, ce programme copie le contenu dune chane dans une autre. Cependant, la ligne 12 a t modie puisquelle appelle dsormais la fonction strncpy(). Celle-ci utilise un troisime paramtre, qui est le nombre maximal de caractres copier. La taille du tampon Chaine2 est gale LongMax+1 caractres. Le caractre supplmentaire correspond au caractre nul qui est ajout automatiquement la n de la chane. Comme avec le tableau dentiers du Listing 13.9, les tableaux de caractres peuvent tre redimensionns laide des techniques dallocation sur le tas et de la copie, lment par lment. Les classes de chanes de C++, plus souples, utilisent une variante de cette technique pour permettre aux chanes de grandir et de se rduire en fonction des besoins, ou pour insrer ou supprimer des lments au milieu de la chane.
Info
La classe String
Hrites du langage C, les chanes termines par un caractre nul et la bibliothque de fonctions (parmi lesquelles gure strcpy()) sintgrent mal une structure de programme oriente objet. La bibliothque standard de C++ inclut une classe String qui fournit un ensemble encapsul de donnes et de mthodes permettant de masquer les dtails internes aux clients de la classe. Avant dutiliser cette classe, nous allons crer une classe String personnalise qui nous permettra de comprendre les problmes rsoudre. Notre classe String doit au mininum surmonter les limites des tableau de caractres.
426
Le langage C++
Comme tous les tableaux, un tableau de caractres est statique. Le programmeur doit donc dnir sa taille en mmoire, mme sil ne lutilise pas en totalit. Comme nous lavons vu, toute tentative dcriture au-del du dernier lment est dsastreuse. Une classe String bien conue ne doit utiliser que lespace mmoire ncessaire et sufsant. Si elle narrive pas allouer de la mmoire, lapplication doit se terminer proprement. Le Listing 13.14 prsente une premire approche de notre classe String. Cette classe String personnalise a ses limites et ne doit pas tre utilise en ltat dans une application commerciale. La bibliothque standard en propose une qui offre toutes les garanties.
Info
// constructeur priv
Chapitre 13
Tableaux et chanes
427
30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76:
unsigned short saLongueur; }; // le constructeur par dfaut cre une chane de 0 octet String::String() { saChaine = new char[1]; saChaine[0] = \0; saLongueur = 0; } // constructeur (utilitaire) priv, utilis seulement par // les mthodes de la classe pour crer une nouvelle chane // de la taille requise. Complte par des zros. String::String(unsigned short lg) { saChaine = new char[lg+1]; for (unsigned short i = 0; i <= lg; i++) saChaine[i] = \0; saLongueur = lg; } // Convertit un tableau de caractres en une chane String::String(const char * const chaineC) { saLongueur = strlen(chaineC); saChaine = new char[saLongueur+1]; for (unsigned short i = 0; i < saLongueur; i++) saChaine[i] = chaineC[i]; saChaine[saLongueur]=\0; } // constructeur de copie String::String (const String & rhs) { saLongueur = rhs.GetLen(); saChaine = new char[saLongueur+1]; for (unsigned short i = 0; i < saLongueur; i++) saChaine[i] = rhs[i]; saChaine[saLongueur] = \0; } // destructeur, libre toute la mmoire alloue String::~String () { delete [] saChaine; saLongueur = 0;
428
Le langage C++
77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89: 90: 91: 92: 93: 94: 95: 96: 97: 98: 99: 100: 101: 102: 103: 104: 105: 106: 107: 108: 109: 110: 111: 112: 113: 114: 115: 116: 117: 118: 119: 120: 121: 122: 123:
} // oprateur daffectation, libre la mmoire existante // puis copie la chane et la taille String& String::operator=(const String & rhs) { if (this == &rhs) return *this; delete [] saChaine; saLongueur = rhs.GetLen(); saChaine = new char[saLongueur+1]; for (unsigned short i = 0; i < saLongueur; i++) saChaine[i] = rhs[i]; saChaine[saLongueur] = \0; return *this; } // oprateur dindexation non constant, renvoie une // rfrence au caractre pour quil puisse // tre modifi! char & String::operator[](unsigned short indice) { if (indice > saLongueur) return saChaine[saLongueur-1]; else return saChaine[indice]; } // oprateur dindexation constant pour utilisation sur // les objets constants (voir le constructeur de copie!) char String::operator[](unsigned short indice) const { if (indice > saLongueur) return saChaine[saLongueur-1]; else return saChaine[indice]; } // cre une nouvelle chane en ajoutant // la chane actuelle rhs String String::operator+(const String& rhs) { unsigned short lgTotale = saLongueur +rhs.GetLen(); String temp(lgTotale); unsigned short i; for ( i= 0; i < saLongueur; i++) temp[i] = saChaine[i];
Chapitre 13
Tableaux et chanes
429
124: 125: 126: 127: 128: 129: 130: 131: 132: 133: 134: 135: 136: 137: 138: 139: 140: 141: 142: 143: 144: 145: 146: 147: 148: 149: 150: 151: 152: 153: 154: 155: 156: 157: 158: 159: 160: 161: 162: 163: 164: 165: 166: 167: 168: 169: 170:
for (unsigned short j = 0; j < rhs.GetLen(); j++, i++) temp[i] = rhs[j]; temp[lgTotale]=\0; return temp; } // modifie la chane actuelle, ne renvoie rien void String::operator+=(const String& rhs) { unsigned short lgRhs = rhs.GetLen(); unsigned short lgTotale = saLongueur+lgRhs; String temp(lgTotale); unsigned short i; for (i = 0; i < saLongueur; i++) temp[i] = saChaine[i]; for (unsigned short j = 0; j < lgRhs; j++, i++) temp[i] = rhs[i-saLongueur]; temp[lgTotale]=\0; *this = temp; } int main() { String s1("Test initial"); cout << "S1:\t" << s1.GetString() << endl; char * temp = "Bonjour tout le monde "; s1 = temp; cout << "S1:\t" << s1.GetString() << endl; char temp2[20]; strcpy(temp2,"; comment ca va?"); s1 += temp2; cout << "temp2:\t" << temp2 << endl; cout << "S1:\t" << s1.GetString() << endl; cout << "S1[4]:\t" << s1[4] << endl; s1[4]= x; cout << "S1:\t" << s1.GetString() << endl; cout << "S1[999]:\t" << s1[999] << endl; String s2(" Autre chane"); String s3; s3 = s1 + s2; cout << "S3:\t" << s3.GetString() << endl;
430
Le langage C++
String s4; s4 = "Pourquoi ca marche?"; cout << "S4:\t" << s4.GetString() << endl; return 0; }
La dclaration de notre classe String est comprise de la ligne 7 la ligne 31. Pour ajouter de la souplesse la classe, il y a trois constructeurs aux lignes 11 13 : le constructeur par dfaut, le constructeur de copie et un constructeur prenant en paramtre une chane existante termine par un caractre nul (style C). Pour permettre aux utilisateurs de manipuler facilement les chanes, cette classe surcharge plusieurs oprateurs, dont loprateur dindexation ([ ]), loprateur plus (+) et loprateur plus-gal (+=). Loprateur dindexation est surcharg deux fois : la premire comme mthode constante renvoyant un caractre, la deuxime comme mthode variable renvoyant une rfrence sur un caractre. La version non constante est utilise dans des instructions comme celle-ci (ligne 161) :
S1[4]=x;
Elle permet davoir un accs direct aux caractres de la chane. Cette version renvoie une rfrence au caractre an que la fonction appelante puisse le traiter. La version constante, quant elle, permet davoir accs aux objets constants de la classe String, pour limplmentation du constructeur de copie, par exemple ( partir de la ligne 63). Vous pouvez noter que le programme lit la variable rhs[i], bien quelle soit dclare comme une const String &. Comme il est interdit de lire cet objet laide dune fonction membre variable, loprateur dindexation doit tre surcharg avec une fonction daccs constante. Si lobjet renvoy tait volumineux, il serait prfrable de renvoyer une rfrence constante, mais cest inutile ici car un caractre noccupe quun octet en mmoire.
Chapitre 13
Tableaux et chanes
431
Le constructeur par dfaut est implment des lignes 34 39. Il cre une chane de taille nulle ne contenant que le caractre nul nal. Ce dernier nest pas pris en compte par la classe String lorsquelle renvoie la longueur de la chane. Le constructeur de copie dnit la longueur de la nouvelle chane, en ajoutant le caractre nul la longueur existante. Pour cela, il copie un un les caractres de la chane source vers la chane cible, puis ajoute le caractre nul (lignes 63 70). la diffrence des oprateurs daffectation, les constructeurs de copie nont pas besoin de tester si la chane copie dans ce nouvel objet est cet objet lui-mme ; cela ne peut jamais arriver. De la ligne 53 la ligne 60, un constructeur, similaire au constructeur de copie, prend en paramtre une chane de type C. Pour dterminer la taille de la chane existante, il appelle la fonction standard strlen(). La ligne 28 dclare le constructeur String(unsigned short) comme une mthode prive, an dempcher toute tentative de cration dune chane de longueur arbitraire par les clients de la classe. Il a pour rle daider les fonctions spciales crer des chanes comme dans le cas, par exemple, de operator+= la ligne 131. Cette fonctionnalit est dcrite dans la section consacre loprateur +=, plus loin dans ce chapitre. Aux lignes 44 50, le constructeur String(unsigned short) complte tous les lments du tableau par des caractres nuls (\0). Cest pourquoi la boucle for vrie que le nombre dlments lus est bien infrieur ou gal la longueur de la chane (i< = lg). La chane de caractres gre par la classe est supprime par le destructeur (lignes 73 77). Assurez-vous de ne pas oublier les crochets aprs le mot-cl delete car il faut supprimer tous les lments du tableau, pas seulement le premier. Loprateur daffectation (=) est surcharg aux lignes 81 92. Cette mthode dtermine si les deux cts de lexpression sont identiques ou non. Sils sont diffrents, la chane existante est supprime, puis remplace par la nouvelle. Cette mthode renvoie une rfrence, ce qui permet dcrire :
Chaine1 = Chaine2 = Chaine3;
Loprateur dindexation est galement surcharg. Il lest dabord aux lignes 97 103, puis nouveau aux lignes 107 113. Dans les deux cas, il effectue un test trs simple pour vrier que lindice fourni est infrieur la borne suprieure de la chane. Si lutilisateur tente daccder un caractre en dehors de la limite suprieure du tableau, loprateur renvoie le dernier caractre. Loprateur + permet de concatner deux chanes (lignes 117 127) car il est pratique de pouvoir crire :
Chaine3 = Chaine1+Chaine2;
432
Le langage C++
an que Chaine3 contienne le rsultat de la concatnation des deux autres chanes. Pour cela, loprateur + additionne les tailles respectives des deux chanes, puis appelle le constructeur priv en lui passant cette longueur totale an quil cre une chane temporaire temp de cette longueur en linitialisant avec des caractres nuls. Ceux-ci sont ensuite remplacs par les contenus des deux chanes. La chane de gauche (*this) est copie en premier, puis cest au tour de la chane de droite (rhs). La premire boucle for parcourt la chane de gauche et ajoute chacun de ses caractres la nouvelle chane. La seconde boucle for fait de mme pour la chane de droite. Vous pouvez constater que les compteurs i et j sincrmentent simultanment, car i dsigne lindice de la chane cible, tandis que j dsigne lindice de la chane de droite. la ligne 127, la chane temp renvoye par valeur par loprateur plus est affecte la chane situe gauche dans laffectation (chaine1). Aux lignes 131 143, loprateur += agit directement sur la chane existante (cest--dire sur la partie gauche de linstruction chaine1 += chaine2). Il fonctionne comme loprateur plus, sauf que la valeur temporaire temp est affecte la chaine courante (*this = temp) la ligne 142. La fonction main() (lignes 143 173) sert de programme de test pour la classe. La ligne 147 cre un objet String en utilisant le constructeur qui prend en paramtre une chane de type C. La ligne suivante afche son contenu, quelle obtient via un appel sa mthode daccs GetString(). La ligne 150 cre une seconde chane " la C", qui est ensuite affecte la premire la ligne 151. La ligne 153 afche le rsultat de cette affectation et montre que la surcharge de loprateur daffectation a bien fonctionn. La ligne 154 cr une troisime chane C appele temp2 et la ligne suivante appelle la fonction strcpy() pour lui affecter la chane littrale comment ca va?. Loprateur += est ensuite utiliser pour concatner le contenu de temp2 la chane s1 et le rsultat est afch la ligne 158. La ligne 160 lit le cinquime caractre de s1 laide de loprateur dindexation surcharg et lafche. Ce caractre reoit la nouvelle valeur x la ligne 16. Cette ligne fait appel loprateur dindexation non constant. La ligne 162 montre que ce caractre a bien t modi dans la chane. Le programme tente alors de lire un caractre aprs la n du tableau. Daprs ce qui safche, on peut constater que le caractre qui a t renvoy est le dernier, comme prvu. Les lignes 166 et 167 crent deux autres objets String et la ligne suivante appelle loprateur daddition surcharg pour les concatner. Le rsultat est afch en ligne 169. La ligne 171 cre un nouvel objet String, s4, qui est initialis avec une chane de type C la ligne suivante laide de loprateur daffectation surcharg. La ligne 173 afche le
Chapitre 13
Tableaux et chanes
433
rsultat. Vous pourriez vous demander pourquoi cette affectation dune chane C a t autorise alors que loprateur daffectation a t dni pour attendre un paramtre de type String ? La rponse est que le compilateur attend effectivement un objet String, mais quil reoit un tableau de caractres. Par consquent, il vrie sil peut crer un objet String partir des informations reues. la ligne 12, vous avez dclar un constructeur capable de crer des chanes partir de tableaux de caractres : le compilateur cre donc un objet String temporaire partir du tableau de caractres quon lui a transmis, puis le passe loprateur daffectation. Cette opration est appele transtypage implicite ou promotion. Si vous naviez pas dclar (ni fourni une implmentation) de constructeur prenant en paramtre un tableau de caractres, cette affectation aurait produit une erreur de compilation. La classe String du Listing 13.14 devient assez robuste. Vous pouvez galement constater que ce listing est plus long que les prcdents. Heureusement, la bibliothque C++ standard fournit une classe String encore plus robuste, que vous pourrez utiliser en incluant la bibliothque <string>.
434
Le langage C++
Collection ordonne. Les lments sont tris selon un certain critre. Ensemble. Chaque lment napparat quune fois dans le tableau. Dictionnaire. Les valeurs sont regroupes par paires, le premier lment servant de cl pour extraire le second lment. Tableau creux. Les indices permettent dinsrer un trs grand nombre dlments, mais seuls les valeurs rellement stockes consomment de la mmoire. Si le tableau Creux[200] ne contient que cinq valeurs, par exemple, il occupe le mme espace mmoire que Creux[4]. Sac (ou bag). Collection de donnes non ordonne, dont les lments sont ajouts et obtenus dans un ordre indtermin..
En surchargeant loprateur dindexation ([ ]), vous pouvez transformer une liste chane en une collection ordonne. En supprimant les doublons, une collection devient un ensemble. Si chaque objet de la liste est associ deux valeurs (lune tant la cl de la seconde), vous pouvez crer un dictionnaire ou un tableau creux. crire sa propre classe tableau prsente de nombreux avantages par rapport lutilisation de tableaux intgrs, mais utiliser les implmentations fournies par la bibliothque standard pour ces classes (ou des versions analogues) est encore mieux.
Info
Questions-rponses
Q Que contient un lment de tableau non initialis ? R Llment contient ce qui est cet emplacement mmoire ce moment-l. Les oprations ralises sur un tel lment peuvent produire des rsultats imprvisibles. Si le compilateur respecte le standard C++, les lments dun tableau qui sont statiques et qui ne sont pas des objets non locaux seront initialiss zro.
Chapitre 13
Tableaux et chanes
435
Q Est-il possible de combiner des tableaux ? R Bien sr. Avec les tableaux simples, vous pouvez combiner les pointeurs pour former un nouveau tableau plus gros. Avec les chanes de caractres, vous pouvez utiliser certaines fonctions prdnies comme strcat. Q Pourquoi crer une liste chane si un tableau fait laffaire ? R La taille dun tableau est dnie une fois pour toutes, alors quune liste chane peut tre rduite ou agrandie de faon dynamique pendant lexcution du programme. LAnnexe E traite de la cration de listes chanes. Q Pourquoi utiliser les tableaux prdnis si lon peut crer des classes tableaux plus performantes ? R Les tableaux prdnis sont rapides et simples demploi et vous en avez gnralement besoin pour construire votre propre classe tableau. Q Existe-t-il une meilleure construction que les tableaux ? R Au Chapitre 19, vous verrez les modles et la STL (Standard Template Library), qui contient des modles de collections disposant de toutes les fonctionnalits dont vous avez besoin. Lutilisation de ces modles est prfrable la cration de classes personnelles. Q Est-ce quune classe string doit utiliser un char * pour stocker le contenu de la chane ? R Non. Elle peut utiliser le moyen de stockage que son concepteur juge le plus adapt.
436
Le langage C++
Exercices
1. Dclarez un tableau deux dimensions reprsentant un jeu de morpion. 2. Initialisez zro tous les lments du tableau de lexercice prcdent. 3. crivez un programme contenant quatre tableaux de caractres. Les trois premiers doivent contenir respectivement votre prnom, vos initiales et votre nom. Utilisez la fonction de copie de chane que nous avons prsente dans ce chapitre pour regrouper ces chanes dans le quatrime tableau, qui contiendra ainsi votre nom complet. 4. CHERCHEZ LERREUR dans ce fragment de code :
unsigned short Tableau[5][4]; for (int i = 0; i<4; i++) for (int j = 0; j<5; j++) Tableau[i][j] = i+j;
14
Polymorphisme
Au sommaire de ce chapitre
Caractristiques et utilisation de lhritage multiple Nature et rle de lhritage virtuel Caractristiques des classes abstraites Caractristiques des fonctions virtuelles pures
Au Chapitre 12, vous avez appris crire des fonctions virtuelles dans des classes drives. Ces fonctions sont les briques de base du polymorphisme, qui est la capacit de lier des objets dune classe drive des pointeurs vers la classe de base en cours dexcution.
438
Le langage C++
types, parmi lesquels gure le type Cheval. La classe Cheval inclut les fonctions membres Hennir() et Galoper(). Soudain, vous ralisez que vous avez besoin dun objet Pegase, un animal mi-chemin entre le cheval et loiseau. Pgase est capable de voler, de hennir et de galoper. Vous ne pouvez pas rsoudre ce problme avec lhritage simple. Avec lhritage simple, vous ne pouvez driver que dune seule classe. Vous pouvez crer Pegase partir de la classe Oiseau, mais alors il ne saura ni Hennir() ni Galoper(). Si vous en faites un Cheval, il ne saura pas Voler(). Une premire solution consiste copier la mthode Voler() dans la classe Pegase et driver Pegase de Cheval. Cela fonctionnera correctement, mais au prix de la duplication de la mthode Voler() dans les deux classes Oiseau et Pegase. Si vous modiez une classe, il ne faudra pas oublier de mettre lautre jour. Un dveloppeur se penchant sur votre code plusieurs mois ou annes plus tard devra galement savoir quil doit appliquer ses corrections deux endroits. Toutefois, vous rencontrerez rapidement un problme. Si vous crez une liste dobjets Cheval et une liste dobjets Oiseau, vous devez pouvoir ajouter des objets Pegase lune ou lautre. Mais, si Pgase est un cheval, vous ne pourrez pas lajouter la liste des Oiseau. Vous avez alors deux solutions possibles. Vous pouvez renommer la mthode Galoper() en Deplacer(), puis rednir cette dernire dans lobjet Pegase an quelle fasse comme Voler(). Il faudra aussi rednir la fonction Deplacer() dans les autres objets Cheval pour quelle fasse comme Galoper(). Un objet Pegase pourrait, par exemple, galoper sur de courtes distances et voler sur des distances plus longues.
Pegase::Deplacer(long distance) { if (distance > loin) Voler(distance); else Galoper(distance); }
Ce code est un peu limit, car Pegase souhaitera peut-tre un jour voler sur une courte distance et galoper sur une distance plus longue. La solution suivante consisterait alors dplacer la mthode Voler() dans la classe Cheval, comme le montre le Listing 14.1. Le problme est que la plupart des chevaux ne sachant pas voler, cette mthode ne doit rien faire, sauf si le cheval est un Pegase.
Chapitre 14
Polymorphisme
439
440
Le langage C++
Les chevaux ne savent pas voler. Je vole! Je vole! Je vole! Les chevaux ne savent pas voler. Je vole! Je vole! Je vole! Les chevaux ne savent pas voler.
Ce programme sexcute normalement, condition que la classe Cheval comprenne une mthode Voler(). La ligne 10 ajoute la mthode Voler() Cheval. Dans une vraie classe, vous pourriez faire en sorte quelle produise une erreur ou choue en silence. la ligne 18, la classe Pegase rednit la mthode Voler() pour quelle produise un message enthousiaste. Le tableau des pointeurs Cheval, appel Ranch, permet de dmontrer la ligne 25 que la mthode Voler() approprie est appele grce la liaison dynamique dun objet Cheval ou dun objet Pegase. Aux lignes 28 37, lutilisateur doit choisir un Cheval ou un Pegase. Un objet du type correspondant est cr et plac dans le tableau Ranch. Aux lignes 38 43, le programme parcourt nouveau le tableau Ranch, cette fois-ci pour appeler la mthode Voler() sur chaque lment du tableau. En lisant les lignes produites par le programme, vous pouvez constater que la mthode Voler() appele dpend du fait que lobjet soit un Cheval ou un Pegase. Ce programme nutilisant plus les objets de Ranch, la ligne 42 appelle delete pour librer la mmoire quils occupaient. Ces exemples ont t rduits lessentiel an de mettre laccent sur le point qui nous intresse. Les constructeurs, les destructeurs virtuels entre autres, ne gurent pas ici. Cette pratique nest pas conseille pour vos programmes.
Info
Filtrage ascendant
Placer la fonction requise un niveau suprieur dans la hirarchie des classes constitue la solution la plus courante ce problme et produit de nombreuses fonctions qui "remontent" dans la classe de base. Cette dernire court alors le risque dtre transforme en un espace de noms global pour toutes les fonctions qui pourraient servir lune des classes
Chapitre 14
Polymorphisme
441
drives. La taille de la classe de base devient alors dmesure et lapproche oriente objet du programme est srieusement compromise, ce qui est dommage lorsquon utilise un langage orient objet comme C++. En gnral, on souhaite faire remonter vers le haut de la hirarchie une fonctionnalit partage, sans dplacer linterface de chaque classe. Si deux classes ont en commun une classe de base (comme cest le cas des classes drives Cheval et Oiseau avec la classe Animal) et une mthode en commun (les oiseaux et les chevaux mangent, par exemple), il est souhaitable de dplacer cette fonctionnalit vers la classe de base et den faire une mthode virtuelle. Ce quil faut viter, par contre, cest de remonter une mthode comme Voler() un endroit o elle na rien faire uniquement pour pouvoir lappeler dans certaines classes drives.
Transtypage descendant
Une autre possibilit, toujours dans le cadre de lhritage simple, consisterait conserver la mthode Voler() dans lobjet Pegase et de lappeler uniquement lorsque le pointeur pointe effectivement sur un objet Pegase. Vous devrez pour cela tre capable dinterroger le pointeur pour identier le type point. Cette identication du type au moment de lexcution est nomme RTTI (Run Time Type Identication). RTTI tant une nouvelle spcication C++, les compilateurs ne le prennent pas tous en charge. Si votre compilateur ne supporte pas RTTI, vous pouvez imiter son fonctionnement en plaant une mthode qui renvoie un type numr dans chaque classe. Vous aurez ainsi la possibilit de tester le type au moment de lexcution et dappeler Voler() sil a renvoy Pegase. Nabusez pas de RTTI dans vos classes. Son emploi pourrait tre le signe dune mauvaise conception. Envisagez plutt lutilisation des mthodes virtuelles, des modles ou de lhritage multiple.
Info
Dans lexemple prcdent, les objets Cheval et Pegase ont t dclars et placs dans un tableau dobjets Cheval. Avec RTTI, il faudrait vrier chacun de ces lments du tableau pour savoir sil sagit dun cheval ou dun Pegase. Pour appeler Voler(), cependant, il faut transtyper le type du pointeur pour linformer que lobjet quil pointe est un Pegase et non un Cheval. Cest ce que lon appelle un transtypage descendant car on transtype dun type plus gnral vers un type plus spcialis.
442
Le langage C++
Ofciellement, mais parfois contrecur, C++ gre ces transtypages laide du nouvel oprateur dynamic_cast. Si vous affectez un pointeur dune classe de base, comme Cheval, un pointeur dune classe drive comme Pegase, vous pouvez utiliser le pointeur sur Cheval de faon polymorphe. Si vous avez besoin daccder lobjet Pegase, il suft de crer un pointeur sur Pegase et dutiliser loprateur dynamic_cast pour effectuer cette conversion. Lors de lexcution, le pointeur de base sera examin et, si la conversion est correcte, votre nouveau pointeur sur Pegase sera oprationnel. Si cette conversion est incorrecte, par contre, vous ne disposeriez pas dun objet Pegase et ce nouveau pointeur vaudrait alors null. Le Listing 14.2 illustre ce processus. Listing 14.2 : Transtypage descendant
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: // Listing14.2 Utilisation de transtypage descendant // dynamique : fonctionnalit RTTI #include <iostream> using namespace std; enum TYPE { CHEVAL, PEGASE }; class Cheval { public: virtual void Galoper(){ cout << "Au galop...\n"; } private: int sonAge; }; class Pegase: public Cheval { public: virtual void Voler() {cout<<"Je vole! Je vole! Je vole!\n";} }; const int NbChevaux = 5; int main() { Cheval* Ranch[NbChevaux]; Cheval* pCheval; int choix, i;
Chapitre 14
Polymorphisme
443
30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52:
for (i = 0; i < NbChevaux; i++) { cout << "(1)Cheval (2)Pegase - Votre choix: "; cin >> choix; if (choix == 2) pCheval = new Pegase; else pCheval = new Cheval; Ranch[i] = pCheval; } cout << endl; for (i = 0; i < NbChevaux; i++) { Pegase *pPeg = dynamic_cast< Pegase *> (Ranch[i]); if (pPeg!= NULL) pPeg->Voler(); else cout << "Ce nest quun cheval\n"; delete Ranch[i]; } return 0; }
nest quun cheval vole ! Je voler ! Je vole ! nest quun cheval vole ! Je voler ! Je vole ! nest quun cheval
Avertissement du compilateur Le compilateur Microsoft Visual C++ peut produire lavertissement suivant :
warning C4541: dynamic_cast used on polymorphic type class Cheval with /GR-; unpredictable behavior may result.
Lorsque jexcute le programme, un message indique une n dexcution inhabituelle et me demande de contacter lquipe dassistance.
444
Le langage C++
Ces messages sont assez confus. Vous pouvez rsoudre le problme de la faon suivante : 1. Dans votre projet, choisissez Projet/Conguration. 2. Choisissez longlet C/C++. 3. Slectionnez C++ dans la liste droulante Catgorie. 4. Cochez la case Activer Runtime Type Information (RTTI). 5. Recrez la totalit du projet. Si vous utilisez le compilateur Visual C++en ligne de commande, ajoutez loption /GR :
Cl /GR List1402.cpp
Cette solution fonctionne aussi, mais elle nest pas conseille. La fonction Voler() reste en dehors de la classe Cheval et nest pas appele avec les objets de cette classe. Lorsquelle est invoqu sur des objets Pegase (ligne 45), ceux-ci doivent tre convertis explicitement (ligne 43). Les objets Cheval ne disposant pas de la mthode Voler(), il faut indiquer au pointeur que lobjet point est un Pegase avant de lutiliser. La ncessit de convertir lobjet Pegase est probablement le signe dune mauvaise conception. En fait, ce programme compromet le polymorphisme des fonctions virtuelles puisquil dpend du transtypage de lobjet vers son type effectif au moment de lexcution.
Chapitre 14
Polymorphisme
445
Faire
Dplacer la fonctionnalit vers le haut dans
Ne pas faire
Encombrer les classes anctres avec des fonc-
une hirarchie dhritage lorsque que cela est cohrent avec la signication de la classe anctre.
viter les oprations qui dpendent du type
tionnalits qui ne sont ajoutes que pour soutenir le besoin de polymorphisme dans les classes descendantes.
Convertir des pointeurs sur des objets de la
de lobjet au moment de lexcution faire plutt appel aux mthodes virtuelles, aux modles et lhritage multiple.
Hritage multiple
Lhritage multiple consiste driver une nouvelle classe partir de plusieurs classes de base. Pour driver plusieurs classes de base, vous devez placer une virgule entre chacune delle dans la liste dhritage :
class ClasseDerivee: public ClasseBase1, public ClasseBase2 {}
Cette ligne ressemble exactement une dclaration dhritage simple, mais avec une classe de base supplmentaire, ClasseBase2. Dans le Listing 14.3, la classe Pegase drive maintenant la fois de la classe Cheval et de la classe Oiseau. Le programme ajoute ensuite des objets Pegase des listes de ces deux types. Listing 14.3 : Hritage multiple
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: // Listing14.3 - Hritage multiple #include <iostream> using std::cout; using std::cin; using std::endl; class Cheval { public: Cheval() { cout << "Constructeur de Cheval... "; } virtual ~Cheval() { cout << "Destructeur de Cheval... "; } virtual void Hennir() const { cout << "Hihiii!... "; } private: int sonAge; };
446
Le langage C++
16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 25a: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61:
class Oiseau { public: Oiseau() { cout << "Constructeur de Oiseau... "; } virtual ~Oiseau() { cout << "Destructeur de Oiseau... "; } virtual void Gazouiller() const { cout << "Cuicui... "; } virtual void Voler() const { cout << "Je vole! Je vole! Je vole! "; } private: int sonPoids; }; class Pegase: public Cheval, public Oiseau { public: void Gazouiller() const { Hennir(); } Pegase() { cout << "Constructeur de Pegase... "; } ~Pegase() { cout << "Destructeur de Pegase... "; } }; const int Max = 2; int main() { Cheval* Ranch[Max]; Oiseau* Voliere[Max]; Cheval * pCheval; Oiseau * pOiseau; int choix, i; for (i = 0; i < Max; i++) { cout << "\n(1)Cheval (2)Pegase - Votre choix: "; cin >> choix; if (choix == 2) pCheval = new Pegase; else pCheval = new Cheval; Ranch[i] = pCheval; } for (i = 0; i < Max; i++) { cout << "\n(1)Oiseau (2)Pegase - Votre choix: "; cin >> choix; if (choix == 2)
Chapitre 14
Polymorphisme
447
62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84:
pOiseau = new Pegase; else pOiseau = new Oiseau; Voliere[i] = pOiseau; } cout << endl; for (i = 0; i < Max; i++) { cout << "\nRanch[" << i << "]: "; Ranch[i]->Hennir(); delete Ranch[i]; } for (i = 0; i < Max; i++) { cout << "\nVoliere[" << i << "]: "; Voliere[i]->Gazouiller(); Voliere[i]->Voler(); delete Voliere[i]; } return 0; }
La classe Cheval est dclare aux lignes 7 15. Le constructeur et le destructeur afchent un message et la mthode Hennir() donne la parole notre pgase. La classe Oiseau est dclare aux lignes 17 29. Outre un constructeur et un destructeur, elle inclut deux mthodes : Gazouiller() et Voler(), qui produisent toutes les deux un
448
Le langage C++
message caractristique. Dans un vrai programme, elles pourraient produire un son ou lancer une animation multimdia. Enn, la classe Pegase est dclare aux lignes 31 37. Elle utilise la syntaxe que nous avons prsente pour lhritage multiple. Elle drive la fois de la classe Cheval et de la classe Oiseau (ligne 31). Cette classe rednit la mthode Gazouiller() (ligne 34) pour appeler simplement la mthode Hennir(), hrite de la classe Cheval. La fonction principale de ce programme cre deux listes : la liste Ranch, qui contient des pointeurs sur des objets Cheval (ligne 42) et la liste Voliere qui contient des pointeurs sur des objets Oiseau (ligne 43). Les lignes 47 56 ajoutent des objets Cheval et Pegase la liste Ranch alors que les lignes 57 66 ajoutent des objets Oiseau et Pegase Voliere. Les appels des mthodes virtuelles sur des pointeurs de Oiseau et de Cheval permettent de raliser les oprations appropries sur les objets Pegase. la ligne 79, par exemple, les lments du tableau Voliere appellent la mthode Gazouiller() sur les objets sur lesquels ils pointent, car la classe Oiseau a dclar cette mthode comme virtuelle. chaque fois quun objet Pegase est cr, lopration se rpercute la fois dans sa partie Oiseau et sa partie Cheval (comme le montre le rsultat). De mme, lorsquun objet Pegase est dtruit, ses parties Oiseau et Cheval sont galement supprimes grce aux destructeurs virtuels.
Dclaration dhritage multiple Pour quune classe hrite de une ou plusieurs classes, il faut fournir la liste de ses classes de bases aprs le caractre deux-points qui suit le nom de la nouvelle classe. Les diffrentes classes de base doivent tre spares par des virgules. Exemple 1
Chapitre 14
Polymorphisme
449
Cheval
Oiseau
Pegase
Les objets associs plusieurs classes de base posent un certain nombre de problmes. Par exemple, que se passe-t-il si deux classes de base ont des donnes ou des mthodes virtuelles portant le mme nom ? Comment sont initialiss les diffrents constructeurs des classes de base ? Que se passe-t-il si plusieurs classes de base hritent de la mme classe ? Les sections qui suivent rpondent ses questions et expliquent comment mettre en uvre lhritage multiple.
450
Le langage C++
18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 32a: 33: 34: 35: 36: 36a: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62:
TAILLE saTaille; COULEUR saCouleur; }; Cheval::Cheval(COULEUR couleur, TAILLE taille): saCouleur(couleur),saTaille(taille) { cout << "Constructeur de Cheval...\n"; } class Oiseau { public: Oiseau(COULEUR couleur, bool migrates); virtual ~Oiseau() {cout << "Destructeur de Oiseau...\n"; } virtual void Gazouiller()const { cout << "Cuicui... "; } virtual void Voler()const { cout << "Je vole! Je vole! Je vole! "; } virtual COULEUR GetCouleur()const { return saCouleur; } virtual bool GetMigration() const { return saMigration; } private: COULEUR saCouleur; bool saMigration; }; Oiseau::Oiseau(COULEUR couleur, bool migrateur): saCouleur(couleur), saMigration(migrates) { cout << "Constructeur de Oiseau...\n"; } class Pegase: public Cheval, public Oiseau { public: void Gazouiller()const { Hennir(); } Pegase(COULEUR, TAILLE, bool,long); ~Pegase() {cout << "Destructeur de Pegase...\n";} virtual long GetNbCroyants() const { return nbCroyants; }
Chapitre 14
Polymorphisme
451
63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89: 90: 91: 92: 93: 94:
private: long nbCroyants; }; Pegase::Pegase( COULEUR uneCouleur, TAILLE unetaille, bool migrateur, long nb): Cheval(uneCouleur, uneTaille), Oiseau(uneCouleur, migrateur), nbCroyants(nb) { cout << "Constructeur de Pegase...\n"; } int main() { Pegase *pPeg = new Pegase(Rouge, 5, true, 10); pPeg->Voler(); pPeg->Hennir(); cout << "\nPegase mesure " << pPeg->GetTaille(); cout << " cm et "; if (pPeg->GetMigration()) cout << "il peut migrer."; else cout << "il ne peut pas migrer."; cout << "\nAu total " << pPeg->GetNbCroyants(); cout << " personnes croient quil existe." << endl; delete pPeg; return 0; }
452
Le langage C++
La classe Cheval est dclare aux lignes 9 20. Le constructeur prend deux paramtres : une numration de couleurs dclare la ligne 7 et une valeur du type typedef dclar la ligne 6. Le constructeur initialise simplement les variables membres et afche un message (ligne 21 25). La classe Oiseau est dclare de la ligne 28 la ligne 44 et limplmentation de son constructeur se trouve aux lignes 46 50. La classe de base Oiseau attend deux paramtres, comme celui de la classe Cheval. Il faut noter que constructeur de Cheval a un paramtre couleur (qui indique la couleur de sa robe), comme le constructeur de Oiseau (pour indiquer la couleur de ses plumes). Comme nous le verrons plus loin, ceci peut poser problme lorsque vous souhaiterez demander la couleur de Pegase. La classe Pegase est dclare aux lignes 52 65, son constructeur aux lignes 67 77. Linitialisation de lobjet Pegase comprend trois instructions. Le constructeur de Cheval est dabord initialis avec une couleur et une taille (ligne 72). Le constructeur de Oiseau lest avec une couleur et une valeur boolenne indiquant sil sagit dun oiseau migrateur (ligne 73). Enn, la variable membre de Pegase, nbCroyants est initialise. Le corps du constructeur de Pegase peut alors sexcuter. Dans la fonction main(), un pointeur sur Pegase (ligne 81) permet daccder aux fonctions membres hrites des classes de base. Laccs ces mthodes est relativement vident.
Pour rsoudre ce problme, vous devez rendre plus explicite lappel de la mthode :
COULEUR saCouleur = pPeg->Cheval::GetCouleur();
Chapitre 14
Polymorphisme
453
chaque fois que vous devez connatre la classe dune mthode ou une donne membre dont vous hritez, vous pouvez qualier cet appel en prxant le nom du membre par celui de la classe dont il provient. Si cette mthode avait t rednie par la classe Pegase, le problme se serait dplac vers la mthode membre de Pegase :
virtual COULEUR GetCouleur()const { return Cheval::saCouleur; }
Ceci cache le problme aux clients de la classe Pegase en encapsulant la connaissance de la classe de base dont on hrite la couleur. Cela dit, un client peut toujours crire :
COULEUR saCouleur = pPeg->Oiseau::GetCouleur();
Animal
Animal
Cheval
Oiseau
Pegase
Vous pouvez constater quil y a maintenant deux objets de la classe de base. Lors de lappel dune mthode ou dune donne membre de cette classe partage, une autre ambigut survient alors. Si la classe Animal contient une variable membre sonAge et la mthode membre GetAge(), et que vous appelez :
pPeg->GetAge()
454
Le langage C++
voulez-vous dire que vous appelez la mthode GetAge() hrite de Animal par lintermdiaire de Cheval ou par lintermdiaire de Oiseau ? Vous devez galement rsoudre cette ambigut, comme on le montre dans le Listing 14.5. Listing 14.5 : Classes de base communes
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 30a: 31: 32: 33: 34: 35: 36: 37: 38: // Listing14.5 // classes de base communes #include <iostream> using namespace std; typedef int TAILLE; enum COULEUR { Rouge, Vert, Bleu, Jaune, Blanc, Noir, Brun }; class Animal // classe de base commune Cheval et Oiseau { public: Animal(int); virtual ~Animal() { cout << "Destructeur de Animal...\n"; } virtual int GetAge() const { return sonAge; } virtual void SetAge(int age) { sonAge = age; } private: int sonAge; }; Animal::Animal(int age): sonAge(age) { cout << "Constructeur de Animal...\n"; } class Cheval: public Animal { public: Cheval(COULEUR couleur, TAILLE taille, int age); virtual ~Cheval() { cout << "Destructeur de Cheval...\n"; } virtual void Hennir()const { cout << "Hihiii!... "; } virtual TAILLE GetTaille() const { return saTaille; } virtual COULEUR GETCouleur() const { return saCouleur; } protected: TAILLE saTaille; COULEUR saCouleur; };
Chapitre 14
Polymorphisme
455
39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 76a: 77: 78: 79: 80: 81: 82: 83: 84:
Cheval::Cheval(COULEUR couleur, TAILLE taille, int age): Animal(age), saCouleur(couleur),saTaille(taille) { cout << "Constructeur de Cheval...\n"; } class Oiseau: public Animal { public: Oiseau(COULEUR couleur, bool migrateur, int age); virtual ~Oiseau() {cout << "Destructeur de Oiseau...\n"; } virtual void Gazouiller()const { cout << "CuiCui... "; } virtual void Voler()const { cout << "Je vole! Je vole! Je vole! "; } virtual COULEUR GetCouleur()const { return saCouleur; } virtual bool GetMigration() const { return saMigration; } protected: COULEUR saCouleur; bool saMigration; }; Oiseau::Oiseau(COULEUR couleur, bool migrateur, int age): Animal(age), saCouleur(couleur), saMigration(migrateur) { cout << "Constructeur de Oiseau...\n"; } class Pegase: public Cheval, public Oiseau { public: void Gazouiller()const { Hennir(); } Pegase(COULEUR, TAILLE, bool, long, int); virtual ~Pegase() {cout << "Destructeur de Pegase...\n";} virtual long GetNbCroyants() const { return nbCroyants; } virtual COULEUR GetCouleur()const { return Cheval::wCouleur; } virtual int GetAge() const { return Cheval::GetAge(); } private: long nbCroyants; }; Pegase::Pegase( COULEUR uneCouleur, HANDS uneTaille,
456
Le langage C++
85: 86: 87: 88: 89: 90: 91: 92: 93: 94: 95: 96: 97: 98: 99: 100: 101: 102:
bool migrateur, long nb, int age): Cheval(uneCouleur, uneTaille, age), Oiseau(uneCouleur, migrateur, age), nbCroyants(nb) { cout << "Constructeur de Pegase...\n"; } int main() { Pegase *pPeg = new Pegase(Rouge, 5, true, 10, 2); int age = pPeg->GetAge(); cout << "Pegase a " << age << " ans.\n"; delete pPeg; return 0; }
Les fonctionnalits de ce programme sont intressantes bien des gards. Dclare de la ligne 9 la ligne 18, la classe Animal contient une variable membre sonAge et deux mthodes daccs, GetAge() et SetAge(). la ligne 26, la classe Cheval est dclare comme classe drive de Animal. Son constructeur passe le troisime paramtre, age, la classe de base (ligne 40). La classe Cheval ne rednit pas la fonction GetAge(), mais se contente den hriter. la ligne 46, la classe Oiseau est dclare comme classe drive de Animal. Son constructeur initialise la classe de base (ligne 68) avec le paramtre age. Comme pour la classe drive prcdente, la classe Oiseau hrite de la fonction GetAge() sans la rednir. Pegase hrite la fois de Oiseau et de Animal, ce qui cre une chane dhritage avec deux fois la classe Animal. Si vous appelez la fonction GetAge() sur un objet Pegase
Chapitre 14
Polymorphisme
457
alors quelle na pas t rednie dans cette classe, il faudrait indiquer clairement le nom de la classe dont elle dpend an de supprimer toute ambigut. Ce problme est rsolu la ligne 77, lorsque lobjet Pegase rednit la mthode GetAge() en effectuant simplement un chanage, cest--dire un appel de la mme mthode dans la classe de base. Un chanage ascendant permet datteindre deux objectifs : supprimer toute ambigut entre les deux classes de base ou effectuer un traitement puis demander la mthode de la classe de base den faire de mme. Parfois, ce traitement peut seffectuer avant le chanage, parfois le chanage peut avoir lieu avant le traitement local : cela dpend de vos besoins. Le constructeur de Pegase, qui commence la ligne 82, attend cinq paramtres : la couleur de lanimal, sa taille, sil est migrateur, le nombre de personnes qui croient en son existence et son ge. la ligne 88, il initialise la partie hrite de la classe Cheval avec la couleur, la taille et lge. Il ajoute ensuite la couleur, la migration et lge appartenant la partie Oiseau (ligne 89). Enn, il initialise sa propre variable nbCroyants la ligne 90. Lappel du constructeur de Cheval la ligne 88 invoque limplmentation situe la ligne 39. Il affecte lobjet Pegase la valeur dge commune Animal et Cheval, puis initialise les variables membres saCouleur et saTaille de Cheval. Lappel du constructeur de Oiseau la ligne 89 invoque limplmentation situe la ligne 46. Ici aussi, le paramtre age sert initialiser la variable membre de la partie Animal de Oiseau. Vous remarquerez que le paramtre de couleur associ Pegase permet dinitialiser les variables membres dans Oiseau et Cheval. Vous constaterez galement que le paramtre age sert initialiser la variable sonAge dans la classe de base Animal de Cheval et dans la classe de base Animal de Oiseau.
ntion Atte
Ds que vous levez explicitement lambigut dune classe anctre, il existe un risque quune nouvelle classe insre entre votre classe et son anctre amne votre classe appeler "aprs" le nouvel anctre dans lancien anctre. Cela peut avoir des effets inattendus.
Hritage virtuel
Dans le listing prcdent, la classe Pegase faisait beaucoup defforts pour lever toutes les ambiguts concernant les versions de la classe de base Animal quelle utilisait. La plupart du temps, cette dcision est arbitraire aprs tout, les classes Cheval et Oiseau ont la mme classe de base.
458
Le langage C++
Comme le montre la Figure 14.3, C++ vous permet de prciser que vous ne voulez pas deux copies de la classe de base partage, comme dans la Figure 14.2, mais une seule.
Figure 14.3 Hritage en losange.
Animal
Cheval
Oiseau
Pegase
Pour ce faire, il faut que Animal soit une classe de base virtuelle de Cheval et Oiseau. La classe Animal ne sera en rien modie ; Cheval et Oiseau utiliseront le mot-cl virtual dans leur dclarations dhritage. La classe Pegase, par contre, subit des modications non ngligeables. En gnral, un constructeur dobjet initialise uniquement ses propres variables membres et sa classe de base. Les classes de base hrites virtuellement sont une exception puisquelles sont initialises par leur classe drive la plus basse dans larbre dhritage. Animal sera donc initialise par Pegase et non par Cheval et Oiseau. Les constructeurs de ces deux classes doivent initialiser Animal dans leurs constructeurs, mais ses initialisations seront ignores lors de la cration dun objet Pegase. Le Listing 14.6 reprend le code du Listing 14.5, en lui ajoutant des oprations de drivation virtuelle. Listing 14.6 : Hritage virtuel
0: 1: 2: 3: 4: 5: 6: 7: // Listing14.6 // Hritage virtuel #include <iostream> using namespace std; typedef int TAILLE; enum COULEUR { Rouge, Vert, Bleu, Jaune, Blanc, Noir, Brun };
Chapitre 14
Polymorphisme
459
8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55:
class Animal // base commune Cheval et Oiseau { public: Animal(int); virtual ~Animal() { cout << "Destructeur de Animal...\n"; } virtual int GetAge() const { return sonAge; } virtual void SetAge(int age) { sonAge = age; } private: int sonAge; }; Animal::Animal(int age): sonAge(age) { cout << "Constructeur de Animal...\n"; } class Cheval: virtual public Animal { public: Cheval(COULEUR couleur, TAILLE taille, int age); virtual ~Cheval() { cout << "Destructeur de Cheval...\n"; } virtual void Hennir()const { cout << "Hiihiii!... "; } virtual TAILLE GetTaille() const { return saTaille; } virtual COULEUR GetCouleur() const { return saCouleur; } protected: TAILLE saTaille; COULEUR saCouleur; }; Cheval::Cheval(COULEUR couleur, TAILLE taille, int age): Animal(age), saCouleur(couleur),saTaille(taille) { cout << "Constructeur de Cheval...\n"; } class Oiseau: virtual public Animal { public: Oiseau(COULEUR couleur, bool migrateur, int age); virtual ~Oiseau() {cout << "Destructeur de Oiseau...\n"; } virtual void Gazouiller()const { cout << "Cuicui... "; } virtual void Voler()const { cout << "Je vole! Je vole! Je vole! "; } virtual COULEUR GetCouleur() const { return saCouleur; } virtual bool GetMigration() const { return saMigration; } protected:
460
Le langage C++
56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 75a: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89: 90: 91: 92: 93: 94: 95: 96: 97: 98: 99: 100: 101:
COULEUR saCouleur; bool saMigration; }; Oiseau::Oiseau(COULEUR couleur, bool migrateur, int age): Animal(age), saCouleur(couleur), saMigration(migrateur) { cout << "Constructeur de Oiseau...\n"; } class Pegase: public Cheval, public Oiseau { public: void Gazouiller() const { Hennir(); } Pegase(COULEUR, TAILLE, bool, long, int); virtual ~Pegase() {cout << "Destructeur de Pegase...\n";} virtual long GetNbCroyants() const { return nbCroyants; } virtual COULEUR GetCouleur() const { return Cheval::saCouleur; } private: long nbCroyants; }; Pegase::Pegase( COULEUR uneCouleur, TAILLE uneTaille, bool migrateur, long nb, int age): Cheval(saCouleur, saTaille, age), Oiseau(saCouleur, migrateur, age), Animal(age*2), nbCroyants(nb) { cout << "Constructeur de Pegase...\n"; } int main() { Pegase *pPeg = new Pegase(Rouge, 5, true, 10, 2); int age = pPeg->GetAge(); cout << "Pegase a " << age << " ans.\n"; delete pPeg; return 0; }
Chapitre 14
Polymorphisme
461
Aux lignes 25 et 45, Cheval et Oiseau hritent virtuellement de Animal. Vous remarquerez que les constructeurs de Oiseau et Cheval initialisent quand mme lobjet Animal. Pegase hrite la fois de Oiseau et Cheval et, en tant que classe la plus drive de Animal, initialise galement Animal. Cest linitialisation de Pegase qui est appele et les appels au constructeur de Animal dans Oiseau et Cheval sont ignors. Vous pouvez le vrier car lge de lobjet Pegase est pass trois fois au constructeur de Animal : par le constructeur de Pegase et par les constructeurs de Cheval et Oiseau. La diffrence est que Pegase double cette valeur avant de la transmettre et cest bien le double de lge (4) qui safche la ligne 98. Pegase na plus besoin de supprimer lambigut de lappel la fonction GetAge(), ce qui lui permet dhriter simplement cette mthode de la classe Animal. En revanche, lappel la fonction GetCouleur() doit tre explicite, car celle-ci se trouve dans les deux classes de base, mais pas dans Animal.
Dclaration de classes virtuelles Pour que les classes drives ne contiennent quune seule instance des classes de base communes, dclarez les classes intermdiaires de sorte quelles hritent virtuellement de la classe de base. Exemple 1
class Cheval: virtual public Animal class Oiseau: virtual public Animal class Pegase: public Cheval, public Oiseau
Exemple 2
class Anesse: virtual public Equide class Cheval: virtual public Equide class Bardot: public Anesse, public Cheval
462
Le langage C++
Ne pas faire
Utiliser lhritage multiple lorsquun hri-
nouvelle classe doit faire appel des fonctions et des donnes de plusieurs classes de base.
Utiliser lhritage virtuel lorsque la classe
dont le niveau de drivation est le plus bas dans la hirarchie ne doit avoir quune seule instance de la classe de base partage.
Initialiser la classe de base partage depuis la
classe dont le niveau de drivation est le plus bas dans la hirarchie lorsque vous utilisez des classes de bases virtuelles.
Chapitre 14
Polymorphisme
463
sr dune distinction arbitraire et un mixin nest nalement quune faon simple dindiquer que, parfois, on souhaite ajouter quelques fonctionnalits supplmentaires une classe drive sans la compliquer outre mesure. Certains dbogueurs traiteront plus aisment les classes drives contenant des classes de fonctionnalits que celles qui utilisent lhritage multiple. En outre, il y a moins de risque dambigut lors de laccs aux donnes de lautre classe de base principale. Par exemple, si Cheval drive de Animal et de Affichable, cette dernire ne contiendra pas de donnes. Animal sera ce quelle a toujours t. Toutes les donnes de Cheval proviendront donc de Animal alors que ses mthodes seront issues la fois de Animal et de Affichable. Le terme mixin vient dun glacier de Sommerville dans le Massachusetts, qui mlangeait des bonbons et des biscuits ses prparations de glaces. Cela a sembl tre une bonne mtaphore pour certains programmeurs qui avaient lhabitude dy faire une pause lt, notamment lorsquils dveloppaient avec le langage de programmation orient objet SCOOPS.
Info
464
Le langage C++
12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 44a: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57:
virtual long GetSurface() { return -1; } // erreur virtual long GetPerim() { return -1; } virtual void Tracer() {} private: }; class Cercle: public Forme { public: Cercle(int rayon): sonRayon(rayon){} ~Cercle(){} long GetSurface() { return 3 * sonRayon * sonRayon; } long GetPerim() { return 6 * sonRayon; } void Tracer(); private: int sonRayon; int saCirconference; }; void Cercle::Tracer() { cout << "Trac dun cercle!\n"; }
class Rectangle: public Forme { public: Rectangle(int longueur, int largeur): saLongueur(longueur), saLargeur(largeur){} virtual ~Rectangle(){} virtual long GetSurface() {return saLongueur * saLargeur;} virtual long GetPerim() { return 2 * saLongueur +2 * saLargeur; } virtual int GetLongueur() { return saLongueur; } virtual int GetLargeur() { return saLargeur; } virtual void Tracer(); private: int saLargeur; int saLongueur; }; void Rectangle::Tracer() { for (int i = 0; i < saLongueur; i++) { for (int j = 0; j < saLargeur; j++)
Chapitre 14
Polymorphisme
465
58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89: 90: 91: 92: 93: 94: 95: 96: 97: 98: 99: 100: 101: 102: 103: 104:
cout << "x "; cout << "\n"; } } class Carre: public Rectangle { public: Carre(int longueur); Carre(int longueur, int largeur); ~Carre(){} long GetPerim() {return 4 * GetLongueur();} }; Carre::Carre(int longueur): Rectangle(longueur, longueur) {} Carre::Carre(int longueur, int largeur): Rectangle(longueur, largeur) { if (GetLongueur()!= GetLargeur()) cout << "Erreur, ce nest pas un carr... Rectangle??\n"; } int main() { int choix; bool fQuit = false; Forme * pforme; while (!fQuit ) { cout << "(1)Cercle (2)Rectangle (3)Carr (0)Quitter : "; cin >> choix; switch (choix) { case 0: fQuit = true; break; case 1: pforme = new Cercle(5); break; case 2: pforme = new Rectangle(4,6); break; case 3: pforme = new Carre(5); break;
466
Le langage C++
105: 106: 107: 108: 109: 110: 111: 112: 113: 114: 115: 116: 117:
default: cout << "Entrez un nombre entre 0 et 3\n" ; continue; break; } if(!fQuit ) pforme ->Tracer(); delete pforme pforme = 0; cout << endl; } return 0; }
La classe Forme est dclare de la ligne 7 la ligne 16. Ses mthodes GetSurface() et GetPerim() renvoient une valeur derreur, alors que sa mthode Tracer() ne fait rien puisquon aurait bien du mal expliquer comment tracer une forme : on ne sait tracer que certains types de formes (les cercles, les rectangles, etc.) ; une forme est une abstraction qui ne peut pas tre dessine. Cercle drive de Forme aux lignes 18 29 et rednit les trois mthodes virtuelles. Vous remarquerez quil ny aucune raison de rpter le mot-cl virtual puisquil fait partie de leur hritage, mais cela ne pose aucun problme de le ritrer, comme dans la classe Rectangle (lignes 43, 44 et 47). Cest mme une pratique conseille, car cest une forme de documentation. Carre drive de Rectangle (lignes 64 71), rednit galement la mthode GetPerim() et hrite des autres mthodes de la classe Rectangle.
Chapitre 14
Polymorphisme
467
Il est cependant troublant quun client puisse crer une instance de Forme et il serait souhaitable de len empcher. Cette classe nexiste en effet que pour fournir une interface commune aux classes qui en drivent ; cest donc un type abstrait de donnes ou TAD. Dans une classe abstraite, linterface reprsente un concept (comme une forme) et non un objet spcique (comme un cercle). En C++, une classe abstraite est toujours une classe de base pour dautre classe quil nest pas possible dinstancier.
Dans cet exemple, la classe possde une fonction Tracer(), mais une implmentation nulle et elle ne peut pas tre appele. Elle peut toutefois tre crase dans les classes descendantes. Toute classe comprenant une ou plusieurs fonctions virtuelles pures est une classe abstraite, quil est impossible dinstancier. En fait, il nest pas permis dinstancier une classe abstraite, ni une classe qui drive dune classe abstraite et qui nimplmente pas toutes ses fonctions virtuelles pures. Lajout dune fonction virtuelle pure une classe indique au client que :
Il est impossible den crer un objet. Cette classe doit tre drive. Toutes les fonctions virtuelles pures hrites par une classe doivent tre rednies pour que lon puisse en crer des objets.
Toute classe drive qui drive dune classe abstraite hrite de la fonction virtuelle pure et doit rednir cette dernire pour quelle puisse devenir elle-mme instanciable. Si Rectangle hrite de Forme et que cette dernire contient trois mthodes virtuelles pures, Rectangle doit rednir ces trois mthodes sous peine dtre elle-mme une classe virtuelle. Le Listing 14.8 rcrit la classe Rectangle pour quelle soit un type abstrait de donnes. Pour des raisons de mise en page, le reste du Listing 14.7 na pas t insr ici. Si vous souhaitez excuter le programme, remplacez la dclaration de Forme aux lignes 7 17 du Listing 14.7 par la dclaration du Listing 14.8 et recompilez le programme. Listing 14.8 : Classe abstraite
0: 1: 2: 3: //Listing14.8 Classes abstraites class Forme {
468
Le langage C++
4: 5: 6: 7: 8: 9: 10: 11:
public: Forme(){} ~Forme(){} virtual long GetSurface() = 0; virtual long GetPerim()= 0; virtual void Tracer() = 0; private: };
Comme vous pouvez le constater, le programme fonctionne correctement. La seule diffrence est quil est dsormais impossible de crer des objets de la classe Forme.
Types Abstraits de Donnes (TAD) Un type abstrait se caractrise par la prsence de une ou plusieurs mthodes virtuelles pures dans la dclaration de classe (on dit galement que la classe est abstraite). Une mthode virtuelle pure est initialise 0. Exemple
// virtuelle pure
Chapitre 14
Polymorphisme
469
470
Le langage C++
29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75:
private: int sonRayon; int saCirconference; }; void Cercle::Tracer() { cout << "Trac du cercle !\n"; Forme::Tracer(); }
class Rectangle: public Forme { public: Rectangle(int longueur, int largueur): saLongueur(longueur), saLargeur(largeur){} virtual ~Rectangle(){} long GetSurface() { return saLongueur * saLargeur; } long GetPerim() {return 2 * saLongueur +2 * saLargeur; } virtual int GetLongueur() { return saLongueur; } virtual int GetLargeur() { return saLargeur; } void Tracer(); private: int saLargeur; int saLongueur; }; void Rectangle::Tracer() { for (int i = 0; i < saLongueur; i++) { for (int j = 0; j < saLargeur; j++) cout << "x "; cout << "\n"; } Forme::Tracer(); }
class Carre: public Rectangle { public: Carre(int longueur); Carre(int longueur, int largeur); virtual ~Carre(){}
Chapitre 14
Polymorphisme
471
76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89: 90: 91: 92: 93: 94: 95: 96: 97: 98: 99: 100: 101: 102: 103: 104: 105: 106: 107: 108: 109: 110: 111: 112: 113: 114: 115: 116: 117: 118: 119: 120: 121:
long GetPerim() {return 4 * GetLongueur();} }; Carre::Carre(int longueur): Rectangle(longueur, longueur) {} Carre::Carre(int longueur, int largeur): Rectangle(longueur, largeur) { if (GetLongueur()!= GetLargeur()) cout << "Erreur, ce nest pas un carr... Rectangle??\n"; } int main() { int choix; bool fQuit = false; Forme * pforme; while (fQuit == false) { cout << "(1)Cercle (2)Rectangle (3)Carr (0)Quitter : "; cin >> choix; switch (choix) { case 1: pforme = new Cercle(5); break; case 2: pforme = new Rectangle(4,6); break; case 3: pforme = new Carre (5); break; default: fQuit = true; break; } if (fQuit == false) { pforme->Tracer(); delete pforme; cout << endl; } } return 0; }
472
Le langage C++
(1)Cercle (2)Rectangle (3)Carr (0)Quitter : 3 x x x x x x x x x x x x x x x x x x x x x x x x x Mcanisme de dessin abstrait! (1)Cercle (2)Rectangle (3)Carr (0)Quitter : 0
La classe abstraite Forme est dclare de la ligne 5 la ligne 14 et ses trois mthodes daccs sont des mthodes virtuelles pures. Ce ntait pas ncessaire, mais cest une pratique conseille. Il suft que lune des mthodes soit virtuelle pure pour que la classe soit une classe abstraite. la diffrence de Tracer() (implmente lignes 16 19), les mthodes GetSurface() et GetPerim() ne sont pas implmentes. Cercle et Rectangle rednient Tracer() et ralisent un chanage ascendant vers la mthode de la classe de base an de proter de la fonctionnalit partage quelle propose.
Chapitre 14
Polymorphisme
473
En tant que concepteur, vous indiquez donc que lon ne pourra pas crer dobjets Animal ou Mammifere, mais que tous les mammifres pourront hriter de la mthode Reproduire() fournie par cette classe sans la rednir. Le Listing 14.10 illustre cette technique dimplmentation dun TAD. Listing 14.10 : Drivation de classes abstraites
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 34a: 35: 36: 37: // Listing14.10 // Drivation de classes abstraites #include <iostream> using namespace std; enum COULEUR { Rouge, Vert, Bleu, Jaune, Blanc, Noir, Brun }; class Animal // base commune Mammiferes et Poisson { public: Animal(int); virtual ~Animal() {cout << "Destructeur de Animal...\n"; } virtual int GetAge() const { return sonAge; } virtual void SetAge(int age) { sonAge = age; } virtual void Dormir() const = 0; virtual void Manger() const = 0; virtual void Reproduire() const = 0; virtual void Bouger() const = 0; virtual void Crier() const = 0; private: int sonAge; }; Animal::Animal(int age): sonAge(age) { cout << "Constructeur de Animal...\n"; } class Mammifere: public Animal { public: Mammifere(int age): Animal(age) { cout << "Constructeur de Mammifere...\n";} virtual ~Mammifere() { cout << "Destructeur de Mammifere...\n";} virtual void Reproduire() const { cout << "Reproduction des mammifres...\n"; } };
474
Le langage C++
38: 39: 40: 41: 42: 43: 44: 44a: 45: 45a: 46: 46a: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 80a:
class Poisson: public Animal { public: Poisson(int age):Animal(age) { cout << "Constructeur de Poisson...\n";} virtual ~Poisson() {cout << "Destructeur de Poisson...\n"; } virtual void Dormir() const { cout << "Je fais la sieste...\n"; } virtual void Manger() const { cout << "Je djeune...\n"; } virtual void Reproduire() const { cout << "Je ponds mes oeufs...\n"; } virtual void Bouger() const { cout << "Je nage...\n"; } virtual void Crier() const { } }; class Cheval: public Mammifere { public: Cheval(int age, COULEUR couleur ): Mammifere(age), saCouleur(couleur) { cout << "Constructeur de Cheval...\n"; } virtual ~Cheval() { cout << "Destructeur de Cheval...\n"; } virtual void Crier()const { cout << "Hiihiii!... \n"; } virtual COULEUR GetCouleur() const { return saCouleur; } virtual void Dormir() const { cout << "Je fais la sieste...\n"; } virtual void Manger() const {cout << "Mon picotin...\n"; } virtual void Bouger() const {cout << "Je galope...\n";} protected: COULEUR saCouleur; }; class Chien: public Mammifere { public: Chien(int age, COULEUR couleur ): Mammifere(age), saCouleur(couleur) { cout << "Constructeur de Chien...\n"; } virtual ~Chien() { cout << "Destructeur de Chien...\n"; } virtual void Crier()const { cout << "Ouah Ouah!... \n"; } virtual void Dormir() const { cout << "Je fais la sieste...\n"; }
Chapitre 14
Polymorphisme
475
81: 81a: 82: 82a: 83: 84: 85: 86: 87: 88: 89: 90: 91: 92: 93: 94: 95: 96: 97: 98: 98a: 99: 100: 101: 102: 103: 104: 105: 106: 107: 108: 109: 110: 111: 112: 113: 114: 115: 116: 117: 118: 119: 120: 121: 122: 123: 124:
virtual void Manger() const { cout << "Ma pte prfre...\n"; } virtual void Bouger() const { cout << "Une petite course...\n"; } virtual void Reproduire() const { cout << "Jassure ma descendance...\n"; } protected: COULEUR saCouleur; }; int main() { Animal *pAnimal=0; int choix; bool fQuit = false; while (fQuit == false) { cout << "(1)Chien (2)Cheval (3)Poisson (0)Quitter : "; cin >> choix; switch (choix) { case 1: pAnimal = new Chien(5, Brun); break; case 2: pAnimal = new Cheval(4, Noir); break; case 3: pAnimal = new Poisson (5); break; default: fQuit = true; break; } if (fQuit == false) { pAnimal->Crier(); pAnimal->Manger(); pAnimal->Reproduire(); pAnimal->Bouger(); pAnimal->Dormir(); delete pAnimal; cout << endl; } } return 0; }
476
Le langage C++
La classe abstraite Animal est dclare de la ligne 7 la ligne 21. Ses mthodes daccs sonAge ne sont pas pures et sont partages par tous les animaux. Cette classe contient cinq mthodes virtuelles pures : Dormir(), Manger(), Reproduire(), Bouger() et Crier(). La classe Mammifere drive de Animal aux lignes 30 38 et najoute aucune donnes. Elle rednit cependant la fonction Reproduire() an de fournir une forme de reproduction commune tous les mammifres. Poisson doit galement rednir cette mthode, car elle hrite directement de Animal et ne peut donc pas utiliser la reproduction des mammifres (et cest une bonne chose !). Cette rednition lieu aux lignes 47 et 48. Les classes hritant de Mammifere nont donc plus besoin de rednir la fonction Reproduire(), mme si elles sont libres de le faire (cest dailleurs ce que fait la classe Chien, la ligne 83). Poisson, Cheval et Chien rednissent les mthodes virtuelles pures restantes, an que lon puisse crer des objets de ces types. Dans le corps du programme principal, on utilise un pointeur sur Animal an de traiter un un les diffrents objets drivs. Les mthodes virtuelles sont appeles et, selon les liaisons dynamiques de ce pointeur, cest la mthode de la classe drive approprie qui sera invoque. Tenter dinstancier un objet Animal ou un objet Mammifere produira une erreur de compilation puisque les deux classes sont abstraites.
Chapitre 14
Polymorphisme
477
La rponse cette question dpend non pas dun facteur intrinsque du monde rel, mais de son rle dans votre programme. Si vous dnissez la classe Animal pour un programme cens reprsenter une ferme ou un zoo, cette classe pourrait tre abstraite, alors que Chien serait une classe concrte vous permettant de crer des chiens. Si, par contre, vous crez une simulation de chenil, la classe Chien devrait rester abstraite, alors que les autres classes (Terrier, Levrier, etc.) devraient pouvoir tre instancies. Le niveau dabstraction dpend donc de la nesse dont vous avez besoin pour pouvoir distinguer les types.
Faire
Utiliser les classes abstraites pour fournir une
Ne pas faire
Vouloir crer une instance dune classe
abstraite.
tre rednie.
Questions-rponses
Q Que signie v-ptr ? R Le v-ptr, ou pointeur de mthode virtuelle, est un dtail dimplmentation des mthodes virtuelles. Chaque objet dune classe ayant des mthodes virtuelles possde un v-ptr, qui pointe vers la table de mthodes virtuelles de cette classe. La table des mthodes virtuelles est consulte ds que le compilateur doit dterminer la mthode appeler dans une situation particulire. Q Le ltrage ascendant est-il souhaitable ? R Oui, si la fonctionnalit est partage vers le haut. Non, si vous ne faites que dplacer linterface. Si toutes les classes drives ne peuvent pas utiliser une mthode, cest une erreur de la faire remonter dans une classe de base commune. En effet, cela vous oblige tester le type de lobjet au moment de lexcution an de savoir si vous pouvez appeler cette mthode. Q Pourquoi faut-il viter les dcisions en fonction du type au moment de lexcution ? R Parce que cela indique que la hirarchie dhritage pour la classe na pas t correctement construite. Il vaut mieux revenir sur ses pas et corriger la conception plutt quessayer de contourner le problme.
478
Le langage C++
Q Pourquoi le transtypage est-il une mauvaise pratique ? R Le transtypage est acceptable sil est fait correctement. Cependant, il peut servir contourner le typage fort de C++, ce que lon souhaite gnralement viter. Si vous devez tester un type au cours de lexcution dun programme, puis transtyper un pointeur, cest gnralement un signe que la conception a t mal faite. En outre, les mthodes doivent oprer avec le type dclar de leurs paramtres et des variables membres, et ne pas dpendre de conjectures sur ce que fournira le programme appelant, en vertu dune espce de contrat implicite. Si cette hypothse est errone, les rsultats peuvent tre imprvisibles. Q Pourquoi ne pas rendre toutes les mthodes virtuelles ? R Les mthodes virtuelles sont gres au moyen dune table, ce qui inue sur la taille du programme et ses performances. Si votre programme doit exploiter des classes simples sans classes drives, il est inutile de dnir des mthodes virtuelles. Toutefois, si cette hypothse change, vous devrez revenir sur vos pas et rendre virtuelles les fonctions des classes anctres, faute de quoi des problmes inattendus peuvent survenir. Q Quand faut-il quun destructeur soit virtuel ? R chaque fois que vous pensez quune classe sera drive et quun pointeur sur la classe de base sera utilis pour accder un objet dune classe drive. La rgle gnrale est que si lune des mthode de la classe est virtuelle, le destructeur doit ltre aussi. Q Quel est lintrt dune classe abstraite ? Pourquoi ne pas simplement crer une classe non abstraite et sinterdire de crer des objets de ce type ? R C++ rpertorie les erreurs au cours de la compilation du programme pour vous permettre de les rsoudre avant de livrer le programme au client. Si vous crez une classe abstraite, cest--dire une classe comprenant des mthodes virtuelles pures, le compilateur signalera comme erreurs les tentatives de cration dobjets de ce type abstrait.
Chapitre 14
Polymorphisme
479
4. Si Cheval et Oiseau hritent de Animal selon une relation dhritage virtuel public, leurs constructeurs initialisent-ils le constructeur Animal ? Si Pegase hrite de Cheval et de Oiseau, comment initialise-t-il le constructeur de Animal ? 5. Dclarez une classe abstraite Vehicule. 6. Supposons quune classe de base soit abstraite et contienne trois mthodes virtuelles pures. Combien doivent tre rednies dans les classes drives ?
Exercices
1. Dclarez une classe Jet hritant de la classe Fusee et de la classe Avion. 2. Dclarez une classe Boeing, hritant de la classe Jet dcrite dans lexercice prcdent. 3. crivez le code qui drive Auto et Bus de la classe Vehicule. La classe Vehicule doit tre abstraite et dclarer deux mthodes virtuelles pures. Auto et Bus ne doivent pas tre des classes abstraites. 4. Modiez le code de lExercice 3 pour que Auto soit une classe abstraite et drivez les classes Formule1 et Berline partir de Auto. Dans la classe Auto, fournissez une implmentation de lune des mthodes virtuelles pures de Vehicule et rendez-l non pure.
Partie III
Vous avez termin la deuxime partie de cet ouvrage. Certains des concepts avancs de la programmation oriente objet, tels que lencapsulation et le polymorphisme, doivent prsent vous paratre familiers. Nous allons, dans cette troisime partie, aborder dabord les fonctions statiques et les fonctions amies avant dattaquer les concepts avancs de lhritage. Au Chapitre 17, vous tudierez les ux. Le Chapitre 18 traite en dtail des espaces de noms. La notion de modles est aborde au Chapitre 19. Le Chapitre 20 traite des exceptions et de la gestion derreurs. Enn, le Chapitre 21 examine diffrents concepts non encore abords dans ce livre. Suivra une discussion sur les tapes qui vous permettront de devenir un vritable "gourou de la programmation C++".
15
Classes et fonctions spciales
Au sommaire de ce chapitre
Partage dinformations entre objets de mme type Caractristiques des variables membres et fonctions membres statiques Utilisation de ces nouvelles fonctionnalits Cration et gestion de pointeurs sur les fonctions et les fonctions membres Tableaux de pointeurs de fonctions
C++ fournit un certain nombre de fonctionnalits permettant de limiter la porte et laction des variables et pointeurs. Au cours des chapitres prcdents, vous avez appris crer des variables globales, des variables locales, des pointeurs sur des variables et des variables membres de classe.
484
Le langage C++
Chapitre 15
485
20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40:
int main() { const int MaxChats = 5; int i; Chat *Panier[MaxChats]; for (i = 0; i < MaxChats; i++) Panier[i] = new Chat(i); for (i = 0; i < MaxChats; i++) { cout << "Il reste "; cout << Chat::NbrChats; cout << " chat(s)!" << endl; cout << "Suppression de celui qui a "; cout << Panier[i]->GetAge(); cout << " ans" << endl; delete Panier[i]; Panier[i] = 0; } return 0; }
Les lignes 5 16 dclare une classe Chat simplie. La ligne 12 dclare NbrChats comme tant une variable membre statique de type entier. la diffrence des variables membres non statiques, la dclaration de NbrChats ne dnit pas de valeur numrique entire. La cration dun objet Chat ne rserve pas de mmoire pour NbrChats puisque cette dernire ne fait pas partie de lobjet. Cette variable est dnie et initialise la ligne 18. Une erreur classique consiste oublier de dclarer les variables membres statiques, puis oublier de les dnir. En ce cas, lditeur de liens vous en informe laide dun message du type :
undefined symbol Chat::NbrChats
486
Le langage C++
Ceci est inutile pour la variable sonAge puisquil sagit dun membre non statique et quelle est dnie chaque fois que lon cre un objet Chat, ce que lon fait la ligne 26. Le constructeur de Chat incrmente la variable membre statique la ligne 8. Le destructeur la dcrmente la ligne suivante, ce qui permet de dterminer tout moment combien dobjets ont t crs, mais pas encore supprims. Le programme cre cinq instances de Chat aux lignes 20 40, puis les copie dans un tableau. Pour cela, il appelle cinq fois le constructeur de Chat et la variable statique NbrChats est donc incrmente cinq fois partir de sa valeur initiale 0. Le programme parcourt ensuite les cinq positions et afche la valeur de NbrChats avant de supprimer le pointeur sur lobjet Chat courant (ligne 36). Lafchage produit montre que lon part de la valeur 5 (car on a bien cr 5 chats) et quil y a un chat de moins chaque tour de boucle. Vous remarquerez que NbrChats est publique et quelle est lue directement depuis la fonction principale du programme. Il ny aucune raisin de lexposer ainsi : en fait, il serait prfrable de la rendre prive comme les autres membres et dutiliser une mthode daccs publique pour la consulter (mais vous devrez alors y accder via une instance de Chat). Si vous souhaitez y accder directement, sans passer par un objet Chat, vous avez deux possibilits : conserver la variable publique (comme dans le Listing 15.2) ou crer une mthode membre statique comme nous le verrons plus loin. Listing 15.2 : Accs aux membres statiques sans passer par un objet
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: //Listing15.2 donnes membres statiques #include <iostream> using namespace std; class Chat { public: Chat(int age): sonAge(age) { NbrChats++; } virtual ~Chat() { NbrChats--; } virtual int GetAge() { return sonAge; } virtual void SetAge(int age) { sonAge = age; } static int NbrChats; private: int sonAge; }; int Chat::NbrChats = 0;
Chapitre 15
487
20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45:
void Telepathie(); int main() { const int MaxChats = 5; int i; Chat *Panier[MaxChats]; for (i = 0; i < MaxChats; i++) { Panier[i] = new Chat(i); Telepathie(); } for ( i = 0; i < MaxChats; i++) { delete Panier[i]; Telepathie(); } return 0; } void Telepathie() { cout << "Il reste "; cout << Chat::NbrChats << " chat(s)!" << endl; }
Ce listing ressemble beaucoup au prcdent, sauf quil inclut une nouvelle fonction, Telepathie(). Celle-ci, dclare lignes 41 45, ne cre pas dobjet Chat, ni nen prend en paramtre, mais accde pourtant directement la variable membre NbrChats. Cette variable ne fait partie daucun objet, mais appartient la classe o elle est accessible toute mthode membre. Si elle est publique, cette variable peut galement tre lue par nimporte quelle fonction du programme, mme si celle-ci na pas dinstance dune classe.
488
Le langage C++
Lautre solution consiste rendre cette variable prive. Vous pourrez alors y accder par lintermdiaire dune mthode membre, mais vous devrez alors passer par un objet de la classe. Le Listing 15.3 montre cette approche, lautre possibilit lutilisation dune mthode statique sera tudie ensuite. Listing 15.3 : Lecture de membres statiques laide de fonctions membres non statiques
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: //Listing15.3 donnes membres statiques prives #include <iostream> using std::cout; using std::endl; class Chat { public: Chat(int age): sonAge(age) { NbrChats++; } virtual ~Chat() { NbrChats--; } virtual int GetAge() { return sonAge; } virtual void SetAge(int age) { sonAge = age; } virtual int GetNbr() { return NbrChats; } private: int sonAge; static int NbrChats; }; int Chat::NbrChats = 0; int main() { const int MaxChats = 5; int i; Chat *Panier[MaxChats]; for (i = 0; i < MaxChats; i++) Panier[i] = new Chat(i); for (i = 0; i < MaxChats; i++) { cout << "Il reste "; cout << Panier[i]->GetNbr(); cout << " chat(s)!\n"; cout << "Suppression de celui qui a "; cout << Panier[i]->GetAge() + 2;
Chapitre 15
489
cout << " ans" << endl; delete Panier[i]; Panier[i] = 0; } return 0; }
La ligne 16 dclare la variable membre statique NbrChats comme prive. Elle nest donc dsormais accessible qu partir des fonctions membres. Bien quelle soit statique, la porte de la variable NbrChats est encore celle de la classe. Toute fonction membre, comme GetNbr(), peut donc y accder. Cependant, pour quune fonction situe lextrieur de la classe Chat puisse appeler GetNbr(), elle doit disposer dun objet Chat sur lequel appeler cette mthode.
Faire
Utiliser des variables membres statiques pour
Ne pas faire
Utiliser des variables membres statiques pour
stocker les donnes dun objet particulier. Les donnes membres statiques sont partages par tous les objets de sa classe.
490
Le langage C++
Chapitre 15
491
Laccs la variable membre statique NbrChats est priv, comme lindique la ligne 14 dans la dclaration de Chat. La fonction publique daccs, GetNbr(), est dclare publique et statique la ligne 11. GetNbr() tant publique, cette fonction est accessible aux autres fonctions et, puisquelle est statique, elle na pas besoin dun objet Chat sur lequel tre appele. la ligne 41, la fonction Telepathie() peut donc appeler la mthode daccs statique sans avoir recours un objet Chat. Notez toutefois que la mthode est totalement qualie lorsquelle est appele, ce qui signie que lappel de la mthode est prx avec le nom de classe suivi du signe deux-points :
Chat::GetNbr()
Bien entendu, vous auriez pu appeler GetNbr() sur les objets Chat disponibles dans main(), comme pour nimporte quelle autre mthode publique daccs. Les mthodes membres statiques ne contenant pas de pointeur this, elles ne peuvent tre dclares const. De mme, dans les mthodes, les variables membres sont lues laide du pointeur this : cela signie que les mthodes membres statiques ne peuvent pas accder aux variables membres non statiques !
Info
Mthodes membres statiques Pour accder aux mthodes membres statiques, il suft de les appeler sur un objet de la classe, comme vous le feriez pour nimporte quelle mthode membre. Vous pouvez galement les appeler sans objet, en indiquant clairement le nom de la classe et le nom de lobjet.
492
Le langage C++
Exemple
class Chat { public: static int GetNbr() { return NbrChats; } private: static int NbrChats; }; int Chat::NbrChats = 0; int main() int Nbr; Chat leChat; Nbr = leChat.GetNbr(); Nbr = Chat::GetNbr();
ptrFction est dclar comme un pointeur (reconnaissable lastrisque devant le nom) sur une fonction prenant un paramtre entier et renvoyant un entier long. Les parenthses encadrant * ptrFction sont ncessaires car celles autour de int sont plus prioritaires que loprateur dindirection (*). Sans ces premires parenthses, cette ligne dclarerait donc une fonction prenant un paramtre entier et renvoyant un pointeur sur un type long (noubliez pas que les espaces nont aucune importance ici). Examinez les deux dclarations suivantes :
long * Fonction (int); long (* ptrFonction) (int);
Chapitre 15
493
Dans la premire, Fonction() prend un entier et renvoie un pointeur sur une variable de type long. Dans la seconde, prtFonction est un pointeur sur une fonction prenant un entier en paramtre et renvoyant une variable de type long. Une dclaration de pointeur de fonction doit toujours comprendre le type du rsultat et des parenthses entourant les types des paramtres. Listing 15.5 : Pointeurs sur fonctions
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 20a: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: // Listing15.5 Utilisation de pointeurs de fonction #include <iostream> using namespace std; void void void void void Carre (int&,int&); Cube (int&, int&); Permuter (int&, int &); GetVals(int&, int&); AffVals(int, int);
int main() { void (* pFonc) (int &, int &); bool fQuit = false; int Val1 = 1, Val2 = 2; int choix; while (fQuit == false) { cout << "(0)Quitter (1)Modifier Valeurs (2)Carr "; cout << "(3)Cube (4)Permuter: "; cin >> choix; switch (choix) { case 1: pFonc = GetVals; break; case 2: pFonc = Carre; break; case 3: pFonc = Cube; break; case 4: pFonc = Permuter; break; default: fQuit = true; break; } if (fQuit == false) { AffVals(Val1, Val2); pFonc(Val1, Val2); AffVals(Val1, Val2); }
494
Le langage C++
37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79:
} return 0; } void AffVals(int x, int y) { cout << "x : " << x << " y : " << y << endl; } void Carre(int & rX, int & rY) { rX *= rX; rY *= rY; } void Cube(int & rX, int & rY) { int tmp; tmp = rX; rX *= rX; rX = rX * tmp; tmp = rY; rY *= rY; rY = rY * tmp; } void Permuter(int & rX, int & rY) { int temp; temp = rX; rX = rY; rY = temp; } void GetVals (int & rVal1, int & rVal2) { cout << "Nouvelle valeur de Val1: "; cin >> rVal1; cout << "Nouvelle valeur de Val2: "; cin >> rVal2; }
Chapitre 15
495
Les lignes 5 8 dclare quatre fonctions renvoyant void et prenant en paramtre deux rfrences des entiers. La ligne 13 dclare pFonc comme un pointeur sur une fonction qui renvoie void et prend deux rfrences des entiers en paramtres. Comme leurs signatures correspondent, il peut donc pointer sur les quatre fonctions. Laffectation de pFonc dpend du choix de lutilisateur. La valeur des deux entiers est afche (lignes 33 35), la fonction choisie est appele, puis les valeurs safchent de nouveau.
Pointeur de fonction Un pointeur de fonction est appel de la mme faon que les fonctions sur lesquelles il pointe, sauf que cest le nom du pointeur de fonction qui est utilis la place du nom de la fonction. On affecte un pointeur une fonction spcique en lui affectant le nom de la fonction sans parenthses. Le nom de la fonction est un pointeur constant sur la fonction ellemme. Le pointeur de fonction est utilis comme un nom de fonction classique. La valeur renvoye et la signature dclares pour le pointeur doivent correspondre celles de la fonction que vous lui affectez. Exemple
long (*pFoncUne) (int, int); long Fonction (int, int); pFoncUne = Fonction; pFoncUne(5,7);
496
Le langage C++
ntion Atte
Sachez que les pointeurs de fonctions peuvent tre trs dangereux. Vous pouvez affecter accidentellement une fonction un pointeur alors que vous vouliez lappeler ou appeler la fonction par inadvertance alors que vous vouliez laffecter son pointeur.
int main() { bool fQuit = false; int Val1 = 1, Val2 = 2; int choix; while (fQuit == false) { cout << "(0)Quitter (1)Modifier Valeurs (2)Carr "; cout << "(3)Cube (4)Permuter: "; cin >> choix;
Chapitre 15
497
20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67:
switch (choix) { case 1: AffVals(Val1, Val2); GetVals(Val1, Val2); AffVals(Val1, Val2); break; case 2: AffVals (Val1, Val2); Carre(Val1,Val2); AffVals (Val1, Val2); break; case 3: AffVals(Val1, Val2); Cube(Val1, Val2); AffVals (Val1, Val2); break; case 4: AffVals (Val1, Val2); Permuter(Val1, Val2); AffVals (Val1, Val2); break; default: fQuit = true; break; } } return 0; } void AffVals(int x, int y) { cout << "x: " << x << " y: " << y << endl; } void Carre(int & rX, int & rY) { rX *= rX; rY *= rY; } void Cube(int & rX, int & rY) { int tmp;
498
Le langage C++
68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89: 90: 91: 92:
tmp = rX; rX *= rX; rX = rX * tmp; tmp = rY; rY *= rY; rY = rY * tmp; } void Permuter(int & rX, int & rY) { int temp; temp = rX; rX = rY; rY = temp; } void GetVals (int & rVal1, int & rVal2) { cout << "Nouvelle valeur de Val1: "; cin >> rVal1; cout << "Nouvelle valeur de Val2: "; cin >> rVal2; }
Il aurait t tentant de placer la fonction AffVals() au dbut et la n de la boucle while, plutt que dans chaque instruction case mais, dans ce cas, elle aurait t appele une fois
Chapitre 15
499
de trop (au niveau de linstruction case correspondant Quitter) et cela ne faisait pas partie de la spcication. Avec les appels rpts et la redondance du code, le programme devient rapidement confus et difcile grer. Dans une application professionnelle, lavantage des pointeurs de fonctions est encore plus clair : ils permettent dviter la duplication du code, de le rendre plus clair et de mettre en place des tables de fonctions qui pourront tre appeles en fonction de conditions testes au moment de lexcution. La programmation oriente objet permet gnralement dviter la cration ou le passage des pointeurs de fonctions en paramtres. Appelez plutt la mthode souhaite de lobjet concern ou invoquez la mthode de classe voulue. Si vous avez besoin dun tableau de pointeurs de fonctions, demandez-vous si un tableau dobjets ne serait pas plus appropri.
ce Astu
Appels raccourcis Il est inutile de drfrencer un pointeur de fonction, mme si cela est autoris. Si pFonc est un pointeur sur une fonction prenant un entier en paramtre et renvoyant un rsultat de type long, et que vous lui affectez une fonction qui correspond ces conditions, vous pouvez appeler cette fonction de la faon suivante :
pFonc(x);
ou
(*pFonc)(x);
Les deux expressions ont le mme effet, la premire est simplement un raccourci de la seconde.
500
Le langage C++
3: #include <iostream> 4: using namespace std; 5: 6: void Carre(int&,int&); 7: void Cube(int&, int&); 8: void Permuter(int&, int &); 9: void GetVals(int&, int&); 10: void AffVals(int, int); 11: 12: int main() 13: { 14: int val1 = 1, val2 = 2; 15: int choix, i; 16: const Max = 5; 17: void (*TableauPtrFonctions[Max])(int&, int&); 18: 19: for (i = 0; i < Max; i++) 20: { 21: cout << "(0)Quitter (1)Modifier Valeurs (2)Carr "; 21a: cout << "(3)Cube (4)Permuter: "; 22: cin >> choix; 23: switch (choix) 24: { 25: case 1: TableauPtrFonctions[i] = GetVals; break; 26: case 2: TableauPtrFonctions[i] = Carre; break; 27: case 3: TableauPtrFonctions[i] = Cube; break; 28: case 4: TableauPtrFonctions[i] = Permuter; break; 29: default: TableauPtrFonctions[i] = 0; 30: } 31: } 32: 33: for (i = 0; i < Max; i++) 34: { 35: if (TableauPtrFonctions[i] == 0 ) 36: continue; 37: TableauPtrFonctions[i](val1,val2); 38: AffVals(val1,val2); 39: } 40: return 0; 41: } 42: 43: void AffVals(int x, int y) 44: { 45: cout << "x: " << x << " y: " << y << endl; 46: } 47: 48: void Carre(int & rX, int & rY) 49: {
Chapitre 15
501
50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81:
rX *= rX; rY *= rY; } void Cube(int & rX, int & rY) { int tmp; tmp = rX; rX *= rX; rX = rX * tmp; tmp = rY; rY *= rY; rY = rY * tmp; } void Permuter(int & rX, int & rY) { int temp; temp = rX; rX = rY; rY = temp; } void GetVals(int & rVal1, int & rVal2) { cout << "Nouvelle valeur de Val1: "; cin >> rVal1; cout << "Nouvelle valeur de Val2: "; cin >> rVal2; }
502
Le langage C++
La ligne 17 dclare TableauPtrFonctions comme un tableau de cinq pointeurs de fonctions renvoyant void et prenant deux rfrences entires en paramtres. De la ligne 19 la ligne 31, lutilisateur est invit choisir une fonction et chaque lment du tableau reoit ladresse de la fonction choisie. De la ligne 33 la ligne 39, les fonctions sont appeles les unes aprs les autres et le rsultat safche aprs chaque appel.
int main() { int val1 = 1, val2 = 2; int choix; bool fQuit = false; void (*pFonc)(int&, int&); while (fQuit == false) { cout << "(0)Quitter (1)Modifier Valeurs (2)Carr "; cout << "(3)Cube (4)Permuter: "; cin >> choix; switch (choix) { case 1: pFonc = GetVals; break;
Chapitre 15
503
26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72:
= = = =
if (fQuit == false) AffVals( pFonc, val1, val2); } return 0; } void AffVals( void (*pFonc)(int&, int&),int& x, int& y) { cout << "x: " << x << " y: " << y << endl; pFonc(x,y); cout << "x: " << x << " y: " << y << endl; } void Carre(int & rX, int & rY) { rX *= rX; rY *= rY; } void Cube(int & rX, int & rY) { int tmp; tmp = rX; rX *= rX; rX = rX * tmp; tmp = rY; rY *= rY; rY = rY * tmp; } void Permuter(int & rX, int & rY) { int temp; temp = rX; rX = rY; rY = temp; }
504
Le langage C++
void GetVals(int & rVal1, int & rVal2) { cout << "Nouvelle valeur de Val1: "; cin >> rVal1; cout << "Nouvelle valeur de Val2: "; cin >> rVal2; }
La ligne 17 dclare pFonc comme un pointeur sur une fonction renvoyant void et prenant deux paramtres (des rfrences des entiers). La ligne 9 dclare AffVals() comme une fonction acceptant trois paramtres : le premier est un pointeur sur une fonction qui renvoie void et attend deux paramtres qui sont des rfrences des entiers ; les deuxime et troisime paramtres de AffVals() sont des rfrences entires. Les lignes 19 et 20 demandent lutilisateur de choisir une fonction et la ligne 33 appelle AffVals() en lui passant le pointeur de fonction pFonc comme premier paramtre. Demandez un programmeur C++ ce que signie cette dclaration :
void AffVals(void (*)(int&, int&),int&, int&);
Cest une dclaration peu commune dont vous vrierez probablement la syntaxe dans un livre chaque fois que vous en avez besoin, mais qui sauvera votre programme dans les rares occasions o cette construction est exactement celle qui convient.
Chapitre 15
505
Listing 15.9) reprsentant un pointeur sur une fonction renvoyant void et prenant comme paramtres deux rfrences sur des entiers. Le Listing 15.9 rcrit le Listing 15.8 en utilisant linstruction typedef. Listing 15.9 : Utilisation de typedef pour rendre plus lisibles les pointeurs de fonctions
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 23a: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: // Listing15.9. Utilisation de typedef // pour rendre plus lisibles les pointeurs sur fonctions #include <iostream> using namespace std; void Carre(int&,int&); void Cube(int&, int&); void Permuter(int&, int &); void GetVals(int&, int&); typedef void (*PFV) (int&, int&); void AffVals(PFV, int&, int&); int main() { int val1 = 1, val2 = 2; int choix; bool fQuit = false; PFV pFonc; while (fQuit == false) { cout << "(0)Quitter (1)Modifier Valeurs (2)Carr "; cout << "(3)Cube (4)Permuter: "; cin >> choix; switch (choix) { case 1: pFonc = GetVals; break; case 2: pFonc = Carre; break; case 3: pFonc = Cube; break; case 4: pFonc = Permuter; break; default: fQuit = true; break; } if (fQuit == false) AffVals( pFonc, val1, val2); } return 0; }
506
Le langage C++
39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80:
void AffVals(PFV pFonc, int& x, int& y) { cout << "x: " << x << " y: " << y << endl; pFonc(x, y); cout << "x: " << x << " y: " << y << endl; } void Carre(int & rX, int & rY) { rX *= rX; rY *= rY; } void Cube(int & rX, int & rY) { int tmp; tmp = rX; rX *= rX; rX = rX * tmp; tmp = rY; rY *= rY; rY = rY * tmp; } void Permuter(int & rX, int & rY) { int temp; temp = rX; rX = rY; rY = temp; } void GetVals(int & rVal1, int & rVal2) { cout << "Nouvelle valeur de Val1: "; cin >> rVal1; cout << "Nouvelle valeur de Val2: "; cin >> rVal2; }
Chapitre 15
507
Nouvelle valeur de Val1: 2 Nouvelle valeur de Val2: 3 x: 2 y:3 (0)Quitter (1)Modifier Valeurs x: 2 y:3 x: 8 y: 27 (0)Quitter (1)Modifier Valeurs x: 8 y: 27 x: 64 y: 729 (0)Quitter (1)Modifier Valeurs x: 64 y: 729 x: 729 y: 64 (0)Quitter (1)Modifier Valeurs
Linstruction typedef de la ligne 10 dclare PFV comme un alias du type "pointeur sur une fonction renvoyant void et prenant deux paramtres (des rfrences dentiers)". la ligne suivante, la fonction AffVals() attend trois paramtres : un PFV et deux rfrences des entiers. La ligne 19 dclare pFonc comme tant dsormais de type PFV. Grce la dnition du type PFV, les dclarations de pFonc et de AffVals() sont bien plus claires et, comme vous pouvez le constater, le rsultat est identique. Noubliez pas, cependant, que typedef effectue essentiellement un remplacement : il ne "cre" pas un nouveau type. Ici, son utilisation simplie beaucoup la relecture du code.
Les pointeurs vers les mthodes membres sont utiliss exactement comme les pointeurs de fonctions, sauf quils doivent tre appels sur un objet de la classe approprie. Le Listing 15.10 montre comment faire.
508
Le langage C++
class Chat: public Mammifere { public: void Crier()const { cout << "Miaou!" << endl; } void Bouger() const { cout << "Nonchalant..." << endl; } };
class Cheval: public Mammifere { public: void Crier()const { cout << "hihiii!" << endl; } void Bouger() const { cout << "Au galop..." << endl; } };
int main() { void (Mammifere::*pFonc)() const =0; Mammifere* ptr =0; int Animal; int Methode;
Chapitre 15
509
46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74:
bool fQuit = false; while (fQuit == false) { cout << "(0)Quitter (1)Chien (2)Chat (3)Cheval: "; cin >> Animal; switch (Animal) { case 1: ptr = new Chien; break; case 2: ptr = new Chat; break; case 3: ptr = new Cheval; break; default: fQuit = true; break; } if (fQuit == false) { cout << "(1)Crier (2)Bouger: "; cin >> Methode; switch (Methode) { case 1: pFonc = Mammifere::Crier; break; default: pFonc = Mammifere::Bouger; break; } (ptr->*pFonc)(); delete ptr; } } return 0; }
La classe abstraite Mammifere est dclare de la ligne 5 la ligne 14, avec deux fonctions virtuelles pures : Crier() et Bouger(). Cette classe est hrite par les classes drives Chien, Chat et Cheval, qui rednissent Crier() et Bouger().
510
Le langage C++
La fonction principale commence la ligne 40. la ligne 50, lutilisateur doit choisir entre trois types danimaux. Selon son choix, une nouvelle classe drive (Animal) est cre sur le tas et affecte ptr (lignes 54 56). la ligne 61, lutilisateur doit choisir la mthode appeler. La mthode choisie, Crier() ou Bouger(), est affecte au pointeur pFonc, aux lignes 65 et 66. la ligne 69, elle est appele par lobjet cr laide de ptr pour accder cet objet et de pFonc pour accder la mthode. Enn, delete supprime le pointeur ptr et libre lespace mmoire occup sur le tas (ligne 70). Il ny a pas de raison dappeler delete sur pFonc car cest un pointeur sur du code, pas sur un objet du tas. Si vous tentiez cette opration, le compilateur vous signalerait une erreur.
Chapitre 15
511
23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 36a: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51:
Chien::Bouger, Chien::Manger, Chien::Grogner, Chien::Couiner, Chien::Jouer, Chien::FaireMort }; Chien* pChien =0; int Methode; bool fQuit = false; while (!fQuit) { cout << "(0)Quitter (1)Crier (2)Bouger "; cout << "(3)Manger (4)Grogner "; cout << "(5)Couiner (6)Jouer (7)Faire le mort: "; std::cin >> Methode; if (Methode <= 0 || Methode >= 8) { fQuit = true; } else { pChien = new Chien; (pChien->*FonctionsChien[Methode - 1])(); delete pChien; } } return 0; }
512
Le langage C++
La classe Chien est cre de la ligne 5 la ligne 15 avec sept fonctions membres renvoyant le mme type de rsultat et ayant la mme signature. typedef dclare le type PFC la ligne 17 comme pointeur vers une fonction membre constante de Chien sans paramtre et ne renvoyant rien, ce qui correspond aux signatures des sept mthodes de Chien. Le Tableau FonctionsChien est dclar de la ligne 21 la ligne 28, pour contenir sept pointeurs de fonctions membres analogues, et il est initialis avec les adresses de ces fonctions. On demande ensuite lutilisateur de choisir une mthode. Sauf sil choisit Quitter, un objet Chien est cr sur le tas, puis appelle la mthode choisie la ligne 46) Voici une autre ligne que vous pouvez prsenter aux programmeurs chevronns de votre entreprise ; demandez-leur ce quelle signie :
(pChien->*FonctionsChien[Methode - 1])();
Il sagit dun appel une mthode dun objet via un pointeur de mthode stock dans un tableau lindice Methode - 1. Une fois de plus, cette instruction doit tre vite autant que possible. Si vous lutilisez, documentez-la minutieusement et essayez dimaginer une autre manire daccomplir la tche.
Faire
Appeler des pointeurs sur des fonctions
Ne pas faire
Utiliser des pointeurs sur des fonctions
un pointeur vers une fonction (contrairement une fonction qui renvoie un pointeur).
Questions-rponses
Q Pourquoi utiliser des donnes statiques au lieu de donnes globales ? R La porte des donnes statiques est limite la classe. Elles ne sont accessibles que via un objet de la classe, par un appel utilisant le nom de la classe si elles sont publiques, ou en utilisant une fonction membre statique. La porte des donnes statiques est celle de leur classe ; grce aux restrictions daccs et au typage fort, elles sont donc plus ables que les donnes globales.
Chapitre 15
513
Q Pourquoi utiliser des fonctions membres statiques au lieu de fonctions globales ? R Les fonctions membres statiques ont une porte limite la classe et ne peuvent tre appeles que via un objet de la classe ou par une spcication explicite complte (comme NomClasse::NomFonction()). Q Est-il courant davoir recours aux pointeurs de fonctions et de fonctions membres ? R Non. Leur utilisation est spcique certaines situations. Nombreux sont les programmes, mme complexes, qui nexploitent pas ces fonctionnalits, mais il peut exister des situations o ils constituent la seule solution.
Exercices
1. crivez un programme qui dclare une classe contenant une variable membre et une variable membre statique. Le constructeur initialisera la variable membre et incrmentera la variable statique. Utilisez le destructeur pour dcrmenter la variable membre. 2. En reprenant le programme de lExercice 1, crivez un petit programme de test qui cre trois objets puis afche leurs variables membres et la variable membre statique. Dtruisez ensuite chaque objet et montrez leffet obtenu sur la variable membre statique. 3. Modiez le programme de lexercice prcdent pour utiliser une fonction membre statique pour accder la variable membre statique qui doit maintenant tre prive. 4. Ajoutez un pointeur de fonction membre pour accder la donne membre non statique du programme de lExercice 3. Utilisez ce pointeur pour afcher la valeur de ce membre.
514
Le langage C++
5. Dans le programme prcdent, ajoutez deux variables membres la classe. Dnissez des fonctions daccs pour lire leurs valeurs en faisant en sorte quelles renvoient le mme type de rsultat et quelles aient la mme signature. Utilisez le pointeur de fonction membre pour accder ces mthodes.
16
Concepts avancs dhritage
Au sommaire de ce chapitre
Caractristiques et modlisation de lagrgation (relation a-un) Caractristiques et modlisation de la dlgation Implmentation dune classe en termes dune autre Utilisation de lhritage priv
Agrgation
Vous avez vu dans les exemples prcdents que les donnes membres dune classe peuvent inclure des objets dautres types de classes, une situation souvent appele agrgation, ou relation a-un.
516
Le langage C++
Ces deux classes pourraient tre incluses dans une classe Employe :
Class Employe { Nom NomEmp; Adresse AdresseEmp; // Autres lments de la classe Employe... }
Une classe Employe pourrait donc contenir des variables membres pour un nom et pour une adresse (Employe a-un Nom et Employe a-une Adresse). Le Listing 16.1 prsente un exemple plus complexe en implmentant une classe String incomplte mais nammoins fonctionnelle. Ce programme ne produit pas de rsultat, mais son contenu sera repris dans les listings suivants. Listing 16.1 : Classe String
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: // Listing16.1 La classe String #include <iostream> #include <String.h> using namespace std; class String { public: // constructeurs String(); String(const char *const); String(const String &); ~String(); // oprateurs surchargs char & operator[](int indice); char operator[](int indice) const;
Chapitre 16
517
18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64:
String operator+(const String&); void operator+=(const String&); String & operator=(const String &); // mthodes gnrales daccs int GetLongueur()const { return saLongueur; } const char * GetString() const { return saChaine; } // static int CptConstructeur; private: String (int); // constructeur priv char * saChaine; unsigned short saLongueur; }; // le constructeur par dfaut cre des chanes de 0 octet String::String() { saChaine = new char[1]; saChaine[0] = \0; saLongueur=0; // cout << "\tConstructeur par dfaut de String \n"; // CptConstructeur++; } // constructeur (utilitaire) prive, utilis seulement par // les mthodes de la classe pour crer une nouvelle chane // de la taille requise, contenant des caractres nuls. String::String(int longueur) { saChaine = new char[longueur+1]; for (int i = 0; i <= longueur; i++) saChaine[i] = \0; saLongueur = longueur; // cout << "\tConstructeur de String(int) \n"; // CptConstructeur++; } // Convertit un tableau de caractres en chane String::String(const char * const uneChaine) { saLongueur = strlen(uneChaine); saChaine = new char[saLongueur+1]; for (int i = 0; i < saLongueur; i++) saChaine[i] = uneChaine[i]; saChaine[saLongueur]=\0;
518
Le langage C++
65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89: 90: 91: 92: 93: 94: 95: 96: 97: 98: 99: 100: 101: 102: 103: 104: 105: 106: 107: 108: 109: 110: 111:
// cout << "\tConstructeur de String(char*) \n"; // CptConstructeur++; } // constructeur de copie String::String (const String & rhs) { saLongueur = rhs.GetLongueur(); saChaine = new char[saLongueur+1]; for (int i = 0; i < saLongueur; i++) saChaine[i] = rhs[i]; saChaine[saLongueur] = \0; // cout << "\tConstructeur de String(String&) \n"; // CptConstructeur++; } // destructeur, libre la mmoire String::~String () { delete [] saChaine; saLongueur = 0; // cout << "\tDestructeur de String \n"; } // oprateur gal, libre la mmoire existante // puis copie la chane et la taille String& String::operator=(const String & rhs) { if (this == &rhs) return *this; delete [] saChaine; saLongueur = rhs.GetLongueur(); saChaine = new char[saLongueur + 1]; for (int i = 0; i < saLongueur; i++) saChaine[i] = rhs[i]; saChaine[saLongueur] = \0; return *this; // cout << "\tOperator= de String\n"; } // oprateur dindexation non constant, renvoie // une rfrence sur un caractre pour quil puisse // tre modifi! char & String::operator[](int indice) { if (indice > saLongueur) return saChaine[saLongueur - 1];
Chapitre 16
519
112: 113: 114: 115: 116: 117: 118: 119: 120: 121: 122: 123: 124: 125: 126: 127: 128: 129: 130: 131: 132: 133: 134: 135: 136: 137: 138: 139: 140: 141: 142: 143: 144: 145: 146: 147: 148: 149: 150: 151: 152: 153: 154: 155: 156:
else return saChaine[indice]; } // oprateur dindexation constant pour utilisation // sur les objets const (voir constructeur de copie!) char String::operator[](int indice) const { if (indice > saLongueur) return saChaine[saLongueur - 1]; else return saChaine[indice]; } // cre une nouvelle chane en ajoutant // la chane actuelle rhs String String::operator+(const String& rhs) { int totalLongueur = saLongueur+rhs.GetLongueur(); String temp(totalLongueur); int i, j; for (i = 0; i < saLongueur; i++) temp[i] = saChaine[i]; for (j = 0; j < rhs.GetLongueur(); j++, i++) temp[i] = rhs[j]; temp[totalLongueur]=\0; return temp; } // modifie la chane courante, ne renvoie rien void String::operator+=(const String& rhs) { unsigned short rhsLongueur = rhs.GetLongueur(); unsigned short totalLongueur = saLongueur+rhsLongueur; String temp(totalLongueur); int i, j; for (i = 0; i < saLongueur; i++) temp[i] = saChaine[i]; for (j = 0; j < rhs.GetLongueur(); j++, i++) temp[i] = rhs[i - saLongueur]; temp[totalLongueur]=\0; *this = temp; } // int String::CptConstructeur = 0;
520
Le langage C++
Info
Placez le code du Listing 16.1 dans un chier appel String.hpp. chaque fois que vous aurez besoin de la classe String, vous pourrez inclure ce code laide de la directive #include "String.hpp", comme dans lexemple suivant. Les lignes du listing qui ont t commentes seront expliques au cours de ce chapitre.
Le Listing 16.1 dnit une classe String qui ressemble beaucoup celle du Listing 13.12 du Chapitre 13. La diffrence importante entre ces deux classes est, quici, les instructions dafchage des constructeurs et quelques autres fonctions du Listing 13.12 ont t mises en commentaires an de les dsactiver. Ces fonctions seront utilises dans les exemples suivants. La ligne 25 dclare la variable membre statique CptConstructeur, qui est initialise la ligne 156. Cette variable est incrmente dans chaque constructeur. Tout ceci est, pour le moment, mis en commentaire et sera activ ultrieurement. Pour des raisons pratiques, limplmentation de la classe est place dans le mme chier que sa dclaration. Dans une application relle, la dclaration se trouverait dans String.hpp et son implmentation dans String.cpp. Vous ajouteriez alors String.cpp votre programme ( laide de ajouter fichiers ou dun makele) et incluriez String.hpp dans String.cpp laide dune directive #include. Dailleurs, dans un vrai programme, vous utiliseriez la classe String de la bibliothque Standard de C++ la place de cette classe String personnalise. Le Listing 16.2 dcrit une classe Employe contenant trois objets String. Ceux-ci correspondent au prnom, au nom et ladresse dun employ. Listing 16.2 : Classe Employe et programme principal
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: // Listing16.2 Classe Employe et programme principal #include "String.hpp" class Employe { public: Employe(); Employe(char *, char *, char *, long); ~Employe(); Employe(const Employe&); Employe & operator= (const Employe &); const String & GetPrenom() const { return sonPrenom; } const String & GetNom() const { return sonNom; }
Chapitre 16
521
15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61:
const String & GetAdresse() const { return sonAdresse; } long GetSalaire() const { return sonSalaire; } void SetPrenom(const String & petitNom) { sonPrenom = petitNom; } void SetNom(const String & nomFamille) { sonNom = nomFamille; } void SetAdresse(const String & adresse) { sonAdresse = adresse; } void SetSalaire(long salaire) { sonSalaire = salaire; } private: String sonPrenom; String sonNom; String sonAdresse; long sonSalaire; }; Employe::Employe(): sonPrenom(""), sonNom(""), sonAdresse(""), sonSalaire(0) {} Employe::Employe(char * Prenom, char * Nom, char * adresse, long salaire): sonPrenom(Prenom), sonNom(Nom), sonAdresse(adresse), sonSalaire(salaire) {} Employe::Employe(const Employe & rhs): sonPrenom(rhs.GetPrenom()), sonNom(rhs.GetNom()), sonAdresse(rhs.GetAdresse()), sonSalaire(rhs.GetSalaire()) {} Employe::~Employe() {} Employe & Employe::operator= (const Employe & rhs) { if (this == &rhs) return *this; sonPrenom = rhs.GetPrenom();
522
Le langage C++
62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85:
sonNom = rhs.GetNom(); sonAdresse = rhs.GetAdresse(); sonSalaire = rhs.GetSalaire(); return *this; } int main() { Employe JB007("J", "B", "Avenue Ian Fleming", 20000); JB007.SetSalaire(500000); String Nom("Bond"); JB007.SetNom(Nom); JB007.SetPrenom("James"); cout << "Nom: "; cout << JB007.GetPrenom().GetString(); cout << " " << JB007.GetNom().GetString(); cout << ".\nAdresse: "; cout << JB007.GetAdresse().GetString(); cout << ".\nSalaire: "; cout << JB007.GetSalaire(); return 0; }
Dans ce programme, la classe Employe comprend trois objets String (lignes 26 28) : sonPrenom, sonNom et sonAdresse. La ligne 71 cre un objet Employe nomm JB007 en lui passant quatre valeurs. La ligne 72 appelle la fonction daccs SetSalaire() de Employe avec la valeur littrale 500000. Dans un programme professionnel, cette valeur serait soit une valeur dynamique (xe lors de lexcution), soit une constante. La ligne 73 cre et initialise lobjet Nom partir dune chane littrale (de type C). Cet objet String est pass la fonction SetNom() la ligne suivante. La ligne 75 appelle la fonction membre SetPrenom() avec une autre chane littrale. Si vous examinez attentivement le code de la classe Employe, vous remarquerez quelle ne dispose pas dune mthode SetPrenom() prenant une chaine de type C en paramtre : la mthode qui porte ce nom attend une rfrence constante un objet String (ligne 18).
Chapitre 16
523
Le compilateur est capable de sen sortir car il sait construire un objet String partir dune chane de caractres littrale. Il le sait parce que vous lui avez indiqu comment faire la ligne 11 du Listing 16.1. Si vous examinez les lignes 78, 79 et 81, vous constaterez quelque chose dinhabituel. Vous pourriez en effet vous demander pourquoi GetString() a t plac aprs les appels aux diffrentes mthodes de la classe Employe, comme ici :
78: cout << JB007.GetPrenom().GetString();
La mthode GetPrenom() de lobjet JB007 renvoie un objet String. Malheureusement, notre objet String du Listing 16.1 ne permet pas encore dutiliser directement linstruction cout << : nous devons encore passer par une chane de type C, ce qui est justement le rle de la mthode GetString(). Nous proposerons une meilleure solution ultrieurement.
Info
524
Le langage C++
GetPrenom() renvoie un objet String constant, or vous ne pouvez pas appeler operator+ sur un objet constant. Pour rsoudre ce problme, vous pouvez surcharger la fonction GetPrenom() et fournir une version non constante :
const String & GetPrenom() const { return sonPrenom; } String & GetPrenom() { return sonPrenom; }
La valeur renvoye comme la fonction membre ne sont plus constantes. Il naurait pas suft de modier simplement la valeur renvoyer puisque le rsultat dune fonction nest pas sufsant pour surcharger une fonction : vous devez galement modier la "constance" de la fonction.
Cot de lagrgation
Les objets agrgs peuvent pnaliser les performances. chaque fois quun objet Employe est cr ou copi, il faut en effet construire tous ses objets String agrgs. Supprimez les barres obliques de toutes les instructions cout du Listing 16.1 an de vous rendre compte du nombre de fois o sont appels les constructeurs. Le Listing 16.3 reprend le programme prcdent en lui ajoutant des instructions dafchage indiquant les instants o le programme cre des objets. Supprimez les barres obliques du Listing 16.1, puis compilez le Listing 16.3. Pour compiler ce listing, dcommentez les lignes 40, 53, 65, 77, 86 et 102 du Listing 16.1.
Info
Chapitre 16
525
14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62:
const String & GetNom() const { return sonNom; } const String & GetAdresse() const { return sonAdresse; } long GetSalaire() const { return sonSalaire; } void SetPrenom(const String & petitNom) { sonPrenom = petitNom; } void SetNom(const String & nomFamille) { sonNom = nomFamille; } void SetAdresse(const String & adresse) { sonAdresse = adresse; } void SetSalaire(long salaire) { sonSalaire = salaire; } private: String sonPrenom; String sonNom; String sonAdresse; long sonSalaire; }; Employe::Employe(): sonPrenom(""), sonNom(""), sonAdresse(""), sonSalaire(0) {} Employe::Employe(char * Prenom, char * Nom, char * adresse, long salaire): sonPrenom(Prenom), sonNom(Nom), sonAdresse(adresse), sonSalaire(salaire) {} Employe::Employe(const Employe & rhs): sonPrenom(rhs.GetPrenom()), sonNom(rhs.GetNom()), sonAdresse(rhs.GetAdresse()), sonSalaire(rhs.GetSalaire()) {} Employe::~Employe() {} Employe & Employe::operator= (const Employe & rhs) { if (this == &rhs) return *this; sonPrenom = rhs.GetPrenom(); sonNom = rhs.GetNom();
526
Le langage C++
63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89:
sonAdresse = rhs.GetAdresse(); sonSalaire = rhs.GetSalaire(); return *this; } int main() { cout << "Cration de JB007...\n"; Employe JB007("J", "B", "Avenue Ian Fleming", 20000); JB007.SetSalaire(20000); cout << "Appel de SetPrenom() avec char *...\n"; JB007.SetPrenom("James"); cout << "Cration du String temporaire Nom...\n"; String Nom("Bond"); JB007.SetNom(Nom); cout << "Nom: "; cout << JB007.GetPrenom().GetString(); cout << " " << JB007.GetNom().GetString(); cout << "\nAdresse: "; cout << JB007.GetAdresse().GetString(); cout << "\nSalaire: "; cout << JB007.GetSalaire(); cout << endl; return 0; }
Chapitre 16
527
Les dclarations de classe sont communes aux Listing 16.1 et 16.2. Toutefois, les instructions cout ne gurent plus en commentaires. Le rsultat a t numrot pour faciliter son analyse. Le message Cration de JB007... est afch par la ligne 71 du Listing 16.3 (ligne 1 du rsultat). Lobjet est alors cr avec quatre paramtres, comme lindique le rsultat, les trois premiers tant des String. Le constructeur de String est appel trois fois, comme prvu. Le prnom du salari est alors initialis (ligne 75) comme lindique le rsultat. Cette variable temporaire est dtruite immdiatement aprs laffectation. La ligne 77 cre un objet String. Le programmeur utilise explicitement lopration faite implicitement par le compilateur linstruction prcdente. Le rsultat indique que le constructeur est appel, mais pas le destructeur. En effet, lobjet ne sera supprim que lorsquil deviendra hors de porte, la n de la fonction. Les objets String de lobjet Employe sont supprimes lorsque lobjet lui-mme devient hors de porte. La chane Nom disparat galement (lignes 81 87).
Info
528
Le langage C++
5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51:
public: Employe(); Employe(char *, char *, char *, long); ~Employe(); Employe(const Employe&); Employe & operator= (const Employe &); const String & GetPrenom() const { return sonPrenom; } const String & GetNom() const { return sonNom; } const String & GetAdresse() const { return sonAdresse; } long GetSalaire() const { return sonSalaire; } void SetPrenom(const String & petitNom) { sonPrenom = petitNom; } void SetNom(const String & nomFamille) { sonNom = nomFamille; } void SetAdresse(const String & adresse) { sonAdresse = adresse; } void SetSalaire(long salaire) { sonSalaire = salaire; } private: String sonPrenom; String sonNom; String sonAdresse; long sonSalaire; }; Employe::Employe(): sonPrenom(""), sonNom(""), sonAdresse(""), sonSalaire(0) {} Employe::Employe(char * Prenom, char * Nom, char * adresse, long salaire): sonPrenom(Prenom), sonNom(Nom), sonAdresse(adresse), sonSalaire(salaire) {} Employe::Employe(const Employe & rhs): sonPrenom(rhs.GetPrenom()), sonNom(rhs.GetNom()), sonAdresse(rhs.GetAdresse()), sonSalaire(rhs.GetSalaire())
Chapitre 16
529
52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89: 90: 91: 92: 93: 94: 95: 96: 97: 98: 99:
{} Employe::~Employe() {} Employe & Employe::operator= (const Employe & rhs) { if (this == &rhs) return *this; sonPrenom = rhs.GetPrenom(); sonNom = rhs.GetNom(); sonAdresse = rhs.GetAdresse(); sonSalaire = rhs.GetSalaire(); return *this; } void FctAffichage(Employe); void rFctAffichage(const Employe&); int main() { Employe JB007("J", "B", "Avenue Ian Fleming", 20000); JB007.SetSalaire(20000); JB007.SetPrenom("James"); String Nom("Bond"); JB007.SetNom(Nom); cout << "Total constructeurs: "; cout << String::CptConstructeur << endl; rFctAffichage (JB007); cout << "Total constructeurs: "; cout << String::CptConstructeur << endl; FctAffichage(JB007); cout << "Total constructeurs: "; cout << String::CptConstructeur << endl; return 0; } void FctAffichage(Employe JB007) { cout << "Nom: "; cout << JB007.GetPrenom().GetString(); cout << " " << JB007.GetNom().GetString(); cout << ".\nAdresse: "; cout << JB007.GetAdresse().GetString(); cout << ".\nSalaire: "; cout << JB007.GetSalaire(); cout << endl;
530
Le langage C++
100: 101: 102: 103: 104: 105: 106: 107: 108: 109: 110: 111: 112:
} void rFctAffichage(const Employe& JB007) { cout << "Nom: "; cout << JB007.GetPrenom().GetString(); cout << " " << JB007.GetNom().GetString(); cout << "\nAdresse: "; cout << JB007.GetAdresse().GetString(); cout << "\nSalaire: "; cout << JB007.GetSalaire(); cout << endl; }
Le rsultat montre quil faut cinq objets String pour crer un objet Employe (ligne 74). Lorsque celui-ci est pass, ligne 82, en rfrence rFctAffichage(), le programme ne cre pas dobjets Employe ni dobjets String supplmentaires (ils sont passs galement par rfrence). On le voit au dbut du rsultat, car que le compte reste 5 et quaucun constructeur nest appel
Chapitre 16
531
la ligne 85, lobjet Employe est pass par valeur la fonction FctAffichage(), ce qui entrane la copie de cet objet et la cration de trois objets String supplmentaires (par appel du constructeur de copie).
Hritage et Dlgation/Agrgation
Il arrive quune classe ait besoin des fonctionnalits dune autre classe. Supposons, par exemple, que vous souhaitiez crer une classe Catalogue. Par dnition, un catalogue comprend des rfrences uniques des objets (des numros de pices, par exemple). Il est donc impossible de crer des doublons dans la classe Catalogue. Vous pourriez faire en sorte que Catalogue contienne une classe ListePieces et quelle dlgue la gestion de la liste chane des pices son objet agrg ListePieces. Il serait galement possible faire driver Catalogue de ListePieces et donc den hriter ses fonctionnalits. Cependant, lhritage public tablit une relation est-un (voir Chapitre 11) et vous devez vous demander si un Catalogue est vraiment un type de ListesPieces. Une faon de rpondre cette question consiste admettre que ListePieces est la classe de base de Catalogue et se poser les questions suivantes : 1. La classe de base contient-elle un ou plusieurs objets qui ne doivent pas gurer dans la classe drive ? ListePieces contient-elle, par exemple, des mthodes qui nont rien faire dans la classe Catalogue ? Si la rponse est oui, renoncez une relation dhritage public. 2. La classe drive peut-elle tre associe plusieurs objets de la classe de base ? Un objet Catalogue, par exemple, peut-il tre avoir besoin de plusieurs objets ListePieces pour accomplir sa tche ? Si la rponse est oui, prfrez lagrgation. 3. Devez-vous hriter de la classe de base pour bncier des fonctions virtuelles et des membres accs protg ? Si la rponse est oui, crez une relation dhritage public ou priv. En fonction de vos rponses, vous pouvez choisir entre lhritage public (relation est-un), lhritage priv (qui sera expliqu plus loin) ou lagrgation (relation a-un).
Quelques termes Nous utilisons ici plusieurs termes nouveaux : Agrgation. Un objet dclar membre dune autre classe est contenu dans cette classe. Aussi appel relation a-un.
532
Le langage C++
Dlgation. Utilisation des membres dune classe agrge pour excuter des mthodes pour le compte de la classe contenante. Implmentation en termes de. Crer une classe partir des fonctionnalits dune autre, sans avoir recours lhritage public (en utilisant, par exemple, un hritage protg ou priv).
Dlgation
Pourquoi ne pas driver Catalogue de ListePieces ? Catalogue nest pas une ListePieces puisque cette dernire reprsente une collection trie dont les lments peuvent se rpter. Un Catalogue, par contre, contient des entres uniques stockes ple-mle. Son cinquime lment ne porte pas le numro 5, mais nimporte quel numro de pice. Il aurait t certainement possible dhriter publiquement de ListePieces, puis de rednir la fonction Inserer() et les oprateurs dindexation pour quils se comportent de faon approprie, mais on modierait alors lessence mme de la classe ListePieces. Il est prfrable de construire un Catalogue sans oprateur dindexation et qui nautorise pas les doublons, et dnir loprateur + an quil concatne deux ensembles. La premire faon de raliser tout cela consiste utiliser lagrgation : la classe Catalogue dlguera la gestion de la liste des pices un objet ListePieces agrg. Cest ce qui est prsent dans le Listing 16.5. Listing 16.5 : Dlgation une ListePieces agrge
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: // Listing16.5 Dlgation une liste agrge #include <iostream> using namespace std; // **************** Piece ************ // Classe de base abstraite de pices class Piece { public: Piece(): saNumPiece(1) {} Piece(int NumPiece): saNumPiece(NumPiece) {} virtual ~Piece(){} int GetNumPiece() const { return saNumPiece; } virtual void Afficher() const = 0; private:
Chapitre 16
533
19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65:
int saNumPiece; }; // implmentation dune fonction virtuelle pure // pour chaner les classes drives void Piece::Afficher() const { cout << "\nNumro de pice: " << saNumPiece << endl; } // **************** Piece Auto ************ class PieceAuto: public Piece { public: PieceAuto(): sonAnneeAuto(97) {} PieceAuto(int annee, int NumPiece); virtual void Afficher() const { Piece::Afficher(); cout << "Millsime: "; cout << sonAnneeAuto << endl; } private: int sonAnneeAuto; }; PieceAuto::PieceAuto(int annee, int NumPiece): sonAnneeAuto(annee), Piece(NumPiece) {}
// **************** Piece Avion ************ class PieceAvion: public Piece { public: PieceAvion(): saRefMoteur(1) {}; PieceAvion (int RefMoteur, int NumPiece); virtual void Afficher() const { Piece::Afficher(); cout << "Rfrence moteur: "; cout << saRefMoteur << endl; }
534
Le langage C++
66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89: 90: 91: 92: 93: 94: 95: 96: 97: 98: 99: 100: 101: 102: 103: 104: 105: 106: 107: 108: 109: 110: 111: 112:
private: int saRefMoteur; }; PieceAvion::PieceAvion (int RefMoteur, int NumPiece): saRefMoteur(RefMoteur), Piece(NumPiece) {} // **************** Noeud Piece ************ class NoeudPiece { public: NoeudPiece(Piece*); ~NoeudPiece(); void SetSuivant(NoeudPiece * noeud) { sonSuivant = noeud; } NoeudPiece * GetSuivant () const; Piece * GetPiece () const; private: Piece *saPiece ; NoeudPiece * sonSuivant; }; // NoeudPiece: implementations... NoeudPiece::NoeudPiece(Piece* pPiece): saPiece (pPiece), sonSuivant(0) {} NoeudPiece::~NoeudPiece() { delete saPiece ; saPiece = 0; delete sonSuivant; sonSuivant = 0; } // Renvoie NULL si pas de NoeudPiece suivant NoeudPiece * NoeudPiece::GetSuivant () const { return sonSuivant; } Piece * NoeudPiece::GetPiece () const {
Chapitre 16
535
113: 114: 115: 116: 117: 118: 119: 120: 121: 122: 123: 124: 125: 126: 127: 128: 129: 130: 131: 132: 133: 134: 135: 136: 137: 138: 139: 140: 141: 142: 143: 144: 145: 146: 147: 148: 149: 150: 151: 152: 153: 154: 155: 156: 157: 158: 159:
// **************** Liste Pieces ************ class ListePieces { public: ListePieces(); ~ListePieces(); // ncessite constructeur de copie et oprateur Egal! void Traiter(void (Piece::*f)()const) const; Piece* Rechercher(int & position, int NumPiece) const; Piece* GetPremier() const; void Inserer(Piece *); Piece* operator[](int) const; int GetTotal() const { return sonTotal; } static ListePieces& GetListePiecesGlobale() { return ListePiecesGlobale; } private: NoeudPiece * pTete; int sonTotal; static ListePieces ListePiecesGlobale; }; ListePieces ListePieces::ListePiecesGlobale;
ListePieces::ListePieces(): pTete(0), sonTotal(0) {} ListePieces::~ListePieces() { delete pTete; } Piece* ListePieces::GetPremier() const { if (pTete)
536
Le langage C++
160: 161: 162: 163: 164: 165: 166: 167: 168: 169: 170: 171: 172: 173: 174: 175: 176: 177: 178: 179: 180: 181: 182: 183: 184: 185: 186: 187: 188: 189: 190: 191: 192: 193: 194: 195: 196: 197: 198: 199: 200: 201: 202: 203: 204: 205: 206:
return pTete->GetPiece (); else return NULL; // interception derreur } Piece * ListePieces::operator[](int indice) const { NoeudPiece* pNoeud = pTete; if (!pTete) return NULL; // interception derreur if (indice > sonTotal) return NULL; // erreur for (int i=0; i < indice; i++) pNoeud = pNoeud->GetSuivant (); return pNoeud->GetPiece (); } Piece* ListePieces::Rechercher( int & position, int NumPiece) const { NoeudPiece * pNoeud = 0; for (pNoeud = pTete, position = 0; pNoeud!=NULL; pNoeud = pNoeud->GetSuivant (), position++) { if (pNoeud->GetPiece ()->GetNumPiece() == NumPiece) break; } if (pNoeud == NULL) return NULL; else return pNoeud->GetPiece (); } void ListePieces::Traiter(void (Piece::*fct)()const) const { if (!pTete) return; NoeudPiece* pNoeud = pTete; do (pNoeud->GetPiece ()->*fct)(); while ((pNoeud = pNoeud->GetSuivant ())!= 0);
Chapitre 16
537
207: 208: 209: 210: 211: 212: 213: 214: 215: 216: 217: 218: 219: 220: 221: 222: 223: 224: 225: 226: 227: 228: 229: 230: 231: 232: 233: 234: 235: 236: 237: 238: 239: 240: 241: 242: 243: 244: 245: 246: 247: 248: 249: 250: 251: 252: 253:
} void ListePieces::Inserer(Piece* pPiece) { NoeudPiece * pNoeud = new NoeudPiece(pPiece); NoeudPiece * pCourant = pTete; NoeudPiece * pSuivant = 0; int Nouveau = pPiece->GetNumPiece(); int Suivant = 0; sonTotal++; if (!pTete) { pTete = pNoeud; return; } // si celui-ci est plus petit que Tete // il devient le nouveau noeud de Tete if (pTete->GetPiece ()->GetNumPiece() > Nouveau) { pNoeud->SetSuivant(pTete); pTete = pNoeud; return; } for (;;) { // si pas de suivant, ajouter celui-ci if (!pCourant->GetSuivant ()) { pCourant->SetSuivant(pNoeud); return; } // sil vient aprs celui-ci et avant le suivant // linsrer ici, sinon lire le suivant pSuivant = pCourant->GetSuivant (); Suivant = pSuivant->GetPiece ()->GetNumPiece(); if (Suivant > Nouveau) { pCourant->SetSuivant(pNoeud); pNoeud->SetSuivant(pSuivant); return; } pCourant = pSuivant;
538
Le langage C++
254: 255: 256: 257: 258: 259: 260: 261: 262: 263: 264: 264a: 265: 266: 267: 268: 269: 270: 271: 272: 273: 274: 275: 276: 277: 278: 279: 280: 281: 282: 283: 284: 285: 286: 287: 288: 289: 290: 291: 292: 293: 294: 295: 296: 297: 298: 299:
} } class Catalogue { public: void Inserer(Piece *); int Existe(int NumPiece); Piece * Lire(int NumPiece); // operator+(const Catalogue &); void AfficherTout() { saListePieces.Traiter(Piece::Afficher); } private: ListePieces saListePieces; }; void Catalogue::Inserer(Piece * nouvPiece ) { int NumPiece = nouvPiece ->GetNumPiece(); int indice; if (!saListePieces.Rechercher(indice, NumPiece)) { saListePieces.Inserer(nouvPiece ); } else { cout << NumPiece << " tait la "; switch (indice) { case 0: cout << "premire "; break; case 1: cout << "deuxime "; break; case 2: cout << "troisime "; break; default: cout << indice + 1 << "e"; } cout << "entre. Refuse!" << endl; } } int Catalogue::Existe(int NumPiece) { int indice; saListePieces.Rechercher(indice, NumPiece); return indice; } Piece * Catalogue::Lire(int NumPiece)
Chapitre 16
539
300: 301: 302: 302a: 303: 304: 305: 306: 307: 308: 309: 310: 311: 312: 313: 314: 315: 316: 317: 318: 319: 320: 321: 322: 323: 324: 325: 326: 327: 328: 329: 330: 331: 332: 333: 334: 335: 336: 337: 338: 339: 340: 341: 342:
int main() { Catalogue cat; Piece * pPiece = 0; int NumPiece; int valeur; int choix = 99; while (choix!= 0) { cout << "(0)Quitter (1)Auto (2)Avion - Votre choix: "; cin >> choix; if (choix!= 0) { cout << "Entrez un numro de pice: "; cin >> NumPiece; if (choix == 1) { cout << "Millsime? "; cin >> valeur; pPiece = new PieceAuto(valeur, NumPiece); } else { cout << "Rfrence moteur? "; cin >> valeur; pPiece = new PieceAvion(valeur, NumPiece); } cat.Inserer(pPiece); } } cat.AfficherTout(); return 0; }
540
Le langage C++
Info
Certains compilateurs ne peuvent traiter la ligne 264, bien quelle soit lgale en C++. Si votre compilateur signale une erreur, remplacez la ligne par :
264: void AfficherTout() { laListePieces.Traiter(&Piece::Afficher); }
(Remarquez lajout de lesperluette devant Piece::Afficher.) Si le problme est rsolu de cette faon, appelez immdiatement le fournisseur de votre compilateur pour protester. Une nouvelle classe Catalogue est dclare aux lignes 257 267. Elle dlgue la gestion de sa liste la liste ListePieces (ligne 265). En dautres termes, Catalogue est implmente en termes de ListePieces. Les clients de Catalogue nont pas directement accs ListePieces car elle est dclare comme membre priv. Laccs cette classe passe par linterface de Catalogue, ce qui modie considrablement son comportement. La mthode Catalogue::Inserer(), par exemple, ne permet pas de dupliquer les entres dans ListePieces. Limplmentation de Catalogue::Inserer() est ralise partir de la ligne 269. La valeur saNumPiece de la pice passe en paramtre est transmise la mthode
Chapitre 16
541
Rechercher() de ListePieces la ligne 274. Si elle na pas t trouve, la nouvelle pice est insre dans la liste (ligne 276) ; sinon, un message derreur apparat ( partir de la ligne 280). Vous remarquerez que Catalogue ralise linsertion en appelant la mthode Inserer() sur sa variable membre saListePieces, qui est une ListePieces. En fait, les mcanismes dinsertion et de gestion de la liste chane, ainsi que les oprations de recherche et dextraction de la liste chane sont encapsuls dans le membre ListePieces agrg Catalogue. Il ny a aucune raison que Catalogue reproduise ce code ; elle peut tirer parti dune interface bien conue. Cest lessence de la rutilisabilit du code en C++ : Catalogue peut rutiliser le code de ListePieces, ce qui vite au concepteur de Catalogue de devoir connatre les dtails dimplmentation de ListePieces. Linterface de cette dernire (cest--dire sa dclaration) fournit toutes les informations ncessaires au concepteur de Catalogue.
Hritage priv
Pour accder aux membres protgs de ListePieces (qui nen possde pas ici) ou pour rednir des mthodes de ListePieces, Catalogue doit hriter publiquement de ListePieces. Catalogue ntant pas un objet ListePieces et comme vous ne souhaitez pas exposer lensemble des fonctionnalits de ListePieces aux clients de Catalogue, vous devez utiliser un hritage priv. Lhritage priv permet en effet dhriter dune autre classe tout en maintenant prive la partie interne de cette classe pour la classe drive. La premire chose savoir sur lhritage priv est que toutes les variables et les fonctions membres de la classe de base sont traites comme si elles avaient t dclares prives, quel que soit leur dclaration daccs dans la classe de base. Par consquent, chaque fonction hrite de ListePieces est inaccessible toute fonction qui nest pas membre de Catalogue. Ce point est essentiel : lhritage priv nimplique pas lhritage de linterface, seulement de limplmentation. La classe ListePieces est invisible pour les clients de Catalogue. La totalit de son interface leur est inaccessible et ils ne peuvent appeler aucune de ses mthodes. Ils peuvent appeler les mthodes de Catalogue qui, elles, ont accs tous les objets de ListePieces car Catalogue drive de ListePieces. Le point important, ici, est que Catalogue nest pas une ListePieces comme cela aurait t le cas avec un hritage public. Catalogue est implmente en termes dune ListePieces, comme cela aurait t le cas avec une agrgation. Lhritage priv est simplement une question de confort.
542
Le langage C++
La Listing 16.6 montre comment utiliser lhritage priv en rcrivant la classe Catalogue pour quelle hrite en priv de ListePieces. Listing 16.6 : Hritage priv
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: //Listing16.6 dmonstration de lhritage priv #include <iostream> using namespace std; // **************** Piece ************ // Classe de base abstraite de pices class Piece { public: Piece():saNumPiece(1) {} Piece(int NumPiece): saNumPiece(NumPiece){} virtual ~Piece(){} int GetNumPiece() const { return saNumPiece; } virtual void Afficher() const = 0; private: int saNumPiece; }; // implmentation dune fonction virtuelle pure // pour chaner les classes drives void Piece::Afficher() const { cout << "\nNumro de pice: " << saNumPiece << endl; } // **************** Pice Auto ************ class PieceAuto: public Piece { public: PieceAuto():sonAnneeAuto(97){} PieceAuto(int annee, int NumPiece); virtual void Afficher() const { Piece::Afficher(); cout << "Millsime: "; cout << sonAnneeAuto << endl; } private:
Chapitre 16
543
42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88:
// **************** Pice Avion************ class PieceAvion: public Piece { public: PieceAvion():saRefMoteur(1){}; PieceAvion(int RefMoteur, int NumPiece); virtual void Afficher() const { Piece::Afficher(); cout << "Rfrence moteur: "; cout << saRefMoteur << endl; } private: int saRefMoteur; }; PieceAvion::PieceAvion (int RefMoteur, int NumPiece): saRefMoteur(RefMoteur), Piece(NumPiece) {} // **************** Noeud Pice ************ class NoeudPiece { public: NoeudPiece (Piece*); ~NoeudPiece(); void SetSuivant(NoeudPiece * noeud) { sonSuivant = noeud; } NoeudPiece * GetSuivant () const; Piece * GetPiece () const; private: Piece *saPiece ; NoeudPiece * sonSuivant; }; // NoeudPiece: implmentations...
544
Le langage C++
89: 90: 91: 92: 93: 94: 95: 96: 97: 98: 99: 100: 101: 102: 103: 104: 105: 106: 107: 108: 109: 110: 111: 112: 113: 114: 115: 116: 117: 118: 119: 120: 121: 122: 123: 124: 125: 126: 127: 128: 129: 130: 131: 132: 133: 134: 135:
NoeudPiece::NoeudPiece(Piece* pPiece): saPiece (pPiece), sonSuivant(0) {} NoeudPiece::~NoeudPiece() { delete saPiece ; saPiece = 0; delete sonSuivant; sonSuivant = 0; } // Renvoie NULL si pas de NoeudPiece suivant NoeudPiece * NoeudPiece::GetSuivant () const { return sonSuivant; } Piece * NoeudPiece::GetPiece () const { if (saPiece ) return saPiece ; else return NULL; // erreur }
// **************** Liste Pices ************ class ListePieces { public: ListePieces(); ~ListePieces(); // ncessite constructeur de copie et oprateur gal! void Traiter(void (Piece::*f)()const) const; Piece* Rechercher(int & position, int NumPiece) const; Piece* GetPremier() const; void Inserer(Piece *); Piece* operator[](int) const; int GetTotal() const { return sonTotal; } static ListePieces& GetListePiecesGlobale() { return ListePiecesGlobale; }
Chapitre 16
545
136: 137: 138: 139: 140: 141: 142: 143: 144: 145: 146: 147: 148: 149: 150: 151: 152: 153: 154: 155: 156: 157: 158: 159: 160: 161: 162: 163: 164: 165: 166: 167: 168: 169: 170: 171: 172: 173: 174: 175: 176: 177: 178: 179: 179a: 180: 181:
private: NoeudPiece * pTete; int sonTotal; static ListePieces ListePiecesGlobale; }; ListePieces ListePieces::ListePiecesGlobale;
ListePieces::ListePieces(): pTete(0), sonTotal(0) {} ListePieces::~ListePieces() { delete pTete; } Piece* ListePieces::GetPremier() const { if (pTete) return pTete->GetPiece (); else return NULL; // interception derreur } Piece * ListePieces::operator[](int indice) const { NoeudPiece* pNoeud = pTete; if (!pTete) return NULL; // interception derreur if (indice > sonTotal) return NULL; // erreur for (int i=0; i < indice; i++) pNoeud = pNoeud->GetSuivant (); return } Piece* ListePieces::Rechercher(int & position, int NumPiece) const { NoeudPiece * pNoeud = 0; pNoeud->GetPiece ();
546
Le langage C++
182: 183: 184: 185: 186: 187: 188: 189: 190: 191: 192: 193: 194: 195: 196: 197: 198: 199: 200: 201: 202: 203: 204: 205: 206: 207: 208: 209: 210: 211: 212: 213: 214: 215: 216: 217: 218: 219: 220: 221: 222: 223: 224: 225: 226: 227: 228:
for (pNoeud = pTete, position = 0; pNoeud != NULL; pNoeud = pNoeud->GetSuivant (), position++) { if (pNoeud->GetPiece ()->GetNumPiece() == NumPiece) break; } if (pNoeud == NULL) return NULL; else return pNoeud->GetPiece(); } void ListePieces::Traiter(void (Piece::*fct)()const) const { if (!pTete) return; NoeudPiece* pNoeud = pTete; do (pNoeud->GetPiece()->*fct)(); while ((pNoeud = pNoeud->GetSuivant ())!= 0); } void ListePieces::Inserer(Piece* pPiece) { NoeudPiece * pNoeud = new NoeudPiece(pPiece); NoeudPiece * pCourant = pTete; NoeudPiece * pSuivant = 0; int Nouveau = pPiece->GetNumPiece(); int Suivant = 0; sonTotal++; if (!pTete) { pTete = pNoeud; return; } // si celui-ci est plus petit que Tete // il devient le nouveau noeud de Tete if (pTete->GetPiece ()->GetNumPiece() > Nouveau) { pNoeud->SetSuivant(pTete); pTete = pNoeud; return; }
Chapitre 16
547
229: 230: 231: 232: 233: 234: 235: 236: 237: 238: 239: 240: 241: 242: 243: 244: 245: 246: 247: 248: 249: 250: 251: 252: 253: 254: 255: 256: 257: 258: 259: 260: 261: 262: 263: 264: 265: 266: 267: 268: 269: 270: 271: 272: 273: 274: 275:
for (;;) { // si pas de suivant, ajouter celui-ci if (!pCourant->GetSuivant ()) { pCourant->SetSuivant(pNoeud); return; } // sil vient aprs celui-ci et avant le suivant // linsrer ici, sinon lire le suivant pSuivant = pCourant->GetSuivant(); Suivant = pSuivant->GetPiece()->GetNumPiece(); if (Suivant > Nouveau) { pCourant->SetSuivant(pNoeud); pNoeud->SetSuivant(pSuivant); return; } pCourant = pSuivant; } } class Catalogue: private ListePieces { public: void Inserer(Piece *); int Existe(int NumPiece); Piece * Lire(int NumPiece); operator+(const Catalogue &); void AfficherTout() { Traiter(Piece::Afficher); } private: }; void Catalogue::Inserer(Piece * nouvPiece ) { int NumPiece = nouvPiece ->GetNumPiece(); int indice; if (!Rechercher(indice, NumPiece)) { ListePieces::Inserer(nouvPiece); } else { cout << NumPiece << " tait la ";
548
Le langage C++
276: 277: 278: 279: 280: 281: 282: 283: 284: 285: 286: 287: 288: 289: 290: 291: 292: 293: 294: 295: 296: 297: 298: 299: 300: 301: 302: 303: 304: 305: 306: 307: 308: 309: 310: 311: 312: 313: 314: 315: 316: 317: 318: 319: 320: 321: 322:
switch (indice) { case 0: cout << case 1: cout << case 2: cout << default: cout << } cout << "entre. } }
"premire "; break; "deuxime"; break; "troisime"; break; indice + 1 << "e"; Refuse!" << endl;
int Catalogue::Existe(int NumPiece) { int indice; Rechercher(indice, NumPiece); return indice; } Piece * Catalogue::Lire(int NumPiece) { int indice; return (Rechercher(indice, NumPiece)); } int main() { Catalogue cat; Piece * pPiece = 0; int NumPiece; int valeur; int choix = 99; while (choix!= 0) { cout << "(0)Quitter (1)Auto (2)Avion - Votre choix: "; cin >> choix; if (choix != 0) { cout << "Entrez un numro de pice: "; cin >> NumPiece; if (choix == 1) { cout << "Millsime? "; cin >> valeur;
Chapitre 16
549
323: 324: 325: 326: 327: 328: 329: 330: 331: 332: 333: 334: 335: 336:
pPiece = new PieceAuto(valeur, NumPiece); } else { cout << "Rfrence moteur?: "; cin >> valeur; pPiece = new PieceAvion(valeur, NumPiece); } cat.Inserer(pPiece); } } cat.AfficherTout(); return 0; }
: 1
: 1
: 1
: 0
Dans ce programme, nous navons modi que linterface de Catalogue et la fonction principale du listing prcdent. la ligne 253, Catalogue est dclare comme drivant en priv de ListePieces. Dsormais, Catalogue na donc plus besoin dun membre ListePieces.
550
Le langage C++
La fonction AfficherTout() de Catalogue (ligne 160) appelle la fonction Traiter() de ListePieces en passant le pointeur appropri la fonction membre de la classe Piece. AfficherTout() agit comme linterface publique de Traiter() en fournissant les informations adquates mais en empchant les classes clientes dappeler directement Traiter(). Bien que ListePieces autorise le passage dautres fonctions en paramtre de Traiter(), Catalogue ne le fait pas. La fonction Inserer() a galement t modie (lignes 164 284). la ligne 269, Trouver() est dsormais appele directement, car elle a t hrite de la classe de base. Pour viter un appel rcursif sans n de la fonction Inserer() sur elle-mme, lappel de cette fonction doit tre pleinement quali (ligne 271). En rsum, les mthodes de Catalogue peuvent appeler directement celles de ListesPieces. La seule exception concerne les cas o Catalogue a rednit une mthode et quil faut appeler la version de ListesPieces : en ce cas, cet appel doit tre pleinement quali. Lhritage priv permet Catalogue dhriter de ce quelle peut utiliser, tout en permettant de contrler laccs Insert() et aux autres mthodes auxquelles les classes clientes ne doivent pas avoir directement accs.
Faire
Utiliser lhritage public lorsque lobjet
Ne pas faire
Utiliser lhritage priv lorsque vous devez
tionnalits une autre classe et que vous navez pas besoin daccder ses membres protgs.
Utiliser lhritage priv lorsque vous devez
utiliser plusieurs objets de la classe de base. En ce cas, utilisez lagrgation. Si Catalogue avait besoin de deux ListePieces, par exemple, vous ne pourriez pas utiliser lhritage priv.
Utiliser lhritage public lorsque les membres
implmenter une classe en termes dune autre et que vous devez avoir accs aux membres protgs de la classe de base.
Classes amies
Il peut parfois tre intressant de regrouper des classes dans un ensemble. Par exemple, NoeudPiece et ListePieces tant troitement associes, il aurait t pratique que ListePieces puisse lire directement le pointeur de Piece de NoeudPiece (saPiece). Il nest pas souhaitable que saPiece soit publique ou mme protge car il sagit dun dtail dimplmentation de NoeudPiece et quelle doit donc rester prive. En revanche, vous voulez que ListePieces puisse y accder directement.
Chapitre 16
551
La solution consiste dnir une classe amie, ce qui permettra cette dernire daccder aux donnes et aux fonctions prives de votre classe. Ceci tend donc linterface de votre classe pour y inclure la classe amie. Lorsquune classe dclare quune autre classe est son amie, toutes les donnes et fonctions membres de la classe dclarante sont publiques pour la classe amie. Si, par exemple, NoeudPiece dclare que ListePieces est son amie, toutes les donnes et mthodes membres de NoeudPiece seront publiques pour ListePieces. Il faut bien noter que lamiti ne peut pas tre transfre. Elle nest pas transitive : les amis de mes amis ne sont pas toujours mes amis. Lamiti nest pas non plus transmise par hritage : si vous tes mon ami et que je veux partager mes secrets avec vous, cela ne signie pas que je veuille le faire avec vos enfants. Elle nest pas non plus commutative : si la classe A est amie de B, B ne devient pas de ce fait une amie de A. Vous voulez peut-tre me dire vos secrets, mais cela nimplique pas que je veuille vous dire les miens. Pour dclarer une classe amie, utilisez le mot-cl friend :
class ClasseUn { public: friend class ClasseAmie; . . .
Dans cet exemple, ClasseUn a dclar ClasseAmie comme son amie. Cette dernire a donc un accs total aux membres de ClasseUn. Dans le Listing 16.6, ListePieces devient une classe amie de NoeudPiece, mais la rciproque nest pas vraie. Listing 16.7 : Implmentation dune classe amie
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: //Listing16.7 Implmentation dune classe amie #include <iostream> using namespace std; // **************** Pice ************ // Classe de base abstraite de pices class Piece { public: Piece():saNumPiece(1) {}
552
Le langage C++
12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58:
Piece(int NumPiece): saNumPiece(NumPiece){} virtual ~Piece(){} int GetNumPiece() const { return saNumPiece; } virtual void Afficher() const = 0; private: int saNumPiece; }; // implmentation dune fonction virtuelle pure // pour chaner les classes drives void Piece::Afficher() const { cout << "\nNumro de pice: "; cout << saNumPiece << endl; } // **************** Pice Auto ************ class PieceAuto: public Piece { public: PieceAuto():sonAnneeAuto(97){} PieceAuto(int annee, int NumPiece); virtual void Afficher() const { Piece::Afficher(); cout << "Millsime: "; cout << sonAnneeAuto << endl; } private: int sonAnneeAuto; }; PieceAuto::PieceAuto(int annee, int NumPiece): sonAnneeAuto(annee), Piece(NumPiece) {}
Chapitre 16
553
59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89: 90: 91: 92: 93: 94: 95: 96: 97: 98: 99: 100: 101: 102: 103: 104: 105:
PieceAvion(int RefMoteur, int NumPiece); virtual void Afficher() const { Piece::Afficher(); cout << "Rfrence moteur: "; cout << saRefMoteur << endl; } private: int saRefMoteur; }; PieceAvion::PieceAvion(int RefMoteur, int NumPiece): saRefMoteur(RefMoteur), Piece(NumPiece) {} // **************** Noeud Pice ************ class NoeudPiece { public: friend class ListePieces; NoeudPiece (Piece*); ~NoeudPiece(); void SetSuivant(NoeudPiece * noeud) { sonSuivant = noeud; } NoeudPiece * GetSuivant () const; Piece * GetPiece () const; private: Piece *saPiece ; NoeudPiece * sonSuivant; };
NoeudPiece::NoeudPiece(Piece* pPiece): saPiece(pPiece), sonSuivant(0) {} NoeudPiece::~NoeudPiece() { delete saPiece ; saPiece = 0; delete sonSuivant; sonSuivant = 0; } // Renvoie NULL si pas de NoeudPiece suivant
554
Le langage C++
106: 107: 108: 109: 110: 111: 112: 113: 114: 115: 116: 117: 118: 119: 120: 121: 122: 123: 124: 125: 126: 127: 128: 129: 130: 131: 132: 133: 134: 135: 136: 137: 138: 139: 140: 141: 142: 143: 144: 145: 146: 147: 148: 149: 150: 151: 152:
NoeudPiece * NoeudPiece::GetSuivant () const { return sonSuivant; } Piece * NoeudPiece::GetPiece () const { if (saPiece ) return saPiece ; else return NULL; // erreur }
// **************** Liste Pices ************ class ListePieces { public: ListePieces(); ~ListePieces(); // ncessite constructeur de copie et oprateur Egal! void Traiter(void (Piece::*f)()const) const; Piece* Rechercher(int & position, int NumPiece) const; Piece* GetPremier() const; void Inserer(Piece *); Piece* operator[](int) const; int GetTotal() const { return sonTotal; } static ListePieces& GetListePiecesGlobale() { return ListePiecesGlobale; } private: NoeudPiece * pTete; int sonTotal; static ListePieces ListePiecesGlobale; }; ListePieces ListePieces::ListePiecesGlobale; // Listes: implmentations... ListePieces::ListePieces(): pTete(0), sonTotal(0) {} ListePieces::~ListePieces()
Chapitre 16
555
153: 154: 155: 156: 157: 158: 159: 160: 161: 162: 163: 164: 165: 166: 167: 168: 169: 170: 171: 172: 173: 174: 175: 176: 177: 178: 179: 180: 181: 182: 183: 184: 185: 186: 187: 188: 189: 190: 191: 192: 193: 194: 195: 196: 197: 198: 199:
{ delete pTete; } Piece* ListePieces::GetPremier() const { if (pTete) return pTete->saPiece ; else return NULL; // interception derreur } Piece * ListePieces::operator[](int indice) const { NoeudPiece* pNoeud = pTete; if (!pTete) return NULL; // interception derreur if (indice > sonTotal) return NULL; // erreur for (int i=0; i < indice; i++) pNoeud = pNoeud->sonSuivant; return } Piece* ListePieces::Rechercher(int & position, int NumPiece) const { NoeudPiece * pNoeud = 0; for (pNoeud = pTete, position = 0; pNoeud!=NULL; pNoeud = pNoeud->sonSuivant, position++) { if (pNoeud->saPiece ->GetNumPiece() == NumPiece) break; } if (pNoeud == NULL) return NULL; else return pNoeud->saPiece ; } void ListePieces::Traiter(void (Piece::*func)()const) const { if (!pTete) pNoeud->saPiece ;
556
Le langage C++
200: 201: 202: 203: 204: 205: 206: 207: 208: 209: 210: 211: 212: 213: 214: 215: 216: 217: 218: 219: 220: 221: 222: 223: 224: 225: 226: 227: 228: 229: 230: 231: 232: 233: 234: 235: 236: 237: 238: 239: 240: 241: 242: 243: 244: 245: 246:
return; NoeudPiece* pNoeud = pTete; do (pNoeud->saPiece ->*func)(); while (pNoeud = pNoeud->sonSuivant); } void ListePieces::Inserer(Piece* pPiece) { NoeudPiece * pNoeud = new NoeudPiece(pPiece); NoeudPiece * pCourant = pTete; NoeudPiece * pSuivant = 0; int Nouveau = pPiece->GetNumPiece(); int Suivant = 0; sonTotal++; if (!pTete) { pTete = pNoeud; return; } // si celui-ci est plus petit que Tete // il devient le nouveau noeud de Tete if (pTete->saPiece ->GetNumPiece() > Nouveau) { pNoeud->sonSuivant = pTete; pTete = pNoeud; return; } for (;;) { // si pas de suivant, ajouter celui-ci if (!pCourant->sonSuivant) { pCourant->sonSuivant = pNoeud; return; } // sil vient aprs celui-ci et avant le suivant // linsrer ici, sinon lire le suivant pSuivant = pCourant->sonSuivant; Suivant = pSuivant->saPiece ->GetNumPiece(); if (Suivant > Nouveau) {
Chapitre 16
557
247: 248: 249: 250: 251: 252: 253: 254: 255: 256: 257: 258: 259: 260: 261: 262: 263: 264: 265: 266: 267: 268: 269: 270: 271: 272: 273: 274: 275: 276: 277: 278: 279: 280: 281: 282: 283: 284: 285: 286: 287: 288: 289: 290: 291: 292: 293:
pCourant->sonSuivant = pNoeud; pNoeud->sonSuivant = pSuivant; return; } pCourant = pSuivant; } } class Catalogue: private ListePieces { public: void Inserer(Piece *); int Existe(int NumPiece); Piece * Lire(int NumPiece); operator+(const Catalogue &); void AfficherTout() { Traiter(Piece::Afficher); } private: }; void Catalogue::Inserer(Piece * nouvPiece ) { int NumPiece = nouvPiece->GetNumPiece(); int indice; if (!Rechercher(indice, NumPiece)) ListePieces::Inserer(nouvPiece); else { cout << NumPiece << " tait la "; switch (indice) { case 0: cout << "premire "; break; case 1: cout << "deuxime "; break; case 2: cout << "troisime "; break; default: cout << indice + 1 << "e"; } cout << "entre. Refuse!" << endl; } } int Catalogue::Existe(int NumPiece) { int indice; Rechercher(indice, NumPiece); return indice; }
558
Le langage C++
294: 295: 296: 297: 298: 299: 300: 301: 302: 303: 304: 305: 306: 307: 308: 309: 310: 311: 312: 313: 314: 315: 316: 317: 318: 319: 320: 321: 322: 323: 324: 325: 326: 327: 328: 329: 330: 331: 332: 333: 334: 335:
Piece * Catalogue::Lire(int NumPiece) { int indice; return (Rechercher(indice, NumPiece)); } int main() { Catalogue cat; Piece * pPiece = 0; int NumPiece; int valeur; int choix = 99; while (choix!= 0) { cout << "(0)Quitter (1)Auto (2)Avion - Votre choix: "; cin >> choix; if (choix != 0) { cout << "Entrez un numro de pice: "; cin >> NumPiece; if (choix == 1) { cout << "Millsime? "; cin >> valeur; pPiece = new PieceAuto(valeur, NumPiece); } else { cout << "Rfrence moteur?: "; cin >> valeur; pPiece = new PieceAvion(valeur, NumPiece); } cat.Inserer(pPiece); } } cat.AfficherTout(); return 0; }
Chapitre 16
559
(0)Quitter (1)Auto (2)Avion - Votre choix Entrez un numro de pice : 4434 Millsime ? 93 (0)Quitter (1)Auto (2)Avion - Votre choix Entrez un numro de pice : 1234 Millsime ? 94 1234 tait la premire entre. Refuse ! (0)Quitter (1)Auto (2)Avion - Votre choix Entrez un numro de pice : 2345 Millsime ? 93 (0)Quitter (1)Auto (2)Avion - Votre choix Numro de pice : 1234 Millsime : 94 Numro de pice : 2345 Millsime : 93 Numro de pice : 4434 Millsime : 93
: 1
: 1
: 1
: 0
la ligne 79, la classe ListePieces est dclare amie de la classe NoeudPiece. La dclaration de la relation damiti gure dans la section publique, mais elle pourrait tre place nimporte o dans la dclaration de classe sans modier pour autant la signication de linstruction. Grce cette amiti, tous les membres et mthodes privs de NoeudPiece seront disponibles depuis nimporte quelle fonction membre de la classe ListePieces. la ligne 157, limplmentation de la fonction membre GetPremier() rete cette modication. Au lieu de renvoyer pTete->GetPiece() , elle peut dsormais se contenter de renvoyer le membre priv pTete->saPiece. De mme, la fonction Inserer() crit dsormais pNoeud->sonSuivant = pTete, au lieu de pNoeud->SetSuivant(pTete). Il sagit, bien sr, de modications triviales qui ne justient pas que lon ait besoin que ListePieces soit amie de NoeudPiece, mais lobjectif, ici, est simplement dillustrer le fonctionnement du mot-cl friend. Les dclarations de classes amies doivent tre utilises avec la plus grande prudence. Si deux classes sont troitement lies et que lune a frquemment besoin des donnes de lautre, tablir une relation damiti constitue une bonne solution. Mais il faut lutiliser avec parcimonie ; il est parfois tout aussi simple de se servir de mthodes daccs publiques. En outre, cela vous vite de devoir recompiler les deux classes lorsque lune delle est modie.
560
Le langage C++
Info
Vous entendrez souvent les dbutants en C++ se plaindre que les dclarations damiti sapent lencapsulation des donnes, qui est si importante en programmation oriente objet. Ce nest pas ncessairement vrai : une dclaration damiti permet dintgrer la partie amie linterface de la classe, ce qui ne pertube pas forcment lencapsulation. Lutilisation dune amie, par contre, implique de sengager maintenir en parallle les deux classes, ce qui peut rduire la modularit du code.
Mot-cl friend Pour dclarer une classe amie dune autre classe, il suft de faire prcder son nom du mot-cl friend dans la classe qui donne les droits daccs. En dautres termes, je peux dclarer que vous tes mon ami, mais vous ne pouvez pas le dcider vous-mme. Exemple
class NoeudPiece{ public: friend class ListePieces; // ListePieces est devenue amie de NoeudPiece };
Fonctions amies
Nous venons de voir quune dclaration de classe amie donne un accs total. Parfois, on ne ne souhaite pas accorder ce niveau daccs toute une classe, mais uniquement une ou deux de ses mthodes. Pour ce faire, on peut dclarer amies les mthodes membres de lautre classe au lieu de dclarer toute celle-ci comme amie. En fait, nimporte quelle fonction, quelle soit membre dune classe ou non, peut tre dclare amie.
Info
Chapitre 16
561
Cependant, vous ne pouvez pas crer une chane C pour lui ajouter ensuite un objet String, comme ici :
char uneChaineC[] = "Bonjour"; String unString(" les amis"); String unString2 = uneChaineC + unString; // erreur !
Les chanes C ne disposent pas dun oprateur + surcharg. Comme vous lavez vu au Chapitre 10, lexpression uneChaineC + unString; revient crire uneChaineC.operator+(unString). Comme vous ne pouvez pas appeler la mthode operator+() sur une chane C, cela produira une erreur de compilation. Pour rsoudre ce problme, vous pouvez dclarer une fonction amie dans String, qui surcharge operator+() pour quelle prenne deux paramtres de type String. La chane C sera alors convertie en objet String laide du constructeur appropri, puis operator+() sera appele avec les deux objets. Pour plus de dtails, examinez le Listing 16.8. Listing 16.8 : Un oprateur + amical
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: //Listing16.8 - oprateurs amis #include <iostream> #include <String.h> using namespace std; // Classe String rudimentaire class String { public: // constructeurs String(); String(const char *const); String(const String &); ~String(); // oprateurs surcharges char & operator[](int indice); char operator[](int indice) const; String operator+(const String&); friend String operator+(const String&, const String&); void operator+=(const String&); String & operator= (const String &); // mthodes daccs gnrales int GetLongueur()const { return saLongueur; } const char * GetString() const { return saChaine; }
562
Le langage C++
27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73:
private: String (int); // constructeur priv char * saChaine; unsigned short saLongueur; }; // le constructeur par dfaut cre des chanes de 0 octet String::String() { saChaine = new char[1]; saChaine[0] = \0; saLongueur=0; // cout << "\tConstructeur par dfaut de String" << endl; // CptConstructeur++; } // constructeur (utilitaire) priv, utilis seulement par // les mthodes de la classe pour crer une nouvelle chane // de la taille requise, contenant des caractres nuls. String::String(int longueur) { saChaine = new char[longueur+1]; for (int i = 0; i <= longueur; i++) saChaine[i] = \0; saLongueur = longueur; // cout << "\tConstructeur de String(int)" << endl; // CptConstructeur++; } // Convertit un tableau de caractres en String String::String(const char * const chaineC) { saLongueur = strlen(chaineC); saChaine = new char[saLongueur+1]; for (int i = 0; i < saLongueur; i++) saChaine[i] = chaineC[i]; saChaine[saLongueur]=\0; // cout << "\tConstructeur de String(char*)" << endl; // CptConstructeur++; } // constructeur de copie String::String (const String & rhs) { saLongueur=rhs.GetLongueur(); saChaine = new char[saLongueur+1];
Chapitre 16
563
74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89: 90: 91: 92: 93: 94: 95: 96: 97: 98: 99: 100: 101: 102: 103: 104: 105: 106: 107: 108: 109: 110: 111: 112: 113: 114: 115: 116: 117: 118: 119: 120:
for (int i = 0; i < saLongueur;i++) saChaine[i] = rhs[i]; saChaine[saLongueur] = \0; // cout << "\tConstructeur de String(String&)" << endl; // CptConstructeur++; } // destructeur, libre la mmoire String::~String () { delete [] saChaine; saLongueur = 0; // cout << "\tDestructeur de String" << endl; } // oprateur daffectation, libre la mmoire existante // puis copie la chane et la taille String& String::operator=(const String & rhs) { if (this == &rhs) return *this; delete [] saChaine; saLongueur=rhs.GetLongueur(); saChaine = new char[saLongueur+1]; for (int i = 0; i < saLongueur; i++) saChaine[i] = rhs[i]; saChaine[saLongueur] = \0; return *this; // cout << "\toperator= de String" << endl; } // oprateur dindexation non constant, renvoie // une rfrence sur caractre pour quil puisse // tre modifi! char & String::operator[](int indice) { if (indice > saLongueur) return saChaine[saLongueur-1]; else return saChaine[indice]; } // oprateur dindexation constant pour utilisation // sur des objets const (voir constructeur de copie!) char String::operator[](int indice) const { if (indice > saLongueur)
564
Le langage C++
121: 122: 123: 124: 125: 126: 127: 128: 129: 130: 131: 132: 133: 134: 135: 136: 137: 138: 139: 140: 141: 142: 143: 144: 145: 146: 147: 148: 149: 150: 150a: 151: 152: 153: 154: 155: 156: 157: 158: 159: 160: 161: 162: 163: 164: 165: 166:
return saChaine[saLongueur-1]; else return saChaine[indice]; } // cre une nouvelle chane en ajoutant // la chane actuelle rhs String String::operator+(const String& rhs) { int longueurTotale = saLongueur+rhs.GetLongueur(); String temp(longueurTotale); int i, j; for (i = 0; i < saLongueur; i++) temp[i] = saChaine[i]; for (j=0, i=saLongueur; j < rhs.GetLongueur(); j++, i++) temp[i] = rhs[j]; temp[longueurTotale]=\0; return temp; } // cre une nouvelle chane en ajoutant // une chane une autre String operator+(const String& lhs, const String& rhs) { int longueurTotale = lhs.GetLongueur()+rhs.GetLongueur(); String temp(longueurTotale); int i, j; for (i = 0; i < lhs.GetLongueur(); i++) temp[i] = lhs[i]; for (j=0, i=lhs.GetLongueur(); j<rhs.GetLongueur() ; j++, i++) temp[i] = rhs[j]; temp[longueurTotale]=\0; return temp; } int main() { String s1("String Une "); String s2("String Deux "); char *c1 = { "Chaine C Une " }; String s3; String s4; String s5; cout << "s1 : " << s1.GetString() << endl; cout << "s2 : " << s2.GetString() << endl;
Chapitre 16
565
cout << "c1 : s3 = s1+s2; cout << "s3 : s4 = s1+c1; cout << "s4 : s5 = c1+s2; cout << "s5 : return 0; }
" << c1 << endl; " << s3.GetString() << endl; " << s4.GetString() << endl; " << s5.GetString() << endl;
Hormis limplmentation de operator+, les mthodes de la classe String nont pas t modies par rapport au Listing 16.1. La ligne 20 surcharge operator+ pour quelle prenne en paramtre deux rfrences constantes de String et quelle renvoie un String. Cette fonction est dclare amie. Vous remarquerez que cette version de operator+ nest membre daucune classe. Cette fonction est dclare dans la classe String uniquement pour quelle puisse devenir son amie. En outre, comme elle est dclare, vous navez pas besoin dun prototype de fonction. Limplmentation de cette version de operator+ apparat de la ligne 143 la ligne 154. Cette fonction ressemble la mthode operator+ prcdente, sauf quelle attend deux String en paramtres et quelle y accde grce leurs mthodes daccs publiques. La ligne 172 du programme de test montre que lon peut maintenant appeler cet oprateur sur une chane C !
Les fonctions amies On dclare une fonction amie laide du mot-cl friend suivi de la spcication complte de la fonction. Le fait de dclarer une fonction amie ne lui donne pas accs votre pointeur this, mais lui permet daccder lensemble de vos donnes et de vos fonctions membres prives et protges. Exemple
566
Le langage C++
// classe est dclare amie friend void ListePieces::Inserer(Piece *); // une fonction globale est dclare amie friend int Fonction(); //... };
Surcharge de operator<<()
La classe String est dsormais prte utiliser cout comme nimporte quel autre type standard. Jusqu prsent, pour afcher un objet String, il fallait crire une instruction comme celle-ci :
cout << uneString.GetString();
Pour cela, vous devez rednir operator<<(). Au Chapitre 17, vous apprendrez grer les ux dentre-sortie mais, pour le moment, nous allons voir avec le Listing 16.9 comment surcharger operator<<() laide dune fonction amie. Listing 16.9 : Surcharge de loprateur <<()
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: // Listing16.9 Surcharge de operator<<() #include <iostream> #include <string.h> using namespace std; class String { public: // constructeurs String(); String(const char *const); String(const String &); ~String(); // oprateurs surchargs char & operator[](int indice); char operator[](int indice) const; String operator+(const String&);
Chapitre 16
567
19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65:
void operator+=(const String&); String & operator= (const String &); friend ostream& operator<< ( ostream& leFlux, String& leString); // mthodes gnrales daccs int GetLongueur()const { return saLongueur; } const char * GetString() const { return saChaine; } private: String (int); // constructeur priv char * saChaine; unsigned short saLongueur; };
// le constructeur par dfaut cre des chanes de 0 octet String::String() { saChaine = new char[1]; saChaine[0] = \0; saLongueur=0; // cout << "\tConstructeur par dfaut de String" << endl; // CptConstructeur++; } // constructeur (utilitaire) priv, utilis seulement par // les mthodes de la classe pour crer une nouvelle // chane de la taille requise, contenant des caractres nuls. String::String(int longueur) { saChaine = new char[longueur+1]; for (int i = 0; i<=longueur; i++) saChaine[i] = \0; saLongueur=longueur; // cout << "\tConstructeur de String(int)" << endl; // CptConstructeur++; } // Convertit un tableau de caractres en String String::String(const char * const chaineC) { saLongueur = strlen(chaineC); saChaine = new char[saLongueur+1]; for (int i = 0; i < saLongueur; i++) saChaine[i] = chaineC[i]; saChaine[saLongueur]=\0; // cout << "\tConstructeur de String(char*)" << endl;
568
Le langage C++
66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89: 90: 91: 92: 93: 94: 95: 96: 97: 98: 99: 100: 101: 102: 103: 104: 105: 106: 107: 108: 109: 110: 111: 112:
// CptConstructeur++; } // constructeur de copie String::String (const String & rhs) { saLongueur = rhs.GetLongueur(); saChaine = new char[saLongueur+1]; for (int i = 0; i < saLongueur; i++) saChaine[i] = rhs[i]; saChaine[saLongueur] = \0; // cout << "\tConstructeur de String(String&)" << endl; // CptConstructeur++; } // destructeur, libre la mmoire String::~String () { delete [] saChaine; saLongueur = 0; // cout << "\tDestructeur de String" << endl; } // oprateur daffectation, libre la mmoire existante // puis copie la chane et la taille String& String::operator=(const String & rhs) { if (this == &rhs) return *this; delete [] saChaine; saLongueur=rhs.GetLongueur(); saChaine = new char[saLongueur+1]; for (int i = 0; i < saLongueur; i++) saChaine[i] = rhs[i]; saChaine[saLongueur] = \0; return *this; // cout << "\tOperator= de String" << endl; } // oprateur dindexation non constant, renvoie // une rfrence sur caractre pour quil puisse // tre modifi! char & String::operator[](int indice) { if (indice > saLongueur) return saChaine[saLongueur-1]; else
Chapitre 16
569
113: 114: 115: 116: 117: 118: 119: 120: 121: 122: 123: 124: 125: 126: 127: 128: 129: 130: 131: 132: 133: 134: 135: 136: 137: 138: 139: 140: 141: 142: 143: 144: 145: 146: 147: 148: 149: 150: 151: 152: 153: 154: 155: 156: 157: 158: 159:
return saChaine[indice]; } // oprateur dindexation constant pour utilisation // sur les objets const (voir constructeur de copie!) char String::operator[](int indice) const { if (indice > saLongueur) return saChaine[saLongueur-1]; else return saChaine[indice]; } // cre une nouvelle chane en ajoutant // la chane actuelle rhs String String::operator+(const String& rhs) { int longueurTotale = saLongueur+rhs.GetLongueur(); String temp(longueurTotale); int i, j; for (i = 0; i < saLongueur; i++) temp[i] = saChaine[i]; for (j = 0; j < rhs.GetLongueur(); j++, i++) temp[i] = rhs[j]; temp[longueurTotale]=\0; return temp; } // modifie la chane courante, ne renvoie rien void String::operator+=(const String& rhs) { unsigned short rhsLongueur = rhs.GetLongueur(); unsigned short longueurTotale = saLongueur+rhsLongueur; String temp(longueurTotale); int i, j; for (i = 0; i < saLongueur; i++) temp[i] = saChaine[i]; for (j = 0, i = 0; j < rhs.GetLongueur(); j++, i++) temp[i] = rhs[i - saLongueur]; temp[longueurTotale]=\0; *this = temp; } // int String::CptConstructeur = ostream& operator<<(ostream& leFlux,String& leString) { leFlux << leString.saChaine;
570
Le langage C++
return leFlux; } int main() { String unString("Bonjour."); cout << unString; return 0; }
Les lignes 21 et 22 dclarent operator<<() comme une fonction amie prenant en paramtre une rfrence un ostream et une rfrence String, et renvoyant une rfrence un ostream. Il ne sagit pas dune fonction membre de String. Elle renvoie une rfrence un ostream an de vous permettre denchaner ses appels, comme ici :
cout << "Age: " << sonAge << "an(s).";
Cette fonction amie est implmente de la ligne 157 la ligne 161. En fait, vous pouvez constater quelle se borne cacher le transfert de lobjet String vers lobjet ostream. Le Chapitre 17 reviendra en dtail sur la surcharge de operator<<() et operator>>()..
Questions-rponses
Q Pourquoi est-il si important de faire la distinction entre les relations est-un, a-un et implment en termes de ? R Lobjectif de C++ est de vous permettre de raliser des programmes orients objet clairs et optimiss. Distinguer clairement ces types de relations permet de sassurer que la conception correspond bien la ralit que lon souhaite modliser. En outre, une conception propre se retera dans un code bien contruit. Q Quest-ce que la composition ? R Cest un synonyme de lagrgation. Q Pourquoi prfrer lagrgation lhritage priv ? R De nos jours, le dveloppeur cherche simplier les choses pour mieux les matriser. Plus vous pouvez utiliser les objets comme des botes noires, moins vous aurez de dtails vous soucier et mieux vous pourrez grer la complexit. Lagrgation est une technique dencapsulation plus able que lhritage priv, car ce dernier ne masque pas limplmentation des objets de la classe. Dans une certaine mesure, cela vaut aussi
Chapitre 16
571
pour lhritage public traditionnel, qui est parfois utilis alors quune agrgation serait une meilleure solution. Q Pourquoi toutes les classes ne seraient-elles pas amies ? R Dclarer une classe amie expose les dtails dimplmentation et rduit lencapsulation. Lide consiste masquer le plus possible les dtails de chaque classe aux autres classes. Q Si une fonction amie est surcharge, dois-je dclarer chaque version de cette fonction comme amie ? R Oui. Si vous surchargez une fonction amie dune classe, vous devez dclarer amies toutes les variantes de cette fonction auxquelles vous voulez donner cet accs.
Exercices
1. Dclarez la classe Animal contenant une donne membre de type String. 2. Dclarez la classe TableauBorne, qui est un tableau. 3. Dclarez la classe Ensemble en termes de tableau. 4. Modiez le Listing 16.1, de sorte que la classe String contienne operator>>(). 5. CHERCHEZ LERREUR dans ce programme :
0: 1: //Cherchez lerreur #include <iostream>
572
Le langage C++
2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28:
using namespace std; class Animal; void SetVal(Animal&, int); class Animal { public: int GetPoids() const {return sonPoids; } int GetAge() const {return sonAge; } private int sonPoids; int sonAge; }; void SetVal(Animal& unAnimal, int lePoids) { friend class Animal; unAnimal.sonPoids = lePoids; } int main() { Animal medor; SetVal(medor, 5); return 0; }
6. Corrigez les erreurs du listing ci-dessus pour quil puisse se compiler. 7. CHERCHEZ LERREUR dans ce programme :
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: //Cherchez lerreur #include <iostream> using namespace std; class Animal; void SetVal(Animal&, int); void SetVal(Animal&, int, int); class Animal { friend void SetVal(Animal& ,int); private: int sonPoids; int sonAge; };
// modification!
Chapitre 16
573
16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33:
void SetVal(Animal& unAnimal, int lePoids) { unAnimal.sonPoids = lePoids; } void SetVal(animal& unAnimal, int lePoids, int unAge) { unAnimal.sonPoids = lePoids; unAnimal.sonAge = unAge; } int main() { Animal medor; SetVal(medor, 5); SetVal(medor, 7, 9); return 0; }
17
Les ux
Au sommaire de ce chapitre
Les ux et la faon de les utiliser Grer les entres/sorties avec les ux crire ou lire des chiers avec les ux
Jusqu prsent, nous avons utilis cout pour crire lcran et cin pour lire les donnes entres au clavier, sans connatre le dtail de leur mode de fonctionnement.
Prsentation des ux
Le langage C++ ne dnit pas la manire dont les donnes sont crites lcran ou dans un chier, ni la manire dont elles sont lues. Ce sont cependant des parties importantes lorsque lon programme en C++ et la bibliothque standard inclut donc la bibliothque iostream an de faciliter les entres/sorties (E/S).
576
Le langage C++
En sparant les E/S du langage et en les dplaant dans des bibliothques, il est plus facile dobtenir un langage portable, indpendant de la plate-forme. Cela signie que vous pouvez, en thorie, crire des programmes C++ sur un PC, puis les recompiler et les excuter sur une station Sun ou prendre un code cr laide de lenvironnement Visual C++ sous Windows et le recompiler sous Linux. Il suft pour cela que le constructeur fournisse les bonnes bibliothques. Une bibliothque est un ensemble de chiers objet (.obj ou .o) qui peuvent tre lis avec votre programme pour apporter une fonctionnalit supplmentaire. Cest donc la forme la plus basique de rutilisation du code et elle existe depuis que les anciens programmeurs ont commenc graver des 0 et des 1 sur les murs des cavernes.
Info
Les ux sont aujourdhui moins importants pour la programmation C++, sauf peut-tre pour la lecture des chiers plats. Les programmes C++ utilisent dsormais les bibliothques dinterface graphique du systme dexploitation ou celles du fabricant du compilateur pour travailler avec lcran, les chiers et lutilisateur : les bibliothques Windows et les bibliothques X Window fournissent en effet des abstractions pour les environnements Windows et X Window. Ces bibliothques tant spciques au systme dexploitation et ne faisant pas partie de la norme C++, elles ne seront pas traites dans cet ouvrage. Les ux faisant partie de cette norme, nous allons les prsenter ici. En outre, il est bon de comprendre leur fonctionnement pour apprhender le mcanisme des entres et des sorties. Vous devriez toutefois rapidement passer la bibliothque GUI de votre fournisseur ou de votre systme dexploitation.
Chapitre 17
Les ux
577
Print Screen
Scroll Lock
Pause
esc
F1
F2
F3
F4
F5
F6
F7
F8
F9
F10
F11
F12
F13
F14
F15
Num Lock
Caps Lock
Scroll Lock
~ `
Tab Caps Lock
! 1 Q
@ 2 W A Z
# 3 E S X
$ 4 R D C
% 5 T F V
^ 6 Y G B
& 7 U H N
* 8 I J M
( 9 O K < ,
) 0 P L > .
{ [ : ; ? / " '
+ = } ]
ins
Backspace
help
del
X
home
num Lock
clear
= 8 5 2
/ 9 6 3 .
* +
| \
Return
end
7 4 1
Shift
Shift
Control
Option
Option
Control
enter
Disque
Disque
Lorsque leau (les donnes) atteignent le haut du rservoir, une valve souvre et toute leau scoule dun seul coup (voir Figure 17.3). Lorsque le tampon est vide, la valve dcoulement se ferme, celle du haut souvre nouveau et de leau entre nouveau dans le rservoir, comme dans la Figure 17.4. Parfois, vous devez vider le rservoir mme sil nest pas plein (voir Figure 17.5).
578
Le langage C++
Lun des risques de lutilisation des tampons est que le programme se plante alors que des donnes sont toujours dans les tampons. En ce cas, il est possible de perdre des donnes.
Flux et tampons
Comme on pouvait sy attendre, C++ implmente les ux et les tampons selon une approche oriente objet, grce un certain nombre de classes et dobjets :
La classe streambuf gre le tampon, ses fonctions membres permettent de remplir, vider, purger et manipuler le tampon.
Chapitre 17
Les ux
579
La classe ios est la classe de base des classes de ux dentre et de sortie. Elle possde un objet streambuf comme variable membre. Les classes istream et ostream drivent de la classe ios et spcialisent, respectivement, le comportement des ux dentre et de sortie. La classe iostream drive des classes istream et ostream ; elle fournit les mthodes dentre et de sortie pour lafchage lcran. Les classes fstream grent les entres/sorties chiers.
Info
cin gre les entres en provenance de lentre standard : le clavier. cout gre les sorties vers la sortie standard : lcran. cerr gre les sorties vers la sortie standard des erreurs : lcran. cerr nutilisant pas de tampon, les donnes qui lui sont envoyes sont donc immdiatement afches lcran. clog gre les messages derreur qui sont envoys vers la sortie standard des erreurs : lcran. Ces messages, qui transitent par un tampon, sont souvent "redirigs" vers un chier journal, comme on lexplique dans la section suivante.
580
Le langage C++
Rediriger signie envoyer des sorties (ou des entres) vers un emplacement diffrent de celui par dfaut. La redirection est une fonctionnalit du systme dexploitation, plus quune fonction des bibliothques iostream. C++ se contente de donner accs aux quatre ux standard et cest lutilisateur de les rediriger selon ses besoins. Les oprateurs de redirection de DOS, de Windows et dUnix sont < (pour les entres) et > (pour les sorties). Unix offre des possibilits plus volues, mais lide gnrale reste la mme : prendre une sortie destine lcran pour lcrire dans un chier, ou pour la transmettre en entre dun programme par un tube. De la mme faon, lentre dun programme peut tre lue dans un chier plutt qu partir du clavier. Lutilisation des tubes permet dutiliser la sortie dun programme comme entre dun autre.
Info
Lobjet global cout sera prsent plus loin ; pour linstant, intressons-nous la troisime ligne, cin >> uneVariable;. Que peut-on en dduire de cin ? cin doit forcment tre un objet global puisque vous ne lavez pas dni dans votre code. Vous savez que cin surcharge loprateur dextraction (>>) an quil crive les donnes de son tampon dans la variable locale uneVariable. Ce qui est moins vident est que cin a surcharg cet oprateur pour un grand nombre de paramtres : int&, short&, long&, double&, float&, char&, char*, etc. Lorsque vous crivez cin >> uneVariable;, le type de uneVariable est valu. Dans lexemple prcdent, uneVariable est un entier, ce qui provoque lappel de la fonction suivante :
istream & operator>> (int &)
Le paramtre tant pass par rfrence, loprateur dextraction peut donc agir sur la variable dorigine. Le Listing 17.1 montre comment lutiliser.
Chapitre 17
Les ux
581
582
Le langage C++
Les lignes 7 11 contiennent les dclarations de variables de divers types. Les lignes 13 22 demandent lutilisateur de fournir des valeur pour ces variables et le rsultat est afch ( laide de cout) aux lignes 24 28. Le rsultat montre que les donnes ont t enregistres avec le bon "type" de variable.
Si vous tapez Arthur, la variable VotreNom recevra les caractres A, r, t, h, u, r, \0. Le dernier caractre est le caractre nul, qui est automatiquement ajout par cin : vous devez donc prvoir une taille de tampon sufsante pour stocker la chane "utile", plus ce caractre. Le caractre nul signale la "n de la chane" lobjet cin.
Chapitre 17
Les ux
583
La ligne 6 cre un tableau de caractres VotreNom pour pouvoir y stocker les saisies de lutilisateur. La ligne 7 demande lutilisateur de saisir son prnom, qui est stock pour tre ensuite afch. La ligne 10 demande la saisie dun nom complet. cin lit ce qui est saisi, insre un caractre nul ds quil rencontre lespace entre le prnom et le nom et met n la saisie, ce qui nest pas ce que lon attendait. Pour comprendre la raison de ce comportement, examinez le Listing 17.3. Listing 17.3 : Entre multiple
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: // Listing17.3 - Les chanes de caractres et cin #include <iostream> using namespace std; int main() { int unInt; long unLong; double unDouble; float unFloat; unsigned int unUnsigned; char unMot[50]; cout << "int : "; cin >> unInt; cout << "Long : "; cin >> unLong; cout << "Double : "; cin >> unDouble; cout << "Float : "; cin >> unFloat; cout << "Mot : "; cin >> unMot; cout << "Unsigned : "; cin >> unUnsigned; cout << "\n\nInt :\t" << unInt << endl;
584
Le langage C++
28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45:
"Long :\t" << unLong << endl; "Double :\t" << unDouble << endl; "Float :\t" << unFloat << endl; "Mot : \t" << unMot << endl; "Unsigned :\t" << unUnsigned << endl;
cout << "\n\nInt, Long, Double, Float, Mot, Unsigned : "; cin >> unInt >> unLong >> unDouble; cin >> unFloat >> unMot >> unUnsigned; cout << "\n\nInt :\t" << unInt << endl; cout << "Long :\t" << unLong << endl; cout << "Double :\t" << unDouble << endl; cout << "Float :\t" << unFloat << endl; cout << "Word : \t" << unMot << endl; cout << "Unsigned :\t" << unUnsigned << endl; return 0; }
Int, Long, Double, Float, Mot, Unsigned: 33049383938474736.66 bye -2 Int : Long : Double : Float : Mot : Unsigned 3 304938 3.93847e+008 6.66 bye : 4294967294
Ce programme cre plusieurs variables, dont un tableau de caractres. Lutilisateur doit fournir des donnes qui sont ensuite afches.
Chapitre 17
Les ux
585
La ligne 34 demande toutes ces donnes dun seul coup, puis chaque "mot" de la saisie est attribu la variable approprie. Cest pour faciliter ce type daffectations multiples que cin doit considrer chaque mot saisi comme une entre complte destine chaque variable. Sil considrait la saisie entire comme celle dune seule variable, ce type de saisie combine ne serait pas possible. Vous pouvez remarquer, la ligne 42, que le dernier objet demand tait un entier non sign et que lutilisateur a saisi -2. cin sattendant recevoir un entier non sign, la reprsentation binaire de -2 est value comme un entier non sign et cout afche par consquent la valeur 4294967294, car cest cette valeur non signe qui correspond au motif binaire de la valeur signe -2. Plus loin, nous verrons comment saisir une chane de plusieurs mots dans un tampon. Pour le moment, nous devons comprendre comment fait loprateur surcharg pour grer cette concatnation des valeurs saisies.
Dans la dernire ligne, la premire lecture est value (cin >> varUn) ; on obtient alors un autre objet istream auquel on peut appliquer loprateur dextraction pour obtenir la valeur qui sera affecte varDeux. Cest donc comme si vous aviez crit :
((cin >> varUn) >> varDeux) >> varTrois;
Vous aurez loccasion de retrouver cette technique dans le paragraphe consacr cout.
586
Le langage C++
La valeur renvoye par (cin.get() >> maVarUn) est, en fait, un entier et non un objet iostream. Le Listing 17.4 illustre lutilisation de get() sans paramtres. Listing 17.4 : Fonction get() sans paramtres
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: // Listing17.4 #include <iostream> int main() { char ch; while ( (ch = std::cin.get())!= EOF) { std::cout << "ch : " << ch << std::endl; } std::cout << "\nTermin!\n"; return 0; } La fonction get() sans paramtres
Chapitre 17
Les ux
587
Info
Pour sortir de ce programme, vous devrez envoyer un caractre n de chier partir du clavier. Pour cela, faites Ctrl+Z avec DOS et Ctrl+D avec Unix.
La ligne 6 dclare une variable locale, ch, de type caractre. La boucle while affecte les entres obtenues de cin.get() ch, et, sil ne sagit pas de EOF, la chane est afche. Cette sortie est, en fait, mise dans un tampon jusqu la rception de EOF qui entrane galement la sortie de la boucle. Notez que cette version de get() nest pas prise en charge dans toutes les implmentations de istream, bien quelle fasse dsormais partie de la norme ANSI/ISO.
588
Le langage C++
La ligne 6 cre trois variables caractres, a, b et c, La ligne 10 enchane trois appels cin.get(). Le premier, cin.get(a) place la premire lettre dans a et renvoie cin, de sorte que lappel suivant, cin.get(b), place la deuxime lettre dans b. Enn, cin.get(c) est appel et la troisime lettre est place dans c. Puisque le rsultat de cin.get(a) est cin, vous auriez pu crire :
cin.get(a) >> b;
Sous cette forme, cin.get(a) renvoie cin et le deuxime appel serait donc cin>>b;.
Faire
Utiliser loprateur dextraction >> lorsque
Ne pas faire
Enchaner les instructions cin pour obtenir
plusieurs entres si votre action nest pas claire. Il vaut mieux utiliser plusieurs commandes plus faciles comprendre.
Chapitre 17
Les ux
589
Le premier (pTabChar) est un pointeur sur un tableau de caractres ; le deuxime (tailleFlux) est le nombre maximal de caractres lire, plus un ; le troisime (charFin) reprsente le caractre de n. Si le deuxime paramtre vaut 20, par exemple, get() lira 19 caractres et les fera suivre du caractre nul, puis la chane sera stocke dans le premier paramtre. Le troisime paramtre, le caractre de n, vaut \n (nouvelle ligne) par dfaut. Si un caractre de n est rencontr avant que le nombre maximal de caractres nai t lu, un caractre nul est crit et le caractre de n reste dans le tampon. Le Listing 17.6 illustre lutilisation de cette forme de get(). Listing 17.6 : Utilisation de get() avec un tableau de caractres
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: // Listing17.6 - Utilisation de get() avec un tableau de caractres #include <iostream> using namespace std; int main() { char chaineUne[256]; char chaineDeux[256]; cout << "Entrez la premire chane: "; cin.get(chaineUne, 256); cout << "chaineUne: " << chaineUne << endl; cout << "Entrez la seconde chane: "; cin >> chaineDeux; cout << "chaineDeux: " << chaineDeux << endl; return 0; }
590
Le langage C++
Les lignes 7 et 8 crent deux tableaux de caractres. La ligne 10 invite lutilisateur saisir une chane et cin.get() est appele la ligne 11. Le premier paramtre est le tampon remplir, le deuxime est le nombre de caractres (plus un) acceptable par get() (cette position supplmentaire est utilise pour placer le caractre nul \0). Comme on ne fournit pas le troisime paramtre, il sagit du caractre "Nouvelle ligne". Lutilisateur saisit "Cest lheure" suivi dun saut de ligne, ce qui place cette phrase suivie dun caractre nul dans Chaine1. Lutilisateur est ensuite invit entrer une nouvelle chane mais, cette fois-ci, laide de loprateur dextraction. Cet oprateur sarrtant ds quil rencontre un blanc, seule la chane pour suivie dun caractre nul sera stocke dans la seconde chane ce qui, videmment, ntait pas ce que lon souhaitait. Lutilisation de get() avec les trois paramtres est donc une solution parfaite pour lire des chanes, mais ce nest pas la seule solution. Un autre moyen de rsoudre le problme consiste utiliser la mthode getline(), comme dans le Listing 17.7. Listing 17.7 : Utilisation de getline()
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: // Listing17.7 - Utilisation de getline() #include <iostream> using namespace std; int main() { char chaineUne[256]; char chaineDeux[256]; char chaineTrois[256]; cout << "Entrez la premire chane: "; cin.getline(chaineUne, 56); cout << "chaineUne: " << chaineUne << endl; cout << "Entrez la seconde chane: "; cin >> chaineDeux; cout << "chaineDeux: " << chaineDeux << endl; cout << "Entrez la troisime chane: "; cin.getline(chaineTrois, 256); cout << "chaineTrois: " << chaineTrois << endl; return 0; }
Chapitre 17
Les ux
591
Cet exemple mrite un examen attentif et rserve quelques surprises. Les tableaux de caractres sont dclars lignes 7 9. La ligne 11 demande une chane de caractres lutilisateur et cette chane est lue par la fonction getline(). Comme la fonction get(), getline() prend en paramtre un tampon et un nombre maximal de caractres. Cependant, ici, le retour la ligne est lu et supprim du tampon (alors quavec get() il reste dans le tampon). La ligne 15 demande une nouvelle chane lutilisateur. Cette fois, cest loprateur dextraction qui est utilis. Dans le rsultat, on voit que lutilisateur a entr quatre cinq six, mais que seul le premier mot, quatre, a t plac dans chaineDeux. Le programme demande ensuite la troisime chane lutilisateur et la fonction getline() est de nouveau appele. Les mots cinq six tant toujours prsents dans le tampon dentre, ils sont immdiatement lus jusquau retour la ligne. getline() se termine et la chane dans chaineTrois est afche la ligne 20. Lutilisateur na eu aucune occasion de saisir la troisime chane car le tampon dentre contenait dj des donnes qui correspondaient cette requte. Loprateur dextraction de cin, la ligne 16, na pas utilis tout ce qui se trouvait dans le tampon dentre : il a lu les caractres jusquau premier espace rencontr, puis a stock le mot ainsi form dans le tableau de caractres.
get() et getline()
La fonction membre get() est surcharge. Dans la premire version, elle ne reoit aucun paramtre et renvoie la valeur du caractre reu. Dans la deuxime version, elle prend une rfrence caractre et renvoie lobjet istream par rfrence. Dans la troisime et dernire version, get() reoit un tableau de caractres, le nombre de caractres lire et un caractre de n (nouvelle ligne par dfaut). Cette version de get() lit des caractres et les place dans le tableau jusqu ce quelle en ait lu un de moins que le nombre maximal, ou quelle rencontre le caractre de n. Dans ce cas, ce caractre est laiss dans le tampon dentre et la fonction arrte la lecture. La fonction membre getline() reoit galement trois paramtres : le tampon remplir, le nombre maximal de caractres lire plus un et le caractre de n. Cette mthode fonctionne exactement comme get(), sauf quelle supprime le caractre de n du tampon dentre.
592
Le langage C++
Utilisation de cin.ignore()
La fonction membre ignore() permet dignorer les caractres restants sur une ligne jusqu la rencontre dune n de ligne (EOL) ou dune n de chier (EOF). Elle attend deux paramtres : le nombre maximal de caractres ignorer et le caractre de n. Si vous crivez ignore(80, \n), 80 caractres au maximum seront ignors jusquau caractre nouvelle ligne. Ce caractre nouvelle ligne est alors supprim du tampon dentre et la fonction ignore() se termine. Le Listing 17.8 illustre lutilisation de cette mthode. Listing 17.8 : Utilisation de ignore()
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: // Listing17.8 - Utilisation de ignore() #include <iostream> using namespace std; int main() { char chaineUne[255]; char chaineDeux[255]; cout << "Entrez la premire chane:"; cin.get(chaineUne, 255); cout << "chaineUne: " << chaineUne << endl; cout << "Entrez la deuxime chane: "; cin.getline(chaineDeux, 255); cout << "chaineDeux: " << chaineDeux << endl; cout << "\n\nEncore une fois...\n"; cout << "Entrez la premire chane: "; cin.get(chaineUne, 255); cout << "chaineUne: " << chaineUne<< endl; cin.ignore(255, \n); cout << "Entrez la deuxime chane: "; cin.getline(chaineDeux, 255); cout << "chaineDeux: " << chaineDeux<< endl; return 0; }
Chapitre 17
Les ux
593
Entrez la deuxime chane: chaineDeux: Encore une fois... Entrez la premire chane: Il tait une fois chaineUne: Il tait une fois Entrez la deuxieme chane: une princesse mlancolique chaineDeux: une princesse melancolique
Les lignes 6 et 7 crent deux tableaux de caractres. Lutilisateur entre une premire chane, Il tait une fois, suivie de la touche Entre. la ligne 10, get() lit cette chane, et la copie dans chaineUne jusquau caractre nouvelle ligne qui est laiss dans le tampon dentre. La ligne 13 invite nouveau lutilisateur entrer une chane, mais cest getline() qui, la ligne 14, lit le tampon dentre jusquau caractre nouvelle ligne. Comme il restait une nouvelle ligne dans le tampon cause de lappel get() prcdent, la lecture sinterrompt immdiatement, avant mme que lutilisateur ait pu entrer quoi que ce soit. la ligne 19, lutilisateur entre nouveau la premire chane mais, cette fois, ignore() vide le ux dentre la ligne 23, en "mangeant" le caractre nouvelle ligne. Par consquent, lappel getline() de la ligne 26 est excut et, le tampon dentre tant vide, lutilisateur peut saisir la ligne suivante de lhistoire.
594
Le langage C++
La ligne 6 dclare la variable caractre ch et la ligne 7 demande une phrase lutilisateur. Le but de ce programme est de transformer les points dexclamation en dollars et de supprimer les symboles dise (#). Ce programme boucle des lignes 8 16 tant quil reoit des caractres diffrents dune n de chier (souvenez-vous que cin.get() renvoie 0 pour la n de chier). Si le caractre courant est un point dexclamation, il est supprim et le symbole $ est remis dans le tampon dentre : ce symbole est donc relu par le prochain appel cin.get() dans la boucle. Si le caractre courant nest pas un point dexclamation, il est afch la ligne 13. La ligne 14 "examine" le caractre suivant du tampon et, sil sagit dun symbole #, il est supprim laide de la mthode ignore() la ligne 15. Ce nest pas lapproche la plus efcace pour raliser ces deux oprations (en outre, elle ne trouvera pas le symbole dise sil est le premier caractre), mais elle illustre bien le fonctionnement de ces deux mthodes. Les fonctions peek() et putback() sont gnralement utilises pour lanalyse lexicale des chanes et des autres donnes lors de lcriture dun compilateur, par exemple.
ce Astu
Chapitre 17
Les ux
595
Purger la sortie
Vous avez vu que endl crit une nouvelle ligne et permet de purger le tampon de sortie. endl appelle la mthode flush() de cout, qui crit toutes les donnes contenues dans le tampon de sortie. Vous pouvez galement appeler directement la mthode flush(), soit en linvoquant sur lobjet cout, soit laide de linstruction suivante :
cout << flush();
Cette mthode est pratique lorsque vous voulez vous assurer que le tampon a t vid et que son contenu a t crit lcran.
Fonctions de sortie
Tout comme loprateur dextraction (>>) est complt par les mthodes get() et getline(), loprateur dinsertion (<<) peut tre complt par les mthodes put() et write().
Info
Certains compilateurs non conformes la norme auront des problmes avec ce code. Si votre compilateur nafche pas le mot Hello, utilisez une autre mthode.
Le premier paramtre est le texte afcher. Le deuxime correspond au nombre de caractres de Texte qui seront afchs. Ce nombre peut tre infrieur ou suprieur la taille relle de Texte : sil est suprieur, vous afcherez galement les valeurs qui se trouvent en mmoire la suite de Texte. Le Listing 17.10 illustre lutilisation de cette mthode.
596
Le langage C++
Info
La dernire ligne sera probablement diffrente, car elle accde une zone mmoire qui ne fait pas partie de la variable initialise.
Ce listing afche une phrase mais, chaque fois, elle en afche une quantit diffrente. La phrase est cre la ligne 7. la ligne 9, lentier complet reoit la longueur de la phrase laide de fonction globale strlen() incluse grce la directive de la ligne 2. Deux autres longueur seront galement utilises : tropCourt est dnie avec cette longueur moins quatre, alors que tropLong est dnie avec cette longueur plus six. La phrase complte est afche la ligne 13 laide de la fonction write(). La longueur correspondant la longueur relle de la phrase, celle-ci safche donc correctement. La phrase est nouveau afche la ligne 14, mais avec quatre caractres de moins. La ligne 15 afche de nouveau la phrase, mais on demande cette fois-ci la fonction write() dcrire six caractres de plus que la longueur de cette phrase. Les six octets qui sont stocks en mmoire la suite de la phrase sont donc crits. Cette mmoire pouvant contenir nimporte quoi, votre rsultat sera donc srement diffrent de ce qui est prsent.
Chapitre 17
Les ux
597
Utilisation de cout.width()
La largeur par dfaut de la sortie sera toujours ajuste pour permettre dafcher les nombres, les caractres ou les chanes du tampon de sortie. Vous pouvez la modier avec la fonction width(), qui doit tre appele sur un objet cout car il sagit dune fonction membre. Elle nagit que sur le champ de sortie suivant ; la largeur reprend ensuite immdiatement sa valeur par dfaut. Listing 17.11 : Modication de la largeur de la sortie
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: // Listing17.11 - Modification de la taille de la sortie #include <iostream> using namespace std; int main() { cout << "Dbut >"; cout.width(25); cout << 123 << "< Fin\n"; cout << "Dbut >"; cout.width(25); cout << 123 << "< Suivant >"; cout << 456 << "< Fin\n"; cout << "Dbut >"; cout.width(4); cout << 123456 << "< Fin\n"; return 0; }
598
Le langage C++
Les lignes 6 8 afchent 123 sur la premire ligne, dans un champ dont la largeur a t dnie 25 caractres (ligne 7). La deuxime ligne afche la valeur 123 dans le mme champ que la ligne prcdente, puis la valeur 456. Vous remarquerez que cette dernire valeur est afche dans un champ dont la largeur a t ajuste celle de la valeur ; comme on la indiqu prcdemment, leffet de width() nagit que sur la sortie suivante, pas sur les autres. La dernire ligne vous montre que le fait de dnir un champ plus petit que la sortie donne le mme rsultat que sil tait dni sa taille exacte. Une largeur trop petite ne tronquera donc pas ce qui est afch.
Chapitre 17
Les ux
599
Pour les utiliser, vous devez inclure iostream dans votre programme. En outre, pour les indicateurs qui prennent des paramtres, vous devez galement inclure iomanip.
Tableau 17.1 : Certains indicateurs iostream
Indicateur showpoint showpos left right internal scientific fixed showbase Uppercase dec oct hex
Objectif
Afche un point dcimal et les zros de droite exigs par la prcision. Active le signe plus (+) devant les nombres positifs. Aligne le rsultat gauche. Aligne le rsultat droite. Aligne le signe dun nombre gauche et sa valeur droite. Afche les valeurs virgule ottante en notation scientique. Afche les valeurs virgule ottante en notation dcimale. Ajoute "Ox" devant les nombres hexadcimaux. Afche les nombres hexadcimaux et scientiques en majuscules. Fixe la base des nombres pour un afchage en dcimal. Fixe la base des nombres pour un afchage en octal (base 8). Fixe la base des nombres pour un afchage en hexadcimal (base 16).
Ces indicateurs peuvent galement tre concatns avec loprateur dinsertion. Le Listing 17.12 montre un exemple et introduit galement le manipulateur setw, qui xe la largeur, mais peut aussi tre concatn avec loprateur dinsertion. Listing 17.12 : Utilisation de setf
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: // Listing17.12 - Utilisation de setf #include <iostream> #include <iomanip> using namespace std; int main() { const int nombre = 185; cout << "Nombre " << nombre << endl;
600
Le langage C++
10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31:
cout.setf(ios::showbase); cout << "Nombre " << hex << nombre << endl; cout << "Nombre "; cout.width(10); cout << hex << nombre << endl; cout << "Nombre "; cout.width(10); cout.setf(ios::left); cout << hex << nombre << endl; cout << "Nombre "; cout.width(10); cout.setf(ios::internal); cout << hex << nombre << endl; cout << "Nombre return 0; } " << setw(10) << hex << nombre << endl;
La ligne 7 initialise la constante entire nombre avec la valeur 185 et la ligne suivante lafche normalement. Cette valeur est nouveau afche la ligne 10, mais le manipulateur hex est concatn, ce qui provoque lafchage en hexadcimal, soit b9. La ligne 12 dnit lindicateur showbase. Le prxe 0x sera donc ajout tous les nombres hexadcimaux comme le montre lafchage. La ligne 16 xe la largeur 10 et, par dfaut la valeur est aligne droite. la ligne 20, la largeur est de nouveau 10, mais lalignement est dni gauche (left). la ligne 25, la largeur est toujours 10, mais avec un alignement internal. 0x est donc align gauche, mais b9 est align droite.
Chapitre 17
Les ux
601
Enn, la ligne 29, le manipulateur setw() est utilis pour dnir la largeur 10 et la valeur est nouveau afche. Dans ce listing, vous remarquerez que si les indicateurs sont utiliss dans la liste cout, ils nont pas besoin dtre qualis : hex peut tre pass tel quel. Par contre, lorsque vous utilisez la fonction setf(), vous devez qualier les indicateurs avec la classe ; hex est pass sous la forme ios::hex. Vous pouvez constater cette diffrence entre les lignes 17 et 21.
Spcicateurs %s %d %l %ld %f
Utilis pour
Chanes Entiers Entier long
double float
602
Le langage C++
Chaque spcicateur de conversion peut galement fournir des indications de taille et de prcision, exprimes sous la forme dun nombre virgule ottante. Les chiffres situs gauche du point dcimal reprsentent la largeur totale, ceux de droite la prcision pour les nombres virgule ottante. Ainsi, %5d est le spcicateur pour un entier de cinq chiffres et %15.5f celui dun nombre virgule ottante de 15 chiffres, dont 5 pour la partie dcimale. Le Listing 17.13 illustre diverses utilisations de la fonction printf(). Listing 17.13 : Utilisation de printf()
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 23a: 24: 25: 26: //17.13 Utilisation de printf() #include <stdio.h> int main() { printf("%s", "Bonjour\n"); char *phrase = "Bonjour tout le monde!\n"; printf("%s", phrase); int x = 5; printf("%d\n", x); char *phraseDeux = "Voici des valeurs: "; char *phraseTrois = " et quelques autres: "; int y = 7, z = 35; long longVar = 98456; float floatVar = 8.8f; printf("%s%d%d", phraseDeux, y, z); printf("%s%ld%f\n",phraseTrois, longVar, floatVar); char *phraseQuatre = "Version mise en forme: "; printf("%s%5d%10d %10.5f\n", phraseQuatre, y, z, floatVar); return 0; }
Chapitre 17
Les ux
603
La premire instruction printf(), la ligne 5, utilise la forme standard : elle prend en paramtre une chane entre guillemets contenant un spcicateur de conversion et une valeur insrer la place de ce spcicateur. Le %s indique quil sagit dune chane et, ici, cette chane est un littral entre guillemets : "Bonjour". La deuxime instruction (ligne 8) est comme la premire, mais plutt que dindiquer la chane de caractres entre guillemets, on utilise un pointeur de caractre. Le rsultat est toutefois identique. La troisime instruction printf(), ligne 11, utilise le spcicateur de conversion en entier (%d) et la variable entire x. La quatrime instruction, ligne 19, est plus complexe car elle concatne trois valeurs. La chane de format contenant les trois spcicateurs de conversion est suivie des trois valeurs, spares par des virgules. La ligne 20 est identique la ligne 19, mais les valeurs utilises sont diffrentes. Linstruction de la ligne 23, enn, utilise des spcicateurs de format pour indiquer la largeur et la prcision. Il ne faut pas oublier que cette instruction ne contrle pas le type des donnes et que printf() ne peut tre dclare comme une fonction amie ou comme une fonction membre dune classe.
Manipulation des sorties : rsum Pour formater les sorties en C++, on utilise une combinaison de caractres spciaux, de manipulateurs et dindicateurs. Les caractres spciaux suivants sont inclus dans une chane en sortie envoye cout laide de loprateur dinsertion :
\n. Nouvelle ligne. \r. Retour chariot. \t. Tabulation. \\. Antislash. \ddd (nombre octal). Caractre ASCII. \a. Alarme (sonore).
Par exemple :
604
Le langage C++
Les oprateurs suivants ne requirent pas iomanip : flush. Purge le tampon de sortie. endl. Insre une nouvelle ligne et purge le tampon de sortie. oct. Fixe la base octale en sortie. dec. Fixe la base dcimale en sortie. hex. Fixe la base hexadcimale en sortie. Les oprateurs suivants requirent iomanip: setbase (base). Fixe la base en sortie (0 = dcimal, 8 = octal, 10 = dcimal, 16 = hexa). setw (largeur). Fixe la largeur minimale du champ en sortie. setfill (car). Caractre de remplissage utiliser lorsque largeur est dni. setprecision (p). Fixe la prcision pour les nombres virgule ottante. setiosflags (f). Dnit un ou plusieurs indicateurs ios. resetiosflags (f). Rednit un ou plusieurs indicateurs ios. Par exemple :
cout << setw(12) << setfill(#) << hex << x << endl;
xe 12 la largeur du champ et le # comme caractre de remplissage, prcise une sortie hexa, afche la valeur de x, place une nouvelle ligne dans le tampon et le purge. Tous les manipulateurs, sauf flush, endl et setw, restent en place jusqu ce quils soient modis ou jusqu la n du programme. setw revient sa valeur par dfaut aprs le traitement cout en cours. Un certain nombre dindicateurs peuvent tre utiliss avec les manipulateurs setiosflags et resetiosflags. Ils ont t numrs au Tableau 17.1. Des informations supplmentaires peuvent tre obtenues dans le chier ios et la documentation de votre compilateur.
ofstream et ifstream
Les objets utiliss pour lire ou crire dans des chiers sont des objets ifstream et ofstream. Ces classes drive de la classe iostream que vous avez dj utilise.
Chapitre 17
Les ux
605
Avant dcrire dans un chier, vous devez dabord crer un objet ofstream, puis lui associer un chier sur disque. Pour utiliser des objets ofstream, votre programme doit inclure fstream. Vous navez pas besoin dinclure explicitement le chier iostream puisquil est compris dans le chier fstream.
Info
Cette instruction tente douvrir le chier monFichier.cpp en sortie. Louverture de ce chier en entre fonctionne de la mme faon, mais avec un objet ifstream :
ifstream fin("monFichier.cpp");
fout et fin ont t choisis ici en raison de leur similitude avec cout et cin. Les noms pourraient aussi reter le contenu du chier accd. Chaque objet ux que vous crez ouvre un chier en lecture (en entre), en criture (en sortie) ou les deux. Il est important de refermer ce chier en n de traitement laide de la fonction close() ; cette opration garantit que le chier ne sera pas corrompu et que les donnes que vous avez crites sont bien transfres sur le disque. Lorsque les objets ux ont t associs des chiers, ils peuvent tre utiliss comme nimporte quel autre objet ux. Le Listing 17.14 illustre ces manipulations.
606
Le langage C++
<< "Cette ligne est crite directement dans le fichier...\n"; cout << "Entrez le texte destin au fichier: "; // supprimer la nouvelle ligne aprs le nom de fichier cin.ignore(1, \n); // rcuprer la saisie de lutilisateur cin.getline(tampon, 255); fout << tampon << "\n"; // et lcrire dans le fichier // fermer le fichier, prt pour rouverture fout.close(); ifstream fin(nomFichier); // rouvrir en lecture cout << "Contenu du fichier:\n"; char ch; while (fin.get(ch)) cout << ch; cout << "\n***Fin du fichier.***\n"; fin.close(); return 0; } // il faut toujours fermer le fichier!
Chapitre 17
Les ux
607
La ligne 7 cre un tableau de caractres pour stocker le nom du chier et la ligne 8 cre un autre tampon pour recevoir la saisie de lutilisateur. Ce dernier est invit entrer un nom de chier sur la ligne suivante, qui est stock dans le tampon nomFichier. La ligne 12 cre un objet ofstream, fout, et lassocie au nom du chier. Cette ligne ouvre le chier en criture ; sil existait dj, son contenu est supprim. La ligne 13 crit directement dans le chier une chane de texte La ligne suivante demande lutilisateur de saisir une chane. Le caractre nouvelle ligne de lentre utilisateur prcdente (nom du chier) qui restait dans le tampon dentre est supprim la ligne 15 par la fonction ignore(), que nous avons vue prcdemment. La nouvelle saisie de lutilisateur est ensuite stocke dans tampon. La ligne 17 crit son contenu puis le caractre nouvelle ligne dans le chier. La ligne suivante ferme le chier. La ligne 20 rouvre le chier, cette fois en entre avec ifstream, et le contenu est lu caractre par caractre aux lignes 23 et 24.
ios::app. Ajoute les donnes la n du chier existant au lieu de supprimer son contenu. ios::ate. Vous place en n de chier, mais vous pouvez crire nimporte o dans ce chier. ios::trunc. Comportement par dfaut. Le chier existant est vid. ios::nocreate. chec de louverture si le chier nexiste pas. ios::noreplace. chec de louverture si le chier existe dj. Le Listing 17.15 illustre lajout de donnes un chier existant en rouvrant celui du listing prcdent pour lui ajouter du texte.
608
Le langage C++
5: int main() // renvoie 1 si erreur 6: { 7: char nomFichier[80]; 8: char tampon[255]; 9: cout << "Entrez de nouveau le nom du fichier: "; 10: cin >> nomFichier; 11: 12: ifstream fin(nomFichier); 13: if (fin) // existe dj? 14: { 15: cout << "Contenu actuel du fichier:\n"; 16: char ch; 17: while (fin.get(ch)) 18: cout << ch; 19: cout << "\n***Fin du fichier.***\n"; 20: } 21: fin.close(); 22: 23: cout << "\nOuverture de " << nomFichier 23a: << " en mode ajout...\n"; 24: 25: ofstream fout(nomFichier, ios::app); 26: if (!fout) 27: { 28: cout << " Ouverture impossible de " 28a: << nomFichier << " en ajout.\n"; 29: return(1); 30: } 31: 32: cout << "\nEntrez le texte ajouter au fichier: "; 33: cin.ignore(1, \n); 34: cin.getline(tampon, 255); 35: fout << tampon << "\n"; 36: fout.close(); 37: 38: fin.open(nomFichier); // raffecte lobjet fin existant! 39: if (!fin) 40: { 41: cout << "Ouverture impossible de " 41a: << nomFichier << " en lecture.\n"; 42: return(1); 43: } 44: cout << "\nContenu du fichier:\n"; 45: char ch;
Chapitre 17
Les ux
609
while (fin.get(ch)) cout << ch; cout << "\n***Fin du fichier.***\n"; fin.close(); return 0; }
Comme dans le prcdent listing, ce programme demande un nom de chier lutilisateur aux lignes 9 et 10. Cette fois-ci, la ligne 12 cre un objet ux de chier en entre. Louverture du chier est teste ligne 13 et, si le chier existe, son contenu est afch aux lignes 15 19. Notez que if (fin) est quivalent if (fin.good()). Le chier est ensuite ferm puis rouvert la ligne 25, cette fois-ci en mode ajout. Le chier est test aprs chaque ouverture pour sassurer que cette opration sest droule correctement. Ici, if (!fout) est quivalent if (fout.fail()). Si le chier na pas pu tre ouvert, le programme afche un message derreur en ligne 28 et se termine par linstruction return. Si louverture a russi, lutilisateur est invit saisir le texte ajouter, puis le chier est ferm la ligne 36. Enn, comme dans le listing prcdent, on rouvre le chier en mode lecture. Cette foisci, cependant, il ny a pas besoin de redclarer fin : on lui attribue simplement le mme nom de chier. Louverture du chier est teste ligne 39. Si tout sest bien pass, le contenu du chier est afch et le chier est ferm four la dernire fois.
610
Le langage C++
Faire
Tester le bon droulement de chaque ouver-
Ne pas faire
Essayer de fermer ou de raffecter cin ou
ture de chier.
Rutiliser les objets ifstream et ofstream
cout.
Utiliser
existants.
Fermer tous les objets fstream quand vous
Chapitre 17
Les ux
611
5: 6: 7: 8: 8a: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 32a: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 44a: 45: 46: 47: 48:
class Animal { public: Animal(int poids, long jours): sonPoids(poids), saDureeVie(jours) {} ~Animal(){} int GetPoids()const { return sonPoids; } void SetPoids(int poids) { sonPoids = poids; } long GetDureeVie()const { return saDureeVie; } void SetDureeVie (long jours) { saDureeVie = jours; } private: int sonPoids; long saDureeVie; }; int main() // renvoie 1 si erreur { char nomFichier[80];
cout << "Entrez le nom du fichier: "; cin >> nomFichier; ofstream fout(nomFichier, ios::binary); if (!fout) { cout << "Impossible douvrir " << nomFichier<< " en criture.\n"; return(1); } Animal Ours(50, 100); fout.write((char*) &Ours, sizeof Ours); fout.close(); ifstream fin(nomFichier, ios::binary); if (!fin) { cout << "Impossible douvrir " << nomFichier << " en lecture.\n"; return(1); } Animal Ours2(1, 1);
612
Le langage C++
49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59:
cout << "Poids Ours2 : " << Ours2.GetPoids() << endl; cout << "Jours Ours2: " << Ours2.GetDureeVie() << endl; fin.read((char*) &Ours2, sizeof Ours2); cout << "Poids Ours2: " << Ours2.GetPoids() << endl; cout << "Jours Ours2: " << Ours2.GetDureeVie() << endl; fin.close(); return 0; }
La classe Animal est dclare aux lignes 5 20. Les lignes 24 34 crent un chier et louvrent pour une sortie en mode binaire. Un animal dun poids de 50 kg et dun ge de 100 jours est cr la ligne 36, ses donnes sont crites dans le chier la ligne 37. Le chier est ferm la ligne 39, puis rouvert pour une lecture en mode binaire la ligne 41. La ligne 48 cre un second animal la ligne 48, avec un poids de 1 kg et un ge de 1 jour. Les donnes du chiers sont lues et places dans ce nouvel animal la ligne 53 ; elles crasent donc les anciennes valeurs, ce que lon peut vrier par ce qui est afch par le programme.
Ces paramtres ne sont pas directement transmis main(). En effet, cette fonction reoit deux paramtres. Le premier est un entier qui reprsente le nombre de paramtres de la ligne de commande, y compris le nom du programme : chaque programme a donc toujours au moins un paramtre. Lexemple ci-dessus contient quatre paramtres (le nom du programme, plus trois paramtres en ligne de commande).
Chapitre 17
Les ux
613
Le second paramtre est un tableau de pointeurs sur des chanes de caractres. Le nom dun tableau tant lui-mme un pointeur sur le premier lment de ce tableau, vous pouvez donc dclarer ce paramtre comme tant un pointeur de pointeur de caractres, un pointeur sur un tableau de caractres, ou un tableau de tableaux de caractres. Le premier argument est souvent appel argc (argument count) et le second argv (argument vector), mais vous pouvez adopter votre propre convention. Il est courant de tester argc pour tre sr davoir reu le bon nombre de paramtre, puis dutiliser argv pour accder aux chanes elles-mmes. argv[0] tant le nom du programme, argv[1] est donc le premier paramtre pass au programme en ligne de commande. Il est important de noter que tous ces paramtres sont reprsents comme des chanes : si un paramtre de votre programme est cens tre un nombre, vous devrez le convertir en nombre dans le programme partir de la chane obtenue. Le Chapitre 21 vous apprendra utiliser les conversions des bibliothques standard. Le Listing 17.17 illustre lutilisation des paramtres de la ligne de commande. Listing 17.17 : Utilisation des paramtres de la ligne de commande
0: 1: 2: 3: 4: 5: 6: 6a: 7: 8: //Listing17.17 Paramtres de la ligne de commande #include <iostream> int main(int argc, char **argv) { std::cout << argc << " paramtres reus...\n"; for (int i = 0; i < argc; i++) std::cout << "paramtre " << i << ": " << argv[i] << std::endl; return 0; }
Info
Vous devez excuter ce programme soit partir de la ligne de commande (avec DOS), soit congurer les paramtres de ligne de commande de votre compilateur (voir la documentation de celui-ci).
614
Le langage C++
La fonction main() dclare deux paramtre : argc est un entier qui contient le nombre de paramtres passs en la ligne de commande et argv est un pointeur sur un tableau de chanes. Chaque chane du tableau point par argv est un paramtre de la ligne de commande. Notez que vous auriez pu dclarer ce paramtre comme char* argv[] ou comme char argv[][] : ce nest quune question de style de programmation car, dans tous les cas, vous pourrez accder aux diffrents paramtres en utilisant des indices sur ce tableau. La ligne 4 utilise argc pour afcher le nombre de paramtres du programme : il y en a cinq en comptant le nom du programme. Les lignes 5 et 6 afchent chaque paramtre en passant cout les chanes termines par un caractre nul via un parcours du tableau des chanes. Une utilisation plus courante des options de la ligne de commande consiste prendre un nom de chier en paramtre, comme le montre le Listing 17.18. Listing 17.18 : Utilisation des paramtres de la ligne de commande pour rcuprer un nom de chier
0: 1: 2: 3: 4: 5: 6: 7: 8: 8a: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: //Listing17.18 Paramtres de la ligne de commande #include <fstream> #include <iostream> using namespace std; class Animal { public: Animal(int poids, long jours): sonPoids(poids),saDureeVie(jours) {} ~Animal(){} int GetPoids()const { return sonPoids; } void SetPoids(int poids) { sonPoids = poids; } long GetDureeVie()const { return saDureeVie; } void SetDureeVie (long jours) { saDureeVie = jours; } private: int sonPoids; long saDureeVie; }; int main(int argc, char *argv[]) { if (argc!= 2) { // renvoie 1 si erreur
Chapitre 17
Les ux
615
26: 27: 28: 29: 30: 31: 32: 33: 33a: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 45a: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: }
cout << "Usage: " << argv[0] << " <nom_fichier>\n"; return(1); } ofstream fout(argv[1], ios::binary); if (!fout) { cout << "Impossible douvrir " << argv[1] << " en criture.\n"; return(1); } Animal Ours(50,100); fout.write((char*) &Ours,sizeof Ours); fout.close(); ifstream fin(argv[1], ios::binary); if (!fin) { cout << "Impossible douvrir " << argv[1] << " en lecture.\n"; return(1); } Animal Ours2(1,1); cout << "Poids Ours2: " << Ours2.GetPoids() << endl; cout << "Jours Ours2: " << Ours2.GetDaysAlive() << endl; fin.read((char*) &Ours2, sizeof Ours2); cout << "Poids Ours2: " << Ours2.GetPoids() << endl; cout << "Jours Ours2: " << Ours2.GetDureeVie() << endl; fin.close(); return 0;
La dclaration de la classe Animal est identique celle du Listing 17.16 mais, au lieu de demander un nom de chier lutilisateur, on utilise ici les paramtres de la ligne de
616
Le langage C++
commande. main() est dclare la ligne 22 avec deux paramtres : le nombre de paramtres de la ligne de commande et un pointeur sur le tableau des chanes de paramtres de la ligne de commande. Les lignes 24 28 vrient que main() a bien reu deux paramtres ; dans le cas contraire, on afche un message derreur :
Usage ProgTest <nom_fichier>
et le programme se termine. Lutilisation de argv[0] au lieu de mettre littralement le nom du programme permet de compiler ce code an de produire un programme portant nimporte quel nom : linstruction usage afchera toujours le nom correct. Vous pouvez mme renommer lexcutable aprs sa compilation ! la ligne 30, le programme tente douvrir en criture binaire le chier dont le nom (argv[1]) est fourni en paramtre. Cette technique est utilise la ligne 42 pour rouvrir le chier en entre, puis dans les instructions qui afchent un message derreur si le chier ne peut pas tre ouvert, aux lignes 33 et 45.
Questions-rponses
Q Comment savoir sil faut utiliser les oprateurs dinsertion et dextraction ou les autres fonctions membres de la classe stream ? R Il est en gnral plus facile dutiliser les oprateurs dinsertion et dextraction et ils doivent tre choisis de prfrence lorsque leur comportement convient. Dans des circonstances plus inhabituelles, lorsque ces oprateurs ne conviennent plus (pour lire une suite de mots, par exemple), vous pouvez utiliser les autres mthodes. Q Quelle diffrence y a-t-il entre cerr et clog ? R cerr nutilise pas de tampon. Tout ce qui est transmis vers cerr est immdiatement crit. Ce comportement convient parfaitement aux erreurs qui safchent sur lcran de la console, mais serait trop coteux pour crire ces erreurs sur disque. Les sorties de clog, par contre, passent par un tampon, ce qui est plus efcace, au risque toutefois de perdre une partie du journal si le programme plante. Q Pourquoi a-t-on cr les ux puisque printf() fonctionne bien ? R printf() ne sait pas grer le typage fort de C++, ni les classes dnies par lutilisateur. La prise en charge de printf() nest quun reste du langage C.
Chapitre 17
Les ux
617
Q Quand doit-on utiliser putback() ? R Lorsquune opration de lecture doit dterminer si un caractre est valide, mais quune autre opration de lecture (ralise ventuellement par un autre objet) a besoin que ce caractre soit dans le tampon. Cette commande est gnralement utilise lors de lanalyse lexicale dun chier. Le compilateur C++, par exemple, pourrait utiliser putback(). Q Peut-on utiliser printf() dans des programmes C++ ? R Non, cette fonction doit prsent tre considre comme obsolte.
Exercices
1. crivez un programme qui crit dans les quatre objets iostream standard : cin, cout, cerr et clog. 2. crivez un programme qui demande lutilisateur dentrer ses nom et prnom, puis qui les afche lcran. 3. Modiez le Listing 17.9 pour obtenir le mme rsultat sans utiliser putback() et ignore(). 4. crivez un programme qui reoit un nom de chier en paramtre et qui louvre en lecture. Lisez chaque caractre du chier et afchez uniquement les lettres et les caractres de ponctuations (ignorez tous les caractres non imprimables). Fermez le chier avant la n du programme. 5. crivez un programme qui afche ses paramtres en ligne de commande dans lordre inverse, sans afcher le nom du programme.
18
Espaces de noms
Au sommaire de ce chapitre
Rsolution des fonctions et des classes par leurs noms Cration dun espace de noms Utilisation dun espace de noms Utilisation de lespace de noms standard std
Les espaces de noms aident les programmeurs organiser les classes et viter les conits de noms lors de lutilisation de plusieurs bibliothques.
620
Le langage C++
Il nest pas non plus surprenant de rencontrer une classe List dans une bibliothque de fentrage pour stocker la liste des fentres ouvertes par une application. Supposons que vous utilisiez la classe List de la bibliothque des classes conteneurs. Vous dclarez une instance de la classe List de la bibliothque pour mmoriser vos fentres et vous dcouvrez que vous ne disposez pas des fonctions membres que vous vouliez appeler. Le compilateur a en fait associ votre dclaration au conteneur List de la bibliothque standard alors que vous vouliez la classe List dune bibliothque spcique. Les espaces de noms permettent de rduire les conits possibles. Ils ressemblent aux classes par certains cts et leur syntaxe est dailleurs trs similaire. Les lments dclars au sein dun espace de noms lui appartiennent et leur visibilit est publique. Les espaces de noms peuvent tre imbriqus. Les fonctions peuvent tre dnies dans le corps de lespace de noms ou lextrieur. Dans ce dernier cas, elles doivent tre qualies laide du nom de lespace de noms ou le programme doit avoir import lespace de noms dans son espace global.
Chapitre 18
Espaces de noms
621
Le message de lditeur de liens sur notre systme est : "in second.obj: valeurEntiere already defined in premier.obj" (cest--dire "dans second.obj : valeurEntiere est dj dnie dans premier.obj"). Ce message napparatrait pas si les noms avaient une porte diffrente. Le compilateur peut galement envoyer un avertissement du type identificateur hiding, signalant que valeurEntiere dans main() masque la variable globale de mme nom. Pour utiliser la variable valeurEntiere dclare lextrieur de main(), vous devez explicitement lui attribuer une porte globale. Lexemple des Listings 18.2a et 18.2b affecte la valeur 10 la variable valeurEntiere externe main(), pas celle qui est dclare dans main(). Listing 18.2a : Premier listing pour le masquage de lidenticateur
0: 1: 2: 3: 4: 5: 6: 7: 8: // fichier premier.cpp int valeurEntiere = 0; int main( ) { int valeurEntiere = 0; ::valeurEntiere = 10; // valeurEntiere globale // . . . return 0; };
Info
Notez lusage de loprateur de rsolution de porte :: qui indique que la variable valeurEntiere concerne est globale et non locale.
622
Le langage C++
Le problme avec ces deux listings est quils dnissent chacun une variable globale qui a le mme nom et la mme visibilit, ce qui provoquera une erreur lors de ldition des liens.
La premire dnition, intPorteeGlobale, est visible dans les fonctions f() et main(). La dnition suivante, intPorteeLocale, se trouve dans la fonction f() la ligne 4. La porte de cette variable est locale, elle nest donc visible que dans le bloc qui la dnit. La fonction main() ne peut accder la variable intPorteeLocale de f(), puisquelle est devenue hors de porte au retour de la fonction. La troisime dnition, galement appele intPorteeLocale, se trouve la ligne 8, dans la fonction main() ; sa porte est limite au niveau du bloc. La variable intPorteeLocale de main() nest pas en conit avec celle de f(). Les deux dnitions qui suivent, lignes 10 et 11, autreLocale et intPorteeLocale, ont une porte
Chapitre 18
Espaces de noms
623
au niveau du bloc. Ds quelles atteignent laccolade fermante de la ligne 12, elles deviennent hors de porte. Cette dernire variable intPorteeLocale masque celle de mme nom qui a t dnie juste avant laccolade ouvrante (la seconde intPorteeLocale dnie dans le programme). Lorsque le programme dpasse laccolade fermante de la ligne 12, la seconde variable intPorteeLocale rcupre sa visibilit. Tout changement apport la variable intPorteeLocale dnie entre les accolades naffecte pas la variable externe de mme nom.
Liaison
Les noms peuvent avoir une liaison interne et externe. Ces deux termes dsignent lutilisation ou la disponibilit dun nom dans plusieurs units de compilation ou au sein dune seule unit. Un nom ayant une liaison interne ne peut tre rfrenc quau sein de lunit dans laquelle il est dni. Par exemple, une variable dnie comme ayant une liaison interne peut tre partage par les fonctions au sein de la mme unit, alors que les variables ayant une liaison externe sont galement accessibles aux autres units. Les Listings 18.4a et 18.4b montrent ces deux types de liaisons. Listing 18.4a : Liaisons interne et externe
0: 1: 2: 3: 4: 5: 6: } // fichier: premier.cpp int intExterne = 5; const int j = 10; int main() { return 0;
La variable intExterne dnie la ligne 1 de premier.cpp (Listing 18.4a) a une liaison externe. Bien quelle soit dnie dans premier.cpp, second.cpp peut donc aussi y avoir accs. Les deux j prsentes dans les deux chiers sont des constantes qui, par dfaut, ont une liaison interne. Vous pouvez rednir cette proprit des const en fournissant une dclaration explicite, comme le montrent les Listing 18.5a et 18.5b.
624
Le langage C++
Remarquez lusage de cout la ligne 5 en prcisant explicitement lespace de nom std. Cet exemple afche le rsultat suivant :
j vaut 10
Lutilisation de static pour limiter la porte des variables externes nest plus recommande et peut mme devenir illgale lavenir. Il faut dsormais utiliser les espaces de noms.
Faire
Utiliser les espaces de noms au lieu des varia-
Ne pas faire
Appliquer le mot-cl static une variable
Chapitre 18
Espaces de noms
625
Le nom Fenetre identie lespace de noms de faon unique. Plusieurs occurrences dun mme nom despace de noms peuvent exister dans le mme chier ou dans plusieurs units de compilation. Dans ce cas, les diffrentes instances seront fusionnes par le compilateur an de former un seul espace de noms. Cest dailleurs ce qui se passe pour lespace de noms de la bibliothque C++ Standard, std : la bibliothque standard est un regroupement logique de plusieurs fonctionnalits, mais elle est trop volumineuse et trop complexe pour tre stocke dans un seul chier. Le concept essentiel des espaces de noms est de regrouper les lments connexes dans un emplacement (nomm) spci. Les Listing 18.6a et 18.6b en montrent des exemples. Listing 18.6a : Regrouper des lments connexes
0: 1: 2: 3: 4: // entete1.h namespace Fenetre { void deplacer( int x, int y); }
626
Le langage C++
Comme vous pouvez le constater, lespace de noms Fenetre est rparti sur les deux chiers den-tte. Le compilateur considre les fonctions deplacer() et modifTaille() comme faisant partie de lespace de noms Fenetre.
Un espace de noms comme celui-ci devient trs rapidement confus ! Cet exemple fait peu prs 20 lignes ; imaginez si lespace de noms tait quatre fois plus grand !
Chapitre 18
Espaces de noms
627
Cette ligne de code est illgale. La solution est de dplacer la dclaration dans le corps de lespace de noms. Lorsque vous ajoutez de nouveaux membres, il ne faut pas ajouter de modicateurs daccs, comme private ou public. Tous les membres au sein dun espace de noms sont publics. Le code suivant ne pourra donc pas tre compil, car vous ne pouvez pas utiliser private :
namespace Fenetre { private: void deplacer( int x, int y ); }
628
Le langage C++
de noms qui lenglobe. Si vous avez imbriqu des espaces de noms, leurs noms devront donc tous apparatre dans la qualication. Lexemple suivant montre un espace de noms imbriqu dans un autre espace de noms :
namespace Fenetre { namespace Panneau { void taille( int x, int y ); } }
Pour accder la fonction taille() en dehors de lespace de nom Fenetre, vous devez qualier la fonction avec les deux noms des espaces de noms. Vous devrez donc utiliser la syntaxe suivante :
Fenetre::Panneau::taille( 10, 20 );
Chapitre 18
Espaces de noms
629
17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54:
int x; int y; }; } int Fenetre::Panneau::cpteur = 0; Fenetre::Panneau::Panneau(): x(0), y(0) { } Fenetre::Panneau::~Panneau() { } void Fenetre::Panneau::taille( int x, int y ) { if( x < Fenetre::MAX_X && x > 0 ) Panneau::x = x; if( y < Fenetre::MAX_Y && y > 0 ) Panneau::y = y; } void Fenetre::Panneau::deplacer( int x, int y ) { if( x < Fenetre::MAX_X && x > 0 ) Panneau::x = x; if( y < Fenetre::MAX_Y && y > 0 ) Panneau::y = y; } void Fenetre::Panneau::afficher( ) { std::cout << "x " << Panneau::x; std::cout << " y " << Panneau::y << std::endl; } int main( ) { Fenetre::Panneau panneau; panneau.deplacer( 20, 20 ); panneau.afficher( ); return 0; }
Vous remarquerez que la classe Panneau (lignes 7 19) est imbrique dans lespace de noms Fenetre (lignes 3 20). Cest la raison pour laquelle vous devez qualier le nom Panneau avec le qualicateur Fenetre::.
630
Le langage C++
La variable statique cpteur, dclare dans Panneau la ligne 16, est dnie de la faon habituelle. Notez que dans la mthode Panneau::taille(), MAX_X et MAX_Y sont qualis aux lignes 26 32 pour viter une erreur de compilation. Ceci est galement valable pour la mthode Panneau::deplacer(). Notez galement la qualication de Panneau::x et Panneau::y au sein des deux dnitions de mthodes. En effet, si la fonction Panneau::deplacer() tait crite de la faon suivante, vous auriez un souci :
void Fenetre::Panneau::deplacer( int x, int y ) { if( x < Fenetre::MAX_X && x > 0 ) x = x; if( y < Fenetre::MAX_Y && y > 0 ) y = y; Platforme::deplacer( x, y ); }
Voyez-vous o se situe le problme ? Le message de votre compilateur ne vous sera pas dun grand secours, certains nmettront mme aucun diagnostic. La source du problme rside dans les paramtres de la fonction : x et y masquent les variables dinstance prives x et y de la classe Panneau. En ralit, les instructions suivantes affecteraient x et y elles-mmes :
x = x; y = y;
Mot-cl using
Le mot-cl using est utilis la fois pour la directive using et la dclaration using. Cest la syntaxe employe qui permettra de dterminer sil sagit dune directive ou dune dclaration.
La directive using
La directive using expose dans la porte courante tous les noms dclars dans un espace de noms. Vous pouvez ensuite faire rfrence ces noms sans avoir besoin de les qualier laide de leurs espaces de noms respectifs. Par exemple :
namespace Fenetre { int valeur1 = 20; int valeur2 = 40; }
Chapitre 18
Espaces de noms
631
La porte de la directive using stend de sa dclaration jusqu la n de la porte courante. Remarquez que valeur1 doit tre qualie pour pouvoir tre rfrence, contrairement la valeur2 car la directive using a introduit dans la porte courante tous les noms de lespace de noms. La directive using peut tre utilise tous les niveaux de porte, ce qui permet de lemployer notamment dans la porte dun bloc ; lorsque ce bloc devient hors de porte, tous les noms de lespace de noms disparaissent galement. Examinez lexemple suivant :
namespace Fenetre { int valeur1 = 20; int valeur2 = 40; } //. . . void f() { { using namespace Fenetre; valeur2 = 30; } valeur2 = 20; // Erreur! }
La dernire ligne de code provoque une erreur car valeur2 nest pas dnie. Ce nom est accessible dans le bloc prcdent puisque la directive la intgr dans ce bloc. Lorsque ce dernier devient hors de porte, les noms de lespace de noms Fenetre le deviennent galement. Pour que valeur2 fonctionne la dernire ligne, vous devez la qualier totalement :
Fenetre::valeur2 = 20;
Les noms de variable dclars dans une porte locale masquent tous les noms de lespace de nom introduits dans cette porte. Ce comportement est similaire la faon dont une variable locale masque une variable globale. Mme si vous introduisez un espace de noms aprs une variable locale, celle-ci masquerait quand mme le nom de lespace de noms. Exemple :
namespace Fenetre { int valeur1 = 20; int valeur2 = 40; } //. . .
632
Le langage C++
void f() { int valeur2 = 10; using namespace Fenetre; std::cout << valeur2 << std::endl ; }
Cette fonction afchera 10 et non 40. La variable valeur2 de lespace de noms Fenetre est masque par la variable value2 locale f(). Si vous voulez utiliser un nom dun espace de noms, vous devez le qualier totalement. Une ambigut peut se produire lors de lutilisation dun nom dni la fois globalement et dans un espace de noms. Cette ambigut napparat que si le nom est employ, pas lors de lintroduction de lespace de noms. Le fragment de code suivant en fait la dmonstration :
namespace Fenetre { int valeur1 = 20; } //. . . using namespace Fenetre; int valeur1 = 10; void f( ) { valeur1 = 10; }
Lambigut intervient dans la fonction f(). La directive introduit effectivement Fenetre::valeur1 dans lespace de noms global ; valeur1 tant dj dnie globalement, lemploi de value1 dans f() est donc une erreur. Notez que si vous supprimez la ligne de code dans f(), aucune erreur napparatra.
La dclaration using
Une dclaration using ressemble une directive using, sauf quune dclaration permet un niveau de contrle plus n. Plus prcisment, une dclaration using sert dclarer quun nom spcique (provenant dun espace de noms) fait dsormais partie de la porte courante. Vous pouvez ensuite faire rfrence lobjet concern par son nom seul. Lexemple suivant montre comment utiliser une dclaration using :
namespace Fenetre int valeur1 = int valeur2 = int valeur3 = } //. . . { 20; 40; 60;
Chapitre 18
Espaces de noms
633
using Fenetre::valeur2; // met valeur2 dans la porte courante Fenetre::valeur1 = 10; // valeur1 doit tre qualifie valeur2 = 30; Fenetre::valeur3 = 10; // valeur3 doit tre qualifie
La dclaration using ajoute le nom indiqu la porte courante ; elle naffecte pas les autres noms de lespace de noms. Dans lexemple prcdent, valeur2 est utilise sans qualication, mais valeur1 et valeur3 requirent une qualication. Contrairement la directive, qui ajoute tous les noms dun espace de noms la porte courante, une dclaration permet donc de choisir ceux que lon veut ajouter. Lorsquun nom a t ajout une porte, il reste visible jusqu la n de celle-ci, comme pour toute dclaration. Une dclaration using peut tre utilise dans lespace de noms global ou dans nimporte quel porte locale. Il est interdit dintroduire un nom despace de noms existant dj dans une porte locale. Linverse est galement vrai. Lexemple suivant en fait la dmonstration :
namespace Fenetre { int valeur1 = 20; int valeur2 = 40; } //. . . void f() { int valeur2 = 10; using Fenetre::valeur2; // dclaration duplique std::cout << valeur2 << std::endl ; }
La deuxime ligne de la fonction f() provoquera une erreur de compilation, car le nom valeur2 est dj dni. La mme erreur se serait produite si la dclaration using avait t place avant la dnition de la variable locale valeur2. Tout nom introduit dans une porte locale laide dune dclaration using masque tout nom identique lextrieur de cette porte, comme le montre lexemple suivant :
namespace Fenetre { int valeur1 = 20; int valeur2 = 40; } int valeur2 = 10; //. . . void f() { using Fenetre::valeur2; std::cout << valeur2 << std::endl ; }
634
Le langage C++
La dclaration using de f() masque la variable valeur2 dnie dans lespace de noms global. Comme on la dj indiqu, une dclaration using permet un contrle plus n sur les noms introduits partir dun espace de noms. Une directive using ajoute tous les noms dun espace de noms la porte courante. Il est donc prfrable dutiliser une dclaration car une directive va, en fait, lencontre de lintrt du mcanisme des espaces de noms. Une dclaration est plus dnitive car on identie explicitement tous les noms que lon souhaite introduire dans une porte. Elle ne risque donc pas de polluer lespace de nom global comme une directive (sauf si, bien sr, vous dclarez tous les noms de lespace de noms). Avec une dclaration using, le masquage de nom, la pollution de lespace de noms global et les ambiguts sont rduits un niveau plus raisonnable.
Le risque, bien entendu, est dentrer un conit avec un nom existant. Dans ce cas, le compilateur le signalera.
Chapitre 18
Espaces de noms
635
Les Listings 18.9a et 18.9b illustrent deux espaces de noms anonymes dans deux chiers spars. Listing 18.9a : Un espace de nom anonyme
0: 1: 2: 3: 4: 5: 6: // fichier: un.cpp namespace { { int valeur; char p( char *p ); //. . . }
Si ces deux listings taient compils dans le mme excutable, chacun des noms valeur et p() serait distinct pour son chier respectif. Pour accder un nom (despace de noms anonyme) dans une unit, on utilise ce nom sans qualication. Cest ce que lon constate dans lexemple prcdent avec lappel la fonction p() dans chaque chier. Cette utilisation implique une directive using pour les objets rfrencs partir dun espace de noms anonyme. Il en rsulte que vous ne pouvez pas accder aux membres dun espace de noms anonyme dans une autre unit. Le comportement dun espace de noms anonyme est donc comparable celui dun objet statique ayant une liaison externe. Considrez lexemple suivant :
static int valeur = 10;
Souvenez-vous cependant que cet usage du mot-cl static est maintenant dconseill au prot des espaces de noms. On peut aussi comparer les espaces de noms anonymes des variables globales avec liaison interne.
636
Le langage C++
Vous savez maintenant que lutilisation de cette directive using place tout le contenu de std dans lespace de noms global. Il nest pas souhaitable demployer la directive using avec la bibliothque standard. Pourquoi ? Parce que cela pollue lespace de noms global de vos applications avec tous les noms trouvs dans len-tte. Souvenez-vous que tous les chiers den-tte utilisent cet espace de noms ; si vous incluez plusieurs chiers den-tte standard et que vous utilisez la directive using, tout ce qui est dclar dans ces en-ttes se retrouvera dans lespace de noms global. Vous aurez dailleurs peut-tre remarqu que la plupart des exemples de ce livre violent cette rgle, mais cest uniquement pour la brivet des exemples. Vous devriez plutt utiliser la dclaration using, comme dans le Listing 18.10. Listing 18.10 : La mthode correcte pour utiliser les lments despace de nom std
0: #include <iostream> 1: using std::cin; 2: using std::cout; 3: using std::endl; 4: int main( ) 5: { 6: int valeur = 0; 7: cout << "Combien doeufs voulez-vous?" << endl; 8: cin >> valeur; 9: cout << valeur << " oeufs au plat!" << endl; 10: return( 0 ); 11: }
Chapitre 18
Espaces de noms
637
Comme vous pouvez le constater, nous utilisons trois lments de lespace de noms std. Ils sont dclars des lignes 1 3. Vous pouvez aussi qualier pleinement les noms que vous utilisez, comme dans le Listing 18.11. Listing 18.11 : Qualier en ligne les lments des espaces de noms
0: 1: 2: 3: 4: 4a: 5: 6: 7: 8: #include <iostream> int main( ) { int valeur = 0; std::cout << "Combien doeufs voulez-vous?" << std::endl; std::cin >> valeur; std::cout << valeur << " oeufs au plat!" << std::endl; return( 0 ); }
Qualier en ligne les lments des espaces de nom est justi dans le cas de programmes courts, mais peut vite devenir fastidieux si le code devient plus important. Imaginez lennui davoir prxer par std:: chaque nom provenant de la bibliothque standard !
Questions-rponses
Q Est-il obligatoire davoir recours aux espaces de noms ? R Non : pour des programmes simples, vous pouvez vous passer des espaces de noms. Vriez que vous utilisez les anciennes bibliothques standard (par exemple, #include <string.h>) plutt que les nouvelles (comme #include <cstring>). Cependant, pour toutes les raisons que nous avons expliques dans ce chapitre, il est prfrable dutiliser les espaces de noms. Q C++ est-il le seul langage utiliser les espaces de noms ? R Non. Dautres langages les emploient aussi pour organiser et sparer les valeurs : Visual Basic 7 (.NET), C#, etc. Dautres langages disposent de concepts similaires. Java, par exemple, utilise des paquetages.
638
Le langage C++
Q Quest-ce quun espace de noms anonyme ? Quelle est son utilit ? R Cest un espace de noms qui na pas de nom. Il sert envelopper une collection de dclarations an dviter dventuels conits de noms. Les noms dans un espace de noms anonyme ne sont pas utilisables en dehors de lunit o il est dclar.
Quelle est la valeur de X lorsque ce programme atteind "ICI" dans ce listing ? 3. Est-il possible dutiliser des noms dnis dans un espace de noms sans avoir recours au mot-cl using ? 4. Quelles sont les principales diffrences entre un espace de nom normal et anonyme ? 5. Quelles sont les deux formes dinstructions utilisant le mot-cl using ? Quelles sont les diffrences entre les deux ? 6. Que sont les espaces de noms anonymes ? quoi servent-ils ? 7. Quest-ce que lespace de noms standard ?
Chapitre 18
Espaces de noms
639
Exercices
1. CHERCHEZ LERREUR ! Ce programme est bogu :
0: 1: 2: 3: 4: 5: #include <iostream> int main() { cout << "Bonjour!" << end; return 0; }
2. Proposez trois solutions pour rsoudre le problme du programme prcdent. 3. crivez le code permettant de dclarer un espace de noms MesElements. Cet espace de noms doit contenir une classe appele MaClasse.
19
Les modles
Au sommaire de ce chapitre
Les modles et leur utilisation La cration de modles de classes La cration de modles de fonctions La bibliothque des modles standard et certains de ses modles
Les types paramtrs ou modles constituent un outil trs puissant pour les programmeurs C++. Limportance des modles ayant t reconnue, la bibliothque des modles standard (STL, Standard Template Library) a t intgre la dnition du langage C++.
642
Le langage C++
Pour rsoudre ce problme, vous pouvez crer une classe de base Liste. Vous pourrez ainsi couper et coller entre la classe ListePieces et la nouvelle dclaration ListeChats. Plus tard, lorsque vous aurez besoin dune liste dobjets Voiture, vous devrez crer une nouvelle classe ListeVoitures et recommencer le copier/coller. Inutile de prciser quil ne sagit pas dune solution satisfaisante. Au cours du temps, la classe Liste et ses classes drives devront srement tre tendues. Sassurer que toutes les modications ncessaires sont propages dans toutes les classes concernes risque de devenir rapidement un cauchemar. Vous pourriez aussi faire hriter Chat de Pieces pour quun chat puisse entrer dans la hirarchie dhritage et quune collection de pices puisse aussi contenir des chats. Cela pose videmment un problme quant la propret de la hirarchie conceptuelle des classes, car les chats ne sont pas des pices. Il serait galement possible de crer une classe Liste qui contiendrait des objets dune classe "Object" et qui pourrait donc contenir tous les objets drivant de cette classe de base. Cependant, cela diminuerait le typage fort et compliquerait la tche du compilateur pour vrier que votre programme est correct. Vous voulez en fait crer une famille de classes apparentes, dont la seule diffrence sera le type des lments sur lequel elles oprent et vous souhaitez neffectuer les modications qu un seul endroit an de minimiser vos oprations de maintenance. La cration et lutilisation de modles reprsentent une solution ce problme. Bien qu lorigine ils taient absents du langage C++, ils font maintenant partie intgrante de la norme. Comme tous les lments de C++, ils sont souples et fournissent une gestion robuste des types, mais ils peuvent toutefois rebuter les dbutants. Cependant, lorsque aurez compris leur fonctionnement, vous constaterez quils sont trs utiles. Les modles permettent de crer une classe dont le type des lments quelle manipule peut tre modi. Vous pouvez, par exemple, les utiliser pour indiquer au compilateur comment fabriquer une liste de nimporte quel type dobjet au lieu de crer un ensemble de listes spciques ListePieces pour une liste de pices, ListeChats pour une liste de chats. En effet, le seul point qui diffrencie ces deux listes est le type de leurs lments. Avec les modles, ce type devient un paramtre de la dnition de la classe. Vous pouvez donc crer une famille de classes partir du modle, chacune tant congure pour fonctionner avec un type dlment diffrent. Les bibliothques C++ contiennent trs souvent une classe tableau. Mais crer une classe de tableaux pour les entiers, une autre pour les double et une troisime pour des animaux est fastidieux et inefcace. Les modles permettent de dclarer une seule classe de tableau paramtre, puis de prciser le type des lments de chaque instance du tableau.
Chapitre 19
Les modles
643
Bien que la bibliothque des modles standard fournit un jeu de classes conteneurs standardis contenant des tableaux, des listes et ainsi de suite, le meilleur moyen dapprendre les modles consiste crer les siens ! Dans les sections qui suivent, nous allons donc crer nos propres classes conteneurs pour bien comprendre comment fonctionnent les modles. Dans un contexte commercial, par contre, vous aurez tout intrt utiliser les classes de la STL au lieu de crer les vtres. La cration dun type spcique partir dun modle est appele instanciation, les classes individuelles sont alors appeles instances du modle. Les instances dun modle diffrent des instances dobjets cres laide du modle. Plus gnralement, "linstanciation" sert dsigner la cration dune instance (objet) partir dune classe. Il faut donc que le contexte dutilisation du mot "instanciation" soit bien clair.
Info
Les modles paramtrs permettent de crer une classe gnrale et de lui passer des types en paramtres pour construire des instances spciques. Avant de pouvoir instancier un modle, vous devez toutefois le dnir.
Ici, template et class sont des mots-cls. T est un emplacement un peu comme un nom de variable. Cet emplacement pourrait porter nimporte quel nom mais on utilise gnralement T ou Type. La valeur de T sera un type de donnes. Le mot-cl class pouvant tre confus dans ce contexte, vous pouvez utiliser le mot-cl typename la place :
template <typename T>
Dans ce chapitre, nous utiliserons le plus souvent la mot-cl class. Le mot-cl typename indique toutefois plus clairement ce que vous dnissez lorsque le paramtre du modle est un type primitif et non une classe.
644
Le langage C++
Si lon revient la cration de notre propre type tableau, nous pouvons donc utiliser linstruction template pour dclarer que notre classe Array est un type paramtr :
template <class T> // dclare le modle et le paramtre class Array // La classe qui est paramtre { public: Array(); // Ici, dclaration complte de la classe };
Ce code forme la base de la dclaration dune classe modle Array. Le mot-cl template dbute chaque dclaration et dnition dune classe modle. Il est suivi des paramtres du modle, qui changeront avec chaque instance, exactement comme pour les fonctions. Dans le modle de tableau prcdent, par exemple, on souhaite que le type des objets stocks dans le tableau puisse tre modi. Une instance pourra stocker un tableau dentiers, par exemple, alors quune autre stockera un tableau danimaux. Dans notre exemple, le mot-cl class est suivi de lidenticateur T. Comme on la indiqu plus haut, class signie que ce paramtre est un type. Lidenticateur T est utilis dans la suite de la dnition du modle pour dsigner le type qui aura t pass en paramtre. Cette classe tant maintenant un modle, une instance pourra par exemple remplacer T par int et une autre le remplacera par Chat. Sil est crit correctement, le modle doit pouvoir accepter nimporte quel type de donnes (ou classe) valide comme valeur de T. On xe le type pour le modle lorsque lon dclare la variable qui en sera une instance. On peut, pour cela, utiliser la syntaxe suivante :
nomClasse<type> instance;
Ici, nomClasse correspond au nom du modle, instance reprsente le nom de linstance ou de lobjet cr et type est le type de donnes utiliser pour linstance cre. Voici, par exemple, la dclaration dune instance int et dune instance Animal de la classe tableau paramtre :
Array<int> unTableauInt; Array<Animal> unTableauAnimal;
Lobjet unTableauInt est de type tableau dentiers et lobjet unTableauAnimal est de type tableau danimaux. Vous pouvez maintenant utiliser le type Array<int> partout o vous utiliseriez un type en temps normal comme type du rsultat ou dun paramtre dune fonction, etc. Le Listing 19.1 prsente une dclaration complte dun modle de tableau simpli (attention, ce nest pas un programme complet, mais plutt un code consacr la dnition du modle).
Chapitre 19
Les modles
645
La dclaration du modle commence la ligne 5. Le paramtre, identi par le mot-cl class, est un type et lidenticateur T reprsente le type paramtr. Comme on la dj prcis, vous auriez galement pu utiliser le mot typename la place de class :
5: template <typename T> // dclare le modle et son paramtre
Utilisez ce qui vous semble le plus clair, bien quil soit prfrable demployer class lorsque le type est une classe et typename dans les autres cas. La suite de la dclaration ressemble nimporte quelle dclaration de classe dans laquelle le type de lobjet serait remplac par T. Par exemple, operator[] est dclar pour renvoyer une rfrence un objet de type T car, dhabitude, il renvoie une rfrence un objet du tableau. Quand une instance dun tableau dentiers sera dnie, T sera remplac par un entier ; la mthode operator[], qui est fournie pour ce tableau, renverra donc une rfrence dentier, ce qui quivaudra :
int& operator[](int indice) { return pType[indice]; }
646
Le langage C++
Lorsquune instance dun tableau dAnimal sera dclare, operator[] renverra une rfrence dun Animal :
Animal& operator[](int indice) { return pType[indice]; }
Ceci ressemble beaucoup au fonctionnement des macros et, en fait, les modles ont t crs pour rduire le nombre de macros en C++.
Utilisation du nom
Dans la dclaration de la classe, le mot Array peut tre utilis sans autre qualication. Ailleurs dans le programme, cette classe sera dsigne par Array<T>. Si, par exemple, vous ne codez pas le constructeur dans la dclaration de la classe, la dclaration de cette mthode devra tre crite de la faon suivante :
template <class T> Array<T>::Array(int taille): saTaille = taille { pType = new T[taille]; for (int i = 0; i < taille; i++) pType[i] = 0; }
Comme elle fait partie dun modle, la premire ligne de cette dclaration est ncessaire pour identier le type pour la fonction (class T). la deuxime ligne, le nom du modle est Array<T>, et le nom de la fonction est Array(int taille). Vous pouvez galement constater que cette mthode attend un entier en paramtre. La suite de cette fonction ne prsente pas de caractristiques particulires, sauf quon emploie le paramtre T partout o le type des lments du tableau doit tre utilis.
ce Astu
Une pratique courante consiste dclarer normalement la classe et ses mthodes avant de la transformer en modle. Le dveloppement sen trouve simpli et cela permet de se concentrer tout dabord sur lobjectif de programmation, puis de gnraliser la solution avec les modles. Vous devez dnir les mthodes du modle dans le chier o il est dclar. la diffrence des autres classes, o il est possible de placer la dclaration dune classe et de ses fonctions membres dans un chier en-tte et leurs dnitions dans un chier .cpp, les modles exigent que ces deux parties se trouvent ensemble soit dans un chier en-tte, soit dans un chier .cpp. Si vous partagez le modle avec dautres parties de votre projet, il est courant de dnir les fonctions membres en ligne, dans la dclaration du modle, ou de les dnir juste aprs la dclaration de la classe, dans le chier en-tte.
Chapitre 19
Les modles
647
Implmentation du modle
Aprs avoir dni un modle, vous voudrez lutiliser. Limplmentation complte du tableau de la classe modle ncessite limplmentation du constructeur de copie, de operator=, etc. Le Listing 19.2 vous prsente le code du modle Array ainsi quun programme de test simple, qui utilise ce modle. Bien que les modles fassent partie du standard ANSI C++, certains anciens compilateurs ne les supportent pas. Si votre compilateur en fait partie, vous devrez installer sa version la plus rcente si vous voulez compiler les exercices de ce chapitre. Cela ne doit pas vous empcher dtudier ce chapitre, quitte y revenir plus tard quand votre compilateur aura t mis jour.
Info
648
Le langage C++
31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78:
{ public: // constructeurs Array(int taille = TailleDefaut); Array(const Array &rhs); ~Array() { delete [] pType; } // oprateurs Array& operator=(const Array&); T& operator[](int indice) { return pType[indice]; } const T& operator[](int indice) const { return pType[indice]; } // mthode daccs int GetTaille() const { return saTaille; } private: T *pType; int saTaille; }; // Implmentations // implmentation du Constructeur template <class T> Array<T>::Array(int taille ): saTaille(taille) { pType = new T[taille]; // les constructeurs du type que vous crez // doivent fixer une valeur par dfaut } // constructeur copie template <class T> Array<T>::Array(const Array &rhs) { saTaille = rhs.GetTaille(); pType = new T[saTaille]; for (int i = 0; i < saTaille; i++) pType[i] = rhs[i]; } // operator= template <class T> Array<T>& Array<T>::operator=(const Array &rhs) { if (this == &rhs) return *this;
Chapitre 19
Les modles
649
79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89: 90: 91: 92: 93: 94: 95: 96: 97: 98: 99: 100: 101: 102: 103: 104: 105: 106: 107: 108: 109: 110: 111: 112: 113:
delete [] pType; saTaille = rhs.GetTaille(); pType = new T[saTaille]; for (int i = 0; i < saTaille; i++) pType[i] = rhs[i]; return *this; } // programme de test int main() { Array<int> unTableau; Array<Animal> unZoo; Animal *pAnimal;
// remplissage des tableaux for (int i = 0; i < unTableau.GetTaille(); i++) { unTableau[i] = i*2; pAnimal = new Animal(i*3); unZoo[i] = *pAnimal; delete pAnimal; } // Affichage du contenu des tableaux for (int j = 0; j < unTableau.GetTaille(); j++) { std::cout << "unTableau[" << j << "]:\t"; std::cout << unTableau[j] << "\t\t"; std::cout << "unZoo[" << j << "]:\t"; unZoo[j].Affiche(); std::cout << std::endl; } return 0; }
650
Le langage C++
Ce programme est assez basique, mais il illustre bien la cration et lutilisation dun modle. Ici, on dnit et on utilise le modle Array pour crer des instances dobjets Array de types int et Animal. Le tableau dentiers est rempli dentiers valant deux fois la valeur de lindice. Lobjet Array constitu dobjets Animal est appel unZoo ; il est rempli de valeurs gales trois fois la valeur de lindex. Les lignes 8 26 fournissent une classe Animal minimale dont le seul but est dillustrer la manipulation dun type dni par lutilisateur avec un modle. Linstruction de ligne 29 dbute la dclaration du modle dont le paramtre est un type dsign par T. Cette ligne aurait aussi pu tre dclare avec typename. Les lignes 34 et 35 dclarent les deux constructeurs de la classe Array ; le premier prend une taille en paramtre, qui vaut par dfaut la valeur de la constante TailleDefaut. Les oprateurs daffectation et dindexation sont ensuite dclars. La seule mthode daccs est GetTaille() (ligne 44), qui renvoie la taille du tableau. Cette interface est, bien sr, incomplte. Un programme Array srieux ncessiterait, au minimum, des oprateurs pour supprimer des lments, tendre le tableau, remplir le tableau, etc. Quand vous utiliserez la classe Array de la bibliothque standard, vous dcouvrirez que toutes ces fonctionnalits ont t fournies. Vous en saurez plus dans ce chapitre. Les donnes private de la classe du modle Array sont la taille du tableau et le pointeur du tableau dobjets en mmoire. partir de la ligne 53 commence limplmentation de certaines fonctions membres de la classe modle. Comme elles sont dnies lextrieur de la dclaration de la classe, il faut indiquer nouveau quelles font partie du modle laide de la mme instruction que lon avait place avant la classe (ligne 54). On prcise galement que Array est une classe modle en incluant le paramtre de type aprs le nom de la classe : comme on la appel T la ligne 53, on utilise donc Array<T> avec les fonctions membres (ligne 55). Dans une fonction membre, on peut utiliser le paramtre T partout o lon aurait normalement employ le type dun lment du tableau. La ligne 58, par exemple, dnit le pointeur pType pour quil pointe vers un nouveau tableau dlments de type T, cest--dire celui dclar lors de linstanciation dun objet avec cette classe modle. Lorsquun lment dun type donn est cr, sa construction doit linitialiser. Cette procdure se rpte avec la dclaration du constructeur de copie aux lignes 64 71 et avec la surcharge de loprateur daffectation aux lignes 74 85. La classe modle Array est, en fait, utilise aux lignes 90 et 91. La ligne 90 instancie un objet unTableau, qui utilise le modle avec des int. La ligne 91 instancie unZoo pour tre un tableau dlments de type Animal.
Chapitre 19
Les modles
651
Le reste du programme effectue ce qui a t dcrit prcdemment et est assez simple comprendre.
o UneFonction est le nom de la fonction laquelle vous passez lobjet Array et unType est le type des lments du tableau. Par consquent, si UneFonction() reoit un tableau dentiers en paramtre, vous pouvez crire :
void UneFonction(Array<int>&); // OK
et non :
void UneFonction(Array<T>&); // Erreur!
car il ny a aucun moyen de savoir ce que reprsente T&. Vous ne pouvez pas non plus crire :
void UneFonction(Array &); // Erreur!
parce quil nexiste pas de classe Array, uniquement un modle et des instances. Pour crer des fonctions non membres procurant certains avantages des modles, vous pouvez dclarer une fonction modle de la mme manire que pour la dclaration dune classe modle et la dnition dune fonction membre modle. Il faut dabord indiquer que la fonction est un modle, puis utiliser le paramtre de modle o lon aurait employ un type ou un nom de classe :
template <class T> void MaFonctionModele(Array<T>&); // OK
La premire ligne indique que la fonction MaFonctionModele() est une fonction modle. Les fonctions modles peuvent galement prendre en paramtres des instances du modle, en plus de la forme paramtre, comme ici :
template <class T> void MonAutreFonction(Array<T>&, Array<int>&); // OK
652
Le langage C++
Vous pouvez remarquer que cette fonction reoit deux tableaux : un tableau paramtr et un tableau dentiers. Le premier peut contenir tout type dobjet, mais le second devra toujours tre un tableau dentiers. Nous verrons un peu plus loin un exemple de fonction modle.
Modles et amis
Vous avez vu les amis au Chapitre 16. Les classes modles peuvent dclarer trois types damis :
une fonction ou une classe amie qui nest pas un modle ; une fonction ou une classe amie modle gnrale ; une fonction ou une classe amie modle spcique un type.
Chapitre 19
Les modles
653
10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54:
class Animal { public: Animal(int); Animal(); ~Animal() {} int GetPoids() const { return sonPoids; } void Affiche() const { cout << sonPoids; } private: int sonPoids; }; Animal::Animal(int poids): sonPoids(poids) {} Animal::Animal(): sonPoids(0) {} template <class T> // dclaration du modle et du paramtre class Array // la classe paramtrer { public: // constructeurs Array(int taille = TailleDefaut); Array(const Array &rhs); ~Array() { delete [] pType; } // oprateurs Array& operator=(const Array&); T& operator[](int indice) { return pType[indice]; } const T& operator[](int indice) const { return pType[indice]; } // mthode daccs int GetTaille() const { return saTaille; } // fonction amie friend void Introduire(Array<int>); private: T *pType; int saTaille; };
654
Le langage C++
55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89: 90: 91: 92: 93: 94: 95: 96: 97: 98: 99:
// fonction amie. Ce nest pas un modle, utiliser // uniquement avec des tableaux dentiers! void Introduire(Array<int> unTableau) { cout << endl << "*** Introduire ***" << endl; for (int i = 0; i < unTableau.saTaille; i++) cout << "i: " << unTableau.pType[i] << endl; cout << endl; } // Implmentations... // implmentation du constructeur template <class T> Array<T>::Array(int taille): saTaille(taille) { pType = new T[taille]; // les constructeurs du type cr // doivent dfinir une valeur par dfaut } // constructeur de copie template <class T> Array<T>::Array(const Array &rhs) { saTaille = rhs.GetTaille(); pType = new T[saTaille]; for (int i = 0; i < saTaille; i++) pType[i] = rhs[i]; } // operator= template <class T> Array<T>& Array<T>::operator=(const Array &rhs) { if (this == &rhs) return *this; delete [] pType; saTaille = rhs.GetTaille(); pType = new T[saTaille]; for (int i = 0; i < saTaille; i++) pType[i] = rhs[i]; return *this; }
Chapitre 19
Les modles
655
100: 101: 102: 103: 104: 105: 106: 107: 108: 109: 110: 111: 112: 113: 114: 115: 116: 117: 118: 119: 120: 121: 122: 123: 124: 125: 126: 127: 128: 129:
// programme de test int main() { Array<int> unTableau; Array<Animal> unZoo; Animal *pAnimal;
// remplissage des tableaux for (int i = 0; i < unTableau.GetTaille(); i++) { unTableau [i] = i*2; pAnimal = new Animal(i*3); unZoo[i] = *pAnimal; } int j; for (j = 0; j < unTableau.GetTaille(); j++) { cout << "unZoo[" << j << "]:\t"; unZoo[j].Affiche(); cout << endl; } cout << "Utilisons maintenant la fonction amie "; cout << "pour trouver les membres de Array<int>"; Introduire(unTableau); cout << endl << "Fin." << endl; return 0; }
656
Le langage C++
i: i: i: i: i: i: i: i: i:
2 4 6 8 10 12 14 16 18
Fin.
La fonction amie Introduire() a t ajoute la dclaration du modle Array. Cet ajout la ligne 48 signie que chaque instance dun Array de int considrera Introduire() comme une fonction amie. Celle-ci aura donc accs aux donnes membres et aux fonctions prives de linstance du tableau. La fonction Introduire() est dnie de la ligne 57 la ligne 63. Introduire() accde directement saTaille la ligne 60 et pType la ligne suivante. Cette utilisation triviale des donnes membres tait inutile puisque que la classe Array fournit des mthodes daccs publiques pour ces donnes, mais cela permet de montrer comment dclarer des fonctions amies avec les modles.
Pour ce faire; il faut dclarer operator<< comme tant une fonction modle :
template <class T> ostream& operator<< (ostream&, Array<T>&)
operator<< tant dsormais une fonction modle, il ne reste plus qu lui fournir une implmentation. Le Listing 19.4 dveloppe le modle Array pour inclure cette dclaration et fournir limplmentation du modle operator<<.
Chapitre 19
Les modles
657
658
Le langage C++
45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89: 90: 91:
private: T *pType; int saTaille; }; template <class T> ostream& operator<<(ostream& sortie, Array<T>& unTableau) { for (int i = 0; i < unTableau.saTaille; i++) sortie << "[" << i << "] " << unTableau[i] << endl; return sortie; } // Implmentations... // Implmentation du constructeur template <class T> Array<T>::Array(int taille): saTaille(taille) { pType = new T[taille]; for (int i = 0; i < taille; i++) pType[i] = 0; } // constructeur de copie template <class T> Array<T>::Array(const Array &rhs) { saTaille = rhs.GetTaille(); pType = new T[saTaille]; for (int i = 0; i < saTaille; i++) pType[i] = rhs[i]; } // operator= template <class T> Array<T>& Array<T>::operator=(const Array &rhs) { if (this == &rhs) return *this; delete [] pType; saTaille = rhs.GetTaille(); pType = new T[saTaille]; for (int i = 0; i < saTaille; i++) pType[i] = rhs[i]; return *this;
Chapitre 19
Les modles
659
92: 93: 94: 95: 96: 97: 98: 99: 100: 101: 102: 103: 104: 105: 106: 107: 108: 109: 110: 111: 112: 113: 114: 115: 116: 117: 118: 119: 120: 121:
} int main() { bool Stop = false; int indice, valeur; Array<int> leTableau ;
// indicateur de boucle
while (Stop == false) { cout << "Entrez un indice (0-9) "; cout << "et une valeur. (-1 pour arrter) : "; cin >> indice >> valeur; if (indice < 0) break; if (indice > 9) { cout << "Entrez un indice entre 0 et 9.\n"; continue; } leTableau[indice] = valeur; } cout << "\nVoici le tableau complet:\n"; cout << leTableau << endl; return 0; }
Info
Si vous utilisez un compilateur Windows, dcommentez la ligne 42. Selon la norme de C++, cette ligne ne devrait pas tre ncessaire, mais elle lest pour le compilateur Microsoft C++.
660
Le langage C++
Entrez un indice (0-9) et une valeur. (-1 pour arrter) : 1010 Entrez un indice entre 0 et 9. Entrez un indice (0-9) et une valeur. (-1 pour arrter) : -1 -1 Voici le tableau complet: [0] 0 [1] 10 [2] 20 [3] 30 [4] 40 [5] 50 [6] 60 [7] 70 [8] 80 [9] 90
La ligne 43 dclare la fonction modle operator<<() comme amie du modle Array. operator<<() tant implmente comme une fonction modle, chaque instance de son type de tableau paramtr possdera automatiquement un operator<<(). Limplmentation de cet oprateur commence la ligne 50. La boucle simple des lignes 53 et 54 utilise tour tour aux lments du tableau. Cette technique ne peut videmment fonctionner que si operator<<() est dni pour tous les types dobjet stocks dans le tableau. Vous remarquerez que ce listing exige galement que operator[] ait t surcharg. Comme vous pouvez le constater la ligne 37, cest ce que nous avons fait en utilisant galement le paramtre du type..
Chapitre 19
Les modles
661
6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50:
// classe simple ajouter aux tableaux class Animal { public: // constructeurs Animal(int); Animal(); ~Animal(); // mthodes daccs int GetPoids() const { return sonPoids; } void SetPoids(int poids) { sonPoids = poids; } // oprateurs amis friend ostream& operator<< (ostream&, const Animal&); private: int sonPoids; }; // oprateur dinsertion pour afficher les animaux ostream& operator<< (ostream& unFlux, const Animal& unAnimal) { unFlux << unAnimal.GetPoids(); return unFlux; } Animal::Animal(int poids): sonPoids(poids) { // cout << "Animal(int)" << endl; } Animal::Animal(): sonPoids(0) { // cout << "Animal()" << endl; } Animal::~Animal() { // cout << "Destruction dun animal..." << endl; }
662
Le langage C++
51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89: 90: 91: 92: 93: 94: 95: 96: 97:
template <class T> // dclaration du modle et du paramtre class Array // la classe qui est paramtre { public: Array(int taille = TailleDefaut); Array(const Array &rhs); ~Array() { delete [] pType; } Array& operator=(const Array&); T& operator[](int indice) { return pType[indice]; } const T& operator[](int indice) const { return pType[indice]; } int GetTaille() const { return saTaille; } // fonction amie // template <class T> friend ostream& operator<< (ostream&, const Array<T>&); private: T *pType; int saTaille; }; template <class T> ostream& operator<< (ostream& out, const Array<T>& unTableau) { for (int i = 0; i < unTableau.saTaille; i++) out << "[" << i << "] " << unTableau[i] << endl; return out; } // Partie des implmentations... // Implmentation du constructeur template <class T> Array<T>::Array(int taille): saTaille(taille) { pType = new T[taille]; for (int i = 0; i < taille; i++) pType[i] = 0; } // constructeur de copie template <class T> Array<T>::Array(const Array &rhs) { saTaille = rhs.GetTaille();
Chapitre 19
Les modles
663
98: 99: 100: 101: 102: 103: 104: 105: 106: 107: 108: 109: 110: 111: 112: 113: 114: 115: 116: 117: 118: 119: 120: 121: 122: 123: 124: 125: 126: 127: 128: 129: 130: 131: 132: 133: 134: 135: 136: 137: 138: 139: 140: 141: 142:
pType = new T[saTaille]; for (int i = 0; i < saTaille; i++) pType[i] = rhs[i]; } void RemplirEntiers(Array<int>& unTableau); void RemplirAnimaux(Array<Animal>& unTableau); int main() { Array<int> tabInt; Array<Animal> tabAnimal; RemplirEntiers(tabInt); RemplirAnimaux(tabAnimal); cout << "tabInt...\n" << tabInt; cout << "\ntabAnimal...\n" << tabAnimal << endl; return 0; } void RemplirEntiers(Array<int>& unTableau) { bool Stop = false; int indice, valeur; while (Stop == false) { cout << "Entrez un indice (0-9) "; cout << "et une valeur. (-1 pour arrter) : "; cin >> indice >> valeur; if (indice < 0) break; if (indice > 9) { cout << "Entrez un indice entre 0 et 9.\n"; continue; } unTableau[indice] = valeur; } }
void RemplirAnimaux(Array<Animal>& unTableau) { Animal * pAnimal; for (int i = 0; i < unTableau.GetTaille(); i++) {
664
Le langage C++
pAnimal = new Animal; pAnimal->SetPoids(i*100); unTableau[i] = *pAnimal; delete pAnimal; // copie place dans le tableau } }
Info
Si vous utilisez un compilateur Windows, dcommentez la ligne 65. Selon la norme C++, cette ligne ne devrait pas tre ncessaire, mais elle lest pour le compilateur Microsoft C++.
tabInt... [0] 0 [1] 10 [2] 20 [3] 30 [4] 40 [5] 50 [6] 60 [7] 70 [8] 80 [9] 90 tabAnimal... [0] 0 [1] 100 [2] 200 [3] 300
Chapitre 19
Les modles
665
La classe Animal est dclare des lignes 7 24. Bien que cette classe soit trs simplie, elle contient son propre oprateur dinsertion (<<) pour lafchage des animaux. Comme vous pouvez le constater, cet afchage se borne afcher le poids de lanimal. Vous remarquerez que Animal a un constructeur par dfaut. Ce constructeur est ncessaire, car cest lui qui cre lobjet lorsquon lajoute un tableau, ce qui pose quelques problmes, comme nous le verrons bientt. La fonction RemplirEntiers() est dclare la ligne 103. Son prototype indique que cette fonction prend en paramtre un Array dentiers. Ce nest pas une fonction modle, car elle ne connat quun type de Array : un tableau dentiers. De faon analogue, la fonction RemplirAnimaux() de la ligne 104 est dclare pour les Array danimaux. Les implmentations de ces deux fonctions sont diffrentes parce que remplir un tableau dentiers ne peut pas se faire de la mme faon que le remplissage dun tableau danimaux.
666
Le langage C++
Chapitre 19
Les modles
667
45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89: 90: 91:
Animal::~Animal() { cout << "Destruction dun animal..."; } template <class T> // dclare le modle et le paramtre class Array // la classe qui est paramtre { public: Array(int taille =::TailleDefaut); Array(const Array &rhs); ~Array() { delete [] pType; } // oprateurs Array& operator=(const Array&); T& operator[](int indice) { return pType[indice]; } const T& operator[](int indice) const { return pType[indice]; } // mthode daccs int GetTaille() const { return saTaille; } // fonction amie // template <class T> friend ostream& operator<< (ostream&, const Array<T>&); private: T *pType; int saTaille; }; template <class T> Array<T>::Array(int taille): saTaille(taille) { pType = new T[taille]; for (int i = 0; i < taille; i++) pType[i] = (T)0; } template <class T> Array<T>& Array<T>::operator=(const Array &rhs) { if (this == &rhs) return *this; delete [] pType; saTaille = rhs.GetTaille(); pType = new T[saTaille];
668
Le langage C++
92: 93: 94: 95: 96: 97: 98: 99: 100: 101: 102: 103: 104: 105: 106: 107: 108: 109: 110: 111: 112: 113: 114: 115: 116: 117: 118: 119: 120: 121: 122: 123: 124: 125: 126: 127: 128: 129: 130: 131: 132: 133: 134: 135: 136: 137: 138:
for (int i = 0; i < saTaille; i++) pType[i] = rhs[i]; return *this; } template <class T> Array<T>::Array(const Array &rhs) { saTaille = rhs.GetTaille(); pType = new T[saTaille]; for (int i = 0; i < saTaille; i++) pType[i] = rhs[i]; }
template <class T> ostream& operator<<(ostream& out, const Array<T>& unTableau) { for (int i = 0; i < unTableau.GetTaille(); i++) out << "[" << i << "] " << unTableau[i] << endl; return out; }
void RemplirEntiers(Array<int>& unTableau); void RemplirAnimaux(Array<Animal>& unTableau); int main() { Array<int> tabInt; Array<Animal> tabAnimal; RemplirEntiers(tabInt); RemplirAnimaux(tabAnimal); cout << "tabInt...\n" << tabInt; cout << "\ntabAnimal...\n" << tabAnimal << endl; return 0; } void RemplirEntiers(Array<int>& unTableau) {
Chapitre 19
Les modles
669
139: 140: 141: 142: 143: 144: 145: 146: 147: 148: 149: 150: 151: 152: 153: 154: 155: 156: 157: 158: 159: 160: 161: 162: 163: 164: 165: 166: 167:
bool Stop = false; int indice, valeur; while (Stop == false) { cout << "Entrez un indice (0-2) et une valeur. "; cout << "(-1 pour arrter): "; cin >> indice >> valeur; if (indice < 0) break; if (indice > 2) { cout << "Entrer un indice entre 0 et 2.\n"; continue; } unTableau[indice] = valeur; } }
void RemplirAnimaux(Array<Animal>& unTableau) { Animal * pAnimal; for (int i = 0; i < unTableau.GetTaille(); i++) { pAnimal = new Animal(i*10); unTableau[i] = *pAnimal; delete pAnimal; } }
Info
Si vous utilisez un compilateur Windows, dcommentez la ligne 67. Selon la norme C++, cette ligne ne devrait pas tre ncessaire, mais elle lest pour le compilateur Microsoft C++. Les numros de lignes ont t ajouts la sortie de ce programme an den faciliter lanalyse. Vous ne les verrez pas apparatre sur votre cran.
670
Le langage C++
5: Entrez un indice (0-2) et une valeur. (-1 pour arrter): -1 -1 6: Animal(int) Destruction dun animal...Animal(int) Destruction dun animal...Animal(int) Destruction dun animal...tabInt... 7: [0] 0 8: [1] 1 9: [2] 2 10: 11: tabAnimal... 12: [0] 0 13: [1] 10 14: [2] 20 15: 16: Destruction dun animal...Destruction dun animal...Destruction dun animal...
Seconde excution :
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: Animal(int) Destruction dun Animal(int) Destruction dun Animal(int) Destruction dun Entrez un indice (0-9) et une Entrez un indice (0-9) et une Entrez un indice (0-9) et une Entrez un indice (0-9) et une Animal(int) Destruction dun animal... Animal(int) Destruction dun animal... Animal(int) Destruction dun animal... tabInt... [0] 0 [1] 1 [2] 2 tabAnimal... [0] 0 [1] 10 [2] 20 Destruction dun animal... Destruction dun animal... Destruction dun animal... animal... animal... animal... valeur. (-1 valeur. (-1 valeur. (-1 valeur. (-1
00 11 22 33
Chapitre 19
Les modles
671
Le Listing 19.6 reproduit les deux classes dans leur intgralit pour illustrer la cration et la destruction des objets Animal temporaires. La valeur de TailleDefaut a t rduite de 10 3 pour simplier la sortie. Les constructeurs et destructeurs de Animal, aux lignes 33 48, afchent chacun un message qui signale leur appel. Un constructeur Array est dclar lignes 75 82 en tant que modle. Les lignes 116 120 montrent comment crer un constructeur spcialis pour un Array danimaux. Vous remarquerez que, dans ce constructeur spcial, le constructeur par dfaut peut dnir la valeur initiale de chaque Animal et quil ny a aucune affectation explicite. La premire excution de ce programme donne la premire srie de messages. La premire ligne montre les trois constructeurs par dfaut qui sont appels pour crer le tableau. Lutilisateur entre ensuite quatre nombres qui sont stocks dans le tableau dentiers. Lexcution se poursuit avec RemplirAnimaux(). Un objet Animal temporaire est alors cr la ligne 163 et sa valeur sert modier lobjet Animal du tableau la ligne suivante. Cet Animal temporaire est dtruit la ligne 165. Les messages afchs par la ligne 6 indiquent que ce processus se rpte pour chaque lment du tableau. Les tableaux sont dtruits la n du programme. Lorsque leurs destructeurs sont appels, tous les objets quils contiennent sont galement dtruits, comme le montrent les messages de la ligne 16. Pour la seconde srie de messages, on a mis en commentaire limplmentation spciale du constructeur du tableau danimaux, aux lignes 116 120 du programme. Lorsque le programme est de nouveau excut, cest donc le constructeur modle (lignes 75 82 du programme) qui est appel quand le tableau Animal est construit. Cela provoque lappel dobjets Animal temporaires pour chaque lment du tableau aux lignes 80 et 81 du programme (messages des lignes 1 3 de la seconde srie de rsultats). La suite du droulement du programme est identique celle de la premire excution.
672
Le langage C++
Listing 19.7 : Utilisation de fonctions membres et de donnes membres statiques avec les modles
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: #include <iostream> using namespace std; const int TailleDefaut= 3; // Une classe simple ajouter aux tableaux class Animal { public: // constructeurs Animal(int); Animal(); ~Animal(); // Mthodes daccs int GetPoids() const { return sonPoids; } void SetPoids(int poids) { sonPoids = poids; } // Oprateur ami friend ostream& operator<< (ostream&, const Animal&); private: int sonPoids; }; // oprateur dinsertion pour afficher les animaux ostream& operator<< (ostream& unFlux, const Animal& unAnimal) { unFlux << unAnimal.GetPoids(); return unFlux; } Animal::Animal(int poids): sonPoids(poids) { //cout << "Animal(int) "; } Animal::Animal(): sonPoids(0) {
Chapitre 19
Les modles
673
42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85:
//cout << "Animal() "; } Animal::~Animal() { //cout << "Un animal dtruit..."; } template <class T> // dclare le modle et le paramtre class Array // la classe qui est paramtre { public: // constructeurs Array(int taille = TailleDefaut); Array(const Array &rhs); ~Array() { delete [] pType; sonNbreArray--; } // oprateurs Array& operator=(const Array&); T& operator[](int indice) { return pType[indice]; } const T& operator[](int indice) const { return pType[indice]; } // mthodes daccs int GetTaille() const { return saTaille; } static int GetNbreArray() { return sonNbreArray; } // fonction amie friend ostream& operator<< (ostream&, const Array<T>&); private: T *pType; int saTaille; static int sonNbreArray; }; template <class T> int Array<T>::sonNbreArray = 0; template <class T> Array<T>::Array(int taille): saTaille(taille) { pType = new T[taille]; for (int i = 0; i < taille; i++)
674
Le langage C++
86: 87: 88: 89: 90: 91: 92: 93: 4: 95: 96: 97: 98: 99: 100: 101: 102: 103: 104: 105: 106: 107: 108: 109: 110: 111: 112: 113: 114: 115: 116: 117: 118: 119: 120: 121: 122: 122a: 123: 124: 125: 126: 127: 128: 128a:
pType[i] = (T)0; sonNbreArray++; } template <class T> Array<T>& Array<T>::operator=(const Array &rhs) { if (this == &rhs) return *this; delete [] pType; saTaille = rhs.GetTaille(); pType = new T[saTaille]; for (int i = 0; i < saTaille; i++) pType[i] = rhs[i]; } template <class T> Array<T>::Array(const Array &rhs) { saTaille = rhs.GetTaille(); pType = new T[saTaille]; for (int i = 0; i < saTaille; i++) pType[i] = rhs[i]; sonNbreArray++; } template <class T> ostream& operator<<(ostream& out, const Array<T>& unTableau) { for (int i = 0; i < unTableau.GetTaille(); i++) out << "[" << i << "] " << unTableau[i] << endl; return out; } int main() { cout << Array<int>::GetNbreArray() << " tableaux dentiers\n"; cout << Array<Animal>::GetNbreArray(); cout << " tableaux danimaux" << endl << endl; Array<int> tabInt; Array<Animal> tabAnimal; cout << tabInt.GetNbreArray() << " tableaux dentiers\n";
Chapitre 19
Les modles
675
129: 130: 131: 132: 133: 134: 134a: 135: 136: 137: 138: 139: 140: 140a: 141: 142: 143: 144:
cout << tabAnimal.GetNbreArray(); cout << " tableaux danimaux" << endl << endl; Array<int> *pTabInt = new Array<int>; cout << << cout << cout << Array<int>::GetNbreArray() " tableaux dentiers\n"; Array<Animal>::GetNbreArray(); " tableaux danimaux" << endl << endl;
delete pTabInt; cout << Array<int>::GetNbreArray() << " tableaux dentiers\n"; cout << Array<Animal>::GetNbreArray(); cout << " tableaux danimaux" << endl << endl; return 0; }
La ligne 74 ajoute la variable statique sonNbreArray la classe Array. Cette donne tant prive, la mthode daccs statique GetNbreArray() a t ajoute la ligne 66. Linitialisation de la donne statique est ralise aux lignes 77 et 78, avec une qualication complte qui mentionne le modle. Les constructeurs et le destructeur de Array ont galement t modis pour mmoriser le nombre de tableaux existants un instant donn. Laccs ces membres statiques est identique celui des membres statiques de nimporte quelle classe : il peut tre ralis avec un objet existant, comme le montrent les lignes 134 et 135 ou en utilisant la spcication complte de la classe, comme le montrent les
676
Le langage C++
lignes 128 et 129. Vous remarquerez que vous devez utiliser un type de tableau spcique pour accder aux donnes statiques. Il nexiste quune seule variable pour chaque type.
Faire
Utiliser des modles chaque fois que vous
Ne pas faire
Ne pas continuer vous intresser aux mod-
avez un concept pouvant fonctionner sur des objets de diffrentes classes ou sur diffrents types de donnes primitives.
Utiliser les paramtres des fonctions modles
les. Ce chapitre na prsent que quelquesunes des applications des modles. Une prsentation complte sort du cadre de ce livre.
Vous inquiter si vous navez pas tout
compris les techniques de cration des modles. Pour linstant, il est plus important de savoir les utiliser. Comme vous le verrez dans la prochaine section, la STL contient un grand nombre de modles prts lemploi.
Les conteneurs
Un conteneur est un objet qui en contient dautres. La bibliothque standard de C++ fournit une srie de classes conteneur partir desquelles les dveloppeurs C++ peuvent traiter les tches de programmation courantes. Les deux types de classes conteneur de la STL sont les conteneurs squentiels et les conteneurs associatifs. Les squences sont conues pour offrir un accs squentiel et direct
Chapitre 19
Les modles
677
leurs membres, ou lments. Les conteneurs associatifs sont optimiss pour permettre un accs leurs lments partir de valeurs cls. Toutes les classes conteneurs de cette bibliothque sont dnies dans lespace de nom std.
Le conteneur vector
On utilise souvent des tableaux pour stocker et accder un certain nombre dlments. Les lments dun tableau sont tous du mme type et sont accessibles partir dun indice. La classe conteneur vector fournie par la STL se comporte comme un tableau, mais offre davantage de puissance et de scurit quun tableau classique de C++. Un vector est un conteneur optimis pour offrir un accs rapide ses lments via un indice. Cette classe est dnie dans le chier en-tte <vector> de lespace de nom std (les espaces de noms sont dtaills au Chapitre 18). La taille dun vector volue automatiquement en fonction des besoins. Supposons que vous ayez cr un vector de 10 lments. Lorsque vous aurez stock 10 objets, ce vector sera plein ; si vous ajoutez alors un autre objet, le vector augmentera automatiquement sa capacit pour recevoir le onzime objet. Voici comment la classe vector est dnie :
template <class T, class Allocator = allocator<T>> class vector { // membres de la classe };
Le premier paramtre (class T) reprsente le type des lments du vector. Le second (class Allocator) est une classe allocateur. Les allocateurs sont des gestionnaires de mmoire chargs dallouer et librer la mmoire requise pour les lments des conteneurs. Le concept dallocateur et limplmentation de ces gestionnaires sont des sujets trs pointus qui sortent du cadre de cet ouvrage. Par dfaut, les lments sont crs avec loprateur new() et sont librs avec loprateur delete(). Ceci signie que cest le constructeur par dfaut de la classe T qui est appel pour crer un nouvel lment. Cest donc encore une autre bonne raison de dnir explicitement un constructeur par dfaut pour ses propres classes. Si vous ne le faites pas, vous ne pourrez pas utiliser le conteneur vector standard pour stocker des instances de votre classe.
678
Le langage C++
Voici comment dnir des vector pour y stocker des entiers et des nombres virgule ottante :
vector<int> vInts; vector<float> vFloats; // vector dentiers // vector de flottants
Quand vous crez un vector, vous avez gnralement une ide du nombre dlments quil devra contenir. Supposons, par exemple, que le nombre maximal dtudiants par classe dans votre cole soit de 50. Si vous crez un vector pour y stocker les tudiants dune classe, celui-ci devra tre capable de recevoir jusqu 50 lments. La classe vector standard fournit un constructeur auquel vous pouvez transmettre le nombre dlments en paramtre. Voici comment vous pouvez dnir ce vector de 50 tudiants :
vector<Etudiant> ClasseMaths(50);
Le compilateur allouera sufsamment de mmoire pour enregistrer 50 tudiants. Chacun deux sera cr avec le constructeur par dfaut Etudiant::Etudiant(). Vous pouvez connatre le nombre dlments dun vector avec sa fonction membre size(). Pour le vecteur dEtudiant ClasseMaths qui vient dtre dni, Classe.size() renverra 50. Une autre fonction membre, capacity(), permet de savoir combien dlments un vector peut recevoir avant que sa capacit nait besoin dtre augmente. Nous dvelopperons ce sujet un peu plus loin. On dit quun vector est vide sil ne contient aucun lment, cest--dire si sa taille est nulle. La classe vector fournit la fonction membre empty() pour tester facilement si le conteneur est vide. Elle renvoie true si le vecteur est vide. Pour affecter un objet Etudiant Jean la Classe, vous pouvez utiliser loprateur dindexation :
ClasseMaths[5] = Jean;
Les indices commencent zro. Vous avez certainement remarqu que nous avons utilis ici loprateur daffectation surcharg de la classe Etudiant pour affecter Jean au 6e lment de Classe. De la mme faon, pour trouver lge de Jean, vous pouvez accder son enregistrement en crivant, par exemple:
ClasseMaths[5].GetAge();
Comme nous lavons dj mentionn, les vector sont capables daugmenter automatiquement leur capacit quand vous ajoutez plus dlments quils ne sont capables den recevoir. Supposons quune des classes de votre cole devienne subitement trs populaire et que le nombre dtudiants excde les 50. Quand le 51e tudiant, Michel, est ajout dans
Chapitre 19
Les modles
679
ClasseMaths, le vecteur peut augmenter sa taille pour recevoir cet lment supplmentaire. Il existe plusieurs mthodes pour ajouter un lment dans un vector. Lune dentre elles consiste utiliser la fonction push_back() :
ClasseMaths.push_back(Michel);
Cette fonction membre ajoute le nouvel objet Etudiant Michel la n du vector ClasseMaths. Ce dernier contient maintenant 51 lments et Michel est llment ClasseMaths[50]. Pour que cette fonction sexcute correctement, la classe Etudiant doit dnir un constructeur de copie. Sinon, push_back() sera incapable deffectuer une copie de lobjet Michel. La STL ne prcise pas le nombre maximal dlments quun vector peut recevoir ; les diteurs de compilateur sont les mieux placs pour prendre cette dcision. La classe vector fournit une fonction membre qui vous donne ce prcieux renseignement pour votre compilateur : max_size(). Le Listing 19.8 met en uvre les membres de la classe vector que nous venons de prsenter. On utilise ici la classe string standard pour simplier la gestion des chanes. Pour plus de dtails sur la classe string, consultez la documentation de votre compilateur. Listing 19.8 : Cration dun vecteur et accs ses lments
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: #include <iostream> #include <string> #include <vector> using namespace std; class Etudiant { public: Etudiant(); Etudiant(const string& nom, const int age); Etudiant(const Etudiant& rhs); ~Etudiant(); void string void int SetNom(const string& nom); GetNom() const; SetAge(const int age); GetAge() const;
680
Le langage C++
20: private: 21: string sonNom; 22: int sonAge; 23: }; 24: 25: Etudiant::Etudiant() : 26: sonNom("Nouvel tudiant"), sonAge(16) 27: {} 28: 29: Etudiant::Etudiant(const string& nom, const int age) : 30: sonNom(nom), sonAge(age) 31: {} 32: 33: Etudiant::Etudiant(const Etudiant& rhs) : 34: sonNom(rhs.GetNom()), sonAge(rhs.GetAge()) 35: {} 36: 37: Etudiant::~Etudiant() 38: {} 39: 40: void Etudiant::SetNom(const string& nom) 41: { 42: sonNom = nom; 43: } 44: 45: string Etudiant::GetNom() const 46: { 47: return sonNom; 48: } 49: 50: void Etudiant::SetAge(const int age) 51: { 52: sonAge = age; 53: } 54: 55: int Etudiant::GetAge() const 56: { 57: return sonAge; 58: } 59: 60: Etudiant& Etudiant::operator=(const Etudiant& rhs) 61: { 62: sonNom = rhs.GetNom(); 63: sonAge = rhs.GetAge(); 64: return *this; 65: } 66:
Chapitre 19
Les modles
681
67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89: 90: 91: 92: 93: 94: 95: 96: 97: 97a: 98: 99: 100: 101: 101a: 102: 103: 104: 105: 106: 107: 108: 109: 110: 111:
ostream& operator<<(ostream& os, const Etudiant& rhs) { os << rhs.GetNom() << " a " << rhs.GetAge() << " ans"; return os; } template<class T> // affichage des proprits du vector void AffVector(const vector<T>& v); typedef vector<Etudiant> Classe; int main() { Etudiant Etudiant Etudiant Etudiant
Classe ClasseVide; cout << "ClasseVide :" << endl; AffVector(ClasseVide); Classe ClasseCroissante(3); cout << "ClasseCroissante(3) :" << endl; AffVector(ClasseCroissante); ClasseCroissante [0] = Jean; ClasseCroissante [1] = Michel; ClasseCroissante [2] = Martine; cout << "ClasseCroissante(3) aprs ajout des tudiants :" << endl; AffVector(ClasseCroissante); ClasseCroissante.push_back(Pierre); cout << "ClasseCroissante() aprs ajout du 4 e tudiant :" << endl; AffVector(ClasseCroissante); ClasseCroissante [0].SetNom("Jean"); ClasseCroissante [0].SetAge(18); cout << "ClasseCroissante() aprs SetNom/SetAge :\n"; AffVector(ClasseCroissante); return 0; }
682
Le langage C++
112: 113: 114: 115: 116: 117: 118: 119: 120: 121: 122: 123: 124: 125: 126: 127: 128:
// // Affichage des proprits du vector // template<class T> void AffVector(const vector<T>& v) { cout << "max_size() = " << v.max_size(); cout << "\tsize() = " << v.size(); cout << "\tcapacity() = " << v.capacity(); cout << "\t" << (v.empty()? "vide": "non vide"); cout << endl; for (int i = 0; i < v.size(); ++i) cout << v[i] << endl; cout << endl; }
Chapitre 19
Les modles
683
La classe Etudiant est dnie aux lignes 5 23. Les implmentations de ses fonctions membres se situent aux lignes 25 65. Pour les raisons que nous avons exposes plus tt, nous avons dni un constructeur par dfaut, un constructeur de copie et un oprateur daffectation surcharg. Vous pouvez remarquer que sa variable membre sonNom est dnie comme une instance de la classe string. Vous pouvez constater dans ce code quil est beaucoup plus facile de travailler avec le type chane standard de C++ quavec une chane char* de type C. La fonction modle AffVector() est dclare aux lignes 73 et 75 et dnie aux lignes 115 128. Elle illustre lutilisation de quelques fonctions membres de vector: max_size(), size(), capacity() et empty(). En examinant le rsultat, vous pouvez constater que le nombre maximal dobjets Etudiant quun vector peut recevoir est 536 870 911 avec notre compilateur ; il pourra tre diffrent avec dautres types dlments. Un vector dentiers, par exemple, peut contenir jusqu 1 073 741 823 lments. Ces valeurs peuvent varier en fonction des compilateurs. Aux lignes 124 et 125, la valeur de chaque lment du vector est afche via loprateur dinsertion surcharg <<, qui est dni aux lignes 67 71. Les lignes 80 85 de la fonction principale du programme cre quatre tudiants. La ligne 86 dnit le vector vide ClasseVide avec le constructeur par dfaut de la classe vector. Quand un vector est cr de cette faon, le compilateur ne rserve pas de mmoire pour lui : comme vous pouvez le constater avec ce que produit lappel AffVector(ClasseVide), sa taille et sa capacit sont toutes les deux nulles. La ligne 90 dnit un vector de trois objets Etudiant. Sa taille et sa capacit sont de 3. Les lments de ClasseCroissante sont des objets Etudiant qui lui sont affects aux lignes 94 96 via loprateur dindexation. Pierre, le quatrime tudiant, est ajout au vector la ligne 100. La taille du conteneur passe donc quatre et nous constatons que sa capacit est maintenant de six, ce qui signie que le compilateur a allou de la mmoire pour stocker jusqu six objets Etudiant. Les vector devant tre stocks dans un bloc contigu de mmoire, laugmentation de leur capacit impose une srie doprations. Un nouveau bloc de mmoire sufsant pour recevoir les quatre objets Etudiant doit tout dabord tre allou. Les trois lments existants sont ensuite copis dans ce nouveau bloc et le quatrime est ajout la suite du troisime. Pour terminer, le bloc de mmoire initial est libr. Quand le conteneur contient beaucoup dlments, ce processus dallocation et de libration de mmoire peut prendre du temps ; cest la raison pour laquelle le compilateur appliquer une stratgie doptimisation pour rduire la frquence doprations si coteuses. Dans cet exemple, si nous ajoutons deux objets supplmentaires au vector, il ne sera pas ncessaire de librer et dallouer nouveau de la mmoire.
684
Le langage C++
Nous utilisons nouveau loprateur dindexation au lignes 104 et 105 pour changer les variables membres du premier objet de ClasseCroissante.
Faire
Dnir un constructeur par dfaut pour toute
Ne pas faire
Crer votre propre classe vector ! Vous
pouvez utiliser celle de la STL : comme elle fait partie de la norme, tous les compilateurs compatibles devraient en disposer.
classe.
Dnir un oprateur daffectation surcharg
La classe conteneur vector possde dautres fonctions membres. La mthode front(), en particulier, renvoie une rfrence vers le premier lment dune liste alors que back() renvoie une rfrence vers le dernier lment. La mthode at() se comporte comme loprateur dindexation [], mais son rsultat est plus able parce quil vrie si lindice qui lui a t pass en paramtre se situe bien dans lintervalle des indices disponibles (vous pourriez, bien sr, crire un oprateur dindexation qui effectue galement ces tests). Si lindice est en dehors des limites, cette mthode lance une exception out_of_range (les exceptions sont traites au prochain chapitre). La mthode insert() insre un ou plusieurs nuds une position donne dun vector. La mthode pop_back() supprime le dernier lment. Enn, remove() permet de supprimer un ou plusieurs lments.
Le conteneur List
Une list est un conteneur conu pour tre optimal lorsque lon insre et que lon supprime frquemment des lments. La classe list de la STL est dnie dans le chier en tte <list> dans lespace de noms std. Elle est implmente comme une liste doublement chane dans laquelle chaque nud possde des liens la fois vers le nud prcdent et vers le nud suivant dans la liste. La classe list dispose de toutes les fonctions membres de la classe vector. Gnralement, on parcourt une liste en suivant les liens fournis dans chaque nud, qui sont typiquement implments laide de pointeurs. Pour ce type de traitement, la classe conteneur list standard utilise un mcanisme nomm itrateur. Un itrateur est une gnralisation dun pointeur qui tente dviter certains de ses dangers. En drfrenant un itrateur, on obtient le nud sur lequel il pointe. Le Listing 19.9 illustre lutilisation des itrateurs pour laccs aux nuds dune liste.
Chapitre 19
Les modles
685
Le Listing 19.9 utilise le modle list de la STL. La ligne 1 inclut le chier en-tte qui contient le code de ce modle. La ligne 4 utilise la commande typedef pour crer un alias ListeEntiers plus facile lire que list<int>. La ligne 8 dniti lobjet listeInt comme une liste dentiers. Aux lignes 10 et 11, la mthode push_back() ajoute cette liste les dix premiers nombres positifs pairs. Aux lignes 13 15, nous accdons chaque nud de la liste laide dun itrateur constant. Ce choix indique que nous navons pas lintention de modier les nuds avec cet itrateur. Si vous voulez pouvoir modier un nud dsign par un itrateur, ce dernier doit tre un itrateur non constant, cest--dire un ListeEntiers::iterator. La fonction membre begin() renvoie un itrateur pointant sur le premier nud de la liste. Comme on le voit ici, loprateur dincrmentation ++ permet de faire pointer un itrateur sur le nud suivant. La fonction membre end() est assez bizarre : elle renvoie un itrateur pointant sur lemplacement qui suit le dernier nud dune liste. Vous devez donc tre certain que votre itrateur natteint pas end() !
686
Le langage C++
Pour obtenir le nud point, on applique loprateur dindirection litrateur, exactement comme on le fait pour un pointeur (voir la ligne 15). Bien que nous ayons introduit les itrateurs avec la classe list, ils existent galement pour la classe vector. Outre les mthodes dj prsentes avec cette dernire, la classe list fournit galement les mthodes push_front() et pop_front() qui se comportent exactement comme push_back() et pop_back() sauf, quau lieu dajouter et de supprimer les lments la n dune liste, elles les ajoutent et les suppriment en tte de la liste.
Le conteneur stack
La pile est lune des structures de donnes les plus souvent utilises en programmation. La classe modle stack fournie par la STL est, en fait, implmente comme une enveloppe de conteneur, non comme une classe conteneur indpendante. Elle est dnie dans le chier en tte <stack>, dans lespace de nom std. Une pile est stocke dans un bloc de mmoire allou de faon contigu. Vous ne pouvez accder qu llment situ la n de la pile et vous ne pouvez supprimer que celui-l. Les conteneurs squence, et en particulier vector et deque, prsentent des caractristiques analogues. En fait, tout conteneur squence qui dispose des oprations back(), push_back() et pop_back() pourrait servir implmenter une pile. La plupart des autres mthodes de conteneur ne sont pas ncessaires pour ce type de structure. La classe modle stack de la STL est conue pour recevoir tout type dobjet. La seule restriction est que tous les lments doivent tre de mme type. Une pile est une structure de type LIFO (Last In, First Out, dernier entr, premier sorti). Une analogie classique consiste comparer une pile une pile dassiettes sales dans un restaurant. On charge la pile en ajoutant une assiette sur le dessus et on la dcharge en prenant lassiette situe son sommet. Par convention, lextrmit ouverte dune pile est nomme sommet de la pile et les oprations dajout et de suppression sont appeles, respectivement, empiler et dpiler. La classe stack hrite de ces termes conventionnels. La classe stack de la STL nest pas tout fait identique au mcanisme de pile utilis par les compilateurs et les systmes dexploitation car ces derniers sont capables dempiler des objets de types diffrents. La fonctionnalit sousjacente est, cependant, analogue.
Info
Chapitre 19
Les modles
687
Le conteneur deque
Vous pouvez considrer un deque comme un vector avec deux extrmits : il hrite de lefcacit de la classe conteneur vector pour les oprations de lecture et dcriture squentielles, mais la classe conteneur deque fournit galement des oprations optimises sur les "extrmits" du conteneur. Ces oprations sont implmentes comme celles de la classe conteneur list, avec des allocations de mmoire qui nauront lieu que pour les nouveaux lments. Cette caractristique de la classe deque supprime donc le besoin de rallouer tout le conteneur dans un nouvel emplacement mmoire, comme cest le cas avec la classe vector. Les deque conviennent donc particulirement bien aux applications dans lesquelles les insertions et les suppressions seffectuent surtout en dbut ou en n de liste et pour lesquelles il est aussi important de pouvoir accder squentiellement aux lments. Un simulateur dassemblage de convoi ferroviaire en est un bon exemple puisque les wagons peuvent tre ajouts des deux cts dun train.
Le conteneur queue
Une le attente est une autre structure de donnes trs utilise en programmation. Les lments sont ajouts dans la le partir dune extrmit puis rcuprs lautre extrmit. Une le dattente peut tre compare celle dun thtre. On entre dans la le en se positionnant la n, et on la quitte lorsquon est en tte. Cest donc une structure du type FIFO (First In, First Out pour premier entr, premier sorti) alors quune pile est une structure de type LIFO. Comme stack, queue est implmente sous la forme dune enveloppe de conteneur. Ce conteneur doit disposer des oprations front(), back(), push_back() et pop_front().
688
Le langage C++
Le conteneur map
Le premier conteneur associatif que nous prsenterons sappelle map. Son nom provient de lide quil contient des "cartes" (maps), cest--dire la cl vers la valeur associe, exactement comme un point sur une carte gographique correspond un vritable endroit sur la Terre. Dans le Listing 19.10, on utilise un map pour implmenter lexemple de la classe dtudiants dj prsent dans le Listing 19.8. Listing 19.10 : Classe conteneur map
0: #include <iostream> 1: #include <string> 2: #include <map> 3: using namespace std; 4: 5: class Etudiant 6: { 7: public: 8: Etudiant(); 9: Etudiant(const string& nom, const int age); 10: Etudiant(const Etudiant& rhs); 11: ~Etudiant(); 12: 13: void SetNom(const string& nom); 14: string GetNom() const; 15: void SetAge(const int age); 16: int GetAge() const; 17: 18: Etudiant& operator=(const Etudiant& rhs); 19: 20: private: 21: string sonNom; 22: int sonAge; 23: }; 24: 25: Etudiant::Etudiant() : 26: sonNom("Nouvel tudiant"), sonAge(16) 27: {} 28: 29: Etudiant::Etudiant(const string& nom, const int age) : 30: sonNom(nom), sonAge(age) 31: {} 32: 33: Etudiant::Etudiant(const Etudiant& rhs) : 34: sonNom(rhs.GetNom()), sonAge(rhs.GetAge()) 35: {}
Chapitre 19
Les modles
689
36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82:
Etudiant::~Etudiant() {} void Etudiant::SetNom(const string& nom) { sonNom = nom; } string Etudiant::GetNom() const { return sonNom; } void Etudiant::SetAge(const int age) { sonAge = age; } int Etudiant::GetAge() const { return sonAge; } Etudiant& { sonNom sonAge return } Etudiant::operator=(const Etudiant& rhs) = rhs.GetNom(); = rhs.GetAge(); *this;
ostream& operator<<(ostream& os, const Etudiant& rhs) { os << rhs.GetNom() << " a " << rhs.GetAge() << " ans"; return os; } template<class T, class A> void AffMap(const map<T, A>& v); typedef map<string, Etudiant> Classe; int main() { Etudiant Jean("Jean", 18); Etudiant Martine("Martine", 15); Etudiant Michel("Michel", 17);
690
Le langage C++
83: 84: 85: 86: 87: 88: 89: 90: 91: 92: 93: 94: 95: 96: 97: 98: 99: 100: 101: 102: 103: 104: 105: 106: 107: 108: 109: 110: 111:
Etudiant Pierre("Pierre", 16); Classe ClasseMaths; ClasseMaths [Jean.GetNom()] = Jean; ClasseMaths [Martine.GetNom()] = Martine; ClasseMaths [Michel.GetNom()] = Michel; ClasseMaths [Pierre.GetNom()] = Pierre; cout << "ClasseMaths :" << endl; AffMap(ClasseMaths); cout << "On sait que " << ClasseMaths ["Michel"].GetNom() << " a " << ClasseMaths["Michel"].GetAge() << " ans" << endl; return 0; } // // Affiche les proprits du map // template<class T, class A> void AffMap(const map<T, A>& v) { for (map<T, A>::const_iterator iter = v.begin(); iter!= v.end(); ++iter) cout << iter->first << " : " << iter->second << endl; cout << endl; }
Dans cet exemple, on cre une classe de Maths et on y ajoute quatre tudiants. La liste est ensuite afche. Puis, Michel est afch avec son ge mais, au lieu dutiliser un indexeur numrique, comme dans lexemple prcdent, cest le nom de Michel qui sert retrouver son ge. Ceci est possible grce au modle map. Vous pouvez constater galement que la majeure partie du listing correspond la classe Etudiant. Cest un code que vous devriez maintenant comprendre.
Chapitre 19
Les modles
691
Les seuls lments propres ce listing dmarrent la ligne 2, o le chier en tte <map> est inclus parce que nous allons utiliser le conteneur map standard. la ligne 73, vous pouvez constater que lon fournit un prototype pour la fonction AffMap(), qui est une fonction modle. Elle permet dafcher les lments du map. La ligne 76 utilise typedef pour dnir Classe comme un map dont les lments sont constitus de paires (cl, valeur). Le premier lment dune paire correspond une chane qui est la valeur de la cl. Dans notre Classe, nous utilisons le nom des tudiants comme valeur de cl ; cette dernire est donc de type string. La cl de chaque lment du conteneur doit tre unique, il ne doit donc pas avoir deux lments avec la mme cl. Le second lment de la paire reprsente lobjet rel, ici un objet Etudiant. Le type des donnes "paire" est implment dans la STL sous la forme dune struct de deux membres : first et second. Nous pouvons utiliser ces membres pour accder la cl et la valeur dun nud. Commenons par tudier la fonction AffMap() dnie de la ligne 103 la ligne 111. Celle-ci utilise un itrateur constant pour accder un objet du map. la ligne 108, iter>first pointe sur la cl, cest--dire sur le nom dun tudiant et iter->second pointe sur lobjet Etudiant. Il ne reste que la fonction main() qui est comprise entre les lignes 78 et 98. Intressonsnous aux lignes 80 83, dans lesquelles quatre objets Etudiant sont crs. La ClasseMaths est dnie comme une instance de notre Classe de la ligne 85. Au lignes 86 89, nous ajoutons les quatre tudiants de la ClasseMaths en utilisant la syntaxe suivante :
objet_map[cl] = valeur_objet;
la ligne 86, vous pouvez constater que la cl utilise correspond au nom dun objet Etudiant, obtenu grce sa mthode GetNom(). valeur_objet est un objet Etudiant. Vous pourriez galement utiliser les fonctions push_back() ou insert() pour ajouter une paire (cl, valeur) dans le map. Vous trouverez des informations complmentaires ce sujet en consultant la documentation de votre compilateur. Lorsque tous les objets Etudiant ont t ajouts dans le map, vous pouvez accder lun dentre eux par sa cl. Aux lignes 94 96, ClasseMaths["Michel"] permet donc dobtenir lenregistrement de Michel.
692
Le langage C++
la cl. La classe conteneur multiset est une classe set dans laquelle les cls dupliques sont autorises. Enn, la classe conteneur bitset est un modle qui sert stocker une suite de bits.
Chapitre 19
Les modles
693
La classe modle Print est dnie aux lignes 3 11 ; il sagit dune classe modle standard. Aux lignes 6 9, loprateur () est surcharg pour recevoir un objet en paramtre et lafcher sur la sortie standard. La ligne 15 dnit DoPrint comme une instance de la classe Print utilisant une valeur int. Nous pouvons ensuite lutiliser exactement comme une fonction pour afcher nimporte quelle valeur entire, comme le montre la ligne 17. Les classes dalgorithme standard fonctionnent exactement comme Print : elles ont surcharg loprateur () pour que vous puissiez les utiliser comme des fonctions.
694
Le langage C++
cout << "for_each()" << endl; for_each(vInt.begin(), vInt.end(), DoPrint); cout << endl; return 0; }
Tous les algorithmes standard de C++ sont dnis dans le chier <algorithm>, que nous avons donc inclus la ligne 2. Mme si la plus grande partie du programme ncessite peu dexplications, une ligne mrite notre attention. La fonction for_each() est appele la ligne 24 pour parcourir chaque lment du vector vInt. Pour chaque lment de ce vector, elle appelle lobjet fonction DoPrint et passe en paramtre llment DoPrint.operator(). La valeur de cet lment est donc afche sur lcran.
DoPrint;
Chapitre 19
Les modles
695
18: 19: 20: 21: 22: 23: 24: 25: 26: 27:
vector<int>
vInt(10);
fill(vInt.begin(), vInt.begin()+5, 1); fill(vInt.begin()+5, vInt.end(), 2); for_each(vInt.begin(), vInt.end(), DoPrint); cout << endl << endl; return 0; }
La seule partie nouvelle dans ce listing se situe aux lignes 20 et 21, dans lesquelles apparat lalgorithme fill(). Ce dernier stocke des lments dans une squence avec une valeur donne. la ligne 20, il affecte la valeur entire 1 au cinq premiers lments de vInt. Puis, il affecte 2 aux cinq derniers lments du conteneur (ligne 21).
Info
Questions-rponses
Q Pourquoi utiliser des modles plutt que des macros ? R Les modles permettent de contrler le type et sont intgrs au langage. Ils peuvent ainsi tre vris par le compilateur, au moins lorsque vous instanciez la classe pour crer une variable particulire.
696
Le langage C++
Q Quelle diffrence y a-t-il entre le type paramtr dune fonction modle et les paramtres dune fonction normale ? R Une fonction normale (qui nest pas modle) reoit des paramtres sur lesquels elle peut agir. Avec une fonction modle, vous pouvez paramtrer le type dun paramtre particulier de la fonction. Cela signie que vous pouvez transmettre un tableau de Type une fonction : ce Type sera dtermin par la dnition de la variable qui est une instance de la classe pour un type spcique. Q Comment choisir entre lutilisation des modles et celle de lhritage ? R Utilisez les modles lorsque la seule chose qui varie est le type de llment sur lequel la classe agit. Si vous devez recopier une classe existante pour nen changer que le type dun ou plusieurs de ses membres, un modle sera probablement une bonne solution. Employez aussi un modle lorsque vous tes tent de modier une classe pour oprer sur une classe anctre de plus haut niveau (ce qui rduit la scurit de type) de ses oprandes ou pour amener deux classes non lies partager un anctre commun, de sorte que votre classe puisse fonctionner avec les deux (ce qui rduit une fois de plus la scurit de type). Q Quand faut-il utiliser les classes amies modles gnrales? R Lorsque chaque instance, quel que soit le type, doit tre une amie de cette classe ou de cette fonction. Q Quand faut-il utiliser les classes ou fonctions amies modles spciques ? R Lorsque vous voulez mettre deux classes en relation. Par exemple, Array<int> devra correspondre iterator<int> et non iterator<Animal>.
Chapitre 19
Les modles
697
Exercices
1. Crez un modle bas sur cette classe Liste :
class Liste { private: public: Liste():tete(0),fin(0),leNbre(0) {} virtual ~Liste(); void insere( int valeur ); void ajoute( int valeur ); int present( int valeur ) const; int vide() const { return tete == 0; } int nbre() const { return leNbre; } private: class ListeCellule { public: ListeCellule( int valeur, ListCell *cell = 0):val(valeur), suivante(cell){} int val; ListeCellule *suivante; }; ListeCellule *tete; ListeCellule *fin; int leNbre; };
2. crivez limplmentation de la version non modle de cette classe Liste. 3. crivez la version modle des implmentations. 4. Dclarez trois objets Liste : une liste de chanes, une liste de chats et une liste dentiers. 5. CHERCHEZ LERREUR : (on suppose que le modle Liste est dni et que Chat est la classe qui a t dnie dans les exemples prcdents).
Liste<Chat> ListeChats; Chat Felix; ListeChats.ajoute( Felix ); cout << "Felix est " << (ListeChats.present(Felix) )? "prsent": "absent " << endl;
698
Le langage C++
6. Dclarez loprateur ami == pour Liste. 7. Implmentez loprateur ami == pour Liste. 8. operator== prsente-t-il le mme problme que celui de lExercice 5 ? 9. Implmentez une fonction modle pour permuter qui change deux variables.
20
Gestion des erreurs et exceptions
Au sommaire de ce chapitre
Les exceptions Lutilisation des exceptions et les problmes quelles soulvent La construction des hirarchies dexceptions La place des exceptions dans une approche gnrale de gestion des erreurs Les dbogueurs
Les lignes de code que vous avez tudies jusqu prsent ntaient que des exemples : elles ne comprenaient donc aucun traitement des erreurs an de se concentrer sur les problmes quelles avaient pour but dillustrer. Les vrais programmes, par contre, doivent prendre en considration les conditions derreurs.
700
Le langage C++
Info
Chapitre 20
701
Circonstances exceptionnelles
Vous ne pouvez pas empcher les situations exceptionnelles, vous devez simplement vous y prparer. Que se passera-t-il si votre programme exige de la mmoire pour allouer dynamiquement un objet et quil ny en a plus de disponible ? Comment rpondra-t-il ? Que faire si vous produisez lune des erreurs mathmatiques les plus classique en divisant par zro ? Voici quelques possibilits :
interrompre brutalement le programme ; informer lutilisateur puis arrter proprement le programme ; informer lutilisateur et lui permettre dessayer de corriger et de poursuivre ; prendre les mesures qui simposent et poursuivre lexcution du programme sans inquiter lutilisateur.
Le Listing 20.1 est extrmement simple et prt se planter, mais il illustre un problme trs srieux, prsent dans de nombreux programmes ! Listing 20.1 : Crer une situation exceptionnelle
0: // Ce programme va planter 1: #include <iostream> 2: using namespace std; 3: 4: const int TailleDefaut = 10; 5: 6: int main() 7: { 8: int haut = 90; 9: int bas = 0; 10: 11: cout << "haut / 2 = " << (haut/ 2) << endl; 12: 13: cout << "haut divis par bas = "; 14: cout << (haut / bas) << endl; 15: 16: cout << "haut / 3 = " << (haut/ 3) << endl; 17: 18: cout << "Termin." << endl; 19: return 0; 20: }
702
Le langage C++
ntion Atte
Ce programme peut afcher ce rsultat sur la console, mais il plantera srement immdiatement aprs.
Le Listing 20.1 a t conu pour planter ; si vous aviez demand lutilisateur dentrer deux nombres, il aurait pu produire le mme rsultat. Les lignes 8 et 9 dclarent et initialisent deux variables entires. Vous auriez galement pu demander ces deux nombres lutilisateur ou les lire partir dun chier. Aux lignes 11, 14 et 16, ces nombres sont utiliss dans des oprations mathmatiques et plus particulirement pour effectuer une division. Si les lignes 11 et 16 ne posent pas de problme, la ligne 14 en prsente. La division par zro pose un problme exceptionnel, un plantage. Le programme se termine et une exception sera srement afche par le systme dexploitation. Bien quil ne soit pas toujours ncessaire (ni mme souhaitable) que tous les programmes grent automatiquement toutes sortes de situations, il est clair quune simple interruption de ces programmes est insufsante. La gestion des exceptions fournies par C++ permet de traiter toutes les conditions inhabituelles, mais prvisibles, qui peuvent se produire pendant lexcution dun programme.
Lordinateur tente dexcuter un lment de code qui pourrait tenter dallouer des ressources comme de la mmoire, essayer de verrouiller un chier ou effectuer tout type de tche. On intgre la logique (le code) au cas o les oprations que vous essayez dexcuter choueraient pour une raison exceptionnelle. Vous pourriez, par exemple, inclure du code pour intercepter tous les problmes (la mmoire ne peut tre alloue, le chier ne peut tre verrouill, etc.). Si le code est utilis par un autre code (par une fonction, par exemple), on a galement besoin dun mcanisme permettant de faire remonter les informations sur les problmes (exceptions) de votre niveau au niveau prcdent. Il devrait exister un chemin allant du code qui a pos problme jusqu celui qui traite lerreur. Si ces deux extrmits sont spares par des couches de fonctions, celles-ci devraient avoir la possibilit de rsoudre le problme, mais ne devraient pas tre obliges dinclure du code dans le seul but de transmettre la condition derreur.
La gestion des exceptions associe ces trois points, de manire relativement simple.
Chapitre 20
703
Ici, si une exception survient lors de lexcution de UneFonctionDangereuse(), elle est note et intercepte. Il suft dajouter le mot-cl try et les accolades pour que le programme commence surveiller les exceptions. Bien sr, en cas dexception, il faut agir. Si une exception survient au cours de lexcution du code contenu dans un bloc try, on dit que lexception est "lance". Elle peut ensuite tre intercepte (capture) avec un bloc catch. Lorsquune exception est lance, le contrle est transmis au bloc catch appropri qui suit le bloc try. Dans lexemple prcdent, lellipse (...) dsigne nimporte quelle exception, mais on peut galement capturer des types spciques dexceptions. Pour cela, on utilise un ou plusieurs blocs catch aprs le bloc try, comme dans lexemple suivant :
try { UneFonctionDangereuse(); } catch(OutOfMemory) { // actions } catch(FileNotFound) { // autres actions } catch (...) { }
Cet exemple traite les exceptions qui pourraient tre lances par UneFonctionDangereuse(). Si une exception est lance, elle est envoye dans le premier bloc catch qui suit
704
Le langage C++
immdiatement le bloc try. Si celui-ci possde un paramtre de type, comme cest le cas dans cet exemple, lexception est contrle pour voir si elle correspond au type indiqu. Sinon, on vrie la prochaine instruction catch, etc., jusqu trouver une concordance ou autre chose quun bloc catch. la premire concordance, le bloc catch correspondant est excut. moins de vouloir vraiment laisser passer les autres types dexceptions, il est donc toujours prfrable que le dernier bloc catch utilise lellipse. Un bloc catch est aussi appel gestionnaire dexception, car il permet de grer une exception. Vous pouvez considrer les blocs catch comme des fonctions surcharges. En cas de concordance de la signature, cette fonction est excute.
Info
Info
Voici les tapes de base pour le traitement des exceptions : 1. Identiez les zones du programme dans lesquelles une opration peut conduire une situation exceptionnelle et mettez-les dans des blocs try. 2. Crez des blocs catch pour capturer les exceptions si elles sont lances. Vous pouvez crer un bloc catch pour un type dexception spcique (en prcisant un paramtre typ pour le bloc catch) ou pour toutes les exceptions (en utilisant une ellipse (...) comme paramtre). Le Listing 20.2 ajoute une gestion minimale des exceptions au Listing 20.1. Il utilise un bloc try et un bloc catch. Certains compilateurs trs anciens ne supportent pas les exceptions. Si votre compilateur en fait partie, vous devrez installer sa version la plus rcente si vous voulez compiler les exercices de ce chapitre. Les exceptions faisant partie de la norme ANSI C++, elles sont bien sr supportes par toutes les versions actuelles des compilateurs.
Info
Chapitre 20
705
9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27:
= 0;
cout << "haut / 2 = " << (haut/ 2) << endl; cout << "haut divis par bas = "; cout << (haut / bas) << endl; cout << "haut / 3 = " << (haut/ 3) << endl; } catch(...) { cout << "Il y a un problme!" << endl; } cout << "Termin." << endl; return 0; }
la diffrence du listing prcdent, lexcution du Listing 20.2 ne plante pas. Le programme est dsormais capable de signaleur une erreur et de se terminer proprement. Nous avons ajout un bloc try autour du code o aurait pu se produire un problme. Ici, cest autour de la division (lignes 11 19). Au cas o une exception surviendrait, nous avons fait suivre ce bloc try dun bloc catch, aux lignes 20 23. Ce bloc catch de la ligne 20 contient trois points, ou ellipse. Comme on la dj indiqu, cest un cas spcial qui indique que toutes les exceptions survenant dans le code try prcdent seront traites par ce bloc, moins quun bloc catch prcdent ne lait fait. Dans ce listing, ce sera srement une erreur de division par zro. Comme vous le verrez plus loin, il est souvent prfrable de rechercher des types dexceptions plus spciques, an de pouvoir personnaliser la gestion de chacune. Vous remarquerez que ce listing ne plante pas. En outre, le rsultat vous montre que son excution sest poursuivie la ligne 25, aprs linstruction catch. Cela est conrm par lafchage du mot Termin.
706
Le langage C++
Blocs try Un bloc try est une suite dinstructions commenant par le mot-cl try, suivi dune accolade ouvrante et se terminant par une accolade fermante. Exemple :
try { Fonction(); };
Blocs catch Un bloc catch est un lment de code commenant par le mot-cl catch, suivi dun type dexception entre parenthses, puis dune accolade ouvrante et se terminant par une accolade fermante. Les blocs catch ne sont autoriss quaprs un bloc try. Exemple :
Chapitre 20
707
Cette instruction lance exception. Le contrle est alors pass un gestionnaire ; sil ny en a pas, le programme se termine. La valeur lance dans lexception peut tre de quasiment nimporte quel type. Comme on la indiqu, vous pouvez congurer des gestionnaires pour chaque type dobjet que votre programme pourrait dclencher. Le Listing 20.3 montre comment lancer une exception de base en modiant le Listing 20.2. Listing 20.3 : Lancement dune exception
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: // Lancement dune exception #include <iostream> using namespace std; const int TailleDefaut = 10; int main() { int haut = 90; int bas = 0; try { cout << "haut / 2 = " << (haut/ 2) << endl; cout << "haut divis par bas = "; if ( bas == 0 ) throw "Division par zro!"; cout << (haut / bas) << endl; cout << "haut / 3 = " << (haut/ 3) << endl; } catch( const char * ex ) { cout << "\n*** " << ex << " ***" << endl; } cout << "Termin." << endl; return 0; }
708
Le langage C++
la diffrence du listing prcdent, celui-ci contrle mieux ses exceptions. Mme sil ne fait pas le meilleur usage de ces exceptions, il montre bien lutilisation de linstruction throw. La ligne 17 dtermine si la valeur de bas est gale zro, auquel cas on lance une exception. Ici, cette exception est une chane. Linstruction catch de la ligne 24 dbute un gestionnaire qui attend un pointeur vers un caractre constant. Avec les exceptions, les chanes sont captures par un pointeur de caractre constant : le gestionnaire commenant la ligne 24 intercepte donc le throw de la ligne 18. La ligne 26 afche entre astrisques la chane qui a t passe. La ligne 27 contient laccolade fermante qui signale la n du gestionnaire ; le contrle passe alors la premire ligne qui suit les instructions catch et le programme se poursuit jusqu la n. Si votre exception avait concern un problme plus srieux, vous auriez pu quitter lapplication aprs avoir afch le message de la ligne 26. Si vous lancez lexception dans une fonction qui a t appele par une autre fonction, vous pouvez la faire remonter la fonction appelante. Pour cela, il suft dappeler linstruction throw sans aucun paramtre. Lexception en cours sera alors relance partir de lemplacement courant.
Chapitre 20
709
13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59:
// oprateurs Array& operator=(const Array&); int& operator[](int indice); const int& operator[](int indice) const; // mthode daccs int GetTaille() const { return saTaille; } // fonction amie friend ostream& operator<< (ostream&, const Array&); class xLimites {}; private: int *pType; int saTaille; }; Array::Array(int taille): saTaille(taille) { pType = new int[taille]; for (int i = 0; i < taille; i++) pType[i] = 0; } Array& Array::operator=(const Array &rhs) { if (this == &rhs) return *this; delete [] pType; saTaille = rhs.GetTaille(); pType = new int[saTaille]; for (int i = 0; i < saTaille; i++) { pType[i] = rhs[i]; } return *this; } Array::Array(const Array &rhs) { saTaille = rhs.GetTaille(); pType = new int[saTaille]; for (int i = 0; i < saTaille; i++) { pType[i] = rhs[i]; // dfinit la classe exception
710
Le langage C++
60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89: 90: 91: 92: 93: 94: 95: 96: 97: 98: 99: 100: 101: 102: 103: 104: 105:
} } int& Array::operator[](int indice) { int size = GetTaille(); if (indice >= 0 && indice < GetTaille()) return pType[indice]; throw xLimites(); return pType[0]; } const int& Array::operator[](int indice) const { int maTaille = GetTaille(); if (indice >= 0 && indice < GetTaille()) return pType[indice]; throw xLimites(); return pType[0]; // pour MSC } ostream& operator<< (ostream& out, const Array& unTableau) { for (int i = 0; i < unTableau.GetTaille(); i++) out << "[" << i << "] " << unTableau[i] << endl; return out; } int main() { Array TabInt(20); try { for (int j = 0; j< 100; j++) { TabInt [j] = j; cout << "TabInt [" << j << "] OK..." << endl; } } catch (Array::xLimites) { cout << "Impossible de traiter lentre !" << endl; } cout << "Fin." << endl; return 0; }
Chapitre 20
711
Le Listing 20.4 prsente une classe Array trs simple mais, cette fois-ci, on a ajout le traitement des exceptions pour traiter les cas o lon tente daccder en dehors des limites du tableau. La ligne 24 dclare la nouvelle classe xLimites au sein mme de la dclaration de Array. Cette classe, qui ne contient aucune donne, nest quune classe comme les autres, qui le compilateur attribuera automatiquement un constructeur, un destructeur, un constructeur copie et loprateur daffectation (gal) par dfaut. Elle a donc uniquement ces quatre mthodes. Le fait de dclarer la classe exception dans Array na dautre but que de grouper ces deux classes. Comme on la expliqu au Chapitre 16, Array ne bnciera pas pour autant dun accs particulier xLimites, et celle-ci naura pas non plus un accs privilgi aux membres de Array. Les lignes 63-70 et 72-79 modient les oprateurs dindexation pour quils testent lindice demand. Si sa valeur ne se trouve pas dans la bonne plage de valeurs, ils lancent la classe xLimites comme une exception. Les parenthses sont ncessaires pour diffrencier lappel au constructeur de xLimites et lutilisation dune constante numre.
712
Le langage C++
la ligne 90, la fonction principale dclare un objet Array pouvant contenir vingt valeurs. Le bloc try se trouve aux lignes 91 98 et tente dajouter 101 entiers au tableau. Le gestionnaire destin intercepter les exceptions xLimites est dclar la ligne 99. Chaque lment du tableau est initialis dans le bloc try qui est cr dans le programme. Lorsque j atteint la valeur 20 ( la ligne 93), on accde llment cet indice, le test de la ligne 66 choue, et operateur[] lance une exception xLimites la ligne 67. Lexcution du programme se poursuit avec le bloc catch de la ligne 99, qui traite lexception sur la mme ligne pour afcher un simple message derreur. Lexcution reprend la n du bloc catch, la ligne 102.
Chapitre 20
713
stocks dans la pile. Une fonction rcursive peut donc apparatre plusieurs fois dans cette pile. Lexception entrane le droulement de la pile pour chaque bloc de code. mesure que la pile est droule, les destructeurs des objets locaux placs sur la pile sont appels et ces objets sont dtruits. Une ou plusieurs instructions catch suivent chaque bloc try. Si lexception correspond lune des instructions catch, elle est considre comme traite par lexcution de cette instruction. Si elle ne correspond aucun catch, le droulement de la pile se poursuit. Si lexception remonte ainsi jusquau dbut du programme (main()) sans avoir t traite, un gestionnaire intgr est appel pour interrompre le programme. Il est important de noter que le droulement de la pile pour traiter une exception est une opration sens unique. mesure quelle se produit, la pile est droule et les objets quelle contient sont dtruits. Il ny a aucune possibilit de retour en arrire. Une fois lexception traite, le programme reprend lexcution aprs le bloc try dans lequel tait intgre linstruction catch qui a trait lexception. Dans le Listing 20.4, par exemple, lexcution va continuer en ligne 101, la premire ligne aprs le bloc try de linstruction catch qui a trait lexception xLimites. Noubliez jamais que lorsquune exception est lance, lexcution du programme reprend aprs le bloc catch, pas aprs lendroit do a t dclench lexception.
714
Le langage C++
10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53:
Array(const Array &rhs); ~Array() { delete [] pType;} // oprateurs Array& operator=(const Array&); int& operator[](int indice); const int& operator[](int indice) const; // mthodes daccs int GetTaille() const { return saTaille; } // fonction amie friend ostream& operator<< (ostream&, const Array&); // dfinit les classes dexception class xLimites {}; class xTropGrand {}; class xTropPetit{}; class xZero {}; class xNegatif {}; private: int *pType; int saTaille; }; int& Array::operator[](int indice) { int taille = GetTaille(); if (indice >= 0 && indice < GetTaille()) return pType[indice]; throw xLimites(); return pType[0]; }
const int& Array::operator[](int indice) const { int maTaille = GetTaille(); if (indice >= 0 && indice < GetTaille()) return pType[indice]; throw xLimites(); return pType[0]; } // pour MFC
Chapitre 20
715
54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89: 90: 91: 92: 93: 94: 95: 96: 97: 98: 99:
Array::Array(int taille): saTaille(taille) { if (taille == 0) throw xZero(); if (taille < 10) throw xTropPetit(); if (taille > 30000) throw xTropGrand(); if (taille < 1) throw xNegatif(); pType = new int[taille]; for (int i = 0; i < taille; i++) pType[i] = 0; } int main() { try { Array TabInt(0); for (int j = 0; j < 100; j++) { TabInt[j] = j; cout << "TabInt[" << j << "] OK..." << endl; } } catch (Array::xLimites) { cout << "Impossible de traiter votre entre !" << endl; } catch (Array::xTropGrand) { cout << "Ce tableau est trop grand..." << endl; } catch (Array::xTropPetit) { cout << "Ce tableau est trop petit..." << endl; } catch (Array::xZero) { cout << "Vous avez demand un tableau"; cout << " de zro objets !" << endl;
716
Le langage C++
} catch (...) { cout << "Un incident sest produit !" << endl; } cout << "Fin." << endl; return 0; }
Les lignes 25 29 crent quatre nouvelles classes : xTropGrand, xTropPetit, xZero, et xNegatif. Le constructeur teste maintenant la taille qui lui a t transmise (lignes 56 71) et lance une exception si cette taille est trop petite, trop grande, ngative ou nulle. On a modi le bloc try en y ajoutant des instructions catch qui traitent chaque cas, sauf xNegatif : cette dernire sera donc prise en charge par dernire instruction catch(...), la ligne 101. Testez ce programme en utilisant diverses valeurs pour la taille du tableau. Si vous choisissez une taille gale -5, vous remarquerez que lexception xNegatif nest pas lance : ceci est d lordre des tests dans le constructeur puisque la condition taille < 10 est value avant taille < 1. Il suft donc dinverser les lignes 61-62 avec les lignes 65-66 pour corriger ce problme puis de recompiler. Lorsque le constructeur a t appel, de la mmoire a t alloue lobjet. Le lancement dune exception depuis le constructeur peut donc laisser lobjet allou, mais inutilisable. Il faut donc gnralement entourer le constructeur dun bloc try/catch et, en cas dexception, marquer lobjet (en interne) comme inutilisable. Chaque fonction membre devrait vrier cette balise "valide" pour tre sre que dautres erreurs ne surgiront pas lorsque lon utilisera un objet dont linitialisation a t interrompue.
ce Astu
Chapitre 20
717
Array::Array(int taille): saTaille(taille) { if (taille == 0) throw xZero(); if (taille > 30000) throw xTropGrand(); if (taille < 1)
718
Le langage C++
45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89: 90: 91:
throw xNegatif(); if (taille < 10) throw xTropPetit(); pType = new int[taille]; for (int i = 0; i < taille; i++) pType[i] = 0; } int& Array::operator[](int indice) { int taille = GetTaille(); if (indice >= 0 && indice < taille) return pType[indice]; throw xLimites(); return pType[0]; // pour MFC } const int& Array::operator[](int indice) const { int maTaille = GetTaille(); if (indice >= 0 && indice < maTaille) return pType[indice]; throw xLimites(); return pType[0]; } int main() { try { Array TabInt(0); for (int j = 0; j < 100; j++) { TabInt[j] = j; cout << "TabInt[" << j << "] OK..." << endl; } } catch (Array::xLimites) { cout << "Impossible de traiter votre entre !" << endl; } catch (Array::xTropGrand) { cout << "Ce tableau est trop grand..." << endl; // pour MFC
Chapitre 20
719
92: 93: 94: 95: 96: 97: 98: 99: 100: 101: 102: 103: 104: 105: 106: 107: 108: 109:
} catch (Array::xTropPetit) { cout << "Ce tableau est trop petit..." << endl; } catch (Array::xZero) { cout << "Vous avez demand un tableau"; cout << " de zro objets !" << endl; } catch (...) { cout << "Un incident sest produit !" << endl; } cout << "Fin." << endl; return 0; }
Les modications les plus signicatives se situent aux lignes 27 30, qui dcrivent la hirarchie des classes. Les classes xTropGrand, xTropPetit et xNegatif drivent dsormais de xTaille, et xZero drive de xTropPetit. Vous remarquerez quon a cr un tableau de taille zro, mais que lon na pourtant pas obtenu lexception attendue ! En examinant les blocs catch, vous pouvez constater que lexception xTropPetit est examine avant lexception xZero. Notre objet xZero tant aussi un objet xTropPetit, cest le gestionnaire de cette dernire exception qui traite le problme. Lordre des gestionnaires dexception est important et les plus spciques doivent tre placs en tte alors que les plus gnraux doivent apparatre la n. Pour corriger le problme prcdent, il suft donc dinverser les deux gestionnaires de xTropPetit et xZero.
720
Le langage C++
donc leur associer des donnes et les initialiser dans le constructeur. Celles-ci pourront ensuite tre consultes tout moment. Le Listing 20.7 en prsente un exemple. Listing 20.7 : Rcupration de donnes dans un objet exception
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: #include <iostream> using namespace std; const int TailleDefaut = 10; class Array { public: // constructeurs Array(int taille = TailleDefaut); Array(const Array &rhs); ~Array() { delete [] pType;} // oprateurs Array& operator=(const Array&); int& operator[](int indice); const int& operator[](int indice) const; // mthode daccs int GetTaille() const { return saTaille; } // fonction amie friend ostream& operator<< (ostream&, const Array&); // dfinit les classes dexception class xLimites {}; class xTaille { public: xTaille(int taille):saTaille(taille) {} ~xTaille(){} int GetTaille() { return saTaille; } private: int saTaille; }; class xTropGrand: public xTaille { public: xTropGrand(int taille):xTaille(taille) {} };
Chapitre 20
721
42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88:
class xTropPetit: public xTaille { public: xTropPetit(int taille):xTaille(taille) {} }; class xZero: public xTropPetit { public: xZero(int taille):xTropPetit(taille) {} }; class xNegatif: public xTaille { public: xNegatif(int taille):xTaille(taille) {} }; private: int *pType; int saTaille; };
Array::Array(int taille): saTaille(taille) { if (taille == 0) throw xZero(taille); if (taille > 30000) throw xTropGrand(taille); if (taille < 1) throw xNegatif(taille); if (taille < 10) throw xTropPetit(taille); pType = new int[taille]; for (int i = 0; i < taille; i++) pType[i] = 0; }
int& Array::operator[] (int indice) { int taille = GetTaille(); if (indice >= 0 && indice < taille) return pType[indice];
722
Le langage C++
89: 90: 91: 92: 93: 94: 95: 96: 97: 98: 99: 100: 101: 102: 103: 104: 105: 106: 107: 108: 109: 110: 111: 112: 113: 114: 115: 116: 117: 118: 119: 119a: 120: 120a: 121: 122: 123: 124: 125: 125a: 126: 127: 128: 129: 130: 130a: 131:
throw xLimites(); return pType[0]; } const int& Array::operator[] (int indice) const { int taille = GetTaille(); if (indice >= 0 && indice < taille) return pType[indice]; throw xLimites(); return pType[0]; } int main() { try { Array TabInt(9); for (int j = 0; j < 100; j++) { TabInt[j] = j; cout << "TabInt[" << j << "] OK..." << endl; } } catch (Array::xLimites) { cout << "Impossible de traiter lentre !" << endl; } catch (Array::xZero uneException) { cout << "Vous avez demand un tableau de zro objet !" << endl; cout << "Taille indique : " << uneException.GetTaille() << endl; } catch (Array::xTropGrand uneException) { cout << "Ce tableau est trop grand..." << endl; cout << "Taille indique : " << uneException.GetTaille() << endl; } catch (Array::xTropPetit uneException) { cout << "Ce tableau est trop petit..." << endl; cout << "Taille indique : " << uneException.GetTaille() << endl; }
Chapitre 20
723
catch (...) { cout << "Un incident inconnu sest produit !\n"; } cout << "Fin." << endl; return 0; }
La dclaration de la classe xTaille a t modie pour inclure la variable membre saTaille (ligne 33) et la fonction membre getTaille() (ligne 31). En outre, on a ajout en ligne 29 un constructeur prenant un entier en paramtre, an dinitialiser la variable membre saTaille. Les classes drives dclarent un constructeur qui se contente dinitialiser la classe de base. Aucune autre fonction na t dclare an de rduire la taille de notre exemple. Les instructions catch des lignes 113 135 ont t modie pour nommer lexception quelles interceptent : uneException. Elles utilisent cet objet pour accder au contenu de saTaille. Noubliez jamais que si vous construisez une exception, cest que vous vous trouvez dans une situation exceptionnelle : une erreur sest produite et votre exception ne doit pas crer le mme problme. Si vous crez, par exemple, une exception OutOfMemory, vous ne devrez pas allouer de mmoire dans son constructeur.
Info
Lafchage du message appropri par chaque instruction catch est fastidieux et peut tre lorigine de nouvelles erreurs. Cette tche appartient lobjet qui connat son type et la valeur quil a reue. Le Listing 20.8 traite le problme avec une approche plus "oriente objet", en utilisant des fonctions virtuelles pour que chaque exception "fasse ce quil faut". Listing 20.8 : Passage par rfrence et utilisation des fonctions virtuelles dans les exceptions
0: 1: 2: 3: 4: #include <iostream> using namespace std; const int TailleDefaut = 10;
724
Le langage C++
5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51:
class Array { public: // constructeurs Array(int taille = TailleDefaut); Array(const Array &rhs); ~Array() { delete [] pType;} // oprateurs Array& operator=(const Array&); int& operator[](int indice); const int& operator[](int indice) const; // mthode daccs int GetTaille() const { return saTaille; } // fonction amie friend ostream& operator<< (ostream&, const Array&); // dfinit les classes dexception class xLimites {}; class xTaille { public: xTaille(int taille):saTaille(taille) {} ~xTaille(){} virtual int GetTaille() { return saTaille; } virtual void AffErreur() { cout << "Erreur de taille. Taille indique : "; cout << saTaille << endl; } protected: int saTaille; }; class xTropGrand: public xTaille { public: xTropGrand(int taille):xTaille(taille){} virtual void AffErreur() { cout << "Trop grand ! Taille indique : "; cout << xTaille::saTaille << endl; } };
Chapitre 20
725
52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89: 90: 91: 92: 93: 94: 95: 96: 97: 98:
class xTropPetit: public xTaille { public: xTropPetit(int taille):xTaille(taille){} virtual void AffErreur() { cout << "Trop petit ! Taille indique : "; cout << xTaille::saTaille << endl; } }; class xZero : public xTropPetit { public: xZero(int taille):xTropPetit(taille){} virtual void AffErreur() { cout << "Zro !!. Taille indique : "; cout << xTaille::saTaille << endl; } }; class xNegatif: public xTaille { public: xNegatif(int taille):xTaille(taille){} virtual void AffErreur() { cout << "Ngatif ! Taille indique : "; cout << xTaille::saTaille << endl; } }; private: int *pType; int saTaille; }; Array::Array(int taille): saTaille(taille) { if (taille == 0) throw xZero(taille); if (taille > 30000) throw xTropGrand(taille); if (taille < 0)
726
Le langage C++
99: 100: 101: 102: 103: 104: 105: 106: 107: 108: 109: 110: 111: 112: 113: 114: 115: 116: 117: 118: 119: 120: 121: 122: 123: 124: 125: 126: 127: 128: 129: 130: 131: 132: 133: 134: 135: 136: 137: 138: 139: 140: 141: 142:
throw xNegatif(taille); if (taille < 10) throw xTropPetit(taille); pType = new int[taille]; for (int i = 0; i < taille; i++) pType[i] = 0; } int& Array::operator[] (int indice) { int taille = GetTaille(); if (indice >= 0 && indice < taille ) return pType[indice]; throw xLimites(); return pType[0]; } const int& Array::operator[] (int indice) const { int taille = GetTaille(); if (indice >= 0 && indice < taille) return pType[indice]; throw xLimites(); return pType[0]; } int main() { try { Array TabInt(9); for (int j = 0; j < 100; j++) { TabInt[j] = j; cout << "TabInt[" << j << "] OK..." << endl; } } catch (Array::xLimites) { cout << "Impossible de traiter lentre !" << endl; } catch (Array::xTaille& uneException) {
Chapitre 20
727
uneException.AffErreur(); } catch (...) { cout << "Un incident sest produit !" << endl; } cout << "Fin." << endl; return 0; }
La mthode virtuelle AffErreur() est dclare dans la classe xTaille (lignes 33 37). Elle afche un message derreur et la taille relle de la classe. Cette fonction est surcharge dans chacune des classes drives. la ligne 141, lobjet exception est dclar dans le gestionnaire dexceptions comme une rfrence. Lorsque AffErreur() est appele avec la rfrence dun objet, cest la bonne version de cette fonction qui est excute grce au polymorphisme. Le code est plus facile comprendre et donc maintenir.
Exceptions et modles
Lorsque vous crez des exceptions pour fonctionner avec des modles, vous avez le choix entre crer une exception pour chaque instance du modle ou utiliser des classes dexceptions dclares en dehors de la dclaration du modle. Le Listing 20.9 illustre les deux solutions. Listing 20.9 : Utilisation des exceptions avec les modles
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: #include <iostream> using namespace std; const int TailleDefaut = 10; class xLimites {}; template <class T> class Array { public:
728
Le langage C++
10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56:
// constructeurs Array(int taille = TailleDefaut); Array(const Array &rhs); ~Array() { delete [] pType;} // oprateurs Array& operator=(const Array<T>&); T& operator[](int indice); const T& operator[](int indice) const; // mthode daccs int GetTaille() const { return saTaille; } // fonction amie friend ostream& operator<< (ostream&, const Array<T>&); // dfinit les classes dexception class xTaille {}; private: int *pType; int saTaille; }; template <class T> Array<T>::Array(int taille): saTaille(taille) { if (taille < 10 || taille > 30000) throw xTaille(); pType = new T[taille]; for (int i = 0; i < taille; i++) pType[i] = 0; } template <class T> Array<T>& Array<T>::operator=(const Array<T> &rhs) { if (this == &rhs) return *this; delete [] pType; saTaille = rhs.GetTaille(); pType = new T[saTaille]; for (int i = 0; i < saTaille; i++) pType[i] = rhs[i]; }
Chapitre 20
729
57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89: 90: 91: 92: 93: 94: 95: 96: 97: 98: 99: 100: 101: 102: 103:
template <class T> Array<T>::Array(const Array<T> &rhs) { saTaille = rhs.GetTaille(); pType = new T[saTaille]; for (int i = 0; i < saTaille; i++) pType[i] = rhs[i]; } template <class T> T& Array<T>::operator[](int indice) { int taille = GetTaille(); if (indice >= 0 && indice < taille) return pType[indice]; throw xLimites(); return pType[0]; } template <class T> const T& Array<T>::operator[](int indice) const { int taille = GetTaille(); if (indice >= 0 && indice < taille) return pType[indice]; throw xLimites(); } template <class T> ostream& operator<< (ostream& out, const Array<T>& unTab) { for (int i = 0; i < unTab.GetTaille(); i++) out << "[" << i << "] " << unTab[i] << endl; return out; }
int main() { try { Array<int> TabInt(9); for (int j = 0; j < 100; j++) { TabInt[j] = j; cout << "TabInt[" << j << "] OK..." << endl; }
730
Le langage C++
104: 105: 106: 107: 108: 109: 110: 111: 112: 113: 114: 115: 116:
} catch (xLimites) { cout << "Impossible de traiter lentre !" << endl; } catch (Array<int>::xTaille) { cout << "Taille incorrecte !" << endl; } cout << "Fin." << endl; return 0; }
La premire exception, xLimites, est dclare la ligne 4 en dehors de la dnition du modle. La seconde exception, xTaille, est dclare dans la dnition du modle, la ligne 28. Lexception xLimites nest donc pas lie la classe modle, mais on peut lutiliser comme toute autre classe. xTaille, au contraire, est lie au modle et doit tre appele sur la base de linstanciation de Array. La syntaxe des deux instructions catch est donc diffrente : la ligne 105 utilise catch(xLimites) alors que la ligne 109 utilise catch(Array<int>::xTaille). Cette dernire ligne est lie linstanciation dun Array dentiers.
Exceptions et erreurs
Faut-il utiliser les exceptions pour le traitement des erreurs classiques ? Lavis des programmeurs C++ est partag. Certains pensent que, par leur nature mme, les exceptions devraient tre rserves aux circonstances exceptionnelles mais prvisibles (do leurs noms !), que le programmeur doit anticiper et quelles ne devraient donc pas tre servir traiter les erreurs classiques. Dautres pensent que les exceptions reprsentent une mthode efcace pour traverser plusieurs couches dappels de fonctions sans courir le risque de provoquer des fuites mmoires. Un exemple classique est celui o lutilisateur demande une action dans un environnement GUI. La partie de code qui rcupre la demande doit appeler une fonction membre dans un gestionnaire de dialogue, qui appelle son tour le code qui va traiter la demande, qui appelle le code qui choisit la bote de dialogue utiliser, qui appelle le code
Chapitre 20
731
qui afche la bote de dialogue, qui appelle enn le code qui va traiter lentre de lutilisateur. Si lutilisateur clique sur le bouton Annuler, le code doit revenir la toute premire mthode appele, o la requte initiale tait traite. Une solution ce problme consiste placer un bloc try autour de lappel initial et intercepter AnnulerDialog comme une exception pouvant tre lance par le gestionnaire du bouton Annuler. Cette mthode est sre et efcace, mais actionner le bouton Annuler est un comportement classique du programme, pas une circonstance exceptionnelle. Il existe une faon raisonnable daborder la question : cette utilisation des exceptions rendelle le code plus simple ou plus compliqu ? Y a-t-il ainsi moins ou plus de risques derreurs ou de fuites de mmoire ? Le code sera-t-il plus facile ou plus dur maintenir ? Vous trouverez les rponses en analysant les compromis possibles car il nexiste pas de rponse toute faite.
Pourriture du code
La pourriture du code est un phnomne bien connu de dtrioration du logiciel lorsque celui-ci est nglig. Un programme bien conu, parfaitement test et corrig, aura invitablement besoin dtre entretenu. Aprs quelques mois, votre client dcouvrira quune moisissure verte recouvre votre code et que la plupart de vos objets commencent scailler. part livrer votre code source dans un conteneur sous vide, votre seule protection sera dcrire du code facile corriger ds que les problmes surgiront, car ils ne manqueront pas dapparatre tt ou tard. La pourriture du code est une plaisanterie de programmeur, mais elle enseigne une chose importante. Les programmes sont de plus en plus complexes et les erreurs peuvent rester caches trs longtemps. Protgez-vous en produisant du code facile maintenir.
Info
Ceci signie que votre code doit tre clair et correctement comment dans ses parties les plus compliques. Aprs six mois, vous le consulterez avec les yeux dun tranger en vous demandant quel esprit tortur a pu produire une telle chose...
732
Le langage C++
Tous les compilateurs vous autorisent compiler avec ou sans symboles. Dans le cas dune compilation avec symbole, le compilateur doit crer les correspondances ncessaires entre le code source et le programme produit : cest ce qui permettra au dbogueur de pointer sur la ligne de code qui correspond laction suivante du programme. Les dbogueurs symboliques plein cran vous facilitent encore plus la tche puisquils peuvent lire votre code source et lafcher dans une fentre. Vous pouvez ensuite sauter les appels de fonction ou demander au dbogueur dexcuter la fonction, ligne par ligne. Avec la plupart des dbogueurs, vous pouvez passer du code source la sortie du programme pour examiner le rsultat de chaque instruction excute. Vous avez mme la possibilit de contrler ltat courant des variables, de visualiser les structures de donnes complexes et la valeur des donnes membres dans une classe. Vous pouvez galement contrler les valeurs relles en mmoire des divers pointeurs et autres emplacements mmoire. Vous pouvez galement effectuer plusieurs types de contrles depuis le dbogueur en incluant des points darrt, des points de contrle, en examinant la mmoire et en visualisant le code assembleur.
Points darrt
Les points de darrt sont des instructions destines au dbogueur qui demandent larrt du programme une ligne donne. Ils permettent dexcuter normalement le programme jusqu la ligne indique. Les points darrt facilitent donc lexamen de ltat des variables avant et aprs une ligne de code critique.
Points de contrle
Vous pouvez demander au dbogueur de vous montrer la valeur dune variable particulire, ou de faire une pause lorsque cette variable est lue ou crite. Les points de contrle vous permettent de dnir ces conditions et mme de modier la valeur dune variable pendant lexcution du programme.
Examen de la mmoire
Il est quelquefois important de pouvoir visualiser les valeurs relles stockes en mmoire. Les dbogueurs modernes peuvent montrer ces valeurs sous la forme de la variable relle : les chanes sont afches avec des caractres, les entiers longs sous la forme de nombres plutt que quatre octets, et ainsi de suite. Les dbogueurs les plus sophistiqus permettent mme de visualiser les classes compltes, avec la valeur de toutes les variables membres, y compris le pointeur this.
Chapitre 20
733
Assembleur
La lecture du code source est gnralement sufsante pour dnicher les erreurs, mais si toutes les tentatives ont chou, vous pouvez demander au dbogueur dafcher le code assembleur produit pour chaque ligne du code source. Vous pouvez ainsi examiner les registres mmoire et les indicateurs (ags) pour vous plonger dans le fonctionnement interne du programme. Apprenez utiliser votre dbogueur car cest loutil le plus efcace pour la recherche des erreurs. Les erreurs dexcution sont les plus difciles trouver, mais un dbogueur puissant rend possible, sinon facile, leur limination quasi complte.
Questions-rponses
Q Quel intrt y a-t-il provoquer des exceptions ? Pourquoi ne pas traiter les erreurs l o elles se trouvent ? R La mme erreur se retrouve souvent divers endroits dans le programme. Les exceptions permettent de centraliser la gestion des erreurs. En outre, la partie du code qui produit lerreur peut ne pas tre lendroit idal pour savoir comment traiter cette erreur. Q Pourquoi faut-il produire un objet ? Pourquoi ne pas passer simplement un code de lerreur ? R Les objets sont plus souples et plus puissants que les codes derreurs. Ils vhiculent davantage dinformations et le mcanisme constructeur/destructeur permet de crer et supprimer les ressources qui peuvent tre ncessaires un traitement correct de lexception. Q Pourquoi ne pas utiliser les exceptions pour traiter dautres conditions que les erreurs ? Il pourrait tre intressant davoir la possibilit de revenir trs vite des zones de code prcdentes, mme sil ny a pas eu de condition exceptionnelle. R Effectivement, certains programmeurs C++ utilisent les exceptions de cette faon. Le danger est que ces exceptions peuvent crer des fuites mmoires lorsque la pile est droule et que certains objets peuvent tre laisss par inadvertance sur le tas. Une bonne technique de programmation et un bon compilateur permettent gnralement dviter ces problmes. Cest donc essentiellement une question de got, certains programmeurs pensent que les exceptions, par nature, ne sont pas destines traiter des conditions normales. Q Doit-on intercepter une condition l o le bloc try la cre ? R Non, lexception peut tre intercepte nimporte o dans la pile des appels. Elle remonte dans cette pile jusqu ce quelle soit traite.
734
Le langage C++
Q Pourquoi utiliser un dbogueur alors que vous pouvez employer cout et dautres instructions du mme genre ? R Un dbogueur fournit un mcanisme beaucoup plus efcace pour parcourir le code pas pas et visualiser la modication des valeurs, sans avoir besoin de polluer le code avec des milliers dinstructions de dbogage. Il existe en outre un risque signicatif chaque fois que vous ajoutez ou supprimez des lignes de code. Si vous venez de rgler un problme grce au dbogage et que vous supprimez accidentellement une vraie ligne de code en effaant la ligne de dbogage, la situation ne se sera pas meilleure.
Exercices
1. Crez un bloc try, une instruction catch et une exception simple. 2. Modiez la rponse prcdente pour placer une donne et une mthode daccs dans lexception et utilisez cette mthode dans le bloc catch. 3. Transformez la classe de lExercice 2 pour crer une hirarchie dexceptions. Modiez le bloc catch pour utiliser les objets drivs et les objets de base. 4. Modiez le programme de lExercice 3 pour obtenir trois niveaux dappels de fonction.
Chapitre 20
735
5. CHERCHEZ LERREUR :
#include "stringc.h" // notre classe chane
class xOutOfMemory { public: xOutOfMemory( const String& ou ): endroit( ou ){} ~xOutOfMemory(){} virtual String ou(){ return endroit }; private: String endroit; } int main() { try { char *var = new char; if ( var == 0 ) throw xOutOfMemory(); } catch( xOutOfMemory& uneException ) { cout << "Mmoire insuffisante ladresse " << uneException.endroit() << endl; } return 0; }
Ce listing prsente la gestion des exceptions lors de la survenue dune erreur pour cause de mmoire insufsante.
21
Et maintenant ?
Au sommaire de ce chapitre
La compilation conditionnelle et la faon de la grer Lcriture de macros en utilisant le prprocesseur Lutilisation du prprocesseur pour rechercher les erreurs La manipulation des bits et leur utilisation comme indicateurs Les tapes suivantes pour utiliser efcacement le langage C++
Flicitations ! Vous devriez maintenant avoir atteint un bon niveau de programmation en C++. Vous ne devez cependant pas relcher vos efforts parce que vous aurez toujours quelque chose de nouveau apprendre. Ce chapitre apporte quelques prcisions supplmentaires et donne quelques pistes pour la poursuite de votre tude. Vos chiers source sont constitus de code C++. Ce code est interprt par le compilateur pour tre transform en programme excutable. Cependant, le compilateur nintervient quaprs le passage du prprocesseur, qui offre donc lopportunit deffectuer une compilation conditionnelle.
738
Le langage C++
Le prprocesseur et le compilateur
chaque fois que vous excutez le compilateur, le prprocesseur sexcute dabord. Celui-ci recherche les instructions qui lui sont destines, celles qui commencent par le symbole dise (#). Chacune de ces instructions entrane une modication du code source. Le rsultat obtenu est un nouveau chier source, un chier temporaire que vous ne voyez pas en temps normal, mais que vous pouvez sauvegarder par lintermdiaire du compilateur si vous le dsirez. Le compilateur ne lit pas le chier source initial : cest la sortie du prprocesseur quil compile. Vous avez dj constat cet effet avec la directive #include, qui demande au prprocesseur de trouver le chier indiqu et de placer son contenu dans le chier temporaire en remplacement de la directive. Cela revient donc exactement saisir le contenu du chier inclus dans le code source : lorsque le compilateur verra votre code, le chier inclus y sera. Presque tous les compilateurs possdent une option que vous pouvez activer dans lenvironnement de dveloppement intgr (IDE) ou sur la ligne de commande et qui vous permet de demander la sauvegarde du chier intermdiaire. Consultez la documentation de votre compilateur si vous voulez lutiliser.
ce Astu
vous demandez au prprocesseur de remplacer toutes les occurrences de GRAND par la chane 512. Il ne sagit pas dune chane au sens C++ de ce terme. Les caractres 512 seront simplement substitus dans le code source chaque fois que le mot GRAND sera rencontr. Si vous crivez :
#define GRAND 512 int monTableau[GRAND];
Chapitre 21
Et maintenant ?
739
Vous remarquerez que la directive #define a disparu. Les directives du prprocesseur sont toutes supprimes du chier intermdiaire ; elles napparaissent plus dans le code source nal.
Vous pourrez tester ensuite si DEBUG a t dni et agir en consquence. Noubliez pas que tout ceci se passe au niveau du prprocesseur, pas au niveau du compilateur, ni au cours de lexcution du programme. Lorsque le prprocesseur lit la directive #if defined, il recherche si vous avez dni la valeur qui la suit. Si cest le cas, defined est value vrai : tout ce qui se trouve entre #if defined DEBUG et #endif est crit dans le chier intermdiaire et sera donc compil. Dans le cas contraire, la partie entre #if defined DEBUG et #endif ne sera pas crite dans le chier intermdiaire, comme si elle ntait jamais apparue dans le code source.
#if defined DEBUG cout << "Debug est dfini"; #endif
Vous pouvez aussi tester si une valeur nest pas dnie, en utilisant loprateur not avec la directive defined :
#if!defined DEBUG cout << "Debug nest pas dfini"; #endif
740
Le langage C++
#ifndef est linverse logique de #ifdef. Lvaluation de cette instruction donne true si la chane na pas t dnie dans les lignes prcdentes du chier. Notez que tous ces tests exigent #endif pour indiquer la n du code concern.
La commande #else
La commande #else peut tre insre entre #ifdef ou #ifndef et le #endif de n. Le Listing 21.1 illustre lutilisation de ces divers termes. Listing 21.1 : Utilisation de #dene
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: #define DemoVersion #define SW_VERSION5 #include <iostream> using std::endl; using std::cout; int main() { cout << "Contrle des dfinitions de DemoVersion, "; cout << "SW_VERSION et WINDOWS_VERSION..." << endl; #ifdef DemoVersion cout << "DemoVersion dfinie." << endl; #else cout << "DemoVersion non dfinie." << endl; #endif #ifndef SW_VERSION cout << "SW_VERSION non dfinie !" << endl; #else cout << "SW_VERSION dfinie avec: " << SW_VERSION << endl; #endif #ifdef WINDOWS_VERSION cout << "WINDOWS_VERSION dfinie !" << endl; #else cout << "WINDOWS_VERSION nest pas dfinie." << endl; #endif cout << "Fin." << endl; return 0; }
Chapitre 21
Et maintenant ?
741
VersionDemo et SW_VERSION sont dnies aux lignes 0 et 1, SW_VERSION tant associe la chane 5. La dnition de VersionDemo est teste la ligne 12 : comme le rsultat de ce test renvoie true (bien que cette constante ne soit associe aucune valeur), la chane de la ligne 11 est afche. La ligne 18 teste si SW_VERSION nest pas dni. Cette constante tant dnie, ce test renvoie false et lexcution se poursuit la ligne 21. La chane 5 est alors substitue au mot SW_VERSION, cest la ligne suivante qui sera vue par le compilateur :
cout << "SW_VERSION dfinie avec: " << 5 << endl;
Vous pouvez remarquer que le premier mot SW_VERSION na pas t remplac parce quil se trouve entre guillemets. Le second a cependant t remplac et le compilateur voit donc la valeur 5 comme si vous laviez ici vous-mme plac l. Pour terminer, le programme teste la prsence de la dnition de WINDOWS_VERSION la ligne 25. Ce mot ntant pas dni, le test choue et le message de la ligne 28 est afch.
742
Le langage C++
hriter de Animal. Le chier en-tte de Chat devra galement inclure animal.hpp pour la mme raison. Si vous crez un programme qui utilise la fois Chat et Chien, vous risquez donc dinclure deux fois animal.hpp, ce qui produira une erreur de compilation parce que lon ne peut pas dclarer deux fois la mme classe (Animal), mme si ces dclarations sont identiques. Vous pouvez rsoudre ce problme avec des clauses dinclusion. Entourez le contenu du chier en-tte animal.hpp par ces lignes :
#ifndef ANIMAL_HPP #define ANIMAL_HPP ... #endif
Si le terme ANIMAL_HPP na pas t dni, les lignes suivantes seront excutes. Elles dniront donc ANIMAL.HPP, puis inclueront le chier entier. La seconde fois que le programme incluera ce chier, il lira la premire ligne mais, cette fois-ci, le test chouera car vous avez dj dni ANIMAL.HPP. Le prprocesseur ne traitera donc aucune ligne jusqu la prochaine directive #else (il ny en pas ici) ou #endif (qui se trouve ici la n du chier). La classe ne sera donc jamais dnie deux fois. Le nom de la constante symbolique (ANIMAL.HPP) na pas dimportance, bien quil soit courant dutiliser le nom du chier en majuscules en remplaant le point par un blanc soulign. Il ne sagit toutefois que dune convention ; cependant, comme vous ne pouvez pas donner le mme nom deux chiers diffrents, cette convention fonctionne bien. Nhsitez pas utiliser des clauses dinclusion car elles peuvent vous faire gagner des heures de dbogage.
Info
Les macros
La directive #define permet galement de crer des macro-fonctions (souvent appeles simplement macros). Une macro est un symbole cr avec #define et qui reoit un argument comme une fonction. Le prprocesseur substituera la macro et son paramtre par la chane de substitution dans laquelle le paramtre aura lui-mme t substitu. Vous pouvez dnir, par exemple, la macro DOUBLE de la faon suivante :
#define DOUBLE(x) ((x) * 2)
Chapitre 21
Et maintenant ?
743
Le prprocesseur remplacera la chane DOUBLE(4) par la valeur ( (4) * 2 ), puis remplacera cette expression par 4 * 2, soit 8. Une macro peut avoir plusieurs paramtres, et chaque paramtre peut tre utilis plusieurs fois dans le texte de remplacement. MAX et MIN sont deux macros courantes :
#define MAX(x, y) ((x) > (y)? (x): (y)) #define MIN(x, y) ((x) < (y)? (x): (y))
Dans une dnition de macro, la premire parenthse de la liste de paramtres doit suivre immdiatement le nom de la macro, sans espace car le prprocesseur, contrairement au compilateur, nest pas indiffrent aux espaces. Sil y a un espace, il effectuera une substitution standard comme nous lavons vu prcdemment. Si vous criviez :
#define MAX (x,y) ( (x) > (y)? (x): (y) )
Il y a donc eu une simple substitution de texte au lieu de lappel de la macro. Le mot-cl MAX a t remplac par (x,y) ((x) > (y)? (x): (y)), suivi du (x,y) qui lui-mme suivait MAX. En supprimant lespace entre MAX et (x,y), le code intermdiaire devient :
int x = 5, y = 7, z; a = ( (5) > (7)? (5): (7) );
744
Le langage C++
puis que vous transmettiez les valeurs 5 et 7, la macro va fonctionner comme prvu. Si vous transmettez au contraire une expression plus complique, vous risquez dobtenir des rsultats inattendus comme illustr dans le Listing 21.2. Listing 21.2 : Utilisation des parenthses dans les macros
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: // Listing21.2 expansion de macro #include <iostream> using namespace std; #define CUBE(a) ( (a) * (a) * (a) ) #define TROIS(a) a * a * a int main() { long x = 5; long y = CUBE(x); long z = TROIS(x); cout << "y : " << y << endl; cout << "z : " << z << endl; long a = 5, b = 7; y = CUBE(a + b); z = TROIS(a + b); cout << "y : " << y << endl; cout << "z : " << z << endl; return 0; }
La macro CUBE est dnie la ligne 4 et le paramtre x est indiqu chaque fois entre parenthses. la ligne 5, la macro TROIS est dnie sans parenthses. Ces macros sont tout dabord utilises aux lignes 10 et 11 en transmettant la valeur 5 comme paramtre et le rsultat obtenu est satisfaisant. CUBE(5) se dveloppe en ( (5) * (5) * (5) ) qui est valu en 125 et TROIS(5) se dveloppe en 5 * 5 * 5 qui est aussi valu en 125.
Chapitre 21
Et maintenant ?
745
Pour la seconde utilisation, aux lignes 16 18, le paramtre transmis est 5+7. Dans ce cas, lvaluation de CUBE(5 + 7) donne le rsultat :
( (5+7) * (5+7) * (5+7) )
qui donne nalement 82. Comme vous pouvez le constater, labsence des parenthses a provoqu une erreur.
Loprateur #
Loprateur # place entre guillemets les caractres qui le suivent jusquau premier espace. Si vous crivez :
#define ECRITCHAINE(x) cout << #x
746
Le langage C++
La concatnation
Loprateur de concatnation permet dobtenir un nouveau mot partir de plusieurs termes. Ce nouveau mot pourra tre utilis pour un nom de classe, un nom de variable, un indice de un tableau et partout o une suite de caractres peut apparatre. Supposons que vous ayez cinq fonctions nommes fUneImpression, fDeuxImpression, fTroisImpression, fQuatreImpression et fCinqImpression. Vous pouvez dclarer :
#define fIMPRESSION(x) f ## x ## Impression
puis utiliser fIMPRESSION(Deux) pour produire fDeuxImpression ou fIMPRESSION(Trois) pour obtenir fTroisImpression. Rappelez-vous que la classe ListePieces ne pouvait recevoir que des objets de type Liste. Supposons que cette liste fonctionne bien et que vous ayez besoin de constituer des listes danimaux, de voitures, dordinateurs, et ainsi de suite. Une approche consisterait crer ListeAnimal, ListeVoiture ListeOrdinateur, etc., et couper puis coller le code. Ceci deviendrait rapidement un cauchemar, car tout changement dans une liste devra tre rpercut dans les autres. Une solution consiste utiliser les macros avec loprateur de concatnation. Vous pourriez dnir par exemple :
#define ListeDe(Type) { \ public: \ Liste##Type(){} \ private: int saLongueur; \ }; \ class Liste##Type \
Cet exemple est exagrment simple, mais lide consiste placer toutes les mthodes et les donnes ncessaires dans la dnition de la macro. Lorsque vous voudrez crer une ListeAnimal, il sufra alors dcrire :
ListeDe(Animal)
Cet appel sera transform en une dclaration de la classe ListeAnimal. Cette solution prsente cependant quelques inconvnients qui ont t voqus lorsque nous avons prsent les modles au Chapitre 19.
Chapitre 21
Et maintenant ?
747
La macro assert()
Beaucoup de compilateurs reconnaissent la macro assert(). Celle-ci renvoie true si ses paramtres sont valus true et ralise une certaine action dans le cas contraire. La plupart des compilateurs interrompent le programme lorsquune macro assert() choue, dautres lancent une exception (voir Chapitre 20). La macro assert() sert au dbogage du programme avant sa sortie. En fait, si DEBUG nest pas dnie, le prprocesseur ne la transformera pas, de sorte quelle ne sera pas incluse dans le source fourni au compilateur. Elle est donc trs utile pendant le dveloppement et noccasionne aucune perte de performance, ni augmentation de la taille de la version excutable du programme. Vous pouvez crire votre propre macro assert(). Le Listing 21.3 prsente une macro assert() personnalise et montre comment lutiliser. Listing 21.3 : Une macro assert() simple
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: // Listing21.3 ASSERTS #define DEBUG #include <iostream> using namespace std; #ifndef DEBUG #define ASSERT(x) #else #define ASSERT(x) \ if (! (x)) \
748
Le langage C++
10: 11: 11a: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: } #endif
{ \ cout << "ERREUR !! Assert " << #x << " a chou" << endl; \ cout << " la ligne " << __LINE__ } << endl; \ \ cout << " dans le fichier " << __FILE__ << endl;
int main() { int x = 5; cout << "Premire assertion : " << endl; ASSERT(x==5); cout << "\nDeuxime assertion: " << endl; ASSERT(x!= 5); cout << "\nFin." << endl; return 0;
DEBUG est dni la ligne 1. En gnral, on le fait partir de la ligne de commande (ou de lIDE) au moment de la compilation, an den contrler facilement le lancement et larrt. La macro ASSERT() est dnie aux lignes 8 14. Gnralement, cette dnition est place dans un chier en-tte (assert.hpp) qui est inclus dans tous les chiers dimplmentation. Le terme DEBUG est test la ligne 5. Sil na pas t dni, ASSERT() ne produit aucun code. Dans le cas contraire, la fonctionnalit des lignes 8 14 est applique. Cette macro est constitue dune seule longue instruction dcoupe en sept lignes de code. La valeur transmise sous la forme dun paramtre est teste la ligne 9. Si le rsultat de son valuation est false, les instructions des lignes 11 13 afchent un message derreur. Si cette valuation donne le rsultat true, aucune action nest entreprise.
Chapitre 21
Et maintenant ?
749
Effets de bord
Il est frquent de voir apparatre une erreur aprs avoir enlev la macro assert(). Cela est presque toujours d aux effets de bord, sur le programme, des actions de assert() ou de tout code rserv la correction des erreurs. Si vous crivez, par exemple :
ASSERT (x = 5)
alors que vous vouliez tester x == 5, vous crerez une erreur particulirement difcile reprer. Supposons que juste avant cette assert(), une fonction ait initialis x zro. Dans cette macro assert(), vous pensiez tester lgalit de x avec 5 mais, en fait, vous lavez initialise 5. Le test renvoie donc la valeur 5 et, comme cette valeur est diffrente de zro, le test est valu comme vrai. Aprs lexcution de linstruction assert(), x est effectivement gale 5 et le programme se droule normalement. Vous dcidez alors de supprimer DEBUG, assert() disparat, x conserve la valeur zro et le programme ne fonctionne plus. Si vous introduisez de nouveau DEBUG, lerreur, bien sr, disparatra. Vous devez donc tre trs prudent avec le code destin la recherche des erreurs, notamment aux effets de bord quil peut provoquer.
750
Le langage C++
6: 7: 8: 9: 10: 11: 12: 12a: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51:
#ifndef DEBUG #define ASSERT(x) #else #define ASSERT(x) \ if (! (x)) \ { \ cout << "ERREUR !! Assert " << #x << " a chou" \ << endl; \ cout << " la ligne " << __LINE__ << endl; \ cout << " dans le fichier " << __FILE__ << endl; \ } #endif
class String { public: // constructeurs String(); String(const char *const); String(const String &); ~String(); char & operator[](int indice); char operator[](int indice) const; String & operator= (const String &); int GetLongueur()const { return saLongueur; } const char * GetString() const { return saString; } BOOL Invariants() const; private: String (int); // constructeur priv char * saString; // unsigned short saLongueur; int saLongueur; }; // Constructeur par dfaut qui cre des chanes de 0 octet String::String() { saString = new char[1];
Chapitre 21
Et maintenant ?
751
52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89: 90: 91: 92: 93: 94: 95: 96: 97: 98: 99:
saString[0] = \0; saLongueur = 0; ASSERT(Invariants()); } // constructeur priv, utilis uniquement par les // mthodes de la classe pour crer une nouvelle chane // de la taille demande remplie par des caractres null. String::String(int longueur) { saString = new char[longueur+1]; for (int i = 0; i <= longueur; i++) saString[i] = \0; saLongueur = longueur; ASSERT(Invariants()); } // Convertit un tableau de caractres en chane String::String(const char * const chaineC) { saLongueur = strlen(chaineC); saString = new char[saLongueur+1]; for (int i = 0; i < saLongueur; i++) saString[i] = chaineC[i]; saString[saLongueur]=\0; ASSERT(Invariants()); } // constructeur de copie String::String (const String & rhs) { saLongueur = rhs.GetLongueur(); saString = new char[saLongueur+1]; for (int i = 0; i < saLongueur;i++) saString[i] = rhs[i]; saString[saLongueur] = \0; ASSERT(Invariants()); } // destructeur, libre la mmoire alloue String::~String () { ASSERT(Invariants()); delete [] saString; saLongueur = 0; } // oprateur daffectation, libre la mmoire puis
752
Le langage C++
100: 101: 102: 103: 104: 105: 106: 107: 108: 109: 110: 111: 112: 113: 114: 115: 116: 117: 118: 119: 120: 121: 122: 123: 124: 125: 126: 127: 128: 129: 130: 131: 132: 133: 134: 135: 136: 137: 138: 139: 140: 141: 142: 143: 144: 145: 146:
// copie la chane et la taille String& String::operator=(const String & rhs) { ASSERT(Invariants()); if (this == &rhs) return *this; delete [] saString; saLongueur = rhs.GetLongueur(); saString = new char[saLongueur+1]; for (int i = 0; i < saLongueur;i++) saString[i] = rhs[i]; saString[saLongueur] = \0; ASSERT(Invariants()); return *this; } //oprateur dindexation non constant char & String::operator[](int indice) { ASSERT(Invariants()); if (indice > saLongueur) { ASSERT(Invariants()); return saString[saLongueur-1]; } else { ASSERT(Invariants()); return saString[indice]; } } // oprateur dindexation constant char String::operator[](int indice) const { ASSERT(Invariants()); char result; if (indice > saLongueur) result = saString[saLongueur-1]; else result = saString[indice]; ASSERT(Invariants()); return result; } BOOL String::Invariants() const {
Chapitre 21
Et maintenant ?
753
147: 148: 149: 150: 150a: 151: 152: 153: 154: 155: 156: 157: 158: 159: 160: 161: 162: 163: 164: 165: 166: 167: 168: 169: 170: 171: 172: 173: 174: 175: 176: 177: 178: 179: 180: 181: 182: 183: 184: 185: 186: 187: 188: 189: 190: 191: 192:
#ifdef SHOW_INVARIANTS cout << "Chane teste OK "; #endif return ( (saLongueur && saString) || (!saLongueur &&!saString) ); } class Animal { public: Animal():sonAge(1) ,sonNom("John Q. Animal") {ASSERT(Invariants());} Animal(int, const String&); ~Animal(){} int GetAge() { ASSERT(Invariants()); return sonAge;} void SetAge(int Age) { ASSERT(Invariants()); sonAge = Age; ASSERT(Invariants()); } String& GetNom() { ASSERT(Invariants()); return sonNom; } void SetNom(const String& nom) { ASSERT(Invariants()); sonNom = nom; ASSERT(Invariants()); } BOOL Invariants(); private: int sonAge; String sonNom; }; Animal::Animal(int age, const String& nom): sonAge(age), sonNom(nom) { ASSERT(Invariants()); } BOOL Animal::Invariants() {
754
Le langage C++
193: 194: 195: 196: 197: 198: 199: 200: 201: 202: 203: 204: 205: 206: 207: 208:
#ifdef SHOW_INVARIANTS cout << "Animal test OK "; #endif return (sonAge > 0 && sonNom.GetLongueur()); } int main() { Animal sparky(5, "Sparky"); cout << endl << sparky.GetNom().GetString() << " a "; cout << sparky.GetAge() << " ans."; sparky.SetAge(8); cout << endl << sparky.GetNom().GetString() << " a "; cout << sparky.GetAge() << " ans."; return 0; }
La macro ASSERT() est dnie en lignes 9 15. Si DEBUG a t dni, un message derreur est afch si la macro est value fausse. La fonction membre Invariants() est dclare la ligne 39 et dnie aux lignes 143 150. Le constructeur est dclar aux lignes 49 55 : Invariants() nest appele quaprs la construction complte de lobjet, ligne 54, pour conrmer que la construction est correcte. Ce schma est rpt dans le cas des autres constructeurs ; le destructeur, au contraire, nappelle Invariants() que lorsquil est sur le point de dtruire lobjet. Les fonctions restantes appellent Invariants() avant toute action et avant de renvoyer leur rsultat. Cela afrme et valide un principe fondamental de C++ : les fonctions membres qui ne sont ni des constructeurs ni des destructeurs doivent travailler avec des objets valides et les laisser dans un tat correct. la ligne 176, la classe Animal dclare sa propre mthode Invariants(), qui est implmente aux lignes 189 195. Vous pouvez noter que les fonctions inline, aux lignes 155, 158, 161 et 163, peuvent appeler la mthode invariants().
Chapitre 21
Et maintenant ?
755
756
Le langage C++
La macro PRINT() des lignes 8 et 9 afche la valeur courante de son paramtre. la ligne 9, vous pouvez remarquer que lon commence par transmettre cout le paramtre sous forme de chane de caractres ( laide de #). Si vous avez pass x, cout afchera donc "x". cout reoit ensuite la chane ":\t" an dafcher deux points et se dplacer dune tabulation, puis la valeur du paramtre (x) et, enn, endl, qui crit un retour la ligne et vide la mmoire tampon. Sur votre machine, la valeur du pointeur px (0012FEDC, ici) sera probablement diffrente.
Chapitre 21
Et maintenant ?
757
758
Le langage C++
Les lignes 3 et 4 dclarent deux fonctions, Carre() et Cube(). Comme elles sont dclares inline, elles seront donc dveloppes en ligne chaque appel, comme les macro. Il ny aura par consquent pas de surcharge systme induite par un appel de fonction. "Dveloppe en ligne" signie que le contenu de la fonction est insr dans le code pour chaque appel de la fonction ( la ligne 17, par exemple). Il ny a donc jamais dappel de fonction, il nest donc pas ncessaire denregistrer ladresse de retour et les paramtres dans la pile. La fonction Carre est appele la ligne 17 et la fonction Cube la ligne 19. Ces deux fonctions tant inline, cela revient exactement avoir crit :
16: 17: 18: 19: cout cout cout cout << << << << ". Carr(" << x << ") : "; x * x; ". Cube(" << x << ") : "; x * x * x << "." << endl;
Faire
crire les noms de macro en CAPITALES.
Ne pas faire
Crer des effets de bord avec vos macros.
Nincrmentez pas de variables et ne leur affectez aucune valeur dans une macro.
Utiliser des valeurs #define alors quune
Chapitre 21
Et maintenant ?
759
Les oprateurs bit bit de C++ permettent de manipuler individuellement les bits dune variable. Comme ils ressemblent beaucoup aux oprateurs logiques, les programmeurs dbutants les confondent souvent, alors que leurs effets sont diffrents. Ces oprateurs sont prsents dans le Tableau 21.1.
Tableau 21.1 : Oprateurs bit bit
Symbole
& | ^ ~
Oprateur
ET OU OU exclusif Complment 1
Oprateur ET
Loprateur ET sexprime sous la forme dune esperluette (&). Il ne faut pas le confondre avec le ET logique qui, lui, est reprsent par les deux signes "&&". Le rsultat est 1 si les deux bits sont gaux 1, mais 0 si au moins lun des deux est gal 0. Cela se traduit par : "Si le premier bit est 1 et que le deuxime est aussi 1, le rsultat est 1 ; sinon le rsultat est 0."
Oprateur OU
Loprateur OU est une barre verticale (|). Il ne faut pas non plus le confondre avec loprateur OU logique qui est reprsent par deux barres verticales (||). Le rsultat est 1 si lun et/ou lautre bit est gal 1. Si aucun bit nest 1, la valeur est 0.
Oprateur OU exclusif
Loprateur OU exclusif est reprsent par un accent circonexe (^). Le rsultat est 1 si les deux bits sont diffrents. Le rsultat est gal 0 si les deux bits sont identiques (les deux sont 1 ou aucun ne lest).
Oprateur de complment 1
Cet oprateur reprsent par un tilde (~) "inverse" chaque bit en mettant 0 un bit 1 ou 1 un bit 0. Si le nombre vaut 10100011, son complment 1 est donc 01011100.
760
Le langage C++
Comme dhabitude, les bits sont compts de droite gauche. Les bits de la valeur 128 sont tous nuls sauf le bit 8 (celui que lon veut activer dans lindicateur), qui est gal 1. On remarque galement que le nombre de dpart 1010011000100110 nest pas modi par lopration OU, sauf son huitime bit, qui est pass 1. Sil avait dj t 1, il le serait rest, ce qui est exactement ce que lon souhaitait obtenir.
Pour comprendre le rsultat, prenez un crayon et un papier. chaque fois que les deux bits sont 1, crivez 1 comme rponse ; si lun des deux est 0, inscrivez 0. Comparez le rsultat loriginal, vous constaterez quils sont identiques, part le bit 8 qui a t forc 0.
Chapitre 21
Et maintenant ?
761
Faire
Utiliser un masque et loprateur OU pour
Ne pas faire
Confondre les diffrents oprateurs de bits. Oublier les bits gauche de ceux que vous
permutez. Un octet vaut huit bits ; il faut connatre le nombre doctets de la variable utilise.
Champs de bits
Dans certains ca, le moindre octet compte et conomiser 6 ou 8 octets dans une classe peut faire toute la diffrence. Si votre classe ou votre structure contient des variables boolennes ou des variables ayant un choix limit de valeurs vous pouvez gagner de la place en utilisant les champs de bits. Parmi les types de donnes standard du C++, cest le type char qui est le plus petit, puisque sa taille nest que dun octet. Vous utiliserez le plus souvent les entiers (int) qui ont le plus souvent une taille de 4 octets sur un processeur 32 bits. En utilisant les champs de bits, vous pouvez stocker huit bits dans un type char, et trente-deux dans un entier de 4 octets. Le fonctionnement des champs de bits est le suivant : ils portent un nom et on y accde comme nimporte quel membre dune classe. Leur type est toujours dclar comme unsigned int. Aprs le nom du champ de bits, vous devez placer un caractre deuxpoints suivi dun nombre. Ce nombre indique au compilateur le nombre de bits qui seront assigns la variable. Si vous crivez 1, le bit contiendra la valeur 0 ou 1. Si vous crivez 2, deux bits pourront reprsenter des nombres : le champ pourra alors reprsenter 0, 1, 2 ou 3, soit un total de
762
Le langage C++
quatre valeurs. Selon le mme principe, un champ de 3 bits pourra reprsenter huit valeurs, et ainsi de suite. Le Listing 21.7 illustre lutilisation des champs de bits. Listing 21.7 : Champs de bits
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: #include <iostream> using namespace std; #include <string.h> enum enum enum enum STATUT { TempsPlein, TempsPartiel }; GRADE { Etudiant, Diplome }; LOGEMENT { ChambreUniversitaire, Exterieur }; REGIME { DemiPension, Pension, WeekEnds, Externe };
class etudiant { public: etudiant(): monStatut(TempsPlein), monGrade(Etudiant), monLogement(ChambreUniversitaire), monRegime(Externe) {} ~etudiant(){} STATUT GetStatut(); void SetStatut(STATUT); unsigned GetRegime() { return monRegime; } private: unsigned unsigned unsigned unsigned };
STATUT etudiant::GetStatut() { if (monStatut) return TempsPlein; else return TempsPartiel; } void etudiant::SetStatut(STATUT statut) { monStatut = statut; }
Chapitre 21
Et maintenant ?
763
42: 43: int main() 44: { 45: etudiant Jean; 46: 47: if (Jean.GetStatut()== TempsPartiel) 48: cout << "Jean est temps partiel" << endl; 49: else 50: cout << "Jean est temps complet" << endl; 51: 52: Jean.SetStatut(TempsPartiel); 53: 54: if (Jean.GetStatut()) 55: cout << "Jean est temps partiel" << endl; 56: else 57: cout << "Jean est temps complet" << endl; 58: 59: cout << "Jean est "; 60: 61: char Regime[80]; 62: switch (Jean.GetRegime()) 63: { 64: case DemiPension : strcpy(Regime, "Demi-pensionnaire"); 64a: break; 65: case Pension: strcpy(Regime, "Pensionnaire"); break; 66: case WeekEnds: strcpy(Regime, "Pensionnaire le week-end"); 66a: break; 67: case Externe: strcpy(Regime, "Externe");break; 68: default: cout << "Il y a eu un problme !" << endl; 69: break; 70: } 71: cout << Regime << endl; 72: return 0; 73: }
Les lignes 4 7 dnissent plusieurs types numrs qui permettent de dcrire les valeurs disponibles pour les champs de bits de la classe etudiant. La classe etudiant est dclare aux lignes 9 28. Cette classe trs simple permet de regrouper toutes les donnes dans 5 bits aux lignes 24 27. Le premier, ligne 24, reprsente le statut de ltudiant, temps complet ou temps partiel. Le deuxime, ligne 25, indique si
764
Le langage C++
celui-ci est diplm ou non. Le troisime, ligne 25, signale si ltudiant dort linternat. Les deux derniers bits reprsentent les quatre possibilits de pension/repas. Les mthodes sont cres de la mme faon que pour toutes les classes. Le fait que lon travaille avec des champs de bits plutt quavec des types entiers ou numrs na aucune rpercussion. Bien que cette opration ne soit pas indispensable, la fonction membre GetStatut(), aux lignes 30 36, analyse le bit boolen et renvoie un type numr. Elle aurait pu aussi galement renvoyer directement la valeur du champ de bit. Dans ce cas, la conversion aurait t ralise par le compilateur. Vous pouvez le vrier en remplaant limplmentation de GetStatut() par le code suivant :
STATUT student::GetStatut() { return monStatut; }
Ces lignes ne devraient modier en rien le fonctionnement du programme. Cest une simple question de clart du code. Notez que le code de la ligne 47 doit contrler le statut puis afcher le message appropri. Si vous aviez crit :
cout <<"Jean est " << Jean.GetStatut() << endl;
Le compilateur ne peut en effet savoir comment convertir la constante numre TempsPartiel en texte clair. Le programme slectionne ensuite la formule de pension choisie la ligne 62 et enregistre le message correspondant dans le tampon, qui est ensuite afch la ligne 71. Ici aussi, vous auriez pu crire autrement linstruction switch :
case case case case 0: 1: 2: 3: strcpy(Regime, strcpy(Regime, strcpy(Regime, strcpy(Regime, "Demi-pensionnaire"); break; "Pensionnaire"); break; "Pensionnaire le week-end"); break; "Externe");break;
Le plus important, dans les champs de bits, est que le client de la classe na pas besoin de se soucier de limplmentation des donnes. Les champs de bits tant privs, vous pouvez les modier tout moment sans toucher linterface.
Chapitre 21
Et maintenant ?
765
Styles de programmation
Nous avons dj mentionn limportance dadopter un style de programmation cohrent, quel quil soit. Un style cohrent faciliter la relecture et la comprhension du code et vite de devoir se rappeler si le nom dune fonction commence par une majuscule ou une minuscule, par exemple. Les conseils de cette section ne sont fournis qu titre indicatif. Ils sont tirs des directives de projet sur lesquels nous avons travaill par le pass. Vous pouvez bien sr crer vos propres rgles et ces informations pourraient alors constituer un point de dpart.
Lindentation
Si vous utilisez lindentation, elle ne devrait tre que de trois espaces. Assurez-vous que votre diteur de texte convertisse chaque tabulation en trois espaces.
Les accolades
Les accolades reprsentent un autre domaine de la programmation assez controvers. Voici quelques suggestions :
Alignez verticalement les accolades correspondantes. Le premier niveau daccolades dans une dnition ou une dclaration doit tre plac sur la marge de gauche. Les instructions quelles encadrent seront alors indentes. Les autres paires daccolades seront alignes avec les instructions quelles suivent. Placez les accolades sur leur propre ligne, comme dans lexemple suivant :
if (condition == true) { j = k; UneFonction(); } m++;
Info
Lalignement des accolades peut tre controvers. De nombreux programmeurs C++ considrent que laccolade ouvrante doit tre place sur la mme ligne que la commande laquelle elle est associe, et que laccolade fermante saligne avec la commande :
if (condition == true) { j = k; UneFonction(); }
Toutefois, certains considrent que ce format est plus difcile lire car les accolades ne sont pas alignes.
766
Le langage C++
Comme vous pouvez le constater, les instructions case sont lgrement indentes et alignes. De mme, les instructions de chaque cas sont alignes. Grce cette disposition, on repre facilement une instruction case et le code est facile suivre.
Le texte du programme
Plusieurs astuces permettent dobtenir un code lisible et den simplier ainsi la maintenance.
Arez le texte pour le rendre plus lisible. Nutilisez pas despaces entre les objets et les tableaux et leurs oprateurs (., ->, []). Nintroduisez pas despace entre les oprateurs unaires et leurs oprandes. Les oprateurs unaires sont ~, ++, --, -, * (pour les pointeurs), & (adressage) et sizeof.
Chapitre 21
Et maintenant ?
767
Insrez un espace de chaque ct des oprateurs binaires : +, =, *, /, %, >>, <<, <, >, ==, !=, &, |, &&, ||, ?:, =, +=, etc. Ntez pas les espaces pour signaler lordre dexcution des oprations (4+ 3*2). Insrez un espace aprs les virgules et les points-virgules, pas avant. Ne mettez pas despaces autour des parenthses. Les mots-cls (comme if) devraient tre mis en valeur laide dun espace : if == b). (a
Les commentaires dune seule ligne ressortent mieux sils sont spars de // par un espace. Accolez lindicateur de pointeur ou de rfrence au nom du type plutt quau nom de la variable :
char* ptr; int& unInt;
plutt que :
char *ptr; int &unInt;
Leur nom doit tre assez long pour tre descriptif. vitez les abrviations qui ne reprsentent pas avec prcision le mot complet. Prenez le temps dexpliquer clairement les choses. Nutilisez pas la notation hongroise. Le contrle des types en C++ est assez efcace et il ny a donc aucune raison pour lindiquer dans le nom de la variable. Cette notation perd dailleurs de sa signication dans le cas des types utilisateur (classes). Vous pouvez faire une exception en employant un prxe pour les pointeurs (p) et les rfrences (r), ainsi que pour les variables membres de classe (son, sa). Nemployez des noms courts (i, p, x, etc.) que dans les cas o leur simplicit amliore la lisibilit du code ou lorsque leur utilisation est assez vidente pour que lon puisse se passer dun nom descriptif. En gnral, il est toutefois prfrable de lviter. vitez aussi dutiliser les lettres i, l et o, car elles se confondent facilement avec des chiffres.
768
Le langage C++
La longueur du nom dune variable doit tre proportionnelle sa porte. Les identicateurs doivent pouvoir tre facilement diffrencis pour viter toute confusion. Les noms de fonctions (ou de mthodes) sont gnralement constitus de verbes ou de groupes verbaux : Recherche(), Initialiser(), TrouverParagraphe(), AfficherCurseur(). Les noms de variables sont plutt constitus de noms abstraits, accompagns ventuellement dun autre substantif : compteur, etat, hauteurFenetre, vitesseVent. Les variables boolennes doivent tre particulirement descriptives : fenetreReduite, fichierOuvert.
Lorthographe et la casse
Lorthographe et lutilisation de la casse ne doivent jamais tre ngliges.
Utilisez uniquement des majuscules et sparez les mots des noms #defined par le caractre soulign (exemple MODELE_FICHIER_SOURCE). Notez cependant que cette notation est rarement utilise en C++. Utilisez plutt des constantes et des modles. Pour tous les autres identicateurs, mlangez les majuscules et les minuscules sans introduire de caractres souligns. Vous pouvez dbuter tous les noms de fonctions, mthodes, classes, typedef et struct par une lettre majuscule. Les lments comme les donnes membres ou les variables locales devront plutt commencer par une minuscule. Vous pouvez prxer les constantes numres par des lettres minuscules reprsentant labrviation de lnumration concerne. Exemple :
enum StyleTexte { stNormal, stGras, stItaliques, stSouligne, };
Commentaires
Lors de la saisie dun programme, vous savez avec prcision ce que vous faites. Mais ouvrez de nouveau le chier source un mois plus tard et certains passages du listing risquent de vous paratre difciles dchiffrer. Pour viter ce dsagrment et permettre aux autres de comprendre vos chiers, insrez des commentaires. Ceux-ci ont pour but dinformer le lecteur sur telle ou telle action du programme. Voici quelques conseils :
Chapitre 21
Et maintenant ?
769
chaque fois que cela est possible, prfrez les commentaires dune seule ligne (//) ceux sur plusieurs lignes (/* */). Rservez plutt ces derniers aux cas o vous devez commenter des blocs de code qui pourraient contenir des commentaires sur une seule ligne. Les commentaires destins dcrire la structure du programme doivent apporter une valeur ajoute et non reprendre les dtails du code. n++; // on incrmente n de un Ce commentaire napporte rien. Dcrivez plutt la smantique ou les objectifs des fonctions et des blocs de code. Signalez les effets de bord, les types des paramtres et les valeurs renvoyes. Dcrivez les hypothses mises, comme "suppose que n nest pas ngatif" ou "renvoie -1 si x est invalide". Lorsque la logique du programme se complique, insrez des commentaires en divers points stratgiques du code.
Faites des phrases compltes. Introduisez la ponctuation et les majuscules de faon approprie. Nutilisez pas dabrviations ou de phrases dont le sens nest pas absolument clair. Ce qui vous semble limpide au moment o vous crivez le code pourrait devenir confus quelques mois plus tard. Insrez des lignes blanches pour aider le lecteur comprendre le droulement du programme. Regroupez logiquement les instructions.
Codez toujours explictement les indicateurs public:, private:, protected:. Nutilisez pas les valeurs par dfaut. Fournissez dabord la liste des membres publics, puis celle des membres protgs et enn celle des membres privs. Les donnes membres devront apparatre dans un groupe aprs les mthodes. Placez le constructeur suivi du destructeur dans une section part. Dans la mesure du possible, regroupez les mthodes surcharges de mme nom et faites de mme pour les mthodes daccs. Classez par ordre alphabtique les mthodes dans chaque groupe et faites de mme pour les variables membres et les noms des chiers inclus. Mme si le mot-cl virtual est facultatif lors de la substitution, ne lomettez pas. Vous obtiendrez ainsi une dclaration plus cohrente et vous noublierez pas quil sagit dun lment virtuel.
770
Le langage C++
Info
assert()
Vous avez dj vu la macro assert(). Utilisez-la librement. Cette macro permet de retrouver les erreurs et reprsente une bonne indication des hypothses sur lesquelles repose le code. Elle permet dindiquer de faon trs claire ce qui est valide ou ce qui ne lest pas.
Chapitre 21
Et maintenant ?
771
Wide Web regorgent dinformations de tous types. Vous pouvez rechercher des groupes de discussion ou des sites web avec le mot-cl C++, le nom de votre systme dexploitation, ou programming pour obtenir une liste exhaustive. La plupart des magazines de programmation possdent maintenant leurs sites web partir desquels vous pouvez tlcharger des articles, dialoguer avec les auteurs, etc. Voici les deux groupes de discussion que nous utilisons rgulirement et que nous vous recommandons : comp.lang.c++ et comp.lang.c++.moderated (leur version francophone est fr.comp.lang.c++). Il existe galement des sites comme http://www.CodeGuru.com et http://www.CodeProject.com o se retrouvent des centaines de milliers de dveloppeurs chaque mois. Ils proposent des articles, des didacticiels, des informations et des discussions sur C++. Il existe galement bien dautres communauts quivalentes. La plate-forme .NET de Microsoft reprsente une rvolution dans notre faon de programmer pour Internet. Le nouveau langage C# est un lment cl de cette plate-forme. C# est une extension naturelle du langage C++ ; il permet aux programmeurs C++ daborder plus facilement la plate-forme .NET (via les extensions gres de C++). C# prsente toutefois quelques diffrences avec C++ : lhritage multiple, par exemple, nest pas autoris en C#, mme si les interfaces proposent des fonctionnalits similaires. En outre, C# vite les pointeurs, ce qui limine certains problmes comme les pointeurs fous, mais qui lempche de crer des programmes de trs bas niveau ou en temps rel. Enn, C# utilise un runtime et un ramasse-miettes qui libre les ressources lorsquelles ne sont plus ncessaires, ce qui pargne au programmeur de devoir le faire. Le C++ gr provient aussi de Microsoft et fait partie de .NET. En termes simples, il sagit dune extension de C++ qui permet dutiliser toutes les fonctions de .NET, y compris le ramasse-miettes.
Garder le contact
Je serais heureux de recevoir vos commentaires ou vos questions propos de ce livre et dy rpondre. La meilleure faon de me joindre est de consulter mon site web : www.libertyassociates.com.
772
Le langage C++
Faire
Consulter dautres livres. Il y a beaucoup
Ne pas faire
Lire simplement du code ! La meilleure faon
apprendre et un seul livre ne peut pas vous apporter toutes les informations que vous devez connatre.
Rejoindre un bon groupe dutilisateurs C++.
Questions-rponses
Q Si C++ offre de meilleures solutions de rechange lutilisation du prprocesseur, pourquoi cette option est-elle toujours disponible ? R La premire raison est que C++ est compatible avec le langage C. Toute partie signicative de C doit donc tre supporte par C++. La seconde est que certaines caractristiques du prprocesseur sont encore frquemment utilises en C++, comme #ifndef et #endif. Q Pourquoi utiliser une macro quand lon peut utiliser une fonction normale ? R Les macros sont dveloppes en ligne et permettent de ne pas devoir rcrire une commande plusieurs fois avec des changements mineurs. Les modles offrent gnralement une meilleure solution. Q Comment savoir sil faut utiliser une macro ou une fonction inline ? R La plupart du temps, cela na pas dimportance. Les macros permettent la substitution de caractres ou la concatnation, qui ne sont pas disponibles avec les fonctions. Q Quelle est lalternative au prprocesseur pour afcher des valeurs intermdiaires, pendant la phase de correction des erreurs ? R La meilleure solution consiste utiliser des instructions watch dans un dbogueur ? Pour plus dinformations sur ces instructions, consultez la documentation de votre dbogueur ou de votre compilateur. Q Comment choisir entre lutilisation de assert() et le lancement dune exception ? R Si la situation teste peut tre vraie mme si le programme ne contient pas derreur, utilisez une exception. Si la seule raison pour que cette situation soit vraie est la prsence dune erreur dans le programme, utilisez assert(). Q Quand doit-on utiliser les structures de bits plutt que de simples entiers ? R Quand la taille de lobjet est capitale. Lorsque vous travaillez avec une mmoire limite ou avec des logiciels de communication, les types denregistrements offerts par ces structures sont essentiels au succs du produit.
Chapitre 21
Et maintenant ?
773
Q Puis-je affecter un pointeur un champ de bit ? R Non. Les adresses mmoire pointent gnralement vers le dbut dun octet. Un champ de bits pourrait se trouver au milieu dun octet. Q Pourquoi les problmes de style font-ils couler autant dencre ? R Les programmeurs sont trs attachs leurs habitudes. Si vous tes habitus lindentation suivante :
if (UneCondition){ // instructions } // accolade de fin
vous aurez du mal changer votre notation. Les nouveaux styles sont peu sduisants et peuvent introduire certaines confusions. Si vous le dsirez, vous pouvez vous connecter sur un service en ligne connu et demander lindentation qui fonctionne le mieux, lditeur qui est le mieux adapt au C++ ou le meilleur diteur de texte. Vous risquez alors de recevoir 10 000 messages, tous en contradiction les uns avec les autres. Q Avons-nous puis ce sujet ? R Il y a encore dix ans, il tait possible de tout connatre des ordinateurs ou, du moins, dtre familier avec tout ce qui les concernait. Cela nest plus possible aujourdhui. La technique volue trop rapidement. Documentez-vous et tenez-vous toujours au courant des dernires modications en consultant rgulirement les magazines et les services en ligne.
Quel sera le rsultat si elle est appele avec 4 ? 5. Quel sera le rsultat si elle est appele avec 10+10 ? 6. Comment pourriez-vous modier la macro MOITIE pour viter dobtenir des rsultats errons ?
774
Le langage C++
7. Combien de valeurs de bits pourraient tre stockes dans une variable de 2 octets ? 8. Combien peut-on stocker de valeurs dans 5 bits ? 9. Quel est le rsultat de 0011 1100 | 1111 1111 ? 10. Quel est le rsultat de 0011 1100 & 1111 1111 ?
Exercices
1. crivez les instructions de contrle des inclusions multiples pour le chier en-tte STRING.H. 2. crivez une macro assert() qui afche un message derreur avec le chier et le numro de ligne si le niveau de debug est 2, qui afche seulement un message si ce niveau est gal 1 et qui ne fait rien si ce niveau est gal 0. 3. crivez la macro DPrint, qui teste si DEBUG est dnie et qui, si cest le cas, afche la valeur transmise en paramtre. 4. crivez la dclaration pour stocker le mois, le jour et lanne dans une mme variable entire non signe.
A
Mots-cls C++
Les mots-cls sont des mots rservs par le compilateur pour le langage de programmation. Vous ne pouvez pas les utiliser comme noms de classes, de variables ou de fonctions.
asm auto bool break case catch char class const const_cast continue default delete do double dynamic_cast else enum explicit export extern false float for friend goto if inline int long mutable namespace new operator private protected public register
776
Le langage C++
reinterpret_cast return short signed sizeof static static_cast struct switch template this throw true
try typedef typeid typename union unsigned using virtual void volatile wchar_t while
B
Priorit des oprateurs
Il est important de savoir que les oprateurs ont une priorit, mais il nest pas essentiel de la connatre par cur. La priorit est lordre dans lequel un programme ralise les oprations dans une formule. Si un oprateur a la priorit sur un autre, il est valu en premier. Les oprateurs dont la priorit est plus leve seront donc valus avant ceux dont la priorit est moins leve. Dans le Tableau B.1, plus le rang est petit, plus la priorit est leve.
Tableau B.1 : Priorit des oprateurs
Rang
1 2
Nom
Rsolution de porte Slection de membre, indication dindice, appels de fonction, incrmentation et dcrmentation sufxe
Oprateur :: . -> () ++ --
778
Le langage C++
Rang
3
Oprateur ++ -^! - + & * () .* ->* * /% + << >> < <= > >= ==!= & ^ | && || ?: = *= /=%= += -= <<= >>= &= |= ^=
4 5 6 7 8 9 10 11 12 13 14 15 16
Slection de membre pour pointeur Multiplication, division, modulo Addition, soustraction Dcalage (gauche ou droit) Ingalit galit, diffrence ET bit bit OU exclusif bit bit OU bit bit ET logique OU logique Conditionnel Oprateurs daffectation
17
Virgule
C
Solutions des exercices
Chapitre 1
Testez vos connaissances
1. Les interprteurs lisent le code source et traduisent directement les instructions du programme en actions, alors que les compilateurs transforment le code source en un programme excutable ultrieurement. 2. Chaque compilateur est diffrent. Consultez la documentation qui laccompagne. 3. La fonction de lditeur de liens consiste lier votre code compil avec les bibliothques ou autres sources fournies avec le compilateur. Lditeur de liens vous permet de construire votre programme par "morceaux", puis de les lier pour obtenir un seul programme plus important. 4. Modication du code source, compilation, dition de liens, test (excution), puis rptition du processus si ncessaire.
780
Le langage C++
Exercices
1. Ce programme initialise deux variables entires (nombres) puis en afche la somme, 12, et le produit, 35. 2. Consultez la documentation de votre compilateur. 3. Vous devez placer un symbole # avant le mot include de la premire ligne. 4. Ce programme afche le mot Bonjour sur la console, suivi dune nouvelle ligne (retour chariot).
Chapitre 2
Testez vos connaissances
1. chaque fois que vous lancez le compilateur, le prprocesseur sexcute dabord. Il lit votre code source et inclut les chiers demands ou toute autre tche qui lui a t signie. Le compilateur sexcute ensuite pour transformer ce code source pr-trait en code objet. 2. La fonction main() est spciale car elle est appele automatiquement chaque fois que votre programme sexcute. Sa prsence est obligatoire dans un programme. 3. Les commentaires C++, sur une seule ligne, commencent avec deux barres obliques (//) et se poursuivent jusqu la n de la ligne. Les commentaires de style C, sur plusieurs lignes, sont dlimits par la paire /* ... */, et tout ce qui se trouve entre ces deux signes (ventuellement sur plusieurs lignes) fait partie du commentaire. 4. Les commentaires de style C++, sur une ligne, peuvent tre imbriqus dans un commentaire de style C, sur plusieurs lignes :
/* Cette marque initie un commentaire. Tout est ignor // y compris ce commentaire sur une ligne, jusqu la marque de fin */
Vous pouvez, en fait, imbriquer un commentaire multi-lignes dans un commentaire de style C++, mais noubliez pas que les commentaires de style C++ se terminent la n de la ligne. 5. Les commentaires de style C peuvent occuper plusieurs lignes. Si vous voulez poursuivre les commentaires C++ dune ligne sur la ligne suivante, vous devez la commencer par deux caractres slash (//).
Annexe C
781
Exercices
1. Voici une rponse possible :
1: 2: 3: 4: 5: 6: 7: #include <iostream.h> int main() { cout << "LeC++, jadore\n"; return 0; }
2. Le programme suivant contient une fonction main() qui ne fait rien. Cest toutefois un programme complet, qui peut tre compil, li et excut. Lexcution ne produira rien :
int main(){}
Ce programme afche :
Y a-t-il un bug dans la salle?
782
Le langage C++
13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37:
int main() { using std::cout; using std::cin; cout << "Je suis dans main()!\n"; int a, b, c; cout << "Entrez deux nombres: "; cin >> a; cin >> b; cout << "\nAppel de Ajouter()\n"; c = Ajouter(a, b); cout << "\nRetour dans main().\n"; cout << "c a t initialis " << c; cout << "\n\nAppel de Soustraire()\n"; c = Soustraire(a, b); cout << "\nRetour dans main().\n"; cout << "c a t initialis " << c; cout << "\nActuel...\n\n"; return 0; }
Chapitre 3
Testez vos connaissances
1. Les variables int sont des nombres entiers. Les variables en virgule ottante sont des "rels" et ont un point dcimal "ottant". Les nombres en virgule ottante peuvent tre reprsents laide dune mantisse et dun exposant. 2. Le mot-cl unsigned signie que lentier ne stocke que des nombres positifs. Sur la plupart des ordinateurs avec processeur 32 bits, les entiers courts occupent 2 octets et les entiers longs 4. La seule garantie, cependant, est quun entier long sera au moins aussi grand ou plus grand quun entier ordinaire, qui est lui-mme au moins aussi grand quun entier court. Gnralement, un entier long est deux fois plus grand quun entier court. 3. Par dnition, le nom dune constante symbolique doit reter son rle dans le programme. Elles peuvent galement tre rednies un seul endroit dans le code source, le programmeur nest donc pas oblig de vrier la totalit du code pour modier chaque occurrence de la variable.
Annexe C
783
4. Les variables const ont un type bien dni, ce qui permet au compilateur de contrler la faon dont elles sont implmentes. En outre, le dbogueur peut les traiter puisquelles existent encore aprs le passage du prprocesseur. Surtout, lutilisation de #define pour dclarer des constantes nest plus prise en charge par la norme C++. 5. Un nom de variable bien choisi indique ce que cette variable reprsente. Un mauvais nom ne contient aucune information. monAge et gensDansLeBus sont des noms corrects, alors que x, xjk et prnd1 ne sont pas explicites. 6. BLEU est associ la valeur 102. 7. Voici les rponses : a. Bon b. Incorrect c. Correct, mais ce nest pas un bon choix d. Bon e. Correct, mais ce nest pas un bon choix
Exercices
1. Voici les types de variables correctes : a. Entier court non sign. b. Entier long non sign ou ottant non sign. c. Double non sign. d. Entier court non sign. 2. Voici quelques exemples de noms corrects pour ces variables : a. monAge b. AireCour c. EtoilesDansGalaxie d. MoyPluieJanv 3. Une dclaration pour PI : const float PI = 3.14159; 4. Voici une dclaration et une initialisation de variable :
float valPi = PI;
784
Le langage C++
Chapitre 4
Testez vos connaissances
1. Une expression est une instruction qui renvoie une valeur. 2. Cest une expression dont la valeur est 12. 3. 50. 4. 1. 5. Age: 41, a: 39, b: 41. 6. 14. 7. if (x = 3) affecte la valeur 3 et renvoie la valeur 3, qui est considre comme vraie. if (x == 3) teste si x est gal 3 ; elle renvoie vrai si la valeur de x est gale 3 et faux dans le cas contraire. 8. a. est valu faux, b. vrai, c. vrai, d. faux et e. vrai.
Exercices
1. Voici une rponse possible :
if (x > y) x = y; else y = x;
// x > y || y == x
2. Voir Exercice 3. 3. Entrer 20, 10, 50 donne pour rsultat : a: 20, b: 30 et c: 10 : La ligne 14 contient une affectation et non un test dgalit. 4. Voir Exercice 5. 5. La ligne 6 attribue la valeur de a-b c, la valeur de laffectation est donc a(2) moins b(2), soit 0. Zro tant valu comme faux, linstruction if choue et rien nest afch.
Chapitre 5
Testez vos connaissances
1. Le prototype de fonction dclare la fonction, la dnition la dnit. Le prototype se termine par un point-virgule, pas la dnition. la diffrence de la dnition, la
Annexe C
785
dclaration peut contenir le mot-cl inline et des valeurs de paramtre par dfaut. Dans une dclaration, il nest pas utile dinclure les noms des paramtres. 2. Non. Les paramtres sont identis par leur position, jamais par leur nom. 3. Vous dclarez la fonction avec un type renvoy void. 4. En labsence de type de rsultat, une fonction renvoie par dfaut une valeur entire. Il est conseill de prendre la bonne habitude de toujours dclarer le type du rsultat. 5. Une variable locale est une variable qui est passe ou dclare dans un bloc, en gnral une fonction. Elle nest accessible que dans ce bloc dinstructions. 6. La porte se rapporte la visibilit et la dure de vie des variables globales et locales. Elle est gnralement dlimite par une accolade ouvrante et une accolade fermante. 7. La rcursivit est la possibilit, pour une fonction, de sappeler elle-mme. 8. Les variables globales sont utilises quand de nombreuses fonctions ont besoin daccder aux mmes donnes. Les variables globales sont trs rares en C++. Lorsque vous saurez crer des variables de classe statiques, vous navez presque plus jamais besoin de crer des variables globales. 9. La surcharge de fonction est la possibilit dcrire plusieurs fonctions portant le mme nom, mais avec un nombre ou des types de paramtres diffrents.
Exercices
1. unsigned long int Perimetre(unsigned short int, unsigned short int); 2. Voici une rponse possible :
unsigned long int Perimetre (unsigned short int longueur, unsigned short int largeur) { return (2*longueur)+(2*largeur); }
3. La fonction tente de renvoyer une valeur, bien quelle soit dclare comme renvoyant void et elle ny parvient donc pas. 4. Cette fonction serait correcte sans le point-virgule la n de la ligne den-tte de la dnition de fonction. 5. Voici une rponse possible :
short int Division(unsigned short int val1, unsigned short int val2)
786
Le langage C++
Annexe C
787
6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28:
ULONG LirePuissance(USHORT n, USHORT puissance); int main() { USHORT nombre, puissance; ULONG reponse; cout << "Entrez un nombre: "; cin >> nombre; cout << " quelle puissance? "; cin >> puissance; reponse = LirePuissance(nombre, puissance); cout << nombre << " la puissance " << puissance << " est gal "<< reponse << endl; return 0; } ULONG LirePuissance(USHORT n, USHORT puissance) { if(puissance == 1) return n; else return(n * LirePuissance(n, puissance-1)); }
Chapitre 6
Testez vos connaissances
1. Loprateur point (.) permet daccder aux membres dune classe ou dune structure. 2. Ce sont les dnitions de variables qui rservent la mmoire. Les dnitions de classes ne rservent pas de mmoire. 3. La dclaration dune classe constitue son interface. Elle indique aux clients de cette classe comment interagir avec elle. Limplmentation de la classe est lensemble des fonctions membres ; elle est place en gnral dans un chier .cpp. 4. Les donnes membres publiques peuvent tre lues par les clients de la classe. On ne peut accder aux donnes membres prives que par lintermdiaire des fonctions membres de la classe. 5. Oui. Les fonctions membres peuvent tre prives, mme si ce nest pas indiqu dans ce chapitre. Seules les autres fonctions membres de la classe pourront utiliser la fonction prive.
788
Le langage C++
6. Oui, mais il est prfrable de les dclarer prives et de fournir des mthodes publiques daccs pour ces donnes. 7. Oui, chaque objet dune classe possde ses propres donnes membres. 8. la diffrence des dnitions de fonctions, les dclarations ont un point-virgule aprs laccolade fermante. 9. Voici quoi ressemble len-tte dune fonction Miaou() de la classe Chat, qui ne reoit aucun paramtre et renvoie void :
void Chat::Miaou()
10. Le constructeur est appel pour initialiser une classe. Cette fonction spciale porte le mme nom que la classe.
Exercices
1. Voici une rponse possible :
class Personnel { int Age; int Anciennete; int Salaire; };
2. Voici une rponse possible. Vous remarquerez que les mthodes daccs Get ont aussi t rendues constantes, car elles ne changeront rien dans la classe.
// Personnel.hpp class Personnel { public: int GetAge() const; void SetAge(int age); int GetAnciennete() const; void SetAnciennete(int annees); int GetSalaire() const; void SetSalaire(int salaire); private: int Age; int Anciennete; int Salaire; };
Annexe C
789
return Age; } void Personnel::SetAge(int age) { Age = age; } int Personnel::GetAnciennete() const { return Anciennete; } void Personnel::SetAnciennete(int annees) { Anciennete = annees; } int Personnel::GetSalaire()const { return Salaire; } void Personnel::SetSalaire(int salaire) { Salaire = salaire; } int main() { using namespace std; Personnel John; Personnel Sally; John.SetAge(30); John.SetAnciennete(5); John.SetSalaire(150000); Sally.SetAge(32); Sally.SetAnciennete(8); Sally.SetSalaire(140000); cout << "Chez Sexiste SA, John et Sally ont
790
Le langage C++
46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59:
cout << "le mme travail.\n\n"; cout cout cout cout cout cout cout cout cout cout } << << << << << << << << << << "John a "<< John.GetAge() << " ans."; << endl; "Son anciennet est de "; John.GetAnciennete() << " ans." << endl; " John gagne " << John.GetSalaire(); " par an.\n\n"; "Sally, elle, a " << Sally.GetAge(); " ans et son anciennet est de "; Sally.GetAnciennete(); " ans. Sally gagne " << Sally.GetSalaire(); " par an. Sexiste SA porte bien son nom!";
Annexe C
791
7. La mthode daccs GetAge() est prive. Noubliez pas que tous les membres dune classe sont privs, sauf si vous leur attribuez le mot-cl public. 8. La variable saStation tant prive, vous ne pouvez pas y accder directement. Vous ne pouvez pas appeler SetStation() sur la classe, seulement sur des objets. Vous ne pouvez pas initialiser monAutreTele parce quil ny a pas de constructeur correspondant.
Chapitre 7
Testez vos connaissances
1. Il suft de sparer les initialisations par des virgules :
for (x=0, y=10; x<100; x++, y++)
2. Il faut viter dutiliser goto parce que cette instruction provoque un saut dans nimporte quelle direction et vers une ligne de code quelconque. Le code source devient difcile comprendre et mettre jour. 3. Oui. Si la condition est fausse aprs linitialisation, le corps de la boucle for ne sera jamais excut. Voici un exemple :
for (int x=100; x<100; x++)
4. La variable x est hors de porte, elle na donc pas de valeur valide. 5. Oui. Toute boucle peut tre imbrique dans une autre. 6. Voici deux exemples, lun avec une boucle for, lautre avec une boucle while :
for(;;) { // cette boucle for ne se termine jamais! } while(true) { // cette boucle while ne se termine jamais! }
7. Votre programme semble tre "bloqu" car il boucle sans n, ce qui vous oblige redmarrer lordinateur ou utiliser des fonctions avances de votre systme dexploitation pour mettre n la tche.
792
Le langage C++
Exercices
1. Voici une rponse possible :
for (int i=0; i<10; i++) { for (int j=0; j<10; j++) cout << "0"; cout << endl; }
5. Le compteur nest jamais incrment, la boucle while ne se terminera jamais. 6. Il y a un point-virgule aprs la boucle, ce qui correspond une boucle vide. Le compteur afchera sa valeur aprs la n de la boucle for. 7. compteur est initialis 100, mais le test contrle sil est infrieur 10. Le rsultat sera donc toujours faux et le corps de la boucle ne sexcutera jamais. Si vous transformez la premire ligne en int compteur=5; la boucle sexcutera tant quelle na pas atteint la plus petite valeur possible de int. Comme int est une valeur signe par dfaut, ce nest probablement pas non plus ce qui tait prvu. 8. Linstruction case 0 a certainement besoin dune instruction break. Sinon, elle devrait inclure un commentaire pour tre certain que cest ce qui tait prvu.
Annexe C
793
Chapitre 8
Testez vos connaissances
1. Loprateur adresse de (&) permet de dterminer ladresse dune variable. 2. Cest loprateur dindirection (*) qui permet daccder la valeur stocke ladresse contenue dans un pointeur. 3. Un pointeur est une variable qui contient ladresse dune autre variable. 4. Ladresse stocke dans le pointeur est ladresse dune autre variable. Loprateur dindirection (*) renvoie la valeur stocke cette adresse qui est elle-mme stocke dans le pointeur. 5. Loprateur dindirection renvoie la valeur stocke ladresse contenue dans un pointeur. Loprateur adresse de (&) renvoie ladresse mmoire de la variable. 6. const int * ptrUn dclare que ptrUn est un pointeur vers une constante entire. Lentier lui-mme ne peut tre modi avec ce pointeur. int * const ptrDeux dclare que ptrDeux est un pointeur constant vers un entier. Une fois initialis, il ne pourra pas tre raffect.
Exercices
1. Effet de ces dclarations : a. int * pUn; : dclaration dun pointeur sur un entier. b. int vDeux; : dclaration dune variable entire. c. int * pTrois = &vDeux; : dclaration dun pointeur sur un entier et initialisation de celui-ci avec ladresse dune autre variable, vDeux. 2. unsigned short *pAge=&votreAge; 3. *pAge=50; 4. Voici une rponse possible :
1: 2: 3: 4: 5: 6: 7: 8: 9: #include <iostream> int main() { int unEntier; int *pEntier = &unEntier; *pEntier = 5; std::cout << "Un Entier: "
794
Le langage C++
5. pInt aurait d tre initialis. Le plus important est que, ntant pas initialis, il pointe sur un emplacement quelconque en mmoire. Si vous stockez un littral (9) cet emplacement, vous risquez de produire un srieux bogue. 6. Le programmeur a certainement voulu attribuer 9 la valeur pointe par pVar, ce qui serait une affectation Variable. Malheureusement, 9 a t attribu pVar car loprateur dadressage indirect (*) a t oubli. Cette opration risque de provoquer une erreur dsastreuse si pVar est utilise pour affecter une valeur, car elle pointe vers ce qui se trouve ladresse 9 et non celle de Variable.
Chapitre 9
Testez vos connaissances
1. Une rfrence est un alias, et un pointeur est une variable qui contient une adresse. Les rfrences ne peuvent tre ni nulles (NULL), ni affectes. 2. Lorsque linformation sur laquelle on pointe doit tre raffecte, ou si le pointeur peut tre nul. 3. Un pointeur nul (0). 4. Cest un raccourci pour dire "une rfrence un objet constant". 5. Passer par rfrence signie ne pas faire de copie locale. Cette opration peut tre ralise en passant une rfrence ou un pointeur. 6. Les trois propositions sont correctes, mais il convient de choisir un style et de le conserver.
Exercices
1. Voici une rponse possible :
1: 2: 3: 4: 5: 6: 7: // Exercice 9.1 #include <iostream> int main() { int varUne = 1; // initialise varUne 1 int& rVar = varUne;
Annexe C
795
int* pVar = &varUne; rVar = 5; // initialise varUne 5 *pVar = 7; // initialise varUne 7 // Les trois std::cout << std::cout << std::cout << return 0; } venir afficheront: "variable: " << varUne << std::endl; "reference: " << rVar << std::endl; "pointeur: " << *pVar << std::endl;
3. Vous ne pouvez pas affecter une valeur un objet constant et vous ne pouvez pas raffecter un pointeur constant. Cela signie que les lignes 6 et 8 posent problme. 4. Voici une rponse possible. Sachez que ce programme est dangereux cause du pointeur perdu :
1: 2: 3: 4: 5: 6: int main() { int * pVar; *pVar = 9; return 0; }
796
Le langage C++
6. Voici une rponse possible. Faites attention aux fuites mmoires dans vos programmes :
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: #include <iostream> int FoncUne(); int main() { int localVar = FoncUne(); std::cout << "La valeur de localVar est: " << localVar; return 0; } int FoncUne() { int * pVar = new int (5); return *pVar; }
8. CreerChat renvoie une rfrence lobjet CHAT cr sur le tas. Puisquil nexiste aucun moyen de librer cet espace mmoire, vous avez produit une fuite mmoire. 9. Voici une rponse possible :
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: #include <iostream> using namespace std; class CHAT { public: CHAT(int age) { sonAge = age; } ~CHAT(){} int GetAge() const { return sonAge;} private: int sonAge; };
Annexe C
797
12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26:
CHAT * CreerChat(int age); int main() { int age = 7; CHAT * Caroline = CreerChat(age); cout << "Caroline a " << Caroline->GetAge() << " ans"; delete Caroline; return 0; } CHAT * CreerChat(int age) { return new CHAT(age); }
Chapitre 10
Testez vos connaissances
1. Les fonctions membres surcharges sont des fonctions dune classe portant le mme nom, mais qui diffrent par le nombre et/ou le type de leurs paramtres. 2. la diffrence dune dclaration, une dnition rserve de la mmoire. Les dclarations sont gnralement des dnitions, lexception des dclarations de classe, des prototypes de fonction et des instructions typedef. 3. Quand la copie temporaire dun objet est cre. Cela se produit chaque fois quun objet est pass par valeur. 4. Le destructeur est appel chaque fois quun objet est dtruit, soit parce quil devient hors de porte, soit parce que vous avez supprim son pointeur avec delete. 5. Loprateur daffectation agit sur un objet existant, alors que le constructeur de copie le duplique. 6. Cach dans chaque fonction membre, le pointeur this pointe sur lobjet lui-mme. 7. Loprateur prxe na pas de paramtre. Loprateur sufxe contient un seul paramtre int, qui signale simplement au compilateur quil sagit dun prxe. 8. Non. Vous ne pouvez surcharger aucun oprateur pour des types intgrs (ou prdnis). 9. On peut le faire, mais cest fortement dconseill. La faon dont vous surchargez les oprateurs doit tre facilement comprhensible pour ceux qui liront votre code. 10. Aucune. Comme les constructeurs et les destructeurs, ils ne renvoient aucune valeur.
798
Le langage C++
Exercices
1. Voici une rponse possible :
class Cercle { public: Cercle(); ~Cercle(); void SetRayon(int); int GetRayon(); private: int sonRayon; };
Annexe C
799
800
Le langage C++
Annexe C
801
34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77:
{ int val = rhs.GetRayon(); sonRayon = new int(val); } Cercle& Cercle::operator=(const Cercle & rhs) { if (this == &rhs) return *this; *sonRayon = rhs.GetRayon(); return *this; } const Cercle& Cercle::operator++() { ++(*sonRayon); return *this; } // Oprateur ++(int) suffixe // extrait puis incrmente const Cercle Cercle::operator++(int) { // dclare le cercle local et linitialise avec la valeur de *this Cercle temp(*this); ++(*sonRayon); return temp; } int Cercle::LireRayon() const { return *sonRayon; } int main() { Cercle CercleUn, CercleDeux(9); CercleUn++; ++CercleDeux; cout << "CercleUn: " <<CercleUn.GetRayon() << endl; cout << "CercleDeux: " <<CercleDeux.GetRayon() << endl; CercleUn = CercleDeux; cout << "CercleUn: " <<CercleUn.GetRayon() << endl; cout << "CercleDeux: " <<CercleDeux.GetRayon() << endl; return 0; }
802
Le langage C++
10. Cet operator+ modie la valeur dun des oprandes plutt que de crer un nouvel objet EntierCourt avec la somme. Voici le code corrig :
EntierCourt EntierCourt::operator+ (const EntierCourt& rhs) { return EntierCourt(saVal+rhs.GetVal()); }
Chapitre 11
Testez vos connaissances
1. La programmation procdurale met laccent sur les fonctions qui sont spares des donnes. La programmation oriente objet associe les donnes et les fonctions, les intgre dans les objets et sappuie sur les interactions entre ces objets. 2. Les phases danalyse et de conception orientes objet sont la conceptualisation (simple phrase dcrivant lobjectif du projet), lanalyse (processus de comprhension des besoins), et la conception (processus de cration du modle de vos classes), permettant de gnrer le code. Elles sont suivies de limplmentation, des tests et du dploiement. 3. Lencapsulation consiste runir en une classe toutes les donnes et les fonctionnalits dune entit. 4. Un domaine est un secteur dactivit pour lequel vous crez un produit. 5. Un acteur est une personne ou un systme externe au systme dvelopp ; il interagit avec le systme que vous dveloppez. 6. Un cas dutilisation est une description de lutilisation du logiciel. Cest la description dune interaction entre un acteur et le systme lui-mme. 7. A est vrai, B ne lest pas.
Exercices
1. Le diagramme suivant propose une rponse possible : 2. Les voitures, les motos, les camions, les vlos, les pitons et les vhicules prioritaires empruntent tous le carrefour. Des feux de circulation sont prsents avec un signal lumineux rserv aux pitons. La surface de la route devrait tre incluse dans cette simulation, car son tat peut inuencer le trac, mais nous laisserons cette question de ct pour cette premire conception.
Annexe C
803
Figure C.01
Ordinateur
1 Clavier
1 Souris
1..2 Moniteur
Lobjet le plus naturel est le carrefour lui-mme. Il pourrait mettre jour une liste des voitures qui attendent pour passer dans chaque direction et une liste des pitons qui attendent pour traverser sur le passage rserv. Il aura besoin de mthodes pour slectionner les vhicules ou les pitons autoriss traverser. Il ny a quun carrefour, vous devez donc chercher comment vous assurer de linstanciation dun seul objet (pensez aux mthodes statiques et laccs protg). Les pitons et les voitures sont les clients de lintersection. Ils ont plusieurs caractristiques communes : ils peuvent apparatre tout moment, leur nombre est alatoire, et ils attendent le signal pour passer (bien que ce soit des endroits diffrents). Vous pouvez donc envisager une classe de base commune pour les pitons et les voitures. Les classes pourraient donc tre les suivantes :
class Entite; // un client de lintersection
// la racine de toutes les voitures, camions, cycles // et vhicules prioritaires. class Vehicule: Entite ...; // la racine de tous les pitons class Pieton: Entite...; class class class class class Voiture: public Vehicule...; Camion: public Vehicule...; Moto: public Vehicule...; Velo: public Vehicule...; Vehicule_Prioritaire : public Vehicule...;
// contient les listes de voitures et de pitons // qui attendent pour passer class Intersection;
804
Le langage C++
Chapitre 12
Testez vos connaissances
1. Une v-table, ou table de fonction virtuelle, est un moyen couramment utilis par les compilateurs pour grer les fonctions virtuelles en C++. Cette table contient la liste des adresses de toutes les fonctions virtuelles et, selon le type de lobjet point au moment de lexcution, elle appelle la fonction approprie. 2. Tout destructeur de classe peut tre dclar virtuel. Lorsque le pointeur est supprim, le type lobjet au moment de lexcution est valu et le destructeur driv adquat est appel. 3. Il sagissait dune question pige : il ny a pas de constructeurs virtuels. 4. Vous pouvez le faire, en crant dans votre classe une mthode virtuelle qui, ellemme, appelle le constructeur de copie. 5. Base::NomFonction(); 6. NomFonction(); 7. Oui, la virtualit est hrite et il est impossible de la contourner. 8. Les membres protected sont accessibles aux fonctions membres des objets drivs.
Exercices
1. virtual void UneFonction(int); 2. Puisque vous prsentez une dclaration de Carre, vous navez pas vous occuper de Forme puisquelle est automatiquement incluse dans Rectangle.
class Carre: public Rectangle {};
Annexe C
805
5. Ce code ne contient peut-tre pas derreur. Fonction attend un objet Forme. Vous lui avez pass un Rectangle "rduit" en Forme. Tant que vous navez pas besoin des parties de Rectangle, tout ira bien. Sinon, vous devrez modier Fonction pour quelle prenne en paramtre un pointeur ou une rfrence sur un objet Forme. 6. Vous ne pouvez pas dclarer virtuel un constructeur de copie.
Chapitre 13
Testez vos connaissances
1. Tableau[0] et Tableau[24]. 2. Il faut ajouter un niveau dindice pour chaque dimension. Tableau[2] [3] [2], par exemple, est un tableau trois dimensions. La premire dimension contient deux lments, la deuxime trois et la troisime deux. 3. Voici linitialisation du tableau prcdent :
Tableau[2][3][2]= { { {1, 2}, {3, 4}, {5, 6} }, { {7, 8}, {9, 10}, {11, 12} } };
4. 10 * 5 * 20 = 1 000. 5. Les tableaux et les listes chanes sont des conteneurs dinformations. Les listes chanes peuvent cependant tre lies ensemble en fonction des besoins. 6. Cette chane contient 16 caractres : le dernier que vous voyez est le quinzime et le caractre nul termine la chane. 7. Le caractre nul.
Exercices
1. Voici une solution possible. Votre tableau pourrait avoir un autre nom, mais il doit tre suivi de [3][3] pour contenir un chiquier de 3 sur 3 :
int Echiquier[3][3];
806
Le langage C++
3. Voici une solution possible. Elle utilise les fonctions strcpy() et strlen() :
#include <iostream> #include <string.h> using namespace std; int main() { char prenom[] = "Alfred"; char initiale[] = "E"; char nom[] = "Numan"; char complet[80]; int offset = 0; strcpy(complet, prenom); offset = strlen(prenom); strcpy(complet + offset, " "); offset += 1; strcpy(complet + offset, initiale); offset += strlen(initiale); strcpy(complet + offset, ". "); offset += 2; strcpy(complet + offset, nom); cout << prenom << "-" << initiale << "-" << nom << endl; cout << "Nom complet: " << complet << endl; return 0; }
4. La taille du tableau est de cinq lments par quatre lments, mais le code en initialise 4 * 5. 5. Vous vouliez crire i < 5, mais vous avez crit i <= 5. Ce code fonctionnera pour les valeurs i == 5 et j == 4, or llment Tableau[5][4] nexiste pas.
Chapitre 14
Testez vos connaissances
1. Un transtypage descendant dclare quun pointeur dune classe de base doit tre considr comme le pointeur dune classe drive. 2. Cela fait rfrence au dplacement des fonctionnalits partages dans une classe de base commune, plus haut dans la hirarchie. Si plusieurs classes partagent une
Annexe C
807
mthode, il est souhaitable de trouver une classe de base commune dans laquelle placer cette mthode. 3. Si ni lune ni lautre des classes nhrite avec le mot-cl virtual, deux Formes sont crs, une pour Rectangle et une autre pour Forme. Si le mot-cl virtual est utilis pour chaque classe, il ny aura quune seule Forme cre. 4. Cheval et Oiseau initialisent leur classe de base, Animal, dans leur constructeur. Pegase le fait galement ; lorsquun Pegase est cr, les initialisations dAnimal pour un Cheval et un Oiseau sont ignores. 5. Voici une rponse possible :
class Vehicule { virtual void Deplacer() = 0; }
6. Aucune ne doit tre rednie, sauf si vous voulez une classe qui ne soit pas abstraite. Dans ce cas, les trois devront tre rednies.
Exercices
1. class Jet: public Fusee, public Avion 2. class Boeing: public Jet 3. Voici une rponse possible :
class Vehicule { virtual void Deplacer() = 0; virtual void Transporter() = 0; }; class Auto: public Vehicule { virtual void Deplacer(); virtual void Transporter(); }; class Bus: public Vehicule { virtual void Deplacer(); virtual void Transporter(); };
808
Le langage C++
{ virtual void Deplacer() = 0; virtual void Transporter() = 0; }; class Auto: public Vehicule { virtual void Deplacer(); }; class Bus: public Vehicule { virtual void Deplacer(); virtual void Transporter(); }; class Formule1: public Auto { virtual void Transporter(); }; class Coupe: public Auto { virtual void Transporter(); };
Chapitre 15
Testez vos connaissances
1. Oui, ce sont des variables membres et leur accs peut tre contrl comme pour toute autre variable. Si elles sont prives, on ne peut y accder quavec des fonctions membres ou, plus couramment, avec des fonctions membres statiques. 2. static int saStatique; 3. static int Fonction(); 4. long (* fonction) (int); 5. long ( Auto::*fonction) (int); 6. long ( Auto::*fonction) (int) leTableau [10];
Exercices
1. Voici une rponse possible :
0: 1: 2: // Ex1501.cpp class maClasse {
Annexe C
809
3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28:
public: maClasse(); ~maClasse(); private: int sonMembre; static int saStatique; }; maClasse::maClasse(): sonMembre(1) { saStatique++; } maClasse::~maClasse() { saStatique--; } int maClasse::saStatique = 0; int main() { // faire quelque chose return 0; }
810
Le langage C++
18: 19: 20: 21: 22: 23: 24: 24a: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52:
saStatique++; } maClasse::~maClasse() { saStatique--; cout << "Dans le destructeur. saStatique: " << saStatique << endl; } void maClasse::AfficherMembre() { cout << "sonMembre: " << sonMembre << endl; } void maClasse::AfficherStatique() { cout << "saStatique: " << saStatique << endl; } int maClasse::saStatique = 0; int main() { maClasse obj1; obj1.AfficherMembre(); obj1.AfficherStatique(); maClasse obj2; obj2.AfficherMembre(); obj2.AfficherStatique(); maClasse obj3; obj3.AfficherMembre(); obj3.AfficherStatique(); return 0; }
Annexe C
811
8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 24a: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53:
void AfficherMembre(); static int GetStatique(); private: int sonMembre; static int saStatique; }; maClasse::maClasse(): sonMembre(1) { saStatique++; } maClasse::~maClasse() { saStatique--; cout << "Dans le destructeur. saStatique: " << saStatique << endl; } void maClasse::AfficherMembre() { cout << "sonMembre: " << sonMembre << endl; } int maClasse::saStatique = 0; int maClasse::GetStatique() { return saStatique; } int main() { maClasse obj1; obj1.AfficherMembre(); cout << "Statique: " << maClasse::GetStatique() << endl; maClasse obj2; obj2.AfficherMembre(); cout << "Statique: " << maClasse::GetStatique() << endl; maClasse obj3; obj3.AfficherMembre(); cout << "Statique: " << maClasse::GetStatique() << endl; return 0; }
812
Le langage C++
Annexe C
813
44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57:
maClasse obj1; (obj1.*PFM) (); cout << "Statique: " << maClasse::GetStatique() << endl; maClasse obj2; (obj2.*PFM)(); cout << "Statique : " << maClasse::GetStatique() << endl; maClasse obj3; (obj3.*PFM)(); cout << "Statique: " << maClasse::GetStatique() << endl; return 0; }
814
Le langage C++
30: 30a: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71:
cout << "Dans le destructeur. saStatique: " << saStatique << endl; } void maClasse::AfficherMembre() { cout << "sonMembre: " << sonMembre << endl; } void maClasse::AfficherSecond() { cout << "sonSecond: " << sonSecond << endl; } void maClasse::AfficherTroisieme() { cout << "sonTroisieme: " << sonTroisieme << endl; } int maClasse::saStatique = 0; int maClasse::GetStatique() { return saStatique; } int main() { void (maClasse::*PFM) (); maClasse obj1; PFM = maClasse::AfficherMembre; (obj1.*PFM) (); PFM = maClasse::AfficherSecond; (obj1.*PFM) (); PFM = maClasse::AfficherTroisieme; (obj1.*PFM) (); cout << "Statique: " << maClasse::GetStatique() << endl; maClasse obj2; PFM=maClasse::AfficherMembre; (obj2.*PFM)(); PFM=maClasse::AfficherSecond; (obj2.*PFM)();
Annexe C
815
72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86:
PFM=maClasse::AfficherTroisieme; (obj2.*PFM)(); cout << "Statique : " << maClasse::GetStatique() << endl; maClasse obj3; PFM=maClasse::AfficherMembre; (obj3.*PFM)(); PFM=maClasse::AfficherSecond; (obj3.*PFM)(); PFM=maClasse::AfficherTroisieme; (obj3.*PFM)(); cout << "Statique: " << maClasse::GetStatique() << endl; return 0; }
Chapitre 16
Testez vos connaissances
1. Une relation est-un est tablie avec un hritage multiple. 2. Une relation a-un est tablie avec lagrgation (la composition) ; une classe possde un membre qui est un objet dun autre type. 3. Lagrgation correspond lide dune classe ayant une donne membre qui est un objet dun autre type. La dlgation illustre le concept quune classe utilise une autre classe pour raliser une tche. 4. La dlgation illustre lide quune classe utilise une autre classe pour raliser une tche. "Implment en terme de" illustre lide de lhritage de limplmentation dune autre classe. 5. Une fonction amie est une fonction qui peut accder aux membres privs et protgs de votre classe. 6. Une classe amie est une classe dont toutes les fonctions membres sont des fonctions amies de votre classe. 7. Non, cette relation nest pas commutative. 8. Non, cette relation ne se transmet pas par hritage. 9. Non, cette relation nest pas associative. 10. Nimporte o dans la dclaration de la classe.
816
Le langage C++
Exercices
1. Voici une dclaration possible pour Animal :
class Animal: { private: String sonNom; };
Annexe C
817
21: 22: 23: 24: 24a: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: }
String& laChaine); // mthodes daccs int GetLongueur()const { return saLongueur; } const char * GetString() const { return saString; } // static int CpteConstructeur; private: String (int); // constructeur priv char * saString; unsigned short saLongueur; }; ostream& operator<<( ostream& leFlux, String& laChaine) { leFlux << laChaine.GetString(); return leFlux; } istream& operator>>( istream& leFlux,String& laChaine) { leFlux >> laChaine.GetString(); return leFlux; } int main() { String laChaine("Bonjour."); cout << laChaine; return 0;
5. Vous ne pouvez pas placer la dclaration friend dans la fonction. Vous devez dclarer la fonction comme une fonction amie dans la classe. 6. Voici le listing corrig :
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: // Cherchez lerreur #include <iostream> using namespace std; class Animal; void SetVal(Animal& , int); class Animal { public:
818
Le langage C++
10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28:
friend void SetVal (Animal&, int); int GetPoids()const { return sonPoids; } int GetAge() const { return sonAge; } private int sonPoids; int sonAge; }; void SetVal (Animal& unAnimal, int lePoids) { unAnimal.sonPoids = lePoids; } int main() { Animal medor; SetVal(medor, 5); return 0; }
7. La fonction SetVal(Animal&, int) a t dclare comme amie, mais pas la fonction surcharge SetVal(Animal&, int, int). 8. Voici le listing corrig :
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: /// Cherchez lerreur #include <iostream> using namespace std; class Animal; void SetVal(Animal& , int); void SetVal(Animal&, int, int); // voici la modification! class Animal { friend void SetVal(Animal& , int); friend void SetVal(Animal&, int, int); private int sonPoids; int sonAge; }; void SetVal(Animal& unAnimal, int lePoids) { unAnimal.sonPoids = lePoids; } void SetVal(Animal& unAnimal, int lePoids, int unAge)
Annexe C
819
23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34:
{ unAnimal.sonPoids = lePoids; unAnimal.sonAge = unAge; } int main() { Animal medor; SetVal(medor, 5); SetVal(medor,7,9); return 0; }
Chapitre 17
Testez vos connaissances
1. Loprateur dinsertion (<<) est un oprateur membre de lobjet ostream. Il permet dcrire vers lunit de sortie. 2. Loprateur dextraction (>>) est un oprateur membre de lobjet istream. Il permet dcrire dans les variables de votre programme. 3. La premire forme de get() na pas de paramtres. Elle renvoie la valeur du caractre trouv ou EOF (n de chier) si lon a atteint la n de chier. La deuxime forme de get() reoit comme paramtre une rfrence de caractre. Ce caractre est rempli avec le caractre suivant du ux dentre. La valeur renvoye est un objet iostream. La troisime forme de get() reoit un tableau, un nombre maximal de caractres rcuprer et un caractre de n. Cette forme de get() remplit le tableau avec un nombre de caractres gal au nombre dlments du tableau moins un, sauf si elle rencontre un caractre de n. Dans ce cas elle crit immdiatement un caractre null en laissant le caractre de n dans le tampon. 4. cin.read() permet de lire des structures de donnes binaires. getline() permet de lire partir du tampon de istream. 5. Largeur ncessaire pour afcher le nombre entier. 6. Une rfrence un objet istream. 7. Nom du chier ouvrir. 8. ios::ate vous positionne en n de chier, mais vous pouvez crire des donnes nimporte o dans le chier.
820
Le langage C++
Exercices
1. Voici une rponse possible :
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: // Ex1701.cpp #include <iostream> int main() { int x; std::cout << "Entrez un nombre: "; std::cin >> x; std::cout << "Vous avez tap: " << x << std::endl; std::cerr << "...destine a cerr!" << std::endl; std::clog << "...destine a clog!" << std::endl; return 0; }
Annexe C
821
822
Le langage C++
4: 5: 6: 7:
{ for (int ctr = argc-1; ctr>0; ctr--) std::cout << argv[ctr] << " "; }
Chapitre 18
Testez vos connaissances
1. Externe::Interne::MaFonction();. 2. Lorsque le listing atteindra ICI, ce sera la version globale de X qui sera utilise, donc 4. 3. Oui, vous pouvez utiliser des noms dnis dans un espace de noms en les qualiant laide de lespace de noms. 4. Dans un espace de noms normal, les noms sont utilisables en dehors de lunit o lespace de noms est dclar. Dans un espace de noms anonyme, leur utilisation est limite lunit o lespace de noms est dclar. 5. Le mot-cl using peut servir pour les directives et les dclarations. Une directive using permet dutiliser tous les noms dun espace de noms comme sils taient normaux. Une dclaration using permet par contre au programme dutiliser un nom dun espace de noms sans le qualier. 6. Les espaces de noms anonymes sont des espaces nayant pas de noms. Ils permettent denvelopper une collection de dclarations pour les protger de conits de noms ventuels. Dans ce cas, les noms ne peuvent pas tre utiliss en dehors de lunit dans laquelle est dclar lespace de noms. 7. Lespace de noms standard std est dni par la bibliothque C++ standard. Elle comprend les dclarations de tous les noms rpertoris dans la bibliothque standard.
Exercices
1. Le chier en-tte standard C++ iostream dclare cout et endl dans lespace de noms std. Ils ne peuvent tre utiliss en dehors de std que sils sont qualis laide de cet espace de noms. 2. Vous pouvez ajouter la ligne suivante entre les lignes 0 et 1 :
using namespace std;
Vous pouvez ajouter les deux lignes suivantes entre les lignes 0 et 1 :
Annexe C
823
Chapitre 19
Testez vos connaissances
1. Les modles sont intgrs au langage C++ et le type des donnes est contrl. Les macros sont implmentes par le processeur et neffectuent aucun contrle de type. 2. Le paramtre dun modle cre une instance du modle pour chaque type. Si vous crez six instances de modle, six classes ou fonctions diffrentes seront cres. Les paramtres dune fonction modient le comportement ou les donnes de la fonction et une seule fonction est cre. 3. Une fonction amie modle gnral cre une fonction pour chaque type de la classe paramtre. La fonction de type spcique cre une instance de type spcique pour chaque instance de la classe paramtre. 4. Oui, en crant une fonction spcialise pour linstance considre. Outre la cration de Array<t>::UneFonction(), crez galement Array<int>::UneFonction() pour modier le comportement des tableaux dentiers. 5. Une pour chaque type dinstance de la classe. 6. La classe doit dnir un constructeur par dfaut ainsi quun oprateur daffectation surcharg. 7. STL signie Standard Template Library (bibliothque de modles standard). Cette bibliothque est importante, car elle contient plusieurs classes modles dj cres et prtes lemploi. Puisquelles font partie du standard C++, nimporte quel compilateur conforme au standard les acceptera.
824
Le langage C++
Exercices
1. Voici une faon dimplmenter ce modle :
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 19a: 20: 21: 22: 23: 24: 25: 26: 27: //Exercice 19.1 template <class type> class Liste { public: Liste():tete(0),fin(0),leNbre(0) virtual -Liste();
{ }
void insere( Type valeur ); void ajoute( Type valeur ); int present( Type valeur ) const; int vide() const { return tete == 0; } int nbre() const { return leNbre; } private: class ListeCellule { public: ListeCellule(Type val, ListeCellule *cell = 0): val(valeur), suivante(cell) {} Type val; ListeCellule *suivante; }; ListeCellule *tete; ListeCellule *fin; int leNbre; };
Annexe C
825
10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36:
} void List::ajoute(int valeur) { ListeCellule *pt = new ListeCellule( valeur ); if ( tete == 0 ) tete = pt; else fin -> suivante = pt; fin = pt; leNbre++; } int List::present( int valeur ) const { if (tete == 0 ) return 0; if (tete->val == valeur || fin->val == valeur ) return 1; ListeCellule *pt = tete->suivante; for (; pt!= fin; pt = pt ->suivante) if ( pt->val == valeur ) return 1; return 0; }
826
Le langage C++
17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54:
{ ListeCellule *pt = new ListeCellule ( valeur, tete ); assert (pt!=0); // cette ligne est ajoute pour grer fin if (tete == 0) fin = pt; tete = pt; leNbre++; } template <class Type> void Liste<Type>::ajoute( Type valeur ) { ListeCellule *pt = new ListeCellule( valeur ); if (tete == 0) tete = pt; else fin->suivante = pt; fin = pt; leNbre++; } template <class Type> int Liste<Type>::present( Type valeur ) const { if (tete == 0) return 0; if (tete->val == valeur || fin->val == valeur) return 1; ListeCellule *pt = tete->suivante; for (; pt!= fin; pt = pt ->suivante) if ( pt -> val == valeur ) return 1; return 0; }
5. Chat nayant pas dni loprateur ==, toutes les oprations qui comparent les valeurs dans la liste de cellules, comme present(), vont provoquer des erreurs de compilation.
Annexe C
827
Pour viter ce genre de situation, nhsitez pas bien commenter avant la dnition du modle, pour indiquer les oprations dnir an que linstanciation puisse tre compile. 6. Voici une rponse possible :
friend int operator==( const Type& lhs, const Type& rhs );
8. Oui, parce que la comparaison des tableaux implique la comparaison de leurs lments. operator!= doit tre dni pour les lments galement. 9. Voici une rponse possible :
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: //Exercice 19.9 // modle permuter : // doit avoir laffectation et le constructeur de copie // dfinis pour le Type. template <class Type> void permuter( Type& lhs, Type& rhs) { Type temp ( lhs ); lhs = rhs; rhs = temp; }
828
Le langage C++
Chapitre 20
Testez vos connaissances
1. Une exception est un objet qui est cr par lappel du mot-cl throw. Elle signale une situation exceptionnelle et est transmise par la pile des appels vers la premire instruction catch qui gre son type. 2. Un bloc try est un ensemble dinstructions qui peuvent produire une exception. 3. Une instruction catch est un traitement qui a une signature du type dexception quelle gre. Elle suit un bloc try et reoit les exceptions dclenches dans ce bloc. 4. Une exception est un objet pouvant contenir toute information dnissable dans une classe cre par lutilisateur. 5. Les objets exception sont crs avec le mot-cl throw. 6. En gnral, on transmet les exceptions par rfrence. Si vous navez pas lintention de modier le contenu de lobjet exception, vous devez passer une rfrence const. 7. Oui, si vous transmettez lexception par rfrence. 8. Les instructions catch sont examines selon leur ordre dapparition dans le code source. Cest la premire instruction catch dont la signature correspond qui sera utilise. En gnral, il vaut mieux commencer avec lexception la plus spcique et nir par la plus gnrale. 9. catch(...) intercepte toutes les exceptions quel quen soit le type. 10. Un point darrt est un emplacement du code o le dbogueur va arrter lexcution.
Exercices
1. Voici une rponse possible :
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: #include <iostream> using namespace std; class OutOfMemory {}; int main() { try { int *monInt = new int; if (monInt == 0) throw OutOfMemory(); }
Annexe C
829
catch (OutOfMemory) { cout << "Impossible dallouer la mmoire!" << endl; } return 0; }
830
Le langage C++
Annexe C
831
41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 76a: 77: 78: 79: 80: 81: 82: 83: 84:
void RangeError::AffErreur() { cout << "Le nombre " cout << GetNombre() << "est incorrect!!" << endl; } void maFonction(); // prototype de fonc. int main() { try { maFonction(); } // Un seul catch ncessaire, on utilise les fonctions // virtuelles pour faire ce quil faut catch (Exception& uneException) { uneException.AffErreur(); } return 0; } void maFonction() { unsigned int *monInt = new unsigned int; long testNombre; if(monInt == 0) throw OutOfMemory(); cout << "Entrez un entier : "; cin >> testNombre; // ce test bizarre pourrait tre remplac par une srie // de tests pour signaler une mauvaise saisie de // lutilisateur. if (testNombre > 3768 || testNombre < 0) throw RangeError(testNombre); *monInt = testNombre cout << "OK, monInt : " << *monInt; delete monInt; }
832
Le langage C++
Annexe C
833
43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89:
cout << "Le nombre " Cout << GetNombre() << "est incorrect!!" << endl; } // prototype de fonc. void maFonction(); unsigned int * FonctionDeux(); void FonctionTrois(unsigned int *) int main() { try { maFonction(); } // Un seul catch est ncessaire, on utilise les fonctions // virtuelles pour faire ce quil faut catch (Exception& uneException) { uneException.AffErreur(); } return 0; } unsigned int * FonctionDeux() { unsigned int *monInt = new unsigned int; if (monInt == 0) throw OutOfMemory(); return monInt; } void maFonction() { unsigned int *monInt = fonctionDeux(); FonctionTrois(monInt); cout << "OK. monInt: " << *monInt; delete monInt; } void FonctionTrois(unsigned int *ptr) { long testNombre; cout << "Entrez un int: "; cin >> testNombre; // ce test bizarre pourrait tre remplac par une srie // de tests pour signaler une mauvaise saisie de
834
Le langage C++
// lutilisateur. if (testNombre > 3768 || testNombre < 0) throw RangeError(testNombre); *ptr = testNombre; }
5. Dans le processus de gestion dun manque de mmoire, le constructeur de xOutOfMemory cre un objet chane. Cette exception ne peut tre lance que lorsque le programme manque de mmoire et cette lallocation va donc chouer. Il est possible que la tentative de cration de cette chane dclenche la mme exception, ce qui va crer une boucle innie jusqu ce que le programme sarrte. Si cette chane est rellement ncessaire, vous pouvez allouer lespace dans un tampon statique avant de commencer le programme, puis lutiliser si ncessaire au moment de lenvoi de lexception. Vous pouvez tester ce programme en remplaant la ligne if (var==0) par if (1), qui force le dclenchement de lexception.
Chapitre 21
Testez vos connaissances
1. #ifndef permet dviter linclusion multiple dun chier en-tte dans un programme. 2. La rponse cette question dpend du compilateur que vous utilisez. 3. #define debug 0 dnit le terme debug comme tant la valeur 0 (zro). Pour chaque mot debug trouv, le caractre 0 sera donc substitu. #undef debug supprime toutes les dnitions de debug. Tous les mots debug du chier ne seront pas substitus. 4. La rponse est 4 / 2, cest--dire 2. 5. Le rsultat est 10 + 10 / 2, cest--dire 10 + 5, soit 15. Ce nest videmment pas le rsultat souhait. 6. Il faut ajouter les parenthses :
MOITIE(x) ((x)/2)
7. Deux octets font 16 bits, on peut donc stocker jusqu 16 bits. 8. Cinq bits peuvent contenir 32 valeurs (de 0 31). 9. Le rsultat est 1111 1111. 10. Le rsultat est 0011 1100.
Annexe C
835
Exercices
1. Voici la rponse :
#ifndef STRING_H #define STRING_H ... #endif
" << #x << " a chou\n"; __LINE__ << endl; \ __FILE__ << endl; \
D
tude des listes chanes
Le Chapitre 13 vous a fait dcouvrir les tableaux et les listes chanes. Une liste chane est une structure de donnes constitue de petits conteneurs conus pour tre lis ensemble, en fonction des besoins. Le principe consiste crire une classe contenant un seul objet de donnes et qui peut pointer sur le prochain conteneur du mme type. On cre ensuite un conteneur pour chaque objet stocker et on les chane ensemble, en fonction des besoins. Les conteneurs sont appels nuds. Le premier de la liste sappelle la tte, et le dernier la queue. Il existe trois types de listes de base qui sont, de la plus simple la plus complexe :
les listes simplement chanes ; les listes doublement chanes ; les arborescences.
Dans une liste simplement chane, chaque nud pointe vers le suivant, jamais vers larrire. Pour retrouver un nud, on commence donc au dbut et lon passe de nud en nud. Une liste doublement chane permet davancer ou de reculer dans la chane.
838
Le langage C++
Quant larborescence, il sagit dune structure complexe construite partir de nuds, chacun pointant dans deux directions ou plus. La Figure D.1 prsente ces trois structures.
Figure D.1 Des listes chanes.
Liste chane simple Liste chane double NULL NULL Donnes Donnes Donnes Donnes NULL
Donnes
Donnes
Donnes
Donnes
Arborescences Donnes
Donnes
Donnes
Donnes
Donnes
Donnes
Donnes
NULL
NULL
Donnes
NULL NULL
Dans cette annexe, nous tudierons en dtail une liste chane pour comprendre comment crer des structures complexes et, ce qui est plus important, comment les utiliser.
Annexe D
839
Le programme utilisateur ne sait rien des nuds : il manipule une liste qui, elle, fait peu de choses puisquelle se contente de dlguer le travail aux nuds. Le Listing D.1 prsente un code que nous tudierons en dtail dans le reste de cette annexe. Listing D.1 : Une liste chane
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: // // // // // // // // // // // // // // // // // // *********************************************** FICHIER: Listing D.1 OBJET: Prsenter une liste chane NOTES: COPYRIGHT: Copyright (C) 2000-04 Liberty Associates, Inc. Tous droits rservs Prsente une approche oriente objet des listes chanes. La liste dlgue au noeud. Le noeud est un type de donnes abstrait. Il existe trois types de noeuds: de tte, de queue et internes. Seuls les noeuds internes contiennent des donnes. La classe Donnees sert contenir les objets de la liste chane. ***********************************************
#include <iostream> using namespace std; enum { kPlusPetit, kPlusGrand, kIdentique}; // Classe Donnees pour insrer dans la liste chane // Toute classe de cette liste doit accepter deux mthodes: // Affiche (affiche la valeur) et // Compare (renvoie la position relative) class Donnees { public: Donnees(int val): maValeur(val){} ~Donnees(){} int Compare(const Donnees &); void Affiche() { cout << maValeur << endl; } private: int maValeur; };
840
Le langage C++
40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 82a: 83: 84: 85:
// Compare permet de dcider de lendroit dans la liste // o placer un objet particulier. int Donnees::Compare(const Donnees & AutresDonnees) { if (maValeur < AutresDonnees.maValeur) return kPlusPetit; if (maValeur > AutresDonnees.maValeur) return kPlusGrand; else return kIdentique; } // Dclarations anticipes class Noeud; class NoeudTete; class NoeudQueue; class NoeudInterne; // TDA reprsentant lobjet noeud dans la liste // Chaque classe drive doit surcharger Insere et Affiche class Noeud { public: Noeud(){} virtual ~Noeud(){} virtual Noeud * Insere(Donnees * lesDonnees)=0; virtual void Affiche() = 0; private: }; // Voici le noeud contenant lobjet rel // Ici, lobjet est de type Donnees // Nous verrons comment le rendre plus gnral // lorsque nous traiterons des modles class NoeudInterne: public Noeud { public: NoeudInterne(Donnees * lesDonnees, Noeud * suivant); ~NoeudInterne(){ delete leSuivant; delete mesDonnees; } virtual Noeud * Insere(Donnees * lesDonnees); // Dlguer! virtual void Affiche() { mesDonnees->Affiche(); leSuivant->Affiche(); } private: Donnees * mesDonnees;
// Les donnes
Annexe D
841
86: 87: 88: 89: 90: 91: 92: 93: 94: 95: 96: 97: 98: 99: 100: 101: 102: 103: 104: 105: 106: 107: 108: 109: 110: 111: 112: 112a: 113: 114: 115: 116: 117: 118: 119: 120: 121: 122: 123: 124: 125: 126: 127: 128: 129: 130: 131:
Noeud * leSuivant; };
// Le constructeur se contente dinitialiser NoeudInterne::NoeudInterne(Donnees * lesDonnees, Noeud * suivant): mesDonnees(lesDonnees),leSuivant(suivant) { } // Lessentiel de la liste // Lorsque vous placez un nouvel objet dans la liste // il est pass au noeud, qui dcide // o il va et linsre dans la liste Noeud * NoeudInterne::Insere(Donnees * lesDonnees) { // Le nouveau est-il plus grand ou plus petit que moi? int resultat = mesDonnees->Compare(*lesDonnees);
switch(resultat) { // Par convention, sil est pareil que moi, il passe en 1er case kIdentique: case kPlusGrand: // Les nouvelles donnes viennent avant moi { NoeudInterne * donneesNoeud = new NoeudInterne(lesDonnees, this); return donneesNoeud; } // Il est plus grand que moi, le passer au noeud // suivant et le laisser grer. case kPlusPetit: leSuivant = leSuivant->Insere(lesDonnees); return this; } return this; }
// Le noeud de queue nest quune sentinelle class NoeudQueue: public Noeud { public: NoeudQueue(){}
842
Le langage C++
132: 133: 134: 135: 136: 137: 138: 139: 140: 141: 142: 143: 144: 144a: 145: 146: 147: 148: 149: 150: 151: 152: 153: 154: 155: 156: 157: 158: 159: 160: 161: 162: 163: 164: 165: 166: 167: 168: 169: 170: 171: 172: 173: 174: 175: 176: 177:
~NoeudQueue(){} virtual Noeud * Insere(Donnees * lesDonnees); virtual void Affiche() { } private: }; // Si les donnes viennent moi, elles doivent tre insres // avant moi car je suis la queue et RIEN ne vient aprs Noeud * NoeudQueue::Insere(Donnees * lesDonnees) { NoeudInterne * donneesNoeud = new NoeudInterne(lesDonnees, this); return donneesNoeud; } // Le noeud de tte na pas de donnes, il se contente de pointer // vers le dbut de la liste class NoeudTete: public Noeud { public: NoeudTete(); ~NoeudTete() { delete leSuivant; } virtual Noeud * Insere(Donnees * lesDonnees); virtual void Affiche() { leSuivant->Affiche(); } private: Noeud * leSuivant; }; // Ds sa cration, la tte // cre la queue NoeudTete::NoeudTete() { leSuivant = new NoeudQueue; } // Rien ne vient avant la tte, // donc passer les donnes au noeud suivant Noeud * NoeudTete::Insere(Donnees * lesDonnees) { leSuivant = leSuivant->Insere(lesDonnees); return this; } // Jai tout le mrite et ne fais rien class ListeChainee
Annexe D
843
178: 179: 180: 181: 182: 183: 184: 185: 186: 187: 188: 189: 190: 191: 192: 193: 194: 195: 196: 197: 198: 199: 200: 201: 202: 203: 204: 205: 206: 207: 208: 209: 210: 211: 212: 213: 214: 215: 216: 217: 218: 219: 220: 221: 222: 223: 224: 225:
{ public: ListeChainee(); ~ListeChainee() { delete maTete; } void Insere(Donnees * lesDonnees); void AfficheTout() { maTete->Affiche(); } private: NoeudTete * maTete; }; // la naissance, je cre le noeud de tte // Celui-ci cre le noeud de queue // Une liste vide pointe donc vers la tte qui // pointe vers la queue et ne contient rien ListeChainee::ListeChainee() { maTete = new NoeudTete; } // Dlguer, dlguer, dlguer void ListeChainee::Insere(Donnees * pDonnees) { maTete->Insere(pDonnees); } // Programme de test int main() { Donnees * pDonnees; int val; ListeChainee ll; // Demander lutilisateur de produire des valeurs // les placer dans la liste for (;;) { cout << "Insrer une valeur (0 pour arrter): "; cin >> val; if (val == 0) break; pDonnees = new Donnees(val); ll.Insere(pDonnees); } // Parcourir la liste et afficher les donnes ll.AfficheTout(); return 0; // ll est hors de porte, donc dtruit! }
844
Le langage C++
La premire chose remarquer concerne la constante numre dnie la ligne 24, qui fournit trois valeurs constantes : kPlusPetit, kPlusGrand et kIdentique. Chaque objet qui pourrait tre contenu dans cette liste chane doit fournir une mthode Compare(). Ces constantes seront le rsultat renvoye par cette mthode Compare(). Pour les besoins de lexemple, la classe Donnees est cre aux lignes 30 39. La mthode Compare() est implmente aux lignes 43 51. Un objet Donnees contient une valeur et peut se comparer dautres objets Donnees. Il fournit galement une mthode Affiche() qui afche la valeur de lobjet Donnees. Pour comprendre le fonctionnement de la liste chane, le plus simple est dtudier un exemple pas pas. La ligne 203 commence le programme de test ; la ligne 206 dclare un pointeur vers un objet Donnees. Enn, la ligne 208 dnit une liste chane locale. tudiez la classe ListeChainee aux lignes 177 186. Lorsquelle est cre, le constructeur de la ligne 192 est appel. La seule tche effectue dans le constructeur consiste allouer un objet NoeudTete et affecter ladresse de cet objet au pointeur de tte de la liste chane, qui est dclar la ligne 185. Cette affectation appelle le constructeur NoeudTete implment aux lignes 163 166. son tour, celui-ci alloue un NoeudQueue et affecte son adresse au pointeur leSuivant du nud de tte. La cration du nud de queue appelle le constructeur NoeudQueue prsent la ligne 131, qui est dclar inline et qui ne fait rien. Ainsi, le simple fait dallouer une liste chane sur la pile cre cette liste, un nud de tte et un nud de queue, ainsi que les liaisons entre eux, comme le montre la Figure D.2. La ligne 212 du programme principal commence une boucle sans n. Lutilisateur doit alors entrer des valeurs qui seront ajoutes la liste chane. Il peut ajouter autant de valeurs quil le souhaite, puis 0 lorsquil a termin. Le code de la ligne 216 value la valeur saisie : si elle est gale 0, le programme interrompt la boucle.
Annexe D
845
laTete
leSuivant
Dans le cas contraire, la ligne 218 cre un objet Donnees, qui est insr dans la liste la ligne 219. Si lutilisateur a saisi par exemple la valeur 15, la mthode Insere est appele la ligne 198. La liste chane dlgue immdiatement la responsabilit de linsertion de lobjet son nud de tte, ce qui provoque lappel de la mthode Insere de la ligne 170. Le nud de tte passe immdiatement la responsabilit au nud vers lequel pointe leSuivant. Dans ce (premier) cas, il pointe vers le nud de queue (noubliez pas que, lors de sa cration, le nud de tte a cr un lien vers le nud de queue). Cela appelle donc la mthode Insere de la ligne 142. NoeudQueue::Insere sait que lobjet quil gre doit tre insr immdiatement avant lui, cest--dire que le nouvel objet se trouvera dans la liste juste avant le nud de queue. Ainsi, la ligne 144, il cre un nouvel objet NoeudInterne, en lui passant les donnes et un pointeur vers lui-mme, ce qui provoque lappel du constructeur de lobjet NoeudInterne de la ligne 90. Le constructeur NoeudInterne ne fait rien dautre que dinitialiser son pointeur Donnees avec ladresse de lobjet Donnees quil a reu, puis son pointeur leSuivant avec ladresse du nud quil a reu. Dans ce cas, le nud vers lequel il pointe est le nud de queue (noubliez pas que le nud de queue lui a pass son propre pointeur this). Maintenant que NoeudInterne a t cr, son adresse est affecte au pointeur donneesNoeud ligne 144, puis renvoye partir de la mthode NoeudQueue::Insere(). Cela nous renvoie NoeudTete::Insere(), o ladresse du NoeudInterne est affecte au pointeur leSuivant de NoeudTete (ligne 172). Enn, ladresse de NoeudTete est renvoye la liste chane o, ligne 200, elle est supprime (on nen fait rien, car la liste chane connat dj ladresse du nud de tte). Pourquoi sinquiter de renvoyer ladresse si elle nest pas utilise ? Insere est dclare dans la classe de base, Noeud. Or, la valeur renvoye est ncessaire aux autres implmentations. Si vous modiez la valeur de retour de NoeudTete::Insere(), vous recevrez une erreur du compilateur ; il est donc plus simple de renvoyer le NoeudTete et de laisser la liste chane se dbarrasser de son adresse.
846
Le langage C++
Que sest-il donc pass ? Les donnes ont t insres dans la liste. La liste les a passes sa tte. La tte les a passes llment vers lequel elle pointe. Dans ce (premier) cas, la tte pointait vers la queue. La queue a immdiatement cr un nouveau nud interne, initialisant ainsi le nouveau nud pour quil pointe vers la queue. La queue a renvoy ladresse du nouveau nud la tte, qui a raffect son pointeur leSuivant de sorte quil pointe vers le nouveau nud. Et voil ! les donnes se trouvent dans la liste, au bon endroit, comme lindique la Figure D.3.
Figure D.3 La liste chane aprs insertion du premier nud.
Liste chane Nud de tte Nud de queue
laTete
leSuivant
Nud interne
mesDonnees leSuivant
Donnes
Aprs linsertion du premier nud, le contrle du programme reprend la ligne 214. Une fois de plus, on value la valeur qui a t saisie. Pour notre exemple, supposons que nous entrions la valeur 3. Un nouvel objet Donnees est alors cr la ligne 218, pour tre insr dans la liste la ligne 219. nouveau, la ligne 200 la liste passe les donnes son NoeudTete. La mthode NoeudTete::Insere() passe son tour la nouvelle valeur llment vers lequel pointe leSuivant. Comme nous venons de le voir, celui-ci pointe maintenant vers le nud qui contient lobjet Donnees valant 15. Cela provoque lappel de la mthode NoeudInterne::Insere() de la ligne 99. la ligne 103, le NoeudInterne utilise son pointeur lesDonnees pour indiquer son objet Donnees (qui vaut 15) dappeler sa mthode Compare() en passant le nouvel objet Donnees (qui vaut 3), ce qui provoque lappel de la mthode Compare() de la ligne 43. Les deux valeurs sont compares. Comme maValeur vaudra 15 et que AutresDonnees.maValeur vaudra 3, la valeur renvoye sera kPlusGrand. Le ux du programme passe donc au cas kPlusGrand ligne 110. Un nouveau NoeudInterne est cr pour le nouvel objet Donnees. Le nouveau nud pointe vers lobjet NoeudInterne courant et ladresse du nouveau NoeudInterne est renvoye partir de la mthode NoeudInterne::Insere() vers le NoeudTete.
Annexe D
847
Le nouveau nud, dont la valeur dobjet est infrieure la valeur de lobjet du nud courant, est donc insr dans la liste qui ressemble maintenant la Figure D.4.
Figure D.4 La liste chane aprs insertion du second nud.
Liste chane Nud de tte Nud de queue
laTete
leSuivant
Dans le troisime appel de la boucle, le client ajoute la valeur 8. Celle-ci est suprieure 3, mais infrieure 15 et doit donc tre insre entre les deux nuds existants. La progression est donc identique lexemple prcdent, sauf que lorsque le nud dont la valeur dobjet est 3 effectue la comparaison, il renvoie kPlusPetit au lieu de kPlusGrand ce qui signie que lobjet valant 3 est plus petit que le nouvel objet valant 8. La mthode NoeudInterne::Insere() va donc effectuer un branchement sur le cas kPlusPetit de la ligne 118. Au lieu de crer un nouveau nud et de linsrer, le NoeudInterne passe simplement les nouvelles donnes la mthode Insere de llment vers lequel pointe leSuivant. Dans ce cas, il appelle donc InsereNoeud sur le NoeudInterne dont la valeur de lobjet Donnees est 15. La comparaison est nouveau ralise et un nouvel objet NoeudInterne est cr. Ce dernier pointe vers le NoeudInterne dont lobjet Donnees vaut 15 et son adresse est repasse au NoeudInterne dont lobjet Donnees vaut 3, comme le montre la ligne 119. De ce fait, le nouveau nud est insr dans la liste, au bon endroit. Si possible, essayez de suivre linsertion de plusieurs nuds partir de votre dbogueur. Vous devriez pouvoir tudier lappel de ces mthodes entre elles et lajustement correct des pointeurs.
Quavez-vous appris ? Dans un programme orient objet correctement conu, personne nest responsable. Chaque objet fait un petit travail, avec pour rsultat une machine bien huile. La liste chane a pour seule tche de maintenir le nud de tte, qui passe immdiatement les nouvelles donnes llment vers lequel il pointe, sans soccuper de qui il est.
848
Le langage C++
Le nud de queue cre un nouveau nud et linsre ds quil reoit des donnes. Il ne sait quune seule chose : si un lment vient lui, celui-ci sera insr juste avant lui. Les nuds internes sont un peu plus complexes ; ils demandent leur objet existant de se comparer avec le nouvel objet. Selon le rsultat, ils linsrent ou le font passer. Vous remarquerez que le nud interne (NoeudInterne dans ce listing) ne sait pas comment effectuer la comparaison ; cest de la responsabilit de lobjet lui-mme. Tout ce quil sait faire est de demander aux objets de se comparer et dattendre lune des trois rponses possibles. Sil reoit une rponse, il insre llment, sinon il le transmet sans savoir ni se soucier de lendroit o il atterrira.
Index
Symboles
# 738, 745 #include (instruction) 24 // 17, 26, 580 >> 580, 589 & 210, 244, 759 && 759 * 214 ++ 53 = 48 || 759
A
Accs 769 agrgs 523 dune classe 141 donnes et fonctions membres 228 membre de classe 228 Accolades 25, 765 dans classe 139
dans dfinition de fonction 161 dans numration 59 dans fonction 34, 102 Acteur 328 Activation de bits 760 Adressage indirect 213 oprateur 214 Adresse mmoire variable 210 mmoire Voir Oprateurs pointeur 215 stockage dans un pointeur 212 Affectation 66 de valeurs une variable 141 oprateur d Voir Oprateurs Affichage dune chane 566 Agrgation 523 cot 524 prsentation 515
Algorithmes 693 Voir aussi Conteneurs Alias 50, 244 despace de noms 634 Alignement de caractres en sortie 28 Amies classes Voir Classes fonctions Voir Fonctions Analyse 13, 338 des besoins 327 documents d 339 et conception oriente objet 322 Anonyme, variable temporaire 299 ANSI 12 Appel de fonction 98, 128 argc (argument) 613, 614 Arguments de fonction 111 par valeur 107
850
Le langage C++
argv (argument) 613 ASCII 14, 43, 54 Associatifs, conteneurs Voir Conteneurs Association (POO) 334 Astrisque (oprateur) 214 Avertissements du compilateur 21
B
Bibliothque 15 dfinition 576 iostream 575 modles standard 641, 676 standard 18, 25, 28 Voir aussi std Bits, champs 761 Blocs catch 703, 706 multiples 713 try 706, 712 bool 44 Boolen 77 Boucles 173 complexes 177 for 186 instructions nulles 189 sans fin 201 while (true) 181 break, syntaxe 181
C
C# 11 C++ et langage C 11 et programmation oriente objet 9 volution 11
historique 5 liste des mots-cls 775 notions 5 Caractres alignement 28 ASCII Voir ASCII blanc 66 casse 46, 768 chane de 26, 582, 745 cout 603 dchappement 56 de formatage 26, 28 fin de ligne (EOL) Voir EOL spciaux 56 Cas dutilisation 327 paquetages 338 reprsentation 336 catch (bloc) 704 cerr 579 Chanage ascendant 457 Chane de caractres 26 type C 421 char 44, 54 cin 25, 36, 579 cin, valeur 585 cin.ignore() (fonction) 592 class (mot-cl) 139, 644 Classes accs 362 conteneurs 677 dalgorithmes 692 de conception 341 dclaration 139 drives 349, 358, 549 encapsulation 138 exception 719 fstream 579 interface 156
ios 579 iostream 579 istream 579 membres de 138 modles 652 streambuf 578 string 425 surcharge de constructeurs 367 tableaux 434 Cls 691 Client 156, 160 clog 579 Clonage dobjet 388 Code accolade Voir Accolades assembleur 733 commentaires 30, 768 compilation 6 espace de 129 fractionnement 42, 766 lisibilit 766 source Voir Fichiers sources Commentaires dans le code 30 Compilation 6 du code 6 erreurs 15, 20, 143, 147 messages davertissement 21 prprocesseur 24, 738 Composition 333 ou hritage multiple 348 Concatnation 746 Conception 341 application 13 Conflit de noms, rsoudre 619 const 770 fonctions membres 237 pointeur 237
Index
851
const this (pointeur) 240 Constantes 57 dfinition avec #define 58 dfinition avec const 59 numres 59 littrales 57 symboliques 58 Constructeurs 365 ajout 151 objets de lhritage multiple 449 par dfaut 152, 159, 285 privs 432, 562 surcharge 285 virtuels 388 Construction dun projet 19 de donnes membres 229 Contacter lauteur 771 Conteneurs 676 associatifs 687 avec modification 694 deque 687 list 684 map 688 multimap 691 multiset 692 queue 687 sans modification 693 squentiels 677 set 691 stack 686 vector 677 continue, syntaxe 181 Conventions dattribution de nom 139 du livre 2 Conversion de type Voir Transtypage implicite et explicite 316
oprateurs de 316 printf() (spcificateurs) 601 Copie constructeur de 288 de surface 289 en profondeur 289 locale 261 par valeur 527 Corps boucle 176, 183, 791 constructeur 287 espace de noms 620 fonction 34 cout 25, 29, 36, 579 prsentation 26 utilisation des oprateurs 126 CPU Voir UC Cration dun espace de noms 625 dune variable 44 de nouveaux types 137 de plusieurs variables 48
D
Dbogueur mode DEBUG 755 Voir aussi Point de rupture DEBUG 748 Dclaration dhritage multiple 448 de classes 139, 159 de fonctions 99 de membres de classe 142 de pointeurs 220 de tableaux 402, 404, 414 et dfinition, diffrence 168 exemple 162
using 632 virtuelles 461 default, instruction (switch) 198 Dfinition dobjet 140 de classe 770 de fonction 101 de modle 643 de type 50 des indicateurs 598 Dlgation 531 de proprits 532 Dpassement, valeur limite des entiers 52 Drfrencement 214 Drivation objet 363 TAD 473 Dsactivation de bits 760 Destructeurs ajout 151 dobjets 226 par dfaut 152 Dveloppement cycle 15 itratif 324 Directives #define 742 #include 24, 100, 160 using 630 Discrimination 349 do...while boucle 184 syntaxe 185 Domaine, modle 331 Donnes accs 228 dcoupage 385 gestion avec pointeurs 216
852
Le langage C++
membres 138 prives, ou protges 362 stockage en mmoire 40 type abstrait 467 double (type de variable) 44
E
diteur de liens 6, 15 de texte 14 et traitement de texte 21 lments de tableaux 396 En ligne, fonction 757 Encapsulation 9, 138, 576 End Of File, fin de fichier Voir EOF End Of Line, fin de ligne Voir EOL En-tte de boucle 193 de fonction 34, 256 fichier d 24, 741 Entiers, signs 53 Entre avec cin 580 de lutilisateur 36 standard, rcupration de chanes 589 Entres/sorties, fichiers 604 numration 59 Environnement de dveloppement 13 EOF 586, 587, 592, 819 EOL 592 Erreurs compilation 15, 20, 101 dition de liens 620 limite de tableaux 401 logiques 700 points darrt 732
Espace ajouter membres 627 anonyme 634 de nom 619 standard 26, 28 std Voir std fonctions 626 nom 625 standard 636 tapes dexcution dun programme 19 tat des E/S 605 valuation des expressions 68 Exceptions 701 crer une classe 708 dtection 712 modles 727 multiples 713 new 223 produire 706 traitement 704, 708 Excution dun programme, tapes 19 de fonction 103 Exemples #define 740 accs aux membres public dune classe 144 adresses dune rfrence 245 ajout de donnes en fin de fichier 607 break et continue 179 chanes de caractres et cin 581, 582 champs de bits 762 classes abstraites 463 agrges 524 amies 551 complte 164
contenant des objets classe String 426, 520 de base communes 454 constructeurs 153, 286, 365, 449 dans classes drives 367 de copie 290 virtuels de copie 388 consultation dune liste avec un itrateur 685 conteneur map 688 conversion 316 de type 314 corps dune boucle for vide 192 daffectation 311 dlgation une liste agrge 532 destructeurs 153, 365 do...while 184 change dobjets modles avec les fonctions 660 criture dun objet dans un fichier 610 en ligne 122 entres multiples 583 erreurs de compilation 157 exceptions avec les modles 727 multiples 713 fonction amie non modle 652 de classe 148 en ligne plutt quune macro 757 et donnes membres statiques avec modles 672 membres 280 virtuelles 379, 380
Index
853
for 187 imbriques 193 vide 190 fuites mmoire 273 get() avec paramtres 588 avec un tableau de caractres 589 sans paramtres 586 hritage priv 542 hirarchies de classes et exceptions 717 ignore() 592 inclusion de fichiers en-tte 162 initialisation de variables membres 288 itration ou rcursivit 195 lire le contenu dun pointeur 218 macro assert() simple 747 manipulation de donnes par pointeurs 216 masquage de mthodes 374 mthode de base depuis mthode redfinie 377 mthodes daccs 146 virtuelles pures 469 modle Array 647 modification de la largeur de la sortie 597 multiple 445 nom de fichier en paramtre de la ligne de commande 614 nulles dans boucle for 190 objet fonction 692
objet temporaire anonyme 299 oprateur ++ 296 ami 561 daddition 304 sur ostream 657 operator 566 ouverture de fichiers en lecture et criture 606 paramtres de la ligne de commande 613 parenthses dans les macros 744 passage de rfrences des objets 267 par rfrence et fonctions virtuelles dans exceptions 723 par valeur et dcoupage de donnes 385 plusieurs valeurs par pointeurs 257 par rfrence 259 pointeur comme donnes membres dune classe 230 comme paramtres 502 const 264 perdu 234 sur fonctions membres 508 this 232, 300 polymorphe 119 prfixe et suffixe 303 printf() 602 raffectation dune rfrence 247 rcupration de donnes dans un objet exception 720
rcursive 125 redfinition dune mthode de base dans une classe drive 372 rfrence un objet inexistant 271 aux objets 249 sans fin 201 squentiel avec modification 694 setf 599 simple 360 strcpy() 424 String 516 strncpy() 424 suppression dobjets 226 dun pointeur 224 sur objets constants 238 swap() avec des rfrences 255 switch 199 tableau plusieurs dimensions 407 constantes et numrations dans 403 dentiers 396 dobjets 404 de pointeurs sur fonctions membres 510 criture hors limite 398 remplissage 422 stockage dans le tas 409 sur le tas avec new 416 transtypage descendant 442 typedef pour les pointeurs 505 types abstraits de donnes 467
854
Le langage C++
utilisation de rfrences 244 valeurs par dfaut 282 des paramtres 116 variables locales et globales 109 vecteur et accs ses lments 679 virtuel 458 while 175, 183 modifie 186 true 182 write() 596 Experts du domaine 328 Expressions C++ 111 dcomposition 76 Extensions de fichiers 160 sources 14
F
Fichiers binaires 610 en-tte 24, 160 iostream.h 24 entres/sorties 604 excutables, cration 15 include 770 iostream 27 objet 15 sources, extensions 14 FIFO 687 fill() (fonction) 694 find() 693 float (type de variable) 44 flush() (fonction) 595 Flux de sortie 597
et tampons 578 prsentation 575 printf() 601 Fonctionnalits Voir Classes Fonctionnement des exceptions 712 Fonctions 8, 33 accolades 34 arguments 34 bad() 605 cin 585 close() 605 comme argument 111 comportement 128 const 156 de classes 148 de pointeurs 502 dclaration 565 dfinition 161 en ligne 121 en-tte 34 eof() 605 et pile 131 exemple 35, 102 fail() 605 fill() 598 getline() 590 good() 605 ignore() 592 macro 742 main() 25, 98 membres 138 modles 651, 683 nature et rle 7 noms 101 par dfaut 115 paramtres 34 parenthses 743 polymorphes 118 prdfinies 100
printf() 601 prives 143 pures 467 put() 595 putback() 593 read() 610 rsultats multiples 257 return 35 rle 98 setf() 598 sizeof() 43 spcialises 665 surcharges 279 taille 111 typedef 504 valeurs par dfaut 284 virtuelles 383 width() 597 write() 595 for utilisation avance 188 vides 191 for_each() 693 Format ASCII Voir ASCII Formatage 28 donnes en sortie 594 Voir aussi Caractres friend (mot-cl) Voir Fonctions/Classes fstream, fichier 605 Fuite mmoire 223, 225
G
Garbage 130 Gnralisation (POO) 322, 333 get() (fonction) 586 Globales fonctions 98
Index
855
H
Heap (mmoire) Voir Tas Hritage 10 avanc 515 classe de base 453 constructeurs dobjets 449 dclaration 448 drivation 358 multiple 347, 445 parties dun objet 448 priv 541 problmes 462 public 347 reprsentation 323 simple 437 Hirarchie complexe dabstraction 472 exceptions 716
I
If, instruction et erreurs classiques 81 ifstream objet 604 Imbrication boucles 192 classes Voir Composition espaces de noms 627 Implmentation en ligne 161 en termes de 532 Impression 26 et caractres spciaux 56 Indentation 765 Indicateurs dtat du flux de sortie 597
iostream 598, 599 ostream 597 Indirection 213 Initialisation donnes membres de classe 151 tableau 407 Instance de classe 144 de modle 643 Instanciation 643 Instructions #include 24 catch(...) 713 du prprocesseur 24 endl 29, 595 return 35 std::endl 28 switch 198 int (type de variable) 44 type renvoy 25 Interaction diagrammes 337 Interface 257 de classes 160 fonctions 148 multiple (Java) 347 publique 156 Interprteur 6 Invariant de classe 749 Invariants() (mthode) 749 Inversion de bits 761 iostream fichier en-tte 24 indicateurs 598, 605 ISO 54
L
Langages C 11 C# 11 C++ Voir C++ compils 6 de modlisation Voir UML interprts 6 Java Voir Java procduraux 136 Lecture dune adresse 217 Liaison interne et externe 623 liste chane 837 Ligne de commande, paramtres 612 Limite, criture dans tableau 398 Linker Voir diteur de liens Liste arborescence 837 chane 837 de paramtres, dclaration 269 double 837 nuds 837 simple 837 Littrales, constantes 57 Locale, variable 104 Localisation des erreurs 731 long (type de variable) 51 long int (type de variable) 44 Lvalue 69
M
Macros assert() 747 contre fonctions et modles 756 prdfinies 747
J
Java 6, 11
856
Le langage C++
main() (fonction) 25, 98 void ou int 115 Masquage donnes 9 mthode 374 Membres classe agrge Voir Agrgation et modles 671 statiques, donnes 484 Mmoire libration 221 organisation 221 Mthodes avantages et inconvnients 391 daccs 145 dfinition 159 membres Voir Fonctions membres valeurs par dfaut 282 mixin Voir Classes de fontionnalits Modles 321 amis de modles 656 bibliothque standard Voir Bibliothque dynamiques 352 et classes 652 et macros 756 paramtrs 643 sous-types 349 statiques 344 STL Voir Bibliothque utilisation 660 Modulo 178, 778 Mots-cls 28, 47 class 144, 147 const 59, 156 continue 179
delete 223 enum 59 goto 174 inline 121, 161 namespace 30, 625 new 222, 677 private 142, 150 return 112 signed 43 template 644 typedef 50 using 630
N
namespace (mot-cl) 30 Nuds 684 tte 838 Noms 767 des paramtres (fonctions) 101 Normes ANSI 12 ISO 54 Notation hongroise 46, 767 UML 321 Nouvelle ligne 26 Nuls objets 274 pointeurs et rfrences 250
fichiers Voir Fichiers fonctions 692 rfrence 248 tableaux 404 ofstream comportement par dfaut 607 objet 604 Oprateurs bit bit 758 dextraction 580 dincrmentation 295 de concatnation 746 de redirection 17, 580 de rsolution de porte 621 et fonctions 595 prfixe et suffixe 302 priorits 90 rfrence 244 restrictions 309 sizeof() 610 surcharge 293 syntaxe 270 types renvoys 297 operator / 656 Ouverture des fichiers E/S 605
P
Paquetage 338 Paramtres de fonctions 107 exemple 527 par rfrence 244, 260 Passage de paramtres 108 aux constructeurs de base 367 et dcoupage donnes 385 Pause dans excution 20 peek() (fonction) 593 Pile 130, 220
O
Objets 9, 136 cin Voir cin cout Voir cout cration dans le tas 226 et classes 140 tat 598 exceptions 719
Index
857
de donnes 686 pointeur de 130 structure 130 Point darrt 732 de contrle 732 Pointeurs adresse et valeur 215 arithmtique 411 cration 214 dinstruction (pile) 129 de fonctions 492 et rfrences 269 et suivi 275 fous 212, 233 initialisation 213 membres 507 perdus 236 rle et fonction 210 sur fonctions membres 510 tableaux 409, 499 this 232 utilit 220 Polymorphisme 10, 379 de fonction 118 POO 9 et C++ 9, 135 Porte dans une boucle 194 locale 132 Prfixe, surcharge 295 Prprocesseur 24 Priorit des oprateurs 777 private (mot-cl) 362 Priv(es) donnes membres Voir Donnes hritage 541
Procdures 8, 136 Programmation commentaires 30 dveloppement cycle 15 environnement 13 tapes dans Visual C++ 19 niveaux dabstraction 128 oriente objet Voir POO procdurale 7 structure 7, 136 styles 765 Programme dfinition 7 droulement 173 distribution 6 dition de liens 15 excution 20 fichier excutable 15 prparation 13 Projet analyse 340 construction (tapes) 19 protected (mot-cl) 362 Prototype de fonction 100 Public/ques drivation 360 fonctions 143 membres 168 mthodes Voir Fonctions putback() (fonction) 593
Redfinition de mthodes 372 Redirection 579 Rfrences alias 246 const 268 de valeurs multiples 257 extraction dadresses 245 nature et rle 244 problmes de raffectation 247 Registres 129, 130, 221, 733 Relation a-un 531 est-un 531 Remplissage, caractres de 598 Renvoi, variables temporaires anonymes 299 Respect casse des caractres Voir Casse Retour de fonction 98 return, instructions 35 Rutilisabilit 10 RTTI 441, 444 Rvalue 69
S
Saisie de caractres 586 Scnario 334 Script 6 short (type de variable) 51 short int (type de variable) 44 Signature dune fonction 100 sizeof() (oprateur) 43 Sorties Voir Impression Source, fichier Voir Fichiers stack, classe 686
R
RAM 40 organisation 210 partitionnement 129 Rational Rose 331, 353 Rcursivit 111, 122, 123
858
Le langage C++
Standard bibliothque Voir Bibliothque entres et sorties 579 std 25, 28, 121, 677, 684, 686, 692 Step into 128, 302 Stockage des donnes en mmoire 40 strcpy() (fonction) 423 Structures 136, 168 de la pile 130 file dattente 687 pile 686 Styles de programmation 765 Suppression de tableaux 417 Surcharge daddition 306 dinsertion 566 et fonctions amies 560 et redfinition 374 fonctions 117 suffixe 302 swap() (fonction) 108 par rfrence 253 par valeur 251 switch clause default 198 instruction 766 syntaxe 198, 200 Symboliques, constantes 58 Syntaxe drivation 360 for 188 rfrences 250 tableaux 404 while 177
T
Tableaux plusieurs dimensions 406 de caractres 421 de chanes de caractres 430 de fonctions 499 erreurs dintervalle 401 initialisation 402, 407 listes chanes 433 nom des 415 numrotation des lments 396 prsentation 395 problmes mmoire 409 rallocation 419 redimensionner 418 Tabulation 28 TAD (Type abstrait de donnes) 476 Taille des entiers 41 Tampon de caractres 582 utilisation 577 Tas renvoi dune rfrence 272 suppression dobjets 226 this Voir Pointeurs Transtypage descendant 441 Tri 695 typedef 50 Types abstraits 463 de commentaires 31 de donnes 28 de variables 43 int 25 logiques 350 taille 42
U
UC (unit centrale) 129 UML classes 341 interaction entre classes 352 relations entre classes 347 Voir aussi Cas dutilisation Use case Voir Cas dutilisation using, mot-cl 28 Utilisateur, entres 36
V
Valeurs intermdiaires, affichage 755 par rfrence 259 passes en paramtre 111 rcuprer pour une variable 213 type 99 void 112 Variables 39 affecter une valeur 48 caractre 54 cration 48 et mots-cls 47 globales 104 intermdiaires 112 locales 104 nom des 45 porte 104 types 40 utilisation 49 virgule flottante 43 Virgule flottante 28
Index
859
Virtuel(le)s destructeurs Voir Destructeur hritage Voir Hritage mthodes Voir Mthodes Visibilit Voir Porte
Visual Basic 6 void type 25 valeurs renvoyes 112 vptr 383 v-table 383
W
while boucle 175 write() (fonction) 610
Le langage C++
Matrisez rapidement les principes fondamentaux de la programmation oriente objet ! Grce cet ouvrage, initiez-vous rapidement au C++ en dcouvrant les principaux concepts du langage et de la programmation oriente objet : gestion des entres-sorties, boucles et tableaux, modles, etc. Ces concepts sont mis en uvre dans de nombreux exemples de codes, qui sont analyss en dtail pour vous permettre ensuite dcrire vos propres programmes. Vous pourrez galement vous exercer et tester vos connaissances avec les quizz et exercices prsents dans chaque chapitre, dont vous trouverez les rponses la n de louvrage. Entirement rvis pour cette nouvelle dition, ce best-seller vous guidera pas pas dans votre apprentissage du C++.
Bien dbuter en C++ Anatomie dun programme C++ Variables et constantes Expressions et instructions Fonctions Programmation oriente objet Droulement dun programme Pointeurs Rfrences Fonctions avances Analyse et conception oriente objet Hritage Tableaux et chanes Polymorphisme Classes et fonctions spciales Concepts avancs dhritage Les ux Espaces de noms Les modles Gestion des erreurs et exceptions Et maintenant ? Mots-cls C++ Priorit des oprateurs Solutions des exercices tude des listes chanes
propos de lauteur
Jesse Liberty est lauteur de nombreux best-sellers sur ASP.NET, C# et C++. Il a travaill pour AT&T et Citibank avant dintgrer les quipes de Microsoft. Bradley L. Jones dirige de nombreux sites web ddis la programmation, dont CodeGuru.com, Developer.com et Javascripts.com.
ISBN : 978-2-7440-4023-8
Pearson Education France 47 bis, rue des Vinaigriers 75010 Paris Tl. : 01 72 74 90 00 Fax : 01 42 05 22 17 www.pearson.fr