Vous êtes sur la page 1sur 872

Le langage

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++

Jesse Liberty et Bradley Jones

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

1 3 5 23 39 65 97 135 173 207 209 243 279 321 357 395

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

Table des matires

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

Table des matires

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

Table des matires

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

Bien dbuter en C++

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

Comment rsoudre les problmes


Les problmes auxquels sont confronts les programmeurs sont totalement diffrents de ceux quils devaient rsoudre il y a une vingtaine dannes. Dans les annes 80, les programmes traitaient des volumes importants de donnes brutes. Le programmeur et lutilisateur nal taient tous deux des spcialistes de linformatique. De nos jours, les utilisateurs sont bien plus nombreux et peu connaissent tous les dtails des ordinateurs et du fonctionnement des programmes. Lutilisateur nal actuel recherche des solutions prtes lemploi et capables deffectuer des oprations de gestion courantes ou ponctuelles. Linformatique est devenue plus conviviale, mais ce processus a galement conduit la mise en uvre de programmes de plus en plus complexes. Dans les annes 70, les utilisateurs taient contraints de saisir des commandes nigmatiques pour voir dler lcran des volumes impressionnants de donnes brutes. Cette poque est rvolue ! Une application se compose dsormais de fentres, de menus et de botes de dialogue intgrs une interface conviviale. Avec le dveloppement du Web, les ordinateurs ont abord une re nouvelle de pntration du march ; les utilisateurs dordinateurs sont plus nombreux que jamais, et ils sont trs exigeants. Au cours des dernires annes, les applications se sont galement tendues dautres priphriques : lordinateur de bureau nest plus la seule cible des applications. Les tlphones portables, les assistants personnels (PDA), les PC de poche et autres priphriques constituent des cibles toutes trouves pour les applications modernes. Depuis la premire dition de ce livre, les programmeurs ont rpondu aux demandes des utilisateurs et les programmes sont devenus plus volumineux et plus complexes. La ncessit de dvelopper des techniques de programmation permettant de grer cette complexit est devenue vidente. Les besoins changeant, les techniques et les langages voluent galement pour aider les programmeurs grer la complexit des demandes. Dans cet ouvrage, nous nous

Le langage C++

concentrerons uniquement sur une partie essentielle de cette volution : le passage de la programmation procdurale la programmation oriente objet.

La programmation procdurale, structure et oriente objet


Il y a quelques annes encore, les programmes taient conus comme des suites de procdures destines traiter les donnes. Une procdure galement appele fonction ou mthode est un ensemble dinstructions sexcutant lune aprs lautre. Les donnes et les procdures taient totalement dissocies et le travail du programmeur consistait connatre les fonctions appeles par dautres fonctions et les donnes qui taient modies. Pour faire face ce niveau de complexit, on a donc invent la programmation structure. Le principe gnral de la programmation structure consiste diviser pour mieux rgner. Un programme peut alors tre considr comme un ensemble de tches. Toute opration trop complexe pour tre dcrite simplement est dcompose en un ensemble doprations plus simples, jusqu nobtenir que des tches sufsamment triviales pour tre aisment comprhensibles. Le calcul du salaire moyen de chaque employ dune entreprise est, par exemple, une tche assez complexe. Toutefois, il est possible de diviser le traitement en plusieurs tches secondaires : 1. Compter le nombre demploys. 2. Dterminer le revenu de chaque employ. 3. Faire le total de tous les salaires. 4. Diviser cette valeur par le nombre demploys. La troisime tape (total des salaires) peut galement se diviser en tches plus simples : 1. Lire lenregistrement de chaque salari. 2. Extraire le salaire de chaque employ. 3. Ajouter cette valeur au total gnral. 4. Accder lenregistrement suivant. La lecture de chaque enregistrement peut, de la mme faon, tre dcompose en oprations plus lmentaires : 1. Ouvrir le chier des employs. 2. Rechercher le bon enregistrement. 3. Lire les donnes.

Chapitre 1

Bien dbuter en C++

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.

La programmation oriente objet (POO)


La programmation oriente objet (POO) rpond ces besoins. Elle fournit les techniques permettant de traiter des applications trs complexes, exploite des composants logiciels rutilisables et associe les donnes aux tches qui les manipulent. La caractristique essentielle de la programmation oriente objet est de modliser des "objets" (cest--dire des concepts) plutt que des "donnes". Ces objets peuvent tre des lments graphiques afchables, comme des boutons ou des zones de liste, ou des objets rels, comme des clients, des bicyclettes, des avions, des chats ou de leau. Les objets possdent des caractristiques, galement appeles proprits ou attributs, comme ge, rapidit, volume, noir, humide. Ils ont aussi des fonctionnalits, appeles oprations ou fonctions, comme acclrer, voler, miauler ou couler. Le rle de la programmation oriente objet est de reprsenter ces objets dans le langage de programmation.

C++ et la programmation oriente objet


Le langage C++ permet dutiliser toutes les possibilits de la programmation oriente objet, notamment ses trois piliers que sont lencapsulation, lhritage et le polymorphisme.

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

Bien dbuter en C++

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.

Est-il ncessaire dapprendre dabord le langage C ?


Nombreux parmi vous sont ceux qui vont se poser cette question, puisque C++ est un surensemble du langage C. La rponse de son crateur et de la plupart des programmeurs C++ est la suivante : il est inutile dapprendre le langage C, voire prfrable de commencer directement par le langage C++. La programmation C est fonde sur les concepts de programmation structure, alors que la programmation C++ repose sur ceux de la programmation oriente objet. Si vous apprenez dabord le langage C, vous devrez alors vous dfaire des habitudes nfastes lies ce langage. Ce livre ne sadresse pas obligatoirement un public ayant une exprience pralable de la programmation. Si vous tes programmeur C, vous pouvez vous contenter de survoler les premiers chapitres du livre. Le dveloppement orient objet nest rellement abord qu partir du Chapitre 6.

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.

Extensions gres de Microsoft pour C++


Avec larrive de .NET, Microsoft a introduit les extensions gres pour C++ ( Managed C++). Il sagit dune extension du langage C++ lui permettant dutiliser la nouvelle plateforme Microsoft et ses bibliothques. Managed C++ permet surtout un programmeur C++ de proter des fonctionnalits avances de lenvironnement .NET. Au cas o vous dcideriez de crer des applications spciquement conues pour la plate-forme .NET, vous devrez tendre votre connaissance du C++ standard pour y inclure ces extensions.

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

Bien dbuter en C++

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.

Votre environnement de dveloppement


Pour utiliser ce livre, nous supposons que vous disposez dun compilateur permettant de saisir des donnes directement sur une "console" (par exemple une fentre de commande MS-DOS ou une fentre shell), cest--dire sans vous proccuper dun environnement

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

texte fourni avec le compilateur ou dun diteur externe.


Enregistrer votre chier source avec lexten-

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-

sion .c, .cp ou .cpp.


Consulter la documentation du compilateur et

de lditeur de liens pour connatre les diffrentes tapes de la cration du programme.

teur considre ces chiers comme du code C et non du code C++.

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

Bien dbuter en C++

15

Cration dun chier objet laide du compilateur


Pour transformer le code source en programme, vous allez utiliser un compilateur. Linvocation et la conguration de cet outil variant dun compilateur un autre, il est vivement recommand de consulter la documentation. La compilation produit un chier objet. Le plus souvent, il porte lune des extensions .obj ou .o. Toutefois, il ne sagit pas encore dun programme excutable. Pour crer le chier .exe, vous devez utiliser lditeur de liens (linker).

Cration dun chier excutable laide de lditeur de liens


Les programmes C++ sont produits en liant un ou plusieurs chiers objet ( .obj ou .o) avec une ou plusieurs bibliothques. Une bibliothque regroupe des chiers fournis avec le compilateur, achets sparment ou que vous crez et compilez vous-mme. Tous les compilateurs C++ sont livrs avec une bibliothque de fonctions et de classes trs utiles, que vous pouvez inclure dans vos programmes. Nous traiterons les classes et les fonctions plus en dtail ultrieurement. La cration dun chier excutable se dcompose en trois tapes : 1. Cration dun chier source portant lextension .cpp. 2. Compilation du code source en un chier objet portant lune des extensions .obj ou .o. 3. Liaison du chier objet avec les diffrentes bibliothques, an dobtenir le programme excutable.

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++

Figure 1.1 Les tapes du cycle de dveloppement dun programme C++.

Dbut

Ecriture/ modification du code source

Compilation

Oui

Erreurs de compilation ? Non Edition de liens

Oui

Erreurs d'dition de liens ? Non Lancement du programme

Oui

Erreurs d'excution ?

Non Fin

Votre premier programme C++ : BONJOUR.cpp


Les ouvrages traitant de programmation proposent traditionnellement un programme qui permet dafcher "Bonjour" lcran. Nous ne drogerons donc pas cette rgle ! Ouvrez lditeur de texte, puis tapez le code source du Listing 1.1 en respectant scrupuleusement la syntaxe (ne tenez pas compte de la numrotation des lignes qui ne sert ici qu clarier les choses). Aprs vrication, enregistrez le chier, compilez-le, ditez les liens, puis lancez-le. Si tout se passe correctement, "Bonjour !" apparat lcran. Ne vous souciez pas des instructions utilises ici ; pour le moment, nous nous contentons de vous prsenter

Chapitre 1

Bien dbuter en C++

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>

Listing 1.1 : Programme bonjour.cpp


1: 2: 3: 4: 5: 6: 7: #include <iostream> int main() { std::cout << "Bonjour!\n"; return 0; }

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:

#include <iostream.h> int main() { cout << "Bonjour!\n"; return 0; }

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

Bien dbuter en C++

19

Comment tirer parti de votre compilateur


Ce livre nest pas spcique un compilateur donn ; les programmes inclus fonctionneront donc avec nimporte quel compilateur C++ compatible ANSI, sur nimporte quelle plate-forme (Windows, Macintosh, Unix, Linux, etc.). Ceci dit, une grande majorit de programmeurs travaillent sous lenvironnement Windows et la plupart des dveloppeurs professionnels ont recours aux compilateurs Microsoft. Il nest pas possible dindiquer ici les dtails de la compilation et de ldition de liens avec tous les compilateurs possibles, mais nous pouvons vous montrer comment dbuter avec Microsoft Visual C++ ; cela ne devrait pas tre trs diffrent avec votre compilateur, bien quil soit conseill de vous reporter sa documentation spcique.

Construction du projet Bonjour


Les tapes ncessaires la cration et au test du programme Bonjour sont les suivantes : 1. Lancez le compilateur. 2. Choisissez Nouveau/Projet dans le menu Fichier. 3. Choisissez Application Console Win32 et entrez un nom pour le projet, Bonjour, par exemple, puis cliquez sur Terminer. 4. Slectionnez Projet vide dans les choix proposs et cliquez sur OK. Une bote de dialogue contenant des informations sur le nouveau projet safche. 5. Cliquez sur OK. Vous revenez la fentre principale de lditeur. 6. Choisissez Nouveau dans le menu Fichier. 7. Choisissez Fichier C++ et donnez-lui un nom : bonjour, par exemple, que vous saisirez dans la zone de texte Nom du chier. 8. Cliquez sur OK. Vous revenez la fentre principale de lditeur. 9. Saisissez le code du Listing prcdent. 10. Choisissez Gnrer bonjour dans le menu Gnrer. 11. Vriez que vous navez pas derreurs de compilation. Vous trouverez ces informations au bas de lditeur. 12. Excutez le programme en appuyant sur Ctrl+F5. 13. Appuyez sur la barre despacement pour terminer le programme.

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

char reponse; std::cin >> reponse;


Le programme fera une pause pour attendre que vous entriez une valeur. Pour terminer le programme, entrez un nombre (par exemple 1) puis appuyez sur Entre (si ncessaire). La signication des termes std::cin et std::cout sera prsente dans les prochains chapitres ; pour linstant, utilisez-les sans vous poser de questions.

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

Bien dbuter en C++

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.

Testez vos connaissances


1. Quelle est la diffrence entre un interprteur et un compilateur ? 2. Comment compiler un chier source ? 3. quoi sert lditeur de liens ? 4. Quelles sont les diffrentes tapes du cycle de dveloppement ?

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

Ce programme afche le rsultat suivant :


Bonjour!

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

Anatomie dun programme C++

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.

tude rapide de lobjet cout


Au Chapitre 17, vous apprendrez afcher des donnes lcran laide de lobjet cout. Pour le moment, vous pouvez lutiliser sans connatre parfaitement son mode de fonctionnement. Pour afcher une valeur sur votre moniteur, tapez le mot cout, suivi de loprateur dinsertion (<<), cest--dire de deux signes "infrieur ". Mme si le signe est constitu de deux caractres, C++ considre quil ny en a quun. Tapez les donnes aprs cet oprateur. Le Listing 2.2 est un exemple dutilisation de loprateur dinsertion. Recopiez le chier source de ce petit programme en indiquant vos propres prnom et nom. Listing 2.2 : Exemple dutilisation de cout
1: 2: 3: 4: 5: 6: 7: 8: // Listing2.2 utilisation de #include <iostream> int main() { std::cout << "Salut.\n"; std::cout << "Je tape 5: std::cout << "Loprateur std::cout << "provoque un std::cout

" << 5 << "\n"; std::endl "; saut de ligne lcran.";

Chapitre 2

Anatomie dun programme C++

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

Ce programme produit le rsultat suivant :


Salut. Je tape 5: 5 Loprateur std::endl provoque un saut de ligne lcran. Voici un trs grand nombre: 70000 8 et 5 font: 13 Voici une fraction: 0.625 Et un nombre astronomique: 4.9e+07 Remplacez le nom par le votre ... Elie Kopter est un programmeurC++!

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 !

Utilisation de lespace de nom standard


Lutilisation systmatique de std:: devant cout et endl pouvant devenir plutt fastidieuse la longue, la norme ANSI propose deux solutions ce petit problme. La premire consiste indiquer au compilateur, au dbut du code, que vous allez utiliser les objets cout et endl de la bibliothque standard, comme au Listing 2.3 (lignes 5 et 6). Listing 2.3 : Utilisation du mot-cl using
1: 2: // Listing2.3 - Utilisation du mot-cl using #include <iostream>

Chapitre 2

Anatomie dun programme C++

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

Ce qui produit le rsultat suivant :


Salut. Je tape 5: 5 Loprateur endl provoque un saut de ligne lcran. Voici un tres grand nombre: 70000 8 et 5 font: 13 Voici une fraction: 0.625 Et un nombre astronomique: 4.9e+07 Remplacez le nom par le vtre ... Elie Kopter est un programmeurC++!

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++

Listing 2.4 : Utilisation du mot-cl namespace


1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: // Listing2.4 - Utilisation de lespace de nom standard #include <iostream> int main() { using namespace std; cout << "Salut.\n"; cout << "Je tape 5: " << 5 << "\n"; cout << "Loprateur endl "; cout << "provoque un saut de ligne 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 indique"; cout << "par le votre...\n"; cout << "Elie Kopter est un programmeur C++!\n"; return 0; }

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

Anatomie dun programme C++

31

par le compilateur. Ils ont pour but dinformer le lecteur sur telle ou telle action du programme.

Les diffrents types de commentaires


C++ utilise deux types de commentaires : les commentaires sur une seule ligne et les commentaires multilignes. Les premiers sont introduits par deux caractres slashs (//) qui indiquent au compilateur quil doit ignorer tout ce qui suit jusqu la n de la ligne. Les seconds sont introduits par les caractres slash-toile (/*) et demandent au compilateur de ne pas traiter les caractres qui suivent, jusquau symbole de n de commentaires toile-slash (*/). Ces marques peuvent se trouver sur la mme ligne ou tre spares par plusieurs lignes. chaque symbole /* doit correspondre un symbole de n de commentaires */. En gnral, les programmeurs C++ utilisent le double slash, puis une ligne de commentaire. Ils rservent les commentaires de plusieurs lignes pour dlimiter de grandes parties dun programme. Vous pouvez inclure des commentaires dune ligne dans un bloc "comment" : ce qui gure entre les symboles de commentaires multilignes nest pas pris en compte, y compris les doubles slashs. Les commentaires sur plusieurs lignes sont connus sous le nom de "style C" car ils ont t introduits par le langage C. Les commentaires dune ligne sont apparus avec le C++ et sont donc devenus le "style C++". Les normes actuelles de ces deux langages intgrent les deux styles.

Info

Utilisation des commentaires


Certains prconisent dcrire des commentaires au dbut de chaque fonction pour indiquer son rle et les valeurs quelle renvoie. Cette recommandation est discutable, car ce type de commentaire mis en en-tte nest pratiquement jamais remis jour lors des diffrentes volutions du programme. Les noms de fonctions doivent tre sufsamment explicites pour viter toute ambigut et les portions de code trop nigmatiques doivent tre rcrites pour tre sufsamment claires par elles-mmes. Les commentaires ne doivent pas tre une excuse dun code obscur. Ceci ne veut pas dire que les commentaires ne servent rien, mais simplement quils ne doivent pas se substituer un code lisible : il est toujours prfrable de clarier un code plutt que dexpliquer ce quil fait. Bref, crivez du code lisible et nutilisez les commentaires que pour fournir des informations supplmentaires.

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

Ce programme produit le rsultat suivant :


Bonjour! Exemples de commentaires!

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

Anatomie dun programme C++

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

Listing 2.6 : Appel de fonction dans un programme


1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: #include <iostream> // Fonction DemoFonction // affiche un message dinformation void DemoFonction() { std::cout << "Dans la fonction DemoFonction \n"; } // Fonction main - affiche un message, // appelle DemoFonction, puis affiche // un second message. int main() { std::cout << "Dans la fonction main\n";

34

Le langage C++

16: 17: 18: 19:

DemoFonction(); std::cout << "Retour dans main\n"; return 0; }

Ce programme produit le rsultat suivant :


Dans la fonction main Dans la fonction DemoFonction Retour dans main

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.

Utilisation des fonctions


Lintrt dune fonction est de transmettre au programme une valeur dont il a besoin. Par exemple, une fonction qui additionne deux entiers renverra la somme obtenue sous la forme dune valeur entire. En revanche, une fonction qui afche simplement un message ne transmet pas de valeur au programme et doit donc tre dclare comme renvoyant une valeur void. Une fonction se compose dun en-tte et dun corps. Len-tte se divise lui-mme en plusieurs parties : le type de la valeur renvoye, le nom de la fonction et les paramtres quelle attend. Ces derniers permettent de transmettre des valeurs la fonction. Par exemple, si la fonction additionne deux nombres, ceux-ci devront lui tre passs en paramtre. La ligne ci-dessous est un en-tte caractristique dune fonction Somme qui attend deux valeurs entires (a et b), puis renvoie une valeur entire :
int Somme(int a, int b)

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

Anatomie dun programme C++

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

Ce programme produit le rsultat suivant :


Vous etes dans main()! Entrez deux nombres: 35 Appel de Somme()

36

Le langage C++

Somme() a recu 3 et 5 Retour dans main(). c vaut maintenant 8 Fin du traitement...

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

Anatomie dun programme C++

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.

Testez vos connaissances


1. Quelle est la diffrence entre un compilateur et un prprocesseur ? 2. Quelles sont les particularits de la fonction main() ? 3. Quels sont les deux types de commentaires admis en C++ ? En quoi diffrent-ils ? 4. Est-il possible dimbriquer des commentaires ? 5. Un commentaire peut-il tre plus long quune ligne ?

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.

Quest-ce quune variable ?


En C++, une variable est un emplacement destin recevoir des donnes. Cette zone se situe dans la mmoire vive de votre ordinateur et reoit une valeur qui peut ensuite tre relue. Les variables sont utilises pour un stockage temporaire. Si vous quittez un programme et arrtez votre ordinateur, linformation stocke dans ces variables est perdue. Un stockage

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.

Stockage des donnes en mmoire


La mmoire de votre ordinateur peut tre compare un ensemble de cases alignes les unes ct des autres. Ces emplacements sont numrots squentiellement par des nombres appels adresses mmoire. Une variable peut occuper une ou plusieurs cases an de stocker la valeur quelle contient. Le nom de la variable (par exemple, maVariable) est une tiquette place sur lune des cases. Il identie la variable et vous pargne de devoir connatre son adresse relle en mmoire. La Figure 3.1 est une reprsentation symbolique de lagencement de la mmoire. Comme vous pouvez le constater, maVariable commence ladresse mmoire 103. En fonction de sa taille, elle peut occuper une ou plusieurs adresses.
Figure 3.1 Reprsentation schmatique de la mmoire.
maVariable Nom de la variable RAM Adresse 100 101 102 103 104 105 106

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.

Gnralits sur la mmoire


En C++, vous devez attribuer un type chaque variable que vous dnissez. Il peut sagir dune variable de type entier, dun nombre virgule ottante, dun caractre, etc. Grce ces informations, le compilateur dtermine lespace attribuer la variable en mmoire, ainsi que le type des donnes quelle pourra contenir. Cela permet aussi au compilateur de vous avertir ou de produire un message derreur si vous tentez, par inadvertance, de stocker une valeur dun type erron dans votre variable (cette caractristique de certains langages de programmation sappelle typage fort).

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.

Taille des entiers


Dun ordinateur un autre, les tailles occupes par les diffrents types de variables peuvent tre diffrentes, mais elles seront toujours les mmes sur un ordinateur donn. Ainsi, un entier peut occuper deux octets sur une machine ou quatre sur une autre, mais cette proprit ne variera jamais sur lune ou lautre de ces machines. Les caractres simples, comme les lettres, les chiffres ou les symboles, sont stocks dans des variables de type char, qui occupent gnralement un octet. Pour les nombres entiers les plus petits, on peut employer une variable de type short qui, sur la plupart des ordinateurs, occupera deux octets, alors quune variable de type long occupe gnralement quatre octets. Si vous nindiquez pas le mot-cl short ou long, la taille dun entier varie de deux quatre octets selon les ordinateurs. En fait, contrairement ce que lon pourrait attendre, le langage ne le prcise pas exactement. Tout ce que nous savons est que la taille dun entier short doit tre infrieure ou gale celle dun int qui, son tour, doit tre infrieure ou gale celle dun entier long. Ceci dit, vous travaillez srement sur un ordinateur ayant des short sur deux octets et des int et des long de quatre octets. La taille dun entier est dtermine par le processeur (16 bits, 32 bits ou 64 bits) et le compilateur que vous utilisez. Sur une machine 32 bits (Pentium) utilisant des compilateurs modernes, les entiers occupent quatre octets.
ntion Atte

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

Ce programme produit le rsultat suivant :


La La La La La La La taille taille taille taille taille taille taille dun dun dun dun dun dun dun entier est: entier court est: entier long est: type char est: type float est: type double est: booleen est: 4octets. 2octets. 4octets. 1octets. 4octets. 8octets. 1 octets.

Info

Sur votre ordinateur, le nombre doctets annonc peut tre diffrent !

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

Valeurs true (vrai) ou false (faux)


de 0 65 535 de 32 768 32 767 de 0 4 294 967 295 de 2 147 483 648 2 147 483 647 de 32 768 32 767 de 2 147 483 648 2 147 483 647 de 0 65 535 de 0 4 294 967 295 256 caractres de 1,2e38 3,4e38 de 2,2e308 1,8e308

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.

Dnition dune variable


Pour linstant, vous avez vu la cration et lutilisation de plusieurs variables. Il est temps maintenant dapprendre crer les vtres.

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.

Respect de la casse des caractres


Le langage C++ fait la diffrence entre les majuscules et les minuscules, ce qui signie quune variable appele age est diffrente dune variable appele Age, qui est elle-mme diffrente de la variable AGE. Certains compilateurs permettent de dsactiver cette fonctionnalit. Cette pratique est dconseille car vous risquez de ne pas pouvoir compiler vos programmes avec dautres compilateurs et den rendre la lecture plus difcile pour les autres programmeurs.

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++

Les mots suivants sont rservs :


And and_eq bitand bitor compl not not_eq or or_eq xor xor_eq

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

stockage dune variable.


Utiliser des variables non signes pour

caractres.
Tenir compte de lespace occup par la varia-

stocker des nombres ngatifs.

ble en mmoire en fonction de son type, pour affecter des valeurs adquates.

Cration de plusieurs variables la fois


Vous pouvez crer plusieurs variables de type identique dans la mme instruction en sparant les noms par des virgules :
unsigned int monAge, monPoids; // 2 vars entires non signes

long int surface, largeur, longueur; // 3 variables entires longues

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.

Affectation de valeurs aux variables


Pour affecter une valeur une variable, on utilise loprateur daffectation (=). Dans lexpression suivante, on affecte la valeur 5 la variable largeur :
unsigned short largeur; largeur = 5;

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;

Vous pouvez mlanger des dnitions et des initialisations :


int monAge = 39, tonAge, sonAge = 40;

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++

13a: 14: 15: 16: 17: 18: 19: 20:

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

Ce programme produit le rsultat suivant :


Largeur: 5 Longueur: 10 Surface: 50

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.

Cration dalias avec typedef


la longue, linstruction unsigned short int peut devenir contraignante. Pourtant, elle prsente lavantage dviter les erreurs de type lors de la compilation. Le C++ permet de crer un alias en utilisant le mot-cl typedef (qui signie dnition de type). En ralit, ce mot-cl permet de crer un synonyme, ce qui est diffrent de la cration dun nouveau type (voir Chapitre 6). typedef est suivi du nom du type existant puis de lalias et dun point-virgule. Exemple :
typedef unsigned short int USHORT

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

Listing 3.3 : Utilisation dun alias de type


1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: // Dmonstration de lutilisation du mot cl typedef #include <iostream> typedef unsigned short int USHORT; int main() { using std::cout; using std::endl; USHORT Largeur = 5; USHORT Longueur; Longueur = 10; USHORT Surface = Largeur * Longueur; cout << "Largeur:" << Largeur << endl; cout << "Longueur: " << Longueur << endl; cout << "Surface: " << Surface <<endl; return 0; } //dfinition dalias

Ce programme produit le rsultat suivant :


Largeur: 5 Longueur: 10 Surface: 50

Info

Lastrisque (*) reprsente une multiplication.

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.

short ou long : que choisir ?


Les programmeurs qui dbutent en C++ hsitent souvent entre ces deux types. La solution est simple : il est prfrable de choisir le type long lorsque le contenu risque dexcder la taille maximale autorise pour une variable de type short. Comme le montre le Tableau 3.1, les entiers courts non signs, supposs tre dune taille de deux octets, acceptent une valeur maximale de 65 535. Pour les entiers signs courts,

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.

Rebouclage des entiers non signs


Les entiers non signs acceptent une valeur limite. Quen est-il si vous la dpassez ? Quand la valeur maximale dune variable entire non signe est atteinte, le contenu de cette dernire est remis zro, un peu comme le compteur dune voiture. Examinez le Listing 3.4. Listing 3.4 : Incrmentation de un de la valeur maximale autorise pour un entier short non sign
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: #include <iostream> int main() { using std::cout; using std::endl; unsigned short int Nombre = 65535; cout << "Nombre:" Nombre++; cout << "Nombre:" Nombre++; cout << "Nombre:" return 0; } Nombre; << Nombre << endl; << Nombre << endl; << Nombre << endl;

Ce programme produit le rsultat suivant :


Nombre:65535 Nombre:0 Nombre:1

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.

Rebouclage des entiers signs


Un entier sign se distingue dun entier non sign puisque la moiti des valeurs admises sont ngatives. Au lieu de vous reprsenter un compteur de voiture, vous pourriez imaginer une horloge comme celle de la Figure 3.2, o les chiffres augmentent dans les sens des aiguilles, et diminuent dans le sens inverse. Ils se croisent en bas du cadran ( 6 heures).
Figure 3.2 Si les pendules utilisaient des nombres signs.

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++

Listing 3.5 : Incrmentation de la valeur maximale admise pour un entier sign


1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: #include <iostream> int main() { short int Nombre; Nombre = 32767; std::cout << "Nombre:" << Nombre << std::endl; Nombre++; std::cout << "Nombre:" << Nombre << std::endl; Nombre++; std::cout << "Nombre:" << Nombre << std::endl; return 0; }

Ce programme produit le rsultat suivant :


Nombre: 32767 Nombre: -32768 Nombre: -32767

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;

Ce programme produit le rsultat suivant :


!"#$%&()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMN OPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~

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.

Caractres spciaux pour limpression


Le compilateur C++ reconnat certains caractres spciaux de formatage. Le Tableau 3.3 regroupe les codes de mise en forme les plus courants. Pour les insrer dans un code source, il suft de les faire prcder dune barre oblique inverse (appele caractre dchappement). Pour insrer une tabulation, tapez la ligne suivante :
char carTab = \t;

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

Tableau 3.3 : Caractres dchappement (suite)

Caractre \f \n \r \t \v \ \" \? \\ \000 \xhhh

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.

Dnition de constantes avec #define


De nombreux programmes faisant appel la directive #define, vous devez bien en comprendre le fonctionnement. Pour dnir une constante de cette manire (obsolte), vous pouvez taper :
#define elevesParClasse 15

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

Dnition de constantes laide de const


Bien que la directive #define fonctionne, le mot-cl const permet de crer des constantes C++ plus proprement :
const unsigned short int elevesParClasse = 15;

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

dpasse pas ses limites an quil ny ait pas de rebouclage.


Attribuer des noms signicatifs aux

constantes.
Utiliser la directive #define du prproces-

constantes.

seur pour dclarer des constantes. Prfrer const.

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

Ce programme produit le rsultat suivant :


Au boulot!

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

Ce programme produit le rsultat suivant :


Au boulot!

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.

Testez vos connaissances


1. Quelle est la diffrence entre une variable entire et une variable virgule ottante ? 2. Quelles sont les diffrences entre un unsigned short int et un longint ? 3. Pourquoi prfrer les constantes symboliques aux constantes littrales ? 4. Pourquoi prfrer le mot-cl const la directive #define ? 5. Quest-ce qui caractrise un "bon" nom de variable ou un "mauvais" nom ? 6. Dans cette numration, quelle est la valeur de BLEU ?
enum COLOR { BLANC, NOIR = 100, ROUGE, BLEU, VERT = 300 };

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 !

Blocs et instructions composes


Partout o vous pouvez placer une instruction simple, vous pouvez placer une instruction compose, appele bloc. Un bloc commence par une accolade ouvrante et se termine par

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

Ce bloc agit comme une seule instruction, en intervertissant les valeurs de a et de b.


Faire
Terminer chaque instruction par un point-

Ne pas faire
Oublier dinsrer une accolade fermante

virgule.
Utiliser judicieusement des espaces pour

correspondant une accolade ouvrante.

rendre le code plus lisible.

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;

Ce programme produit le rsultat suivant : ,


a: 0 b: 0 x: 0 y: 35 a: 9 b: 7 x: 16 y: 16

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 :

les oprateurs daffectation ; les oprateurs mathmatiques.

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;

affecte le rsultat de laddition loprande x.

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

Ce programme produit le rsultat suivant :


La difference est: 50 Maintenant la difference est: 4294967246

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.

Division entire et modulo


La division entire est celle que vous avez tudie lcole primaire. Si vous divisez 21 par 4 (21 / 4), la rponse est 5 (avec un reste). Le cinquime oprateur mathmatique vous est peut-tre inconnu. Il sagit de loprateur modulo (%) qui donne la valeur du reste de la division entire. Pour obtenir le reste de la division de 21 par 4, on calcule 21 modulo 4 (21%4), ce qui donne 1. Extraire le reste peut tre trs utile pour, par exemple, afcher une instruction toutes les dix actions. En effet, si le reste de la division dun nombre par 10 est gal 0, ce nombre est un multiple de 10. Ainsi, 1%10 vaut 1, 2%10 vaut 2, etc., jusqu 10%10 qui vaut 0. 11%10 repasse 1 et cette srie se continue jusquau prochain multiple de 10, qui est 20 (20%10 vaut nouveau 0). Vous utiliserez cette technique avec les boucles au Chapitre 7.

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,

double ou long double). 5.0/3.0 vous donnera un rsultat fractionnaire : 1,66667


Si, soit le diviseur, soit le dividende est une valeur virgule ottante, le compilateur produit un quotient virgule ottante. Toutefois, sil est affect une lvalue entire, la valeur sera nouveau tronque.

72

Le langage C++

Combinaison doprateurs daffectation et doprateurs mathmatiques


Il est courant dajouter une valeur une variable, puis de renvoyer le rsultat dans cette dernire. Par exemple, prenons la variable age, laquelle nous allons ajouter 2 :
int age = 5; int temp; temp = age+2; age = temp;

// 5+2 dans temp // affectation de temp age

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.

Ce qui revient crire :


compteur = compteur+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++

Listing 4.3 : 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: // Listing4.3 - Utilisation des oprateurs // dincrmentation et de dcrmentation // en prfixe et suffixe #include <iostream> int main() { using std::cout; int monAge = 39; // initialise deux entiers int tonAge = 39; cout << "Jai: " << monAge << " ans.\n"; cout << "Tu as: " << tonAge << " ans\n"; monAge++; // suffixe ++tonAge; // prfixe cout << "Un an plus tard...\n"; cout << "Jai: " << monAge << " ans.\n"; cout << "Tu as: " << tonAge << " ans\n"; cout << "Encore une annee de plus\n"; cout << "Jai: " << monAge++ << " ans.\n"; cout << "Tu as: " << ++tonAge << " ans\n"; cout << "Reaffichons le resultat\n"; cout << "Jai: " << monAge << " ans.\n"; cout << "Tu as: " << tonAge << " ans\n"; return 0; }

Ce programme produit le rsultat suivant :


Jai: 39 ans Tu as: 39 ans Un an plus tard... Jai: 40 ans Tu as: 40 ans Encore une annee de plus Jai: 40 ans Tu as: 41 ans Reaffichons le resultat Jai: 41 ans Tu as: 41 ans

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.

Priorit des oprateurs


Dans linstruction compose suivante :
x = 5+3 * 8;

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;

La multiplication est prioritaire, de gauche droite. Do : 8 * 9 = 72, et 6 * 4 = 24. Ce qui revient :


x = 5+3+72+24;

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-

des qui rendent lexpression difcile comprendre et mettre jour.


Confondre le sufxe et le prxe.

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;

La rponse est true, 45 tant infrieur 50.


ntion Atte

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

Oprateur == != > >= < <=

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

renvoient une valeur true (vrai) ou false (faux).

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 produit par exemple le rsultat suivant :


Score de lOM: 2 Score du PSG: 2 Egalite? Oh non! Donne-moi le score du PSG: 2 Match nul! OK.

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

if(uneValeur < 10); uneValeur = 10;

// Attention! Remarquez le point-virgule.

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 :

if (uneValeur < 10) // test ; // ne rien faire uneValeur = 10; // affectation


La suppression du point-virgule la n de linstruction if rsoudra le problme. Pour rduire les risques, vous pouvez toujours crire vos instructions if entre accolades, mme lorsque le corps ne stale que sur une ligne :

if (uneValeur < 10) { uneValeur = 10; }

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 }

Les accolades 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

Listing 4.5 : Utilisation du mot-cl else


1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: // Utilisation de la clause else // #include <iostream> int main() { using std::cout; using std::cin; int premierNombre, secondNombre; cout << "Entrez un nombre: "; cin >> premierNombre; cout << "\nEntrez un nombre plus petit: "; cin >> secondNombre; if (premierNombre > secondNombre) cout << "\nMerci!\n"; else cout << "\nErreur! Le premier nest pas superieur!"; return 0; }

Ce programme produit le rsultat suivant :


Entrez un nombre: 10 Entrez un nombre plus petit: 12 Erreur! Le premier nest pas superieur!

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.

Instruction if La syntaxe de linstruction if est la suivante : Forme 1

if (expression) instruction; instruction_suivante;

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

if (expression) instruction1; else instruction2; instruction_suivante;


Si expression est vraie, linstruction1 sexcute. Dans le cas contraire, cest linstruction2. Le programme passe ensuite linstruction_suivante. Exemple :

if (val cout else cout cout <<

< 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++

Ce programme produit par exemple le rsultat suivant :


Entrez deux nombres. Premier: 15 Second: 4 Ils ne sont pas divisibles!

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.

Utilisation daccolades dans les instructions if imbriques


Bien quil soit permis de ne pas mettre daccolades pour des instructions if composes dune seule instruction et que lon puisse ainsi imbriquer des instructions if, cela peut entraner une grosse confusion. Le code suivant, par exemple, est parfaitement autoris en C++ mais semble bien confus :
if (x > y) if (x < z) x = y; else x = z; else y = x; // // // // // // // si x > y et x < z x reoit la valeur de y sinon, si x nest pas inferieur z definir x sur la valeur de z sinon, si x nest pas superieur y definir y sur la valeur de x

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

4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18:

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

Ce programme produit par exemple le rsultat suivant :


Entrez un nombre infrieur 10 ou suprieur 100: 30 Infrieur 10, Merci!

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++

Listing 4.8 : Utilisation correcte daccolades dans une instruction if


1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: // Utilisation correcte daccolades // dans les instructions if imbriques #include <iostream> 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 // Bogue corrig ! std::cout << "Infrieur 10, Merci!\n"; return 0; }

Ce programme ragit maintenant correctement, par exemple :


Entrez un nombre infrieur 10 ou suprieur 100: 34 Entrez un nombre infrieur 10 ou suprieur 100: 8 Infrieur 10, Merci! Entrez un nombre infrieur 10 ou suprieur 100: 101 Suprieur 100, Merci!

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

Oprateur AND OR NOT

Symbole && (et) || (ou) ! (non)

Exemple expression1 && expression2 expression1 || expression2 !expression

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

ne sera vraie que si x nest pas gal 5, ce qui revient crire :


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.

Priorit des oprateurs relationnels


Comme pour toutes les expressions C++, lutilisation des oprateurs relationnels et des oprateurs logiques, renvoie une valeur qui est, ici, true ou false. Pour connatre lordre dans lequel ils sont excuts, reportez-vous lAnnexe C. Voici un exemple :
if ( x > 5 && y > 5 || z > 5)

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.

Oprateur conditionnel (ternaire)


Loprateur conditionnel (?:) est le seul oprateur C++ ternaire (il comprend trois termes). Il utilise trois expressions et renvoie une valeur :
(expression1)? (expression2): (expression3)

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

21: 22: 23: 24: 25: 26: 27: 28: 29:

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

Ce programme produit le rsultat suivant :


Entrez deux nombres. Premier: 5 Second: 8 Aprs le test if, z: 8 Aprs le test conditionnel, z: 8

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.

Testez vos connaissances


1. Quest-ce quune expression ? 2. Est-ce que x = 5+7 est une expression ? Quelle est sa valeur ? 3. Quelle est la valeur de 201 / 4 ? 4. Quelle est la valeur de 201% 4 ? 5. Si monAge, a et b sont des variables entires, quel est le rsultat de ces expressions ?
monAge = 39; a = monAge++; b = ++monAge;

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. Saisissez, compilez et lancez le programme prcdent. Obtenez-vous le rsultat attendu ? Pourquoi ?

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++

Quest-ce quune fonction ?


Par nature, une fonction est un sous-programme qui agit sur des donnes et renvoie une valeur. Tout programme C++ comprend au moins une fonction : la fonction main(), qui est la fonction principale qui sexcute automatiquement et qui peut appeler dautres fonctions, qui peuvent elles-mmes en appeler dautres. Ces fonctions ne faisant pas partie dun objet, elles sont appeles "globales", cest--dire quelles sont accessibles de nimporte quel endroit du programme. Les fonctions abordes dans ce chapitre sont les fonctions globales, sauf mention contraire. Chaque fonction porte un nom particulier qui lidentie dans le programme. Lorsque ce nom est rencontr, le programme se place directement au dbut de la fonction cest que lon dsigne par le terme d appel de fonction. Lorsque la fonction se termine (en rencontrant une instruction return ou laccolade fermante de la fonction), le programme poursuit son excution la ligne qui suit lappel de la fonction (voir Figure 5.1).
Figure 5.1 Aprs lexcution dune fonction, le programme revient linstruction qui suit lappel.
Programme main() { instruction; fonction1(); instruction; fonction2(); instruction; fonction4(); instruction; } Fonction1 Fonction3 return; Fonction2 instruction; fonction3(); return; Fonction4 return;

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

Valeurs renvoyes, paramtres formels et effectifs


Vous lavez vu au Chapitre 2, les fonctions peuvent recevoir des valeurs et renvoyer une valeur. Lorsquelle est appele, une fonction effectue une certaine tche puis renvoie un rsultat lappelant. Cette valeur sappelle la valeur renvoye et son type doit tre dclar. Lorsque vous crivez :
int maFonction();

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.

Dclaration et dnition de fonctions


Pour tre exploitable dans le programme, une fonction doit tre dclare puis dnie. La dclaration indique au compilateur le nom de la fonction, le type de la valeur quelle renvoie et les paramtres attendus par la fonction. La dnition dcrit au compilateur le fonctionnement de la fonction. Il est impossible dappeler une fonction qui na pas au pralable t dclare. La dclaration dune fonction est dsigne sous le nom de prototype.

100

Le langage C++

Il existe trois manires de dclarer une fonction. Vous pouvez :

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

(int longueur, int largeur) ; paramtres point-virgule

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

mot cl valeur renvoye accolade fermante

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

24: 25: 26: 27: 28: 29: 30: 31:

cout << " mtres carrs\n\n"; return 0; } int Surface(int longue, int large) { return longue * large; }

Ce programme produit le rsultat suivant :


Largeur de votre cour? 10 Longueur de votre cour? 200 La surface de votre cour est de 2000 mtres carrs

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

Excution des fonctions


Lorsque vous appelez une fonction, son excution commence avec linstruction situe immdiatement aprs laccolade ouvrante ({). Cette excution peut bien sr contenir des parties conditionnelles (les instructions if et associes seront prsentes plus en dtail au

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.

Porte des variables


Une variable a une porte, qui dtermine sa dure de vie dans le programme et les endroits qui y ont accs. La porte des variables dclares dans un bloc se limite celui-ci ; elles ne peuvent tre lues que dans ce bloc et disparaissent une fois quil a t excut. En revanche, les variables globales ont une porte globale, cest--dire quelles sont disponibles depuis nimporte quel point du programme.

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

Quelques exemples de rsultats :


Entrez la temprature en degrs Fahrenheit: 212 Temprature en degrs Celsius: 100 Entrez la temprature en degrs Fahrenheit: 32 Temprature en degrs Celsius: 0 Entrez la temprature en degrs Fahrenheit: 85 Temprature en degrs Celsius: 29.4444

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.

Variables locales dans des blocs


Vous pouvez dnir des variables tout endroit dune fonction, pas seulement au dbut. Lorsquelle est dnie dans un bloc dinstructions, la porte dune variable locale est limite ce bloc. Pour illustrer ces propos, nous vous proposons le listing suivant : Listing 5.3 : Variables visibles au niveau du bloc
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: 32a: 33: // Listing5.3 - dmonstration de la visibilit de // variables au niveau du bloc #include <iostream> void maFct(); int main() { int x = 5; std::cout << "\nDans main(), x vaut: " << x; maFct(); std::cout << "\nDe retour dans main, x vaut: " << x; return 0; } void maFct() { int x = 8; std::cout << "\nDans maFct, variable locale x: " << x << std::endl; { std::cout << "\nDans le bloc de maFct, x vaut: " << x; int x = 9; std::cout << "\nVariable x trs locale: " << x; } std::cout << "\nHors du bloc, dans maFct, x: " << x << std::endl; }

Chapitre 5

Fonctions

107

Ce programme produit le rsultat suivant :


Dans main(), x vaut: 5 Dans maFct, variable locale x : 8 Dans le bloc de maFct, x vaut: 8 Variable x trs locale: 9 Hors du bloc, dans maFct, x: 8 De retour dans main, x vaut: 5

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 !

Les paramtres sont des variables locales


Les paramtres passs une fonction sont locaux cette fonction. Les modications qui leur sont apportes naffectent pas les valeurs dans la fonction appelante. Cest ce que lon appelle un passage par valeur, qui revient crer une copie locale de chaque paramtre dans la fonction. Ces copies locales sont considres comme nimporte quelle autre variable locale. Un exemple est propos dans le Listing 5.4.

108

Le langage C++

Listing 5.4 : Passage par valeur


1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 11a: 12: 13: 13a: 14: 15: 16: 17: 18: 19: 20: 21: 21a: 22: 23: 24: 25: 26: 27: 27a: 28: // Listing5.4 - Dmonstration de passage PAR VALEUR #include <iostream> using namespace std; void swap(int x, int y); int main() { int x = 5, y = 10; 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; } void swap (int x, int y) { int temp; cout << "Dans swap. Avant change, x: " << x << ", y: " << y << endl; temp = x; x = y; y = temp; cout << "Dans swap. Aprs change, x: " << x << ", y: " << y << endl; }

Ce programme produit le rsultat suivant :


Dans Dans Dans Dans main(). Avant change. x: 5, y:10 swap. Avant change, x: 5, y: 10 swap. Aprs change, x: 10, y: 5 main(). Aprs change. x: 5, y:10

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;

// prototype // variables globales

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 produit le rsultat suivant :


x dans main: 5 y dans main: 7 x dans maFonction: 5 y dans maFonction: 10 De retour de maFonction! x dans main: 5 y dans main: 7

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

Variables globales : quelques conseils


Bien que les variables globales soient autorises par le langage, on ne les utilise quasiment jamais en C++. En C, elles sont dangereuses mais ncessaires lorsque lon souhaite partager des donnes entre plusieurs fonctions sans les passer constamment en paramtre, notamment lorsquil sagit simplement de les transmettre dune fonction lautre. Les variables globales sont dangereuses, car ce sont des donnes partages et parce quune fonction peut modier une variable globale sans que les autres fonctions ne le voient, ce qui peut produire des bogues trs difciles dtecter. En C++, il est possible de remplacer les variables globales par des variables membres statiques. Pour en savoir plus, reportez-vous au Chapitre 15.

Instructions des fonctions


En thorie, les instructions dans le corps dune fonction ne sont limites ni en nombre ni en type. Bien que vous nayez pas le droit de dnir une fonction lintrieur dune fonction, vous pouvez appeler une fonction, ce qui est courant dans la fonction principale main(). Les fonctions peuvent sappeler elles-mmes, ce que nous verrons bientt dans la section consacre la rcursivit. Bien que sa taille ne soit pas limite, une fonction C++ bien conue est de taille raisonnable. Certains programmeurs limitent la taille de chaque fonction un cran, ce qui leur permet de visualiser toutes les instructions simultanment. Cette rgle nest pas stricte, mais il est vrai quune fonction courte est plus facile comprendre et mettre jour. Une fonction ne devrait tre consacre qu une seule tche simple et facile comprendre. Une fonction longue et complexe peut toujours se dcomposer en fonctions courtes et simples.

Retour sur les paramtres des fonctions


Toute expression valide de C++ peut tre un paramtre de fonction, y compris les constantes, les expressions mathmatiques et logiques et les autres fonctions qui renvoient une valeur. Le point important est que le type de la valeur renvoye par lexpression doit correspondre au type du paramtre attendu par la fonction. Il est mme possible dutiliser la valeur renvoye par une fonction comme paramtre dune autre fonction. Toutefois, cela peut rendre le code correspondant difcile lire et dboguer.

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

cube(), qui renvoient

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

Le programme est plus explicite !


ntion Atte

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.

Retour sur les valeurs renvoyes


Les fonctions renvoient une valeur ou ne renvoient rien, auquel cas le type du rsultat est void. Pour renvoyer une valeur, il suft dentrer le mot-cl return suivi de la valeur rcuprer. Cette valeur peut tre, elle-mme, une expression qui produit une valeur :
return 5; return (x > 5); return (Fonction());

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

Ce programme produit le rsultat suivant :


Entrez un nombre entre 0 et 10000 multiplier par 2: 9000 Avant dappeler la fonction Doubleur()... Valeur entre: 9000, Valeur double: 0 De retour de la fonction Doubleur() ... Valeur entre: 9000, Valeur double: 18000 Entrez un nombre entre 0 et 10000 multiplier par 2: 11000 Avant dappeler la fonction Doubleur()... Valeur entre: 11000, Valeur double: 0 De retour de la fonction Doubleur()... Valeur entre: 11000, Valeur double: -1

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.

Paramtres par dfaut


chaque paramtre dclar dans un prototype ou dans une dnition correspond une valeur que le programme doit passer la fonction. Bien entendu, le type de valeur doit tre identique celui qui a t dclar. Exemple :
long maFonction(int);

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

26: 27: 28: 29:

{ return (longueur * largeur * hauteur); }

Ce programme produit le rsultat suivant :


Le premire volume est gal : 10000 Le deuxime volume est gal : 5000 Le troisime volume est gal: 2500

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

locales dont la porte se limite la fonction appele.


Se souvenir que la modication dune varia-

sil nen existe pas pour le paramtre suivant.


Oublier quun paramtre pass par valeur ne

ble globale dans une fonction est rpercute dans toutes les fonctions.

modie pas la variable correspondante dans la fonction appelante.

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

Grce la surcharge, il sufra de dclarer la mme fonction, de la faon suivante :


int Double(int); long Double(long);

Chapitre 5

Fonctions

119

float Double(float); double Double(double);

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

Ce programme produit le rsultat suivant :


monInt: 6500 monLong: 65000 monFloat: 6.5 monDouble: 6.5e+20 Dans Double(int) Dans Double(long) Dans Double(float) Dans Double(double) DoubleInt: 13000 DoubleLong: 130000 DoubleFloat: 13 DoubleDouble: 1.3e+21

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 !

Pour en savoir plus sur les fonctions


Les fonctions tant un aspect crucial de la programmation, certains points particuliers mritent dtre connus pour rsoudre des problmes inhabituels. Utilises correctement, les fonctions en ligne permettent dextraire la dernire goutte de performances dun programme. La rcursivit, quant elle, fait partie de ces merveilles sotriques de la programmation et permet de rsoudre aisment des problmes qui pourraient pas tre traits aussi simplement sans elle.

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

Listing 5.9 : Exemple de fonction en ligne


1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: // Listing5.9 - Exemple de fonction en ligne #include <iostream> inline int Double(int); int main() { int cible; using std::cout; using std::cin; using std::endl; cout << "Entrez un nombre: "; cin >> cible; cout << "\n"; cible = Double(cible); cout << "Valeur cible: " << cible << endl; cible = Double(target); cout << "Valeur cible: " << cible << endl; cible = Double(target); cout << "Valeur cible: " << cible << endl;

Chapitre 5

Fonctions

123

25: 26: 27: 28: 29: 30: 31:

return 0; } int Double(int cible) { return 2*cible; }

Ce programme produit le rsultat suivant :


Entrez un nombre: 20 Valeur cible: 40 Valeur cible: 80 Valeur cible: 160

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;

chaque fois que le programme va rencontrer :


cible = Double(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 !

Listing 5.10 : La rcursivit applique la suite de Fibonacci


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: // Exemple de rcursivit #include <iostream> int fib (int n); int main() { int n, reponse; std::cout << "Entrez le nombre a trouver: "; std::cin >> n; std::cout << "\n\n"; reponse = fib(n); std::cout << reponse << " est le " << n; std::cout << "e nombre dans la suite\n"; return 0; } int fib (int n) { std::cout << "Traitement fib(" << n << ")... "; if (n < 3 ) { std::cout << "Renvoie 1!\n"; return (1); } else { std::cout << "Appel de fib(" << n-2 << ") "; std::cout << "et fib(" << n-1 << ").\n"; return( fib(n-2)+ fib(n-1)); } }

126

Le langage C++

Ce programme produit le rsultat suivant :


Entrez le rang a trouver: 6 Traitement fib(6)... Appel de fib(4) et fib(5). Traitement fib(4)... Appel de fib(2) et fib(3). Traitement fib(2)... Renvoie 1! Traitement fib(3)... Appel de fib(1) et fib(2). Traitement fib(1)... Renvoie 1! Traitement fib(2)... Renvoie 1! Traitement fib(5)... Appel de fib(3) et fib(4). Traitement fib(3)... Appel de fib(1) et fib(2). Traitement fib(1)... Renvoie 1! Traitement fib(2)... Renvoie 1! Traitement fib(4)... Appel de fib(2) et fib(3). Traitement fib(2)... Renvoie 1! Traitement fib(3)... Appel de fib(1) et fib(2). Traitement fib(1)... Renvoie 1! Traitement fib(2)... Renvoie 1! 8 est le 6e nombre dans la suite

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

Figure 5.4 Utilisation de la rcursivit.

int main() { int x = fib(6); } fib(4)

fib(6) return fib(4) + fib(5)

fib(5) return fib(3) + fib(4)

fib(3) return fib(1) + fib(2)

fib(4) return fib(2) + fib(3)

return fib(2) + fib(3)

fib(2) return 1

fib(3) return fib(1) + fib(2) fib(1) return 1

fib(2) return 1

fib(3) return fib(1) + fib(2)

fib(1) return 1

fib(2) return 1

fib(2) return 1

fib(1) return 1

fib(2) return 1

Figure 5.5 Retour aprs les appels rcursifs.

int main() { int x = fib(6); }

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)

return fib(1) + fib(2)

return fib(2) + fib(3)

1 fib(2) return 1 fib(3)

2
fib(2) 1 return 1 1

1 fib(3)

return fib(1) + fib(2) fib(1) 1 1 return 1

return fib(1) + fib(2)

1 fib(2) return 1 fib(1) return 1 fib(2) return 1

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

Principe des fonctions


Lors de lappel dune fonction, le programme se branche directement dans le code correspondant, les paramtres sont passs et le corps de la fonction sexcute. Une fois termine, la fonction renvoie une valeur (sauf si vous avez indiqu le mot-cl void) et rend la main la fonction appelante. Comment cela est-il possible ? Comment le code sait-il o se brancher ? O sont stockes les variables qui sont transmises la fonction ? Quarrive-t-il aux variables qui sont dnies dans la fonction ? Comment la valeur renvoye est-elle retransmise au programme ? Comment le programme sait-il o il doit reprendre ? La plupart des manuels dinitiation la programmation nabordent pas ces questions et le dveloppement garde ses secrets. Pour comprendre comment cela se passe, immergeonsnous dans la mmoire de lordinateur.

Les niveaux dabstraction de la programmation


Au dbut de sa carrire, un programmeur manque de mthodologie et gre difcilement les niveaux dabstraction intellectuelle du dveloppement. Bien entendu, les ordinateurs sont des machines lectroniques totalement dnues dintelligence. Ils ne "connaissent" rien des fentres et des menus, ils ne savent rien des programmes ou des instructions et ne connaissent mme pas les zros et les uns, puisquils fonctionnent par impulsions lectriques au niveau des circuits imprims... ce qui est aussi intrinsquement une abstraction.

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

Figure 5.8 Le pointeur de pile.


laVariable

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

Figure 5.9 Dplacement du pointeur de pile.

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

La pile et les fonctions


Ce qui suit est une approximation de ce qui se passe lorsque votre programme se branche une fonction. Les dtails peuvent diffrer en fonction du systme dexploitation et du compilateur. 1. Ladresse dans le pointeur dinstruction devient celle qui suit lappel de la fonction. Elle est place sur la pile, ce sera ladresse de retour aprs la n de la fonction. 2. La pile est prpare pour recevoir le type de la valeur renvoye : si ce type est un int et quun int occupe deux octets sur lordinateur, deux octets vides sont ajouts au sommet de la pile, mais ne sont pas inititaliss (cela signie que ce quils contiennent y restera jusqu ce que la variable locale soit initialise). 3. Ladresse de la fonction appele, conserve dans une zone spciale de la mmoire, est charge dans le pointeur dinstruction. La prochaine instruction excute sera donc la premire instruction de la fonction appele.

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

Testez vos connaissances


1. Quelle diffrence y a-t-il entre un prototype de fonction et une dnition de fonction ? 2. Les noms de paramtres doivent-ils tre identiques dans le prototype, la dnition et lappel la fonction ? 3. Comment dclarer une fonction qui ne renvoie pas de valeur ? 4. En labsence de valeur renvoye, quel sera le type du rsultat ? 5. Quest-ce quune variable locale ? 6. Quest-ce que la porte ? 7. Que signie le mot "rcursivit" ? 8. Quand doit-on utiliser des variables globales ? 9. Quest-ce que la surcharge de fonction ?

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++

3. CHERCHEZ LERREUR : cette fonction est bogue.


#include <iostream> void maFct(unsigned short int x); int main() { unsigned short int x, y; y = maFct(int); std::cout << "x : " << x << " y : " << y << "\n"; return 0; } void maFct(unsigned short int x) { return (4*x); }

4. CHERCHEZ LERREUR : cette fonction est galement bogue.


#include <iostream> int maFct(unsigned short int x); int main() { unsigned short int x, y; x = 7; y = maFct(x); std::cout << "x : " << x << " y : " << y << "\n"; return 0; } int maFct(unsigned short int x); { return (4*x); }

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

C++ est-il orient objet ?


une certaine poque, C le prdcesseur de C++ tait le langage de programmation le plus populaire pour le dveloppement de logiciels professionnels. Cest lui qui a t utilis pour crer les systmes dexploitation (comme Unix), pour la programmation en temps rel (contrle des machines, des priphriques et de llectronique). Ce nest que plus tard quil a t employ comme langage de programmation conventionnel. Il avait pour but de simplier la programmation en ce qui concerne laspect matriel.

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

Programmation oriente objet

137

Cration de nouveaux types


Les programmes ont gnralement pour but de rsoudre des problmes de la vie courante, comme grer la liste des employs ou simuler le fonctionnement dun systme de chauffage. Bien quil soit possible de rsoudre des problmes complexes uniquement avec des nombres et des caractres, il est bien plus pratique de pouvoir crer des reprsentations des objets que vous voulez traiter texte. En dautres termes, il est plus facile de simuler le fonctionnement dun systme de chauffage si vous pouvez crer des variables qui reprsentent les pices, les capteurs, les thermostats et les radiateurs. Plus les variables seront proches de la ralit, plus le programme sera simple crire. Au cours des chapitres prcdents, vous avez tudi un certain nombre de types de variables, dont les entiers non signs et les caractres. Le type dune variable fournit des informations prcieuses sur celle-ci. Si, par exemple, vous dclarez Hauteur et Largeur comme des entiers courts non signs (unsigned short), vous savez quelles pourront accueillir une valeur comprise entre 0 et 65 535, en supposant quun entier non sign soit cod sur deux octets sur votre plate-forme. Si vous essayez dy copier autre chose, le programme dtectera une erreur. Vous ne pouvez donc pas stocker votre nom dans ces variables et vous ne devriez mme pas essayer de le faire. Le type dune variable nous renseigne sur :

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

Prsentation des classes et des membres


La cration dun type passe, en C++, par la dclaration dune classe. Une classe est simplement un ensemble de variables, souvent de types diffrents, associes un ensemble de fonctions. Une voiture, par exemple, est un ensemble constitu de cinq roues, dun moteur, dune carrosserie avec des portes, de siges, etc. Avec votre auto, vous pouvez avancer, reculer, tourner, vous arrter, vous garer, etc. Une classe permet dencapsuler les diffrents lments et les fonctions dans un unique ensemble appel objet. Lencapsulation de tous les lments en une classe prsente de nombreux avantages pour le programmeur. Tout est regroup au mme endroit, ce qui facilite les actions sur les donnes (accs, copie, etc.). Les clients de la classe (cest--dire les parties du programme qui utilisent la classe) peuvent utiliser les objets sans se soucier ni de leur structure, ni de leur mode de fonctionnement. Une classe peut contenir nimporte quelle combinaison de types de variables, mais galement dautres types de classes. Les variables dune classe sont gnralement appeles variables membres ou donnes membres. Une classe Voiture, par exemple, pourrait avoir des variables membres reprsentant les siges, le type dautoradio, les pneus, etc. Une classe peut aussi contenir des fonctions appeles fonctions membres, ou mthodes. Les fonctions membres font partie de la classe au mme titre que les variables membres. Elles dterminent les actions pouvant tre ralises par la classe. Gnralement, les fonctions membres de la classe manipulent ses variables membres. Par exemple, les mthodes de la classe Voiture, par exemple, pourraient inclure les fonctions Demarrer() et Freiner(). Une classe Chat pourrait contenir des donnes membres correspondant lge et au poids de lanimal et ses mthodes pourraient inclure les fonctions Dormir(), Miauler() et ChasserSouris().

Chapitre 6

Programmation oriente objet

139

Dclaration dune classe


La dclaration dune classe donne au compilateur des informations sur la classe. Pour dclarer une classe, vous devez utiliser le mot-cl class suivi du nom de la classe, dune accolade ouvrante et de la liste des donnes membres et des mthodes associes. Pour clore la dclaration, utilisez une accolade fermante suivie dun point-virgule. Voici par exemple comment dclarer la classe Chat :
class Chat { public: unsigned int sonAge; unsigned int sonPoids; void Miauler(); };

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.

Conventions dattribution de noms


Lattribution dun nom chaque variable, fonction membre et classe fait partie de la tche dun programmeur. Vous avez appris au Chapitre 3 que les noms de variables et de constantes devaient tre signicatifs. Chat, Rectangle et Employe sont de bons noms de classes, par exemple. Miauler() et StopperMoteur() sont galement de bons noms de mthodes puisquils indiquent clairement le rle de chaque fonction. Nombre de programmeurs choisissent de prxer le nom de leurs variables membres avec son (ou its) comme dans sonAge, sonPoids ou saVitesse. Cela permet de distinguez plus facilement les variables membres de celles qui ne le sont pas. Il est galement possible dutiliser dautres prxes, comme monAge, monPoids ou maVitesse, ou tout simplement la lettre "m" (comme membre), avec ventuellement un caractre de soulignement (_), par exemple mAge ou m_age. Certains choisissent de prxer leur nom de classe avec une lettre particulire, comme cChat ou cPersonne et dautres prfrent nutiliser que des majuscules ou que des minuscules. Dans ce livre, les noms de classes commenceront tous par une majuscule comme dans Chat ou Personne.

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

Dnition dun objet


Aprs avoir dclar une classe, vous pouvez lutiliser comme nouveau type pour dclarer des variables de ce type. Il sagit dune opration aussi simple que la dnition dune variable entire :
unsigned int PoidsBrut; Chat Frisky; // dfinition dun entier non sign // dfinition dun chat

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

Programmation oriente objet

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.

Accs aux membres dune classe


Aprs avoir dni un objet rel Chat (Frisky, par exemple), vous pouvez accder ses membres laide de loprateur point (.). Pour affecter 5 la variable membre sonPoids de Frisky, on utilise donc lexpression suivante :
Frisky.sonPoids = 5;

Pour appeler la fonction Miauler(), il suft de taper :


Frisky.Miauler();

Pour utiliser une mthode de la classe, il suft de lappeler. Dans lexemple ci-dessus, vous appelez la fonction Miauler() de Frisky.

Affectez des valeurs aux objets, pas aux classes


En C++, vous ne pouvez pas affecter des valeurs des types. Lattribution de valeurs concerne les variables. Par exemple, vous navez pas le droit dcrire :
int = 5; // erreur

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;

Lexpression suivante produit galement une erreur :


Chat.sonAge = 5; // erreur

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++

Les membres non dclars nexistent pas


Essayez lexprience suivante : montrez votre chat un enfant de trois ans et dites-lui "il sappelle Frisky et il connat un tour de magie : Frisky, aboie !". Lenfant gloussera et vous rpondra : "mais non, idiot, les chats naboient pas". Si vous crivez :
Chat Frisky; Frisky.Aboyer(); // Cration dun chat nomm Frisky // Demande Frisky daboyer

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

membres et aux mthodes de la classe.

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.

Accs priv et accs public


Une dclaration de classe fait galement appel des mots-cls supplmentaires. private et public font partie des plus importants. Ces deux mots-cls sont utiliss avec les membres dune classe, que ce soient des donnes ou des mthodes. Les membres privs ne peuvent tre accdes que depuis les mthodes de la classe elle-mme alors que les membres publics peuvent tre accds via nimporte quel objet de la classe. Cette distinction est trs importante, mais souvent source de confusion chez le dveloppeur dbutant. Par dfaut, tous les membres dune classe sont privs. Pour clarier les choses, reprenons lexemple dcrit plus haut :
class Chat {

Chapitre 6

Programmation oriente objet

143

unsigned int sonAge; unsigned int sonPoids; void Miauler(); };

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

Ce programme produit le rsultat suivant :


Frisky est un chat qui a 5 ans.

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

Programmation oriente objet

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

Rendre prives les donnes membres


Une rgle gnrale de la conception est que les donnes membres dune classe doivent rester prives. Bien entendu, si toutes les donnes membres deviennent prives, vous risquez de vous demander comment accder aux informations sur la classe. Si sonAge est priv, par exemple, comment dnir ou rcuprer lge dun objet Chat ? Pour accder aux donnes prives dune classe, vous devez crer des fonctions publiques, appeles mthodes daccs. Ces mthodes sont des fonctions membres qui peuvent tre appeles dautres endroits de votre programme pour accder aux donnes prives. Une mthode daccs publique est une fonction membre de la classe capable de lire (rcuprer) la valeur dune variable membre prive de la classe et/ou de lui affecter une valeur. Pourquoi faire intervenir un niveau supplmentaire daccs ? Pourquoi ajouter dautres fonctions alors quil est plus ais et plus simple dutiliser directement des donnes ? Pourquoi passer par des mthodes daccs ? La rponse ces questions est que les mthodes daccs permettent de dissocier la faon dont les donnes sont stockes et la manire dont elles sont utilises. En obligeant le code client passer par des mthodes daccs, le concepteur de la classe peut ensuite modier le mode de stockage des donnes membres (et donc galement les mthodes daccs) sans que cela ne perturbe le code client. Une fonction qui accde directement llment sonAge pour connatre lge dun chat devra tre rcrite si vous dcidez, plus tard, de modier le mode de stockage de cette information. En obligeant la fonction cliente appeler LireAge() pour obtenir lge de notre flin, celle-ci na pas besoin de connatre le type de la variable qui contient lge, ni de savoir comment il est calcul. Il va sans dire que cette technique simplie normment la maintenance des programmes. En outre, le code a une dure de vie suprieure car dventuelles modications de conception naffecteront plus les programmes qui lutilisent. Les mthodes daccs peuvent galement contenir des traitements supplmentaires. Sil y a peu de chances, par exemple, que lge du chat soit suprieur 100, ou que son poids

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

Programmation oriente objet

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 Sa syntaxe se prsente ainsi :


class nom_de_la_classe { // mots cls des contrles daccs // variables et mthodes membres };

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-

ques si ce nest pas ncessaire.


Essayer daccder des variables membres

depuis des fonctions membres dans une classe.

prives de lextrieur dune classe.

Implmentations des mthodes de la classe


Comme vous venez de le voir, une fonction daccs fournit une interface publique aux donnes membres prives dune classe. Comme nimporte quelle mthode de la classe, elle doit tre associ du code. Son implmentation est appele dnition de la fonction. Cette dnition commence de la mme manire que celle dune fonction ordinaire. On indique dabord le type du rsultat de la fonction, ou void si elle ne renvoie rien. Il est suivi du nom de la classe, puis de deux caractres deux-points, du nom de la fonction et de ses paramtres. Le Listing 6.3 montre comment dclarer la classe Chat et implmenter ses mthodes daccs et une mthode membre gnrale. Listing 6.3 : Implmentation des mthodes dune classe simple
1: 2: 3: // Dclaration dune classe et // dfinition des mthodes de la classe, #include <iostream> // pour cout

Chapitre 6

Programmation oriente objet

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++

48: 49: 50: 51: 52:

std::cout << "Frisky est un chat qui a "; std::cout << Frisky.LireAge() << " ans.\n"; Frisky.Miauler(); return 0; }

Ce programme produit le rsultat suivant :


Miaou. Frisky est un chat qui a 5 ans. Miaou.

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

Programmation oriente objet

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.

Ajout de constructeurs et de destructeurs


Pour dnir une variable entire, vous pouvez procder de deux faons. Vous pouvez dnir la variable, puis lui affecter ultrieurement une valeur dans le programme. Exemple :
int Poids; ... Poids = 7; // dfinition dune variable // autres instructions // affectation dune valeur

Il est galement possible de dnir une variable et de linitialiser aussitt. Exemple :


int Poids = 7; // dfinition et initialisation 7

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

Constructeurs et destructeurs par dfaut


Il existe diffrents types de constructeurs ; certains acceptent des paramtres, dautres non. Un constructeur sans paramtre est appel constructeur par dfaut. Il nexiste par contre quune sorte de destructeur qui, comme le constructeur par dfaut, ne prend aucun paramtre. Si vous ne crez ni constructeur ni destructeur, le compilateur vous en fournit automatiquement. Ce constructeur et ce destructeur par dfaut crs par le compilateur ne prennent pas de paramtres et, en fait, ne font rien. Si vous souhaitez quils fassent quelque chose, vous devez crer vos propres constructeur ou destructeur par dfaut.

Appeler le constructeur par dfaut


quoi peut bien servir un constructeur qui ne fait rien ? Cest en partie une question de forme. Tous les objets doivent tre "construits" puis "dtruits" et ces fonctions "inoprantes" sont appeles dans le cadre du processus de construction et de destruction. Pour dclarer un objet sans passer de paramtres, comme ici :
Chat Felix; // Felix ne prend pas de paramtres

vous devez avoir un constructeur de la forme :


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

Programmation oriente objet

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

// dbut de section prive // variable membre

// destructeur, ne fait rien

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

Programmation oriente objet

155

60: 61: 62: 63: 64: 65: 66: }

std::cout << Frisky.LireAge() << " ans.\n"; Frisky.Miauler(); Frisky.DefAge(7); std::cout << "Maintenant Frisky a "; std::cout << Frisky.LireAge() << " ans.\n"; return 0;

Ce programme produit le rsultat suivant :


Miaou. Frisky est un chat qui a 5 ans. Miaou. Maintenant Frisky a 7 ans.

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-

teurs et aux destructeurs.


Attribuer des paramtres aux destructeurs.

constructeur.

156

Le langage C++

Inclure des fonctions membres const


Vous avez dj employ le mot-cl const pour dclarer des variables qui ne changeront pas. Vous pouvez galement lutiliser avec les fonctions membres dune classe. Lorsque lon dclare une mthode const, on sengage ce que cette mthode ne modie jamais aucun membre de la classe. Pour dclarer une mthode const, il suft dintercaler le mot-cl entre la liste des paramtres et le point-virgule qui termine la dclaration de la mthode. Par exemple :
void Fonction() const;

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

Programmation oriente objet

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

La compilation du Listing 6.5 chouera !

Listing 6.5 : Programme contenant plusieurs erreurs de compilation


1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: // Dmonstration derreurs de compilation // La compilation chouera! #include <iostream> // pour cout class Chat { public: Chat(int initialAge); ~Chat(); int LireAge() const; void DefAge (int age); void Miauler(); private: int sonAge; };

// fonction daccs constante

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

Programmation oriente objet

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.

O placer les dclarations de classes et les dnitions de mthodes ?


Toute fonction dclare dans une classe doit possder une dnition, que lon appelle galement implmentation de la fonction. Comme pour les autres fonctions, la dnition dune mthode de classe comprend un en-tte de fonction et un corps de fonction.

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

Programmation oriente objet

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++

Listing 6.6 : Dclaration de la classe Chat dans le chier Chat.hpp


1: 2: 3: 4: 5: 6: 6a: 7: 8: 9: 10: 11: 12: #include <iostream> class Chat { public: Chat (int ageInitial); ~Chat(); // les trois fonctions suivantes sont en ligne int LireAge() const { return sonAge;} void DefAge (int age) { sonAge = age;} void Miauler() const { std::cout << "Miaou.\n";} private: int sonAge; };

Listing 6.7 : Implmentation de la classe Chat dans le chier Chat.CPP


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: // Dmonstration de fonctions en ligne // et inclusion de fichiers en-tte // Noubliez pas dinclure le fichier en-tte! #include "Chat.hpp"

Chat::Chat(int ageInitial) { sonAge = ageInitial; } Chat::~Chat() { }

// constructeur

// destructeur, ne fait rien

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

Programmation oriente objet

163

Ce programme produit le rsultat suivant :


Miaou. Frisky est un chat qui a 5 ans. Miaou. Maintenant, Frisky a 7 ans.

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++

Listing 6.8 : Dclaration complte dune classe


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: 43: 44: 45: // dbut de Rectangle.hpp #include <iostream> class Point // contient les coordonnes x,y { // pas de constructeur, on utilise celui par dfaut public: void SetX(int x) { sonX = x; } void SetY(int y) { sonY = y; } int GetX()const { return sonX;} int GetY()const { return sonY;} private: int sonX; int sonY; }; // fin de la dclaration de la classe Point

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

int GetSurface() const; private:

Chapitre 6

Programmation oriente objet

165

46: 47: 48: 49: 50: 51: 52: 53: 54: 55:

Point Point Point Point int int int int

sonHautGauche; sonHautDroite; sonBasGauche; sonBasDroite; sonHaut; saGauche; sonBas; saDroite;

}; // fin de Rectangle.hpp

Listing 6.9 : Rect.cpp


1: 2: 3: 3a: 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: // dbut de Rect.cpp #include "Rectangle.hpp" Rectangle::Rectangle(int haut, int gauche, int bas, int droite) { sonHaut = haut; saGauche = gauche; sonBas = bas; sonBas = droite; sonHautGauche.SetX(gauche); sonHautGauche.SetY(haut); sonHautDroite.SetX(droite); sonHautDroite.SetY(haut); sonBasGauche.SetX(gauche); sonBasGauche.SetY(bas); sonBasDroite.SetX(droite); sonBasDroite.SetY(bas); }

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

Ce programme produit le rsultat suivant :


Surface: 3000 Coordonne X coin suprieur gauche: 20

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

Programmation oriente objet

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-

(.hpp) et les fonctions membres dans le chier source du programme (.cpp).


Utiliser le mot-cl const le plus souvent

ment des classes.

possible.

Chapitre 6

Programmation oriente objet

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.

Testez vos connaissances


1. Quest-ce que loprateur point (.) ? quoi sert-il ? 2. Qui rserve de lespace en mmoire vive, la dclaration ou la dnition ? 3. La dclaration dune classe correspond-elle son interface ou son implmentation ? 4. Quelle est la diffrence entre des donnes membres prives et des donnes membres publiques ? 5. Les fonctions membres peuvent-elles tre prives ? 6. Les donnes membres peuvent-elles tre publiques ? 7. Si vous dclarez deux objets Chat, leurs donnes membres sonAge peuvent-elles avoir des contenus diffrents ? 8. Les dclarations de classe se terminent-elles par un point-virgule ? Et les dnitions de mthodes de la classe ? 9. Quel serait len-tte de la fonction Miaou appartenant la classe Chat, ne prenant en charge aucun paramtre et renvoyant void ? 10. Quelle est la fonction qui permet dinitialiser une classe ?

Chapitre 6

Programmation oriente objet

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

8. CHERCHEZ LERREUR : il y a trois erreurs dans ce code. O se cachent-elles ?


class TV { public: void SetStation(int Station); int GetStation() const; private: int saStation; };

172

Le langage C++

int main() { TV maTele; maTele.saStation = 9; TV.SetStation(10); TV monAutreTele(2); }

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++

Les origines des boucles : goto


Aux dbuts de linformatique, les programmes manquaient cruellement de convivialit. Ils taient courts et directs. Les boucles taient formes dune tiquette, de quelques instructions puis dun saut vers cette tiquette. En C++, une tiquette est simplement un nom suivi du signe deux-points ( :) plac gauche dune instruction. Pour raliser un saut, il suft de taper le mot-cl goto suivi du nom de ltiquette, comme le montre le Listing 7.1. Listing 7.1 : Boucle laide du mot-cl goto
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 15a: 16: 17: // Listing7.1 // Utilisation de goto #include <iostream> int main() { using namespace std; int compteur = 0; // initialiser le compteur boucle: compteur++; // dbut de la boucle cout << "compteur: " << compteur << endl; if (compteur < 5) // tester le compteur goto boucle; // revenir au dbut cout << "Fin du traitement. Valeur du compteur: " << compteur << endl; return 0; }

Ce programme produit le rsultat suivant :


compteur: 1 compteur: 2 compteur: 3 compteur: 4 compteur: 5 Fin du traitement. Valeur du compteur: 5

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

Droulement dun programme

175

gal 5. En ce cas, le programme quitte la boucle et excute la ligne 13 qui indique que le traitement est termin.

Pourquoi jeter linstruction goto aux oubliettes ?


Cette instruction est dcrie, juste titre, par les dveloppeurs. Elle permet deffectuer un saut vers nimporte quelle instruction situe en amont ou en aval de la squence en cours. A priori, cette fonctionnalit est trs pratique mais, en ralit, lorsquelle est utilise abusivement, elle rend les programmes illisibles et impossibles maintenir car les sauts seffectuent dans toutes les directions : on parle alors de code spaghetti.

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.

Les boucles while


Une boucle while rpte une squence dinstructions tant que la condition de dpart est vraie. Dans lexemple du Listing 7.1, le compteur tait incrment jusqu ce quil soit gal 5. Pour cela, nous utilisions linstruction goto. Au Listing 7.2, le mme programme a t optimis, laide de linstruction while. Listing 7.2 : Boucles while
1: 2: 3: 4: // Listing7.2 // Une boucle while #include <iostream>

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 produit le rsultat suivant :


Compteur: 0 Compteur: 1 Compteur: 2 Compteur: 3 Compteur: 4 Compteur: 5 Termin. Le compteur vaut: 5

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

Dans cet exemple, compteur++ nest jamais excut.

Chapitre 7

Droulement dun programme

177

Linstruction while Syntaxe de linstruction while :

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

Instructions while complexes


La condition teste dans une boucle while peut tre aussi complexe que nimporte quelle expression C++ : elle peut donc contenir des oprateurs logiques comme && (ET), || (OU) et ! (NON), comme le montre le Listing 7.3. Listing 7.3 : Boucles while complexes
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: // Listing7.3 // Instructions while complexes #include <iostream> int main() { using namespace std; unsigned short petit; unsigned long grand; const unsigned short MAXPETIT = 65535; cout << "Entrez un petit nombre: "; cin >> petit; cout << "Entrez un grand nombre: "; cin >> grand; cout << "Petit nombre: " << petit << "..."; // pour chaque itration, tester deux conditions

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;

Ce programme produit le rsultat suivant :


Entrez un petit nombre: 2 Entrez un grand nombre: 100000 Petit nombre: 2......... Petit nombre: 33335, Grand nombre: 33334

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

Droulement dun programme

179

Les instructions continue et break


Dans certains cas, il est souhaitable de retourner au dbut de la boucle avant que le bloc dinstructions du corps de la boucle ne soit totalement excut. Linstruction continue permet deffectuer cette opration. Inversement, linstruction break permet de sortir de la boucle et dexcuter linstruction situe immdiatement aprs laccolade fermante. Nous utilisons ces instructions dans le Listing 7.4 car le jeu sest compliqu. Lutilisateur saisit maintenant un petit nombre, un grand nombre, une valeur de saut et une valeur cible. Comme dans le listing prcdent, le petit nombre est incrment de un point, alors que le grand nombre est dcrment de deux points. La dcrmentation ne seffectuera pas lorsque le petit nombre est un multiple de la valeur du saut. Le jeu sarrte lorsque petit est suprieur grand. Si le grand nombre est gal la valeur cible, un message apparat lcran et le programme se termine. Lobjectif est de trouver une valeur cible pour le grand nombre, de sorte que le jeu sarrte. Listing 7.4 : break et continue
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: // Listing7.4 - Instructions break et continue #include <iostream> int main() { using namespace std; unsigned short unsigned long unsigned long unsigned long const unsigned cout << "Entrez cin >> petit; cout << "Entrez cin >> grand; cout << "Entrez cin >> saut; cout << "Entrez cin >> cible; cout << "\n"; // configurer deux conditions darrt pour la boucle petit; grand; saut; cible; short MAXPETIT = 65535; un petit nombre: "; un grand nombre: "; un nombre pour les sauts: "; un nombre cible: ";

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;

Ce programme produit le rsultat suivant :


Entrez Entrez Entrez Entrez un un un un petit nombre: 2 grand nombre: 20 nombre pour les sauts: 4 nombre cible: 6

Saut 4 Saut 8 Petit nombre: 10, grand nombre: 8

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

Droulement dun programme

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

break; provoque la sortie immdiate dune boucle while, dowhile ou for.


Exemple

while (condition) { if (condition2) break; // instructions; }

Les boucles while (true)


La condition teste au dbut dune boucle while peut correspondre nimporte quelle expression C++ valide. Tant quelle est vraie, litration se poursuit. Vous pouvez donc crer une boucle sans n en utilisant la valeur boolenne true comme condition. Dans le Listing 7.5, nous utilisons cette fonctionnalit pour incrmenter un compteur jusqu 10.

182

Le langage C++

Listing 7.5 : Exemple de boucle while (true)


1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: } } std::cout << "Compteur: " << compteur << std::endl; return 0; while (true) { compteur++; if (compteur > 10) break; int main() { int compteur = 0; // Listing7.5 // Dmonstration dune boucle while (true) #include <iostream>

Ce programme produit le rsultat suivant :


Compteur: 11

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

Droulement dun programme

183

Faire
Utiliser une boucle while pour rpter une

Ne pas faire
Utiliser linstruction goto. Oublier la diffrence entre continue et

tche tant que la condition est vraie.


Utiliser les instructions break et continue

avec prcaution.
Vrier que litration peut prendre n.

break. continue revient au dbut, break va la n.

Les boucles do...while


Le corps dune boucle while peut ne jamais sexcuter car la condition est vrie avant tout traitement : si son rsultat est gal false, la squence dinstructions ne sera donc pas traite, comme le montre le Listing 7.6. Listing 7.6 : Cas o le traitement de la boucle while ne sexcute jamais
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 17a: 18: 19: // Listing7.6 // Exemple de saut du corps de la boucle while // si la condition est false. #include <iostream> int main() { int compteur; std::cout << "Combien de bips voulez-vous afficher? "; std::cin >> compteur; while (compteur > 0) { std::cout << "Bip!\n"; compteur--; } std::cout << "Compteur la sortie de la boucle: " << compteur; return 0; }

Ce programme produit le rsultat suivant :


Combien de bips voulez-vous afficher? 2 Bip! Bip! Compteur la sortie de la boucle: 0 Combien de bips voulez-vous afficher? 0 Compteur la sortie de la boucle: 0

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

Mais cette solution est un peu boiteuse et inlgante.

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

Droulement dun programme

185

Ce programme produit le rsultat suivant :


Combien de bips voulez-vous afficher? 0 Bip! Compteur la sortie de la boucle: -1

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.

Linstruction do...while La syntaxe de linstruction do...while est la suivante :

do instruction while (condition);


Linstruction est excute, puis la condition est value. Si cette dernire renvoie true, le programme effectue un autre passage dans la boucle, sinon le traitement prend n. Une instruction do...while peut inclure les mmes instructions et conditions quune boucle while. Exemple 1

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

sexcuter au moins une fois.


Utiliser une boucle while pour sauter le

corps de la boucle si la condition est fausse.


Vrier toutes les boucles pour vous assurer

boucles, moins que laction du code ne soit vidente. Il y a souvent des mthodes plus claires pour accomplir ces oprations.
Utiliser linstruction goto.

quelles effectuent le traitement attendu.

Les boucles for


Dans une boucle while, il est souvent ncessaire de raliser trois tapes : dnir une condition de dpart, dterminer si elle est vraie et incrmenter une variable ou y affecter une valeur diffrente chaque passage. Cest ce que lon fait dans le Listing 7.8. Listing 7.8 : Boucle while revisite
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 16a: 17: 18: // Listing7.8 // Traitement itratif avec while #include <iostream> int main() { int compteur = 0; while (compteur < 5) { compteur++; std::cout << "Bip! }

";

std::cout << "\nCompteur la sortie de la boucle: " << compteur << std::endl; return 0; }

Ce programme produit le rsultat suivant :


Bip! Bip! Bip! Bip! Bip! Compteur la sortie de la boucle: 5.

Chapitre 7

Droulement dun programme

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++

Ce programme produit le rsultat suivant :


Bip! Bip! Bip! Bip! Bip! Compteur la sortie de la boucle: 5.

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.

Linstruction for La syntaxe de linstruction for est la suivante :

for (initialisation; test; action) instruction;


Linstruction initialisation permet daffecter une valeur au compteur ou de prparer le traitement itratif. Le test (nimporte quelle expression C++) est valu chaque passage dans la boucle. Si le rsultat est vrai, le corps de la boucle sexcute puis linstruction spcie dans action (en gnral, le compteur est incrment). Exemple 1

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

Boucles for volues


Linstruction for est trs puissante et dune utilisation trs souple. Ses trois lments (initialisation, test et action) autorisent un grand nombre de variations.

Initialisations et incrmentations multiples


Il nest pas rare dinitialiser plusieurs variables, de tester une expression logique complexe et dexcuter plusieurs instructions. Vous avez la possibilit de remplacer linitialisation et

Chapitre 7

Droulement dun programme

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

Ce programme produit le rsultat suivant :


i: 0 , j: 0 i: 1 , j: 1 i: 2 , j: 2

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.

Utilisation dinstructions nulles dans les boucles for


Toute instruction dune boucle for peut tre omise. Pour cela, utilisez une instruction nulle en insrant simplement un point-virgule lendroit o aurait gur la clause. Dans le Listing 7.11, la premire et la troisime clause sont nulles : la boucle for agit alors comme une boucle while.

190

Le langage C++

Listing 7.11 : Instructions nulles dans une boucle for


1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 16a: 17: 18: // Listing7.11 // Boucle For avec des instructions nulles #include <iostream> int main() { int compteur = 0; for(; compteur < 5; ) { compteur++; std::cout << "Bip! }

";

std::cout << "\nCompteur la sortie de la boucle: " << compteur << std::endl; return 0; }

Ce programme produit le rsultat suivant :


Bip! Bip! Bip! Bip! Bip! Compteur la sortie de la boucle: 5.

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

Droulement dun programme

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

Ce programme produit le rsultat suivant :


Combien de bips voulez-vous afficher? 3 Bip! Bip! Bip!

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.

Boucles for vides


Parfois, linstruction for se suft elle-mme pour raliser un traitement. Le corps de la boucle devient alors inutile et, dans ce cas, vous devez insrer une instruction nulle ( ;). Le point-virgule peut gurer sur la mme ligne que linstruction for, mais il est alors facile de loublier. Le Listing 7.13 prsente un exemple de boucle for vide.

192

Le langage C++

Listing 7.13 : Corps dune boucle for vide


1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: } #include <iostream> int main() { for (int i=0; i<5; std::cout << "I: " << i++ << std::endl) ; return 0; // instruction vide (corps de la boucle) // Listing7.13 // Exemple de corps de la boucle for // compos dune instruction nulle

Ce programme produit le rsultat suivant :


i: 0 i: 1 i: 2 i: 3 i: 4

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;

Le rsultat est le mme, mais avouez que cest plus clair.

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

Droulement dun programme

193

Listing 7.14 : Boucles for imbriques


1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: //Listing7.14 //Boucles imbriques #include <iostream> int main() { using namespace std; int lignes, colonnes; char car; cout << "Nombre de lignes? "; cin >> lignes; cout << "Nombre de colonnes? "; cin >> colonnes; cout << "Caractre utiliser? "; cin >> car; for (int i = 0; i < lignes; i++) { for (int j = 0; j < colonnes; j++) cout << car ; cout << endl; } return 0; }

Ce programme produit le rsultat suivant :


Nombre de lignes: 4 Nombre de colonnes: 12 Caractre utiliser? X XXXXXXXXXXXX XXXXXXXXXXXX XXXXXXXXXXXX XXXXXXXXXXXX

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

Porte des variables dans une boucle for


Par le pass, la porte des variables dclares dans une boucle for stendait au bloc extrieur. La norme ANSI a modi ce comportement en rduisant la porte de ces variables au bloc de la boucle for ; cependant, certains compilateurs ne respectent pas encore cette spcication. Le mieux est de tester le vtre avec le code suivant :
#include <iostream> int main() { // porte de i limite la boucle for? for (int i = 0; i < 5; i++) { std::cout << "i : " << i << std::endl; } i = 7; // ne devrait pas tre accessible! return 0; }

Chapitre 7

Droulement dun programme

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

Rsum des boucles


Au Chapitre 5, nous avons rsolu la suite de Fibonacci en faisant appel la rcursivit. Rappelons que cette suite commence par les chiffres 1, 1, 2, 3, les autres valeurs tant la somme des deux nombres prcdents. Exemple :
1, 1, 2, 3, 5, 8, 13, 21, 34...

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

Ce programme produit le rsultat suivant :


Quelle position ? 4 3 est le 4e nombre dans la suite. Quelle position ? 5 5 est le 5e nombre dans la suite. Quelle position ? 20 6765 est le 20e nombre dans la suite.

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

Droulement dun programme

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++

Contrler le ux avec des instructions switch


Au Chapitre 4, nous avons tudi les instructions if et if...else. Lorsque leur niveau dimbrication devient trop lev, il est prfrable dutiliser linstruction switch. la diffrence de if, qui ne permet de tester quune seule condition, une instruction switch permet deffectuer un "aiguillage" dans le programme en fonction des diffrentes valeurs dune expression. Cette instruction se prsente sous la forme suivante :
switch (expression) { case valeur1: instruction; break; case valeur2: instruction; break; .... case valeurN: instruction; break; default: instruction; }

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

Droulement dun programme

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

case case case case

suivant suivant suivant suivant

Ce programme produit le rsultat suivant :


Entrez un nombre entre 1 et 5: Excellent! Formidable! Incroyable! 3

Entrez un chiffre entre 1 et 5: 8 Trop grand!

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

Linstruction switch La syntaxe de linstruction switch est la suivante :

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

"Zro!" << endl;

"Un!" << endl;

"Deux!" << endl; "Clause par dfaut!" << endl;

Chapitre 7

Droulement dun programme

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

Utilisation de switch dans un menu


Le Listing 7.17 reprend le principe de la boucle for(;;) dcrite plus haut. Une boucle qui ne se termine pas est appele boucle sans n, car elle se rptera continuellement si elle ne rencontre pas une instruction break. Dans le Listing 7.17, le programme afche un menu, invite lutilisateur faire un choix, agit en fonction de ce choix, puis afche de nouveau le menu. Pour mettre n ce traitement, lutilisateur doit choisir loption Quitter. Certains programmeurs aiment crire :
#define EVER;; for (EVER) { // instructions }

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

Droulement dun programme

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

Ce programme produit le rsultat suivant :


**** (1) (2) (3) (4) (5) Menu **** Choix un. Choix deux. Choix trois. Rafficher le menu Quitter.

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

nant pas (sciemment) dinstruction break.


Prvoir une clause default dans les instruc-

alors quune instruction switch plus claire conviendrait.


Oublier linstruction break dans une suite de

tions switch, an de traiter les situations exceptionnelles.

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

Droulement dun programme

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.

Testez vos connaissances


1. Comment peut-on initialiser plusieurs variables dans une boucle for ? 2. Pourquoi faut-il viter dutiliser linstruction goto ? 3. Est-il possible de crer une boucle for dont la squence dinstructions ne sexcute jamais ? 4. Lorsque cette boucle for se termine, quelle est la valeur de x ?
for (int x = 0; x < 100; x++)

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

6. CHERCHEZ LERREUR : pourquoi ce code est-il bogu ?


for (int compteur = 0; compteur < 10; compteur++); cout << compteur << "\n ";

7. CHERCHEZ LERREUR : pourquoi ce code est-il bogu ?


int compteur = 100; while (compteur < 10) { cout << "Le compteur indique: " << compteur; compteur--; }

8. CHERCHEZ LERREUR : pourquoi ce code est-il bogu ?


cout << "Entrez une valeur cin >> Valeur; switch (Valeur) { case 0: Traitement0(); case 1: // case 2: // case 3: // case 4: // case 5: Traitement1A5(); break; default: ParDefaut(); break; } comprise entre 0 et 5: ";

case case case case

suivant suivant suivant suivant

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.

Quest-ce quun pointeur ?


Un pointeur est une variable qui contient une adresse mmoire. Cest tout. Si vous comprenez cette simple phrase, vous savez lessentiel au sujet des pointeurs.

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

10110101 11110110 01110110 11101110

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

Rcuprer ladresse mmoire dune variable


Lorganisation de la mmoire et la numrotation des adresses varient dun ordinateur un autre. En gnral, les dveloppeurs nont pas besoin de connatre les adresses des variables utilises, car ces dtails sont grs par le compilateur. Si vous avez besoin de connatre ladresse dun objet en mmoire, vous pouvez toutefois utiliser loprateur adresse de (&), comme le montre le Listing 8.1.

Chapitre 8

Pointeurs

211

Listing 8.1 : Exemple dutilisation de loprateur adresse de


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: // Listing8.1 - Utilisation de loprateur "adresse de" (&) // et adresses des variables locales #include <iostream> int main() { using namespace std; unsigned short varCourte = 5; unsigned long varLongue = 65535; long varSignee = -65535; cout << "varCourte :\t" << varCourte; cout << "\tAdresse de varCourte :\t"; cout << &varCourte << endl; cout << "varLongue :\t" << varLongue; cout << "\tAdresse de varLongue :\t"; cout << &varLongue << endl; cout << "varSignee :\t" << varSignee; cout << "\tAdresse de varSignee :\t"; cout << &varSignee << endl; return 0; }

Ce programme produit le rsultat suivant :


varCourte :5 varLongue :65535 varSignee :-65535 Adresse de varCourte : Adresse de varLongue : Adresse de varSignee : 0xbffff97e 0xbffff978 0xbffff974

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

Stockage dune adresse de variable dans un pointeur


Comme nous lavons vu, toute variable est associe une adresse. Il est possible de stocker cette adresse dans un pointeur sans en connatre la valeur. Supposons que la variable votreAge soit de type entier. Le pointeur pAge associ, qui va recevoir son adresse, sera dclar ainsi :
int *pAge = 0;

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.

Noms des pointeurs


Les pointeurs ntant que des variables, vous pouvez utiliser nimporte quel nom autoris pour les variables : les mmes rgles et suggestions sappliquent. De nombreux programmeurs respectent la convention de nommage avec un p initial, comme dans pAge ou pNombre. Dans lexemple,
int *pAge = 0;

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

Le pointeur pAge contient ladresse de la variable MonAge.

Obtenir la valeur dune variable


laide de pAge, vous pouvez dterminer la valeur numrique contenue dans MonAge. La technique consistant accder la valeur stocke dans une variable par lintermdiaire dun pointeur sappelle adressage indirect ou indirection, car vous accdez indirectement la variable, via un pointeur. Vous pouvez, par exemple, utiliser lindirection avec le pointeur pAge pour accder la valeur MonAge. Lindirection consiste accder la valeur stocke ladresse que contient un pointeur. Le pointeur fournit un moyen indirect daccder la valeur stocke cette adresse.
Avec une variable normale, le type indique au compilateur la taille mmoire ncessaire pour contenir la valeur. Dans le cas dun pointeur, cest diffrent : tous les pointeurs ont la mme taille, gnralement 4 octets sur un processeur 32 bits et 8 octets sur un processeur 64 bits. Le type indique alors au compilateur la mmoire ncessaire pour contenir lobjet ladresse que contient le pointeur !

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.

Drfrencement avec loprateur dindirection


Loprateur dindirection (*) est galement appel oprateur de drfrencement. Lorsquun pointeur est drfrenc, on obtient la valeur situe ladresse stocke dans le pointeur. Laccs la valeur dune variable classique est direct. Pour crer la variable VotreAge, de type entier court non sign et lui affecter la valeur de MonAge, il suft dcrire les instructions suivantes :
unsigned short int VotreAge; VotreAge = MonAge;

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 :

// cration dun pointeur vers un unsigned short unsigned short * pAge = 0;

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;

// affecte 5 la valeur situe pAge

Ce caractre est aussi utilis comme oprateur de multiplication. Rassurez-vous : le compilateur sait, daprs le contexte, de quel oprateur il sagit.

Pointeurs, adresses et variables


Avant de continuer, il convient de distinguer le pointeur, ladresse stocke dans le pointeur et la valeur ladresse contenue par le pointeur. Ces trois concepts sont souvent source de confusion chez les programmeurs. Prenons un exemple :
int maVariable = 5; int * pPointeur = &maVariable;

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

Il sagit de deux octets (16 bits) dont la valeur dcimale est 5.


Figure 8.2 Reprsentation schmatique de la mmoire.
maVariable Nom de la variable pPointeur

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++

La variable pointeur se trouve lemplacement 106. Sa valeur est :


0000000000000000000000001100101

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.

Gestion de donnes laide de pointeurs


Outre loprateur dindirection qui permet de lire les donnes stockes une position pointe par une variable, vous pouvez aussi manipuler ces donnes car, aprs avoir affect une adresse un pointeur, vous pouvez avoir accs aux donnes quil pointe. Le Listing 8.2 reprend ce que vous venez dapprendre sur les pointeurs. Vous verrez comment ladresse dune variable locale est affecte un pointeur, qui peut ensuite tre utilis avec loprateur dindirection pour manipuler les donnes contenues dans cette variable. Listing 8.2 : Manipulation de donnes par pointeurs
1: 2: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: // Listing8.2 Utilisation des pointeurs #include <iostream> typedef unsigned short int USHORT; int main() { using namespace std; USHORT monAge; USHORT * pAge = 0; moAge = 5; cout << "moAge: " << moAge << endl; pAge = &moAge; // affecte ladresse de monAge pAge cout << "*pAge: " << *pAge << endl << endl; cout << "Affectation *pAge de la valeur 7... " << endl; *pAge = 7; // affecte la valeur 7 monAge cout << "*pAge: " << *pAge << endl; // une variable // un pointeur

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 produit le rsultat suivant :


monAge : 5 *pAge : 5 Affectation *pAge de la valeur 7... *pAge : 7 monAge : 7 Affectation myAge de la valeur 9... monAge : 9 *pAge : 9

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.

Lecture dune adresse


Un pointeur permet de manipuler une adresse sans mme connatre sa valeur. Laffectation dune adresse de variable un pointeur fait que celui-ci contient ladresse de cette valeur. Nous allons juste le vrier laide du Listing 8.3.

218

Le langage C++

Listing 8.3 : Lire le contenu dun pointeur


1: 2: 3: 4: 5: 6: 7: 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: // Listing8.3 Ce qui est stock dans un pointeur. #include <iostream> int main() { using namespace std; unsigned short int monAge = 5, tonAge = 10; // un pointeur unsigned short int * pAge = &monAge; 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 << "\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; }

Ce programme produit le rsultat suivant :


monAge : &monAge : 5 0xbffff96e tonAge : &tonAge : 10 0xbffff96c

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

dun pointeur, laide de loprateur dindirection (*).


Initialiser lensemble des pointeurs dun

avec la valeur stocke cette adresse.

programme avec une adresse existante ou la valeur nulle (0).

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 :

unsigned short int * pPointeur = 0;


Pour affecter une valeur un pointeur ou linitialiser, vous devez faire prcder le nom de la variable sur laquelle il porte de loprateur adresse de (&). Exemple :

unsigned short int maVariable = 5; unsigned short int * pPointeur = &maVariable;


Pour drfrencer un pointeur, faites prcder son nom de loprateur (*). Exemple :

unsigned short int maValeur = *pPointeur

Utilit des pointeurs


Vous venez dapprendre les dtails de laffectation dune adresse de variable un pointeur. Dans la pratique, cependant, vous ne le ferez jamais. Pourquoi passer par un pointeur pour lire une variable, alors que la valeur de cette dernire est directement accessible ? Lobjectif de ce type de manipulation tait simplement de vous montrer le fonctionnement des pointeurs. Maintenant que leur syntaxe na plus de secrets pour vous, vous pouvez les utiliser correctement. Les pointeurs sont gnralement utiliss pour trois raisons :

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.

La pile et lespace mmoire adressable (tas)


la n du Chapitre 5, nous avons voqu les cinq parties suivantes de la mmoire :

lespace global de noms ; lespace mmoire adressable (appel aussi mmoire "heap" ou tas) ; les registres ;

Chapitre 8

Pointeurs

221

lespace de code ; la pile.

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.

Allouer de lespace avec le mot-cl new


Le mot-cl new permet de rserver de lespace dans le tas. Il doit tre suivi du type de lobjet an que le compilateur dtermine la quantit de mmoire requise. Ainsi, lexpression new unsigned short int et lexpression new long rservent, respectivement, 2 et 4 octets sur le tas, en supposant que votre systme utilise un entier court non sign sur 2 octets et une entier long sur 4 octets. La valeur renvoye par new est ladresse de lemplacement mmoire rserv sur le tas. Sachant que les adresses mmoire sont stockes dans des pointeurs, il nest pas surprenant que la valeur de retour de new soit affecte un pointeur. Pour crer un entier court non sign dans le tas, on crira donc :
unsigned short int * pPointeur; pPointeur = new unsigned short int;

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

Librer de la mmoire : le mot-cl delete


Aprs utilisation, la zone de mmoire dnie laide du mot-cl new doit tre libre pour tre rendue au systme. Pour cela, appelez le mot-cl delete sur le pointeur. Il est essentiel de se souvenir que la mmoire alloue grce new nest pas automatiquement libre. Si une variable de pointeur pointe vers de la mmoire dans lespace libre et que le pointeur devient hors de porte, la mmoire nest pas automatiquement rendue lespace libre. Elle est considre alloue ; le pointeur ntant plus disponible, vous ne pouvez plus y accder. Cela survient, par exemple, lorsquun pointeur est une variable locale. Au retour de la fonction dans laquelle ce pointeur est dclar, la porte du pointeur cesse et celui-ci est perdu. La mmoire alloue avec new nest pas libre (et ne peut plus ltre puisquon a perdu le pointeur) elle devient indisponible. Cette situation est appele fuite mmoire, car lespace mmoire ne pourra tre rcupr quaprs la n du programme : cest comme si la mmoire fuyait de votre ordinateur. Pour viter ces fuites de mmoire, vous devrez restituer au tas toute la mmoire que vous avez alloue en utilisant le mot-cl delete :
delete pPointeur;

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

Listing 8.4 : Allocation, utilisation et suppression dun pointeur


1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: // Listing8.4 // Allocation et suppression dun pointeur #include <iostream> int main() { using namespace std; int variableLocale = 5; int * pLocal= &variableLocale; int * pTas = new int; *pTas = 7; cout << "variableLocale : " << variableLocale << endl; cout << "*pLocal: " << *pLocal << endl; cout << "*pTas: " << *pTas << endl; delete pTas; pTas = new int; *pTas = 9; cout << "*pTas: " << *pTas << endl; delete pTas; return 0; }

Ce programme produit le rsultat suivant :


variableLocale : 5 *pLocal: 5 *pTas: 7 *pTas: 9

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.

En savoir plus sur les fuites mmoire


Les fuites mmoire sont lun des problmes les plus srieux des pointeurs. Nous venons de voir une des situations qui provoquent ces fuites, mais raffecter une nouvelle adresse un pointeur avant de librer lemplacement quil pointait en est une autre. Voici un exemple :
1: 2: 3: 4: unsigned short int * pPointeur = new unsigned short int; *pPointeur = 72; pPointeur = new unsigned short int; *pPointeur = 84;

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.

Cration dobjets sur le tas


Un pointeur peut pointer sur nimporte quel type de donnes, y compris des instances de classes. Si vous disposez dune classe Chat, par exemple, vous pouvez dclarer un pointeur permettant de pointer des objets de cette classe et crer une instance de Chat sur le tas, exactement comme vous pouvez crer un objet sur la pile. La syntaxe est la mme que pour les entiers :
Chat *pChat = new Chat;

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.

Suppression dobjets du tas


Linstruction delete appelle le destructeur de lobjet avant de librer lemplacement point, ce qui permet deffectuer des oprations de nettoyage (consistant, gnralement, librer dautres emplacements du tas, allous par lobjet), exactement comme pour un objet allou sur la pile. Le Listing 8.5 cre et supprime des objets sur le tas. Listing 8.5 : Cration et suppression dobjets sur le tas
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: // Listing8.5 - Cration dobjets dans le tas // laide de new et delete #include <iostream> using namespace std; class ChatSimple { public: ChatSimple(); ~ChatSimple(); private: int sonAge;

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

Ce programme produit le rsultat suivant :


ChatSimple Frisky... Appel du constructeur. ChatSimple *pRags = new ChatSimple... Appel du constructeur. Suppression de pRags... Appel du destructeur. Fin, Frisky disparait... Appel du destructeur.

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.

Accs aux donnes membres


Le Chapitre 6 vous a montr comment accder aux donnes et aux fonctions membres laide de loprateur point (.). Les objets de la classe Chat avaient t crs sur la pile. Accder aux membres dun objet lors de lutilisation dun pointeur est un peu plus complexe. Pour lire lun de ses membres, vous devez dabord drfrencer le pointeur, puis utiliser loprateur point sur lobjet dsign par le pointeur. Il nest pas inutile de le rpter : vous devez dabord drfrencer le pointeur et vous utilisez ensuite la valeur drfrence (celle qui est pointe) avec loprateur point pour accder aux membres de lobjet. Pour accder la fonction membre GetAge() dun objet point par pRags, par exemple, vous devez donc utiliser linstruction suivante :
(*pRags).GetAge();

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

Listing 8.6 : Lecture de donnes membres des objets dans le tas


1: 2: 3: 4: 5: // Listing8.6 - Accs aux donnes membres des objets du tas // laide de loprateur -> #include <iostream>

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

Ce programme produit le rsultat suivant :


Frisky a 2 ans Frisky a 5 ans

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

Cration de donnes membres dans le tas


Outre crer des objets sur le tas, vous pouvez aussi crer des donnes membres sur le tas car des membres dune classe peuvent eux-mmes tre des pointeurs. Grce ce que vous avez appris, vous pouvez allouer de la mmoire sur le tas pour ces pointeurs. Cette allocation peut avoir lieu dans le constructeur de la classe ou dans lune de ses mthodes. Lorsque vous avez termin dutiliser le membre, vous pouvez et devez le supprimer dans lune des mthodes ou dans le destructeur, comme le montre le Listing 8.7.

230

Le langage C++

Listing 8.7 : Les pointeurs comme donnes membres dune classe


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: 43: 44: 45: // Listing8.7 - Donnes membres pointeurs // Accs laide de loprateur -> #include <iostream> class ChatSimple { public: ChatSimple(); ~ChatSimple(); int GetAge() const { return *sonAge; } void SetAge(int age) { *sonAge = age; } int GetPoids() const { return *sonPoids; } void SetPoids (int poids) { *sonPoids = poids; } private: int * sonAge; int * sonPoids; }; ChatSimple::ChatSimple() { sonAge = new int(2); sonPoids = new int(5); } ChatSimple::~ChatSimple() { delete sonAge; delete sonPoids; } int main() { using namespace std; ChatSimple *Frisky = new ChatSimple; cout << "Frisky a " << Frisky->GetAge() << " ans " << endl; Frisky->SetAge(5); cout << "Frisky a " << Frisky->GetAge() << " ans\ " << endl; delete Frisky; return 0; }

Chapitre 8

Pointeurs

231

Ce programme produit le rsultat suivant :


Frisky a 2 ans Frisky a 5 ans

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

Ce programme produit le rsultat suivant :


unRect unRect unRect unRect mesure mesure mesure mesure 10cm de long. 5cm de large. 20cm de long. 10cm de large.

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.

Pointeurs perdus, incontrlables ou pointeurs fous


Une fois de plus, les problmes de pointeurs refont surface. Ceci est d au fait que les erreurs imputables aux pointeurs sont parmi les plus difciles dceler et les plus problmatiques. Les pointeurs sont la cause de nombreux bogues particulirement difciles dtecter. Un pointeur perdu apparat lorsque vous le supprimez laide du mot-cl delete vous librez donc la mmoire vers laquelle il pointait sans lui affecter ensuite la valeur null.

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

Listing 8.9 : Cration dun pointeur perdu


1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: // Listing8.9 - Un pointeur perdu typedef unsigned short int USHORT; #include <iostream> int main() { USHORT * pInt = new USHORT; *pInt = 10; std::cout << "*pInt: " << *pInt << std::endl; delete pInt; long * pLong = new long; *pLong = 90000;

Chapitre 8

Pointeurs

235

15: 16: 17: 18: 19: 20: 21: 22: 23:

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

Ce programme produit le rsultat suivant :


*pInt: *pLong: *pInt: *pLong: 10 90000 20 65556

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

Chacune effectue toutefois des actions diffrentes :

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

Pointeurs const et fonctions membres const


Au Chapitre 6, vous avez appris dnir des fonctions constantes laide du mot-cl const. Si une fonction const tente de modier les donnes de lobjet, le compilateur indique une erreur. Si vous dclarez un pointeur vers un objet constant, seules des mthodes constantes peuvent tre appeles sur ce pointeur, comme le montre le Listing 8.10.

238

Le langage C++

Listing 8.10 : Utilisation de pointeurs sur des objets constants


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: 38a: 39: 40: 41: // Listing8.10 - Pointeurs et mthodes const #include <iostream> using namespace std; class Rectangle { public: Rectangle(); ~Rectangle(); void SetLongueur(int longueur) { saLongueur = longueur; } int GetLongueur() const { return 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() { Rectangle* pRect = new Rectangle; const Rectangle * pRectConst = new Rectangle; Rectangle * const pPtrConst = new Rectangle; cout << << cout << << << cout << << "Largeur de pRect: " << pRect->GetLargeur() " m" << endl; "Largeur de pRectConst: " pRectConst->GetLargeur() " m" << endl; "Largeur de pPtrConst: " << pPtrConst->GetLargeur() " m" << endl;

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

Ce programme produit le rsultat suivant :


Largeur Largeur Largeur Largeur Largeur Largeur de de de de de de pRect: 5 m pRectConst: 5 m pPtrConst: 5 m pRect: 10 m pRectConst: 5 m pPtrConst: 10 m

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++

Pointeurs const this


Lorsque vous dclarez un objet const, vous dclarez en fait que le pointeur this de cet objet est un pointeur sur un objet constant. Un pointeur const this ne peut donc tre utilis quavec des fonctions membres constantes.
Faire
Protger les objets passs par rfrence

Ne pas faire
Utiliser un pointeur dont on a libr lempla-

laide du mot-cl const sils ne doivent pas tre modis.


Affecter 0 aux pointeurs plutt que de les

cement quil pointait.


Appeler plusieurs fois delete sur le mme

pointeur.

laisser non-initialiss ou pendants.

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

Testez vos connaissances


1. Quel oprateur permet dextraire ladresse dune variable ? 2. Quel oprateur permet dextraire la valeur stocke lemplacement point par un pointeur ? 3. Quest-ce quun pointeur ? 4. Quelle est la diffrence entre ladresse stocke dans un pointeur et la valeur cette adresse ? 5. Quelle est la diffrence entre loprateur dindirection et loprateur adresse de ? 6. Quelle est la diffrence entre ces deux dclarations ?
const int * ptrUn; int * const ptrDeux;

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++

6. CHERCHEZ LERREUR : o lerreur se cache-t-elle dans ce programme ?


#include <iostream> using namespace std; int main() { int Variable = 5; cout << "Variable: " << Variable << endl; int *pVar = &Variable; pVar = 9; cout << "Variable: " << *pVar << endl; return 0; }

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++

Quest-ce quune rfrence ?


Une rfrence est un alias. En dautres termes, une rfrence est initialise avec le nom dun autre objet appel objet cible. Ds lors, elle se comporte comme lobjet cible et toute opration effectue sur la rfrence est rpercute sur celui-ci. Pour crer une rfrence, il suft dindiquer le type de lobjet cible, suivi de loprateur rfrence (&) et du nom de la rfrence, puis le signe gal et, enn, le nom de lobjet cible. Une rfrence peut porter nimporte quel nom de variable autoris en C++. Certains programmeurs utilisent une convention consistant prxer le nom des rfrences par la lettre "r", comme ici :
int &rReference = VariableEntiere;

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

Listing 9.1 : Cration et utilisation de rfrences


1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: //Listing9.1 - Utilisation des rfrences #include <iostream> int main() { using namespace std; int intUn; int &rUneRef = intUn; intUn = 5; cout << "intUn: " << intUn << endl; cout << "rUneRef: " << rUneRef << endl;

Chapitre 9

Rfrences

245

14: 15: 16: 17: 18: 19: 20:

rUneRef = 7; cout << "intUn: " << intUn << endl; cout << "rUneRef: " << rUneRef << endl; return 0; }

Ce programme produit le rsultat suivant :


intUn: 5 rUneRef: 5 intUn: 7 rUneRef: 7

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.

Utilisation de loprateur adresse de (&) sur des rfrences


Vous savez maintenant que le symbole & permet la fois dobtenir ladresse dune variable et de dclarer une rfrence. Mais que se passe-t-il si vous demandez ladresse dune variable rfrence ? Le programme renverra ladresse de lobjet cible, ce qui est logique puisque la rfrence est un alias de la cible (voir Listing 9.2). Listing 9.2 : Extraction de ladresse dune rfrence
1: 2: 3: 4: 5: 6: 7: 8: //Listing9.2 - Utilisation des rfrences #include <iostream> int main() { using namespace std; int intUn;

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

Ce programme produit le rsultat suivant :


intUn : 5 rUneRef : 5 &intUn : 0x3500 &rUneRef : 0x3500

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

Tentative de raffecter des rfrences (ne le faites pas !)


Les variables rfrence ne peuvent pas tre raffectes. Mme des programmeurs chevronns peuvent se laisser piger en croyant raffecter une rfrence alors quils raffectent simplement lobjet cible, comme le montre le Listing 9.3. Les variables de rfrence sont toujours des alias de leur cible. Listing 9.3 : Affectation une rfrence
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: //Listing9.3 - Raffectation dune r frence #include <iostream> int main() { using namespace std; int intUn; int &rUneRef = intUn; intUn = cout << cout << cout << cout << 5; "intUn : "rUneRef : "&intUn : "&rUneRef :

" << 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++

Ce programme produit le rsultat suivant :


intUn : rUneRef : &intUn : &rUneRef : intUn : intDeux : rUneRef : &intUn : &intDeux : &UneRef : 5 5 0012FEDC 0012FEDC 8 8 8 0012FEDC 0012FEE0 0012FEDC

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.

Rfrencement des objets


Tous les objets, y compris ceux dnis par lutilisateur, peuvent tre rfrencs. Une rfrence ne portant jamais sur une classe, mais sur lun de ses objets, cette expression est interdite :
int & rRefInt = int; // erreur

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++

28: 29: 30: 31: 32: 33:

std::cout std::cout std::cout std::cout return 0; }

<< << << <<

"Frisky a "; Frisky.GetAge() << " ans. " << std::endl; "Frisky pse "; rChat.GetPoids() << " kilos." std::endl;

Ce programme produit le rsultat suivant :


Frisky a 5 ans Frisky pse 4kilos.

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

int sonAge; int &rAge = sonAge;


Exemple 2

Chat Frisky; Chat &rRefChat = Frisky;

Pointeurs nuls et rfrences nulles


Lorsquun pointeur na pas t initialis ou lorsque lemplacement quil pointe a t supprim, il doit tre initialis zro. Ceci ne sapplique pas aux rfrences, car elles doivent tre initialises sur ce quelles rfrencent lors de la dclaration. Toutefois, C++ tant utilisable par des pilotes de priphriques, des systmes embarqus et des systmes en temps rel qui peuvent accder directement au matriel, il faut pouvoir rfrencer des adresses spciques. La plupart des compilateurs permettent

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.

Passage de paramtres par rfrence


Au Chapitre 5, nous avons vu que les fonctions prsentent deux restrictions : les paramtres sont passs par valeur et linstruction return ne peut renvoyer quune seule valeur. Passer des valeurs par rfrence permet de contourner ces deux problmes. En C++, vous pouvez passer des paramtres par rfrence de deux faons : en utilisant des pointeurs ou des rfrences. Vous passez par rfrence en utilisant un pointeur ou vous passez une rfrence en utilisant une rfrence. La syntaxe qui utilise un pointeur est diffrente de celle qui utilise une rfrence, mais leffet est le mme : cest lobjet dorigine qui est mis la disposition de la fonction, non une copie de celui-ci Passer un objet par rfrence permet la fonction de modier lobjet rfrenc. Au Chapitre 5, vous avez appris que les paramtres des fonctions sont passs via la pile. Lorsquune valeur est passe par rfrence avec un pointeur ou une rfrence , seule ladresse de lobjet original est place sur la pile, pas lobjet lui-mme. En ralit, sur certains ordinateurs, ladresse est stocke dans un registre, ce qui vite dutiliser la pile. Dans les deux cas, comme cest une adresse qui est transmise, le compilateur peut identier lobjet dorigine et les modications auront lieu sur celui-ci, pas sur une copie. Dans le Listing 5.5 du Chapitre 5, nous avions utilis une fonction de permutation appele swap() qui naffectait pas les valeurs dans la fonction appelante. titre de rappel, le Listing 9.5 reproduit cet exemple. Listing 9.5 : Passage de paramtres par valeur
1: 2: 3: 4: 5: 6: 7: 8: 9: //Listing9.5 - Passage par valeur #include <iostream> using namespace std; void swap(int x, int y); int main() { int x = 5, y = 10;

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 produit le rsultat suivant :


Dans Main. Avant change, x : 5 y : 10 Dans Swap. Avant change, x : 5 y : 10 Dans Swap. Aprs change, x : 10 y : 5 Dans Main. Aprs change, x : 5 y : 10

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

Utilisation de pointeurs avec la fonction swap()


Si vous passez des pointeurs en paramtre, vous transmettez en fait les adresses des objets, ce qui fait que la fonction peut modier les valeurs situes ces adresses. swap() doit donc tre dclare pour attendre deux pointeurs dentiers. En drfrenant ces pointeurs, la fonction pourra alors accder aux vritables valeurs de x et y et les changer. Cest ce que fait le Listing 9.6 Listing 9.6 : Passage par rfrence laide de pointeurs
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 11a: 12: 13: 13a: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: //Listing9.6 Passage par rfrence #include <iostream> using namespace std; void swap(int *x, int *y); int main() { int x = 5, y = 10; 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; } void swap (int *px, int *py) { int temp; cout << "Dans Swap. Avant change, *px : " << *px << " *py : " << *py << endl; temp = *px; *px = *py; *py = temp; cout << "Dans Swap. Aprs change, *px : " << *px << " *py : " << *py << endl; }

254

Le langage C++

Ce programme produit le rsultat suivant :


Dans Main. Avant change, x : 5 y : 10 Dans Swap. Avant change, *px : 5 *py : 10 Dans Swap. Apres change, *px : 10 *py : 5 Dans Main. Apres change, x : 10 y : 5

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.

Utililisation de rfrences avec la fonction swap()


Bien que programme prcdent fasse ce que lon attend, la syntaxe de la fonction swap() est un peu lourde. Les oprations rptes de drfrencement des pointeurs sont difciles relire et sujettes aux erreurs si vous oubliez de drfrencer le pointeur, le compilateur vous permettra quand mme de lui affecter un entier et cest lutilisateur de votre fonction qui subira lerreur. En outre, devoir explicitement passer en paramtre les adresses des variables de la fonction appelante lors de lappel expose aux utilisateurs le fonctionnement interne de la fonction swap(). Un langage orient objet comme C++ se doit de masquer les dtails de fonctionnement interne dune fonction. Lutilisation des fonction doit tre totalement transparente pour les utilisateurs. Lutilisation de paramtres pointeurs reporte donc la charge du passage par rfrence sur la fonction appelante alors que cela devrait tre gr par la fonction appele. Le Listing 9.7 propose une autre criture de swap(), qui utilise cette fois-ci des paramtres rfrences.

Chapitre 9

Rfrences

255

Listing 9.7 : Fonction swap() rcrite avec des rfrences


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: //Listing9.7 Passage par rfrence // laide de rfrences! #include <iostream> using namespace std; void swap(int &x, int &y); int main() { int x = 5, y = 10; 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; } void swap (int &rx, int &ry) { int temp; cout << "Dans Swap. Avant change, rx : " << rx << " ry : " << ry << endl; temp = rx; rx = ry; ry = temp; "

cout << "Dans Swap. Aprs change, rx : " << rx << " ry : " << ry << endl; }

Ce programme produit le rsultat suivant :


Dans Dans Dans Dans Main. Swap. Swap. Main. Avant Avant Aprs Aprs change, change, change, change, x : 5 y : 10 rx : 5 ry : 10 rx : 10 ry : 5 x : 10, y : 5

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 !

En-ttes et prototypes de fonctions


Dans les deux listings ci-dessus, les programmes ont utilis successivement des pointeurs et des rfrences. Lutilisation des rfrences est plus aise et rend le code plus facile comprendre mais comment la fonction appelante peut-elle savoir si les paramtres ont t passs par valeur ou par rfrence ? En tant que client (ou utilisateur) de swap(), le programmeur doit sassurer que cette fonction modiera bien ses paramtres. Pour le savoir, il peut examiner les paramtres dclars dans le prototype de la fonction. Ce dernier se trouve gnralement dans un chier en-tte, accompagn dautres prototypes. Grce ces informations, le programmeur peut donc savoir que ses valeurs seront passes par rfrence la fonction swap() et seront donc correctement changes. Le prototype swap() apparat la ligne 6 du Listing 9.7 : on peut constater que les deux paramtres sont passs comme des rfrences. Si swap() avait t une fonction membre dune classe, la dclaration de classe galement disponible dans le chier en-tte aurait fourni ces informations. En C++, les clients des classes et des fonctions peuvent se er aux chiers en-ttes pour connatre tout ce quils ont besoin de savoir. Ce chier agit alors comme une interface vers la classe ou la fonction, dont limplmentation est cache lutilisateur. Celui-ci peut alors se consacrer son problme et utiliser les classes ou les fonctions sans se soucier des dtails de leur fonctionnement. Lors de la construction du pont de Brooklyn, le colonel John Roebling supervisait totalement les travaux, de la fabrication des matriaux (cbles, briques et ciment) au respect des plans. De nos jours, les ingnieurs utilisent mieux leur temps en ne soccupant que de la

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.

Renvoi de plusieurs valeurs


Comme nous lavons vu plus haut, une fonction ne peut renvoyer quune seule valeur la fois. Comment faire pour en renvoyer deux ? La premire solution consiste passer deux objets par rfrence la fonction. Le passage par rfrence autorisant une fonction modier les objets dorigine, elle peut donc renvoyer deux lments dinformation. Cette pratique permet de se passer de la valeur renvoye par la fonction, qui peut par exemple servir signaler une erreur. L encore, on peut utiliser des rfrences ou des pointeurs. Dans le Listing 9.8, la fonction renvoie trois valeurs, la valeur de renvoi habituelle et deux paramtres pointeurs. Listing 9.8 : Renvoi de valeurs laide de pointeurs
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: //Listing9.8 - Renvoi de plusieurs valeurs par une fonction #include <iostream> using namespace std; short Facteur(int n, int* pCarre, int* pCube); int main() { int nombre, carre, cube; short erreur; cout << "Entrez un nombre entre 0 et 20: "; cin >> nombre; erreur = Facteur(nombre, &carre, &cube);

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

Ce programme produit le rsultat suivant :


Entrez un nombre entre 0 et 20: 3 Valeur: 3 Carr: 9 Cube: 27

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

Renvoi de valeurs par rfrence


Bien que le Listing 9.8 fonctionne correctement, il est conseill de privilgier les rfrences aux pointeurs car la maintenance et la relecture du code seront plus faciles. Le Listing 9.9 correspond au Listing 9.8 mais utilise des rfrences la place des pointeurs. Il inclut galement une autre amlioration puisquil utilise un enum pour que la valeur de retour soit plus simple comprendre. Au lieu de renvoyer 0 ou 1, cet enum permet la fonction de renvoyer SUCCES ou ECHEC. Listing 9.9 : Utilisation de rfrences
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: //Listing9.9 // Renvoi de plusieurs valeurs par une fonction // laide de rfrences #include <iostream> using namespace std; enum CODE_ERREUR { SUCCES, ECHEC }; CODE_ERREUR Facteur(int, int&, int&); int main() { int nombre, carre, cube; CODE_ERREUR resultat; cout << "Entrez un nombre entre 0 et 20: "; cin >> nombre; resultat = Facteur(nombre, carre, cube); if (resultat == SUCCES) { cout << "Nombre: " << nombre << endl;

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

Ce programme produit le rsultat suivant :


Entrez un nombre entre 0 et 20: 3 Valeur: 3 Carr: 9 Cube: 27

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.

Efcacit du passage de paramtres par rfrence


Le passage par valeur dun objet une fonction implique une copie de cet objet. Lorsque la fonction appele renvoie un objet par valeur, une autre copie a lieu. Au Chapitre 5, nous avons vu que ces objets sont stocks sur la pile. Ces oprations prennent du temps et consomme de la mmoire. Ces cots soient ngligeables pour les petits objets comme les entiers.

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

// constructeur // constructeur de copie // destructeur

SimpleChat::SimpleChat() { cout << "Constructeur de SimpleChat..." << endl; }

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

Ce programme produit le rsultat suivant :


Cration de lobjet Chat... Constructeur de SimpleChat... Appel de FonctionUn... Constructeur de copie de SimpleChat... Retour de FonctionUn...

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++

Passer un pointeur const


Le passage dun pointeur FonctionDeux() a beau tre plus efcace, il nen demeure pas moins dangereux. Cette fonction nest, en effet, pas cense modier lobjet qui lui est pass, or elle reoit son adresse ce qui expose franchement lobjet original des modications et brise la protection offerte par le passage par valeur. Le passage par valeur revient donner un muse une photographie de la pice matresse de votre collection : si un vandale labime, loriginal ne sera pas touch. Le passage par rfrence, au contraire, consiste donner votre adresse au muse et inviter les visiteurs venir chez vous voir cette uvre. La solution consiste passer un pointeur sur une constante SimpleChat, ce qui aura pour effet dinterdire lappel de mthodes modicatrices sur cet objet et donc de le protger contre les modications. Le passage dune rfrence constante permet aux visiteurs de contempler votre peinture, mais leur interdit de laltrer de quelque faon que ce soit. Le Listing 9.11 donne un exemple dutilisation dun paramtre pointeur vers une constante. Listing 9.11 : Passage de pointeur sur un objet constant
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: //Listing9.11 - Passage de pointeurs dobjets #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; }

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++

Ce programme produit le rsultat suivant :


Cration dun objet Chat... Constructeur de SimpleChat... Frisky a 2 ans Frisky a 5 ans Appel de FonctionDeux... Fin de FonctionDeux... Frisky a maintenant 5 ans Frisky a 5 ans Destructeur de SimpleChat...

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 !

Une autre solution : le passage de rfrences


Dans le Listing 9.11, le programme passe les paramtres par rfrence, ce qui vite de crer des copies locales et donc dappeler le constructeur de copie et le destructeur. Il utilise pour cela des pointeurs constants sur des objets constants an dempcher la fonction de modier lobjet. Toutefois, le passage de pointeurs exige une notation assez lourde.

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

Ce programme produit le rsultat suivant :


Cration dun objet Chat... Constructeur de SimpleChat... Frisky a 2 ans Frisky a 5 ans Appel de FunctionDeux Fin de FunctionDeux... Frisky a maintenant 5 ans Frisky a 5 ans Destructeur de SimpleChat...

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

Choisir entre rfrences et pointeurs


Les dveloppeurs expriments prfrent souvent les rfrences aux pointeurs parce quelles sont plus faciles utiliser, plus lisibles et quelles permettent de mieux masquer les informations, comme on la vu dans le Listing 9.12. Nanmoins, une rfrence ne peut pas tre raffecte. Si vous devez dsigner dabord un objet, puis un autre, vous devez donc utiliser un pointeur. En outre, une rfrence ne pouvant jamais tre nulle, vous ne pouvez pas utiliser une rfrence si lobjet dsign risque dtre nul ; vous devez utiliser un pointeur. Supposons que vous vouliez rserver un emplacement mmoire pour un objet. Si le motcl new est incapable dallouer cet emplacement sur le tas, il renvoie un pointeur nul. Une rfrence ne pouvant jamais tre nulle, vous ne devez pas initialiser une rfrence avec cet emplacement tant que vous navez pas vri quil nest pas nul. Lexemple suivant montre comment procder :
int *pInt = new int; if (pInt!= NULL) int &rInt = *pInt;

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

laide du mot-cl const.

variable. Cest impossible.

Mlanger les rfrences et les pointeurs dans une liste de paramtres


Il est tout fait possible de mlanger des objets passs par valeur, des pointeurs et des rfrences dans la mme liste de paramtres. Voici un exemple :
Chat * Fonction(Utilisateur &Proprio, Objet *unObjet, int age);

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.

Renvoi de rfrences dobjet hors de porte


Les programmeurs C++ qui ont pris lhabitude de passer des paramtres par rfrence ont tendance abuser de cette pratique. Une rfrence correspond lalias dun objet existant. Lorsque vous passez ou rcuprez une rfrence dans une fonction, posez-vous la question suivante : "Quel est lobjet que je rfrence et existera-t-il toujours chaque fois quil est utilis ?". Le Listing 9.13 illustre le danger du renvoi dune rfrence un objet qui nexiste plus. Listing 9.13 : Renvoi dune rfrence un objet inexistant
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: // Listing9.13 // Renvoi dune rfrence un objet // qui nexiste plus #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 = poids; } SimpleChat &UneFonction();

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.

Renvoi dune rfrence un objet dans le tas


On pourrait tre tent de rsoudre le problme prcdent en faisant en sorte que la fonction UneFonction() cre Frisky sur le tas. De cette faon, Frisky continuerait dexister aprs lexcution de la fonction. Le problme de cette approche est alors ce quil advient de la mmoire alloue lobjet Frisky lorsque lon nen a plus besoin. Le Listing 9.14 illustre ce problme.

Chapitre 9

Rfrences

273

Listing 9.14 : Fuites mmoire


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: 43: 44: // Listing9.14 - Rsoudre les problmes de fuites mmoire #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 weight) { sonAge = age; sonPoids = poids; } SimpleChat & UneFonction(); int main() { SimpleChat & rChat = UneFonction(); int age = rChat.GetAge(); std::cout << "rChat a " << age << " ans!" << std::endl; std::cout << "&rChat : " << &rChat << std::endl; // Comment se dbarrasser de cette mmoire? SimpleChat * pChat = &rChat; delete pChat; // rChat fait maintenant rfrence ?? return 0; } SimpleChat &UneFonction() { SimpleChat * pFrisky = new SimpleChat(5, 9); std::cout << "pFrisky : " << pFrisky << std::endl; return *pFrisky; }

274

Le langage C++

Ce programme produit le rsultat suivant :


pFrisky : 0x00431C60 rChat a 5 ans! &rChat : 0x00431C60

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

O le pointeur est-il pass ?


Lorsquun programme alloue de lespace mmoire sur le tas, il faut imprativement conserver un pointeur vers cet emplacement. Dans le cas contraire, la mmoire ne peut plus tre libre ce que entrane une fuite mmoire. Durant le droulement du programme, ce pointeur passe dune fonction une autre. En rgle gnrale, la valeur place dans le bloc de mmoire est passe laide de rfrences et cest la fonction qui a cr cet emplacement qui le supprime galement. Mais il ne sagit que dune rgle gnrale qui nest pas grave dans le marbre. Toutefois, il est dangereux de supprimer une zone mmoire cr dans une fonction partir dune autre fonction. En effet, le pointeur risque de ne pas tre supprim ou de ltre deux fois, ce qui produit des erreurs dexcution dans le programme. Il est plus sr de construire vos fonctions de sorte quelles librent les espaces quelles ont elles-mmes allous. Si vous crivez une fonction qui doit allouer de la mmoire puis la repasser la fonction appelante, vous devriez sans doute modier votre interface pour faire en sorte que ce soit la fonction appelante qui alloue la mmoire, puis la passe par rfrence votre fonction. Cette approche dplace toute la gestion de la mmoire de votre programme vers la fonction qui est prpare pour la supprimer.
Faire
Passer les paramtres par valeur si ncessaire. Renvoyer le rsultat dune fonction par valeur

Ne pas faire
Renvoyer un lment par rfrence si sa

porte est locale.


Perdre la trace du moment et de lendroit o

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.

Testez vos connaissances


1. Quelle est la diffrence entre une rfrence et un pointeur ? 2. Quand faut-il prfrer les pointeurs aux rfrences ? 3. Que renvoie new lorsquil ny a plus assez de mmoire pour crer un nouvel objet ? 4. Quest-ce quune rfrence constante ? 5. Quelle est la diffrence entre le passage dobjets par rfrence et le passage dune rfrence ? 6. Lors de la dclaration dune rfrence, que faut-il crire ? a. int& b. int c. int & maRef = monInt; maRef = monInt; &maRef = monInt;

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

9. Corrigez le programme de lexercice prcdent.

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.

Fonctions membres surcharges


Au Chapitre 5, nous avons vu comment implmenter le polymorphisme de fonctions, ou surcharge de fonctions, qui consiste crer au moins deux fonctions de mme nom mais avec des paramtres diffrents. Cette technique concerne galement les fonctions membres des classes (ou mthodes).

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

Ce programme produit le rsultat suivant :


DessinerForme() : ****************************** ****************************** ****************************** ****************************** ****************************** DessinerForme(40, 2) : ************************************************************ ************************************************************

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.

Utilisation de valeurs par dfaut


Tout comme les fonctions globales, les mthodes dune classe peuvent inclure une ou plusieurs valeurs par dfaut, comme le montre le Listing 10.2. Listing 10.2 : Utilisation de valeurs par dfaut
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: //Listing10.2 Valeurs par dfaut dans les fonctions membres #include <iostream> using namespace std; // Dclaration de la classe Rectangle class Rectangle { public: // constructeurs Rectangle(int largeur, int hauteur); ~Rectangle(){} void DessinerForme(int uneLargeur, int uneHauteur, bool ValsActuelles = false) const; private: int saLargeur; int saHauteur; }; // Implmentation du constructeur Rectangle::Rectangle(int largeur, int hauteur): saLargeur(largeur), // initialisations saHauteur(hauteur) {} // le corps est vide

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++

Ce programme produit le rsultat suivant :


DessinerForme(0, 0, true)... ****************************** ****************************** ****************************** ****************************** ****************************** DessinerForme(40, 2)... ************************************************************ ************************************************************

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.

Choisir entre des valeurs par dfaut et des fonctions surcharges


Les deux premiers programmes de ce chapitre ralisent les mmes oprations. Pourtant, les fonctions surcharges du Listing 10.1 sont plus faciles utiliser et comprendre. En outre, il est plus simple dtendre la surcharge en crant, par exemple, une troisime fonction si lutilisateur souhaite entrer uniquement la hauteur ou la largeur du rectangle mais pas les deux. En revanche, une valeur par dfaut deviendrait rapidement trs complexe mesure que lon ajouterait des variantes.

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.

Le constructeur par dfaut


Le rle dun constructeur est de dnir un objet ; celui dun constructeur Rectangle, par exemple, est de construire un objet rectangle valide. Avant le lancement du constructeur, il nexiste pas dobjet rectangle mais uniquement un espace en mmoire. Aprs son excution, il existe un objet prt lemploi. Cest un avantage essentiel de la programmation oriente objet : le programme appelant na rien faire pour sassurer que lobjet dmarre dans un tat cohrent. Comme nous lavons vu au Chapitre 6, si vous ne dnissez pas explicitement de constructeur pour une classe, le compilateur cre pour vous un constructeur par dfaut, qui nattend aucun paramtre et qui ne fait rien. Bien entendu, vous avez la possibilit de dclarer votre propre constructeur par dfaut, dpourvu de paramtres, mais permettant de "mettre en place" lobjet comme vous le souhaitez Ce constructeur est appel constructeur "par dfaut" bien quil soit dni par lutilisateur ce terme dsigne en fait tout constructeur qui nattend pas de paramtre. Si vous dnissez un ou plusieurs constructeurs dans une classe, le compilateur ne produira pas de constructeur par dfaut pour celle-ci. Si vous en avez besoin dun, vous devez donc le crer vous-mme.

Info

Surcharge des constructeurs


Les constructeurs, comme nimporte quelle fonction membre, peuvent tre surchargs, ce qui leur ajoute de la puissance et de la souplesse. Par exemple, les objets rectangles pourraient avoir deux constructeurs. Lun tracerait un rectangle partir de la longueur et de la hauteur entres par lutilisateur alors que lautre ne prendrait aucune valeur et dnirait un rectangle par dfaut. Le Listing 10.3 implmente ces deux constructeurs.

286

Le langage C++

Listing 10.3 : Surcharge dun constructeur


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: 35a: 36: 37: 38: 39: 40: 41: 42: 43: // Listing10.3 - surcharge de constructeurs #include <iostream> using namespace std; class Rectangle { public: Rectangle(); Rectangle(int largeur, int longueur); ~Rectangle() {} int GetLargeur() const { return saLargeur; } int GetLongueur() const { return saLongueur; } private: int saLargeur; int saLongueur; }; Rectangle::Rectangle() { saLargeur = 5; saLongueur = 10; } Rectangle::Rectangle (int largeur, int longueur) { saLargeur = largeur; saLongueur = longueur; } int main() { Rectangle Rect1; cout << "Largeur de Rect1: " << Rect1.GetLargeur() << endl; cout << "Longueur de Rect1: " << Rect1.GetLongueur() << endl; int uneLargeur, uneLongueur; cout << "Entrez la largeur: "; cin >> uneLargeur; cout << "\nEntrez la longueur: "; cin >> uneLongueur; Rectangle Rect2(uneLargeur, uneLongueur);

Chapitre 10

Fonctions avances

287

44: 44a: 45: 45a: 46: 47:

cout << "\nLargeur de Rect2: " << Rect2.GetLargeur() << endl; cout << "Longueur de Rect2: " << Rect2.GetLongueur() << endl; return 0; }

Ce programme produit le rsultat suivant :


Largeur de Rect1: 5 Longueur de Rect1: 10 Entrez la largeur : 20 Entrez la longueur: 50 Largeur de Rect2: 20 Longueur de Rect2: 50

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.

Initialisation des objets


Jusqu prsent, vous avez dni les variables membres des objets dans le corps du constructeur. Les constructeurs, cependant, sont invoqus en deux tapes : la phase dinitialisation et lexcution de leurs corps. La plupart des variables peuvent tre dnies durant lune ou lautre de ces tapes, soit lors de la phase initialisation, soit par affectation de valeurs dans le corps du constructeur. Il est plus propre et gnralement plus lisible dinitialiser les variables membres lors de la phase dinitialisation. Voici un exemple dinitialisation des variables membre dun Chat :
Chat(): sonAge(5), sonPoids(8) { } // nom et paramtres du constructeur // liste dinitialisation // corps du constructeur

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

Tas 5 nouvel 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; }

// accs public // accs priv

Ce programme produit le rsultat suivant :


ge de Frisky : 5 Lge de Frisky est fix 6 ans... Cration de lobjet Boots partir de Frisky ge de Frisky : 6 ge de Boots : 6 Lge de Frisky est fix 7 ans... ge de Frisky : 7 ge de Boots : 6

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

Figure 10.3 Illustration dune copie en profondeur.

Tas 5

5 ancien objet Chat nouvel objet Chat

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.

Surcharge des oprateurs


C++ dispose dun certain nombre de types prdnis (int, char, float, etc.) et chacun deux possde un certain nombre doprateurs (+, *, etc.) permettant de raliser des oprations mathmatiques ou logiques. Vous pouvez galement ajouter ces oprateurs vos propres classes. La surcharge doprateur est une technique que nous allons tudier dans le Listing 10.6. Nous allons crer une classe Compteur qui servira au comptage (quelle surprise !) dans les boucles et les autres situations o lon a besoin dincrmenter, dcrmenter ou tester un nombre. Listing 10.6 : Implmentation de la classe Counter
1: 2: 3: 4: // Listing10.6 - La classe Compteur #include <iostream> using namespace std;

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

Ce programme produit le rsultat suivant :


La valeur de i est 0

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 !

Implmentation dune fonction dincrmentation


La surcharge doprateurs va permettre de restituer un certain nombre des fonctionnalits qui ont t supprimes de cette classe. Il y a deux moyens, par exemple, dajouter la possibilit dincrmenter un objet Compteur. Le premier consiste crire une mthode dincrmentation, comme dans le Listing 10.7.

Chapitre 10

Fonctions avances

295

Listing 10.7 : Ajout dun oprateur dincrmentation


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: // Listing10.7 - La classe Compteur #include <iostream> using namespace std; class Compteur { public: Compteur(); ~Compteur(){} int GetVal() const { return saVal; } void SetVal(int x) {saVal = x; } void Increment() { ++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; return 0; }

Ce programme produit le rsultat suivant :


La valeur de iest 0 La valeur de iest 1

La fonction Increment est dnie la ligne 13. Bien quelle fonctionne, elle est assez lourde utiliser. Il serait ici plus judicieux dajouter un oprateur ++.

Surcharger loprateur prxe


Les oprateurs prxes peuvent tre surchargs laide dune dclaration de la forme :
Type_de_valeur_renvoyee operatorop()

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

Ce programme produit le rsultat suivant :


La valeur de i est 0 La valeur de i est 1 La valeur de i est 2

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

Renvoi de valeurs partir des oprateurs surchargs


Votre objectif est de renvoyer un objet Compteur qui pourra tre affect un autre objet de cette classe. Quel objet devez-vous rcuprer aprs traitement ? Une solution consiste crer un objet temporaire dans la fonction, puis le renvoyer, comme dans le Listing 10.9. Listing 10.9 : Renvoi dun objet temporaire
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: // Listing10.9 - operator++ renvoie un objet temporaire #include <iostream> using namespace std; class Compteur { public: Compteur (); ~Compteur (){} int GetVal()const { return itsVal; }

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

Ce programme produit le rsultat suivant :


La valeur La valeur La valeur Valeur de de i est de i est de i est a: 3 et 0 1 2 de i: 3

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

Renvoi de variables temporaires anonymes


Il nest pas toujours utile dattribuer un nom une variable temporaire comme celle de la ligne 29. Si Compteur avait un constructeur qui prenait une valeur en paramtre, vous auriez pu simplement renvoyer le rsultat de ce constructeur comme valeur de retour de loprateur dincrmentation. Cest ce que nous faisons dans le Listing 10.10. Listing 10.10 : Renvoi dun objet temporaire anonyme
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: // Listing10.10 - operator++ renvoie un objet temporaire anonyme #include <iostream> using namespace std; class Compteur { public: Compteur (); Compteur (int val); ~Compteur (){} int GetVal()const { return saVal; } void SetVal(int x) {saVal = x; } void Increment() { ++saVal; } Compteur operator++ (); private: int saVal; }; Compteur::Compteur (): saVal(0) {} Compteur::Compteur (int val): saVal(val) {} Compteur Compteur::operator++() { ++saVal; return Compteur(saVal); } int main() { Compteur i;

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

Ce programme produit le rsultat suivant :


La valeur de i La valeur de i La valeur de i Valeurs de a: est 0 est 1 est 2 3 et de i: 3

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.

Utilisation du pointeur this


Le pointeur this est automatiquement pass toutes les fonctions membres, y compris les oprateurs surchargs comme operator++(). Dans les listings que nous avons montrs, this pointe vers i. Une fois drfrenc, il renvoie lobjet i qui a dj la bonne valeur dans sa variable membre itsVal. Le Listing 10.11 illustre le renvoi du pointeur this drfrenc an dconomiser la cration dun objet temporaire superu. Listing 10.11 : Renvoi du pointeur this
1: 2: 3: // Listing10.11 - Renvoi du pointeur this drfrenc #include <iostream>

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

Ce programme produit le rsultat suivant :


La valeur La valeur La valeur Valeur de de i est de i est de i est a: 3 et 0 1 2 de i: 3

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.

Surcharger loprateur sufxe


Sil est possible de surcharger un oprateur prxe (voir plus haut), pourquoi ne pourrions-nous pas galement surcharger loprateur sufxe dincrmentation ? Comment le compilateur va-t-il distinguer le prxe du sufxe ? Par convention, il suft dindiquer un paramtre entier dans la dclaration de loprateur. La valeur quil contient ne sera pas prise en compte, mais il servira signaler quil sagit dun oprateur sufxe.

Diffrence entre oprateurs prxe et sufxe


Les deux oprateurs prxe et sufxe ont t tudis au Chapitre 4 (voir Listing 4.3). Vous avez vu que prxe signie "on incrmente puis on utilise", alors que sufxe signie "on utilise puis on incrmente".

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

Ce programme produit le rsultat suivant :


La valeur La valeur La valeur Valeur de Valeur de de i est de i est de i est a: 3 et a: 3 et 0 1 2 de i: 3 de i: 4

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.

Surcharge des oprateurs mathmatiques binaires


Loprateur dincrmentation est un oprateur unaire, car il nagit que sur un objet. La plupart des oprateurs mathmatiques sont binaires ; ils portent sur deux objets (un de

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

Ce programme produit le rsultat suivant :


varUne: 2 varDeux: 4 varTrois: 6

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.

Surcharge de loprateur daddition (oprateur +)


La surcharge de loprateur + permet dutiliser la classe Compteur de faon plus naturelle. On rappelle que, pour surcharger un oprateur, il faut utiliser la syntaxe suivante :
TypeRetour operator op()

Le Listing 10.4 surcharge loprateur daddition en utilisant cette fonctionnalit.

Chapitre 10

Fonctions avances

307

Listing 10.14 : 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: 35: 36: 37: 38: 39: 40: 41: 42: // Listing10.14 - Surcharge de loprateur + #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 operator+ (const Compteur&); private: int saVal; }; Compteur::Compteur(int valInit): saVal(valInit) {} Compteur::Compteur (): saVal(0) {} Compteur Compteur::operator+(const Compteur & rhs) { return Compteur(saVal+rhs.GetVal()); } int main() { Compteur varUne(2), varDeux(4), varTrois; varTrois = varUne+varDeux; cout << "varUne: " << varUne.GetVal()<< endl; cout << "varDeux: " << varDeux.GetVal() << endl; cout << "varTrois: " << varTrois.GetVal() << endl; return 0; }

308

Le langage C++

Ce programme produit le rsultat suivant :


varUne: 2 varDeux: 4 varTrois: 6

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;

Ce qui est traduit par le compilateur en :


varTrois = varUne.operator+(varDeux);

(vous auriez dailleurs pu lcrire galement sous cette forme). La mthode operator+() est appele sur loprande gauche et reoit loprande droit en paramtre.

Pour en savoir plus sur la surcharge doprateurs


Les oprateurs surchargs peuvent tre des mthodes membres, comme on vient de le montrer, ou des fonctions standard. Cette deuxime possibilit sera dcrite au Chapitre 15 lorsque nous aborderons les fonctions amies. Les seuls oprateurs qui doivent tre membres dune classe sont loprateur daffectation (=), loprateur dindexation ([]), loprateur dappel de fonction (()) et loprateur dindirection (->). Loprateur [] sera prsent au Chapitre 13, alors que la surcharge de loprateur -> sera traite au Chapitre 15 lorsque nous prsenterons les pointeurs intelligents.

Chapitre 10

Fonctions avances

309

Restrictions sur la surcharge des oprateurs


Les oprateurs des types prdnis (comme int) ne peuvent pas tre surchargs. Par ailleurs, leur priorit ne peut pas tre modie, pas plus que leur arit (unaire ou binaire). Vous ne pouvez pas non plus crer de nouveaux oprateurs : il est par exemple impossible de crer un oprateur ** pour implmenter lopration "puissance de". Larit dun oprateur indique le nombre de termes utiliss avec celui-ci. Certains oprateurs C++ sont unaires et agissent sur un seul terme ( maValeur++). Dautres sont binaires et portent sur deux termes (a+b). Il nexiste quun seul oprateur ternaire (trois termes), loprateur ? et cest la raison pour laquelle on le nomme souvent tout simplement loprateur ternaire ( a > b? x: y).

Info

Savoir utiliser la surcharge


La surcharge doprateur est lune des fonctionnalits dont abusent le plus les programmeurs C++ dbutants. Il est en effet tentant de dnir de nouvelles fonctionnalits pour certains des oprateurs les plus obscurs, mais cela a pour effet irrmdiable de rendre le code confus et difcile relire. Il est peut tre amusant de transformer les oprateurs, en dcidant par exemple que loprateur + et loprateur * permettront deffectuer, respectivement, des divisions et des additions, mais aucun dveloppeur srieux ne sy risquerait. Le plus grand danger vient dune utilisation bien intentionne mais originale des oprateurs utiliser + pour effectuer une concatnation de chane ou / pour dcouper une chane, par exemple. Il y a de bonnes raisons de penser ces utilisations, mais il y en a encore de meilleures pour sen mer. Noubliez pas que le but de la surcharge des oprateurs est damliorer la lisibilit du code et de faciliter lutilisation des objets.
Faire
Surcharger les oprateurs pour simplier la

Ne pas faire
Crer des oprateurs arithmtiques "non

mise en uvre et la maintenance dun programme.


Renvoyer un objet de la classe partir des

intuitifs".
Confondre les oprateurs de prxe et de

sufxe, en particulier lors dune surcharge.

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

Listing 10.15 : Oprateur daffectation


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: 43: 44: 45: // Listing10.15 - Loprateur daffectation #include <iostream> using namespace std; class Chat { public: Chat(); // constructeur par dfaut // constructeur de copie et destructeur absents! int GetAge() const { return *sonAge; } int GetPoids() const { return *sonPoids; } void SetAge(int age) { *sonAge = age; } Chat & operator=(const Chat &); private: int *sonAge; int *sonPoids; }; Chat::Chat() { sonAge = new int; sonPoids = new int; *sonAge = 5; *sonPoids = 9; }

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++

46: 47: 48: 49: 50: 51: 52: 53:

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 produit le rsultat suivant :


ge de Frisky: 5 Lge de Frisky est fix a 6... ge de Minou : 5 Copie de Frisky dans Minou... ge de Minou : 6

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.

Conversion de type (transtypage)


Maintenant que vous savez affecter un objet un autre objet du mme type, considrons une autre situation. Que se passera-t-il si vous tentez daffecter une variable dun type prdni (int ou unsigned short, par exemple) un objet dune classe dnie par lutilisateur ? Prenons lexemple de la classe Compteur cre prcdemment : que

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

Ce programme ne se compilera pas !

Listing 10.16 : Tentative daffectation dun int un 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: // Listing10.16 - Ce code provoquera une erreur de compilation! #include <iostream> using namespace std; class Compteur { public: Compteur(); ~Compteur(){} int GetVal()const {return saVal; } void SetVal(int x) {saVal = x; } private: int saVal; }; Compteur::Compteur (): saVal(0) {} int main() { int unInt = 5; Compteur unCpt = unInt; cout << "unCpt: " << unCpt.GetVal() << endl; return 0; }

La compilation de ce programme produira le message suivant :


Compiler error! Unable to convert int to Compteur

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

Ce programme produit le rsultat suivant :


unCpt: 5

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

Ce programme produit le rsultat suivant :


unInt: 5

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.

Testez vos connaissances


1. En quoi les fonctions surcharges doivent-elles tre diffrentes ? 2. Quelle est la diffrence entre une dclaration et une dnition ? 3. Quand un constructeur de copie est-il appel ? 4. Quand le destructeur est-il appel ? 5. En quoi le constructeur de copie diffre-t-il de loprateur daffectation ( =) ? 6. Quest-ce que le pointeur this ? 7. Quelle est la diffrence entre la surcharge dun oprateur prxe dincrmentation et la surcharge dun oprateur sufxe ? 8. Est-il possible de surcharger loprateur + avec des entiers courts ? 9. Peut-on surcharger loprateur ++ pour quil dcrmente un objet dune classe ? 10. Quelle type de rsultat doit gurer dans la dclaration des oprateurs de conversion ?

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

10. CHERCHEZ LERREUR : limplmentation de loprateur daddition est errone.


EntierCourt EntierCourt::operator+(const EntierCourt& rhs) { saVal += rhs.GetVal(); 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.

Conception : le langage de modlisation


Le langage de modlisation est laspect le moins important de lanalyse et la conception oriente objet, mais cest malheureusement lui que sintressent principalement la plupart des gens. Un langage de modlisation nest rien de plus quune convention permettant de dnir un modle sur un autre support papier ou systme informatique et dans un autre format comme les graphiques, le texte ou les symboles. Rien ne nous empche, par exemple, de reprsenter nos classes sous la forme de triangles et la relation dhritage comme une ligne pointille. Vous pourriez alors modliser un granium comme sur la Figure 11.1. Cette gure montre quun granium est une sorte particulire de eur. Tant que nous sommes daccord sur cette reprsentation du concept dhritage (gnralisation/spcialisation), nous nous comprendrons parfaitement. Au cours du temps, nous voudrons srement modliser de nombreuses relations complexes et nous dvelopperons donc nos propres conventions et rgles pour nos diagrammes. Il reste bien entendu faire connatre ces conventions toutes les personnes qui travaillent avec nous et les apprendre aux nouveaux venus. Si nous collaborons avec dautres socits qui utilisent leurs propres conventions, nous devrons alors mettre en place une convention commune de manire viter les malentendus invitables.

Chapitre 11

Analyse et conception oriente objet

323

Figure 11.1 Gnralisation/ spcialisation.

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.

Dveloppement itratif ou dveloppement en cascade ?


Le dveloppement itratif se distingue du dveloppement en cascade, dans lequel la sortie dune tape devient lentre de la suivante et o il est impossible de revenir en arrire (voir Figure 11.3). Dans un processus en cascade, les besoins sont dtaills et conrms par le client, ils sont alors transmis au concepteur et gravs sans le marbre. Le concepteur fait son travail puis passe sa conception au programmeur qui limplmente. Le programmeur crit le code qui est test par le Service Qualit, puis livr au client. Belle thorie qui peut se rvler catastrophique en pratique.

Chapitre 11

Analyse et conception oriente objet

325

Figure 11.3 La mthode en cascade.

Analyse Conception Implmentation Tests

Processus du dveloppement itratif


Dans un dveloppement itratif, on part dun concept (une ide de ce que lon veut construire). Cette vision stend et volue mesure que lon examine les dtails. Ds que nous avons une ide des besoins, la conception dmarre tout en sachant que les questions qui vont se poser entraineront des modications qui auront probablement un effet sur les besoins. Tout en travaillant sur la conception, on commence galement le prototypage puis limplmentation du produit. Les problmes survenant pendant le dveloppement ont une rpercussion sur la conception et peuvent mme inuencer notre comprhension des besoins. Nous ne concevons et implmentons que des parties du produit complet, en rebouclant continuellement dans les phases de conception et dimplmentation. Bien que les tapes du processus se rptent, il est pratiquement impossible de les dcrire de manire cyclique ; cest la raison pour laquelle la liste qui suit les dcrit de manire squentielle. tapes du processus de dveloppement itratif : 1. Conceptualisation. La conceptualisation est la "vision". Cest une simple phrase qui dcrit la grande ide. 2. Analyse. Lanalyse est le processus de comprhension des besoins. 3. Conception. La conception est la phase de cration du modle de vos classes, partir duquel vous produirez votre code. 4. Implmentation. Limplmentation est la transcription dans le langage choisi (C++, par exemple). 5. Tests. Les tests consistent vrier que le travail a t bien fait.

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

Ni de son workow, qui est constitu des tapes suivantes :


Modlisation dactivit ; Besoins ; Analyse et conception ; Implmentation ; Tests ; Dploiement ; Conguration et gestion des modications ; Gestion de projet ; Environnement.

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

Analyse et conception oriente objet

327

tape 1 La conceptualisation : commencer par la vision


Tout bon logiciel commence par une vision, un produit ou un service que quelquun pense intressant de dvelopper. La vision dun projet est rarement le fait dun comit. La toute premire phase dune analyse et dune conception orientes objet consiste capturer cette vision sous la forme dune simple phrase (ou, au plus, dun court paragraphe), qui deviendra le principe directeur du dveloppement. Lquipe qui sera charge dimplmenter cette vision pourra sy rfrer et la modier si ncessaire mesure de lavancement du projet. Mme si lnonc de la vision a t produit par un comit du dpartement nancier, une seule personne devrait tre considre comme le "visionnaire". Son travail consiste tre le gardien de la lumire sacre. mesure que le projet avance, les besoins voluent ; les contraintes de planication et de livraison peuvent (et devraient) modier ce que vous tentez daccomplir la premire itration du programme, mais le visionnaire doit garder un il sur lide essentielle pour sassurer que ce qui est produit rete trs dlement la vision initiale. Cest ce dvouement sans faille, passionn, qui permettra damener le projet ses ns. Si cette vision est perdue, le produit lest aussi. La phase de conceptualisation, dans laquelle sarticule la vision, est trs courte. Elle ne doit pas dpasser le temps du ash rvlateur et de sa retranscription. Dans dautres projets, la vision exige une phase de dtermination de lenvergure plus complexe, o il faut parvenir un accord sur les composants, entre les personnes impliques. Dans ce cas, le succs du projet sera fonction des lments retenus ou refuss.

tape 2 Lanalyse : collecter les besoins


Certaines socits confondent la vision et les besoins. Une bonne vision est ncessaire, mais pas sufsante. Pour passer la conception, il faut comprendre lutilisation qui sera faite du produit et le comportement quil devra avoir. Lobjectif de lanalyse est darticuler et de collecter ces besoins. Son rsultat est un document des besoins, dont la premire partie est lanalyse des case dutilisation (use cases).

Les cas dutilisation


Les use cases , ou cas dutilisation, sont tout simplement une description de haut niveau de la faon dont le produit sera utilis. Ils orientent non seulement lanalyse, mais galement la conception en vous aidant dterminer les classes et sont particulirement importants pour les tests du produit.

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.

Identier les acteurs


Les acteurs ne sont pas ncessairement des personnes, les systmes qui interagissent avec le systme que vous construisez le sont aussi. Si, par exemple, vous construisez un guichet automatique de banque, le client et lemploy de banque sont tous les deux acteurs de mme que tout systme impliqu, comme un systme de prt ou de suivi des dcouverts. Les caractristiques essentielles dun acteur sont :

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

Analyse et conception oriente objet

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.

Cette liste suft pour commencer la gnration de nos cas dutilisation.

Dterminer les premiers cas dutilisation


Dans lexemple du guichet automatique, commenons par le client. Il peut :

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

Analyse et conception oriente objet

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.

Cration du modle du domaine


Aprs avoir tabli un premier dcoupage de vos cas dutilisation, le document des besoins peut tre toff avec un modle du domaine dtaill. Ce document capture tout ce que vous savez du domaine. Les objets du domaine que vous crez, dcrivant tous les objets mentionns dans vos cas dutilisation, en font partie. Notre exemple comprend pour linstant les objets suivants : client, personnel de la banque, systmes back-ofce, compte courant, compte pargne, etc. Pour chacun de ces objets du domaine, nous devons capturer des donnes essentielles comme le nom de lobjet (client, compte, etc.), si lobjet est un acteur, ses attributs et ses comportements principaux, etc. De nombreux outils de modlisation permettent de capturer ce type dinformations dans des descriptions de "classe". La Figure 11.4 montre comment ces informations sont reprsentes avec loutil de modlisation Rational Rose.
Figure 11.4 Rational Rose.

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 :

la gnralisation (ou spcialisation) ; la composition ; lassociation.

Chapitre 11

Analyse et conception oriente objet

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++

Figure 11.7 Relations entre objets.


Compte courant Compte pargne

Compte bancaire

Solde

Historique des transactions

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

tablir des scnarios


Avec nos cas dutilisation prliminaires et les outils permettant de reprsenter les relations entre les objets du domaine, nous pouvons formaliser les cas et les approfondir.

Chapitre 11

Analyse et conception oriente objet

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.

tablir des directives


Vous devez crer des directives pour documenter chaque scnario. Celles-ci sont captures dans vos spcications des besoins. Gnralement, vous devrez vous assurer que chaque scnario comprend :

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 :

Ce cas peut tre reprsent laide du schma simple de la Figure 11.9.


Figure 11.9 Reprsentation dun cas dutilisation.
Acteur Cas d'utilisation

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

Analyse et conception oriente objet

337

Figure 11.10 Le strotype <<utilise>>.

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.

Crer des paquetages


Les cas dutilisation peuvent tre regroups en packages avec UML. Un paquetage ressemble un rpertoire ou un dossier : cest une collection dobjets de modlisation (classes, acteurs, etc.). Pour grer la complexit des cas dutilisation, vous pouvez les regrouper en paquetages en fonction de critres correspondant votre problme. Les cas dutilisation peuvent ainsi tre agrgs par type de compte (compte courant ou compte pargne), par crdit ou dbit, par type de client ou toute autre caractristique qui vous semble judicieuse. En outre, un mme cas dutilisation peut apparatre dans des paquetages diffrents, ce qui offre une grande souplesse.

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.

Analyse des systmes


Certains logiciels sont autonomes et ninteragissent quavec le client nal. Le plus souvent, vous devrez raliser une interface avec un systme existant. L analyse du systme est le processus consistant recueillir tous les dtails concernant le systme avec lequel

Chapitre 11

Analyse et conception oriente objet

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

Analyse et conception oriente objet

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.

Quest-ce que les classes ?


Vous savez maintenant crer des classes. Une mthode de conception formelle exige de sparer le concept de classe C++ de celui de classe de conception, bien quils soient intimement lis. La classe C++ que vous crivez est la mise en uvre de la classe que vous avez conue. Chaque classe de votre conception correspondra une classe dans votre code, mais elles ne doivent pas tre confondues : il est possible dimplmenter vos classes de conception dans un autre langage, auquel cas il faudra modier la syntaxe des dnitions de classes. Cela dit, la plupart du temps nous parlons de ces classes sans faire la distinction car les diffrences sont purement abstraites. Si vous dites que, dans votre modle, votre classe Chat aura une mthode Miauler(), cela signie que vous crirer aussi une mthode Miauler() dans votre classe C++. Vous capturez les classes du modle dans des diagrammes UML et vous capturez les classes C++ de limplmentation dans le code qui est compil. La distinction est subtile, mais signicative. La plus grande difcult, pour bien des dbutants, consiste dterminer lensemble initial des classes et de comprendre ce qui fait une classe bien conue. Une technique trs simple suggre dcrire les scnarios des cas dutilisation puis de crer une classe pour chaque nom. Considrez, par exemple, le scnario suivant : 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

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

Analyse et conception oriente objet

343

Figure 11.12 Classes prliminaires.


Compte Reseau

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

Construction du modle statique


Votre ensemble prliminaire de classes tant tabli, vous pouvez commencer modliser leurs relations et interactions. Nous dcrivons ici dabord le modle statique, puis le modle dynamique, mais, dans le processus rel de conception, vous passerez de lun lautre pour en complter les dtails en ajoutant de nouvelles classes et en les bauchant au fur et mesure de votre progression.

Chapitre 11

Analyse et conception oriente objet

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

Analyse et conception oriente objet

347

Figure 11.13 Fiche CompteCourant.

<<Abstrait>> Compte

CompteCourant Solde : int RetraitsGuichetAutoJour LireSolde() : int Deposer(int montant)() : void VirementCredit(int montant)() : bool VirementDebit() : int EcrireCheques(int montant)() : bool

Relations entre les classes


Lorsque les classes sont transcrites en notation UML, vous pouvez commencer vous proccuper des relations entre les diffrentes classes. Les relations principales modliser sont les suivantes :

gnralisation ; association ; agrgation ; composition.

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 d hritage 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

Analyse et conception oriente objet

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++

Figure 11.17 Modlisation des sous-types.

Auto

Globe

Jupiter

Mars

Venus

Pluton

Figure 11.18 Modlisation du discriminateur.

Auto moteur performance carrosserie

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

Analyse et conception oriente objet

351

Figure 11.19 Un discriminateur en tant que typepuissance.

Auto moteur performance:CaracteristiquesPerformance carrosserie <<typepuissance>> Caractristiques de performances palier vitesses tours/mn puissant berline coup Familiale Acclrer

Familiale:CaracteristiquesPerformance peu puissant Sport Sport:CaracteristiquesPerformance

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

Caractristiques de performances Auto palier vitesses tours/mn Acclrer

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

1: Vrifier soldes 2: Lire solde

3: Afficher solde

4 : Retirer liquide 5 : Distribuer

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

Analyse et conception oriente objet

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

Diagrammes de transition dtat


Chaque objet a plusieurs tats possibles, dont les transitions peuvent tre modlises dans un diagramme dtat ou diagramme de transition dtat. La Figure 11.23 montre les diffrents tats de la classe CompteClient lors de la connexion du client au systme.
Figure 11.23 tats du compte client.
Dbut tat transitoire tat Non connect

Lecture infos du compte

Garde [ID compte valide]

Saisie mot de passe

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

Lecture infos du compte

Annul

Saisie mot de passe

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

Non connect Annulable Annul Lecture infos du compte

Saisie mot de passe

Connect

Fin

Chapitre 11

Analyse et conception oriente objet

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

tapes 4 6 Implmentation, tests et dploiement


Les trois dernires tapes sont importantes, mais ne seront pas traites ici. Si vous utilisez C++, limplmentation sera dtaille dans le reste de cet ouvrage. Les tests et le dploiement sont deux disciplines complexes, qui nentrent pas dans le cadre de ce livre. Noubliez pas tout de mme de tester soigneusement vos classes la fois sparment et ensemble pour vrier votre implmentation et votre conception.

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.

Testez vos connaissances


1. Quelle est la diffrence entre la programmation oriente objet et la programmation procdurale ? 2. Quelles sont les phases de lanalyse et de la conception orientes objet ? 3. Quest-ce que lencapsulation ? 4. Au regard de lanalyse, quest-ce quun domaine ? 5. Au regard de lanalyse, quest-ce quun acteur ? 6. Quest-ce quun cas dutilisation ? 7. Laquelle des afrmations suivantes est vraie ? a.Un chat est une forme danimal spcialise. b.Un animal est une forme spcialise de chat et de chien.

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.

Quest-ce que lhritage ?


Lorsque vous regardez un chien, que voyez-vous ? Une tte sur quatre pattes. Un biologiste verra un ensemble dorganes, un physicien considrera quil sagit datomes exerant des forces, et un zoologiste le classera dans lespce canine domesticus.

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

Figure 12.1 Hirarchie danimaux.

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

Ce programme produit le rsultat suivant :


Le cri du mammifre! Je remue la queue... Fido a 2 ans

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

Lhritage avec les constructeurs et les destructeurs


Par hritage, les objets Chien sont galement des objets Mammifere. Lorsque Fido est cr, son constructeur de base est donc appel pour produire un objet Mammifere. Puis, le constructeur de Chien est appel son tour pour complter lobjet Fido. Comme on na pas fourni de paramtre au constructeur de Chien, cest le constructeur par dfaut qui est appel dans les deux cas. Fido ne peut exister tant quil na pas t totalement construit, ce qui signie que sa partie et sa partie Chien doivent avoir t construites. Cest pour cette raison que les deux constructeurs doivent tre appels. Lorsque lobjet Fido est supprim, le destructeur de Chien est dabord appel, puis cest le tour du destructeur de la partie Mammifere. Chacun deux permet de nettoyer les attributs de sa propre partie de Fido. Le Listing 12.3 met en vidence les appels aux constructeurs et aux destructeurs. Listing 12.3 : Appel des constructeurs et des destructeurs
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: //Listing12.3 Appel des constructeurs et des destructeurs. #include <iostream> using namespace std; enum RACE { GOLDEN, CAIRN, DANDIE, SHETLAND, DOBERMAN, LAB }; class Mammifere { public: // constructeurs Mammifere(); ~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

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

74: 75: 76: 77:

Fido.RemuerQueue(); std::cout << "Fido a " << Fido.GetAge() << " ans" << endl; return 0; }

Ce programme produit le rsultat suivant :


Constructeur de Mammifere... Constructeur de Chien... Le cri du mammifre! Je remue la queue... Fido a 3 ans Destructeur de Chien... Destructeur de Mammifere...

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.

Passer des paramtres aux constructeurs de base


Vous souhaiterez peut-tre initialiser certaines valeurs dans un constructeur de base. Vous pourriez, par exemple, surcharger le constructeur de Mammifere et le constructeur de Chien pour utiliser respectivement un ge et une race spciques. Comment faire pour passer lge et le poids au bon constructeur de Mammifere ? Comment faire pour initialiser le poids dun objet Chien si cette opration nest pas prvue dans la classe Mammifere ? Linitialisation de la classe de base peut avoir lieu pendant celle de la classe drive, en prcisant le nom de la classe de base, suivi des paramtres voulus. Le Listing 12.4 montre comment faire. Listing 12.4 : Surcharge de constructeurs dans des classes drives
1: 1a: 2: 3: 4: 5: 6: 7: 8: 9: // Listing12.4 Surcharge des constructeurs // dans des classes drives #include <iostream> using namespace std; enum RACE { GOLDEN, CAIRN, DANDIE, SHETLAND, DOBERMAN, LAB }; class Mammifere { public:

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

Le rsultat obtenu a t numrot pour faciliter lanalyse du code.

Ce programme produit le rsultat suivant :


1: 2: 3: 4: 5: 6: 7: 8: 9: Constructeur Constructeur Constructeur Constructeur Constructeur Constructeur Constructeur Constructeur Constructeur de de de de de de de de de Mammifere... Chien... Mammifere(int)... Chien(int)... Mammifere(int)... Chien(int, int)... Mammifere(int)... Chien(int, RACE).... Mammifere(int)...

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.

Rednition des mthodes de la classe de base


Un objet Chien a accs toutes les donnes et fonctions membres de la classe Mammifere, ainsi qu ses propres fonctions et donnes membres, comme RemuerQueue(). Une classe drive peut galement rednir une mthode de la classe de base. La rdnition dune mthode modie limplmentation dune mthode de la classe de base dans une classe drive. Quand on rednit une mthode, sa signature doit correspondre celle de la mthode de la classe de base. Une signature de mthode est son prototype, sans le type du rsultat elle est donc forme du nom de la mthode, de la liste de ses paramtres et du mot const sil est prsent. Le rsultat dune mthode rednie peut donc tre diffrent de celui de la mthode dans la classe de base. Dans le Listing 12.5, la classe Chien rednit la mthode Crier() de la classe Mammifere. Pour des raisons despace, les fonctions daccs ont t volontairement omises. Listing 12.5 : Rednition dune mthode de classe de base dans une classe drive
1: 1a: 2: 3: 4: 5: 6: 7: 8: 9: // Listing12.5 Redfinition dune mthode de // classe de base dans une classe drive #include <iostream> using std::cout; enum RACE { GOLDEN, CAIRN, DANDIE, SHETLAND, DOBERMAN, LAB }; class Mammifere { public:

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

Ce programme produit le rsultat suivant :


Constructeur de Mammifere... Constructeur de Mammifere... Constructeur de Chien... Le cri du mammifre!

374

Le langage C++

Ouah Ouah! Destructeur de Chien... Destructeur de Mammifere... Destructeur de Mammifere...

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

Masquage de la mthode de la classe de base


Dans le listing prcdent, la mthode Crier() de la classe Chien cachait la mthode de la classe de base. Cest ce que nous voulions, mais cette opration risque de produire des rsultats inattendus. Sil existe une mthode Bouger() surcharge dans la classe Mammifere et que la classe Chien la rednit, la mthode de la classe Chien masquera toutes les mthodes portant le mme nom dans la classe Mammifere. Supposons que la classe Mammifere surcharge la mthode Bouger() laide de trois fonctions la premire nattend aucun paramtre, la deuxime attend un entier et la dernire attend un entier et une direction et que la classe Chien rednit simplement la mthode Bouger() qui ne prend pas de paramtre. Il sera alors difcile davoir accs aux deux autres mthodes depuis un objet Chien. Le Listing 12.6 illustre ce problme. Listing 12.6 : Masquage de mthodes
1: 2: 3: 4: //Listing12.6 Masquage de mthodes #include <iostream> using std::cout;

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

Ce programme produit le rsultat suivant :


Les mammifres se dplacent dun pas Les mammifres se dplacent de 2 pas Le chien se dplace de 5 pas

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.

Appel de la mthode de base


Si vous avez redni une mthode de la classe de base, il est toujours possible de lappeler en indiquant son nom complet, aprs le nom de la classe de base :
classeBase::Methode()

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

Ce programme produit le rsultat suivant :


Les mammifres se dplacent de 2 pas Les mammifres se dplacent de 6 pas

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

pour en accrotre les fonctionnalits.


Rednir les mthodes de la classe de base

modiant sa signature.
Oublier que const fait partie de la signature. Oublier que le type du rsultat ne fait pas

pour modier le comportement de certaines mthodes dans la classe drive.

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++

26: 27: 28: 29: 30: 31: 32: 33: 34:

int main() { Mammifere *pChien = new Chien; pChien->Bouger(); pChien->Crier(); return 0; }

Ce programme produit le rsultat suivant :


Constructeur de Mammifere... Constructeur de Chien... Les mammifres se deplacent dun pas Ouah Ouah!

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 produit le rsultat suivant :


(1)Chien (2)Chat (3)Cheval (1)Chien (2)Chat (3)Cheval (1)Chien (2)Chat (3)Cheval (1)Chien (2)Chat (3)Cheval (1)Chien (2)Chat (3)Cheval Ouah Ouah ! Miaou! Hihiiiii! Grouiiiik ! Le cri du mammifre! (4)Cochon(4)Cochon(4)Cochon(4)Cochon(4)CochonVotre Votre Votre Votre Votre choix: choix: choix: choix: choix: 1 2 3 4 5

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.

Fonctionnement des mthodes virtuelles


Lorsquon cre un objet driv comme un objet Chien, on appelle dabord le constructeur de la classe de base puis le constructeur de la classe drive. La Figure 12.2 prsente lobjet Chien aprs sa cration : vous remarquerez que la partie Mammifere de lobjet se situe en mmoire ct de la partie Chien.
Figure 12.2 Lobjet Chien aprs sa cration.

Partie Mammifere

Mammifere Objet Chien Chien

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++

Figure 12.3 La v-table dun objet Mammifere.

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.

Accs aux mthodes partir dune classe de base


Nous avons vu des mthodes dune classe drive, auxquelles on a accd partir dune classe de base grce au mcanisme des fonctions virtuelles. Que se passerait-il sil y avait une mthode dans la classe drive qui ne soit pas dans la classe de base ? Pourriez-vous y accder de la mme manire quen utilisant la classe de base pour accder aux mthodes virtuelles ? Il ne devrait pas y avoir de conit de noms puisque seule la classe drive dispose de cette mthode. Si lobjet Chien possde une mthode RemuerQueue() par exemple, qui ne gure pas dans la classe Mammifere, vous ne pouvez pas vous servir du pointeur de la classe de base pour avoir accs la fonction de la classe drive. La mthode RemuerQueue() ntant pas virtuelle et nappartenant pas la classe Mammifere, elle ne peut tre appele si vous ntes ni un Chien ni un pointeur sur celui-ci.

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.

Dcoupage des donnes


La magie des mthodes virtuelles nagit que sur les pointeurs et les rfrences. Passer un objet par valeur ne permet pas dappeler une fonction virtuelle, comme le montre le Listing 12.10. Listing 12.10 : Passage de paramtres par valeur et dcoupage de donnes
1: 1a: 2: 3: 4: 5: 6: 7: 8: 9: 10: 10a: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: // Listing12.10 Passage de paramtres par valeur // et dcoupage de donnes #include <iostream> using namespace std; class Mammifere { public: Mammifere():sonAge(1) { } virtual ~Mammifere() { } virtual void Crier() const { cout << "Le cri du mammifre!\n"; } protected: int sonAge; }; class Chien: public Mammifere { public: void Crier()const { cout << "Ouah Ouah !\n"; } }; class Chat: public Mammifere { public: void Crier()const { cout << "Miaou!\n"; } }; void FoncVal(Mammifere); void FoncPtr(Mammifere*);

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

Ce programme produit le rsultat suivant :


(1)Chien (2)Chat (0)Quitter - Votre choix: 1 Ouah Ouah ! Ouah Ouah ! Le cri du mammifre! (1)Chien (2)Chat (0)Quitter - Votre choix: 2 Miaou! Miaou! Le cri du mammifre! (1)Chien (2)Chat (0)Quitter - Votre choix: 0

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

Constructeurs de copie virtuels


Les constructeurs ne pouvant pas tre virtuels, on ne peut donc techniquement pas crire un constructeur de copie virtuel. Cependant, un programme a parfois dsesprment besoin de pouvoir passer un pointeur vers un objet de la classe de base et dobtenir une copie de lobjet driv correspondant qui a t cr. La solution la plus courante consiste crer une mthode Clone() dans la classe de base en la dclarant virtuelle. Cette mthode cre une copie de lobjet de la classe courante et la renvoie. Toutes les classes drives rednissant la mthode Clone(), la copie cre sera une copie de la classe drive. Cette procdure est mise en uvre dans le Listing 12.11. Listing 12.11 : Constructeur de copie virtuel
1: 2: 3: 4: 5: 6: 7: 8: 8a: 9: 9a: 10: 11: 11a: 12: 12a: //Listing12.11 Constructeur de copie virtuel #include <iostream> using namespace std; class Mammifere { public: Mammifere():sonAge(1) { cout << "Constructeur de Mammifere...\n"; } virtual ~Mammifere() { cout << "Destructeur de Mammifere...\n"; } Mammifere const Mammifere & rhs); virtual void Crier() const { cout << "Le cri du mammifre!\n"; } virtual Mammifere* Clone() { return new Mammifere(*this); }

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;

Ce programme produit le rsultat suivant :


1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: (1)Chien (2)Chat (3)Mammifre - Votre choix : 1 Constructeur de Mammifere... Constructeur de Chien... (1)Chien (2)Chat (3)Mammifre - Votre choix : 2 Constructeur de Mammifere... Constructeur de Chat... (1)Chien (2)Chat (3)Mammifre - Votre choix: 3 Constructeur de Mammifere... Ouah Ouah ! Constructeur de copie de Mammifere... Constructeur de copie de Chien... Miaou! Constructeur de copie de Mammifere... Constructeur de copie de Chat... Le cri du mammifre!

Chapitre 12

Hritage

391

16: 17: 18: 19:

Constructeur de copie de Mammifere... Ouah Ouah ! Miaou! Le cri du mammifre!

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

Avantages et inconvnients des mthodes virtuelles


Les objets qui possdent des mthodes virtuelles consomment plus de mmoire cause de la v-table quils doivent grer. Si votre est trs petite et que vous ne prvoyez pas que dautres classes en hriteront, il est inutile davoir recours aux mthodes virtuelles. La v-table occupe sa place en mmoire ds la cration de la premire fonction virtuelle et sa taille volue peu par la suite (mme si chaque nouvelle entre augmente un peu sa

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

sagez de crer des classes drives.


Utiliser un destructeur virtuel si la classe

comporte au moins une mthode virtuelle.

une classe de base partir dune classe drive.

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.

Testez vos connaissances


1. Quest-ce quune v-table ? 2. Quest-ce quun destructeur virtuel ? 3. Comment dclare-t-on un constructeur virtuel ? 4. Comment cre-t-on un constructeur de copie virtuel ? 5. Comment peut-on appeler une fonction membre dune classe de base partir dune classe drive dans laquelle elle a t rednie ? 6. Comment peut-on appeler une fonction membre dune classe de base partir dune classe drive dans laquelle elle na pas t rednie ? 7. Si une classe de base dclare une fonction virtuelle et que le mot-cl virtual est omis dans la classe drive lors de la rednition de cette fonction, sera-t-elle toujours virtuelle lorsquelle sera hrite par une classe de troisime gnration ? 8. quoi sert le mot-cl protected ?

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++

Forme * pRect = new Rectangle; Fonction(*pRect);

6. CHERCHEZ LERREUR dans cet extrait de programme :


class Forme() { public: Forme(); virtual ~Forme(); virtual Forme(const Forme&); };

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

Quest-ce quun tableau ?


Un tableau est une collection squentielle demplacements en mmoire pouvant contenir, chacun, des donnes de mme type. Chaque emplacement est appel lment du tableau. Pour dclarer un tableau, vous devez indiquer un type, suivi du nom du tableau et dun nombre entre crochets, correspondant au nombre dlments du tableau. Par exemple :
long TableauLong[25];

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

Accs aux lments de tableau


On accde un lment particulier dun tableau en indiquant son dcalage par rapport au dbut du tableau. Les dcalages dlments (appels plus communment indices) tant numrots partir de 0, le premier lment du tableau TableauLong est reprsent par TableauLong[0], le second par TableauLong[1], etc. Cette syntaxe peut prter confusion. La dclaration Tableau[3], par exemple, indique que Tableau contiendra trois lments : Tableau[0], Tableau[1] et Tableau[2]. Plus gnralement, la dclaration Tableau[n] cre un tableau de n lments numrots de Tableau[0] Tableau[n-1]. Ceci est d au fait que lindice est un dcalage et que le premier lment est donc 0 emplacement par rapport au dbut du tableau, le deuxime un emplacement, et ainsi de suite. En consquence, TableauLong[25] est numrot de TableauLong[0] TableauLong[24]. Le Listing 13.1 dclare un tableau de cinq entiers, puis affecte une valeur chacun de ses lments. partir de maintenant, les numros de ligne commenceront par zro pour vous aider vous souvenir que les tableaux C++ commencent aussi par zro !

Info

Listing 13.1 : Utilisation dun tableau dentiers


0: 1: 2: 3: 4: //Listing13.1 - Tableaux #include <iostream> int main() {

Chapitre 13

Tableaux et chanes

397

5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15:

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

Ce programme produit le rsultat suivant :


Valeur Valeur Valeur Valeur Valeur 0: 3 1: 6 2: 9 3: 12 4: 15 de de de de de monTableau[0]: monTableau[1]: monTableau[2]: monTableau[3]: monTableau[4]: 3 6 9 12 15

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++

criture dlments hors limite


Lorsque lon crit une valeur dans un tableau, le compilateur calcule lemplacement de stockage en fonction de la taille de chaque lment et son indice. Supposons, par exemple, que vous souhaitiez stocker une valeur dans TableauLong[5] alors que le tableau ne comprend que 5 lments. Le compilateur va multiplier la valeur du dcalage (5) par la taille de chaque lment (4 octets, ici) et donc dplacer le pointeur de 20 octets par rapport au dbut du tableau. Enn, il va crire la nouvelle valeur cet emplacement. En vrit, le compilateur ralise la copie de la valeur lendroit indiqu, mme si ce dernier se situe en dehors de la limite suprieure du tableau. Par exemple, si vous tentez de stocker une valeur dans TableauLong[50], le compilateur se placera 200 octets partir du dbut du tableau, puis crasera les donnes qui se trouvent cet emplacement en les remplaant par la nouvelle valeur. Cette opration peut, dans le meilleur des cas, produire une erreur immdiate (dans le meilleur des cas). Dans le pire des cas, cette erreur ne surviendra pas tout de suite et vous nirez par obtenir des rsultats bizarres que vous aurez beaucoup de mal expliquer. Le compilateur se comporte comme un aveugle estimant une distance partir dune maison, Maison[0]. Si vous lui demandez daller la sixime maison de la rue, il se dira "je dois aller 5 maisons plus loin. Chaque maison faisant quatre grands pas de large, je dois avancer de 20 pas.". Si vous lui demandez daller Maison[100] alors que la rue na que 25 maisons, il se dplacera de 400 pas et se retrouvera srement sous les roues dun camion avant de les avoir tous parcourus. Vous devez donc tre sr de lendroit o vous lenvoyez. Le Listing 13.2 crit une valeur aprs la limite suprieure dun tableau. Compilez ce listing pour voir les messages derreur et davertissement qui safchent. Sil ny en a pas, faites dautant plus attention lorsque vous travaillerez avec les tableaux !
ntion Atte

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++

Ce programme produirait le rsultat suivant :


Test 1: TableauCible[0]: 10 TableauCible[24]: 10 SentinelUne[0]: 0 SentinelDeux[0]: 0 SentinelUne[1]: 0 SentinelDeux[1]: 0 SentinelUne[2]: 0 SentinelDeux[2]: 0 Affectation en cours... Test 2: TableauCible[0]: 20 TableauCible[24]: 20 TableauCible[25]: 20 SentinelUne[0]: 20 SentinelDeux[0]: 0 SentinelUne[1]: 0 SentinelDeux[1]: 0 SentinelUne[2]: 0 SentinelDeux[2]: 0

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++

Initialisation des tableaux


Lors de la dclaration dun tableau dlments de type prdni (entiers, caractres, par exemple), vous pouvez en proter pour initialiser ses valeurs. Pour ce faire, vous devez placer le signe gal (=) aprs la dclaration du tableau, suivi dune liste de valeurs entre accolades, spares les unes des autres par des virgules. Exemple :
int TableauInt[5] = { 10, 20, 30, 40, 50 };

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

tableau porte le numro de dcalage zro.

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

Ce programme produit le rsultat suivant :


La valeur de Mardi est: 30

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

// affecter le 9e lment de TableauInt neuviemeElt int neuviemeElt = TableauInt[8];


Exemple 2

// affecter le 9e lment de TabPtrSurLongs pLong long * pLong = TabPtrSurLongs[8];


Un tableau de n lments est toujours numrot de 0 n-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; }

Ce programme produit le rsultat suivant :


Chat Chat Chat Chat Chat numro numro numro numro numro 1: 2: 3: 4: 5: 1 3 5 7 9

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.

Initialisation de tableaux plusieurs dimensions


Comme les tableaux une dimension, les tableaux plusieurs dimensions peuvent tre initialiss. Les valeurs sont affectes dans lordre des lments, le dernier indice (celui qui est le plus droite) voluant pendant que le prcdent reste stable (comme un compteur kilomtrique). Si vous avez la dclaration suivante :
int Tableau[5][3];

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++

3: 4: 5: 6: 6a: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16:

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

Ce programme produit le rsultat suivant :


UnTableau[0][0]: UnTableau[0][1]: UnTableau[0][2]: UnTableau[0][3]: UnTableau[0][4]: UnTableau[1][0]: UnTableau[1][1]: UnTableau[1][2]: UnTableau[1][3]: UnTableau[1][4]: 0 1 2 3 4 0 2 4 6 8

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.

Construire des tableaux de pointeurs


Jusqu prsent, les tableaux utiliss stockaient leurs lments dans la pile et, comme vous le savez, lespace disponible dans la pile est limit. Pour plus de libert, vous pouvez dclarer les objets sur le tas et ne stocker dans le tableau que des pointeurs vers ces objets. La quantit requise despace mmoire sen trouvera ainsi considrablement rduite. Le Listing 13.6 effectue les mmes oprations que le programme du Listing 13.4, mais stocke tous les objets sur le tas. Le tableau peut alors passer de 5 500 lments. Il sappelle dsormais Famille et non plus Portee. Listing 13.6 : Stockage dun tableau dans le tas
0: 1: 2: 3: 4: // Listing13.6 - Un tableau de pointeurs sur les objets #include <iostream> using namespace std;

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

Ce programme produit le rsultat suivant :


Chat Chat Chat ... Chat Chat numro 1: 1 numro 2: 3 numro 3: 5 numro 499: 997 numro 500: 999

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.

Larithmtique des pointeurs, un sujet complexe


Les pointeurs ont t introduits au Chapitre 8. Avant de poursuivre avec les tableaux, revenons un instant sur ce sujet pour comprendre larithmtique des pointeurs. Les pointeurs ne permettent que peu de choses du point de vue mathmatique ; ils peuvent tre soustraits les uns des autres. Une technique puissante consiste faire pointer deux pointeurs vers des lments diffrents dun tableau et calculer la diffrence entre ces deux pointeurs pour savoir combien dlments se trouvent entre les deux. Cette technique est particulirement utile pour analyser des tableaux de caractres, comme le montre le Listing 13.7. Listing 13.7 : Analyse des mots partir dune chane de caractres
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: #include <iostream> #include <ctype.h> #include <string.h> bool LireMot(char* chaine, char* mot, int& motDecale); // Programme principal int main() { const int tailleTampon = 255; char tampon[tailleTampon+1]; char mot[tailleTampon+1];

// contient la chane // contient le mot

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 produit le rsultat suivant :


Entrez une Mot obtenu Mot obtenu Mot obtenu Mot obtenu Mot obtenu Mot obtenu Mot obtenu Mot obtenu Mot obtenu Mot obtenu Mot obtenu chane : ce code est apparu pour la premiere fois dans C++ Report : ce : code : est : apparu : pour : la : premiere : fois : dans : C : Report

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.

Dclaration de tableaux sur le tas


Il est possible de stocker un tableau entier sur le tas. Pour cela, vous devez crer un pointeur vers le tableau en utilisant le mot-cl new et loprateur dindexation. Vous obtenez ainsi un pointeur vers une zone du tas contenant le tableau. La ligne suivante, par exemple :
Chat *Famille = new Chat[500];

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.

Pointeurs sur tableau et tableaux de pointeurs


Examinez les trois dclarations suivantes :
1: 2: 3: Chat Famille1[500]; Chat * Famille2[500]; Chat * Famille3 = new Chat[500];

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.

Pointeurs et noms de tableaux


En C++, un nom de tableau est un pointeur constant sur le premier lment du tableau. Par consquent, dans la dclaration suivante :
Chat Famille[50];

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

36: 37: 38: 39: 40: 41: 42:

std::cout << Famille[i].GetAge() << std::endl; } delete [] Famille; return 0; }

Ce programme produit le rsultat suivant :


Chat Chat Chat ... Chat Chat numro 1: 1 numro 2: 3 numro 3: 5 numro 499: 997 numro 500: 999

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.

Suppression des tableaux stocks sur le tas


Que devient la mmoire alloue aux objets Chat lorsque le tableau est dtruit ? Il y a-t-il un risque de fuite mmoire ? La suppression de Famille libre automatiquement toute la mmoire alloue au tableau si vous utilisez delete avec loprateur []. Lorsquil voit les crochets, le compilateur est sufsamment intelligent pour savoir quil doit dtruire chaque objet du tableau et restituer sa mmoire au tas. Pour le constater, modiez la taille du tableau pour la faire passer de 500 10 lments (lignes 27, 28 et 33). Puis supprimez les barres obliques de commentaire devant linstruction cout, la ligne 20. Lorsque le programme atteint la ligne 39 et dtruit le tableau, vous verrez apparaitre les diffrents appels aux destructeurs des objets Chat.

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.

Redimensionner les tableaux en cours dexcution


Le plus gros avantage dallouer des tableaux sur le tas est quil est possible de dterminer la taille du tableau au moment de lexcution, avant de lallouer. Si, par exemple, vous avez demand lutilisateur dentrer la taille dune famille dans une variable appele TailleFamille, vous pourrez ensuite dclarer un tableau Chat de la faon suivante :
Chat *pFamille = new Chat[TailleFamille];

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

Listing 13.9 : Rallocation dun tableau en cours dexcution


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: 28a 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: //Listing13.9 #include <iostream> using namespace std; int main() { int TailleAllocation = 5; int *pTableauDeNombres = new int[TailleAllocation]; int ElementsUtilises = 0; int ElementsMaximumAutorises = TailleAllocation; int NombreSaisi = -1; cout << endl << "Nombre suivant = "; cin >> NombreSaisi; while ( NombreSaisi > 0 ) { pTableauDeNombres[ElementsUtilises++] = NombreSaisi; if ( ElementsUtilises == ElementsMaximumAutorises ) { int *pGrandTableau = new int[ElementsMaximumAutorises+TailleAllocation]; for ( int IndiceCopie = 0; IndiceCopie < ElementsMaximumAutorises; IndiceCopie ++ ) { pGrandTableau[IndiceCopie] = pTableauDeNombres[IndiceCopie]; } delete [] pTableauDeNombres; pTableauDeNombres = pGrandTableau; ElementsMaximumAutorises+= TailleAllocation; } cout << endl << "Nombre suivant = "; cin >> NombreSaisi; } for (int Index = 0; Index < ElementsUtilises; Index++) { cout << pTableauDeNombres[Index] << endl; } return 0; }

420

Le langage C++

Ce programme produit le rsultat suivant :


Nombre suivant = 10 Nombre suivant = 20 Nombre suivant = 30 Nombre suivant = 40 Nombre suivant = 50 Nombre suivant = 60 Nombre suivant = 70 Nombre suivant = 0 10 20 30 40 50 60 70

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

teur sur un tableau.


Oublier de supprimer la mmoire alloue

pointeurs qui pointent sur des tableaux.


Utiliser delete[] pour supprimer un tableau

avec new.

cr sur le tas. delete (sans les crochets) ne supprime que le premier lment.

Tableaux de caractres et chanes


Une chane "de type C" est un tableau de caractres qui se termine par un caractre nul. Jusqu prsent, les seules chanes de ce type que vous avez rencontres taient les constantes chanes anonymes utilises dans les instructions dafchage comme celle-ci :
cout << "bonjour";

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

Il faut noter deux choses dans cette syntaxe :

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

Ce programme produit le rsultat suivant :


Entrez la chane: Bonjour les amis Contenu : Bonjour

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 :

le tampon remplir ; le nombre maximal de caractres lire ; le dlimiteur de n de saisie.

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

Ce programme produit le rsultat suivant :


Entrez une chane: Bonjour les amis Contenu du tampon: Bonjour les amis

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.

Les fonctions strcpy() et strncpy()


La bibliothque C++ fournit plusieurs fonctions permettant de traiter des chanes. C++ hrite de son anctre C les fonctions de manipulations des chanes de type C. Parmi elles, strcpy() et strncpy() permettent de copier une chane vers une autre. strcpy() copie le contenu entier dune chane dans un tampon, tandis que strncpy() ne copie quun nombre donn de caractres. Le Listing 13.12 montre comment utiliser strcpy().

424

Le langage C++

Listing 13.12 : Utilisation de la fonction strcpy()


0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: //Listing13.12 Utiliser strcpy() #include <iostream> #include <string.h> using namespace std; int main() { char Chaine1[] = "Nul nest prophete en son pays"; char Chaine2[80]; strcpy(Chaine2, Chaine1); cout << "Chaine1: " << Chaine1 << endl; cout << "Chaine2: " << Chaine2 << endl; return 0; }

Ce programme produit le rsultat suivant :


Chaine1: Nul nest prophete en son pays Chaine2: Nul nest prophete en son pays

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

5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16:

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

Ce programme produit le rsultat suivant :


Chaine1: Nul nest prophete en son pays Chaine2: Nul nest prophete en son pays

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

Listing 13.14 : Cration dune classe String


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: //Listing13.14 Utilisation dune classe String #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 surchargs char & operator[](unsigned short indice); char operator[](unsigned short indice) const; String operator+(const String&); void operator+=(const String&); String & operator= (const String &); // mthodes daccs unsigned short GetLen() const { return saLongueur; } const char * GetString() const { return saChaine; } private: String (unsigned short); char * saChaine;

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

171: 172: 173: 174: 175:

String s4; s4 = "Pourquoi ca marche?"; cout << "S4:\t" << s4.GetString() << endl; return 0; }

Ce programme produit le rsultat suivant :


S1: S1: temp2: S1: S1[4]: S1: S1[999]: S3: S4: Test initial Bonjour tout le monde ; comment ca va? Bonjour tout le monde; comment ca va? o Bonjxur tout le monde; comment ca va? ? Bonjxur tout le monde; comment ca va? Autre chane Pourquoi ca marche?

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

Listes chanes et autres structures


Les tableaux sont comme des Tupperware. Bien quils puissent contenir de grands volumes de donnes, leur taille est xe. Si vous choisissez une botre trop grande, une partie de lespace ne sera donc pas exploite. linverse, si elle est trop petite, les aliments dborderont et vous serez bien embt. Le Listing 13.9 proposait une solution ce problme. Toutefois, lorsque lon commence utiliser de grands tableaux ou lorsque que lon souhaite dplacer, effacer ou insrer des entres dun tableau, le nombre des allocations et des dsallocations peut tre assez coteux. Les listes chanes ont t conues pour rsoudre ce type de problmes. Une liste chane est une structure de donnes compose de conteneurs de petite taille lis entre eux en fonction des besoins. Dans notre exemple, lobjectif est de concevoir une classe contenant un seul objet (Chat ou Rectangle, par exemple), mais pouvant pointer sur le conteneur suivant. Il suft alors de mettre en place un chanage de conteneurs en crant un nouveau conteneur chaque fois que lon a besoin de stocker un objet et de faire en sorte de le relier aux autres. Les listes chanes sont considres comme un sujet avanc. Vous trouverez plus dinformations dans l Annexe E.

434

Le langage C++

Cration de classes tableaux


Une classe tableau personnalise prsente de nombreux avantage par rapport aux tableaux prdnis. Pour les dbutants, elle permet dj dviter les accs en dehors des bornes. En outre, une telle classe peut tre dynamique : sa cration, elle ne contiendra quun lment et grossira en fonction des besoins au cours du programme. Il est galement possible de trier ou de modier lordre des lments du tableau. Les variantes suivantes sont celles que lon utilise le plus souvent :

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.

Testez vos connaissances


1. Citez le premier et le dernier lment de Tableau[25]. 2. Comment dclare-t-on un tableau plusieurs dimensions ? 3. Initialisez les lments dun tableau dclar comme Tableau[2][3][2]. 4. Combien y a-t-il dlments dans Tableau[10][5][20] ? 5. En quoi une liste chane diffre-t-elle dun tableau ? 6. Combien de caractres sont stocks dans la chane "Jean apprend C++" ? 7. Quel est le dernier lment de la chane "Qui ne dit mot consent" ?

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;

5. CHERCHEZ LERREUR dans ce fragment de code :


unsigned short Tableau[5][4]; for (int i = 0; i<=5; i++) for (int j = 0; j<=4; j++) Tableau[i][j] = 0;

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.

Problmes lis lhritage simple


Alors que vous travaillez avec des classes danimaux, vous dcidez de crer une hirarchie de classes drives Oiseau et Mammiferes. La classe Oiseau comprend la fonction membre Voler(), alors que la classe Mammiferes a t divise en un certain nombre de

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

Listing 14.1 : Si les chevaux savaient voler


0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 10a: 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: // Listing14.1. Si les chevaux savaient voler... // Dplacer Voler() dans Cheval #include <iostream> using namespace std; class Cheval { public: void Galoper() { cout << "Au galop...\n"; } virtual void Voler() { cout << "Les chevaux ne savent pas voler.\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; 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++) { Ranch[i]->Voler(); delete Ranch[i]; } return 0; }

440

Le langage C++

Ce programme produit le rsultat suivant :


(1)Cheval (1)Cheval (1)Cheval (1)Cheval (1)Cheval (2)Pegase (2)Pegase (2)Pegase (2)Pegase (2)Pegase Votre Votre Votre Votre Votre choix: choix: choix: choix: choix: 1 2 1 2 1

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

Ce programme produit le rsultat suivant :


(1)Cheval (1)Cheval (1)Cheval (1)Cheval (1)Cheval Ce Je Ce Je Ce (2)Pegase (2)Pegase (2)Pegase (2)Pegase (2)Pegase Votre Votre Votre Votre Votre choix: choix: choix: choix: choix: 1 2 1 2 1

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.

Ajouter dans deux listes


Ces solutions prsentent un autre inconvnient : si vous avez dclar que Pegase est une classe drive de Cheval, vous ne pouvez pas ajouter un objet Pegase une liste dobjets Oiseau. Vous avez dj t contraint soit dintroduire Voler() dans la classe Cheval, soit de transtyper le pointeur vers le bas et vous ne disposez toujours pas de la fonctionnalit recherche. Il existe une dernire solution avec lhritage simple. Vous pouvez remonter les trois mthodes Voler(), Hennir() et Galoper() dans une classe de base commune Oiseau et Cheval : la classe Animal. Dsormais, au lieu davoir une liste doiseaux et une liste de chevaux, vous pouvez donc utiliser une unique liste danimaux. Cela fonctionne, mais vous nirez par avoir une classe de base qui possde toutes les caractristiques de toutes ses classes descendantes. Pourquoi alors crer ces dernires ? Bien sr, vous pourriez laisser ces mthodes o elles sont et effectuer un transtypage descendant des objets Cheval, Oiseau et Pegase, mais cest encore pire !

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.

classe de base en pointeurs vers des objets des classes drives.

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

Ce programme produit le rsultat suivant :


(1)Cheval (2)Pegase - Votre choix: 1 Constructeur de Cheval... (1)Cheval (2)Pegase - Votre choix: 2 Constructeur de Cheval... Constructeur de Oiseau... Constructeur de Pegase (1)Oiseau (2)Pegase - Votre choix: 1 Constructeur de Oiseau... (1)Oiseau (2)Pegase - Votre choix: 2 Constructeur de Cheval... Constructeur de Oiseau... Constructeur de Pegase Ranch[0]: Hihiii!... Destructeur de Cheval... Ranch[1]: Hihiii!... Destructeur de Pegase.. Destructeur de Oiseau... Destructeur de Cheval... Voliere[0]: Cuicui... Je vole! Je vole! Je vole! Destructeur de Oiseau... Voliere[1]: Hihiii!... Je vole ! Je vole ! Je vole ! Destructeur de Pegase... Destructeur de Oiseau... Destructeur de Cheval...

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

class Pegase: public Cheval, public Oiseau


Exemple 2

class Bardot: public Cheval, public Anesse

Parties dun objet hritage multiple


Lorsquun objet Pegase est cr en mmoire, les deux classes de base font partie de cet objet, comme le montre la Figure 14.1. Cette gure reprsente un objet Pegase entier. Celui-ci contient les mthodes ajoutes la classe Pegase et celles hrites des classes de base.

Chapitre 14

Polymorphisme

449

Figure 14.1 Objet dun hritage multiple.

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.

Constructeurs dobjets de lhritage multiple


Si Pegase drive de Cheval et dOiseau et que chacune de ces classes de base fournit des constructeurs attendant des paramtres, la classe drive va initialiser ces constructeurs tour tour, comme le montre le Listing 14.4. Listing 14.4 : Appel de plusieurs constructeurs
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: // Listing14.4 // Appel de plusieurs constructeurs #include <iostream> using namespace std; typedef int TAILLE; enum COULEUR { Rouge, Vert, Bleu, Jaune, Blanc, Noir, Brun }; class Cheval { public: Cheval(COULEUR couleur, TAILLE taille); 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; } private:

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

Ce programme produit le rsultat suivant :


Constructeur de Cheval... Constructeur de Oiseau... Constructeur de Pegase... Je vole! Je vole! Je vole! Hihiii!... Pegase mesure 5cm et il peut migrer. Au total, 10 personnes croient quil existe. Destructeur de Pegase... Destructeur de Oiseau... Destructeur de Cheval...

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.

Rsolution des ambiguts


Dans le Listing 14.4, les deux classes de base Cheval et Oiseau ont toutes les deux une mthode membre GetCouleur(). Vous remarquerez que nous navons pas appel ces mthodes dans le Listing 14.4 ! Si vous souhaitez connatre la couleur de lobjet Pegase, un problme va se poser puisque la classe drive hrite des classes Oiseau et Cheval. Toutes les deux ont une couleur et leurs mthodes pour lobtenir ont un nom et une signature identiques. Le compilateur est donc incapable de rsoudre lambigut si vous ne corrigez pas le code source. Linstruction :
COULEUR saCouleur = pPeg->GetCouleur();

provoque une erreur de compilation :


Member is ambiguous: Cheval::GetCouleur and Oiseau::GetCouleur

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

Hritage dune classe de base commune


Que se passerait-il si Oiseau et Cheval hritaient toutes les deux de la classe de base Animal ? La Figure 14.2 illustre cette relation.
Figure 14.2 Classes de base communes.

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

Ce programme produit le rsultat suivant :


Constructeur de Animal... Constructeur de Cheval... Constructeur de Animal... Constructeur de Oiseau... Constructeur de Pegase... Pegase a 2 ans. Destructeur de Pegase... Destructeur de Oiseau... Destructeur de Animal... Destructeur de Cheval... Destructeur de Animal...

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

Ce programme produit le rsultat suivant :


Constructeur de Animal... Constructeur de Cheval... Constructeur de Oiseau... Constructeur de Pegase... Pegase a 4 ans Destructeur de Pegase... Destructeur de Oiseau... Destructeur de Cheval... Destructeur de Animal...

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++

Problmes lis lhritage multiple


Bien que lhritage multiple offre plusieurs avantages par rapport lhritage simple, nombreux sont les programmeurs qui hsitent ladopter dans leurs applications. Leurs objections les plus frquents sont une difcult accrue pour le dbogage, lvolution plus complexe et plus risque des hirarchies de classes et le fait que quasiment tout ce qui peut tre ralis par lhritage multiple peut galement ltre sans lui. Certains langages, comme Java et C#, nautorisent dailleurs pas lhritage multiple pour certaines de ces raisons. Ces objections sont recevables et il est souhaitable dviter dajouter une complexit inutile dans ses programmes. Certains dbogueurs ont du mal avec lhritage multiple et certaines conceptions sont inutilement obscurcies par une utilisation irrchie de lhritage multiple.
Faire
Utiliser lhritage multiple lorsquune

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

tage simple suft.

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.

Mixins et classes de fonctionnalits


Les classes de fonctionnalits (ou classes mixin) sont une solution intermdiaire entre lhritage simple et lhritage multiple. La classe Cheval pourrait, par exemple, driver de Animal et de Affichable, cette dernire najoutant que quelques mthodes dafchage dobjets quelconques. Un mixin est une classe permettant dajouter des fonctionnalits spcialises une classe drive sans introduire beaucoup de mthodes ou de donnes supplmentaires. Une classe de fonctionnalits est intgre une classe drive comme nimporte quelle autre classe, en dclarant que la classe drive en hrite publiquement. La seule diffrence est quune classe de fonctionnalits contient peu de donnes, voire aucune. Il sagit bien

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

Types abstraits de donnes (TAD)


Vous aurez souvent besoin de crer une hirarchie pour regrouper vos classes. . Vous pourriez ainsi crer une classe Forme et en driver les classes Rectangle et Cercle. La classe Carre peut son tour driver de Rectangle puisquun carr est un rectangle particulier. Chaque classe drive rednira les mthodes Tracer(), GetSurface(), etc. Le Listing 14.7 prsente une implmentation dpouille de la classe Forme et de ses classes drives Cercle et Rectangle. Listing 14.7 : Classes Forme
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: //Listing14.7. Classes Forme. #include <iostream> using std::cout; using std::cin; using std::endl; class Forme { public: Forme(){} virtual ~Forme(){}

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

Ce programme produit le rsultat suivant :


(1)Cercle x x x x x x x x x x x x x x x x x x x x (2)Rectangle (3)Carr (0)Quitter : 2 x x x x

(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 (1)Cercle (2)Rectangle (3)Carr (0)Quitter :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++, un