Vous êtes sur la page 1sur 269

Initiation la programmation

http://www.free-livres.com/

avec Python et C++


Apprenez simplement les bases de la programmation

Yves Bailly

L E P R O G R A M M E U R

Initiation la programmation
avec Python et C++

Yves Bailly

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 Mise en pages : TyPAO ISBN : 978-2-7440-4086-3 Copyright 2009 Pearson Education France Tous droits rservs

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 ........................................... 1. Mise en place ..................................... 2. Stockage de linformation : les variables .......................................

VII 3 15

10. Mise en abme : la rcursivit ........ 11. Spcicits propres au C++ ............ 12. Ouverture des fentres : programmation graphique .............. 13. Stockage de masse : manipuler les chiers .......................................... 14. Le temps qui passe ..........................

129 139 153 185 193 207 215 229 237 247 251 253

3. Des programmes dans un programme : les fonctions ....................................... 29 4. Linteractivit : changer avec lutilisateur ............................... 5. Ronds-points et embranchements : boucles et alternatives .................................... 6. Des variables composes ................... 7. Les Tours de Hano ........................... 39

15. Rassembler et diffuser : les bibliothques ............................... 16. Introduction OpenGL ..................

47 59 79

17. Aperu de la programmation parallle ............................................. 18. Aperu dautres langages ............... 19. Le mot de la n ................................ Glossaire ................................................. Index .......................................................

8. Le monde modlis : la programmation oriente objet (POO) ........................ 103 9. Hano en objets .................................. 115

Table des matires

Introduction ................................................ IX Un peu dhistoire ..................................... X Le choix des langages ............................. XI Et maintenant ...................................... XII Convention dcriture .................................. XIII

3.2. Les paramtres ................................. 33 3.3. Les blocs dinstructions ................... 35 3.4. Retourner une valeur ........................ 36 3.5. Quand crer une fonction ? .............. 38 CHAPITRE 4. Linteractivit : changer avec lutilisateur .................................... 39 4.1. Salutations ! ..................................... 40 4.2. Obtenir un nombre entier ................. 42 4.3. Obtenir une valeur dcimale ............ 44 4.4. Saisir plusieurs valeurs ..................... 44 CHAPITRE 5. Ronds-points et embranchements : boucles et alternatives ......................................... 47 5.1. Lalternative ..................................... 49 5.2. La boucle .......................................... 54 5.3. Algbre de Boole et algorithmique .. 57 CHAPITRE 6. Des variables composes ...... 59

Partie 1 - Dcouverte
CHAPITRE 1. Mise en place ......................... 1.1. Windows ........................................... 1.2. Mac OS X ........................................ 1.3. Linux ................................................ 1.4. Votre premier programme ................

1
3 5 8 8 9

CHAPITRE 2. Stockage de linformation : les variables ............................................ 15 2.1. Des types des donnes ..................... 2.2. Les entiers ........................................ 2.3. Les dcimaux et la notation scientique .............................................. 2.4. Les chanes de caractres ................. 2.5. Erreurs typiques lies aux variables . 19 20 21 23 25

6.1. Plusieurs variables en une : les tableaux .............................................. 60 6.2. Plusieurs types en un : structures ..... 67 6.3. Variables composes et paramtres .. 72 CHAPITRE 7. Les Tours de Hano ............... 79 7.1. Modlisation du problme ............... 82

CHAPITRE 3. Des programmes dans un programme : les fonctions ...... 29 3.1. Dnir une fonction ......................... 31

VI

Initiation la programmation avec Python et C++

7.2. Conception du jeu .................................. 86 7.3. Implmentation ...................................... 90

CHAPITRE 13. Stockage de masse : manipuler les chiers ............................ 185 13.1. criture .................................................187 13.2. Lecture ..................................................189 13.3. Apart : la ligne de commande .............190 CHAPITRE 14. Le temps qui passe ................... 193 14.1. Ralisation dun chronomtre ...............195

Partie 2 - Dveloppement logiciel

101

CHAPITRE 8. Le monde modlis : la programmation oriente objet (POO) ... 103 8.1. Les vertus de lencapsulation ................. 105 8.2. La rutilisation par lhritage ................. 109 8.3. Hritage et paramtres : le polymorphisme ......................................... 112 CHAPITRE 9. Hano en objets ........................... 115 9.1. Des tours compltes ............................... 116 9.2. Un jeu autonome .................................... 119 9.3. Linterface du jeu ................................... 122 9.4. Le programme principal ......................... 126 CHAPITRE 10. Mise en abme : la rcursivit ................................................ 129 10.1. La solution du problme ...................... 130 10.2. Une fonction rcursive ......................... 132 10.3. Intgration au grand programme .......... 135 CHAPITRE 11. Spcicits propres au C++ ..... 139 11.1. Les pointeurs ........................................ 140 11.2. Mmoire dynamique ............................ 142 11.3. Compilation spare ............................ 144 11.4. Fichiers de construction ....................... 149 CHAPITRE 12. Ouverture des fentres : programmation graphique ......................... 153 12.1. La bibliothque Qt .............................. 154 12.2. Installation des bibliothques Qt et PyQt .......................................................... 155 12.3. Le retour du bonjour ........................... 159 12.4. Un widget personnalis et boutonn .... 162 12.5. Hano graphique ................................... 169

Partie 3 - Pour aller plus loin

205

CHAPITRE 15. Rassembler et diffuser : les bibliothques .......................................... 207 15.1. Hirarchie dun logiciel ........................209 15.2. Dcoupage de Hano .............................210 CHAPITRE 16. Introduction OpenGL ........... 215 16.1. Installation ............................................217 16.2. Triangle et cube de couleur ...................217 16.3. La GLU et autres bibliothques ............226 CHAPITRE 17. Aperu de la programmation parallle ....................................................... 229 17.1. Solution simple pour Hano ..................231 17.2. Solution paralllise .............................232 17.3. Prcautions et contraintes .....................236 CHAPITRE 18. Aperu dautres langages ........ 237 18.1. Ada ........................................................238 18.2. Basic .....................................................240 18.3. C# ..........................................................241 18.4. OCaml ...................................................242 18.5. Pascal ....................................................243 18.6. Ruby ......................................................244 CHAPITRE 19. Le mot de la n ......................... 247 Glossaire ........................................................... 251 Index ................................................................. 253

Introduction

Depuis que vous utilisez un ordinateur, vous avez trs certainement entendu parler de cette activit mystrieuse quest "programmer", exerce par ces tres tranges que sont les "programmeurs". Tout ce que vous voyez sur votre cran est lexpression de programmes. Nous vous proposons, dans cet ouvrage, de passer de lautre ct de lcran, de dcouvrir la programmation. Un ordinateur est un outil, comme un tournevis. Mais la diffrence du tournevis, lordinateur nest pas conu pour excuter une tche prcise et unique : il lest, au contraire, pour tre fortement modulable et adaptable pratiquement tous les contextes possibles. Pour cela, il a la capacit dexcuter un certain nombre dordres, plus communment appels instructions, chacune correspondant une tche simple et lmentaire. Programmer consiste dnir une succession ordonne dinstructions lmentaires devant tre excutes par la machine, pour obtenir laccomplissement dune tche complexe. Un aspect essentiel de la programmation est donc la mise en pratique de la mthode cartsienne : dcomposer un problme complexe en sousproblmes simples. Mais peut-tre vous demandez-vous : "Pourquoi programmer ?" Simplement par jeu ! crire un programme et le voir sexcuter, chercher pourquoi il ne fait pas ce que vous attendiez, est une activit ludique en soi. Plus srieusement, aussi bien en milieu professionnel que familial, on prouve frquemment le besoin dautomatiser certaines choses rptitives et laborieuses, dobtenir certains rsultats, de traiter certaines donnes pour lesquelles on ne dispose

Initiation la programmation avec Python et C++

pas de logiciel tout prt ou alors, ceux qui existent sont mal adapts ou trop onreux acqurir. Dans ce genre de situations, avoir la capacit dcrire un petit programme peut faire gagner beaucoup de temps et de prcision, de limiter les efforts. Voici un autre aspect important de la programmation : elle est bien souvent la meilleure rponse la paresse !

Un peu dhistoire
Les premiers ordinateurs taient programms laide de cartes perfores, les instructions tant donnes sous la forme de longues suites de 0 et de 1 quil fallait entrer manuellement. Les programmeurs devaient se confronter directement au langage machine, cest--dire au langage brut que comprend la machine qui na rien dhumain. Au milieu des annes cinquante, apparurent les premiers langages de programmation, dont lobjectif premier tait de dtacher le programmeur du langage machine. Ds lors, les programmes sont crits dans un dialecte relativement comprhensible, respectant une syntaxe et une grammaire strictes et prcises. Comme ces langages furent dvelopps par nos amis anglo-saxons, on pourrait les qualier de langue anglaise trs simplie. Tandis que le langage machine est, par nature, trs li llectronique de lordinateur, le dveloppement des langages de programmation a ouvert la voie de labstraction, principe fondamental qui cherche loigner autant que possible le programmeur des contingences matrielles, pour le rapprocher des concepts, de la "chose" concrte ou abstraite quil doit traiter. Lorsquon veut modliser informatiquement un moteur de voiture ou la courbe subtile dune fonction mathmatique, autant ne pas trop sencombrer lesprit avec ltat molculaire des transistors du microprocesseur Cest ainsi quau l du temps diffrents paradigmes (ensembles de rgles et de principes de conception) de programmation se sont dvelopps. Programmation structure, fonctionnelle, oriente objet chaque volution, lobjectif est toujours le mme : permettre la meilleure reprsentation informatique possible, la plus intuitive possible, de lide sur laquelle opre le programme. On peut esquisser une sorte darbre gnalogique des langages de programmation. En donner une liste exhaustive est pratiquement impossible : des milliers sont ou ont t utiliss. La Figure I en prsente tout de mme quelques-uns, les ches indiquant les sources dinspiration utilises pour un langage donn.

Introduction

XI

1954 Fortran

1960

1970

1980

1990

2000

Pascal Modula Algol Simula Lisp Smalltalk C

Ada Python Ruby Eiffel Perl Java VB.NET C#

C++

Basic Cobol

Visual Basic

Figure I Gnalogie simplie de quelques langages de programmation.

Il ne sagit l que dune vue vraiment trs simplie, presque caricaturale. Mais vous pouvez constater que les langages sinspirent fortement les uns des autres. Par ailleurs, la plupart de ceux prsents sur ce schma sont toujours utiliss et continuent dvoluer cest, par exemple, le cas du vnrable anctre Fortran, notamment dans les applications trs gourmandes en calculs scientiques.

Le choix des langages


Il en va de la programmation comme de bien dautres disciplines : cest en forgeant que lon devient forgeron. Cette initiation sappuiera donc sur deux langages de programmation, appartenant deux grandes familles diffrentes : celle des langages interprts et celle des langages compils.
Python C++

Le langage Python a t conu en 1991, principalement par le Nerlandais Guido Van Rossum, comme successeur du langage ABC, langage interactif lui-mme destin remplacer le langage Basic.

Le langage C++ a t conu en 1983 par Bjarne Stroustrup, comme une extension du langage C, langage proche de llectronique et encore trs largement utilis, pour lui apporter les principes de la POO (programmation oriente objet).

XII

Initiation la programmation avec Python et C++

Python

C++

Le langage Python est un langage orient objet interprt. Les programmes crits dans un langage interprt ne sont pas directement excuts par lordinateur, mais plutt par un interprteur. Grossirement, linterprteur lit les instructions du programme et les transforme en instructions destines la machine. Linterprteur est donc toujours ncessaire pour excuter un programme en langage interprt. Il gure aujourdhui en bonne place dans de nombreux domaines, de lanalyse scientique la programmation de jeux, en passant par la ralisation rapide de petits outils spcialiss ou de sites web dynamiques.

Le langage C++ est un langage orient objet compil. Les programmes crits dans un langage compil doivent subir une phase intermdiaire, la compilation, lissue de laquelle on obtient un chier directement excutable par la machine. La compilation est effectue par un compilateur, qui nest alors plus ncessaire ds que le chier excutable a t produit. Le C++ est aujourdhui lun des langages les plus utiliss dans le monde, dans toutes sortes de contextes, notamment ceux o la rapidit dexcution est primordiale.

Bien quappartenant deux familles diffrentes, ces deux langages partagent un point commun essentiel : ce sont des langages objet, cest--dire des langages utilisant les techniques de la programmation oriente objet. Nous verrons plus loin ce que ce terme sibyllin reprsente. Pour lheure, disons simplement quil sagit, de trs loin, du paradigme de programmation le plus rpandu dans le monde.

Et maintenant
Que les impatients se rassurent, nous allons trs bientt passer la pratique. Une premire partie prsentera quelques-uns des concepts fondamentaux de la programmation au moyen de nombreux exemples, partir desquels nous construirons progressivement un vritable programme permettant de jouer au problme des Tours de Hano (aussi appel problme des Tours de Brahma). Cela aprs avoir install sur votre ordinateur les logiciels ncessaires, essentiellement linterprteur Python et le compilateur C++. Une deuxime partie nous emmnera plus loin dans lexploration des techniques permettant la ralisation dun logiciel, notamment en dcrivant ce quest exactement cette mystrieuse programmation objet. Nous aboutirons alors un programme presque complet, disposant dune interface graphique attrayante (du moins, esprons-le !).

Introduction

XIII

Enn, cet ouvrage se terminera par quelques pistes destines vous ouvrir de larges horizons, de lafchage en trois dimensions la programmation parallle, sans oublier une petite prsentation dautres langages courants. Nous naurons pas la prtention de vous faire devenir en quelques pages un ingnieur en dveloppement logiciel : cest un mtier part entire, qui ncessite des annes dtudes et dexpriences. Ce livre nest en aucun cas un manuel pour apprendre les langages Python ou C++ : ceux-ci ne sont utiliss que comme support des concepts gnraux de la programmation, tels quils se retrouvent dans la plupart des langages de programmation. Les approches qui seront proposes ici ne seront pas toujours les plus efcaces en termes de performances, mais nous recherchons avant tout la comprhension et lacquisition des concepts. lissue de cette lecture, vous serez en mesure de crer vos programmes pour vos propres besoins et daller chercher les informations qui vous manqueront invitablement. Ce sera alors pleinement vous de jouer.

Convention dcriture
La che indique la continuit de la ligne de code.

Partie

Dcouverte
CHAPITRE 1. Mise en place CHAPITRE 2. Stockage de linformation : les variables CHAPITRE 3. Des programmes dans un programme : les fonctions CHAPITRE 4. Linteractivit : changer avec lutilisateur CHAPITRE 5. Ronds-points et embranchements : boucles et alternatives CHAPITRE 6. Des variables composes CHAPITRE 7. Les Tours de Hano

1
Mise en place
Au sommaire de ce chapitre :

Windows Mac OS X Linux Votre premier programme

Initiation la programmation avec Python et C++

Pour forger, encore faut-il disposer dune forge et de quelques outils indispensables. Nous allons donc commencer par mettre en place un environnement de dveloppement, cest--dire installer sur votre ordinateur les outils ncessaires la pratique de la programmation. Si vous cherchez un peu, vous trouverez sans doute de nombreux environnements sophistiqus, chatoyants, parfois onreux. Mais noubliez pas que mme les plus grands pilotes de Formule 1 ont appris conduire, probablement sur de petites voitures Aussi allons-nous nous contenter dun environnement minimal, simple matriser. Au moins dans un premier temps, nous naurons besoin que de trois outils :

un interprteur du langage Python, pour excuter nos programmes crits en Python ; un compilateur du langage C++, pour compiler nos programmes crits en C++ an dobtenir un chier excutable ; un diteur de texte adapt la programmation.

Dtaillons ce dernier point. Un diteur de texte, raccourci pour dire en ralit "programme permettant de modier un chier ne contenant que du texte brut", nest en aucun cas un logiciel de traitement de texte. En effet, un chier issu dun traitement de texte ne contient pas que des caractres : il contient galement de nombreuses informations de mise en forme, comme le gras, litalique, la taille de certaines lettres Toutes ces informations sont parfaitement indigestes pour un interprteur ou un compilateur. Ce quil nous faut, cest un logiciel ne stockant rellement que les lettres que nous allons taper au clavier pour crire nos programmes. Sous Windows, un exemple typique est Notepad ; sous GNU/Linux, KEdit ou GEdit ; sous Mac OS X, TextEdit. Sils pourraient convenir, ces (petits) logiciels sont pourtant trop simples pour notre tche de programmation : il faut des outils possdant certaines fonctionnalits adaptes. On pourrait citer les clbres Vim ou Emacs, sur tous les systmes, mais leur prise en main peut tre pour le moins dlicate. Les sections suivantes proposeront des outils plus simples manipuler.

Chapitre 1

Mise en place

1.1. Windows
Pour commencer, nous vous recommandons dinstaller lditeur de texte Notepad++, que vous trouverez sur le site http://notepad-plus.sourceforge.net/fr/ site.htm. Il sera plus que sufsant pour nos besoins. partir de la page indique, suivez le lien Tlcharger, puis Tlcharger les binaires de Notepad++, enn choisissez le chier npp.4.6.Installer.exe. Excutez ce programme une fois tlcharg an dinstaller Notepad++. Ensuite, linterprteur Python. Rcuprez le chier dinstallation correspondant votre systme depuis le site http://www.python.org/download/releases/2.5.1/ , le plus souvent sous la forme dun chier dinstallation MSI. Par exemple, si vous tes sur un systme 64 bits bas sur un processeur AMD, choisissez le chier python2.5.1.amd64.msi. Si vous tes sur un systme 32 bits, probablement le cas le plus rpandu, choisissez le chier python-2.5.1.msi. Ouvrez ce chier, acceptez les options par dfaut proposes. Et voil, linterprteur Python est install ! Enn, nous allons installer un compilateur C++, nommment celui faisant partie de la suite GCC. Celle-ci est un ensemble de compilateurs trs largement utiliss dans la vaste communaut du logiciel libre. Pour en obtenir une version sous Windows, nous allons installer lenvironnement MinGW, dont vous pouvez tlcharger le programme dinstallation partir du site http://sourceforge.net/project/showfiles.php?group_id=2435&package_id=240780 . Choisissez la version la plus rcente, celle contenue dans le chier MinGW-5.1.3.exe au moment o ces lignes sont crites. Une fois ce chier tlcharg, excutez-le. Linstallation tant lgrement plus complique que celle de linterprteur Python, nous allons la dtailler. Le tlchargement des composants puis linstallation devraient se drouler sans problme. Terminez tout cela en choisissant Next autant de fois que ncessaire. Nous en avons presque termin. Pour nir, crez un rpertoire Programmes dans lequel seront stocks les programmes que vous allez crire (par exemple la racine du disque C:), puis crez un petit chier de commande pour obtenir une ligne de commande dans laquelle nous pourrons effectuer les compilations et excutions. Pour cela, crez sur le bureau un chier nomm prog.bat et contenant les lignes suivantes :
set PATH=C:\Python25;C:\MinGW\bin;C:\MinGW\libexec\gcc\mingw32\3.4.5;%PATH% C: cd \Programmes cmd.exe

Initiation la programmation avec Python et C++

Cette installation ncessite une connexion Internet, pour tlcharger les composants ncessaires. Dans le premier cran, choisissez Download and Install puis cliquez sur Next. Acceptez ensuite lcran de licence en cliquant sur I Agree.

Ensuite vient le choix du type de paquetage installer, parmi une ancienne version, la version stable actuelle ou une version exprimentale. Choisissez Current, pour installer la version stable actuelle.

Vous devez maintenant choisir les lments installer. En plus du composant de base (le premier de la liste), cochez les cases devant g++ compiler et MinGW Make. Si vous le souhaitez, rien ne vous interdit dinstaller galement les autres compilateurs (pour les langages Fortran, Ada, Java et Objective-C), mais nous ne les aborderons pas ici !

Chapitre 1

Mise en place

Nous vous recommandons vivement daccepter le choix par dfaut du rpertoire de destination, savoir C:\MinGW. Vous pouvez naturellement choisir un autre rpertoire, mais alors il vous faudra adapter les indications qui vous seront donnes par la suite.

Si vous avez install Python ou MinGW des endroits autres que ceux proposs par dfaut, modiez la ligne commenant par set PATH en consquence : elle est destine faciliter laccs linterprteur et au compilateur. De mme, si vous avez cr le rpertoire Programmes un autre endroit que la racine du disque C:, adaptez les deux lignes suivantes. Lorsque vous lancez ce chier de commande, vous devriez obtenir quelque chose comme ce qui est illustr par la Figure 1.1 (exemple sous Windows Vista).
Figure 1.1 Ligne de commande pour la programmation sous Windows.

Vous tes maintenant prt commencer programmer !

Initiation la programmation avec Python et C++

1.2. Mac OS X
Un excellent diteur de texte adapt la programmation est Smultron, disponible partir de la page http://smultron.sourceforge.net. Slectionnez la version adapte votre version de Mac OS. Ensuite, le plus simple pour disposer dun compilateur C++ est dinstaller le logiciel Xcode, distribu gratuitement par Apple. Il sagit en fait dun environnement de dveloppement complet, le compilateur fourni tant celui issu de la suite GCC, le mme que nous allons utiliser sous Windows et GNU/Linux. Vous trouverez lenvironnement Xcode partir de la page http://developer.apple.com/tools/xcode/ , le tlchargement ncessitant toutefois de senregistrer auprs dApple. Cela reprsente environ 1 Go tlcharger, linstallation occupant environ 2,5 Go : prenez donc garde ne pas remplir votre disque ! Pour linterprteur Python, rcuprez le chier dinstallation python-2.5.1macosx.dmg depuis le site http://www.python.org/download/releases/2.5.1/ . Python ncessite au minimum une version 10.3.9 de Mac OS X et fonctionne aussi bien sur les systmes base de processeurs PowerPC ou Intel. Enn, partir de Finder, crez un rpertoire Programmes dans votre dossier personnel : cest l que vous stockerez les diffrents chiers que vous allez crer par la suite.

1.3. Linux
Normalement, tout est dj install si vous utilisez une distribution gnraliste, comme Mandriva, Debian, Ubuntu, RedHat, SuSE cette liste ntant pas exhaustive. Si tel ntait pas le cas, il suft dinstaller quelques paquetages supplmentaires. Leurs noms peuvent varier dune distribution lautre, mais ils devraient se ressembler. Pour le compilateur C++, recherchez les paquetages nomms g++ et libstdc++. Pour linterprteur Python, recherchez les paquetages nomms python2.5 et python2.5-dev.

Chapitre 1

Mise en place

Concernant lditeur de texte, vous disposez dun choix assez considrable, pour ne pas dire droutant. Si vous utilisez principalement lenvironnement Gnome, lditeur GEdit convient parfaitement. Si vous utilisez plutt lenvironnement KDE, lditeur Kate est probablement le mieux adapt. Sachez que, quel que soit votre environnement de bureau, vous pouvez utiliser nimporte lequel de ces deux diteurs. Dautres sont naturellement disponibles, comme les clbres Emacs ou Vim, mais sils sont extrmement puissants, ils peuvent tre dlicats prendre en main. Enn, crez un rpertoire Programmes dans votre rpertoire personnel pour contenir vos programmes.

1.4. Votre premier programme


Nous allons sacrier une vieille tradition : lorsquil sagit de prsenter un langage ou une technique de programmation, le premier programme consiste habituellement tout simplement afcher le message "Bonjour, Monde !" Cette tradition remonte (au moins) louvrage de Brian Kernighan, The C Programming Language, prsentant le langage C en 1974, qui a eu une grande inuence sur plus dune gnration de programmeurs. Voici donc les programmes, en Python et en C++ :
Python C++

Fichier bonjour.py :
1. # -*- coding: utf8 -*2. print "Bonjour, Monde!"

Fichier bonjour.cpp :
1. #include <iostream> 2. int main(int argc, char* argv[]) 3. { 4. std::cout << "Bonjour, Monde!" << std::endl; 5. return 0; 6. }

Si vous tes surpris de voir que le programme C++ est beaucoup plus long que le programme Python, sachez que cest une manifestation du fait que le langage Python est un langage dit de haut niveau, tandis que le langage C++ est de plus bas niveau. Dit autrement, en Python, linterprteur masque beaucoup daspects que le compilateur C++ laisse au grand jour. Chacune des deux approches prsente des avantages et des inconvnients : tout dpend du contexte et de lobjectif de votre programme.

10

Initiation la programmation avec Python et C++

Gnralement, on peut dire que masquer les dtails permet de programmer plus rapidement, tandis quavoir la possibilit dagir sur ces dtails permet damliorer les performances au moment dexcuter le programme. Mais revenons justement nos programmes. Les deux textes que vous voyez, et que vous tes invit recopier dans votre diteur de texte, constituent ce que lon appelle le code source dun programme. Le code source est ainsi le texte que le programmeur tape dans un diteur. Cest ce texte que nous allons bientt soumettre, pour lun, linterprteur Python, pour lautre, au compilateur C++. Une prcision avant cela : vous aurez remarqu que les lignes des programmes sont numrotes, de 1 2 en Python, de 1 6 en C++. Cette numrotation na pas dautre objectif que de nous permettre de faire rfrence aisment telle ou telle partie du programme, dans le cadre de cet ouvrage. En aucun cas, vous ne devez recopier ces numros dans votre diteur de texte. Sil existe bien des langages de programmation qui exigeaient que les lignes soient numrotes, comme les premiers Basic ou Cobol, ce procd a t abandonn depuis fort longtemps car inefcace. Les Figures 1.2 1.4 montrent quoi pourrait ressembler votre diteur sur cran.
Windows

Figure 1.2 diteur de texte sous Windows (Notepad++).

Chapitre 1

Mise en place

11

Mac OS X

Figure 1.3 diteur de texte sous Mac OS X (Smultron).

Linux

Figure 1.4 diteur de texte sous Linux (Kate).

Remarquez que les lignes sont automatiquement numrotes par lditeur lui-mme : vous navez donc pas vous en soucier. Vous voyez galement que, ds que vous avez sauvegard votre chier, son aspect change : certains mots se voient attribuer une couleur. Cest la mise en vidence syntaxique (ou syntax highlight en anglais), une aide visuelle prcieuse lorsquon programme. chaque couleur correspond une certaine nature de mot dans le programme, ce que nous allons dcouvrir au l des pages qui suivent. Enn, la norme du langage C++ exige que tout programme se termine par une ligne vide : cest pourquoi vous voyez sept lignes sur les captures dcran au lieu de six. Cette ligne vide supplmentaire sera implicite dans tous les exemples qui seront donns par la suite, donc ne loubliez pas.

12

Initiation la programmation avec Python et C++

1.4.1. Excution pour Python


tant un langage interprt, le programme Python peut tre excut directement par linterprteur, comme ceci :
python bonjour.py

Par exemple, la Figure 1.5 vous montre le rsultat dans une ligne de commande Linux.
Figure 1.5 Excution du programme sous Linux.

Cest un des principaux avantages des langages interprts : leur mise en uvre est trs simple. Si on regarde ce trs court programme, la premire ligne indique selon quel encodage le texte du programme a t enregistr. Cela est particulirement important pour les messages que vous voudrez afcher, notamment sils contiennent des lettres accentues. Lafchage proprement dit du message a lieu la ligne suivante. la n de cet afchage, un saut de ligne est automatiquement effectu. Remarquez labsence de toute ponctuation la n de la ligne 2, celle contenant linstruction dafchage : cest l une diffrence fondamentale avec le programme en langage C++, sur lequel nous allons nous pencher immdiatement.

1.4.2. Compilation et excution pour le C++


Nous lavons dit, le langage C++ est un langage compil, cest--dire que le code source doit tre trait par le compilateur pour obtenir un chier excutable. Allez la fentre de ligne de commande, dans le rpertoire dans lequel vous avez enregistr votre chier bonjour.cpp. Excutez linstruction :
g++ -o bonjour bonjour.cpp

Cela va crer un chier excutable nomm bonjour (nanti de lextension .exe sous Windows) dans le rpertoire courant. Vous pouvez excuter ce programme immdiatement

Chapitre 1

Mise en place

13

en appelant simplement bonjour, ou ./bonjour si vous tes sous GNU/Linux. La Figure 1.6 montre un exemple sous Windows.
Figure 1.6 Compilation et excution sous Windows.

Flicitations, vous venez de saisir, de compiler et dexcuter votre premier programme en langage C++ ! Ce ntait pas si difcile, nest-ce pas ? Mais examinons un peu ce code. La premire ligne, dont le sens profond sclaircira plus tard, est ncessaire pour nous permettre dutiliser les capacits dafchage du C++. Lafchage en lui-mme est effectu ligne 4, en "envoyant" un texte dlimit par des guillemets dans un "objet dafchage". Cet objet est std::cout, lenvoi en luimme est symbolis par <<. Le prxe std:: signale quil sagit dun objet de la bibliothque standard, ce qui signie quil est normalement disponible sur nimporte quel autre compilateur C++. Nous verrons plus loin la signication exacte de ce prxe. Le morceau cout pourrait se traduire par console output, cest--dire sortie vers lcran. la suite du texte, on envoie (toujours avec <<) lobjet std::endl, qui est une faon de reprsenter un saut de ligne. Encore une fois, cet objet fait partie de la bibliothque standard, endl signiant end of line (n de ligne en franais). Sans cela, nous naurions pas de saut de ligne aprs lafchage du texte, ce qui pourrait tre disgracieux. Les autres lignes font partie de ce quon pourrait appeler "lemballage" minimal dun programme C++. Inutile de rentrer pour linstant dans les dtails (mais cela viendra). Pour lheure, il suft de savoir que, sans elles, le programme serait incorrect et donc ne pourrait pas tre transform en chier excutable par le compilateur.

14

Initiation la programmation avec Python et C++

Peut-tre encore plus que linterprteur Python, un compilateur C++ peut se rvler particulirement tatillon sur lexactitude de ce quon crit. Enn, dernire remarque, la prsence du point-virgule est obligatoire pour sparer deux instructions du programme. Si vous oubliez un seul point-virgule dans votre programme, le compilateur vous couvrira dinsultes diverses et refusera de crer un chier excutable.

2
Stockage de linformation : les variables
Au sommaire de ce chapitre :

Des types des donnes Les entiers Les dcimaux et la notation scientique Les chanes de caractres Erreurs typiques lies aux variables

16

Initiation la programmation avec Python et C++

Nous navons manipul dans les exemples prcdents que des valeurs littrales, cest-dire des donnes quon crit littralement, en toutes lettres. Mais lessence mme dun programme est de travailler sur des donnes (plus ou moins) quelconques, pour les combiner, les modier, an de produire un certain rsultat. Il est donc ncessaire de les stocker an de pouvoir les rutiliser plus tard. Ce stockage seffectue dans la mmoire de lordinateur et chaque donne, pour tre ultrieurement identie, reoit un nom : cest ce quon dsigne usuellement par le terme de variable. Au sens strict, une variable est une zone mmoire contenant une donne susceptible dtre modie, le nom ntant quun moyen dy accder. Dans la pratique, on identie le nom la variable, par abus de langage. Considrez les deux programmes suivants :
Python
1. # -*- coding: utf8 -*2. x = 5 3. print 10*x

C++
1. 2. 3. 4. 5. 6. 7. 8. #include <iostream> int main(int argc, char* argv[]) { int x; x = 5; std::cout << 10*x << std::endl; return 0; }

La ligne 2 a deux effets : dabord, elle dclare la variable x, ensuite la valeur 5 y est stocke. Tout cela au moyen du simple signe =. En Python, la dclaration dune variable se fait simplement en lui affectant une valeur.

La ligne 4 a pour effet de dclarer la variable x. La valeur 5 y est stocke en ligne 5, par le signe =. En C++, les variables doivent tre dclares avant toute utilisation. Mais une dclaration peut combiner une affectation.

Excutez ces deux programmes, comme nous lavons vu prcdemment. Les deux devraient simplement afcher la valeur 50, alors que celle-ci napparat nulle part Elle est en fait calcule partir de la valeur contenue dans une variable, ici nomme x. Dans pratiquement tous les langages de programmation, une variable doit tre dclare avant dtre utilise. La dclaration dune variable consiste (au minimum) lui donner un nom, an que linterprteur ou le compilateur reconnaisse ce nom par la suite et "sache" quil correspond une variable dont on veut utiliser le contenu.

Chapitre 2

Stockage de linformation : les variables

17

Techniquement, la dclaration dune variable consiste rserver une zone dune certaine taille dans la mmoire de lordinateur et identier cette zone avec une tiquette (voir Figure 2.1). Lopration consistant placer une certaine valeur dans cette zone est laffectation. Ltiquette, cest--dire le nom de la variable, est un moyen daccder la valeur contenue dans cette zone.
Figure 2.1 Une variable permet de stocker une valeur dans la mmoire de lordinateur et daccder celle-ci.
programme mmoire x 5

Regardez les instructions dafchage des deux programmes prcdents, ligne 3 en Python et ligne 6 en C++. Le symbole toile (ou astrisque) * est celui de la multiplication. Lcriture 10*x peut donc se traduire par :
multiplier par 10 le contenu de la zone mmoire identie par x

Ou, plus simplement et par un abus trs commun de langage :


multiplier par 10 la valeur de x

On demande ensuite simplement lafchage du rsultat de cette opration. Il est naturellement possible daffecter une autre valeur la variable x, par exemple le rsultat dune opration limpliquant elle-mme :
Python
1. x = 5 2. x = 10*x 3. print x

C++
1. x = 5; 2. x = 10*x; 3. std::cout << x << std::endl;

La deuxime ligne des extraits prcdents peut surprendre : quel sens cela a-t-il de dire que x est gal dix fois sa propre valeur ? Il faut bien comprendre que, dans ces contextes, les symboles = ne reprsentent pas une situation dgalit, mais quils sont une opration dans laquelle on change le contenu dans la variable gurant gauche de loprateur pour y placer la valeur gurant droite. Au cours de vos exprimentations, vous constaterez probablement une certaine difcult trouver un nom vos variables. La tentation est alors grande de les nommer simplement par a, b, c Surtout ne cdez pas cette tentation. Rappelez-vous quun

18

Initiation la programmation avec Python et C++

programme nest crit quune seule fois mais gnralement relu de nombreu ses fois. Comparez ces trois extraits de code, qui font exactement la mme chose :
d = 100; t = 2; v = d/t; distance = 100; temps = 2; vitesse = distance/temps; distance_km = 100; temps_h = 2; vitesse_km_h = distance_km/temps_h;

Vous laurez devin, le symbole barre incline (ou slash) / est celui de la division. Lextrait de gauche ne prsente aucune signication. Lextrait au milieu est, par contre, assez limpide dans ses intentions, bien que subsiste une incertitude sur les units utilises. Lextrait de droite est parfaitement clair, les noms donns aux variables indiquent exactement lopration effectue. Pour nommer vos variables, vous disposez des 26 lettres de lalphabet (sans accent), des 10 chiffres et du caractre de soulignement ( _). De plus, les langages C++ et Python sont sensibles la casse, cest--dire quils diffrencient minuscules et majuscules : var, VAR et Var pourraient tre les noms de trois variables diffrentes1. Cela vous laisse donc au total 63 caractres disponibles combiner entre eux, la seule contrainte est que le nom doit commencer par une lettre (ou le caractre de soulignement, mais ce nest pas conseill, car cela peut avoir une signication particulire dans certains contextes). Usez et abusez des possibilits offertes. Si une paresse lgitime fait que les noms des variables vous paraissent bien longs taper, pensez au temps considrable que vous gagnerez quand vous voudrez reprendre et modier votre programme dans plusieurs mois, voire plusieurs annes. Une paresse efcace doit avoir une vision long terme !

1. Dautres langages, comme Basic, Pascal ou Ada, ne font pas cette distinction entre minuscules et majuscules. En revanche, certains comme Ada autorisent lutilisation de pratiquement nimporte quel caractre, mme ceux issus dalphabets non latins.

Chapitre 2

Stockage de linformation : les variables

19

2.1. Des types des donnes


Nous navons manipul dans les exemples prcdents que des nombres, plus prcisment des nombres entiers. Heureusement, les possibilits ne se limitent pas cela. Voyez par exemple :
Python
1. 2. 3. 4. # -*- coding: utf8 -*pi = 3.14159 texte = "Pi vaut " print texte, pi

C++
1. 2. 3. 4. 5. 6. 7. #include <iostream> #include <string> int main(int argc, char* argv[]) { double pi = 3.14159; std::string texte = "Pi vaut "; std::cout << texte << pi << std::endl; 8. return 0; 9. }

Remarquez dans le programme C++, en lignes 5 et 6, la combinaison dune dclaration dune variable et dune affectation. Dans ces programmes, la variable pi est une valeur dcimale, un nombre en virgule flottante. La variable texte contient du texte, ce quon dsigne par chane de caractres (imaginez des caractres individuels aligns le long dune chane, an de former un texte). lvidence, ces variables ne sont pas de la mme nature que la variable x utilise prcdemment. On ne parle pas ici de nature dune variable, mais plutt de son type. Le type dune variable dnit la nature de ce quon peut y stocker, ce qui dtermine directement la faon dont cette variable est code lintrieur de lordinateur (qui, rappelons-le, ne sait manipuler que des groupes de 0 et de 1) ainsi que lespace dont elle a besoin. Formellement, un type est dni par lensemble des valeurs quon peut stocker dans les variables de ce type, ainsi que les oprations disponibles entre ces valeurs. Plus simplement, disons que le type dune variable permet dattacher celle-ci une smantique rudimentaire. La plupart des langages interprts, comme Python, sont capables de dterminer par eux-mmes le type dune variable selon la valeur qui lui est affecte et selon le contexte, ce type pouvant ventuellement changer par la suite. Par contre, les langages compils exigent en gnral que le type soit donn explicitement et dnitivement ds la dclaration. Dans les programmes C++ que nous avons vus jusquici, les

20

Initiation la programmation avec Python et C++

lments int, double et std::string sont des noms de types, dsignant respectivement des nombres entiers relatifs, des nombres virgule ottante et des chanes de caractres. int et double sont normalement toujours disponibles, tandis que, pour pouvoir utiliser std::string, il est ncessaire dajouter une ligne #include <string> au dbut du programme. Mais nous reviendrons plus tard sur cet aspect. mesure que nous avancerons dans nos exprimentations, nous dcouvrirons de nouveaux types et diverses oprations disponibles sur chacun deux. Nous verrons galement comment crer nos propres types. Mais dtaillons un peu les trois grands types prcdents.

2.2. Les entiers


Les types entiers permettent de reprsenter de manire exacte des valeurs entires, comme les dnombrements. Par exemple, vous pouvez calculer ainsi le nombre de combinaisons possibles au Loto national :
Python
1. # -*- coding: utf8 -*2. comb_loto = (49*48*47*46*45*44) / \ 3. (6*5*4*3*2) 4. print comb_loto

C++
1. #include <iostream> 2. int main(int argc, char* argv[]) 3. { 4. int comb_loto = (49*48*47*46*45*44) / 5. (6*5*4*3*2); 6. std::cout << comb_loto << std::endl; 7. return 0; 8. }

Le programme en Python afche le rsultat 13 983 816, tandis que le programme C++ afche 2 053 351. Lequel a raison, pourquoi lautre a-t-il tort ? Cet exemple a deux objectifs. Dabord montrer comment vous pouvez crire des oprations relativement complexes, ventuellement en utilisant des parenthses comme vous le feriez sur une feuille de papier. Remarquez, de plus, que lopration est crite sur deux lignes, an de lui donner un aspect plus harmonieux. La prsentation du code nest pas quun souci esthtique, cela facilite galement la relecture. En Python, il est ncessaire dajouter un caractre barre inverse \ (ou backslash) an dindiquer que linstruction se poursuit sur la ligne suivante. Ce nest pas ncessaire en C++. Ensuite, ayez toujours un regard critique sur les rsultats que vous obtenez de vos programmes : jaugez-en la cohrence et la crdibilit. Dans cet exemple, sans pour

Chapitre 2

Stockage de linformation : les variables

21

autant douter un instant de laltruisme de la Franaise des Jeux, on peut considrer que la valeur la plus probable est la plus leve, cest--dire celle donne par le programme Python. Mais alors, pourquoi une valeur errone en C++ ? Parce que les ordinateurs en gnral, et les programmes en particulier, prsentent une limite la quantit de chiffres quils peuvent manipuler. Le langage Python, qui est un langage de haut niveau1, est organis de telle sorte que cette limite est trs leve, en fait pratiquement celle autorise par la mmoire dont vous disposez cela au prix de performances amoindries. En C++, par contre, cette limite est dnie par la capacit du processeur, la taille de lunit de donne quil peut accepter. Elle est donc bien infrieure, mais, en contrepartie, les calculs se font beaucoup plus rapidement. Le produit intermdiaire 49*48*47*46*45*44, qui vaut 10 068 347 520, dpasse de loin la limite dun processeur ou dun systme 32 bits (encore les plus courants aujourdhui). Ce calcul provoque un dbordement, cest--dire qu une certaine tape le processeur ne peut plus contenir la valeur et "oublie" tout simplement une partie du nombre. Il en rsulte une erreur de calcul.

2.3. Les dcimaux et la notation scientique


Les nombres entiers ne sufsent naturellement pas pour reprsenter le monde qui nous entoure. Par exemple, la surface dun disque na que peu de chances dtre un entier. Aussi utilise-t-on frquemment des nombres dcimaux pour reprsenter des dimensions physiques :
Python
1. # -*- coding: utf8 -*2. aire_disque = 3.141592654 * \ 3. 2.0 * 2.0 4. print aire_disque

C++
1. 2. 3. 4. 5. 6. 7. 8. 9. #include <iostream> int main(int argc, char* argv[]) { double aire_disque = 3.141592654 * 2.0 * 2.0; std::cout << aire_disque << std::endl; return 0; }

1. Un langage est dit de "haut niveau" sil permet (ou impose) une certaine distance vis--vis du matriel par lutilisation de concepts plus ou moins sophistiqus. Au contraire, un langage est dit de "bas niveau" sil est en prise assez directe avec llectronique de lordinateur. Le langage C++ est dans une position intermdiaire, autorisant les deux approches.

22

Initiation la programmation avec Python et C++

Remarquez lutilisation du point anglo-saxon en lieu et place de notre virgule. Le programme Python afche 12.5663706144, tandis que le programme C++ afche 12.5664. Ces deux valeurs sont proches, mais elles ne sont pas gales Encore un problme de prcision ? Tel est bien le cas, mais pas uniquement. Pour la mme raison que les entiers, lordinateur ne peut manipuler quun nombre limit de dcimales (nombre total de chiffres), environ une douzaine. Les calculs sont donc ncessairement entachs dune certaine erreur, mme minime. cette approximation "interne" sajoute lapproximation de lafchage : si Python afche normalement toutes les dcimales disponibles, C++ se contente en gnral de 6 mme si davantage sont disponibles dans la mmoire. Une fois de plus, ne croyez pas aveuglment ce que vous voyez ! Les nombres dcimaux permettent en outre de reprsenter des valeurs plus grandes que les nombres entiers. Essayez, par exemple, de refaire ainsi le calcul du nombre de possibilits au Loto en C++ :
double comb_loto = (49.0*48.0*47.0*46.0*45.0*44.0) / (6.0*5.0*4.0*3.0*2.0);

Ce qui donne lafchage 1.39838E+07 (ou 1.39838e+07, avec un "e" minuscule, ce qui est identique). Cette forme dcriture, un nombre dcimal suivi dun E, dun signe puis dun entier, est appele notation scientique. Elle permet de reprsenter de grandes valeurs avec peu de chiffres. Lentier relatif qui suit le E est la puissance de 10 par laquelle il faut multiplier le premier nombre pour avoir le rsultat. Par exemple, lcriture 1.234E+24 signie littralement "1.234 multipli par 10 24", soit 1 234 000 000 000 000 000 000 000. Mais encore une fois, tous ces chiffres ne sont pas rellement stocks dans lordinateur Essayez dafcher le rsultat du calcul suivant, en Python comme en C++ :
deux = 1.0E+50 + 2.0 1.0E+50

Il suft de regarder cette instruction une seconde pour sapercevoir que les deux grands nombres sannulent : mathmatiquement, ce calcul vaut donc exactement 2.

Chapitre 2

Stockage de linformation : les variables

23

Mais lordinateur effectue les calculs oprateur par oprateur : il va donc commencer par calculer 1.0E+50+2. Du fait des limites de prcision, la valeur 2 est ngligeable par rapport la valeur 1050 : cette somme nest en ralit mme pas effectue. Ce qui donne 1050, auquel on soustrait 1050 ce qui donne 0. Mathmatiquement faux, mais informatiquement invitable.
Ces virgules qui ottent Peut-tre vous demandez-vous pourquoi on dsigne les nombres dcimaux par le terme de nombres virgule ottante. Cela est d au fait que, dans la reprsentation de ces nombres, la virgule (qui est reprsente par un point, selon la convention anglo-saxonne) na pas une position xe, cest--dire que le nombre de chiffres gauche et droite de la virgule peut varier selon la situation. Il est important de comprendre que le nombre que nous voyons lcran nest quune reprsentation du contenu dune zone mmoire de lordinateur, qui nalement ne sait manipuler que des suites de 0 et de 1, le fameux codage binaire. Nous avons vu que dans le cas du type dcimal de Python, environ 16 chiffres sont disponibles. La virgule ottante nous permet donc de contenir en mmoire les nombres 1.123456789012345 et 123456789012345.1 avec la mme prcision, sans arrondi (ou presque). Cette reprsentation soppose celle dite virgule xe. Celle-ci impose un nombre xe de chiffres de part et dautre de la virgule. Selon ce principe, pour pouvoir reprsenter avec la mme prcision les deux nombres prcdents, il faudrait pouvoir disposer de 32 chiffres, ce qui impliquerait que la zone mmoire soit deux fois plus vaste. De nos jours, les processeurs des ordinateurs utilisent (presque) tous la reprsentation en virgule ottante, car cest celle qui offre le meilleur compromis entre prcision, complexit de traitement et occupation mmoire.

2.4. Les chanes de caractres


Aussi tonnant que cela puisse paratre, notre lectronique actuelle moderne et surpuissante na toujours aucune ide de ce quest une simple lettre. Encore moins la notion de phrase ou de texte ! Du point de vue de lordinateur, ce que nous percevons comme lettres nest en fait que codes numriques et nombres entiers. La correspondance entre une valeur numrique et une lettre prcise se fait au travers dun processus relativement complexe, que nous passerons sous silence pour linstant.

24

Initiation la programmation avec Python et C++

Une chane de caractres nest ainsi quune suite ordonne de codes numriques, que nous dcidons de considrer comme un tout ayant un sens. Considrons les programmes suivants :
Python
1. # -*- coding: utf8 -*2. chaine = "Bonjour!" 3. print chaine

C++
1. 2. 3. 4. 5. 6. 7. 8. 9. #include <iostream> #include <string> int main(int argc, char* argv[]) { std::string chaine; chaine = "Bonjour!"; std::cout << chaine << std::endl; return 0; }

La variable chaine (sans accent circonexe sur le "i", car les noms de variables ne doivent pas contenir de lettre accentue) est alors un point daccs une suite de caractres (reprsents par des codes numriques), un peu comme si on avait dclar autant de variables de type caractre quil y en a dans la chane. Seulement, cette suite est vue comme un tout, ce que lon pourrait reprsenter selon la Figure 2.2.
Figure 2.2 Reprsentation dune chane de caractres.

chaine B o n j o u r !

Toutefois, cette reprsentation est approximative : le programme a besoin de "connatre" la longueur de cette chane, le nombre de caractres quelle contient. Chaque langage de programmation applique sa propre technique pour cela, qui ne prsente pas beaucoup dintrt pratique pour nous. Disons simplement que dans le schma prcdent, il manque un petit bloc dinformations utilis pour les besoins propres du programme, an quil puisse manipuler cette chane. Comme pour les types numriques, de nombreuses oprations sont disponibles sur les chanes de caractres.

Chapitre 2

Stockage de linformation : les variables

25

Par exemple :
Python
# -*- coding: utf8 -*bonjour = "Bonjour, " monde = "Monde!" print bonjour + monde tiret = "-" print 16*tiret

C++
#include <iostream> #include <string> int main(int argc, char* argv[]) { std::string bonjour = "Bonjour, "; std::string monde = "Monde!"; std::cout << bonjour+monde << std::endl; return 0; }

Loprateur daddition + permet de "coller" deux chanes, lune au bout de lautre, pour en crer une nouvelle plus longue. Dans le jargon informatique, on appelle cela la concatnation de deux chanes de caractres. Plus curieux, loprateur de multiplication * permet de dupliquer une chane pour en produire une plus longue. Cet oprateur nest toutefois disponible quen Python : cest pourquoi, ici, le programme C++ nest pas quivalent. Nous verrons plus loin comment obtenir un rsultat similaire.

2.5. Erreurs typiques lies aux variables


Invitablement, vous commettrez des erreurs lors de la saisie de vos programmes. Ce nest pas grave, cest parfaitement naturel, surtout au dbut. Une erreur commune est dutiliser une variable avant de lavoir dclare :
Python
1. # -*- coding: utf8 -*2. print une_variable

C++
1. 2. 3. 4. 5. #include <iostream> int main(int argc, char* argv[]) { std::cout << une_variable; }

Le retour la ligne par std::endl a ici t omis, car ne prsentant aucun intrt pour la dmonstration.

26

Initiation la programmation avec Python et C++

Cela sera impitoyablement sanctionn par linterprteur et par le compilateur par une borde dinsultes diverses, dans ce style :
Python
Traceback (most recent call last): File "test.py", line 2, in <module> print une_variable NameError: name une_variable is not defined

C++
test.cpp: In function int main(int, char**): test.cpp:4: error: une_variable was not declared in this scope

Python nous dit "une_variable is not defined ", C++ plutt "une_variable was not declared". Mais la signication est la mme : vous avez tent dutiliser une variable sans lavoir dclare auparavant. Le message derreur contient dautres informations utiles, comme le chier dans lequel elle apparat et la ligne concerne. Il peut arriver galement que lon tente de combiner deux variables de faon incorrecte, le plus souvent quand elles sont de types diffrents. Par exemple :
Python
1. 2. 3. 4. # -*var_1 var_2 print coding: utf8 -*= "chane" = 5 var_1 + var_2

C++
1. 2. 3. 4. 5. 6. 7. #include <iostream> int main(int argc, char* argv[]) { std::string var_1 = "chane"; int var_2 = 5; std::cout << var_1 + var_2; }

Ces programmes tentent une trange opration : additionner une chane et un entier moins quil ne sagisse de concatnation ? Autant additionner des chaussures avec des tomates. Cette opration na pas de sens, elle est donc immdiatement sanctionne :
Python
Traceback (most recent call last): File "test.py", line 4, in <module> print var_1 + var_2 TypeError: cannot concatenate str and int objects

C++
test.cpp: In function int main(int, char**): test.cpp:6: error: no match for operator+ in var_1 + var_2

Chapitre 2

Stockage de linformation : les variables

27

nouveau, interprteur et compilateur nous donnent la localisation prcise de lerreur. Python nous dit quil est impossible de concatner ( cannot concatenate) des objets de types chane et entier, tandis que C++ nous informe quil nexiste pas doprateur + valide (no match for operator+) pour lopration demande. Aussi agaants quils puissent tre, ces messages derreurs ne sont pas l pour vous ennuyer mais pour vous aider construire un programme correct. Ils ne font que signaler une incohrence, manifestation probable dun problme dans la conception du programme. Corriger ces erreurs permet non seulement dobtenir un programme qui fonctionne mais, de plus, augmente les chances que ce programme fonctionne correctement.

3
Des programmes dans un programme : les fonctions
Au sommaire de ce chapitre :

Dnir une fonction Les paramtres Les blocs dinstructions Retourner une valeur Quand crer une fonction ?

30

Initiation la programmation avec Python et C++

Jusqu prsent, nous avons essentiellement excut des instructions la suite les unes des autres. Mais imaginez une suite dinstructions qui revient sans cesse dans un programme : la retaper chaque fois peut rapidement devenir fastidieux. Prenons un exemple : vous voulez afcher une suite de nombres, un par ligne, chacun tant spar des autres par une ligne de caractres de soulignement ( underscore en anglais) et une ligne de tirets. Essayez dcrire vous-mme le programme, par exemple pour afcher les nombres 10, 20, 30 et 40. Le programme pourrait ressembler ceci :
Python
1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. # -*print print print print print print print print print print coding: utf8 -*10 "_"*10 "-"*10 20 "_"*10 "-"*10 30 "_"*10 "-"*10 40

C++
1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. #include <iostream> #include <string> int main(int argc, char* argv[]) { std::string soulign = "_____"; std::string tirets = "-----"; std::cout << 10 << std::endl; std::cout << soulign << std::endl; std::cout << tirets << std::endl; std::cout << 20 << std::endl; std::cout << soulign << std::endl; std::cout << tirets << std::endl; std::cout << 30 << std::endl; std::cout << soulign << std::endl; std::cout << tirets << std::endl; std::cout << 40 << std::endl; return 0; }

Pour commencer, essayez dimaginer ce que fait ce programme. Ensuite, saisissez-le dans votre diteur de texte et excutez-le. Vous voyez quune paire de lignes revient trois fois. Ce ne sont que deux lignes et que trois fois, mais imaginez si cela reprsentait cinquante lignes devant tre rptes vingt fois ! Rptons-le, les informaticiens sont paresseux, donc une solution a t cherche pour viter de rpter ainsi une squence dinstructions. Dans les "temps anciens", on parlait de sous-programmes, cest--dire quune suite dinstructions taient regroupes dans une sorte de miniprogramme, lequel tait excut la demande par ce qui devenait alors le programme principal. Ceux dentre vous qui ont pratiqu des langages comme le Basic se rappellent peut-tre des instructions comme GOTO ou GOSUB, qui taient les prmices de la programmation structure.

Chapitre 3

Des programmes dans un programme : les fonctions

31

Aujourdhui, on parle plutt de fonctions, parfois de procdures. Une fonction est donc une suite dinstructions, qui peut tre excute la demande par un programme : tout se passe alors comme si on avait insr les instructions de la fonction lendroit o on lappelle.

3.1. Dnir une fonction


Crer une fonction est trs simple, autant en Python quen C++. Voyez les deux programmes prcdents modis :
Python
1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. # -*- coding: utf8 -*def Separateur(): print "_"*10 print "-"*10 print 10 Separateur() print 20 Separateur() print 30 Separateur()

C++
1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. #include <iostream> #include <string> void Separateur() { std::cout << "__________" << std::endl; std::cout << "----------" << std::endl; } int main(int argc, char* argv[]) { std::cout << 10 << std::endl; Separateur(); std::cout << 20 << std::endl; Separateur(); std::cout << 30 << std::endl; Separateur(); std::cout << 40 << std::endl; return 0; }

12. print 40

La fonction est ici dnie aux lignes 2 4. Cette dnition est introduite par le mot def (pour dene, dnir en anglais) suivi du nom de la fonction, suivi dune paire de parenthses et enn du caractre deuxpoints. Les deux lignes suivantes constituent le corps de la fonction.

La fonction est ici dnie aux lignes 3 7. Cette dnition commence par donner le type des valeurs ventuellement fournies par la fonction (ici, void signie "rien"), suivi de son nom, suivi dune paire de parenthses. Le corps de la fonction est encadr dune paire daccolades.

32

Initiation la programmation avec Python et C++

Nous dnissons ici une fonction nomme Separateur(). Cette fonction a pour rle dafcher la double ligne de sparation voulue. On dsigne par corps de la fonction la suite dinstructions quelle doit excuter. Remarquez la prsentation de ces instructions : elles sont dcales vers la droite, simplement par lajout dun certain nombre despaces en dbut de chaque ligne. Ce dcalage vers la droite est appel indentation. Cette indentation est purement dcorative en C++ ; elle ne vise qu amliorer la prsentation du code et donc faciliter sa relecture. Par contre, elle est absolument essentielle en Python : cest prcisment cette indentation des lignes qui suivent celle commenant par def qui indique quelles sont les instructions "appartenant" la fonction. Dans les deux cas, la ligne vide qui suit la fonction (ligne 5 en Python et ligne 8 en C++) na quun but esthtique, an de bien sparer visuellement la fonction du reste du programme. La suite, justement, vous montre comment utiliser cette fonction : lignes 7, 9 et 11 en Python, lignes 12, 14 et 16 en C++. Il suft de donner son nom, suivi dune paire de parenthses. Tout se passe alors comme si les instructions contenues dans la fonction taient places lendroit o son nom apparat. On peut schmatiser le droulement des oprations (en se basant sur le code Python, mais cest exactement la mme chose en C++) comme illustr la Figure 3.1.
Figure 3.1 Chemin des instructions lors de lappel dune fonction.

2 3 print 10*_ print 10*_ 1

print 10 Sparateur ( ) print 20

Les oprations se droulent ainsi : 1. Linstruction print 10 est excute normalement. 2. On rencontre linstruction Separateur() : Python (ou C++) reconnat quil sagit dune fonction prcdemment dnie ; le programme est alors drout vers les instructions de la fonction.

Chapitre 3

Des programmes dans un programme : les fonctions

33

3. Les instructions contenues dans la fonction sont excutes. 4. lissue de lexcution de la fonction, on retourne "dans" le programme qui reprend son cours normal. La deuxime tape est dsigne par le terme d appel de fonction. Lensemble de ce mcanisme, de la dnition lutilisation de fonctions, est la base de ce que lon appelle la programmation structure. Mais regardez dun peu plus prs le programme en C++, en particulier la ligne 9 :
int main(int argc, char* argv[])

On retrouve les mmes lments que pour la dclaration de la fonction Separateur() : dabord un type (ici le type entier int), puis un nom (ici main, que lon peut traduire par principale), puis une paire de parenthses sauf quici des choses gurent lintrieur. Il sagit bel et bien dune fonction : tout programme en C++ (ou en C) doit ainsi dclarer une fonction nomme main(), qui est en fait la fonction excute au moment o le programme est lanc. Mais que sont ces lments entre les parenthses ?

3.2. Les paramtres


On ne le rptera jamais assez, un programme volue au cours du temps, au fur et mesure que les besoins changent. Par exemple, on pourrait estimer quune largeur de 10 caractres pour les lignes sparatrices est insufsante ; 20 serait prfrable. Dans le premier programme, cela ncessite de changer six valeurs, alors quil suft den changer deux dans le second. Nous avons donc gagn en facilit de maintenance et dvolution. Toutefois, ce nest pas encore satisfaisant. Il est prvisible que, plus tard, nous voudrons encore changer la largeur de nos lignes de tirets. Mieux : sans doute voudrons-nous un jour pouvoir afcher des lignes de diffrentes longueurs ! Par exemple, une ligne de 10 tirets, puis une ligne de 20, puis une ligne de 30 On pourrait imaginer crer une fonction pour chaque largeur de ligne, mais on retomberait alors dans les mmes problmes que ceux du premier programme. Ce quil nous faut, cest une fonction plus gnrique, cest--dire qui puisse adapter son action selon la situation. Pour raliser cela, nous allons utiliser des paramtres.

34

Initiation la programmation avec Python et C++

Modions lgrement les programmes prcdents :


Python
1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. # -*- coding: utf8 -*def Separateur(largeur): print largeur*_ print largeur*- print 10 Separateur(10) print 20 Separateur(20) print 30 Separateur(30) print 40

C++
1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. #include <iostream> #include <string> void Separateur(int largeur) { std::cout << std::string (largeur, _) << std::endl; std::cout << std::string (largeur, -) << std::endl; } int main(int argc, char* argv[]) { std::cout << 10 << std::endl; Separateur(10); std::cout << 20 << std::endl; Separateur(20); std::cout << 30 << std::endl; Separateur(30); std::cout << 40 << std::endl; return 0; }

Nous avons ajout un lment, nomm largeur, entre les parenthses de la dnition de la fonction Separateur(). Il sagit dun paramtre donn la fonction, au sein de laquelle il se comporte comme une variable, dont la valeur nest pas connue a priori. Ce paramtre dtermine le nombre de caractres qui seront afchs sur chaque ligne. ce sujet, notez quon place habituellement un caractre unique entre apostrophes plutt quentre guillemets. Notez galement que lon construit une chane de caractres en C++ comme la rptition dun unique caractre (lignes 5 et 7) : on retrouve le nom du type std::string, puis on donne entre parenthses le nombre de rptitions et le caractre rpter. Lorsque la fonction est utilise (lignes 7, 9 et 11 en Python, lignes 13, 15 et 17 en C++), on place dsormais une valeur entre les parenthses de lappel de fonction. Cette valeur est transmise la fonction par lintermdiaire du paramtre largeur : tout se passe alors comme si largeur tait une variable contenant la valeur indique au moment de lappel de fonction. Au premier appel, largeur vaut 10, au deuxime, elle vaut 20, et 30 au troisime appel.

Chapitre 3

Des programmes dans un programme : les fonctions

35

3.3. Les blocs dinstructions


Les instructions contenues dans une fonction forment un tout : elles vont ensemble. On dit quelles constituent un bloc dinstructions. Plus gnralement, un bloc est un ensemble dinstructions qui se distinguent du programme. Cette notion est fondamentale car en dcoule (en partie) la notion de porte dune variable : toute variable nexiste qu lintrieur du bloc dans lequel elle a t dclare.
Python C++

Un bloc est dni par lindentation des lignes qui le composent. Une suite de lignes indentes dun mme nombre de caractres constitue donc un bloc. Par exemple :
1. # -*- coding: utf8 -*2. print "Dbut" 3. entier = 1 4. print entier 5. print entier

Un bloc est dlimit par une paire daccolades. Par exemple :


1. 2. 3. 4. 5. 6. 7. 8. 9. 10. #include <iostream> int main(int argc, char* argv[]) { std::cout << "Dbut"; { int entier = 1; std::cout << entier; } std::cout << entier; }

Les lignes 3 et 4 constituent un bloc distinct du reste du programme.

Les lignes 6 et 7 constituent un bloc distinct du reste du programme.

Dans les deux programmes prcdents, une variable nomme entier est dclare lintrieur dun bloc. Cette variable na alors dexistence qu lintrieur de ce bloc : en dehors, elle nexiste pas. Aussi, la ligne 5 du programme Python et la ligne 9 du programme C++ provoqueront-elles une erreur, car cet endroit on se situe en dehors du bloc dinstructions prcdent : la variable entier est donc inconnue ce point prcis. Les blocs sont essentiellement utiliss pour rassembler des instructions au sein dune fonction. Une utilisation comme celle montre prcdemment est plus rare, mais prsente parfois un intrt quand on souhaite faire "disparatre" des variables dont on na plus besoin. Incidemment, cela montre que vous pouvez parfaitement dclarer des variables lintrieur dune fonction.

36

Initiation la programmation avec Python et C++

3.4. Retourner une valeur


Il nous reste un dernier pas faire sur le chemin de la gnricit (enn, pour linstant). Notre fonction effectue certains calculs, dans la mesure o on considre que la construction dune chane de caractres est un calcul, puis afche le rsultat de ces calculs. Cest justement l le dernier problme qui reste rsoudre : cette fonction ne devrait pas afcher quoi que ce soit. La possibilit de dnir des fonctions, ou dautres types dentits que nous rencontrerons plus tard, participe du grand principe de la programmation structure. Cest-dire quon sefforce dorganiser les programmes selon une mthode cartsienne, consistant diviser les gros problmes en plus petits. On constitue ainsi des lments de programme, qui communiquent et travaillent ensemble, tout en vitant dans la mesure du possible que ces lments dpendent trop les uns des autres. Tous les langages de programmation modernes offrent des techniques pour atteindre cet objectif, les fonctions en tant une parmi dautres. Le but est dviter le fouillis que prsentaient danciens programmes crits, par exemple, en langage Basic. De lexprience accumule depuis les origines de la programmation, deux grands ensembles se sont dgags, chacun tant prsent dans chaque programme :

Linterface. Partie du programme responsable de linteraction avec lutilisateur, cest--dire de ce que doit afcher le programme et les moyens par lesquels lutilisateur peut donner des informations au programme ; on utilise parfois le terme savant dIHM (interface homme-machine) ou langlicisme look and feel (apparence et manire dtre). Le noyau. Cur du programme, contenant ce quon pourrait appeler (avec les prcautions dusage) son intelligence : la partie du programme qui effectue des calculs pour produire des rsultats certains devant tre afchs, dautres pas.

Lexprience a montr quil tait vivement recommand de sparer nettement ces deux grandes parties. La structure du programme nen est que plus claire et aise apprhender ; ainsi, la modication dune partie risque moins dentraner des modications importantes dans lautre partie. Ne pas respecter ce principe lmentaire dbouche invitablement, tt ou tard, sur un effort considrable si le programme doit voluer : si tout est trop mlang, un changement dun ct ncessitera un changement de lautre, qui son tour risquera dentraner un changement dans la premire partie Cela devient rapidement infernal. Notre exemple, si trivial soit-il, nchappe pas cette rgle. Nous avons une partie de programme qui effectue un calcul, savoir construire une chane de caractres.

Chapitre 3

Des programmes dans un programme : les fonctions

37

strictement parler, la fonction qui effectue ce calcul na pas "savoir" ce que lon va faire de cette chane de caractres. Pour linstant, la fonction provoque un afchage. Mais comment faire si, demain, au lieu dafcher cette chane nous voulons lcrire dans un chier ou la combiner de manire complexe avec dautres chanes ? En ltat actuel, une telle volution du programme ncessiterait des modications profondes stendant sur tout le code. Lide consiste donc dnir des fonctions qui font ce pour quoi elles ont t cres, mais pas plus. Notre fonction calcule une chane de caractres, daccord. Mais ce nest pas son rle de lafcher. Ce rle est dvolu au programme principal. Il faut donc un moyen pour "rcuprer" ce que la fonction aura produit. On dit alors que la fonction va retourner un rsultat, ce rsultat tant la valeur de retour de la fonction. Ce qui sera fait de ce rsultat est de la responsabilit de celui qui appelle la fonction, ici, le programme principal. Voyez la nouvelle et dernire version de notre programme :
Python
1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. # -*- coding: utf8 -*def Separateur(largeur): chaine = largeur*_ + "\n" + \ largeur*- + "\n" return chaine print print print print print print print 10 Separateur(10), 20 Separateur(20), 30 Separateur(30), 40

C++
1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. #include <iostream> #include <string> std::string Separateur(int largeur) { std::string chaine = std::string(largeur, _) + "\n" + std::string(largeur, -) + "\n"; return chaine; } int main(int argc, char* argv[]) { std::cout << 10 << std::endl << Separateur(10) << 20 << std::endl << Separateur(20) << 30 << std::endl << Separateur(30) << 40 << std::endl; return 0; }

La virgule la n des instructions print des lignes 8, 10 et 12 demande Python de ne pas sauter de ligne aprs lafchage. En effet, le saut de ligne est dj contenu dans la chane produite par Separateur().

38

Initiation la programmation avec Python et C++

Dsormais, la fonction Separateur() construit une chane de caractres et la "retourne". Dans les deux langages, le retour dune valeur est effectu par linstruction return (ligne 5 en Python, ligne 10 en C++). Invoquer return provoque la sortie immdiate de la fonction, mme si des instructions se trouvent aprs. Par ailleurs, notez comment la dclaration de la fonction a d tre change dans le programme C++ : void est devenu std::string, signalant ainsi littralement que la fonction ne retourne pas rien, mais une valeur de type std::string. Une fonction dnie ainsi peut tre rapproche de la notion mathmatique ponyme. Enn, le lecteur attentif aura probablement remarqu au sein de la fonction Separateur() la prsence de la chane "\n", concatne aux suites de tirets. Il sagit en ralit dune chane dun unique caractre, reprsent par le symbole \n : celui-ci correspond tout simplement au saut de ligne. Lorsque ce caractre est afch, les afchages suivants commencent au dbut de la ligne suivante.

3.5. Quand crer une fonction ?


Cest sans doute lune des premires questions que vous vous poserez lors de vos exprimentations personnelles. Il ny a pas vraiment de rponse absolue. mesure que vous gagnerez en exprience, lintrt des fonctions vous apparatra plus clairement. Pour vous donner une ide, quelquun a dit un jour : " Si a fait plus de trois lignes et que je men serve plus de trois fois, alors je fais une fonction." Cest un peu simpliste, mais vous voyez lide. Dune manire gnrale, on fait une fonction lorsquune squence dinstructions a toutes les chances dtre utilise de nombreuses fois, quitte remplacer quelques valeurs par des paramtres.

4
Linteractivit : changer avec lutilisateur
Au sommaire de ce chapitre :

Salutations ! Obtenir un nombre entier Obtenir une valeur dcimale Saisir plusieurs valeurs

40

Initiation la programmation avec Python et C++

Afcher des rsultats est une fonction importante de limmense majorit des programmes. Encore faut-il les prsenter correctement, dune manire aisment comprhensible par lutilisateur Lequel utilisateur doit gnralement entrer des informations pour que le programme puisse calculer ces rsultats. Cet change dinformations entre un logiciel et celui qui lutilise est gnralement dsign par le terme d interactivit : le programme et lutilisateur agissent lun sur lautre, par lintermdiaire de lcran (ou tout autre dispositif dafchage, comme une imprimante) et le clavier (ou tout autre dispositif dentre, comme la souris). Voyons ensemble comment mettre en uvre cette interactivit avec quelques exemples simples.

4.1. Salutations !
Notre premier programme saluait le monde. Voici comment crire un programme qui demande son nom lutilisateur, avant de le saluer :
Python
1. 2. 3. 4. # -*print nom = print coding: utf8 -*"Quel est votre nom?", raw_input() "Bonjour,", nom, "!"

C++
1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. #include <iostream> #include <string> int main(int argc, char* argv[]) { std::cout << "Quel est votre nom? "; std::string nom; std::cin >> nom; std::cout << "Bonjour, " << nom << "!" << std::endl; return 0; }

La virgule dans linstruction print insre un espace lafchage et empche le retour la ligne : cest cette n quelle est utilise ligne 2.

Essayez de deviner par vous-mme ce qui se passe dans ces programmes, en usant de votre imagination pour dterminer ce que font les lments encore inconnus.

Chapitre 4

Linteractivit : changer avec lutilisateur

41

Le principe est extrmement simple : on cre une variable, nom, destine recevoir une chane de caractres contenant justement le nom de lutilisateur. Ce nom est demand, rcupr partir du clavier, puis stock dans la variable. Laquelle est enn utilise pour saluer lutilisateur. La partie "rcupration partir du clavier" est effectue ligne 3 en Python, ligne 7 en C++.
Python C++

On utilise linstruction raw_input(), qui est, en fait, une fonction retournant une chane de caractres. Lorsque cette fonction est invoque, le programme est comme "bloqu" : il attend que lutilisateur appuie sur la touche Entre. Ds que cette action survient, la fonction retourne les caractres qui ont t saisis auparavant.

On utilise lobjet std::cin, qui est le miroir de lobjet std::cout que vous connaissez dj. Remarquez dailleurs que les chevrons >> sont en sens inverse, comme si des caractres "sortaient" de std::cin pour aller dans la variable. cet endroit, le programme est en attente de lappui de la touche Entre, aprs quoi les caractres saisis auparavant sont transmis par std::cin.

Comme vous le voyez, il ny a l rien de bien compliqu (pour linstant). Comme toujours, la diffrence essentielle entre les deux programmes est quen C++, la variable nom doit tre dclare avant de pouvoir y placer quoi que ce soit. Sans doute serez-vous amen frquemment parler d entres-sorties dun programme. Par convention, le point de rfrence est le programme lui-mme : des informations entrent dans le programme ou sortent du programme. La distinction est particulirement visible en C++ : si vous considrez les objets std::cin et std::cout comme des canaux de communication avec le monde extrieur au programme, les chevrons indiquent dans quel sens les informations transitent.

42

Initiation la programmation avec Python et C++

4.2. Obtenir un nombre entier


Nous venons de voir comment obtenir une chane de caractres de lutilisateur. Mais quen est-il lorsque nous voulons obtenir un nombre entier ? Par exemple, tant donn un jeu de cartes et un nombre de joueurs, comment savoir combien de cartes aura chaque joueur, et ventuellement combien il en restera ? Une solution possible est la suivante :
Python
1. 2. 3. 4. 5. 6. 7. 8. 9. # -*- coding: utf8 -*print "Combien de cartes?", nb_cartes = int(raw_input()) print "Combien de joueurs?", nb_joueurs = int(raw_input()) print "Chaque joueur aura", \ nb_cartes / nb_joueurs, \ "cartes, il en restera", \ nb_cartes % nb_joueurs

C++
1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. #include <iostream> int main(int argc, char* argv[]) { int nb_cartes = 0; int nb_joueurs = 1; std::cout << "Combien de cartes? "; std::cin >> nb_cartes; std::cout << "Combien de joueurs? "; std::cin >> nb_joueurs; std::cout << "Chaque joueur aura " << nb_cartes / nb_joueurs << " cartes, il en restera " << nb_cartes % nb_joueurs << std::endl; return 0; }

Cet exemple met en lumire une nouvelle diffrence entre Python et C++. Comparons deux extraits de code faisant exactement la mme chose, savoir obtenir un entier de lutilisateur :
Python
nb_cartes = int(raw_input())

C++
int nb_cartes; std::cin >> nb_cartes;

Chapitre 4

Linteractivit : changer avec lutilisateur

43

Le code en C++ est trs proche de celui que nous avons vu pour la chane de caractres. Par contre, le code Python "enferme" la fonction raw_input() dans ce qui pourrait tre une fonction nomme int(). Rappelez-vous, nous avons dit que, dune part, Python dtermine le type dune variable partir du contexte et de sa valeur, et dautre part, que raw_input() retourne une chane de caractres. Or, ici, nous voulons des nombres entiers an deffectuer certains calculs. Contrairement au C++, o toute variable doit tre dclare avec un type clairement spci, Python na aucun moyen de "deviner" que nous voulons un nombre entier. Le rle de cette pseudo-fonction int() est prcisment de prciser le type recherch : partir de la chane de caractres renvoye par raw_input(), on "fabrique" une valeur numrique, laquelle sera stocke dans la variable qui sera donc, par consquent, de type numrique et non pas de type chane de caractres. Du ct du C++, la fabrication de la valeur numrique partir des caractres donns par lutilisateur est effectue automatiquement par lobjet std::cin lui-mme, celuici ayant reconnu que la variable dans laquelle il doit placer ses informations est de type numrique. Nous sommes donc en prsence dune conversion de type : partir dune valeur dun type donn (une chane de caractres), on fabrique une valeur dun autre type (une valeur numrique entire). Dans le verbiage de la programmation, on parle de transtypage (type cast, ou simplement cast, en anglais). Dernier mot, remarquez lutilisation de loprateur % : celui-ci donne le reste dune division entre nombres entiers, en Python comme en C++.

44

Initiation la programmation avec Python et C++

4.3. Obtenir une valeur dcimale


Pour tre complet, voyons maintenant un exemple utilisant des nombres virgule ottante, par exemple pour calculer la vitesse moyenne dun trajet :
Python
1. 2. 3. 4. 5. 6. # -*- coding: utf8 -*print "Distance (en km)?", distance = float(raw_input()) print "Dure (en h)?", duree = float(raw_input()) print "Vitesse:", distance/duree

C++
1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. #include <iostream> int main(int argc, char* argv[]) { double distance = 0.0; double duree = 1.0; std::cout << "Distance (en km)? "; std::cin >> distance; std::cout << "Dure (en h)? "; std::cin >> duree; std::cout << "Vitesse: " << distance / duree << std::endl; return 0; }

Nous retrouvons un transtypage en Python, cette fois-ci en utilisant la pseudo-fonction float(). En dehors de cela, ces exemples sont parfaitement quivalents aux prcdents !

4.4. Saisir plusieurs valeurs


Si vous utilisez la version C++ du programme de salutation vu prcdemment en lui donnant un nom complet (prnom suivi du nom), vous remarquerez sans doute un problme qui napparat pas en Python. Comparez les Figures 4.1 et 4.2.
Python C++

Figure 4.1 Salutation en Python, avec nom et prnom.

Figure 4.2 Salutation en C++, avec nom et prnom.

Chapitre 4

Linteractivit : changer avec lutilisateur

45

Le programme C++ ne "rcupre" quune partie de ce qui a t saisi, en fait jusqu lespace sparant les deux mots. Dans certaines situations, cela peut tre ennuyeux. Au contraire, le programme Python rcupre bien les deux mots en une seule chane de caractres Mais alors, comment faire si vous voulez effectivement les sparer pour les stocker dans deux variables ?

4.4.1. Le cas du C++


Pour rcuprer une ligne complte sous la forme dune chane de caractres, il est ncessaire dutiliser une fonction spcialement ddie cela. Il suft de modier ainsi la ligne 7 du programme :
std::getline(std::cin, nom);

La fonction standard std::getline() reoit deux paramtres :

dabord un ux de caractres, cest--dire un objet partir duquel obtenir des caractres, ce quest prcisment std::cin ; ensuite, une variable de type chane de caractres, dans laquelle placer les caractres obtenus partir du ux prcdent.

Tous les caractres du ux sont placs dans la chane, jusqu ce que std::getline() rencontre un saut de ligne : il sagit dun caractre spcial "cr" par lappui de la touche Entre. Autrement dit, std::getline() place dans une chane tous les caractres se trouvant sur une ligne. Ainsi modi, le programme C++ se comporte comme le programme Python. Si, au contraire, vous voulez effectivement obtenir les mots de la ligne dans diffrentes variables, vous devez dclarer autant de variables et les donner comme "cible" std::cin. Par exemple, pour lire en une seule fois un prnom, un nom et un ge (donc une valeur numrique) :
std::string prenom; std::string nom; int age; std::cin >> prenom >> nom >> age;

Lutilisateur peut alors saisir un texte comme "Yves Bailly34" (sans les guillemets !), les trois lments seront bien rpartis dans les trois variables.

46

Initiation la programmation avec Python et C++

4.4.2. Le cas de Python


La fonction Python raw_input() est lquivalent de la fonction C++ std::getline() que nous venons de voir. Pour obtenir les diffrents lments dune chane entre par lutilisateur, il est ncessaire de dcouper (split en anglais) celle-ci, puis daffecter chaque morceau aux variables concernes. Le code qui suit anticipe un peu sur lavenir, nommment les Chapitres 6 et 9, mais vous devriez pouvoir lapprhender sans difcult :
chaine = raw_input() morceaux = chaine.split() prenom = morceaux[0] nom = morceaux[1] age = int(morceaux[2])

Le dcoupage de la chane est ralis en lui appliquant une opration nomme split(). Sans entrer pour linstant dans les dtails, une opration est un peu comme une fonction qui sapplique sur un objet dont le nom la prcde, spar par un point (cela sera expliqu au Chapitre 9). Ici, on applique donc lopration split() lobjet chaine. Le rsultat est le morcellement de la chane, les morceaux tant stocks dans une variable nomme morceaux (qui est, en fait, un tableau, mais nous reverrons cela au Chapitre 6). Chaque morceau de la chane, lui-mme une chane de caractres, est nalement obtenu en donnant le nom du morcellement (ici morceaux) suivi du numro du morceau entre crochets, le premier ayant le numro 0 (zro), le deuxime le numro 1, et ainsi de suite. On retrouve dailleurs le transtypage ncessaire pour obtenir une valeur de type numrique. Cet extrait de code Python est ainsi quivalent lextrait de code C++ que nous venons de voir.

5
Ronds-points et embranchements : boucles et alternatives
Au sommaire de ce chapitre :

Lalternative La boucle Algbre de Boole et algorithmique

48

Initiation la programmation avec Python et C++

Les programmes que nous avons vus jusquici sont essentiellement linaires, cest-dire que les instructions sont excutes lune aprs lautre, dans lordre dans lequel elles apparaissent. Cela prsente le mrite de la simplicit mais, galement, linconvnient dtre trop sommaire. Dans votre vie quotidienne, vous tes sans cesse amen faire des choix guids par votre environnement. Par exemple, avant de partir en balade, sans doute vous posezvous la question en regardant le ciel : "Pleut-il ?" Si oui, vous mettez des bottes, si non, vous mettez des chaussures. De mme, il arrive que lon doive effectuer des tches caractrises par la rptition dune ou de plusieurs actions, de la laborieuse vrication dun relev de compte bancaire lpluchage dun kilo de pommes de terre. Pour triviales quelles puissent paratre, ces situations se retrouvent galement en programmation. An dillustrer cela, nous allons raliser le petit jeu suivant : le programme va choisir un nombre au hasard entre 1 et 100, que lutilisateur devra deviner en faisant des propositions. chaque proposition, le programme afchera "trop grand" ou "trop petit", selon le cas. Le dbut de ce programme pourrait ressembler ceci :
Python
1. 2. 3. 4. 5. 6. # -*- coding: utf8 -*from random import randint nombre_a_trouver = randint(1, 100) print "Donner un nombre:" chaine = raw_input() nombre_joueur = int(chaine)

C++
1. 2. 3. 4. 5. 6. 7. 8. 9. 10. #include <iostream> #include <cstdlib> int main(int argc, char* argv[]) { srandom((long)argv); int nombre_a_trouver = (random()%100) + 1; int nombre_joueur; std::cout << "Donnez un nombre: "; std::cin >> nombre_joueur;

La fonction randint() (ligne 3) donne un nombre entier au hasard compris entre les deux valeurs quelle reoit en paramtre. Cette fonction est rendue disponible par la ligne 2, dont la signication sera explicite au chapitre consacr aux bibliothques.

La fonction random() (ligne 7) donne un nombre entier au hasard compris entre 0 et une valeur maximale nomme RAND_MAX. Cette fonction est rendue disponible par la ligne 2.

Nous anticipons un peu sur lavenir pour bncier de la fonction randint() en Python et de la fonction random() en C++. Remarquez que ces fonctions ne sont pas

Chapitre 5

Ronds-points et embranchements : boucles et alternatives

49

exactement quivalentes : si la premire permet de spcier lintervalle dans lequel on souhaite obtenir un nombre au hasard, la seconde donne toujours un nombre entre 0 et RAND_MAX dont la valeur dpend du compilateur utilis et du systme. On doit donc ajuster ce que nous donne random(). Pour cela, on commence par demander le reste de la division du nombre donn par random() par le nombre le plus grand quon souhaite obtenir, ici 100. Le reste dune division entre nombres entiers est donn par loprateur % (en C++ comme en Python). Ce reste est forcment compris entre 0 et 99. On ajoute donc 1 pour obtenir effectivement un nombre entre 1 et 100. Ainsi sexplique la formule ligne 7. Il convient de prciser ici que le hasard nexiste pas en programmation. Si vous appelez plusieurs fois random(), vous obtiendrez une suite de nombres qui correspond lapplication dune formule complexe, conue de faon simuler le hasard. Cette formule utilise ce que lon appelle une graine, partir de laquelle la suite de nombres est calcule. Sans autre prcision, cette graine est toujours 1, ce qui signie que la suite de nombres gnre par random() est toujours la mme. La ligne 5 du programme C++ a pour effet de modier cette graine, au moyen de la fonction srandom(). Sans entrer dans les dtails, le procd utilis ici exploite la localisation en mmoire du programme lors de son excution, laquelle localisation est trs certainement diffrente chaque excution. On obtient ainsi une nouvelle suite de valeurs au hasard chaque excution. Remarquez que ce nest pas ncessaire en Python, la graine tant automatiquement modie chaque excution. Le hasard en programmation, que lon peut rapprocher de la notion de variable alatoire en probabilits, est un sujet bien plus complexe quil ny parat. Ce que nous venons de voir devrait toutefois rpondre la plupart de vos besoins. Mais revenons au sujet principal de ce chapitre.

5.1. Lalternative
Une alternative est une situation o lon a le choix entre deux possibilits, deux chemins. Chacune des possibilits est parfois dsigne par le terme de branche de lalternative. Plaons-nous dans la situation o lutilisateur a saisi un nombre : il nous faut dterminer le message afcher. Suivez bien, le procd voqu maintenant est lun des plus fondamentaux pour concevoir une partie de programme, voire un programme complet.

50

Initiation la programmation avec Python et C++

Mettez-vous la place du programme, l o nous en sommes, et agissez mentalement sa place. Vous disposez dun nombre de rfrence, celui tir au hasard, ainsi que dun nombre donn par lutilisateur : ce sont les donnes dentre, celles que vous devez traiter. Par ce traitement, vous devez produire un rsultat qui est un message adapt la comparaison de ces deux nombres. Dcomposez autant que possible votre processus de pense, en oubliant pour linstant le cas particulier o les deux nombres sont gaux. Cela devrait ressembler ceci :
si le nombre donn est plus grand que le nombre trouver, alors le message est "Trop grand", sinon le message est "Trop petit".

Cette simple phrase, parfois dsigne par le terme de principe, est une description synthtique et concise que ce que lon veut faire. Maintenant, rapprochons-nous de la programmation, en remplaant certains termes par les variables qui les reprsentent :
si nombre_joueur est plus grand que nombre_a_trouver, alors etc.

La notion de message, dans notre cas, correspond un afchage lcran. Par ailleurs, la comparaison de deux nombres se fait en utilisant les oprateurs mathmatiques usuels que sont les symboles < et >. Poursuivons le "codage" de notre principe, par exemple en utilisant le langage Python :
si nombre_joueur > nombre_a_trouver, alors print "Trop grand", sinon print "Trop petit".

Voil qui commence ressembler un programme ! Il nous reste traduire les mots "si", "alors" et "sinon". Le mot "si" se traduit en anglais, ainsi que dans la plupart des langages de programmation, par if. Le mot "alors" se traduit par then, mais de nombreux langages, dont Python et C++, omettent simplement ce mot. Enn, "sinon" se traduit par else. Ce qui nous donne les instructions suivantes :
Python
1. if ( nombre_joueur > \ 2. nombre_a_trouver ): 3. print "Trop grand!" 4. else: 5. print "Trop petit!"

C++
1. 2. 3. 4. 5. 6. if ( nombre_joueur > nombre_a_trouver ) { std::cout << "Trop grand!" << std::endl; }

Chapitre 5

Ronds-points et embranchements : boucles et alternatives

51

Python

C++
7. 8. 9. 10. 11. else { std::cout << "Trop petit!" << std::endl; }

Remarquez la n des lignes 2 et 4, vous retrouvez les deux-points que nous avions rencontrs pour la dnition dune fonction. Les lignes 3 et 5 sont indentes : de mme que les instructions dune fonction sont rassembles dans un bloc dinstructions, les instructions correspondant chacune des branches de lalternative sont rassembles dans un bloc. Ici chacun des deux blocs nest constitu que dune seule ligne, mais naturellement vous tes libre den rajouter. Rappelez-vous quen Python lindentation est trs importante : cest ce dcalage vers la droite, obtenu en insrant des espaces en dbut de ligne, qui dtermine ce qui fait partie de tel ou tel bloc.

On retrouve ici les accolades que nous avions rencontres lors de la dnition dune fonction. En effet, les instructions qui doivent tre excutes dans chacune des branches de lalternative sont rassembles dans un bloc, dlimit par ces accolades. Ici, nous navons quune seule instruction pour chacun de ces blocs : dans ce cas particulier, les accolades sont facultatives. Mais il est vivement recommand de toujours les utiliser, an dviter toute ambigut.

Lalternative est introduite par la premire ligne de ces deux extraits :


if ( nombre_joueur > nombre_a_trouver ) ...

Ce qui se trouve entre les parenthses constitue la condition, ou le test, qui permet de dterminer quelle branche de lalternative sera excute. Si le test russit, cest--dire si la condition est vraie, alors on excute la premire branche, celle qui se trouve juste aprs le if. Sinon, on excute lautre branche. Une fois retraduit en franais, tout cela est en fait trs naturel ! Ne ngligez pas limportance de la formulation et de la smantique en langage naturel, elle est bien souvent un guide prcieux pour lcriture du code informatique. Dans certaines situations, vous naurez pas besoin de deuxime branche lalternative. Vous voudrez simplement excuter quelque chose si une condition est remplie, et ne rien faire sinon. Dans ce cas, vous avez essentiellement deux possibilits.

52

Initiation la programmation avec Python et C++

La premire consiste explicitement indiquer que la seconde branche ne fait rien, ce qui scrit ainsi :
Python
if ( une_condition ): des_instructions else: pass autres_instructions

C++
if ( une_condition ) { des instructions; } else { } autres_instructions;

Linstruction pass signie exactement "passer la suite sans rien faire".

Si vous vous demandez quel est lintrt dutiliser une instruction comme pass en Python ou de crer un bloc vide en C++, disons simplement que de nombreux programmeurs estiment que, dune part, cela amliore la lisibilit du programme, dautre part, cela prpare en quelque sorte la place pour de futures volutions. Comparez avec lautre possibilit, qui consiste tout simplement "oublier" la partie else :
Python
if ( une_condition ): des_instructions autres_instructions

C++
if ( une_condition ) { des_instructions; } autres_instructions;

Cette forme plus concise est probablement la plus rpandue. vous de choisir celle qui vous convient le mieux, bien quil soit recommand dutiliser la forme longue (avec blocs vides), au moins dans un premier temps. Comme exercice, essayez de modier un peu le principe prcdent pour traiter le cas o les deux nombres sont gaux et de modier le code en consquence, sachant que, pour tester lgalit entre deux nombres, il faut utiliser loprateur == (et non pas le simple signe =, cest une source trs courante derreurs).

Chapitre 5

Ronds-points et embranchements : boucles et alternatives

53

Voici une possibilit :


Python
1. if ( nombre_joueur == \ 2. nombre_a_trouver ): 3. print "Vous avez gagn!" 4. else: 5. if ( nombre_joueur > \ 6. nombre_a_trouver ): 7. print "Trop grand!" 8. else: 9. print "Trop petit!"

C++
1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. if ( nombre_joueur == nombre_a_trouver ) { std::cout << "Vous avez gagn!"; } else { if ( nombre_joueur > nombre_a_trouver ) { std::cout << "Trop grand!" << std::endl; } else { std::cout << "Trop petit!" << std::endl; } }

Respectez bien lindentation, elle est trs importante en Python, surtout dans des situations comme celle-ci.

Cette solution est un exemple typique dune imbrication de blocs, que lon peut schmatiser comme la Figure 5.1, o un cadre plus fonc reprsente un bloc plus "interne" :
Figure 5.1 Blocs dinstructions imbriqus.
if ( nombre_ joueur == nombre_a_trouver ) : print Vous avez gagn ! else : if ( nombre_ joueur > nombre_a_trouver ) : print Trop grand ! else : print Trop petit !

54

Initiation la programmation avec Python et C++

Les blocs dinstructions peuvent ainsi tre imbriqus les uns dans les autres. En fait, vous constaterez quil sagit dune pratique trs courante, laquelle il est pratiquement impossible dchapper ! Votre attention est attire sur la matrialisation de ces blocs dans le code source : sils sont relativement aisment reprables en C++, grce aux accolades, la seule faon de les reconnatre en Python se fonde, par contre, sur lindentation. Rptons-le, cette indentation nest pas seulement esthtique en Python : cest elle qui dnit rellement les blocs dinstructions, linterprteur va sappuyer dessus pour identier les blocs. Alors quen C++, elle nest rien dautre quune aide visuelle, le compilateur nen faisant aucun cas.

5.2. La boucle
Nous avons progress, mais notre programme nest pas termin : actuellement, lutilisateur ne peut proposer quun seul nombre, puis le programme se termine. Comment faire pour que lutilisateur puisse proposer plusieurs nombres ? Une solution naturelle consisterait dupliquer les lignes de code. Mais cela pose au moins deux problmes majeurs. Si les lignes dupliquer sont trs nombreuses, on augmenterait ainsi considrablement la masse totale du code source, cest--dire le nombre dinstructions. Or il a t dmontr que les risques derreurs et la difcult de maintenance dun programme augmentent mcaniquement et rapidement avec sa taille : cette solution conduirait donc un programme quil serait extrmement pnible de faire voluer et de corriger en cas de problme. Dans bien des situations, dont la ntre, on ne sait pas lavance combien de fois il sera ncessaire de rpter ces mmes instructions. Ici, si on peut estimer quune centaine de rptitions serait largement sufsante et supportable, cela ne serait vraiment pas raisonnable dans les cas o les instructions doivent tre rptes des milliers ou des millions de fois. Le premier point pourrait tre rgl par lutilisation dune fonction. Mais cela ne changerait rien au second. Nous avons besoin de quelque chose de nouveau. Encore une fois, cherchons un principe, formul en langage clair. Considrons le suivant :
tant que les nombres ne sont pas gaux, afcher un message adapt puis demander un nouveau nombre.

Chapitre 5

Ronds-points et embranchements : boucles et alternatives

55

La partie "afcher un message" a dj t traite, nous pouvons donc remplacer ce membre par notre principe prcdent (que nous savons dj traduire) :
tant que les nombres ne sont pas gaux, si le nombre donn est plus grand que le nombre de rfrence, alors afcher "Trop grand", sinon afcher "Trop petit", puis demander un nouveau nombre.

Ce qui nous intresse ici est le premier membre de cette phrase. Les mots "tant que" sous-entendent que nous allons effectuer les oprations qui suivent jusqu ce quune certaine condition soit vrie ou, plus prcisment ici, tant quune condition nest pas vrie. Premier problme, comment traduire lexpression "ne sont pas gaux" ? Il suft dexprimer la ngation de la condition "sont gaux". En langage Python comme en C++, la ngation dune condition scrit simplement not. En effectuant les remplacements par les variables que nous avons, et en utilisant les oprateurs de comparaison, nous pouvons rcrire la phrase ainsi :
tant que not ( nombre_joueur == nombre_a_trouver ), si ( nombre_joueur > nombre_a_trouver ) alors print "Trop grand", sinon print "Trop petit", puis demander un nouveau nombre.

Maintenant, si vous saviez que les mots "tant que" se traduisent en anglais par while, avec un peu dimagination vous devriez pouvoir crire le code correspondant cette phrase. Prcisons simplement que while est galement un mot cl en Python et en C++ pour reprsenter une boucle Voici ce que cela devrait donner nalement comme programmes complets :
Python
1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. # -*- coding: utf8 -*from random import randint nombre_a_trouver = randint(1, 100) print "Donnez un nombre:", chaine = raw_input() nombre_joueur = int(chaine) while ( not ( nombre_joueur == \ nombre_a_trouver ) ): if ( nombre_joueur > \ nombre_a_trouver ): print "Trop grand!" else: print "Trop petit!"

C++
1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. #include <iostream> #include <cstdlib> int main(int argc, char* argv[]) { srandom((long)argv); int nombre_a_trouver = (random()%100) + 1; int nombre_joueur; std::cout << "Donnez un nombre: "; std::cin >> nombre_joueur; while ( not ( nombre_joueur == nombre_a_trouver ) )

56

Initiation la programmation avec Python et C++

Python
14. print "Donnez un nombre:", 15. chaine = raw_input() 16. nombre_joueur = int(chaine) 17. print "Vous avez trouv!"

C++
13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. } { if ( nombre_joueur > nombre_a_trouver ) { std::cout << "Trop grand!\n"; } else { std::cout << "Trop petit!\n"; } std::cout << "Donnez un nombre: "; std::cin >> nombre_joueur; } std::cout << "Vous avez trouv!\n"; return 0;

Nous ne le rpterons jamais sufsamment : prenez garde lindentation. Si vous constatez que ce programme ne fonctionne pas correctement, vriez votre indentation. Les instructions dun mme bloc doivent tre dcales vers la droite dun mme nombre despaces ! Cela nest valable quen langage Python, lindentation na quun but esthtique dans limmense majorit des autres langages.

Remarquez que les sauts de ligne rpondent au caractre \n plutt qu std::endl.

Si vous avez essay de raliser vous-mme ce programme et que vous aboutissiez une solution un peu diffrente, cela ne signie pas forcment que vous tes dans lerreur : pour une mme tche, ds que la quantit de code augmente, diffrents programmeurs "produiront" diffrentes solutions, chacun sappuyant sur son exprience, ses connaissances et mme sa sensibilit. Cest ainsi que le style fait son apparition dans une activit pourtant apparemment si stricte. Ce que nous venons de raliser est parfois dsign par le terme de rptitive, et plus communment par le terme de boucle. Une boucle consiste rpter une suite dinstructions (rassembles dans un bloc, comme vous pouvez le constater ici), chaque rptition (ou tour de boucle) tant prcde dune condition. Si cette condition est

Chapitre 5

Ronds-points et embranchements : boucles et alternatives

57

vrie, le bloc de la boucle est excut, sinon il est ignor et le programme se poursuit linstruction suivant immdiatement la boucle. Dans notre exemple, la condition est la ngation (not) de lgalit de deux nombres. Cela signie que la boucle sarrtera ds que les deux nombres seront gaux. Formul diffremment et plus prcisment, la boucle sarrtera ds quil sera faux de dire que les deux nombres ne sont pas gaux : comme en langage naturel, une double ngation quivaut une afrmation. Ou encore : la boucle se poursuivra tant quil sera vrai de constater quil est faux de dire que les deux nombres sont gaux

5.3. Algbre de Boole et algorithmique


Nous venons de voir trois notions trs importantes en programmation, dont une de faon implicite. Dabord la notion dalternative, qui permet dorienter le ux dun programme, la manire dun aiguillage sur une voie ferre. Grce cela, certaines parties dun programme ne seront excutes que selon certaines conditions donnes. Puis la notion de boucle, qui permet dexcuter plusieurs fois une mme suite dinstructions, comme si la voie ferre formait un anneau, revenant sur elle-mme. La sortie de lanneau, cest--dire la n de la boucle, dpend dune condition donne (dun aiguillage). La troisime notion vient juste dtre mentionne deux fois, car elle est implicite dans les deux prcdentes : la notion de condition. La valeur dune condition est donne par le rsultat dun calcul particulier, qui est le test correspondant la condition. strictement parler, le type de cette valeur nest ni un entier, ni une chane de caractres. Il sagit dun type dit boolen (prononcez "bou-l-un"), par rfrence au modle dvelopp par le mathmaticien et logicien britannique George Boole (1815-1864, dates mettre en regard du terme si commun "nouvelles technologies"). Une variable entire (de type entier) peut prendre les valeurs 1, 2, 3, 197635, et bien dautres. Une variable boolenne (de type boolen) ne peut prendre que deux valeurs : soit vrai (true), soit faux (false). Par commodit, on note souvent chacune de ces valeurs respectivement 1 et 0, mais ce nest quune reprsentation. Les rgles qui rgissent les calculs quon peut effectuer sur de telles variables (et bien dautres choses encore) sont rassembles dans ce quon appelle l algbre de Boole, conu justement par M. Boole. Nous avons rencontr lune de ces oprations : la ngation.

58

Initiation la programmation avec Python et C++

Cette opration donne une valeur oppose la valeur de la variable sur laquelle elle est applique : si une variable a la valeur vrai, sa ngation est la valeur faux. Si une variable a la valeur faux, sa ngation a la valeur vrai. Cela a t utilis dans lcriture de la condition de la boucle dans les programmes prcdents. Ces trois notions font aujourdhui partie des fondements de ce quon appelle l algorithmique, la science qui tudie les algorithmes. Ltymologie de ce mot remonte au Moyen ge, au moment de la traduction dun ouvrage publi en lan 825 par lastronome et mathmaticien perse Muhammad ibn Musa al-Khuwarizmi, ouvrage dont le titre (Al-jabr wal-muqbalah) a donn le mot algbre. Un autre ouvrage du mme auteur a t le premier traiter du systme des nombres indiens que nous utilisons aujourdhui, premier systme de lhistoire inclure le nombre zro. Un algorithme est la description dune suite dactions ou doprations effectuer dans un certain ordre. Au cours de cette suite, peuvent intervenir des boucles ou des alternatives. La notion mme dalgorithme est loin dtre nouvelle. On doit Euclide un procd systmatique pour trouver le plus grand diviseur commun (PGDC) de deux nombres, vers le IIIe sicle avant lan 1. peine plus jeune, ratosthne a imagin une mthode, appele crible dratosthne, qui permet dobtenir les nombres premi ers. La notion dalgorithme a t formalise par lady Ada Lovelace Byron, lle du pote ponyme au XIXe sicle, en hommage de laquelle fut nomm le rigoureux langage de programmation Ada. Nous venons de pratiquer un peu lalgorithmique, en imaginant les successions dactions ncessaires pour obtenir le rsultat dsir. Laction de traduire cet algorithme en langage Python ou C++ est dsigne par le terme d implmentation de lalgorithme. Tout travail de programmation est, en fait, compos au minimum de ces deux tapes : conception puis implmentation de lalgorithme. Avec lexprience, dans bien des situations la premire phase devient presque inconsciente et on a tendance se jeter sur le clavier. Il faut se mer de cette tendance, qui conduit parfois omettre des cas particuliers importants.

6
Des variables composes
Au sommaire de ce chapitre :

Plusieurs variables en une : les tableaux Plusieurs types en un : structures Variables composes et paramtres

60

Initiation la programmation avec Python et C++

Plaons-nous un instant dans la peau dun enseignant distribuant des notes ses lves et voulant obtenir quelques informations, comme la note moyenne lissue dune preuve, ceux qui sont au-dessus, ceux qui sont en dessous Bref, un certain nombre de statistiques simples mais laborieuses obtenir "manuellement". Ajoutons que notre enseignant ignore que son tablissement scolaire dispose de trs bons logiciels pour faire cela. Avec ce que nous venons de voir, vous tes dj mme de concevoir un programme capable de calculer une moyenne : il suft de demander des notes les unes aprs les autres, de les additionner au fur et mesure, pour enn afcher le total divis par le nombre de notes. Seulement, cela ne nous permet pas de dterminer quelles sont les notes au-dessus ou en dessous de cette moyenne : il faudrait pour cela se "souvenir" de chaque note, pour pouvoir la comparer la moyenne aprs que celle-ci a t calcule. Le problme essentiel tant quon ne sait pas forcment lavance combien il y aura de notes dont il faudra se souvenir Peut-tre pensez-vous crer un grand nombre de variables, comme note_01, note_02, etc., pour mmoriser toutes ces valeurs. Mais jusquo devrez-vous aller ? Si lenseignant est dans un collge, une quarantaine de variables devraient sufre, ce qui est dj assez pnible saisir dans un programme. Mais sil est dans une universit, alors il en faudra peut-tre des centaines Mais il est hors de question de crer " la main" des centaines de variables ! Heureusement, des outils existent pour ce type de situations.

6.1. Plusieurs variables en une : les tableaux


Plutt que de dclarer des centaines de variables, nous allons en dclarer une unique, ayant la capacit de contenir des centaines de valeurs. Ce sera par la suite possible de manipuler chacune de ces valeurs, comme sil sagissait dune variable individuelle. Selon les situations et les besoins, il existe diffrentes techniques pour raliser un tel stockage. Nous ne verrons ici que la plus simple : lutilisation de tableaux (array en anglais). Le principe est extrmement simple. Imaginez quune valeur que vous voulez mmoriser soit un uf. Si vous navez besoin que dun uf (que dune seule valeur), vous pouvez le saisir directement. Mais si vous avez besoin dune douzaine dufs, vous aurez naturellement recours une bote ufs : vous ne manipulez plus les ufs un par un, mais plutt la bote dans son ensemble. Un tableau nest rien dautre quune

Chapitre 6

Des variables composes

61

bote ufs, o les valeurs sont ranges les unes ct des autres. De mme que vous pouvez dsigner chaque uf dans la bote en les dnombrant (le premier uf, le deuxime, etc.), les valeurs dans un tableau sont numrotes : elles sont donc implicitement ordonnes, telle valeur se trouvant avant ou aprs telle autre valeur. Un tableau est un exemple de conteneur, cest--dire dun type de variable destin contenir plusieurs valeurs. Pour poursuivre votre omelette, vous pouvez utiliser un panier plutt quune bote : dans ce cas, les ufs sont dsordonns. Il existe des types de conteneurs dans lesquels les valeurs sont mmorises "en vrac". Revenons notre enseignant. Pour mmoriser ses notes, il va donc utiliser un tableau, par exemple nomm notes. La Figure 6.1 montre comment on peut reprsenter la variable notes.
Figure 6.1 Un tableau de notes.
programme mmoire notes
12 8.5 13.5 7 11 ...

Peut-tre cette reprsentation vous rappelle-t-elle quelque chose : celle dune chane de caractres, que nous avons vue au Chapitre 2. Ce nest pas un hasard : fondamentalement, une chane de caractres est un tableau de caractres. Vous manipulez donc des tableaux depuis le dbut ! Voyons enn comment dclarer et utiliser un tableau dans un programme. Voici le dbut du programme moyenne que pourrait crer lenseignant :
Python
# -*- coding: utf8 -*print "Combien de notes?", nb_notes = int(raw_input()) notes = nb_notes * [0.0]

C++
#include <iostream> #include <vector> int main(int argc, char* argv[]) { int nb_notes = 0; std::cout << "Combien de notes? "; std::cin >> nb_notes; std::vector<double> notes(nb_notes, 0.0);

Le programme commence par demander combien il devra mmoriser de notes (nous verrons un peu plus loin comment faire si cette information nest pas connue), ce

62

Initiation la programmation avec Python et C++

nombre tant stock dans la variable nb_notes. Puis le tableau notes lui-mme est cr. La syntaxe en Python nest pas sans rappeler celle permettant de dupliquer une chane de caractres : on utilise loprateur de multiplication *, en donnant dun ct le nombre dlments dans le tableau, de lautre la valeur initiale de chacun de ces lments entre crochets. Ce sont les crochets qui indiquent que nous construisons un tableau, plutt que de simplement multiplier deux nombres. Comme on pouvait sy attendre, les oprations sont un peu plus complexes en C++. Notez dabord la deuxime ligne de lextrait de code prcdent : #include <vector>. Elle est ncessaire pour pouvoir utiliser les tableaux, du moins ceux reprsents par le type std::vector<>. Remarquez bien les chevrons < et > la n du nom du type. Entre eux, vous devez inscrire le type des valeurs qui seront stockes dans le tableau, ici le type double. Cela pourrait tre (presque) nimporte quel autre type. Dans notre cas, std::vector<double> est donc un type "tableau de valeurs de type double". Si vous crivez std::vector<std::string>, il sagit dun type "tableau de valeurs de type chane de caractres". Vous pouvez mme dclarer une variable contenant un tableau de tableaux, par exemple :
std::vector<std::vector<double> > variable;

On dit alors que variable est de type "tableau de valeurs de type tableau de valeurs de type double", ou plus simplement, "tableau de tableaux de doubles". Il sagit dun tableau deux dimensions. Un tableau "simple", dont les valeurs ne sont pas ellesmmes des tableaux, est une dimension. Vous pouvez crer ainsi des tableaux aussi complexes que vous le souhaitez, mais prenez garde ne pas vous perdre dans des mandres spatio-temporels la suite du type std::vector<double> se trouve, comme toujours, le nom de la variable, ici accompagne du nombre dlments dans le tableau ( nb_notes) et la valeur de chacun de ces lments ( 0.0). Cette criture est parfaitement analogue celle que nous avons rencontre prcdemment pour crer des lignes de tirets, la section 3.2. Une preuve supplmentaire quune chane de caractres est un type particulier de tableau dont les valeurs sont des caractres. En Python comme en C++, la suite de ces instructions nous disposons dune variable notes permettant daccder un tableau de valeurs de type double, pour linstant toutes initialises 0.0. Si lutilisateur a demand cinq notes, la situation en mmoire est telle que montre par la Figure 6.2.

Chapitre 6

Des variables composes

63

Figure 6.2 Tableau de notes initialises 0.

notes
0.0 0.0 0.0 0.0 0.0

6.1.1. La boucle borne


Maintenant que nous avons notre tableau, il est temps dy placer les notes obtenues par les lves. Nous allons avoir besoin dune boucle : pour chaque lve, demander la note obtenue et la placer dans le tableau. Vous pourriez construire une boucle en utilisant le mot cl while vu au chapitre prcdent, mais nous nous trouvons ici dans une situation un peu particulire : nous savons exactement combien de notes il y a demander. Dans ce genre de cas, assez typique lorsquon utilise des tableaux, on se sert plutt dun autre type de boucle, une boucle borne. Celle-ci sappuie sur une variable utilise comme compteur pour dnombrer le nombre de fois que la boucle doit tre excute. Ce type de boucle est introduit par le mot cl for, dont voici une application :
Python
for indice_note in range(nb_notes): print "Note", indice_note+1, "?", notes[indice_note] = \ float(raw_input())

C++
for(int indice_note = 0; indice_note < nb_notes; ++indice_note) { std::cout << "Note " << indice_note+1 << "? "; std::cin >> notes[indice_note]; }

La fonction range() permet de dclarer un intervalle de valeurs entires comprises entre 0 et la valeur passe en paramtre, moins 1. Par exemple, range(5) reprsente un intervalle compris entre 0 et 4, soit les valeurs 0, 1, 2, 3 et 4.

Pour une variable v de type entier, ++v augmente la valeur de v de 1 ; cette criture est donc quivalente v=v+1.

La ligne qui commence par for (ou les trois lignes en C++, remarquez les parenthses) pourrait se traduire par :
pour indice_note allant de 0 nb_notes1, faire

64

Initiation la programmation avec Python et C++

Cette forme de boucle est quivalente une boucle introduite par while. On pourrait crire la mme chose de la faon suivante :
Python
indice_note = 0 while ( indice_note < nb_notes ): print "Note", indice_note+1, "?", notes[indice_note] = \ float(raw_input()) indice_note += 1

C++
int indice_note = 0; while ( indice_note < nb_notes ) { std::cout << "Note " << indice_note+1 << "? "; std::cin >> notes[indice_note]; ++indice_note; }

Lcriture avec for() et celle avec while() se comportent de la mme faon1, mais la premire est gnralement considre comme plus "compacte" car elle rassemble au mme endroit la dclaration du compteur de boucle (ici, indice_note), la vrication que ce compteur est dans lintervalle voulu et lincrmentation de la valeur de ce compteur. lintrieur de la boucle, le compteur se comporte comme une variable. Toutefois, ne modiez jamais la valeur du compteur lintrieur de la boucle ! Bien que cela soit techniquement possible, lexprience montre quune modication du compteur dune boucle for() lintrieur de la boucle (cest--dire dans les instructions excutes par la boucle) conduit gnralement des catastrophes, pouvant aller jusquau plantage du programme. Mais sans doute vous demandez-vous pourquoi le compteur de boucle va de 0 nb_notes-1, plutt que de 1 nb_notes. La raison est simple : en Python comme en C++, le premier lment dun tableau porte le numro 0 (zro). Plutt que de numro, on parle dindice, do le nom donn au compteur. Vous pouvez voir cet indice comme le nombre de cases quil faut sauter dans le tableau pour arriver llment : pour le premier lment, il faut sauter zro (aucune) case, pour le deuxime, il faut en sauter une, et ainsi de suite.

1. En ralit, ces deux formes ne sont pas exactement quivalentes et interchangeables. En Python, range() cre en fait un tableau temporaire qui sera parcouru, ce qui narrive pas avec la boucle while(). En C++, le compteur de boucle nexiste qu lintrieur de la boucle for(), alors quil existe aprs la boucle while(). Toutefois ces subtilits ne devraient pas vous gner avant davoir davantage dexprience.

Chapitre 6

Des variables composes

65

Pour obtenir llment du tableau situ un indice donn, il suft dinscrire le nom de la variable contenant le tableau, suivi de lindice donn entre crochets. Dans lexemple, le premier lment du tableau notes est donc obtenu par notes[0], le deuxime par notes[1], et ainsi de suite. Dans la boucle for() prcdente, on donne le compteur de boucle plutt quune valeur xe : mesure que le compteur va prendre les valeurs 0, 1, 2 On va ainsi parcourir le tableau en faisant rfrence chacun de ses lments, lun aprs lautre. Essayez maintenant dcrire le code qui va calculer la moyenne des notes. Il suft pour cela de dclarer une variable destine recevoir la somme des notes, puis de la diviser par le nombre de notes. Voici une solution possible :
Python
somme = 0.0 for indice_note in range(nb_notes): somme += notes[indice_note] moyenne = somme / nb_notes print "Moyenne =", moyenne

C++
double somme = 0.0; for(int indice_note = 0; indice_note < nb_notes; ++indice_note) { somme += notes[indice_note]; } double moyenne = somme / nb_notes; std::cout << "Moyenne = " << moyenne << std::endl;

Il est ici particulirement important de bien initialiser la variable somme 0.0, an que le calcul soit juste. Sil est obligatoire en Python de donner une valeur initiale une variable au moment de sa dclaration, cela ne lest pas en C++, mais cest plus que vivement recommand.

6.1.2. Si la taille est inconnue


Plus souvent que vous ne le souhaiteriez, vous ne connatrez pas lavance le nombre dlments stocker dans un tableau. Par ailleurs, du point de vue de la convivialit dun programme, on considre quil est prfrable de dcharger lutilisateur du soin de compter le nombre de valeurs. Aprs tout, compter est encore ce que les ordinateurs font le mieux, or les ordinateurs sont l pour nous simplier la tche. Mais alors, comment faire avec nos tableaux ? La solution consiste dclarer un tableau vide, ne contenant aucun lment, puis lui en ajouter au fur et mesure que les lments nous parviennent. Pour notre calcul de moyenne, cela signie que lon ne peut plus utiliser une boucle borne pour demander

66

Initiation la programmation avec Python et C++

les notes : nous devons revenir une boucle while classique. Voici comment procder :
Python
notes = [] print "Note ", len(notes)+1, "?", une_note = float(raw_input()) while ( une_note >= 0.0 ): notes.append(une_note) print "Note ", len(notes)+1, "?", une_note = float(raw_input()) nb_notes = len(notes) print nb_notes, "notes."

C++
std::vector<double> notes; double une_note = 0.0; std::cout << "Note " << notes.size()+1 << "? "; std::cin >> une_note; while ( une_note >= 0.0 ) { notes.push_back(une_note); std::cout << "Note " << notes.size()+1 << "? "; std::cin >> une_note; } int nb_notes = notes.size(); std::cout << nb_notes << " notes.\n";

La premire ligne de chacun des programmes dclare une variable notes de type tableau, ce tableau tant vide, cest--dire quil ne contient aucun lment. Il va sans dire que si vous tentez de manipuler, ne serait-ce que consulter, une valeur dans lun de ces tableaux, votre programme sarrtera brutalement en vous couvrant dinjures. Dune manire gnrale, si vous tentez daccder un lment dun tableau en donnant un indice suprieur ou gal au nombre dlments dans le tableau, alors vous allez au-devant de graves ennuis : dans le meilleur des cas, un plantage du programme, dans le pire, une corruption de donnes. la suite de cette dclaration, on amorce une boucle sur le mme modle que celle que nous avions utilise dans le petit jeu du chapitre prcdent. Comme il faut bien un moyen quelconque dinterrompre la boucle, on dcide arbitrairement quon cesse de demander des valeurs ds quune note infrieure 0 est donne ou, plus prcisment, on continue tant que la note donne est suprieure ou gale 0. Si une note valide a t donne, on lajoute au tableau :
Python
notes.append(une_note)

C++
notes.push_back(une_note);

Chapitre 6

Des variables composes

67

Cet ajout est effectu au moyen dune opration disponible pour les types tableau. Nous avons dj rencontr la notion dopration, la section 4.4.2 qui concernait le dcoupage dune chane de caractres en Python. Ici, lopration est append() pour Python, push_back() pour C++. Leffet est le mme dans les deux langages : le tableau auquel lopration est applique est agrandi dune case, dans laquelle la valeur passe en paramtre (une_note dans lexemple) est stocke. Les mots anglais append et push_back sont ici synonymes et signient "ajouter la n". La dernire valeur ajoute est donc le dernier lment du tableau. Lorsque la boucle sarrte, on peut obtenir le nombre dlments effectivement contenu dans le tableau, au moyen de la fonction len() en Python et de lopration size() en C++ :
Python
nb_notes = len(notes)

C++
int nb_notes = notes.size();

Ds lors, la suite du programme est inchange, il est nouveau possible dutiliser une boucle borne pour les traitements suivants.

6.2. Plusieurs types en un : structures


Revenons notre enseignant. Calculer une moyenne, cest bien, mais il est encore plus intressant de savoir quels sont les lves au-dessus ou au-dessous de cette moyenne. Essayez dcrire la boucle qui permet, par exemple, dobtenir les notes infrieures la moyenne. Elle pourrait ressembler ceci :
Python
for indice_note in range(nb_notes): if ( notes[indice_note] < moyenne ): print indice_note,

C++
for(int indice_note = 0; indice_note < nb_notes; ++indice_note) { if ( notes[indice_note] < moyenne ) { std::cout << indice_note << " "; } }

68

Initiation la programmation avec Python et C++

Cela fonctionne, mais il y a un inconvnient : lutilisateur obtient une suite dindices, lui ensuite de faire correspondre ces indices avec les noms des lves. Tche laborieuse, ingrate et source derreurs. Nous devons faire mieux. Il faudrait pouvoir associer un nom une note. Cest--dire mmoriser non seulement les notes mais galement les noms des lves les ayant obtenues. Comme si notre tableau ne contenait pas seulement des valeurs numriques mais aussi des chanes de caractres. Une telle association est possible par lutilisation de structures, aussi appeles parfois enregistrements. Une structure est un nouveau type, cr de toutes pices par le programmeur, qui rassemble en une seule entit des informations de diffrents types. Ces informations, appeles les membres de la structure, se comportent un peu comme des "sous-variables". Crons donc une structure nomme Eleve dans le programme de moyenne, modi ainsi :
Python
1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. # -*- coding: utf8 -*class Eleve: pass eleves = [] un_eleve = Eleve() print "Nom", len(eleves)+1, "?", un_eleve.nom = raw_input(); while ( len(un_eleve.nom) > 0 ): print "Note", len(eleves)+1, "?", un_eleve.note = \ float(raw_input()) eleves.append(un_eleve) un_eleve = Eleve() print "Nom", len(notes)+1, "?", un_eleve.nom = raw_input(); nb_eleves = len(eleves)

C++
1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. #include <iostream> #include <vector> #include <string> struct Eleve { std::string nom; double note; }; int main(int argc, char* argv[]) { std::vector<Eleve> eleves; Eleve un_eleve; std::cout << "Nom " << eleves.size()+1 << "? "; std::getline(std::cin, un_eleve.nom); while ( un_eleve.nom.size() > 0 ) {

Chapitre 6

Des variables composes

69

Python

C++
20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. std::cout << "Note " << eleves.size()+1 << "? "; std::cin >> un_eleve.note; eleves.push_back(un_eleve); std::cout << "Nom " << eleves.size()+1 << "? "; std::cin.ignore(); std::getline(std::cin, un_eleve.nom); } int nb_eleves = eleves.size();

Du ct Python, la dclaration dune structure est introduite par le mot cl class. Du ct C++, on utilise le mot cl struct, suivi de la liste des membres de la structure entre accolades, comme sil sagissait de variables. Noubliez pas le point-virgule la n de cette liste ! Remarquez que pour Python, les membres de la structure ne sont pas donns sa dclaration, mais "ajouts" ds quon leur affecte une valeur, comme pour les variables classiques. Rptons-le, la dclaration dune structure quivaut crer un nouveau type. Il est donc possible de crer des variables de ce type, comme ici un_eleve, et mme de crer des tableaux de ce type, comme ici le tableau eleves. En Python, la dclaration dune variable dun type structur se fait en utilisant le nom du type suivi de parenthses, comme sil sagissait dune fonction (ligne 6). Lorsque vous avez une variable dun tel type, vous pouvez accder aux membres en utilisant la notation pointe : le nom de la variable, suivi dun point, suivi du nom du membre. Par exemple, un_eleve.nom permet daccder au membre nom (en loccurrence, une chane de caractres) contenu dans la variable de type Eleve nomme un_eleve. Par convention, on parle plutt dinstance que de variable dans le cas des types structurs. Ainsi, on dit que un_eleve est une instance de la structure Eleve, ou plus simplement que un_eleve est une instance dEleve. De mme, un_eleve.note dsigne le membre note de linstance un_eleve dEleve.

70

Initiation la programmation avec Python et C++

Naturellement, si vous crez deux instances dun mme type de structure, vous obtenez deux groupes de membres parfaitement distincts :
Python
un_eleve = Eleve() autre_eleve = Eleve() un_eleve.nom = "Yves" autre_eleve.nom = "Lubin"

C++
Eleve un_eleve; Eleve autre_eleve; un_eleve.nom = "Yves"; autre_eleve.nom = "Lubin";

lissue de ces instructions, on peut reprsenter la situation en mmoire comme la Figure 6.3.
Figure 6.3 Deux instances pour deux lves.
un_lve nom
Y v e s

autre_lve nom
L u b i n

note ??

note ??

Les instances (les variables) un_eleve et autre_eleve nont aucun lien entre elles, si ce nest quelles partagent le mme type, savoir Eleve. Mais peut-tre vous demandez-vous quelle est la signication des points dinterrogation dans le cadre qui reprsente le membre note ? Cela dpend en fait du langage considr.

En Python, ce membre nexiste pas encore, car aucune valeur ne lui a t affecte. En C++, comme pour toute variable, sa valeur nest pas connue tant quaucune ne lui a t affecte. On ne sait donc pas ce que contient ce membre.

Ces deux points dinterrogation peuvent tre gnants dans un programme : la suite de la cration dune instance d Eleve, quel que soit le langage, nous sommes en prsence dune forme dincertitude. Or, les programmeurs dtestent lincertitude : elle est le chemin le plus sr vers un programme dfectueux. Heureusement, une solution existe.

Chapitre 6

Des variables composes

71

6.2.1. Initialiser le contenu


Lobjectif est ici de savoir exactement ce que contient une instance dune structure, ds sa cration, sans doute possible. Il nous faut pour cela initialiser les membres de la structure au moment mme de sa cration. Voici comme procder :
Python
class Eleve: def __init__(self): self.nom = " self.note = 0.0

C++
struct Eleve { Eleve() { this->nom = "; this->note = 0.0; } std::string nom; double note; };

Remarquez lindentation : les trois lignes qui suivent la premire "appartiennent" la structure Eleve, elles en font partie.

Il suft dajouter la structure ce quon appelle un constructeur, cest--dire une fonction spciale charge de construire toute instance de cette structure.
Python C++

Cette fonction spciale porte un nom spcial qui est __init__(). Elle doit prendre (au moins) un paramtre spcial, portant le nom spcial self. Comme vous le constatez, tout cela est trs spcialis. Le paramtre self reprsente linstance elle-mme, cest--dire celle en cours de cration. self.nom reprsente donc le membre nom de linstance et celui daucune autre. Ce paramtre self est lquivalent de la variable implicite this du C++.

Cette fonction spciale doit porter le mme nom que la structure, Eleve() dans notre exemple. Au sein de cette fonction, il existe implicitement une variable nomme this qui reprsente linstance elle-mme, cest-dire celle en cours de cration. Cette variable implicite est un peu particulire : pour accder aux membres de la structure, il faut utiliser la che -> plutt que le point. Ainsi this->nom reprsente le membre nom de linstance et celui daucune autre. Cette variable implicite this est lquivalent du paramtre self en Python.

72

Initiation la programmation avec Python et C++

partir de maintenant, ds que vous crez une instance dEleve, les instructions qui ont t places dans le constructeur seront excutes. De cette faon, il ny a plus aucune incertitude quant au contenu de linstance nouvellement cre. Par exemple :
Python
un_eleve = Eleve() print un_eleve.note

C++
Eleve un_eleve; std::cout << un_eleve.note << std::endl;

Sans constructeur, cet extrait provoquerait un plantage du programme, car alors le membre note nexisterait pas encore dans linstance un_eleve.

Sans constructeur, cet extrait afcherait probablement une valeur parfaitement fantaisiste, car alors le membre note naurait pas t initialis.

Ces deux extraits afchent simplement 0.0, qui est la note attribue par dfaut un lve. Cest une trs mauvaise note, mais au moins elle est parfaitement connue et dtermine.

6.3. Variables composes et paramtres


Pour terminer ce long chapitre consacr aux variables composes, revenons un instant au problme du passage de paramtres une fonction. Plus prcismen t, au mcanisme par lequel une fonction reoit un paramtre. Le but tant ici de crer une fonction pour calculer la moyenne des notes obtenues par les lves, laquelle moyenne dpend naturellement de la liste des notes, donc du tableau dlves que nous avons cr. Autrement dit, nous allons devoir passer ce tableau en paramtre notre fonction. Toutefois, on ne transmet pas un objet compos une fonction comme on transmet une simple valeur numrique. Il convient ici de sinterroger sur le "chemin" suivi par un paramtre.

Chapitre 6

Des variables composes

73

6.3.1. Paramtre par valeur


Essayez les programmes suivants :
Python
# -*- coding: utf8 -*def Fonction(entier): entier = 2 print "entier =", entier un_entier = 1 Fonction(un_entier) print "un_entier =", un_entier

C++
#include <iostream> void Fonction(int entier) { entier = 2; std::cout << "entier = " << entier << std::endl; } int main(int argc, char* argv[]) { int un_entier = 1; Fonction(un_entier); std::cout << "un_entier = " << un_entier << std::endl; return 0; }

Les deux programmes afcheront :


entier = 2 un_entier = 1

Regardez la fonction dnie dans ces programmes. Elle modie le paramtre quelle reoit en y plaant une valeur, puis afche la valeur contenue dans le paramtre. Comme on pouvait sy attendre, la valeur afche est bien celle qui a t stocke la ligne prcdente. Par contre, aprs lappel de cette fonction, la variable qui lui a t passe en paramtre na pas t modie, comme le prouve lafchage de sa valeur. On parle dans ce cas dun passage de paramtre par valeur : le paramtre entier que reoit la fonction est comme une "vraie" nouvelle variable, qui contient une copie de la valeur contenue dans la variable un_entier passe la fonction. Le temps de lexcution de la fonction, on a effectivement deux variables, la variable "temporaire" (le paramtre) tant initialise avec une copie du contenu de la variable "permanente". Celle-ci nest en aucun cas modie par la fonction, quoi que celle-ci applique comme opration sur son paramtre.

74

Initiation la programmation avec Python et C++

6.3.2. Paramtre par rfrence


Mais voyons maintenant ce qui se passe dans le cas dune structure ne contenant, par exemple, quun seul membre de type entier :
Python
# -*- coding: utf8 -*def Fonction(structure): structure.membre = 2 print "structure.membre =", \ structure.membre class Une_Structure: def __init__(self): self.membre = 1 une_instance = Une_Structure() print "une_instance.membre =", \ une_instance.membre Fonction(une_instance) print "une_instance.membre =", \ une_instance.membre

C++
#include <iostream> struct Une_Structure { int membre; Une_Structure() { this->membre = 1; } }; void Fonction(Une_Structure structure) { structure.membre = 2; std::cout << "structure.membre = " << structure.membre << std::endl; } int main(int argc, char* argv[]) { Une_Structure une_instance; std::cout << "une_instance.membre = " << une_instance.membre << std::endl; Fonction(une_instance); std::cout << "une_instance.membre = " << une_instance.membre << std::endl; return 0; }

Afchage :
une_instance.membre = 1 structure.membre = 2 une_instance.membre = 2

Afchage :
une_instance.membre = 1 structure.membre = 2 une_instance.membre = 1

Cette fois, les deux programmes se comportent diffremment. Si le programme C++ donne un rsultat analogue au programme prcdent, il en va tout autrement pour le

Chapitre 6

Des variables composes

75

programme Python : il semble que la fonction ait effectivement modi la valeur du membre membre de linstance une_instance de la structure Une_Structure. Autrement dit, dans cette situation, en Python la fonction modie linstance qui lui est donne en paramtre. On parle alors de passage de paramtre par rfrence : la fonction ne reoit plus une copie de la variable, mais en fait un nouveau point daccs celle-ci. Lorsque vous passez une variable compose (tableau ou structure) une fonction en Python, vous ne transmettez quun nouveau point daccs cette variable, dont le contenu peut tre modi par la fonction. En C++ au contraire, vous pouvez constater que lon reste dans le cas dun passage par valeur : la fonction reoit bel et bien une copie de linstance, toute action de la fonction sur son paramtre ne se retrouve pas dans la variable passe en paramtre. Les deux langages se comportent trs diffremment dans ce genre de situations. Il nest pas possible de reproduire en Python (du moins de manire simple) le comportement du C++ concernant le passage en paramtre de variables composes. Par contre, il est possible en C++ de prciser quon souhaite un passage de paramtre par rfrence. Il suft pour cela de modier lgrement la dclaration de la fonction comme suit :
void Fonction(Une_Structure & structure) { /* des instructions */ }

Si vous ajoutez le symbole perluette (ou "et commercial") la suite du type du paramtre, alors le mcanisme du passage par rfrence sera utilis. Essayez de modier ainsi le programme C++ prcdent : il se comportera alors comme le programme Python.

6.3.3. Mode de passage des paramtres


Ds que vous voulez passer des variables composes en paramtres des fonctions, il est vivement recommand dutiliser le passage par rfrence. Cest automatique en Python, par contre vous devez le prciser explicitement en C++ au moyen de lperluette. Ce mcanisme vite la duplication des donnes en mmoire, qui survient lors du passage par valeur. Cette duplication nest pas gnante sil sagit dun simple entier ou dun nombre en virgule ottante. Elle peut devenir assez coteuse, en temps dexcution ainsi quen encombrement de la mmoire, dans le cas de variables composes. Dupliquer un tableau contenant un million de valeurs nest pas gratuit !

76

Initiation la programmation avec Python et C++

Incidemment, nous avons vu que les chanes de caractres ressemblaient fortement des tableaux de caractres. La remarque prcdente sapplique galement pour ce type de donnes : si vous devez passer une chane de caractres une fonction, utilisez un passage par rfrence. Les Figures 6.4 et 6.5 schmatisent la diffrence de comportement entre les deux modes de passage.
Figure 6.4 Passage de paramtre par valeur.
valeur valeur

une_variable Une_Fonction (parametre) { parametre }

Figure 6.5 Passage de paramtre par rfrence.

valeur

une_variable Une_Fonction (parametre) { parametre }

Dernier petit rafnement, que vous verrez frquemment utilis dans du code C++ crit par des programmeurs plus expriments. Pour diverses raisons, il peut tre souhaitable de sassurer quune fonction ne modie pas le contenu du paramtre qui lui est pass. Cest une faon efcace de contrler les modications que subit une variable (dun type compos) et ainsi de rduire la complexit dun programme. Dun point de vue strictement thorique, on peut mme dire quune fonction qui retourne une valeur ne devrait jamais modier aucun de ses paramtres, bien quen pratique une telle rigueur soit rarement applique. Si vous crez une fonction qui ne devrait jamais modier ses paramtres, il suft dajouter le mot cl const devant le nom du type du paramtre. Nous pouvons alors enn construire une fonction calculant une moyenne partir des notes des lves :
double Moyenne( const std::vector<Eleve> & eleves) { double somme = 0.0; for(int indice_note = 0; indice_note < eleves.size(); ++indice_note)

Chapitre 6

Des variables composes

77

{ somme += eleves[indice_note].note; } return somme / eleves.size(); }

La fonction Moyenne() va recevoir un paramtre "doublement structur", puisque nous lui passerons un tableau de structures Eleve. On utilise donc le passage par rfrence avec &. De plus, il est logique de considrer quune fonction calculant une moyenne ne doit en aucun cas modier les valeurs qui lui sont donnes : on ajoute donc const devant le type du paramtre, qui est galement le type du tableau contenant les lves. Si vous tentez de modier lune des valeurs contenues dans le tableau, le compilateur vous avertira dune erreur. Vous pouvez vrier cela sur le petit programme de la section prcdente qui montrait le passage par rfrence : ajoutez const dans la dclaration de la fonction, le compilateur refusera le programme avec un message dans le style de celui-ci :
$ g++ test_passage_reference.cpp test_passage_reference.cpp: In function void Fonction(const Une_Structure&): test_passage_reference.cpp:12: error: assignment of data-member Une_Structure::membre in read-only structure

Ce qui signie, en gros, "tentative daffectation dun membre dans une structure en lecture seule". Malgr tous ces rafnements, la fonction Moyenne() sutilise tout naturellement dans le programme principal :
double moyenne = Moyenne(eleves);

Pour vous exercer, modiez le programme en Python de faon calculer la moyenne par une fonction. En sachant que le passage par rfrence sera automatique et quil nexiste pas dquivalent au mot cl const en Python Dune manire gnrale, en C++, nhsitez pas utiliser le mot cl const. Il donne des informations prcieuses au compilateur concernant vos intentions, ainsi il est mieux mme de vrier que votre code est correct. Cela peut galement permettre certaines optimisations aboutissant une excution plus rapide.

7
Les Tours de Hano
Au sommaire de ce chapitre :

Modlisation du problme Conception du jeu Implmentation Rsum de la dmarche

80

Initiation la programmation avec Python et C++

Le moment est venu de rassembler lensemble de ce que nous avons vu jusquici pour construire un programme plus important. Lexemple choisi est celui du jeu des Tours de Hano, ou Tours de Brahma, jeu imagin par le mathmaticien franais douard Lucas et inspir dune trs ancienne lgende.
Dans le grand et fameux temple de Bnars, sous le dme marquant le centre du monde, trne un socle de bronze sur lequel sont xes trois aiguilles de diamant, chacune delles haute dune coude et ne comme la taille dune gupe. Sur une de ces aiguilles, la Cration, Dieu empila soixante-quatre disques dor pur, du plus grand au plus petit, le plus large reposant sur le socle de bronze. Il sagit de la tour de Brahma. Jour et nuit, inlassablement, les moines dplacent les disques dune aiguille vers une autre tout en respectant les immuables lois de Brahma qui les obligent ne dplacer quun disque la fois et ne jamais le poser sur un disque plus petit. Quand les soixante-quatre disques auront t dplacs de laiguille sur laquelle Dieu les dposa la Cration vers une des autres aiguilles, la tour, le temple et les brahmanes seront rduits en poussire, et le monde disparatra dans un grondement de tonnerre.

Figure 7.1 Modle en bois du jeu des Tours de Hano.

Chapitre 7

Les Tours de Hano

81

Lintrt de ce jeu, trs couramment utilis en programmation, est quil est sufsamment simple pour tre compris sans difcult, tout en tant sufsamment riche pour mettre contribution toutes les techniques que nous avons rencontres et dautres encore, comme nous le verrons dans la suite de cet ouvrage. Premire tape : comprendre peu prs de quoi il retourne. La lgende parle de soixante-quatre disques, ce qui est assez considrable (nous verrons dans un instant pourquoi). La puissance des matriels disponibles ne cessant de crotre, il est frquent denvisager des programmes destins manipuler de grandes quantits de donnes ou, du moins, ncessitant des quantits astronomiques de calculs. Dans ces situations, on commence par tudier le problme sur un ensemble plus petit. Pour notre exemple, prenons simplement trois disques. Saurez-vous trouver le bon enchanement de mouvements ? La Figure 7.2 donne la solution.
Figure 7.2 Solution au problme avec trois disques.
T1 T2 T3

Nous avons d effectuer sept mouvements. Et cest la meilleure solution : il est impossible de rsoudre le problme avec moins de mouvements. En fait, on peut dmontrer (ce que nous ne ferons pas ici) que pour n disques, il faut effectuer au minimum 2n 1 mouvements. Question : combien les moines de la lgende devront-ils effectuer de mouvements ? Rponse : 264 1 = 18 446 744 073 709 551 615, soit environ dix-huit milliards de milliards, en supposant quil ny ait aucune erreur. Si les moines parviennent dplacer un disque par seconde, le temps ncessaire au dplacement

82

Initiation la programmation avec Python et C++

de toute la tour sera dun peu moins de 585 milliards dannes Ce qui est rassurant, la n du monde nest pas pour demain ! Mme simuls sur un ordinateur actuel, capable deffectuer des dizaines de millions de mouvements par seconde, tous ces dplacements ncessiteraient tout de mme des milliers dannes pour tre numrs. Ce petit exemple montre que parfois, un problme apparemment simple demeure hors de porte de nos machines, pourtant si puissantes.

7.1. Modlisation du problme


Voyons maintenant comment nous allons reprsenter les diffrents lments. A priori, le jeu compte au moins deux types dobjets : le disque et la tour. Pour simplier un peu les choses, nous pouvons dcider quun disque est simplement reprsent par un entier, dont la valeur est sa taille. Dans lexemple de trois disques prcdent, au tout dbut du jeu, le petit disque au sommet sera donc reprsent par lentier 1, et le grand tout en bas par lentier 3. Une tour est un empilement vertical de disques. Si on la fait tomber, on a un alignement horizontal de disques, cest--dire, en fait, dentiers. Une tour sera donc constitue dun tableau dentiers, le premier lment dans le tableau contenant la taille du disque le plus bas. Par convention, on peut dcider quune valeur de 0 indique quil ny a pas de disque. Ce tableau devra pouvoir contenir autant dentiers quil y a de disques dplacer. Toutefois, un instant donn, une tour ne porte que quelques disques, voire aucun : il est donc galement ncessaire de connatre le nombre de disques actuellement empils sur une tour. On doit associer un tableau dentiers et un compteur : nous sommes en prsence dune structure, qui pourrait tre nomme Tour et avoir lallure suivante :
Python
class Tour: def __init__(self, taille_max): self.nb_disques = 0 self.disques = \ [0 for e in range(taille_max)]

C++
struct Tour { int nb_disques; std::vector<int> disques; Tour(int taille_max): nb_disques(0), disques(taille_max, 0) { } };

Chapitre 7

Les Tours de Hano

83

Nous retrouvons ici les tableaux et les structures, dment initialiss, que nous avons vus au chapitre prcdent. Remarquez toutefois la prsence dun paramtre supplmentaire dans chacun des constructeurs (en Python et en C++) : il est possible de donner des informations la structure au moment o on en cre une instance. Ainsi, lexemple suivant cre une instance de la structure Tour dimensionne pour recevoir cinq disques :
Python
une_tour = Tour(5)

C++
Tour une_tour(5);

En Python, linformation (le paramtre) est passe entre parenthses aprs le nom de la structure, tandis quelle est passe aprs le nom de linstance en C++. Finalement, le constructeur se comporte un peu comme une fonction qui serait intimement attache la structure. De plus, nous avons utilis une notation particulire en C++ pour initialiser les membres de la structure : ils sont donns les uns aprs les autres, spars par des virgules, accompagns dune paire de parenthses contenant les informations ncessaires leur initialisation. Cette liste est introduite par le caractre deux-points ( :). Dans ce contexte, lcriture nb_disques(0) signie exactement "initialiser le membre nb_disques la valeur 0". Le membre disques, qui est un tableau, est initialis partir de deux informations : la taille du tableau et la valeur placer dans chaque case. Remarquez que cette criture ressemble beaucoup celle utilise lors de la cration dune instance dune structure, lorsque le constructeur de cette structure demande un paramtre. Cette similitude nest pas fortuite. Mais nous reviendrons ldessus. Il existe un troisime type dobjet : le jeu lui-mme. Celui-ci est caractris par trois tours, ainsi que par une hauteur maximale. En effet, il est souhaitable que lutilisateur puisse choisir le nombre maximal de disques avec lesquels il veut jouer. Inutile de lui imposer un nombre trop lev, ce qui le dcouragerait, ou trop faible, ce qui lui paratrait trivial.

84

Initiation la programmation avec Python et C++

Nous avons donc une autre structure, charge de contenir ltat du jeu tout instant :
Python
1. class Hanoi: 2. def __init__(self, hauteur_max): 3. self.hauteur = hauteur_max 4. self.tours = \ 5. [Tour(hauteur_max) \ 6. for t in range(3)] 7. for etage in range(hauteur_max): 8. self.tours[0].disques[etage]= \ 9. hauteur_max - etage 10. self.tours[0].nb_disques = \ 11. hauteur_max

C++
1. struct Hanoi 2. { 3. int hauteur; 4. std::vector<Tour> tours; 5. Hanoi(int hauteur_max): 6. hauteur(hauteur_max), 7. tours(3, Tour(hauteur_max)) 8. { 9. for(int etage = 0; 10. etage < hauteur_max; 11. ++etage) 12. { 13. this->tours[0].disques[etage] = 14. hauteur_max - etage; 15. } 16. this->tours[0].nb_disques = 17. hauteur_max; 18. } 19. };

Cette structure Hanoi contient donc, en plus dun entier, un tableau de structures contenant elles-mmes un tableau. Voil qui commence devenir une imbrication complexe. Cette complexit transparat dans linitialisation de la premire tour, qui est simplement "remplie" avec les disques demands an que le jeu soit prt dmarrer ds sa cration (lignes 7 11 en Python, lignes 9 17 en C++). Dans nos structures, tours est un tableau de structures Tour. Notez quil est ncessaire de passer un paramtre au constructeur de Hanoi pour pouvoir construire les instances de Tour quelle contient, le constructeur de Tour attendant lui-mme un paramtre. Dans le cas de linitialisation du tableau en C++, ce paramtre est donn la suite du type contenu dans le tableau : le membre tours sera donc un tableau de trois instances de la structure Tour, le constructeur de chacune de ces instances recevant le paramtre hauteur_max. Ainsi, dans le cadre dune instance de cette structure Hanoi :

tours[0] est le premier lment de ce tableau, donc dans notre cas une variable (structure) de type Tour.

Chapitre 7

Les Tours de Hano

85

tours[0].nb_disques dsigne le membre nomm nb_disques dans la structure de type Tour se trouvant en premire position dans le tableau tours. tours[0].disques dsigne le membre nomm disques dans la structure de type Tour se trouvant en premire position dans le tableau tours, ce membre tant luimme un tableau. tours[0].disques[etage] dsigne, enn, llment la position etage dans le tableau disques contenu dans la structure de type Tour se trouvant en premire position dans le tableau tours.

Prenez le temps de lire et de comprendre chacune de ces dsignations. Nous avons des objets qui englobent dautres objets. Dans une dsignation, lobjet "le plus englobant" se trouve gauche. mesure quon parcourt la dsignation vers la droite, on traverse les niveaux dimbrication des objets, un peu comme on traverserait des strates gologiques ou les couches dun oignon. On passe dune couche lautre en utilisant soit les crochets (lorsque la couche do on vient est un tableau), soit le point suivi dun nom de membre (lorsque la couche do on vient est une structure). Imaginons que nous dclarions un jeu pour trois disques :
Python
le_jeu = Hanoi(3)

C++
Hanoi le_jeu(3);

On peut alors reprsenter, la Figure 7.3, le contenu de la variable le_jeu que nous venons de dclarer, dans les deux langages.
Figure 7.3 Instance du jeu pour trois disques.
le_ jeu tours disques
3 2 1

disques
q q q

disques
q q q

nb_disques 3 hauteur 3

nb_disques 0

nb_disques 0

Si nous voulons, par exemple, la taille du disque situ au troisime tage de la deuxime tour, nous devrons crire le_jeu.tours[1].disques[2]. Suivez le cheminement pour arriver cette valeur.

86

Initiation la programmation avec Python et C++

7.2. Conception du jeu


Nous disposons maintenant de tout ce qui est ncessaire lafchage du jeu. Il est temps de nous pencher sur sa logique interne, cest--dire sur la mise en uvre des rgles du jeu telles qunonces dans la lgende, la plus importante tant quil est interdit de poser un disque sur un autre plus petit. Dans ce genre de situation, une mthode qui fonctionne assez bien (pour des programmes de taille modeste) consiste exprimer littralement une tape du jeu ou de laction que doit effectuer le programme. Cette expression doit tre aussi gnrale que possible, exempte de dtails, loigne de toute ide de programmation. Dans notre cas, cela donnerait quelque chose comme ceci : 1. Afcher ltat du jeu. 2. Dterminer le piquet de dpart. 3. Dterminer le piquet darrive. 4. Dplacer le disque du piquet de dpart vers celui darrive. 5. Recommencer tant que le jeu nest pas termin. Remarquez quon utilise principalement des verbes linnitif. Ces verbes vont se traduire en presque autant de fonctions, ce qui donne une premire bauche du programme, comme vu de trs loin. Notez que la plupart de ces fonctions nexistent pas encore, elles se dgagent naturellement de la liste prcdente. On peut ds maintenant crire cette bauche, en supposant disposer de toutes les variables ncessaires (dans notre exemple, une instance de la structure Hanoi) :
Python
while ( not Est_Fini(le_jeu) ): Afficher_Jeu(le_jeu) tour_depart = \ Demander_Tour_Depart(le_jeu) tour_arrivee = \ Demander_Tour_Arrivee(le_jeu) Deplacer_Disque(le_jeu, \ tour_depart, \ tour_arrivee)

C++
while (! Est_Fini(le_jeu) ) { Afficher_Jeu(le_jeu); int tour_depart = Demander_Tour_Depart(le_jeu); int tour_arrivee = Demander_Tour_Arrivee(le_jeu); Deplacer_Disque(le_jeu, tour_depart, tour_arrivee); }

Chapitre 7

Les Tours de Hano

87

On passe systmatiquement linstance de Hanoi, qui contient ltat du jeu, aux fonctions qui ont t envisages : cela semble simplement logique que ces fonctions aient "besoin" de connatre ltat du jeu pour oprer. Pour la mme raison, la fonction Deplacer_Disque() doit logiquement "savoir" de quel piquet quel piquet elle doit effectuer le dplacement dun disque. Il est maintenant temps dintroduire une considration extrmement importante en programmation, surtout dans un programme qui doit interagir avec un utilisateur (ce qui est en fait le cas de la plupart). Elle sexprime sous la forme dune rgle simple : Ne jamais faire conance aux informations reues de lextrieur ! Autrement dit, toujours vrier que ce que donne lutilisateur est cohrent. Dans notre cas, cela signie quil est ncessaire de vrier :

Que le piquet de dpart demand est correct, cest--dire quil ne sagit pas dun piquet vide ou dun numro de piquet aberrant (plus petit que 1 ou plus grand que 3). Que le piquet darrive est correct, cest--dire que le mouvement est autoris selon les rgles du jeu et quil sagit dun numro valide (compris entre 1 et 3).

Il va donc falloir deux fonctions supplmentaires charges deffectuer ces vrications. Elles retourneront une valeur boolenne (vrai ou faux) selon le cas. Le droulement dune tape du jeu devient alors : 1. Afcher ltat du jeu. 2. Demander le piquet de dpart. 3. Si ce piquet est valide, alors : demander le piquet darrive ; si ce piquet est valide, alors : dplacer le disque ; sinon, afcher un message derreur. 4. Sinon, afcher un message derreur. 5. Recommencer tant que le jeu nest pas termin. Comme vous pouvez le constater, cette expression littrale laisse entrevoir la structure gnrale du programme. Une faon assez commune de reprsenter graphiquement un

88

Initiation la programmation avec Python et C++

tel droulement est dutiliser un ordinogramme (ou algorigramme, ou organigramme de programmation), qui se prsente comme la Figure 7.4.
Figure 7.4 Ordinogramme du jeu.

Afficher ltat du jeu

Demander le piquet de dpart

Piquet de dpart valide ? Oui

Non

Afficher un message derreur

Demander le piquet darrive

Piquet darrive valide ? Oui Dplacer le disque

Non

Afficher un message derreur

Non

Le jeu est fini ?

Oui

Chapitre 7

Les Tours de Hano

89

Les ches indiquent le droulement des oprations. Les cadres aux bords inclins sont utiliss pour reprsenter les interactions avec lutilisateur, tandis que les formes en losange reprsentent les alternatives. Enn, un cadre simple reprsente simplement des calculs "internes" au programme. Ce type de reprsentation a longtemps t trs en vogue et est toujours utilis pour reprsenter des algorithmes simples ou une vision trs gnrale dun logiciel. Son principal intrt est sa simplicit. Il prsente toutefois certaines limites ds quil sagit de traiter un problme complexe ou de grande taille. Aujourdhui, on utilise plutt le diagramme dtat, qui nest quune partie dune technique beaucoup plus vaste nomme UML (Unied Modeling Language, langage de modlisation uni), particulirement adapte la programmation oriente objet (que nous allons aborder trs prochainement). Nous ne dtaillerons pas ici ce procd sophistiqu, qui ncessiterait lui seul un ouvrage complet. partir de cette reprsentation, nous pouvons toffer notre bauche de programme :
Python
while ( not Est_Fini(le_jeu) ): Afficher_Jeu(le_jeu) tour_depart = Demander_Tour_ Depart(le_jeu) if ( Tour_Depart_Valide(le_jeu, tour_depart) ): tour_arrivee = Demander_Tour_Arrivee(le_jeu) if ( Tour_Arrivee_Valide(le_jeu, tour_depart, tour_arrivee) ): Deplacer_Disque(le_jeu, \ tour_depart, \ tour_arrivee) else: print "Arrive invalide!" else: print "Dpart invalide!"

90

Initiation la programmation avec Python et C++

C++
while (! Est_Fini(le_jeu) ) { Afficher_Jeu(le_jeu); int tour_depart = Demander_Tour_Depart(le_jeu); if ( Tour_Depart_Valide(le_jeu, tour_depart) ) { int tour_arrivee = Demander_Tour_Arrivee(le_jeu); if ( Tour_Arrivee_Valide(le_jeu, tour_depart, tour_arrivee) ) { Deplacer_Disque(le_jeu, tour_depart, tour_arrivee); } else { std::cout << "Arrive invalide!\n"; } } else { std::cout << "Dpart invalide!\n"; } }

Ds que vous vous attaquez un problme complexe, il est vivement recommand den exprimer littralement les tapes et ventuellement de construire un ou plusieurs ordinogrammes. Lide est de partir dune vision trs gnrale, de "haut niveau", puis de "descendre" progressivement dans les dtails de chacun des lments identis. On retrouve l une mise en pratique de la mthode cartsienne, ce que nous allons faire immdiatement.

7.3. Implmentation
Nous pouvons maintenant raliser limplmentation des diverses fonctions qui ont t prvues la section prcdente.

Chapitre 7

Les Tours de Hano

91

7.3.1. Afcher le jeu


Voyons comment nous allons coder la fonction dafchage Afficher_Jeu(). Tout naturellement, lutilisateur ne saurait se contenter dun simple afchage de valeurs numriques. On pourrait en effet trs simplement afcher ceci (ce qui est laiss en exercice au lecteur) :
Tour 0: 3 2 1 Tour 1: 0 0 0 Tour 2: 0 0 0

Cest un reet dle de la ralit, mais pas trs pratique utiliser et encore moins ludique. Il serait dj plus sympathique davoir quelque chose comme ceci :
<=!=> <==!==> <===!===> ! ! ! ! ! !

Les colonnes de points dexclamation reprsentent chacune un piquet, les disques tant gurs par une suite de caractres = entre chevrons. Chaque disque, ou plutt chaque tage, devra ainsi tre reprsent individuellement en fonction du disque qui sy trouve (ou non) et de la largeur maximale possible. Cette reprsentation tant une chane de caractres, il semble logique denvisager la cration dune fonction Chaine_Etage() charge de "produire" la chane en question. Voici quelle pourrait tre cette fonction :
Python
def Chaine_Etage(taille, largeur): espace = *(largeur-taille) chaine = "" if ( taille > 0 ): disque = =*taille chaine = espace + "<" + \ disque + "!" + \ disque + ">" + espace else: chaine = espace + "! " + espace return chaine

C++
std::string Chaine_Etage(int taille, int largeur) { std::string espace(largeur-taille, ); std::string chaine; if ( taille > 0 ) { std::string disque(taille, =); chaine = espace + "<" + disque + "!" + disque + ">" + espace; } else { chaine = espace + "! " + espace; } return chaine; }

92

Initiation la programmation avec Python et C++

Cette fonction prend deux paramtres, le premier est la taille du disque se trouvant ventuellement cet tage (ou 0, sil ny a pas de disque), le second la largeur totale de ltage, ce qui correspond en fait la plus grande taille possible de disque, soit le nombre de disques. partir de ces deux paramtres, on construit une chane reprsentant un tage, ventuellement avec un disque. Maintenant que nous savons reprsenter un tage, voyons comment reprsenter lensemble du jeu. Le mode texte pose une contrainte particulire : on ne peut afcher les caractres que ligne par ligne, la nouvelle ligne repoussant la prcdente vers le haut. Cela pose deux problmes :

Si nous parcourons une tour "normalement", du premier tage au dernier, elle arrivera lcran la tte en bas : le premier tage sera la premire ligne afche, qui sera repousse vers le haut, la ligne du dernier tage tant la dernire afche donc la plus basse lcran. Si nous afchons les tours lune aprs lautre, elles apparatront lune en dessous de lautre lcran, ce qui nest gure gracieux ; il nous faudrait trouver un moyen pour quelles apparaissent cte cte.

Vous le voyez, ce qui peut passer pour un problme simple afcher les disques de chacune des tours peut en ralit cacher des difcults inattendues. Tentons de les rsoudre. Le premier point est assez simple : plutt que de parcourir les tages du premier (en bas) au dernier (en haut), il suft de les parcourir du dernier au premier. Plusieurs solutions sont possibles pour cela, par exemple utiliser une boucle borne classique dans laquelle on dterminerait le numro correct de ltage, comme ceci :
Python
for etage in range(nb_etages): etage_correct = nb_etages - etage 1 # afficher ltage

C++
for(int etage = 0; etage < nb_etages; ++etage) { etage_correct = nb_etages etage 1; // afficher ltage }

La "correction" du numro dtage tient compte du fait que, si nb_etages reprsente le nombre dtages afcher, alors les tages sont effectivement numrots

Chapitre 7

Les Tours de Hano

93

de 0 nb_etages-1. Une autre solution consiste parcourir les numros en ordre inverse :
Python
for etage in range(nb_etages, 0, -1): # afficher ltage

C++
for(int etage = nb_etages-1; etage >= 0; --etage) { // afficher ltage }

Cette criture de range() utilisant trois paramtres permet de spcier la valeur de dpart, la valeur darrive (qui nest jamais atteinte) et le dcalage appliquer chaque tape pour passer dune valeur la suivante.

Le compteur est cette fois dcrment plutt quincrment. Le symbole >= signie "suprieur ou gale", qui est lexacte ngation de <.

La solution la seconde difcult est presque aussi simple : plutt que dafcher ltat de chaque tage de chaque tour, nous allons afcher ltat de chaque tour chaque tage. Autrement dit, plutt que dexaminer les tours lune aprs lautre et, pour chacune, dexaminer ses tages, nous allons examiner les tages et, pour chacun, examiner les tours cet tage. Cest--dire que nous allons afcher ltat du jeu tage par tage. Implicitement, nous avons deux boucles imbriques lune dans lautre :
Python
for etage in range(nb_etages, 0, -1): for tour in range(3): # afficher ltage de la tour

C++
for(int etage = nb_etages; etage >= 0; --etage) { for(int tour = 0; tour < 3; ++tour) { # afficher ltage de la tour } }

Il semble que nous ayons l le principe qui permettra la fonction Afficher_Jeu() de fonctionner. Pour pouvoir afcher ltat du jeu, celle-ci a naturellement besoin de connatre, justement, ltat du jeu, qui se trouve mmoris dans une structure de type Hanoi.

94

Initiation la programmation avec Python et C++

Elle devra donc recevoir un paramtre de ce type, en appliquant les principes que nous avons vus la n du chapitre prcdent. Voici enn cette fonction :
Python
def Afficher_Jeu(jeu): for etage in range(jeu.hauteur, 0, -1): for colonne in range(3): taille = jeu.tours[colonne].disques[etage-1] print " " + Chaine_Etage(taille, jeu.hauteur), print

C++
void Afficher_Jeu(const Hanoi& jeu) { for(int etage = jeu.hauteur-1; etage >= 0; --etage) { for(int colonne = 0; colonne < 3; ++colonne) { int taille = jeu.tours[colonne].disques[etage]; std::cout << " " << Chaine_Etage(taille, jeu.hauteur) << " "; } std::cout << std::endl; } }

Remarquez lutilisation dun passage par rfrence constante, avec lutilisation de const et de &.

Voyez comme la fonction Chaine_Etage() dnie prcdemment est mise contribution. Chaque chane est, de plus, encadre dun espace de chaque ct, an que la prsentation soit un peu are.

7.3.2. Fonctions utilitaires


On dsigne par ce terme des fonctions gnralement trs simples destines remplir des tches assez lmentaires mais qui peuvent ncessiter plusieurs lignes de code. Pour simples quelles soient, ces portions de code prsentes au milieu des instructions dun algorithme compliqu sont comme une nuisance : en quelque sorte, elles brouillent la lecture de lalgorithme. On parle alors de bruit.

Chapitre 7

Les Tours de Hano

95

Ici, les fonctions permettant de demander les piquets de dpart et darrive sont typiquement des fonctions utilitaires. Les instructions quelles contiennent pourraient parfaitement tre intgres directement dans lbauche que nous avons construite, mais lalgorithme reprsent par cette bauche serait alors moins ais suivre. Voici lune de ces deux fonctions :
Python
def Demander_Tour_Depart(jeu): print "Tour de dpart?", tour_depart = int(raw_input()) return tour_depart - 1

C++
int Demander_Tour_Depart(const Hanoi& jeu) { int tour_depart; std::cout << "Tour de dpart? "; std::cin >> tour_depart; return tour_depart - 1; }

Comme vous pouvez le constater, rien de bien terrible. La seule petite subtilit qui naura pas chapp au lecteur attentif est la valeur retourne : pourquoi soustraire 1 la valeur donne par lutilisateur ? Souvenez-vous que pour les langages Python et C++, le premier lment dun tableau porte le numro 0. Les tours sont donc numrotes de 0 2, les tages de 0 au nombre dtages diminu de 1. Mais une telle numrotation est gnralement perue comme non naturelle : spontanment, on numroterait plutt les lments dun tableau en partant de 1, pas de 0. Dans un souci de convivialit avec lutilisateur, on lui permet ici dutiliser une numrotation naturelle, en partant de 1. On sattend donc ce que lutilisateur nous donne un nombre entre 1 et 3, quil nous faut transformer pour nos besoins internes de programmation en nombre entre 0 et 2. Do la soustraction de 1 la valeur donne par lutilisateur. La fonction pour demander la tour darrive est tout fait similaire. Enn, celle permettant de savoir si le jeu est termin ou non se contente de vrier si le nombre de disques sur le troisime piquet est gal au nombre total de disques, cest--dire la hauteur de la tour. Elle tient en une seule ligne :
Python
def Est_Fini(jeu): return jeu.tours[2].nb_disques == \ jeu.hauteur

C++
bool Est_Fini(const Hanoi& jeu) { return jeu.tours[2].nb_disques == jeu.hauteur; }

96

Initiation la programmation avec Python et C++

Rptons-le, le symbole pour tester lgalit de deux valeurs est le double gal ==, pas le simple signe gal = utilis pour laffectation. La confusion des deux symboles est une erreur trs courante, surtout lorsquon dbute en programmation, donc prenez garde !

7.3.3. Vrication des entres


Commenons par vrier le numro demand pour le piquet de dpart. Celui-ci nest valide que sil est compris entre 0 et 2 et quil y ait bien un disque sur ce pique t. La fonction est assez simple :
Python
def Tour_Depart_Valide(jeu, depart): if ( depart < 0 ): return False if ( depart > 2 ): return False return \ jeu.tours[depart].nb_disques > 0

C++
bool Tour_Depart_Valide(const Hanoi& jeu, int depart) { if ( (depart < 0) or (depart > 2) ) { return false; } return jeu.tours[depart].nb_disques > 0; }

Les deux versions de la fonction font exactement la mme chose mais laide de deux critures diffrentes. La version en Python utilise deux tests pour vrier lintervalle du numro de piquet donn, tandis que la version en C++ utilise un seul test compos. Le mot anglais or se traduit par ou, donc le test (depart<0) or (depart>2) vaut vrai (true) si lun des deux membres au moins est vri. Notez que ces fonctions contiennent plusieurs instructions return : il est en effet possible de "sortir" dune fonction en plusieurs endroits. Naturellement, ds quune instruction return est rencontre et excute, les ventuelles instructions qui suivent ne sont pas excutes. Ici, la fonction est assez simple, mais dans le cas dune fonction complexe avec beaucoup dembranchements (boucles ou alternatives), il est prfrable de sarranger pour navoir quune seule instruction return, place la n : cela facilite la relecture et lvolution du code.

Chapitre 7

Les Tours de Hano

97

Comme on pouvait sy attendre, la vrication du piquet darrive est un peu plus complique, car sa validit dpend du disque prsent sur le piquet de dpart et de celui ventuellement prsent sur celui darrive. Cest prcisment ce moment que nous allons assurer le respect de la rgle qui dit quil est interdit de poser un disque sur un autre plus petit. Pour cela, nous devons trouver la taille des disques au sommet de chacune des deux tours. Sachant que la structure Tour contient un membre nb_disques qui est justement le nombre de disques prsents sur un piquet, si cette valeur est suprieure 0, alors le disque au sommet se trouve lindice nb_disques-1 dans le tableau disques (la soustraction de 1 tant due, encore une fois, la numrotation des lments dun tableau qui commence 0). partir de tous ces lments, vous devriez tre mme dcrire la fonction vriant le piquet darrive. La voici, mais ne regardez pas la solution propose avant davoir essay !
Python
def Tour_Arrivee_Valide(jeu, tour_depart, tour_arrivee): if tour_arrivee < 0: return False if tour_arrivee > 2: return False nb_disques_arrivee = \ jeu.tours[tour_arrivee].nb_disques if ( nb_disques_arrivee == 0 ): return True disque_arrivee = \ jeu.tours[tour_arrivee]. \ disques[nb_disques_arrivee-1] nb_disques_depart = \ jeu.tours[tour_depart].nb_disques disque_depart = \ jeu.tours[tour_depart]. \ disques[nb_disques_depart-1] return \ disque_arrivee > disque_depart

C++
bool Tour_Arrivee_Valide(const Hanoi&jeu, int tour_depart, int tour_arrivee) { if ( (tour_arrivee < 0) or (tour_arrivee > 2) ) { return false; } int nb_disques_arrivee = jeu.tours[tour_arrivee].nb_disques; if ( nb_disques_arrivee == 0 ) return true; int disque_arrivee = jeu.tours[tour_arrivee]. disques[nb_disques_arrivee-1]; int nb_disques_depart = jeu.tours[tour_depart].nb_disques; int disque_depart = jeu.tours[tour_depart]. disques[nb_disques_depart-1]; return disque_arrivee > disque_depart; }

98

Initiation la programmation avec Python et C++

La mme chose aurait pu tre crite plus succinctement, sans utiliser les variables intermdiaires que sont nb_disques_arrivee, disque_depart, etc. Ces variables ont pour objet de clarier lcriture de chaque instruction. Sans elles, nous aurions quelque chose de relativement illisible, par exemple en Python :
return \ jeu.tours[tour_depart].disques[jeu.tours[tour_depart].nb_disques-1] > \ jeu.tours[tour_arrivee].disques[jeu.tours[tour_arrivee].nb_disques-1]

Pour nir, il serait pertinent de vrier galement le nombre de disques demands par lutilisateur au dbut du programme : on peut considrer quil faut au moins deux disques, mais pas plus dune dizaine, le nombre optimal de dplacements pour dix disques tant de 1 023, ce qui est dj beaucoup.

7.3.4. Dplacement dun disque


Nous en arrivons au cur du programme, la fonction qui va effectivement dplacer un disque dun piquet vers un autre. Ou plutt, simuler ce dplacement, qui va en ralit se traduire par des modications opres dans les tableaux de disques des tours. Le principe est en fait assez comparable celui utilis dans la vrication du piquet darrive. Sauf quau lieu de retourner une valeur boolenne nous allons modier le contenu des instances de la structure Tour : linstance qui correspond la tour de dpart perdra un disque, que celle qui correspond la tour darrive gagnera. Cette fonction ne devrait pas prsenter trop de difcults :
Python
def Deplacer_Disque(jeu, \ depart, \ arrivee): nb_disques_depart = \ jeu.tours[depart].nb_disques nb_disques_arrivee = \ jeu.tours[arrivee].nb_disques disque_depart = \ jeu.tours[depart]. \ disques[nb_disques_depart-1]

C++
void Deplacer_Disque(Hanoi& jeu, int depart, int arrivee) { int nb_disques_depart = jeu.tours[depart].nb_disques; int nb_disques_arrivee = jeu.tours[arrivee].nb_disques; int disque_depart = jeu.tours[depart]. disques[nb_disques_depart-1];

Chapitre 7

Les Tours de Hano

99

Python
jeu.tours[arrivee]. \ disques[nb_disques_arrivee] = \ disque_depart jeu.tours[depart]. \ disques[nb_disques_depart-1] = 0 jeu.tours[depart].nb_disques -= 1 jeu.tours[arrivee].nb_disques += 1

C++
jeu.tours[arrivee]. disques[nb_disques_arrivee] = \ disque_depart; jeu.tours[depart]. disques[nb_disques_depart-1] = 0; jeu.tours[depart].nb_disques -= 1; jeu.tours[arrivee].nb_disques += 1; }

Remarquez lantpnultime instruction, qui place la valeur 0 lendroit du tableau o se trouvait le disque quon vient de retirer. Cela permet de conserver le contenu de nos tableaux dans un tat cohrent : un tage o il ny a pas de disques est suppos contenir la valeur 0. Par ailleurs, la dclaration de la fonction dans la partie C++ nutilise pas le qualicatif const devant le type Hanoi: en effet, le contenu de linstance de Hanoi doit tre modi par la fonction (cest son objet mme), le paramtre ne peut donc pas tre considr comme invariable au sein de la fonction.

Le reste du programme est laiss en exercice au lecteur, tous les lments sont dsormais disponibles. Vous trouverez le code complet sur le site web de Pearson www.pearson.fr la page ddie cet ouvrage.

7.4. Rsum de la dmarche


Nous arrivons au terme de cette premire partie. Le chapitre que vous venez de lire vous a donn un aperu de la dmarche usuelle lorsquon souhaite dvelopper un programme qui nest pas trivial :

dabord, modliser les donnes du problme traiter, en usant de structures et de tableaux pour rassembler les objets logiquement lis entre eux ; ensuite, concevoir les principaux algorithmes, en les exprimant littralement, le cas chant en dessinant des ordinogrammes pour une prsentation plus visuelle ; poursuivre la conception tant quil reste des tapes qui ne paraissent pas triviales ; enn et seulement, crire le code correspondant aux algorithmes prcdemment conus et aux fonctions prvues en cours danalyse.

100

Initiation la programmation avec Python et C++

Naturellement, il nest pas ncessaire de suivre un cheminement aussi prcis lorsque vous avez raliser un petit programme rapide. Tout aussi naturellement, cette dmarche est indispensable dans le cadre de tout projet important. Les grands logiciels que vous avez sans doute lhabitude de manipuler ont t conus en suivant un paradigme analogue, gnralement bien plus strict et dtaill. Avec lexprience, vous prouverez de moins en moins le besoin de dtailler les tapes dun programme mesure que vous analysez le problme et les algorithmes impliqus. Un programmeur ayant peine une ou deux annes de pratique serait capable de produire le programme que nous venons de raliser en une heure, aboutissant un rsultat sans doute diffrent, mais assez ressemblant. Noubliez pas, cest en forgeant que lon devient forgeron ! Vous constaterez galement une forte tendance vous jeter sur le clavier, pour crire des kilomtres dinstructions qui vous viennent presque naturellement lesprit. Rsistez cette tentation : elle est souvent le plus court chemin vers linefcacit et la perte de temps. Il est de loin prfrable de sasseoir quelques minutes, avec un crayon et une feuille de papier, pour prvoir les tapes raliser ft-ce au minimum et gnralement, la n, ce sera un gain de temps.

Partie

Dveloppement logiciel
CHAPITRE 8. Le monde modlis : la programmation oriente objet (POO) CHAPITRE 9. Hano en objets CHAPITRE 10. Mise en abme : la rcursivit CHAPITRE 11. Spcicits propres au C++ CHAPITRE 12. Ouverture des fentres : programmation graphique CHAPITRE 13. Stockage de masse : manipuler les chiers CHAPITRE 14. Le temps qui passe

8
Le monde modlis : la programmation oriente objet (POO)
Au sommaire de ce chapitre :

Les vertus de lencapsulation La rutilisation par lhritage Hritage et paramtres : le polymorphisme

104

Initiation la programmation avec Python et C++

Les programmes que nous avons crs jusquici dclaraient des variables, dnissaient des fonctions agissant sur ces variables, tout cela un peu ple-mle. Si cest acceptable pour un petit programme, cela devient rapidement ingrable ds que lon sattaque une application denvergure. Aussi, le besoin dorganiser les choses, de regrouper ensemble les lments lis logiquement, sest-il rapidement impos aux programmeurs. Plusieurs techniques ont t labores pour permettre cette organisation. Par exemple, regrouper les fonctions en modules cohrents, auxquels un programme principal peut faire appel. Nous verrons plus tard cette notion de modules. Ici, nous allons tudier une autre technique, qui prend ses racines en 1967 avec le langage Simula, puis un peu plus tard avec SmallTalk : la programmation par objets. Ce paradigme de programmation sest vritablement impos dans les annes 1980, avec lavnement du langage C++, extension du langage C, pour introduire dans ce dernier les principes de la programmation objet. Dautres langages ont galement t tendus pour fournir les outils objet : Ada, Pascal, mme Basic sont des exemples. Des langages objet ds leur cration ont galement t conus, comme Java, Ruby, ou notre ami Python. Aujourdhui, la programmation par objets est le paradigme de programmation le plus largement utilis dans le monde. Mais nalement, quest-ce quun objet ? Il sagit avant tout dun mcanisme dabstraction, qui repose sur un principe fondamental. La chose manipule par le programme, quil sagisse de la modlisation dun objet physique, comme une voiture, ou dun objet abstrait, comme une fonction mathmatique, est plus que la simple collection de ses caractristiques. Pour modliser correctement cette chose, il faut galement prendre en compte les mthodes, les rgles de son utilisation. Une voiture ne se rduit pas une vitesse et une quantit dessence : cest galement un acclrateur par lequel on agit sur ces deux quantits. Lobjet vise donc fournir une reprsentation plus proche de la ralit de ce quon manipule : contenir en une mme structure informatique, non seulement les donnes associes la chose, mais aussi les moyens, les mthodes par lesquelles on peut agir dessus. Un autre objectif essentiel de la programmation oriente objet est la rutilisation du code. Cest--dire, fournir des mcanismes facilitant la cration de nouvelles structures partir de plus anciennes, lesquelles sont adaptes, ajustes, spcialises.

Chapitre 8

Le monde modlis : la programmation oriente objet (POO)

105

la base de ces mcanismes se trouve la notion dhritage ou de drivation : un nouveau type objet est construit partir dun autre, dont il hrite les caractristiques et les mthodes. On aboutit ainsi une vritable taxonomie des "choses" que le programme doit manipuler, qui nest pas sans rappeler celle des espces animales.

8.1. Les vertus de lencapsulation


Mais avant dexaminer le principe dhritage, penchons-nous un instant sur celui de lencapsulation. Ce terme trange, qui voque laction de mettre une capsule (un bouchon) sur quelque chose, dsigne une rgle afrmant que, autant que possible, les donnes dun objet ne doivent pas tre directement accessibles. Cest--dire que lutilisateur dun objet, que cela soit vous-mme ou un autre programmeur, ne doit pas pouvoir modier (ni mme consulter) les valeurs des variables contenues dans lobjet sans que cet accs soit contrl par lobjet lui-mme. Si cela peut sembler bien contraignant au premier abord, ce principe sest impos de lui-mme comme une ncessit absolue pour obtenir des programmes ables, fonctionnant correctement et donnant des rsultats cohrents. Imaginons que vous vouliez modliser un vhicule motoris, comme une voiture, un bateau ou un vaisseau spatial. Pour simplier les choses lextrme, nous nallons considrer que deux caractristiques de ce vhicule : sa vitesse et la quantit de carburant quil lui reste un instant donn. Au moment de sa "cration", notre vhicule est statique (sa vitesse est nulle) et nemporte pas de carburant. Avec ce que nous avons vu jusquici, nous pourrions modliser cela ainsi :
Python
class Vehicule: def __init__(self): self.vitesse = 0.0 self.carburant = 0.0

C++
struct Vehicule { Vehicule() { this->vitesse = 0.0; this->carburant = 0.0; } double vitesse; double carburant; };

106

Initiation la programmation avec Python et C++

Nous pouvons maintenant crer des vhicules, de nimporte quel type (croyons-nous). Lutilisation de cette structure parat tout fait triviale. Pour faire le plein dun vhicule, rien de plus simple :
Python
moto = Vehicule() moto.carburant = 100.0

C++
Vehicule moto; moto.carburant = 100.0;

Puis nous pouvons "acclrer", sans problme, simplement en modiant la vitesse de notre moto :
Python
moto.vitesse = 90.0

C++
moto.vitesse = 90.0;

Tout va bien. Nous pouvons continuer dacclrer ainsi ou ralentir. La magie de tout cela, cest que la quantit de carburant ne change pas. Et cest bien l le problme. Nous venons de modliser le vhicule idal, capable de changer de vitesse volont sans consommer une goutte de carburant. Vous admettrez que nous sommes bien loin de la ralit : lorsque vous conduisez une voiture, vous ne pouvez acclrer que par lintermdiaire de la pdale dacclrateur, action qui se traduit forcment par une diminution de la quantit disponible de carburant. Jusqu la panne sche. Autrement dit : notre modle offre toutes les possibilits pour donner des rsultats incohrents. Le problme vient du fait que nous pouvons modier directement les donnes de nos objets. Et pour lheure, il ne saurait en tre autrement, nous navons gure le choix. Lidal serait de disposer dune mthode, nomme Acceleration(), qui se chargerait elle-mme de modier les valeurs la fois de la vitesse et du carburant restant.

Chapitre 8

Le monde modlis : la programmation oriente objet (POO)

107

Voici une solution possible :


Python
class Vehicule: def __init__(self): self.vitesse = 0.0 self.carburant = 0.0 def Remplir(self, quantite): self.carburant += quantite def Accelerer(self, delta): self.vitesse += delta self.carburant -= delta/10.0 def Vitesse(self): return self.vitesse def Carburant(self): return self.carburant # ... moto = Vehicule() moto.Remplir(50.0) moto.Accelerer(90.0) } double Vitesse() const { return this->vitesse; } double Carburant() const { return this->carburant; } protected: double vitesse; double carburant; }; /* ... */ Vehicule moto; moto.Remplir(50.0); moto.Accelerer(90.0); } virtual void Accelerer(double delta) { this->vitesse += delta; this->carburant -= delta/10.0;

C++
class Vehicule { public: Vehicule(): vitesse(0.0), carburant(0.0) { } void Remplir(double quantite) { this->carburant += quantite;

La notion de donnes prives en Python nexiste pas rellement, cest--dire quil nest pas "naturel" de chercher limiter laccs aux donnes dune classe.

En C++, on utilise de prfrence le mot cl class plutt que struct. Tout ce qui se trouve dans une classe C++ est normalement

108

Initiation la programmation avec Python et C++

Python

C++

Une technique consistant faire prcder le nom dune donne par un double caractre de soulignement existe (par exemple, self.__carburant), mais elle nest gure pratique et prsente plus dinconvnients que davantages. La protection des donnes en Python est donc essentiellement une affaire de convention entre le concepteur dune classe et lutilisateur de cette classe.

priv (private), cest--dire que lutilisateur de la classe ne peut y accder. Les mots cls public et protected permettent dindiquer si ce qui les suit est, respectivement, librement accessible ou dun accs restreint lintrieur de la classe. Le sens du mot virtual qui prcde la mthode Accelerer() sera explicit la dernire section de ce chapitre.

Notez les changements subtils (et moins subtils) par rapport aux dclarations prcdentes. Dsormais, lors de lutilisation de la classe Vehicule, les donnes qui lui sont propres comme le carburant et la vitesse ne sont plus directement modiables : il est ncessaire de passer par lintermdiaire de mthodes, ici Remplir() et Accelerer(), pour altrer leurs valeurs. De cette faon, il est possible dassurer la cohrence des donnes de la classe et ainsi dviter dobtenir des rsultats fantaisistes. Les mthodes Vitesse() et Carburant() sont ce quon appelle des accesseurs : leur seule et unique vocation est de donner la valeur des donnes correspondantes, celles-ci ntant pas accessibles puisque prives. Dans notre situation, cela se rsume retourner ce qui est contenu dans les donnes membres de linstance. Mais cela peut galement rsulter dun calcul plus ou moins complexe, comme nous allons le voir bientt. Remarquez la prsence du mot cl const en C++ : il indique que ces mthodes ne vont pas modier le contenu de linstance (cest--dire modier les valeurs des donnes membres). Cest une indication donne au compilateur, qui va vrier que cette rgle est effectivement suivie. Si, par mgarde, vous tentez de modier une donne au sein dune mthode qualie par le mot cl const, le compilateur vous le signalera par un message derreur. Ce sera alors trs certainement le signe dun problme de conception. Ce principe consistant limiter laccs aux donnes dune classe est donc dsign par le terme dencapsulation. Dune manire gnrale, il est communment admis que toutes les donnes dune classe devraient tre prives, la classe fournissant des mthodes spcialises, des accesseurs, la fois pour obtenir les valeurs des donnes et pour les changer. De ce fait, les changements sont contrls, ce qui doit normalement induire une meilleure cohrence de lensemble.

Chapitre 8

Le monde modlis : la programmation oriente objet (POO)

109

Enn, notre modle est ici extrmement simple. Dans la mthode Accelerer(), on ne devrait effectivement modier la vitesse que sil reste sufsamment de carburant pour cela. Le code est toujours potentiellement incohrent, mais au moins la structure est plus robuste. Le lecteur est invit modier cette mthode pour la rendre plus raliste.

8.2. La rutilisation par lhritage


Notre vhicule nous a permis de reprsenter une moto. Et nous pourrions reprsenter de mme une voiture, un camion Mais considrez le cas dun avion, voire dun sous-marin. Il serait intressant de disposer de donnes supplmentaires, comme laltitude ou la profondeur, ainsi que de mthodes supplmentaires, comme monter ou descendre. Ce sont des vhicules bien diffrents dune moto, mais cela reste des vhicules. Naturellement, nous pourrions simplement dupliquer le code de notre classe Vehicule, par un copier-coller trop facile, et ajouter ce quil nous manque. Mais, dune part, lexprience montre que la duplication de code est source derreurs et de dysfonctionnements (car on copie galement les bogues contenus dans le code), dautre part, il pourrait tre intressant de conserver la smantique qui afrme quune moto, un avion et un sous-marin sont tous des vhicules. Ils font tous partie dune mme famille. La solution consiste utiliser l hritage, cest--dire dnir de nouvelles classes partir de classes existantes. Voici quoi pourrait ressembler une classe Avion :
Python
class Avion(Vehicule): def __init__(self): Vehicule.__init__(self) self.altitude = 0.0 def Monter(self, delta): self.altitude += delta def Descendre(self, delta): sefl.altitude -= delta def Altitude(self): return self.altitude def Accelerer(self, delta): print "Avion acclre..." self.vitesse += delta

C++
class Avion: public Vehicule { public: Avion(): Vehicule(), altitude(0.0) { } void Monter(double delta) { this->altitude += delta; }

110

Initiation la programmation avec Python et C++

Python
self.carburant -= \ delta * \ ((self.Altitude()+1.0)/1000.0)

C++
void Descendre(double delta) { this->altitude -= delta; } virtual void Accelerer(double delta) { std::cout << "Avion acclre...\n"; this->vitesse += delta; this->carburant -= \ delta * ((this->Altitude()+1.0)/1000.0); } double Altitude() const { return this->altitude; } protected: double altitude; };

Voyez comment est dclare la nouvelle classe :

En Python, class Avion(Vehicule) indique que la classe Avion drive de la classe Vehicule, ou encore quelle en hrite. En C++, class Avion: public Vehicule indique que la classe Avion drive de la classe Vehicule, ou encore quelle en hrite.

Tout cela signie que la classe Avion "contient" tout ce que contient la classe Vehicule. Constatez la puissance du procd : nous avons une nouvelle classe, offrant au total deux fois plus de mthodes que la prcdente, mais sans devoir crire deux fois plus de code. Le lecteur est invit crire le code ncessaire une classe Sous_Marin, tout fait similaire, mais qui aurait une profondeur plutt quune altitude. Ou alors, imaginez une classe Poisson_Volant, ayant la fois une altitude et une profondeur ce qui imposerait de savoir tout moment laquelle des deux valeurs est pertinente. Le monde naturel est une source inpuisable de surprises.

Chapitre 8

Le monde modlis : la programmation oriente objet (POO)

111

Remarquez tout de mme que nous avons surdni la mthode Accelerer(). Cela signie que nous avons dni dans la nouvelle classe une mthode de mme nom, ayant le mme aspect (on dit plutt la mme signature), cest--dire prenant les mmes paramtres dans le mme ordre. Si nous dclarons une instance d Avion et que nous invoquions la mthode Accelerer(), cest bien celle surdnie dans Avion qui sera utilise, pas celle venant de la classe mre Vehicule. Par exemple :
Python
robin = Avion() robin.Remplir(50.0) print robin.Carburant() robin.Accelerer(100.0) print robin.Carburant()

C++
int main(int argc, char* argv[]) { Avion robin; robin.Remplir(50.0); std::cout << robin.Carburant() << std::endl; robin.Accelerer(100.0); std::cout << robin.Carburant() << std::endl; return 0; }

Les mthodes Remplir() et Carburant() dans les extraits prcdents viennent directement de la classe Vehicule, bien quelles soient appliques une instance de la classe Avion. On dit que la mthode Remplir() est hrite de la classe Vehicule, ou encore que la classe Avion hrite de la mthode Remplir() partir de la classe Vehicule. Par contre, Accelerer() est bien celle dnie dans Avion. Du ct du C++, il est prfrable que toute mthode pouvant tre ainsi surdnie dans une classe lle soit qualie par le mot cl virtual, comme cest le cas ici. Nous allons voir immdiatement pourquoi.

112

Initiation la programmation avec Python et C++

8.3. Hritage et paramtres : le polymorphisme


En utilisant lhritage, nous pouvons crer toute une famille de type de vhicules. Remarquez que nous navons dni quune mthode Accelerer(), pas de mthode permettant de dnir exactement une vitesse. Ce nest pas trs grave : partir dune vitesse donne, on peut atteindre une autre vitesse en acclrant ou en freinant (freiner quivalant une acclration ngative). On peut alors imaginer une fonction ressemblant ceci :
Python
def Atteindre_Vitesse(un_vehicule, une_vitesse): delta = une_vitesse - \ un_vehicule.Vitesse() un_vehicule.Accelerer(delta)

C++
void Atteindre_Vitesse(Vehicule& un_vehicule, double une_vitesse) { double delta = une_vitesse un_vehicule.Vitesse(); un_vehicule.Accelerer(delta); }

Remarquez la prsence de lperluette : on passe bien une rfrence sur une instance de Vehicule.

La question que lon peut alors se poser est, si ce que nous venons dcrire fonctionne pour une instance de Vehicule, comment faire pour une instance dAvion ou pour toute autre classe drive de Vehicule ? La bonne nouvelle est que le comportement est naturel, cest--dire quil correspond ce quon attend effectivement.

Chapitre 8

Le monde modlis : la programmation oriente objet (POO)

113

Par exemple :
Python
moto = Vehicule() moto.Remplir(50.0) robin = Avion() robin.Remplir(50.0) Atteindre_Vitesse(moto, 90.0) Atteindre_Vitesse(robin, 100.0)

C++
Vehicule moto; moto.Remplir(50.0); Avion robin; robin.Remplir(50.0); Atteindre_Vitesse(moto, 90.0); Atteindre_Vitesse(robin, 100.0);

Nous crons deux vhicules, lun "gnrique" (la moto), lautre un modle davion. Puis nous cherchons pour chacun deux atteindre une vitesse donne, en utilisant la fonction Atteindre_Vitesse(). En son sein, celle-ci va faire appel la mthode Accelerer() prsente dans chacune des classes Vehicule et Avion. Ce qui se passe ensuite est trs naturel : si la fonction reoit en paramtre une instance de Vehicule, alors cest la mthode Accelerer() de Vehicule qui sera invoque ; si elle reoit en paramtre une instance d Avion, alors cest la mthode Accelerer() dAvion qui sera invoque. Si vous excutez ce programme, vous obtiendrez lafchage :
Vehicule acclre... Avion acclre...

Ce qui est la preuve que cest la "bonne" mthode qui est utilise. Cela peut sembler vident en Python, mais beaucoup moins en C++ : le type du paramtre pass la fonction Atteindre_Vitesse() est une rfrence une instance de Vehicule et cest justement en raison de cette rfrence que cela fonctionne, associ au fait que la classe Avion drive de la classe Vehicule. La variable robin est une instance dAvion. Mais comme Avion drive de Vehicule, robin est aussi une instance de Vehicule. On peut donc la passer la fonction Atteindre_Vitesse(). Consquence de la rfrence, un phnomne un peu trange survient : la fonction "croit" recevoir une instance de Vehicule, mais cest bel et bien une instance dAvion quelle manipule, sans mme le "savoir". Les mthodes invoques dans la fonction ne peuvent qutre des mthodes prsentes dans la classe Vehicule, mais ce sont bien les mthodes de la classe Avion qui seront invoques. Sil a t ncessaire de surdnir une mthode, comme cest le cas de la mthode Accelerer(), la prsence du mot cl virtual garantit que, dans cette situation, cest la

114

Initiation la programmation avec Python et C++

mthode surdnie qui sera invoque. Cest pourquoi toute mthode dune classe destine tre surdnie dans une classe drive doit tre qualie par le mot cl virtual. Faites les expriences suivantes. Dabord, retirez le mot cl virtual prcdant la dclaration de la mthode Accelerer() dans la classe Vehicule, puis compilez et excutez nouveau le programme. Ensuite, replacez le mot cl virtual, mais retirez lperluette dans la dclaration de la fonction Atteindre_Vitesse(), puis compilez et excutez nouveau le programme. Vous obtiendrez ce rsultat :
Vehicule acclre... Vehicule acclre...

Seule la mthode Accelerer() de la classe Vehicule est invoque. Avec la combinaison de virtual et de lperluette, on peut dire que le paramtre un_vehicule pass la fonction change daspect selon la nature relle de la variable qui lui est donne. Ce phnomne est dsign par le terme de polymorphisme, qui signie littralement "pouvant prendre plusieurs formes". Pour la fonction Atteindre_Vitesse(), le paramtre un_vehicule est polymorphe : selon le type de la variable qui lui est donne, il aura en ralit tantt la forme dun Vehicule, tantt la forme dun Avion, ou toute autre forme dnie par une classe drivant de Vehicule (ou mme dAvion). En langage Python, le polymorphisme est implicite et automatique. En langage C++, il ncessite lemploi des rfrences pour les paramtres et du mot cl virtual pour les mthodes susceptibles dtre surdnies dans des classes drives. Il sagit l dun mcanisme fondamental et extrmement puissant de la programmation oriente objet, comme nous allons le constater bientt.

9
Hano en objets
Au sommaire de ce chapitre :

Des tours compltes Un jeu autonome Linterface du jeu Le programme principal

116

Initiation la programmation avec Python et C++

Il est maintenant temps que transformer le programme du jeu des Tours de Hano, cr au Chapitre 7, an de lui appliquer ce que nous venons de voir. Toutes les techniques ne seront pas utilises immdiatement, mais cela va vous donner une vue densemble de ce quoi peut ressembler un programme objet complet.

9.1. Des tours compltes


Pour commencer, voyons la classe nous permettant de reprsenter une tour, ou plutt laiguille portant la tour. La structure prsente au Chapitre 7 se limitait au seul constructeur. Nous avons maintenant la possibilit de faire bien mieux, en ajoutant de nombreuses petites mthodes pratiques.
Python
class Tour: def __init__(self, taille_max): self.nb_disques = 0 self.disques = \ [0 for e in range(taille_max)] def Est_Vide(self): return self.nb_disques == 0 def Est_Pleine(self): return \ self.nb_disques == \ len(self.disques) def Sommet(self): if self.Est_Vide(): return 0 else: return self.disques [self.nb_disques-1] def Disque_Etage(self, etage): if ( self.Est_Vide() or \ (etage >= self.nb_disques) ): return 0 else: return self.disques[etage] def Peut_Empiler(self, disque): if self.Est_Vide(): return True else: return (self.Sommet() > disque) def Empiler(self, disque): self.disques[self.nb_disques] = disque self.nb_disques += 1 def Depiler(self): self.nb_disques -= 1 self.disques[self.nb_disques] = 0

Pour Python, il importe de bien respecter lindentation : cest en effet elle qui va assurer, dune part, que la dnition dune mthode appartient une classe, dautre part, que des instructions appartiennent une mthode. Par ailleurs, si vous comparez ce code avec la version C++ qui suit, vous constatez quil nexiste pas de signes particuliers pour indiquer quune mthode ne modie pas linstance ou quune donne ne doit pas tre utilise directement. Python ne possde pas, en effet, de moyen simple et immdiat pour mettre en uvre ces techniques normalement destines amliorer la robustesse du programme. En contrepartie, le code est plus simple et plus "compact".

Chapitre 9

Hano en objets

117

C++
class Tour { public: Tour(int taille_max): nb_disques(0), disques(taille_max, 0) { } bool Est_Vide() const { return this->nb_disques == 0; } bool Est_Pleine() const { return this->nb_disques == this->disques.size(); } int Sommet() const { if ( this->Est_Vide() ) return 0; else return this->disques [this->nb_disques-1]; } int Disque_Etage(int etage) const { if ( this->Est_Vide() or (etage >= this->nb_disques) ) return 0; else return this->disques[etage]; } bool Peut_Empiler(int disque) const { if ( this->Est_Vide() ) return true; else { if ( this->Est_Pleine() ) return false; else return (this->Sommet() > disque); } } void Empiler(int disque) { this->disques[this->nb_disques] = disque; this->nb_disques += 1; }

Ct C++, remarquez comme les mthodes purement informatives, comme Est_Vide(), sont toutes qualies par le mot cl const. Comme nous lavons vu au chapitre prcdent, cela indique que ces mthodes ne doivent pas modier linstance sur laquelle elles sont appliques. Grce quoi, des erreurs dinattention peuvent tre dtectes, comme utiliser un simple signe = (affectation) la place du signe == (comparaison dgalit). Par ailleurs, les donnes de la classe sont places dans une section private (prive), contrairement la section protected (protge) vue au chapitre prcdent. La consquence est une meilleure protection de ces donnes : non seulement, elles ne sont pas directement utilisables lextrieur de la classe mais, en plus, mme une classe drivant de celle-ci ne pourrait pas les manipuler. On aboutit ainsi une encapsulation complte de ces donnes. Enn, notez quil est fait une certaine conomie sur les accolades, notamment dans les structures alternatives (if). En labsence daccolades, seule lunique instruction suivant immdiatement le test introduit par if est excute si la condition est vrie. Ainsi :
if ( une_condition ) une_instruction; une_autre_instruction;

118

Initiation la programmation avec Python et C++

C++
void Depiler() { this->nb_disques -= 1; this->disques[this->nb_disques] = 0; } private: int nb_disques; std::vector<int> disques; }; // class Tour

est quivalent :
if ( une_condition ) { une_instruction; } une_autre_instruction;

Rappelez-vous que lindentation en C++ est purement dcorative et na aucune valeur pour le langage.

Les mthodes ajoutes dans cette classe devraient parler delles-mmes. Dcrivons-les brivement :

Est_Vide() permet de savoir si une tour est vide ou non, cest--dire si elle contient au moins un disque ou non. Est_Pleine(), symtrique de la prcdente, permet de savoir si tous les tages disponibles sont occups. Sommet() donne la taille du disque prsent au sommet de la tour, Disque_Etage() donne la taille du disque prsent un tage donn ; une taille nulle signie quaucun disque nest prsent lendroit demand. Peut_Empiler() fait partie de la modlisation de la rgle du jeu : elle indique si la tour peut recevoir la taille de disque quon lui donne en paramtre. Empiler() et Depiler() permettent, respectivement, dajouter ou de retirer un disque au sommet de la tour. Elles sont ici particulirement sommaires : dans un vritable programme, il faudrait ajouter des tests pour vrier que ces oprations sont lgitimes (par exemple, on ne peut dpiler un disque dune tour vide). Ces tests sont laisss en exercice au lecteur.

Peut-tre toutes ces mthodes assez lmentaires vous semblent-elles inutiles. Aprs tout, elles contiennent si peu dinstructions quon pourrait tout aussi bien les rcrire lorsque le besoin se prsente. Mais voyons comment, maintenant, nous allons modliser le jeu lui-mme.

Chapitre 9

Hano en objets

119

9.2. Un jeu autonome


Vous laurez compris, nous allons maintenant tendre la structure Hanoi du Chapitre 7, comme nous avons tendu la structure Tour. Mais ne perdez pas de vue la raison dtre de cette structure : modliser le jeu des Tours de Hano et rien dautre. En particulier, cela signie que nous nallons y intgrer aucune mthode ralisant un afchage quelconque : nous allons bel et bien crer un moteur de jeu, parfaitement indpendant de la manire dont il sera prsent lutilisateur. Nous verrons plus loin limportance que cela aura. Vous retrouverez dans cette classe certaines des fonctions que nous avions cres dans notre premier programme de jeu, naturellement adaptes au nouveau contexte. Un changement important rside dans la cration dune mthode Initialiser(), dont le but est tout simplement de prparer le jeu dans un tat initial en fonction dun certain nombre de disques demands. Voici donc notre nouvelle classe Tour :
Python
class Hanoi: def __init__(self): self.hauteur_max = 0 self.tours = [] def Initialiser(self, nb_disques): self.tours = [Tour(nb_disques), \ Tour(nb_disques), \ Tour(nb_disques)] for disque in range(nb_disques,0, -1): self.tours[0].Empiler(disque) self.hauteur_max = nb_disques def Hauteur(self): return self.hauteur_max def Une_Tour(self, num_tour): return self.tours[num_tour]

Dans le constructeur, lcriture self.tours=[] permet de dclarer la donne membre tours comme un tableau, initialement vide (ne contenant aucun lment). Au sein de la mthode Initialiser(), le tableau des tours est tout simplement recr. Un nouveau tableau, dont on donne explicitement la liste des lments entre crochets, lui est affect.

120

Initiation la programmation avec Python et C++

Python
def Tour_Depart_Valide(self, depart): if (depart < 0) or (depart > 2): return False return (not self.tours[depart].Est_Vide()) def Tour_Arrivee_Valide(self, depart,arrivee): if (arrivee < 0) or (arrivee > 2): return false; disque_depart = self.tours[depart].Sommet() return self.tours[arrivee].Peut_Empiler(disque_depart) def Deplacer_Disque(self, depart, arrivee): disque = self.tours[depart].Sommet() self.tours[depart].Depiler() self.tours[arrivee].Empiler(disque) def Jeu_Termine(self): return self.tours[2].Est_Pleine()

C++
class Hanoi { public: Hanoi(): hauteur_max(0), tours() { } void Initialiser(int nb_disques) { this->tours.clear(); this->tours.resize(3, Tour(nb_disques)); for(int disque = nb_disques; disque > 0; --disque) { this->tours[0].Empiler(disque); } this->hauteur_max = nb_disques; } int Hauteur() const { return this->hauteur_max; } const Tour& Une_Tour(int i) const

Au sein de la mthode Initialiser(), on utilise deux mthodes propres au type std::vector. clear() a pour effet de vider le tableau, cest--dire de dtruire tous les lments quil contient : tout se passe alors comme si on avait un tableau vide aprs son application. resize(), au contraire, permet de spcier la taille du tableau, cest--dire le nombre dlments quil pourra contenir.

Chapitre 9

Hano en objets

121

C++
{ return this->tours[i]; } bool Tour_Depart_Valide(int depart)const { if ( (depart < 0) or (depart > 2) ) { return false; } return (not this->tours[depart].Est_Vide()); } bool Tour_Arrivee_Valide(int depart, int arrivee)const { if ( (arrivee < 0) or (arrivee > 2) ) { return false; } int disque_depart = this->tours[depart].Sommet(); return this->tours[arrivee].Peut_Empiler(disque_depart); } void Deplacer_Disque(int depart, int arrivee) { int disque = this->tours[depart].Sommet(); this->tours[depart].Depiler(); this->tours[arrivee].Empiler(disque); } bool Jeu_Termine() const { return this->tours[2].Est_Pleine(); } private: int hauteur_max; std::vector<Tour> tours; }; // class Hanoi

Le deuxime paramtre de resize() est une valeur initiale recopier dans chacun des nouveaux lments du tableau. Vous pouvez constater que resize() prsente un aspect tout fait analogue celui que nous avons rencontr jusquici lors de la dclaration dun tableau : une taille suivie dune valeur initiale.

Comparez maintenant lcriture des mthodes telles que Tour_Arrivee_Valide(), avec celle que nous avons vue dans les fonctions ponymes du Chapitre 7. Vous pouvez constater que le code donn ici est plus lisible : lutilisation des mthodes

122

Initiation la programmation avec Python et C++

fournies par la classe Tour permet dobtenir une criture plus compacte et plus expressive. Cest lun des avantages "annexes" de la programmation oriente objet : lobtention de programmes plus lisibles car plus expressifs, donc plus aiss maintenir dans le temps et faire voluer. La mthode Une_Tour() est un peu particulire : elle donne accs lune des tours du jeu en cours aux utilisateurs de la classe Hanoi. Remarquer en C++ lutilisation du mot cl const : la rfrence sur une tour renvoye est ainsi qualie pour garantir que lutilisateur ne pourra pas modier cette tour. En effet, normalement seule linstance de la classe Hanoi doit pouvoir modier les tours, seule faon dapporter une garantie (relative) la robustesse et la cohrence de lensemble. Incidemment, cela signie qu partir de cette instance constante de la classe Tour obtenue, lutilisateur ne pourra utiliser que des mthodes de Tour elles-mmes qualies par const : les seules garantissant lintgrit des donnes de la tour.

9.3. Linterface du jeu


Rptons-le, un principe essentiel de la programmation consiste bien sparer linterface dun programme, cest--dire la partie charge dinteragir avec lutilisateur, du moteur de calcul, cest--dire la partie contenant effectivement la mise en uvre des rgles et contraintes du problme rsoudre. Limportance de cette sparation sera pleinement dmontre dans quelques chapitres. Pour lheure, nous allons utiliser la programmation par objet pour traduire cette distinction, en crant une classe Hanoi_Texte charge prcisment de (presque) tous les aspects de lafchage du programme. Cette classe est ici donne dans son intgralit, an que vous puissiez comparer son contenu avec celui des fonctions cres au Chapitre 7.
Python
class Hanoi_Texte: def __init__(self, jeu): self.jeu_hanoi = jeu self.nb_mouvements = 0 def Jeu(self): return self.jeu_hanoi def Demander_Tour_Depart(self): print "Tour de dpart?", tour_depart = int(raw_input()) return tour_depart-1

Chapitre 9

Hano en objets

123

Python
def Demander_Tour_Arrivee(self): print "Tour darrive?", tour_arrivee = int(raw_input()) return tour_arrivee-1 def Afficher_Jeu(self): for etage in \ range(self.Jeu().Hauteur(), -1, -1): for tour in range(3): print " " + \ self.Chaine_Etage(tour, etage), print def Nb_Mouvements(self): return self.nb_mouvements; def Etape(self): tour_depart = self.Demander_Tour_Depart() if self.Jeu().Tour_Depart_Valide (tour_depart): tour_arrivee = self.Demander_Tour_Arrivee() if self.Jeu().Tour_Arrivee_Valid (tour_depart, \ tour_arrivee): self.Jeu().Deplacer_Disque(tour_depart, \ tour_arrivee) self.nb_mouvements += 1 else: print "Arrive invalide!" else: print "Dpart invalide!" def Chaine_Etage(self, tour, etage): largeur_max = self.Jeu().Hauteur() disque = self.Jeu().Une_Tour(tour).Disque_Etage(etage) espace = *(largeur_max-disque) chaine = "" if ( disque > 0 ): ligne = =*disque chaine = espace + "<" + ligne + "!" + ligne + ">" + espace else: chaine = espace + "! " + espace return chaine

124

Initiation la programmation avec Python et C++

C++
class Hanoi_Texte { public: Hanoi_Texte(Hanoi& jeu): jeu_hanoi(jeu), nb_mouvements(0) { } Hanoi& Jeu() { return this->jeu_hanoi; } const Hanoi& Jeu() const { return this->jeu_hanoi; } int Demander_Tour_Depart() { int tour_depart; std::cout << "Tour de dpart? "; std::cin >> tour_depart; return tour_depart - 1; } int Demander_Tour_Arrivee() { int tour_arrivee; std::cout << "Tour darrive? "; std::cin >> tour_arrivee; return tour_arrivee - 1; } void Afficher_Jeu() const { for(int etage = this->Jeu().Hauteur(); etage >= 0; --etage) { for(int tour = 0; tour < 3; ++tour) { std::cout << " " << this->Chaine_Etage(tour, etage) << " "; } std::cout << std::endl; } }

Chapitre 9

Hano en objets

125

C++
int Nb_Mouvements() const { return this->nb_mouvements; } void Etape() { int tour_depart = this->Demander_Tour_Depart(); if ( this->Jeu().Tour_Depart_Valide(tour_depart) ) { int tour_arrivee = \ this->Demander_Tour_Arrivee(); if ( this->Jeu().Tour_Arrivee_Valide(tour_depart, tour_arrivee) ) { this->Jeu().Deplacer_Disque(tour_depart, tour_arrivee); this->nb_mouvements += 1; } else { std::cout << "Arrive invalide!\n"; } } else { std::cout << "Dpart invalide!\n"; } } std::string Chaine_Etage(int tour, int etage)const { int largeur_max = this->Jeu().Hauteur(); int disque = this->Jeu().Une_Tour(tour).Disque_Etage(etage); std::string espace(largeur_max-disque, ); std::string chaine; if ( disque > 0 ) { std::string ligne(disque, =); chaine = espace + "<"+ ligne + "!" + ligne + ">" + espace; }

126

Initiation la programmation avec Python et C++

C++
else { chaine = espace + "! " + espace; } return chaine; } protected: Hanoi& jeu_hanoi; int nb_mouvements; }; // Hanoi_Texte

Quelques remarques simposent. Tout dabord, le constructeur de cette classe Hanoi_Texte attend un paramtre, qui doit tre une instance de la classe Hanoi cest--dire, une instance du jeu afcher. Il serait en effet absurde de vouloir afcher un jeu qui nexiste pas. Cela signie que linstance du jeu devrait tre cre "en dehors" de linstance de la classe dinterface. On gagne ainsi une certaine souplesse, en ayant la possibilit de faire afcher un mme jeu par diffrentes interfaces (en supposant que cela prsente un intrt quelconque). Remarquez quen C++ cette instance du jeu est passe par une rfrence et stocke en tant que rfrence : cela signie que la donne jeu_hanoi permettra effectivement daccder au jeu dclar par ailleurs, cette donne nest pas une autre instance du jeu. Une telle rfrence est implicite en Python : dans ce langage, tout paramtre reprsentant une variable structure est pass par rfrence. Enn, voyez comment lutilisation dune rfrence sur la classe Tour dans la mthode Chaine_Etage(), en C++, se limite lutilisation de la mthode Disque_Etage(), elle-mme qualie par const. Si vous retirez ce qualicatif dans la dclaration de cette mthode dans Tour, vous obtiendrez une erreur la compilation, car la rfrence retourne par Une_Tour() (de la classe Hanoi) est qualie par const : seules les mthodes constantes sont autorises sur une rfrence constante. Encore une fois, il sagit l dune mesure de protection pour viter des erreurs de conception. Un tel mcanisme est toutefois absent en Python.

9.4. Le programme principal


Maintenant que lessentiel des fonctionnalits du programme est organis en classes et mthodes, le programme principal se rsume nalement sa plus simple expression :

Chapitre 9

Hano en objets

127

Python
print "Nombre de disques?", hauteur_max = int(raw_input()) le_jeu = Hanoi() le_jeu.Initialiser(hauteur_max); interface = Hanoi_Texte(le_jeu) while (not le_jeu.Jeu_Termine()): interface.Afficher_Jeu() interface.Etape() interface.Afficher_Jeu() print "Russit en", \ interface.Nb_Mouvements(), \ "mouvements!"

C++
int main(int argc, char* argv[]) { int hauteur_max; std::cout << "Nombre de disques? "; std::cin >> hauteur_max; Hanoi le_jeu; le_jeu.Initialiser(hauteur_max); Hanoi_Texte interface(le_jeu); while ( not le_jeu.Jeu_Termine() ) { interface.Afficher_Jeu(); interface.Etape(); } interface.Afficher_Jeu(); std::cout << "Russit en " << interface.Nb_Mouvements() << " mouvements!\n"; return 0; }

Sa structure fondamentale est la mme que celui du Chapitre 7. Toutefois, lalgorithmique en est inniment plus simple, tout tant assur par les diverses classes que nous avons cres. Il en devient de fait beaucoup plus facile lire.

10
Mise en abme : la rcursivit
Au sommaire de ce chapitre :

La solution du problme Intgration au grand programme

130

Initiation la programmation avec Python et C++

Non, nous nallons pas tudier ici un procd cinmatographique. Maintenant que nous avons un programme permettant un utilisateur de jouer aux Tours de Hano, il est temps de proposer une fonctionnalit essentielle pour un jeu de rexion : afcher la solution pour rsoudre le problme.

10.1. La solution du problme


Avant de nous lancer dans une programmation effrne, il vaut mieux se demander comment nous pouvons rsoudre le problme, dj en tant qutres humains. Et si possible, trouver une solution optimale, en un minimum de mouvements. Et sachant galement que nous ne prtendrons pas ne serait-ce quefeurer les principes conduisant lintelligence articielle. Une approche possible consisterait examiner systmatiquement tous les mouvements possibles, an de dterminer la succession la plus rapide pour parvenir au rsultat. Toutefois, nous nous heurterions rapidement ce quon appelle parfois l explosion combinatoire : le nombre de possibilits est tellement vaste que mme un ordinateur puissant ne pourrait les examiner toutes en un temps raisonnable. Nous allons donc devoir nous reposer sur notre intuition, tre intelligents et transmettre notre intelligence au programme. Posons-nous la question : comment moi, tre humain, puis-je rsoudre ce problme ? Pour xer les ides, prenons par exemple quatre disques. Parmi eux, un est remarquable : le plus grand. Il ne peut tre pos sur aucun autre, il est donc le plus contraignant dplacer. Une solution idale ne devrait dplacer ce disque quune seule fois. Nommons T1, T2 et T3 les trois piquets, la pile de disques tant au dpart sur T1. Idalement, donc, le plus grand disque sera dplac de T1 T3 en une seule fois. Comme il ne peut tre pos sur aucun autre disque car il est le plus grand , cela ne pourra se faire que si aucun disque nest prsent sur T3. Autrement dit, on ne pourra effectuer ce dplacement particulier que si le grand disque est seul sur T1, et que tous les autres disques sont sur T2. Incidemment, nous venons de rduire le problme : pour pouvoir dplacer nos quatre disques de T1 vers T3, il faut dabord dplacer (seulement) trois disques de T1 vers T2. Puis dplacer le grand sur T3. Puis redplacer les trois disques de T2 vers T3. Ces trois tapes sont illustres la Figure 10.1. Le grand disque prsente une autre particularit : il est le seul sur lequel on peut poser nimporte lequel des autres disques. Donc, dans le cadre dun dplacement des trois disques suprieurs, il na aucune incidence : tout se passe comme sil ntait pas l. Cette remarque nous amne un constat intressant : on peut traiter le problme du dplacement des trois disques, exactement de la mme manire que nous avons trait

Chapitre 10

Mise en abme : la rcursivit

131

Figure 10.1 tapes pour une rduction du problme quatre disques.

T1

T2

T3

celui des quatre. La Figure 10.2, dans sa partie droite, montre comment la premire des trois tapes peut elle-mme tre dcompose en trois "sous-tapes" : dplacer les deux disques suprieurs, puis le disque infrieur, puis redplacer les deux disques suprieurs. La troisime tape de la partie gauche pourrait tre dcompose de la mme faon.
T1 T2 T3 1.1

1 1.2 2 1.3

Figure 10.2 tapes pour une rduction du problme quatre disques.

132

Initiation la programmation avec Python et C++

Gnralisons. Nous avons comme objectif de dplacer n disques dun piquet T1 vers un piquet T3, par lintermdiaire dun piquet T2 (appel le pivot). La marche suivre est alors : 1. Dplacer n1 disques de T1 vers T2. 2. Dplacer un disque (le dernier restant) de T1 vers T3. 3. Dplacer n1 disques de T2 vers T3. Les tapes 1 et 3 peuvent elles-mmes se dcomposer selon le mme principe, sauf que les rles des piquets sont intervertis. Par exemple, dans la premire, on va de T1 vers T2, donc forcment par lintermdiaire de T3. Voici la marche suivre, donne sur deux niveaux : 1. Dplacer n1 disques de T1 vers T2 : dplacer n2 disques de T1 vers T3, dplacer un disque de T1 vers T2, dplacer n2 disques de T3 vers T2. 2. Dplacer un disque de T1 vers T3. 3. Dplacer n1 disques de T2 vers T3 : dplacer n2 disques de T2 vers T1, dplacer un disque de T2 vers T3, dplacer n2 disques de T1 vers T3. Et ainsi de suite. Nous tenons l une ide de solution. Maintenant, voyons comment toutes ces ides se traduisent en programmation.

10.2. Une fonction rcursive


Nous allons commencer par un petit programme tout simple, qui afche la solution au problme sans soccuper de lesthtique. Lide de la fonction rcursive est la suivante : tant donn un problme de taille N, on suppose savoir le rsoudre si la taille est 1, ainsi que disposer dune fonction permettant de le rsoudre la taille N1. La solution pour la taille N sappuie donc sur la solution la taille N1. Les lecteurs ayant un penchant pour les mathmatiques auront reconnu l une expression du principe de rcurrence. Un exemple concret dans la vie courante se rencontre dans le cas dun emprunt bancaire ou dun placement nancier. Si vous placez la somme de 100 au taux de 5 % par an, quel sera votre capital au bout de dix ans ? Exactement 5 % de plus que ce quil tait au bout de neuf ans. Et au bout de neuf ans ? Exactement 5 % de plus que ce

Chapitre 10

Mise en abme : la rcursivit

133

quil tait au bout de huit ans. Et ainsi de suite. Si on suppose disposer dune fonction Capital() donnant le capital au bout de n annes (passes en paramtre) partir dune mise initiale de x (passe en paramtre) en fonction dun taux t (pass en paramtre), une criture naturelle reprenant le principe prcdent serait :
Python
def Capital(annees, mise, taux): return (1.0 + (taux/100.0)) * \ Capital(annees-1, mise, taux)

C++
double Capital(int annees, double mise, double taux) { return (1.0 + (taux/100.0)) * Capital(annees-1, mise, taux); }

La fonction Capital() sutilise elle-mme pour calculer son rsultat. crite ainsi, elle prsente toutefois un inconvnient majeur : elle va sappeler elle-mme linni, sans jamais sarrter ou du moins, en ne sarrtant quau moment o le programme aura puis la mmoire disponible, tout appel de fonction consommant une petite quantit de mmoire. Il faut ajouter un test, pour retourner le rsultat lorsquon peut le calculer directement. Ici, lorsquon demande le capital au bout dune seule anne. Ce test est ce que lon appelle parfois la condition darrt de la rcursivit. Voici un programme complet :
Python
def Capital(annees, mise, taux): if ( annees == 1 ): return (1.0 + (taux/100.0)) * mise else: return (1.0 + (taux/100.0)) * \ Capital(annees-1, mise, taux) print Capital(10, 100.0, 5.0)

C++
#include <iostream> double Capital(int annees, double mise, double taux) { if ( annees == 1 ) return (1.0 + (taux/100.0)) * mise; else return (1.0 + (taux/100.0)) * Capital(annees-1, mise, taux); } int main(int argc, char* argv[]) { std::cout << Capital(10, 100.0, 5.0) << std::endl; return 0; }

134

Initiation la programmation avec Python et C++

Encore une fois, les mathmaticiens auront reconnu une exponentielle. Mais limportant est ici de comprendre le mcanisme en uvre : une fonction sutilisant elle-mme. Essayez maintenant dcrire un programme minimaliste afchant les dplacements ncessaires pour rsoudre le problme des Tours de Hano. Cela pourrait ressembler ceci :
Python
def Hanoi_Solution(nb_disques, depart, pivot, arrivee): if ( nb_disques == 1 ): print depart, "->", arrivee else: Hanoi_Solution(nb_disques-1, \ depart, \ arrivee, \ pivot) Hanoi_Solution(1, \ depart, \ pivot, \ arrivee) Hanoi_Solution(nb_disques-1, \ pivot, \ depart, \ arrivee) Hanoi_Solution(3, 1, 2, 3)

C++
#include <iostream> void Hanoi_Solution(int nb_disques, int depart, int pivot, int arrivee) { if ( nb_disques == 1 ) std::cout << depart << " -> " << arrivee << std::endl; else { Hanoi_Solution(nb_disques-1, depart, arrivee, pivot); Hanoi_Solution(1, depart, pivot, arrivee); Hanoi_Solution(nb_disques-1, pivot, depart, arrivee); } } int main(int argc, char* argv[]) { Hanoi_Solution(4, 1, 2, 3); return 0; }

La fonction Hanoi_Solution() attend en paramtre le nombre de disques dplacer, la tour de dpart, la tour darrive et la tour intermdiaire utilise comme pivot. Celleci pourrait tre dtermine automatiquement, mais cela alourdirait inutile notre exemple (toutefois, le lecteur est invit crire les quelques tests ncessaires). Voyez comme on retrouve exactement le principe de solution esquiss la premire section de ce chapitre.

Chapitre 10

Mise en abme : la rcursivit

135

10.3. Intgration au grand programme


Voyons maintenant comme intgrer tout cela notre grand programme. Le calcul dune solution au jeu semble bien faire partie de la partie "interne" du programme, aussi serait-on tent dajouter une mthode effectuant ce calcul dans la classe Hanoi. Cependant, ce calcul est indpendant du jeu : il na pas besoin des donnes contenues dans la classe. De plus, se pose le problme de lafchage du rsultat : il nest videmment pas question dafcher quoi que ce soit en dehors de la classe Hanoi_Texte prvue cet effet. Pour ces raisons, naturellement discutables, le choix a t fait de placer le calcul dune solution dans une fonction totalement indpendante. De plus, le rsultat de ce calcul sera stock au fur et mesure dans un paramtre supplmentaire, un tableau dans lequel sera place la suite de mouvements dtermine.
Python
def Hanoi_Solution(nb_disques, \ depart, \ pivot, \ arrivee, \ soluce): if ( nb_disques == 1 ): soluce.append( (depart, arrivee) ) else: Hanoi_Solution(nb_disques-1,depart, arrivee, pivot, soluce) Hanoi_Solution(1, depart, pivot,arrivee, soluce) Hanoi_Solution(nb_disques-1,pivot, depart, arrivee, soluce)

Le paramtre soluce est attendu comme un tableau. mesure quon avance dans le calcul des dplacements, on ajoute la n de ce tableau (append) une paire de valeurs, ce que lon appelle un tuple en Python. Un tuple est crit comme une liste de valeurs, spares par des virgules, encadre de parenthses. Les parenthses en apparence surnumraires dans linstruction mise en vidence ici ne sont donc pas une erreur, elles permettent effectivement dcrire un tuple compos de deux lments.

136

Initiation la programmation avec Python et C++

C++

De mme quen Python, nous allons stocker des paires de valeurs dans un tableau.
void Hanoi_Solution(int nb_disques, int depart, int pivot, int arrivee, std::vector<std::pair <int, int> >& soluce) { if ( nb_disques == 1 ) soluce.push_back(std::make_pair(depart, arrivee)); else { Hanoi_Solution(nb_disques-1, depart, arrivee, pivot, soluce); Hanoi_Solution(1, depart, pivot,arrivee, soluce); Hanoi_Solution(nb_disques-1, pivot,depart, arrivee, soluce); } }

En C++, une paire de valeurs peut tre stocke dans une instance du type gnrique std::pair<>, en donnant dans les chevrons les types de chacune des valeurs (ici des entiers). Une instance dun tel type est obtenue par la fonction std::make_ pair(), qui prend en paramtres les deux valeurs stocker. Enn, lajout dun lment la n dun tableau est obtenu par la mthode push_ back() du type gnrique std::vector<> que nous connaissons depuis longtemps.

Le contenu de ce tableau est ensuite exploit par la classe Hanoi_Texte pour afcher effectivement la solution. Cette fois, nous allons ajouter une mthode dans cette classe, nomme Solution(), ddie cette tche. Sa premire action sera de rinitialiser le jeu (par la mthode Initialiser() de la classe Hanoi), an de pouvoir simuler un droulement normal.

Chapitre 10

Mise en abme : la rcursivit

137

Python
def Solution(self): print "-"*40 print "SOLUTION" print "-"*40 soluce = [] self.Jeu().Initialiser (self.Jeu().Hauteur()) self.Jeu().Solution(soluce) self.Afficher_Jeu() print "-"*40 for mouvement in soluce: self.Jeu().Deplacer_Disque(mouvement[0], mouvement[1]) self.nb_mouvements += 1 self.Afficher_Jeu() print "-"*40 void Solution() { std::string ligne(40, -); std::cout << ligne << std::endl; std::cout << "SOLUTION\n";

Remarquez la forme particulire de la boucle for utilise ici : la variable de boucle mouvement va automatiquement et successivement prendre la valeur de chacun des lments du tableau soluce, sans quil soit ncessaire de passer par un intervalle (range). chaque "tour de boucle", mouvement prendra donc la valeur de lun des tuples qui auront t stocks dans le tableau soluce. Les lments dun tuple sont obtenus comme ceux dun tableau, en donnant leur indice (le premier tant 0) entre crochets

C++
std::cout << ligne << std::endl; std::vector<std::pair<int, int> >soluce; this->Jeu().Solution(soluce); this->Jeu().Initialiser(this->Jeu().Hauteur()); this->Afficher_Jeu(); std::cout << ligne << std::endl;

138

Initiation la programmation avec Python et C++

C++
for(int ind_mouvement = 0; ind_mouvement < soluce.size(); ++ind_mouvement) { this->Jeu().Deplacer_Disque(soluce [ind_mouvement].first, soluce[ind_mouvement].second); this->nb_mouvements += 1; this->Afficher_Jeu(); std::cout << ligne << std::endl; } }

En C++, les deux valeurs contenues dans une instance de std::pair<> peuvent tre rcupres par les membres first (pour la premire) et second (pour la seconde). Cest lun des trs rares exemples o des donnes membres sont directement accessibles.

Enn, une mthode Solution() est galement ajoute dans la classe Hanoi, dont le rle se limite invoquer la fonction globale Hanoi_Solution() en lui donnant notamment le nombre total de disques :
Python
def Solution(self, soluce): Hanoi_Solution(self.hauteur_max, 0, 1, 2, soluce)

C++
void Solution(std::vector<std::pair<int,int> >& soluce) const { Hanoi_Solution(this->hauteur_max, 0, 1, 2, soluce); }

Nous avons maintenant un programme complet, permettant de jouer au jeu des Tours de Hano et capable dafcher la solution du problme.

11
Spcicits propres au C++
Au sommaire de ce chapitre :

Les pointeurs Mmoire dynamique Compilation spare Fichiers de construction

140

Initiation la programmation avec Python et C++

Le chapitre suivant abordera les techniques propres la programmation graphique, an de crer des programmes permettant lutilisation de la souris pour agir sur une interface plus attrayante. Il est toutefois ncessaire de nous arrter un instant pour examiner quelques aspects propres aux langages de programmation compils, en gnral, et au langage C++, en particulier. Ces aspects auront en effet un rle majeur dans ce qui va suivre.

11.1. Les pointeurs


Imaginez que vous soyez lincarnation vivante dun programme. Vous avez connaissance dau moins une autre personne, qui serait lincarnation dune information dans lordinateur et aussi llue de votre cur. Pour joindre cette personne, vous ne disposez que du tlphone : celui-ci est comme une variable dans un programme, il vous permet davoir accs une information. Mais vous ne savez pas o se trouve physiquement cette autre personne. Vous pouvez la consulter et lui transmettre des donnes, mais il est par exemple impossible de demander un euriste de lui livrer un bouquet : vous ne connaissez pas son adresse. Ce qui peut parfois tre bien malheureux. Or, toute personne possde une adresse, une localisation, mme si elle nest pas xe. Il en va de mme dans le cas dune variable dans un programme. Le nom que vous utilisez est comme un tlphone, qui vous permet daccder une donne sans savoir quel emplacement elle se trouve en mmoire. Dans le cadre du langage Python, cette information vous est compltement masque : elle est inaccessible (du moins par des moyens simples). En C++, on dsigne par le terme de pointeur une variable qui contient la position dune autre variable en mmoire. Cette position est dsigne par le terme d adresse, que lon peut comparer une adresse postale pour une personne, ou plutt une borne kilomtrique le long dune route : en pratique, une adresse est un nombre entier, gnralement assez grand. Notez que la valeur mme de ce nombre est le plus souvent sans aucun intrt pour le programmeur, sauf dans certaines situations trs particulires que nous nous garderons dvoquer. Les pointeurs en C++ sont des variables comme les autres, donc ayant un type. Si un pointeur contient ladresse dune variable de type int, alors on dit quil est de type "pointeur sur int". On le note int*, cest--dire le nom du type dont on veut connatre les adresses des variables, suivie dune toile (astrisque). On dit alors que le type int* pointe sur des variables de type int.

Chapitre 11

Spcicits propres au C++

141

Lorsque vous disposez dune variable, vous pouvez obtenir son adresse en faisant prcder son nom par le caractre perluette :
int une_variable = 0; int* pointeur = &une_variable;

Lutilisation de ce caractre nest pas sans rappeler la notion de rfrence, que nous avons vue la n du Chapitre 6. Ce nest pas un hasard : ces deux notions sont assez proches, mais elles sutilisent dans des contextes diffrents. Naturellement, si vous tentez de prendre ladresse dune variable dun certain type pour la stocker dans un pointeur dun autre type, le compilateur vous insultera copieusement. Par exemple, lextrait suivant sera refus par un compilateur C++ :
int une_variable = 0; double* pointeur = &une_variable;

L o cela devient vraiment subtil, cest quune telle affectation est possible dans le cadre de la programmation objet : vous pouvez affecter une variable dun type pointeur ladresse dune variable dun type driv du type point. Voyez cet extrait de code :
class Mere { }; class Derivee: public Mere { }; /* ... */ Derivee une_instance; Mere* pointeur = &une_instance;

Cela fonctionne parfaitement. Cest mme une technique trs largement utilise pour la mise en uvre du polymorphisme, que nous avons explor au chapitre prcdent. Nous verrons trs bientt des exemples concrets dutilisation. Prcisons ds maintenant que lutilisation des pointeurs est une source innie de dysfonctionnements dans un programme. Au cours de vos expriences, vous remarquerez quil est bien trop ais de mettre un dsordre infernal au point de vous retrouver, dune part, avec des pointeurs qui pointent sur "rien" ou sur une donne errone, et dautre part, dans lincapacit de comprendre ce qui se passe, votre propre code devenant un labyrinthe digne de Ddale.

142

Initiation la programmation avec Python et C++

11.2. Mmoire dynamique


Vous pouvez alors vous demander quel est lintrt de toute cette histoire de pointeurs, puisque cela semble tre un nid problmes. Ce qui nest pas faux. Rappelons que le langage C++ tire son hritage du langage C, qui est un langage dit de "bas niveau", cest--dire relativement proche de llectronique de lordinateur. La majorit des systmes dexploitation, que cela soit Windows, Linux ou Mac OS, sont crits principalement en langage C. Dans ces contextes particuliers, lutilisation des pointeurs est une obligation pour avoir la matrise ncessaire de lorganisation des lments en mmoire. Il serait ainsi impossible dcrire un systme dexploitation en Python, ce langage occultant compltement la notion de pointeurs. Plus communment, les pointeurs permettent deffectuer ce quon appelle une allocation dynamique de mmoire. Lorsque vous crez une variable dans un programme ou une fonction, elle reoit un emplacement particulier dans la mmoire de lordinateur pour y contenir les donnes quelle reprsente : on dit que de la mmoire est alloue la variable. Cette allocation est automatique, ds la dclaration de la variable (on parle dailleurs de variables automatiques). Dans le cas dune variable dclare dans une fonction, nous lavons vu, elle "disparat" ds que la fonction se termine : techniquement, cela se traduit par la libration de la mmoire alloue, qui devient ds lors utilisable par une autre variable, voire la mme variable mais lors dun autre appel de la fonction. Dans bien des situations, toutefois, il est souhaitable de matriser plus nement la manire dont la mmoire est alloue et libre. Par exemple, si on ne sait pas lavance (lors de lcriture du programme) de quelle quantit on a besoin ou si on souhaite crer une variable dont la dure de vie sera suprieure celle de la fonction dans laquelle elle a t cre. Voyez lextrait suivant :
int* pointeur = 0; pointeur = new int;

On commence par dclarer une variable pointeur, donc destine contenir ladresse dune zone de mmoire. ce moment-l, aucun espace nest effectivement rserv en mmoire : le pointeur est dit invalide, sa valeur nayant pas de sens car arbitraire. An de clarier les choses, on lui attribue ds la dclaration la valeur 0 (zro), valeur universelle reprsentant une adresse mmoire invalide. La seconde ligne rserve effectivement un emplacement dans la mmoire de lordinateur, ici destin contenir un nombre entier, par le mot cl new (techniquement, il sagit en fait dun oprateur, comme le sont +, /, etc.). Lcriture new int "produit"

Chapitre 11

Spcicits propres au C++

143

une adresse mmoire, que nous mmorisons soigneusement dans la variable pralablement dclare : la variable pointeur contient ds lors ladresse dune zone mmoire valide, destine recevoir la valeur dun nombre entier. Mais ce qui nous intresse rellement, cest justement de placer une valeur dans cette zone mmoire. partir dun pointeur, on peut accder linformation qui se trouve derrire en le faisant prcder dune toile, par exemple :
*pointeur = 1; int entier = 2 * *pointeur;

Remarquez que dans la seconde ligne, les espaces ont une certaine importance pour la lisibilit, bien quils ne soient pas rellement ncessaires. Ces deux lignes sont parfaitement quivalentes la prcdente :
int entier = 2**pointeur; int entier = 2*(*pointeur);

vous de trouver la notation qui vous semble la plus lisible. Par lutilisation dune allocation dynamique laide de new, on obtient une zone mmoire qui va perdurer au-del de la fonction dans laquelle elle a eu lieu, jusqu la n du programme. moins, naturellement, quon dcide de librer cette zone mmoire explicitement :
delete pointeur;

Le mot cl delete (qui est galement un oprateur, en ralit) a pour effet de librer une zone mmoire pralablement rserve par new, an quelle puisse tre subsquemment utilise par dautres variables ou dautres allocations dynamiques. Il convient de prendre garde deux points importants :

La valeur de la variable pointeur (non pas le contenu de la zone mmoire pointe) nest en rien modie par delete : vous navez aucun moyen immdiat de savoir si une zone rserve a t libre ou non. Le contenu de la zone mmoire pointe, par contre, peut ds cet instant tre modifi : vous navez aucune garantie que ce contenu a encore un sens ds que loprateur delete a t appliqu.

Lorsque vous utilisez delete, une bonne habitude est de systmatiquement placer une valeur invalide dans la variable pointeur :
delete pointeur; pointer = 0;

144

Initiation la programmation avec Python et C++

Ainsi, vous pourrez tester par la suite si un pointeur a t libr ou non, et donc savoir si les donnes sur lesquelles il pointe sont cohrentes ou non. Vous voyez quil y a dans ces mcanismes de nombreuses sources de soucis. Un problme assez rpandu, et gnralement difcile corriger, est ce quon appelle la fuite de mmoire (memory leak en anglais). Cela se produit lorsquun programme rserve frquemment de la mmoire, avec new, mais nglige de librer les zones rserves lorsquelles ne sont plus utiles. Au l du temps, le programme consomme de plus en plus de mmoire alors quil nen a pas besoin. Jusquau moment o le systme nest plus capable de fournir davantage de mmoire, ce qui rsulte au mieux en un plantage du programme, au pire en un plantage du systme. Prenez donc toujours bien soin de vrier que toute mmoire que vous rservez est symtriquement libre ds quelle nest plus utile.

11.3. Compilation spare


mesure que vos programmes deviendront plus complexes, vous vous trouverez confront au problme de la taille des chiers de code source : vous aurez de plus en plus de difcult vous y retrouver dans un chier contenant plusieurs milliers de lignes. Il deviendra bientt ncessaire de "ventiler" le code source du programme dans plusieurs chiers, le mieux tant un chier par type objet (classe). Si un tel morcellement du code source est assez naturel et simple mettre en uvre en Python, il en va tout autrement en C++. Pour un langage compil, la distribution du code source en plusieurs chiers ncessite en gnral deffectuer une compilation spare suivie dune dition des liens. La compilation spare consiste compiler chaque chier de code source indpendamment du reste. Au sens strict, la compilation ne produit pas de chier excutable : le code source est transform en un chier dit "objet", dans lequel les instructions contenues dans le chier source ont t pour la plupart transformes en codes binaires comprhensibles par le processeur. Ce chier nest toutefois pas excutable en tant que tel, car il ne contient ni les instructions prsentes dans les autres chiers sources, ni les informations "administratives" ncessaires son excution par le systme dexploitation. lissue de cette compilation, les diffrents chiers objet sont assembls en une seule entit cohrente, chacun tant li lensemble. Cette phase est nomme dition des liens, car cest ce moment que les relations entre chacun des lments sont effectivement tablies et vries.

Chapitre 11

Spcicits propres au C++

145

En ralit, ces deux tapes sont prsentes depuis notre premier programme. Jusquici, elles ont t traites ensemble, car les exemples taient sufsamment simples. Nous allons maintenant nous attaquer des activits plus sophistiques, aussi la connaissance de ce mcanisme est-elle ncessaire. Reprenons la salutation, le programme qui demande le nom de lutilisateur avant de lafcher. On pourrait sparer ce programme pourtant trivial en quatre chiers diffrents :

un chier contenant une fonction pour demander le nom ; un chier contenant une fonction pour saluer un nom reu en paramtre ; un chier contenant la fonction principale main(), ncessaire tout programme C++, qui utiliserait les deux fonctions prcdentes ; un chier den-tte qui raliserait la liaison entre les trois prcdents.

Un chier den-tte, en langages C/C++, est un chier dextension .h (ou .hpp, parfois .hxx) ne contenant en gnral que des dclarations, cest--dire ne faisant quannoncer quun certain objet existe. Dans notre cas, on pourrait crer un chier salutation.h qui contiendrait ceci :
#ifndef SALUTATION_H__ #define SALUTATION_H__ 1 #include <string> void Demander_Nom(std::string& nom); void Saluer(const std::string& nom); #endif // SALUTATION_H__

Ce chier ne contient que des dclarations de fonctions, pas de vritable code. Remarquez au dbut et la n du chier les lignes commenant par #ifndef, #define et #endif : il sagit de directives de compilation, destines viter que le chier soit inclus plus dune fois au sein dune implmentation. La raison de cela est explicite un peu plus loin. Limplmentation de la fonction Demander_Nom() pourrait se situer dans un chier demander_nom.cpp ressemblant ceci :
#include <iostream> #include "salutation.h" void Demander_Nom(std::string& nom) { std::cout << "Quel est votre nom? "; std::cin >> nom; }

146

Initiation la programmation avec Python et C++

Remarquez la deuxime ligne : on fait rfrence notre chier den-tte. En fait, tout se passe comme si le chier salutation.h tait intgralement recopi lintrieur du chier demander_nom.cpp . Du point de vue du compilateur, il devient alors ceci :
#include <iostream> #ifndef SALUTATION_H__ #define SALUTATION_H__ 1 #include <string> void Demander_Nom(std::string& void Saluer(const std::string& #endif // SALUTATION_H__ void Demander_Nom(std::string& { std::cout << "Quel est votre std::cin >> nom; }

nom); nom); nom) nom? ";

Cest l le vritable sens de cette directive #include que nous utilisons depuis le dbut : elle signie, littralement, "recopier cet endroit le contenu du chier auquel il est fait rfrence". Vous laurez compris, ce qui est vrai pour notre chier den-tte salutation.h lest galement pour les chiers iostream et string : ce sont bel et bien, eux aussi, des chiers den-tte, situs un emplacement prdni par le compilateur lui-mme. Les deux lignes #include restantes seront donc remplaces par les contenus de ces chiers. Et ainsi de suite jusqu ce quil nen reste plus une seule. Cette opration consistant remplacer les chiers mentionns dans des directives #include par leur contenu est ce que lon appelle ltape de prtraitement (preprocessing en anglais). Ce nest quaprs cette tape que la compilation va effectivement pouvoir avoir lieu. Les directives #ifndef, #define et #endif sont galement interprtes lors de cette tape. Ces trois directives, justement, permettent dviter une situation qui pourrait tre dlicate : celle o un chier den-tte est inclus (par #include) dans un autre, luimme inclus dans un programme avec le premier. On pourrait ainsi aboutir une situation o le contenu dun mme chier den-tte est inclus plusieurs fois avant la compilation. Si dans certains cas, ce nest gure gnant, dans la plupart des situations cela peut aboutir des incohrences subtiles. Le fait "dencadrer" le contenu dun chier den-tte laide des directives #ifndef, #define et #endif, permet

Chapitre 11

Spcicits propres au C++

147

de dtecter si un contenu a dj t inclus ou non et donc de ne pas linclure une seconde fois. Cela est fait simplement en dnissant une sorte didentiant du chier, par convention son nom en majuscules suivi de deux caractres de soulignement, comme cest fait ici. Si vous tes curieux, vous pouvez consulter le rsultat intermdiaire de cette tape, qui normalement ne vous est jamais prsent :
> g++ -E demander_nom.cpp -o demander_nom.e

Le rsultat est stock dans le chier demander_nom.e, lequel est, vous le constatez, considrablement plus volumineux que votre chier de dpart. Vous avez en fait sous les yeux ce qui sera effectivement compil. La compilation, justement, est effectue en passant une option spciale au compilateur :
> g++ -c demander_nom.cpp -o demander_nom.o

Loption -c indique que lon souhaite effectuer seulement la compilation (et le prtraitement, naturellement) et rien dautre. Le nom du chier objet rsultant utilise lextension .o, par convention (parfois .obj si on utilise dautres compilateurs). Nous crons et compilons de la mme manire limplmentation de la fonction Saluer(), dans saluer.cpp :
#include <iostream> #include "salutation.h" void Saluer(const std::string& nom) { std::cout << "Bonjour, " << nom << std::endl; } > g++ -c saluer.cpp -o saluer.o

Et enn la fonction principale, dans salutation.cpp :


#include "salutation.h" int main(int argc, char* argv[]) { std::string nom; Demander_Nom(nom);

148

Initiation la programmation avec Python et C++

Saluer(nom); return 0; } > g++ -o salutation.cpp -o salutation.o

Cette fonction utilise les deux fonctions Demander_Nom() et Saluer() : cela nest possible que parce quelles ont t dclares (annonces) dans le chier den-tte salutation.h, lequel est justement inclus ici. Nous disposons donc maintenant de trois chiers objet. Pour obtenir enn un chier excutable, nous pouvons effectuer ldition des liens ainsi :
> g++ demander_nom.o saluer.o salutation.o -o salutation

Finalement, les tapes ncessaires la construction dun chier excutable peuvent tre schmatises comme la Figure 11.1.
source demander_nom.cpp source saluer.cpp source salutation.cpp

prtraitement g++ E (le rsulat nest conserv quen mmoire)

prtraitement g++ E (le rsulat nest conserv quen mmoire)

prtraitement g++ E (le rsulat nest conserv quen mmoire)

compilation g++ c

compilation g++ c

compilation g++ c

fichier objet demander_nom.o

fichier objet saluer.o

fichier objet salutation.o

dition des liens g++

fichier excutable salutation (.exe)

Figure 11.1 tapes pour la construction dun chier excutable.

Chapitre 11

Spcicits propres au C++

149

Il ne devrait chapper personne quil sagit l dun processus relativement laborieux, pouvant tre entach derreurs chacune des tapes. Et encore ne sagit-il que dun exemple extrmement modeste. Heureusement, des solutions existent pour nous simplier la vie, que nous allons examiner ds maintenant.

11.4. Fichiers de construction


Nous avons vu quun programme pouvait tre dcompos en plusieurs chiers. Cest mme presque une rgle dans le cas de logiciels vritablement complexes : le code source de votre traitement de texte favori se rpartit en plusieurs milliers de chiers ! Compiler chacun deux " la main", comme nous lavons fait prcdemment, nest tout simplement pas envisageable. Dans ces cas-l, on fait appel ce que lon appelle un programme de construction (aussi appel moteur de production). Il sagit dun logiciel spcialement ddi la tche consistant produire des logiciels. La structure du programme quon souhaite construire est dcrite dans un chier de construction , ainsi que les ventuelles rgles particulires devant tre appliques. partir de ce chier de construction, le moteur de production va prendre en charge automatiquement la compilation des diffrents chiers source puis ldition des liens nale pour obtenir un logiciel excutable. Le plus rpandu des moteurs de production est aujourdhui lutilitaire GNU make. Il est normalement presque toujours disponible sur tous les systmes de type Unix (ce qui inclut GNU/Linux et Mac OS). Une version accompagne lensemble MinGW sous Windows. Par convention, le chier de construction quutilisera lutilitaire make se nomme simplement Makefile (sans extension). Si on reprend lexemple de la section prcdente, nous pourrions dcrire ainsi notre programme de salutation dans un chier de construction nomm Makefile :
CXX = g++ TARGET = salutation OBJECTS = demander_nom.o saluer.o salutation.o %.o: %.cpp $(CXX) -c $^ -o $@ all: $(OBJECTS) $(CXX) -o $(TARGET) $(OBJECTS)

150

Initiation la programmation avec Python et C++

Nous nallons pas rentrer dans le dtail des rgles dcriture des chiers de construction, qui peuvent tre assez complexes. Lexemple prcdent donne toutefois une ide gnrale. Pour commencer, les ches que vous voyez reprsentent un caractre de tabulation : cest absolument obligatoire, impossible dutiliser simplement des espaces. Concernant le contenu, on commence par renseigner quelques variables usuelles (CXX pour le compilateur utiliser, TARGET pour le rsultat nal que lon souhaite obtenir, OBJECTS pour la liste des chiers intermdiaires), puis on dnit quelques rgles sous la forme :
cible: dpendances commande excuter

Par "dpendances" on entend ici des chiers partir desquels la cible va tre gnre, en excutant la commande indique. La cible nomme all est un peu particulire : cest la cible par dfaut si aucune nest prcise lors de lutilisation de make. Si vous placez ce chier Makefile dans le mme rpertoire que celui contenant les chiers de notre programme de salutation, lutilisation de make est facile. Il suft dexcuter la commande :
$ make

Ou si vous tes sous Windows :


> mingw32-make

Vous devriez alors obtenir quelque chose comme ceci :


g++ -c demander_nom.cpp -o demander_nom.o g++ -c saluer.cpp -o saluer.o g++ -c salutation.cpp -o salutation.o g++ -o salutation demander_nom.o saluer.o salutation.o

Vous pouvez constater que chacun des chiers est compil indpendamment, puis lexcutable nal est cr par ldition des liens. On retrouve des commandes analogues celles que nous avions utilises la section prcdente. Faites lexprience suivante : relancer immdiatement la commande make. Normalement, presque rien ne devrait se passer. Cest l un autre intrt dutiliser un outil de construction : seuls les chiers devant tre recompils le sont effectivement. Modiez lun des chiers source, simplement en ajoutant un espace ou une ligne vide, puis relancez make. Seul le chier modi sera recompil.

Chapitre 11

Spcicits propres au C++

151

Nous verrons bientt que nous naurons mme pas crire nous-mme de chier de construction. En effet, ils peuvent tre assez dlicats mettre au point. Ne vous laissez pas tromper par lexemple donn ici, il est dune simplicit caricaturale (et, en fait, pas tout fait correct, par souci de comprhension). Des outils existent pour crer ces chiers de construction, partir dautres chiers encore plus simples.

12
Ouverture des fentres : programmation graphique
Au sommaire de ce chapitre :

La bibliothque Qt Installation des bibliothques Qt et PyQt Le retour du bonjour Un widget personnalis et boutonn Hano graphique

154

Initiation la programmation avec Python et C++

Tous nos programmes taient jusqu maintenant des programmes en mode texte, cest--dire quils ne font quafcher et recevoir des caractres, rien dautre. Ce qui na rien dinfamant. Linterface graphique, qui nous semble aujourdhui si commune, na t imagine dans les laboratoires de lentreprise Xerox quaux alentours de 1973, avant dtre largement dveloppe et diffuse par les entreprises Apple (ordinateurs Macintosh, 1984), Sun (stations de travail graphiques, 1983), SGI (afchage 3D, 1984) et Microsoft (environnement Windows 1.0 en 1985 et 3.0 en 1990). Aujourdhui, mme les systmes les plus rcents que sont Mac OS X, Windows Vista ou les nombreux environnements graphiques disponibles pour systmes Linux (et Unix), sinscrivent dans la ligne des concepts gs de plus de trente-cinq ans. Tout au long de cette priode, de trs nombreux programmes de premier ordre, comme les applications comptables, sont rests longtemps cantonns un afchage base de caractres. Mais aujourdhui, il parat naturel pour la majorit des utilisateurs de disposer du confort dune interface graphique, manipulable laide dune souris. Il ny a dailleurs gure dautre solution lorsque la nature mme des informations quon souhai te afcher est fondamentalement graphique. Aussi allons-nous dsormais dcouvrir quelques-unes des possibilits qui soffrent nous. Vous pourrez constater que, si les programmes gagnent alors en convivialit et attractivit, ils gagnent aussi en complexit. Il sagit l dune rgle qui semble safrmer : plus un logiciel est agrable et ais dutilisation, plus il est complexe dans sa conception et son criture.

12.1. La bibliothque Qt
Un langage de programmation, en lui-mme, noffre gnralement pas de possibilits pour raliser un programme graphique ( quelques rares exceptions prs). Le graphisme nest atteint le plus souvent quen utilisant des bibliothques doutils, adaptes un langage donn et un environnement graphique donn. En effet, la manire de programmer graphiquement diffre assez considrablement selon que lon se trouve sous un systme Linux, Windows ou Mac OS, chacun deux pouvant ventuellement proposer diffrents contextes (X-Window ou framebuffer sur Linux, par exemple). Aussi, allons-nous devoir utiliser une bibliothque spcialise. Le choix a t fait de la bibliothque Qt (prononcez "kiouti"), cre et dveloppe depuis de nombreuses

Chapitre 12

Ouverture des fentres : programmation graphique

155

annes par la socit norvgienne TrollTech (http://www.trolltech.com, rcemment passe sous le contrle de Nokia). Plusieurs raisons ont guid ce choix :

Il sagit dune bibliothque en langage C++ disponible sur tous les systmes dexploitation majeurs. Elle propose une myriade doutils divers et puissants, tout en conservant une certaine cohrence et une logique interne, qui facilite son apprentissage. La documentation qui laccompagne est dune grande qualit (quoique uniquement en anglais), aspect essentiel pour pouvoir raliser un projet sans passer des nuits chercher une information. Elle est employe quotidiennement par des millions dutilisateurs, ne serait-ce quau travers de lenvironnement graphique KDE couramment utilis sur systmes Linux, dont elle constitue les fondations. Une version libre (au sens du logiciel libre) est disponible gratuitement, de mme que plusieurs adaptations dans dautres langages que le C++. Il est ainsi possible de lutiliser en langage Python, au travers de ladaptation PyQt, elle-mme de grande qualit.

Au-del de toutes ces bonnes raisons, ne perdez pas de vue que le choix dune bibliothque, comme celui dun langage de programmation, obit galement des motivations subjectives. Mme si cela peut vous paratre encore un peu abstrait, vous constaterez que lesthtique dune bibliothque a galement son importance. Gardez lesprit que cet ouvrage nest en aucun cas un manuel de la bibliothque Qt, dont la documentation complte reprsente plusieurs fois le volume que vous tenez entre les mains. lissue de linstallation des bibliothques, vous trouverez cette documentation dans un rpertoire nomm doc lendroit o Qt a t installe. Nous ne dcrirons pas tous les outils utiliss dans le moindre dtail, aussi nhsitez pas aller consulter cette documentation. La lecture est une part non ngligeable de la programmation !

12.2. Installation des bibliothques Qt et PyQt


Au moment o ces lignes sont crites, la version libre 4.3.3 est la dernire de Qt. Vous trouverez son code source sur le site FTP ftp://ftp.trolltech.com/qt/source , pour toutes les plates-formes.

156

Initiation la programmation avec Python et C++

La bibliothque PyQt, ladaptation en Python de Qt, ncessite linstallation dun petit utilitaire pour pouvoir tre compile. Nomm SIP, vous le trouverez partir de la page http://www.riverbankcomputing.co.uk/sip/download.php . PyQt est, quant elle, disponible partir de la page http://www.riverbankcomputing.co.uk/pyqt/ download.php.

12.2.1. Windows
Tlchargez le chier qt-win-opensource-src-4.3.3.zip partir du site FTP indiqu prcdemment, puis dcompressez son contenu dans le rpertoire C:\Programmes. Renommez le rpertoire nouvellement cr en qt433, an dconomiser quelques touches de clavier par la suite. partir dune ligne de commande, placez-vous dans ce rpertoire, puis excutez la commande suivante :
C:\Programmes\qt433> configure -platform win32-g++ -release -qt-sql-sqlite -qt-zlib -qt-libpng -qt-libmng -qt-libtiff -qt-libjpeg -no-dsp -no-vcproj -no-qt3support -no-openssl -no-qdbus

Cela va paramtrer Qt pour votre systme et prparer sa compilation, que vous lancerez laide de la commande :
C:\Programmes\qt433> mingw32-make

Puis, allez prparer quelques cafs. En plus de la bibliothque elle-mme, dune taille assez considrable, de nombreux outils annexes et des exemples seront galement compils. Le processus complet peut demander plusieurs heures, selon la puissance de votre machine. Cela fait, modiez comme suit le chier de commande prog.bat que nous avions cr au premier chapitre, qui permet dobtenir un environnement de dveloppement adquat (attention, la ligne commenant par set PATH est bien une seule et unique ligne !) :
set QTDIR=C:\Programmes\qt433 set QMAKESPEC=win32-g++ set PATH=%QTDIR%\bin;C:\Python25;C:\MinGW\bin;C:\MinGW\libexec\gcc\mingw32\3.4.5;%PATH% C: cd \Programmes cmd.exe

Fermez la ligne de commande puis relancez-la, an de bncier du nouvel environnement. Essayez dexcuter la commande qtdemo an de voir quelques dmonstrations et exemples de Qt.

Chapitre 12

Ouverture des fentres : programmation graphique

157

Rcuprez maintenant le chier sip-4.7.4.zip partir de la page http:// www.riverbankcomputing.co.uk/sip/download.php . Dcompactez ce chier, nommez le rpertoire en sip474, partir duquel vous excutez les commandes :
C:\Programmes\sip474> python configure.py -p win32-g++ C:\Programmes\sip474> mingw32-make C:\Programmes\sip474> mingw32-make install

Nous pouvons maintenant installer PyQt. Rcuprez le chier PyQt-win-gpl4.3.3.zip sur la page http://www.riverbankcomputing.co.uk/pyqt/download.php. Comme prcdemment, dcompactez-le dans C:\Programmes, puis nommez le rpertoire nouveau pyqt433. partir de ce rpertoire, excutez simplement :
C:\Programmes\pyqt433> python configure.py C:\Programmes\pyqt433> mingw32-make C:\Programmes\pyqt433> mingw32-make install

Vous tes maintenant prt dvelopper des logiciels graphiques aussi bien en C++ quen Python.

12.2.2. Linux
Si vous utilisez une distribution majeure rcente (datant de lanne 2006), il est certain que les bibliothques Qt et PyQt sont disponibles parmi les paquetages quelle propose. En particulier, cherchez les paquetages dont les noms ressemblent ceux-ci :

pour Qt, libqt4-dev, libqt4-core, libqt4-gui ; pour PyQt, python-qt4, pyqt4-dev-tools, python-qt4-gl, python-qt4-doc.

Installez tous ces paquetages, ainsi que ceux desquels ils dpendent.

12.2.3. Mac OS X
Tlchargez le chier qt-mac-opensource-src-4.3.3.tar.gz partir du site FTP de TrollTech, puis placez-le dans votre rpertoire personnel. partir de la ligne de commande, dcompressez-le puis congurez Qt comme suit :
$ tar zxf qt-mac-opensource-src-4.3.3.tar.gz $ cd qt-mac-opensource-src-4.3.3 $ ./configure -prefix $HOME/opt/qt433 -qt-sql-sqlite -no-qt3support -qt-zlib -qt-libtiff -qt-libmng -qt-libjpeg -no-framework -no-dwarf2

158

Initiation la programmation avec Python et C++

Enn, lancez la compilation de Qt, non sans avoir prvu quelques cafs ou un divertissement quelconque, car cette opration peut prendre un certain temps :
$ make -j2 && make install

Il est ensuite ncessaire de modier votre environnement, an que la bibliothque Qt soit dment reconnue et localise. Crez pour cela un chier nomm qt_env.sh dans votre rpertoire Programmes, contenant les lignes suivantes :
$ export QTDIR=$HOME/opt/qt433 $ export LD_LIBRARY_PATH=$QTDIR/lib:$LD_LIBRARY_PATH $ export PATH=$QTDIR/bin:$PATH

Dsormais, chaque fois que vous lancerez une ligne de commande, il vous sufra dinvoquer ce chier an de mettre jour lenvironnement :
$ source Programmes/qt_env.sh

Cela fait, vous pouvez vrier que votre installation fonctionne correctement en excutant le programme de dmonstration de Qt :
$ qtdemo

Tlchargez maintenant le chier sip-4.7.4.tar.gz sur le site http://www.riverbankcomputing.co.uk/sip/download.php . Dcompactez-le puis, partir du rpertoire nouvellement cr, excutez les commandes :
$ python configure.py $ make $ make install

Il ne nous reste maintenant plus qu installer PyQt. Rcuprez le chier PyQt-macgpl-4.3.3.zip sur la page http://www.riverbankcomputing.co.uk/pyqt/ download.php. Comme prcdemment, dcompactez-le puis, partir du rpertoire cr, excutez simplement :
$ python configure.py $ make $ make install

Vous tes maintenant prt dvelopper des logiciels graphiques aussi bien en C++ quen Python.

Chapitre 12

Ouverture des fentres : programmation graphique

159

12.3. Le retour du bonjour


Il est toujours prfrable de commencer la dcouverte dune nouvelle technique ou dun nouvel outil par des ralisations simples. Nous avons entam notre initiation la programmation en saluant le monde, faisons de mme pour dcouvrir la programmation graphique ! Voici le programme.
Python
import sys from PyQt4 import QtCore, QtGui appli = QtGui.QApplication(sys.argv) bijour = QtGui.QLabel("Bonjour, Monde!") bijour.show() sys.exit(appli.exec_())

C++
#include <Qapplication> #include <Qlabel> int main(int argc, char* argv[]) { QApplication appli(argc, argv); QLabel bijour("Bonjour, Monde!"); bijour.show(); return appli.exec(); }

Vous pouvez constater que, si ces programmes demeurent extrmement courts, ils sont nanmoins beaucoup plus longs que leurs quivalents purement textuels. Examinons un instant leur contenu. Pour commencer, tout programme graphique sappuyant sur Qt doit contenir une instance et une seule de la classe QApplication (ici la variable appli). Celle-ci reprsente en quelque sorte le cadre gnral dans lequel le programme va sexcuter. Elle maintient galement la boucle dvnements dont il a dj t question. Du point de vue de Python, cette classe fait partie du module QtGui. Le terme module pour Python est plus ou moins lquivalent du terme bibliothque : un regroupement en une seule entit de fonctions et de classes utilisables par un autre programme . La premire ligne du programme prcdent, import sys, signale que nous allons utiliser le contenu du module nomm sys. La deuxime rend disponibles les contenus des modules QtCore et QtGui, qui sont, en fait, des sous-modules du module gnral PyQt4. Pour le C++, il est ncessaire dajouter la ligne #include <QApplication> pour pouvoir lutiliser. Notez bien quil est ncessaire et indispensable de crer une instance de QApplication avant dutiliser nimporte laquelle des possibilits graphiques de

160

Initiation la programmation avec Python et C++

Qt : si vous ne le faites pas, votre programme plantera irrmdiablement avant mme dafcher la moindre fentre. Ensuite, nous crons un label (variable bijour), cest--dire un lment graphique dont le seul objet est dafcher un texte, sur lequel lutilisateur na pas de possibilit daction directe. Tout lment textuel dune interface graphique doit ainsi tre contenu dans un objet graphique, dune manire ou dune autre. Ce qui tranche radicalement avec les programmes que nous avons raliss jusquici, o les chanes de caractres taient simplement afches. Ici, ce nest pas "vous" qui afchez, cest lobjet contenant votre chane de caractres. On demande ensuite cet lment graphique, que lon appelle communment un widget (contraction des mots window gadget, en franais gadget de fentre) ou un contrle, de justement safcher. leur cration, les widgets sont normalement masqus, il faut donc demander explicitement leur apparition lcran : cest le rle de la mthode show() (montrer en anglais). Enn, on dmarre la fameuse boucle dvnements au moyen de la mthode exec() de la classe QApplication. Cest elle qui va recevoir tous les vnements et les transmettre aux lments appropris du programme. Par exemple, si une fentre vient occulter celle de votre programme, celui-ci recevra un vnement linformant quil est masqu. Sil redevient visible, un nouvel vnement sera transmis, lequel sera dirig vers les diffrents composants pour leur demander de se redessiner lcran. Nous en avons presque termin avec notre premier programme graphique. La version en Python doit normalement sexcuter directement, simplement en invoquant la commande :
$ python bonjour_gui.py

Par contre, la version C++ doit tre compile, ce qui peut devenir rapidement assez complexe pour un programme graphique utilisant une bibliothque aussi sophistique que Qt (cela est vrai pour la plupart des bibliothques graphiques). Juste pour satisfaire votre curiosit, voici quoi pourrait ressembler la compilation de ce programme tout simple (exemple sur systme Windows) :
> g++ -c -O2 -frtti -fexceptions -mthreads -Wall -DUNICODE DQT_LARGEFILE_SUPPORT -DQT_DLL -DQT_NO_DEBUG -DQT_GUI_LIB -DQT_CORE_LIB DQT_THREAD_SUPPORT -DQT_NEEDS_QMAIN -I"qt433\include\QtCore" I"qt433\include\QtCore" -I"qt433\include\QtGui" -I"qt433\include\QtGui" -

Chapitre 12

Ouverture des fentres : programmation graphique

161

I"qt433\include" -I"c:\Programmes\qt433\include\ActiveQt" -I".moc" -I"." I"qt433\mkspecs\win32-g++" -o .objs\bonjour_gui.o bonjour_gui.cpp > g++ -enable-stdcall-fixup -Wl,-enable-auto-import -Wl,-enable-runtime-pseudoreloc -Wl,-s -mthreads -Wl -Wl,-subsystem,windows -o bin\bonjour_gui.exe .objs/ bonjour_gui.o -L"c:\Programmes\qt433\lib" -lmingw32 -lqtmain -lQtGui4 -lQtCore4

Non seulement cette compilation se fait en deux tapes, mais en plus les lignes de commande excuter sont particulirement charges. Il est tout simplement hors de question de saisir des commandes aussi complexes chaque compilation. La solution consiste utiliser un chier de construction, dans le genre de ceux voqus au chapitre prcdent. Par chance, Qt fournit un moyen dobtenir aisment ce chier partir de quelques informations beaucoup plus simples contenues dans un chier, dit chier projet, dextension .pro. Voici le contenu dun tel chier, que nous nommerons ici bonjour_gui.pro, que vous pouvez saisir avec votre diteur de texte favori :
TEMPLATE = app QT += gui CONFIG += release OBJECTS_DIR = .objs MOC_DIR = .moc DESTDIR = bin SOURCES += bonjour_gui.cpp

Cela pourrait presque ressembler un programme Python dans lequel on ne ferait quaffecter des valeurs dans des variables. La premire, TEMPLATE, indique de quel type de projet il sagit. Ici, nous voulons une application, donc on donne la valeur app. Dautres valeurs sont possibles, comme lib pour crer une bibliothque. Les deux variables suivantes, QT et CONFIG, prcisent quelques paramtres pour la compilation. Ici, on demande simplement utiliser les outils graphiques de Qt ( gui) et obtenir une version optimise (release). Notez lutilisation du symbole +=, qui a pour effet dajouter des valeurs ces variables, comme si on effectuait une concatnation de chanes de caractres (ce qui est en ralit le cas, mais pour lheure vous navez pas vous en soucier). Les trois variables suivantes concernent lorganisation des rpertoires, notamment le stockage des chiers intermdiaires utiliss lors de la phase de compilation : ces chiers sont crs par le compilateur lui-mme pour ses besoins propres. On demande ce quils soient rangs dans deux sous-rpertoires (par MOC_DIR et OBJECTS_DIR), an de ne pas encombrer le rpertoire contenant les

162

Initiation la programmation avec Python et C++

chiers source. DESTDIR indique le rpertoire de destination nal, cest--dire celui qui contiendra effectivement le chier excutable rsultant de la compilation. Enn, la variable la plus importante est SOURCES : placez dans celle-ci tous les chiers de code source constituant votre programme. Une solution simple la compilation spare voque au chapitre prcdent. Une fois ce chier cr, on fait appel lutilitaire qmake fourni par Qt :
$ qmake bonjour_gui.pro

Le rsultat est un chier de construction (nomm normalement Makele), que nous pouvons utiliser immdiatement par la commande :
$ make

ou si vous tes sous systme Windows :


> mingw32-make

lissue de quoi, vous devriez obtenir un chier excutable nomm bonjour_gui (avec lextension .exe sur Windows) dans un sous-rpertoire nomm bin, comme nous lavons demand dans le chier projet. Le nom de ce chier excutable est dduit du nom du chier projet, moins que vous ne layez prcis en affectant une valeur la variable TARGET. Mais inutile de compliquer les choses.
Figure 12.1 Premier programme graphique.

La Figure 12.1 montre quoi devrait ressembler ce premier programme graphique sur systme Windows. Si vous tes un peu dpit par son aspect lmentaire, considrez le chemin parcouru depuis le dbut de cet ouvrage

12.4. Un widget personnalis et boutonn


Naturellement, dans limmense majorit des cas, votre programme ne saurait se contenter dun simple label afchant un texte statique. Il devra offrir un minimum dinteractions lutilisateur et adapter son afchage. Reprenons lexemple de la salutation. Nous nous proposons maintenant de raliser un programme peine moins minimaliste (voir Figure 12.2). Deux boutons sont offerts. Le premier afche au

Chapitre 12

Ouverture des fentres : programmation graphique

163

dpart simplement "Bonjour !" ; lorsque lutilisateur clique dessus, le programme lui demande son nom puis lafche dans le bouton. Le deuxime permet simplement de quitter le programme.
Figure 12.2 Salutations la souris.

Chacun de ces boutons est un widget devant ragir une action de lutilisateur. Mais, en fait, ce sont au total trois widgets qui seront crs et afchs : les deux boutons, plus un widget les "contenant" et assurant leur disposition harmonieuse, autant que possible. Chaque widget peut, en effet, en contenir dautres, crant ainsi une sorte dembotements des lments graphiques les uns dans les autres. Un logiciel complexe, comme un traitement de texte, peut ainsi contenir des centaines de widgets, dont une partie nexistent que pour en contenir dautres et les agencer correctement. Par exemple, les boutons dune barre doutils sont tous des widgets, "contenus" dans le widget quest la barre doutils, elle-mme "contenue" avec dautres dans le widget quest la fentre gnrale du logiciel. Il existe diffrentes techniques pour raliser cela. Celle prsente ici nest pas toujours la plus simple, mais certainement celle qui offre le plus de souplesse. Elle consiste crer un nouveau widget, en sappuyant sur la classe de base QWidget fournie par Qt. Tous les contrles graphiques de Qt hritent de cette classe y compris la classe QLabel vue prcdemment. partir de maintenant, il est vivement recommand de placer les chiers composant chacun des programmes que nous allons raliser dans un rpertoire particulier : un rpertoire par programme. ventuellement, distinguez les chiers propres Python et les chiers propres C++, an de bien vous y retrouver. Car ds prsent, nos programmes ne seront plus stocks dans un unique chier. En effet, lorsque lon ralise une application complexe dans laquelle on cre de nouveaux widgets, il est dusage de placer chacun deux dans un chier qui lui est ddi. Si cela peut paratre superu pour cet exemple, qui reste simple, cette sparation devient indispensable ds que vous vous attaquez quelque chose de plus sophistiqu. Sans elle, vous aboutirez des chiers contenant des milliers de lignes au sein desquelles il deviendra extrmement difcile de naviguer.

164

Initiation la programmation avec Python et C++

12.4.1. En Python
La situation la plus simple est, comme souvent, en langage Python. La sparation est lmentaire : un widget, un chier, plus un chier supplmentaire pour le programme principal. Voici la dclaration de notre widget personnalis, dans un chier nomm salutations.py :
from PyQt4 import QtCore, QtGui class Salutation(QtGui.QWidget): def __init__(self, parent=None): QtGui.QWidget.__init__(self, parent) self.bt_bonjour = QtGui.QPushButton("Bonjour!", self) self.bt_quitter = QtGui.QPushButton("Quitter", self) layout = QtGui.QVBoxLayout(self) layout.addWidget(self.bt_bonjour) layout.addWidget(self.bt_quitter) self.connect(self.bt_bonjour, \ QtCore.SIGNAL("clicked()"), \ self.demanderNom) self.connect(self.bt_quitter, \ QtCore.SIGNAL("clicked()"), \ self, \ QtCore.SLOT("close()")) def demanderNom(self): nom = QtGui.QInputDialog.getText(self, "Salutation", "Quel est votre nom?") self.bt_bonjour.setText(QtCore.QString("Bonjour, %1!").arg(nom[0]))

Ne vous laissez pas impressionner par lapparente longueur de ce code, cest en fait assez simple. On dclare une classe Salutation, en signalant quelle drive de la classe QWidget contenue dans le module QtGui (lui-mme issu du module PyQt4 et import par la ligne prcdente). Le constructeur de notre classe prend (au moins) un paramtre parent, dsignant le widget dans lequel il sera ventuellement contenu, la valeur par dfaut tant aucun (None). La premire action de ce constructeur est dinvoquer celui de la classe anctre : ce nest pas syntaxiquement obligatoire, mais il est plus que vivement recommand de toujours commencer par invoquer ce constructeur anctre. Ensuite, nous crons les deux boutons en instanciant deux instances de la classe QPushButton : celle-ci reprsente, en effet, un simple bouton lcran sur lequel lutilisateur peut cliquer. Le premier paramtre dinstanciation est le texte afcher dans le bouton, tandis que le second est le widget dans lequel sera contenu chaque bouton. Comme nous sommes prcisment en train de construire un widget destin contenir ces boutons, on donne donc la valeur self, linstance elle-mme de Salutation en cours de cration. Du point de vue des boutons (qui sont galement des widgets), cette

Chapitre 12

Ouverture des fentres : programmation graphique

165

valeur sera reue par le paramtre parent de leur propre constructeur. On tablit ainsi une relation parent-enfant entre les widgets composants une application (un parent pouvant avoir plusieurs enfants, mais un enfant ne pouvant avoir quun seul parent). Les trois lignes qui suivent concernent lagencement de ces widgets lcran. Il est toujours possible de positionner " la main" les lments graphiques, en donnant leurs coordonnes et leurs dimensions. Mais lexprience a montr que cette manire de faire tait loin dtre satisfaisante : les fentres et les botes de dialogue taient alors dune taille xe, impossible modier, ce qui est parfois gnant. Aussi, utilisons-nous ici un layout, cest--dire un outil fourni par Qt pour agencer automatiquement les lments graphiques. En lespce, il sagit dune instance de la classe QVBoxLayout. Cette instance (nomme layout) est attache notre widget personnalis (qui lui est donn en paramtre) : elle va alors en contrler automatiquement certaines contraintes concernant son aspect. On ajoute layout les deux boutons pralablement crs par la mthode addWidget(). Cet "agenceur" particulier a pour effet de placer les widgets quon lui donne en colonne (le VBox dans QVBoxLayout signiant bote verticale), le premier ajout tant celui du haut. Nous navons ainsi pas nous proccuper de la position ni de la taille des boutons : ces proprits, particulirement rbarbatives rgler, seront automatiquement prises en charge par layout. Nous en arrivons la partie essentielle de la cration de notre widget : paramtrer la raction du programme lorsque lutilisateur clique sur chacun des boutons. Pour cela, Qt utilise la mtaphore des signaux et des slots. Le terme anglais de slot na pas, dans ce contexte, de bon quivalent en franais ; pensez un emplacement, une cible, voire une prise (au sens prise de courant). Lide est quun objet Qt (par exemple, un bouton) peut "mettre" des signaux (par exemple, "on ma cliqu dessus"). Ces signaux peuvent ensuite tre capts par dautres objets Qt, en provoquant lexcution de mthodes particulires, les fameux slots. noter quun mme objet peut la fois mettre et recevoir des signaux. La seule vritable contrainte tant que les classes impliques doivent obligatoirement driver, directement ou non, de la classe QObject. Ce qui est le cas de la classe QWidget sur laquelle nous nous appuyons ici. Lassociation entre un signal mis par un contrle et une mthode devant y rpondre est ralise par la mthode connect(). Celle-ci demande trois ou quatre paramtres, selon la situation. Le premier est linstance mettrice du signal. Le deuxime est un objet dcrivant le signal concern, obtenu en utilisant la fonction SIGNAL() du module QtCore, laquelle attend une chane de caractres.

166

Initiation la programmation avec Python et C++

Ensuite :

Si la fonction ou mthode devant tre excute lorsque le signal est mis est une mthode que vous avez vous-mme dnie, au sein de la mme classe, le troisime et dernier paramtre est simplement le nom de cette mthode, prcd de linstance concerne, comme cest le cas ici pour la mthode demanderNom(). Sil sagit au contraire dune mthode propre Qt, cest--dire venant dune classe de base de Qt comme QWidget, alors le troisime paramtre est linstance devant recevoir ce signal et le quatrime une rfrence sur la mthode concerne, obtenue par un appel la fonction SLOT() du module QtCore.

Dans notre exemple, la mthode close() vient directement de la classe QWidget, aussi utilisons-nous lcriture quatre paramtres. Il ne nous reste plus qu utiliser cette classe graphique, ce qui est fait dans un programme principal, dans un chier nomm salutation_gui.py :
1. 2. 3. 4. 5. 6. 7. 8. import sys from PyQt4 import QtCore, QtGui import salutations app = QtGui.QApplication(sys.argv) salut = salutations.Salutation() salut.show() sys.exit(app.exec_())

Voyez comment nous signalons laccs aux outils (unique dans notre cas) que nous avons dnis dans le chier salutations.py : simplement avec la ligne import salutations (ligne 3). Nous pouvons ds lors crer une instance de la classe Salutation (ligne 6) et lancer la boucle dvnements.

12.4.2. En C++
Comme lon pouvait sy attendre, la situation est sensiblement plus complexe en C++. Non pas au niveau du code lui-mme, mais dans la faon de lorganiser. En premier lieu, nous devons dclarer la classe Salutation dans un chier den-tte, de la faon suivante :
1. 2. 3. 4. 5. 6. 7. 8. #ifndef SALUTATIONS_H__ #define SALUTATIONS_H__ #include <QtGui> class Salutation: public QWidget { Q_OBJECT public: Salutation(QWidget* parent = 0);

Chapitre 12

Ouverture des fentres : programmation graphique

167

9. public slots: 10. void demanderNom(); 11. private: 12. QPushButton* bt_bonjour; 13. QPushButton* bt_quitter; 14. }; 15. #endif // SALUTATIONS_H__

Remarquez que, pour la premire fois, ce chier ne contient aucun code. De la mme manire que nous avons, au chapitre prcdent, dclar des fonctions sans en donner le code, ici nous dcrivons simplement ce que contient la classe Salutation. Quelques remarques :

Le mot Q_OBJECT la ligne 6 est ncessaire au fonctionnement interne de Qt : ds que vous crez une classe drivant directement ou non de la classe QObject (ce qui est le cas de la classe QWidget), vous devez inscrire Q_OBJECT ainsi (cest ce quon appelle une macro, qui sera interprte par le prprocesseur). Lindication de section public slots:, ligne 9, nest pas une criture standard du langage C++ : elle est utilise par les mcanismes de Qt permettant dassocier un signal une mthode devant tre excute lorsquil est mis. Ne cherchez pas utiliser une telle criture dans un programme nutilisant pas la bibliothque Qt, cela ne compilera tout simplement pas.

Nous devons maintenant donner le code devant tre excut par les diffrentes mthodes de notre classe, dans un chier salutations.cpp :
#include "salutations.h" Salutation::Salutation(QWidget* parent): QWidget(parent), bt_bonjour(0) { this->bt_bonjour = new QPushButton("Bonjour!", this); this->bt_quitter = new QPushButton("Quitter", this); QVBoxLayout* layout = new QVBoxLayout(this); layout->addWidget(this->bt_bonjour); layout->addWidget(this->bt_quitter); this->connect(this->bt_bonjour, SIGNAL(clicked()), SLOT(demanderNom())); this->connect(this->bt_quitter, SIGNAL(clicked()), this, SLOT(close())); } void Salutation::demanderNom() { QString nom = QInputDialog::getText(this,

168

Initiation la programmation avec Python et C++

"Salutations", "Quel est votre nom?"); this->bt_bonjour->setText(QString("Bonjour, %1!").arg(nom)); }

Voyez comment ce code est donn : chacune des mthodes est rpte, son nom prcd du nom de la classe, spar par oprateur :: (quon appelle loprateur de rsolution de porte). En dehors de cette criture particulire, le code mme est tout fait analogue celui utilis dans la version en langage Python. Remarquez, toutefois, quon ne donne pas de chanes de caractres aux fonctions SIGNAL() et SLOTS() (qui sont en ralit des macrofonctions, destines au prprocesseur), mais simplement les noms des mthodes concernes. De mme, on utilise intensivement les possibilits de cration dobjets dynamiques, comme nous lavons vu au chapitre prcdent. En termes de code, il ne nous reste plus qu crire le programme principal, dans un chier salutation_gui.cpp :
#include <QApplication> #include "salutations.h" int main(int argc, char* argv[]) { QApplication appli(argc, argv); Salutation salut; salut.show(); return appli.exec(); }

Enn, on termine par la cration dun chier projet, nomm par exemple salutations.pro :
TEMPLATE = app Qt += gui CONFIG += release OBJECTS_DIR = .objs MOC_DIR = .moc DESTDIR = bin HEADERS += salutations.h SOURCES += salutations.cpp \ salutation_gui.cpp

Voyez comme on donne explicitement la liste des chiers den-tte (dans la variable HEADERS) et de code (dans la variable SOURCES) que nous avons crs. Dune manire gnrale, vous aurez autant de chiers de code que de chiers den-tte, plus un contenant le programme principal.

Chapitre 12

Ouverture des fentres : programmation graphique

169

Vous pouvez maintenant gnrer un chier de construction laide de lutilitaire qmake, construire votre programme par la commande make (ou mingw32-make sur Windows), pour enn pouvoir lexcuter, le chier excutable se trouvant dans un sous-rpertoire bin.

12.5. Hano graphique


Il est maintenant temps de raliser une version graphique de notre programme des Tours de Hano. Nous allons ltudier de faon exhaustive (du moins la partie graphique), puisque cest le premier programme aussi complexe que nous ralisons. Par la suite, seules certaines parties pertinentes du code seront examines. La Figure 12.3 montre quoi ressemblera notre version graphique des Tours de Hano.
Figure 12.3 Hano graphique.

Les rectangles reprsentent les diffrents layouts (outils dagencement) qui seront utiliss : on peut en distinguer un grand principal, qui en contient deux autres plus petits pour les boutons. Il est en effet possible dimbriquer les layouts les uns dans les autres, ce qui rend possible la cration dune interface pouvant tre particulirement complexe sans avoir trop se proccuper de la position et de la taille de ses lments.

12.5.1. En Python
Pour commencer, crez un chier nomm hanoi.py et contenant la classe Hanoi que nous avons vue prcdemment (ainsi que la fonction calculant une solution). Ne nous attardons pas sur cette partie dj connue. La partie centrale du programme est une reprsentation de ltat du jeu un moment donn. Plusieurs solutions sont possibles pour obtenir une telle reprsentation. Le choix a t fait ici de recourir tout simplement au dessin, cest--dire que nous allons afcher une image qui sera cre la demande. La cration et lafchage mme de

170

Initiation la programmation avec Python et C++

cette image seront pris en charge par un widget ddi, nomm Dessin. Voici donc le chier dessin.py, contenant la dnition de ce widget :
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. 46. 47. 48. 49. 50. # -*- coding: utf8 -*from PyQt4 import QtCore, QtGui import hanoi hauteur_disque = 10 rayon_unite = 10 rayon_aiguille = 5 espace_inter_disques = 2 espace_inter_tours = 20 marge = 8 couleur_disques = QtGui.QColor(255, 160, 96) couleur_supports = QtGui.QColor(128, 48, 16) couleur_fond = QtGui.QColor(255, 255, 255) def rayon_max_tour(taille_max): return \ (taille_max + 1) * rayon_unite + \ rayon_aiguille def hauteur_max_tour(taille_max): return \ taille_max * (hauteur_disque + espace_inter_disques) + \ 2 * (2*rayon_aiguille) class Dessin(QtGui.QWidget): def __init__(self, le_jeu, parent=None): QtGui.QWidget.__init__(self, parent) self.jeu = le_jeu largeur_max = \ 3 * (2*rayon_max_tour(self.jeu.Hauteur())) + \ 2 * espace_inter_tours + \ 2 * marge hauteur_max = \ hauteur_max_tour(self.jeu.Hauteur()) + \ 2 * marge self.setFixedSize(largeur_max, hauteur_max) self.image = QtGui.QPixmap(largeur_max, hauteur_max) self.Mettre_A_Jour() def Centre_Etage(self, tour, etage): centre_tour_x = \ marge + \ (1+2*tour)*rayon_max_tour(self.jeu.Hauteur()) + \ tour * espace_inter_tours base_y = self.height() - (marge + (2*rayon_aiguille)) etage_y = base_y - \ (espace_inter_disques+hauteur_disque)*(etage+1) + \ (hauteur_disque/2) return (centre_tour_x, etage_y) def Mettre_A_Jour(self): self.image.fill(couleur_fond);

Chapitre 12

Ouverture des fentres : programmation graphique

171

51. 52. 53. 54. 55. 56. 57. 58. 59. 60. 61. 62. 63. 64. 65. 66. 67. 68. 69. 70. 71. 72. 73. 74. 75. 76. 77. 78. 79. 80. 81. 82. 83. 84. 85. 86. 87. 88. 89. 90. 91. 92. 93. 94.

p = QtGui.QPainter(self.image); # dessin des supports p.setPen(QtGui.QColor(0, 0, 0)) p.setBrush(couleur_supports) base_y = self.height() - marge for tour in range(3): centre_tour_x = \ marge + \ (1+2*tour)*rayon_max_tour(self.jeu.Hauteur()) + \ tour * espace_inter_tours rayon = rayon_max_tour(self.jeu.Hauteur()) # aiguille p.drawRoundRect(centre_tour_x - rayon_aiguille, \ base_y - hauteur_max_tour(self.jeu.Hauteur()), \ 2 * rayon_aiguille, \ hauteur_max_tour(self.jeu.Hauteur()), \ 20, 20) # base p.drawRoundRect(centre_tour_x - rayon, \ base_y - (2*rayon_aiguille), \ 2 * rayon, \ 2 * rayon_aiguille, \ 20, 20) # dessin des disques p.setBrush(couleur_disques) for tour in range(3): nb_disques = self.jeu.Une_Tour(tour).Nombre_Disques() for etage in range(nb_disques): disque = self.jeu.Une_Tour(tour).Disque_Etage(etage) centre = self.Centre_Etage(tour, etage) self.Dessiner_Disque(disque, centre, p) p.end() def Dessiner_Disque(self, disque, centre, p): x = centre[0] - rayon_aiguille - disque*rayon_unite y = centre[1] - hauteur_disque/2 largeur = 2*(disque*rayon_unite + rayon_aiguille) p.drawRoundRect(x, y, largeur, hauteur_disque, 20, 20) def paintEvent(self, event): p = QtGui.QPainter(self) p.drawPixmap(0, 0, self.image) p.end() QtGui.QWidget.paintEvent(self, event)

Les premires lignes (4 12) ne sont destines qu "nommer" les diffrentes dimensions que nous allons utiliser par la suite. Pour la clart du programme, il est en effet prfrable dutiliser des noms que des valeurs brutes : les noms des variables devraient parler deux-mmes. Par exemple, la formule la ligne 87, qui calcule la largeur dun disque lcran, est plus parlante ainsi que si nous avions crit :
largeur = 2*(disque * 10 + 5)

172

Initiation la programmation avec Python et C++

Lunit de ces dimensions est le pixel (contraction de picture element, lment dimage), cest--dire le point le plus petit que votre cran puisse afcher. Lorsquon dit quun cran a une rsolution de 1 024 768 (par exemple), on indique par l quon peut afcher un tableau de pixels, dune largeur de 1 024 colonnes de 768 pixels, ou dune hauteur de 768 lignes de 1 024 pixels (ce qui en fait au total un peu moins dun demimillion). Plus la rsolution est leve, plus les pixels sont "petits", la taille relle dun pixel dpendant naturellement de la taille relle de lcran. Incidemment, le fait dutiliser ainsi des variables pour stocker les dimensions permet de faciliter grandement toute modication ultrieure, si vous voulez que la reprsentation du jeu soit plus grande ou plus petite. Les trois dernires variables sont destines contenir les couleurs qui seront appliques pour le dessin (voir, ci-aprs, lencadr consacr au codage des couleurs). Ces couleurs sont reprsentes par des instances de la classe QColor, une classe fournie par la bibliothque Qt et obtenue dans le module QtGui. Suivent deux fonctions "utilitaires", permettant de calculer lespace total occup par chaque tour en fonction des valeurs prcdentes et de la hauteur maximale choisie par lutilisateur. Il ne sagit que de calculs lmentaires ne prsentant pas de difcults particulires. une petite subtilit prs : par convention choisie voil bien des annes (mme des dcennies), les coordonnes de chaque pixel de lcran sont exprimes partir du coin suprieur gauche de lcran (ou de limage si on dessine dans une image). Dans une rsolution de 1 024 768, la colonne (ou labscisse, ou la coordonne x) de chaque pixel va de la gauche vers la droite, de 0 1 023 ; la ligne de chaque pixel (ou lordonne, ou la coordonne y) va de haut en bas, de 0 767. Lordonne est inverse par rapport aux reprsentations graphiques usuelles. Le pixel de coordonnes (0, 0) est celui tout en haut gauche, celui de coordonnes (1 023, 0) celui tout en haut droite, (0, 767) est tout en bas gauche et (1023, 767) dans le coin en bas droite. Cette inversion de laxe des ordonnes peut tre droutante dans un premier temps, mais vous vous y ferez vite avec un peu de pratique. Notamment, cherchez comprendre les formules utilises dans le programme.
Les couleurs de lcran : codage RGB Les couleurs innombrables que nos crans modernes sont capables dafcher sont constitues du mlange de trois couleurs de base, le rouge ( red), le vert (green) et le bleu (blue), dans cet ordre, do le nom de codage RGB (en anglais) ou RVB (en franais). Une couleur est obtenue en mlangeant ces trois couleurs fondamentales selon diverses proportions, tout comme un peintre le fait laide de son pinceau. Par exemple, le jaune est obtenu en mlangeant parts gales du rouge et du vert. Il existe de multiples faons de spcier la

Chapitre 12

Ouverture des fentres : programmation graphique

173

quantit, ou lintensit, de chacune des couleurs de base ; toutefois, la plus utilise consiste donner un nombre entier entre 0 et 255, la valeur 0 correspondant une intensit nulle (noir) et la valeur 255, une intensit maximale. Ainsi, un jaune pur serait donn par la combinaison (255, 255, 0). Une combinaison comme (128, 128, 0) produirait un jaune plus sombre, tandis que (255, 255, 128) donnerait un jaune plus clair. En effet, le mlange des trois couleurs pleine intensit, soit la combinaison (255, 255, 255), donne un blanc parfait. Au contraire, le noir absolu est donn par (0, 0, 0). Dun extrme lautre, vous disposez de 16 777 216 couleurs diffrentes, exactement. Notez quil existe dautres faons de donner la proportion de chacune de ces couleurs, par exemple par le biais dun nombre dcimal entre 0.0 et 1.0. Lexactitude de la reprsentation dpend en gnral du matriel disponible : ainsi, certaines cartes graphiques associes des crans de trs haut de gamme peuvent afcher des milliards de couleurs. Enn, dautres reprsentations des couleurs existent, comme le codage HSV (fond sur un cercle chromatique) ou le codage CMYK (mlange de cyan, magenta, jaune et noir, couramment utilis sur les imprimantes). Nous ne les aborderons pas ici.

Nous en arrivons la classe Dessin, le widget charg dafcher une image reprsentant ltat du jeu. Le constructeur de cette classe reoit en paramtre une instance de la classe Hanoi, ncessaire an de calculer les diverses dimensions et de tracer la reprsentation. La mthode setFixedSize() (ligne 34), issue de la classe QWidget, permet de xer une taille au widget : ainsi, les layouts ne pourront pas changer sa taille, ce qui permet dviter des effets disgracieux sur le dessin (il serait cependant possible dadapter le dessin un changement de taille, mais cela deviendrait vraiment compliqu). Enn, limage elle-mme est reprsente par une instance de la classe QPixmap, classe optimise spcialement pour tre afche lcran. Son constructeur attend en paramtre les dimensions de limage, dabord la largeur, puis la hauteur. Lessentiel se passe dans la mthode Mettre_A_Jour() ( partir de la ligne 49). Cest l que va effectivement tre dessine limage reprsentant le jeu. La premire action est de "vider" limage, cest--dire dobtenir une image vierge, remplie de la couleur de fond choisie au dbut du chier (ici du blanc). Cela est ralis par la mthode fill() de la classe QPixmap : tous les pixels de limage sont alors blancs, prts recevoir un nouveau dessin. Pour dessiner, il faut un pinceau, justement fourni par la classe QPainter de Qt. Celleci contient de nombreuses mthodes permettant de tracer les points individuels, mais aussi des rectangles, des ellipses, des courbes de Bzier La plupart des formes pouvant tre dessines acceptent une couleur de bord et une couleur de remplissage. La premire est dnie par la mthode setPen() de QPainter, la seconde par la mthode setBrush().

174

Initiation la programmation avec Python et C++

Les supports des disques sont dessins en premier. Il importe ici de comprendre que le dessin que nous ralisons est tout fait comparable celui quon ferait avec de lencre sur une feuille de papier : ce qui est dessin apparatra toujours "par-dessus" ce qui se trouvait pralablement au mme endroit. Le dessin lui-mme est effectu par la mthode drawRoundRect() de QPainter, qui permet dobtenir des rectangles aux coins arrondis. Les valeurs numriques qui lui sont passes sont des coordonnes et des dimensions exprimes en pixels, toujours avec lorigine des coordonnes dans le coin suprieur gauche. Enn, la dernire mthode intressante est la mthode paintEvent(), hrite de la classe QWidget. Cest elle qui est invoque lorsquil est ncessaire de redessiner le widget lcran, que cela soit lors de sa premire apparition ou lorsquil se retrouve expos aprs avoir t cach par une autre fentre. Nous utilisons nouveau une instance de QPainter, simplement pour "plaquer" limage sur le widget. Remarquez que nous invoquons pour terminer la mthode paintEvent() de la classe anctre, ce qui est gnralement recommand. La classe principale reprsentant lensemble de la fentre, ainsi que le programme principal permettant de lancer lapplication, se trouve dans un chier hanoi_gui.py :
# -*- coding: utf8 -*import sys from PyQt4 import QtCore, QtGui import hanoi import dessin class Hanoi_Gui(QtGui.QWidget): def __init__(self, le_jeu, parent): QtGui.QWidget.__init__(self, parent) self.jeu = le_jeu self.nb_mouvements = 0 self.tour_depart = -1 colonne = QtGui.QVBoxLayout(self) ligne_boutons_tours = QtGui.QHBoxLayout() self.bt_tour_1 = QtGui.QPushButton("Tour 1", self) ligne_boutons_tours.addWidget(self.bt_tour_1) self.connect(self.bt_tour_1, QtCore.SIGNAL("clicked()"), self.Tour_Choisie) self.bt_tour_2 = QtGui.QPushButton("Tour 2", self) ligne_boutons_tours.addWidget(self.bt_tour_2) self.connect(self.bt_tour_2, QtCore.SIGNAL("clicked()"), self.Tour_Choisie)

Chapitre 12

Ouverture des fentres : programmation graphique

175

self.bt_tour_3 = QtGui.QPushButton("Tour 3", self) ligne_boutons_tours.addWidget(self.bt_tour_3) self.connect(self.bt_tour_3, QtCore.SIGNAL("clicked()"), self.Tour_Choisie) colonne.addLayout(ligne_boutons_tours) self.dessin = dessin.Dessin(self.jeu, self) colonne.addWidget(self.dessin) ligne_boutons_fonctions = QtGui.QHBoxLayout() bt_solution = QtGui.QPushButton("Solution", self) ligne_boutons_fonctions.addWidget(bt_solution) self.connect(bt_solution, QtCore.SIGNAL("clicked()"), self.Solution) bt_quitter = QtGui.QPushButton("Quitter", self) ligne_boutons_fonctions.addWidget(bt_quitter) self.connect(bt_quitter, QtCore.SIGNAL("clicked()"), QtGui.qApp, QtCore.SLOT("quit()")) colonne.addLayout(ligne_boutons_fonctions) self.dessin.Mettre_A_Jour() def Jeu(self): return self.jeu def Tour_Choisie(self): if ( self.tour_depart < 0 ): # on a choisi la tour de dpart self.tour_depart = self.Bouton_Tour_Choisie(self.sender()) if ( self.tour_depart < 0 ): return if ( self.Jeu().Tour_Depart_Valide(self.tour_depart) ): if ( self.tour_depart!= 0 ): self.bt_tour_1.setEnabled( self.Jeu().Tour_Arrivee_Valide(self.tour_depart, 0)) if ( self.tour_depart!= 1 ): self.bt_tour_2.setEnabled( self.Jeu().Tour_Arrivee_Valide(self.tour_depart, 1)) if ( self.tour_depart!= 2 ): self.bt_tour_3.setEnabled( self.Jeu().Tour_Arrivee_Valide(self.tour_depart, 2)) else: self.tour_depart = -1; QtGui.QMessageBox.warning(self, self.trUtf8("Tours de Hano"), self.trUtf8("Tour de dpart invalide!"))

176

Initiation la programmation avec Python et C++

else: # on a choisi la tour darrive tour_arrivee = self.Bouton_Tour_Choisie(self.sender()) if ( tour_arrivee!= self.tour_depart ): if ( self.Jeu().Tour_Arrivee_Valide(self.tour_depart, tour_arrivee) ): self.Jeu().Deplacer_Disque(self.tour_depart, tour_arrivee) self.nb_mouvements += 1 self.dessin.Mettre_A_Jour() self.dessin.update() else: QtGui.QMessageBox.warning(self, self.trUtf8("Tours de Hano"), self.trUtf8("Tour darrive invalide!")) if ( self.Jeu().Jeu_Termine() ): self.bt_tour_1.setEnabled(False) self.bt_tour_2.setEnabled(False) self.bt_tour_3.setEnabled(False) QtGui.QMessageBox.information(self, self.trUtf8("Tours de Hano"), self.trUtf8("Bravo! Rsolu en %1 mouvements!").arg(self.nb_mouvements)) else: self.bt_tour_1.setEnabled(True) self.bt_tour_2.setEnabled(True) self.bt_tour_3.setEnabled(True) self.tour_depart = -1 def Solution(self): pass def Bouton_Tour_Choisie(self, bt): if ( bt == self.bt_tour_1 ): return 0 elif ( bt == self.bt_tour_2 ): return 1 elif ( bt == self.bt_tour_3 ): return 2 else: return -1

app = QtGui.QApplication(sys.argv) hauteur_max = \ QtGui.QInputDialog.getInteger(None, app.trUtf8("Tours de Hano"),

Chapitre 12

Ouverture des fentres : programmation graphique

177

app.trUtf8("Nombre de disques?"), 3, 2, 8) le_jeu = hanoi.Hanoi() le_jeu.Initialiser(hauteur_max[0]) gui = Hanoi_Gui(le_jeu, None) gui.show() sys.exit(app.exec_())

Lide est que lutilisateur choisit les tours de dpart et darrive laide de trois boutons prvus cet effet. Ces trois boutons sont connects une unique mthode, Tour_Choisie(), dont le rle sera de vrier les choix et de raliser effectivement le dplacement dun disque (ainsi que de demander linstance de Dessin, que la classe contient, de se remettre jour). Une petite subtilit a t introduite : en fonction de la tour choisie comme point de dpart, les autres boutons sont ventuellement griss (mthode setEnabled() issue de QWidget) an que lutilisateur ne puisse pas choisir un dplacement interdit. Autre subtilit, la mthode Bouton_Tour_Choisie() qui permet de retrouver le numro dune tour partir du bouton cliqu. Cela est permis par le fait quil existe une mthode sender(), charge de fournir une rfrence sur lobjet, lmetteur, ayant provoqu lexcution de la mthode Tour_Choisie() (qui est un slot dans le vocable Qt). Sans cette possibilit, il aurait t ncessaire de connecter chacun des boutons une mthode diffrente, an de les identier. En regardant ce code, sans doute vous tes-vous demand ce qutait cette fonction (une mthode en ralit, fournie par la classe de base QObject) trUtf8() frquemment employe pour les chanes de caractres afcher. Son rle est de transformer la chane de caractres qui lui est donne en paramtre en une instance de la classe QString. Cette classe de Qt est galement un type permettant la manipulation de chanes de caractres, mais la chane est code selon le standard Unicode. Cela donne laccs un catalogue beaucoup plus vaste de caractres, facilitant ainsi lafchage dans des langues possdant dautres alphabets (langues arabes ou asiatiques, mais galement certaines langues europennes comme le polonais ou le norvgien). Qt offre en effet un support assez complet de l internationalisation, cest--dire un ensemble doutils permettant dadapter un programme en diffrentes langues. Nous naborderons pas cet aspect ici. Il naura pas chapp au lecteur attentif que rien ne semble prvu pour montrer, lutilisateur, la solution au problme. Nous verrons cela au Chapitre 14, qui mettra en uvre une petite animation pour le dplacement des disques.

178

Initiation la programmation avec Python et C++

12.5.2. En C++
Comme on pouvait sy attendre, la situation en C++ est un peu plus complexe. Pour commencer, il est ncessaire de sparer en deux chiers les dclarations de nos classes et le code quelles devront effectivement excuter. Voici le chier dessin.h, chier den-tte pour la classe Dessin (charge dafcher ltat du jeu) :
#ifndef DESSIN_H__ #define DESSIN_H__ 1 #include <QWidget> #include <QPixmap> #include "hanoi.h" class Dessin: public QWidget { Q_OBJECT public: Dessin(const Hanoi& le_jeu, QWidget* parent = 0); QPoint Centre_Etage(int tour, int etage) const; public slots: void Mettre_A_Jour(); protected: void Dessiner_Disque(int disque, const QPoint& centre, QPainter& p) const; virtual void paintEvent(QPaintEvent* event); private: QPixmap image; const Hanoi& jeu; }; // class Dessin #endif // DESSIN_H__

Rien de bien exceptionnel ici. La classe QPoint, fournie par Qt, reprsente simplement les coordonnes dun point lcran. Elle apporte un moyen commode pour "transporter" des coordonnes dune fonction lautre. Remarquez, tout de mme, la faon dont nous conservons linstance sur le jeu (donne jeu). On utilise une rfrence constante, faon de garantir que cette classe dafchage ne va pas modier le jeu lui-mme. Voyons maintenant limplmentation de tout cela, dans le chier dessin.cpp.
#include <QPainter> #include "dessin.h" namespace {

Chapitre 12

Ouverture des fentres : programmation graphique

179

const int hauteur_disque = 10; const int rayon_unite = 10; const int rayon_aiguille = 5; const int espace_inter_disques = 2; const int espace_inter_tours = 20; const int marge = 8; const QColor couleur_disques(255, 160, 96); const QColor couleur_supports(128, 48, 16); const QColor couleur_fond(Qt::white); int rayon_max_tour(int taille_max) { return (taille_max + 1) * rayon_unite + rayon_aiguille; } int hauteur_max_tour(int taille_max) { return taille_max * (hauteur_disque + espace_inter_disques) + 2 * (2*rayon_aiguille); } }

On retrouve ici les diverses constantes dcrivant la gomtrie de notre afchage, ainsi que les deux fonctions utilitaires rayon_max_tour() et hauteur_max_tour(). Voyez comment chacune des valeurs est qualie par const, toujours pour leur garantir une certaine cohrence et viter des modications intempestives. De plus, tous ces lments sont contenus dans un bloc namespace{}. Le mot cl namespace signie espace de nommage. Il permet de dnir une sorte de catgorie au sein de laquelle les diffrents lments doivent avoir un nom unique. Depuis le dbut de cet ouvrage, nous utilisons couramment un espace de nommage : celui nomm std, rserv pour les outils de la bibliothque standard du langage C++. Et cest bien l le sens des critures telles que std::cout : on dsigne lobjet nomm cout, dans la catgorie nomme std. De cette faon, vous pouvez parfaitement crer vous-mme un objet nomm cout au sein de votre programme : il ny aura pas de confusion sur les noms. Nous utilisons ici un espace de nommage anonyme, cest--dire sans nom. Sa fonction est uniquement dindiquer que ce quil contient est propre au chier dans lequel il se trouve et ne saurait tre accessible depuis une autre partie du programme. On retrouve une exigence de cohrence et de sparation nette entre les diffrentes parties dun programme. Mais poursuivons dans ce chier dessin.cpp.

180

Initiation la programmation avec Python et C++

Dessin::Dessin(const Hanoi& le_jeu, QWidget* parent): image(0,0), jeu(le_jeu) { const int largeur_max = 3 * (2*rayon_max_tour(this->jeu.Hauteur())) + 2 * espace_inter_tours + 2 * marge; const int hauteur_max = hauteur_max_tour(this->jeu.Hauteur()) + 2 * marge; this->setFixedSize(largeur_max, hauteur_max); image = QPixmap(largeur_max, hauteur_max); this->Mettre_A_Jour(); } QPoint Dessin::Centre_Etage(int tour, int etage) const { const int centre_tour_x = marge + (1+2*tour)*rayon_max_tour(this->jeu.Hauteur()) + tour * espace_inter_tours; const int base_y = this->height() - (marge + (2*rayon_aiguille)); const int etage_y = base_y (espace_inter_disques+hauteur_disque)*(etage+1) + (hauteur_disque/2); return QPoint(centre_tour_x, etage_y); } void Dessin::Mettre_A_Jour() { this->image.fill(couleur_fond); QPainter p(&(this->image)); // dessin des supports p.setPen(Qt::black); p.setBrush(couleur_supports); const int base_y = this->height() - marge; for(int tour = 0; tour < 3; ++tour) { const int centre_tour_x = marge + (1+2*tour)*rayon_max_tour(this->jeu.Hauteur()) + tour * espace_inter_tours; const int rayon = rayon_max_tour(this->jeu.Hauteur()); // aiguille

Chapitre 12

Ouverture des fentres : programmation graphique

181

p.drawRoundRect(centre_tour_x - rayon_aiguille, base_y - hauteur_max_tour(this->jeu.Hauteur()), 2 * rayon_aiguille, hauteur_max_tour(this->jeu.Hauteur()), 20, 20); // base p.drawRoundRect(centre_tour_x - rayon, base_y - (2*rayon_aiguille), 2 * rayon, 2 * rayon_aiguille, 20, 20); } // dessin des disques p.setPen(Qt::black); p.setBrush(couleur_disques); for(int tour = 0; tour < 3; ++tour) { const int nb_disques = this->jeu.Une_Tour(tour).Nombre_Disques(); for(int etage = 0; etage < nb_disques; ++etage) { const int disque = this->jeu.Une_Tour(tour).Disque_Etage(etage); QPoint centre = this->Centre_Etage(tour, etage); this->Dessiner_Disque(disque, centre, p); } } p.end(); } void Dessin::Dessiner_Disque(int disque, const QPoint& centre, QPainter& p) const { const int x = centre.x() - rayon_aiguille - disque*rayon_unite; const int y = centre.y() - hauteur_disque/2; const int largeur = 2*(disque*rayon_unite + rayon_aiguille); p.drawRoundRect(x, y, largeur, hauteur_disque, 20, 20); } void Dessin::paintEvent(QPaintEvent* event) { QPainter p(this); p.drawPixmap(0, 0, this->image); p.end(); QWidget::paintEvent(event); }

Comme vous pouvez le constater, le code est parfaitement comparable celui que nous avons vu dans la version Python. Aussi nallons-nous pas le dtailler davantage. Les diffrences essentielles sont imposes par le changement de langage, notamment dans lutilisation explicite de rfrences et de pointeurs en C++.

182

Initiation la programmation avec Python et C++

Le chier hanoi_gui.h contient la dclaration de la classe Hanoi_Gui, qui est, comme prcdemment, la classe reprsentant linterface de notre programme :
#ifndef HANOI_GUI_H__ #define HANOI_GUI_H__ 1 #include <QWidget> #include <QPushButton> #include "hanoi.h" #include "dessin.h" class Hanoi_Gui: public QWidget { Q_OBJECT public: Hanoi_Gui(Hanoi& le_jeu, QWidget* parent = 0); Hanoi& Jeu(); const Hanoi& Jeu() const; public slots: void Tour_Choisie(); void Solution(); protected: int Bouton_Tour_Choisie(QObject* bt) const; private: Dessin* dessin; QPushButton* bt_tour_1; QPushButton* bt_tour_2; QPushButton* bt_tour_3; int tour_depart; int nb_mouvements; Hanoi& jeu; }; // class Hanoi_Gui #endif // HANOI_GUI_H__

Encore une fois, rien de bien spcial ici. Limplmentation des mthodes ne prsente aucune difcult particulire, le code tant presque quivalent celui du programme Python (toujours aux diffrences de syntaxe prs). Aussi, pouvons-nous passer directement au programme principal, dans main.cpp :
#include <QApplication> #include <QInputDialog> #include "hanoi.h" #include "hanoi_gui.h" int main(int argc, char* argv[]) { QApplication app(argc, argv); int hauteur_max;

Chapitre 12

Ouverture des fentres : programmation graphique

183

hauteur_max = QInputDialog::getInteger(0, app.trUtf8("Tours de Hano"), app.trUtf8("Nombre de disques?"), 3, 2, 8); Hanoi le_jeu; le_jeu.Initialiser(hauteur_max); Hanoi_Gui gui(le_jeu); gui.show(); return app.exec(); }

Et mme ce programme principal est tout ce quil y a de plus classique ! Si vous prenez lhabitude de structurer ainsi vos programmes, mme les plus complexes, vous ne devriez pas rencontrer de problmes majeurs pour vous y retrouver.

13
Stockage de masse : manipuler les chiers
Au sommaire de ce chapitre :

criture Lecture Apart : la ligne de commande

186

Initiation la programmation avec Python et C++

On dsigne habituellement par stockage de masse lenregistrement de donnes sur un support physique, gnralement de grande capacit, et capable de conserver ces donnes mme sans tre aliment lectriquement. Par exemple, un disque dur (dont vous pouvez voir lintrieur la Figure 13.1 mais ne faites pas cela, un disque dur ouvert ne fonctionne plus !). La diffrence essentielle avec les donnes que vous pouvez stocker en mmoire lors de lexcution dun programme est justement que le stockage de masse permet den mmoriser beaucoup plus et de faon permanente.
Figure 13.1 Vue de lintrieur dun disque dur.

Les donnes ainsi stockes sont organises en chiers, chacun possdant sa propre organisation "interne" dnie par le programme layant cr. On distingue habituellement deux types de chiers :

Les chiers texte, dont le contenu est constitu uniquement de caractres lisibles par un tre humain. Ces caractres sont organiss en lignes, chaque n de ligne tant marque par un caractre spcial (ou deux caractres spciaux, selon le systme sur lequel vous vous trouvez). Les chiers contenant les codes source de vos programmes, une page Web visualise dans un navigateur, sont des chiers texte. Les chiers binaires, dont le contenu est comparable ce qui se trouve rellement dans la mmoire de lordinateur lors de lexcution du programme. Les informations sont alors gnralement plus "compactes" que dans le cas dun chier texte et ne sont pas lisibles par un il humain ( moins quil ne soit extrmement entran).

Chapitre 13

Stockage de masse : manipuler les chiers

187

Les chiers rsultants dune compilation, les photos issues dun appareil numrique ou dun scanner, sont des chiers binaires. Le type de chier utiliser dpend du programme que vous dveloppez. Dune manire gnrale, les chiers binaires sont privilgis lorsque lencombrement sur disque et la rapidit de lecture et dcriture sont des considrations importantes. On prfrera plutt les chiers texte si on souhaite obtenir un chier pouvant tre lu facilement par nimporte quel diteur de texte. Nous allons ici nous limiter dlibrment aux chiers texte, car ce sont encore les plus simples manipuler. Comme vous allez le constater, il ny a pas grande diffrence avec les techniques dentres-sorties que nous avons vues depuis le Chapitre 4.

13.1. criture
Pour commencer, nous allons crer un programme qui permet de stocker dans un chier la liste des dplacements ncessaires pour rsoudre le jeu des Tours de Hano. Cela peut tre utile pour transmettre cette information capitale un ami, sans devoir noter scrupuleusement ces dplacements la main, avec tous les risques derreurs que cela comporte. Voici donc le programme ecrire_solution :
Python
# -*- coding: utf8 -*import hanoi print "Nombre de disques?", hauteur_max = int(raw_input()) le_jeu = hanoi.Hanoi() le_jeu.Initialiser(hauteur_max); solution = [] le_jeu.Solution(solution) fichier = file("solution", "w") for mouvement in solution: print >> fichier, \ mouvement[0]+1, \ mouvement[1]+1 fichier.close()

C++
#include <iostream> #include <fstream> #include "hanoi.h" int main(int argc, char* argv[]) { int nb_disques = 3; std::cout << "Nombre de disques? "; std::cin >> nb_disques; Hanoi jeu; jeu.Initialiser(nb_disques); std::vector<std::pair<int, int> >solution; jeu.Solution(solution); std::ofstream fichier("solution"); for(int mouvement = 0; mouvement < solution.size(); ++mouvement)

188

Initiation la programmation avec Python et C++

Python

C++
{ fichier << solution[mouvement].first+1 << " " << solution[mouvement].second+1 << std::endl; } fichier.close(); return 0; }

Lcriture dans un chier se fait en crant une instance dun type particulier : le type file en Python, le type std::ofstream en C++. La simple cration de cette instance provoque louverture du chier, cest--dire que le programme signale au systme dexploitation quil sapprte manipuler un chier. Le nom de ce chier est pass en argument la cration de linstance (vous laurez compris, file et std::ofstream sont en ralit des classes). Le type C++ std::ofstream reprsente un ux de sortie vers un chier (o pour out, sortie ; f pour le, chier ; stream est langlais de ux). Cela signie que linstance ne permet que dcrire dans le chier, pas den lire le contenu. Pour Python, le "sens douverture" (lecture ou criture) est dtermin par un second paramtre pass au constructeur de file, une chane de caractres. Ici, la chane "w" (pour write, crire) indique que nous allons crire dans le chier : linstance ne nous permettra pas de lire depuis le chier. Dans les deux cas, si un chier du mme nom existe dj (dans le mme rpertoire), il est tout simplement "cras", cest--dire que le contenu prcdent est irrmdiablement perdu. Si ce nest pas ce que vous souhaitez, si vous voulez ajouter des lignes un chier existant sans perdre celles dj prsentes :

En C++, passez en second paramtre la valeur std::ios::app. En Python, passez la chane "w+" en second paramtre.

Lcriture en elle-mme est tout fait comparable lafchage lcran que nous avons vu depuis le dbut de cet ouvrage. Elle est mme parfaitement quivalente en C++, tandis que Python ncessite lemploi dune notation particulire pour linstruction print : la faire suivre de deux chevrons fermants (>>), suivis de linstance de file prcdemment cre, avant la liste de ce que lon veut inscrire.

Chapitre 13

Stockage de masse : manipuler les chiers

189

Lorsque lcriture est termine, il convient de fermer le chier, au moyen de la mthode close(). On signale ainsi au systme dexploitation que lon a termin. Ces deux oprations symtriques, louverture et la fermeture dun chier, sont assez importantes pour le systme sous-jacent : elles permettent la mise en uvre de diverses techniques doptimisation qui conduisent un meilleur "rpondant" du systme.

13.2. Lecture
Maintenant que nous avons stock dans un chier les mouvements ncessaires la rsolution du problme, il serait tout aussi intressant de relire ce chier an den extraire cette prcieuse information. Voici le programme lire_solution :
Python
# -*- coding: utf8 -*fichier = file("solution", "r") while ( fichier ): ligne = fichier.readline() mouvement = ligne.split() if ( len(mouvement) == 2 ): print int(mouvement[0]), \ "->", \ int(mouvement[1]) fichier.close()

C++
#include <iostream> #include <fstream> int main(int argc, char* argv[]) { std::ifstream fichier("solution"); while ( not fichier.eof() ) { std::string ligne; int depart; int arrivee; fichier >> depart; fichier >> arrivee; std::cout << depart << " -> " << arrivee << "\n"; } fichier.close(); return 0; }

La lecture se fait comme lcriture, en obtenant une instance sur un type ddi cette tche. On retrouve le type file en Python, cette fois construit avec en second paramtre la chane "r" (pour read, lire). En C++, on utilise plutt le type std::ifstream (i pour input, entre). Si vous remplacez mentalement lutilisation des objets chier dans ces programmes par les moyens usuels pour recevoir des donnes de lutilisateur, vous constaterez une grande similarit avec ce que nous avions lors de ltude de linteractivit, notamment la saisie de plusieurs valeurs (voir la section 4 du Chapitre 4).

190

Initiation la programmation avec Python et C++

Le problme essentiel consiste "dtecter" lorsquon arrive la n du chier. En effet, cela naurait pas de sens de chercher lire au-del. Cest pour cela quon utilise une boucle while pour la lecture : le chier est lu ligne ligne (pour Python, en utilisant la mthode readline() de linstance de file) ou information par information (pour C++, en utilisant les chevrons comme nous le faisons habituellement avec std::cin), tant que nous ne sommes pas la n. Dans le cas de Python, il suft de tester linstance de le, test qui chouera ds quon aura termin la lecture. Dans le cas de C++, on fait appel la mthode eof() de std::ifstream (pour End Of File, n de chier), laquelle retourne vrai ( true) ds que lon est effectivement la n du chier. Incidemment, remarquez que nous navons absolument pas utilis les outils raliss pour les Tours de Hano. Un bon exercice consisterait modier ce programme, an de lui faire "simuler" la solution du jeu, comme nous lavons vu au Chapitre 10.

13.3. Apart : la ligne de commande


Probablement serez-vous tent un jour dcrire un programme qui lit les lignes dun chier, effectue une certaine opration sur chacune, puis crit un nouveau chier rsultat de la transformation. Cest ce quon appelle un ltre. Idalement, ce ltre pourrait fonctionner par la ligne de commande, de la mme faon que les commandes classiques que sont copy, cd ou mkdir. Autrement dit, il doit tre possible de passer des paramtres au programme, qui sont par nature inconnus au moment de son criture. Il existe un moyen assez simple pour cela, tant en Python quen C++. Modions le programme dcriture prcdent, an quil accepte en paramtre le nombre de disques et le chier devant les stocker :
Python
# -*- coding: utf8 -*import sys import hanoi hauteur_max = int(sys.argv[1]) le_jeu = hanoi.Hanoi() le_jeu.Initialiser(hauteur_max); solution = [] le_jeu.Solution(solution) fichier = file(sys.argv[2], "w") for mouvement in solution: print >> fichier, \ mouvement[0]+1, \

C++
#include <iostream> #include <fstream> #include <cstdlib> #include "hanoi.h" int main(int argc, char* argv[]) { int nb_disques = atoi(argv[1]); Hanoi jeu; jeu.Initialiser(nb_disques); std::vector<std::pair<int, int> > solution;

Chapitre 13

Stockage de masse : manipuler les chiers

191

Python
mouvement[1]+1 fichier.close()

C++
jeu.Solution(solution); std::ofstream fichier(argv[2]); /* inchang... */ return 0; }

Exemple dutilisation (solution pour trois disques stocke dans un chier soluce.txt) :
$ python ecriture.py 3 soluce.txt

Exemple dutilisation (solution pour trois disques stocke dans un chier soluce.txt) :
$ ./ecriture 3 soluce.txt

ou sur Windows :
> ecriture 3 soluce.txt

Les lignes modies ont t mises en vidence. Et vous voyez maintenant le sens des paramtres argc et argv passs la fonction principale main() en C++. Le paramtre argv est, strictement parler, un tableau de pointeurs sur donnes de type char, soit un tableau de tableaux de caractres autrement dit, un tableau de chanes de caractres "brutes", ne fournissant aucune des facilits du type std::string que nous utilisons depuis le dbut. Chacune de ces chanes contient lun des paramtres passs au programme lors de son excution. Lquivalent en Python est le tableau de chanes de caractres argv, auquel on accde par le module sys. Le paramtre C++ argc, quant lui, contient le nombre de paramtres. Normalement, ce nombre vaut toujours au moins 1, le premier paramtre (celui dindice 0) tant, par convention, le nom du programme lui-mme. Pour retrouver cette valeur en Python, il suft de demander la taille du tableau par len(sys.argv). Chacun des lments de ces tableaux peut donc tre vu comme une chane de caractres. Or, dans notre cas, nous attendons en premier paramtre un nombre entier. Si cela est simple en Python, par lutilisation de la pseudo-fonction int(), en C++ nous devons faire appel une fonction de conversion spcialise pour ce genre de situation : la fonction atoi(), qui attend en paramtre un type char* (une chane de caractres standard du langage C) et retourne un entier. Cette fonction est disponible dans len-tte <cstdlib>. Naturellement, dans un vritable programme, il conviendrait de vrier quon ne cherche pas obtenir plus de paramtres quil nen a t pass. Si vous ne donnez quun seul paramtre aux programmes prcdents, ceux-ci planteront coup sr.

14
Le temps qui passe
Au sommaire de ce chapitre :

Ralisation dun chronomtre Animation

194

Initiation la programmation avec Python et C++

Comme annonc au Chapitre 12, nous allons ajouter la version graphique des Tours de Hano lafchage de la solution au problme. Une faon de faire serait de tout simplement simuler les dplacements en mettant jour lafchage aprs chacun deux. Mais cela poserait au moins deux problmes :

Des changements limits la position dun unique disque dune image lautre sont assez difciles suivre pour lil humain : un tel afchage ne serait donc pas trs attrayant, voire pas sufsamment dmonstratif pour lutilisateur. Plus grave, les machines actuelles sont tellement puissantes et performantes que les diffrentes images se succderaient une vitesse bien suprieure ce que lil humain est capable de suivre : un utilisateur ne verrait quun bref clignotement entre le dbut et la n de la dmonstration.

Bref, il faut faire mieux que cela. Une ide consiste alors animer les dplacements des disques, cest--dire proposer une simulation plus proche de la ralit. Mais qui dit animation, dit succession dimages dans le temps Nous devons donc avoir une certaine matrise du temps. Dans le monde de la programmation, la notion de temps est parfois assez oue et incertaine. Notamment dans le cas de systmes dexploitation qui "font comme si" ils permettaient plusieurs programmes de sexcuter simultanment. En ralit, sur une machine ne disposant que dun unique processeur, un seul programme sexcute un instant donn. Sur une machine quipe de deux processeurs ou dun processeur double cur, deux programmes au plus peuvent sexcuter simultanment. Et on entend ici "programme" au sens large, ce qui inclut le systme luimme. Ce qui se passe en ralit, cest que le systme "prend la main" intervalles rguliers et attribue une certaine quantit de temps chacun des programmes qui sexcutent, lun aprs lautre. Cette opration est tellement rapide quelle passe presque inaperue (sauf dans le cas dune activit particulirement intense). Mais cela signie surtout que le temps coul entre le moment o votre programme dmarre et le moment o il sarrte, nest pas quivalent au temps "rel" durant lequel son code a effectivement t excut le second est gnralement infrieur au premier. Un mot justement, au sujet du terme temps rel. Un systme est dit temps rel si son bon fonctionnement dpend de rponses devant imprativement tre donnes dans un intervalle de temps prcis, et ce quelle que soit la charge du systme. Cet intervalle nest pas ncessairement court (bien que cela soit souvent le cas). Par exemple, sur une voiture, le systme vitant le blocage des roues en cas de freinage brutal est

Chapitre 14

Le temps qui passe

195

un systme temps rel : quelles que soient les tches en cours dexcution sur le calculateur, celui-ci doit imprativement donner une rponse en un temps donn et x. Cette notion ne doit pas tre confondue avec celle de haute performance. Celle-ci consiste effectuer un grand nombre de calculs en un minimum de temps, sans que lexactitude du rsultat dpende de ce temps. Les jeux dits temps rel nont (le plus souvent) rien de temps rel : ils sefforcent dutiliser au mieux la puissance disponible pour fournir des ractions aussi rapides que possible. Mais que la charge du systme augmente, par exemple du fait dune autre application consommatrice de puissance, et les temps de rponse du jeu seront inexorablement augments. Fermons cette parenthse pour nous pencher sur notre problme.

14.1. Ralisation dun chronomtre


Avant de nous attaquer au gros morceau du programme des Tours de Hano, commenons modestement par la ralisation dun simple chronomtre : un bouton pour dmarrer, un bouton pour arrter et lafchage du temps coul. La Figure 14.1 montre le programme que nous allons raliser.
Figure 14.1 Un chronomtre lmentaire.

Lorsque lutilisateur clique sur le bouton Go, le programme dclenche un minuteur, un objet tout fait comparable ces horribles instruments quon trouve communment dans les cuisines : vous demandez un temps dattente, subissez le tic-tac, puis sursautez linfernale sonnerie. La classe QTimer de Qt propose une fonction semblable, sauf quelle est beaucoup plus prcise (de lordre de quelques dizaines de millisecondes) et que la sonnerie prend la forme dun signal auquel le programme va ragir. Les calculs sur des dures sont, quant eux, effectus laide de la classe QTime.

196

Initiation la programmation avec Python et C++

Voici le code du programme chrono, limit la classe contenant linterface :


Python
class Chrono(QtGui.QWidget): def __init__(self, \ le_jeu, \ parent=None): QtGui.QWidget.__init__(self, parent) self.colonne = \ QtGui.QVBoxLayout(self) self.affichage = \ QtGui.QLCDNumber(12, self) self.colonne.addWidget( \ self.affichage) self.bt_go = \ QtGui.QPushButton("Go!", self) self.colonne.addWidget(self.bt_go) self.connect(self.bt_go, \ QtCore.SIGNAL("clicked()"), \ self.Go) self.bt_stop = \ QtGui.QPushButton("Stop", self) self.colonne.addWidget(self.bt_stop) self.connect(self.bt_stop, \ QtCore.SIGNAL("clicked()"), \ self.Stop) self.chrono = QtCore.QTimer(self) self.connect(self.chrono, \ QtCore.SIGNAL("timeout()"), \ self.timeout) def Go(self): self.debut = \ QtCore.QTime.currentTime() self.chrono.start(20) def Stop(self): self.chrono.stop() self.timeout() def timeout(self): maintenant = \ QtCore.QTime.currentTime() ecart = \ QtCore.QTime().addMSecs( \

C++
Chrono::Chrono(QWidget* parent): QWidget(parent), chrono(0), affichage(0) { QVBoxLayout* colonne = new QVBoxLayout(this); this->affichage = new QLCDNumber(12, this); colonne->addWidget(this->affichage); QPushButton* bt_go = new QPushButton("Go!", this); colonne->addWidget(bt_go); connect(bt_go, SIGNAL(clicked()), this, SLOT(Go())); QPushButton* bt_stop = new QPushButton("Stop", this); colonne->addWidget(bt_stop); connect(bt_stop, SIGNAL(clicked()), this, SLOT(Stop())); this->chrono = new QTimer(this); connect(this->chrono, SIGNAL(timeout()), this, SLOT(timeout())); } void Chrono::Go() { this->debut = QTime::currentTime(); this->chrono->start(20); } void Chrono::Stop() { this->chrono->stop(); this->timeout(); }

Chapitre 14

Le temps qui passe

197

Python
self.debut.msecsTo(maintenant)) self.affichage.display( \ ecart.toString("m:ss:zzz"))

C++
void Chrono::timeout() { QTime maintenant = QTime::currentTime(); QTime ecart = QTime().addMSecs( this->debut.msecsTo(maintenant)); this->affichage->display( ecart.toString("m:ss:zzz")); }

Le signal qui nous intresse dans la classe QTimer est timeout(), mis lorsque la dure demande est coule et connect une mthode ponyme dans la classe Chrono. Le minuteur est dclench par un appel la mthode start(), laquelle prend en paramtre la dure dattente en millisecondes. Si vous examinez le code de la mthode Go() de la classe Chrono, vous voyez quon demande une dure de 20 millisecondes, autrement dit on souhaite afcher la dure coule 50 fois par seconde. Dans la ralit, il est assez peu probable quon obtienne un rythme aussi lev, encore moins de faon able. Les systmes dexploitation auxquels nous nous intressons ne sont pas des systmes temps rel. Cela signie que le systme va "faire de son mieux" pour nous dlivrer un message (un signal) toutes les vingt millisecondes, mais sans aucune garantie. La consquence est quon ne peut pas savoir avec certitude quel rythme notre mthode timeout() sera invoque. Autrement dit, on ne peut pas calculer le temps coul simplement en ajoutant vingt millisecondes un compteur. Cest l quintervient la classe QTime. Au moment o on dmarre le minuteur, on mmorise lheure courante (mthode currentTime() de QTime, utilisable sans crer dinstance). Lorsque la mthode timeout() est invoque, on calcule le temps coul en millisecondes entre la nouvelle heure courante et celle qui a t mmorise. Avec les outils dont nous disposons, cest la seule faon davoir une mesure qui ne soit pas trop entache derreurs. Accessoirement, remarquez lutilisation de la classe QLCDNumber, laquelle propose un afchage de type montre quartz.

198

Initiation la programmation avec Python et C++

14.2. Animation
Fort de notre exprience, nous pouvons maintenant attaquer le gros morceau : modier le programme des Tours de Hano, pour enn pouvoir montrer la solution lutilisateur. La Figure 14.2 montre le programme alors quil est en train dafcher cette solution. Remarquez que les boutons permettant de choisir une tour ont t dsactivs.
Figure 14.2 Une solution anime pour Hano.

Pour commencer, nous allons mmoriser les positions de chaque disque dans un tableau. Cela nous permettra par la suite de savoir prcisment o chacun se trouve et de facilement modier cette position. La mthode Mettre_A_Jour() de la classe Dessin se trouve rduite simplement calculer ces positions (version Python) :
def Mettre_A_Jour(self): if ( self.anim_chrono.isActive() ): self.anim_chrono.stop() self.emit(QtCore.SIGNAL("Fin_Deplacement")) for tour in range(3): nb_disques = self.jeu.Une_Tour(tour).Nombre_Disques() for etage in range(nb_disques): disque = self.jeu.Une_Tour(tour).Disque_Etage(etage) centre = self.Centre_Etage(tour, etage) self.positions [disque-1] = list(centre) self.Dessiner()

Laissons de ct pour linstant les trois premires lignes de cette mthode modie, bien que le nom de la variable anim_chrono vous laisse probablement dj deviner certains changements venir. Voyez comment les positions des disques sont mmorises dans le tableau positions. Le dessin effectif de limage a t dplac dans une mthode Dessiner(), qui ressemble beaucoup lancienne mthode Mettre_A_Jour() : son contenu est pratiquement

Chapitre 14

Le temps qui passe

199

le mme, la seule diffrence est quelle parcourt le tableau positions plutt que de calculer le centre de chaque disque. Voici un extrait de son contenu (en C++) :
void Dessin::Dessiner() { this->image.fill(couleur_fond); /* lignes identiques non recopies... */ // dessin des disques p.setPen(Qt::black); p.setBrush(couleur_disques); for(int disque = 0; disque < this->jeu.Hauteur(); ++disque) this->Dessiner_Disque(disque+1, this->positions [disque], p); p.end(); }

Lanimation elle-mme est rpartie en deux mthodes. Une premire, Deplacer_Disque(), lance lanimation ainsi quun minuteur, comme pour le chronomtre de la section prcdente. La seconde, timeout(), sera invoque priodiquement par ce minuteur pour modier la position courante du disque en cours de dplacement et mettre jour limage. Voici Deplacer_Disque(), en Python :
def Deplacer_Disque(self, tour_depart, tour_arrivee): self.anim_disque = self.jeu.Une_Tour(tour_depart).Sommet() - 1 self.anim_depart = tuple(self.positions[self.anim_disque]) etage_arrivee = \ self.jeu.Une_Tour(tour_arrivee).Nombre_Disques() self.anim_arrivee = self.Centre_Etage(tour_arrivee, etage_arrivee) self.anim_deplacement = (0, -1) self.anim_chrono.start(10)

Le membre donne de la classe Dessin nomm anim_disque contient le disque en cours de dplacement, cest--dire en fait sa taille. Dans anim_depart, on mmorise les coordonnes de ce disque au dbut de lanimation, tandis qu anim_arrivee reoit les coordonnes "cibles" auxquelles on veut amener ce disque. La donne anim_deplacement, pour sa part, contient le dplacement effectuer pour obtenir la nouvelle position du disque au cours de lanimation : dplacement horizontal et dplacement vertical. Initialement, le disque va monter le long de laiguille : le dplacement horizontal est donc nul, tandis que le dplacement vertical est ngatif (rappelez-vous que la coordonne verticale va de haut en bas). Ensuite, il sera dplac latralement, jusqu se positionner au-dessus de laiguille de destination (dplacement (1,0) ou (-1,0), selon quil va vers la droite ou vers la gauche). Enn, il descendra le long de cette aiguille (dplacement (0,1)).

200

Initiation la programmation avec Python et C++

Cette donne anim_deplacement permet donc de savoir "o nous en sommes". Elle est utilise cette n dans la mthode timeout(), ventuellement modie, chaque tape de lanimation. La dernire ligne de la mthode Deplacer_Disque() montre quon demande que le minuteur mette un signal toutes les 10 millisecondes ce qui a fort peu de chances darriver, en fait. Voici les actions qui sont effectues chaque tape de lanimation :
void Dessin::timeout() { QPoint distance = this->positions[this->anim_disque] this->anim_arrivee; if ( this->anim_deplacement.y() < 0 ) { // dplacement vertical vers le haut if ( this->positions[this->anim_disque].y() <= marge + hauteur_disque ) { // on est en haut, on prpare le dplacement latral this->positions[this->anim_disque].ry() = marge + hauteur_disque; if ( this->anim_depart.x() < this->anim_arrivee.x() ) this->anim_deplacement = QPoint(1, 0); else this->anim_deplacement = QPoint(-1, 0); } else this->positions[this->anim_disque] += this->anim_deplacement; } else if ( this->anim_deplacement.y() == 0 ) { // dplacement latral if ( std::abs(distance.x()) < 2 ) { // fin du dplacement latral, on descend le disque this->positions[this->anim_disque].rx() = this->anim_arrivee.x(); this->anim_deplacement = QPoint(0, 1); } else this->positions[this->anim_disque] += this->anim_deplacement; } else { // dplacement vertical vers le bas if ( (std::abs(distance.x())+std::abs(distance.y())) < 2 ) { // dplacement termin this->anim_chrono->stop(); this->positions[this->anim_disque] = this->anim_arrivee; emit Fin_Deplacement(); }

Chapitre 14

Le temps qui passe

201

else this->positions[this->anim_disque] += this->anim_deplacement; } this->Dessiner(); this->update(); }

On commence par calculer le chemin quil reste parcourir en calculant la diffrence entre la position actuelle du disque et la position cible tablie prcdemment. Puis on dplace le disque en modiant sa position courante, ou alors on modie le dplacement pour passer dune tape lautre. Ce code peut sembler compliqu mais, si vous le suivez calmement, vous verrez quil ne lest pas tant que cela. La partie la plus intressante est la ligne contenant le mot emit. Ce nest rien dautre que le moyen propos par Qt pour mettre explicitement un signal (lequel doit avoir t pralablement dclar dans une section signals: au sein de la classe, dans le cas du langage C++). Cette instruction est ici utilise pour littralement signaler que le dplacement du disque est termin, donc que lanimation est arrive son terme. Le mme signal est mis dans la mthode Mettre_A_Jour(), dont nous avons vu la version en Python au dbut de cette section. Nous allons voir comment ce signal est utilis par le reste de lapplication. Car nous devons maintenant modier lgrement la classe Hanoi_Gui, qui contient lensemble de linterface, ne serait-ce que pour bncier de la nouvelle fonctionnalit de Dessin : celle qui anime le dplacement dun disque. Un premier changement se situe au sein de la mthode Tour_Choisie(), dans le cas o on a effectivement choisi une tour darrive valide :
def Tour_Choisie(self): if ( self.tour_depart < 0 ): # on a choisi la tour de dpart # lignes identiques non recopies... else: # on a choisi la tour darrive tour_arrivee = self.Bouton_Tour_Choisie(self.sender()) if ( tour_arrivee!= self.tour_depart ): if ( self.Jeu().Tour_Arrivee_Valide(self.tour_depart, \ tour_arrivee) ): self.dessin.Deplacer_Disque(self.tour_depart, tour_arrivee) self.Jeu().Deplacer_Disque(self.tour_depart, tour_arrivee) self.nb_mouvements += 1 else: # etc.

On ne se contente plus de mettre jour le dessin : dsormais, on lui demande de dplacer le disque, cest--dire de faire fonctionner lanimation.

202

Initiation la programmation avec Python et C++

Mais la vritable volution se situe dans la mthode Solution(), charge de montrer la solution lutilisateur. Comme nous lavions fait dans la version en pur mode texte (voir le Chapitre 10), cette mthode commence par calculer la solution pour la mmoriser dans un tableau (version en C++) :
void Hanoi_Gui::Solution() { this->jeu.Initialiser(this->jeu.Hauteur()); this->dessin->Mettre_A_Jour(); this->jeu.Solution(this->solution); this->nb_mouvements = 0; this->bt_tour_1->setEnabled(false); this->bt_tour_2->setEnabled(false); this->bt_tour_3->setEnabled(false); this->en_simulation = true; this->Fin_Deplacement(); }

Les boutons de choix des tours sont dsactivs (grce la mthode setEnabled() issue de QWidget), manire simple dviter une interfrence de lutilisateur durant lanimation. La donne en_simulation est de type boolen (valant soit vrai soit faux) et elle nest utilise que pour indiquer que lon se trouve dans un tat particulier, savoir la simulation du jeu pour afcher la solution. Lutilisation de variables telles que celle-ci pour savoir tout instant dans quel "tat" se trouve un programme est une technique couramment utilise, qui vite bien souvent de sombrer dans des abmes de rexion face une situation complexe. Nhsitez donc pas en faire usage. Le compteur du nombre de mouvements, nb_mouvements, va tre utilis pour parcourir le tableau contenant la liste des mouvements de la solution (tableau contenu dans la variable solution). Le lancement rel de la simulation est effectu par un appel Fin_Deplacement(), que voici :
void Hanoi_Gui::Fin_Deplacement() { if ( not this->en_simulation ) return; if ( this->Jeu().Jeu_Termine() ) { this->en_simulation = false; this->bt_tour_1->setEnabled(true); this->bt_tour_2->setEnabled(true); this->bt_tour_3->setEnabled(true); } else { std::pair<int, int> deplacement = this->solution[this->nb_mouvements];

Chapitre 14

Le temps qui passe

203

this->dessin->Deplacer_Disque(deplacement.first, deplacement.second); this->Jeu().Deplacer_Disque(deplacement.first, deplacement.second); this->nb_mouvements += 1; } }

Cette mthode est connecte au signal ponyme de la classe Dessin : elle est donc invoque lissue de chaque animation du dplacement dun disque. Dans notre exemple, si on nest pas en train dafcher la solution du jeu, aucune action particulire nest effectuer : cest l quentre en jeu la donne en_simulation, comme vous pouvez le constater sur les deux premires lignes. Ensuite, si le jeu nest pas termin (autrement dit, si tous les disques nont pas t dplacs, faon de dtecter la n de la simulation), on recherche le dplacement suivant partir du tableau solution et du compteur nb_mouvements. Ce dplacement est simul, autant pour le dessin que pour le jeu. Lvolution dans les mouvements est assure par le signal Fin_Deplacement(), mis par la classe Dessin, lissue dun dplacement. Cest pourquoi lafchage de la solution nutilise pas de boucle en tant que telle, contrairement la version texte du Chapitre 10. Vous voyez l une traduction du fait que la programmation graphique est une programmation dite vnementielle. Ici, lvnement est lexpiration dun dlai, dune attente contrle par un minuteur. Cet vnement en dclenche dautres, qui leur tour nissent par aboutir au relancement de ce minuteur. On a bien ainsi une boucle (une mme srie dactions rptes plusieurs reprises), bien quelle ne soit pas explicitement crite.

Partie

Pour aller plus loin


CHAPITRE 15. Rassembler et diffuser : les bibliothques CHAPITRE 16. Introduction OpenGL CHAPITRE 17. Aperu de la programmation parallle CHAPITRE 18. Aperu dautres langages CHAPITRE 19. Le mot de la n

15
Rassembler et diffuser : les bibliothques
Au sommaire de ce chapitre :

Hirarchie dun logiciel Dcoupage de Hano

208

Initiation la programmation avec Python et C++

Au cours des chapitres prcdents, mesure que nous avancions dans la ralisation de notre programme, nous avons dment dupliqu les parties communes en particulier, la classe Hanoi qui contient le moteur interne du jeu, indpendamment de toute interface. Au l du temps, vous crerez vous-mme des outils (fonctions ou classes) que vous serez amen rutiliser dans vos nouveaux projets. Il est gnralement prfrable de sappuyer sur des outils existants, plutt que de sans cesse "rinventer la roue". De plus, si vous utilisez un seul et mme outil (ou un seul et mme ensemble doutils) dans plusieurs de vos programmes, toute correction ou amlioration en son sein protera automatiquement tous ces programmes : la maintenance et mme lvolutivit sen trouvent grandement facilites. Ce double besoin, rutiliser le travail dj effectu et mutualiser les oprations de maintenance, a donn naissance la notion de bibliothque. Au sens "programmation" du terme, une bibliothque est un ensemble de fonctions et de classes rassembles en un objet unique et cohrent, lesquelles fonctions et classes peuvent tre utilises par diffrents programmes, voire dautres bibliothques. Par exemple, Qt est une bibliothque de classes en langage C++ : nous avons utilis ces classes, sans pour autant devoir dupliquer quelque partie de code source de Qt que ce soit. Techniquement, une bibliothque est compose (en gnral) dune partie binaire, ressemblant un chier excutable et donc incomprhensible, et dune partie "visible" qui sera utilise par les clients de la bibliothque pour exploiter ses services. Cette partie visible est dsigne sous le terme d API, pour Application Programming Interface, en franais interface de programmation. En C++, une API se prsente sous la forme de chiers den-tte. Ce chapitre concerne essentiellement le langage C++ et la compilation dune bibliothque grce aux facilits offertes par Qt. Pour Python, pratiquement tout chier peut tre vu comme une minibibliothque, ce que lon nomme module dans le jargon propre Python. Pour lutiliser en tant que telle, il suft gnralement de copier les chiers un endroit appropri dpendant du systme. Nous allons rcuprer le travail effectu au cours des chapitres prcdents, an de transformer les lments de notre programme des Tours de Hano en autant de bibliothques. Si cela est certainement inutile sur un programme aussi restreint, limportant est lide sous-jacente et la technique utilise.

Chapitre 15

Rassembler et diffuser : les bibliothques

209

15.1. Hirarchie dun logiciel


Tout logiciel non trivial est organis de faon hirarchique, le programme principal dpendant de bibliothques elles-mmes dpendant dautres bibliothques. La Figure 15.1 prsente la hirarchie que nous allons mettre en place pour le programme des Tours de Hano, les ches se lisant " dpend de". La notion de dpendance est importante lorsquon manipule des bibliothques : dire que A dpend de B signie que A utilise des fonctions fournies par B, donc, incidemment, que A ne peut fonctionner sans B. Par ailleurs, Qt a t insr au schma comme rfrence, bien quelle ne fasse pas partie elle-mme de "nos" bibliothques.
Figure 15.1 Hirarchie des bibliothques pour le programme Hano.
hanoi_lib qt

hanoi_txt

hanoi_gui

hanoi

Rptons-le, cet exemple est extrmement simple. On peut donc sy retrouver sans difcult. Mais dans un programme de plus grande envergure, une telle hirarchie peut rapidement devenir trs complexe. Si on ny prend garde, on peut mme arriver la situation o deux bibliothques sont interdpendantes, cest--dire o une bibliothque A dpend dune bibliothque B laquelle, en mme temps, dpend de A. Mais ce qui arrive le plus souvent est lapparition dun cycle de dpendances, o plusieurs bibliothques dpendent indirectement les unes des autres, formant une boucle infernale. La Figure 15.2 illustre ces deux cas.
Figure 15.2 Exemples dinterdpendances.
inter-dpendance directe biblio_1 cycle de dpendances biblio_1

biblio_2

biblio_2

biblio_3

210

Initiation la programmation avec Python et C++

Inutile de prciser que, dans ce genre de situations, on se retrouve rapidement confront des problmes inextricables, comparables la grande question de savoir qui de luf ou de la poule est apparu le premier. Aussi, prenez soin dviter cela dans vos dveloppements.

15.2. Dcoupage de Hano


Le dcoupage que nous allons effectuer est le suivant :

hanoi_lib (le sufxe lib signiant library, langlais de bibliothque) contiendra la classe Hanoi, le moteur interne du jeu. hanoi_txt contiendra linterface en mode texte Hanoi_Txt. hanoi_gui contiendra linterface en mode graphique Hanoi_Gui. Enn, hanoi sera tout simplement le programme principal qui fera usage des bibliothques prcdentes.

Vous laurez compris, nous allons obtenir un programme pouvant sexcuter aussi bien en mode texte quen mode graphique. La Figure 15.3 montre comment les chiers vont tre rpartis en diffrents rpertoires.
Figure 15.3 Rpartition des chiers pour un programme Hano modulaire.

Chapitre 15

Rassembler et diffuser : les bibliothques

211

Dans un rpertoire quelconque, crez quatre sous-rpertoires nomms comme les bibliothques que nous allons crer. Crez galement un chier, nomm hanoi.pro, ayant le contenu suivant :
TEMPLATE = subdirs SUBDIRS = hanoi_lib \ hanoi_txt \ hanoi_gui \ hanoi

Il sagit dun chier projet "matre" (la variable TEMPLATE vaut subdirs), dont le seul rle est de construire les sous-projets rfrencs. Chacun des sous-projets se trouve dans un rpertoire, dont le nom est donn la variable SUBDIRS. Dans chacun de ces rpertoires, lutilitaire qmake va chercher un chier projet portant le mme nom (mais avec lextension .pro, naturellement). Lordre a son importance : les sous-projets seront compils dans lordre o ils apparaissent, qui dcoule des dpendances entre les bibliothques. Cela naurait en effet pas de sens de chercher compiler hanoi_gui avant hanoi_lib, sachant que la premire a besoin de la seconde. Voici par exemple le contenu du chier hanoi_lib.pro, dans le sous-rpertoire hanoi_lib :
TEMPLATE = lib CONFIG -= qt CONFIG += release console MOC_DIR = .moc OBJECTS_DIR = .objs DESTDIR = ../bin HEADERS += hanoi.h SOURCES += hanoi.cpp

Cette fois-ci, la variable TEMPLATE vaut lib, la valeur quil faut donner lorsquon souhaite crer une bibliothque. La ligne CONFIG-=qt a pour effet de "retirer" la bibliothque Qt des dpendances du projet : en effet, cette bibliothque de base nutilise aucun des services de Qt. Enn, la ligne DESTDIR indique dans quel rpertoire on va crer le chier bibliothque (qui nest pas un chier excutable), en donnant un chemin relatif au rpertoire dans lequel se trouve le chier projet. Autrement dit, le rsultat de la compilation se retrouvera dans un rpertoire bin voisin des quatre rpertoires que vous venez de crer. Petite remarque concernant la spcication de chemins. Il est vivement recommand de toujours utiliser la notation Unix pour sparer les noms des rpertoires, donc de toujours utiliser la barre oblique (ou slash) /, mme si vous vous trouvez sur un systme

212

Initiation la programmation avec Python et C++

Windows (qui utilise normalement la barre inverse \, ou backslash). Le compilateur, tout comme qmake, sadaptera de lui-mme. Voyons maintenant le chier projet pour construire linterface texte :
TEMPLATE = lib CONFIG -= qt CONFIG += release console MOC_DIR = .moc OBJECTS_DIR = .objs LIBS += -L../bin -lhanoi_lib DESTDIR = ../bin INCLUDEPATH += .. HEADERS += hanoi_txt.h SOURCES += hanoi_txt.cpp

On retrouve les mmes lments que prcdemment avec, en plus, deux nouvelles variables : INCLUDEPATH et LIBS. La premire permet dindiquer au compilateur les rpertoires dans lesquels chercher les chiers den-tte. En effet, le chier den-tte hanoi.h, qui contient lAPI (la partie visible et utilisable par dautres) de la bibliothque hanoi_lib, ne se trouve plus dans le mme rpertoire. Une convention parmi dautres, applique ici, consiste indiquer, dans le code source, le chier den-tte prcd du rpertoire qui le contient, lequel rpertoire se trouve galement tre le nom de la bibliothque laquelle correspond cet en-tte. Ainsi, le dbut du chier hanoi_txt.h ressemble ceci :
#ifndef HANOI_TXT_H__ #define HANOI_TXT_H__ 1 #include <string> #include <hanoi_lib/hanoi.h> class Hanoi_Text { public: Hanoi_Text(Hanoi& jeu); /* etc. */ }; // Hanoi_Text #endif // HANOI_TXT_H__

Pour trouver effectivement len-tte hanoi.h, il est en fait ncessaire de "remonter" dun cran dans les rpertoires, par rapport lendroit o se trouve hanoi_txt.h. Et peut-tre voudrez-vous un jour dplacer la bibliothque hanoi_lib un endroit compltement diffrent. Le fait de renseigner INCLUDEPATH permet de savoir partir de quels rpertoires il convient de chercher les en-ttes. Ainsi, le compilateur

Chapitre 15

Rassembler et diffuser : les bibliothques

213

cherchera en ralit <../hanoi_lib/hanoi.h>. Si vous dplacez hanoi_lib, il sufra dadapter INCLUDEPATH, sans quil soit besoin de toucher au code source. Vous pouvez rapprocher cette variable de la clbre variable denvironnement PATH, utilise pour la recherche des chiers excutables. Remarquez, par ailleurs, que dans le code source nous utilisons galement la convention Unix pour la sparation des noms des rpertoires. La variable LIBS permet dindiquer les bibliothques dont dpend le projet. Elle suit pour cela une syntaxe particulire : dabord lindication de rpertoires o chercher les bibliothques, chaque rpertoire devant tre prcd de -L ; ensuite, les noms des bibliothques, chacun prcd de -l (la lettre "l" en minuscule). Remarquez que lon donne bien le nom de chaque bibliothque, non pas le nom du chier qui la contient. Ce nom de chaque bibliothque est dduit du nom du chier projet. Pour le programme principal, qui dpend des trois autres bibliothques, la ligne LIBS dans le chier hanoi.pro ressemble ceci :
LIBS += -L../bin -lhanoi_lib -lhanoi_txt -lhanoi_gui

Et voici le contenu abrg du programme principal :


#include <string> #include <iostream> #include <QApplication> #include <QInputDialog> #include <hanoi_lib/hanoi.h> #include <hanoi_txt/hanoi_txt.h> #include <hanoi_gui/hanoi_gui.h> int main(int argc, char* argv[]) { bool mode_texte = true; if ( argc > 1 ) { if ( std::string(argv[1]) == "gui" ) mode_texte = false; } if ( mode_texte ) { // interface texte } else { // interface graphique } }

214

Initiation la programmation avec Python et C++

Ne soyez pas effray par la profusion de directives #include au dbut : il est trs commun den avoir beaucoup, surtout dans un logiciel de grande taille. Voyez comme nous exploitons la ligne de commande, suivant ce que nous avons vu la n du Chapitre 13. Si on trouve le mot "gui" en premier paramtre, alors on utilise linterface graphique fonde sur Qt. Sinon, on utilise linterface en mode texte.

Vous trouverez le code source complet sur le site web de Pearson France. Mais cest un bon exercice que dessayer de remplir par vous-mme les blancs qui nont pas t traits ici.
Une fois tout cela en place, vous pouvez vous placer dans le rpertoire qui contient tous les autres (celui contenant le chier projet rfrenant les sous-projets, le premier que nous avons vu) et excuter tout simplement :
$ qmake $ make

Remplacez make par mingw32-make si vous tes sous Windows. lissue dune compilation qui devrait tre assez rapide, vous obtiendrez dans un nouveau sousrpertoire bin trois chiers contenant les bibliothques, ainsi quun chier excutable hanoi. vous de le tester maintenant !

16
Introduction OpenGL
Au sommaire de ce chapitre :

Installation Triangle et cube de couleur La GLU et autres bibliothques

216

Initiation la programmation avec Python et C++

Tout ce que nous avons fait jusquici a consist "dessiner" dans un plan, comme sur une feuille de papier. Ce qui semble tout fait naturel, tant donn que nos crans noffrent que deux dimensions (sachez que des recherches sont en cours pour proposer des dispositifs dafchage qui soient rellement en trois dimensions, cest--dire en volume). Pourtant, que cela soit dans le monde des jeux vido ou dans celui de limage de synthse (utilise pour les effets spciaux au cinma, par exemple), les exemples abondent de programmes afchant des objets en trois dimensions sur un cran en deux dimensions. Nous allons, au l de ce chapitre, approcher lune des techniques disponibles pour obtenir un tel rsultat. De nos jours, il existe essentiellement deux grandes bibliothques concurrentes facilitant grandement lafchage dobjets en volume (ou objets 3D) sur un cran :

Direct3D, bibliothque dnie et dveloppe par Microsoft pour ses systmes Windows et ses consoles de jeu, faisant partie de lensemble plus vaste DirectX (voir http://msdn2.microsoft.com/en-us/directx/). OpenGL, bibliothque dnie initialement par SGI pour ses stations de travail et dsormais maintenue par un consortium de plusieurs entreprises (dont Microsoft sest retir il y a peu), destine tre utilisable sur (presque) toutes les platesformes (voir http://www.opengl.org).

Nous pourrions discuter pendant quelques centaines de pages des mrites et inconvnients de chacune de ces deux bibliothques, dans la continuit dune longue srie de dbats parfois passionns entre les "pro-Direct3D" et les "pro-OpenGL", dbats qui pouvaient parfois prendre lallure de guerre de religion. Nous nous pargnerons cette peine, en constatant simplement que notre exigence initiale dcrire un programme unique pouvant fonctionner sur plusieurs plates-formes nous impose delle-mme lutilisation dOpenGL. Il sagit donc dune bibliothque, dnie en langage C, grce laquelle un programme peut afcher des objets 3D sur un cran, qui est, par nature, une surface plane. La bibliothque prend en charge la plupart des calculs (compliqus) permettant de projeter un point dni dans un espace 3D une certaine position sur lcran, ainsi que bien dautres aspects, comme de ne pas afcher les objets masqus par dautres ou reprsenter un effet dclairage par des sources lumineuses. Depuis quelques annes, une part croissante de ces calculs est effectue directement par la carte graphique plutt que par le processeur central de votre ordinateur. On parle alors de carte graphique acclre 3D. La spcialisation du processeur graphique (ou

Chapitre 16

Introduction OpenGL

217

GPU) fait quil devient possible dobtenir des performances inimaginables il ny a pas si longtemps. Mais voyons comment nous pouvons utiliser cette bibliothque OpenGL.

16.1. Installation
La bibliothque OpenGL et les lments ncessaires son utilisation en C++ sont normalement toujours disponibles, pratiquement quel que soit le systme que vous utilisez. Il nen va pas de mme avec Python : il est ncessaire dinstaller un petit module, nomm PyOpenGL. Si vous utilisez Windows ou Mac OS, tlchargez le chier http://peak.telecommunity.com/dist/ez_setup.py. Excutez ensuite ce chier :
$ python ez_setup.py

Il suft ensuite dexcuter la commande suivante pour installer PyOpenGL :


$ easy_install PyOpenGL

Cela va prendre en charge le tlchargement et linstallation du module. Si vous utilisez un systme Linux, installez simplement le paquetage PyOpenGL, il devrait tre disponible quelle que soit votre distribution.

16.2. Triangle et cube de couleur


Prcisons ds maintenant que OpenGL est une bibliothque qui peut sembler assez pauvre premire vue, en ce sens quelle ne fournit pas doutils de haut niveau pour des oprations apparemment aussi simples que, par exemple, afcher un texte. Nous nallons donc pas pouvoir saluer le monde pour nos premires exprimentations. De plus, lafchage 3D lui-mme doit seffectuer dans un contexte OpenGL, lequel doit tre fourni par le systme graphique du systme dexploitation. Chaque systme proposant naturellement ses propres fonctions pour obtenir ce contexte, ce qui complique encore la tche. Heureusement, la bibliothque Qt propose un widget spcialement destin lafchage utilisant OpenGL, par la classe QGLWidget. Nous pouvons donc obtenir aisment un contexte OpenGL, une zone rectangulaire lcran dans laquelle nous

218

Initiation la programmation avec Python et C++

pourrons "dessiner" en trois dimensions, simplement en drivant cette classe et en surchargeant quelques mthodes.

16.2.1. Le triangle
Comme premier exemple, voici comment afcher un simple triangle en OpenGL en nous basant sur QGLWidget :
Python
# -*- coding: utf8 -*import sys from PyQt4 import QtCore, QtGui,QtOpenGL from OpenGL.GL import * class Triangle(QtOpenGL.QGLWidget): def __init__(self, parent): QtOpenGL.QGLWidget.__init__(self, \ parent) def initializeGL(self): glEnable(GL_DEPTH_TEST) def resizeGL(self, w, h): glViewport(0, 0, w, h) glMatrixMode(GL_PROJECTION) glLoadIdentity() def paintGL(self): glClearColor(0.0, 0.0, 0.0, 1.0) glClear(GL_COLOR_BUFFER_BIT | \ GL_DEPTH_BUFFER_BIT) glMatrixMode(GL_MODELVIEW) glLoadIdentity() glShadeModel(GL_SMOOTH) glBegin(GL_TRIANGLES) glColor3d(1.0, 0.0, 0.0) glVertex3d(0.0, 0.0, 0.0) glColor3d(0.0, 1.0, 0.0) glVertex3d(1.0, 0.0, 0.0) glColor3d(0.0, 0.0, 1.0) glVertex3d(0.0, 1.0, 0.0) glEnd()

C++
#include "triangle.h" Triangle::Triangle(QWidget* parent): QGLWidget(parent) { } void Triangle:: initializeGL () { glEnable(GL.GL_DEPTH_TEST); } void Triangle:: resizeGL(int w, int h) { glViewport(0, 0, w, h); glMatrixMode(GL_PROJECTION); glLoadIdentity(); } void Triangle:: paintGL() { glClearColor(0.0, 0.0, 0.0, 1.0); glClear(GL_COLOR_BUFFER_BIT); glClear(GL_DEPTH_BUFFER_BIT); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); glShadeModel(GL_SMOOTH); glBegin(GL_TRIANGLES); glColor3d(1.0, 0.0, 0.0); glVertex3d(0.0, 0.0, 0.0); glColor3d(0.0, 1.0, 0.0); glVertex3d(1.0, 0.0, 0.0); glColor3d(0.0, 0.0, 1.0); glVertex3d(0.0, 1.0, 0.0); glEnd(); }

Chapitre 16

Introduction OpenGL

219

Nous navons montr ici que le contenu dune classe Triangle, laquelle drive de QGLWidget. Remarquez les modules que nous importons par rapport aux programmes prcdents en Python : OpenGL.GL pour les fonctions OpenGL elles-mmes, QtOpenGL ( partir de PyQt4) pour la classe QGLWidget. Ct C++, une simple ligne #include <QGLWidget> suft dans le chier den-tte correspondant cette classe Triangle (essayez de reconstruire vous-mme cet en-tte, cest un bon exercice). Par contre, toujours pour le C++, il est ncessaire de prciser, dans le chier projet, que nous allons utiliser les fonctionnalits OpenGL, par les variables CONFIG et QT :
CONFIG += release opengl QT += gui opengl

Le programme principal se rduit sa plus simple expression, ne faisant rien dautre que crer une instance de Triangle, lafcher et lancer la boucle dvnements, comme pour tout programme utilisant Qt. Mais examinons un peu le contenu de cette classe Triangle, justement. Trois mthodes doivent tre surcharges partir de QGLWidget :

initializeGL(), appele une seule fois, est destine paramtrer le contexte OpenGL (dans la pratique, pour une application complexe, on ne lutilise que rarement). resizeGL() est invoque chaque fois que le widget change de taille ; en effet, la faon dafcher un "monde" en trois dimensions dpend fortement des dimensions du rectangle plan dans lequel il doit sinscrire. paintGL(), enn, est la mthode invoque lorsquil est ncessaire de dessiner effectivement les objets en trois dimensions : cest en son sein que seffectue le vritable travail dafchage.

Toutes les fonctions de la bibliothque OpenGL commencent par le sufxe gl. La premire que nous rencontrons, glEnable() dans initializeGL(), est une fonction assez gnrique permettant dactiver certaines fonctionnalits. Son miroir est glDisable(), pour au contraire en dsactiver certaines. Par exemple, dans notre cas, on demande OpenGL dactiver le test de profondeur, qui a en gros pour effet de ne pas afcher les objets qui se trouveraient "derrire" dautres objets, selon la direction dans laquelle on regarde. Sans doute est-il temps de voir comment OpenGL passe de la modlisation dun monde en trois dimensions une reprsentation en deux dimensions sur lcr an.

220

Initiation la programmation avec Python et C++

Ce que nous allons voir maintenant est un peu simpli par rapport la ralit, mais cela devrait vous donner une ide. Pour cela, considrez un point dans lespace. Un point au sens mathmatique du terme, cest--dire nayant pas dpaisseur. Une tte dpingle vue dun peu loin, par exemple. Ce point est localis laide de coordonnes, tout comme les pixels sur lcran, sauf que ces coordonnes sont (le plus souvent) des nombres virgule ottante et quelles sont au nombre de trois do le terme trois dimensions, ou 3D en abrg. Ces coordonnes sont exprimes par rapport un repre, lequel est dni par la matrice modle/vue (model/view matrix en anglais). Le terme matrice renvoie lobjet mathmatique utilis, une matrice de transformation : il sagit grossirement dun tableau de quatre lignes et de quatre colonnes, contenant donc seize valeurs numriques. Dune certaine faon, cette matrice dtermine la position et lorientation de votre il dans lespace. Chaque point ncessaire la description des objets reprsenter est ainsi spci par ses coordonnes dans cet espace 3D. Imaginez maintenant que, juste devant votre il, vous placiez une immense plaque de verre. Sur cette plaque, laide dun feutre, vous tracez un point lendroit o vous voyez la tte dpingle. Et vous faites de mme avec tous les objets que vous voyez et que vous voulez reprsenter. Vous obtenez nalement un dessin plat, en deux dimensions, dun ensemble dobjets en trois dimensions. Ce que vous venez de raliser est une projection, consistant "craser" le monde en trois dimensions sur un plan en deux dimensions. La taille de la plaque de verre dtermine directement les limites du monde que vous reprsentez. La faon dont cette projection doit tre effectue est dtermine par la matrice de projection ( projection matrix en anglais). Pour notre tte dpingle, le rsultat est le passage de coordonnes 3D en coordonnes 2D. Enn, il reste obtenir une image, de la mme faon quon obtient une photo : il faut dnir les dimensions de cette photo, dans quelle zone de la feuille de papier elle va se situer Cest ce quon appelle le cadrage. Cette opration est rgie par la fonction glViewport(), cest--dire la faon dont limage (plane) nale sera insre dans la fentre graphique. Ses paramtres sont les coordonnes du coin infrieur gauche et les dimensions dun rectangle, par rapport au widget contenant le contexte OpenGL. Le plus souvent, on donne simplement (0, 0) comme origine et les largeur et hauteur du widget, comme ici. Limage nale, rsultant de la transformation des objets 3D en un dessin plan en deux dimensions, sera insre dans ce rectangle. Petite subtilit, source de nombreuses confusions : contrairement lusage dans la programmation

Chapitre 16

Introduction OpenGL

221

dune interface graphique, OpenGL considre que lorigine des coordonnes se situe dans le coin infrieur gauche, non pas dans le coin suprieur gauche. Le cadrage est la dernire opration de transformation gomtrique, conclusion dune chane illustre la Figure 16.1.
Figure 16.1 La chane des transformations OpenGL.
point dans lespace 3D transformation modle / vue point dans lespace de la camra transformation de projection point dans un espace plan transformation de cadrage pixel lcran

Gnralement, cadrage et projection sont dnis dans la mthode resizeGL(), car ces deux transformations dpendent directement des dimensions du widget. glMatrixMode() indique OpenGL sur quelle matrice on sapprte agir : la constante GL_PROJECTION dsigne la matrice de projection. glLoadIdentity() place dans celle-ci la matrice identit, cest--dire que le cadre de notre widget reprsente exactement le volume despace que nous allons projeter. Enn, le dessin effectif est effectu dans la mthode paintGL(). Les deux premires fonctions utilises, glClearColor() et glClear(), ont pour effet de "nettoyer" le contexte dafchage OpenGL en le remplissant de noir. Puis on positionne notre il, en donnant la matrice modle/vue ( laide de glMatrixMode() et glLoadIdentity()). La combinaison de transformations telle que nous lavons dnie ici place lorigine des coordonnes 3D au centre du widget, laxe X (premire coordonne) allant de la gauche vers la droite, entre 1.0 et 1.0, laxe Y (deuxime coordonne) allant de bas en haut, entre 1.0 et 1.0, laxe Z (troisime coordonne) "jaillissant" de lcran comme pour vous rentrer dans lil. Pour vous reprsenter ce systme de coordonnes, utilisez votre main droite, poing ferm. cartez le pouce vers lextrieur de la main : il reprsente laxe X. Tendez compltement lindex, qui devient ainsi perpendiculaire au pouce : il reprsente

222

Initiation la programmation avec Python et C++

laxe Y. Enn, dpliez les deux dernires phalanges du majeur, qui devient ainsi perpendiculaire la fois au pouce et lindex : il reprsente laxe Z.
Figure 16.2 Repre direct ou repre main droite.
Z Y X

Votre main ainsi ouverte reprsente un repre direct, aussi appel repre main droite. Si vous placez le pouce de faon quil pointe vers la droite de votre cran et lindex vers le haut, vous voyez que le majeur semble "sortir" de lcran. Toutes les oprations effectues jusquici nont fait que prparer le contexte OpenGL pour le dessin. Celui-ci est effectu sur la base de primitives, cest--dire dobjets lmentaires quOpenGL "sait" effectivement reprsenter. Ils sont trs peu nombreux, les plus utiliss tant le triangle et la ligne brise. Tout autre objet plus complexe, comme un cylindre, une sphre ou un simple cercle, est en ralit obtenu en dessinant un nombre plus ou moins lev de ces primitives de base. Comme illustration, voyez la Figure 16.3, qui montre une mme scne du monde virtuel SecondLife : limage de gauche est celle qui est normalement expose lutilisateur, limage de droite montre la dcomposition en triangles de chacun des objets afchs. Le dessin dune primitive (ou plusieurs) est ralis en donnant les coordonnes de chacun de ses sommets, dans une suite dinstructions encadres par la paire glBegin() / glEnd(). La premire annonce le commencement dun dessin, son paramtre identiant quelle primitive on sapprte dessiner. La seconde signale la n du dessin. Entre les deux, on trouve une suite dappels la fonction glVertex3d(), chacun deux dnissant les coordonnes dun sommet de la primitive. Dans notre exemple, comme nous dessinons un simple triangle, trois appels sont ncessaires.

Chapitre 16

Introduction OpenGL

223

Figure 16.3 Un monde virtuel en OpenGL : ce que vous voyez et la face cache.

Vous pouvez constater quavant chaque appel glVertex3d() nous invoquons glColor3d() : cette fonction permet de spcier la couleur de chacun des sommets. Le modle de couleur est ici aussi le modle RGB (voir la section 5 du Chapitre 12), sauf que les proportions de rouge, vert et bleu sont donnes par un nombre virgule ottante compris entre 0.0 (noir) et 1.0 (pleine intensit). Aprs un appel glColor3d(), tous les sommets suivants seront de la couleur indique. Donc, nous indiquons un premier

224

Initiation la programmation avec Python et C++

sommet en rouge, un deuxime en vert et un troisime en bleu. Le rsultat est un triangle montrant un dgrad entre ces trois couleurs, comme le montre la Figure 16.4.
Figure 16.4 Dgrad de couleur dans un triangle.

16.2.2. Le cube
Notre triangle est sans doute trs esthtique, mais il reste dsesprment plat : nous aurions pu obtenir le mme rsultat en utilisant les possibilits offertes par la classe QPainter de Qt. Voyons maintenant comment nous pouvons passer rellement en trois dimensions. Nous ne pouvons plus nous contenter des matrices identit pour la projection et la vue. Commencez donc par modier ainsi la mthode resizeGL() (en Python) :
def resizeGL(self, w, h): glViewport(0, 0, w, h) glMatrixMode(GL_PROJECTION) glLoadIdentity() glOrtho(-1.0, 2.0, -1.0, 2.0, -2.0, 3.0)

La fonction glOrtho() dnit une projection orthogonale, cest--dire dans laquelle deux droites parallles dans lespace restent parallles une fois projetes. Ce type de projection est adapt pour des reprsentations techniques mais ne correspond pas la ralit. La vision humaine est mieux reprsente par une projection utilisant des points de fuite. Par exemple, si vous conduisez une voiture sur une route longue et droite, les bords de celle-ci semblent sincurver pour se rejoindre lhorizon. Une telle perspective peut tre obtenue avec glFrustum(), mais elle est plus dlicate mettre en uvre. Les quatre paramtres passs glOrtho() dterminent les limites du volume visualis. Voici maintenant paintGL() (en C++), dans laquelle on dessine trois faces dun cube :
void Cube::paintGL() {

Chapitre 16

Introduction OpenGL

225

glClearColor(0.0, 0.0, 0.0, 0.0); glClear(GL_COLOR_BUFFER_BIT); glClear(GL_DEPTH_BUFFER_BIT); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); glRotated(-45.0, 1.0, 0.0, 0.0); glRotated(-30.0, 0.0, 0.0, 1.0); glShadeModel(GL_SMOOTH); glBegin(GL_QUADS); // premire face (avant) glColor3d(0.0, 0.0, 0.0); glVertex3d(0.0, 0.0, 0.0); glColor3d(1.0, 0.0, 0.0); glVertex3d(1.0, 0.0, 0.0); glColor3d(1.0, 0.0, 1.0); glVertex3d(1.0, 0.0, 1.0); glColor3d(0.0, 0.0, 1.0); glVertex3d(0.0, 0.0, 1.0); // deuxime face (ct) glColor3d(1.0, 0.0, 0.0); glVertex3d(1.0, 0.0, 0.0); glColor3d(1.0, 1.0, 0.0); glVertex3d(1.0, 1.0, 0.0); glColor3d(1.0, 1.0, 1.0); glVertex3d(1.0, 1.0, 1.0); glColor3d(1.0, 0.0, 1.0); glVertex3d(1.0, 0.0, 1.0); // troisime face (dessus) glColor3d(0.0, 0.0, 1.0); glVertex3d(0.0, 0.0, 1.0); glColor3d(1.0, 0.0, 1.0); glVertex3d(1.0, 0.0, 1.0); glColor3d(1.0, 1.0, 1.0); glVertex3d(1.0, 1.0, 1.0); glColor3d(0.0, 1.0, 1.0); glVertex3d(0.0, 1.0, 1.0); glEnd(); glBegin(GL_LINES); glColor3d(0.0, 0.0, 0.0); glVertex3d(1.0, 0.0, 1.0); glVertex3d(1.0, 0.0, 0.0); glVertex3d(1.0, 0.0, 1.0); glVertex3d(1.0, 1.0, 1.0); glVertex3d(1.0, 0.0, 1.0); glVertex3d(0.0, 0.0, 1.0); glEnd(); }

Les appels la fonction glRotated() ont pour effet de faire tourner notre il autour dun axe donn cest--dire, en pratique, de modier la matrice modle/vue.

226

Initiation la programmation avec Python et C++

Notez que ces transformations se composent les unes avec les autres : le premier appel glRotated() fait tourner autour de laxe X, le deuxime autour dun axe Z rsultant de la rotation prcdente. Par des combinaisons plus ou moins complexes, vous pouvez ainsi obtenir des effets sophistiqus. Vous pouvez galement obtenir trs facilement une image parfaitement noire, ne montrant rien du tout : les combinaisons de transformations peuvent tre assez dlicates manipuler, au point de vous retrouver perdu quelque part dans lespace. Les instructions qui suivent dessinent tout simplement trois rectangles (primitive GL_QUADS), puis trois segments de droite (primitive GL_LINES), ces derniers nayant dautre but que de souligner les bords du cube. Les rectangles ncessitent chacun quatre points, tandis que les segments nen ncessitent que deux. Voyez comment plusieurs primitives dun mme type sont dessines au sein dun unique bloc glBegin()/glEnd(). La Figure 16.5 montre le rsultat de ce programme.
Figure 16.5 Le cube en couleur.

16.3. La GLU et autres bibliothques


Nous navons voqu ici que quelques fonctions de la bibliothque OpenGL. Des centaines dautres sont disponibles. Pourtant, la plupart restent dassez "bas niveau" : si OpenGL fournit tous les outils pour dessiner une simple sphre, aucune fonction ne permet de la dessiner facilement. Aussi, de nombreuses bibliothques ont-elles t cres "par-dessus" OpenGL, an de fournir des outils de plus haut niveau. La premire dentre elles est la GLU (pour

Chapitre 16

Introduction OpenGL

227

OpenGL Utilities), qui offre plusieurs possibilits pour le dessin dobjets gomtriques plus complexes, le rglage des matrices de transformation selon diverses situations, etc. Limportance de cette bibliothque est telle quelle est normalement toujours disponible aux cts de toutes les implmentations dOpenGL. Concernant lafchage de texte, encore une fois, OpenGL ne fournit aucune facilit. Pourtant, une spcication a t tablie, nomme GLC, pour dnir une API permettant lafchage de caractres dans un contexte OpenGL. Une implmentation libre et multi-plate-forme de cette spcication est offerte par la bibliothque QuesoGLC (http://quesoglc.sourceforge.net/ ). mesure que vous progresserez dans lutilisation dOpenGL, vous ressentirez invitablement le besoin de structurer le monde que vous voudrez reprsenter, dassocier des lments entre eux, de permettre lutilisateur de les slectionner la souris, voire de pouvoir changer librement langle de vue En rsum, vous aurez le besoin dun vritable moteur 3D de haut niveau. Citons quelques bibliothques.

Coin3D (http://www.coin3d.org/) utilise une reprsentation hirarchique dune scne 3D pour son afchage, permettant ainsi la reprsentation densembles complexes et structurs. Elle offre, en plus, diverses options pour linteraction avec les objets de la scne, ce qui simplie considrablement le travail de dveloppement. Ajoutons quelle sintgre fort bien la bibliothque Qt. Coin3D est adapte pour les applications vocation technique. OpenSceneGraph (http://www.openscenegraph.org/projects/osg ) offre des fonctionnalits analogues celles de Coin3D, mais avec une encapsulation plus pousse des possibilits avances dOpenGL. Elle est galement plus gnraliste, permettant la cration aussi bien dapplications scientiques que de jeux. Soya3D (http://home.gna.org/oomadness/en/soya3d/index.html ) est une bibliothque spcialement ddie au langage Python pour la cration dapplications 3D, aussi bien jeux que programmes de visualisation ou simulation scientique. Et ce qui ne gte rien, les dveloppeurs de Soya3D sont principalement franais.

Gardez lesprit que ces bibliothques ne sont pas des jouets destins un enfant Pour puissantes quelles soient, elles peuvent tre plus ou moins difciles apprhender. Si vous souhaitez vous lancer dans le dveloppement dune application 3D complexe, que cela soit un jeu ou un programme scientique, nhsitez pas passer du temps pour exprimenter les possibilits offertes et dterminer ainsi laquelle vous conviendra le mieux.

17
Aperu de la programmation parallle
Au sommaire de ce chapitre :

Solution simple pour Hano Solution paralllise Prcautions et contraintes

230

Initiation la programmation avec Python et C++

Si vous vous intressez un tant soit peu au march des ordinateurs, il ne vous aura pas chapp que la tendance actuelle en matire de processeurs est la multiplication des curs, cest--dire en fait fournir des ordinateurs contenant plusieurs processeurs au lieu dun seul. Les ordinateurs quips de plusieurs processeurs ne sont pas une nouveaut. Cela fait des dizaines dannes que ces congurations sont couramment utilises dans les domaines scientiques et techniques, particulirement gourmands en puissance de calcul. Un exemple clbre est la machine Deep Blue, premier ordinateur vaincre le grand matre des checs Garry Kasparov en 1997 : elle contenait 30 processeurs gnraux, assists de 480 processeurs spcialiss pour les checs. Toutefois, les systmes quips de plusieurs processeurs demeuraient trs onreux et rservs des domaines assez spcialiss, tels que les prvisions mto. Dans le monde des micro-ordinateurs destins au grand public, durant de nombreuses annes, la course la puissance sest traduite par une course la frquence : chaque anne, le nouveau processeur tait plus rapide que celui de lanne prcdente, cette vitesse tant grossirement exprime par la frquence de lhorloge interne, qui reprsentait plus ou moins le nombre dinstructions lmentaires (trs lmentaires) que le processeur tait capable deffectuer par seconde. Les progrs ont t considrables, depuis le premier 8086 du dbut des annes 1980, afchant une frquence de 4,77 MHz, aux derniers processeurs atteignant des frquences de lordre de 4 GHz : en une vingtaine dannes, la frquence a t multiplie par mille. Mais malgr toute lhabilet des ingnieurs, les lois physiques sont ttues. Il est devenu impossible de miniaturiser davantage, daugmenter la frquence davantage, du moins pour un cot raisonnable. Dune certaine manire, on peut dire que la puissance brute a atteint ses limites, au moins jusqu la prochaine innovation technologique majeure. Laugmentation de la frquence nest ainsi plus la voie suivre pour augmenter la vitesse de calcul de nos processeurs "domestiques". Alors, les fabricants de processeurs se sont tourns vers une autre voie : multiplier les curs au sein dun mme processeur, cest--dire, en fait, fournir plusieurs processeurs en un seul. Ds lors, du point de vue du programmeur, une nouvelle possibilit pour amliorer les performances de son programme consiste exploiter les multiples processeurs disponibles en effectuant des traitements en parallle, cest--dire, littralement, en faisant plusieurs choses la fois.

Chapitre 17

Aperu de la programmation parallle

231

17.1. Solution simple pour Hano


Comme illustration, nous allons reprendre la simulation de la solution de notre jeu des Tours de Hano. La solution ne sera pas rellement calcule, mais simplement nous allons compter le nombre de mouvements effectus. Voici le programme, tout ce quil y a de plus classique :
#include <cstdlib> #include <iostream> #include <QTime> void Hanoi_Simple(int nb_disques, int depart, int pivot, int arrivee, long long& nb_mouvements) { if ( nb_disques == 1 ) { ++nb_mouvements; } else { Hanoi_Simple(nb_disques - 1, depart, arrivee, pivot, nb_mouvements); Hanoi_Simple(1, depart, pivot, arrivee, nb_mouvements); Hanoi_Simple(nb_disques - 1, pivot, depart, arrivee, nb_mouvements); } } int main(int argc, char* argv[]) { int nb_disques = atoi(argv[1]); long long nb_mouvements = 0; QTime chrono; chrono.start(); Hanoi_Simple(nb_disques, 1, 2, 3, nb_mouvements); std::cout << nb_mouvements << " " << chrono.elapsed() / 1000.0 << std::endl; return 0; }

232

Initiation la programmation avec Python et C++

On retrouve la classe QTime, fournie par Qt, que nous utilisons ici pour mesurer le temps ncessaire la simulation. Le type long long utilis dans ce programme est un type entier sign, mais dune plus grande capacit que le type habituel int. Son utilisation est ncessaire, car nous allons dnombrer un trs grand nombre de mouvements (plusieurs milliards), valeurs quun simple int ne pourrait pas reprsenter. Lorsquon excute ce programme en lui donnant en paramtre 34 disques, on obtient 17 179 869 183 mouvements en environ cent secondes. Naturellement, cette dure peut varier dune machine lautre, selon sa puissance.

17.2. Solution paralllise


Limmense majorit des langages de programmation ne fournissent par eux-mmes aucun support pour la mise en uvre dune programmation parallle. Une exception notable est le langage Ada, qui prvoyait la dnition de tches pouvant sexcuter simultanment ds sa conception, la n des annes 1970. Mais les langages Python et C++ doivent explicitement sappuyer sur des bibliothques externes. Naturellement, il existe diffrentes faons de "faire du paralllisme" ; de plus, pratiquement chaque systme propose son propre ensemble de fonctions pour cela. La solution que nous allons exposer ici sappuie sur la bibliothque Qt, qui a limmense mrite de fournir une interface de programmation unique pour tous les systmes. Le principe consiste crer, au sein dun mme programme, plusieurs ux dinstructions. Les programmes que nous avons raliss jusquici ne contenaient quun seul ux, les boucles et les alternatives ntant en fait que des "sauts" dans ce ux. Nous allons crer deux ux qui vont sexcuter en parallle. On utilise plus couramment le terme anglais de thread, dont ux nest quune traduction approximative. Ainsi, parle-t-on de programmation multithread, ou encore dun programme multithread. Dans notre exemple consistant compter le nombre de mouvements ncessaires au dplacement de n disques, chaque thread sera charg de simuler le dplacement de n1 disques. La cration dun thread se fait simplement en drivant la classe QThread

Chapitre 17

Aperu de la programmation parallle

233

et en surchargeant la mthode run(), laquelle doit contenir les instructions excuter. Voici le programme (lgrement abrg) :
#include <cstdlib> #include <iostream> #include <QTime> #include <QCoreApplication> #include <QThread> void Hanoi_Simple(int nb_disques, int depart, int pivot, int arrivee, long long& nb_mouvements) { /* inchange par rapport au prcdent */ } class Hanoi_Thread: public QThread { public: Hanoi_Thread(int nb_disques, int depart, int pivot, int arrivee): nb_disques(nb_disques), depart(depart), pivot(pivot), arrivee(arrivee), nb_mouvements(0) { } virtual void run() { Hanoi_Simple(this->nb_disques, this->depart, this->pivot, this->arrivee, this->nb_mouvements); } int nb_disques; int depart; int pivot; int arrivee; long long nb_mouvements; }; // class Hanoi_Thread void Hanoi_Parallele(int nb_disques, int depart, int pivot, int arrivee, long long& nb_mouvements)

234

Initiation la programmation avec Python et C++

{ Hanoi_Thread thread_1(nb_disques-1, depart, arrivee, pivot); thread_1.start(); Hanoi_Thread thread_2(nb_disques-1, pivot, depart, arrivee); thread_2.start(); thread_1.wait(); thread_2.wait(); nb_mouvements = thread_1.nb_mouvements + 1 + thread_2.nb_mouvements; } int main(int argc, char* argv[]) { int nb_disques = atoi(argv[1]); QCoreApplication app(argc, argv); long long nb_mouvements = 0; QTime chrono; chrono.start(); Hanoi_Parallele(nb_disques, 1, 2, 3, nb_mouvements); std::cout << nb_mouvements << " " << chrono.elapsed() / 1000.0 << std::endl; return 0; }

Pour des raisons qui sont propres Qt, nous devons imprativement crer une instance de la classe QCoreApplication avant de crer toute instance de QThread (ou dune classe drive). Cette classe reprsente en quelque sorte lapplication elle-mme, de la mme manire que la classe QApplication, rencontre pour les programmes graphiques, sauf que QCoreApplication est utilisable dans une application non graphique. Incidemment, remarquons que QApplication drive de QCoreApplication . Les threads sont crs dans la fonction Hanoi_Parallle(), tout simplement travers la cration de deux instances de la classe Hanoi_Thread, celle-ci drivant de QThread. Lorsquune instance est cre, les instructions que nous avons places dans la mthode run() ne sont pas encore excutes : le thread est bel et bien cr, mais il

Chapitre 17

Aperu de la programmation parallle

235

est inactif. Il ne sexcutera effectivement quaprs avoir invoqu la mthode start() (issue de QThread). Cette mthode start() se termine trs rapidement, quelle que soit la longueur du traitement effectu au sein de la mthode run(). Aussi, est-il gnralement ncessaire dattendre que ce traitement se termine : cest le rle de la mthode wait(). En pratique, invoquer cette mthode wait() suspend lexcution du programme tant que linstance de QThread partir de laquelle elle a t invoque na pas termin son excution. Celle-ci est termine au moment o lon sort de la mthode run(). La Figure 17.1 schmatise le droulement des oprations. Les lignes en pointills symbolisent les moments o les diffrents threads sont inactifs, cest--dire en attente de "quelque chose". Remarquez la prsence dun thread principal : en ralit, tout programme contient au moins un thread.
Figure 17.1 Hano paralllis.
thread principal

cration de thread_1 thread_1

thread_1.start ( )

cration de thread_2 thread_2 thread_2.start ( )

thread_1.run ( )

thread_2.run ( ) thread_1.wait ( )

thread_2.wait ( )

236

Initiation la programmation avec Python et C++

Lexcution de ce programme, sur une machine quipe dun processeur double cur (donc comme si elle disposait de deux processeurs), donne 17 179 869 183 mouvements en environ cinquante secondes. Il est heureux de constater que lon obtient le mme nombre de mouvements. Mais le plus intressant est que le temps dexcution a bien t divis par deux : par une rorganisation somme toute assez simple de lalgorithme, nous exploitons la puissance du matriel disponible et augmentons ainsi considrablement les performances.

17.3. Prcautions et contraintes


Naturellement, il est bien rare quun algorithme se prte aussi aisment au paralllisme. Dans la majorit des cas, la mise en uvre dune programmation parallle peut tre extrmement complexe. Les problmes surgissent ds quil devient ncessaire que deux threads sexcutant en parallle puissent lire et modier une mme zone mmoire. On peut alors se trouver confront des problmes subtils o un thread lit une valeur, tandis que lautre modie cette valeur : le premier risque de se retrouver avec une valeur errone, conduisant presque immanquablement un dysfonctionnement. De plus, il est pratiquement impossible de savoir dans quel ordre sont excutes les instructions contenues dans chacun des threads : si lordre au sein dun thread est connu, il est impossible de savoir si des instructions dautres threads ne vont pas venir sintercaler. Et cet ordre peut varier dune excution lautre dun mme programme, ce qui conduit une grande incertitude. Pour pallier ce problme, des techniques existent qui consistent xer des moments o les threads sont srialiss, cest--dire explicitement ordonns, comme sil sagissait de fonctions classiques (non parallles). Mais ces techniques ne sont elles-mmes pas sans risques : elles peuvent conduire des situations o deux threads sattendent lun lautre, provoquant un blocage complet du programme. Celui-ci nest pas "plant" proprement parler, il est tout simplement pris dans une attente innie. Dans un programme non trivial, lutilisation de la programmation parallle peut donc rapidement devenir trs dlicate. Elle nest simple que dans les cas o chaque thread ne manipule quun ensemble limit des donnes qui lui est ddi . Ds que deux threads doivent lire ou modier la mme zone mmoire, les problmes peuvent surgir, parfois de faon tout fait inattendue. Aussi, soyez particulirement prcautionneux si vous vous lancez dans ce genre daventure, nhsitez pas vous documenter abondamment.

18
Aperu dautres langages
Au sommaire de ce chapitre :

Ada Basic C# OCaml Pascal Ruby

238

Initiation la programmation avec Python et C++

Pour terminer, nous allons voquer ici quelques autres langages de programmation parmi les plus rpandus ou les plus clbres. Nous nentrerons pas dans les dtails cet ouvrage nest pas une encyclopdie ! , mais nous verrons simplement comment pourrait scrire notre programme donnant la solution du problme des Tours de Hano. Sans doute est-il galement temps de dvoiler une petite vrit. Vous trouverez dans ce qui suit des langages dits interprts, dautres dits compils. En ralit, aujourdhui, pratiquement aucun langage nest rellement interprt. Tous les langages modernes dits interprts sappuient en ralit sur une machine virtuelle, vritable dnomination de linterprteur. Les programmes en langage Python que nous avons crits tout au long de cet ouvrage, au moment de leur excution, sont en fait transforms en une reprsentation intermdiaire et binaire nomme bytecode. Ce nest pas exactement une compilation, mais ce nest pas non plus une "bte" interprtation des instructions une une. Cette reprsentation est ensuite excute par la machine virtuelle, dont le rle est en fait dexcuter ce bytecode en lui fournissant un environnement parfaitement dni une sorte de modle dordinateur idal. Le clbre langage Java, par exemple, se rfre clairement sa JVM (pour Java Virtual Machine, machine virtuelle Java) pour sexcuter. Notez bien que la slection propose ici est purement subjective et ne saurait traduire ni les prfrences de lauteur, ni la popularit de tel ou tel langage de programmation. Signalons tout de mme que la plupart font partie du groupe des langages impratifs. On dsigne par ce terme les langages de programmation consistant en la description dun tat du programme, reprsent par ses variables, ainsi que les oprations excuter pour modier cet tat. Il sagit du plus ancien paradigme de programmation et de loin le plus rpandu. Les langages Python et C++ font partie de ce groupe. Enn, les langages voqus maintenant appliquent tous les principes de la programmation oriente objet.

18.1. Ada
Le langage Ada a t dvelopp la n des annes 1970 par une quipe franaise dirige par Jean Ichbiah, au sein de lentreprise franaise CII-Honeywell Bull. Ce langage fait suite un appel doffres international lanc par le dpartement de la Dfense des tats-Unis dAmrique, qui cherchait alors rduire le nombre de langages de programmation utiliss an de gagner en cohrence et en efcacit globale.

Chapitre 18

Aperu dautres langages

239

Les contraintes imposes taient extrmement svres, lobjectif tant moins la rapidit du dveloppement et de lexcution que la robustesse et la abilit des programmes obtenus. Le nom Ada a t choisi en hommage lady Augusta Ada King, comtesse de Lovelace, seule lle lgitime du pote anglais lord Byron. Lady Ada vcut de 1815 1852, brves annes durant lesquelles elle entretint une correspondance soutenue avec linventeur de la machine analytique Charles Babbage. Elle a longtemps t perue comme lauteur du premier programme informatique. Sans plus attendre, voici un exemple de programme en Ada :
with Ada.Text_IO; use Ada.Text_IO; with Ada.Integer_Text_IO; use Ada.Integer_Text_IO; procedure Hanoi_Ada is procedure Solution(nb_disques: in integer; depart : in integer; pivot : in integer; arrivee : in integer) is begin if nb_disques = 1 then Put(depart); Put(" -> "); Put(arrivee); New_Line; else Solution(nb_disques-1, depart, arrivee, pivot); Solution(1, depart, pivot, arrivee); Solution(nb_disques-1, pivot, depart, arrivee); end if; end Solution; nb_disques: integer; begin Put("Nombre de disques? "); Get(nb_disques); Solution(nb_disques, 1, 2, 3); end Hanoi_Ada;

La syntaxe du langage emprunte beaucoup au langage Pascal, qui avait lui-mme t conu comme le langage idal pour apprendre la programmation. Sa caractristique principale, qui rebute plus dun programmeur, est une extrme rigueur sur le plan des types : sil est possible en C++ daffecter la valeur dune variable dun type entier une variable dun type en virgule ottante, cela est interdit en Ada sans utiliser explicitement une conversion de type (ou transtypage). Cette contrainte trs forte, associe dautres, fait que lcriture dun programme en Ada est gnralement perue comme

240

Initiation la programmation avec Python et C++

plus difcile que dans aucun autre langage. En contrepartie, une fois que le programme passe ltape de la compilation, il est presque garanti quil sexcute correctement. Le langage Ada offre des facilits pour la programmation parallle ou le mlange de plusieurs langages au sein dun mme logiciel. Il est aujourdhui utilis dans le cadre de missions critiques, comme la fuse Ariane 5, la gestion du trac arien ou ferroviaire ou par certaines institutions nancires. Le compilateur le plus rpandu pour le langage Ada est celui dvelopp par la socit AdaCore, nomm GNAT, dsormais intgr la suite de compilation GCC (celle que nous avons utilise depuis le dbut de cet ouvrage).

18.2. Basic
Ce langage de programmation, qui fut un moment peut-tre le plus populaire, a t invent en 1964. Le mot "BASIC" est un acronyme de Beginners All-purpose Symbolic Instruction Code, qui signie quelque chose comme "langage symbolique gnraliste pour dbutants". Lun des fondements du Basic tait en effet quil soit simple manipuler, notamment par des non-informaticiens. Les premires versions du langage furent de fait assez... basiques. Les lignes du programme devaient tre numrotes, il nexistait pas de boucles bornes ( for) ni de sous-programmes. Loutil principal pour passer dun endroit lautre dans un programme consistait utiliser linstruction GOTO, suivie du numro de ligne excuter. On aboutissait ainsi un programme non structur, comparable un plat de spaghettis... Ce qui t dire de nombreux informaticiens que le langage Basic faisait plus de mal que de bien et produisait plus de "mauvais" programmes quaucun autre. Pourtant, ce langage a connu une popularit considrable, du fait de sa simplicit et de sa diffusion dans pratiquement tous les ordinateurs personnels, ds la n des annes 1970. Il a depuis considrablement volu, abandonnant la numrotation des lignes et intgrant jusquaux notions de la Programmation Oriente Objet. Lune de ses incarnations les plus connues est le langage VisualBasic, dvelopp par Microsoft, mais de nombreuses autres existent comme FreeBasic (multiplate-formes) ou Gambas (principalement sous Linux).
Sub Solution(ByVal nb_disques As Integer, ByVal depart As Integer, ByVal pivot As Integer, ByVal arrivee As Integer) If nb_disques = 1 Then

Chapitre 18

Aperu dautres langages

241

Print Using "# -> #"; depart, arrivee Else Solution(nb_disques-1, depart, arrivee, pivot) Solution(1, depart, pivot, arrivee) Solution(nb_disques-1, pivot, depart, arrivee) End If End Sub Dim nb_disques As integer Input "Nombre de disques ? ", nb_disques Solution(nb_disques, 1, 2, 3)

En-dehors de ses "dfauts" intrinsques, qui font de Basic lun des langages les plus dcris parmi les informaticiens professionnels ou chercheurs, un problme majeur est la prolifration des "dialectes" du langage. Le programme prcdent a t test en utilisant FreeBasic, mais il nest pas garanti quil fonctionne avec un autre compilateur ou interprteur Basic.

18.3. C#
Le langage C# (prononcez "ci-sharp") a t dvelopp par la socit Microsoft en 2001 an de fournir un langage ddi sa plate-forme .NET (prononcez "dotte-net"), laquelle constitue lquivalent dune machine virtuelle pour les systmes Windows. Du point de vue de nombreux observateurs, C# et .NET ont t dvelopps pour concurrencer directement le langage Java cr par Sun. Si le seul compilateur "ofciel" de C# est Visual C#, dvelopp par Microsoft uniquement pour les systmes Windows, dautres compilateurs sont apparus pour dautres plates-formes. Citons notamment les projets Mono et DotGNU.
using System; namespace Hanoi { class Solution { public static void Solution(int nb_disques, int depart, int pivot, int arrivee) { if ( nb_disques == 1 ) { System.Console.Write(depart); System.Console.Write(" -> ");

242

Initiation la programmation avec Python et C++

System.Console.WriteLine(arrivee); } else { Solution(nb_disques-1, depart, arrivee, pivot); Solution(1, depart, pivot, arrivee); Solution(nb_disques-1, pivot, depart, arrivee); } } static void Main(string[] args) { System.Console.Write("Nombre de disques? "); int nb_disques = System.Convert.ToInt32(System.Console.ReadLine()); Solution(nb_disques, 1, 2, 3); System.Environment.Exit(0); } } }

La syntaxe gnrale du langage sinspire fortement de celle de Java, tout en reprenant des lments au C++ et au Pascal. Cette dernire inuence vient probablement du fait que le crateur de C#, Anders Hejlsberg, fut galement lauteur du compilateur quon trouve dans les produits TurboPascal puis Delphi de Borland ; il fut responsable du dveloppement de ce dernier avant de rejoindre Microsoft.

18.4. OCaml
Le langage OCaml, contraction d Objective Caml, a t cr en 1996 par Xavier Leroy, directeur de recherche lINRIA. Cest, en fait, limplmentation la plus avance du langage de programmation Caml, dvelopp depuis 1985. Il sagit essentiellement dun langage fonctionnel. La programmation fonctionnelle considre un programme comme une suite dvaluations de fonctions mathmatiques, plutt que comme une suite de changements dtat comme le fait la programmation imprative. Bien que droutante au premier abord, elle est particulirement bien adapte dans les situations o il est ncessaire de manipuler des structures de donnes complexes et rcursives. OCaml propose toutefois des possibilits permettant de mlanger les paradigmes impratif et fonctionnel : il entre donc dans la catgorie des langages multiparadigmes. Dans une certaine mesure, Python entre galement dans cette catgorie, car il offre quelques possibilits de programmation fonctionnelle.

Chapitre 18

Aperu dautres langages

243

let rec solution nb_disques depart pivot arrivee = if nb_disques = 1 then begin print_int(depart); print_string(" -> "); print_int(arrivee); print_newline(); end else begin solution (nb_disques-1) depart arrivee pivot; solution 1 depart pivot arrivee; solution (nb_disques-1) pivot depart arrivee; end;; print_string("Nombre de disques? "); let nb_disques = read_int() in solution nb_disques 1 2 3; exit 0

18.5. Pascal
Le langage Pascal doit son nom au philosophe Blaise Pascal, auquel il rend hommage. Cr au dbut des annes 1970, son objectif initial tait dtre utilis dans lenseignement de la programmation en mettant laccent sur la rigueur et la clart des programmes, rle quil remplit encore de nos jours. Il a connu une volution assez remarquable, notamment par la diffusion des outils TurboPascal puis Delphi par la socit Borland. Ainsi est-il utilis dans lindustrie, pour le dveloppement dapplications professionnelles aussi diverses que varies.
program Hanoi(input, output); var nb_disques: integer; procedure Solution(nb_disques: integer; depart : integer; pivot : integer; arrivee : integer); begin if nb_disques = 1 then writeln(depart, -> , arrivee) else begin Solution(nb_disques-1, depart, arrivee, pivot);

244

Initiation la programmation avec Python et C++

Solution(1, depart, pivot, arrivee); Solution(nb_disques-1, pivot, depart, arrivee); end end; begin write(Nombre de disques ? ); readln(nb_disques); writeln; Solution(nb_disques, 1, 2, 3); END.

Si vous comparez ce programme avec celui donn en langage Ada, vous remarquerez une certaine similarit : cela nest pas un hasard. En effet, le Pascal a t une source dinspiration pour le langage Ada, ce dernier en ayant conserv lexigence de clart (entre autres). En-dehors du clbre Delphi de Borland, citons le projet FreePascal, proposant un compilateur Pascal efcace, que lon retrouve dans lenvironnement graphique Lazarus. FreePascal et Lazarus prsentent lintrt dtres multiplate-formes, en plus dtre libres et gratuits.

18.6. Ruby
Le langage Ruby a t cr en 1993 par Yukihiro Matsumoto, alors frustr de ne pas trouver satisfaction dans les langages interprts existants (principalement Perl et Python). Il a t inuenc par les concepts prsents dans les langages Eiffel et Ada. En particulier, tout lment dun programme Ruby est un objet (ou une classe) au sens de la programmation objet, mme les variables numriques simples. Ainsi Ruby est-il lun des rares langages purement objet, bien que permettant une programmation procdurale.
def Solution(nb_disques, depart, pivot, arrivee) if ( nb_disques == 1 ) print depart.to_s + " -> " + arrivee.to_s + "\n" else Solution(nb_disques-1, depart, arrivee, pivot) Solution(1, depart, pivot, arrivee) Solution(nb_disques-1, pivot, depart, arrivee) end end

Chapitre 18

Aperu dautres langages

245

print "Nombre de disques? " nb_disques = gets.to_i Solution(nb_disques, 1, 2, 3)

La simplicit de la syntaxe nest pas sans rappeler celle des langages Ada et Python. Aujourdhui, Ruby est surtout connu dans le monde des applicatifs Web, par le biais de lenvironnement RoR (Ruby On Rails) destin faciliter la cration de vritables logiciels sexcutant sur un serveur distant et safchant dans un navigateur Web. Dans ce domaine, il offre une forte concurrence au clbre PHP.

19
Le mot de la n
Nous voil arrivs au terme de notre initiation la programmation. Naturellement, beaucoup daspects ont t passs sous silence. En particulier, nous navons pas abord certaines structures de donnes, comme les arbres ou les graphes, essentielles pour modliser correctement bien des domaines de la ralit. De mme, nous navons pas trait des environnements de dveloppement, qui sont des logiciels spcialement conus pour lcriture de logiciels. Il est toutefois prfrable davoir au pralable "tt" de la ligne de commande, comme nous lavons fait, pour tirer le maximum de ces outils sophistiqus. Si vous voulez progresser, nhsitez pas vous plonger dans les nombreuses documentations qui existent, aussi bien en librairie que sur Internet. Un bon moyen de trouver des ides et dapprendre des techniques consiste galement examiner le code source dun programme crit par quelquun dautre. Cest trs formateur, bien que cela ne soit videmment possible que dans le cas de logiciels Open Source.

248

Initiation la programmation avec Python et C++

Cest galement un aspect de la programmation que nous avons laiss de ct : lpineux problme des licences logicielles. En caricaturant un peu, on distingue deux grandes familles.

Les logiciels propritaires, ceux que vous trouvez gnralement en vente, aussi bien en magasin que sur Internet. Lorsque vous achetez un logiciel propritaire, vous nachetez en ralit quun droit dutilisation du logiciel, pas le logiciel en lui-mme. En particulier, il vous est le plus souvent interdit de le copier, de le distribuer, de le modier, et mme ne serait-ce que tenter de comprendre la faon dont il fonctionne. Les logiciels libres, qui sont principalement diffuss par Internet. Ceux-ci suivent une philosophie pratiquement loppos de la prcdente : lorsque vous obtenez un logiciel (que cela soit contre paiement ou gratuitement), vous obtenez galement le droit de le diffuser, de ltudier, de le modier. La seule contrepartie demande (mais pas vraiment exige) est de faire bncier tout le monde de vos amliorations.

Notez que cette distinction stablit sur la philosophie du logiciel, pas sur son prix. Un logiciel propritaire peut tre gratuit, un logiciel libre peut tre payant. Mme si dans la pratique, cest plutt linverse qui est vri. Pratiquement tous les logiciels mentionns dans cet ouvrage sont des logiciels libres. Les deux exceptions majeures tant les systmes dexploitation Windows et Mac OS X (bien que ce dernier sappuie largement sur le systme Unix libre BSD). Si cette question des licences logicielles vous semble une argutie juridique sans grand intrt, nous ne saurions toutefois trop vous recommander dy prter quelque attention. Une activit trs en vogue aux tats-Unis, qui arrive doucement en Europe, consiste lancer des procs tort et travers dans le seul but dobtenir des indemnits nancires en rponse des prjudices, parfois bien illusoires. Si vous voulez utiliser tel outil ou telle bibliothque, consultez toujours soigneusement sa licence dutilisation. Et posez-vous la question de la destine de votre programme : sil ne sagit que de raliser un prot maximal court terme ou sil sagit de rendre service au plus grand nombre. Cest maintenant vous de jouer. Attrapez votre clavier, dominez-le, pour crer les programmes qui vous feront rver. Comme vous avez pu le constater, laventure nest pas sans cueils. Soyez ambitieux dans vos projets, sans tre irraliste.

Chapitre 19

Le mot de la n

249

Commencez doucement, montez en puissance mesure que vous gagnez en exprience. Il en va de la programmation comme de bien dautres domaines : un moment survient o vous dpasserez la technique pour enn pouvoir vous exprimer pleinement. Alors, vous apparatra lart de la programmation.

Glossaire

API
Acronyme dApplication Programming Interface . Ensemble des dclarations de fonctions (ou classes) destines lutilisateur dune bibliothque, pour en utiliser les services.

Bibliothque
Ensemble doutils (fonctions et classes) constituant un tout cohrent et regroup en une seule entit. Divers langages utilisent plutt les termes de modules (Python), paquetages (Ada) ou unit (Pascal), quoique ces notions ne soient pas exactement quivalentes. Les outils dune bibliothque sont utiliss par lintermdiaire de son API.

Classe
Type de donne, au mme titre quun entier ou une chane de caractres, dni par les moyens de la programmation oriente objet.

Compilation
Opration consistant transformer un chier contenant le code source dun programme crit dans un certain langage de programmation, en une reprsentation binaire exploitable par le processeur de lordinateur.

252

Initiation la programmation avec Python et C++

Constructeur
Mthode spciale dans une classe, excute lorsquon cre une instance de cette classe.

Instance
Variable dont le type est une classe, comme 10 est une instance du type entier ou "bonjour" est une instance du type chane de caractres.

Mthode
Fonction dnie lintrieur dune classe, agissant sur les donnes de celle-ci.

Thread
Flux dinstructions dans un programme. Tout programme dexcution contient au moins un thread. Il est possible de crer plusieurs threads, ce qui donne un programme multithread pouvant exploiter la puissance offerte par plusieurs processeurs.

Type
Ensemble de valeurs et oprations pouvant leur tre appliques. De l est gnralement dduite la faon de stocker ces valeurs dans la mmoire de lordinateur.

Variable
Nom donn une zone mmoire contenant une information et moyen daccder cette zone. Le plus souvent, une variable est associ un type, dnissant la nature de linformation et la manire de la stocker.

Index

A
Accesseur 108 Adresse 140 Alatoire 49 Allocation 142 API 208 Append 135 argc 191 argv 191

Conteneur 61 Couleurs 172

D
Dbordement 21 Dclarations 145 Drivation 105 Directive #define 145 #endif 145 #ifndef 145 #include 146

B
Bytecode 238

C
Cast 43 Code source 10 Concatnation 25

E
diteur de texte 4 dition des liens 144 Enregistrements 68 perluette 75

254

Initiation la programmation avec Python et C++

Espace de nommage 179 vnementielle 203

O
Opration 67 Ordinogramme 88 Organigramme 88

F
Fichiers binaires 186 fermer 189 ouverture 188 textes 186 Fonctions 31 Fuite de mmoire 144

P
Paramtres par rfrence 75 par valeur 73 Pixel 172 Pointeur 140 Polymorphisme 114 Porte 35 Prcision 22 Private 117 Procdures 31 Projection 220 Protected 117

H
Hasard 48 Hritage 105

I
#include 146 Indentation 32

L
Library 210

Q
QLCDNumber 197 QTime 195 QTimer 195

M
Machine virtuelle 238 Macro 167 make 149 Mthode 106 Mode texte 154 Modle/vue 220 Module 159

R
Rptitive 56 RGB 172 RVB 172

S
Signal 165 Signature 111 Slot 165 Surdfinir 111

N
Namespace 179 Notation scientifique 22

Index

255

T
Temps rel 194 Thread 232 Transtypage 43, 239 Tuple 135 Type 19 constructeur 71 double 20 instance 69 int 20 notation pointe 69 std

prtraitement 146 string 20 vector<> 62

U
UML 89

V
Valeur de retour 37

Initiation la programmation
avec Python et C++
La programmation porte de tous ! En moins de 300 pages, Yves Bailly russit la prouesse de vous prsenter dans un style clair et concis toutes les notions fondamentales de la programmation et de vous apprendre construire progressivement un vritable programme, le jeu des Tours de Hano. Louvrage aborde aussi bien les aspects des langages interprts (Python) que ceux des langages compils (C++). Les diffrentes problmatiques sont illustres simultanment dans ces deux langages, tablissant ainsi un parallle efcace et pdagogique. Les bases acquises, vous apprendrez les techniques de la programmation objet et crerez des interfaces graphiques attrayantes. Enn, en dernire partie, quelques pistes vous aideront poursuivre votre apprentissage, de lafchage en trois dimensions la programmation parallle, sans oublier une petite prsentation dautres langages courants. Trs concret, extrmement pdagogique, cet ouvrage est accessible tous, et nexige pas de connaissances pralables en programmation.
propos de lauteur
Yves Bailly est lauteur de nombreux articles parus dans Linux Magazine et Linux Pratique, consacrs lutilisation de la bibliothque graphique Qt et la dcouverte des langages Python, Ada et C++.

Mise en place Stockage de l'information : les variables Des programmes dans un programme : les fonctions L'interactivit : changer avec l'utilisateur Ronds-points et embranchements : boucles et alternatives Des variables composes Les Tours de Hano Le monde modlis : la programmation oriente objet (POO) Hano en objets Mise en abme : la rcursivit Spcicits propres au C++ Ouverture des fentres : programmation graphique Stockage de masse : manipuler les chiers Le temps qui passe Rassembler et diffuser : les bibliothques Introduction OpenGL Aperu de la programmation parallle Aperu d'autres langages

Niveau : Dbutant / Intermdiaire Catgorie : Programmation Conguration : Multiplate-forme

Pearson Education France 47 bis, rue des Vinaigriers 75010 Paris Tl. : 01 72 74 90 00 Fax : 01 42 05 22 17 www.pearson.fr

ISBN : 978-2-7440-4086-3