Vous êtes sur la page 1sur 630

Jean-Bernard Boichat

Java Java C++ C++ parallle


et en

Apprendre Apprendre

4e di tio n

parallle

Java C++ parallle


et en

Apprendre

CHEZ LE MME DITEUR Ouvrages sur Java A. Tasso. Le livre de Java premier langage (5e dition). N12376, 2008, 520 pages + CD-Rom. C. Delannoy. Programmer en Java. Java 5 et 6. N12326, 2008, 788 pages (format semi-poche). J. Bougeault. Java - La matrise. Java 5 et 6. N12250, 2008, 550 pages. A. Patricio. Java Persistence et Hibernate. N12259, 2008, 364 pages. C. Delannoy. Exercices en Java. Java 5.0. N11989, 2006, 314 pages. E. Puybaret. Les Cahiers du programmeur Java (3e dition). Java 1.4 et 5.0. N11916, 2006, 370 pages + CD-Rom. R. Fleury. Les Cahiers du programmeur Java/XML. N11316, 2004, 218 pages. P. Haggar. Mieux programmer en Java. 68 astuces pour optimiser son code. N9171, 2000, 256 pages. J.-P. Retaill. Refactoring des applications Java/J2EE. N11577, 2005, 390 pages. Ouvrages sur C++ C. Delannoy. C++ pour les programmeurs C. N12231, 2007, 602 pages. C. Delannoy. Exercices en langage C++ (3e dition). N12201, 2007, 336 pages. C. Delannoy. Apprendre le C++. N12135, 2007, 760 pages. H. Sutter. Mieux programmer en C++. N9224, 2000, 215 pages.

Jean-Bernard Boichat

Java C++ parallle


et en
4e dition

Apprendre

DITIONS EYROLLES 61, bd Saint-Germain 75240 Paris Cedex 05 www.editions-eyrolles.com

Le code de la proprit intellectuelle du 1er juillet 1992 interdit en effet expressment la photocopie usage collectif sans autorisation des ayants droit. Or, cette pratique sest gnralise notamment dans les tablissements denseignement, provoquant une baisse brutale des achats de livres, au point que la possibilit mme pour les auteurs de crer des uvres nouvelles et de les faire diter correctement est aujourdhui menace. En application de la loi du 11 mars 1957, il est interdit de reproduire intgralement ou partiellement le prsent ouvrage, sur quelque support que ce soit, sans autorisation de lditeur ou du Centre Franais dExploitation du Droit de Copie, 20, rue des Grands-Augustins, 75006 Paris. Groupe Eyrolles, 2008, ISBN : 978-2-212-12403-3

Pour Ornella, qui ma soutenu et cajol durant cet t 2008 pas comme les autres ! mes chers enfants, Nathalie, Stphanie et Nicolas.

Table des matires

Avant-propos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Pourquoi un tel ouvrage ? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . qui sadresse ce livre ? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Quel est lintrt dapprendre plusieurs langages ? . . . . . . . . . . . . . . . Quelles versions de Java et de C++ ? . . . . . . . . . . . . . . . . . . . . . . . . . . . Pourquoi le Standard C++ ? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Comment est prsent cet ouvrage ? . . . . . . . . . . . . . . . . . . . . . . . . . . . . De quel matriel a-t-on besoin ? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Pourquoi autant dexemples et dexercices ? . . . . . . . . . . . . . . . . . . . . . Commentaires et suivi de louvrage . . . . . . . . . . . . . . . . . . . . . . . . . . . .
CHAPITRE 1

XXV XXV XXV XXV XXVI XXVII XXVII XXVII XXVIII XXVIII

Lincontournable Hello world . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .


Hello world en C++ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Hello world en Java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . La machine virtuelle Java JRE . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Erreurs de compilation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Notre premier chier Makele . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Enn un premier make effectif. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Le point dentre main() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Les paramtres de main() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . main() et C++ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . main() et Java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

1 2 4 5 6 6 8 9 9 10 10

VIII

Apprendre Java et C++ en parallle

Analyse comparative. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Jouer avec Crimson. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

11 13 13 13

Rsum . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Exercices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
CHAPITRE 2

La dclaration et laffectation des variables numriques . . .


Dclaration des variables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Choix des noms de variables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

15 15 16 17 21 21 23 24 24 25 26 27 27 28 29 30 31 32 32 34 35 38 40 40

Affectation des variables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Transtypage . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Positionnement des variables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Variables du type pointeur en C++ . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Utilisation des pointeurs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Utilisation de malloc() en C++ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

Variables constantes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Variables globales en C++ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Fichiers den-tte en C++ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Oprations et oprateurs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . La classe Java Math . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Les oprateurs traditionnels . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Char et byte . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Intervalles des types entiers en Java . . . . . . . . . . . . . . . . . . . . . . . . . . . . Rgles de priorit des oprateurs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Une diversion sur le cin (entre) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Les oprateurs daffectation composs . . . . . . . . . . . . . . . . . . . . . . . . . . Les oprations binaires . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Typedef et numration en C++ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Rsum . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Exercices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

Table des matires

IX

CHAPITRE 3

Et si on contrlait lexcution ? . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Recommandations pour la forme . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Oprateurs de condition . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Et si ctait faux (False) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Loprateur logique NOT . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Prconisation du bool en C++ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Les boucles for, while et do . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Les boucles for en Java partir du JDK 1.5. . . . . . . . . . . . . . . . . . . . . . . . Tester plusieurs conditions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Ceci ET cela . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Optimisation dans le cas de conditions multiples . . . . . . . . . . . . . . . . . . . Ceci OU cela . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . viter les tests de conditions multiples compliqus. . . . . . . . . . . . . . . . . . Plusieurs slections avec switch . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Linfme goto . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Rsum . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Exercices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
CHAPITRE 4

41 41 42 44 45 45 46 49 49 49 50 51 51 52 54 55 55

On fait ses classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .


Notre premire classe en C++ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Dnition de la classe Personne. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Dnition des objets dune classe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Un seul constructeur. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Une seule mthode . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Nom et dnition des classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Code de la classe Personne. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Directive include . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Commentaires et documentation des classes . . . . . . . . . . . . . . . . . . . . . . . Un Makele volu . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Notre premire classe en Java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Tester les classes. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Commentaires et documentation des classes . . . . . . . . . . . . . . . . . . . . . . . Cration des objets . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

57 57 57 59 59 59 60 60 62 63 65 66 68 68 71

Apprendre Java et C++ en parallle

Make, javac et redondance . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Nos classes Java dans un paquet .jar . . . . . . . . . . . . . . . . . . . . . . . . . . . . Comment tendre notre classe Personne ? . . . . . . . . . . . . . . . . . . . . . . . Diversion sur les structures C . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Rsum . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Exercices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
CHAPITRE 5

72 73 74 75 77 77

On enchane avec les tableaux . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .


Tableaux dentiers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Copie de tableau dentiers en Java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Tableau dynamique en C++ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Tableaux multidimensionnels . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Le jeu dOthello en Java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Le jeu dOthello en C++. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Le cavalier du jeu dchecs. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Chanes de caractres en C . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Les String de Java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Les string du C++, un nouvel atout . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Les mthodes des classes String (Java) et string (C++) . . . . . . . . . . . . Rsum . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Exercices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
CHAPITRE 6

79 80 82 83 85 85 86 88 88 90 92 94 97 97

De la mthode dans nos fonctions . . . . . . . . . . . . . . . . . . . . . . . . . .


Fonctions et mthodes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . splice() de Perl . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . splice() comme fonction C . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Retour par arguments . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Accs des donnes rptitives . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Retour de fonction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Recommandation pour des programmeurs C potentiels. . . . . . . . . . . . . . . Comment utiliser nos fonctions C ? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Nos fonctions C dans un module spar. . . . . . . . . . . . . . . . . . . . . . . . . . .

99 99 100 101 101 102 102 103 103 106

Table des matires

XI
107 107 109 109 111 112 113 114 116 117 118 118 120 121 122 123 123 124 125 126 127 127

Les arguments de mthodes en C++ . . . . . . . . . . . . . . . . . . . . . . . . . . . .


Passage par rfrence (classe C++ Perl1). . . . . . . . . . . . . . . . . . . . . . . . . . Paramtres dclars comme const . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Passage par valeur (classe C++ Perl2) . . . . . . . . . . . . . . . . . . . . . . . . . . . . const et passage par valeur . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Passage par pointeur (classe C++ Perl3) . . . . . . . . . . . . . . . . . . . . . . . . . . Le sufxe const pour une mthode C++ . . . . . . . . . . . . . . . . . . . . . . . . . .

Fonctions et mthodes inline en C++ . . . . . . . . . . . . . . . . . . . . . . . . . . .


Utilisation des numrations avec des mthodes C++ . . . . . . . . . . . . . . . . Utilisation des numrations en Java . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

Les arguments de mthodes en Java . . . . . . . . . . . . . . . . . . . . . . . . . . . .


splice() avec retour par largument (classe Java Perl1) . . . . . . . . . . . . . . . splice() avec retour de mthode (classe Java Perl2) . . . . . . . . . . . . . . . . . .

Java : argument par rfrence ou par valeur ? . . . . . . . . . . . . . . . . . . . Les espaces de noms en C++ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Utilisation classique du namespace . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Conit de nom . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Comment dnir un espace de noms . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

Fichiers den-tte et namespace . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Fichiers den-tte multiples en C++ . . . . . . . . . . . . . . . . . . . . . . . . . . . . Rsum . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Exercices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .


CHAPITRE 7

Notre code dans des bibliothques . . . . . . . . . . . . . . . . . . . . . . . . .


Les extensions .jar, .a et .dll . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Les packages en Java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Compiler les classes de notre package . . . . . . . . . . . . . . . . . . . . . . . . . . . . La variable denvironnement CLASSPATH. . . . . . . . . . . . . . . . . . . . . . . . Nos classes dans un chier darchive .jar. . . . . . . . . . . . . . . . . . . . . . . . . . Signer et vrier un chier .jar. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Test avec le chier monpaquet.jar . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Rsum des diffrentes tapes avec les chiers .bat . . . . . . . . . . . . . . . . .

129 130 131 133 135 136 136 136 137

XII

Apprendre Java et C++ en parallle

Les constructions de bibliothques C et C++ . . . . . . . . . . . . . . . . . . . . . Cration dune bibliothque statique en C++ . . . . . . . . . . . . . . . . . . . . . . . Utilisation de notre bibliothque C++ . . . . . . . . . . . . . . . . . . . . . . . . . . . . Rsum . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Exercices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
CHAPITRE 8

141 141 142 144 144

quelques exceptions prs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .


Au contraire du C++, Java est n avec les exceptions . . . . . . . . . . . . . . Utilisation des exceptions en Java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Capture des exceptions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Ignorer les exceptions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Plusieurs exceptions en une seule fois . . . . . . . . . . . . . . . . . . . . . . . . . . . . Lancement dune exception . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Recommandation pour lcriture de mthodes rutilisables. . . . . . . . . . . . Retour avec -1 comme en C ou C++ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Cration de nouvelles exceptions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Nettoyage laide de nally . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Utilisation des exceptions en C++ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Un exemple sans exceptions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Un exemple avec exceptions. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Propager les exceptions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Exception dans la bibliothque Standard C++ . . . . . . . . . . . . . . . . . . . . . . Gnraliser les exceptions en C++ comme en Java ? . . . . . . . . . . . . . . Rsum . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Exercices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
CHAPITRE 9

145 145 146 147 148 150 151 153 153 153 155 156 157 158 161 163 164 164 165

Entres et sorties . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Du texte dlimit partir de Microsoft Access . . . . . . . . . . . . . . . . . . . Lecture de chiers texte en C++ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . La mthode getline(). . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Lecture de chiers texte en Java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Utilisation de la variable separatorChar . . . . . . . . . . . . . . . . . . . . . . . . . . . Lecture de chiers sur Internet en Java . . . . . . . . . . . . . . . . . . . . . . . . .

167 168 169 170 172 173 174

Table des matires

XIII
176 178 179 181 183 185 186 186 187 189 189 191 193 193 195 197 199 200 200 201 203 203

Lecture de chier binaire en C++ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . criture dun chier binaire en C++ . . . . . . . . . . . . . . . . . . . . . . . . . . . Compilation conditionnelle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . criture dun chier binaire en Java . . . . . . . . . . . . . . . . . . . . . . . . . . . Lecture dun chier binaire en Java . . . . . . . . . . . . . . . . . . . . . . . . . . . . criture dun chier texte en Java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . criture dun chier texte en C++ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Le XML pour linformation structure . . . . . . . . . . . . . . . . . . . . . . . . . . . criture du chier XML . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Accs des rpertoires sur le disque . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Lecture dun rpertoire en C++ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Lecture dun rpertoire en Java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Les ux en mmoire (C++) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . sprintf() de la bibliothque C . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . istringstream et ostringstream . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Un exemple complet avec divers formatages . . . . . . . . . . . . . . . . . . . . . . . Le printf Java du JDK 1.5. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . istrstream et ostrstream. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Formatage en Java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Filtrer du texte en Java avec StringTokenizer et StreamTokenizer . . Rsum . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Exercices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
CHAPITRE 10

Variations sur un thme de classe . . . . . . . . . . . . . . . . . . . . . . . . . .


Le constructeur par dfaut en C++ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Le constructeur de copie en C++ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Le constructeur de copie par dfaut . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . La forme du constructeur de copie . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Ne pas confondre constructeur et affectation . . . . . . . . . . . . . . . . . . . . . . . Le constructeur par dfaut en Java . . . . . . . . . . . . . . . . . . . . . . . . . . . . Le constructeur de copie en Java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Les variables et mthodes statiques dune classe . . . . . . . . . . . . . . . . . Nous tirons un numro . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

205 205 209 209 210 212 213 215 216 216

XIV

Apprendre Java et C++ en parallle

En C++ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . En Java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

216 219 222 222 223 223

nalize() en Java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Un dernier exemple en Java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Rsum . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Exercices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .


CHAPITRE 11

Manipuler des objets en Java et C++ . . . . . . . . . . . . . . . . . . . . . . . .


Loprateur = ou un exercice daffectation . . . . . . . . . . . . . . . . . . . . . . Commenons en Java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Poursuivons en C++ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Crer un oprateur = . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Lincontournable classe string en C++ . . . . . . . . . . . . . . . . . . . . . . . . . . Recommandation dordre des mthodes. . . . . . . . . . . . . . . . . . . . . . . . . . . Retour la source . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Le clonage dobjet en Java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Surcharge doprateurs en C++, et nos amis friend . . . . . . . . . . . . . . . Surcharge doprateur. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Pas de surcharge doprateur en Java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Les friend, ces amis qui nous donnent laccs . . . . . . . . . . . . . . . . . . . . . . Amis : un exemple plus complet. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Faut-il viter les amis (friend) ? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Rsum . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Exercices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
CHAPITRE 12

225 225 225 227 230 232 235 236 236 238 238 240 241 243 245 247 248

Un hritage attendu . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Lexemple de java.lang.Integer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . La rutilisation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Hritage et composition . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Lencapsulation des donnes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . La syntaxe de lhritage en Java et C++ . . . . . . . . . . . . . . . . . . . . . . . . . Linitialisation des constructeurs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

249 249 251 251 254 255 256

Table des matires

XV
260 260 260 261 262 265 267 267 269 270 273 273 274 275 276 277 277

Combiner hritage et composition . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Accs public, priv ou protg . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Le polymorphisme . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .


Les Schtroumpfs en Java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Les Schtroumpfs en C++ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Le virtual en C++ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

Les classes abstraites en C++ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .


Fonction purement virtuelle en C++ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Destructeur virtuel en C++. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

Les classes abstraites en Java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Le transtypage (casting) dobjet . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .


Le transtypage en Java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Comment viter le transtypage. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Le transtypage en C++ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

Lhritage en Java et en C++ : les diffrences . . . . . . . . . . . . . . . . . . . . Rsum . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Exercices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .


CHAPITRE 13

Des hritages multiples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .


Hritage multiple en C++ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Hritage multiple en Java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Dnition dune interface en Java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Jai dj hrit, que faire avec mon Thread ? . . . . . . . . . . . . . . . . . . . . . . . Une interface au lieu dun hritage classique. . . . . . . . . . . . . . . . . . . . . . . Des constantes dans une interface Java . . . . . . . . . . . . . . . . . . . . . . . . . . . Grouper des constantes dans une interface. . . . . . . . . . . . . . . . . . . . . . . . .

279 279 282 283 284 285 286 287 287 287 290 291 292

Srialisation et clonage dobjets en Java . . . . . . . . . . . . . . . . . . . . . . . .


Srialiser des objets Java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Le clonage dobjet . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

Rsum . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Exercices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

XVI

Apprendre Java et C++ en parallle

CHAPITRE 14

Devenir collectionneur . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Le vector en C++ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Utiliser un itrateur . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Les algorithmes du langage C++ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . La classe vector en C++ et lalgorithme sort() . . . . . . . . . . . . . . . . . . . . . . La classe list en C++ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Linterface List en Java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Linterface Set en Java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Une liste de tlphone en Java avec HashMap . . . . . . . . . . . . . . . . . . . . La mme liste de tlphone avec map en C++ . . . . . . . . . . . . . . . . . . . . Les types gnriques en Java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Un premier exemple simple . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Autoboxing et Fibonacci . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Rsum . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Exercices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
CHAPITRE 15

293 294 295 296 298 300 301 304 306 308 310 310 312 314 314

Concours de performance . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Comment analyser les performances ? . . . . . . . . . . . . . . . . . . . . . . . . . . Les outils en Java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Les outils en C++ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Gagner en performance : une rude analyse . . . . . . . . . . . . . . . . . . . . . . Que peut apporter une meilleure analyse ?. . . . . . . . . . . . . . . . . . . . . . . . . Passage par valeur ou par rfrence en C++ . . . . . . . . . . . . . . . . . . . . . Performance et capacit mmoire . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Les entres-sorties . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Lecture de chiers en C++ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Inuence de lappel de fonctions successives. . . . . . . . . . . . . . . . . . . . . . . Lecture de chiers en Java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Tests globaux de performance . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Avec system() en C++ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Avec exec() en Java. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

315 315 316 317 320 322 322 324 324 325 327 328 330 330 331

Table des matires

XVII
332 333 333

Autres calculs de performance ou de contraintes . . . . . . . . . . . . . . . . . Rsum . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Exercices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .


CHAPITRE 16

Comment tester correctement ? . . . . . . . . . . . . . . . . . . . . . . . . . . . .


Le Y2K bug . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Une stratgie de test ds la conception . . . . . . . . . . . . . . . . . . . . . . . . . . Avec ou sans dbogueur . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Les tests de base . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . La fonction extraction() en C++. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Le programme de test de la fonction extraction () . . . . . . . . . . . . . . . . . . . Le programme de test extraction () en Java . . . . . . . . . . . . . . . . . . . . . . . . Suivre la trace . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Dnition du problme. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . La classe Traceur en C++ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Tester la classe Traceur en C++ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . La classe Traceur en Java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Encore des amliorations pour notre traceur ? . . . . . . . . . . . . . . . . . . . . . . Rsum . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Exercices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
CHAPITRE 17

335 335 335 336 336 337 338 340 341 341 341 343 344 347 348 348

Ces fameux patterns . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .


Qui sont donc ces fameux patterns ? . . . . . . . . . . . . . . . . . . . . . . . . . . . . Les patterns Singleton et Observer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Le Singleton ou le constructeur protg . . . . . . . . . . . . . . . . . . . . . . . . . Le Singleton en Java. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Le Singleton en C++ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Le pattern Observer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Java MVC : linterface Observer et la classe Observable . . . . . . . . . . . . . Le pattern Observer en Java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Rsum . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Exercices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

349 349 349 350 350 351 353 353 354 355 356

XVIII

Apprendre Java et C++ en parallle

CHAPITRE 18

Un livre sur Java sans lAWT ! . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .


Apprendre programmer avec des applications graphiques . . . . . . . Le code de notre premire application AWT . . . . . . . . . . . . . . . . . . . . . Classes anonymes et classes internes . . . . . . . . . . . . . . . . . . . . . . . . . . . . Sadapter aux vnements traditionnels de lAPI . . . . . . . . . . . . . . . . . . . Et si on sadaptait dautres types dvnements ? . . . . . . . . . . . . . . . . . Applets ou applications . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . init() et start() pour une applet . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Un mot sur les servlets . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . get(), set() et les JavaBeans . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Quest-ce quun Bean ? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Beans et C++ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Rsum . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Exercice . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
CHAPITRE 19

357 358 358 360 362 363 365 369 370 370 371 371 371 372

Un livre sur C++ sans templates ! . . . . . . . . . . . . . . . . . . . . . . . . . . .


Les modles ou patrons en C++ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Un modle de fonction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Un modle de classe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Rsum . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Exercice . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
CHAPITRE 20

373 373 374 376 377 377

Impossible sans SQL ! . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .


Cration dun chier dlimit en C++ . . . . . . . . . . . . . . . . . . . . . . . . . . Cration dune base de donnes sous Microsoft Access . . . . . . . . . . . . Activation dODBC - XP . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Activation dODBC - Vista. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Accs ODBC partir de Java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Requte SQL. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Cration dune nouvelle table depuis Java . . . . . . . . . . . . . . . . . . . . . . .

379 379 381 382 384 386 388 389

Table des matires

XIX
391 392 392 392

MySQL et Linux : recommandations . . . . . . . . . . . . . . . . . . . . . . . . . . . Autres choix dinterfaces . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Rsum . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Exercices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .


CHAPITRE 21

Java et C++ main dans la main : le JNI . . . . . . . . . . . . . . . . . . . . . .


Pourquoi et comment utiliser JNI ? . . . . . . . . . . . . . . . . . . . . . . . . . . . . Des salutations dune bibliothque C++ . . . . . . . . . . . . . . . . . . . . . . . . . javah pour le code C++ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Cration de notre salut.dll . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . JNI, passage de paramtres et Swing . . . . . . . . . . . . . . . . . . . . . . . . . . . Notre interface Swing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Rsum . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
CHAPITRE 22

393 393 394 395 396 397 401 405

Quelques applications usuelles . . . . . . . . . . . . . . . . . . . . . . . . . . . . .


Coupons et collons . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Nous coupons en Java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Nous coupons en C++ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Un exemple de chier .acr . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Recollons les morceaux . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Nous collons en Java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Nous collons en C++ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Un message sur notre tlphone mobile . . . . . . . . . . . . . . . . . . . . . . . . . Programmons le jeu dOthello . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Les rgles du jeu . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . La conception . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Le jeu dOthello en C++. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Le jeu dOthello en Java. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Suggestions dautres applications . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Archiver des chiers. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Tlcharger un site Web entier . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Rsum . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

407 407 408 411 416 416 416 419 423 428 428 428 429 431 436 436 436 436

XX

Apprendre Java et C++ en parallle

CHAPITRE 23

Ltape suivante : le langage C# de Microsoft . . . . . . . . . . . . . .


Que vient donc faire le C# dans cet ouvrage ? . . . . . . . . . . . . . . . . . . . . Un peu dhistoire . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . C++, Java et C# : les diffrences majeures . . . . . . . . . . . . . . . . . . . . . . . . . Hello world en C# . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Les Makele avec C# . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Espace de noms . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Les structures en C# . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . La classe Personne du chapitre 4 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Couper et coller en C# . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Rsum . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

437 437 438 439 439 444 444 446 449 451 457

ANNEXES
ANNEXE A

Contenu du CD-Rom . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
ANNEXE B

461

Installation des outils de dveloppement pour Java et C++


Installation de 7-Zip . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Installation des exemples et des exercices . . . . . . . . . . . . . . . . . . . . . . . . Installation du JDK de Sun Microsystems . . . . . . . . . . . . . . . . . . . . . . . Quest-ce que le JDK ? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Dsinstallation des anciennes versions. . . . . . . . . . . . . . . . . . . . . . . . . . . . Tlchargement partir du site Web de Sun Microsystems . . . . . . . . . . . . Installation partir du CD-Rom . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Installation de MinGW (g++) et MSYS . . . . . . . . . . . . . . . . . . . . . . . . . Installation simplie de MinGW et MSYS. . . . . . . . . . . . . . . . . . . . . . . . Vrications nales et dernires mises au point . . . . . . . . . . . . . . . . . . Vrication de linstallation des outils . . . . . . . . . . . . . . . . . . . . . . . . . . . . Le fameux chapitre 21 avec JNI . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Le chier source src.jar. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

463 464 466 467 467 467 467 468 471 472 474 474 476 478

Table des matires

XXI
482 483 487 488 489 489 492 492 493 494 494 495

Installation de la documentation. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Raccourci ou favori . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

MinGW et MSYS sur Internet . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Problmes potentiels avec le make et MSYS . . . . . . . . . . . . . . . . . . . . . . . Les outils Linux de MSYS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . La commande msys.bat . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . La commande cd . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Les commandes ls et pwd. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Copie dans un chier . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Emploi du pipe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Awk, lun des outils essentiels de Linux . . . . . . . . . . . . . . . . . . . . . . . . . . Un script de sauvegarde . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
ANNEXE C

Installation et utilisation de Crimson . . . . . . . . . . . . . . . . . . . . . . .


Site Web de Crimson . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Installation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Rinstallation de Crimson . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Conguration prpare . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Installation partir du CD-Rom. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Association des chiers Crimson dans lexplorateur . . . . . . . . . . . . Installation dun raccourci . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Premier dmarrage de Crimson . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Demande dautorisation sous Windows Vista . . . . . . . . . . . . . . . . . . . . . . Glisser les chiers depuis lexplorateur . . . . . . . . . . . . . . . . . . . . . . . . . Conguration de Crimson . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Conguration des menus . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Fermer toutes les fentres. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Remarques gnrales . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Exercices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
ANNEXE D

499 500 500 500 501 501 503 505 505 506 507 508 510 519 520 521

Installation du SDK du Framework de .NET . . . . . . . . . . . . . . . .


Installation du SDK 3.5 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

523 523

XXII

Apprendre Java et C++ en parallle

Tlchargement de .NET depuis Internet . . . . . . . . . . . . . . . . . . . . . . . Pas de nouveau PATH. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Vrication de linstallation du .NET Framework SDK . . . . . . . . . . . . . . Documentation du SDK de .NET . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Le compilateur du langage C# . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Visual C# - Ldition Express. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
ANNEXE E

526 527 528 528 532 533

Apprendre Java et C++ avec NetBeans . . . . . . . . . . . . . . . . . . . . .


Gnralits . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Linux. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Tlchargement de nouvelles versions . . . . . . . . . . . . . . . . . . . . . . . . . . Documentations et didacticiels . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Installation partir du CD-Rom . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Conguration pour le C++ et le make . . . . . . . . . . . . . . . . . . . . . . . . . . . . Prsentation de NetBeans . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . NetBeans et Java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Java et la classe Personne . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Nouveau projet avec source existante. . . . . . . . . . . . . . . . . . . . . . . . . . . . . Distribuer nos applications . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Naviguer et dboguer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Javadoc . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . UML Diagramme de classe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . NetBeans et C++ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Le jeu dOthello dans NetBeans . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Cration du projet C++ dans NetBeans . . . . . . . . . . . . . . . . . . . . . . . . . . . Dboguer un projet C++ avec NetBeans . . . . . . . . . . . . . . . . . . . . . . . . . . Conclusion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
ANNEXE F

535 535 536 536 536 536 546 547 547 547 549 556 556 559 563 565 565 567 575 577

Apprendre Java et C++ avec Linux . . . . . . . . . . . . . . . . . . . . . . . . . .


Dmarrage de Linux . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Installation des outils . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Vrication de linstallation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Les exemples du chapitre 1. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

579 580 581 582 583

Table des matires

XXIII
585 587

gedit comme diteur Linux. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . NetBeans sous Linux . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .


ANNEXE G

Dans la littrature et sur le Web . . . . . . . . . . . . . . . . . . . . . . . . . . . . .


Dans la littrature . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Sur le Web . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Le projet GNU . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Les compilateurs GNU pour C et C++. . . . . . . . . . . . . . . . . . . . . . . . . . . . Les newsgroups . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . GNU EMACS. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . C++. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . C# . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Perl et Python . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Le Web lui-mme . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Autres recherches dinformations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Rechercher des sujets de travaux pratiques . . . . . . . . . . . . . . . . . . . . .

589 589 591 592 592 592 592 592 593 593 593 593 594 594

Index . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

597

Avant-propos
Pourquoi un tel ouvrage ?
Les rponses cette question sont multiples. Au cours de cette prface, nous essayerons dy rpondre sans trop de philosophie et de dbats contradictoires. Nous commencerons par deux raisons videntes : Ces deux langages de programmation, Java et C++, sont trs semblables, tout au moins dans leurs syntaxes. Il est ainsi tout fait possible de reprendre un morceau de code du premier langage et de lappliquer sans adaptation dans le second. Ils sont tous les deux trs populaires dans le monde de linformatique, le premier avec la venue dInternet, le second comme langage systme essentiel. Pour assurer une progression constante et logique, au travers dune comparaison directe de ces deux langages, il nous a fallu structurer la prsentation du livre en fonction des particularits de chacun. Cela na pas t facile et nous avons accept le d de tenter une telle exprience. Il a t videmment impossible de couvrir tous les dtails de Java et de C++ car nous avons voulu que cet ouvrage conserve une paisseur raisonnable.

qui sadresse ce livre ?


Nous aimerions dire aux dbutants, mais ce ne serait sans doute pas honnte vis--vis des experts et des gourous C++, car ce dernier langage est considr comme lun des plus difciles assimiler et matriser. Nous pensons aussi aux programmeurs Basic ou, mieux encore, ceux favoriss par leurs connaissances dans des langages plus orients objet, comme Delphi, le langage Pascal de Borland ou le C#. Comme autres cas de gure, nous citerons galement les programmeurs Java, Smalltalk, C ou C++ traditionnels qui aimeraient sinitier lun de ces deux langages essentiels et les ajouter leur curriculum vitae.

Quel est lintrt dapprendre plusieurs langages ?


Dans le cas prcis, comme ces deux langages sont trs proches, autant les apprendre en parallle. Un autre aspect, encore plus essentiel, est lapprentissage de leurs diffrences,

XXVI

Apprendre Java et C++ en parallle

de leurs qualits et de leurs dfauts, ce qui nous amnera ainsi programmer en utilisant des techniques varies et rchies. Le rsultat sera un code beaucoup plus propre, conu selon une vision plus large des systmes, de leurs possibilits et de leurs limites. Pour un programmeur, aussi bien dbutant que professionnel, lapprentissage dun nouveau langage est avant tout enrichissant. Une fonctionnalit manquante dans un langage lamnera une remise en question de ses mthodes. Par exemple, quand un programmeur C++ dcouvre quil nexiste pas de destructeur en Java, il va se poser toute une srie de questions. En n de compte, cette rexion pourra sans aucun doute le conduire rviser ses concepts et crire ses programmes C++ diffremment. De la mme manire, un programmeur C qui cherche apprendre C++ et Smalltalk en parallle, ce qui est loin dtre une mauvaise ide, va sans doute dcouvrir de nouveaux termes comme celui de variable de classe et se mettra douter de ses vieilles habitudes en programmation C. Enn, un programmeur qui, en guise de premier exercice en Java, dcide de transfrer une page Web sur son site, va certainement remettre en question ses choix lorsquil devra employer tel ou tel langage pour ses applications. En n douvrage, dans le dernier chapitre, nous donnerons un aperu du langage de Microsoft n en 2001, le C#. Nous serons alors surpris de retrouver la plupart des thmes dj abords avec Java et C++ et combien il sera ais alors dcrire un premier programme dans ce langage. Le lecteur se rendra compte alors que son bagage informatique est dj solide. Dans cet ouvrage, nous parlerons aussi de performances, ainsi que de la manire de vrier et de tester les programmes. Ces deux aspects font partie de la formation des informaticiens ; ils sont importants dans lacquisition des mthodes de travail et des connaissances techniques pour concevoir et dvelopper des applications professionnelles.

Quelles versions de Java et de C++ ?


Il faut tout dabord indiquer que cet ouvrage ne couvre que le bien nomm pure Java . Il en va de mme pour le C++ que nous appelons plus prcisment le Standard C++ . Un grand nombre de fonctions intgres dans le JDK 1.6 (ofciellement nomm JDK 6) peuvent tre directement compares celles disponibles dans le Standard C++. Le code C++ prsent dans cet ouvrage, tout comme le code en Java, devrait de plus pouvoir se compiler et sexcuter dans plusieurs environnements comme Windows ou Linux. Les exemples contenus dans ce livre couvrent des domaines varis et parfois complexes. Cependant, le code prsent restera compilable, nous aimerions dire pour lternit, et lacheteur de ce livre naura pas besoin dacqurir une nouvelle dition dans les prochains mois. Le langage Java a beaucoup volu ces dernires annes, au contraire du C++ qui a atteint une stabilit naturelle. Aujourdhui, nous parlons de Java 2 (Swing, Beans), Java 5 ou Java 6, qui reprsentent les versions de 1.2 1.4, 1.5 et 1.6 respectivement, des JDK de Sun Microsystems. Certains concepts et constructions de la version 1.1 ont t remplacs par des techniques plus volues. Dans cet ouvrage, nous laisserons volontairement de

Avant-propos

XXVII

ct la plupart des mthodes et des classes dprcies des versions antrieures la 1.6. Nous mentionnerons aussi quelques nouveauts apparues depuis la version 1.5.

Pourquoi le Standard C++ ?


Pourquoi avons-nous choisi le Standard C++ et non pas le langage C ou le C++ traditionnel ? Il y a en fait plusieurs raisons cela. La principale concerne les nouvelles bibliothques qui ont t rajoutes partir de 1995 et qui font partie du langage au mme titre que celles, nombreuses, qui font partie de Java. Comme exemple, nous pourrions citer la classe string qui nexiste pas dans le C++ classique. Certaines classes des premires versions du Standard C++ ont t dprcies et corriges dans cette dition. Le Standard C++ est maintenant aussi agr par les standards ANSI et ISO et reprsente une base encore plus solide pour un langage qui ne devrait plus beaucoup voluer selon les dires de son crateur, Bjarne Stroustrup.

Comment est prsent cet ouvrage ?


Lors de la prsentation des langages, nous commenons en gnral par le C++ et poursuivons ensuite avec la partie Java. En effet, pourquoi ne serions-nous pas respectueux avec le plus vieux dabord ? En cas de difcults, suivant les connaissances du programmeur, il est tout fait possible de passer plus rapidement sur un sujet et dy revenir par la suite. Pour un programmeur Visual Basic, sinitier en Java et C++ va reprsenter un travail important, car la syntaxe sera toute nouvelle pour lui. Dans ce cas-l, il devra sans doute se concentrer dabord sur la partie Java et revenir ensuite sur le C++ avec ses particularits et ses variantes plus complexes. Si nous avions trait lun ou lautre de ces langages sparment, la structure de cet ouvrage aurait t totalement autre. Java et C++ possdent en effet quelques particularits foncirement diffrentes qui auraient pu tre exposes dune autre manire. Ici, la difcult principale rside dans les premiers chapitres o, pour prsenter des exemples qui tiennent debout, nous avons souvent besoin de connatre certains aspects du langage qui seront examins dans les chapitres suivants, et ce tout en traitant en parallle les deux langages. Tous les exemples de code prsents dans les diffrents chapitres ont t compils sparment et souvent sous diffrents systmes dexploitation. Ils sont bien videmment prsents sur le CD-Rom accompagnant cet ouvrage.

De quel matriel a-t-on besoin ?


Cet ouvrage est conu pour travailler dans un environnement Windows (XP ou Vista). Dans lannexe F, nous montrerons quil est aussi possible dutiliser Linux. Un systme quip dun processeur cadenc 450 MHz et de 256 Mo de mmoire vive est sufsant pour assurer une compilation des programmes et garantir un environnement

XXVIII

Apprendre Java et C++ en parallle

agrable pour le dveloppement. Le CD-Rom fourni avec cet ouvrage contient tous les outils de dveloppement ncessaires ainsi que NetBeans (voir annexe E) pour Java et C++. Pour pouvoir utiliser correctement NetBeans, un processeur rcent et 1 Go de mmoire sont recommands. Nous trouverons aussi, sur ce mme CD-Rom, un diteur de texte et la version la plus rcente du SDK de la plate-forme Framework .NET de Microsoft qui inclut un compilateur C#.

Pourquoi autant dexemples et dexercices ?


notre avis, il ny en a jamais assez. Lorsque nous dbutons en programmation, nous commenons par crire de petits morceaux de programmes. Ensuite, nous passons des exercices plus complexes, de petits outils pour grer notre travail ou mme crire de petits jeux intelligents. Lessentiel est dcrire du code, beaucoup de code. Un bon programmeur sera toujours un collectionneur dexemples, dessais et de programmes, quil pourra rapidement retrouver lorsquil en aura besoin. Pratiquement tous les exercices de ce livre sont proposs dans les deux langages, sauf si cela est explicitement indiqu. Les solutions sont disponibles sur le CD-Rom daccompagnement. Des Makefile (GNU make) sont disposition pour chaque chapitre et permettent de recompiler le code en cas de besoin. Les exercices ont donn lauteur loccasion de vrier la consistance et la structure de cet ouvrage. Ils peuvent aussi permettre aux lecteurs de suivre les progrs de leur apprentissage.

Commentaires et suivi de louvrage


Lauteur apprcierait de recevoir des commentaires ou des critiques de son ouvrage ladresse lectronique suivante :
: jean-bernard@boichat.ch Objet: Java et C++

Sur le site Web :


http://www.boichat.ch/javacpp/

nous pourrons trouver dventuelles corrections dcouvertes dans les exemples et les exercices, ainsi que des remarques pertinentes ou des difcults rencontres par certains lecteurs. Nous aurons galement notre disposition des informations sur les nouvelles versions des compilateurs et les nouveaux outils, ainsi quune liste de sites et douvrages intressants et recommands. Toutes ces informations seront rgulirement mises jour par lauteur.

1
Lincontournable Hello world
Nous le retrouvons dans presque tous les ouvrages de programmation. Il vient toujours en tte ! Ce sera aussi pour nous loccasion de nous familiariser avec lenvironnement de dveloppement. Dans ce chapitre, il sagira tout dabord dditer les chiers source, savoir hello.cpp et Hello.java. Sur le CD-Rom daccompagnement gure un diteur bien pratique pour Windows, que nous pouvons utiliser pour les diffrentes tapes de la programmation de nos exemples ou exercices : lditeur Crimson (voir annexe C). Lenvironnement de Crimson permet dditer les programmes source C++ et Java qui sont des chiers de texte ASCII, de les compiler et de les excuter. La compilation et le rsultat de lexcution des programmes peuvent safcher dans une fentre de travail intgre Crimson ; cest donc loutil idal pour cela. Quant aux utilisateurs de Linux, ils ne devraient pas avoir de difcults choisir les outils adapts aux tches dcrites ci-dessous (voir annexe F). Dans ce chapitre, nous allons aussi introduire le fameux make de GNU, qui nous permettra dautomatiser la compilation. Avant de pouvoir utiliser le make ainsi que les compilateurs Java et C++ et de les intgrer dans lditeur Crimson, il faudra tout dabord installer ces produits. Ils sont tous disponibles sur le CD-Rom, comme dcrit dans lannexe B. Un autre diteur que Crimson peut videmment tre utilis, mais il faudra alors compiler les programmes avec le make et les excuter dans une fentre DOS (procdures dcrites dans lannexe B). Un outil Open Source beaucoup plus complet, NetBeans, peut aussi tre utilis (voir les annexes E et F pour son installation et son utilisation travers des exemples complets). Nous avons tendu lexercice traditionnel en ajoutant la date, qui sera imprime avec notre incontournable Hello world .

Apprendre Java et C++ en parallle

Hello world en C++


La premire tche consiste introduire le code source dans le chier hello.ccp via lditeur Crimson tel que dcrit dans lannexe C.
// hello.cpp #include <ctime> #include <iostream> using namespace std; int main() { time_t maintenant; time(&maintenant); cout << "Hello world en C++: " << ctime(&maintenant) << endl; return 0; }

Avant de passer la compilation, nous allons examiner ce code dans les dtails, en laissant de ct le main() et le cout, qui seront analyss et compars entre les deux langages un peu plus loin. Il en va de mme pour le :
using namespace std;

qui identie un espace de noms, que nous tudierons au chapitre 6. Nous pouvons dj signaler ici que lutilisation de namespace nest disponible que dans le Standard C++. Cet emploi est li la disparition en Standard C++ des .h dans les en-ttes. Si nous avions crit le code suivant :
// hello2.cpp #include <time.h> #include <iostream.h> int main() { time_t maintenant; time(&maintenant); cout << "Hello world2 en C++: " << ctime(&maintenant) << endl; return 0; }

nous aurions obtenu en fait le mme rsultat, except que cette dernire version est plus familire aux programmeurs C et C++ traditionnels ! En le compilant, nous obtiendrions ces remarques de codes dprcis :
C:/MinGW/bin/../lib/gcc/mingw32/3.4.5/../../../../include/c++/3.4.5/backward/ iostream.h:31, from hello2.cpp:4: #warning This file includes at least one deprecated or antiquated header.

Lincontournable Hello world CHAPITRE 1

Le binaire (hello2.exe) serait tout de mme gnr et correctement excutable. Pour en revenir la premire version, les :
#include <ctime> #include <iostream> using namespace std;

vont en effet de pair, et nous les utiliserons tout au long de cet ouvrage. Nous travaillerons presque exclusivement avec les formes sans les .h, cest--dire avec le Standard C++, mme si, de rares occasions, il nous arrivera dutiliser des .h, cest--dire des fonctions C qui ne sont pas disponibles dans la bibliothque du Standard C++. Les #include <ctime> et #include <iostream> sont des chiers den-ttes ncessaires la dnition des classes et des objets utiliss. Si time_t, time et ctime ntaient pas introduits, la ligne #include <ctime> ne serait pas obligatoire. Il faut noter que le chier <ctime> du Standard C++ contient en fait linclude <time.h>, qui est un hritage des fonctions C. Nous reviendrons sur la directive include au chapitre 4, section Directive include . Dans <iostream>, nous avons la dnition de cout et de son oprateur <<.
time_t est un type dni dans la bibliothque ANSI C++ permettant de dnir la variable maintenant, qui sera utilise pour obtenir la date actuelle au travers de time(&maintenant). Nous reviendrons plus loin sur le &, qui doit tre ajout devant la variable car la fonction C time() sattend recevoir une adresse. Le rsultat sera obtenu au moyen de << ctime (&maintenant), comme nous allons le voir ci-dessous. Le chanage des oprateurs <<

est noter, ce qui permet denvoyer une srie de blocs de donnes sans devoir les dnir sur des lignes spares. Le chier hello.cpp est ensuite compil par le compilateur C++, accessible par le chemin daccs PATH dcrit dans lannexe B. Cette compilation se fait de la manire suivante :
g++ -o hello.exe hello.cpp

Le paramtre o de g++ indique que le nom hello.exe correspond au chier binaire compil partir des chiers source inclus dans la ligne de commande (ici, hello.cpp). Avec Linux, nous pourrions utiliser :
g++ -o hello hello.cpp

Les chiers hello.exe ou hello sont les excutables. Par la suite, nous utiliserons systmatiquement lextension .exe, qui est ncessaire sous DOS. Un programme nomm hello.exe pourra aussi sexcuter sous Linux, qui possde une proprit dexcutable pour le chier. Ce nest pas le cas sous DOS, o les programmes doivent se terminer avec une extension dtermine comme .bat, .com ou .exe. Lorsque nous excutons le programme hello.exe, on obtient :
Hello world en C++: Mon Mar 31 14:55:20 2008

Apprendre Java et C++ en parallle

Le chier hello.exe, nomm parfois excutable binaire, contient le code machine que le processeur pourra directement excuter grce aux fonctions du systme dexploitation (DOS ou Linux). Il sexcutera uniquement sur les machines ayant le mme systme dexploitation. Le programme hello.exe peut aussi ne pas fonctionner sur un PC dot dun processeur ancien, car le compilateur naura sans doute pas assur de compatibilit avec les modles antrieurs, par exemple, au Pentium 586 dIntel. Pour fonctionner sous Linux, avec dautres systmes dexploitation ou dautres types de congurations matrielles, il faudra recompiler le programme hello.cpp sur ce type de machine. Un programme C ou C++ compilable sur diffrents supports est communment appel portable. Nous allons voir que cela se passe bien diffremment avec Java.

Hello world en Java


Avant tout, une premire remarque simpose concernant le nom des chiers. Comme le nom dune classe commence toujours par une majuscule, il est ncessaire de dnir le nom du chier selon le mme principe, cest--dire ici Hello.java. Faillir cette rgle est une erreur classique, et la plupart des dbutants tombent dans le pige. En effet, si on oublie ce principe, cette classe ne pourra tre compile :
import java.util.*; public class Hello { public static void main(String[] args) { Date aujourdhui; aujourdhui = new Date(); System.out.println("Hello world en Java: " + aujourdhui); } }

En Java, le traitement de la date est vraiment trs simple. Nous navons plus des variables ou des structures compliques comme en C ou en C++, mais simplement un objet. aujourdhui est ainsi un objet de la classe Date qui est cr grce loprateur new. Les dtails de la mthode println sont expliqus plus loin. La compilation seffectue ainsi :
javac Hello.java

et nous lexcutons par :


java Hello

ce qui nous donne comme rsultat :


Hello world en Java: Mon Mar 31 14:55:44 GMT+02:00 2008

Nous remarquons ici que nous navons pas de Hello.exe. En effet, le processus se droule diffremment en Java : aprs la compilation de Hello.java, avec javac.exe (qui est, comme pour C++, un excutable binaire diffrent sur chaque systme dexploitation), un chier compil Hello.class est gnr. Pour excuter Hello.class, nous utilisons le

Lincontournable Hello world CHAPITRE 1

programme java.exe, qui est la machine virtuelle de Java. Notons quil faut enlever dans la commande lextension .class. java.exe trouvera alors un point dentre main() dans Hello.class et pourra lexcuter. Si la classe Hello utilise dautres classes, elles seront charges en mmoire part la machine virtuelle si ncessaire. Le GMT (Greenwitch Mean Time) pourrait tre diffrent suivant linstallation et la conguration du PC, la langue ou encore la rgion. Un CET, par exemple, pourrait tre prsent : Central European Time. Une fois que Hello.java a t compil en Hello.class, il est alors aussi possible de lexcuter sur une autre machine possdant une machine virtuelle Java, mais avec la mme version (ici 1.6) ou une version suprieure. Le chier Hello.class sera aussi excutable sous Linux avec sa propre machine virtuelle 1.6. Si la machine virtuelle ne trouve pas les ressources ncessaires, elle indiquera le problme. Dans certains cas, il faudra recompiler le code Java avec une version plus ancienne pour le rendre compatible. Nous constatons donc quen Java, le chier Hello.class ne contient pas de code machine directement excutable par le processeur, mais du code interprtable par la machine virtuelle de Java. Ce nest pas le cas du programme C++ hello.exe, qui utilise directement les ressources du systme dexploitation, cest--dire de Windows. Lapplication hello.exe ne pourra pas tre excute sous Linux et devra tre recompile (voir les exemples de lannexe F).

La machine virtuelle Java JRE


Dans cet ouvrage, nous devons tout de mme mentionner la machine virtuelle JRE (Java Runtime Environment) bien que nous allons certainement passer la moiti de notre temps diter et compiler des programmes Java (lautre moiti pour le C++). Sur notre machine de dveloppement, lexcutable java.exe se trouve dans le rpertoire C:\Program Files\Java\jdk1.6.0_06\bin. Si nous examinons le rpertoire C:\Program Files\Java, nous dcouvrirons un second rpertoire nomm jre1.6.0_06 ainsi quun sous-rpertoire bin qui contient galement un chier java.exe. Nos amis ou clients qui nous livrerons la classe compile Hello.class nauront pas besoin dinstaller JDK, le kit de dveloppement, mais uniquement la machine virtuelle, cest--dire JRE. Sun Microsystems met disposition diffrentes distributions de JRE, juste pour excuter notre Hello.class :
"C:\Program Files\Java\jre1.6.0_06\bin\java.exe" Hello Hello world en Java: Wed Jul 30 13:58:09 CEST 2008

et ceci depuis le rpertoire C:\JavaCpp\EXEMPLES\Chap01. Le JDK nest ncessaire que pour la compilation, cest--dire lorsque nous utilisons la commande javac.exe. Sur une mme machine, nous pourrions avoir plusieurs JDK et plusieurs JRE dans le rpertoire C:\Program Files\Java (voir annexe B, section Dsinstallation des anciennes versions ).

Apprendre Java et C++ en parallle

Erreurs de compilation
Loubli de la dclaration des ressources est une erreur classique. Si nous effaons la premire ligne (import java.util.*;) ou si nous la mettons en commentaire (// devant), nous gnrerons lerreur suivante :
javac Hello.java Hello.java:5: Class Date not found. Date aujourdhui; ^ Hello.java:6: Class Date not found. aujourdhui = new Date(); ^ 2 errors

Il est noter la manire claire dont le compilateur nous indique la position des erreurs. Ici, il ne trouve pas la classe Date. En ajoutant import java.util.*, nous indiquons au compilateur dimporter toutes les classes du package (paquet) des utilitaires de Java. Au lieu dimporter toutes les classes du package, nous aurions pu crire :
import java.util.Date;

Compiler rgulirement est une trs bonne habitude en Java et C++, en crivant de petits morceaux et avant de terminer son code. Le simple oubli dun point-virgule en C++ (qui indique la n dune instruction) peut entraner une erreur quelques lignes plus loin et faire perdre inutilement du temps prcieux.

Notre premier chier Makele


Pour compiler nos programmes Java et C++, nous allons utiliser tout au long de cet ouvrage un outil GNU bienvenu : le make. Le chier Makefile est le chier utilis par dfaut lorsque la commande make est excute sans paramtre. Cette commande est un hritage du monde Unix (Linux) que nous retrouverons aussi avec NetBeans (voir annexe E, section Conguration pour le C++ et le make ). Le Makefile possde une syntaxe trs prcise base sur un systme de dpendances. Le make pourra identier, en utilisant la date et lheure, quune recompilation ou une action devra tre excute. Dans le cas de chiers Java, si un chier .java est plus rcent quun chier .class, nous devrons considrer quun chier .class devra tre rgnr. Loutil make permet donc dautomatiser ce processus. Voici notre tout premier exemple de Makefile, soit le chier MakefilePremier, dont nous allons expliquer le fonctionnement. Les Makefile sont des chiers texte ASCII que nous pouvons aussi diter et excuter sous Windows avec lditeur Crimson (voir annexe C) qui va nous simplier le travail.
# # Notre premier Makefile # all: cpp java

Lincontournable Hello world CHAPITRE 1

cpp: java: hello.exe:

hello.exe hello2.exe Hello.class hello.o g++ -o hello.exe hello.o hello.cpp g++ -c hello.cpp hello2.o g++ -o hello2.exe hello2.o hello2.cpp g++ -c hello2.cpp

hello.o:

hello2.exe:

hello2.o:

Hello.class: Hello.java javac Hello.java clean: rm -f *.class *.o *.exe

Avant dexcuter ce Makefile, il faudrait sassurer que tous les objets sont effacs. Dans le cas contraire, nous risquons de navoir aucun rsultat tangible ou partiel, bien que cela resterait correct : uniquement les chiers objets (.o, .exe et .class), dont les sources respectives ont t modies plus rcemment, seront recompils ! Nous aurions pu fournir un chier efface.bat pour faire ce travail avec les commandes DOS :
del *.exe del *.o del *.class

mais nous avons prfr cette version :


make clean

Lentre clean (nettoyer) va permettre ici deffacer tous les chiers .class, .o et .exe (sils existent) an que le make puisse rgnrer tous les objets et tous les excutables. La commande rm est lquivalent Linux de la commande DOS del et le paramtre f va forcer leffacement sans demander une quelconque conrmation. Un chier efface.bat est fourni dans chaque rpertoire des exemples et des exercices. Le make clean et sa prsence dans le Makefile sont importants : NetBeans (voir annexe E) en a besoin pour construire ses projets C++. Mentionnons ce message que le lecteur pourrait rencontrer lors de son travail :
make: *** No rule to make target `clean'. Stop.

Apprendre Java et C++ en parallle

Il indique quil ny a pas de rgle (rule) pour le point dentre clean. Nous devrons alors le rajouter dans le Makefile ou vrier que la syntaxe du chier est correcte (espaces et marques de tabulations tout particulirement).

Enn un premier make effectif


Pour excuter le chier MakefilePremier, nous devrons entrer la commande :
make f MakefilePremier

En navigant dans le rpertoire, nous pourrons constater que les chiers .o, .exe et .class ont t rgnrs. Lerreur sur la recompilation de hello2.cpp rapparatra videmment (voir ci-dessus). Si nous avons un nom de chier Makefile, comme cest le cas dans tous les chapitres, il nous faudra simplement excuter :
make

Lorsque la commande make est excute, le chier Makefile sera charg et le point dentre all excut. La commande make permet aussi de spcier un point dentre :
make -f MakefilePremier java

Ici, uniquement le point dentre java serait activ pour compiler la classe Hello.class. Les points dentre Hello.class ou hello.exe sont aussi possibles pour un choix plus slectif.
all, cpp et java sont des actions excuter. hello.exe, hello.o et Hello.class sont des chiers gnrs par les compilateurs. Il faut tre trs attentif avec le format des chiers Makefile, car ils sont extrmement sensibles la syntaxe. Aprs les deux-points (:), il est prfrable davoir un tabulateur (TAB), bien que nous puissions avoir des espaces, mais sur la ligne suivante nous avons toujours des tabulateurs. Suivant la grandeur des variables, nous en aurons un ou plusieurs, mais cela dpend aussi de la prsentation que nous avons choisie. Aprs les hello.exe: et hello.o:, nous trouvons les dpendances et sur les lignes suivantes les commandes.

la premire excution du make, cpp et java seront activs, car aucun des hello.o, hello.exe et Hello.class nexiste. En cas derreur de compilation dans hello.cpp, ni hello.o ni hello.exe ne seront crs. Passons maintenant la partie la plus intressante : si hello.cpp est modi, sa date sera nouvelle et prcdera celle de hello.o. Les deux commandes g++ seront alors excutes. Dans ce cas prcis, la sparation en deux parties hello.o et hello.exe nest pas vraiment ncessaire, puisque nous navons quun seul chier hello.cpp. La commande suivante sufrait :
g++ -o hello.exe hello.cpp

Lincontournable Hello world CHAPITRE 1

Enn, que se passe-t-il si nous dnissons :


hello.o: Hello.java hello.cpp g++ -c hello.cpp

hello.o sera rgnr si Hello.java a chang et mme si hello.cpp na pas t touch. On

voit donc que cette dpendance est inutile. Les trois premires lignes du chier MakefilePremier sont des commentaires. Ils commencent par le caractre # :
# # Notre premier Makefile #

Nous pouvons, par exemple, les utiliser pour liminer des parties de compilation pendant le dveloppement :
all: cpp #java

Ici, uniquement la partie C++ sera compile. Nous reviendrons sur les dpendances dues aux chiers den-tte au chapitre 4. Les paramtres -c et -o y seront expliqus en dtail.

Le point dentre main()


La fonction main() est le point dentre de tout programme. Le corps de la fonction situe entre les accolades sera excut. En Java, il nest pas possible de dnir une fonction indpendante, car elle doit faire partie dune classe. De plus, elle doit tre dclare public et static. Nous en comprendrons les raisons plus loin dans cet ouvrage. Contrairement au C++, chaque classe en Java peut possder son entre main(). Lorsque nous entrons :
java Hello

la mthode main() de la classe Hello.class est active. La classe Hello pourrait utiliser dautres classes possdant aussi un point dentre main(). Nous verrons plus loin que cette technique peut tre employe pour tester chaque classe indpendamment.

Les paramtres de main()


main() peut recevoir des paramtres. Lorsque nous entrons une commande DOS telle que :
copy hello.cpp hello.bak

les chiers hello.cpp et hello.bak sont les deux paramtres reus par la commande copy. Nous allons examiner prsent les diffrences entre les deux langages et la manire de procder pour tester et acqurir des paramtres. Par exemple, si nous devions programmer la commande DOS copy (quivalente la commande cp sous Linux), il faudrait que nous vriions les paramtres de la manire qui va suivre.

10

Apprendre Java et C++ en parallle

main() et C++
Le chier copy_arg.cpp peut se prsenter sous cette forme :
// copy_arg.cpp #include <iostream> using namespace std; int main(int argc, char **argv) { if (argc != 3) { cerr << "Nombre invalide de paramtres" << endl; return -1; } cout << "argv[0]: " << argv[0] << endl; cout << "argv[1]: " << argv[1] << endl; cout << "argv[2]: " << argv[2] << endl; return 0; }

Ce programme C++ peut se compiler avec :


g++ -o copy_arg.exe copy_arg.cpp

ou avec un Makefile qui se trouve sur le CD-Rom. Nous pouvons ensuite lexcuter avec :
copy_arg hello.cpp hello.bak

Il faut absolument joindre ces deux arguments, sous peine de rcolter une erreur (nombre invalide de paramtres). Le rsultat sera ainsi prsent :
argv[0]: copy_arg argv[1]: hello.cpp argv[2]: hello.bak

Nous savons dj que copy_arg.exe peut tre abrg en copy_arg sous DOS, mais pas sous Linux.

main() et Java
Le chier CopyArgs.java est en fait trs similaire si nous ltudions en dtail :
public class CopyArgs { public static void main (String[] args) { if (args.length != 2) { System.err.println("Nombre invalide de paramtres"); return; }

Lincontournable Hello world CHAPITRE 1

11

System.out.println("args[0]: " + args[0]); System.out.println("args[1]: " + args[1]); } }

Ce programme Java peut se compiler avec :


javac CopyArgs

ou un Makefile, combin avec la compilation de la version C++ ci-dessus, qui se trouve sur le CD-Rom. La classe CopyArgs.class compile sera excute avec la machine virtuelle Java de cette manire :
java CopyArgs hello.cpp hello.bak

Nous obtiendrons le rsultat suivant :


args[0]: hello.cpp args[1]: hello.bak

Analyse comparative
La premire grande diffrence est le nombre de paramtres retourns, cest--dire un de plus pour la version C++, o nous recevons (dans argv[0]) le nom du programme. Les strings existent aussi en Standard C++, mais la fonction main() est un hritage du C. Linstruction if viendra au chapitre 3, mais la traduction de :
if (argc != 3) {

serait si argc nest pas gal 3 alors . Les diffrentes sorties lcran, cout et cerr pour C++, ainsi que out et err en Java, sont utilises sur les deux canaux de sorties. Il est possible, plus particulirement sous Linux, de ltrer ces deux sorties pour diffrencier les cas normaux (cout, out) des erreurs (cerr, err). Nous ne donnerons pas ici dexemples sur lutilisation de telles techniques, mais nous garderons cependant lhabitude dutiliser le canal cerr/err pour les erreurs. Observons ce qui se passe avec linstruction :
cout << "argv[0]: " << argv[0] << endl;

Le premier argv[0]: est simplement un texte. Le texte entre guillemets est envoy au travers de loprateur << cout. Ce dernier est dni dans le chier iostream, qui se trouve dans la bibliothque standard (std) du Standard C++. Nous reviendrons plus tard sur ces dtails. Les sorties lcran (cout) pouvant tre chanes, le terme suivant se trouve tre argv[0], argv tant un pointeur une liste de pointeurs, cest--dire la raison du ** dans la dclaration du main(). argv[0] va pointer sur ladresse mmoire o gure le premier paramtre, cest--dire le nom de la commande. Si nous crivions :
cout << "Adresse de argv[0]: " << &argv[0] << endl;

12

Apprendre Java et C++ en parallle

nous verrions apparatre sous forme hexadcimale ladresse mmoire o se trouve en fait le texte copy_arg.exe . On peut galement concevoir argv comme un tableau de chanes de caractres, le 0 de argv[0] tant un index dans ce tableau. Enn, le endl est une opration aussi dnie dans <iostream>, qui consiste envoyer le caractre de saut de ligne, que lon reprsente parfois comme le \n ou \010 (valeur octale). En Java, nous navons pas de chanes de caractres comme en C++, mais un String. Celui-ci est un objet reprsentant aussi une chane de caractres. length est une opration excute par le compilateur qui nous permet de recevoir la dimension de la variable args, qui est un tableau de String identi avec le []. En C et en C++ nous recevons la variable argc. La dimension de args doit tre de 2 dans notre exemple, ce qui nous permet dobtenir les deux paramtres args[0] et args[1]. Lindex commence toujours 0 dans un tableau. Lindex maximal est args.length -1. Lobjet out dans la bibliothque Java System permet de sortir du texte sur la console au moyen de println(). out, dni dans le package java.lang.System, est un objet statique de la classe PrintStream ; println() est une mthode (fonction) de la classe PrintStream. Nous verrons ces concepts plus loin. Il nest pas ncessaire dimporter le package System avec une instruction import, car il est reconnu par le compilateur. Le ln de println() est quivalent au endl du C++. Dans cet exemple Java, nous utilisons loprateur + pour associer deux strings avant de les envoyer au travers de println(). Il est aussi possible dutiliser plusieurs print() et le "\n" comme ici :
System.out.print("args[0]: "); System.out.print(args[0]); System.out.print("\n");

Le return est quivalent en C/C++ et en Java. En Java, nous avons un void main() et nous ne devons pas retourner de valeur. Le return peut dailleurs tre omis si nous atteignons correctement la n du programme. Avec un int main() en C++, il est ncessaire de retourner une valeur quil est judicieux de dnir ngativement en cas derreur. Toute dmarche doit se faire dune manire logique. Si nous devions crire les programmes
copy_arg.cpp et Copy_arg.java en totalit, il faudrait vrier lexistence du chier entr comme premier paramtre (ici, hello.cpp). Ensuite, si le deuxime paramtre tait un chier existant (ici, hello.bak), il faudrait dcider si nous acceptons de lcraser ou non. Nous pourrions alors demander, avec un oui ou un non, si cela est acceptable ou encore dnir un autre paramtre (par exemple, f) pour forcer lcriture. Nous aurions alors

une commande comme :


copy -f hello.cpp hello.bak

Comme -f serait optionnel, il faudrait traiter les arguments diffremment. Enn, nous pourrions pousser lanalyse lextrme et vrier si lespace disque est sufsant avant de commencer la copie. En cas derreur, il faudrait effacer la mauvaise copie. Si nous navions pas la permission dcrire, cela affecterait vraisemblablement la logique du traitement des erreurs ! Il y a en effet des cas en programmation o nous dcouvrons quun oubli

Lincontournable Hello world CHAPITRE 1

13

dans lanalyse ou la conception (design, en anglais) peut entraner une restructuration complte du code !
Note Que se passe-t-il sous DOS ? Si nous excutons cette ligne dinstruction C++ sous DOS :

cout << "" << endl;


cela pourrait nous surprendre au premier abord. En effet, les caractres sous DOS sont diffrents. Les lettres et caractres de la langue anglaise vont apparatre correctement, alors que les lettres avec accents poseront quelques difcults. Mais comme nous excuterons nos programmes avec Crimson, cest--dire sous Windows, nous ne rencontrerons pas ce type de problme. Nous reviendrons sur ce point plus loin dans cet ouvrage.

Jouer avec Crimson


Cest sans doute le meilleur moment pour retourner dans lditeur Crimson (voir annexe C) et pour refaire quelques-unes des dmarches et fonctions usuelles : Charger un chier .java, le compiler et lexcuter. Charger un chier .cpp , le compiler et lexcuter. Charger un Makefile et lexcuter avec le make. Constater que nous pouvons aussi charger des chiers .bat (efface.bat ou Makefile.bat) et les excuter dans Crimson directement et sans fentre DOS.

Rsum
la n de ce chapitre, nous savons dj compiler des programmes Java et C++, bien quil ne soit pas encore possible dcrire un programme digne de ce nom. Loutil make de GNU permet dautomatiser le processus lorsque des changements dans le code ont t apports.

Exercices
Toutes les solutions des exercices de cet ouvrage sont disponibles sur le CD-Rom (voir annexe B, section Installation des exemples et des exercices ). En programmation, il ny a pas UNE solution, mais plusieurs. Il vaut cependant la peine de saccrocher et de faire quelques-uns de ces exercices, voire tous, avant de consulter les solutions. 1. crire une classe Java Bonjour qui nous sortira un :
Bonjour et bonne journe

2. crire le Makefile pour cette classe et vrier quune compilation est nouveau excute si le chier Bonjour.class est effac ou bien si le chier Bonjour.java est modi avec un autre texte de salutations !

14

Apprendre Java et C++ en parallle

3. crire une version de copy_arg.cpp et de CopyArgs.java qui, lorsque aucun paramtre nest donn, imprime lcran un descriptif des paramtres ncessaires aux programmes. 4. Crer un chier execHello.bat pour excuter les binaires Bonjour.class et bonjour.exe des exercices 1 et 2 prcdents. Ajouter une pause en n de chier et excuter ce dernier deux fois depuis Crimson. Examiner leffet de la pause et le rsultat obtenu en double-cliquant sur le chier depuis lexplorateur de Windows.

2
La dclaration et laffectation des variables numriques
Le traitement des variables non numriques sera tudi au chapitre 7. Comme la chane de caractres char* en C++, qui est une variable essentielle de ce langage, nexiste pas en Java (remplace par la classe String), il nous a sembl ncessaire de traiter sparment et soigneusement ce sujet essentiel.

Dclaration des variables


Une variable est un moyen donn par le langage pour dnir et manipuler un objet et sa valeur. Elle correspond en fait un emplacement dans la mmoire. Si notre programme a besoin de conserver le nombre de personnes invites le week-end prochain, nous devons dnir cette variable de la manire suivante :
int nb_personne;

Une dclaration est une instruction Java ou C++. Elle doit se terminer par un point-virgule. La premire partie de cette instruction est un mot-cl qui correspond soit un type prdni soit un nom de classe. Dans le cas dune classe, sa dnition doit apparatre avant linstruction. Cette dnition est gnralement contenue dans un chier den-tte en C++ (#include) ou de package en Java (import). Nous reviendrons sur les dtails tout au long de ce chapitre. La deuxime partie de linstruction reprsente le nom de la variable. Ce nom doit tre unique et ne pas correspondre un mot-cl dni par le langage. Il doit imprativement commencer par une lettre ou le caractre de soulignement _, mais peut contenir des chiffres et ce mme caractre de soulignement. Les lettres doivent faire partie de lalphabet anglais

16

Apprendre Java et C++ en parallle

et peuvent tre en majuscule ou en minuscule. Dans certains langages de programmation comme ADA, la variable MonNombre sera identique monNombre. En Java et C++, ces deux variables seront diffrentes. En C++, il nest pas souhaitable de commencer un nom de variable ou de fonction par le caractre de soulignement, car les compilateurs lutilisent souvent pour des variables et fonctions spciales.

Choix des noms de variables


Un nom de variable ne doit tre ni trop long ni trop court. Dans le cas de nb_personne, ce nom est presque parfait, car il atteint dj une grandeur respectable. Un nom trop court comme n ou trop long comme nombre_de_personnes_invitees nest pas judicieux. En C++, il est recommand de nutiliser que des minuscules, le caractre _ et des chiffres ; en Java, il est souhaitable demployer des minuscules, des majuscules et des chiffres. Par ailleurs, il serait prfrable de toujours ajouter un commentaire dans le code, du style de :
int nb_personnes; // nombre de personnes invites int nbPersonnes; // alternative Java (minuscule au dbut)

Un commentaire tel que :


int nb_personnes; // nombre de personnes

napportera rien de nouveau. Une difcult pourrait apparatre si nous devions dnir une autre variable pour le nombre de personnes qui ne sont pas invites ! Nous pourrions crire par exemple :
int nb_pers_inv; // nombre de personnes invites int nb_pers_abs; // nombre de personnes non invites (absentes)

Bien que absent ne veuille pas dire pas invit , nous prfrons cette criture la forme :
int nb_pers_pinv; // nombre de personnes non invites

car la lettre p pourrait tre facilement oublie ou rajoute et entraner des erreurs de programmation difciles corriger ! Une variable trop longue est aussi difcile grer si nous avons plusieurs tests de conditions sur la mme ligne :
if ((nombre_de_personnes_invitees < 0) || ((nombre_de_personnes_invitees == 13) || .... )

Il nest pas recommand dadopter des particularits de langage comme dter toutes les voyelles dune variable. Ce nest pas une invention, cela existe ! La variable nombre_de _personnes_invitees deviendrait alors nmbr_d_prsnns_nvts Il faut toujours penser lventuel programmeur qui reprendrait un jour ce code. Pourquoi ne pas utiliser simplement la langue anglaise, dont les mots sont plus courts (nb_person) ? Cest beaucoup plus facile, et il suft de lire une fois un programme en langue allemande pour se rendre compte quun style international serait bienvenu.

La dclaration et laffectation des variables numriques CHAPITRE 2

17

Enn, utiliser des lettres uniques, comme i, j ou k, est tout fait appropri dans des instructions de rptitions et de boucles.

Affectation des variables


Dans cet ouvrage, nous avons utilis les termes daffectation et dassignement . Ces deux termes sont quivalents, bien que le deuxime ne soit pas vraiment franais, mais utilis dans la littrature anglaise et tout fait comprhensible. Les variables sont dclares et affectes au moyen dinstructions. Si nous crivons :
int valeur1; valeur1 = 10; int valeur2 = 20;

nous remarquerons que la variable valeur1 est dclare et affecte en deux tapes. Aprs la premire instruction, valeur1 ne sera pas encore initialise, bien que le compilateur lui ait dj rserv une zone mmoire. La forme :
int valeur3(30) ;

est intressante, mais seulement accepte en C++. Nous en verrons la raison lors du traitement et de laffectation des objets au chapitre 4. En Java, le code suivant ne compilera pas :
public class InitInt { public static void main(String[] args) { int valeur1; System.out.print("Valeur1: "); System.out.println(valeur1); } }

Lerreur suivante sera reporte :


---------- Capture Output --------->"C:\Program Files\Java\jdk1.6.0_06\bin\javac.exe" -Xlint -classpath C:\JavaCpp\EXEMPLES\Chap02 InitInt.java InitInt.java:5: variable valeur1 might not have been initialized System.out.println(valeur1); ^ 1 error > Terminated with exit code 1.

Nous remarquons donc quil nest pas possible de compiler ce code, alors que cela est tout fait permis en C++. Cest lun des grands avantages de Java : son compilateur est trs restrictif et force le programmeur qui a une culture C++ coder correctement son programme. La compilation est simplement refuse ! Le paramtre Xlint (voir le chapitre 14, section Les types gnriques en Java , et lannexe C, en n de section Raccourci ou

18

Apprendre Java et C++ en parallle

favori ) nest pas ncessaire ici, mais donnerait encore plus dinformations sur des formes ou usages (warnings) qui ne suivent pas prcisment les spcications du langage Java. En C++, cest donc une bonne habitude dinitialiser les variables au moment de leurs dclarations. Si nous devions vrier plus tard la valeur dune variable, nous pourrions avoir des surprises. Dans ce code :
int erreur; // beaucoup plus loin if (erreur) { // code derreur }

les caractres // nous permettent dintroduire des commentaires qui seront ignors par les compilateurs. En C++, pour que le code derreur soit excut, il faut que la variable erreur soit diffrente de 0. Mme si les compilateurs mettent gnralement les variables non initialises 0, ce nest pas une raison pour ne pas le faire. Nous reviendrons sur les variables boolennes dans le chapitre suivant. Ce type de code est gnralement utilis avec des instructions telles que :
erreur = fonction();

qui consiste appeler function(), qui nous retournera une valeur qui sera stocke dans la variable erreur. Si cette ligne de code venait disparatre aprs des corrections un peu trop rapides, nous aurions soit un rsultat inattendu, soit une instruction inutile. Dans ce cas prcis, un :
int erreur = 1;

serait judicieux.
int est aussi appel un mot-cl ou mot rserv. En effet, il nest pas possible par exemple dutiliser int pour un nom de variable. int dnit le type de variable reprsentant des nombres entiers positifs et ngatifs. Sa zone mmoire lui permet de stocker un nombre dans des limites prdnies. Si le nombre est trop grand ou dune autre forme comme une valeur numrique dcimale (avec virgule ottante), il faudra utiliser dautres types comme :
float valeur3 = 1.2; double valeur4 = 1000000;

Un point essentiel au sujet de la valeur dune variable est noter : il est possible que des oprations arithmtiques affectent cette valeur et dbordent de sa capacit. Il est souvent prfrable en cas de doute dutiliser un long au lieu dun int. Le code Java suivant, qui augmente de 1 la variable valeur1, compile parfaitement :
public class IntLong { public static void main(String[] args) { int valeur1 = 2147483647; valeur1++; System.out.print("Valeur1: ");

La dclaration et laffectation des variables numriques CHAPITRE 2

19

System.out.println(valeur1); } }

mais retournerait une erreur de compilation si nous avions donn la valeur de 2147483648 la variable :
---------- Capture Output --------->"C:\Program Files\Java\jdk1.6.0_06\bin\javac.exe" -Xlint -classpath C:\JavaCpp\EXEMPLES\Chap02 IntLong.java IntLong.java:3: integer number too large: 2147483648 int valeur1 = 2147483648; ^ 1 error > Terminated with exit code 1.

Cependant, si nous compilons et excutons le code avec la valeur de 2147483647, le rsultat pourrait sembler stupant :
Valeur1: -2147483648

Cest simple comprendre si nous examinons ce tableau et les valeurs binaires prsentes. Il faut dj se rappeler comment est form un nombre binaire, et nous prendrons lexemple dun octet, cest--dire de 8 bits :
00000000 = 0 00000101 = 5 00000001 = 1 00001000 = 8 00000010 = 2 00001010 = 10 00000011 = 3 00010000 = 16 00000100 = 4 10000000 = 128

Nous voyons que les bits se promnent de droite gauche et sont simplement des 0 ou 1 en mmoire. Notre 128, dans une reprsentation signe, est en fait le bit de ngation. Sa valeur signe serait alors 127. Si nous faisions ce mme exercice en C++ nous pourrions avoir des surprises et ne pas obtenir mme rsultat suivant les machines et les versions de compilateur. Si nous devions travailler avec de telles valeurs, pour limiter les risques, il sufrait de passer dint en long. Voici un exemple de codage des nombres ngatifs (complment 2). Ce nest pas si simple dtablir une formule pour les personnes qui ont quelques difcults avec les mathmatiques. Ce quon peut dire tout simplement : si nous avons un bit 1 tout gauche, nous avons un chiffre ngatif :
0111.1111.1111.1111.1111.1111.1111.1111 = 2147483647 1000.0000.0000.0000.0000.0000.0000.0000 = -2147483648

Le point permet simplement de sparer les groupes par 4 bits tals sur 4 octets. Linstruction valeur1++; ajoute 1 en mmoire, cest--dire dcale gauche, comme pour le passage de 3 (011) 4 (100), et la nouvelle valeur binaire correspond la valeur ngative extrme. Nous avons fait le tour, de lextrme droite lextrme gauche, et le bit de ngation est positionn ! Dans notre tableau 8 bits ci-dessus, nous aurions pu ajouter le 11111111, cest--dire 1, le premier nombre ngatif.

20

Apprendre Java et C++ en parallle

Dans ce mme contexte, le code C++ suivant :


// int_long.cpp #include <iostream> using namespace std; int main() { int valeur = 2147483648; valeur++; cout << "Valeur: " << valeur << endl; return 0; }

va compiler, mais en donnant un avertissement :


---------- Capture Output ---------> "C:\MinGW\bin\g++.exe" -o int_long int_long.cpp int_long.cpp: In function `int main()': int_long.cpp:7: warning: this decimal constant is unsigned only in ISO C90 > Terminated with exit code 0.

LISO C90, identique lANSI C89 de lAmerican National Standards Institute (ANSI), indique la norme utilise pour le langage C. lexcution, le rsultat est au premier abord surprenant :
Valeur: -2147483647 (1000.0000.0000.0000.0000.0000.0000.0001)

car il correspond 2147483648 + 1. Le int 2147483648 de laffectation de la variable valeur tait bien 2147483648. Utilisons la calculatrice de Windows en mode scientique an de convertir des valeurs dcimales en valeurs hexadcimales ou binaires, et de visualiser rapidement la position des bits 0 ou 1. Contrairement Java, il faut tre beaucoup plus prudent en C++, car les compilateurs ne sont pas tout fait semblables et les valeurs possibles des entiers (int) peuvent tre diffrentes dune machine lautre. La solution est dutiliser un long pour tre certain davoir une rserve sufsante. Dans le cas prsent, cest un peu particulier puisque le int a la dimension dun long sur une machine 32 bits comme Windows. Nous sommes donc aussi dans les limites de capacit dun long. Encore une fois, si nous connaissons prcisment dans quel domaine de valeurs nous travaillons, nous pouvons tre plus prcis :
// int_long2.cpp #include <iostream> using namespace std;

La dclaration et laffectation des variables numriques CHAPITRE 2

21

int main() { unsigned long valeur = 2147483648ul; valeur++; cout << "Valeur: " << valeur << endl; return 0; }

Le rsultat est attendu. Le bit tout gauche nest plus la marque du signe positif (0) ou ngatif (1) mais est utilisable pour cette valeur leve :
Valeur: 2147483649

Nous avons indiqu clairement que nos valeurs ntaient jamais ngatives (unsigned et ul).

Transtypage
Le transtypage permet de convertir un type en un autre lorsque cest ncessaire. Cest valable aussi bien en C++ quen Java, bien quil faille lviter dans la mesure du possible dans ce dernier langage. Dune manire gnrale, lors de lcriture dun programme, nous devrions connatre davance les valeurs extrmes contenues dans les variables. Dans le code C++ suivant :
cout << "Valeur: " << (unsigned)valeur << endl; // donnera 2147483649 cout << "Valeur: " << static_cast<unsigned>(valeur) << endl;

les deux instructions sont quivalentes, la deuxime forme tant celle utilise en Standard C++. En fait, nous pourrions ignorer les valeurs ngatives et utiliser un unsigned int, qui nous permettrait de travailler avec des entiers positifs, mais sur une plage de valeurs diffrentes. Mais cest loin dtre aussi vident lorsquil faut sadapter aux fonctions ou mthodes mises disposition par les bibliothques des langages.

Positionnement des variables


Il y a plusieurs manires de positionner une variable. Pour simplier, nous nallons considrer ici que les variables de type primitif. Nous considrerons aussi plus loin les variables de classe et les variables globales. Toute variable a une porte. En crivant ce code en C++ :
#include <iostream> using namespace std; int main() { int valeur1 = 1; cout << "Valeur1: " << valeur1 << endl;

22

Apprendre Java et C++ en parallle

{ int valeur1 = 2; cout << "Valeur1: " << valeur1 << endl; } cout << "Valeur1: " << valeur1 << endl; }

il nous donnera ce rsultat :


Valeur1: 1 Valeur1: 2 Valeur1: 1

Nous voyons donc que la variable a uniquement une signication lintrieur dun mme corps dni entre deux accolades. Si nous faisons de mme en Java :
public class Portee { public static void main(String[] args) { int valeur1 = 1; { int valeur1 = 2; } } }

le compilateur est plus restrictif et nous retournera :


javac Portee.java Portee.java:5: Variable 'valeur1' is already defined in this method. int valeur1 = 2; ^ 1 error *** Error code 1 make: Fatal error: Command failed for target `Portee.class'

Cest plus propre, et il y a moins de risques derreur. Une mme variable en Java ne peut tre dclare quune seule fois dans le bloc dune mthode. Il y a galement dautres cas o la dnition des variables peut soprer de manire plus lgante. Lorsque nous avons ce type de code en C++ ou en Java (nous reviendrons au chapitre 3 sur les instructions de conditions et de boucles) :
int i = 0; for (; i < 10; i++) { // ... };

et que i nest utilis qu lintrieur du corps de la boucle for(), il est judicieux de le coder autrement :

La dclaration et laffectation des variables numriques CHAPITRE 2

23

for (int i = 0; i < 10; i++) { // ... };

dans le cas o la variable i ne serait plus utilise lextrieur de la boucle. Pour le positionnement de la dnition des variables, il est souvent prfrable de les dplacer en dbut de corps :
int main() { int valeur1 = 1; int valeur2 = 2;

Si la variable valeur2 nest utilise que beaucoup plus loin dans le corps de la fonction, nous pourrions argumenter pour un dplacement de la dnition de valeur2 juste avant son utilisation. Cependant, comme le code lintrieur dune fonction doit se limiter un certain nombre de lignes (autre recommandation), cette argumentation ne tient plus !

Variables du type pointeur en C++


En C ou en C++, il est possible de dnir une variable comme un pointeur, cest--dire une adresse mmoire. Cette dernire correspond une adresse dun autre objet existant ou celle dun objet allou avec la fonction C malloc() ou loprateur new, sur lequel nous reviendrons au chapitre 4. Tous les objets crs avec le mot-cl new sont des pointeurs. Bien que Java cre des objets avec loprateur new, il ne possde pas de pointeurs. Si nous crivons ce morceau de code en C++ :
int nombre1 = 1; int *pnombre2; pnombre2 = &nombre1; *pnombre2 = 2;

la variable nombre1 contiendra initialement la valeur 1. La deuxime instruction dnit une variable pnombre2 de type pointeur sur un entier, ce qui se fait avec loprateur *. La troisime instruction permet de donner la variable pnombre2 ladresse en mmoire de la variable nombre1, ceci avec loprateur &. Enn, linstruction :
*pnombre2 = 2;

attribue la valeur 2 la variable entire qui pointe sur pnombre2, cest--dire la variable nombre1. Si nous crivons :
cout << nombre1 << *pnombre2;

nous obtiendrons 22 et non pas 12. Si nous entrons par erreur :


cout << pnombre2;

ladresse actuelle de la mmoire o se trouve stock le nombre 2 sera afche ! Une variable de type pointeur nous permet dassocier plusieurs variables au mme objet, qui se trouve un endroit dtermin en mmoire.

24

Apprendre Java et C++ en parallle

Utilisation des pointeurs


Nassocions jamais des variables de type pointeur des objets qui disparaissent de leur porte comme ici :
int main() { int *pcompteur; { int compteur = 20; pcompteur = &compteur; compteur = 30; } // le compteur disparat de sa porte *pcompteur = 40; }

Ce code fonctionnera vraisemblablement, car la mmoire adresse par pcompteur ne sera sans doute pas encore rutilise. Cependant, une erreur de ce type peut tre difcile dceler, et il faut donc se mer. Le code qui se trouve lintrieur de la porte va perdre sa variable compteur, dont nous avons rcupr ladresse ! Nous reviendrons sur la lettre p de notre variable pcompteur au chapitre 4 lorsque nous ferons une autre recommandation au sujet du nom des variables en C++.

Utilisation de malloc() en C++


Il ne faut pas utiliser les fonctions C malloc() et free() : elles constituent lun des hritages du langage C que nous pouvons oublier sans grandes difcults. En effet, les oprateurs new et delete du langage C++ les remplacent avantageusement.
#include <iostream> #include <cstdlib> using namespace std; int main() { int *mon_int; mon_int = (int *)malloc(sizeof(int)); *mon_int = 10; cout << *mon_int << endl; free(mon_int); }

Nous verrons au chapitre 4 comment utiliser loprateur new. La fonction C sizeof() nous permet de recevoir la dimension du type utilis, an dallouer la mmoire correspondante. Nous pouvons dterminer le nombre doctets attribu chaque type de cette manire :
cout << "sizeof int: " << sizeof(int) << endl;

La dclaration et laffectation des variables numriques CHAPITRE 2

25

cout cout cout cout

<< << << <<

"sizeof "sizeof "sizeof "sizeof

char: " << sizeof(char) << endl; short: " << sizeof(short) << endl; double: " << sizeof(double) << endl; long: " << sizeof(long) << endl;

et qui nous donnera le rsultat :


sizeof sizeof sizeof sizeof sizeof int: 4 char: 1 short: 2 double: 8 long: 4

Ce rsultat peut tre diffrent sur un autre systme (par exemple Linux). Ce nest pas le cas en Java o cest une ncessit davoir la mme dimension pour chaque type, puisque le code est excutable sur diffrentes machines. Nous remarquons ici que, sous Windows, int et long ont la mme dimension.

Variables constantes
Les variables constantes sont dnies avec les mots-cls const en C++ et final en Java. Nous reviendrons plus tard sur leurs utilisations conjointement avec le mot-cl static dans le cadre des objets constants de classe.
const int constante1 = 10; // C++ final int constante1 = 10; // Java

Lemploi de variables constantes permet de dnir des variables xes qui ne seront jamais modies par le programme. Le compilateur C++ ou Java rejettera la compilation si une nouvelle valeur est affecte une constante :
public class Constante { public static void main(String[] args) { final int valeur1 = 1; valeur1 = 2; } } javac Constante.java Constante.java:4: Can't assign a value to a final variable: valeur1 valeur1 = 2; ^ 1 error

Ces variables constantes peuvent tre par exemple les dimensions dun chiquier, qui resteront toujours de 8 sur 8. Des fonctions de recherche dans des tableaux pourraient tre utilises pour le jeu dchecs, mais aussi pour dautres jeux avec des tableaux plus grands. Dans ce dernier cas, elles fonctionneraient galement aprs une nouvelle compilation, condition davoir utilis ces variables constantes et non des valeurs xes dans le code.

26

Apprendre Java et C++ en parallle

Trs souvent, une variable constante est utilise pour dterminer des conditions internes qui ne devraient pas changer, ou bien pour tester des limites de systme. Le DOS accepte le fameux format 8.3 pour les chiers (exemple : autoexec.bat). Le code C++ suivant :
const const char char int dim_nom = 8; int dim_ext = 3; nom_fichier[dim_nom + 1]; ext_fichier[dim_ext + 1];

est tout fait ralisable avec un +1 pour le 0 de la terminaison de la chane de caractres. Nous reviendrons sur la construction des tableaux et des chanes de caractres au chapitre 5. Les variables constantes pouvant tre modies un jour ou lautre, il est ncessaire de les regrouper dans un chier den-tte (.h), si possible commun. Un changement de ces valeurs ncessiterait alors une nouvelle compilation. Les #define du C devraient tre remplacs par des variables constantes, bien quils soient nombreux dans les bibliothques de ce langage. Ainsi, nous avons dans la bibliothque C math.h la dnition suivante :
#define M_PI 3.14159265358979323846

Celle-ci pourrait tre avantageusement remplace par :


const double m_pi 3.14159265358979323846;

Variables globales en C++


Il nest pas possible de dnir une variable globale en Java de la mme manire quen C++. En Java, elle sera dnie soit comme variable de classe, mme statique, soit comme variable de travail lintrieur dune mthode. Le code C++ qui suit nest ainsi pas possible en Java :
int variable1 = 1; const int variable2 = 2; extern int variable3 = 3; int main(int argc, char **argv) { }

Nous voyons que ces variables sont dnies en dehors de la porte de main(). Elles sont donc accessibles par toutes les fonctions du programme. Cest une manire classique de procder, hritage du langage C, qui na pas dautres mcanismes sa disposition, tel celui de dnir des variables de classe constantes. Le mot-cl extern permet des modules spars daccder leurs variables. Si nous compilons un programme de cette manire :
gcc o programme.exe module1.cpp module2.cpp

une variable variable1 dnie dans le module1.cpp pourra tre utilise dans le module2.cpp si elle est dclare extern dans le module2.cpp. Sans la dclaration en extern de cette variable

La dclaration et laffectation des variables numriques CHAPITRE 2

27

ou son existence dans un chier den-tte, le compilateur indiquerait quil ne trouve pas de dclaration ou de rfrence cette variable. Dans lexemple ci-dessus, les trois variables seront initialises avant que le code du main() ne soit excut. Dans la mesure du possible, il faut viter ce genre de construction distribue dans plusieurs modules. Une alternative est de regrouper toutes ces variables dans un chier den-tte .h. Nous donnons cependant la prfrence des variables de classe statiques, que nous tudierons au chapitre 10.

Fichiers den-tte en C++


Nous verrons au chapitre 4 que les chiers den-tte sont principalement utiliss pour la dnition des classes C++. Comme pour les chiers .cpp, ce sont des chiers ASCII avec gnralement lextension .h. Ils contiennent des dnitions, par exemple des constantes, qui peuvent tre utilises par diffrentes sources .cpp. Nous apprendrons au chapitre 6 quils peuvent aussi tre dvelopps pour nos propres bibliothques de classe C++ ou de fonctions C. Dans ce cas-l, le compilateur devra utiliser la directive I pour identier la localisation de ces chiers dans un rpertoire prdni. Pour linstant, il nous suft de savoir que le compilateur sait identier par lui-mme lemplacement des chiers den-tte du systme, par exemple iostream ou cmath.

Oprations et oprateurs
Aprs nous tre familiariss la dclaration et laffectation des variables numriques, nous allons tudier les oprateurs mis disposition par les deux langages, ainsi que certaines fonctions faisant partie des diffrentes bibliothques C, C++ ou Java. Les microprocesseurs, ou plus gnralement les processeurs des ordinateurs, sont capables aujourdhui de calculer diffrentes fonctions mathmatiques. Il y a vingt ou trente ans, une simple multiplication ntait pas encore possible, et tout devait tre programm par le logiciel. Mais ce nest pas trs important en premire analyse. Si nous crivons en C++ :
// math.cpp #include <iostream> #include <cmath> using namespace std; int main() { double a = 2.0; double b = 3.0; double c = a * b; cout << "Multiplication: " << c << endl;

28

Apprendre Java et C++ en parallle

c = sin(a)*cos(b) + sin(b)*cos(a); // sin(a + b) cout << "Sinus(a + b): " << c << endl; c = pow(a, b); cout << "a la puissance b: " << c << endl; return 0; }

qui fournit comme rsultat :


Multiplication: 6 Sinus(a + b): -0.958924 a la puissance b: 8

Nous dcouvrons toute une srie doprations. Nous constatons que nous avons la fois des oprateurs et des fonctions de la bibliothque du langage C (<math.h>). <cmath> est le chier den-tte utiliser pour la bibliothque du Standard C++ ; <math.h> est en fait inclus dans <cmath>. Le point important est de savoir o trouver ces fonctions (qui sont ici sin(), cos() et pow()) : dans un manuel de rfrence, un outil de dveloppement sous Windows ou des pages de manuel (man) sous Linux. Cependant, nous pouvons toujours lire directement le chier math.h, qui se trouve, par exemple pour linstallation de GNU, dans le rpertoire dinstallation du compilateur :
..\Mingw32\i386-mingw32\include\math.h

et o nous pourrions trouver ceci :


extern extern extern extern extern extern extern extern extern double double double double double double double double double atan _PARAMS((double)); cos _PARAMS((double)); sin _PARAMS((double)); tan _PARAMS((double)); tanh _PARAMS((double)); log _PARAMS((double)); log10 _PARAMS((double)); pow _PARAMS((double, double)); sqrt _PARAMS((double));

ce qui est aussi une manire de vrier les paramtres que nous passons la fonction. Nous reviendrons plus loin sur la dnition et lutilisation des fonctions C++ ou des mthodes en Java. Pour linstant, nous oublierons le _PARAMS et nous lirons simplement un double cos(double). Cela nous indique quune variable de type double est demande par la fonction cos() et que le rsultat nous est retourn comme un double.

La classe Java Math


La classe Java Math possde un grand nombre de fonctions mathmatiques utiles aux scientiques, conomistes ou autres statisticiens. En C++, nous utilisons des fonctions C.

La dclaration et laffectation des variables numriques CHAPITRE 2

29

Ici, nous allons travailler avec des variables ou mthodes statiques, ce qui est en fait quivalent aux fonctions C. Voici une liste non exhaustive de quelques mthodes de la classe Math :
public public public public public static static static static static final double PI; double sin(double double cos(double double pow(double long round(double a); a); a, double b); a);

ainsi quun programme en Java similaire au prcdent en C++ :


public class MathTest { public static void main(String[] args) { double a = 2.0; double b = 3.0; double c = a * b; System.out.println("Multiplication: " + c); c = Math.sin(a)*Math.cos(b) + Math.sin(b)*Math.cos(a); // sin(a + b) System.out.println("Sinus(a + b): " + c); c = Math.pow(a, b); System.out.println("a la puissance b: " + c); } }

Le rsultat peut paratre surprenant :


Multiplication: 6.0 Sinus(a + b): -0.9589242746631385 a la puissance b: 8.0

car il nous donne une meilleure prcision quen C++. Si nous voulions obtenir le mme rsultat en C++, il faudrait ajouter les deux lignes suivantes lendroit correct :
#include <iomanip> cout << "Sinus(a + b): " << setprecision(16) << c << endl;

Le setprecision(16) est envoy au cout (le canal de sortie) avant la variable double c pour lui indiquer que nous aimerions plus de chiffres aprs la virgule (6 tant le dfaut de cout). Le dernier chiffre dcimal est aussi diffrent, mais cest vraisemblablement d la manire dont le double est arrondi.

Les oprateurs traditionnels


propos des oprateurs arithmtiques traditionnels (+, -, * et /), il faut revenir sur loprateur de division (/) et son compagnon, le reste de la division (%). Si nous divisons

30

Apprendre Java et C++ en parallle

un nombre en virgule ottante et que nous restons dans ce type, il est vident que la division se fera convenablement.
double double int int d1 d2 i1 i2 = = = = 5; 2; 5; 2; // 2.5 // 2 // 1

double d3 = d1/d2; int i3 = i1/i2; int i4 = i1%i2;

d3 = d1%d2; // erreur de compilation

Ces instructions sont communment utilises, mais chacune dans leur domaine particulier. Pour compter une population ou pour en faire des moyennes, les entiers peuvent tre utiliss. Pour des applications bancaires ou statistiques prcises, les double sont ncessaires.

Char et byte
Un programmeur C ou C++ qui entre dans le monde Java sera surpris en dcouvrant le char de Java 16 bits. En C++, une dclaration telle que :
char mon_char = 'c';

qui est aussi correcte en Java, reprsente un bon vieux caractre de 8 bits parmi le jeu des caractres ASCII. Si nous retournons un peu plus en arrire, nous nous souviendrons mme des dbuts des communications sur modem, quand nous ne transmettions que 7 bits aux tats-Unis, car nos voisins outre-atlantiques nont pas de caractres accentus. De trs anciens programmeurs se rappelleront aussi des difcults avec le bit 7 ( gauche), o il fallait parfois crire en C un :
unsigned char mon_char;

an dutiliser certaines procdures de la bibliothque C qui ne fonctionnaient pas correctement. unsigned veut dire ici que mon_char peut tre considr comme un nombre entre 0 et 255, et non pas entre -128 et 127. Si, en Java, nous voulons absolument travailler avec une variable 8 bits, nous utiliserons le byte (octet) :
byte mon_byte = (byte)'b';

Le transtypage est ncessaire, sinon le compilateur retourne une erreur. Mais passons sur les dtails et ne soyons pas trop surpris avec la reprsentation Java 16 bits, savoir l Unicode dont le site Web, http://www.unicode.org, nous donnera plus de dtails sur un travail de dnition qui se poursuit encore. Un caractre Unicode permet de reprsenter une lettre dans nimporte quel langage : 65 536 possibilits nous sont ainsi offertes pour coder toutes les lettres de tous les alphabets de la plante. Nous pouvons dj mentionner que le String de Java est une suite de caractres Unicode de 16 bits.

La dclaration et laffectation des variables numriques CHAPITRE 2

31

En C++, nous navons donc que 8 bits. Le jeu de caractres ASCII/ANSI est cod sur un octet et noffre que 255 combinaisons. Les 255 caractres utiliss sous Windows sont en fait les 255 premiers caractres de lUnicode. Une notation hexadcimale permet de spcier un caractre particulier qui ne peut tre slectionn au clavier. Linstruction suivante en Java :
char unicar = '\u0044'; // au lieu de = 'D';

na pas vraiment de sens puisquelle correspond la lettre majuscule D. Terminons par la table des caractres spciaux que nous utilisons rgulirement en C++ et en Java :
Code dchappement Unicode en Java Description
Effacement arrire Tabulation horizontale Saut de ligne Retour de chariot Guillemet Apostrophe Antislash Saut de page Caractre octal (ex: '\101' = 'a') Caractre unicode

\b \t \n \r \" \' \\ \f \DDD

\u0008 \u0009 \u000A \u000D \u0022 \u0027 \u005C \u000C \u00HH \uHHHH

Nous dnirons volontiers, en C++, une variable constante pour reprsenter un de ces caractres :
const char nl = '\n';

Intervalles des types entiers en Java


Les types entiers ont des intervalles xes en Java : byte : de 128 127 inclus ; short : de 32 768 32 767 inclus ; int : de 2 147 483 648 2 147 483 647 inclus ; long : de 9 223 372 036 854 775 808 9 223 372 036 854 775 807 inclus ; char : de \u0000 \uffff inclus, cest--dire de 0 65 535. Ceci est une obligation pour garantir la portabilit entre machines.

32

Apprendre Java et C++ en parallle

En C++, int peut tre dni sur 16 ou 32 bits. Cela dpendra la fois de la machine et du systme dexploitation. Lorsque le programmeur a des doutes sur la capacit dun int, il peut toujours choisir long ou double comme alternative.

Rgles de priorit des oprateurs


Dans ce chapitre, il faut mentionner les rgles de priorit ncessaires en mathmatiques et qui existent en gnral dans tous les langages. En Java et C++, une instruction telle que :
7*3+2

nous donnera 23 et non pas 35 : la multiplication a la priorit sur laddition. Les oprateurs * ou / ont la priorit sur laddition et la soustraction. Bien que les programmeurs connaissent ces rgles, il est tout de mme plus lgant et comprhensible dcrire ce code comme suit :
(7*3)+2

Cette criture est propre et nentranera aucune diffrence lexcution. Cependant, nous pouvons forcer lordre des oprations et crire :
7*(3+2)

Dans la ralit, ces nombres seront des variables.

Une diversion sur le cin (entre)


Comme nous lavons afrm dans la prface, il est difcile dlaborer des exercices sans utiliser un certain nombre de concepts qui seront prsents plus loin dans cet ouvrage. An que nos petits exercices tiennent la route, il faut tout de mme pouvoir entrer des donnes via la console. En C++, le travail se fait dune manire relativement simple :
// cin.cpp #include <iostream> #include <string> using namespace std; int main() { string texte = ""; int nombre = 0; cout cin cout cout cin cout } << >> << << >> << "Entre un texte: "; texte; "\nLe texte: " << texte << endl; "Entre un nombre: "; nombre; "\nLe nombre: " << nombre << endl;

La dclaration et laffectation des variables numriques CHAPITRE 2

33

Nous dirons simplement que le cin est loppos du cout : il permet dobtenir des donnes entres par loprateur sur la console. Nous y reviendrons plus en dtail dans le chapitre consacr aux entres et sorties. De plus, loprateur >> est capable de grer diffrents types dobjets et de stocker linformation correctement. Si, dans le programme ci-dessus, nous entrons un texte pour le nombre, un 0 sera transfr dans la variable nombre. Le cin est en fait directement li au stdin du langage C qui correspond au ux dentre. Au sujet du string, nous devons pour linstant le considrer comme un objet capable de stocker une chane de caractres. En passant en Java, cela se gte :
import java.io.*; public class Cin { public static void main(String[] args) { try { String texte = ""; int nombre = 0; BufferedReader stdin = new BufferedReader(new InputStreamReader(System.in)); System.out.print("Entre un texte: "); texte = stdin.readLine(); System.out.println("\nLe texte: " + texte); System.out.print("Entre un nombre: "); nombre = Integer.parseInt(stdin.readLine()); System.out.println("\nLe nombre: " + nombre); } catch(IOException ioe) {} } }

Ce nest pas encore possible ici de passer au travers de toutes ces nouvelles fonctionnalits. Disons simplement que les classes BufferedReader et InputStreamReader sont dnies dans le paquet java.io. Par analogie au C++, nous dirons que linstruction :
BufferedReader stdin = new BufferedReader(new InputStreamReader(System.in));

correspond au cin du langage C++. Les deux instructions suivantes :


texte = stdin.readLine(); nombre = Integer.parseInt(stdin.readLine());

sont en fait quivalentes au >> du C++ sur un string et sur un entier. Nous verrons que parseInt() est une mthode statique similaire une fonction C pour raliser la conversion ncessaire. Si le nombre tait un double, il faudrait crire :
double fortune = 0; fortune = Double.parseDouble(stdin.readLine());

34

Apprendre Java et C++ en parallle

Nous oublierons le try et le catch pour linstant, mais nous pouvons aussi essayer dentrer des lettres la place du nombre pour constater le problme suivant :
Entre un texte: Le texte: aaa Entre un nombre: java.lang.NumberFormatException: aaa at java.lang.Integer.parseInt(Compiled Code) at java.lang.Integer.parseInt(Integer.java:458) at Cin.main(Cin.java:16) Exception in thread "main"

Nous verrons comment traiter et rgler ce problme plus loin lorsque nous aborderons les exceptions.

Les oprateurs daffectation composs


Un certain nombre doprateurs composs sont applicables dans les deux langages, Java et C++. Leur prsentation se fera au travers de quelques exemples. Les trois instructions suivantes sont quivalentes :
nombre = nombre + 1; nombre += 1; nombre++;

La deuxime forme est gnralement prfre la premire pour une incrmentation diffrente de 1 ; pour une incrmentation gale 1, la troisime serait de mise. Ces formes simplient aussi bien lcriture que la lecture. Quant aux deux instructions :
nombre--; //prfrable nombre -= 1;

elles sont aussi identiques. Nous avons galement la possibilit de combiner des oprations comme suit :
int nombre = 4; nombre *= 50; // devient 200 nombre /= 49; // devient 4 et non 4.0816 nombre %= 3; // devient 1

Ces oprations sont tout fait correctes, mais rarement utilises. Mais revenons sur les oprateurs ++ et --, que lon retrouve trs souvent et qui peuvent aussi prcder la variable :
int nombre1 = 1; int nombre2 = nombre1++; nombre2 = ++nombre1; // nombre2 = 1 // nombre2 = 3 nombre1 = 2 nombre1 = 3

La dclaration et laffectation des variables numriques CHAPITRE 2

35

Dans la deuxime instruction, lopration ++ se fera avant dassigner la valeur du nombre1 la variable nombre2. Dans la dernire, cest linverse : nous transfrons nombre1 dans nombre2 et ensuite nous incrmentons nombre2. Mais que se passe-t-il avec :
--nombre2 *= ++nombre2;

Bien difcile de le savoir. Il faut donc essayer !


#include <iostream> using namespace std; int main() { int nombre2 = 0; cin >> nombre2; --nombre2 *= ++nombre2; cout << "nombre2: " << nombre2 << endl; return 0; }

On se rendra compte que le rsultat est simplement :


nombre2 = nombre2 * nombre2;

car les oprations ++ et -- ont la prsance sur *=. En Java, si nous essayons de compiler une telle forme, nous obtiendrons :
Quiz.java:17: Invalid left hand side of assignment. (--nombre2) *= (++nombre2); ^ 1 error

En Java, des oprateurs tels que -- ne peuvent tre excuts sur la partie gauche de linstruction, mais seulement droite. Le compilateur est plus restrictif que celui du C++, et il a raison de rejeter cette forme. Linstruction suivante en C++ :
un_nombre++ = 2;

est accepte. Elle na en fait aucun sens, car la valeur 2 est de toute manire affecte un_nombre, et ceci aprs lopration ++. Il faudrait donc garder une forme simple et penser aux lecteurs et correcteurs potentiels de ce code.

Les oprations binaires


Les oprateurs binaires permettent de travailler directement sur les bits qui composent des nombres entiers. Les oprateurs binaires en C++ et Java sont les suivants : & (et), | (ou), ^ (ou exclusif), ~ (non), >> (dplacement vers la droite) et << (dplacement vers la

36

Apprendre Java et C++ en parallle

gauche). Nous allons tudier prsent quelques oprations binaires que les programmeurs utilisent, par exemple, dans des applications lies aux circuits logiques en lectronique. Imaginons un carrefour de circulation routire comportant huit feux de signalisation, qui pourront tre rouges ou verts. Nous nallons pas considrer le passage lorange, que nous pourrions traiter comme une transition. Dnissons les bits comme suit : 0 : signal au vert ; 1 : signal au rouge. En outre, nous nallons pas dnir huit variables, mais une seule contenant 8 bits. Nous utiliserons les types short int en C++ et byte en Java. La combinaison suivante 11111110 signie donc que le premier feu (bit 0) est au vert et tous les autres au rouge. De la mme manire, avec la combinaison 01111111, le bit de gauche (bit 7) nous indique que le huitime feu est au vert. Nous allons prsent effectuer quelques oprations sur ces feux de signalisation, tout dabord en C++ :
// feux.cpp #include <iostream> #include <iomanip> using namespace std; int main() { short int feux = 0xFE; short int nfeux = feux; cout << "Nos feux: " << hex << nfeux << endl; nfeux = cout << nfeux = cout << nfeux = cout << feux | 0x01; "Nos feux: " << feux ^ 0x03; "Nos feux: " << (~feux) & 0xFF; "Nos feux: " << // (A) hex << nfeux << endl; // (B) hex << nfeux << endl; // (C) hex << nfeux << endl;

cout << "Le feu: " << hex << (feux & 0xA0) << endl; // (D) return 0; }

Nous utilisons ici de nouvelles formes 0xFE ou 0x01, qui sont une manire en C++ et en Java de reprsenter des nombres dans une forme hexadcimale. Un nombre hexadcimal est une reprsentation dun nombre en base 16 (de 0 15) en utilisant la notation suivante : 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E et F. Les lettres minuscules (de a f) sont aussi acceptes. Un nombre hexadcimal est en fait un nombre binaire de 4 bits (de 0000 1111). Le nombre 0xFE reprsente donc 11111110, cest--dire tous les feux allums (au rouge) sauf le

La dclaration et laffectation des variables numriques CHAPITRE 2

37

premier. Le nombre 0x01 (00000001) nous indique que seul le premier feu est au rouge. nfeux est une copie de la valeur dorigine. Le tableau ci-dessous devrait vous aider comprendre les explications qui vont suivre. OR, XOR et AND sont des oprateurs binaires qui agissent sur deux composants. NOT, en revanche, est un oprateur unaire. Les deux tableaux suivants (les tables de vrit et un exemple) devraient nous aider :
OR
0 1

0
0 1

1
1 1

XOR
0 1

0
0 1

1
1 0

AND
0 1

0
0 0

1
0 1

Composant 1
0110 0110 0110 0110

Composant 2
1100 1100 1100

Oprateur
OR XOR AND NOT

Rsultat
1110 1010 0100 1001

Revenons au code prcdent : Ligne (A) : loprateur | (OR ou inclusif) permet dajouter des bits, quils soient ou non dj positionns. Le premier feu 0x01 (00000001) deviendra rouge, mme sil ltait dj. Ligne (B) : loprateur ^ (XOR ou exclusif) va mettre les bits 1 (rouge) sils sont diffrents. Avec 0x03 (00000011), nous allons mettre au rouge le premier feu, alors que le deuxime deviendra vert. Le rsultat sera 0XFD, cest--dire 11111101. Ligne (C) : loprateur ~ (NOT) va nous inverser tous les feux. Malheureusement, lopration dinversion va aussi toucher les autres bits du short int, et il est alors ncessaire de masquer le rsultat avec loprateur & (AND) sur les 8 bits qui nous intressent (0xFF, 11111111). Ligne (D) : cette dernire opration (AND et) permet de masquer un ou plusieurs bits an de nous indiquer sils sont ou non positionns. Le cas 0xA0 (10100000) pourrait tre utilis, par exemple pour contrler des feux de passages pitons. Nous allons passer un exemple beaucoup plus bref en Java, car la reprsentation de nombres de ce type nest pas aussi aise quen C++.
public class Feux { public static void main(String[] args) { byte feux = 0x03; // 00000011 System.out.println("Feux: " + Integer.toBinaryString(feux)); feux <<= 2; // 00001100 System.out.println("Feux: " + Integer.toBinaryString(feux));

38

Apprendre Java et C++ en parallle

feux <<= 4; // 01000000 System.out.println("Feux: " + feux >>= 6; // 00000001 System.out.println("Feux: " +

+ ngatif Integer.toBinaryString(feux)); + ngatif Integer.toBinaryString(feux));

int ifeux = 0x03; // 00000011 ifeux >>= 1; // 00000001 System.out.println("IFeux: " + Integer.toBinaryString(ifeux)); } }

Integer.toBinaryString() est une mthode statique, dont nous comprendrons plus tard le mcanisme, qui nous permet dobtenir une reprsentation binaire du rsultat. En excutant ce code, nous obtiendrons :
Feux: Feux: Feux: Feux: IFeux: 11 1100 11111111111111111111111111000000 11111111111111111111111111111111 1

Ici, nous avons volontairement laiss de ct les oprateurs OR, XOR et AND pour utiliser les oprateurs de dcalage. Le premier dcalage gauche se fait avec loprateur <<=, qui est en fait quivalent :
feux = feux << 2;

qui est dune lecture plus simple. Nous aurions donc les deux premiers feux (0 et 1) qui passeraient au vert et les deux suivants (2 et 3), au rouge. Le dcalage suivant de 4 bits nous donne une premire difcult. Le dcalage sur le bit 8 avec loprateur <<= rend la variable ngative et perd en fait le bit. La raison de ces nombreux bits 1 vient dInteger .toBinaryString( ), qui nous donne une reprsentation sur un Integer (4 8 bits) alors que nous avions un byte sur 16 bits. En dcalant nouveau droite, nous nallons pas rcuprer le bit perdu, et la variable restera ngative. Le dernier exemple nous montre que le bit 0 est perdu en dcalant droite et nest pas rcupr sur la droite. Il faut enn noter que si nous crivons :
ifeux = 0xFF; //255

nous avons bien la valeur de 255 (11111111). Si nous voulions faire des rotations de feux de signalisation il faudrait dnir une autre logique et revenir certainement nos oprateurs logiques OR, XOR et AND.

Typedef et numration en C++


Le mot-cl typedef permet en C++ de dnir de nouveaux types de donnes, ce qui nest pas possible en Java. Si nous crivons :
#include <iostream>

La dclaration et laffectation des variables numriques CHAPITRE 2

39

using namespace std; int main() { typedef int Entier; Entier ent = 1; cout << ent << endl; }

nous dnissons Entier comme un nouveau type. Il sagit en fait simplement dun nouveau synonyme (alias) qui remplace int et qui savre vraiment inutile dans ce cas prcis. Le mot-cl typedef na de sens que pour simplier des dclarations parfois compliques. En consquence, un listing de code de programme pour Windows est souvent surprenant :
WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int) { }

car il donne en fait limpression de travailler avec un autre langage, ce qui demande donc un certain temps avant de se familiariser. La dnition de LPSTR, par exemple, tire du Visual C++ de Microsoft, est la suivante : Pointer to a null-terminated string of 8-bit Windows (ANSI) characters. En fait, ce nest rien de plus quun banal char *, une chane de caractres que nous tudierons au chapitre 5. Les numrations en Java sont enn disponibles depuis la version 1.5 du JDK et nous y reviendrons au chapitre 6. Nous verrons aussi une autre manire de faire, en groupant des constantes dans une interface, la n du chapitre 13. En C++, une numration permet de dcrire, sous un mme chapeau dun nom dni avec le mot-cl enum, une liste de noms qui seront affects avec une valeur prdnie. Dans les deux exemples qui suivent :
enum position1 { vide, blanc, noir, exterieur }; enum position { vide = 0, blanc = 1, noir = 2, exterieur = -1 };

la seule diffrence portera sur le deuxime exterieur, qui aura pour valeur 1, au lieu de la valeur 3 alloue automatiquement par le compilateur. Ce dernier commencera toujours par la valeur 0, si elle nest pas choisie explicitement. Cette numration pourrait tre avantageusement utilise dans le jeu dOthello, que nous rencontrerons plusieurs occasions dans cet ouvrage. Naturellement, il aurait t tout fait possible dcrire ceci :
const const const const int vide = 0; int blanc = 1; int noir = 2; exterieur = -1;

Mais si nous crivons prsent :


position pos = vide; pos = 2;

40

Apprendre Java et C++ en parallle

le compilateur refusera cette dernire construction, quil faudrait remplacer par :


pos = noir;

Cette manire de dnir des variables peut donc avoir certains avantages vidents.

Rsum
Dans ce long chapitre, nous avons donc abord la manipulation des variables numriques et de quelques oprations arithmtiques et binaires, tout en mettant laccent sur les cueils et les erreurs grossires de programmation viter. Ces oprations devraient permettre aux programmeurs dlaborer des calculs complexes et varis.

Exercices
Tous les exercices prsents dans cet ouvrage seront programms dans les deux langages. 1. Quelles instructions du langage devons-nous utiliser, sans passer par des fonctions de bibliothque, pour arrondir volontairement un nombre dcimal de la manire suivante : par exemple, 5,500 est arrondi 5 et 5,501 6 ? 2. Afcher la valeur de ! 3. Calculer le reste dune division par 2 sur un entier, sans utiliser les oprateurs / et %, mais en travaillant sur la valeur binaire. 4. crire un programme qui va produire une division par 0 et constater les effets. 5. Je possde la banque une fortune de 12 245,20 qui me rapporte 5,25 % dintrts mais sur lesquels je dois payer 15 % dimpt sur le rendement et 0,02 % sur la fortune. crire le programme pour quil accepte ces donnes de la console et donner un ensemble de valeurs signicatives pour tester la justesse du programme. 6. Reprendre le code int_long2.cpp et crire un nouveau code intitul int_long3.cpp qui multiplie par 2 le rsultat nal. Expliquer la valeur retourne : trange, mais correcte.

3
Et si on contrlait lexcution ?
Contrler lexcution laide dinstructions conditionnelles est la cl de la programmation. Que ce soit pour tester des variables ou des rsultats de fonctions avec linstruction if ou encore rpter une opration avec for, le principe est le mme en Java comme en C++. Tout dabord, nous allons dire un mot sur la forme et la prsentation du code, cest--dire lendroit o les instructions devraient apparatre dans le texte, ainsi que la position des accolades pour du code regroup dans des blocs conditionnels.

Recommandations pour la forme


Ces recommandations ne sont pas ncessairement des rgles suivre, mais plutt des conseils sur la syntaxe adopter. Il suft souvent de recevoir du code de lextrieur, dautres programmeurs, pour se rendre compte que cest un aspect capital. Un code mal crit et mal document donne toujours une mauvaise impression et entrane souvent une perte de temps lorsquil sagit de le comprendre, en vue de le corriger, de le modier ou de le perfectionner. Cest une sorte de frustration. Certes, nous aurions pu ou d accepter un consensus pralable, car il y a souvent des divergences entre les programmeurs. La plupart des entreprises ou socits informatiques dnissent des Design Rules (rgles de conception) an de pallier ces divergences dapproche, dcriture et de style entre les auteurs des programmes. Il ny a alors pratiquement plus de discussion sur le sujet, sauf sur des aspects plus subjectifs comme les commentaires dans le code. Nous allons simplement commencer par un exemple en C++ :
int main(int argc, char **argv) { int variable = 0;

42

Apprendre Java et C++ en parallle

variable = .....; // (appel de fonction) // commentaire if (variable == 0) { // commentaire court for (int i = 0; i < 10; i++) { if (i == 5) { ... } // commentaire ...... } }

En examinant ce morceau de code, voici ce que nous prconisons : Le corps du code principal (main()) doit apparatre avec un ou deux espaces aprs le dbut de la ligne. Seulement un espace entre les diffrentes parties des instructions. Jamais despace aprs les parenthses ouvrante et fermante (), les virgules , ou les points-virgules ;. Deux espaces avant les commentaires courts qui commencent avec des //. Deux espaces ou plus pour les nouveaux blocs dnis avec deux accolades. La premire accolade doit se trouver sur la premire ligne du bloc et la deuxime doit tre aligne verticalement avec le dbut de linstruction. Autre alternative qui apparat plus souvent en C++ :
if (variable == 0) { .... }

Pas de tabulateurs dans le code source. Certains diteurs de texte acceptent les deux, mais cest souvent catastrophique lors de limpression sur papier ou du transfert dans dautres traitements de texte (conversion du TAB en huit espaces, par exemple). Un alignement des variables et de la documentation donnera plus dallure aux programmes :
int nombre; // texte double mon_compte; // texte

Oprateurs de condition
Linstruction if (si) permet de tester une condition et daccepter une ou plusieurs instructions (dans un corps de bloc dlimit par des {}) si cette condition est remplie. Linstruction

Et si on contrlait lexcution ? CHAPITRE 3

43

else (sinon) est utilise, si ncessaire, pour ajouter dautres instructions lorsque la premire condition nest pas accepte.

Nous allons prendre un exemple en C++ de deux nombres entiers entrs par lutilisateur. Le programme nous retournera une indication de comparaison. Si la premire condition nest pas valable, alors la deuxime partie du code sera excute.
// iftest.cpp #include <iostream> using namespace std; int main(int argc, char **argv) { int nombre1 = 0; int nombre2 = 0; cout << "Entre un premier nombre: "; cin >> nombre1; cout << "Entre un deuxime nombre: "; cin >> nombre2; if (nombre1 > nombre2) { cout << nombre1 << " est plus grand que " << nombre2 << endl; } else { cout << nombre1 << " est plus petit ou gal " << nombre2 << endl; } return 0; }

En Java, nous ajouterons un test dgalit en plus.


import java.io.*; public class IfTest { public static void main(String[] args) { try { int nombre1 = 0; int nombre2 = 0; BufferedReader stdin = new BufferedReader(new InputStreamReader(System.in)); System.out.print("Entre un premier nombre: "); nombre1 = Integer.parseInt(stdin.readLine()); System.out.println(nombre1); System.out.print("Entre un second nombre: "); nombre2 = Integer.parseInt(stdin.readLine()); System.out.println(nombre2); if (nombre1 > nombre2) { System.out.println(nombre1 + " est plus grand que " + nombre2);

44

Apprendre Java et C++ en parallle

} else { if (nombre1 < nombre2) { System.out.println(nombre1 + " est plus petit que " + nombre2); } else { System.out.println(nombre1 + " est egal a " + nombre2); } } } catch(IOException ioe) {} } }

Linstruction if et son oprateur > :


if (nombre1 > nombre2) {

signie que nous testons si nombre1 est plus grand que nombre2. Il y a dautres squences possibles comme < (infrieur ), >= (suprieur ou gal ), <= (infrieur ou gal ) ou encore != (non gal , diffrent de). Linstruction if ci-dessus est quivalente :
if (nombre2 <= nombre1) {

cest--dire si le nombre2 est infrieur ou gal au nombre1 ...

Et si ctait faux (False)


Lorsquen C++ nous dsirons tester lgalit entre deux variables ou entre une variable et une valeur constante, il peut nous arriver dcrire linstruction suivante :
if (variable = constante) {

alors que nous voulions en fait crire :


if (variable == constante) {

En revanche, en Java nous aurons :


Mif.java:5: Incompatible type for if. Can't convert int to boolean. if (variable = constante) { ^ 1 error

Ici, variable est un int, et le rsultat est un int. Java naccepte que des variables de type boolean pour linstruction if, et cest une bonne chose. En fait, le programmeur voulait certainement utiliser loprateur == ! En Java, nous pouvons utiliser des variables de type boolean dune manire beaucoup plus systmatique, puisque uniquement des boolens sont accepts pour les tests de condition. Le code Java suivant correspond des situations que nous devrions rencontrer rgulirement :
boolean vrai = true; //... code qui peut modifier la variable vrai if (vrai) {

Et si on contrlait lexcution ? CHAPITRE 3

45

System.out.println("C'est vrai"); }

Une autre construction compose possible et assez courante en C++ est celle-ci :
if ((variable = fonction()) < 0) {

Laffectation de la variable par lappel dune fonction, ainsi que le test, se fait dans la mme instruction. Nous pensons cependant que la forme suivante est prfrable :
variable = fonction(); if (variable < 0) {

Une autre forme encore plus simplie est aussi utilise en C++ :
if (variable) {

Les rgles de loprateur if en C++ sappliquent sur la valeur de la variable. Si la valeur de la variable est 0, le code ne sera pas excut ; sinon, dans tous les autres cas, le code sera excut, mme si ce nest pas forcment 1. Un commentaire tel que :
int resultat = 0; // initialise le rsultat faux (false)

est donc tout fait justi. Malheureusement, la plupart des fonctions C utilisent le retour de fonction avec un 0 pour indiquer que lopration ou le test a pass, alors quune valeur ngative, gnralement 1, est utilise pour indiquer une erreur.

Loprateur logique NOT


Il peut sappliquer tous les oprateurs logiques que nous venons de voir dans les deux langages. Une opration telle que :
if (i !> 2) { //identique <=

signie si i nest pas plus grand que 2 . Cette forme sera prise en compte suivant la condition tester et peut amliorer la lecture. Nous verrons quelle est souvent utilise lorsque plusieurs conditions doivent tre combines. Les codes C++ et Java suivants sont sufsamment explicites et ne demandent pas danalyse particulire :
int i = 1; if (!(i == 2)) { cout << "i n'est pas gal 2" << endl; } int i = 0; if (!(i == 2)) { System.out.println("i n'est pas gal 2"); }

Prconisation du bool en C++


Le Standard C++ a apport un nouveau type, le bool. Ce type peut tre associ deux valeurs possibles, true (vrai) et false (faux). Nous pensons quil peut tre utilis systmatiquement pour du nouveau code, car il amliorera la lisibilit. Dans ce morceau de code :

46

Apprendre Java et C++ en parallle

#include <iostream> using namespace std; int main() { bool resultat = false; cout << "Resultat: " << resultat << endl; // 0 resultat = true; cout << "Resultat: " << resultat << endl; // 1 resultat = -2; // viter cout << "Resultat: " << resultat << endl; // 1 resultat = true; // 0 resultat++; // Jamais ! cout << "Resultat: " << resultat << endl; // 1 }

nous remarquons que le type bool ne ncessite pas de chier den-tte particulier. Il est donc intgr au compilateur, mais nexiste ni en C ni dans les compilateurs plus anciens. Laffectation de la variable avec la valeur de 2 entrane une conversion automatique en 1 (false). Loprateur ++ na aucun sens sur un boolen et pourrait disparatre des nouveaux compilateurs sans grande perte. Loprateur -- est dailleurs dj refus sur une variable de type bool.

Les boucles for, while et do


Les trois structures de contrle for, while et do permettent de rpter des instructions. Bien que la dernire soit la moins utilise, il est tout fait possible dutiliser nimporte laquelle des trois pour faire un travail quivalent. Cependant, il convient de choisir la forme qui fournira le code le plus simple et la prsentation la plus adquate, sans ncessairement penser des considrations doptimisation. Avant de passer notre exemple comparatif, regardons tout dabord une forme tout fait particulire de la boucle for ternelle :
for (;;) { ..... instructions if (...) { break; } }

Cette forme est trs souvent utilise. Cest une boucle innie ! Lorsque la condition if () sera atteinte, correspondant une certaine valeur, un vnement extrieur ou encore un moment dtermin, le break nous permettra de stopper le processus et de se retrouver la sortie du bloc de linstruction for (;;).

Et si on contrlait lexcution ? CHAPITRE 3

47

Passons prsent notre exemple comparatif des trois boucles au moyen de ce mme exercice : imprimer les trois lettres a, b et c au moyen dune boucle et dune instruction spare pour chaque lettre et rpter ceci avec les trois instructions for(), while() et do. Le rsultat sera abcabcabc. Faire une variante du code dans la version Java qui va enchaner les caractres avant de les imprimer. En C++ :
// abc.cpp #include <iostream> using namespace std; int main(int argc, char **argv) { for (int i = 0; i < 3; i++) { cout << (char)('a' + i); } int j = 3; while (j != 0) { cout << (char)('d' - j--); } char lettre = 'a'; do { cout << lettre; lettre++; } while (lettre != 'd'); cout << endl; return 0; }

Il y a dautres constructions possibles, mais la plus tordue reste celle du while() ! Lorsque nous prenons la premire lettre, nous obtenons bien un 'a' avec le 'd' - 3. Comme le -est excut avant, cause des rgles de priorit des oprateurs, il est ncessaire de mettre un 'd'. Nous pourrions trs bien jouer avec un 'z' et calculer les autres valeurs pour que cela fonctionne. Tout ceci pour afrmer que nous pouvons toujours crire du code inutilement illisible (ce qui est le cas ici), mais correct ! Dans la version Java :
public class Abc { public static void main(String[] args) { String abc = ""; for (int i = 0; i < 3; i++) { abc += (char)('a' + i); } int j = 3;

48

Apprendre Java et C++ en parallle

while (j != 0) { abc += (char)('d' - j--); } char lettre = 'a'; do { abc += lettre; lettre++; } while (lettre != 'd'); System.out.println(abc); } }

nous retrouvons la mme structure et la mme syntaxe. Cependant, nous y construisons un String qui nest envoy la console quau dernier moment. En fait, le String abc pourrait tre rutilis dautres ns et, par exemple, tre conserv en interne dans la classe. Revenons la construction particulire du (char), quon retrouve dans les deux langages. La forme (char) est un transtypage.
abc += (char)('a' + i);

Sans le (char), la boucle for () nous donnerait ce rsultat :


979899

cest--dire les valeurs entires dcimales de 'a' (97), 'b' (98) et 'c' (99). En excutant ('a' + i) nous obtenons la valeur dcimale de la lettre dans la table ASCII. Nous comprenons enn comment ce processus fonctionne, avec une astuce en Java, o le caractre ASCII est converti avec loprateur += de la classe String. Lors de lanalyse des performances, plus loin dans cet ouvrage, nous montrerons que lutilisation du String de cette manire est loin dtre efcace. Pour rsumer, la forme for() est vraiment la plus simple. Il faudrait toujours crire du code en pensant quun autre programmeur pourrait modier et corriger ce code. Il est vident que le while() peut trs bien tre accept condition dinverser la logique. La forme do {} while est rarement utilise. Le code suivant :
int k = 0; while (k < 3) { cout << (char)('a' + k); k++; }

est trs clair, bien que la version suivante apparaisse plus souvent :
int k = 0; while (k < 3) { cout << (char)('a' + k++); }

Et si on contrlait lexcution ? CHAPITRE 3

49

Les boucles for en Java partir du JDK 1.5


La boucle for en Java a t amliore et tendue partir du JDK 1.5. Nous y reviendrons plusieurs occasions dans ce livre, mais voici un premier exemple avec les 7 chiffres dun tirage de lEuro Millions :
public class EuroMillions { public static void main(String[] args) { int[] tirage = {3, 7, 13, 23, 32, 1, 2 }; for (int i = 0; i < tirage.length; i++) { System.out.print(tirage[i] + " "); } System.out.println(); for (int numero:tirage) { System.out.print(numero + " "); } System.out.println(); } }

En excutant le programme, nous recevrons deux fois le mme tirage du vendredi de la semaine :
3 7 13 23 32 1 2 3 7 13 23 32 1 2

Cette nouvelle forme for (int numero:tirage) est nettement plus simple. Cest bien le caractre : et le caractre ;. Nous retirons du dbut la n chaque entier dpos dans numero depuis le tableau tirage. Lancienne forme tirage[i] est aussi plus risque si la valeur de i est ngative ou plus grande que 7 ici.

Tester plusieurs conditions


Il est tout fait possible de tester plusieurs conditions lintrieur de ces trois formes ou dexcuter plusieurs instructions dans le for(). Il est aussi possible lintrieur du corps, comme pour lexemple du for (;;), de sarrter pour dautres raisons. Si de tels cas peuvent arriver, il est essentiel dy ajouter les commentaires appropris ou daffecter correctement les variables qui seront utilises aprs la boucle.

Ceci ET cela
Prenons un exemple en C++ : considrons que lcole est obligatoire pour un enfant g dau moins 6 ans, mais de moins de 17 ans. Cela peut se traduire par :
// ecole.cpp #include <iostream>

50

Apprendre Java et C++ en parallle

using namespace std; int main(int argc, char **argv) { int age = 0; bool balecole = false; cout << "Entre ton ge: "; cin >> age; if ((age >= 6) && (age < 17)) { cout << "Tu dois aller l'cole mon fils !" << endl; balecole = true; } else { cout << "Tu peux rester la maison !" << endl; } return 0; }

Cet exemple nous montre comment sutilise loprateur de condition && (AND, et), qui va nous permettre de tester deux conditions. Loprateur >= (plus grand ou gal) va aussi inclure la valeur 6, alors que pour loprateur <, nous limiterons laccs aux jeunes de moins de 17 ans. Le balecole (besoin daller lcole) est ici pour expliquer comment ajouter une variable qui pourrait tre rutilise plus loin dans le code. Le moment est venu de faire une remarque essentielle sur les deux oprateurs && et &, quil ne faut pas prendre lun pour lautre. Si nous crivons :
int valeur1 = 1; int valeur2 = 2; cout << "& : :" << (valeur1 & valeur2) << endl; // 0 cout << "&&: :" << (valeur1 && valeur2) << endl; // 1

nous obtiendrons des rsultats diffrents. Ce problme nexiste pas en Java, qui travaille avec un boolen pour les oprateurs de condition. Comme nous lavons dj mentionn, il vaudrait mieux travailler avec des variables bool, qui sont apparues dans le Standard C++.

Optimisation dans le cas de conditions multiples


Il faut toujours penser ce qui se passe rellement dans le code. Dans linstruction :
if ((age >= 6) && (age < 17)) {

la condition (age >= 6) va tre teste avant (age < 17). Si lge est infrieur 6, la deuxime condition ne sera jamais excute, puisque la premire nest pas vraie. En revanche, si nous crivons :
if ((age < 17) && (age >= 6)) {

Et si on contrlait lexcution ? CHAPITRE 3

51

ceci va nous paratre moins lisible, mais la premire instruction (age < 17) va dabord tre excute. Bien que cette forme soit moins vidente, il faut considrer le cas o elle serait utilise des milliers, voire plusieurs millions de fois ! Comme la plupart des individus sont gs de plus de 16 ans, la deuxime partie (age >= 6) sera excute plus rarement. Donc cette dernire forme est plus avantageuse !

Ceci OU cela
Autre exemple : aujourdhui, il y a aussi des invits de tout ge qui iront lcole, car cest la visite annuelle pour certains parents et amis. Ces invits seront donc accepts lcole indpendamment de leur ge. Voici prsent lexercice en Java :
import java.util.*; public class Ecole { public static void main(String[] args) { int age = 17; boolean bvisiteur = true; if ((bvisiteur) || ((age < 17) && (age >= 6))) { System.out.println("Tu vas l'cole"); } else { System.out.println("Tu ne vas pas l'cole"); } } }

An de simplier le code, nous avons laiss de ct lentre des donnes sur la console. Pour tester dautres conditions, il faudra changer la valeur des variables directement dans le code. bvisiteur sera test en premier. Si celui-ci est vrai, les conditions sur lge ne seront pas vries. En revanche, dans le cas contraire, en raison de loprateur || (OR, ou), la deuxime partie :
((age < 17) && (age >= 6))

sera excute selon la combinaison dj utilise dans lexercice C++. Les parenthses nous aident visionner correctement le code.

viter les tests de conditions multiples compliqus


Une instruction telle que :
if (!((!bvisiteur) && (age != 12))) {

devrait tre vite. Nous remarquons que nous vrions si lge nest pas de 12 ans (!=) : cette dernire construction est tout fait raisonnable. Le (!bvisiteur) est galement acceptable : la condition sera vraie si bvisiteur est faux. Cependant, nous inversons encore une fois le rsultat, et ce code devient alors simplement illisible ! Nous laisserons au lecteur le soin dimaginer une solution plus propre !

52

Apprendre Java et C++ en parallle

Plusieurs slections avec switch


Linstruction switch est parfaite ! Elle combine efcacit et prsentation. Elle na quun seul dfaut : elle ne sapplique quaux types char et int. Le point de dpart doit tre donc une valeur ou un caractre. Au lieu dutiliser une srie dif, else, if, il suft dajouter un nouveau choix. Considrons la commande suivante :
cmd -l -g -s

Les diffrents paramtres, prcds du signe moins, convention trs utilise, sont des options de la commande cmd. Voici le code C++ :
// cmd.cpp #include <iostream> using namespace std; int main(int argc, char **argv) { for (int i = 1; i < argc; i++) { if (argv[i][0] != '-') { cerr << "Caractre - du paramtre manquant" << endl; return -1; } else { if (argv[i][2] != 0) { cerr << "Paramtre invalide (trop long)" << endl; return -1; } switch (argv[i][1]) { case 'l': cout << "Paramtre break; case 'g': cout << "Paramtre break; case 's': cout << "Paramtre break; default: cerr << "Paramtre return -1; } } } return 0; }

l prsent" << endl;

g prsent" << endl;

s prsent" << endl;

invalide" << endl;

Et si on contrlait lexcution ? CHAPITRE 3

53

Le traitement des tableaux viendra au chapitre 5. Cependant, nous pensons que le lecteur ne devrait pas avoir trop de difcults comprendre ce code un peu plus labor. Considronsle comme une introduction plus directe aux tableaux !
argv est un tableau de chane de caractres. argv[1] contient dans notre cas la chane "-l". argv[1][0] contient le signe "-", argv[1][1] la lettre "l" et argv[1][2] le chiffre "0", qui

indique que cest la n de la chane. Cest ensuite une question de structure pour contrler les cas derreur, comme un signe "-" isol sans lettre ou encore une lettre invalide. Voici le code Java prsent :
import java.util.*; public class Cmd { public static void main(String[] args) { for (int i = 0; i < args.length; i++) { if (args[i].charAt(0) != '-') { System.err.println("Caractre - du paramtre manquant"); return; } else { if (args[i].length() != 2) { System.err.println("Paramtre invalide"); return; } switch (args[i].charAt(1)) { case 'l': System.out.println("Paramtre l prsent"); break; case 'g': System.out.println("Paramtre g prsent"); break; case 's': System.out.println("Paramtre s prsent"); break; default: System.err.println("Paramtre invalide"); return; } } } } }

Nous avons dj analys les diffrences concernant les paramtres argc, argv et args au chapitre 1. Au sujet du switch(), le thme de cette partie, il ny a pas de mystre : cest tout fait quivalent en C++ et Java. La diffrence se situe au niveau des variables argv et args. En Java, il faut utiliser le charAt() pour obtenir un caractre dans une position dtermine, car nous travaillons avec un String. En C/C++, cela se fait plus simplement.

54

Apprendre Java et C++ en parallle

Nous notons aussi une diffrence de conception entre les deux versions. En C++, aprs avoir test argv[i][0], nous passons directement argv[i][2], qui devrait tre 0. Cependant, nous navons pas encore test argv[i][1], qui pourrait tre 0 si nous nentrons que "-" sans lettre. En revanche, le langage C++ accepte tout de mme de lire sans problme en dehors des limites. Cest l une des grandes faiblesses de C++. Le cas particulier dun "-" unique sortira en fait avec le choix default, ce qui signie : tous les autres cas. Si nous avions utilis la mme logique en Java avec une instruction quivalente :
if (args[i].charAt(2) != 0) {

nous aurions obtenu une erreur pendant lexcution :


java Cmd -o Exception in thread "main" java.lang.StringIndexOutOfBoundsException: String index out of range: 2 at java.lang.String.charAt(Compiled Code) at Cmd.main(Compiled Code)

Lindex de charAt() doit tre plus petit que args[i].length() ! En Java, il faut distinguer ces deux formes :
args.length args[i].length()

La premire donne la grandeur du tableau, alors que la seconde indique la dimension de la chane pour un String dans le tableau. Nous verrons plus loin que length est intgr dans le compilateur et que length() est une mthode de classe. Une dernire remarque : nous ne contrlons pas le nombre darguments ou la rptition du mme paramtre. Si aucun paramtre nest spci, la commande est accepte. En dautres termes, cela signie que tous les paramtres sont optionnels.

Linfme goto
Il est vraiment infme et devrait tre interdit ! Un programmeur de culture Basic ou assembleur devra apprendre sen passer. Il est accept en C++ et rejet en Java. Nous nen donnerons pas la syntaxe, car cela pourrait donner de mauvaises ides au programmeur. Linstruction goto autorise un transfert direct un endroit spcique du code. Ce qui est plus grave encore est la prolifration possible de multiples points de transfert, dont nous narriverions plus identier lorigine et la raison. Un programmeur Basic devrait samuser programmer sans instructions goto ! Il verrait alors apparatre un code structur quil serait enn possible de documenter dune manire simplie et de modier sans grandes difcults.

Et si on contrlait lexcution ? CHAPITRE 3

55

Rsum
Nous avons appris ici les structures et les instructions fondamentales que nous pouvons retrouver dans la plupart des langages de programmation. Nous sommes prsent prpars pour passer des concepts plus spciques et plus srieux que sont les classes et les objets.

Exercices
1. Modier le programme qui teste les paramtres de la commande cmd -l -g -s an quil accepte un format plus tendu du style cmd -lgs ou cmd -l -gs. Les instructions if, for et switch devraient apparatre. 2. Tester si un entier est pair avec loprateur &. Si lentier est pair, indiquer sil est aussi divisible par quatre.

4
On fait ses classes
Certains diront que cest un peu tt pour prsenter les classes. Mais le problme se pose ici diffremment puisquil sagit dune comparaison directe entre le C++ et Java. Ds le premier exemple Java prsent dans cet ouvrage, nous avions dj rencontr un objet, notre fameux String[] args. En C++, nous avons le char * pour les chanes de caractres et les tableaux multidimensionnels. Nous pouvons mme utiliser la structure struct, qui est hrite du langage C, et crire des programmes complets avec un compilateur C++, sans jamais instancier un objet dune classe (voir dnitions de ces termes page suivante). Nous reviendrons sur ces aspects dans les chapitres suivants.

Notre premire classe en C++


Nous allons crire une classe Personne, partir de laquelle nous pourrons crer des objets C++ qui nous permettront de conserver et dobtenir les donnes suivantes : le nom de famille dune personne ; son prnom ; son anne de naissance sous la forme dun nombre entier.

Dnition de la classe Personne


La premire opration consiste dnir la classe Personne. Comme elle sera rutilise plus tard, nous allons la dnir dans un module part, avec un chier den-tte spar. Ce chier den-tte nous permettra dutiliser cette classe dans dautres parties de code et dapplications. Nous verrons, plus loin dans ce chapitre, comment compiler les diffrents

58

Apprendre Java et C++ en parallle

modules, lorsque nous aborderons le Makefile pour construire une application utilisant notre classe Personne. Voici donc notre premire dnition de classe en C++ :
// Personne.h : dfinition de la classe Personne #include <string> class Personne { private: std::string nom; std::string prenom; int annee; public: Personne(std::string leNom, std::string lePrenom, std::string lAnnee); void un_test(); };

Nous nallons pas trop philosopher sur lutilisation prmature de lobjet string, qui fait partie de la bibliothque Standard C++ et qui est dni par la directive :
#include <string>

Avec cette directive, nous allons accder et inclure le chier string dont le chemin daccs est retrouv par le compilateur (voir la section Directive include plus loin dans ce chapitre). Lutilisation de chanes de caractres C (char *) aurait t galement possible, mais aurait rendu notre prsentation encore plus lourde. Mais revenons la dnition de notre classe qui a la forme :
class Nom_de_la_classe { };

lintrieur des accolades, nous trouvons la dnition des objets et des mthodes de la classe. Cest sans doute le bon moment de donner quelques dnitions : Classe Entit gnrique qui permet dengendrer des objets analogues. Objet Instance dune classe. Un objet sera compos dattributs et de mthodes. Les attributs des objets peuvent tre eux-mmes des objets (nom et prenom) ou des attributs de type primitif (annee est un entier). nom et prenom sont des instances de la classe string de la bibliothque du Standard C++. La forme std::string, dans le chier de dnition Personne.h, est en relation avec lutilisation des espaces de noms pour le Standard C++ (std). Nous verrons au chapitre 6 la raison de lutilisation de cette forme. Nous pouvons considrer pour linstant que std::string est identique string. Instance Terme indiquant quun objet est cr partir dune classe. nom est une instance de la classe string. Objets et instances sont souvent utiliss pour exprimer la mme chose. Mthode Fonction ou procdure unique attribue cette classe permettant daccder aux donnes de lobjet ou de modier son comportement en changeant ses attributs.

On fait ses classes CHAPITRE 4

59

Constructeur Mthode qui permet dinitialiser lobjet. Elle est appele la cration de linstance. Destructeur Mthode appele la destruction de lobjet an deffacer ou de librer certaines ressources encore actives qui ont t utilises pendant une partie ou toute la dure de vie de lobjet. Prsentons ensuite les deux mots-cls, private et public. public nous indique que les objets ou les mthodes de la classe sont directement accessibles. Nous verrons des exemples plus loin. private est utilis pour cacher des objets lintrieur de la classe et ne les rendre accessibles quau moyen de mthodes de classe. Les trois attributs de la classe Personne (nom, prenom et annee) sont privs, ce qui constitue un bon dpart en programmation oriente objet. Le caractre : de private nous indique le dbut de la partie prive de la classe. Ce bloc se termine ici au public: suivant. Il est tout fait possible de mlanger les parties publiques et prives et den dnir plusieurs. Cest cependant une bonne habitude de les regrouper comme cest le cas dans notre dnition de classe. Cest propre et clair.

Dnition des objets dune classe


Tous les attributs devraient tre privs (private) ; cest lun des fondements de la programmation oriente objet. Un attribut dclar public sera accessible et modiable par nimporte qui, ce qui signie quil ny aura aucun contrle. En revanche, sil est priv, seules les mthodes de la classe auront un droit daccs. Celui-ci pourra donc se faire au travers de mthodes dnies une fois pour toute (API), et le programmeur aura ainsi un contrle total du code de sa classe. Il pourra mme changer cette dernire, la dnition de ces attributs, et ceci avec la seule contrainte extrieure de garder inchange la dnition des mthodes publiques de sa classe.

Un seul constructeur
Pour ce premier exercice, nous ne dnirons quun seul constructeur et quune seule mthode. Le constructeur :
Personne(std::string leNom, std::string lePrenom, std::string lAnnee);

permet au programmeur de crer un objet de cette classe. Nous le verrons dans lexemple ci-dessous. Un constructeur ne peut retourner de valeur, ce qui signie que nous ne pouvons jamais vrier le rsultat de la cration dobjet directement. Nous traiterons les dtails plus loin dans cet ouvrage. Dans notre exemple de constructeur, nous avons choisi trois paramtres. Cest un choix que nous avons fait. Si dans la conception du programme nous avions dtermin que lanne de naissance ntait pas primordiale, nous laurions laisse de ct.

Une seule mthode


La mthode que nous avons choisie ne peut tre plus simple : elle na ni paramtre ni valeur de retour (indique par void).

60

Apprendre Java et C++ en parallle

void un_test();

Nous en comprendrons la raison trs rapidement.

Nom et dnition des classes


Il est recommand de dnir un nom de classe commenant par une majuscule. En C++, au contraire de Java, il nest pas ncessaire dcrire systmatiquement des classes. Il est tout fait possible dlaborer un programme complet en C++ sans construire de classes, mais en utilisant simplement les outils des bibliothques C ou C++ du langage. Cependant, ds que nous pensons rutiliser ce code dautres ns, il est judicieux de penser laborer de nouvelles classes, plus ou moins globales et gnriques, et de les collectionner dans une bibliothque, comme nous le verrons au chapitre 7.

Code de la classe Personne


Ds que la classe est dnie, il sagit dcrire le code ncessaire qui nous permettra de crer des instances de cette classe et dutiliser ses mthodes. La forme "Personnes.h" de la directive #include ci-dessous nous indique que le chier Personnes.h, qui sera inclus lors de la compilation, se trouve dans le rpertoire courant, cest--dire celui du chier Personne.cpp (voir la section Directive include plus loin dans ce chapitre).
// Personne.cpp #include "Personne.h" #include <iostream> #include <sstream> using namespace std; Personne::Personne(string leNom, string lePrenom, string lAnnee) { nom = leNom; prenom = lePrenom; //flux pour la conversion dun string istringstream iss(lAnnee); iss >> annee; } void Personne::unTest() { cout << "Nom et prnom: " << nom << " " << prenom << endl; cout << "Anne de naissance: " << annee << endl; }

La premire remarque concerne loprateur ::, quon nomme oprateur de porte. Le nom prcdant :: nous indique le nom de la classe. Personne::Personne(...) reprsente le constructeur et Personne::un_test() une mthode de la classe Personne.

On fait ses classes CHAPITRE 4

61

Nous dcouvrons ici le code ncessaire pour le constructeur de Personne et de la mthode un_test(), qui nous permettra de vrier notre premire classe. Les deux directives include sont ncessaires pour obtenir les dnitions de lobjet cout, que nous avons vu dans le premier chapitre, et de la classe istringstream. Nous reviendrons plus loin sur cette dernire, qui fait partie de ces classes essentielles du Standard C++ quil sagit de matriser. Ce quil faut retenir ici est que les instructions :
istringstream iss(lAnnee); iss >> annee;

nous permettent dextraire du string lAnnee sa valeur entire, car annee est un entier (int). Nous dposons notre string dans lobjet iss qui possde un oprateur dextraction >> dans un entier, an de nous retourner sa valeur (voir chapitre 9 pour plus de dtails). Nous aurions pu choisir de dnir notre constructeur de cette manire :
Personne(string leNom, string lePrenom, int lAnnee);

et la conversion naurait pas t ncessaire. Cependant, nous pouvons considrer que les trois variables (nom, prnom et anne de naissance) seront dnies par une interface utilisateur qui passera les paramtres en tant que string. Ceci est plus une question de choix au cours de la conception, mais rien ne nous empcherait de dnir un deuxime constructeur. Le langage C++, comme Java dailleurs, nous le permet. Il faudrait alors ajouter la ligne ci-dessus dans Personne.h, ainsi que le code suivant dans le chier Personne.cpp :
Personne::Personne(string leNom, string lePrenom, int lAnnee) { nom = leNom; prenom = lePrenom; lannee = lAnnee; }

Une autre forme plus concise est aussi disponible pour linitialisation des objets (attributs) de la classe :
Personne::Personne(string leNom, string lePrenom, int lAnnee) :nom(leNom), prenom(lePrenom), annee(lAnnee) { }

Ici, les variables prives qui suivent directement le caractre : seront affectes avec les valeurs dnies entre parenthses. Ces dernires doivent faire partie des paramtres du constructeur. La mthode un_test() na besoin ni de recevoir de paramtre ni de retourner un quelconque rsultat. Elle est juste l pour sortir sur la console le contenu des attributs de cette classe. Enn, nous passons au programme de test de notre classe :
// fichier: TestPersonne.cpp // test de la classe Personne #include "Personne.h"

62

Apprendre Java et C++ en parallle

int main() { Personne haddock("Haddock", "Capitaine", "1907"); haddock.un_test(); return 0; }

Ce code viendra dans un chier spar, TestPersonne.cpp. Le #include est essentiel, car il nous permettra daccder la dnition de la classe Personne et de sa mthode un_test(). Au premier abord, linstruction qui correspond crer une instance de la classe Personne :
Personne haddock("Haddock", "Capitaine", "1907");

peut nous paratre trange. Personne est le nom de la classe, haddock est la variable, suivie des trois paramtres exigs par le constructeur. Une instruction telle que :
int valeur(10);

sur une variable de type primitif est aussi possible en C++. Nous allons dailleurs trs rapidement nous familiariser avec cette forme, qui nest pas permise en Java. lexcution du programme, nous obtiendrons nalement :
Nom et prnom: Haddock Capitaine Anne de naissance: 1907

Ceci correspond au rsultat attendu de la mthode test() appliqu linstance haddock de la classe Personne. Enn, et par analogie la forme de linstanciation en Java, il nous faut prsenter ici une autre manire essentielle de crer une instance en C++ :
Personne *ppersonne; ppersonne = new Personne("Haddock", "Capitaine", "1907"); ppersonne->un_test(); delete ppersonne;

Ceci se fait de la mme manire quune variable de type pointeur, que nous avons dcouvert au chapitre 2. Le caractre * indique que nous avons affaire une variable du type pointeur, car loprateur new nous retournera un pointeur en mmoire o se trouve effectivement lobjet. En C++, il y a donc deux formes pour la cration dobjets, ce qui nest pas le cas en Java. Le -> est ncessaire pour indiquer au compilateur que ppersonne est un pointeur. Si le delete ntait pas prsent, lobjet resterait en mmoire et ne serait jamais libr. Nous reviendrons sur les dtails en n de chapitre, aprs la partie consacre Java.

Directive include
Jusqu prsent, nous avons rencontr trois formes de directive include, dont la dernire au chapitre 1 :
#include "Personne.h"

On fait ses classes CHAPITRE 4

63

#include <iostream> #include <time.h>

La premire, avec les "", va inclure le chier Personne.h lors de la compilation g++ de Personne.cpp en recherchant le chier dans le rpertoire courant. Si nous avions crit :
#include "perso/Personne.h"

il aurait fallu crer un sous-rpertoire perso dans le rpertoire de travail (le caractre \ convient aussi, mais le caractre / est prfrable pour la compatibilit avec Linux). Les iostream et time.h indiquent des rfrences des chiers den-tte du C++ et du C respectivement. Cest le compilateur qui retrouvera ces rfrences. Pour notre installation, ils se trouvent dans les rpertoires suivants : C:\MinGW\include\c++\3.4.5 ; C:\MinGW\include. Nous pouvons videmment charger ces chiers dans Crimson et comprendre comment ils sont construits. Le lecteur y dcouvrira sans doute les directives #define et #ifndef qui permettent dintroduire un mcanisme pour ninclure quune seule fois dautres chiers den-tte, dj inclus lors dincludes multiples. Nous y reviendrons la n du chapitre 6, section Fichiers den-tte multiples en C++ . Ceci concerne videmment la compilation des objets .o. Cependant, lors de la gnration de lexcutable, nous aurons un mme mcanisme, transparent pour le programmeur, qui va inclure cette fois-ci du code excutable. Le cout ci-dessus, dans le code unTest() du chier Personne.cpp, aura besoin du chier C:\MinGW\include\c++\3.4.5\string pour la gnration de Personne.o. Le chier nal excutable TestPersonne.exe aura besoin de plusieurs morceaux de code binaire disponibles dans une ou plusieurs bibliothques .a que nous retrouvons dans le rpertoire du compilateur g++ : C:\MinGW\lib. Au chapitre 7, section Utilisation de notre bibliothque C++ , nous dcrirons le paramtre de compilation I. Celui-ci nous permettra de compiler nos codes source en recherchant nos chiers den-tte .h dans les rpertoires spcis par ce I. Nous avons un mme mcanisme pour des bibliothques prives avec le L. Cest aussi dans ce mme chapitre que nous verrons comment crer nos propres bibliothques .a.

Commentaires et documentation des classes


Il nest pas ncessaire dajouter trop de commentaires dans les chiers .cpp correspondant limplmentation des classes. Cependant, si, par exemple pour des raisons doptimisation, le code devient plus complexe, il est conseill de dcrire plus en dtail ce qui pourrait sembler difcile dchiffrer pour un programmeur qui devrait reprendre ce code. Cependant, lendroit correct et idal pour la documentation reste le chier den-tte. Celui-ci peut aussi tre livr sparment avec une bibliothque prcompile. Nous allons donner ici un exemple de documentation possible pour notre classe Personne. Le chier Personne.h pourrait se prsenter comme ceci :

64

Apprendre Java et C++ en parallle

#include <string> class Personne { private: std::string nom; // nom de la personne std::string prenom; // prnom de la personne int annee; // anne de naissance de la personne public: Personne(std::string leNom, std::string lePrenom, std::string lAnnee); // Description : // Constructeur // Aucun contrle sur le contenu des variables nest // excut. // // Paramtres : // leNom - le nom de la personne // lePrenom - le prnom de la personne // lAnnee - lanne de naissance de la personne // Personne(char *unNom, char *unPrenom, char *uneAnnee); // Description : // Constructeur // Avec des char* classiques // // Paramtres : // unNom - le nom de la personne // unPrenom - le prnom de la personne // uneAnnee lanne de naissance de la personne // Personne(std::string leNom, std::string lePrenom, int lAnnee); // Description : // Constructeur // Aucun contrle sur le contenu des variables nest // excut. // // Paramtres : // leNom - le nom de la personne // lePrenom - le prnom de la personne // lAnnee lanne de naissance de la personne // void unTest(); // Description : // Affiche sur la console tous les attributs de la classe. // Uniquement utilis des fins de test. // // Paramtres : // Aucun

On fait ses classes CHAPITRE 4

65

// // Valeur de retour : // Aucune // };

Notons dj quelques points de dtail importants : Documenter les variables prives nest en fait utile que pour les dveloppeurs de la classe, car les utilisateurs nont pas directement accs ces attributs. Comme le constructeur ne peut retourner de valeur, cest--dire de rsultat, une description de la valeur de retour nest pas ncessaire. Nous touchons dj un sujet dlicat, sur lequel nous reviendrons, qui est de savoir comment traiter les cas derreur dans un constructeur. Comme rgle de base, nous dirons simplement que le constructeur ne devrait pas allouer de ressources pouvant entraner des difcults. La premire remarque qui nous saute aux yeux est le contenu de ce code, qui ne contient en fait que des dnitions, et ceci des ns danalyse et de conception. Nous pouvons trs bien distribuer cette dnition sans coder limplmentation, simplement pour vrication (design review). Cette dnition de classe est-elle sufsante nos besoins ? Certainement pas ! Il faudrait y ajouter un certain nombre de mthodes plus volues et se poser toute une srie de questions comme celle de savoir ce que voudrait bien dire un ge ngatif ou un nom et un prnom vide. Ce quil faut retenir avant tout ici, cest de savoir si les commentaires sont sufsants pour un programmeur qui va utiliser cette classe. Si ce but est atteint, cest trs bien, tout simplement !

Un Makele volu
Lexemple ci-dessus de la classe Personne et de son programme de test est plus complexe que celui du premier chapitre, dans lequel le make de GNU a t prsent. Nous pouvons maintenant passer un exemple nous montrant dautres aspects dun Makefile.
all: testp.exe testp.exe: Personne.o TestPersonne.o g++ -o testp.exe Personne.o TestPersonne.o Personne.cpp Personne.h g++ -c Personne.cpp TestPersonne.cpp Personne.h Personne.h g++ -c TestPersonne.cpp

Personne.o:

TestPersonne.o:

Attention tout dabord aux tabulateurs et aux espaces : nous rappellerons, par exemple, quentre testp.exe et Personne.o, il ny a pas despace, seulement un ou plusieurs tabulateurs.

66

Apprendre Java et C++ en parallle

En revanche, entre Personne.o et TestPersonne.o, il y a un espace et non un tabulateur. En cas de doute, consultons un Makefile sur le CD-Rom fourni avec cet ouvrage. Le chier binaire et excutable testp.exe est form de deux composants, Personne.o et TestPersonne.o, qui sont compils sparment puis lis (option o). Lorsque Test Personne.o, qui contient le point dentre main(), est li avec Personne.o, il est possible que le compilateur dcouvre une ressource manquante. Celle-ci provient en gnral de la bibliothque standard du compilateur et non pas de la classe Personne, car le chier dentte Personne.h est inclus dans le chier TestPersonne.cpp, et les ressources de cette classe ont dj t vries par la premire phase de compilation. Ce qui est important de noter ici, cest la dpendance supplmentaire sur Personne.h que nous avons rajoute. Si ce dernier chier est modi, la dpendance se rpercutera sur les trois parties, et tous les composants seront nouveau compils. Il est donc essentiel de vrier soigneusement son Makefile ainsi que la prsence correcte de tabulateurs, comme nous lavons vu au chapitre 1.
Personne.o est donc la premire partie excute par le Makele. Notons que loption -c demande au compilateur de produire un chier binaire .o partir du code source (par exemple, Personne.cpp deviendra Personne.o). Personne.o reprsente du code binaire, mais

il nest pas excutable. Ce nest que le rsultat de la premire phase de la compilation, et il lui manque par exemple toutes les fonctions de la bibliothque, ainsi que le code ncessaire au dmarrage du programme. Loption -o est suivie du nom que nous voulons donner au programme excutable. Le compilateur devra lier ses diffrents composants et lui ajouter les rfrences externes comme les fonctions de la bibliothque des iostreams. Le compilateur saura en fait luimme trouver ces bibliothques avec le chemin daccs ..\lib\ partir de son rpertoire dinstallation (bin) sur le disque. Si nous crivons :
g++ -o testp.exe Personne.o testPersonne.cpp

le compilateur commencera par compiler testPersonne.cpp, mais ne produira pas de test Personne.o car il sera directement intgr dans testp.exe. Cependant, il est prfrable de passer par diffrentes phases, qui peuvent tre spcies par des dpendances dans un
Makefile.

Notre premire classe en Java


Nous allons prsent faire le mme exercice en Java avec la classe Personne que nous venons de crer en C++. Nous constaterons quil existe beaucoup de similitudes avec le C++. Cependant, en Java, il ny a pas de chiers spars pour la dnition et le code de la classe, car ces deux composants ne peuvent tre dissocis et doivent se trouver dans le mme chier .java.

On fait ses classes CHAPITRE 4

67

Comme en C++, il est recommand de dnir un nom de classe commenant par une majuscule. Le nom des chiers .h et .cpp suivra cette rgle. Dans la mesure du possible, nous ne dnirons quune seule classe par chier den-tte .h. Nous avions au chapitre 1 deux chiers source : hello.cpp et Hello.java. Le h de hello.cpp tait en minuscule, mais ce ntait pas le cas de Hello.java. On se rappellera de plus que le nom de la classe et le nom du chier doivent tre identiques, incluant les majuscules et minuscules correctes. Voici donc notre code Java correspondant notre classe Personne prcdemment dnie :
public class Personne { private String nom; private String prenom; private int annee; Personne(String lenom, String leprenom, String lannee) { nom = lenom; prenom = leprenom; annee = Integer.parseInt(lannee); } Personne(String lenom, String leprenom, int lannee) { nom = lenom; prenom = leprenom; annee = lannee; } void un_test() { System.out.println("Nom et prnom: " + nom + " " + prenom); System.out.println("Anne de naissance: " + annee); } public static void main(String[] args) { Personne nom1 = new Personne("Haddock", "Capitaine", "1907"); Personne nom2 = new Personne("Kaddock", "Kaptain", 1897); nom1.un_test(); nom2.un_test(); } }

Nous retrouvons les mmes mots-cls quen C++ pour les permissions daccs : private et public. Nous avons aussi laiss les deux constructeurs. Tout dabord, il nous faut revenir sur une construction trs particulire :
annee = Integer.parseInt(lannee);

qui peut sembler complexe. lannee est un String, et il sagit de le convertir en int. parseInt est une mthode statique de la classe Integer qui fera la conversion. Dans la partie main(), nous constatons que les deux directives new sont ncessaires en Java. Il ny a pas dautre alternative comme en C++. Nous analyserons les dtails un peu plus loin dans ce chapitre.

68

Apprendre Java et C++ en parallle

lexcution du programme, nous obtiendrons :


java Personne Nom et prnom: Haddock Capitaine Anne de naissance: 1907 Nom et prnom: Kaddock Kaptain Anne de naissance: 1897

Apparat ici une diffrence essentielle avec la version C++. En effet, le :


public static void main(String[] args) {

nous permet de tester directement notre classe. Si nous voulions tester la classe sparment, comme nous lavons fait avec TestPersonne en C++, il nous faudrait crire une classe spare, TestPersonne, qui serait programme de cette manire :
public class TestPersonne { public static void main(String[] args) { Personne nom1 = new Personne("Paddock", "Capitaine", "1907"); Personne nom2 = new Personne("Maddock", "Kaptain", 1897); nom1.un_test(); nom2.un_test(); } }

Pour pouvoir excuter java TestPersonne, il est ncessaire, sans autres complications pour linstant, davoir la classe Personne.class dnie dans le mme rpertoire !

Tester les classes


Il est tout fait possible de laisser un :
public static void main(String[] args) {

dans toutes les classes an de pouvoir les tester sparment. Une application, du style de TestPersonne, mais employant plusieurs classes, ne va pas contrler ni utiliser toutes les mthodes et cas derreurs implments dans la classe. Alors, pourquoi ne pas garder un main() dans la classe, avec une bonne logique de test, et ventuellement leffacer lors de la prparation nale ou de la livraison du produit ?

Commentaires et documentation des classes


Lorsque nous parlons de documentation en Java, nous pensons immdiatement javadoc, un produit de Sun Microsystems. La premire fois quun programmeur excute javadoc, il est simplement merveill ! Essayons donc avec notre premire classe Personne.java. Le premier conseil donner est de copier notre chier Personne.java dans un nouveau rpertoire vide et dexcuter :
javadoc Personne.java

On fait ses classes CHAPITRE 4

69

La premire surprise vient du nombre de chiers gnrs, savoir une feuille de style .css et de nombreux documents .html ! Pour constater le rsultat, il suft de charger le document index.html avec Internet Explorer ou Netscape. Une merveille, cette petite classe Personne ! Par analogie avec notre documentation de classe en C++, il est aussi possible dutiliser cette documentation an de vrier la conception de notre classe Personne.java. Dans ce cas, il est souhaitable de gnrer aussi la partie prive de la classe. Cela se fait avec loption private :
javadoc doc doc -private Personne.java

Loption doc permet de spcier le rpertoire dans lequel seront gnrs les chiers .html. Pour simplier ou rgnrer rapidement la documentation, nous avons cr un chier genJavadoc.bat.
javadoc est un outil puissant quil est possible de congurer nos besoins, an de dcrire notre classe tout aussi bien que nous lavons fait pour notre classe C++. Au premier abord, cette nouvelle version peut nous paratre trange, mais aprs lavoir javadoce , il en sera tout autrement ds que nous laurons visionne avec notre navigateur Web favori.
/** * La classe <code>Personne</code> est notre premire classe en Java. * Dune simplicit extrme, elle est juste l comme introduction. * Il est possible de lutiliser de cette manire : * <p><blockquote><pre> * Personne nom1 = new Personne("Paddock", "Capitaine", "1907"); * nom1.un_test(); * * </pre></blockquote> * * @author J-B.Boichat * @version 1.0, 18/07/08 */ public class Personne { private String nom; private String prenom; private int annee; /** * Alloue une nouvelle <code>Personne</code>. * Lanne est un String. * * @param leNom Le nom de la personne. * @param lePrenom Le prnom de la personne. * @param lAnnee Lanne de naissance de la personne. */ public Personne(String leNom, String lePrenom, String lAnnee) { nom = leNom;

70

Apprendre Java et C++ en parallle

prenom = lePrenom; annee = Integer.parseInt(lAnnee); } /** * Alloue une nouvelle <code>Personne</code>. * Lanne est un entier. * * @param leNom Le nom de la personne. * @param lePrenom Le prnom de la personne. * @param lAnnee Lanne de naissance de la personne. */ public Personne(String leNom, String lePrenom, int lAnnee) { nom = leNom; prenom = lePrenom; annee = lAnnee; } /** * Affiche sur la console tous les attributs de la classe. * Uniquement utilis des fins de test. * * @return Aucun */ public void un_test() { System.out.println("Nom et prenom: " + nom + " " + prenom); System.out.println("Anne de naissance: " + annee); } public static void main(String[] args) { Personne nom1 = new Personne("Haddock", "Capitaine", "1907"); nom1.un_test(); } }

Notons tout dabord la squence des /** .*/. javadoc est capable dinterprter des commandes lintrieur dune squence commenant par /** et qui se termine par */. Les * sur les lignes intrieures sont ignors par javadoc. Nous dcouvrons de plus toute une srie de mots-cls (<code>, </code>, <p>, <blockquote>, <pre>, </pre> ou encore </blockquote>) qui permettent dencastrer du code HTML. Ceci permet de rendre la documentation plus lisible, car ils apparatront de toute manire en HTML. Il y a aussi dautres codes comme @author, @version, @param ou @return qui sont interprts par javadoc. @param est utilis pour dnir les paramtres dentre des mthodes et du constructeur et @return pour la valeur de retour. Nous montrons ici une partie du code gnr en HTML pour notre classe :

On fait ses classes CHAPITRE 4

71

Classe Personne
java.lang.Object | +--Personne public class Personne extends java.lang.Object

La classe Personne est notre premire classe en Java. Dune simplicit extrme, elle est juste l comme introduction ! Il est possible de lutiliser de cette manire :
Personne nom1 = new Personne("Paddock", "Capitaine", "1907"); nom1.un_test();

La syntaxe communment utilise pour Firefox ou Internet Explorer est la suivante (nous pouvons aussi double-cliquer sur le document index.html dans lexplorateur de Windows) :
file:///C:/JavaCpp/EXEMPLES/Chap04/doc/index.html

file:/// au lieu de http:// indique que le document est accessible localement sur notre

machine. Nous verrons quil nous est possible de naviguer entre les attributs ou mthodes. Tout en haut de ce document index.html, nous aurons des liens renvoyant dautres documents gnrs par javadoc : le lien Index est particulirement intressant. La documentation de javadoc avec ses nombreuses options se trouve sur le CD-Rom daccompagnement ; elle est installe avec les outils du JDK de Sun Microsystems, comme dcrit dans lannexe B. Loutil de dveloppement NetBeans (voir annexe E) possde des instruments puissants pour prparer ldition de la documentation (gnration automatique de modle incluant les paramtres des mthodes de classe).

Cration des objets


Nous allons montrer prsent les diffrences importantes entre Java et C++ lors de la cration des objets. Le langage Java a simpli au maximum le processus, alors que le C++ a plusieurs faons de procder et requiert beaucoup plus de prcautions. Lorsque nous crivons en Java :
String nom = new String("mon_nom_de_famille"); System.out.println(nom);

la variable nom aprs lexcution de la premire instruction contient un objet instanci de la classe String qui renferme notre texte "mon_nom_de_famille". Le println de linstruction suivante est lgitime car la classe String possde une mthode toString() qui va rendre lopration possible au travers de la mthode println(). Cependant, si nous crivions :
String nom = "mon_nom_de_famille";

la manire C/C++, cela fonctionnerait aussi, car le compilateur accepte cette forme particulire, mais uniquement pour cette classe.

72

Apprendre Java et C++ en parallle

Nous allons examiner les quivalences possibles en C++ :


string nom = "mon_nom_de_famille"; cout "nom" endl;

ou bien
string *pnom; pnom = new string("mon_nom_de_famille"); cout "*pnom" endl; delete nom;

En Java, tous les objets sont construits avec le new : ce nest pas le cas en C++. Un objet C++ peut tre cr sur la pile (stack) et disparatra lorsque nous sortirons du corps de la mthode (ici laccolade fermante }). En Java et en C++, lutilisation de new fonctionne de la mme manire, et les objets seront crs dans le tas (heap). Cependant, dans tous les cas, mme sans oprateur new en C++, le constructeur sera appel avec les paramtres ncessaires. En Java, il ny a pas de destructeur, donc pas de delete. Le delete en C++ sapplique sur une adresse dobjet. Le delete sera responsable de leffacement des ressources au moyen du destructeur, qui sera aussi appel si lobjet est dans la pile. Les objets Java sont automatiquement effacs par le rcuprateur de mmoire (leffaceur ou ramasse-miettes ou garbage collector) lorsquils ont perdu toutes rfrences des variables utilises. En C++, si on oublie un delete, cest dramatique. Si nous crivons en C++ :
string *pnom; pnom = new string("mon_nom_de_famille"); pnom = new string("mon_nouveau_nom_de_famille");

un objet est nouveau cr avec la dernire instruction, et le pointeur de lancienne mmoire est perdu. Cette zone mmoire restera alloue jusqu lachvement du programme. Comme celui-ci peut trs bien tre un processus permanent, il faut donc absolument effacer lobjet avant de rutiliser la variable pnom pour sauvegarder ladresse dun nouvel objet. Sauvegarder cette adresse est essentiel, car il faudra, un moment ou un autre, effacer cette zone mmoire du tas. En C++, il est recommand didentier clairement les variables situes sur le tas de celles prsentes sur la pile, en utilisant par exemple un nom de variable commenant par p (pointeur). Une variable C++ commenant par un p pourra signier que nous avons affaire un objet allou avec un new, qui devra tre effac avec un delete en n de procdure ou dans un destructeur pour des attributs dobjet ou de classe (variables statiques).

Make, javac et redondance


Il y a une fonctionnalit de javac quil nous faut absolument mentionner. Dans notre exemple prcdent, si nous modions le code de Personne.java et excutons la compilation sur la classe de test seulement :

On fait ses classes CHAPITRE 4

73

javac Test_personne.java

nous allons constater que la classe Personne.class sera aussi rgnre. javac est assez intelligent pour se comporter en fait comme un make qui sait dterminer, grce aux dates de modication des chiers, si une nouvelle compilation est ncessaire. Il apparat donc une certaine redondance avec le make de GNU, qui reste tout de mme beaucoup plus puissant.

Nos classes Java dans un paquet .jar


Nous reviendrons sur les bibliothques de plusieurs classes Java regroupes dans un .jar et le CLASSPATH au chapitre 7. Nous allons tout de mme vous prsenter un exemple simple dune petite application regroupe dans un chier .jar et comment lexcuter. Nous verrons dans lannexe E que NetBeans construit ses applications de cette manire. Dans le rpertoire Chap04 des exemples, nous avons ajout les deux chiers suivants : DansUnJar.bat et monManifest.txt avec leur contenu respectif :
@rem fichier DansUnJar.bat del personnes.jar jar cvf personnes.jar Personne.class TestPersonne.class java -classpath personnes.jar TestPersonne java -classpath personnes.jar Personne jar umf monManifest.txt personnes.jar java -jar personnes.jar

et
Main-Class: TestPersonne

Le @rem, que nous utilisons ici pour la premire fois, nous permet dajouter un commentaire dans un chier .bat, ce qui est trs pratique. Le premier jar :
jar cvf personnes.jar Personne.class TestPersonne.class

permet de construire une archive compresse de nos deux classes :


manifest ajout ajout : Personne.class (entre = 1285) (sortie = 693) (46% compresss) ajout : TestPersonne.class (entre = 499) (sortie = 334) (33% compresss)

Le manifest contient les informations sur larchive : ici, un chier par dfaut sera gnr. Il ne contient pas de point dentre et nous serons alors obligs dexcuter notre programme sous la forme :
java -classpath personnes.jar TestPersonne

74

Apprendre Java et C++ en parallle

Le point dentre main() de la classe TestPersonne sera activ. La classe TestPersonne est contenue dans larchive personnes.jar avec les autres classes (ici, Personne uniquement). Si nous navions pas de classpath, cest--dire :
java TestPersonne

nous devrions alors copier les chiers Personne.class et TestPersonne.class dans le rpertoire courant. Lutilisation de chiers .jar savre ncessaire lorsquil y a un grand nombre de classes ou dautres chiers venant dailleurs (pas dexemples donns dans cet ouvrage). La forme
java -classpath personnes.jar Personne

fonctionne aussi mais, cette fois, le main() de la classe Personne est utilis et la classe TestPersonne, dans larchive .jar, ne sert rien. Comme nous le reverrons plusieurs occasions, un petit main() dans chaque classe pourrait savrer utile pour une vrication rapide de notre application. La forme
jar umf monManifest.txt personnes.jar

est intressante : elle ajoute notre chier personnes.jar une option du manifest dans larchive, que nous avons introduite dans le chier monManifest.txt ci-dessus, cest--dire la dnition du point dentre main(). Lexcution avec
java -jar personnes.jar

va fonctionner, car le point dentre est dni dans le manifest de notre archive (les dtails et options de notre chier personnes.jar). Pour plus dinformations et dautres options, il faudra consulter la documentation de Java.

Comment tendre notre classe Personne ?


Au travers de llaboration de ces deux classes, nous nous sentons plus laise et avons le sentiment dacqurir une meilleure matrise du sujet. Cependant, ces deux classes restent trs primitives et mme inutilisables. Essayons maintenant dimaginer comment tendre cette classe Personne an quelle puisse acqurir plus de consistance. Pour ce faire, nous allons introduire de nouvelles donnes (attributs) ainsi que des fonctionnalits supplmentaires (mthodes), que nous allons dnir comme suit : Cette personne travaille dans une entreprise qui lui a attribu un numro de tlphone unique quatre chiffres. Cette entreprise multinationale rmunre en dollars ses employs, mais ces derniers reoivent leur salaire mensuel dans la monnaie du pays o ils travaillent. Le taux de change peut tre modi tout instant.

On fait ses classes CHAPITRE 4

75

Tout salari reoit au minimum vingt jours de vacances par anne, mais avec un jour de plus tous les cinq ans partir de vingt-cinq ans. Un employ de 50 ans aura donc vingtsix jours de vacances. Le nombre de jours de vacances restant peut tre transfr lanne suivante. Pour traiter ce genre de problmes, cest--dire ajouter les mthodes et les attributs ncessaires notre classe, nous voyons tout de suite les difcults. ce stade de nos connaissances, nous allons buter sur un certain nombre daspects ou de constructions que nous ne saurons pas comment traduire en programmation. En voici une petite liste : Nous ne savons pas encore traiter les tableaux. Un tableau deux dimensions avec le nom et le taux de change est vraisemblable. Lorsque les donnes ont t originellement acquises, comme le nom, le prnom et la date de naissance, nous pouvons nous attendre ce quelles soient stockes sur un support permanent, comme dans des chiers ou une base de donnes. Le format des donnes devrait tre tel quaussi bien la classe C++ que la classe Java puissent y accder. Pour lattribution ou lacquisition des donnes, ainsi que le traitement des erreurs, il nous faudra matriser la conception et la ralisation des mthodes selon la spcication des paramtres ncessaires. Ainsi, avant de passer ce dernier point, qui consiste avant tout tendre notre classe Personne, nous allons consacrer les prochains chapitres aux tableaux et aux entres-sorties an dacqurir les connaissances sufsantes pour pouvoir excuter ce travail proprement.

Diversion sur les structures C


Les structures (struct) sont un vieil hritage du langage C. Cependant, ds quun programmeur est confront danciennes techniques de programmation ou de code existant, il est oblig de les matriser et de les utiliser. Par consquent, nous nallons pas interdire ici lutilisation des structures C, mais plutt conseiller une approche plus oriente objet avec les classes C++. La programmation Windows constitue un exemple de cas o le dbutant ne devra surtout pas seffrayer devant lutilisation toutes les sauces de typedef (nouvelle dnition de type comme nous lavons vu au chapitre 2) et de structures. Pour montrer la diffrence entre une structure C et une classe, nous avons choisi lexemple dune structure de donnes contenant un texte que nous pourrions dessiner lintrieur dun rectangle dune certaine dimension :
// Structure.cpp #include <string> #include <iostream> using namespace std; struct Rectangle1 {

76

Apprendre Java et C++ en parallle

string texte; int hauteur; int largeur; }; class Rectangle2 { public: string texte; int hauteur; int largeur; }; int main(int argc, char* argv[]) { Rectangle1 mon_rec1; Rectangle2 mon_rec2; Rectangle1 *pmon_rec3; pmon_rec3 = new Rectangle1; mon_rec1.texte = mon_rec2.texte = pmon_rec3->texte = "Salut"; mon_rec1.hauteur = mon_rec2.hauteur = pmon_rec3->hauteur = 10; mon_rec1.largeur = mon_rec2.largeur = pmon_rec3->largeur = 100; cout cout cout cout cout cout << << << << << << mon_rec1.texte << ": " << mon_rec1.hauteur; " sur " << mon_rec1.largeur << endl; mon_rec2.texte << ": " << mon_rec2.hauteur; " sur " << mon_rec2.largeur << endl; pmon_rec3->texte << ": " << pmon_rec3->hauteur; " sur " << pmon_rec3->largeur << endl;

return 0; }

Nous commencerons par la classe Rectangle2 o le mot-cl public va permettre daccder aux attributs comme dans une structure C. Dans le paragraphe consacr la dnition des objets dune classe en C++, nous avons afrm quen programmation objet, il est dconseill de rendre les attributs publics : pourtant, cest malheureusement ce que nous faisons ici. Avec cette construction, il est alors possible daccder directement lattribut lorsque nous possdons une instance de cette classe. Cest le cas par exemple de mon_rec2.texte. Cest exactement la mme forme quune structure C o tous les attributs sont publics. Nous remarquons quil est aussi possible dutiliser loprateur new pour des structures, alors quun programmeur C utilisera plutt malloc(). Notons aussi laffectation des attributs au moyen de loprateur = rpt trois fois sur la mme ligne : cette construction est tout fait raisonnable. La directive struct en programmation C est essentielle car elle permet de structurer des donnes sous un mme chapeau. En programmation C++, tout comme en Java qui ne possde pas de structure, nous dnirons une classe avec les recommandations et les

On fait ses classes CHAPITRE 4

77

mthodes daccs appropries. Cependant, aucune raison ne nous empche dutiliser des structures comme attributs de classe pour rendre notre code plus soign.

Rsum
Nous sommes encore loin de matriser tous les concepts de classes et dobjets en Java et en C++. Avant dy revenir plus en dtail, il nous faut tudier dautres aspects plus gnraux, an de progresser par tapes dans lapprentissage de ces deux langages.

Exercices
1. Crer une classe nomme Dette qui va conserver une dette initiale en dbut danne, un taux dintrt applicable sur cette somme et un remboursement mensuel xe. 2. crire une mthode qui nous afche la dette restante aprs une anne sans prciser les centimes. Documenter la classe comme dcrit dans ce chapitre.

5
On enchane avec les tableaux
Dnir, utiliser et manipuler des tableaux (arrays) constitue lun des domaines essentiels de tout langage de programmation. Lorsque nous parlons de tableaux, nous pensons plus gnralement une prsentation multicolonnes ou une feuille de calcul. Cependant, le tableau le plus simple reste encore celui compos dune seule ligne de plusieurs lments. Ces derniers peuvent tre aussi bien composs de nombres que de toutes sortes dobjets de diffrents types. Un mot, une phrase, voire un texte entier sont aussi des tableaux, dans le sens quils peuvent reprsenter une zone continue en mmoire, dont nous connaissons ladresse de dpart et dont chaque caractre peut tre accessible individuellement par un index. Voici une petite liste dexemples de tableaux :
int tableau1[10]; long[] tableau2 = new long[10]; char tableau[8][8];

ainsi que ces deux chanes de caractres, respectivement en C++ et en Java :


char *message1 = "Mon premier message"; String message2 = new String("Mon premier message");

Certaines de ces constructions sont possibles dans les deux langages, tandis que dautres subiront quelques changements. En Java, la conception des tableaux a t optimalise par des contraintes et des amliorations apportes au langage, tandis quen C++, de nouvelles classes ont amlior la programmation par rapport au langage C ou C++ classique. Il existe aussi dautres formes de collections dobjets comme les vector et list, qui seront

80

Apprendre Java et C++ en parallle

traites plus loin, au chapitre 14. Ces dernires classes vont nous permettre de simplier la cration et laccs des tableaux de dimensions variables.

Tableaux dentiers
Deux aspects essentiels doivent tre abords lors de la cration de tableaux : leur dnition et leur initialisation. Prenons lexemple dun tableau contenant les cinq plus petits nombres premiers. Nous devrons dabord crer le tableau, avant de dposer une valeur dans chaque lment. Si nous crivons en C++:
// premier1.cpp #include <iostream> using namespace std; const int dim =5; int main() { int premier[dim]; for (int i = 0; i <= dim; i++) { cout << premier[i] << " "; } cout << endl; return 0; }

et son quivalent Java :


public class Premier1 { static final int dim = 5; public static void main(String[] args) { int[] premier = new int[dim]; for (int i = 0; i <= dim; i++) { System.out.print(premier[i] + " "); } System.out.println(""); } }

nous constatons que si les tableaux sont bien crs, ils ne sont cependant pas initialiss par le programme. Le rsultat du programme C++ sera :
39124524 2143490060 4198431 4276224 4276228 39124520

alors que celui de Java, bien diffrent, nous retourne une erreur :
0 0 0 0 0 java.lang.ArrayIndexOutOfBoundsException

On enchane avec les tableaux CHAPITRE 5

81

at Premier1.main(Compiled Code) Exception in thread "main"

Commenons par lerreur en Java. Linstruction premier[i] permet daccder llment i du tableau. Lexception ArrayIndexOutOfBoundsException nous indique un dpassement de lindex en dehors des limites : nous avons crit i <= dim, alors quil fallait crire i < dim. Comme lindex i commence 0, que nous allons jusqu 5 (sixime lment du tableau premier) et que Java est beaucoup mieux construit que C++, il nous reporte une erreur. Ce nest pas le cas en C++, o mme une criture dans ce sixime octet est possible et pourrait savrer catastrophique. Notons galement linitialisation 0 en Java, alors quen C++ nous avons vraiment nimporte quoi (39124524 ). En Java, mme si chaque lment nest pas assign une valeur par le programme, le new int[dim] initialisera chaque lment 0. Ceci est valable pour les types primitifs. En Java, la forme C++ sans le new, int premier[dim], nest pas possible et donnerait une erreur la compilation. Nous remarquons, dune manire gnrale, que le langage Java est plus prcis et nous empche de crer des erreurs qui peuvent tre extrmement difciles dceler. La forme en Java :
int[] premier = new int[dim];

avec les [] suivant directement le type, est la forme usuellement pratique et correspondant en C++ :
int premier[dim];

La forme :
int premier[] = new int[dim];

est tout aussi correcte en Java, mais sans doute moins parlante. Le code Java :
static final int dim = 5;

reprsente lquivalent en C++ de :


const int dim = 5;

qui est dans notre cas une constante globale, car dnie avant le main(). En Java, ce nest pas possible, et une variable constante doit tre une variable de classe. final signie que la variable dim ne peut tre modie ; static indique que toutes les instances de la classe Premier1 utiliseront la mme variable. Si nous voulions prsent initialiser le tableau avec nos nombres premiers, les codes C++ et Java se prsenteraient ainsi :
// premier2.cpp #include <iostream> using namespace std; const int dim =5;

82

Apprendre Java et C++ en parallle

int main() { int premier[5] = {1, 2, 3, 5, 7}; for (int i = 0; i < dim; i++) { cout << premier[i] << " "; } cout << endl; return 0; } public class Premier2 { public static void main(String[] args) { int[] premier = {1, 2, 3, 5, 7 }; for (int i = 0; i < premier.length; i++) { System.out.print(premier[i] + " "); } System.out.println(""); } }

Et cela avec le mme rsultat :


1 2 3 5 7

En C++, les deux formes int premier[5] et int premier[ ] sont possibles. Ce nest pas le cas en Java, o uniquement le [ ] est permis. En Java, cette forme dinitialisation sans new est accepte par le compilateur. Nous retrouvons en Java le length que nous avions dj rencontr au chapitre 1. Il nous donne la dimension du tableau.

Copie de tableau dentiers en Java


Lorsque nous copions des tableaux, opration qui se rencontre souvent, nous devons toujours vrier que notre code est correct et ne travaille pas en fait sur le tableau dorigine. En Java, si nous crivons :
public class Premier3 { public static void main(String[] args) { int[] premier1 = {1, 2, 3, 5, 7 }; int[] premier2; premier2 = premier1; premier2[3] = 11; for (int i = 0; i < premier1.length; i++) { System.out.print(premier1[i] + " "); } System.out.println(""); } }

On enchane avec les tableaux CHAPITRE 5

83

nous aurons le rsultat :


1 2 3 11 7

Nous constatons que le tableau premier2 est en fait le tableau premier1. Linstruction premier2[3] permet de vrier que nous accdons bien au tableau premier1. Si nous voulions avoir deux tableaux distincts initialiss avec le mme contenu, il nous faudrait procder comme suit :
public class Premier4 { public static void main(String[] args) { int[] premier1 = {1, 2, 3, 5, 7 }; int[] premier2 = new int[5]; for (int i = 0; i < premier1.length; i++) { premier2[i] = premier1[i]; } } }

Ce dernier code est applicable en C++ pour la copie de tableaux, condition davoir pour la dclaration de premier2 :
int premier2[5];

Tableau dynamique en C++


Cette partie tant dun niveau de complexit plus lev, il est tout fait envisageable de passer au sujet suivant et dy revenir une autre occasion. Il est possible et parfois ncessaire de crer des tableaux dynamiques en C++. Voici une manire de procder :
// premier3.cpp #include <iostream> using namespace std; const int dim = 5; int main() { int premier[5] = {1, 2, 3, 5, 7}; int **ppremier1; int **ppremier2; int i = 0; ppremier1 = (int **)new int[dim]; ppremier2 = ppremier1;

84

Apprendre Java et C++ en parallle

for (i = 0; i < dim; i++) { ppremier1[i] = new int(0); *ppremier1[i] = premier[i]; } for (i = 0; i < dim; i++) { cout << *ppremier2[i] << " "; delete ppremier2[i]; } cout << endl; delete[] ppremier1; return 0; }

Ce petit exercice nous montre comment copier un tableau prsent sur la pile (stack) dans un tableau allou dynamiquement. La premire remarque qui simpose concerne la variable ppremier2, qui est l uniquement pour montrer comment acqurir ladresse dun tableau existant. ppremier2 va simplement pointer sur le tableau dynamique ppremier1. Le terme dynamique est utilis pour indiquer que le tableau est allou avec loprateur new ; autrement dit, le tableau sera prsent dans le tas (heap) et devra tre libr lorsque nous ne lutiliserons plus. Nous savons dj quun int *nombre reprsente un pointeur un nombre. Ici, la prsence dun double ** indique un pointeur un tableau. La premire opration consiste allouer de la mmoire ncessaire pour contenir cinq pointeurs. Cela se fait au moyen du new int[dim]. Le transtypage (int **) a t ncessaire avec ce compilateur, car le programme, qui pourtant acceptait de compiler, refusait dexcuter cette instruction alors que dautres compilateurs produisaient du code parfaitement excutable ! La deuxime opration consiste allouer un entier, individuellement, pour chaque composant du tableau :
ppremier1[i] = new int(0);

Nous protons de la boucle pour copier chaque lment :


*ppremier1[i] = premier[i];

et remarquer la notation avec le pointeur (*) et le nom de la variable commenant par un premier p. Cest bien un pointeur au ie lment dans le tableau. Effacer les ressources doit dabord se faire sur les lments, et ensuite sur le tableau de pointeurs. Pour ce dernier, la prsence du [] suivant le delete permet dindiquer que nous avons un tableau effacer et non pas un seul lment.

On enchane avec les tableaux CHAPITRE 5

85

Tableaux multidimensionnels
Si nous devons programmer le jeu dchecs ou le jeu dOthello, dont nous verrons les rgles et les dtails au chapitre 22, il nous faut travailler avec un tableau de 8 par 8. Par exemple :
int jeu[8][8];

Le coin suprieur gauche de lchiquier sera rfrenc par jeu[0][0] et linfrieur droit par jeu[7][7]. Durant la conception du jeu, nous pourrons nous rendre compte que nous devons continuellement tester les bords pour savoir si un dplacement est possible. Il est donc avantageux dajouter une case supplmentaire de chaque ct. Au jeu dchecs, si nous travaillons avec un tableau de dplacement possible pour le cavalier (2,1), il faut donc vraisemblablement tendre le tableau 12 par 12. Nous aurons donc :
int othello[10][10]; int echec[12][12];

Avec lexemple qui suit, crit en Java et en C++, nous allons montrer comment initialiser un tableau pour le jeu dOthello avec les quatre pions dorigine et les conditions suivantes : 1 : bord extrieur ; 0 : position libre ; 1 : pion noir ; 2 : pion blanc.

Le jeu dOthello en Java


public class Othello1 { static final int dim = 10; int[][] othello = new int[dim][dim]; public Othello1() { int i = 0; // position horizontale int j = 0; // position verticale for (i = 0; i < dim; i++) { // bord -1 othello[i][0] = -1; othello[i][dim-1] = -1; othello[0][i] = -1; othello[dim-1][i] = -1; } for (j = 1; j < dim-1; j++) { // intrieur vide for (i = 1; i < dim-1; i++) { othello[i][j] = 0; } }

86

Apprendre Java et C++ en parallle

othello[4][5] othello[5][4] othello[4][4] othello[5][5] }

= = = =

1; 1; 2; 2;

// // // //

blanc blanc noir noir

public void test1() { int i = 0; // position horizontale int j = 0; // position verticale for (j = 0; j < dim; j++) { for (i = 0; i < dim; i++) { if (othello[i][j] >= 0) System.out.print(" "); System.out.print(othello[i][j]); } System.out.println(); } } public static void main(String[] args) { Othello1 monjeu = new Othello1(); monjeu.test1(); } }

Le jeu dOthello en C++


// othello1.cpp #include <iostream> using namespace std; class Othello1 { private: const static int dim = 10; // 8 x 8 plus les bords int othello[dim][dim]; // le jeu public: Othello1(); void un_test(); }; Othello1::Othello1() { int i = 0; // position horizontale int j = 0; // position verticale for (i = 0; i < dim; i++) { // bord -1 othello[i][0] = -1;

On enchane avec les tableaux CHAPITRE 5

87

othello[i][dim-1] = -1; othello[0][i] = -1; othello[dim-1][i] = -1; } for (j = 1; j < dim-1; j++) { // intrieur vide for (i = 1; i < dim-1; i++) { othello[i][j] = 0; } } othello[4][5] othello[5][4] othello[4][4] othello[5][5] } void Othello1::un_test() { int i = 0; // position horizontale int j = 0; // position verticale for (j = 0; j < dim; j++) { for (i = 0; i < dim; i++) { if (othello[i][j] >= 0) cout << " "; cout << othello[i][j] << " "; } cout << endl; } } int main() { Othello1 lejeu; lejeu.un_test(); } = = = = 1; 1; 2; 2; // // // // blanc blanc noir noir

Le rsultat est identique pour les deux exemples :


-1-1-1-1-1-1-1-1-1-1 -1 0 0 0 0 0 0 0 0-1 -1 0 0 0 0 0 0 0 0-1 -1 0 0 0 0 0 0 0 0-1 -1 0 0 0 2 1 0 0 0-1 -1 0 0 0 1 2 0 0 0-1 -1 0 0 0 0 0 0 0 0-1 -1 0 0 0 0 0 0 0 0-1 -1 0 0 0 0 0 0 0 0-1 -1-1-1-1-1-1-1-1-1-1

Devant ce rsultat, cela nous donnera certainement lenvie de programmer ce jeu et daller un peu plus loin. Cest ce que nous ferons plusieurs occasions dans cet ouvrage,

88

Apprendre Java et C++ en parallle

et plus particulirement au chapitre 20. En effet, Othello est un jeu tout fait abordable pour un dbutant et un excellent exercice de conception de programme. Sa seule vraie difcult est datteindre un niveau de jeu acceptable, ce qui est loin dtre vident. Il y a trs peu de choses dire car le code est sufsamment explicite. La manire de dnir un tableau deux dimensions est similaire au cas dun tableau une seule dimension, le [ ] devenant simplement [ ][ ]. Lors de linitialisation du tableau, il faut travailler avec deux index, lun sur la position verticale, lautre sur lhorizontale. En analysant le code, nous pourrions constater certaines redondances et remarquer que certaines cases sont assignes deux fois, mais cela au prot dun code plus simple et plus lisible. La manire de procder pour un programmeur C peut paratre saugrenue. Nous voulions montrer comment utiliser un tableau multidimensionnel, et nous commenons par crire une classe. En fait, il aurait t tout fait possible dcrire cette bauche de programme en C classique, avec un tableau global. En Java, nous navons pas le choix, et, en n de compte, la solution C++ dcrire une classe est lgante et oriente objet. Est-ce vraiment plus difcile ? Nous ne le pensons pas, surtout si nous avons lintention dcrire le jeu complet par la suite.

Le cavalier du jeu dchecs


Mais revenons au jeu dchecs, avec notre cavalier qui peut se dplacer de 2 et 1 dans huit cases diffrentes autour de sa position actuelle. Le code suivant nous montre comment initialiser un tableau avec ces huit paires de valeurs.
int cavalier[8][2] = { // C++ {1, 2}, {2, 1}, {2, -1}, {1, -2}, {-1, -2}, {-2, -1}, {-2, 1}, {-1, 2}, }; int cavalier[ ][ ] = { // Java {1, 2}, {2, 1}, {2, -1}, {1, -2}, {-1, -2}, {-2, -1}, {-2, 1}, {-1, 2}, };

En Java, si nous le dsirons, il est possible dobtenir la dimension de chaque composant de cette manire :
cavalier.length; cavalier[0].length; //rsultat : 8 //rsultat : 2

Chanes de caractres en C
Une chane de caractres en C et C++ est en fait un tableau de caractres. Lorsque nous avons besoin dun texte ou dun message, nous pouvons simplement utiliser un pointeur, dont nous voyons la forme ici :

On enchane avec les tableaux CHAPITRE 5

89

// message.cpp #include <iostream> #include <cstring> using namespace std; int main() { char *message1 = "Mon message"; cout << message1 << endl; int longueur = strlen(message1); char message2[longueur + 1]; strcpy(message2, message1); message2[4] = 'M'; cout << message2 << endl; for (int i = 0; i < longueur; i++) { cout << message2[i]; } cout << endl; char *message3; message3 = new char[longueur + 2]; strcpy(message3, message1); message3[longueur] = 'r'; message3[longueur + 1] = 0; cout << message3 << endl; delete[] message3; return 0; }

Et le rsultat correspondant :
Mon Mon Mon Mon message Message Message messager

Ce type de code, plus proche du C que du C++, se rencontre encore trs souvent de nos jours. Ce nest certainement pas la meilleure manire de procder, mais il est cependant essentiel de matriser ces diffrentes constructions. La variable message1 est un pointeur un texte xe en mmoire que nous pouvons utiliser dans diffrentes fonctions C ou C++. Le pointeur reste et restera la mode en C++, en particulier comme paramtre pour le cout. Les deux cout suivants sont quivalents, bien que lutilisation de la variable bonjour ne soit pratique que si elle est rutilise :

90

Apprendre Java et C++ en parallle

char *bonjour = "Bonjour"; cout << bonjour; cout << "Bonjour";

Les fonctions C strlen() et strcpy() sont encore beaucoup utilises, mais il faut rester trs prudent. Si nous crivons :
char *message1 = "1234"; char message2[3]; strcpy(message2, message1);

le message1 est copi dans un tampon trop court. message2 doit avoir au moins une longueur de 5, cest--dire 4 plus un zro qui dtermine la n du texte. Dans le programme prcdent, nous voyons aussi comment accder des caractres individuels dans la chane pour les modier ou pour sortir les diffrents textes sur la console. La manire dutiliser message2 comme pointeur est en fait reconnue par le compilateur, et les deux instructions suivantes sont quivalentes :
cout << message2 << endl; cout << &message2[0] << endl;

elles nous fournissent ladresse du premier caractre de la chane. cout sarrtera au moment o le caractre 0 sera trouv dans la chane. Si nous ajoutions une instruction comme celle-ci avant le cout :
message2[1] = 0; cout << message2 << endl;

seule la lettre initiale 'M' apparatrait ! Pour terminer, nous avons ajout une allocation dynamique, avec le new, dun message qui sera effac de la mmoire, avec le delete[], lorsquil ne sera plus utilis. La longueur du tampon de message3 devra tre sufsante pour contenir un caractre supplmentaire, car nous y ajouterons la lettre 'r' du messager. Ce dernier caractre vient se placer sur le 0 terminant la copie prcdente avec le strcpy(). Enn, il faut remettre un 0 lindex suivant. Le chier den-tte cstring contient la dnition des fonctions C strcpy() et strlen(), ainsi que bien dautres fonctions mises disposition, comme ajouter des chanes les unes derrire les autres ou rechercher des squences de caractres. Le problme, cependant, reste le mme : le risque de dnir un tampon trop court ou de travailler avec un index en dehors des limites !

Les String de Java


Il ny a pas de chanes de caractres en Java. Cependant, les deux instructions suivantes sont identiques et correctes. La premire forme nest accepte que pour les String, et la deuxime correspond la forme standard de la cration dobjet. String est bien une classe et non un type primitif comme int, char ou long.

On enchane avec les tableaux CHAPITRE 5

91

String message1 = "Mon message"; String message1 = new String("Mon message");

Le programme C++ que nous venons de voir, message.cpp, ne peut tre transcrit directement en Java. En effet, il nest pas possible daccder un caractre avec son index, tel que message2[i]. Nous allons en analyser les dtails :
public class Message1 { public static void main(String[] args) { String message1 = new String("Mon message"); System.out.println(message1); System.out.println(message1.charAt(4)); String message2 = message1.substring(0, 4) + 'M' + message1.substring(5, message1.length()) + 'r'; System.out.println(message2); StringBuffer messageb3 = new StringBuffer(message1); messageb3.setCharAt(4, 'M'); messageb3.append('r'); System.out.println(messageb3); } }

La mthode charAt(index) de la classe String permet daccder un caractre individuellement. De plus, il ny a pas de mthodes pour modier quoi que ce soit lintrieur dun String en Java. Les objets de cette classe sont immuables, comme un const string en C++. De plus, la classe String va vrier la position de lindex. Si nous avions malencontreusement crit ceci :
System.out.println(message1.charAt(message1.length()));

nous aurions reu alors un :


java.lang.StringIndexOutOfBoundsException: String index out of range: 11 at java.lang.String.charAt(Compiled Code) at Message1.main(Message1.java:5)

qui nous indique que nous essayons daccder un lment en dehors des limites du String (StringIndexOutOfBoundsException). Cest propre et beaucoup moins dangereux quen C++. Il faut noter que nous accdons ici la position o se situait notre 0 terminant une chane de caractres en C/C++. En Java, ce nest pas ncessaire, car il possde un autre mcanisme. Si nous voulions changer le m minuscule en M majuscule, il nous faudrait, par exemple, recomposer le String en utilisant la mthode substring(index1, index2). Cette dernire est trs particulire : index1 est la position de dpart, alors quindex2 est la position dans la chane du premier caractre que nous ne voulons plus copier ! En revanche, la classe StringBuffer est modiable (mutable). Non seulement nous pouvons modier son contenu (ici, avec setCharAt(4, 'M'), qui va nous positionner notre fameux

92

Apprendre Java et C++ en parallle

M majuscule), mais nous pouvons aussi tendre notre chane en lui ajoutant des caractres. La mmoire sera ralloue automatiquement, sans devoir passer par du code aussi complexe que celui-ci en C++ :
char *texte1 = "Un deux trois "; char *texte2 = "quatre cinq six"; char texte1_2[strlen(texte1) + strlen(texte2) + 1]; strcpy(texte1_2, texte1); strcpy(texte1_2 + strlen(texte1), texte2);

o nous utilisons des fonctions C pour recalculer lespace ncessaire. La dernire instruction est assez particulire. Le texte2 est copi dans le nouveau tableau texte1_2 depuis la position du 0 gnr par le strcpy() prcdent. Linstruction :
texte1_2 + strlen(texte1)

est simplement le calcul de la position dune adresse mmoire. Nous verrons, au chapitre 9, que nous avons dautres outils disposition en C++ pour former et combiner des chanes de caractres variables. Et ceci en plus de la classe string, que nous allons tudier prsent.

Les string du C++, un nouvel atout


La classe string est apparue rcemment en C++. Cest lune des classes essentielles du Standard C++. Pendant des annes, les programmeurs ont utilis des tableaux de caractres, comme nous venons de le voir, ont cr leurs propres classes String ou ont utilis des produits commerciaux. Lutilisation de la classe string va sans doute se gnraliser, et cest avant tout au bnce de la simplicit et de la rutilisation du code. Nous allons reprendre prsent une partie des fonctionnalits que nous venons danalyser au travers de ce code :
// strings.cpp #include <iostream> #include <string> using namespace std; int main() { string message1 = "Mon message"; cout << "1: " << message1 << endl; cout << "2: " << message1[4] << endl; cout << "3: " << message1[100] << endl; //indfini string message2; message2 = message1; message2[4] = 'M'; message2.append("r");

On enchane avec les tableaux CHAPITRE 5

93

cout << "4: " << message1 << endl; cout << "5: " << message2 << endl; string *pmessage1; string *pmessage2; pmessage1 = new string("Mon message"); pmessage2 = pmessage1; (*pmessage2)[5] = 'a'; cout << "6: " << *pmessage2 << endl; delete pmessage1; return 0; }

Et le rsultat donne :
1: 2: 3: 4: 5: 6: Mon m ? Mon Mon Mon message

message Messager massage

Le constructeur du string accepte un pointeur un tableau de caractres, et loprateur [ ] permet daccder des caractres individuels. Nous voyons quil est toujours possible dutiliser un index (notre 100) totalement en dehors des limites, et que le rsultat obtenu reste indni (3: ?), sans autres mcanismes de dtection derreur. Lanalyse de linstruction :
message2 = message1;

est essentielle. Le contenu du message1 est copi dans le message2. Les modications apportes ensuite message2, cest--dire la lettre M lindex 4 et lajout de la lettre r en n de string avec la rallocation automatique de la mmoire, ne touchent en rien au message1. En revanche, si nous crivions en Java :
StringBuffer message1 = new StringBuffer("Mon message"); StringBuffer message2; message2 = message1; message2.setCharAt(4, 'M'); System.out.println(message1); // rsultat : Mon Message

message1 et message2 seraient les mmes objets : ils utilisent la mme rfrence. Cependant,

avec le code suivant, nous procdons bien une copie :


StringBuffer message1 = new StringBuffer("Mon message"); StringBuffer message2 = new StringBuffer(message1.toString()); message2.setCharAt(4, 'M'); System.out.println(message1); // rsultat : Mon message

Le message1 dorigine ne sera pas modi. Le toString() qui retourne un String est ncessaire, car le constructeur de StringBuffer naccepte pas de StringBuffer comme paramtre. Ensuite, il faut revenir au code C++ ci-dessus (strings.cpp), o nous avons utilis un string dynamique pmessage1 et allou avec new un nouvel objet. pmessage2 est non pas un deuxime

94

Apprendre Java et C++ en parallle

objet, mais une adresse qui est assigne pmessage1. En modiant le contenu de pmessage2, nous touchons en fait au string dynamique pmessage1. Il existe donc une similitude avec le code Java ci-dessus, mais il est trs important de matriser les diffrences. Linstruction :
cout << *pmessage2 << endl;

est bien correcte. Si nous omettons le caractre *, nous recevrons ladresse mmoire en hexadcimal o se trouve lobjet *pmessage1 de la classe string ! Cela pourrait tre utilis des ns de test, pour vrier que nous travaillons bien sur la bonne zone mmoire. Enn, nous noterons encore une fois dans le code qui suit :
// strings2.cpp #include <iostream> #include <string> using namespace std; int main() { string *pmessage1; string *pmessage2; pmessage1 = new string("Mon premier message"); pmessage2 = pmessage1; pmessage1 = new string("Mon second message"); cout << "pmessage1: " << *pmessage1 << endl; //Mon second message cout << "pmessage2: " << *pmessage2 << endl; //Mon premier message delete pmessage1; delete pmessage2; return 0; }

quil faut rester trs prudent an dviter des fuites de mmoire. Bien que ce code soit un peu farfelu, il est tout fait correct. Nous avons deux new et deux delete. Est-ce vraiment juste ? Le second new se fait aussi sur la variable pmessage1, mais sa premire valeur a t sauve dans pmessage2. Le delete pmessage2; va donc bien nous effacer la ressource du premier message. Ce code est aussi un avertissement de ce quun programmeur C++ ne devrait jamais faire. Java ne nous le permettrait pas, et cest tout son honneur.

Les mthodes des classes String (Java) et string (C++)


Lors du traitement des chanes de caractres en Java ou en C++, le programmeur doit avant tout consulter la documentation (API) pour rechercher une fonction, cest--dire une mthode, dont il a besoin. La qualit dun programmeur est souvent en relation directe avec sa facilit de naviguer dans la documentation pour y dcouvrir ce quil recherche. Une autre possibilit, qui se rvle aussi trs efcace, est de collectionner de nombreux

On enchane avec les tableaux CHAPITRE 5

95

exemples dans une hirarchie simple o des outils de recherches composes, comme egrep sous Linux, pourraient beaucoup apporter (voir n de lannexe B). Certaines mthodes seront videmment disponibles dans un langage et pas dans lautre. Il faudra souvent faire un choix : soit privilgier la performance mais ce au prix de longues heures de programmation, soit prfrer une programmation rapide au dtriment de la performance. Nous allons prsenter ici un petit aperu de quelques mthodes disposition, puis nous en donnerons quelques variantes en exercices. Nous commencerons par du code en Java :
public class TestString { public static void main(String[] args) { String texte1 = " abcd1234abcd5678abcd System.out.println("[" + texte1 + "]"); texte1 = texte1.trim(); System.out.println("[" + texte1 + "]"); int index = texte1.indexOf("bcd", texte1.indexOf("bcd") + 3); String texte2 = texte1.substring(0, index) + texte1.substring(index + 3).toUpperCase(); System.out.println("[" + texte2 + "]"); } }

";

qui fournit comme rsultat :


[ abcd1234abcd5678abcd [abcd1234abcd5678abcd] [abcd1234a5678ABCD] ]

Le trim() nous efface les espaces au dbut et la n, ce qui peut savrer pratique pour un traitement de texte. Les deux indexOf() vont nous retourner lindex du dpart du deuxime "bcd". Enn, nous allons extraire, avec substring(), tout ce qui vient avant le deuxime "bcd" et ensuite y ajouter tout ce qui suit ce mme "bcd", mais en le convertissant en majuscules avec la mthode toUpperCase(). Nous y trouvons donc notre bonheur, tout en nous demandant quoi cela peut bien servir ! Nous allons prsent passer la classe string du C++ et montrer aussi quelques possibilits dutilisation de ces mthodes :
// teststring.cpp #include <iostream> #include <string> using namespace std; int main() { string aref = "<a href=\"mailto:Jean-Bernard@Boichat.ch\">";

96

Apprendre Java et C++ en parallle

for (int i = 0; i < aref.length(); i++) { if ((aref[i] >= 'A') && (aref[i] <= 'Z')) { aref[i] += 32; } } int index1 = aref.find("href=\""); if (index1 >= 0) { index1 += 6; int index2 = aref.find("\"", index1); if (index2 >= 0) { string = aref.substr(index1, index2 - index1); if (urlstr.find("mailto:") == 0) { string emaila = urlstr.substr(7); int indexa = emaila.find("@"); if (indexa <= 0) { cout << "Adresse email invalide: " << emaila << endl; } else { cout << "Adresse email: " << emaila << endl; } } } } }

Et le rsultat produit :
Adresse email: jean-bernard@boichat.ch

qui correspond lextraction dune adresse e-mail dans un morceau (un trs petit morceau) dune page Web. Dans notre adresse URL, <a href=\"mailto:Jean-Bernard@Boichat.ch\">, nous remarquons les codes dchappement \" qui sont ncessaires pour affecter le string dans notre code. Nous avons bien un seul caractre " qui va dlimiter ce que nous recherchons, savoir une adresse URL. La premire boucle for() est une conversion de toutes les majuscules en minuscules. Pour du texte, en particulier dans une page Web, cette conversion ne serait pas trs pertinente, mais ici il est raisonnable de ne garder que les minuscules pour ces adresses URL, dont les majuscules sont converties en minuscules de toute manire. Cette manire de procder, sans utiliser de mthodes de classe, est tout fait envisageable lorsque aucune mthode correspondante nexiste ou lorsque nous voulons gagner en efcacit. Dans la boucle for(), nous pourrions aussi ajouter dautres fonctionnalits telles que celle de rechercher un caractre particulier ou un cas derreur. Nous verrons, dans le chapitre 15 sur les performances, que tout appel de fonction est une perte sensible en performance. Notons aussi que nous utilisons loprateur [] de la classe string, qui se rvle en fait similaire la mthode charAt() en Java. Nous reviendrons sur ces dtails dans les chapitres suivants.

On enchane avec les tableaux CHAPITRE 5

97

Le reste du code correspond plutt une prsentation de quelques mthodes de la classe string qu un code propre et complet. De nombreux cas derreurs sont traits, mais il reste encore du travail. La premire recherche :
int index1 = aref.find("href=\"");

nous donne lindex du href=" dans le string. Dans un document HTML, il y a dautres codes, mais comme nous cherchons dabord une adresse e-mail, cela semble justi. La deuxime recherche (aref.find("\"", index1)) possde cette fois un second argument, lindex1, an de rechercher la position du dernier " partir de la position index1 += 6. Nous utilisons la mthode substr() avec un ou deux paramtres. Nous nous souvenons de la mthode Java substring(), dont le deuxime paramtre tait un index. Ici, ce nest pas une position, mais une longueur. Le string urlstr est un tat intermdiaire. Il contient ici mailto:jean-bernard@boichat.ch. Nous aurions pu avoir, par exemple, http://www.w3.org/ et transformer notre code en un traitement complet dune page Web. Lune des premires tches serait dliminer les valeurs xes comme 6, 7, href ou mailto par des constantes ou des dictionnaires. Enn, nous remarquerons que ce travail, trs concret cette fois-ci, nest en fait pas bien diffrent de notre exemple Java, TestString, qui nous paraissait totalement abstrait et inutile.

Rsum
Nous progressons, nous le sentons, mais beaucoup reste faire. Nos chanes de caractres sont encore malheureusement beaucoup trop statiques. Au chapitre 14, nous reviendrons sur le traitement des listes dynamiques (list et vector, par exemple) qui sont essentielles pour llaboration dapplications contenant des objets persistants et modiables.

Exercices
1. Dnir un tableau qui nous permet, dans le jeu dOthello, didentier les huit directions possibles partir dune position dtermine. 2. Pour le jeu dOthello, calculer le nombre de positions prises par chaque couleur dans le tableau entier. Ce nombre correspond au score. 3. crire, dune manire simple, le jeu de la vie avec un petit tableau et quelques exemples pour vrier la logique. Nous pouvons trouver une description du jeu de la vie crite par J.Conway dans Scientic American doctobre 1970. Les rgles sont les suivantes : Une cellule continue vivre si elle a deux ou trois cellules voisines. Une cellule meurt disolement si elle a moins de deux voisines. Une cellule meurt dtouffement si elle a plus de trois voisines. Une cellule nat dans une case vide si trois cases voisines exactement sont occupes.

98

Apprendre Java et C++ en parallle

4. En Java, utiliser les mthodes suivantes de la classe String : boolean startsWith(String a) : commence avec le String a. boolean endsWith(String b) : termine avec le String b. int compareTo(String c) : comparaison avec le String c. int compareToIgnoreCase(String d) : comparaison avec le String d indpendamment des majuscules ou des minuscules. String toLowerCase(String e) : convertit le String e en minuscules. 5. En C++, utiliser les mthodes suivantes de la classe string : insert(int pos1, string a) : insre le string a aprs la position pos1. insert(int pos2, int nombre, char car) : insre le caractre car le nombre de fois la position pos2. insert(int pos3, string b, int pos4, int dim1) : insre la position pos3 les dim1 caractres du string b commenant la position pos4. erase(int pos5, int dim2) : efface dim2 caractres depuis la position pos5. replace(int pos6, int dim3, string c, int pos7) : remplace depuis la position pos6 et pour dim3 caractres par le contenu du string c commenant la position pos7.

6
De la mthode dans nos fonctions
Fonctions et mthodes
Durant la conception dun programme, nous sommes tous amens regrouper lintrieur dun fragment de code un certain nombre dinstructions dont nous avons identi quelles se rpteront rgulirement. Suivant le langage, nous les appelons procdures, fonctions ou mthodes. Ces dernires peuvent recevoir ou non des paramtres et produire un rsultat sous diffrentes formes. Dans les deux listes qui suivent, nous allons rsumer les diffrentes possibilits que nous retrouvons gnralement dans ce que nous appelons les entres et les sorties. Nous commencerons par les donnes initiales reues ou utilises par une de ces fonctions : arguments passs la fonction ; paramtres stocks quelque part en mmoire, comme des attributs de classe ; paramtres disponibles ou accessibles lextrieur du programme comme le disque, un serveur de base de donnes SQL, le clavier ou encore Internet. Quant au rsultat, nous pourrons le retrouver aussi sous diffrentes formes. Sil sagit dun calcul arithmtique lmentaire, on simagine quun simple retour de fonction peut se rvler sufsant. Cependant, la fonction elle-mme peut produire une multitude dactions et de rsultats directement ou indirectement visibles, par exemple : un retour de la fonction (une seule valeur ou objet en Java et C++) ; un retour par des arguments passs la fonction ;

100

Apprendre Java et C++ en parallle

un rsultat stock quelque part en mmoire, comme dans des attributs dobjet ou de classe ; une action lextrieur comme lcran, un chier sur le disque, un serveur SQL, un quipement hardware ou encore Internet. Nous verrons, au travers de quelques exemples, ces diffrents cas et la manire de les programmer correctement. Il est essentiel de dcrire ces dtails dans la documentation de lAPI.
API : Application Programming Interface LAPI est le terme gnralement utilis pour dnir les interfaces dont le programmeur dispose pour construire ses applications. Par exemple, toutes les classes dlivres avec le JDK (Java Development Kit) de Sun Microsystems font partie de lAPI de Java, et ceci toujours pour une version dtermine. Lorsque nous parlons dAPI, nous entendons par-l la dnition exacte des fonctions, des classes et des mthodes. La documentation peut tre un simple chier den-tte, un document ou, encore mieux, un chier HTML ou un chier daide sous Windows.

Il arrive aussi que lutilisation et la transformation de variables temporaires prives puissent affecter le comportement ultrieur dautres fonctions. Il faut tre trs prudent lors de lallocation des ressources dans un constructeur ou dans une mthode. Nous introduisons invitablement des dpendances qui devront plus tard tre considres. Si nous avons une mthode pour lire une ligne de texte depuis un chier, il aura bien fallu appeler une premire fonction pour ouvrir ce chier. Mais que se passerait-il si cette dernire navait jamais t appele ? Une procdure, qui est un terme plus gnralement utilis en langage Pascal, est appele fonction en C et mthode en C++ et en Java. En C, elle ne sera pas attache une classe ou un objet. Cependant, il ny a pas de grandes diffrences de syntaxe entre une fonction C et une mthode C++. Nous le verrons plus explicitement au travers de quelques exemples pratiques. En Java, toute mthode doit tre dnie imprativement lintrieur du code dune classe. Un programmeur C++ studieux pourrait trs bien dnir toutes ces fonctions C au dpart comme mthodes de classe C++. Nous commencerons par un exemple de fonction C trs simple et ltendrons avec des mthodes C++ et Java.

splice() de Perl
Nous avons choisi ici la commande splice() du langage Perl comme premier exercice. Nous la dvelopperons en C, C++ et Java. Perl5 est la dernire version dun langage qui fut originellement conu pour lcriture de scripts et doutils sous Linux. Il est devenu plus rcemment lun des langages essentiels pour les scripts CGI (Common Gateway Interface) des serveurs Web. Nous trouverons dans lannexe G un certain nombre de rfrences sur Perl. An de montrer les effets de la commande splice(), nous allons prsenter ce morceau de code Perl :

De la mthode dans nos fonctions CHAPITRE 6

101

#!/usr/local/bin/perl my @str = ("H", "e", "l", "l", "o", "1", "2", "3", "4", "5"); print "@str\n"; splice(@str, 3, 3); print "@str\n"; splice(@str, 3, 2); print "@str\n"; splice(@str, 3, 1); print "@str\n";

et son rsultat :
H H H H e e e e l l l l l o 1 2 3 4 5 2 3 4 5 4 5 5

splice() reoit comme premier argument une chane de caractres. La variable @str contiendra une chane quivalente un string en C++. lorigine, elle contiendra "Hello12345". splice() va effacer un certain nombre de caractres dnis par le dernier

argument et partir dune position dtermine par le second argument. Si lindex est en dehors des limites, la chane ne sera pas touche, et si le nombre de caractres effacer dpasse le maximum possible, le reste de la chane sera tout de mme coup.

splice() comme fonction C


Retour par arguments
Dans le code qui suit, nous avons choisi de passer un pointeur une chane de caractres C classique. Le contenu de la chane sera modi, comme cest dailleurs le cas en Perl, mais aucune rallocation de mmoire ne sera apporte. Ceci aurait t cependant possible puisque, en principe, au moins un caractre va disparatre. Voici donc notre premire version :
void splice1(char *pstr, int pos, int longueur) { int dimension = 0; //longueur de la chane while (pstr[dimension] != 0) dimension++; if ((pos < 0) || (longueur <= 0) || (pos >= dimension)) return; int i = pos; for (i = pos; i < dimension - longueur; i++) { pstr[i] = pstr[i + longueur]; } pstr[i] = 0; }

Le premier calcul est de dterminer la longueur de la chane, car elle sera utilise plus dune reprise.

102

Apprendre Java et C++ en parallle

Linstruction lintrieur while() est en fait quivalente un strlen() de la bibliothque C qui nous calcule la longueur dune chane de caractres. La premire instruction if dtermine tous les cas derreur. Ici, aucune indication derreur ne sera reporte, et la chane sera retourne intacte. Dans la boucle for() nous dplaons un ou plusieurs caractres, ce qui correspond notre fonction Perl splice(). Il faut absolument vrier que nous ne dpassons pas la limite droite du tableau. Le dernier str[i] est sufsant pour marquer la n de la chane dans le tampon str puisque le 0 indiquera la n de la nouvelle chane de caractres splice !

Accs des donnes rptitives


Si une fonction utilise plus dune fois une donne qui doit tre recalcule, il est avantageux de la conserver localement dans une variable. Certains programmeurs afrmeront, avec raison dailleurs, quune variable isole et inutilise peut tre intressante pour vrier le code pendant la phase de test. Il est alors possible de sarrter aprs lexcution de linstruction avec un dbogueur.

Retour de fonction
Avant de passer la phase de test, nous allons directement proposer une autre version de splice() qui cette fois-ci ne va pas toucher la chane dorigine, mais qui va en crer une nouvelle. La fonction va donc nous retourner le rsultat, qui sera constitu dun pointeur sur une chane de caractres nouvellement alloue. Lapplication utilisant cette fonction devra tester le rsultat an deffacer cette nouvelle ressource ou de choisir laction ncessaire en cas derreur. Voici donc cette nouvelle version :
char *splice2(const char *pstr, int pos, int longueur) { int dimension = 0; //longueur de la chane while (pstr[dimension] != 0) dimension++; if ((pos < 0) || (longueur <= 0) || (pos >= dimension)) return 0; int max = dimension - longueur; if ((pos + longueur) > dimension) max = pos; int i = 0; int j = 0; char *pnew_str = new char[max + 1]; for (; i < max; i++) { if (i == pos) j += longueur; pnew_str[i] = pstr[j++]; } pnew_str[i] = 0; return pnew_str; }

De la mthode dans nos fonctions CHAPITRE 6

103

Outre le retour avec un char *, nous constatons ici une forme assez particulire :
const char *pstr

forme qui nexiste pas en Java ni sur les anciens compilateurs C. Si nous avions utilis cette construction dans la version splice1(), nous aurions obtenu une erreur de compilation pour les deux instructions str[i] = .... En effet, le const va tre utilis par le compilateur pour vrier que la variable nest pas touche. Nous reviendrons dans ce mme chapitre sur les diffrentes variantes dutilisation et du passage des variables en C++. La logique de la fonction splice2() est lgrement diffrente, car nous devons calculer soigneusement la dimension de la nouvelle chane avant son allocation. La variable max doit tre adapte dans le cas o la longueur de la partie effacer serait suprieure la dimension du tableau. Ce cas-l est aussi vri. Le nouveau tableau doit toujours avoir un octet supplmentaire pour stocker le 0 de la terminaison. La variable j est essentielle, car elle nous permet de sauter sur la partie que nous voulons couper.

Recommandation pour des programmeurs C potentiels


lexception du cas o un programmeur se doit dcrire des programmes C sur danciennes machines, il ny a pas ou peu davantages vouloir absolument programmer en C ou vouloir dbuter en C avant de passer C++, ne serait-ce que pour des raisons de simplicit. Un dbutant peut trs bien crire de petits programmes en utilisant les nouvelles ressources du langage C++, mais sans ncessairement crire de nouvelles classes. Cest en fait ce que nous faisons ici !

Comment utiliser nos fonctions C ?


Nous avons prsent deux fonctions C, que nous devons prsent intgrer dans un chier source que nous dnirons commun pour les deux : csplice.cpp. Nous pouvons compiler ces deux fonctions dans un chier csplice.o, que nous allons rutiliser trs rapidement :
g++ -c csplice.cpp

Le lecteur pourra retrouver ces chiers et le Makefile sur le CD-Rom. Cependant, il nous faut dabord revenir sur un point de dtail dans le cas o ces deux fonctions seraient dveloppes pralablement dans le mme chier source. Si nous crivons le code avec cette forme :
#include <iostream.h> void splice1(char *str, int pos, int longeur) { //............ } char *splice2(const char *str, int pos, int longeur) { //................... }

104

Apprendre Java et C++ en parallle

int main() { // utilisation de splice1() et splice2() }

nous naurions aucune difcult. En revanche, si nous inversions le code ainsi :


#include <iostream.h> int main() { // utilisation de splice1() et splice2() } void splice1(char *str, int pos, int longeur) { //............ } char *splice2(const char *str, int pos, int longeur) { //................... }

le compilateur retournerait les erreurs :


"splice1.cpp", line 12: Error: The function splice2 must have a prototype.

car ces deux fonctions ne sont pas dnies et ne le seront que plus loin. Il faut donc dnir un prototype pour ces fonctions, de cette manire :
#include <iostream.h> void splice1(char *str, int pos, int longeur); char *splice2(const char *str, int pos, int longeur); int main() { // utilisation de splice1() et splice2() } void splice1(char *str, int pos, int longeur) { //............ } char *splice2(const char *str, int pos, int longeur) { //................... }

Il sagit prsent de tester soigneusement notre code. Ceci est une ncessit absolue, car le code ci-dessus est relativement pointu pour le traitement des codes derreurs et de dbordement. Pour ce faire, nous allons intgrer ces deux fonctions dans un programme de test, de la manire suivante :
// splice1.cpp #include <iostream>

De la mthode dans nos fonctions CHAPITRE 6

105

using namespace std; void splice1(char *pstr, int pos, int longueur); char *splice2(const char *pstr, int pos, int longueur); int main() { //char *pmon_string = "0123456789"; char pmon_string[11]; char *pmon_nouveau; strcpy(pmon_string, "0123456789"); cout << " 0: " <<pmon_string << endl; splice1(pmon_string, 3, 2); cout << "11: " << pmon_string << endl; if ((pmon_nouveau = splice2(pmon_string, 3, 2)) == 0) return 0; cout << "12: " << pmon_nouveau << endl; delete pmon_nouveau; splice1(pmon_string, 0, 2); cout << "21: " << pmon_string << endl; if ((pmon_nouveau = splice2(pmon_string, 0, 2)) == 0) return 0; cout << "22: " << pmon_nouveau << endl; delete pmon_nouveau; splice1(pmon_string, 3, 100); cout << "31: " << pmon_string << endl; if ((pmon_nouveau = splice2(pmon_string, 3, 100)) == 0) return 0; cout << "32: " << pmon_nouveau << endl; delete pmon_nouveau; } void splice1(char *pstr, int pos, int longueur) { // code ci-dessus } char *splice2(const char *pstr, int pos, int longueur) { // code ci-dessus }

Le rsultat peut sembler incongru premire vue, mais demeure cependant tout fait correct :
0: 0123456789 11: 01256789 12: 012789 21: 256789 22: 6789 31: 256

Il faut dabord revenir sur la premire partie du code, qui a donn des sueurs froides lauteur. Le code :

106

Apprendre Java et C++ en parallle

char pmon_string[11]; strcpy(pmon_string, "0123456789");

pourrait en fait tre remplac par :


char *pmon_string = "0123456789";

Mais le compilateur que nous avons utilis semble protger la location mmoire pour cette dernire forme et plante la machine sur la fonction strcpy(). Ce nest pas le cas avec dautres compilateurs, comme celui de Borland (C++Builder), qui accepte les deux formes. Le strcpy() est une fonction C que nous pourrions avantageusement remplacer par des mthodes de la classe iostream, que nous verrons plus loin dans cet ouvrage. Notre chane de caractres dorigine tant 0123456789, le premier rsultat 01256789 correspond bien leffacement de deux caractres partir de la position 3. Le second rsultat 012789 correspond aussi leffacement de deux caractres partir de la position 3, mais partir du rsultat prcdent. Cependant, pour le test 21 (test 2, splice1()), nous nutilisons pas le prcdent rsultat car splice2() a prserv le contenu de la chane. Enn, le dernier test 32 napparatra pas, car lindex 3 se trouve prsent en dehors du tableau, et une nouvelle chane ne sera pas retourne.

Nos fonctions C dans un module spar


Si nous crivons le code ci-dessous :
// splice2.cpp #include <iostream> using namespace std; extern void splice1(char *pstr, int pos, int longueur); extern char *splice2(const char *pstr, int pos, int longueur); int main() { char pmon_string[27]; char *pmon_nouveau; strcpy(pmon_string, "aBcDeFgHiJkLmNoPqRsTuVwXyZ"); cout << "a: " <<pmon_string << endl; splice1(pmon_string, 16, 6); cout << "b: " << pmon_string << endl; if ((pmon_nouveau = splice2(pmon_string, 0, 10)) == 0) return 0; cout << "c: " << pmon_nouveau << endl; delete pmon_nouveau; }

cela nous indique que nos deux fonctions extern splice1() et splice2() doivent se trouver dans un module spar. Le Makefile va nous aider mieux comprendre la composition et la cration de cette application :

De la mthode dans nos fonctions CHAPITRE 6

107

splice2.exe:

csplice.o splice2.o g++ -o splice2.exe splice2.o csplice.o splice2.cpp g++ -c splice2.cpp csplice.cpp g++ -c csplice.cpp

splice2.o:

csplice.o:

Lexcutable splice2.exe est compos de deux modules, splice2.o et csplice.o, qui sont compils sparment. Il est important dajouter les dpendances, car si nous modions le code de csplice.cpp, la recompilation doit seffectuer correctement. Si nous changions la dnition des fonctions, il faudrait aussi modier les deux lignes dextern dans le code de splice2.cpp. Ce nest donc pas la meilleure manire de faire, et il faudrait transfrer ces deux lignes de code dans un chier den-tte commun aux deux chiers .cpp. Nous ferons ce travail comme exercice.

Les arguments de mthodes en C++


Cette introduction aux fonctions du langage C tait sans aucun doute un passage oblig. Nous allons prsent rutiliser notre fonction Perl splice() et lintgrer dans une classe C++ que nous allons tout simplement nommer Perl1. Nous allons prsenter les diffrentes variantes du passage des paramtres en C++. Nous allons commencer par la manire traditionnelle, le passage par rfrence, qui nexiste pas en langage C.

Passage par rfrence (classe C++ Perl1)


Nous allons procder par tape, an danalyser les diffrentes manires en C++ de traiter le passage darguments ainsi que le retour du rsultat de la mthode. Il est tout fait raisonnable de crer une mthode splice1() et dutiliser cette fois-ci la classe string du Standard C++. Nous commencerons donc par la dnition de la classe Perl1 dans le chier den-tte perl1.h :
// Perl1.h #include <string> class Perl1 { public: void splice1(string &str, int pos, int longueur = 1); };

Presque trop simple pour tre juste ! Une classe sans constructeur ni attribut ne peut tre plus dshabille ! Ce qui est important ici, cest lapparition dune rfrence (&str) et de lutilisation dun paramtre par dfaut (int longueur = 1). Nous en comprendrons trs rapidement lutilisation, aprs avoir dvelopp le code de la mthode :
// Perl1.cpp #include "Perl1.h"

108

Apprendre Java et C++ en parallle

using namespace std; void Perl1::splice1(string &str, int pos, int longueur) { int posend = pos + longueur; int dimension = str.length(); if ((pos < 0) || (longueur <= 0) || (pos >= dimension)) return; if ((longueur + pos) > dimension) { str = str.substr(pos, dimension); } else { str = str.substr(0, pos) + str.substr(posend, dimension); } }

Comme notre mthode splice1() ne retourne pas de valeur, nous devons nous imaginer que le rsultat sera disponible dans la variable str. Cest effectivement le cas, car nous utilisons une rfrence. Lancienne chane de caractres sera donc perdue comme dans le langage Perl. La mthode splice1() modiera directement le contenu de str. Nous allons le vrier prsent avec le code suivant, qui nous permet de tester la classe :
// Perltest1.cpp #include <iostream> #include <string> #include "Perl1.h" using namespace std; int main() { string mon_string("0123456789abcdef"); Perl1 mon_perl; cout << " 0 : " << mon_string << endl;

mon_perl.splice1(mon_string, 3, 2); cout << " 1 (3, 2) : " << mon_string << endl; mon_perl.splice1(mon_string, 0, 3); cout << " 2 (0, 3) : " << mon_string << endl; mon_perl.splice1(mon_string, 100, 3); cout << " 3 (100, 4) : " << mon_string << endl; mon_perl.splice1(mon_string, 4, 100); cout << " 4 (4, 100) : " << mon_string << endl; mon_perl.splice1(mon_string, 2); cout << " 5 (2) : " << mon_string << endl; }

De la mthode dans nos fonctions CHAPITRE 6

109

Nous voyons aussi dans ce code lutilisation de la mthode splice1() avec uniquement deux paramtres. Dans le chier den-tte nous avions :
public void splice1(string &str, int pos, int longueur = 1);

ce qui indique que, si le troisime paramtre est omis, il sera automatiquement initialis 1. Cest une forme trs pratique, uniquement possible en C++, sur laquelle nous allons revenir. Nous pouvons constater les diffrents rsultats qui sortent de ce programme de test :
0 1 2 3 4 5 (3, 2) (0, 3) (100, 4) (4, 100) (2) : : : : : : 0123456789abcdef 01256789abcdef 56789abcdef 56789abcdef 9abcdef 9acdef

et qui correspondent la spcication dnie pralablement. Un test complet aurait d inclure plus de cas limites comme des entiers ngatifs. Cette version avec un passage par rfrence est sans doute notre prfre et la plus propre. Nous le comprendrons mieux aprs avoir examin les deux autres variantes, par valeur et par pointeur. Pour le dernier appel, mon_perl.splice1(mon_string, 2), nous noterons que le paramtre longueur = 1 est automatiquement ajout. Il est possible den dnir plusieurs de ce type, mais ils doivent imprativement se trouver en n de liste. Une construction telle que :
void Perl1::fonction(int va = 1, int vb, int vc = -1);

nest pas possible. Il faudrait dplacer le paramtre vb devant va.

Paramtres dclars comme const


Dans lexemple qui prcde, pourrions-nous utiliser la forme suivante ?
void Perl1::splice1(const string &str, int pos, int longueur)

Eh bien, non ! La directive const indique que la variable str ne sera pas touche par la mthode splice1(). Pour sen convaincre, il suft de modier les chiers Perl1.h et Perl1.cpp et dessayer de recompiler le code. Les deux lignes suivantes :
str = str.substr(pos, dimension); str = str.substr(0, pos) + str.substr(posend, dimension);

nous retourneraient une erreur, puisque le string str est modi.

Passage par valeur (classe C++ Perl2)


Essayons prsent une version sans rfrence (&). Cette forme est appele passage par valeur. Nous inclurons ici les trois parties du code, cest--dire la dnition, le code de la classe et le test dans le mme chier Perl2.cpp, ceci uniquement pour des raisons de prsentation. Il nest pas obligatoire de nommer la mthode splice2() avec un 2. Nous

110

Apprendre Java et C++ en parallle

avons fait ce choix pour la diffrencier plus aisment par rapport la prcdente, qui se nomme en fait Perl1::splice1(). Nous aurions fort bien pu mettre les deux mthodes dans une seule classe. Voici donc le code avec un passage par valeur :
// Perl2.cpp #include <iostream> #include <string> using namespace std; class Perl2 { public: void splice2(string str, int pos, int longeur = 1); }; void Perl2::splice2(string str, int pos, int longueur) { int posend = pos + longueur; int dimension = str.length(); if ((pos < 0) || (longueur <= 0) || (pos >= dimension)) return; if ((longueur + pos) > dimension) { str = str.substr(pos, dimension); } else { str = str.substr(0, pos) + str.substr(posend, dimension); } } int main() { string mon_string("0123456789abcdef"); Perl2 mon_perl; cout << " 0 : " << mon_string << endl;

mon_perl.splice2(mon_string, 3, 2); cout << " 1 (3, 2) : " << mon_string << endl; }

Si nous excutons ce code, nous remarquons que str na pas t touch. En effet, dans ce cas-l, la mthode splice2() va copier str sur la pile et travailler sur celle-ci. Au retour, ce travail sera perdu, comme nous le voyons :
0 1 : 0123456789abcdef (3, 2) : 0123456789abcdef

Cette solution nest pas possible, et il nous faut, cette fois-ci, recevoir le string comme rsultat de la mthode. Cest ce que nous ferons dans la version Perl2a ci-dessous :
// Perl2a.cpp #include <iostream> #include <string>

De la mthode dans nos fonctions CHAPITRE 6

111

using namespace std; class Perl2 { public: string splice2(string str, int pos, int longeur = 1); }; string Perl2::splice2(string str, int pos, int longueur) { int posend = pos + longueur; int dimension = str.length(); if ((pos < 0) || (longueur <= 0) || (pos >= dimension)) return str; if ((longueur + pos) > dimension) { return str.substr(pos, dimension); } else { return str.substr(0, pos) + str.substr(posend, dimension); } } int main() { string mon_string("0123456789abcdef"); Perl2 mon_perl; cout << " 0 : " << mon_string << endl;

mon_string = mon_perl.splice2(mon_string, 3, 2); cout << " 1 (3, 2) : " << mon_string << endl; }

dont le rsultat, cette fois-ci, nous satisfait :


0 1 : 0123456789abcdef (3, 2) : 01256789abcdef

Le problme principal concerne la copie de tous les paramtres passs comme arguments par valeur. Si ces string sont importants, nous pouvons nous imaginer le travail fourni, qui peut savrer inutile dans cette situation, pour produire une seconde copie en mmoire. Cela affectera les performances, et il faut donc viter cette construction, en gnral sans intrt.

const et passage par valeur


Le morceau de code suivant pourrait sembler stupide en premire lecture, mais demeure essentiel pour les programmeurs dbutants :
// untest.cpp #include <iostream>

112

Apprendre Java et C++ en parallle

using namespace std; void untest(const int var1, int var2) { int v1 = var1; int v2 = var2; v1 = 100; v2 = 200; // var1 = 10; impossible var2 = 20; // etc. } int main() { int variable1 = 1; int variable2 = 2; untest(variable1, variable2); cout << "variable2: " << variable2 << endl; }

Et son rsultat :
variable2: 2

De ce code, nous pouvons tirer les conclusions suivantes : Le paramtre var2 tant pass par valeur, il restera inchang au retour de la fonction untest(). Il est donc inutile de le copier dans une nouvelle variable v2 sur la pile, sauf si la mthode untest() doit rutiliser la valeur initiale plusieurs reprises. Le const est inutile, puisque la variable var1 est passe par valeur et restera inchange pour la partie appelante. Elle obligerait inutilement la copie dans une autre variable (v1), si nous dsirions lutiliser comme variable de travail.

Passage par pointeur (classe C++ Perl3)


Cest la forme classique C que nous avons dj traite, mais sur laquelle nous allons revenir rapidement pour terminer cette partie :
// Perl3.cpp #include <iostream> #include <string> using namespace std; class Perl3 { public: void splice3(string *str, int pos, int longeur = 1); };

De la mthode dans nos fonctions CHAPITRE 6

113

void Perl3::splice3(string *str, int pos, int longueur) { int posend = pos + longueur; int dimension = str->length(); if ((pos < 0) || (longueur <= 0) || (pos >= dimension)) return; if ((longueur + pos) > dimension) { *str = str->substr(pos, dimension); } else { *str = str->substr(0, pos) + str->substr(posend, dimension); } } int main() { string mon_string("0123456789abcdef"); Perl3 mon_perl; cout << " 0 : " << mon_string << endl;

mon_perl.splice3(&mon_string, 3, 2); cout << " 1 (3, 2) : " << mon_string << endl; }

Par rapport la version Perl2, il ny a quune seule diffrence dans le main(), notre & devant le mon_string. En effet, splice3() sattend recevoir un pointeur. En revanche, dans le code de splice3(), cest un peu plus sauvage . Un programmeur C y retrouvera ses habitudes avec ces bons vieux * et ->. Un programmeur Java dbutant en C++ va sans doute tre horripil par cette construction. Comme, dans ce cas-ci, il ny a aucune ncessit dutiliser cette construction, nous aimerions conseiller, voire contraindre, les programmeurs utiliser le passage par rfrence (Perl1::splice1()).

Le sufxe const pour une mthode C++


Il est possible dajouter le sufxe const la n de la dclaration dune mthode. Nous allons tudier sa signication au travers de cet exemple :
class UneClasse { private: int data; public: void inspecte() const { // data = 1; impossible } void fait_quelque_chose() /* const impossible */ { data = 1; } };

114

Apprendre Java et C++ en parallle

Nous promettons que la mthode inspecte() ne modiera sous aucun prtexte ltat de lobjet, cest--dire ici lattribut data. En revanche, la mthode fait_quelque_chose() peut changer ltat de lobjet. Si nous remettions le data = 1; nous obtiendrions du compilateur :
const.cpp: In method `void UneClasse::inspecte() const': const.cpp:13: assignment of member `UneClasse::data' in read-only structure

ce qui est justi et propre. prsent, si nous voulons utiliser des mthodes au travers dobjets de cette classe, nous pourrions utiliser une fonction crite ainsi :
void ma_fonction(UneClasse& uc1, const UneClasse& uc2) { uc1.inspecte(); uc2.inspecte(); uc1.fait_quelque_chose(); // uc2.fait_quelque_chose(); impossible }

Nous ne pouvons pas laisser la dernire instruction, car le compilateur nous retournerait :
const.cpp: In function `void ma_fonction(UneClasse &, const UneClasse &)': const.cpp:26: passing `const UneClasse' as `this' argument of `void UneClasse::fait_quelque_chose()' discards qualifiers

Lors de la dnition de ma_fonction(), nous dsirons que lobjet uc2 reste constant, cest-dire inchang. Un objet dclar non constant, comme uc1, pourra utiliser nimporte quel type de mthode publique de la classe UneClasse. Cest un aspect essentiel contribuant une bonne spcication des paramtres dune fonction ou dune mthode. De trs nombreux objets peuvent tre passs comme constants des mthodes. Nous aurions pu crire simplement, sans penser trop loin :
void fait_quelque_chose() const {}

Le compilateur nous aurait alors forc regarder notre code plus srieusement. Il peut aussi arriver que le simple rajout dun sufxe const nous permette de compiler notre code qui refusait systmatiquement certaines constructions ! Toute cette discussion est aussi applicable des pointeurs que nous pouvons dclarer
constant alors que cela na aucun sens pour des paramtres passs par valeur.

Comme nous lavons vu ci-dessus, lutilisation du mot-cl const pour les mthodes et fonctions C++ doit faire lobjet dune attention particulire. En dautres termes, il nest pas recommand dignorer systmatiquement la directive const simplement par paresse. Un emploi courant de passage de paramtres par rfrence constante est la marque dune programmation C++ professionnelle !

Fonctions et mthodes inline en C++


Une fonction peut tre dclare inline (en ligne) :

De la mthode dans nos fonctions CHAPITRE 6

115

inline int une_fonction(const int nombre1, const int nombre2) { // code de la fonction }

Le mot-cl inline est destin au compilateur an de lui demander de remplacer lappel de la fonction par du code en ligne. Chaque fois que nous utiliserons une_function(), le code sera insr par le compilateur, ce qui permettra dconomiser un appel de fonction. Cela peut reprsenter un gain important de performance. Pour une classe, si nous crivons le code dune mthode faisant partie de la dnition de la classe, comme suit :
class UneClasse { public: int une_methode(const int nombre1, const int nombre2) { // code de la fonction } };

la mthode une_methode() sera automatiquement inline. Ceci veut dire que chaque fois que cette mthode sera utilise, pour un objet de la classe UneClasse, le compilateur insrera le code en ligne. Si nous dnissons notre classe ainsi :
class UneClasse { public: int une_methode(const int nombre1, const int nombre2); }; int UneClasse::une_methode(const int nombre1, const int nombre2) { // code de la fonction }

la mthode restera normale, avec un appel de mthode. Il y a videmment une perte en performance, mais en contrepartie un gain en dimension de programme puisque le code ne sera pas recopi chaque fois. Nous rappelons que la dnition de la classe, et non le code de la classe, est en gnral incluse dans un chier den-tte. Toutefois, si nous souhaitons que le compilateur recopie le code chaque fois, nous devons employer la forme suivante :
class UneClasse { public: int une_methode(const int nombre1, const int nombre2); }; inline int UneClasse::une_methode(const int nombre1, const int nombre2) { // code de la fonction }

Bruce Eckel, par exemple, dans son ouvrage Thinking in C++, consacre un chapitre entier ce sujet, ainsi quaux aspects de performance des compilateurs et lcriture de ces derniers. Si la mthode est trs souvent utilise et ne contient que quelques lignes de code, il est tout fait raisonnable de la dnir inline.

116

Apprendre Java et C++ en parallle

Utilisation des numrations avec des mthodes C++


Au chapitre 2, pour le C++ seulement, nous avons dj tudi les numrations, au travers du mot-cl enum, que nous pouvons utiliser judicieusement dans une classe et dans des appels de fonctions ou de mthodes. Voici deux utilisations possibles des numrations, comme lillustre cette petite classe abrge, qui devrait tre dnie dans un chier dentte spar :
// methode_enum.cpp class EnumTest { public: enum ENUM_TEST_RES { ENUM_TEST_OK, ENUM_TEST_ERR }; enum ENUM_OPTION { ENUM_OPTION_DEC, ENUM_OPTION_OCT, ENUM_OPTION_HEX }; ENUM_TEST_RES calcule(const char *pinput, const char *poutput, ENUM_OPTION enum_opt) { // code de la mthode return ENUM_TEST_OK; } }; int main() { EnumTest et; char *pentree = "123456"; char *psortie; EnumTest::ENUM_TEST_RES res = et.calcule(pentree, psortie, EnumTest::ENUM_OPTION_DEC); switch(res) { case EnumTest::ENUM_TEST_OK: //.... break; case EnumTest::ENUM_TEST_ERR: //.... break; default: //... erreur break; } return 0; }

La dclaration de lnumration doit tre publique si nous voulons accder des dnitions comme EnumTest::ENUM_TEST_OK. Il est tout fait possible dutiliser des numrations prives, pour ne les utiliser qu lintrieur de la classe. Dnir des numrations globales, cest--dire en dehors dune classe et dans un chier den-tte, est galement envisageable, mais dconseiller, au risque de nous trouver avec une multitude de noms. On retrouve parfois, dans la littrature, le terme de pollution dans ce contexte.

De la mthode dans nos fonctions CHAPITRE 6

117

Dans cet exemple, nous utilisons une numration la fois pour passer un paramtre et pour retourner un rsultat. La mthode calcule() prendra laction ncessaire, suivant que nous travaillons en dcimal, en octal ou en hexadcimal. On ne sait pas pourquoi ici, mais on simagine que le rsultat viendra dans une chane de caractres alloue dynamiquement sur le pointeur psortie. Retourner un rsultat avec un enum est trs souvent utilis en lieu et place dun entier, qui contient parfois nimporte quoi.

Utilisation des numrations en Java


Les numrations en Java sont apparues partir du JDK 1.5. Sans trop rentrer dans les dtails, nous donnerons quelques exemples et commencerons par un cas simple, une liste de fruits :
public class EnumFruits1 { public enum Fruits { poire, pomme, fraise, mirabelle, prune }; public static void main(String[] args) { for (Fruits unFruit : Fruits.values()) { System.out.println(unFruit); } System.out.println(); System.out.println(Fruits.pomme); } }

Maintenant, si nous excutons ce code :


poire pomme fraise mirabelle prune pomme

Nous voyons que les diffrentes valeurs de lnumration sont prises comme des Strings. La boucle for utilise aussi la forme tendue du JDK 1.5 que nous avons vu au chapitre 3. Nous pouvons tendre notre exemple de cette manire :
public class EnumFruits2 { public enum Fruits { poire, pomme, fraise; public String toString() { switch (this) { case poire: return ("Poire"); case pomme:

118

Apprendre Java et C++ en parallle

return ("Pomme"); case fraise: return ("Fraise"); } return "Fruit inconnu"; } } public static void main(String[] args) { for (Fruits unFruit : Fruits.values()) { System.out.println(unFruit); } if (unFruit == Fruits.pomme) { System.out.println("--> Nous avons vraiment une pomme"); } } }

Le rsultat sera prsent ainsi avec nos fruits en majuscules :


Poire Pomme --> Nous avons vraiment une pomme Fraise

En fait cest quivalent une classe interne. Pour chaque fruit, la mthode toString() sera appele an de nous prsenter nos fruits dans la forme dsire : ici avec une majuscule devant.

Les arguments de mthodes en Java


Le langage Java, au contraire de C++, na pas trois mthodes de passage darguments (par valeur, par pointeur ou par rfrence), mais quune seule, que nous appellerons par valeur bien quil faille prciser les dtails plus loin. Nous pouvons aussi choisir, comme pour C++, de retourner le rsultat soit avec un argument soit avec un retour de fonctions. Nous allons prsenter ici deux variantes : Perl1 : similaire limplmentation du langage Perl, o la chane de caractres nest pas prserve. Perl2 : retour de la mthode avec un nouveau String, sans toucher loriginal.

splice() avec retour par largument (classe Java Perl1)


class Perl1 { public void splice(StringBuffer str, int pos, int longueur) { int posfin = pos + longueur; int dimension = str.length();

De la mthode dans nos fonctions CHAPITRE 6

119

if ((pos < 0) || (longueur <= 0) || (pos >= dimension)) return; if ((longueur + pos) > dimension) posfin = dimension; str.delete(pos, posfin); } public static void main(String[] args) { Perl1 myperl = new Perl1(); StringBuffer str = new StringBuffer("0123456789"); System.out.println(str); if (args.length > 0) { int pos = Integer.parseInt(args[0]); int larg = Integer.parseInt(args[1]); myperl.splice(str, pos, larg); System.out.println(str); } else { myperl.splice(str, 3, 2); System.out.println(str); myperl.splice(str, 0, 2); System.out.println(str); myperl.splice(str, 3, 100); System.out.println(str); } } }

Durant la phase de dveloppement du programme, il est essentiel de tester toutes les variations possibles. Un bon programmeur nest pas ncessairement un bon testeur ! Certains cas limites sont souvent oublis. Nos trois petits tests sont bons, mais rien de plus ! Le rsultat :
0123456789 01256789 256789 256

est devenu correct alors qu lorigine nous avions utilis une dimension 1. LAPI du delete() nous indique bien : lexclusion de posfin ! En effet, si nous avions crit :
if ((longueur + pos) > dimension) posfin = dimension - 1;

au lieu de :
if ((longueur + pos) > dimension) posfin = dimension;

nous aurions alors reu le rsultat suivant :


0123456789 01256789 256789 2569

120

Apprendre Java et C++ en parallle

qui nous semble correct au premier coup dil ! Lintroduction de diffrents choix par lutilisateur augmente les possibilits de combinaisons des tests, souvent sans le vouloir, alors que des algorithmes systmatiques de test naboutiraient pas forcment toutes ces possibilits. Lentre suivante, avec le code dimension - 1, nous montrerait immdiatement lerreur :
java Perl1 0 1000 0123456789 9

Il nous faut prsent passer aux dtails du code. Nous commencerons par lutilisation de StringBuffer, qui est mutable (modiable), au contraire de String. Il ny a pas de mthode telle que delete dans la classe String. Nous pourrions nous poser la question de savoir pourquoi nous nutilisons pas simplement :
str.delete(pos, posfin);

sans vrier la validit des arguments. La rponse est simple ! Une petite erreur dindex entranerait une exception et un rsultat inattendu. Dans notre exemple, nous avons accept le consensus de Perl, cest--dire ne pas toucher la chane si la position ou la longueur du morceau couper est incorrecte.

splice() avec retour de mthode (classe Java Perl2)


Nous passons prsent la version qui nous retourne un nouveau String laissant inchange la chane dorigine.
class Perl2 { public String splice(String str, int pos, int longueur) { int posend = pos + longueur; int dimension = str.length(); if ((pos < 0) || (longueur <= 0) || (pos >= dimension)) return str; if ((longueur + pos) > dimension) return str.substring(pos, dimension); return str.substring(0, pos) + str.substring(posend, dimension); } public static void main(String[] args) { Perl2 myperl = new Perl2(); String str = new String("0123456789"); System.out.println(str); if (args.length > 0) { int pos = Integer.parseInt(args[0]); int larg = Integer.parseInt(args[1]); System.out.println(myperl.splice(str, pos, larg)); } else {

De la mthode dans nos fonctions CHAPITRE 6

121

System.out.println(myperl.splice(str, 3, 2)); System.out.println(myperl.splice(str, 0, 2)); System.out.println(myperl.splice(str, 3, 100)); } } }

Le rsultat sera videmment diffrent de celui de la classe Perl1 puisque la chane de caractres initiale est toujours utilise :
0123456789 01256789 23456789 3456789

Dans ce genre de code, il est essentiel de vrier lAPI de la mthode Java substring(). Nous ne pouvons pas la comparer la mthode substr() du C++, dont le deuxime paramtre est une dimension et non une position comme en Java. Il est donc judicieux de nommer correctement le nom des variables, comme ici avec pos, pos2 ou posfin. Le programmeur pourrait se faire un petit schma an danalyser son code.

Java : argument par rfrence ou par valeur ?


Cest une question qui revient souvent, principalement pour les programmeurs qui ont une culture C++. Nous allons entrer dans les dtails aprs avoir examin ce morceau de code :
class RefOuVal { public void monTest(StringBuffer monstr, int monnb) { monnb = 9999; monstr.append("_un_appendice_" + monnb); System.out.println("Pendant: " + monstr + " " + monnb); monstr = new StringBuffer("Vraiment_0123456789"); System.out.println("Pendant: " + monstr + " " + monnb); } public static void main(String[] args) { RefOuVal unobjet = new RefOuVal(); StringBuffer unstr = new StringBuffer("0123456789"); int leNombre = 10; System.out.println("Avant: " + unstr + " " + leNombre); unobjet.monTest(unstr, leNombre); System.out.println("Aprs: " + unstr + " " + leNombre); } }

et bien videmment le rsultat :


Avant: 0123456789 10 Pendant: 0123456789_un_appendice_9999 9999

122

Apprendre Java et C++ en parallle

Pendant: Vraiment_0123456789 9999 Aprs: 0123456789_un_appendice_9999 10

Commenons par le plus simple ! La variable leNombre de type primitif int est passe par valeur la mthode monTest(). lorigine, elle possde la valeur de 10 et ne sera pas affecte par les modications dans la mthode (affectation 9999). Ainsi, une mthode Java peut jouer avec nimporte quel type dargument de type primitif. En ce qui concerne la variable unstr, qui est un objet de la classe StringBuffer, elle est aussi passe par valeur, mais pour la rfrence de lobjet. Comme StringBuffer est mutable, le contenu sera bien modi comme le montre le rsultat. Cependant, le dernier :
monstr = new StringBuffer("Vraiment_0123456789");

ne va pas toucher le contenu original. monstr est en fait une copie de ladresse de la rfrence. la n de la mthode, ce nouvel objet sera envoy dans le ramasse-miettes (garbage collector). Cette dernire construction serait dailleurs contraire une programmation oriente objet. Enn, cette forme est aussi possible :
public void monTest(final StringBuffer monstr, int monnb) {

mais entranerait une erreur de compilation :


RefOuVal.java:6: Can't assign a second value to a blank final variable: monstr monstr = new StringBuffer("Vraiment_0123456789"); ^ 1 error

Le seul avantage de ce code serait de forcer le programmeur utiliser dautres constructions et de ne pas crer un objet dont il pourrait penser quil sera rutilis aprs avoir t modi. Pour rsumer, nous dirons que : Les arguments de type primitif sont passs par valeur. Les autres types dargument sont passs par valeur pour la rfrence de lobjet, mais leur contenu peut tre modi si ce dernier est mutable.

Les espaces de noms en C++


Lorsque nous avons rencontr pour la premire fois lespace de noms :
using namespace std;

ctait bien videmment au chapitre 1, o nous avons montr clairement notre intention de ne pas crire du C ou du C++ traditionnel. Le std indique ici que les noms que nous allons utiliser se trouvent dans lespace de noms de la bibliothque Standard C++. Le Standard C++ a introduit un mcanisme permettant de prvenir la collision des noms utiliss au travers du systme. Un programmeur C a sans doute rencontr ce problme en

De la mthode dans nos fonctions CHAPITRE 6

123

utilisant par erreur des noms dj dnis globalement ou dans des chiers den-tte. En passant en C++, en liminant par exemple les variables globales, la situation sest amliore, mais pas totalement, et ce notamment dans le cas de gros projets informatiques. Lancien programmeur C aura remplac ses variables globales par des attributs de classe et ses fonctions par des mthodes de classe. Il accdait autrefois des variables globales, alors que maintenant, en bon programmeur C++, il naccdera mme plus un attribut public dune classe, mais laura protg pour forcer laccs au travers dune ou de plusieurs mthodes de cette classe. Dans son dernier ouvrage de rfrence, Bjarne Stroustrup consacre un chapitre entier sur les espaces de noms et les diverses techniques daccs de plusieurs espaces de noms. Dans le prsent ouvrage, nous ne donnerons quun aperu rapide, dans le but den comprendre le mcanisme et laccs.

Utilisation classique du namespace


Prenons pour premier exemple un programme traditionnel :
// namespace1.cpp #include <cstdio> using namespace std; void main() { printf("J'efface le fichier test.txt\n"); remove("test.txt"); }

Les deux fonctions printf() et remove() sont de bonnes vieilles fonctions C qui sont en fait dnies dans le chier den-tte stdio.h, bien connu des programmeurs C. Nous savons dj que printf() peut tre avantageusement remplac par un cout <<. La fonction remove() est une de ces fameuses fonctions C qui nexistent pas dans iostream. Comme elle entrane souvent des problmes de compatibilit, elle est parfois remplace par unlink(), qui permet deffacer un chier. Le <cstdio> est lillustration de comment procder prsent. Cette forme nous permet daccder indirectement aux fonctions des anciennes bibliothques C, qui sont toujours accessibles et dont la plupart sont certies ANSI ou ISO. Si nous avions crit nos deux instructions du main() de cette manire :
std::printf("J'efface le fichier test.txt\n"); std::remove("test.txt");

notre code aurait fonctionn pareillement. La notation :: permet daccder un membre dans un espace de noms dtermin. Ici, lespace de noms std, utilis tout au long de notre ouvrage, est spci formellement, bien que cela ne soit pas ncessaire, car nous lavons dj dni avec using namespace std;, notre espace de noms par dfaut.

Conit de nom
prsent, nous aimerions dnir une fonction nomme remove() ainsi :

124

Apprendre Java et C++ en parallle

// namespace2.cpp #include <cstdio> using namespace std; void remove(const char *pfile) { printf("J'efface ici le fichier test.txt\n"); remove("test.txt"); } void main() { printf("J'efface le fichier test.txt\n"); remove("test.txt"); }

Si nous essayons de compiler ce code, nous voyons bien la difcult :


g++ -c namespace2.cpp namespace2.cpp: In function `void remove(const char *)': namespace2.cpp:7: new declaration `void remove(const char *)' /usr/include/stdio.h:226: ambiguates old declaration `int remove(const char *)'

Nous avons donc bien un conit de nom entre la mthode remove() disponible dans <cstdio> et celle que nous aimerions dnir. Il nous faut soit changer de nom soit utiliser un autre mcanisme.

Comment dnir un espace de noms


Pour corriger le code prcdent, nous sommes obligs de dnir notre nouvelle fonction dans un espace de noms spar. Cela se fera de cette faon :
// namespace3.cpp #include <cstdio> #include <iostream> using namespace std; namespace MaLibrairie { void remove(const char *pfile) { printf("J'efface ici le fichier test.txt\n"); std::remove("test.txt"); } class UneClasse { private: int valeur; public: static int attribut;

De la mthode dans nos fonctions CHAPITRE 6

125

UneClasse(int une_valeur) : valeur(une_valeur) {} void printf() { std::printf("Bonjour d'UneClasse: "); cout << attribut << " : " << valeur << endl; } }; } int MaLibrairie::UneClasse::attribut = 0; int main() { printf("J'efface le fichier test.txt\n"); MaLibrairie::remove("test.txt"); MaLibrairie::UneClasse::attribut = 10; MaLibrairie::UneClasse maclasse(20); maclasse.printf(); }

Dans main(), nous notons la manire daccder remove() dans lespace de noms MaLibrairie avec loprateur de porte ::. Dans notre code remove() la forme :
std::remove("test.txt");

est essentielle. Sans le std::, la fonction remove() de lespace de noms MaLibrairie serait appele, et ceci dans une boucle rcursive innie. Nous avons ajout notre classe UneClasse dans cet espace de noms. Linstruction :
int MaLibrairie::UneClasse::attribut = 0;

est fondamentale, sinon la variable statique ne serait pas initialise. Nous en verrons dautres exemples au chapitre 10. Ce qui est important ici, cest de comprendre la syntaxe et le mcanisme. Un exercice supplmentaire devrait sufre pour sy familiariser. Nous prsentons tout de mme le rsultat du programme ci-dessus :
J'efface le fichier test.txt J'efface ici le fichier test.txt Bonjour d'UneClasse: 10 : 20

qui est attendu. Bien videmment, si le chier test.txt existe, il sera effac. Dans le cas contraire, rien ne se passera, car lexistence ou le rsultat de lopration nest pas vri. Pour sen convaincre, nous pouvons crer un chier test.txt, excuter namespace3.exe et vrier que test.txt a disparu.

Fichiers den-tte et namespace


Nous avons vu au chapitre 4 la forme que devait prendre le chier den-tte Personne.h :
// Personne.h : dfinition de la classe Personne #include <string>

126

Apprendre Java et C++ en parallle

class Personne { private: std::string nom; // nom de la personne std::string prenom; // prnom de la personne int annee; // anne de naissance de la personne

et plus loin dans le code associ de Personne.cpp :


// Personne.cpp #include "Personne.h" #include <iostream> #include <sstream> using namespace std; Personne::Personne(string leNom, string lePrenom, string lAnnee) {

Cest la rgle conventionnelle du Standard C++ que nous adopterons : ne jamais utiliser de directive namespace dans un chier den-tte .h. Cest au programmeur, lors de la saisie de son code (cest--dire des chiers .cpp) de dnir son espace de noms. Pour plus dinformations, consultons par exemple le site suivant :
http://www.gotw.ca/publications/migrating_to_namespaces.htm

Fichiers den-tte multiples en C++


Si nous consultons le chier den-tte cstdio de la distribution C++ de GNU, qui se trouve dans le rpertoire include\g++, nous pourrions y dcouvrir ceci :
// The -*- C++ -*- standard I/O header. // This file is part of the GNU ANSI C++ Library. #ifndef __CSTDIO__ #define __CSTDIO__ #include <stdio.h> #endif

Le fait que nous retrouvions le chier <stdio.h> nest pas vraiment surprenant, puisque nous aimerions rutiliser ces bonnes vieilles fonctions C. Ce qui est vraiment nouveau ici, ce sont ces diffrentes directives #ifndef, #define et #endif. Le #ifndef indique que si la dnition __CSTDIO__ existe dj tout le code entre #ifndef et #endif sera ignor. Si notre code possdait les trois directives suivantes :
#include <cstdio> #include <cstdio> #include <cstdio>

cela signierait que <stdio.h> ne serait inclus quune seule fois. Ceci est important, car des rednitions de nom pourraient crer des complications. Certains chiers den-tte

De la mthode dans nos fonctions CHAPITRE 6

127

crent des dnitions qui seront rutilises. Lexemple ci-dessus peut paratre stupide, mais si nous avions ceci :
#include <headera> #include <headerb> #include <headerc>

il pourrait se rvler que les trois <header.> contiennent eux-mmes, chacun, le chier den-tte <cstdio> ! Nous naurons jamais de telles combinaisons dans cet ouvrage, qui ne contient que des exemples et exercices simples. Si nous avions un chier de dnition de classe nomm MaClasse1A.h, nous aimerions conseiller dinclure systmatiquement ces trois directives :
#ifndef __MACLASSE1A__ #define __MACLASSE1A__ // ..... le fichier den-tte #endif

Nous mettons tout en majuscule et ajoutons un __ devant et derrire. la premire apparition du chier den-tte MaClasse1A.h, __MACLASSE1A__ sera dni, et le code ne sera plus ajout nouveau en cas dinclusions multiples. Nous reviendrons sur la compilation conditionnelle au chapitre 9.

Rsum
Dans ce chapitre, nous avons expos les diffrentes manires de passer des arguments des mthodes de classe. Les nombreux exemples donns nous ont sans doute permis de nous y retrouver parmi ces nuances que nous appelons passage par valeur, par rfrence ou encore, en C++, par pointeur. Nous avons aussi rapidement pass en revue le mcanisme du namespace. Le lecteur devrait maintenant possder un meilleur bagage pour dvelopper et tendre ces futures classes.

Exercices
1. De la mme manire que notre module csplice.cpp et lexemple splice2.cpp, crire une fonction mamult() qui nous multiplie deux int et nous retourne le rsultat en double la fois comme retour et comme arguments de fonction (pour les deux cas : par pointeur et par rfrence). Vrier le rsultat et crire cette fois-ci un chier dentte et un module spar mamult.o. Ne pas oublier ce dernier dans le Makefile. 2. En Java, montrer quen passant comme argument un tableau dentiers (type primitif int[ ]) ne contenant quun seul lment il est possible de modier sa valeur (classe ArgChaine). 3. crire, en C++, une classe MonString qui ne possde que trois mthodes publiques qui vont excuter, toutes les trois, le mme travail. Deux string C++ seront passs comme paramtres, une fois par rfrence, une fois par valeur, et enn par pointeur.

128

Apprendre Java et C++ en parallle

Le deuxime string sera ajout au premier, une position dtermine et pour une certaine longueur. Cette longueur est toujours connue. Si la position nest pas dnie, elle se fera la n et sera donc un paramtre par dfaut. En cas derreur quelconque, le premier string est retourn inchang. Comme il y a plusieurs implmentations possibles, expliquer ses choix. Exemple : strings "abcdef" et "12345" avec la position 3 et la longueur 4, rsultat : "abc1234def". 4. Modier le programme namespace3.cpp en dnissant un nouvel espace de noms qui contiendra la fonction printf(). Cette dernire fonction sera appele en lieu et place du printf() actuel, an dinclure le message entre crochets ([...]), message qui sera imprim avec le printf() inclus dans <cstdio>. On se rendra compte que le mixage de cout et de printf() nest pas vraiment bienvenu !

7
Notre code dans des bibliothques
Lune des grandes diffrences entre les langages Java et C++ concerne la manire dont les programmes sont construits et compils. Lorsque nous examinons un programme Java, nous dcouvrons par exemple :
import java.util.Date;

tandis quen C++ nous avons :


#include <iostream>

En Java, ceci implique que des ressources, cest--dire des classes, sont disposition grce la directive import. Le compilateur pourra alors contrler que le programme compiler utilise correctement ces ressources. En C++, cest presque quivalent, bien que la compilation se fasse en deux phases :
g++ -c hello.cpp g++ -o hello.exe hello.o

Dans la premire phase, seul le chier den-tte, ici iostream, est utilis pour le contrle de la syntaxe et la compilation. Ensuite, lors de la seconde tape, le compilateur va lier le chier binaire hello.o avec les bibliothques du systme, dont le compilateur sait, dans le cas prsent, o trouver toutes les ressources. Lors du dveloppement de grands projets informatiques, il se rvle souvent essentiel de partager les tches, an de construire des modules spars qui seront inclus dans des bibliothques. Ces modules pourront aussi tre vris et maintenus sparment, mais

130

Apprendre Java et C++ en parallle

galement rutiliss et distribus pour des produits diffrents. Le code sera donc organis en composants disponibles dans une ou plusieurs bibliothques. Cest ce que nous allons voir dans ce chapitre en prsentant un certain nombre de mcanismes que nous avons en fait dj utiliss implicitement.

Les extensions .jar, .a et .dll


Jusqu prsent, dans cet ouvrage, nous avons rencontr de nombreux chiers comportant les extensions suivantes : .java pour les chiers source Java. .class pour les classes Java, qui sont des compilations avec javac de chiers .java et qui sont directement excutables par la machine virtuelle java, si elle possde un point dentre statique main(). .h pour les chiers den-tte C ou C++ qui contiennent des dnitions de fonctions C ou de classes C++. Nous inclurons aussi les chiers #include sans le .h, par exemple <iostream> qui sont en fait des chiers dont lextension .h a t masque. .cpp pour du code C++, cest--dire le code des programmes ou des classes. .o pour les chiers objets C et C++ compils par la premire phase du compilateur C++ (par exemple le g++) avant quils ne soient lis avec le code des bibliothques pour former des excutables .exe. .exe pour des chiers binaires excutables par le systme dexploitation. Il faut rappeler que lextension .exe nest pas ncessaire sous Linux.
Note Nous aurions pu galement mentionner les extensions .c, utilises pour des chiers source en langage C, ou encore les extensions .C et .H, utilises plus souvent sous Linux en C++ pour les diffrencier des .h et .c.

Nous allons prsent rencontrer dautres catgories de chiers qui sont, par exemple : .jar pour des chiers darchives Java qui sont composs dun ensemble de chiers .class compresss. .a pour des chiers darchives C ou C++ qui pourront tre inclus dans lexcutable .exe durant la deuxime tape de la compilation C ou C++. .dll pour des chiers de bibliothques dynamiques que nous rencontrons sous Windows. Ils sont chargs durant lexcution du programme et font partie de la programmation Windows que nous aborderons au chapitre 21, consacr JNI (Java Native Interface). Nous ne parlerons pas des bibliothques dynamiques sous Linux, qui nont pas lextension .dll.

Notre code dans des bibliothques CHAPITRE 7

131

Les packages en Java


La cration de paquets Java prsente ici nest pas vidente. Pour des projets informatiques dune certaine dimension, il est vident que des outils comme Crimson ou le make ne sont pas appropris. NetBeans, dans lannexe E, le fait trs bien et automatiquement. la n de cette section, nous vous proposerons un rcapitulatif des chiers .bat dans le rpertoire des exemples, an de comprendre les diffrentes tapes de la cration de paquets et des tests associs. la n du chapitre 4, nous avons examin un certain nombre de fonctionnalits nous permettant dtendre notre classe Personne. An de comprendre le mcanisme de cration des packages, nous avons pris lexemple de trois classes : La classe Personne contenant les informations gnrales sur le salari. La classe Salaire avec son montant et la monnaie dans laquelle elle sera verse. La classe ConversionMonnaie, qui calcule le taux de conversion. Cette dernire pourrait se faire, par exemple, par le biais dInternet ou dune base de donnes. Nous allons considrer que les classes Personne et Salaire sont publiques, cest--dire quelles pourraient tre utilises par nimporte quelle application future. La classe ConversionMonnaie tant tout fait particulire, elle ne sera pas ouverte au public ! Cette dernire ne sera donc utilise que par les classes Personne et Salaire. Cest ici que nous introduisons le concept daccs package. Une grande partie du code de la classe Personne ne ncessite aucun claircissement :
package monpaquet; public class Personne { private String nom; private String prenom; private int annee; private Salaire salaire; public Personne(String leNom, String lePrenom, String lAnnee) { nom = leNom; prenom = lePrenom; annee = Integer.parseInt(lAnnee); salaire = new Salaire(0, "Dollar"); } public void setSalaire(int unSalaire, String uneMonnaie) { salaire = new Salaire(unSalaire, uneMonnaie); } public void un_test() { System.out.println("Nom et prenom: " + nom + " " + prenom); System.out.println("Annee de naissance: " + annee); System.out.println("Salaire annuel: " + salaire.salaireAnnuel() + " " + salaire.getMonnaie()); }

132

Apprendre Java et C++ en parallle

public static void main(String[] args) { Personne unePersonne = new Personne("Dupont", "Justin", "1960"); unePersonne.setSalaire(5000, "Euro"); unePersonne.un_test(); ConversionMonnaie uneConversion = new ConversionMonnaie("Euro"); System.out.println("Taux: " + uneConversion.tauxConversion()); } }

si ce nest la premire instruction avec le mot-cl package, qui doit absolument se trouver au dbut du code :
package monpaquet;

Cette instruction indique que la classe Personne fait partie du package nomm monpaquet. La classe Personne utilise aussi une nouvelle classe nomme Salaire que nous allons aussi intgrer dans le mme paquet :
package monpaquet; public class Salaire { private int montant; private String monnaie; private double bonus; public Salaire(int leMontant, String laMonnaie) { montant = leMontant; monnaie = laMonnaie; bonus = 1.10; //10 % de bonus annuel } public int salaireAnnuel() { ConversionMonnaie conversion = new ConversionMonnaie(monnaie); double tauxDeChange = conversion.tauxConversion(); return (int)(12 * tauxDeChange * montant * bonus); } public String getMonnaie() { return monnaie; } }

An de tester nos classes Personne et Salaire, nous avons introduit une mthode salaire Annuel() dans cette dernire classe Salaire. Comme le salaire dni en dollars sera pay dans la monnaie du pays, il nous faudra le calculer en fonction du taux de change. Cest pour cette raison que nous avons introduit en troisime classe notre classe Conversion Monnaie avec sa mthode tauxConversion() :

Notre code dans des bibliothques CHAPITRE 7

133

package monpaquet; class ConversionMonnaie { private String monnaie; public ConversionMonnaie(String laMonnaie) { monnaie = laMonnaie; } public double tauxConversion() { //Accs sur Internet ou une base de donnes pour obtenir le taux // partir du nom de la monnaie. Sera 1 pour le dollar. return 1.2; //valeur de test } }

Elle sera aussi dans notre package monpaquet, mais sans un accs public. En fait, seule la classe Salaire va lutiliser. Lintroduction du bonus dans la classe Salaire nest ici que pour compliquer un peu le jeu, et non pas pour envenimer le climat salarial par des revendications syndicales.

Compiler les classes de notre package


Il sagit prsent de compiler nos classes, ConversionMonnaie, Salaire.java et Personne.java. Si nous les compilions dune manire traditionnelle, nous obtiendrions ce type derreurs :
Salaire.java:15: Class monpaquet.ConversionMonnaie not found. ConversionMonnaie conversion = new ConversionMonnaie(monnaie); Personne.java:7: Class monpaquet.Salaire not found. private Salaire salaire;

En fait, deux points essentiels doivent tre rgls : Ces trois classes compiles, dposes quelque part, doivent faire partie dun rpertoire nomm monpaquet. Le chemin daccs (CLASSPATH) doit tre dni pour le compilateur pour identier o sont les diffrentes classes utilises. Pour ce faire, et pour nous simplier la tche, nous avons intgr ces paramtres dans un
Makefile nomm MakefileMonPaquet :
MCLASSPATH = C:/JavaCpp/EXEMPLES/Chap07/devPaquet MDIR = C:/JavaCpp/EXEMPLES/Chap07/devPaquet all: java: java ConversionMonnaie.class Salaire.class Personne.class

ConversionMonnaie.class: ConversionMonnaie.java

134

Apprendre Java et C++ en parallle

javac -d $(MDIR) -classpath $(MCLASSPATH) ConversionMonnaie.java Salaire.class: Salaire.java javac -d $(MDIR) -classpath $(MCLASSPATH) Salaire.java

Personne.class: Personne.java javac -d $(MDIR) -classpath $(MCLASSPATH) Personne.java

MCLASSPATH est ici une variable de notre chier make qui est utilise pour le classpath (voir ci-dessous). Le MDIR indique le sous-rpertoire o seront stocks nos trois chiers Java compils (.class) :
C:\JavaCpp\EXEMPLES\Chap07\devPaquet\monpaquet

Il faut vraiment se mer des / ou des \. Il faudra parfois sy reprendre plusieurs fois car, suivant les outils utiliss (make ou javac), ils ne fonctionneront pas correctement. Le premier rexe, en cas derreur de compilation, est de les modier et dessayer nouveau. Pour compiler ce Makefile, nous pourrons le faire avec Crimson ou directement :
make -f MakefileMonPaquet

Ce Makefile compilera aussi bien Personne.java que Salaire.java ou encore Conversion Monnaie.java, car ces classes sont interdpendantes, et nous savons dj que javac est assez intelligent pour compiler les trois chiers. Par exemple, pour le chier Salaire.java, le chier Salaire.class sera dpos dans le sous-rpertoire devPaquet/monpaquet : la premire partie venant du d et la seconde du package monpaquet. Salaire.java aura besoin de la classe ConversionMonnaie, dj compile et accessible grce au chemin daccs du classpath (voir ci-dessous). Le Makefile ci-dessus est plus dans la logique C++ de cet ouvrage, car les compilateurs C++ nont pas cette facilit. Il faut noter lutilisation des / dans C:/JavaCpp/EXEMPLES/Chap07, qui viennent dune notation Linux et qui sont ncessaires dans le make. Sous Windows, les deux notations sont acceptes. Pour nir, nous allons tester nos trois classes en revenant dans le rpertoire C:\JavaCpp\ EXEMPLES\Chap07 et crer une classe TestMonPaquet :
import monpaquet; public class TestMonPaquet { public static void main(String[] args) { Personne unePersonne = new Personne("Dupont", "Justin", "1960"); unePersonne.setSalaire(5000, "Euros"); unePersonne.un_test(); Salaire salaire = new Salaire(1000, "Euros"); System.out.println("Salaire annuel: " + salaire.salaireAnnuel() + " " + salaire.getMonnaie()); } }

Notre code dans des bibliothques CHAPITRE 7

135

Le programme compilera et sexcutera correctement seulement en utilisant cette forme (incluse dans le Makefile standard) :
javac -classpath C:/JavaCpp/EXEMPLES/Chap07/devPaquet TestMonPaquet.java "C:\Program Files\Java\jdk1.6.0_06\bin\java.exe" -classpath .;"C:\JavaCpp\EXEMPLES\ Chap07\devPaquet" TestMonPaquet

Avec le rsultat :
Nom et prenom: Dupont Justin Annee de naissance: 1960 Salaire annuel: 79200 Euros Salaire annuel: 15840 Euros

Les classes utilises sont disponibles dans le rpertoire monpaquet du sous-rpertoire devPaquet. Pour le vrier, nous pouvons renommer le rpertoire monpaquet :
C:\JavaCpp\EXEMPLES\Chap07>move monpaquet lepaquet C:\JavaCpp\EXEMPLES\Chap07>java TestMonPaquet java.lang.NoClassDefFoundError: monpaquet/Personne Exception in thread "main"

TestMonPaquet na pas trouv la classe Personne.

La variable denvironnement CLASSPATH


Nous lavons dj rencontre lors de la compilation prcdente en utilisant javac class path ou encore avec :
"C:\Program Files\Java\jdk1.6.0_06\bin\java.exe" -classpath .;"C:\JavaCpp\EXEMPLES\ Chap07\devPaquet" TestMonPaquet

La variable denvironnement CLASSPATH est compose de un ou de plusieurs chemins daccs, spars par un point-virgule (;) ou le caractre deux-points (:), suivant les systmes dexploitation, pour identier les classes utilises par lapplication. Le caractre ; doit tre utilis sous Windows et le caractre : sous Linux. Le point (.) nindique pas la n de la phrase ou de la ligne, mais le rpertoire courant. Nous donnerons un exemple pour lexcution (java.exe). Dans le cas de la classe TestMon Paquet, nous savons dj que le main() doit tre prsent dans le chier TestMonPaquet.java. Ce nest pas le cas ici, mais cette classe de test pourrait trs bien utiliser dautres classes prsentes dans ce mme rpertoire (par le caractre .). Les autres classes qui sont ncessaires lors de lexcution seront retrouves dans le sous-rpertoire devPaquet\monpaquet (devPaquet au travers du classpath et monpaquet au travers de limport).

136

Apprendre Java et C++ en parallle

Nous aurions trs bien pu dnir un sous-ensemble p1 :


import monpaquet.p1;

Il aurait alors fallu dnir un :


package monpaquet.p1;

en sassurant que tout tait construit correctement dans un rpertoire :


C:/JavaCpp/EXEMPLES/Chap07/devPaquet/monpaquet/p1

Nos classes dans un chier darchive .jar


Nous pouvons prsent introduire nos trois classes dans un chier darchive .jar de la manire dnie dans le chier devJar.bat dans le rpertoire devPaquet :
jar cf monpaquet.jar monpaquet/*.class copy monpaquet.jar .. pause

jar.exe est un outil distribu avec le JDK de Java qui permet de compresser des classes Java compiles dans une archive.

Signer et vrier un chier .jar


Nous signalons au passage que les chiers .jar peuvent tre signs des ns de vrication. Si nous excutons la commande :
jarsigner -verify monpaquet.jar

nous obtiendrons :
jar is unsigned. (signatures missing or not parsable)

car notre chier monpaquet.jar ne contient pas de signature. jarsigner est un outil disponible avec la distribution de Java de Sun Microsystems. keytool, un autre outil de cette mme distribution, permet de crer une cl des ns dauthentication scurise. Nous nen dirons pas plus sur ce sujet.

Test avec le chier monpaquet.jar


Nous avons copi ci-dessus, avec devJar.bat, le paquet monpaquet.jar. Il est maintenant possible de procder diffremment (TestMonPaquetJar.bat) :
"C:\Program Files\Java\jdk1.6.0_06\bin\java.exe" -classpath .;monpaquet.jar TestMonPaquet

Notre code dans des bibliothques CHAPITRE 7

137

Les classes Personne et Salaire utilises par TestMonPaquet sont incluses dans larchive monpaquet.jar. Un chier .jar peut tre ouvert avec WinZip ou 7-Zip (voir annexe B) :

Figure 7-1

Visualisation du contenu dun chier .jar avec 7-Zip

Rsum des diffrentes tapes avec les chiers .bat


En examinant les chiers .bat disponibles dans le rpertoire EXEMPLES\Chap07, nous comprendrons mieux les tapes de la construction dun paquet Java (package) : efface.bat (voir gure 7-2) Efface tous les chiers dobjets crs par le make. makefile.bat (voir gure 7-3) Effectue les trois tapes suivantes la suite : make -f MakefileMonPaquet make -f Makefile devJar.bat (dans le rpertoire devPaquet) MakeFileMonPaquet Makefile pour gnrer les trois classes Java du paquet dans le rpertoire devPaquet. Makefile Le traditionnel make pour ce chapitre, mais sans la gnration du paquet Java. devJar.bat (voir gure 7-4) Cration du .jar qui sera utilis pour excuter TestMon PaquetJar.bat. TestMonPaquetJar.bat (voir gure 7-5) Test de la classe TestMonPaquet avec le paquet monpaquet.jar.

138

Apprendre Java et C++ en parallle

Nous savons quil est possible dexcuter un chier .bat dans Crimson. La gure 7-2 prsente lexcution du chier efface.bat :

Figure 7-2

Effacement de tous les objets dun rpertoire dexemple

Notre code dans des bibliothques CHAPITRE 7

139

Le Makefile.bat gnre toutes les tapes de compilation du chapitre (si tous les objets ont t pralablement effacs) :

Figure 7-3

Recompilation du chapitre entier

Ici, il faudra entrer un Retour dans la fentre des rsultats, pour terminer le processus cause du PAUSE dans le chier .bat.

140

Apprendre Java et C++ en parallle

Le chier suivant, devJar.bat, se trouve dans le rpertoire devPaquet et gnre le chier monpaquet.jar en le copiant lendroit correct :

Figure 7-4

Cration du chier monpaquet.jar

Finalement, nous naurons pas de surprise avec TestMonPaquetJar.bat :

Figure 7-5

Le rsultat nal attendu

Notre code dans des bibliothques CHAPITRE 7

141

Les constructions de bibliothques C et C++


Pour illustrer la cration de bibliothques en C ou C++, nous aurions pu choisir le mme exemple quen Java, mais cela ne nous aurait rien apport de plus. Nous devons en effet traiter le cas dune fonction C sparment, et notre classe ConversionMonnaie avec un accs package na en fait aucune quivalence en C++. Nous allons montrer ici le mcanisme de la construction de bibliothques statiques en C++. Par statique, nous voulons dire que, lors de la compilation, des bibliothques extrieures seront ajoutes au programme inclus dans le chier binaire et non pas charges pendant lexcution comme nous le ferions avec des chiers DLL.

Cration dune bibliothque statique en C++


Pour ce faire, nous allons crer une fonction C et une classe C++, crer une bibliothque pour ces deux dernires et les rutiliser dans un programme de test. Nous allons commencer par la dnition de la fonction C et de la classe C++ dans deux chiers den-tte spars :
// cfunction.h extern int cfonction(int, int);

et :
// Cppclass.h class Cppclass { public: int fonction(int num1, int num2); };

Ces chiers den-tte sont essentiels car ils seront rutilisables par nimporte quel programmeur qui voudra rutiliser ces classes. Nous remarquerons incidemment que, dans cfunction.h, le nom des variables nest en fait pas ncessaire. Nous passons prsent limplmentation du code :
// cfonction.cpp #include "cfonction.h" int cfonction(int num1, int num2) { return num1 * num2; }

et :
// Cppclass.cpp #include "Cppclass.h" int Cppclass::fonction(int num1, int num2) { return num1 + num2; }

142

Apprendre Java et C++ en parallle

Ces deux petits modules correspondent en fait deux fonctions qui multiplient et qui additionnent deux nombres. La compilation de ces deux morceaux, cfonction.o et Cppclass.o, ne va pas causer de difcults, car les quatre chiers se trouvent dans le mme rpertoire. Il faut noter lutilisation des "" pour les chiers den-tte, au lieu des <> classiques. Ceci indique quils sont prsents sur le rpertoire courant. Sinon, il faudra utiliser le paramtre I lors de la compilation. Nous verrons les dtails en n de chapitre. Dans le Makefile nous aurons :
cfonction.o: cfonction.h cfonction.cpp g++ -c cfonction.cpp Cppclass.h Cppclass.cpp g++ -c Cppclass.cpp

Cppclass.o:

Il sagit prsent de crer une bibliothque qui va se faire ainsi :


libfonctions.a: cfonction.o Cppclass.o ar -q libfonctions.a cfonction.o Cppclass.o

ar veut dire archive et q quick (archive rapide). Le nom de larchive doit commencer par lib et avoir lextension .a. Nous en comprendrons rapidement les raisons. Nous aurons donc un chier darchives libfonctions.a compos de cfonction.o et de Cppclass.o. Il est ensuite possible dutiliser nouveau ar avec le paramtre t :
ar -t libfonctions.a

pour vrier que notre bibliothque contient bien notre nouvelle fonction C cfonction() et notre classe Cppclass.

Utilisation de notre bibliothque C++


An dutiliser cette bibliothque, nous avons volontairement cr un sous-rpertoire TestFonctions an de vrier laccs aux chiers den-tte et la nouvelle bibliothque. Nous allons crire un petit programme dans le sous-rpertoire TestFonctions :
// TestFonctions.cpp #include <iostream> #include <cfonction.h> #include <cppclass.h> using namespace std; int main() { cout << cfonction(2,10) << endl; Cppclass ma_classe; cout << ma_classe.fonction(2,10) << endl; }

qui va pouvoir nous permettre de vrier nos deux fonctions et leurs inclusions dans le chier binaire TestFonctions.exe. Nous percevons immdiatement la difcult que vont rencontrer nos deux chiers den-tte cfonction.h et cppclass.h. Le compilateur doit non

Notre code dans des bibliothques CHAPITRE 7

143

seulement trouver la dnition de la fonction cfonction() et de la classe Cppclass, mais aussi ajouter le code de notre bibliothque lors de la phase nale de la compilation. Cela va se faire de cette manire dans un Makefile du sous-rpertoire TestFonctions :
INC = C:/JavaCpp/EXEMPLES/Chap07 LIB = C:/JavaCpp/EXEMPLES/Chap07 all: TestFonctions.exe

TestFonctions.exe: TestFonctions.o g++ -I$(INC) -L$(LIB) -o TestFonctions.exe TestFonctions.o lfonctions TestFonctions.o: TestFonctions.cpp g++ -I$(INC) -c TestFonctions.cpp

Ce Makefile dbute par la dnition de deux variables, INC et LIB, et il faut noter la syntaxe pour y accder : $(INC) et $(LIB). Nous voyons apparatre deux nouvelles options de compilation : -I. Dnit le ou les chemins daccs o le compilateur va rechercher les chiers den-tte lors de la compilation du code source. Dans cet ouvrage, nous avons toujours utilis des chiers den-tte qui se trouvaient sur le rpertoire de travail ou qui taient connus par le compilateur (par exemple, <iostream> et <string>). Dans ces deux cas, le -I nest pas ncessaire, et un -I. serait redondant (. pour le rpertoire courant). -L. Dnit le ou les chemins daccs o le compilateur va rechercher la ou les bibliothques spcies par le paramtre -l. Ces bibliothques doivent tre lies avec les autres composants pour construire le chier excutable. La classe string, par exemple, fait partie de la bibliothque du Standard C++, dont il nest pas ncessaire de donner le chemin daccs avec -L, car le compilateur C++ sait la retrouver. La dclaration -lfonctions, dans la deuxime tape de la compilation, indique que le nom de la bibliothque est en fait libfonctions.a. Cest une convention, lib et .a tant en fait rajouts par le compilateur. Les deux variables INC et LIB doivent tre videmment adaptes suivant linstallation sur le disque dur. Si plusieurs chemins daccs sont ncessaires pour les chiers den-tte ou les bibliothques, il est possible de les chaner. Si nous crivions :
g++ -o application.exe -Ichemin1 -Ichemin2 -Lchemin4 -Lchemin3 application.cpp -leux leui

ceci indiquerait, sans doute, que certains en-ttes utiliss dans application.cpp sont dnis dans chemin1 et chemin2 et que les bibliothques libeux.a et libeui.a sont disponibles dans les chemin3 et chemin4. Il peut aussi arriver que le code dapplication.cpp nutilise que des classes et fonctions dnies dans la bibliothque libeux.a. Cela signierait alors que le code de libeux.a ncessite dautres fonctions disponibles dans libeui.a. Ce genre de dpendance devrait tre dni dans la documentation de lAPI. Nous verrons au chapitre 21 comment construire une bibliothque dll (Windows).

144

Apprendre Java et C++ en parallle

Rsum
Les extensions de chier .jar en Java,.a et .dll en C/C++ nous indiquent la prsence de bibliothques. Nous avons appris dans ce chapitre aussi bien les construire qu y accder. En Java, le chemin daccs doit tre spci avec CLASSPATH.

Exercices
1. Crer, en Java, une classe Banque qui conserve notre fortune et retourne la valeur du compte aprs un certain nombre dannes et un taux donn. Crer une classe Magasin, qui possde elle-mme un compte pouvant tre aliment soit par le client directement (prpaiement) soit par la banque, condition que les achats dpassent ltat du compte. Introduire ces deux classes, dignes du commerce lectronique, dans un package monpaquet et dans un chier .jar. crire le Makefile, les procdures habituelles et un programme de test. 2. Reprendre la classe Banque ci-dessus, la convertir en une classe C++, et larchiver dans une bibliothque statique. Puis crire un programme de test. Ne pas introduire les commandes de compilation dans un Makefile, mais les excuter la main, en donnant une brve explication pour chaque tape.

8
quelques exceptions prs
Une division par zro devrait se rencontrer de manire exceptionnelle dans lexcution dun programme. De mme, un accs dans un tableau en dehors des limites avec un index ngatif ou plus grand que la dimension alloue doit tre tout aussi exceptionnel et considr plutt comme une erreur de programmation. Quant la lecture dun chier qui nexiste pas, cela fait partie de ces domaines de la conception des logiciels pour lesquels les explications sont multiples. Loprateur a entr un nom de chier incorrect ou bien a essay daccder des ressources du systme qui ont disparu. Certains cas sont invraisemblables, et la seule alternative est de rinstaller un logiciel ou le systme dexploitation en entier ! La difcult dans cet ouvrage est de maintenir un paralllisme entre les langages C++ et Java, an de couvrir les sujets ncessaires notre apprentissage. Le traitement des exceptions na t introduit que tardivement en C++, dans des versions rcentes, alors quil fait partie intgrante des concepts de base du langage Java. Il ny a, par exemple, aucune exception gnre par les fonctions de la bibliothque C, partie intgrante du langage C++. En C++, que nous utilisions des fonctions C ou des mthodes de classe de la bibliothque iostream des entres-sorties, le traitement se fait en gnral en utilisant les valeurs de retour de ces mmes fonctions ou mthodes. linverse, comme nous le verrons dans le chapitre suivant sur les entres et sorties, une simple ouverture dun chier qui nexiste pas se traite en Java au moyen dune exception.

Au contraire du C++, Java est n avec les exceptions


Dans les chapitres prcdents, nous avons dj rencontr un certain nombre dexceptions qui font partie des toutes premires erreurs commises par un programmeur dbutant en Java. Des erreurs de compilation lui indiquent quil faut traiter correctement les exceptions de

146

Apprendre Java et C++ en parallle

la mme manire que, par exemple, le passage de paramtres pour les mthodes. Lors des premiers tests en Java, dautres exceptions nous dvoilent quil faut tendre notre code pour vrier ces cas particuliers. Ce programme en Java :
public class Exception1 { public static void main(String[] args) { System.out.println(args[0].charAt(1)); } }

est videmment incomplet. Il essaie daccder au deuxime caractre du premier paramtre pass au programme et peut produire deux types derreurs : 1. ArrayIndexOutOfBoundsException dans le cas o aucun paramtre nest pass au programme ; 2. StringIndexOutOfBoundsException dans le cas o uniquement une lettre serait entre comme premier paramtre. Nous constatons que cette manire de procder est propre et quelle oblige le programmeur introduire le code ncessaire pour tester non seulement lexistence dventuels paramtres, mais aussi du contenu, comme sa dimension dans ce cas prcis. Nous verrons plus loin les diffrentes possibilits offertes au programmeur. En revanche, dans cette version C++, il ny aura pas dexception gnre :
#include <iostream> using namespace std; int main(int argc, char **argv) { cout << argv[1][1] << endl; }

Le programme soit sortira nimporte quel caractre, soit produira une opration non conforme et sarrtera brutalement : cela dpendra des compilateurs.

Utilisation des exceptions en Java


Dans cet ouvrage, nous ne traiterons que les exceptions de Java qui hritent de la classe Exception, qui elle-mme est une sous-classe de Throwable. La classe Exception possde deux branches principales (sous-classes) : 1. RuntimeException, qui traite en principe des problmes lis la programmation. Cest le cas de nos ArrayIndexOutOfBoundsException et StringIndexOutOfBoundsException cidessus ou encore lors de lemploi dun pointeur null. 2. Les autres exceptions comme IOException, lorsque nous essayons de lire un chier inexistant sur le disque.

quelques exceptions prs CHAPITRE 8

147

Il est difcile dtablir des rgles prcises, mais, globalement, nous dirons que dans le cas de RuntimeException le code devrait tre adapt pour que ce type derreur napparaisse jamais. Durant la phase de dveloppement, le programmeur devrait sassurer, en excutant des tests adquats, que tous les cas limites ont t vris. Pour ces derniers, le code devrait tre adapt judicieusement. Le code que nous avons vu prcdemment ne produira plus derreur si nous lcrivons ainsi :
public class Exception1 { public static void main(String[] args) { if ((args.length > 0) && (args[0].length() > 1)) { System.out.println(args[0].charAt(1)); } } }

Pour les autres exceptions, par exemple pour un chier manquant, lexception doit tre capture et le code ncessaire pour ce cas particulier doit tre introduit.

Capture des exceptions


Gnralement, un programmeur dbutant en Java est surpris par lapparition de ces erreurs de compilation mentionnant le traitement des exceptions. Au chapitre 2, nous avions introduit lexemple suivant, que nous avons ici volontairement raccourci et modi :
import java.io.*; public class Cin1 { public static void main(String[] args) { int nombre = 0; BufferedReader stdin = new BufferedReader(newInputStreamReader(System.in)); System.out.print("Entre un nombre: "); nombre = Integer.parseInt(stdin.readLine()); System.out.println("\nLe nombre: " + nombre); } }

Si nous compilons ce code, nous obtiendrons ceci :


Cin1.java:10: Exception java.io.IOException must be caught, or it must be declared in the throws clause of this method. nombre = Integer.parseInt(stdin.readLine()); ^ 1 error

Pour comprendre la raison de cette erreur de compilation, il nous faut regarder la dnition de readline() dans la classe BufferedReader :
public String readLine() throws IOException

148

Apprendre Java et C++ en parallle

Celle-ci nous indique quune IOException peut tre retourne et que le programmeur doit absolument la prendre en considration. Le code correct peut tre crit ainsi :
import java.io.*; public class Cin2 { public static void main(String[] args) { int nombre = 0; BufferedReader stdin = new BufferedReader(new InputStreamReader(System.in)); System.out.print("Entre un nombre: "); try { nombre = Integer.parseInt(stdin.readLine()); } catch(IOException ioe) { nombre = -1; } System.out.println("\nLe nombre: " + nombre); } }

Nous rencontrons ici la squence du try et du catch(). Celle-ci signie que si lune des exceptions de la classe IOException se produit dans la mthode readline(), alors le code de la partie catch() sera excut. Le lecteur peut consulter, dans lAPI du JDK de Sun Microsystems, les nombreuses exceptions de la classe IOException et constater quune erreur dans le domaine de lentre la console avec la mthode readline() est plutt du domaine de linvraisemblable et que linstruction :
catch(IOException ioe) { nombre = -1; }

nous donnera un rsultat de 1, ce qui nous fait penser un style de retour en C ou C++. Nous pourrions cependant laisser la variable nombre 0. Ce serait sans doute plus raisonnable car le programme accepte aussi des entres ngatives. Nous entrerons dailleurs dans ces considrations, que nous pourrions presque considrer comme philosophiques, dans les prochaines pages.

Ignorer les exceptions


Il est tout fait acceptable, dans le cas prcdent, de vouloir ignorer les exceptions en les laissant passer au niveau suprieur. En revanche, ce ne serait pas le cas lors de la lecture dun chier auquel le programmeur sattendrait pouvoir accder. Il faudrait alors traiter lerreur avec du logiciel appropri pour contourner ce cas prcis ou lindiquer lextrieur. Le code suivant :
import java.io.*;

quelques exceptions prs CHAPITRE 8

149

public class Cin3 { public static void main(String[] args) throws IOException { int nombre = 0; BufferedReader stdin = new BufferedReader(new InputStreamReader(System.in)); System.out.print("Entre un nombre: "); nombre = Integer.parseInt(stdin.readLine()); System.out.println("\nLe nombre: " + nombre); } }

va compiler et fonctionner normalement. En cas derreur, tout fait improbable, nous aurions un message directement sur la console. Cependant, la version try catch(), pour ce cas prcis, est sans doute plus judicieuse. prsent, continuons notre petit jeu avec ceci :
Entre un nombre: oo Exception in thread "main" java.lang.NumberFormatException: oo at java.lang.Integer.parseInt(Compiled Code) at java.lang.Integer.parseInt(Compiled Code) at Cin3.main(Compiled Code)

Un nouveau problme se pose, bien plus grave que le prcdent. Nous nentrons pas des chiffres, mais des lettres ! Un vrai jeu tlvis ! Nous devons aussi constater que la mme erreur se produira si nous ajoutons des lettres aprs des chiffres. Un programmeur connat vraisemblablement la fonction atoi(), qui, elle, accepte aussi des lettres en n de chane de caractres et qui peut tre parfois bien pratique. Ici, nous navons aucune chance, car la mthode Java Integer.parseInt() est trs restrictive. Le lecteur doit en fait se mer et sy reprendre deux fois, car la ligne de code :
nombre = Integer.parseInt(stdin.readLine());

peut gnrer deux exceptions, une sur le BufferedReader.readline() et une sur le Integer .parseInt(). Nous avons dj examin la premire, et nous pouvons consulter la documentation de lAPI de Java pour la seconde et dcouvrir cette dnition :
public static int parseInt(String s) throws NumberFormatException

cest--dire aussi avec un throws. Cependant, le compilateur nous a tout de mme permis de compiler ce code, au contraire du cas prcdent avec IOException. La raison en est simple : la classe NumberFormatException hrite de RuntimeException, qui na pas besoin dtre captur. Le problme ici est donc clair : notre exception de la classe RuntimeException est en fait plus importante nos yeux que lIOException dune entre la console, erreur tout fait improbable. Bien que la squence try et catch() pour notre NumberFormatException ne soit pas requise par le compilateur, nous allons tout de mme lintroduire pour traiter notre cas derreur, tout fait probable ici. Voici donc notre code :

150

Apprendre Java et C++ en parallle

import java.io.*; public class Cin4 { public static void main(String[] args) throws IOException { int nombre = 0; BufferedReader stdin = new BufferedReader(new InputStreamReader(System.in)); System.out.print("Entre un nombre: "); try { nombre = Integer.parseInt(stdin.readLine()); System.out.println("\nLe nombre: " + nombre); } catch (NumberFormatException nfe) { System.out.println("\nL'entree ne contient pas que des chiffres !"); } } }

que nous pourrions modier, en guise dexercice, an de demander lutilisateur, en cas derreur, de retaper son entre avec des chiffres seulement. Une autre variante pourrait consister analyser et trier le String au retour de stdin.readLine() an quil ne possde que des chiffres lentre dInteger.parseInt. Une construction telle que :
catch (RuntimeException nfe) { System.out.println(nfe); }

est tout fait possible pour identier quelle sous-classe de RuntimeException doit tre capture avant de terminer le code.

Plusieurs exceptions en une seule fois


Reprenons le mme exercice, mais cette fois-ci en produisant une division de 1 000 sur le chiffre entr la console. Il faudra donc considrer, en plus des entres invalides, la division par zro, do les deux catch() la suite :
import java.io.*; public class Cin5 { public static void main(String[] args) throws IOException { int nombre = 0; BufferedReader stdin = new BufferedReader(new InputStreamReader(System.in)); System.out.print("Entre un nombre: "); try { nombre = 1000/Integer.parseInt(stdin.readLine()); System.out.println("\nLe nombre: " + nombre);

quelques exceptions prs CHAPITRE 8

151

} catch (NumberFormatException nfe) { System.out.println("\nL'entree ne contient pas que des chiffres !"); } catch (ArithmeticException ae) { System.out.println("\nL'entree ne peut tre 0 !"); } } }

La division par zro est traite par la machine virtuelle Java laide de lexception ArithmeticException. En gnral, il nest ni conseill ni logique de capturer une exception hritant de Runtime Exception, car nous avons en principe toujours des solutions ces types de traitements derreur, qui doivent tre analyss pendant la phase de conception du programme et de ses classes. Dans le cas prcis ci-dessus, le programmeur devrait dcider de lui-mme, avant de laisser la machine virtuelle gnrer un ArithmeticException. Un code tel que :
int entree = Integer.parseInt(stdin.readLine()); if (entree == 0 ) { //quelque chose } nombre = 1000/entree;

pourrait remplacer la squence try et catch() de notre ArithmeticException. Lors de lanalyse de ce code, nous pourrions dcouvrir quil nest pas ncessaire dutiliser des entiers, car nous aimerions peut-tre dessiner un graphique o nous aurions alors besoin de prcision. En utilisant le double, il faudrait modier deux lignes de ce code :
double nombre = 0; nombre = 1000/Double.parseDouble(stdin.readLine());

et le rsultat serait :
Entre un nombre: 0 Le nombre: Infinity

Nous navons donc plus le problme de lexception ArithmeticException.

Lancement dune exception


Si nous voulons nous-mmes, dans notre code, gnrer une exception, deux possibilits nous sont offertes : soit lancer une exception partir dune classe existante, soit en crer une nouvelle comme nous le verrons ci-dessous. Mais pourquoi ne pas reprendre notre exercice prcdent en ladaptant nos besoins ? Nous aimerions dnir une mthode qui nous retourne un int avec la valeur entre la console et qui nous gnre une exception EOFException lorsque nimporte quelle erreur se

152

Apprendre Java et C++ en parallle

produit. EOF (End Of File) identie en fait la n du chier, ce qui est plus ou moins raisonnable pour notre cas. Avant de coder la mthode proprement dite, il est tout fait possible de dnir ces paramtres :
public int recoitEntree() throws EOFException { }

Cela nous indique que lutilisateur de cette fonction devra soit inclure une squence de try et catch() pour lexception EOFException, soit propager plus loin cette exception, comme nous lavons vu prcdemment. Mais passons maintenant au code :
import java.io.*; public class Cin6 { public static void main(String[] args) { Cin6 monCin = new Cin6(); for (;;) { try { System.out.print("Entre un nombre: "); System.out.println("Le nombre: " + monCin.recoitEntree()); } catch (EOFException eofe) { break; } } System.out.print("Cin6 termine correctement"); } public int recoitEntree() throws EOFException { int nombre = 0; BufferedReader stdin = new BufferedReader(new InputStreamReader(System.in)); for (;;) { try { return Integer.parseInt(stdin.readLine()); } catch (NumberFormatException nfe) { throw new EOFException(); } catch (IOException ioe) { throw new EOFException(); } } } }

Et nous constatons une nouvelle forme dinstruction :


throw new EOFException();

quelques exceptions prs CHAPITRE 8

153

Cette instruction va nous lancer une exception. throw accepte un objet de la classe EOFException, qui hrite elle-mme dIOException, dException et, enn, tout en bas, de la base des exceptions, Throwable. En dautres termes, throw fonctionnera sur un Throwable. Pour illustrer lutilisation de throw, nous avons crit une mthode spare et remani le code de manire signicative. Nous constatons que les deux exceptions possibles, IOException et NumberFormatException, sont en fait converties en une seule, EOFException. Enn, la mthode recoitEntree() ne contient pas de System.out.

Recommandation pour lcriture de mthodes rutilisables


Le programmeur doit toujours simaginer lutilisation de ces mthodes dans diffrents contextes ou interfaces. Des instructions telles que System.out, qui assume le fait quune console est attache lapplication, devraient se trouver lextrieur. Dans un systme, il y a beaucoup de processus de contrle qui travaillent en arrire-plan.

Retour avec -1 comme en C ou C++


Un programmeur C++ qui programme rgulirement ces fonctions C ou mthodes C++ fera sans doute cette remarque : nous pourrions retourner un -1 et ne pas lancer lexception. En effet, ce peut tre un dbat intressant, mais difcile. Il nous apparat cependant important dindiquer quil faut avant tout rechercher la simplicit. Le traitement des erreurs peut se rvler dlicat, et des raccourcis sont parfois ncessaires pour viter soit trop de code (plus il y a de code, plus il y a de test, et donc besoin de temps mais aussi de risques derreurs supplmentaires) soit du code qui ralentit considrablement lapplication. Mais revenons au -1, qui dans notre cas est possible et fonctionne ! Si nous devions retourner un String, nous pourrions dnir un null comme retour ou une chane vide. Si nous voulions absolument retourner un -1, le String devrait alors tre pass en argument. Mais un String nest pas mutable, et nous devrions retourner un StringBuffer. Pourquoi pas ! Il nous semble tout de mme que notre squence try catch() reste simple et lgante !

Cration de nouvelles exceptions


Pour traiter le cas prcdent, nous allons nous amuser un peu en crant une classe exceptionnelle , car nous navons pas t heureux avec le nom prcdemment choisi, notre EOFException ! Voil pour le cadre. Pour la dnition, nous choisirons une exception nomme FindEntreeException. Comme nous le remarquons, il est dusage dajouter Exception dans le nom ( la n) de notre nouvelle classe. Nous navons pas encore trait de lhritage des classes, mais ce nest pas bien complexe et il ny aura pas besoin de faire semblant de comprendre !

154

Apprendre Java et C++ en parallle

Voici donc notre nouveau code, similaire au prcdent :


import java.io.*; class FindEntreeException extends IOException { public FindEntreeException() {} public FindEntreeException(String message) { super(message); } } public class Cin7 { public static void main(String[] args) { Cin7 monCin = new Cin7(); for (;;) { try { System.out.print("Entre un nombre: "); System.out.println("Le nombre: " + monCin.recoitEntree()); } catch (FindEntreeException fe) { System.out.println(fe); break; } } System.out.println("Cin6 termine correctement"); } public int recoitEntree() throws FindEntreeException { int nombre = 0; BufferedReader stdin = new BufferedReader(new InputStreamReader(System.in)); for (;;) { try { return Integer.parseInt(stdin.readLine()); } catch (NumberFormatException nfe) { throw new FindEntreeException("Erreur1"); } catch (IOException ioe) { throw new FindEntreeException("Erreur2"); } } } }

Notre nouvelle classe dexception FindEntreeException hrite (extends) donc dIOException. Cest une bonne pratique dajouter un deuxime constructeur, car nous pourrons alors dnir un message pour une meilleure comprhension de lexception et de son origine. La mthode super(message) est ncessaire car elle permet dappeler la classe de base (ou

quelques exceptions prs CHAPITRE 8

155

une prcdente) an de sauver le message, cest--dire un attribut qui sera rutilis lors dun ventuel System.out.println(fe);. Nous allons constater que le message Erreur1 est ainsi correctement transport. Si nous excutons le code, nous aurons par exemple :
java Cin7 Entre un nombre: 101 Le nombre: 101 Entre un nombre: a FindEntreeException: Erreur1 Cin7 termine correctement

Nettoyage laide de nally


Il peut parfois tre ncessaire de nettoyer certaines ressources, car nous navons pas en Java de destructeur comme en C++. Dans une squence try catch(), un des deux blocs sera excut. Il nous faut donc un mcanisme permettant dexcuter ce travail qui peut se rvler essentiel comme lors de lutilisation des classes AWT (interface utilisateur). Ce mcanisme se fait laide de linstruction (appele gnralement clause) finally. En voici les dtails dans un exemple :
public class Finalement { public static void main(String[] args) { Finalement monFin = new Finalement(); monFin.methode1(); monFin.methode2(); System.out.print("Finalement termine correctement"); } public void methode1() { try { Integer.parseInt("oooooh"); } catch (NumberFormatException nfe) { System.out.println("Finalement1: erreur capture"); } finally { System.out.println("Finalement1"); } } public void methode2() throws NumberFormatException { try { Integer.parseInt("aaaaah"); } finally { System.out.println("Finalement2");

156

Apprendre Java et C++ en parallle

} } }

Et son rsultat :
Finalement1: erreur capture Finalement1 Finalement2 Exception in thread "main" java.lang.NumberFormatException: aaaaah at java.lang.Integer.parseInt(Compiled Code) at java.lang.Integer.parseInt(Compiled Code) at Finalement .methode2(Compiled Code) at Finalement.main(Compiled Code)

Ce rsultat peut paratre trange premire vue, mais tout fait correct et attendu. Les deux instructions :
Integer.parseInt("oooooh"); nteger.parseInt("aaaaah");

vont simplement crer un NumberFormatException, et aucun retour de paramtre nest ncessaire pour ce petit exercice expliquant la clause finally.
Integer.parseInt() va donc produire une erreur que nous capturons. Ensuite, le code de la clause est aussi excut. Cela explique donc nos deux messages Finalement1 produits par la methode1().

Pour la methode2(), cest diffrent, car nous avons dni un throws :


public void methode2() throws NumberFormatException {

Ceci va relancer lexception au niveau suprieur. La forme du try dans la methode2() nest l en fait que pour activer le code du finally. Le message de lexception aaaaah sur la console vient du fait que nous avons ignor le NumberFormatException dans le main(). Le message "Finalement termine correctement" napparat donc pas.

Utilisation des exceptions en C++


Le traitement des exceptions dans le langage C++ ne faisait pas partie des premires versions des compilateurs. Ce nest que plus tard quil est apparu, alors que de son ct la bibliothque iostream (les entres-sorties) avait dj subi quelques modications, mais toujours sans utilisation des exceptions. En Java, il nest pas possible dignorer le mcanisme des exceptions lors dune lecture de chier, alors quen C++ un programmeur pourra mme coder toute sa vie en ignorant les exceptions. Dans cet ouvrage, nous nallons donc pas trop nous tendre sur ce sujet, en particulier sur la hirarchie de la classe exception dans la bibliothque du Standard C++, car il pourrait tre considr comme un sujet part entire. Nous allons simplement en expliquer le

quelques exceptions prs CHAPITRE 8

157

mcanisme par quelques exemples. Notre travail sera dailleurs facilit par la partie Java prsente ci-dessus, condition que le lecteur nait pas saut ce passage la manire dun infme goto! Un programmeur Java qui matrise et utilise correctement les exceptions pourrait sans aucun doute adopter la mme approche pour le dveloppement de ces classes et applications C++. Cest un peu le but que lauteur sest x dans cette partie.

Un exemple sans exceptions


linverse de notre prsentation Java, nous serons ici plus directs et dbuterons par un exemple plus concret, celui dun distributeur automatique de billets, appel Bancomat. Lors du retrait, nous allons considrer deux cas derreurs particuliers : il ny a plus assez de billets dans lappareil, et il ny a plus assez dargent sur le compte. Dans les deux cas, mme si lappareil pouvait retourner quelques billets, rien ne serait dlivr par le Bancomat. Nous pourrions imaginer la classe suivante :
#include <iostream> #include <string> using namespace std; class Bancomat1 { private: string utilisateur; int soldem; // solde de lappareil int soldeutil; // solde de lutilisateur public: void set_solde_machine(int lasoldem) { soldem = lasoldem; } void set_utilisateur(const string lutilisateur) { utilisateur = lutilisateur; } void set_solde_utilisateur(int lasoldeutil) { soldeutil = lasoldeutil; } bool retrait_possible(int nb_billets); }; bool Bancomat1::retrait_possible(int nb_billets) { if (((soldeutil - (100*nb_billets)) < 0) || ((soldem - (100*nb_billets)) < 0)) return false; return true; }

158

Apprendre Java et C++ en parallle

int main() { Bancomat1 labanque; labanque.set_solde_machine(1000); labanque.set_utilisateur("Tintin"); labanque.set_solde_utilisateur(500); cout << "Test1: " << labanque.retrait_possible(4) << endl; cout << "Test2: " << labanque.retrait_possible(6) << endl; cout << "Test3: " << labanque.retrait_possible(11) << endl; }

Toutes les mthodes inline que nous avons introduites devraient en fait tre relies lappareil pour obtenir les donnes, et lextrieur pour vrier ventuellement le compte de lutilisateur. Lidentication de celui-ci ne sera pas utilise dans nos exemples. Cependant, nous lavons tout de mme gard pour montrer que si une extension de cet exercice tait apporte, il deviendrait lun des objets essentiels dans la conception du programme complet. Dans cette version, la mthode :
bool retrait_possible(int nb_billets);

nous retournerait simplement un vrai ou faux :


Test1: 1 Test2: 0 Test3: 0

o il ny a pas de possibilits de diffrencier lerreur. Il faudrait alors retourner par exemple un -1 ou -2 pour chaque cas.

Un exemple avec exceptions


Nous allons donc reprendre lexemple prcdent, mais sans passer par les diffrentes phases que nous avons rencontres en Java, et donner une solution qui pourrait presque tre considre comme dnitive. Dans la premire partie de ce code apparaissent deux nouvelles classes, SoldeUtilisateurException et SoldeMachineException, que nous allons utiliser pour lancer (throw) des exceptions :
#include <iostream> #include <string> using namespace std; class SoldeUtilisateurException { private: int solde_du_compte; public:

quelques exceptions prs CHAPITRE 8

159

SoldeUtilisateurException(int un_solde) { solde_du_compte = un_solde; } int getsolde() { return solde_du_compte; } }; class SoldeMachineException { private: int solde_billets; public: SoldeMachineException(int solde_machine) { solde_billets = solde_machine/100; } int getsolde() { return solde_billets; } }; class Bancomat2 { private: string utilisateur; int soldem; //solde de lappareil int soldeutil; //solde de lutilisateur public: void set_solde_machine(int lasoldem) { soldem = lasoldem; } void set_utilisateur(const string lutilisateur) { utilisateur = lutilisateur; } void set_solde_utilisateur(int lasoldeutil) { soldeutil = lasoldeutil; } void retrait_possible(int nb_billets); void retrait(int nb_billets); }; void Bancomat2::retrait_possible(int nb_billets) { if ((soldeutil - (100*nb_billets)) < 0) throw SoldeUtilisateurException(soldeutil); if ((soldem - (100*nb_billets)) < 0) throw SoldeMachineException(soldem);

160

Apprendre Java et C++ en parallle

} void Bancomat2::retrait(int nb_billets) { try { retrait_possible(nb_billets); cout << "Retirez vos " << nb_billets << " billets ! Merci !" << endl; } catch (SoldeUtilisateurException sue) { cerr << "Vous n'avez plus que " << sue.getsolde() << " euros sur votre compte !" << endl; } catch (SoldeMachineException sme) { cerr << "Il n'y a plus que " << sme.getsolde() << " billets dans la machine !" << endl; } } int main() { Bancomat2 labanque; labanque.set_solde_machine(1000); labanque.set_utilisateur("Tintin"); labanque.set_solde_utilisateur(500); labanque.retrait(4); labanque.retrait(6); labanque.set_solde_utilisateur(2000); labanque.retrait(11); }

An de mieux comprendre le code ci-dessus, nous pouvons dj examiner le rsultat, qui se prsentera ainsi :
Retirez vos 4 billets ! Merci ! Vous n'avez plus que 500 euros sur votre compte ! Il n'y a plus que 10 billets dans la machine !

Nous avons aussi ajout le mot Exception la n du nom de nos deux classes, Solde UtilisateurException et SoldeMachineException. Ce nest pas lusage en C++, comme en Java, mais cest tout de mme plus lisible. Il faut noter que la forme Java :
throw new SoldeUtilisateurException(soldeutil);

nest pas possible en C++. Nous avons dailleurs constat que le programme compilait sans message derreur, mais ne fonctionnait simplement pas correctement. Il faut vraiment appeler le constructeur directement ! Dans la partie main(), nous aurions pu crire ceci :
try { labanque.retrait_possible(4); labanque.retrait_possible(6);

quelques exceptions prs CHAPITRE 8

161

labanque.set_solde_utilisateur(2000); labanque.retrait_possible(11); } catch (SoldeUtilisateurException sue) { cerr << "Vous n'avez plus que " << sue.getsolde() << " sur votre compte !" << endl; } catch (SoldeMachineException sme) { cerr << "Il n'y a plus que " << sme.getsolde() << " billets dans la machine !" << endl; }

et le rsultat aurait t simplement :


Vous n'avez plus que 500 sur votre compte !

Cest propre et bien crit. Si nous avions crit le code suivant :


Bancomat2 labanque; labanque.set_solde_machine(1000); labanque.set_utilisateur("Tintin"); labanque.set_solde_utilisateur(500); labanque.retrait_possible(4); cout << "Retrait 4 a pass" << endl; labanque.retrait_possible(6); cout << "Retrait 6 a pass" << endl; labanque.retrait_possible(11); cout << "Retrait 6 a pass" << endl;

nous aurions alors obtenu comme rsultat :


Retrait 4 a pass abnormal program termination

Le compilateur C++ accepte de compiler ce code o aucune squence de try ni de catch() nest prsente. Il faut donc reconnatre que Java est tout de mme plus propre. Autant dire quaucune piste nous est donne en C++ et que la prsence de squences dun ou de plusieurs try et catch() va nous aider considrablement dans le traitement des erreurs.

Propager les exceptions


Dans lexemple qui suit, pour lequel nous avons volontairement laiss de ct la dnition et une partie du code de la classe Bancomat3, nous allons voir comment relancer une exception au niveau suprieur :
void Bancomat3::retrait(int nb_billets) { try { retrait_possible(nb_billets); cout << "Retirez vos " << nb_billets << " billets ! Merci !" << endl; } catch (SoldeUtilisateurException sue) { cerr << "Vous n'avez plus que " << sue.getsolde() << " euros sur votre compte !" << endl;

162

Apprendre Java et C++ en parallle

} catch (SoldeMachineException sme) { cerr << "Il n'y a plus que " << sme.getsolde() << " billets dans la machine !" << endl; throw; } } int main() { Bancomat3 labanque; labanque.set_solde_machine(1000); labanque.set_utilisateur("Tintin"); labanque.set_solde_utilisateur(500); labanque.retrait(4); labanque.retrait(6); labanque.set_solde_utilisateur(2000); try { labanque.retrait(11); } catch (SoldeMachineException sme) { cerr << "Il n'y a vraiment plus que " << sme.getsolde() << " billets dans la machine !" << endl; throw; } }

En excutant ce code, nous obtiendrons :


Retirez vos 4 billets ! Merci ! Vous n'avez plus que 500 euros sur votre compte ! Il n'y a plus que 10 billets dans la machine ! Il n'y a vraiment plus que 10 billets dans la machine ! abnormal program termination

Nous voyons ainsi comment, avec un simple throw, propager lexception SoldeMachine Exception dans la partie main(). Lorsquune exception surviendra dans la mthode retrait(), elle sera passe au programme principal, qui va lui-mme la propager au systme dexploitation, qui va nous retourner notre :
abnormal program termination

Sans la squence try et catch() du main(), nous aurions eu le mme rsultat, sans le :
Il n'y a vraiment plus que 10 billets dans la machine !

Dans ce cas prsent, un throw dans le main() nest vraiment pas ncessaire, puisque nous contrlons cette erreur et devrions en accepter les consquences avec du code appropri.

quelques exceptions prs CHAPITRE 8

163

Exception dans la bibliothque Standard C++


Une hirarchie de classe a aussi t dnie dans la bibliothque du Standard C++. Nous donnerons un exemple simple qui permettra ventuellement aux lecteurs de lutiliser dans leurs applications futures. Nous insisterons nouveau sur le fait que la bibliothque Standard C++ est relativement rcente et quil existe diffrentes versions de limplmentation de cette bibliothque. Lexemple suivant compilera trs bien sous C++ Builder (Borland) sans len-tte <stdexcept>. Nous rappellerons quen Java le mcanisme des exceptions est beaucoup plus restrictif, forant le programmeur introduire des squences try et catch(), alors que les compilateurs C++ ne sont pas aussi limitatifs. Dans lexemple qui suit, nous constaterons que la classe string du Standard C++ peut gnrer dans certains cas une exception :
// STLex.cpp #include <string> #include <iostream> #include <stdexcept> using namespace std; int main(int argc, char* argv[]) { string mon_str = "ABC"; try { char mon_char = mon_str.at(10); } catch (const out_of_range &oer) { cerr << "Exception: out_of_range" << endl; } cout << "Fin de test 1" << endl; try { char mon_char = mon_str.at(10); } catch (logic_error &le) { cerr << le.what() << endl; } cout << "Fin de test 2" << endl; try { char mon_char = mon_str.at(10); } catch (exception &e) { cerr << e.what() << endl; } cout << "Fin de test 3" << endl; return 0; }

164

Apprendre Java et C++ en parallle

Linstruction mon_str.at(10); accde donc au dixime octet dune chane de caractres qui nen contient que 3. La mthode at() de la classe string vrie que lindex ne dpasse pas les limites possibles et va lancer une exception out_of_range si cest le cas. Si nous consultons le chier den-tte stdexcept, nous dcouvrirons :
class out_of_range : public logic_error {

et :
class logic_error : public exception {

Nous voyons que out_of_range hrite de la classe logic_error, et ce dernier dexception. Lhritage de classe sera trait au chapitre 12. Cela nous a donn lide de montrer ces trois manires de faire et leurs rsultats :
Exception: out_of_range Fin de test 1 pos >= length () Fin de test 2 pos >= length () Fin de test 3

Il serait donc tout fait possible de traiter plusieurs exceptions et de les relancer par exemple sous une autre forme avec un throw.

Gnraliser les exceptions en C++ comme en Java ?


Nous ne le pensons pas. Il y a trop de fonctions C ou de bibliothques C++, comme les iostream, qui nutilisent pas ce mcanisme. Cependant, le programmeur C++ doit tre sensibilis ce problme et traiter correctement et systmatiquement les cas derreurs. Toutes les mthodes de classe qui allouent ou utilisent des ressources pouvant provoquer des erreurs ou des exceptions pourraient retourner un boolen indiquant si une erreur sest produite. Les classes elles-mmes pourraient conserver des attributs indiquant par exemple un code derreur et un texte descriptif. Ces derniers pourraient tre retourns aux applications qui aimeraient connatre les dtails sur le problme.

Rsum
Ce chapitre, beaucoup plus dlicat dans sa partie C++, sera certainement assimil aprs criture dun certain nombre dexercices comparatifs. Comme le mcanisme des exceptions en Java a t dni ds la cration du langage, il ne pose pas de difcults : il force le programmeur Java traiter et utiliser les exceptions. En C++ en revanche, cest beaucoup moins ais et sujet discussion.

quelques exceptions prs CHAPITRE 8

165

Exercices
1. Modier la classe Cin4 pour quelle demande loprateur de rpter son entre si celle-ci ne contient pas que des chiffres. Constater ce qui se passe si le nombre est trop grand. 2. crire la classe et lexemple Bancomat2 en Java. 3. Dvelopper une classe C++ nomme Arithmetic avec une mthode divise() qui gnre une exception DivisionParZeroException.

9
Entres et sorties
Avec ce chapitre, nous entamons une partie beaucoup plus srieuse, qui va nous permettre dcrire de vrais programmes. Jusqu maintenant, toutes les entres se faisaient sur le clavier et toutes les sorties sur la console. Nous allons prsent examiner comment lire et crire des chiers qui se trouvent sur le disque local. Ces chiers peuvent contenir du texte que nous pourrions lire avec un diteur traditionnel, des documents avec un format particulier ou encore des chiers totalement binaires comme des excutables ou des images, qui sont traits par des programmes spciques. Nous irons mme lire, en Java, un document HTML sur un site Web qui pourrait se situer lextrieur de notre environnement. Nous allons examiner, en premire partie de ce chapitre, un cas bien particulier : un chier texte dlimit. Ce sujet va aussi nous permettre dentrer dans la jungle des classes C++ et Java qui traitent des entres et sorties. Un dbutant en C++ ou en Java risque de se retrouver perdu sil essaie de considrer chaque classe indpendamment et sil essaie de comprendre son usage et son utilit. En Java notamment, cest encore plus complexe, car parfois plusieurs classes doivent tre associes en parallle. Un exercice pourrait consister crire un exemple par classe, mais cela prendrait vite la place dun ouvrage substantiel. Le meilleur moyen de sen sortir est encore de se poser les deux questions suivantes : Est-ce que nous lisons ou nous crivons ? Est-ce un chier binaire ou simplement du texte ? Lorsque nous avons rpondu ces deux questions, il suft alors de consulter le sommaire de ce chapitre, de sauter au paragraphe appropri et dexaminer le code associ. Cette manire de faire peut sembler bizarre, mais elle a le mrite dtre pragmatique et efcace pour sy retrouver dans cette jungle.

168

Apprendre Java et C++ en parallle

Avant de passer aux cas dtude, nous allons prsenter dans ce tableau les diffrents exemples que nous avons choisis de traiter :
Tableau 9-1
Lecture texte
C++ Java Lecture dun chier Access dlimit. Lecture dun chier Access dlimit.

Les combinaisons de lecture et dcriture


criture texte
Information structure au format XML. Sauvegarde dune partie du jeu dOthello.

Lecture binaire
Commande Linux strings : extraction de chanes visibles. Chargement dune sauvegarde du jeu dOthello.

criture binaire
100 octets binaires au hasard. Sauvegarde dune partie du jeu dOthello.

Comme extra nous avons ajout la lecture en Java dune page Web (HTML). Une page Web peut aussi tre considre comme un chier texte. Cependant, elle nest pas accessible sur le disque de notre PC, mais sur Internet.

Du texte dlimit partir de Microsoft Access


Access est le fameux outil de bases de donnes Microsoft. Si nous ne le possdons pas, ce nest pas trs important, car le chier texte que nous allons crer peut tre construit la main. Au chapitre 4, nous avons cr notre premire classe nomme Personne, qui contenait un nom, un prnom et une anne de naissance. Nous allons donc crer une table dans Access qui contient ces trois champs. Nous pouvons prendre, par exemple, bd1.mdb comme nom de base de donnes, Personnes comme nom de table et Nom, Prenom et Annee_Naissance comme noms de champs. Nom et Prenom sont des champs texte alors que Annee_Naissance sera de type numrique. Nous dnirons sur cette table une cl primaire nomme Numero. Finalement, nous entrerons les enregistrements suivants :
Tableau 9-2
Nom
Haddock Kaddock

Notre table Microsoft Access


Annee_Naissance
1907 1897

Prenom
Capitaine Kaptain

Lorsque la table aura t enregistre, nous allons lexporter (Fichier > Enregistrer sous > Exporter) en tant que chier texte avec le nom Personnes.txt aprs avoir choisi le format dlimit avec le point-virgule comme sparateur. Le chier texte peut tre lu par un diteur de texte traditionnel comme Crimson :
1;"Haddock";"Capitaine";1907 2;"Kaddock";"Kaptain";1897

Ce qui est intressant ici, cest de constater que le chier Personnes.txt peut aussi tre cr et modi par un traitement de texte ou par un programme, comme nous allons le

Entres et sorties CHAPITRE 9

169

voir ici. Le nouveau chier Personnes.txt peut trs bien tre import aprs modication dans Access de Microsoft, ce qui peut se faire avec le menu Importer (Fichier > Donnes externes > Importer). Il est vident que cette mthode nest pas approprie sil fallait changer continuellement des donnes. Il faudrait alors travailler directement avec un serveur SQL ou une interface ODBC, ce que nous verrons au chapitre 20.

Lecture de chiers texte en C++


Nous allons utiliser prsent la classe ifstream pour lire notre chier Personnes.txt. Cette classe fait partie de la bibliothque iostream du C++ traditionnel. Nous aurions pu utiliser des fonctions C comme open() ou fopen(), mais elles ne supportent pas les oprateurs << ou >>, qui sont devenus les outils traditionnels du C++. Nous allons donc les laisser aux oubliettes, comme dailleurs toutes les fonctions C qui peuvent tre avantageusement remplaces par des mthodes de classe de la bibliothque du Standard C++, lorsque cellesci sont disponibles. Nous allons directement crire le code pour excuter cette tche, puis passer aux explications. Cette mthode de prsentation est plus directe et se retrouvera tout au long de cet ouvrage.
// lecture_texte.cpp #include <string> #include <iostream> #include <fstream> using namespace std; class Lecture_texte { private: string nom_fichier; public: Lecture_texte(const string le_fichier); void lecture(); }; Lecture_texte::Lecture_texte(const string le_fichier) : nom_fichier(le_fichier) { } void Lecture_texte::lecture() { ifstream ifichier(nom_fichier.c_str()); if (!ifichier) { cerr << "Impossible d'ouvrir le fichier " << nom_fichier << endl; return; } string une_ligne; int numero_de_ligne = 1;

170

Apprendre Java et C++ en parallle

while (ifichier >> une_ligne) { cout << numero_de_ligne << ": " << une_ligne << endl; numero_de_ligne++; } } int main() { Lecture_texte les_personnes("Personnes.txt"); les_personnes.lecture(); }

Le <fstream> fait son apparition. Cest dans ce chier den-tte que la classe ifstream est dnie. Le constructeur difstream sattend recevoir un pointeur une chane de caractres correspondant au nom du chier. Ce dernier pourrait contenir le chemin daccs complet du chier. La mthode c_str() de la classe string fait le travail. La construction :
if (!ifichier) {

est un peu particulire. Elle dtermine en fait si le chier Personnes.txt est effectivement accessible. La boucle :
while (ifichier >> une_ligne) {

nous permet de lire ligne par ligne notre chier Personnes.txt. Une ligne de texte dans un chier se termine par un \n ou \r\n, mais ces derniers ne sont pas copis dans le string une_ligne, car ils sont ltrs par loprateur >>. Lorsque la n du chier est atteinte, la boucle se terminera.
Note

\n (octal 012) termine en gnral une ligne de texte dit ou construit sous Linux. La squence \r \n (octal 015 et 012) est applicable dans notre cas, car le chier est prpar et enregistr sous DOS.

Le programme ci-dessus nous sortira le rsultat suivant :


1: 1;"Haddock";"Capitaine";1907 2: 2;"Kaddock";"Kaptain";1897

qui sera identique au programme Java qui va suivre. Il est important de noter que si une ligne vide est ajoute en n de chier, elle sera traite comme un enregistrement vide par Access de Microsoft en cas dimportation. Le 7 de 1897 devra tre le dernier caractre du chier.

La mthode getline()
Que se passerait-il avec le programme prcdent si les donnes contenaient des espaces ? Ou, en dautres mots, si notre chier Personne.txt contenait les donnes suivantes :
1;"Haddock";"Le Capitaine";1907 2;"Kaddock";"Kaptain";1897

Entres et sorties CHAPITRE 9

171

Il suft de lessayer pour constater que le rsultat est loin de nous satisfaire :
1: 1;"Haddock";"Le 2: Capitaine";1907 3: 2;"Kaddock";"Kaptain";1897

Le problme vient de linstruction suivante :


while (ifichier >> une_ligne) {

Si une espace ou autre tabulateur est dcouvert dans le chier, loprateur >> stoppera son transfert. Le nom de la variable nest vraiment pas appropri, et une_ligne devrait plutt tre nomme un_mot. En fait, les points-virgules (;) ne sont pas considrs comme des sparateurs par loprateur >> de la classe istream. Pour que notre programme fonctionne avec "Le Capitaine", il nous faudrait modier le code comme suit :
// lecture_texte2.cpp #include <string> #include <iostream> #include <fstream> using namespace std; class Lecture_texte { private: string nom_fichier; public: Lecture_texte(const string le_fichier); void lecture(); }; Lecture_texte::Lecture_texte(const string le_fichier) : nom_fichier(le_fichier) { } void Lecture_texte::lecture() { ifstream ifichier(nom_fichier.c_str()); if (!ifichier) { cerr << "Impossible d'ouvrir le fichier " << nom_fichier << endl; return; } char une_ligne[1024]; int numero_de_ligne = 1; while (ifichier.getline(une_ligne, 1024)) { cout << numero_de_ligne << ": " << une_ligne << endl; numero_de_ligne++; } }

172

Apprendre Java et C++ en parallle

int main() { Lecture_texte les_personnes("Personnes2.txt"); les_personnes.lecture(); }

Nos donnes sont lues du chier Personnes2.txt et le rsultat sera correct :


1: 1;"Haddock";"Le Capitaine";1907 2: 2;"Kaddock";"Kaptain";1897

La diffrence vient de notre :


while (ifichier.getline(une_ligne, 1024))

qui va lire une ligne entire jusqu un maximum de 1 024 caractres. Le getline() va en fait sarrter ds quil trouve un \n ou un \r, qui correspond la n de la ligne. Le while() se terminera aussi ds que la n du chier sera atteinte. Pour terminer, il nous faut absolument indiquer une autre construction :
char un_char; ifichier.get(un_char); cout << "Un caractre: " << un_char << endl;

get() est une mthode de la classe istream (classe de base difstream) qui peut tre utile

dans des situations o il se rvle ncessaire de lire caractre par caractre. Nous lutiliserons dans lexercice 2 avec son quivalent put() pour lcriture dans la classe ostream (classe de base dofstream) :
ofstream ofichier(); char onechar = ; ofichier.put(onechar);

Lecture de chiers texte en Java


Comme pour le programme ci-dessus, qui lit notre chier dlimit Personne.txt export depuis Microsoft Access, la version Java (LectureTexte.java) scrira de cette manire :
import java.io.*; public class LectureTexte { private String nom_fichier; public LectureTexte(String le_fichier) { nom_fichier = le_fichier; } public void lecture() throws Exception { String une_ligne; BufferedReader in = new BufferedReader(new FileReader(nom_fichier)); int numero_de_ligne = 1;

Entres et sorties CHAPITRE 9

173

for (;;) { une_ligne = in.readLine(); if (une_ligne == null) break; System.out.println(numero_de_ligne + ": " + une_ligne); numero_de_ligne++; } } public static void main(String[] args) throws Exception { LectureTexte les_personnes = new LectureTexte("Personnes.txt"); les_personnes.lecture(); } }

et nous obtenons le mme rsultat quen C++. La premire remarque doit se faire sur ce throws Exception, car nous avons ici dcid de rejeter toutes les exceptions qui peuvent tre gnres dans la mthode lecture(). Le code serait cependant plus robuste si nous retournions, par exemple, un boolean de la mthode lecture(). La classe BufferedReader possde une mthode readline() qui permet de lire un tampon de lecture ligne par ligne, de la mme manire que notre exemple en C++. Le Buffered Reader est associ un chier ouvert en lecture au travers de la classe FileReader. Une question se pose immdiatement : que se passe-t-il si notre chier Personnes.txt nexiste pas ? Lorsque le constructeur de FileReader ne peut ouvrir ce chier, il va gnrer une exception nomme FileNotFoundException. Ici, nous utilisons un mcanisme particulier pour ignorer les exceptions : la combinaison throws Exception. Ces exceptions seront ignores non pas lexcution, mais seulement dans le code o les squences de try et catch ne sont pas ncessaires.

Utilisation de la variable separatorChar


Nous devons aussi dire quelques mots sur le caractre de sparation des rpertoires. Ce petit programme :
import java.io.*; public class FichierSeparateur { public static void main(String[] args) throws Exception { File mon_fichier = new File("Personnes.txt"); System.out.println("Chemin: " + mon_fichier.getAbsolutePath()); System.out.println("Sparateur: " + File.separatorChar); } }

nous donnera :
Chemin: E:\JavaCpp\EXEMPLES\Ch06\Personnes.txt Sparateur: \

174

Apprendre Java et C++ en parallle

Cela nous indique la location du chier sur le disque. Le sparateur sera diffrent sous Linux (/), et attention : la variable statique File.separatorChar doit tre absolument utilise si nous devons accder ou rechercher un rpertoire dans le chemin complet et bncier dun programme qui fonctionne aussi sous Linux. Si nous dplaons notre chier Personne.txt dans un sous-rpertoire xxx, les deux formes suivantes sont acceptables aussi bien sous Windows que sous Linux :
File mon_fichier = new File("xxx/Personnes.txt"); File mon_fichier = new File("xxx\\Personnes.txt");

Les deux \\ sont ncessaires, car le premier est le caractre dchappement pour le suivant. Si nous voulions rester totalement portables entre diffrents systmes, une instruction telle que :
String fichier_complet = System.getProperty("user.dir") + File.separator + "Personnes.txt";

serait souhaitable. La proprit user.dir, qui est reconnue par toutes les machines virtuelles de Java, nous donne le rpertoire courant.

Lecture de chiers sur Internet en Java


Lire des chiers sur Internet en C++ ne sera pas trait dans cet ouvrage car accder Internet en C++ ncessite des bibliothques spcialises qui sont spciques la machine et au systme dexploitation. En Java, cette fonctionnalit est extrmement simple. Lire un chier sur le disque local ou sur Internet est tout fait similaire. Pour cet exercice, nous avons choisi un chier HTML trs compact, que nous avons nomm mon_test.html et install sur un serveur Web Apache (http://www.apache.org) sur notre machine. Ladresse URL est donc http://localhost/mon_test.html et apparat de cette manire avec Firefox ou Microsoft Internet Explorer :

Figure 9-1

Page Web mon_test.html avec Firefox

Entres et sorties CHAPITRE 9

175

Sun Oct 31 11:18:35 UTC+0100 1999 Mon test Bonjour

Pour ceux qui ne dsirent pas installer un serveur Apache, il est tout fait possible de spcier une autre adresse URL en utilisant la forme :
file:///C:/JavaCpp/EXEMPLES/Chap09/mon_test.html

sous Firefox qui correspond un chier sur le disque local. Le code Java simpli (LectureUrl.java) se prsente ainsi :
import java.net.*; import java.io.*; public class LectureUrl { public static void main(String[] args) throws Exception { URL apache_local = new URL("http://localhost/mon_test.html"); BufferedReader in = new BufferedReader(new InputStreamReader(apache_local.openStream())); String inputLine; while ((inputLine = in.readLine()) != null) { System.out.println(inputLine); in.close(); } }

et le rsultat sera le suivant :


<html> <head><title>Mon test</title></head> <body> <script language="JavaScript">document.write(new Date());</script> <h2>Mon test</h2> Bonjour </body> </html>

Si nous utilisons la forme chier, il nous faudra ajouter sous Windows une srie de \\, car le caractre \ doit tre entr avec son caractre dchappement :
URL apache_local = new URL("file:\\\\\\C:\\JavaCpp\\EXEMPLES \\Ch09\\mon_test.html");

En ce qui concerne le code, nous retrouvons la classe BufferedReader, que nous utilisons cette fois-ci sur un InputStreamReader. Cette dernire classe va nous offrir une transparence complte au niveau du protocole HTTP, protocole ncessaire pour lire un document sur le rseau Internet. Dans lannexe E, nous prsentons NetBeans, environnement qui permet de dvelopper des applications Web. Le serveur http Apache Tomcat est intgr NetBeans.

176

Apprendre Java et C++ en parallle

Lecture de chier binaire en C++


Il nous est parfois ncessaire de devoir extraire dun chier binaire, par exemple dun programme excutable, la partie visible de son contenu, cest--dire tout ce qui correspond du texte bien visible (caractres ASCII entre lespace et le caractre binaire 127 (0x7F)). Un outil Linux, strings, existe dailleurs et savre souvent utilis avec dautres ltres pour rechercher des chanes de caractres bien particuliers. Pour ce faire, nous allons lire le chier par bloc et ne sortir sur la console que les parties qui excdent cinq caractres. Chaque squence de caractres visibles sera prcde de sa position dans le chier, ceci entre parenthses et chaque fois sur une nouvelle ligne.
// strings.cpp #include <iostream> #include <fstream> #include <string> using namespace std; int main(int argc, char **argv) { if (argc != 2) { cerr << "Nombre d'arguments invalides" << endl; cerr << "Strings Fichier" << endl; return -1; } ifstream infile(argv[1], ios::in|ios::binary); if (!infile) { cout << "Fichier d'entre n'existe pas"; return -1; } const int int char int bloc = 512; avant_bloc = 0; ncinq_char = 0; cinq_char[6]; // // // // dim bloc lu dim * n lu nombre de premiers chars lus cinq premiers chars

cinq_char[5] = 0;

// toujours 5 imprims

char tampon[bloc]; int octets_lus; for (;;) { // lecture par bloc infile.read(tampon, bloc); octets_lus = infile.gcount(); for (int i = 0; i < octets_lus; i++) { if ((tampon[i] >= ' ') && (tampon[i] < 127)) { if (ncinq_char < 5) { cinq_char[ncinq_char] = tampon[i]; ncinq_char++; }

Entres et sorties CHAPITRE 9

177

else { if (ncinq_char == 5) { cout << (avant_bloc + i - 5) << ": " << cinq_char << tampon[i]; ncinq_char++; } else cout << tampon[i]; } } else { // char binaire if (ncinq_char > 5) { cout << endl; } ncinq_char = 0; } } if (octets_lus < bloc) break; avant_bloc += bloc; } infile.close(); } // dernier bloc // avance compteur relatif

La constante bloc a pour valeur 512, et ce nest pas par hasard. Lorsque nous devons lire des chiers binaires, il ny a pas de restrictions ni dobligations de lire les octets lun aprs lautre. Loprateur >> en C++ de la classe ifstream ou le readline() en Java, que nous avons vus tous deux prcdemment, possdent en fait un test interne sur chaque caractre pour identier une n de ligne. Ici, ce nest pas ncessaire, et nous choisirons des dimensions de blocs sufsamment grandes. Si nous avons choisi une dimension de 512 et non pas de 100, de 1 000, de 511 ou de 513, cela vient sans doute de vieilles habitudes de lauteur, qui considre encore quil est avantageux de choisir une dimension quivalant ou correspondant un nombre entier de blocs sur un secteur du disque. un moment ou un autre, les primitives du systme devront bien accder au disque ou son cache ! Ce qui est important ici, cest le nombre dappels de infile.read() qui se fera selon cette formule :
(dimension du fichier)/(dimension du bloc (512 ici)) + 1

Si nous lisons le chier octet par octet, nous multiplierons ce nombre par 512. Cest de cette manire que nous obtiendrons les plus mauvaises performances. Choisir des blocs correspondant la dimension du chier nest pas non plus un choix sens, car certains chiers pourraient dpasser les capacits de mmoire vive ou virtuelle. Si nous devions travailler avec des chiers plus importants, il serait avantageux daugmenter le bloc 4 096, par exemple, ce qui nentamerait pas trop les ressources en mmoire du programme. Cette discussion correspond aux calculs de performance que nous effectuerons au chapitre 15. Nous lisons donc par blocs de 512, ce qui limite le nombre daccs aux primitives qui vont nalement accder au disque, travers le systme dexploitation, et au contrleur du disque. Ensuite, chaque caractre sera lu de la mmoire et donc vri plus rapidement. Le tampon cinq_char garde une suite conscutive de caractres qui sont prsents lcran

178

Apprendre Java et C++ en parallle

lorsque le sixime apparat. Dans le cas contraire, nous remettons le compteur ncinq_char zro et recommenons. Cette manire de faire nous permet de sortir des squences de caractres mme sils se trouvent cheval sur deux blocs de 512 octets. Sinon il faudrait avoir deux blocs tampon de travail, garder leurs index respectifs et aller de lun lautre.

criture dun fichier binaire en C++


Nous avons choisi ici un exemple un peu particulier, gure vraisemblable, mais qui nous permettra dutiliser des fonctions pouvant poser problme avec dautres compilateurs : cest en effet un aspect important quun programmeur C++ doit absolument matriser. Nous allons crire un chier binaire de 100 octets avec des valeurs totalement alatoires.
// ecritbin.cpp #include <iostream> #include <fstream> #include <ctime> #include <cstdlib> using namespace std; const int dim = 100; int main(int argc, char* argv[]) { ofstream outfile("au_hasard.bin", ios::out | ios::binary); if (!outfile) { cerr << "Fichier de sortie ne peut tre cr" << endl; return false; } char tampon[dim] ; // nos valeurs alatoires srand(time(NULL)); for (int i = 0; i < dim; i++) { tampon[i] = (char)((rand() * 256)/RAND_MAX); // entre 0 et 255 //tampon[i] = (char)rand(256); // entre 0 et 255 certains compilateurs } outfile.write(tampon, dim) << endl; outfile.close(); return 0; }

La fonction C srand() va dnir un point de dpart pour le gnrateur de nombres alatoires assur par la fonction rand(). Pour ce faire, nous utiliserons lheure actuelle en secondes, qui nous fournira des rsultats diffrents chaque utilisation du programme. Le chier au_hasard.bin va contenir 100 octets de donnes alatoires. La fonction rand() peut avoir diffrentes implmentations suivant les compilateurs : il vaut donc mieux consulter

Entres et sorties CHAPITRE 9

179

attentivement la documentation ou plus simplement faire un test pralable. Nous comprendrons ainsi ce que nous retourne rand(), puisquil faut ensuite le multiplier par 256/RAND_MAX. Nous verrons les dtails un peu plus loin. Sous Windows, nous pouvons associer le .bin un diteur hexadcimal pour visionner le rsultat. Les trois indicateurs ios doivent tre inclus (avec loprateur logique OU (|)) pour indiquer que : ios::out chier dcriture ; ios::binary chier binaire. Nous trouvons la dnition de ces bits dans le chier den-tte streambuf.h, qui est luimme inclus dans iostream. Les out, binary et autres noreplace sont des numrations publiques de la classe ios, et il y a parfois des variantes selon les implmentations de la bibliothque du Standard C++ et les compilateurs. Dans le code prcdent, le chier au_hasard.bin est crit chaque excution du programme. Si nous ne voulions pas le remplacer, il faudrait sassurer au pralable quil existe bien. La classe ofstream est extrmement simple utiliser. De la mme manire quifstream, le (!outfile) nous permet de vrier si louverture du chier a t faite correctement. Le rand(256) nous retourne un entier entre 0 et 255, cest--dire une des valeurs possibles pour reprsenter un octet. Notre tampon de 100 caractres est donc du type char. Certains nous diront quun unsigned char (0 255) aurait t plus appropri quun char (128 127). Cependant, le transtypage fonctionne correctement, et, pour tre convaincus, nous pouvons le vrier avec un diteur hexadcimal, qui devrait nous montrer quil y a bien des octets avec le bit 7 correctement positionn. Le rand(256) devrait dailleurs nous allouer environ la moiti de lensemble ! La mthode write() de la classe ofstream a besoin ici de deux paramtres, le tampon (un char *) et le nombre doctets crire (dim). Cest une bonne habitude de fermer correctement le stream avec un close() en n dopration.

Compilation conditionnelle
Comme nous venons de le voir dans lexemple ci-dessus, il y a parfois des circonstances o une compilation conditionnelle savre ncessaire. Ce peut tre un problme de plateforme (DOS ou Linux (Unix)) ou de compilateur ne supportant pas certaines fonctions, ou les supportant diffremment. Le morceau de code suivant devrait nous aider pouvoir contourner ce genre de difcult avec des compilations conditionnelles.
// define.cpp #ifdef GNU #define GNU_UNIX #endif #ifdef UNIX #define GNU_UNIX #endif

180

Apprendre Java et C++ en parallle

#include <iostream> using namespace std; int main(int argc, char* argv[]) { #ifdef GNU cout << "Avec GNU" << endl; #endif #ifdef GNU_UNIX cout << "Avec GNU et Unix" << endl; #endif #ifndef DOS cout << "Sans DOS" << endl; #endif cout << "Avec tous" << endl; return 0; }

Si nous compilons ce code sans options de compilation, cest--dire avec :


g++ -o define.exe define.cpp

nous obtiendrons :
Sans DOS Avec tous

Dans ce code, nous dcouvrons les directives de compilation conditionnelle. Le message Sans DOS apparat car la constante du prprocesseur na pas t dnie. #ifndef veut donc dire : si DOS nest pas dni, alors je compile le code jusquau prochain #endif, qui indique la n dune condition de compilation. Si nous voulons prsent compiler avec la constante GNU, il faudra compiler le programme de cette manire :
g++ -DGNU -o define.exe define.cpp

ou bien :
g++ -DGNU -c define.o define.cpp g++ -DGNU -o define.exe define.o

Ce qui nous donnera :


Avec Avec Sans Avec GNU GNU et Unix DOS tous

Entres et sorties CHAPITRE 9

181

Le #define GNU_UNIX permet de dnir une nouvelle constante lintrieur du code source. Cela permet de ne pas avoir recopier le mme code pour des conditions diffrentes.
Note Des constantes de compilations sont parfois utilises pour du code de test durant les phases de dveloppement. Lorsque le produit nal est dlivr, il suft alors dajouter ou denlever la constante dans un Makefile.

define.o: define.cpp g++ $BTEST -c define.cpp

Nous avons dj parl de lutilisation des #define au chapitre 6 pour la manipulation de chiers den-tte multiples.

criture dun fichier binaire en Java


Pour cette partie, nous allons revenir notre jeu dOthello (voir chapitres 5 et 20), dans lequel nous allons prsent sauvegarder et recharger une partie en cours (chiers source SauveOthello.java et ChargeOthello.java). Au chapitre 5, nous avions dni notre chiquier comme un int jeu[10][10], avec quatre possibilits : 0 pour libre, 1 pour noir, 2 pour blanc et 1 pour les bords. Comme nous ne nous intressons qu lintrieur, un simple chier de 64 octets (8 8) sera sufsant, et nous navons pas traiter la valeur de 1 sur un octet de 8 bits. Nous commencerons par lcriture de notre chier binaire, que nous allons nommer
othello.bin :
import java.io.*; public class SauveOthello { private String fichier; private static final int dim = 10; private int[][] othello = new int[dim][dim]; public SauveOthello(String fichier_de_sauvetage) { fichier = fichier_de_sauvetage; for (int j = 1; j < dim-1; j++) { // intrieur vide for (int i = 1; i < dim-1; i++) { othello[i][j] = 0; } } othello[4][5] othello[5][4] othello[4][4] othello[5][5] = = = = 1; 1; 2; 2; // // // // blanc blanc noir noir

182

Apprendre Java et C++ en parallle

} public boolean sauve() { File outFile = new File(fichier); try { FileOutputStream out = new FileOutputStream(outFile); for (int j = 1; j < dim-1; j++) { for (int i = 1; i < dim-1; i++) { out.write(othello[i][j]); } } out.close(); } catch (IOException e) { return false; } return true; } public static void main(String[] args) { SauveOthello mon_sauvetage = new SauveOthello("othello.bin"); if (mon_sauvetage.sauve()) { System.out.println("Sauvegarde effectu"); } else { System.out.println("Erreur de sauvegarde"); } } }

La premire partie de ce code nous est dj familire. Nous positionnons nos quatre pions de dpart (voir chapitre 20 pour les rgles du jeu) an de vrier le rsultat produit. Pour ce qui est de lcriture, nous allons essayer dclairer le lecteur sur la manire de procder pour savoir quelles classes utiliser. Quelque part, il nous faut rechercher une mthode write(). Celle-ci doit pouvoir crire des octets et non pas du texte ou des objets de taille variable.
FileOutputStream est la classe la plus lmentaire pour envoyer des donnes binaires dans un chier au travers dun ux. Elle possde des mthodes write() pour envoyer des octets,

mais aussi des entiers. Elle reoit aussi le nom du chier de sortie comme paramtre de constructeur. othello tant un tableau dentier, le chier aura donc un format binaire dtermin par le type int du langage Java. Si nous devions lire othello.bin depuis un autre langage ou avec dautres classes, nous pourrions rencontrer des difcults. La squence try et catch() devrait nous sortir les erreurs. Dans notre cas, il y a peu derreurs possibles. Si nous voulions provoquer une erreur, nous pourrions alors essayer dcrire dans un chier tel que Z:\othello.bin. Il faudrait alors modier le code pour vrier le type derreur et dnir une action spcique pour chaque type. Si nous crivions :

Entres et sorties CHAPITRE 9

183

catch (IOException e) { System.out.println(e); return false; }

et vriions le rsultat avec Z:\othello.bin, nous obtiendrions ceci :


java.io.FileNotFoundException: Z:\othello.bin (Le chemin d'accs spcifi est introuvable) Erreur de sauvegarde

Un bon traitement des erreurs ncessiterait donc un peu plus de code.

Lecture dun chier binaire en Java


Nous terminons avec la relecture (ChargeOthello.java) de notre chier binaire othello.bin prcdemment sauvegard :
import java.io.*; public class ChargeOthello { private String fichier; private static final int dim = 10; private int[][] othello = new int[dim][dim]; public ChargeOthello(String fichier_de_sauvetage) { fichier = fichier_de_sauvetage; } public boolean charge() { File inFile = new File(fichier); try { FileInputStream in = new FileInputStream(inFile); for (int j = 1; j < dim-1; j++) { for (int i = 1; i < dim-1; i++) { othello[i][j] = in.read(); } } in.close(); } catch (IOException e) { return false; } return true; } public void test() { int i = 0; // position horizontale int j = 0; // position verticale

184

Apprendre Java et C++ en parallle

for (j = 0; j < dim; j++) { for (i = 0; i < dim; i++) { if (othello[i][j] >= 0) System.out.print(" "); System.out.print(othello[i][j]); } System.out.println(); } } public static void main(String[] args) { ChargeOthello mon_sauvetage = new ChargeOthello("othello.bin"); if (mon_sauvetage.charge()) { System.out.println("Chargement effectu"); mon_sauvetage.test(); } else { System.out.println("Erreur de chargement"); } } }

Ici, nous faisons linverse : nous utilisons la classe FileInputStream et la mthode read(). Il ny a aucun contrle du format du chier. Nous assumons le fait que le chier contienne des entiers et possde le nombre correct de caractres. Il ny a, nouveau, aucun vritable traitement des erreurs. Pour vrier que le programme lit correctement, il faudrait sans doute rutiliser la classe prcdente et vrier un plus grand nombre de possibilits que nos quatre premiers pions ! Comme les chiers de sauvegarde du jeu dOthello peuvent tre rutiliss an de tester notre jeu pendant la construction du programme, il est vident que lutilisation dun chier binaire nest pas intressante. Il serait donc avantageux dutiliser un format texte simple, an de pouvoir modier les sauvegardes avec un diteur traditionnel. Un format tel que :
VVVVVVVV VVVVVVVV VVVVBNVV VVNNNVVV VVVNBVVV VVVVVVVV VVVVVVVV VVVVVVVV V - position vide B - pion blanc N - pion noir

se comprendrait de lui-mme. Cest ce que nous allons aborder prsent.

Entres et sorties CHAPITRE 9

185

criture dun chier texte en Java


Nous allons continuer de jouer avec notre exemple dOthello, reprsent ci-dessous dans le code source WriteOthello.java, o les noirs viennent de jouer. Le chier produit, othello.txt, sera cette fois-ci un chier de type texte traditionnel :
import java.io.*; public class WriteOthello { private String nomFichier; private static final int dim = 10; private int[][] othello = new int[dim][dim]; public WriteOthello(String fichierDeSauvetage) { nomFichier = fichierDeSauvetage; for (int j = 1; j < dim-1; j++) { // intrieur vide for (int i = 1; i < dim-1; i++) { othello[i][j] = 0; } } othello[3][4] = 2; // noir joue othello[4][4] othello[5][4] othello[4][5] othello[5][5] } public boolean sauve() { try { PrintWriter out = new PrintWriter(new FileWriter(nomFichier)); char pion; StringBuffer tampon = new StringBuffer("12345678"); for (int j = 0; j < 8; j++) { for (int i = 0; i < 8; i++) { pion = 'V'; // vide if (othello[i + 1][j + 1] == 1) pion = 'B'; // blanc else if (othello[i + 1][j + 1] == 2) pion = 'N'; // noir tampon.setCharAt(i, pion); } out.println(tampon); } out.close(); } catch(IOException ioe) { = = = = 2; 2; 2; 1; // // // // noir noir noir blanc

186

Apprendre Java et C++ en parallle

return false; } return true; } public static void main(String[] args) { String fichierDeSauvetage = "othello.txt"; WriteOthello mon_sauvetage = new WriteOthello("othello.txt"); if (mon_sauvetage.sauve()) { System.out.println("Sauvegarde effectu dans " + fichierDeSauvetage); } else { System.out.println("Erreur de sauvegarde dans " + fichierDeSauvetage); } } }

Nous rencontrons ici une nouvelle classe, PrintWriter. Cette classe nous permet dcrire dans le chier de la mme manire que nous le faisons pour une sortie lcran avec notre println() habituel. Lors de lcriture binaire en Java, nous avions utilis FileOutputStream. Ici, cest presque pareil avec PrintWriter, mais write() est remplac par println(). Cette dernire mthode nous crira une chane de caractres termine par une nouvelle ligne. Notre variable tampon possde une rserve pour 8 octets, qui seront initialiss, avant lcriture, avec le contenu dune ligne de notre tableau othello. Les lettres employes, V, B et N, remplaceront des valeurs binaires 0, 1 et 2, illisibles avec un diteur. Nous rappellerons que StringBuffer est mutable, au contraire de la classe String. Nous pourrions aussi construire notre chane avec cette dernire, mais cela serait moins efcace, puisquil faudrait composer le String avec une srie de + (objet String rgnr chaque fois).

criture dun chier texte en C++


Nous allons proter de cette occasion pour prsenter le XML, qui va nous permettre denregistrer des donnes dans un chier texte traditionnel dans le cas de donnes structures telles que des bases de donnes. Ce sera la fois un exercice de style en C++, pouvant tre adapt sans difcult en Java, mais aussi une introduction essentielle pour cette technologie de plus en plus utilise dans le commerce lectronique.

Le XML pour linformation structure


Le XML, ou eXtensible Markup Language, a t conu pour remdier aux insufsances du HTML, Hyper Text Markup Language, dans lexploitation des informations structures. Tout comme le HTML, le XML dcoule du SGML, ou Standard General Markup Language,

Entres et sorties CHAPITRE 9

187

qui a t dvelopp pour maintenir une structure et un contenu standard pour des documents lectroniques. Sur le site http://www.w3.org du World Wide Web Consortium, nous trouverons les recommandations pour les technologies du Web et les spcications du XML. Le XML a hrit du HTML, mais sans garder ses dfauts. Il reste aussi beaucoup plus simple que son aeul, le SGML. La structure hirarchique dun document est propre et linformation intgre entre des balises symtriques, claires et concises. Le balisage XML permet une identication rapide du contenu des donnes. Comme exemple, nous allons reprendre notre classe Personne, que nous avions dveloppe au chapitre 4, mais sans lanne de naissance. Nous garderons les deux premiers attributs, le nom et le prnom. Le document XML devrait se prsenter ainsi :
<?xml version="1.0"?> <carnet> <personne> <nom>Haddock</nom> <prenom>Capitaine</prenom> </personne> <personne> <nom>Boichat</nom> <prenom>Jean-Bernard</prenom> </personne> </carnet>

Les balises personne, nom et prenom sont similaires celle du HTML. Dans notre carnet, nous avons deux personne, qui possdent chacune un nom et un prenom. Loubli du est volontaire pour des raisons de programmation. Contrairement au HTML, la n de balise, comme </personne>, est essentielle. Sur la deuxime ligne, il ny a pas de DTD, cest--dire de dclaration de document type. Elle nest pas obligatoire en XML, car notre document est bien form, rigoureux et explicite. Un processeur XML naura aucune difcult pour le traiter.

criture du chier XML


Lexemple ci-dessous est simpli lextrme et devait tre en fait intgr la classe Personne du chapitre 4, avec une mthode que nous pourrions appeler par exemple write_XML(). Nous avons ici cr une classe nomme WriteXML, qui va gnrer un chier personne.xml sur le rpertoire courant ::
// write_xml.cpp #include <iostream> #include <fstream> #include <string> using namespace std; class WriteXML {

188

Apprendre Java et C++ en parallle

private: ofstream outfile; string avecBalise(const string balise, const string valeur) { return " <" + balise + ">" + valeur + "</" + balise + ">"; } public: bool write_debut(const char *fichier) { outfile.open(fichier, ios::out); if (!outfile.is_open()) { return false; } outfile << "<?xml version=\"1.0\"?>" << endl; outfile << "<carnet>" << endl; } void write_record(const char *nom, const char *prenom) { outfile << " <personne>" << endl; outfile << avecBalise("nom", nom) << endl; outfile << avecBalise("prenom", prenom) << endl; outfile << " </personne>" << endl; } void write_fin() { outfile << "</carnet>" << endl; outfile.close(); } }; int main() { WriteXML mon_xml; if (!mon_xml.write_debut("personne.xml")) { cout << "Ne peut pas crire dans le fichier personne.xml" << endl; return -1; } mon_xml.write_record("Haddock", "Capitaine"); mon_xml.write_record("Boichat", "Jean-Bernard"); mon_xml.write_fin(); cout << "Fichier personne.xml gnr" << endl; }

Dans ce code, nous utilisons la mthode open() de la classe ofstream. Elle nous retourne un void, et il est donc essentiel dappeler la mthode bool is_open() de cette mme classe pour vrier si le chier a effectivement t cr et sil est bien accessible pour y crire nos donnes. Bien que le traitement des erreurs dcriture soit rduit au minimum, nous fermons tout de mme le chier avec la mthode close(). Lemploi de loprateur << de

Entres et sorties CHAPITRE 9

189

la classe ofstream et du manipulateur endl, pour crire le caractre de n de ligne, relve vraiment dune criture lgante. Nous crivons dans un chier texte comme sur une console. La mthode prive avecBalise() nous permet dexcuter le travail rptitif dune manire simplie, sans une rexion trop approfondie, car le contenu mme dun objet personne pourrait aussi y tre intgr. Il nous faut aussi mentionner ici quil est tout fait possible dinclure, en XML, plusieurs objets dun mme type dans une structure. personne pourrait contenir plusieurs blocs de prnoms : ce serait dailleurs absolument ncessaire si nous voulions, par exemple, inclure la liste des enfants de cette mme personne. Aprs avoir excut le programme ci-dessus, qui cre le chier personne.xml, nous pourrions le visualiser, par exemple avec Microsoft Internet Explorer, qui va ltrer ce document pour nous retourner ceci :
<?xml version="1.0" ?> - <carnet> - <personne> <nom>Haddock</nom> <prenom>Capitaine</prenom> </personne> - <personne> <nom>Boichat</nom> <prenom>Jean-Bernard</prenom> </personne> </carnet>

Accs des rpertoires sur le disque


De nombreux outils informatiques accdent aux rpertoires et aux chiers dun disque. Il est donc essentiel de couvrir ce sujet. Lorsque nous accdons au rpertoire dun disque et utilisons la commande dir sous DOS ou ls -l sous Linux (voir annexe C), le rsultat nous est prsent sous forme de liste de chiers ou de rpertoires, et nous obtenons par exemple des donnes sur la protection, la dimension ou encore la date des chiers. Les deux morceaux de code suivants peuvent tre le dpart dun grand nombre de petits programmes qui peuvent tre conus plus particulirement dans le cadre dexercices de programmation. Nous pourrions nous imaginer des programmes de sauvegarde incrmentiel, de statistique, de compression ou encore de contrle de versions (plusieurs versions du mme code dans le cadre de projets informatiques, de dveloppement ou de maintenance).

Lecture dun rpertoire en C++


Le code suivant utilise un certain nombre de fonctions de la bibliothque C pour accder au rpertoire dun disque :
// listdir.cpp #include <sys/types.h> #include <sys/stat.h> #include <dirent.h>

190

Apprendre Java et C++ en parallle

#include <ctime> #include <iostream> using namespace std; int main() { DIR *pdir; struct dirent *pdirent; struct stat statbuf; if ((pdir = opendir(".")) == NULL) { cout << "Ne peut ouvrir le rpertoire ." << endl; return -1; } while ((pdirent = readdir(pdir)) != NULL) { cout << "Fichier: " << pdirent->d_name << endl; if (stat(pdirent->d_name, &statbuf) != 0) continue; if (statbuf.st_mode & S_IWRITE) { cout << "Nous avons la permission d'crire" << endl; } else { cout << "Nous n'avons pas la permission d'crire" << endl; } if (statbuf.st_mode & S_IFDIR) { cout << "C'est un rpertoire" << endl; } cout << "La lettre du disque: " << (char)('A'+statbuf.st_dev) << endl; cout << "La dimension en octets: " << statbuf.st_size << endl; cout << "La dernire fois qu'il fut ouvert: " << ctime(&statbuf.st_ctime) << endl; } closedir(pdir); return 0; }

Il est vraiment parfait, ce petit programme ! Nous montrerons ici une partie du rsultat :
Fichier: Lecture_texte.java Nous avons la permission d'crire La lettre du disque: E La dimension en octets: 684 La dernire fois qu'il fut ouvert: Thu Nov 11 18:27:32 1999 Fichier: strings.cpp Nous avons la permission d'crire La lettre du disque: E La dimension en octets: 1473 La dernire fois qu'il fut ouvert: Tue Nov 16 19:06:10 1999

Les trois premiers chiers den-tte, <sys/types.h>, <sys/stat.h> et <dirent.h>, nous indiquent que nous utilisons des fonctions C. Cela signie que ce programme pourrait rencontrer des difcults en cours de compilation ou dutilisation sur une autre plate-forme, ou bien

Entres et sorties CHAPITRE 9

191

en utilisant, sous Windows, des outils tels que C++ Builder de Borland ou Visual C++ de Microsoft. Certaines adaptations pourront donc tre ncessaires. La fonction C opendir() nous permet daccder au rpertoire courant qui est spci avec la notation ".". Nous aurions pu donner le chemin complet, en respectant la notation / ou \ suivant le systme dexploitation. opendir() est dni dans dirent.h sur le rpertoire i386-mingw32\include du compilateur et retourne un pointeur une structure DIR dnie dans ce mme chier den-tte. Nous conseillerons, comme ici, de vrier les cas derreur. La partie intressante commence avec readdir(), qui nous permet dobtenir un premier niveau dinformation dun chier dans la structure dirent. Chaque fois que nous rappelons readdir(), jusqu lobtention du NULL, nous recevons linformation du prochain chier. Ici, nous nutilisons que le nom du chier obtenu avec d_name, car les autres donnes de la structure pdirent ne nous apporteraient rien. Avec celui-ci, nous pouvons utiliser une autre fonction C, beaucoup plus intressante, stat() :
stat(pdirent->d_name, &statbuf)

Il faut tre attentif la manire de passer ladresse de statbuf la fonction stat(). Enn, nous utilisons un certain nombre de donnes disponibles dans la structure stat pour nous retourner linformation telle quelle nous apparat la console. Il faudra se mer de certains de ces attributs, comme st_dev, qui peuvent avoir une autre signication sur un autre systme dexploitation. Le chier den-tte stat.h, dans le rpertoire 386-mingw32\include\sys, doit absolument tre consult pour vrier les dnitions utilises. S_IFDIR, par exemple, nous permettrait dexcuter une nouvelle recherche rcursive sur les sous-rpertoires, ce que nous ferons dailleurs en exercice.

Lecture dun rpertoire en Java


Nous allons prsent faire le mme travail en Java, cest--dire obtenir la liste de tous les chiers dun rpertoire. Nous verrons que la cration dune liste de chiers est nettement plus lgante et exible. Voici donc le code que nous allons commenter :
import java.io.*; import java.util.Date; class MonFileFilter implements FilenameFilter { public boolean accept(File dir, String name) { String tempname = name.toLowerCase(); return (tempname.endsWith ("doc") || tempname.endsWith ("txt")); } } public class ListDir { private String repertoire; public static void main (String args[]) {

192

Apprendre Java et C++ en parallle

if ( args.length != 0 ) { ListDir dr2 = new ListDir(args[0]); } else { ListDir dr2 = new ListDir("E:\\Mes Documents"); } } public ListDir(String leRepertoire) { repertoire = leRepertoire; if (leRepertoire.charAt(leRepertoire.length() - 1) != File.separatorChar) { repertoire += File.separatorChar; } File monRep = new File(repertoire); if (monRep.exists()) { String lesFichiers[] = monRep.list(new MonFileFilter()); int nombre = lesFichiers.length; System.out.println("Nombre de fichiers: " + lesFichiers.length); for (int i = 0; i < nombre; i++) { File leFichier = new File(repertoire + lesFichiers[i]); Date dernModif = new Date(leFichier.lastModified()); System.out.println(lesFichiers[i] + " " + dernModif); } } else { System.out.println("Le rpertoire " + repertoire + " n'existe pas"); } } }

Le rsultat apparatra alors ainsi :


Nombre de fichiers: 3 document1.doc Sun Apr 25 11:23:08 GMT+02:00 1999 document2.doc Wed May 26 21:21:56 GMT+02:00 1999 readme.txt Sun Jun 06 18:28:04 GMT+02:00 1999

Dans cette version Java, au contraire de la version C++, aucune discussion de portabilit nest prvoir : le fonctionnement de ce code est garanti sur toutes les plates-formes. Comme ce code est de plus bien structur, dans une classe, il nous donne tout de suite une meilleure impression. Le main() est traditionnel ; il contient un rpertoire par dfaut, E:\\Mes Documents, si aucun nest spci comme argument lexcution du programme. Lutilisation du File.separatorChar est essentielle si nous voulons que le programme soit portable, car il vriera et ajoutera le caractre \ ou / si ncessaire.

Entres et sorties CHAPITRE 9

193

Lire la liste des chiers se fait laide de la classe File et la mthode list(). Cette dernire reoit un paramtre tout fait particulier, et dune puissance extraordinaire, un objet dune classe hrite de FilenameFilter. Nous allons trs vite comprendre lutilisation de ce ltre. En effet, la mthode accept(), de la classe MonFileFilter, qui hrite de FilenameFilter, va tre excute lappel de monRep.list(). Aprs une conversion locale du nom du chier en minuscules pour accepter tous les .txt, .Txt, .doc ou autres .DoC, nous retournons un boolen au cas o le chier possderait une extension .txt ou .doc. Au retour de monRep.list(), la variable lesFichiers contiendra un tableau avec tous les chiers dsirs. Nous passerons au travers du tableau lesFichiers, non sans avoir consult au passage la documentation de la classe File ; celle-ci nous donnera lusage de mthode lastModified(), qui nous retourne un long, valeur que la classe Date va accepter comme paramtre de constructeur. Nous devrons nous demander comment notre programme fonctionne, puisque la mthode
exists() de la classe File teste si le chier existe alors que nous dsirons contrler lexistence du rpertoire. Cest en effet correct, car nous avons toujours un File.separatorChar

en n de chier.

Les ux en mmoire (C++)


Les streams en C++ ont t utiliss jusqu prsent pour connecter des entres et sorties sur des chiers. Nous allons maintenant continuer notre tour dhorizon et parler de la fonction sprintf() de la bibliothque C et des classes istringstream et ostringstream. An de ne pas prendre les unes pour les autres, nous commencerons donc par dnir deux catgories pour ces classes : la classe istringstream pour extraire des caractres ; la classe ostringstream pour composer des caractres. Avant de passer la description et lutilisation de ces deux classes, il est essentiel de revenir en arrire, dans le temps, et de se demander comment les programmeurs C se dbrouillaient avant.

sprintf() de la bibliothque C
Un programmeur C qui utilise rgulirement les fonctions C telles quatoi() (conversion chane de caractres un int) ou atof() (conversion un float) pour extraire des caractres ou sprintf() pour composer de nouvelles chanes reconnatra rapidement ce type de code :
// sprintf.cpp #include <iostream> #include <cstdio> #include <cstdlib> using namespace std; int const protect = 10;

194

Apprendre Java et C++ en parallle

int main() { unsigned char protection1[protect]; char tampon[25]; unsigned char protection2[protect]; int i; for (i = 0; i < protect; i++) { protection1[i] = 255; protection2[i] = 255; } sprintf(tampon, "Bonjour monsieur %s Salut %d\n", "ABCDEFG", atoi("1234")); cout << tampon; cout << "12345678901234567890123456789012345" << endl; for (i = 0; i < protect; i++) { if (protection1[i] != 255) { cout << "Erreur dans protection1 << protection1[i] << ")" << } if (protection2[i] != 255) { cout << "Erreur dans protection2 << protection1[i] << ")" << } } }

l'index " << i << "(" endl;

l'index " << i << "(" endl;

et son rsultat :
Bonjour monsieur ABCDEFG Salut 1234 12345678901234567890123456789012345 Erreur dans protection1 l'index 0(2) Erreur dans protection1 l'index 1(3) Erreur dans protection1 l'index 2(4) Erreur dans protection1 l'index 3( ) Erreur dans protection1 l'index 4(

Ce morceau de code pourrait sembler vraiment particulier premire vue, mais il nest ici que pour montrer les problmes potentiels de fonctions C comme sprintf(). Les chiers den-tte cstdio et cstdlib sont ncessaires pour disposer des fonctions C que sont sprintf() et atoi(). Nous avons encastr la zone tampon entre deux blocs de chane de caractres, protection1 et protection2, qui sont initialiss avec des octets de bits 1 (255) an de vrier si le programme est crit dans ces zones. Cest effectivement le cas, car le 25 pour la dimension de notre tampon est beaucoup trop petit. Le rsultat nous montre clairement les caractres 234 bien visibles et qui ont dbord. Ce nest pas trs important de comprendre plus en dtail le positionnement exact car il est videmment dpendant de la machine et du compilateur. La corruption pourrait tre beaucoup plus vicieuse, comme crer des erreurs dautres endroits de la mmoire, du systme ou du programme et ne pas se reproduire

Entres et sorties CHAPITRE 9

195

chaque excution ! La solution parfaite ce problme serait en fait de dfendre les tampons protection1 et protection2 avec des moyens hardware, an de protger la mmoire contre lcriture et de gnrer des exceptions par le logiciel. Latoi("1234") nest ici que pour montrer son utilisation, la chane de caractres 1234 tant convertie en un entier qui sera repris par le %d du sprintf(). Pour les dtails de sprintf(), il faudrait consulter la documentation. Nous retiendrons cependant la premire partie, tampon, qui recevra le rsultat du formatage. Dans la deuxime partie, nous avons des parties xes et variables, "Bonjour monsieur %s Salut %d\n". Comme nous avons deux parties variables, %s (pour une chane de caractres) et %d (pour un nombre dcimal), nous obtiendrons aussi deux parties, qui doivent absolument apparatre, les ABCDEFG et atoi("1234"). Les fonctions C sprintf() et son quivalent printf() pour la sortie lcran possdent de nombreuses fonctions de formatage comme celle-ci :
sprintf(tampon,"AA%10.4fBB", 1.2); // rsultat : AA 1.2000BB

Le %10.4f nous indique un nombre avec dix positions et quatre chiffres aprs la virgule. Nous comprenons donc les espaces aprs les deux premiers AA. Cependant, elles peuvent tre avantageusement remplaces par des fonctionnalits quivalentes, qui sont disposition dans les classes strstream ou stringstream.

istringstream et ostringstream
Pour illustrer lutilisation de ces deux classes, nous allons prsenter un exemple simpli qui comporte quelques variantes concrtes et utilisables sans grandes difcults :
// FluxMem.cpp #include <iostream> #include <sstream> #include <string> using namespace std; int main(int argc, char **argv) { // Lit une ligne de la console : cout << "Entrer deux chiffres en HEXA (ex: 12 A0): "; string input; getline(cin, input); // Dcode lentre istringstream is(input); int c1; int c2; is.setf(ios::hex, ios::basefield); // on travaille en hexadcimal is >> c1 >> c2; cout << "test1: " << c1 <<endl; cout << "test2: " << c2 <<endl;

196

Apprendre Java et C++ en parallle

int resultat = c1 * c2; // Affiche le rsultat ostringstream os; os << "Le resultat est " << resultat << endl; cout << os.str(); // Empche la fentre DOS de se fermer cout << "Entrer retour"; getline(cin, input); return 0; }

Aprs avoir compil FluxMem.cpp avec g++ ou le make dans lditeur Crimson, il faudra lexcuter sans capture (voir annexe C) ou alors avec FluxMem.exe depuis lexplorateur de Windows et dans le rpertoire C:\JavaCpp\EXEMPLES\Chap09.

Figure 9-2

Excution de FluxMem.exe

Linstruction :
is >> c1 >> c2;

fonctionne, car nous avons un espace entre les deux chiffres et listringstream possde par dfaut lespace comme sparateur pour loprateur >>. Les instructions suivantes sont pratiques :
cout << "test1: " << c1 <<endl; cout << "test2: " << c2 <<endl;

dans le cas o nous navons pas de dbogueur pour faire du pas pas (voir annexe E, section Prsentation de NetBeans ). Toute la partie du ostringstream fait davantage partie ici dun gadget, car le rsultat aurait pu tre prsent plus simplement : nous envoyons tout dans le stream os avant de le rcuprer avec os.str(). Lexemple ci-dessus pourrait tre tendu linni (avec ou sans sstream), par exemple pour crire un petit calculateur !

Entres et sorties CHAPITRE 9

197

Un exemple complet avec divers formatages


Aprs notre exemple simpli lextrme, nous allons prsenter quelques fonctions de formatage qui se trouvent disposition dans la bibliothque des iostream et sstream. Au l des exemples suivants, nous allons dcouvrir toute une srie doptions de formatage possibles. Dans ce long exemple, nous avons encore gard notre mcanisme de protection, seulement pour nous convaincre quil faut oublier nos anciennes habitudes de programmeur C. Il faut noter ici que ce code est extrmement dlicat et quil faudrait utiliser des outils comme PurifyPlus (Rational Software) pour vrier si tous les tampons sont correctement initialiss et quil ny a pas de fuite de mmoire.
// strstr.cpp #include <iostream> #include <sstream> #include <string> using namespace std; int main(int argc, char* argv[]) { ostringstream sortie1; sortie1 << "Bonjour Monsieur "; sortie1 << "1234567890abcdefg" << ends; cout << "Test1: " << sortie1.str() << endl; ostringstream sortie3; sortie3 << "12345" << ends; sortie3 << "67890" << ends; cout << "Test2: " << sortie3.str() << endl; stringstream entree_sortie1; entree_sortie1 << "abcd\n1234" << ends; cout << "Test3: " << entree_sortie1.str() << endl; stringstream entree_sortie2; int nombre1 = 10; int nombre2 = 20; string un_string1 = "Bonjour"; entree_sortie2 << nombre1 << "." << nombre2 << un_string1 << ends; cout << "Test4: " << entree_sortie2.str() << endl; double double1; entree_sortie2 >> double1; cout << "Test5: " << double1 << endl; string un_string2; entree_sortie2 >> un_string2; cout << "Test6: " << un_string2 << endl;

198

Apprendre Java et C++ en parallle

ostringstream sortie4; sortie4.fill('0'); sortie4.setf(ios::right, ios::adjustfield); sortie4.width(10); double double2 = 1.234; sortie4 << double2; sortie4 << "|"; sortie4.setf(ios::left, ios::adjustfield); sortie4.width(8); sortie4.precision(3); sortie4 << double2; sortie4 << "|"; int nombre5 = 31; sortie4 << hex << nombre5 << ":" << oct << nombre5 << ends; cout << "Test7: " << "[" << sortie4.str() << "]" << endl; return 0; }

Ce code nous donnera le rsultat suivant sans entrer de valeurs comme arguments :
Test1: Test2: Test3: 1234 Test4: Test5: Test6: Test7: Bonjour Monsieur 1234567890abcdefg 12345 abcd 10.20Bonjour 10.2 Bonjour [000001.234|1.230000|1f:37

Le Test1 utilise un ostringstream dynamique. La mmoire sera alloue automatiquement, et ends terminera lentre sur le ux. La mthode str() va nous retourner la chane de caractres. Le Test2 est trs similaire au Test1, mais le premier ends va fermer le ux : le 67890 est perdu. Dans le cas des Test3, Test4, Test5 et Test6, il est intressant de noter lutilisation de stringstream au lieu de ostringstream. Remarquons que notre chane de caractres "abcd\n1234" est sur deux lignes, ce qui explique le rsultat du Test3 sur deux lignes. Dans entree_sortie1, nous pourrions avoir un chier texte complet. Dans le Test4, nous montrons ce qui a t effectivement stock dans entree_sortie2. Dans les Test5 et Test6, nous faisons lextraction dans lautre sens. Nous voyons que nous trichons en quelque sorte, puisque nous traitons un double et un String : double1 et un_string2. Si nous avions un autre format ou une entre totalement alatoire et confuse, nous devrions traiter les erreurs correctement.

Entres et sorties CHAPITRE 9

199

Le Test7, qui est en effet un concentr doptions de formatage, nous sortira le rsultat suivant, compos de trois morceaux :
[000001.234|1.230000|1f:37]

Les deux premires parties sont composes du nombre 1.234, qui est une fois align droite avec setf(ios::right, ios::adjustfield) et lautre fois gauche. Nous contrlons le remplissage, gauche ou droite, grce fill('0'), mais le caractre 0 pourrait trs bien tre remplac par autre chose. La largeur des champs, qui vaut 10 et 8, est modiable avant loprateur << par la mthode width(). Le deuxime nombre a-t-il perdu des chiffres ? Non, car 1.23 reprsente une prcision de trois chiffres, indpendamment du nombre de chiffres aprs la virgule. Enn, le 1f:37 reprsente la nombre dcimal 33 dans les formats hexadcimal et octal.

Le printf Java du JDK 1.5


La fonction printf() est une fonction bien connue des programmeurs C. Nous avons crit un petit morceau de code en C :
// printfC.c #include <stdio.h> //.c et non .cpp //du C pas du C++

main() { int nombre = 17; char *text = "hexa"; printf("Le nombre %d est %x en %s\n", nombre, nombre, text); }

Le rsultat nous donne :


Le nombre 17 est 11 en hexa

Les %d, %x et %s (dcimal, hexadcimal et string) vont prendre et convertir les donnes des trois variables qui suivent la partie "" ! Dans le Makefile de ce chapitre, nous avons ajout une entre c et un appel au compilateur C de cette manire :
gcc -o printfC.exe printfC.c

En revanche, en ditant avec Crimson le chier printF.c, nous pourrons tout aussi bien le compiler avec le compilateur C++, cest--dire g++ dans le menu. Depuis la version 1.5 de Java, nous pouvons crire le mme programme qui donnera le mme rsultat :
import java.io.*; class Printf { public static void main (String args[]) { int nombre = 17;

200

Apprendre Java et C++ en parallle

String text = "hexa"; System.out.printf("Le nombre %d est %x en %s\n", nombre, nombre, text); } }

istrstream et ostrstream
Si le lecteur rencontre ces anciennes formes dprcies (ditions prcdentes de cet ouvrage), il devra les convertir avec les stringstream. Lors de la compilation de ces anciennes classes avec g++ (version rcente), les messages derreur nous aiderons dans ce travail. Si le langage C++ avait t dvelopp ds le dpart avec une bibliothque standard et une classe String comme en Java, nous naurions peut-tre jamais invent les strstream ni entendu parler deux. Nous allons maintenant prsenter dautres exemples varis que nous devrions rencontrer dans la programmation de tous les jours.

Formatage en Java
Passons prsent au formatage et la conversion en Java, car nous nous trouvons dans le mme contexte : cest en effet lune des premires difcults rencontres par le programmeur. Nous allons reprendre ici un certain nombre de formes dj utilises, que nous utilisons par exemple pour la conversion des arguments reus par le main(). Cest une sorte de synthse titre comparatif puisquen C++ les ux seront utiliss en gnral. La mthode println() de la classe System.out applique lopration suivante :
int valeur1 = 8; double valeur2 = 1423.35 * 0.033; System.out.println(valeur1 + ":" + valeur2);

et qui nous sortira le rsultat suivant :


8:46.970549999999996

ne nous donnera certainement pas satisfaction. Ce rsultat ne correspondra sans doute pas celui dsir, avec uniquement deux dcimales imprimes, des 0 ou des espaces gauche et un alignement prcis du style :
00008:00046.97

Ce format peut tre obtenu, dune manire extrmement simple, grce au code suivant :
DecimalFormat df1 = new DecimalFormat("00000"); DecimalFormat df2 = new DecimalFormat("00000.00"); System.out.println(df1.format(valeur1) + ":" + df2.format(valeur2));

La classe DecimalFormat, dune simplicit dconcertante, nous permet dobtenir le rsultat dsir. Les 0 indiquent la position des chiffres, ces derniers pouvant prendre toutes les valeurs, y compris 0. Il faut tre trs attentif aux rsultats dans les cas o nous dpasserions

Entres et sorties CHAPITRE 9

201

les limites indiques. Il faut vraiment vrier si cela correspond notre spcication, car nous pourrions tre surpris :
System.out.println(df2.format(7654321.999));

Dans ce cas, nous sommes en mesure de nous poser deux questions : 1. Que se passe-t-il avec le .999 car nous avons choisi deux dcimales ? 2. Notre maximum spci dans df2 est en principe 99999.99. Que va-t-il se passer ? Le rsultat ne nous surprendra qu moiti :
7654322.00

Filtrer du texte en Java avec StringTokenizer et StreamTokenizer


Un token, en anglais, signie une marque. En effet, lorsque nous voulons analyser, par exemple, une chane de caractres, nous pourrions utiliser une ou plusieurs marques pour identier, ltrer ou chercher des mots ou des parties de mots. Lune des premires applications est lextraction de donnes. Il est souvent plus facile dutiliser StringTokenizer au lieu de se lancer dans du code complexe utilisant les mthodes de la classe String, ou dans une recherche caractre par caractre. Outre la classe java.util.StringTokenizer, qui utilise comme entre un String, la bibliothque Java possde une autre classe, le java.io.StreamTokenizer, qui a lui besoin dun InputStream comme entre. Cet InputStream peut reprsenter un chier ou encore correspondre une entre de donnes directement sur la console. Cest ce cas-l que nous avons choisi dans lexemple qui suit. Nous commencerons par trois exemples de ltres avec la classe StringTokenizer :
import java.util.StringTokenizer; import java.io.*; class MonTokenizer { public static void main(String[] args) { StringTokenizer st = new StringTokenizer("Nous faisons un premier test"); while (st.hasMoreTokens()) { System.out.println(st.nextToken()); } st = new StringTokenizer("Nous\tfaisons un\r \n \r deuxime \ntest"); while (st.hasMoreTokens()) { System.out.println(st.nextToken()); } st = new StringTokenizer("Ma;base;de;donne", ";"); while (st.hasMoreTokens()) { System.out.println(st.nextToken()); } Reader lecteur = new BufferedReader(new InputStreamReader(System.in));

202

Apprendre Java et C++ en parallle

StreamTokenizer streamt = new StreamTokenizer(lecteur); try { while (streamt.nextToken() != StreamTokenizer.TT_EOF) { switch (streamt.ttype) { case StreamTokenizer.TT_NUMBER: System.out.println("Un nombre: " + streamt.nval); break; case StreamTokenizer.TT_WORD: System.out.println("Un mot: " + streamt.sval); break; default: System.out.println("Un autre type: " + (char)streamt.ttype); break; } } } catch (IOException e) { System.out.println("IOException: " + e); } } }

Les deux premiers constructeurs de StringTokenizer ne reoivent pas de second paramtre : cela signie que le dfaut sera utilis. Celui-ci correspond lespace, au tabulateur et aux nouvelles lignes (PC et Linux inclus). Le rsultat de cette partie :
Nous faisons un premier test Nous faisons un deuxime test

nous montre que les espaces et autres caractres de sparations multiples sont bien traits. Pour le deuxime objet, si nous avions utilis la forme :
st = new StringTokenizer("Nous\tfaisons un\r \n \r deuxime \ntest", "x\n\r\t ");

nous aurions obtenu le mme rsultat, sauf que le mot deuxime aurait t coup en deux en perdant le x puisque nous laurions trait comme une marque (token). Le troisime objet de StringTokenizer utilise cette fois-ci une sparation avec le caractre ;. Cest le cas classique dune base de donnes :
Ma base de donnes

Entres et sorties CHAPITRE 9

203

qui pourrait tre par exemple exporte depuis Microsoft Access. Nous noterons que la classe StringTokenizer possde les mthodes hasMoreTokens() et nextToken(), qui nous permettent de nous promener de morceau morceau. Nous terminons notre exemple avec le StreamTokenizer, dont nous avons initialis le Reader depuis la console (System.in). Au contraire de StringTokenizer, nous ne pouvons pas dnir les caractres qui seront utiliss comme dlimiteur. La classe StreamTokenizer va nous retourner le type du morceau quil a ltr. Aprs avoir rceptionn le prochain morceau avec nextToken(), nous vrions son type avec ttype. Ici, nous avons vri TT_NUMBER et TT_WORD spcialement titre dexercice. An de ne pas toujours entrer les mmes caractres, nous avons introduit le texte suivant dans le chier test.txt :
abcd 1234 xyz unTabSuit aprsLeTab ligneVide_avant

et entr la ligne de commande :


type test.txt | java MonTokenizer

Nous avons obtenu pour la dernire partie du programme le rsultat :


Un Un Un Un Un Un Un Un mot: abcd nombre: 1234.0 mot: xyz mot: unTabSuit mot: aprsLeTab mot: ligneVide autre type: _ mot: avant

avec le cas particulier du caractre _. Cest relativement puissant, mais encore trs loin dune tokenisation pour une chane de caractres telle que "<a href="mailto:jeanbernard@boichat.ch">Email at home</a>".

Rsum
Nous savons prsent matriser la lecture et lcriture de chiers. Un grand nombre dapplications utilisent des donnes stockes sur le disque, les analysent, les transforment et peuvent aussi les sauvegarder nouveau pour une utilisation future. Nous avons vu aussi comment former et ltrer des chanes de caractres en mmoire, en utilisant des techniques et oprateurs similaires la lecture et lcriture de chiers sur le disque.

Exercices
Les quatre premiers exercices seront cods en Java et C++. Nous avons crit un certain nombre dexercices, mais avec la tentation den crire beaucoup plus. Ce chapitre est non seulement lun des plus importants, mais aussi lun des plus intressants en ce qui

204

Apprendre Java et C++ en parallle

concerne les possibilits dcriture de petits programmes dapprentissage. Un rsultat sous forme de chier est toujours plus concret et motivant, donnant au programmeur le sentiment davoir vraiment cr quelque chose. 1. crire un programme de copie de chier binaire. Prendre lexcutable compil en C++ comme exemple et vrier que sa copie est identique en lexcutant ou en utilisant lexercice 3. 2. crire un programme qui lit un chier texte Linux (respectivement DOS) et le convertit en chier texte DOS (respectivement Linux). 3. crire un programme de comparaison de deux chiers binaires. Si les chiers sont diffrents, donner lindex et la valeur du premier octet diffrent. 4. Mme exercice que le 3, mais pour un chier texte avec le numro et la position de la premire ligne diffrente. 5. Sauver en C++ une partie dOthello en format texte simple, comme nous lavons fait prcdemment en Java dans la classe WriteOthello. 6. partir du programme listdir.cpp en C++ et de la classe ListDir en Java, crire dans les deux langages une classe ListeRepertoires qui va pouvoir prsenter une liste de tous les chiers dans le rpertoire slectionn et ses sous-rpertoires. Il sagit dun exercice dune certaine complexit qui pourrait mme tre tendu ou rutilis pour lcriture doutils de maintenance ou de statistique.

10
Variations sur un thme de classe
Le constructeur par dfaut en C++
Lorsque nous crons une instance dune classe, nous utilisons le code disponible dans le constructeur. Ce dernier peut tre surcharg. Si nous dnissons une classe Personne, nous pourrions imaginer crer des instances de cette classe de ces trois manires :
Personne personne1("Nom1", "Prnom1"); Personne personne2("Nom2"); Personne personne3(15286);

dont il faudrait dnir le code suivant dans la dnition de la classe :


Personne(const char *pnom, const char *pprenom); Personne(const char *pnom); Personne(int numero);

Au lieu dun const char *pnom, nous pourrions aussi utiliser un const string ou encore un const &string. Le numero pourrait tre pass diffremment, comme un string. Pour linstant, dans ce contexte particulier, ce nest pas trs important. Ces trois formes nous indiquent quil y a au moins trois constructeurs surchargs et que notre classe doit pouvoir conserver le nom, le prnom et un code didentication. Si le premier constructeur peut nous sembler sufsant, les autres formes doivent nous rendre attentifs. Ces dernires nous indiquent que, probablement, le prnom peut tre omis et quun numro demploy ou autre code didentication devrait permettre de rechercher les donnes avec un autre mcanisme. Cependant, aucune information ne nous est donne : par exemple, le prnom doit-il tre spci plus tard au travers dune mthode,

206

Apprendre Java et C++ en parallle

et comment sera utilis ce fameux numro ? Si quelquun venait nous dire que, dans les trois cas, une base de donnes sera consulte, nous pourrions alors imaginer les cas suivants : Pour le constructeur ("Nom1", "Prnom1"), une nouvelle entre dans la base de donnes sera cre avec une identication qui correspond ce fameux numro. Sinon les donnes existantes de la base de donnes seront attribues aux diffrents attributs de la classe Personne. Pour le cas ("Nom2"), le constructeur nacceptera la requte que si le prnom est unique pour ce nom dans la base de donnes. Si nous navons pas encore dentre, la requte sera rejete. La dernire forme du constructeur sera aussi accepte si le numro est dj allou. Il y a bien sr dautres cas de gure tout aussi acceptables. Un aspect essentiel toutes ces considrations est le traitement des erreurs. Il faudra prvoir soit une exception gnre dans le constructeur et capture par lapplication, soit un code derreur vri par toutes les mthodes qui pourraient accder ces objets, car ces derniers ont pu ne pas tre initialiss correctement. Enn, nous devons nous poser cette question essentielle : que se passerait-t-il si nous crivions ceci :
Personne personne4;

Cette forme ne sera accepte que si aucun constructeur nest dni ! Dans le cas prcis de nos trois exemples de constructeur de la classe Personne, il est inutile, en principe, de prvoir un constructeur par dfaut. Cette dernire ne doit pas tre confondue avec des constructions telles que :
Personne(0); Personne("");

Dans la premire construction, le pointeur nul serait le 0, et, dans la seconde, le nom vide de la personne. Ce sont des cas qui ne devraient jamais se produire ou sinon qui devraient tre contrler, explicitement, dans le code du constructeur affect. Nous pouvons aussi viter lutilisation dun constructeur par dfaut, en utilisant cette forme :
class Personne { private: Personne() { } }; int main() { Personne p1; }

Variations sur un thme de classe CHAPITRE 10

207

Si nous essayions de compiler ce code, nous obtiendrions :


personne.cpp: In function `int main()': personne.cpp:3: `Personne::Personne()' is private personne.cpp:8: within this context

Le programmeur comprendra quil y a un problme et devra adapter son code. La forme suivante peut nous entraner des complications :
Personne personne[100];

Si nous crivions :
class Personne { public: Personne(const char *pnom, const char *prenom) {} }; int main() { Personne personnes[100]; }

nous aurions une erreur de compilation :


personne.cpp: In function `int main()': personne.cpp:8: no matching function for call to `Personne::Personne ()' personne.cpp:3: candidates are: Personne::Personne(const char *, const char *) personne.cpp:4: Personne::Personne(const Personne &)

Cela nous indique que le constructeur par dfaut, Personne() {}, doit absolument tre cod. Cest ce que nous ferons comme exercice. Il existe galement une autre construction dont il faut se mer :
Personne(int numero = 0) {}

Il sagit de la troisime forme de constructeur que nous avions dnie en dbut de chapitre, mais avec un paramtre par dfaut. Dans ce cas, si nous avions :
class Personne { public: Personne(int numero = 0) {} }; int main() { Personne personnes[100]; }

les 100 objets auraient alors le mme numro, et ce nest certainement pas ce que nous dsirons. Pour la forme :
Personne(const char *pnom, const char *pprenom = 0);

208

Apprendre Java et C++ en parallle

ce serait diffrent. Cela pourrait signier que nous ne connaissons que le nom de la personne et pas encore son prnom. Linstruction :
Personne personne("Haddock");

serait accepte. La forme :


Personne(const char *pnom);

pourrait alors tre omise. Enn, il nous faut parler dune autre faon de programmer, lorsque plusieurs constructeurs sont ncessaires et que le code doit tre rpt. Cela consiste crire une mthode, prive en gnral, qui contiendra la partie commune, principalement lorsquelle renferme du code relativement long. Cette manire de faire est aussi applicable en Java, sans lutilisation de paramtres par dfaut :
class Personne { private: code_commun(.....) { // ...... } public: Personne(......) { // constructeur 1 // code de prparation code_commun(.....); } Personne(..........) { // constructeur 2 // code de prparation code_commun(.....); } };

Nous rpterons encore une fois quil faut limiter lallocation de ressources dans les constructeurs, car elle pourrait ncessiter un traitement derreurs, si bien que le constructeur ne retournerait jamais de rsultat directement. Nous remarquons en fait que le langage C++ met notre disposition toute une srie de techniques pour rsoudre nos problmes. Nous constatons ici quil est en fait plus facile de matriser ces techniques que de dnir une conception correcte et propre, dans le cas prsent, de notre classe Personne. Nous avons vraiment le sentiment dun ou parfait et que tout reste faire. Une analyse plus profonde des fonctionnalits de notre classe est absolument ncessaire. Savons-nous vraiment ce que nous voulons ? Sommes-nous certains que cela correspondra aux exigences du produit que nous voulons distribuer nos clients ? Dnir le ou les constructeurs dune classe, ainsi que linitialisation correcte des variables prives, cest--dire des attributs de cet objet, est donc essentiel dans la conception de logiciels programms en Java et C++.

Variations sur un thme de classe CHAPITRE 10

209

Le constructeur de copie en C++


Aprs le constructeur par dfaut, il nous faut considrer le constructeur de copie. Si nous crivions :
Personne personne1("Nom1", "Prnom1"); Personne personne2(personne1);

le nouvel objet personne2 devrait tre cr avec les mmes attributs que le premier. Nous allons voir prsent comment traiter correctement ce problme, bien que nous puissions nous poser la question de lutilit de produire une deuxime copie dun objet de la classe Personne. Ce serait diffrent dans le cas dune classe reprsentant une partie du jeu dchecs, dans laquelle nous pourrions essayer une combinaison particulire ou certaines variations sur la partie courante, sans toucher loriginal.

Le constructeur de copie par dfaut


Dans lexemple qui suit, nous avons ajout un attribut annee_naissance une classe nomme PersonneA. Cet attribut ne sera affect que par une mthode spare, en dehors du constructeur. Nous allons ensuite crer une seconde instance de cette classe PersonneA, en utilisant un constructeur de copie :
// personnea.cpp #include <iostream> #include <string> using namespace std; class PersonneA { private: string nom; string prenom; int annee_naissance; public: PersonneA(const char *plenom, const char *pleprenom) { nom = plenom; prenom = pleprenom; annee_naissance = -1; } void set_annee(int lannee) { annee_naissance = lannee; } void test() { cout << nom << " " << prenom << " : " << annee_naissance << endl; } };

210

Apprendre Java et C++ en parallle

int main() { PersonneA personne1("LeRoi", "Louis"); personne1.set_annee(1978); personne1.test(); PersonneA personne2(personne1); personne2.test(); return 0; }

Si nous excutons ce programme, nous obtenons :


LeRoi Louis : 1978 LeRoi Louis : 1978

Le constructeur de copie par dfaut sera gnr par le compilateur et excut, puisque nous ne lavons pas cod, comme nous le verrons plus loin. Le rsultat de linstruction :
PersonneA personne2(personne1);

sera une copie de toutes les valeurs des attributs dans le nouvel objet. Nous aurons donc deux objets diffrents, car personne2 est non pas un pointeur, mais bien un objet spar dont les caractristiques sont les mmes. Un pre donne parfois son prnom son ls, mais lanne de naissance ne peut tre identique dans ce cas prcis. Ce qui est important ici, cest linstruction :
annee_naissance = -1;

dans le constructeur gnral. Avec -1, nous indiquons par exemple que lanne de naissance na pas t spcie, car nous pourrions avoir un autre constructeur avec ce paramtre, ou que cette donne devra tre indique plus tard, au travers dune mthode comme set_annee(). Dans le cas dun constructeur de copie, la valeur -1 pour lanne de naissance est tout fait justie. Nous pourrions imaginer dautres variantes, et nous le verrons en exercice, aprs la prsentation de la forme de ce constructeur. La mthode set_annee() pourrait tre beaucoup plus complexe, comme accder une base de donnes ou calculer dautres paramtres. Elle pourrait tre dnie comme suit :
bool set_annee(int lannee) { .... }

Le rsultat de lopration pourrait aussi tre retourn, ce qui nest pas le cas pour un constructeur. Utiliser des mthodes de ce type peut apporter une simplication au traitement des erreurs et aux contraintes de notre design.

La forme du constructeur de copie


Il est donc prfrable et conseill de toujours dnir un constructeur de copie. Il aura, dans notre cas, la forme suivante :
inline Personne::Personne(const Personne &pers) :nom(pers.nom), prenom(pers.prenom) { };

Variations sur un thme de classe CHAPITRE 10

211

Cette forme se rencontre gnralement, car elle est simple et efcace. Dans ce cas, lutilisation dinline est correcte, mais ne serait pas ncessaire si nous avions dni le code dans le chier den-tte (automatiquement inline). Comme pour le constructeur par dfaut, il est aussi tout fait possible de fermer la porte un constructeur de copie. Si nous voulons que le compilateur rejette linstruction :
Personne personne2(personne1);

an dempcher le programmeur dutiliser cette forme pour une raison ou pour une autre, nous pourrions alors dnir ceci :
private Personne::Personne(const Personne &pers);

qui provoquerait une erreur de compilation. Nous reprenons prsent notre exercice prcdent, la classe PersonneA, que nous avons analyse pour le constructeur par dfaut et que nous allons adapter avec un constructeur de copie :
// personneb.cpp #include <iostream> #include <string> using namespace std; class PersonneB { private: string nom; string prenom; int annee_naissance; public: PersonneB(const char *plenom, const char *pleprenom) { nom = plenom; prenom = pleprenom; annee_naissance = -1; } PersonneB(const PersonneB &une_autre_personne) { nom = une_autre_personne.nom; prenom = une_autre_personne.prenom; annee_naissance = -1; } void set_annee(int lannee) { annee_naissance = lannee; } void test() { cout << nom << " " << prenom << " : " << annee_naissance << endl; }

212

Apprendre Java et C++ en parallle

}; int main() { PersonneB personne1("LeRoi", "Louis"); personne1.set_annee(1978); personne1.test(); PersonneB personne2(personne1); personne2.test(); return 0; }

Ici, chaque attribut est copi un un. Nous voyons donc la diffrence comme pour :
nom = une_autre_personne.nom;

une_autre_personne.nom est bien un attribut priv de lobjet dorigine, mais notre code possde les droits daccder des attributs de cette mme classe. La diffrence rside dans notre instruction :
annee_naissance = -1;

qui indique que lanne de naissance na pas encore t spcie. Nous le voyons avec le rsultat, qui nous satisfait cette fois-ci :
LeRoi Louis : 1978 LeRoi Louis : -1

Cest videmment un choix de conception qui variera de cas en cas. Nous ne pourrions faire de mme avec une copie du jeu dchecs et un attribut qui nous indiquerait que le joueur est en position dchec, cest--dire quil naurait pas le droit nimporte quel dplacement. Nous devons soigneusement analyser quels attributs doivent tre copis ou modis, comme le cas dun compteur du nombre dinstance de cette classe.

Ne pas confondre constructeur et affectation


Pour terminer, il nous faut revenir sur les deux formes suivantes :
Personne personne2(personne1); personne2 = personne1;

Elles reprsentent un constructeur et une affectation. Ces deux formes sont trs souvent confondues et prises lune pour lautre, principalement pour le rsultat attendu. Nous avons dj analys la premire forme dans ce chapitre, alors que la deuxime, celle de laffectation, sera traite au dbut du chapitre suivant. Nous esprons que le lecteur se sera rendu compte dun problme srieux. Comment peut-on utiliser un constructeur de copie pour cette classe Personne qui va affecter deux objets diffrents le mme nom et le mme prnom ? Nous savons que ce cas est tout fait vraisemblable et na rien voir avec cette partie thorique et essentielle du langage. Notre classe Personne est trs loin dune laboration complte et sans erreurs de conception.

Variations sur un thme de classe CHAPITRE 10

213

Les outils et les constructions disposition dans un langage nous permettent en fait de vrier la conception de nos classes et de nos applications. Que des personnes puissent avoir le mme nom et le mme prnom a en fait une relation directe avec les contraintes et besoins de lapplication ou du produit. Cependant, les ressources du langage peuvent nous aider dcouvrir les faiblesses de la spcication du systme et corriger, modier ou mieux prciser ces dernires.

Le constructeur par dfaut en Java


Il y a quelques diffrences signicatives quand on passe du C++ au Java. La complexit du C++ provient principalement du fait que les attributs de classes peuvent tre de diffrents types. Nous avons vu par exemple la difcult du constructeur de copie avec des attributs pointeurs. La premire remarque en Java va porter sur la construction :
String unNom;

Si nous crivons la classe suivante, pour utiliser la mthode toLowerCase(), qui consiste convertir en minuscules un objet String qui na pas t initialis :
class Test { public static void main (String[] args) { String unNom; unNom.toLowerCase(); } }

et que nous essayons de compiler ce programme, nous obtiendrons ceci :


Test.java:5: Variable unNom may not have been initialized. unNom.toLowerCase(); ^ 1 error

Cest propre et clair. La variable doit absolument tre initialise. Ce mcanisme de protection nexiste pas en C++, qui, de plus, aurait deux formes disposition :
string un_nom; string *pun_nom;

De ce point de vue, ces deux langages sont trs diffrents ! Si nous crivons prsent cette classe de test :
class Test { private String unNom; private boolean flag; private int nombre; public void unTest() { unNom.toLowerCase(); if (flag == true);

214

Apprendre Java et C++ en parallle

if (nombre == 1); } public static void main (String[] args) { Test test = new Test(); test.unTest(); } }

nous y dcouvrons que les variables unNom, flag et nombre ne sont pas initialises. Le compilateur ne peut le voir, et cest seulement lexcution que nous dcouvrirons le problme :
Exception in thread "main" java.lang.NullPointerException at Test.unTest(Compiled Code) at Test.main(Compiled Code)

Cela est d au fait quun constructeur par dfaut sera excut selon les rgles suivantes : les attributs numriques de la classe prendront la valeur 0 ; les boolens recevront la valeur false ; les variables objets seront initialises null.
unNom sera donc null et provoquera notre java.lang.NullPointerException.

Il y a plusieurs solutions ce problme spcique, comme :


if (unNom != null) { unNom.toLowerCase(); }

Nous dirons que cette solution ne nous plat pas ! Nous prcherions pour un meilleur design et lcriture dun constructeur. Pour la classe Personne et les attributs nom et prenom, il faudrait dnir une rgle. Un String vide pourrait indiquer que lattribut nest pas connu, pourrait ne jamais tre identi ou devrait absolument tre attribu dans une phase ultrieure. Nous pouvons revenir notre classe Test et prsenter cette fois-ci une implmentation sans constructeur par dfaut :
class Test { private String unNom; private boolean flag; private int nombre; public Test() { unNom = ""; flag = true; nombre = -1; } public void unTest() {

Variations sur un thme de classe CHAPITRE 10

215

unNom.toLowerCase(); if (flag == true) { // quelque chose }; if (nombre == 1){ // quelque chose }; } public static void main (String[] args) { Test test = new Test(); test.unTest(); } }

Il serait videmment bienvenu dcrire dans le code une documentation complte pour la valeur de ces attributs, qui pourraient alors apparatre dans les documents HTML produits par javadoc, que nous avons utiliss au chapitre 4.

Le constructeur de copie en Java


Nous ne parlons pas de constructeur de copie par dfaut en Java, car il nexiste pas. Dans lexemple qui suit :
class ObjetCopiable { private String objet; private int valeur; public ObjetCopiable(String nom, int prix) { objet = nom; valeur = prix; System.out.println(objet + " : " + valeur); } public ObjetCopiable(ObjetCopiable oc) { objet = oc.objet; valeur = (int)(oc.valeur * 0.9); System.out.println(objet + " : " + valeur); } public static void main (String[] args) { ObjetCopiable co1 = new ObjetCopiable("Statuette", 1000); ObjetCopiable co2 = new ObjetCopiable(co1); } }

Nous voyons quil nous faut dnir un constructeur spcique. co2 sera bien un autre objet, une deuxime statuette laquelle nous dcidons dattribuer une valeur diminue de 10 %, puisquelle nest plus unique. Dans la ralit, nous pourrions augmenter le prix de la deuxime si la premire se vend rapidement ou encore diminuer fortement leurs valeurs

216

Apprendre Java et C++ en parallle

si elles restent invendues pendant une priode trop longue. Le mot de la n serait sans doute de devoir conserver le prix de revient de nos objets, en tenant compte de la location du magasin et de lamortissement des tagres !

Les variables et mthodes statiques dune classe


Nous avons dj rencontr le mot-cl static en C++ pour les variables globales. Il nous faut prsent le considrer dans le contexte des classes Java et C++. Dans les deux langages, nous avons la mme dnition, bien que sa syntaxe diffre lgrement. Le mot-cl static dsigne des variables ou des mthodes qui appartiennent toutes les instances dune classe dtermine. Une variable statique pourra tre modie par toutes les instances de sa classe, condition quelle ne soit pas constante (const en C++ ou final en Java). Comme nous prfrons garder les variables prives, nous pourrons accder une variable statique avec une mthode statique et publique. Une mthode statique ne peut accder des variables de classe traditionnelles, car une instance de cette classe doit tre utilise. Nous aimons bien le terme dattribut de classe pour dsigner ces variables statiques. Ce terme vient du langage Smalltalk. Nous allons voir les dtails et la syntaxe au travers de deux exemples en Java et C++.

Nous tirons un numro


Lexemple que nous avons choisi va nous permettre de tirer un numro entre 1 et 10 et de le rserver. Chaque instance de la classe aura donc un numro unique entre 1 et 10. Ce numro pourra tre libr lorsque linstance disparat. Si plus de dix instances sont cres, les nouvelles recevront le numro 0, jusquau moment o lune des dix rserves sera libre. Nous ne pousserons pas lexercice jusqu attribuer un numro libr au premier objet auquel le numro 0 avait t attribu. Seuls les nouveaux venus seront ventuellement des veinards. Comme nous demanderons rgulirement le nombre de numros allous, la mthode pour faire ce travail devra tre rapide, sans toutefois rendre publics les attributs de la classe.

En C++
Nous passons immdiatement la prsentation du code pour notre problme, qui semble avoir t bien dni :
// tirenumero.cpp #include <iostream> #include <string> using namespace std; class TireNumero {

Variations sur un thme de classe CHAPITRE 10

217

private: static int nombre_sorti; static bool *ptirage; int quel_numero; public: TireNumero() { if (ptirage == 0 ) { // premier usage ptirage = new bool[10]; for (int i = 1; i < 10; i++) { *(ptirage + i) = false; } quel_numero = 1; *ptirage = true; nombre_sorti = 1; return; } if (nombre_sorti == 10) { quel_numero = 0; } else { nombre_sorti++; for (int i = 0; i < 10; i++) { if (*(ptirage + i) == false) { *(ptirage + i) = true; quel_numero = i + 1; break; } } } } ~TireNumero() { if (quel_numero != 0) { nombre_sorti--; *(ptirage + quel_numero - 1) = false; if (nombre_sorti == 0) { delete ptirage; ptirage = 0; cout << "Tous les numros sont rentrs" << endl; } } } int get_numero() { return quel_numero; } static int get_nombre_sorti() { return nombre_sorti; }

218

Apprendre Java et C++ en parallle

static void message1() { cout << "Nombre sorti: " << get_nombre_sorti() << endl; } void message2() { cout << "Nombre actuel: " << get_numero() << endl; } }; int TireNumero::nombre_sorti = 0; bool *TireNumero::ptirage = 0; int main() { TireNumero *ptn; TireNumero tn1; TireNumero::message1(); tn1.message2(); TireNumero tn2; TireNumero::message1(); tn2.message2(); ptn = new TireNumero(); TireNumero::message1(); ptn->message2(); delete ptn; TireNumero::message1(); ptn = new TireNumero(); TireNumero ttn[20]; TireNumero::message1(); ttn[19].message2(); delete ptn; TireNumero tn3; tn3.message1(); tn3.message2(); ttn[5].message2(); cout << "Fin des tests" << endl; }

La premire remarque concerne le compilateur. Il nous indiquera immdiatement si les mots-cls sont correctement positionns ou si nous accdons par exemple la variable quel_numero partir dune mthode statique, ce qui est impossible. La partie nouvelle consiste ici en linitialisation des variables statiques :
int TireNumero::nombre_sorti = 0; bool *TireNumero::ptirage = 0;

Variations sur un thme de classe CHAPITRE 10

219

Elles ne doivent apparatre quune seule fois dans le code et devraient se trouver dans le code de la classe, car, nouveau pour des raisons de prsentation, tout le code se trouve dans le mme chier. Lutilisation dun tableau dynamique ptirage rend le code plus complexe, mais plus exible. Nous pourrions, dynamiquement, attribuer un plus grand nombre de numros disposition. Les variables nombre_sorti et ptirage sont donc statiques. Le grand travail se fait principalement dans le constructeur, avec lallocation mmoire de ptirage. Nous retrouverons ce dernier dans le destructeur, o toutes les ressources devraient tre effaces. Le rsultat :
Nombre sorti: 1 Nombre actuel: 1 Nombre sorti: 2 Nombre actuel: 2 Nombre sorti: 3 Nombre actuel: 3 Nombre sorti: 2 Nombre sorti: 10 Nombre actuel: 0 Nombre sorti: 10 Nombre actuel: 3 Nombre actuel: 9 Fin des tests Tous les numros sont rentrs

devrait nous convaincre que le programme semble fonctionner correctement. Nous avons une longue liste de tests, o les delete permettent de rcuprer des numros. Le ttn[5] est le sixime composant dans le tableau ttn. Comme nous avons dj trois autres numros allous, le dernier 9 est vraisemblablement correct. Le dernier tn3.message1(); est volontaire, car il est possible dappliquer une mthode statique un objet de cette classe. La position dans le tableau commence 0 et nos numros 1. Cela explique les +1 et -1 dans notre code. Les mthodes message1() et message2() ne sont l que pour les tests et vrier le comportement des mthodes et variables statiques.

En Java
Nous prsentons dabord le code Java, avant de passer aux dtails et aux diffrences, relativement importantes, par rapport la version C++ :
public class TireNumero { private static int nombreSorti; private static boolean tirage[]; public int quelNumero; public TireNumero() { if (tirage == null) { tirage = new boolean[10]; }

220

Apprendre Java et C++ en parallle

if (nombreSorti == 10) { quelNumero = 0; } else { nombreSorti++; for (int i = 0; i < 10; i++) { if (tirage[i] == false) { tirage[i] = true; quelNumero = i + 1; break; } } } } public void dispose() { if (quelNumero != 0) { nombreSorti--; tirage[quelNumero - 1] = false; } } public int getNumero() { return quelNumero; } static int getNombreSorti() { return nombreSorti; } static void message1() { System.out.println("Nombre sorti: " + getNombreSorti()); } void message2() { System.out.println("Nombre actuel: " + getNumero()); } public static void TireNumero tn1 = TireNumero tn2 = TireNumero tn3 = main(String[] args) { new TireNumero(); new TireNumero(); new TireNumero();

tn1.message2(); tn3.message2(); TireNumero.message1(); tn2.dispose(); tn1 = new TireNumero(); // !!! tn1.message2(); message1();

Variations sur un thme de classe CHAPITRE 10

221

TireNumero ttn[] = new TireNumero[10]; for (int i = 0; i < 10; i++) { ttn[i] = new TireNumero(); } ttn[4].message2(); message1(); for (int i = 0; i < 10; i++) { ttn[i].dispose(); } tn1.dispose(); tn2.dispose(); // !!! tn3.dispose(); ttn[9].message1(); } }

Nous avons modi quelques fonctionnalits que nous avions certainement apprcies dans la version C++. En fait, reprendre le design dune application dveloppe dans un langage et lappliquer directement dans un autre nest pas toujours la meilleure solution. Nous le verrons trs rapidement avec la libration de nos objets. Nous savons dj quil ny a pas de destructeur en Java. Pour librer un numro allou, nous avons introduit une mthode dispose() pour remplacer le destructeur en C++. Si nous examinons le rsultat :
Nombre Nombre Nombre Nombre Nombre Nombre Nombre Nombre actuel: 1 actuel: 3 sorti: 3 actuel: 2 sorti: 3 actuel: 8 sorti: 10 sorti: 0

il nous semble correct. La valeur 8 retourne vient de notre ttn[4].message2();, cest-dire du cinquime lment du tableau. Nous dirons que nous avons plus ou moins allou trois objets, puisque le code :
tn2.dispose(); tn1 = new TireNumero(); // !!!

a t mal crit volontairement. Nous aurions d utiliser dispose() sur lobjet tn1, et non tn2. Aprs la deuxime instruction, lancien tn1 partira dans le ramasse-miettes. En fait, nous avons le mme problme en C++ avec des pointeurs si nous oublions un delete. Le dernier 0 peut tre considr comme un accident, car nous avons juste appel la mthode dispose() le bon nombre de fois. Nous navons pas vri par exemple quelle

222

Apprendre Java et C++ en parallle

pourrait tre appele deux fois ou oublie. Ce dernier cas est beaucoup plus complexe et se rsout de manire diffrente en C++. Il ny a pas de grand mystre dans ce code, sinon quil ne faut pas oublier lallocation des dix lments du tableau tirage avant de les utiliser. Les autres constructions sont similaires aux commentaires que nous avons faits dans la version C++. La dernire instruction :
ttn[9].message1();

est tout fait correcte. Nous pouvons aussi appliquer une mthode statique sur un objet de sa classe, et lobjet ttn[9] existe encore, bien que nous eussions d viter toute utilisation dobjets aprs lappel la mthode dispose().

nalize() en Java
Nous devons mentionner quil existe tout de mme une mthode finalize(), que nous pouvons ajouter nimporte quelle classe. finalize() sera appele par le ramasse-miettes. La mthode statique System.runFinalizersOnExit(true) peut tre excute pour sassurer que les finalize() seront appeles avant la terminaison du programme. Nous aimerions cependant suggrer, comme dans lexemple de la classe TireNumero prcdente, de ne pas recourir ce type de construction. Nous pourrions par exemple conseiller de mieux tester la classe et de modier la mthode dispose() pour vrier un double appel. Un tableau avec un tat plus prcis de lobjet semble ncessaire, cest--dire un design modi par rapport la version C++ !

Un dernier exemple en Java


Lexemple que nous allons montrer pour terminer est le genre de code que le programmeur peut et doit crire lorsquil se pose un certain nombre de questions sur le comportement dun langage. crire le programme, sortir les rsultats sur la console et faire une analyse est une dmarche conseille. Nous commenons par le code :
public class UnMessage { private String le_message; UnMessage() { le_message = ""; System.out.println("Constructeur par dfaut de la classe UnMessage"); } UnMessage(String msg) { le_message = msg; System.out.println("Constructeur de la classe UnMessage avec " + msg); } public String toString() { return "Le message: " + le_message; }

Variations sur un thme de classe CHAPITRE 10

223

public static void main(String[] args) { UnMessage[] ma1 = new UnMessage[2]; UnMessage[] ma2 = { new UnMessage(), new UnMessage("Salut") }; ma1[0] = new UnMessage("Bonjour"); ma1[1] = new UnMessage("Bonsoir"); for (int i = 0; i < 2; i++) { System.out.println(ma1[i] + " et " + ma2[i]); } } }

Nous poursuivons par le rsultat :


Constructeur par defaut de la classe UnMessage Constructeur de la classe UnMessage avec Salut Constructeur de la classe UnMessage avec Bonjour Constructeur de la classe UnMessage avec Bonsoir Le message: Bonjour et Le message: Le message: Bonsoir et Le message: Salut

Et nous terminons par une analyse : Avec ma1, nous allouons dabord lespace pour les deux pointeurs. ma[0] et ma[1] seront null au dpart. Avec ma2, nous faisons les deux : la dnition et laffectation. Dans le constructeur par dfaut, nous avons notre le_message = ""; qui est ncessaire. Sinon le_message serait et resterait null et pourrait crer des erreurs inattendues. toString() est dni dans la classe Object et doit tre redni avec la mme signature (public String ()). Elle permettra de rendre possible lexcution correcte de println(), qui utilise toString() pour faire le travail. La dclaration de la mthode toString dans une classe est essentielle. Elle sera souvent utilise des ns de tests.

Rsum
Le langage C++, plus encore que Java, offre de nombreuses possibilits pour construire des objets, ce qui ncessite une progression systmatique dans notre apprentissage. La boucle ne sera boucle quen n de chapitre suivant.

Exercices
1. Crer en C++ une classe Personnet pour quelle accepte la construction Personne1 personnes[100];. Chaque objet de cette classe sera associ un numro entre 1 000 et 1 099, ceci en utilisant une variable de classe statique.

224

Apprendre Java et C++ en parallle

2. Crer en C++ une classe Dessin qui possde un nom et une position (x, y) sur lcran. Dnir un constructeur de copie qui va placer le nouveau dessin une position (x + 2, y + 1). Si le nom du dessin dorigine est Oeuvre1, le nouveau dessin se nommera Oeuvre1.a.

11
Manipuler des objets en Java et C++
Loprateur = ou un exercice daffectation
Nous avons choisi volontairement damliorer nos connaissances, en procdant par petits pas, et dattendre le plus longtemps possible avant de passer cette partie, plus dlicate mais essentielle. Dans les chapitres prcdents, nous avons dj cr de nombreux objets. Nous nous sommes pos toute une srie de questions, mais nous avons soigneusement gard celle-ci an de conclure cette partie de louvrage, consacre la cration et linstanciation de classes traditionnelles. Les rponses la question suivante : Comment se comporte loprateur = ? devraient nous permettre de bien mieux matriser ces deux langages.

Commenons en Java
Dans le petit exemple qui suit, nous allons crer une nouvelle classe nomme MaClasse et instancier un certain nombre dobjets pour exposer le sujet.
class MaClasse { private int compteur; private String nom; public MaClasse(int valeur, String identif) { compteur = valeur; nom = identif; }

226

Apprendre Java et C++ en parallle

public String toString() { return nom + ": " + compteur; } public void setData(int valeur, String identif) { compteur = valeur; nom = identif; } }; public class Assignement { public static void main(String args[]) { int a = 1; int b = 2; b = a; a = 3; System.out.println("a et b: " + a + " " + b); String stra = "str1"; String strb = "str2"; strb = stra; stra = "str3"; System.out.println("stra et strb: " + stra + " " + strb); String nom1 = "Nom1"; String nom2 = "Nom2"; String nom3 = "Nom3"; MaClasse mca = new MaClasse(1, nom1); MaClasse mcb = new MaClasse(2, nom2); mcb = mca; if (mca.equals(mcb)) { System.out.println("Les deux objets sont les mmes!"); } mca.setData(3, nom3); nom3 = "Salut"; System.out.println("mca et mcb: "); System.out.println(mca); System.out.println(mcb); } }

Nous prsentons ici la premire partie du rsultat :


a et b: 3 1 stra et strb: str3 str1

Manipuler des objets en Java et C++ CHAPITRE 11

227

qui est tout fait attendue, si bien que le lecteur pourrait se demander o nous voulons en venir ! Nous avons dni deux entiers, a et b, avec des valeurs diffrentes. Ce quil est important de noter au sujet des instructions :
b = a; a = 3;

cest que nous allons seulement attribuer de nouvelles valeurs ces deux objets : lobjet b ne va pas devenir lobjet a. En donnant une nouvelle valeur de 3 lentier a, nous constatons que nous touchons bien lobjet dsir. Il en va de mme pour les String stra et strb, o le rsultat est identique. Cependant, si nous examinons la deuxime partie du rsultat :
Les deux objets sont les mmes! mca et mcb: Nom3: 3 Nom3: 3

nous sommes peut-tre tonns ! Linstruction mca.equals(mcb) utilise la mthode equals() de la classe Object, qui implmente un test dquivalence le plus discriminatif qui existe ! Les deux objets sont donc identiques, et cela implique que lobjet original mcb est perdu et se trouve dj dans le ramasse-miettes (garbage collection). La mthode setData() de MaClasse va bien affecter mcb, puisque cest le mme objet. Si nous crivions en C++ :
int *pnombre1 = new int(1); int *pnombre2 = new int(2); pnombre2 = pnombre1;

ce serait tout fait quivalent, o pnombre1 et pnombre2 pointeraient la mme adresse mmoire, donc au mme objet. Nous devons nous rappeler quil ny a pas de ramassemiettes et quici nous avons en fait oubli le :
delete pnombre2;

avant la dernire instruction.

Poursuivons en C++
La transition est assure. Nous allons reprendre, en C++, notre classe Java avec deux variantes possibles. Lattribut compteur sera pour la classe MaClasse1 un attribut entier traditionnel, alors que pour la classe MaClasse2, nous aurons un pointeur un entier. Puisque lobjet pcompteur est allou dynamiquement par le constructeur, nous sommes donc obligs dintroduire un destructeur pour MaClasse2. Assez de thorie, il nous faut prsenter maintenant le code :
// assignement1.cpp #include <iostream> #include <string> using namespace std;

228

Apprendre Java et C++ en parallle

class MaClasse1 { private: int compteur; public: MaClasse1(int valeur):compteur(valeur) {}; int getCompteur() { return compteur; } void setCompteur(int valeur) { compteur = valeur; } }; class MaClasse2 { private: int *pcompteur; public: MaClasse2(int valeur) { pcompteur = new int(valeur); }; ~MaClasse2() { delete pcompteur; } int getCompteur() { return *pcompteur; } void setCompteur(int valeur) { *pcompteur = valeur; } }; int main() { int a = 1; int b = 2; b = a; a = 3; cout << "a et b: " << a << " " << b << endl; string stra = "str1"; string strb = "str2"; strb = stra; stra = "str3"; cout << "stra et strb: " << stra << " " << strb << endl;

Manipuler des objets en Java et C++ CHAPITRE 11

229

MaClasse1 mc1a(1); MaClasse1 mc1b(2); mc1b = mc1a; mc1a.setCompteur(3); cout << "mc1a et mc1b: " << mc1a.getCompteur() << " " << mc1b.getCompteur() << endl; MaClasse2 mc2a(1); MaClasse2 mc2b(2); mc2b = mc2a; mc2a.setCompteur(3); cout << "mc2a et mc2b: " << mc2a.getCompteur() << " " << mc2b.getCompteur() << endl; MaClasse2 mc2d(2); { MaClasse2 mc2c(1); mc2d = mc2c; mc2c.setCompteur(3); cout << "mc2c: " << mc2c.getCompteur() << endl; } cout << "mc2d: " << mc2d.getCompteur() << endl; }

Le rsultat global est identique la version Java :


a et b: 3 1 stra et strb: str3 str1 mc1a et mc1b: 3 1 mc2a et mc2b: 3 3 mc2c: 3 mc2d: 4325492

Pour les entiers a et b, ainsi que pour les deux string C++ stra et strb, nous obtenons le mme rsultat quen Java. Nous aurions t surpris du contraire. Nos objets restent tels quils taient dnis au dpart, et nous ne changeons que leurs valeurs. Les objets mc1a et mc1b sont des instances de la classe MaClasse1, et nous devons nous demander ce qui se passe avec linstruction :
mc1b = mc1a;

Le langage C++, linverse de Java, qui a simpli la procdure en travaillant en fait comme des pointeurs, va chercher une dnition pour loprateur =. Nous allons voir, dans un instant, que cela est tout fait possible, mais, ici, le compilateur va excuter un assignement par dfaut. Il va en fait copier le contenu de lattribut compteur de lobjet mc1a de la classe MaClasse1, cest--dire sa valeur, dans compteur de mc1b. linstruction suivante :
mc1a.setCompteur(3);

230

Apprendre Java et C++ en parallle

nous remettons bien le compteur de mca une nouvelle valeur. O cela se gte, cest dans la troisime partie, avec lutilisation dun pointeur pcompteur dans la classe maClasse2. Linstruction :
mc2b = mc2a;

va craser le pointeur pcompteur dans lobjet mc2b de la classe MyClasse2, et la mmoire qui se trouvait cet endroit ne sera pas efface. Lorsque nous dposons une nouvelle valeur dans mc1a avec mc1a.setCompteur(3), nous touchons aussi lobjet mc1b. Comme MyClasse2 possde un constructeur, il sera appel deux fois pour le mme objet ! Une alternative dans le destructeur de la classe MaClasse2 serait :
~MaClasse2() { if (pcompteur != 0) { delete pcompteur; pcompteur = 0; } }

Dans la dernire partie du code, nous procdons exactement au mme exercice, mais lobjet mc2c est allou et libr lintrieur dune nouvelle porte. Le rsultat 4325492, qui serait sans doute diffrent sur une autre machine, montre bien quil y a un problme ! Lalternative ci-dessus serait inutile et ne rsoudrait le problme qu moiti. Il faudrait connatre combien dinstances utilise pcompteur.

Crer un oprateur =
La surcharge de loprateur = est essentielle pour des classes du type prcdent, o il nest pas possible de laisser le compilateur utiliser lassignement par dfaut. Nous allons construire prsent une nouvelle classe MaClasse, dont loprateur = se dnit ainsi :
MaClasse& operator= (const MaClasse& original) {

Il reoit bien une rfrence et retourne une rfrence de la mme classe. Voici donc le code complet et la partie de test dans le main() :
// assignement2.cpp #include <iostream> #include <string> using namespace std; class MaClasse { private: int *pcompteur; public: MaClasse(int valeur) { pcompteur = new int(valeur); };

Manipuler des objets en Java et C++ CHAPITRE 11

231

MaClasse& operator= (const MaClasse& original) { if (this == &original) { cout << "Mme objet" << endl; return *this; } cout << "Cela roule" << endl; delete pcompteur; pcompteur = new int(*original.pcompteur); return *this; } ~MaClasse() { delete pcompteur; } int getCompteur() { return *pcompteur; } void setCompteur(int valeur) { *pcompteur = valeur; } }; int main() { MaClasse mca(1); MaClasse mcb(2); mcb = mca; mca.setCompteur(3); cout << "mca et mcb: " << mca.getCompteur() << " " << mcb.getCompteur() << endl; MaClasse mcd(2); { MaClasse mcc(1); mcd = mcc; mcc.setCompteur(3); cout << "mcc: " << mcc.getCompteur() << endl; } cout << "mcd: " << mcd.getCompteur() << endl; mcd = mcd; cout << "mcd: " << mcd.getCompteur() << endl; }

Ce dernier programme nous donnera le rsultat suivant :


Ca roule mca et mcb: 3 1 Ca roule mcc: 3

232

Apprendre Java et C++ en parallle

mcd: 1 Mme objet mcd: 1

Un des aspects essentiels dans ce code est vraisemblablement le :


return *this;

qui nous retournerait un pointeur une instance de lobjet. Il ne faudrait pas loublier, car il nous permettrait des constructions chanes telles que :
mca = mcb = mcd;

Lattribut pcompteur, comme dni dans notre classe MaClasse, napporte rien. Il est ici pour montrer la manire de copier notre attribut. Nous avons videmment besoin dun destructeur. Lorsque nous avons :
mcd = mcc;

la mthode operateur=, car nous pouvons effectivement lassimiler une mthode, sera appele sur lobjet mcd. Comme pcompteur est un objet dynamique, il faut commencer par leffacer. Ensuite, linstruction :
pcompteur = new int(*original.pcompteur);

va nous crer un nouvel objet pcompteur avec la valeur contenue dans lobjet mcc. Le code :
mcd = mcd;

peut paratre trange, mais savre essentiel dans cette partie :


if (this == &original) { cout << "Mme objet" << endl; return *this; }

Il nous faut effectivement vrier si nous avons affaire au mme objet. Dans ce cas, nous retournerons simplement ladresse du mme objet, et il ne se passera rien du tout. Nous pourrions donner des exemples de cas prcis o des accidents sont possibles si ce test dgalit nest pas excut. Ici, nous navons quun seul objet, bien particulier. Si nous avions dautres attributs, comme nous navons plus dassignement par dfaut, il faudrait aussi les copier. Certains de ces attributs pourraient tre dnis diffremment. Ce dernier point est une question de conception et ne sapplique en gnral quau constructeur de copie que nous avons examin au chapitre prcdent.

Lincontournable classe string en C++


Dans tous les ouvrages dignes de ce nom, nous trouvons toujours un exercice dimplmentation dune classe string. Nous savons quil existe la classe string de la bibliothque

Manipuler des objets en Java et C++ CHAPITRE 11

233

standard du C++, mais nous allons tout de mme crer notre propre classe, Mon_string, qui utilisera elle-mme un string standard dynamique, dont nous librerons lespace mmoire utilis cette occasion. Cette classe pourrait tre utilise comme modle pour des dbutants ou comme rptition gnrale aprs une absence prolonge de pratique de ce langage. Nous en proterons aussi pour reprendre un certain nombre de concepts et points essentiels qui concernent la cration dune classe en C++. Dans ce qui va suivre, la partie essentielle reste le destructeur. Si nous avons vu que Java na pas besoin de destructeur, nous allons revoir ici son importance en C++, car il permet de librer toutes les ressources utilises pendant la dure de vie de lobjet. La classe qui suit est aussi loin dtre complte :
// monstring.cpp #include <iostream> #include <string> using namespace std; class Mon_string { private: string *ple_string; public: Mon_string(const string le_string); Mon_string(const char *pchaine = 0); Mon_string(const Mon_string &mon_string); Mon_string& operator=(const char *pchaine); ~Mon_string(); }; Mon_string::Mon_string(const string le_string) { ple_string = new string(le_string); cout << "Constructeur1 pour " << *ple_string << endl; } Mon_string::Mon_string(const char *pchaine) { ple_string = new string(pchaine); cout << "Constructeur2 pour " << *ple_string << endl; } Mon_string::Mon_string(const Mon_string &mon_string) { ple_string = new string(*mon_string.ple_string); cout << "Constructeur3 pour " << *ple_string << endl; } Mon_string::~Mon_string() { cout << "Destructeur pour " << *ple_string << endl; if (ple_string != NULL) { delete ple_string; }

234

Apprendre Java et C++ en parallle

} Mon_string& Mon_string::operator=(const char *pchaine) { cout << "Operateur = avant " << *ple_string << endl; delete ple_string; ple_string = new string(pchaine); cout << "Operateur = aprs " << *ple_string << endl; return *this; } int main() { Mon_string un_string("Dbut des tests"); Mon_string *pnom_str = new Mon_string("Salut"); *pnom_str = "Bonjour"; delete pnom_str; { Mon_string mon_str(string("Hello")); mon_str = "Good morning"; } cout << "Fin du programme" << endl; }

Nous allons tout de suite passer au travers des diffrentes parties de ce code, pour mieux comprendre pourquoi nous obtenons le rsultat suivant :
Constructeur2 pour Debut des tests Constructeur2 pour Salut Operateur = avant Salut Operateur = aprs Bonjour Destructeur pour Bonjour Constructeur1 pour Hello Operateur = avant Hello Operateur = aprs Good morning Destructeur pour Good morning Fin du programme Destructeur pour Debut des tests

Pour simplier, nous avons dabord inclus, dans le mme chier, les trois parties distinctes du code, qui sont : la dnition de la classe, en principe dans un chier den-tte nomm, par exemple, Mon_string.h ; le code de la classe, gnralement dans un chier nomm Mon_string.cpp, cest--dire toutes les mthodes commenant par Mon_string:: ;

Manipuler des objets en Java et C++ CHAPITRE 11

235

la partie du main() qui devrait se retrouver dans un troisime chier et qui pourrait tre tout aussi bien un programme de test complet quune classe ou quune application utilisant dautres classes. La premire chose que nous pouvons analyser dans cet exercice est lidentication des objets et quel moment ils sont initialiss et librs. Lobjet un_string existe sur le tas. Il na pas t cr avec loprateur new et ne sera libr que tout la n, mme aprs notre dernier message. Nous pouvons mme afrmer dans ce cas-l que le destructeur est appel par la dernire accolade ferme de main() ! Dans cet exercice, nous avons aussi utilis des chanes de caractres du style C. Il est tout fait acceptable de garder ce type de variable, mme en Standard C++, au lieu dutiliser systmatiquement des string C++. La partie intrieure entre les accolades {} samuse avec lobjet mon_str. Nous remarquons que le premier constructeur est utilis, puisque son unique paramtre est un string. Juste aprs que "Good morning" a remplac "Hello", le destructeur est appel, puisque nous sortons de la porte. Nous retrouvons nouveau la forme particulire de loprateur =, que nous avons dj analys dans ce chapitre. Sa fonction principale est deffacer lancien objet, "Hello", et de le remplacer par le nouveau, "Good morning". Cette forme apparat trs souvent dans le code de classe utilisant loprateur =, et le programmeur sy habituera trs vite aprs quelques exercices. Enn, il doit aussi retourner une rfrence String& pour dventuels chanages. Nous devons faire remarquer que loprateur = se fait non sur le mme type, mais sur un const char *. Dans le cas de limplmentation de loprateur :
Mon_string & operator= (const Mon_string & original)

il faudrait aussi vrier que les deux objets ne soient pas les mmes. Au niveau des tests de cette classe, nous devrons nous assurer que les objets "Hello" et "Salut" ont bien disparu et ont t effacs correctement. Le destructeur devra faire proprement son travail. Il est responsable de leffacement de toutes les ressources alloues dynamiquement avec loprateur new durant la dure de vie de lobjet. Il faut aussi remarquer que si nous avions fait lexercice avec un string au lieu dun string*, toutes les oprations de nettoyage auraient t excutes par la classe string. Le destructeur de Mon_string naurait pas t ncessaire, ainsi que les delete et new pour loprateur =. Un simple le_string = chaine; aurait suf. En fait, le mcanisme dcrit cidessus se retrouve dans la classe string. Le destructeur de la classe string lui-mme aurait t appel pour les deux objets restants, "Bonjour" et "Good morning".

Recommandation dordre des mthodes


Dans le code de la classe Mon_string qui prcde, nous trouvons les constructeurs, le destructeur et les mthodes. Cest un ordre conseill aussi en Java, pour une lecture conviviale. Les mthodes private devraient tre regroupes dans un bloc, aprs la partie public. En C++, lordre devrait tre le mme dans le chier den-tte.

236

Apprendre Java et C++ en parallle

Certaines personnes groupent les mthodes par ordre alphabtique. Nous prfrons choisir un ordre par groupe de fonctions ou dimportance. Par exemple, les mthodes de test pourraient se retrouver en n de code.

Retour la source
Aprs ce petit dtour essentiel la cration de notre propre classe string en C++, il nous faut rpter, une fois encore, la construction particulire en Java :
String nom = new String("mon_nom"); nom = "ton_nom";

La deuxime forme est en fait, exceptionnellement, accepte et intgre dans le compilateur. Nous rappellerons lquivalent en C++ :
string nom = "mon_nom"; nom = "ton_nom";

Le clonage dobjet en Java


Nous avons donc vu que la forme en Java :
Personne p1 = new Personne(...); Personne p2 = p1;

nous crait un alias. p2 est bien le mme objet. Si nous utilisons une mthode de p2 pour modier un attribut de p2, lobjet p1 sera aussi affect. Il y a cependant des situations o nous aimerions vraiment copier la brebis Dolly, an de crer une copie conforme, un clone. Ceci peut se faire de cette manire :
Personne P2 = (Personne)p1.clone();

La machine virtuelle de Java alloue de la mmoire pour recopier lobjet courant dans une nouvelle instance. La mthode clone(), disponible dans la classe Objet, va copier bit bit notre objet. Nous allons en comprendre le mcanisme au travers de ce petit exemple :
public class Clown implements Cloneable { private String nom; private String prenom; private int annee; public Clown(String leNom, String lePrenom, String lAnnee) { nom = leNom; prenom = lePrenom; annee = Integer.parseInt(lAnnee); } public void setAnnee(int lannee) { annee = lannee; }

Manipuler des objets en Java et C++ CHAPITRE 11

237

public void unTest() { System.out.print("Nom et prenom: " + nom + " " + prenom); System.out.println(" : " + annee); } public static void main(String[] args) { Clown nom1 = new Clown("Haddock", "Capitaine", "1907"); Clown nom2; Clown nom3; nom2 = nom1; try { nom3 = (Clown)nom1.clone(); if (!nom3.equals(nom1)) { System.out.println("Les objets nom1 et nom3 sont diffrents"); } nom2.setAnnee(1917); nom3.setAnnee(1927); nom1.unTest(); nom2.unTest(); nom3.unTest(); if (nom2.equals(nom1)) { System.out.println("Les objets nom1 et nom2 sont gaux"); } } catch (CloneNotSupportedException cnse) { System.out.println("clone() exception" + cnse); } } }

Nous oublierons pour linstant implements Cloneable, que nous verrons au chapitre 13, car, sans cette construction et bien que le code compile correctement, nous aurions une exception CloneNotSupportedException. clone() est en fait une mthode protge de la classe de base Objet, et cette construction nous permettra daccder la mthode clone(). Si nous examinons le rsultat :
Les objets nom1 et nom3 sont diffrents Nom et prenom: Haddock Capitaine : 1917 Nom et prenom: Haddock Capitaine : 1917 Nom et prenom: Haddock Capitaine : 1927 Object nom1 et nom2 sont gaux

nous voyons bien que les deux objets nom1 et nom3 sont diffrents. Nous avons chang lanne de lobjet nom3 sans toucher nom1. Ce nest pas le cas de nom2, car cest un alias de nom1.

238

Apprendre Java et C++ en parallle

Nous pouvons donc utiliser lun ou lautre nom pour accder au mme objet. Linstruction nom2.setAnnee(1917) nous donne aussi une conrmation de nos afrmations. Il est vident que nous allons retrouver le mme problme en C++, o parfois nous pourrions copier des rfrences dautres objets qui seraient des attributs de cette classe. Il est cependant possible de dnir son propre clonage, comme en C++, mais il faudra utiliser une interface. Comme ce sujet na pas encore t trait, nous en donnerons un exemple en n de chapitre 13.

Surcharge doprateurs en C++, et nos amis friend


Cest vraiment le dernier moment pour prsenter la surcharge des oprateurs, bien que nous layons dj considre pour loprateur = en C++. Mais le cas se pose aussi pour les autres oprateurs, dont nous verrons quils sont souvent directement associs au motcl friend. Ce mot-cl na rien voir avec la srie tlvise ni avec Java, mais reste un sujet essentiel traiter en C++.

Surcharge doprateur
Dans le code qui va suivre, nous allons prsenter en C++ deux classes, EntierA et EntierB, sur lesquelles nous aimerions dnir une opration daddition. Nous utiliserons une mthode, et non un oprateur, pour montrer en fait la manire de faire en Java. En effet, si nous dnissions ces deux classes en Java et que nous crivions ceci :
EntierA ea = new EntierA(1); EntierA ea = new EntierA(1);

il serait absolument impossible dcrire :


ea = ea + eb;

En Java, loprateur + nest disponible que pour les types prdnis, comme int, double et autres. Mais avant de passer la dnition de loprateur + en C++, prsentons le code quivalent dune addition au travers dune mthode add() :
// amis1.cpp #include <iostream> using namespace std; class EntierB; class EntierA { private: int entiera; public: EntierA(int nombre):entiera(nombre) {} int add(const EntierB& un_entierb); int get() const { return entiera; }

Manipuler des objets en Java et C++ CHAPITRE 11

239

}; class EntierB { private: int entierb; public: EntierB(int nombre):entierb(nombre) {} int add(const EntierA& un_entiera); int get() const { return entierb; } }; int EntierA::add(const EntierB& un_entierb) { entiera = entiera + un_entierb.get(); return entiera; } int EntierB::add(const EntierA& un_entiera) { entierb = entierb + un_entiera.get(); return entierb; } int main() { EntierA ea(10); // ea est 10 EntierB eb(20); // eb est 20 ea.add(eb); eb.add(ea); // ea devient 30 (10 + 20) // eb devient 50 (30 + 20)

cout << ea.get() << " " << eb.get() << endl; }

La dclaration simplie et avance (forward declaration) avec class EntierB; au dbut du code peut paratre trange, mais elle est ncessaire pour permettre la compilation de la classe EntierA qui lutilise dans la mthode add(). Cest un truc connatre lorsque les dnitions de classe sont ainsi enchevtres. Il y a peu dautres choses dire sur ce code, sinon que nous utilisons certaines constructions videntes dont il faut se rappeler les consquences. Dans la mthode add(const EntierB& un_entierb) de la classe EntierA, nous avons :
entiera = entiera + un_entierb.get();

et le get() est effectivement ncessaire, car nous ne pouvons pas accder lattribut entierb, qui est priv. Il faudrait vraiment tre trs ami pour obtenir ce privilge et pouvoir crire directement :
entiera = entiera + un_entierb.entierb;

240

Apprendre Java et C++ en parallle

La seule manire dans ce cas serait de rendre entierb public, et nous savons dj que ce nest pas du tout la bonne solution. La construction :
entiera = entiera + un_entierb;

ou
entiera += un_entierb;

serait possible en C++ condition de dnir loprateur + et +=, ce que nous verrons plus loin. Enn, la construction :
cout << ea.add(eb);

est possible, car un int retourn est accept par lostream. Une forme telle que :
cout << ea;

ne serait pas possible sans dnir loprateur << pour la classe EntierA. Le rsultat du code prcdent :
30 50

est la somme de 10 et 20 laquelle nous ajoutons encore 20 an dutiliser la mthode add() de la deuxime classe EntierB.

Pas de surcharge doprateur en Java


Il ny a pas de surcharge doprateur en Java. Certains sen plaindront, dautres sen passeront sans autres commentaires. Limplmentation des classes EntierA et EntierB est relativement directe :
class EntierA { private int entiera; public EntierA(int nombre) { entiera = nombre; } public int add(EntierB unEntierb) { entiera = entiera + unEntierb.getEntierb(); return entiera; } public int getEntiera() { return entiera; } } class EntierB { private int entierb; public EntierB(int nombre) { entierb = nombre;

Manipuler des objets en Java et C++ CHAPITRE 11

241

} public int add(EntierA unEntiera){ entierb = entierb + unEntiera.getEntiera(); return entierb; } public int getEntierb() { return entierb; } } public class Amis { public static void main(String[] args) { EntierA ea = new EntierA(10); EntierB eb = new EntierB(20); System.out.println(ea.add(eb) + " " + eb.add(ea)); } }

Le mcanisme de dclaration avance pour EntierA nest pas ncessaire en Java, et nous retrouvons les new, qui ntaient pas requis en C++ pour des objets sur le tas. Nous avons devin quune surcharge de loprateur << nexistait pas en Java, tout en reconnaissant que le chanage des << en C++ est bien pratique.

Les friend, ces amis qui nous donnent laccs


Nous ferons ici un passage rapide, mais oblig, sur la prsentation du mot-cl friend. Ce dernier, comme son nom lindique, ami, va permettre nos classes dobtenir laccs une autre classe ou fonction. Il va obtenir en particulier le privilge daccder des variables prives ou protges. Nous prsentons prsent le code de nos classes EntierA et EntierB en employant friend :
// amis2.cpp #include <iostream> using namespace std; class EntierB; class EntierA { friend EntierA operator+(const EntierA& un_entiera, const EntierB& un_entierb); friend EntierA operator+(const EntierA& un_entiera, const int nombre) { return EntierA(un_entiera.entiera + nombre); } friend ostream& operator<<(ostream& os, const EntierA& un_entier) { return os << un_entier.entiera; }

242

Apprendre Java et C++ en parallle

private: int entiera; public: EntierA(int nombre):entiera(nombre) {} }; class EntierB { friend int operator+(const int nombre, const EntierB& un_entierb) { return un_entierb.entierb + nombre; } private: int entierb; public: EntierB(int nombre):entierb(nombre) {} }; EntierA operator+(const EntierA& un_entiera, const EntierB& un_entierb) { return EntierA(un_entiera.entiera + un_entierb); } int main() { EntierA ea(10); EntierB eb(20); ea = ea + 70 + eb; // ea = eb + ea; cout << ea << endl; }

Pour comprendre ce code dune manire plus directe, nous commencerons par le 70 + eb. gauche nous avons un int, et droite, un EntierB. Si nous voulions remplacer loprateur + par une mthode, nous ne le pourrions pas, car le rsultat, qui sera un entier, na rien voir avec une mthode de classe dont il faudrait dnir un objet. Cette addition pourrait tre remplace par une fonction C telle que :
int res = add(int nombre, const EntierB& un_entierb);

Comme cette fonction accde une variable prive, nous avons besoin dun friend, an de pouvoir accder lattribut entierb priv. Si nous voulions nous passer du friend, il faudrait rintroduire une mthode get() sur la classe EntierB. Aprs avoir excut 70 + eb, nous passons le rsultat ea + (rsultat). Cette fois-ci, loprateur :
friend EntierA operator+(const EntierA& un_entiera, const int nombre)

sera utilis avec le int droite. Nous pourrions aussi crire ceci :
ea = (ea + 70 ) + eb;

Manipuler des objets en Java et C++ CHAPITRE 11

243

car loprateur :
friend EntierA operator+(const EntierA& un_entiera, const EntierB& un_entierb);

est aussi dni. Il faut toujours lire gauche et droite. Nous navons bien entendu pas couvert toutes les possibilits, puisque la ligne de code :
// ea = eb + ea;

serait rejete avec :


g++ -c amis2.cpp amis2.cpp: In function `int main()': amis2.cpp:50: no match for `EntierB & + EntierA &' amis2.cpp:40: candidates are: class EntierA operator +(const EntierA &, const EntierB &) amis2.cpp:12: class EntierA operator +(const EntierA &, int) amis2.cpp:28: int operator +(int, const EntierB &) make: *** [amis2.o] Error 1

Enn, le :
friend ostream& operator<<(ostream& os, const EntierA& un_entier)

est une forme trs souvent utilise an de dnir loprateur << sur ces classes. Ce friend va tre remplac par lemploi dune mthode toString() publique, comme cest le cas en Java. Il nest pas ncessaire davoir systmatiquement des friend pour toutes les surcharges et rednitions doprateurs. Les friend sont en fait des fonctions dnies en dehors de la classe. Lcriture de classes contenant des amis est trs particulire. Il faut parfois beaucoup de temps pour dcouvrir une erreur et comprendre vraiment ce qui se passe. Le lecteur peut samuser, par exemple, effacer un friend ou remplacer une rfrence par un passage par valeur ! Bon courage ! Dans le chier amis2.cpp qui prcde, nous avons nouveau un assemblage des trois composants du code, qui vont du chier den-tte lapplication, elle-mme dnie par le main().

Amis : un exemple plus complet


Dans lexemple qui va suivre, nous allons montrer la ncessit de lutilisation des amis pour un certain nombre doprateurs.
// Vitesse1.cpp #include <iostream> using namespace std; class Vitesse1 { private: double x, y;

244

Apprendre Java et C++ en parallle

public: Vitesse1(double le_x = 0, double le_y = 0) : x(le_x), y(le_y) {} Vitesse1(const Vitesse1 &vit) : x(vit.x), y(vit.y) {} Vitesse1& operator=(const Vitesse1& vit) { x = vit.x; y = vit.y; } friend Vitesse1 operator*(Vitesse1& une_vit, double facteur); friend Vitesse1 operator*(double facteur, Vitesse1& une_vit); friend Vitesse1 operator*(Vitesse1& une_vit1, Vitesse1& une_vit2); friend ostream& operator<<(ostream& os, const Vitesse1& vit) { return os << "Direction x:y = " << vit.x << ":" << vit.y; } }; Vitesse1 operator*(Vitesse1& la_vit, double facteur) { Vitesse1 une_vit(la_vit); une_vit.x *= facteur; une_vit.y *= facteur; return une_vit; } Vitesse1 operator*(double facteur, Vitesse1& la_vit) { Vitesse1 une_vit(la_vit); une_vit.x *= facteur; une_vit.y *= facteur; return une_vit; } Vitesse1 operator*(Vitesse1& la_vit1, Vitesse1& la_vit2) { Vitesse1 une_vit(la_vit1); une_vit.x *= la_vit2.x; une_vit.y *= la_vit2.y; return une_vit; } int main() { Vitesse1 vit(10, 20); cout << vit << endl; vit = vit * 1.2; cout << vit << endl; vit = 0.8 * vit; cout << vit << endl; vit = vit * vit; cout << vit << endl;

Manipuler des objets en Java et C++ CHAPITRE 11

245

return 0; }

Et le rsultat, qui nous semble correct :


Direction Direction Direction Direction x:y x:y x:y x:y = = = = 10:20 12:24 9.6:19.2 92.16:368.64

La manire la plus directe de comprendre ce code est dexaminer la partie principale du programme, le main(). Si nous remplacions Vitesse1 vit(10, 20); par double vit = 10.1;, nous naurions alors plus besoin de notre classe Vitesse1. Nous pourrions alors compiler et excuter ce programme, sans difcult, avec bien videmment un rsultat diffrent. Cela veut dire que les oprateurs << et *, qui sont dnis par le compilateur pour le type primitif double, doivent tre implments dans notre classe Vitesse1. Les quatre friend de la classe Vitesse1 correspondent au cas prsent dans le main(). Il faut toujours considrer la partie droite et la partie gauche, et nous comprendrons la raison des deux implmentations pour vit * 1.2 et 0.8 * vit. Le double est dun ct ou de lautre ! Sil y avait dautres types de variables, il faudrait ajouter dautres dnitions doprateurs friend ou ventuellement utiliser un transtypage dans le type implment. Les deux formes suivantes seront acceptes :
vit = vit * 10L; vit = vit * (double)10L;

mme pour la deuxime, car le transtypage fonctionne correctement. Par exemple, il ne serait pas ncessaire de dnir cette entre :
friend Vitesse1 operator*(Vitesse1& une_vit, long facteur);

Le L du 10L indique que nous avons en fait un long et non pas un int par dfaut.

Faut-il viter les amis (friend) ?


Cest videmment un non-sens dans la vie de tous les jours, mais pour les professionnels en programmation C++, cest un vrai dbat. Le but de cet ouvrage nest pas de philosopher pour savoir si nous violons les rgles de la programmation oriente objet, mais plutt de prsenter un certain nombre dalternatives, an que le lecteur se familiarise avec les techniques du langage et puisse, peu peu, se faire une opinion personnelle sur lutilisation correcte ou non de ces formes spcialises et souvent ncessaires. Par ailleurs, il ne faut pas oublier que ces constructions nexistent pas en Java qui sen passe trs bien. Dans le code qui suit, nous avons rcrit le code prcdent en faisant disparatre toutes les instructions utilisant friend. Lastuce majeure est dutiliser une construction qui naccde jamais aux attributs privs de la classe :

246

Apprendre Java et C++ en parallle

// Vitesse2.cpp #include <iostream> using namespace std; class Vitesse2 { private: double x, y; public: Vitesse2(double le_x = 0, double le_y = 0) : x(le_x), y(le_y) {} Vitesse2(const Vitesse2 &vit) : x(vit.x), y(vit.y) {} Vitesse2& operator=(const Vitesse2& vit) { x = vit.x; y = vit.y; } void operator*=(const Vitesse2& une_vit) { x *= une_vit.x; y *= une_vit.y; } void operator*=(const double valeur) { x *= valeur; y *= valeur; } ostream& toString(ostream& os) const { return os << "Direction x:y = " << x << ":" << y; } void print() { cout << "Direction x:y = " << x << ":" << y << endl; } }; Vitesse2 operator*(const Vitesse2& la_vit, const double facteur) { Vitesse2 une_vit(la_vit); une_vit *= facteur; return une_vit; } Vitesse2 operator*(const double facteur, const Vitesse2& la_vit) { Vitesse2 une_vit(la_vit); une_vit *= facteur; return une_vit; } Vitesse2 operator*(const Vitesse2& la_vit1, const Vitesse2& la_vit2) {

Manipuler des objets en Java et C++ CHAPITRE 11

247

Vitesse2 une_vit(la_vit1); une_vit *= la_vit2; return une_vit; } ostream& operator<<(ostream& os, const Vitesse2& vit) { return vit.toString(os); } int main() { Vitesse2 vit(10, 20); cout << vit << endl; vit = vit * 1.2; cout << vit << endl; vit = 0.8 * vit; cout << vit << endl; vit = vit * vit; cout << vit << endl; return 0; }

Nous nallons pas piloguer cette fois-ci sur cette partie de code, mais nous dirons cependant quelle nous plat beaucoup. Le lecteur peut en faire une comparaison directe avec lexemple prcdent. Les dbutants auront besoin de plus de temps pour assimiler ces constructions essentielles. Ce type de code devrait se retrouver trs souvent, lorsque les programmeurs dcident dutiliser des oprateurs au lieu de mthodes. Loprateur << est videmment essentiel, proche du toString() en Java. Cependant, sil sagit par exemple demployer loprateur * pour la classe Personne, nous pourrions nous poser la question de son utilit. Pour une classe Employee, nous pourrions nous demander sil peut augmenter le salaire et ladapter en fonction de lination. De nombreux programmeurs se sont souvent pos la question de savoir lequel des oprateurs + ou & (AND) devrait tre utilis pour une classe string. Nous pourrions rpondre que Java se dbrouille trs bien sans oprateur avec la mthode concat() de sa classe String :
public String concat(String str);

Rsum
Comprendre le comportement de loprateur = en Java et C++ est essentiel, an de le surcharger ventuellement en C++. Utiliser ou non les oprateurs en C++, amicalement ou non, demande une certaine attention, bien quun programmeur Java doive se dbrouiller

248

Apprendre Java et C++ en parallle

sans cette ressource C++ essentielle. Cest vraiment un sujet plein de contradictions, mais fondamental dans la comprhension et lutilisation correcte de ces deux langages.

Exercices
1. Crer en Java une classe AssigneTest avec ce code dans la partie main() :
MonString mstr1 = new MonString("Test1"); MonString mstr2 = new MonString("Test2"); mstr2 = mstr1; MaClasse mca = new MaClasse(mstr1); MaClasse mcb = new MaClasse(mstr2); mcb = mca; mstr1.setData("Test3"); System.out.println(mca); System.out.println(mcb);

2. crire le code ncessaire pour obtenir ce rsultat :


Test3 Test3

Les deux classes MonString et MaClasse ne possderont quun seul attribut, respectivement un String traditionnel et un objet de MonString. Linstruction mstr1.setData("Test3"); va en fait affecter lobjet identi avec la variable mcb. 3. Reprendre la classe MaClasse en C++ an de traiter le constructeur par dfaut, de vrier si les deux objets sont gaux pour loprateur = et dimplmenter le code correctement, si le pointeur pcompteur est 0. 4. tendre le code damis2.cpp, an quil puisse traiter au minimum lexemple suivant :
ea += eb + 1; eb++; eb += 10.1 + ea; cout << eb;

et sortir le rsultat :
Mon test: 62

si ea et eb sont initialiss avec des valeurs de 10 et 20.

12
Un hritage attendu
Nous avons appris quune classe tait en fait un nouveau type, dni par le programmeur, an de lui permettre de reprsenter un problme dans le monde de la programmation Java ou C++. Dans ce chapitre, nous allons tendre le concept de classe celui de classe drive. Les exemples traditionnels que nous trouvons dans la littrature concernent aussi bien des formes graphiques que le personnel dune compagnie ou les membres dune socit. Dans le cas de formes graphiques, telles que des carrs, des rectangles ou des cercles, nous pourrons crer une classe pour chacune de ces formes partir dune classe de base qui possdera des caractristiques communes, comme un point dorigine ou des mthodes pour dessiner, dplacer, cacher ou encore effacer lobjet. Dans le cas de la classe Carre, reprsentant notre forme carre, nous dirons quelle drive de la classe de base Forme. Pour le personnel dune compagnie ou les membres dune socit, nous pourrons faire la mme analogie avec des directeurs et des employs. Un employ pourra faire des heures supplmentaires, alors quun directeur naura pas pointer son arrive et son dpart du travail ! Si ce directeur fait partie dune socit en tant que membre honoraire, il naura sans doute pas de cotisation payer. Dans ce dernier cas, il pourra hriter dune classe de base Societaire avec des caractristiques spciques.

Lexemple de java.lang.Integer
Une autre approche, beaucoup plus directe, pour comprendre le concept de classe de base et de classe drive, est danalyser lune des nombreuses classes de Java. Nous prendrons en fait un exemple avec une seconde drivation, car toute classe en Java drive de la classe de base Object. En regardant la documentation de la classe Integer, nous dcouvrons ceci :
public final class Integer extends Number implements Comparable

250

Apprendre Java et C++ en parallle

Nous oublierons la dernire partie, implements Comparable, qui nous permettra de comparer des objets de diffrents types et sur laquelle nous reviendrons plus loin, ainsi que le final, qui nous indique que la classe Integer ne pourra plus tre utilise comme nouvelle classe de base pour des extensions possibles. La partie essentielle :
class Integer extends Number

nous indique que la classe Integer va hriter des caractristiques de la classe de base Number. En regardant prsent la classe Number, nous dcouvrons ceci :
public abstract class Number extends Object implements Serializable

Nous faisons de mme en oubliant implements Serializable, qui permettra de srialiser des objets au travers dune interface. Nous devons cependant revenir sur abstract. Ce mot-cl nous indique que nous avons affaire une classe abstraite, Number, dont il ne sera pas possible dinstancier un objet directement. Cela peut sembler bien mystrieux, jusquau moment o nous dcouvrons que les classes BigDecimal, BigInteger, Byte, Double, Float, Long et Short, en plus dInteger, hritent aussi de la classe abstraite Number. Mais que sestil donc pass ? Cest en fait trs simple. Durant la conception de ces diffrentes classes, les analystes ont dcouvert que chacune dentre elles pouvait hriter de toute une srie de fonctionnalits communes. Cest ici que nous rencontrons le terme de rutilisation (reusability ou reuse en anglais). Dans notre exemple ci-dessus dune socit, nous pourrions crire ceci :
public class MembreHonoraire extends Societaire

qui nous indique que la classe MembreHonoraire va simplement tendre la classe Societaire. Cependant, durant la conception de nos classes, nous aurions pu dcouvrir quune implmentation telle que :
public abstract class Membre public class MembreHonoraire extends Membre public class Societaire extends Membre

est peut-tre plus judicieuse. Les classes MembreHonoraire et Societaire peuvent tre instancies, ce qui nest pas le cas de la classe abstraite Membre, qui ne va contenir que des mthodes et attributs utiliss par ses sous-classes. Le terme de sous-classe doit tre expliqu, car il peut sembler encore moins clair que son quivalent de classe drive. Il veut bien dire quil hrite de toutes les caractristiques dune classe de base et non pas dune partie, par exemple avec le sens de sous-value. La sous-classe aura en fait plus de caractristiques que sa superclasse, un autre nom pour la classe de base, qui, elle-mme, hritera peut-tre dune autre classe devant se terminer, dans tous les cas en Java, par la classe Object. Il ny a pas ce concept en C++, et la classe Membre sera la premire classe dans la hirarchie. En Java, Membre hritera automatiquement dObject, et sans dclaration explicite.

Un hritage attendu CHAPITRE 12

251

La rutilisation
Un programmeur C na souvent pas dautres choix que de copier et de modier son code. Sil est assez expriment, il essaiera dcrire le maximum de code dans des fonctions gnriques et rutilisables. Le pas suivant est la conception de classes, en Java et en C++, sufsamment gnriques pour tre rutilisables. Cette notion de classe gnrique et rutilisable est essentielle lors du dveloppement de nouveaux produits. Il y a plusieurs cas de gure comme ceux-ci : Une partie du code peut dpendre de la machine ou du systme dexploitation, et il doit tre isol proprement. Linterface graphique dun jeu dchecs sur PC peut tre utilise pour un jeu totalement diffrent. Le cur dun programme de jeu dchecs en C++ peut utiliser les iostreams pour sauver des parties en cours ou charger des modles ou des parties de grand matre. Il pourrait mme compiler et tourner sur un serveur Unix, Linux ou Windows NT. Le dplacement sur lchiquier peut se faire sur des tableaux avec des mthodes rutilisables pour dautres types de jeux. Dans les paragraphes qui suivent, les sujets comme lhritage, la composition ou les classes abstraites sont essentiels dans ce concept de rutilisation.

Hritage et composition
Le concept dhritage est fond sur la cration dune nouvelle classe sur la base dun type de classe existant. Celui de la composition nous est dj familier, car il consiste crer des objets, plus communment appels des attributs, dans une nouvelle classe. Dans ce code Java :
public class SocietaireC { private Personne unSocietaire; private int cotisation; }

la classe SocietaireC possde deux attributs privs, dont la classe Personne. Comme nous utiliserons la classe Societaire pour un hritage classique, nous avons ajout la lettre C pour indiquer une composition. Le terme de sous-objet est parfois utilis pour des attributs de classe comme unSocietaire, tout comme celui dembedded en anglais, qui apparat trs souvent dans dautres domaines informatiques. Le terme embedded, qui signie encastr ou cach, savre probablement lun des termes les plus corrects pour nous clairer dans ce contexte de composition et dhritage. Nous remarquons que les variables sont prives, ce qui est une rgle essentielle en programmation objet ; nous aurons alors besoin de mthodes particulires pour accder aux attributs ou aux mthodes de la classe Personne. En utilisant la composition, nous ne pouvons pas accder directement une mthode de la classe Personne au travers dune instance de la

252

Apprendre Java et C++ en parallle

classe Societaire. Cependant, ce serait le cas si nous avions la dnition de Societaire de cette manire :
public class Societaire extends Personne;

Avant de revenir sur la syntaxe et des exemples en Java et C++ de classes drives, nous allons prsenter notre classe SocietaireC, qui utilise lapproche de composition. Nous avons un peu raccourci notre classe Personne, prsente au chapitre 4, pour des raisons de simplicit dans la prsentation. Voici donc la version Java :
class PersonneC private String private String private int { nom; prenom; annee;

public PersonneC(String lenom, String leprenom, int lannee) { nom = lenom; prenom = leprenom; annee = lannee; } public void un_test() { System.out.println("Nom et prnom: " + nom + " " + prenom); System.out.println("Anne de naissance: " + annee); } } public class SocietaireC { private PersonneC unSocietaire; private int cotisation; public SocietaireC(String lenom, String leprenom, int lannee, int lacotis) { unSocietaire = new PersonneC(lenom, leprenom, lannee); cotisation = lacotis; } public void un_test() { unSocietaire.un_test(); System.out.println("Cotisation: " + cotisation); } public static void main(String[] args) { SocietaireC unSoc = new SocietaireC("Haddock", "Capitaine", 1907, 100); unSoc.un_test(); } }

et la version C++ :
// SocietaireC.cpp #include <iostream> #include <string>

Un hritage attendu CHAPITRE 12

253

using namespace std; class Personne { private: string nom; string prenom; int annee; public: Personne(const string lenom, const string leprenom, int lannee); void un_test(); }; class SocietaireC { private: Personne *pun_societaire; int cotisation; public: SocietaireC(const string lenom, const string leprenom,int lannee, int lacotis); ~SocietaireC(); void un_test(); }; Personne::Personne(const string lenom, const string leprenom, int lannee) :nom(lenom), prenom(leprenom), annee(lannee) { } void Personne::un_test() { cout << "Nom et prnom: " << nom << " " << prenom << endl; cout << "Anne de naissance: " << annee << endl; } SocietaireC::SocietaireC(const string lenom, const string leprenom, int lannee, int lacotis) { pun_societaire = new Personne(lenom, leprenom, lannee); cotisation = lacotis; } SocietaireC::~SocietaireC() { delete pun_societaire; } void SocietaireC::un_test() { pun_societaire->un_test(); cout << "Cotisation: " << cotisation << endl; } int main() { SocietaireC un_soc("Haddock", "Capitaine", 1907, 100);

254

Apprendre Java et C++ en parallle

un_soc.un_test(); return 0; }

Comme les variables unSocietaire en Java et pun_societaire en C++ sont des objets de la classe Personne et quils sont des attributs de la classe Societaire, nous parlons donc de composition. Ce qui est intressant ici, cest lappel de la mthode un_test() de la classe SocietaireC. Comme nous aimerions aussi appeler la mthode un_test() de lobjet de la classe Personne, nous devons utiliser la variable dobjet pour appeler cette mthode. Il ny a rien de mystrieux, mais nous pourrons comparer plus loin ce code avec limplmentation dune classe Societaire qui hrite de Personne et non plus compose dun objet de la classe Personne. Dans la version C++, pun_societaire est un pointeur, et il ncessite donc un destructeur dans sa classe Societaire pour effacer les ressources.
Note Une classe final en Java ne peut plus tre tendue. Si nous voulions tendre la classe String de Java, nous naurions quune seule solution : utiliser la composition. Nous pourrions alors tout de mme tendre cette classe final, mais dune manire beaucoup moins lgante et accessible que si nous avions pu la driver. Nous aborderons les aspects de performance de classes final dans le chapitre 15, traitant des performances.

Lencapsulation des donnes


Lencapsulation peut tre dnie comme lisolement des donnes. Nous allons voir ici que cet aspect est essentiel pour assurer le maximum de exibilit et de rutilisation du code. Nous prendrons ici notre exemple prcdent en C++, qui est tout aussi applicable en Java. En dnissant la variable prive *pun_societaire dans la classe SocietaireC, cela signie quil est tout fait possible de changer le code dans le futur, cest--dire dans les classes Personne et SocietaireC. Linterface de la mthode un_test() de SocietaireC, extrmement simple ici, mais qui pourrait contenir des arguments et une valeur de retour, doit simplement rester identique. Le code de la mthode un_test() dans la classe SocietaireC pourrait tre totalement rcrit, et nous pourrions trs bien remplacer Personne par une autre classe ou une autre implmentation. Si nous rendions la variable *pun_societaire publique de cette manire :
class SocietaireC { private: int cotisation; public: Personne *pun_societaire; SocietaireC(string lenom, string leprenom, int lannee, int lacotis);

Un hritage attendu CHAPITRE 12

255

~SocietaireC(); void un_test(); };

nous pourrions alors crire ceci dans le main() :


int main() { SocietaireC un_soc("Haddock", "Capitaine", 1907, 100); un_soc.un_test(); un_soc.pun_societaire->un_test(); return 0; }

Le rsultat sera donc :


Nom et prnom: Haddock Capitaine Anne de naissance: 1907 Cotisation: 100 Nom et prnom: Haddock Capitaine Anne de naissance: 1907

Nous comprenons bien que cette construction nous ferait perdre tous les avantages dcrits ci-dessus. Le fait de protger et de cacher les donnes est fondamental en programmation objet et en rutilisation de code. Un code bien crit, qui doit tre modi pour toute sorte de raisons, ncessitera un minimum ou aucune correction dans les applications qui utiliseront nos API (Application Programming Interface).

La syntaxe de lhritage en Java et C++


Nous avons dj donn un aperu ci-dessus de la syntaxe de lhritage en Java, avec lexemple de la classe Integer. La voici donc nouveau, pour notre classe Societaire :
public class Societaire extends Personne { }

En C++ la forme est un peu diffrente. Nous pourrions dire que les deux-points (:) remplacent le extend :
class Societaire : public Personne { }

Le public en Java et en C++ na pas la mme signication et nest dailleurs pas la mme position. En Java nous connaissons dj sa signication, comme nous lavons vu au chapitre 7, lors du traitement des paquets (package). Nous rappellerons quun chier .java ne peut contenir quune seule classe public. Cette remarque est judicieuse ici, car, pour simplier la prsentation de nos exemples et exercices, nous avons dni les classes de bases et drives dans le mme chier. Il en va de mme pour la dnition des classes en C++, qui devraient tre dnies dans des chiers den-tte .h pour des raisons de rutilisation.

256

Apprendre Java et C++ en parallle

Enn, le public en C++ indique que tous les membres publics de la classe de base resteront publics dans la classe drive. Nous passerons sur ce dtail, et il y en a beaucoup en C++, et nutiliserons par la suite que cette forme avec le public.

Linitialisation des constructeurs


Nous allons prsent reprendre notre exemple de classe Societaire et montrer deux exemples pratiques dutilisation. Nous verrons aussi la manire dinitialiser la classe ellemme et sa ou ses classes de base. Notre classe Java Societaire se prsentera ainsi :
class Personne { private String nom; private String prenom; private int annee; public Personne(String lenom, String leprenom, int lannee) { nom = lenom; prenom = leprenom; annee = lannee; System.out.println("Constructeur de Personne"); } public void un_test() { System.out.println("Nom et prnom: " + nom + " " + prenom); System.out.println("Anne de naissance: " + annee); } } public class Societaire extends Personne { private int cotisation; public Societaire(String lenom, String leprenom, int lannee, int lacotis) { super(lenom, leprenom, lannee); cotisation = lacotis; System.out.println("Constructeur de Societaire"); } public void un_test() { super.un_test(); System.out.println("Cotisation: " + cotisation); } public static void main(String[] args) { Societaire unSoc = new Societaire("Haddock", "Capitaine", 1907, 100); unSoc.un_test(); } }

Un hritage attendu CHAPITRE 12

257

alors quen C++ nous aurons :


// Societaire.cpp #include <iostream> #include <string> using namespace std; class Personne { private: string nom; string prenom; int annee; public: Personne(const string lenom, const string leprenom, int lannee); ~Personne(); void un_test(); }; class Societaire : public Personne { private: int cotisation; public: Societaire(const string lenom, const string leprenom, int lannee, int lacotis); ~Societaire(); void un_test(); }; Personne::Personne(const string lenom, const string leprenom, int lannee) :nom(lenom), prenom(leprenom), annee(lannee) { cout << "Constructeur de Personne" << endl; } Personne::~Personne() { cout << "Destructeur de Personne" << endl; } void Personne::un_test() { cout << "Nom et prnom: " << nom << " " << prenom << endl; cout << "Anne de naissance: " << annee << endl; } Societaire::Societaire(const string lenom, const string leprenom, int lannee, int lacotis) :Personne(lenom, leprenom, lannee) { cotisation = lacotis; cout << "Constructeur de Societaire" << endl; }

258

Apprendre Java et C++ en parallle

Societaire::~Societaire() { cout << "Destructeur de Societaire" << endl; } void Societaire::un_test() { Personne::un_test(); cout << "Cotisation: " << cotisation << endl; } int main() { Societaire un_soc("Haddock", "Capitaine", 1907, 100); un_soc.un_test(); return 0; }

Nous avons introduit des messages dans les diffrentes parties du code. Ils nous permettent danalyser le chemin des appels de constructeurs, mthodes et destructeurs (ces derniers en C++ seulement). Dans les deux exemples, nous avons le mme rsultat :
Constructeur de Personne Constructeur de Societaire Nom et prnom: Haddock Capitaine Anne de naissance: 1907 Cotisation: 100

avec en plus pour le cas C++ et la n :


Destructeur de Societaire Destructeur de Personne

Les appels des diffrents constructeurs et destructeurs de la classe de base Personne et de sa sous-classe Societaire suivent une logique attendue. Cest cependant un aspect important considrer si des ressources communes taient utilises. Cest le cas en particulier pour le constructeur de la sous-classe, qui peut utiliser des ressources alloues par le constructeur de sa classe de base. Cependant, ce genre de problme ne devrait pas apparatre si nous suivons la rgle dallouer le minimum de ressources dans les constructeurs. Ces derniers ne peuvent de toute manire retourner des erreurs quen gnrant des exceptions. La grande diffrence entre Java et C++ est la manire dinitialiser la classe de base (super() en Java et :Personne() en C++) et la faon dappeler une mthode du mme nom de la classe de base (super. en Java et Personne:: en C++). Ce ne sont en fait que des dtails de syntaxe, mais il faut se les rappeler. En cas derreur, les compilateurs peuvent nous donner des indications intressantes. Comme illustration, nous prendrons deux exemples. Nous commencerons par le code Java de la classe Societaire, o nous mettrons en commentaire la ligne :
// super(lenom, leprenom, lannee);

En supprimant cette ligne, nous empchons une initialisation correcte du constructeur de base. Si nous essayions de compiler ce code, nous obtiendrions ceci :

Un hritage attendu CHAPITRE 12

259

Societaire.java:22: No constructor matching Personne() found in class Personne. public Societaire(String lenom, String leprenom, int lannee, int lacotis) { ^ 1 error

Le compilateur va rechercher un constructeur Personne() quil ne trouve pas. Il y a deux possibilits : 1. Nous corrigeons le code. 2. Nous introduisons un constructeur Personne() avec dventuelles initialisations pour les variables nom, prenom et annee, et vraisemblablement un constructeur Societaire() sans paramtres. Durant la conception de ce type de classe, ce nest certainement pas ncessaire dimplmenter cette dernire solution, car une personne sans identit est peu vraisemblable dans une application traditionnelle. Une vue globale du problme est essentielle. Pour une application de base de donnes o une classe Personne serait associe des coureurs dun marathon, nous pourrions imaginer une rservation des petits numros de dossard pour des invits de marque. Il faudrait alors se poser la question de savoir comment positionner et initialiser ces numros. Nous devrions certainement crer des objets sans identit, o un constructeur par dfaut pourrait tre ncessaire ! En faisant le mme exercice en C++ avec :
Societaire::Societaire(string lenom, string leprenom, , int lacotis) { cotisation = lacotis; cout << "Constructeur de Societaire" << endl; }

au lieu de :
Societaire::Societaire(string lenom, string leprenom, , int lacotis) :Personne(lenom, leprenom, lannee) { cotisation = lacotis; cout << "Constructeur de Societaire" << endl; }

nous aurions aussi une surprise trs similaire Java :


Societaire.cpp:48: no matching function for call to `Personne::Personne ()' Societaire.cpp:34: candidates are: Personne::Personne(basic_string<char,string_char_traits<char Societaire.cpp:19: Personne::Personne(const Personne &)

o la dernire erreur est sans doute plus intressante et nous indique quil manque un constructeur de copie. En fait, nous reviendrions sur la rexion prcdente et nirions par initialiser lobjet de la classe de base correctement.

260

Apprendre Java et C++ en parallle

Combiner hritage et composition


Dans le premier exercice de cette partie qui va suivre, nous combinerons hritage et composition. Pour ce faire, nous devrons aussi connatre en C++ cette construction :
class X2 : public X1 { Y y; X2(int i, int j) : X1(i), y(j) {} }

La classe X2 hrite de X1. Le constructeur de X2 reoit deux paramtres, un pour initialiser un attribut de la classe de base X1, et un autre pour lattribut Y. Il faut noter le y minuscule du y(j). Cette variable est un membre de la classe X2 et non un appel un constructeur ou une quelconque mthode de la classe de base X1.

Accs public, priv ou protg


Cest vraiment le moment idal pour introduire la directive protected, car nous connaissons dj les public et private. Pour ce faire, avant dillustrer les diffrents cas par deux exemples en Java et C++ sur le polymorphisme, nous allons reprendre la dnition des directives pour la protection des accs : public (publique), cest--dire visible pour toutes les classes ; private (prive), cest--dire visible uniquement par la classe ; protected (protge), cest--dire visible par les classes drives et packages en Java.
Note Lauteur prote de loccasion pour expliquer lapproche choisie ici dans la prsentation. Un ouvrage complet prsentant dans un seul de ces deux langages la fois lhritage et le polymorphisme aurait besoin dune cinquantaine de pages ! Le lecteur comprendra sans doute cette approche plus directe au travers dexemples qui apportent plusieurs nouveaux concepts. Le plus important est de couvrir les sujets essentiels an dapprhender ces deux langages de la manire la plus rapide.

Le polymorphisme
Une classe qui permet linterface diverses autres classes est appele polymorphe. Ce sont des classes drives dune classe de base qui elle-mme contient des dnitions de mthodes qui seront en fait codes dans les sous-classes. Si nous possdions une collection de formes graphiques de diffrents types comme des rectangles ou des cercles, et que nous aimions appliquer une mthode dessine() sur chacun de ces objets sans connatre le type de cet objet, nous aurions alors besoin dun mcanisme particulier. Dans les deux exemples en Java et C++ qui suivent, nous allons examiner ce concept de polymorphisme et analyser son implmentation. Nous allons rencontrer les deux termes de rednition et de surcharge dune mthode dnie dans une classe de base.

Un hritage attendu CHAPITRE 12

261

Tout le monde connat bien ces petits hommes bleus appels les Schtroumpfs et plus particulirement le Grand Schtroumpf, la Schtroumpfette ou le Schtroumpf Grognon. Nous allons donc schtroumpfer une classe de base nomme SchtroumpfGeneric1, de laquelle nous schtroumpferons une sous-classe nomme SchtroumpfGrognon1.

Les Schtroumpfs en Java


Nous commencerons par la version Java :
class SchtroumpfGeneric1 { private String nom; private int compteur1 = 0; protected int compteur2 = 0; public SchtroumpfGeneric1(String lenom) { nom = lenom; } public String getNom() { return nom; } public void parle(String texte) { System.out.println(getNom() + " dit " + texte); compteur1++; compteur2++; } public int getCompteurs() { return (100 * compteur1) + compteur2; } } public class SchtroumpfGrognon1 extends SchtroumpfGeneric1 { public SchtroumpfGrognon1(String lenom) { super(lenom); } public void parle(String texte) { System.out.println(getNom() + " dit en grognant " + texte); // compteur1++; impossible car private compteur2++; } public static void unStroumpfParle(SchtroumpfGeneric1 unStroumpf, String texte) { unStroumpf.parle(texte); } public static void main(String[] args) {

262

Apprendre Java et C++ en parallle

SchtroumpfGeneric1 unBleu = new SchtroumpfGrognon1("Petit Grognon"); unBleu.parle("Bonjour"); unStroumpfParle(unBleu, "Salut"); System.out.println("Compteurs: " + unBleu.getCompteurs()); } }

Si nous lexcutons, nous schroumpferons alors le rsultat suivant :


Petit Grognon dit en grognant Bonjour Petit Grognon dit en grognant Salut Compteurs: 2

La raison de dnir la variable unBleu comme instance de la classe SchtroumpfGeneric1 est, en fait, lanalyse de la mthode parle() sur cet objet. Comme pour des formes, nous pourrions avoir une collection de rectangles ou de cercles que nous aimerions dessiner. Nous aurions alors une collection de formes, que nous pourrions dessiner sans connatre le type de lobjet, cest--dire en utilisant le polymorphisme. Ici, nous nallons pas essayer de faire parler plusieurs Schtroumpfs, car nous savons que ce serait la pagaille ! La mthode :
public static void unStroumpfParle(StroumpfGeneric1 unStroumpf, String texte)

pourrait nous sembler trange. Le static est correct, car aucune ressource dun objet de classe StroumpfGrognon1 nest utilise. Sa vraie place pourrait tre dans la classe StroumpfGeneric1, mais en fait largument StroumpfGeneric1 unStroumpf peut se trouver comme paramtre de nimporte quelle mthode ou de nimporte quel constructeur dune classe qui veut traiter une rfrence un StroumpfGeneric1. Le rsultat est donc attendu. Nous faisons bien parler notre Schtroumf Grognon deux fois. La mthode parle() de la classe de base StroumpfGeneric1 nest donc jamais appele. Les deux variables nom et compteur1 sont prives (private). Il nest alors pas possible la classe SchtroumpfGrognon1 daccder directement ces variables, et il faut donc des mthodes pour y accder. La variable compteur2 est protge (protected) et ainsi accessible toute classe drive de StroumpfGeneric1. Si nous avions voulu que compteur2 nait une signication que pour StroumpfGrognon1, nous laurions dplace dans cette dernire et dnie private ou ventuellement protected, si nous avons lintention dtendre encore la hirarchie de ces hommes bleus. Le rsultat 2, retourn par la mthode getCompteurs(), est attendu. La multiplication nest ici qu des ns de prsentation, car un Schtroumpf Grognon qui va parler plus de cent fois est du domaine du plausible.

Les Schtroumpfs en C++


Cette partie va nous tonner et nous faire plonger dans les profondeurs des difcults du langage C++. Lhritage en Java est autrement plus simple et direct.
// SchtroumpfGrognon1.cpp #include <iostream> #include <string>

Un hritage attendu CHAPITRE 12

263

using namespace std; class SchtroumpfGeneric1 { private: string nom; int compteur1; protected: int compteur2; public: SchtroumpfGeneric1(string lenom); string getNom(); void parle(string texte); int getCompteurs(); static void unStroumpfParle(const SchtroumpfGeneric1 *unStroumpf, string texte); }; class SchtroumpfGrognon1 : public SchtroumpfGeneric1 { public: SchtroumpfGrognon1(string lenom); void parle(string texte); }; SchtroumpfGeneric1::SchtroumpfGeneric1(string lenom) :nom(lenom), compteur1(0), compteur2(0) { } string SchtroumpfGeneric1::getNom() { return nom; } void SchtroumpfGeneric1::parle(string texte) { cout << getNom() << " dit " << texte << endl; compteur1++; compteur2++; } int SchtroumpfGeneric1::getCompteurs() { return (100 * compteur1) + compteur2; } SchtroumpfGrognon1::SchtroumpfGrognon1(string lenom) :SchtroumpfGeneric1(lenom) { } void SchtroumpfGrognon1::parle(string texte) { cout << getNom() << " dit en grognant " << texte << endl; // compteur1++; impossible car private compteur2++; }

264

Apprendre Java et C++ en parallle

void unStroumpfParle(SchtroumpfGeneric1 *unStroumpf, const string texte) { unStroumpf->parle(texte); } int main() { SchtroumpfGrognon1 *un_bleu; un_bleu = new SchtroumpfGrognon1("Petit Grognon"); un_bleu->parle("Bonjour"); unStroumpfParle(un_bleu, "Salut"); cout << "Les compteurs: " << un_bleu->getCompteurs() << endl; delete un_bleu; return 0; }

Le rsultat sera diffrent :


Petit Grognon dit en grognant Bonjour Petit Grognon dit Salut Les compteurs: 102

Est-ce vraiment ce que nous voulions ? Pas ncessairement ! Si nous avons redni la fonction dans la classe drive, cest que nous voulions en fait la surcharger ! Il nous faut donc un mcanisme qui nous permette de forcer le compilateur, an quil excute la bonne mthode. Il faut noter ici, et cest aussi applicable en Java, que la rednition ou la surcharge sapplique des mthodes qui ont la mme signature et le mme type de retour. En ce qui concerne les constructeurs, les destructeurs en C++ et loprateur = en C++, il ny a pas de mcanisme dhritage comme cela sapplique aux mthodes. Avant de passer la solution de ce problme, nous allons examiner le code de plus prs pour remarquer la manire dinitialiser les attributs de classes. Si nous faisions comme en Java :
8: 9: 10: 11: class SchtroumpfGeneric1 { private: string nom; int compteur1 = 1;

nous aurions alors lerreur de compilation suivante :


SchtroumpfGrognon1.cpp:11: ANSI C++ forbids initialization of member `compteur1' SchtroumpfGrognon1.cpp:11: making `compteur1' static SchtroumpfGrognon1.cpp:11: ANSI C++ forbids in-class initialization of non-const static member `compteur1' SchtroumpfGrognon1.cpp: In method `SchtroumpfGeneric1::SchtroumpfGeneric1(basic_string<char,string_char _traits<char>,__default_alloc_template<false,0> >)': SchtroumpfGrognon1.cpp:33: field `int SchtroumpfGeneric1::compteur1' is static; only point of initialization is its declaration

o une affectation nest possible que pour des variables statiques.

Un hritage attendu CHAPITRE 12

265

Ceci pour indiquer que linitialisation des types de base est essentielle, car nous aurions alors tendance supprimer le = 1 sans penser trop loin. Si nous avions omis compteur2(0) dans le constructeur de SchtroumpfGeneric1, nous aurions pu recevoir un 5375074 comme rsultat. Sur une machine Sparc sous Solaris, nous avons constat que la variable tait initialise 0 par le compilateur. Lors dune rutilisation du code sur une autre machine, il est donc essentiel la fois dinitialiser toutes les variables et de prvoir les tests correspondants (ici vrier la mthode getCompteurs() avant davoir laiss un Schtroumpf, ce qui est loin dtre vident).

Le virtual en C++
Le mot-cl virtual attach une mthode dune classe de base va garantir que la bonne version de la mthode va tre appele, cest--dire garantir le polymorphisme qui se fait automatiquement en Java. Il ne servirait rien dappliquer la mthode dessine() la classe Forme, qui ne saurait dessiner un rectangle si lobjet tait un Rectangle. La seule chose que pourrait connatre la classe Forme serait sans doute la position du rectangle, comme tous autres objets, rien de plus. Nous allons donc retourner nos petits hommes bleus avec une fonction ici dclare virtuelle :
// SchtroumpfGrognon2.cpp #include <iostream> #include <string> using namespace std; class SchtroumpfGeneric2 { protected: string nom; public: SchtroumpfGeneric2(string lenom); virtual void parle(string texte); static void unStroumpfParle(SchtroumpfGeneric2 *unStroumpf, const string texte) { unStroumpf->parle(texte); }; }; class SchtroumpfGrognon2 : public SchtroumpfGeneric2 { public: SchtroumpfGrognon2(string lenom); void parle(string texte); }; SchtroumpfGeneric2::SchtroumpfGeneric2(string lenom) :nom(lenom) {

266

Apprendre Java et C++ en parallle

} void SchtroumpfGeneric2::parle(string texte) { cout << nom << " dit " << texte << endl; } SchtroumpfGrognon2::SchtroumpfGrognon2(string lenom) :SchtroumpfGeneric2(lenom) { } void SchtroumpfGrognon2::parle(string texte) { cout << nom << " dit en grognant " << texte << endl; } int main() { SchtroumpfGrognon2 *un_bleu; un_bleu = new SchtroumpfGrognon2("Gros Grognon"); un_bleu->parle("Bonsoir"); SchtroumpfGeneric2::unStroumpfParle(un_bleu, "Bonne nuit"); delete un_bleu; return 0; }

Et le rsultat tant espr :


Gros Grognon dit en grognant Bonsoir Gros Grognon dit en grognant Bonne nuit

Il faut noter ici que la mthode parle() dans la classe SchtroumpfGrognon2 pourrait trs bien tre laisse de ct, mme si le mot-cl virtual a t utilis. Il y a certainement un nombre de varits de Schtroumpfs qui parlent normalement et qui peuvent utiliser la mthode gnrique sans rechigner ou chanter une chanson. Il faudra noter lutilisation simplie de la variable nom au travers dun accs protected et la forme SchtroumpfGeneric2::unStroumpfParle(), car nous avons ici dni cette mthode statique. Nous pourrions aussi appeler la mthode parle() de la classe de base :
void SchtroumpfGrognon2::parle(string texte) { cout << nom << " dit en grognant " << texte << endl; SchtroumpfGeneric2::parle(texte); }

et nous obtiendrions :
Gros Gros Gros Gros Grognon Grognon Grognon Grognon dit dit dit dit en grognant Bonsoir Bonsoir en grognant Bonne nuit Bonne nuit

Le Stroumpfgeneric2::parle() na pas trop de sens ici. Il ne va pas parler deux fois mme sil est grognon ou sympa. Nous pourrions toujours trouver un Schtroumpf bgayeur, qui aurait un comportement encore diffrent ! Cependant, si nous voulions par exemple

Un hritage attendu CHAPITRE 12

267

imprimer des attributs privs la classe de base pour des raisons de test, il serait tout fait plausible dappeler la mthode de base pour accder ces informations. Nous pourrions aussi donner un autre nom ou dnir une autre signature la mthode. Le passage aux classes abstraites est donc tout fait naturel. Si nous reprenons notre classe Forme, nous pourrons dnir dans cette dernire une mthode dessine(). Cependant, la classe Forme ne saura pas dessiner et devra dlguer la responsabilit totale aux sousclasses comme Cercle ou Rectangle.

Les classes abstraites en C++


Si nous dnissons notre classe Personne abstraite, nous pourrions penser que nous avons un problme puisquune personne, en principe, est loin dtre un objet abstrait car elle possde au moins un nom, un prnom et un ge. Pour une classe Forme, partir de laquelle nous allons crer dautres classes comme Rectangle ou Cercle, cest un peu diffrent, un peu plus abstrait. Une forme reste un objet, comme le myope qui aurait de la peine diffrencier un cercle dun rectangle. La dnition dune classe abstraite en C++ ou Java est utilise en fait uniquement pour indiquer quelle regroupe un certain nombre de fonctionnalits communes. Ces dernires seront utilises dans des sous-classes qui vont hriter de ces caractristiques. Si nous dclarons notre classe Personne abstraite, nous ne pourrons plus crer dinstances de cette classe, mais seulement de ses drives. Cependant, les mthodes resteront accessibles au travers de nouvelles classes comme Societaire ou Employee. Voyons prsent comment nous pourrions dnir notre classe Schtroumpf abstraite.

Fonction purement virtuelle en C++


Nous avons vu prcdemment le mot-cl virtual utilis de cette manire :
virtual void parle(string texte);

Nous allons prsent indiquer une forme en C++ qui au premier abord va nous paratre trs trange :
virtual void parle(string texte) = 0;

Celle-ci nous indique que la mthode est virtuelle pure et doit tre absolument implmente dans la classe drive. Voici donc le code complet de la classe SchtroumpfGrognon3, qui utilise la classe abstraite Schtroumpf :
// SchtroumpfGrognon3.cpp #include <iostream> #include <string> using namespace std; class Schtroumpf {

268

Apprendre Java et C++ en parallle

protected: string nom; public: Schtroumpf(string lenom) : nom(lenom) {}; virtual void parle(string texte) = 0; static void unStroumpfParle(Schtroumpf *unStroumpf, const string texte) { unStroumpf->parle(texte); }; }; class SchtroumpfGrognon3 : public Schtroumpf { public: SchtroumpfGrognon3(string lenom) : Schtroumpf(lenom) {} ; void parle(string texte); }; void SchtroumpfGrognon3::parle(string texte) { cout << nom << " dit en grognant " << texte << endl; } int main() { Schtroumpf *un_bleu = new SchtroumpfGrognon3("Un Gros Grognon"); un_bleu->parle("Bonsoir"); Schtroumpf::unStroumpfParle(un_bleu, "Bonne journe"); delete un_bleu; return 0; }

Il ny a pas beaucoup de diffrence avec la forme prcdente, si ce nest que nous avons remani les deux classes an davoir plus de code en ligne dans la dnition. La forme :
Schtroumpf *un_bleu = new SchtroumpfGrognon3("Un Gros Grognon");

tait dj possible dans la version prcdente, et il est important de la mentionner, car nous pourrions par exemple dnir une collection de Schtroumpfs du mme type avec diffrents noms. La mthode :
void Schtroumpf::parle(string texte) { ..

na pas besoin dtre dnie, car il nest pas possible de crer directement une instance de la classe Schtroumpf. Si nous crivions ce code :
Schtroumpf unpetit = new Schtroumpf("Petit Schtroumpf");

nous aurions alors une erreur de compilation :


SchtroumpfGrognon3.cpp: In function `int main()': SchtroumpfGrognon3.cpp:40: cannot declare variable `unpetit' to be of type `Schtroumpf' SchtroumpfGrognon3.cpp:40: since the following virtual functions are abstract: SchtroumpfGrognon3.cpp:14: void Schroumpf::parle(basic_string<char, string_char_traits<char>,

Un hritage attendu CHAPITRE 12

269

__default_alloc_template<false,0> >) SchtroumpfGrognon3.cpp:40: cannot allocate an object of type `Schtroumpf' SchtroumpfGrognon3.cpp:40: since type `Schtroumpf' has abstract virtual functions

Il nous faut donc dnir une sous-classe pour tous les types possibles de Schtroumpf. Si cette solution est vraiment trop lourde, il nous faudrait revenir une classe Schtroumpf gnrique. Une classe abstraite en C++ est en fait une dnition dinterface pour ces classes drives.

Destructeur virtuel en C++


Un des aspects essentiels en programmation C++ est la comprhension et lutilisation correcte des destructeurs virtuels. Si nous crivons ce morceau de code :
// Virtueldestr.cpp #include <iostream> using namespace std; class Mabase { private: char *preservea; public: Mabase() { preservea = new char[100]; cout << "preserva allou" << endl; } ~Mabase() { delete[] preservea; cout << "preserva effac" << endl; } }; class Maderivee : public Mabase { private: char *preserveb; public: Maderivee() { preserveb = new char[100]; cout << "preservb allou" << endl; } ~Maderivee() { delete[] preserveb; cout << "preservb effac" << endl; } };

270

Apprendre Java et C++ en parallle

int main() { Maderivee *pobjet1 = new Maderivee(); delete pobjet1; cout << endl; Mabase *pobjet2 = new Maderivee(); delete pobjet2; }

et que nous regardons le rsultat obtenu :


preserva preservb preservb preserva preserva preservb preserva allou allou effac effac allou allou effac

nous voyons immdiatement un problme : il manque un preservb effac ! Une premire remarque doit tre faite pour le constructeur de Maderivee. Sa premire action est dappeler le constructeur de Mabase, qui va allouer preservea, et ceci avant de faire de mme pour preserveb. Les attributs preservea et preserveb sont de simples tampons dynamiques de 100 octets pour un usage divers dans les deux classes. Il faut absolument les effacer avec les destructeurs. Nous croyons bien faire avec un destructeur correctement cod pour chaque classe. Le problme viendra lors de ce type de construction :
Mabase *pobjet2 = new Maderivee();

pobjet2 est un objet Mabase contenant en fait un objet de Maderivee. Ensuite, lorsque le destructeur sera appel au travers du delete pobjet2, il ne saura excuter le code dsir. Pour nous en sortir, il faudra ajouter le mot-cl virtual pour le destructeur de la classe de

base :
virtual ~Mabase() {

Nous pouvons modier le code ci-dessus avec cette ligne de code et vrier que cette fois le mcanisme virtuel fonctionnera correctement et que toutes les ressources alloues auront t effaces.

Les classes abstraites en Java


Jusqu prsent, nous avons beaucoup parl de C++ et peu de Java. La raison en est simple : lhritage de classes en Java est nettement simpli. Nous navons pas ici de mot-cl virtual, avec une extension possible pure virtuelle, nous avons simplement celui dabstract. Une mthode dclare abstract indiquera que la classe ne sait pas comment implmenter le code et doit laisser ce travail la classe drive. Cest le cas de dessine(), dans la classe Forme. Si lobjet est un cercle, seule la classe Cercle, qui possde son rayon comme attribut, saura dessiner cette forme.

Un hritage attendu CHAPITRE 12

271

Ds quune classe Java possde une mthode abstraite, la classe sera abstraite et devra tre dclare abstract. Nous pouvons aussi dcider de dclarer une classe abstract mme si elle na pas de mthode abstraite. Comme en C++, toute classe abstraite en Java ne peut tre instancie. Nous avons dj beaucoup parl de la classe Forme et de ses classes drives Cercle et Rectangle. Voici donc prsent un exemple en Java, o nous commenons avec la classe abstraite Forme :
public abstract class Forme { protected int origineX; protected int origineY; public Forme(int posx, int posy) { origineX = posx; origineY = posy; } public abstract void dessine(); }

Elle est abstraite, car la mthode dessine() est dclare abstract et ne possde pas de code. Elle correspond une dnition. Nous devrons donc la dnir dans une sous-classe de Forme. Cela veut dire, aussi, quil nest pas possible de dnir une instance de cette classe abstraite. Nous dnissons maintenant la classe Rectangle comme suit :
public class Rectangle extends Forme { protected int longueur; protected int hauteur; public Rectangle(int posx, int posy, int laLongueur, int laHauteur) { super(posx, posy); longueur = laLongueur; hauteur = laHauteur; } public void dessine() { System.out.print("Notre rectangle la position " + origineX + "," + origineY); System.out.println(" et de dimensions " + longueur + "," + hauteur + " est dessin!"); } }

Si nous navions pas dni la mthode dessine(), nous aurions alors reu cette erreur :
Rectangle.java:1: class Rectangle must be declared abstract. It does not define void dessine() from class Forme. public class Rectangle extends Forme { ^

272

Apprendre Java et C++ en parallle

Cela peut signier deux choses : 1. Nous avons oubli le code de dessine(). Ce qui est effectivement notre cas. 2. La classe dessine() sera dnie plus loin dans une autre sous-classe de Rectangle, cest--dire dans un troisime niveau de notre hirarchie. Comme pour notre classe Rectangle, nous pouvons dnir une mthode dessine() pour la classe Cercle :
public class Cercle extends Forme { protected int rayon; public Cercle(int posx, int posy, int leRayon) { super(posx, posy); rayon = leRayon; } public void dessine() { System.out.print("Notre cercle la position " + origineX + "," + origineY); System.out.println(" et de rayon " + rayon + " est dessin!"); } }

Un cercle est vraiment diffrent, et ce ne serait pas le cas dun carr, qui peut trs bien utiliser la reprsentation de notre classe Rectangle. Nous pourrions trs bien crer une nouvelle classe pour dnir ce carr ou encore un nouveau constructeur pour la classe Rectangle de cette manire :
public Rectangle(int posx, int posy, int leCote) { super(posx, posy); longeur = leCote; largeur = leCote; }

Il nous reste prsent vrier nos trois nouvelles classes :


public class Dessin { Forme[] lesObjets; public Dessin() { lesObjets = new lesObjets[0] = new lesObjets[1] = new lesObjets[2] = new lesObjets[3] = new }

Forme[4]; Rectangle(0,0,1,1); Rectangle(5,1,1,2); Cercle(1,5,1); Cercle(2,2,4);

public void dessine() { for (int i = 0; i < lesObjets.length; i++) { lesObjets[i].dessine();

Un hritage attendu CHAPITRE 12

273

} } static public void main(String args[]) { Dessin unDessin = new Dessin(); unDessin.dessine(); } }

La classe Dessin possde quatre objets, que nous dessinons en utilisant une collection de formes. Il faut toujours prendre des valeurs sufsamment diffrentes pour contrler lutilisation correcte des attributs de classe. Choisir des int pour les attributs de position et de grandeur pour les objets que nous aimerions dessiner naurait un sens que si nous travaillions en pixels. Le rsultat prsent :
Notre Notre Notre Notre rectangle rectangle cercle la cercle la la position 0,0 la position 5,1 position 1,5 et position 2,2 et et et de de de dimension 1,1 est dessin! de dimension 1,2 est dessin! rayon 1 est dessin! rayon 4 est dessin!

correspond tout fait notre attente.

Le transtypage (casting) dobjet


Lorsquune mthode existe dans une sous-classe et savre spcique celle-ci, nous ne pouvons lappliquer un objet de sa classe de base sans tester la validit de son existence au moyen dun transtypage.

Le transtypage en Java
Nous reprendrons lexemple ci-dessus avec une mthode dessineTexte() dans notre classe Rectangle, mthode qui nexiste pas dans la classe Cercle. Nous trouverons sur le CD-Rom daccompagnement, dans les classes Dessin2 et Rectangle2, le code qui correspond ce cas prcis. Dans la classe de test Dessin2, nous utiliserons prsent des objets Rectangle2 en lieu et place de Rectangle. Dans la classe Rectangle2, nous avons ajout le code suivant :
public void dessineTexte() { System.out.println("Notre rectangle contient du texte l'intrieur"); }

Dans la classe Dessin2, si nous compilions le code :


public void dessine() { for (int i = 0; i < lesObjets.length; i++) { lesObjets[i].dessine(); lesObjets[i].dessineTexte(); } }

274

Apprendre Java et C++ en parallle

nous obtiendrions une erreur car la mthode dessineTexte() nexiste pas dans la classe Forme. Le seul moyen est dappliquer un transtypage, que nous pourrions faire de cette manire :
public void dessine() { for (int i = 0; i < lesObjets.length; i++) { lesObjets[i].dessine(); Rectangle2 rec = (Rectangle2)lesObjets[i]; rec.dessineTexte(); } }

Si nous excutons ce code, nous obtiendrons cette erreur :


Exception in thread "main" java.lang.ClassCastException: Cercle at Dessin2.dessine(Compiled Code) at Dessin2.main(Compiled Code)

Il est en effet impossible de prendre un rectangle pour un cercle et, en plus, dessayer de lui appliquer une mthode dessineTexte() qui nest admissible que pour des objets de la classe Rectangle. Nous sommes donc contraints dutiliser loprateur instanceof, qui va nous permettre didentier la classe avant dy appliquer la mthode dessineTexte().
public void dessine() { for (int i = 0; i < lesObjets.length; i++) { lesObjets[i].dessine(); if (lesObjets[i] instanceof Rectangle2) { Rectangle2 rec = (Rectangle2)lesObjets[i]; rec.dessineTexte(); } } }

Et le rsultat attendu nous sera prsent si nous excutons la classe de test Dessin2 :
Notre Notre Notre Notre Notre Notre rectangle la position 0,0 rectangle contient du texte rectangle la position 5,1 rectangle contient du texte cercle la position 1,5 et cercle la position 2,2 et et de dimension 1,1 est dessin! l'intrieur et de dimension 1,2 est dessin! l'intrieur de rayon 1 est dessin! de rayon 4 est dessin!

Comment viter le transtypage


Cest effectivement une grande question, aprs avoir examin le code prcdent. Il y a parfois des cas o cela se prsente mal comme lorsque des modications de dernire minute doivent tre apportes au code et, dans ce cas prcis, dans une classe drive. Il faudrait en fait revenir la conception des classes de base et de la hirarchie. Nous aurions d en fait concevoir une mthode abstraite dessineTexte() dans la classe de base

Un hritage attendu CHAPITRE 12

275

Forme. Nous aurions pu alors activer dessineTexte() sur nimporte quelle sous-classe, qui aurait alors contenu une implmentation sans code de dessineTexte(). Une autre manire de faire aurait t de dnir une mthode non abstraite dans Forme et de la surcharger dans la classe Rectangle. Cette dernire possibilit sera donne en exercice.

Le transtypage en C++
Nous avons vu que lorsque nous avons une collection dobjets de diffrents types, il est parfois ncessaire dappliquer un transtypage. Dans lexemple qui suit, la mthode dirige() nexiste que pour la classe Directeur, alors que la classe de base Employe ne la possde pas. Dans une collection dinstance dEmploye, nous pourrions liminer les directeurs, mais ce serait sans doute plus convenable de choisir ces derniers pour les faire diriger lentreprise. Voici donc le code de ces deux classes avec le programme de test associ :
// Entreprise.cpp #include <iostream> #include <string> using namespace std; class Employe { protected: string nom; public: Employe(const string le_nom) : nom(le_nom) {}; virtual string get_nom() { return nom; }; }; class Directeur : public Employe { public: Directeur(const string le_nom) : Employe(le_nom) { }; void dirige() { cout << "Le directeur " << nom << " dirige !" << endl; } }; int main() { Employe *lentreprise[3]; lentreprise[0] = new Employe("employ1"); lentreprise[1] = new Employe("employ2"); lentreprise[2] = new Directeur("directeur1"); for (int i = 0; i < 3; i++) { cout << "Le nom: " << lentreprise[i]->get_nom() << endl; } for (int i = 0; i < 3; i++) {

276

Apprendre Java et C++ en parallle

Directeur *direc = dynamic_cast<Directeur*>(lentreprise[i]); if (direc != NULL) { direc->dirige(); } } for (int i = 0; i < 3; i++) { delete lentreprise[i]; } }

Le nom de lemploy, en considrant quun directeur est aussi un employ, est conserv dans la classe de base Employe. La mthode virtuelle get_nom() est ici juste pour sortir plus tard la liste des employs. Cependant, le mot-cl virtual est essentiel pour dclencher ce mcanisme didentication (RTTI, Run-Time Type Identication). Si nous ne dclarons pas au moins une mthode virtuelle, la ligne de code :
Directeur *direc = dynamic_cast<Directeur*>(lentreprise[i]);

ne compilerait simplement pas. Nous avons dni une liste de trois employs qui se trouve dans la variable lentreprise. Il ne faudra dailleurs pas oublier deffacer ces trois objets, comme nous lavons fait en n de programme ou lorsquils ne sont plus utiliss. Nous avons donc une liste demploys avec deux objets de la classe Employe et un de la classe Directeur. Lorsque nous traversons la liste, nous navons pas de problmes avec la mthode get_nom() car elle est dnie dans la classe de base. Il en va tout autrement pour la mthode dirige(), qui nest disposition que dans la classe Directeur. Cest ici que notre construction dynamic_cast<Directeur*> va nous permettre dobtenir le transtypage si celui-ci est possible. En Java nous avions besoin du instanceof, alors quici un simple contrle sur un pointeur null sera sufsant. Le rsultat sera prsent ainsi :
Le Le Le Le nom: employ1 nom: employ2 nom: directeur1 directeur directeur1 dirige !

Comme ce fut le cas de lexemple en Java, nous pourrions nous poser toute une srie de questions, comme celle de savoir si notre classe Employe devrait tre abstraite et dnir par exemple une sous-classe Ouvrier. Dans une entreprise, un directeur peut tre absent et un des ouvriers pourrait tre nomm remplaant. Une bonne conception de ses classes et de leurs hirarchies est essentielle, surtout lorsque nous pourrions viter de telles constructions comme notre dynamic_cast<>.

Lhritage en Java et en C++ : les diffrences


Nous allons rcapituler, dans ce domaine, les principales diffrences entre Java et C++ :

Un hritage attendu CHAPITRE 12

277

1. En Java, toutes les mthodes sont virtuelles par dfaut. Elles sexcutent donc plus lentement. 2. En C++, il faut utiliser virtual pour dclarer quune mthode est virtuelle, cest-dire pour quelle soit dnie dans une classe drive. En Java, il faut utiliser la directive abstract pour la mthode. 3. En dclarant final une mthode en Java, celle-ci ne pourra plus tre surcharge. Une classe dclare final aura toutes ses mthodes dnies final. Les mthodes Java final peuvent tre alors remplaces par du code en ligne et gagner en performance. 4. En C++, si nous voulons que le compilateur remplace le code pour amliorer les performances, il est ncessaire dutiliser la directive inline. Au chapitre 15, nous reviendrons sur le thme de la performance. Quels sont donc ces facteurs de performance ? Nous serons trs surpris !

Rsum
Ce chapitre est galement trs important car il couvre un certain nombre daspects essentiels de la programmation objet en Java et C++. Si la manire Java est nettement simplie, le lecteur comprendra aisment que cette partie est lun des aspects les plus complexes du langage C++.

Exercices
1. Pour montrer les deux approches de composition et dhritage, crire deux classes de base Base1 et Base2 qui chacune contient un String et un int comme attributs. crire une classe DoubleBase qui hrite de Base1 et qui est compose dun objet de Base2 avec en plus un String et un int. Le constructeur de DoubleBase doit recevoir six paramtres an dinitialiser ces trois couples dattributs. Tracer les constructeurs avec un message indicatif sur la console. 2. Le prsident (classe drive) dune socit doit faire un discours lassemble, mais comme tous les autres socitaires (classe de base) il doit aussi faire un rapport. crire ces deux classes avec une mthode parle() qui va reter ces deux parties. Les textes des discours et rapports sont passs dans les constructeurs. En C++, nous crirons un chier den-tte spar. 3. Pour viter le transtypage, reprendre la classe Dessin2 et dnir la mthode dessine Texte() non abstraite dans la classe de base Forme. Inclure toutes les classes dans un chier commun Dessin3.java.

13
Des hritages multiples
Hritage multiple en C++
Nous ne donnerons ici quun aperu rapide et un exemple simple. Nous pensons pouvoir dconseiller, dune manire gnrale, lemploi de lhritage multiple en C++, car il se rvle beaucoup trop complexe et difcile matriser. Tout au long de cet ouvrage, nous avons demand au programmeur de procder une criture simple de son code. Lutilisation de lhritage multiple ne va pas dans ce sens. Pour la prsentation de lhritage multiple, nous allons adopter une dmarche tout fait particulire. Pour la premire et dernire fois dans cet ouvrage, nous allons utiliser une notation abstraite. Jusqu prsent, nous avions toujours travaill avec des objets ou des sujets bien rels et concrets, comme des personnes ou des formes dessiner. Dans la plupart des ouvrages thoriques ou de rfrence pour les compilateurs, et dans les articles spcialiss, nous rencontrons ce type de notation totalement abstraite, auquel il faut aussi se familiariser et shabituer : cest ici ce que nous avons choisi. Nous avons cependant gard quelques objets de la classe string, an que cela ne devienne pas illisible ni, surtout, impossible compiler sans la dclaration complte de tous les objets utiliss dans le code ! Soit deux classes A et B dnies comme suit, ainsi que la classe C qui hrite de ces deux classes :
// multiherit.cpp #include <iostream> #include <string> using namespace std; class A {

280

Apprendre Java et C++ en parallle

private: string attributA; public: A(string unAttribut) { attributA = unAttribut; } string getAttribut() { return attributA; } virtual string getInfo() { return "classe A"; } };

class B { private: string attributB; public: B(string unAttribut) { attributB = unAttribut; } string getAttributB() { return attributB; } virtual string getInfo() { return "classe B"; } };

class C: public A, public B { private: string attributC; public: C(string unAttribut1, string unAttribut2, string unAttribut3); string getAttribut(); virtual string getInfo(); };

Des hritages multiples CHAPITRE 13

281

C::C(string unAttribut1, string unAttribut2, string unAttribut3) :A(unAttribut1), B(unAttribut2) { attributC = unAttribut3; } string C::getAttribut() { return attributC + " " + A::getAttribut() + " " + getAttributB(); }

string C::getInfo() { return "classe C hrite de " + A::getInfo() + " et de " + B::getInfo() + " (" + attributC + " " + A::getAttribut() + " " + getAttributB() + ")"; }

int main() { C objet1("A1", "B1", "C1"); cout << objet1.getAttribut() << endl; cout << objet1.getInfo() << endl; A *objet2 = new C("A2", "B2", "C2"); cout << objet2->getAttribut() << endl; cout << objet2->getInfo() << endl; delete objet2; }

// objet de la classe C

// objet de la classe A

Les classes A et B nont rien de particulier au niveau de leurs constructeurs et de leurs attributs. Chaque objet de ces deux classes aura un attribut string initialis par le constructeur, attributA pour la classe A et attributB pour la classe B. Nous avons dni volontairement trois mthodes publiques diffrentes :
string getAttribut() string getAttributB() virtual string getInfo() // classe A seulement // classe B seulement // classes A et B

La classe C possde une structure identique aux classes A et B avec son attribut attributC, mais, en plus, hrite des deux classes A et B. Il faut noter la forme du constructeur de la classe C et la manire dinitialiser les classes de base :
C::C(string unAttribut1, string unAttribut2, string unAttribut3) :A(unAttribut1), B(unAttribut2) { attributC = unAttribut3; }

Une construction telle que :


C::C(string unAttribut1, string unAttribut2, string unAttribut3) :A(unAttribut1), B(unAttribut2), attributC(unAttribut3) {}

aurait aussi t accepte.

282

Apprendre Java et C++ en parallle

La mthode getAttributB() de la classe B est unique et nentrane pas de confusion. Tout objet instanci de la classe C pourra donc lutiliser sans difcult. Pour getAttribut(), qui nest pas virtuelle et qui existe dans la classe C et la classe A, cest beaucoup moins clair. Le rsultat prsent, provenant du code inclus dans le main() :
C1 A1 B1 classe C hrite de classe A et de classe B (C1 A1 B1) A2 classe C hrite de classe A et de classe B (C2 A2 B2)

correspond-t-il notre attente ? Nous navons pas essay de drouter lattention du lecteur avec le new pour lobjet2, mais nous avons simplement voulu montrer une autre construction. Le rsultat A2 est bien correct. Nous avons un getAttribut() sur la classe A, alors que getInfo() est virtuel et sapplique bien sur la mthode de la classe C. Comme nous lavons vu au chapitre prcdent, dans lequel nous avons analys en dtail les aspects de transtypage pour lhritage simple, nous avons les mmes difcults, avec ici encore plus dambigut. Nous laisserons donc le lecteur juger par lui-mme de lutilisation possible de lhritage multiple. Nous conseillerons, dans tous les cas, dtre trs prudent lors de la dnition du nom des mthodes, comme ici notre getAttribut().

Hritage multiple en Java


Dans le langage C++, une classe peut hriter de plusieurs superclasses, alors quen Java ceci nest pas possible. Une des raisons de cette restriction est la complexit de limplmentation pour les compilateurs Java. La notion dinterface a t introduite en Java et elle permet de rcuprer la plupart des fonctionnalits de lhritage multiple. Comme son nom lindique, lhritage multiple permet une classe de reter le comportement de deux ou de plusieurs parents. Nous nous souviendrons, au chapitre 12 prcdent, de la prsentation de la classe java.lang.Integer, qui tait dnie ainsi :
public final class Integer extends Number implements Comparable

ce moment-l, nous avions laiss la partie implements Comparable, dont nous allons maintenant claircir le fonctionnement. Dune manire gnrale, nous pouvons dire que la classe Integer hrite la fois de la classe Number et de linterface Comparable. Nous rencontrons ici un nouveau terme, car la forme :
public final class Integer extends Number, Comparable

nest pas possible en Java pour deux raisons : 1. Lhritage multiple de plusieurs classes nexiste pas (mais possible en C++). 2. Comparable nest pas une classe, mais une interface. Cette dcision dans la conception de Java est en fait une simplication apporte au langage Java, et notre avis cela passe trs bien. Nous rpterons, encore une fois, quil

Des hritages multiples CHAPITRE 13

283

est tout fait possible de dnir des recommandations pour viter ou mme pour ne jamais utiliser lhritage multiple en C++, hritage qui rend le code trop complexe. Malheureusement, le langage C++ na pas dinterface, bien quil possde dautres atouts trs solides, comme les incontournables surcharges doprateurs.

Dnition dune interface en Java


Nous pouvons dnir une interface comme une classe totalement abstraite et sans code. Le terme ou mot-cl implements, dans la dnition de la classe qui va utiliser cette interface, nous indique que le code de toutes les mthodes dnies dans cette interface doit faire partie de cette classe, comme dans notre exemple ci-dessus avec la classe Integer et linterface Comparable. La classe Integer hrite de Number. Ainsi, toutes les mthodes publiques disponibles dans cette classe de base sont aussi utilisables, comme byteValue() ou shortValue(). Cependant, nous hritons aussi, cette fois-ci par une interface de Comparable, de la mthode compare To(Object o), la seule mthode dnie ! Cest un drle dhritage, dans le sens que compareTo() doit tre absolument cod. Nous dirons ici que nous avons affaire un hritage conditionnel ; nous devrons en effet satisfaire les conditions dnies par linterface, cest--dire crire le code. Cette technique est utilise pour forcer une implmentation et pour la rutiliser dans dautres cas. Si le lecteur a le courage, il peut samuser consulter le code source de la classe Integer, qui fait partie du matriel distribu par Sun Microsystems. Le chier src.jar contient le code source de toutes les classes de cette distribution. Il est possible, avec 7-Zip par exemple (logiciel Open Source sous Windows, voir annexe B), dextraire le chier src/ java/lang/Integer.java, dans lequel nous pourrons dcouvrir ce code indispensable puisque provenant dune interface :
public int compareTo(Object o) { return compareTo((Integer)o); }

Ce code nous indique quune autre mthode publique de la classe Integer sera appele :
public int compareTo(Integer anotherInteger) { int thisVal = this.value; int anotherVal = anotherInteger.value; return (thisVal<anotherVal ? -1 : (thisVal==anotherVal ? 0 : 1)); }

pour implmenter le code de comparaison de nos objets. Seul un objet de la classe Integer sera accept, et la documentation, partie intgrante de ce code, nous indique bien lexception ClassCastException qui pourrait tre gnre si nous essayions de comparer une instance dInteger avec quelque chose dautre. La double forme ?: nous permet ici de combiner deux sries imbriques dinstructions de condition if et else.

284

Apprendre Java et C++ en parallle

Jai dj hrit, que faire avec mon Thread ?


Cest une manire diffrente de poser le problme. Lorsque nous avons conu un ensemble de classes dans une hirarchie, il peut arriver que nous voulions hriter des fonctionnalits dune autre classe. Pour illustrer ce problme, nous allons proter de loccasion pour prsenter la classe Thread et son quivalent, dni comme interface, Runnable. La classe Thread permet dintroduire un mcanisme pour que plusieurs tches puissent sexcuter en mme temps. Chaque opration va donc fonctionner en parallle et indpendamment des autres. Notre premire classe, MonThread1, va hriter de cette fameuse classe Thread.
public class MonThread1 extends Thread { public MonThread1(String nom) { super(nom); } public void run() { System.out.println("Commence pour " + getName()); try { sleep(1000); } catch(InterruptedException ie) { } System.out.println("Termine pour " + getName()); } public static void main (String[] args) { new MonThread1("Processus 1").start(); System.out.println("Test 1"); new MonThread1("Processus 2").start(); System.out.println("Test 2"); } }

En excutant ce programme, nous obtiendrons ce rsultat :


Test 1 Commence pour Processus 1 Test 2 Commence pour Processus 2 Termine pour Processus 1 Termine pour Processus 2

Suivant le systme dexploitation et la machine virtuelle dans laquelle le programme est excut, il est tout fait possible davoir Test 2 juste aprs Test 1. Nous lavons constat sur une station Sparc de Sun Microsystems sous Solaris. La mthode run() est en fait dj dnie dans la classe de base Thread et est ici surcharge, an dexcuter le code dsir. Le fait de ne pas avoir associ le rsultat de new Mon Thread1() une variable nest pas interdit, car ici aucune autre mthode que start() ne sera associe plus loin linstance cre. La mthode start() va lancer un processus parallle et indpendant du processus courant. Directement lexcution de start(), le

Des hritages multiples CHAPITRE 13

285

point dentre run() de cette mme classe va tre excut pour attendre 1 000 millisecondes avant dimprimer le message "Termine". Nous pouvons maintenant comprendre pourquoi le "Commence" sur le deuxime processus parallle vient avant la terminaison du premier processus. La partie main() sera donc termine bien avant la terminaison des deux autres processus dans le code de la mthode run(). La mthode sleep() est statique. Si nous voulons lutiliser dautres occasions, il faudra choisir cette forme :
try { Thread.sleep(2000); // pause de 2 secondes } catch(InterruptedException ie) { }

car ici, notre classe hrite de Thread, ce qui explique lutilisation de sleep() sans sa partie gauche.

Une interface au lieu dun hritage classique


Pour la classe MonThread2, nous allons utiliser linterface Runnable, qui est similaire la classe Thread :
public class MonThread2 implements Runnable { public void depart() { Thread t1 = new Thread(this, "Processus 1"); t1.start(); System.out.println("Test 1"); Thread t2 = new Thread(this, "Processus 2"); t2.start(); System.out.println("Test 2"); } public void run() { System.out.println("Commence pour " + Thread.currentThread().getName()); try { Thread.sleep(1000); } catch(InterruptedException ie) { } System.out.println("Termine pour " + Thread.currentThread().getName()); } public static void main (String[] args) { MonThread2 mt = new MonThread2(); mt.depart(); } }

Lavantage de cette technique est que nous pourrions demander MonThread2 dhriter dune autre classe dont nous voudrions rutiliser limplmentation. Si la classe MonThread2 a

286

Apprendre Java et C++ en parallle

dj hrit dune classe et que nous voulions hriter dune troisime classe, notre choix serait encore une fois une interface. Il est en effet possible dhriter de plusieurs interfaces en les sparant avec des virgules (voir la classe Clown1 en n de chapitre). Nous retrouvons nouveau la mthode run(). La diffrence avec la classe prcdente est que la mthode run() nest pas surcharge, mais doit tre implmente et code. Mais, attention, il ne suft pas de remplacer extends Thread par implements Runnable. Dans le constructeur de MonThread1, le super() permettait de dnir le nom du Thread qui serait rcupr par le getName() de cette mme classe. Dans cette version, il sera ncessaire dobtenir le nom du Thread au travers de la mthode statique Thread.currentThread() sur le processus courant actif. Lorigine du problme vient de la ncessit de crer une mthode dpart() sur un objet de notre classe, mthode qui va instancier deux Threads. Il est en effet impossible de crer un objet pour une interface. Le new Thread(), avec deux paramtres, permet de crer un processus parallle sur celui actif, cest--dire this, et de lui donner un nom qui pourra tre rcupr avec getName(). Enn, nous constaterons que le rsultat est identique au prcdent.

Des constantes dans une interface Java


Dans une interface, il est possible de dnir des constantes, au contraire des mthodes, dont nous ne pouvons dnir que leurs dclarations. Dans cette dnition dinterface, que nous retrouverons dans le chier MaConstante.java :
public interface MaConstante { public int ANNEE = 1970; }

le nom MaConstante.ANNEE est une constante. Nous noterons le style Java, qui utilise des majuscules pour ces types de constantes. Pour le vrier, cette classe :
class UneClasse implements MaConstante { public void test() { System.out.println(ANNEE); } } public class UnTest { public static void main(String[] args) { System.out.println(MaConstante.ANNEE); UneClasse uc = new UneClasse(); uc.test(); } }

nous montre la manire daccder la constante ANNEE de linterface MaConstante. La classe UneClasse nous permet daccder directement ANNEE, car elle implmente linterface MaConstante, qui, il faut le remarquer, ne possde aucune dclaration de mthodes,

Des hritages multiples CHAPITRE 13

287

mais seulement une constante. Il nest pas ncessaire de dclarer ANNEE comme final, car cest implicitement le cas.

Grouper des constantes dans une interface


Dune manire similaire aux numrations en C++, que nous avons tudies au chapitre 2 au travers du mot-cl enum, nous pouvons grouper des listes de constantes dans une interface :
public interface Jours { public int LUNDI = 1, MARDI = 2, MERCREDI = 3, JEUDI = 4, VENDREDI = 5, SAMEDI = 6, DIMANCHE = 7; }

Il est alors possible de crer un package avec ces constantes et de les importer lors de leurs utilisations. Le JDK 1.5 a enn introduit les numrations (enum) dont nous avons parl au chapitre 6.

Srialisation et clonage dobjets en Java


Ayant prsent assimil le concept dinterface en Java, nous pouvons passer rapidement deux fonctionnalits essentielles du langage Java, la srialisation et le clonage. Ces deux aspects nexistent pas directement en C++, bien que le constructeur de copie en C++ reprsente en fait un clonage.

Srialiser des objets Java


La srialisation est un mcanisme qui permet, entre autres, dcrire un objet complet sur le disque. Comme nous travaillons avec des ux, nous pourrions utiliser la srialisation dautres ns de communication que simplement un chier. Cest un sujet important, mais qui dpasse les limites que nous nous sommes xes pour cet ouvrage. La description rapide de cette technologie et un exemple concret devraient sufre. La srialisation dobjets peut permettre des instances de classes de persister, mme aprs que le programme est termin. Ils pourront ensuite tre rechargs, sans avoir besoin dune base de donnes qui assure cette persistance. Il y a deux aspects importants qui font partie de la technologie Java, ce sont le RMI et les JavaBeans. Le RMI (Remote Method Invocation), similaire aux objets CORBA, permet de distribuer des objets sur dautres machines. La srialisation permet de transporter le contenu et ltat dobjets sur une autre machine. Les Beans, des composants Java suivant des rgles strictes pour leurs dnitions et constructions, peuvent rcuprer leurs tats lorsque lapplication est relance. Dans lexercice qui suit, nous allons crire une classe de test qui va nous enregistrer une instance dun objet Personne dans un chier et la relire directement. Nous reprendrons notre fameux capitaine Haddock, que nous pourrions stocker, en C++, dans un chier dlimit tel que :
"Haddock", "Capitaine", "1907"

288

Apprendre Java et C++ en parallle

Ici, nous nallons ni lire de chier dlimit ni en crire un pour une exportation possible en C++. Nous comprendrons rapidement que le chier srialis en Java ne pourra pas tre lu en C++, car il nous faudrait connatre le format binaire gnr par Java. Dans lexemple qui suit, nous allons srialiser un objet de la classe PersonneSerial, aprs avoir dni ces attributs :
import java.io.*; public class PersonneSerial implements Serializable { private String nom; private String prenom; private int annee; private static final long serialVersionUID = 6526472292623776149L; public PersonneSerial(String lenom, String leprenom, String lannee) { nom = lenom; prenom = leprenom; annee = Integer.parseInt(lannee); } public void unTest() { System.out.print("Nom et prnom: " + nom + " " + prenom); System.out.println(" : " + annee); } public static void main(String[] args) { PersonneSerial nom = new PersonneSerial("Haddock", "Capitaine", "1907"); nom.unTest(); try { ObjectOutputStream out = new ObjectOutputStream (new FileOutputStream("haddock.dat")); out.writeObject(nom); out.close(); ObjectInputStream in = new ObjectInputStream(new FileInputStream("haddock.dat")); PersonneSerial nomlu = (PersonneSerial)in.readObject(); in.close(); nomlu.unTest(); } catch(Exception e) { System.out.println("L'objet de la classe Personne ne peut tre sauv"); System.out.println(" ou rcupr du fichier haddock.dat"); return; } } }

Des hritages multiples CHAPITRE 13

289

Nous avons afrm prcdemment quune interface ne possdait pas de code et que la nouvelle classe devait limplmenter. Sans entrer dans les dtails, nous dirons simplement que tous les attributs de la classe PersonneSerial doivent tre srialisables. Pour la variable annee, qui est un int dont le compilateur connat la dimension, il ny aura pas de difcult pour lcriture des octets correspondant la valeur de ce type de variable. Pour les deux Strings nom et prenom, nous pouvons nous imaginer le mcanisme lorsque nous consultons lAPI de la classe String :
public final class String extends Object implements Serializable, Comparable

String est bien un objet srialisable. Pour srialiser notre classe PersonneSerial, nous utilisons la classe ObjectOutputStream et sa mthode writeObject(). Lopration inverse est garantie avec la classe ObjectInputStream et sa mthode readObject(). serialVersionUID est un code pour identier la version de la classe. Nous lavons introduit

ici uniquement pour liminer un message dalerte lors de la compilation. Pour plus de dtails, consultons la documentation de Java an de gnrer cette valeur correctement. Si nous excutons le programme, nous obtiendrons :
Nom et prenom: Haddock Capitaine : 1907 Nom et prenom: Haddock Capitaine : 1907

Durant ce transfert, les donnes ont t correctement sauvegardes et recharges. titre dinformation, nous pouvons examiner le contenu du chier binaire haddock.dat avec loutil Linux od (voir par exemple lannexe B, section Les outils Linux de MSYS ) :
od -bc haddock.dat

Et le rsultat prsent :

Figure 13-1

Od bc de haddock.dat

Nous comprenons que nous aurions eu quelques difcults lire ce chier en C++.

290

Apprendre Java et C++ en parallle

Pour terminer, nous devons indiquer quil y a des situations o il serait ncessaire de contrler soi-mme la srialisation. Il y a diffrentes alternatives, comme celle de rednir les mthodes writeObject() et readObject().

Le clonage dobjet
Nous revenons prsent rapidement sur ce sujet, qui a dj t trait au chapitre 11. Ce fut ce moment-l un passage oblig, car nous avions fait un paralllisme avec loprateur = en C++. Nous avions alors rencontr un :
public class Clown implements Cloneable { };

Nous navions aussi aucune mthode dinterface dnir, car la mthode clone() de la classe Object faisait tout le travail de copie bit bit. Ce mcanisme est identique la srialisation que nous avons vue ci-dessus, dans laquelle tous les objets (int et String) taient srialisables. Il nous faut donc montrer prsent comment rednir la mthode clone(). Nous reprenons le code du chapitre 11 et le modions :
public class Clown1 implements Cloneable { private String nom; private String prenom; private int annee; public Clown1(String lenom, String leprenom, String lannee) { nom = lenom; prenom = leprenom; annee = Integer.parseInt(lannee); } public void setAnnee(int lannee) { annee = lannee; } public void unTest() { System.out.print("Nom et prnom: " + nom + " " + prenom); System.out.println(" : " + annee); } public Object clone() { Clown1 co = null; try { co = (Clown1)super.clone(); co.annee = -1; } catch (CloneNotSupportedException e) {} return co;

Des hritages multiples CHAPITRE 13

291

} public static void main(String[] args) { Clown1 nom1 = new Clown1("Haddock", "Capitaine", "1907"); Clown1 nom2; Clown1 nom3; nom2 = nom1; nom3 = (Clown1)nom1.clone(); if (!nom3.equals(nom1)) { System.out.println("Object nom1 et nom3 sont diffrents"); } nom2.setAnnee(1927); nom1.unTest(); nom2.unTest(); nom3.unTest(); if (nom2.equals(nom1)) { System.out.println("Object nom1 et nom2 sont gaux"); } } }

Il y a ici quelques variantes noter, en particulier la disparition de la squence try et catch(), qui a t dplace dans la rednition de mthode clone(). Nous avons aussi modi la copie des objets en donnant lattribut annee la valeur de 1 aprs le clonage. Nous avons bien le rsultat attendu :
Object Nom et Nom et Nom et Object nom1 et prnom: prnom: prnom: nom1 et nom3 sont diffrents Haddock Capitaine : 1927 Haddock Capitaine : 1927 Haddock Capitaine : -1 nom2 sont gaux

Pour nir, il nous faut revenir au clonage et la srialisation. En effet, une analyse plus profonde nous montrerait que ces deux domaines devraient tre considrs en parallle, ceci an dobtenir une implmentation correcte. Il faudra toujours se mer des objets qui possdent des rfrences communes dautres objets.

Rsum
Aussi bien le langage C++ que Java supportent lhritage multiple, mais sous une forme diffrente. En C++, cela se fait dune manire attendue, o nous dclarons plusieurs

292

Apprendre Java et C++ en parallle

hritages lors de la dnition de la classe. Comme cette manire de faire se rvle trop complexe, principalement pour les compilateurs, le langage Java utilise le concept dinterface. Cette dernire est en fait similaire une classe abstraite, dans laquelle le code doit tre entirement implment dans la classe qui hrite dune ou de plusieurs interfaces.

Exercices
1. Essayer de crer une classe SalaireFrancais en Java, similaire la classe Integer, cest--dire qui hrite de cette dernire et qui implmente linterface Comparable. La classe SalaireFrancais conservera le montant du salaire en euros et le taux de change pour le franc franais. La mthode compareTo(Object o) de linterface Comparable effectuera la comparaison en franc franais. 2. Transformer les classes Forme, Cercle, Rectangle et Dessin du chapitre 12 an dutiliser une interface. Forme devra hriter de la classe java.awt.Point et dune interface GraphiqueObjet qui dnira deux mthodes dessine() et efface(). La classe Dessin vriera les diffrentes mthodes.

14
Devenir collectionneur
Collectionner des objets nest certainement pas lune des proccupations majeures des programmeurs dbutants. Au dpart, ces derniers se contentent de tableaux dentiers ou de chanes de caractres. Ces tableaux sont souvent xes et cods statiquement dans le programme. Mais arrive un jour o ces programmeurs amateurs deviennent des professionnels et se rendent compte de la ncessit de sauvegarder ces listes sur un support magntique. Ensuite, ils vont vouloir effacer ou ajouter des composants. Cest ce momentl que les difcults apparaissent. Lorsquune liste demploys dune entreprise doit tre conserve, nous commenons par crer un tableau dobjets en Java ou C++. Lorsque nous effaons un employ pour le remplacer ventuellement par un nouveau venu, nous devons tre capables de manipuler ces listes, de rechercher et de remplacer un objet aprs avoir dsactiv et recr les ressources. Cest ici que nos conteneurs, des collections dobjets, vont prendre toute leur signication. Un employ a peut-tre des entres dans plusieurs tables, qui sont elles-mmes lies entre elles avec des index ou des cls. Nous entrons ici dans le domaine des bases de donnes, dans lequel il est tout fait vraisemblable et raisonnable de considrer et dutiliser les conteneurs disposition dans les bibliothques Java et C++, comme support logiciel pour une interface avec des bases de donnes traditionnelles.

Note Ce chapitre est une introduction aux collections et autres algorithmes. Le lecteur devra consulter la documentation des langages Java et C++ sur ce vaste sujet, qui pourrait tre couvert dans un ouvrage spcialis. Le but de ce livre est de donner sufsamment dexemples pour se familiariser avec ces nombreux algorithmes et classes. Il y a ainsi plus de 80 algorithmes dans le Standard C++ !

294

Apprendre Java et C++ en parallle

Le vector en C++
Le vector fait partie de la bibliothque STL (Standard Template Library) et se trouve tre le conteneur le plus simple, par lequel nous commencerons notre prsentation. Pour illustrer lutilisation de cette classe, nous allons prendre lexemple de deux listes, une liste dentiers (int) et une liste de chanes de caractres (string). Nous nallons pas prendre peur la vue de la syntaxe des templates (modles), que nous verrons plus loin, au chapitre 19. Nous dirons simplement que la classe vector est capable de collectionner diffrents types dobjets et quil nest pas ncessaire de crer une classe pour chaque type, comme vectorInt et vectorString. La mthode replace() nest pas simple et il faudra consulter la documentation, par exemple http://wwwinfo.cern.ch/asd/lhc++/RW/stdlibcr/bas_0007.htm. Elle permet de remplacer un certain nombre de caractres en fonction de critres dnis par ses diffrents paramtres. La mthode replace() du Standard C++ devrait tre vrie sparment suivant les cas dutilisation. De plus, cette mthode nest pas trs solide. Si des paramtres sont incorrects ou inconsistants, nous pourrions nous retrouver avec des rsultats surprenants. Le dernier paramtre est le plus intressant : '0' + i. '0' nous retourne ici la valeur du caractre ASCII du chiffre 0. Si i = 2, nous avons bien un 2, cest--dire la valeur binaire ASCII du chiffre 2. Cette construction est possible, car les chiffres de 0 9 dans la table des caractres ASCII sont contigs. Voici donc notre premier exemple :
// testvector.cpp #include <iostream> #include <string> #include <vector> using namespace std; int main() { vector<int> v_entier; vector<string> v_chaine; char *un_char = "0"; string nombre = "nombreX"; for (int i = 0; i < 5; i++) { // remplissage v_entier.insert(v_entier.end(), i); nombre.replace(6, 1, 1, '0' + i); v_chaine.insert(v_chaine.begin(), nombre); } for (int j = 0; j < v_entier.size(); j++) { cout << (string)v_chaine[j] << " "; cout << v_entier[j] << endl; } vector<int>::iterator it1; vector<string>::iterator it2; it2 = v_chaine.end() - 1;

// liste d'int // liste de string

Devenir collectionneur CHAPITRE 14

295

for (it1 = v_entier.begin(); it1 != v_entier.end(); it1++) { cout << *it2-- << " " << *it1 << endl; } vector<string>::iterator it_debut = v_chaine.begin(); vector<string>::iterator it_fin = v_chaine.end(); while (it_debut != it_fin) { if (*it_debut == "nombre3") v_chaine.erase(it_debut); it_debut++; } it_debut = v_chaine.begin(); it_fin = v_chaine.end() - 1; while (it_fin >= it_debut) { cout << *it_fin-- << endl; } cout << "Dimension du vector v_entier: " << v_entier.size() << endl; cout << "Dimension du vector v_chaine: " << v_chaine.size() << endl; }

Avec les dclarations de v_entier et de v_chaine comme vector<int> et vector<string>, nous rencontrons cette nouvelle forme, avec les <>. Nous aurons donc deux vector qui seront dnis pour maintenir deux collections distinctes dentiers et de string. Aprs leurs dclarations, nos deux vector sont vides. Ils seront ensuite remplis avec la mthode insert(). Le vector v_entier contiendra une liste de nombres de 0 4. Nous ajoutons chaque fois le nouveau en n de liste, avec le paramtre de positionnement v_entier.end(). Pour le vector v_chaine, nous faisons linverse, avec linsertion en dbut de liste. v_entier.end() et v_chaine.begin() sont en fait des itrateurs, dont nous verrons lutilit ci-dessous. La deuxime boucle, for(), va nous montrer une liste combine, qui va apparatre ainsi :
nombre4 nombre3 nombre2 nombre1 nombre0 0 1 2 3 4

Pour ce faire, nous avons utilis loprateur [], qui est disposition dans la classe vector. Il permet daccder un lment de la collection avec un index. En cas derreur dindex, il ny a pas dexception gnre. Il faut noter que le code du programme nest correct que si les deux listes possdent le mme nombre dlments.

Utiliser un itrateur
La bibliothque STL met disposition une classe iterator pour notre classe vector. Ces types ditrateurs sont surtout intressants parce quils nous permettent de travailler

296

Apprendre Java et C++ en parallle

indpendamment du type de conteneur. Ici, cest un vector, mais nous pourrions avoir une autre collection de la bibliothque STL. Avant la boucle for() suivante :
for (it1 = v_entier.begin(); it1 != v_entier.end(); it1++) { cout << *it2-- << " " << *it1 << endl; }

nous avons les deux dclarations de nos itrateurs :


vector<int>::iterator it1; vector<string>::iterator it2;

Ils nous permettront de traverser les deux listes dans deux ordres diffrents, ce qui permettra de remettre nos listes correctement, puisque la deuxime a t introduite dans lautre sens :
nombre0 nombre1 nombre2 nombre3 nombre4 0 1 2 3 4

Il est essentiel de bien utiliser les pointeurs it1 et it2 et de vrier sils atteignent les limites permises. Les formes *it1 et *it2 nous retournent un pointeur sur les objets, qui peuvent tre prsents avec le cout traditionnel. Pour terminer, nous allons effacer lobjet contenant la chane de caractres nombre3 dans la deuxime liste. Ceci se fait avec la mthode erase() dans une boucle de test avec un itrateur. Nous constaterons aussi comment les boucles while() sont simples utiliser avec un itrateur. Nous pouvons dailleurs les traverser dans un sens comme dans lautre. Le dernier rsultat est attendu :
nombre0 nombre1 nombre2 nombre4 Dimension du vector v_entier: 5 Dimension du vector v_chaine: 4

Les algorithmes du langage C++


Nous associons souvent, en C++, les algorithmes et les conteneurs. Ce nest pas tout fait correct, car ces nombreux algorithmes ne sont pas des mthodes de ces conteneurs, mais sont des fonctions spares. Ceci peut se faire, car les algorithmes emploient des itrateurs, et il est donc tout fait possible de les utiliser avec des tableaux C traditionnels. Avant de passer leur fonctionnement avec des conteneurs, nous allons montrer, dans lexemple qui suit, un certain nombre de ces algorithmes qui peuvent remplacer avantageusement certaines fonctions C ou constructions traditionnelles :
// algoC.cpp #include <iostream>

Devenir collectionneur CHAPITRE 14

297

#include <algorithm> using namespace std; int main() { char pchar1[21] = "<daiKRgAcDlTewQ96Qd>"; char pchar2[21]; int table[7] = {5, 5, 5, 4, 3, 2, 1}; cout << "Test1: " << pchar1 << endl; sort(pchar1 + 1, pchar1 + 19); cout << "Test2: " << pchar1 << endl; copy(pchar1, pchar1 + 10, pchar2 + 10); copy(pchar1 + 10, pchar1 + 20, pchar2); pchar2[20] = 0; cout << "Test3: " << pchar2 << endl; sort(table, table + 7); cout << "Test4: "; for (int i = 0; i < 7; i++) { cout << table[i] << " "; } cout << endl; cout << "Test5: " << count(table, table + 7, 5) << endl; }

Ce code nous donnera le rsultat suivant :


Test1: Test2: Test3: Test4: Test5: <daiKRgAcDlTewQ96Qd> <69ADKQQRTacddegilw> acddegilw><69ADKQQRT 1 2 3 4 5 5 5 3

Dans la chane de caractres pchar1 (Test1), nous commenons par trier, avec la fonction sort(), toutes les lettres entre la deuxime et lavant-dernire position. Nous voyons avec Test2 que le tri se fait aussi sur les majuscules et autres caractres, tels quils se prsentent dans la table ASCII. Les deux paramtres sont des pointeurs. Pour le Test3, nous utilisons lalgorithme copy(), qui possde trois paramtres : le dbut et la n de la squence, ainsi que la cible. Ce sont nouveau des pointeurs. Nous avons utilis pour le Test4 une table avec sept entiers que nous trions. Le sort() reoit bien des pointeurs des entiers, mais ici litration se fera diffremment que pour

298

Apprendre Java et C++ en parallle

les caractres, pour lesquels nous avions une donne par octet. Ces pointeurs sont bien des itrateurs qui connaissent la dimension du type. Enn, le count() est un de ces nombreux algorithmes disposition dans le Standard C++ : il va nous retourner le nombre dobjets qui ont la mme valeur.

La classe vector en C++ et lalgorithme sort()


Nous passons prsent un exemple de lalgorithme sort() (triage), appliqu un objet de la classe vector :
// algovector.cpp #include <iostream> #include <vector> #include <algorithm> #include <ctime> #include <cstdlib> using namespace std; bool monAlgo(int nombre1, int nombre2) { int reste1 = nombre1 % 10; int reste2 = nombre2 % 10; if (reste1 > reste2) return true; if (reste1 == reste2) { if (nombre1 < nombre2) return true; } return false; } int main() { vector<int> v_entier; srand((unsigned)time(NULL)); for (int i = 0; i < 16; i++) { v_entier.push_back((rand() * 200)/RAND_MAX); } vector<int>::iterator it_debut = v_entier.begin(); const vector<int>::iterator it_fin = v_entier.end(); sort(v_entier.begin(), v_entier.end()); while (it_debut != it_fin) { cout << *it_debut++ << " "; } cout << endl; it_debut = v_entier.begin();

Devenir collectionneur CHAPITRE 14

299

sort(v_entier.begin(), v_entier.end(), monAlgo); while (it_debut != it_fin) { cout << *it_debut++ << " "; } cout << endl; cout << "Dimension du vector v_entier: " << v_entier.size() << endl; }

v_entier est nouveau une collection de nombres entiers. Elle contiendra des nombres alatoires entre 0 et 200. Le remplissage se fait avec la mthode push_back(), laquelle

nous reviendrons ci-dessous. Lorsque cette besogne est termine, nous dclarons nos deux itrateurs, dont un va rester constant tout au long du programme. Litrateur du dbut de la collection devra, bien videmment, tre rinitialis avant chaque nouvelle utilisation. Le premier sort() est classique, car il va nous trier nos nombres alatoires dans lordre croissant. Le deuxime sort() est trs particulier, car nous lui attribuons une fonction comme paramtre : monAlgo(). Cette fonction sera appele par lalgorithme sort() chaque opration de tri sur deux nombres. Il est donc tout fait possible de rednir totalement lalgorithme de tri pour nos besoins. Nous pourrions mme imaginer mlanger encore mieux la liste, si elle ne satisfaisait pas un certain critre de dsordre ! Ici, nous trions sur le chiffre des units en utilisant le reste de la division par 10. Le tri se fait dans lordre dcroissant, cest--dire les 9 devant. Si le chiffre des units est pareil, nous trions alors dans lordre croissant sur le nombre entier ! Le lecteur se posera la question de savoir si un tel algorithme a une utilit quelconque, et nous lui dirons que nous ne la voyons pas trs bien non plus. Il faut parfois trier les employs dune entreprise suivant des critres mixtes, comme par dpartement, par fonction, par grade, par salaire ou encore par numro de tlphone ou de scurit sociale ! Nous prsentons enn un des rsultats possibles, puisque la gnration de ces nombres se fait dune manire alatoire :
4 7 8 25 46 59 59 68 74 81 97 137 169 175 179 197 59 59 169 179 8 68 7 97 137 197 46 25 175 4 74 81 Dimension du vector v_entier: 16

Faut-il utiliser push_back() ou insert() sur les vector C++ ? Cest effectivement une question des plus intressantes, en relation avec les performances. Nous verrons, au chapitre 15, un certain nombre doutils pour mesurer nos programmes, ce qui nous permettrait dessayer certaines constructions ou alternatives. Avec lexercice 2 de ce mme chapitre 15, nous allons pouvoir mesurer les diffrences entre les push_back() ou autres insert(). Nous en prsentons ici les conclusions. La classe vector est construite dune manire linaire, continue en mmoire et rapide en insertion la n. La mthode suivante du premier exemple :
v_entier.insert(v_chaine.begin(), i);

300

Apprendre Java et C++ en parallle

est catastrophique, surtout lorsque la collection atteint une dimension respectable. Les deux mthodes :
v_entier.insert(v_chaine.end(), i); v_entier.push_back(i);

vont insrer les entiers la n de la collection, mais v_chaine.end() est un itrateur et un appel de mthode en plus. push_back() est donc, en principe, la recommandation.

La classe list en C++


Aprs avoir tudi le classique vector en C++, nous avons choisi parmi les nombreux autres conteneurs, comme array ou deque, la tout aussi classique et puissante list. De la classe vector, nous aurions d retenir au moins trois aspects : 1. Elle possde un oprateur [ ] et est contigu en mmoire. 2. Elle est trs rapide en insertion et effacement en n de liste. 3. Elle est viter pour des insertions en dbut et en milieu de liste. La classe list, en revanche, est compose de liens pour le chanage interne des objets. Comme pour la classe vector, si nous voulions slectionner trois aspects importants et signicatifs de la classe list, nous choisirions les suivants : 1. Elle ne possde pas doprateur [ ]. 2. Elle est rapide pour toutes sortes dinsertions ou deffacements. 3. Elle possde de nombreuses mthodes spciques comme le tri (sort()), linversion (reverse()) ou encore la concatnation de deux listes (merge()). Lexemple suivant nous montre quelques aspects dutilisation de la classe C++ list :
// testlist.cpp #include <iostream> #include <list> #include <algorithm> using namespace std; int main() { int tableau1[] = {1, 2, 4, 8, 16, 32, 64}; int tableau2[] = {9, 8, 7, 6, 5, 4, 3, 2, 1}; list<int> liste1(tableau1, tableau1 + 7); list<int> liste2(tableau2, tableau2 + 9); liste1.merge(liste2); liste1.remove(32); liste1.sort(); liste1.unique();

Devenir collectionneur CHAPITRE 14

301

liste1.reverse(); ostream_iterator<int> out(cout, " "); copy(liste1.begin(), liste1.end(), out); cout << endl; }

Le rsultat prsent :
64 16 9 8 7 6 5 4 3 2 1

est bien une liste inverse (reverse()), aprs avoir t trie (sort()). La liste nale est compose de la fusion (merge()) de deux listes dorigine, liste1 et liste2, que nous avons construites partir de deux tableaux dentiers. En demandant lafchage de liste1 aprs liste1.merge(liste2), on voit que la fusion des deux listes se fait de manire assez htroclite. Le merge() sert en fait fusionner des listes tries. Nous constaterons la forme du constructeur. Ce dernier utilise les pointeurs aux deux tableaux. tableau1 + 7 est effectivement positionn sur llment suivant le dernier dans la liste. Lobjet de la collection avec la valeur 32 a t effac (remove()), et nous navons gard que les lments uniques : 1, 2, 4 et 8 taient des doublons qui ont t effacs avec la mthode unique(). La mthode unique() est trs particulire, car elle ne va effacer que les lments identiques et adjacents. Si nous avions appliqu unique() avant sort(), rien ne se serait pass dans notre cas. Il faut enn revenir sur la trs jolie forme :
ostream_iterator<int> out(cout, " "); copy(liste1.begin(), liste1.end(), out);

qui nous permet dappliquer un itrateur sur notre traditionnel cout. Le deuxime paramtre dout() est intressant, car il permet dajouter un espace de formatage aprs chaque chiffre. Il y a donc un espace invisible aprs le dernier chiffre, le 1 !

Linterface List en Java


Les collections Java avant le JDK 1.5 Les exemples donns ci-dessous sont applicables aux anciennes collections, cest--dire celles existant avant lintroduction des types gnriques, lesquels sont abords la n de ce chapitre. Nous recevrons, lors de la compilation, des messages dalerte nous indiquant que des conversions devraient tre apportes. Tous ces exemples peuvent dailleurs tre convertis : nous lavons fait pour lun deux dans le second exercice.

La suite logique notre prsentation des collections est linterface List en Java. Celle-ci dnit prcisment le comportement de cette collection dobjets ordonne et squentielle. Comme ce nest pas une classe, nous ne pouvons pas linstancier, mais pouvons utiliser deux de ces implmentations, qui sont par exemple les classes Vector et ArrayList.

302

Apprendre Java et C++ en parallle

Lexercice suivant va consister gnrer vingt nombres alatoires entre 0 et 4 et les dposer dans un Vector. De cette liste, nous allons extraire dix lments entre la position 5 incluse et la position 15 exclue et les dplacer dans un ArrayList. De cette seconde liste nous ferons un tri et une inversion. Enn, nous crirons, toujours sur cette liste, un algorithme de tri qui dplace les 0 en dbut de liste ! Voici donc le code :
import java.io.*; import java.util.*; public class TestList { public static void main (String args[]) { List maListe1 = new Vector(); List maListe2 = new ArrayList(); for (int i = 0; i < 20; i++) { maListe1.add(new Integer((int)(5 * Math.random()))); } for (int i = 0; i < 20; i++) { System.out.print(maListe1.get(i) + " "); } System.out.println(""); maListe2.addAll(maListe1.subList(5,15)); maListe1.set(15, new Integer(9)); maListe1.subList(5,14).clear(); Iterator it1 = maListe1.iterator(); while (it1.hasNext()) { System.out.print(it1.next() + " "); } System.out.println(""); Collections.sort(maListe2); Collections.reverse(maListe2); Iterator it2 = maListe2.iterator(); while (it2.hasNext()) { System.out.print(it2.next() + " "); } System.out.println(""); Collections.sort(maListe2, compareNombre); it2 = maListe2.iterator(); while (it2.hasNext()) { System.out.print(it2.next() + " "); } System.out.println(""); }

Devenir collectionneur CHAPITRE 14

303

static final Comparator compareNombre = new Comparator() { public int compare(Object obj1, Object obj2) { Integer i1 = (Integer)obj1; Integer i2 = (Integer)obj2; int num1 = i1.intValue(); int num2 = i2.intValue(); if (num1 == 0) return -1; if (num2 == 0) return 1; return 0; } }; }

Ainsi que le rsultat :


4 4 4 0 1 1 4 0 2 2 3 4 0 0 3 4 2 2 3 3 3 4 3 3 1 9 3 3 3 1 1 3 0 3 0 3 4 3 0 3 3 4 2 1 3 3 0 3 0 0 1

Les classes Vector et ArrayList sont trs similaires, et il faudra consulter lAPI pour les diffrences ou crire des programmes de tests pour en savoir plus sur les performances, et ceci suivant les cas dutilisation. Vector est par exemple synchronis pour les processus parallles (threads), ce qui nest pas le cas des ArrayList. Aprs avoir mentionn que ces collections et ces outils sont dnis dans :
import java.util.*;

nous nous pencherons sur :


List maListe1 = new Vector(); List maListe2 = new ArrayList();

List est effectivement une interface, et cette construction est tout fait possible, car Vector et ArrayList sont de vraies classes. Cela nous permet de travailler de manire beaucoup

plus gnrique et au besoin dinterchanger les collections, juste en changeant deux ou trois lignes de code.
5 * Math.random() sera toujours entre 0 et 4.99999 et nous retournera bien un nombre entier entre 0 et 4. La mthode add() ajoute simplement un objet en n de liste. Lobjet ne peut tre de type primitif, car il doit hriter de la classe Object. Nous utilisons donc naturellement la classe Integer.

Le get(i) permet un accs direct et squentiel dans la liste, mais nous pouvons aussi utiliser un itrateur. Il faut dabord crer une instance de la classe Iterator sur la liste dsire. Nous ne pourrons accder un lment avec next() que sil existe, en vriant le hasNext().

304

Apprendre Java et C++ en parallle

Il est possible de spcier une liste dlments avec la mthode subList() avec lindex de dpart et de n. Les mthodes addAll() et clear() permettent dajouter [5, 15[ ou deffacer ces parties de listes [5, 14[ (premire valeur incluse, seconde exclue). Linstruction :
maListe1.set(15, new Integer(9));

est juste l pour montrer que lobjet lindex 15, qui a dj t copi dans la deuxime liste, est bien remplac dans la premire. La classe Collection possde un certain nombre de mthodes statiques similaires aux algorithmes en C++ (sort() et reverse()). Nous avons jou le jeu avec lcriture de la fonction compareNombre, qui est loin dtre vidente crire, mais qui est dune exibilit absolue. Sa seule fonctionnalit est de prendre les 0 et de les positionner en tte de liste. Les diffrentes valeurs de retour, 0, 1 et 1, indiquent le rsultat de la comparaison de deux objets. Dans ce type de collections, nous avons continuellement du transtypage :
public int compare(Object obj1, Object obj2) { Integer i1 = (Integer)obj1; Integer i2 = (Integer)obj2;

Nous devons connatre le type dobjet, ici Integer, que nous avons dpos dans la collection. Si nous insrons un objet dun mauvais type, nous ne le verrons que lors de lexcution, avec la gnration dune exception. En n de chapitre, nous parlerons des types gnriques qui sont apparus partir du JDK 1.5 pour viter ce genre de problme.

Linterface Set en Java


Au contraire de linterface List, Set ne contient pas de doublons. Si un nouvel objet est insr et quil existe dj, il ne sera pas ajout ce type de collection. Dans notre exemple, nous allons utiliser la classe HashSet, qui implmente linterface Set. Nous nentrerons pas dans les dtails de la construction de ce type de table (hash table), et nous dirons simplement quelle garantit une distribution constante des performances suivant que nous ajoutons, effaons ou recherchons des lments dans cette collection. Voici donc le code dont nous allons prsenter tout de suite la structure et le fonctionnement :
import java.io.*; import java.util.*; public class TestSet { public static void filtre(Collection uneCollect) { Integer objet; int nombre; Iterator it1 = uneCollect.iterator(); while (it1.hasNext()) { objet = (Integer)it1.next(); nombre = objet.intValue();

Devenir collectionneur CHAPITRE 14

305

if ((nombre % 2) == 1) { it1.remove(); } } System.out.println(""); }

public static void viewList(Collection uneCollect) { System.out.println("Dimension: " + uneCollect.size()); Iterator it1 = uneCollect.iterator(); while (it1.hasNext()) { System.out.print(it1.next() + " "); } System.out.println(""); }

public static void main (String args[]) { Set maListe1 = new HashSet(); for (int i = 0; i < 20; i++) { maListe1.add(new Integer((int)(20 * Math.random()))); } viewList(maListe1); filtre(maListe1); viewList(maListe1); } }

Le premier travail de cet exemple a t dcrire une fonction static pour nous prsenter le rsultat : viewList(). Cette mthode est totalement gnrique et pourrait fonctionner pour nimporte quelle collection, car elle utilise simplement un itrateur sur une Collection. Cette dernire se trouve tre la racine (root) de toutes les collections en Java. Dans view List(), nous avons ajout linformation sur la taille avec size(), pour montrer que notre boucle de vingt rptitions ne va pas allouer vingt nombres. Comme les nombres sont choisis au hasard entre 0 et 19 et que la collection Set est trie avec des lments uniques, nous aurons en moyenne entre douze et quinze objets, bien que la probabilit den avoir vingt ne soit pas nulle ! Notre mthode filtre() nest gnrique que dans ces paramtres, car nous assumons une liste dobjets de type Integer. Tous les entiers non pairs sont effacs avec la mthode, nouveau gnrique, remove(). Nous nous trouverons nalement avec une liste de dix nombres pairs au maximum, entre 0 et 18, comme prsent ici en excutant la classe TestSet :
Dimension: 14 19 17 16 15 14 13 12 11 10 9 6 5 4 3

306

Apprendre Java et C++ en parallle

Dimension: 6 16 14 12 10 6 4

Une liste de tlphone en Java avec HashMap


Dans la hirarchie des classes utilitaires (java.util.*), HashMap hrite dune classe qui porte bien son nom, un Dictionary. Si nous devions crer une liste de tlphone, ce serait vraisemblablement une approche convenable, cest--dire un dictionnaire de noms de personnes associes chacune un numro de tlphone. Dans lexemple qui suit, nous allons rencontrer deux termes, une cl (key) et une valeur, et ceci respectivement pour la personne et son numro de tlphone. Nous nallons pas tergiverser sur les dtails de cette classe, mais nous dirons que HashMap possde un mcanisme pour obtenir un code (le hash code ) qui est une valeur numrique calcule partir des caractres dun String. Cette cl permet de dnir les entres dans la table, qui obtient ainsi des valeurs daccs raisonnables pour toutes les sortes de fonctions comme linsertion, leffacement et la recherche. Le choix de HashMap est gnralement justi au dtriment dautres tables comme la TreeMap, qui est base sur un autre algorithme : le Red-Black tree. Pour ceux qui sont intresss, louvrage de Larry Nyhoof, C++ An Introduction to Data Structures, est essentiel (voir annexe G). Nous passons immdiatement limplmentation de notre liste de tlphone, dont nous allons prsenter les dtails :
import java.util.*; class TestListeTel { private Map telListe = new HashMap(); public void ajoute(String nom, String numTel) { if (telListe.put(nom, numTel) != null) { telListe.remove(nom); telListe.put(nom, numTel); } } public String getNumero(String nom) { String resultat = (String)telListe.get(nom); if (resultat == null) return "INCONNU"; return resultat; } public String getNom(String numero) { String clef; String resultat = ""; int nombre = 0; for (Iterator it = (telListe.keySet()).iterator(); it.hasNext(); ) {

Devenir collectionneur CHAPITRE 14

307

clef = (String)it.next(); if ((String)telListe.get(clef) == numero) { nombre++; resultat += clef + "\n"; } } if (nombre == 0) return "AUCUNE"; return resultat; } public String toString() { String resultat = "Liste des numros de tlphone:\n"; String clef; int i; for (Iterator it = (telListe.keySet()).iterator(); it.hasNext(); ) { clef = (String)it.next(); resultat += clef; for (i = clef.length(); i < 30; i++) { resultat += " "; } resultat += (String)telListe.get(clef) + "\n"; } return resultat; } public static void main(String[] args) { String tournesol = "Tournesol Tryphon"; String numBoichat = "098009812"; TestListeTel ttl = new TestListeTel(); ttl.ajoute("Haddock Capitaine", "099005512"); ttl.ajoute(tournesol, "099005519"); ttl.ajoute("Boichat Jean-Bernard", numBoichat); ttl.ajoute("Boichat Nicolas", numBoichat); ttl.ajoute("Haddock Capitaine", "099006612"); System.out.println(ttl); System.out.println("Le numro de " + tournesol + " est " + ttl.getNumero(tournesol)); System.out.println("Le numro " + numBoichat + " est attribu :\n" + ttl.getNom(numBoichat)); } }

La premire remarque importante est de constater quun numro ne sera pas ncessairement unique et pourrait tre le mme pour diffrents membres de la mme famille. Ensuite,

308

Apprendre Java et C++ en parallle

nous choisirons, sans autres commentaires, de remplacer le numro de tlphone si le nom existe dj. La mthode ajoute() va donc vrier si le retour de la mthode put() de la classe HashMap est null ou non, an deffacer lentre prcdente si elle existe. Le Capitaine a demand un nouveau numro, car le 099005512 tait trop proche de celui de la boucherie Sanzot ! Si la mthode put() est dune simplicit extrme, il nen va pas de mme pour la recherche des noms et des numros. Pour la mthode gnrale toString(), nous utiliserons un iterator, avec lequel nous allons extraire la cl (cest--dire le nom de la personne), grce laquelle, avec get(clef), nous pourrons obtenir la valeur du numro de tlphone. Nous ajoutons des espaces pour obtenir une prsentation convenable :
Liste des numros de tlphone: Boichat Jean-Bernard 098009812 Tournesol Tryphon 099005519 Boichat Nicolas 098009812 Haddock Capitaine 099006612

Nous avons ensuite programm deux mthodes, getNumero() et getNom(), pour obtenir le numro de tlphone dune personne et, inversement, recevoir la liste de toutes les personnes ayant un numro dtermin. La mthode get() de HashMap nous donne directement la valeur, alors que pour la mthode getNom() nous traversons toute la table avec un itrateur. Les deux rsultats, INCONNU et AUCUNE, sont vidents. Enn, nous prsenterons tout de mme la deuxime partie du rsultat :
Le numro de Tournesol Tryphon est 099005519 Le numro 098009812 est attribu : Boichat Jean-Bernard Boichat Nicolas

La mme liste de tlphone avec map en C++


Maintenir une liste de tlphone est un exercice traditionnel en programmation. Nous allons donc la reprendre une fois encore, en utilisant cette fois-ci la classe map en C++ :
// telephone.cpp #include #include #include #include <iostream> <string> <map> <iterator>

using namespace std; int main() { string tournesol = "Tournesol Tryphon"; string numBoichat = "098009812"; map<string, string> list_tel;

Devenir collectionneur CHAPITRE 14

309

list_tel[tournesol] list_tel["Boichat Jean-Bernard"] list_tel["Boichat Nicolas"] list_tel["Haddock Capitaine"] list_tel["Haddock Capitaine"]

= = = =

"099005519"; numBoichat; numBoichat; "099005512";

= "099006612";

map<string, string>::iterator it; for (it = list_tel.begin(); it != list_tel.end(); it++) { cout << (*it).first << " : " << (*it).second << endl; } cout << endl; it = list_tel.find(tournesol); if (it == list_tel.end()) { cout << "Le numro de " << tournesol << " est INCONNU" << endl; } else { cout << "Le numro de " << tournesol << " est " << (*it).second << endl; } cout << endl; int nombre = 0; string resultat = "Le numro " + numBoichat + " est attribu : \n"; for (it = list_tel.begin(); it != list_tel.end(); it++) { if ((*it).second == numBoichat) { resultat += (*it).first + "\n"; nombre++; } } if (nombre == 0) { cout << "Il n'y a personne avec le numro: " << numBoichat << endl; } else { cout << resultat; } }

Comme la classe map est un modle (template), il nous faut spcier le type de la cl et celui de sa valeur. Nous pourrions nous poser la question de lutilisation dun int ou dun long pour le numro de tlphone ! Cependant, comme celui-ci peut commencer avec un ou plusieurs 0, une reprsentation avec un string est plus simple. Lutilisation des crochets ([]), pour ajouter une paire de string dans notre dictionnaire, pourrait nous sembler trs particulire. Nous nous rappellerons que loprateur [], qui est utilis en gnral pour accder un tableau index, peut en fait tre redni en C++ pour obtenir lopration dsire. Lors de linsertion du mme nom, nous navons pas le mme comportement quen

310

Apprendre Java et C++ en parallle

Java : nous avons simplement un remplacement de lentre sans la ncessit dun effacement pralable. Comme dans lexemple prcdent en Java, nous utilisons un string, soit directement, soit au travers dune variable, pour lajouter au conteneur. Dans la ralit, cette liste de tlphone pourrait tre lue dun chier ou dune base de donnes. La mthode find() nous permet de chercher un numro dans la liste partir dun nom, alors quun itrateur doit tre utilis pour sortir la liste complte ou rechercher les noms associs au mme numro. Nous pourrions inverser cl et valeur, mais il faudrait que le numro de tlphone soit unique. Ce serait certainement applicable dans dautres situations, bien quici nous puissions aussi retrouver le mme nom et prnom ! crire ce petit programme avec une classe amliorerait sans aucun doute la prsentation, comme cest le cas dans lexemple en Java. Cest ce que nous ferons comme exercice. Il est inutile de prsenter le rsultat de ce programme, qui est le mme que prcdemment, la diffrence que les numros de tlphone ne sont pas aligns la colonne 30.

Les types gnriques en Java


Dans ce chapitre, nous avons couvert jusqu prsent les collections traditionnelles de Java, avant quapparaissent, partir du JDK 1.5, les types gnriques. Ces derniers sont assez similaires aux templates en C++ que nous traitons sparment au chapitre 19.

Un premier exemple simple


Nous allons crer une classe Position an de conserver deux points dans un espace deux dimensions :
// Position x,y dans lespace public class Position { private int x, y; public int getX() { return x; } public int getY() { return y; } public Position(int x, int y){ this.x = x; this.y = y; } }

Cest une classe traditionnelle avec deux mthodes pour retourner la position. Cette classe se compile sans problme.

Devenir collectionneur CHAPITRE 14

311

Cette premire version, compatible avec des JDK antrieurs la version 1.5, va nous permettre de collectionner un certain nombre de points dans notre espace :
import java.util.*; public class Generics1 { public static void main(String[] args) { /** *Avant le JDK 1.5 */ ArrayList liste = new ArrayList(); liste.add(new Position(5, 80)); liste.add(new Position(12, 120)); liste.add(new Position(43, 251)); Iterator it = liste.iterator(); while(it.hasNext()){ Position pos = (Position)it.next(); System.out.println("X=" + pos.getX() + " Y=" + pos.getY()); } } }

Nous connaissons dj les dtails avec litrateur et le problme de transtypage : (Position)it.next();.

Si nous compilons ce programme avec le JDK 1.6, sans le paramtre Xlint intgr Crimson, nous recevrons ceci :
Note: Generics1.java uses unchecked or unsafe operations. Note: Recompile with -Xlint:unchecked for details.

Avec le Xlint, nous en saurons un peu plus :


Generics1.java:10: warning: [unchecked] unchecked call to add(E) as a member of the raw type java.util.ArrayList liste.add(new Position(5, 80)); ^ Generics1.java:11: warning: [unchecked] unchecked call to add(E) as a member of the raw type java.util.ArrayList liste.add(new Position(12, 120)); ^ Generics1.java:12: warning: [unchecked] unchecked call to add(E) as a member of the raw type java.util.ArrayList liste.add(new Position(43, 251)); ^ 3 warnings

Cependant, cela ne nous empche pas dexcuter le programme, car il a tout de mme t compil. Voici le rsultat attendu de lextraction de nos trois positions dans lespace :

312

Apprendre Java et C++ en parallle

X=5 Y=80 X=12 Y=120 X=43 Y=251

Nous pourrons faire maintenant beaucoup mieux avec le JDK 1.5 qui intgre les types gnriques :
import java.util.*; public class Generics2 { public static void main(String[] args) { /** *Depuis le JDK 1.5 */ ArrayList<Position> liste = new ArrayList<Position>(); liste.add(new Position(5, 80)); liste.add(new Position(12, 120)); liste.add(new Position(43, 251)); for (Position pos : liste) { System.out.println("X=" + pos.getX() + " Y=" + pos.getY()); } } }

Nous aimerions dire : Mon dieu, comme cest simple et propre ! . Le rsultat est videmment le mme que prcdemment, mais sans message dalerte du compilateur.
ArrayList est maintenant associ un type de classe, ici Position, et avec la forme :
ArrayList<Position> liste = new ArrayList<Position>();

Nous allons pouvoir dposer dans liste des objets de la classe Position sans risquer dy inclure un objet dun autre type, ce qui entranerait alors une erreur lors de lexcution. Nous avons aussi la forme :
for (Position pos : liste) {

Celle-ci est aussi nouvelle dans le JDK 1.5. Nous lavons dj dcouverte au chapitre 4. Un exemple de la nouvelle forme pour lIterator, sans transtypage, sera donn dans le second exercice de ce chapitre.

Autoboxing et Fibonacci
Nous connaissons sans doute le nombre de Fibonnaci ou lavons peut-tre rencontr dans le livre de Dan Brown, le Da Vinci Code. Nous pouvons aussi consulter larticle sur le site Web de Wikipedia (http://fr.wikipedia.org/wiki/Nombre_de_Fibonacci), le lire attentivement en comptant les couples de lapins et nous amuser programmer cette suite jusqu

Devenir collectionneur CHAPITRE 14

313

lindex 21 et avec lautoboxing, une nouvelle fonctionnalit du JDK 1.5. Nous commencerons par prsenter le code :
import java.util.*; public class AutoUnBoxing { public static void main(String[] args) { ArrayList<Long> fiboListe = new ArrayList<Long>(); long fibNum1 = 0; long fibNum2 = 1; fiboListe.add(fibNum1); // index 0 fiboListe.add(fibNum2); // index 1 long ancienFibo = 0; for(int i = 0; i < 20; i++) { ancienFibo = fibNum2; fibNum2 = fibNum1 + fibNum2; fibNum1 = ancienFibo; fiboListe.add(fibNum2); } int index = 0; for (long num : fiboListe) { System.out.println(index++ + ": " + num); } } }

Voici son rsultat, qui est bien celui prsent sur le site Web de Wikipdia :
0: 0 1: 1 2: 1 3: 2 4: 3 5: 5 6: 8 20: 6765 21: 10946

Nous retrouvons en fait les mmes mcanismes que les types gnriques ou le transtypage corrig ou adapt depuis le JDK 1.5, mais pour une variable de type primitif. Avant le JDK 1.5, il fallait utiliser un objet de type wrapper correspondant, ici un Long. Linstruction :
fiboListe.add(fibNum2);

aurait d tre crite ainsi dans la version 1.4 du JDK :


fiboListe.add(new Long(fibNum2));

314

Apprendre Java et C++ en parallle

Le nombre fibNum2 est une variable de type primitif, mais new Long(fibNum2) est un Object Java. Lunboxing est lopration inverse, que nous retrouvons dans la boucle for() qui, elle aussi, utilise la nouvelle forme du JDK 1.5.

Rsum
Matriser les conteneurs et les algorithmes est essentiel en programmation Java et C++. Nous avons un certain nombre de classes disposition, qui vont de simples listes des dictionnaires de paires de cl et de valeur. Le choix devrait se faire en fonction de leur capacit et efcacit, suivant la tche requise par lapplication. Les conteneurs sont en gnral traverss avec des itrateurs.

Exercices
Ces deux exercices sont trs importants pour bien mmoriser et comprendre les notations et les utilisations des classes de collections et des outils associs dans ces deux langages, lesquels sont en relation directe avec les types gnriques et les templates. Nous pourrons ensuite les comparer et les reprendre pour dautres exemples que nous pourrions inventer nous-mmes, comme la cration et laccs de petites bases de donnes. 1. Reprendre la liste de tlphone en C++ et crire une classe, comme dans lexemple en Java, qui implmente les diffrentes oprations. 2. Reprendre la classe TestListeTel.java et la convertir avec les listes gnriques en considrant les formats Map<String, String> et HashMap<String, String>. Nous utiliserons toujours un Iterator, mais sans transtypage, avec la forme Iterator<String>.

15
Concours de performance
Dans ce chapitre, nous allons analyser les performances de certains programmes et de certaines constructions, en jouant sur les variantes et alternatives offertes par du code sensible ces aspects. Le but nest pas ncessairement de rechercher le code le plus performant aux dpens de sa lisibilit ou de sa maintenance. Cette partie est en fait plus importante pour sensibiliser les dveloppeurs, an de les guider dans certains choix au cours de la conception des programmes et de classes rutilisables.

Comment analyser les performances ?


Dans cette premire partie, il nous faut parler des outils qui vont nous permettre de faire des mesures correctes. Suivant la base de temps utilise, qui sera la seconde ou la milliseconde, il faudra non seulement rpter sufsamment la fonction ou lopration mesurer, mais aussi lexcuter plusieurs fois dans un environnement constant. Les conditions de test doivent tre toujours identiques. Cela concerne non seulement la machine, ce qui semble vident, mais aussi dautres composants qui pourraient donner des rsultats errons, comme la dnition de lcran, le nombre de programmes chargs ou en activit. Nous nallons pas transfrer le dernier JDK de Sun Microsystems pendant lexcution de ces tests de performance. Les conditions de test doivent tre non seulement identiques, mais aussi dnies et dcrites prcisment, comme partie intgrante des rsultats et des conclusions que nous pourrions y apporter. Si le rsultat dun test nous semble erron, nous aurons tendance ritrer ce test, puisque nous avons t surpris du rsultat. Au contraire, sil nous parat correct, nous aurons trop facilement tendance, tort, laccepter, alors quil faut en fait, dans tous les cas, rpter le test et repenser notre environnement : par exemple savoir sil correspond la ralit. Des simulations extrieures, telles celles de surcharge, pourraient se rvler ncessaires,

316

Apprendre Java et C++ en parallle

mais sappliquent gnralement aux tests de capacit et aux traitements des erreurs en relation avec les limites du systme.

Les outils en Java


An de mesurer le temps dexcution dun programme ou dune partie de celui-ci, nous allons rdiger une classe en Java, qui nous permettra didentier le temps coul avec une prcision en millisecondes (ms). Nous utiliserons par la suite cette classe dans nos diffrents exemples :
import java.util.Properties; import java.io.*; public class Mesure { private long mtime1; private long mtime2; private long resultat; public void start() { mtime1 = System.currentTimeMillis(); } public long end() { mtime2 = System.currentTimeMillis(); resultat = mtime2 - mtime1; return resultat; } public String toString() { return Temps coul: + resultat + ms ; } public static void main(String[] args) { Mesure mesure1 = new Mesure(); System.out.println("Test de la classe Java Mesure"); Properties props = System.getProperties(); mesure1.start(); for (int i = 0; i < 2000000; i++) { props.put("myProperty", "true"); } mesure1.end(); System.out.println(mesure1); } }

Les mthodes start() et stop() de la classe Mesure permettent respectivement de lancer et de terminer la mesure. Dans la partie main() de notre classe, nous avons inclus un petit programme de test. Sur une machine dote dun Pentium III 450 MHz et de 128 Mo de mmoire vive, nous avons obtenu le rsultat suivant :

Concours de performance CHAPITRE 15

317

Test de la classe Mesure Temps coul: 1540 ms

Nous avons ensuite rpt lopration plusieurs fois et obtenu 1 540, 1 490, 1 530 et 1 480 ms. Le temps dexcution de 1,5 secondes par rapport la boucle de 2 000 000 est tout fait raisonnable. Nous pouvons prsent continuer le jeu et mettre en commentaire la mthode :
// props.put("myProperty", "true");

Nous obtiendrons alors 0 ms ! Nous pourrions donc nous demander si le compilateur nest pas trop intelligent pour dcouvrir quil ny a rien faire ! Cependant, si nous crivions maintenant ceci :
int j = 0; for (int i = 0; i < 2000000; i++) { j = j + i; }

nous aurions aussi 0 ms ! La boucle de 2 000 000 est donc insigniante et la conclusion que nous allons proposer devrait tre tout fait correcte ! En cas de doute, il ne faut pas hsiter apporter des modications au programme et recommencer. Lopration :
props.put("myProperty", "true");

prend donc 0,765 microseconde pour une moyenne de 1 530 ms en effectuant 2 000 000 fois lopration. Une autre valeur, beaucoup plus parlante, serait de donner le nombre doprations par seconde. Nous en aurions ici 1 307 189. Il serait donc possible dutiliser cette valeur pour des estimations de calcul pour des applications demandant un grand nombre daccs ou doprations. Sur un Intel Quad Core Q9450 (dbut 2008), la valeur ci-dessus de 1 530 ms passe 125 ms. Il y a sans doute aussi des aspects de performances lis la version de la machine virtuelle Java.

Les outils en C++


Dans les bibliothques C et C++ disposition pour la mesure du temps, il y a aussi une fonction pour retourner lhorloge en millisecondes, mais elle nest pas toujours disposition sur toutes les machines et tous les systmes dexploitation. Cependant, la fonction C time() devrait tre disponible sur nimporte quel systme. La classe Mesures ci-dessous devrait donc fonctionner aussi bien sous DOS que sous Linux. Nous avons ajout un s au nom de la classe. Il nous indiquera que nous travaillons en secondes. La classe suivante, Mesure, nous donnera une prcision en millisecondes, qui sera plus favorable pour nos exemples et notre patience. Voici donc la premire, notre classe Mesures :
// Mesures.h #include <ctime> #include <iostream>

318

Apprendre Java et C++ en parallle

#include <string> #include <sstream> class Mesures { private: long stime1, stime2, resultat; public: inline void debut() { stime1 = std::time(NULL); } inline void fin() { stime2 = std::time(NULL); resultat = stime2 - stime1; } inline std::string toString() { std::ostringstream os; os << "Le temps coul: " << resultat << " secs" << std::endl; return os.str(); } };

Comme toutes les mthodes de la classe C++ Mesures sont inline, il nest pas ncessaire de dnir de code supplmentaire dans un chier Mesures.cpp. Nous passons directement un petit programme de test :
// TestMesures.cpp #include <cmath> #include "Mesures.h" using namespace std; int main() { cout << "Test de la classe C++ Mesures" << endl; Mesures mes1; mes1.debut();

// signe le dbut de la mesure

for (int i = 0; i < 5000000; i++) { sin(cos(sinh(cosh(sqrt(i))))); } mes1.fin(); // signe lae fin de la mesure

cout << mes1.toString() << endl; return 0; }

Concours de performance CHAPITRE 15

319

Et le rsultat de ce test farfelu, combinant les fonctions mathmatiques du sinus, cosinus, sinus hyperbolique, cosinus hyperbolique et racine carre, nous donnera ceci :
Test de la classe C++ Mesure Le temps coul: 3 secs

Un temps de 3 secondes, pour des mesures statistiques convenables, ne serait pas sufsant, car le rsultat naura une prcision qu la seconde prs. Il faudrait alors multiplier la boucle par 10, voire par 100, et attendre jusqu plusieurs minutes pour lobtention dune mesure quil nous faudra encore et encore rpter. Par chance, le compilateur que nous avons disposition nous permet aussi de retourner le temps en millisecondes ; voici le code de notre nouvelle classe Mesure (sans s) :
// Mesures.h #include <ctime> #include <iostream> #include <string> #include <sstream> class Mesures { private: long stime1, stime2, resultat; public: inline void debut() { stime1 = std::time(NULL); } inline void fin() { stime2 = std::time(NULL); resultat = stime2 - stime1; } inline std::string toString() { std::ostringstream os; os << "Le temps coul: " << resultat << " secs" << std::endl; return os.str(); } };

Si nous adaptons le code ci-dessus pour la classe TestMesures :


// TestMesures.cpp #include <cmath> #include "Mesures.h" using namespace std; int main() {

320

Apprendre Java et C++ en parallle

cout << "Test de la classe C++ Mesures" << endl; Mesures mes1; mes1.debut();

// signe le dbut de la mesure

for (int i = 0; i < 5000000; i++) { sin(cos(sinh(cosh(sqrt(i))))); } mes1.fin(); // signe lae fin de la mesure

cout << mes1.toString() << endl; return 0; }

nous obtiendrons le rsultat en millisecondes cette fois-ci :


Test de la classe C++ Mesure Le temps coul: 2688 millisecs

Ce rsultat est nettement plus dans des normes convenables de calcul statistique.

Gagner en performance : une rude analyse


laide dun petit exemple pratique, nous allons comprendre quune analyse dtaille du code et des rsultats de performance est absolument essentielle avant de prendre mot pour mot des arguments sortis tout droit douvrages de rfrence ou de la bouche des gourous informaticiens. Il est souvent ncessaire de dvelopper un prototype avant de prendre des dcisions de conception et de code. Au chapitre 15, nous avons dcouvert le mot-cl final en Java, pour lappliquer des mthodes. Les compilateurs Java vont mettre en uvre le code ncessaire pour inclure le code en ligne. Notre classe DesMath va nous calculer les fonctions mathmatiques du sinus, du cosinus et de la tangente pour diffrentes valeurs, dans une boucle qui se rptera 2 millions de fois :
import java.lang.Math; public class DesMath { private double sin; private double cos; private double tan; private int compteur; public final void calcule(double nombre) { sin = Math.sin(nombre); cos = Math.cos(nombre); tan = Math.tan(nombre); compteur++; }

Concours de performance CHAPITRE 15

321

public int getCompteur() { return compteur; }

public static void main(String[] args) { DesMath desmath = new DesMath(); Mesure mesure = new Mesure(); mesure.start(); for (double i = 0; i <= 2*Math.PI; i += 0.000002) { desmath.calcule(i); } mesure.end(); System.out.println(mesure); System.out.println("Le compteur est " + desmath.getCompteur()); } }

Nous verrons, au travers de cet exemple, une foule de petits dtails qui sont extrmement importants considrer lorsquil faut analyser un rsultat de performance. Une petite erreur peut en effet conduire une analyse totalement errone. En n de programme, nous aurons le compteur imprim avec la valeur de 3 141 593 (1 000 000 ) ) ! Est-ce vraiment un nombre trange ? Il est dabord trs grand, et cest important, surtout si nous tournons le programme sur une machine trs rapide. linverse, sur une machine trop lente, nous pourrions attendre plusieurs minutes inutilement. La raison de prendre le 2*Math.PI est essentielle, car toutes les valeurs possibles du sinus et du cosinus seront testes. Il est fondamental de savoir quaussi bien le sinus que le cosinus prendront des temps dexcution diffrents suivant la valeur de langle. Il faut aussi noter que si la mthode calcule() ntait utilise que pour des angles trs petits, comme pour la dviation dune fuse Ariane au dpart, il faudrait certainement revoir notre mthode de calcul de performance. En excutant ce code plusieurs fois, nous obtiendrons un rsultat rgulier autour de 780 ms (Intel Core Quad Q9450 [dbut 2008]). prsent, si nous dclarons la mthode calcule() comme final de cette manire :
public final void calcule(double nombre) {

le code sera compil en ligne. Ce qui veut dire que les quatre instructions lintrieur de la mthode calcule seront en fait dans la boucle for(). Nous conomisons ainsi 3 141 593 appels de mthode. Comme rsultat, nous nobtiendrons aucune diffrence. Sommes-nous surpris ? Certainement, car cest un sujet qui revient relativement souvent dans les ouvrages spcialiss ! Nous pourrions aussi jouer avec une mthode calcule() non nale et vide de code, pour montrer quun appel de mthode est de toute manire extrmement rapide et quil est inutile de sattarder sur ce point de dtail. Sur une station

322

Apprendre Java et C++ en parallle

Ultra 5 de Sun Microsystems, nous avons constat une amlioration denviron 0,1 % pour la mthode final.

Que peut apporter une meilleure analyse ?


Si nous reprenons notre code prcdent et faisons une analyse plus prcise, nous allons dcouvrir que notre :
tan = Math.tan(nombre);

pourrait tre corrig en :


tan = sin/cos;

puisque la tangente est bien la division du sinus par le cosinus. Si nous changeons le code en gardant le final et le recompilons, nous obtiendrons 1 150 ms ! Cest presque invraisemblable, puisque globalement nous gagnons un facteur 4 ! nouveau sur une station Ultra 5, nous navons constat quune amlioration de 22 % ! Il y a donc des variantes selon les diffrentes machines virtuelles, les processeurs et ventuellement les coprocesseurs mathmatiques. Nous pourrions pousser le jeu crire :
cos = Math.sqrt(1 - (sin*sin));

et constater que cette opration ncessite beaucoup trop doprations et quune partie du bnce gagn par notre tan = sin/cos serait perdue ! Enn, il ne faudra pas oublier les cots pour une telle analyse et surtout pas crire du code qui pourrait devenir dlicat maintenir car trop complexe. Les diffrences de performance sur dautres systmes sont aussi un point dlicat qui pourrait remettre en question certaines de nos conclusions pour des produits livrables sur diffrentes plates-formes.

Passage par valeur ou par rfrence en C++


Nous avons examin, au chapitre 6, les diffrentes manires de passer des paramtres aux mthodes C++. Nous avions alors recommand lutilisation du passage par rfrence, et, puisque nous avons prsent des outils disposition, nous allons le constater par les chiffres. Nous avons aussi ajout le cas du passage par pointeur, an de vrier cette autre possibilit. Il est important pour le programmeur de vrier rgulirement ses prjugs. Pendant des annes nous avons pris lhabitude de travailler avec les mmes rgles, qui sont parfois dsutes. Une remise en question nest jamais interdite. Un processeur qui roule 1 GHz et les rvolutions sur les couches profondes des systmes dexploitation affectent non seulement les performances, mais surtout les rapports de performance des fonctions mesures. De nouvelles instructions processeurs ou de nouveaux bus entre les diffrentes parties du systme affecteront, sans aucun doute, nos ides prconues. Nous retrouvons ici notre classe Personne, rduite au minimum pour ces tests de performance.

Concours de performance CHAPITRE 15

323

// ValRef.cpp #include <iostream> #include <string> #include "Mesure.h" using namespace std; class Personne { public: string method1(const string str) { return str.substr(1, 2); } string method2(const string &str) { return str.substr(1, 2); } string method3(const string *str) { return str->substr(1, 2); } };

int main() { Personne pers; string resultat; string teststr("Bonjour Monsieur ! Comment allez-vous ?"); Mesure mesure; mesure.debut(); for (int i = 0; i < 2000000; i++) { resultat = pers.method1(teststr); } mesure.fin(); cout << mesure.toString() << endl; mesure.debut(); for (int i = 0; i < 2000000; i++) { resultat = pers.method2(teststr); } mesure.fin(); cout << mesure.toString() << endl; mesure.debut(); for (int i = 0; i < 2000000; i++) { resultat = pers.method3(&teststr); } mesure.fin(); cout << mesure.toString() << endl; }

324

Apprendre Java et C++ en parallle

Il est important davoir une boucle sufsante pour une bonne prcision. Les deux rsultats suivants, sur un PC datant de lan 2000 :
Temps coul: 5880 millisecs Temps coul: 5170 millisecs Temps coul: 5050 millisecs Temps coul: 5870 millisecs Temps coul: 5100 millisecs Temps coul: 5110 millisecs

extraits dune srie de mesures, sont donc satisfaisants. La grandeur de la boucle est sufsante pour garantir une bonne prcision. Ce rsultat nous donne donc le sentiment que notre recommandation pour une utilisation du passage par rfrence est tout fait correcte. Il faut aussi reconnatre que le code, lintrieur de ces trois mthodes, ne correspond pas du code rel dapplications conventionnelles. Nous aurions alors un rapport beaucoup plus faible entre les deux rsultats (< 13 %). Cependant, cest de toute manire un gain (environ 700 ms). Cette manire de faire est aussi plus lgante. Sur un processeur plus rcent (2008), nous obtenons des rsultats avoisinant les 1 000 ms, mais la mme analyse est applicable. Nous navons pas essay de comprendre la diffrence pour la dernire valeur, qui correspond plus ou moins notre attente. Cest aussi le but des mesures de performance, qui devraient exiger une analyse plus profonde en cas de surprise. Cest loin dtre le cas ici, et cela conrme nos suspicions.

Performance et capacit mmoire


Nous devons tout de mme mentionner les problmes de performance qui pourraient se produire en C++ et en Java par une utilisation exagre de la mmoire. Il faut aussi mentionner lutilisation potentielle de mmoire virtuelle (swap) qui pourrait se produire et ralentir plus particulirement des systmes aux ressources limites. Le dveloppement doutils permettant dobtenir des statistiques dutilisation de mmoire ou mme de processeur est tout fait ralisable. Nous nen donnerons pas dexemple, mais nous devons absolument revenir sur le programme prcdent. Lors de lexcution de la mthode method1(), qui reoit son paramtre par valeur, nous aurons besoin de mmoire supplmentaire. Dans le cas prsent, cest tout fait insigniant. Cependant, il faut se rendre compte que cette mthode pourrait tre incluse dans une bibliothque et utilise par une multitude de processus parallles. De plus, le string pass cette mthode pourrait tre dune dimension importante et demander des ressources supplmentaires en mmoire virtuelle.

Les entres-sorties
Nous allons remarquer, dans cette partie, que les entres-sorties en programmation sont un des domaines les plus sensibles en ce qui concerne les performances. Si lapplication

Concours de performance CHAPITRE 15

325

ncessite une certaine vitesse dexcution, il est recommand, durant la conception, de procder une phase danalyse au travers de prototypes. Ces derniers nous donneraient vraisemblablement des indications pour une criture plus performante de nos programmes dentres-sorties. Un autre aspect peut tre essentiel, celui de la machine et de son matriel. Certains systmes peuvent tre construits avec des logiciels, des disques ou des caches plus performants. Un rsultat sur une machine X avec une version Y du compilateur ou de la machine virtuelle tournant sur la version Z du systme dexploitation pourrait donner des rsultats totalement errons sur lesquels il serait impossible de tirer des conclusions irrprochables. Les deux programmes qui suivent devraient tre excuts sur la machine et lenvironnement o le produit sera nalement install. Ceci uniquement si la conclusion des rsultats de lanalyse de performance entrane une amlioration signicative et sans rendre le code illisible ni difcile maintenir. Nous pensons quil est toujours possible de garder une criture simple de ces programmes et surtout de documenter les parties les plus dlicates et les raisons de certaines constructions au premier abord tranges.

Lecture de chiers en C++


Notre programme de test de lecture va analyser les diffrences de performance suivant le nombre doctets lus par appel de fonction. Les deux limites extrmes sont respectivement un octet et le chier entier transfr en mmoire. Ce dernier cas na pas de sens, car pour dnormes chiers nous toucherions alors dautres ressources du systme comme la mmoire virtuelle (swap). Voici donc le programme qui utilise les iostreams du C++ tels que nous les avons tudis au chapitre 9 :
// lecture.cpp #include <iostream> #include <sstream> #include <fstream> #include <string> #include "Mesure.h" using namespace std; int main(int argc, char **argv) { Mesure mes1; int dim_bloc; if (argc != 3) { cerr << "Nombre d'arguments invalide" << endl; cerr << "lecture fichier dimension" << endl; return -1; } istringstream entree(argv[2]);

326

Apprendre Java et C++ en parallle

entree >> dim_bloc; if (dim_bloc <= 0) { cout << "La dimension doit tre > 0"; return -1; } mes1.debut(); ifstream infile(argv[1], ios::in|ios::binary); if (!infile) { cout << "Le fichier d'entre n'existe pas"; return -1; } char *tampon = new char[dim_bloc]; int octets_lus; int total_lus = 0; for (;;) { // lecture par bloc infile.read(tampon, dim_bloc); octets_lus = infile.gcount(); for (int i = 0; i < octets_lus; i++) { total_lus++; } if (octets_lus < dim_bloc) break; } infile.close(); delete[] tampon; mes1.fin(); cout << mes1.toString() << endl; cout << "Nombre d'octets lus: " << total_lus << endl; } // dernier bloc

Ce qui est important, avec la classe Mesure et ces deux mthodes start() et end(), cest denglober non seulement les blocs de code for(), qui vont consommer le maximum de temps, mais aussi lallocation et la libration des ressources (new et delete). En cas de doute ou danalyse plus pointue, il pourrait se rvler ncessaire dajouter dautres instances de la classe Mesure. Nous devons aussi choisir une grandeur de chier sufsante pour obtenir un rsultat de lordre de la seconde. Si nous utilisons la mthode read(tampon, dim_bloc) avec dim_bloc correspondant un seul octet, nous aurons ce rsultat sur un PC datant de lan 2000 pour un chier de 4 102 301 octets :
Temps coul: 1600 millisecs Nombre d'octets lus: 4102302 Le tableau ci-dessous a t labor sur ce mme PC et avec une moyenne calcule sur six mesures pour chaque bloc. Lorsquune ou deux valeurs taient en dehors des limites

Concours de performance CHAPITRE 15

327

prcdentes, nous les avons parfois limines ou alors nous avons nouveau procd de nouveaux tests.
Tableau 15-1
Bloc ms
1 1600 2 930 3 660

Lecture en C++ avec des dimensions de bloc diffrentes


4 550 10 390 50 270 64 220 128 220 1024 160-220 4096 220 (un 160) 40960 220

En conclusion, nous voyons immdiatement et distinctement une amlioration avec des blocs allant jusqu 64. Ensuite, cela devient nettement moins signicatif. La gure correcte doit donc certainement se situer entre 512 et 4 096 octets. Dans la ralit de la programmation, utiliser des tampons de 2, 3 ou 4 octets na aucun sens. Soit nous adoptons la lecture octet octet, soit nous transfrons par bloc en mmoire. Comme nous avons un facteur 10 avec des blocs aux alentours de 1 Ko, nous nhsiterons pas. Cependant, il y a des situations o le logiciel se rvle plus complexe et ncessite la relecture de certains octets en arrire par rapport la position courante. Il nous faudrait alors travailler avec deux tampons. La solution de relire en arrire avec un repositionnement dans le chier (seek) pourrait tre catastrophique pour les performances. Il faut revenir sur notre 160 ms pour un chier de 4 102 302 octets. quoi correspond-il? En une seconde, cela nous donne plus de 25 Mo. Cest un rsultat plus quhonorable sur une machine quipe dun disque Ultra DMA avec un transfert maximal de 33,3 Mo. Nous rappellerons quun CD-Rom 40X possde un taux de transfert de 6 Mo et quun disque Ultra2-Wide-SCSI peut aller jusqu 80 ou 160 Mo par seconde. Suivant les applications concevoir, ces chiffres restent des gures essentielles et que ce soit avec un PC datant de 2000 ou de 2008, le rsultat de lanalyse est identique.

Inuence de lappel de fonctions successives


Il serait injuste de ne pas revenir sur le programme prcdent sans considrer les appels successifs de la mthode read(). Cette remarque est aussi applicable en Java. Nous pourrions donc remplacer les deux lignes suivantes :
infile.read(tampon, dim_bloc); octets_lus = infile.gcount();

par deux fonctions internes vides de code :


read_test(tampon, 1); octets_lus = gcount_test();

dont nous trouverons le code complet sur le CD-Rom, dans le chier lecture2.cpp. Si nous excutons lecture2.exe en rptant 5 000 000 de fois ces deux instructions, nous obtiendrons un rsultat de 31 ms, ce qui est loin dtre insigniant. Cette valeur de 31 ms sera videmment beaucoup plus leve sur un processeur moins rcent.

328

Apprendre Java et C++ en parallle

Il faut remarquer que read_test() et gcount_test() sont vides et devraient contenir le code ncessaire pour transfrer les donnes depuis le systme dexploitation, qui lui-mme doit aussi, de temps autre, accder au disque ! Il est donc de toute manire plus performant de rduire le nombre dappels de la mthode read() de la classe ifstream, mme si cette dernire garantit dj un transfert par bloc (buffering ou cache).

Lecture de chiers en Java


Le programme suivant est conu sur le mme modle que le programme en C++. Le nom du chier et la dimension des blocs de lecture sont des arguments passs lapplication.
import java.io.*; public class Lecture { public static void main(String[] args) { if (args.length != 2) { System.err.println("Nombre d'arguments invalide"); System.err.println("Java Lecture fichier dimension"); return; } int dimBloc; try { dimBloc = Integer.parseInt(args[1]); } catch(NumberFormatException nfe) { System.err.println("Java Lecture fichier dimension"); System.err.println("dimension doit tre un nombre"); return; } if (dimBloc < 1) { System.err.println("La dimension doit tre > 0"); return; } byte[] tampon = new byte[dimBloc]; int octetsLus; int totalLus = 0; Mesure mesure1 = new Mesure(); mesure1.start(); File inFile = new File(args[0]); try { FileInputStream in = new FileInputStream(inFile); for (;;) {

Concours de performance CHAPITRE 15

329

octetsLus = in.read(tampon); for (int i = 0; i < octetsLus; i++) { totalLus++; } if (octetsLus < dimBloc) break; } for (int j = 1; j < dimBloc; j++) { in.read(); totalLus++; } in.close(); } catch (IOException e) { System.err.println("Le fichier d'entre n'existe pas"); return; } mesure1.end(); System.out.println(mesure1); System.err.println("Nombre d'octets lus: " + totalLus); } } // dernier bloc

Pour les premires mesures, nous navons pas utilis le mme chier quen C++, car cela prenait trop de temps pour la lecture caractre par caractre. Avec un bloc dun seul octet :
Temps coul: 2520 ms Nombre d'octets lus: 177008

nous obtenons un rsultat presque catastrophique ! Pour comparer avec lexemple et le rsultat en C++ ci-dessus, nous devons excuter la formule suivante, 2 520 / 177 008 4 103 302, qui nous donne 58 417 millisecondes. Nous sommes donc trente-six fois plus lents. Le tableau ci-dessous a t labor de la mme manire que le tableau pour la lecture en C++ et sur un PC datant de lan 2000.
Tableau 15-2
Bloc ms
1 2520 2 1310

Lecture en Java avec des dimensions de bloc diffrentes


3 840 4 630 10 275 50 65 64 55 128 650* 1024 160* 4096 150* 40960 460*

(*) Les mesures avec un chier de 177 008 octets devenaient trop petites et imprcises. Nous avons donc repris notre chier de 4 103 302 octets, qui est plus de vingt-trois fois plus grand.

330

Apprendre Java et C++ en parallle

En rsum, le rsultat est diffrent en Java, o nous constatons une progression plus constante. Nous obtenons mme une valeur infrieure la version C++ pour 4 096 octets ! Cest un rsultat assez surprenant, mais beaucoup moins que pour 40 960, o un autre phnomne se produit. Nous navons pas cherch en connatre la raison, mais cette dgradation tait encore plus signicative sur une station Sparc de Sun Microsystems sous Solaris.

Tests globaux de performance


Avec system() en C++
la livraison du JDK 1.3.1 de Java, nous avons vu apparatre un nouveau compilateur plus performant, selon Sun Microsystems, mais aussi la prsence de lancienne version, nomme oldjavac. Les rsultats ne nous ont pas convaincus et nous devons dire, notre dcharge, que nous navons pas cherch en dcouvrir les vraies raisons. En consquence, nous ne prsenterons pas ces rsultats. Ce qui est important ici, cest lutilisation de la fonction C system(), qui nous permet dexcuter une commande systme lintrieur dun programme C++. Voici donc un exemple dutilisation de la fonction system() pour mesurer les performances de compilateurs Java :
// TestJava.cpp #include "Mesure.h" using namespace std; void unTest(const char *cmd) { Mesure mes1; mes1.debut(); system(cmd); mes1.fin(); cout << cmd << ". " << mes1.toString() << endl; }

int main() { int i; cout << "Test de performance de diffrentes versions de javac" << endl; for (i = 0; i < 5; i++) { unTest("javac DesMath.java"); }

Concours de performance CHAPITRE 15

331

for (i = 0; i < 5; i++) { unTest("oldjavac DesMath.java"); // JDK 1.3.1 seulement } return 0; }

Le rsultat retourn va nous donner un chiffre qui correspond non seulement au temps dexcution de la compilation, mais aussi au chargement du programme. Au retour de system(), la compilation est vraiment termine, et le chier DesMath.class est bien rgnr et sauv sur le disque. Il y a donc plusieurs facteurs qui peuvent affecter les performances, comme la vitesse du processeur ou le temps daccs au disque. Les rsultats de ce type de test pourraient nous donner des indications sur la rapidit des compilateurs dans un environnement dtermin. Les conclusions pourraient tre utilises pour amliorer linfrastructure et lefcacit pour un groupe de dveloppeurs dans une entreprise. Souvent, il suft simplement dajouter de la mmoire vive. Nous rappellerons que ce nest jamais une bonne ide dditer de gros chiers de code sans compilation intermdiaire, car des fautes stupides de syntaxe peuvent ncessiter pas mal de temps tre corriges.

Avec exec() en Java


Nous allons faire le mme travail en Java, cest--dire excuter une compilation C++ depuis un programme Java et obtenir le temps ncessaire ce processus. Voici donc le programme en Java, qui est un peu plus complexe que sa version C++ :
import java.util.*; import java.io.*; public class ExecJava { public static void main(String args[]) { try { Process leProcessus; String[] cmd = {"g++", "-o", "lecture.exe", "lecture.cpp"}; System.out.println("Nous allons excuter: g++ -o lecture.exe lecture.cpp"); Mesure mesure = new Mesure(); mesure.debut(); leProcessus = Runtime.getRuntime().exec(cmd); try { System.out.println("Le rsultat: " + leProcessus.waitFor()); } catch(InterruptedException ie) { System.err.println("Processus interrompu" + ie); }

332

Apprendre Java et C++ en parallle

mesure.fin(); System.out.println(mesure); } catch(IOException ioe) { System.err.println("Processus non excut" + ioe); } } }

Ici, nous prsenterons le rsultat :


Nous allons excuter: g++ -o lecture.exe lecture.cpp Le rsultat: 0 Temps coul: 594 ms

La mthode exec() nous permet dexcuter un processus parallle. Elle est diffrente de la fonction C system(), qui terminera son excution avant de nous redonner la main. Des fonctions exec() en C existent aussi, mais sont fortement dpendantes des systmes dexploitation. Ces dernires sont cependant trs souvent employes en programmation sous Linux. La mthode getRuntime() est essentielle pour obtenir un objet de la classe Runtime. Elle nous permettra dobtenir toutes les caractristiques ncessaires pour pouvoir excuter un processus parallle avec la commande exec(). Cette dernire mthode doit recevoir un tableau de String avec la commande et ses paramtres. Elle nous retournera un objet de la classe Process sur lequel nous pourrons appliquer la mthode waitFor(). Celle-ci nous retournera le contrle lorsque la compilation sera termine. Nous pourrions jouer avec lecture.cpp et provoquer une erreur. Dans ce cas, le code retourn serait 1, mais aucun message derreur de compilation napparatrait. Nous laisserons cette partie plus dlicate aux programmeurs dsirant approfondir ce sujet. Nous dirons simplement que ce type de traitement est parfaitement possible et quil nous fait penser aux erreurs qui apparaissent dans notre console de rsultat avec notre diteur Crimson.

Autres calculs de performance ou de contraintes


Nous aurions pu reprendre une ou plusieurs collections du chapitre 14. Cest une source innie de combinaisons et dalternatives. Nous ferons juste un exercice, le dernier, avec la classe vector en C++, an de reprendre les techniques utilises dans ce chapitre. Lorsque nous parlons de collections, nous pensons trs rapidement des donnes persistantes que nous transfrerons dans des chiers ou des bases de donnes SQL. Ce dernier sujet sera trait au chapitre 20. Si nous devions rserver une place dans un avion, il ne serait pas judicieux de crer une collection en mmoire. Le client peut changer davis, prendre son temps ou se faire piquer sa place ct fentre par un autre individu de lautre ct de la plante. Il ny a aucun aspect de performance de calcul considrer, mais dautres contraintes comme laccs en temps rel. En revanche, sil faut analyser, convertir ou

Concours de performance CHAPITRE 15

333

mettre jour globalement une base de donnes, un transfert dans des collections pour des accs rapides est de toute manire un bon choix. Lorsque nous transfrons un chier sur Internet, nous devons nous poser la question sil faut le compresser ou non. Une analyse de performance nous aidera certainement. Celle-ci pourrait se rvler totalement inutile, si le consommateur exige des factures de tlphone minimales ou des accs rapides dautres services en parallle. Obtenir des vidences, par des tests de capacit et de performance, est une ncessit absolue.

Rsum
Avant de prendre des dcisions trop rapides en vue damliorer les performances du code, il est donc essentiel de procder une analyse dtaille du code. Un certain pragmatisme est ncessaire pour gagner en efcacit et en simplicit. De bons outils permettront didentier des amliorations invisibles lors dune premire lecture.

Exercices
1. Rcrire les deux classes Java et C++ Mesure avec deux constructeurs. Ils recevront tous les deux une identication de ce qui est mesur et, pour le deuxime, un paramtre qui correspond au nombre de rptitions. La mthode toString() devra retourner, en plus du rsultat, lidentication. Si le deuxime constructeur est utilis, un second rsultat sera prsent, non plus en millisecondes, mais en microsecondes et en virgule ottante, aprs la division par le nombre de rptitions. 2. Reprendre la classe vector en C++ du chapitre 14 et insrer un grand nombre dentiers avec push_back(), insert(vector_instance.end(), i) et insert(vector instance.begin(), i). Dterminer les performances de chacune de ces mthodes. Pour la meilleure des implmentations, vrier si lallocation prdnie de la dimension du vector apporte ou non une amlioration.

16
Comment tester correctement ?
Le Y2K bug
Le fameux bug de lan 2000 a-t-il t la plus grande arnaque conomique de ces dernires annes, comme certains lafrment ? Dans une certaine mesure, on peut rpondre oui. Cependant, si nous examinons les cts positifs de cet norme travail de contrle, il nous faut retenir toutes ces heures de travail o les informaticiens ont planch sur le problme. Ils ont essay toutes sortes de tests et de combinaisons farfelues, dans le but de trouver une faille ou encore des ns de prestige personnel ! Ce travail a souvent t labor de lextrieur, par de simples tests de fonctionnalits, comme celle de changer la date sans savoir comment tait structur le programme informatique. Dautres ingnieurs sont alls plus profondment dans le code des applications ou des systmes dexploitation. Ils ont crit des outils de test, par exemple la vrication de certaines fonctions des API (Application Programming Interface). En Java et en C++, nous pensons aux classes qui accdent la date et lheure et qui ont d tre examines avec une attention redouble.

Une stratgie de test ds la conception


Le message que nous aimerions faire passer, an de sensibiliser le programmeur, est de ne pas attendre que lapplication soit termine pour se poser la question de savoir comment la tester. Ds que le programmeur commence coder, il doit avoir dni une stratgie de dveloppement et une structure pour les tests de ces programmes et de ces modules. Il doit avoir aussi sa disposition les outils ncessaires. Il y a ainsi parfois des situations o il faudra allouer des ressources dans le but unique dcrire des outils de test.

336

Apprendre Java et C++ en parallle

Il est aussi essentiel de penser pouvoir rpter ces tests lorsque le produit sera termin. Ce dernier aura sans doute t livr sans les modules de test. Il faudra donc dnir des moyens pour collectionner les donnes au moment du problme an de pouvoir simuler et reproduire les fautes dans un environnement de tests. Chaque interface interne devra pouvoir tre vrie sparment. Il faudra aussi penser des tests automatiques, dont nous constaterons les rsultats aprs une nuit de sommeil ou un week-end de ski. Lors de la dnition, dans une entreprise, dun projet informatique, il est essentiel de mettre au point une stratgie de dveloppement et les diffrentes phases du projet. Nous devrions y retrouver non seulement la dnition et la spcication des tests, mais aussi les rsultats sous forme de documents ofciels. Il faudra aussi, dans des cas particuliers, investir pour acheter de nouveaux outils ou du matriel spcique comme des simulateurs.

Avec ou sans dbogueur


Cest un sujet controvers. Dans lannexe E, nous parlerons de NetBeans, un outil de dveloppement qui intgre un dbogueur permettant du pas pas dans un programme Java ou C++. Dans des cas bien spciques, il pourra nous aider identier notre problme tout en gagnant du temps, cest tout ! De lavis de lauteur, nous pensons que de grosses applications ou projets qui ncessitent absolument lutilisation dun dbogueur sont simplement mal crits. Ctait autrefois le cas avec du code procdural et des programmeurs qui navaient aucune ide de lcriture en modules, mme si ceux-ci ntaient pas encore orients objet ou encore labors avec des outils de conception (UML).

Les tests de base


Les tests de base (basic tests en anglais) sont essentiels en programmation. Dans cet ouvrage, nous nous concentrerons sur ces derniers car ils sont souvent ngligs par les informaticiens, au prot des tests dintgration, de fonctions ou de systme, qui viennent en principe beaucoup plus tard dans les phases de dveloppement. Tester toutes les possibilits dinterface interne dun programme est du domaine de limpossible. Nous allons commencer par un petit exercice de rexion. Problme : crire une mthode ou fonction qui extrait un nombre de caractres n dans une chane depuis la position pos. Ces caractres ne peuvent tre que des chiffres de 0 9. Test : crire un programme de test qui contrle le maximum de conditions possibles. La premire remarque qui nous vient lesprit est le fait que nous posions le problme globalement. Nous sommes en fait en train de dnir tous les dtails de la conception de notre mthode, et cest loin dtre un mal. Avons-nous oubli un dtail ? Bonne question ! Cest lune des raisons pour lesquelles les entreprises ou socits informatiques organisent des inspections de design et de code. Bien que les dveloppeurs soient responsables des

Comment tester correctement ? CHAPITRE 16

337

tests de base, nous ne donnons pas en gnral aux mmes personnes la tche de dcrire et dexcuter les tests de fonctions.

La fonction extraction() en C++


Voici donc, en C++, une implmentation possible dune fonction pour rsoudre notre problme :
bool extraction(const char *original, char *tampon, int pos, int nombre) { if ((original == 0) || (tampon == 0)) return false; if ((pos < 0) || (nombre < 0)) return false; int longueur; int dernier = pos + nombre; for (longueur = 0; ; longueur++) { if (longueur >= dernier) break; if (original[longueur] == 0) { if (longueur < dernier) return false; } } int i; char chiffre; for (int i = pos; i < dernier; i++) { chiffre = original[i]; if ((chiffre < '0') || (chiffre > '9')) return false; } for (int i = 0; i < nombre; i++) { tampon[i] = original[i + pos]; } return true; }

Les morceaux de code source prsents ici font tous partie du chier Extraction.cpp des exemples de ce chapitre. Ce code vraiment particulier est loin dtre vident au premier coup dil. Nous devons analyser chaque ligne de code pour en comprendre le sens. Nous aurions pu utiliser une simple fonction C ou la classe string du Standard C++ pour faire le travail. Nous avons choisi ici de tester le plus grand nombre de cas derreurs possibles et certainement davantager aussi la performance. Les deux premiers tests :
if ((original == 0) || (tampon == 0)) return false; if ((pos < 0) || (nombre < 0)) return false;

pourraient en fait tre supprims la livraison du produit, condition davoir excut sufsamment de combinaisons de tests. Cest ici que nous pourrions faire la remarque de limportance deffacer certaines variables en cas de rutilisation. Dans le code suivant :
char *temporaire = new char[20]; if (extraction("a56d", temporaire, 1, 2)

338

Apprendre Java et C++ en parallle

. delete[] temporaire; temporaire = 0; . if (extraction("b67ef", temporaire, 1, 2)

si nous omettions le :
temporaire = 0;

la mmoire adresse par cette variable pourrait tre rutilise, alors quelle pointe sur une position de mmoire libre et peut-tre dj ralloue par cette application ou une autre ! Enn, il nous faut faire remarquer que les deux dernires boucles for() pourraient tre combines. Mais cela signierait une autre dnition de linterface, o le tampon de retour pourrait avoir t utilis, mme en cas derreur. Cest certainement viter, et notre solution est plus convenable.

Le programme de test de la fonction extraction ()


Il nous faut passer prsent la phase de test. Le programme suivant est avant tout un exemple pour sensibiliser les dbutants dans cette phase extrmement complexe et coteuse. En effet, il nous a fallu beaucoup plus de temps pour crire et vrier le programme de test que pour dvelopper la fonction elle-mme ! Cependant, ce programme nous a aussi autoris reprendre notre code prcdent pour lamliorer en consquence.
void init_test(char *tampon, int size) { for (int i = 0; i < size; i++) { tampon[i] = 0; } } bool doit_etre_vide(const char *tampon, int size, int pos) { for (int i = pos; i < size; i++) { if (tampon[i] != 0) return false; } return true; } void print(const char *tampon, int size) { for (int i = 0; i < size; i++) { cout << tampon[i]; } cout << endl; }

Les fonctions init_test(), doit_etre_vide() et print() nous permettent dcrire un code plus compact. Cest aussi une manire simplie de vrier que notre mthode extraction() ne va pas dborder ni produire des rsultats inattendus. Il est pratiquement impossible de

Comment tester correctement ? CHAPITRE 16

339

tester tous les cas. Le but est datteindre une certaine conance. Si une erreur est dcouverte, non seulement nous changerons le code, mais nous ajouterons aussi de nouveaux tests.
int main() { char tampon[20]; if (extraction(0, 0, 0, 0) == true) { cout << "Test1a: erreur" << endl; } if (extraction("abcd", 0, 0, 0) == true) { cout << "Test2a: erreur" << endl; } if (extraction("abcd", tampon, -1, 0) == true) { cout << "Test3a: erreur" << endl; } if (extraction("abcd", tampon, 0, -1) == true) { cout << "Test4a: erreur" << endl; }

init_test(tampon, 20); if (extraction("abcd", tampon, 0, 0) == false) { cout << "Test5a: erreur" << endl; } if (!doit_etre_vide(tampon, 20, 0)) { cout << "Test5b: erreur" << endl; }

init_test(tampon, 20); if (extraction("abcd", tampon, 0, 5) == true) { cout << "Test6a: erreur" << endl; } if (!doit_etre_vide(tampon, 20, 0)) { cout << "Test6b: erreur" << endl; }

init_test(tampon, 20); if (extraction("abcd", tampon, 5, 1) == true) { cout << "Test7a: erreur" << endl; } if (!doit_etre_vide(tampon, 20, 0)) { cout << "Test7b: erreur" << endl; }

init_test(tampon, 20); if (extraction("abcd", tampon, 2, 4) == true) { cout << "Test8a: erreur" << endl; } if (!doit_etre_vide(tampon, 20, 0)) { cout << "Test8b: erreur" << endl; }

init_test(tampon, 20); if (extraction("abcd", tampon, 0, 1) == true) { cout << "Test9a: erreur" << endl; } if (!doit_etre_vide(tampon, 20, 0)) { cout << "Test9b: erreur" << endl; }

init_test(tampon, 20);

340

Apprendre Java et C++ en parallle

if (extraction("abcd", tampon, 1, 2) == true) { cout << "Test10a: erreur" << endl; } if (!doit_etre_vide(tampon, 20, 0)) { cout << "Test10b: erreur" << endl; } init_test(tampon, 20); if (extraction("12345", tampon, 0, 1) == false) { cout << "Test11a: erreur" << endl; } if (!doit_etre_vide(tampon, 20, 1)) { cout << "Test11b: erreur" << endl; } print(tampon,1); init_test(tampon, 20); if (extraction("12345", tampon, 2, 3) == false) { cout << "Test12a: erreur" << endl; } if (!doit_etre_vide(tampon, 20, 3)) { cout << "Test12b: erreur" << endl; } print(tampon,3); init_test(tampon, 20); if (extraction("aaa9876zxx", tampon, 3, 4) == false) { cout << "Test13a: erreur" << endl; } if (!doit_etre_vide(tampon, 20, 4)) { cout << "Test13b: erreur" << endl; } print(tampon,4); }

Ce code est parlant de lui-mme. Il na pas fonctionn la premire criture, et il a fallu jouer avec les !, true et false correctement. Nous remarquerons le cas dun nombre de caractres extraire ayant la valeur 0 : nous pensons convenable daccepter ce cas, qui pourrait mme simplier lutilisation de cette interface. Ce point de dtail, qui peut avoir t dcouvert pendant les tests, est essentiel : il doit absolument faire partie de la documentation de la fonction extraction(). En cas de rcriture complte de cette mthode extraction(), le code de test est rutilisable, sans changement, condition que linterface (lAPI) reste identique.

Le programme de test extraction () en Java


La classe Extraction en Java et le programme de test seront raliss en exercice. Nous avons afrm, au dbut de cet ouvrage, quil pourrait se rvler intressant dajouter une entre main() dans toutes les classes titre de vrication ou de test. Dans le cas de tests de cette complexit, ce nest certainement pas une ide gniale dadopter cette rgle et de

Comment tester correctement ? CHAPITRE 16

341

livrer ce code avec ces tests. Il serait alors conseill dcrire des classes de test sparment. Lorsque nous examinons un tel code :
init_test(tampon, 20); if (extraction("aaa9876zxx", tampon, 3, 4) == false) { cout << "Test13a: erreur" << endl; } if (!doit_etre_vide(tampon, 20, 4)) { cout << "Test13b: erreur" << endl; } print(tampon,4);

nous avons le sentiment que nous pourrions crire une fonction pour combiner tous ces contrles ! Cest ce que nous ferons comme exercice en n de chapitre.

Suivre la trace
Nous allons prsent crire une classe, nomme Traceur, qui va nous permettre de tracer lexcution de programmes.

Dnition du problme
Nous aimerions tre capables dajouter du code de test dans nos applications an de pouvoir enregistrer des donnes pendant lexcution dun programme. Ces donnes seront disponibles sur le disque avec la date et lheure, lidentication de lenregistrement et des variables qui viennent du programme. Cette classe pourra tre utilise aussi bien pour identier les cas derreurs que pour suivre la trace les cas normaux. Elle pourrait avoir dintressantes applications pour produire des statistiques de performance ou dutilisation de certaine partie du code que nous aimerions, par exemple, amliorer ou rutiliser dans le futur.

La classe Traceur en C++


Voici prsent une manire de faire, simplie au maximum et prsente au travers du chier den-tte pour la dnition de la classe Traceur :
// Traceur.h #include <fstream.h> #include <string> class Traceur { public: Traceur(const char *info, const char *nom_fichier); ~Traceur(); void ecrit(const char *texte);

342

Apprendre Java et C++ en parallle

void ecrit(const char *texte, int valeur); private: void commun();

// info fixe

string tnom; // le nom du traceur ofstream mon_ofstr; // fstream pour criture int numref; // numro de rfrence };

Le constructeur va donc recevoir largument info qui sera utilis pour identier le traceur ainsi que le nom du chier dans lequel les donnes seront crites. Les deux mthodes crit(), avec des arguments diffrents, vont nous permettre dcrire des donnes depuis le programme. Nous aurions pu aussi ajouter dautres mthodes comme :
void ecrit(const char *texte1, const char *texte2); void ecrit(const int valeur); void ecrit(const char *texte, int valeur1, int valeur2);

ou encore avec dautres types. Le numref va tre un numro de squence que nous pourrions utiliser pour trier ou rechercher des donnes. Nous allons maintenant dcouvrir la prsence de <ctime>, qui nous indique que nous avons bien lintention denregistrer la date et lheure. Nous passons donc au code proprement dit de la classe Traceur :
// Traceur.cpp #include "Traceur.h" #include <ctime> using namespace std; Traceur::Traceur(const char *info, const char *nom_fichier) { tnom = "-" + string(info) + ": "; numref = 1; mon_ofstr.open(nom_fichier); } Traceur::~Traceur() { mon_ofstr.close(); } void Traceur::ecrit(const char * texte) { commun(); mon_ofstr << texte << endl; } void Traceur::ecrit(const char * texte, int valeur) {

Comment tester correctement ? CHAPITRE 16

343

commun(); mon_ofstr << texte << " " << valeur << endl; } void Traceur::commun() { time_t maintenant; time(&maintenant); string lheure(ctime(&maintenant)); mon_ofstr << string(lheure, 0, 24) << " " << numref++ << tnom; }

Cest vraiment une simplication lextrme. Il ny a pas de retour derreurs, et louverture du chier dans le constructeur nest pas vraiment judicieuse. Le :
string(lheure, 0, 24)

est ncessaire pour liminer le "\n" en n de chane, car celui-ci nous est retourn par la fonction C ctime(). Le rsultat dans le chier pourra se prsenter sous cette forme :
Tue Jul 08 11:05:28 2008 1-T2: test2a 2000000

1-T2 reprsente la valeur numref suivie de largument donn au constructeur. Compteur 2000000 vient des deux paramtres de la mthode :
ecrit(const char * texte, int valeur)

Nous voyons ainsi comment la mthode commun() va nous gnrer la premire partie de lenregistrement. Avant de revenir un exemple avec la mthode extraction(), nous allons donner ici une liste damliorations ou doptions que nous pourrions introduire dans ce code : traiter les erreurs daccs au chier correctement ; ajouter les millisecondes la mthode commun() ; tre capable dutiliser le mme chier pour plusieurs instances de la classe Traceur ; pouvoir enclencher ou dclencher lenregistrement avec une variable denvironnement ; introduire un mcanisme pour visualiser (par exemple en Java [AWT ou Swing]) la fois les enregistrements actifs (besoin de les mmoriser et dun destructeur) et les donnes en temps rel.

Tester la classe Traceur en C++


Sur le CD-Rom, nous trouverons le programme de test TraceurTest.cpp :
// TraceurTest.cpp #include "Traceur.h" #include <iostream> #include <cmath> using namespace std;

344

Apprendre Java et C++ en parallle

int main() { Traceur t1("T1", "fichier1.txt"); Traceur t2("T2", "fichier2.txt"); cout << "Bonjour" << endl; t1.ecrit("test1a"); const long cons = 2000000; for (long i = 0; i < cons; i++) { sin(cos(i)); } cout << endl; t2.ecrit("test2a", cons); cout << "Au revoir" << endl; t2.ecrit("test2b"); }

Pour le compiler, il faudra utiliser le Makefile puisque la classe Traceur.cpp doit tre compile pralablement. En excutant TraceurTest.exe nous recevrons ceci :
Bonjour Au revoir

Il ny a rien de bien surprenant, car il faudra encore consulter les chiers de traage fichier1.txt :
Tue Jul 08 11:05:28 2008 1-T1: test1a

et fichier2.txt pour examiner les traces demandes :


Tue Jul 08 11:05:28 2008 1-T2: test2a 2000000 Tue Jul 08 11:05:28 2008 2-T2: test2b

La classe Traceur en Java


Aprs avoir cod notre classe Traceur en C++, nous sommes arrivs la conclusion que beaucoup damliorations pourraient lui tre apportes. Dans la version Java, nous avons choisi de travailler diffremment avec les chiers et dy ajouter quelques options comme celle de prsenter la date et lheure avec une prcision montrant les millisecondes. Voici donc le code Java de la classe Traceur, que nous pourrions prsenter comme un deuxime prototype avant de pouvoir laborer une classe Java ou C++ encore plus professionnelle :
import java.io.*; import java.util.Date; import java.text.DecimalFormat; public class Traceur {

Comment tester correctement ? CHAPITRE 16

345

static static static static

private private private private

int sequence = 0; boolean tousActif = false; String nomFichier = "trace"; PrintWriter traceout;

private boolean instanceActif = false; private String trNom; public Traceur(String name) { trNom = name; if (sequence == 0) { sequence++; try { traceout = new PrintWriter(new FileWriter(nomFichier + sequence + ".trc")); if (sequence == 1) { tousActif = true; } instanceActif = true; } catch(IOException ioe) { if (sequence == 1) { sequence =0; } return; } tousActif = true; } instanceActif = true; } private void commun() { Date date = new Date(); long milli = date.getTime()%1000; if (tousActif && instanceActif) { DecimalFormat df = new DecimalFormat("000"); traceout.print(date + " " + df.format(milli) + " " + trNom + " "); } } public void ecrit(String texte1) { if (tousActif && instanceActif) { commun(); traceout.println(texte1); traceout.flush(); } }

346

Apprendre Java et C++ en parallle

public void ecrit(String texte1, long valeur1) { if (tousActif && instanceActif) { commun(); traceout.println(texte1 + " " + valeur1); traceout.flush(); } }

Le traitement des erreurs est peine plus sophistiqu que la version C++. Il manque encore le code ncessaire pour informer lutilisateur dun problme quelconque. Ce point pourrait tre plus dlicat, car lutilisation de telle classe pour tracer une application a un sens principalement pour des processus dtachs de la console, comme des applications client-serveur. Les quatre variables statiques nous permettent dutiliser les mmes donnes pour plusieurs instances de la classe. la cration de la premire instance de la classe Traceur, nous savons quil faut ouvrir le chier en criture. Cette manire de faire nous permettrait dintroduire de nouvelles mthodes pour fermer le chier et en ouvrir un nouveau, tout en continuant les enregistrements pour les instances existantes. Le chier sera donc toujours nomm trace1.trc, avec le code existant. La mthode commun() nous enregistre la partie xe, avec en plus le nom du traceur actif qui est pass dans le constructeur de la classe. Le calcul des millisecondes est intressant :
long milli = date.getTime()%1000; DecimalFormat df = new DecimalFormat("000"); df.format(milli);

Il faut en effet extraire la partie des millisecondes par le reste de la division par 1 000. Le 000 du DecimalFormat va nous ajouter les 0 ncessaires, si le rsultat est plus petit que 100 ou encore plus petit que 10. Nous aurons donc un alignement de la prsentation plus prcis pour un traitement et une analyse ventuelle. Notre classe TestTraceur reprsente une manire simple et sufsante pour vrier notre code.
import java.lang.Math; public class TestTraceur { public static void main(String[] args) { Traceur tr1 = new Traceur("Premier_traceur"); Traceur tr2 = new Traceur("Second_traceur"); tr1.ecrit("Dbut"); System.out.println("Dbut"); for (long i = 0; i <= 1000000; i++) { Math.sin(Math.cos(Math.tan(i))); if ((i%200000) == 0) { tr2.ecrit("Milieu", i);

Comment tester correctement ? CHAPITRE 16

347

} } System.out.println("Fin"); tr1.ecrit("Fin"); } }

Voici maintenant le rsultat prsent dans le chier trace1.trc :


Tue Tue Tue Tue Tue Tue Tue Tue Jul Jul Jul Jul Jul Jul Jul Jul 08 08 08 08 08 08 08 08 10:56:31 10:56:31 10:56:31 10:56:31 10:56:31 10:56:31 10:56:31 10:56:31 CEST CEST CEST CEST CEST CEST CEST CEST 2008 2008 2008 2008 2008 2008 2008 2008 801 832 848 848 848 848 864 864 Premier_traceur Dbut Second_traceur Milieu Second_traceur Milieu Second_traceur Milieu Second_traceur Milieu Second_traceur Milieu Second_traceur Milieu Premier_traceur Fin 0 200000 400000 600000 800000 1000000

Les trois fonctions mathmatiques ne sont l que pour ralentir le processus. Le traceur du milieu ne sera activ que toutes les 20 000 fois. Sans le caractre = du <= de la boucle for(), nous naurions pas la dernire trace du milieu !

Encore des amliorations pour notre traceur ?


Ce petit exercice pourrait certainement se prolonger jusqu la n de la journe ou de la semaine ! Nous allons nous arrter ici aprs avoir mentionn : la ncessit dy ajouter un plus grand nombre de mthodes ecrit() ; le travail avec plusieurs chiers simultanment, par exemple pour enregistrer sparment les cas normaux et les cas derreurs ; lidentication du processus actif, car plusieurs applications ou plusieurs instances de la mme application pourraient travailler sur le mme chier (dans une version simplie, il sufrait de travailler avec un nom de chier cr dynamiquement et unique) ; lutilisation dune variable denvironnement pour tester si le traceur est actif ou non. Comme ce dernier point sera propos en exercice, nous allons prsenter le code pour tester une variable denvironnement. Comme il nest pas possible de lire, depuis Java, une variable denvironnement gnrale DOS ou Linux, nous sommes obligs de passer cette variable au moyen du paramtre -D la machine virtuelle. Pour une classe de test nomme TraceurETest, ceci peut se faire de cette manire :
java -Dtraceur=actif TraceurETest

Nous aurons donc une variable traceur qui aura la valeur actif. Si nous voulons lire et vrier cette variable, nous devrons utiliser le code suivant :

348

Apprendre Java et C++ en parallle

String traceur_prop = System.getProperty("traceur"); if ((traceur_prop != null) && (traceur_prop.equals("actif"))) { ... }

Cette solution nest cependant pas des plus exibles. Nous pourrions par exemple lire ce paramtre depuis un chier ou encore crer un mcanisme de communication sans devoir stopper et relancer le programme.

Rsum
Avant dcrire les premires lignes de code, le programmeur devra penser la manire de tester ces programmes. Il faudra bien sr vrier chaque module et avoir la possibilit de modier les paramtres et les conditions. Les tests devront pouvoir tre rpts, non seulement durant les phases de dveloppement et dintgration avec les autres parties du systme, mais aussi durant toute la vie du produit. Dautres outils de test devront tre penss pour dpister les erreurs. Ces derniers devront aussi tre disponibles en dehors de lenvironnement traditionnel de dveloppement.

Exercices
1. crire en Java notre fonction C++ extraction() comme mthode dune classe Extraction et tester les diffrents cas derreurs. crire une classe qui automatise les tests (Test Extraction)avec une mthode qui reoit comme paramtre la fois les arguments de la fonction extraction(), mais aussi le rsultat attendu. Lors de lextraction des caractres, au lieu de vrier les limites de lindex, utiliser lexception StringIndexOutOfBounds Exception. 2. crire en Java une classe de test TraceurETest qui possde un attribut de la classe TraceurE. TraceurETest est instanci avec un constructeur qui reoit un String et un int comme arguments. Nous dnirons une mthode avec un entier comme paramtre dont une valeur ngative serait une erreur. Utiliser le TraceurE pour enregistrer toutes les informations. La nouvelle classe TraceurE possdera des mthodes particulires pour enregistrer et identier les erreurs, ainsi que la lecture de la proprit traceur. Le traage ne sera excut que si cette proprit (System.getProperty()) est marque comme active (java D).

17
Ces fameux patterns
Qui sont donc ces fameux patterns ?
Les Design Patterns peuvent tre considrs comme une approche de design avec un catalogue de classes prdnies. Louvrage de rfrence est le fameux Design Patterns dErich Gamma, Richard Helm, Ralph Johnson et John Vlissides, crit en 1995. Cette dnition de vingt-trois solutions pour des problmes classiques rencontrs en programmation SmallTalk, C++ et plus tard Java a constitu une tape importante en analyse et programmation oriente objet. John Vlissides a crit un certain nombre darticles et de discussions quil a inclus dans un autre ouvrage : Pattern Hatching. Dans lintroduction de ce dernier ouvrage, nous trouvons une jolie rexion : Misconception 9 : The pattern community is a clique of elites , qui se trouve dans le neuvime sujet dune liste de dix malentendus ou dides fausses. Mme sil serait injuste de dclarer les utilisateurs de ces patterns comme faisant partie dune lite dingnieurs informaticiens, ladoption de ce catalogue de classes ne peut tre que bnque puisque cette technologie est largement reconnue et utilise en SmallTalk, en C++ et plus rcemment en Java. Un programmeur expriment qui dcouvre cette technologie se rend souvent compte quil a en fait dj utilis un ou plusieurs de ces patterns sans le savoir !

Les patterns Singleton et Observer


Dans notre ouvrage, nous avons choisi deux patterns bien spciques, mais loin dtre inintressants, le Singleton et lObserver. Le tableau 17-1 vous en donne les dnitions.

350

Apprendre Java et C++ en parallle

Tableau 17-1
Intentions
Singleton Observer

Dnitions connatre absolument

Sassurer quune classe na quune seule instance et lui donner un point daccs . Dnir une dpendance entre des objets et assurer que, quand un objet change dtat, toutes ces dpendances seront averties et mises jour automatiquement.

Le Singleton ou le constructeur protg


Comment pouvons-nous interdire la cration de plusieurs instances dune classe ? Il faut dabord protger le constructeur et ensuite dnir une mthode statique qui va crer notre unique instance. Mais quoi donc pourrait bien nous servir le pattern Singleton dans la vie de tous les jours ? Nous pourrions donner lexemple dune unit hardware utilise par plusieurs composants, dun serveur responsable dimprimer et de distribuer le travail, dune servlet Java ou encore dune connexion une base de donnes. Nous allons montrer ici deux classes similaires en Java et C++, mais avec une implmentation diffrente due aux diffrences du langage, trs marques dans ce cas prcis. Nous commencerons par la prsentation du pattern Singleton en Java.

Le Singleton en Java
public class MonSingleton { private static MonSingleton monSingle; private static int compteur; private static int nbAppel; private MonSingleton() { compteur = 1; nbAppel = 0; } public static MonSingleton obtientInstance() { if (monSingle == null) { monSingle = new MonSingleton(); } nbAppel++; return monSingle; } public void augmente() { compteur++; } public String toString() { return "Compteur = " + compteur + " (" + nbAppel + ")"; } }

Ces fameux patterns CHAPITRE 17

351

Le constructeur priv est la partie essentielle de la classe MonSingleton. Il est en effet impossible dutiliser cette construction :
MonSingleton ms = new MonSingleton().

car elle sera rejete par le compilateur. La seule possibilit dobtenir une instance de cette classe est dutiliser une mthode statique comme obtientInstance(). La classe de test TestMonSingleton suivante :
public class TestMonSingleton { public static void main(String args[]) { MonSingleton ms1 = MonSingleton.obtientInstance(); ms1.augmente(); MonSingleton ms2 = MonSingleton.obtientInstance(); ms2.augmente(); System.out.println(ms1); } }

nous retournera le rsultat suivant :


Compteur = 3 (2)

Mais que se passe-t-il prcisment ? Nous navons pas choisi de crer deux objets de la classe MonSingleton, mais avons obtenu une rfrence un objet. ms1 et ms2 reprsentant bien le mme objet. La deuxime valeur, 2, nous indique le nombre de fois que nous avons utilis la mthode obtientInstance(). Le compteur est de 3, car nous avons commenc 1 et appel deux fois la mthode augmente(). Il aurait t tout fait possible de crer linstance de cette manire :
private static MonSingleton monSingle = new MonSingleton();

mais nous avons dcid de ne crer linstance que lors de sa premire utilisation, ce qui est tout fait raisonnable et pratique dans certaines circonstances. Nous allons prsent passer la version C++.

Le Singleton en C++
#include <iostream> #include <sstream> #include <string> using namespace std; class MonSingleton { private: static MonSingleton monSingleton; static int nbAppel;

352

Apprendre Java et C++ en parallle

int compteur; MonSingleton(int le_compteur) : compteur(le_compteur) { nbAppel = 0; } MonSingleton(MonSingleton &); void operator=(MonSingleton &); public: static MonSingleton& obtientInstance() { nbAppel++; return monSingleton; } void augmente() { compteur++; } string toString() { ostringstream sortie; sortie << "Compteur = "; sortie << compteur << " (" << nbAppel << ")" << ends; return sortie.str(); } };

MonSingleton MonSingleton::monSingleton(1); int MonSingleton::nbAppel(1);

int main() { MonSingleton& ms1 = MonSingleton::obtientInstance(); ms1.augmente(); MonSingleton& ms2 = MonSingleton::obtientInstance(); ms2.augmente(); cout << ms1.toString() << endl; }

Le rsultat est identique au programme Java ci-dessus, mais avec quel travail ! Cest vraiment ici que nous voyons la fois les faiblesses et la puissance de chacun de ces deux langages. Il est inutile de philosopher an de savoir pour lequel nous prcherions. Ce qui est intressant ici, cest de dcouvrir, en quelques lignes de code, un concentr dun certain nombre de diffrences entre ces deux langages. Nous commencerons par les deux dclarations :

Ces fameux patterns CHAPITRE 17

353

MonSingleton(MonSingleton &); void operator=(MonSingleton &);

Ces deux lignes de code ainsi que le constructeur normal :


MonSingleton(int le_compteur)

sont tous les trois dclars privs. Nous empchons alors toutes les formes possibles de cration dobjets ou de copie de la classe MonSingleton. Puisque nous avons dni au moins un constructeur, il ny aura pas de constructeur par dfaut. Nous avons aussi les deux :
MonSingleton MonSingleton::monSingleton(1); int MonSingleton::nbAppel(1);

qui nous initialisent les parties statiques de lobjet. Vive le C++ ! Dans le cas dun Singleton responsable dimprimer des documents, nous pourrions nous imaginer quil contrle une srie dimprimantes, distribuant le travail, mais aussi dsactivant les appareils hors service ou les mettant en veille pour la nuit. Le Singleton devrait alors grer les ressources dune manire centralise.

Le pattern Observer
Java MVC : linterface Observer et la classe Observable
Avant de passer dune manire tout fait naturelle larchitecture MVC (Model View Controller), nous allons redonner la dnition dintention du pattern Observer : Dnir une dpendance de un plusieurs objets et, lorsquun objet change dtat, assurer que toutes ces dpendances seront averties et mises jour automatiquement. Prenons lexemple dun compteur. Il fait partie dun modle (le M de MVC), qui correspond en gnral un ensemble de donnes. Si le modle change, cest--dire ici la valeur du compteur, nous aimerions que toutes les vues (les views ou les reprsentations, le V de MVC) soient averties du changement et apparaissent avec la nouvelle valeur. Ce compteur peut aussi faire partie dun ensemble de valeurs qui sont utilises pour reprsenter un graphique. Ce graphique peut trs bien exister sous diffrentes formes et en mme temps sur lcran ou encore des endroits diffrents sur le rseau. Il nous reste encore le C de MVC, le contrleur. Il reprsente par exemple le clic sur un objet graphique pour augmenter ou diminuer la valeur de notre compteur. Cela pourrait tre aussi lentre complte dune nouvelle valeur dans un champ lcran. Larchitecture MVC est essentielle et fait partie de la dcomposition traditionnelle dune application en programmation Smalltalk. Il ne faut pas oublier ici que certains afrment que Java hrite plus de Smalltalk que de C++. Cest certainement juste au niveau des concepts orients objet, mais absolument pas pour la syntaxe du langage. Larchitecture MVC est aussi tout fait prsente en Java avec linterface Observer et la classe Observable. Nous noterons ici que les dveloppeurs Java de chez Sun Microsystems

354

Apprendre Java et C++ en parallle

ont pris quelques liberts, certainement avec raison, en intgrant et en rassemblant le View et le Controller pour des raisons de simplication et defcacit.

Le pattern Observer en Java


Nous allons maintenant prsenter notre exemple de compteur ; nous le souhaitons observable par dautres objets, an que ces derniers soient avertis lorsquil change de valeur. Nous commencerons par la classe CompteurObservable, qui, comme son nom lindique, hrite de la classe Java Observable :
import java.util.Observable; public class CompteurObservable extends Observable { private int compteur; public CompteurObservable(int initiale) { compteur = initiale; } public int getValue() { return compteur; } public void augmente() { compteur++; setChanged(); notifyObservers(); } }

Le constructeur devra recevoir une valeur initiale du compteur. La mthode augmente() na rien de particulier, si ce nest que deux mthodes de sa classe de base seront appeles : setChanged() et notifyObservers(). Ces deux mthodes sont ncessaires pour que les objets dpendant de CompteurObservable puissent tre avertis dun changement dtat. Si nous regardons de plus prs la classe Observable, nous allons y dcouvrir la mthode addObserver(Observer o). Il y a donc un mcanisme pour ajouter des observateurs aux objets de la classe Observable. Nous allons crer prsent un objet graphique ctif, que nous allons appeler Compteur
Graphique :
import java.util.*; public class CompteurGraphique implements Observer { CompteurObservable compteur; int forme; public CompteurGraphique(CompteurObservable leCompteur, int uneForme) { forme = uneForme; compteur = leCompteur;

Ces fameux patterns CHAPITRE 17

355

compteur.addObserver(this); } public void update(Observable ob, Object arg) { System.out.print("Montre le compteur " + compteur.getValue()); System.out.println(" dans la forme " + forme); } }

Lattribut forme nest ici que pour identier quel objet est actif. Cependant, nous pourrions lutiliser pour dnir diffrentes formes graphiques la prsentation de notre compteur. Une autre classe aurait aussi t une alternative. Limportant ici est que la classe reoit une rfrence une instance de CompteurObservable. Aprs lavoir conserve dans la variable compteur, nous appelons la mthode addObserver() avant dajouter un observateur de plus pour cet objet. La mthode update() de linterface Observer doit tre code et prsente. Sil se passe un changement dtat de lobjet compteur, nous sortirons un message titre de test. Nous ne reviendrons pas sur les paramtres de la mthode update(), qui ne sont pas utiliss ici. Pour nir, nous crirons une classe de test :
public class TestCompteur { public static void main(String[] args) { CompteurObservable compteur = new CompteurObservable(50); CompteurGraphique dessin1 = new CompteurGraphique(compteur, 1); CompteurGraphique dessin2 = new CompteurGraphique(compteur, 2); compteur.augmente(); } }

dont le rsultat est attendu :


Montre le compteur 51 dans la forme 2 Montre le compteur 51 dans la forme 1

En effet, les deux objets (Views) dessin1 et dessin2 sont notis, si nous augmentons le compteur (Model).

Rsum
Ce chapitre peut tre considr comme une ouverture desprit diverses technologies orientes objet. Nous esprons que les lecteurs feront dautres dcouvertes dans ces catalogues de classes que sont les Design Patterns.

356

Apprendre Java et C++ en parallle

Exercices
1. Rcrire la classe MonSingleton en C++ avec une allocation dynamique de monSingleton (oprateur new) et en utilisant des pointeurs pour les objets. Nous ne nous poserons pas la question de lutilit dun destructeur dans ce cas prcis. 2. crire en Java une classe Singleton nomme MonIP qui va conserver ladresse IP de la machine. Cette adresse est dynamique et peut changer. crire deux classes Application1 et Application2 qui devront tre noties lorsque le numro dIP change. Nous utiliserons pour cela le pattern Observer. Les constructeurs dApplication1 et dApplication2 ne reoivent pas de paramtres. Crer deux instances de ces dernires, changer ladresse IP au travers dune mthode de la classe MonIP et vrier que le mcanisme fonctionne correctement.

18
Un livre sur Java sans lAWT !
Nous trouverons dans ce chapitre toutes les motivations qui nous ont entrans prsenter tout de mme ces composants dinterface graphique. Comme ceux-ci sont fortement dpendants de la machine, de la carte graphique ou du systme dexploitation, nous comprendrons que de tels outils et bibliothques ne soient pas disponibles pour des applications C++. Les programmeurs Java et C++ dsirant dvelopper des applications GUI (Graphical User Interface) doivent en principe se tourner vers des outils tels que Visual C++ (Microsoft) ou NetBeans (voir annexe E). Microsoft met aussi disposition des ditions Express de Visual Studio pour le C++ et le C#, qui permettent de dvelopper des applications graphiques dans ces deux langages mais pour Windows uniquement. LAWT (Abstract Window Toolkit) de Java permet de dvelopper non seulement des applications graphiques traditionnelles, mais aussi des applets qui sont excutes lintrieur de pages HTML et ceci dans notre navigateur Internet favori. Au chapitre 21, nous montrerons aussi un exemple dapplication Java qui utilise les composants Swing. Ces derniers reprsentent une extension amliore et plus rcente des composants AWT. Nous pouvons faire ici un paralllisme avec les classes MFC (Microsoft Foundation Classes), qui prsentent une analogie avec Swing, de la programmation GUI C++ sous Windows avec une approche plus oriente objet. Nous aurions pu laisser de ct les composants AWT et faire la mme dmarche avec les composants Swing. Cependant, ces derniers ne sont pas des plus rapides sur des machines anciennes avec peu de ressources CPU ou mmoire. Pour ceux qui sintressent lhistoire du dveloppement de Java, une analyse de lvolution de lAWT depuis son origine est particulirement intressante. Certains aspects de limplmentation ont t totalement revus et corrigs dans les dernires versions du langage Java. Nous ne donnerons pas plus de dtails, sinon que nous ne parlerons ici que de la version 1.2 ou suprieure. Pour les programmeurs utilisant de lancien code Java,

358

Apprendre Java et C++ en parallle

nous conseillerons de compiler les programmes avec loption -deprecation, an didentier les mthodes et techniques dprcies. Nous encouragerons aussi lutilisation systmatique des classes internes (inner class). Pour les utilisateurs de Crimson, il faudra se rfrer lannexe C pour le problme reli la Capture output .

Apprendre programmer avec des applications graphiques


Si nous examinons cette application graphique dont nous allons trs rapidement tudier le code en Java :

Figure 18-1

Notre premier compteur

nous savons quelle pourrait trs bien tre dveloppe par un grand nombre de langages de programmation comme Visual Basic de Microsoft, Delphi (le Pascal de Borland), ou encore Visual C++ ou C# de Microsoft. Dans cette application, le seul vrai problme concerne la tasse de caf. En effet, si nous voulions lcrire en Visual Basic, ce serait certainement la seule difcult que nous pourrions rencontrer : dessiner cette tasse et cet endroit. Pour le reste, crer un bouton et une tiquette en Visual Basic, est la porte dun dbutant : chaque fois que nous cliquons sur le bouton, nous augmentons un compteur et prsentons un message rafrachi avec la nouvelle valeur. Lapproche visuelle de ces outils est essentielle, mais le code gnr automatiquement napportera rien aux dbutants qui dsirent tudier et comprendre les bases du langage. Le seul avantage certain de ces programmes de dveloppement est leurs outils de dbogage et leur documentation intgre. Ici, pour pouvoir comprendre et tendre les exemples de ce chapitre, il faudra consulter la documentation des composants AWT qui est livre avec le JDK de Sun Microsystems (voir annexe B) et disponible dans le format HTML.

Le code de notre premire application AWT


Voici donc prsent le code en Java de lapplication que nous venons de prsenter, et que nous avons cre la main avec un diteur traditionnel. Les applications gnres par des outils comme NetBeans (voir annexe E), qui nous permettent de placer nos composants

Un livre sur Java sans lAWT ! CHAPITRE 18

359

sur une feuille vide, ne gnreront pas un code aussi simple et dnu de toutes oritures. Ils utiliseront sans doute leurs propres classes, principalement dans le but de positionner correctement les diffrents composants dans la fentre. Dans le code qui suit :
import java.awt.*; import java.awt.event.*; public class JuniorAnonymFrame extends Frame { private Button bouton; private Label etiquette; private int compteur; public JuniorAnonymFrame() { super("Notre compteur"); compteur = 0; setLayout(new FlowLayout()); bouton = new Button("Compteur"); bouton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { compteur++; String texte = new String("Le compteur est " + compteur); etiquette.setText(texte); } } ); etiquette = new Label("Le compteur est 0 "); add(bouton); add(etiquette); }

public static void main(String args[]) { JuniorAnonymFrame f = new JuniorAnonymFrame(); f.setSize(200,100); f.setVisible(true); f.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } } ); } }

lentre main() de notre programme va nous crer une instance de notre classe JuniorAnonym Frame qui hrite de Frame, la classe essentielle pour nous offrir notre premire application GUI. Les mthodes setSize() et setVisible() sont traditionnelles. Les valeurs de 200 et 100 ont t positionnes aprs quelques tentatives, qui dpendent de la prsentation choisie (setLayout(new FlowLayout())) et des composants insrs avec la mthode add(). Il y a

360

Apprendre Java et C++ en parallle

donc deux lments, un bouton et une tiquette. Ltiquette nest jamais active, mais sera modie avec setText() chaque fois que le compteur est augment par un clic sur le bouton. Nous avons nomm ce dernier Compteur. Il y a deux lments actifs, le bouton et la fentre Windows. Cette dernire demande des explications. La rponse est vidente lorsque nous comprenons quil faut traiter la sortie de lapplication Windows. Celle-ci peut tre excute avec la petite croix en haut droite du cadre de la fentre. Nous allons comprendre rapidement comment traiter ces vnements.

Classes anonymes et classes internes


Les concepts de classes anonymes et de classes internes sont essentiels en programmation Java. Nous allons trs rapidement en comprendre le mcanisme en analysant le code extrait de notre prcdente application. La mthode addWindowListener(), applique sur lobjet f de la classe JuniorAnonymFrame, hrite de Frame, o cette mthode est effectivement dnie ; elle doit recevoir une instance dune interface WindowAdapter. Cela peut sembler bien compliqu pour un programmeur junior , auquel nous dirions plutt : nous allons coller la fentre un outil qui va traiter notre clic de souris, an dappeler la mthode statique System.exit() pour quitter le programme.
f.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } } );

En fait, en regardant ce code, nous nous rendons compte que linstance de linterface WindowAdapter, dont nous devons produire le code, puisque cest une interface, cest-dire une dnition et non une classe, se trouve directement entre les deux parenthses de la mthode addWindowListener(). Cest ce que nous appelons une classe anonyme. En ce qui concerne le bouton, nous suivons pour la mthode addActionListener() la mme dmarche :
bouton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { compteur++; String texte = new String("Le compteur est " + compteur); etiquette.setText(texte); } } );

Nous remarquons que le code de la classe anonyme peut accder aux attributs compteur et etiquette. chaque clic, nous augmenterons donc le compteur et le prsenterons comme nouveau texte de ltiquette avec la mthode setText(). Ltape suivante est la transformation de ces classes anonymes en classes internes, dont nous allons prsenter tout aussi directement le code :

Un livre sur Java sans lAWT ! CHAPITRE 18

361

import java.awt.*; import java.awt.event.*; public class JuniorInternFrame extends Frame { private Button bouton; private Label etiquette; private int compteur; public JuniorInternFrame() { compteur = 0; setLayout(new FlowLayout()); bouton = new Button("Compteur"); bouton.addActionListener(new EvtCompte()); etiquette = new Label("Le compteur est 0 "); add(bouton); add(etiquette); setBackground(Color.lightGray); } class EvtCompte implements ActionListener { public void actionPerformed(ActionEvent e) { compteur++; String texte = new String("Le compteur est " + compteur); etiquette.setText(texte); } } static class EvtQuit extends WindowAdapter { public void windowClosing(WindowEvent e) { System.exit(0); } } public static void main(String args[]) { JuniorInternFrame f = new JuniorInternFrame(); f.setSize(200,100); f.setVisible(true); f.addWindowListener(new EvtQuit()); } }

Nous voyons prsent lapparition de ces fameuses classes internes qui font la joie des programmeurs Java. Si nous examinons les chiers produits par la compilation de Junior InternFrame.java, nous serons peut-tre surpris :

362

Apprendre Java et C++ en parallle

JuniorInternFrame.class JuniorInternFrame$EvtCompte.class JuniorInternFrame$EvtQuit.class

Mais nous avions dj ceci dans la version JuniorAnonymFrame.java avec des classes anonymes :
JuniorAnonymFrame.class JuniorAnonymFrame$1.class JuniorAnonymFrame$2.class

Aprs le $ qui suit le nom de la classe, JuniorInternFrame, nous voyons apparatre le nom de la classe interne, cest--dire EvtCompte et EvtQuit. Dans le cas de classes anonymes, sans nom, le compilateur Java va simplement allouer un numro. Ici, nous avons 1 et 2 pour la classe JuniorAnonymFrame. Une analyse des chiers .class nous donne ainsi une ide sur la structure interne des classes compiles.

Sadapter aux vnements traditionnels de lAPI


Dans le cas de la fentre, nous utilisons la mthode addWindowListener(), et pour le bouton, nous avons addActionListener(). Par vnements traditionnels, nous entendons les actions simples, telles quun clic sur un bouton. Ces couteurs sont ncessaires pour permettre notre code dobtenir le contrle lorsquun vnement se produit. Dans le cas de la fentre, si nous oublions de traiter lvnement qui veut fermer lapplication, nous ne pourrons simplement jamais quitter le programme. Pour tous les composants que nous utilisons, nous devons consulter la documentation de lAPI, qui va nous indiquer quelle mthode est disponible et quelle interface doit tre utilise. Ds ce moment-l, le compilateur nous force implmenter le code ncessaire et choisir lopration excuter. Si nous voulions oublier addWindowListener() dans le cadre principal de lapplication, il nous faudrait alors introduire un deuxime bouton pour quitter avec notre System.exit() ou bien dcider de sortir, aprs une srie de clics sur notre bouton unique. Il est essentiel de constater que si lutilisateur ne bouge pas, il ne va absolument rien se passer ! Si nous voulions alors quitter le programme de toute manire, il faudrait introduire un timer, cest--dire une horloge interne ! Il nous faut revenir sur la partie essentielle du code, qui est certainement plus lisible que la classe JuniorAnonymFrame, puisque le code du traitement des vnements se fait en dehors du constructeur :
public JuniorInternFrame() { compteur = 0; setLayout(new FlowLayout()); bouton = new Button("Compteur"); bouton.addActionListener(new EvtCompte()); etiquette = new Label("Le compteur est 0 "); add(bouton);

Un livre sur Java sans lAWT ! CHAPITRE 18

363

add(etiquette); setBackground(Color.lightGray); }

La premire mthode intressante est la mise en place du gestionnaire de mise en forme, setLayout(). LAWT possde de nombreux gestionnaires pour placer les lments sur le cadre de lapplication. Nous ne donnerons pas plus de dtails, sinon quil est aussi possible dinclure plusieurs panneaux sur le cadre principal, ceux-ci pouvant avoir chacun un gestionnaire diffrent. Les deux classes Button et Label reoivent un texte comme paramtre de leurs constructeurs respectifs. Comme elles hritent toutes les deux de la classe java.awt.Component, elles peuvent tre ajoutes au cadre principal avec la mthode add(). Cette dernire est dnie dans la classe java.awt.Container, classe de base de java.awt.Window, elle-mme classe de base de java.awt.Frame. Le positionnement des lments dans le cadre de la fentre dpendra du gestionnaire de mise en forme choisi, de la longueur des textes passs aux constructeurs, ainsi que de la dimension de la fentre choisie avec setSize() dans main() pour lobjet f de notre classe de JuniorInternFrame. Enn, il est possible de changer la couleur de fond, le gris clair, avec setBackground() et la couleur Color.lightGray, qui est statique. Cest sans doute le bon moment pour indiquer que JuniorInternFrame hrite de Frame, qui hrite de Window, qui hrite de Container, qui hrite nalement de Component et dObject. Lorsquil sagit de retrouver une mthode dans cette hirarchie, une vritable fort, une chatte ny retrouverait plus ces petits ! Les mthodes setSize() et setBackground() sont dnies dans la classe Component. Notre bouton est aussi driv de Component et linstruction :
bouton.setBackground(Color.red);

aurait attribu un rouge ptant larrire-plan de notre bouton, ce qui naurait pas t des plus prsentables.

Et si on sadaptait dautres types dvnements ?


Dans la version qui suit, nous allons effectivement nous adapter dautres vnements ! Il est possible dajouter une coute un autre type dvnement, an de recevoir, par exemple, une notication si nous nous dplaons sur un composant. Nous pourrions ajouter dautres actions, par exemple au moment o nous pressons ou lchons le bouton de la souris. Dans lexemple qui suit, nous allons contrler le moment o nous entrons et sortons de la zone du bouton Compteur :
import java.awt.*; import java.awt.event.*; public class JuniorFrame extends Frame { private Button bouton; private Label etiquette; private int compteur; public JuniorFrame() {

364

Apprendre Java et C++ en parallle

compteur = 0; setLayout(new BorderLayout()); bouton = new Button("Compteur"); bouton.addActionListener(new EvtCompte()); bouton.addMouseListener(new EvtMouse()); etiquette = new Label("Le compteur est 0"); etiquette.setAlignment(Label.CENTER); add(bouton, BorderLayout.CENTER); add(etiquette, BorderLayout.SOUTH); setBackground(Color.lightGray); } private void nouveauTexte(String texte) { etiquette.setText(texte); } class EvtCompte implements ActionListener { public void actionPerformed(ActionEvent e) { compteur++; nouveauTexte("Le compteur est " + compteur); } } class EvtMouse implements MouseListener { public void mouseEntered(MouseEvent e) { nouveauTexte("Clique sur le bouton pour augmenter le compteur"); } public void mouseExited(MouseEvent e) { nouveauTexte("Le compteur est " + compteur + " "); } public void mouseClicked(MouseEvent e) { } public void mousePressed(MouseEvent e) { } public void mouseReleased(MouseEvent e) { } } public static void main(String args[]) { JuniorFrame f = new JuniorFrame(); f.setSize(300, 100); f.setVisible(true); f.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } } ); } }

Un livre sur Java sans lAWT ! CHAPITRE 18

365

Notre nouvelle application va apparatre comme sur la gure 18-2.

Figure 18-2

Lapplication compteur

Deux ou trois modications sont apparues dans notre application. Le titre dabord, Notre compteur, qui vient de lappel de la mthode super() dans le constructeur de notre nouvelle classe JuniorFrame. Le texte Clique sur le bouton pour augmenter le compteur va apparatre et disparatre lorsque nous entrons dans la zone, trop grande dailleurs, de notre bouton Compteur. Ceci peut se faire grce lajout dun autre gestionnaire dvnements, addMouse Listener(), grce auquel nous allons pouvoir recevoir une notication lentre et la sortie de cette zone. Nous avons alors ajout une nouvelle classe interne, EvtMouse, qui va dnir les cinq implmentations de mthodes requises pour linterface MouseListener. Seules deux sont intressantes pour nos besoins, mouseEntered() et mouseExited(), dans lesquelles nous avons utilis une nouvelle mthode publique nouveauTexte() qui va nous accomplir notre code rptitif, consistant changer le texte dans ltiquette. Nous avons choisi un autre gestionnaire de mise en forme, le BorderLayout, qui nous donne un bouton beaucoup trop large. Nous ne reviendrons pas trop sur les dtails de :
add(bouton, BorderLayout.CENTER); add(etiquette, BorderLayout.SOUTH);

qui nous positionne le bouton au centre et au sud du cadre ainsi que de :


etiquette.setAlignment(Label.CENTER);

qui nous centre ltiquette.

Applets ou applications
Le code prcdent nous a permis de nous familiariser avec lcriture dune application GUI, lutilisation de lAWT et du traitement des vnements. Mais quen est-il des applets ? Une applet est un programme Java qui sera excut lintrieur dun document HTML, cest--dire partir de Netscape ou dInternet Explorer. Nous allons mieux le comprendre en examinant ce document HTML, qui contient une rfrence une applet pour une classe nomme JuniorApplet, que nous allons examiner plus loin. Voici donc le code source du chier :
<HTML> <TITLE>Mon compteur</TITRE> <BODY>

366

Apprendre Java et C++ en parallle

<h1>Mon applet Junior</h1> <hr> <applet code=JuniorApplet.class width=450 height=100></applet> <hr> </BODY> </HTML>

La balise <applet> indique que le navigateur devra charger le code Java compil partir de sa machine virtuelle intgre. Il y a aussi les deux paramtres width et height qui correspondent la largeur et la hauteur de lapplet dans le document HTML. Il ne faut pas oublier que dautres balises HTML peuvent tre prsentes et que les valeurs que nous avons choisies, 450 et 100, ncessitent quelquefois des ajustements. Il ne faut pas oublier non plus quune fentre du navigateur peut tre redimensionne et peut parfois donner quelques surprises, surtout si celle-ci devient trop petite. prsent, nous allons examiner une dclinaison de notre classe JuniorFrame, que nous allons transformer en applet. La caractristique intressante de cette classe JuniorApplet est quelle est construite de telle manire quelle peut tre la fois utilise comme applet et comme application :
import java.applet.*; import java.awt.*; import java.awt.event.*; public class JuniorApplet extends Applet { private Button bouton; private Label etiquette; private int compteur; public void init() { compteur = 0; setLayout(new FlowLayout()); setFont(new Font("Courier New", Font.BOLD, 18)); bouton = new Button("Compteur"); bouton.addActionListener(new EvtCompte()); bouton.addMouseListener(new EvtMouse()); etiquette = new Label("Clique sur le bouton pour augmenter le compteur"); etiquette.setAlignment(Label.CENTER); add(bouton); add(etiquette); setBackground(Color.lightGray); }

Un livre sur Java sans lAWT ! CHAPITRE 18

367

private void nouveauTexte(String texte) { etiquette.setText(texte); } class EvtCompte implements ActionListener { public void actionPerformed(ActionEvent e) { compteur++; nouveauTexte("Le compteur est " + compteur); } } class EvtMouse implements MouseListener { public void mouseEntered(MouseEvent e) { nouveauTexte("Clique sur le bouton pour augmenter le compteur"); } public void mouseExited(MouseEvent e) { nouveauTexte("Le compteur est " + compteur + " "); } public void mouseClicked(MouseEvent e) { } public void mousePressed(MouseEvent e) { } public void mouseReleased(MouseEvent e) { } } public static void main(String args[]) { JuniorApplet applet = new JuniorApplet(); Frame f = new Frame("JuniorApplet"); f.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } } ); f.setSize(500, 120); f.add(applet); applet.init(); applet.start(); f.setVisible(true); } }

Cette nouvelle classe, JuniorApplet, hrite de la classe Applet dnie dans :


import java applet.*;

et na pas les mmes caractristiques ni constructeurs que la classe Frame. Par exemple, il nest pas possible dinitialiser des paramtres dans la classe de base avec super().

368

Apprendre Java et C++ en parallle

Avant de passer aux autres dtails, il nous faut prsenter appletviewer, cet outil distribu avec le JDK de Sun Microsystems. Si nous excutons la commande dans le rpertoire C:\JavaCpp\EXEMPLES\Chap18 et dans une fentre DOS :
appletviewer junior.html

nous obtiendrons ceci :

Figure 18-3

Lapplet compteur

appletviewer est un outil trs pratique pour tester nos applets en lieu et place dun navigateur

Web traditionnel. Au contraire de Netscape ou dInternet Explorer, il ne montre pas le code HTML, comme ici le titre H1. Il possde cependant un certain nombre doutils dans son menu, pour, par exemple, arrter ou relancer lapplet. Cest aussi le bon moment pour faire remarquer que notre applet est situe sur notre machine, cest--dire sur notre disque. Dans la ralit, cest diffrent. Les pages HTML ainsi que les applets viennent de lextrieur, du site Web o est enregistr tout le matriel ncessaire, dont font partie par exemple les images ou les fonds dcran. Comme lapplet vient de lextrieur et sera excute sur le PC hte, nous pourrions nous imaginer quelle pourrait crire des chiers ou effacer volontairement des ressources qui ne lui appartiennent pas. Nous pouvons faire lexercice suivant aprs avoir ajout :
import java.io.*;

en dbut de chier. Ensuite, nous introduisons le code suivant :


try { PrintWriter out = new PrintWriter(new FileWriter("test.txt")); out.println("Salut"); out.close(); } catch(IOException ioe) { return; }

aprs la ligne :
nouveauTexte("Le compteur est " + compteur);

Un livre sur Java sans lAWT ! CHAPITRE 18

369

Cela veut dire que chaque fois que nous activons le bouton, le chier test.txt sera crit sur le disque. nouveau depuis le rpertoire de travail C:\JavaCpp\EXEMPLES\Chap18 et dans une fentre DOS , nous pouvons le vrier avec :
java JuniorApplet

qui nous gnrera effectivement un chier. Cependant, si nous excutons :


appletviewer junior.html

et si nous actionnons le bouton, nous aurons une surprise :


Exception occurred during event dispatching: java.security.AccessControlException: access denied (java.io.FilePermission fichier.txt write) at java.security.AccessControlContext.checkPermission(Comp..) at java.security.AccessController.checkPermission(Compiled Code) at java.lang.SecurityManager.checkPermission(Compiled Code) at java.lang.SecurityManager.checkWrite(Compiled Code) at java.io.FileOutputStream.<init>(Compiled Code) at java.io.FileWriter.<init>(Compiled Code) at JuniorApplet$EvtCompte.actionPerformed(Compiled Code) at java.awt.Button.processActionEvent(Compiled Code)

Il y a donc un mcanisme de protection. Sans ce dernier, les applets nauraient jamais pu survivre dans la tempte du dveloppement technologique de ces dernires annes. En dautres mots, si un programmeur veut utiliser Java pour construire des outils systme, il ne pourra pas utiliser dapplet.

init() et start() pour une applet


Il faut videmment revenir sur ce code essentiel. Dans notre premire classe JuniorFrame, nous avions plac tout le code dinitialisation dans le constructeur. Pour JuniorApplet, cest diffrent, et ce code fait partie de la mthode init(). Cette manire de faire est ncessaire, car la mthode init() sera appele la premire fois, et seulement une fois, lorsque lapplet sera dmarre. La classe Applet possde une mthode start() qui peut tre rednie en cas de ncessit. Chaque fois que le navigateur revient une page contenant une applet, la mthode start() est active, ce qui nest pas le cas de la mthode init(). En passant, nous noterons quil existe aussi une mthode stop() qui pourrait tre utilise pour arrter une activit, par exemple une animation. Le reste du code de la classe JuniorApplet, pour le traitement de la souris et des vnements, est similaire notre classe JuniorFrame prcdente. Enn, il nous faut revenir au code de la partie main(). Cest l que nous trouvons la fameuse astuce qui consiste inclure un objet de la classe Applet dans un Frame. Linstruction :
f.add(applet);

370

Apprendre Java et C++ en parallle

va dposer lapplet lintrieur de linstance f de Frame, qui est capable de fonctionner comme application. Les deux instructions suivantes :
applet.init(); applet.start():

correspondent la discussion prcdente pour dmarrer lapplet. La classe JuniorApplet pourra donc fonctionner la fois comme applet et comme application.

Un mot sur les servlets


Les servlets fonctionnent plus ou moins comme des applets, mais sont des applications Java qui tournent sur un serveur HTTP supportant cette technologie. Les servlets sont quivalentes aux scripts CGI, trs souvent crites en Perl, en PHP4, mais aussi en C et en C++. Elles sont capables, par exemple, de traiter des feuilles de requtes (FORM) en HTML et de gnrer du code HTML dynamique. Pour se familiariser avec cette technologie, on peut recommander de tlcharger le kit de Sun Microsystems :
http://java.sun.com/products/jsp/ http://java.sun.com/products/servlet/

qui permettra de dvelopper de petites applications servlets et de les excuter sur son PC sans la ncessit de se connecter Internet. Dans lannexe E, nous prsenterons NetBeans avec lequel nous pourrions dvelopper des servlets et les tester avec un serveur Web Apache Tomcat intgr.

get(), set() et les JavaBeans


En consultant par exemple la documentation de java.awt.Component, nous constaterons les nombreuses mthodes qui sont marques deprecated, cest--dire qui ne devraient plus tre utilises. Nous avons entre autres la mthode size(), qui nous retourne la dimension du composant AWT :
public Dimension size();

Eh bien, celle-ci devrait tre remplace par la mthode getSize(), qui retournera une Dimension.
public Dimension getSize();

En fait, il doit exister une variable prive nomme size, laquelle nous mettons une majuscule pour la premire lettre et ajoutons un get() ou un set() ; cest la manire Beans.

Un livre sur Java sans lAWT ! CHAPITRE 18

371

public void setSize(int width, int height)

Cest un peu lhistoire des Beans rien voir avec les petites histoires drles ou les manires de Mr Bean !

Quest-ce quun Bean ?


Une des origines des Beans se trouve certainement dans une inspiration venue des composants de deuxime gnration, qui sont apparus dans des outils de programmation comme Delphi de Borland. Lide est de promouvoir une technique qui permet de crer un bloc de code avec un certain standard, an quun outil puisse dcouvrir par lui-mme les proprits et les vnements pour ce composant. Nous nirons pas plus loin sur le sujet, qui dpasse encore une fois les buts xs pour cet ouvrage. Nous mentionnerons aussi lexistence de la classe Beans, qui fait partie du paquet java.beans, o nous trouvons par exemple une interface essentielle comme BeanInfo.

Beans et C++
Il ny a pas de Beans en C++, mais rien ne nous empcherait de dnir le mme standard pour accder aux variables prives dune classe, comme dans cet exemple :
class UnBean { public: int getAttribut() { return attribut; } void setAttribut(int valeur) { attribut = valeur; } private: int attribut; };

Rsum
Cet aperu de lAWT, les composants graphiques de Java, tait ncessaire. Il nous a permis de nous familiariser avec les classes internes et anonymes en Java. La diversion sur les jeunes programmeurs tait aussi une manire de prsenter lAWT comme un dpart possible en programmation. Nous savons prsent diffrencier une applet dune application et mme les combiner.

372

Apprendre Java et C++ en parallle

Exercice
Une application AWT un peu plus volue.
Note Le lecteur ne possdera pas toutes les bases pour faire ce travail. Il devra consulter la documentation de lAWT et de ses nombreuses classes, interfaces et mthodes de composants.

Reprendre la classe JuniorApplet et lui apporter les modications suivantes : Ajouter un bouton Mise zro pour remettre le compteur 0. Ajouter un bouton Quitte, mais seulement pour une application (Frame). Ce bouton napparatra pas pour une applet. Les deux ou trois boutons seront inclus dans un panneau (Panel) avec une prsentation FlowLayout. Le panneau et ltiquette utiliseront une prsentation BorderLayout lintrieur de lapplet, et ceci avec une orientation nord-sud. Ces deux dernires sont des paramtres de la mthode add(). Nous dnirons trois ActionListeners pour nos boutons, mais le MouseListeners sera commun. Pour identier le bouton concern, il faudra utiliser la mthode getSource() sur lvnement. Il sera alors ncessaire de dnir les trois boutons comme des attributs de la classe et non comme des variables de la mthode init(). Nous nous amuserons avec diffrents types de caractres et polices, comme le gras ou litalique. La mthode setFont() sur chaque composant devra tre utilise.

19
Un livre sur C++ sans templates !
Les modles ou patrons en C++
Ce chapitre est spcique au C++. Il serait inconvenant de laisser de ct un aspect aussi essentiel que les modles en C++. Dans une certaine mesure, ils peuvent tre compars aux types gnriques de Java, apparus partir du JDK 1.5, et que nous avons dj traits au chapitre 14. Le terme de modle va de pair avec le concept de programmation gnrique. Il est en effet possible de programmer ces classes et ces algorithmes pour plusieurs varits de types ou dobjets. Cette technique permet avant tout dliminer des centaines, voire des milliers de lignes de code qui vont pouvoir sappliquer sur diffrents types dobjets. Matriser et comprendre ce concept et ce style dcriture est sans doute un bagage supplmentaire intressant pour les programmeurs de classes et dinterfaces en Java. Nous ne pourrions parler des modles sans mentionner la bibliothque STL (Standard Template Library). Apparue ces dernires annes, elle sera toujours plus utilise. Un programmeur C++ expriment qui dbute en Java pourra avoir quelques difcults lorsquil devra, par exemple, convertir en Java du code contenant des classes de la bibliothque STL, ainsi que des algorithmes gnriques, an dy retrouver rapidement lquivalence et la mme efcacit. Nous ne reviendrons pas sur les nombreux algorithmes du Standard C++. Nous en avons dj utilis quelques-uns au chapitre 14 avec les collections. Algorithmes et modles sont en fait indissociables.

374

Apprendre Java et C++ en parallle

Un modle de fonction
Les programmeurs dbutants sont souvent effrays par la syntaxe des modles. Il est parfois ncessaire de sy reprendre plusieurs fois avant de compiler avec succs son premier programme. La syntaxe tant relativement lourde, le programmeur aura souvent la tentation de bcler son code, sans vraiment faire leffort den comprendre la forme. Aprs quelques tentatives et exercices, il se rendra nalement compte, certainement avec tonnement, que ce ntait pas la mer boire. Comme premier exemple, nous allons dnir une fonction gnrique qui nous retourne soit le rsultat de la somme de deux objets sils sont diffrents, soit, sils sont identiques, un seul de ces deux objets. Si nous pensons des nombres, tels que 10 et 11 ou 20 et 20, nous nen voyons pas vraiment lutilit ! En revanche, dans une suite de mots tels que la maison ou le le, nous en verrions plus facilement lapplication dans un traitement de texte dans lequel on pourrait demander conrmation avant deffacer des mots rpts. Avant de passer lcriture de la fonction gnrique, qui devrait fonctionner sur toutes sortes dobjets, nous devons nous poser un certain nombre de questions. Cette mthode va utiliser les oprateurs == et +. Il faut donc que ces oprateurs existent, ceci pour les objets dont nous aimerions crer une instance pour cette fonction gnrique. Nous allons voir prsent ces dtails, ainsi que la syntaxe des modles dans notre premier exemple :
// unefonction.cpp #include <iostream> using namespace std; template<class T> T somme(const T& obj1, const T& obj2) { if (obj1 == obj2) return obj1; return obj1 + obj2; } int main() { cout << somme<int>(10, 11) << endl; cout << somme<int>(20, 20) << endl; cout << somme<double>(2.2, 7.8) << endl; cout << somme<string>("Hello", "Bonjour") << endl; return 0; }

Le prxe template<class T> indique le dbut dune dclaration pour un modle de type T. Ce dernier peut tre de type primitif comme char, int ou double ou encore le nom dune classe standard, comme string ou autres classes propritaires. Le reste de la dnition est pareil la dclaration dune mthode normale. Si nous voulions avoir une fonction somme() uniquement valable pour des string, il sufrait deffacer le template<class T> et de remplacer tous les T par des string et nous obtiendrions ceci :

Un livre sur C++ sans templates ! CHAPITRE 19

375

string somme(const string& obj1, const string& obj2) { if (obj1 == obj2) return obj1; return obj1 + obj2; }

Nous voyons donc que pour pouvoir construire une fonction gnrique il nous faut absolument dnir tous les oprateurs et les mthodes utiliss lintrieur du modle. La fonction somme() peut tre utilise, condition que les oprateurs + et == soient implments. Si nous sommes attentifs et dnissons correctement nos propres classes, nous pouvons donc aussi les rendre utilisables par ces fonctions gnriques. Nous allons prsent crer une nouvelle classe, maClasse, pour excuter notre fonction somme() sur deux instances de cette mme classe :
// uneclasse.cpp #include <iostream> using namespace std; class UneClasse { private: int compteur; public: UneClasse(int valeur = 0):compteur(valeur) {}; UneClasse(const UneClasse &objet1):compteur(objet1.compteur) {}; UneClasse& operator=(const UneClasse& objet1) { compteur = objet1.compteur; } friend int operator== (const UneClasse &objet1, const UneClasse &objet2) { if (objet1.compteur == objet2.compteur) return 1; return 0; } UneClasse operator+ (const UneClasse &original) const { int nouveau = compteur + original.compteur; UneClasse nouvelle(nouveau); return nouvelle; } friend ostream& operator<<(ostream& stream, const UneClasse &original) { return stream << original.compteur; } }; template<class T> T somme(const T& obj1, const T& obj2) { if (obj1 == obj2) return obj1;

376

Apprendre Java et C++ en parallle

return obj1 + obj2; }

int main() { UneClasse mca(1); UneClasse mcb(2); cout << somme<UneClasse>(mca, mcb) << endl; cout << somme<UneClasse>(mca, mca) << endl; cout << somme<UneClasse>(mcb, mcb) << endl; }

et son rsultat :
3 1 2

Limplmentation de la classe UneClasse est presque complte. Nous y avons ajout un constructeur par dfaut, un constructeur de copie, ainsi que loprateur daffectation. Loprateur == considre lgalit des deux objets si lattribut compteur est identique. Nous aurions mme pu vrier si les deux objets taient identiques, ce qui est le cas de nos deuxime et troisime exemples, mais nous constatons que ce nest pas ncessaire. Il est vident quaussi bien la dnition de la classe que celle du modle devraient tre dans un ou deux chiers den-tte.

Un modle de classe
Nous allons prsent passer un modle de classe et constater quil ny a rien de nouveau et que la syntaxe est la mme. Voici donc notre classe Point :
// unmodele.cpp #include <iostream> template <class T> class Point { T posx; T posy; public: Point(T x, T y) { posx = x; posy = y; } void test(); }; template <class T>

Un livre sur C++ sans templates ! CHAPITRE 19

377

void Point<T>::test() { std::cout << "Position: " << posx << ":" << posy << std::endl; };

using namespace std; int main() { Point<int> pint(1,2); pint.test(); Point<double> pdouble(1.1,2.22); pdouble.test(); return 0; }

qui pourrait reprsenter aussi bien un pixel dans une image quune position en centimtres avec plusieurs dcimales dans un espace deux dimensions. Comme pour le modle de fonction, nous retrouvons notre template <class T> et nous avons dj limpression dtre familiers avec les modles, dont tout programmeur C++ avanc devrait absolument considrer lutilisation. Le rsultat du programme :
Position: 1:2 Position: 1.1:2.22

nous conrme que le code se compile et fonctionne comme prvu. Nous retrouvons les formes std::cout et std::endl du chapitre 6, puisque le code, avant le using namespace std;, peut tre considr comme un chier den-tte.

Rsum
Les modles (templates) sont lun des aspects essentiels de la programmation C++. Ils sont trs prsents dans limplmentation de la bibliothque Standard (STL, Standard Template Library) et doivent tre absolument considrs durant lapprentissage du langage C++.

Exercice
Crer un modle de fonction nomm soustraction() qui va soustraire deux entiers (int) ou deux objets de notre classe UneClasse, que nous allons modier pour supporter loprateur -. La nouvelle classe UneClasse1 sera aussi corrige, an que son attribut compteur ne soit jamais ngatif. Dans ce cas-l, sa valeur sera 0, comme dailleurs le rsultat de la soustraction si elle se rvle ngative.

20
Impossible sans SQL !
Pour cette partie, nous supposerons que le lecteur possde une licence dAccess de Microsoft et quelques notions de cet outil de base de donnes. Dans la premire partie, nous allons revenir sur notre chier dlimit, celui que nous avons dj rencontr au chapitre 9. Nous allons crer, en C++, des donnes que nous importerons dans Access. Nous nutiliserons que Java pour accder notre base de donnes, avec des instructions SQL, car nous navons pas de bibliothque en C++. Ces dernires existent, mais elles sont en gnral livres avec des produits commerciaux comme Oracle, IBM DB3, Microsoft SQL Server ou Sybase. Une alternative serait dutiliser Linux et MySQL, par exemple. Nous y reviendrons en n de chapitre. Nous en proterons pour prsenter le langage SQL et la technologie ODBC de Microsoft.

Cration dun chier dlimit en C++


Le programme qui suit va nous crer un chier dlimit Personnes.txt. La premire ligne du chier va contenir la dnition des champs de notre base de donnes, cest--dire le nom, le prnom et lanne de naissance. Les enregistrements viendront ensuite sur chaque ligne. Le dernier champ, lanne de naissance, nest pas mis entre guillemets. Nous verrons comment Microsoft Access convertira ce champ, comme un entier, dans la base de donnes. Nous utiliserons ici les termes de champs ou de colonnes, qui sont en fait quivalents.
// write_db.cpp #include <iostream> #include <sstream> #include <fstream> #include <string>

380

Apprendre Java et C++ en parallle

using namespace std; class Personne { private: string nom; // nom de la personne string prenom; // prnom de la personne int annee; // anne de naissance de la personne public: Personne::Personne(string leNom, string lePrenom, int lAnnee) :nom(leNom), prenom(lePrenom), annee(lAnnee) {} // char *get_delim() const { string get_delim() const { ostringstream enregistrement; enregistrement << "\"" << nom << "\";\"" << prenom << "\";" << annee << ends; return enregistrement.str(); } }; class WritePersonDB { private: ofstream outfile; public: bool write_debut(const char *fichier) { outfile.open(fichier, ios::out); if (!outfile.is_open()) { return false; } outfile << "\"Nom\";\"Prenom\";\"Annee\""; return true; } void write_record(const Personne &personne) { outfile << endl; outfile << personne.get_delim(); } void write_fin() { outfile.close(); } }; int main() { WritePersonDB wpersonne_db; Personne personnes[2] = {

Impossible sans SQL ! CHAPITRE 20

381

Personne("Haddock", "Capitaine", 1907), Personne("Kaddock", "Kaptain", 1897) }; if (!wpersonne_db.write_debut("personnes.txt")) { cout << "Ne peut pas crire dans le fichier personnes.txt" << endl; return -1; } wpersonne_db.write_record(personnes[0]); wpersonne_db.write_record(personnes[1]); wpersonne_db.write_fin(); cout << "Fichier personnes.txt gnr" << endl; }

Cest un trs joli programme, dans lequel nous utilisons toute une srie de techniques prsentes dans cet ouvrage. Il y a dabord le ostringstream, que nous avons analys en dtail au chapitre 9 et qui nous permet dassembler des string. Nous pourrions utiliser loprateur +, mais nous aurions une difcult avec annee, qui est un entier. Si nous tions le const du get_delim(), le programme ne compilerait pas. Cet aspect a t considr au chapitre 6, lors du traitement du sufxe const pour les mthodes. Le reste ne prsente pas de difcults particulires, condition de matriser les entres et sorties que nous avons tudies au chapitre 9. En effet, lcriture dun chier XML est presque identique. Nous avons cr deux classes pour faire ce travail, mais il y a certainement dautres approches tout aussi lgantes et structures. WritePersonDB pourrait certainement hriter dune classe de base indpendante du format de la table. Il faut cependant retenir un dtail. Nous ncrivons jamais de nouvelle ligne endl la n de lenregistrement, mais seulement au dbut. Cest important, car le dernier enregistrement contiendrait une ligne vide et donnerait des difcults limportation dans Microsoft Access.

Cration dune base de donnes sous Microsoft Access


Avant de pouvoir enregistrer des donnes dans Microsoft Access ou y accder, il nous faut dabord crer une base de donnes. Nous lancerons donc Microsoft Access 2000 pour crer un chier bd1.mdb dans le rpertoire Chap20, dans lequel nous avons notre code C++ et Java correspondant. Nous nallons pas crer de table, mais notre chier Personnes.txt :
"Nom";"Prenom";"Annee" "Haddock";"Capitaine";1907 "Kaddock";"Kaptain";1897

382

Apprendre Java et C++ en parallle

qui vient dtre gnr par notre programme C++, sera import dans une table Personnes de Microsoft Access. Pour ce faire, il suft de lancer Access, de slectionner Fichier > Donnes externes > Importer et de spcier le chier texte Personnes.txt. Lassistant dimportation demande un certain nombre de paramtres qui nont rien de mystrieux. Il ne faudra pas oublier dindiquer loption Premire ligne contient le nom des champs, et nous laisserons Microsoft Access ajouter une cl primaire. La table se nommera aussi Personnes. Si nous examinons le contenu de la table ou sa dnition, nous sommes absolument satisfaits :

Figure 20-1

Contenu de la table Personnes

Figure 20-2

Dnition de la table Personnes

La premire ligne de notre chier nous a bien dni les champs de notre base de donnes, et la colonne Annee a bien t considre comme numrique. Nous pouvons prsent fermer notre base de donnes ainsi quAccess.

Activation dODBC - XP
Ltape suivante est lactivation dODBC (Open DataBase Connectivity). ODBC va nous permettre de communiquer entre Java et notre base de donnes Access disponible dans le chier bd1.mdb.

Impossible sans SQL ! CHAPITRE 20

383

Sous Windows XP, nous devons installer laccs ODBC notre chier Access en allant dans le menu Dmarrer puis, Panneau de conguration>Outils dadministration>Sources de donnes ODBC>Sources de donnes utilisateur. En assumant que lutilisateur na jamais utilis lODBC, nous remplacerons MS Access Database par ODBC-Java-test :

Figure 20-3

Windows XP Installation ODBC pour bd1.mdb

Avec de cliquer sur le bouton Slectionner, nous devrons naviguer sur notre disque pour trouver et dnir notre rfrence C:\JavaCpp\EXEMPLES\Chap20\bd1.mdb. Aprs avoir cliqu sur le bouton OK, nous devrions retrouver notre ODBC-Java-test dans la liste qui peut varier dun systme lautre en fonction des outils installs.

Figure 20-4

Windows XP Access driver

384

Apprendre Java et C++ en parallle

Depuis longlet Sources de donnes utilisateur, nous pourrions aussi cliquer sur le bouton Ajouter pour obtenir la fentre de la gure 20-5, spcier le driver Access et ajouter notre ODBC-Java-test. Cette squence est ncessaire dans le cas de plusieurs entres ODBC.

Figure 20-5

Windows XP Nouvelle entre Access

Activation dODBC Vista


Sous Windows Vista, linstallation est presque quivalente ; il faut juste ne pas se perdre dans ce systme dexploitation remodel ! Laccs se fait depuis le Panneau de conguration (Afchage classique)>Outils dadministration>Source de donnes ODBC. Notre base de donnes bd1.mdb est une base de donnes Access.

Figure 20-6

Windows Vista Nouvelle entre ODBC pour Access

Impossible sans SQL ! CHAPITRE 20

385

Le driver est encore Microsoft Access.

Figure 20-7

Windows Vista Driver Access

Nous devrons choisir notre source de base de donnes et son nom daccs.

Figure 20-8

Windows Vista Source de donnes ODBC-Java-Test

Ds cet instant, le driver ODBC sera actif sur cette base de donnes bd1.mdb. Cette entre restera active, mme au prochain dmarrage de Windows XP ou Vista. Pour effacer cette ressource du systme, il faudra supprimer lentre en utilisant le bouton correspondant. Le nom de la source utilise est essentiel : ODBC-Java-Test. Cest grce lui que linterface Java JDBC dcrite ci-aprs fera le lien avec jdbc:odbc:ODBC-Java-Test.

386

Apprendre Java et C++ en parallle

Pour les personnes qui utilisent danciennes versions de Windows ou dAccess, ou encore des versions anglaises, les termes sont parfois diffrents. Pour les onglets, DSN est parfois remplac par Sources de donnes et Connexion multiple par Groupement de connexions. Il faut alors essayer quelques combinaisons, comme celle dutiliser les Sources de donnes utilisateur au lieu des Sources de donnes systme.

Accs ODBC partir de Java


Un nouveau venu en programmation Java sera videmment surpris. Nous connaissons dj les qualits de ce langage dans le domaine dInternet, mais il est tout aussi puissant en programmation pour laccs aux bases de donnes. La premire instruction du programme qui va suivre :
import java.sql.*;

devrait nous conduire consulter la documentation du JDK de Sun Microsystems. Nous y dcouvrirons le terme de JDBC, Java Database Connectivity, qui correspond au logiciel qui va nous permettre daccder notre base de donnes bd1.mdb au travers dODBC. Nous allons prsenter le code et passer ensuite aux dtails :
// ODBC 32 bits doit tre actif // ODBC-Java-Test associ Chap20\bd1.mdb import java.sql.*; public class PersonnesODBC { public static void main(String args[]) { String url = "jdbc:odbc:ODBC-Java-Test"; Connection con; Statement stmt; String requete = "select Nom, Prenom, Annee FROM Personnes"; try { Class.forName("sun.jdbc.odbc.JdbcOdbcDriver"); } catch(java.lang.ClassNotFoundException e) { System.err.print("ClassNotFoundException: "); System.err.println(e.getMessage()); return; } try { System.out.println("Connexion avec ODBC-Java-Test"); con = DriverManager.getConnection(url, "", ""); stmt = con.createStatement(); System.out.println("Excute la requte");

Impossible sans SQL ! CHAPITRE 20

387

ResultSet rs = stmt.executeQuery(requete); System.out.println("Les entres dans notre base de donnes: "); while (rs.next()) { String nom = rs.getString("Nom"); String prenom = rs.getString("Prenom"); int annee = rs.getInt("Annee"); System.out.println(nom + " " + prenom + " est n en " + annee); } stmt.close(); con.close(); } catch(SQLException ex) { System.err.println("SQLException: " + ex.getMessage()); } } }

Si nous faisons une analyse dtaille de ce code, une des premires surprises viendra de nos deux instances de Connection et de Statement. Ce sont des interfaces, et cela signie que nous devons implmenter le code. Mais comment donc cela est-il possible pour ce grand nombre de mthodes dnies dans ces deux interfaces ? Pour sen rendre compte, il suft de consulter la documentation de lAPI telle que nous lavons dcrite dans lannexe B. La rponse est relativement simple :
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");

Un appel la fonction forName() va nous charger le driver ncessaire, ici notre sun.jdbc .odbc.JdbcOdbcDriver. Nous pourrions aussi communiquer lextrieur, avec un serveur SQL de Microsoft, dOracle, de Sybase ou MySQL. Dans ces cas-l, il faudrait utiliser le driver appropri, auquel nous ajouterions ladresse IP du serveur, ainsi que le numro du port. Cest certainement ici que nous pourrions faire la remarque que, mme si la connectivit peut tre garantie avec diffrents serveurs, il nest pas certain que toutes les commandes SQL fonctionnent. Il y a en effet des diffrences. Une personne familire avec les commandes SQL sous Access dcouvrira tout de suite des diffrences en ouvrant un ouvrage dcrivant limplmentation de MySQL. Des adaptations pourraient se rvler ncessaires. La variable url, que nous avons appele ainsi pour des raisons videntes, est en fait le nom du serveur SQL. Ici, ce nom est un peu particulier, car il correspond notre entre dnie dans notre administrateur ODBC et se trouve tre le premier paramtre de la premire instruction importante de notre classe :
con = DriverManager.getConnection(url, "", "");

388

Apprendre Java et C++ en parallle

La mthode getConnection() possde plusieurs implmentations. Nous avons choisi celle avec le nom et le mot de passe de lutilisateur. Ils sont vides, car nous ne les avons pas dnis avec ladministrateur ODBC de Windows. Linstruction :
stmt = con.createStatement();

est ncessaire pour obtenir un objet de la classe Statement avant de pouvoir excuter des instructions SQL.

Requte SQL
Le langage SQL permet dexcuter des requtes dans une base de donnes. Dans le programme prcdent, nous navons quune seule requte :
SELECT Nom, Prenom, Annee FROM Personnes

SELECT est une commande SQL qui nous permet de slectionner les champs Nom, Prenom et Annee depuis (from) la table Personnes. Nous pouvons en fait nous amuser avec ce langage, en utilisant le mode SQL sous Microsoft Access, lorsque nous choisissons une Requte Selection. La commande :
SELECT Nom,Prenom,Annee FROM Personnes;

peut alors tre entre, et nous pouvons nous placer dans Access en mode Feuilles de donnes pour examiner le rsultat. Il est possible de ne spcier quune partie des champs :
SELECT Nom,Prenom FROM Personnes;

La suppression dAnnee dans la commande SELECT ncessiterait videmment des modications de la classe PersonnesODBC. Une condition peut aussi tre spcie :
SELECT * FROM Personnes WHERE Annee = 1907;

Et seuls les enregistrements avec lanne gale 1907 seront prsents. Le caractre * indique que tous les champs seront exposs. Avant de passer la dernire partie de programme Java prcdent, nous allons montrer son rsultat :
Excute la requte Les entres dans notre base de donnes: Haddock Capitaine est n en 1907 Kaddock Kaptain est n en 1897

Pour extraire des donnes de notre table Personnes, il nous faut excuter une requte que nous avons dnie dans la variable requete :
String requete = "SELECT Nom, Prenom, Annee FROM Personnes";

cet effet, la mthode executeQuery() sur lobjet stmt de la classe Statement sera utilise. En cas derreur quelconque de la requte, qui pourrait tre un format incorrect ou une table efface, la mthode executeQuery() gnrera une exception. Pour terminer, nous lisons les rsultats de la requte dans une boucle sur chaque enregistrement, avec la mthode next() sur lobjet rs, qui est un ResultSet retourn aprs lexcution

Impossible sans SQL ! CHAPITRE 20

389

de notre requte sur les trois colonnes de la table. La mthode getString() reoit le nom des champs tels quils ont t dnis dans la commande SQL. Nous avons ici trois possibilits, et le rsultat nous montre le contenu de la table avec une ligne de texte format par enregistrement. Nous pourrions utiliser lAWT ou le Swing de Java pour prsenter nos rsultats dune manire plus propre. Ceci nous permettrait dcrire de vraies applications de base de donnes, comme nous pouvons le faire avec les outils de Microsoft Access.

Cration dune nouvelle table depuis Java


Comme nous avons dj cr une base de donnes et quelle est actuellement accessible, rien ne nous interdit de crer de nouvelles tables, avant dy introduire des donnes. Nous proterons de cette occasion pour prsenter les instructions SQL pour la cration de table, ainsi que la modication denregistrement existant. Nous allons remarquer, trs rapidement, que le code qui va suivre ressemble comme un frre notre premire classe, PersonneODBC :
// ODBC 32 bits doit tre actif // ODBC-Java-Test associ Chap20\bd1.mdb import java.sql.*; public class CreationODBC { public static void main(String args[]) { String url = "jdbc:odbc:ODBC-Java-Test"; Connection con; Statement stmt; try { Class.forName("sun.jdbc.odbc.JdbcOdbcDriver"); } catch(java.lang.ClassNotFoundException e) { System.err.print("ClassNotFoundException: "); System.err.println(e.getMessage()); return; } try { System.out.println("Connexion avec ODBC-Java-Test"); con = DriverManager.getConnection(url, "", ""); stmt = con.createStatement(); System.out.println("Cration de la table Telephone"); stmt.executeUpdate("CREATE TABLE Telephone (Nom VARCHAR, Prenom VARCHAR, NumTel VARCHAR)"); System.out.println("Insertion d'objets dans la table Telephone"); stmt.executeUpdate("INSERT INTO Telephone (Nom, Prenom, NumTel)

390

Apprendre Java et C++ en parallle

VALUES ('Boichat', 'Jean-Bernard', '04698761272')"); stmt.executeUpdate("INSERT INTO Telephone (Nom, Prenom) VALUES ('Boichat', 'Nicolas')"); System.out.println("Modifie un enregistrement dans la table Telephone"); stmt.executeUpdate("UPDATE Telephone SET NumTel = '04698761272' WHERE Prenom = 'Nicolas'"); System.out.println("Excute la requte"); ResultSet rs = stmt.executeQuery("SELECT Nom, Prenom, NumTel FROM Telephone"); System.out.println("Les entres dans notre table Telephone: "); while (rs.next()) { String nom = rs.getString(1); String prenom = rs.getString(2); String numTel = rs.getString(3); System.out.println("Enregistrement: " + nom + " " + prenom + " : " + numTel); } stmt.close(); con.close(); } catch(SQLException ex) { System.err.println("SQLException: " + ex.getMessage()); } } }

Dans le code prcdent, nous dcouvrons les instructions SQL suivantes :


CREATE TABLE Telephone (Nom VARCHAR, Prenom VARCHAR, NumTel VARCHAR); INSERT INTO Telephone (Nom, Prenom, NumTel) VALUES ('Boichat', 'Jean-Bernard', '04698761272'); INSERT INTO Telephone (Nom, Prenom) VALUES ('Boichat', 'Nicolas'); UPDATE Telephone SET NumTel = '04698761272' WHERE Prenom = 'Nicolas'; SELECT Nom, Prenom, NumTel FROM Telephone;

Notre nouvelle table se nommera Telephone et contiendra trois colonnes pour le nom, le prnom et le numro. Ces trois champs seront des VARCHAR, cest--dire des chanes de caractres traditionnelles. Linstruction CREATE en SQL fait le travail, et nous constaterons la manire de dnir le nom des champs et leur type. Linstruction CREATE TABLE va donc crer la table Telephone dans la base de donnes db1.mdb, puisque cette dernire est connecte ODBC-Java-Test, comme nous lavons dni au travers de ladministrateur ODBC. Pour dnir le nom des champs, il suft de spcier, entre parenthses, la liste de ceux-ci suivie par le type dsir. Ces paires sont spares par des virgules.

Impossible sans SQL ! CHAPITRE 20

391

Linstruction INSERT INTO nous permet dintroduire des enregistrements dans la base de donnes. Aprs le nom de la table, nous devons indiquer, entre parenthses, le nom des champs affects. Avec VALUES(), nous attribuons des valeurs chacun des noms indiqus prcdemment. Si nous avions un champ avec des entiers, les guillemets ne seraient pas ncessaires pour des chiffres. Il est possible de dnir moins de colonnes que la dnition de la table. Mais cest un risque, et il faudrait dnir une valeur de dfaut lors de la cration de la table. Ici, ce nest pas trs important, mais en cas de ncessit il faudrait consulter la documentation de la commande CREATE. La commande UPDATE est capable de modier des enregistrements existants sur des colonnes et des critres particuliers. Le paramtre SET dnit quel champ sera mis jour avec sa valeur, et le paramtre WHERE, la condition. Ici, tous les enregistrements qui ont le prnom Nicolas seront dnis avec le mme numro de tlphone. Nous connaissons dj la commande SELECT, qui nous permet de reprendre dans le code Java le rsultat de la requte. Le getString() est utilis ici diffremment. Nous utilisons non pas le nom de la colonne, mais son index, qui commence 1 et qui dpend du nombre de champs dnis dans les paramtres de la commande SELECT, cest--dire 3 dans notre cas. Pour les quatre premires instructions SQL, nous devons utiliser la mthode executeUpdate(), alors que le select doit utiliser executeQuery().

MySQL et Linux : recommandations


Pour ceux qui dsirent approfondir leurs connaissances en Java, en C++ et en SQL, nous recommandons : dacqurir louvrage MySQL & mSQL de Randy Jay Yarger, George Reese & Tim King (voir annexe G) ; dinstaller une version rcente de Linux avec une distribution de MySQL ; dinstaller LAMP (Linux, Apache, MySQL et PHP), http://fr.wikipedia.org/wiki/LAMP. Pour ces deux derniers points, nous pourrions suggrer Ubuntu (voir annexe F). Pour plus dinformations, nous vous recommandons les sites suivants : http://doc.ubuntu-fr.org/mysql http://www.aide-ubuntu.com/Installer-Apache2-Mysql-5-et-Php-5 En ce qui concerne MySQL, nous trouverons linformation ncessaire sur les sites : http://www.mysql.com/ http://www.mysql.com/download.html Le langage PHP (Hypertext Preprocessor) est devenu incontournable pour le dveloppement de pages dynamiques sur Internet. Nous consulterons le site de Wikipdia pour davantage dinformations sur PHP (http://fr.wikipedia.org/wiki/PHP:_Hypertext_Preprocessor).

392

Apprendre Java et C++ en parallle

NetBeans (voir annexe E) peut aussi tre utilis pour dvelopper des applications PHP avec des accs MySQL : http://www.netbeans.org/. Beaucoup de serveurs Web sont aujourdhui accessibles au public et quips pour dvelopper des applications PHP et MySQL.

Autres choix dinterfaces


Des bibliothques C++ et une multitude dexemples et dinterfaces pour dautres langages, comme Perl, PHP ou Python, sont disposition. Une version shareware existe pour Windows, mais na pas t considre dans cet ouvrage, car le travail et la prparation pour linstallation de la bibliothque C++ auraient dpass les objectifs xs pour cet ouvrage. Mais cette dernire possibilit reste tout de mme une alternative.

Rsum
Nous esprons que ce chapitre aura intress un grand nombre de lecteurs. Le traitement et laccs des bases de donnes sont des sujets essentiels en informatique. La manire de procder en Java est lgante et devrait amener certains programmeurs utiliser ou largir leurs connaissances, comme celle de remplir des collections partir dune base de donnes ou dcrire des programmes interactifs pour le Web (CGI et servlets).

Exercices
1. Modier la classe CreationODBC an que tous les enregistrements soient effacs si la table existe dj. Pour ceci nous utiliserons linstruction SQL DELETE * FROM Telephone. Partager les diffrentes fonctions dans des mthodes qui retourneront une erreur en cas de difcult. 2. Crer deux tables Table1 et Table2. Les deux tables auront la mme structure, avec deux colonnes, contenant un index (Champ1) et un texte (Champ2). Remplir ces deux tables avec des donnes et crer une requte, qui extrait les deux champs de texte pour un index dtermin qui existe dans les deux tables. Pour faire cet exercice, nous devrons crer nos tables en SQL avec, par exemple :
CREATE TABLE Table1 (Champ1 INTEGER, Champ2 VARCHAR)

La recherche sur deux tables pourra se faire ainsi, et sur une seule ligne :
SELECT Table1.Champ1, Table1.Champ2, Table2.Champ2 FROM Table1, Table2 WHERE Table1.Champ1 = Table2.Champ1

21
Java et C++ main dans la main : le JNI
Pourquoi et comment utiliser JNI ?
Le JNI (Java Native Interface) permet Java dutiliser du code crit dans dautres langages et avec dautres compilateurs. Nous choisirons ici notre compilateur C++ et crirons quelques petites fonctions pour nous donner une ide des possibilits et surtout de la manire de gnrer le code, qui est loin dtre triviale. Cest pour cette raison quune approche avec des exemples simples devrait nous faciliter la tche. Il existe de nombreux cas o le JNI savre trs utile, et nous en citerons quelques-uns : rutiliser du code C et C++ existant sans devoir rcrire tout le code en Java ; utiliser des API existantes en C et C++ ; accder des ressources hardware dans lesquelles il ne serait pas possible, ou difcile, dcrire le pilote en Java ; utiliser de lAWT de Java pour linterface utilisateur pour du code crit dans dautres langages.

394

Apprendre Java et C++ en parallle

Note Pour les programmeurs qui utilisent les anciennes versions 1.2 ou 1.3 du JDK de Java et qui voudraient compiler les exemples de ce chapitre avec le compilateur C++ de GNU, il faudra modier lun des chiers den-tte du JDK distribu par Sun Microsystems. Dans le chier : jdk1.2\include\win32\jni_md.h ou : jdk1.3.1\include\win32\jni_md.h la ligne : Typedef __int64 jlong; devra tre remplace par : #ifdef __GNUC__ typedef long jlong; #else typedef __int64 jlong; #endif

Des salutations dune bibliothque C++


Nous avons choisi un premier petit exercice qui consiste recevoir des salutations dune bibliothque crite en C++ partir dun programme Java. Nous allons examiner les dtails aprs avoir prsent ce code :
class SalutCPP { public native void salutations(); static { System.out.println("Java: chargement de la bibliothque salut.dll"); System.loadLibrary("salut"); } } class MonPremierJNI { public static void main(String[] args) { System.out.println("Java: cration d'une instance de SalutCPP"); SalutCPP monSalut = new SalutCPP(); System.out.println("Java: appel de la mthode native salutations()"); monSalut.salutations(); System.out.println("Java: fin de programme"); } }

Il y a deux nouveauts dans ce code. La premire consiste en la dclaration de la mthode native, qui se fait de cette manire :
public native void salutations();

Le mot-cl native indique que la mthode salutations() sera disponible dans une bibliothque extrieure crite dans un autre langage. Cette dernire dtermine le nom de la mthode

Java et C++ main dans la main : le JNI CHAPITRE 21

395

ainsi que dventuels paramtres ou des valeurs de retour dont nous verrons les dtails plus loin. La deuxime nouvelle instruction :
System.loadLibrary("salut");

va permettre la machine virtuelle de Java de charger la bibliothque salut, qui sera ici, sous Windows, une bibliothque dynamique nomme salut.dll qui devra se trouver dans le chemin daccs (PATH) ou dans le rpertoire de nos deux classes MonPremierJNI.class et SalutCPP.class. Le code ci-dessus va compiler sans aucune difcult, mais ne pourra pas tre excut immdiatement, puisque le code de la bibliothque na pas encore t prpar ni assembl.

javah pour le code C++


Aprs la cration de notre classe SalutCPP.class, qui contient la dnition de la mthode salutations(), nous devons prsent excuter javah sur cette classe. Cet outil va nous gnrer le chier den-tte C++, an que nous puissions ensuite coder le code correspondant en C++ et crer notre bibliothque salut.dll. En excutant :
javah SalutCPP

le code suivant est alors gnr dans le chier SalutCPP.h :


/* DO NOT EDIT THIS FILE - it is machine generated */ #include <jni.h> /* Header for class SalutCPP */ #ifndef _Included_SalutCPP #define _Included_SalutCPP #ifdef __cplusplus extern "C" { #endif /* * Class: SalutCPP * Method: salutations * Signature: ()V */ JNIEXPORT void JNICALL Java_SalutCPP_salutations(JNIEnv *, jobject); #ifdef __cplusplus } #endif #endif

Il sagit prsent de crer le code dans le chier SalutCPP.cpp, qui doit inclure au minimum le code de la fonction Java_SalutCPP_salutations(). Nous allons le faire de la manire la plus simple qui soit :
#include "SalutCPP.h" #include <iostream>

396

Apprendre Java et C++ en parallle

using namespace std; JNIEXPORT void JNICALL Java_SalutCPP_salutations(JNIEnv *, jobject) { cout << "C++: salutations depuis la bibliothque salut.dll" << endl; }

Nous noterons que les dnitions de JNIEXPORT, de JNICALL et de JNIEnv sont donnes dans le chier den-tte jni.h, qui est lui-mme dni dans notre chier prcdent SalutCPP.h.

Cration de notre salut.dll


Il reste enn compiler ce code C++ et gnrer notre bibliothque Windows dynamique salut.dll. Ceci peut se faire avec un Makefile, qui contiendra toutes les parties ncessaires la gnration de notre code :
JDK = "C:/Program Files/Java/jdk1.6.0_06" all: java cpp salut.dll java: cpp: MonPremierJNI.class SalutCPP.h SalutCPP.o

MonPremierJNI.class:

MonPremierJNI.java javac MonPremierJNI.java MonPremierJNI.class javah SalutCPP MonPremierJNI.class SalutCPP.cpp g++ -I$(JDK)/include -I$(JDK)/include/win32 -c SalutCPP.cpp SalutCPP.o dllwrap --driver-name=c++ --output-def salut.def --add-stdcall-alias -o salut.dll -s SalutCPP.o

SalutCPP.h:

SalutCPP.o:

salut.dll:

Si le programmeur dcide dutiliser une autre version du JDK de Java, il devra modier la premire ligne du Makefile. Lorsque nous compilons SalutCPP.cpp, il est ncessaire dinclure, avec le paramtre de compilation I, les chemins daccs de jni.h (ici, /jdk1.6.0_06/include), mais aussi de jni_md.h, qui est inclus dans jni.h et qui se trouve dans le sous-rpertoire win32 du JDK. Pour la gnration de la bibliothque avec dllwrap, nous ne reviendrons pas sur les dtails, qui ne sont pas vraiment ncessaires pour comprendre ou tendre plus tard cet exemple. Enn, il nous faut revenir notre programme Java MonPremierJNI, que nous pouvons prsent excuter :
java MonPremierJNI

Java et C++ main dans la main : le JNI CHAPITRE 21

397

Nous obtiendrons le rsultat attendu :


Java: cration d'une instance de SalutCPP Java: chargement de la bibliothque salut.dll Java: appel de la mthode native salutations() C++: salutations depuis la bibliothque salut.dll Java: fin de programme

Nous conseillerons ici aux lecteurs de consulter lannexe C, Installation et utilisation de Crimson , et son exercice 2.

JNI, passage de paramtres et Swing


Lexemple prcdent ne pouvait tre plus simple. Aucun paramtre ntait pass la fonction, et aucune valeur de retour ntait utilise. An dillustrer ces deux derniers aspects, nous allons proter de loccasion pour prsenter Swing, avec cette petite application qui apparatra comme sur la gure 21-1.

Figure 21-1

Notre premier calcul

Cette application Java est crite non avec les composants AWT que nous avons dj prsents, mais avec le dernier kit de dveloppement pour les interfaces graphiques, qui fait partie de Java depuis la version 1.2, Swing. Ce dernier fournit des composants dinterface Java plus performants et portables que lAWT, mais avec le dfaut dtre beaucoup plus lent que son prdcesseur. Ce problme tend sattnuer sur des machines rcentes qui ont la fois plus de mmoire et de vitesse de processeur. La fentre prsente possde deux champs dentre pour des nombres. Les deux boutons nous permettent de choisir entre une multiplication ou une addition de ces nombres. Le rsultat sera prsent sous les deux boutons avec le message appropri. Bien que ces deux oprations ne prsentent aucune difcult, nous allons nous rendre la tche plus complique, en codant lopration arithmtique dans une bibliothque dll crite en C++. Pour ce faire, nous avons choisi dcrire une classe Java nomme OperationCPP, qui va nous permettre dexcuter une opration dnie par le paramtre du constructeur. Cette opration pourra tre rpte au travers de la mthode :
execute(int num1, int num2)

398

Apprendre Java et C++ en parallle

Voici donc notre classe, qui va dnir et effectuer linterface JNI :


public class OperationCPP { private String lOperation; public native String operation(String loper, int num1, int num2); static { System.out.println("Java: chargement de la bibliothque operation.dll"); System.loadLibrary("operation"); } public OperationCPP(String lOper) { lOperation = lOper; } public String execute(int num1, int num2) { return operation(lOperation, num1, num2); } }

Lorsquune instance de la classe OperationCPP a t obtenue, il ne sera plus possible de changer dopration puisquelle a t dtermine par le paramtre du constructeur. Cest un choix que nous avons fait, et uniquement pour illustrer notre exemple JNI. Comme dans lexemple prcdent, une bibliothque, nomme ici operation.dll, sera charge la premire instantation de la classe OperationCPP. La mthode native operation() devra encore tre code. Avant dcrire notre application Swing, il est judicieux et prfrable dcrire une classe de test pour vrier notre classe OperationCPP :
public class MultiplicationJNI { public static void main(String[] args) { OperationCPP maMult = new OperationCPP("Multiplication"); System.out.println("Rsultat: " + maMult.execute(12, 13)); } }

Aprs avoir compil la classe MultiplicationJNI et avant de pouvoir lexcuter, nous devrons crire le code C++ correspondant notre interface JNI. De la mme manire que dans notre premier exemple, nous devrons gnrer le chier den-tte C++ avec la commande :
javah OperationCPP

Cette dernire va nous crer notre chier OperationCPP.h :


/* DO NOT EDIT THIS FILE - it is machine generated */ #include <jni.h> /* Header for class OperationCPP */ #ifndef _Included_OperationCPP #define _Included_OperationCPP #ifdef __cplusplus

Java et C++ main dans la main : le JNI CHAPITRE 21

399

extern "C" { #endif /* * Class: OperationCPP * Method: operation * Signature: (Ljava/lang/String;II)Ljava/lang/String; */ JNIEXPORT jstring JNICALL Java_OperationCPP_operation(JNIEnv *, jobject, jstring, jint, jint); #ifdef __cplusplus } #endif #endif

Nous devons rappeler qu lorigine notre dnition tait la suivante :


public native String operation(String loper, int num1, int num2);

et nous voyons prsent apparatre de nouveaux types de variables qui sont jstring et jint, dont nous pouvons trouver la dnition (typedef) dans les chiers den-tte jni.h et jni_md.h ; nous pouvons consulter ces derniers dans la distribution du JDK de Sun Microsystems dans les rpertoires ../jdk1.6.0_06/include et ../jdk1.6.0_06/include/win32.
jint, sous Windows, sera interprt comme un long. An denrichir notre exemple, nous avons dni une classe C++ nomme Operation, qui conservera lopration arithmtique

de la mme manire que nous lavons dnie en Java. La dnition de la classe sera code dans le chier Operation.h :
#include <string> class Operation { private: string loperation;

// nom de lopration

public: Operation(const char *loper); int execute(int num1, int num2); };

et limplmentation dans le chier OperationCPP.cpp, qui contiendra aussi linterface JNI ncessaire notre application :
// OperationCPP.cpp #include "OperationCPP.h" #include "Operation.h" #include <iostream> #include <sstream> using namespace std;

400

Apprendre Java et C++ en parallle

Operation::Operation(const char *loper) { loperation = loper; } int Operation::execute(int num1, int num2) { if (loperation == "Multiplication") { return num1 * num2; } if (loperation == "Addition") { return num1 + num2; } return 0; }

JNIEXPORT jstring JNICALL Java_OperationCPP_operation(JNIEnv *env, jobject obj, jstring loperation, jint jnum1, jint jnum2) { const char *loper = env->GetStringUTFChars(loperation, 0); Operation mon_oper(loper); env->ReleaseStringUTFChars(loperation, loper); int resultat = mon_oper.execute(jnum1, jnum2); ostringstream un_stream; un_stream << "Le rsultat est " << resultat << endl; return env->NewStringUTF(un_stream.str().c_str()); }

La mthode de classe Operation::execute() est des plus traditionnelles. Lattribut loperation de la classe Operation dtermine la fonction excuter, cest--dire une addition ou une multiplication. La fonction JNI Java_OperationCPP_operation(), qui est responsable de linterface avec Java, est dun autre domaine de complexit. Nous avons choisi volontairement un des paramtres comme un String ainsi que le retour de la fonction. Ce type dinterface pourrait tre accept comme standard. Un String peut en effet tre compos dun type quelconque dont une conversion est possible en Java ou encore de plusieurs morceaux de types diffrents avec un sparateur choisi pralablement. La fonction GetStringUTFChars() :
const char *loper = env->GetStringUTFChars(loperation, 0);

retourne un pointeur une chane de caractres (UTF-8 : cod sur 8 bits) partir du String Java qui devrait tre Addition ou Multiplication. loper restera valide jusqu lappel de ReleaseStringUTFChars(). Comme le resultat est un entier, nous utilisons un ostringstream qui va nous convertir la valeur de retour en une chane de caractres.

Java et C++ main dans la main : le JNI CHAPITRE 21

401

Enn, la fonction :
env->NewStringUTF(un_stream.str().c_str());

construit un nouvel objet java.lang.String compos dune chane de caractres Unicode. De un_stream, nous retirons un String avec str() et ensuite une chane de caractres avec c_str() demande par NewStringUTF(). Ces formes C et C++ sont plus que dcourageantes. Pour terminer, nous dirons que les fonctions GetStringUTFChars(), Release-StringUTFChars() et NewStringUTF() utilisent la variable env (de type JNIEnv), qui est un pointeur une structure possdant elle-mme une liste de pointeurs toutes les fonctions JNI. Il faut noter que toutes ces fonctions sont documentes dans la distribution du JDK de Sun Microsystems et quun bon programmeur C ou C++ ne devrait pas avoir de grandes difcults utiliser dautres fonctions ou adapter ce code avec dautres compilateurs. La dernire phase est la compilation de notre chier OperationCPP.cpp et la cration de la bibliothque operation.dll.
g++ -O2 -I$(JDK)/include -I$(JDK)/include/win32 -c OperationCPP.cpp dllwrap --driver-name=c++ --output-def operation.def --add-stdcall-alias -o operation.dll -s OperationCPP.o

Avant de passer notre interface graphique en Java (Swing), il est conseill de vrier notre code en excutant :
java MultiplicationJNI

Le programme nous retournera :


Java: chargement de la bibliothque operation.dll Rsultat: 156

Notre interface Swing


Nous ne prsenterons Swing que trs brivement, car ce nest pas le but de cet ouvrage. Un bon programmeur Java qui possde, en outre, quelques connaissances de lAWT, que nous avons aussi rapidement prsent, ne devrait pas avoir de difcults comprendre ce code. Il est cependant sufsamment intressant pour donner lenvie de ladapter ou dcrire dautres applications plus substantielles. Si nous examinons nouveau notre application Swing :

Figure 21-2

Notre second calcul

402

Apprendre Java et C++ en parallle

et le code qui suit :


import javax.swing.*; import java.awt.*; import java.awt.event.*; public class SwingJNI { public void creationDesComposants(JFrame frame) { final JLabel label = new JLabel("Le rsultat de la multiplication ou de l'addition"); final ChampNombre entree1 = new ChampNombre(10, 5); final ChampNombre entree2 = new ChampNombre(20, 5); JButton bouton1 = new JButton("Multiplication"); bouton1.setMnemonic(KeyEvent.VK_I); bouton1.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { OperationCPP oper = new OperationCPP("Multiplication"); int num1 = entree1.getValue(); int num2 = entree2.getValue(); label.setText("Le rsultat de la multiplication: " + oper.execute(num1, num2)); } }); label.setLabelFor(bouton1); JButton bouton2 = new JButton("Addition"); bouton2.setMnemonic(KeyEvent.VK_I); bouton2.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { OperationCPP oper = new OperationCPP("Addition"); int num1 = entree1.getValue(); int num2 = entree2.getValue(); label.setText("Le rsultat de l'addition: " + oper.execute(num1, num2)); } }); label.setLabelFor(bouton2); JPanel panneau1 = new JPanel(); panneau1.setLayout(new FlowLayout()); panneau1.add(entree1); panneau1.add(entree2); JPanel panneau2 = new JPanel(); panneau2.add(bouton1); panneau2.add(bouton2);

Java et C++ main dans la main : le JNI CHAPITRE 21

403

JPanel panneau3 = new JPanel(); panneau3.add(label); frame.getContentPane().add(panneau1, BorderLayout.NORTH); frame.getContentPane().add(panneau2, BorderLayout.CENTER); frame.getContentPane().add(panneau3, BorderLayout.SOUTH); return; } public static void main(String[] args) { try { UIManager.setLookAndFeel( UIManager.getCrossPlatformLookAndFeelClassName()); } catch (Exception e) { } JFrame frame = new JFrame("SwingJNI"); SwingJNI app = new SwingJNI(); app.creationDesComposants(frame); frame.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); frame.pack(); frame.setVisible(true); } }

nous remarquerons dabord les trois JPanel, qui sont panneau1, panneau2 et panneau3. Dans le premier, nous dcouvrons nos champs dentres, avec nos deux 123 et 321. Le deuxime, JPanel, contient nos boutons de la slection de lopration arithmtique, et le dernier, notre panneau3, ltiquette JLabel, qui nous indiquera les rsultats. Ces trois panneaux sont ajouts la fentre de base (JFrame) et nous permettent dobtenir une prsentation (layout) dcente. Il y a dautres manires de faire, mais cela dpasse de loin les buts que nous nous sommes xs dans le cadre de cet ouvrage. Les deux mthodes actionPerformed() sont les points dentre de nos deux oprations arithmtiques. Nous y retrouvons le paramtre du constructeur de la classe operationCPP et la mthode execute(). La partie la plus intressante est lie la classe ChampNombre, que nous avons crite an de ltrer les caractres qui sont entrs :
import javax.swing.*; import javax.swing.text.*; import java.awt.Toolkit;

404

Apprendre Java et C++ en parallle

import java.text.NumberFormat; import java.text.ParseException; import java.util.Locale; public class ChampNombre extends JTextField { private Toolkit toolkit; private NumberFormat integerFormatter; public ChampNombre(int value, int columns) { super(columns); toolkit = Toolkit.getDefaultToolkit(); integerFormatter = NumberFormat.getNumberInstance(Locale.FRANCE); integerFormatter.setParseIntegerOnly(true); setValue(value); } public int getValue() { int retVal = 0; try { retVal = integerFormatter.parse(getText()).intValue(); } catch (ParseException e) { toolkit.beep(); } return retVal; } public void setValue(int value) { setText(integerFormatter.format(value)); } protected Document createDefaultModel() { return new DocumentNombre(); } protected class DocumentNombre extends PlainDocument { public void insertString(int pos, String str, AttributeSet as) throws BadLocationException { char[] source = str.toCharArray(); char[] resultat = new char[source.length]; int j = 0; for (int i = 0; i < resultat.length; i++) { if (Character.isDigit(source[i])) { resultat[j++] = source[i]; } else { toolkit.beep(); // si une lettre est entre } }

Java et C++ main dans la main : le JNI CHAPITRE 21

405

super.insertString(pos, new String(resultat, 0, j), as); } } }

La classe ChampNombre tend la classe JTextField de Swing, et nous lutilisons en lieu et place du traditionnel JTextField. Cette nouvelle classe nous permettra de ne voir apparatre que des nombres dans nos deux champs dentres. Les mthodes setValue() et getValue() nous permettent de dposer ou de recevoir nos deux nombres. La surcharge de la mthode createDefaultModel() de la classe JTextField est essentielle. cet effet nous avons cr une autre classe, DocumentNombre, qui ne fait rien dautre que ltrer chaque caractre individuellement, en ignorant tout ce qui nest pas un chiffre. La classe Document sert de modle pour les composants Swing. Lutilisation de la classe Toolkit et de la mthode beep() pourrait apparatre dans dautres applications. Une alarme sonore est toujours intressante. Pour terminer, et avant dexcuter notre application :
java SwingJNI

il faudra compiler nos deux classes :


javac ChampNombre.java javac SwingJNI.java

Nous rappellerons que les diffrentes phases de la compilation, qui sont plus complexes dans ce cas-ci, peuvent tre regroupes avantageusement dans un Makefile. Ce dernier se trouve sur le CD-Rom accompagnant cet ouvrage. Une analyse plus profonde des rsultats nous montrerait que, si les valeurs des entres dpassent une certaine limite, le rsultat peut devenir ngatif ou nul. Un traitement correct de ces cas derreurs devrait tre considr. Nous navons pas jug ncessaire dinventer un exercice. Cependant, nous rpterons que la cl du JNI et son point de dpart se situent dans le Makefile. Cest cet endroit quapparaissent clairement les diffrentes phases de la cration dune telle interface.

Rsum
Ce quil faut retenir de la technologie JNI est la possibilit de rutiliser du code existant faisant partie du systme lui-mme, ou encore dapplications qui ont ncessit des annes de dveloppement. Pour cet ouvrage, il sagit galement dun lien possible entre les deux langages Java et C++.

22
Quelques applications usuelles
Le but de ce chapitre est de titiller lesprit des programmeurs an de leur donner des sujets de programmes dapplication en Java et C++. Certains de ces sujets pourraient dailleurs convenir des projets de n dtudes (voir en n dannexe G, Rechercher des sujets de travaux pratiques ). Tous ces sujets ont dj t partiellement traits dans cet ouvrage, mais nous avons prfr les inclure ici en vue de conserver lidentit de chaque chapitre.

Coupons et collons
Aprs avoir tudi les diffrents aspects des entres et sorties en C++ et en Java, il nous a sembl intressant de montrer un exemple complet dapplication. Une bonne matrise des entres et des sorties est un avantage certain lorsquil sagit dcrire de petits outils systme. Combien de fois nous sommes-nous trouvs en face du problme ? Mon chier est trop grand, il ne tient pas sur une disquette. Ou alors il est tellement norme que je ne peux le joindre un e-mail ! Ou encore je ne parviens pas transfrer le JDK 1.6 chez un ami, car soit la ligne nest pas assez bonne, soit le transfert par FTP sinterrompt rgulirement. La solution est simple : nous coupons en morceaux et nous reconstruisons plus tard. C++ et Java sont deux langages tout fait appropris pour ce genre dexercice. Cependant, il nous faut tout de mme construire un outil un peu plus intelligent pour le traitement des erreurs (disquette fautive, disquette confondue avec une autre, etc.).

408

Apprendre Java et C++ en parallle

Commenons donc par un peu de design : Par dfaut, le chier sera coup pour remplir une disquette haute densit (1 457 664 octets). Si un paramtre est spci, il sera considr comme un nombre de mgaoctets correspondant la dimension des morceaux. Le rsultat sera compos de chiers au format 8.3 de DOS, et un chier de contrle .acr permettra de reconstruire le chier dorigine avec son nom de dpart. Un mcanisme simple de contrle doit permettre didentier les erreurs. Le chier .acr contiendra linformation ncessaire. En cas derreur, le chier fautif, cest--dire le morceau, doit tre identiable. An de vrier le contenu du chier, nous utilisons loprateur ^ (XOR). Nous allons prsent aborder la partie du code qui va traiter notre problme et qui est relativement substantielle. Cette fois-ci, nous ferons une analyse globale pour les deux versions Java et C++. Nous commencerons par la partie Coupe , qui consiste couper notre chier en morceaux, et nous terminerons par la reconstruction avec Colle . Le listing du code Java est prsent en premier, car il nous donne un meilleur aperu de la structure avec ses sries forces de try et catch().

Nous coupons en Java


import java.io.*; public class Coupe { private String mon_fichier; private int dimension; private String merreur; private String facr; static public String fin = "\r\n"; public Coupe(String le_fichier, int la_dim) { mon_fichier = le_fichier; dimension = la_dim; int index_point = mon_fichier.indexOf("."); if (index_point > 8) index_point = 8; else { if (index_point < 0) { index_point = mon_fichier.length(); if (index_point > 8) index_point = 8; } } if (index_point == 0) facr = "xxx"; else facr = mon_fichier.substring(0, index_point);

Quelques applications usuelles CHAPITRE 22

409

} public boolean coupe() { FileOutputStream out = null; FileWriter outacr = null; byte[] tampon = new byte[512]; int octets_lus = 512; // nombre doctets lus int octets_trans = 0; // nombre doctets transfrs int total = 0; // nombre total doctets transfrs int num_morceau = 1; String le_morceau = null; int cs1 = 0; int cs2 = 0; try { FileInputStream in = new FileInputStream(new File(mon_fichier)); try { outacr = new FileWriter(new File(facr + ".acr")); outacr.write(mon_fichier + fin); } catch (IOException e) { merreur = "Fichier " + facr + " ne peut tre crit"; return false; } while ((octets_lus = in.read(tampon)) > 0) { total += octets_lus; if (octets_trans == 0) { le_morceau = facr + "." + num_morceau; try { cs1 = num_morceau; // toujours diffrent si fichier identique cs2 = 0; out = new FileOutputStream(new File(le_morceau)); outacr.write(le_morceau + fin); } catch (IOException e) { merreur = "Fichier " + le_morceau + " ne peut tre crit"; return false; } } for (int j = 0; j < octets_lus; j++) { cs1 = cs1 ^ (int)tampon[j++]; if (j != octets_lus) { cs2 = cs2 ^ (int)tampon[j]; } }

410

Apprendre Java et C++ en parallle

try { out.write(tampon, 0, octets_lus); octets_trans += octets_lus; if (octets_trans == dimension) { out.close(); outacr.write(((256*cs1) + cs2) + fin); octets_trans = 0; num_morceau++; } } catch (FileNotFoundException e) { merreur = "Fichier " + le_morceau + " ne peut tre crit"; return false; } } if (octets_trans > 0) { outacr.write(((256*cs1) + cs2) + fin); out.close(); } outacr.write("EnD" + fin); outacr.write(total + fin); outacr.close(); in.close(); } catch (IOException e) { merreur = "Fichier " + mon_fichier + " n'existe pas: " + e; return false; } return true; } public String erreur_message() { return merreur; } public static void main(String[] args) { int args_len = args.length; if ((args_len < 1) || (args_len > 2)) { System.err.println("Nombre de paramtres invalide"); System.err.println("java Coupe fichier (pour disquettes)"); System.err.println("java Coupe dim fichier (en fichiers de dim >= 1024)"); System.err.println("java Coupe Kn fichier (en fichiers de n Kilo octets)"); System.err.println("java Coupe Mn fichier (en fichiers de n Mega octets)"); return; } int fi = 0; int adim = 1457664;

// dimension dune disquette

Quelques applications usuelles CHAPITRE 22

411

if (args_len == 2) { // dimension donne fi = 1; char first_char = args[0].charAt(0); if ((first_char == 'K') || (first_char == 'M')) { adim = 1024 * Integer.parseInt(args[0].substring(1, args[0].length())); if (first_char == 'M') adim *= 1024; } else { adim = Integer.parseInt(args[0]); if ((adim % 512) != 0) { System.err.println("La dimension doit tre un multiple de 512"); return; } } } Coupe ma_coupe = new Coupe(args[fi], adim); if (ma_coupe.coupe()) { System.out.println("Coupe termine correctement"); } else { System.err.println("Coupe erreur: " + ma_coupe.erreur_message()); } }

Nous coupons en C++


// coupe.cpp #include <string> #include <iostream> #include <sstream> #include <fstream> using namespace std; class Coupe private: string string int string { mon_fichier; facr; dimension; merreur;

public: Coupe(const string le_fichier, const int la_dim); bool coupe(); string erreur_message() { return merreur; } };

412

Apprendre Java et C++ en parallle

Coupe::Coupe(const string le_fichier, const int la_dim) { mon_fichier = le_fichier; dimension = la_dim; int index_point = mon_fichier.find("."); if (index_point > 8) index_point = 8; else { if (index_point < 0) { index_point = mon_fichier.length(); if (index_point > 8) index_point = 8; } } if (index_point == 0) facr = "xxx"; else facr = mon_fichier.substr(0, index_point); } bool Coupe::coupe() { ifstream in; ofstream out; ofstream outacr; string fileName; char tampon[512]; int octets_lus = 512; int octets_trans = 0; int total = 0; int num_morceau = 1; int cs1 = 0; int cs2 = 0;

// nombre doctets lus // nombre doctets transfrs // nombre total doctets transfrs

in.open(mon_fichier.c_str(), ios::in|ios::binary); if (in.fail() != 0) { merreur = "Fichier " + mon_fichier + " ne peut tre ouvert"; return false; } ostringstream oss; oss << facr << ".acr"; fileName = oss.str(); outacr.open(fileName.c_str(), ios::out); if (outacr.fail() != 0) { merreur = "Fichier " + fileName + " ne peut tre crit"; return false; } outacr << mon_fichier << endl; for (;;) {

Quelques applications usuelles CHAPITRE 22

413

in.read(tampon, 512); if ((octets_lus = in.gcount()) <= 0) break; total += octets_lus; if (octets_trans == 0) { ostringstream strout; strout << facr << "." << num_morceau; fileName = strout.str(); out.open(fileName.c_str(), ios::out|ios::binary); if (out.fail() != 0) { merreur = "Fichier " + fileName + " ne peut tre ouvert"; return false; } cs1 = num_morceau; // toujours diffrent si fichier identique cs2 = 0; outacr << fileName << endl; } for (int j = 0; j < octets_lus; j++) { cs1 = cs1 ^ (int)tampon[j++]; if (j != octets_lus) { cs2 = cs2 ^ (int)tampon[j]; } } out.write(tampon, octets_lus); octets_trans += octets_lus; if (octets_trans == dimension) { out.close(); outacr << ((256*cs1) + cs2) << endl; octets_trans = 0; num_morceau++; } } if (octets_trans > 0) { outacr << ((256*cs1) + cs2) << endl; out.close(); } outacr << "EnD" << endl; outacr << total << endl; outacr.close(); in.close(); return true; }

414

Apprendre Java et C++ en parallle

int main(int argc, char *argv[]) { string file_name; // nom du fichier couper int fi = 1; // position dans argv int cdim = 1457664; // dimension dune disquette if ((argc cerr << cerr << cerr << cerr << cerr << < 2) || (argc > 3)) "Nombre invalide de "coupe fichier "coupe dim fichier "coupe Kn fichier "coupe Mn fichier { paramtres" << endl; (pour disquettes)" << endl; (en fichiers de dim >= 1024 octets)" << endl; (en fichiers de n Kilo-octets)" << endl; (en fichiers de n mgaoctets)" << endl;

file_name = "prgm.exe"; cdim = 102400; // K100 // return -1; } else { if (argc == 3) { // dimension donnes fi = 2; char *pdimension; pdimension = &argv[1][0]; if ((argv[1][0] == 'K') || (argv[1][0] == 'M')) { pdimension++; istringstream(pdimension) >> cdim; cdim *= 1024; if (argv[1][0] == 'M') { cdim *= 1024; } } else { istringstream(pdimension) >> cdim; } } if (cdim < 1024) { cerr << "La dimension des fichiers doit tre >= 1024" << endl; return -1; } file_name = (char *)argv[fi]; } Coupe ma_coupe(file_name, cdim); if (ma_coupe.coupe() == false) { cerr << "Erreur: " << ma_coupe.erreur_message() << endl; return -1; }

Quelques applications usuelles CHAPITRE 22

415

cout << "Coupe termine correctement" << endl; return 0; }

La partie du main() est rduite un minimum. Si nous avons un ou plusieurs paramtres, nous noterons lutilisation de la variable fi pour laccs la position correcte du nom du chier dans le tableau des arguments (args et argv). Nous le comprendrons mieux aprs avoir examin la commande, ses paramtres et son rsultat :
coupe.exe K100 prgm.exe Coupe termine correctement

Nous couperons donc le chier prgm.exe en morceaux de 100 Ko. La vrication des cas derreurs aurait pu tre tendue. Accepter un K ou un M minuscule serait un choix aussi acceptable. Un paramtre avec une autre lettre pourrait donner un message derreur plus signicatif. Le lecteur devrait avoir t immdiatement surpris par linstruction :
if ((adim % 512) != 0)

Cette restriction du programme est essentielle pour une construction simple du programme. Durant lanalyse, nous avons constat que la valeur de 1 457 664, cest--dire la capacit maximale dune disquette haute densit, tait en fait un multiple de 512. Des coupes en kilo-octets ou mgaoctets sont aussi des multiples de 512. Comme nous devons gnrer plusieurs morceaux partir dun chier qui peut tre norme, il serait impensable de copier caractre par caractre. Ce serait beaucoup trop lent, et 512 est sans doute une limite infrieure, comme nous lavons vu au chapitre 15 sur les performances. Lors de la lecture par blocs de 512 octets et de la copie dans un morceau, il y a un moment o nous devrons fermer le morceau pour en ouvrir un autre. Comme les morceaux sont des multiples de 512, le processus de lecture va concider avec lcriture. Il ny aura pas la ncessit de copier les caractres restants depuis le tampon de lecture et de garder un index de position. De cette manire la conception et la structure du programme sen trouvent simplies. Le constructeur de la classe Coupe possde un minimum de fonctionnalits, et cest une bonne habitude. Le string facr est construit pour limiter le nom des morceaux dans un format DOS 8.3. Le xxx est un cas particulier o le nom du chier commence par un point, ce qui peut tre le cas sous Linux. Le code du main() et du constructeur est tout fait dans la ligne de la dnition dune classe qui pourrait tre reprise sans retouche avec une interface utilisateur graphique du style AWT ou Swing en Java. Tous les paramtres qui sont prpars, dcods et contrls dans le main() se feraient alors dans le code associ aux mthodes des classes AWT ou Swing. La mthode erreur_message() va aussi dans cette direction, car aucun message ne sera directement sorti par la mthode principale coupe(). En cas derreur, le programme main() dcidera de sortir le message derreur sur la console, comme procderait un programme UI, qui ferait apparatre ce message textuellement dans une fentre.

416

Apprendre Java et C++ en parallle

Un exemple de chier .acr


Si nous examinons le chier prgm.acr que nous avons gnr par la commande :
coupe.exe K100 prgm.exe

ci-dessus, nous y dcouvrirons ceci :


prgm.exe prgm.1 29947 prgm.2 5759 prgm.3 32392 prgm.4 32374 EnD 329253

Dans ce chier, nous retrouvons le nom du chier dorigine et les diffrents morceaux. Ces derniers sont entrelacs avec des nombres, dont nous comprendrons rapidement la signication. Dans le cas prsent, tous les chiers .n auront une dimension de 100 Ko, comme nous lavons dni dans la commande. Le dernier chiffre, aprs le code EnD, correspond la variable total, qui nous donne en fait la dimension du chier dorigine. Les variables cs1 et cs2 correspondent notre mcanisme pour identier la validit des chiers et les erreurs possibles. Cest cette valeur sur deux octets que nous retrouvons dans notre chier prgm.acr, avec le nom du chier coup. Un chier pourrait avoir t mal crit sur disquette ou coup pendant un transfert FTP. Nous utilisons loprateur ^ (XOR) sur chaque caractre pair et impair. Il y a un certain nombre de petits trucs, comme le cs1 initialis avec le numro du morceau, an de diminuer les risques, minimes, dobtenir une mme valeur. Enn, si la dimension du chier est impaire, il ne faudra pas calculer le dernier cs2. Il ne faudra pas stonner de retrouver des valeurs ngatives dans un chier .acr, cest tout fait correct.

Recollons les morceaux


La reconstruction du chier partir de morceaux dnis dans un chier .acr est un peu plus simple. Il suft en fait de lire les chiers, les uns aprs les autres, bloc par bloc, et de les insrer dans un nouveau chier aprs un certain nombre de vrications.

Nous collons en Java


import java.io.*; public class Colle { private String fichier_acr; private String merreur;

Quelques applications usuelles CHAPITRE 22

417

public Colle(String le_fichier) { fichier_acr = le_fichier; } public boolean colle() { String mon_fichier; String un_fichier; int dim_total = 0; // dimension du fichier construit byte[] tampon = new byte[512]; int octets_lus = 0; // nombre doctets lus int octets_trans = 0; // nombre doctets transfrs int num_morceau = 0; int cs = 0; // cs du fichier acr int cs1 = 0; int cs2 = 0; try { BufferedReader inacr = new BufferedReader(new FileReader(fichier_acr)); try { mon_fichier = inacr.readLine(); }

// le fichier final

catch(IOException ioe) { merreur = "Erreur de lecture du fichier .acr"; return false; } if (mon_fichier.length() > 50) { merreur = "Fichier trop long (>50) ou erreur de lecture du fichier .acr"; return false; } try { FileOutputStream out =new FileOutputStream(new File(mon_fichier)); try { for (;;) { un_fichier = inacr.readLine(); if (un_fichier == null) { merreur = "Fichier .acr incorrect"; return false; } if (un_fichier.equals("EnD")) { out.close(); dim_total = Integer.parseInt(inacr.readLine()); if (dim_total == octets_trans) return true; else {

418

Apprendre Java et C++ en parallle

merreur = "Nombre invalide de caractres transfrs"; return false; } } cs = Integer.parseInt(inacr.readLine()); num_morceau++; cs1 = num_morceau; cs2 = 0; FileInputStream in = new FileInputStream(un_fichier); try { for (;;) { octets_lus = in.read(tampon); if (octets_lus <= 0) break; for (int j = 0; j < octets_lus; j++) { cs1 = cs1 ^ (int)tampon[j++]; if (j != octets_lus) { cs2 = cs2 ^ (int)tampon[j]; } } out.write(tampon, 0, octets_lus); octets_trans += octets_lus; } in.close(); } catch(IOException ioe) { merreur = "Erreur de lecture pour " + un_fichier; return false; } if (cs != ((256*cs1) + cs2)) { merreur = "Acr invalide pour le fichier " + un_fichier; return false; } } } catch(IOException ioe) { merreur = "Fichier .acr invalide"; return false; } } catch (FileNotFoundException e) { merreur = "Le fichier " + mon_fichier + " ne peut tre crit"; return false; }

Quelques applications usuelles CHAPITRE 22

419

} catch (IOException e) { merreur = "Le fichier " + fichier_acr + " n'existe pas"; return false; } } public String erreur_message() { return merreur; } public static void main(String[] args) { int args_len = args.length; if (args.length != 1) { System.err.println("Nombre de paramtres invalides"); System.err.println("java Colle fichier_acr"); return; } String le_fichier_arc = args[0]; if (le_fichier_arc.lastIndexOf(".acr") != (le_fichier_arc.length() - 4)) { System.err.println("Ce n'est pas un fichier .acr"); return; } Colle ma_colle = new Colle(le_fichier_arc); if (ma_colle.colle()) { System.out.println("Colle termine correctement"); } else { System.err.println("Colle erreur: " + ma_colle.erreur_message()); } } }

Nous collons en C++


// colle.cpp #include <string> #include <iostream> #include <fstream> using namespace std; class Colle { private: string fichier_acr; string merreur;

420

Apprendre Java et C++ en parallle

public: Colle(const string le_fichier); bool colle(); string erreur_message() { return merreur; } };

Colle::Colle(const string le_fichier) { fichier_acr = le_fichier; }

bool Colle::colle() { string mon_fichier; string un_fichier; // ifstream in; ifstream inacr; ofstream out; int dim_total = 0; // dimension du fichier construit char tampon[512]; int octets_lus = 0; // nombre doctets lus int octets_trans = 0; // nombre doctets transfrs int num_morceau = 0; int cs = 0; // cs du fichier acr int cs1 = 0; int cs2 = 0; inacr.open(fichier_acr.c_str(), ios::in); if (inacr.fail() != 0) { merreur = "Erreur de lecture du fichier .acr"; return false; } inacr >> mon_fichier; // le fichier final if (mon_fichier.length() > 50) { merreur = "Fichier trop long (>50) ou erreur de lecture du fichier .acr"; return false; } out.open(mon_fichier.c_str(), ios::out|ios::binary); if (out.fail() != 0) { merreur = "Le fichier " + string(mon_fichier)+ " ne peut tre crit"; return false; } for (;;) { inacr >> un_fichier;

Quelques applications usuelles CHAPITRE 22

421

if (un_fichier.length() < 1) { merreur = "Fichier .acr incorrect"; return false; } if (un_fichier.compare("EnD") == 0) { out.close(); inacr >> dim_total; if (dim_total == octets_trans) return true; else { merreur = "Nombre invalide de caractres transfrs"; return false; } } inacr >> cs; num_morceau++; cs1 = num_morceau; cs2 = 0; cout << "TEST: [" << un_fichier.c_str() << "]" << endl; ifstream in; in.open(un_fichier.c_str(), ios::in|ios::binary); if (in.fail() != 0) { merreur = "fichier " + string(un_fichier) + " ne peut tre ouvert"; return false; } for (;;) { in.read(tampon, 512); octets_lus = in.gcount(); if (octets_lus <= 0) break; for (int j = 0; j < octets_lus; j++) { cs1 = cs1 ^ (int)tampon[j++]; if (j != octets_lus) { cs2 = cs2 ^ (int)tampon[j]; } } out.write(tampon, octets_lus); octets_trans += octets_lus; } in.close(); if (cs != ((256*cs1) + cs2)) {

422

Apprendre Java et C++ en parallle

merreur = "Acr invalide pour le fichier " + string(un_fichier); return false; } } }

int main(int argc, char *argv[]) { string le_fichier_arc; if (argc != 2) { cerr << "Nombre de paramtres invalides"