Didacticiel Bases de programmation en C++
Transcription
Didacticiel Bases de programmation en C++
1 Université Grenoble alpes. Auteur :Frédéric Faure Version : 10 septembre 2016. Didacticiel Bases de programmation en C++ Table des matières 1 Introduction 8 1.1 Choix du système d'exploitation . . . . . . . . . . . . . . . . . . . . . . . . 9 1.2 Environnement de programmation . . . . . . . . . . . . . . . . . . . . . . . 9 1.2.1 (conseillé) Programmer avec l'éditeur emacs et une fenêtre de commandes 1.2.2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . (optionnel) Programmer avec l'environnement codeblocks . . . . . 10 11 I Bases du langage C++ 13 2 Le tout début 14 3 Lire le clavier et acher à l'écran 16 4 3.1 Achage dans une console . . . . . . . . . . . . . . . . . . . . . . . . . . . 16 3.2 Lire le clavier 17 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Déclaration et aectation des objets 4.1 4.2 Objets de base 19 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Les déclarations d'objets de base 4.1.2 Initialisation d'un objet de base . . . . . . . . . . . . . . . . . . . . 19 4.1.2.1 . . . . . . . . . . . . . . . . . . . . . . . . . . 20 4.1.3 Achage des nombres avec une précision donnée . . . . . . . . . . . 21 4.1.4 Attention à la division Euclidienne 4.1.5 Pour générer un nombre entier p aléatoire 4.1.6 Quelques opérations élémentaires 4.1.7 Quelques opérations sur les bits (base deux) Remarques Objets plus élaborés 4.2.1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19 21 22 22 . . . . . . . . . . . . . 23 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24 Chaines de caractères (string) 4.2.1.1 . . . . . . . . . . . . . . . . . . . 19 4.1.1 . . . . . . . . . . . . . . . . . . . . . Conversion de chires en chaine de caractère et inversement 24 25 4.2.2 Nombres complexes . . . . . . . . . . . . . . . . . . . . . . . . . . . 25 4.2.3 Liste d'objets : vector . . . . . . . . . . . . . . . . . . . . . . . . . . 26 4.2.4 Vecteurs et matrices pour l'algèbre linéaire . . . . . . . . . . . . . . 27 4.2.5 tuple : ensemble d'objets divers 28 2 . . . . . . . . . . . . . . . . . . . . 3 TABLE DES MATIÈRES 5 6 Les instructions de base 30 5.1 La boucle "for" 30 5.2 Ecrire une condition 31 5.2.0.2 Les opérateurs logiques . . . . . . . . . . . . . . . . . . . 32 5.2.0.3 Remarque . . . . . . . . . . . . . . . . . . . . . . . . . . . 32 5.4 La boucle while (..) {..} ; 5.5 L'instruction if 5.6 break : pour sortir de la boucle. continue : pour sauter l'instruction. . . . . 34 . . . . . . . . . . . . . . . . . . . . . . . . . 32 . . . . . . . . . . . . . . . . . . . . . . . . . . . 32 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33 Les fonctions 36 6.1 Exemple de fonction qui ne renvoit rien. 6.2 Exemple de fonction qui renvoit un objet . . . . . . . . . . . . . . . . . . . 37 6.3 Paramètres des fonctions par référence . . . . . . . . . . . . . . . . . . . . 38 6.4 La surcharge des fonctions. . . . . . . . . . . . . . . . . . . . . . . . . . . 39 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39 Exemple : . . . . . . . . . . . . . . . . . . . Les chiers 7.2 36 41 Ecriture de données dans un chier . . . . . . . . . . . . . . . . . . . . . . 41 7.1.0.1 Remarque : . . . . . . . . . . . . . . . . . . . . . . . . . . 41 7.1.0.2 Exercice . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42 Lecture de données depuis un chier 7.2.0.1 Remarques : . . . . . . . . . . . . . . . . . . . . . 42 . . . . . . . . . . . . . . . . . . . . . . . . . 43 Les pointeurs 44 8.1 Déclaration et aectation d'un pointeur . . . . . . . . . . . . . . . . . . . . 44 8.2 Allocation dynamique de la mémoire 8.2.0.1 9 . . . . . . . . . . . . . . 31 Les opérateurs de comparaison : La boucle do {..} while (..) ; 7.1 8 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5.2.0.1 5.3 6.4.1 7 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Remarques . . . . . . . . . . . . . . . . . . . . . 45 . . . . . . . . . . . . . . . . . . . . . . . . . . 46 Graphisme avec la librairie ROOT 48 9.1 Commande de compilation . . . . . . . . . . . . . . . . . . . . . . . . . . . 48 9.2 Exemple de départ qui dessine un cercle 49 9.3 Utilisation des pointeurs avec ROOT 9.3.1 9.3.2 9.4 . . . . . . . . . . . . . . . . . . . . . 51 . . . . . . . . . . . . . . . . . . . . . . . 52 . . . . . . . . . . . . . . . . . . . . . . . . . 53 Utiliser une liste d'ellipses 9.3.1.1 Remarques : . . . . . . . . . . . . . . . . . . . Utiliser un pointeur sur l'ellipse . . . . . . . . . . . . . . . . . . . . 53 9.3.2.1 Remarques : . . . . . . . . . . . . . . . . . . . . . . . . . . 54 9.3.2.2 Informations succintes sur les pointeurs . . . . . . . . . . . 55 Représentation graphique des vecteurs et matrices de Armadillo . . . . . . 55 4 TABLE DES MATIÈRES 10 Création d'une classe 61 10.1 Exemple . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61 10.1.1 Commentaires . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62 10.2 Utilisation de pointeurs sur objets . . . . . . . . . . . . . . . . . . . . . . . 64 10.3 Surdénitions d'opérateurs . . . . . . . . . . . . . . . . . . . . . . . . . . . 65 10.3.1 Exemple d'opérateur binaire . . . . . . . . . . . . . . . . . . . . . . 65 10.3.2 Commentaire 65 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10.3.3 Exemple d'un opérateur unaire . . . . . . . . . . . . . . . . . . . . 66 10.3.4 Commentaires . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67 10.4 Fonctions amies . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67 10.4.1 Exemple . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67 10.4.2 Commentaire . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 68 10.4.3 Exemple de cout << . . . . . . . . . . . . . . . . . . . . . . . . . . 68 10.5 Vecteurs de taille variable . . . . . . . . . . . . . . . . . . . . . . . . . . . 69 10.5.1 Constructeur par recopie . . . . . . . . . . . . . . . . . . . . . . . . 69 10.5.2 Opérateur d'aectation = 70 . . . . . . . . . . . . . . . . . . . . . . . 11 Micro-projets 72 11.1 Suite de Syracuse . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72 11.2 Pendule simple . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73 11.3 La fractale du dragon . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74 11.4 Equation de la chaleur . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76 11.6 Algorithme de MonteCarlo pour le modèle d'Ising . . . . . . . . . . . . . . 76 11.5 Le jeu de la vie 11.6.1 Algorithme de Monte-Carlo . . . . . . . . . . . . . . . . . . . . . . 77 II Projet 78 III Compléments sur le langage C++ 80 12 Librairie Root : graphisme et autre.. (compléments) 82 12.1 Graphisme de base 2D . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 82 12.1.1 Objets graphiques . . . . . . . . . . . . . . . . . . . . . . . . . . . . 82 12.1.2 Plusieurs fenêtres . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83 12.1.3 Un cadre avec des axes gradués : DrawFrame . . . . . . . . . . . . . 84 12.1.4 Un seul axe gradué : TGaxis . . . . . . . . . . . . . . . . . . . . . . 85 12.2 Histogrammes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 86 12.2.1 Courbe (x,y) avec axes à partir de tableaux de valeurs x[] y[] : TGraph 86 12.2.2 Histograme 1Dim à partir de tableau y[] : TH1F . . . . . . . . . . . 86 12.2.3 Courbe a partir de l'expression d'une formule y=f(x) : TF1 . . . . . 87 12.2.4 Surface 2Dim : z=f(x,y) à partir d'un tableau z[i,j] : TH2F . . . . . 88 5 TABLE DES MATIÈRES 12.2.5 Surface 2D a partir de l'expression d'une formule z=f(x,y) : TF2 . . 88 12.2.6 Crée histogramme 1D par tirage aléatoire et ajustement par une formule . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12.2.7 Champ de vecteur 12.3 Utilisation de la souris 12.3.0.1 89 . . . . . . . . . . . . . . . . . . . . . . . . . . . 90 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 92 Pour gérer la souris lorsque le programme calcule . . . . . 94 12.4 Comment créer un chier gif animé. . . . . . . . . . . . . . . . . . . . . . . 94 12.5 Boutons de commandes (Widgets) . . . . . . . . . . . . . . . . . . . . . . . 95 12.6 Autres trucs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 103 12.6.0.1 Nombre aléatoire avec Root . . . . . . . . . . . . . . . . . 103 12.6.0.2 Options générales de Root . . . . . . . . . . . . . . . . . . 103 13 Mesure du temps 105 13.1 Mesure de la date et du temps écoulé . . . . . . . . . . . . . . . . . . . . . 13.1.0.1 Mesure très précise du temps : 13.1.0.2 Achage de la date 105 . . . . . . . . . . . . . . . 105 . . . . . . . . . . . . . . . . . . . . . 106 13.2 Attendre . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13.2.1 Attendre une certaine durée 13.2.2 Attendre jusqu'à une certaine date 106 . . . . . . . . . . . . . . . . . . 14 Ecrire et lire des données en binaires sur un chier 14.1 Fichiers en format texte 106 . . . . . . . . . . . . . . . . . . . . . . 106 108 . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14.2 Fichiers en format binaire 14.2.0.1 Exemple 14.2.0.2 Remarques 108 . . . . . . . . . . . . . . . . . . . . . . . . . . . 109 . . . . . . . . . . . . . . . . . . . . . . . . . . . 109 . . . . . . . . . . . . . . . . . . . . . . . . . . 110 15 Quelques commandes utiles 111 15.0.1 La commande system . . . . . . . . . . . . . . . . . . . . . . . . . . 111 16 Intégrer des équations diérentielles ordinaires (EDO) avec la librairie ODEINT 112 17 Lecture et écriture d'un chier son de format WAV 117 17.0.0.1 Introduction . . . . . . . . . . . . . . . . . . . . . . . . . 117 17.0.0.2 Travail préalable pour le projet C++ . . . . . . . . . . . . 117 17.0.1 Programme pour écrire un chier WAV : 17.0.2 Programme pour lire un chier WAV : . . . . . . . . . . . . . . . 117 . . . . . . . . . . . . . . . . 120 18 Gestion de la carte son (audio) avec Jack 122 19 Gestion de la carte son (audio) avec la librairie sndio 123 19.1 Installation (à faire la première fois) . . . . . . . . . . . . . . . . . . . . . . 19.1.0.1 123 Travail préalable pour le projet C++ . . . . . . . . . . . . 124 19.2 Utilisation du micro (entrée) et du haut-parleur (sortie) . . . . . . . . . . . 124 6 TABLE DES MATIÈRES 19.2.1 Le micro . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19.2.1.1 Exemple : détection de note de musique en temps réel 124 . . 126 19.2.2 Le haut-parleur . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 129 20 Plusieurs programmes en parallèle qui communiquent, avec thread 20.1 Lancement de threads ou processus 130 . . . . . . . . . . . . . . . . . . . . . . 131 20.1.0.1 Commande de compilation : . . . . . . . . . . . . . . . . . 131 20.1.0.2 Commentaires 132 20.1.0.3 Lancer un thread d'une fonction membre de classe 20.1.0.4 Lancer une liste de thread (tableau) 20.1.0.5 Lancer une liste de thread (vector) . . . . . . . . . . . . . . . . . . . . . . . . . . . . 132 . . . . . . . . . . . . 133 . . . . . . . . . . . . . 133 . . . . . . . . . . . . . . . . . . . . . . 134 . . . . . . . . . . . . . . . . . . . . . . . . . 134 20.2 Mutex pour annoncer occupé/libre 20.2.0.1 Introduction 20.2.0.2 Remarques . . . . . . . . . . . . . . . . . . . . . . . . . . 134 20.2.0.3 Exemple . . . . . . . . . . . . . . . . . . . . . . . . . . . . 135 20.2.0.4 Résultat : . . . . . . . . . . . . . . . . . . . . . . . . . . . 135 20.3 Communications : variables conditionnelles pour réveiller un programme en attente . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20.3.0.1 Exemple de base où un thread endormi est reveillé par un autre. 20.3.0.2 136 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Remarques . . . . . . . . . . . . . . . . . . . . . . . . . . 21 Messages MIDI (Musical) avec la librairie sndio 136 137 143 21.1 Installation (à faire la première fois) . . . . . . . . . . . . . . . . . . . . . . 143 21.2 Exemple élémentaire sur l'envoi et reception de messages MIDI . . . . . . . 143 21.3 Exemple d'envoie de message MIDI à un synthétiseur . . . . . . . . . . . . 147 21.4 Reception de notes midi depuis un piano électrique 151 . . . . . . . . . . . . . 22 Convertir un programme C++ en Javascript avec emscripten 154 23 Autres librairies conseillées (?) 155 23.1 algèbre linéaire . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 155 23.2 Images, vidéos, graphisme et boites de dialogues . . . . . . . . . . . . . . . 155 24 Le traitement d'images et de vidéos avec la librairie OpenCV 156 24.1 Commande de compilation . . . . . . . . . . . . . . . . . . . . . . . . . . . 156 24.2 Lecture et ecriture de chiers images 156 24.2.0.1 . . . . . . . . . . . . . . . . . . . . . Fonctions utiles sur les images . . . . . . . . . . . . . . . 157 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 158 24.4 Lecture et écriture d'un chier video ou de la webcam . . . . . . . . . . . . 161 24.5 Détection d'objet par leur couleur . . . . . . . . . . . . . . . . . . . . . . . 162 24.6 Détection et suivit d'objet de couleur avec l'algorithme CamShift 164 24.3 Utilisation de la souris . . . . . 7 TABLE DES MATIÈRES 25 Gestion de projets avec chiers .h, Makele, Git et GitHub 25.1 Projet avec plusieurs chiers, chier .h . . . . . . . . . . . . . . . . . . . . 172 172 25.1.0.1 Résultat du programme : . . . . . . . . . . . . . . . . . . 172 25.1.0.2 Code c++ . . . . . . . . . . . . . . . . . . . . . . . . . . . 172 25.1.0.3 Commandes de compilation . . . . . . . . . . . . . . . . . 174 25.2 Un programme Makele pour gérer la compilation . . . . . . . . . . . . . . 175 25.2.1 Un programme Makele plus pratique . . . . . . . . . . . . . . . . . 176 25.3 Gestion de versions d'un projet avec Git 25.3.0.1 . . . . . . . . . . . . . . . . . . . Utilisation de Git sur son ordinateur 176 . . . . . . . . . . . . 177 25.4 Collaboration et partage de projets sur internet avec GitHub . . . . . . . . 177 Chapitre 1 Introduction Ce didacticiel vous permettra d'appréhender le langage de programmation C++ (version C++11) et ses avantages. Dans ce didacticiel, les exercices marqués par (*) seront vériés par l'enseignant pendant les séances. Il y a de nombreux liens html qui renvoit vers plus d'informations. Le grand avantage du langage C++ par rapport au langage C est la programma- tion objet. On peut utiliser des objets appartenant à des classes déjà existantes ou à des classes que l'on a fabriqué soi-même. Par exemple, il existe la classe des nombres complexes. Les objets appartenant à cette classe sont des nombres complexes. Il nous sura de déclarer trois objets a, b et c comme appartenant à la classe des nombres complexes, et l'on pourra directement écrire a=b+c ou n'importe quelle autre expression mathématique. Comme autre exemple, il y a la classe de matrice complexe : on peut déclarer trois objets et écrire A, B et C appartenant à cette classe A=B+C ou n'importe quelle autre expression mathématique matricielle. Vers la n de ce cours, nous aborderons l'aspect fabrication de classe. Pourquoi enseigner le C++ à l'université, dans la lière physique ? d'une part c'est un langage qui est très bien adapté pour la simulation ou l'analyse de problèmes physiques et de façon plus générale pour le travail scientique. Dans les laboratoires il a remplacé peu à peu le langage fortran. Le langage python est un autre langage qui utilise aussi des classes et est aussi très utilisé dans le domaine scientique. Un programme en python est peut être plus simple à mettre en oeuvre qu'un programme en C++, c'est une histoire de goût et d'habitude, mais il est aussi beaucoup plus lent à l'exécution. Un avantage du langage python est qu'il est interprété : on peut exécuter un programme ligne après ligne alors que le langage C++ est compilé : un programme appelé compilateur le transforme en langage machine (chier executable) avant qu'il ne soit exécuté et cela le rend rapide. La dernière version C++11 fait du C++ un langage aussi conviviale et simple d'utilisation que le langage python (et plus rapide !). Nouvelles version du C++:les récentes versions C++11 puis C++14 ont pour vocation de rendre le C++ plus facile à apprendre et toujours performant. Références : 8 CHAPITRE 1. 9 INTRODUCTION Sur le web : voici un site très utile : http://www.cplusplus.com/. Il contient un tutorial de très bonne qualité, où les exemples peuvent être essayé en ligne (avec la petite icône edit & run à droite de example, essayez par exemple sur setprecision) !. Il contient aussi une documentation complète au langage C++. Un autre site en français :cppreference.com ou en anglais : cppreference.com. Une façon pratique de se documenter sur une fonction précise est d'utiliser le moteur de recherche de google. Par exemple pour acher un nombre avec une certaine précision numérique, si vous ne savez pas comment faire, tapez quelques mots clef comme achage precision c++ dans la fenêtre de recherche de google, qui vous amènera sur une page de forum avec un exemple. Vous y verrez que la fonction à utiliser est setprecision. Ensuite pour avoir une bonne documentation sur cette fonction et des exemples que l'on peut copier/coller, tapez toujours dans google cplusplus setprecision. Signalons le cours en ligne en francais sur le C++ de Coursera sur : ce sont des vidéos courtes, ou Cours de C++ de OpenClassRoom en français. La page web de Bjarne Stroustrup, auteur du langage C++, et en particulier la FAQ sur le C++11 Livre pour le langage C++ : Programmer en C++ de Claude Delannoy, éditions Eyrolles The C++ programming langage de Bjarne Stroustroup, 4eme édition. Livre pour le langage C : Le langage C de B.W. Kernighan et D.M. Ritchie edition Masson. 1.1 Choix du système d'exploitation Ce didacticiel est indépendant du système d'exploitation. Cependant en séance on travaille avec Linux et plus particulièrement avec la distribution Xubuntu ou Ubuntu. On conseille cette distribution gratuite qui peut être installée sur votre ordinateur personnel en suivant ce lien : installer Ubuntu. Il y a cependant possibilité d'utiliser d'autres systèmes comme windows, mac-os, etc... Dans ce didacticiel, nous utiliserons les logiciels et librairies suivantes qui sont en général téléchargeables depuis votre distribution : Codeblocks. Root. Armadillo. 1.2 Environnement de programmation Avec un petit exemple nous allons voir comment : 1. écrire le code d'un programme en C++ CHAPITRE 1. 2. le 10 INTRODUCTION compiler, c'est à dire que l'ordinateur traduit le programme C++ en un pro- gramme executable (en langage machine) qu'il pourra exécuter ensuite. 3. éxécuter le programme 4. éventuellement de le débugger (cela signie recherche d'erreurs) : si le programme ne fonctionne pas comme on le désire, on l'éxécute instruction après instruction an de suivre ce qu'il fait pas à pas, dans le but de trouver un bug (= une erreur). Dans la Section suivante nous eectuons ces taches avec l'éditeur emacs et une fenêtre de commandes (terminal). 1.2.1 (conseillé) Programmer avec l'éditeur emacs et une fenêtre de commandes Lancer le 1. Ouvrir une fenêtre de commandes ( ou terminal). Se placer dans le bon répertoire pwd (qui dit dans quel dossier on est situé), ls (qui les chiers et dossiers) , cd dossier ( : qui passe au dossier indiqué), cd .. passe au dossier parent), cd . (qui reste dans le même dossier, c'est inutile), de travail avec les commandes liste (qui cd (qui revient au dossier de départ), 2. Ecrire la commande emacs & qui lance l'éditeur. Le signe & signie que le ter- minal reste utilisable. Dans emacs, une fois pour toutes, cocher dans le menu Options/UseCUAKeys et Options/SaveOptions qui vous permettra d'utiliser les raccourcis clavier C-c C-v pour copier/coller. 3. Dans emacs, ouvrir un nouveau chier projet1.cc qui sera vide la première fois. Le suxe .cc signie qu'il contiendra du C++ (ou peut aussi choisir le suxe .cpp). Copier /coller le code suivant : #include <iostream> using namespace std; int main() { cout << "Bonjour!" << endl; return 0; } et sauvegarder ce chier qui contient le code C++ de votre pro- gramme. (Observer les raccourcis clavier proposés). 4. Dans emacs ouvir un nouveau chier Makefile qui sera vide la première fois. Co- pier/coller le code de compilation suivant : all: g++ projet1.cc -o projet1 -std=c++11 -lm et sauvegarder ce chier qui contient les instructions pour compiler votre programme. Attention : au début de la deuxieme ligne rajouter un espace CHAPITRE 1. 11 INTRODUCTION de tabulation avant g++. Remarque : on peut basculer d'un chier à l'autre dans emacs par le menu Buers ou par le clavier avec 5. C-x C-→. Compiler le programme par la commande du menu : Tools/Compile.. qui fait apparaitre en bas de page : make -k (puis faire entrée).Remarque : cela a pour eet d'éxécuter la commande de compilation qui se trouve dans le chier Makele : elle dit qu'il faut utiliser le compilateur g++ et qu'à partir du chier créer un chier éxecutable appelé projet1. projet1.cc il faut Si tout se passe bien le dernier message est compilation nished. 6. Dans le terminal, placez vous dans le répertoire qui contient votre projet, vériez qu'il contient le chier executable ./projet1 projet1 , et éxécutez ce chier en écrivant : Remarque 1.2.1. 1. Dans le programme projet1.cc ajoutez une erreur, par exemple en enlevant un signe ; en n de ligne. Recompiler le programme et observer que le compilateur détecte l'erreur et en cliquant sur le message d'erreur, emacs vous place à la ligne où est l'erreur à corriger. 2. Dans les instructions ci-dessus nous vous avons proposé d'utiliser un chier Makele qui sera surtout utile pour les projets de programation en C++. Vous pouvez plus directement compiler votre programme en écrivant dans un terminal la commande de compilation : g++ projet1.cc -o projet1 -std=c++11 -lm le désavantage est que pour corriger l'erreur il faut soi même se placer sur la ligne indiquée. Ou encore ecrire la commande d'éxecuter le chier 3. Makefile. make dans le terminal qui a pour eet emacs peut mettre votre programme en forme : pour cela sélectionnez tout le texte et appuyez sur la touche tabulation. Il apparait des décalages de lignes convenables, mais non obligatoires. 4. Pour écrire le programme, à la place de emacs vous pouvez utiliser tout autre éditeur de texte. Remarque 1.2.2. Voici une documentation sur les commandes possibles bash pour la fenêtre terminal. 1.2.2 (optionnel) Programmer avec l'environnement codeblocks Au lieu d'utiliser emacs comme ci-dessus vous pouvez utiliser un environnement de programmation plus convivial ou évolué comme codeblocks. Le désavantage est qu'il necessite un certain apprentissage, qu'il eectue automatique certaines taches à votre place et vous perdez un peu le controle de votre programmation. Remarque 1.2.3. Il existe aussi d'autres environnements de développement sophistiqués comme eclipse ou emacs. CHAPITRE 1. 12 INTRODUCTION Exercice 1.2.4. 1. Lancer le programme codeblocks. 2. Choisir "create a new project 3. Faire les choix : projet de type "Console application, C++ ; lui donner un nom, comme "projet1 et choisir le répertoire où il sera hébergé. Dans le répertoire Sources, il apparait un chier appelé main.cpp contenant le code C++ suivant : #include <iostream> using namespace std; int main() { } cout << "Hello world!" << endl; return 0; 4. Une fois pour toute on va choisir la version 11 du C++. Pour cela, dans le menu Settings/Compiler/Compiler Flags, cocher l'option de compilation -std=c++11 5. La commande du menu Plugins/SourceCodeFormatter vous permet de mettre votre programme en forme standard. Faites le souvent. 6. Pour compiler et exécuter ce programme, taper F9 (ou build and run dans le menu). Il apparait alors une fenêtre (console) avec le message "Hello world !. Dans la suite, vous pouvez modier le code de ce programme pour l'adapter aux exemples donnés ci-dessous. Vous pouvez aussi créer d'autres projets de la même manière. Pour copier un exemple, vous pouvez "copier le code donné en exemple dans ce tutoriel et le "coller dans votre éditeur, sans le retaper. Première partie Bases du langage C++ 13 Chapitre 2 Le tout début Un petit programme pour commencer : Voici un petit programme C++ qui additionne deux nombres entiers a et b et aecte le résultat dans c. La classe int permet justement de stocker des nombres entiers. (Cela vient de integer en anglais). /*! -----------------------ceci est votre premier programme -------------------------*/ #include <iostream> using namespace std; int main() //! entête du programme principal { // début cout << "debut"<<endl; int a,b; // déclaration des objets a et b de la classe int. a=1; // affectation: a prend la valeur 1 b=a+1; // affectation b prend la valeur 2 int c; // déclaration de l'objet c de la classe int. c=a+b; // affectation: c prend la valeur a+b c'est à dire 3 cout << "fin"<<endl; // fin } Exercice 2.0.1. Recopiez ce programme dans un chier prog.cc , modier la commande de compilation de Makele pour le compiler et exécutez le. 14 CHAPITRE 2. 15 LE TOUT DÉBUT Résultat du programme : debut fin Remarque 2.0.2. 1. Le point virgule sépare les instructions. 2. Les commentaires doivent débuter par //. Le reste de la ligne est alors considéré comme un commentaire. Un commentaire n'a aucune inuence sur le programme, il sert en général à expliquer ce que fait le programme pour le rendre compréhensible au lecteur. Une autre possibilité est de commencer le commentaire par /* et de nir quelques lignes après par */. 3. Le début et la n d'un bloc d'instructions sont respectivement { et } 4. "main " veut dire "principal " en anglais. C'est le début du programme principal ou de la fonction principale. Les parenthèses () signient que le programme principal n'utilise aucun paramètre dans cet exemple. Lorsque vous lancez votre programme, son exécution commence toujours par cette fonction principale. Chapitre 3 Lire le clavier et acher à l'écran 3.1 Achage dans une console Dans le programme précédent, nous avons aecté la somme de deux entiers a et b dans un autre entier c. Mais nous n'avons pas aché le résultat à l'écran. Pour cela on cout. Cet objet représente l'écran. C'est un objet de la classe ostream (classe standard du C++) qui est dénie dans le chier : iostream. Il ne faut donc pas oublier d'inclure ce chier avec la commande #include en début du programme pour que, lors de la compilation, l'ordinateur puisse savoir ce que veut dire cout. utilise l'objet #include <iostream> using namespace std; int main() { int a,b; // déclaration des objets a et b de la classe int a=1; // affectation: a prend la valeur 1 b=a+1; // affectation b prend la valeur 2 int c; // déclaration de l objet c de classe int c=a+b; // affectation: c prend la valeur a+b c'est à dire 3 // affichage à l'écran: cout<<"la somme de "<< a <<" et "<< b <<" vaut "<< c <<endl; } Résultat du programme : la somme de 1 et 2 vaut 3 Exercice 3.1.1. Recopiez ce programme et exécutez le. Remarque 3.1.2. 16 CHAPITRE 3. 17 LIRE LE CLAVIER ET AFFICHER À L'ÉCRAN 1. Les caractères entre guillements " " sont considérés comme une chaîne de caractères et écrits tels quels à l'écran . Par contre les caractères a, b,c ne sont pas entre ". Cela signie que ce sont des noms d'objets, et qu'il faut acher le contenu de ces objets (et non pas leur nom). 2. Le terme cout symbolise l'écran. Les signes << sont évocateurs : on envoit ce qui suit vers l'écran. 3. Ce symbole endl cout signie que l'on va à la ligne (end of line = n de ligne). appartient à la bibliothèque iostream qui est chargée grâce #include <iostream> . En principe pour l'achage il faustd::cout car cout appartient à l'espace de nom appelé std comme standard ; mais la deuxième ligne using namespace std; signie que l'on pourra écrire plus simplement cout. (Il peut être utile de ne pas faire cette simplication si le symbôle cout est utilisé par une autre bibliothèque dans un autre espace de nom comme std2 . On pourra alors les distinguer : std::cout et std2::cout). à la première ligne drait écrire 4. iostream est un chier déjà présent sur l'ordinateur. Ce chier contient des informations sur des commandes C++ d'achage à l'écran et de saisie de touches appuyées au clavier. Iostream vient de l'anglais : Input (=entrée) , Output (=sortie) , Stream (=ux d'information). 5. cout est un objet C++ de la classe ostream. Cet objet est associé à l'écran comme expliqué ci-dessus. Le signe << est un opérateur de cette classe à qui on a donné le sens précis d'acher à l'écran. Tout cela est contenu dans le chier iostream qui est lui même un programme C++. L'existence de cette classe simplie donc la programmation pour les utilisateurs. C'est l'esprit du C++. Vous apprendrez à écrire vous même des classes et des opérateurs par la suite. Exercice 3.1.3. (*) "\t" permet d'acher une tabulation (i.e. sauter un espace jusqu'à la colonne sui- vante). Modier le programme précédent pour acher : objets: valeurs: 3.2 a 1 b 2 c=a+b 3 Lire le clavier Il peut être intéressant que l'utilisateur puisse lui-même entrer les valeurs de a et b. Pour cela l'ordinateur doit attendre que l'utilisateur entre les données au clavier et les valide par la touche entrée. #include <iostream> using namespace std; int main() { CHAPITRE 3. LIRE LE CLAVIER ET AFFICHER À L'ÉCRAN 18 int a,b; // déclaration des objets a et b de la classe int cout <<"Quelle est la valeur de a ? "<<flush; cin >> a; // lire a au clavier, attendre return cout <<"Quelle est la valeur de b? "<<flush; cin >> b; // entrer b au clavier puis return int c; // déclaration de la objet c de la classe int c = a + b; // affectation: c prend la valeur a+b // affichage à l'écran: cout <<"la somme de "<<a<<" et "<<b<<" vaut "<<c<<endl ; } Exercice 3.2.1. Recopier ce programme et exécuter le. Remarque 3.2.2. 1. L'objet cin appartient à la classe istream et représente le clavier. Le signe >> est un opérateur associé à cet objet et qui a pour eet de transférer les données tapées au clavier dans l'objet qui suit (une fois la touche entrée enfoncée). flush à la n de la phrase d'achage à pour eet d'acher la phrase sans faire de retour à la ligne. Si on ne met rien (ni flush, ni endl) la 2. L'instruction à l'écran phrase n'apparait pas forcément tout de suite à l'écran. Chapitre 4 Déclaration et aectation des objets 4.1 Objets de base 4.1.1 Les déclarations d'objets de base Pour stocker une information dans un programme, on utilise un objet qui est symbolisé par une lettre ou un mot. (Comme a,b,c précédement). On choisit la classe de cet objet, selon la nature de l'information que l'on veut stocker (nombre entier, nombre à virgule, nombre complexe, ou série de lettres, ou matrice, ...). Voici les diérentes classes de base qui existent en C++ (il y en a d'autres, voir variables) : classe objet; int a; double d; char e; bool v; char * t; Déclaration : signication Limites nombre entier -2147483648 à 2147483647 ±10±308 à 10−9 près nombre réel double précision caractère booléen : vrai (true) ou faux (false) chaîne de caractères Remarque 4.1.1. Les classes ci-dessus sont les classes de base standard du langage C++ qui existent aussi en langage C. Dans le vocabulaire du C, on dirait plutot de classe, et variable à la place de objet. Par exemple en écrivant que d est une variable du type double. type à la place double d; on dirait 4.1.2 Initialisation d'un objet de base On peut déclarer un objet et l'initialiser en même temps de diérentes manières : #include <iostream> 19 CHAPITRE 4. 20 DÉCLARATION ET AFFECTATION DES OBJETS using namespace std; int main () { int i = 0; double x = 1.23; double x1(3.4),x2(6); // equivalent a x1=3.4 double y(x1), y2 = x2; char mon_caractere_prefere = 'X'; const char * texte = "que dire de plus?"; char c = texte[0]; // premier caractere auto z = x + 2; { double x=5; } } 4.1.2.1 Remarques 1. i est un int qui vaut 0, x un oat qui vaut 1.23, x1 est un double qui vaut 3.4, etc.. auto double. 2. Le terme est un signie que le compilateur devine lui même la classe de l'objet, ici z 3. Dans le nom des objets, le langage C++ fait la diérence entre les majuscules et les minuscules. On peut choisir ce que l'on veut et utiliser même des chires et le caractère _. 4. On peut initialiser un objet avec des paranthèses comme x1(3.4). C'est équivalent x1=3.4. On peut initialiser un objet avec un objet déjà existant comme y2 = x2. que d'écrire y(x1) ou 5. Un caractère est entre le signe ', comme 'X'. 6. Un texte (chaîne de caractère) est entre le signe ". Ici l'objet const char * texte est du type qui signie que c'est une chaine de caractères (ou tableau de carac- tères) constante. Les indices du tableau commencent à 0. C'est pourquoi texte[0] extrait le premier caractère qui est q. 7. A la dernière ligne on déclare x = 5 dans un bloc d'accolades ou bloc d'instruc- tions {..}. Cela est possible et signie que dans ce bloc x est une nouvelle variable locale (qui masque la valeur de x précédente). 8. On pourra bien sûr modier ces valeurs dans la suite du programme. Exercice 4.1.2. (*) Recopiez le programme, modiez le pour qu'il ache la valeur de chaque objet, et exécuter le. En particulier acher la valeur de x dans le bloc d'accolades et après le bloc d'accolades. Essayer de ne pas mettre d'accolades. (si auto signale une erreur, vériez si l'option c++11 est cochée, voir remarque de l'exercice 1.2.4(4).) CHAPITRE 4. DÉCLARATION ET AFFECTATION DES OBJETS 21 4.1.3 Achage des nombres avec une précision donnée (il faut inclure <iomanip> et <math.h>) #include <iostream> using namespace std; #include <math.h> #include <iomanip> //.................. int main () { double c = M_PI; cout<<setprecision(3)<<c<<endl; } Résultat : 3.14 Référence : voir setprecision. 4.1.4 Attention à la division Euclidienne Voici un exemple : #include <iostream> using namespace std; //...... int main () { int a=7, b=2; double c=7.; cout<<"7/2 = "<<a/b<<" = "<<7/2<<endl; cout<<"7./2 = "<<c/b<<" = "<<7./2<<" = "<<(double)a/b<<endl; cout<<"Le quotient de 7/2 = "<<7/2<<" Le reste de 7/2 est r= "<<7%2<<endl; } Résultat : 7/2 = 3 = 3 7./2 = 3.5 = 3.5 = 3.5 Le quotient de 7/2 = 3 Le reste de 7/2 est r= 1 CHAPITRE 4. 22 DÉCLARATION ET AFFECTATION DES OBJETS Remarques : L'exemple précédent montre que l'écriture euclidenne qui est bien 3 car 7/2 signie le quotient de la division 7=2*3+1. Le reste 1 euclidien est obtenu par l'opération 7%2. Pour eectuer la division parmi les nombres réels (à virgule) il faut utiliser le type double. L'instruction une variable (double)a permet de convertir la variable a qui est de classe int de classe double. Ce changement de classe est appelé cast. en Attention, cette convention que 3/2 est la division euclidienne en langage C et C++ est souvent source d'erreur. 4.1.5 Pour générer un nombre entier p aléatoire #include <iostream> using namespace std; #include <time.h> #include <stdlib.h> int main () { srand(time(NULL)); // initialise le hasard.(1 seule fois) int N = 100; int p=rand()%(N+1); // entier aléatoire entre 0 et N compris. cout<<" p="<<p<<endl; } Puis au moment de choisir un nombre au hasard :dans le programme p=rand()%(N+1); Remarque 4.1.3. signie modulo, rand() a%b est génère un nombre entier aléatoire entre 0 et 32768. Ensuite le reste de la division de reste de la division du nombre aléatoire par N+1. a par b. Ainsi rand()%(N+1) % est le C'est donc un entier compris entre 0 et N (inclus). En c++11 il y a une classe random plus élaborée pour générer des nombres aléatoires selon diverses lois. 4.1.6 Quelques opérations élémentaires #include <iostream> using namespace std; int main () CHAPITRE 4. DÉCLARATION ET AFFECTATION DES OBJETS { int b=1; cout<<" b="<<b<<endl; b++; // equivalent a b=b+1 cout<<" b="<<b<<endl; b-- ; // equivalent a b=b-1 cout<<" b="<<b<<endl; } 4.1.7 Quelques opérations sur les bits (base deux) #include <iostream> using namespace std; #include <bitset> int main () { //... Declaration et Affichage en base deux: int A=0b1001; // declaration en base 2 (prefixe 0b) cout<<" A="<<A<<" en binaire = "<<bitset<8>(A)<<endl; //.. decalages de bits int b= A<<4; // equivalent a b= A * (2^4) cout<<" b ="<<b<<" = "<< bitset<8>(b)<<endl; int c = b>>3; // equivalent a c= b / (2^3) cout<<" c ="<<c<<" = "<< bitset<8>(c)<<endl; //.. int int int int operations B=0b1; d = A | B; e = A ^ B; f = A & B; cout<<" cout<<" cout<<" cout<<" } bits a bits // ou inclusif bit a bit cad 1|1 donne 1 // ou exclusif bit a bit cad 1^1 donne 0 // et bit a bit B="<<B<<" = "<<bitset<4>(B)<<endl; A|B = "<<d<<" = "<<bitset<4>(d)<<endl; A^B = "<<e<<" = "<<bitset<4>(e)<<endl; A&B = "<<f<<" = "<<bitset<4>(f)<<endl; Resultat : A=9 en binaire = 00001001 b =144 = 10010000 23 CHAPITRE 4. c =18 B=1 = A|B = A^B = A&B = DÉCLARATION ET AFFECTATION DES OBJETS 24 = 00010010 0001 9 = 1001 8 = 1000 1 = 0001 Remarque 4.1.4. De même on peut utiliser la base 16 (hexadécimale) avec le préxe 0x. Par exemple : a=0xFA; cout<<a<<endl; cout<<hex<<a<<endl; 4.2 // pour acher en base 16. Objets plus élaborés Nous présentons ici quelques classes plus élaborées et très utiles. Ce ne sont plus des "classes de base. Pour une liste complète des classes d'objets possibles en C++, voir par exemple la référence. 4.2.1 Chaines de caractères (string) Référence : string. Voici un exemple de programme que vous pouvez essayer. #include <iostream> #include <string> using namespace std; int main () { string texte1 = "J'aime le café"; cout << texte1<<endl; string texte2 = texte1 + " sans sucre"; cout << texte2<<endl; cout<<"Le premier caractère est du texte précédent est: "<<texte2[0]<<endl; } Résultat : J'aime le café J'aime le café sans sucre Le premier caractère du texte précédent est:J Remarque 4.2.1. Voici plus d'informations et d'exemples sur la classe string. Exercice 4.2.2. (*) Modier le programme précédent pour qu'il ache : J'aime le café sans sucre Le troisième caractère du texte précédent est:a CHAPITRE 4. 4.2.1.1 25 DÉCLARATION ET AFFECTATION DES OBJETS Conversion de chires en chaine de caractère et inversement (On peut sauter cette section en premiere lecture et revenir si besoin.) On souhaite par exemple construire une chaine de caractère s qui contient le resultat d'une opération numerique. Pour cela il faut utiliser la classe ostringstream qui fonc- tionne comme la classe. Inversement pour extraire des chires d'une chaine de caractère il faut utiliser la classe istringstream . #include <iostream> using namespace std; #include <string> #include <sstream> //..... int main () { //..... Conversion chiffre -> string int c=7; // chiffre ostringstream os; os<<"resultat = "<<c; // on concatene du texte et le chiffre string s = os.str(); // convertit os en string cout<<s<<endl<<endl; // affichage pour verifier //..... Conversion string -> chiffres string s2 = "3 4"; // chaine contenant des chiffres cout<<s2<<endl; istringstream is; is.str(s2); // convertit chaine s2 en is int a,b; is>>a; // extrait le premier chiffre trouve dans is is>>b; // extrait chiffre de is cout<<a<<" "<<b<<endl; // affichage pour verifier } Résultat : resultat = 7 3 4 3 4 4.2.2 Nombres complexes Référence : complex. CHAPITRE 4. 26 DÉCLARATION ET AFFECTATION DES OBJETS #include <iostream> using namespace std; #include <complex> typedef complex<double> Complex; int main () { Complex I(0,1); // on définit le nombre I cout<<"I.I="<<I*I<<endl; Complex a=2.+I*4., b=3.-2.*I; Complex c=a+b, d=a*b; cout<<" a="<<a<<" b="<<b<<endl; cout<<" a+b="<<c<<" a*b="<<d<<endl; cout<<" real(a)="<<a.real()<<" norme(a)="<<abs(a)<<endl; } Résultat : I.I=(-1,0) a=(2,4) b=(3,-2) a+b=(5,2) a*b=(14,8) real(a)=2 norme(a)=4.47214 Remarque 4.2.3. L'écriture 2. est équivalente à 2.0 ou à 2. Ainsi a=2.+I*4. est équivalente à a=2+4*I typedef complex<double> Complex; signie que l'écriture Complex est un raccourci pour le type complex<double> qui est un nombre complexe avec double en partie réelle et imaginaire. 4.2.3 Liste d'objets : vector La classe string ci-dessus est une liste de caractères. Plus généralement la classe vector permet de manipuler des listes d'objets quelconques (des nombres ou autres). Par exemple pour une liste de nombres entiers avec #include <iostream> using namespace std; #include <vector> #include <algorithm> vector<int> : CHAPITRE 4. DÉCLARATION ET AFFECTATION DES OBJETS 27 int main () { vector<int> L={5,7,3,12,34,6}; // on initialise une liste d'entiers cout<<"Le premier élément de la liste est "<<L[0]<<endl; auto i = max_element(L.begin(),L.end()); //renvoit la position du maximum int p=i-L.begin(); // position depuis le debut de la liste (0,1,2..) cout<<"L'élement le plus grand est "<<L[p]<<" ou "<<*i<<endl; cout<<"Il est à la position "<<p<<endl; } Résultat : Le premier élément de la liste est 5 L'élement le plus grand est 34 ou 34 Il est à la position 4 i qui représente une position sur la liste utilise ici le terme auto qui détecte cette Remarque 4.2.4. Dans l'exemple ci-dessus, l'objet est de la classe vector<int>::iterator. On classe et est plus simple à écrire. Voici plus d'informations et d'exemples sur la classe vector. Pour plus d'informations et d'exemples sur les manipulations de listes (appelées containers) voir algorithms. 4.2.4 Vecteurs et matrices pour l'algèbre linéaire Pour cela on utilise la librairie armadillo dont voici la documentation. Cette librairie ne fait pas partie de la "librairie standard STL, il faut donc l'installer au préalable sur l'ordinateur. Il faut aussi rajouter -larmadillo dans les options de compilation. (Si vous utili-lm, Si vous utilisez codeblocks, c'est à rajouter dans sez un Makele c'est à la suite de Menu/Settings/Compiler/LinkerSettings/OtherLinkerOptions). #include <iostream> #include <armadillo> using namespace std; using namespace arma; int main () { Col<int> V("1 2 3"); // vecteur colonne d'entiers cout<<"V="<<endl<<V<<endl; CHAPITRE 4. DÉCLARATION ET AFFECTATION DES OBJETS 28 Mat<int> M("3 2 1; 4 5 0");// matrice d'entiers cout<<"M="<<endl<<M<<endl; Col<int> W = M * V; // calcule le produit cout<<"M*V="<<endl<<W<<endl; cout<<"element de matrice M(0,0)="<<M(0,0)<<endl; } Résultat : V= 1 2 3 M= 3 2 1 4 5 0 M*V= 10 14 element de matrice M(0,0)=3 Remarque 4.2.5. Vous pourrez de la même façon utiliser d'autres classes selon vos besoins à condition cette fois ci d'inclure le chier d'entête #include <...> approprié au début du programme et d'associer les librairies correspondantes à la compilation. Exercice 4.2.6. Recopiez ce programme et exécuter le. Exercice 4.2.7. (*) Modiez le programme précédent pour qu'il calcule et ache : A = 1 1 0 1 B = 1 0 1 1 A*B = 2 1 1 1 4.2.5 tuple : ensemble d'objets divers Un objet de la classe tuple est un ensemble d'objets divers. Par exemple : CHAPITRE 4. 29 DÉCLARATION ET AFFECTATION DES OBJETS Résultat du programme suivant : a=10 b=10 c=x d=14 o1[0]=100 o3[5]=y #i n c l u d e <i o s t r e a m > using namespace std ; #i n c l u d e <t u p l e > int main () { //... construction t u p l e <i n t , c h a r > o1 ( 1 0 , ' x ' ) ; auto o2 = make_tuple //... lecture int des // (" t e s t " , creation 3.1 , 14 , d ' un objet 'y ' ) ; // constitué d ' un en construction sans pr elements a = g e t <0>(o1 ) ; // on extrait le premier element ( position 0) c o u t <<"a="<<a<<e n d l ; int b; tie ( b , c ) = o1 ; char c; // extraits les elements de o1 c o u t <<"b="<<b<<" c="<<c<<e n d l ; int d; tie ( ignore , ignore , d, i g n o r e ) = o2 ; // extrait certains c o u t <<"d="<<d<<e n d l ; //..... ecriture g e t <0>(o1 ) = 1 0 0 ; d ' elements // on remplace c o u t <<"o1 [0]="<< g e t <0>(o1)<< e n d l ; //.. concatenation auto de tuples o 3 = t u p l e _ c a t ( o1 , o2 ) ; c o u t <<"o3 [5]="<< g e t <5>(o3)<< e n d l ; return } 0; l ' element 0 de o1 elements d Chapitre 5 Les instructions de base 5.1 La boucle "for" #include <iostream> using namespace std; int main() { for(int i=2; i<=8; i=i+2) { int j=i*i; cout <<i << "\t "<<j <<endl; } } Ce programme produira : 2 4 6 8 4 16 36 64 Remarque 5.1.1. La syntaxe de la boucle for dans l'exemple ci-dessous signie : Fait les instructions en partant de i=2, repête l'instruction i=i+2 et les instructions qui suivent dans le bloc {...} tant que i<=8 (inférieur ou égal). Une autre possibilité d'utilisation de la boucle #include <iostream> using namespace std; #include <vector> 30 for en C++11 est : CHAPITRE 5. 31 LES INSTRUCTIONS DE BASE int main() { vector<int> L={5,7,3,12}; // une liste for(auto x:L) // la variable x parcourt la liste L cout<<x+1<<","; } Ce programme produira : 6,8,4,13, Exercice 5.1.2. (*) : dans l'exemple ci-dessus, remplacer le corps du programme par string chaine("Salut!"); for (auto a : chaine) { cout << a << ","; } cout<<endl; Qu'est ce que cela produit ? quel est le type de la variable a ? Aide : pour connaitre le type de la variable a on peut utiliser typeid(c).name() et écrire #include <typeinfo> //.... cout << typeid(a).name() <<endl; 5.2 Ecrire une condition On a utilisé ci-dessus la condition i<=30. Voici la syntaxe pour écrire une condition plus générale : 5.2.0.1 Les opérateurs de comparaison : Signication supérieur à inférieur à supérieur ou égal à inférieur ou égal à égal à diérent de > < >= <= == != a == 2 sert à comparer l'objet a avec 2 (et ne change a = 2 met la valeur 2 dans l'objet a. Attention à la confusion possible : pas la valeur de a). Par contre symbole CHAPITRE 5. 5.2.0.2 32 LES INSTRUCTIONS DE BASE Les opérateurs logiques Signication symbole ou && || non ! et 5.2.0.3 Remarque Lorsqu'une condition est évaluée comme i<=30, la valeur rendue est de classe boolean (vrai ou faux). 5.3 La boucle do {..} while (..) ; signie fait le bloc d'instructions {..} tant que la condition (..) est vraie. #include<iostream> using namespace std; int main( ) { int MAX(11); int i(1),j; do { j=i*i; cout<<i<<"\t "<<j<<endl; i=i+1; // Ne pas oublier l'incrémentation } while(i<MAX); // i < MAX est la condition } produira : 1 1 2 4 etc... 10 100 5.4 La boucle while (..) {..} ; signie tant que la condition (..) est vraie, fait le bloc d'instructions {..} . CHAPITRE 5. LES INSTRUCTIONS DE BASE #include <iostream> using namespace std; int main ( ) { int max=11; int i=1, j; while(i<max) // condition { j=i*i; cout<<i<<"\t"<<j<<endl; i=i+1; // ne pas oublier l'incrémentation } ; // ne pas oublier le point virgule } Produira : 1 1 2 4 etc... 10 5.5 100 L'instruction if #include<iostream> using namespace std; int main() { for(int i=1; i<=10; i=i+1) { if (i==4) cout<<"i= "<<i<<endl; } } Ache : 4 On peut compléter avec if.. else if.... else.. comme ceci : #include<iostream> using namespace std; int main() { 33 CHAPITRE 5. LES INSTRUCTIONS DE BASE 34 for(int i=1; i<=10; i=i+1) { if (i == 4) cout<<"i="<<i<<endl; else if(i==5) cout<<"2i= "<<2*i<<endl; else { cout<<"."<<endl; cout<<":-)"<<endl; } } } Remarque 5.5.1. Dans le dernier cas on a mis les deux instructions à executer dans un bloc d'accolades. En eet, il faut toujours mettre un ensemble d'instructions dans un bloc d'accolades sauf si il y en a une seule à executer. Exercice 5.5.2. On souhaite acher la suite 12345. Trouver l(es) erreur(s) et corriger le programme suivant : #include<iostream> using namespace std; int main() { int i=1 ; for(i=1; i<=5; i=i+1); cout<<i; } 5.6 break : pour sortir de la boucle. continue : pour sauter l'instruction. #include <iostream> using namespace std; int main() { int i; int j; for(i=2;i<=10;i=i+2) { if (i==4) CHAPITRE 5. LES INSTRUCTIONS DE BASE 35 continue; // on passe au suivant. if (i==8) break; // on sort de la boucle j=i*i; cout <<i << "\t "<<j <<endl; } } Ce programme produira : 2 6 4 36 Exercice 5.6.1. (*) Faire un programme qui au départ choisit un nombre au hasard entre 0 et 1000 (Utiliser la Section 4.1.5), puis demande à l'utilisateur de le trouver, en répondant "trop grand " ou "trop petit " à chaque essai. L'utilisateur a le droit à 10 essais. Chapitre 6 Les fonctions Une fonction est un petit sous programme qui eectue des instructions et utilise éventuellement certains paramètres donnés en entrée. Une fonction peut renvoyer un objet ou ne rien renvoyer du tout. 6.1 Exemple de fonction qui ne renvoit rien. #include <iostream> using namespace std; //!========declaration de la fonction carre============= void carre(int i) { int j=i*i; // declaration d'un objet local cout<<"le carré de "<<i<<" est "<<j<<endl; } //!====declaration de la fonction principale ===================== int main() { int x; cout<<" entrer x "<<endl; cin>>x; carre(x); // appel de la fonction carre } Ce programme renvoit : 36 CHAPITRE 6. 37 LES FONCTIONS entrer x 2 le carré de 2 est 4 Remarque 6.1.1. 1. La déclaration de la fonction carre montre que cette fonction prend un paramètre (l'objet i de la classe int) et ne renvoit rien (à cause du préxe vide). La fonction main appelle la fonction carre void qui signie et lui passe un parametre qui est l'objet x choisi par l'utilisateur. {..} de la fonction carre on a déclaré l'objet j.Par conséquent, cet j n'est connu que dans ce bloc et pas ailleurs. On dit que c'est un objet local. On ne peut pas l'utiliser dans la fonction main. De même l'objet x déclaré 2. Dans le bloc objet dans le bloc de la fonction n'est pas connu ailleurs : On ne peut pas l'utiliser dans la fonction 6.2 carre. Exemple de fonction qui renvoit un objet #include<iostream> using namespace std; //======declaration de la fonction carre =================================== int carre(int i) { int j=i*i; return j; //on renvoie le résultat au programme principal } //===declaration int main() { int x; cout<<"entrer x cin>>x; int y=carre(x); cout<<"Le carré } de la fonction principale ===== "<<endl; // appel de la fonction carre de "<<x<<" est "<<y<<endl; CHAPITRE 6. LES FONCTIONS 38 Ce programme renvoit : entrer x 2 le carré de 2 est 4 Remarque 6.2.1. La seule diérence avec l'exemple précédent est que la fonction carre renvoit son résultat qui est un objet de la classe int. Pour cela on utilise l'instruction return. Pour appeler cette fonction, on utilise la syntaxe y=carre(x) si bien que le résultat renvoyé est tout de suite stocké dans l'objet y. 6.3 Paramètres des fonctions par référence Quand le programme principal fournit un objet à une fonction, celle-ci peut éventuellement vouloir modier le contenu de l'objet. Dans l'exemple précédent la procédure ou la fonction carre ne modie pas la valeur de x. Dans l'exemple ci-dessous, on veut échanger le contenu de deux objets a et b. On doit alors dire à la fonction qu'elle a le droit de changer les objets qu'on lui donne en entrée. Pour cela, dans la déclaration des paramètres, on met le signe & devant les paramètres pouvant être modié par la fonction : #include<iostream> using namespace std; //====== fonction permute ================== void permute(int& i,int& j) { int t=i; // stockage temporaire i=j; j=t; } //======== fonction main =================== int main() { int a=10, b=5; cout<<a<<" "<<b<<endl; permute(a,b); cout<<a<<" "<<b<<endl; } CHAPITRE 6. LES FONCTIONS 39 Ce programme renvoit : 10 5 5 10 Exercice 6.3.1. Executer ce programme, puis enlever les signes & (de référence) dans le passage des paramètres, et ré-essayer. Conclusion ? 6.4 La surcharge des fonctions. Un aspect interessant du C++ est que l'on peut "surcharger " les fonctions : c'est à dire que l'on peut donner le même nom à des fonctions qui font des choses diérentes. Ce sont les paramètres demandés lors de l'appel de la fonction qui permet à l'ordinateur de distinguer qu'elle est la fonction à appeler. 6.4.1 Exemple : #include<iostream> using namespace std; //------------------------------------------void func(int i) { cout<<"fonction 1 appelée"<<endl; cout<<" paramètre = "<<i<<endl; } //------------------------------------------void func(char *s,int i) { cout<<"fonction 2 appelée"<<endl; cout<<" paramètre = "<< s <<endl; cout<<" paramètre = "<< i <<endl; } //--------------------------------------------int main() { func(10); func("Chaîne ",4); } CHAPITRE 6. 40 LES FONCTIONS Renvoit fonction 1 appelée paramètre = 10 fonction 2 appelée paramètre = Chaîne paramètre = 4 Exercice 6.4.1. (*) Ecrire une fonction qui en entrée prend un objet sa factorielle x ! de la classe long. x de la classe long, et en sortie renvoit Dans la fonction principale on utilisera cette fonction avec le code suivant : long a=5; long b = factorielle(a); cout<<" a="<<a<<" a!="<<b<<endl; an qu'il ache : a= 5 a!=120 Remarque 6.4.2. En pratique il n'est pas nécessaire de programmer la fonction factorielle ou d'autres fonctions spéciales que l'on trouve dans la librairie boost. Chapitre 7 Les chiers Jusqu'à présent, nous avons lu des données au clavier et nous les avons aché à l'écran. On peut aussi écrire des données dans un chier (sur le disque dur de l'ordinateur ou sur un disque dur ou clef USB externe). Pour cela il faut utiliser des fonctions qui manipulent les chiers. Ces fonctions sont regroupées dans le chier ofstream pour écrire dans un chier ou ifstream fstream et font partie des classes pour lire dans un chier. Traduction : Output (=sortie) File (=chier) Stream (= ux de données), ou Input(=entrée),etc.. 7.1 Ecriture de données dans un chier Par exemple pour écrire un chire dans un nouveau chier que l'on appellera hector.txt, il sut de faire 3 étapes : #include <iostream> using namespace std; #include <fstream> // utilisation des fichiers int main() { ofstream f("hector.txt"); // ouvre le nouveau fichier en ecriture. On lui associe l'objet: f f<<3.1514<<endl; // permet d'ecrire dans le fichier. f.close(); // fermeture du fichier f } 7.1.0.1 Remarque : hector.txt par le suxe .txt. Ce n'est pas une obligation, mais une convention : txt signie texte, et précise que ce chier 1. On a choisit de terminer le nom du chier peut se lire comme un texte (même si il y a des chires). De la même façon le chier 41 CHAPITRE 7. 42 LES FICHIERS qui contient votre programme se termine par .cc pour signier que c'est le texte d'un programme C++. 2. Pour écrire dans le chier, on a utilisé un objet intermédiaire de la classe ofstream (abréviation de output-le-stream).Cet objet a été initialisé avec le nom du chier, et pour écrire dans le chier on utilise l'écran (avec f à la place de cout). la même syntaxe que pour écrire à 3. L'ouverture du chier se fait par l'initialisation de l'objet f avec le nom du chier donné sous la forme d'une chaine de caractères. On peut donc passer un objet intermédiaire (chaine de caractères) de la façon suivante : string nom=hector.txt ofstream f(nom); 4. Lorsque l'on a eectué l'opération et close() f.close(), f est un objet de la classe est une fonction de cette classe. On parle de ofstream fonction membre. Re- marquez la syntaxe : l'objet et la fonction membre sont reliés par un point. 5. Pour rajouter des données à un chier existant (ou non existant) il faut l'ouvrir avec l'instruction ofstream f("hector.txt",ios::app); (app signie appen- d, ajouter) 7.1.0.2 Exercice Ecrire le programme précédent et vérier l'existence du chier. Ouvrir le chier dans codeblocks ou avec un autre éditeur et vérier son contenu. 7.2 Lecture de données depuis un chier Le programme suivant permet de lire le chire écrit précédement dans le chier #include<iostream> using namespace std; #include<fstream> // utilisation des fichiers int main() { ifstream g("hector.txt");// ouvre un nouveau fichier en lecture. On lui associe l'objet: g double x; g>>x; //on lit ce qu'il y a au début du fichier, et on le copie dans l'objet x cout<<x<<endl; // on écrit à l'écran le contenu de x g.close(); // fermeture du fichier g } hector.txt. CHAPITRE 7. 7.2.0.1 43 LES FICHIERS Remarques : 1. Si le début du chier n'avait pas contenu de chire (mais des lettres ou rien du tout) le programme n'aurait rien mit dans l'objet x. 2. On peut de la même façon écrire ou lire plusieurs données à la suite dans un chier : il est important de savoir que le programme est au départ positionné au début du chier, puis après avoir lu (ou écrit) une donnée, il se trouve positionné juste après cette donnée, prêt à lire la donnée suivante. 3. La lecture g>>x depuis le chier est similaire à la syntaxe de lecture cin>>x depuis le clavier. 4. A la place de cout<<x<<endl; on peut écrire les instructions : if(g.good()) cout<<x<<endl; En eet g.good() renvoit true si la lecture précédente s'est bien faite. Exercice 7.2.1. (*) Reprendre et modier le programme de l'exercice 5.6.1 pour que les numéros proposés par l'utilisateur soient ecrit dans un chier. Chapitre 8 Les pointeurs 8.1 Déclaration et aectation d'un pointeur La notion de pointeur est importante dans le langage C et C++. Elle est réputée comme étant dicile et technique ; nous espérons que vous aurez néanmoins les idées claires après la lecture de cette section. Nous introduisons rapidement la notion de pointeur, et montrons comme exemple, son intérêt pour créer des tableaux de taille variable au cours du programme. Rappellons déjà ce qu'est un objet. Par exemple : int i; Cette instruction a pour eet de réserver une case en mémoire de l'ordinateur, permettant de stocker un nombre entier. Cette case s'appelle 'i'. Bien sûr, cette case se trouve quelque part dans la mémoire de l'ordinateur. Elle a un certain emplacement, caractérisée par son adresse, appellée son Il faut donc retenir que pointeur. pointeur d'un objet signie adresse d'un objet dans la mémoire de l'ordinateur. On peut avoir accès à l'adresse de la case i en faisant : int i=2; // déclare l'objet i et affecte la valeur 2 int *p; // déclaration du pointeur p p=&i; // p devient l'adresse de i Grâce au signe *, la deuxième ligne déclare p comme étant un pointeur (désignant une case mémoire qui est sensée contenir un entier). Le signe &i signie l'adresse de l'objet i. Donc la troisième ligne met dans l'adresse de l'objet i. 44 p CHAPITRE 8. 45 LES POINTEURS Pour acher le contenu de l'objet i on a maintenant deux possibilités : cout<<i; ou : cout<<(*p); qui sont équivalentes car : (*p) signie le contenu de la case où pointe p. Pour modier le contenu de l'objet i on a maintenant deux possibilités : i=5; ou : (*p)=5 ; qui sont équivalentes. Attention : avant d'eectuer l'opération d'écriture : (*p)=5 ; il faut être sur que l'adresse du pointeur correspond à une case existante. Vous avez donc compris que avant d'écrire, il faut prendre le soin de réserver de la place mémoire. 8.2 Allocation dynamique de la mémoire On peut réserver une suite de n cases mémoires, par l'instruction : int n; cout <<"entrez n ?"<< flush; cin >> n; double *p; //on déclare le pointeur p p=new double[n]; // on réserve n cases mémoire de la classe double Cela s'appelle une allocation dynamique de la mémoire, car elle se fait au cours de l'éxécution du programme. Après cela, p pointe sur la première case réservée : on peut légitimement écrire dans cette première case par l'instruction : (*p)=1; ou (ce qui est équivalent) p[0]=1; On peut de même écrire dans la case suivante par : *(p+1)=2; ou p[1]=2; etc... jusqu'à p[n-1]. CHAPITRE 8. 46 LES POINTEURS Remarquer qu'il y a n cases, numérotées de 0 à n-1. A la n de l'utilisation, n'oubliez pas de libérer l'emplacement mémoire par l'ins- truction : delete [ ] p; // on libere les cases mémoire 8.2.0.1 Remarques 1. On peut réserver un nombre xe de cases en mémoire par l'instruction : double p[6]; // creation de 6 cases mémoires de type double, numérotées de 0 à 5. p[1] = 3.14; // on écrit dans la case 1. Il s'agit d'une allocation de mémoire dite statique (non dynamique) car le nombre de case est xé à la compilation, et ne peut être variable. 2. Si l'on veut ne réserver qu'une seule case mémoire au lieu d'un tableau, il sut de faire : int *p; p=new int; // on réserve une case mémoire de classe int. (*p)=1; delete p; // pour libérer la place mémoire 3. Un tableau de caractères est aussi appelé une chaîne de caractères. On utilise souvent la déclaration : char *chaine = toto; qui déclare chaine comme étant un pointeur sur caractère, pointant sur les 4 caractères toto. CHAPITRE 8. LES POINTEURS 47 Exercice 8.2.1. (*) Ecrire un petit programme qui demande à l'utilisateur un entier n compris entre 1 et 5, puis alloue de façon dynamique un tableau p de taille n de la classe string. Ensuite dans une bloucle on demande à l'utilisateur le contenu de chaque case du tableau. A la n, on ache le contenu du tableau. L'execution du programme doit donner cela (par exemple) : entrez n? 3 entrez p[0] ? un entrez p[1] ? petit entrez p[2] ? programme p[0] =un p[1] =petit p[2] =programme Chapitre 9 Graphisme avec la librairie ROOT Comme il est très utile et agréable de représenter des données et résultats de façon graphique, nous présentons tout de suite l'utilisation d'une librairie graphique. La librairie root est développée au Cern (Laboratoire international de physique des particules). C'est une librairie en C++ gratuite et très performante, qui permet : de faire du graphisme évolué (dessiner des axes, des courbes, des surfaces, des objets en 3D,..), mais aussi du de traiter des graphisme simple (lignes, points, ronds,..) données pour faire des statistiques ; cela est très utile au CERN pour étudier les milliards de résultats issus d'une expérience de collisions entre particules. de faire des interfaces graphiques pour un programme (gestion de la souris, menus déroulants, boites de dialogues avec boutons, ...) et beaucoup d'autres choses. Voir la page web de presentation. Sur le réseau il y a une documentation complète. On peut aussi accéder à la liste des classes. Il y a une mailling list d'utilisateurs qui s'entraident. Quand on a un problème, il sut d'envoyer un mail, la réponse nous revient quelques minutes ou heures plus tard. Le plus simple est souvent d'écrire dans le moteur de recherche de google des mots clefs comme root cern ellipse pour trouver comment dessiner une ellipse avec root. Remarque 9.0.1. Root peut s'utiliser aussi en mode interprété C++ : lancer la commande root dans une fenêtre xterm, et suivre les instructions... 9.1 Commande de compilation Avant d'écrire un programme utilisant les librairies Root, il faut, une fois pour toutes, Si vous utilisez l'éditeur codeblocks Dans Settings/Compiler/Compiler_Settings/Other_Options, rajouter : -I/usr/include/root Dans Settings/Compiler/Linker_Settings/Other_linker_Options, rajouter : 48 CHAPITRE 9. ` r o o t −c o n f i g 49 GRAPHISME AVEC LA LIBRAIRIE ROOT −− c f l a g s ` ` r o o t −c o n f i g −− l i b s ` ` r o o t −c o n f i g −− g l i b s ` Si vous n'utilisez pas codeblocks, mais un Makele par exemple, la commande de compilation du programme g++ main . cpp 9.2 −o main main.cpp pour donner l'executable main −g − s t d=c++11 − I / u s r / i n c l u d e / r o o t doit être : ` r o o t −c o n f i g −− c f l a g s Exemple de départ qui dessine un cercle #include <TApplication.h> // (A) #include <TCanvas.h> //(B) #include <TEllipse.h> // (C) //------ fonction main (A) -------------------int main(int argc, char **argv) { TApplication theApp("App", &argc, argv); // (A) TCanvas c("c","fenetre",400,400); // (B) Fenetre graphique. Taille en pixels double xmin(0),ymin(0),xmax(5),ymax(5); c.Range(xmin,ymin,xmax,ymax); // coordonnees de la fenetre (optionnel, par defaut: 0-1) //------ dessin d'une ellipse dans la fenetre c (C) ------TEllipse e(2,3,1); // on precise le centre (x=2,y=3) et le rayon=1 e.Draw(); // dessine l'ellipse c.Update(); //(B) Montre le dessin theApp.Run(); //(A) donne la main a l'utilisateur return 0; } Résultat du programme : ` CHAPITRE 9. 50 GRAPHISME AVEC LA LIBRAIRIE ROOT Remarque 9.2.1. Le programme précédent est découpé en trois types de blocs : (A) : dans tout programme utilisant Root, ces parties doivent toujours être réécrites. Il y a l'objet theApp de la classe TApplication. (B) : c'est le code qu'il faut pour créer la fenetre graphique (objet c de la Classe TCanvas). Si votre programme fait du graphisme, il faut toujours créer une fenêtre graphique qui contiendra votre dessin. (C) : c'est le code qu'il faut pour créer le cercle (objet e de la Classe TEllipse). Exercice 9.2.2. Recopier le programme ci-dessus, le compiler et l'exécuter. Une fois que le dessin apparait, vous pouvez choisir dans le menu : View/Event Status Bar qui ache la position de la souris. Modier le programme an d'acher un cercle rouge au centre de la fenetre. Pour cela, rajouter la commande table des couleurs : e.SetLineColor(kRed); à la bonne place. (voici la table des couleurs). Remarque 9.2.3. la commande e.Draw() appelle la fonction membre Draw() de la classe TEllipse qui dessine l'objet e. Pour connaitre toutes les opérations possibles que l'on peut eectuer sur l'ellipse, il faut regarder la liste des fonctions membres de cette classe TEllipse. On remarquera dans l'entête, que la classe TEllipse hérite d'autres classes comme la classe TAttLine qui concerne les propriétés des traits. Ainsi la classe TEllipse peut utiliser les fonctions de la classe TAttLine comme la fonction la couler des traits. SetLineColor() qui permet de changer CHAPITRE 9. GRAPHISME AVEC LA LIBRAIRIE ROOT 51 Exercice 9.2.4. Modier le programme ci-dessus pour dessiner une ellipse remplie d'un motif bleu comme ci-dessous. Aide : d'après la documentation, TEllipse hérite de la classe TAttFill. On utilisera les fonctions membres : e.SetFillColor(kBlue); e.SetFillStyle(3025); // motif en carreaux Exercice 9.2.5. (*) Modier le programme ci-dessus pour faire apparaitre un cercle de rayon 1 qui tourne autour de l'origine, à la distance liser les fonctions membre SetX1(x1), SetY1(y1) R = 2. Aide : une possibilité est d'utiTEllipse pour positionner de la classe son centre et utiliser une boucle for. 9.3 Utilisation des pointeurs avec ROOT Dans l'exercice précédent, vous avez remarqué que lorsque vous redessinez l'ellipse e, l'ancien dessin disparait. Cela est pratique, pour faire de l'animation (faire déplacer un cercle par exemple), mais comment dessiner plusieurs cercles simultanéments ? Nous vous proposons deux possibilités : CHAPITRE 9. GRAPHISME AVEC LA LIBRAIRIE ROOT 52 9.3.1 Utiliser une liste d'ellipses Ecrire le programme : #include <TApplication.h> #include <TEllipse.h> #include <TCanvas.h> #include <vector> using namespace std; int main(int argc, char **argv) { TApplication theApp("App", &argc, argv); TCanvas c( "c","fenetre",400,400); // on precise la taille en pixels CHAPITRE 9. 53 GRAPHISME AVEC LA LIBRAIRIE ROOT c.Range(-2,-2,8,2); // coordonnees de la fenetre //------------vector<TEllipse> tab_e(3); // liste de 3 ellipses for (int i=0; i<3; i++) { tab_e[i].SetX1(3*i); // coord x du centre tab_e[i].SetY1(0); //coord y du centre tab_e[i].SetR1(1); // rayon selon x tab_e[i].SetR2(1); // rayon selon y tab_e[i].SetFillColor(kWhite); tab_e[i].Draw(); // dessin } //------------c.Update(); theApp.Run(); return 0; } Ainsi l'ellipse numéro i+1 n'eace pas l'ellipse numéro i. 9.3.1.1 Remarques : Rappel : dans l'instruction for, l'instruction même i-- est équivalente à i = i-1 i++ est équivalente à Pour enlever l'ellipse numéro 1 de la liste, il faut utiliser la fonction classe vector i = i+1. erase() : tab_e.erase(tab_e.begin()+1); Rajouter cette ligne au bon endroit, dans le programme précédent et observer le résultat. 9.3.2 Utiliser un pointeur sur l'ellipse Ecrire le programme : #include <TApplication.h> #include <TEllipse.h> #include <TCanvas.h> #include <vector> using namespace std; int main(int argc, char **argv) De de la CHAPITRE 9. 54 GRAPHISME AVEC LA LIBRAIRIE ROOT { TApplication theApp("App", &argc, argv); TCanvas c( "c","fenetre",400,400); // on precise la taille en pixels c.Range(-2,-2,8,2); // coordonnees de la fenetre //------------TEllipse *pe; // crée un pointeur sur TEllipse for (int i=0;i<3;i++) { pe= new TEllipse(3*i,0,1); // pe pointe sur une ellipse crée en position x=3*i, y=0, rayon 1 pe->Draw(); // dessin } //------------c.Update(); theApp.Run(); return 0; } 9.3.2.1 Remarques : Cette dernière méthode est simple, et voici ce qu'il se passe : un objet de la classe TEllipse() TEllipse pe est un pointeur sur autrement dit l'adresse de l'objet. L'instruction crée un objet ellipse et aecte pe pe= new à l'adresse de cet objet. La deuxième fois, cette même instruction crée un nouvel objet, sans détruire l'ancien, et pe devient l'adresse de ce nouvel objet. A la n on a la situation suivante où les trois ellipses existent (numéros 0,1,2), et pe est l'adresse du dernier objet : L'avantage de cette dernière méthode est que l'on peut créer un nombre indéni d'ellipses. Pour modier une ellipse déjà existante il faudrait stocker au fur et à mesure les adresses pe dans un tableau (ou utiliser une fonction de ROOT qui permet de récupérer les adresses des objets existants). CHAPITRE 9. 9.3.2.2 GRAPHISME AVEC LA LIBRAIRIE ROOT 55 Informations succintes sur les pointeurs (plus de détails dans la Section 8) pe est un pointeur (une adresse), alors *pe est le contenu (l'objet) à cette adresse. e est un objet alors &e est le pointeur (adresse) de l'objet. Si pe est un pointeur (une adresse), l'instruction pe->Draw(); est équivalente à (*pe).Draw(); On comprends l'écriture TEllipse *pe; comme la déclaration que *pe est un objet, donc pe est un pointeur. Si Réciproquement si Exercice 9.3.1. (*) Ecrire un programme qui dessine un tableau de 10*8 cercles colorés au hasard comme cela : 9.4 Représentation graphique des vecteurs et matrices de Armadillo Description : Ces fonctions combinent les classes de vecteur et matrice de armadillo et les classes graphiques de Root, an de représenter graphiquement le contenu de vecteurs et de matrices. Cela peut être pratique en diverses circonstances. CHAPITRE 9. GRAPHISME AVEC LA LIBRAIRIE ROOT 56 Travail préalable : 1. Fichiers à télécharger et sauver dans son répertoire : bib_fred.h et bib_fred.cc. 2. Si vous utilisez codeblocks, il faut ajouter ces chiers au projet en faisant dans le menu : Projet/add_les ... #include "bib_fred.cc" #include "bib_fred.h". Ou bien utiliser la 3. Si vous utilisez simplement emacs, il faut rajouter la ligne dans le code suivant à la place de gestion de projet avec Makele comme expliqué dans la Section 25.2. 4. Il faut aussi rajouter -larmadillo dans les options de compilation (pour code- blocks c'est dans Setting/Compiler/LinkerSettings/OtherLinkerOptions) Exemples #include <iostream> using namespace std; #include <armadillo> using namespace arma; #include #include #include #include <TApplication.h> <TEllipse.h> <TCanvas.h> "bib_fred.h" int main(int argc, char **argv) { TApplication theApp("App", &argc, argv); //!--------Dessin de vecteur réel -----------------------------vec v1=randu<vec>(100); // vecteur réel avec elements au hasard dans (0,1) Dessin(v1,"V1"); // Dessin du vecteur. On lui donne le titre V1 //!... plus d'options: int opt_log=1;// echelle log en y TH1D *h=Dessin(v1,"x","y",0,7,"V2",0,"L",0.01,100,kRed,opt_log); h->SetYTitle("z"); // change le titre de l'axe Y à partir du pointeur h récupéré. h->Draw("L"); // redessine //!....Dessin sur un cercle............ Dessin_phases(v1,"V3"); CHAPITRE 9. GRAPHISME AVEC LA LIBRAIRIE ROOT 57 //!------Dessin de matrice réelle---------------------------mat m1=randu<mat>(10,10); // vecteur avec elements au hasard dans (0,1) Dessin(m1,"M1"); // Dessin de la matrice avec le titre "M1" //!... plus d'options: Dessin(m1,"x",0,3,"y",0,7,"M2","lego2"); //!---- Dessin d'un vecteur de nombres complexes cx_vec w1=randu<cx_vec>(20)-complex<double>(0.5,0.5); //distribution dans [-0.5,05]*[-0.5,0.5] Dessin(w1,"W1"); // dessin des points dans le plan complexe //!------- Affichage de vector -----------vector<double> V1({1.2,2.1,3.4}); cout<<V1<<endl; vector<int> V2({1,2,3}); cout<<V2<<endl; theApp.Run(); return 0; } Resultats : CHAPITRE 9. GRAPHISME AVEC LA LIBRAIRIE ROOT 58 CHAPITRE 9. GRAPHISME AVEC LA LIBRAIRIE ROOT 59 CHAPITRE 9. GRAPHISME AVEC LA LIBRAIRIE ROOT 60 [1.2,2.1,3.4] [1,2,3] Exercice 9.4.1. (*) vec et mat de armadillo (vecteur et matrice de nombres à virgule) et les fonctions Dessin() ci-dessus, dessiner la fonction Gaussienne à 2 2 2 une variable et deux variables : f (x) = exp (−x ) et f (x, y) = exp (− (x + y )) pour un intervalle de x, y convenable. En utilisant respectivement les classes Chapitre 10 Création d'une classe Programmer une classe consiste à dénir un nouveau type d'objets avec des opérations précises que l'on pourra faire avec. Vous avez utilisé par exemple la classe des Complexes déjà existante (écrite par d'autres informaticiens avant vous). Avec cette classe, on peut déclarer des variables complexes et eectuer directement des opérations sur les nombres complexes comme z3=z1*z2*exp(z4),...sans devoir décomposer l'opération sur les parties réelles et imaginaires. Dans cette section, on va apprendre à écrire soi-même une classe, et l'on prendra l'exemple des vecteurs et des matrices. Il existe déjà des classes de vecteurs et de matrices prêtes à l'emploi et très performantes, comme armadillo, voir Section 4.2.4. Considérez donc cette Section comme étant à but pédagogique. 10.1 Exemple Un vecteur de R3 est dénit par ses trois composantes (x, y, z). Voici un programme de départ pour dénir un vecteur en C++. Essayez ce code. Lisez ensuite les commentaires. Résultat : vecteur construit... Norme = 3.74166 vecteur détruit.... #i n c l u d e <i o s t r e a m > using namespace std ; #i n c l u d e <math . h> // ===============d é c l a r a t i o n class de la Vect { 61 c l a s s e =============== CHAPITRE 10. 62 CRÉATION D'UNE CLASSE public : //−−−− variables double //−− le membres x,y,z; c o n s t r u c t e u r −−− Vect ( ) { x =0; y =0; z =0; c o u t << " v e c t e u r construit . . . c o u t << " v e c t e u r détruit . . . . " << e n d l ; } //−− le d e s t r u c t e u r −−− ~Vect ( ) { " << e n d l ; } //−− l a Norme−−− fonction double Norme ( ) { double n =0; n=x * x+y * y+z * z ; return sqrt (n ) ; } }; //====== Programme p r i n c i p a l ======================= main ( ) { Vect v; v . x =1; // declaration v . y =2; v . z =3; c o u t << "Norme = " << v . Norme ( ) << e n d l ; } 10.1.1 Commentaires Commençons par la première ligne du programme principal : on a déclaré un objet v de la classe Vect. classe, appelée Cela a pour eet d'appeler la fonction Vect() déclarée dans la constructeur. Dans la partie déclaration, il est écrit qu'un objet de la classe Vect possède trois variables x,y,z de type double appelées variables CHAPITRE 10. 63 CRÉATION D'UNE CLASSE membres. Dans notre exemple la fonction constructeur a pour eet de mettre les variables x,y,z à zéro et d'acher le texte vecteur construit.... Ainsi à la deuxième ligne du programme principal, le code dans la variable membre x de l'objet v. De façon générale v.x=1; met la valeur 1 objet.variable est la façon de désigner une variable associée à un objet. A la troisième ligne du programme principal, le code Norme() qui est déclarée dans la classe. p √ 2 2 x + y + z 2 = 1 + 4 + 9 = 3.74.. De v.Norme() appèle la fonction Cela a pour eet de calculer et acher façon générale objet.fonction() est la façon d'appeler une fonction membre associée à un objet. a la n du programme principal, l'objet v va être détruit et le programme appelle la fonction ~Vect() appelée destructeur, même si cela n'est pas explicitement écrit. Dans notre exemple cela a pour eet d'acher le texte "vecteur détruit.... ". Remarque 10.1.1. Dans la déclaration d'une fonction membre comme x,y,z Norme() les variables membre s'accèdent directement (sans la syntaxe v.x) car ce sont sans ambiguité les v. Vect() est appelée chaque fois qu'un objet Vect variables membre de l'objet en cours pour qui la fonction a été appelée, ici La fonction membre constructeur est déclaré au cours du programme. Comme son nom l'indique, le constructeur sert à initialiser le nouvel objet créé. Le constructeur doit toujours porter le nom de la classe en question (ici Vect). la fonction membre destructeur ∼Vect() est appelée chaque fois qu'un objet Vect est détruit au cours du programme. Le destructeur doit toujours porter le nom de la classe en question (ici L'annonce public: Vect) avec ~ devant . en haut des déclarations permet à ce qui suit d'être connu et accessible hors de la déclaration de la classe. Au contraire on rend les déclarations non accessibles (si besoin est) par l'annonce private: Remarquez dans le programme la présence ou non de points virgule ; en particulier à la n de la déclaration de la classe. Exercice 10.1.2. (*) 1. Ecrire le programme ci dessus dans un chier vecteur.cc (dans un nouveau réper- toire /classes/), éxécutez le, et vériez le comportement attendu. 2. Rajouter une fonction membre de la classe Vect que l'on appelera Affiche(), et qui ache à l'écran le contenu du vecteur sous la forme : Vect: x=1 | y=2 | z=3 Appelez cette fonction dans le programme principal et testez le bon fonctionnement. 3. Rajouter une fonction membre de la classe v2), Vect que l'on appelera double Produit(Vect et qui permet de calculer le produit scalaire entre deux vecteurs. Le résultat double. Dans le programme Vect v,w; double d=v.Produit(w); est un principal, l'appel doit se faire sous la forme : CHAPITRE 10. 64 CRÉATION D'UNE CLASSE 4. Appelez cette fonction dans le programme principal et testez le bon fonctionnement. Remarque : essayez de comprendre la cause des messages construit et détruit. Il manque un construit. La raison de cela sera expliquée ultérieurement au paragraphe Constructeurs par recopie, et aectations. 5. Rajoutez un constructeur qui permet d'initialiser directement les variables membres x,y,z Vect(double xi,double yi,double zi). Vect v(1,2,3); constructeur précédent Vect() qui ne prend pas d'argu- du vecteur. Il aura la syntaxe Dans le programme principal, on pourra alors déclarer un objet par Remarque : vous gardez le ment. N'oubliez pas en eet qu'en C++ des fonctions diérentes peuvent avoir le même nom et ne diérent que par leurs arguments. 10.2 Utilisation de pointeurs sur objets Nous avons expliqué les pointeurs dans la section 8. Dans l'exemple 10.1 ci-dessus, remplacer les quelques lignes du programme principal par //====== Programme p r i n c i p a l ======================= main ( ) { Vect *w = new Vect ( ) ; // declaration w−>x =0; w−>y =3; w−>z =4; c o u t << "Norme d e w = " << w−>Norme ( ) << e n d l ; } Résultat : vecteur construit... Norme de w = 5 Commentaires La déclaration Vect *w signie que w est un pointeur (c'est à dire une adresse) sur un objet de la classe Vect. le code w = new Vect() ; a pour eet de créer un objet à cette adresse. A la deuxième ligne le code w->x=0; met la valeur 0 dans la variable membre de l'objet pointé par w. De façon générale pointeur->variable x est la façon de désigner une variable associée à un objet qui est pointé par le pointeur. w->Norme() appèle la fonction générale pointeur->fonction() A la troisième ligne du programme principal, le code Norme() qui est déclarée dans la classe. De façon CHAPITRE 10. 65 CRÉATION D'UNE CLASSE est la façon d'appeler une fonction membre associée à un objet pointé par le pointeur pointeur. Remarque 10.2.1. Il n'y a pas d'appel automatique de la fonction destructeur à la n du programme. En fait seule la variable pointeur est détruite. Si l'on souhaite appeler le destructeur de l'objet pointé il faut le faire explicitement par w->~Vect(); ou delete(w); qui a le même eet. 10.3 Surdénitions d'opérateurs 10.3.1 Exemple d'opérateur binaire Pour eectuer le produit scalaire entre deux vecteurs, plutôt que d'utiliser la fonction Produit ci dessus, on aimerait utiliser la syntaxe plus agréable : d=v*w; cela est possible en redénissant l'opérateur * de la façon suivante. Code à rajouter dans le code des fonctions membres : double operator*(Vect v2) { double p=0; p=x*v2.x+y*v2.y+z*v2.z; return p; } Utilisation dans le programme principal : main() { double d; Vect v(1,2,3); Vect w(2,3,4); d=v*w; cout <<d= << d << endl; } 10.3.2 Commentaire La syntaxe de l'opération * est semblable à celle de la fonction membre Comme pour Produit , Il est important de remarquer Vect v2 Produit . est un paramètre de l'opération *. Lorsque l'on écrit d=v*w, l'opération * est appelée pour l'objet v qui se trouve à gauche de * (et non w) ; à l'appel de cette opération, le vecteur v2 prend la valeur de w qui se trouve à droite de *. CHAPITRE 10. 66 CRÉATION D'UNE CLASSE On dit que * est un opérateur binaire car lors de l'appel d=v*w il y a deux entrées pour *. Il y a à sa gauche l'objet de la classe, et à sa droite un autre objet (à priori quelconque, ici Les Vect v2 ). symboles possibles pour un opérateur binaire sont : (),[ ], -> , *,/,%,+,-, << ,>>,<,<=,>,>=,==,!=,&,^, ||,&&,|,=, +=,-=,*=,/=,%=,&=,^=,|=, <<=,>>=, et la virgule , . Exercice 10.3.1. (*) 1. Ecrivez le code pour eectuer la somme (termes à termes) et la diérence de deux vecteurs par la syntaxe : w=u+v; w=u-v; et testez. 2. Ecrivez le code pour eectuer le produit et la division externe d'un réel vecteur v (produit de chaque élément) par la syntaxe w=v*a; ou w=v/a; a avec un et testez. v.x,v.y,v.z respectivement, par la v[1],v[2],v[3] . Cela est possible en dénissant l'opérateur [ ] avec la déclaration double & operator [ ](int i); A l'utilisation, on écrit par exemple d=v[1]; . Ecrivez le code de cet opérateur et testez. Rem : le signe & permet d'écrire aussi une aectation comme v[1]=3; . Cela sera 3. On aimerait accéder aux variables membres syntaxe mieux expliqué au paragraphe aectations. 10.3.3 Exemple d'un opérateur unaire Comme en mathématiques, on aimerait donner un sens à vecteur v. Pour cela il faut écrire : Code à rajouter dans le code des fonctions membres : Vect operator-() { return (*this)*(-1); } Utilisation dans le programme principal : main() { Vect v(1,2,3); Vect w; w=-v; w.Affiche(); } (−v) qui est l'opposé du CHAPITRE 10. 67 CRÉATION D'UNE CLASSE 10.3.4 Commentaires On dit que - est un opérateur unaire car lors de l'appel w=-v car la seule entré de - est l'objet v de la classe qui se trouve à droite. (il n'y a pas d'entrée à gauche). Les symbôles possibles pour un opérateur unaire sont : +,-,++,--,!,~,*,&,new,new[],delete,(cast) (*this) dans la fonction membre signie l'objet en cours. Ici il s'agit du vecteur v. Remarquez la façon économique de calculer le résultat : on utilise la multiplication externe déjà programmée. Exercice 10.3.2. Simpliez le code de l'opérateur binaire w=u-v déjà écrit en utilisant l'opérateur binaire + et l'opérateur unaire - seulement. Simpliez le code de l'opérateur binaire w=v/a déjà écrit en utilisant l'opérateur binaire * . 10.4 Fonctions amies 10.4.1 Exemple La syntaxe que permet les fonctions membres ci-dessus a une petite limitation. Dans la classe Vect, on aimerait par exemple dénir le produit externe * d'un double Vect v, et écrire : a avec un w=a*v; Mais d'après la disposition des objets, il faudrait que * soit un opérateur binaire de l'objet a qui se trouve à gauche . On peut cependant associer cette opération à la classe Vect de l'objet v en écrivant : Code à rajouter dans le code des fonctions membres : friend Vect operator*(double a,Vect v2) { return (v2*a); } Utilisation dans le programme principal : main() { double d(2); Vect v(1,2,3); Vect w; w=d*v; } CHAPITRE 10. 68 CRÉATION D'UNE CLASSE 10.4.2 Commentaire Le préxe friend signie que cet opérateur n'est pas exactement une fonction membre de la classe Vect, mais pas non plus indépendante puisque elle est déclarée Vect:: . Les paramètres de l'opération * dans la déclaration sont (double a,Vect v2). Ils se trouvent de part et d'autre de * lors de l'utilisation : w=a*v; avec la classe. Remarquez l'ommission du préxe de rattachement Comme en mathématiques, on a dénit a*v=v*a. Mais on pourrait écrire ce que l'on veut et ne pas respecter la commutativité. double d(2) est équivalente à double d=2. constructeur double(int) est appelé.) la déclaration le (La signication est que 10.4.3 Exemple de cout << On aimerait pouvoir acher le contenu du vecteur v au moyen de la commande : <<v; Remarquons que l'objet à gauche de l'opérateur << cout est cout appartenant à la classe ostream (ici o signie output donc achage, et cette classe standard du C++ est obtenue par #include <iostream> ). Si l'on veut dénir cette opération dans notre classe Vect, il faudra donc la déclarer comme une fonction amie. Exactement comme l'exemple précédent, cela se fait en écrivant : Code à rajouter dans le code des fonctions membres : friend ostream & operator <<(ostream & sortie, Vect v2) { sortie <<Vecteur: <<v2.x <<| <<v2.y <<| <<v2.z <<|; return sortie; } Utilisation dans le programme principal : main() { Vect v(1,2,3); cout <<v <<endl; } Exercice 10.4.1. (*) Ecrivez le code nécessaire an de pouvoir demander le contenu d'un vecteur à l'utili- cin >>v; L'utilisateur doit entrer les composantes à la suite des Vect x=?,y=?,z=?. La syntaxe est cette fois-ci : istream & operator >>(istream & entree,Vect & v2); Le symbole & s'appelle passage par références, et est nécessaire puisque le vecteur v sateur par la syntaxe questions est modié à la sortie de la fonction. CHAPITRE 10. 10.5 69 CRÉATION D'UNE CLASSE Vecteurs de taille variable 3 On va améliorer la classe de vecteurs de R précédente, en permettant d'avoir des N vecteurs de R avec N quelconque, au choix de l'utilisateur. Important : avant de commencer, nous conseillons vivement de (re)lire le paragraphe sur les pointeurs. Il apparaît que les variables membres de la nouvelle classe, doivent être : (Code à écrire dans la déclaration de la classe :) //---- variables membres int N; //taille double *element; //pointeur Important : la valeur de N nous renseigne à tout moment sur la taille de l'espace mémoire réservé. Cette modication nous obligera à réécrire les fonctions membres de la classe Vect, mais aussi d'introduire deux nouvelles fonctions membres : le constructeur par recopie, et l'opérateur d'aectation =. Exercice 10.5.1. Créer une nouvelle classe de vecteurs, qui contient les variables membres ci-dessus, avec les fonctions membres suivantes : Une fonction membre Une fonction membre Libere() qui libère l'espace mémoire de taille N. Init(Ni) qui fait dans l'ordre les opérations suivantes : (1) Libère l'emplacement mémoire déjà existante (si N>0) (2) :Réserve l'emplacement mémoire de taille Ni , (si Ni>0). (3) :met N=Ni. (4) met les composantes du vecteur à zéro. Un constructeur Vect(), qui met seulement N = 0, et ne réserve pas de place mémoire. (il appelle la fonction : Init(0) ; ) Un constructeur Vect(int Ni), pour créer un vecteur de taille Ni (il appelle la fonction : Init(Ni) ; ). Un destructeur ~Vect() qui libère l'espace mémoire. (il appelle la fonction Li- bere()) 10.5.1 Constructeur par recopie double Produit v.Produit(w) dans le pro- Dans un exercice précédent, nous avons écrit une fonction membre (Vect v2) . Lors de l'appel de cette fonction par la syntaxe gramme principal, un objet v2 est créé, et l'objet w est copié dedans. Mais il était apparu qu'aucun Constructeur n'était explicitement appelé pour cette recopie. La raison est que le compilateur C++ a appelé un constructeur de recopie par défaut, qu'il a créé lui-même. Dans ce constructeur par défaut, il est eectuée une recopie simple des variables membres (il s'agissait de x,y,z). CHAPITRE 10. 70 CRÉATION D'UNE CLASSE Dans le cas actuel, une recopie simple des variables membres ne sut pas. A la création du nouvel objet Vect v2, il faut en eet réserver l'emplacement mémoire, et donc écrire nous même le constructeur par recopie. Le code est le suivant : Code à rajouter dans le code des fonctions membres : Vect (const Vect & v2) { // ... code à écrire (exercice)... } 10.5.2 Opérateur d'aectation = Dans la classe Vect de R3 , si v,w sont deux vecteurs, et que l'on écrit l'opération d'aectation : v=w; alors les composantes de w sont copiées dans celles de v. Cette opération d'aectation a été faite automatiquement par le compilateur. Il y a recopie des variables membres x,y,z. Dans notre cas actuel de vecteur à taille variable, on ne peut laisser faire le compilateur, puisqu'il faut libérer l'ancien emplacement mémoire de v, puis lui réserver un nouvel emplacement mémoire, et ensuite copier les composantes de w dans celles de v. On va écrire nous même le code de l'opérateur d'aectation = Code à rajouter dans le code des fonctions membres : Vect & operator=(const Vect & v2) { // ... code à écrire (exercice)... } Exercice 10.5.2. 1. Ecrivez le code du constructeur par recopie et de l'opérateur d'aectation = ci dessus, et testez. (en achant des messages). 2. Reprenez les fonctions membres de la classe Vect de versions Vect de R N R3 précédente, et écrivez les correspondantes. Concernant spécialement l'opérateur [ ], avec N éléments, on décide d'y accéder de la façon suivante : Vect v(N); v[0],v[1],...,v[N-1], Rem : Dans le code de l'opérateur [ ](int i), il est bien de tester si l'indice i est dans l'intervalle autorisé, et sinon d'acher un message d'erreur (i.e. si i<0, ou i>N-1). CHAPITRE 10. 71 CRÉATION D'UNE CLASSE 3. Dans un deuxième temps, on aimerait que le premier indice i1 soit quelconque (et pas forcément i1 = 0). Par exemple, que les trois composantes d'un vecteur de R3 soit v[2],v[3],v[4], i.e. i1 = 2, N = 3; Précisement, on voudrait qu'un vecteur soit caractérisé par sa taille N, et par son v[i] i[i1 , i1 + 1, ..., i1 + N ]. Il faut donc rajouter i1 comme variable membre de la classe, et modier les construcpremier indice i1 , an d'accéder aux éléments du vecteur par la commande avec teurs et destructeurs, et fonctions membres si besoin est. A la n, on aura une classe de vecteur assez sophistiquée, et prête à l'emploi. 4. (Long) Sur le même modèle que la classe vecteur, créer une classe matrice, avec les opérations usuelles. Rem : on pourra dénir une matrice N*M comme étant un tableau de vecteurs, et prendre comme variables membres: int N,M; // taille Vect * ligne; Rem : attention aux fonctions Init() et Libere() ! Chapitre 11 Micro-projets En guide de conclusion de cette première partie, nous vous proposons de réaliser un micro-projet parmi la liste suivante. Ce sont des programmes rapides à réaliser (quelques lignes de code), an de vous montrer l'aspect ludique et créatif de la programmation, et aussi pour résumer diérentes notions vues dans ce didacticiel. Choisissez et réalisez un de ces micro-projet, selon votre motivation et l'aisance que vous ressentez en informatique. Pour le graphisme, utiliser la Section 12. 11.1 Suite de Syracuse Diculté : facile. u0 ≥ 1. Par récurrence, on dénit un+1 ( 1 un si un est pair un+1 = 21 (3un + 1) si un est impair 2 Soit un nombre entier à partir de un par : u0 = 1 donne la suite innie périodique 1, 2, 1, 2, 1, . . . La valeur de départ u0 = 7 donne la suite 7, 11, 17, 26, 13, 20, 10, 5, 16, 8, 4, 2, 1, 2, 1, 2, 1, . . . Observer que la valeur de départ La fameuse valeur conjecture de Syracuse non démontrée à ce jour est que partant de toute u0 la suite arrive forcément au bout d'un moment T à la suite périodique 1, 2, 1, 2, . . . Exercice 11.1.1. 1. Ecrire un programme qui demande ce que la valeur 2. Pour u0 un = 1 à l'utilisateur et ache la suite (un )n jusqu'à soit atteinte. T (u0 ) la plus u0 ∈ {1, 2, . . . 1000}. donné, on note en fonction de u0 petite valeur de 72 n telle que un = 1. Tracer T CHAPITRE 11. 11.2 73 MICRO-PROJETS Pendule simple Diculté : moyenne. Dessiner (en animation) dans l'espace réel et dans l'espace des phases, le mouvement d'oscillation d'un pendule simple de masse m, longeur l , soumis à la force de pesanteur P = mg avec g = 9.8m/s2 et à une force de frottement de coef γ . Equations physiques : temps t si a (t) est la position angulaire et b (t) la vitesse angulaire au on a da = Fa := b dt db g γ = Fb := − sin (a) − b dt l m Position du pendule : x = l sin (a) , Méthode de résolution numérique petit intervalle de temps dt ; y = −l cos (a) Avec la au premier ordre en méthode de Euler, on considère un dt on a a (t + dt) = a (t) + dt · Fa b (t + dt) = b (t) + dt · Fb Structure du programme Ecrire une fonction : /* Formule du champ de vecteur sur le plan (a,b) entree: a,b CHAPITRE 11. 74 MICRO-PROJETS sortie: Fa,Fb */ void F(double a, double b, double & Fa,double & Fb) { //... code ici } Dans la fonction main, partir de valeurs a, b initiales, calculer a, b à chaque pas de temps et dessiner le pendule. 11.3 La fractale du dragon Diculté : moyenne. Si on prend une feuille de papier que l'on plie N =1 fois et que l'on impose au pli de faire un angle droit (90°) on obtient la forme suivante. Si on plie N = 3 fois ou ...N = 16 fois on obtient les formes suivantes assez surprenantes. La forme pour N 1 s'appelle la fractale du dragon. N =2 fois ou CHAPITRE 11. 75 MICRO-PROJETS Ecrire un programme qui demande une valeur plie N N ≥ 1 et dessine la forme obtenue si l'on fois. Qu'observez vous de surprenant ? Aide : on peut coder la forme comme une suite −1) SN de 2N − 1 valeurs ±1, où +1 (respect. signie que le pli est à droite (respect. à gauche). Observer que la suite SN se déduit facilement de la suite SN −1 . Dans le programme il faut faire une fonction qui s'appelle elle même (récursivité). 11.4 Equation de la chaleur Diculté : moyenne. Notons x = (x1 , . . . xn ) les variables d'espace et t la variable de temps. Joseph Fourier a découvert en 1822 que la propagation de la température dans un matériau est régit par l'équation d'évolution ∂T (x, t) = (∆T ) (x, t) ∂t appelée équation de la chaleur, avec l'opérateur Laplacien : ∆T := ∂ 2T ∂ 2T + . . . + ∂x21 ∂x2n n = 2. On discrétise l'espace avec un x1 = ih, x2 = jh avec i, j = 0 . . . N − 1. Le champ l'instant t est modélisé par une matrice T (i, j) de taille N × N . On 0 avec un pas 1 très petit et on note T le champ de température à On va modéliser cette évolution en dimension pas h1 très petit. On note donc de températures à discrétise le temps l'instant t + . On remplaçant les dérivées par des diérences nies montrer que l'équation de la chaleur ci-dessus devient : CHAPITRE 11. 76 MICRO-PROJETS T 0 (i, j) = T (i, j) + (T (i + 1, j) + T (i − 1, j) + T (i, j + 1) + T (i, j − 1) − 4T (i, j)) . h2 Partant d'un champ de températures quelconque, en utilisant cette modélisation, écrire un programme qui fait évoluer et dessine le champ de température. Discuter le choix des paramètres 11.5 , h. Le jeu de la vie Diculté : moyenne. En utilisant une matrice d'entiers de taille 10*10 avec des conditions périodiques, faire évoluer et dessiner les coecients de matrice selon les règles du jeu de la vie. 11.6 Algorithme de MonteCarlo pour le modèle d'Ising Diculté : moyenne à dicile. Figure 11.1 Resultat de l'algorithme de Monte-Carlo pour le modèle d'aimantation de Ising, aux températures β = 1/(kb T ) = 0.3 (désordre), 0.4 (transition de phase) et 0.5 (ordre magnétique). (Explication physique : voir Cours de Master 1: système dynamiques, chapitre Dynamique probabiliste sur les graphes). On considère un réseau les variables sont fX = ±1 N ×N X = (x, y) et dont et modélisent des spins (up/down). Si deux sites sont voisins (chaque site a 4 voisins) on note f = (fX )X . périodique dont les sites sont notés X ∼ Y . Une conguration des spins est un champ donné : Son énergie ferromagnétique est : E (f ) := X X X Y,X∼Y (−fX .fY ) CHAPITRE 11. 77 MICRO-PROJETS Remarque 11.6.1. Il y a N2 sites et 2N 2 congurations possibles. (−fX .fY ) minimal soit fX .fY = 1 pour tous X, Y . Il y a donc deux congurations d'énergie minimale qui sont : fup ={fX = 1 pour tous site X } et fdown ={fX = −1 pour tous site X }. Les congurations d'énergie minimale sont celles donnant (−fX .fY ) maximal soit fX .fY = −1 pour tous X, Y . Cela est possible si N est pair avec des spins dont les valeurs sont alLes congurations d'énergie minimale sont celles donnant ternées (il y a deux congurations). 11.6.1 Algorithme de Monte-Carlo L'algorithme d'évolution suivant est appelé algorithme de montecarlo. paramètre xé qui est l'inverse de la température : 1. A l'instant n = 0, 2. On choisit un site site X β = 1/ (kb T ). on part d'une conguration X β ≥ 0 est un f choisie au hasard. 0 au hasard, et on note f la conguration identique à fX0 = −fX (spin opposé). f sauf au où le spin est opposé : 3. Choix de la conguration à l'instant n + 1, E (f 0 ) < E (f ) on choisit la conguration f 0 . 0 0 Si E (f ) ≥ E (f ) on choisit la conguration f exp (−β. (E (f 0 ) − E (f ))) (et on choisit f avec la (a) Si (b) avec la probabilité Pf →f 0 = probabilité complémentaire). 4. On revient en (2) pour poursuivre l'évolution du champ de spins. Remarque 11.6.2. Cet algorithme correspond à une matrice stochastique réversible pour la mesure de Boltzmann : u (f ) = 1 exp (−βE (f )) Z Exercice 11.6.3. 1. Ecrire un programme qui fait évoluer et représente la conguration du champ de spins fX selon l'algorithme de Monte-Carlo. (Utiliser une matrice d'entiers avec armadillo et la fonction Dessin()). de sls sls d β. f est M (f ) := P X fX . Représenter l'aimantation au cours du temps. Quand l'équilibre statistique est atteind, calculer la moyenne hM i et la variance V = (M − hM i)2 . Représenter hM i (β) et V (β) en fonction 2. L'aimantation de la conguration Deuxième partie Projet 78 79 On propose maintenant de réaliser un projet d' expérimentation numérique. Voir la page projets. Troisième partie Compléments sur le langage C++ 80 81 Dans les chapitres suivants nous présentons quelques librairies C++ utiles. Nous conseillons de consulter la page de Boost qui présente d'autres librairies. Chapitre 12 Librairie Root : graphisme et autre.. (compléments) 12.1 Graphisme de base 2D 12.1.1 Objets graphiques Voici quelques exemples de graphisme 2D. Attention à ne pas oublier la ligne d'entête #include<..> correspondant à l'objet que vous dessinez. Figure 12.1 Graphisme de base 2D #include #include #include #include #include <TApplication.h> <TCanvas.h> <TLine.h> <TEllipse.h> <TMarker.h> 82 CHAPITRE 12. LIBRAIRIE ROOT : GRAPHISME ET AUTRE.. (COMPLÉMENTS)83 r #include <TBox.h> #include <TText.h> int main(int argc, char **argv) { TApplication theApp("App", &argc, argv); //.. La fenetre TCanvas *c=new TCanvas("titre","titre",400,400); c->Range(0,0,1,1); //.. une ligne bleue TLine *l=new TLine(0.1,0.5,0.3,0.9); // x1,y1,x2,y2. l->SetLineColor(kBlue); // bleu l->Draw(); //.. Une Ellipse (ou cercle) TEllipse *e= new TEllipse(0.2,0.3,0.1); // (centre (x,y) et rayon e->Draw(); //.. Un point TMarker *m=new TMarker(0.3,0.4,8); // (x,y) et 6 ou 8: forme du Marker m->SetMarkerColor(kPink); m->Draw(); //.. Un rectangle vert TBox *p=new TBox(0.15,0.6,0.25,0.7); // on precise les coins bas-gauche (x1,y1) et haut droit (x2,y2) : p->SetFillColor(kGreen); p->Draw(); //.. Du texte TText *texte=new TText(0.2,0.2,"un petit texte"); // position x,y texte->Draw(); c->Update(); // Montre le dessin theApp.Run(); return 0; } 12.1.2 Plusieurs fenêtres Avec la classe TCanvas qui hérite de TPad. c2 TCanvas c1("c1","fenetre",400,400); // creation de la fenetre c1 TCanvas c2( "c2","fenetre",400,400); // creation de la fenetre TMarker *m1=new TMarker(0.3,0.4,8); // un point CHAPITRE 12. LIBRAIRIE ROOT : GRAPHISME ET AUTRE.. (COMPLÉMENTS)84 TMarker *m2=new TMarker(0.3,0.4,8); // un point m2->SetMarkerColor(kRed); c1.cd(); // choix de la fenetre c1 pour le prochain dessin m1->Draw(); // dessin du point 1 c2.cd(); // choix de la fenetre c2 pour le prochain dessin m2->Draw(); // dessin du point 2 12.1.3 Un cadre avec des axes gradués : DrawFrame Avec la classe TPad. TCanvas *c=new TCanvas("titre","titre",400,400); double x1(0),y1(0),x2(1),y2(1); // position du cadre c->DrawFrame(x1,y1,x2,y2); Figure 12.2 Un cadre seul avec axes gradués. On peut compléter le dessin du cadre de la façon suivante. On récupère un pointeur sur un histogramme de la classe TH1, et on a accès aux fonctions membres pour modier le cadre, par exemple : #include #include #include #include <TApplication.h> <TCanvas.h> <TEllipse.h> <TH1F.h> int main(int argc, char **argv) { TApplication theApp("App", &argc, argv); TCanvas *c=new TCanvas("titre","titre",400,400); CHAPITRE 12. LIBRAIRIE ROOT : GRAPHISME ET AUTRE.. (COMPLÉMENTS)85 double xmin(0),ymin(0),xmax(5),ymax(5); double x1(0),y1(0),x2(10),y2(10); // position du cadre TH1F *h =c->DrawFrame(x1,y1,x2,y2); TEllipse e(2,3,1); e.Draw(); h->SetXTitle("x"); h->SetYTitle("y"); h->SetMinimum(-2); // change les bornes en y h->SetMaximum(8); h->SetBins(1000,1,5); // change les bornes en x: 1->5 c->Update(); // Montre le dessin theApp.Run(); return 0; } Figure 12.3 Un cadre seul avec axes gradués. On a rajouté un titre aux axes et changé les bornes. 12.1.4 Un seul axe gradué : TGaxis Avec la classe TGaxis. #include <TGaxis.h> double x1(0),y1(0),x2(1),y2(0); // position de l'axe double v1(0),v2(1); // graduations extremes TGaxis axe(x1,y1,x2,y2,v1,v2,510,""); axe.SetTitle("x"); // met le titre sur l'axe axe.Draw(); CHAPITRE 12. LIBRAIRIE ROOT : GRAPHISME ET AUTRE.. (COMPLÉMENTS)86 Figure 12.4 un axe seul gradué. 12.2 Histogrammes Voici quelques exemples d'utilisation des Histogrammes. Pour utiliser ces exemples, il faut remplacer les parties (C) de l'exemple de la Section 9.2, par le code ci-dessous. Attention : la ligne #include <..> mentionnée chaque fois est à mettre en haut de votre programme. 12.2.1 Courbe (x,y) avec axes à partir de tableaux de valeurs x[] y[] : TGraph Avec la classe TGraph. #include <TGraph.h> #include <math.h> // pour utiliser sin(.) //---- creation de tableaux int n = 20; float x[20], y[20]; for (int i=0;i<n;i++) // remplissage { x[i] = i*0.1; y[i] = 10*sin(10*x[i]); } //--- dessin TGraph gr(n,x,y); gr.SetMarkerColor(kBlack); gr.SetMarkerStyle(6); // 6:petit cercle gr.Draw("AC"); // trace un segment entre les points 12.2.2 Histograme 1Dim à partir de tableau y[] : TH1F Avec la classe TH1F. #include <TH1.h> int nx(10); // nombre de points double xmin(0.0),xmax(1.0); // graduation de l'axe x TH1F *h1=new TH1F("nom","titre",nx,xmin,xmax); h1->SetXTitle("x"); CHAPITRE 12. LIBRAIRIE ROOT : GRAPHISME ET AUTRE.. (COMPLÉMENTS)87 Figure 12.5 Courbe (x,y) avec axes à partir de tableaux x[] y[] h1->SetYTitle("y"); for(int i=1;i<=nx;i++) // remplissage { double z=i*i; h1->SetBinContent(i,z); } h1->Draw("L"); // option L: ligne ou P: marker Figure 12.6 Histograme 1Dim à partir de tableau y[]. 12.2.3 Courbe a partir de l'expression d'une formule y=f(x) : TF1 Avec la classe TF1. #include <TF1.h> TF1 *f1 = new TF1("f1","sin(x)/x",0,10); f1->Draw(); CHAPITRE 12. LIBRAIRIE ROOT : GRAPHISME ET AUTRE.. (COMPLÉMENTS)88 12.2.4 Surface 2Dim : z=f(x,y) à partir d'un tableau z[i,j] : TH2F Avec la classe TH2F. #include <TH2.h> int nx(10),ny(10); double xmin(0),xmax(1),ymin(0),ymax(2); TH2F *h= new TH2F("h1","titre",nx,xmin,xmax,ny,ymin,ymax); h->SetXTitle("x"); h->SetYTitle("y"); h->SetStats(kFALSE); // pas de panneau stat for(int i=1;i<=nx;i++) // remplissage for(int j=1;j<=ny;j++) { double z=i*j; h->SetCellContent(i,j,z); } h->Draw("surf2"); // essayer options: surf1, surf3,surf4,lego Figure 12.7 Surface 2Dim : z=f(x,y)a partir d'un tableau z[i,j] 12.2.5 Surface 2D a partir de l'expression d'une formule z=f(x,y) : TF2 Avec la classe TF2. #include <TF2.h> TF2 *f2 = new TF2("f2","sin(x)*sin(y)/(x*y)",0,5,0,5); f2->Draw("surf4"); CHAPITRE 12. LIBRAIRIE ROOT : GRAPHISME ET AUTRE.. (COMPLÉMENTS)89 12.2.6 Crée histogramme 1D par tirage aléatoire et ajustement par une formule Remarque 12.2.1. voir http ://root.cern.ch/root/HowtoFit.html #i n c l u d e <TRandom . h> #i n c l u d e <T A p p l i c a t i o n . h> #i n c l u d e <TCanvas . h> #i n c l u d e <TF1 . h> #i n c l u d e <TH1 . h> #i n c l u d e <i o s t r e a m > using int namespace main ( i n t std ; argc , char ** a r g v ) { TApplication TCanvas //−−−−−−− cree theApp ( " App " , &a r g c , * c=new histo argv ) ; TCanvas ( " t i t r e " , " t i t r e " , 4 0 0 , 4 0 0 ) ; avec tirage aleatoire avec loi gaussienne −−−−−−−−−−−− d o u b l e xmin=−3,xmax=3; * h=new TH1D( "TW" , "TW" , n b i n , xmin , xmax ) ; gRandom−>S e t S e e d ( ) ; / / i n i t i a l i s e s u r l ' h o r l o g e int nbin =100; TH1D for ( int i =1; i <=1e 5 ; i ++) // t i r a g e s { double x= gRandom−>Gaus ( 0 , 1 ) ; // c ' est P( x)= e x p ( − 0 . 5 * ( x / s i g m a CHAPITRE 12. LIBRAIRIE ROOT : GRAPHISME ET AUTRE.. (COMPLÉMENTS)90 h−> F i l l ( x ) ; } h−>Draw ( ) ; //−−−− calcule integrale et normalise l ' histogramme −−−−−−−−−−− I= h−>I n t e g r a l ( ) ; double h−>S c a l e ( 1 / I ) ; h−>Draw ( ) ; //−−− Fit TF1 l ' histogramme * f1 avec une Gaussienne −−−−−−−−−−−−−− = new TF1 ( " f 1 " , " g a u s " ) ; h−>F i t ( " f 1 " ) ; c o u t <<"E c a r t //−−− m o n t r e t y p e ="<<f 1 −>G e t P a r a m e t e r (2)<< e n d l ; fenetres de commandes −−− h−>DrawPanel ( ) ; h−>F i t P a n e l ( ) ; //−−−−−− c−>Update ( ) ; //−−−−−− theApp . Run ( ) ; return 0; } 12.2.7 Champ de vecteur /// Ce code dessine un champ de vecteur spécié à deux dimensions. #include <TApplication.h> #include <TCanvas.h> #include <TArrow.h> CHAPITRE 12. LIBRAIRIE ROOT : GRAPHISME ET AUTRE.. (COMPLÉMENTS)91 Figure 12.8 Champ de vecteur #include <TLine.h> #include <math.h> #include <iostream> using namespace std; ///--- definition du champ de vecteur void vect(double x, double y, double &vx, double &vy) { vx = x ; vy= -y; } //---------------------------------int main(int argc, char **argv) { TApplication theApp("App", &argc, argv); //-----------double xmin=-1, xmax=1, ymin=-1,ymax=1; // coordonnées TCanvas *c=new TCanvas("c","c",500,500); // fenetre c->DrawFrame(xmin,ymin,xmax,ymax); //-----------------double N=20; // nbre d'echantillons double dx=(xmax-xmin)/N; double dy=(ymax-ymin)/N; for (int i=0; i<N; i++) for (int j=0; j<N; j++) { double x=xmin + dx*i; double y=ymin + dy*j; double vx,vy; vect(x,y,vx,vy);// calcul les coordonnées du champ de vecteur vx *= dx *1; // normalise CHAPITRE 12. LIBRAIRIE ROOT : GRAPHISME ET AUTRE.. (COMPLÉMENTS)92 vy *= dy *1; double n=sqrt(vx*vx+vy*vy); // norme TArrow *t= new TArrow(x,y,x+vx,y+vy,dx*n); // TLine *t= new TLine(x,y,x+vx,y+vy); t->Draw(); } // for i,j //-donne la main a l'utilisateur (A) theApp.Run(); return 0; } 12.3 Utilisation de la souris Fenetre (héritée de la Voici un code complet qui denit une classe que l'on appelle classe TCanvas). La nouveauté est la fonction ExecuteEvent() qui est appelée chaque fois que la souris bouge devant la fenetre. Lorsque le programme execute l'instruction theApp.Run(); il est en attente d'évènements provenant du clavier ou de la souris. Si un tel évènement se produit, il appelle la fonction void ExecuteEvent(Int_t event, Int_t px, Int_t py), en passant les paramètres de l'évènement. C'est donc dans cette fonction que vous allez rajouter du code ou des appels à d'autres fonctions. #i n c l u d e <i o s t r e a m > using namespace std ; #i n c l u d e <TCanvas . h> #i n c l u d e <TMarker . h> #i n c l u d e <T A p p l i c a t i o n . h> #i n c l u d e < T E l l i p s e . h> //====Ma C l a s s e class Fenetre : F e n e t r e================================ public TCanvas { public : // constructeur . . . . . . F e n e t r e ( Text_t * name , { //− void Cette Text_t * title , I n t _ t ww, I n t _ t wh ) : TCanvas ( nam } fonction est ExecuteEvent ( Int_t appelle event , si la Int_t souris px , s ' agitte . Int_t py ) { // affiche c o u t <<" // l ' etat position : convertit la de la souris px="<<px<<" py="<<py<<" c o d e position en pixel px , py en du b o u t o n="<<e coordonnees (x , y) CHAPITRE 12. LIBRAIRIE ROOT : GRAPHISME ET AUTRE.. (COMPLÉMENTS)93 double x = gPad−>A b s P i x e l t o X ( px ) ; double y = gPad−>A b s P i x e l t o Y ( py ) ; // si bouton gauche appuye dessine un point i f ( e v e n t ==1) { *m=new TMarker TMarker ( x , y , 8 ) ; m−>Draw ( ) ; Update ( ) ; } // Ev e n t if ( e v e n t==k K e y P r e s s ) clavier // touche appuyée { c o u t <<"t o u c h e switch px="<<px<<e n d l ; ( px ) { // change case de mode ( t e m p é r é <−> juste ) 'm' : c o u t <<"Touche m a p p u y é e"<< e n d l ; break ; }; } // if // switch px event } }; ///−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− int main ( i n t argc , char ** a r g v ) { TApplication Fenetre double *c theApp ( " App " , &a r g c , = new Fenetre (" c " , theApp . Run ( ) ; return 400 ,400); // crée une fenet xmin ( 0 ) , ymin ( 0 ) , xmax ( 5 ) , ymax ( 5 ) ; c−>Range ( xmin , ymin , xmax , ymax ) ; } argv ) ; " fenetre1 " , 0; // coordonnees de la fenetre ( optionne CHAPITRE 12. LIBRAIRIE ROOT : GRAPHISME ET AUTRE.. (COMPLÉMENTS)94 12.3.0.1 Pour gérer la souris lorsque le programme calcule Si votre programme est lancé dans un calcul interminable, et que vous vouliez cependant modier des paramètres par l'intermédiaire de la souris ou d'un commande clavier, il faut dans votre boucle mettre la commande suivante : gSystem->ProcessEvents(); // avec #include <TSystem> Ainsi si la souris est actionnée, dans l'exemple ci-dessus, la fonction Fenetre::ExecuteEvent sera appelée. 12.4 Comment créer un chier gif animé. Voici un exemple complet qui permet de créer un chier lm.gif animé à partir d'images créées par root. Travail préalable : 1. Fichiers à télécharger et sauver dans son répertoire : bib_fred.h et bib_fred.cc. 2. Il faut avoir installé le logiciel gifsicle, #include <TApplication.h> #include <TCanvas.h> #include <TEllipse.h> #include "bib_fred.h" ///-----------------------------------int main(int argc, char **argv) { TApplication theApp("App", &argc, argv); TCanvas *c= new TCanvas("c","fenetre",400,400); double xmin(-1),ymin(-1),xmax(2),ymax(2); c->Range(xmin,ymin,xmax,ymax); int periode = 10; // temps entre chaque image en 1/100e sec. int imin=1, imax=30; for(int i =imin; i<=imax ; i++) { double x = i/(double)imax; TEllipse e(x,0.5,1); e.Draw(); // dessine l'ellipse c->Update(); Animation(c,i,imin,imax,"film",periode); // -> crée fichier film.gif } CHAPITRE 12. LIBRAIRIE ROOT : GRAPHISME ET AUTRE.. (COMPLÉMENTS)95 theApp.Run(); return 0; } Ensuite pour visualiser le lm vous pouvez utiliser la commande 12.5 firefox film.gif. Boutons de commandes (Widgets) Documentation Exemple très complet qui montre toutes les possibilités et comment le faire : guitest.cc à copier/coller. (attention, il y a une erreur : remplacer (char **) par (const char **)). Voici un exemple plus simple, à copier/coller, donnant : / * ! =========================== * Exemple s i m p l e d ' une f e n e t r e de commande . * I l y a deux c l a s s e s : l a c l a s s e Com q u i e s t l a f e n e t r e * e t q u i p e r m e t d e commander une a u t r e c l a s s e A * Chaque c l a s s e a un l i e n v e r s l ' a u t r e a f i n d e p o u v o i r */ #i n c l u d e <TCanvas . h> #i n c l u d e <TGFrame . h> de commande communiquer . CHAPITRE 12. LIBRAIRIE ROOT : GRAPHISME ET AUTRE.. (COMPLÉMENTS)96 #i n c l u d e <TGButton . h> #i n c l u d e <TGComboBox . h> #i n c l u d e <TGTextEntry . h> #i n c l u d e <TGLabel . h> #i n c l u d e <T G P r o g r e s s B a r . h> #i n c l u d e <TPad . h> #i n c l u d e <TFrame . h> #i n c l u d e <TH1F . h> #i n c l u d e <TSystem . h> #i n c l u d e <TGNumberEntry . h> #i n c l u d e <T G S l i d e r . h> #i n c l u d e <T G D o u b l e S l i d e r . h> #i n c l u d e <T G T r i p l e S l i d e r . h> #i n c l u d e <TGMenu . h> #i n c l u d e <T A p p l i c a t i o n . h> #i n c l u d e <TRootEmbeddedCanvas . h> #i n c l u d e <t h r e a d > #i n c l u d e <c h r o n o > #i n c l u d e <i o s t r e a m > using namespace class A; // std ; classe definie /*!==========f e n e t r e * Description */ class Com : de public la de après commandes ======================== classe Com TGMainFrame // ( derivee d une fenetre la fenetre { public : *fMB ; // b a r r e de menu * fM ; // menu TGTextButton * fB ; // b o u t o n TGTextEntry * fT ; // z o n e t e x t e TGLabel * f L ; // t e x t e f i x e e c r i t TGComboBox * fC ; // l i s t e deroulante TGCheckButton * fCB ; TGMenuBar TGPopupMenu TGHProgressBar TGNumberEntry TGHSlider * fS ; * fH ; * fNE ; // barre de progres sur GUI ) CHAPITRE 12. LIBRAIRIE ROOT : GRAPHISME ET AUTRE.. (COMPLÉMENTS)97 * fS2 ; TGTripleHSlider * fS3 ; TRootEmbeddedCanvas * fEC ; TGDoubleVSlider // fenetre //−−−−−−−−−−−−−−−−−−− * a ; / / l i e n v e r s un o b j e t a //−−−−−−−−−−−−−−−−−−−−− Com(A * a_i ) ; // c o n s t r u c t e u r A ~Com ( ) ; Bool_t // d ' une classe A destructeur P r o c e s s M e s s a g e ( Long_t msg , Long_t parm1 , Long_t ) ; // fonction appe //========================================= // met void a jour les valeurs Met_a_jour ( ) ; // de la fonction fenetre qui met à jour les valeurs de la fenetre this_thread : : s l e e p _ f o r ( chrono : : m i l l i s e c o n d s {100}); // attend }; /*!=============== * Une classe pour effectuer un calcul ... */ class A { public : double x; //−−−−−−−−−−−−− void Calcul () { c o u t <<" c a l c u l : " ; f o r ( x =1; x <=50; x++) { 0.5 ms// attente , com−>Met_a_jour ( ) ; c o u t <<x<<","<< f l u s h ; } c o u t <<e n d l ; } //−−−−−−−−−−−− Com * com ; // pointeur vers l ' objet fenetre de control CHAPITRE 12. LIBRAIRIE ROOT : GRAPHISME ET AUTRE.. (COMPLÉMENTS)98 void Lance_commandes ( ) { TApplication com= l ' objet theApp ( " App " , NULL, NULL ) ; new Com( t h i s ) ; // cree fenetre de control et l ' associe a present theApp . Run ( ) ; //−−−−donne la main a l ' utilisateur −−− } }; /*!===================== Constructeur */ Com : : Com(A * a_i ) : TGMainFrame ( g C l i e n t −>GetRoot ( ) , 4 0 0 , 3 5 0 ) // taille de la f { a=a_i ; // initialise le lien vers l ' objet à controller ( sera utile pour a−>com= t h i s ; //−−−−−−−−−−−−−−−− SetWindowName ( " F e n e t r e //−−−−−−−−− Menu de commandes " ) ; // nom d e la fenetre −−−−−−−−−− fMB = new TGMenuBar ( t h i s , AddFrame ( fMB , new 35 , 50 , kHorizontalFrame ) ; TGLayoutHints ( kLHintsTop | kLHintsExpandX , 2, 2, 2, fM=fMB−>AddPopup("&Menu " ) ; fM−>AddEntry ("& O p t i o n 1" ,3); fM−>A d d S e p a r a t o r ( ) ; fM−>AddEntry ( "O&p t i o n 2" ,4); fM−>A s s o c i a t e ( t h i s ) ; //−−−−− b o u t o n s −−−−−−−−−−−−−−−−−−−−−−−− fB= new TGTextButton ( t h i s , " S&t a r t " , 2 ) ; code message // cree bouton , lettre 2 fB−>Move ( 1 0 0 , 1 0 ) ; // position fB−>S e t T o o l T i p T e x t ( " Demarre un x,y calcul ..."); // texte d ' aide fB−>A s s o c i a t e ( t h i s ) ; fB−>R e s i z e ( 8 0 , fB−>G e t D e f a u l t H e i g h t ( ) ) ; / / . . Label = t e x t e fixe . . . . . . . . . . . . . . . . . // taille (x , y) t , et 5)) CHAPITRE 12. LIBRAIRIE ROOT : GRAPHISME ET AUTRE.. (COMPLÉMENTS)99 f L= new TGLabel ( t h i s , f L −>Move ( 1 0 , 4 0 ) ; / / . . . Zone string de // texte new TGString ( " L a b e l = Texte position x,y . . . . . . . . . . . . . . . . t e x t =" t o t o " ; fT = new TGTextEntry ( t h i s , t e x t . c _ s t r ( ) , 1 ) ; fT−>Move ( 1 0 , 7 0 ) ; // position // c o d e 1 x,y fT−>R e s i z e ( 4 0 , fT−>G e t D e f a u l t H e i g h t ( ) ) ; char fixe ")); // taille (x , y) buf [100]=" t e x t e " ; fT−>A s s o c i a t e ( t h i s ) ; //−−−−−−− Liste −−−− Combo fC = new TGComboBox ( t h i s , 88); fC−>AddEntry ( " e n t r e e 1" ,1); fC−>AddEntry ( " e n t r e e 2" ,2); fC−>R e s i z e ( 1 5 0 , fC−>S e l e c t ( 2 ) ; AddFrame ( fC , 20);// // new fC−>Move ( 1 0 , 1 0 0 ) ; //−−−−− P r o g r e s s fH = new taille selection a (x , y) priori TGLayoutHints ( kLHintsTop // | kLHintsLeft , 5 , 5 , 5 , 5 ) ) ; // mis position B a r s −−−−−−−−−−−−−− TGHProgressBar ( t h i s , T G P r o g r e s s B a r : : kFancy , 3 0 0 ) ; fH−>S h o w P o s i t i o n ( ) ; fH−>S e t B a r C o l o r ( " g r e e n " ) ; fH−>Move ( 1 0 , 1 3 0 ) ; // position fH−>S e t R a n g e ( 0 . 0 , 5 0 . ) ; fH−>S e t P o s i t i o n ( 1 0 . ) ; // // min , max position fH−>S h o w P o s i t i o n (kTRUE, kFALSE , " v a l e u r : //−−−−−−−−−−− f S = new Sliders %.0 f " ) ; −−−−−−−−−−− TGHSlider ( t h i s , 1 5 0 , k S l i d e r 1 | k S c a l e D o w n R i g h t , 2 ) ; // simple , f S −>S e t R a n g e ( 0 , 5 0 ) ; f S −>S e t P o s i t i o n ( 3 9 ) ; f S −>Move ( 1 0 , 2 0 0 ) ; f S 2 = new TGDoubleVSlider ( t h i s , 1 0 0 , kDoubleScaleNo , 3 ) ; f S 2 −>S e t R a n g e ( −10 ,10); f S 2 −>Move ( 2 0 0 , 2 0 0 ) ; // d o u b l e I d =2 CHAPITRE 12. LIBRAIRIE ROOT : GRAPHISME ET AUTRE.. (COMPLÉMENTS)100 f S 3 = new TGTripleHSlider ( t h i s , 1 0 0 , kDoubleScaleBoth , 4 , // t r i p l e kHorizontalFrame ) ; f S 3 −>S e t C o n s t r a i n e d (kTRUE ) ; f S 3 −>S e t R a n g e ( 0 , 1 0 ) ; f S 3 −>S e t P o s i t i o n ( 2 , 3 ) ; −>S e t P o i n t e r P o s i t i o n f S 3 −>Move ( 2 5 0 , 2 0 0 ) ; fS3 //−−−−−−−− e n t r e e double numerique (2.5); −−−−−− val =3.1415; fNE = new TGNumberEntry ( t h i s , val , 5, 4 0 0 , ( TGNumberFormat : : E S t y l e ) 2); // fNE−>A s s o c i a t e ( t h i s ) ; fNE−>Move ( 1 0 , 2 5 0 ) ; //−−−−− F e n e t r e Canvas // position −−−−−−−−−−−−−− fEC = new TRootEmbeddedCanvas ( " e c 1 " , AddFrame ( fEC , new this , TGLayoutHints ( kLHintsTop 200 , | 100); kLHintsLeft kLHintsExpandY , 5, 5, 5, | 5)); fEC−>Move ( 2 0 0 , 1 0 ) ; TCanvas *c = fEC−>GetCanvas ( ) ; c−>DrawFrame ( 0 , 0 , 1 , 1 ) ; c−>Update ( ) ; // c−>M o d i f i e d ( ) ; / / . . . . Check Button fCB= new TGCheckButton ( t h i s , " : b o u t o n fCB−>S e t T o o l T i p T e x t ( " t e x t e d ' aide check " , 4 2 0 ) ; pour bouton check " ) ; fCB−>A s s o c i a t e ( t h i s ) ; fCB−>R e s i z e ( 1 2 0 , fCB−>G e t D e f a u l t H e i g h t ( ) ) ; fCB−>Move ( 1 0 , 2 8 0 ) ; //−−−−−−−−−−−− MapSubwindows ( ) ; MapWindow ( ) ; } // // dessin dessin de des la objets fenetre ( bouttons ,...) kLHintsExpandX // les opti CHAPITRE 12. LIBRAIRIE ROOT : GRAPHISME ET AUTRE.. (COMPLÉMENTS)101 /*!==================================== * */ Destructeur Com : : ~ Com ( ) { delete fB ; delete fT ; delete fH ; delete fC ; } /*!==================================== * Fonction appelee l o r s q u e l ' * −> I c i on g à ¨ r e l e s a c t i o n s */ utilisateur Bool_t Com : : P r o c e s s M e s s a g e ( Long_t msg , agit Long_t p1 , sur la Long_t fenetre de p2 ) { i n t M = GET_MSG( msg ) , c o u t <<" M = "<<M<<" switch S=GET_SUBMSG( msg ) ; S = "<<S<<" p1="<<p1<<" (M) { case 1: switch (S) { case 3: s w i t c h ( p1 ) { case 2: c o u t <<"b o u t o n 2"<< e n d l ; a−>C a l c u l ( ) ; p2="<<p2<<e n d l ; commandes CHAPITRE 12. LIBRAIRIE ROOT : GRAPHISME ET AUTRE.. (COMPLÉMENTS)102 break ; } break ; case 4: i f ( p1 ==420) { c o u t <<"b o u t o n check .. "<< e n d l ; i f ( fCB−>IsOn ( ) ) c o u t <<" on."<< e n d l ; c o u t <<" o f f ."<< e n d l ; else } break ; case 7: c o u t <<"combo choisi : "<<fC−>G e t S e l e c t e d ()<< e n d l ; break ; } case 4: switch (S) { case 1: c o u t <<" t e x t e switch c h a n g e"<< e n d l ; ( p1 ) { case 1: c o u t <<"Le nouveau texte est : nouveau chiffre "<<((fT−>G e t B u f f e r ()) − > G e t S t r i n g break ; case 400: c o u t <<"Le break ; } break ; } break ; default : break ; } est : "<<((fNE−>GetNumber ())) < < e n d l ; CHAPITRE 12. LIBRAIRIE ROOT : GRAPHISME ET AUTRE.. (COMPLÉMENTS)103 Met_a_jour ( ) ; r e t u r n kTRUE ; } / * ! ========================================= * F o n c t i o n a p p e l à © e p a r une f o n c t i o n e x t e r n e * p o u r m e t t r e a j o u r l e s v a l e u r s de l a f e n e t r e */ void , Com : : Met_a_jour ( ) { fH−>S e t P o s i t i o n ( a−>x ) ; // // progress Bar g C l i e n t −>NeedRedraw ( fH ) ; gSystem −>P r o c e s s E v e n t s ( ) ; // handle GUI events } /*!===Main================================================ */ int main ( i n t argc , char ** a r g v ) { * a=new A ; a−>Lance_commandes ( ) ; A return 0; } 12.6 Autres trucs 12.6.0.1 Nombre aléatoire avec Root Voir TRandom. #include <TRandom.h> gRandom->SetSeed(0); // initialise le hasard sur l'horloge double x= gRandom->Uniform(2); // nombre aléatoire dans l'intervalle (0,2) 12.6.0.2 Options générales de Root avec #include <TROOT.h> gROOT->SetBatch(kTRUE); // mode Batch cad sans sortie d'ecran (+ rapide) gROOT->SetStyle("Plain"); // style standard gPad->SetEditable(0); // rend les dessins non modifiables CHAPITRE 12. LIBRAIRIE ROOT : GRAPHISME ET AUTRE.. (COMPLÉMENTS)104 gPad->SetEditable(1); // rend les dessins modifiables TCanvas *c=h->GetCanvas(); //Pour obtenir la fenetre TCanvas histogramme TH1 *h gStyle->SetPadGridX(kTRUE); // grilles en X,Y gStyle->SetPadGridY(kTRUE); à partir d'un Chapitre 13 Mesure du temps On présente ici la classe chrono. Mais on peut aussi conseiller la classe Boost.Date_Time. 13.1 13.1.0.1 Mesure de la date et du temps écoulé Mesure très précise du temps : #include <chrono> using namespace std::chrono; #include <iostream> using namespace std; int main() { auto t1=high_resolution_clock::now(); // mesure du "time point 1" //....... instruction qui prend du temps ..... for(int i=0;i<1e5;i++) cout<<" \t"<<i<<flush; //............. auto t2=high_resolution_clock::now(); // mesure du "time point 2" auto dt1=duration_cast<nanoseconds>(t2-t1).count(); // calcul de la durée en ns auto dt2=duration_cast<milliseconds>(t2-t1).count(); // calcul de la durée en ms 105 CHAPITRE 13. MESURE DU TEMPS 106 auto dt3=duration_cast<duration<double>>(t2-t1).count(); // calcul de la durée en s cout<<"durée: "<<dt1<< "ns"<<" = "<<dt2<< "ms"<<" = "<<dt3<<" s."<<endl; } Ache : 0 1 2 3 ...99996 99997 99998 99999 durée: 491264417ns = 491ms = 0.491264 s. 13.1.0.2 Achage de la date Au programme précédent, si on rajoute : auto tt=system_clock::to_time_t(t1); // convertit au type time_t cout << "Maintenant c'est: " << ctime(&tt); On obtient : Maintenant c'est: Mon Aug 25 19:37:30 2014 13.2 Attendre 13.2.1 Attendre une certaine durée #include <thread> #include <chrono> using namespace std::chrono; using namespace std::this_thread; sleep_for(seconds{2}); // attend 2 secondes sleep_for(milliseconds{3}); // attend 3 millisecondes 13.2.2 Attendre jusqu'à une certaine date #include <iostream> #include <thread> #include <chrono> using namespace std::chrono; using namespace std::this_thread; using namespace std; CHAPITRE 13. MESURE DU TEMPS 107 int main () { auto t1 = high_resolution_clock::now(); // date actuelle cout<<" J'attends 3 secondes"<<endl; auto t2 = t1 + seconds {3}; // date future sleep_until(t2); // attente auto t3 = high_resolution_clock::now(); // date mesurée auto dt = duration_cast<duration<double>>(t3-t1).count(); // mesure dt = t3-t1 cout<<"La durée a été dt = "<<dt<<" s."<<endl; return 0; } On obtient : J'attends 3 secondes La durée a été dt = 3.00016 s. Chapitre 14 Ecrire et lire des données en binaires sur un chier 14.1 Fichiers en format texte Dans les leçons précédentes, nous avons vu comment écrire des données numériques dans un chier. Ces données étaient écrites sous forme de chires (ex : 17) et sont lisibles depuis un éditeur. On dit qu'il s'agit d'une écriture en format texte ou format ASCII ; car un objet numérique est représentée en base dix par un ou plusieurs chires dans le chier. Or dans la mémoire de l'ordinateur une objet numérique (ex : short int i=17 stockée en binaire (i.e. base 2) sous forme d'une suite de 0 et 1 (appelés des séquence de 8 bits est appellée Par exemple la objet ) est bits. Une un octet). short int i contenant 17 est stockée en mémoire en base 2 par une suite de 16 bits, soit 2 octets : 0000 0000 0001 0001 Lorsque l'on écrit dans un chier en format texte (par la commande fichier <<i <<endl; ) l'ordinateur convertit le nombre en base dix et écrit la suite des chires, ce qui donne 17. (Il faut savoir que chaque caractère est codé par un octet en mémoire, selon le codage ASCII). L'avantage du format texte est que l'on peut visualiser le contenu du chier à l'aide d'un éditeur comme emacs. Rappels sur le code ASCII : cout <<(int)'A' << endl; // --affiche 65 qui est le code ASCII de A cout <<(char)(65) <<endl; //--affiche A qui a pour code ASCII: 65 108 CHAPITRE 14. ECRIRE ET LIRE DES DONNÉES EN BINAIRES SUR UN FICHIER 14.2 109 Fichiers en format binaire Il y a cependant la possibilité d'écrire les données dans un chier de façon brute et compacte, en format binaire. L'avantage est la rapidité, et l'économie de place mémoire. C'est aussi utile (mais non nécéssaire) si l'on veut accéder en lecture ou écriture à un endroit quelconque du chier. 14.2.0.1 Exemple Le programme suivant montre comment écrire et lire en format binaire, et comment se positionner dans un chier. #include <stdlib> // -----fichiers d'entetes ---#include <iostream> using namespace std; #include <fstream> #include <iomanip> main() { double x=3.14; int i=17; int nx=sizeof(double); //nombre d'octets occupés par une objet de classe double int ni=sizeof(int); //nombre d'octets occupés par une objet de classe int cout << "nx= " <<nx << " ni= " <<ni <<endl; //=============================== ofstream sortie( "donnees.dat "); //ouverture d un fichier en ecriture -sortie.write(&x, nx); //--- ecriture de x --sortie.write(&i, ni); //--- ecriture de i --sortie.close(); //- fermeture du fichier -//=============================== ifstream entree( "donnees.dat "); //-- ouverture d un fichier en lecture -entree.read(&x, nx); //--- lecture de x --entree.read(&i, ni); //--- lecture de i --cout << " x= " <<x << " i= " <<i<<endl; entree.seekg(nx); // on se positionne après nx octets en partant du début, soit après la valeur de x entree.read(&i, ni); //--- lecture de i --cout << " i= " <<i <<endl; CHAPITRE 14. ECRIRE ET LIRE DES DONNÉES EN BINAIRES SUR UN FICHIER 110 int pos=entree.tellg(); // donne la position du curseur dans le fichier (en octets) cout << "la position du curseur est maintenant: " <<pos << endl; } 14.2.0.2 Remarques 1. La fonction sizeof(double) permet de connaitre la taille en octet occupée par le classe double en mémoire. Voici le taille des principales classes de base : char short int int long float double Complex Classe Taille (octets) 1 2 4 4 4 1. Pour se positionner en lecture, il faut utiliser la fonction en écriture, il faut utiliser la fonction seekp. 8 16 seekg. Pour se positionner Le premier argument de ces fonctions indique le déplacement relatif du pointeur (en octets). Le deuxième argument (facultatif ) indique l'origine du déplacement. Ce peut-être : le début du chier : ios::beg la position actuelle du curseur : ios::end entree.seekg(nx,ios::beg); la n du chier exemple : ios::cur Chapitre 15 Quelques commandes utiles 15.0.1 La commande system La commande system() permet de lancer d'autres commandes ou programmes sur l'ordinateur. Par exemple pour lancer la commande ls qui ache la liste des chiers : #include <iostream> // pour cout using namespace std; #include <stdlib.h> // pour system //----Fonction principale ---------------int main() { system("ls"); // execute la commande ls. (affiche les fichiers) return 0; } 111 Chapitre 16 Intégrer des équations diérentielles ordinaires (EDO) avec la librairie ODEINT On propose d'utiliser la librairie Introduction : Une équation diérentielle ordinaire (E.D.O.) est une équation de la forme : dX = F (X, t) dt t ∈ R, et F : Rn × R → Rn est une fonction connue. On connait X (0) et F l'on cherche X (t) pour d'autres valeurs de t ∈ R. Le théorème de Cauchy Lipchitz garantit l'existence et l'unicité de X (t) mais on où X (t) ∈ Rn odeint de boost. Voir aussi la documentation ODEINT. est un vecteur qui dépend du temps a rarement une expression explicite de la solution. L'ordinateur est utile pour trouver des valeurs approchées de X (t). Exemple Pour intégrer avec la methode runge_kutta_dopri5 with standard error bounds 3 10-6 for the steps, le champ de vecteur dans X (t) = (x0 (t) , x1 (t) , x2 (t)) ∈ R du système de Lorenz déni par : dx0 = σ · (x1 − x0 ) dt dx1 = Rx0 − x1 − x0 x2 dt dx2 = −bx2 + x0 x1 dt σ = 10, R = 28, b = 8./3., #include <iostream> #include <boost/array.hpp> #include <boost/numeric/odeint.hpp> avec les paramètres : 112 on écrit : CHAPITRE 16. INTÉGRER DES ÉQUATIONS DIFFÉRENTIELLES ORDINAIRES (EDO) AVEC LA L using namespace std; using namespace boost::numeric::odeint; const double sigma = 10.0; const double R = 28.0; const double b = 8.0 / 3.0; typedef boost::array< double , 3 > state_type; // le chiffre 3 car il y a 3 variables ///- definition de la fonction à intégrer void lorenz( const state_type &x , state_type &dxdt , double t ) { dxdt[0] = sigma * ( x[1] - x[0] ); dxdt[1] = R * x[0] - x[1] - x[0] * x[2]; dxdt[2] = -b * x[2] + x[0] * x[1]; } /// int main(int argc, char **argv) { state_type x = { 10.0 , 1.0 , 1.0 }; // conditions initiales double t1 =0 ,t2=25; // temps initial et final double dt =0.1; //pas de temps for(double t=t1; t<=t2; t=t+dt) { integrate( lorenz , x , t, t+dt, dt); // intégre entre t et t+dt cout << t << '\t' << x[0] << '\t' << x[1] << '\t' << x[2] << endl; } } Remarque 16.0.1. Pour plus d'options ou autres méthodes d'intégration voir la documentation. Exercice 16.0.2. Modier le code ci-dessus pour dessiner la trajectoire dans le plan avec un point tous les pas de temps dt = 0.02 et t = 0 → 100 x1 , x2 et obtenir l'image suivante : CHAPITRE 16. INTÉGRER DES ÉQUATIONS DIFFÉRENTIELLES ORDINAIRES (EDO) AVEC LA L Solution : #i n c l u d e <i o s t r e a m > #i n c l u d e <b o o s t / a r r a y . hpp> #i n c l u d e <b o o s t / n u m e r i c / o d e i n t . hpp> #i n c l u d e <T A p p l i c a t i o n . h> #i n c l u d e <TCanvas . h> #i n c l u d e <TMarker . h> #i n c l u d e <TH1F . h> CHAPITRE 16. INTÉGRER DES ÉQUATIONS DIFFÉRENTIELLES ORDINAIRES (EDO) AVEC LA L using namespace std ; using namespace boost : : numeric : : o d e i n t ; const double const double R = const double typedef sigma = 10.0; 28.0; b = 8.0 / 3.0; boost : : array< double /// − definition void lorenz ( de la const , 3 > state_type ; fonction à // le chiffre 3 car double t ) il y a 3 v intégrer s t a t e _ t y p e &x , s t a t e _ t y p e &d x d t , { dxdt [ 0 ] = sigma dxdt [ 1 ] = R dxdt [ 2 ] = * * ( x[0] − x[0] ); − x[0] * x[0] * x [1]; x[1] − x[1] −b * x[2] + argc , char ** a r g v ) x[2]; } /// int main ( i n t { TApplication TCanvas double TH1F *c theApp ( " App " , &a r g c , = new TCanvas ( argv ) ; "c " ," f e n e t r e " ,500 ,500); // on precise * h=c−>DrawFrame ( xmin , ymin , xmax , ymax ) ; // coordonnees de h−>S e t Y T i t l e ( " x2 " ) ; c p t =0; state_type double taille xmin ( − 3 0 ) , ymin ( 0 ) , xmax ( 3 0 ) , ymax ( 5 0 ) ; h−>S e t X T i t l e ( " x1 " ) ; int la t1 x = { =0 10.0 , t 2 =100; , // 1.0 , temps 1.0 }; // initial conditions et final initiales la fenetre ( CHAPITRE 16. INTÉGRER DES ÉQUATIONS DIFFÉRENTIELLES ORDINAIRES (EDO) AVEC LA L double dt =0.002; f o r ( double t=t 1 ; // p a s t<=t 2 ; de temps t=t+d t ) { integrate ( // lorenz c o u t << t << , x , t , t+dt , ' \ t ' << x [ 0 ] << dt ) ; TMarker * p=new TMarker TMarker ( x [ 1 ] , x [ 2 ] , 6 ) ; p2 ( x [ 1 ] , x [ 2 ] , 8 ) ; p2 . S e t M a r k e r C o l o r ( kRed ) ; p2 . S e t M a r k e r S i z e ( 2 ) ; p2 . Draw ( ) ; i f ( c p t %10==0) c−>Update ( ) ; } c−>Update ( ) ; theApp . Run ( ) ; return } 0; intégre ' \ t ' << x [ 1 ] << c p t ++; p−>Draw ( ) ; // entre t et t+d t ' \ t ' << x [ 2 ] << e n d l Chapitre 17 Lecture et écriture d'un chier son de format WAV 17.0.0.1 Un Introduction signal audio est une fonction de pression sonores en fonction du Un t ∈ R → s (t) ∈ R temps t. qui représente les variations chier audio au format WAV représente le signal audio pour des valeurs dis- crètes du temps : tj = j.δt, j = 1, 2, . . . −1 avec un pas de temps δt appelée fréquence d'échantillonage. Les valeurs standard −1 −1 sont : δt = 8000Hz, δt = 44100Hz, δt−1 = 48000Hz, etc. Chaque valeur s (tj ) est codée sur 8 bits ou 16 bits. Le nombre de caneaux 1 ou 2 indique si le chier peut contient un (mode mono) ou deux signaux (mode stéréo). Le chier WAV contient les valeurs s (tj ) à la suite, mais au début du chier il y a les paramètres du codage. Regarder le code source pour plus d'informations. 17.0.0.2 Travail préalable pour le projet C++ 1. Fichiers à télécharger et sauver dans son répertoire : bib_fred.h et bib_fred.cc. 2. Dans codeblocks, il faut ajouter ces chiers au projet en faisant dans le menu : Projet/add_les ... 3. Il faut aussi rajouter -larmadillo dans les options de compilation (pour code- blocks c'est dans Setting/Compiler/LinkerSettings/OtherLinkerOptions) 17.0.1 Programme pour écrire un chier WAV : Pour tester, télécharger ce chier Voyelles_Par_Malik.wav et sauvegarder le dans le répertoire de votre projet. Le programme suivant permet 117 CHAPITRE 17. LECTURE ET ÉCRITURE D'UN FICHIER SON DE FORMAT WAV118 de lire et acher les paramètres du codage audio de ce chier sonore s (tj ) ∈ R (nombre de canaux, taux d'echantillonage etc..), de mettre les amplitudes s (tj ) dans un vecteur de nombres de dessiner ce vecteur avec la librairie ROOT. De plus il lance le logiciel vlc qui permet d'entendre le chier (il envoie le chier sur la carte son). #include <iostream> using namespace std; #include <TCanvas.h> #include <TMarker.h> #include <TApplication.h> #include "bib_fred.h" ///-------------------------------------------int main(int argc, char **argv) { TApplication theApp("App", &argc, argv); //---- lecture audio du fichier wav avec le logiciel vlc system("vlc Voyelles_Par_Malik.wav"); //--- lecture de tout le fichier vec signal; double f=Lecture_fichier_wav("Voyelles_Par_Malik.wav", signal); // transfert les données du fichier dans le vecteur double dt=1./f; // pas en temps //Dessin(signal, "signal"); Dessin(signal,"s","s",0,signal.size()*dt,"signal",0,"L",-1e4,1e4,kBlue,0); //--- lecture d'une partie vec signal2; double t1=4, t2=4.03; // intervalle de temps en secondes f=Lecture_fichier_wav("Voyelles_Par_Malik.wav", signal2, 1,t1,t2); dt=1./f; //Dessin(signal2, "s2"); Dessin(signal2,"s","s",t1,t1+signal2.size()*dt,"signal2",0,"L",-1e4,1e4,kRed,0); //--------theApp.Run(); return 0; } Sortie du programme : CHAPITRE 17. LECTURE ET ÉCRITURE D'UN FICHIER SON DE FORMAT WAV119 Remarquer que vous pouvez zoomer le dessin etc... Sortie du programme sur le terminal : =====Crée tableau à partir du fichier: Voyelles_Par_Malik.wav ChunkID=RIFF format=WAVE Nombre de canaux=1 Frequence d'echantillonage=48000 Hz Bits par echantillon=16 Nombre de données =834402 Nombre d'octets par echantillon=2 Pas de temps dt =2.08333e-05 s. Durée T =8.69169 s. Lecture fichier finie. -------------------- CHAPITRE 17. LECTURE ET ÉCRITURE D'UN FICHIER SON DE FORMAT WAV120 17.0.2 Programme pour lire un chier WAV : Le programme suivant crée un signal échantilloné dans un vecteur de double (de type vec) et ensuite écrit ce vecteur dans un chier au format WAV. De plus il lance le logiciel vlc qui permet de jouer le chier (envoie sur la carte son). #include <iostream> using namespace std; #include <TCanvas.h> #include <TMarker.h> #include <TApplication.h> #include "bib_fred.h" ///-------------------------------------------int main(int argc, char **argv) { TApplication theApp("App", &argc, argv); double dt=1./48000; // pas de temps en s. double T=5;// durée en s. int N=T/dt; vec signal(N); double f0=440; // frequence de la note que l'on veut en Hz for(int i=0; i<N; i++) { double t=i*dt; double f= f0 *(1+0.01*cos(2.*M_PI*t/1.)); // modulation de frequences double s= sin( 2*M_PI * f * t ); // note pure de frequence f s = s + 0.5 * sin( 2*M_PI * 2*f * t ); // rajoute harmonique 2 s = s + 0.3 * sin( 2*M_PI * 3*f * t ); // rajoute harmonique 3 signal(i) = s; } Dessin(signal,"s","s",0,signal.size()*dt,"signal",0,"L",-4,4,kBlue,0); // ecrit le vecteur signal dans le fichier son.wav. On indique dt Ecriture_fichier_wav(signal, dt,"son.wav"); //---- lecture audio du fichier wav avec le logiciel vlc system("vlc son.wav"); //--------theApp.Run(); return 0; } CHAPITRE 17. LECTURE ET ÉCRITURE D'UN FICHIER SON DE FORMAT WAV121 Sortie du programme : ce chier son.wav, l'image du signal et la sortie sur terminal : =====Crée un fichier wav à partir du tableau ====fichier:son.wav Nombre de canaux=1 Frequence d'echantillonage=48000 Hz Bits par echantillon=16 Nombre d'octets par echantillon=2 Nombre d' echantillons =240000 Pas de temps dt =2.08333e-05 s. Durée T =5 s. Chapitre 18 Gestion de la carte son (audio) avec Jack On va expliquer l'utilisation de base de jack. reference gcc -o myprog `pkg-cong --cags --libs jack` myprog.c Tutorial sur le lancement de Jack Tutorial 122 Chapitre 19 Gestion de la carte son (audio) avec la librairie sndio On utilisera la librairie sndio développée pour OpenBSD mais utilisable aussi avec linux. Cette librairie permet de gérer le son audio et aussi les évènements midi en temps réel (entrée/sortie). Remarques Pour avoir la liste des cartes sons faire cela donne par exemple cat /proc/asound/cards ou aplay -l, 0 [HDMI ] : HDA-Intel - HDA Intel HDMI HDA Intel HDMI at 0xf7a1c000 irq 38 1 [PCH ] : HDA-Intel - HDA Intel PCH HDA Intel PCH at 0xf7a18000 irq 37 19.1 Installation (à faire la première fois) Si vous êtes administrateur, suivre cette documentation. Puis tester : test.wav sur la carte aucat -i ~/test.wav. Pour envoyer (écouter) le chier par exemple il faut écrire : 0 (carte son par default) Si il n'y a pas de son et le message snd0: rsnd/0: failed to open audio device c'est que la carte son 0 n'existe pas. Vérier la liste des cartes sons de votre ordi avec la commande aplay -l et : test.wav aucat -f rsnd/1 -i ~/test.wav. Pour envoyer (écouter) le chier écrire : sur la carte 1 par exemple il faut Si la carte 1 est la carte son par defaut (au lieu de la carte 0) il faut éditer /etc/default/sndiod et ajouter l'option DAEMON_OPTS="-f rsnd/1" Sinon si vous n'êtes pas administrateur et que cette librairie n'est pas déjà installée, il vous faut l'installer sur votre compte. Pour cela : 123 CHAPITRE 19. GESTION DE LA CARTE SON (AUDIO) AVEC LA LIBRAIRIE SNDIO124 Télécharger le source et décompresser l'archive. Dans le répertoire de sndio ainsi créé, taper : configure --prefix=$HOME make make install Remarque : cela a pour eet de compiler la bibliothèque et d'installer les librairies dans HOME/lib et HOME/include. Dans les options de compilation, il faudra rajouter : -I/$HOME/include ( : dans codeblocks, c'est à rajouter dans Settings/Compi- ler/CompilerSetting/OtherOptions), -L/$HOME/lib et -lsndio ( : dans codeblocks, c'est à rajouter dans Settings/- Compiler/LinkerSettings/OtherOptions) Dans le chier .bashrc il faut rajouter export LD_LIBRARY_PATH=$HOME/lib . .bashrc et executer dans une fenêtre de commande : 19.1.0.1 Travail préalable pour le projet C++ 1. Fichiers à télécharger et sauver dans son répertoire : bib_fred.h et bib_fred.cc, audio.h et audio.cc. 2. Dans codeblocks, il faut ajouter ces chiers au projet en faisant dans le menu : Projet/add_les ... 3. Il faut aussi rajouter -larmadillo dans les options de compilation (pour code- blocks c'est dans Setting/Compiler/LinkerSettings/OtherLinkerOptions) 4. Démarrage du serveur sndiod, si vous êtes administrateur, écrire dans un terminal : sudo sndiod -dd -z480 -f rsnd/0 et le laisser ouvert, sinon, si sndio a été installé sur votre compte, écrire dans un terminal : -z480 -f rsnd/0 19.2 sndiod -dd et le laisser ouvert. Utilisation du micro (entrée) et du haut-parleur (sortie) 19.2.1 Le micro Le micro échantillonne le son avec un pas de temps Dt = 1/48000sec. et envoie les données à la carte son qui les convertit sous forme numérique. Cet exemple lit les données du micro depuis la carte son par bloc de et les dessine au fur et à mesure. N = 1000 donnée, soit de durée T = N.Dt = 0.02sec., CHAPITRE 19. GESTION DE LA CARTE SON (AUDIO) AVEC LA LIBRAIRIE SNDIO125 #include "bib_fred.h" #include "audio.h" #include <iostream> using namespace std; #include <TROOT.h> #include <TApplication.h> //===Main================================================ int main(int argc, char **argv) { TApplication theApp("App", &argc, argv); Audio s; s.Initialise_audio(1); // initialise la carte son en entree (1) et af- che ses informations int N=1000; // on donne la taille que l'on veut (nombre d'echantillons) vec V(N); while(1) // boucle infinie { s.Lit_donnees_audio(V); // remplit V depuis le micro CHAPITRE 19. GESTION DE LA CARTE SON (AUDIO) AVEC LA LIBRAIRIE SNDIO126 Dessin(V,"V"); // Dessin(V,"t","s",0,N*s.Dt,"V",0,"L",-1e4,1e4,kRed,0); // dessin normalisé en rouge } } 19.2.1.1 /// ----Donne la main a l'utilisateur --theApp.Run(); return 0; Exemple : détection de note de musique en temps réel Une note de musique est un signal périodique de période dans l'intervalle audible 30Hz ≤ f ≤ 3000Hz . T, et fréquence f = 1/T Le programme suivant utilise un al- gorithme de détection de période dans un signal, et ache le signal sur une (ou plusieurs périodes) ainsi que le nom de la note jouée. (Pour information, la fonction utilisée Signal::Detecte_Periode() se trouve dans le chier signal.cc.) Résultat : Periode : T=0.0034036s. <-> Frequence f=1/T=293.806 Hz Note =Re, Ecart = 0.00833494 demitons, Octave=0. CHAPITRE 19. GESTION DE LA CARTE SON (AUDIO) AVEC LA LIBRAIRIE SNDIO127 #i n c l u d e " b i b _ f r e d . h" #i n c l u d e " a u d i o . h" #i n c l u d e <i o s t r e a m > using namespace std ; #i n c l u d e <TROOT. h> #i n c l u d e <T A p p l i c a t i o n . h> string Notes [ 1 2 ] = {"Do " , " Do#" , "Re " , " Re#" , "Mi " , "Fa " , "Fa#" , " Sol " , " S o l #" , //===Main================================================ int main ( i n t argc , char ** a r g v ) { TApplication Audio theApp ( " App " , &a r g c , argv ) ; s; s . Initialise_audio (1); // initialise la carte son en entree (1) et affich CHAPITRE 19. GESTION DE LA CARTE SON (AUDIO) AVEC LA LIBRAIRIE SNDIO128 int N= 2 0 0 0 ; // on donne la taille que l ' on veut ( determine la fenetre tem v e c V(N ) ; // p a r a m e t r e s pour la int 1 ,2: nbre NP=2; double // f_A = 4 4 0 ; detection // de de la periodes periode à afficher diapason s . s e u i l _ n o r m e _ p a r _ I=1e 4 ; s . seuil_C =0.1; s . verbose =0; //1 affiche messages erreur //−−−−−−−−−−−−−−−−−−−−− while (1) // boucle infinie { s . L i t _ d o n n e e s _ a u d i o (V ) ; double int t0 =0; // date opt_dessin_C =0; // de // remplit V debut 0 ou depuis le micro d ' analyse 1 d o u b l e T=s . D e t e c t e _ P e r i o d e (V, t0 , opt_dessin_C ) ; // −> P e r i o d e T ou 0 i f (T>0) { double f =1/T ; c o u t <<" P e r i o d e //−−− conversion T="<<T<<"s . <−> en note de nu_i = Frequence + 69; f l o o r ( nu + 0 . 5 ) ; c o u t <<" Note ="<<N o t e s [ nu_i%12]<<" //−−−− vec affiche NP p e r i o d e s E x t r a i t = V. rows ( uword i f =1/T="<<f <<" Hz"<< e musique nu = 1 2 * l o g ( f /f_A ) / l o g ( 2 . ) double int : E c a r t = "<<nu−nu_i<<" d e m i t o n s extraites t 0 / s . Dt , ( t 0+NP*T) / s . Dt ) ; // on extrait NP = E x t r a i t . index_max ( ) ; E x t r a i t =s h i f t ( Extrait , −i ); // place le max de l ' extraitau debu D e s s i n ( E x t r a i t , " t " , " s " , 0 ,NP*T, " E x t r a i t " , 0 , " L" , − 1 1 1 1 , − 1 1 1 1 , kRed , 0 ) } } /// −−−−Donne la theApp . Run ( ) ; main a l ' utilisateur −−− CHAPITRE 19. GESTION DE LA CARTE SON (AUDIO) AVEC LA LIBRAIRIE SNDIO129 return 0; } 19.2.2 Le haut-parleur Voici un exemple qui lit les données sur le micro et les envoie sur le haut-parleur après un délai de 200ms (la latence). Mettre un casque. #i n c l u d e " b i b _ f r e d . h" #i n c l u d e " a u d i o . h" #i n c l u d e <i o s t r e a m > using namespace std ; #i n c l u d e <TROOT. h> #i n c l u d e <T A p p l i c a t i o n . h> int main ( i n t argc , char ** a r g v ) { TApplication Audio theApp ( " App " , &a r g c , argv ) ; s; s . Initialise_audio (3); // initialise la carte son en entree et sortie (3 vec V; while (1) // boucle infinie { s . L i t _ d o n n e e s _ a u d i o (V ) ; // micro −> V D e s s i n (V, " t " , " s " , 0 ,V . s i z e ( ) * s . Dt , "V" , 0 , " L" , − 1 e4 , 1 e4 , kRed , 0 ) ; de V s . E c r i t _ d o n n e e s _ a u d i o (V ) ; // V −> } /// −−−−Donne la theApp . Run ( ) ; return } 0; main a l ' utilisateur −−− Haut Parleur // dessin Chapitre 20 Plusieurs programmes en parallèle qui communiquent, avec thread Il peut être utile dans un projet de répartir le travail entre plusieurs programmes qui fonctionnent ensemble et communiquent. Un ordinateur est capable de gérer plusieurs programmes qui fonctionnent. Si l'ordinateur ne possède qu'un processeur, il va faire avancer chaque programme tour à tour en alternant sur des intervalles de temps très court. Cela donne l'impression que plusieurs programmes fonctionnent en parallèle. On va s'interesser à la communication entre ces programmes. On peut imaginer cela comme des travailleurs qui fabriquent une maison. Chacun travaille sur sa partie mais il y a des moments où ils doivent échanger des informations, parfois un travailleur doit attendre qu'un autre ait ni son travail. Alors c'est le chef de projet (le processeur) qui se charge de superviser tout cela, de mettre en attente certains et réactiver d'autres. Il y a donc trois concepts importants dont nous allons voir la mise en oeuvre : 1. Les threads : au niveau de la terminologie, chaque programme s'appelle un thread ou process ou context. On va tout d'abord apprendre à démarrer des threads. 2. Les mutex : lorsque ces programmes doivent communiquer c'est à dire échanger des données (et c'est souvent le cas) il y a des précautions à prendre an que tout ce passe bien : par exemple il faut éviter qu'une variable soit manipulée par deux threads en même temps. Ce sera le rôle des mutex qui sont simplement des indicateurs booleen (vrai/faux) que l'on interprète comme (occupé/libre). 3. Les variables conditionnelles, cond_var : c'est comme une sonnette. Cela permet au processeur de mettre en attente certains thread et d'en réactiver d'autres sous certaines conditions. références : article sur C++11 threads, locks and condition variables. 130 CHAPITRE 20. PLUSIEURS PROGRAMMES EN PARALLÈLE QUI COMMUNIQUENT, AVEC 20.1 THRE Lancement de threads ou processus #include <iostream> // std::cout using namespace std; #include <thread> //---- Une petite fonction qui affiche des a---------------void f(int n) { for(int i=0;i<n;i++) cout<<"a"<<flush; cout<<endl; } //----Fonction principale ---------------int main() { // ici la fonction main est lancée int n=10; thread t(f,n); // lance la fonction f(n) dans un thread cout << "Les fonctions main et f sont maintenant en concurrence.\n"; for(int i=0;i<n;i++) // affiche des b cout<<"b"<<flush; cout<<endl; t.join(); // attend ici que la fonction f soit finie cout << "f est finie.\n"; return 0; } 20.1.0.1 Commande de compilation : Si on utilise codeblocks, il faut rajouter -pthread -std=c++11 dans Settings/Com- piler/Compiler_Settings/Other_Options et dans Settings/Compiler/Linker_Settings/Other_linker Pour une compilation en ligne de commande : g++ main.cpp -o main.out -pthread -std=c++11 Résultat : Les fonctions main et f sont maintenant en concurrence. bbbbbaaaaaaaaaabbbbb f est finie. CHAPITRE 20. PLUSIEURS PROGRAMMES EN PARALLÈLE QUI COMMUNIQUENT, AVEC 20.1.0.2 Commentaires 1. Les fonctions f et main fonctionnent en parallèle et leur achages 'a' ou 'b' sur l'écran se mélangent de façon incontrollée. Un deuxième lancement du programme donnerait éventuellement autre chose. 2. On peut passer (ou pas) des variables à la fonction lancée en parallèle, ici on passe la variable n. f en écrivant par exemple, void f(int * pn) {..} pour la déclaration et thread t(f,&n); pour le lancement.. Cela permet de transmettre un résultat de la fonction f vers la fonction main. (a) On peut passer un pointeur (adresse d'une variable) à la fonction (b) Mais pour passer une variable par référence, il faut écrire par exemple, void f(int & n) {..} pour la déclaration et thread t(f,ref(n)); pour le lancement. 3. Il peut être utile de créer une certaine attente dans un programme. Utiliser Section 13.2. 4. Si vous oubliez t.join() à la n, il y aura une erreur à l'execution du programme. 5. Documentation complète sur thread. 20.1.0.3 Lancer un thread d'une fonction membre de classe #include <iostream> using namespace std; #include <thread> class A { public: void f(int n ) { cout << n << endl; } }; int main() { A a; thread t1(&A::f, &a, 100); t1.join(); return 0; } THRE CHAPITRE 20. PLUSIEURS PROGRAMMES EN PARALLÈLE QUI COMMUNIQUENT, AVEC Commentaires Si on lance le thread depuis une autre fonction membre, il faut bien sûr remplacer le pointeur 20.1.0.4 &a par this. Lancer une liste de thread (tableau) #include <iostream> // std::cout using namespace std; #include <thread> //---- Une petite fonction qui affiche n fois n ---------------void f(int n) { for(int i=0; i<n; i++) cout<<n<<flush; } //----Fonction principale ---------------int main() { int n=7; thread L_t[n]; // tableau de threads for (int i=0; i<n; i++) L_t[i] = thread(f,i); // lance de thread i qui affiche i fois i for(int i=0; i<n; i++) L_t[i].join(); // attend ici que le thread i soit fini return 0; } Résultat : 20.1.0.5 315524563656666254443 Lancer une liste de thread (vector) Même chose que précédement mais avec un vector. #include <iostream> // std::cout using namespace std; #include <thread> //---- Une petite fonction qui affiche n fois n ---------------void f(int n) { for(int i=0; i<n; i++) cout<<n<<flush; } //----Fonction principale ---------------int main() THRE CHAPITRE 20. PLUSIEURS PROGRAMMES EN PARALLÈLE QUI COMMUNIQUENT, AVEC { int n=7; vector<thread> L_t; // tableau de threads for (int i=0; i<n; i++) L_t.push_back(thread(f,i)); // lance de thread i qui affiche i fois i for(int i=0; i<n; i++) L_t[i].join(); // attend ici que le thread i soit fini return 0; } Résultat : 20.2 213332444456666665555 Mutex pour annoncer occupé/libre 20.2.0.1 Introduction Un mutex est simplement une variable booléenne dont on pense que la valeur est occupé/libre. Une analogie possible est qu'un mutex est comme le panneau devant une salle de bain (sans serrure) qui annonce une valeur occupé/libre. Par exemple, on peut utiliser un mutex avant de modier une variable commune à plusieurs threads. On déclare un mutex par mutex mtx; Ensuite il y a deux commandes de base : 1. La commande mtx.lock(); qui signie : si mtx est libre on le met à occupé et on continue, sinon, si il est occupé alors on attend jusqu'à ce qu'il soit libre. 2. La commande mtx.unlock(); qui signie : on met 20.2.0.2 mtx à libre Remarques mtx ordisi mtx est libre on le met à occupé et on continue, sinon on attend ? Quel intéret d'utiliser un mutex mtx plutot qu'une variable boolenne naire pour eectuer la suite d'instructions réponse : en utilisant une variable ordinaire, il y a le risque qu'un autre processus viennent modier la valeur de la variable pendant cette suite d'instructions, ce que l'on ne souhaite absolument pas.En utilisant un mutex il est garantit que cette suite d'instruction est faite tout à la suite par l'ordinateur, sans être interrompu (on dit de façon atomique). THRE CHAPITRE 20. PLUSIEURS PROGRAMMES EN PARALLÈLE QUI COMMUNIQUENT, AVEC 20.2.0.3 Exemple #include <iostream> // std::cout using namespace std; #include <thread> #include <mutex> mutex mtx; //-------------------void f(int n,char c) { mtx.lock(); // si mtx est libre on le bloque, sinon on attend for(int i=0;i<n;i++) cout<<c<<flush; cout<<endl; mtx.unlock(); // débloque mtx } //-------------------int main() { int n=10; thread t1(f,n,'a'); thread t2(f,n,'b'); t1.join(); t2.join(); return 0; } 20.2.0.4 Résultat : aaaaaaaaaa bbbbbbbbbb Dans cet exemple le thread t1 est arrivé le premier et a bloqué le mutex. Le thread t2 a donc attendu qu'il soit débloqué pour le bloquer à son tour. THRE CHAPITRE 20. PLUSIEURS PROGRAMMES EN PARALLÈLE QUI COMMUNIQUENT, AVEC 20.3 Communications : variables conditionnelles pour réveiller un programme en attente Introduction Dans la communication entre programmes (process), il peut arriver qu'un programme 2 attende le resultat d'un autre(s) programme(s) 1. Voici une solution naive et maladroite pour programmer cette solution : Il y a une variable qui est a=0 au départ et que le programme met à la valeur a=1 dès qu'il a obtenu sont résultat. (Cette variable est protégée par un mutex). Le programme 2 eectue une boucle innie dans laquelle il lit la variable a et si a==1 alors il sort de la boucle et récupère le résultat. Cette solution est maladroite car le programme 2 consomme du CPU (processeur de la machine) pour rien. Une meilleure solution est que le programme 2 soit mis en attente (en veille, il ne consomme pas de CPU) et réveillé à l'arrivée du résultat. Pour cela on utilise une variable condi- tionelle. Instructions de base : Une variable conditionnelle se déclare par condition_variable cv; Il y a la commande cv.wait(lck); qui met le thread en attente (endormi), et cv.notify_one(); qui reveille un parmi les autres thread qui se sont mis en attente, (ou cv.notify_all(); qui reveille tous les autres thread qui se sont mis en attente.) 20.3.0.1 Exemple de base où un thread endormi est reveillé par un autre. #include <iostream> using namespace std; #include <thread> #include <chrono> #include <mutex> #include <condition_variable> mutex mtx; condition_variable cv; //-------------------void f() { unique_lock<mutex> lck(mtx); // bloque le mutex mtx. Il sera debloque automatiquement par wait(lck), ou a la destruction de lck. THRE CHAPITRE 20. PLUSIEURS PROGRAMMES EN PARALLÈLE QUI COMMUNIQUENT, AVEC cv.wait(lck); // met le thread en attente (endormi). Il sera réveillé par un signal extérieur. cout<<"FIN"<<endl; } //-------------------int main() { thread t(f); cout<<"On attend 2sec."<<endl; this_thread::sleep_for(chrono::seconds{2}); cout<<"voilà."<<endl; // ... reveille cv cv.notify_one(); // reveille le thread endormi //......... t.join(); return 0; } Résultat : On attend 2sec. voilà. FIN 20.3.0.2 Remarques L'instruction wait() sauvegarde tous les registres du processus (tout son environnement mémoire) et notify_one() les restore. dans cet exemple l'utilisation du mutex ne sert à rien, mais le langage C++ oblige à l'utiliser dans l'instruction wait(lck). Voir ci-dessous l'exemple 20.3.4 où l'utilisation du mutex est utile et obligatoire. Pourquoi faut il utiliser le mutex mtx ? L'utilisation d'un mutex est obligatoire, car les thread se partagent une variable cv qui spécie l'état sommeil/reveillé et donner l'instruction de réveil. La fonction wait() utilise cette variable en faisant en fait de façon séquentielle : lecture de cv, si signal je dois me réveiller. Exemple 20.3.1. où un thread endormi attend des évènements provenant de dierents thread extérieurs. THRE CHAPITRE 20. PLUSIEURS PROGRAMMES EN PARALLÈLE QUI COMMUNIQUENT, AVEC #include <iostream> using namespace std; #include <thread> #include <chrono> #include <mutex> #include <condition_variable> mutex mtx; condition_variable cv; // void f1() { while(1) { this_thread::sleep_for(chrono::milliseconds {1000}); cout<<"1"<<flush; cv.notify_one(); // reveille le thread main() endormi } } // void f2() { while(1) { this_thread::sleep_for(chrono::milliseconds {1502}); cout<<"2"<<flush; cv.notify_one(); // reveille les thread main() endormi } } // int main() { thread t1(f1); thread t2(f2); unique_lock<mutex> lck(mtx); // bloque le mutex mtx. Il sera debloque automatiquement par wait(lck), ou a la destruction de lck. while(1) { cv.wait(lck); // met le thread main() en sommeil cout<<","<<flush; } //......... t1.join(); THRE CHAPITRE 20. PLUSIEURS PROGRAMMES EN PARALLÈLE QUI COMMUNIQUENT, AVEC t2.join(); return 0; } Résultat : 1,2,1,1,2,1,2,1,1,2,1,2,1,1,2,1,2,1,1,2,... Remarque 20.3.2. Pour expliquer le résultat, il faut remarquer que la fonction f1() ache 1 aux dates suivantes (en ms) : 1000, 2000, 3000, 4000, 5000,.... Après chaque achage, la fonction f1() reveille la fonction main() qui ache ,. De même, la fonction f2() ache 2 aux dates : 1502, 3004, 4506, 6008, ... et de même à chaque achage elle reveille la fonction main() qui ache ,. La suite chronologique de ces dates est : 1000,1502,2000,3000,3004,4000,4506,5000,6000,6008, ... ce qui explique le résultat 1,2,1,1,2,1,2,1,1,2,.. Plusieurs threads peuvent être mis en attente par cv.wait(lck); et la commande cv.notify_one(); va en reveillé un seul (on ne sait pas lequel). Par contre commande cv.notify_all(); reveillerait tous les threads mis en attente. la Exemple 20.3.3. où deux thread endormi sont reveillés par une notication venant d'un 3eme thread. #include <iostream> using namespace std; #include <thread> #include <chrono> #include <mutex> #include <condition_variable> mutex mtx; condition_variable cv; // void f1() { unique_lock<mutex> lck(mtx); // bloque le mutex automatiquement par wait(lck), ou a la destruction cout<<"a"<<endl; cv.wait(lck); cout<<"b"<<endl; } // void f2() { unique_lock<mutex> lck(mtx); // bloque le mutex automatiquement par wait(lck), ou a la destruction cout<<"A"<<endl; mtx. Il sera debloque de lck. mtx. Il sera debloque de lck. THRE CHAPITRE 20. PLUSIEURS PROGRAMMES EN PARALLÈLE QUI COMMUNIQUENT, AVEC THRE cv.wait(lck); cout<<"B"<<endl; } // int main() { thread t1(f1); thread t2(f2); for(int t=3; t>=0; t--) // compte à rebour { this_thread::sleep_for(chrono::seconds {1}); cout<<t<<","<<flush; // \r permet de re-ecrire au debut de la ligne } cout<<"partez!"<<endl; cv.notify_all(); //......... t1.join(); t2.join(); return 0; } Exemple 20.3.4. Un thread remplit une liste. Le deuxieme thread vide la liste. #i n c l u d e <i o s t r e a m > using namespace std ; #i n c l u d e <t h r e a d > #i n c l u d e <c h r o n o > #i n c l u d e <mutex> #i n c l u d e <c o n d i t i o n _ v a r i a b l e > #i n c l u d e < l i s t > mutex mtx ; condition_variable cv ; l i s t <i n t > L ; int N= 1 0 ; //−−−−−−−−−−−−−−−−−−−− void f () { while (1) // boucle infinie { { u n i q u e _ l o c k <mutex> l c k ( mtx ) ; i f ( L . s i z e () < N) // cv . w a i t ( l c k ) ; si // liste // bloque non pleine debloque le mutex le lck mutex mtx . et met le Il sera thread debl en at CHAPITRE 20. PLUSIEURS PROGRAMMES EN PARALLÈLE QUI COMMUNIQUENT, AVEC } // ici lck est detruit donc mtx est debloque mtx . l o c k ( ) ; c o u t <<" L i s t e pleine . On vide la liste par le d e b u t :"<< f l u s h ; w h i l e (L . s i z e () >0) { c o u t <<L . f r o n t ()<<","<< f l u s h ; this_thread : : s l e e p _ f o r ( chrono : : m i l l i s e c o n d s L . pop_front ( ) ; // enleve le premier reveille le thread {100}); element } c o u t <<e n d l ; mtx . u n l o c k ( ) ; cv . n o t i f y _ o n e ( ) ; // endormi } } //−−−−−−−−−−−−−−−−−−−− int main ( ) { thread t( f ); while (1) // // lance boucle la fonction f dans un thread infinie { mtx . l o c k ( ) ; // bloque i f ( L . s i z e ()==0) // si le mutex liste vide { c o u t <<" L i s t e Vide . Remplit for ( int j< N; j ++) j =0; la l i s t e :"<< f l u s h ; { c o u t <<j <<","<< f l u s h ; L . push_back ( j ) ; this_thread : : s l e e p _ f o r ( chrono : : m i l l i s e c o n d s } c o u t <<e n d l ; } mtx . u n l o c k ( ) ; // cv . n o t i f y _ o n e ( ) ; // se met en debloque // reveille attente u n i q u e _ l o c k <mutex> l c k ( mtx ) ; cv . w a i t ( l c k ) ; } //......... t . join (); return } 0; le thread endormi {100}); THRE CHAPITRE 20. PLUSIEURS PROGRAMMES EN PARALLÈLE QUI COMMUNIQUENT, AVEC Résultat : Liste Liste Liste Liste ... Vide. Remplit la liste:0,1,2,3,4,5,6,7,8,9, pleine. On vide la liste par le debut:0,1,2,3,4,5,6,7,8,9, Vide. Remplit la liste:0,1,2,3,4,5,6,7,8,9, pleine. On vide la liste par le debut:0,1,2,3,4,5,6,7,8,9, THRE Chapitre 21 Messages MIDI (Musical) avec la librairie sndio La convention sur les messages MIDI (Musical Instrument Digital Interface) entre ordinateurs sont apparus dans les années 80 pour la musique électronique, l'informatique musicale et les synthétiseurs. Référence sur les messages MIDI. Dans ce chapitre nous voyons comment envoyer et/ou recevoir des messages midi depuis un programme en utilisant la librairie sndio. On verra aussi comment utiliser ces messages midi en musique. Conguration préalable 21.1 Installation (à faire la première fois) Suivre les instructions de la Section 19.1. De plus il faut télécharger les chiers suivants et les sauver dans son répertoire : midi.h et midi.cc. 1. Si vous utilisez codeblocks, il faut ajouter ces chiers au projet en faisant dans le menu : Projet/add_les ... 2. Si vous utilisez un Makele, rajouter les chiers au projet selon les explications de la Section 25. 3. Il faut aussi rajouter -lsndio dans les options de compilation (pour codeblocks c'est dans Setting/Compiler/LinkerSettings/OtherLinkerOptions) 21.2 Exemple élémentaire sur l'envoi et reception de messages MIDI Ce premier exemple montre comment envoyer des octets (chires) entre deux programmes via un port midi virtuel de l'ordinateur. En pratique les messages MIDI ont 143 CHAPITRE 21. MESSAGES MIDI (MUSICAL) AVEC LA LIBRAIRIE SNDIO 144 un contenu très particulier qui ont une signication musicale comme début de telle note, changement de volume,.., et dans la Section suivante on précisera comment utiliser ces messages pour l'informatique musicale (reception des notes d'un clavier numérique et envoie de notes à un synthétiseur). Instructions Copier et compiler les programmes test_midi_in.cc et test_midi_out.cc avec le programme Makele donnés ci-dessous. Lancer le programme test_midi_in dans un terminal puis le programme test_midi_out dans un autre terminal. Résultat : Le programme (provenant du test_midi_in se met en attente et lorsqu'il recoit programme test_midi_out) il l'ache à l'écran : un message midi port ouvert. Attente de message .. 1 messages recus. Message recu sur le port=0: 144, 60, 50 Midi::On ferme les ports midi.. Le programme test_midi_out ache : port ouvert. J'ai envoye le message 144, 60, 50 sur le port 0 Midi::On ferme les ports midi.. Code C++ des programmes : Voici le code C++ ainsi que le Makele permet de compiler les deux programmes par l'instruction //===== Programme make all. t e s t _ m i d i _ i n . c c ========== #i n c l u d e <i o s t r e a m > using namespace #i n c l u d e int std ; " midi . h" main ( ) { Midi midi ; //−−− o u v r e un port int // numero p o r t =0; midi de en entree port CHAPITRE 21. 145 MESSAGES MIDI (MUSICAL) AVEC LA LIBRAIRIE SNDIO int r e s = midi . I n i t i a l i s e _ p o r t _ m i d i _ i n ( port ) ; i f ( r e s ==0) c o u t <<"p o r t est encore f e r m e ."<< e n d l ; c o u t <<"p o r t o u v e r t ."<< e n d l ; else //−−− attente de c o u t <<"A t t e n t e messages de message .." < < e n d l ; midi . Attente_Message ( ) ; c o u t <<"M e s s a g e s r e c u s ."<< e n d l ; //−−− on affiche les f o r ( auto message : messages recus m i d i . L_message ) dans // v e c t o r <v e c t o r <u n s i g n e d analyse chacun des messages { int p o r t _ i n =( i n t ) m e s s a g e [ 0 ] ; // message . e r a s e ( message . begin ( ) ) ; octet . Il reste le message midi c o u t <<"M e s s a g e for ( int numero // de enleve port le sur le i =0; i <m e s s a g e . s i z e ( ) ; i ++) c o u t <<e n d l ; } port midi m i d i . Ferme_port_midi_in ( p o r t ) ; return 0; } //===== Programme t e s t _ m i d i _ o u t . c c ========== #i n c l u d e <i o s t r e a m > using namespace #i n c l u d e int std ; " midi . h" main ( ) 1 er p o r t="<<p o r t _ i n <<": "<< f l u s h ; c o u t <<( i n t ) m e s s a g e [ i ] < <" , "<< f l u s h ; //−−− f e r m e entrant standard recu c h a r >> mid CHAPITRE 21. MESSAGES MIDI (MUSICAL) AVEC LA LIBRAIRIE SNDIO 146 { Midi midi ; //−−− o u v r e port int port = 0; int res = midi out midi . I n i t i a l i s e _ p o r t _ m i d i _ o u t ( port ) ; i f ( r e s ==0) { c o u t <<"p o r t est encore f e r m e ."<< e n d l ; exit (1); } else c o u t <<"p o r t //−−−− envoie o u v e r t ."<< e n d l ; message : v e c t o r <u n s i g n e d 144 , 60 , 50 char> message ; m e s s a g e . push_back ( 1 4 4 ) ; m e s s a g e . push_back ( 6 0 ) ; m e s s a g e . push_back ( 5 0 ) ; m i d i . Envoi_Message ( p o r t , c o u t <<"J ' a i //−− ferme envoye les le ports message ) ; message midi 144 ,60 ,50 ouverts m i d i . Ferme_port_midi_out ( p o r t ) ; return 0; } #=== F i c h i e r LIBR= −lm CC =g++ Makefile −l p t h r e a d − l s n d i o − s t d=c++11 − s t d=c++11 #========================================== OBJETS_MIDI_O= test_midi_out . o midi . o sur le port "<<p o r t <<e n d l ; CHAPITRE 21. midi . o : MESSAGES MIDI (MUSICAL) AVEC LA LIBRAIRIE SNDIO midi . cc $ (CC) $ (CC) : −c $ * . cc test_midi_out . cc $ (CFLAGS) test_midi_out : $ (CC) midi . h $ (CFLAGS) test_midi_out . o 147 −c $ * . cc −o $@ bib_fred . h −o $@ $ (OBJETS_MIDI_O) −o test_midi_out $ (OBJETS_MIDI_O) $ ( LIBR ) #========================================== OBJETS_MIDI_I= t e s t _ m i d i _ i n . o midi . o : midi . cc $ (CC) test_midi_in . o $ (CC) test_midi_in : $ (CC) midi . h $ (CFLAGS) : midi . o −c $ * . cc test_midi_in . cc $ (CFLAGS) −c −o $@ bib_fred . h $ * . cc −o $@ $ ( OBJETS_MIDI_I ) −o test_midi_in $ ( OBJETS_MIDI_I ) $ ( LIBR ) #========================== all : 21.3 test_midi_out test_midi_in Exemple d'envoie de message MIDI à un synthétiseur Comme application de l'exemple précédent, le programme gamme.cc suivant envoie des notes à un synthetiseur ou à un instrument électronique et cela joue la gamme chromatique. Pour cela il envoit un premiere message MIDI qui sélectionne le son de l'instrument sur le canal 0, puis envoie une série d'instruction correspondant à jouer une note/ eteindre une note, avec un espace de 0.2 seconde. Le numéro de la note do est 60, ensuite do# = 61, etc... Voir messages midi. CHAPITRE 21. MESSAGES MIDI (MUSICAL) AVEC LA LIBRAIRIE SNDIO Travail préalable Installer un synthétiseur MIDI comme midithru/0 l'écoute du port 0 qui est le port midisyn 148 et le lancer. Il sera alors à dans notre programme. Par exemple on lance midisyn dans une fenetre de commande à part, par l'instruction : midisyn /home/faure/alex_ratchov/patches/midisyn.conf Brancher un piano électrique ou autre instrument midi sur l'ordinateur, et observer le numéro de port par l'instruction amidi -l dans un terminal. Cela donne le numéro de port à mettre dans le programme suivant d'après la liste dans le chier midi.h. Par exemple rmidi/2 (port 2 de l'instruction amidi -l) est sur le port 4. Lancer le programme suivant qui va jouer la gamme chromatique. //===== Programme gamme . c c ========== #i n c l u d e <i o s t r e a m > using namespace #i n c l u d e int std ; " midi . h" main ( ) { Midi midi ; //−−− o u v r e port int port = 0; int res = midi out midi . I n i t i a l i s e _ p o r t _ m i d i _ o u t ( port ) ; i f ( r e s ==0) { c o u t <<"p o r t est encore f e r m e ."<< e n d l ; exit (1); } else c o u t <<"p o r t o u v e r t ."<< e n d l ; //..... int ch = 0 ; int inst // = 0; canal // instrument , m i d i . Program_Change ( p o r t , for ( int { key =60; key <=72; ch , 0: piano inst );// associe k e y++) // montee de la l ' instrument gamme inst chromatique au c CHAPITRE 21. 149 MESSAGES MIDI (MUSICAL) AVEC LA LIBRAIRIE SNDIO //....... int vol joue une note midi . . . . . . . . . . =100; c o u t <<" j o u e n o t e "<<key<<e n d l ; midi . Joue_note ( p o r t , midi . S l e e p ( 2 0 0 ) ; //.... eteind // une ch , key , attente note ch , // message pour jouer une en ms midi midi . Eteind_note ( port , vol ) ; . . . . . . . . . key ) ; // message pour eteindre la } //−− ferme les ports midi ouverts m i d i . Ferme_port_midi_out ( p o r t ) ; return 0; } #====== F i c h i e r M a k e f i l e==================================== OBJETS_MIDI_G= gamme . o midi . o : midi . cc $ (CC) gamme . o : midi . h $ (CFLAGS) −c $ * . cc −o $@ −c $ * . cc −o $@ gamme . c c $ (CC) gamme : midi . o $ (CFLAGS) $ (OBJETS_MIDI_G) $ (CC) −o gamme $ (OBJETS_MIDI_G) $ ( LIBR ) Remarque 21.3.1. La liste des codes des instruments est donnée d'après la convention general midi. Le canal 9 est réservé aux percussions. Mais si vous utilisez le synthetiseur midisyn, pour le moment il n'y a que les possibilités suivantes : code program_change 0 1 2 3 4 33 73 74 75 76 instrument piano guitare guitare guitare guitare bass ute ute ute ute et spécialement sur le canal 9 des percussions : instrument : kick stick snare1 snare2 hihatc hihatf hihato crash1 ride1_bell ride1 key : 36 37 38 40 42 44 46 49 51 53 Le détail des sons est congurable dans les chiers sp patches/midisyn.conf patches/piano/piano.conf CHAPITRE 21. etc... MESSAGES MIDI (MUSICAL) AVEC LA LIBRAIRIE SNDIO 150 On peut par exemple rajouter de nouveaux sons en ajoutant les chiers raw cor- respondant et un chier de conguration associé. //===== Programme gamme . c c ========== #i n c l u d e <i o s t r e a m > using namespace #i n c l u d e int std ; " midi . h" main ( ) { Midi midi ; //−−− o u v r e port int port = 0; int res = midi out midi . I n i t i a l i s e _ p o r t _ m i d i _ o u t ( port ) ; i f ( r e s ==0) { c o u t <<"p o r t est encore f e r m e ."<< e n d l ; exit (1); } else c o u t <<"p o r t o u v e r t ."<< e n d l ; //..... int ch = 0 ; int inst // = 0; canal // instrument , m i d i . Program_Change ( p o r t , for ( int key =60; key <=72; 0: ch , piano inst );// associe k e y++) // montee de la l ' instrument gamme { //....... int vol joue une midi . . . . . . . . . . =100; c o u t <<" j o u e n o t e "<<key<<e n d l ; midi . Joue_note ( p o r t , //... note ch , key , vol ) ; ride midi . Joue_note ( p o r t , 9, 53 , vol ) ; inst chromatique au c CHAPITRE 21. MESSAGES MIDI (MUSICAL) AVEC LA LIBRAIRIE SNDIO midi . S l e e p ( 2 0 0 ) ; //... // attente 151 en ms stick midi . Joue_note ( p o r t , midi . S l e e p ( 2 0 0 ) ; //.... eteind une // 9, 37 , vol ) ; attente en ms note midi . Eteind_note ( port , midi ch , . . . . . . . . . key ) ; } //... splash midi . Joue_note ( p o r t , //−− ferme les ports midi 9, 55 , 100); ouverts m i d i . Ferme_port_midi_out ( p o r t ) ; return 0; } 21.4 Reception de notes midi depuis un piano électrique Comme application de l'exemple précédent, le programme reception_notes.cc suivant ecoute les messages midi et les analyse. Si il reconnait Note on et Note o , il ache les notes identiées. Travail préalable Brancher un piano électrique ou autre instrument midi sur l'ordinateur, et observer le numéro de port par l'instruction amidi -l dans un terminal. Cela donne le numéro de port à mettre dans le programme suivant d'après la liste dans le chier midi.h. Par exemple //===== Programme namespace (port 2 de l'instruction amidi -l) est sur le port 4. r e c e p t i o n _ n o t e s . c c ========== #i n c l u d e <i o s t r e a m > using rmidi/2 std ; CHAPITRE 21. #i n c l u d e int 152 MESSAGES MIDI (MUSICAL) AVEC LA LIBRAIRIE SNDIO " midi . h" main ( ) { Midi midi ; //−−− o u v r e un port int // numero p o r t =0; int midi en de entree port r e s = midi . I n i t i a l i s e _ p o r t _ m i d i _ i n ( port ) ; i f ( r e s ==0) c o u t <<"p o r t est encore f e r m e ."<< e n d l ; c o u t <<"p o r t o u v e r t ."<< e n d l ; else //−−− attente de messages midi . Attente_Message ( ) ; //−−− a n a l y s e f o r ( auto des message messages : recus m i d i . L_message ) { int p o r t _ i n =( i n t ) m e s s a g e [ 0 ] ; // numero message . e r a s e ( message . begin ( ) ) ; octet . Il reste le message midi // de enleve port le entrant 1 er standard i f ( ( ( i n t ) m e s s a g e [ 0 ] ) / 1 6 == 0 x9 && ( ( i n t ) m e s s a g e [ 2 ] ) // note on avec > 0 ) v e l >0 { int ch =( i n t ) m e s s a g e [ 0 ] − 0 x 9 0 ; int k e y =( i n t ) m e s s a g e [ 1 ] ; int v e l =( i n t ) m e s s a g e [ 2 ] ; c o u t <<"Note on recue : p o r t="<<p o r t _ i n <<" c a n a l="<<ch } else // note off i f ( ( ( i n t ) m e s s a g e [ 0 ] ) / 1 6 = = 0 x8 || ( ( ( i n t ) message [ 0 ] ) / 1 CHAPITRE 21. MESSAGES MIDI (MUSICAL) AVEC LA LIBRAIRIE SNDIO 153 { int ch =( i n t ) m e s s a g e [ 0 ] ; i f ( c h /16==0x8 ) ch−= 0 x 8 0 ; else i f ( ch /16==0x9 ) ch−= 0 x 9 0 ; int k e y =( i n t ) m e s s a g e [ 1 ] ; int v e l =( i n t ) m e s s a g e [ 2 ] ; c o u t <<"Note off recue : p o r t="<<p o r t _ i n <<" } } //−−− f e r m e port midi m i d i . Ferme_port_midi_in ( p o r t ) ; return 0; } #========================================== OBJETS_MIDI_R= midi . o : reception_notes . o midi . cc $ (CC) $ (CC) : −o −c −c : −o $@ $ * . cc −o $@ $ (OBJETS_MIDI_R) reception_notes #========================== all $ * . cc reception_notes . cc $ (CFLAGS) reception_notes : $ (CC) midi . h $ (CFLAGS) reception_notes . o midi . o reception_notes $ (OBJETS_MIDI_R) $ ( LIBR ) c a n a l="<<c Chapitre 22 Convertir un programme C++ en Javascript avec emscripten Le langage javascript est un langage qui peut tourner sur toutes les plateformes (Windows, Mac, Android etc..) et qui est bien adapté au interfaces du web. Voici un tutorial. Il est possible de convertir automatiquement un programme C++ en javascript à l'aide du programme emscripten. On convertit un programme c++ test.cc avec emcc test.cc -o test.html On lance le programme avec firefox test.html 154 en javascript encapsulé dans du html Chapter 23 Autres librairies conseillées (?) Trinilos Math Kernel Library 23.1 algèbre linéaire SimuNova Eigen pour l'algèbre linéaire. 23.2 Images, vidéos, graphisme et boites de dialogues FLTK pour les boites de dialogues et le graphisme. Bibliothèque légère et performante. QT bibliothèque très générale, performante, multiplateformes mais assez complexe. OpenCV pour le traitement d'image et de vidéos (CV signie Computer-Vision). Un Tutoriel. Livre conseillé : OpenCV by example de P. Joshi, D.M. Escriva et V. Godoy. 155 Chapitre 24 Le traitement d'images et de vidéos avec la librairie OpenCV 24.1 Commande de compilation Avant d'écrire un programme utilisant les librairies de OpenCV, il faut, une fois pour toutes, Dans l'éditeur codeblocks Dans Settings/Compiler/Linker_Settings/Other_linker_Options, rajouter : ` pkg− c o n f i g −− c f l a g s −− l i b s opencv ` Remarque 24.1.1. Si vous n'utilisez pas codeblocks, mais Makele par exemple, la compilation en ligne de commande du programme main.cpp pour donner l'executable main est : g++ main . cpp 24.2 −o main −g − s t d=c++11 ` pkg− c o n f i g −− c f l a g s −− l i b s opencv ` Lecture et ecriture de chiers images Les commentaires expliquent les fonctions. Il faut avoir le chier Image.jpg dans le répertoire du programme. #i n c l u d e <i o s t r e a m > using namespace std ; #i n c l u d e < s t r i n g > #i n c l u d e <o p e n c v 2 / c o r e / c o r e . hpp> 156 CHAPITRE 24. LE TRAITEMENT D'IMAGES ET DE VIDÉOS AVEC LA LIBRAIRIE OPENCV157 #i n c l u d e <o p e n c v 2 / h i g h g u i / h i g h g u i . hpp> using namespace cv ; //========================= int main ( i n t argc , char ** a r g v ) { //..... Lecture des images −> img Mat img= i m r e a d ( " Image . j p g " ) ; //...... Ecriture d ' une i m w r i t e ( " Image2 . j p g " , //..... Lecture d ' un int x=img . c o l s / 2 ; int y=img . r o w s / 2 ; Vec3b img −> fichier Image2 . j p g pixel // selectionne point (x , y) au centre de l ' image p= img . a t<Vec3b >(x , y ) ; c o u t << " La //..... valeur dessin de imshow ( " Image " , //..... int image : img ) ; Attend du en x="<<x<<", y="<<y<<" est ( B , G, R ) : ( " << ( i n t ) p l ' image img ) ; qu ' une c = waitKey ( 0 ) ; return pixel // touche soit rem : montre appuyee . le dessin . Renvoit le code de la 0; } Resultat : Ache deux images : une en couleur et l'autre en noir et blanc. Pixel value (B,G,R): (3,7,12) 24.2.0.1 Fonctions utiles sur les images Copie d'une image : Mat image2 = image1; ne fait pas une copie des images mais seuleimage1 et image2 vont désigner la même image). dupliquer l'image il faut faire image1.copyTo(image2); La commande ment de l'adresse (c'est à dire que Pour touche . O CHAPITRE 24. LE TRAITEMENT D'IMAGES ET DE VIDÉOS AVEC LA LIBRAIRIE OPENCV158 Recadrage d'une image : resize(frame, frame, Size(), scalingFactor, scalingFactor, INTER_AREA) ; Mat img= imread("Image.jpg", 0) ; // 0 : convertit en gris 24.3 Utilisation de la souris Le programme suivant montre comment utiliser la souris. Dans cet exemple on selectionne une partie de l'image avec clic gauche (down/up) et on revient à l'image de départ avec clic droit (down). Le codesuivant utilise le chier image M2.png à télécharger dans le même répertoire. #i n c l u d e <i o s t r e a m > using namespace std ; #i n c l u d e < s t r i n g > CHAPITRE 24. LE TRAITEMENT D'IMAGES ET DE VIDÉOS AVEC LA LIBRAIRIE OPENCV159 #i n c l u d e <o p e n c v 2 / c o r e / c o r e . hpp> #i n c l u d e <o p e n c v 2 / h i g h g u i / h i g h g u i . hpp> using namespace cv ; //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− Mat img , int image , roi ; etat_select = 0;// Rect selection ; Point //−−−− static origin ; // // fonction void 0: selection taille point de appelee onMouse ( de int la la de souris event , faite , 1: selection en cours , 2: sele selection depart si pas int la selection est x, actionnee int y, int , void * ) { // c o u t <<"e v e n t="<<e v e n t <<" x="<<x<<" y="<<y<<e n d l ; //−−− switch ( si action event du clic souris ) { case 1: // clic gauche down i f ( e t a t _ s e l e c t ==0) { origin = Point ( x , y ) ; selection = Rect ( x , y , 0 , 0 ) ; etat_select = 1; } break ; case 4: if ( // clic gauche up e t a t _ s e l e c t ==1 && s e l e c t i o n . w i d t h > 0 && s e l e c t i o n . h e i g h t > 0 ) { etat_select =2; bitwise_not ( roi , imshow ( " Image " , } break ; // selection roi ); roi ); // finie inversion de roi ( dans imag CHAPITRE 24. LE TRAITEMENT D'IMAGES ET DE VIDÉOS AVEC LA LIBRAIRIE OPENCV160 case 2: // c l i c droit down i f ( e t a t _ s e l e c t ==2) { etat_select = 0; imshow ( " Image " , img ) ; } } //−−− if ( si deplacement e t a t _ s e l e c t == 1 souris , traitement de l ' image ) { s e l e c t i o n . x = MIN( x , origin .x ); s e l e c t i o n . y = MIN( y , origin .y ); − origin .x ); : : abs ( y − o r i g i n . y ) ; s e l e c t i o n . width = s t d : : abs ( x s e l e c t i o n . height = std s e l e c t i o n &= R e c t ( 0 , 0, img . c o l s , img . r o w s ) ; // pour ne i f ( s e l e c t i o n . w i d t h > 0 && s e l e c t i o n . h e i g h t > 0 pas depasser ) { img . copyTo ( i m a g e ) ; roi = Mat ( image , bitwise_not ( roi , imshow ( " Image " , } } } //========================= int main ( i n t argc , char ** a r g v ) { //..... Lecture des images img= i m r e a d ( "M2 . png " ) ; selection ); roi ); image ) ; // // // la matrice inversion montre de image roi roi est ( dans avec sa un imag selec CHAPITRE 24. LE TRAITEMENT D'IMAGES ET DE VIDÉOS AVEC LA LIBRAIRIE OPENCV161 //..... Affiche namedWindow ( l ' image " Image " , setMouseCallback ( imshow ( " Image " , 1 a l ' ecran ); " Image " , onMouse , 0 ); // associe la fonction de img ) ; waitKey ( 0 ) ; return 0; } 24.4 Lecture et écriture d'un chier video ou de la webcam #i n c l u d e <i o s t r e a m > #i n c l u d e < s t r i n g > #i n c l u d e <s s t r e a m > using namespace std ; #i n c l u d e " o p e n c v 2 / c o r e . hpp " #i n c l u d e " o p e n c v 2 / h i g h g u i . hpp " using namespace cv ; //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− int main ( int argc , const c h a r ** argv ) { //.... ouverture String nom = " v i d e o _ p e n d u l e . mp4 " ; VideoCapture du fichier video ou webcam // fichier cap ; c a p . o p e n ( nom ) ; // c a p . o p e n ( 0 ) ; / / ouvre i f ( ! cap . isOpened ( ) ) return / / . . . ouverture // la webcam . si erreur d ' ouverture − 1; d ' un fichier video en ecriture video lecture de CHAPITRE 24. LE TRAITEMENT D'IMAGES ET DE VIDÉOS AVEC LA LIBRAIRIE OPENCV162 String Size nom2= " v i d e o _ p e n d u l e 2 . a v i " ; ( out video ( nom2 , CV_FOURCC( ' P ' , ' I ' , 'M' , ' 1 ' ) , ! out . isOpened ( ) boucle 20 , frameSize , static_ true ) ; ) − 1; return //.... fichier f r a m e S i z e ( s t a t i c _ c a s t <i n t >( c a p . g e t (CV_CAP_PROP_FRAME_WIDTH) ) , VideoWriter if // infinie while ( true ) { Mat img ; c a p >> img ; // extrait imshow ( " V i d e o " , img ) ; o u t . w r i t e ( img ) ; // la // ecrit i f ( w a i t K e y ( 3 0 ) >= 0 ) prochaine dessin de l ' image break ; // image img dans la dans fichier Montre dessin . fenetre Attente Video de 30 ms } cap . r e l e a s e ( ) ; return // l i b e r e la memoire allouee 0; } Remarque 24.4.1. La commande waitKey(0) permettrait de faire avancer la video image par image. 24.5 Détection d'objet par leur couleur Voici un exemple de programme qui lit une image et selectionne les objets bleus. Pour acher les images intermediares, il faut décommenter les lignes correspondantes. Voici une documentation sur les formats de couleurs. On utilise le format HSV (voir H ∈ [0, 1] est la H ∈ [0.5, 0.7] est une palette de bleus. S ∈ [0, 1] est la saturation entre S = 0 (blanc) et S = 1 (couleur vive). V ∈ [0, 1] est la luminosité entre V = 0 (noir) et V = 1 aussi HSV sur wikipedia) qui est proche de la perception humaine : teinte de la couleur, par exemple Sauver l'image objet_bleu.jpg ci-dessous dans le répertoire du programme. et arr CHAPITRE 24. LE TRAITEMENT D'IMAGES ET DE VIDÉOS AVEC LA LIBRAIRIE OPENCV163 Résultat du programme : CHAPITRE 24. LE TRAITEMENT D'IMAGES ET DE VIDÉOS AVEC LA LIBRAIRIE OPENCV164 #include <iostream>using namespace std;#include <string> #include <opencv2/core/core.hpp 24.6 Détection et suivit d'objet de couleur avec l'algorithme CamShift Voici des informations, sur les algorithmes MeanShift et CamShift , ou sur wikipedia. CHAPITRE 24. LE TRAITEMENT D'IMAGES ET DE VIDÉOS AVEC LA LIBRAIRIE OPENCV165 Le code ci-dessous est tiré du site web de opencv. #i n c l u d e " o p e n c v 2 / v i d e o / t r a c k i n g . hpp " #i n c l u d e " o p e n c v 2 / i m g p r o c / i m g p r o c . hpp " #i n c l u d e " o p e n c v 2 / h i g h g u i / h i g h g u i . hpp " #i n c l u d e <i o s t r e a m > #i n c l u d e <c t y p e . h> using namespace cv ; using namespace std ; CHAPITRE 24. LE TRAITEMENT D'IMAGES ET DE VIDÉOS AVEC LA LIBRAIRIE OPENCV166 //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− Mat image ; bool backprojMode = bool showHist = bool selectObject = int false ; false ; selection ; Point origin ; //−−−− static Si // // //0: taille point de l ' utilisateur void onMouse ( // true : true : false ; trackObject = 0; Rect // // de la si on montre l ' image seuillee l ' histogramme est selection depart event , on en train faite . − 1: de selectionner selection faite m a i s à selection de selection int si montre true : pas de on un int la selection rectangle x, int y, −> int , selection void * et le flag trackO ) { // c o u t <<"e v e n t="<<e v e n t <<" x="<<x<<" y="<<y<<e n d l ; if ( selectObject ) { s e l e c t i o n . x = MIN( x , origin .x ); s e l e c t i o n . y = MIN( y , origin .y ); − origin .x ); : : abs ( y − o r i g i n . y ) ; s e l e c t i o n . width = s t d : : abs ( x s e l e c t i o n . height = std s e l e c t i o n &= R e c t ( 0 , 0, image . c o l s , image . rows ) ; // } switch ( event ) { c o u t <<"e v e n t="<<e v e n t <<e n d l ; c a s e CV_EVENT_LBUTTONDOWN: origin // coin de depart = Point ( x , y ) ; selection = Rect ( x , y , 0 , 0 ) ; selectObject = true ; break ; c a s e CV_EVENT_LBUTTONUP: selectObject = if ( false ; s e l e c t i o n . w i d t h > 0 && s e l e c t i o n . h e i g h t > 0 trackObject = break ; } − 1; ) pour ne pas depas CHAPITRE 24. LE TRAITEMENT D'IMAGES ET DE VIDÉOS AVEC LA LIBRAIRIE OPENCV167 } //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− static void help () { c o u t << "\ n T h i s "You select " Usage : " is a demo a color c o u t << "\ n \ nHot "\ t c "\ t b "\ t h "\ t p "To shows objects mean− s h i f t such as your based face t r a c k i n g \n" and it − − − − − keys : quit stop the p rog ram \ n " t r a c k i n g \n" t o / from show / h i d e pause \n" the switch \n " ; backprojection object view \n" h i s t o g r a m \n" v i d e o \n" initialize tracking , select the object with mouse \ n " ; } ///−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− int main ( int argc , const c h a r ** argv ) { help ( ) ; VideoCapture Rect int cap ; trackWindow ; hsize = 16; float hranges [ ] const float String // * // pour if ( histogramme = {0 ,180}; phranges = hranges ; v i d e o F i l e= " p e n d u l e _ j a u n e . mp4 " ; cap . open ( v i d e o F i l e ) ; cap . open ( 0 ) ; // ouvre ! cap . isOpened ( ) // ouvre le // fichier fichier video video camera ) { c o u t << " *** Could return } tracks \n" . / camshiftdemo "\ tESC that − 1; not initialize capturing . . . * * * \ n "; i t . \ n" CHAPITRE 24. LE TRAITEMENT D'IMAGES ET DE VIDÉOS AVEC LA LIBRAIRIE OPENCV168 //... initialise les fenetres namedWindow ( " Histogram " , namedWindow ( " C a m S h i f t Demo " , setMouseCallback ( //.... int pose les vmin = 1 0 , 0 ); 0 ); " C a m S h i f t Demo " , onMouse , 0 ); // associe vmax = 2 5 6 , createTrackbar ( "Vmax" , " C a m S h i f t Demo " , &vmax , 256 , 0 ); createTrackbar ( " Smin " , " C a m S h i f t Demo " , &smin , 256 , 0 ); Mat hue , hsv , paused = d smin = 3 0 ; "Vmin " , frame , fonction sliders createTrackbar ( bool la " C a m S h i f t Demo " , &vmin , mask , hist , 256 , 0 ); h i s t i m g = Mat : : z e r o s ( 2 0 0 , 3 2 0 , CV_8UC3 ) , false ; //−−−−−− b o u c l e infinie −−−−−−−−−− for ( ; ; ) { if ( ! paused ) { c a p >> f r a m e ; if ( f r a m e . empty ( ) ) break ; } f r a m e . copyTo ( i m a g e ) ; if ( ! paused // −> image ) { c v t C o l o r ( image , hsv , COLOR_BGR2HSV ) ; if ( ) trackObject // convertit −> hsv { int // _vmin = vmin , seuillage i n R a n g e ( hsv , // imshow ( −> _vmax = vmax ; mask ( fenetre Scalar (0 , " mask " , smin , mask ) ; de 0 et 1) MIN( _vmin , _vmax ) ) , Scalar (180 , 2 CHAPITRE 24. LE TRAITEMENT D'IMAGES ET DE VIDÉOS AVEC LA LIBRAIRIE OPENCV169 // Mix int the ch [ ] specified = {0 , channels 0}; hue . c r e a t e ( h s v . s i z e ( ) , m i x C h a n n e l s (& hsv , // on if ( traite la hsv . depth ( ) ) ; 1 , &hue , selection trackObject < 0 1, −> ch , // 1); −> // hue : −> nouvelle copie hsv fene dans hu interest (r histogramme ) { c o u t <<" i c i "<< e n d l ; // Create Mat // imshow ( // imshow ( " hue " , " mask " , images r o i ( hue , based on selection ) , selected regions of m a s k r o i ( mask , selection ); and it hue ) ; mask ) ; // w a i t K e y ( 0 ) ; // Compute the histogram c a l c H i s t (& r o i , 1, normalize ( hist , trackWindow = 0, normalize maskroi , hist , 0, hist , −> tableau 1 , &h s i z e , hist &p h r a n g e s ) 2 5 5 , CV_MINMAX) ; selection ; trackObject = 1; histimg = Scalar : : a l l ( 0 ) ; int binW = h i s t i m g . c o l s Mat buf ( 1 , for ( int i hsize , = 0; / hsize ; CV_8UC3 ) ; i < hsize ; i++ ) b u f . a t<Vec3b >( i ) = Vec3b ( s a t u r a t e _ c a s t <u c h a r >( i c v t C o l o r ( buf , buf , // rectangles dessin for ( int des i = 0; *180./ CV_HSV2BGR ) ; i < de hsize ; l ' histogramme sur la fenetre i++ ) { int v a l = s a t u r a t e _ c a s t <i n t >( h i s t . a t<f l o a t >( i ) * h i s t i m rectangle ( histimg , Point ( i * binW , h i s t i m g . r o w s ) , Point } } // Compute the histogram c a l c B a c k P r o j e c t (&hue , 1, backprojection 0, hist , backproj , &p h r a n g e s ) ; // −> CHAPITRE 24. LE TRAITEMENT D'IMAGES ET DE VIDÉOS AVEC LA LIBRAIRIE OPENCV170 b a c k p r o j &= mask ; //... trackwindow , RotatedRect Check if ( ameliore backproj backproj −> trackBox trackBox = CamShift ( b a c k p r o j , T e r m C r i t e r i a ( CV_TERMCRIT_EPS | // // if CV_TERMCRIT_ITER, the area of 10 , 1 )); is too small sure the trackingRect trackingRect trackWindow . a r e a ( ) <= 1 trackWindow , ) { // Use int an cols offset value to = backproj . cols , make r o w s = b a c k p r o j . rows , trackWindow = R e c t ( trackWindow . x − r , trackWindow . x + r , Rect ( 0 , 0, cols , has r = (MIN( trackWindow . y − r , trackWindow . y + r ) & rows ) ; } if ( backprojMode cvtColor ( // draw backproj , rotated Point2f ) image , COLOR_GRAY2BGR ) ; rectangle trackBox rect_points [ 4 ] ; trackBox . p o i n t s ( rect_points ); RNG r n g ( 1 2 3 4 5 ) ; Scalar for ( color = Scalar ( int j = 0; line ( // Draw ellipse ( image , the j < } if ( trackObject < 0 paused = false ; top trackBox , } else on ) 255) , rng . uniform ( 0 , 2 5 5 j++ ) rect_points [ j ] , ellipse image , 4; rng . uniform ( 0 , of the r e c t _ p o i n t s [ ( j +1)%4] , image Scalar (0 ,0 ,255) , 3 , CV_AA ) ; color , CHAPITRE 24. LE TRAITEMENT D'IMAGES ET DE VIDÉOS AVEC LA LIBRAIRIE OPENCV171 // Apply if ( the ' negative ' effect on the Mat r o i ( image , selection ); bitwise_not ( roi , roi ); } imshow ( " C a m S h i f t Demo " , imshow ( " Histogram " , char if ( image histimg ); ); c = ( c h a r ) waitKey ( 1 0 ) ; c == 27 ) // esc . break ; switch ( c ) { case 'b ' : backprojMode = ! backprojMode ; break ; case 'c ' : trackObject = 0; histimg = Scalar : : a l l ( 0 ) ; break ; case 'h ' : showHist = if ( ! showHist ; ! showHist ) destroyWindow ( " Histogram " ); else namedWindow ( break ; case 'p ' : paused = break ; default : ; } } } region of interest s e l e c t O b j e c t && s e l e c t i o n . w i d t h > 0 && s e l e c t i o n . h e i g h t > 0 { return selected 0; ! paused ; " Histogram " , 1 ); ) Chapitre 25 Gestion de projets avec chiers .h, Makele, Git et GitHub 25.1 Projet avec plusieurs chiers, chier .h Si on developpe un projet il est parfois utile d'écrire le code c++ dans diérents chiers. D'une part pour l'organisation, pour la réutilisation ultérieure, etc.. Voici un projet informatique en C++, très simple (et sans trop de sens) qui est fait de 3 chiers : un chier main.cc qui contient la fonction main() par laquelle commence le programme. un chier autres.cc qui contient une classe Vect et une fonction f utilisées dans la fonction main. un chier main.h qui contient les déclarations de la classe Vect et de la fonction f. Le suxe .h signie header (comme entêtes). 25.1.0.1 Résultat du programme : Norme de v=1 3*3=9 25.1.0.2 //== Code c++ fichier main . c c ============= #i n c l u d e <i o s t r e a m > using namespace #i n c l u d e int std ; " main . h " main ( ) 172 CHAPITRE 25. GESTION DE PROJETS AVEC FICHIERS .H, MAKEFILE, GIT ET GITHUB173 { Vect v; v . x =1; c o u t <<"Norme de v="<<v . Norme()<< e n d l ; c o u t <<"3*3="<< f (3)<< e n d l ; return 0; } Remarque 25.1.1. Au début du chier main.cc, l'instruction #include "main.h" a pour eet d'inclure le contenu du chier main.h à cet endroit. Il y a les déclarations (mais pas le code) de la classe Vect et de la fonction f qui sont utilisées dans la fonction main(). //== fichier #i n c l u d e a u t r e s . c c ============= " main . h " #i n c l u d e <math . h> //−−− c o n t e n u du constructeur Vect : : Vect ( ) { x =0; y =0; z =0; } //−−−−− c o n t e n u double de la fonction Norme V e c t : : Norme ( ) { s q r t ( x * x+y * y+z * z ) ; return } //−−− c o d e double de la fonction f ( double x) return x*x ; f { } Remarque 25.1.2. Le chier autres.cc contient le code des fonctions membres de la classe Vect et le code de la fonction f(). Noter la syntaxe particulière Vect : :Norme() qui signie fonction Norme de la classe Vect. //== #i f fichier main . h ============= ! d e f i n e d (__MAIN_H) #d e f i n e __MAIN_H //−−−−−− d ' une classe CHAPITRE 25. GESTION DE PROJETS AVEC FICHIERS .H, MAKEFILE, GIT ET GITHUB174 class Vect { public : double x,y,z; Vect ( ) ; double // constructeur Norme ( ) ; }; // −−−−− double declaration f ( double d ' une fonction x); #e n d i f Remarque 25.1.3. Le chier main.h contient les déclarations (et non pas le code) de la classe Vect et de la fonction f(). Les lignes particulières : #if !dened(__MAIN_H) #dene __MAIN_H ... #endif ne sont pas nécessaires ici mais sont souvent utiles : ce sont des instructions du préprocesseur c'est à dire que au moment de la compilation, si le compilateur voit le code intermédiaire ... la première fois, il le considère (et met la variable __MAIN_H à 1). Les fois suivantes, il ne considère par cette partie de code. L'intérêt de cela est que si le chier main.h se trouve répété à cause d'inclusions multiples (par exemple le chier A.cc inclut main.h et B.h, et le chier B.h inclut main.h), alors le code n'est écrit qu'une seule fois, ce qui évite une erreur de code déjà déclaré. 25.1.0.3 Commandes de compilation g++ -c main.cc -o main.o g++ -c autres.cc -o autres.o g++ main.o autres.o -o main Remarque 25.1.4. Le première ligne signie on compile le chier c++ main.cc et on fabrique un chier en langage machine main.o. De même pour la deuxièmre ligne. La troisième ligne signie à partir des hciers main.o et autres.o on fabrique le chier exécutable nal main. CHAPITRE 25. GESTION DE PROJETS AVEC FICHIERS .H, MAKEFILE, GIT ET GITHUB175 25.2 Un programme Makele pour gérer la compilation Dans l'exemple de la section 25.1.0.3, si les chiers sont importants et si on eectue main.cc, on souhaiterait de pas refaire la -c autres.cc -o autres.o. Le programme Makefile une modication du code que dans le chier commande de compilation g++ permet de gérer cela automatiquement (il regarde les dates de modication des chiers). # −−− fichier main . o : main . c c g++ autres . o : : −c main . h main . c c autres . cc g++ final Makefile −c main . o −o main . o main . h autres . cc −o autres . o autres . o g++ main . o autres . o −o main Remarque 25.2.1. Attention le décalage en début de ligne est une tabulation. Commande de compilation on écrit : make final résultat : g++ -c main.cc -o main.o g++ -c autres.cc -o autres.o g++ main.o autres.o -o main Commentaires : Dans le chier Makele, les deux lignes main.o: main.cc main.h g++ -c main.cc -o main.o signient que si on souhaite main.o, cela dépend des chiers main.cc et main.h, g++ -c main.cc et si un de ces derniers a été modié il faut éxécuter la commande -o main.o la commande make nal signie que l'on souhaite final. Cela a pour eet d'enclencher les commandes de compilation nécessaires. Essayer par exemple de modier main.cc et refaites make final. g++ -c main.cc -o main.o g++ main.o autres.o -o main seulement le chier Le résultat est : CHAPITRE 25. GESTION DE PROJETS AVEC FICHIERS .H, MAKEFILE, GIT ET GITHUB176 25.2.1 Un programme Makele plus pratique Le programme suivant est totalement équivalent au précédent mais il est plus pratique car on a placé des instructions de compilation dans des variables (en lettres majuscules). Ainsi si vous rajoutez des librairies, il sut de la faire à un seul endroit. De plus grâce au symbols $@ et $* on évite des réécritures. # # −−− fichier .... librairies −lm LIBR = # ... Makefile instructions CC =g++ autres . o : main . h −c $ * . cc autres . cc $ (CC) : autres . o main . c c $ (CC) final compilation − s t d=c++11 OBJETS= main . o main . o : de −c main . o $ (CC) −o $@ main . h $ * . cc −o $@ −o main autres . o $ (OBJETS) $ ( LIBR ) clean : rm *. o Remarque 25.2.2. Par exemple dans ce tutorial, on utilise souvent beaucoup de librairies et au total on écrira : LIBR=$(shell root-config --libs) $(shell root-config --glibs) -lm -lpthread -lsndio -larmadillo -std=c++11 -lboost_system CC =g++ -std=c++11 $(shell root-config --cflags) # compilateur On a rajouté l'entrée clean qui permet d'eacer tous les chiers en langage machine .o par la commande 25.3 make clean Gestion de versions d'un projet avec Git Le logiciel Git permet de gérer diérentes versions d'un projet. Il se souvient de l'historique. On lui indique une liste de chiers (le dépot) et régulièrement on lui demande de faire une sauvegarde (par l'instruction commit) CHAPITRE 25. GESTION DE PROJETS AVEC FICHIERS .H, MAKEFILE, GIT ET GITHUB177 25.3.0.1 Utilisation de Git sur son ordinateur ]La premiere fois : Dans le répertoire de travail, taper git init Cela initialise un répertoire .git/ Ecrire (en mettant votre nom et adresse) : git config --global user.name "Frederic Faure" git config --global user.email [email protected] Cela modie le chier ~/.gitconfig A faire au cours du projet : git status git add chier.txt ou git add '*.cc' ou git add * pour ajouter un (des) chier(s) :montre l'état des chiers par rapport au dépot. au dépot git commit -a ou git commit -a -m 'titre' fait une sauvegarde des chiers du dépot avec le titre indiqué git log pour voir l'historique des opérations git rm chier :enleve un chier du dépot git mv le1 le2 : deplace ou change le chier. git di pour lire les dierences Dans le chier 25.4 .gitignore il y a les extensions et repertoires à ignorer. Collaboration et partage de projets sur internet avec GitHub Si vous développez un projet avec des collaborateurs, vous pouvez déposer (gratuitement) votre projet sur le site github (par exemple). Au départ il faut vous créer un compte sur github qui va héberger votre projet. ]Pour poser/ retirer l'archive sur le site web de github : Si votre projet s'appelle projet, et votre compte s'appelle fdrfaure Commandes : git remote add projet https://github.com/fdrfaure/projet.git Cela associe votre projet local au dépot sur le site web git push -u expanding_map master Cela dépose les dernières modications de votre projet sur le site web git pull expanding_map master Cela permet de récupérer les dernières modications (posées par les collaborateurs) qui sont sur le site web. CHAPITRE 25. GESTION DE PROJETS AVEC FICHIERS .H, MAKEFILE, GIT ET GITHUB178 git clone https://github.com/fdrfaure/projet.git repertoire_local Cela copie le dépot du site web sur votre répertoire local. (a faire une premiere fois pour copier un projet) Bibliography 179