You are on page 1of 287

LA PROGRAMMATION POUR...

les lves ingnieurs


. . . ou les collgiens

dbutants
. . . ou conrms

Cours de lcole des Ponts ParisTech - 2008/2009 Renaud Keriven CERTIS - ENPC keriven@certis.enpc.fr
Version lectronique et programmes : http://certis.enpc.fr/~keriven/Info/

"Ne traitez pas vos ordinateurs comme des tres vivants... Ils naiment pas a !"

"Cet ordinateur ne fait pas du tout ce que je veux !" "Exact... Il fait ce que tu lui demandes de faire !"

TABLE DES MATIRES

TABLE DES MATIRES

Table des matires


1 Prambule 1.1 Pourquoi savoir programmer ? . . 1.2 Comment apprendre ? . . . . . . 1.2.1 Choix du langage . . . . . 1.2.2 Choix de lenvironnement 1.2.3 Principes et conseils . . . 7 9 10 10 10 11 13 15 15 17 18 20 21 22 22 23 23 25 25 25 29 31 32 34 37 38 39 42 43 44 44 45

. . . . .

. . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . .

2 Bonjour, Monde ! 2.1 Lordinateur . . . . . . . . . . . . . 2.1.1 Le micro-processeur . . . . . 2.1.2 La mmoire . . . . . . . . . 2.1.3 Autres Composants . . . . . 2.2 Systme dexploitation . . . . . . . 2.3 La Compilation . . . . . . . . . . . 2.4 Lenvironnement de programmation 2.4.1 Noms de chiers . . . . . . . 2.4.2 Debuggeur . . . . . . . . . . 2.4.3 TP . . . . . . . . . . . . . .

3 Premiers programmes 3.1 Tout dans le main() ! . . . . . . . . . 3.1.1 Variables . . . . . . . . . . . . 3.1.2 Tests . . . . . . . . . . . . . . 3.1.3 Boucles . . . . . . . . . . . . 3.1.4 Rcrations . . . . . . . . . . 3.2 Fonctions . . . . . . . . . . . . . . . 3.2.1 Retour . . . . . . . . . . . . . 3.2.2 Paramtres . . . . . . . . . . 3.2.3 Passage par rfrence . . . . . 3.2.4 Porte, Dclaration, Dnition 3.2.5 Variables locales et globales . 3.2.6 Surcharge . . . . . . . . . . . 3.3 TP . . . . . . . . . . . . . . . . . . . 3.4 Fiche de rfrence . . . . . . . . . . . 4 Les 4.1 4.2 4.3

tableaux 47 Premiers tableaux . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47 Initialisation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49 Spcicits des tableaux . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50

TABLE DES MATIRES

TABLE DES MATIRES

4.4

4.5 4.6

4.3.1 Tableaux et fonctions 4.3.2 Aectation . . . . . Rcrations . . . . . . . . . 4.4.1 Multi-balles . . . . . 4.4.2 Avec des chocs ! . . . 4.4.3 Mlanger les lettres . TP . . . . . . . . . . . . . . Fiche de rfrence . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

50 52 53 53 55 57 59 59 63 63 63 63 64 65 65 66 67 68 71 72 72 73 74 74 76 77 77 78 79 79 83 83 83 85 87 87 87 88 88 89 90 91 91 92 93 93

5 Les structures 5.1 Rvisions . . . . . . . . 5.1.1 Erreurs classiques 5.1.2 Erreurs originales 5.1.3 Conseils . . . . . 5.2 Les structures . . . . . . 5.2.1 Dnition . . . . 5.2.2 Utilisation . . . . 5.3 Rcration : TP . . . . . 5.4 Fiche de rfrence . . . .

6 Plusieurs chiers ! 6.1 Fichiers spars . . . . . . . . . 6.1.1 Principe . . . . . . . . . 6.1.2 Avantages . . . . . . . . 6.1.3 Utilisation dans un autre 6.1.4 Fichiers den-ttes . . . . 6.1.5 A ne pas faire... . . . . . 6.1.6 Implmentation . . . . . 6.1.7 Inclusions mutuelles . . 6.2 Oprateurs . . . . . . . . . . . . 6.3 Rcration : TP suite et n . . 6.4 Fiche de rfrence . . . . . . . .

. . . . . . . . . . . . projet . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

7 La mmoire 7.1 Lappel dune fonction . . . . . . . 7.1.1 Exemple . . . . . . . . . . . 7.1.2 Pile des appels et dbuggeur 7.2 Variables Locales . . . . . . . . . . 7.2.1 Paramtres . . . . . . . . . 7.2.2 La pile . . . . . . . . . . . . 7.3 Fonctions rcursives . . . . . . . . . 7.3.1 Pourquoi a marche ? . . . . 7.3.2 Ecacit . . . . . . . . . . . 7.4 Le tas . . . . . . . . . . . . . . . . 7.4.1 Limites . . . . . . . . . . . . 7.4.2 Tableaux de taille variable . 7.4.3 Essai dexplication . . . . . 7.5 Loptimiseur . . . . . . . . . . . . . 7.6 TP . . . . . . . . . . . . . . . . . . 2

TABLE DES MATIRES

TABLE DES MATIRES

7.7 7.8

Fiche de rfrence . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 94 Examens sur machine . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 96 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97 97 97 97 98 99 100 101 101 103 105 106 106

8 Allocation dynamique 8.1 Tableaux bidimensionnels . . . . . 8.1.1 Principe . . . . . . . . . . . 8.1.2 Limitations . . . . . . . . . 8.1.3 Solution . . . . . . . . . . . 8.2 Allocation dynamique . . . . . . . 8.2.1 Pourquoi a marche ? . . . . 8.2.2 Erreurs classiques . . . . . . 8.2.3 Consquences . . . . . . . . 8.3 Structures et allocation dynamique 8.4 Boucles et continue . . . . . . . . 8.5 TP . . . . . . . . . . . . . . . . . . 8.6 Fiche de rfrence . . . . . . . . . . 9 Premiers objets 9.1 Philosophie . . . . . . . . . 9.2 Exemple simple . . . . . . . 9.3 Visibilit . . . . . . . . . . . 9.4 Exemple des matrices . . . . 9.5 Cas des oprateurs . . . . . 9.6 Interface . . . . . . . . . . . 9.7 Protection . . . . . . . . . . 9.7.1 Principe . . . . . . . 9.7.2 Structures vs Classes 9.7.3 Accesseurs . . . . . . 9.8 TP . . . . . . . . . . . . . . 9.9 Fiche de rfrence . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

109 . 109 . 110 . 112 . 112 . 114 . 116 . 117 . 117 . 119 . 119 . 120 . 120 . . . . . . . . . . . . . . . . . 125 125 126 126 126 128 129 130 131 131 131 133 134 136 136 137 138 138

10 Constructeurs et Destructeurs 10.1 Le problme . . . . . . . . . . . . . 10.2 La solution . . . . . . . . . . . . . 10.3 Cas gnral . . . . . . . . . . . . . 10.3.1 Constructeur vide . . . . . . 10.3.2 Plusieurs constructeurs . . . 10.3.3 Tableaux dobjets . . . . . . 10.4 Objets temporaires . . . . . . . . . 10.5 TP . . . . . . . . . . . . . . . . . . 10.6 Rfrences Constantes . . . . . . . 10.6.1 Principe . . . . . . . . . . . 10.6.2 Mthodes constantes . . . . 10.7 Destructeur . . . . . . . . . . . . . 10.8 Destructeurs et tableaux . . . . . . 10.9 Constructeur de copie . . . . . . . 10.10Aectation . . . . . . . . . . . . . . 10.11Objets avec allocation dynamique . 10.11.1 Construction et destruction 3

TABLE DES MATIRES

TABLE DES MATIRES

10.11.2 Problmes ! 10.11.3 Solution ! . 10.12Fiche de rfrence . 10.13Devoir crit . . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . .

139 140 142 145 147 147 149 149 150 150 151 151 152 152 152 153 153 154 154 155 157 157 158

11 En vrac... 11.1 Chanes de caratres . . . . . . . . . . 11.2 Fichiers . . . . . . . . . . . . . . . . . 11.2.1 Principe . . . . . . . . . . . . . 11.2.2 Chanes et chiers . . . . . . . 11.2.3 Objets et chiers . . . . . . . . 11.3 Valeurs par dfaut . . . . . . . . . . . 11.3.1 Principe . . . . . . . . . . . . . 11.3.2 Utilit . . . . . . . . . . . . . . 11.3.3 Erreurs frquentes . . . . . . . 11.4 Accesseurs . . . . . . . . . . . . . . . . 11.4.1 Rfrence comme type de retour 11.4.2 Utilisation . . . . . . . . . . . . 11.4.3 operator() . . . . . . . . . . . 11.4.4 Surcharge et mthode constante 11.4.5 "inline" . . . . . . . . . . . . . 11.5 Assertions . . . . . . . . . . . . . . . . 11.6 Types numrs . . . . . . . . . . . . . 11.7 Fiche de rfrence . . . . . . . . . . . . 12 En vrac (suite) ... 12.1 Oprateur binaires . . . . . 12.2 Valeur conditionnelle . . . . 12.3 Boucles et break . . . . . . 12.4 Variables statiques . . . . . 12.5 const et tableaux . . . . . . 12.6 template . . . . . . . . . . 12.6.1 Principe . . . . . . . 12.6.2 template et chiers . 12.6.3 Classes . . . . . . . . 12.6.4 STL . . . . . . . . . 12.7 Fiche de rfrence . . . . . . 12.8 Devoir nal . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

163 . 163 . 164 . 165 . 166 . 167 . 167 . 167 . 169 . 169 . 172 . 174 . 179 181 . 181 . 181 . 183 . 185 . 186 . 186 . 186 . 187 . 187

A Travaux Pratiques A.1 Lenvironnement de programmation . . . . A.1.1 Bonjour, Monde ! . . . . . . . . . . A.1.2 Premires erreurs . . . . . . . . . . A.1.3 Debugger . . . . . . . . . . . . . . A.1.4 Deuxime programme . . . . . . . A.1.5 Sil reste du temps . . . . . . . . . A.1.6 Installer Visual Studio chez soi . . A.2 Variables, boucles, conditions, fonctions . . A.2.1 Premier programme avec fonctions 4

TABLE DES MATIRES

TABLE DES MATIRES

A.3

A.4

A.5

A.6

A.7

A.8

A.9

A.2.2 Premier programme graphique avec la CLGraphics A.2.3 Jeu de Tennis . . . . . . . . . . . . . . . . . . . . . Tableaux . . . . . . . . . . . . . . . . . . . . . . . . . . . . A.3.1 Mastermind Texte . . . . . . . . . . . . . . . . . . A.3.2 Mastermind Graphique . . . . . . . . . . . . . . . . Structures . . . . . . . . . . . . . . . . . . . . . . . . . . . A.4.1 Etapes . . . . . . . . . . . . . . . . . . . . . . . . . A.4.2 Aide . . . . . . . . . . . . . . . . . . . . . . . . . . A.4.3 Thorie physique . . . . . . . . . . . . . . . . . . . Fichiers spars . . . . . . . . . . . . . . . . . . . . . . . . A.5.1 Fonctions outils . . . . . . . . . . . . . . . . . . . . A.5.2 Vecteurs . . . . . . . . . . . . . . . . . . . . . . . . A.5.3 Balle part . . . . . . . . . . . . . . . . . . . . . . A.5.4 Retour la physique . . . . . . . . . . . . . . . . . Les tris . . . . . . . . . . . . . . . . . . . . . . . . . . . . . A.6.1 Mlanger un tableau . . . . . . . . . . . . . . . . . A.6.2 Tris quadratiques . . . . . . . . . . . . . . . . . . . A.6.3 Quicksort . . . . . . . . . . . . . . . . . . . . . . . A.6.4 Gros tableaux . . . . . . . . . . . . . . . . . . . . . Images . . . . . . . . . . . . . . . . . . . . . . . . . . . . . A.7.1 Allocation . . . . . . . . . . . . . . . . . . . . . . . A.7.2 Tableaux statiques . . . . . . . . . . . . . . . . . . A.7.3 Tableaux dynamiques . . . . . . . . . . . . . . . . . A.7.4 Charger un chier . . . . . . . . . . . . . . . . . . . A.7.5 Fonctions . . . . . . . . . . . . . . . . . . . . . . . A.7.6 Structure . . . . . . . . . . . . . . . . . . . . . . . A.7.7 Suite et n . . . . . . . . . . . . . . . . . . . . . . Premiers objets et dessins de fractales . . . . . . . . . . . . A.8.1 Le triangle de Sierpinski . . . . . . . . . . . . . . . A.8.2 Une classe plutt quune structure . . . . . . . . . . A.8.3 Changer dimplmentation . . . . . . . . . . . . . . A.8.4 Le ocon de neige . . . . . . . . . . . . . . . . . . . Tron . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . A.9.1 Serpent . . . . . . . . . . . . . . . . . . . . . . . . A.9.2 Tron . . . . . . . . . . . . . . . . . . . . . . . . . . A.9.3 Graphismes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

187 189 191 191 193 195 195 197 198 200 200 200 201 201 203 203 204 204 205 207 207 207 208 208 208 209 209 210 210 211 211 211 212 212 213 213 215 215 215 215 217 217 217 219 219 219 220 220

B Examens B.1 Examen sur machine 2007 : nonc . . . . . . . B.1.1 Chemins entre deux points . . . . . . . . B.1.2 Travail demand . . . . . . . . . . . . . B.2 Examen sur machine 2006 : nonc . . . . . . . B.2.1 Voyageur de commerce par recuit simul B.2.2 Travail demand . . . . . . . . . . . . . B.3 Examen sur machine 2005 : nonc . . . . . . . B.3.1 Construction du Modle 3D . . . . . . . B.3.2 Projection : du 3D au 2D . . . . . . . . B.3.3 Achage lcran . . . . . . . . . . . . B.3.4 Animation du ttradre . . . . . . . . . 5

TABLE DES MATIRES

TABLE DES MATIRES

B.4

B.5

B.6 B.7 B.8

B.9

B.10 B.11 B.12 B.13

B.14

B.15

B.16

B.3.5 Un modle plus labor . . . . . . . . . . . . . . Examen sur machine 2004 : nonc . . . . . . . . . . . B.4.1 Calcul de lexponentielle dun nombre complexe B.4.2 Compression RLE . . . . . . . . . . . . . . . . . Examen sur machine 2003 : nonc . . . . . . . . . . . B.5.1 Crible dratosthne . . . . . . . . . . . . . . . B.5.2 Calcul de par la mthode de Monte Carlo . . B.5.3 Serpent . . . . . . . . . . . . . . . . . . . . . . Devoir maison 2007 : nonc . . . . . . . . . . . . . . . Devoir maison 2006 : nonc . . . . . . . . . . . . . . . B.7.1 Enonc Tours de Hanoi . . . . . . . . . . . . . Devoir maison 2004 : nonc . . . . . . . . . . . . . . . B.8.1 Tableau dexcution . . . . . . . . . . . . . . . B.8.2 Constructeurs . . . . . . . . . . . . . . . . . . . B.8.3 Le compte est bon . . . . . . . . . . . . . . . . Devoir maison 2003 : nonc . . . . . . . . . . . . . . . B.9.1 Tableau dexcution . . . . . . . . . . . . . . . B.9.2 Grands entiers . . . . . . . . . . . . . . . . . . . B.9.3 Constructeurs . . . . . . . . . . . . . . . . . . . Devoir surveill 2007 : nonc . . . . . . . . . . . . . . Groupes . . . . . . . . . . . . . . . . . . . . . . . . . . Anniversaires . . . . . . . . . . . . . . . . . . . . . . . Devoir surveill 2006 : nonc . . . . . . . . . . . . . . B.13.1 Erreurs corriger . . . . . . . . . . . . . . . . . B.13.2 Quache ce programme ? . . . . . . . . . . . . B.13.3 Tableau dexcution . . . . . . . . . . . . . . . B.13.4 Huit dames . . . . . . . . . . . . . . . . . . . . Devoir surveill 2005 : nonc . . . . . . . . . . . . . . B.14.1 Erreurs corriger . . . . . . . . . . . . . . . . . B.14.2 Quache ce programme ? . . . . . . . . . . . . B.14.3 Tableau dexcution . . . . . . . . . . . . . . . B.14.4 Rsolveur de Sudoku . . . . . . . . . . . . . . . Devoir surveill 2004 : nonc . . . . . . . . . . . . . . B.15.1 Erreurs . . . . . . . . . . . . . . . . . . . . . . . B.15.2 Quache ce programme ? . . . . . . . . . . . . B.15.3 Chemins dans un graphe . . . . . . . . . . . . . B.15.4 Tours de Hano . . . . . . . . . . . . . . . . . . B.15.5 Table de hachage . . . . . . . . . . . . . . . . . Devoir surveill 2003 : nonc . . . . . . . . . . . . . . B.16.1 Tableau dexcution . . . . . . . . . . . . . . . B.16.2 Erreurs . . . . . . . . . . . . . . . . . . . . . . . B.16.3 Quache ce programme ? . . . . . . . . . . . . B.16.4 Le jeu du Pendu . . . . . . . . . . . . . . . . . B.16.5 Programme mystre . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

221 222 222 222 225 225 225 227 229 231 231 234 234 235 237 240 240 241 241 244 244 246 247 247 248 250 252 254 254 254 256 257 260 260 261 264 265 268 270 270 271 272 274 275 277 279

C La CLGraphics D Fiche de rfrence nale

1. Prambule

Chapitre 1 Prambule
Note : Ce premier chapitre maladroit correspond ltat desprit dans lequel ce cours a dbut en 2003, dans une priode o lInformatique avait mauvaise presse lcole des ponts. Nous le maintenons ici en tant que tmoin de ce quil faillait faire alors pour amener les lves ne pas ngliger lInformatique. Si lon ignore la navet de cette premire rdaction (et le fait que Star Wars nest plus autant la mode !), lanalyse et les conseils qui suivent restent dactualit. (Ce premier chapitre tente surtout de motiver les lves ingnieurs dans leur apprentissage de la programmation. Les enfants qui se trouveraient ici pour apprendre programmer sont srement dj motivs et peuvent sauter au chapitre suivant ! Protons-en pour tenir des propos qui ne les concernent pas...) Le Matre Programmeur 1 : "Rassure toi ! Les ordinateurs sont stupides ! Programmer est donc facile." LApprenti Programmeur 2 : "Matre, les ordinateurs ne sont certes que des machines et les dominer devrait tre ma porte. Et pourtant... Leur manque dintelligence fait justement quil mest pnible den faire ce que je veux. Programmer exige de la prcision et la moindre erreur est sanctionne par un message incomprhensible, un bug 3 ou mme un crash de la machine. Pourquoi doit-on tre aussi... prcis ?" Programmer rend maniaque ! Dailleurs, les informaticiens sont tous maniaques. Et je nai pas envie de devenir comme a...
1. Permettez ce terme ouvertement Lucasien. Il semble plus appropri que lhabituel Gourou souvent utilis pour dcrire lexpert informaticien. Nous parlons bien ici dun savoir-faire transmettre de Matre Apprenti et non dune secte... 2. Le jeune Padawan, donc, pour ceux qui connaissent... 3. Je naurai aucun remord dans ce polycopi utiliser les termes habituels des informaticiens... en essayant videmment de ne pas oublier de les expliquer au passage. Anglicismes souvent incomprhensibles, ils constituent en ralit un argot propre au mtier dinformaticien, argot que doit bien videmment accepter et faire sien lApprenti sous peine de ne rien comprendre au discours de ses collgues dune part, et demployer des adaptations franaises ridicules ou peu usites dautre part. Naviguer sur la toile, envoyer un courriel ou avoir un bogue commencent peut-tre devenir des expressions comprhensibles. Mais demandez-donc votre voisin sil reoit beaucoup de pourriels (terme propos pour traduire "Spams") !

1. Prambule

M.P. : "La prcision est indispensable pour communiquer avec une machine. Cest lHomme de sadapter. Tu dois faire un eort. En contre-partie tu deviendras son matre. Rjouis-toi. Bientt, tu pourras crer ces tres obissants que sont les programmes." A.P. : "Bien, Matre..." Quel vieux fou ! Pour un peu, il se prendrait pour Dieu. La vrit, cest quil parle aux machines parce quil ne sait pas parler aux hommes. Il comble avec ses ordinateurs son manque de contact humain. Linformaticien type... Il ne lui manque plus que des grosses lunettes et les cheveux gras 4 . "Matre, je ne suis pas sr den avoir envie. Je ny arriverai pas. Ne le prenez pas mal, mais je crois tre davantage dou pour les Mathmatiques ! Et puis, quoi savoir programmer me servira-til ?" M.P. : "Les vrais problmes qui se poseront toi, tu ne pourras toujours les rsoudre par les Mathmatiques. Savoir programmer, tu devras !" A.P. : "Jessaierai..." Je me demande sil a vraiment raison ! Je suis sr quil doit tre nul en Maths. Voil la vrit ! ... Oublions l ce dialogue aussi caricatural que maladroit. Il montre pourtant clairement la situation. Rsumons : Pour celui qui sait, programmer : est un jeu denfant. est indispensable. est une activit cratrice et panouissante. Pour celui qui apprend, programmer : est dicile. ne sert rien. est une activit ingrate qui favorise le renfermement 5 sur soi-mme. Dans le cas o llve est ingnieur, nous pouvons complter le tableau : Pour le professeur, apprendre programmer : devrait tre simple et rapide pour un lve ingnieur. est plus utile quapprendre davantage de Mathmatiques. Pour llve, programmer : est un travail de "technicien 6 " quil naura jamais faire lui-mme. nest pas aussi noble que les Mathmatiques, bref, nest pas digne de lui. En fait, les torts sont partags : Le professeur : ne ralise pas que ses lves ont un niveau avanc en maths parce quils en font depuis plus de dix ans, et quil leur faudra du temps pour apprendre ne serait-ce que les bases de la programmation. Du temps... et de la pratique, car, si programmer est eectivement simple en regard de ce que ses lves savent faire en maths, il ncessite une tournure desprit compltement dirente et beaucoup de travail personnel devant la machine.
4. Toute ressemblance avec des personnages rels ou imaginaires, etc. 5. Utiliser un ordinateur pour programmer a tout aussi mauvaise presse que de jouer aux jeux vido. Programmer est pourtant souvent un travail dquipe. 6. avec tout le sens pjoratif que ce terme peut avoir pour lui.

1. Prambule

1.1. Pourquoi savoir programmer ?

oublie quil a le plus souvent appris seul quand il tait plus jeune, en programmant des choses simples et ludiques 7 . Il devrait donc faire venir ses lves la programmation par le ct ludique, et non avec les mmes sempiternels exemples 8 . Llve : ne se rend pas compte que savoir programmer lui sera utile. Il sagit pourtant dune base qui se retrouve dans tous les langages et mme dans la plupart des logiciels modernes 9 . Et puis, considr comme "le jeune" donc le moins "allergique" aux ordinateurs, il se verra vraisemblablement coner son premier poste la ralisation de quelques petits programmes en plus de ses attributions normales. sarrange un peu trop facilement dun mpris de bon ton pour la programmation. Il lui est plus ais dapprendre une n-ime branche des mathmatiques que de faire leort dacqurir par la pratique une nouvelle tournure desprit. On laura compris, il est la fois facile et dicile dapprendre programmer. Pour lingnieur, cela demandera de la motivation et un peu deort : essentiellement de mettre ses maths de ct et de retrouver le got des choses basiques. Pour un collgien, motivation et got de leort seront au rendez-vous. Il lui restera malgr tout acqurir quelques bases darithmtique et de gomtrie. Comme annonc par le titre de ce cours, collgien et ingnieur en sont au mme point pour lapprentissage de la programmation. De plus, et cest un phnomne relativement nouveau, il en est de mme pour le dbutant et le "geek 10 ". Expliquons nous : le passionn dinformatique a aujourdhui tellement de choses faire avec son ordinateur quil sera en gnral incollable sur les jeux, internet, les logiciels graphiques ou musicaux, linstallation ou la conguration de son systme, lachat du dernier gadget USB la mode, etc. mais quen contrepartie il sera mauvais programmeur. Il y a quelques annes, il y avait peu faire avec son ordinateur sinon programmer. Programmer pour combler le manque de possibilits de lordinateur. Aujourdhui, faire le tour de toutes les possibilits dun ordinateur est une occupation plein temps ! Ainsi, le "fana info" passe-til sa journe se tenir au courant des nouveaux logiciels 11 et en oublie quil pourrait lui aussi en crer. En conclusion, collgiens ou ingnieurs, dbutants ou passiones, tous les lves sont galit. Cest donc sans complexe que lingnieur pourra apprendre programmer en mme temps que le ls de la voisine.

1.1

Pourquoi savoir programmer ?

Nous venons partiellement de le dire. Rsumons et compltons : 1. Cest la base. Apprendre un langage prcis nest pas du temps perdu car les mmes concepts se retrouvent dans la plupart des langages. De plus, les logiciels courants eux-mmes peuvent se programmer.
7. Cest une erreur frquente de croire quil intressera ses lves en leur faisant faire des programmes centrs sur les mathmatiques ou le calcul scientique. De tels programmes leur seront peut-tre utiles plus tard, mais ne sont pas forcment motivants. Lalgbre linaire ou lanalyse numrique sont des domaines passionnants tudier... mais certainement pas programmer. Il faut admettre sans complexe que programmer un ipper, un master-mind ou un labyrinthe 3D est tout aussi formateur et plus motivant quinverser une matrice creuse. 8. La liste est longue, mais tellement vraie : quel cours de programmation ne rabche pas les clbres "factorielle", "suites de Fibonacci", "Quick Sort", etc ? 9. Savoir programmer ne sert pas seulement faire du C++ ou du Java, ni mme du Scilab, du Matlab ou du Maple : une utilisation avance dExcel ou du Word demande parfois de la programmation ! 10. Une rcompense qui me trouve un substitut satisfaisant cette expression consacre. 11. Sans mme dailleurs avoir le temps den creuser convenablement un seul !

1.2. Comment apprendre ?

1. Prambule

2. Il est frquent quun stage ou quune embauche en premier poste comporte un peu de programmation, mme, et peut-tre surtout, dans les milieux o peu de gens programment. 3. Savoir programmer, cest mieux connatre le matriel et les logiciels, ce qui est possible techniquement et ce qui ne lest pas. Mme un poste non technique, cest important pour prendre les bonnes dcisions.

1.2
1.2.1

Comment apprendre ?
Choix du langage

Il faut dabord choisir un langage de programmation. Un ingnieur pourrait videmment tre tent dapprendre programmer en Maple, Matlab, Scilab ou autre. Il faut quil comprenne quil sagit l doutils spcialiss pour mathmaticien ou ingnieur qui lui seront utiles et qui, certes, se programment, mais pas proprement parler de langages gnralistes complets. Sans argumenter sur les dfauts respectifs des langages qui en font partie, il nous semble vident quil ne sagit pas du bon choix pour lapprentissage de la programmation. En pratique, le choix actuel se fait souvent entre C++ et Java. Bien que Java aie t conu, entre autres, dans un soucis de simplication du C++ 12 , nous prfrerons C++ pour des raisons pdagogiques : 1. C++ est plus complexe dans son ensemble mais nen connatre que les bases est dj bien susant. Nous ne verrons donc dans ce cours quun sous ensemble du C++, susant en pratique. 2. Plus complet, C++ permet une programmation de haut niveau mais aussi une programmation simple, adapte au dbutant 13 . C++ permet galement une programmation proche de la machine, ce qui est important pour le spcialiste mais aussi pour le dbutant, car seule une bonne comprhension de la machine aboutit une programmation convenable et ecace 14 . 3. C++ est souvent incontournable dans certains milieux, par exemple en nance. 4. Enn, certains aspects pratiques et pourtant simples de C++ ont disparu dans Java 15 . Encore une fois, rptons que le choix du langage nest pas le plus important et que lessentiel est dapprendre programmer.

1.2.2

Choix de lenvironnement

Windows et Linux ont leurs partisans, souvent farouchement opposs, tel point que certains nadmettent pas quil est possible dtre partisan des deux systmes la fois. Conscients des avantages et des inconvnients de chacun des deux systmes, nous nen prnons aucun en particulier 16 . Ceci dit, pour des raisons pdagogiques, nous pensons
12. Nous ne rduisons videmment pas Java un sous ensemble de C++. Il lui est suprieur sur certains aspects mais il est dexpressivit plus rduite. 13. Java force un cadre de programmation objet, droutant pour le dbutant. 14. Ne pas comprendre ce que la machine doit faire pour excuter un programme, conduit des programmes inconsidrment gourmands en temps ou mmoire. 15. Les oprateurs par exemple. 16. Lidal est en fait davoir les deux "sous la main".

10

1. Prambule

1.2. Comment apprendre ?

quun environnement de programmation intgr, cest dire un logiciel unique permettant de programmer, est prfrable lutilisation de multiples logiciels (diteur, compilateur, debuggeur, etc.). Cest vrai pour le programmeur conrm, qui trouve en gnral dans cet environnement des outils puissants, mais cest encore plus crucial pour le dbutant. Un environnement de programmation, cest : Toutes les tapes de la programmation regroupes en un seul outil de faon cohrente. Editer ses chiers, les transformer en programme, passer en revue ses erreurs, dtecter les bugs, parcourir la documentation, etc. tout cela avec un seul outil ergonomique. Sans arrire pense de principe, nous avons opt pour lenvironnement de Microsoft, Visual Studio, la fois simple et puissant. Il est le plus utilis des produits commerciaux. Il en existe quelques quivalents gratuits sous linux, mais pas encore susamment aboutis pour nous faire hsiter. Cest donc le choix de Visual Studio et ce choix seul qui est la raison de lutilisation de Windows au dtriment de linux... Mieux encore, il existe maintenant une version de Visual gratuite : Visual Express. Comme pour le choix du langage, le choix de lenvironnement nest pas limitant et en connatre un permet de sadapter facilement nimporte quel autre.

1.2.3

Principes et conseils

Au niveau auquel nous prtendons lenseigner, la programmation ne requiert ni grande thorie, ni connaissances encyclopdiques. Les concepts utiliss sont rudimentaires mais cest leur mise en oeuvre qui est dlicate. Sil ny avait quun seul conseil donner, ce serait la rgle des trois "P" : 1. Programmer 2. Programmer 3. Programmer La pratique est eectivement essentielle. Cest ce qui fait quun enfant a plus de facilits, puisquil a plus de temps. Ajoutons quand mme quelques conseils de base : 1. Samuser. Cest une vidence en matire de pdagogie. Mais cest tellement facile dans le cas de la programmation, quil serait dommage de passer ct ! Au pire, si programmer nest pas toujours une partie de plaisir pour tout le monde, il vaut mieux que le programme obtenu dans la douleur soit intressant pour celui qui la fait ! 2. Bricoler. Ce que nous voulons dire par l, cest quil ne faut pas hsiter ttonner, tester, fouiller, faire, dfaire, casser, etc. Lordinateur est un outil exprimental. Mais sa programmation est elle aussi une activit exprimentale la base. Mme si le programmeur aguerri trouvera la bonne solution du premier jet, il est important pour le dbutant dapprendre connatre le langage et loutil de programmation en jouant avec eux. 3. Faire volontairement des erreurs. Provoquer les erreurs pendant la phase dapprentissage pour mieux les connatre est le meilleur moyen de comprendre beaucoup de choses et aussi de reprer ces erreurs quand elles ne seront plus volontaires. 4. Rester (le) matre 17 (de la machine et de son programme). Que programmer soit
17. Le vocabulaire nest pas choisi au hasard : un programme est une suite dordres, de commandes ou dinstructions. On voit bien qui est le chef !

11

1.2. Comment apprendre ?

1. Prambule

exprimental ne signie pas pour autant quil faille faire nimporte quoi jusqu ce que a marche plus ou moins. Il faut avancer progressivement, mthodiquement, en testant au fur et mesure, sans laisser passer la moindre erreur ou imprcision. 5. Debugger. Souvent, la connaissance du debuggeur (loutil pour rechercher les bugs) est nglige et son apprentissage est repouss au stade avanc. Il sagit pourtant dun outil essentiel pour comprendre ce qui se passe dans un programme, mme dpourvu de bugs. Il faut donc le considrer comme essentiel et faisant partie intgrante de la conception dun programme. L encore, un bon environnement de programmation facilite la tche. Gardons bien prsents ces quelques principes car il est maintenant temps de... passer notre premier programme !

12

2. Bonjour, Monde !

Chapitre 2 Bonjour, Monde !


(Si certains collgiens sont arrivs ici, ils sont bien courageux ! Lorsque je disais tout lheure quils pouvaient facilement apprendre programmer, je le pensais vraiment. Par contre, cest avec un peu doptimisme que jai prtendu quils pouvaient le faire en lisant un polycopi destin des ingnieurs. Enn, je suis pris mon propre pige ! Alors, tout hasard, je vais tenter dexpliquer au passage les mathmatiques qui pourraient leur poser problme.) Si lon en croit de nombreux manuels de programmation, un premier programme doit toujours ressembler a :
1 2 3 4 5 6 7 8

#include <iostream> using namespace std; int main() { cout << "Hello, World!" << endl; return 0; }

Eh bien, allons-y ! Dcortiquons-le ! Dans ce programme, qui ache lcran 1 le texte "Hello, World !", les lignes 1 et 2 sont des instructions magiques 2 qui servent pouvoir utiliser dans la suite cout et endl. La ligne 4 int main() dnit une fonction appele main(), qui renvoie 3 un nombre entier. Cette fonction est spciale car cest la fonction principale dun programme C++, celle qui est appele automatiquement 4 quand le pro1. Cette expression, vestige de lpoque o les ordinateurs taient dots dun cran capable de nacher que des caractres et non des graphiques (courbes, dessins, etc.), signie aujourdhui que lachage se fera dans une fentre simulant lcran dun ordinateur de cette poque. Cette fentre est appele terminal, console, fentre de commande, fentre DOS, xterm, etc. suivant les cas. Souvenons nous avec un minimum de respect que ctait dj un progrs par rapport la gnration prcdente, dpourvue dcran et qui utilisait une imprimante pour communiquer avec lhomme... ce qui tait relativement peu interactif ! 2. Entendons par l des instructions que nous nexpliquons pas pour linstant. Il ny a (mal ?)heureusement rien de magique dans la programmation. 3. On dit aussi retourne. A qui renvoie-telle cet entier ? Mais celui qui la appele, voyons ! 4. Voil, maintenant vous savez qui appelle main(). Dans un programme, les fonctions sappellent les unes les autres. Mais main() nest appele par personne puisque cest la premire de toutes. (Du moins en apparence car en ralit le programme a plein de choses faire avant darriver dans main() et il commence par plusieurs autres fonctions que le programmeur na pas connatre et qui nissent par appeler main(). Dailleurs, si personne ne lappelait, qui main() retournerait-elle un entier ?)

2. Bonjour, Monde !

gramme est lanc 5 . Dlimite par les accolades ({ ligne 5 et } ligne 8), la fonction main() se termine ligne 7 par return 0; qui lui ordonne de retourner lentier 0. Notons au passage que toutes les instructions se terminent par un point-virgule ;. Enn, la ligne 6, seule ligne "intressante", cout << "Hello, World!" << endl; ache, grce la variable 6 cout qui correspond la sortie console 7 , des donnes spares par des <<. La premire de ces donnes est la chane de caractres 8 "Hello, World!". La deuxime, endl, est un retour la ligne 9 . Ouf ! Que de termes en italique. Que de concepts essayer dexpliquer ! Et pour un programme aussi simple ! Mais l nest pas le problme. Commencer par expliquer ce programme, cest tre encore dans le vide, dans le magique, dans labstrait, dans lapproximatif. Nous ne sommes pas rellement matres de la machine. Taper des instructions et voir ce qui se passe sans comprendre ce qui se passe nest pas raisonnable. En fait, cest mme trs dommageable pour la suite. On ne donne pas ecacement dordre quelquun sans comprendre comment il fonctionne ni ce que les ordres donns entranent comme travail. De mme, on ne programme pas convenablement sans comprendre ce que lordinateur aura exactement besoin de faire pour excuter ce programme. Cest toute cette approche qui est nglige quand on commence comme nous venons de le faire. Donc... Stop ! Stop ! Stop ! Faux dpart ! On reprend le :

5. Je savais bien que vouloir expliquer tous les barbarismes propres aux informaticiens minterromprait souvent. Mais bon. Donc, un programme dmarre ou est lanc. Aprs quoi, il sexcute ou tourne. Enn, il se termine ou meurt. 6. Les donnes sont ranges ou stockes dans des variables qui mmorisent des valeurs. Ces variables ne sont dailleurs pas toujours variables au sens usuel, puisque certaines sont constantes ! 7. Quest-ce que je disais ! On ache dans une fentre console ! 8. En clair, un texte. 9. Ce qui signie que la suite de lachage sur la console se fera sur une nouvelle ligne.

14

2. Bonjour, Monde !

2.1. Lordinateur

Chapitre 2 (deuxime essai) Comment a marche ?


Le problme avec le programme prcdent est quil est trs loin de ce quun ordinateur sait faire naturellement. En fait, un ordinateur ne sait pas faire de C++. Il ne sait que calculer 10 , transformer des nombres en autres nombres. Bien que peu comprhensible pour le dbutant, un programme en C++ se veut le plus proche possible de lHomme, tout en restant videmment accessible 11 la machine. Le C++ est un langage trs complet, peuttre mme trop. Il peut tre relativement proche de la machine si ncessaire et au contraire de "haut niveau" quand il le faut. La largeur de son spectre est une des raisons de son succs. Cest aussi ce qui fait que son apprentissage complet demande un long travail et nous ne verrons ici quun partie restreinte du C++ !

2.1

Lordinateur

Pour savoir ce quun ordinateur sait vraiment faire, il faut commencer par son organe principal : le micro-processeur.

2.1.1

Le micro-processeur

Quel quil soit 12 et quelle que soit sa vitesse 13 , un micro-processeur ne sait faire que des choses relativement basiques. Sans tre exhaustif, retenons juste ceci : Il sait excuter une suite ordonne dinstructions. Il possde un petit nombre de mmoires internes appeles registres. Il dialogue avec le monde extrieur via de la mmoire 14 en plus grande quantit que ses registres. Cette mmoire contient, sous forme de nombres, les instructions excuter et les donnes sur lesquelles travailler. Les instructions sont typiquement : Lire ou crire un nombre dans un registre ou en mmoire. Eectuer des calculs simples : addition, multiplication, etc. Tester ou comparer des valeurs et dcider ventuellement de sauter une autre partie de la suite dinstructions. Voici par exemple ce que doit faire le micro-processeur quand on lui demande dexcuter "c=3*a+2*b;" en C++, o a,b,c sont trois variables entires :
10. Un computer, quoi ! 11. Cette notion est videmment dpendante de notre savoir faire informatique linstant prsent. Les premiers langages taient plus loigns de lHomme car plus proches de la machine qui tait alors rudimentaire, et lon peut envisager que les futurs langages seront plus proches de lHomme. 12. Pentium ou autre 13. Plus exactement la frquence laquelle il excute ses instructions. Aujourdhui lhorloge va environ 3GHz. (Mais attention : une instruction demande plus dun cycle dhorloge !) 14. Aujourdhui, typiquement 1Go (giga-octets), soit 1024 1024 1024 mmoires de 8 bits (mmoires pouvant stocker des nombres entre 0 et 255).

15

2.1. Lordinateur

2. Bonjour, Monde !

00415A61

mov

00415A64 00415A67 00415A6A 00415A6D

eax,dword ptr [a] // // // imul eax,eax,3 // mov ecx,dword ptr [b] // lea edx,[eax+ecx*2] // mov dword ptr [c],edx // // //

mettre dans le registre eax le contenu de ladresse o est mmorise la variable a effectuer eax=eax*3 idem mais b dans ecx effectuer edx=eax+ecx*2 mettre le contenu du registre edx ladresse o est mmorise la variable c

Sous lenvironnement Visual Studio que nous utiliserons, ce programme est dsign comme du Code Machine. Le nombre au dbut de chaque ligne est une adresse. Nous allons en reparler. A part lui, le reste est relativement lisible pour lHomme (attention, cest moi qui ai ajout les remarques sur le cot droit !). Ceci parce quil sagit dun programme en langage assembleur, cest--dire un langage o chaque instruction est vraiment une instruction du micro-processeur, mais o le nom de ces instructions ainsi que leurs arguments sont explicites. En ralit, le micro-processeur ne comprend pas lassembleur. Comprendre "mov eax,dword ptr [a]" lui demanderait non seulement de dcoder cette suite de symboles, mais aussi de savoir o est range la variable a. Le vrai langage du micro-processeur est le langage machine, dans lequel les instructions sont des nombres. Voici ce que a donne pour notre "c=3*a+2*b;" :
00415A61 00415A64 00415A67 00415A6A 00415A6D 8B 6B 8B 8D 89 45 C0 4D 14 55 F8 03 EC 48 E0

A part encore une fois la colonne de gauche, chaque suite de nombres 15 correspond videmment une instruction prcise. Cest tout de suite moins comprhensible 16 ! Notons que chaque micro-processeur son jeu dinstructions ce qui veut dire que la traduction de c=3*a+2*b; en la suite de nombres 8B45F86BC0038B4DEC8D14488955E0 est propre au Pentium que nous avons utilis pour notre exemple : Une fois traduit en langage machine pour un micro-processeur donn, un programme C++ na de sens que pour ce micro-processeur. Remarquons aussi que les concepteurs du Pentium ont dcid de crer une instruction spcique pour calculer edx=eax+ecx*2 en une seule fois car elle est trs frquente. Si on avait demand c=3*a+3*b;, notre programme serait devenu :
00415A61 00415A64 00415A67 00415A6A 00415A6D 00415A6F 8B 6B 8B 6B 03 89 45 C0 4D C9 C1 45 F8 03 EC 03 E0 mov imul mov imul add mov eax,dword ptr [a] eax,eax,3 ecx,dword ptr [b] ecx,ecx,3 eax,ecx dword ptr [c],eax

car "lea edx,[eax+ecx*3]" nexiste pas ! Mais revenons nos nombres...


15. Nombres un peu bizarres, certes, puisquil contiennent des lettres. Patience, jeune Padawan ! Nous en reparlons aussi tout de suite ! 16. Et pourtant, les informaticiens programmaient comme cela il ny a pas si longtemps. Ctait dj trs bien par rapport lpoque antrieure o il fallait programmer en base 2... et beaucoup moins bien que lorsquon a pu enn programmer en assembleur !

16

2. Bonjour, Monde !

2.1. Lordinateur

2.1.2

La mmoire

La mmoire interne du micro-processeur est gre comme des registres, un peu comme les variables du C++, mais en nombre prdni. Pour stocker 17 la suite dinstructions lui fournir, on utilise de la mmoire en quantit bien plus importante, dsigne en gnral par la mmoire de lordinateur. Il sagit des fameuses "barrettes" 18 de mmoire que lon achte pour augmenter la capacit de sa machine et dont les prix uctuent assez fortement par rapport au reste des composants dun ordinateur. Cette mmoire est dcoupe en octets. Un octet 19 correspond un nombre binaire de 8 bits 20 , soit 28 = 256 valeurs possibles. Pour se reprer dans la mmoire, il nest pas question de donner des noms chaque octet. On numrote simplement les octets et on obtient ainsi des adresses mmoire. Les nombres 00415A61, etc. vus plus haut sont des adresses ! Au dbut, ces nombres taient crits en binaire, ce qui tait exactement ce que comprenait le micro-processeur. Cest devenu draisonnable quand la taille de la mmoire a dpass les quelques centaines doctets. Le contenu dun octet de mmoire tant lui aussi donn sous la forme dun nombre, on a opt pour un systme adapt au fait que ces nombres sont sur 8 bits : plutt que dcrire les nombre en binaire, le choix de la base 16 permettait de reprsenter le contenu dun octet sur deux chires (0,1,...,9,A,B,C,D,E,F). Le systme hexadcimal 21 tait adopt... Les conversions de binaire hexadcimal sont trs simples, chaque chire hexadcimal valant pour un paquet de 4 bits, alors quentre binaire et dcimal, cest moins immdiat. Il est aujourdhui encore utilis quand on dsigne le contenu dun octet ou une adresse 22 . Ainsi, notre fameux c=3*a+2*b; devient en mmoire : adresse mmoire 00415A61 00415A62 00415A63 00415A64 00415A65 ... contenu 8B 45 F8 6B C0 ... reprsente mov eax,dword ptr [a]

imul eax,eax,3

17. Encore un anglicisme... 18. Aujourdhui, typiquement une ou plusieurs barrettes pour un total de 1 ou 2Go, on la dj dit. Souvenons nous avec une larme loeil des premiers PC qui avaient 640Ko (kilo-octet soit 1024 octets), voire pour les plus ags dentre nous des premiers ordinateurs personnels avec 4Ko, ou mme des premires cartes programmables avec 256 octets ! 19. byte en anglais. Attention donc ne pas confondre byte et bit, surtout dans des abrviations comme 512kb/s donnes pour le dbit dun accs internet... b=bit, B=byte=8 bits 20. Le coin des collgiens : en binaire, ou base 2, on compte avec deux chires au lieu de dix dhabitude (cest dire en dcimal ou base 10). Cela donne : 0, 1, 10, 11, 100, 101, 110, 111, ... Ainsi, 111 en binaire vaut 7 . Chaque chire sappelle un bit. On voit facilement quavec un chire on compte de 0 1 soit deux nombres possibles ; avec deux chires, de 0 3, soit 4 = 2 2 nombres ; avec 3 chires, de 0 7, soit 8 = 2 2 2 nombres. Bref avec n bits, on peut coder 2n (2 multipli par lui-mme n fois) nombres. Je me souviens avoir appris la base 2 en grande section de maternelle avec des cubes en bois ! trange programme scolaire. Et je ne dis pas a pour me trouver une excuse dtre devenu informaticien. Quoique... 21. Coin des collgiens (suite) : en base 16, ou hexadcimal, on compte avec 16 chires. Il faut inventer des chires au del de 9 et on prend A,B,C,D,E,F. Quand on compte, cela donne : 0, 1, 2, ..., 9, A, B, C, D, E, F, 10, 11, 12, 13, ..., 19, 1A, 1B, 1C, ... Ainsi 1F en hexadcimal vaut 31. Avec 1 chire, on compte de 0 15 soit 16 nombres possibles ; avec 2 chires, de 0 255 soit 256 = 16 16 nombres possibles, etc. Un octet peut scrire avec 8 bits en binaire, ou 2 nombres en hexadcimal et va de 0 255, ou 11111111 en binaire, ou FF en hexadcimal. 22. Dans ce cas, sur plus de 2 chires : 8 pour les processeurs 32 bits, 16 pour les processeurs 64 bits.

17

2.1. Lordinateur

2. Bonjour, Monde !

La mmoire ne sert pas uniquement stocker la suite dinstructions excuter mais aussi toutes les variables et donnes du programme, les registres du micro-processeur tant insusants. Ainsi nos variables a,b,c sont elles stockes quelque part en mmoire sur un nombre doctets susant 23 pour reprsenter des nombres entiers (ici 4 octets) et dans un endroit dcid par le C++, de tel sorte que linstruction 8B45F8 aille bien chercher la variable a ! Cest un travail pnible, que le C++ fait pour nous et que les programmeurs faisaient autrefois la main 24 . Bref, on a en plus 25 : adresse mmoire ... 00500000 00500001 00500002 00500003 00500004 00500005 00500006 00500007 ... contenu ... a1 a2 a3 a4 b1 b2 b3 b4 ... reprsente

o les octets a1 , ..., a4 combins donnent lentier a sur 32 bits. Certains processeurs (dits little-endians dcident a = a1 a2 a3 a4 , dautres (big-endians) que a = a4 a3 a2 a1 . Cela signie que : Tout comme pour les instructions, un nombre stock par un microprocesseur dans un chier peut ne pas tre comprhensible par un autre micro-processeur qui relit le chier !

2.1.3

Autres Composants

Micro-processeur et mmoire : nous avons vu le principal. Compltons le tableau avec quelques autres lments importants de lordinateur. Types de mmoire La mmoire dont nous parlions jusquici est de la mmoire vive ou RAM. Elle est rapide 26 mais a la mauvaise ide de seacer quand on teint lordinateur. Il faut donc aussi de la mmoire morte ou ROM, cest--dire de la mmoire conservant ses donnes quand
23. Les variables ayant plus de 256 valeurs possibles sont forcment stockes sur plusieurs octets. Ainsi, avec 4 octets on peut compter en binaire sur 48 = 32 bits, soit 232 valeurs possibles (plus de 4 milliards). 24. Ce qui tait le plus pnible ntait pas de dcider o il fallait ranger les variables en mmoire, mais dajuster les instructions en consquence. Si on se trompait, on risquait dcrire au mauvais endroit de la mmoire. Au mieux, cela eaait une autre variable ce comportement est encore possible de nos jours au pire, cela eaait des instructions et le programme pouvait faire de "grosses btises" ceci est aujourdhui impossible sous Windows ou Linux, et ne concerne plus que certains systmes. 25. Nous faisons ici un horrible mensonge des ns simplicatrices. Dans notre cas, les variables taient des variables locales la fonction main() donc stockes dans la pile. Elles ne sont pas une adresse mmoire dnie lavance de manire absolue mais une adresse relative lemplacement o la fonction rangera ses variables locales en fonction de ce que le programme aura fait avant. Cela explique la simplicit de linstruction mov eax,dword ptr [a] dans notre cas. Nous verrons tout cela plus tard. 26. Moins que les registres, ou mme que le cache mmoire du processeur, dont nous ne parlerons pas ici.

18

2. Bonjour, Monde !

2.1. Lordinateur

lordinateur est teint mais qui en contre-partie ne peut tre modie 27 . Cette mmoire contient en gnral le minimum pour que lordinateur dmarre et excute une tche prdnie. Initialement, on y stockait les instructions ncessaires pour que le programmeur puisse remplir ensuite la RAM avec les instructions de son programme. Il fallait retaper le programme chaque fois 28 ! On a donc rapidement eu recours des moyens de stockage pour sauver programmes et donnes lextinction de lordinateur. Il susait alors de mettre en ROM le ncessaire pour grer ces moyens de stockages. Moyens de stockage Certains permettent de lire des donnes, dautres den crire, dautres les deux la fois. Certains ne dlivrent les donnes que dans lordre, de manire squentielle, dautres, dans lordre que lon veut, de manire alatoire. Ils sont en gnral bien plus lents que la mmoire et cest srement ce quil faut surtout retenir ! On recopie donc en RAM la partie des moyens de stockage sur laquelle on travaille. Faire travailler le micro-processeur avec le disque dur est BEAUCOUP plus lent quavec la mmoire (1000 fois plus lent en temps daccs, 100 fois plus en dbit) a
a. Rajoutez un facteur 50 supplmentaire entre la mmoire et la mmoire cache du processeur !

Au dbut, les moyens de stockages taient mcaniques : cartes ou bandes perfores. Puis ils devinrent magntiques : mini-cassettes 29 , disquettes 30 , disques durs 31 ou bandes magntiques. Aujourdhui, on peut rajouter les CD, DVD, les cartes mmoire, les "cls USB", etc, etc. Priphriques On appelle encore priphriques dirents appareils relis lordinateur : clavier, souris, cran, imprimante, modem, scanner, etc. Ils taient initialement l pour servir dinterface avec lHomme, comme des entres et des sorties entre le micro-processeur et la ralit. Maintenant, il est dicile de voir encore les choses de cette faon. Ainsi les cartes graphiques, qui pouvaient tre considres comme un priphrique allant avec lcran, sont-elles devenues une partie essentielle de lordinateur, vritables puissances de calcul, tel point que certains programmeur les utilisent pour faire des calculs sans mme acher quoi que ce soit. Plus encore, cest lordinateur qui est parfois juste considr comme maillon entre dirents appareils. Qui appellerait priphrique un camscope quon relie un ordinateur pour envoyer des vidos sur internet ou les transfrer sur un DVD ? Ce serait presque lordinateur qui serait un priphrique du camscope !
27. Il est pnible quune ROM ne puisse tre modie. Alors, une poque, on utilisait des mmoires modiables malgr tout, mais avec du matriel spcialis (EPROMS). Maintenant, on a souvent recours de la mmoire pouvant se modier de faon logicielle (mmoire "ashable") ou, pour de trs petites quantits de donnes, une mmoire consommant peu (CMOS) et complte par une petite pile. Dans un PC, la mmoire qui sert dmarrer sappelle le BIOS. Il est ashable et ses paramtres de rglage sont en CMOS. Attention lusure de la pile ! 28. A chaque fois quon allumait lordinateur mais aussi chaque fois que le programme plantait et seaait lui-mme, cest--dire la plupart du temps ! 29. Trs lent et trs peu able, mais le quotidien des ordinateurs personnels. 30. Le luxe. Un lecteur de 40Ko cotait 5000F ! 31. Les premiers taient de vritables moteurs de voiture, rservs aux importants centres de calcul.

19

2.2. Systme dexploitation

2. Bonjour, Monde !

2.2

Systme dexploitation

Notre vision jusquici est donc la suivante : 1. Le processeur dmarre avec les instructions prsentes en ROM. 2. Ces instructions lui permettent de lire dautres instructions prsentes sur le disque dur et quil recopie en RAM. 3. Il excute les instructions en question pour il lire des donnes (entres) prsentes elles-aussi sur le disque dur et gnrer de nouvelles donnes (sorties). A moins que les entres ou les sorties ne soient changes via les priphriques. Assez vite, ce principe a volu : 1. Le contenu du disque dur a t organis en chiers. Certains chiers reprsentaient des donnes 32 , dautres des programmes 33 , dautres encore contenaient eux-mmes des chiers 34 . 2. Les processeurs devenant plus rapides et les capacits du disque dur plus importantes, on a eu envie de grer plusieurs programmes et den excuter plusieurs : lun aprs lautre, puis plusieurs en mme temps (multi-tches), puis pour plusieurs utilisateurs en mme temps (multi-utilisateurs) 35 , enn avec plusieurs processeurs par machine. Pour grer tout cela, sest dgag le concept de systme dexploitation 36 . Windows, Unix (dont linux) et MAC/OS sont les plus rpandus. Le systme dexploitation est aujourdhui responsable de grer les chiers, les interfaces avec les priphriques ou les utilisateurs 37 , mais son rle le plus dlicat est de grer les programmes (ou tches ou process) en train de sexcuter. Il doit pour cela essentiellement faire face deux problmes 38 : 1. Faire travailler le processeur successivement par petites tranches sur les dirents programmes. Il sagit de donner la main de manire intelligente et quitable, mais aussi de replacer un process interrompu dans la situation quil avait quitte lors de son interruption. 2. Grer la mmoire ddie chaque process. En pratique, une partie ajustable de la mmoire est rserve chaque process. La mmoire dun process devient mmoire virtuelle : si un process est dplac un autre endroit de la mmoire physique (la RAM), il ne sen rend pas compte. On en prote mme pour mettre temporairement hors RAM (donc sur disque dur) un process en veille. On peut aussi utiliser le disque dur pour quun process utilise plus de mmoire que la mmoire physique : mais attention, le disque tant trs lent, ce process risque de devenir lui aussi trs lent.
32. Les plus courantes taient les textes, o chaque octet reprsentait un caractre. Ctait le clbre code ASCII (65 pour A, 66 pour B, etc.). A lre du multimdia, les formats sont aujourdhui nombreux, concurrents, et plus ou moins normaliss. 33. On parle de chier excutable... 34. Les rpertoires. 35. Aujourdhui, cest pire. Un programme est souvent lui mme en plusieurs parties sexcutant en mme temps (les threads). Quant au processeur, il excute en permanence plusieurs instructions en mme temps (on dit quil est super-scalaire) ! 36. Operating System 37. Esprons quun jour les utilisateurs ne seront pas eux-aussi des priphriques ! 38. Les processeurs ont videmment volu pour aider le systme dexploitation faire cela ecacement.

20

2. Bonjour, Monde !

2.3. La Compilation

Lorsquun process besoin de trop de mmoire, il utilise, sans prvenir, le disque dur la place de la mmoire et peut devenir trs lent. On dit quil swappe (ou pagine). Seule sa lenteur (et le bruit du disque dur !) permet en gnral de sen rendre compte (on peut alors sen assurer avec le gestionnaire de tche du systme). Autre progrs : on gre maintenant la mmoire virtuelle de faon sparer les process entre eux et, au sein dun mme process, la mmoire contenant les instructions de celle contenant les donnes. Il est rigoureusement impossible quun process bugg puisse modier ses instructions ou la mmoire dun autre process en crivant un mauvais endroit de la mmoire 39 . Avec larrive des systmes dexploitation, les chiers excutables ont du sadapter pour de nombreuse raisons de gestion et de partage de la mmoire. En pratique, un programme excutable linux ne tournera pas sous Windows et rciproquement, mme sils contiennent tous les deux des instructions pour le mme processeur. Un chier excutable est spcique, non seulement un processeur donn, mais aussi un systme dexploitation donn. Au mieux, tout comme les versions successives dune famille de processeur essaient de continuer comprendre les instructions de leurs prdcesseurs, tout comme les versions successives dun logiciel essaient de pouvoir lire les donnes produites avec les versions prcdentes, les direntes versions dun systme dexploitation essaient de pouvoir excuter les programmes faits pour les versions prcdentes. Cest la compatibilit ascendante, que lon paye souvent au prix dune complexit et dune lenteur accrues.

2.3

La Compilation

Tout en essayant de comprendre ce qui se passe en dessous pour en tirer des informations utiles comme la gestion de la mmoire, nous avons entrevu que transformer un programme C++ en un chier excutable est un travail dicile mais utile. Certains logiciels disposant dun langage de programmation comme Maple ou Scilab ne transforment pas leurs programmes en langage machine. Le travail de traduction est fait lexcution du programme qui est alors analys au fur et mesure 40 : on parle alors de langage interprt. Lexcution alors est videmment trs lente. Dautres langages, comme Java, dcident de rsoudre les problmes de portabilit, cest--dire de dpendance au processeur et au systme, en plaant une couche intermdiaire entre le processeur et le programme : la machine virtuelle. Cette machine, videmment crite pour un processeur et un systme donns, peut excuter des programmes dans un langage machine virtuel 41 , le "byte code". Un programme Java est alors traduit en son quivalent dans ce langage machine. Le rsultat peut tre excut sur nimporte quelle machine virtuelle Java. La contrepartie de cette portabilit est videmment une perte decacit. La traduction en code natif ou en byte code dun programme sappelle la compilation 42 . Un langage compil est alors opposer un langage interprt. Dans le cas du
39. Il se contente de modier anarchiquement ses donnes, ce qui est dj pas mal ! 40. mme sil est parfois pr-trait pour acclrer lexcution. 41. Par opposition, le "vrai" langage machine du processeur est alors appel code natif. 42. Les principes de la compilation sont une des matires de base de linformatique, traditionnelle et trs formatrice. Quand on sait programmer un compilateur, on sait tout programmer (videmment, un

21

2.4. Lenvironnement de programmation

2. Bonjour, Monde !

C++ et de la plupart des langages compils (Fortran, C, etc), la compilation se fait vers du code natif. On transforme un chier source, le programme C++, en un chier objet, suite dinstructions en langage machine. Cependant, le chier objet ne se sut pas lui-mme. Des instructions supplmentaires sont ncessaires pour former un chier excutable complet : de quoi lancer le main() ! Plus prcisment, tout ce que le process doit faire avant et aprs lexcution de main(). des fonctions ou variables faisant partie du langage et que le programmeur utilise sans les reprogrammer lui-mme, comme cout, min(), etc. Lensemble de ces instructions constitue ce quon appelle une bibliothque 43 . des fonctions ou variables programmes par le programmeur lui-mme dans dautres chiers source compils par ailleurs en dautres chiers objet, mais quil veut utiliser dans son programme actuel. La synthse de ces chiers en un chier excutable sappelle ldition des liens. Le programme qui ralise cette opration est plus souvent appel linker quditeur de liens... En rsum, la production du chier excutable se fait de la faon suivante : 1. Compilation : chier source chier objet. 2. Link : chier objet + autres chiers objets + bibliothque standard ou autres chier excutable.

2.4

Lenvironnement de programmation

Lenvironnement de programmation est le logiciel permettant de programmer. Dans notre cas il sagit de Visual Studio Express. Dans dautres cas, il peut simplement sagir dun ensemble de programmes. Un environnement contient au minimum un diteur pour crer les chiers sources, un compilateur/linker pour crer les excutables, un debuggeur pour traquer les erreurs de programmation, et un gestionnaire de projet pour grer les dirents chiers sources et excutables avec lesquels on travaille. Nous reportons ici le lecteur au texte du premier TP. En plus de quelques notions rudimentaires de C++ que nous verrons au chapitre suivant, quelques informations supplmentaires sont utiles pour le suivre.

2.4.1

Noms de chiers

Sous Windows, lextension (le suxe) sert se reprer dans les types de chier : Un chier source C++ se terminera par .cpp 44 . Un chier objet sera en .obj Un chier excutable en .exe Nous verrons aussi plus loin dans le cours :
compilateur est un programme ! On le programme avec le compilateur prcdent ! Mme chose pour les systmes dexploitation...). Elle ncessite un cours part entire et nous nen parlerons pas ici ! 43. Une bibliothque est en fait un ensemble de chiers objets pr-existants regroups en un seul chier. Il peut sagir de la bibliothque des fonctions faisant partie de C++, appele bibliothque standard, mais aussi dune bibliothque supplmentaire fournie par un tiers. 44. Un chier en .c sera considr comme du C. Dirence avec linux : un chier en .C sera aussi trait comme du C et non comme du C++ !

22

2. Bonjour, Monde !

2.4. Lenvironnement de programmation

Les "en-tte" C++ ou headers servant tre inclus dans un chier source : chiers .h Les bibliothques (ensembles de chiers objets archivs en un seul chier) : chier .lib ou .dll

2.4.2

Debuggeur

Lorsquun programme ne fait pas ce quil faut, on peut essayer de comprendre ce qui ne va pas en truant son source dinstructions pour imprimer la valeur de certaines donnes ou simplement pour suivre son droulement. Ca nest videmment pas trs pratique. Il est mieux de pouvoir suivre son droulement instruction par instruction et dacher la demande la valeur des variables. Cest le rle du debuggeur 45 . Lorsquun langage est interprt, il est relativement simple de le faire sexcute pas pas car cest le langage lui-mme qui excute le programme. Dans le cas dun langage compil, cest le micro-processeur qui excute le programme et on ne peut pas larrter chaque instruction ! Il faut alors mettre en place des points darrt en modiant temporairement le code machine du programme pour que le processeur sarrte lorsquil atteint linstruction correspondant la ligne de source debugger. Si cest compliqu mettre au point, cest trs simple utiliser, surtout dans un environnement de programmation graphique. Nous verrons au fur et mesure des TP comment le debuggeur peut aussi inspecter les appels de fonctions, espionner la modication dune variable, etc.

2.4.3

TP

Vous devriez maintenant aller faire le TP en annexe A.1. Si la pratique est essentielle, en retenir quelque chose est indispensable ! Vous y trouverez aussi comment installer Visual sur votre ordinateur (lien http://certis.enpc.fr/~keriven/CertisLibs mentionn la n du TP). Voici donc ce quil faut retenir du TP :

45. Dbogueur (F) !

23

2.4. Lenvironnement de programmation

2. Bonjour, Monde !

1. Toujours travailler en local et sauvegarder sur le disque partag, cl USB, etc. 2. Type de projet utilis : CertisLibs Project 3. Nettoyer ses solutions quand on quitte. 4. Lancer directement une excution sauve et gnre automatiquement. Attention toutefois de ne pas conrmer lexcution si la gnration sest mal passe. 5. Double-cliquer sur un message derreur positionne lditeur sur lerreur. 6. Toujours bien indenter. 7. Ne pas laisser passer des warnings ! 8. Savoir utiliser le debuggeur. 9. Touches utiles :
F7 Ctrl+F5 Ctrl+F7 F5 Maj+F5 F10 F11 Ctrl+K,Ctrl+F = = = = = = = = = = = = = = = Build Start without debugging Compile only Start debugging Stop Step over Step inside Indent selection

Nous en savons maintenant assez pour apprendre un peu de C++...

24

3. Premiers programmes

Chapitre 3 Premiers programmes


Pars exprimenter au fur et mesure avec notre environnement de programmation, il est temps dapprendre les premiers rudiments du C++. Nous allons commencer par programmer nimporte comment... puis nous ajouterons un minimum dorganisation en apprenant faire des fonctions. On organise souvent un manuel de programmation de faon logique par rapport au langage, en dirents points successifs : les expressions, les fonctions, les variables, les instructions, etc. Le rsultat est indigeste car il faut alors tre exhaustif sur chaque point. Nous allons plutt ici essayer de voir les choses telles quelles se prsentent quand on apprend : progressivement et sur un peu tous les sujets la fois 1 ! Ainsi, ce nest que dans un autre chapitre que nous verrons la faon dont les fonctions mmorisent leurs variables dans la "pile".

3.1

Tout dans le main() !

Rien dans les mains, rien dans les poches... mais tout dans le main(). Voici comment un dbutant programme 2 . Cest dj une tape importante que de programmer au kilomtre, en plaant lintgralit du programme dans la fonction main(). Lessentiel est avant tout de faire un programme qui marche !

3.1.1
Types

Variables

Les variables sont des mmoires dans lesquelles sont stockes des valeurs (ou donnes). Une donne ne pouvant tre stocke nimporte comment, il faut chaque fois dcider de la place prise en mmoire (nombre doctets) et du format, cest--dire de la faon dont les octets utiliss vont reprsenter les valeurs prises par la variable. Nous avons dj rencontr les int qui sont le plus souvent aujourdhui stocks sur quatre octets, soit
1. La contre-partie de cette prsentation est que ce polycopi, sil est fait pour tre lu dans lordre, est peut-tre moins adapt servir de manuel de rfrence. . 2. Et bien des lves, ds que le professeur nest plus derrire !

3.1. Tout dans le main() !

3. Premiers programmes

32 bits, et pouvant prendre 232 = 4294967296 valeurs possibles 3 . Par convention, les int stockent les nombres entiers relatifs 4 , avec autant de nombres ngatifs que de nombres positifs 5 , soit, dans le cas de 32 bits 6 , de 2147483648 2147483647 suivant une certaine correspondance avec le binaire 7 . Dire quune variable est un int, cest prciser son type. Certains langages nont pas la notion de type ou essaient de deviner les types des variables. En C++, cest initialement pour prciser la mmoire et le format des variables quelles sont types. Nous verrons que le compilateur se livre un certain nombre de vrications de cohrence de type entre les direntes parties dun programme. Ces vrications, pourtant bien pratiques, ntaient pas faites dans les premires versions du C, petit frre du C++, car avant tout, rptons-le : Prciser un type, cest prciser la place mmoire et le format dune variable. Le compilateur, sil pourra mettre cette information prot pour dtecter des erreurs de programmation, en a avant tout besoin pour traduire le source C++ en langage machine. Dnition, Aectation, Initialisation, Constantes Avant de voir dautres types de variables, regardons sur un exemple la syntaxe utiliser :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16

int i; // Dfinition i=2; // Affectation cout << i << " "; int j; j=i; i=1; // Ne modifie que i, pas j! cout << i << " " << j << " "; int k,l,m; // Dfinition multiple k=l=3; // Affectation multiple m=4; cout << k << " " << l << " " << m << " "; int n=5,o=n,p=INT_MAX; // Initialisations cout << n << " " << o << " " << p << endl; int q=r=4; // Erreur! const int s=12; s=13; // Erreur!

Dans ce programme :
3. Nous avons aussi vu que cette simple ide donne dj lieu deux faons dutiliser les 4 octets : big-endian ou little-endian. 4. Coin des collgiens : cest dire 0, 1, 2, ... mais aussi 1, 2, 3, ... 5. un prs ! 6. En fait, les int sadaptent au processeur et un programme compil sur un processeur 64 bits aura des int sur 64 bits ! Si lon a besoin de savoir dans quel cas on est, le C++ fournit les constantes INT_MIN et INT_MAX qui sont les valeurs minimales et maximales prises par les int. 7. L, tout le monde fait pareil ! On compte en binaire partir de 0, et arriv 2147483647, le suivant est -2147483648, puis -2147483647 et ainsi de suite jusqu -1. On a par exemple : 0 = 000...000, 1 = 000...001, 2147483647 = 011...111, 2147483648 = 100...000, 2147483647 = 100..001, 2 = 111...110, 1 = 111...111

26

3. Premiers programmes

3.1. Tout dans le main() !

Les lignes 1 et 2 dnissent une variable nomme i 8 de type int puis aecte 2 cette variable. La reprsentation binaire de 2 est donc stocke en mmoire l o le compilateur dcide de placer i. Ce qui suit le "double slash" (//) est une remarque : le compilateur ignore toute la n de la ligne, ce qui permet de mettre des commentaires aidant la comprhension du programme. La ligne 3 ache la valeur de i puis un espace (sans aller la ligne) Les lignes 4, 5 et 6 dnissent un int nomm j, recopie la valeur de i, soit 2, dans j, puis mmorise 1 dans i. Notez bien que i et j sont bien deux variables direntes : i passe 1 mais j reste 2 ! La ligne 8 nous montre comment dnir simultanment plusieurs variables du mme type. La ligne 9 nous apprend que lon peut aecter des variables simultanment une mme valeur. A la ligne 12, des variables sont dnies et aectes en mme temps. En fait, on parle plutt de variables initialises : elles prennent une valeur initiale en mme temps quelles sont dnies. Notez que, pour des raisons decacit, les variables ne sont pas initialises par dfaut : tant quon ne leur a pas aect une valeur et si elles nont pas t initialises, elles valent nimporte quoi 9 ! Attention toutefois, il est inutile de tenter une initialisation simultane. Cest interdit. La ligne 14 provoque une erreur. Enn, on peut rajouter const devant le type dune variable : celle-ci devient alors constante et on ne peut modier son contenu. La ligne 15 dnit une telle variable et la ligne 16 est une erreur. En rsum, une fois les lignes 14 et 16 supprimes, ce (passionnant !) programme ache 10 : 2 1 2 3 3 4 5 5 2147483647 Porte Dans lexemple prcdent, les variables ont t dnies au fur et mesure des besoins. Ce nest pas une vidence. Par exemple, le C ne permettait de dnir les variables que toutes dun coup au dbut du main(). En C++, on peut dnir les variables en cours de route, ce qui permet davantage de clart. Mais attention : les variables "nexistent" (et ne sont donc utilisables) qu partir de la ligne o elles sont dnies. Elles ont une dure de vie limite et meurent ds que lon sort du bloc limit par des accolades auquel elles appartiennent a . Cest ce quon appelle la porte dune variable.
a. Cest un peu plus compliqu pour les variables globales. Nous verrons a aussi...

Ainsi, en prenant un peu davance sur la syntaxe des tests, que nous allons voir tout de suite, le programme suivant provoque des erreurs de porte aux lignes 2 et 8 :
8. Le nom dune variable est aussi appel identicateur. Les messages derreur du compilateur utiliseront plutt ce vocabulaire ! 9. Ainsi, un entier ne vaut pas 0 lorsquil est cr et les octets o il est mmoris gardent la valeur quil avaient avant dtre rquisitionns pour stocker lentier en question. Cest une mauvaise ide dutiliser sa valeur dune variable qui vaut nimporte quoi et un compilateur mettra gnralement un warning si on utilise une variable avant de lui fournir une valeur ! 10. du moins sur une machine 32 bits, cf. remarque prcdente sur INT_MAX

27

3.1. Tout dans le main() !

3. Premiers programmes

1 2 3 4 5 6 7 8

int i; i=j; // Erreur: j nexiste pas encore! int j=2; if (j>1) { int k=3; j=k; } i=k; // Erreur: k nexiste plus.

Autres types Nous verrons les dirents types au fur et mesure. Voici malgr tout les plus courants :
1 2 3 4 5

int i=3; double x=12.3; char c=A; string s="hop"; bool t=true;

// // // // //

Entier relatif Nombre rel (double prcision) Caractre Chane de caractres Boolen (vrai ou faux)

Les nombres rels sont en gnral approchs par des variables de type double ("double prcision", ici sur 8 octets). Les caractres sont reprsents par un entier sur un octet (sur certaines machines de -128 127, sur dautres de 0 255), la correspondance caractre/entier tant celle du code ASCII (65 pour A, 66 pour B, etc.), quil nest heureusement pas besoin de connatre puisque la syntaxe A entre simples guillemets est traduite en 65 par le compilateur, etc. Les doubles guillemets sont eux rservs aux "chanes" de caractres 11 . Enn, les boolens sont des variables qui valent vrai (true) ou faux (false). Voici, pour information, quelques types supplmentaires :
6 7 8 9 10

float y=1.2f; unsigned int j=4; signed char d=-128; unsigned char d=254; complex<double> z(2,3);

// // // // //

Nombre Entier Entier Entier Nombre

rel simple prcision naturel relatif un octet naturel un octet complexe

o lon trouve : les float, nombres rels moins prcis mais plus courts que les double, ici sur 4 octets (Les curieux pourront explorer la documentation de Visual et voir que les float valent au plus FLT_MAX (ici, environ 3.4e+38 12 ) et que leur valeur la plus petite strictement positive est FLT_MIN (ici, environ 1.2e-38), de mme que pour les double les constantes DBL_MAX et DBL_MIN valent ici environ 1.8e+308 et 2.2e-308), les unsigned int, entiers positifs utiliss pour aller plus loin que les int dans les positifs (de 0 UINT_MAX, soit 4294967295 dans notre cas), les unsigned char, qui vont de 0 255, les signed char, qui vont de -128 127, et enn les nombres complexes 13 .
11. Attention, lutilisation des string ncessite un #include<string> au dbut du programme. 12. Coin des collgiens : 1038 ou 1e+38 vaut 1 suivi de 38 zros, 1038 ou 1e-38 vaut 0.000...01 avec 37 zros avant le 1. En compliquant : 3.4e+38 vaut 34 suivis de 37 zros (38 chires aprs le 3) et 1.2e-38 vaut 0.00...012 toujours avec 37 zros entre la virgule et le 1 (le 1 est la place 38). 13. Il est trop tt pour comprendre la syntaxe "objet" de cette dnition mais il nous parait important

28

3. Premiers programmes

3.1. Tout dans le main() !

3.1.2

Tests

Tests simples Les tests servent excuter telle ou telle instruction en fonction de la valeur dune ou de plusieurs variables. Ils sont toujours entre parenthses. Le et scrit &&, le ou ||, la ngation !, lgalit ==, la non-galit !=, et les ingalits >, >=, < et <=. Si plusieurs instructions doivent tre excutes quand un test est vrai (if) ou faux (else), on utilise des accolades pour les regrouper. Tout cela se comprend facilement sur lexemple suivant :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16

if (i==0) // i est-il nul? cout << "i est nul" << endl; ... if (i>2) // i est-il plus grand que 2? j=3; else j=5; // Si on est ici, cest que i<=2 ... // Cas plus compliqu! if (i!=3 || (j==2 && k!=3) || !(i>j && i>k)) { // Ici, i est diffrent de 3 ou alors // j vaut 2 et k est diffrent de 3 ou alors // on na pas i plus grand a la fois de j et de k cout << "Une premire instruction" << endl; cout << "Une deuxime instruction" << endl; } Les variables de type boolen servent mmoriser le rsultat dun test :

1 2 3

bool t= ((i==3)||(j==4)); if (t) k=5;

Enn, une dernire chose trs importante : penser utiliser == et non = sous peine davoir des surprises 14 . Cest peut-tre lerreur la plus frquente chez les dbutants. Elle est heureusement signale aujourdhui par un warning... Attention : utiliser if (i==3) ... et non if (i=3) ... ! Le "switch" On a parfois besoin de faire telle ou telle chose en fonction des valeurs possibles dune variable. On utilise alors souvent linstruction switch pour des raisons de clart de prsentation. Chaque cas possible pour les valeurs de la variable est prcis avec case et doit se terminer par break 15 . Plusieurs case peuvent tre utiliss pour prciser un cas
de mentionner ds maintenant que les complexes existent en C++. Coin des collgiens : pas de panique ! Vous apprendrez ce que sont les nombres complexes plus tard. Ils ne seront pas utiliss dans ce livre. 14. Faire if (i=3) ... aecte 3 i puis renvoie 3 comme rsultat du test, ce qui est considr comme vrai car la convention est quun boolen est en fait un entier, faux sil est nul et vrai sil est non nul ! 15. Cest une erreur grave et frquente doublier le break. Sans lui, le programme excute aussi les instructions du cas suivant !

29

3.1. Tout dans le main() !

3. Premiers programmes

multiple. Enn, le mot cl default, placer en dernier, correspond aux cas non prciss. Le programme suivant 16 ragit aux touches tapes au clavier et utilise un switch pour acher des commentaires passionnants !
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

#include <iostream> using namespace std; #include <conio.h> // Non standard! int main() { bool fini=false; char c; do { c=_getch(); // Non standard! switch (c) { case a: cout << "Vous avez tap break; case f: cout << "Vous avez tap fini=true; break; case e: case i: case o: case u: case y: cout << "Vous avez tap break; default: cout << "Vous avez tap break; } } while (!fini); return 0; }

a!" << endl;

f. Au revoir!" << endl;

une autre voyelle!" << endl;

autre chose!" << endl;

Si vous avez tout compris, le switch prcdent ceci est quivalent 17 :


11 12

if (c==a) cout << "Vous avez tap a!" << endl;


16. Attention, un cin c, instruction que nous verrons plus loin, lit bien un caractre au clavier mais ne ragit pas chaque touche : il attend quon appuie sur la touche Entre pour lire dun coup toutes les touches frappes ! Rcuprer juste une touche la console nest malheureusement pas standard et nest plus trs utilis dans notre monde dinterfaces graphiques. Sous Windows, il faudra utiliser _getch() aprs avoir fait un #include <conio.h> (cf. lignes 3 et 10) et sous Unix getch() aprs avoir fait un #include <curses.h>. 17. On voit bien que le switch nest pas toujours plus clair ni plus court. Cest comme tout, il faut lutiliser bon escient... Et plus nous connatrons de C++, plus nous devrons nous rappeler cette rgle et viter de faire des fonctions pour tout, des structures de donnes pour tout, des objets pour tout, des chiers spars pour tout, etc.

30

3. Premiers programmes

3.1. Tout dans le main() !

13 14 15 16 17 18 19

else if (c==f) { cout << "Vous avez tap fini=true; } else if (c==e || c==i cout << "Vous avez tap else cout << "Vous avez tap

f. Au revoir!" << endl; || c==o || c==u || c==y) une autre voyelle!" << endl; autre chose!" << endl;

Avant tout, rappelons la principale source derreur du switch : Dans un switch, ne pas oublier les break !

3.1.3

Boucles

Il est dicile de faire un programme qui fait quelque chose sans avoir la possibilit dexcuter plusieurs fois la mme instruction. Cest le rle des boucles. La plus utilise est le for(), mais a nest pas la plus simple comprendre. Commenons par le do...while, qui "tourne en rond" tant quun test est vrai. Le programme suivant attend que lutilisateur tape au clavier un entier entre 1 et 10, et lui ritre sa question jusqu obtenir un nombre correct :
1 2 3 4 5 6 7 8 9 10 11 12 13 14

#include <iostream> using namespace std; int main() { int i; do { // Dbut de la boucle cout << "Un nombre entre 1 et 10, cin >> i; } while (i<1 || i>10); // On retourne // ce test est cout << "Merci! Vous avez tap " << i return 0; }

SVP: "; au dbut de la boucle si vrai << endl;

Notez la ligne 9 qui met dans i un nombre tap au clavier. La variable cin est le pendant en entre ("console in") de la sortie cout. Vient ensuite le while qui vrie le test au dbut de la boucle. Le programme suivant ache les entiers de 1 100 :
1 2 3 4 5

int i=1; while (i<=100) { cout << i << endl; i=i+1; }

Enn, on a cre une boucle spciale tant elle est frquente : le for() qui excute une instruction avant de dmarrer, eectue un test au dbut de chaque tour, comme le while, et excute une instruction la n de chaque boucle. Instruction initiale, test et instruction nale sont spares par un ;, ce qui donne le programme suivant, absolument quivalent au prcdent : 31

3.1. Tout dans le main() !

3. Premiers programmes

1 2 3 4

int i; for (i=1;i<=100;i=i+1) { cout << i << endl; }

En gnral, le for() est utilis comme dans lexemple prcdent pour eectuer une boucle avec une variable (un indice) qui prend une srie de valeurs dans un certain intervalle. On trouvera en fait plutt :
1 2

for (int i=1;i<=100;i++) cout << i << endl;

quand on sait que : On peut dnir la variable dans la premire partie du for(). Attention, cette variable admet le for() pour porte : elle nest plus utilisable en dehors du for() 18 . i++ est une abbrviation de i=i+1 Puisquil ny a ici quune seule instruction dans la boucle, les accolades taient inutiles. On utilise aussi la virgule , pour mettre plusieurs instructions 19 dans linstruction nale du for. Ainsi, le programme suivant part de i=1 et j=100, et augmente i de 2 et diminue j de 3 chaque tour jusqu ce que leurs valeurs se croisent 20 :
1 2

for (int i=1,j=100;j>i;i=i+2,j=j-3) cout << i << " " << j << endl;

Notez aussi quon peut abrger i=i+2 en i+=2 et j=j-3 en j-=3.

3.1.4

Rcrations

Nous pouvons dj faire de nombreux programmes. Par exemple, jouer au juste prix. Le programme choisit le prix, et lutilisateur devine :
1 2 3 4 5 6 7 8 9 10 11 12 13

#include <iostream> #include <cstdlib> using namespace std; int main() { int n=rand()%100; // nombre deviner entre 0 et 99 int i; do { cout << "Votre prix: "; cin >> i; if (i>n) cout << "Cest moins" << endl;

18. Les vieux C++ ne permettaient pas de dnir la variable dans la premire partie du for(). Des C++ un peu moins anciens permettaient de le faire mais la variable survivait au for() ! 19. Pour les curieux : a na en fait rien dextraordinaire, car plusieurs instructions spares par une virgule deviennent en C++ une seule instruction qui consiste excuter lune aprs lautre les direntes instructions ainsi rassembles. 20. Toujours pour les curieux, il sarrte pour i=39 et j=43.

32

3. Premiers programmes

3.1. Tout dans le main() !

14 15 16 17 18 19 20

else if (i<n) cout << "Cest plus" << endl; else cout << "Gagne!" << endl; } while (i!=n); return 0; } Seule la ligne 7 a besoin dexplications : la fonction rand() fournit un nombre entier au hasard entre 0 et RAND_MAX. On a besoin de rajouter #include <cstdlib> pour lutiliser % est la fonction modulo 21 . Cest videmment plus intressant, surtout programmer, quand cest le programme qui devine. Pour cela, il va procder par dichotomie, an de trouver au plus vite :

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

#include<iostream> using namespace std; int main() { cout << "Choisissez un nombre entre 1 et 100" << endl; cout << "Repondez par +, - ou =" << endl; int a=1,b=100; // Valeurs extrmes bool trouve=false; do { int c=(a+b)/2; // On propose le milieu cout << "Serait-ce " << c << "?: "; char r; do cin >> r; while (r!== && r!=+ && r!=-); if (r===) trouve=true; else if (r==-) b=c-1; // Cest moins, on essaie entre a et c-1 else a=c+1; // Cest plus, on essaie entre c+1 et b } while (!trouve && (a<=b)); if (trouve) cout << "Quel boss je suis!" << endl; else cout << "Vous avez triche!" << endl; return 0; }

On peut aussi complter le programme "supplmentaire" du TP de lannexe A.1. Il sagissait dune balle rebondissant dans un carr. (Voir lannexe C pour les instructions graphiques...)
21. Coin des collgiens : compter "modulo N", cest retomber 0 quand on atteint N. Modulo 4, cela donne : 0,1,2,3,0,1,2,3,0,.... Par exemple 12%10 vaut 2 et 11%3 aussi ! Ici, le modulo 100 sert retomber entre 0 et 99.

33

3.2. Fonctions

3. Premiers programmes

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27

#include <CL/Graphics/Graphics.h> using namespace CL::Graphics; int main() { int w=300,h=210; OpenWindow(w,h); // Fentre graphique int i=0,j=0; //Position int di=2,dj=3; //Vitesse while (true) { FillRect(i,j,4,4,Red); // Dessin de la balle MilliSleep(10); // On attend un peu... if (i+di>w || i+di<0) { di=-di; // Rebond horizontal si on sort } int ni=i+di; // Nouvelle position if (j+dj>h || j+dj<0) { dj=-dj; // Rebond vertical si on sort } int nj=j+dj; FillRect(i,j,4,4,White); // Effacement i=ni; // On change de position j=nj; } Terminate(); return 0; }

3.2

Fonctions

Lorsquon met tout dans le main() on ralise trs vite que lon fait souvent des copier/coller de bouts de programmes. Si des lignes de programmes commencent se ressembler, cest quon est vraisemblablement devant loccasion de faire des fonctions. On le fait pour des raisons de clart, mais aussi pour faire des conomies de frappe au clavier ! Il faut regrouper les passages identiques en fonctions : pour obtenir un programme clair... et pour moins se fatiguer ! Attention bien comprendre quand faire une fonction et ne pas simplement dcouper un programme en petits morceaux sans aucune logique a .
a. ou juste pour faire plaisir au professeur. Mal dcouper un programme est la meilleure faon de ne plus avoir envie de le faire la fois suivante. Encore une fois, le bon critre est ici que la bonne solution est gnralement la moins fatiguante.

En fait, pouvoir rutiliser le travail dj fait est le l conducteur dune bonne programmation. Pour linstant, nous nous contentons, grce aux fonctions, de rutiliser ce que nous venons de taper quelques lignes plus haut. Plus tard, nous aurons envie de rutiliser 34

3. Premiers programmes

3.2. Fonctions

Figure 3.1 Traits et cercles au hasard...

ce qui aura t fait dans dautres programmes, ou longtemps auparavant, ou dans les programmes dautres personnes, ... et nous verrons alors comment faire. Prenons le programme suivant, qui dessine des traits et des cercles au hasard, et dont la gure 3.1 montre un rsultat :

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24

#include <CL/Graphics/Graphics.h> using namespace CL::Graphics; #include <cstdlib> using namespace std; int main() { OpenWindow(300,200); for (int i=0;i<150;i++) { int x1=rand()%300; // Point initial int y1=rand()%200; int x2=rand()%300; // Point final int y2=rand()%200; Color c=Color(rand()%256,rand()%256,rand()%256); // RVB DrawLine(x1,y1,x2,y2,c); // Trac de segment int xc=rand()%300; // Centre du cercle int yc=rand()%200; int rc=rand()%10; // Rayon Color cc=Color(rand()%256,rand()%256,rand()%256); // RVB FillCircle(xc,yc,rc,cc); // Cercle } Terminate(); return 0; } 35

3.2. Fonctions

3. Premiers programmes

La premire chose qui choque 22 , cest lappel rpt rand() et modulo pour tirer un nombre au hasard. On aura souvent besoin de tirer des nombres au hasard dans un certain intervalle et il est naturel de le faire avec une fonction. Au passage, nous corrigeons une deuxime chose qui choque : les entiers 300 et 200 reviennent souvent. Si nous voulons changer les dimensions de la fentre, il faudra remplacer dans le programme tous les 300 et tous les 200. Il vaudrait mieux mettre ces valeurs dans des variables et faire dpendre le reste du programme de ces variables. Cest un dfaut constant de tous les dbutants et il faut le corriger tout de suite : Il faut ds le dbut dun programme reprer les paramtres constants utiliss plusieurs reprises et les placer dans des variables dont dpendra le programme. On gagne alors beaucoup de temps a quand on veut les modier par la suite.
a. Encore la rgle du moindre eort... Si on fait trop de copier/coller ou de remplacer avec lditeur, cest mauvais signe !

Bref, notre programme devient :


6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28

// Nombre entre 0 et n-1 int hasard(int n) { return rand()%n; } int main() { const int w=300,h=200; OpenWindow(w,h); for (int i=0;i<150;i++) { int x1=hasard(w),y1=hasard(h); // Point initial int x2=hasard(w),y2=hasard(h); // Point final Color c=Color(hasard(256),hasard(256),hasard(256)); DrawLine(x1,y1,x2,y2,c); // Trac de segment int xc=hasard(w),yc=hasard(h); // Centre du cercle int rc=hasard(w/20); // Rayon Color cc=Color(hasard(256),hasard(256),hasard(256)); FillCircle(xc,yc,rc,cc); // Cercle } Terminate(); return 0; }

On pourrait penser que hasard(w) est aussi long taper que rand()%w et que notre fonction est inutile. Cest un peu vrai. Mais en pratique, nous navons alors plus nous souvenir de lexistence de la fonction rand() ni de comment on fait un modulo. Cest mme mieux que a : nous devenons indpendant de ces deux fonctions, et si vous voulions tirer
22. part videmment la syntaxe "objet" des variables de type Color pour lesquelles on se permet un Color(r,v,b) bien en avance sur ce que nous sommes censs savoir faire...

36

3. Premiers programmes

3.2. Fonctions

des nombres au hasard avec une autre fonction 23 , nous naurions plus qu modier la fonction hasard(). Cest encore une rgle importante : On doit galement faire une fonction quand on veut sparer et factoriser le travail. Il est ensuite plus facile a de modier la fonction que toutes les lignes quelle a remplaces !
a. Moindre eort, toujours !

3.2.1

Retour

Nous venons de dnir sans lexpliquer une fonction hasard() qui prend un paramtre n de type int et qui retourne un rsultat, de type int lui aussi. Il ny a pas grand chose savoir de plus, si ce nest que : 1. Une fonction peut ne rien renvoyer. Son type de retour est alors void et il ny a pas de return la n. Par exemple :
1 2 3 4 5 6 7

void dis_bonjour_a_la_dame(string nom_de_la_dame) { cout << "Bonjour, Mme " << nom_de_la_dame << "!" << endl; } ... dis_bonjour_a_la_dame("Germaine"); dis_bonjour_a_la_dame("Fitzgerald"); ...

2. Une fonction peut comporter plusieurs instructions return 24 . Cela permet de sortir quand on en a envie, ce qui est bien plus clair et plus proche de notre faon de penser :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17

int signe_avec_un_seul_return(double x) { int s; if (x==0) s=0; else if (x<0) s=-1; else s=1; return s; } int signe_plus_simple(double x) { if (x<0) return -1; if (x>0) // Notez labsence de else, devenu inutile! return 1; return 0;

23. Pourquoi vouloir le faire ? Dans notre cas parce que la fonction rand() utilise est susante pour des applications courantes mais pas assez prcise pour des applications mathmatiques. Par exemple, faire un modulo ne rpartit pas vraiment quitablement les nombres tirs. Enn, nous avons oubli dinitialiser le gnrateur alatoire. Si vous le permettez, nous verrons une autre fois ce que cela signie et comment le faire en modiant juste la fonction hasard(). 24. Contrairement certains vieux langages, comme le Pascal

37

3.2. Fonctions

3. Premiers programmes

18

3. Pour une fonction void, on utilise return sans rien derrire pour un retour en cours de fonction :
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

void telephoner_avec_un_seul_return(string nom) { if (j_ai_le_telephone) { if (mon telephone_marche) { if (est_dans_l_annuaire(nom)) { int numero=numero_telephone(nom); composer(numero); if (ca_decroche) { parler(); raccrocher(); } } } } } void telephoner_plus_simple(string nom) { if (!j_ai_le_telephone) return; if (!mon telephone_marche) return; if (!est_dans_l_annuaire(nom)) return; int numero=numero_telephone(nom); composer(numero); if (!ca_decroche) return; parler(); raccrocher(); }

3.2.2

Paramtres

Nous navons vu que des fonctions un seul paramtre. Voici comment faire pour en passer plusieurs ou nen passer aucun :
1 2 3 4 5 6 7 8 9 10 11

// Nombre entre a et b int hasard2(int a,int b) { return a+(rand()%(b-a+1)); } // Nombre entre 0 et 1 double hasard3() { return rand()/double(RAND_MAX); } 38

3. Premiers programmes

3.2. Fonctions

12 13 14 15

... int a=hasard2(1,10); double x=hasard3(); ... Attention bien utiliser x=hasard3() et non simplement x=hasard3 pour appeler cette fonction sans paramtre. Ce simple programme est aussi loccasion de parler dune erreur trs frquente : la division de deux nombres entiers donne un nombre entier ! Ainsi, crire double x=1/3; est une erreur car le C++ commence par calculer 1/3 avec des entiers, ce qui donne 0, puis convertit 0 en double pour le ranger dans x. Il ne sait pas au moment de calculer 1/3 quon va mettre le rsultat dans un double ! Il faut alors faire en sorte que le 1 ou le 3 soit une double et crire double x=1.0/3; ou double x=1/3.0;. Si, comme dans notre cas, on a aaire deux variables de type int, il sut de convertir une de ces variables en double avec la syntaxe double(...) que nous verrons plus tard. 1. Fonction sans paramtre : x=hop() ; et non x=hop ;. 2. Division entire : double x=1.0/3 ; et non double x=1/3 ; double x=double(i)/j ; et non double x=i/j ;, ni mme double x=double a (i/j) ;
a. Cette conversion en double arrive trop tard !

3.2.3

Passage par rfrence

Lorsquune fonction modie la valeur dun de ses paramtres, et si ce paramtre tait une variable dans la fonction appelante, alors la variable en question nest pas modie. Plus clairement, le programme suivant choue :
1 2 3 4 5 6 7

void triple(int x) { x=x*3; } ... int a=2; triple(a); cout << a << endl;

Il ache 2 et non 6. En fait, le paramtre x de la fonction triple vaut bien 2, puis 6. Mais son passage 6 ne modie pas a. Nous verrons plus loin que x est mmoris un endroit dirent de a, ce qui explique tout ! Cest la valeur de a qui est passe la fonction triple() et non pas la variable a ! On parle de passage par valeur. On peut toutefois faire en sorte que la fonction puisse vraiment modier son paramtre. On sagit alors dun passage par rfrence (ou par variable). Il sut de rajouter un & derrire le type du paramtre :
1 2 3

void triple(int& x) { x=x*3; }

Gnralement, on choisit lexemple suivant pour justier le besoin des rfrences : 39

3.2. Fonctions

3. Premiers programmes

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17

void echanger1(int x,int y) { int t=x; x=y; y=t; } void echanger2(int& x,int& y) { int t=x; x=y; y=t; } ... int a=2,b=3; echanger1(a,b); cout << a << " " << b << " "; echanger2(a,b); cout << a << " " << b << endl; ...

Ce programme ache 2 3 3 2, echanger1() ne marchant pas. Une bonne faon de comprendre le passage par rfrence est de considrer que les variables x et y de echanger1 sont des variables vraiment indpendantes du a et du b de la fonction appelante, alors quau moment de lappel echanger2, le x et le y de echanger2 deviennent des "liens" avec a et b. A chaque fois que lon utilise x dans echanger2, cest en fait a qui est utilise. Pour encore mieux comprendre allez voir le premier exercice du TP 2 (A.2.1) et sa solution. En pratique, on utilise aussi les rfrences pour faire des fonctions retournant plusieurs valeurs la fois, et ce, de la faon suivante :
1 2 3 4 5 6 7 8

void un_point(int& x, int& y) { x=...; y=...; } ... int a,b; un_point(a,b); ...

Ainsi, notre programme de dessin alatoire deviendrait :


1 2 3 4 5 6 7 8

#include <CL/Graphics/Graphics.h> using namespace CL::Graphics; #include <cstdlib> using namespace std; // Nombre entre 0 et n-1 int hasard(int n) { 40

3. Premiers programmes

3.2. Fonctions

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

return rand()%n; } Color une_couleur() { return Color(hasard(256),hasard(256),hasard(256)); } void un_point(int w,int h,int& x,int& y){ x=hasard(w); y=hasard(h); } int main() { const int w=300,h=200; OpenWindow(w,h); for (int i=0;i<150;i++) { int x1,y1; // Point initial un_point(w,h,x1,y1); int x2,y2; // Point final un_point(w,h,x2,y2); Color c=une_couleur(); DrawLine(x1,y1,x2,y2,c); // Trac de segment int xc,yc; // Centre du cercle un_point(w,h,xc,yc); int rc=hasard(w/20); // Rayon Color cc=une_couleur(); FillCircle(xc,yc,rc,cc); // Cercle } Terminate(); return 0; }

Avec le conseil suivant Penser utiliser directement le rsultat dune fonction et ne pas le mmoriser dans une variable lorsque cest inutile il devient mme :
26 27 28 29 30 31 32 33 34

int x1,y1; // Point initial un_point(w,h,x1,y1); int x2,y2; // Point final un_point(w,h,x2,y2); DrawLine(x1,y1,x2,y2,une_couleur()); // Trac de segment int xc,yc; // Centre du cercle un_point(w,h,xc,yc); int rc=hasard(w/20); // Rayon FillCircle(xc,yc,rc,une_couleur()); // Cercle 41

3.2. Fonctions

3. Premiers programmes

3.2.4

Porte, Dclaration, Dnition

Depuis le dbut, nous crons des fonctions en les dnissant. Il est parfois utile de ne connatre que le type de retour et les paramtres dune fonction sans pour autant savoir comment elle est programme, cest--dire sans connatre le corps de la fonction. Une des raisons de ce besoin est que Comme les variables, les fonctions ont une porte et ne sont connues que dans les lignes de source qui lui succdent Ainsi, le programme suivant ne compile pas :
1 2 3 4 5 6 7

int main() { f(); return 0; } void f() { }

car la ligne 3, f() nest pas connue. Il sut ici de mettre les lignes 6 et 7 avant le main() pour que le programme compile. Par contre, il est plus dicile de faire compiler :
1 2 3 4 5 6 7 8

void f() { g(); // Erreur: g() inconnue } void g() { f(); }

puisque les deux fonctions on besoin lune de lautre, et quaucun ordre ne conviendra. Il faut alors connatre la rgle suivante : Remplacer le corps dune fonction par un ; sappelle dclarer la fonction. Dclarer une fonction sut au compilateur, qui peut "patienter" a jusqu sa dnition.
a. En ralit, le compilateur na besoin que de la dclaration. Cest le linker qui devra trouver quelque part la dnition de la fonction, ou plus exactement le rsultat de la compilation de sa dnition !

Notre programme prcdent peut donc se compiler avec une ligne de plus :
1 2 3 4 5 6 7 8 9 10

void g(); void f() { g(); }

// Dclaration de g

// OK: fonction dclare

void g() { // Dfinition de g f(); } 42

3. Premiers programmes

3.2. Fonctions

3.2.5

Variables locales et globales

Nous avons vu section 3.1.1 la porte des variables. La rgle des accolades sapplique videmment aux accolades du corps dune fonction. Les variables dune fonction sont donc inconnues en dehors de la fonction On parle alors de variables locales la fonction. Ainsi, le programme suivant est interdit :
1 2 3 4 5 6 7 8 9 10

void f() { int x; x=3; } void g() { int y; y=x; // Erreur: x inconnu }

Si vraiment deux fonctions doivent utiliser des variables communes, il faut alors les "sortir" des fonctions. Elles deviennent alors des variables globales, dont voici un exemple :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17

int z; // globale void f() { int x; // locale ... if (x<z) ... } void g() { int y; // locale ... z=y; ... }

Lutilisation de variables globales est tolre et parfois justie. Mais elle constitue une solution de facilit dont les dbutants abusent et 43

3.3. TP

3. Premiers programmes

les variables globales sont viter au maximum car elles permettent parfois des communications abusives entre fonctions, sources de bugs a . les fonctions qui les utilisent sont souvent peu rutilisables dans des contextes dirents. En gnral, elles sont le signe dune mauvaise faon de traiter le problme.
a. Cest pourquoi les variables globales non constantes ne sont pas tolres chez le dbutant. Voir le programme prcdent o g() parle f() au travers de z.

3.2.6

Surcharge

Il est parfois utile davoir une fonction qui fait des choses direntes suivant le type dargument quon lui passe. Pour cela on peut utiliser la surcharge : Deux fonctions qui ont des listes de paramtres direntes peuvent avoir le mme nom a . Attention : deux fonctions aux types de retour dirents mais aux paramtres identiques ne peuvent avoir le mme nom b .
a. Car alors la faon de les appeler permettra au compilateur de savoir laquelle des fonctions on veut utiliser b. Car alors le compilateur ne pourra direncier leurs appels.

Ainsi, nos fonctions "hasard" de tout lheure peuvent trs bien scrire :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20

// Nombre entre 0 et n-1 int hasard(int n) { return rand()%n; } // Nombre entre a et b int hasard(int a,int b) { return a+(rand()%(b-a+1)); } // Nombre entre 0 et 1 double hasard() { return rand()/double(RAND_MAX); } ... int i=hasard(3); // entre 0 et 2 int j=hasard(2,4) // entre 2 et 4 double k=hasard();// entre 0 et 1 ...

3.3

TP

Nous pouvons maintenant aller faire le deuxime TP donn en annexe A.2 an de mieux comprendre les fonctions et aussi pour obtenir un mini jeu de tennis (gure 3.2). 44

3. Premiers programmes

3.4. Fiche de rfrence

Figure 3.2 Mini tennis...

3.4

Fiche de rfrence

Nous commenons maintenant nous fabriquer une "che de rfrence" qui servira daide mmoire lorsque lon est devant la machine. Nous la complterons aprs chaque chapitre. Elle contient ce qui est vu pendant le chapitre, mais aussi pendant le TP correspondant. Les nouveauts par rapport la che prcdente seront en rouge. La che nale est en annexe D.
Fiche de rfrence (1/2) Variables Dnition : int i; int k,l,m; Aectation : i=2; j=i; k=l=3; Initialisation : int n=5,o=n; Constantes : const int s=12; Porte : int i; // i=j; interdit! int j=2; i=j; // OK! if (j>1) { int k=3; j=k; // OK! } //i=k; interdit! Types : int i=3; double x=12.3; char c=A; string s="hop"; bool t=true; float y=1.2f; unsigned int j=4; signed char d=-128; unsigned char d=254; complex<double> z(2,3); Variables globales : int n; const int m=12; void f() { n=10; // OK int i=m; // OK ... Conversion : int i=int(x); int i,j; double x=double(i)/j; Tests Comparaison : == != < > <= >= Ngation : ! Combinaisons : && || if (i==0) j=1; if (i==0) j=1; else j=2; if (i==0) { j=1; k=2; } bool t=(i==0); if (t) j=1; switch (i) { case 1: ...; ...; break; case 2: case 3: ...; break; default: ...; } Boucles do { ... } while (!ok); int i=1; while (i<=100) { ... i=i+1; } for (int i=1;i<=100;i++) ... for (int i=1,j=100;j>i; i=i+2,j=j-3) ...

45

3.4. Fiche de rfrence

3. Premiers programmes

Fiche de rfrence (2/2) Fonctions Dnition : int plus(int a,int b) { int c=a+b; return c; } void affiche(int a) { cout << a << endl; } Dclaration : int plus(int a,int b); Retour : int signe(double x) { if (x<0) return -1; if (x>0) return 1; return 0; } void afficher(int x, int y) { if (x<0 || y<0) return; if (x>=w || y>=h) return; DrawPoint(x,y,Red); } Appel : int f(int a) { ... } int g() { ... } ... int i=f(2),j=g(); Rfrences : void swap(int& a,int& b) { int tmp=a; a=b;b=tmp; } ... int x=3,y=2; swap(x,y); Surcharge : int hasard(int n); int hasard(int a,int b); double hasard(); Divers i++; i--; i-=2; j+=3; j=i%n; // Modulo #include <cstdlib> ... i=rand()%n; x=rand()/double(RAND_MAX); if i==2 // NON! if (i==2) then // NON! for (int i=0,i<100,i++) // NON! int f() {...} ... int i=f; // NON! double x=1/3; // NON! int i,j; double x; x=i/j; // NON! x=double(i/j); //NON! CLGraphics Voir documentation... Clavier Build : F7 Start : Ctrl+F5 Compile : Ctrl+F7 Debug : F5 Stop : Maj+F5 Step over : F10 Step inside : F11 Indent : Ctrl+K,Ctrl+F

Entres/Sorties #include <iostream> using namespace std; Conseils ... Travailler en local cout << "I=" << i << endl; CertisLibs Project cin >> i >> j; Nettoyer en quittant. Erreurs et warnings : cliquer. Erreurs frquentes Pas de dnition de fonction Indenter. Ne pas laisser de warning. dans une fonction ! Utiliser le debuggeur. int q=r=4; // NON! Faire des fonctions. if (i=2) // NON!

46

4. Les tableaux

Chapitre 4 Les tableaux


Tout en continuant utiliser les fonctions pour les assimiler, nous allons rajouter les tableaux qui, sinon, nous manqueraient rapidement. Nous nirons pas trop vite et ne verrons pour linstant que les tableaux une dimension et de taille xe. Nous tudierons dans un autre chapitre les tableaux de taille variable et les questions de mmoire ("pile" et "tas").

4.1

Premiers tableaux

De mme quon a tout de suite ressenti le besoin davoir des boucles pour faire plusieurs fois de suite la mme chose, il a t rapidement utile de faire plusieurs fois la mme chose mais sur des variables direntes. Do les tableaux... Ainsi, le programme suivant :
1 2 3 4 5 6 7 8 9 10

int x1,y1,u1,v1; // Balle int x2,y2,u2,v2; // Balle int x3,y3,u3,v3; // Balle int x4,y4,u4,v4; // Balle ... BougeBalle(x1,y1,u1,v1); BougeBalle(x2,y2,u2,v2); BougeBalle(x3,y3,u3,v3); BougeBalle(x4,y4,u4,v4); ...

1 2 3 4

pourra avantageusement tre remplac par :


1 2 3 4 5

int x[4],y[4],u[4],v[4]; // Balles ... for (int i=0;i<4;i++) BougeBalle(x[i],y[i],u[i],v[i]); ...

dans lequel int x[4] dnit un tableau de 4 variables de type int : x[0], x[1], x[2] et x[3]. En pratique, le compilateur rserve quelque part en mmoire de quoi stocker les 4 variables en question et gre de quoi faire en sorte que x[i] dsigne la bonne variable. Un autre exemple pour mieux comprendre, qui additionne des double deux par deux en mmorisant les rsultats :

4.1. Premiers tableaux

4. Les tableaux

1 2 3 4 5 6 7 8 9

double x[100],y[100],z[100]; ... ... // ici, les x[i] et y[i] prennent des valeurs ... for (int i=0;i<100;i++) z[i]=x[i]+y[i]; ... ... // ici, on utilise les z[i] ... Il y deux choses essentielles retenir. 1. Dabord, que les indices dun tableau t de taille n vont de 0 n-1. Tout accs t[n] peut provoquer une erreur grave pendant lexcution du programme. Cest une des erreurs les plus frquentes en C++. Soit on va lire ou crire dans un endroit utilis pour une autre variable a , soit on accde une zone mmoire illgale et le programme peut "planter " b .
a. Dans lexemple ci-dessus, si on remplaait la boucle pour que i aille de 1 100, x[100] irait certainement chercher y[0] la place. De mme, z[100] irait peut-tre chercher la variable i de la boucle, ce qui risquerait de faire ensuite des choses tranges, i valant nimporte quoi ! b. Ci-dessus, z[i] avec nimporte quoi pour i irait crire en dehors de la zone rserve aux donnes, ce qui stopperait le programme plus ou moins dlicatement !

Dans le dernier exemple, on utilise x[0] x[99]. Lhabitude est de faire une boucle avec i<100 comme test, plutt que i<=99, ce qui est plus lisible. Mais attention ne pas mettre i<=100 ! 2. Ensuite, qu un tableau doit avoir une taille xe connue la compilation. Cette taille peut tre un nombre ou une variable constante, mais pas une variable. Mme si on pense que le compilateur pourrait connatre la taille, il joue au plus idiot et naccepte que des constantes :
1 2 3 4 5 6 7 8 9 10 11 12 13

double x[10],y[4],z[5]; const int n=5; int i[n],j[2*n],k[n+1]; int n1; int t1[n1]; int n2; cin >> n2; int t2[n2]; int n3; n3=5; int t3[n3];

// OK // OK // n1 na mme pas de valeur // donc ERREUR // n2 prend une valeur, mais connue // uniquement lexcution // donc ERREUR // n3 prend une valeur, connue // lexcution, mais... non constante // donc ERREUR (SI!) 48

4. Les tableaux

4.2. Initialisation

Connaissant ces deux points, on peut trs facilement utiliser des tableaux. Attention toutefois ne pas utiliser de tableau quand cest inutile, notamment quand on traduit une formule mathmatique. Je mexplique. Si vous devez calculer s = 100 f (i) pour f donne 1 , par exemple f (i) = i=1 3i + 4, nallez pas crire, comme on le voit parfois :
1 2 3 4 5 6

double f[100]; for (int i=1;i<=100;i++) f[i]=3*i+4; double s; for (int i=1;i<=100;i++) s=s+f[i];

ni, mme, ayant corrig vos bugs :


5 6 7 8 9 10

double f[100]; // Stocke f(i) dans f[i-1] for (int i=1;i<=100;i++) f[i-1]=3*i+4; // Attention aux indices! double s=0; // Ca va mieux comme ca! for (int i=1;i<=100;i++) s=s+f[i-1];

mais plutt directement sans tableau :


5 6 7

double s=0; for (int i=1;i<=100;i++) s=s+(3*i+4);

ce qui pargnera, la machine, un tableau (donc de la mmoire et des calculs), et vous des bugs (donc vos nerfs !).

4.2

Initialisation

Tout comme une variable, un tableau peut tre initialis :


1 2

int t[4]={1,2,3,4}; string s[2]={"hip","hop"}; Attention, la syntaxe utilise pour linitialisation ne marche pas pour une aectation 2 :

int t[2]; t={1,2}; // Erreur!


1. Coin des collgiens : cest--dire s = f (1) + f (2) + ... + f (100). 2. Nous verrons plus bas que laectation ne marche mme pas entre deux tableaux ! Tout ceci sarrangera avec les objets...

49

4.3. Spcicits des tableaux

4. Les tableaux

4.3

Spcicits des tableaux

Les tableaux sont des variables un peu spciales. Ils ne se comportent pas comme toujours comme les autres variables 3 ...

4.3.1

Tableaux et fonctions

Tout comme les variables, on a besoin de passer les tableaux en paramtres des fonctions. La syntaxe utiliser est simple :
1 2 3 4 5 6 7

void affiche(int s[4]) { for (int i=0;i<4;i++) cout << s[i] << endl; } ... int t[4]={1,2,3,4}; affiche(t);

mais il faut savoir deux choses : Un tableau est toujours pass par rfrence bien quon nutilise pas le & a . Une fonction ne peut pas retourner un tableau b .
a. Un void f(int& t[4]) ou toute autre syntaxe est une erreur. b. On comprendra plus tard pourquoi, par soucis decacit, les concepteurs du C++ ont voulu quun tableau ne soit ni pass par valeur, ni retourn.

donc :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16

// Rappel: ceci ne marche pas void affecte1(int x,int val) { x=val; } // Rappel: cest ceci qui marche! void affecte2(int& x,int val) { x=val; } // Une fonction qui marche sans & void remplit(int s[4],int val) { for (int i=0;i<4;i++) s[i]=val; } ... int a=1; affecte1(a,0); // a ne sera pas mis 0

3. Il est du coup de plus en plus frquent que les programmeurs utilisent directement des variables de type vector qui sont des objets implmentant les fonctionnalits des tableaux tout en se comportant davantage comme des variables standard. Nous prfrons ne pas parler ds maintenant des vector car leur comprhension ncessite celle des objets et celle des "template". Nous pensons aussi que la connaissance des tableaux, mme si elle demande un petit eort, est incontournable et aide la comprhension de la gestion de la mmoire.

50

4. Les tableaux

4.3. Spcicits des tableaux

17 18 19 20 21 22

cout << a << endl; affecte2(a,0); cout << a << endl; int t[4]; remplit(t,0); affiche(t); et aussi :

// vrification // a sera bien mis 0 // vrification // Met les t[i] 0 // Vrifie que les t[i] valent 0

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22

// Somme de deux tableaux qui ne compile mme pas // Pour retourner un tableau int somme1(int x[4],int y[4])[4] { // on peut imaginer mettre le // [4] ici ou ailleurs: // rien ny fait! int z[4]; for (int i=0;i<4;i++) z[i]=x[i]+y[i]; return z; } // En pratique, on fera donc comme a! // Somme de deux tableaux qui marche void somme2(int x[4],int y[4],int z[4]) for (int i=0;i<4;i++) z[i]=x[i]+y[i]; // OK: z est pass par rfrence! } int a[4],b[4]; ... // remplissage de a et b int c[4]; c=somme1(a,b); // ERREUR somme2(a,b,c); // OK Enn, et cest utilis tout le temps, Une fonction nest pas oblige de travailler sur une seule taille de tableau... mais il est impossible de retrouver la taille dun tableau !

On utilise la syntaxe int t[] dans les paramtres pour un tableau dont on ne prcise pas la taille. Comme il faut bien parcourir le tableau dans la fonction et quon ne peut retrouver sa taille, on la passe en paramtre en plus du tableau :
1 2 3 4 5 6 7 8 9 10

// Une fonction qui ne marche pas void affiche1(int t[]) { for (int i=0;i<TAILLE(t);i++) // TAILLE(t) nexiste pas!???? cout << t[i] << endl; } // Comment on fait en pratique void affiche2(int t[],int n) { for (int i=0;i<n;i++) cout << t[i] << endl; } 51

4.4. Rcrations

4. Les tableaux

11 12 13 14 15

... int t1[2]={1,2}; int t2[3]={3,4,5}; affiche2(t1,2); // OK affiche2(t2,3); // OK

4.3.2

Aectation

Cest simple : Aecter un tableau ne marche pas ! Il faut traiter les lments du tableau un par un... Ainsi, le programme :
1 2

int s[4]={1,2,3,4},t[4]; t=s; // ERREUR de compilation

ne marche pas et on est oblig de faire :


1 2 3

int s[4]={1,2,3,4},t[4]; for (int i=0;i<4;i++) t[i]=s[i]; // OK

Le problme, cest que : Aecter un tableau ne marche jamais mais ne gnre pas toujours une erreur de compilation, ni mme un warning. Cest le cas entre deux paramtres de fonction. Nous comprendrons plus tard pourquoi et leet exact dune telle aectation... .
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16

// Fonction qui ne marche pas // Mais qui compile trs bien! void set1(int s[4],int t[4]) { t=s; // Ne fait pas ce quil faut! // mais compile sans warning! } // Fonction qui marche (et qui compile!-) void set2(int s[4],int t[4]) { for (int i=0;i<4;i++) t[i]=s[i]; // OK } ... int s[4]={1,2,3,4},t[4]; set1(s,t); // Sans effet set2(s,t); // OK ... 52

4. Les tableaux

4.4. Rcrations

Figure 4.1 Des balles qui rebondissent... (momentanment ges ! Allez sur la page du cours pour un programme anim !)

4.4
4.4.1

Rcrations
Multi-balles

Nous pouvons maintenant reprendre le programme de la balle qui rebondit, donn la section 3.1.4, puis amlior avec des fonctions et de constantes lors du TP de lannexe A.2. Grce aux tableaux, il est facile de faire se dplacer plusieurs balles la fois. Nous tirons aussi la couleur et la position et la vitesse initiales des balles au hasard. Plusieurs fonctions devraient vous tre inconnues : Linitialisation du gnrateur alatoire avec srand(unsigned int(time(0))), qui est explique dans le TP 3 (annexe A.3) Les fonctions NoRefreshBegin et NoRefreshEnd qui servent acclrer lachage de toutes les balles (voir documentation de la CLGraphics annexe C). Voici le listing du programme (exemple dachage (malheureusement statique !) gure 4.1) :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16

#include <CL/Graphics/Graphics.h> using namespace CL::Graphics; #include <cstdlib> #include <ctime> using namespace std; ///////////////////////////////////////////////////////////////////// // Constantes du programme const int width=256; // Largeur de la fenetre const int height=256; // Hauteur de la fenetre const int ball_size=4; // Rayon de la balle const int nb_balls=30; // Nombre de balles ///////////////////////////////////////////////////////////////////// // Generateur aleatoire // A nappeler quune fois, avant Random() void InitRandom() { 53

4.4. Rcrations

4. Les tableaux

17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65

srand(unsigned int(time(0))); } // Entre a et b int Random(int a,int b) { return a+(rand()%(b-a+1)); } ///////////////////////////////////////////////////////////////////// // Position et vitesse aleatoire void InitBalle(int &x,int &y,int &u,int &v,Color &c) { x=Random(ball_size,width-ball_size); y=Random(ball_size,height-ball_size); u=Random(0,4); v=Random(0,4); c=Color(byte(Random(0,255)), byte(Random(0,255)), byte(Random(0,255))); } ///////////////////////////////////////////////////////////////////// // Affichage dune balle void DessineBalle(int x,int y,Color col) { FillRect(x-ball_size,y-ball_size,2*ball_size+1,2*ball_size+1,col); } ///////////////////////////////////////////////////////////////////// // Deplacement dune balle void BougeBalle(int &x,int &y,int &u,int &v) { // Rebond sur les bords gauche et droit if (x+u>width-ball_size || x+u<ball_size) u=-u; // Rebond sur les bords haut et bas et comptage du score if (y+v<ball_size || y+v>height-ball_size) v=-v; // Mise a jour de la position x+=u; y+=v; } ///////////////////////////////////////////////////////////////////// // Fonction principale int main() { // Ouverture de la fenetre OpenWindow(width,height); // Position et vitesse des balles int xb[nb_balls],yb[nb_balls],ub[nb_balls],vb[nb_balls]; Color cb[nb_balls]; // Couleurs des balles InitRandom(); for (int i=0;i<nb_balls;i++) { InitBalle(xb[i],yb[i],ub[i],vb[i],cb[i]); DessineBalle(xb[i],yb[i],cb[i]); 54

4. Les tableaux

4.4. Rcrations

66 67 68 69 70 71 72 73 74 75 76 77 78 79 80

} // Boucle principale while (true) { MilliSleep(25); NoRefreshBegin(); for (int i=0;i<nb_balls;i++) { DessineBalle(xb[i],yb[i],White); BougeBalle(xb[i],yb[i],ub[i],vb[i]); DessineBalle(xb[i],yb[i],cb[i]); } NoRefreshEnd(); } Terminate(); return 0; }

4.4.2

Avec des chocs !

Il nest ensuite pas trs compliqu de modier le programme prcdent pour que les balles rebondissent entre-elles. Le listing ci-aprs a t construit comme suit : 1. Lorsquune balle se dplace, on regarde aussi si elle rencontre une autre balle. Il faut donc que BougeBalle connaisse les positions des autres balles. On modie donc BougeBalle en passant les tableaux complets des positions et des vitesses, et en prcisant juste lindice de la balle dplacer (lignes 71 et 110). La boucle de la ligne 78 vrie ensuite via le test de la ligne 81 si lune des autres balles est heurte par la balle courante. Auquel cas, on appelle ChocBalles qui modie les vitesses des deux balles. Notez les lignes 79 et 80 qui vitent de considrer le choc dune balle avec elle-mme (nous verrons le continue une autre fois). 2. Les formules du choc de deux balles peuvent se trouver facilement dans un cours de prpa ... ou sur le WEB, par exemple sur le trs didactique laboratoire virtuel labo.ntic.org. La fonction ChocBalles implmente ces formules. (Notez linclusion du chier <cmath> pour avoir accs la racine carr sqrt(), aux sinus et cosinus cos() et sin(), et larc-cosinus acos(). 3. On ralise ensuite que les variables entires qui stockent positions et vitesses font que les erreurs darrondis saccumulent et que les vitesses deviennent nulles ! On bascule alors toutes les variables concernes en double, en pensant bien les reconvertir en int lors de lachage (ligne 37). Le tout donne un programme bien plus anim. On ne peut videmment constater la dirence sur une gure dans un livre. Tlchargez donc le programme sur la page du cours !
23 24 25 26 27 28 29

///////////////////////////////////////////////////////////////////// // Position et vitesse aleatoire void InitBalle(double &x,double &y,double &u,double &v,Color &c) { x=Random(ball_size,width-ball_size); y=Random(ball_size,height-ball_size); u=Random(0,4); v=Random(0,4); 55

4.4. Rcrations

4. Les tableaux

30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78

c=Color(byte(Random(0,255)), byte(Random(0,255)), byte(Random(0,255))); } ///////////////////////////////////////////////////////////////////// // Affichage dune balle void DessineBalle(double x,double y,Color col) { FillRect(int(x)-ball_size,int(y)-ball_size, 2*ball_size+1,2*ball_size+1,col); } ///////////////////////////////////////////////////////////////// // Choc elastique de deux balles spheriques // cf labo.ntic.org #include <cmath> void ChocBalles(double&x1,double&y1,double&u1,double&v1, double&x2,double&y2,double&u2,double&v2) { // Distance double o2o1x=x1-x2,o2o1y=y1-y2; double d=sqrt(o2o1x*o2o1x+o2o1y*o2o1y); if (d==0) return; // Mme centre? // Repre (o2,x,y) double Vx=u1-u2,Vy=v1-v2; double V=sqrt(Vx*Vx+Vy*Vy); if (V==0) return; // Mme vitesse // Repre suivant V (o2,i,j) double ix=Vx/V,iy=Vy/V,jx=-iy,jy=ix; // Hauteur dattaque double H=o2o1x*jx+o2o1y*jy; // Angle double th=acos(H/d),c=cos(th),s=sin(th); // Vitesse aprs choc dans (o2,i,j) double v1i=V*c*c,v1j=V*c*s,v2i=V*s*s,v2j=-v1j; // Dans repre dorigine (O,x,y) u1=v1i*ix+v1j*jx+u2; v1=v1i*iy+v1j*jy+v2; u2+=v2i*ix+v2j*jx; v2+=v2i*iy+v2j*jy; } ///////////////////////////////////////////////////////////////////// // Deplacement dune balle void BougeBalle(double x[],double y[],double u[],double v[],int i) { // Rebond sur les bords gauche et droit if (x[i]+u[i]>width-ball_size || x[i]+u[i]<ball_size) u[i]=-u[i]; // Rebond sur les bords haut et bas et comptage du score if (y[i]+v[i]<ball_size || y[i]+v[i]>height-ball_size) v[i]=-v[i]; for (int j=0;j<nb_balls;j++) { 56

4. Les tableaux

4.4. Rcrations

79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117

if (j==i) continue; if (abs(x[i]+u[i]-x[j])<2*ball_size && abs(y[i]+v[i]-y[j])<2*ball_size) { ChocBalles(x[i],y[i],u[i],v[i],x[j],y[j],u[j],v[j]); } } // Mise a jour de la position x[i]+=u[i]; y[i]+=v[i]; } ///////////////////////////////////////////////////////////////////// // Fonction principale int main() { // Ouverture de la fenetre OpenWindow(width,height); // Position et vitesse des balles double xb[nb_balls],yb[nb_balls],ub[nb_balls],vb[nb_balls]; Color cb[nb_balls]; // Couleurs des balles InitRandom(); for (int i=0;i<nb_balls;i++) { InitBalle(xb[i],yb[i],ub[i],vb[i],cb[i]); DessineBalle(xb[i],yb[i],cb[i]); } // Boucle principale while (true) { MilliSleep(25); NoRefreshBegin(); for (int i=0;i<nb_balls;i++) { DessineBalle(xb[i],yb[i],White); BougeBalle(xb,yb,ub,vb,i); DessineBalle(xb[i],yb[i],cb[i]); } NoRefreshEnd(); } Terminate(); return 0; }

4.4.3

Mlanger les lettres

Le programme suivant considre une phrase et permute alatoirement les lettres intrieures de chaque mot (cest--dire sans toucher aux extrmits des mots). Il utilise pour cela le type string, chane de caractre, pour lequel s[i] renvoie le i-me caractre de la chane s, et s.size() le nombre de caractres de s (nous expliquerons plus tard la notation "objet" de cette fonction). La phrase considre ici devient par exemple : Ctete pteite psahre dreviat erte ecorne libslie puor vorte parvue ceeravu 57

4.4. Rcrations

4. Les tableaux

Lavez vous comprise ? Peu importe ! Cest le listing que vous devez comprendre :
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

#include <iostream> #include <string> #include <cstdlib> #include <ctime> using namespace std; ///////////////////////////////////////////////////////////////////// // Generateur aleatoire // A nappeler quune fois, avant Random() void InitRandom() { srand(unsigned int(time(0))); } // Entre a et b int Random(int a,int b) { return a+(rand()%(b-a+1)); } ///////////////////////////////////////////////////////////////////// // Permuter les lettres interieures de s n fois string Melanger(string s,int n) { int l=int(s.size()); if (l<=3) return s; string t=s; for (int i=0;i<n;i++) { int a=Random(1,l-2); int b; do b=Random(1,l-2); while (a==b); char c=t[a]; t[a]=t[b]; t[b]=c; } return t; } int main() { const int n=11; string phrase[n]={"Cette","petite","phrase","devrait","etre", "encore","lisible","pour","votre","pauvre", "cerveau"}; InitRandom(); 58

4. Les tableaux

4.5. TP

Figure 4.2 Master mind...


48 49 50 51 52 53

for (int i=0;i<n;i++) cout << Melanger(phrase[i],3) << " "; cout << endl; return 0; }

4.5

TP

Nous pouvons maintenant aller faire le troisime TP donn en annexe A.3 an de mieux comprendre les tableaux et aussi pour obtenir un master mind (voir gure 4.2 le rsultat dune partie intressante !).

4.6

Fiche de rfrence

Comme promis, nous compltons, en rouge, la "che de rfrence" avec ce qui a t vu pendant ce chapitre et son TP.

59

4.6. Fiche de rfrence

4. Les tableaux

Fiche de rfrence (1/2) Variables Dnition : int i; int k,l,m; Aectation : i=2; j=i; k=l=3; Initialisation : int n=5,o=n; Constantes : const int s=12; Porte : int i; // i=j; interdit! int j=2; i=j; // OK! if (j>1) { int k=3; j=k; // OK! } //i=k; interdit! Types : int i=3; double x=12.3; char c=A; string s="hop"; bool t=true; float y=1.2f; unsigned int j=4; signed char d=-128; unsigned char d=254; complex<double> z(2,3); Variables globales : int n; const int m=12; void f() { n=10; // OK int i=m; // OK ... Conversion : int i=int(x); int i,j; double x=double(i)/j; Tests Comparaison : == != < > <= >= Ngation : ! Combinaisons : && || if (i==0) j=1; if (i==0) j=1; else j=2; if (i==0) { j=1; k=2; } bool t=(i==0); if (t) j=1; switch (i) { case 1: ...; ...; break; case 2: case 3: ...; break; default: ...; } Tableaux Dnition : double x[10],y[10]; for (int i=0;i<10;i++) y[i]=2*x[i]; const int n=5; int i[n],j[2*n]; // OK Initialisation : int t[4]={1,2,3,4}; string s[2]={"ab","cd"}; Aectation : int s[4]={1,2,3,4},t[4]; for (int i=0;i<4;i++) t[i]=s[i]; En paramtre : void init(int t[4]) { for (int i=0;i<4;i++) t[i]=0; } void init(int t[], int n) { for (int i=0;i<n;i++) t[i]=0; } Boucles do { ... } while (!ok); int i=1; while (i<=100) { ... i=i+1; } for (int i=1;i<=100;i++) ... for (int i=1,j=100;j>i; i=i+2,j=j-3) ... Fonctions Dnition : int plus(int a,int b) { int c=a+b; return c; } void affiche(int a) { cout << a << endl; } Dclaration : int plus(int a,int b); Retour : int signe(double x) { if (x<0) return -1; if (x>0) return 1; return 0; } void afficher(int x, int y) { if (x<0 || y<0) return; if (x>=w || y>=h) return; DrawPoint(x,y,Red); } Appel : int f(int a) { ... } int g() { ... } ... int i=f(2),j=g(); Rfrences : void swap(int& a,int& b) { int tmp=a; a=b;b=tmp; } ... int x=3,y=2; swap(x,y); Surcharge : int hasard(int n); int hasard(int a,int b); double hasard();

60

4. Les tableaux

4.6. Fiche de rfrence

Fiche de rfrence (2/2) Divers i++; i--; i-=2; j+=3; j=i%n; // Modulo #include <cstdlib> ... i=rand()%n; x=rand()/double(RAND_MAX); int t[4] t=f(); Erreurs frquentes int s[4]={1,2,3,4},t[4]; Pas de dnition de fonction t=s; // NON! dans une fonction ! int t[2]; int q=r=4; // NON! t={1,2}; // NON! if (i=2) // NON! CLGraphics if i==2 // NON! Voir documentation... if (i==2) then // NON! for (int i=0,i<100,i++) Clavier // NON! Build : F7 int f() {...} Start : Ctrl+F5 #include <ctime> ... Compile : Ctrl+F7 ... int i=f; // NON! Debug : F5 srand( double x=1/3; // NON! Stop : Maj+F5 unsigned int(time(0))); int i,j; Step over : F10 #include <cmath> double x; Step inside : F11 double sqrt(double x); x=i/j; // NON! Indent : Ctrl+K,Ctrl+F double cos(double x); x=double(i/j); //NON! double sin(double x); Conseils double x[10],y[10]; double acos(double x); Travailler en local for (int i=1;i<=10;i++) #include <string> CertisLibs Project // NON! using namespace std; Nettoyer en quittant. y[i]=2*x[i]; string s="hop"; Erreurs et warnings : cliquer. int n=5; char c=s[0]; Indenter. int t[n]; // NON int l=s.size(); Ne pas laisser de warning. int f()[4] { // NON! Utiliser le debuggeur. Entres/Sorties int t[4]; Faire des fonctions. #include <iostream> ... Tableaux : quand cest utile ! using namespace std; return t; // NON! (Pas pour transcrire une for... } mule mathmatique.) cout << "I=" << i << endl; ... cin >> i >> j;

61

5. Les structures

Chapitre 5 Les structures


Les fonctions et les boucles nous ont permis de regrouper des instructions identiques. Les tableaux ont fait de mme pour les variables, mais pour manipuler plusieurs variables simultanment, il est tout aussi indispensable des fabriquer des structures de donnes...

5.1

Rvisions

Avant cela, il est utile de nous livrer une petite rvision, qui prendra la forme dun inventaire des erreurs classiques commises par de nombreux dbutants... et mme de celles, plus rares mais plus originales, constates chez certains ! Enn, nous rpterons, encore et toujours, les mmes conseils.

5.1.1

Erreurs classiques

En vrac : Mettre un seul = dans les tests : if (i=2) Oublier les parenthses : if i==2 Utiliser then : if (i==2) then Mettre des virgules dans un for : for (int i=0,i<100,i++) Oublier les parenthses quand on appelle une fonction sans paramtre : int f() {...} ... int i=f; Vouloir aecter un tableau un autre : int s[4]={1,2,3,4},t[4]; t=s;

5.1.2

Erreurs originales

L, le dbutant ne se trompe plus : il invente carrment avec sans doute le fol espoir que a existe peut-tre. Souvent, non seulement a nexiste pas, mais en plus a ne colle ni aux grands principes de la syntaxe du C++, ni mme ce quun compilateur peut comprendre ! Deux exemples : Mlanger la syntaxe (si peu !) :

5.1. Rvisions

5. Les structures

void set(int t[5]) { ... } ... int s[5]; //Jusque l, tout va bien! set(int s[5]); // L, cest quand mme un peu nimporte quoi! alors quil sut dun : set(s); Vouloir faire plusieurs choses la fois, ou ne pas comprendre quun programme est une suite dinstructions excuter lune aprs lautre et non pas une formule 1 . Par exemple, croire que le for est un symbole mathmatique comme n 1 ou n . Ainsi, pour excuter une instruction quand tous les ok(i) sont vrais, on a 1 dj vu tenter un : if (for (int i=0;i<n;i++) ok(i)) // Du grand art... ... alors quil faut faire : bool allok=true; for (int i=0;i<n;i++) allok=(allok && ok(i)); if (allok) ... ou mme mieux (voyez vous la dirence ?) : bool allok=true; for (int i=0;i<n && allok;i++) allok=(allok && ok(i)); if (allok) ... Il est comprhensible que le dbutant puisse tre victime de son manque de savoir, dune mauvaise assimilation des leons prcdentes, de la confusion avec un autre langage, ou de son imagination dbordante ! Toutefois, il faut bien comprendre quun langage est nalement lui aussi un programme, limit et conu pour faire des choses bien prcises. En consquence, il est plus raisonnable dadopter la conduite suivante :

Tout ce qui na pas t annonc comme possible est impossible !

5.1.3

Conseils

Indenter. Indenter. Indenter ! Cliquer sur les messages derreurs et de warnings pour aller directement la bonne ligne ! Ne pas laisser de warning. Utiliser le debuggeur.
1. Ne me fates pas dire ce que je nai pas dit ! Les informaticiens thoriques considrent parfois les programmes comme des formules, mais a na rien voir !

64

5. Les structures

5.2. Les structures

5.2
5.2.1

Les structures
Dnition

Si les tableaux permettent de manipuler plusieurs variables dun mme type, les structures sont utilises pour regrouper plusieurs variables an de les manipuler comme une seule. On cre un nouveau type, dont les variables en question deviennent des "sousvariables" appeles champs de la structure. Voici par exemple un type Point possdant deux champs de type double nomms x et y : struct Point { double x,y; }; Les champs se dnissent avec la syntaxe des variables locales dune fonction. Attention par contre Ne pas oublier le point virgule aprs laccolade qui ferme la dnition de la structure ! Lutilisation est alors simple. La structure est un nouveau type qui se manipule exactement comme les autres, avec la particularit supplmentaire quon accde aux champs avec un point : Point a; a.x=2.3; a.y=3.4; On peut videmment dnir des champs de dirents types, et mme des structures dans des structures : struct Cercle { Point centre; double rayon; Color couleur; }; Cercle C; C.centre.x=12.; C.centre.y=13.; C.rayon=10.4; C.couleur=Red; Lintrt des structures est vident et il faut Regrouper dans des structures des variables ds quon repre quelles sont logiquement lies. Si un programme devient pnible parce quon passe systmatiquement plusieurs paramtres identiques de nombreuses fonctions, alors il est vraisemblable que les paramtres en question puissent tre avantageusement regroups en une structure. Ce sera plus simple et plus clair. 65

5.2. Les structures

5. Les structures

5.2.2

Utilisation

Les structures se manipulent comme les autres types 2 . La dnition, laectation, linitialisation, le passage en paramtre, le retour dune fonction : tout est semblable au comportement des types de base. Seule nouveaut : on utilise des accolades pour prciser les valeurs des champs en cas dinitialisation 3 . On peut videmment faire des tableaux de structures... et mme dnir un champ de type tableau ! Ainsi, les lignes suivantes se comprennent facilement : Point a={2.3,3.4},b=a,c; // Initialisations c=a; // Affectations Cercle C={{12,13},10.4,Red}; // Initialisation ... double distance(Point a,Point b) { // Passage par valeur return sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y)); } void agrandir(Cercle& C,double echelle) { // Par rfrence C.rayon=C.rayon*echelle; // Modifie le rayon } Point milieu(Point a,Point b) { // retour Point M; M.x=(a.x+b.x)/2; M.y=(a.y+b.y)/2; return M; } ... Point P[10]; // Tableau de structures for (int i=0;i<10;i++) { P[i].x=i; P[i].y=f(i); } ... // Un dbut de jeu de Yams struct Tirage { // int de[5]; // champ de type tableau }; Tirage lancer() { Tirage t; for (int i=0;i<5;i++) t.de[i]=1+rand()%6; // Un d de 1 6 return t; } ... Tirage t; t=lancer(); ...
2. Dailleurs, nous avions bien promis que seuls les tableaux avaient des particularits (passage par rfrence, pas de retour possible et pas daectation. 3. Comme pour un tableau !

66

5. Les structures

5.3. Rcration : TP

Figure 5.1 Corps clestes et duel... Attention, tout comme pour les tableaux, la syntaxe utilise pour linitialisation ne marche pas pour une aectation 4 : Point P; P={1,2}; // Erreur! Dailleurs, rptons-le : Tout ce qui na pas t annonc comme possible est impossible !

5.3

Rcration : TP

Nous pouvons maintenant aller faire le TP donn en annexe A.4 an de mieux comprendre les structures. Nous ferons mme des tableaux de structures 5 ! Nous obtiendrons un projectile naviguant au milieu des toiles puis un duel dans lespace (gure 5.1) !

4. La situation samliorera avec les objets. 5. Coin des collgiens : il y a dans ce TP des mathmatiques et de la physique pour tudiant de lenseignement suprieur... mais on peut trs bien faire les programmes en ignorant tout a !

67

5.4. Fiche de rfrence

5. Les structures

5.4

Fiche de rfrence

Encore une fois, nous compltons, en rouge, la "che de rfrence" avec ce qui a t vu pendant ce chapitre et son TP.

Fiche de rfrence (1/2) Variables Dnition : int i; int k,l,m; Aectation : i=2; j=i; k=l=3; Initialisation : int n=5,o=n; Constantes : const int s=12; Porte : int i; // i=j; interdit! int j=2; i=j; // OK! if (j>1) { int k=3; j=k; // OK! } //i=k; interdit! Types : int i=3; double x=12.3; char c=A; string s="hop"; bool t=true; float y=1.2f; unsigned int j=4; signed char d=-128; unsigned char d=254; complex<double> z(2,3); Variables globales : int n; const int m=12; void f() { n=10; // OK int i=m; // OK ... Conversion : int i=int(x); int i,j; double x=double(i)/j; Tests Comparaison : == != < > <= >= Ngation : ! Combinaisons : && || if (i==0) j=1; if (i==0) j=1; else j=2; if (i==0) { j=1; k=2; } bool t=(i==0); if (t) j=1; switch (i) { case 1: ...; ...; break; case 2: case 3: ...; break; default: ...; } Boucles do { ... } while (!ok); int i=1; while (i<=100) { ... i=i+1; } for (int i=1;i<=100;i++) ... for (int i=1,j=100;j>i; i=i+2,j=j-3) ... Fonctions Dnition : int plus(int a,int b) { int c=a+b; return c; } void affiche(int a) { cout << a << endl; } Dclaration : int plus(int a,int b); Retour : int signe(double x) { if (x<0) return -1; if (x>0) return 1; return 0; } void afficher(int x, int y) { if (x<0 || y<0) return; if (x>=w || y>=h) return; DrawPoint(x,y,Red); } Appel : int f(int a) { ... } int g() { ... } ... int i=f(2),j=g(); Rfrences : void swap(int& a,int& b) { int tmp=a; a=b;b=tmp; } ... int x=3,y=2; swap(x,y); Surcharge : int hasard(int n); int hasard(int a,int b); double hasard();

68

5. Les structures

5.4. Fiche de rfrence

Fiche de rfrence (2/2) Tableaux Dnition : double x[10],y[10]; for (int i=0;i<10;i++) y[i]=2*x[i]; const int n=5; int i[n],j[2*n]; // OK Initialisation : int t[4]={1,2,3,4}; string s[2]={"ab","cd"}; Aectation : int s[4]={1,2,3,4},t[4]; for (int i=0;i<4;i++) t[i]=s[i]; En paramtre : void init(int t[4]) { for (int i=0;i<4;i++) t[i]=0; } void init(int t[], int n) { for (int i=0;i<n;i++) t[i]=0; } Structures struct Point { double x,y; Color c; }; ... Point a; a.x=2.3; a.y=3.4; a.c=Red; Point b={1,2.5,Blue}; Divers i++; i--; i-=2; j+=3; j=i%n; // Modulo #include <cstdlib> ... i=rand()%n; x=rand()/double(RAND_MAX); #include <ctime> ... srand( unsigned int(time(0))); #include <cmath> double sqrt(double x); double cos(double x); double sin(double x); double acos(double x); #include <string> using namespace std; string s="hop"; char c=s[0]; int l=s.size(); int t[n]; // NON int f()[4] { // NON! int t[4]; ... return t; // NON! } ... int t[4] t=f(); int s[4]={1,2,3,4},t[4]; t=s; // NON! int t[2]; t={1,2}; // NON! struct Point { double x,y; } // NON! Point a; a={1,2}; // NON!

Entres/Sorties #include <iostream> using namespace std; CLGraphics ... cout << "I=" << i << endl; Voir documentation... cin >> i >> j; Clavier Build : F7 Erreurs frquentes Pas de dnition de fonction Start : Ctrl+F5 Compile : Ctrl+F7 dans une fonction ! Debug : F5 int q=r=4; // NON! Stop : Maj+F5 if (i=2) // NON! Step over : F10 if i==2 // NON! Step inside : F11 if (i==2) then // NON! Indent : Ctrl+K,Ctrl+F for (int i=0,i<100,i++) // NON! Conseils int f() {...} Travailler en local ... CertisLibs Project int i=f; // NON! Nettoyer en quittant. double x=1/3; // NON! Erreurs et warnings : cliquer. int i,j; Indenter. double x; Ne pas laisser de warning. x=i/j; // NON! Utiliser le debuggeur. x=double(i/j); //NON! Faire des fonctions. double x[10],y[10]; Tableaux : quand cest utile ! for (int i=1;i<=10;i++) (Pas pour transcrire une for// NON! mule mathmatique.) y[i]=2*x[i]; Faire des structures. int n=5;

69

6. Plusieurs chiers !

Chapitre 6 Plusieurs chiers !


Lors du dernier TP, nous avons ralis deux projets quasiment similaires dont seuls les main() taient dirents. Modier aprs coup une des fonctions de la partie commune aux deux projets ncessiterait daller la modier dans les deux projets. Nous allons voir maintenant comment factoriser cette partie commune dans un seul chier, de faon en simplier les ventuelles futures modications. Au passage 1 nous verrons comment dnir un oprateur sur de nouveaux types. Rsumons notre progression dans le savoir-faire du programmeur : 1. Tout programmer dans le main() : cest un dbut et cest dj bien ! 2. Faire des fonctions : pour tre plus lisible et ne pas se rpter ! (Axe des instructions) 3. Faire des tableaux et des structures : pour manipuler plusieurs variables la fois. (Axe des donnes) Nous rajoutons maintenant : 4. Faire plusieurs chiers : pour utiliser des parties communes dans dirents projets ou solutions. (A nouveau, axe des instructions)

Figure 6.1 Plusieurs chiers sources...


1. Toujours cette ide que nous explorons les direntes composantes du langages quand le besoin sen fait sentir.

6.1. Fichiers spars

6. Plusieurs chiers !

6.1
6.1.1

Fichiers spars
Principe

Jusqu prsent un seul chier source contenait notre programme C++. Ce chier source tait transform en chier objet par le compilateur puis le linker compltait le chier objet avec les bibliothques du C++ pour en faire un chier excutable. En fait, un projet peut contenir plusieurs chiers sources. Il sut pour cela de rajouter un chier .cpp la liste des sources du projet : Ouvrir le menu Project/Add New Item ou faire Ctrl+Maj+A ou cliquer sur Choisir lajout dun chier Visual C++/Code/C++ le et en prciser le nom (sans quil soit besoin de prciser .cpp dans le nom) Ainsi, en rajoutant un chier C++ hop un projet contenant dj main.cpp, on se retrouve avec une structure de projet identique celle de la gure 6.1. Aprs cela, chaque gnration du projet consistera en : 1. Compilation : chaque chier source est transform en un chier objet (de mme nom mais de suxe .obj). Les chiers sources sont donc compils indpendamment les uns des autres. 2. Link : les dirents chiers objets sont runis (et complts avec les bibliothques du C++) en un seul chier excutable (de mme nom que le projet). Une partie des instructions du chier principal (celui qui contient main()) peut donc tre dporte dans un autre chier. Cette partie sera compile sparment et rintgre pendant ldition des liens. Se pose alors le problme suivant : comment utiliser dans le chier principal ce qui ce trouve dans les autres chiers ? En eet, nous savions (cf section 3.2.4) quune fonction ntait "connue" que dans les lignes qui suivaient sa dnition ou son ventuelle dclaration. Par "connue", il faut comprendre que le compilateur sait quil existe ailleurs une fonction de tel nom avec tel type de retour et tels paramtres. Malheureusement 2 : une fonction nest pas "connue" en dehors de son chier. Pour lutiliser dans un autre chier, il faut donc ly dclarer ! En clair, nous allons devoir procder ainsi : Fichier hop.cpp : 1 // Dfinitions 2 void f(int x) { 3 ... 4 } 5 int g() { 6 ... 7 } 8 // Autres fonctions 9 ... Fichier main.cpp : 1 // Dclarations 2 void f(int x); 3 int g();
2. Heureusement, en fait, car lorsque lon runit des chiers de provenances multiples, il est prfrable que ce qui se trouve dans les dirents chiers ne se mlange pas de faon anarchique...

72

6. Plusieurs chiers !

6.1. Fichiers spars

... int main() { ... // Utilisation int a=g(); f(a); ... Nous pourrions aussi videmment dclarer dans hop.cpp certaines fonctions de main.cpp pour pouvoir les utiliser. Attention toutefois : si des chiers sutilisent de faon croise, cest peut-tre que nous sommes en train de ne pas dcouper les sources convenablement.
4 5 6 7 8 9 10

6.1.2

Avantages

Notre motivation initiale tait de mettre une partie du code dans un chier spar pour lutiliser dans un autre projet. En fait, dcouper son code en plusieurs chiers a dautres intrts : Rendre le code plus lisible et vitant les chiers trop longs et en regroupant les fonctions de faon structure. Acclrer la compilation. Lorsquun programme devient long et complexe, le temps de compilation nest plus ngligeable. Or, lorsque lon rgnre un projet, lenvironnement de programmation ne recompile que les chiers sources qui ont t modis depuis la gnration prcdente. Il serait en eet inutile de recompiler un chier source non modi pour ainsi obtenir le mme chier objet 3 ! Donc changer quelques lignes dans un chier nentranera pas la compilation de tout le programme mais seulement du chier concern 4 . Attention toutefois ne pas sparer en de trop nombreux chiers ! Il devient alors plus compliqu de sy retrouver et de naviguer parmi ces chiers.

Figure 6.2 Mme source dans deux projets

3. Cest en ralit un peu plus compliqu : un source peu dpendre, via des inclusions (cf section 6.1.4), dautres chiers, qui, eux, peuvent avoir t modis ! Il faut alors recompiler un chier dont une dpendance a t modie. Visual gre automatiquement ces dpendances (Cest plus compliqu, mais possible aussi, sous linux...) 4. En fait, Visual est mme capable de ne recompiler que certaines parties du chier quand les modications apportes sont simples...

73

6.1. Fichiers spars

6. Plusieurs chiers !

6.1.3

Utilisation dans un autre projet

Pour utiliser dans un projet 2 un chier source dun projet 1, il sut de rajouter le source en question dans la liste des sources du projet 2 ! Pour cela, aprs avoir slectionn le projet 2 : Choisir le menu Project/Add Existing Item (ou Alt+Maj+A ou ) Slectionner le chier source. ou plus simplement : Avec la souris, glisser/dplacer le chier entre les deux projets 5 en maintenant la touche Ctrl enfonce (un + apparat). On obtient le rsultat gure 6.2. Attention : il faut bien comprendre que le chier ainsi partag reste dans le rpertoire du premier projet comme le conrme lachage de ses proprits ( droite gure 6.2). Dans notre exemple, chaque projet a son propre chier principal, main.cpp ou main2.cpp, mais il y a un seul hop.cpp. Modier le contenu de ce chier aura des consquences sur les deux projets la fois. Cest ce que nous voulions !

6.1.4

Fichiers den-ttes

Le chier spar nous permet de factoriser une partie du source. Toutefois, il faut taper les dclarations de toutes les fonctions utilises dans chaque chier principal les utilisant. Nous pouvons mieux faire 6 . Pour cela, il est est temps dexpliquer ce que fait linstruction #include que nous rencontrons depuis nos dbuts : La ligne #include "nom" est automatiquement remplace par le contenu du chier nom avant de procder la compilation. Il sagit bien de remplacer par le texte complet du chier nom comme avec un simple copier/coller. Cette opration est faite avant la compilation par un programme dont nous navions pas parl : le pr-processeur. La plupart des lignes commenant par un # lui seront destines. Nous en verrons dautres. Attention : jusquici nous utilisions une forme lgrement dirente : #include <nom>, qui va chercher le chier nom dans les rpertoires des bibliothques C++ 7 . Grce cette possibilit du pr-processeur, il nous sut de mettre les dclarations se rapportant au chier spar dans un troisime chier et de linclure dans les chiers principaux. Il est dusage de prendre pour ce chier supplmentaire le mme nom que le chier spar, mais avec lextension .h : on appelle ce chier un chier den-tte 8 . Pour crer ce chier, faire comme pour le source, mais en choisissant "Visual C++/Code/Header le" au lieu de "Visual C++/Code/C++ le". Voila ce que cela donne : Fichier hop.cpp : 1 // Dfinitions 2 void f(int x) {
5. On peut aussi glisser/dplacer le chier depuis lexplorateur Windows 6. Toujours le moindre eort !... 7. Les chiers den-tte iostream, etc. sont parfois appels en-ttes systme. Leur nom ne se termine pas toujours par .h (voir aprs) 8. .h comme header.

74

6. Plusieurs chiers !

6.1. Fichiers spars

} int g() { ... } // Autres fonctions ... Fichier hop.h : 1 // Dclarations 2 void f(int x); 3 int g(); Fichier main.cpp du premier projet : 1 #include "hop.h" 2 ... 3 int main() { 4 ... 5 // Utilisation 6 int a=g(); 7 f(a); 8 ... Fichier main2.cpp du deuxime projet (il faut prciser lemplacement complet de len-tte, qui se trouve dans le rpertoire du premier projet 9 ) : 1 #include "../Project1/hop.h" 2 ... 3 int main() { 4 ... 5 // Utilisation 6 f(12); 7 int b=g(); 8 ... En fait, pour tre sur que les fonctions dnies dans hop.cpp sont cohrentes avec leur dclaration dans hop.h, et bien que a soit pas obligatoire, on inclut aussi len-tte dans le source : ce qui donne : Fichier hop.cpp : 1 #include "hop.h" 2 ... 3 // Dfinitions 4 void f(int x) { 5 ... 6 } 7 int g() { 8 ... 9 } 10 // Autres fonctions 11 ...
9. On peut aussi prciser au compilateur une liste de rpertoires o il peut aller chercher les chiers den-tte. Utiliser pour cela les proprits du projet, option "C/C++ / General / Additional Include Directories" en prcisant "../Project1" comme rpertoire. Aprs quoi, un #include "hop.h" sut.

3 4 5 6 7 8 9

...

75

6.1. Fichiers spars

6. Plusieurs chiers !

En pratique, le chier den-tte ne contient pas seulement les dclarations des fonctions mais aussi les dnitions des nouveaux types (comme les structures) utiliss par le chier spar. En eet, ces nouveaux types doivent tre connus du chier spar, mais aussi du chier principal. Il faut donc vraiment : 1. Mettre dans len-tte les dclarations des fonctions et les dnitions des nouveaux types. 2. Inclure len-tte dans le chier principal mais aussi dans le chier spar. Cela donne par exemple : Fichier vect.h : 1 // Types 2 struct Vecteur { 3 double x,y; 4 }; 5 // Dclarations 6 double norme(Vecteur V); 7 Vecteur plus(Vecteur A,Vecteur B); Fichier vect.cpp : 1 #include "vect.h" // Fonctions et types 2 // Dfinitions 3 double norme(Vecteur V) { 4 ... 5 } 6 Vecteur plus(Vecteur A,Vecteur B) { 7 ... 8 } 9 // Autres fonctions 10 ... Fichier main.cpp du premier : 1 #include "vect.h" 2 ... 3 int main() { 4 ... 5 // Utilisation 6 Vecteur C=plus(A,B); 7 double n=norme(C); 8 ...

6.1.5

A ne pas faire...

Il est "fortement" conseill de : 1. ne pas dclarer dans len-tte toutes les fonctions du chier spar mais seulement celles qui seront utilises par le chier principal. Les fonctions secondaires nont pas apparatre 10 .
10. On devrait mme tout faire pour bien les cacher et pour interdire au chier principal de les utiliser. Il serait possible de le faire ds maintenant, mais nous en reparlerons plutt quand nous aborderons les objets...

76

6. Plusieurs chiers !

6.1. Fichiers spars

2. ne jamais inclure un chier spar lui-mme ! Cest gnralement une "grosse btise" 11 . Donc pas de #include "vect.cpp" !

6.1.6

Implmentation

Finalement, la philosophie de ce systme est que Le chier spar et son en-tte forment un tout cohrent, implmentant un certain nombre de fonctionnalits. Celui qui les utilise, qui nest pas ncessairement celui qui les a programmes, se contente de rajouter ces chiers son projet, dinclure len-tte dans ses sources et de proter de ce que len-tte dclare. Le chier den-tte doit tre susamment clair et informatif pour que lutilisateur nait pas regarder le chier spar lui-mme a .
a. Dailleurs, si lutilisateur le regarde, il peut tre tent de tirer prot de ce qui sy trouve et dutiliser plus que ce que len-tte dclare. Or, le crateur du chier spar et de len-tte peut par la suite tre amen changer dans son source la faon dont il a programm les fonctions sans pour autant changer leurs fonctionnalits. Lutilisateur qui a "trich" en allant regarder dans le chier spar peut alors voir ses programmes ne plus marcher. Il na pas respect la rgle du jeu qui tait de nutiliser que les fonctions de len-tte sans savoir comment elles sont implmentes. Nous reparlerons de tout a avec les objets. Nous pourrons alors faire en sorte que lutilisateur ne triche pas... De toute faon, notre niveau actuel, le crateur et lutilisateur sont une seule et mme personne. A elle de ne pas tricher !

6.1.7

Inclusions mutuelles

En passant laction, le dbutant dcouvre souvent des problmes non prvus lors du cours. Il est mme en gnral imbattable pour cela ! Le problme le plus frquent qui survient avec les chiers den-tte est celui de linclusion mutuelle. Il arrive que les chiers den-tte aient besoin den inclure dautres eux-mmes. Or, si le chier A.h inclut B.h et si B.h inclut A.h alors toute inclusion de A.h ou de B.h se solde par une phnomne dinclusions sans n qui provoque une erreur 12 . Pour viter cela, on utilise une instruction du pr-processeur signalant quun chier dj inclus ne doit plus ltre nouveau : on ajoute #pragma once au dbut de chaque chier den-tte . Certains compilateurs peuvent ne pas connatre #pragma once. On utilise alors une astuce que nous donnons sans explication : Choisir un nom unique propre au chier den-tte. Par exemple VECT_H pour le chier vect.h. Placer #ifndef VECT_H et #define VECT_H au dbut du chier vect.h et #endif la n.
11. Une mme fonction peut alors se retrouver dnie plusieurs fois : dans le chier spar et dans le chier principal qui linclut. Or, sil est possible de dclarer autant de fois que ncessaire une fonction, il est interdit de la dnir plusieurs fois (ne pas confondre avec la surcharge qui rend possible lexistence de fonctions direntes sous le mme nom - cf section 3.2.6) 12. Les pr-processeurs savent heureusement dtecter ce cas de gure.

77

6.2. Oprateurs

6. Plusieurs chiers !

6.2

Oprateurs

Le C++ permet de dnir les oprateurs +, -, etc. quand les oprandes sont de nouveaux types. Voici trs succinctement comment faire. Nous laissons au lecteur le soin de dcouvrir seul quels sont les oprateurs quil est possible de dnir. Considrons lexemple suivant qui dnit un vecteur 13 2D et en implmente laddition :
1 2 3 4 5 6 7 8 9 10 11 12

struct vect { double x,y; }; vect plus(vect m,vect n) { vect p={m.x+n.x,m.y+n.y}; return p; } int main() { vect a={1,2},b={3,4}; vect c=plus(a,b); return 0; }

Voici comment dnir le + entre deux vect et ainsi remplacer la fonction plus() :
1 2 3 4 5 6 7 8 9 10 11 12

struct vect { double x,y; }; vect operator+(vect m,vect n) { vect p={m.x+n.x,m.y+n.y}; return p; } int main() { vect a={1,2},b={3,4}; vect c=a+b; return 0; }

Nous pouvons aussi dnir un produit par un scalaire, un produit scalaire 14 , etc 15 .
15 16 17 18 19 20 21

// Produit par un scalaire vect operator*(double s,vect m) { vect p={s*m.x,s*m.y}; return p; } // Produit scalaire double operator*(vect m,vect n) {
13. Coin des collgiens : vous ne savez pas ce quest un vecteur... mais vous tes plus forts en programmation que les "vieux". Alors regardez les sources qui suivent et vous saurez ce quest un vecteur 2D ! 14. Dans ce cas, on utilise a*b et non a.b, le point ntant pas dnissable car rserv laccs aux champs de la structure 15. On peut en fait dnir ce qui existe dj sur les types de base. Attention, il est impossible de rednir les oprations des types de base ! Pas question de donner un sens dirent 1+1.

78

6. Plusieurs chiers !

6.3. Rcration : TP suite et n

22 23 24 25 26 27 28 29

return m.x*n.x+m.y*n.y; } int main() { vect a={1,2},b={3,4}; vect c=2*a; double s=a*b; return 0; } Remarquez que les deux fonctions ainsi dnies sont direntes bien que de mme nom (operator*) car elles prennent des paramtres dirents (cf surcharge section 3.2.6).

6.3

Rcration : TP suite et n

Le programme du TP prcdent tant un exemple parfait de besoin de chiers spars (structures bien identies, partages par deux projets), nous vous proposons, dans le TP A.5 de convertir (et terminer ?) notre programme de simulation de gravitation et de duel dans lespace !

6.4

Fiche de rfrence

La che habituelle...

Fiche de rfrence (1/3) Variables Dnition : int i; int k,l,m; Aectation : i=2; j=i; k=l=3; Initialisation : int n=5,o=n; Constantes : const int s=12; Porte : int i; // i=j; interdit! int j=2; i=j; // OK! if (j>1) { int k=3; j=k; // OK! } //i=k; interdit! Types : int i=3; double x=12.3; char c=A; string s="hop"; bool t=true; float y=1.2f; unsigned int j=4; signed char d=-128; unsigned char d=254; complex<double> z(2,3); Variables globales : int n; const int m=12; void f() { n=10; // OK int i=m; // OK ... Conversion : int i=int(x); int i,j; double x=double(i)/j; Tests Comparaison : == != < > <= >= Ngation : ! Combinaisons : && || if (i==0) j=1; if (i==0) j=1; else j=2; if (i==0) { j=1; k=2; } bool t=(i==0); if (t) j=1; switch (i) { case 1: ...; ...; break; case 2: case 3: ...; break; default: ...; }

79

6.4. Fiche de rfrence

6. Plusieurs chiers !

Fiche de rfrence (2/3) Boucles do { ... } while (!ok); int i=1; while (i<=100) { ... i=i+1; } for (int i=1;i<=100;i++) ... for (int i=1,j=100;j>i; i=i+2,j=j-3) ... Fonctions Dnition : int plus(int a,int b) { int c=a+b; return c; } void affiche(int a) { cout << a << endl; } Dclaration : int plus(int a,int b); Retour : int signe(double x) { if (x<0) return -1; if (x>0) return 1; return 0; } void afficher(int x, int y) { if (x<0 || y<0) return; if (x>=w || y>=h) return; DrawPoint(x,y,Red); } Appel : int f(int a) { ... } int g() { ... } ... int i=f(2),j=g(); Rfrences : void swap(int& a,int& b) { int tmp=a; a=b;b=tmp; } ... int x=3,y=2; swap(x,y); Surcharge : int hasard(int n); int hasard(int a,int b); double hasard(); Oprateurs : vect operator+( vect A,vect B) { ... } ... vect C=A+B; Tableaux Dnition : double x[10],y[10]; for (int i=0;i<10;i++) y[i]=2*x[i]; const int n=5; int i[n],j[2*n]; // OK Initialisation : int t[4]={1,2,3,4}; string s[2]={"ab","cd"}; Aectation : int s[4]={1,2,3,4},t[4]; for (int i=0;i<4;i++) t[i]=s[i]; En paramtre : void init(int t[4]) { for (int i=0;i<4;i++) t[i]=0; } void init(int t[], int n) { for (int i=0;i<n;i++) t[i]=0; } Structures struct Point { double x,y; Color c; }; ... Point a; a.x=2.3; a.y=3.4; a.c=Red; Point b={1,2.5,Blue}; Compilation spare #include "vect.h", y compris dans vect.cpp Fonctions : dclarations dans le .h, dnitions dans le .cpp Types : dnitions dans le .h Ne dclarer dans le .h que les fonctions utiles. #pragma once au dbut du chier. Ne pas trop dcouper... Divers i++; i--; i-=2; j+=3; j=i%n; // Modulo #include <cstdlib> ... i=rand()%n; x=rand()/double(RAND_MAX); #include <ctime> ... srand( unsigned int(time(0))); #include <cmath> double sqrt(double x); double cos(double x); double sin(double x); double acos(double x); #include <string> using namespace std; string s="hop"; char c=s[0]; int l=s.size(); Entres/Sorties #include <iostream> using namespace std; ... cout << "I=" << i << endl; cin >> i >> j;

80

6. Plusieurs chiers !

6.4. Fiche de rfrence

Fiche de rfrence (3/3) Erreurs frquentes Pas de dnition de fonction dans une fonction ! int q=r=4; // NON! if (i=2) // NON! if i==2 // NON! if (i==2) then // NON! for (int i=0,i<100,i++) // NON! int f() {...} ... int i=f; // NON! double x=1/3; // NON! int i,j; double x; x=i/j; // NON! x=double(i/j); //NON! double x[10],y[10]; for (int i=1;i<=10;i++) // NON! y[i]=2*x[i]; int n=5; int t[n]; // NON int f()[4] { // NON! int t[4]; ... return t; // NON! } ... int t[4] t=f(); int s[4]={1,2,3,4},t[4]; t=s; // NON! int t[2]; t={1,2}; // NON! struct Point { double x,y; } // NON! Point a; a={1,2}; // NON! #include "vect.cpp"// NON! Stop : Maj+F5 Step over : F10 Step inside : F11 Indent : Ctrl+K,Ctrl+F Add New It. : Ctrl+Maj+A Add Exist. It. : Alt+Maj+A

CLGraphics Voir documentation... Clavier Build : F7 Start : Ctrl+F5 Compile : Ctrl+F7 Debug : F5

Conseils Travailler en local CertisLibs Project Nettoyer en quittant. Erreurs et warnings : cliquer. Indenter. Ne pas laisser de warning. Utiliser le debuggeur. Faire des fonctions. Tableaux : quand cest utile ! (Pas pour transcrire une formule mathmatique.) Faire des structures. Faire des chiers spars. Le .h doit sure lutilisateur (qui ne doit pas regarder le .cpp)

81

7. La mmoire

Chapitre 7 La mmoire
Il est grand temps de revenir sur la mmoire et son utilisation. Nous pourrons alors mieux comprendre les variables locales, comment marche exactement lappel dune fonction, les fonctions rcursives, etc. Aprs cela, nous pourrons enn utiliser des tableaux de taille variable (sans pour autant rentrer vraiment dans la notion dlicate de pointeur).

7.1

Lappel dune fonction

Il sagit l dune nouvelle occasion pour vous de comprendre enn ce qui se passe dans un programme...

7.1.1
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20

Exemple

Considrons le programme suivant : #include <iostream> using namespace std; void verifie(int p, int q,int quo,int res) { if (res<0 || res>=q || q*quo+res!=p) cout << "Tiens, cest bizarre!" << endl; } int divise(int a,int b,int& r) { int q; q=a/b; r=a-q*b; verifie(a,b,q,r); return q; } int main() { int num,denom; do { cout << "Entrez deux entiers positifs: ";

7.1. Lappel dune fonction

7. La mmoire

21 22 23 24 25 26 27 28

cin >> num >> denom; } while (num<=0 || denom<=0); int quotient,reste; quotient=divise(num,denom,reste); cout << num << "/" << denom << " = " << quotient << " (Il reste " << reste << ")" << endl; return 0; } Calculant le quotient et le reste dune division entire, et vriant quils sont corrects, il nest pas passionnant et surtout inutilement long (en fait, ce sont juste les lignes 11 et 12 qui font tout !). Il sagit par contre dun bon exemple pour illustrer notre propos. Une bonne faon dexpliquer exhaustivement son droulement est de remplir le tableau suivant, dj rencontr au TP A.2. En ne mettant que les lignes o les variables changent, en supposant que lutilisateur rentre 23 et 3 au clavier, et en indiant avec des lettres les direntes tapes dune mme ligne 1 , cela donne :
Ligne 18 21 23 24a 9 10 11 12 13a 4 5 7 13b 14 15 24b 25 28 num ? 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 denom ? 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 quotient reste a b r qd retd pv qv quo res

? ? ? ? ? ? ? ? ? ? ? ? ? 7 7

? ? ? ? ? 2 2 2 2 2 2 2 2 2 2

23 23 23 23 23 23 23 23 23 23

3 3 3 3 3 3 3 3 3 3

[reste] [reste] [reste] [reste] [reste] [reste] [reste] [reste] [reste] [reste]

? 7 7 7 7 7 7 7 7

23 23

3 3

7 7

2 2

7 7 7

A posteriori, on constate quon a implicitement suppos que lorsque le programme est en train dexcuter divise(), la fonction main() et ses variables existent encore et quelles attendent simplement la n de divise(). Autrement dit : Un appel de fonction est un mcanisme qui permet de partir excuter momentanment cette fonction puis de retrouver la suite des instructions et les variables quon avait provisoirement quittes. Les fonctions sappelant les unes les autres, on se retrouve avec des appels de fonctions imbriqus les uns dans les autres : main() appelle divise() qui lui-mme appelle verifie() 2 . Plus prcisment, cette imbrication est un empilement et on parle de pile des appels. Pour mieux comprendre cette pile, nous allons utiliser le debuggeur. Avant cela, prcisons ce quun informaticien entend par pile.
1. par exemple 24a et 24b 2. Et dailleurs main() a lui-mme t appel par une fonction a laquelle il renvoie un int.

84

7. La mmoire

7.1. Lappel dune fonction

Pile/File Une pile est une structure permettant de mmoriser des donnes dans laquelle celles-ci sempilent de telle sorte que celui qui est rang en dernier dans la pile en est extrait en premier. En anglais, une pile (stack ) est aussi appele LIFO (last in rst out 3 ). On y empile (push) et on y dpile (pop) les donnes. Par exemple, aprs un push(1), un push(2) et un push(3), le premier pop() donnera 3, le deuxime pop() donnera 2 et un dernier pop() donnera 1. Pour une le (en anglais queue), cest la mme chose mais le premier arriv est le premier sorti (FIFO). Par exemple, aprs un push(1), un push(2) et un push(3), le premier pop() donnera 1, le deuxime pop() donnera 2 et un dernier pop() donnera 3.

7.1.2

Pile des appels et dbuggeur

Observons donc la gure 7.1 obtenue en lanant notre programme dexemple sous debuggeur. En regardant la partie gauche de chaque tape, nous pouvons voir la pile des appels. La partie droite ache le contenu des variables, paramtres et valeurs de retour dont nous pouvons constater la cohrence avec le tableau prcdent. (a) Comme lindique la pile des appels, nous sommes ligne 24 de la fonction main(), qui se trouve en haut de la pile. Plus profond dans la pile, nous voyons que main() a t appel par une fonction mainCRTStartup() elle mme appele par un trange kernel32.dll 4 . Vriez aussi les variables et le fait quelles valent nimporte quoi (ici -858993460 !) tant quelles ne sont pas initialises ou aectes. (b) Avanons en pas--pas dtaill (touche F11) jusqu la ligne 12. Nous sommes dans la fonction divise(), q vient de valoir 7, et la ligne 24 de main() est descendue dun cran dans la pile des appels. (c) Nous sommes maintenant la ligne 5 dans verifie(). La pile des appels un niveau de plus, divise() est en attente la ligne 13 et main() toujours en 24. Vriez au passage que la variable q ache est bien celle de verifie(), qui vaut 3 et non pas celle de divise(). (d) Ici, lexcution du programme na pas progresse et nous en sommes toujours la ligne 5. Simplement, Visual ore la possibilit en double-cliquant sur la pile dappel de regarder ce qui se passe un des niveaux infrieurs, notamment pour acher les instructions et les variables de ce niveau. Ici, en cliquant sur la ligne de divise() dans la fentre de la ligne dappel, nous voyons apparatre la ligne 13 et ses variables dans leur tat alors que le programme est en 5. Entre autres, le q ach est celui de divise() et vaut 7. (e) Toujours sans avancer, voici ltat du main() et de ses variables (entre autres, reste est bien pass 2 depuis la ligne 12 de divise(). (f) Nous excutons maintenant la suite jusqu nous retrouver en ligne 24 au retour de divise(). Pour cela, on peut faire du pas--pas dtaill, ou simplement deux fois de
3. Dernier rentr, premier sorti. 4. Ces deux fonctions sont respectivement : (i) la fonction que le compilateur cre pour faire un certain nombre de choses avant et aprs main() et (ii) la partie de Windows qui lance le programme lui-mme.

85

7.1. Lappel dune fonction

7. La mmoire

(a)

(b)

(c)

(d)

(e)

(f)

(g) Figure 7.1 Appels de fontions 86

7. La mmoire

7.2. Variables Locales

pile

variable

valeur

pile top

place libre

top

a b qd r denom num quotient reste pris par les les fonctions avant main()

23 3 7 [reste] 3 23 ? ? ... ... ...

variable place libre pv qv quo res a b qd r denom num quotient reste pris par les les fonctions avant main()

valeur 23 3 7 2 23 3 7 [reste] 3 23 ? 2 ... ... ...

pile

variable

valeur

place libre

top

denom num quotient reste pris par les les fonctions avant main()

3 23 7 2 ... ... ...

Figure 7.2 Pile et variables locales. De gauche droite : tape (b) (ligne 12), tape (c) (ligne 5) et tape (g) (ligne 25/26). suite un pas--pas sortant 5 (Maj-F11) pour relancer jusqu sortir de verifie(), puis jusqu sortir de divise(). On voit bien quotient, qui est encore non dni, et aussi la valeur de retour de divise(), non encore aecte quotient. (g) Un pas--pas de plus et nous sommes en 25/26. La variable quotient vaut enn 7.

7.2

Variables Locales

Il va tre important pour la suite de savoir comment les paramtres et les variables locales sont stocks en mmoire.

7.2.1

Paramtres

Pour les paramtres, cest simple : Les paramtres sont en fait des variables locales ! Leur seule spcicit est dtre initialiss ds le dbut de la fonction avec les valeurs passes lappel de la fonction.

7.2.2

La pile

Les variables locales (et donc les paramtres) ne sont pas mmorises des adresses xes en mmoire 6 , dcides la compilation. Si on faisait a, les adresses mmoire en question devraient tre rserves pendant toute lexcution du programme : on ne pourrait y ranger les variables locales dautres fonctions. La solution retenue est beaucoup plus conome en mmoire 7 :
5. Step Out ou Maj-F11 ou . Notez aussi possibilit de continuer le programme jusqu une certaine ligne sans avoir besoin de mettre un point darrt temporaire sur cette ligne mais simplement en cliquant sur la ligne avec le bouton de droite et en choisissant "excuter jusqu cette ligne" ( ) 6. Souvenons nous du chapitre 2. 7. Et permettra de faire des fonctions rcursives, cf section suivante !

87

7.3. Fonctions rcursives

7. La mmoire

Les variables locales sont mmorises dans un pile : Quand une variables locale est cre, elle est rajoute en haut de cette pile. Quand elle meurt (en gnral quand on quitte sa fonction) elle est sortie de la pile. Ainsi, au fur et mesure des appels, les variables locales sempilent : la mmoire est utilise juste pendant le temps ncessaire. La gure 7.2 montre trois tapes de la pile pendant lexcution de notre exemple.

7.3

Fonctions rcursives

Un fonction rcursive est une fonction qui sappelle elle-mme. La fonction la plus classique pour illustrer la rcursivit est la factorielle 8 . Voici une faon simple et rcursive de la programmer :
5 6 7 8 9 10

int fact1(int n) { if (n==1) return 1; return n*fact1(n-1); }

On remarque videmment que les fonctions rcursives contiennent (en gnral au dbut, et en tout cas avant lappel rcursif !) une condition darrt : ici si n vaut 1, la fonction retourne directement 1 sans sappeler elle-mme 9 .

7.3.1

Pourquoi a marche ?

Si les fonctions avaient mmoris leurs variables locales des adresses xes, la rcursivit naurait pas pu marcher : lappel rcursif aurait cras les valeurs des variables. Par exemple, fact1(3) aurait cras la valeur 3 mmorise dans n par un 2 en appelant fact1(2) ! Cest justement grce la pile que le n de fact1(2) nest pas le mme que celui de fact1(3). Ainsi, lappel fact1(3) donne-til le tableau suivant :
Ligne 5f act1(3) 9af act1(3) 5f act1(2) 9af act1(2) 5f act1(1) 8f act1(1) 10f act1(1) 9bf act1(2) 10f act1(2) 9bf act1(3) 10f act1(3) nf act1(3) 3 3 3 3 3 3 3 3 3 3 retf act1(3) nf act1(2) retf act1(2) nf act1(1) retf act1(1)

2 2 2 2 2 2 6 6

1 1 2 2 2

1 1 1

8. Coin des collgiens : La factorielle dun nombre entier n scrit n! et vaut n! = 1 2 ... n. 9. Le fait de pouvoir mettre des return au milieu des fonctions est ici bien commode !

88

7. La mmoire

7.3. Fonctions rcursives

Ce tableau devient dicile crire maintenant quon sait que les variables locales ne dpendent pas que de la fonction mais changent chaque appel ! On est aussi oblig de prciser, pour chaque numro de ligne, quel appel de fonction est concern. Si on visualise la pile, on comprend mieux pourquoi a marche. Ainsi, arrivs en ligne 8 de fact1(1) pour un appel initial fact1(3), la pile ressemble : pile top variable place libre nf act1(1) nf act1(2) nf act1(3) valeur 1 2 3

ce que lon peut aisment vrier avec le dbuggeur. Finalement : Les fonctions rcursives ne sont pas direntes des autres. Cest le systme dappel des fonctions en gnral qui rend la rcursivit possible.

7.3.2

Ecacit

Une fonction rcursive est simple et lgante crire quand le problme sy prte 10 . Nous venons de voir quelle nest toujours pas facile suivre ou debugger. Il faut aussi savoir que la pile des appels nest pas innie et mme relativement limite. Ainsi, le programme suivant
22 23 24 25 26 27 28

// Fait dborder la pile int fact3(int n) { if (n==1) return 1; return n*fact3(n+1); // erreur! } dans lequel une erreur sest glisse va sappeler thoriquement linni et en pratique sarrtera avec une erreur de dpassement de la pile des appels 11 . Mais la vraie raison qui fait quon vite parfois le rcursif est qu appeler une fonction est un mcanisme coteux ! Lorsque le corps dune fonction est susamment petit pour que le fait dappeler cette fonction ne soit pas ngligeable devant le temps pass excuter la fonction elle-mme, il est prfrable dviter ce mcanisme dappel 12 . Dans le cas dune fonction rcursive, on essaie donc si cest ncessaire dcrire une version drcursive (ou itrative) de la fonction. Pour notre factorielle, cela donne :
10. Cest une erreur classique de dbutant que de vouloir abuser du rcursif. 11. Sous Visual, il sarrte pour n = 5000 environ. 12. Nous verrons dans un autre chapitre les fonctions inline qui rpondent ce problme.

89

7.4. Le tas

7. La mmoire

1 2 3 4 5 6 7 8

// Version itrative int fact2(int n) { int f=1; for (int i=2;i<=n;i++) f*=i; return f; }

ce qui aprs tout nest pas si terrible. Enn, il arrive qucrire une fonction sous forme rcursive ne soit pas utilisable pour des raisons de complexit. Une exemple classique est la suite de Fibonacci dnie par : f0 = f1 = 1 fn = fn1 + fn2 et qui donne : 1, 1, 2, 3, 5, 8, ... En version rcursive :
32 33 34 35 36 37

// Trs lent! int fib1(int n) { if (n<2) return 1; return fib1(n-2)+fib1(n-1); } cette fonction a la mauvaise ide de sappeler trs souvent : n = 10 appelle n = 9 et n = 8, mais n = 9 appelle lui aussi n = 8 de son ct en plus de n = 7, n = 7 qui lui-mme est appel par tous les n = 8 lancs, etc. Bref, cette fonction devient rapidement trs lente. Ainsi, pour n = 40, elle sappelle dj 300.000.000 de fois elle mme, ce qui prend un certain temps ! Il est donc raisonnable den programmer une version drcursive :

39 40 41 42 43 44 45 46 47 48

// Drcursive int fib2(int n) { int fnm2=1,fnm1=1; for (int i=2;i<=n;i++) { int fn=fnm2+fnm1; fnm2=fnm1; fnm1=fn; } return fnm1; } Mentionnons aussi quil existe des fonctions susamment tordues pour que leur version rcursive ne se contente pas de sappeler un grand nombre de fois en tout, mais un grand nombre de fois en mme temps, ce qui fait quindpendamment des questions decacit, leur version rcursive fait dborder la pile dappels !

7.4

Le tas

La pile nest pas la seule zone de mmoire utilise par les programmes. Il y a aussi le tas (heap en anglais). 90

7. La mmoire

7.4. Le tas

7.4.1

Limites

La pile est limite en taille. La pile dappel ntant pas innie et les variables locales ntant pas en nombre illimit, il est raisonnable de rserver une pile de relativement petite taille. Essayez donc le programme :
32 33 34 35 36 37

int main() { const int n=500000; int t[n]; ... } Il sexcute avec une erreur : "stack overow". La variable locale t nest pas trop grande pour lordinateur 13 : elle est trop grande pour tenir dans la pile. Jusqu prsent, on savait quon tait limit aux tableaux de taille constante. En ralit, on est aussi limit aux petits tableaux. Il est donc grand temps dapprendre utiliser le tas !

7.4.2

Tableaux de taille variable

Nous fournissons ici une rgle appliquer en aveugle. Sa comprhension viendra plus tard si ncessaire. Lorsquon veut utiliser un tableau de taille variable, il ny a que deux choses faire, mais elle sont essentielles toutes les deux 14 : 1. Remplacer int t[n] par int* t=new int[n] (ou lquivalent pour un autre type que int) 2. Lorsque le tableau doit mourir (en gnral en n de fonction), rajouter la ligne delete[] t ; Le non respect de la rgle 2 fait que le tableau reste en mmoire jusqu la n du programme, ce qui entraine en gnral une croissance anarchique de la mmoire utilise (on parle de fuite de mmoire). Pour le reste, on ne change rien. Programmer un tableau de cette faon fait quil est mmoris dans le tas et non plus dans la pile. On fait donc ainsi : 1. Pour les tableaux de taille variable. 2. Pour les tableaux de grande taille. Voici ce que cela donne sur un petit programme :
1 2 3 4 5 6 7

#include <iostream> using namespace std; void remplit(int t[], int n) { for (int i=0;i<n;i++) t[i]=i+1;

13. 500000x4 soit 2Mo seulement ! 14. Et le dbutant oublie toujours la deuxime, ce qui a pour consquence des programmes qui grossissent en quantit de mmoire occupe...

91

7.4. Le tas

7. La mmoire

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

} int somme(int t[], int n) { int s=0; for (int i=0;i<n;i++) s+=t[i]; return s; } void fixe() { const int n=5000; int t[n]; remplit(t,n); int s=somme(t,n); cout << s << " devrait valoir " << n*(n+1)/2 << endl; } void variable() { int n; cout << "Un entier SVP: "; cin >> n; int* t=new int[n]; // Allocation remplit(t,n); int s=somme(t,n); cout << s << " devrait valoir " << n*(n+1)/2 << endl; delete[] t; // Desallocation: ne pas oublier! } int main() { fixe(); variable(); return 0; }

7.4.3

Essai dexplication

Ce qui suit nest pas essentiel pour un dbutant mais peut ventuellement rpondre ses interrogations. Sil comprend, tant mieux, sinon, quil oublie et se contente pour linstant de la rgle prcdente ! Pour avoir accs toute la mmoire de lordinateur 15 , on utilise le tas. Le tas est une zone mmoire que le programme possde et qui peut crotre sil en fait la demande au systme dexploitation (et sil reste de la mmoire de libre videmment). Pour utiliser le
15. Plus exactement ce que le systme dexploitation veut bien attribuer au maximum chaque programme, ce qui est en gnral rglable mais en tout cas moins que la mmoire totale, bien que beaucoup plus que la taille de la pile.

92

7. La mmoire

7.5. Loptimiseur

tas, on appelle une fonction dallocation laquelle on demande de rserver en mmoire de la place pour un certain nombre de variables. Cest ce que fait new int[n]. Cette fonction retourne ladresse de lemplacement mmoire quelle a rserv. Nous navons jamais rencontr de type de variable capable de mmoriser une adresse. Il sagit des pointeurs dont nous reparlerons plus tard. Un pointeur vers de la mmoire stockant des int est de type int*. Do le int* t pour mmoriser le retour du new. Ensuite, un pointeur peut sutiliser comme un tableau, y compris comme paramtre dune fonction. Enn, il ne faut pas oublier de librer la mmoire au moment o le tableau de taille constante aurait disparu : cest ce que fait la fonction delete[] t qui libre la mmoire pointe par t.

7.5

Loptimiseur

Mentionnons ici un point important qui tait nglig jusquici, mais que nous allons utiliser en TP. Il y a plusieurs faons de traduire en langage machine un source C++. Le rsultat de la compilation peut donc tre dirent dun compilateur lautre. Au moment de compiler, on peut aussi rechercher produire un excutable le plus rapide possible : on dit que le compilateur optimise le code. En gnral, loptimisation ncessite un plus grand travail mais aussi des transformations qui font que le code produit nest plus facilement dbuggable. On choisit donc en pratique entre un code debuggable et un code optimis. Jusquici, nous utilisions toujours le compilateur en mode "Debug". Lorsquun programme est au point (et seulement lorsquil lest), on peut basculer le compilateur en mode "Release" pour avoir un programme plus performant. Dans certains cas, les gains peuvent tre considrables. Un programmeur expriment fait mme en sorte que loptimiseur puisse ecacement faire son travail. Ceci dit, il faut respecter certaines rgles : Ne pas debugger quand on est en mode Release ( !) Rester en mode Debug le plus longtemps possible pour bien mettre au point le programme. Savoir que les modes Debug et Release crent des chiers objets et excutables dans des rpertoires dirents et donc que lorsquon nettoie les solutions, il faut le faire pour chacun des deux modes.

7.6

TP

Le TP que nous proposons en A.6 est une illustration des fonctions rcursive et des tableaux de taille variable. Il consiste en la programmation de quelques faons de trier des donnes : tri bulle, Quicksort, etc. An de rendre le tout plus attrayant, le tri sera visualis graphiquement et chronomtr (gure 7.3).

93

7.7. Fiche de rfrence

7. La mmoire

Figure 7.3 Deux tris en cours dexcution : tri bulle et Quicksort...

7.7

Fiche de rfrence

Fiche de rfrence (1/3) Variables Dnition : int i; int k,l,m; Aectation : i=2; j=i; k=l=3; Initialisation : int n=5,o=n; Constantes : const int s=12; Porte : int i; // i=j; interdit! int j=2; i=j; // OK! if (j>1) { int k=3; j=k; // OK! } //i=k; interdit! Types : int i=3; double x=12.3; char c=A; string s="hop"; bool t=true; float y=1.2f; unsigned int j=4; signed char d=-128; unsigned char d=254; complex<double> z(2,3); Variables globales : int n; const int m=12; void f() { n=10; // OK int i=m; // OK ... Conversion : int i=int(x); int i,j; double x=double(i)/j; Pile/Tas Tests Comparaison : == != < > <= >= Ngation : ! Combinaisons : && || if (i==0) j=1; if (i==0) j=1; else j=2; if (i==0) { j=1; k=2; } bool t=(i==0); if (t) j=1; switch (i) { case 1: ...; ...; break; case 2: case 3: ...; break; default: ...; } Boucles do { ... } while (!ok); int i=1; while (i<=100) { ... i=i+1; } for (int i=1;i<=100;i++) ... for (int i=1,j=100;j>i; i=i+2,j=j-3) ...

94

7. La mmoire

7.7. Fiche de rfrence

Fiche de rfrence (2/3) Fonctions Dnition : int plus(int a,int b) { int c=a+b; return c; } void affiche(int a) { cout << a << endl; } Dclaration : int plus(int a,int b); Retour : int signe(double x) { if (x<0) return -1; if (x>0) return 1; return 0; } void afficher(int x, int y) { if (x<0 || y<0) return; if (x>=w || y>=h) return; DrawPoint(x,y,Red); } Appel : int f(int a) { ... } int g() { ... } ... int i=f(2),j=g(); Rfrences : void swap(int& a,int& b) { int tmp=a; a=b;b=tmp; } ... int x=3,y=2; swap(x,y); Surcharge : int hasard(int n); int hasard(int a,int b); double hasard(); Oprateurs : vect operator+( vect A,vect B) { ... } ... vect C=A+B; Pile des appels Itratif/Rcursif Tableaux Dnition : double x[10],y[10]; for (int i=0;i<10;i++) y[i]=2*x[i]; const int n=5; int i[n],j[2*n]; // OK Initialisation : int t[4]={1,2,3,4}; string s[2]={"ab","cd"}; Aectation : int s[4]={1,2,3,4},t[4]; for (int i=0;i<4;i++) t[i]=s[i]; En paramtre : void init(int t[4]) { for (int i=0;i<4;i++) t[i]=0; } void init(int t[], int n) { for (int i=0;i<n;i++) t[i]=0; } Taille variable : int* t=new int[n]; ... delete[] t; Structures struct Point { double x,y; Color c; }; ... Point a; a.x=2.3; a.y=3.4; a.c=Red; Point b={1,2.5,Blue}; Ne pas trop dcouper... Divers i++; i--; i-=2; j+=3; j=i%n; // Modulo #include <cstdlib> ... i=rand()%n; x=rand()/double(RAND_MAX); #include <ctime> ... srand( unsigned int(time(0))); #include <cmath> double sqrt(double x); double cos(double x); double sin(double x); double acos(double x); #include <string> using namespace std; string s="hop"; char c=s[0]; int l=s.size(); #include <ctime> s=double(clock()) /CLOCKS_PER_SEC; Entres/Sorties #include <iostream> using namespace std; ... cout << "I=" << i << endl; cin >> i >> j; Clavier Build : F7 Start : Ctrl+F5 Compile : Ctrl+F7 Debug : F5 Stop : Maj+F5 Step over : F10 Step inside : F11 Indent : Ctrl+K,Ctrl+F Add New It. : Ctrl+Maj+A Add Exist. It. : Alt+Maj+A

Compilation spare #include "vect.h", y compris dans vect.cpp Fonctions : dclarations dans le .h, dnitions dans le .cpp Types : dnitions dans le .h Ne dclarer dans le .h que les Step out : Maj+F11 fonctions utiles. Run to curs. : Click droit #pragma once au dbut du - Compltion : Alt+ chier.

95

7.8. Examens sur machine

7. La mmoire

Fiche de rfrence (3/3) Erreurs frquentes Pas de dnition de fonction dans une fonction ! int q=r=4; // NON! if (i=2) // NON! if i==2 // NON! if (i==2) then // NON! for (int i=0,i<100,i++) // NON! int f() {...} ... int i=f; // NON! double x=1/3; // NON! int i,j; double x; x=i/j; // NON! x=double(i/j); //NON! double x[10],y[10]; for (int i=1;i<=10;i++) // NON! y[i]=2*x[i]; int n=5; int t[n]; // NON int f()[4] { // NON! int t[4]; ... return t; // NON! } ... int t[4] t=f(); int s[4]={1,2,3,4},t[4]; t=s; // NON! int t[2]; t={1,2}; // NON! struct Point { double x,y; } // NON! Point a; a={1,2}; // NON! #include "vect.cpp"// NON! CLGraphics Voir documentation... Conseils Travailler en local CertisLibs Project Nettoyer en quittant. Erreurs et warnings : cliquer. Indenter. Ne pas laisser de warning. Utiliser le debuggeur. Faire des fonctions. Tableaux : quand cest utile ! (Pas pour transcrire une formule mathmatique.) Faire des structures. Faire des chiers spars. Le .h doit sure lutilisateur (qui ne doit pas regarder le .cpp) Ne pas abuser du rcursif. Ne pas oublier delete. Compiler rgulirement. Debug/Release : nettoyer les deux.

7.8

Examens sur machine

Nous vous conseillons aussi de vous confronter aux examens proposs en annexe. Vous avez toutes les connaissances ncessaires.

96

8. Allocation dynamique

Chapitre 8 Allocation dynamique


Nous revenons une fois de plus sur lutilisation du tas pour grer des tableaux de taille variable. Aprs avoir mentionn lexistence de tableaux bidimensionnels de taille xe, nous dtaillons lallocation dynamique 1 dj vue en 7.4.2 et expliquons enn les pointeurs, du moins partiellement. A travers lexemple des matrices (et des images en TP) nous mlangeons structures et allocation dynamique. Il sagira l de notre structure de donne la plus complexe avant larrive tant attendue - et maintenant justie - des objets...

8.1
8.1.1

Tableaux bidimensionnels
Principe

Il existe en C++ des tableaux deux dimensions. Leur utilisation est similaire celle des tableaux standards : Il faut utiliser des crochets (lignes 1 et 4 du programme ci-dessous). Attention : [i][j] et non [i,j]. Linitialisation est possible avec des accolades (ligne 5). Attention : accolades imbriques. Leurs dimensions doivent tre constantes (lignes 6 et 7).
1 2 3 4 5 6 7

int A[2][3]; for (int i=0;i<2;i++) for (int j=0;j<3;j++) A[i][j]=i+j; int B[2][3]={{1,2,3},{4,5,6}}; const int M=2,N=3; int C[M][N];

8.1.2

Limitations

Vis--vis des fonctions, les particularits sont les mmes quen 1D : Impossible de retourner un tableau 2D. Passage uniquement par variable.
1. cest--dire lallocation de mmoire dans le tas avec new et delete.

8.1. Tableaux bidimensionnels

8. Allocation dynamique

mais avec une restriction supplmentaire : On est oblig de prciser les dimensions dun tableau 2D paramtre de fonction. Impossible donc de programmer des fonctions qui peuvent travailler sur des tableaux de direntes tailles comme dans le cas 1D (cf 4.3.1). Cest trs restrictif et explique que les tableaux 2D ne sont pas toujours utiliss. On peut donc avoir le programme suivant :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21

// Passage de paramtre double trace(double A[2][2]) { double t=0; for (int i=0;i<2;i++) t+=A[i][i]; return t; } // Le passage est toujours par rfrence... void set(double A[2][3]) { for (int i=0;i<2;i++) for (int j=0;j<3;j++) A[i][j]=i+j; } ... double D[2][2]={{1,2},{3,4}}; double t=trace(D); double E[2][3]; set(E); ...

mais il est impossible de programmer une fonction trace() ou set() qui marche pour direntes tailles de tableaux 2D comme on laurait fait en 1D :
1 2 3 4 5 6 7 8 9 10 11 12

// OK void set(double A[],int n,double x) { for (int i=0;i<n;i++) A[i]=x; } // NON!!!!!!!!!!!!!!! // double A[][] est refus void set(double A[][],double m,double n,double x) { for (int i=0;i<m;i++) for (int j=0;j<n;j++) A[i][j]=x; }

8.1.3

Solution

En pratique, ds que lon doit manipuler des tableaux de dimension 2 (ou plus !) de direntes tailles, on les mmorise dans des tableaux 1D en stockant par exemple les lignes 98

8. Allocation dynamique

8.2. Allocation dynamique

les unes aprs les autres pour proter des avantages des tableaux 1D. Ainsi, on stockera une matrice A de m lignes de n colonnes dans un tableau T de taille mn en plaant llment A(i, j) en T (i + mj). Cela donne :
1 2 3 4 5 6 7 8 9 10

void set(double A[],int m,int n) { for (int i=0;i<m;i++) for (int j=0;j<n;j++) A[i+m*j]=i+j; } ... double F[2*3]; set(F,2,3); double G[3*5]; set(G,3,5);

ou par exemple, ce produit matrice vecteur dans lequel les vecteurs et les matrices sont stocks dans des tableaux 1D :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15

// y=Ax void produit(double A[],int m,int n,double x[],double y[]) { for (int i=0;i<m;i++) { y[i]=0; for (int j=0;j<n;j++) y[i]+=A[i+m*j]*x[j]; } } ... double P[2*3],x[3],y[2]; ... // P=... x=... produit(P,2,3,x,y); // y=Px

8.2

Allocation dynamique

Il ny a pas dallocation dynamique possible pour les tableaux 2D. Il faut donc vraiment les mmoriser dans des tableaux 1D comme expliqu ci-dessus pour pouvoir les allouer dynamiquement dans le tas. Lexemple suivant montre comment faire. Il utilise la fonction produit() donne ci-dessus sans quil soit besoin de la rednir :
1 2 3 4 5 6 7 8 9

int m,n; ... double* A=new double[m*n]; double* x=new double[n]; double* y=new double[m]; ... // A=... x=... produit(A,m,n,x,y); // y=Ax ... 99

8.2. Allocation dynamique

8. Allocation dynamique

10 11 12

delete[] A; delete[] x; delete[] y;

8.2.1

Pourquoi a marche ?

Il est maintenant temps dexpliquer pourquoi, une fois allous, nous pouvons utiliser des tableaux dynamiques exactement comme des tableaux de taille xe. Il sut de comprendre les tapes suivantes : 1. int t[n] dnit une variable locale, donc de la mmoire dans la pile, capable de stocker n variables int. 2. int* t dnit une variable de type "pointeur" dint, cest--dire que t peut mmoriser ladresse dune zone mmoire contenant des int. 3. new int[n] alloue dans le tas une zone mmoire pouvant stocker n int et renvoie ladresse de cette zone. Do le int* t=new int[n] 4. delete[] t libre dans le tas ladresse mmorise dans t. 5. Lorsque t est un tableau de taille xe t[i] dsigne son ieme lment. Lorsque t est un pointeur dint, t[i] dsigne la variable int stocke i places 2 plus loin en mmoire que celle situe ladresse t. Ainsi, aprs un int t[n] comme aprs un int* t=new int[n], la syntaxe t[i] dsigne bien ce quon veut. 6. Lorsque t est un tableau de taille xe, la syntaxe t tout court dsigne ladresse (dans la pile) laquelle le tableau est mmoris. De plus, lorsquune fonction prend un tableau comme paramtre, la syntaxe int s[] signie en ralit que s est ladresse du tableau. Ce qui fait quen n de compte : une fonction f(int s[]) est conue pour quon lui passe une adresse s elle marche videmment avec les tableaux allous dynamiquement qui ne sont nalement que des adresses cest plutt lappel f(t), avec t tableau de taille xe, qui sadapte en passant f ladresse o se trouve le tableau. logiquement, on devrait mme dclarer f par f(int* s) au lieu de f(int s[]). Les deux sont en fait possibles et synonymes. Vous pouvez donc maintenant programmer, en comprenant, ce genre de choses :
1 2 3 4 5 6 7 8 9 10 11 12 13

double somme(double* t,int n) { // Syntaxe "pointeur" double s=0; for (int i=0;i<n;i++) s+=t[i]; return s; } ... int t1[4]; ... double s1=somme(t1,4); ... int* t2=new int[n]; ...
2. Ici, une place est videmment le nombre doctets ncessaires au stockage dun int.

100

8. Allocation dynamique

8.2. Allocation dynamique

14 15 16

double s2=somme(t2,n); ... delete[] t2;

8.2.2

Erreurs classiques

Vous comprenez maintenant aussi les erreurs classiques suivantes (que vous nviterez pas pour autant !). 1. Oublier dallouer : int *t; for (int i=0;i<n;i++) t[i]=... // Horreur: t vaut nimporte // quoi comme adresse 2. Oublier de dsallouer : void f(int n) { int *t=new int[n]; ... } // On oublie delete[] t; // Chaque appel f() va perdre n int dans le tas! 3. Ne pas dsallouer ce quil faut : int* t=new int[n]; int* s=new int[n]; ... s=t; // Aie! Du coup, s contient la mme adresse que t // (On na pas recopi la zone pointe par t dans celle // pointe par s!) ... delete[] t; // OK delete[] s; // Cata: Non seulement on ne libre pas la mmoire // initialement mmorise dans s, mais en plus on // dsalloue nouveau celle qui vient dtre libre!

8.2.3

Consquences

Quand librer ? Maintenant que vous avez compris new et delete, vous imaginez bien quon nattend pas toujours la n de lexistence du tableau pour librer la mmoire. Le plus tt est le mieux et on libre la mmoire ds que le tableau nest plus utilis :
1 2 3 4 5 6 7 8

void f() { int t[10]; int* s=new int[n]; ... delete[] s; // si s ne sert plus dans la suite... // Autant librer maintenant... ... } // Par contre, t attend cette ligne pour mourir. 101

8.2. Allocation dynamique

8. Allocation dynamique

En fait, le tableau dont ladresse est mmorise dans s est allou ligne 3 et libr ligne 5. La variable s qui mmorise son adresse, elle, est cre ligne 3 et meurt ligne 8 ! Pointeurs et fonctions Il est frquent que le new et le delete ne se fassent pas dans la mme fonction (attention, du coup, aux oublis !). Ils sont souvent intgrs dans des fonctions. A ce propos, lorsque des fonctions manipulent des variables de type pointeur, un certain nombre de questions peuvent se poser. Il sut de respecter la logique : Une fonction qui retourne un pointeur se dclare int* f(); 1 int* alloue(int n) { 2 return new int[n]; 3 } 4 .... 5 int* t=alloue(10); 6 ... Un pointeur pass en paramtre une fonction lest par valeur. Ne pas mlanger avec le fait quun tableau est pass par rfrence ! Considrez le programme suivant : 1 void f(int* t, int n) { 2 .... 3 t[i]=...; // On modifie t[i] mais pas t! 4 t=... // Une telle ligne ne changerait pas s 5 // dans la fonction appelante 6 } 7 ... 8 int* s=new int[m]; 9 f(s,m); En fait, cest parce quon passe ladresse dun tableau quon peut modier ses lments. Par ignorance, nous disions que les tableaux taient passs par rfrence en annonant cela comme une exception. Nous pouvons maintenant rectier : Un tableau est en fait pass via son adresse. Cette adresse est passe par valeur. Mais ce mcanisme permet la fonction appele de modier le tableau. Dire quun tableau est pass par rfrence tait un abus de langage simplicateur. Si on veut vraiment passer le pointeur par rfrence, la syntaxe est logique : int*& t. Un cas typique de besoin est : 1 // t et n seront modifis (et plus seulement t[i]) 2 void alloue(int*& t,int& n) { 3 cin >> n; // n est choisi au clavier 4 t=new int[n]; 5 } 6 ... 7 int* t; 8 int n; 9 alloue(t,n); // t et n sont affects par alloue() 10 ... 11 delete[] t; // Ne pas oublier pour autant!

102

8. Allocation dynamique

8.3. Structures et allocation dynamique

Bizzarerie ? Les lignes 7 et 8 ci-dessus auraient pu scrire int*s,n;. En fait, il faut remettre une toile devant chaque variable lorsquon dnit plusieurs pointeurs en mmetemps. Ainsi, int *t,s,*u; dnit deux pointeurs dint (les variables t et u) et un int (la variable s).

8.3

Structures et allocation dynamique

Passer systmatiquement un tableau et sa taille toutes les fonctions est videmment pnible. Il faut les runir dans une structure. Je vous laisse mditer lexemple suivant qui pourrait tre un passage dun programme implmentant des matrices 3 et leur produit :
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

#include <iostream> #include <string> using namespace std; //================================================== // fonctions sur les matrices // pourraient etre dans un matrice.h et un matrice.cpp struct Matrice { int m,n; double* t; }; Matrice cree(int m,int n) { Matrice M; M.m=m; M.n=n; M.t=new double[m*n]; return M; } void detruit(Matrice M) { delete[] M.t; } Matrice produit(Matrice A,Matrice B) { if (A.n!=B.m) { cout << "Erreur!" << endl; exit(1); } Matrice C=cree(A.m,B.n); for (int i=0;i<A.m;i++) for (int j=0;j<B.n;j++) { // Cij=Ai0*B0j+Ai1*B1j+... C.t[i+C.m*j]=0;

3. Coin des enfants : les matrices et les vecteurs vous sont inconnus. Ca nest pas grave. Comprenez le source quand mme et rattrapez vous avec le TP qui, lui, joue avec des images.

103

8.3. Structures et allocation dynamique

8. Allocation dynamique

36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73

for (int k=0;k<A.n;k++) C.t[i+C.m*j]+=A.t[i+A.m*k]*B.t[k+B.m*j]; } return C; } void affiche(string s,Matrice M) { cout << s << " =" << endl; for (int i=0;i<M.m;i++) { for (int j=0;j<M.n;j++) cout << M.t[i+M.m*j] << " "; cout << endl; } } //================================================== // Utilisateur int main() { Matrice A=cree(2,3); for (int i=0;i<2;i++) for (int j=0;j<3;j++) A.t[i+2*j]=i+j; affiche("A",A); Matrice B=cree(3,5); for (int i=0;i<3;i++) for (int j=0;j<5;j++) B.t[i+3*j]=i+j; affiche("B",B); Matrice C=produit(A,B); affiche("C",C); detruit(C); detruit(B); detruit(A); return 0; } Lutilisateur na maintenant plus qu savoir quil faut allouer et librer les matrices en appelant des fonctions mais il na pas savoir ce que font ces fonctions. Dans cette logique, on pourra rajouter des fonctions pour quil nait pas non plus besoin de savoir comment les lments de la matrice sont mmoriss. Il na alors mme plus besoin de savoir que les matrices sont des structures qui ont un champ t ! (Nous nous rapprochons vraiment de la programmation objet...) Bref, on rajoutera en gnral :

10 11 12 13

double get(Matrice M,int i,int j) { return M.t[i+M.m*j]; }

104

8. Allocation dynamique

8.4. Boucles et continue

14 15 16

void set(Matrice M,int i,int j,double x) { M.t[i+M.m*j]=x; } que lutilisateur pourra appeler ainsi :

51 52 53

for (int i=0;i<2;i++) for (int j=0;j<3;j++) set(A,i,j,i+j); et que celui qui programme les matrices pourra aussi utiliser pour lui :

39 40 41 42 43 44 45 46

void affiche(string s,Matrice M) { cout << s << " =" << endl; for (int i=0;i<M.m;i++) { for (int j=0;j<M.n;j++) cout << get(M,i,j) << " "; cout << endl; } } Attention, il reste facile dans ce contexte : Doublier dallouer. Doublier de dsallouer. De ne pas dsallouer ce quil faut si on fait A=B entre deux matrices. (Cest alors deux fois la zone alloue initialement pour B qui est dsalloue lorsquon libre A et B tandis que la mmoire initiale de A ne le sera jamais). La programmation objet essaiera de faire en sorte quon ne puisse plus faire ces erreurs. Elle essaiera aussi de faire en sorte que lutilisateur ne puisse plus savoir ce quil na pas besoin de savoir, de faon rendre vraiment indpendantes la conception des matrices et leur utilisation.

8.4

Boucles et continue

Nous utiliserons dans le TP linstruction continue qui est bien pratique. Voici ce quil fait : lorsquon la rencontre dans une boucle, toute la n de la boucle est saute et on passe au tour suivant. Ainsi : for (...) { ... if (A) continue; ... if (B) continue; ... } est quivalent (et remplace avantageusement au niveau clart et mise en page) : 105

8.5. TP

8. Allocation dynamique

for (...) { ... if (!A) { ... if (!B) { ... } } } Ceci est rapprocher de lutilisation du return en milieu de fonction pour vacuer les cas particuliers (section 7.3).

Figure 8.1 Deux images et dirents traitements de la deuxime (ngatif, ou, relief, dformation, contraste et contours).

8.5

TP

Le TP que nous proposons en A.7 est une illustration de cette faon de manipuler des tableaux bidimensionnels dynamiques travers des structures de donnes. Pour changer de nos passionnantes matrices, nous travaillerons avec des images (gure 8.1).

8.6

Fiche de rfrence

Fiche de rfrence (1/3) Boucles do { ... } while (!ok); int i=1; while (i<=100) { ... i=i+1; } for (int i=1;i<=100;i++) ... for (int i=1,j=100;j>i; i=i+2,j=j-3) ... for (int i=...) for (int j=...) { // saute le cas i==j if (i==j) continue; ... }

106

8. Allocation dynamique

8.6. Fiche de rfrence

Fiche de rfrence (2/3) Variables Dnition : int i; int k,l,m; Aectation : i=2; j=i; k=l=3; Initialisation : int n=5,o=n; Constantes : const int s=12; Porte : int i; // i=j; interdit! int j=2; i=j; // OK! if (j>1) { int k=3; j=k; // OK! } //i=k; interdit! Types : int i=3; double x=12.3; char c=A; string s="hop"; bool t=true; float y=1.2f; unsigned int j=4; signed char d=-128; unsigned char d=254; complex<double> z(2,3); Variables globales : int n; const int m=12; void f() { n=10; // OK int i=m; // OK ... Conversion : int i=int(x); int i,j; double x=double(i)/j; Pile/Tas Clavier Build : F7 Start : Ctrl+F5 Compile : Ctrl+F7 Debug : F5 Stop : Maj+F5 Step over : F10 Step inside : F11 Indent : Ctrl+K,Ctrl+F Add New It. : Ctrl+Maj+A Add Exist. It. : Alt+Maj+A Step out : Maj+F11 Run to curs. : Click droit Compltion : Alt+ Gest. tches : Ctrl+Maj+Ech vect A,vect B) { ... } ... vect C=A+B; Pile des appels Itratif/Rcursif

Fonctions Tableaux Dnition : Dnition : int plus(int a,int b) { double x[10],y[10]; int c=a+b; for (int i=0;i<10;i++) return c; y[i]=2*x[i]; } const int n=5; void affiche(int a) { int i[n],j[2*n]; // OK cout << a << endl; Initialisation : } int t[4]={1,2,3,4}; Dclaration : string s[2]={"ab","cd"}; int plus(int a,int b); Aectation : Retour : int s[4]={1,2,3,4},t[4]; int signe(double x) { for (int i=0;i<4;i++) if (x<0) t[i]=s[i]; return -1; En paramtre : if (x>0) void init(int t[4]) { return 1; for (int i=0;i<4;i++) return 0; t[i]=0; } } void afficher(int x, void init(int t[], int y) { int n) { if (x<0 || y<0) for (int i=0;i<n;i++) return; t[i]=0; if (x>=w || y>=h) } return; Taille variable : DrawPoint(x,y,Red); int* t=new int[n]; } ... Appel : delete[] t; int f(int a) { ... } En paramtre (suite) : int g() { ... } void f(int* t, int n) { ... t[i]=... int i=f(2),j=g(); } Rfrences : void alloue(int*& t) { void swap(int& a,int& b) { t=new int[n]; int tmp=a; } a=b;b=tmp; 2D : } int A[2][3]; ... A[i][j]=...; int x=3,y=2; int A[2][3]= swap(x,y); {{1,2,3},{4,5,6}}; Surcharge : void f(int A[2][2]); int hasard(int n); 2D dans 1D : int hasard(int a,int b); int A[2*3]; double hasard(); A[i+2*j]=...; Oprateurs : Taille variable (suite) : vect operator+( int *t,*s,n;

107

8.6. Fiche de rfrence

8. Allocation dynamique

Fiche de rfrence (3/3) Structures struct Point { double x,y; Color c; }; ... Point a; a.x=2.3; a.y=3.4; a.c=Red; Point b={1,2.5,Blue}; cout << "I=" << i << endl; int f()[4] { // NON! cin >> i >> j; int t[4]; ... Divers return t; // NON! i++; } i--; ... i-=2; int t[4] j+=3; t=f(); j=i%n; // Modulo int s[4]={1,2,3,4},t[4]; #include <cstdlib> t=s; // NON! ... int t[2]; Compilation spare i=rand()%n; t={1,2}; // NON! #include "vect.h", y comx=rand()/double(RAND_MAX); struct Point { pris dans vect.cpp #include <ctime> double x,y; Fonctions : dclarations dans le ... } // NON! .h, dnitions dans le .cpp srand( Point a; Types : dnitions dans le .h unsigned int(time(0))); a={1,2}; // NON! Ne dclarer dans le .h que les #include <cmath> #include "vect.cpp"// NON! fonctions utiles. double sqrt(double x); void f(int t[][]);//NON! #pragma once au dbut du double cos(double x); int t[2,3]; // NON! chier. double sin(double x); t[i,j]=...; // NON! Ne pas trop dcouper... double acos(double x); int* t; #include <string> Tests t[1]=...; // NON! using namespace std; Comparaison : int* t=new int[2]; string s="hop"; == != < > <= >= int* s=new int[2]; char c=s[0]; Ngation : ! s=t; // On perd s! int l=s.size(); Combinaisons : && || delete[] t; #include <ctime> if (i==0) delete[] s; // Dja fait! s=double(clock()) j=1; int *t,s;// s est int /CLOCKS_PER_SEC; if (i==0) // et non int* ! #define _USE_MATH_DEFINES j=1; t=new int[n]; #include <cmath> else s=new int[n];// NON! double pi=M_PI; j=2; CLGraphics if (i==0) { Erreurs frquentes Voir documentation... j=1; Pas de dnition de fonction Conseils k=2; dans une fonction ! Travailler en local } int q=r=4; // NON! CertisLibs Project bool t=(i==0); if (i=2) // NON! Nettoyer en quittant. if (t) if i==2 // NON! Erreurs et warnings : cliquer. j=1; if (i==2) then // NON! Indenter. switch (i) { for (int i=0,i<100,i++) Ne pas laisser de warning. case 1: // NON! Utiliser le debuggeur. ...; int f() {...} Faire des fonctions. ...; ... Tableaux : quand cest utile ! break; int i=f; // NON! (Pas pour transcrire une forcase 2: double x=1/3; // NON! mule mathmatique.) case 3: int i,j; Faire des structures. ...; double x; Faire des chiers spars. break; x=i/j; // NON! Le .h doit sure lutilisadefault: x=double(i/j); //NON! teur (qui ne doit pas regarder ...; double x[10],y[10]; le .cpp) } for (int i=1;i<=10;i++) Ne pas abuser du rcursif. // NON! Entres/Sorties Ne pas oublier delete. y[i]=2*x[i]; #include <iostream> Compiler rgulirement. int n=5; using namespace std; Debug/Release : nettoyer les int t[n]; // NON ... deux.

108

9. Premiers objets

Chapitre 9 Premiers objets


Nous abordons maintenant notre dernire tape dans la direction dune meilleure organisation des programmes. Tantt nous structurions davantage les instructions (fonctions, chiers), tantt nous nous intressions aux donnes (structures, tableaux). Nous allons maintenant penser donnes et instructions simultanment : cest l lide premire des objets, mme sils possdent de nombreux autres aspects 1 . Enn, nous justierons lemploi des objets par la notion d"interface" 2 .

9.1

Philosophie

Runir les instructions en fonctions ou chiers est une bonne chose. Runir les donnes en tableaux ou structures aussi. Il arrive que les deux soient lis. Cest dailleurs ce que nous avons constat naturellement dans les exemples des chapitres prcdents, dans lesquels un chier regroupait souvent une structure et un certain nombre de fonctions sy rapportant. Cest dans ce cas quil faut faire des objets. Lide est simple : un objet est un type de donne possdant un certain nombre de fonctionnalits propres 3 . Ainsi : Ce ne sont plus les fonctions qui travaillent sur des donnes. Ce sont les donnes qui possdent des fonctionnalits. Ces "fonctionnalits" sont souvent appeles les mthodes de lobjet. En pratique, lutilisation dun objet remplacera ce genre dinstructions : obj a; int i=f(a); // fonction f() applique a par : obj a; int i=a.f(); // appel la mthode f() de a
1. Le plus important tant lhritage, que nous ne verrons pas dans ce cours, prfrant nous consacrer dautres aspects du C++ plus indispensables et ngligs jusquici... 2. Nous exposerons une faon simple de crer des interfaces. Un programmeur C++ expriment utilisera plutt de lhritage et des fonctions virtuelles pures, ce qui dpasse largement ce cours ! 3. Il arrive mme parfois quun objet regroupe des fonctionnalits sans pour autant stocker la moindre donne. Nous nutiliserons pas ici cette faon de prsenter les choses, dont le dbutant pourrait rapidement abuser.

9.2. Exemple simple

9. Premiers objets

Vous lavez compris, il sagit ni plus ni moins de "ranger" les fonctions dans les objets. Attention, crions tout de suite haut et fort qu il ne faut pas abuser des objets, surtout lorsquon est dbutant. Les dangers sont en eet : de voir des objets l o il ny en na pas. Instructions et donnes ne sont pas toujours lies. de mal penser lorganisation des donnes ou des instructions en objets. Un conseil donc : quand a devient trop compliqu pour vous, abandonnez les objets. Ce qui ne veut pas dire quun dbutant ne doit pas faire dobjets. Des petits objets dans des cas simples sont toujours une bonne ide. Mais seule lexprience permet de correctement organiser son programme, avec les bons objets, les bonnes fonctions, etc. Un exemple simple : lorsquune fonction travaille sur deux types de donnes, le dbutant voudra souvent sacharner en faire malgr tout une mthode de lun des deux objets, et transformer : obj1 a; obj2 b; int i=f(a,b); // f() applique a et b en : obj1 a; obj2 b; int i=a.f(b); // mthode f() de a applique b // Est-ce bien l chose faire???? Seuls un peu de recul et dexprience permettent de rester simple quand il le faut. Le premier code tait le plus logique : la fonction f() na souvent rien faire chez a, ni chez b.

9.2

Exemple simple

On laura compris dans les exemples prcdents, les mthodes des objets sont considres comme faisant partie du type de lobjet, au mme titre que ses champs. Dailleurs, les champs dun objet sont parfois appels membres de lobjet, et ses mthodes des fonctions membres. Voici ce que cela donne en C++ : struct obj { int x; int f(); int g(int y); }; ... int main() { obj a; a.x=3; int i=a.f(); // champ x // mthode f() // mthode g()

110

9. Premiers objets

9.2. Exemple simple

int j=a.g(2); ... Il y a juste un dtail, mais dimportance : la dnition de la structure obj ci-dessus ne fait que dclarer les mthodes. Elles ne sont dnies nulle part dans le code prcdent. Pour les dnir, on fait comme pour les fonctions habituelles, sauf que pour permettre plusieurs objets davoir les mmes noms de mthodes, on prxe leur dnition par le nom de lobjet suivi de :: a .
a. Ce mcanisme existe aussi pour les fonctions usuelles. Ce sont les espaces de nom, que nous avons rencontrs et contourns immdiatement avec using namespace std pour ne pas avoir crire std::cout ...

Voici comment cela scrit : struct obj1 { int x; // champ x int f(); // mthode f() (dclaration) int g(int y); // mthode g() (dclaration) }; struct obj2 { double x; // champ x double f(); // mthode f() (dclaration) }; ... int obj1::f() { // mthode f() de obj1 (dfinition) ... return ... } int obj1::g(int y) { // mthode g() de obj1 (dfinition) ... return ... } double obj2::f() { // mthode f() de obj2 (dfinition) ... return ... } ... int main() { obj1 a; obj2 b; a.x=3; // le champ x de a est int b.x=3.5; // celui de b est double int i=a.f(); // mthode f() de a (donc obj1::f()) int j=a.g(2); // mthode g() de a (donc obj1::g()) double y=b.f(); // mthode f() de b (donc obj2::f()) ... 111

9.3. Visibilit

9. Premiers objets

9.3

Visibilit

Il y a une rgle que nous navons pas vue sur les espaces de nom mais que nous pouvons facilement comprendre : quand on est "dans" un espace de nom, on peut utiliser toutes les variables et fonctions de cet espace sans prciser lespace en question. Ainsi, ceux qui ont programm cout et endl ont dni lespace std puis se sont "placs lintrieur" de cet espace pour programmer sans avoir mettre std:: partout devant cout, cin, endl et les autres... Cest suivant cette mme logique, que dans ses mthodes, un objet accde directement ses champs et ses autres mthodes, cest--dire sans rien mettre devant a !
a. Vous verrez peut-tre parfois traner le mot cl this qui est utile certains moment en C++ et que les programmeurs venant de Java mettent partout en se trompant dailleurs sur son type. Vous nen naurez en gnral pas besoin.

Par exemple, la fonction obj1::f() ci-dessus pourrait scrire :


1 2 3 4 5 6 7 8 9 10 11 12 13 14

int obj1::f() { // mthode f() de obj1 (dfinition) int i=g(3); // mthode g() de lobjet dont la mthode f() est en train // de sexcuter int j=x+i; // champ x de lobjet dont la mthode f() est en train de //sexcuter return j; } ... int main() { obj1 a1,a2; int i1=a1.f(); // Cet appel va utiliser a1.g() ligne 2 // et a1.x ligne 4 int i2=a2.f(); // Cet appel va utiliser ligne 2 a2.g() // et a2.x ligne 4

Il est dailleurs normal quun objet accde simplement ses champs depuis ses mthodes, car si un objet nutilise pas ses champs dans une mthode, cest probablement quon est en train de ranger dans cet objet une fonction qui na rien voir avec lui (cf abus mentionn plus haut)

9.4

Exemple des matrices

En programmation, un exemple de source vaut mieux quun long discours. Si jusquici vous naviguiez dans le vague, les choses devraient maintenant sclaircir ! Voil donc ce que devient notre exemple du chapitre 8 avec des objets : #include <iostream> #include <string> using namespace std; //================================================== // fonctions sur les matrices 112

9. Premiers objets

9.4. Exemple des matrices

// pourraient etre dans un matrice.h et matrice.cpp // ========= declarations (dans le .h) struct Matrice { int m,n; double* t; void cree(int m1,int n1); void detruit(); double get(int i,int j); void set(int i,int j,double x); void affiche(string s); }; Matrice operator*(Matrice A,Matrice B); // ========= dfinitions (dans le .cpp) void Matrice::cree(int m1,int n1) { // Notez que les parametres ne sappellent plus m et n // pour ne pas mlanger avec les champs! m=m1; n=n1; t=new double[m*n]; } void Matrice::detruit() { delete[] t; } double Matrice::get(int i,int j) { return t[i+m*j]; } void Matrice::set(int i,int j,double x) { t[i+m*j]=x; } void Matrice::affiche(string s) { cout << s << " =" << endl; for (int i=0;i<m;i++) { for (int j=0;j<n;j++) cout << get(i,j) << " "; cout << endl; } } Matrice operator*(Matrice A,Matrice B) { if (A.n!=B.m) { cout << "Erreur!" << endl; exit(1); } 113

9.5. Cas des oprateurs

9. Premiers objets

Matrice C; C.cree(A.m,B.n); for (int i=0;i<A.m;i++) for (int j=0;j<B.n;j++) { // Cij=Ai0*B0j+Ai1*B1j+... C.set(i,j,0); for (int k=0;k<A.n;k++) C.set(i,j, C.get(i,j)+A.get(i,k)*B.get(k,j)); } return C; } // ==================== main =========== int main() { Matrice A; A.cree(2,3); for (int i=0;i<2;i++) for (int j=0;j<3;j++) A.set(i,j,i+j); A.affiche("A"); Matrice B; B.cree(3,5); for (int i=0;i<3;i++) for (int j=0;j<5;j++) B.set(i,j,i+j); B.affiche("B"); Matrice C=A*B; C.affiche("C"); C.detruit(); B.detruit(); A.detruit(); return 0; }

9.5

Cas des oprateurs

Il est un peu dommage que loprateur * ne soit pas dans lobjet Matrice. Pour y remdier, on adopte la convention suivante : Soit A un objet. Sil possde une mthode operatorop (objB B), alors Aop B appellera cette mthode pour tout B de type objB. En clair, le programme : struct objA { ... 114

9. Premiers objets

9.5. Cas des oprateurs

}; struct objB { ... }; int operator+(objA A,objB B) { ... } ... int main() { objA A; objB B; int i=A+B; // appelle operator+(A,B) ... peut aussi scrire : struct objA { ... int operator+(objB B); }; struct objB { ... }; int objA::operator+(objB B) { ... } ... int main() { objA A; objB B; int i=A+B; // appelle maintenant A.operator+(B) ... ce qui pour nos matrices donne : struct Matrice { ... Matrice operator*(Matrice B); }; ... // A*B appelle A.operator*(B) donc tous // les champs et fonctions utiliss directement // concernent ce qui tait prfix prcdemment par A. Matrice Matrice::operator*(Matrice B) { // On est dans lobjet A du A*B appel if (n!=B.m) { // Le n de A cout << "Erreur!" << endl; exit(1); } Matrice C; 115

9.6. Interface

9. Premiers objets

C.cree(m,B.n); for (int i=0;i<m;i++) for (int j=0;j<B.n;j++) { // Cij=Ai0*B0j+Ai1*B1j+... C.set(i,j,0); for (int k=0;k<n;k++) // get(i,j) sera celui de A C.set(i,j, C.get(i,j)+get(i,k)*B.get(k,j)); } return C; } Notez aussi que largument de loprateur na en fait pas besoin dtre un objet. Ainsi pour crire le produit B=A*2, il sura de crer la mthode : Matrice Matrice::operator*(double lambda) { ... } ... B=A*2; // Appelle A.operator*(2) Par contre, pour crire B=2*A, on ne pourra pas crer : Matrice double::operator*(Matrice A) // IMPOSSIBLE // double nest pas un objet! car cela reviendrait dnir une mthode pour le type double, qui nest pas un objet 4 . Il faudra simplement se contenter dun oprateur standard, qui, dailleurs, sera bien inspir dappeler la mthode Matrice::operator*(double lambda) si elle est dj programme : Matrice operator*(double lambda,Matrice A) { return A*lambda; // dfini prcdemment, donc rien reprogrammer! } ... B=2*A; // appelle operator*(2,A) qui appelle son tour // A.operator*(2) Nous verrons au chapitre suivant dautres oprateurs utiles dans le cas des objets...

9.6

Interface

Si on regarde bien le main() de notre exemple de matrice, on saperoit quil nutilise plus les champs des Matrice mais seulement leurs mthodes. En fait, seule la partie struct Matrice { void cree(int m1,int n1); void detruit(); double get(int i,int j);
4. et de toute faon nappartient pas au programmeur !

116

9. Premiers objets

9.7. Protection

void set(int i,int j,double x); void affiche(string s); Matrice operator*(Matrice B); }; intresse lutilisateur. Que les dimensions soient dans des champs int m et int n et que les lments soient dans un champ double* t ne le concerne plus : cest le problme de celui qui programme les matrices. Si ce dernier trouve un autre moyen 5 de stocker un tableau bidimensionnel de double, libre lui de le faire. En fait Si lutilisateur des Matrice se conforme aux dclarations des mthodes ci-dessus, leur concepteur peut les programmer comme il lentend. Il peut mme les reprogrammer ensuite dune autre faon : les programmes de lutilisateur marcheront toujours ! Cest le concept mme dune interface : Le concepteur et lutilisateur des objets se mettent daccord sur les mthodes qui doivent exister. Le concepteur les programme : il implmente a linterface. Lutilisateur les utilise de son ct. Le concepteur peut y retoucher sans gner lutilisateur. En particulier le chier den-tte de lobjet est le seul qui intresse lutilisateur. Cest lui qui prcise linterface, sans rentrer dans les dtails dimplmentation. Bref, relies uniquement par linterface, utilisation et implmentation deviennent indpendantes b .
a. Il se trouve en gnral face au dicile problme du choix de limplmentation : certaines faons de stocker les donnes peuvent rendre ecaces certaines mthodes au dtriment de certaines autres, ou bien consommer plus ou moins de mmoire, etc. Bref, cest lui qui doit grer les problmes dalgorithmique. Cest aussi en gnral ce qui fait que, pour une mme interface, un utilisateur prfrera telle ou telle implmentation : le concepteur devra aussi faire face la concurrence ! b. Ce qui est sur, cest que les deux y gagnent : le concepteur peut amliorer son implmentation sans gner lutilisateur, lutilisateur peut changer pour une implmentation concurrente sans avoir retoucher son programme.

9.7
9.7.1

Protection
Principe

Tout cela est bien beau, mais les dtails dimplmentation ne sont pas entirement cachs : la dnition de la structure dans le chier den-tte fait apparatre les champs utiliss pour limplmentation. Du coup, lutilisateur peut-tre tent des les utiliser ! Rien ne lempche en eet des faire des btises : Matrice A; A.cree(3,2); A.m=4; // Aie! Les accs vont tre faux!
5. Et il en existe ! Par exemple pour stocker ecacement des matrices creuses, cest--dire celles dont la plupart des lments sont nuls. Ou bien, en utilisant des objets implmentant dj des tableaux de faon sure et ecace, comme il en existe dj en C++ standard ou dans des bibliothques complmentaires disponibles sur le WEB. Etc, etc.

117

9.7. Protection

9. Premiers objets

ou tout simplement de prfrer ne pas sembter en remplaant for (int i=0;i<3;i++) for (int j=0;j<2;j++) A.set(i,j,0); par for (int i=0;i<6;i++) A.t[i]=0; // Horreur! Et si on implmente autrement? Dans ce cas, lutilisation nest plus indpendante de limplmentation et on a perdu une grande partie de lintrt de la programmation objet... Cest ici quintervient la possibilit dempcher lutilisateur daccder certains champs ou mme certaines mthodes. Pour cela : 1. Remplacer struct par class : tous les champs et les mthodes deviennent privs : seules les mthodes de lobjet lui-mme ou de tout autre objet du mme type a peuvent les utiliser. 2. Placer la dclaration public: dans la dnition de lobjet pour dbuter la zone b partir de laquelle seront dclars les champs et mthodes publics, cest--dire accessibles tous.
a. Bref, les mthodes de la classe en question ! b. On pourrait nouveau dclarer des passages privs avec private:, puis publics, etc. Il existe aussi des passages protgs, notion qui dpasse ce cours...

Voici un exemple : class obj { int x,y; void a_moi(); public: int z; void pour_tous(); void une_autre(obj A); }; void obj::a_moi() { x=..; // OK ..=y; // OK z=..; // OK } void obj::pour_tous() { x=..; // OK a_moi(); // OK } void obj::une_autre(obj A) { x=A.x; // OK A.a_moi(); // OK } ... int main() { 118

9. Premiers objets

9.7. Protection

obj A,B; A.x=..; A.z=..; A.a_moi(); A.pour_tous(); A.une_autre(B);

// // // // //

NON! OK NON! OK OK

Dans le cas de nos matrices, que nous avions dj bien programmes, il sut de les dnir comme suit : class Matrice { int m,n; double* t; public: void cree(int m1,int n1); void detruit(); double get(int i,int j); void set(int i,int j,double x); void affiche(string s); Matrice operator*(Matrice B); }; pour empcher une utilisation dpendante de limplmentation.

9.7.2

Structures vs Classes

Notez que, nalement, une structure est une classe o tout est public... Les anciens programmeurs C pensent souvent tort que les structures du C++ sont les mmes quen C, cest--dire quelles ne sont pas des objets et quelles nont pas de mthode 6 .

9.7.3

Accesseurs

Les mthodes get() et set() qui permettent daccder en lecture (get) ou en criture (set) notre classe, sont appeles accesseurs. Maintenant que nos champs sont tous privs, lutilisateur na plus la possibilit de retrouver les dimensions dune matrice. On rajoutera donc deux accesseurs en lecture vers ces dimensions : int Matrice::nbLin() { return m; } int Matrice::nbCol() { return n; } ... int main() { ... for (int i=0;i<A.nbLin();i++) for (int j=0;j<A.nbCol();j++)
6. sans compter quils les dclarent souvent comme en C avec dinutiles typedef. Mais bon, ceci ne devrait pas vous concerner !

119

9.8. TP

9. Premiers objets

A.set(i,j,0); ... mais pas en criture, ce qui est cohrent avec le fait que changer m en cours de route rendrait fausses les fonctions utilisant t[i+m*j] !

Figure 9.1 Fractales

9.8

TP

Vous devriez maintenant pouvoir faire le TP en A.8 qui dessine quelques courbes fractales (gure 9.1) en illustrant le concept dobjet..

9.9

Fiche de rfrence

Fiche de rfrence (1/4) Boucles do { ... } while (!ok); int i=1; while (i<=100) { ... i=i+1; } for (int i=1;i<=100;i++) ... for (int i=1,j=100;j>i; i=i+2,j=j-3) ... for (int i=...) for (int j=...) { // saute le cas i==j if (i==j) continue; ... } Clavier Build : F7 Start : Ctrl+F5 Compile : Ctrl+F7 Debug : F5 Stop : Maj+F5 Step over : F10 Step inside : F11 Indent : Ctrl+K,Ctrl+F Add New It. : Ctrl+Maj+A Add Exist. It. : Alt+Maj+A Step out : Maj+F11 Run to curs. : Click droit Compltion : Alt+ Gest. tches : Ctrl+Maj+Ech

120

9. Premiers objets

9.9. Fiche de rfrence

Fiche de rfrence (2/4) Variables Dnition : int i; int k,l,m; Aectation : i=2; j=i; k=l=3; Initialisation : int n=5,o=n; Constantes : const int s=12; Porte : int i; // i=j; interdit! int j=2; i=j; // OK! if (j>1) { int k=3; j=k; // OK! } //i=k; interdit! Types : int i=3; double x=12.3; char c=A; string s="hop"; bool t=true; float y=1.2f; unsigned int j=4; signed char d=-128; unsigned char d=254; complex<double> z(2,3); Variables globales : int n; const int m=12; void f() { n=10; // OK int i=m; // OK ... Conversion : int i=int(x); int i,j; double x=double(i)/j; Pile/Tas Fonctions Dnition : int plus(int a,int b) { int c=a+b; return c; } void affiche(int a) { cout << a << endl; } Dclaration : int plus(int a,int b); Retour : int signe(double x) { if (x<0) return -1; if (x>0) return 1; return 0; } void afficher(int x, int y) { if (x<0 || y<0) return; if (x>=w || y>=h) return; DrawPoint(x,y,Red); } Appel : int f(int a) { ... } int g() { ... } ... int i=f(2),j=g(); Rfrences : void swap(int& a,int& b) { int tmp=a; a=b;b=tmp; } ... int x=3,y=2; swap(x,y); Surcharge : int hasard(int n); int hasard(int a,int b); double hasard(); Oprateurs : vect operator+( vect A,vect B) { ... } ... vect C=A+B; Pile des appels Itratif/Rcursif int i[n],j[2*n]; // OK Initialisation : int t[4]={1,2,3,4}; string s[2]={"ab","cd"}; Aectation : int s[4]={1,2,3,4},t[4]; for (int i=0;i<4;i++) t[i]=s[i]; En paramtre : void init(int t[4]) { for (int i=0;i<4;i++) t[i]=0; } void init(int t[], int n) { for (int i=0;i<n;i++) t[i]=0; } Taille variable : int* t=new int[n]; ... delete[] t; En paramtre (suite) : void f(int* t, int n) { t[i]=... } void alloue(int*& t) { t=new int[n]; } 2D : int A[2][3]; A[i][j]=...; int A[2][3]= {{1,2,3},{4,5,6}}; void f(int A[2][2]); 2D dans 1D : int A[2*3]; A[i+2*j]=...; Taille variable (suite) : int *t,*s,n; Structures struct Point { double x,y; Color c; }; ... Point a; a.x=2.3; a.y=3.4; a.c=Red; Point b={1,2.5,Blue}; Une structure est un objet entirement public ( cf objets !)

Tableaux Dnition : double x[10],y[10]; for (int i=0;i<10;i++) y[i]=2*x[i]; const int n=5;

121

9.9. Fiche de rfrence

9. Premiers objets

Fiche de rfrence (3/4) Objets struct obj { int x; // champ int f(); // mthode int g(int y); }; int obj::f() { int i=g(3); // mon g int j=x+i; // mon x return j; } ... int main() { obj a; a.x=3; int i=a.f(); class obj { int x,y; void a_moi(); public: int z; void pour_tous(); void une_autre(obj A); }; void obj::a_moi() { x=..; // OK ..=y; // OK z=..; // OK } void obj::pour_tous() { x=..; // OK a_moi(); // OK } void une_autre(obj A) { x=A.x; // OK A.a_moi(); // OK } ... int main() { obj A,B; A.x=..; // NON! A.z=..; // OK A.a_moi(); // NON! A.pour_tous(); // OK A.une_autre(B); // OK class obj { obj operator+(obj B); }; ... int main() { obj A,B,C; C=A+B; int q=r=4; // NON! if (i=2) // NON! Compilation spare if i==2 // NON! #include "vect.h", y comif (i==2) then // NON! pris dans vect.cpp for (int i=0,i<100,i++) Fonctions : dclarations dans le // NON! .h, dnitions dans le .cpp int f() {...} Types : dnitions dans le .h ... Ne dclarer dans le .h que les int i=f; // NON! fonctions utiles. double x=1/3; // NON! #pragma once au dbut du int i,j; chier. double x; Ne pas trop dcouper... x=i/j; // NON! Tests x=double(i/j); //NON! Comparaison : double x[10],y[10]; == != < > <= >= for (int i=1;i<=10;i++) Ngation : ! // NON! Combinaisons : && || y[i]=2*x[i]; if (i==0) int n=5; j=1; int t[n]; // NON if (i==0) int f()[4] { // NON! j=1; int t[4]; else ... j=2; return t; // NON! if (i==0) { } j=1; ... k=2; int t[4] } t=f(); bool t=(i==0); int s[4]={1,2,3,4},t[4]; if (t) t=s; // NON! j=1; int t[2]; switch (i) { t={1,2}; // NON! case 1: struct Point { ...; double x,y; ...; } // NON! break; Point a; case 2: a={1,2}; // NON! case 3: #include "vect.cpp"// NON! ...; void f(int t[][]);//NON! break; int t[2,3]; // NON! default: t[i,j]=...; // NON! ...; int* t; } t[1]=...; // NON! int* t=new int[2]; Entres/Sorties int* s=new int[2]; #include <iostream> s=t; // On perd s! using namespace std; delete[] t; ... delete[] s; // Dja fait! cout << "I=" << i << endl; int *t,s;// s est int cin >> i >> j; // et non int* ! Erreurs frquentes t=new int[n]; Pas de dnition de fonction s=new int[n];// NON! dans une fonction ! // C=A.operator+(B)

122

9. Premiers objets

9.9. Fiche de rfrence

Fiche de rfrence (4/4) Divers i++; i--; i-=2; j+=3; j=i%n; // Modulo #include <cstdlib> ... i=rand()%n; x=rand()/double(RAND_MAX); #include <ctime> ... srand( unsigned int(time(0))); #include <cmath> double sqrt(double x); double cos(double x); double sin(double x); double acos(double x); #include <string> using namespace std; string s="hop"; char c=s[0]; int l=s.size(); #include <ctime> s=double(clock()) /CLOCKS_PER_SEC; #define _USE_MATH_DEFINES #include <cmath> double pi=M_PI; CLGraphics Voir documentation... Conseils Travailler en local CertisLibs Project Nettoyer en quittant. Erreurs et warnings : cliquer. Indenter. Ne pas laisser de warning. Utiliser le debuggeur. Faire des fonctions. Tableaux : quand cest utile ! (Pas pour transcrire une formule mathmatique.) Faire des structures. Faire des chiers spars. Le .h doit sure lutilisateur (qui ne doit pas regarder le .cpp) Ne pas abuser du rcursif. Ne pas oublier delete. Compiler rgulirement. Debug/Release : nettoyer les deux. Faire des objets. Ne pas toujours faire des objets ! Penser interface / implmentation / utilisation.

123

10. Constructeurs et Destructeurs

Chapitre 10 Constructeurs et Destructeurs


Dans ce long chapitre, nous allons voir comment le C++ ore la possibilit dintervenir sur ce qui se passe la naissance et la mort dun objet. Ce mcanisme essentiel repose sur la notion de constructeur et de destructeur. Ces notions sont trs utiles, mme pour le dbutant qui devra au moins connatre leur forme la plus simple. Nous poursuivrons par un aspect bien pratique du C++, tant pour lecacit des programmes que pour la dcouverte de bugs la compilation : une autre utilisation du const. Enn, pour les plus avancs, nous expliquerons aussi comment les problmes de gestion du tas peuvent tre ainsi automatiss.

10.1

Le problme

Avec lapparition des objets, nous avons transform : struct point { int x,y; }; ... point a; a.x=2;a.y=3; i=a.x;j=a.y; en : class point { int x,y; public: void get(int&X, int&Y); void set(int X,int Y); }; ... point a; a.set(2,3); a.get(i,j); Consquence :

10.2. La solution

10. Constructeurs et Destructeurs

point a={2,3}; est maintenant impossible. On ne peut remplir les champs privs dun objet, mme linitialisation, car cela permettrait daccder en criture une partie prive 1 !

10.2

La solution

La solution est la notion de constructeur : class point { int x,y; public: point(int X,int Y); }; point::point(int X,int Y) { x=X; y=Y; } ... point a(2,3); Un constructeur est une mthode dont le nom est le nom de la classe elle-mme. Il ne retourne rien mais son type de retour nest pas void : il na pas de type de retour. Le constructeur est appel la cration de lobjet et ses paramtres sont passs avec la syntaxe ci-dessus. Il est impossible dappeler un constructeur sur un objet dj cre a .
a. Ce qui explique quil nest pas besoin de lui prciser un type de retour.

Ici, cest le constructeur point::point(int X,int Y) qui est dni. Notez bien quil est impossible dappeler un constructeur sur un objet dj contruit : point a(1,2); // OK! Valeurs initiales // On ne fait pas comme a pour changer les champs de a. a.point(3,4); // ERREUR! // Mais plutt comme a. a.set(3,4); // OK!

10.3
10.3.1

Cas gnral
Constructeur vide

Lorsquun objet est cre sans rien prciser, cest le construteur vide qui est appel, cest--dire celui sans paramtre. Ainsi, le programme : class obj { public: obj();
1. En ralit, il y a une autre raison, plus profonde et trop dicile expliquer ici, qui fait quen gnral, ds quon programme des objets, cette faon dinitialiser devient impossible.

126

10. Constructeurs et Destructeurs

10.3. Cas gnral

}; obj::obj() { cout << "hello" << endl; } ... obj a; // appelle le constructeur par dfaut ache "hello". Le constructeur vide obj::obj() est appel chaque fois quon construit un objet sans prciser de paramtre. Font exception les paramtres des fonctions et leur valeur de retour qui, eux, sont construits comme des recopies des objets passs en paramtre ou retourns a .
a. Nous allons voir plus loin cette construction par copie.

Ainsi, le programme : #include <iostream> using namespace std; class obj { public: obj(); }; obj::obj() { cout << "obj "; } void f(obj d) { } obj g() { obj e; cout << 6 << " "; return e; } int main() { cout << 0 << " "; obj a; cout << 1 << " "; for (int i=2;i<=4;i++) { obj b; cout << i << " "; } f(a); cout << 5 << " "; a=g(); 127

10.3. Cas gnral

10. Constructeurs et Destructeurs

return 0; } ache : 0 obj 1 obj 2 obj 3 obj 4 5 obj 6 Bien reprer les deux objets non construits avec obj::obj() : le paramtre d de f(), copie de a, et la valeur de retour de g(), copie de e.

10.3.2

Plusieurs constructeurs

Un objet peut avoir plusieurs constructeurs. class point { int x,y; public: point(int X,int Y); point(int V); }; point::point(int X,int Y) { x=X; y=Y; } point::point(int V) { x=y=V; } ... point a(2,3); // construit avec point(X,Y) point b(4); // construit avec point(V) Il faut cependant retenir la chose suivante : Si on ne dnit aucun constructeur, tout se passe comme sil ny avait quun constructeur vide ne faisant rien. Mais attention : ds quon dnit soit mme un constructeur, le constructeur vide nexiste plus, sauf si on le rednit soi-mme. Par exemple, le programme : class point { int x,y; }; ... point a; a.set(2,3); point b;

// OK

devient, avec un constructeur, un programme qui ne se compile plus : 128

10. Constructeurs et Destructeurs

10.3. Cas gnral

class point { int x,y; public: point(int X,int Y); }; point::point(int X,int Y) { x=X; y=Y; } ... point a(2,3); // construit avec point(X,Y) point b; // ERREUR! point() nexiste plus et il faut alors rajouter un constructeur vide, mme sil ne fait rien : class point { int x,y; public: point(); point(int X,int Y); }; point::point() { } point::point(int X,int Y) { x=X; y=Y; } ... point a(2,3); // construit avec point(X,Y) point b; // OK! construit avec point()

10.3.3

Tableaux dobjets

Il nest pas possible de spcier globalement quel constructeur est appel pour les lments dun tableau. Cest toujours le constructeur vide qui est appel... point t[3]; // Construit 3 fois avec le constructeur vide // sur chacun des lments du tableau point* s=new point[n]; // Idem, n fois point* u=new point(1,2)[n]; // ERREUR et HORREUR! // Un bon essai de construire chaque u[i] // avec point(1,2), mais a nexiste pas !-) Il faudra donc crire : point* u=new point[n]; for (int i=0;i<n;i++) u[i].set(1,2); ce qui nest pas vraiment identique car on construit alors les points vide puis on les aecte. Par contre, il est possible dcrire : 129

10.4. Objets temporaires

10. Constructeurs et Destructeurs

point t[3]={point(1,2},point(2,3},point{3,4}}; ce qui nest videmment pas faisable pour un tableau de taille variable.

10.4

Objets temporaires

On peut, en appelant soit mme un constructeur a , construire un objet sans quil soit rang dans une variable. En fait il sagit dun objet temporaire sans nom de variable et qui meurt le plus tt possible.
a. Attention, nous avions dj dit quon ne pouvait pas appeler un constructeur dun objet dj construit. Ici, cest autre chose : on appelle un constructeur sans prciser dobjet !

Ainsi, le programme : void f(point p) ... } point g() { point e(1,2); return e; } ... point a(3,4); f(a); point b; b=g(); point c(5,6); b=c; {

// pour le retourner

// uniquement pour pouvoir appeler f()

// on pourrait avoir envie de faire // a pour mettre b (5,6)

peut largement sallger, en ne stockant pas dans des variables les points pour lesquels ce ntait pas utile :
1 2 3 4 5 6 7 8 9 10 11 12 13

void f(point p) { ... } point g() { return point(1,2); // retourne directement // lobjet temporaire point(1,2) } ... f(point(3,4)); // Passe directement lobj. temp. point(3,4) point b; b=g(); b=point(5,6); // affecte directement b lobjet // temporaire point(5,6)

Attention la ligne 12 : elle est utile quand b existe dj mais bien comprendre quon construit un point(5,6) temporaire qui est ensuite aect b. On ne remplit pas b directement avec (5,6) comme on le ferait avec un b.set(5,6). Attention aussi lerreur suivante, trs frquente. Il ne faut pas crire 130

10. Constructeurs et Destructeurs

10.5. TP

point p=point(1,2); // NON!!!!!!! mais plutt point p(1,2); // OUI!

Lutilit de ces objets temporaires est visible sur un exemple rel : point point::operator+(point b) { point c(x+b.x,y+b.y); return c; } ... point a(1,2),b(2,3); c=a+f(b); scrira plutt : point point::operator+(point b) { return point(x+b.x,y+b.y); } ... c=point(1,2)+f(point(2,3));

Figure 10.1 Jeu de Tron.

10.5

TP

Nous pouvons faire une pause et aller faire le TP que nous proposons en A.9. Il sagit de programmer le jeu de motos de Tron (gure 10.1).

10.6
10.6.1

Rfrences Constantes
Principe

Lorsquon passe un objet en paramtre une fonction, il est recopi. Cette recopie est source dinecacit. Ainsi, dans le programme suivant : 131

10.6. Rfrences Constantes

10. Constructeurs et Destructeurs

const int N=1000; class vecteur { double t[N]; ... }; class matrice { double t[N][N]; ... }; // rsout AX=B void solve(matrice A,vecteur B,vecteur& X) { ... } ... vecteur b,x; matrice a; ... solve(a,b,x); // rsout ax=b les variables A et B de la fonction solve() sont des copies des objets a et b de la fonction appelante. Notez bien que, pass par rfrence, le paramtre X nest pas une copie car il sagit juste dun lien vers la variable x. La recopie de a dans A nest pas une trs bonne chose. La variable a fait dans notre cas pas moins de 8 millions doctets : les recopier dans A prend du temps ! Mme pour des objets un peu moins volumineux, si une fonction est appele souvent, cette recopie peut ralentir le programme. Lorsquune fonction est courte, il nest pas rare non plus que ce temps de recopie soit suprieur celui pass dans la fonction ! Lide est alors, pour des objets volumineux, de les passer eux-aussi par rfrence, mme si la fonction na pas les modier ! Il sut donc de dnir la fonction solve() ainsi : void solve(matrice& A,vecteur& B,vecteur& X) { ... pour acclrer le programme. Cependant, cette solution nest pas sans danger. Rien ne garantit en eet que solve ne modie pas ses paramtres A et B. Il est donc possible, suivant la faon dont solve est programme, quen sortie de solve(a,b,x), a et b eux-mmes aient t modis, alors que prcdemment ctaient leurs copies A et B qui ltaient. Cest videmment gnant ! Le C++ ore heureusement la possibilit de demander au compilateur de vrier quune variable passe par rfrence nest pas modie par la fonction. Il sut de rajouter const au bon endroit : void solve(const matrice& A,const vecteur& B,vecteur& X) { ... Si quelque part dans solve (ou dans les sous-fonctions appeles par solve !), la variable A ou la variable B est modie, alors il y aura erreur de compilation. La rgle est donc : Lorsquun paramtre obj o dune fonction est de taille importante a , cest une bonne ide de le remplacer par const obj& o.
a. En ralit, le programme sen trouvera acclr pour la plupart des objets courants.

132

10. Constructeurs et Destructeurs

10.6. Rfrences Constantes

10.6.2

Mthodes constantes

Considrons le programme suivant : void g(int& x) { cout << x << endl; } void f(const int& y) { double z=y; // OK ne modifie pas y g(y); // OK? } ... int a=1; f(a); La fonction f() ne modie pas son paramtre y et tout va bien. Imaginons une deuxime version de g() : void g(int& x) { x++; } Alors y serait modie dans f() cause de lappel g(). Le programme ne se compilerait videmment pas... En ralit, la premire version de g() serait refuse elle aussi car pour savoir si une sous-fonction modie ou non un des paramtres dune fonction, le compilateur ne se base que sur la dclaration de cette sous-fonction et non sur sa dnition complte a .
a. Le C++ nessaie pas de deviner lui-mme si une fonction modie ses paramtres puisque la logique est que le programmeur indique lui-mme avec const ce quil veut faire, et que le compilateur vrie que le programme est bien cohrent.

Bref, notre premier programme ne se compilerait pas non plus car lappel g(y) avec const int& y impose que g() soit dclare void g(const int& x). Le bon programme est donc : void g(const int& x) { cout << x << endl; } void f(const int& y) { double z=y; // OK ne modifie pas y g(y); // OK! Pas besoin daller regarder dans g() } ... int a=1; f(a); Avec les objets, nous avons besoin dune nouvelle notion. En eet, considrons maintenant : void f(const obj& o) { o.g(); // OK? } 133

10.7. Destructeur

10. Constructeurs et Destructeurs

Il faut indiquer au compilateur si la mthode g() modie ou non lobjet o. Cela se fait avec la syntaxe suivante : class obj { ... void g() const; ... }; void obj::g() const { ... } void f(const obj& o) { o.g(); // OK! Mthode constante } Cela nest nalement pas compliqu : On prcise quune mthode est constante, cest--dire quelle ne modie pas son objet, en plaant const derrire les parenthses de sa dclaration et de sa dnition. On pourrait se demander si toutes ces complications sont bien ncessaires, notre point de dpart tant juste le passage rapide de paramtres en utilisant les rfrences. En ralit, placer des const dans les mthodes est une trs bonne chose. Il ne faut pas le vivre comme une corve de plus, mais comme une faon de prciser sa pense : "suis-je ou non en train dajouter une mthode qui modie lobjets ?". Le compilateur va ensuite vrier pour nous la cohrence de ce const avec tout le reste. Ceci a deux eets importants : Dcouverte de bugs la compilation. (On pensait quun objet ntait pas modi et il lest.) Optimisation du programme 2 . La n du chapitre peut tre considre comme dicile. Il est toutefois recommand de la comprendre, mme si la matrise et la mise en application de ce qui sy trouve est laisse aux plus avancs.

10.7

Destructeur

Lorsquun objet meurt, une autre de ses mthodes est appele : le destructeur. Le destructeur : est appel quand lobjet meurt. porte le nom de la classe prcd de . comme les constructeurs, na pas de type. na pas de paramtres (Il ny a donc quun seul destructeur par classe.)
2. Lorsque le compilateur sait quun objet reste constant pendant une partie du programme, il peut viter daller le relire chaque fois. Le const est donc une information prcieuse pour la partie optimisation du compilateur.

134

10. Constructeurs et Destructeurs

10.7. Destructeur

Un exemple sera plus parlant. Rajoutons un destructeur au programme de la section 10.3 : #include <iostream> using namespace std; class obj { public: obj(); ~obj(); }; obj::obj() { cout << "obj "; } obj::~obj() { cout << "dest "; } void f(obj d) { } obj g() { obj e; cout << 6 << " "; return e; } int main() { cout << 0 << " "; obj a; cout << 1 << " "; for (int i=2;i<=4;i++) { obj b; cout << i << " "; } f(a); cout << 5 << " "; a=g(); return 0; } Il ache maintenant : 0 obj 1 obj 2 dest obj 3 dest obj 4 dest dest 5 obj 6 dest dest dest Reprez bien quel moment les objets sont dtruits. Constatez aussi quil y a des appels au destructeur pour les objets qui sont construits par copie et pour lesquels nous navons pas encore parl du constructeur... 135

10.8. Destructeurs et tableaux

10. Constructeurs et Destructeurs

10.8
10 11 12 13

Destructeurs et tableaux
if (a==b) { obj t[10]; ... }

Le destructeur est appel pour tous les lments du tableau. Ainsi,

appellera 10 fois le constructeur vide en ligne 11 et dix fois le destructeur en ligne 13. Dans le cas dun tableau dynamique, cest au moment du delete[] que les destructeurs sont appels (avant la dsallocation du tableau !).
10 11 12 13 14

if (a==b) { obj* t=new obj[n]; // n appels obj() ... delete[] t; // n appels ~obj(); } Attention : il est possible dcrire delete t sans les []. Cest une erreur ! Cette syntaxe est rserve une autre utilisation du new/delete. Lutiliser ici a pour consquence de bien dsallouer le tas, mais doublier dappeler les destructeurs sur les t[i] .

10.9

Constructeur de copie

Voyons enn ce fameux constructeur. Il na rien de mystrieux. Il sagit dun constructeur prenant en paramtre un autre objet, en gnral en rfrence constante. Le constructeur de copie : Se dclare : obj::obj(const obj& o) ; Est utilis videmment par : obj a ; obj b(a) ; // b partir de a Mais aussi par : obj a ; obj b=a ; // b partir de a, synonyme de b(a) ne pas confondre avec : obj a,b ; b=a ; // ceci nest pas un constructeur ! Et aussi pour construire les paramtres des fonctions et leur valeur de retour. Notre programme exemple est enn complet. En rajoutant : obj::obj(const obj& o) cout << "copy "; } il ache : 136 {

10. Constructeurs et Destructeurs

10.10. Aectation

0 obj 1 obj 2 dest obj 3 dest obj 4 dest copy dest 5 obj 6 copy dest dest dest Nous avons enn autant dappels aux constructeurs quau destructeur ! Il reste malgr tout savoir une chose sur ce constructeur, dont nous comprendrons limportance par la suite : Lorsquil nest pas programm explicitement, le constructeur par copie recopie tous les champs de lobjet copier dans lobjet construit. Remarquez aussi que lorsquon dnit soi-mme un constructeur, le constructeur vide par dfaut nexiste plus mais le constructeur de copie par dfaut existe toujours !

10.10

Aectation

Il reste en fait une dernire chose quil est possible de reprogrammer pour un objet : laectation. Si laectation nest pas reprogramme, alors elle se fait naturellement par recopie des champs. Pour la reprogrammer, on a recours loprateur =. Ainsi a=b, se lit a.operator=(b) si jamais celui-ci existe. Rajoutons donc : void obj::operator=(const obj&o) { cout << "= "; } notre programme, et il ache : 0 obj 1 obj 2 dest obj 3 dest obj 4 dest copy dest 5 obj 6 copy dest = dest dest On rane en gnral un peu. Linstruction a=b=c; entre trois entiers marche pour deux raisons : Elle se lit a=(b=c); Linstruction b=c aecte c b et retourne la valeur de c Pour pouvoir faire la mme chose entre trois objets, on reprogrammera plutt laectation ainsi : obj obj::operator=(const obj&o) { cout << "= "; return o; } ... obj a,b,c; a=b=c; // OK car a=(b=c) ou mme ainsi, ce qui dpasse nos connaissances actuelles, mais que nous prconisons car cela vite de recopier un objet au moment du return : const obj& obj::operator=(const obj&o) { cout << "= "; return o; } ... obj a,b,c; a=b=c; // OK car a=(b=c) 137

10.11. Objets avec allocation dynamique

10. Constructeurs et Destructeurs

Un dernier conseil : Attention ne pas abuser ! Il nest utile de reprogrammer le constructeur par copie et loprateur daectation que lorsquon veut quils fassent autre chose que leur comportement par dfaut a !
a. Contrairement au constructeur vide, qui, lui, nexiste plus ds quon dnit un autre constructeur, et quil est donc en gnral indispensable de reprogrammer, mme pour reproduire son comportement par dfaut

10.11

Objets avec allocation dynamique

Tout ce que nous venons de voir est un peu abstrait. Nous allons enn dcouvrir quoi a sert. Considrons le programme suivant : #include <iostream> using namespace std; class vect { int n; double *t; public: void alloue(int N); void libere(); }; void vect::alloue(int N) { n=N; t=new double[n]; } void vect::libere() { delete[] t; } int main() { vect v; v.alloue(10); ... v.libere(); return 0; }

10.11.1

Construction et destruction

Il apparat videmment que les constructeurs et les destructeurs sont l pour nous aider : #include <iostream> using namespace std; 138

10. Constructeurs et Destructeurs

10.11. Objets avec allocation dynamique

class vect { int n; double *t; public: vect(int N); ~vect(); }; vect::vect(int N) { n=N; t=new double[n]; } vect::~vect() { delete[] t; } int main() { vect v(10); ... return 0; } Grce aux constructeurs et au destructeur, nous pouvons enn laisser les allocations et les dsallocations se faire toutes seules !

10.11.2

Problmes !

Le malheur est que cette faon de faire va nous entraner assez loin pour des dbutants. Nous allons devoir aronter deux types de problmes. Un problme simple Puisquil ny a quun seul destructeur pour plusieurs constructeurs, il va falloir faire attention ce qui se passe dans le destructeur. Rajoutons par exemple un constructeur vide : vect::vect() { } alors la destruction dun objet cr vide va vouloir dsallouer un champ t absurde. Il faudra donc faire, par exemple : vect::vect() { n=0; } vect::~vect() { if (n!=0) delete[] t; } 139

10.11. Objets avec allocation dynamique

10. Constructeurs et Destructeurs

Des problmes compliqus Le programme suivant ne marche pas : int main() { vect v(10),w(10); w=v; return 0; } Pourquoi ? Parce que laectation par dfaut recopie les champs de v dans ceux de w. Du coup, v et w se retrouvent avec les mmes champs t ! Non seulement ils iront utiliser les mmes valeurs, do certainement des rsultats faux, mais en plus une mme zone du tas va tre dsalloue deux fois, tandis quune autre ne le sera pas 3 ! Il faut alors reprogrammer laectation, ce qui nest pas trivial. On dcide en gnral de rallouer la mmoire et de recopier les lments du tableau : const vect& vect::operator=(const vect& v) { if (n==0) delete[] t; // On se desalloue si necessaire n=v.n; if (n!=0) { t=new double[n]; // Reallocation et recopie for (int i=0;i<n;i++) t[i]=v.t[i]; } return v; } Cette version ne marche dailleurs pas si on fait v=v car alors v est dsallou avant dtre recopi dans lui-mme, ce qui provoque une lecture dans une zone qui vient dtre dsalloue 4 .

10.11.3

Solution !

Des problmes identiques se posent pour le constructeur de copie... Ceci dit, en factorisant le travail faire dans quelques petites fonctions prives, la solution nest pas si complique. Nous vous la soumettons en bloc. Elle peut mme servir de schma pour la plupart des objets similaires 5 : #include <iostream> using namespace std; class vect { // champs
3. Ne pas dsallouer provoque videmment des fuites de mmoire. Dsallouer deux fois provoque dans certains cas une erreur. Cest le cas en mode Debug sous Visual, ce qui aide reprer les bugs ! 4. Il sut de rajouter un test (&v==this) pour reprer ce cas, ce qui nous dpasse un petit peu... 5. Ceci nest que le premier pas vers une srie de faon de grer les objets. Doit-on recopier les tableaux ? Les partager en faisant en sorte que le dernier utilisateur soit charger de dsallouer ? Etc, etc.

140

10. Constructeurs et Destructeurs

10.11. Objets avec allocation dynamique

int n; double *t; // fonctions prives void alloc(int N); void kill(); void copy(const vect& v); public: // constructeurs "obligatoires" vect(); vect(const vect& v); // destructeur ~vect(); // affectation const vect& operator=(const vect& v); // constructeurs supplmentaires vect(int N); }; void vect::alloc(int N) { n=N; if (n!=0) t=new double[n]; } void vect::kill() { if (n!=0) delete[] t; } void vect::copy(const vect& v) { alloc(v.n); for (int i=0;i<n;i++) // OK mme si n==0 t[i]=v.t[i]; } vect::vect() { alloc(0); } vect::vect(const vect& v) { copy(v); } vect::~vect() { kill(); } const vect& vect::operator=(const vect& v) { if (this!=&v) { 141

10.12. Fiche de rfrence

10. Constructeurs et Destructeurs

kill(); copy(v); } return v; } vect::vect(int N) { alloc(N); } // Pour tester constructeur de copie vect f(vect a) { return a; } // Pour tester le reste int main() { vect a,b(10),c(12),d; a=b; a=a; a=c; a=d; a=f(a); b=f(b); return 0; }

10.12

Fiche de rfrence

Fiche de rfrence (1/4) Boucles do { ... } while (!ok); int i=1; while (i<=100) { ... i=i+1; } for (int i=1;i<=100;i++) ... for (int i=1,j=100;j>i; i=i+2,j=j-3) ... for (int i=...) for (int j=...) { // saute le cas i==j if (i==j) continue; ... } Clavier Build : F7 Start : Ctrl+F5 Compile : Ctrl+F7 Debug : F5 Stop : Maj+F5 Step over : F10 Step inside : F11 Indent : Ctrl+K,Ctrl+F Add New It. : Ctrl+Maj+A Add Exist. It. : Alt+Maj+A Step out : Maj+F11 Run to curs. : Click droit Compltion : Alt+ Gest. tches : Ctrl+Maj+Ech

Structures struct Point { double x,y; Color c; }; ... Point a; a.x=2.3; a.y=3.4; a.c=Red; Point b={1,2.5,Blue}; Une structure est un objet entirement public ( cf objets !)

142

10. Constructeurs et Destructeurs

10.12. Fiche de rfrence

Fiche de rfrence (2/4) Variables Dnition : int i; int k,l,m; Aectation : i=2; j=i; k=l=3; Initialisation : int n=5,o=n; Constantes : const int s=12; Porte : int i; // i=j; interdit! int j=2; i=j; // OK! if (j>1) { int k=3; j=k; // OK! } //i=k; interdit! Types : int i=3; double x=12.3; char c=A; string s="hop"; bool t=true; float y=1.2f; unsigned int j=4; signed char d=-128; unsigned char d=254; complex<double> z(2,3); Variables globales : int n; const int m=12; void f() { n=10; // OK int i=m; // OK ... Conversion : int i=int(x); int i,j; double x=double(i)/j; Pile/Tas Fonctions Dnition : int plus(int a,int b) { int c=a+b; return c; } void affiche(int a) { cout << a << endl; } Dclaration : int plus(int a,int b); Retour : int signe(double x) { if (x<0) return -1; if (x>0) return 1; return 0; } void afficher(int x, int y) { if (x<0 || y<0) return; if (x>=w || y>=h) return; DrawPoint(x,y,Red); } Appel : int f(int a) { ... } int g() { ... } ... int i=f(2),j=g(); Rfrences : void swap(int& a,int& b) { int tmp=a; a=b;b=tmp; } ... int x=3,y=2; swap(x,y); Surcharge : int hasard(int n); int hasard(int a,int b); double hasard(); Oprateurs : vect operator+( vect A,vect B) { ... } ... vect C=A+B; Pile des appels Itratif/Rcursif Rfrences constantes (pour un passage rapide) : void f(const obj& x){ ... } void g(const obj& x){ f(x); // OK } Tableaux Dnition : double x[10],y[10]; for (int i=0;i<10;i++) y[i]=2*x[i]; const int n=5; int i[n],j[2*n]; // OK Initialisation : int t[4]={1,2,3,4}; string s[2]={"ab","cd"}; Aectation : int s[4]={1,2,3,4},t[4]; for (int i=0;i<4;i++) t[i]=s[i]; En paramtre : void init(int t[4]) { for (int i=0;i<4;i++) t[i]=0; } void init(int t[], int n) { for (int i=0;i<n;i++) t[i]=0; } Taille variable : int* t=new int[n]; ... delete[] t; En paramtre (suite) : void f(int* t, int n) { t[i]=... } void alloue(int*& t) { t=new int[n]; } 2D : int A[2][3]; A[i][j]=...; int A[2][3]= {{1,2,3},{4,5,6}}; void f(int A[2][2]); 2D dans 1D : int A[2*3]; A[i+2*j]=...; Taille variable (suite) : int *t,*s,n;

143

10.12. Fiche de rfrence

10. Constructeurs et Destructeurs

Fiche de rfrence (3/4) Objets struct obj { int x; // champ int f(); // mthode int g(int y); }; int obj::f() { int i=g(3); // mon g int j=x+i; // mon x return j; } ... int main() { obj a; a.x=3; int i=a.f(); class obj { int x,y; void a_moi(); public: int z; void pour_tous(); void une_autre(obj A); }; void obj::a_moi() { x=..; // OK ..=y; // OK z=..; // OK } void obj::pour_tous() { x=..; // OK a_moi(); // OK } void une_autre(obj A) { x=A.x; // OK A.a_moi(); // OK } ... int main() { obj A,B; A.x=..; // NON! A.z=..; // OK A.a_moi(); // NON! A.pour_tous(); // OK A.une_autre(B); // OK class obj { obj operator+(obj B); }; ... int main() { obj A,B,C; C=A+B; // C=A.operator+(B) Mthodes constantes : void obj::f() const{ ... } void g(const obj& x){ x.f(); // OK } Constructeur : class point { int x,y; public: point(int X,int Y); }; point::point(int X,int Y){ x=X; y=Y; } ... point a(2,3); Constructeur vide : obj::obj() { ... } ... obj a; Objets temporaires : point point::operator+( point b) { return point(x+b.x, y+b.y); } ... c=point(1,2) +f(point(2,3)); Destructeur : obj::~obj() { ... } Constructeur de copie : obj::obj(const obj& o) { ... } Utilis par : - obj b(a); - obj b=a; //Diffrent de obj b;b=a; - paramtres des fonctions - valeur de retour Aectation : const obj& obj::operator= (const obj&o) { ... return o; } Objets avec allocation dynamique automatique : cf section 10.11 Compilation spare #include "vect.h", y compris dans vect.cpp Fonctions : dclarations dans le .h, dnitions dans le .cpp Types : dnitions dans le .h Ne dclarer dans le .h que les fonctions utiles. #pragma once au dbut du chier. Ne pas trop dcouper... Tests Comparaison : == != < > <= >= Ngation : ! Combinaisons : && || if (i==0) j=1; if (i==0) j=1; else j=2; if (i==0) { j=1; k=2; } bool t=(i==0); if (t) j=1; switch (i) { case 1: ...; ...; break; case 2: case 3: ...; break; default: ...; } Entres/Sorties #include <iostream> using namespace std; ... cout << "I=" << i << endl; cin >> i >> j;

144

10. Constructeurs et Destructeurs

10.13. Devoir crit

Fiche de rfrence (4/4) Divers i++; i--; i-=2; j+=3; j=i%n; // Modulo #include <cstdlib> ... i=rand()%n; x=rand()/double(RAND_MAX); #include <ctime> ... srand( unsigned int(time(0))); #include <cmath> double sqrt(double x); double cos(double x); double sin(double x); double acos(double x); #include <string> using namespace std; string s="hop"; char c=s[0]; int l=s.size(); #include <ctime> s=double(clock()) /CLOCKS_PER_SEC; #define _USE_MATH_DEFINES #include <cmath> double pi=M_PI; CLGraphics Voir documentation... Conseils Travailler en local CertisLibs Project Nettoyer en quittant. Erreurs et warnings : cliquer. Indenter. Ne pas laisser de warning. Utiliser le debuggeur. Faire des fonctions. Tableaux : quand cest utile ! (Pas pour transcrire une formule mathmatique.) Faire des structures. Faire des chiers spars. Le .h doit sure lutilisateur (qui ne doit pas regarder le .cpp) Ne pas abuser du rcursif. Ne pas oublier delete. Compiler rgulirement. Debug/Release : nettoyer les deux. Faire des objets. Ne pas toujours faire des objets ! Penser interface / implmentation / utilisation. return t; // NON! } ... int t[4] t=f(); int s[4]={1,2,3,4},t[4]; t=s; // NON! int t[2]; t={1,2}; // NON! struct Point { double x,y; } // NON! Point a; a={1,2}; // NON! #include "vect.cpp"// NON! void f(int t[][]);//NON! int t[2,3]; // NON! t[i,j]=...; // NON! int* t; t[1]=...; // NON! int* t=new int[2]; int* s=new int[2]; s=t; // On perd s! delete[] t; delete[] s; // Dja fait! int *t,s;// s est int // et non int* ! t=new int[n]; s=new int[n];// NON! class point { int x,y; public: ... }; ... point a={2,3}; // NON! Oublier de rednir le constructeur vide. point p=point(1,2);// NON! point p(1,2); // OUI! obj* t=new obj[n]; ... delete t; // oubli de []

Erreurs frquentes Pas de dnition de fonction dans une fonction ! int q=r=4; // NON! if (i=2) // NON! if i==2 // NON! if (i==2) then // NON! for (int i=0,i<100,i++) // NON! int f() {...} ... int i=f; // NON! double x=1/3; // NON! int i,j; double x; x=i/j; // NON! x=double(i/j); //NON! double x[10],y[10]; for (int i=1;i<=10;i++) // NON! y[i]=2*x[i]; int n=5; int t[n]; // NON int f()[4] { // NON! int t[4]; ...

10.13

Devoir crit

Vous pouvez maintenant vous confronter aux devoirs crits proposs en annexe, par exemple B.9 et B.8. Vous avez toutes les connaissances ncessaires...

145

11. En vrac...

Chapitre 11 En vrac...
Nous commenons avec ce chapitre un tour de tout ce qui est utile et mme souvent indispensable et que nous navons pas encore vu : chanes de caractres, chiers, etc. Encore une fois, nous ne verrons pas tout de manire exhaustive, mais les fonctions les plus couramment utilises. Vous en connaissez susamment pour raliser de nombreux programmes. Ce qui vous manque en gnral ici, cest la pratique. Aprs avoir aront les exercices tout faits, vous ralisez que, livrs vous-mme, il vous est dicile de vous en sortir. Alors lancezvous ! Tentez de programmer lun des projets proposs sur la page WEB du cours. Vous constaterez rapidement quil vous manque aussi quelques fonctions ou types de variables usuels. Ce chapitre est l pour y remdier...

11.1

Chanes de caratres

Les chanes de caractres sont les variables stockant des suites de caractres, cest-dire du texte. Nous les avons dj rencontres : #include <string> using namespace std; ... string s="hop"; char c=s[0]; int l=s.size(); Compltons : 1. Les chanes peuvent tres compares. Cest lordre alphabtique qui est videmment utilis : if if if if if if (s1==s1) ... (s1!=s2) ... (s1<s2) ... (s1>s2) ... (s1>=s2) ... (s1<=s2) ...

2. On peut chercher un caractre dans un chane :

11.1. Chanes de caratres

11. En vrac...

size_t i=s.find(h); // position de h dans s? size_t j=s.find(h,3); // position de h dans s partir de la // position 3, ie. en ignorant s[0], s[1] et s[2] Attention cest le type size_t 1 qui est utilis et non int. Considrez-le comme un entier mais pour lequel C++ choisit lui-mme sur combien doctets il faut le mmoriser... Si le caractre nest pas trouv, find retourne -1. 3. On peut aussi chercher une sous-chane : size_t i=s.find("hop"); // o est "hop" dans s? size_t j=s.find("hop",3); // o est "hop" dans s partir de la // position 3? 4. Ajouter une chane la n dune autre : string a="comment"; string b="a va, les amis?"; string txt=a+" "+b; 5. Extraire une sous chane : string s1="un deux trois"; string s2=string(s1,3,4); // sous chane de longueur 4 partir // commenant en s1[3] (ici "deux") 6. Attention : la rcupration dune string au clavier coupe la chane si lon appuie sur la touche "Entre" mais aussi au premier espace rencontr. Ainsi, si lon tape "bonjour les amis", le programme : string s; cin >> s; // Jusqu "Entre" ou un espace rcuprera "bonjour" comme valeur de s (et ventuellement "les" puis "amis" si lon programme dautres cin>>t...). Pour rcuprer la ligne complte, espaces compris, il faudra faire un getline(cin,s); // Toute la ligne jusqu "Entre" On pourra ventuellement prciser un autre caractre que la n de ligne : getline(cin,s,:); // Tout jusqu un : (non compris) 7. Convertir une string en une chane au format C : le C mmorise ses chanes dans des tableaux de caractres termins par un 0. Certaines fonctions prennent encore en paramtre un char* ou un const char* 2 . Il faudra alors leur passer s.c_str() pour convertir une variable s de type string (cf section 11.2.2). string s="hop hop"; const char *t=s.c_str(); Vous trouverez dautres fonctions dans laide en ligne de Visual, ou tout simplement proposes par Visual quand vous utiliserez les string.

1. En ralit, il faut utiliser le type string::size_type. 2. Nous navons pas encore vu le rle de const avec les tableaux.

148

11. En vrac...

11.2. Fichiers

11.2
11.2.1

Fichiers
Principe

Pour lire et crire dans un chier, on procde exactement comme avec cout et cin. On cre simplement une variable de type ofstream pour crire dans un chier, ou de type ifstream pour lire... 1. Voic comment faire : #include <fstream> using namespace std; ... ofstream f("hop.txt"); f << 1 << << 2.3 << << "salut" << endl; f.close(); ifstream g("hop.txt"); int i; double x; string s; g >> i >> x >> s; g.close(); 2. Il est bon de vrier que louverture sest bien passe. Une erreur frquente est de prciser un mauvais nom de chier : le chier nest alors pas ouvert : ifstream g("../data/hop.txt"); if (!g.is_open()) { cout << "help!" << endl; return 1; } On peut aussi avoir besoin de savoir si on est arriv au bout du chier : do { ... } while (!(g.eof()); 3. Un chier peut souvrir aprs construction : ofstream f; f.open("hop.txt"); ... 4. Moins frquent, mais trs utile connatre : on peut crire dans un chier directement la suite doctets en mmoire qui correspond une variable ou un tableau. Le chier est alors moins volumineux, lcriture et la lecture plus rapides (pas besoin de traduire un nombre en une suite de caractres ou linverse !) double x[10]; double y; ofstream f("hop.bin",ios::binary); f.write((const char*)x,10*sizeof(double)); f.write((const char*)&y,sizeof(double)); 149

11.2. Fichiers

11. En vrac...

f.close(); ... ifstream g("hop.bin",ios::binary); g.read((char*)x,10*sizeof(double)); g.read((const char*)&y,sizeof(double)); g.close(); Attention ne pas oublier le "mode douverture" ios::binary

11.2.2

Chanes et chiers

1. Pour ouvrir un chier, il faut prciser le nom avec une chane au format C. Do la conversion... void lire(string nom) { ifstream f(nom.c_str()); // Conversion obligatoire... ... } 2. Pour lire une chane avec des espaces, mme chose quavec cin : getline(g,s); getline(g,s,:); 3. Enn, un peu technique mais trs pratique : les stringstream qui sont des chanes simulant des chiers virtuels. On les utilise notamment pour convertir une chane en nombre ou linverse : #include <sstream> using namespace std; string s="12"; stringstream f; int i; // Chane vers entier f << s; // On crit la chane f >> i; // On relit un entier! (i vaut 12) i++; // Entier vers chane f.clear(); // Ne pas oublier si on a dj utilis f f << i; // On crit un entier f >> s; // On relit une chane (s vaut "13")

11.2.3

Objets et chiers

Le grand intrt des << et >> 3 est la possibilit de les rednir pour des objets ! Cest technique, mais il sut de recopier ! Voici comment : struct point { int x,y; };
3. Ils ont lair un peu pnibles utiliser pour le programmeur habitu au printf et scanf du C. On voit ici enn leur puissance !

150

11. En vrac...

11.3. Valeurs par dfaut

ostream& operator<<(ostream& f,const point& p) { f << p.x << << p.y; // ou quoi que ce soit dautre! // (on a dcid ici dcrire les deux // coordonnes spares par un espace...) return f; } istream& operator>>(istream& f,point& p) { f >> p.x >> p.y; // ou quoi que ce soit dautre! return f; } ... point p; cin >> p; cout << p; ofstream f("../hop.txt"); f << p; ... ifstream g("../hop.txt"); g >> p;

11.3
11.3.1

Valeurs par dfaut


Principe

Souvent utile ! On peut donner des valeurs par dfaut aux derniers paramtres dune fonction, valeurs quils prendront sils ne sont pas prciss lappel : void f(int a,int b=0,int c=0) { // ... } void g() { f(12); // Appelle f(12,0,0); f(10,2); // Appelle f(10,2,0); f(1,2,3); // Appelle f(1,2,3); } Sil y a dclaration puis dnition, on ne prcise les valeurs par dfaut que dans la dclaration : void f(int a,int b=0); // dclaration void g() { f(12); // Appelle f(12,0); f(10,2); // Appelle f(10,2); }

151

11.4. Accesseurs

11. En vrac...

void f(int a,int b) { // ne pas re-prciser ici le b par dfaut... // ... }

11.3.2

Utilit

En gnral, on part dune fonction : int f(int a,int b) { ... } Puis, on veut lui rajouter un comportement spcial dans un certain cas : int f(int a,int b,bool special) { ... } Plutt que de transformer tous les anciens appels f(.,.) en f(.,.,false), il sut de faire : int f(int a,int b,bool special=false) { ... } pour laisser les anciens appels inchangs, et uniquement appeler f(.,.,true) dans les futurs cas particuliers qui vont se prsenter.

11.3.3

Erreurs frquentes

Voici les erreurs frquentes lorsquon veut utiliser des valeurs par dfaut : 1. Vouloir en donner aux paramtres au milieu de la liste : void f(int a,int b=3,int c) { // NON! Les derniers paramtres // Pas ceux du milieu! } 2. Engendrer des problmes de surcharge : void f(int a) { ... } void f(int a,int b=0) { // Problme de surcharge! ... // On ne saura pas rsoudre f(1) }

11.4

Accesseurs

Voici, en cinq tapes, les points utiles connatre pour faire des accesseurs pratiques et ecaces. 152

11. En vrac...

11.4. Accesseurs

11.4.1

Rfrence comme type de retour

Voici une erreur souvent rencontre, qui fait hurler ceux qui comprennent ce qui se passe : int i; // Variable globale int f() { return i; } ... f()=3; // Ne veut rien dire (pas plus que 2=3) On ne range pas une valeur dans le retour dune fonction, de mme quon ncrit pas 2=3 ! En fait, si ! Cest possible. Mais uniquement si la fonction retourne une rfrence, donc un "lien" vers une variable : int i; // Variable globale int& f() { return i; } ... f()=3; // OK! Met 3 dans i! Attention : apprendre a un dbutant est trs dangereux. En gnral, il se dpche de commettre lhorreur suivante : int& f() { int i; // Var. locale return i; // rfrence vers une variable qui va mourir! // CEST GRAVE! } ... f()=3; // NON!!! Le i nexiste plus. Que va-til se passer?!

11.4.2

Utilisation

Mme si un objet nest pas une variable globale, un champ de cet objet ne meurt pas en sortant dune de ses mthodes ! On peut, partant du programme : class point { double x[N]; public: void set(int i,double v); }; void point::set(int i,double v) { x[i]=v; } ... point p; p.set(1,2.3); le transformer en : 153

11.4. Accesseurs

11. En vrac...

class point { double x[N]; public: double& element(int i); }; double& point::element(int i) { return x[i]; } ... point p; p.element(1)=2.3;

11.4.3

operator()

Etape suivante : ceci devient encore plus utile quand on connat operator() qui permet de rednir les parenthses : class point { double x[N]; public: double& operator()(int i); }; double& point::operator()(int i) { return x[i]; } ... point p; p(1)=2.3; // Joli, non? Notez que lon peut passer plusieurs paramtres, ce qui est utile par exemple pour les matrices : class mat { double x[M*N]; public: double& operator()(int i,int j); }; double& mat::operator()(int i,int j) { return x[i+M*j]; } ... mat A; A(1,2)=2.3;

11.4.4

Surcharge et mthode constante

Nous sommes maintenant face un problme : le programme prcdent ne permet pas dcrire : void f(mat& A) { 154

11. En vrac...

11.4. Accesseurs

A(1,1)=2; // OK } void f(const mat& A) { double x=A(1,1); // NON! operator() nest pas une mthode constante // Le compilateur ne sait pas que cette ligne ne modifiera pas A! } car la mthode operator() nest pas constante. Il y a heureusement une solution : programmer deux accesseurs, en protant du fait quentre une mthode et une mthode constante, il y a surcharge possible, mme si elles ont les mmes paramtres ! Cela donne : class mat { double x[M*N]; public: // Mme nom, mmes paramtres, mais lune est const! // Donc surcharge possible double& operator()(int i,int j); double operator()(int i,int j)const; }; double mat::operator()(int i,int j) const { return x[i+M*j]; } double& mat::operator()(int i,int j) { return x[i+M*j]; } void f(mat& A) { A(1,1)=2; // OK, appelle le premier operator() } void f(const mat& A) { double x=A(1,1); // OK, appelle le deuxime }

11.4.5
Principe

"inline"

Dernire tape : appeler une fonction et rcuprer sa valeur de retour est un mcanisme complexe, donc long. Appeler A(i,j) au lieu de faire A.x[i+M*j] est une grande perte de temps : on passe plus de temps appeler la fonction A.operator()(i,j) et rcuprer sa valeur de retour, qu excuter la fonction elle-mme ! Cela pourrait nous conduire retourner aux structures en oubliant les classes ! 4 Il existe un moyen de supprimer ce mcanisme dappel en faisant en sorte que le corps de la fonction soit recopi dans le code appelant lui-mme. Pour cela, il faut dclarer la fonction inline. Par exemple : inline double sqr(double x) {
4. Les programmeurs C pourraient aussi tre tents de programmer des "macros" (ie. des raccourcis avec des #define, ce que nous navons pas appris faire). Celles-ci sont moins puissantes que les inline car elles ne vrient pas les types, ne permettent pas daccder aux champs privs, etc. Le programmeur C++ les utilisera avec parcimonie !

155

11.4. Accesseurs

11. En vrac...

return x*x; } ... double y=sqr(z-3); fait exactement comme si on avait crit y=(z-3)(z-3), sans quil ny ait dappel de fonction ! Prcautions Bien comprendre ce qui suit : Une fonction inline est recompile chaque ligne qui lappelle, ce qui ralentit la compilation et augmente la taille du programme ! inline est donc rserv aux fonctions courtes pour lesquelles lappel est pnalisant par rapport au corps de la fonction ! Si la fonction tait dclare dans un .h et dnie dans un .cpp, il faut maintenant la mettre entirement dans le .h car lutilisateur de la fonction a besoin de la dnition pour remplacer lappel de la fonction par son corps ! Pour pouvoir excuter les fonctions pas pas sous debuggeur, les fonctions inline sont compiles comme des fonctions normales en mode Debug. Seul le mode Release protera donc de lacclration. Cas des mthodes Dans le cas dune mthode, il faut bien penser la mettre dans le cher .h si la classe tait dnie en plusieurs chiers. Cest le moment de rvler ce que nous gardions cach : Il est possible de dfinir une mthode entirement dans la dfinition de la classe, au lieu de seulement ly dclarer puis placer sa dnition en dehors de celle de la classe. Cependant, ceci nest pas obligatoire a , ralentit la compilation et va lencontre de lide quil faut masquer le contenu des mthodes lutilisateur dune classe. Cest donc rserv aux petites fonctions, en gnral de type inline.
a. Contrairement ce quil faut faire en Java ! Encore une source de mauvaises habitudes pour le programmeur Java qui se met C++...

Voici ce que cela donne en pratique : class mat { double x[M*N]; public: inline double& operator()(int i,int j) { return x[i+M*j]; } inline double operator()(int i,int j)const { return x[i+M*j]; } }; 156

11. En vrac...

11.5. Assertions

11.5

Assertions

Une fonction trs utile pour faire des programmes moins buggs ! La fonction assert() prvient quand un test est faux. Elle prcise le chier et le numro de ligne o elle se trouve, ore la possibilit de debugger le programme, etc. Elle ne ralentit pas les programmes car elle ne disparat la compilation en mode Release. Cest une fonction peu connue des dbutants, et cest bien dommage ! Voici par exemple comment rendre srs nos accesseurs : #include <cassert> class mat { double x[M*N]; public: inline double& operator()(int i,int j) { assert(i>=0 && i<M && j>=0 && j<N); return x[i+M*j]; } inline double operator()(int i,int j)const { assert(i>=0 && i<M && j>=0 && j<N); return x[i+M*j]; } };

11.6

Types numrs

Cest une bonne ide de passer par des constantes pour rendre un programme plus lisible : const int nord=0,est=1,sud=2,ouest=3; void avance(int direction); mais il est maladroit de faire ainsi ! Il vaut mieux connatre lexistence des types numrs : enum Dir {nord,est,sud,ouest}; void avance(Dir direction); Il sagit bien de dnir un nouveau type, qui, en ralit, masque des entiers. Une prcision : on peut forcer certaines valeurs si besoin. Comme ceci : enum Code {C10=200, C11=231, C12=240, C13, // Vaudra 241 C14}; // " 242 Voil. Cest tout pour aujourdhui ! Nous continuerons au prochain chapitre. Il est donc temps de retrouver notre clbre che de rfrence... 157

11.7. Fiche de rfrence

11. En vrac...

11.7

Fiche de rfrence

Fiche de rfrence (1/4) Boucles do { ... } while (!ok); int i=1; while (i<=100) { ... i=i+1; } for (int i=1;i<=100;i++) ... for (int i=1,j=100;j>i; i=i+2,j=j-3) ... for (int i=...) for (int j=...) { // saute le cas i==j if (i==j) continue; ... } Clavier Build : F7 Start : Ctrl+F5 Compile : Ctrl+F7 Debug : F5 Stop : Maj+F5 Step over : F10 Step inside : F11 Indent : Ctrl+K,Ctrl+F Add New It. : Ctrl+Maj+A Add Exist. It. : Alt+Maj+A Step out : Maj+F11 Run to curs. : Click droit Compltion : Alt+ Gest. tches : Ctrl+Maj+Ech Structures struct Point { double x,y; Color c; }; ... Point a; a.x=2.3; a.y=3.4; a.c=Red; Point b={1,2.5,Blue}; Une structure est un objet entirement public ( cf objets !) Variables Dnition : int i; int k,l,m; Aectation : i=2; j=i; k=l=3; Initialisation : int n=5,o=n; Constantes : const int s=12; Porte : int i; // i=j; interdit! int j=2; i=j; // OK! if (j>1) { int k=3; j=k; // OK! } //i=k; interdit! Types : int i=3; double x=12.3; char c=A; string s="hop"; bool t=true; float y=1.2f; unsigned int j=4; signed char d=-128; unsigned char d=254; complex<double> z(2,3); Variables globales : int n; const int m=12; void f() { n=10; // OK int i=m; // OK ... Conversion : int i=int(x); int i,j; double x=double(i)/j; Pile/Tas Type numr : enum Dir{nord,est, sud,ouest}; void avance(Dir d); Tests Comparaison : == != < > <= >= Ngation : ! Combinaisons : && || if (i==0) j=1; if (i==0) j=1; else j=2; if (i==0) { j=1; k=2; } bool t=(i==0); if (t) j=1; switch (i) { case 1: ...; ...; break; case 2: case 3: ...; break; default: ...; } Conseils Travailler en local CertisLibs Project Nettoyer en quittant. Erreurs et warnings : cliquer. Indenter. Ne pas laisser de warning. Utiliser le debuggeur. Faire des fonctions. Tableaux : quand cest utile ! (Pas pour transcrire une formule mathmatique.) Faire des structures. Faire des chiers spars. Le .h doit sure lutilisateur (qui ne doit pas regarder le .cpp) Ne pas abuser du rcursif. Ne pas oublier delete. Compiler rgulirement. Debug/Release : nettoyer les deux. Faire des objets. Ne pas toujours faire des objets ! Penser interface / implmentation / utilisation.

158

11. En vrac...

11.7. Fiche de rfrence

Fiche de rfrence (2/4) Fonctions Dnition : int plus(int a,int b) { int c=a+b; return c; } void affiche(int a) { cout << a << endl; } Dclaration : int plus(int a,int b); Retour : int signe(double x) { if (x<0) return -1; if (x>0) return 1; return 0; } void afficher(int x, int y) { if (x<0 || y<0) return; if (x>=w || y>=h) return; DrawPoint(x,y,Red); } Appel : int f(int a) { ... } int g() { ... } ... int i=f(2),j=g(); Rfrences : void swap(int& a,int& b) { int tmp=a; a=b;b=tmp; } ... int x=3,y=2; swap(x,y); Surcharge : int hasard(int n); int hasard(int a,int b); double hasard(); Oprateurs : vect operator+( vect A,vect B) { ... } ... vect C=A+B; Pile des appels Itratif/Rcursif Rfrences constantes (pour un passage rapide) : void f(const obj& x){ ... } void g(const obj& x){ f(x); // OK } Valeurs par dfaut : void f(int a,int b=0); void g() { f(12); // f(12,0); f(10,2);// f(10,2); } void f(int a,int b) { // ... } Inline (appel rapide) : inline double sqr( double x) { return x*x; } ... double y=sqr(z-3); Rfrence en retour : int i; // Var. globale int& f() { return i; } ... f()=3; // i=3! Aectation : int s[4]={1,2,3,4},t[4]; for (int i=0;i<4;i++) t[i]=s[i]; En paramtre : void init(int t[4]) { for (int i=0;i<4;i++) t[i]=0; } void init(int t[], int n) { for (int i=0;i<n;i++) t[i]=0; } Taille variable : int* t=new int[n]; ... delete[] t; En paramtre (suite) : void f(int* t, int n) { t[i]=... } void alloue(int*& t) { t=new int[n]; } 2D : int A[2][3]; A[i][j]=...; int A[2][3]= {{1,2,3},{4,5,6}}; void f(int A[2][2]); 2D dans 1D : int A[2*3]; A[i+2*j]=...; Taille variable (suite) : int *t,*s,n; Compilation spare #include "vect.h", y compris dans vect.cpp Fonctions : dclarations dans le .h, dnitions dans le .cpp Types : dnitions dans le .h Ne dclarer dans le .h que les fonctions utiles. #pragma once au dbut du chier. Ne pas trop dcouper...

Tableaux Dnition : double x[10],y[10]; for (int i=0;i<10;i++) y[i]=2*x[i]; const int n=5; int i[n],j[2*n]; // OK Initialisation : int t[4]={1,2,3,4}; string s[2]={"ab","cd"};

159

11.7. Fiche de rfrence

11. En vrac...

Fiche de rfrence (3/4) Objets struct obj { int x; // champ int f(); // mthode int g(int y); }; int obj::f() { int i=g(3); // mon g int j=x+i; // mon x return j; } ... int main() { obj a; a.x=3; int i=a.f(); class obj { int x,y; void a_moi(); public: int z; void pour_tous(); void une_autre(obj A); }; void obj::a_moi() { x=..; // OK ..=y; // OK z=..; // OK } void obj::pour_tous() { x=..; // OK a_moi(); // OK } void une_autre(obj A) { x=A.x; // OK A.a_moi(); // OK } ... int main() { obj A,B; A.x=..; // NON! A.z=..; // OK A.a_moi(); // NON! A.pour_tous(); // OK A.une_autre(B); // OK class obj { obj operator+(obj B); }; ... int main() { obj A,B,C; C=A+B; // C=A.operator+(B) Mthodes constantes : void obj::f() const{ ... } void g(const obj& x){ x.f(); // OK } Constructeur : class point { int x,y; public: point(int X,int Y); }; point::point(int X,int Y){ x=X; y=Y; } ... point a(2,3); Constructeur vide : obj::obj() { ... } ... obj a; Objets temporaires : point point::operator+( point b) { return point(x+b.x, y+b.y); } ... c=point(1,2) +f(point(2,3)); Destructeur : obj::~obj() { ... } Constructeur de copie : obj::obj(const obj& o) { ... } Utilis par : - obj b(a); - obj b=a; //Diffrent de obj b;b=a; - paramtres des fonctions - valeur de retour Aectation : const obj& obj::operator= (const obj&o) { ... return o; } Objets avec allocation dynamique automatique : cf section 10.11 Accesseurs : class mat { double *x; public: inline double& operator() (int i,int j) { assert(i>=0 ...); return x[i+M*j]; } inline double operator() (int i,int j)const { assert(i>=0 ...); return x[i+M*j]; } ... Divers i++; i--; i-=2; j+=3; j=i%n; // Modulo #include <cstdlib> ... i=rand()%n; x=rand()/double(RAND_MAX); #include <ctime> ... srand( unsigned int(time(0))); #include <cmath> double sqrt(double x); double cos(double x); double sin(double x); double acos(double x); #include <string> using namespace std; string s="hop"; char c=s[0]; int l=s.size(); if (s1==s1) ... if (s1!=s2) ... if (s1<s2) ... size_t i=s.find(h); size_t j=s.find(h,3); size_t i=s.find("hop"); size_t j=s.find("hop",3); a="comment"; b="a va?"; txt=a+" "+b; s1="un deux trois"; s2=string(s1,3,4); getline(cin,s); getline(cin,s,:); const char *t=s.c_str(); #include <cassert> ... assert(x!=0); y=1/x; #include <ctime> s=double(clock()) /CLOCKS_PER_SEC; #define _USE_MATH_DEFINES #include <cmath> double pi=M_PI;

160

11. En vrac...

11.7. Fiche de rfrence

Fiche de rfrence (4/4) Entres/Sorties #include <iostream> using namespace std; ... cout << "I=" << i << endl; cin >> i >> j; #include <fstream> using namespace std; ofstream f("hop.txt"); f << 1 << << 2.3; f.close(); ifstream g("hop.txt"); if (!g.is_open()) { return 1; } int i; double x; g >> i >> x; g.close(); do { ... } while (!(g.eof()); ofstream f; f.open("hop.txt"); double x[10],y; ofstream f("hop.bin", ios::binary); f.write((const char*)x, 10*sizeof(double)); f.write((const char*)&y, sizeof(double)); f.close(); ifstream g("hop.bin", ios::binary); g.read((char*)x, 10*sizeof(double)); g.read((const char*)&y, sizeof(double)); g.close(); string nom; ifstream f(nom.c_str()); #include <sstream> using namespace std; stringstream f; // Chane vers entier f << s; f >> i; // Entier vers chane f.clear(); f << i; f >> s; ostream& operator<<( ostream& f, const point&p) { f<<p.x<< << p.y; return f; } istream& operator>>( istream& f,point& p) { f>>p.x>>p.y; return f; } Point a; a={1,2}; // NON! #include "vect.cpp"// NON! void f(int t[][]);//NON! int t[2,3]; // NON! t[i,j]=...; // NON! int* t; t[1]=...; // NON! int* t=new int[2]; int* s=new int[2]; s=t; // On perd s! delete[] t; delete[] s; // Dja fait! Erreurs frquentes int *t,s;// s est int Pas de dnition de fonction // et non int* ! dans une fonction ! t=new int[n]; int q=r=4; // NON! s=new int[n];// NON! if (i=2) // NON! class point { if i==2 // NON! int x,y; if (i==2) then // NON! public: for (int i=0,i<100,i++) ... // NON! }; int f() {...} ... ... point a={2,3}; // NON! int i=f; // NON! Oublier de rednir le double x=1/3; // NON! constructeur vide. int i,j; point p=point(1,2);// NON! double x; point p(1,2); // OUI! x=i/j; // NON! obj* t=new obj[n]; x=double(i/j); //NON! ... double x[10],y[10]; delete t; // oubli de [] for (int i=1;i<=10;i++) //NON! // NON! void f(int a=2,int b); y[i]=2*x[i]; void f(int a,int b=0); int n=5; void f(int a);// NON! int t[n]; // NON Ne pas tout mettre inline ! int f()[4] { // NON! int f() { int t[4]; ... ... } return t; // NON! ... } f()=3; // HORREUR! ... int& f() { int t[4] int i; t=f(); return i; int s[4]={1,2,3,4},t[4]; } t=s; // NON! ... int t[2]; f()=3; // NON! t={1,2}; // NON! struct Point { CLGraphics double x,y; Voir documentation... } // NON!

161

12. En vrac (suite) ...

Chapitre 12 En vrac (suite) ...


Nous continuons dans ce chapitre un inventaire de diverses choses utiles. Parmi elles, les structures de donnes de la STL (Standard Template Library) ncessiteront la comprhension des template. Nous aborderons donc cet aspect intressant du C++ Toujours sous forme dun inventaire, le dbut de ce chapitre sera un peu en vrac, mais nous nous limiterons toujours quelques aspects pratiques du C++ souvent utiliss donc souvent rencontrs dans les programmes des autres ! La n du chapitre est plus utile et plus fondamentale : nous y abordons les template, ou programmation gnrique.

12.1

Oprateur binaires

Parmi les erreurs classiques, il y a videmment celle qui consiste remplacer if (i==0) ... par if (i=0) // NON!!! ... qui range 0 dans i puis considre 0 comme un boolen, cest dire false. Une autre erreur frquente consiste crire if (i==0 & j==2) // NON!!! ... au lieu de if (i==0 && j==2) ... Cette erreur nest possible que parce que & existe. Il sagit de oprateur binaire "ET" sur des entiers. Il est dni ainsi : eectuer a&b revient considrer lcriture de a et de b en binaire puis eectuer un "ET" bit par bit (avec la table 1&1 donne 1 ; 1&0, 0&1 et 0&0 donnent 0). Par exemple : 13&10 vaut 8 car en binaire 1101&1010 vaut 1000. Il existe ainsi toute une panoplie doprateurs binaires :

12.2. Valeur conditionnelle

12. En vrac (suite) ...

symbole & | ^ >>

utilisation a&b a|b a^b a>>n

<< ~

a<<n ~a

rsultat 1&1=1, 0 sinon 0|0=0, 1 sinon 1^0=0^1=1, 0 sinon dcale les bits de a n fois vers la droite et comble gauche avec des 0 (les n premiers de droite sont perdus) dcalage dcale les bits de a n fois vers la gauche gauche et comble droite avec des 0 complment ~1=0, ~0=1

nom et ou ou exclusif dcalage droite

exemple 13&10=8 13|10=15 13^10=7 13>>2=3

5<<2=20 ~13=-14

Remarques : Ces instructions sont particulirement rapides car simples pour le processeur. Les fait que a^b existe est aussi source de bugs (il ne sagit pas le la fonction puissance !) Le rsultat de ~ dpend en fait du type : si par exemple i est un entier non sign sur 8 bits valant 13, alors ~i vaut 242, car ~00001101 vaut 11110010. En pratique, tout cela ne sert pas faire joli ou savant, mais manipuler les nombres bit par bit. Ainsi, il arrive souvent quon utilise un int pour mmoriser un certain nombre de proprits en utilisant le moins possible de mmoire avec la convention que la proprit n est vraie ssi le neme bit de lentier est 1. Un seul entier de 32 bits pourra par ainsi mmoriser 32 proprits l o il aurait fallu utiliser 32 variables de type bool. Voici comment on utilise les oprateurs ci-dessus pour manipuler les bits en question : i|=(1<<n) i&=~(1<<n) i^=(1<<n) if (i&(1<<n)) passe 1 le bit n de i passe 0 le bit n de i inverse le bit n de i vrai ssi le bit n de i est 1

Il existe aussi dautres utilisations frquentes des oprateurs binaires, non pour des raisons de gain de place, mais pour des raisons de rapidit : (1<<n) (i>>1) (i>>n) (i&255) vaut 2n (sinon il faudrait faire int(pow(2.,n)) !) calcule i/2 rapidement calcule i/2n rapidement calcule i%256 rapidement (idem pour toute puissance de 2)

12.2

Valeur conditionnelle

Il arrive quon ait choisir entre deux valeurs en fonction du rsultat dun test. Une construction utile est alors : (test)?val1:val2 qui vaut val1 si test est vrai et val2 sinon. Ainsi if (x>y) maxi=x; else maxi=y; 164

12. En vrac (suite) ...

12.3. Boucles et break

pourra tre remplac par : maxi=(x>y)?x:y; Il ne faut pas abuser de cette construction sous peine de programme illisible !

12.3

Boucles et break

Nous avons dj rencontr la section 8.4 la commande continue qui saute la n dune boucle et passe au tour daprs. Trs utile aussi, la commande break sort de la boucle en ignorant tout ce quil restait y faire. Ainsi le programme : bool arreter=false; for (int i=0;i<N && !arreter;i++) { A; if (B) arreter=true; else { C; if (D) arreter=true; else { E; } } } devient de faon plus lisible et plus naturelle : for (int i=0;i<N;i++) { A; if (B) break; C; if (D) break; E; } Questions rcurrentes des dbutants : 1. break ne sort pas dun if ! if (...) { ...; if (...) break; // NON!!! Ne sort pas du if! (mais ventuellement // dun for qui serait autour...) ... } 2. break ne sort que de la boucle courante, pas des boucles autour : 165

12.4. Variables statiques

12. En vrac (suite) ...

1 2 3 4 5 6 7 8 9 10 11 12

for (int i=0;i<N;i++) { ... for (int j=0;j<M;j++) { ... if (...) break; // termine la boucle en j et passe donc // en ligne 10 (pas en ligne 12) ... } ... } ...

3. break et continue marchent videmment avec while et do ... while de la mme faon quavec for.

12.4

Variables statiques

Il arrive souvent quon utilise une variable globale pour mmoriser de faon permanente une valeur qui nintresse quune seule fonction : // Fonction random qui appelle srand() toute seule // au premier appel... bool first=true; double random() { if (first) { first=false; srand(unsigned int(time(0)); } return double(rand())/RAND_MAX; } Le danger est alors que tout le reste du programme voie cette variable globale et lutilise ou la confonde avec une autre variable globale. Il est possible de cacher cette variable dans la fonction grce au mot cl static plac devant la variable : // Fonction random qui appelle srand() toute seule // au premier appel... avec sa variable globale // masque lintrieur double random() { static bool first=true; // Ne pas oublier static! if (first) { first=false; srand(unsigned int(time(0)); } return double(rand())/RAND_MAX; } Attention : il sagit bien dune variable globale et non dune variable locale. Une variable locale mourrait la sortie de la fonction, ce qui dans lexemple prcdent donnerait un comportement non dsir ! 166

12. En vrac (suite) ...

12.5. const et tableaux

NB : Il est aussi possible de cacher une variable globale dans une classe, toujours grce static. Nous ne verrons pas comment et renvoyons le lecteur la documentation du C++.

12.5

const et tableaux

Nous avons vu malgr nous const char * comme paramtre de certaines fonctions (ouverture de chier par exemple). Il nous faut donc lexpliquer : il ne sagit pas dun pointeur de char qui serait constant mais dun pointeur vers des char qui sont constants ! Il faut donc retenir que : plac devant un tableau, const signie que ce sont les lments du tableau qui ne peuvent tre modis. Cette possibilit de prciser quun tableau ne peut tre modi est dautant plus importante quun tableau est toujours pass en rfrence : sans le const, on ne pourrait assurer cette prservation des valeurs : void f(int t[4]) { ... } void g(const int t[4]) { ... } void h(const int* t,int n) { ... } ... int a[4]; f(a); // modifie peut-tre a[] g(a); // ne modifie pas a[] h(a,4); // ne modifie pas a[] ...

12.6
12.6.1

template
Principe

Considrons la fonction classique pour changer deux variables : void echange(int& a,int& b) { int tmp; tmp=a; a=b; b=tmp; } 167

12.6. template

12. En vrac (suite) ...

... int i,j; ... echange(i,j); Si nous devions maintenant changer deux variables de type double, il faudrait r-crire une autre fonction echange(), identique aux dnitions de type prs. Heureusement, le C++ ore la possibilit de dnir une fonction avec un type gnrique, un peu comme un type variable, que le compilateur devra "instancier" au moment de lappel de la fonction en un type prcis. Cette "programmation gnrique" se fait en dnissant un "template" : // Echange deux variables de nimporte quel type T template <typename T> void echange(T& a,T& b) { T tmp; tmp=a; a=b; b=tmp; } ... int a=2,b=3; double x=2.1,y=2.3; echange(a,b); // "instancie" T en int echange(x,y); // "instancie" T en double ... Autre exemple : // Maximum de deux variables (a condition que operator>() existe // pour le type T) template <typename T> T maxi(T a,T b) { return (a>b)?a:b; } La dclaration typename T prcise le type gnrique. On peut en prciser plusieurs : // Cherche e1 dans le tableau tab1 et met // dans e2 lelement de tab2 de meme indice // Renvoie false si non trouv template <typename T1,typename T2> bool cherche(T1 e1,T2& e2,const T1* tab1,const T2* tab2, int n) { for (int i=0;i<n;i++) if (tab1[i]==e1) { e2=tab2[i]; return true; } return false; } ... string noms[3]={"jean","pierre","paul"}; 168

12. En vrac (suite) ...

12.6. template

int ages[3]={21,25,15}; ... string nm="pierre"; int ag; if (cherche(nm,ag,noms,ages,3)) cout << nm << " a " << ag << " ans" << endl; ...

12.6.2

template et chiers

Il faut bien comprendre que Le compilateur ne fabrique pas une fonction "magique" qui arrive travailler sur plusieurs types ! Il cre en ralit autant de fonctions quil y a dutilisations de la fonction gnrique avec des types dirents (ie. dinstanciations) Pour ces raisons : 1. Faire des fonctions template ralentit la compilation et augmente la taille des programmes. 2. On ne peut plus mettre la dclaration dans un chier den-tte et la dnition dans un chier .cpp, car tous les chiers utilisateurs doivent connatre aussi la dnition. Du coup, la rgle est de tout mettre dans le chier den-tte 1 .

12.6.3

Classes

Il est frquent quune dnition de classe soit encore plus utile si elle est gnrique. Cest possible. Mais attention ! Dans le cas des fonctions, cest le compilateur qui dtermine tout seul quels types sont utiliss. Dans le cas des classes, cest lutilisateur qui doit prciser en permanence avec la syntaxe obj<type> le type utilis : // Paire de deux variables de type T template <typename T> class paire { T x[2]; public: // constructeurs paire(); paire(T A,T B); // accesseurs T operator()(int i) const; T& operator()(int i); }; template <typename T>
1. Ceci est gnant et va lencontre du principe consistant mettre les dclarations dans le .h et masquer les dnitions dans le .cpp. Cette remarque a dj t formule pour les fonctions inline. Le langage prvoit une solution avec le mot cl export, mais les compilateurs actuels nimplmentent pas encore cette fonctionnalit !

169

12.6. template

12. En vrac (suite) ...

paire<T>::paire() { } template <typename T> paire<T>::paire(T A,T B) { x[0]=A; x[1]=B; } template <typename T> T paire<T>::operator()(int i) const { assert(i==0 || i==1); return x[i]; } template <typename T> T& paire<T>::operator()(int i) { assert(i==0 || i==1); return x[i]; } ... paire<int> p(1,2),r; int i=p(1); paire<double> q; q(1)=2.2; ... Dans le cas de la classe trs simple ci-dessus, on aura recours aux fonctions inline vues en 11.4.5 : // Paire de deux variables de type T // Fonctions courtes et rapides en inline template <typename T> class paire { T x[2]; public: // constructeurs inline paire() {} inline paire(T A,T B) { x[0]=A; x[1]=B; } // accesseurs inline T operator()(int i) const { assert(i==0 || i==1); return x[i]; } inline T& operator()(int i) { assert(i==0 || i==1); return x[i]; } }; Lorsque plusieurs types sont gnriques, on les spare par une virgule : 170

12. En vrac (suite) ...

12.6. template

// Paire de deux variables de types diffrents template <typename S,typename T> class paire { public: // Tout en public pour simplifier S x; T y; // constructeurs inline paire() {} inline paire(S X,T Y) { x=X; y=Y; } }; ... paire<int,double> P(1,2.3); paire<string,int> Q; Q.x="pierre"; Q.y=25; ... Enn, on peut aussi rendre gnrique le choix dun entier : // n-uplet de variables de type T // Attention: chaque nuplet<T,N> sera un type diffrent template <typename T, int N> class nuplet { T x[N]; public: // accesseurs inline T operator()(int i) const { assert(i>=0 && i<N); return x[i]; } inline T& operator()(int i) { assert(i>=0 && i<N); return x[i]; } }; ... nuplet<int,4> A; A(1)=3; nuplet<string,2> B; B(1)="pierre"; ... Les fonctions doivent videmment sadapter : template <typename T, int N> T somme(nuplet<T,N> u) { T s=u(0); for (int i=1;i<N;i++) s+=u(i); 171

12.6. template

12. En vrac (suite) ...

return s; } ... nuplet<double,3> C; ... cout << somme(C) << endl; ... Au regard de tout a, on pourrait tre tent de mettre des template partout. Et bien, non ! Les templates sont dlicats programmer, longs compiler, etc. Il ne faut pas en abuser ! Il vaut mieux plutt commencer des classes ou des fonctions sans template. On ne les rajoute que lorsquapparat le besoin de rutiliser lexistant avec des types dirents. Et rptonsle encore une fois : le compilateur cre une nouvelle classe ou une nouvelle fonction chaque nouvelle valeur (instanciation) des types ou des entiers gnriques a .
a. Les nuplets ci-dessus, nont donc rien--voir avec des tableaux de taille variables. Tout se passe comme si on avait programm des tableaux de taille constante pour plusieurs valeurs de la taille.

12.6.4

STL

Les template sont dlicats programmer, mais pas utiliser. Le C++ ore un certain nombre de fonctions et de classes utilisant les template. Cet ensemble est communment dsign sous le nom de STL (Standard Template Library). Vous en trouverez la documentation complte sous Visual ou dfaut sur Internet. Nous exposons ci-dessous quelques exemples qui devraient pouvoir servir de point de dpart et faciliter la comprhension de la documentation. Des fonctions simples comme min et max sont dnies de faon gnrique : int i=max(1,3); double x=min(1.2,3.4); Attention : une erreur classique consiste appeler max(1,2.3) : le compilateur linterprte comme le max dun int et dun double ce qui provoque une erreur ! Il faut taper max(1.,2.3). Les complexes sont eux-aussi gnriques, laissant variable le choix du type de leurs parties relle et imaginaire : #include <complex> using namespace std; ... complex<double> z1(1.1,3.4),z2(1,0),z3; z3=z1+z2; cout << z3 << endl; double a=z3.real(),b=z3.imag(); double m=abs(z3); // module double th=arg(z3); // argument 172

12. En vrac (suite) ...

12.6. template

Les couples sont aussi oerts par la STL : pair<int,string> P(2,"hop"); P.first=3; P.second="hop"; Enn, un certain nombre de structures de donnes sont fournies et sutilisent suivant un mme schma. Voyons lexemple des listes : #include <list> using namespace std; ... list<int> l; l.push_front(2); l.push_front(3); l.push_back(4); l.push_front(5); l.push_front(2);

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

l=[] l=[2] l=[3,2] l=[3,2,4] l=[5,3,2,4] l=[2,5,3,2,4]

Pour dsigner un emplacement dans une liste, on utilise un itrateur. Pour dsigner un emplacement en lecture seulement, on utilise un itrateur constant. Le * sert ensuite accder llment situ lemplacement dsign par litrateur. Seule dicult : le type de ces itrateurs est un peu compliqu taper 2 : list<int>::const_iterator it; it=l.begin(); // Pointe vers le dbut de la liste cout << *it << endl; // affiche 2 it=l.find(3); // Pointe vers lendroit ou se trouve // le premier 3 if (it!=l.end()) cout << "3 est dans la liste" << endl; list<int>::iterator it2; it2=l.find(3); // Pointe vers lendroit ou se trouve // le premier 3 *it=6; // maintenant l=[2,5,6,2,4] Les itrateurs servent galement parcourir les listes (do leur nom !) : // Parcourt et affiche une liste template <typename T> void affiche(list<T> l) { cout << "[ "; for (list<T>::const_iterator it=l.begin();it!=l.end();it++) cout << *it << ; cout << ] << endl; } // Remplace a par b dans une liste template <typename T>
2. Nous navons pas vu comment dnir de nouveaux types cachs dans des classes ! Cest ce qui est fait ici...

173

12.7. Fiche de rfrence

12. En vrac (suite) ...

void remplace(list<T>& l,T a, T b) { for (list<T>::iterator it=l.begin();it!=l.end();it++) if (*it==a) *it=b; } ... affiche(l); remplace(l,2,1); // maintenant l=[1,5,3,1,4] ... Enn, on peut appeler des algorithmes comme le tri de la liste : l.sort(); affiche(l); Sur le mme principe que les listes, vous trouverez dans la STL : Les piles ou stack (Last In First Out). Les les ou queue (First In First Out). Les ensembles ou set (pas deux fois le mme lment). Les vecteurs ou vector (tableaux de taille variable). Les tas ou heap (arbres binaires de recherche). Les tables ou map (table de correspondance cl/valeur). Et quelques autres encore...

12.7

Fiche de rfrence

Fiche de rfrence (1/6) Boucles do { ... } while (!ok); int i=1; while (i<=100) { ... i=i+1; } for (int i=1;i<=100;i++) ... for (int i=1,j=100;j>i; i=i+2,j=j-3) ... for (int i=...) for (int j=...) { // saute le cas i==j if (i==j) continue; ... } for (int i=...) { ... if (t[i]==s){ ... // quitte la boucle break; } ... } Clavier Build : F7 Start : Ctrl+F5 Compile : Ctrl+F7 Debug : F5 Stop : Maj+F5 Step over : F10 Step inside : F11 Indent : Ctrl+K,Ctrl+F Add New It. : Ctrl+Maj+A Add Exist. It. : Alt+Maj+A Step out : Maj+F11 Run to curs. : Click droit Compltion : Alt+ Gest. tches : Ctrl+Maj+Ech Tests Comparaison : == != < > <= >= Ngation : ! Combinaisons : && || if (i==0) j=1; if (i==0) j=1; else j=2; if (i==0) { j=1; k=2; } bool t=(i==0); if (t) j=1; switch (i) { case 1: ...; ...; break; case 2: case 3: ...; break; default: ...; } mx=(x>y)?x:y;

174

12. En vrac (suite) ...

12.7. Fiche de rfrence

Fiche de rfrence (2/6) Fonctions Dnition : int plus(int a,int b) { int c=a+b; return c; } void affiche(int a) { cout << a << endl; } Dclaration : int plus(int a,int b); Retour : int signe(double x) { if (x<0) return -1; if (x>0) return 1; return 0; } void afficher(int x, int y) { if (x<0 || y<0) return; if (x>=w || y>=h) return; DrawPoint(x,y,Red); } Appel : int f(int a) { ... } int g() { ... } ... int i=f(2),j=g(); Rfrences : void swap(int& a,int& b) { int tmp=a; a=b;b=tmp; } ... int x=3,y=2; swap(x,y); Surcharge : int hasard(int n); int hasard(int a,int b); double hasard(); Oprateurs : vect operator+( vect A,vect B) { ... } ... vect C=A+B; Pile des appels Itratif/Rcursif Rfrences constantes (pour un passage rapide) : void f(const obj& x){ ... } void g(const obj& x){ f(x); // OK } Valeurs par dfaut : void f(int a,int b=0); void g() { f(12); // f(12,0); f(10,2);// f(10,2); } void f(int a,int b) { // ... } Inline (appel rapide) : inline double sqr( double x) { return x*x; } ... double y=sqr(z-3); Rfrence en retour : int i; // Var. globale int& f() { return i; } ... f()=3; // i=3! Structures struct Point { double x,y; Color c; }; ... Point a; a.x=2.3; a.y=3.4; a.c=Red; Point b={1,2.5,Blue}; Une structure est un objet entirement public ( cf objets !) Variables Dnition : int i; int k,l,m; Aectation : i=2; j=i; k=l=3; Initialisation : int n=5,o=n; Constantes : const int s=12; Porte : int i; // i=j; interdit! int j=2; i=j; // OK! if (j>1) { int k=3; j=k; // OK! } //i=k; interdit! Types : int i=3; double x=12.3; char c=A; string s="hop"; bool t=true; float y=1.2f; unsigned int j=4; signed char d=-128; unsigned char d=254; complex<double> z(2,3); Variables globales : int n; const int m=12; void f() { n=10; // OK int i=m; // OK ... Conversion : int i=int(x); int i,j; double x=double(i)/j; Pile/Tas Type numr : enum Dir{nord,est, sud,ouest}; void avance(Dir d); Variables statiques : int f() { static bool first=true; if (first) { first=false; ... } ... }

175

12.7. Fiche de rfrence

12. En vrac (suite) ...

Fiche de rfrence (3/6) Objets struct obj { int x; // champ int f(); // mthode int g(int y); }; int obj::f() { int i=g(3); // mon g int j=x+i; // mon x return j; } ... int main() { obj a; a.x=3; int i=a.f(); class obj { int x,y; void a_moi(); public: int z; void pour_tous(); void une_autre(obj A); }; void obj::a_moi() { x=..; // OK ..=y; // OK z=..; // OK } void obj::pour_tous() { x=..; // OK a_moi(); // OK } void une_autre(obj A) { x=A.x; // OK A.a_moi(); // OK } ... int main() { obj A,B; A.x=..; // NON! A.z=..; // OK A.a_moi(); // NON! A.pour_tous(); // OK A.une_autre(B); // OK class obj { obj operator+(obj B); }; ... int main() { obj A,B,C; C=A+B; // C=A.operator+(B) Mthodes constantes : void obj::f() const{ ... } void g(const obj& x){ x.f(); // OK } Constructeur : class point { int x,y; public: point(int X,int Y); }; point::point(int X,int Y){ x=X; y=Y; } ... point a(2,3); Constructeur vide : obj::obj() { ... } ... obj a; Objets temporaires : point point::operator+( point b) { return point(x+b.x, y+b.y); } ... c=point(1,2) +f(point(2,3)); Destructeur : obj::~obj() { ... } Constructeur de copie : obj::obj(const obj& o) { ... } Utilis par : - obj b(a); - obj b=a; //Diffrent de obj b;b=a; - paramtres des fonctions - valeur de retour Aectation : const obj& obj::operator= (const obj&o) { ... return o; } Objets avec allocation dynamique automatique : cf section 10.11 Accesseurs : class mat { double *x; public: inline double& operator() (int i,int j) { assert(i>=0 ...); return x[i+M*j]; } inline double operator() (int i,int j)const { assert(i>=0 ...); return x[i+M*j]; } ... Compilation spare #include "vect.h", y compris dans vect.cpp Fonctions : dclarations dans le .h, dnitions dans le .cpp Types : dnitions dans le .h Ne dclarer dans le .h que les fonctions utiles. #pragma once au dbut du chier. Ne pas trop dcouper... STL min,max,... complex<double> z; pair<int,string> p; p.first=2; p.second="hop"; #include<list> using namespace std; ... list<int> l; l.push_front(1); ... if (l.find(3)!=l.end()) ... list<int>::const_iterator it; for (it=l.begin(); it!=l.end();it++) s+= *it; list<int>::iterator it for (it=l.begin(); it!=l.end();it++) if (*it==2) *it=4; stack, queue, heap, map, set, vector, ...

176

12. En vrac (suite) ...

12.7. Fiche de rfrence

Fiche de rfrence (4/6) Template Fonctions : // A mettre dans LE // fichier qui lutilise // ou dans un .h template <typename T> T maxi(T a,T b) { ... } ... // Le type est trouv // tout seul! maxi(1,2); //int maxi(.2,.3); //double maxi("a","c");//string Objets : template <typename T> class paire { T x[2]; public: paire() {} paire(T a,T b) { x[0]=a;x[1]=b; } T somme()const; }; ... template <typename T> T paire<T>::somme()const{ return x[0]+x[1]; } ... // Le type doit tre // prcis! paire<int> a(1,2); int s=a.somme(); paire<double> b; ... Multiples : template <typename T, typename S> class hop { ... }; ... hop<int,string> A; ... Entiers : #include <sstream> using namespace std; stringstream f; // Chane vers entier f << s; f >> i; // Entier vers chane f.clear(); f << i; Entres/Sorties f >> s; #include <iostream> ostream& operator<<( using namespace std; ostream& f, ... const point&p) { cout << "I=" << i << endl; f<<p.x<< << p.y; cin >> i >> j; return f; #include <fstream> } using namespace std; istream& operator>>( ofstream f("hop.txt"); istream& f,point& p) { f << 1 << << 2.3; f>>p.x>>p.y; f.close(); return f; ifstream g("hop.txt"); } if (!g.is_open()) { return 1; Conseils } Travailler en local int i; CertisLibs Project double x; Nettoyer en quittant. g >> i >> x; Erreurs et warnings : cliquer. g.close(); Indenter. do { Ne pas laisser de warning. ... Utiliser le debuggeur. } while (!(g.eof()); Faire des fonctions. ofstream f; Tableaux : quand cest utile ! f.open("hop.txt"); (Pas pour transcrire une for double x[10],y; mule mathmatique.) ofstream f("hop.bin", Faire des structures. ios::binary); Faire des chiers spars. f.write((const char*)x, Le .h doit sure lutilisa10*sizeof(double)); teur (qui ne doit pas regarder f.write((const char*)&y, le .cpp) sizeof(double)); Ne pas abuser du rcursif. f.close(); Ne pas oublier delete. ifstream g("hop.bin", Compiler rgulirement. ios::binary); Debug/Release : nettoyer les g.read((char*)x, deux. 10*sizeof(double)); Faire des objets. g.read((const char*)&y, Ne pas toujours faire des obsizeof(double)); jets ! g.close(); Penser interface / implmenta string nom; tion / utilisation. ifstream f(nom.c_str()); template <int N> class hop { .. }; ... hop<3> A; hop<5> B; ...

177

12.7. Fiche de rfrence

12. En vrac (suite) ...

Fiche de rfrence (5/6) Divers i++; i--; i-=2; j+=3; j=i%n; // Modulo #include <cstdlib> ... i=rand()%n; x=rand()/double(RAND_MAX); #include <ctime> ... srand( unsigned int(time(0))); #include <cmath> double sqrt(double x); double cos(double x); double sin(double x); double acos(double x); #include <string> using namespace std; string s="hop"; char c=s[0]; int l=s.size(); if (s1==s1) ... if (s1!=s2) ... if (s1<s2) ... size_t i=s.find(h); size_t j=s.find(h,3); size_t i=s.find("hop"); size_t j=s.find("hop",3); a="comment"; b="a va?"; txt=a+" "+b; s1="un deux trois"; s2=string(s1,3,4); getline(cin,s); getline(cin,s,:); const char *t=s.c_str(); #include <cassert> ... assert(x!=0); y=1/x; #include <ctime> s=double(clock()) /CLOCKS_PER_SEC; #define _USE_MATH_DEFINES #include <cmath> double pi=M_PI; Oprateurs binaires and : a&b or : a|b xor : a^b right shift : a>>n left shift : a<<n complement : ~a exemples : set(i,1) : reset(i,1) : test(i,1) : ip(i,1) : i|=(1<<n) i&=~(1<<n) if (i&(1<<n)) i^=(1<<n) // et non int* ! t=new int[n]; s=new int[n];// NON! class point { int x,y; public: ... }; ... point a={2,3}; // NON! Oublier de rednir le constructeur vide. point p=point(1,2);// NON! point p(1,2); // OUI! obj* t=new obj[n]; ... delete t; // oubli de [] //NON! void f(int a=2,int b); void f(int a,int b=0); void f(int a);// NON! Ne pas tout mettre inline ! int f() { ... } ... f()=3; // HORREUR! int& f() { int i; return i; } ... f()=3; // NON! if (i>0 & i<n) ... // NON! if (i<0 | i>n) ... // NON! if (...) { ... if (...) break; // NON! Pour les // boucles seulement! } for (i ...) for (j ...) { ... if (...) break;//NON! Ne quitte // Que la boucle en j! int i; double x; ... j=max(i,0);//OK y=max(x,0);//NON! Utiliser // 0.0 et non 0 (max est // un template (STL)...)

Erreurs frquentes Pas de dnition de fonction dans une fonction ! int q=r=4; // NON! if (i=2) // NON! if i==2 // NON! if (i==2) then // NON! for (int i=0,i<100,i++) // NON! int f() {...} ... int i=f; // NON! double x=1/3; // NON! int i,j; double x; x=i/j; // NON! x=double(i/j); //NON! double x[10],y[10]; for (int i=1;i<=10;i++) // NON! y[i]=2*x[i]; int n=5; int t[n]; // NON int f()[4] { // NON! int t[4]; ... return t; // NON! } ... int t[4] t=f(); int s[4]={1,2,3,4},t[4]; t=s; // NON! int t[2]; t={1,2}; // NON! struct Point { double x,y; } // NON! Point a; a={1,2}; // NON! #include "vect.cpp"// NON! void f(int t[][]);//NON! int t[2,3]; // NON! t[i,j]=...; // NON! int* t; t[1]=...; // NON! int* t=new int[2]; int* s=new int[2]; s=t; // On perd s! delete[] t; delete[] s; // Dja fait! int *t,s;// s est int

CLGraphics Voir documentation...

178

12. En vrac (suite) ...

12.8. Devoir nal

Fiche de rfrence (6/6) Tableaux Dnition : double x[10],y[10]; for (int i=0;i<10;i++) y[i]=2*x[i]; const int n=5; int i[n],j[2*n]; // OK Initialisation : int t[4]={1,2,3,4}; string s[2]={"ab","cd"}; Aectation : int s[4]={1,2,3,4},t[4]; for (int i=0;i<4;i++) t[i]=s[i]; En paramtre : void init(int t[4]) { for (int i=0;i<4;i++) t[i]=0; } void init(int t[], int n) { for (int i=0;i<n;i++) t[i]=0; } Taille variable : int* t=new int[n]; ... delete[] t; En paramtre (suite) : void f(int* t, int n) { t[i]=... } void alloue(int*& t) { t=new int[n]; } 2D : int A[2][3]; A[i][j]=...; int A[2][3]= {{1,2,3},{4,5,6}}; void f(int A[2][2]); 2D dans 1D : int A[2*3]; A[i+2*j]=...; Taille variable (suite) : int *t,*s,n; En paramtre (n) : void f(const int* t, int n) { ... s+=t[i]; // OK ... t[i]=...; // NON! }

12.8

Devoir nal

Vous pouvez enn vous confronter aux devoirs complets proposs en annexe, par exemple B.16 et B.15.

179

A. Travaux Pratiques

Annexe A Travaux Pratiques


Note : les corrigs seront disponibles sur la page WEB du cours aprs chaque TP.

A.1
A.1.1

Lenvironnement de programmation
Bonjour, Monde !

1. Connexion : Se connecter sous Windows. 2. Rpertoires : Identier (sous "Poste de travail") les deux disques locaux (C: et D:) et les disques rseau (H: et Z:). On travaillera en local sur le bureau qui se trouve en ralit dans D:\Documents and Settings\login\Bureau. Tout essai de travail directement dans H : ou Z : est cause de lenteur, de saturation du rseau et peut entraner une perte de chier si vos quotas disque sont atteints. On vitera par contre de saturer le bureau en dplaant ses programmes dans Z: en n de sance. Le principe est donc le suivant : (a) Connexion. (b) Copie ventuelle dun travail prcdent de Z: sur le bureau. (c) Travail sur le bureau. (d) Recopie du travail vers Z: (PS : Z: est sauvegard par les responsables de la DIT, pas H:) (e) Dconnexion. 3. Projet : Crer un projet Tp1 sur le bureau. Visual C++ voudra travailler dans "Mes Documents". Pour ne pas surcharger les transferts, on travaillera plutt par aller-retour entre Z : et le bureau. (a) Lancer "Microsoft Visual C++ Express 2005" (b) Menu "File/New/Project" (c) "Name: Tp1", "Location: Desktop".

A.1. Lenvironnement de programmation

A. Travaux Pratiques

(d) "Type: CertisLibs Project", 4. Vrier avec lexplorateur quun rpertoire Tp1\Tp1 a bien t cre sur le bureau. 5. Conguration : Faire apparatre dans Visual : (a) La "toolbar" Build (b) Licne de lancement correspondant la commande Start Without Debugging 6. Programmation : (a) Rajouter cout << "Hello" << endl; sur la ligne avant return 0; 7. Gnration : (a) Dans la fentre "Solution explorer" de Visual Studio, rechercher et acher le chier Tp1.cpp. (b) "Build/Build solution", ou "F7" ou bouton correspondant. (c) Vrier lexistence dun chier Tp1 (en ralit Tp1.exe) dans Tp1\Tp1\Debug. Touche utile : F7 = 8. Excution : (a) Lancer le programme avec "Debug/Start Without Debugging" (ou "Ctrl+F5" ou bouton correspondant). Une fentre "console" souvre, le programme sexcute dedans, et la fentre console attend lappui sur une touche pour se refermer. = Build

(b) Vrier quon a en fait cr un programme indpendant quon peut lancer dans une fentre de commande : Essayer de le lancer depuis lexplorateur Windows : le programme se referme tout de suite ! Dans les menus Windows : "Dmarrer/Excuter" "Ouvrir: cmd" Taper "D:", puis "cd \Documents and Settings\nom_du_binome\Bureau\Tp1\Tp1\Deb (complter les noms avec la touche TAB). Vrier la prsence de Tp1.exe avec la commande "dir". Taper "Tp1". Touche utile : Ctrl+F5 = = Start without debugging

9. Fichiers : On a dj suivi la cration des chiers principaux au fur et mesure. Constater la prsence de Tp1.obj qui est la compilation de Tp1.cpp (que le linker a ensuite utilis pour crer Tp1.exe). Voir aussi la prsence de nombreux chiers de travail de Visual Studio. Quelle est la taille du rpertoire Tp1 (click droit + proprits) ? 10. Nettoyage : Supprimer les chiers de travail et les rsultats de la gnration avec "Build / Clean solution" puis fermer Visual Studio. Quelle est la nouvelle taille du rpertoire ? 182

A. Travaux Pratiques

A.1. Lenvironnement de programmation

11. Compression : Sous Windows, en cliquant droite sur le rpertoire Tp1, fabriquer une archive comprime Tp1.zip (ou Tp1.7z suivant la machine). Attention il faut quitter Visual Studio avant de comprimer. Il peut sinon y avoir une erreur ou certains chiers trop importants peuvent subsister. La politique de quotas fait quil est indispensable de nettoyer ses solutions quand on quitte. Cest essentiel aussi pour pouvoir envoyer ses exercices son responsable. De plus, il faut quitter Visual Studio avant de comprimer. 12. Envoi : Envoyer le chier par mail son responsable et indiquant bien le nom du binme. 13. Sauvegarde : Recopier aussi larchive comprime sur son compte (disque Z :)

A.1.2

Premires erreurs

1. Modier le programme : Modier le programme en changeant par exemple la phrase ache. (a) Tester une nouvelle gnration/excution. Vrier que Visual Studio sauve le chier automatiquement avant de gnrer. (b) Modier nouveau le programme. Tester directement une excution. Visual Studio demande automatiquement une gnration ! Lancer directement une excution sauve et gnre automatiquement. Attention toutefois de ne pas conrmer lexcution si la gnration sest mal passe. (c) Faire une nouvelle modication. Tester une simple compilation (Build/Compile ou Ctrl+F7). Cette tape cre juste Tp1.obj partir de Tp1.cpp sans aller jusqu Tp1.exe. Vrier dans la fentre de commande avec dir que le chier Tp1.exe est rest antrieur Tp1.cpp et Tp1.obj. Vrier quune gnration ne lance plus ensuite que ldition des liens sans avoir besoin de recompiler. Touche utile : Ctrl+F7 = linking) = Compile current le (without

2. Erreurs de compilation Provoquer, constater et apprendre reconnatre quelques erreurs de compilation : (a) includ au lieu de include (b) iostrem au lieu de iostream (c) Oublier le ; aprs std (d) inte au lieu de int (e) cou au lieu de cout (f) Oublier le " fermant la chane "Hello ..." (g) Rajouter une ligne i=3; avant le return. 183

A.1. Lenvironnement de programmation

A. Travaux Pratiques

A ce propos, il est utile de dcouvrir que : Double-cliquer sur un message derreur positionne lditeur sur lerreur. 3. Erreur de linker Il est un peu tt pour russir mettre le linker en erreur. Il est pourtant indispensable de savoir direncier ses messages de ceux du compilateur. En gnral, le linker sera en erreur sil ne trouve pas une fonction ou des variables parce quil manque un chier objet ou une bibliothque. Cest aussi une erreur sil trouve deux fois la mme fonction... (a) Rajouter une ligne f(2); avant le return et faire Ctrl+F7. Cest pour linstant une erreur de compilation. (b) Corriger lerreur de compilation en rajoutant une ligne (pour linstant "magique") void f(int i); avant la ligne avec main. Compiler sans linker : il ny a plus derreur. Gnrer le programme : le linker constate labsence dune fonction f() utilise par la fonction main() mais quil ne trouve nulle part. (c) Revenir un programme qui marche et taper mai au lieu de main. En dduire le nom de la fonction qui appelle main() sans quon nen soit conscient ! 4. Indentations : Avec toutes ces modications, le programme ne doit plus tre correctement "indent". Cest pourtant essentiel pour une bonne comprhension et reprer dventuelle erreur de parenthses, accolades, etc. Le menu Edit/Advanced fournit de quoi bien indenter. Pour reprer des erreurs, toujours bien indenter. Touche utile : Ctrl+K,Ctrl+F = indenter la zone slectionne. Touche utile : Ctrl+A,Ctrl+K,Ctrl+F = tout indenter. 5. Warnings du compilateur En modiant le main(), provoquer les warnings suivants : (a) int i; i=2.5; cout << i << endl; Excuter pour voir le rsultat. (b) int i; i=4; if (i=3) cout << "salut" << endl; Excuter ! (c) int i,j; j=i; Excuter (rpondre "abandonner" !) (d) Provoquer le warning inverse : variable dclare mais non utilise. (e) Ajouter exit; comme premire instruction de main. Appeler une fonction en oubliant les arguments arrive souvent ! Excuter pour voir. Corriger en mettant exit(0);. Il y a maintenant un autre warning. Pourquoi ? (La fonction exit() quitte le programme en urgence !) 184

A. Travaux Pratiques

A.1. Lenvironnement de programmation

Il est trs formellement dconseill de laisser passer des warnings ! Il faut les corriger au fur et mesure. Une option du compilateur propose mme de les considrer comme des erreurs !

A.1.3

Debugger

Savoir utiliser le debuggeur est essentiel. Il doit sagir du premier rexe en prsence dun programme incorrect. Cest un vritable moyen dinvestigation, plus simple et plus puissant que de truer son programme dinstructions supplmentaires destines espionner son droulement. 1. Taper le main suivant. int main() { int i,j,k; i=2; j=3*i; if (j==5) k=3; else k=2; return 0; } 2. Lancer le programme "sous le debuggeur" avec Build/Start ou F5 ou le bouton correspondant. Que se passe-til ? 3. Placer un "point darrt" en cliquant dans la colonne de gauche la hauteur de la ligne i=2; puis relancer le debuggeur. 4. Avancer en "Step over" avec F10 ou le bouton correspondant et suivre les valeurs des variables (dans la fentre spciale ou en plaant (sans cliquer !) la souris sur la variable). 5. A tout moment, on peut interrompre lexcution avec Stop ou Maj+F5. Arrter lexcution avant datteindre la n de main sous peine de partir dans la fonction qui a appel main ! 6. Placer un deuxime point darrt et voir que F5 redmarre le programme jusquau prochain point darrt rencontr. 7. Ajouter i=max(j,k); avant le return. Utiliser "Step into" ou F11 ou le bouton correspondant quand le curseur est sur cette ligne. Constater la dirence avec F10. 8. Enn, pour voir quoi ressemble du code machine, excuter jusqu un point darrt puis faire Debug/Windows/Disassembly. On peut aussi dans ce mme menu voir les registres du micro-processeur. Il arrive quon se retrouve dans la fentre "code machine" sans lavoir demand quand on debugge un programme pour lequel on na plus le chier source. Cet achage est en fait trs utile pour vrier ce que fait le compilateur et voir sil optimise bien. 185

A.1. Lenvironnement de programmation

A. Travaux Pratiques

F5 Touches utiles : Maj+F5 F10 F11

= = = =

= = = =

Debug Stop Step over Step inside

A.1.4

Deuxime programme

1. Ajouter un nouveau projet la solution. Trouvez bien comment ne pas crer de nouvelle solution !. 2. Que fait Build ? Et Start Without Debugging ? Pourquoi ? Utiliser Project/Set as startup project pour excuter le nouveau projet. Lexcuter. Le lancer aussi depuis une fentre console. Et depuis lexplorateur Windows. Question nale : donnez nouveau votre programme votre responsable (nettoyer, sortir de Visual, comprimer, envoyer par mail)

A.1.5

Sil reste du temps

Tlcharger le programme supplmentaire Tp1Supplement.zip sur la page du cours (http://certis.enpc.fr/~keriven/Info), jouer avec... et le complter !

A.1.6

Installer Visual Studio chez soi

Allez voir sur http://certis.enpc.fr/~keriven/CertisLibs.

186

A. Travaux Pratiques

A.2. Variables, boucles, conditions, fonctions

A.2
A.2.1

Variables, boucles, conditions, fonctions


Premier programme avec fonctions

1. Rcuprer le programme exemple : Tlcharger larchive Tp2.zip sur la page du cours, la dcompresser sur le bureau et ouvrir la solution dans Visual. Etudier le projet Hop dont voici les sources :
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

#include <iostream> using namespace std; int plus(int a,int b) { int c; c=a+b; return c; } void triple1(int a) { a=a*3; } void triple2(int& a) { a=a*3; } int main() { int i,j=2,k; i=3; k=plus(i,j); triple1(i); triple2(i); return 0; }

2. Debugger : Excuter le programme pas pas et tudier la faon dont les variables changent.

A.2.2

Premier programme graphique avec la CLGraphics

Dans ce TP et les suivants, nous utiliserons la librairie graphique CLGraphics (cf annexe du polycopi). La CLGraphics permet de grer trs simplement le fentrage, le dessin, et les entres-sorties clavier et souris. 1. Programme de dpart : Etudier le programme du projet Tennis dont voici le source :
1 2

#include <CL/Graphics/Graphics.h> 187

A.2. Variables, boucles, conditions, fonctions

A. Travaux Pratiques

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

using namespace CL::Graphics; ... ///////////////////////////////////////////////////////// // Fonction principale int main() { // Ouverture de la fenetre OpenWindow(256,256); // Position et vitesse de la balle int xb=128, yb=20, ub=2, vb=3; // Boucle principale while (true) { // Affichage de la balle FillRect(xb-3,yb-3,7,7,Red); // Temporisation MilliSleep(20); // Effacement de la balle FillRect(xb-3,yb-3,7,7,White); // Rebond if (xb+ub>253) ub=-ub; // Mise a jour de la position de la balle xb+=ub; yb+=vb; } Terminate(); return 0; } Ne pas sintresser la fonction Clavier() Gnrer puis excuter la solution. Que se passe-t-il ?

2. Aide de Visual Studio A tout moment, la touche F1 permet daccder la documentation du mot cl situ sous le curseur. Tester cette fonctionnalit sur les mots-cls if, while et return. Touche utile : F1 = Accder la documentation 3. Comprendre le fonctionnement du programme : Identier la boucle principale du programme. Elle se dcompose ainsi : (a) Achage de la balle (b) Temporisation de quelques millisecondes pour que la balle ne se dplace pas trop vite (c) Eacement de la balle (d) Gestion des rebonds 188

A. Travaux Pratiques

A.2. Variables, boucles, conditions, fonctions

(e) Mise jour des coordonnes de la balle Pourquoi la ligne comportant linstruction while suscite-t-elle un warning ? A quoi sert la condition formule par linstruction if ? 4. Gestion de tous les rebonds : Complter le programme an de grer tous les rebonds. Par exemple, il faut inverser la vitesse horizontale ub quand la balle va toucher les bords gauche ou droit de la fentre. 5. Variables globales : Doubler la hauteur de la fentre. Modier la taille de la balle. Cela ncessite de modier le code plusieurs endroits. Aussi, la place de valeurs numriques en dur, il vaut mieux dnir des variables. An de simplier et bien que a ne soit pas toujours conseill, utiliser des variables globales constantes. Pour cela, insrer tout de suite aprs les deux lignes dinclude le code suivant const int width = 256; // Largeur de la fenetre const int height = 256; // Hauteur de la fenetre const int ball_size = 3; // Rayon de la balle et reformuler les valeurs numriques du programmes laide de ces variables. Le mot cl const indique que ces variables ne peuvent tre modies aprs leur initialisation. Essayer de rajouter la ligne width=300; au dbut de la fonction main et constater que cela provoque une erreur de compilation. 6. Utilisation de fonctions : La balle est dessine deux fois au cours de la boucle, la premire fois en rouge et la deuxime fois en blanc pour leacer. Ici le dessin de la balle ne ncessite quune ligne mais cela pourrait tre beaucoup plus si sa forme tait plus complexe. Aussi, pour que le programme soit mieux structur et plus lisible, et que le code comporte le moins de duplications possible, regrouper lachage de la balle et son eacement dans une fonction DessineBalle dnie avant la fonction main : void DessineBalle(int x,int y,Color col) { ... } De mme, dnir une fonction void BougeBalle(int &x,int &y,int &u,int &v) pour grer les rebonds et le dplacement de la balle.

A.2.3

Jeu de Tennis

Nous allons rendre ce programme plus ludique en y ajoutant deux raquettes se dplaant horizontalement en haut et en bas de lcran, et commandes par les touches du clavier. 1. Achage des raquettes : Ajouter dans la fonction main des variables xr1,yr1,xr2,yr2 ddies la position des deux raquettes. Puis dnir une fonction DessineRaquette en prenant modle sur DessineBalle. Placer les appels de ces fonctions aux endroits appropris dans la boucle principale. 2. Gestion du clavier : La gestion du clavier est ralise pour vous par la fonction Clavier dont nous 189

A.2. Variables, boucles, conditions, fonctions

A. Travaux Pratiques

Figure A.1 Mini tennis... ignorerons le contenu pour linstant. Cette fonction nous permet de savoir directement si une des touches qui nous intressent (q et s pour le dplacement de la premire raquette, k et l pour la deuxime) sont enfonces ou non. Cette fonction, Clavier(int& sens1, int& sens2), retourne dans sens1 et sens2, les valeurs 0, -1 ou 1 (0 : pas de dplacement, -1 : vers la gauche, 1 : vers la droite). 3. Dplacement des raquettes : Coder le dplacement dune raquette dans une fonction void BougeRaquette(int &x, int sens) puis appeler cette fonction dans la boucle principale pour chacune des deux raquettes. Evidemment, faire en sorte que les raquettes ne puissent sortir de la fentre. 4. Rebonds sur les raquettes : Sinspirer de la gestion des rebonds de la balle. Ici il faut non seulement vrier si la balle va atteindre le bas ou le haut de lcran mais aussi si elle est assez proche en abscisse de la raquette correspondante. 5. Comptage et achage du score : Modier la fonction BougeBalle an de comptabiliser le score des deux joueurs et lacher dans la console. 6. Pour ceux qui ont ni : Lors dun rebond sur la raquette, modier linclinaison de la trajectoire de la balle en fonction de la vitesse de la raquette ou de lendroit de frappe. A la n de la sance Envoyez la solution nettoye et compresse au responsable de votre groupe Pensez sauvegarder votre travail sur Z : Vous devriez avoir obtenu un programme ressemblant celui de la gure A.1.

190

A. Travaux Pratiques

A.3. Tableaux

Figure A.2 Master mind la console...

A.3

Tableaux

Dans ce TP, nous allons programmer un jeu de Mastermind, o lutilisateur doit deviner une combinaison gnre alatoirement par lordinateur. Le joueur dispose dun nombre dtermin dessais. A chaque essai dune combinaison, lordinateur fournit deux indices : le nombre de pions correctement placs et le nombre de pions de la bonne couleur mais incorrectement positionns.

A.3.1

Mastermind Texte

1. Rcuprer la solution de dpart : Tlcharger larchive Tp3_Initial.zip sur la page du cours, la dcompresser dans un rpertoire faisant apparatre les noms des deux lves et ouvrir la solution MasterMind dans Visual Studio. Etudier le projet Mastermind. 2. Reprsenter une combinaison : Nous prendrons ici une combinaison de 5 pions de 4 couleurs direntes. La couleur dun pion sera reprsente par un entier compris entre 0 et 3. Pour une combinaison de 5 pions, nous allons donc utiliser un tableau de 5 entiers. int combin[5]; // tableau de 5 entiers 3. Acher une combinaison : Programmer une fonction permettant dacher une combinaison donne lcran. La manire la plus simple de faire consistera faire acher les dirents chires de la combinaison sur une mme ligne les uns la suite des autres. 4. Gnrer une combinaison alatoirement : En dbut de partie, lordinateur doit gnrer alatoirement une combinaison faire deviner lutilisateur. Nous allons pour cela utiliser les fonctions dclares dans le chier cstdlib, notamment la fonction rand() permettant de gnrer un nombre au hasard entre 0 et RAND_MAX. An dobtenir un nombre entre 0 et n, on procdera de la manire suivante : x = rand()%n; 191

A.3. Tableaux

A. Travaux Pratiques

Pour que la squence de nombres gnre ne soit pas la mme dune fois sur lautre, il est ncessaire dinitialiser le gnrateur avec une graine variable. La manire la plus simple de procder consiste utiliser lheure courante. La fonction time() dclare dans le chier ctime permet de lobtenir. En n de compte, la fonction suivante nous permet donc de gnrer une combinaison : #include <cstdlib> #include <ctime> using namespace std; void genereCombinaison(int combin[5]) { srand(unsigned int(time(0))); // initialisation // du generateur for (int i=0; i<5; ++i) combin[i] = rand()%4; // appels au generateur } 5. Changer la complexit du jeu : Rapidement, vous allez devenir des experts en Mastermind. Vous allez alors vouloir augmenter la dicult. Il sura alors dallonger la longueur de la combinaison, ou daugmenter le nombre de couleurs possibles. Cela vous est dores et dj trs facile si vous avez pens dnir une constante globale pour chacune de ces deux grandeurs. Si ce nest pas le cas, il est grand temps de le faire. Dnissez par exemple : const int nbcases = 5; const int nbcoul = 4; // longueur de la combinaison // nombre de couleurs diffrentes

Reprenez le code que vous avez dj crit en utilisant ces constantes. Il est trs important de stocker les paramtres constants dans des variables, cela fait gagner beaucoup de temps lorsque lon veut les modier. 6. Saisie dune combinaison au clavier : La fonction suivante, que nous vous demanderons dadmettre, saisit une chane de caractres (string) au clavier et remplit le tableau combi[] avec les chires que les nbcases premiers caractres de la chane reprsentent. void getCombinaison(int combi[nbcases]) { cout << "Votre essai: "; string s; cin >> s; for (int i=0;i<nbcases;i++) combi[i]=s[i]-0; } Dans le cadre de notre Mastermind, il sagit de modier cette fonction pour quelle contrle que la chane rentre est bien de bonne taille et que les chires sont bien entre 0 et nbcoul-1. Lessai devra tre redemand jusqu ce que la combinaison soit valide. On utilisera entre autres la fonction s.size() qui retourne la taille de la chane s (la syntaxe de cette fonction sera comprise plus tard dans le cours...) 192

A. Travaux Pratiques

A.3. Tableaux

7. Traitement dune combinaison : Il faudrait maintenant programmer une fonction comparant une combinaison donne avec la combinaison trouver. Cette fonction devrait renvoyer deux valeurs : le nombre de pions de la bonne valeur bien placs, puis, dans les pions restant, le nombre de pions de la bonne valeur mais mal placs. Par exemple, si la combinaison trouver est 02113 : 00000 : 1 pion bien plac (0xxxx), 0 pion mal plac (xxxxx) 20000 : 0 pion bien plac (xxxxx), 2 pions mal placs (20xxx) 13133 : 2 pions bien placs (xx1x3), 1 pion mal plac (1xxxx) 13113 : 3 pions bien placs (xx113), 0 pion mal plac (xxxxx) 12113 : 4 pions bien placs (x2113), 0 pion mal plac (xxxxx) ... Pour commencer et pouvoir tout de suite tester le jeu, programmer une fonction renvoyant uniquement le nombre de pions bien placs. 8. Boucle de jeu : Nous avons maintenant notre disposition toutes les briques ncessaires 1 , il ny a plus qu les assembler pour former un jeu de mastermind. Pensez par ailleurs ajouter la dtection de la victoire (quand tous les pions sont bien placs), et celle de la dfaite (quand un nombre limite dessais a t dpass). 9. Version complte : Complter la fonction de traitement dune combinaison pour quelle renvoie galement le nombre de pions mal placs.

A.3.2

Mastermind Graphique

Le jeu de Mastermind que nous venons de raliser reste malgr tout trs peu convivial. Nous allons y remdier en y ajoutant une interface graphique. 1. Etude du projet de dpart : Passer dans le projet Mastermind Graphique. Penser le dnir comme projet de dmarrage pour que ce soit lui qui se lance lexcution (son nom doit apparatre en gras dans la liste des projets). Les fonctions graphiques sont dj dnies. Elle fonctionnent selon un principe de division de la fentre graphique en lignes. La fonction : void afficheCombinaison(int combi[nbcases], int n); permet dacher la combinaison combi sur la ligne n. Au dbut du programme, on laisse en haut de la fentre graphique autant de lignes libres que le joueur a dessais pour y acher le droulement du jeu. On ache en bas de la fentre graphique un mini mode demploi qui rsume les correspondances entre touches et couleurs. 2. Mastermind graphique : Rinsrer dans ce projet les fonctions de gnration alatoire dune combinaison et de comparaison de deux comparaisons crites prcdemment. Puis reprogrammer la boucle principale du jeu en utilisant lachage graphique. 3. Ultime amlioration : On souhaite pouvoir eacer une couleur aprs lavoir tape, au cas o lon se serait tromp. Etudier les fonctions int Clavier();
1. mme si la fonction de traitement dune combinaison est pour linstant incomplte

193

A.3. Tableaux

A. Travaux Pratiques

Figure A.3 Master mind graphique ... et void getCombinaison(int[],int); La premire prend dj en compte la touche Retour arrire, mais pas la seconde qui la considre comme une erreur de frappe. Modier cette dernire en consquence. A la n de la sance Nettoyez et envoyez votre programme sous forme ZIP ou RAR votre enseignant. Pensez sauvegarder votre travail sur Z :

194

A. Travaux Pratiques

A.4. Structures

A.4

Structures

Avertissement : Dans ce TP, nous allons faire voluer des corps soumis la gravitation, puis leur faire subir des chocs lastiques. Il sagit dun long TP qui nous occupera deux semaines. Les sections 11 et 15 ne sont donnes que pour les lves les plus laise et ne seront abordes quen deuxime semaine. En section A.4.2 sont dcrites quelques-unes des fonctions utiliser, et en A.4.3 leur justication physique.

A.4.1

Etapes

Mouvement de translation 1. Pour commencer, tudier le projet : Tlcharger le chier TP4.zip sur la page habituelle, le dcompresser et lancer Visual C++. Parcourir le projet, en sattardant sur les variables globales et la fonction main (inutile de regarder le contenu des fonctions dj dnies mais non utilises). Le programme fait voluer un point (x, y) selon un mouvement de translation constante (vx, vy), et ache rgulirement un disque centr en ce point. Pour ce faire, an de leacer, on retient la position du disque au dernier achage (dans ox et oy) ; par ailleurs, deux instructions commenant par NoRefresh sont places autour des instructions graphiques an dacclrer lachage. 2. Utiliser une structure : Modier le programme de faon utiliser une structure Balle renfermant toute linformation sur le disque (position, vitesse, rayon, couleur). 3. Fonctions dachage : Crer (et utiliser) une fonction void AfficheBalle(Balle D) achant le disque D, et une autre void EffaceBalle(Balle D) leaant. 4. Faire bouger proprement le disque : Pour faire voluer la position du disque, remplacer les instructions correspondantes dj prsentes dans main par un appel une fonction qui modie les coordonnes dune Balle, en leur ajoutant la vitesse de la Balle multiplie par un certain pas de temps dni en variable globale (dt = 1 pour linstant). Gravitation 5. volution par acclration : Crer (et utiliser) une fonction qui modie la vitesse dune Balle de faon lui faire subir une attraction constante ax = 0 et ay = 0.0005. Indice : procder comme prcdemment, cest--dire ajouter 0.0005 dt vy ... 6. Ajouter un soleil : On souhaite ne plus avoir une gravit uniforme. Ajouter un champ dcrivant la masse la structure Balle. Crer un soleil (de type Balle), jaune, xe (ie de vitesse nulle) au milieu de la fentre, de masse 10 et de rayon 4 pixels (la masse de la plante qui bouge tant de 1). Lacher. 7. Acclration gravitationnelle : Crer (et utiliser la place de la gravitation uniforme) une fonction qui prend en argument la plante et le soleil, et qui fait voluer la position de la plante. Rappel 195

A.4. Structures

A. Travaux Pratiques

de physique : lacclration prendre en compte est G mS /r3 , avec ici G = 1 r (Vous aurez sans doute besoin de la fonction double sqrt(double x), qui retourne la racine carre de x). Ne pas oublier le facteur dt... Faire tourner et observer. Essayez diverses initialisations de la plante (par exemple x = largeur/2, y = hauteur/3, vx = 1 , vy = 0). 8. Initialisation alatoire : Crer (et utiliser la place des conditions initiales donnes pour le soleil) une fonction initialisant une Balle, sa position tant dans la fentre, sa vitesse nulle, son rayon entre 5 et 15, et sa masse valant le rayon divis par 20. Vous aurez probablement besoin de la fonction Random... 9. Des soleils par milliers... : Placer 10 soleils alatoirement (et en tenir compte lachage, dans le calcul du dplacement de lastrode...). 10. Diminuer le pas de temps de calcul : An dviter les erreurs des la discrtisation du temps, diminuer le pas de temps dt, pour le xer 0.01 (voire 0.001 si la machine est assez puissante). Rgler la frquence dachage en consquent (inversement proportionnelle dt). Lancer plusieurs fois le programme. Chocs lastiques simples 11. Faire rebondir lastrode : Faire subir des chocs lastiques lastrode chaque fois quil sapproche trop dun soleil, de faon ce quil ne rentre plus dedans (fonction ChocSimple), et rtablir dt une valeur plus leve, par exemple 0.1 (modier la frquence dachage en consquent). Pour savoir si deux corps sont sur le point dentrer en collision, utiliser la fonction Collision. Jeu de tir ( droite gure A.4) 12. Ouvrir un nouveau projet : An de partir dans deux voies direntes et travailler proprement, ajouter un nouveau projet (WinLib5), appel Duel, dans cette mme solution, et recopier (par exemple par copier/coller) intgralement le contenu du chier main.cpp du projet Gravitation. 13. vous de jouer ! Transformer le projet Duel, laide des fonctions qui y sont dj prsentes, en un jeu de tir, deux joueurs. Chacun des deux joueurs a une position xe, et divers soleils sont placs alatoirement dans lcran. Chaque joueur, tour de rle, peut lancer une Balle avec la vitesse initiale de son choix, la balle subissant les eets de gravitation des divers soleils, et disparaissant au bout de 250 pas de temps dachage. Le gagnant est le premier qui russit atteindre lautre... Conseils pratiques : positionner symtriquement les joueurs par rapport au centre, de prfrence mi-hauteur en laissant une marge dun huitime de la largeur sur le ct ; utiliser la fonction GetMouse pour connatre la position de la souris ; en dduire la vitesse dsire par le joueur en retranchant ces coordonnes celles du centre de la boule lancer, et en multipliant par un facteur 0.00025. 196

A. Travaux Pratiques

A.4. Structures

Figure A.4 Corps clestes et jeu de tir... 14. Amliorations : Faire en sorte quil y ait systmatiquement un gros soleil au centre de lcran (de masse non ncessairement consquente) an dempcher les tirs directs. 15. Initialisation correcte : Modier la fonction de placement des soleils de faon ce que les soleils ne sintersectent pas initialement, et quils soient une distance minimale de 100 pixels des emplacements des joueurs. Chocs lastiques ( gauche gure A.4) 16. Tout faire voluer, tout faire rebondir : On retourne dans le projet Gravitation. Tout faire bouger, y compris les soleils. Utiliser, pour les chocs lastiques, la fonction Chocs (qui fait rebondir les deux corps). Faire en sorte que lors de linitialisation les soleils ne sintersectent pas.

A.4.2

Aide

Fonctions fournies : void InitRandom(); est excuter une fois avant le premier appel Random. double Random(double a, double b); renvoie un double alatoirement entre a et b (compris). Excuter une fois InitRandom(); avant la premire utilisation de cette fonction. void ChocSimple(double x, double y, double &vx, double &vy, double m, double x2, double y2, double vx2, double vy2); fait rebondir la premire particule, de coordonnes (x, y), de vitesse (vx, vy) et de masse m, sur la deuxime, de coordonnes (x2, y2) et de vitesse (vx2, vy2), sans dplacer la deuxime.

197

A.4. Structures

A. Travaux Pratiques

void Choc(double x, double y, double &vx, double &vy, double m, double x2, double y2, double &vx2, double &vy2, double m2); fait rebondir les deux particules lune contre lautre. bool Collision(double x1, double y1, double vx1, double vy1, double r1, double x2,double y2, double vx2, double vy2, double r2); renvoie true si le corps de coordonnes (x1, y1), de vitesse (vx1, vy1) et de rayon r1 est sur le point dentrer en collision avec le corps de coordonnes (x2, y2), de vitesse (vx2, vy2) et de rayon r2, et false sinon.

A.4.3

Thorie physique

NB : Cette section nest donne que pour expliquer le contenu des fonctions prprogrammes fournies avec lnonc. Elle peut tre ignore en premire lecture. Acclration La somme des forces exerces sur un corps A est gale au produit de sa masse par lacclration de son centre de gravit. F i/A = mA G(A) a
i

Gravitation universelle Soient deux corps A et B. Alors A subit une force dattraction 1 u F B/A = GmA mB 2 BA . dA,B Chocs lastiques Soient A et B deux particules rentrant en collision. Connaissant tous les paramtres avant le choc, comment dterminer leur valeur aprs ? En fait, seule la vitesse des particules reste calculer, puisque dans linstant du choc, les positions ne changent pas. Durant un choc dit lastique, trois quantits sont conserves : 1. la quantit de mouvement P = mA A + mB B v v r v r v 2. le moment cintique M = mA A A + mB B B (qui est un rel dans le cas dun mouvement plan).
2 2 3. lnergie cintique Ec = 1 mA vA + 1 mB vB . 2 2

Ce qui fait 4 quations pour 4 inconnues. Rsolution du choc On se place dans le rfrentiel du centre de masse. On a alors, tout instant : v v 1. P = 0 (par dnition de ce rfrentiel), do mA A = mB B . v v r r r r 2. M = ( A B ) mA A , do, en notant = A B , M = mA A . r r 3. 2Ec = mA (1 +
mA 2 )vA . mB

198

A. Travaux Pratiques

A.4. Structures

La constance de Ec nous informe que dans ce repre, la norme des vitesses est conser r ve, et la constance du moment cintique que les vitesses varient paralllement . Si lon veut que les vitesses varient eectivement, il ne nous reste plus quune possibilit : mutliplier par 1 la composante des i selon . Ce qui fournit un algorithme simple v r de rebond. Dcider de limminence dun choc On ne peut pas se contenter, lors de lvolution pas--pas des coordonnes des disques, de dcider quun choc aura lieu entre t et t + dt rien quen estimant la distance entre les deux disques candidats la collision linstant t, ni mme en prenant en plus en considration cette distance linstant t + dt, car, si la vitesse est trop leve, un disque peut dj avoir travers lautre et en tre ressorti en t+dt... La solution consiste expliciter le minimum de la distance entre les disques en fonction du temps, variant entre t et t + dt. Soit N (u) = ( A (u) B (u))2 le carr de la distance en question. On a : r r N (u) = ( A (t) B (t) + (u t)( A (t) B (t)))2 r r v v Ce qui donne, avec des notations supplmentaires : N (u) = (t)2 + 2(u t) (t) (t) + (u t)2 (t)2 r r v v La norme, toujours positive, est minimale au point u tel que u N (u) = 0, soit : (tm t) = Donc : 1. si tm < t, le minimum est atteint en t, 2. si t < tm < t + dt, le minimum est atteint en tm ; 3. sinon, t + dt < tm , le minimum est atteint en t + dt. Ce qui nous donne explicitement et simplement la plus petite distance atteinte entre les deux corps entre t et t + dt. (t) (t) r v (t)2 v

199

A.5. Fichiers spars

A. Travaux Pratiques

A.5

Fichiers spars

Nous allons poursuivre dans ce TP les simulations de gravitation et de chocs lastiques entames la semaine dernire, en sparant dans dirents chiers les direntes fonctions et structures utilises. 1. De bonnes bases : Tlcharger le chier Tp5.zip sur la page habituelle, le dcompresser et lancer Visual C++. Le projet Gravitation contient une solution partielle au TP4 (jusqu la question 7, incluse). Si vous avez t plus loin et/ou si vous prfrez rutiliser votre propre solution, vous pouvez quitter Visual C++, remplacer le chier Gravitation.cpp par celui que vous aurez rcupr dans votre TP4, et relancer Visual C++.

A.5.1

Fonctions outils

2. Un chier de dnitions... Ajouter un nouveau chier source nomm Tools.cpp au projet avec "Fichier / Ajouter un nouvel lment / Fichier C++". Y placer les fonctions fournies lavance au dbut du TP4 (InitRandom, Random, Choc, ChocSimple et Collision), en les retirant de Gravitation.cpp. Ne pas oublier les lignes suivantes, que lon pourra retirer de Gravitation : #include <cstdlib> #include <ctime> using namespace std; 3. ... et un chier de dclarations Ajouter un nouveau chier den-tte nomm Tools.h. Inclure la protection contre la double inclusion vue en cours (#pragma once). Y placer les dclarations des fonctions mises dans Tools.cpp, ainsi que la dnition de dt, en retirant celle-ci de main. Rajouter au dbut de Tools.cpp et de Gravitation.cpp un #include "Tools.h".

A.5.2

Vecteurs

4. Structure Vector : Crer dans un nouveau chier Vector.h une structure reprsentant un vecteur du plan, avec deux membres de type double. Ne pas oublier le mcanisme de protection contre la double inclusion. Dclarer (et non dnir) les oprateurs et fonction suivants : Vector Vector double Vector Vector operator+(Vector a,Vector b); operator-(Vector a,Vector b); norme2(Vector a); operator*(Vector a,double lambda); operator*(double lambda,Vector a); // // // // // Somme de deux vecteurs Diffrence de deux vecteurs Norme euclidienne dun vecteur Multiplication par un scalaire Multiplication par un scalaire

5. Fonctions et oprateurs sur les Vector : Crer un nouveau chier Vector.cpp. Mettre un #include du chier den-tte correspondant et dnir les oprateurs qui y sont dclars (Rappel : sqrt est dni dans le chier den-tte systme <cmath> ; ne pas oublier non plus le using namespace std; qui permet dutiliser cette fonction). Astuce : une fois quune version de operator* est dnie, la deuxime version peut utiliser la premire dans sa dnition... 200

A. Travaux Pratiques

A.5. Fichiers spars

6. Vecteur vitesse et vecteur position : Systmatiquement remplacer dans Gravitation.cpp les vitesses et positions par des objets de type Vector (y compris dans la dnition de la structure Balle). Utiliser autant que possible les oprateurs et fonction dnis dans Vector.cpp.

A.5.3

Balle part

7. Structure Balle : Dplacer la structure Balle dans un nouveau chier den-tte Balle.h. Puisque Balle utilise les types Vector et Color, il faut aussi ajouter ces lignes : #include <CL/Graphics/Graphics.h> using namespace CL::Graphics; #include "Vector.h" 8. Fonctions associes : Dplacer toutes les fonctions annexes prenant des Balle en paramtres dans un nouveau chier Balle.cpp. Il ne devrait plus rester dans Gravitation.cpp dautre fonction que main. Dclarer dans Balle.h les fonctions dnies dans Balle.cpp. Ajouter les #include ncessaires dans ce dernier chier et dans Gravitation.cpp et faire les adaptations ncessaires (par exemple, si des fonctions utilisent largeur ou hauteur, comme ces constantes ne sont dnies que dans Gravitation.cpp, il faut les passer en argument...)

A.5.4

Retour la physique

9. Des soleils par milliers... : Placer 10 soleils alatoirement (et en tenir compte lachage, dans le calcul du dplacement de lastrode...). 10. Diminuer le pas de temps de calcul : An dviter les erreurs dues la discrtisation du temps, diminuer le pas de temps dt, pour le xer 0.01 (voire 0.001 si la machine est assez puissante). Rgler la frquence dachage en consquence (inversement proportionnelle dt). Lancer plusieurs fois le programme. Chocs lastiques simples 11. Faire rebondir lastrode : Faire subir des chocs lastiques lastrode chaque fois quil sapproche trop dun soleil, de faon ce quil ne rentre plus dedans (fonction ChocSimple), et rtablir dt une valeur plus leve, par exemple 0.1 (modier la frquence dachage en consquent). Pour savoir si deux corps sont sur le point dentrer en collision, utiliser la fonction Collision. Jeu de tir 12. Ouvrir un nouveau projet : An de partir dans deux voies direntes et travailler proprement, ajouter un nouveau projet appel Duel, dans cette mme solution. 201

A.5. Fichiers spars

A. Travaux Pratiques

Figure A.5 Corps clestes et jeu de tir... 13. Ne pas refaire deux fois le travail : Comme nous aurons besoins des mmes fonctions dans ce projet que dans le projet Gravitation, ajouter au projet (sans en crer de nouveaux !) les chiers Vector.h, Vector.cpp, Balle.h, Balle.cpp, Tools.h, Tools.cpp. Les chiers sont les mmes que dans le projet Gravitation, ils ne sont pas recopis. Mettre au dbut du Duel.cpp les #include correspondants. Essayer de compiler Duel.cpp. Comme le compilateur narrive pas trouver les chiers inclus, qui ne sont pas dans le mme rpertoire, il faut lui indiquer o les trouver. (#include "../Gravitation/Tools.h" par exemple). 14. vous de jouer ! Transformer le projet Duel, laide des fonctions dnies auparavant, en un jeu de tir, deux joueurs. Chacun des deux joueurs a une position xe, et divers soleils sont placs alatoirement dans lcran. Chaque joueur, tour de rle, peut lancer une Balle avec la vitesse initiale de son choix, la balle subissant les eets de gravitation des divers soleils, et disparaissant au bout de 250 pas de temps dachage. Le gagnant est le premier qui russit atteindre lautre... Conseils pratiques : positionner symtriquement les joueurs par rapport au centre, de prfrence mi-hauteur en laissant une marge dun huitime de la largeur sur le ct ; utiliser la fonction GetMouse pour connatre la position de la souris ; en dduire la vitesse dsire par le joueur en retranchant ces coordonnes celles du centre de la boule lancer, et en multipliant par un facteur 0.00025. 15. Amliorations : Faire en sorte quil y ait systmatiquement un gros soleil au centre de lcran (de masse non ncessairement consquente) an dempcher les tirs directs. 16. Initialisation correcte : Modier la fonction de placement des soleils de faon ce que les soleils ne sintersectent pas initialement, et quils soient une distance minimale de 100 pixels des emplacements des joueurs. Chocs lastiques 17. Tout faire voluer, tout faire rebondir : On retourne dans le projet Gravitation. Tout faire bouger, y compris les soleils. Utiliser, pour les chocs lastiques, la fonction Chocs (qui fait rebondir les deux corps). Faire en sorte que lors de linitialisation les soleils ne sintersectent pas. 202

A. Travaux Pratiques

A.6. Les tris

Figure A.6 Deux tris en cours dexcution : tri bulle et Quicksort...

A.6

Les tris

Dans ce TP, nous allons programmer quelques algorithmes de tri dlments dans un tableau. Pour que cela soit interactif, et que lon puisse voir comment se passent chacun des tris, une interface graphique a t programme, qui ache le tableau et permet de visualiser les opration quon ralise dessus (gure A.6).

A.6.1

Mlanger un tableau

1. Pour commencer, tudier le projet : Tlcharger le chier Tp6.zip sur la page habituelle, le dcompresser et lancer Visual C++. Le projet est spar entre le chier main.cpp, dans lequel on programmera les algorithmes de tri, et le couple (tools.cpp, tools.h), qui gre linterface graphique et quelques fonctions utiles la comparaison des dirents tris. Ouvrir tools.h pour dcouvrir les fonctions de cette interface graphique, puis main.cpp pour voir comment elles sont utilises (les lignes commentes sont l pour montrer comment vous utiliserez les fonctions melange_tableau et tri_selection que vous allez programmer). Excuter le projet. Pour linstant, il ne fait quinitialiser un tableau et lacher. 2. Accs un tableau, change de 2 valeurs Pour que les oprations que vous eectuerez sur les tableaux soient aches automatiquement, il faudra que vous utilisiez uniquement les fonctions valeur et echange dclare dans tools.h (ne pas accder directement au tableau avec T[i]). Entranez-vous les utiliser dans la fonction main(), en accdant une valeur du tableau T0, et en permutant 2 de ses lments. 3. Mlanger un tableau Une fonction dclare dans tools.h nexiste pas encore dans tools.cpp : la fonction void melange_tableau (double T[], int taille) qui comme son nom lindique mlange un tableau. Ajoutez-la (dans tools.cpp bien sr) Ide : le mlange le plus rapide (et le plus ecace) consiste parcourir le tableau une fois, et permuter chacun des lments avec un autre choisi au hasard (utiliser 203

A.6. Les tris

A. Travaux Pratiques

la fonction int random(int a) dnie dans tools.cpp pour tirer un entier entre 0 et a-1).

A.6.2

Tris quadratiques

Les 3 algorithmes de tri qui suivent trient un tableau en temps 0(n2 ), cest dire que le temps pour trier un tableau est proportionnel au carr de la taille de ce tableau. Si vous avez peu de temps, vous pouvez ne faire quun ou deux des trois, pour pouvoir toucher galement au tri Quicksort. 4. Tri slection Cest le tri le plus naf. Il consiste parcourir le tableau une premire fois pour trouver le plus petit lment, mettre cet lment au dbut (par une permutation), parcourir une seconde fois le tableau (de la 2me la dernire case) pour trouver le second plus petit lment, le placer en 2me position, et ainsi de suite... Programmer la fonction void tri_selection(double T[], int taille). Vous pouvez alors dcommenter les lignes commentes dans main() et excuter. 5. Tri insertion En gnral, cest peu prs lalgorithme quutilise un tre humain pour trier un paquet de cartes, des ches... Il consiste ajouter un un les lments non tris au bon endroit parmis ceux qui sont dj tris : si ncessaire on change les 2 premiers lments du tableau pour les mettre dans lordre, puis (si ncessaire) on dplace le 3me lment vers la gauche, par des changes de proche en proche, jusqu ce quil soit la bonne position par rapport aux 2 premiers, puis le 4me, et ainsi de suite... Programmer void tri_insertion(double T[], int taille) et regarder comment a se passe. 6. Tri bulle Le tri bulle consiste parcourir n fois le tableau, et chaque fois quon est sur un lment, on lchange avec son voisin de droite si ce dernier est plus petit que lui. Programmer tri_bulle(double T[], int taille) et regarder comment a se passe. Constater quon nest pas oblig de parcourir tout le tableau chaque fois et amliorer la fonction.

A.6.3

Quicksort

Lalgorithme : le tri Quicksort adopte la stratgie diviser pour rgner qui consiste rduire le problme du tri dun tableau de taille n aux tris de 2 tableaux de taille n : 2 on choisit un lment dans le tableau (on le prend en gnral au hasard, mais par commodit on prendra ici le premier lment du tableau) quon appelle pivot. On spare ensuite les autres lments entre ceux infrieurs au pivot et ceux suprieurs au pivot. Il ny a plus qu trier alors ces deux moitis. 7. Pivot Crer une fonction int pivot(double T[], int taille) qui prend comme pivot le premier lment du tableau, change les lments du tableau de manire ce quon aie dabord les lments infrieurs au pivot, puis le pivot, puis les lments suprieurs au pivot, et qui renvoie la nouvelle position du pivot. 204

A. Travaux Pratiques

A.6. Les tris

Ne pas oublier dexcuter la fonction pour vrier quelle marche bien ! Ide : on utilise 2 index qui parcourent le tableau, le premier partir du 2me lment (le 1er tant le pivot) et avanant vers la droite, le second partir du dernier lment et avanant vers la gauche le premier index sarrte sur le premier lment suprieur au pivot quil rencontre, et le second index, sur le premier lment infrieur au pivot quil rencontre on change alors les 2 lments, et les 2 index continuent davancer, et ainsi de suite quand les 2 index se rencontrent, gauche de lintersection tous les lments sont infrieurs au pivot, et droite ils sont suprieurs on change le pivot (en 1re position) avec le dernier des lments infrieurs pour obtenir ce quon dsire 8. Fonctions rcursives Le principe mme de la statgie diviser pour rgner implique que la fonction qui eectue le tri quicksort va sappeler elle-mme pour trier une sous-partie du tableau. En pratique, on va utiliser 2 arguments debut et fin qui indiquent quon ne ralise le tri que entre les indices debut et fin. Changer la fonction pivot en lui ajoutant ces 2 arguments, et en ne la faisant eectivement travailler que entre ces 2 indices (par exemple, le pivot est initialement lindice debut) 9. Quicksort On peut maintenant crire une fonction void quicksort_recursif(double T[], int taille, int debut, int fin), qui contient lalgorithme, ainsi que la fonction void quicksort(double T[], int taille), qui ne fait quappeler cette dernire, mais qui sera celle quon utilisera dans main() (car plus simple).

A.6.4

Gros tableaux

Maintenant quon a vu graphiquement comment marchent ces algorithmes, il est intressant de les faire fonctionner et de les comparer sur des tableaux de grande taille. 10. Tableaux de taille variable Si on veut tester nos algorithmes sur des tableaux de grande taille, il faut utiliser des tableaux de taille variable. Remplacer toutes les lignes de type double t[taille]; par double *t = new double[taille], et la n des fonctions o se trouvent ces dclarations, ajouter la ligne delete[] t;. Dautre part il faut dsactiver lachage : init_tools(512, false). 11. Nombre de lectures et dcriture Pour comparer plus rigoureusement 2 algorithmes, on peut comparer le nombre de lectures du tableau et le nombre dcriture dans le tableau. Crer 2 variables globales dans tools.cpp et modier les fonctions init_tri, valeur, echange et fin_tri pour initialiser, compter et acher le nombre de lec205

A.6. Les tris

A. Travaux Pratiques

tures et dcritures. Au fait, en combien doprations seectue en moyenne Quicksort ? 12. Temps de calcul Il est intressant galement davoir les temps de calcul exacts. Pour cela, on peut enregistrer dans une nouvelle variable globale timer0 le temps quil est avant le tri : timer0 = double(clock())/CLOCKS_PER_SEC ; et la retrancher au temps quil est aprs le tri (modier les fonctions init_tri et fin_tri pour faire ce calcul et lacher). 13. Mode Release On peut changer le mode de compilation en le passant de Debug Release. Vrier que lexcution des algorithmes est eectivement bien plus rapide en mode Release !

206

A. Travaux Pratiques

A.7. Images

A.7

Images

Figure A.7 Deux images et dirents traitements de la deuxime (ngatif, ou, relief, dformation, contraste et contours). Dans ce TP, nous allons jouer avec les tableaux bidimensionnels statiques (mais stocks dans des tableaux 1D) puis dynamiques. Pour changer de nos passionnantes matrices, nous travaillerons avec des images (gure A.7).

A.7.1

Allocation

1. Rcuprer le projet : Tlcharger le chier Tp7.zip sur la page habituelle, le dcompresser et lancer Visual C++. 2. Saturer la mmoire : Rien voir avec ce quon va faire aprs mais il faut lavoir fait une fois... Faire, dans une boucle innie, des allocations de 1000000 entiers sans dsallouer et regarder la taille du process grandir. (Utiliser Ctrl+Shift+Echap pour accder au gestionnaire de tches). Compiler en mode Release pour utiliser la "vraie" gestion du tas (Le mode Debug utilise une gestion spcique qui aide trouver les bugs et se comporte diremment...)

A.7.2

Tableaux statiques

3. Niveaux de gris : Une image noir et blanc est reprsente par un tableau de pixels de dimensions constantes W=300 et H=200. Chaque pixel (i,j) est un byte (entier de 0 255) allant de 0 pour le noir 255 pour le blanc. Lorigine est en haut gauche, i est lhorizontale et j la verticale. Dans un tableau de byte mono-dimensionnel t de taille W*H mmorisant le pixel (i,j) en t[i+W*j] : 207

A.7. Images

A. Travaux Pratiques

Stocker une image noire et lacher avec PutGreyImage(0,0,t,W,H). Idem avec une image blanche. Idem avec un dgrad du noir au blanc (attention aux conversions entre byte et double). Idem avec t(i, j) = 128 + 128 sin(4i/W ) sin(4j/H) (cf gure A.7). Utiliser #define _USE_MATH_DEFINES #include <cmath> pour avoir les fonctions et les constantes mathmatiques : M_PI vaut . 4. Couleurs : Acher, avec PutColorImage(0,0,r,g,b,W,H), une image en couleur stocke dans trois tableaux r, g et b (rouge, vert, bleu). Utiliser la fonction Click() pour attendre que lutilisateur clique avec la souris entre lachage prcdent et ce nouvel achage.

A.7.3

Tableaux dynamiques

5. Dimensions au clavier : Modier le programme prcdent pour que W et H ne soient plus des constantes mais des valeurs entres au clavier. Ne pas oublier de dsallouer.

A.7.4

Charger un chier

6. Image couleur : La fonction LoadColorImage("../ppd.jpg",r,g,b,W,H); charge le chier "../ppd.jpg" qui est dans le rpertoire au dessus du projet, alloue elle-mme les tableaux r,g,b, les remplit avec les pixels de limage, et aecte aussi W et H en consquence. Attention : les tableaux r,g,b ne doivent pas tre dsallous avec delete mais avec la fonction spciale DeleteImage(). Charger cette image et lacher. Ne pas oublier les dsallocations. 7. Image noir et blanc : La fonction LoadGreyImage("../ppd.jpg",t,W,H) fait la mme chose mais convertit limage en noir et blanc. Acher limage en noir et blanc...

A.7.5

Fonctions

8. Dcouper le travail : On ne garde plus que la partie noir et blanc du programme. Faire des fonctions pour allouer, dtruire, acher et charger les images : void void void void AlloueImage(byte* &I,int W,int H); DetruitImage(byte *I); AfficheImage(byte* I,int W,int H); ChargeImage(char* name,byte* &I,int &W,int &H);

9. Fichiers : Crer un image.cpp et un image.h en consquence... 208

A. Travaux Pratiques

A.7. Images

A.7.6

Structure

10. Principe : Modier le programme prcdent pour utiliser une structure : struct Image { byte* t; int w,h; }; AlloueImage() et ChargeImage() pourront retourner des Image au lieu de faire un passage par rfrence. 11. Indpendance : Pour ne plus avoir savoir comment les pixels sont stocks, rajouter : byte Get(Image I,int i,int j); void Set(Image I,int i,int j,byte g); 12. Traitements : Ajouter dans main.cpp direntes fonctions de modication des images Image Image Image Image Image Negatif(Image I); Flou(Image I); Relief(Image I); Contours(Image I,double seuil); Deforme(Image I);

et les utiliser : (a) Negatif : changer le noir en blanc et vise-versa par une transformation ane. (b) Flou : chaque pixel devient la moyenne de lui-mme et de ses 8 voisins. Attention aux pixels du bords qui nont pas tous leurs voisins (on pourra ne pas moyenner ceux-l et en proter pour utiliser linstruction continue !). (c) Relief : la drive suivant une diagonale donne une impression dombres projetes par une lumire rasante. Approcher cette drive par dirence nie : elle est proportionnelle I(i + 1, j + 1) I(i 1, j 1). Sarranger pour en faire une image allant de 0 255. (d) Contours : calculer par dirences nies la drive horizontale dx = (I(i + 1, j) I(i 1, j))/2 et la drive verticale dy , puis la norme du gradient | I| = d2 + d2 et acher en blanc les points o cette norme est suprieure un y x seuil. (e) Deforme : Construire une nouvelle image sur le principe J(i, j) = I(f (i, j)) avec f bien choisie. On pourra utiliser un sinus pour aller de 0 W-1 et de 0 H-1 de faon non linaire.

A.7.7

Suite et n

13. Sil reste du temps, samuser : Rtrcir une image. Au lieu du ngatif, on peut par exemple changer le contraste. Comment ? 209

A.8. Premiers objets et dessins de fractales

A. Travaux Pratiques

A.8

Premiers objets et dessins de fractales

Figure A.8 Fractales... Dans ce TP, nous allons nous essayer la programmation objet. Nous allons transformer une structure vecteur en une classe et lutiliser pour dessiner des courbes fractales (gure A.8).

A.8.1

Le triangle de Sierpinski

1. Rcuprer le projet : Tlcharger le chier Tp8.zip sur la page habituelle, le dcompresser et lancer Visual C++. Etudier la structure Vector dnie dans les chiers Vector.cpp et Vector.h. 2. Interfaage avec la WinLib : La structure Vector ne comporte pas de fonction dachage graphique. Ajouter dans main.cpp des fonctions DrawLine et DrawTriangle prenant des Vector en paramtres. Il sut de rebondir sur la fonction void DrawLine(int x1,int y1,int x2,int y2,const Color &col,int pen_width) de la WinLib. Le dernier paramtre contrle lpaisseur du trait. 3. Triangle de Sierpinski : Cest la gure fractale choisie par lENPC pour son logo. La gure ci-dessous illustre sa construction. Ecrire une fonction rcursive pour dessiner le triangle de Sierpinski. Cette fonction

Figure A.9 Construction du triangle de Sierpinski. prendra en paramtres les trois points du triangle en cours et lepaisseur du trait. Les trois sous-triangles seront dessins avec un trait plus n. Ne pas oublier la condition darrt de la rcursion ! Utiliser cette fonction dans le main en lui fournissant un triangle initial dpaisseur 6. 210

A. Travaux Pratiques

A.8. Premiers objets et dessins de fractales

A.8.2

Une classe plutt quune structure

4. Classe vecteur : Transformer la structure Vector en une classe. Y incorporer toutes les fonctions et les oprateurs. Passer en public le strict ncessaire. Faire les modications ncessaires dans main.cpp. 5. Accesseurs pour les membre : Rajouter des accesseurs en lecture et en criture pour les membres, et les utiliser systmatiquement dans le programme principal. Lide est de cacher aux utilisateurs de la classe Vector les dtails de son implmentation. 6. Dessin rcursif dun arbre : Nous allons maintenant dessiner un arbre. Pour cela il faut partir dun tronc et remplacer la deuxime moiti de chaque branche par deux branches de mme longueur formant un angle de 20 degrs avec la branche mre. La gure ci-dessous illustre le rsultat obtenu pour direntes profondeurs de rcursion. Ecrire une fonction rcursive pour dessiner une telle courbe. Vous aurez besoin de

Figure A.10 Construction de larbre. la fonction Rotate de la classe Vector.

A.8.3

Changer dimplmentation

7. Deuxime implmentation : Modier limplmentation de la classe Vector en remplaant les membres double x,y; par un tableau double coord[2];. Quelles sont les modications apporter dans main.cpp ? 8. Vecteurs de dimension suprieure : Lavantage de cette dernire implmentation est quelle se gnralise aisment des vecteurs de dimension suprieure. Placer une constante globale DIM gale 2 au dbut de Vector.h et rendre la classe Vector indpendante de la dimension. NB : la fonction Rotate et les accesseurs que nous avons crits ne se gnralisent pas directement aux dimensions suprieures. Les laisser tels quels pour linstant...

A.8.4

Le ocon de neige

9. Courbe de Koch : Cette courbe fractale sobtient en partant dun segment et en remplaant le deuxime tiers de chaque segment par deux segments formant la pointe dun triangle quilatral. Ecrire une fonction rcursive pour dessiner une courbe de Koch. 10. Flocon de neige : Il sobtient en construisant une courbe de Koch partir de chacun des cts dun triangle quilatral. 211

A.9. Tron

A. Travaux Pratiques

Figure A.11 Construction de la courbe de Koch.

Figure A.12 Jeu de Tron.

A.9

Tron

Dans ce TP, nous allons programmer le jeu TRON. Il sagit dun jeu 2 joueurs, dans lequel chaque joueur pilote un mobile qui se dplace vitesse constante et laisse derrire lui une trace infranchissable. Le premier joueur qui percute sa propre trace ou celle de son adversaire a perdu.

A.9.1

Serpent

Nous allons procder en deux temps. Dabord programmer un jeu de Serpent un joueur. Le programme serpent.exe vous donne une ide du rsultat recherch. Dans ce jeu, le joueur pilote un Serpent qui sallonge petit petit (dun lment tous les x tours, avec la convention que la longueur totale est borne nmax lments). Il sagit de ne pas se rentrer dedans ni de percuter les murs. La solution de dpart comporte deux chiers, utils.h et utils.cpp, qui contiennent une structure point (quil faudra ventuellement toer de mthodes utiles) et une fonction destine rcuprer les touches clavier pour linteraction avec les joueurs. Il sagit ici de concevoir un objet Serpent dot des mthodes adquates, plus une fonction jeu_1p exploitant les capacits du Serpent pour reproduire le comportement dsir. On pourra dans un premier temps ne pas grer les collisions (avec le bord et avec lui-mme), et ne les rajouter que dans un second temps. Votre travail se dcompose en 5 tapes : 1. (sur papier) Dnir linterface de la classe Serpent (cest dire lister toutes les fonctionnalits ncessaires). 2. (sur papier) Rchir limplmentation de la classe Serpent : comment stocker les donnes ? comment programmer les direntes mthodes ? (lire en prliminaire les remarques du paragraphe suivant). 3. Dans un chier serpent.h, crire la dclaration de votre classe Serpent : ses membres, ses mthodes, ce qui est public, ce qui ne lest pas. 212

A. Travaux Pratiques

A.9. Tron

4. Soumettre le rsultat de vos rexions votre enseignant pour valider avec lui les choix retenus. 5. Implmenter la classe Serpent (cest dire programmer les mthodes que vous avez dclares). 6. Programmer la fonction jeu_1p utilisant un Serpent. Remarque : Dans le chier utils.h sont dnis : 1. 4 entiers gauche, bas, haut, droite de telle manire que : (a) la fonction x > (x + 1)%4 transforme gauche en bas, bas en droite, droite en haut et haut en gauche ; cette fonction correspond donc un quart de tour dans le sens trigonomtrique. (b) la fonction x > (x 1)%4 transforme gauche en haut, haut en droite, droite en bas et bas en gauche ; cette fonction correspond donc un quart de tour dans le sens des aiguilles dune montre. 2. un tableau de 4 points dir de telle manire que, moyennant la dnition dune fonction permettant de faire la somme de deux points, la fonction p > p + dir[d] renvoie : (a) pour d=gauche le point correspondant au dcalage de p de 1 vers la gauche. (b) pour d=haut le point correspondant au dcalage de p de 1 vers la haut. (c) pour d=droite le point correspondant au dcalage de p de 1 vers la droite. (d) pour d=bas le point correspondant au dcalage de p de 1 vers la bas.

A.9.2

Tron

A partir du jeu de Serpent ralis prcdemment, nous allons facilement pouvoir implmenter le jeu Tron. Le programme tron.exe vous donne une ide du rsultat recherch. Le principe de ce jeu est que chaque joueur pilote une moto qui laisse derrire elle une trace infranchissable. Le but est de survivre plus longtemps que le joueur adverse. 1. Passage deux joueurs. A partir de la fonction jeu_1p, crer une fonction jeu_2p implmentant un jeu de serpent 2 joueurs. On utilisera pour ce joueur les touches Z, Q, S et D. La fonction Clavier() renverra donc les entiers int(Z), int(Q), int(S) et int(D). Remarque : on ne grera quune touche par tour, soit un seul appel la fonction Clavier() par tour. 2. Ultimes rglages (a) Grer la collision entre les deux serpents. (b) Le principe de Tron est que la trace des mobiles reste. Pour implmenter cela, il sut dallonger nos serpents chaque tour.

A.9.3

Graphismes

Petit bonus pour les rapides : nous allons voir comment grer des graphismes un peu plus sympas que les rectangles uniformes que nous avons utiliss jusquici. Lobjectif est de remplacer le carr de tte par une image que lon dplace chaque tour. Nous allons utiliser pour cela les NativeBitmap de la CLGraphics, qui sont des images achage rapide. Pour charger une image dans une NativeBitmap on procde ainsi : 213

A.9. Tron

A. Travaux Pratiques

// Entiers passs par rfrence lors du chargement de limage pour // quy soient stockes la largeur et la hauteur de limage int w,h; // Chargement de limage byte* rgb; LoadColorImage("nom_fichier.bmp",rgb,w,h); // Dclaration de la NativeBitmap NativeBitmap ma_native_bitmap (w,h); // On place limage dans la NativeBitmap ma_native_bitmap.SetColorImage(0,0,rgb,w,h); Lachage dune NativeBitmap lcran se fait alors avec la mthode : void PutNativeBitmap(int x, int y, NativeBitmap nb) 1. Remplacer dans le serpent lachage de la tte par lachage dune image. On pourra utiliser les images moto_blue.bmp et moto_red.bmp fournies. 2. Utiliser limage explosion.bmp lors de la mort dun des joueurs.

214

Annexe B Examens
Note : les corrigs sont disponibles sur la page WEB du cours.

B.1
B.1.1

Examen sur machine 2007 : nonc


Chemins entre deux points

Le but de lexercice demand est de gnrer et de dessiner tous les chemins possibles entre deux points en utilisant une fonction rcursive.

B.1.2

Travail demand

Notes importantes : travailler sur le bureau Windows et dans un projet portant son nom. ne pas utiliser de variable globale non constante. 1. Modliser un terrain par un tableau de boolens de dimensions NxN, N tant un entier constant (prendre une petite valeur, par exemple N=12). Chaque case est soit vide, soit occupe par un mur. 2. Gnrer un terrain alatoirement. Les bords du terrain sont toujours occups par un mur. Les autres cases seront occupes par un mur 3 fois sur 10 en moyenne. 3. Dessiner le terrain (en le grossissant).

Figure B.1 Chemin le plus court...

B.1. Examen sur machine 2007 : nonc

B. Examens

4. Dsigner la souris un point de dpart et un point darrive (qui ne devront pas tre occups par un mur) 5. Programmer une fonction rcursive explorant tous les chemins entre ces deux points. Un chemin ne peut pas traverser un mur et ne repasse pas deux fois au mme endroit. Les chemins seront dessins et eacs au fur et mesure. 6. Rajouter de quoi acher la longueur du chemin chaque fois que larrive est atteinte. 7. Rajouter de quoi mmoriser et acher la longueur du chemin le plus court, sil existe. 8. Rajouter de quoi mmoriser et acher le plus court chemin une la recherche termine. 9. (Rpondre cette question dans une remarque la n de votre source) La recherche est trs lente puisquelle est exhaustive. Si le seul but est de trouver un des chemins les plus courts, il existe des meilleures solutions. Proposer, sans le programmer, un moyen de limiter (et donc dacclrer) la recherche.

216

B.2
B.2.1

Examen sur machine 2006 : nonc


Voyageur de commerce par recuit simul

Soient n villes situes aux points M1 , ..., Mn , le problme du voyageur de commerce consiste trouver un circuit ferm de longueur minimale passant par toutes les villes. Il sagit donc de permuter les villes entre elles pour minimiser l(M1 , ..., Mn ) = n1 d(Mi , Mi+1 ) + d(Mn , M1 ) i=1 o d(A, B) est la distance entre A et B. Une mthode classique pour trouver une solution approche ce problme est de procder un "recuit simul" : 1. Partir dun circuit quelconque C 2. Pour un "grand" nombre de fois (a) Modier alatoirement C en un circuit D (b) Si l(D) < l(C) alors remplacer C par D (c) Sinon, remplacer C par D avec une probabilit e(l(D)l(C))/T , o T est une constante choisir.

B.2.2

Travail demand

1. Travailler en local dans D:\nom_prenom ; 2. Y crer une solution Examen et lui ajouter un projet "Winlib" de nom voyageur ; 3. Un circuit sera mmoris comme un tableau de Pixel 1 de taille constante n (valeur raisonnable : n = 20) ; 4. Faire et utiliser une fonction qui gnre un circuit correspondant n villes situes en des positions alatoires dans la fentre ; 5. Faire et utiliser une fonction qui ache ce circuit 2 ; 6. Faire et utiliser une fonction qui calcule la longueur de ce circuit ; 7. Faire et utiliser une fonction qui transforme un circuit en un autre par change de deux villes choisies au hasard ; 8. Implmenter le recuit simul sans ltape (c). Acher le circuit et sa longueur chaque fois quil change. Lalgorithme devrait rester coinc dans un minimum local ; 9. Rajouter ltape (c). On rappelle que double(rand())/RAND_MAX est un nombre alatoire entre 0 et 1. Ajuster T pour ne pas toujours remplacer C par D ! 10. Choisir maintenant une valeur de T qui dcrot en 1/ t, o t est le nombre dessais, de faon accepter de plus en plus rarement un D plus long que C ; 11. Pour vraiment faire marcher lalgorithme, il faut programmer une nouvelle faon de transformer un circuit en un autre. La voici : choisir deux villes au hasard et retourner le chemin entre les deux villes, cest--dire transformer (M1 , ..., Mn ) en (M1 , ..., Mi1 , Mj , Mj1 , ..., Mi+1 , Mi , Mj+1 , ..., Mn ) Programmer cette nouvelle faon de faire ;
1. On rappelle que Pixel est une structure de la Winlib, contenant les champs double x et double y 2. Les fonctions de la Winlib telles que OpenWindow ou DrawLine (confre Annexe C du poly) seront trs utiles.

B.2. Examen sur machine 2006 : nonc

B. Examens

12. A la n de lexamen, ne pas vous dconnecter et attendre que le surveillant passe recuprer votre travail.

218

B. Examens

B.3. Examen sur machine 2005 : nonc

B.3

Examen sur machine 2005 : nonc

Le but de ce problme est de crer et danimer un modle 3D et dacher un mouvement de cet objet vu par une camra. Pour cela, nous procdons par tapes.

B.3.1

Construction du Modle 3D

1. Travailler en local sous D:\Info\ : crer un dossier nom_prenom dans D:\Info\ (remplacer nom_prenom par son nom et son prnom !). 2. Crer une solution Exam dans D:\Info\nom_prenom\ et ajouter un projet "Winlib" (Winlib5 Projet) de nom logo la solution Exam. 3. Ajouter au projet deux chiers vect.cpp et vect.h dans lesquels vous dclarerez et dnirez lendroit appropri : (a) une structure vect reprsentant un vecteur de R3 (de composantes double x,y,z dans la base canonique (O,i,j,k) de R3 ), (b) un oprateur + entre deux vect. 4. On souhaite construire une structure reprsentant un ttradre. Ajouter au projet deux chiers tetra.cpp et tetra.h et : (a) dnir une structure tetra contenant 4 sommets (vect M[4]) et une couleur Color c, (b) dclarer et dnir une fonction regulier ne prenant aucun argument et retournant un ttradre rgulier de couleur rouge. Pour information, les quatre points suivant forment un ttradre rgulier : {1, 0, 0}, { 1 , 23 , 0}, { 1 , 23 , 0}, {0, 0, 2}. 2 2 On rappelle que la fonction sqrt est dnie dans #include <cmath>.

B.3.2

Projection : du 3D au 2D

Nous nous proccupons maintenant de la projection 2D de ces reprsentations 3D. Ajouter au projet deux chiers camera.cpp et camera.h dans lesquels vous dclarerez et dnirez : 1. une structure camera contenant les variables suivantes : int u0,v0 (le centre de limage), double x0 (lloignement), et double f (la focale). La gure ci-aprs schmatise la reprsentation dune camra.

219

B.3. Examen sur machine 2005 : nonc

B. Examens

2. une fonction projette qui projette un point 3D (vect M) par lintermdiaire dune camra (camera c). La fonction projette retourne un point 2D de type Pixel. Nous rappelons que le type Pixel est dni dans la WinLib : struct Pixel{ int x,y; }; Par consquent, ne pas oublier dajouter les lignes #include <win> et using namespace Win dans le chier camera.h. Les coordonnes (u,v) du projet dun point 3D de coordonnes (x,y,z) sont : c.f * y u = c.u0 + c.x0-x v = c.v0 c.f * z . c.x0-x

B.3.3

Achage lcran

Nous sommes maintenant prts pour acher notre objet ttradre lcran. 1. Dans les chiers tetra.cpp et tetra.h, dclarer et dnir une fonction affiche prenant en argument un camera c, un tetra T. Cette fonction dessine un ttradre tetra T dans sa couleur, vu par la camera camera c. Pour cela, on utilisera la fonction projette (dclare et dnie dans camera.h et camera.cpp), ainsi que la fonction DrawLine (dnie dans la WinLib) pour tracer un segment entre deux Pixel. La dnition de la fonction DrawLine est la suivante : void DrawLine(const Pixel& p1,const Pixel& p2,const Color& col). Le projet dun ttradre doit ressembler la gure ci-dessous (gure-a) :

2. Dans le chier main.cpp, crer un ttradre rgulier et lacher lcran dans une image de taille 512 512. Pour cela, crer une camra camera C de paramtres : u0=256,v0=256, double x0=10, et double f=500.

B.3.4

Animation du ttradre

Nous cherchons maintenant animer notre modle 3D. 1. Rotation dun vect : dans vect.h et vect.cpp, ajouter une fonction rotate qui applique une rotation dangle double alpha autour de laxe Oz sur un vecteur 220

B. Examens

B.3. Examen sur machine 2005 : nonc

vect a et qui renvoie un vect b. Les fonctions cos et sin sont dnies dans #include <cmath>. b.x = cos() a.x sin() a.y b.y = sin() a.x + cos() a.y b.z = a.z 2. Rotation dun tetra : dans tetra.h et tetra.cpp, dclarer et dnir une fonction rotate qui applique une rotation dangle double alpha autour de laxe Oz sur un tetra et qui retourne un tetra. 3. Premire animation : dans main.cpp, animer le ttradre dun mouvement de rotation sur place (pour linstant sans ce soucier de leacer) : dans une boucle i for (int i=0;i<10000;i++), appliquer une rotation dangle 15 chaque pas de temps. On pourra utiliser la fonction MilliSleep(10). 4. Deuxime animation : maintenant, nous souhaitons acher et eacer notre ttradre en mouvement : (a) dans les chiers tetra.cpp et tetra.h, dclarer et dnir une fonction changeColor prenant en argument un tetra T et un Color c et retournant une structure tetra (de couleur Color c et dont les sommets possdent les mmes coordonnes que celles de tetra T), (b) ajouter une fonction pour eacer un ttradre (utiliser les fonctions dnies prcdemment !). (c) dans main.cpp, animer le ttradre dun mouvement de rotation en eaant proprement chaque pas de temps.

B.3.5

Un modle plus labor

1. Dans le chier main.cpp, crer un tableau tetra tetras[4] de 4 structures tetra. Initialiser les 4 ttradres du tableau de manire produire la gure represente ci-dessus (gure-b). Pour cela, gnrer 4 ttradres rguliers et appliquer lune des 4 translations suivantes sur chacun dentre eux : {1, 0, 0}, { 1 , 23 , 0}, { 1 , 23 , 0}, {0, 0, 2}. 2 2 Dnir pour cela une fonction translate pour gnrer le translat dun ttradre tetra T par un vecteur vect t (utiliser loprateur + de vect). 2. Finalement, nous allons animer notre objet tetra tetras[4] dun mouvement complexe et acher ce dernier dans la camra camera C. A chaque pas de temps dans la i boucle for (int i=0;i<10000;i++), appliquer une rotation dangle 15 suivie par une translation de vecteur : vect t={-12+8*cos(i/150.),8*sin(i/150.0),-3.0}.

221

B.4. Examen sur machine 2004 : nonc

B. Examens

B.4
B.4.1

Examen sur machine 2004 : nonc


Calcul de lexponentielle dun nombre complexe

Le but de cet exercice est de calculer lexponentielle dun nombre complexe et de sen servir pour calculer le sinus et le cosinus dun angle. 1. Partir dun projet "console" Win 32 Basic Console 2. Ajouter au projet deux chiers complexe.cpp et complexe.h dans lesquels vous dclarerez et dnirez lendroit appropri : (a) une structure complexe (et pas complex qui existe dj) reprsentant un nombre complexe sous forme cartsienne (partie relle, partie imaginaire) (b) les oprateurs +, * entre deux complexe (c) loprateur / dnissant la division dun complexe par un double 3. On souhaite approximer la fonction exponentielle en utilisant son dveloppement en srie entire : + i z z e = i! i=0 crire une fonction exponentielle, prenant en argument un complexe z et un int n, et qui retourne la somme :
n

i=0

zi i!

4. crire une fonction cos_sin qui renvoie le cosinus et le sinus dun angle en utilisant le dveloppement limit de ei lordre n (on passera donc, en plus de langle theta, lentier n en argument). On rappelle que : ei = cos + i sin 5. Tester la fonction cos_sin pour direntes valeurs de theta et n. Vrier quavec n = 15 et = , on obtient une bonne approximation des valeurs du cosinus 6 ( 23 0.866025404) et du sinus ( 1 ). 2

B.4.2

Compression RLE

Dans cet exercice nous allons implmenter lune des plus anciennes mthodes de compression : le codage RLE (Run Length Encoding). Le principe consiste dtecter une donne ayant un nombre dapparitions conscutives qui dpasse un seuil xe, puis remplacer cette squence par deux informations : un chire indiquant le nombre de rptitions et linformation rpter. Aussi, cette mthode remplace une squence par une autre beaucoup plus courte moyennant le respect du seuil (que nous xerons, par simplicit, 0). Elle ncessite la prsence de rptitions relativement frquentes dans linformation source compresser. Cette mthode prsente peu davantages pour la compression de chier texte. Par contre, sur une image, on rencontre rgulirement une succession de donnes de mme valeur : des pixels de mme couleur. 222

B. Examens

B.4. Examen sur machine 2004 : nonc

Sur une image monochrome (un fax par exemple), lensemble compresser est une succession de symboles dans un ensemble deux lments. Les lments peuvent tre soit des 255 (pixel allum=blanc), soit des 0 (pixel teint=noir) ; il est relativement facile de compter alternativement une succession de 0 et de 255, et de la sauvegarder telle quelle. source : 0 0 0 0 0 0 0 0 0 0 255 255 255 255 255 255 255 255 0 0 0 0 0 255 255 255 255 compression : 10 8 5 4 Cependant, une convention reste prendre : savoir par quel pixel commencera la succession de chires. Nous considrerons que le premier nombre reprsente une succession de 0. Si la source ne commence pas par des pixels noirs (un zro) il faut alors commencer la chaine codante par un 0 pour indiquer labsence de pixel noir. Dans ce qui suit nous allons encoder une image binaire (constitue de 0 et de 255 stocks dans un tableau de byte) dans un tableau de int. 1. Partir dun projet "Winlib" (Winlib5 Project). 2. Dans la fonction main, (a) Dclarer les variables de type const int w et h representant la largeur et la hauteur de limage encoder. Ces variables pourront tre initialises la valeur 256. (b) Dclarer le tableau de byte image_source de taille w h (rappel : le pixel (i, j) est llment i + j w du tableau). (c) Dclarer le tableau de byte image_decodee de taille w h (rappel : le pixel (i, j) est llment i + j w du tableau). (d) Dclarer le tableau de int image_encodee de taille w h + 1 3. Crer une fonction af f iche_image de dclaration : void affiche_image( byte image[], int w, int h ); Cette fonction ache limage stocke dans image de largeur w et de hauteur h laide de la fonction P utGreyImage de la Winlib. 4. Dans la fonction main, tester la fonction af f iche_image avec image_source, sans lavoir initialise. 5. Crer une fonction remplir_rectangle de dclaration : void remplir_rectangle( byte image[], int w, int h, int xul, int yul, int height, int width ); Cette fonction dessine un rectangle blanc dans une image en mettant 255 tous les pixels de limage contenus dans le rectangle. Limage dans laquelle est dessin le rectangle plein est donne par le tableau image et les dimensions w et h de limage. Le rectangle remplir est donn par les coordonnes xul et yul de son coin suprieur gauche ainsi que par ses dimensions height et width. 6. Nous allons maintenant tester la fonction remplir_rectangle. Pour ce faire : (a) Remplir limage avec des 0. (b) Utiliser la fonction remplir_rectangle sur cette image pour y placer un rectangle de votre choix. (c) Acher cette image laide de af f iche_image 223

B.4. Examen sur machine 2004 : nonc

B. Examens

7. Crer une fonction RLE_encode de dclaration : void RLE_encode( byte source_image[], int w, int h, int compression[], int &comp_size ); Limage compresser est donne par sa largeur w, sa hauteur h et par un tableau source_image. Le rsultat de la compression est stock dans le tableau compression et le nombre dlments utiliss dans ce tableau comp_size. (pour rappel, le tableau prvu pour recevoir limage comprime est dclare de manire statique dans main et w h + 1 nest quun majorant de la taille de limage comprime.) 8. Crer une fonction RLE_decode de dclaration : void RLE_decode( int compression[], int comp_size, byte decomp_image[] ); Limage dcompresser est donne par un tableau compression et la taille des donnes dans ce tableau comp_size. Le resultat de la dcompression est stock dans le tableau decomp_image. 9. Dans la fonction main et laide des fonctions prcdemment dnies : (a) Remplir dans le tableau image_source deux rectangles de votre choix. (b) Acher cette image (c) Encoder image_source dans image_encodee (d) Decoder image_encodee dans image_decodee (e) A laide dune boucle, vrier que les deux images image_source et image_decodee sont identiques. (f) Acher limage dcode et valider la vrication prcdente de manire visuelle. 10. Crer une fonction remplir_diagonale de dclaration : void remplir_diagonale( byte image[], int w, int h ); Cette fonction met tous les pixels (i, j) tels que i == j de image 255. 11. Tester la fonction remplir_diagonale de la mme faon que la fonction remplir_rectangle a t teste. 12. On xe w = 256 et h = 128 (a) Remplir limage source de zros. (b) Remplir dans limage source un rectangle dont le coin suprieur gauche est en (20, 20) de dimensions (80, 80) (c) Remplir dans limage source un second rectangle dont le coin suprieur gauche est en (120, 10) de dimensions (100, 100) (d) Compresser limage source et acher la taille de limage compresse comp_size (e) Remplir nouveau limage source de zros. (f) Remplir dans limage source la diagonale. (g) Compresser limage source et acher la taille de limage compresse comp_size 13. On xe w = 1024 et h = 1024 (a) Un plantage se produit. (b) Identier le problme et modier le programme pour quil fonctionne avec les valeurs de w et h donnes. 224

B. Examens

B.5. Examen sur machine 2003 : nonc

B.5
B.5.1

Examen sur machine 2003 : nonc


Crible dratosthne

Rappel : un entier naturel est dit premier sil nest divisible que par 1 et lui-mme, et quil est dirent de 1. Le but de cet exercice est de dresser par ordre croissant la liste des nombres premiers. On utilisera pour cela le crible dratosthne, qui repose essentiellement sur le fait que les diviseurs ventuels dun entier sont plus petits que lui. La mthode consiste parcourir dans lordre croissant la liste des nombres entiers candidats (par exemple initialement tous les nombres de 2 999, si lon se restreint aux entiers infrieurs 1000), et, chaque nombre rencontr, retirer de la liste des nombres candidats tous les multiples de celui-ci. Une fois la liste parcourue, il ne restera que les nombres premiers. On recherche les nombres premiers infrieurs ou gaux n, n tant un nombre entier suprieur 2, intialement x 100. Plutt que de grer une liste dentiers et den enlever des nombres, on travaillera avec un tableau de n boolens avec la convention que la ie case du tableau contiendra true si i est premier, et false sinon. 1. Partir dun projet "console" (Win 32 Basic Console). 2. Dans la fonction main, crer le tableau de boolens et le remplir avec des true. 3. Crer une fonction multiples qui prend en argument le tableau, sa taille et un entier a, et qui tourne false les lments du tableau correspondants des multiples de a (except a, bien sr). 4. Utiliser la fonction multiples de faon approprie dans la fonction main, dans une boucle parcourant le tableau, an dexcuter le crible dratosthne. Il est inutile de considrer les multiples des nombres qui ne sont pas premiers ( ce propos ne pas oublier que 1 consitue un cas particulier). 5. Acher le nombre de nombres premiers infrieurs ou gaux 211, ainsi que les nombres premiers en question. 6. Vrier que lon peut sarrter n.

B.5.2

Calcul de par la mthode de Monte Carlo

On dsigne par mthode de Monte Carlo une mthode de rsolution dun problme mathmatique laide de suites de nombres alatoires convergeant vers le rsultat, en rfrence aux nombreux casinos mongasques. Le mthode de Monte Carlo classique pour calculer une valeur approche de consiste tirer alatoirement un grand nombre n de fois des points (x, y) du plan avec 1 x 1, 1 y 1 puis de dterminer quelle est la proportion pn de ces points qui sont situs dans le disque unit. Comme la surface de ce dernier vaut , on peut montrer que limn+ pn = . Le but de cet exercice est de 4 programmer ce calcul et une visualisation graphique de celui-ci. Fonctions utiles Nombres pseudo-alatoires #include <cstdlib> using namespace std; void srand(unsigned int seed); // Initialise le gestionnaire de nombres // pseudo-alatoires

225

B.5. Examen sur machine 2003 : nonc

B. Examens

int rand(); // Retourne un nombre pseudo-alatoire entre 0 et RAND_MAX // inclus Temps #include <ctime> using namespace std; long time(...); // time(0) renvoie le nombre de secondes coules // depuis le 1er janvier 1970 WinLib #include <win> using namespace Win; void DrawPoint(int x,int y,const Color& col); // Affiche un pixel // lcran void DrawCircle(int xc,int yc,int r,const Color& col); // // // // Affiche un cercle centr en (xc,yc), de rayon r et de couleur col

1. Ajouter un nouveau projet WinLib nomm MonteCarlo la solution et le dnir cmme projet de dmarrage. 2. Dnir dans main.cpp une constante globale taille_fenetre et lutiliser dans lappel OpenWindow. Dans tout ce qui suit, la fentre sera carre, reprsentant lensemble des points dont les coordonnes sont entre -1 et 1. 3. Ajouter au projet deux chiers Point.h et Point.cpp dans lesquels vous dclarerez et dnirez lendroit appropri : (a) une structure PointPlan reprsentant des points du plan (il faut en particulier pouvoir avoir des coordonnes entre 1 et 1) (b) une fonction GenerePoint retournant un PointPlan dont les coordonnes sont tires alatoirement entre -1 et 1. Ne pas oublier quen C++, la division de deux entiers est la division entire ! (c) une fonction AchePoint, prenant en argument la taille de la fentre dachage, une couleur et un PointPlan, et qui ache ce dernier lcran dans la couleur prcise (penser renormaliser ! ). 4. Appeler la procdure dinitialisation du gestionnaire de nombre pseudo-alatoires au dbut de main avec srand(unsigned int(time(0)));. Penser aux #include et using namespace correspondants. 5. Dessiner lcran le cercle unitaire au dbut de main.cpp. 6. Dnir une constante globale nb_iterations dans main.cpp reprsentant le nombre ditrations eectues. Construire une boucle eectuant nb_iterations fois les oprations suivantes : (a) Gnrer un point pseudo-alatoire (b) Incrmenter une variable compteur si ce point est dans le disque unitaire (cest-dire si x2 +y 2 1). On rajoutera une fonction retournant le carr de la norme dun point. 226

B. Examens

B.5. Examen sur machine 2003 : nonc

(c) Acher ce point lcran, en bleu sil est dans le disque, en rouge sinon. 7. Acher la n du programme la valeur de dduite. 8. Varier nb_iterations an davoir un bon compromis temps dachage / prcision de la valeur calcule. Que constate-t-on ? Pourquoi ? (Rpondre en commentaire du programme)

B.5.3

Serpent

Figure B.2 A gauche : calcul de . A droite : un serpent. Le but de cet exercice est de programmer un serpent qui se dplace sur un terrain rectangulaire en vitant de se mordre lui-mme. Pour cela structures et tableaux seront utiles. Ne pas oublier de compiler et tester chaque tape... NB : certaines questions sont indpendantes. 1. Structure : Partir dun projet WinLib. Crer une structure point mmorisant deux coordonnes entires x et y. 2. Dessin : On va dessiner un serpent de dplaant sur un terrain de dimensions (w,h). Pour bien le visualiser, on lachera dans un fentre de dimensions (w*z,h*z) (Prendre par exemple (w,h,z)=(40,30,10)). (a) Programmer une fonction void dessine(point p,int zoom,Color c) utilisant la fonction FillRect(x,y,w,h,c) pour dessiner un carr plein de coin suprieur gauche (p.x*zoom,p.y*zoom), de ct zoom-1 et de couleur c. (b) Ecrire une fonction void dessine(point s[],int n,int zoom) dessinant un serpent constitu des points s[0] s[n-1], s[0] tant la tte. On pourra tracer le corps du serpent en rouge et sa tte en bleu (gure B.2). (c) Initialiser un serpent dans un tableau de dimension constante n=10 et lacher. 3. Divers : programmer quatre fonctions utiles pour la suite : (a) Une fonction bool operator==(point a,point b) retournant vrai si les points a et b sont gaux, ce qui permettra dcrire un test comme if (p1==p2). (b) Une fonction bool cherche(point s[],int n,point p) retournant vrai si le point p se trouve dans le tableau s de taille n. (c) Une fonction void decale(point s[],int n,point p) qui dcale le tableau s vers le haut (i.e. s[i]=s[i-1]) et range p dans s[0]. Cette fonction sera utile pour la suite ! 227

B.5. Examen sur machine 2003 : nonc

B. Examens

(d) Une fonction point operator+(point a,point b) calculant la somme de deux points (considrs comme des vecteurs). 4. Avancer dune case : Le serpent peut avancer dans une des quatre directions. On mmorise la direction de son dplacement par un entier de 0 3, avec la convention (0,1,2,3)=(est,sud,ouest,nord). (a) Crer et initialiser une variable globale const point dir[4] telle que dir[d] correspond un dplacement dans la direction d. Ainsi, d[0]={1,0}, d[1]={0,1}, etc. (b) Ecrire et tester une fonction void avance(point s[],int n,int d,int zoom) utilisant ce qui prcde pour : Avancer le serpent s,n dans la direction d. Acher sa nouvelle position sans tout re-dessiner (eacer la queue et dessiner la tte). 5. Avancer : Ecrire les quatre fonctions suivantes : bool sort(point a,int w,int h) qui retourne vrai si le point a est en dehors du terrain. void init_rand() qui initialise le gnrateur alatoire. void change_dir(int& d) qui change alatoirement d en d-1 une fois sur 20, en d+1 une fois sur 20 et ne modie pas d les 18 autres fois, ce qui, compte tenu de la convention adopte pour les directions, revient tourner droite ou gauche de temps en temps. Attention bien conserver d entre 0 et 3. bool ok_dir(point s[],int n,int d,int w,int h) qui teste si le dplacement du serpent dans la direction d ne va pas le faire sortir. puis les utiliser pour faire avancer le serpent dans une boucle de 500 pas de temps, en vriant quil ne sort pas du terrain. Penser rajouter un MilliSleep() si le dplacement est trop rapide (sur certains PC de lENPC, la WinLib ne sera pas jour et lachage sera lent et saccad... Ignorer.) 6. Ne pas se manger : modier ok_dir() pour que le serpent ne se rentre pas dans lui-mme. 7. Escargot : le serpent peut se retrouver coinc sil senroule sur lui-mme. Programmer une fonction bool coince(point s[],int n,int w,int h) qui teste si aucune direction nest possible et modier la boucle principale pour terminer le programme dans ce cas. 8. Grandir (question nale dicile) : Changer le programme pour que le tableau s soit allou dans le tas (n pourra alors tre variable). Un pas de temps sur 20, appeler, la place de avance(), une fonction void allonge() qui avance dans le direction d sans supprimer la queue. Du coup, s doit tre rallou et n augmente de 1. Ne pas oublier de dsallouer lancienne valeur de s. Bien grer lachage, etc. Cette fonction sera dclare void allonge(point*& s,int& n, int d,int zoom).

228

B. Examens

B.6. Devoir maison 2007 : nonc

B.6

Devoir maison 2007 : nonc

Enonc Erreurs et Quache ce programme ?


Dans cet exercice, on vous demande deux choses : 1. Corriger les 10 erreurs contenues dans le code du programme suivant, directement sur la feuille dnonc ; 2. Donner, en le justiant, lachage que produit ce programme sur la sortie standard (cest--dire lcran, via la commande cout << ... ;). Procdez avec mthode en faisant attention aux retours de fonction, et lquilibre ncessaire entre appels aux constructeurs/destructeurs.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37

include<iostream> namespace std; class A { int a; A(); A(int b); A(const A& b); void operator=(const A& b); void add(int b); ~A(); } void A() { cout << "C1" << endl; a=0; } void A(int b) { cout << "C2 " << b << endl; a=b; } void A(const B& b){ cout << "C3 " << b.a << endl; a=b.a; } void A::operator=(const A& b){ cout << "E " << a << " " << b.a << endl; a.a=b.a; } void A::add(int b) { cout << "A " << a << " " << b << endl; a+=b; } 229

B.6. Devoir maison 2007 : nonc

B. Examens

38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57

void F(A d,A& e) { d.add(3); e.add(4); f=d; return f; } void ~A() { cout << "D " << a << endl; } int main() { A a; A b(1); A c(b); b.add(4); c=F(b,a); }

230

B. Examens

B.7. Devoir maison 2006 : nonc

B.7
B.7.1

Devoir maison 2006 : nonc


Enonc Tours de Hanoi

Principe et rgles Le jeu est constitu de 3 tiges, A, B et C, sur lesquelles sont empils des anneaux de taille dcroissante, que lon numrotera de N 1.

Figure B.3 Conguration initiale Au dbut du jeu, tous les anneaux sont empils sur la tige A (voir gure B.3). Lobjectif est de transfrer tous les anneaux sur la tige C. Il nest possible de dplacer les anneaux que un par un, et il nest pas possible de poser un anneau sur un autre de taille infrieure. On dsignera par le terme configuration une disposition des anneaux sur les tiges (voir gure B.4).

Figure B.4 Exemple de conguration On dsignera par le terme dplacement de la tige X la tige Y le fait denlever lanneau situ au sommet de la pile de la tige X pour le positionner au sommet de la pile de la tige Y (voir gure B.5).

Objectif Lobjectif ici est dcrire un programme qui ache la suite des dplacements raliser pour rsoudre le problme avec n anneaux, sous la forme suivante : numero_anneau : tige_origine > tige_destination On souhaite de plus acher ltat du jeu (les anneaux prsents sur chaque tige) aprs chaque dplacement. Exemple pour 2 anneaux (la gure B.6 en donne une reprsentation graphique) : 231

B.7. Devoir maison 2006 : nonc

B. Examens

Figure B.5 Exemple de dplacement A> 2 1 B> . . C> . . deplacement: 1 : A->B A> 2 . B> 1 . C> . . deplacement: 2 : A->C A> . . B> 1 . C> 2 . deplacement: 1 : B->C A> . . B> . . C> 2 1

Figure B.6 Solution du problme pour N=2 Ce problme apparemment complexe se traite en fait simplement : pour transfrer n 232

B. Examens

B.7. Devoir maison 2006 : nonc

anneaux dune tige X une tige Y, il "sut" deectuer : le transfert des n-1 anneaux suprieurs de la tige X sur la troisime tige Z le dplacement de lanneau n de la tige X la tige destination Y le transfert des n-1 anneaux de la tige Z la tige Y Pour transfrer n-1 anneaux, on applique la mme mthode : transfert de n-2 anneaux de la tige origine une tige temporaire, dplacement dun anneau de la tige origine la tige destination, transfert des n-2 anneaux de la tige temporaire la tige destination ... Questions 1. Dclarer une constante N, que lon xera 10, et qui reprsentera le nombre danneaux du problme rsoudre. 2. Crer un objet capable de stocker une conguration. On pourra par exemple reprsenter chaque tige par un tableau de taille N, et chaque anneau par un entier entre 1 et N. Vous utiliserez au choix une structure ou une classe, selon ce qui vous semble le plus pertinent, ou dfaut ce avec quoi vous tes le plus laise. 3. Programmer de quoi initialiser une conguration, cest dire lui donner sa valeur de dpart (tous les anneaux sur la premire tige). 4. Programmer une fonction permettant dacher une conguration lcran, par exemple sous la forme utilise ci-dessus pour lexemple pour N=2. 5. Programmer une fonction ralisant un dplacement : pour une tige origine et une tige destination spcies, elle prlve le pion suprieur sur la tige origine et le place sur la tige destination. 6. En vous basant sur la mthode propose, programmer une fonction ralisant un transfert de k anneaux dune tige une autre, et achant au fur et mesure les dplacements eectus, et ltat du jeu aprs chaque dplacement. 7. En utilisant les lments prcdents, crire une fonction main() qui rsout le problme des tours de Hanoi de taille N et ache la solution complte. Questions complmentaires 1. Si lon avait souhait nacher que la liste des dplacements eectuer sans acher chaque fois les congurations intermdaires, le programme aurait t beaucoup plus simple. Pourquoi ? Ecrire la fonction correspondante (5 lignes de code environ). 2. Pour rsoudre le problme avec N anneaux, combien de dplacements sont ncessaires ?

233

B.8. Devoir maison 2004 : nonc

B. Examens

B.8
B.8.1

Devoir maison 2004 : nonc


Tableau dexcution

Pour ce premier exercice, il sagit dexcuter pas pas le programme suivant :

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

#include <iostream> using namespace std; int hop(int x) { x = x+2; return x; } int hip(int& y) { y = y*2; return y+1; } int f(int& z) { int t = z+3; z=2*t; if (z>20) return z; return f(t); } int main() { int a; a = hop(1); int b; b = hip(a); a = f(a); return 0; }

Pour cela, remplissez le tableau ci-dessous, en crivant, si elles existent, les valeurs des variables du programme pour chaque excution de ligne (selon lexemple du cours).

234

B. Examens

B.8. Devoir maison 2004 : nonc

Conventions : mettre le numro de la ligne quon vient dexcuter (ventuellement, on peut dcouper lexcution dune mme ligne en plusieurs tapes) on peut utiliser le symbole pour rpter la valeur dune variable la ligne suivante une case vide signie que la variable nexiste plus ou pas encore pour une variable qui nest autre quune rfrence sur une autre variable x, on indiquera [x] ; sil y a rcursivit, indicer par un entier plusieurs appels imbriqus une mme fonction (f1 , f2 , ...)
Ligne am bm xhop rethop yhip rethip zf1 tf 1 retf1 zf2 tf 2 retf2

B.8.2

Constructeurs

Dans ce deuxime exercice, on vous demande tout simplement de donner et justier lachage que produit lexecution du programme suivant sur la sortie standard (cest-dire lcran, via la commande cout<<...) :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25

#include<iostream> using namespace std; class Vecteur { int tab[2]; void affiche() const; public: Vecteur(); Vecteur(int n); Vecteur(const Vecteur &right); ~Vecteur(); void operator=(const Vecteur& right); Vecteur operator+(const Vecteur& right) const; int operator*(const Vecteur right) const; int& getX(int i); }; Vecteur::Vecteur() { for(int i=0;i<2;i++) tab[i]=0; cout<<"Contructeur par defaut: "; affiche(); } Vecteur::Vecteur(int x) 235

B.8. Devoir maison 2004 : nonc

B. Examens

26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74

{ for(int i=0;i<2;i++) tab[i]=x; cout<<"Constructeur de remplissage: "; affiche(); } Vecteur::Vecteur(const Vecteur &right) { for(int i=0;i<2;i++) tab[i]=right.tab[i]; cout<<"Constructeur par copie: "; affiche(); } Vecteur::~Vecteur() { cout<<"Destructeur: "; affiche(); } void Vecteur::operator=(const Vecteur& right) { for(int i=0;i<2;i++) tab[i]=right.tab[i]; cout<<"Operateur de copie: "; affiche(); } Vecteur Vecteur::operator+(const Vecteur& right) const { Vecteur Res; for(int i=0;i<2;i++) Res.tab[i]=tab[i]+right.tab[i]; return Res; } int Vecteur::operator*(const Vecteur right) const { int Res=0; for(int i=0;i<2;i++) Res+=tab[i]*right.tab[i]; return Res; } int& Vecteur::getX(int i){ return tab[i]; } void Vecteur::affiche() const{ for (int i=0;i<2;i++) cout<<tab[i]<<" "; cout<<endl; } void affiche(Vecteur vec){ cout<<"Affiche: "; for (int i=0;i<2;i++) cout<<vec.getX(i)<<" "; cout<<endl; } int main() { Vecteur A; cout<<endl; 236

B. Examens

B.8. Devoir maison 2004 : nonc

75 76 77 78 79 80 81 82 83 84 85 86 87

Vecteur B(-2); cout<<endl; Vecteur C=B; cout<<endl; B.getX(1)=3; affiche(C); cout<<endl; cout<<"Produit scalaire: "<<B*C<<endl; cout<<endl; A=B+C; cout<<endl; return 0; }

B.8.3

Le compte est bon

Le but de cet exercice va tre dcrire un programme capable de rsoudre le problme du Compte est bon dont voici une description : 6 plaques sont tires au hasard parmi 14 plaques direntes dont chacune peut tre choisi de 0 2 fois. Les 14 plaques comportent les nombres : 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 25, 50, 75, 100. Un nombre entre 100 et 999 (inclus) est tir au hasard, on lappelera par la suite le total. Une des 4 oprations arithmtiques (addition, soustraction, division entire, multiplication) est utilise sur deux des 6 plaques pour obtenir un nouveau nombre (attention : la division entire ne peut tre utilise que si le dividande est divisible par le diviseur ; la soustraction ne peut tre utilise que si le premier terme est plus grand que le second). Il nous reste alors 5 nombres (les 4 plaques non choisies et le nouveau nombre) partir desquels recommencer. Le but est dobtenir nalement le total, en utilisant tout ou partie des 6 plaques ainsi que dcrit prcdemment. Par exemple, on tire le total 987 et les plaques suivantes : 1 50 9 1 50 7 Une solution sera par exemple : 1+1=2 2 50 = 100 100 9 = 91 91 + 50 = 141 141 7 = 987 Il nexiste pas toujours de solution. Dans ce cas, le candidat doit trouver une solution approchant au plus prs le total. Pour simplier, le programme que vous crirez cherchera uniquement des solutions exactes. La manire dont trouver une solution est trs simple : le programme va simplement munrer toutes les possibilits de combiner les nombres crits sur les plaques et les 237

B.8. Devoir maison 2004 : nonc

B. Examens

oprateurs arithmtiques, jusqu tomber sur une bonne solution ou les avoir explores toutes. On utilisera dans le programme partout o cela est possible les constantes globales suivantes : const int total_min=100; const int total_max=999; const int const int const int { 1, 2, nb_plaques=6; nb_plaques_possibles=14; plaques_possibles[nb_plaques_possibles]= 3, 4, 5, 6, 7, 8, 9, 10, 25, 50, 75, 100};

const int nb_operateurs=4; const char operateurs[nb_operateurs]={+,-,/,*}; 1. Dnir une fonction de prototype int random(int a,int b) renvoyant un nombre alatoire entre a et b (inclus). Prciser les #include et using namespace ncessaires. 2. Dnir une fonction int genere_total() renvoyant un nombre alatoire entre total_min et total_max 3. Dnir une fonction void affiche_plaques(int tab[nb_plaques]) que lon utilisera ultrieurement pour acher lcran les plaques tires au hasard. Que faut-il rajouter comme #include ? 4. Dnir une fonction void genere_plaques(int tab[nb_plaques]) eectuant le tirage au hasard des plaques. An de se souvenir du nombre de fois o on a tir chaque plaque et dimposer den tirer au plus 2, on pourra utiliser une variable int plaque_deja_tiree[nb_plaques_possibles]. 5. Dnir la fonction main de votre programme qui initialisera le gestionnaire de nombre alatoires, eectuera le tirage du total et des plaques, et achera le tout lcran. Que faut-il rajouter comme #include ? 6. Nous allons maintenant nous attaquer au cur du problme, la fonction bool trouve_combinaison (int plaques[], int n, int total) qui, tant donne un tableau de plaques plaques de taille n et un total total : Renvoie true et ache lcran une solution si elle existe. Renvoie false sinon. trouve_combinaison sera programme rcursivement suivant le nombre de plaques n: Si n==1, le problme est trivial Sinon, on eectue lopration plaques[i] op plaques[j] pour tous les i et j distincts entre 0 et n et tous les oprateurs arithmtiques op, si elle est possible. Ensuite : Si on tombe sur le nombre total, on ache lopration qui nous a permis dy accder et on renvoie true. Sinon, on appelle rcursivement la fonction bool trouve_combinaison sur un nouveau tableau de plaques. Si cet appel rcursif renvoie true, cest quil est possible dobtenir une solution partir de cette tape intermdiaire. On renvoie alors galement true et on ache lopration qui nous a permir daccder cette tape intermdiaire. Sinon, on continue itrer. 238

B. Examens

B.8. Devoir maison 2004 : nonc

7. Ajouter votre fonction main lutilisation de la fonction trouve_combinaison. Quand aucune combinaison nest trouve, le signaler en achant un message lcran. 8. Le programme que vous avez crit ne sait pas trouver de solution approche, ne retient quune seule solution (pas forcment la plus courte) et lache en ordre inverse. Que faudrait-il faire (en franais, sans essayer de le programmer) pour rsoudre ces limitations ?

239

B.9. Devoir maison 2003 : nonc

B. Examens

B.9
B.9.1

Devoir maison 2003 : nonc


Tableau dexcution

Pour ce premier exercice, il sagit dexcuter pas pas le programme suivant :

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

int hop(int x) { x = x/2; return x; } int hip(double& y) { y = y/2; return (int(y)); } int test(double z) { z = 2*hip(z) - z; if (z>4) z = test(z); return (int(z)); } int main() { double a = 3.14; int b = hop(int(a)); b = hip(a); a = 18; do { a = test(a); } while (a != 0); return 0; }

Pour cela, remplissez le tableau ci-dessous, en crivant, si elles existent, les valeurs des variables du programme pour chaque excution de ligne (selon lexemple du cours).

240

B. Examens

B.9. Devoir maison 2003 : nonc

Conventions : mettre le numro de la ligne quon vient dexcuter (ventuellement, on peut dcouper lexcution dune mme ligne en plusieurs tapes) on peut utiliser le symbole pour rpter la valeur dune variable la ligne suivante pour une variable qui nest autre quune rfrence sur une autre variable x, on indiquera [x] ;

Ligne

amain

bmain

retmain

xhop

rethop

xhip

rethop

xtest(1)

rettest(1)

xtest(2)

rettest(2)

B.9.2

Grands entiers

Le but de cet exercice est de manipuler des entiers arbitrairement grands. Un entier long sera un entier compris entre 0 et 104n 1, o n est une constante xe une fois pour toutes, par exemple n = 30. On dcoupera les grands entiers comme si on les considrait crits en base 104 , cest--dire sous la forme dun tableau de n int. 1. Crer une classe entier comprenant un tableau dentiers de taille n. 2. Le tableau dun entier contient la reprsentation de celui-ci en base 104 , la premire case contenant llment de poids le plus faible. Par exemple 15 439 573 458 se codera sous la forme 3458 3957 154. En consquent, on a besoin de connatre lindice de llment de poids le plus fort an de savoir o arrter la lecture du tableau. Ajouter la classe un champ de type int servant stocker lindice en question. 3. Ajouter cette classe un constructeur vide. 4. Dnir un constructeur prenant en argument un int positif crant un entier de mme valeur. 5. Ajouter la classe une mthode achant lcran un entier, dans lordre standard pour la lecture. 6. Ajouter loprateur daddition (attention aux retenues !). 7. Ajouter loprateur de multiplication. On pourra procder en 3 temps : crer une fonction multipliant un entier par un int (< 104 ), une autre multipliant un entier par 104 , et utiliser de faon adquate ces deux fonctions ainsi que laddition prcdemment dnie. Ou alors on pourra faire mieux (ce qui nest pas bien dicile, il sut deectuer une multiplication la main pour entrevoir dirents algorithmes). 8. Calculer 50! et lacher.

B.9.3

Constructeurs

Examinez bien le contenu du programme suivant. La question est simple : quelles sont les direntes lignes aches sur la sortie standard (cest--dire en console, via les instructions cout << ... ;) au cours de son excution ? Justier vos rponses. 241

B.9. Devoir maison 2003 : nonc

B. Examens

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

#include <iostream> using namespace std; class foo { int x; public: // Constructeurs foo(); foo(const foo &f); foo(int i); // Destructeur ~foo(); // Oprateurs foo operator+(const foo &f) const; void operator=(const foo &f); // Accesseur int getx() const; }; foo::foo() { x=0; cout << "constr vide " << endl; } foo::foo(int i) { x=i; cout << "constr int " << x << endl; } foo::foo(const foo &f) { x=f.x; cout << "constr copie " << x << endl; } foo::~foo() { cout << "dest " << x << endl; } foo foo::operator+(const foo &f) const { cout << "somme " << x << " " << f.x << endl; 242

B. Examens

B.9. Devoir maison 2003 : nonc

50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84

foo somme(x+f.x); return somme; } void foo::operator=(const foo &f) { x=f.x; cout << "affecte " << x << endl; } int foo::getx() const { return x; } void affiche1(foo f) { cout << f.getx() << endl; } void affiche2(const foo &f) { cout << f.getx() << endl; } int main() { foo a; foo b=1; foo c(2); affiche1(b); affiche2(c); a=b+c; return 0; }

243

B.10. Devoir surveill 2007 : nonc

B. Examens

B.10

Devoir surveill 2007 : nonc

Enonc B.11 Groupes

Le programme suivant ache ==== Tous ==== Aarabi Sarah : 21 ans Adamjy Inssia : 20 ans Arlaud Jules : 22 ans Aroles Jeremy : 22 ans Bairi Iken : 23 ans Baumhauer Oriane : 23 ans Begon-Lours Clotilde : 22 ans Bellakrid Amine : 24 ans Benchimol Mike : 20 ans Kharoub Amine : 25 ans ========== Ont 22 ans ========== Arlaud Jules : 22 ans Aroles Jeremy : 22 ans Begon-Lours Clotilde : 22 ans ========== Se prenomment Amine ========== Bellakrid Amine : 24 ans Kharoub Amine : 25 ans (NB : les ges ne sont pas exacts !). Il manque les mthodes des objets. Dnissez-les !
1 2 3 4 5 6 7 8 9 10 11 12

#include <string> #include<iostream> using namespace std; //==================================== class personne { string N,P; int A; public: personne(); personne(string n,string P,int a); string nom() const; 244

B. Examens

B.11. Groupes

13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61

string prnom() const; int age() const; }; //======================================== class groupe { personne* t; int n; public: groupe(); groupe(const groupe& g); void operator=(const groupe& g); ~groupe(); void ajouter(personne p); void affiche() const; groupe agsDe(int a) const; groupe prnomms(string p) const; }; //============================================================= ............ ici, les mthodes de personne et de groupe! //============================================= int main() { groupe g; g.ajouter(personne("Aarabi","Sarah",21)); g.ajouter(personne("Adamjy","Inssia",20)); g.ajouter(personne("Arlaud","Jules",22)); g.ajouter(personne("Aroles","Jeremy",22)); g.ajouter(personne("Bairi","Iken",23)); g.ajouter(personne("Baumhauer","Oriane",23)); g.ajouter(personne("Begon-Lours","Clotilde",22)); g.ajouter(personne("Bellakrid","Amine",24)); g.ajouter(personne("Benchimol","Mike",20)); g.ajouter(personne("Kharoub","Amine",25)); cout << "====" << endl << "Tous" << endl << "====" << endl; g.affiche(); groupe g2; g2=g.agsDe(22); cout << "==========" << endl << "Ont 22 ans" << endl << "==========" << endl; g2.affiche(); cout << "==========" << endl << "Se prenomment Amine" << endl << "==========" g.prnomms("Amine").affiche();

245

B.12. Anniversaires

B. Examens

62 63

return 0; }

B.12

Anniversaires

Il est connu que sur une classe de 23 lves, il y a une chance sur 2 pour que deux lves au moins soient ns le mme jour. crire un programme qui tire au hasard le jour de naissance de N personnes, qui vrie sil en existe au moins deux nes le mme jour, et qui recommence un grand nombre de fois pour estimer la probabilit que ce phnomne se produise. Modier le programme pour estimer cette probabilit pour N allant de 2 30. Voici, pour information, ce quon trouve : 2 eleves. Frequence: 0.00272 3 eleves. Frequence: 0.008289 4 eleves. Frequence: 0.016509 5 eleves. Frequence: 0.026964 6 eleves. Frequence: 0.040578 7 eleves. Frequence: 0.0559 8 eleves. Frequence: 0.074285 9 eleves. Frequence: 0.094771 10 eleves. Frequence: 0.117261 11 eleves. Frequence: 0.141013 12 eleves. Frequence: 0.167189 13 eleves. Frequence: 0.195052 14 eleves. Frequence: 0.222897 15 eleves. Frequence: 0.253055 16 eleves. Frequence: 0.28374 17 eleves. Frequence: 0.314569 18 eleves. Frequence: 0.346247 19 eleves. Frequence: 0.379274 20 eleves. Frequence: 0.411193 21 eleves. Frequence: 0.443089 22 eleves. Frequence: 0.476296 23 eleves. Frequence: 0.508387 24 eleves. Frequence: 0.536925 25 eleves. Frequence: 0.568921 26 eleves. Frequence: 0.598471 27 eleves. Frequence: 0.626731 28 eleves. Frequence: 0.653526 29 eleves. Frequence: 0.68141 30 eleves. Frequence: 0.705967

246

B. Examens

B.13. Devoir surveill 2006 : nonc

B.13
B.13.1
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

Devoir surveill 2006 : nonc


Erreurs corriger

Corrigez les erreurs dans le programme suivant. Cherchez bien : il y en a 23 int dmax=10; class polynom { double coef[dmax]; int deg; void updateDegree() const; public: polynom() { deg=0; coef[0]=0; } void set(int d,double *c); void print() const; polynom derivate() const; polynom integrate() const; void op-(const polynom& Q) const; } void polynom::set(int d,double *c) { deg=d; for (int i=1;i<=d+1;i++) coef[i]=c[i]; } void polynom::print() const { for (int i=deg;i>=0;i++) { if (i<deg & coef[i]>=0) cout<<+; cout << coef[i] << "*x^"<<i; } } polynom derivate(){ polynom D; if (deg>0) { D.deg=deg-1; for (int i=0,i<deg,i++) D.coef[i]=(i+1)*coef[i+1]; } } polynom polynom::integrate(){ polynom I; I.deg=deg+1; for (int i=1;i<=deg+1;i++) 247

B.13. Devoir surveill 2006 : nonc

B. Examens

45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81

I.coef[i]=coef[i-1]/i; return I; } int polynom::updateDegree() { for (int i=deg;i>=1;i--) if (coef[i]==0) deg=i-1; } polynom polynom::op-(const polynom& Q) const { polynom P; P.deg=max(deg,Q.deg); for (int i=0;i<=P.deg;i++) { double c1,c2; if (i>deg) c1=0 else c1=coef[i]; if (i>Q.deg) c2=0 else c2=Q.coef[i]; P.coef[i]=c1-c2; } P.updateDegree; return P; } int main() { polynom P; int t[4]={5,2,3,4}; P.set(3,t); polynom Q; Q=P.derivate(); polynom R; R=Q.integrate(); S=P-R; cout << "P=" ; P.print; cout << "Q=" ; Q.print; cout << "R=" ; R.print; cout << "S=" ; S.print; }

cout cout cout cout

<< << << <<

endl; endl; endl; endl;

B.13.2

Quache ce programme ?

Examinez bien le contenu du programme suivant. La question est simple : quelles sont les direntes lignes aches sur la sortie standard (cest--dire en console, via les instructions cout << ... ;) au cours de son excution ? Procdez avec mthode en faisant attention aux retours de fonction par valeurs, et lquilibre ncessaire entre appels aux constructeurs/destructeurs. Justiez vos rponses.
1 2 3

#include <iostream> #include <cmath> using namespace std; 248

B. Examens

B.13. Devoir surveill 2006 : nonc

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 51 52

int number_of_points; class Point3{ int which_point; int X,Y,Z; void init(int _x=0, int _y=0, int _z=0){ which_point = number_of_points++; X=_x; Y=_y; Z=_z; } void print(){ cout << " Point #" << which_point ; cout << " [ " << X << ", " << Y << ", " << Z << "]" << endl; } public: Point3(){ cout << "Constructor #0: init(); print(); } Point3(int _x, int _y, int _z){ cout << "Constructor #1: init(_x,_y,_z); print(); } Point3(const Point3 &pt){ cout << "Constructor #2: init(pt.X,pt.Y,pt.Z); print(); } ~Point3(){ cout << "Destructor: print(); number_of_points--; }

" ;

";

";

" ;

Point3 operator+(Point3 pt){ cout << "Dans operator+ :" ; print(); Point3 tmp(X + pt.X , Y + pt.Y , Z + pt.Z); cout << "Fin operator+ " <<endl; return tmp; } const Point3& operator=(const Point3 &pt){ cout << "Dans operator= :" ; print(); X=pt.X; Y=pt.Y; Z=pt.Z; cout << "Fin operator= :" ; print(); 249

B.13. Devoir surveill 2006 : nonc

B. Examens

53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69

return pt; } }; int main() { number_of_points = 0 ; cout << number_of_points Point3 PTS[2]; cout << number_of_points PTS[0] = Point3(1,2,3); cout << number_of_points Point3 PT(3,2,1); PTS[1] = PTS[0] + PT; cout << number_of_points return 0; }

<< endl; << endl; << endl;

<< endl;

B.13.3

Tableau dexcution

Remplir le tableau dexcution du programme suivant : le tableau dexcution est fourni en annexe.
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

#include <iostream> using namespace std; void initClasse(int * classe , bool * bienHabilles, int nbEleves) { int identifiant_eleve; for ( int i = 0 ; i < nbEleves ; i++ ) { identifiant_eleve = i*2; classe[i] = identifiant_eleve; if( i%2 == 0 ) bienHabilles[i] = false; else bienHabilles[i] = true; } } bool tousBienHabilles(bool * bienHabilles, int nbEleves) { bool ok = true; for ( int i = 0 ; i < nbEleves ; i++ ) { if( !bienHabilles[i] ) return false; } 250

B. Examens

B.13. Devoir surveill 2006 : nonc

27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68

nbEleves = 2; return ok; nbEleves = 1; } void PrendreLaPhoto(int & nbEleves) { nbEleves--; int &n = nbEleves; for (int i = 0 ; i < nbEleves ; i++ ) cout << "Eleve " << i << " dit : cheese" << endl; cout << "Photographe dit : souriez ... Click Clack, merci ... " << endl; n++ ; }

int main() { int nbEleves = 2; int *classe = new int[nbEleves]; int *copieDeClasse = classe; bool * bienHabilles = new bool [nbEleves]; initClasse(classe,bienHabilles,nbEleves); for (int i = 0 ; i < nbEleves ; i++ ) copieDeClasse[i] =classe[i] ; bienHabilles[0] = true; if( tousBienHabilles(bienHabilles,nbEleves ) ) PrendreLaPhoto(nbEleves); delete[] bienHabilles; delete[] classe; return 0; } Il sagit dcrire, si elles existent, les valeurs des variables du programme pour chaque excution de ligne. Conventions : mettre le numro de la ligne quon vient dexcuter (ventuellement, on peut dcouper lexcution dune mme ligne en plusieurs tapes) ; 251

B.13. Devoir surveill 2006 : nonc

B. Examens

laisser en blanc les variables non existantes ; mettre un point dinterrogation pour les variables dclares non initialises ; pour une variable qui nest autre quune rfrence sur une autre variable x, on indiquera [x] ; mme chose si deux pointeurs adressent le mme espace m moire on peut utiliser le symbole pour rpter la valeur dune variable la ligne suivante ; pour gagner de la place dans le tableau, les noms des variables nont t indexs que par la premire lettre du nom de la fonction laquelle elles sont rattaches, suivie le cas chant du numro dappel la fonction. De mme, le retour dune fonction f a pour nom retf . dans le cas dun tableau, on indiquera les valeurs prises dans tout le tableau dans une seule cellule du tableau dexcution. Par exemple, si on a int tab[2] = {4,6} ;, on indiquera le contenu de tab par : 4,6 dans la cellule correspondante du tableau dexcution. dans le cas des boucles for, il faudra mettre en vidence chaque passage. Ne pas oublier les variables compteurs

B.13.4

Huit dames

Figure B.7 Une solution du problme des huit dames Le but de cet exercice est dcrire un programme capable de gnrer toutes les solutions du problme des 8 dames. Il sagit de poser huit dames dun jeu dchecs sur un chiquier de 88 cases sans que les dames ne puissent se menacer mutuellement, conformment aux rgles du jeu dchecs. Par consquent, deux dames ne doivent jamais partager la mme range, colonne, ou diagonale. Une solution possible est reprsente gure B.7. On demande de fournir les programmes correspondant trois solutions successives. Programmez bien de faon progressive, en dcoupant en fonctions, etc. Mme sil vaut habituellement 8, mettez le nombre N de dames (et donc la taille de lchiquier en paramtre constant au dbut du programme). 1. Solution 1 : Une conguration, cest--dire les positions des huit dames, est mmorise sous forme dun tableau de coordonnes. crire une fonction qui ache une conguration. crire une fonction rcursive qui gnre toutes les congurations possibles (sans tenir compte des menaces !) crire une fonction qui vrie pour une conguration donne quil sagit dune solution, cest--dire que les dames ne se menacent pas. 252

B. Examens

B.13. Devoir surveill 2006 : nonc

Complter la fonction de gnration pour acher les solutions. Combien explore ton de congurations ? Est-ce beaucoup pour un ordinateur quand N = 8 ?

2. Solution 2 :

An de limiter le nombre de congurations explores, modier la solution prcdente pour vrier, au moment de placer un dame, quelle nest pas en prise avec celles qui sont dj poses.

3. Solution 3 :

Protant du fait quil y a exactement une dame par colonne, changer la faon de reprsenter une solution. Changer en consquence la faon de gnrer les congurations. Combien y-a-til maintenant de congurations possibles ? Pour acclrer les tests, il est astucieux de mmoriser pour chaque ligne et chaque diagonale si celle-ci est dj "occupe" par une dame prcdemment pose. Mettre en oeuvre cette solution.

4. NB : les meilleures solutions algorithmiques actuelles arrivent rsoudre le problme avec N = 1000000 en trs peu dtapes !

253

B.14. Devoir surveill 2005 : nonc

B. Examens

B.14
B.14.1

Devoir surveill 2005 : nonc


Erreurs corriger

Corriger le code fourni en annexe : eectuer les corrections directement sur le script. Conseil : il y a une vingtaine derreurs.

B.14.2

Quache ce programme ?

Examinez bien le contenu du programme suivant. La question est simple : quelles sont les direntes lignes aches sur la sortie standard (cest--dire en console, via les instructions cout << ... ;) au cours de son excution ? Procdez avec mthode en faisant attention aux retours de fonction par valeurs, et lquilibre ncessaire entre appels aux constructeurs/destructeurs. Justiez vos rponses.
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

#include <iostream> #include <cmath> using namespace std; class Complexe{ double m_Re, m_Im; public: Complexe(); Complexe(double re, double im); Complexe(const Complexe & c); ~Complexe(); void operator= (const Complexe & c); const Complexe operator* (const Complexe & c) const; void conjugue(); double norme() const; void print() const; }; Complexe::Complexe(){ cout << "Constructeur : vide" << endl; } Complexe::Complexe(double re, double im){ m_Re = re; m_Im = im; cout << "Constructeur : "; print(); } Complexe::Complexe(const Complexe & c){ m_Re = c.m_Re; m_Im = c.m_Im; cout << "Constructeur Copie : "; print(); }

254

B. Examens

B.14. Devoir surveill 2005 : nonc

35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83

Complexe::~Complexe(){ cout << "Destructeur : "; print(); } void Complexe::operator= (const Complexe & c){ cout << "dans op= : " ; m_Re = c.m_Re; m_Im = c.m_Im; print(); } const Complexe Complexe::operator* (const Complexe & c) const{ cout << "dans op* : " << endl; Complexe result(c.m_Re*m_Re-c.m_Im*m_Im,c.m_Re*m_Im+c.m_Im*m_Re); cout << "fin op*." << endl; return( result ); } void Complexe::conjugue (){ cout << "dans conjugue : "; m_Im = -m_Im; print(); } double Complexe::norme() const{ cout << "dans norme : " << endl; Complexe c(m_Re,m_Im); c.conjugue(); c = operator*(c); cout << "fin norme. " << endl; return( sqrt(c.m_Re) ); } void Complexe::print() const{ cout << m_Re << " + I * " << m_Im << endl; } int main(){ cout << "1" << endl; Complexe c1(3,4); Complexe c2(0,1); cout << "2" << endl; Complexe c3 = c1; cout << "3" << endl; c2 = (c1*c2); cout << "4" << endl; double d = c1.norme(); cout << "norme de c1 : " << d << endl; cout << "5" << endl; 255

B.14. Devoir surveill 2005 : nonc

B. Examens

84 85

return 0; }

B.14.3

Tableau dexcution

Remplir le tableau dexcution du programme suivant : le tableau dexcution est fourni en annexe.
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

int U(int &n) { n += 1; return V(n)-5; } int V(int n) { if (n == 4 || n == 16) return -1; int tmp; n = n+2; tmp = U(n); return tmp + n; } int setValueToTwelve(int a) { a = 12; return a; } int main() { int a = 0 ; int fin; fin = setValueToTwelve(a); fin = U(a); if(fin-7) a = 12; else if(fin+7) a = 0; else a = 4; return 0; } 256

B. Examens

B.14. Devoir surveill 2005 : nonc

Il sagit dcrire, si elles existent, les valeurs des variables du programme pour chaque excution de ligne. Conventions : mettre le numro de la ligne quon vient dexcuter (ventuellement, on peut dcouper lexcution dune mme ligne en plusieurs tapes) ; laisser en blanc les variables non existantes ; mettre un point dinterrogation pour les variables dclares non initialises ; pour une variable qui nest autre quune rfrence sur une autre variable x, on indiquera [x] ; on peut utiliser le symbole pour rpter la valeur dune variable la ligne suivante ; pour gagner de la place dans le tableau, les noms des variables nont t indexs que par la premire lettre du nom de la fonction laquelle elles sont rattaches, suivie le cas chant du numro dappel la fonction. De mme, le retour de la fonction f a pour nom retf .

B.14.4

Rsolveur de Sudoku

Le but de cet exercice est dcrire un programme capable de rsoudre le problme du Sudoku dont on rappelle les rgles : une grille 9 9 partiellement remplie est donne, chaque case de cette grille doit contenir un entier de 1 9 il faut complter la grille en respectant les trois rgles suivantes : dans une mme ligne, ne peuvent apparatre quune seule fois les entiers de 1 9, dans une mme colonne, ne peuvent apparatre quune seule fois les entiers de 1 9, dans une mme rgion, ne peuvent apparatre quune seule fois les entiers de 1 9 : une rgion est lun des neuf blocs 3 3 qui constituent la grille. Dans la suite du problme, on considre la structure suivante :
1 2 3 4

struct Grille { int valeur[9][9]; bool indice[9][9]; };

qui indique, pour chacune des 81 cases de la grille, sa valeur (un entier entre 1 et 9 ou bien 0 si la case na pas de valeur) et si la case en question fait partie des indices (sa valeur est xe). 1. Dnir une fonction de prototype void affiche_grille(const Grille& g) qui ache une grille (avec cout comme droite de Fig. B.8 ) : si, en une case de la grille, il ny a pas encore de valeur aecte, un caractre X est ach la place. 2. crire une fonction Grille lit_grille(string l[9]) qui lit une grille en entre (un caractre aprs lautre, ligne par ligne). Chaque string du tableau pass en paramtre reprsente une ligne de la grille. Si s est lune de ces lignes, alors : s[i] est le i-me caractre de la ligne, si s[i] est gal X alors la case est complter, sinon, il sagit dun indice de valeur s[i] - 0. 257

B.14. Devoir surveill 2005 : nonc

B. Examens

4 6

3 8 1 5 8 7 2 1 2 7 4 3 6 9

4 X X X X X X X X

X 3 X 6 X X X X X 6 X X X X X 2 X X 1 X 2 X X X X X X

X X X 8 X X X X 1 X 9 X 6 X X X X X 7 X X X 4 X X X X

X X X X 8 X X 7 X X X X 5 X 3 9 X X

5 9

Figure B.8 Exemple de grille

4 7 3 1 2 6 8 5 9

6 5 9 3 8 7 4 1 2

8 1 2 4 9 5 6 3 7

9 6 5 7 4 2 1 8 3

3 2 7 5 1 8 9 6 4

1 4 8 6 3 9 2 7 5

5 8 4 2 6 3 7 9 1

2 3 6 9 7 1 5 4 8

7 9 1 8 5 4 3 2 6

Figure B.9 Solution correspondante

258

B. Examens

B.14. Devoir surveill 2005 : nonc

3. Programmer une fonction bool verifie_case(const Grille& g, int i, int j) qui indique si la valeur actuellement attribue la case (i, j) entre en conit avec les autres cases valides de la ligne i, de la colonne j et de la rgion dans laquelle se trouve la case. 4. Dnir une fonction de prototype bool case_suivante(const Grille& g, int& i, int& j) qui passe dune case (i, j) dans la grille la suivante dans lordre lexigraphique (la nouvelle case sera renvoye par rfrence) : les cases dindice sont sautes, et, si il ny a pas de case suivante ((i, j) est la dernire case de la grille), alors false est renvoy. 5. Programmer une fonction rcursive bool resout_grille(Grille& g, int i, int j), qui va : donner successivement la case (i, j) toutes les valeurs possibles, tester chaque fois si la valeur entre en conit ou non avec le reste de la grille dj remplie, sil ny a pas de conit, sappeler rcursivement sur la case suivante (uniquement si la n de la grille na pas dj t atteinte). Cette fonction renvoie true si la grille est soluble partir de la conguration donne, et false sinon. Quel est lintrt de passer la structure Grille par rfrence ? 6. Dnir une fonction de prototype bool resout_grille(Grille& g) qui appelle la fonction prcdente avec la bonne position de dpart pour la rsolution. 7. Programmer la fonction int main() qui lit une grille ( dnir dans le code), lafche, puis essaie de la rsoudre en achant la solution sil y en a une ou bien un message. 8. Lalgorithme propos ralise beaucoup de calculs inutiles et nexploite pas susamment les indices. Suggrer des amliorations (ne pas les programmer !).

259

B.15. Devoir surveill 2004 : nonc

B. Examens

B.15
B.15.1

Devoir surveill 2004 : nonc


Erreurs

Corriger le code suivant : (eectuer les corrections directement sur le script en annexe )
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

// fichier main.cpp #include <win> const int w; const int h; struct point { int x,y; } point operator+(point p1, point p2) { point p p.x = p1.x+p2.x p.y = p1.y+p2.y } class quadrilatere { point t[4]; quadrilatere(point pts[4]); affiche(Color col=Black) const; void translate(point v) const; bool dans_la_fenetre(); } quadrilatere::quadrilatere(point pts[4]) { t=pts; } quadrilatere::affiche(Color col) const { for(int i=0;i<=4;i++) { DrawLine(t[i].x,t[i].y,t[(i+1)%4].x,t[(i+1)%4].y,col); } } void quadrilatere::translate(point v) const { 260

B. Examens

B.15. Devoir surveill 2004 : nonc

43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81

for(int i=0;i<4;i=i+1) t[i]=t[i]+v; } bool quadrilatere::dans_la_fenetre() { bool in=false; for(int i=0;(i<4) && in;i++) { if ((t[i].x<0) or (t[i].x>=w) or (t[i].y<0) or (t[i].y>=h)) then in=true; } return in; } int main() { OpenWindow(w,h); quadrilatere Q; Q(pts); point pts[4]; pts={{10,10},{10,100},{100,100},{100,10}}; point v={1,2}; while(Q.dans_la_fenetre()) { Q.affiche(); MilliSleep(10); Q.affiche(White); Q.translate(v); } delete [] Q.t; Terminate(); return 0; }

B.15.2

Quache ce programme ?

Examinez bien le contenu du programme suivant. La question est simple : quelles sont les direntes lignes aches sur la sortie standard (cest--dire en console, via les instructions cout << ... ;) au cours de son excution ? Justiez vos rponses.
1 2 3

#include <iostream> using namespace std;

261

B.15. Devoir surveill 2004 : nonc

B. Examens

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 51 52

int NbPt; void check() { cout<<endl; cout<<"n_points= "<<NbPt<<endl; cout<<endl; }

struct Point { double x,y; Point() { NbPt++; x=y=0; cout<<"Point: Void Cons"<<endl; }; Point( const Point &model) { NbPt++; x=model.x; y=model.y; cout<<"Point: Copy Cons"<<endl; } Point( const double &_x, const double &_y) { NbPt++; x=_x; y=_y; cout<<"Point: (x,y) Cons"<<endl; } ~Point() { NbPt--; cout<<"Point: Dest"<<endl; } void display() const { cout<<"x= "<<x<<" y= "<<y<<endl; } }; void displayPoint ( Point T) { T.display(); 262

B. Examens

B.15. Devoir surveill 2004 : nonc

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

} void displayTriangle2 ( Point *T) { cout<<"A"<<endl; T[0].display(); cout<<"B"<<endl; T[1].display(); cout<<"C"<<endl; T[2].display(); } void displayTriangle1 ( Point *&T) { cout<<"A"<<endl; displayPoint(T[0]); cout<<"B"<<endl; displayPoint(T[1]); cout<<"C"<<endl; displayPoint(T[2]); } void buildTriangleStatic ( Point *T, const double *array) { for(int i=0;i<3;i++) { T[i]=Point(array[2*i],array[2*i+1]); } } Point* buildTriangleDynamic( const double *array) { Point *T=new Point[3]; for(int i=0;i<3;i++) { T[i]=Point(array[2*i],array[2*i+1]); } return T; } void destroyTriangleDynamic (Point *T) { delete[] T; } int main() { NbPt=0; const double d6_1[6]={1.1,2.2,3.3,-0.1,-0.2,-0.3}; const double d6_2[6]={1,1,2,2,3,3}; check(); Point* t1=buildTriangleDynamic(d6_1); check(); Point t2[3]; check(); buildTriangleStatic(t2,d6_2); check(); displayTriangle1(t1); check(); displayTriangle2(t2); 263

B.15. Devoir surveill 2004 : nonc

B. Examens

102 103 104 105 106 107 108 109 110 111 112

check(); destroyTriangleDynamic(t1); check(); t1=t2; check(); displayTriangle1(t1); check(); return 0; }

B.15.3

Chemins dans un graphe


1 0 3 1 1 1 3 2 2 3 5 4 3 1

Figure B.10 Chemins minimaux On se propose de calculer les plus courts chemins dans un graphe par lalgorithme de Floyd. Soient n villes et, pour 0 i < n et 0 j < n, Cij le cot du trajet de la ville i vers la ville j (avec Cii = 0, Cij non ncessairement gal Cji , et ventuellement Cij = p1 si ce trajet nexiste pas). Le cot dun chemin i1 , i2 , ..., ip est la somme k=1 Cik ik+1 . Pour trouver les cots de tous les chemins minimaux entre toutes les villes, il sut de construire, pour 0 k < n, les matrices D(k) dnies par : D(0) = C (k) (k1) (k1) (k1) Dij = min(Dij , Dik + Dkj ) Le cot du chemin minimum entre i et j est alors Dij relier i j). Questions 1. Dnir une matrice int C[n][n] reprsentant le graphe gure B.10 dans laquelle chaque che de i vers j reprsente un Cij 2. Ecrire une fonction void floyd(const int C[n][n],int D[n][n]) calculant D(n1) partir de C 3. Appeler cette fonction et acher les cots des chemins minimaux pour tous les (i, j). 264
(n1)

(ventuellement si on ne peut

B. Examens

B.15. Devoir surveill 2004 : nonc

Chemins Pour mmoriser les chemins et non plus seulement leur cot, il sut de rajouter une matrice P correspondant aux prdcesseurs dans les chemins minimaux. Cette matrice est dnie comme suit : Initialement, Pij = i si i = j et Cij < , Pij = sinon. (k1) (k1) (k) Lorsque Dik + Dkj est retenu comme valeur de Dij , alors faire Pij = Pkj Aprs quoi, le chemin minimal de i vers j est celui de i vers Pij , suivi du trajet Pij j. 1. Modier la fonction floyd pour quelle remplisse cette matrice P . 2. Ecrire une fonction rcursive void chemin(const int P[n][n],int i, int j) achant le chemin minimal de i vers j pour P donne. 3. Utiliser les deux fonctions prcdentes pour calculer et acher tous les chemins minimaux dans le graphe prcdent.

B.15.4

Tours de Hano

Principe et rgles Le jeu est constitu de 3 tiges, A, B et C, sur lesquelles sont empils des anneaux de taille dcroissante, que lon numrotera de N 1.

Figure B.11 Conguration initiale Au dbut du jeu, tous les anneaux sont empils sur la tige A (voir gure B.11). Lobjectif est de transfrer tous les anneaux sur la tige C. Il nest possible de dplacer les anneaux que un par un, et il nest pas possible de poser un anneau sur un autre de taille infrieure. On dsignera par le terme configuration une disposition des anneaux sur les tiges (voir gure B.12).

Figure B.12 Exemple de conguration 265

B.15. Devoir surveill 2004 : nonc

B. Examens

On dsignera par le terme dplacement de la tige X la tige Y le fait denlever lanneau situ au sommet de la pile de la tige X pour le positionner au sommet de la pile de la tige Y (voir gure B.13).

Figure B.13 Exemple de dplacement

Objectif Lobjectif ici est dcrire un programme qui ache la suite des dplacements raliser pour rsoudre le problme avec n anneaux, sous la forme suivante : numero_anneau : tige_origine > tige_destination On souhaite de plus acher ltat du jeu (les anneaux prsents sur chaque tige) aprs chaque dplacement. Exemple pour 2 anneaux (la gure B.14 en donne une reprsentation graphique) : A> 2 1 B> . . C> . . deplacement: 1 : A->B A> 2 . B> 1 . C> . . deplacement: 2 : A->C A> . . B> 1 . C> 2 . deplacement: 1 : B->C A> . . B> . . C> 2 1 Ce problme apparemment complexe se traite en fait simplement : pour transfrer n anneaux dune tige X une tige Y, il "sut" deectuer : le transfert des n-1 anneaux suprieurs de la tige X sur la troisime tige Z le dplacement de lanneau n de la tige X la tige destination Y le transfert des n-1 anneaux de la tige Z la tige Y 266

B. Examens

B.15. Devoir surveill 2004 : nonc

Figure B.14 Solution du problme pour N=2 Pour transfrer n-1 anneaux, on applique la mme mthode : transfert de n-2 anneaux de la tige origine une tige temporaire, dplacement dun anneau de la tige origine la tige destination, transfert des n-2 anneaux de la tige temporaire la tige destination ... Questions 1. Dclarer une constante N, que lon xera 10, et qui reprsentera le nombre danneaux du problme rsoudre. 2. Crer un objet capable de stocker une conguration. On pourra par exemple reprsenter chaque tige par un tableau de taille N, et chaque anneau par un entier entre 1 et N. Vous utiliserez au choix une structure ou une classe, selon ce qui vous semble le plus pertinent, ou dfaut ce avec quoi vous tes le plus laise. 3. Programmer de quoi initialiser une conguration, cest dire lui donner sa valeur de dpart (tous les anneaux sur la premire tige). 4. Programmer une fonction permettant dacher une conguration lcran, par exemple sous la forme utilise ci-dessus pour lexemple pour N=2. 5. Programmer une fonction ralisant un dplacement : pour une tige origine et une tige destination spcies, elle prlve le pion suprieur sur la tige origine et le place sur la tige destination. 6. En vous basant sur la mthode propose, programmer une fonction ralisant un transfert de k anneaux dune tige une autre, et achant au fur et mesure les dplacements eectus, et ltat du jeu aprs chaque dplacement. 7. En utilisant les lments prcdents, crire une fonction main() qui rsoud le problme des tours de Hanoi de taille N et ache la solution complte. Questions complmentaires 1. Si lon avait souhait nacher que la liste des dplacements eectuer sans acher chaque fois les congurations intermdaires, le programme aurait t beaucoup 267

B.15. Devoir surveill 2004 : nonc

B. Examens

plus simple. Pourquoi ? Ecrire la fonction correspondante (5 lignes de code environ). 2. Pour rsoudre le problme avec N anneaux, combien de dplacements sont ncessaires ?

B.15.5

Table de hachage

On a souvent besoin, en informatique, de manipuler des donnes sous forme de tableaux associatifs, dans lesquels on associe une valeur chaque clef dun ensemble. Cest le cas quand on veut reprsenter un annuaire ( un nom, on associe un numro de tlphone) ou un dictionnaire monolingue ( un mot, on associe une dnition). On se place dans le cas o clefs et valeurs sont reprsentes par des chanes de caractres et o une unique valeur est associe chaque clef. Une manire ecace de reprsenter cette notion abstraite de tableau associatif est dutiliser une table de hachage. On se xe un entier naturel N et on alloue en mmoire un tableau de N cases. On suppose que lon dispose dune fonction de hachage h qui une clef (une chane de caractres, donc), associe un entier entre 0 et N 1. On stockera le couple clef/valeur (c, v) dans la case h(c) du tableau. Ainsi, pour rechercher par la suite quelle valeur est associe une clef c, il sura de regarder dans la case h(c), sans avoir besoin de parcourir lintgralit du tableau. Un problme qui se pose est que h nest pas une fonction injective. Non seulement il y a potentiellement un nombre inni de clefs, mais on souhaite galement garder N relativement petit pour ne pas avoir de trop gros besoins en mmoire. Il faut donc prvoir les collisions, cest--dire les cas de deux clefs distinctes c et c telles que h(c) = h(c ). Plusieurs stratgies de gestion des collisions existent, on en choisira une trs simple : si, au moment dinsrer le couple (c , v ), la case h(c ) est dj occupe par le couple (c, v), on retente linsertion dans la case h(c )+1. Si celle-ci est nouveau occupe, on essaye la case suivante, etc., jusqu trouver une case vide ou avoir parcouru lintgralit du tableau (on considre que la case 0 succde la case N 1), auquel cas linsertion choue (le tableau associatif comportera donc au plus N lments). La recherche dune clef dans la table de hachage se passe de faon similaire. Une table de hachage est donc caractrise par : sa taille N (on prendra N =1021) la fonction de hachage h. Pour une clef c = (x0 . . . xl1 ) de longueur l, on prendra :
l1

h(c) =
i=0

B l1i xi

mod N

o B est une constante, que lon xera 256 et x mod y dsigne le reste de la division euclidienne de x par y la manire de grer les collisions, dcrite plus haut 1. Dnir les constantes globales N et B. 2. Dnir la fonction de hachage int hachage(const string &clef). 3. Dnir une structure Entree, qui correspondra une case de la table de hachage. Cette structure contient trois champs : la clef, la valeur et un boolen indiquant si la case est occupe. 4. Dnir une classe TableHachage comprenant : un tableau de N Entree 268

B. Examens

B.15. Devoir surveill 2004 : nonc

une fonction membre void inserer(const string &clef, const string &valeur); une fonction membre string rechercher(const string &clef) const; Un constructeur est-il ncessaire ? Pourquoi ? Le dnir le cas chant. 5. Dnir la fonction TableHachage::inserer. Dans le cas o la table est pleine, on pourra simplement acher un message lcran et ne rien faire dautre. 6. Dnir la fonction TableHachage::rechercher. Dans le cas o la clef nest pas prsente dans la table, on pourra renvoyer la chane vide. 7. Dnir une fonction main utilisant un objet de type TableHachage. On pourra stocker le mini-annuaire suivant, puis rechercher et acher le numro de tlphone du SAMU. SAMU 15 Police Secours 17 Pompiers 18 8. Sans rien programmer, expliquer ce quil faudrait faire pour rajouter la possibilit denlever un lment la table de hachage.

269

B.16. Devoir surveill 2003 : nonc

B. Examens

B.16
B.16.1

Devoir surveill 2003 : nonc


Tableau dexcution

Remplir le tableau dexcution du programme suivant :


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

int suite(int x){ if (x%2 == 0) return x/2; else return 3*x + 1; } bool test(int y){ bool h; if (y == 1) h = true; else h = test(suite(y)); return h; } void initialise(int& z){ z = 4; } int fin(int& w){ w += 36; return w+2; w += 1; } int main(){ int a; initialise(a); if (test(a)) a = fin(a); return 0; }

Il sagit dcrire, si elles existent, les valeurs des variables du programme pour chaque excution de ligne. Question bonus : ce code gnre un warning la compilation ; lequel, quelle ligne ? Conventions : 270

B. Examens

B.16. Devoir surveill 2003 : nonc

mettre le numro de la ligne quon vient dexcuter (ventuellement, on peut dcouper lexcution dune mme ligne en plusieurs tapes) ; laisser en blanc les variables non existantes ; mettre un point dinterrogation pour les variables dclares non initialises ; pour une variable qui nest autre quune rfrence sur une autre variable x, on indiquera [x] ; on peut utiliser le symbole pour rpter la valeur dune variable la ligne suivante ; pour gagner de la place dans le tableau, les noms des variables nont t indexs que par la premire lettre du nom de la fonction laquelle elles sont rattaches, suivie le cas chant du numro dappel la fonction. De mme, le retour de la fonction f a pour nom retf .

B.16.2

Erreurs

Corriger le code suivant :


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

// fichier main.cpp class paire { string clef; int valeur; public: paire(string key, int value); void affecte_clef(string key) const; void affecte_valeur(int value) const; string donne_clef() const; int donne_valeur() const; } paire::paire(string key, int value) { clef=key; valeur=value; } void paire::affecte_clef(string key) { clef=key; } void paire::affecte_valeur(int value) { valeur=value; } string paire::donne_clef() { return clef; } int paire::donne_valeur() { return valeur; } 271

B.16. Devoir surveill 2003 : nonc

B. Examens

34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59

int cherche_valeur(paire repertoire[], int n, string key) { for (i=1, i<=n, i++) { if repertoire[i].donne_clef = key return (repertoire[i].donne_valeur); cout << "clef non trouvee" << endl; } int main() { paire a={"cheval",4}; paire b; b.clef="poule"; b.valeur=2; paire* pattes = new paire[5]; pattes = {a, b, paire("araignee",8), paire("baleine",0), paire("martien",3)}; cout << "Un martien a " << cherche_valeur(pattes[5],5,"martien") << " pattes." << endl; return 0; }

B.16.3

Quache ce programme ?

Examinez bien le contenu du programme suivant. La question est simple : quelles sont les direntes lignes aches sur la sortie standard (cest--dire en console, via les instructions cout << ... ;) au cours de son excution ? Justiez vos rponses.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16

#include <iostream> using namespace std; class Paire { double x,y; public: Paire(); Paire(double a,double b); Paire operator+(const Paire &v) const; Paire operator-(const Paire &v) const; double getx() const; double gety() const; void setxy(double xx,double yy); }; Paire::Paire() { 272

B. Examens

B.16. Devoir surveill 2003 : nonc

17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62

cout << "Constructeur sans argument." << endl; } Paire::Paire(double a,double b) { x=a; y=b; cout << "Constructeur avec arguments [" << x << "," << y << "]" << endl; } Paire Paire::operator+(const Paire& v) const { cout << "Addition [" << x << "," << y << "] [" << v.x << "," << v.y << "]" << endl; Paire w; w.x=x+v.x; w.y=y+v.y; return w; } Paire Paire::operator-(const Paire &v) const { cout << "Soustraction [" << x << "," << y << "] [" << v.x << "," << v.y << "]" << endl; return Paire(x-v.x,y-v.y); } double Paire::getx() const { return x; } double Paire::gety() const { return y; } void Paire::setxy(double xx,double yy) { x=xx; y=yy; } int main(void) { Paire tab[2]; tab[0].setxy(3,4); tab[1]=Paire(4,6); Paire a(1,1); Paire b= ( ( a+Paire(1,2) ) + tab[0] ) - tab[1]; cout << "Resultat [" << b.getx() << "," << b.gety() << "]" << endl; }

273

B.16. Devoir surveill 2003 : nonc

B. Examens

B.16.4

Le jeu du Pendu

Tout le monde connat le jeu de pendu. Rappelons tout de mme les rgles pour plus de sret ... Il sagit de trouver un mot cach en proposant des lettres une par une. Si la lettre propose nest pas prsente dans le mot, une erreur est comptabilise. Si le joueur dpasse 11 erreurs avant davoir trouv le mot, il a perdu. Inversement, si le joueur parvient dcouvrir lintgralit du mot avant de totaliser 11 erreurs, il a gagn. Commencez par programmer au brouillon ! ! ! On dispose dune fonction string dictionnaire(int n) qui renvoie un mot de 10 lettres dirent pour chaque entier compris entre 0 et 999 pass en argument. 1. Utiliser cette fonction pour programmer une fonction mot_mystere qui renvoie un mot mystre faire dcouvrir, en faisant en sorte que ce mot change alatoirement chaque excution. On utilise un tableau de boolens pour stocker le fait que le ieme caractre du mot mystre ait t dcouvert ou non. Exemple : pour "maquillage", si le joueur a dj entr i, a et s, le tableau de boolens est {false, true, false, false, true, false, false, true, false, false}. 2. Programmer une fonction affiche qui partir du mot mystre et du tableau de boolens associ ache lcran le rsultat courant (mot mystre dans lequel les caractres non dcouverts sont remplacs par une toile *). Exemple : pour "maquillage", si le joueur a dj entr i, a et s, le rsultat courant est "*a**i**a**". La fonction ache donc "*a**i**a**". 3. Programmer une fonction essai qui demande lutilisateur de rentrer un caractre au clavier et qui met jour le tableau de boolens en consquence. 4. Programmer une fonction reste qui renvoie le nombre de caractres restant trouver dans le mot mystre (pour 2 caractres identiques, on comptera 2 pour simplier). Exemple : pour "maquillage", si le joueur a dj entr i, a et s, le rsultat courant est "*a**i**a**". Il reste 7 caractres trouver (m,q,u,l,l,g,e). On dispose maintenant de susants dlments pour construire une version simple du jeu, sans notion de nombre limit dessais. 5. A partir des fonctions prcdentes, programmer une fonction jeu_mystere qui demande lutilisateur dentrer un caractre puis ache le rsultat courant jusqu ce que le mot mystre soit entirement dcouvert, et signale ensuite lutilisateur quil a gagn. Il sagit maintenant de rajouter cette premire version du jeu la notion de nombre limit dessais. 6. Programmer une nouvelle version de la fonction essai pour quelle renvoie en plus une variable signalant si le caractre entr par lutilisateur restait dcouvrir (succs) ou non (chec) dans le mot mystre. Exemple : pour "maquillage", si le joueur a dj entr i, a et s, le rsultat courant est "*a**i**a**". Si lutilisateur rentre m, la fonction renvoie succs ; si lutilisateur rentre z, la fonction renvoie chec ; si lutilisateur rentre a (dj rentr), la fonction renvoie chec. 274

B. Examens

B.16. Devoir surveill 2003 : nonc

7. Assembler les fonction prcdentes lintrieur dune fonction jeu_pendu pour crer un jeu de pendu. Grer la dfaite du joueur si il arrive 11 checs. Grer la victoire du joueur si il dcouvre lintgralit du mot mystre avant de totaliser 11 checs.

B.16.5

Programme mystre

Le programme suivant est inspir dun clbre puzzle. Il sagit de deviner la logique dune suite de chanes de caractres. On donne les quatre premiers lments de la suite et on demande de trouver les deux suivants. Il y a un pige et certaines personnes mettent plusieurs jours trouver... Ici, nous ne vous donnons pas les premiers lments, mais un programme qui gnre la suite complte. Pour quelquun qui parle C++, ce programme est donc directement la solution du puzzle. A vous de jouer : Que fait exactement ce programme ? Donnez le principe de la suite et dtaillez un peu le fonctionnement de la fonction next(). Quache t-il ? Le programmeur a construit ligne 15 le caractre reprsentant le chire count en utilisant char(0+count). Il a donc suppos que count restait plus petit que 10. Pour se rassurer, il a ajout un assert() ligne 14. Etait-ce la peine ? Le assert() reste-t-il vrai si on ache le reste de la suite ? La condition aurait-elle pu tre encore plus restrictive ? Question subsidiaire (hors examen !) : faites le test autour de vous et voyez combien de personnes trouvent le 5eme lment partir des 4 premiers...
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

// Programme mystre... #include <iostream> #include <string> #include <cassert> using namespace std; string next(string s) { string t; int count=1; for (unsigned int i=0;i<s.size();i++) { if (i==s.size()-1 || s[i]!=s[i+1]) { string u="xx"; assert(count<10); u[0]=char(0+count); // caractre reprsentant le chiffre count u[1]=s[i]; t+=u; count=1; } else count++; } return t; } int main() 275

B.16. Devoir surveill 2003 : nonc

B. Examens

26 27 28 29 30 31 32 33 34

{ string s="1"; cout << s << endl; for (int i=0;i<8;i++) { s=next(s); cout << s << endl; } return 0; } ...

276

C. La CLGraphics

Annexe C La CLGraphics
Un documentation sera disponible sur la page Web des CertisLibs.

D. Fiche de rfrence nale

Annexe D Fiche de rfrence nale


Fiche de rfrence (1/5) Variables Dnition : int i; int k,l,m; Aectation : i=2; j=i; k=l=3; Initialisation : int n=5,o=n; Constantes : const int s=12; Porte : int i; // i=j; interdit! int j=2; i=j; // OK! if (j>1) { int k=3; j=k; // OK! } //i=k; interdit! Types : int i=3; double x=12.3; char c=A; string s="hop"; bool t=true; float y=1.2f; unsigned int j=4; signed char d=-128; unsigned char d=254; complex<double> z(2,3); Variables globales : int n; const int m=12; void f() { n=10; // OK int i=m; // OK ... Conversion : int i=int(x); int i,j; double x=double(i)/j; Pile/Tas Type numr : enum Dir{nord,est, sud,ouest}; void avance(Dir d); Variables statiques : int f() { static bool first=true; if (first) { first=false; ... } ... } Tests Comparaison : == != < > <= >= Ngation : ! Combinaisons : && || if (i==0) j=1; if (i==0) j=1; else j=2; if (i==0) { j=1; k=2; } bool t=(i==0); if (t) j=1; switch (i) { case 1: ...; ...; break; case 2: case 3: ...; break; default: ...; } mx=(x>y)?x:y; Boucles do { ... } while (!ok); int i=1; while (i<=100) { ... i=i+1; } for (int i=1;i<=100;i++) ... for (int i=1,j=100;j>i; i=i+2,j=j-3) ... for (int i=...) for (int j=...) { // saute le cas i==j if (i==j) continue; ... } for (int i=...) { ... if (t[i]==s){ ... // quitte la boucle break; } ... }

D. Fiche de rfrence nale

Fiche de rfrence (2/5) Fonctions Dnition : int plus(int a,int b) { int c=a+b; return c; } void affiche(int a) { cout << a << endl; } Dclaration : int plus(int a,int b); Retour : int signe(double x) { if (x<0) return -1; if (x>0) return 1; return 0; } void afficher(int x, int y) { if (x<0 || y<0) return; if (x>=w || y>=h) return; DrawPoint(x,y,Red); } Appel : int f(int a) { ... } int g() { ... } ... int i=f(2),j=g(); Rfrences : void swap(int& a,int& b) { int tmp=a; a=b;b=tmp; } ... int x=3,y=2; swap(x,y); Surcharge : int hasard(int n); int hasard(int a,int b); double hasard(); Oprateurs : vect operator+( vect A,vect B) { ... } ... vect C=A+B; Pile des appels Itratif/Rcursif Rfrences constantes (pour un passage rapide) : void f(const obj& x){ ... } void g(const obj& x){ f(x); // OK } Valeurs par dfaut : void f(int a,int b=0); void g() { f(12); // f(12,0); f(10,2);// f(10,2); } void f(int a,int b) { // ... } Inline (appel rapide) : inline double sqr( double x) { return x*x; } ... double y=sqr(z-3); Rfrence en retour : int i; // Var. globale int& f() { return i; } ... f()=3; // i=3! Tableaux Dnition : double x[10],y[10]; for (int i=0;i<10;i++) y[i]=2*x[i]; const int n=5; int i[n],j[2*n]; // OK Initialisation : int t[4]={1,2,3,4}; string s[2]={"ab","cd"}; Aectation : int s[4]={1,2,3,4},t[4]; for (int i=0;i<4;i++) t[i]=s[i]; En paramtre : void init(int t[4]) { for (int i=0;i<4;i++) t[i]=0; } void init(int t[], int n) { for (int i=0;i<n;i++) t[i]=0; } Taille variable : int* t=new int[n]; ... delete[] t; En paramtre (suite) : void f(int* t, int n) { t[i]=... } void alloue(int*& t) { t=new int[n]; } 2D : int A[2][3]; A[i][j]=...; int A[2][3]= {{1,2,3},{4,5,6}}; void f(int A[2][2]); 2D dans 1D : int A[2*3]; A[i+2*j]=...; Taille variable (suite) : int *t,*s,n; En paramtre (n) : void f(const int* t, int n) { ... s+=t[i]; // OK ... t[i]=...; // NON! }

Structures struct Point { double x,y; Color c; }; ... Point a; a.x=2.3; a.y=3.4; a.c=Red; Point b={1,2.5,Blue}; Une structure est un objet entirement public ( cf objets !)

280

D. Fiche de rfrence nale

Fiche de rfrence (3/5) Objets struct obj { int x; // champ int f(); // mthode int g(int y); }; int obj::f() { int i=g(3); // mon g int j=x+i; // mon x return j; } ... int main() { obj a; a.x=3; int i=a.f(); class obj { int x,y; void a_moi(); public: int z; void pour_tous(); void une_autre(obj A); }; void obj::a_moi() { x=..; // OK ..=y; // OK z=..; // OK } void obj::pour_tous() { x=..; // OK a_moi(); // OK } void une_autre(obj A) { x=A.x; // OK A.a_moi(); // OK } ... int main() { obj A,B; A.x=..; // NON! A.z=..; // OK A.a_moi(); // NON! A.pour_tous(); // OK A.une_autre(B); // OK class obj { obj operator+(obj B); }; ... int main() { obj A,B,C; C=A+B; // C=A.operator+(B) Mthodes constantes : void obj::f() const{ ... } void g(const obj& x){ x.f(); // OK } Constructeur : class point { int x,y; public: point(int X,int Y); }; point::point(int X,int Y){ x=X; y=Y; } ... point a(2,3); Constructeur vide : obj::obj() { ... } ... obj a; Objets temporaires : point point::operator+( point b) { return point(x+b.x, y+b.y); } ... c=point(1,2) +f(point(2,3)); Destructeur : obj::~obj() { ... } Constructeur de copie : obj::obj(const obj& o) { ... } Utilis par : - obj b(a); - obj b=a; //Diffrent de obj b;b=a; - paramtres des fonctions - valeur de retour Aectation : const obj& obj::operator= (const obj&o) { ... return o; } Objets avec allocation dynamique automatique : cf section 10.11 Accesseurs : class mat { double *x; public: inline double& operator() (int i,int j) { assert(i>=0 ...); return x[i+M*j]; } inline double operator() (int i,int j)const { assert(i>=0 ...); return x[i+M*j]; } ... Compilation spare #include "vect.h", y compris dans vect.cpp Fonctions : dclarations dans le .h, dnitions dans le .cpp Types : dnitions dans le .h Ne dclarer dans le .h que les fonctions utiles. #pragma once au dbut du chier. Ne pas trop dcouper... STL min,max,... complex<double> z; pair<int,string> p; p.first=2; p.second="hop"; #include<list> using namespace std; ... list<int> l; l.push_front(1); ... if (l.find(3)!=l.end()) ... list<int>::const_iterator it; for (it=l.begin(); it!=l.end();it++) s+= *it; list<int>::iterator it for (it=l.begin(); it!=l.end();it++) if (*it==2) *it=4; stack, queue, heap, map, set, vector, ...

281

D. Fiche de rfrence nale

Fiche de rfrence (4/5) Entres/Sorties #include <iostream> using namespace std; ... cout << "I=" << i << endl; cin >> i >> j; #include <fstream> using namespace std; ofstream f("hop.txt"); f << 1 << << 2.3; f.close(); ifstream g("hop.txt"); if (!g.is_open()) { return 1; } int i; double x; g >> i >> x; g.close(); do { ... } while (!(g.eof()); ofstream f; f.open("hop.txt"); double x[10],y; ofstream f("hop.bin", ios::binary); f.write((const char*)x, 10*sizeof(double)); f.write((const char*)&y, sizeof(double)); f.close(); ifstream g("hop.bin", ios::binary); g.read((char*)x, 10*sizeof(double)); g.read((const char*)&y, sizeof(double)); g.close(); string nom; ifstream f(nom.c_str()); #include <sstream> using namespace std; stringstream f; // Chane vers entier f << s; f >> i; // Entier vers chane f.clear(); f << i; f >> s; ostream& operator<<( ostream& f, const point&p) { f<<p.x<< << p.y; return f; } istream& operator>>( istream& f,point& p) { f>>p.x>>p.y; return f; } Template Fonctions : // A mettre dans LE // fichier qui lutilise // ou dans un .h template <typename T> T maxi(T a,T b) { ... } ... // Le type est trouv // tout seul! maxi(1,2); //int maxi(.2,.3); //double maxi("a","c");//string Objets : template <typename T> class paire { T x[2]; public: paire() {} paire(T a,T b) { x[0]=a;x[1]=b; } T somme()const; }; ... template <typename T> T paire<T>::somme()const{ return x[0]+x[1]; } ... // Le type doit tre // prcis! paire<int> a(1,2); int s=a.somme(); paire<double> b; ... Multiples : template <typename T, typename S> class hop { ... }; ... hop<int,string> A; ... Entiers : template <int N> class hop { .. }; ... hop<3> A; hop<5> B; ... Conseils Travailler en local CertisLibs Project Nettoyer en quittant. Erreurs et warnings : cliquer. Indenter. Ne pas laisser de warning. Utiliser le debuggeur. Faire des fonctions. Tableaux : quand cest utile ! (Pas pour transcrire une formule mathmatique.) Faire des structures. Faire des chiers spars. Le .h doit sure lutilisateur (qui ne doit pas regarder le .cpp) Ne pas abuser du rcursif. Ne pas oublier delete. Compiler rgulirement. Debug/Release : nettoyer les deux. Faire des objets. Ne pas toujours faire des objets ! Penser interface / implmentation / utilisation. Clavier Build : F7 Start : Ctrl+F5 Compile : Ctrl+F7 Debug : F5 Stop : Maj+F5 Step over : F10 Step inside : F11 Indent : Ctrl+K,Ctrl+F Add New It. : Ctrl+Maj+A Add Exist. It. : Alt+Maj+A Step out : Maj+F11 Run to curs. : Click droit Compltion : Alt+ Gest. tches : Ctrl+Maj+Ech

282

D. Fiche de rfrence nale

Fiche de rfrence (5/5) Divers i++; i--; i-=2; j+=3; j=i%n; // Modulo #include <cstdlib> ... i=rand()%n; x=rand()/double(RAND_MAX); #include <ctime> ... srand( unsigned int(time(0))); #include <cmath> double sqrt(double x); double cos(double x); double sin(double x); double acos(double x); #include <string> using namespace std; string s="hop"; char c=s[0]; int l=s.size(); if (s1==s1) ... if (s1!=s2) ... if (s1<s2) ... size_t i=s.find(h); size_t j=s.find(h,3); size_t i=s.find("hop"); size_t j=s.find("hop",3); a="comment"; b="a va?"; txt=a+" "+b; s1="un deux trois"; s2=string(s1,3,4); getline(cin,s); getline(cin,s,:); const char *t=s.c_str(); #include <cassert> ... assert(x!=0); y=1/x; #include <ctime> s=double(clock()) /CLOCKS_PER_SEC; #define _USE_MATH_DEFINES #include <cmath> double pi=M_PI; Oprateurs binaires and : a&b or : a|b xor : a^b right shift : a>>n left shift : a<<n complement : ~a exemples : set(i,1) : reset(i,1) : test(i,1) : ip(i,1) : i|=(1<<n) i&=~(1<<n) if (i&(1<<n)) i^=(1<<n) // et non int* ! t=new int[n]; s=new int[n];// NON! class point { int x,y; public: ... }; ... point a={2,3}; // NON! Oublier de rednir le constructeur vide. point p=point(1,2);// NON! point p(1,2); // OUI! obj* t=new obj[n]; ... delete t; // oubli de [] //NON! void f(int a=2,int b); void f(int a,int b=0); void f(int a);// NON! Ne pas tout mettre inline ! int f() { ... } ... f()=3; // HORREUR! int& f() { int i; return i; } ... f()=3; // NON! if (i>0 & i<n) ... // NON! if (i<0 | i>n) ... // NON! if (...) { ... if (...) break; // NON! Pour les // boucles seulement! } for (i ...) for (j ...) { ... if (...) break;//NON! Ne quitte // Que la boucle en j! int i; double x; ... j=max(i,0);//OK y=max(x,0);//NON! Utiliser // 0.0 et non 0 (max est // un template (STL)...)

Erreurs frquentes Pas de dnition de fonction dans une fonction ! int q=r=4; // NON! if (i=2) // NON! if i==2 // NON! if (i==2) then // NON! for (int i=0,i<100,i++) // NON! int f() {...} ... int i=f; // NON! double x=1/3; // NON! int i,j; double x; x=i/j; // NON! x=double(i/j); //NON! double x[10],y[10]; for (int i=1;i<=10;i++) // NON! y[i]=2*x[i]; int n=5; int t[n]; // NON int f()[4] { // NON! int t[4]; ... return t; // NON! } ... int t[4] t=f(); int s[4]={1,2,3,4},t[4]; t=s; // NON! int t[2]; t={1,2}; // NON! struct Point { double x,y; } // NON! Point a; a={1,2}; // NON! #include "vect.cpp"// NON! void f(int t[][]);//NON! int t[2,3]; // NON! t[i,j]=...; // NON! int* t; t[1]=...; // NON! int* t=new int[2]; int* s=new int[2]; s=t; // On perd s! delete[] t; delete[] s; // Dja fait! int *t,s;// s est int

CertisLibs Voir documentation...

283

You might also like